/**
 * LlmCircuitBreaker — account-global reactive circuit breaker for LLM calls.
 *
 * Motivating incident (2026-05-28): a wild agent burned $452/$455 in usage
 * credits because PromptGate's per-tick Haiku detection loop kept calling
 * `claude -p` while the account was over its weekly spend limit. The loop's
 * error handler swallowed the rate-limit error with no backoff, so every
 * monitor tick spawned another doomed (or, with auto-reload, freshly-billed)
 * subprocess. There is NO legitimate reason to keep calling the LLM once the
 * provider has told us we are over our limit — every further call is either
 * wasted (rejected) or actively harmful (auto-reload refuels the burn).
 *
 * This breaker reacts to the provider's OWN rate-limit signal in milliseconds,
 * which is distinct from (and complementary to) the volume-based
 * token-burn-detection system (BurnDetector + LlmRateGate), which reacts to
 * statistical token-share over a ~30-minute window. Where the burn system
 * answers "this path is spending too much," the breaker answers "the provider
 * just said we're rate-limited — stop."
 *
 * Scope: ACCOUNT-GLOBAL. A usage/rate limit applies to the whole subscription,
 * not one session or one component, so a single shared breaker pauses every
 * LLM-backed feature (PromptGate, PresenceProxy, PromiseBeacon, sentinels,
 * reviewers) at once. It is wired structurally at the IntelligenceProvider
 * construction chokepoint via CircuitBreakingIntelligenceProvider, so no
 * consumer has to remember to consult it (Structure > Willpower).
 *
 * State: in-memory, process-local. A restart resets it — which is correct: the
 * first call after restart probes the provider, and if still limited the
 * breaker re-trips immediately.
 */
/** Thrown by a provider (or re-thrown by the decorator) when the underlying LLM call was rejected for a usage/rate limit. */
export declare class RateLimitError extends Error {
    readonly providerError?: unknown | undefined;
    readonly isRateLimit: true;
    constructor(message: string, providerError?: unknown | undefined);
}
/** Thrown by the decorator when the breaker is open — the call was refused WITHOUT spawning the underlying provider. */
export declare class LlmCircuitOpenError extends Error {
    readonly retryAfterMs: number;
    readonly circuitOpen: true;
    constructor(retryAfterMs: number);
}
/**
 * Classify whether an error message indicates a provider usage/rate/spend
 * limit (as opposed to a timeout, a parse error, a network blip, etc.).
 *
 * Pure + exported so it is unit-testable against representative CLI error
 * strings from both `claude -p` and `codex exec`. Deliberately conservative:
 * a false positive trips the breaker and degrades all LLM-backed features to
 * heuristic-only for one open window, so we match only language that is
 * unambiguously about hitting a usage/spend/quota ceiling — never generic
 * "error"/"failed"/timeout text.
 */
export declare function isRateLimitError(message: string | null | undefined): boolean;
/**
 * Classify an error message as a rate-limit/quota condition AND, best-effort,
 * extract a retry-after hint in milliseconds.
 *
 * SUPERSET of isRateLimitError: `isLimit` uses the exact same phrase/429/402
 * detection. The CLI error text is unstructured (the underlying HTTP
 * retry-after header is invisible to us — we only see the `claude -p` error
 * string), so `retryAfterMs` is returned only when a duration phrase parses;
 * callers fall back to the flat default window otherwise.
 */
