/** * Typed HTTP API client for Rustagent V2 daemon. * Wraps fetch() calls with proper error handling and type safety. */ import type { GraphNode, GraphEdge, ProjectResponse, Session, ActiveAgent, NodeWithEdges, GoalTree, DecisionHistory, ExportResult, ImportResult, DiffResult, CreateProjectRequest, CreateGoalRequest, UpdateNodeRequest, CreateChildRequest, CreateEdgeRequest, SearchRequest, ImportRequest, } from '../types'; /** * API error thrown when a request fails. */ export class ApiError extends Error { constructor( public readonly status: number, message: string ) { super(message); this.name = 'ApiError'; } } /** * Configurable HTTP API client. * Base URL defaults to empty string for same-origin requests (via Vite proxy in dev). */ export class ApiClient { constructor(private readonly baseUrl: string = '') {} /** * Generic request method with error handling. */ private async request( method: string, path: string, body?: unknown ): Promise { const opts: RequestInit = { method, headers: body ? { 'Content-Type': 'application/json' } : {}, body: body ? JSON.stringify(body) : undefined, }; const res = await fetch(`${this.baseUrl}${path}`, opts); if (!res.ok) { const text = await res.text(); throw new ApiError(res.status, text); } if (res.status === 204) { return undefined as T; } return res.json(); } // ============ Projects ============ async listProjects(): Promise> { return this.request('GET', '/api/projects'); } async createProject(req: CreateProjectRequest): Promise { return this.request('POST', '/api/projects', req); } async getProject(id: string): Promise { return this.request('GET', `/api/projects/${id}`); } async deleteProject(id: string): Promise { return this.request('DELETE', `/api/projects/${id}`); } // ============ Goals ============ async listGoals(projectId: string): Promise> { return this.request('GET', `/api/projects/${projectId}/goals`); } async createGoal(projectId: string, req: CreateGoalRequest): Promise { return this.request('POST', `/api/projects/${projectId}/goals`, req); } // ============ Nodes ============ async getNode(id: string): Promise { return this.request('GET', `/api/nodes/${id}`); } async updateNode(id: string, req: UpdateNodeRequest): Promise { return this.request('PATCH', `/api/nodes/${id}`, req); } async createChild(parentId: string, req: CreateChildRequest): Promise { return this.request('POST', `/api/nodes/${parentId}/children`, req); } // ============ Edges ============ async createEdge(req: CreateEdgeRequest): Promise { return this.request('POST', '/api/edges', req); } async deleteEdge(id: string): Promise { return this.request('DELETE', `/api/edges/${id}`); } // ============ Goal Tree and Task Views ============ async getGoalTree(goalId: string): Promise { return this.request('GET', `/api/goals/${goalId}/tree`); } async listTasks(goalId: string): Promise> { return this.request('GET', `/api/goals/${goalId}/tasks`); } async listReadyTasks(goalId: string): Promise> { return this.request('GET', `/api/goals/${goalId}/tasks/ready`); } async getNextTask(goalId: string): Promise { return this.request('GET', `/api/goals/${goalId}/tasks/next`); } // ============ Decisions ============ async listDecisions(projectId: string): Promise> { return this.request('GET', `/api/projects/${projectId}/decisions`); } async getDecisionHistory(projectId: string): Promise { return this.request('GET', `/api/projects/${projectId}/decisions/history`); } async exportDecisions(projectId: string): Promise> { return this.request('POST', `/api/projects/${projectId}/decisions/export`); } // ============ Graph Import/Export ============ async exportAllGoals(projectId: string): Promise> { return this.request('GET', `/api/projects/${projectId}/graph/export`); } async exportGoal(goalId: string): Promise { return this.request('GET', `/api/goals/${goalId}/export`); } async importGraph(projectId: string, req: ImportRequest): Promise { return this.request('POST', `/api/projects/${projectId}/graph/import`, req); } async diffGraph(projectId: string, req: ImportRequest): Promise { return this.request('POST', `/api/projects/${projectId}/graph/diff`, req); } // ============ Sessions ============ async listSessions(goalId: string): Promise> { return this.request('GET', `/api/goals/${goalId}/sessions`); } async getSession(id: string): Promise { return this.request('GET', `/api/sessions/${id}`); } // ============ Search ============ async searchNodes(projectId: string, req: SearchRequest): Promise> { return this.request('POST', `/api/projects/${projectId}/search`, req); } // ============ Agents ============ async listAgents(goalId: string): Promise> { return this.request('GET', `/api/goals/${goalId}/agents`); } // ============ Health ============ async healthCheck(): Promise<{ status: string }> { return this.request('GET', '/api/health'); } } /** * Singleton-style factory for creating an API client instance. */ export function createApiClient(baseUrl?: string): ApiClient { return new ApiClient(baseUrl); }