fix(design): preserve command outcome certainty

This commit is contained in:
2026-09-03 18:25:06 +08:00
parent 4d8b19eeb5
commit a16dfd0d6f
15 changed files with 576 additions and 23 deletions

View File

@@ -332,6 +332,7 @@ function sendRouteError(res: ServerResponse, error: unknown): void {
status: error.status,
code: error.code,
error: error.message,
...(error.commandOutcome ? { commandOutcome: error.commandOutcome } : {}),
});
return;
}

View File

@@ -1,6 +1,7 @@
import type {
DesignAsset,
DesignAssetUploadInput,
DesignCommandFailureOutcome,
DesignCommandInput,
DesignCommandResult,
DesignCreateWorkspaceInput,
@@ -20,12 +21,19 @@ export type CloseEventSessionsOptions = {
export class DesignWorkspaceModuleError extends Error {
readonly status: number;
readonly code: string;
readonly commandOutcome?: DesignCommandFailureOutcome;
constructor(status: number, code: string, message: string) {
constructor(
status: number,
code: string,
message: string,
commandOutcome?: DesignCommandFailureOutcome,
) {
super(message);
this.name = 'DesignWorkspaceModuleError';
this.status = status;
this.code = code;
this.commandOutcome = commandOutcome;
}
}

View File

@@ -6,6 +6,7 @@ import {
type DesignAssistantDeltaEvent,
type DesignCapabilities,
type DesignChangeSet,
type DesignCommandFailureOutcome,
type DesignCommandInput,
type DesignCommandResult,
type DesignCompilationIssue,
@@ -628,25 +629,87 @@ function userFacingErrorMessage(code: string): string {
budget_denied: '当前设计点不足,无法开始生成',
policy_blocked: '当前内容不符合创作安全规则',
design_reasoner_unavailable: '设计 Agent 暂时不可用,请稍后重试',
design_reasoner_invalid: 'AI 没有整理好这次想法,请再试一次',
design_agent_run_failed: 'AI 这次没有完成设计整理,请再试一次',
design_agent_run_cancelled: '这次设计整理已停止',
design_runtime_unavailable: 'AI 设计服务暂时不可用',
design_production_unavailable: '当前生成能力暂时不可用',
agent_runtime_unavailable: 'AI 设计服务暂时不可用,请稍后重试',
agent_command_invalid: '设计请求内容无效,请检查后重试',
auth_required: '请先登录后再使用 AI 设计',
auth_expired: '登录状态已失效,请重新登录',
};
return messages[code] ?? 'AI 设计请求失败,请稍后重试';
return messages[code.toLowerCase()] ?? 'AI 设计请求失败,请稍后重试';
}
function agentRunErrorStatus(code: string): number {
if (code.includes('not_found')) return 404;
if (code === 'budget_denied') return 402;
if (code.includes('conflict') || code.includes('expired') || code.includes('consumed')) return 409;
if (code.includes('invalid') || code === 'policy_blocked' || code === 'design_quote_blocked') {
const normalizedCode = code.toLowerCase();
const exactStatuses: Record<string, number> = {
design_direction_not_found: 404,
design_direction_unavailable: 409,
design_revision_conflict: 409,
design_idempotency_conflict: 409,
design_prompt_unavailable: 409,
design_reasoner_unavailable: 503,
design_reasoner_invalid: 502,
design_field_locked: 409,
design_generation_unavailable: 503,
design_quote_expired: 409,
design_quote_consumed: 409,
design_quote_invalid: 409,
design_quote_dependency_invalid: 409,
budget_denied: 402,
design_billing_unavailable: 503,
design_state_unavailable: 409,
design_command_invalid: 400,
design_input_invalid: 422,
design_agent_run_failed: 502,
design_agent_run_cancelled: 409,
};
if (exactStatuses[normalizedCode] !== undefined) return exactStatuses[normalizedCode];
if (normalizedCode.includes('not_found')) return 404;
if (normalizedCode.includes('conflict')
|| normalizedCode.includes('expired')
|| normalizedCode.includes('consumed')) return 409;
if (normalizedCode.includes('invalid')
|| normalizedCode === 'policy_blocked'
|| normalizedCode === 'design_quote_blocked') {
return 422;
}
if (code.includes('unavailable')) return 503;
if (normalizedCode.includes('unavailable')) return 503;
return 502;
}
function commandFailureError(
error: unknown,
outcome: DesignCommandFailureOutcome,
): DesignWorkspaceModuleError {
if (error instanceof DesignWorkspaceModuleError) {
return new DesignWorkspaceModuleError(
error.status,
error.code,
userFacingErrorMessage(error.code),
outcome,
);
}
return new DesignWorkspaceModuleError(
502,
'DESIGN_WORKSPACE_REQUEST_FAILED',
'AI 设计服务暂时无法连接',
outcome,
);
}
function submissionFailureOutcome(error: unknown): DesignCommandFailureOutcome {
if (error instanceof DesignWorkspaceModuleError
&& error.status >= 400
&& error.status < 500
&& error.status !== 408) {
return 'definitive_failure';
}
return 'unknown';
}
function designRequestTimeoutError(): DesignWorkspaceModuleError {
return new DesignWorkspaceModuleError(
504,
@@ -716,11 +779,22 @@ export class WorksSquareDesignWorkspace implements DesignWorkspaceModule {
}
async submitCommand(input: DesignCommandInput): Promise<DesignCommandResult> {
const command = await this.requestJson<ServerAgentCommand>(
`/api/agents/sessions/${encodeURIComponent(input.sessionId)}/commands`,
{ method: 'POST', body: JSON.stringify(agentCommand(input)) },
);
const run = await this.waitForRun(input.sessionId, command.run_id);
let command: ServerAgentCommand;
try {
command = await this.requestJson<ServerAgentCommand>(
`/api/agents/sessions/${encodeURIComponent(input.sessionId)}/commands`,
{ method: 'POST', body: JSON.stringify(agentCommand(input)) },
);
} catch (error) {
throw commandFailureError(error, submissionFailureOutcome(error));
}
let run: ServerAgentRun;
try {
run = await this.waitForRun(input.sessionId, command.run_id);
} catch (error) {
throw commandFailureError(error, 'unknown');
}
if (run.status !== 'succeeded') {
const code = run.error?.code ?? (
run.status === 'cancelled' ? 'design_agent_run_cancelled' : 'design_agent_run_failed'
@@ -729,12 +803,19 @@ export class WorksSquareDesignWorkspace implements DesignWorkspaceModule {
agentRunErrorStatus(code),
code,
userFacingErrorMessage(code),
'definitive_failure',
);
}
let workspace: DesignWorkspace;
try {
workspace = await this.getWorkspace(input.workspaceId);
} catch (error) {
throw commandFailureError(error, 'unknown');
}
return {
clientOperationId: input.clientOperationId,
runId: run.run_id,
workspace: await this.getWorkspace(input.workspaceId),
workspace,
};
}