vern-llm 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -7,16 +7,7 @@ type LLMErrorType = 'timeout' | 'api' | 'network' | 'parse' | 'validation' | 'in
7
7
  * e.g. `authentication`/`authorization` apply the same way regardless of
8
8
  * which type wraps them.
9
9
  */
10
- type LLMErrorCode = 'unknown_tool' | 'duplicate_tool_call_id' | 'tool_choice_none_violated' | 'unexpected_tool_calls' | 'unsupported_capability' | 'duplicate_tool_names' | 'unknown_tool_choice' | 'duplicate_tool_result_ids' | 'unknown_tool_result_ids' | 'missing_tool_results' | 'middleware_threw' | 'rate_limit_queue_full' | 'rate_limit_queue_timeout' | 'rate_limit_capacity_exceeded' | 'provider_rate_limited' | 'request_timeout' | 'idle_timeout' | 'middleware_timeout' | 'deadline_exceeded' | 'authentication' | 'authorization' | 'not_found' | 'payload_too_large' | 'server_error' | 'empty_response' | 'connection_failed' | 'circuit_cooling_down' | 'circuit_trial_in_flight' | 'fallback_exhausted' | 'tool_arguments_parse_failed' | 'stream_frame_invalid';
11
- /**
12
- * Tool contract codes: a model or provider response defect, not a
13
- * transient provider fault. Deterministic on the wire request, so
14
- * retrying can't change the outcome and it shouldn't count toward the
15
- * circuit breaker either. Shared by `LLMError.retryable` below and by
16
- * `CallExecutor`'s own retry/breaker accounting, so the two can't drift
17
- * apart.
18
- */
19
-
10
+ type LLMErrorCode = 'unknown_tool' | 'duplicate_tool_call_id' | 'tool_choice_none_violated' | 'unexpected_tool_calls' | 'unsupported_capability' | 'duplicate_tool_names' | 'unknown_tool_choice' | 'duplicate_tool_result_ids' | 'unknown_tool_result_ids' | 'missing_tool_results' | 'middleware_threw' | 'rate_limit_queue_full' | 'rate_limit_queue_timeout' | 'rate_limit_capacity_exceeded' | 'provider_rate_limited' | 'retry_budget_exhausted' | 'request_timeout' | 'idle_timeout' | 'middleware_timeout' | 'deadline_exceeded' | 'authentication' | 'authorization' | 'not_found' | 'payload_too_large' | 'server_error' | 'empty_response' | 'connection_failed' | 'circuit_cooling_down' | 'circuit_trial_in_flight' | 'fallback_exhausted' | 'tool_arguments_parse_failed' | 'stream_frame_invalid' | 'soft_failure_detected';
20
11
  /** One tool call's contract failure, used to report every bad call in a response at once. */
