Ask finance and health like other domains, put year-cued collect questions first, and let the classifier distinguish decline vs skip. Skill 10.0.22. Co-authored-by: Cursor <cursoragent@cursor.com>
93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
/**
|
|
* Concrete finish reasons for rectification Agent runs.
|
|
*
|
|
* Incomplete turns must not be described only as “请稍后再试”. The code is
|
|
* durable; the Chinese copy is for the live client and is not persisted as a
|
|
* chat message.
|
|
*/
|
|
|
|
export const RECTIFICATION_FINISH_REASONS = [
|
|
"stop",
|
|
"tool_calls",
|
|
"length",
|
|
"timeout",
|
|
"max_steps",
|
|
"provider_error",
|
|
"aborted",
|
|
"unknown",
|
|
] as const;
|
|
|
|
export type RectificationFinishReason = (typeof RECTIFICATION_FINISH_REASONS)[number];
|
|
|
|
export type RectificationRunDiagnostic = Readonly<{
|
|
runId: string;
|
|
modelId: string;
|
|
finishReason: RectificationFinishReason;
|
|
inputTokens: number | null;
|
|
reasoningTokens: number | null;
|
|
outputTokens: number | null;
|
|
stepCount: number;
|
|
toolCallCount: number;
|
|
readCasePayloadBytes: number | null;
|
|
elapsedMs: number;
|
|
lastCompletedTool: string | null;
|
|
stateMutationCommitted: boolean;
|
|
expectedWrite: "evidence" | "none" | "unknown" | null;
|
|
collectIntent: "classified" | "unclassified" | null;
|
|
}>;
|
|
|
|
const USER_COPY: Readonly<Record<string, string>> = {
|
|
answer_truncated: "模型输出达到上限,状态已记录。",
|
|
length: "模型输出达到上限,状态已记录。",
|
|
run_timeout: "服务端运行超时,状态已记录。",
|
|
timeout: "服务端运行超时,状态已记录。",
|
|
stream_aborted: "服务端运行超时,状态已记录。",
|
|
stream_unfinished: "服务端运行超时,状态已记录。",
|
|
max_steps: "本轮达到步骤上限,状态已记录。",
|
|
tool_calls: "本轮达到步骤上限,状态已记录。",
|
|
provider_error: "上游模型连接失败,状态已记录。",
|
|
run_failed: "上游模型连接失败,状态已记录。",
|
|
empty_stream: "本轮没有生成可展示的回复,状态已记录。",
|
|
evidence_not_written: "这件我还没记上。请再说一次大概年月和发生的事。",
|
|
};
|
|
|
|
export function finishReasonFromErrorCode(errorCode: string | null | undefined): RectificationFinishReason {
|
|
if (errorCode === "answer_truncated") return "length";
|
|
if (errorCode === "run_timeout" || errorCode === "stream_unfinished") return "timeout";
|
|
if (errorCode === "stream_aborted") return "aborted";
|
|
if (errorCode === "max_steps") return "max_steps";
|
|
if (errorCode === "provider_error" || errorCode === "run_failed") return "provider_error";
|
|
return "unknown";
|
|
}
|
|
|
|
export function userFacingRunFailure(errorCode: string | null | undefined): string {
|
|
if (!errorCode) return "本轮没有完成,状态已记录。";
|
|
return USER_COPY[errorCode] ?? "本轮没有完成,状态已记录。";
|
|
}
|
|
|
|
export function isIncompleteRunBanner(text: string): boolean {
|
|
return /本轮处理未完成|当前进度已保留|请稍后再试/.test(text);
|
|
}
|
|
|
|
export function mapModelFinishToErrorCode(input: {
|
|
finishReason: string | null;
|
|
aborted: boolean;
|
|
timedOut: boolean;
|
|
answerText: string;
|
|
stepCount: number;
|
|
maxSteps: number;
|
|
}): string | null {
|
|
if (input.timedOut) return "run_timeout";
|
|
if (input.aborted) return "stream_aborted";
|
|
if (input.finishReason === "length") return "answer_truncated";
|
|
if (input.finishReason === "error") return "provider_error";
|
|
if (
|
|
(input.finishReason === "tool-calls" || input.finishReason === "tool_calls")
|
|
&& !input.answerText.trim()
|
|
) {
|
|
return "max_steps";
|
|
}
|
|
if (input.stepCount >= input.maxSteps && !input.answerText.trim()) return "max_steps";
|
|
return null;
|
|
}
|