| 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<string, unknown> |
| 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<string, unknown> |
| } |
|
|
| export type TaskCreateResponse = { |
| task: TaskRecord |
| } |
|
|
| export type TaskResultResponse = { |
| task_id: string |
| status: TaskStatus |
| raw: unknown | null |
| normalized: unknown | null |
| meta: Record<string, unknown> |
| } |
|
|
| 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<unknown>(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<TaskListResponse>(`tasks?${search.toString()}`) |
| } |
|
|
| export async function createTask(payload: TaskCreateRequest) { |
| const res = await apiPostWrapped<TaskCreateResponse, TaskCreateRequest>('tasks', payload) |
| return res.task |
| } |
|
|
| export async function retryTask(taskId: string) { |
| const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/retry`, {}) |
| return res.task |
| } |
|
|
| export async function cancelTask(taskId: string) { |
| const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/cancel`, {}) |
| return res.task |
| } |
|
|
| export async function markTaskRpa(taskId: string) { |
| const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/mark-rpa`, {}) |
| return res.task |
| } |
|
|
| export async function getTask(taskId: string) { |
| const res = await apiGetWrapped<TaskStatusResponse>(`tasks/${encodeURIComponent(taskId)}`) |
| return res.task |
| } |
|
|
| export async function getTaskResult(taskId: string, signal?: AbortSignal) { |
| try { |
| const res = await http.get<unknown>(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<unknown>(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<TaskResultResponse>(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) |
| } |
| } |
|
|