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>()( "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<void, RuntimeRequestResponseExecutionError>;} 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, }), ), ), }); }),);
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>()(
"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<void, RuntimeRequestResponseExecutionError>;
}
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,
}),
),
),
});
}),
);