1import type { OrchestrationV2ThreadProjection, TurnItemId } from "@t3tools/contracts";
2
3type Projection = OrchestrationV2ThreadProjection;
4
5export interface V2ItemSupport {
6 readonly item: Projection["turnItems"][number] | null;
7 readonly run: Projection["runs"][number] | null;
8 readonly attempts: ReadonlyArray<Projection["attempts"][number]>;
9 readonly node: Projection["nodes"][number] | null;
10 readonly providerSession: Projection["providerSessions"][number] | null;
11 readonly providerThread: Projection["providerThreads"][number] | null;
12 readonly providerTurn: Projection["providerTurns"][number] | null;
13 readonly runtimeRequest: Projection["runtimeRequests"][number] | null;
14 readonly checkpoint: Projection["checkpoints"][number] | null;
15 readonly subagent: Projection["subagents"][number] | null;
16 readonly contextHandoff: Projection["contextHandoffs"][number] | null;
17 readonly contextTransfer: Projection["contextTransfers"][number] | null;
18}
19
20export const EMPTY_V2_ITEM_SUPPORT: V2ItemSupport = Object.freeze({
21 item: null,
22 run: null,
23 attempts: Object.freeze([]),
24 node: null,
25 providerSession: null,
26 providerThread: null,
27 providerTurn: null,
28 runtimeRequest: null,
29 checkpoint: null,
30 subagent: null,
31 contextHandoff: null,
32 contextTransfer: null,
33});
34
35/**
36 * Resolves the relational V2 entities that enrich one projected item. The
37 * caller supplies the item's source-thread projection, including for inherited
38 * rows, so every returned entity retains its original projection identity.
39 */
40export function resolveV2ItemSupport(projection: Projection, itemId: TurnItemId): V2ItemSupport {
41 const item =
42 projection.turnItems.find((candidate) => candidate.id === itemId) ??
43 projection.visibleTurnItems.find((candidate) => candidate.sourceItemId === itemId)?.item ??
44 null;
45 if (item === null) return EMPTY_V2_ITEM_SUPPORT;
46
47 const run =
48 item.runId === null
49 ? null
50 : (projection.runs.find((candidate) => candidate.id === item.runId) ?? null);
51 const attempts =
52 item.runId === null
53 ? EMPTY_V2_ITEM_SUPPORT.attempts
54 : projection.attempts.filter((candidate) => candidate.runId === item.runId);
55 const node =
56 item.nodeId === null
57 ? null
58 : (projection.nodes.find((candidate) => candidate.id === item.nodeId) ?? null);
59 const providerThread =
60 item.providerThreadId === null || item.providerThreadId === undefined
61 ? null
62 : (projection.providerThreads.find((candidate) => candidate.id === item.providerThreadId) ??
63 null);
64 const providerSession =
65 providerThread?.providerSessionId == null
66 ? null
67 : (projection.providerSessions.find(
68 (candidate) => candidate.id === providerThread.providerSessionId,
69 ) ?? null);
70 const providerTurn =
71 item.providerTurnId === null
72 ? null
73 : (projection.providerTurns.find((candidate) => candidate.id === item.providerTurnId) ??
74 null);
75 const requestId =
76 item.type === "approval_request" || item.type === "user_input_request"
77 ? item.requestId
78 : node?.runtimeRequestId;
79 const runtimeRequest =
80 requestId == null
81 ? null
82 : (projection.runtimeRequests.find((candidate) => candidate.id === requestId) ?? null);
83 const checkpoint =
84 item.type === "checkpoint"
85 ? (projection.checkpoints.find((candidate) => candidate.id === item.checkpointId) ?? null)
86 : null;
87 const subagent =
88 item.type === "subagent"
89 ? (projection.subagents.find((candidate) => candidate.id === item.subagentId) ?? null)
90 : null;
91 const contextHandoff =
92 item.type === "handoff"
93 ? (projection.contextHandoffs.find((candidate) => candidate.id === item.contextHandoffId) ??
94 null)
95 : null;
96 const contextTransfer =
97 contextHandoff?.transferId == null
98 ? null
99 : (projection.contextTransfers.find(
100 (candidate) => candidate.id === contextHandoff.transferId,
101 ) ?? null);
102
103 return {
104 item,
105 run,
106 attempts,
107 node,
108 providerSession,
109 providerThread,
110 providerTurn,
111 runtimeRequest,
112 checkpoint,
113 subagent,
114 contextHandoff,
115 contextTransfer,
116 };
117}
118
119function sameEntities<A>(left: ReadonlyArray<A>, right: ReadonlyArray<A>): boolean {
120 return left.length === right.length && left.every((value, index) => value === right[index]);
121}
122
123export function v2ItemSupportEqual(left: V2ItemSupport, right: V2ItemSupport): boolean {
124 return (
125 left.item === right.item &&
126 left.run === right.run &&
127 sameEntities(left.attempts, right.attempts) &&
128 left.node === right.node &&
129 left.providerSession === right.providerSession &&
130 left.providerThread === right.providerThread &&
131 left.providerTurn === right.providerTurn &&
132 left.runtimeRequest === right.runtimeRequest &&
133 left.checkpoint === right.checkpoint &&
134 left.subagent === right.subagent &&
135 left.contextHandoff === right.contextHandoff &&
136 left.contextTransfer === right.contextTransfer
137 );
138}