import type { NormalizedLLMError } from "./llmClient.js"; import { type ResultValue } from "./result.js"; export type LLMRetryReason = "timeout" | "connectionLost" | "streamInterrupted" | "rateLimit" | "serverError" | "overloaded" | "invalidStructuredOutput"; export type Classification = { kind: "retryable"; reason: LLMRetryReason; detail: string; retryAfterMs?: number; } | { kind: "terminal"; detail: string; } | { kind: "abort"; }; export declare function classifyLlmError(err: unknown, normalized: NormalizedLLMError): Classification; export type RetryPolicy = { /** Transport retries: the provider failed to answer (timeout, 5xx, * dropped connection). Independent of validationRetries. */ retries: number; timeout: number; backoff: { initial: number; factor: number; max: number; }; /** Validation retries: the provider answered, but the structured output * failed schema validation. Each retry feeds the validation error back * to the model. Independent of `retries` — setting one never affects * the other. 0 disables (the default: validation retries cost tokens). */ validationRetries: number; }; /** Built-in policy: 2 retries, a 10-minute per-call deadline, exponential * backoff (500ms × 2, capped at 10s), validation retries off. Per-call * llm() options and setLlmOptions / agency.json defaults override these * (see resolveRetryPolicy). */ export declare const DEFAULT_RETRY_POLICY: RetryPolicy; export type RetryDecision = { kind: "propagate"; } | { kind: "terminal"; } | { kind: "surfaceFailure"; reason: LLMRetryReason; detail: string; retryAfterMs?: number; } | { kind: "retry"; delayMs: number; reason: LLMRetryReason; detail: string; }; /** * Decide what to do with a caught LLM-call error on the given attempt. Pure — * no I/O, no hooks, no timers — so the whole policy table is testable in * isolation and the loop stays a thin driver. */ export declare function decideRetry(err: unknown, normalized: NormalizedLLMError, attempt: number, policy: RetryPolicy): RetryDecision; export type RetryConfig = { retries?: number; timeout?: number; backoff?: { initial?: number; factor?: number; max?: number; }; validationRetries?: number; }; /** * Resolve the effective policy for one call. Precedence: per-call `opts` * (from `llm(prompt, {...})`) → `branchDefaults` (`stack.other.llmDefaults`, * set per-branch by `setLlmOptions`) → DEFAULT_RETRY_POLICY. (An agency.json * `llm` defaults block is a future follow-up — it needs the generated * RuntimeContext construction to seed the bag.) */ export declare function resolveRetryPolicy(opts: RetryConfig, branchDefaults: RetryConfig): RetryPolicy; /** * Known provider schema-limitation 400s, rethrown with guidance the user * can act on. Today: Anthropic structured outputs reject recursive * ($ref-cyclic) schemas — the raw message names zod-internal identifiers * ("__schema0 -> __schema0") instead of the user's type (#487; behavior * live-probed 2026-07-09). OpenAI and Gemini accept recursive schemas, so * the guidance names the provider-specific workaround. Returns null for * anything unrecognized (caller rethrows the original). */ export declare function enrichSchemaLimitationError(err: unknown): Error | null; export type ValidationDecision = { kind: "accept"; value: unknown; } | { kind: "surfaceFailure"; message: string; } | { kind: "retry"; feedback: string; reason: LLMRetryReason; detail: string; }; /** The user message injected before a validation retry. The response format * is still enforced by the request's responseFormat, so the message only * says WHAT was wrong, not the whole schema. * COUPLING: the agency-js retry tests key their mock on the literal * "did not match the required output format". Rewording this string * breaks those mocks loudly (they stop returning valid JSON). */ export declare function buildValidationRetryMessage(error: string): string; /** * Pure decision for one drained response under a structured-output schema: * accept it, retry with feedback, or surface a failure. The validation * twin of `decideRetry` — no I/O, no hooks — so the policy is testable in * isolation and runPrompt's loop stays a thin driver. Takes the extraction * ResultValue rather than running the extraction itself, so this module * does not import runtime/utils (import cycle) and the function stays * pure over data. * * The 2000/500 truncation caps differ on purpose: the feedback goes back * to the model (roomy); the hook detail goes to UI/telemetry (tight). */ export declare function decideValidationRetry(extracted: ResultValue, rawContent: unknown, attempt: number, policy: RetryPolicy): ValidationDecision;