1import type {
2 OrchestrationV2Run,
3 OrchestrationV2RunAttempt,
4 OrchestrationV2TurnItem,
5 OrchestrationV2UserMessageInputIntent,
6} from "@t3tools/contracts";
7
8type TimelineRun = Pick<OrchestrationV2Run, "id" | "status">;
9type TimelineRunAttempt = Pick<OrchestrationV2RunAttempt, "runId" | "rootNodeId" | "status">;
10type TimelineTurnItem = Pick<OrchestrationV2TurnItem, "type" | "runId" | "nodeId"> & {
11 readonly inputIntent?: OrchestrationV2UserMessageInputIntent;
12};
13
14export function isOrchestrationV2SupersededInterrupt(input: {
15 readonly item: TimelineTurnItem;
16 readonly attempts: ReadonlyArray<TimelineRunAttempt>;
17 readonly items: ReadonlyArray<TimelineTurnItem>;
18}): boolean {
19 const { item } = input;
20 if (item.type !== "run_interrupt_result" || item.runId === null || item.nodeId === null) {
21 return false;
22 }
23
24 const isSuperseded = input.attempts.some(
25 (attempt) =>
26 attempt.runId === item.runId &&
27 attempt.rootNodeId === item.nodeId &&
28 attempt.status === "superseded",
29 );
30 if (!isSuperseded) {
31 return false;
32 }
33
34 // Paired stop-then-steer results have a matching request on the same run and
35 // must stay visible. Legacy plain-steer results have no request and stay hidden.
36 const hasMatchingRequest = input.items.some(
37 (candidate) => candidate.type === "run_interrupt_request" && candidate.runId === item.runId,
38 );
39 return !hasMatchingRequest;
40}
41
42export function isOrchestrationV2TurnItemVisible(input: {
43 readonly item: TimelineTurnItem;
44 readonly runs: ReadonlyArray<TimelineRun>;
45 readonly attempts: ReadonlyArray<TimelineRunAttempt>;
46 readonly items: ReadonlyArray<TimelineTurnItem>;
47}): boolean {
48 const { item } = input;
49 if (
50 item.runId !== null &&
51 input.runs.some((run) => run.id === item.runId && run.status === "rolled_back")
52 ) {
53 return false;
54 }
55
56 // A queued message is composer state, not conversation history. When its run
57 // is cancelled the input never reached the provider, so it must not surface
58 // as a transcript row.
59 if (
60 item.type === "user_message" &&
61 item.inputIntent === "queued_turn" &&
62 item.runId !== null &&
63 input.runs.some((run) => run.id === item.runId && run.status === "cancelled")
64 ) {
65 return false;
66 }
67
68 return !isOrchestrationV2SupersededInterrupt({
69 item,
70 attempts: input.attempts,
71 items: input.items,
72 });
73}
74
75/** Index once when reconciling a whole timeline after run/attempt state changes. */
76export function createOrchestrationV2TurnItemVisibility(input: {
77 readonly runs: ReadonlyArray<TimelineRun>;
78 readonly attempts: ReadonlyArray<TimelineRunAttempt>;
79 readonly items: ReadonlyArray<TimelineTurnItem>;
80}): (item: TimelineTurnItem) => boolean {
81 const statuses = new Map(input.runs.map((run) => [run.id, run.status]));
82 const supersededRoots = new Map<
83 TimelineRunAttempt["runId"],
84 Set<TimelineRunAttempt["rootNodeId"]>
85 >();
86 for (const attempt of input.attempts) {
87 if (attempt.status !== "superseded") continue;
88 let roots = supersededRoots.get(attempt.runId);
89 if (roots === undefined) supersededRoots.set(attempt.runId, (roots = new Set()));
90 roots.add(attempt.rootNodeId);
91 }
92 const interruptRuns = new Set(
93 input.items.filter((item) => item.type === "run_interrupt_request").map((item) => item.runId),
94 );
95 return (item) => {
96 const status = item.runId === null ? undefined : statuses.get(item.runId);
97 if (status === "rolled_back") return false;
98 if (
99 status === "cancelled" &&
100 item.type === "user_message" &&
101 item.inputIntent === "queued_turn"
102 )
103 return false;
104 return !(
105 item.type === "run_interrupt_result" &&
106 item.runId !== null &&
107 item.nodeId !== null &&
108 supersededRoots.get(item.runId)?.has(item.nodeId) === true &&
109 !interruptRuns.has(item.runId)
110 );
111 };
112}