/** * Robust recovery for provider stream/complete failures. * * Goal: the agent should try *working approaches* before it gives up on a * turn. A single flaky free-tier model (empty admissions, connection glitches, * capacity 5xx, rate limits) must not kill the turn on the first error. Instead * we classify the failure and pick a bounded, escalating strategy — back off, * compact the context, drop thinking, or let the router fall back to another * provider/model — and only surrender in the worst case (every approach for * that failure class exhausted, or the overall recovery budget spent). * * These helpers are pure so the escalation ladder and the "give up only in the * worst case" guarantee are unit-testable without a live provider. */ export type StreamFailureKind = "aborted" | "empty" | "context-overflow" | "rate-limit" | "server" /** Transport died: the socket dropped or never delivered a byte. */ | "network" /** * The connection stayed healthy but the model stopped producing output — * almost always a runtime buffering one very large `tool_calls` argument * string. Distinct from `network` because the request was accepted and the * work was done: retrying the identical request on the identical route * replays the whole generation and stalls again the same way. */ | "stall" | "auth" | "not-found" | "unknown"; export interface StreamRecoveryState { empty: number; rateLimit: number; server: number; network: number; stall: number; context: number; /** auth / not-found / unknown share one "structural" bucket. */ structural: number; progressed: number; total: number; } export interface StreamRecoveryLimits { readonly maxEmpty: number; readonly maxRateLimit: number; readonly maxServer: number; readonly maxNetwork: number; readonly maxStall: number; readonly maxContext: number; readonly maxStructural: number; readonly maxProgressed: number; /** Hard cap across every failure class so a turn can never loop forever. */ readonly maxTotal: number; /** Upper bound on any single backoff so the total wait budget stays sane. */ readonly maxDelayMs: number; } export declare const DEFAULT_STREAM_RECOVERY_LIMITS: StreamRecoveryLimits; export interface StreamRecoveryPlan { /** retry = try again with the strategy below; give-up = rethrow (worst case). */ readonly action: "retry" | "give-up"; readonly kind: StreamFailureKind; /** Backoff before the retry (0 = immediate). */ readonly delayMs: number; /** Force a compaction pass before retrying (context pressure / empty tail). */ readonly forceCompact: boolean; /** Retry with thinking disabled (thinking-only / reasoning-heavy stalls). */ readonly disableThinking: boolean; /** Let the router fall back to another provider/model on the retry. */ readonly allowModelFallback: boolean; /** Try alternates before replaying the selected route (stall recovery only). */ readonly preferModelFallback?: boolean | undefined; /** Optional trailing user nudge (empty-admission recovery). */ readonly nudge?: string | undefined; /** Human-facing one-liner; only set on the FIRST retry of a class (low noise). */ readonly notice?: string | undefined; } /** * Map a raw stream/complete failure to a recovery class. * * Works on both raw {@link ProviderError}s and the router's wrapped * "No provider could stream the request. — : " strings, so it * behaves the same whether the failure bubbles from a single provider or the * whole fallback chain. When several providers failed for different reasons the * most *actionable* class wins (compact > back off > nudge). */ export declare function classifyStreamFailure(error: unknown): StreamFailureKind; export declare function createStreamRecoveryState(): StreamRecoveryState; export declare function resetStreamRecoveryState(state: StreamRecoveryState): void; /** Record that one recovery attempt for `kind` was taken. Mutates `state`. */ export declare function recordRecoveryAttempt(state: StreamRecoveryState, kind: StreamFailureKind, progressed?: boolean): void; /** * Decide the next recovery action for a stream failure. * * `state` holds the attempts already made for each class *this failure episode* * (reset on any successful stream). The planner never mutates it — the caller * records the attempt via {@link recordRecoveryAttempt} once it commits to the * retry — so the plan stays a pure function of (error, state, limits). */ export declare function planStreamRecovery(input: { error?: unknown; kind?: StreamFailureKind; state: StreamRecoveryState; limits?: StreamRecoveryLimits; progressed?: boolean | undefined; }): StreamRecoveryPlan;