import type { FallbackTriggerClass } from "@gajae-code/ai/utils/fallback-transport"; /** Immutable configured fallback intent. Transient attempt state never belongs here. */ export interface ConfiguredFallbackChain { role: string; entries: readonly string[]; origin: string; identity?: string; explicitHead: boolean; } export interface FallbackFailure { selector: string; triggerClass: FallbackTriggerClass; reason: string; } export type FallbackFailureResult = "retry" | "advance" | "exhausted"; export interface FallbackChainRuntimeState { activeIndex: number; attemptsUsed: number; totalAttemptsUsed: number; attemptStarted: boolean; restoredEntryIndices: number[]; tried: FallbackFailure[]; skips: Array<{ selector: string; reason: string; }>; exhaustedForTurn: boolean; } /** * In-memory policy state for one fallback-chain scope. A controller is deliberately * not serializable: configured chain intent is durable, current position is not. */ export declare class FallbackChainController { #private; readonly chain: ConfiguredFallbackChain; readonly maxAttempts: number; activeIndex: number; attemptsUsed: number; tried: FallbackFailure[]; skips: Array<{ selector: string; reason: string; }>; exhaustedForTurn: boolean; constructor(chain: ConfiguredFallbackChain, maxAttempts: number); snapshotRuntimeState(): FallbackChainRuntimeState; restoreRuntimeState(state: FallbackChainRuntimeState): void; get totalAttemptsUsed(): number; currentSelector(): string | undefined; onResolutionSkip(reason: string): boolean; /** Charge an upstream request at its concrete transport boundary. */ onAttemptStarted(): void; /** Remove the current started request from fallback-policy accounting without erasing prior failures. */ discardStartedAttempt(): void; /** Start a logically new request with a fresh fallback-chain budget. */ resetAttemptBudget(): void; /** Seed a controller from auth-aware resolution without charging requests. */ seedResolution(activeIndex: number, skips: Array<{ selector: string; reason: string; }>): void; onAttemptFailure(triggerClass: FallbackTriggerClass, reason: string): FallbackFailureResult; advance(): boolean; /** * Restore the entry that just advanced for one attempt with a rotated credential. * Each non-terminal entry may be restored once, so credential rotation remains * bounded and cannot consume the attempts reserved for downstream entries. */ restorePreviousEntryForRetry(): boolean; isExhausted(): boolean; resetForNewTurn(): void; resetSticky(): void; } export declare function cappedExponentialWithFullJitter(baseDelayMs: number, maxDelayMs: number, attemptK: number, random?: () => number): number; /** * Legacy auto-compaction retry delay. * * Deliberately the mirror image of `effectiveFallbackDelay`: this path recovers * Retry-After by regex over provider error prose (`#parseRetryAfterMsFromError`), * so it follows the documented legacy rule — `retry.maxDelayMs` caps every * legacy session retry delay, including provider retry-after hints. Managed * fallback stays uncapped because it retries within its own per-entry budget; * compaction has no such budget, so the final candidate would otherwise sleep * for the full server-suggested duration. * * `maxDelayMs <= 0` means "no cap", matching `cappedExponentialWithFullJitter`. * A missing, NaN, or infinite hint collapses to "no usable hint". */ export declare function compactionRetryDelay(baseDelayMs: number, maxDelayMs: number, attempt: number, retryAfterMs: number | undefined): number; /** Retry-After is intentionally uncapped. */ export declare function effectiveFallbackDelay(baseDelayMs: number, maxDelayMs: number, attemptK: number, retryAfterMs: number | undefined, random?: () => number): number;