/** * Rate limit reason classification and backoff calculation utilities. * Ported from opencode-antigravity-auth plugin for consistency. */ export type RateLimitReason = "QUOTA_EXHAUSTED" | "INSUFFICIENT_G1_CREDITS_BALANCE" | "RATE_LIMIT_EXCEEDED" | "CONCURRENT_LIMIT" | "MODEL_CAPACITY_EXHAUSTED" | "SERVER_ERROR" | "UNKNOWN"; /** * Classify a rate-limit error message into a reason category. * Priority order: explicit details in a resource-exhausted error > QUOTA * (Antigravity "quota will reset") > CONCURRENT_LIMIT > MODEL_CAPACITY > * QUOTA (account) > RATE_LIMIT > QUOTA (generic) > SERVER_ERROR > bare resource-exhausted > UNKNOWN. * * Bare "resource exhausted" / "resource_exhausted" maps to MODEL_CAPACITY (transient, short wait). * Explicit details such as "quota exceeded" retain their normal classification. */ export declare function parseRateLimitReason(errorMessage: string): RateLimitReason; /** * Calculate backoff delay in ms for a given rate limit reason. * MODEL_CAPACITY gets jitter to prevent thundering herd. */ export declare function calculateRateLimitBackoffMs(reason: RateLimitReason): number; /** * HTTP status codes that, absent richer body classification, represent an * account-local usage cap rather than a bad credential or a transient blip. * HTTP 402 Payment Required is categorically an account-billing cap (xAI * Grok Build "usage balance exhausted", DeepSeek "Insufficient Balance", * OpenRouter credit exhaustion) — never a transient blip or bad credential. * Always combine with {@link isUsageLimitOutcome} when a message is available * — a 429 carrying transient rate-limit wording is NOT a usage cap. */ export declare function isUsageLimitStatus(status: number | undefined): boolean; /** * Returns true for failures that should burn one credential and rotate to a * sibling account. Decision tree: * * 1. Body matches {@link isUsageLimitError} (Codex `usage_limit_reached`, * Anthropic account rate-limit, Google `resource_exhausted`, OpenAI * `insufficient_quota`, …) → rotate. * 2. Status is not a usage-limit status (429/402) → backoff (caller's domain). * 3. Body is absent or {@link isOpaqueStatusBody opaque} (just the status, * empty JSON, HTTP framing only) → rotate conservatively: the server * gave us nothing else to go on. * 4. Body has content → defer to {@link parseRateLimitReason}. `QUOTA_EXHAUSTED` * rotates; for the categorical 402 billing cap a `CONCURRENT_LIMIT` body * also rotates (the cap is concurrent-worded but the status is still an * exhausted billing cap). `RATE_LIMIT_EXCEEDED` (`Too many requests`, * per-minute caps), `MODEL_CAPACITY_EXHAUSTED` (`Service overloaded`), * `SERVER_ERROR`, and `UNKNOWN` (`Please retry in 5s`) stay in the * provider's own backoff layer so transient 429s don't burn sibling * credentials. */ export declare function isUsageLimitOutcome(status: number | undefined, message: string | undefined): boolean; /** * A usage-limit status body is opaque when it carries no signal beyond the * status itself — empty, whitespace-only, the status digits with HTTP/JSON * framing, or generic punctuation. Anything else (retry hints, capacity * wording, error descriptions) is informative enough to defer to the * classifier. */ export declare function isOpaqueStatusBody(message: string): boolean; /** * Internal text matcher for usage/quota-limit phrasing. NOT part of the public * API — callers classify through {@link import("./flags.js").isUsageLimit} (the * flag accessor). `flags.ts` consumes this to populate `Flag.UsageLimit`, and * {@link isUsageLimitOutcome} uses it for the account-rotation decision. */ export declare function matchesUsageLimitText(errorMessage: string): boolean; /** * Account-scoped cap phrasing delivered on a 403 (or a statusless Connect * trailer): "Reached overall message rate limit", "Your limit will reset in …". * Kept separate from {@link matchesUsageLimitText} because the bare wording is * ambiguous without the 403 / statusless-account context; consumed by both * {@link isUsageLimitOutcome} (rotation decision) and `flags.ts` (Flag.UsageLimit). */ export declare function isAccountScopedCapText(message: string): boolean; /** * A concurrency cap on a non-billing status is shed-and-backoff, not * credential-rotatable. This mirrors the exclusion in {@link isUsageLimitOutcome} * for the 403 auth-retry entry points. A 402 remains a categorical billing cap. */ export declare function isConcurrencyCapExclusion(status: number | undefined, message: string | undefined): boolean;