import type { Model } from "../internal/llm.js";
import type { Brain } from "../core/types.js";
import { type BrainErrorCode } from "./errors.js";
/** Stable prefix of an open-breaker error's detail (`[network] circuit open for <key>`). Exported so
 *  the degradation cascade can detect breaker-open **structurally** instead of string-matching a literal
 *  (council #1) — reword the message only by changing this constant. */
export declare const CIRCUIT_OPEN_MARKER = "circuit open for ";
export type BreakerPhase = "closed" | "open" | "half-open";
export interface BreakerSnapshot {
    phase: BreakerPhase;
    /** Consecutive failures while closed. */
    failures: number;
    /** When the breaker opened (epoch ms), for cooldown. */
    openedAt?: number;
    /** Probes currently allowed through while half-open. */
    halfOpenInFlight?: number;
    /**
     * When the current half-open probe round was admitted (epoch ms) — the PROBE LEASE stamp.
     *
     * HRD-BRN-1: `record()` is the only writer that leaves half-open, and it only runs once the probe's
     * stream settles. A probe that never settles — precisely the accepted-then-hangs gateway the breaker
     * exists for, and the default shape since `firstTokenTimeoutMs`/`idleTimeoutMs` both default off —
     * therefore held `halfOpenInFlight` forever and made half-open an ABSORBING state: no clock could
     * release the key again. The lease bounds it: once `cooldownMs` has elapsed since this stamp, the
     * in-flight probe is presumed lost and a fresh probe is admitted (see `tryPass`).
     *
     * Optional/additive: a snapshot deserialized by a shared backend that predates the field simply
     * adopts a stamp on the first refusal, so the lease starts from then rather than never.
     */
    halfOpenAt?: number;
}
/** Pluggable breaker state. In-memory by default; a deployment can back it with Redis/TiDB so all
 *  replicas share one circuit (otherwise each replica trips independently and reacts slowly).
 *
 *  ATOMICITY: the breaker does read-modify-write (`get` → decide → `set`). The in-process Map is safe
 *  under Node's single-threaded run-to-completion. A shared async backend MUST make get/set
 *  **linearizable** (or expose CAS) — otherwise concurrent transitions (closed→open, open→half-open)
 *  race (TOCTOU). Implement it with a Lua script / transaction / optimistic-version on the store. */
export interface BreakerState {
    get(key: string): BreakerSnapshot | undefined;
    set(key: string, snap: BreakerSnapshot): void;
}
export interface CircuitBreakerOptions {
    /** Consecutive transient failures that trip the breaker. Default 5. */
    failureThreshold?: number;
    /** How long the breaker stays open before a half-open probe. Default 30_000 ms. */
    cooldownMs?: number;
    /** Probes allowed through while half-open. Default 1. */
    halfOpenProbes?: number;
    /** Error codes that count toward tripping. Default `["network","server","rate_limit"]`. */
    countCodes?: BrainErrorCode[];
    /** Breaker key from a model. Default `"<provider>:<id>"` (per model+provider). */
    key?: (model: Model) => string;
    /** State backend. Default in-memory (per process). */
    state?: BreakerState;
    /** Clock, injectable for tests. */
    now?: () => number;
}
export declare function createCircuitBreakerBrain(inner: Brain, opts?: CircuitBreakerOptions): Brain;
