























1+import { formatBlockedLivenessError, isBlockedLivenessState } from "../shared/agent-liveness.js";
2+import { AGENT_RUN_ABORTED_ERROR, isAbortedAgentStopReason } from "./run-termination.js";
3+import {
4+normalizeAgentRunTimeoutPhase,
5+normalizeProviderStarted,
6+type AgentRunTimeoutPhase,
7+} from "./run-timeout-attribution.js";
8+9+export type AgentRunWaitStatus = "ok" | "error" | "timeout";
10+11+export type AgentRunTerminalReason =
12+| "completed"
13+| "hard_timeout"
14+| "timed_out"
15+| "cancelled"
16+| "aborted"
17+| "blocked"
18+| "failed";
19+20+export type AgentRunTerminalOutcome = {
21+reason: AgentRunTerminalReason;
22+status: AgentRunWaitStatus;
23+error?: string;
24+stopReason?: string;
25+livenessState?: string;
26+timeoutPhase?: AgentRunTimeoutPhase;
27+providerStarted?: boolean;
28+startedAt?: number;
29+endedAt?: number;
30+};
31+32+export type AgentRunTerminalInput = {
33+status: AgentRunWaitStatus;
34+error?: unknown;
35+stopReason?: unknown;
36+livenessState?: unknown;
37+timeoutPhase?: unknown;
38+providerStarted?: unknown;
39+startedAt?: unknown;
40+endedAt?: unknown;
41+};
42+43+const HARD_TIMEOUT_PHASES = new Set<AgentRunTimeoutPhase>(["preflight", "provider", "post_turn"]);
44+45+function asFiniteTimestamp(value: unknown): number | undefined {
46+return typeof value === "number" && Number.isFinite(value) ? value : undefined;
47+}
48+49+function asNonEmptyString(value: unknown): string | undefined {
50+return typeof value === "string" && value.trim() ? value : undefined;
51+}
52+53+export function isHardAgentRunTimeoutPhase(value: unknown): value is AgentRunTimeoutPhase {
54+const phase = normalizeAgentRunTimeoutPhase(value);
55+return phase !== undefined && HARD_TIMEOUT_PHASES.has(phase);
56+}
57+58+export function isHardAgentRunTimeoutOutcome(
59+outcome: AgentRunTerminalOutcome | undefined | null,
60+): boolean {
61+return outcome?.reason === "hard_timeout";
62+}
63+64+export function isStickyAgentRunTerminalOutcome(
65+outcome: AgentRunTerminalOutcome | undefined | null,
66+): boolean {
67+return outcome?.reason === "hard_timeout" || outcome?.reason === "cancelled";
68+}
69+70+function isCancellationStopReason(value: string | undefined): boolean {
71+return value === "rpc" || value === "stop";
72+}
73+74+export function buildAgentRunTerminalOutcome(
75+input: AgentRunTerminalInput,
76+): AgentRunTerminalOutcome {
77+const stopReason = asNonEmptyString(input.stopReason);
78+const livenessState = asNonEmptyString(input.livenessState);
79+const timeoutPhase = normalizeAgentRunTimeoutPhase(input.timeoutPhase);
80+const providerStarted = normalizeProviderStarted(input.providerStarted);
81+const rawError = asNonEmptyString(input.error);
82+// Queue and gateway-draining timeouts are wait-layer uncertainty. Only
83+// provider-started or provider-phase timeouts are sticky child-run facts.
84+const hardTimeout =
85+input.status === "timeout" &&
86+(isHardAgentRunTimeoutPhase(timeoutPhase) || providerStarted === true);
87+const aborted = isAbortedAgentStopReason(stopReason);
88+// ACP/model `stop` can be a normal successful finish. Treat rpc/stop as
89+// cancellation only for non-success terminal payloads from abort paths.
90+const cancelled = input.status !== "ok" && isCancellationStopReason(stopReason);
91+const blocked = isBlockedLivenessState(livenessState);
92+const error = blocked
93+ ? formatBlockedLivenessError(rawError)
94+ : aborted && !rawError
95+ ? AGENT_RUN_ABORTED_ERROR
96+ : rawError;
97+const reason: AgentRunTerminalReason = blocked
98+ ? "blocked"
99+ : hardTimeout
100+ ? "hard_timeout"
101+ : aborted
102+ ? "aborted"
103+ : cancelled
104+ ? "cancelled"
105+ : input.status === "timeout"
106+ ? "timed_out"
107+ : input.status === "error"
108+ ? "failed"
109+ : "completed";
110+return {
111+ reason,
112+status:
113+reason === "completed"
114+ ? "ok"
115+ : input.status === "timeout" &&
116+(reason === "hard_timeout" || reason === "timed_out" || reason === "cancelled")
117+ ? "timeout"
118+ : "error",
119+ ...(error ? { error } : {}),
120+ ...(stopReason ? { stopReason } : {}),
121+ ...(livenessState ? { livenessState } : {}),
122+ ...(timeoutPhase ? { timeoutPhase } : {}),
123+ ...(providerStarted !== undefined ? { providerStarted } : {}),
124+ ...(asFiniteTimestamp(input.startedAt) !== undefined
125+ ? { startedAt: asFiniteTimestamp(input.startedAt) }
126+ : {}),
127+ ...(asFiniteTimestamp(input.endedAt) !== undefined
128+ ? { endedAt: asFiniteTimestamp(input.endedAt) }
129+ : {}),
130+};
131+}
132+133+function completedBeforeOrAtTimeout(params: {
134+completed: AgentRunTerminalOutcome;
135+timeout: AgentRunTerminalOutcome;
136+}): boolean {
137+return (
138+params.completed.reason === "completed" &&
139+typeof params.completed.endedAt === "number" &&
140+typeof params.timeout.endedAt === "number" &&
141+params.completed.endedAt <= params.timeout.endedAt
142+);
143+}
144+145+export function mergeAgentRunTerminalOutcome(
146+current: AgentRunTerminalOutcome | undefined,
147+incoming: AgentRunTerminalOutcome,
148+): AgentRunTerminalOutcome {
149+if (!current) {
150+return incoming;
151+}
152+if (current.reason === "cancelled") {
153+return current;
154+}
155+// A hard timeout owns the run unless later evidence proves completion ended
156+// before that timeout; late abort/error cleanup must not downgrade it.
157+if (isHardAgentRunTimeoutOutcome(current)) {
158+return completedBeforeOrAtTimeout({ completed: incoming, timeout: current })
159+ ? incoming
160+ : current;
161+}
162+if (incoming.reason === "cancelled") {
163+return incoming;
164+}
165+if (isHardAgentRunTimeoutOutcome(incoming)) {
166+return completedBeforeOrAtTimeout({ completed: current, timeout: incoming })
167+ ? current
168+ : incoming;
169+}
170+return incoming;
171+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。