Squash merge of https://github.com/morluto/rea/pull/2710. Original pull request submitted by @XiaoBinGan. Co-authored-by: 吴佳浩 <wujiahao@supremind.com>
78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
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<Result<AnalysisExecution, AnalysisError>>;
|
|
}
|
|
|
|
/**
|
|
* 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<TRequest>,
|
|
provider: ProviderOperationPort<TRequest>,
|
|
confidence: "observed" | "derived",
|
|
): Promise<Result<Evidence, AnalysisError>> => {
|
|
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);
|
|
};
|