import type { AssistantMessage } from "./types.js"; import { type HeadersLike } from "./utils/retry-after.js"; /** * Transient-failure retry for **oneshot** (non-agent-loop) completions. * * Why this exists: `streamSimple`/`completeSimple` retry *auth* failures * (credential rotation) but deliberately surface *transient* provider failures * — Anthropic `overloaded_error`, `rate_limit_error`, HTTP 429/500/502/503/529 * — as a **resolved** `AssistantMessage` with `stopReason: "error"`. For the * main agent turn that is correct: `TurnRecovery` owns recovery there, and it * must refuse to replay once tool calls or visible text have streamed. * * Oneshots have no such hazard. A summary, title, handoff, or image * description produces no side effects, so re-issuing the whole request is * safe and is almost always what the caller wants. Before this helper every * oneshot call site had to re-implement that decision, and most did not — * failing on the first blip, or swallowing it into `null` so a transient * overload was indistinguishable from a legitimate empty result. * * Classification reuses the existing provider predicates (`AIError`), so the * set of retryable Anthropic failures stays defined in exactly one place. * Usage limits are included: unlike the provider loop — which excludes them so * credential rotation can own them — a oneshot has no rotation layer above it, * and the retry hint the provider supplies (`retry-after`, "try again in ~5m") * is honored, so waiting is the correct response. */ export interface OneshotRetryOptions { /** Total attempts, including the first. Default 3. Values < 1 are treated as 1. */ maxAttempts?: number; /** First backoff step in ms; doubles per attempt. Default 500. */ baseDelayMs?: number; /** * Upper bound for a single wait. Default 30_000. A provider retry hint * longer than this aborts the retry instead of parking the caller — the * error surfaces so higher-level recovery (or the user) can decide. */ maxDelayMs?: number; /** * Stops further attempts. Two distinct paths, both preserving the caller's * intent: an abort already visible when an attempt settles surfaces that * attempt's own result (`completeSimple` reports `stopReason: "aborted"`), * while an abort that lands during the backoff wait rejects with the abort * reason — a user cancel stays a cancel and is never relabelled as the * provider failure we happened to be waiting on. * * This helper does NOT pass the signal into `run` — cancelling the in-flight * request is the closure's job, because a per-attempt deadline must be * rebuilt on every attempt. Construct it inside `run` * (`signal: AbortSignal.timeout(MS)`, or `AbortSignal.any([outer, perAttempt])`); * a deadline captured outside would fire once and then abort every retry, * silently turning this helper into a single attempt. */ signal?: AbortSignal; /** * Headers of the attempt that just failed, used to honor `retry-after`. * * Load-bearing: a transient Anthropic failure arrives as a **resolved** * `AssistantMessage`, and `AssistantMessage` carries no headers — so without * this the real `retry-after` / `x-ratelimit-reset` values on a 429/529 are * invisible and only the (usually hint-free) error text is available. * Callers that already capture headers via `SimpleStreamOptions.onResponse` * should return the latest capture here; it is read once per failed attempt. * Thrown errors need no wiring — headers are recovered from the error itself. */ getResponseHeaders?: () => HeadersLike; /** * Provider id of the model being retried. Selects the catalog-declared * timezone for a timezone-naive absolute reset stamp (Z.AI/Zhipu report * Beijing time), so an over-cap wait is not misread as UTC and discarded. */ provider?: string; /** Observability hook. Fires immediately before sleeping. */ onRetry?: (info: OneshotRetryInfo) => void; } export interface OneshotRetryInfo { /** 1-based index of the attempt that just failed. */ attempt: number; maxAttempts: number; delayMs: number; /** True when `delayMs` came from a provider retry hint rather than backoff. */ fromRetryHint: boolean; errorMessage: string; /** `AIError` classification bits of the failure. */ errorId: number; } /** * Run a oneshot completion, retrying transient provider failures. * * Handles both failure shapes: a resolved `AssistantMessage` carrying * `stopReason: "error"` (what `completeSimple` produces) and a thrown error * (what the raw HTTP helpers produce). A non-retryable failure is returned or * rethrown unchanged, so existing caller error handling keeps working — this * only removes the *first-blip* failure mode. */ export declare function retryTransientCompletion(run: (attempt: number) => Promise, options?: OneshotRetryOptions): Promise;