| 1 | import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; |
| 2 | import { threadSupportsProviderHandoff } from "@t3tools/client-runtime/state/thread-workflows"; |
| 3 | import type { OrchestrationV2ThreadProjection } from "@t3tools/contracts"; |
| 4 | |
| 5 | type ThreadStartMarkers = Pick< |
| 6 | EnvironmentThreadShell, |
| 7 | "latestRun" | "latestUserMessageAt" | "runtime" |
| 8 | >; |
| 9 | |
| 10 | function threadShellHasStarted(thread: ThreadStartMarkers | null | undefined): boolean { |
| 11 | return Boolean( |
| 12 | thread && |
| 13 | (thread.latestRun !== null || thread.latestUserMessageAt !== null || thread.runtime !== null), |
| 14 | ); |
| 15 | } |
| 16 | |
| 17 | /** |
| 18 | * Whether this thread's model picker may offer providers other than the one it |
| 19 | * runs on, matching web's locked-provider rule: a thread that never ran a turn |
| 20 | * is bound to nothing, and a started thread stays on its provider only when its |
| 21 | * session cannot hand the conversation to another one. |
| 22 | */ |
| 23 | export function threadAllowsProviderSwitch(input: { |
| 24 | readonly thread: ThreadStartMarkers | null | undefined; |
| 25 | readonly projection: OrchestrationV2ThreadProjection | null | undefined; |
| 26 | }): boolean { |
| 27 | return input.projection != null |
| 28 | ? threadSupportsProviderHandoff(input.projection) |
| 29 | : !threadShellHasStarted(input.thread); |
| 30 | } |