1import {
2 CheckpointId,
3 CheckpointScopeId,
4 CommandId,
5 ContextHandoffId,
6 ContextTransferId,
7 EventId,
8 MessageId,
9 NodeId,
10 PlanId,
11 ProjectId,
12 ProviderDriverKind,
13 ProviderInstanceId,
14 ProviderSessionId,
15 ProviderThreadId,
16 ProviderTurnId,
17 RawEventId,
18 RunAttemptId,
19 RunId,
20 RuntimeRequestId,
21 ThreadId,
22 TurnItemId,
23} from "@t3tools/contracts";
24import * as Context from "effect/Context";
25import * as Effect from "effect/Effect";
26import * as Layer from "effect/Layer";
27import * as Schema from "effect/Schema";
28
29import { randomUuidV4 } from "./randomUuid.ts";
30
31export const IdAllocatorV2Kind = Schema.Literals([
32 "command",
33 "event",
34 "raw_event",
35 "project",
36 "thread",
37 "message",
38 "run",
39 "run_attempt",
40 "node",
41 "provider_session",
42 "provider_thread",
43 "provider_turn",
44 "runtime_request",
45 "turn_item",
46 "checkpoint_scope",
47 "checkpoint",
48 "context_handoff",
49 "context_transfer",
50 "plan",
51]);
52export type IdAllocatorV2Kind = typeof IdAllocatorV2Kind.Type;
53
54export class IdAllocatorV2AllocationError extends Schema.TaggedError<IdAllocatorV2AllocationError>()(
55 "IdAllocatorV2AllocationError",
56 {
57 kind: IdAllocatorV2Kind,
58 input: Schema.optional(Schema.Unknown),
59 cause: Schema.optional(Schema.Defect()),
60 },
61) {
62 override get message(): string {
63 return `Failed to allocate orchestration ${this.kind} id.`;
64 }
65}
66
67export const IdAllocatorV2Error = Schema.Union([IdAllocatorV2AllocationError]);
68export type IdAllocatorV2Error = typeof IdAllocatorV2Error.Type;
69
70export interface IdAllocatorV2Allocate {
71 readonly command: (input: {
72 readonly fixtureName: string;
73 readonly commandName: string;
74 }) => Effect.Effect<CommandId, IdAllocatorV2Error>;
75 readonly event: (input: {
76 readonly threadId?: ThreadId;
77 readonly commandId?: CommandId;
78 readonly providerSessionId?: ProviderSessionId;
79 }) => Effect.Effect<EventId, IdAllocatorV2Error>;
80 readonly rawEvent: (input: {
81 readonly providerSessionId: ProviderSessionId;
82 readonly method: string | null;
83 }) => Effect.Effect<RawEventId, IdAllocatorV2Error>;
84 readonly project: (input: {
85 readonly fixtureName: string;
86 }) => Effect.Effect<ProjectId, IdAllocatorV2Error>;
87 readonly thread: (input: {
88 readonly fixtureName?: string;
89 readonly projectId?: ProjectId;
90 }) => Effect.Effect<ThreadId, IdAllocatorV2Error>;
91 readonly message: (input: {
92 readonly threadId: ThreadId;
93 readonly ordinal: number;
94 }) => Effect.Effect<MessageId, IdAllocatorV2Error>;
95 readonly providerSession: (input: {
96 readonly providerInstanceId: ProviderInstanceId;
97 readonly threadId: ThreadId;
98 }) => Effect.Effect<ProviderSessionId, IdAllocatorV2Error>;
99 readonly runtimeRequest: (input: {
100 readonly driver: ProviderDriverKind;
101 readonly providerTurnId?: ProviderTurnId;
102 readonly nativeRequestId?: string;
103 }) => Effect.Effect<RuntimeRequestId, IdAllocatorV2Error>;
104 readonly checkpointScope: (input: {
105 readonly threadId: ThreadId;
106 readonly name: string;
107 }) => Effect.Effect<CheckpointScopeId, IdAllocatorV2Error>;
108 readonly checkpoint: (input: {
109 readonly checkpointScopeId: CheckpointScopeId;
110 readonly name: string;
111 }) => Effect.Effect<CheckpointId, IdAllocatorV2Error>;
112 readonly contextHandoff: (input: {
113 readonly threadId: ThreadId;
114 readonly fromProviderInstanceId: ProviderInstanceId;
115 readonly toProviderInstanceId: ProviderInstanceId;
116 }) => Effect.Effect<ContextHandoffId, IdAllocatorV2Error>;
117 readonly contextTransfer: (input: {
118 readonly sourceThreadId: ThreadId;
119 readonly targetThreadId: ThreadId;
120 readonly type: string;
121 }) => Effect.Effect<ContextTransferId, IdAllocatorV2Error>;
122 readonly plan: (input: {
123 readonly threadId: ThreadId;
124 readonly runId?: RunId;
125 readonly driver: ProviderDriverKind;
126 }) => Effect.Effect<PlanId, IdAllocatorV2Error>;
127}
128
129export interface IdAllocatorV2Derive {
130 readonly providerSession: (input: {
131 readonly providerInstanceId: ProviderInstanceId;
132 }) => ProviderSessionId;
133 readonly delegatedTaskNode: (input: { readonly commandId: CommandId }) => NodeId;
134 readonly delegatedTaskThread: (input: { readonly commandId: CommandId }) => ThreadId;
135 readonly delegatedTaskMessage: (input: { readonly commandId: CommandId }) => MessageId;
136 readonly delegatedTaskTurnItem: (input: { readonly commandId: CommandId }) => TurnItemId;
137 readonly createdThreadTurnItem: (input: { readonly commandId: CommandId }) => TurnItemId;
138 readonly threadFromProviderThread: (input: {
139 readonly driver: ProviderDriverKind;
140 readonly nativeThreadId: string;
141 readonly providerInstanceId?: ProviderInstanceId;
142 }) => ThreadId;
143 readonly run: (input: { readonly threadId: ThreadId; readonly ordinal: number }) => RunId;
144 readonly runAttempt: (input: {
145 readonly runId: RunId;
146 readonly attemptOrdinal: number;
147 }) => RunAttemptId;
148 readonly rootNode: (input: { readonly runId: RunId }) => NodeId;
149 readonly rootNodeAttempt: (input: {
150 readonly runId: RunId;
151 readonly attemptOrdinal: number;
152 }) => NodeId;
153 readonly userTurnItem: (input: { readonly messageId: MessageId }) => TurnItemId;
154 readonly runSignalTurnItem: (input: {
155 readonly runId: RunId;
156 readonly signal: string;
157 }) => TurnItemId;
158 readonly providerThread: (input: {
159 readonly driver: ProviderDriverKind;
160 readonly nativeThreadId: string;
161 readonly providerInstanceId?: ProviderInstanceId;
162 }) => ProviderThreadId;
163 readonly providerTurn: (input: {
164 readonly driver: ProviderDriverKind;
165 readonly nativeTurnId: string;
166 }) => ProviderTurnId;
167 readonly nodeFromProviderItem: (input: {
168 readonly driver: ProviderDriverKind;
169 readonly nativeItemId: string;
170 }) => NodeId;
171 readonly messageFromProviderItem: (input: {
172 readonly driver: ProviderDriverKind;
173 readonly nativeItemId: string;
174 }) => MessageId;
175 readonly turnItemFromProviderItem: (input: {
176 readonly driver: ProviderDriverKind;
177 readonly nativeItemId: string;
178 }) => TurnItemId;
179 readonly approvalNode: (input: { readonly requestId: RuntimeRequestId }) => NodeId;
180 readonly approvalTurnItem: (input: { readonly requestId: RuntimeRequestId }) => TurnItemId;
181}
182
183export class IdAllocatorV2 extends Context.Service<
184 IdAllocatorV2,
185 {
186 readonly allocate: IdAllocatorV2Allocate;
187 readonly derive: IdAllocatorV2Derive;
188 }
189>()("@t3tools/provider-core/server/IdAllocator/IdAllocatorV2") {}
190
191const encodePart = (part: string | number): string => encodeURIComponent(String(part));
192
193const joinId = (prefix: string, ...parts: ReadonlyArray<string | number>): string =>
194 [prefix, ...parts.map(encodePart)].join(":");
195
196export function deriveThreadFromProviderThread(input: {
197 readonly driver: ProviderDriverKind;
198 readonly providerInstanceId?: ProviderInstanceId;
199 readonly nativeThreadId: string;
200}): ThreadId {
201 return ThreadId.make(
202 joinId(
203 "thread",
204 "provider",
205 input.driver,
206 ...(input.providerInstanceId === undefined
207 ? []
208 : ["provider-instance", input.providerInstanceId]),
209 "native-thread",
210 input.nativeThreadId,
211 ),
212 );
213}
214
215export function deriveProviderThread(input: {
216 readonly driver: ProviderDriverKind;
217 readonly providerInstanceId?: ProviderInstanceId;
218 readonly nativeThreadId: string;
219}): ProviderThreadId {
220 return ProviderThreadId.make(
221 joinId(
222 "provider-thread",
223 "provider",
224 input.driver,
225 ...(input.providerInstanceId === undefined
226 ? []
227 : ["provider-instance", input.providerInstanceId]),
228 "native-thread",
229 input.nativeThreadId,
230 ),
231 );
232}
233
234const randomId =
235 <Id, Input>(input: {
236 readonly kind: IdAllocatorV2Kind;
237 readonly prefix: string;
238 readonly parts: ReadonlyArray<string | number>;
239 readonly make: (value: string) => Id;
240 }) =>
241 (allocationInput: Input): Effect.Effect<Id, IdAllocatorV2Error> =>
242 randomUuidV4.pipe(
243 Effect.map((uuid) => input.make(joinId(input.prefix, ...input.parts, uuid))),
244 Effect.mapError(
245 (cause) =>
246 new IdAllocatorV2AllocationError({
247 kind: input.kind,
248 input: allocationInput,
249 cause,
250 }),
251 ),
252 );
253
254/** Deterministic id derivations; pure, so callers that only derive need no allocator. */
255export const derive: IdAllocatorV2Derive = {
256 providerSession: (input) =>
257 ProviderSessionId.make(
258 joinId("provider-session", "provider-instance", input.providerInstanceId, "shared"),
259 ),
260 delegatedTaskNode: (input) => NodeId.make(joinId("node", "delegated-task", input.commandId)),
261 delegatedTaskThread: (input) =>
262 ThreadId.make(joinId("thread", "delegated-task", input.commandId)),
263 delegatedTaskMessage: (input) =>
264 MessageId.make(joinId("message", "delegated-task", input.commandId)),
265 delegatedTaskTurnItem: (input) =>
266 TurnItemId.make(joinId("turn-item", "delegated-task", input.commandId)),
267 createdThreadTurnItem: (input) =>
268 TurnItemId.make(joinId("turn-item", "created-thread", input.commandId)),
269 threadFromProviderThread: deriveThreadFromProviderThread,
270 run: (input) => RunId.make(joinId("run", "thread", input.threadId, "ordinal", input.ordinal)),
271 runAttempt: (input) =>
272 RunAttemptId.make(joinId("run-attempt", "run", input.runId, "attempt", input.attemptOrdinal)),
273 rootNode: (input) => NodeId.make(joinId("node", "run", input.runId, "root")),
274 rootNodeAttempt: (input) =>
275 NodeId.make(joinId("node", "run", input.runId, "attempt", input.attemptOrdinal, "root")),
276 userTurnItem: (input) => TurnItemId.make(joinId("turn-item", "message", input.messageId)),
277 runSignalTurnItem: (input) =>
278 TurnItemId.make(joinId("turn-item", "run", input.runId, "signal", input.signal)),
279 providerThread: deriveProviderThread,
280 providerTurn: (input) =>
281 ProviderTurnId.make(
282 joinId("provider-turn", "provider", input.driver, "native-turn", input.nativeTurnId),
283 ),
284 nodeFromProviderItem: (input) =>
285 NodeId.make(joinId("node", "provider", input.driver, "native-item", input.nativeItemId)),
286 messageFromProviderItem: (input) =>
287 MessageId.make(joinId("message", "provider", input.driver, "native-item", input.nativeItemId)),
288 turnItemFromProviderItem: (input) =>
289 TurnItemId.make(
290 joinId("turn-item", "provider", input.driver, "native-item", input.nativeItemId),
291 ),
292 approvalNode: (input) => NodeId.make(joinId("node", "runtime-request", input.requestId)),
293 approvalTurnItem: (input) =>
294 TurnItemId.make(joinId("turn-item", "runtime-request", input.requestId)),
295};
296
297export const layer: Layer.Layer<IdAllocatorV2> = Layer.succeed(
298 IdAllocatorV2,
299 IdAllocatorV2.of({
300 allocate: {
301 command: (input) =>
302 randomId<CommandId, typeof input>({
303 kind: "command",
304 prefix: "command",
305 parts: ["fixture", input.fixtureName, input.commandName],
306 make: CommandId.make,
307 })(input),
308 event: (input) =>
309 randomId<EventId, typeof input>({
310 kind: "event",
311 prefix: "event",
312 parts: [
313 ...(input.threadId === undefined ? [] : ["thread", input.threadId]),
314 ...(input.commandId === undefined ? [] : ["command", input.commandId]),
315 ...(input.providerSessionId === undefined
316 ? []
317 : ["provider-session", input.providerSessionId]),
318 ],
319 make: EventId.make,
320 })(input),
321 rawEvent: (input) =>
322 randomId<RawEventId, typeof input>({
323 kind: "raw_event",
324 prefix: "raw-event",
325 parts: [
326 "provider-session",
327 input.providerSessionId,
328 ...(input.method === null ? [] : ["method", input.method]),
329 ],
330 make: RawEventId.make,
331 })(input),
332 project: (input) =>
333 randomId<ProjectId, typeof input>({
334 kind: "project",
335 prefix: "project",
336 parts: ["fixture", input.fixtureName],
337 make: ProjectId.make,
338 })(input),
339 thread: (input) =>
340 randomId<ThreadId, typeof input>({
341 kind: "thread",
342 prefix: "thread",
343 parts: [
344 ...(input.fixtureName === undefined ? [] : ["fixture", input.fixtureName]),
345 ...(input.projectId === undefined ? [] : ["project", input.projectId]),
346 ],
347 make: ThreadId.make,
348 })(input),
349 message: (input) =>
350 randomId<MessageId, typeof input>({
351 kind: "message",
352 prefix: "message",
353 parts: ["thread", input.threadId, "ordinal", input.ordinal],
354 make: MessageId.make,
355 })(input),
356 providerSession: (input) =>
357 randomId<ProviderSessionId, typeof input>({
358 kind: "provider_session",
359 prefix: "provider-session",
360 parts: ["provider-instance", input.providerInstanceId, "thread", input.threadId],
361 make: ProviderSessionId.make,
362 })(input),
363 runtimeRequest: (input) =>
364 randomId<RuntimeRequestId, typeof input>({
365 kind: "runtime_request",
366 prefix: "runtime-request",
367 parts: [
368 "provider",
369 input.driver,
370 ...(input.providerTurnId === undefined ? [] : ["provider-turn", input.providerTurnId]),
371 ...(input.nativeRequestId === undefined
372 ? []
373 : ["native-request", input.nativeRequestId]),
374 ],
375 make: RuntimeRequestId.make,
376 })(input),
377 checkpointScope: (input) =>
378 Effect.succeed(
379 CheckpointScopeId.make(
380 joinId("checkpoint-scope", "thread", input.threadId, "name", input.name),
381 ),
382 ),
383 checkpoint: (input) =>
384 Effect.succeed(
385 CheckpointId.make(
386 joinId("checkpoint", "scope", input.checkpointScopeId, "name", input.name),
387 ),
388 ),
389 contextHandoff: (input) =>
390 randomId<ContextHandoffId, typeof input>({
391 kind: "context_handoff",
392 prefix: "context-handoff",
393 parts: [
394 "thread",
395 input.threadId,
396 "from-provider-instance",
397 input.fromProviderInstanceId,
398 "to-provider-instance",
399 input.toProviderInstanceId,
400 ],
401 make: ContextHandoffId.make,
402 })(input),
403 contextTransfer: (input) =>
404 randomId<ContextTransferId, typeof input>({
405 kind: "context_transfer",
406 prefix: "context-transfer",
407 parts: [
408 "type",
409 input.type,
410 "source-thread",
411 input.sourceThreadId,
412 "target-thread",
413 input.targetThreadId,
414 ],
415 make: ContextTransferId.make,
416 })(input),
417 plan: (input) =>
418 randomId<PlanId, typeof input>({
419 kind: "plan",
420 prefix: "plan",
421 parts: [
422 "thread",
423 input.threadId,
424 "provider",
425 input.driver,
426 ...(input.runId === undefined ? [] : ["run", input.runId]),
427 ],
428 make: PlanId.make,
429 })(input),
430 },
431 derive,
432 }),
433);