import { ProviderApprovalDecision, ProviderSessionId, ProviderUserInputAnswers, RuntimeRequestId, ThreadId, } from "@t3tools/contracts"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import { ProjectionStoreV2 } from "./ProjectionStore.ts"; import { ProviderSessionManagerV2 } from "./ProviderSessionManager.ts"; export class RuntimeRequestResponseExecutionError extends Schema.TaggedErrorClass()( "RuntimeRequestResponseExecutionError", { reason: Schema.Literals([ "request-missing", "request-not-ready", "request-not-resumable", "provider-session-not-active", "unexpected-failure", ]), threadId: ThreadId, providerSessionId: ProviderSessionId, requestId: RuntimeRequestId, cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { switch (this.reason) { case "request-missing": return `Runtime request ${this.requestId} no longer exists on thread ${this.threadId}.`; case "request-not-ready": return `Runtime request ${this.requestId} on thread ${this.threadId} is not ready for response execution.`; case "request-not-resumable": return `Runtime request ${this.requestId} on thread ${this.threadId} is not resumable on provider session ${this.providerSessionId}.`; case "provider-session-not-active": return `Provider session ${this.providerSessionId} is not active for runtime request ${this.requestId} on thread ${this.threadId}.`; case "unexpected-failure": return `Failed to respond to runtime request ${this.requestId} on thread ${this.threadId} via provider session ${this.providerSessionId}.`; } } } const isRuntimeRequestResponseExecutionError = Schema.is(RuntimeRequestResponseExecutionError); export interface RuntimeRequestServiceV2Shape { readonly respond: (input: { readonly threadId: ThreadId; readonly providerSessionId: ProviderSessionId; readonly requestId: RuntimeRequestId; readonly decision?: ProviderApprovalDecision; readonly answers?: ProviderUserInputAnswers; }) => Effect.Effect; } export class RuntimeRequestServiceV2 extends Context.Service< RuntimeRequestServiceV2, RuntimeRequestServiceV2Shape >()("t3/orchestration-v2/RuntimeRequestService/RuntimeRequestServiceV2") {} export const layer: Layer.Layer< RuntimeRequestServiceV2, never, ProjectionStoreV2 | ProviderSessionManagerV2 > = Layer.effect( RuntimeRequestServiceV2, Effect.gen(function* () { const projections = yield* ProjectionStoreV2; const sessions = yield* ProviderSessionManagerV2; return RuntimeRequestServiceV2.of({ respond: (input) => Effect.gen(function* () { const request = yield* projections.getRuntimeRequest(input.threadId, input.requestId); if (request === undefined) { return yield* new RuntimeRequestResponseExecutionError({ reason: "request-missing", threadId: input.threadId, providerSessionId: input.providerSessionId, requestId: input.requestId, }); } // Dispatch validates the request while it is pending, then persists the // resolved projection before this effect is executed. if (request.status !== "resolved") { return yield* new RuntimeRequestResponseExecutionError({ reason: "request-not-ready", threadId: input.threadId, providerSessionId: input.providerSessionId, requestId: input.requestId, }); } if ( request.responseCapability.type !== "live" || request.responseCapability.providerSessionId !== input.providerSessionId ) { return yield* new RuntimeRequestResponseExecutionError({ reason: "request-not-resumable", threadId: input.threadId, providerSessionId: input.providerSessionId, requestId: input.requestId, }); } const session = yield* sessions.get(input.providerSessionId); if (Option.isNone(session)) { return yield* new RuntimeRequestResponseExecutionError({ reason: "provider-session-not-active", threadId: input.threadId, providerSessionId: input.providerSessionId, requestId: input.requestId, }); } yield* session.value.respondToRuntimeRequest({ requestId: input.requestId, ...(input.decision === undefined ? {} : { decision: input.decision }), ...(input.answers === undefined ? {} : { answers: input.answers }), }); }).pipe( Effect.mapError((cause) => isRuntimeRequestResponseExecutionError(cause) ? cause : new RuntimeRequestResponseExecutionError({ reason: "unexpected-failure", threadId: input.threadId, providerSessionId: input.providerSessionId, requestId: input.requestId, cause, }), ), ), }); }), );