vern-llm 2.1.1 → 2.3.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
@@ -1,87 +1,22 @@
1
- //#region src/circuitBreaker.d.ts
2
- interface CircuitBreakerOptions {
3
- /** Consecutive failures before the circuit opens, default 5 */
4
- threshold?: number;
5
- /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */
6
- cooldownMs?: number;
7
- /**
8
- * Called after every real state change, never for a no-op transition
9
- * (e.g. open to open). `model` is the resolved model of whichever call
10
- * triggered this specific transition (the `model` passed to whichever
11
- * of `assertClosed`/`recordSuccess`/`recordFailure` caused it).
12
- *
13
- * With `isolateByModel` off (the default), this is a label only: the
14
- * breaker still counts failures across every model together, so a
15
- * threshold crossing can be the sum of several different models'
16
- * failures even though only the triggering call's `model` is reported
17
- * here. With `isolateByModel` on, it's exact: each model has its own
18
- * counter, so the transition really was caused solely by that model.
19
- */
20
- onStateChange?: (from: CircuitState, to: CircuitState, consecutiveFailures: number, model?: string) => void;
21
- /**
22
- * Track a separate circuit per resolved model instead of one shared
23
- * circuit for the whole instance. A failure on one model then never
24
- * opens another model's circuit, at the cost of slower detection for
25
- * an outage spread across many distinct models (each model's counter
26
- * must independently cross `threshold`). Default false: one shared
27
- * circuit, matching every version before this option existed.
28
- *
29
- * A call that omits `model` (only possible calling `CircuitBreaker`
30
- * directly, `VernLLM` always passes one) falls into one shared bucket
31
- * alongside every other call that also omits it.
32
- */
33
- isolateByModel?: boolean;
34
- }
35
- type CircuitState = 'closed' | 'open' | 'half-open';
36
- /**
37
- * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across
38
- * calls. Once the threshold is hit, short-circuits new calls with an
39
- * LLMError('circuit_open') instead of hitting the provider, until the
40
- * cooldown elapses and a single trial call is allowed through
41
- */
42
- declare class CircuitBreaker {
43
- private readonly threshold;
44
- private readonly cooldownMs;
45
- private readonly onStateChange?;
46
- private readonly isolateByModel;
47
- private readonly sharedBucket;
48
- private readonly bucketsByModel;
49
- constructor(options?: CircuitBreakerOptions);
50
- /** Returns the bucket for a model if one already exists, without allocating. */
51
- private lookupBucket;
52
- /** Creates and stores a bucket for a model when the first mutation needs one. */
53
- private ensureBucketFor;
54
- /** Every state mutation routes through here, so `onStateChange` fires exactly once per real change. */
55
- private transition;
56
- /**
57
- * Throws if the circuit is open and the cooldown hasn't elapsed, or if
58
- * the circuit is half-open and a trial call is already in flight.
59
- * Otherwise, if the circuit just became eligible for a trial (cooldown
60
- * elapsed, or half-open with no trial currently running), this call
61
- * becomes that trial
62
- */
63
- assertClosed(model?: string): void;
64
- recordSuccess(model?: string): void;
65
- recordFailure(model?: string): void;
66
- /**
67
- * With `isolateByModel` off (the default), `model` is ignored and the
68
- * one shared circuit's state is returned, unchanged from every version
69
- * before this option existed. With `isolateByModel` on, returns that
70
- * model's own state, `'closed'` for a model never seen yet, same as a
71
- * fresh breaker.
72
- */
73
- getState(model?: string): CircuitState;
74
- } //#endregion
75
1
  //#region src/types/errors.d.ts
76
-
77
- //# sourceMappingURL=circuitBreaker.d.ts.map
78
- type LLMErrorType = 'timeout' | 'api' | 'parse' | 'validation' | 'circuit_open' | 'quota_exceeded' | 'unknown' | 'aborted';
2
+ type LLMErrorType = 'timeout' | 'api' | 'network' | 'parse' | 'validation' | 'invalid_params' | 'rate_limited' | 'quota_exceeded' | 'circuit_open' | 'fallback_exhausted' | 'aborted' | 'unknown';
79
3
  /**
80
4
  * Machine readable discriminator within a `type`, for cases where `type`
81
5
  * alone is too coarse to act on. Optional and additive: errors thrown
82
- * before a given code existed simply omit it.
6
+ * before a given code existed simply omit it. Not owned by a single type;
7
+ * e.g. `authentication`/`authorization` apply the same way regardless of
8
+ * which type wraps them.
83
9
  */
84
- type LLMErrorCode = 'unknown_tool' | 'duplicate_tool_call_id' | 'local_rate_limit' | 'provider_rate_limited' | 'fallback_exhausted';
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' | 'rate_limit_queue_full' | 'rate_limit_queue_timeout' | 'rate_limit_capacity_exceeded' | 'provider_rate_limited' | 'request_timeout' | 'idle_timeout' | '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
+
85
20
  /** One tool call's contract failure, used to report every bad call in a response at once. */
