1import {
2 ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE,
3 type OrchestrationV2Run,
4 type OrchestrationV2TurnItem,
5} from "@t3tools/contracts";
6
7const WORKSPACE_PREPARATION_INPUT = "Preparing workspace";
8
9/**
10 * Workspace setup is client bookkeeping; preparation failures have their own
11 * error item. A retry cancels that item, which then has nothing left to say.
12 */
13export function turnItemIsWorkspacePreparation(item: OrchestrationV2TurnItem): boolean {
14 return (
15 (item.type === "command_execution" && item.input === WORKSPACE_PREPARATION_INPUT) ||
16 (item.type === "error" &&
17 item.status === "cancelled" &&
18 item.failure.code === ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE)
19 );
20}
21
22/**
23 * Runs a Retry can prepare again: their workspace preparation failed and the
24 * run still ended there. Older servers record no preparation on the run, so
25 * they never offer it.
26 */
27export function workspacePreparationRetryRunIds(
28 runs: ReadonlyArray<Pick<OrchestrationV2Run, "id" | "status" | "workspacePreparation">>,
29 items: ReadonlyArray<OrchestrationV2TurnItem>,
30): ReadonlySet<OrchestrationV2Run["id"]> {
31 const failedRuns = new Set(
32 runs.flatMap((run) =>
33 run.status === "failed" && run.workspacePreparation !== undefined ? [run.id] : [],
34 ),
35 );
36 const retryable = new Set<OrchestrationV2Run["id"]>();
37 if (failedRuns.size === 0) return retryable;
38 for (const item of items) {
39 if (
40 item.type === "error" &&
41 item.status === "failed" &&
42 item.failure.code === ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE &&
43 item.runId !== null &&
44 failedRuns.has(item.runId)
45 )
46 retryable.add(item.runId);
47 }
48 return retryable;
49}