// Import all necessary types from shared types import { BranchStatus, Config, CreateFollowUpAttempt, CreateProject, CreateTask, CreateTaskAndStart, CreateTaskAttempt, DirectoryEntry, type EditorType, ExecutionProcess, ExecutionProcessSummary, GitBranch, NormalizedConversation, Project, ProjectWithBranch, DeviceStartResponse, Task, TaskAttempt, TaskAttemptActivityWithPrompt, TaskAttemptState, TaskWithAttemptStatus, UpdateProject, UpdateTask, WorktreeDiff, } from 'shared/types'; export const makeRequest = async (url: string, options: RequestInit = {}) => { const headers = { 'Content-Type': 'application/json', ...(options.headers || {}), }; return fetch(url, { ...options, headers, }); }; export interface ApiResponse { success: boolean; data?: T; message?: string; } // Additional interface for file search results export interface FileSearchResult { path: string; name: string; } // Directory listing response export interface DirectoryListResponse { entries: DirectoryEntry[]; current_path: string; } export class ApiError extends Error { constructor( message: string, public status?: number, public response?: Response ) { super(message); this.name = 'ApiError'; } } const handleApiResponse = async (response: Response): Promise => { if (!response.ok) { let errorMessage = `Request failed with status ${response.status}`; try { const errorData = await response.json(); if (errorData.message) { errorMessage = errorData.message; } } catch { // Fallback to status text if JSON parsing fails errorMessage = response.statusText || errorMessage; } console.error('[API Error]', { message: errorMessage, status: response.status, response, endpoint: response.url, timestamp: new Date().toISOString(), }); throw new ApiError(errorMessage, response.status, response); } const result: ApiResponse = await response.json(); if (!result.success) { console.error('[API Error]', { message: result.message || 'API request failed', status: response.status, response, endpoint: response.url, timestamp: new Date().toISOString(), }); throw new ApiError(result.message || 'API request failed'); } return result.data as T; }; // Project Management APIs export const projectsApi = { getAll: async (): Promise => { const response = await makeRequest('/api/projects'); return handleApiResponse(response); }, getById: async (id: string): Promise => { const response = await makeRequest(`/api/projects/${id}`); return handleApiResponse(response); }, getWithBranch: async (id: string): Promise => { const response = await makeRequest(`/api/projects/${id}/with-branch`); return handleApiResponse(response); }, create: async (data: CreateProject): Promise => { const response = await makeRequest('/api/projects', { method: 'POST', body: JSON.stringify(data), }); return handleApiResponse(response); }, update: async (id: string, data: UpdateProject): Promise => { const response = await makeRequest(`/api/projects/${id}`, { method: 'PUT', body: JSON.stringify(data), }); return handleApiResponse(response); }, delete: async (id: string): Promise => { const response = await makeRequest(`/api/projects/${id}`, { method: 'DELETE', }); return handleApiResponse(response); }, openEditor: async (id: string): Promise => { const response = await makeRequest(`/api/projects/${id}/open-editor`, { method: 'POST', }); return handleApiResponse(response); }, getBranches: async (id: string): Promise => { const response = await makeRequest(`/api/projects/${id}/branches`); return handleApiResponse(response); }, searchFiles: async ( id: string, query: string ): Promise => { const response = await makeRequest( `/api/projects/${id}/search?q=${encodeURIComponent(query)}` ); return handleApiResponse(response); }, }; // Task Management APIs export const tasksApi = { getAll: async (projectId: string): Promise => { const response = await makeRequest(`/api/projects/${projectId}/tasks`); return handleApiResponse(response); }, create: async (projectId: string, data: CreateTask): Promise => { const response = await makeRequest(`/api/projects/${projectId}/tasks`, { method: 'POST', body: JSON.stringify(data), }); return handleApiResponse(response); }, createAndStart: async ( projectId: string, data: CreateTaskAndStart ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/create-and-start`, { method: 'POST', body: JSON.stringify(data), } ); return handleApiResponse(response); }, update: async ( projectId: string, taskId: string, data: UpdateTask ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}`, { method: 'PUT', body: JSON.stringify(data), } ); return handleApiResponse(response); }, delete: async (projectId: string, taskId: string): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}`, { method: 'DELETE', } ); return handleApiResponse(response); }, }; // Task Attempts APIs export const attemptsApi = { getAll: async (projectId: string, taskId: string): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts` ); return handleApiResponse(response); }, create: async ( projectId: string, taskId: string, data: CreateTaskAttempt ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts`, { method: 'POST', body: JSON.stringify(data), } ); return handleApiResponse(response); }, getState: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}` ); return handleApiResponse(response); }, stop: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/stop`, { method: 'POST', } ); return handleApiResponse(response); }, followUp: async ( projectId: string, taskId: string, attemptId: string, data: CreateFollowUpAttempt ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/follow-up`, { method: 'POST', body: JSON.stringify(data), } ); return handleApiResponse(response); }, getActivities: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/activities` ); return handleApiResponse(response); }, getDiff: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/diff` ); return handleApiResponse(response); }, deleteFile: async ( projectId: string, taskId: string, attemptId: string, fileToDelete: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/delete-filefile_path=${encodeURIComponent( fileToDelete )}`, { method: 'POST', } ); return handleApiResponse(response); }, openEditor: async ( projectId: string, taskId: string, attemptId: string, editorType?: EditorType ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/open-editor`, { method: 'POST', body: JSON.stringify(editorType ? { editor_type: editorType } : null), } ); return handleApiResponse(response); }, getBranchStatus: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/branch-status` ); return handleApiResponse(response); }, merge: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/merge`, { method: 'POST', } ); return handleApiResponse(response); }, rebase: async ( projectId: string, taskId: string, attemptId: string, newBaseBranch?: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/rebase`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ new_base_branch: newBaseBranch || null, }), } ); return handleApiResponse(response); }, createPR: async ( projectId: string, taskId: string, attemptId: string, data: { title: string; body: string | null; base_branch: string | null; } ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/create-pr`, { method: 'POST', body: JSON.stringify(data), } ); return handleApiResponse(response); }, startDevServer: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/start-dev-server`, { method: 'POST', } ); return handleApiResponse(response); }, getExecutionProcesses: async ( projectId: string, taskId: string, attemptId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/execution-processes` ); return handleApiResponse(response); }, stopExecutionProcess: async ( projectId: string, taskId: string, attemptId: string, processId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/tasks/${taskId}/attempts/${attemptId}/execution-processes/${processId}/stop`, { method: 'POST', } ); return handleApiResponse(response); }, }; // Execution Process APIs export const executionProcessesApi = { getDetails: async ( projectId: string, processId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/execution-processes/${processId}` ); return handleApiResponse(response); }, getNormalizedLogs: async ( projectId: string, processId: string ): Promise => { const response = await makeRequest( `/api/projects/${projectId}/execution-processes/${processId}/normalized-logs` ); return handleApiResponse(response); }, }; // File System APIs export const fileSystemApi = { list: async (path?: string): Promise => { const queryParam = path ? `?path=${encodeURIComponent(path)}` : ''; const response = await makeRequest(`/api/filesystem/list${queryParam}`); return handleApiResponse(response); }, }; // Config APIs export const configApi = { getConfig: async (): Promise => { const response = await makeRequest('/api/config'); return handleApiResponse(response); }, saveConfig: async (config: Config): Promise => { const response = await makeRequest('/api/config', { method: 'POST', body: JSON.stringify(config), }); return handleApiResponse(response); }, }; // GitHub Device Auth APIs export const githubAuthApi = { checkGithubToken: async (): Promise => { try { const response = await makeRequest('/api/auth/github/check'); const result: ApiResponse = await response.json(); if (!result.success && result.message === 'github_token_invalid') { return false; } return result.success; } catch (err) { // On network/server error, return undefined (unknown) return undefined; } }, start: async (): Promise => { const response = await makeRequest('/api/auth/github/device/start', { method: 'POST', }); return handleApiResponse(response); }, poll: async (device_code: string): Promise => { const response = await makeRequest('/api/auth/github/device/poll', { method: 'POST', body: JSON.stringify({ device_code }), headers: { 'Content-Type': 'application/json' }, }); return handleApiResponse(response); }, }; // MCP Servers APIs export const mcpServersApi = { load: async (executor: string): Promise => { const response = await makeRequest( `/api/mcp-servers?executor=${encodeURIComponent(executor)}` ); return handleApiResponse(response); }, save: async (executor: string, serversConfig: any): Promise => { const response = await makeRequest( `/api/mcp-servers?executor=${encodeURIComponent(executor)}`, { method: 'POST', body: JSON.stringify(serversConfig), } ); if (!response.ok) { const errorData = await response.json(); console.error('[API Error] Failed to save MCP servers', { message: errorData.message, status: response.status, response, timestamp: new Date().toISOString(), }); throw new ApiError( errorData.message || 'Failed to save MCP servers', response.status, response ); } }, };