An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at new-directions 211 lines 5.8 kB view raw
1/** 2 * Typed HTTP API client for Rustagent V2 daemon. 3 * Wraps fetch() calls with proper error handling and type safety. 4 */ 5 6import type { 7 GraphNode, 8 GraphEdge, 9 ProjectResponse, 10 Session, 11 ActiveAgent, 12 NodeWithEdges, 13 GoalTree, 14 DecisionHistory, 15 ExportResult, 16 ImportResult, 17 DiffResult, 18 CreateProjectRequest, 19 CreateGoalRequest, 20 UpdateNodeRequest, 21 CreateChildRequest, 22 CreateEdgeRequest, 23 SearchRequest, 24 ImportRequest, 25} from '../types'; 26 27/** 28 * API error thrown when a request fails. 29 */ 30export class ApiError extends Error { 31 constructor( 32 public readonly status: number, 33 message: string 34 ) { 35 super(message); 36 this.name = 'ApiError'; 37 } 38} 39 40/** 41 * Configurable HTTP API client. 42 * Base URL defaults to empty string for same-origin requests (via Vite proxy in dev). 43 */ 44export class ApiClient { 45 constructor(private readonly baseUrl: string = '') {} 46 47 /** 48 * Generic request method with error handling. 49 */ 50 private async request<T>( 51 method: string, 52 path: string, 53 body?: unknown 54 ): Promise<T> { 55 const opts: RequestInit = { 56 method, 57 headers: body ? { 'Content-Type': 'application/json' } : {}, 58 body: body ? JSON.stringify(body) : undefined, 59 }; 60 61 const res = await fetch(`${this.baseUrl}${path}`, opts); 62 63 if (!res.ok) { 64 const text = await res.text(); 65 throw new ApiError(res.status, text); 66 } 67 68 if (res.status === 204) { 69 return undefined as T; 70 } 71 72 return res.json(); 73 } 74 75 // ============ Projects ============ 76 77 async listProjects(): Promise<Array<ProjectResponse>> { 78 return this.request('GET', '/api/projects'); 79 } 80 81 async createProject(req: CreateProjectRequest): Promise<ProjectResponse> { 82 return this.request('POST', '/api/projects', req); 83 } 84 85 async getProject(id: string): Promise<ProjectResponse> { 86 return this.request('GET', `/api/projects/${id}`); 87 } 88 89 async deleteProject(id: string): Promise<void> { 90 return this.request('DELETE', `/api/projects/${id}`); 91 } 92 93 // ============ Goals ============ 94 95 async listGoals(projectId: string): Promise<Array<GraphNode>> { 96 return this.request('GET', `/api/projects/${projectId}/goals`); 97 } 98 99 async createGoal(projectId: string, req: CreateGoalRequest): Promise<GraphNode> { 100 return this.request('POST', `/api/projects/${projectId}/goals`, req); 101 } 102 103 // ============ Nodes ============ 104 105 async getNode(id: string): Promise<NodeWithEdges> { 106 return this.request('GET', `/api/nodes/${id}`); 107 } 108 109 async updateNode(id: string, req: UpdateNodeRequest): Promise<GraphNode> { 110 return this.request('PATCH', `/api/nodes/${id}`, req); 111 } 112 113 async createChild(parentId: string, req: CreateChildRequest): Promise<GraphNode> { 114 return this.request('POST', `/api/nodes/${parentId}/children`, req); 115 } 116 117 // ============ Edges ============ 118 119 async createEdge(req: CreateEdgeRequest): Promise<GraphEdge> { 120 return this.request('POST', '/api/edges', req); 121 } 122 123 async deleteEdge(id: string): Promise<void> { 124 return this.request('DELETE', `/api/edges/${id}`); 125 } 126 127 // ============ Goal Tree and Task Views ============ 128 129 async getGoalTree(goalId: string): Promise<GoalTree> { 130 return this.request('GET', `/api/goals/${goalId}/tree`); 131 } 132 133 async listTasks(goalId: string): Promise<Array<GraphNode>> { 134 return this.request('GET', `/api/goals/${goalId}/tasks`); 135 } 136 137 async listReadyTasks(goalId: string): Promise<Array<GraphNode>> { 138 return this.request('GET', `/api/goals/${goalId}/tasks/ready`); 139 } 140 141 async getNextTask(goalId: string): Promise<GraphNode | null> { 142 return this.request('GET', `/api/goals/${goalId}/tasks/next`); 143 } 144 145 // ============ Decisions ============ 146 147 async listDecisions(projectId: string): Promise<Array<GraphNode>> { 148 return this.request('GET', `/api/projects/${projectId}/decisions`); 149 } 150 151 async getDecisionHistory(projectId: string): Promise<DecisionHistory> { 152 return this.request('GET', `/api/projects/${projectId}/decisions/history`); 153 } 154 155 async exportDecisions(projectId: string): Promise<Array<string>> { 156 return this.request('POST', `/api/projects/${projectId}/decisions/export`); 157 } 158 159 // ============ Graph Import/Export ============ 160 161 async exportAllGoals(projectId: string): Promise<Array<ExportResult>> { 162 return this.request('GET', `/api/projects/${projectId}/graph/export`); 163 } 164 165 async exportGoal(goalId: string): Promise<ExportResult> { 166 return this.request('GET', `/api/goals/${goalId}/export`); 167 } 168 169 async importGraph(projectId: string, req: ImportRequest): Promise<ImportResult> { 170 return this.request('POST', `/api/projects/${projectId}/graph/import`, req); 171 } 172 173 async diffGraph(projectId: string, req: ImportRequest): Promise<DiffResult> { 174 return this.request('POST', `/api/projects/${projectId}/graph/diff`, req); 175 } 176 177 // ============ Sessions ============ 178 179 async listSessions(goalId: string): Promise<Array<Session>> { 180 return this.request('GET', `/api/goals/${goalId}/sessions`); 181 } 182 183 async getSession(id: string): Promise<Session> { 184 return this.request('GET', `/api/sessions/${id}`); 185 } 186 187 // ============ Search ============ 188 189 async searchNodes(projectId: string, req: SearchRequest): Promise<Array<GraphNode>> { 190 return this.request('POST', `/api/projects/${projectId}/search`, req); 191 } 192 193 // ============ Agents ============ 194 195 async listAgents(goalId: string): Promise<Array<ActiveAgent>> { 196 return this.request('GET', `/api/goals/${goalId}/agents`); 197 } 198 199 // ============ Health ============ 200 201 async healthCheck(): Promise<{ status: string }> { 202 return this.request('GET', '/api/health'); 203 } 204} 205 206/** 207 * Singleton-style factory for creating an API client instance. 208 */ 209export function createApiClient(baseUrl?: string): ApiClient { 210 return new ApiClient(baseUrl); 211}