mirror of
https://github.com/morluto/rea.git
synced 2026-11-08 15:56:35 +00:00
* fix(providers): report provider-selection diagnostics when the selected provider refuses its profile An explicitly selected provider that refuses the target during profile resolution now yields the target_unsupported selection error with its candidates and rejections, keeping the refusal's message and, when present, its specific recovery. Fixes #3930 * fix(providers): keep refusal evidence and cleanup ownership on selection failures A refusal with unconfirmed cleanup is returned unchanged; otherwise the selection failure carries the refusal's captured output, diagnostics, partial observation and cause. Registry tests cover the copied recovery and the cleanup guard.
99 lines
3.9 KiB
TypeScript
99 lines
3.9 KiB
TypeScript
import type { JsonValue } from "./jsonValue.js";
|
|
import {
|
|
AnalysisError,
|
|
type AnalysisCapturedOutput,
|
|
type AnalysisErrorOptions,
|
|
} from "./analysisErrorBase.js";
|
|
|
|
/** Stable failure classes for target-bound deep-provider selection. */
|
|
export type ProviderSelectionFailureReason =
|
|
| "unknown_provider"
|
|
| "provider_unavailable"
|
|
| "target_unsupported"
|
|
| "ambiguous"
|
|
| "invalid_options";
|
|
|
|
/** One actionable candidate rejection retained in caller diagnostics. */
|
|
export interface ProviderSelectionRejection {
|
|
readonly providerId: string;
|
|
readonly code: string;
|
|
readonly reason: string;
|
|
readonly diagnostics: Readonly<Record<string, JsonValue>>;
|
|
}
|
|
|
|
/** Complete typed context for one failed deep-provider selection. */
|
|
export interface ProviderSelectionErrorOptions extends Pick<
|
|
AnalysisErrorOptions,
|
|
"cause" | "diagnostics" | "partialObservation"
|
|
> {
|
|
readonly capturedOutput?: AnalysisCapturedOutput | undefined;
|
|
readonly userMessage?: string;
|
|
/** Recovery known to the provider that refused the target, when it has one. */
|
|
readonly remediationAction?: string;
|
|
readonly operation?: string;
|
|
readonly reason: ProviderSelectionFailureReason;
|
|
readonly requestedProviderId: string;
|
|
readonly candidateIds: readonly string[];
|
|
readonly rejections?: readonly ProviderSelectionRejection[];
|
|
}
|
|
|
|
/** No configured provider can satisfy a requested operation or target binding. */
|
|
export class ProviderSelectionError extends AnalysisError {
|
|
readonly _tag = "ProviderSelectionError";
|
|
readonly operation: string;
|
|
readonly reason: ProviderSelectionFailureReason;
|
|
readonly requestedProviderId: string;
|
|
readonly candidateIds: readonly string[];
|
|
readonly rejections: readonly ProviderSelectionRejection[];
|
|
readonly remediationAction: string | undefined;
|
|
override readonly userMessage: string;
|
|
|
|
constructor(options: ProviderSelectionErrorOptions) {
|
|
const operation = options.operation ?? "open_binary";
|
|
super(providerSelectionDiagnostic(options), {
|
|
capturedOutput: options.capturedOutput,
|
|
diagnostics: options.diagnostics,
|
|
partialObservation: options.partialObservation,
|
|
...(options.cause === undefined ? {} : { cause: options.cause }),
|
|
});
|
|
this.operation = operation;
|
|
this.reason = options.reason;
|
|
this.requestedProviderId = options.requestedProviderId;
|
|
this.candidateIds = [...options.candidateIds];
|
|
this.rejections = [...(options.rejections ?? [])].map((rejection) => ({
|
|
...rejection,
|
|
diagnostics: structuredClone(rejection.diagnostics),
|
|
}));
|
|
this.remediationAction = options.remediationAction;
|
|
this.userMessage =
|
|
options.userMessage ?? providerSelectionUserMessage(options);
|
|
}
|
|
}
|
|
|
|
const providerSelectionDiagnostic = (
|
|
options: ProviderSelectionErrorOptions,
|
|
): string => {
|
|
const candidates =
|
|
options.candidateIds.length === 0
|
|
? "none"
|
|
: options.candidateIds.join(", ");
|
|
return `Analysis provider selection failed (${options.reason}) for ${options.requestedProviderId}; candidates: ${candidates}`;
|
|
};
|
|
|
|
const providerSelectionUserMessage = (
|
|
options: ProviderSelectionErrorOptions,
|
|
): string => {
|
|
switch (options.reason) {
|
|
case "ambiguous":
|
|
return "Multiple analysis providers support this target. Choose one with provider_id, --provider, or REA_ANALYSIS_PROVIDER.";
|
|
case "unknown_provider":
|
|
return `The requested analysis provider is unknown. Choose one of: ${options.candidateIds.join(", ") || "no configured providers"}.`;
|
|
case "provider_unavailable":
|
|
return "The selected analysis provider is unavailable. Review the reported local diagnostics or run `rea doctor`, then retry.";
|
|
case "target_unsupported":
|
|
return "The selected analysis provider does not support this target. Choose another provider or open a supported extracted binary.";
|
|
case "invalid_options":
|
|
return "The analysis provider selection or open options are invalid. Correct them and try again.";
|
|
}
|
|
};
|