1import { assert, it } from "@effect/vitest";
2import {
3 MessageId,
4 ProviderInstanceId,
5 ProviderThreadId,
6 RunId,
7 ThreadId,
8 TurnItemId,
9 type OrchestrationV2TurnItem,
10} from "@t3tools/contracts";
11import * as DateTime from "effect/DateTime";
12import * as Effect from "effect/Effect";
13import * as Layer from "effect/Layer";
14
15import * as ContextHandoffService from "./ContextHandoffService.ts";
16import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
17
18const layerTest = ContextHandoffService.layer.pipe(Layer.provide(IdAllocator.layer));
19
20function importedItem(
21 input:
22 | {
23 readonly role: "user";
24 readonly id: string;
25 readonly text: string;
26 readonly ordinal: number;
27 }
28 | {
29 readonly role: "assistant";
30 readonly id: string;
31 readonly text: string;
32 readonly ordinal: number;
33 },
34): OrchestrationV2TurnItem {
35 const now = DateTime.makeUnsafe("2026-01-01T00:00:00.000Z");
36 const base = {
37 id: TurnItemId.make(`turn-item:${input.id}`),
38 threadId: ThreadId.make("thread:legacy-context"),
39 runId: null,
40 nodeId: null,
41 providerThreadId: null,
42 providerTurnId: null,
43 nativeItemRef: null,
44 parentItemId: null,
45 ordinal: input.ordinal,
46 status: "completed" as const,
47 title: null,
48 startedAt: now,
49 completedAt: now,
50 updatedAt: now,
51 messageId: MessageId.make(`message:${input.id}`),
52 text: input.text,
53 };
54 return input.role === "user"
55 ? {
56 ...base,
57 createdBy: "user",
58 creationSource: "server",
59 type: "user_message",
60 inputIntent: "turn_start",
61 attachments: [],
62 }
63 : {
64 ...base,
65 type: "assistant_message",
66 streaming: false,
67 };
68}
69
70it.layer(layerTest)("ContextHandoffService legacy import", (it) => {
71 it.effect("prepares imported history for the first native v2 turn", () =>
72 Effect.gen(function* () {
73 const service = yield* ContextHandoffService.ContextHandoffServiceV2;
74 const handoff = yield* service.prepareLegacyImport({
75 threadId: ThreadId.make("thread:legacy-context"),
76 targetRunId: RunId.make("run:first-v2"),
77 toProviderThreadId: ProviderThreadId.make("provider-thread:first-v2"),
78 toProviderInstanceId: ProviderInstanceId.make("codex"),
79 items: [
80 importedItem({ role: "user", id: "one", text: "What did we decide?", ordinal: 1 }),
81 importedItem({
82 role: "assistant",
83 id: "two",
84 text: "We decided to keep the migration lightweight.",
85 ordinal: 2,
86 }),
87 ],
88 createdAt: DateTime.makeUnsafe("2026-01-02T00:00:00.000Z"),
89 });
90
91 assert.equal(handoff.strategy, "manual_context");
92 assert.deepStrictEqual(handoff.fromProviderThreadIds, []);
93 assert.include(handoff.summaryText, "What did we decide?");
94 assert.include(handoff.summaryText, "keep the migration lightweight");
95 const providerMessage = ContextHandoffService.providerMessageWithContextHandoff({
96 handoff,
97 userText: "Continue from there.",
98 });
99 assert.include(providerMessage, handoff.summaryText);
100 assert.include(providerMessage, "User message:\nContinue from there.");
101 }),
102 );
103
104 it.effect("preserves role attribution when truncating imported history", () =>
105 Effect.gen(function* () {
106 const service = yield* ContextHandoffService.ContextHandoffServiceV2;
107 const handoff = yield* service.prepareLegacyImport({
108 threadId: ThreadId.make("thread:legacy-context"),
109 targetRunId: RunId.make("run:first-v2"),
110 toProviderThreadId: ProviderThreadId.make("provider-thread:first-v2"),
111 toProviderInstanceId: ProviderInstanceId.make("codex"),
112 items: [
113 importedItem({
114 role: "user",
115 id: "long",
116 text: `${"x".repeat(35_000)} retained final words`,
117 ordinal: 1,
118 }),
119 ],
120 createdAt: DateTime.makeUnsafe("2026-01-02T00:00:00.000Z"),
121 });
122
123 assert.isAtMost(handoff.summaryText.length, 32_000);
124 assert.include(handoff.summaryText, "User:\n... retained final words");
125 assert.notMatch(handoff.summaryText, /\n+x+ retained final words/);
126 }),
127 );
128
129 it.effect("retains the newest oversized import even when it has no whitespace", () =>
130 Effect.gen(function* () {
131 const service = yield* ContextHandoffService.ContextHandoffServiceV2;
132 const handoff = yield* service.prepareLegacyImport({
133 threadId: ThreadId.make("thread:legacy-context"),
134 targetRunId: RunId.make("run:first-v2"),
135 toProviderThreadId: ProviderThreadId.make("provider-thread:first-v2"),
136 toProviderInstanceId: ProviderInstanceId.make("codex"),
137 items: [
138 importedItem({
139 role: "assistant",
140 id: "older",
141 text: "older message",
142 ordinal: 1,
143 }),
144 importedItem({
145 role: "user",
146 id: "long-single-token",
147 text: `${"🧪".repeat(20_000)}LATEST_SINGLE_TOKEN`,
148 ordinal: 2,
149 }),
150 ],
151 createdAt: DateTime.makeUnsafe("2026-01-02T00:00:00.000Z"),
152 });
153
154 assert.include(
155 handoff.history?.omittedItemIds ?? [],
156 TurnItemId.make("turn-item:long-single-token"),
157 );
158 assert.isAtMost(handoff.summaryText.length, 32_000);
159 assert.include(handoff.summaryText, "User:\n... ");
160 assert.include(handoff.summaryText, "LATEST_SINGLE_TOKEN");
161 assert.notInclude(handoff.summaryText, "\ufffd");
162 }),
163 );
164});