1import {
2 OrchestrationV2ContextHandoff,
3 type OrchestrationV2TurnItem,
4 type OrchestrationV2Run,
5 ProviderInstanceId,
6 ProviderThreadId,
7 RunId,
8 ThreadId,
9} from "@t3tools/contracts";
10import * as Context from "effect/Context";
11import * as DateTime from "effect/DateTime";
12import * as Effect from "effect/Effect";
13import * as Layer from "effect/Layer";
14import * as Schema from "effect/Schema";
15
16import {
17 DEFAULT_HANDOFF_TOKEN_CAP,
18 handoffTokenCapConfig,
19 handoffCoverage,
20 historicalMessage,
21 renderHistory,
22 selectHistory,
23} from "@t3tools/provider-core/server/handoffBudget";
24import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
25
26export class ContextHandoffPrepareError extends Schema.TaggedError<ContextHandoffPrepareError>()(
27 "ContextHandoffPrepareError",
28 {
29 threadId: ThreadId,
30 targetRunId: RunId,
31 fromProviderThreadIds: Schema.Array(ProviderThreadId),
32 toProviderThreadId: ProviderThreadId,
33 cause: Schema.optional(Schema.Defect()),
34 },
35) {
36 override get message(): string {
37 return `Failed to prepare context handoff for run ${this.targetRunId} in thread ${this.threadId}.`;
38 }
39}
40
41export const ContextHandoffServiceV2Error = Schema.Union([ContextHandoffPrepareError]);
42export type ContextHandoffServiceV2Error = typeof ContextHandoffServiceV2Error.Type;
43
44export interface ContextHandoffServiceV2Shape {
45 readonly prepareLegacyImport: (input: {
46 readonly threadId: ThreadId;
47 readonly targetRunId: RunId;
48 readonly toProviderThreadId: ProviderThreadId;
49 readonly toProviderInstanceId: ProviderInstanceId;
50 readonly items: ReadonlyArray<OrchestrationV2TurnItem>;
51 readonly createdAt: DateTime.Utc;
52 }) => Effect.Effect<OrchestrationV2ContextHandoff, ContextHandoffServiceV2Error>;
53 readonly prepare: (input: {
54 readonly threadId: ThreadId;
55 readonly targetRunId: RunId;
56 readonly fromProviderThreadIds: ReadonlyArray<ProviderThreadId>;
57 readonly toProviderThreadId: ProviderThreadId;
58 }) => Effect.Effect<OrchestrationV2ContextHandoff, ContextHandoffServiceV2Error>;
59 readonly prepareForkDelta: (input: {
60 readonly sourceThreadId: ThreadId;
61 readonly targetThreadId: ThreadId;
62 readonly targetRunId: RunId;
63 readonly transferId: OrchestrationV2ContextHandoff["transferId"];
64 readonly fromProviderThreadIds: ReadonlyArray<ProviderThreadId>;
65 readonly toProviderThreadId: ProviderThreadId;
66 readonly fromProviderInstanceId: ProviderInstanceId;
67 readonly toProviderInstanceId: ProviderInstanceId;
68 readonly coveredRunOrdinals: OrchestrationV2ContextHandoff["coveredRunOrdinals"];
69 readonly deltaItems: ReadonlyArray<OrchestrationV2TurnItem>;
70 readonly createdAt: DateTime.Utc;
71 }) => Effect.Effect<OrchestrationV2ContextHandoff, ContextHandoffServiceV2Error>;
72 readonly prepareProviderHandoff: (input: {
73 readonly threadId: ThreadId;
74 readonly targetRunId: RunId;
75 readonly transferId: OrchestrationV2ContextHandoff["transferId"];
76 readonly fromProviderThreadIds: ReadonlyArray<ProviderThreadId>;
77 readonly toProviderThreadId: ProviderThreadId;
78 readonly fromProviderInstanceId: ProviderInstanceId;
79 readonly toProviderInstanceId: ProviderInstanceId;
80 readonly coveredRunOrdinals: OrchestrationV2ContextHandoff["coveredRunOrdinals"];
81 readonly runs?: ReadonlyArray<OrchestrationV2Run>;
82 readonly strategy: Extract<
83 OrchestrationV2ContextHandoff["strategy"],
84 "delta_since_target_last_seen" | "full_thread_summary"
85 >;
86 readonly items: ReadonlyArray<OrchestrationV2TurnItem>;
87 readonly createdAt: DateTime.Utc;
88 }) => Effect.Effect<OrchestrationV2ContextHandoff, ContextHandoffServiceV2Error>;
89}
90
91export class ContextHandoffServiceV2 extends Context.Service<
92 ContextHandoffServiceV2,
93 ContextHandoffServiceV2Shape
94>()("t3/orchestration-v2/ContextHandoffService/ContextHandoffServiceV2") {}
95
96function compactText(text: string, maxLength = 240): string {
97 const compacted = text.replace(/\s+/g, " ").trim();
98 if (compacted.length <= maxLength) {
99 return compacted;
100 }
101 return `${compacted.slice(0, maxLength - 3)}...`;
102}
103
104function summarizeDeltaItem(item: OrchestrationV2TurnItem): string | null {
105 switch (item.type) {
106 case "user_message":
107 return `- User: ${compactText(item.text)}`;
108 case "assistant_message":
109 return `- Assistant: ${compactText(item.text)}`;
110 case "command_execution":
111 return `- Command: ${compactText(item.input)}`;
112 case "file_change":
113 return `- File change: ${item.fileName}`;
114 case "checkpoint":
115 return `- Checkpoint: ${item.files.length} files`;
116 case "handoff":
117 return `- Handoff: ${compactText(item.summary ?? item.strategy)}`;
118 default:
119 return null;
120 }
121}
122
123function makeForkDeltaSummary(input: {
124 readonly sourceThreadId: ThreadId;
125 readonly targetThreadId: ThreadId;
126 readonly coveredRunOrdinals: OrchestrationV2ContextHandoff["coveredRunOrdinals"];
127 readonly deltaItems: ReadonlyArray<OrchestrationV2TurnItem>;
128}): string {
129 const itemLines = input.deltaItems.flatMap((item) => {
130 const line = summarizeDeltaItem(item);
131 return line === null ? [] : [line];
132 });
133 return [
134 "Merge-back context from forked conversation.",
135 `Source thread: ${input.sourceThreadId}`,
136 `Target thread: ${input.targetThreadId}`,
137 `Covered fork runs: ${input.coveredRunOrdinals.from}-${input.coveredRunOrdinals.to}`,
138 "",
139 "Fork delta:",
140 ...(itemLines.length === 0 ? ["- No user-visible delta items."] : itemLines),
141 ].join("\n");
142}
143
144function makeLegacyImportSummary(items: ReadonlyArray<OrchestrationV2TurnItem>): string {
145 const sections = items.flatMap((item) => {
146 switch (item.type) {
147 case "user_message":
148 return [{ label: "User", body: item.text }];
149 case "assistant_message":
150 return [{ label: "Assistant", body: item.text }];
151 default:
152 return [];
153 }
154 });
155 const header =
156 "Imported conversation history from the previous T3 Code orchestrator. Use it as context; do not repeat it unless the user asks.";
157 const maxChars = 32_000;
158 const selected: Array<string> = [];
159 let remaining = maxChars - header.length - 2;
160 for (const section of sections.toReversed()) {
161 if (remaining <= 0) break;
162 const fullSection = `${section.label}:\n${section.body}`;
163 if (fullSection.length <= remaining) {
164 selected.unshift(fullSection);
165 remaining -= fullSection.length + 2;
166 continue;
167 }
168
169 const prefix = `${section.label}:\n... `;
170 const bodyBudget = remaining - prefix.length;
171 if (bodyBudget <= 0) continue;
172
173 const initialStart = section.body.length - bodyBudget;
174 let bodyStart = Math.max(0, initialStart);
175 if (
176 bodyStart > 0 &&
177 !/\s/.test(section.body[bodyStart - 1] ?? "") &&
178 !/\s/.test(section.body[bodyStart] ?? "")
179 ) {
180 const nextWhitespace = section.body.slice(bodyStart).search(/\s/);
181 if (nextWhitespace !== -1) {
182 bodyStart += nextWhitespace;
183 }
184 }
185 const codeUnitAtStart = section.body.charCodeAt(bodyStart);
186 const codeUnitBeforeStart = section.body.charCodeAt(bodyStart - 1);
187 if (
188 codeUnitAtStart >= 0xdc00 &&
189 codeUnitAtStart <= 0xdfff &&
190 codeUnitBeforeStart >= 0xd800 &&
191 codeUnitBeforeStart <= 0xdbff
192 ) {
193 bodyStart += 1;
194 }
195 const bodySuffix = section.body.slice(bodyStart).trimStart();
196 if (bodySuffix.length === 0) continue;
197
198 const suffix = `${prefix}${bodySuffix}`;
199 selected.unshift(suffix);
200 remaining -= suffix.length + 2;
201 }
202 return [header, ...selected].join("\n\n");
203}
204
205export function providerMessageWithContextHandoff(input: {
206 readonly handoff: OrchestrationV2ContextHandoff;
207 readonly userText: string;
208}): string {
209 return providerMessageWithContextHandoffs({
210 handoffs: [input.handoff],
211 userText: input.userText,
212 });
213}
214
215function providerMessageWithContextHandoffs(input: {
216 readonly handoffs: ReadonlyArray<OrchestrationV2ContextHandoff>;
217 readonly userText: string;
218}): string {
219 const handoffSections = input.handoffs.flatMap((handoff) => {
220 const label =
221 handoff.strategy === "fork_delta_summary"
222 ? "merge_back / fork_delta_summary"
223 : handoff.strategy;
224 return [`Context handoff (${label}):`, handoff.summaryText, ""];
225 });
226 return [...handoffSections, "User message:", input.userText].join("\n");
227}
228
229const makeContextHandoffService = Effect.fn("orchestrationV2.ContextHandoffService.layer")(
230 function* () {
231 const idAllocator = yield* IdAllocator.IdAllocatorV2;
232 const tokenCap = yield* handoffTokenCapConfig.pipe(
233 Effect.orElseSucceed(() => DEFAULT_HANDOFF_TOKEN_CAP),
234 );
235
236 const prepareLegacyImport = Effect.fn("orchestrationV2.contextHandoff.prepareLegacyImport")(
237 function* (input: {
238 readonly threadId: ThreadId;
239 readonly targetRunId: RunId;
240 readonly toProviderThreadId: ProviderThreadId;
241 readonly toProviderInstanceId: ProviderInstanceId;
242 readonly items: ReadonlyArray<OrchestrationV2TurnItem>;
243 readonly createdAt: DateTime.Utc;
244 }) {
245 const handoffId = yield* idAllocator.allocate
246 .contextHandoff({
247 threadId: input.threadId,
248 fromProviderInstanceId: ProviderInstanceId.make("legacy"),
249 toProviderInstanceId: input.toProviderInstanceId,
250 })
251 .pipe(
252 Effect.mapError(
253 (cause) =>
254 new ContextHandoffPrepareError({
255 threadId: input.threadId,
256 targetRunId: input.targetRunId,
257 fromProviderThreadIds: [],
258 toProviderThreadId: input.toProviderThreadId,
259 cause,
260 }),
261 ),
262 );
263 const coverage = handoffCoverage({ ...input, coveredRunOrdinals: { from: 1, to: 1 } });
264 const selected = selectHistory({
265 messages: input.items.flatMap((item) => {
266 const message = historicalMessage(item);
267 return message === null ? [] : [message];
268 }),
269 coverage,
270 budget: tokenCap,
271 });
272 return {
273 id: handoffId,
274 transferId: null,
275 threadId: input.threadId,
276 targetRunId: input.targetRunId,
277 fromProviderThreadIds: [],
278 toProviderThreadId: input.toProviderThreadId,
279 coveredRunOrdinals: { from: 1, to: 1 },
280 strategy: "manual_context",
281 status: "ready",
282 summaryMessageId: null,
283 summaryText: makeLegacyImportSummary(input.items),
284 history: {
285 messages: selected.messages,
286 coverage,
287 omittedItems: selected.omittedItems,
288 omittedItemIds: selected.omittedItemIds,
289 },
290 createdByProviderInstanceId: null,
291 createdAt: input.createdAt,
292 updatedAt: input.createdAt,
293 } satisfies OrchestrationV2ContextHandoff;
294 },
295 );
296
297 const prepare = Effect.fn("orchestrationV2.contextHandoff.prepare")(function* (input: {
298 readonly threadId: ThreadId;
299 readonly targetRunId: RunId;
300 readonly fromProviderThreadIds: ReadonlyArray<ProviderThreadId>;
301 readonly toProviderThreadId: ProviderThreadId;
302 }) {
303 const now = yield* DateTime.now;
304 const handoffId = yield* idAllocator.allocate
305 .contextHandoff({
306 threadId: input.threadId,
307 fromProviderInstanceId: ProviderInstanceId.make("manual"),
308 toProviderInstanceId: ProviderInstanceId.make("manual"),
309 })
310 .pipe(
311 Effect.mapError(
312 (cause) =>
313 new ContextHandoffPrepareError({
314 threadId: input.threadId,
315 targetRunId: input.targetRunId,
316 fromProviderThreadIds: Array.from(input.fromProviderThreadIds),
317 toProviderThreadId: input.toProviderThreadId,
318 cause,
319 }),
320 ),
321 );
322 return {
323 id: handoffId,
324 transferId: null,
325 threadId: input.threadId,
326 targetRunId: input.targetRunId,
327 fromProviderThreadIds: Array.from(input.fromProviderThreadIds),
328 toProviderThreadId: input.toProviderThreadId,
329 coveredRunOrdinals: { from: 1, to: 1 },
330 strategy: "manual_context",
331 status: "ready",
332 summaryMessageId: null,
333 summaryText: "Manual context handoff.",
334 createdByProviderInstanceId: null,
335 createdAt: now,
336 updatedAt: now,
337 } satisfies OrchestrationV2ContextHandoff;
338 });
339
340 const prepareForkDelta = Effect.fn("orchestrationV2.contextHandoff.prepareForkDelta")(
341 function* (input: {
342 readonly sourceThreadId: ThreadId;
343 readonly targetThreadId: ThreadId;
344 readonly targetRunId: RunId;
345 readonly transferId: OrchestrationV2ContextHandoff["transferId"];
346 readonly fromProviderThreadIds: ReadonlyArray<ProviderThreadId>;
347 readonly toProviderThreadId: ProviderThreadId;
348 readonly fromProviderInstanceId: ProviderInstanceId;
349 readonly toProviderInstanceId: ProviderInstanceId;
350 readonly coveredRunOrdinals: OrchestrationV2ContextHandoff["coveredRunOrdinals"];
351 readonly deltaItems: ReadonlyArray<OrchestrationV2TurnItem>;
352 readonly createdAt: DateTime.Utc;
353 }) {
354 const handoffId = yield* idAllocator.allocate
355 .contextHandoff({
356 threadId: input.targetThreadId,
357 fromProviderInstanceId: input.fromProviderInstanceId,
358 toProviderInstanceId: input.toProviderInstanceId,
359 })
360 .pipe(
361 Effect.mapError(
362 (cause) =>
363 new ContextHandoffPrepareError({
364 threadId: input.targetThreadId,
365 targetRunId: input.targetRunId,
366 fromProviderThreadIds: Array.from(input.fromProviderThreadIds),
367 toProviderThreadId: input.toProviderThreadId,
368 cause,
369 }),
370 ),
371 );
372 const coverage = handoffCoverage({
373 threadId: input.sourceThreadId,
374 coveredRunOrdinals: input.coveredRunOrdinals,
375 items: input.deltaItems,
376 });
377 const selected = selectHistory({
378 messages: input.deltaItems.flatMap((item) => {
379 const message = historicalMessage(item);
380 return message === null ? [] : [message];
381 }),
382 coverage,
383 budget: tokenCap,
384 });
385 return {
386 id: handoffId,
387 transferId: input.transferId,
388 threadId: input.targetThreadId,
389 targetRunId: input.targetRunId,
390 fromProviderThreadIds: Array.from(input.fromProviderThreadIds),
391 toProviderThreadId: input.toProviderThreadId,
392 coveredRunOrdinals: input.coveredRunOrdinals,
393 strategy: "fork_delta_summary",
394 status: "ready",
395 summaryMessageId: null,
396 summaryText: makeForkDeltaSummary(input),
397 history: {
398 messages: selected.messages,
399 coverage,
400 omittedItems: selected.omittedItems,
401 omittedItemIds: selected.omittedItemIds,
402 },
403 createdByProviderInstanceId: null,
404 createdAt: input.createdAt,
405 updatedAt: input.createdAt,
406 } satisfies OrchestrationV2ContextHandoff;
407 },
408 );
409
410 const prepareProviderHandoff = Effect.fn(
411 "orchestrationV2.contextHandoff.prepareProviderHandoff",
412 )(function* (input: {
413 readonly threadId: ThreadId;
414 readonly targetRunId: RunId;
415 readonly transferId: OrchestrationV2ContextHandoff["transferId"];
416 readonly fromProviderThreadIds: ReadonlyArray<ProviderThreadId>;
417 readonly toProviderThreadId: ProviderThreadId;
418 readonly fromProviderInstanceId: ProviderInstanceId;
419 readonly toProviderInstanceId: ProviderInstanceId;
420 readonly coveredRunOrdinals: OrchestrationV2ContextHandoff["coveredRunOrdinals"];
421 readonly runs?: ReadonlyArray<OrchestrationV2Run>;
422 readonly strategy: Extract<
423 OrchestrationV2ContextHandoff["strategy"],
424 "delta_since_target_last_seen" | "full_thread_summary"
425 >;
426 readonly items: ReadonlyArray<OrchestrationV2TurnItem>;
427 readonly createdAt: DateTime.Utc;
428 }) {
429 const handoffId = yield* idAllocator.allocate
430 .contextHandoff({
431 threadId: input.threadId,
432 fromProviderInstanceId: input.fromProviderInstanceId,
433 toProviderInstanceId: input.toProviderInstanceId,
434 })
435 .pipe(
436 Effect.mapError(
437 (cause) =>
438 new ContextHandoffPrepareError({
439 threadId: input.threadId,
440 targetRunId: input.targetRunId,
441 fromProviderThreadIds: Array.from(input.fromProviderThreadIds),
442 toProviderThreadId: input.toProviderThreadId,
443 cause,
444 }),
445 ),
446 );
447 const runStatuses = new Map(input.runs?.map((run) => [run.id, run.status]));
448 const coverage = handoffCoverage(input);
449 const selected = selectHistory({
450 messages: input.items.flatMap((item) => {
451 const message = historicalMessage(item);
452 return message === null
453 ? []
454 : [
455 {
456 ...message,
457 ...(item.runId === null ? {} : { runStatus: runStatuses.get(item.runId) }),
458 },
459 ];
460 }),
461 coverage,
462 budget: tokenCap,
463 });
464 return {
465 id: handoffId,
466 transferId: input.transferId,
467 threadId: input.threadId,
468 targetRunId: input.targetRunId,
469 fromProviderThreadIds: Array.from(input.fromProviderThreadIds),
470 toProviderThreadId: input.toProviderThreadId,
471 coveredRunOrdinals: input.coveredRunOrdinals,
472 strategy: input.strategy,
473 status: "ready",
474 summaryMessageId: null,
475 summaryText: renderHistory(selected.messages, selected.context),
476 history: {
477 messages: selected.messages,
478 coverage,
479 omittedItems: selected.omittedItems,
480 omittedItemIds: selected.omittedItemIds,
481 },
482 createdByProviderInstanceId: null,
483 createdAt: input.createdAt,
484 updatedAt: input.createdAt,
485 } satisfies OrchestrationV2ContextHandoff;
486 });
487
488 return ContextHandoffServiceV2.of({
489 prepareLegacyImport,
490 prepare,
491 prepareForkDelta,
492 prepareProviderHandoff,
493 });
494 },
495);
496
497export const layer: Layer.Layer<ContextHandoffServiceV2, never, IdAllocator.IdAllocatorV2> =
498 Layer.effect(ContextHandoffServiceV2, makeContextHandoffService());