export declare function classifyRateLimit(message: string | null | undefined): {
    isLimit: boolean;
    retryAfterMs?: number;
};
type CircuitState = 'closed' | 'open' | 'half-open';
export interface LlmCircuitBreakerOptions {
    /** How long to stay fully open before allowing a single probe. Default 15 min. */
    openMs?: number;
    /** When false, the breaker is a passthrough — acquire() always allows, records are no-ops. Default true. */
    enabled?: boolean;
    /** Injectable clock for tests. */
    now?: () => number;
    /** Injectable logger (defaults to console.warn → server log). */
    log?: (line: string) => void;
    /** Injectable sleep for tests. Default real setTimeout-backed promise. */
    sleep?: (ms: number) => Promise<void>;
    /** Poll interval (ms) used by acquireOrWait while a probe is in flight. Default 250. */
    probePollMs?: number;
}
export interface AcquireDecision {
    /** Whether the caller may invoke the underlying provider. */
    allow: boolean;
    /** True when this acquisition is the single half-open probe. */
    probe: boolean;
    /** When blocked, how long until the next probe window (ms). */
    retryAfterMs: number;
}
export interface LlmCircuitBreakerStatus {
    state: CircuitState;
    enabled: boolean;
    openUntil: number | null;
    retryAfterMs: number;
    tripCount: number;
    lastReason: string | null;
    lastTrippedAt: number | null;
}
export declare class LlmCircuitBreaker {
    private state;
    private openUntil;
    private firstOpenedAt;
    private probeInFlight;
    private tripCount;
    private lastReason;
    private lastTrippedAt;
    private tripListeners;
    private recoverListeners;
    private openMs;
    private enabled;
    private readonly now;
    private readonly log;
    private readonly sleep;
    private readonly probePollMs;
    /**
     * The window length for the CURRENT trip — may be shortened (toward
     * min(30s, openMs)) by a parsed retry-after hint so waiters don't sit out the
     * full flat cooldown when the provider told us when it resets. Reset to openMs
     * on a clean close. The open window is [firstOpenedAt, openUntil); openUntil
     * is firstOpenedAt + currentOpenMs.
     */
    private currentOpenMs;
    constructor(opts?: LlmCircuitBreakerOptions);
    /** Runtime reconfiguration (called once at server startup from config). */
    configure(opts: {
        openMs?: number;
        enabled?: boolean;
    }): void;
    /**
     * Gate a call. Call BEFORE invoking the underlying provider. When it returns
     * { allow: false }, the caller MUST NOT call the provider — throw
     * LlmCircuitOpenError instead (the whole point is to avoid the spend).
     *
     * Exactly one probe is admitted when transitioning out of the open window;
     * concurrent callers are blocked until that probe resolves.
     */
    acquire(): AcquireDecision;
    /**
     * Record that the underlying call returned (or threw a non-rate-limit
     * error). Either way the rate-limit condition is not currently present, so
     * the breaker closes. A non-rate-limit error is handled by the caller's own
     * fallback — it is not the breaker's concern, and staying open on unrelated
     * errors would needlessly keep all LLM features down.
     */
    /** Subscribe to circuit-open (trip) events. Returns an unsubscribe fn. */
    onTrip(cb: (e: {
        reason: string;
        retryAfterMs?: number;
        ts: number;
        tripCount: number;
    }) => void): () => void;
    /** Subscribe to circuit-recover (open→closed) events. Returns an unsubscribe fn. */
    onRecover(cb: (e: {
        ts: number;
    }) => void): () => void;
    private emitTrip;
    private emitRecover;
    onResolved(): void;
    /**
     * Record that the underlying call was rejected for a usage/rate limit. Opens
     * (or re-extends) the breaker for a window.
     *
     * When `retryAfterMs` is a finite, positive number (parsed best-effort from
     * the provider error), the open window for THIS trip is shortened to that
     * value, clamped to [min(30s, openMs), openMs] — so coherence-critical
     * waiters don't sit out the full flat cooldown when the provider told us when
     * it resets. Without a hint, the flat default window is used.
     */
    onRateLimited(reason: string, retryAfterMs?: number): void;
    /**
     * Bounded wait-and-retry acquire — the coherence-critical primitive.
     *
     * Loops until either the breaker admits the caller (allow:true) or `maxWaitMs`
     * elapses (allow:false — bounded fallback so the caller can fail open/closed
     * on its own terms). When the breaker is open, sleeps until just past the
     * window edge; when half-open with another caller's probe in flight, polls at
     * probePollMs. This serializes waiters behind the single half-open probe:
     * exactly one re-acquires the probe after the window, the rest poll until that
     * probe's onResolved closes the breaker (then acquire returns closed→allow) or
     * its onRateLimited reopens it (then they keep waiting until the deadline). No
     * thundering herd — only one probe hits the provider.
     *
     * A passthrough (disabled) breaker allows immediately with no sleep.
     */
    acquireOrWait(maxWaitMs: number): Promise<AcquireDecision>;
    status(): LlmCircuitBreakerStatus;
    /** Reset to a clean closed state. For tests + an explicit operator override. */
    reset(): void;
}
/** Process-wide account-global breaker. All wrapped providers share this instance. */
export declare function getLlmCircuitBreaker(): LlmCircuitBreaker;
/** Configure the singleton at startup. Safe to call before first use. */
export declare function configureLlmCircuitBreaker(opts: {
    openMs?: number;
    enabled?: boolean;
}): void;
/**
 * Read-only convenience: is the shared LLM circuit currently AVAILABLE for new
 * LLM-backed work? True when the breaker is disabled, or its state is 'closed'.
 * False when 'open' or 'half-open' (the provider is rate-limited / probing).
 * Non-mutating — unlike the admission check, it does NOT consume a half-open
 * probe slot, so callers can use it as a pure gate (e.g. the mentor tick backing
 * off rather than re-tripping the circuit). */
export declare function llmCircuitAvailable(): boolean;
/** Test-only: drop the singleton so a fresh one is built next access. */
export declare function __resetLlmCircuitBreakerSingleton(): void;
export {};
//# sourceMappingURL=LlmCircuitBreaker.d.ts.map