86
21
  interface ToolIssue {
87
22
  name: string;
@@ -89,23 +24,184 @@ interface ToolIssue {
89
24
  code: LLMErrorCode;
90
25
  detail?: unknown;
91
26
  }
27
+ /**
28
+ * The specific values behind a `duplicate_tool_names` failure: the
29
+ * offending call's `tools` array had more than one entry sharing a name.
30
+ */
31
+ interface DuplicateToolNamesIssue {
32
+ names: string[];
33
+ }
34
+ /**
35
+ * The specific values behind an `unknown_tool_choice` failure: `toolChoice`
36
+ * named a tool that wasn't in the call's own `tools` array.
37
+ */
38
+ interface UnknownToolChoiceIssue {
39
+ requested: string;
40
+ available: string[];
41
+ }
42
+ /**
43
+ * The specific values behind a `duplicate_tool_result_ids` /
44
+ * `unknown_tool_result_ids` / `missing_tool_results` failure: which
45
+ * `history` turn was affected, and which `toolCallId`s were the problem.
46
+ */
47
+ interface HistoryToolResultIssue {
48
+ historyIndex: number;
49
+ ids: string[];
50
+ }
51
+ /**
52
+ * The specific values behind an `unsupported_capability` failure: which
53
+ * capability the current adapter/client/model doesn't support.
54
+ */
55
+ interface UnsupportedCapabilityIssue {
56
+ capability: string;
57
+ }
58
+ /**
59
+ * Maps each `LLMErrorCode` that carries structured `issues` to that
60
+ * payload's exact shape. Not every code appears here: most `invalid_params`
61
+ * failures are a single deterministic fact the `message` already states in
62
+ * full, so adding a typed `issues` entry for them would only duplicate the
63
+ * message into a field, the same near-duplicate-code problem `code` itself
64
+ * avoids. Codes that repeat here are exactly the ones whose `message`
65
+ * already string-joins a list a caller might want to consume directly
66
+ * rather than re-parse out of prose, or that otherwise want a place to
67
+ * report the exact captured values of a failure.
68
+ *
69
+ * Deliberately not a mapped type over the whole `LLMErrorCode` union: a
70
+ * schema-validation failure's `issues` (the caller's own Zod-compatible
71
+ * validator's error object) has no code and no shape VernLLM could know in
72
+ * advance, so it stays untyped on `LLMError.issues` itself rather than
73
+ * forcing every code into this table.
74
+ */
75
+ interface LLMErrorIssuesByCode {
76
+ unknown_tool: ToolIssue[];
77
+ duplicate_tool_call_id: ToolIssue[];
78
+ duplicate_tool_names: DuplicateToolNamesIssue;
79
+ unknown_tool_choice: UnknownToolChoiceIssue;
80
+ duplicate_tool_result_ids: HistoryToolResultIssue;
81
+ unknown_tool_result_ids: HistoryToolResultIssue;
82
+ missing_tool_results: HistoryToolResultIssue;
83
+ unsupported_capability: UnsupportedCapabilityIssue;
84
+ }
85
+ /**
86
+ * Point-in-time copy of an `LLMError`'s fields, produced by
87
+ * `LLMError.toSnapshot()`. This is what `RetryAttempt.error` holds
88
+ * instead of a live `LLMError`.
89
+ *
90
+ * A past attempt only needs to be describable (message, type, code,
91
+ * whether it was retryable), never thrown again. So it skips `Error`'s
92
+ * behavior, `instanceof` identity, and any live getter. Using the full
93
+ * `LLMError` class here would also make the type self referential
94
+ * through its own `attempts` field.
95
+ *
96
+ * Has no `cause`. `cause` is `unknown` and never validated by VernLLM,
97
+ * and it is meant to be read directly on the live error you just
98
+ * caught, not carried indefinitely inside history. `type`, `code`,
99
+ * `status`, and `issues` are the structured fields a snapshot carries
100
+ * instead.
101
+ *
102
+ * `attempts` is still present, since a recorded attempt can itself be
103
+ * the terminal failure of an inner retry loop with its own history (see
104
+ * `FallbackAttempt`). That's a tree of past data, not a cycle.
105
+ */
106
+ interface LLMErrorSnapshot {
107
+ message: string;
108
+ type: LLMErrorType;
109
+ status?: number;
110
+ issues?: unknown;
111
+ retryAfterMs?: number;
112
+ code?: LLMErrorCode;
113
+ /** Computed once, at snapshot time, since a snapshot has no live getter. */
114
+ retryable: boolean;
115
+ /** This attempt's own prior attempts, if it was itself the terminal failure of a retry loop. */
116
+ attempts?: RetryAttempt[];
117
+ }
118
+ /**
119
+ * One failed attempt on the way to a terminal error: which attempt index
120
+ * it was, and a snapshot of the error it failed with. The base shape
121
+ * every richer attempt record (e.g. `FallbackAttempt`) extends, rather
122
+ * than duplicates.
123
+ */
124
+ interface RetryAttempt {
125
+ index: number;
126
+ error: LLMErrorSnapshot;
127
+ }
128
+ /** Optional fields for constructing an {@link LLMError}. `message` and `type` stay positional since every throw site sets both. */
129
+ interface LLMErrorOptions {
130
+ status?: number;
131
+ issues?: unknown;
132
+ cause?: unknown;
133
+ retryAfterMs?: number;
134
+ /** Stable discriminator within `type`. Absent on errors predating it. */
135
+ code?: LLMErrorCode;
136
+ /** Every attempt made before this error was thrown, in order. Absent when nothing was retried. */
137
+ attempts?: RetryAttempt[];
138
+ }
92
139
  declare class LLMError extends Error {
93
140
  type: LLMErrorType;
94
- status?: number | undefined;
95
- issues?: unknown | undefined;
96
- cause?: unknown | undefined;
97
- retryAfterMs?: number | undefined;
141
+ status?: number;
142
+ issues?: unknown;
143
+ cause?: unknown;
144
+ retryAfterMs?: number;
98
145
  /** Stable discriminator within `type`. Absent on errors predating it. */
99
- code?: LLMErrorCode | undefined;
100
- constructor(message: string, type: LLMErrorType, status?: number | undefined, issues?: unknown | undefined, cause?: unknown | undefined, retryAfterMs?: number | undefined, /** Stable discriminator within `type`. Absent on errors predating it. */
101
- code?: LLMErrorCode | undefined);
102
- /** Every tool contract failure in one response, when there is more than one. */
103
- toolIssues?: ToolIssue[];
146
+ code?: LLMErrorCode;
147
+ /** Every attempt made before this error was thrown, in order. Absent when nothing was retried. */
148
+ attempts?: RetryAttempt[];
149
+ constructor(message: string, type: LLMErrorType, options?: LLMErrorOptions);
150
+ /**
151
+ * Computed purely from `type`/`code`, independent of any specific call's
152
+ * `nonRetryableStatus` list. False for `parse`/`validation`/
153
+ * `invalid_params`/`aborted` types (the caller's own input, the model's
154
+ * own response, or intentional cancellation, none of which are the
155
+ * provider being unhealthy), the tool contract codes, and the local
156
+ * rate limit codes. Subclasses (see `FallbackExhaustedError`) may
157
+ * override this when `type` alone carries no retry signal.
158
+ */
159
+ get retryable(): boolean;
160
+ /**
161
+ * Copies this error's fields into an {@link LLMErrorSnapshot}, for
162
+ * recording as a `RetryAttempt`/`FallbackAttempt`. `retryable` is
163
+ * captured here since a snapshot has no getter of its own. `cause` is
164
+ * not copied, see `LLMErrorSnapshot`'s own doc. `issues` and every
165
+ * nested `attempts` entry's own `issues` go through `safeAttempts`,
166
+ * since a schema validation failure's `issues` is a caller supplied
167
+ * value, not controlled by VernLLM, and `attempts` is itself a public
168
+ * constructor option a caller can hand build.
169
+ */
170
+ toSnapshot(): LLMErrorSnapshot;
171
+ /**
172
+ * Controls what `JSON.stringify(err)` produces. Omits `cause` for the
173
+ * same reason `toSnapshot()` does: `cause` is `unknown` and never
174
+ * validated by VernLLM, and some SDK errors carry circular structures
175
+ * `JSON.stringify` cannot serialize at all. Read `err.cause` directly
176
+ * instead. `issues`, including every nested `attempts` entry's own
177
+ * `issues`, goes through `safeAttempts` for the same reason: a schema
178
+ * validation failure's `issues` is caller supplied and not guaranteed
179
+ * circular free. Also includes `message` and `retryable`, which a
180
+ * plain property walk would otherwise miss: `message` is
181
+ * non-enumerable on `Error`, and `retryable` is a getter, not an own
182
+ * property.
183
+ */
184
+ toJSON(): Record<string, unknown>;
104
185
  }
105
186
  declare function isLLMError(err: unknown): err is LLMError;
106
-
107
- //#endregion
187
+ /**
188
+ * Narrows `err.issues` to the exact shape {@link LLMErrorIssuesByCode} maps
189
+ * `code` to, for any code listed there. `code` stays the only discriminator
190
+ * VernLLM uses; this just gives that existing check a typed return instead
191
+ * of requiring a manual cast of `issues`:
192
+ *
193
+ * ```ts
194
+ * if (isLLMError(err) && hasIssues(err, 'duplicate_tool_names')) {
195
+ * console.log(err.issues.names); // string[], no cast needed
196
+ * }
197
+ * ```
198
+ */
199
+ declare function hasIssues<C extends keyof LLMErrorIssuesByCode>(err: LLMError, code: C): err is LLMError & {
200
+ code: C;
201
+ issues: LLMErrorIssuesByCode[C];
202
+ }; //#endregion
108
203
  //#region src/types/cache.d.ts
204
+
109
205
  //# sourceMappingURL=errors.d.ts.map
110
206
  interface CacheAdapter<T = unknown> {
111
207
  get(key: string): Promise<{
@@ -172,8 +268,102 @@ declare class TieredCacheAdapter<T = unknown> implements CacheAdapter<T> {
172
268
  }
173
269
 
174
270
  //#endregion
175
- //#region src/rateLimit.d.ts
271
+ //#region src/circuitBreaker.d.ts
176
272
  //# sourceMappingURL=cache.d.ts.map
273
+ interface CircuitBreakerOptions {
274
+ /** Consecutive failures before the circuit opens, default 5 */
275
+ threshold?: number;
276
+ /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */
277
+ cooldownMs?: number;
278
+ /**
279
+ * Called after every real state change, never for a no-op transition
280
+ * (e.g. open to open). `model` is the resolved model of whichever call
281
+ * triggered this specific transition (the `model` passed to whichever
282
+ * of `assertClosed`/`recordSuccess`/`recordFailure` caused it).
283
+ *
284
+ * With `isolateByModel` off (the default), this is a label only: the
285
+ * breaker still counts failures across every model together, so a
286
+ * threshold crossing can be the sum of several different models'
287
+ * failures even though only the triggering call's `model` is reported
288
+ * here. With `isolateByModel` on, it's exact: each model has its own
289
+ * counter, so the transition really was caused solely by that model.
290
+ */
291
+ onStateChange?: (from: CircuitState, to: CircuitState, consecutiveFailures: number, model?: string) => void;
292
+ /**
293
+ * Track a separate circuit per resolved model instead of one shared
294
+ * circuit for the whole instance. A failure on one model then never
295
+ * opens another model's circuit, at the cost of slower detection for
296
+ * an outage spread across many distinct models (each model's counter
297
+ * must independently cross `threshold`). Default false: one shared
298
+ * circuit, matching every version before this option existed.
299
+ *
300
+ * A call that omits `model` (only possible calling `CircuitBreaker`
301
+ * directly, `VernLLM` always passes one) falls into one shared bucket
302
+ * alongside every other call that also omits it.
303
+ */
304
+ isolateByModel?: boolean;
305
+ }
306
+ type CircuitState = 'closed' | 'open' | 'half-open';
307
+ /**
308
+ * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across
309
+ * calls. Once the threshold is hit, short-circuits new calls with an
310
+ * LLMError('circuit_open') instead of hitting the provider, until the
311
+ * cooldown elapses and a single trial call is allowed through
312
+ */
313
+ declare class CircuitBreaker {
314
+ private readonly threshold;
315
+ private readonly cooldownMs;
316
+ private readonly onStateChange?;
317
+ /** Whether this breaker tracks failures per model instead of one shared circuit. Read by `CallExecutor`/`VernLLM` to report per-target in `getCircuitStates`. */
318
+ readonly isolateByModel: boolean;
319
+ private readonly sharedBucket;
320
+ private readonly bucketsByModel;
321
+ constructor(options?: CircuitBreakerOptions);
322
+ /** Returns the bucket for a model if one already exists, without allocating. */
323
+ private lookupBucket;
324
+ /** Creates and stores a bucket for a model when the first mutation needs one. */
325
+ private ensureBucketFor;
326
+ /** Every state mutation routes through here, so `onStateChange` fires exactly once per real change. */
327
+ private transition;
328
+ /**
329
+ * Throws if the circuit is open and the cooldown hasn't elapsed, or if
330
+ * the circuit is half-open and a trial call is already in flight.
331
+ * Otherwise, if the circuit just became eligible for a trial (cooldown
332
+ * elapsed, or half-open with no trial currently running), this call
333
+ * becomes that trial
334
+ */
335
+ assertClosed(model?: string): void;
336
+ recordSuccess(model?: string): void;
337
+ recordFailure(model?: string): void;
338
+ /**
339
+ * With `isolateByModel` off (the default), `model` is ignored and the
340
+ * one shared circuit's state is returned, unchanged from every version
341
+ * before this option existed. With `isolateByModel` on, returns that
342
+ * model's own state, `'closed'` for a model never seen yet, same as a
343
+ * fresh breaker.
344
+ */
345
+ getState(model?: string): CircuitState;
346
+ /**
347
+ * Manually opens the circuit, as if `threshold` consecutive failures had
348
+ * just happened, e.g. to pull a provider out of rotation ahead of known
349
+ * maintenance. Resets the cooldown window from now, same as a real
350
+ * threshold-crossing failure would, and clears any in-flight half-open
351
+ * trial since it no longer applies once the circuit is (re)opened.
352
+ */
353
+ open(model?: string): void;
354
+ /**
355
+ * Manually closes the circuit and resets its failure count, e.g. once a
356
+ * provider is confirmed healthy again without waiting out the cooldown.
357
+ * Mirrors `recordSuccess`'s bookkeeping (including dropping the
358
+ * per-model bucket under `isolateByModel`, once idle) but without
359
+ * requiring an actual successful call first.
360
+ */
361
+ close(model?: string): void;
362
+ }
363
+
364
+ //#endregion
365
+ //#region src/rateLimit.d.ts
366
+ //# sourceMappingURL=circuitBreaker.d.ts.map
177
367
  /** The request shape sent to `LLMClient['chat']['completions']['create']`, used for token estimation. */
178
368
  type WireRequest = Parameters<LLMClient['chat']['completions']['create']>[0];
179
369
  /** Which configured bucket is currently blocking a call. */
@@ -331,16 +521,27 @@ interface TargetCircuitState {
331
521
  /** Position in the chain: `0` for the primary, `1`+ for fallback targets. */
332
522
  index: number;
333
523
  isFallback: boolean;
524
+ /** Whether this target tracks failures per model. `false` means `model` on `getCircuitStates` had no effect on this entry. */
525
+ isolateByModel: boolean;
334
526
  /** `undefined` if that target has no circuit breaker configured. */
335
527
  state: CircuitState | undefined;
336
528
  }
337
- /** One target's failure, recorded on the way to either the next target or `FallbackExhaustedError`. */
338
- interface FallbackAttempt {
339
- /** `-1` for the primary target. */
340
- index: number;
529
+ /** Which target/model `VernLLM.getCircuitState`, `openCircuit`, and `closeCircuit` act on. */
530
+ interface CircuitTarget {
531
+ /** Which target to act on. `0` is the primary, `1`+ are fallbacks. Defaults to `0`. */
532
+ index?: number;
533
+ /** Which model bucket to act on, if the resolved target isolates by model. */
534
+ model?: string;
535
+ }
536
+ /**
537
+ * One target's failure, recorded on the way to either the next target or
538
+ * `FallbackExhaustedError`. Extends `RetryAttempt`: `index` is `-1` for
539
+ * the primary target here (rather than a plain retry count), and
540
+ * `provider`/`model` identify which target failed.
541
+ */
542
+ interface FallbackAttempt extends RetryAttempt {
341
543
  provider: string;
342
544
  model: string;
343
- error: LLMError;
344
545
  }
345
546
  /**
346
547
  * Decides what happens after a target's own retries are exhausted or
@@ -361,13 +562,23 @@ declare const defaultFallbackOn: FallbackOn;
361
562
  * or `fallbackOn` chose to stop early. Carries each attempt in order so
362
563
  * an outage across providers stays debuggable without reproducing it.
363
564
  * Extends `LLMError` so `isLLMError` and any `instanceof LLMError` check
364
- * still passes, inheriting the last failure's `type` so existing
365
- * type-based handling keeps working on a fallback-exhausted error too.
565
+ * still passes, inheriting the last failure's `type`/`status`/`retryAfterMs`
566
+ * so existing type-based handling, including reading `retryAfterMs` on an
567
+ * `'api'`-typed error, keeps working on a fallback-exhausted error too.
366
568
  */
367
569
  declare class FallbackExhaustedError extends LLMError {
368
570
  readonly attempts: FallbackAttempt[];
369
571
  constructor(attempts: FallbackAttempt[]);
572
+ /**
573
+ * `type: 'fallback_exhausted'` by itself says nothing about whether
574
+ * retrying could help; the reason the last target failed does. Defers to
575
+ * that attempt's own `retryable` instead of anything about this class's
576
+ * own type.
577
+ */
578
+ get retryable(): boolean;
370
579
  }
580
+ /** Narrows `err` to {@link FallbackExhaustedError}, for direct access to its `attempts` (`provider`/`model` per failed target) without a manual `instanceof` check. */
581
+ declare function isFallbackExhaustedError(err: unknown): err is FallbackExhaustedError;
371
582
 
372
583
  //#endregion
373
584
  //#region src/types/schema.d.ts
@@ -676,6 +887,20 @@ interface CallParams<T = unknown> extends UsageHooks {
676
887
  type ToolEnabledCallParams<T> = CallParams<T> & {
677
888
  tools: NonNullable<CallParams<T>['tools']>;
678
889
  };
890
+ /**
891
+ * A `CallParams` variant where tools are offered but the model is barred
892
+ * from calling one. `toolChoice: 'none'` guarantees the response can never
893
+ * be a `tool_calls` result, so `call()` can narrow straight to
894
+ * `ContentResult<T>` instead of the full `CallWithToolsResult<T>` union.
895
+ * A call site that already knows it forced `'none'` no longer needs a
896
+ * runtime `isToolCallResult` check, or to remember that `String(result)`
897
+ * on the wrapper object silently produces `"[object Object]"` instead of
898
+ * throwing. The type itself rules that shape out.
899
+ */
900
+ type ToolsDisabledCallParams<T> = CallParams<T> & {
901
+ tools: NonNullable<CallParams<T>['tools']>;
902
+ toolChoice: 'none';
903
+ };
679
904
  /** Shared cache-configuration fields, minus the internal `fn` primitive. */
680
905
  interface CachedCallInput extends UsageHooks {
681
906
  cacheKey: string;
@@ -687,9 +912,21 @@ interface CachedCallInput extends UsageHooks {
687
912
  *
688
913
  * Combines the cache configuration with the `CallParams` passed to
689
914
  * `VernLLM.call()`. The cached value is the normal LLM response type `T`.
915
+ *
916
+ * `reserveUsage`/`refundUsage` are omitted from `call`'s type on purpose:
917
+ * `CachedCallInput` already extends `UsageHooks`, so those two hooks
918
+ * belong at the top level, alongside `cacheKey`/`ttl`, not nested inside
919
+ * `call`. Both positions used to typecheck, which meant `cachedCall`
920
+ * could only catch the mistake at runtime with a warning, after silently
921
+ * ignoring the caller's usage hooks. Putting them inside `call` as an
922
+ * inline object literal is now a compile error instead; TypeScript's
923
+ * excess-property check only applies to object literals though, so a
924
+ * preconstructed value carrying `reserveUsage`/`refundUsage` can still be
925
+ * structurally assignable, which is why `cachedCall` also checks for and
926
+ * rejects both hooks at runtime.
690
927
  */
691
928
  type CachedCallParams<T> = CachedCallInput & {
692
- call: CallParams<T>;
929
+ call: Omit<CallParams<T>, 'reserveUsage' | 'refundUsage'>;
693
930
  };
694
931
  /**
695
932
  * Parameters for a cached LLM call with tool calling enabled.
@@ -697,9 +934,12 @@ type CachedCallParams<T> = CachedCallInput & {
697
934
  * The cached value includes the full `CallWithToolsResult<T>`, meaning
698
935
  * tool requests and normal content responses are cached exactly as returned
699
936
  * by the model.
937
+ *
938
+ * See `CachedCallParams` for why `reserveUsage`/`refundUsage` are omitted
939
+ * from `call`'s type here too.
700
940
  */
701
941
  type CachedToolCallParams<T> = CachedCallInput & {
702
- call: ToolEnabledCallParams<T>;
942
+ call: Omit<ToolEnabledCallParams<T>, 'reserveUsage' | 'refundUsage'>;
703
943
  };
704
944
 
705
945
  //#endregion
@@ -810,9 +1050,12 @@ type WireStreamChunk = {
810
1050
  * and a hit synthesizes a one-shot `chunks` replay from the cached value
811
1051
  * (see `VernLLM.cachedCall`'s docs for exactly what that replay looks
812
1052
  * like).
1053
+ *
1054
+ * `reserveUsage`/`refundUsage` are omitted from `call`'s type; see
1055
+ * `CachedCallParams` for why — they belong at the top level here too.
813
1056
  */
814
1057
  type CachedStreamCallParams<T> = CachedCallInput & {
815
- call: StreamEnabledCallParams<T>;
1058
+ call: Omit<StreamEnabledCallParams<T>, 'reserveUsage' | 'refundUsage'>;
816
1059
  };
817
1060
  /**
818
1061
  * Parameters for a cached, streaming LLM call with tool calling enabled.
@@ -822,7 +1065,7 @@ type CachedStreamCallParams<T> = CachedCallInput & {
822
1065
  * replayed-chunks-on-hit behavior as `CachedStreamCallParams<T>`.
823
1066
  */
824
1067
  type CachedStreamToolCallParams<T> = CachedCallInput & {
825
- call: StreamEnabledCallParams<T> & ToolEnabledCallParams<T>;
1068
+ call: Omit<StreamEnabledCallParams<T> & ToolEnabledCallParams<T>, 'reserveUsage' | 'refundUsage'>;
826
1069
  };
827
1070
 
828
1071
  //#endregion
@@ -865,8 +1108,7 @@ type WireToolChoice = 'auto' | 'none' | 'required' | {
865
1108
  };
866
1109
  };
867
1110
  /**
868
- * Minimal shape compatible with the OpenAI SDKs chat.completions.create,
869
- * so consumers can pass an OpenAI client directly
1111
+ * Minimal shape similar to the OpenAI SDK's chat.completions.create API,
870
1112
  * `response_format.json_schema` and `reasoning_effort` are optional on the wire
871
1113
  * providers that don't support them will just ignore fields they don't recognize,
872
1114
  * but not every SDKs TS types accept them, hence this being a structural type
@@ -1223,13 +1465,17 @@ declare class VernLLM {
1223
1465
  *
1224
1466
  * @param params System/user content plus per-call overrides. See `CallParams`.
1225
1467
  * @returns Without `tools` or `stream`: the parsed response, or raw
1226
- * string if `jsonMode` is false. With `tools`: a `CallWithToolsResult<T>`.
1227
- * With `stream: true` (statically): a `{ chunks, finalResult }`
1468
+ * string if `jsonMode` is false. With `tools`: a `CallWithToolsResult<T>`,
1469
+ * narrowed to `ContentResult<T>` when `toolChoice: 'none'` is set, since
1470
+ * the model is then structurally barred from returning a `tool_calls`
1471
+ * result. With `stream: true` (statically): a `{ chunks, finalResult }`
1228
1472
  * `StreamCallResult`, `finalResult` resolving to whichever of the above
1229
1473
  * shapes applies once the stream completes. See `StreamCallResult`.
1230
1474
  */
1475
+ call<T = unknown>(params: StreamEnabledCallParams<T> & ToolsDisabledCallParams<T>): Promise<StreamCallResult<ContentResult<T>>>;
1231
1476
  call<T = unknown>(params: StreamEnabledCallParams<T> & ToolEnabledCallParams<T>): Promise<StreamCallResult<CallWithToolsResult<T>>>;
1232
1477
  call<T = unknown>(params: StreamEnabledCallParams<T>): Promise<StreamCallResult<T>>;
1478
+ call<T = unknown>(params: ToolsDisabledCallParams<T>): Promise<ContentResult<T>>;
1233
1479
  call<T = unknown>(params: ToolEnabledCallParams<T>): Promise<CallWithToolsResult<T>>;
1234
1480
  call<T = unknown>(params: CallParams<T>): Promise<T>;
1235
1481
  /**
@@ -1281,26 +1527,41 @@ declare class VernLLM {
1281
1527
  cachedCall<T>(params: CachedToolCallParams<T>): Promise<CallWithToolsResult<T>>;
1282
1528
  cachedCall<T>(params: CachedCallParams<T>): Promise<T>;
1283
1529
  /**
1284
- * @param model With `circuitBreaker.isolateByModel` on, returns that
1285
- * model's own circuit state instead of the shared one. Ignored
1286
- * otherwise. Omit for the shared circuit (the default) or, under
1287
- * isolation, the state of calls that didn't resolve a model.
1288
- * @returns The current circuit breaker state (`'closed' | 'open' |
1289
- * 'half-open'`), or undefined if no circuit breaker was configured.
1290
- */
1291
- getCircuitState(model?: string): CircuitState | undefined;
1292
- /**
1293
- * @param model With `circuitBreaker.isolateByModel` on, returns each
1294
- * target's circuit state for that model instead of its shared state.
1295
- * Ignored otherwise. Omit for the shared circuit (the default) or, under
1296
- * isolation, the state of calls that didn't resolve a model.
1297
- * @returns The current circuit state for every target in declaration
1298
- * order, including the primary and all fallback targets. Each entry
1299
- * includes the target's provider name, chain index, whether it is a
1300
- * fallback, and its circuit state, or undefined if that target has no
1301
- * circuit breaker configured.
1530
+ * @param target.index Which target to read. Defaults to the primary.
1531
+ * @param target.model Which model bucket to read, if the target isolates by model.
1532
+ * @returns The breaker state, or `undefined` if that target has no breaker.
1533
+ * @throws {RangeError} If `target.index` names no target. Lets a real
1534
+ * target with no breaker (`undefined`) stay distinguishable from a
1535
+ * target that doesn't exist.
1536
+ */
1537
+ getCircuitState(target?: CircuitTarget): CircuitState | undefined;
1538
+ /**
1539
+ * @param model Which model bucket to read, for targets that isolate by model.
1540
+ * @returns Every target's state, in chain order.
1302
1541
  */
1303
1542
  getCircuitStates(model?: string): TargetCircuitState[];
1543
+ /**
1544
+ * Manually opens a target's breaker, e.g. to pull a provider out of
1545
+ * rotation ahead of known maintenance instead of waiting for it to fail.
1546
+ *
1547
+ * @param target.index Which target to open. Defaults to the primary.
1548
+ * @param target.model Which model bucket to open, if the target isolates by model.
1549
+ * @throws {RangeError} If `target.index` names no target.
1550
+ */
1551
+ openCircuit(target?: CircuitTarget): void;
1552
+ /**
1553
+ * Manually closes a target's breaker, e.g. once a provider is confirmed
1554
+ * healthy again without waiting out the cooldown.
1555
+ *
1556
+ * @param target.index Which target to close. Defaults to the primary.
1557
+ * @param target.model Which model bucket to close, if the target isolates by model.
1558
+ * @throws {RangeError} If `target.index` names no target.
1559
+ */
1560
+ closeCircuit(target?: CircuitTarget): void;
1561
+ /** Resolves a target index so every circuit-breaker method agrees on what counts as valid. */
1562
+ private resolveExecutor;
1563
+ /** Warns when `model` can't do anything on this target, so it's never silently ignored. */
1564
+ private warnIfModelUnsupported;
1304
1565
  }
1305
1566
 
1306
1567
  //#endregion
@@ -1358,9 +1619,10 @@ type SupportedImageMimeType = (typeof SUPPORTED_IMAGE_MIME_TYPES)[number];
1358
1619
  //#region src/adapters/internal/nativeStructuredOutput.d.ts
1359
1620
  /**
1360
1621
  * Validates an `ImageBlock.mimeType` against the shared supported set.
1361
- * Throws a non-retryable `LLMError('validation')`, since an unsupported
1362
- * mimeType is a permanent failure, retrying the same input can't fix it,
1363
- * the same way a schema-validation or JSON-parse failure isn't retried.
1622
+ * Throws a non-retryable `LLMError('invalid_params')`, since an unsupported
1623
+ * mimeType is a bug in the caller's own input, deterministic before any
1624
+ * request is built, the same class of failure as every other check in
1625
+ * `RequestBuilder`.
1364
1626
  */
1365
1627
 
1366
1628
  /**
@@ -2249,8 +2511,6 @@ declare const fromNvidiaNIM: typeof fromOpenAICompatible;
2249
2511
  declare const fromVercelAIGateway: typeof fromOpenAICompatible;
2250
2512
  /** Cloudflare Workers AI exposes an OpenAI-compatible endpoint */
2251
2513
  declare const fromCloudflareWorkersAI: typeof fromOpenAICompatible;
2252
- /** GitHub Models is OpenAI-compatible */
2253
- declare const fromGitHubModels: typeof fromOpenAICompatible;
2254
2514
  /** Nebius AI Studio is OpenAI-compatible */
2255
2515
  declare const fromNebius: typeof fromOpenAICompatible;
2256
2516
  /** SambaNova Cloud's API is OpenAI-compatible */
@@ -2277,8 +2537,6 @@ declare const fromSnowflakeCortex: typeof fromOpenAICompatible;
2277
2537
  declare const fromAnyscale: typeof fromOpenAICompatible;
2278
2538
  /** Lepton AI's inference API is OpenAI-compatible */
2279
2539
  declare const fromLepton: typeof fromOpenAICompatible;
2280
- /** kluster.ai's inference API is OpenAI-compatible */
2281
- declare const fromKlusterAI: typeof fromOpenAICompatible;
2282
2540
  /** Inference.net's API is OpenAI-compatible */
2283
2541
  declare const fromInferenceNet: typeof fromOpenAICompatible;
2284
2542
  /** Infermatic's API is OpenAI-compatible */
@@ -2291,5 +2549,5 @@ declare const from01AI: typeof fromOpenAICompatible;
2291
2549
  //#endregion
2292
2550
  //# sourceMappingURL=openaiCompatible.d.ts.map
2293
2551
 
2294
- export { AnthropicClient, BedrockConverseClient, CacheAdapter, CachedCallParams, CachedStreamCallParams, CachedStreamToolCallParams, CachedToolCallParams, CallMeta, CallParams, CallWithToolsResult, CircuitBreaker, CircuitBreakerOptions, CircuitState, ConsoleLogger, ContentBlock, ContentResult, ConversationTurn, FallbackAttempt, FallbackExhaustedError, FallbackOn, FallbackTarget, FetchAdapterConfig, GeminiClient, ImageBlock, InMemoryCacheAdapter, JsonSchemaSpec, LLMClient, LLMError, LLMErrorCode, LLMErrorType, Logger, NormalizedCacheAdapter, OnEvent, OnUsage, RateLimitAcquireResult, RateLimitOptions, RateLimitReason, RateLimiter, RefundUsage, ReserveUsage, SSE_PING, SchemaLike, StreamCallResult, StreamChunk, StreamEnabledCallParams, TargetCircuitState, TextBlock, TieredCacheAdapter, TokenUsage, ToolCall, ToolCallResult, ToolChoice, ToolDefinition, ToolEnabledCallParams, ToolIssue, ToolResult, VernLLM, VernLLMEvent, VernLLMOptions, WireMessage, WireRequest, WireStreamChunk, WireToolCall, WireToolChoice, defaultEstimateTokens, defaultFallbackOn, from01AI, fromAnthropic, fromAnyscale, fromAtlasCloud, fromBaseten, fromBedrock, fromCerebras, fromCloudflareWorkersAI, fromDeepInfra, fromDeepSeek, fromFeatherless, fromFetch, fromFireworks, fromFriendli, fromGemini, fromGitHubModels, fromGroq, fromHyperbolic, fromInferenceNet, fromInfermatic, fromKlusterAI, fromLMStudio, fromLambdaLabs, fromLepton, fromMiniMax, fromMistral, fromMoonshot, fromNebius, fromNovita, fromNvidiaNIM, fromOllama, fromOpenAI, fromOpenAICompatible, fromOpenRouter, fromParasail, fromPerplexity, fromSambaNova, fromSiliconFlow, fromSnowflakeCortex, fromStepFun, fromTogether, fromVLLM, fromVercelAIGateway, fromXAI, fromZhipu, isLLMError, isToolCallResult, parseSseStream };
2552
+ export { AnthropicClient, BedrockConverseClient, CacheAdapter, CachedCallParams, CachedStreamCallParams, CachedStreamToolCallParams, CachedToolCallParams, CallMeta, CallParams, CallWithToolsResult, CircuitBreaker, CircuitBreakerOptions, CircuitState, CircuitTarget, ConsoleLogger, ContentBlock, ContentResult, ConversationTurn, DuplicateToolNamesIssue, FallbackAttempt, FallbackExhaustedError, FallbackOn, FallbackTarget, FetchAdapterConfig, GeminiClient, HistoryToolResultIssue, ImageBlock, InMemoryCacheAdapter, JsonSchemaSpec, LLMClient, LLMError, LLMErrorCode, LLMErrorIssuesByCode, LLMErrorSnapshot, LLMErrorType, Logger, NormalizedCacheAdapter, OnEvent, OnUsage, RateLimitAcquireResult, RateLimitOptions, RateLimitReason, RateLimiter, RefundUsage, ReserveUsage, RetryAttempt, SSE_PING, SchemaLike, StreamCallResult, StreamChunk, StreamEnabledCallParams, TargetCircuitState, TextBlock, TieredCacheAdapter, TokenUsage, ToolCall, ToolCallResult, ToolChoice, ToolDefinition, ToolEnabledCallParams, ToolIssue, ToolResult, ToolsDisabledCallParams, UnknownToolChoiceIssue, UnsupportedCapabilityIssue, VernLLM, VernLLMEvent, VernLLMOptions, WireMessage, WireRequest, WireStreamChunk, WireToolCall, WireToolChoice, defaultEstimateTokens, defaultFallbackOn, 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, parseSseStream };
2295
2553
  //# sourceMappingURL=index.d.cts.map