21
12
  interface ToolIssue {
22
13
  name: string;
@@ -133,21 +124,6 @@ interface LLMRequestSnapshot {
133
124
  /** Wall clock time the attempt started, ms since epoch. */
134
125
  startedAt: number;
135
126
  }
136
- /**
137
- * Builds a point-in-time, plain data copy of one attempt's outgoing
138
- * request. Mirrors `LLMError.toSnapshot()`: never thrown or dispatched
139
- * again, safe to serialize and store. A plain function rather than a
140
- * method, since unlike `LLMError` a request has no throwable identity or
141
- * derived state worth wrapping in a class.
142
- *
143
- * `startedAt` is optional so existing call sites (and tests) that don't
144
- * care about exact timing keep working, but a caller that has a real
145
- * capture time should always pass it: this function may run well after
146
- * the request was actually dispatched (e.g. `callExecutor` only builds
147
- * the snapshot once an attempt has failed), so defaulting to `Date.now()`
148
- * here would record failure-handling time, not request-start time.
149
- */
150
-
151
127
  /**
152
128
  * One failed attempt on the way to a terminal error: which attempt index
153
129
  * it was, and a snapshot of the error it failed with. The base shape
@@ -193,6 +169,14 @@ declare class LLMError extends Error {
193
169
  * alone carries no retry signal.
194
170
  */
195
171
  get retryable(): boolean;
172
+ /**
173
+ * Whether this failure should count toward the circuit breaker's
174
+ * failure threshold. Not the same question as `retryable`:
175
+ * `quota_exceeded` is retryable but says nothing about provider
176
+ * health, so it's excluded here even though `retryable` is true for
177
+ * it. Always false whenever `retryable` is false.
178
+ */
179
+ get countsTowardBreaker(): boolean;
196
180
  /**
197
181
  * Copies this error's fields into an {@link LLMErrorSnapshot}, for
198
182
  * recording as a `RetryAttempt`/`FallbackAttempt`. `retryable` is
@@ -235,10 +219,9 @@ declare function isLLMError(err: unknown): err is LLMError;
235
219
  declare function hasIssues<C extends keyof LLMErrorIssuesByCode>(err: LLMError, code: C): err is LLMError & {
236
220
  code: C;
237
221
  issues: LLMErrorIssuesByCode[C];
238
- }; //#endregion
222
+ };
223
+ //#endregion
239
224
  //#region src/types/cache.d.ts
240
-
241
- //# sourceMappingURL=errors.d.ts.map
242
225
  interface CacheAdapter<T = unknown> {
243
226
  get(key: string): Promise<{
244
227
  hit: boolean;
@@ -249,13 +232,20 @@ interface CacheAdapter<T = unknown> {
249
232
  resolveKey?(key: string): Promise<string>;
250
233
  }
251
234
  /**
252
- * Trivial default so the package works out of the box with no external deps
253
- * Not shared across processes, swap in Redis/Upstash/etc for production
235
+ * Which entry `InMemoryCacheAdapter` evicts once `maxSize` is exceeded.
236
+ * `'fifo'` (default) drops the oldest inserted entry. `'lru'` drops the
237
+ * least recently read or written entry.
238
+ */
239
+ type EvictionOption = 'fifo' | 'lru';
240
+ /**
241
+ * Trivial default so the package works out of the box with no external deps.
242
+ * Not shared across processes, swap in Redis/Upstash/etc for production.
254
243
  */
255
244
  declare class InMemoryCacheAdapter<T = unknown> implements CacheAdapter<T> {
256
245
  private readonly maxSize;
257
246
  private store;
258
- constructor(maxSize?: number);
247
+ private readonly eviction;
248
+ constructor(maxSize?: number, eviction?: EvictionOption);
259
249
  get(key: string): Promise<{
260
250
  hit: boolean;
261
251
  value: T | null;
@@ -302,10 +292,8 @@ declare class TieredCacheAdapter<T = unknown> implements CacheAdapter<T> {
302
292
  set(key: string, value: T, ttl: number): Promise<void>;
303
293
  delete(key: string): Promise<void>;
304
294
  }
305
-
306
295
  //#endregion
307
296
  //#region src/types/events.d.ts
308
- //# sourceMappingURL=cache.d.ts.map
309
297
  /**
310
298
  * Reports what happened during a call. Fire and forget, mirroring
311
299
  * `onUsage`: the return value is never read and a throwing handler cannot
@@ -373,10 +361,8 @@ type VernLLMEvent = {
373
361
  patchedFields?: string[];
374
362
  };
375
363
  type OnEvent = (event: VernLLMEvent) => void;
376
-
377
364
  //#endregion
378
365
  //#region src/types/middleware.d.ts
379
- //# sourceMappingURL=events.d.ts.map
380
366
  /** Capabilities of the target a middleware hook is currently looking at. */
381
367
  interface MiddlewareCapabilities {
382
368
  /**
@@ -584,21 +570,14 @@ interface VernLLMMiddleware {
584
570
  /** Observes the same events reported on `VernLLMOptions.onEvent`, filtered by this middleware's own `enabled`. Called from both stages; narrow on `ctx.stage` before reading stage-specific fields. */
585
571
  onEvent?: (event: VernLLMEvent, ctx: MiddlewareContext) => void;
586
572
  }
587
-
588
573
  //#endregion
589
574
  //#region src/circuitBreaker.d.ts
590
- //# sourceMappingURL=middleware.d.ts.map
591
- /** The call this mutation happened as part of, forwarded to `onStateChange` untouched. `CircuitBreaker` never inspects it. */
575
+ /** The call this mutation happened as part of, forwarded to `onStateChange` untouched. */
592
576
  interface CircuitBreakerCallContext {
593
577
  requestId: string;
594
578
  state: MiddlewareStateBag;
595
579
  signal?: AbortSignal;
596
- /**
597
- * The real, current attempt number for this dispatch, when the call
598
- * site actually has one in scope (i.e. after a dispatch was made or
599
- * failed). Omitted for calls that happen before any attempt exists,
600
- * like `assertClosed`'s pre-dispatch check.
601
- */
580
+ /** Omitted for calls before any attempt exists, like `assertClosed`'s pre-dispatch check. */
602
581
  attempt?: number;
603
582
  }
604
583
  interface CircuitBreakerOptions {
@@ -607,95 +586,218 @@ interface CircuitBreakerOptions {
607
586
  /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */
608
587
  cooldownMs?: number;
609
588
  /**
610
- * Called after every real state change, never for a no-op transition
611
- * (e.g. open to open). `model` is the resolved model of whichever call
612
- * triggered this specific transition (the `model` passed to whichever
613
- * of `assertClosed`/`recordSuccess`/`recordFailure` caused it).
614
- *
615
- * With `isolateByModel` off (the default), this is a label only: the
616
- * breaker still counts failures across every model together, so a
617
- * threshold crossing can be the sum of several different models'
618
- * failures even though only the triggering call's `model` is reported
619
- * here. With `isolateByModel` on, it's exact: each model has its own
620
- * counter, so the transition really was caused solely by that model.
589
+ * Fires after every real state change, never a no-op transition. `model`
590
+ * is the resolved model of whichever call triggered it. With
591
+ * `isolateByModel` off, failures are still counted across every model.
621
592
  */
622
- onStateChange?: (from: CircuitState, to: CircuitState, consecutiveFailures: number, model?: string, /** See `CircuitBreakerCallContext`. */
623
- context?: CircuitBreakerCallContext) => void;
593
+ onStateChange?: (from: CircuitState, to: CircuitState, consecutiveFailures: number, model?: string, context?: CircuitBreakerCallContext) => void;
624
594
  /**
625
595
  * Track a separate circuit per resolved model instead of one shared
626
- * circuit for the whole instance. A failure on one model then never
627
- * opens another model's circuit, at the cost of slower detection for
628
- * an outage spread across many distinct models (each model's counter
629
- * must independently cross `threshold`). Default false: one shared
630
- * circuit, matching every version before this option existed.
631
- *
632
- * A call that omits `model` (only possible calling `CircuitBreaker`
633
- * directly, `VernLLM` always passes one) falls into one shared bucket
634
- * alongside every other call that also omits it.
596
+ * circuit. Default false. A call that omits `model` falls into one
597
+ * shared bucket alongside every other call that also omits it.
635
598
  */
636
599
  isolateByModel?: boolean;
637
- }
600
+ /** Trial calls allowed through per half-open cycle. Default 1, clamped to at least 1. */
601
+ halfOpenProbes?: number;
602
+ /** Fraction of `halfOpenProbes` that must succeed to close the circuit. Default 1, clamped to `[0, 1]`. */
603
+ halfOpenSuccessRatio?: number;
604
+ /**
605
+ * Grows `cooldownMs` on each repeat open instead of a fixed wait.
606
+ * `{ multiplier, maxMs }` covers exponential growth; a `CooldownBackoff`
607
+ * function covers anything else. Omitted means `cooldownMs` stays fixed.
608
+ */
609
+ cooldownBackoff?: ExponentialBackoffOptions | CooldownBackoff;
610
+ /**
611
+ * Decides when a bucket's failures should open the circuit.
612
+ * `{ kind: 'consecutive', threshold }` (the default) opens after that
613
+ * many failures in a row. `{ kind: 'rolling', windowMs, minCalls,
614
+ * failureRatio }` opens once at least `minCalls` calls have landed in
615
+ * the trailing `windowMs` and the failure ratio reaches `failureRatio`.
616
+ * `minCalls` must be a non-negative integer; `failureRatio` must be
617
+ * finite and within `[0, 1]`. Both are validated at construction,
618
+ * thrown as `RangeError`. A `TrippingPolicy` covers anything else, one
619
+ * instance shared across every model automatically under
620
+ * `isolateByModel`, since it tracks its own state per key rather than
621
+ * owning one flat counter.
622
+ */
623
+ tripping?: TrippingOption;
624
+ }
625
+ /** Computes the cooldown for a bucket's `reopenCount`-th repeat open. */
626
+ type CooldownBackoff = (reopenCount: number, baseCooldownMs: number) => number;
627
+ interface ExponentialBackoffOptions {
628
+ /** Growth factor applied per repeat open, e.g. 2 doubles each time. */
629
+ multiplier: number;
630
+ /** Upper bound on the computed cooldown, in ms. Default `Infinity`. */
631
+ maxMs?: number;
632
+ }
633
+ /**
634
+ * Decides when a bucket's failures should open the circuit. Keyed by
635
+ * `key` (a resolved model, or the shared bucket's key when
636
+ * `isolateByModel` is off) rather than holding one flat counter, so a
637
+ * single `TrippingPolicy` instance is always safe to share across every
638
+ * bucket: `CircuitBreaker` never needs to clone or construct a fresh one
639
+ * per model, `isolateByModel` isolation falls out of `key` alone.
640
+ */
641
+ interface TrippingPolicy {
642
+ onSuccess(key: string): void;
643
+ /** Returns true if this failure should open the circuit for `key`. */
644
+ onFailure(key: string): boolean;
645
+ reset(key: string): void;
646
+ /**
647
+ * Called when `key`'s bucket is discarded (closed and idle, under
648
+ * `isolateByModel`), so a keyed policy can release that key's state.
649
+ * Optional: omit if there's nothing to release.
650
+ */
651
+ forget?(key: string): void;
652
+ }
653
+ declare class ConsecutiveTripping implements TrippingPolicy {
654
+ private readonly threshold;
655
+ private failuresByKey;
656
+ constructor(threshold: number);
657
+ onSuccess(key: string): void;
658
+ onFailure(key: string): boolean;
659
+ reset(key: string): void;
660
+ forget(key: string): void;
661
+ }
662
+ declare class RollingTripping implements TrippingPolicy {
663
+ private readonly windowMs;
664
+ private readonly minCalls;
665
+ private readonly failureRatio;
666
+ private ratiosByKey;
667
+ constructor(windowMs: number, minCalls: number, failureRatio: number);
668
+ private ratioFor;
669
+ onSuccess(key: string): void;
670
+ onFailure(key: string): boolean;
671
+ reset(key: string): void;
672
+ forget(key: string): void;
673
+ }
674
+ /** Not exported. Internal shorthand union for `CircuitBreakerOptions.tripping`. */
675
+ type TrippingOption = {
676
+ kind: 'consecutive';
677
+ threshold: number;
678
+ } | {
679
+ kind: 'rolling';
680
+ windowMs: number;
681
+ minCalls: number;
682
+ failureRatio: number;
683
+ } | TrippingPolicy;
638
684
  type CircuitState = 'closed' | 'open' | 'half-open';
639
685
  /**
640
- * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across
641
- * calls. Once the threshold is hit, short-circuits new calls with an
642
- * LLMError('circuit_open') instead of hitting the provider, until the
643
- * cooldown elapses and a single trial call is allowed through
686
+ * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures
687
+ * across calls. Once the threshold is hit, short-circuits new calls with
688
+ * LLMError('circuit_open') until the cooldown elapses and a trial succeeds.
644
689
  */
645
690
  declare class CircuitBreaker {
646
- private readonly threshold;
647
691
  private readonly cooldownMs;
648
692
  private readonly onStateChange?;
649
- /** Whether this breaker tracks failures per model instead of one shared circuit. Read by `CallExecutor`/`VernLLM` to report per-target in `getCircuitStates`. */
693
+ /** Whether this breaker tracks failures per model instead of one shared circuit. */
650
694
  readonly isolateByModel: boolean;
695
+ private readonly halfOpenProbes;
696
+ private readonly halfOpenSuccessRatio;
697
+ private readonly cooldownBackoff?;
698
+ /** One instance, keyed per model internally. See `TrippingPolicy`. */
699
+ private readonly tripping;
651
700
  private readonly sharedBucket;
652
701
  private readonly bucketsByModel;
653
702
  constructor(options?: CircuitBreakerOptions);
654
- /** Returns the bucket for a model if one already exists, without allocating. */
655
- private lookupBucket;
656
- /** Creates and stores a bucket for a model when the first mutation needs one. */
657
- private ensureBucketFor;
658
- /** Every state mutation routes through here, so `onStateChange` fires exactly once per real change. */
659
- private transition;
660
703
  /**
661
704
  * Throws if the circuit is open and the cooldown hasn't elapsed, or if
662
- * the circuit is half-open and a trial call is already in flight.
663
- * Otherwise, if the circuit just became eligible for a trial (cooldown
664
- * elapsed, or half-open with no trial currently running), this call
665
- * becomes that trial
705
+ * half-open with every trial slot claimed. Otherwise claims a trial slot.
666
706
  */
667
707
  assertClosed(model?: string, context?: CircuitBreakerCallContext): void;
668
708
  recordSuccess(model?: string, context?: CircuitBreakerCallContext): void;
669
- recordFailure(model?: string, context?: CircuitBreakerCallContext): void;
670
- /**
671
- * With `isolateByModel` off (the default), `model` is ignored and the
672
- * one shared circuit's state is returned, unchanged from every version
673
- * before this option existed. With `isolateByModel` on, returns that
674
- * model's own state, `'closed'` for a model never seen yet, same as a
675
- * fresh breaker.
676
- */
709
+ /** `code`, when present, is the failing `LLMError`'s `code`. Missing attributes to `'unknown'`. */
710
+ recordFailure(model?: string, context?: CircuitBreakerCallContext, code?: LLMErrorCode): void;
711
+ /** With `isolateByModel` off, `model` is ignored and the shared circuit's state is returned. */
677
712
  getState(model?: string): CircuitState;
713
+ /** Failure counts by `LLMErrorCode` for `model`'s bucket. Returned as a plain object copy. */
714
+ getFailureBreakdown(model?: string): Partial<Record<LLMErrorCode | 'unknown', number>>;
715
+ /** Manually opens the circuit, as if `threshold` consecutive failures had just happened. */
716
+ open(model?: string, context?: CircuitBreakerCallContext): void;
717
+ /** Manually closes the circuit and resets its failure count, without requiring a real success first. */
718
+ close(model?: string, context?: CircuitBreakerCallContext): void;
678
719
  /**
679
- * Manually opens the circuit, as if `threshold` consecutive failures had
680
- * just happened, e.g. to pull a provider out of rotation ahead of known
681
- * maintenance. Resets the cooldown window from now, same as a real
682
- * threshold-crossing failure would, and clears any in-flight half-open
683
- * trial since it no longer applies once the circuit is (re)opened.
720
+ * Opens `bucket`: stamps `openedAt`/`cooldownMsForOpen` and transitions
721
+ * to `open`. Shared by `recordFailure`'s trip, `settleTrialIfComplete`'s
722
+ * reopen, and the manual `open()`, all of which reach this with
723
+ * `bucket.trial` already `null`.
684
724
  */
685
- open(model?: string, context?: CircuitBreakerCallContext): void;
725
+ private openBucket;
726
+ /** Computes and clamps the cooldown for `bucket`'s current `reopenCount`. Called once, on open. */
727
+ private computeCooldown;
728
+ /** Returns the bucket for a model if one already exists, without allocating. */
729
+ private lookupBucket;
686
730
  /**
687
- * Manually closes the circuit and resets its failure count, e.g. once a
688
- * provider is confirmed healthy again without waiting out the cooldown.
689
- * Mirrors `recordSuccess`'s bookkeeping (including dropping the
690
- * per-model bucket under `isolateByModel`, once idle) but without
691
- * requiring an actual successful call first.
731
+ * The key `tripping` is called with. Real per-model isolation under
732
+ * `isolateByModel`, matching `ensureBucketFor`/`lookupBucket`'s own
733
+ * per-model key. Otherwise one fixed shared key regardless of what
734
+ * `model` was passed, matching `sharedBucket` being the one and only
735
+ * bucket in that mode: `model` is never allowed to split tripping state
736
+ * when `isolateByModel` is off, the same way it never splits which
737
+ * bucket a call lands in.
692
738
  */
693
- close(model?: string, context?: CircuitBreakerCallContext): void;
739
+ private trippingKeyFor;
740
+ /** Creates and stores a bucket for a model when the first mutation needs one. */
741
+ private ensureBucketFor;
742
+ /** Drops an idle model's bucket and lets `tripping` release that key's state too. */
743
+ private forgetModel;
744
+ /** Every state mutation routes through here, so `onStateChange` fires exactly once per real change. */
745
+ private transition;
746
+ /** Once every admitted trial has reported in, closes or reopens based on `halfOpenSuccessRatio`. */
747
+ private settleTrialIfComplete;
748
+ }
749
+ //#endregion
750
+ //#region src/internal/retryBudget.d.ts
751
+ /**
752
+ * Tunables for a `RetryBudget`. `windowMs`/`minCalls` behave the same as
753
+ * `RollingTripping`'s (see `circuitBreaker.ts`): `minCalls` gates the
754
+ * check so a cold start with too little traffic to judge doesn't trip.
755
+ * `retryRatio` is the max fraction of calls in the window allowed to be
756
+ * retries before the budget stops allowing more. `minCalls` must be a
757
+ * non-negative integer; `retryRatio` must be finite and within `[0, 1]`.
758
+ * Both are validated at construction, thrown as `RangeError`.
759
+ */
760
+ interface RetryBudgetOptions {
761
+ windowMs: number;
762
+ minCalls: number;
763
+ retryRatio: number;
764
+ }
765
+ /**
766
+ * Caps how much of a target's recent traffic is allowed to be retries,
767
+ * independent of the circuit breaker. The breaker asks whether the
768
+ * provider is healthy; this asks whether retrying is still worth the
769
+ * capacity it costs, regardless of provider health. Reuses `RollingRatio`,
770
+ * the same primitive `RollingTripping` is built on, rather than a second
771
+ * hand rolled window.
772
+ */
773
+ declare class RetryBudget {
774
+ private readonly options;
775
+ private readonly ratio;
776
+ constructor(options: RetryBudgetOptions);
777
+ /**
778
+ * Throws `LLMError('retry_budget_exhausted')` once at least `minCalls`
779
+ * calls have landed in the trailing `windowMs` and the retry ratio
780
+ * among them has reached `retryRatio`. A no-op otherwise.
781
+ */
782
+ assertAvailable(): void;
783
+ /** Records one attempt. `isRetry` is false for a call's first attempt, true for every attempt after it. */
784
+ recordAttempt(isRetry: boolean): void;
785
+ /** Current traffic and retry ratio in the trailing window. */
786
+ getSnapshot(): {
787
+ attempts: number;
788
+ retryRatio: number;
789
+ };
790
+ }
791
+ //#endregion
792
+ //#region src/internal/utils/rateLimitHint.utils.d.ts
793
+ /** A normalized read of a provider's rate limit headers. */
794
+ interface ProviderRateLimitHint {
795
+ remainingRequests?: number;
796
+ limitRequests?: number;
797
+ resetAfterMs?: number;
694
798
  }
695
-
696
799
  //#endregion
697
800
  //#region src/rateLimit.d.ts
698
- //# sourceMappingURL=circuitBreaker.d.ts.map
699
801
  /** The request shape sent to `LLMClient['chat']['completions']['create']`, used for token estimation. */
700
802
  type WireRequest = Parameters<LLMClient['chat']['completions']['create']>[0];
701
803
  /** Which configured bucket is currently blocking a call. */
@@ -724,6 +826,27 @@ interface RateLimitOptions {
724
826
  * chars/4 heuristic over message content plus `max_tokens`.
725
827
  */
726
828
  estimateTokens?: (request: WireRequest) => number;
829
+ /**
830
+ * AIMD against the `requestsPerMinute` bucket. Omit for a fixed
831
+ * ceiling, today's behavior. Requires `requestsPerMinute`.
832
+ */
833
+ aimd?: AimdOptions;
834
+ }
835
+ interface AimdOptions {
836
+ /** Added to the requests-per-minute ceiling on every clean release. */
837
+ increaseBy: number;
838
+ /** Multiplied against the ceiling on a rate-limit signal. Must be in `(0, 1]`; clamped otherwise. */
839
+ decreaseFactor: number;
840
+ /** Floor the ceiling never shrinks below. */
841
+ minCapacity: number;
842
+ /** Ceiling the bucket never grows above. */
843
+ maxCapacity: number;
844
+ /**
845
+ * Shrink proactively once a provider hint reports `remainingRequests`
846
+ * at or below this, before a real 429 happens. Default 0, meaning
847
+ * off.
848
+ */
849
+ proactiveFloor?: number;
727
850
  }
728
851
  interface RateLimitAcquireResult {
729
852
  /**
@@ -732,7 +855,7 @@ interface RateLimitAcquireResult {
732
855
  * Idempotent: only the first call does anything. Must run in a
733
856
  * `finally` block so a slot is never leaked on a failed attempt.
734
857
  */
735
- release: (actualTokens?: number) => void;
858
+ release: (actualTokens?: number, success?: boolean) => void;
736
859
  /** How long this attempt waited in queue before capacity was available. */
737
860
  waitedMs: number;
738
861
  /** Which bucket was blocking this attempt just before it cleared, if any wait happened. */
@@ -740,19 +863,35 @@ interface RateLimitAcquireResult {
740
863
  }
741
864
  /** Default `estimateTokens`: chars/4 over every message's content, plus the requested `max_tokens`. */
742
865
  declare function defaultEstimateTokens(request: WireRequest): number;
866
+ /**
867
+ * What VernLLM's dispatch layer needs from a limiter. `RateLimiter`
868
+ * implements this; a caller wanting cross-process coordination can hand
869
+ * over their own instance instead, see `buildRateLimit`. Every method is
870
+ * required, `RateLimiter` itself already no-ops the AIMD methods when
871
+ * `aimd` isn't configured, so a custom limiter follows the same pattern.
872
+ */
873
+ interface RateLimiterAdapter {
874
+ estimate(request: WireRequest): number;
875
+ acquire(estimatedTokens: number, signal?: AbortSignal): Promise<RateLimitAcquireResult>;
876
+ signalRateLimit(): void;
877
+ reactToRateLimitHint(hint: ProviderRateLimitHint | undefined): void;
878
+ }
743
879
  /**
744
880
  * Per-target rate limiter. Up to three buckets (requests/min, tokens/min,
745
881
  * concurrency) behind one FIFO queue, so a large call isn't starved by a
746
882
  * stream of small ones. Any bucket omitted from `options` has infinite
747
883
  * capacity and never blocks.
748
884
  */
749
- declare class RateLimiter {
885
+ declare class RateLimiter implements RateLimiterAdapter {
750
886
  private readonly requests?;
751
887
  private readonly tokens?;
752
888
  private readonly concurrency?;
889
+ /** Buckets in acquire precedence order (concurrency, rpm, tpm), omitted ones filtered out. Built once so order can't drift between `tryAcquireBuckets` and `scheduleWake`. */
890
+ private readonly buckets;
753
891
  private readonly maxQueueMs;
754
892
  private readonly maxQueueSize;
755
893
  private readonly estimateTokensFn;
894
+ private readonly aimd?;
756
895
  private readonly queue;
757
896
  /**
758
897
  * A single scheduled re-check for the head of the queue when it's
@@ -773,12 +912,7 @@ declare class RateLimiter {
773
912
  acquire(estimatedTokens: number, signal?: AbortSignal): Promise<RateLimitAcquireResult>;
774
913
  private queueFullError;
775
914
  private enqueue;
776
- /**
777
- * Checks and takes from every configured bucket as one atomic unit: if
778
- * any bucket lacks capacity, whatever was already taken from the
779
- * earlier ones in this attempt is rolled back before reporting which
780
- * bucket blocked.
781
- */
915
+ /** Takes from every configured bucket as one atomic unit, in `this.buckets`' order. Rolls back whatever was already taken if any bucket lacks capacity. */
782
916
  private tryAcquireBuckets;
783
917
  /** Drains the queue head first. Stops at the first waiter that still can't proceed, so no one is starved out of turn. */
784
918
  private drain;
@@ -795,13 +929,37 @@ declare class RateLimiter {
795
929
  * bucket is a real spend that only recovers via its own refill, and the
796
930
  * tokens bucket is reconciled against `actualTokens` rather than fully
797
931
  * refunded, since real tokens really were spent.
932
+ *
933
+ * `success` defaults to `false`: the AIMD ceiling only grows when the
934
+ * caller explicitly confirms a successful attempt. A failed or
935
+ * rate-limited attempt still releases its slot (so nothing leaks), but
936
+ * must not also grow the ceiling right back up after
937
+ * `signalRateLimit()` just shrank it.
798
938
  */
799
939
  private makeRelease;
940
+ /** Shared guard and resize call behind both AIMD halves below; only the arithmetic differs. */
941
+ private resizeRequestsCeiling;
942
+ /** AIMD's additive-increase half: grows the ceiling by `aimd.increaseBy` on a clean release. No-op without `aimd`/`requestsPerMinute`. */
943
+ private growOnSuccess;
944
+ /**
945
+ * AIMD's multiplicative-decrease half. Called on a real 429, and,
946
+ * where an adapter can produce a hint, proactively via
947
+ * `reactToRateLimitHint`. Never throws or blocks a call itself, only
948
+ * adjusts the ceiling as a side effect.
949
+ */
950
+ signalRateLimit(): void;
951
+ /**
952
+ * AIMD's proactive entry point: shrinks via `signalRateLimit()` if
953
+ * `hint.remainingRequests` is at or below `aimd.proactiveFloor`.
954
+ */
955
+ reactToRateLimitHint(hint: ProviderRateLimitHint | undefined): void;
800
956
  }
801
-
957
+ //#endregion
958
+ //#region src/internal/utils/rateLimitAdapter.utils.d.ts
959
+ /** Not exported. Internal shorthand only, so this union isn't duplicated between the public option fields and `buildRateLimit`'s own signature. */
960
+ type RateLimitOption = RateLimitOptions | RateLimiterAdapter;
802
961
  //#endregion
803
962
  //#region src/types/fallback.d.ts
804
- //# sourceMappingURL=rateLimit.d.ts.map
805
963
  /**
806
964
  * One provider to try after the primary (or after an earlier fallback
807
965
  * target) fails. Order is the policy: VernLLM never reorders, scores, or
@@ -810,11 +968,11 @@ declare class RateLimiter {
810
968
  * Most per-target overrides fall back to the parent `VernLLM` instance's
811
969
  * own option when omitted, so a target only needs to specify what's
812
970
  * actually different about it (a different client/model is the common
813
- * case). `circuitBreaker` and `rateLimit` are the exception: they are
814
- * never inherited from the parent, since a breaker or limiter tuned for
815
- * the primary provider's limits is rarely right for a fallback's. Leave
816
- * them unset on a target to run it without one, even if the parent has
817
- * one configured.
971
+ * case). `circuitBreaker`, `rateLimit`, and `retryBudget` are the
972
+ * exception: they are never inherited from the parent, since a breaker,
973
+ * limiter, or budget tuned for the primary provider's limits is rarely
974
+ * right for a fallback's. Leave them unset on a target to run it without
975
+ * one, even if the parent has one configured.
818
976
  */
819
977
  interface FallbackTarget {
820
978
  client: LLMClient;
@@ -833,7 +991,16 @@ interface FallbackTarget {
833
991
  /** This target's own circuit breaker, independent of every other target's. Not inherited from the parent's `circuitBreaker`. */
834
992
  circuitBreaker?: boolean | CircuitBreakerOptions;
835
993
  /** This target's own rate limiter, independent of every other target's. Not inherited from the parent's `rateLimit`. */
836
- rateLimit?: RateLimitOptions;
994
+ rateLimit?: RateLimitOption;
995
+ /** This target's own retry budget, independent of every other target's. Not inherited from the parent's `retryBudget`. */
996
+ retryBudget?: RetryBudgetOptions;
997
+ /**
998
+ * Reclassifies an otherwise-successful result from this target as a
999
+ * failure. Falls back to the parent `VernLLM` instance's own
1000
+ * `detectSoftFailure` when omitted, same as most other per-target
1001
+ * options (unlike `circuitBreaker`/`rateLimit`, which never inherit).
1002
+ */
1003
+ detectSoftFailure?: DetectSoftFailure;
837
1004
  }
838
1005
  /**
839
1006
  * Written into `CallParams['meta']` once `call()` resolves, so a caller
@@ -926,10 +1093,8 @@ declare function isFallbackExhaustedError(err: unknown): err is FallbackExhauste
926
1093
  declare function metaRef(): {
927
1094
  current?: CallMeta;
928
1095
  };
929
-
930
1096
  //#endregion
931
1097
  //#region src/types/schema.d.ts
932
- //# sourceMappingURL=fallback.d.ts.map
933
1098
  /**
934
1099
  * Minimal structural type for a Zod-like schema, so this package doesnt need
935
1100
  * a hard dependency on a specific Zod major version. Any object exposing
@@ -959,10 +1124,8 @@ interface JsonSchemaSpec {
959
1124
  strict?: boolean;
960
1125
  description?: string;
961
1126
  }
962
-
963
1127
  //#endregion
964
1128
  //#region src/types/tools.d.ts
965
- //# sourceMappingURL=schema.d.ts.map
966
1129
  /**
967
1130
  * Describes a capability the model may request, not the capability
968
1131
  * itself. VernLLM transports this to the provider and parses what comes
@@ -1113,7 +1276,6 @@ declare function isToolCallResult<Tools extends readonly ToolDefinition[] | unde
1113
1276
  type ToolChoice = 'auto' | 'none' | 'required' | {
1114
1277
  name: string;
1115
1278
  };
1116
-
1117
1279
  //#endregion
1118
1280
  //#region src/types/usage.d.ts
1119
1281
  type ReserveUsage = (params: {
@@ -1179,10 +1341,8 @@ type OnUsage = (usage: TokenUsage) => void;
1179
1341
  * to report.
1180
1342
  */
1181
1343
  type OnUsageFailure = (usage: TokenUsage, error: LLMError) => void;
1182
-
1183
1344
  //#endregion
1184
1345
  //#region src/types/call.d.ts
1185
- //# sourceMappingURL=usage.d.ts.map
1186
1346
  /**
1187
1347
  * Any valid JSON value: a primitive, `null`, or a JSON array/object made
1188
1348
  * of the same. This is what `call()` returns when `jsonMode: true`.
@@ -1514,10 +1674,26 @@ type CachedJsonModeEnabledCallParams = CachedCallInput & {
1514
1674
  schema?: never;
1515
1675
  };
1516
1676
  };
1517
-
1677
+ /** Context handed to `DetectSoftFailure` alongside the response it's inspecting. */
1678
+ interface SoftFailureMeta {
1679
+ requestId: string;
1680
+ model: string;
1681
+ providerName: string;
1682
+ isFallback: boolean;
1683
+ /** 1-based, matching `CallMeta.attempts`. */
1684
+ attempt: number;
1685
+ }
1686
+ /**
1687
+ * Inspects an otherwise-successful result and optionally reclassifies it
1688
+ * as a failure. Returning `undefined` leaves the result as a success;
1689
+ * returning an `LLMErrorCode` fails the attempt with that code, feeding
1690
+ * the same retry and circuit-breaker paths a thrown error would. A
1691
+ * result that parses fine but is empty, truncated, or a low-confidence
1692
+ * refusal is otherwise invisible to both.
1693
+ */
1694
+ type DetectSoftFailure<T = unknown> = (result: T | CallWithToolsResult<T>, meta: SoftFailureMeta) => LLMErrorCode | undefined;
1518
1695
  //#endregion
1519
1696
  //#region src/types/stream.d.ts
1520
- //# sourceMappingURL=call.d.ts.map
1521
1697
  /** One incremental unit of a streaming response, as delivered to the caller. */
1522
1698
  type StreamChunk = {
1523
1699
  type: 'text-delta';
@@ -1622,6 +1798,18 @@ type WireStreamChunk = {
1622
1798
  * Never surfaced to callers as a `StreamChunk`.
1623
1799
  */
1624
1800
  type: 'ping';
1801
+ } | {
1802
+ /**
1803
+ * AIMD's proactive rate-limit hint, read off the stream's
1804
+ * response headers (where the adapter/SDK can get at them) and
1805
+ * yielded once, as early as possible. Mirrors `attachRateLimitHint`
1806
+ * for the non-streaming path, just carried as a chunk instead of a
1807
+ * hidden property on a response object, since a stream has no
1808
+ * single response value to attach one to. Never surfaced to
1809
+ * callers as a `StreamChunk`.
1810
+ */
1811
+ type: 'rate_limit_hint';
1812
+ hint: ProviderRateLimitHint;
1625
1813
  };
1626
1814
  /**
1627
1815
  * Parameters for a cached, streaming LLM call without tool calling.
@@ -1696,10 +1884,8 @@ type CachedStreamJsonModeEnabledCallParams = CachedCallInput & {
1696
1884
  schema?: never;
1697
1885
  };
1698
1886
  };
1699
-
1700
1887
  //#endregion
1701
1888
  //#region src/types/client.d.ts
1702
- //# sourceMappingURL=stream.d.ts.map
1703
1889
  /** A tool call as it appears on the wire, OpenAI's `function`-wrapped shape. */
1704
1890
  interface WireToolCall {
1705
1891
  id: string;
@@ -1829,10 +2015,20 @@ interface LLMClient {
1829
2015
  };
1830
2016
  };
1831
2017
  }
1832
-
2018
+ //#endregion
2019
+ //#region src/internal/utils/cacheAdapter.utils.d.ts
2020
+ /**
2021
+ * Not exported. Internal shorthand for `VernLLMOptions.cache`, so the
2022
+ * union isn't duplicated between that field and `buildCache`'s own
2023
+ * signature. A caller never writes this type by name, either a config
2024
+ * object literal or a real `CacheAdapter`.
2025
+ */
2026
+ type CacheOption = {
2027
+ maxSize?: number;
2028
+ eviction?: EvictionOption;
2029
+ } | CacheAdapter;
1833
2030
  //#endregion
1834
2031
  //#region src/logger.d.ts
1835
- //# sourceMappingURL=client.d.ts.map
1836
2032
  interface Logger {
1837
2033
  debug(message: string): void;
1838
2034
  warn(message: string): void;
@@ -1849,10 +2045,8 @@ declare class ConsoleLogger implements Logger {
1849
2045
  warn(message: string): void;
1850
2046
  error(message: string, meta?: Record<string, unknown>): void;
1851
2047
  }
1852
-
1853
2048
  //#endregion
1854
2049
  //#region src/types/options.d.ts
1855
- //# sourceMappingURL=logger.d.ts.map
1856
2050
  interface VernLLMOptions {
1857
2051
  client: LLMClient;
1858
2052
  model: string;
@@ -1921,8 +2115,24 @@ interface VernLLMOptions {
1921
2115
  * (no redaction).
1922
2116
  */
1923
2117
  redact?: (text: string) => string;
1924
- /** Cache adapter for cachedCall. Defaults to an in-memory adapter */
1925
- cache?: CacheAdapter;
2118
+ /**
2119
+ * Cache for cachedCall. `{ maxSize, eviction }` configures the
2120
+ * built-in in-memory adapter (`eviction` default `'fifo'`). Pass a
2121
+ * `CacheAdapter` directly for a real backend. Default: in-memory,
2122
+ * maxSize 1000, fifo.
2123
+ */
2124
+ cache?: CacheOption;
2125
+ /**
2126
+ * Reclassifies an otherwise-successful result as a failure, e.g. a
2127
+ * response that parsed fine but came back empty or truncated. Runs
2128
+ * once per attempt, right after a response is validated. Returning
2129
+ * `undefined` leaves the result untouched; returning an
2130
+ * `LLMErrorCode` fails that attempt with it, feeding the same retry
2131
+ * and circuit-breaker paths a thrown error would. A throwing hook is
2132
+ * caught, logged, and treated as no soft failure, so a broken hook
2133
+ * degrades safely instead of failing every call.
2134
+ */
2135
+ detectSoftFailure?: DetectSoftFailure;
1926
2136
  /** HTTP status codes that should fail fast without retrying. Default [400, 401, 403, 404, 422] */
1927
2137
  nonRetryableStatus?: number[];
1928
2138
  /** Custom JSON parser. Must return undefined/null on failure. Default: JSON.parse wrapped in try/catch */
@@ -1961,8 +2171,22 @@ interface VernLLMOptions {
1961
2171
  * letting the provider reject them. Independent of the `Retry-After`
1962
2172
  * handling already applied to a provider 429: this avoids tripping the
1963
2173
  * limit in the first place. Omit for unlimited (the default).
2174
+ *
2175
+ * A plain config object builds an in-process limiter. Pass a
2176
+ * `RateLimiterAdapter` instead for cross-process coordination.
2177
+ */
2178
+ rateLimit?: RateLimitOption;
2179
+ /**
2180
+ * Caps how much of this target's recent traffic is allowed to be
2181
+ * retries, independent of `circuitBreaker`. Once at least `minCalls`
2182
+ * calls have landed in the trailing `windowMs` and the retry ratio
2183
+ * among them reaches `retryRatio`, further retries against this target
2184
+ * throw `LLMError('retry_budget_exhausted')` instead of retrying,
2185
+ * protecting the target's real capacity even while its breaker is
2186
+ * still closed. Omit for no budget (the default). Never inherited by
2187
+ * `fallback` targets, same as `circuitBreaker`/`rateLimit`.
1964
2188
  */
1965
- rateLimit?: RateLimitOptions;
2189
+ retryBudget?: RetryBudgetOptions;
1966
2190
  /**
1967
2191
  * Ordered targets tried after the primary, in order, once it (and its
1968
2192
  * own retries) is exhausted or abandoned. Order is the policy: VernLLM
@@ -1999,10 +2223,8 @@ interface VernLLMOptions {
1999
2223
  */
2000
2224
  middlewareTimeoutMs?: number;
2001
2225
  }
2002
-
2003
2226
  //#endregion
2004
2227
  //#region src/types/createMiddleware.d.ts
2005
- //# sourceMappingURL=options.d.ts.map
2006
2228
  /**
2007
2229
  * `VernLLMMiddleware` plus `onError`, a convenience for the common "I
2008
2230
  * only care about failures" case. Everything else is passed through to
@@ -2035,10 +2257,8 @@ type CreateMiddlewareOptions = Omit<VernLLMMiddleware, 'wrap'> & {
2035
2257
  * returns or throws, only what gets observed about it.
2036
2258
  */
2037
2259
  declare function createMiddleware(options: CreateMiddlewareOptions): VernLLMMiddleware;
2038
-
2039
2260
  //#endregion
2040
2261
  //#region src/vernLLM.d.ts
2041
- //# sourceMappingURL=createMiddleware.d.ts.map
2042
2262
  /**
2043
2263
  * A LLM call framework for resilience, observability and control. This is VernLLM!
2044
2264
  *
@@ -2178,6 +2398,26 @@ declare class VernLLM {
2178
2398
  * target that doesn't exist.
2179
2399
  */
2180
2400
  getCircuitState(target?: CircuitTarget): CircuitState | undefined;
2401
+ /**
2402
+ * @param target.index Which target to read. Defaults to the primary.
2403
+ * @param target.model Which model bucket to read, if the target isolates by model.
2404
+ * @returns Failure counts by `LLMErrorCode`, `'unknown'` for a missing
2405
+ * code, or `undefined` if that target has no breaker.
2406
+ * @throws {RangeError} If `target.index` names no target.
2407
+ */
2408
+ getFailureBreakdown(target?: CircuitTarget): Partial<Record<LLMErrorCode | 'unknown', number>> | undefined;
2409
+ /**
2410
+ * @param target.index Which target to read. Defaults to the primary.
2411
+ * @returns This target's current retry traffic/ratio in the trailing
2412
+ * window, or `undefined` if that target has no retry budget
2413
+ * configured. A budget is target-scoped, not model-scoped, so unlike
2414
+ * `getFailureBreakdown` there's no `target.model` to pass.
2415
+ * @throws {RangeError} If `target.index` names no target.
2416
+ */
2417
+ getRetryBudgetState(target?: Pick<CircuitTarget, 'index'>): {
2418
+ attempts: number;
2419
+ retryRatio: number;
2420
+ } | undefined;
2181
2421
  /**
2182
2422
  * @param model Which model bucket to read, for targets that isolate by model.
2183
2423
  * @returns Every target's state, in chain order.
@@ -2202,10 +2442,8 @@ declare class VernLLM {
2202
2442
  */
2203
2443
  closeCircuit(target?: CircuitTarget): void;
2204
2444
  }
2205
-
2206
2445
  //#endregion
2207
2446
  //#region src/paramsHelpers.d.ts
2208
- //# sourceMappingURL=vernLLM.d.ts.map
2209
2447
  /**
2210
2448
  * Identity function preserving `params`'s own precise type, unlike a `:
2211
2449
  * CallParams<T>` annotation, which would widen `tools` away and break the
@@ -2241,10 +2479,8 @@ declare function defineCallParams<P extends CallParams<unknown>>(params: P): P;
2241
2479
  * ```
2242
2480
  */
2243
2481
  declare function defineCachedCallParams<P extends CachedCallParams<unknown>>(params: P): P;
2244
-
2245
2482
  //#endregion
2246
2483
  //#region src/adapters/internal/sse.d.ts
2247
- //# sourceMappingURL=paramsHelpers.d.ts.map
2248
2484
  /**
2249
2485
  * Parses a Server-Sent-Events byte/text stream into the JSON payload of
2250
2486
  * each `data:` frame, in arrival order. Generic over transport: works with
@@ -2280,29 +2516,18 @@ declare function parseSseStream(source: AsyncIterable<Uint8Array | string>): Asy
2280
2516
  * alive" separately from a genuinely empty frame (`NO_DATA`, kept internal).
2281
2517
  */
2282
2518
  declare const SSE_PING: unique symbol;
2283
-
2284
2519
  //#endregion
2285
2520
  //#region src/adapters/internal/imageFormat.d.ts
2286
- //# sourceMappingURL=sse.d.ts.map
2287
2521
  /**
2288
2522
  * MIME types accepted for `ImageBlock.mimeType` across all adapters. This is
2289
2523
  * the intersection of what Anthropic, Gemini, OpenAI-compatible, and Bedrock
2290
2524
  * Converse all natively support, so a `ContentBlock[]` that validates for
2291
2525
  * one provider validates for all of them.
2292
2526
  */
2293
- declare const SUPPORTED_IMAGE_MIME_TYPES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp"];
2527
+ declare const SUPPORTED_IMAGE_MIME_TYPES: readonly ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
2294
2528
  type SupportedImageMimeType = (typeof SUPPORTED_IMAGE_MIME_TYPES)[number];
2295
-
2296
2529
  //#endregion
2297
2530
  //#region src/adapters/internal/nativeStructuredOutput.d.ts
2298
- /**
2299
- * Validates an `ImageBlock.mimeType` against the shared supported set.
2300
- * Throws a non-retryable `LLMError('invalid_params')`, since an unsupported
2301
- * mimeType is a bug in the caller's own input, deterministic before any
2302
- * request is built, the same class of failure as every other check in
2303
- * `RequestBuilder`.
2304
- */
2305
-
2306
2531
  /**
2307
2532
  * A static allow-list or predicate naming which models support native,
2308
2533
  * schema-constrained output as its own request field. Anthropic's
@@ -2323,10 +2548,8 @@ type SupportedImageMimeType = (typeof SUPPORTED_IMAGE_MIME_TYPES)[number];
2323
2548
  * exactly this package's behavior before native support was added.
2324
2549
  */
2325
2550
  type ModelCapabilityOverride = string[] | ((model: string) => boolean);
2326
-
2327
2551
  //#endregion
2328
2552
  //#region src/adapters/internal/reasoningBudget.utils.d.ts
2329
- /** Resolves whether `model` is covered by a caller-supplied allow-list/predicate. */
2330
2553
  /**
2331
2554
  * Shared conversion between the two reasoning controls VernLLM exposes:
2332
2555
  * `reasoningEffort` (a tier string, OpenAI's native shape) and
@@ -2347,7 +2570,6 @@ type ModelCapabilityOverride = string[] | ((model: string) => boolean);
2347
2570
  * doesn't match these numbers.
2348
2571
  */
2349
2572
  type EffortTokenTable = Record<'minimal' | 'low' | 'medium' | 'high', number>;
2350
-
2351
2573
  //#endregion
2352
2574
  //#region src/adapters/anthropic.d.ts
2353
2575
  /** Anthropic's native per-block content shape for a message. */
@@ -2501,6 +2723,13 @@ interface AnthropicAdapterOptions {
2501
2723
  * predicate.
2502
2724
  */
2503
2725
  adaptiveOnlyModels?: ModelCapabilityOverride;
2726
+ /**
2727
+ * Whether the client's `messages.create` supports `.withResponse()`
2728
+ * (needed for AIMD's proactive path). Default `false`, since
2729
+ * `AnthropicClient` is structural and a test fake or thin wrapper
2730
+ * won't implement it.
2731
+ */
2732
+ supportsWithResponse?: boolean;
2504
2733
  }
2505
2734
  /**
2506
2735
  * Wraps an Anthropic SDK client so it satisfies the same `LLMClient`
@@ -2534,7 +2763,6 @@ interface AnthropicAdapterOptions {
2534
2763
  * `output_config.format` or a forced tool call).
2535
2764
  */
2536
2765
  declare function fromAnthropic(anthropicClient: AnthropicClient, options?: AnthropicAdapterOptions): LLMClient;
2537
-
2538
2766
  //#endregion
2539
2767
  //#region src/adapters/gemini.d.ts
2540
2768
  /**
@@ -2747,7 +2975,6 @@ interface GeminiAdapterOptions {
2747
2975
  thinkingLevelModels?: ModelCapabilityOverride;
2748
2976
  }
2749
2977
  declare function fromGemini(client: GeminiClient, options?: GeminiAdapterOptions): LLMClient;
2750
-
2751
2978
  //#endregion
2752
2979
  //#region src/adapters/bedrock.d.ts
2753
2980
  /** Bedrock Converse's supported inline image formats. */
@@ -3107,7 +3334,6 @@ interface AwsSendClient {
3107
3334
  * `create` branch above unwraps it.
3108
3335
  */
3109
3336
  declare function fromBedrock(bedrockClient: BedrockConverseClient | AwsSendClient, options?: BedrockAdapterOptions): LLMClient;
3110
-
3111
3337
  //#endregion
3112
3338
  //#region src/adapters/fetch.d.ts
3113
3339
  /** The chat-completion-shaped request VernLLM builds internally */
@@ -3225,6 +3451,11 @@ interface FetchAdapterConfig {
3225
3451
  * silently empty stream.
3226
3452
  */
3227
3453
  mapStreamEvent?: (event: unknown) => WireStreamChunk | WireStreamChunk[] | undefined;
3454
+ /**
3455
+ * Optional. How to read AIMD's proactive rate limit hint off a
3456
+ * successful response. Defaults to OpenAI's header set.
3457
+ */
3458
+ parseRateLimitHint?: (headers: ResponseLike['headers']) => ProviderRateLimitHint;
3228
3459
  }
3229
3460
  /**
3230
3461
  * A fetch-based escape hatch for providers with no SDK, or where pulling one
@@ -3249,7 +3480,6 @@ interface FetchAdapterConfig {
3249
3480
  * `mapStreamEvent` seam via `WireStreamChunk`'s `tool_call_delta` variant,
3250
3481
  * no separate config is needed for streaming vs non-streaming tool calls.
3251
3482
  *
3252
-
3253
3483
  * `createStream` requires `mapStreamEvent` (there's no non-streaming
3254
3484
  * response to fall back on, unlike the other three optional streaming
3255
3485
  * seams). It opens the request via `requestStream` (defaults to native
@@ -3266,7 +3496,6 @@ interface FetchAdapterConfig {
3266
3496
  * `fetch`.
3267
3497
  */
3268
3498
  declare function fromFetch(config: FetchAdapterConfig): LLMClient;
3269
-
3270
3499
  //#endregion
3271
3500
  //#region src/adapters/openaiCompatible.d.ts
3272
3501
  /**
@@ -3326,6 +3555,12 @@ interface OpenAICompatibleAdapterOptions {
3326
3555
  * effect when `reasoningEffort` is set directly.
3327
3556
  */
3328
3557
  reasoningEffortTokens?: Partial<EffortTokenTable>;
3558
+ /**
3559
+ * Whether the client's request builder supports `.withResponse()`
3560
+ * (needed for AIMD's proactive path). Default `false`, since not
3561
+ * every "OpenAI-compatible" client is confirmed to support it.
3562
+ */
3563
+ supportsWithResponse?: boolean;
3329
3564
  }
3330
3565
  declare function fromOpenAICompatible(client: unknown, options?: OpenAICompatibleAdapterOptions): LLMClient;
3331
3566
  /**
@@ -3335,11 +3570,15 @@ declare function fromOpenAICompatible(client: unknown, options?: OpenAICompatibl
3335
3570
  * variant) in ways that no longer structurally satisfy VernLLM's
3336
3571
  * provider-agnostic `ContentBlock[]` on `userContent`, so passing the SDK
3337
3572
  * instance directly can fail to typecheck depending on the installed
3338
- * `openai` version. Wrapping with `fromOpenAI()` (a plain alias of
3339
- * `fromOpenAICompatible()`) sidesteps that by translating through
3340
- * `unknown` at the boundary, and also picks up multimodal image
3341
- * translation and `createStream` wiring that a raw client doesn't have.
3342
- * See Migration Notes for details.
3573
+ * `openai` version. Wrapping with `fromOpenAI()` sidesteps that by
3574
+ * translating through `unknown` at the boundary, and also picks up
3575
+ * multimodal image translation and `createStream` wiring that a raw
3576
+ * client doesn't have. See Migration Notes for details.
3577
+ *
3578
+ * `supportsWithResponse` defaults to `false` here too:
3579
+ * `client` is `unknown`, so there's no way to verify it's really the
3580
+ * official `openai` package's client versus a fake or a test double.
3581
+ * Pass `supportsWithResponse: true` once you've confirmed it.
3343
3582
  */
3344
3583
  declare const fromOpenAI: typeof fromOpenAICompatible;
3345
3584
  /** Groqs SDK matches the OpenAI wire format */
@@ -3433,9 +3672,6 @@ declare const fromInfermatic: typeof fromOpenAICompatible;
3433
3672
  declare const fromAtlasCloud: typeof fromOpenAICompatible;
3434
3673
  /** 01.AI's (Yi models) API is OpenAI-compatible */
3435
3674
  declare const from01AI: typeof fromOpenAICompatible;
3436
-
3437
3675
  //#endregion
3438
- //# sourceMappingURL=openaiCompatible.d.ts.map
3439
-
3440
- export { AnthropicClient, AssistantContent, AttemptContext, BedrockConverseClient, CacheAdapter, CachedCallParams, CachedConditionalToolCallParams, CachedJsonModeDisabledCallParams, CachedJsonModeEnabledCallParams, CachedStreamCallParams, CachedStreamConditionalToolCallParams, CachedStreamJsonModeDisabledCallParams, CachedStreamJsonModeEnabledCallParams, CachedStreamToolCallParams, CachedToolCallParams, CallMeta, CallParams, CallResult, CallWithToolsResult, CircuitBreaker, CircuitBreakerOptions, CircuitState, CircuitTarget, ConditionalToolCallParams, ConsoleLogger, ContentBlock, ContentResult, ConversationTurn, CreateMiddlewareOptions, DuplicateToolNamesIssue, FallbackAttempt, FallbackExhaustedError, FallbackOn, FallbackTarget, FetchAdapterConfig, GeminiClient, HistoryToolResultIssue, ImageBlock, InMemoryCacheAdapter, JsonModeDisabledCallParams, JsonModeEnabledCallParams, JsonSchemaSpec, JsonValue, LLMClient, LLMError, LLMErrorCode, LLMErrorIssuesByCode, LLMErrorSnapshot, LLMErrorType, LLMRequestShape, LLMRequestSnapshot, Logger, MiddlewareCapabilities, MiddlewareContext, MiddlewareContextBase, MiddlewareStateBag, MiddlewareStateKey, NormalizedCacheAdapter, OnEvent, OnUsage, PreDispatchContext, RateLimitAcquireResult, RateLimitOptions, RateLimitReason, RateLimiter, RefundUsage, ReserveUsage, RetryAttempt, SSE_PING, SchemaLike, StreamCallResult, StreamChunk, StreamEnabledCallParams, StreamJsonModeDisabledCallParams, StreamJsonModeEnabledCallParams, TargetCircuitState, TextBlock, TieredCacheAdapter, TokenUsage, ToolCall, ToolCallResult, ToolChoice, ToolDefinition, ToolEnabledCallParams, ToolIssue, ToolResult, ToolsDisabledCallParams, UnknownToolChoiceIssue, UnsupportedCapabilityIssue, VernLLM, VernLLMEvent, VernLLMMiddleware, VernLLMOptions, WireCallRequest, WireCallRequestPatch, WireMessage, WireRequest, WireResponseFormat, WireStreamChunk, WireTool, WireToolCall, WireToolChoice, createMiddleware, createMiddlewareStateBag, createStateKey, defaultEstimateTokens, defaultFallbackOn, defineCachedCallParams, defineCallParams, defineTool, from01AI, fromAnthropic, fromAnyscale, fromAtlasCloud, fromBaseten, fromBedrock, fromCerebras, fromCloudflareWorkersAI, fromDeepInfra, fromDeepSeek, fromFeatherless, fromFetch, fromFireworks, fromFriendli, fromGemini, fromGroq, fromHyperbolic, fromInferenceNet, fromInfermatic, fromLMStudio, fromLambdaLabs, fromLepton, fromMiniMax, fromMistral, fromMoonshot, fromNebius, fromNovita, fromNvidiaNIM, fromOllama, fromOpenAI, fromOpenAICompatible, fromOpenRouter, fromParasail, fromPerplexity, fromSambaNova, fromSiliconFlow, fromSnowflakeCortex, fromStepFun, fromTogether, fromVLLM, fromVercelAIGateway, fromXAI, fromZhipu, hasIssues, isFallbackExhaustedError, isLLMError, isToolCallResult, metaRef, parseSseStream };
3676
+ export { type AnthropicClient, type AssistantContent, type AttemptContext, type BedrockConverseClient, type CacheAdapter, type CachedCallParams, type CachedConditionalToolCallParams, type CachedJsonModeDisabledCallParams, type CachedJsonModeEnabledCallParams, type CachedStreamCallParams, type CachedStreamConditionalToolCallParams, type CachedStreamJsonModeDisabledCallParams, type CachedStreamJsonModeEnabledCallParams, type CachedStreamToolCallParams, type CachedToolCallParams, type CallMeta, type CallParams, type CallResult, type CallWithToolsResult, CircuitBreaker, type CircuitBreakerOptions, type CircuitState, type CircuitTarget, type ConditionalToolCallParams, ConsecutiveTripping, ConsoleLogger, type ContentBlock, type ContentResult, type ConversationTurn, type CooldownBackoff, type CreateMiddlewareOptions, type DuplicateToolNamesIssue, type EvictionOption, type ExponentialBackoffOptions, type FallbackAttempt, FallbackExhaustedError, type FallbackOn, type FallbackTarget, type FetchAdapterConfig, type GeminiClient, type HistoryToolResultIssue, type ImageBlock, InMemoryCacheAdapter, type JsonModeDisabledCallParams, type JsonModeEnabledCallParams, type JsonSchemaSpec, type JsonValue, type LLMClient, LLMError, type LLMErrorCode, type LLMErrorIssuesByCode, type LLMErrorSnapshot, type LLMErrorType, type LLMRequestShape, type LLMRequestSnapshot, type Logger, type MiddlewareCapabilities, type MiddlewareContext, type MiddlewareContextBase, type MiddlewareStateBag, type MiddlewareStateKey, NormalizedCacheAdapter, type OnEvent, type OnUsage, type PreDispatchContext, type RateLimitAcquireResult, type RateLimitOptions, type RateLimitReason, RateLimiter, type RateLimiterAdapter, type RefundUsage, type ReserveUsage, type RetryAttempt, RetryBudget, type RetryBudgetOptions, RollingTripping, SSE_PING, type SchemaLike, type StreamCallResult, type StreamChunk, type StreamEnabledCallParams, type StreamJsonModeDisabledCallParams, type StreamJsonModeEnabledCallParams, type TargetCircuitState, type TextBlock, TieredCacheAdapter, type TokenUsage, type ToolCall, type ToolCallResult, type ToolChoice, type ToolDefinition, type ToolEnabledCallParams, type ToolIssue, type ToolResult, type ToolsDisabledCallParams, type TrippingPolicy, type UnknownToolChoiceIssue, type UnsupportedCapabilityIssue, VernLLM, type VernLLMEvent, type VernLLMMiddleware, type VernLLMOptions, type WireCallRequest, type WireCallRequestPatch, type WireMessage, type WireRequest, type WireResponseFormat, type WireStreamChunk, type WireTool, type WireToolCall, type WireToolChoice, createMiddleware, createMiddlewareStateBag, createStateKey, defaultEstimateTokens, defaultFallbackOn, defineCachedCallParams, defineCallParams, defineTool, from01AI, fromAnthropic, fromAnyscale, fromAtlasCloud, fromBaseten, fromBedrock, fromCerebras, fromCloudflareWorkersAI, fromDeepInfra, fromDeepSeek, fromFeatherless, fromFetch, fromFireworks, fromFriendli, fromGemini, fromGroq, fromHyperbolic, fromInferenceNet, fromInfermatic, fromLMStudio, fromLambdaLabs, fromLepton, fromMiniMax, fromMistral, fromMoonshot, fromNebius, fromNovita, fromNvidiaNIM, fromOllama, fromOpenAI, fromOpenAICompatible, fromOpenRouter, fromParasail, fromPerplexity, fromSambaNova, fromSiliconFlow, fromSnowflakeCortex, fromStepFun, fromTogether, fromVLLM, fromVercelAIGateway, fromXAI, fromZhipu, hasIssues, isFallbackExhaustedError, isLLMError, isToolCallResult, metaRef, parseSseStream };
3441
3677
  //# sourceMappingURL=index.d.cts.map