import { parseSSEChunk, type StreamEvent } from './chatStream'; import { mergeAuthHeaders, getBearerAuthHeaders } from './authHeaders'; export type CreateTaskRunBody = Record & { session_id: string; project_id: number; user_id?: number; client_turn_id?: string; user_message?: string; }; export type CreateTaskRunResponse = { run_id: string; status: string; workspace_id?: string; }; export async function createTaskRun(body: CreateTaskRunBody): Promise { const res = await fetch('/api/v1/tasks/runs', { method: 'POST', headers: getBearerAuthHeaders('application/json'), body: JSON.stringify(body), }); if (!res.ok) { let detail = ''; let errorCode = ''; try { const payload = (await res.json()) as { message?: string; error?: string; error_code?: string; detail?: string | { reason?: string }; }; const rawCode = payload.error_code || payload.error || ''; errorCode = rawCode === 'document_busy' ? 'DOC_BUSY' : rawCode; const structuredDetail = typeof payload.detail === 'object' ? payload.detail?.reason : payload.detail; detail = payload.message || structuredDetail || errorCode || ''; } catch { // ignore non-json body } throw Object.assign( new Error(`create task run failed: HTTP ${res.status}${detail ? `: ${detail}` : ''}`), { httpStatus: res.status, errorCode, detail }, ); } return res.json(); } export async function subscribeTaskRunEvents( runId: string, handlers: { onEvent: (ev: StreamEvent) => void; afterSeq?: number }, signal?: AbortSignal, ): Promise { const after = handlers.afterSeq ?? 0; const url = `/api/v1/tasks/runs/${encodeURIComponent(runId)}/events?after_seq=${after}`; const resp = await fetch(url, { method: 'GET', headers: mergeAuthHeaders({ Accept: 'text/event-stream' }), signal, }); if (!resp.ok || !resp.body) { throw new Error(`subscribe task run failed: HTTP ${resp.status}`); } const reader = resp.body.getReader(); const decoder = new TextDecoder(); let buf = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); const parsed = parseSSEChunk(buf); buf = parsed.rest; for (const ev of parsed.events) handlers.onEvent(ev); } if (buf) { const flushed = parseSSEChunk(buf, { flush: true }); for (const ev of flushed.events) handlers.onEvent(ev); } } export async function cancelTaskRun(runId: string): Promise { const res = await fetch(`/api/v1/tasks/runs/${encodeURIComponent(runId)}/cancel`, { method: 'POST', headers: getBearerAuthHeaders(), }); if (!res.ok) { throw new Error(`cancel task run failed: HTTP ${res.status}`); } } export async function getTaskRun(runId: string) { const res = await fetch(`/api/v1/tasks/runs/${encodeURIComponent(runId)}`, { headers: getBearerAuthHeaders(), }); if (!res.ok) { throw new Error(`get task run failed: HTTP ${res.status}`); } return res.json(); }