100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
import { parseSSEChunk, type StreamEvent } from './chatStream';
|
|
import { mergeAuthHeaders, getBearerAuthHeaders } from './authHeaders';
|
|
|
|
export type CreateTaskRunBody = Record<string, unknown> & {
|
|
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<CreateTaskRunResponse> {
|
|
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<void> {
|
|
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<void> {
|
|
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();
|
|
}
|