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<Response> {
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<string, unknown>;
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<TaskListResponse> {
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<TaskWithSubtasks> {
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<Task> {
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<Task> {
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<SendMessageResponse> {
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<TaskOutputResponse> {
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<PendingQuestion[]> {
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<AnswerQuestionResponse> {
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();
}