| 1 | import { |
| 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"; |
| 24 | import * as Context from "effect/Context"; |
| 25 | import * as Effect from "effect/Effect"; |
| 26 | import * as Layer from "effect/Layer"; |
| 27 | import * as Schema from "effect/Schema"; |
| 28 | |
| 29 | import { randomUuidV4 } from "./randomUuid.ts"; |
| 30 | |
| 31 | export 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 | ]); |
| 52 | export type IdAllocatorV2Kind = typeof IdAllocatorV2Kind.Type; |
| 53 | |
| 54 | export 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 | |
| 67 | export const IdAllocatorV2Error = Schema.Union([IdAllocatorV2AllocationError]); |
| 68 | export type IdAllocatorV2Error = typeof IdAllocatorV2Error.Type; |
| 69 | |
| 70 | export 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 | |
| 129 | export 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 | |
| 183 | export 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 | |
| 191 | const encodePart = (part: string | number): string => encodeURIComponent(String(part)); |
| 192 | |
| 193 | const joinId = (prefix: string, ...parts: ReadonlyArray<string | number>): string => |
| 194 | [prefix, ...parts.map(encodePart)].join(":"); |
| 195 | |
| 196 | export 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 | |
| 215 | export 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 | |
| 234 | const 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. */ |
| 255 | export 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 | |
| 297 | export 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 | ); |