1import type {
2 ModelSelection,
3 ProviderDriverKind,
4 ProviderInteractionMode,
5 RuntimeMode,
6} from "@t3tools/contracts";
7import { modelSelectionsEqual } from "@t3tools/shared/model";
8
9import type { ProviderContinuationIdentity } from "@t3tools/provider-core/server/driver";
10import type { ProviderSelectionTransitionPlan } from "@t3tools/provider-core/server/selectionTransition";
11
12export type ProviderSessionTransition =
13 | { readonly type: "reuse" }
14 | { readonly type: "switch_model_in_session" }
15 | { readonly type: "restart_and_resume" }
16 | { readonly type: "create_with_handoff" }
17 | { readonly type: "reject"; readonly reason: string };
18
19export interface ProviderSessionTransitionState {
20 readonly driver: ProviderDriverKind;
21 readonly continuationIdentity: ProviderContinuationIdentity;
22 readonly modelSelection: ModelSelection;
23 readonly runtimeMode: RuntimeMode;
24 readonly interactionMode: ProviderInteractionMode;
25 readonly workspace: string;
26}
27
28export interface ProviderSessionTransitionTarget extends ProviderSessionTransitionState {
29 readonly available: boolean;
30}
31
32export function decideProviderSessionTransition(input: {
33 readonly current: ProviderSessionTransitionState | null;
34 readonly target: ProviderSessionTransitionTarget;
35 readonly selectionTransition?: ProviderSelectionTransitionPlan;
36}): ProviderSessionTransition {
37 if (!input.target.available) {
38 return { type: "reject", reason: "The target provider instance is unavailable." };
39 }
40 if (input.current === null) {
41 return { type: "create_with_handoff" };
42 }
43
44 const current = input.current;
45 const target = input.target;
46 const continuationCompatible =
47 current.continuationIdentity.driverKind === target.continuationIdentity.driverKind &&
48 current.continuationIdentity.continuationKey === target.continuationIdentity.continuationKey;
49 if (!continuationCompatible || current.driver !== target.driver) {
50 return { type: "create_with_handoff" };
51 }
52
53 const instanceChanged = current.modelSelection.instanceId !== target.modelSelection.instanceId;
54 const runtimeChanged = current.runtimeMode !== target.runtimeMode;
55 const workspaceChanged = current.workspace !== target.workspace;
56 const selectionChanged = !modelSelectionsEqual(current.modelSelection, target.modelSelection);
57 if (selectionChanged && !instanceChanged) {
58 switch (input.selectionTransition?.type) {
59 case "create_with_handoff":
60 return { type: "create_with_handoff" };
61 case "reject":
62 return { type: "reject", reason: input.selectionTransition.reason };
63 case undefined:
64 return {
65 type: "reject",
66 reason: "The provider adapter did not classify the selection change.",
67 };
68 case "apply_on_next_turn":
69 case "restart_session":
70 break;
71 }
72 }
73 if (instanceChanged || runtimeChanged || workspaceChanged) {
74 return { type: "restart_and_resume" };
75 }
76
77 if (selectionChanged) {
78 switch (input.selectionTransition?.type) {
79 case "apply_on_next_turn":
80 return { type: "switch_model_in_session" };
81 case "restart_session":
82 return { type: "restart_and_resume" };
83 }
84 }
85
86 // Apply interaction mode on the next turn.
87 return { type: "reuse" };
88}