import { fallbackErrorClass } from '../api/chatRetry'; import type { ChatErrorInfo } from '../types/workbench'; /** GET /api/v1/tasks/runs/{id} 的最小视图。兼容修复前的 PascalCase 响应。 */ export type RemoteTaskRunView = { status?: string; Status?: string; error_message?: string; ErrorMessage?: string; workspace_id?: string; WorkspaceID?: string; }; export function readRemoteTaskRun(remote: RemoteTaskRunView | null | undefined): { status: string; errorMessage: string; workspaceId: string; } { if (!remote) { return { status: '', errorMessage: '', workspaceId: '' }; } return { status: remote.status || remote.Status || '', errorMessage: (remote.error_message || remote.ErrorMessage || '').trim(), workspaceId: remote.workspace_id || remote.WorkspaceID || '', }; } const fatalDenialCopy: Record = { DOC_TASK_DISABLED: '该文档任务未启用,当前请求未执行', WORKFLOW_NOT_ACTIVATED: '当前项目尚未激活工作流,文档任务不会启动', WORKFLOW_INSTANCE_CLOSED: '工作流实例已关闭,无法执行该文档任务', DOC_DUTY_NOT_MANAGED: '该文档未配置职责,无法执行', SESSION_FORBIDDEN: '无权访问该会话', TASK_SCOPE_UNRESOLVED: '无法定位该文档任务的工作流阶段,请检查任务名或联系管理员', }; /** 任务直通道失败后应降级到聊天确认引导的错误码(仅降级一次,防循环)。 */ export function isTaskStageDegradeCode(code: string | undefined | null): boolean { return code === 'TASK_SCOPE_UNRESOLVED' || code === 'DOC_TASK_NOT_IN_ACTIVE_STAGE'; } export function buildNetworkErrorInfo( httpStatus?: number, opts?: { errorCode?: string; detail?: string }, ): ChatErrorInfo { const code = opts?.errorCode || ''; const detail = opts?.detail || ''; if (code === 'DOC_BUSY' || code === 'document_busy') { return { userMessage: detail || '该文档正在被其他用户或项目助手编辑,当前请求未创建新任务', code: 'DOC_BUSY', class: 'fatal', actions: [], }; } if (code === 'DOC_TASK_NOT_IN_ACTIVE_STAGE') { return { userMessage: detail || '该文档任务不在当前活动阶段,请先完成当前阶段工作流后再试', code, class: 'transient', actions: ['retry'], }; } if (code === 'DOC_DUTY_FORBIDDEN') { return { userMessage: detail || '当前账号不是该文档任务的职责人,无法执行', code, class: 'fatal', actions: ['retry'], }; } if (fatalDenialCopy[code]) { const retryable = code === 'WORKFLOW_NOT_ACTIVATED' || code === 'DOC_DUTY_NOT_MANAGED' || code === 'TASK_SCOPE_UNRESOLVED'; return { userMessage: detail || fatalDenialCopy[code], code, class: 'fatal', actions: retryable ? ['retry'] : [], }; } if (httpStatus === 401) { return { userMessage: '登录已失效,请重新登录后再试', code: 'ERR_UNAUTHENTICATED', class: 'fatal', actions: ['retry'], }; } if (httpStatus === 403) { if (code === 'ERR_FORBIDDEN' || code === 'AUTHZ') { return { userMessage: detail || '无权访问该项目,请确认登录账号与项目成员身份', code: code || 'ERR_FORBIDDEN', class: 'fatal', actions: ['retry'], }; } return { userMessage: detail || '无权执行该操作,请确认账号权限与工作流阶段', code: code || 'ERR_FORBIDDEN', class: 'fatal', actions: ['retry'], }; } if (httpStatus !== undefined && httpStatus >= 400 && httpStatus < 500) { const shown = code || `HTTP_${httpStatus}`; return { userMessage: detail || `请求失败(${shown}),请重试或联系管理员`, code: shown, class: 'fatal', actions: ['retry'], }; } return { userMessage: '网络连接异常,请检查网络后重试', code: 'NETWORK_ERROR', class: fallbackErrorClass({ network: true }), actions: ['retry'], }; } /** interrupted 且有工作区时可继续;否则只允许按原请求重开。 */ export function buildInterruptedTaskErrorInfo(errorMessage: string, workspaceId: string): ChatErrorInfo { const message = errorMessage.trim() || '任务执行中断,可继续或重新开始'; const resumable = workspaceId.trim() !== ''; return { userMessage: message, code: 'TASK_INTERRUPTED', class: resumable ? 'resumable' : 'fatal', workspaceId: resumable ? workspaceId : undefined, actions: resumable ? ['continue', 'restart'] : ['restart'], }; } /** 服务端已判定 failed:有 error_message 时展示真实原因,否则才回落到网络错误。 */ export function buildFailedTaskErrorInfo(errorMessage: string): ChatErrorInfo { const message = errorMessage.trim(); if (!message) { return buildNetworkErrorInfo(); } return { userMessage: message, code: 'TASK_FAILED', class: 'fatal', actions: ['retry', 'restart'], }; } export function buildCancelledTaskErrorInfo(): ChatErrorInfo { return { userMessage: '任务已取消', code: 'TASK_CANCELLED', class: 'cancelled', actions: ['restart'], }; }