/** * Usage-limit rules — per-runtime patterns that mean "this runtime's account * is out of usage", as opposed to a login that expired (login-expiry-rules.ts, * the re-login flow) or a short rate limit the runtime retries by itself. * * Modelled on login-expiry-rules.ts: output is normalized with * {@link normalizeTerminalOutput} and every pattern is matched against the * normalized text and its spaceless copy (Claude Code places words with * cursor moves instead of spaces), so patterns use `\s*` between words. * * Wording sources: * - Claude Code 2.1.287 binary (`strings`, 2026-10-01): `Usage limit * reached`, `Usage limit reached again`, `continuing automatically {when}`, * `You're out of extra usage`, `You're out of usage credits`, `You've hit * your monthly spend limit`, ` resets $…`, `/upgrade to increase your usage * limit.`, the API's `billing_error` "spend limit reached (daily; resets …)", * and its own note that the client composes "You've hit your limit" from a * 429. Older releases print `5-hour limit reached ∙ resets 3pm`, `Claude * usage limit reached. Your limit will reset at 3pm (America/Los_Angeles).` * and `Claude AI usage limit reached|`. * - Codex: `You've hit your usage limit. Upgrade to Pro (…) or try again in * 4 days 7 hours 3 minutes.` / `… try again at 4:05 PM.`; transient: * `exceeded retry limit, last status: 429 Too Many Requests`. * - Gemini / Antigravity: `RESOURCE_EXHAUSTED` with `Quota exceeded for quota * metric '… per day'`, `You exceeded your current quota`, `You have * exhausted your daily quota`; a bare 429 / RESOURCE_EXHAUSTED is transient. * - DeepSeek (the in-process Crewly Agent): HTTP 402 `Insufficient Balance` * is a `billing` limit (no reset; the owner must top up); HTTP 429 `Rate Limit Reached` is transient. * * Context requirements keep an agent that merely reads or quotes this kind of * text (for example while working on this file) from tripping a rule: the * Claude UI rules need the `·` / `∙` separator, a reset time or a slash * command next to the message; API rules need the API error context. * Warnings that the limit is near ("Approaching usage limit", "You've used * 90% of your session limit") are deliberately not matched. * * @module services/runtime-fallback/usage-limit-rules */ /** * `usage_limit`: the account is out of usage until a window resets; * `billing`: the account is out of money / credit (DeepSeek 402 "Insufficient * Balance", "credit balance is too low") — it has no reset time and only * comes back when the owner tops up, so it is never retried on a timer; * `transient`: a short rate limit the runtime retries. */ export type UsageLimitKind = 'usage_limit' | 'billing' | 'transient'; /** * Whether a kind means the runtime cannot be used (as opposed to a transient * rate limit). * * @param kind - Match kind * @returns True for `usage_limit` and `billing` */ export declare function isExhaustingKind(kind: UsageLimitKind): boolean; /** One rule: every pattern must match (AND); `none` must not match. */ export interface UsageLimitRule { /** Stable id, used in logs (never contains output) */ id: string; /** Runtime type the rule belongs to */ runtime: string; kind: UsageLimitKind; /** All must match the normalized text or its spaceless copy */ all: readonly RegExp[]; /** None of these may match (e.g. a warning, not the limit itself) */ none?: readonly RegExp[]; } /** What {@link detectUsageLimit} found. */ export interface UsageLimitMatch { /** Runtime the rule belongs to */ runtime: string; ruleId: string; kind: UsageLimitKind; /** When the limit lifts (epoch ms), when the message says so */ resetAt: number | null; } /** Claude Code rules (usage limits first, then transient). */ export declare const CLAUDE_USAGE_RULES: readonly UsageLimitRule[]; /** Codex rules. */ export declare const CODEX_USAGE_RULES: readonly UsageLimitRule[]; /** Antigravity CLI rules. */ export declare const ANTIGRAVITY_USAGE_RULES: readonly UsageLimitRule[]; /** Gemini CLI rules (retired runtime, detect only). */ export declare const GEMINI_USAGE_RULES: readonly UsageLimitRule[]; /** * In-process Crewly Agent rules. These run on the error of a failed model * call (never on text the model wrote), so they need no UI context. */ export declare const CREWLY_AGENT_USAGE_RULES: readonly UsageLimitRule[]; /** * Usage-limit rules for a runtime. * * @param runtime - Runtime type * @returns Rules to try (empty for a runtime without rules) */ export declare function getUsageLimitRules(runtime: string): readonly UsageLimitRule[]; /** * Find a usage-limit (or transient rate-limit) message in a runtime's output. * An expired login is never reported here — that is the re-login flow's. * * @param output - Raw PTY output (escape sequences allowed), captured screen, or an error message * @param runtime - The runtime the output came from * @param now - Current time (ms), for the reset time * @param defaultTimeZone - Zone for reset clock times that name none * @returns The match, or null * * @example * ```ts * detectUsageLimit("You've hit your limit · resets 3pm (America/Los_Angeles)", 'claude-code'); * // { runtime: 'claude-code', ruleId: 'claude.hit_your_limit', kind: 'usage_limit', resetAt: } * ``` */ export declare function detectUsageLimit(output: string, runtime: string, now?: number, defaultTimeZone?: string): UsageLimitMatch | null; /** * Claude Code's usage-limit menu, shown when a turn hits the limit: * * ``` * ⎿ You've hit your weekly limit · resets Oct 16 at 8pm (America/New_York) * What do you want to do? * ❯ 1. Stop and wait for limit to reset * 2. Wait here, then continue automatically at 8pm * Enter to confirm · Esc to cancel * ``` * * Both options must be on screen. Matched against the spaceless screen tail. */ export declare const CLAUDE_USAGE_LIMIT_MENU: readonly RegExp[]; /** What {@link detectUsageLimitScreen} found. */ export interface UsageLimitScreenMatch { /** Runtime the match belongs to */ runtime: string; /** Rule that matched the limit line, or `claude.usage_limit_menu` */ ruleId: string; /** When the limit lifts (epoch ms), when the screen says so and it is plausible */ resetAt: number | null; /** Set while the usage-limit menu is open; `selected` is the highlighted option */ menu: { selected: 1 | 2 | null; } | null; } /** * Find a usage-limit state on a captured screen — what the agent shows right * now — as opposed to {@link detectUsageLimit}, which reads live output and * error text. * * Only the bottom of the screen counts (old scrollback and resumed * transcripts sit above it): * * - Claude Code's usage-limit menu ({@link CLAUDE_USAGE_LIMIT_MENU}) is a * modal waiting for an answer, so it always counts; the reset time comes * from the limit line above it or from option 2 ("continue automatically * at 8pm"). * - A plain limit line ("You've hit your … limit · resets …") counts only * when its reset names a calendar date and is plausible * ({@link plausibleReset}), so an old line that names a past date is not * read as a new limit. A line whose reset has no date ("resets 8pm", * "try again in 2h") never counts here: on a resumed conversation it may * be old. Only new live output may report it. * * Transient rate limits and expired logins are never reported. * * @param screen - Captured screen text (escape sequences allowed) * @param runtime - Runtime the session runs * @param now - Current time (ms) * @param defaultTimeZone - Zone for reset clock times that name none * @returns The match, or null * * @example * ```ts * detectUsageLimitScreen(screen, 'claude-code'); * // { runtime: 'claude-code', ruleId: 'claude.hit_your_limit', resetAt: , menu: { selected: 1 } } * ``` */ export declare function detectUsageLimitScreen(screen: string, runtime: string, now?: number, defaultTimeZone?: string): UsageLimitScreenMatch | null; /** * Whether a live output chunk is fresh activity that is not a usage-limit * message: at least {@link USAGE_LIMIT_STATE_CONSTANTS.FRESH_OUTPUT_MIN_CHARS} * printable characters, no usage-limit match, and none of the words of the * limit screen. Used to lift a usage-limit state once the agent works again. * * @param output - Output chunk (escape sequences allowed) * @param runtime - Runtime the session runs * @param now - Current time (ms) * @returns True for fresh non-limit output * * @example * ```ts * isFreshNonLimitOutput('⏺ Read(src/app.ts) … 120 lines, editing the handler now', 'claude-code'); // true * ``` */ export declare function isFreshNonLimitOutput(output: string, runtime: string, now?: number): boolean; //# sourceMappingURL=usage-limit-rules.d.ts.map