Skip to content
Source
Sign inSign up

DispatchModeLimit.ts

112 lines4.1 KB

1import {
2 isRuntimeModeAbove,
3 type OrchestrationV2DomainEvent,
4 type ProviderInteractionMode,
5 type RuntimeMode,
6 type ThreadId,
7} from "@t3tools/contracts";
8import * as Context from "effect/Context";
9import type * as Ref from "effect/Ref";
10​
11export interface DispatchModes {
12 readonly runtimeMode: RuntimeMode;
13 readonly interactionMode: ProviderInteractionMode;
15​
16export interface DispatchModeRefusal extends DispatchModes {
17 readonly threadId: ThreadId;
18 readonly mode: "runtime" | "interaction";
20​
21/** Recheck target modes under the command lock; the user can raise them after preflight. */
22export interface DispatchModeLimitValue extends DispatchModes {
23 readonly refused?: Ref.Ref<DispatchModeRefusal | undefined>;
25​
26export const DispatchModeLimit = Context.Reference<DispatchModeLimitValue | undefined>(
27 "t3/orchestration-v2/DispatchModeLimit",
28 { defaultValue: () => undefined },
29);
30​
31const interactionModeRank: Record<ProviderInteractionMode, number> = { plan: 0, default: 1 };
32​
33export const exceededDispatchModeLimit = (
34 limit: DispatchModes,
35 modes: DispatchModes,
36): "runtime" | "interaction" | undefined =>
37 isRuntimeModeAbove(modes.runtimeMode, limit.runtimeMode)
38 ? "runtime"
39 : interactionModeRank[modes.interactionMode] > interactionModeRank[limit.interactionMode]
40 ? "interaction"
41 : undefined;
42​
43/** The administrator's `limits.maxRuntimeMode`, which applies to every caller, the user included. */
44export interface RuntimeModeCeiling {
45 readonly organization: string;
46 readonly maxRuntimeMode: RuntimeMode;
48​
49/** A caller's limit, lowered to the ceiling when the caller may otherwise go above it. */
50export const narrowToCeiling = <Limit extends DispatchModes>(
51 limit: Limit | undefined,
52 ceiling: RuntimeModeCeiling | undefined,
53): Limit | undefined =>
54 limit === undefined ||
55 ceiling === undefined ||
56 !isRuntimeModeAbove(limit.runtimeMode, ceiling.maxRuntimeMode)
57 ? limit
58 : { ...limit, runtimeMode: ceiling.maxRuntimeMode };
59​
60export interface RuntimeModeCeilingRefusal {
61 readonly threadId: ThreadId;
62 readonly runtimeMode: RuntimeMode;
63 /** `mode`: a thread would be created in or switched to the mode. `turn`: a turn would run in it. */
64 readonly refused: "mode" | "turn";
66​
67/**
68 * What a planned command would do above the ceiling, judged by its events so every command that
69 * creates a thread, switches its mode, or starts or steers a turn is covered. `runtimeModes` holds
70 * the committed mode of each thread a planned run belongs to. Lowering a mode, and commands that
71 * start no work, stay open on a thread above the ceiling: they are how its user gets back under.
72 */
73export const runtimeModeCeilingRefusal = (
74 maxRuntimeMode: RuntimeMode,
75 events: ReadonlyArray<OrchestrationV2DomainEvent>,
76 runtimeModes: ReadonlyMap<ThreadId, RuntimeMode>,
77): RuntimeModeCeilingRefusal | undefined => {
78 const planned = new Map(runtimeModes);
79 for (const event of events) {
80 if (event.type === "thread.created" || event.type === "thread.runtime-mode-updated") {
81 const { runtimeMode } = event.payload;
82 planned.set(event.threadId, runtimeMode);
83 if (isRuntimeModeAbove(runtimeMode, maxRuntimeMode)) {
84 return { threadId: event.threadId, runtimeMode, refused: "mode" };
85 }
86 } else if (event.type === "run.created" || event.type === "run-attempt.created") {
87 const runtimeMode = planned.get(event.threadId);
88 if (runtimeMode !== undefined && isRuntimeModeAbove(runtimeMode, maxRuntimeMode)) {
89 return { threadId: event.threadId, runtimeMode, refused: "turn" };
90 }
91 }
92 }
93 return undefined;
94};
95​
96/** Threads whose committed mode `runtimeModeCeilingRefusal` needs: those a planned run belongs to. */
97export const threadsStartingWork = (
98 events: ReadonlyArray<OrchestrationV2DomainEvent>,
99): ReadonlySet<ThreadId> => {
100 const created = new Set<ThreadId>();
101 const starting = new Set<ThreadId>();
102 for (const event of events) {
103 if (event.type === "thread.created") created.add(event.threadId);
104 else if (
105 (event.type === "run.created" || event.type === "run-attempt.created") &&
106 !created.has(event.threadId)
107 ) {
108 starting.add(event.threadId);
109 }
110 }
111 return starting;
112};