/**
 * withCircuitBreaker — provider decorator that fails fast after N
 * consecutive failures.
 *
 * Pattern: Circuit Breaker (Nygard, *Release It!*) — wraps an
 *          `LLMProvider` and tracks consecutive failures. After
 *          `failureThreshold` failures, the breaker OPENS and
 *          rejects all calls without invoking the wrapped provider.
 *          After `cooldownMs`, the breaker enters HALF-OPEN and
 *          allows probe calls; success closes the breaker, failure
 *          re-opens it.
 *
 * Role:    Outer ring (Hexagonal). Composes with `withRetry` and
 *          `withFallback`:
 *
 *          ```
 *          withFallback(
 *            withCircuitBreaker(anthropic(...)),  // ← stop hammering on outage
 *            withCircuitBreaker(openai(...)),
 *          )
 *          ```
 *
 *          When Anthropic 503s for the 5th time, the breaker opens
 *          and `complete()` throws `CircuitOpenError` immediately —
 *          no network round-trip — which `withFallback` then
 *          catches and routes to OpenAI. After 30 seconds the
 *          breaker probes Anthropic with a single call; if it
 *          succeeds, normal operation resumes.
 *
 * Why a circuit breaker on top of `withRetry`?
 *   - `withRetry` keeps hammering one provider with exponential
 *     backoff — it doesn't know the vendor is down.
 *   - During a multi-minute Anthropic outage, every request still
 *     burns 3 retries + backoff = ~3 sec of latency before failing
 *     to the fallback. Multiplied by your QPS, that's a lot of
 *     wasted time + tokens (some retries DO get billed).
 *   - The breaker says: "we just saw 5 failures in a row; stop
 *     calling for 30 seconds." Subsequent requests fail in <1ms,
 *     `withFallback` routes immediately to OpenAI.
 *
 * Three states:
 *
 *     CLOSED ──[ N consecutive failures ]──► OPEN
 *        ▲                                    │
 *        │                                    │ [cooldownMs elapsed]
 *        │                                    ▼
 *        └──[ M probe successes ]──── HALF-OPEN
 *
 *     HALF-OPEN ──[ probe failure ]──► OPEN (cooldown restarts)
 *
 * `stream()` is decorated identically. `name` passes through unchanged
 * (the consumer's existing observability hooks still see the underlying
 * provider's identity), and the optional per-call `LLMCallHooks` is
 * forwarded inward so a decorator nested inside this one can still
 * report. Nothing else is copied — `LLMProvider` has exactly `name`,
 * `complete` and the optional `stream`.
 *
 * **Scope: per-instance, NOT distributed.** Each `withCircuitBreaker(...)`
 * call holds its own breaker state in process memory. If you run 100
 * server replicas, each has its own independent breaker — one
 * instance can be CLOSED while another is OPEN. This is intentional
 * (no shared state means no Redis dependency, no SPOF, no
 * partial-cluster-blast-radius surprises) and matches Hystrix's
 * default behavior. For cluster-wide coordination, layer your own
 * Redis-backed counter on top via the `onStateChange` hook +
 * `shouldCount` predicate.
 *
 * **Scope: PROVIDER-level, not per-tool — by design (B16).** The breaker
 * wraps `LLMProvider` because a provider outage has unbounded blast
 * radius: the LLM call is every run's heartbeat, fired once per
 * iteration at your full QPS, and a failure aborts the call path. Tool
 * failures don't share that shape — the agent's tool dispatch catches a
 * tool throw and feeds the error message back to the model as the tool
 * result (see `core/agent/stages/toolCalls.ts`), so the ReAct loop
 * itself absorbs and adapts (retry, alternate tool, give up), serialized
 * and bounded by the iteration budget. A flaky tool can't hammer
 * anything the way provider QPS can. For the rare tool that needs a
 * breaker today: its `execute` is a plain async function — wrap it with
 * any breaker yourself, or hide it dynamically via a `gatedTools`
 * predicate (the visible-tool list is recomputed every iteration).
 * First-class per-tool breakers (state keyed by tool name; run-scoped
 * vs process-scoped TBD) are a possible future enhancement.
 *
 * **Status: contract-shaped and tested — independently reproduced against a
 * local harness, 2026-08-13.** Somebody who is not this library's author drove
 * this breaker through its whole walk: it opened after two failures, served the
 * next request from a fallback WITHOUT calling the primary, half-opened after
 * cooldown, and closed after two successful probes. The one gap that run named
 * — *"breaker transitions have no typed AgentFootprint event"* — is what 9.32.0
 * closes with the `'circuit-changed'` report below, and the per-process scope
 * stated further down was restated there and is unchanged.
 *
 * The failures were SCRIPTED and the cooldown was a test clock: those tests
 * *"were local and deterministic, so they consumed no GCP credit"*. **No live
 * provider has tripped this breaker from this repository**, so this is not the
 * field-validated rung — a real degradation, with a real vendor's error mix and
 * a real recovery time, remains unexercised.
 */
