/** * Request/response specification — declares a request/response client for a flow. * * Enables FlowProcessor handlers to make request/response calls to other services * (e.g., calling the prompt service or LLM from within a knowledge extraction handler). * * Python reference: trustgraph-base/trustgraph/base/prompt_client_spec.py */ import { Effect } from "effect"; import type { Spec } from "./types.js"; import type { Flow, FlowDefinition } from "../processor/flow.js"; import { flowResourceNotFoundError, type FlowResourceNotFoundError, } from "../errors.js"; import { type EffectRequestResponse, RequestResponseFactory, } from "../messaging/runtime.js"; declare const RequestResponseSpecType: unique symbol; export interface RequestResponseSpec extends Spec { readonly [RequestResponseSpecType]?: { readonly request: TReq; readonly response: TRes; }; readonly requestorEffect: ( flow: Flow, ) => Effect.Effect, FlowResourceNotFoundError>; } export function makeRequestResponseSpec( name: string, requestTopicName: string, responseTopicName: string, ): RequestResponseSpec { const requestors = new WeakMap>(); const registerRequestor = ( flow: Flow, requestor: EffectRequestResponse, ) => Effect.sync(() => { requestors.set(flow, requestor); }); const unregisterRequestor = ( flow: Flow, requestor: EffectRequestResponse, ) => Effect.sync(() => { if (requestors.get(flow) === requestor) { requestors.delete(flow); } }); const requestorEffect = ( flow: Flow, ): Effect.Effect, FlowResourceNotFoundError> => { const requestor = requestors.get(flow); return requestor === undefined ? Effect.fail(flowResourceNotFoundError(flow.name, "requestor", name)) : Effect.succeed(requestor); }; const addEffect = Effect.fn("RequestResponseSpec.addEffect")(function* ( flow: Flow, definition: FlowDefinition, ) { const requestTopic = definition.topics?.[requestTopicName] ?? requestTopicName; const responseTopic = definition.topics?.[responseTopicName] ?? responseTopicName; const factory = yield* RequestResponseFactory; const requestor = yield* factory.make({ requestTopic, responseTopic, subscription: `${flow.processorId}-${flow.name}-${name}`, }); flow.registerRequestor(name, requestor); yield* registerRequestor(flow, requestor); yield* Effect.addFinalizer(() => unregisterRequestor(flow, requestor)); }); return { name, requestorEffect, addEffect, add: (flow, pubsub, definition, context) => flow.runInCompatibilityScope(addEffect(flow, definition), pubsub, context), }; }