import { supabase } from './supabase'; // ============================================================================= // API Configuration // ============================================================================= const API_CONFIG = { local: { http: 'http://localhost:8080', ws: 'ws://localhost:8080', }, production: { http: 'https://api.makima.jp', ws: 'wss://api.makima.jp', }, } as const; type Environment = 'local' | 'production'; // Detect environment based on __DEV__ flag const env: Environment = __DEV__ ? 'local' : 'production'; export const API_BASE = API_CONFIG[env].http; export const WS_BASE = API_CONFIG[env].ws; export function getEnvironment(): Environment { return env; } // ============================================================================= // Authentication Helper // ============================================================================= /** * Fetch wrapper that automatically adds authentication headers * Gets the access token from Supabase session */ async function authFetch(url: string, options: RequestInit = {}): Promise { const { data: { session } } = await supabase.auth.getSession(); const headers: HeadersInit = { 'Content-Type': 'application/json', }; if (session?.access_token) { headers['Authorization'] = `Bearer ${session.access_token}`; } return fetch(`${API_BASE}${url}`, { ...options, headers: { ...headers, ...options.headers }, }); } // ============================================================================= // Mesh/Task Types // ============================================================================= export type TaskStatus = | 'pending' | 'initializing' | 'starting' | 'running' | 'paused' | 'blocked' | 'done' | 'failed' | 'merged'; export type MergeMode = 'pr' | 'auto' | 'manual'; export type CompletionAction = 'none' | 'branch' | 'merge' | 'pr'; export type ContractPhase = 'research' | 'specify' | 'plan' | 'execute' | 'review'; export interface TaskSummary { id: string; contractId: string | null; contractName: string | null; contractPhase: ContractPhase | null; parentTaskId: string | null; depth: number; name: string; status: TaskStatus; priority: number; progressSummary: string | null; subtaskCount: number; isSupervisor: boolean; version: number; createdAt: string; updatedAt: string; } export interface Task { id: string; ownerId: string; contractId: string | null; parentTaskId: string | null; depth: number; name: string; description: string | null; status: TaskStatus; priority: number; plan: string; daemonId: string | null; containerId: string | null; overlayPath: string | null; repositoryUrl: string | null; baseBranch: string | null; targetBranch: string | null; mergeMode: MergeMode | null; prUrl: string | null; targetRepoPath: string | null; completionAction: CompletionAction | null; progressSummary: string | null; lastOutput: string | null; errorMessage: string | null; startedAt: string | null; completedAt: string | null; version: number; createdAt: string; updatedAt: string; isSupervisor: boolean; } export interface TaskWithSubtasks extends Task { subtasks: TaskSummary[]; } export interface TaskListResponse { tasks: TaskSummary[]; total: number; } export interface TaskOutputEntry { id: string; taskId: string; messageType: string; content: string; toolName?: string; toolInput?: Record; isError?: boolean; costUsd?: number; durationMs?: number; createdAt: string; } export interface TaskOutputResponse { entries: TaskOutputEntry[]; total: number; taskId: string; } export interface SendMessageResponse { success: boolean; taskId: string; messageLength: number; } // ============================================================================= // Pending Question Types // ============================================================================= export interface PendingQuestion { questionId: string; taskId: string; contractId: string; question: string; choices: string[]; context: string | null; createdAt: string; } export interface AnswerQuestionResponse { success: boolean; } // ============================================================================= // Task API Functions // ============================================================================= /** * List all tasks for the current user */ export async function listTasks(): Promise { const res = await authFetch('/api/v1/mesh/tasks'); if (!res.ok) { throw new Error(`Failed to list tasks: ${res.statusText}`); } return res.json(); } /** * Get a specific task with its subtasks */ export async function getTask(id: string): Promise { const res = await authFetch(`/api/v1/mesh/tasks/${id}`); if (!res.ok) { throw new Error(`Failed to get task: ${res.statusText}`); } return res.json(); } /** * Start a pending task */ export async function startTask(id: string): Promise { const res = await authFetch(`/api/v1/mesh/tasks/${id}/start`, { method: 'POST', }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed to start task: ${errorText || res.statusText}`); } return res.json(); } /** * Stop a running task */ export async function stopTask(id: string): Promise { const res = await authFetch(`/api/v1/mesh/tasks/${id}/stop`, { method: 'POST', }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed to stop task: ${errorText || res.statusText}`); } return res.json(); } /** * Send a message to a running task's stdin */ export async function sendTaskMessage( taskId: string, message: string ): Promise { const res = await authFetch(`/api/v1/mesh/tasks/${taskId}/message`, { method: 'POST', body: JSON.stringify({ message }), }); if (!res.ok) { const errorText = await res.text(); throw new Error(`Failed to send message: ${errorText || res.statusText}`); } return res.json(); } /** * Get task output history */ export async function getTaskOutput(taskId: string): Promise { const res = await authFetch(`/api/v1/mesh/tasks/${taskId}/output`); if (!res.ok) { throw new Error(`Failed to get task output: ${res.statusText}`); } return res.json(); } // ============================================================================= // Question API Functions // ============================================================================= /** * Get all pending supervisor questions for the current user */ export async function listPendingQuestions(): Promise { const res = await authFetch('/api/v1/mesh/questions'); if (!res.ok) { throw new Error(`Failed to list questions: ${res.statusText}`); } return res.json(); } /** * Answer a pending supervisor question */ export async function answerQuestion( questionId: string, response: string ): Promise { const res = await authFetch(`/api/v1/mesh/questions/${questionId}/answer`, { method: 'POST', body: JSON.stringify({ response }), }); if (!res.ok) { throw new Error(`Failed to answer question: ${res.statusText}`); } return res.json(); }