import { type AccountPoolState, type AccountSlot } from "./accounts.js"; import { type SchedulingMode, type UsageSnapshot } from "./affinity.js"; import { type FailureClassification } from "./failure.js"; export interface MigrationNotice { /** The account the conversation was bound to, whose prompt cache is now lost. */ from: string; /** The account now serving it. */ to: string; } export interface FailoverEvent { from: AccountSlot; to?: AccountSlot; reason: string; attempt: number; } /** * Raised when no account can serve the request. * * `retryAfterMs` matters for more than diagnostics: senpi's own * `SelectorCooldowns.durationFor()` prefers an explicit `retryAfterMs` over its * keyword heuristics. Carrying the real unblock time makes senpi suppress the * model for exactly as long as this pool is actually unusable, instead of * defaulting to its 30-minute quota bucket and idling accounts that recover * sooner. */ export declare class AllAccountsBlockedError extends Error { readonly retryAfterMs?: number; readonly retryAt?: number; constructor(message: string, retryAt?: number, now?: number); } export interface RunWithFailoverOptions { state: AccountPoolState; /** Conversation fingerprint used for cache-preserving placement. */ key: string; attempt: (account: AccountSlot) => Promise; refresh?: (account: AccountSlot) => Promise; mode?: SchedulingMode; usage?: UsageSnapshot; maxAttempts?: number; onFailover?: (event: FailoverEvent) => void; /** * Called only when a conversation irreversibly leaves the account holding its * warm cache, and only under the `ask` policy. A reversible detour never * fires this, so a rate limit stays silent. */ onMigration?: (notice: MigrationNotice) => void; onStateChange?: (state: AccountPoolState) => void; now?: () => number; refreshSkewMs?: number; random?: () => number; /** Injected so a congestion retry is not a wall-clock wait in tests. */ sleep?: (ms: number) => Promise; /** Congestion retries on one account before it is blocked instead. */ maxTransientRetries?: number; } export interface FailoverResult { value: T; account: AccountSlot; state: AccountPoolState; } export interface AccountFailureTransition { state: AccountPoolState; classification: FailureClassification; } export declare function applyAccountFailure(state: AccountPoolState, account: AccountSlot, key: string, error: unknown, now?: number, attempt?: number): AccountFailureTransition; /** * Run one request against the account pool. * * A failure that another account could survive (429, quota, auth, 5xx) blocks * the offending account and replays the request elsewhere; the conversation's * binding is retained so that once the block expires the conversation returns to * the account whose prompt-prefix cache is still warm. * Client-side errors propagate untouched, because replaying them would only * burn a second subscription on the same bad request. */ export declare function runWithFailover(options: RunWithFailoverOptions): Promise>;