| 1 | import type { OrchestrationV2ThreadProjection, TurnItemId } from "@t3tools/contracts"; |
| 2 | |
| 3 | type Projection = OrchestrationV2ThreadProjection; |
| 4 | |
| 5 | export 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 | |
| 20 | export 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 | */ |
| 40 | export 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 | |
| 119 | function sameEntities<A>(left: ReadonlyArray<A>, right: ReadonlyArray<A>): boolean { |
| 120 | return left.length === right.length && left.every((value, index) => value === right[index]); |
| 121 | } |
| 122 | |
| 123 | export 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 | } |