1import {
2 ProviderInstanceId,
3 RunId,
4 type OrchestrationV2ThreadProjection,
5 type ServerProvider,
6} from "@t3tools/contracts";
7import { describe, expect, it } from "vite-plus/test";
8import {
9 resolveHandoffEndpoints,
10 resolvePendingHandoff,
11 type HandoffTimelineRun,
12} from "./handoff.ts";
13
14const from = ProviderInstanceId.make("codex_personal");
15const to = ProviderInstanceId.make("claudeAgent");
16const item = {
17 runId: RunId.make("target"),
18 fromProviderInstanceIds: [from],
19 toProviderInstanceId: to,
20};
21const run = (
22 id: string,
23 ordinal: number,
24 instanceId: ProviderInstanceId,
25 model: string,
26): HandoffTimelineRun => ({
27 id: RunId.make(id),
28 ordinal,
29 providerInstanceId: instanceId,
30 modelSelection: { instanceId, model },
31});
32
33describe("handoff endpoints shared by web and mobile", () => {
34 it("preserves stamped models including several models from the same provider", () => {
35 const fromModelSelections = [
36 { instanceId: from, model: "source-a" },
37 { instanceId: from, model: "source-b" },
38 ];
39 expect(
40 resolveHandoffEndpoints({ ...item, fromModelSelections, toModel: "destination" }, [
41 run("target", 2, to, "later-model"),
42 ]),
43 ).toEqual({
44 from: fromModelSelections,
45 to: { instanceId: to, model: "destination" },
46 });
47 });
48
49 it("recovers legacy models from the handoff run and latest earlier source run", () => {
50 const runs = [
51 run("later", 4, from, "wrong-later-model"),
52 run("old", 1, from, "old-model"),
53 run("target", 3, to, "destination"),
54 run("source", 2, from, "source-model"),
55 ];
56 expect(resolveHandoffEndpoints(item, runs)).toEqual({
57 from: [{ instanceId: from, model: "source-model" }],
58 to: { instanceId: to, model: "destination" },
59 });
60 });
61
62 it("retains provider identities when historical runs are not loaded", () => {
63 expect(resolveHandoffEndpoints(item, [])).toEqual({
64 from: [{ instanceId: from, model: undefined }],
65 to: { instanceId: to, model: undefined },
66 });
67 });
68
69 it("does not borrow a target model from another provider", () => {
70 expect(
71 resolveHandoffEndpoints(item, [run("target", 2, from, "wrong-model")]).to.model,
72 ).toBeUndefined();
73 });
74});
75
76const projection = {
77 runs: [{ ...run("source", 1, from, "source"), startedAt: "2026-10-11T00:00:00Z" }],
78} as unknown as OrchestrationV2ThreadProjection;
79
80describe("pending context handoff", () => {
81 it("appears on selection, updates the destination, and disappears on returning", () => {
82 expect(resolvePendingHandoff(projection, { instanceId: to, model: "first" }, [])).toEqual({
83 from: [{ instanceId: from, model: "source" }],
84 to: { instanceId: to, model: "first" },
85 });
86 expect(
87 resolvePendingHandoff(projection, { instanceId: to, model: "second" }, [])?.to.model,
88 ).toBe("second");
89 expect(resolvePendingHandoff(projection, { instanceId: from, model: "source" }, [])).toBeNull();
90 expect(resolvePendingHandoff(projection, { instanceId: from, model: "other" }, [])).toBeNull();
91 });
92 it("only previews a same-provider model change when it needs a fresh native thread", () => {
93 const providers = [
94 { instanceId: from, requiresNewThreadForModelChange: true },
95 ] as unknown as ServerProvider[];
96 expect(
97 resolvePendingHandoff(projection, { instanceId: from, model: "other" }, providers)?.to.model,
98 ).toBe("other");
99 });
100 it("resumes compatible account overlays without showing a handoff", () => {
101 const providers = [from, to].map((instanceId) => ({
102 instanceId,
103 driver: "codex",
104 continuation: { groupKey: "shared" },
105 })) as unknown as ServerProvider[];
106 expect(
107 resolvePendingHandoff(projection, { instanceId: to, model: "target" }, providers),
108 ).toBeNull();
109 });
110});