import axios from 'axios' import type { ApiError } from './api' import { apiGetWrapped, apiPostWrapped, http, unwrapApiResponse } from './api' export type TaskStatus = | 'queued' | 'running' | 'retrying' | 'fallback_running' | 'waiting_rpa' | 'rpa_running' | 'rpa_imported' | 'rpa_failed' | 'risk_paused' | 'succeeded' | 'failed' export type TaskError = { kind?: string message?: string [key: string]: unknown } export type TaskRecord = { id: string status: TaskStatus task_type: string target: string payload: Record engine?: string | null callback?: unknown created: number started?: number | null finished?: number | null retry_count: number error?: TaskError | null } export type TaskListResponse = { tasks: TaskRecord[] total: number limit: number offset: number } export type TaskStatusResponse = { task: TaskRecord } export type TaskCreateRequest = { task_type: string target?: string | null engine?: string | null payload?: Record } export type TaskCreateResponse = { task: TaskRecord } export type TaskResultResponse = { task_id: string status: TaskStatus raw: unknown | null normalized: unknown | null meta: Record } export type ListTasksParams = { limit: number offset: number status?: string[] task_type?: string[] engine?: string[] error_kind?: string[] sort?: string } function normalizePath(path: string) { return path.replace(/^\/+/, '') } function toApiError(error: unknown): ApiError { if (axios.isAxiosError(error)) { if (!error.response) { return { status: 0, message: error.message } } const status = error.response?.status ?? 0 const body = error.response?.data const wrapped = unwrapApiResponse(body) const message = wrapped.ok || wrapped.error === '响应缺少 code/msg 字段' ? error.message : wrapped.error return { status, message, data: body } } return { status: 0, message: error instanceof Error ? error.message : String(error), } } function join(values?: string[]) { const parts = (values || []).map((v) => String(v).trim()).filter((v) => v !== '') return parts.length ? parts.join(',') : undefined } export async function listTasks(params: ListTasksParams) { const search = new URLSearchParams() search.set('limit', String(params.limit)) search.set('offset', String(params.offset)) const status = join(params.status) const taskType = join(params.task_type) const engine = join(params.engine) const errorKind = join(params.error_kind) const sort = String(params.sort || '').trim() if (status) search.set('status', status) if (taskType) search.set('task_type', taskType) if (engine) search.set('engine', engine) if (errorKind) search.set('error_kind', errorKind) if (sort) search.set('sort', sort) return apiGetWrapped(`tasks?${search.toString()}`) } export async function createTask(payload: TaskCreateRequest) { const res = await apiPostWrapped('tasks', payload) return res.task } export async function retryTask(taskId: string) { const res = await apiPostWrapped(`tasks/${encodeURIComponent(taskId)}/retry`, {}) return res.task } export async function cancelTask(taskId: string) { const res = await apiPostWrapped(`tasks/${encodeURIComponent(taskId)}/cancel`, {}) return res.task } export async function markTaskRpa(taskId: string) { const res = await apiPostWrapped(`tasks/${encodeURIComponent(taskId)}/mark-rpa`, {}) return res.task } export async function getTask(taskId: string) { const res = await apiGetWrapped(`tasks/${encodeURIComponent(taskId)}`) return res.task } export async function getTaskResult(taskId: string, signal?: AbortSignal) { try { const res = await http.get(normalizePath(`tasks/${encodeURIComponent(taskId)}/result`), { validateStatus: () => true, signal, }) if (res.status === 409) return { ready: false as const, body: res.data } if (res.status !== 200) { const wrapped = unwrapApiResponse(res.data) const message = wrapped.ok || wrapped.error === '响应缺少 code/msg 字段' ? `HTTP ${res.status}` : wrapped.error const apiError: ApiError = { status: res.status, message, data: res.data } throw apiError } const wrapped = unwrapApiResponse(res.data) if (!wrapped.ok) { const apiError: ApiError = { status: res.status, message: wrapped.error, data: res.data } throw apiError } return { ready: true as const, data: wrapped.data ?? null } } catch (e) { if (axios.isAxiosError(e) && e.code === 'ERR_CANCELED') { throw new DOMException('Aborted', 'AbortError') } if ( !!e && typeof e === 'object' && 'status' in e && typeof (e as { status: unknown }).status === 'number' && 'message' in e && typeof (e as { message: unknown }).message === 'string' ) { throw e as ApiError } throw toApiError(e) } }