| 1 | import type { |
| 2 | ModelSelection, |
| 3 | OrchestrationV2Run, |
| 4 | OrchestrationV2ThreadProjection, |
| 5 | OrchestrationV2TurnItem, |
| 6 | ProviderInstanceId, |
| 7 | ServerProvider, |
| 8 | } from "@t3tools/contracts"; |
| 9 | |
| 10 | /** |
| 11 | * The subset of a projection run that handoff rows read. Kept minimal so the |
| 12 | * timeline can hold a content-stable snapshot: run status/timestamps churn on |
| 13 | * every stream event, but these fields only change when a run is added. |
| 14 | */ |
| 15 | export type HandoffTimelineRun = Pick< |
| 16 | OrchestrationV2Run, |
| 17 | "id" | "ordinal" | "providerInstanceId" | "modelSelection" |
| 18 | >; |
| 19 | |
| 20 | export type HandoffEndpoints = ReturnType<typeof resolveHandoffEndpoints>; |
| 21 | |
| 22 | /** Preview the next send's handoff without adding anything to durable history. */ |
| 23 | export function resolvePendingHandoff( |
| 24 | projection: OrchestrationV2ThreadProjection | null | undefined, |
| 25 | selection: ModelSelection | null | undefined, |
| 26 | providers: ReadonlyArray<ServerProvider>, |
| 27 | ): HandoffEndpoints | null { |
| 28 | const from = projection?.runs.findLast( |
| 29 | (run) => run.startedAt !== null && run.status !== "rolled_back", |
| 30 | )?.modelSelection; |
| 31 | if (from === undefined || selection == null) return null; |
| 32 | const source = providers.find((provider) => provider.instanceId === from.instanceId); |
| 33 | const target = providers.find((provider) => provider.instanceId === selection.instanceId); |
| 34 | const needsHandoff = |
| 35 | from.instanceId === selection.instanceId |
| 36 | ? from.model !== selection.model && target?.requiresNewThreadForModelChange === true |
| 37 | : // Accounts sharing a continuation group resume the same native thread. |
| 38 | source?.continuation?.groupKey === undefined || |
| 39 | source.driver !== target?.driver || |
| 40 | source.continuation.groupKey !== target.continuation?.groupKey; |
| 41 | return needsHandoff ? { from: [from], to: selection } : null; |
| 42 | } |
| 43 | |
| 44 | export function resolveHandoffEndpoints( |
| 45 | item: Pick< |
| 46 | Extract<OrchestrationV2TurnItem, { type: "handoff" }>, |
| 47 | "runId" | "fromModelSelections" | "fromProviderInstanceIds" | "toModel" | "toProviderInstanceId" |
| 48 | >, |
| 49 | runs: ReadonlyArray<HandoffTimelineRun>, |
| 50 | ) { |
| 51 | // Items persisted before models were stamped only carry instance ids; |
| 52 | // recover the models from the thread's runs (the handoff's own run is |
| 53 | // the target, the newest earlier run per source instance is the origin). |
| 54 | // Clients fall back to the provider display name when neither source has a model. |
| 55 | const handoffRun = item.runId === null ? undefined : runs.find((run) => run.id === item.runId); |
| 56 | const toModel = |
| 57 | item.toModel ?? |
| 58 | (handoffRun !== undefined && handoffRun.providerInstanceId === item.toProviderInstanceId |
| 59 | ? handoffRun.modelSelection.model |
| 60 | : undefined); |
| 61 | const fromEndpoints: ReadonlyArray<{ |
| 62 | readonly instanceId: ProviderInstanceId; |
| 63 | readonly model?: string | undefined; |
| 64 | }> = |
| 65 | item.fromModelSelections !== undefined && item.fromModelSelections.length > 0 |
| 66 | ? item.fromModelSelections |
| 67 | : item.fromProviderInstanceIds.map((instanceId) => ({ |
| 68 | instanceId, |
| 69 | model: latestRunModelBefore(runs, instanceId, handoffRun?.ordinal), |
| 70 | })); |
| 71 | return { from: fromEndpoints, to: { instanceId: item.toProviderInstanceId, model: toModel } }; |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Model of the newest run for `instanceId` that started before the handoff's |
| 76 | * own run. Legacy handoff items don't record their source models, but the |
| 77 | * covered runs are still in the projection. |
| 78 | */ |
| 79 | function latestRunModelBefore( |
| 80 | runs: ReadonlyArray<HandoffTimelineRun>, |
| 81 | instanceId: ProviderInstanceId, |
| 82 | beforeOrdinal: number | undefined, |
| 83 | ): string | undefined { |
| 84 | let latest: HandoffTimelineRun | undefined; |
| 85 | for (const run of runs) { |
| 86 | if (run.providerInstanceId !== instanceId) continue; |
| 87 | if (beforeOrdinal !== undefined && run.ordinal >= beforeOrdinal) continue; |
| 88 | if (latest === undefined || run.ordinal > latest.ordinal) latest = run; |
| 89 | } |
| 90 | return latest?.modelSelection.model; |
| 91 | } |