import { z } from "zod"; import type { AnalysisError } from "../domain/analysisErrorBase.js"; import { AnalysisCancelledError, AnalysisInputError, } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; import { jsonObjectSchema } from "../domain/jsonValue.js"; import { err, ok, type Result } from "../domain/result.js"; import type { AnalysisExecution, ExecutionOptions, } from "./AnalysisProvider.js"; /** Validated caller-selected request carrying its operation input. */ export interface ProviderOperationRequest { readonly input: unknown; } /** Provider boundary executing one validated caller-selected request. */ export interface ProviderOperationPort< TRequest extends ProviderOperationRequest, > { execute( request: TRequest, options?: ExecutionOptions, ): Promise>; } /** * Shared CLI/MCP admission, execution, and Evidence composition for * single-request providers. The caller retains its operation vocabulary and * confidence; this normalizes cancellation, input validation, and Evidence * assembly once instead of per provider. */ export const executeProviderOperation = async < TRequest extends ProviderOperationRequest, >( operation: string, input: unknown, options: ExecutionOptions | undefined, schema: z.ZodType, provider: ProviderOperationPort, confidence: "observed" | "derived", ): Promise> => { if (options?.signal?.aborted) return err(new AnalysisCancelledError(operation)); const request = schema.safeParse({ operation, input }); if (!request.success) return err(new AnalysisInputError(operation, { cause: request.error })); const executed = await provider.execute(request.data, options); if (!executed.ok) return executed; const execution = executed.value; const evidence = createEvidence( execution.subject ?? undefined, execution.provider, { operation, parameters: jsonObjectSchema.parse(request.data.input), result: execution.result, rawResult: execution.rawResult, limitations: execution.limitations, locations: execution.locations, confidence, }, ); // A provider that cannot observe the signal may finish its work after the // caller cancelled, so re-check rather than reporting a completed operation // the caller no longer wants. The observation it completed is still this // provider's Evidence, so the cancellation carries it as the partial // observation instead of discarding a finished result. if (options?.signal?.aborted) return err( new AnalysisCancelledError(operation, { partialObservation: evidence }), ); return ok(evidence); };