import type { LLMProvider } from '../provider-contract/types.js';
export interface WithCircuitBreakerOptions {
    /** Consecutive failures before the breaker OPENS. Default 5. */
    readonly failureThreshold?: number;
    /** How long the breaker stays OPEN before probing. Default 30s. */
    readonly cooldownMs?: number;
    /** Successes required in HALF-OPEN to fully CLOSE. Default 2. */
    readonly halfOpenSuccessThreshold?: number;
    /**
     * Predicate — does this error count toward the threshold? Default:
     * everything except an AbortError, `UnsupportedThinkingError`,
     * `UnsupportedToolChoiceError` or `UnsafeThinkingReplayError` (a
     * request the adapter refused before sending) counts. Override to ignore client
     * errors (e.g., 4xx) so a malformed request doesn't trip the
     * breaker for everyone.
     */
    readonly shouldCount?: (error: unknown) => boolean;
    /**
     * Hook invoked on every state transition — your own callback, fired
     * whether or not a run is in flight, and therefore the right place
     * for a Redis-backed counter or a cluster-wide coordinator.
     *
     * Since 9.32 it is no longer the ONLY way to see a trip. Every
     * transition is ALSO reported through the per-call `LLMCallHooks`
     * channel as a `'circuit-changed'` `ResilienceReport`, which the
     * in-run call site turns into
     * `agentfootprint.error.circuit_changed` with the run's real
     * correlation ids — the same seam `withFallback` and `withRetry`
     * have always used. The two are complements, not duplicates: this
     * hook fires everywhere, the event fires inside a run.
     */
    readonly onStateChange?: (state: CircuitState, reason: string) => void;
}
export type CircuitState = 'closed' | 'open' | 'half-open';
/**
 * Thrown by the wrapped provider when the breaker is OPEN. Carries
 * the underlying root-cause error from the most recent failure so
 * consumers can observe what tripped the breaker.
 */
export declare class CircuitOpenError extends Error {
    readonly code: "ERR_CIRCUIT_OPEN";
    /** The error that tripped the breaker (or the most recent failure
     *  during HALF-OPEN that re-opened it). */
    readonly cause: unknown;
    /** Wall-clock timestamp at which the breaker may next probe. */
    readonly retryAfter: number;
    constructor(providerName: string, cause: unknown, retryAfter: number);
}
/**
 * Wrap a provider with a circuit breaker.
 *
 * @example
 * ```ts
 * import { anthropic, openai } from 'agentfootprint/providers';
 * import { withCircuitBreaker, withFallback } from 'agentfootprint/resilience';
 *
 * const provider = withFallback(
 *   withCircuitBreaker(anthropic({ apiKey }), { failureThreshold: 5, cooldownMs: 30_000 }),
 *   withCircuitBreaker(openai({ apiKey })),
 * );
 * ```
 */
export declare function withCircuitBreaker(inner: LLMProvider, options?: WithCircuitBreakerOptions): LLMProvider;
//# sourceMappingURL=withCircuitBreaker.d.ts.map