import type { components } from './generated' export type HealthResponse = components['schemas']['HealthResponse'] export type Task = components['schemas']['TaskResponse'] export type TaskAction = components['schemas']['TaskAction'] export type TaskActionType = components['schemas']['TaskActionType'] export type CreateTaskRequest = components['schemas']['CreateTaskRequest'] export type UpdateTaskRequest = components['schemas']['UpdateTaskRequest'] export type TaskListResponse = components['schemas']['TaskListResponse'] export type TaskRun = components['schemas']['TaskRunResponse'] export type TaskRunListResponse = components['schemas']['TaskRunListResponse'] export type RunStatus = components['schemas']['RunStatus'] export type RunTrigger = components['schemas']['RunTrigger'] export type LogChunkResponse = components['schemas']['LogChunkResponse'] export type ErrorResponse = components['schemas']['ErrorResponse'] export type SessionResponse = components['schemas']['SessionResponse'] export type CronPreviewRequest = components['schemas']['CronPreviewRequest'] export type CronPreviewResponse = components['schemas']['CronPreviewResponse'] export interface TaskFilters { q?: string enabled?: boolean action_type?: TaskActionType page?: number page_size?: number } export interface RunFilters { page?: number page_size?: number status?: RunStatus trigger?: RunTrigger } export type ExecutionEvent = | { event: 'run' data: { run: TaskRun } } | { event: 'log' data: { run_id: number offset: number next_offset: number content: string } } export class ApiRequestError extends Error { readonly status: number readonly code?: string readonly field?: string | null readonly details?: Record constructor(status: number, body?: ErrorResponse) { super(body?.message ?? `请求失败(HTTP ${status})`) this.name = 'ApiRequestError' this.status = status this.code = body?.code this.field = body?.field this.details = body?.details } } async function requestJson( path: string, init: RequestInit = {}, ): Promise { const headers = new Headers(init.headers) headers.set('Accept', 'application/json') const method = (init.method ?? 'GET').toUpperCase() if (init.body) { headers.set('Content-Type', 'application/json') } if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) { headers.set('X-TaskPilot-Token', await fetchSessionToken()) } const response = await fetch(path, { ...init, headers, }) if (!response.ok) { let body: ErrorResponse | undefined try { body = (await response.json()) as ErrorResponse } catch { body = undefined } throw new ApiRequestError(response.status, body) } if (response.status === 204) { return undefined as T } return (await response.json()) as T } async function fetchSessionToken(): Promise { const response = await fetch('/api/v1/session', { headers: { Accept: 'application/json' }, }) if (!response.ok) { throw new ApiRequestError(response.status) } const session = (await response.json()) as SessionResponse return session.token } function withQuery(path: string, values: object): string { const query = new URLSearchParams() for (const [key, value] of Object.entries(values)) { if (value !== undefined && value !== null && value !== '') { query.set(key, String(value)) } } const serialized = query.toString() return serialized ? `${path}?${serialized}` : path } export function fetchHealth(signal?: AbortSignal): Promise { return requestJson('/api/v1/health', { signal }) } export function listTasks( filters: TaskFilters = {}, signal?: AbortSignal, ): Promise { return requestJson( withQuery('/api/v1/tasks', filters), { signal }, ) } export function getTask(id: string, signal?: AbortSignal): Promise { return requestJson(`/api/v1/tasks/${encodeURIComponent(id)}`, { signal, }) } export function createTask(payload: CreateTaskRequest): Promise { return requestJson('/api/v1/tasks', { method: 'POST', body: JSON.stringify(payload), }) } export function updateTask( id: string, payload: UpdateTaskRequest, ): Promise { return requestJson(`/api/v1/tasks/${encodeURIComponent(id)}`, { method: 'PUT', body: JSON.stringify(payload), }) } export function deleteTask(id: string): Promise { return requestJson(`/api/v1/tasks/${encodeURIComponent(id)}`, { method: 'DELETE', }) } export function setTaskEnabled( id: string, enabled: boolean, ): Promise { const action = enabled ? 'enable' : 'disable' return requestJson( `/api/v1/tasks/${encodeURIComponent(id)}/${action}`, { method: 'POST' }, ) } export function runTask(id: string): Promise { return requestJson( `/api/v1/tasks/${encodeURIComponent(id)}/run`, { method: 'POST' }, ) } export function listTaskRuns( taskId: string, filters: RunFilters = {}, signal?: AbortSignal, ): Promise { return requestJson( withQuery(`/api/v1/tasks/${encodeURIComponent(taskId)}/runs`, filters), { signal }, ) } export function getRun( runId: number, signal?: AbortSignal, ): Promise { return requestJson(`/api/v1/runs/${runId}`, { signal }) } export function getRunLogs( runId: number, offset = 0, signal?: AbortSignal, ): Promise { return requestJson( withQuery(`/api/v1/runs/${runId}/logs`, { offset }), { signal }, ) } export function cancelRun(runId: number): Promise { return requestJson(`/api/v1/runs/${runId}/cancel`, { method: 'POST', }) } export function runLogDownloadUrl(runId: number): string { return `/api/v1/runs/${runId}/logs/download` } export function previewCron( request: CronPreviewRequest, signal?: AbortSignal, ): Promise { return requestJson('/api/v1/cron/preview', { method: 'POST', body: JSON.stringify(request), signal, }) }