@bivelio/savings-layer 0.4.21 → 0.4.24

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.ts CHANGED
@@ -11,6 +11,20 @@
11
11
  * Where the baseline being compared against comes from.
12
12
  *
13
13
  * - `measured` — reconstructed from this request's provider counter. Real money.
14
+ * - `reference` — MEASURED BY REFERENCE: the call was avoided (a repeat served
15
+ * without calling the provider) and a byte-identical request had
16
+ * already run through the layer untouched. The baseline is the
17
+ * provider's own counter for that request, valued at the
18
+ * provider's cached-input rate. Output is credited only under
19
+ * deterministic sampling, on a model with a list price in the
20
+ * catalog that does not reason and honors that setting, when
21
+ * the call that ran carried no hidden reasoning (zero reasoning
22
+ * tokens reported, or no reasoning block where the provider
23
+ * does not break them out), and then at 95% of its counter.
24
+ * With an output schema, only when the schema traveled exactly
25
+ * as sent, in strict mode, in the provider's native
26
+ * response-format parameter. Valued low on purpose. Enters the
27
+ * commission base.
14
28
  * - `estimated` — the call was AVOIDED: it never ran, so there is no provider
15
29
  * counter and the baseline comes from the local estimator. Still
16
30
  * the best figure available, but not measured.
@@ -24,7 +38,7 @@
24
38
  * exactly what a verify-don't-trust product cannot do — and adding `shadow` to
25
39
  * either would be making the savings up.
26
40
  */
27
- type CostBasis = "measured" | "estimated" | "unmeasured" | "shadow";
41
+ type CostBasis = "measured" | "reference" | "estimated" | "unmeasured" | "shadow";
28
42
  interface CostModel {
29
43
  /** Cost per 1K input tokens, in the caller's currency unit. */
30
44
  inputPer1k: number;
@@ -33,6 +47,61 @@ interface CostModel {
33
47
  /** Optional discounted rate for provider-cached (prefix) input tokens. */
34
48
  cachedInputPer1k?: number;
35
49
  }
50
+ /** Oldest provider counter a repeat can be valued against: one billing period. */
51
+ declare const REFERENCE_MAX_AGE_MS: number;
52
+ /** Share of the counted output credited to a repeat: only under deterministic sampling, on a catalog model that does not reason and honors it, when the call that ran carried no hidden reasoning. */
53
+ declare const REFERENCE_OUTPUT_SHARE = 0.95;
54
+ /** Input tokens deducted per piece the layer inserted in the call that ran: a message, the wrapper around a native output schema, or half of the empty parameters given to a tool that had none. */
55
+ declare const REFERENCE_MESSAGE_ALLOWANCE = 8;
56
+ /**
57
+ * Why an avoided call (a cache or coalesced hit) could not be valued by
58
+ * reference, and so stays `estimated`.
59
+ *
60
+ * - `absent` — the stored answer carries no provider counter.
61
+ * - `malformed` — it carries one, but not in a readable shape.
62
+ * - `witness` — the counter is not bound to the answer being served.
63
+ * - `model-mismatch` — the counter belongs to another model.
64
+ * - `expired` — the counter is older than one billing period.
65
+ * - `different-request` — the request that ran was not byte-identical to this one.
66
+ * - `quality` — the answer being served fails its output contract.
67
+ * - `no-cached-price` — no documented cached-input rate for this exact model:
68
+ * its own catalogue row (or a dated snapshot of it) or
69
+ * `costModels`, never a row inherited from a sibling id.
70
+ * - `no-credit` — nothing left to credit once the allowances are applied.
71
+ */
72
+ type ReferenceRejection = "absent" | "malformed" | "witness" | "model-mismatch" | "expired" | "different-request" | "quality" | "no-cached-price" | "no-credit";
73
+ /**
74
+ * The provider counter an avoided call is valued against, and every figure
75
+ * needed to recompute its baseline:
76
+ * `tierFactor * (creditedInputTokens / 1000 * cachedInputPer1k + creditedOutputTokens / 1000 * outputPer1k)`.
77
+ */
78
+ interface ReferenceCredit {
79
+ /** Id of the report of the call that ran (its report.requestId). */
80
+ requestId: string;
81
+ /** Epoch ms when that call's provider counter was read. */
82
+ at: number;
83
+ /** Input tokens the provider billed for that call. */
84
+ inputTokens: number;
85
+ /** Output tokens the provider billed for that call (the smaller of the first answer and its repair). */
86
+ outputTokens: number;
87
+ /** Input tokens credited to this repeat: inputTokens minus the allowance for what the layer inserted (messages, the native schema wrapper, empty tool parameters). */
88
+ creditedInputTokens: number;
89
+ /**
90
+ * Output tokens credited: a share of outputTokens capped by this request's
91
+ * output limit. 0 unless this request asked for deterministic sampling
92
+ * (`temperature: 0`), the model has a list price in the catalog, does not
93
+ * reason and honors that setting, and the call that ran carried no hidden
94
+ * reasoning (zero reasoning tokens reported, or no reasoning block where the
95
+ * provider does not break them out).
96
+ */
97
+ creditedOutputTokens: number;
98
+ /** Rate applied to every credited input token, per 1K: the provider's cached-input rate. */
99
+ cachedInputPer1k: number;
100
+ /** Rate applied to credited output tokens, per 1K. */
101
+ outputPer1k: number;
102
+ /** Fraction of the standard price applied: 1, or the discounted tier the call ran on or this request declared. */
103
+ tierFactor: number;
104
+ }
36
105
  declare class SavingsLedger {
37
106
  avoidedLlm: number;
38
107
  baselineLlm: number;
@@ -87,6 +156,13 @@ declare class SavingsLedger {
87
156
  * cost; work the layer does to fix its own output has no place in it.
88
157
  */
89
158
  repairInput: number;
159
+ /**
160
+ * Billed provider calls in this request that came back WITHOUT a complete
161
+ * usage counter (`usage.complete === false`). Any value above zero makes the
162
+ * request `unmeasured`: a figure built on a missing counter is not the
163
+ * provider's meter.
164
+ */
165
+ callsWithoutUsage: number;
90
166
  /**
91
167
  * INPUT of the schema-on-demand discovery probe. Held apart exactly like
92
168
  * {@link repairInput}, and for the same reason.
@@ -140,6 +216,12 @@ declare class SavingsLedger {
140
216
  compress(originalTokens: number, compressedTokens: number): void;
141
217
  /** Ledger 4: tokens still present but billed/processed as provider-cached. */
142
218
  providerCached(tokens: number): void;
219
+ /** Set when an avoided call is valued against the provider counter of an identical call that ran. */
220
+ reference: ReferenceCredit | null;
221
+ /** Set when an avoided cache or coalesced hit could not be valued by reference: why it stays estimated. */
222
+ referenceRejected: ReferenceRejection | null;
223
+ /** Record an avoided call valued by reference. */
224
+ avoidCallByReference(credit: ReferenceCredit): void;
143
225
  /** Record that a whole primary LLM call was avoided (cache / resolver hit). */
144
226
  avoidCall(baselineInput: number, baselineOutput: number): void;
145
227
  /**
@@ -182,7 +264,7 @@ declare class SavingsLedger {
182
264
  * catalogue stays out of the way.
183
265
  */
184
266
  /** When these prices were last checked against the providers' public rates. */
185
- declare const PRECIOS_AL_DIA = "2026-09-06";
267
+ declare const PRECIOS_AL_DIA = "2026-09-25";
186
268
  /**
187
269
  * Key = the model id as the provider publishes it, without a vendor prefix.
188
270
  * Lookup tries the exact id, then the id without its provider prefix, then the
@@ -205,7 +287,7 @@ declare function precioDeLista(model: string): CostModel | undefined;
205
287
  * on the customer's machine. `test/version-declarada.test.ts` ties this
206
288
  * constant to package.json so the two can never drift.
207
289
  */
208
- declare const BV_SALA_VERSION = "0.4.21";
290
+ declare const BV_SALA_VERSION = "0.4.24";
209
291
 
210
292
  /**
211
293
  * Environment-agnostic UUID generation.
@@ -217,6 +299,110 @@ declare const BV_SALA_VERSION = "0.4.21";
217
299
  */
218
300
  declare function randomId(): string;
219
301
 
302
+ /** How the layer served a request that is eligible for a verification check. */
303
+ type VerificationStratum =
304
+ /** Answered without calling the provider (cache, resolver, identical request in flight). */
305
+ "avoided_call"
306
+ /** Answered by a cheaper model than the one requested. */
307
+ | "cheaper_model"
308
+ /** Sent with parts removed. */
309
+ | "tokens_removed"
310
+ /** Sent re-encoded with fewer tokens. */
311
+ | "tokens_compressed"
312
+ /** Sent without removing or re-encoding anything. */
313
+ | "unchanged";
314
+ /** Why a request is never checked. */
315
+ type VerificationExclusion = "fail_open" | "truncated" | "incomplete" | "contract_failed" | "unmeasured" | "no_price" | "over_cap" | "opted_out" | "retrieval";
316
+ /** How a check ended. */
317
+ type VerificationOutcome = "paired" | "check_failed" | "check_unmeasured" | "abandoned" | "dropped";
318
+ /**
319
+ * Opt-in A/B verification on your own traffic. Off unless `sampleRate` is above 0.
320
+ *
321
+ * For the sampled share of eligible requests, after the answer has been returned,
322
+ * the layer sends the same request once more to your provider exactly as it would
323
+ * go without the layer (the requested model, all your tools, your data inline, no
324
+ * cache marks) and discards that second answer. Each pair (what the unoptimized
325
+ * copy cost and what the optimized request cost, both from your provider's usage
326
+ * counters) is reported to your dashboard and to `onCheck`.
327
+ *
328
+ * Every copy is a real provider call that you pay for: roughly `sampleRate` times
329
+ * what the eligible traffic would cost without the layer. Pairs are informational:
330
+ * they never enter measured savings, your statement or any commission.
331
+ */
332
+ interface VerificationSettings {
333
+ /** Share of eligible requests to check, from 0 to 1. Any other value turns verification off. */
334
+ sampleRate: number;
335
+ /** Skip requests whose estimated unoptimized cost, in USD, is above this. An invalid value turns verification off. */
336
+ maxCheckCostUsd?: number;
337
+ /** Called once per sampled eligible request, when its check is settled. Errors thrown here are ignored. */
338
+ onCheck?: (check: VerificationCheck) => void;
339
+ /**
340
+ * Keeps a serverless runtime alive until a check is settled and reported, for
341
+ * example your platform's `waitUntil`. Receives one promise per copy actually
342
+ * sent (a check recorded as `dropped` is never sent and passes none); it never rejects.
343
+ */
344
+ waitUntil?: (promise: Promise<void>) => void;
345
+ }
346
+ /** Token counts and cost of one side of a pair, from the provider's usage counters. */
347
+ interface VerificationSide {
348
+ cost: number;
349
+ inputTokens: number;
350
+ cachedInputTokens: number;
351
+ outputTokens: number;
352
+ }
353
+ /** One settled verification check. */
354
+ interface VerificationCheck {
355
+ /** Id of the served request (its report.requestId). */
356
+ requestId: string;
357
+ /** Epoch ms when the served request was decided. */
358
+ at: number;
359
+ stratum: VerificationStratum;
360
+ /**
361
+ * - `paired`: both sides metered; `naive` is present.
362
+ * - `check_failed`: the provider returned an error for the copy, or it timed out.
363
+ * - `check_unmeasured`: the provider returned no usage counter for the copy.
364
+ * - `abandoned`: the copy was still running when `close()` was called.
365
+ * - `dropped`: not sent, because too many checks were already running.
366
+ */
367
+ outcome: VerificationOutcome;
368
+ requestedModel: string;
369
+ servedModel: string;
370
+ cacheResult: CacheOutcome;
371
+ /** The served request, as its report measured it. Zero on an avoided call. */
372
+ optimized: VerificationSide;
373
+ /**
374
+ * The unoptimized copy. Present only when `outcome` is `paired`. The dashboard
375
+ * compares a pair only when the copy finished the way a served request must to
376
+ * be checked: `finishReason` `stop`, `tool_calls` or absent, and a contract that
377
+ * was not `missed`. Other pairs are counted and exported, but left out of every figure.
378
+ */
379
+ naive?: VerificationSide & {
380
+ finishReason?: string;
381
+ /** Whether the copy met your output contract: `none` without a contract or on a tool-call turn. */
382
+ contract: "met" | "missed" | "none";
383
+ };
384
+ /** Rates used to price the copy, in USD per 1K tokens. */
385
+ prices: {
386
+ inputPer1k: number;
387
+ cachedInputPer1k?: number;
388
+ outputPer1k: number;
389
+ };
390
+ }
391
+ /**
392
+ * Whether this request was drawn for verification sampling, and how it counts.
393
+ * Present only when `verification` is enabled on the layer and its pairs have
394
+ * somewhere to go (your dashboard or `onCheck`). Absent after `close()`, and on
395
+ * the rare internal error of the sampler, which never affects the request itself.
396
+ */
397
+ interface VerificationReport {
398
+ /** Drawn for a check. A copy is sent only when the request is also eligible. */
399
+ sampled: boolean;
400
+ /** How the layer served this request, when it is eligible for a check. */
401
+ stratum?: VerificationStratum;
402
+ /** Why this request is never checked. Present exactly when `stratum` is absent. */
403
+ excluded?: VerificationExclusion;
404
+ }
405
+
220
406
  /**
221
407
  * Core contracts for BV-SALA (bivelio-savings-layer).
222
408
  *
@@ -469,6 +655,11 @@ interface SavingsToggles {
469
655
  * tier the request is sent unchanged and the report says so.
470
656
  */
471
657
  deferrable?: boolean;
658
+ /**
659
+ * Set to `false` to keep this request out of verification sampling: it is
660
+ * counted as excluded and never sent twice. No effect when verification is off.
661
+ */
662
+ verification?: boolean;
472
663
  }
473
664
  /**
474
665
  * What asking for a cheaper processing tier actually got you on this request.
@@ -740,6 +931,79 @@ interface SavingsResult<T = unknown> {
740
931
  finishReason?: string;
741
932
  report: SavingsReport;
742
933
  }
934
+ /** One event of a streamed answer. */
935
+ type SavingsStreamEvent<T = unknown> =
936
+ /** A piece of the answer text, as the provider wrote it. Never empty. */
937
+ {
938
+ type: "text";
939
+ delta: string;
940
+ }
941
+ /** One complete tool call the model requested, in the order of `result.toolCalls`. */
942
+ | {
943
+ type: "tool_call";
944
+ call: ToolCallRequest;
945
+ }
946
+ /** Always the last event, exactly once. `result` is the same object `SavingsStream.result` resolves to. */
947
+ | {
948
+ type: "done";
949
+ result: SavingsResult<T>;
950
+ };
951
+ /** Options for `generateStream`. */
952
+ interface SavingsStreamOptions {
953
+ /** Aborting it has the same effect as calling `abort()` on the stream. */
954
+ signal?: AbortSignal;
955
+ }
956
+ /**
957
+ * The handle returned by `generateStream`. Iterate it (once) for the events, or
958
+ * await `result` for the final SavingsResult; both work on their own, because
959
+ * the request starts as soon as the handle is created.
960
+ *
961
+ * Invariants: the concatenation of every `text` delta equals `result.text`; the
962
+ * `tool_call` events, in order, equal `result.toolCalls`; `done` is the last
963
+ * event and `result` is already resolved when it arrives.
964
+ */
965
+ interface SavingsStream<T = unknown> extends AsyncIterable<SavingsStreamEvent<T>> {
966
+ /**
967
+ * Resolves with the final SavingsResult (the same object the `done` event
968
+ * carries). Rejects with the provider or license error that ended the stream,
969
+ * or with StreamAbortedError when the stream was aborted before the provider
970
+ * finished its answer.
971
+ */
972
+ readonly result: Promise<SavingsResult<T>>;
973
+ /**
974
+ * Stops the stream. Iteration ends at once, without an error. While the
975
+ * provider is still writing a live answer, its request is cancelled: `result`
976
+ * rejects with StreamAbortedError, nothing is cached and nothing is reported.
977
+ * Once the provider's answer is complete, or when it is produced in one piece
978
+ * (output contract, deferrable request), nothing is cancelled: `result`
979
+ * resolves with the full report and it is recorded like any other request.
980
+ * Breaking out of a for-await loop calls abort(). No-op after `done`.
981
+ */
982
+ abort(reason?: unknown): void;
983
+ }
984
+ /** How a streamed request reached the caller. Present only on results of `generateStream`. */
985
+ interface StreamingReport {
986
+ /**
987
+ * "live": the text was handed over as the provider produced it.
988
+ * "whole": the complete answer arrived as one `text` event.
989
+ */
990
+ delivery: "live" | "whole";
991
+ /**
992
+ * Why the answer arrived whole. Absent when `delivery` is "live".
993
+ * - "output-contract": the request carried `response.schema`; it was validated first.
994
+ * - "deferrable": the request was declared `deferrable`.
995
+ * - "provider-without-streaming": the provider has no `generateStream`.
996
+ * - "shortcut": answered without a provider call (cache, deterministic resolver, or an identical request in flight).
997
+ * - "fail-open": an internal error sent the request down the direct fallback path.
998
+ */
999
+ wholeReason?: "output-contract" | "deferrable" | "provider-without-streaming" | "shortcut" | "fail-open";
1000
+ /**
1001
+ * Milliseconds from the `generateStream` call to the first `text` or
1002
+ * `tool_call` event handed to the caller. Present only when `delivery` is
1003
+ * "live" and such an event exists.
1004
+ */
1005
+ firstEventMs?: number;
1006
+ }
743
1007
  type CacheOutcome = "miss" | "exact" | "semantic" | "singleflight" | "deterministic";
744
1008
  /**
745
1009
  * The quality claims this layer is able to DEMONSTRATE for the answer it served.
@@ -837,6 +1101,10 @@ interface SavingsReport {
837
1101
  result: CacheOutcome;
838
1102
  confidence?: number;
839
1103
  ageMs?: number;
1104
+ /** Present when this avoided call is valued by reference; see ReferenceCredit. */
1105
+ reference?: ReferenceCredit;
1106
+ /** Present on an exact-cache or coalesced hit that stays estimated: why no reference applied. Semantic-cache hits never carry it. */
1107
+ referenceRejected?: ReferenceRejection;
840
1108
  };
841
1109
  routing: {
842
1110
  executionClass: ExecutionClass;
@@ -891,6 +1159,13 @@ interface SavingsReport {
891
1159
  * `savings.deferrable` is `true`, including when it got you nothing.
892
1160
  */
893
1161
  deferred?: DeferredExecutionReport;
1162
+ /** How a streamed request was delivered. Present only on results of `generateStream`. */
1163
+ streaming?: StreamingReport;
1164
+ /**
1165
+ * Whether this request was drawn for verification sampling, and how it counts.
1166
+ * Present only when `verification` is enabled on the layer.
1167
+ */
1168
+ verification?: VerificationReport;
894
1169
  quality: {
895
1170
  taskPassed?: boolean;
896
1171
  /**
@@ -914,6 +1189,22 @@ interface SavingsReport {
914
1189
  *
915
1190
  * - `measured` — reconstructed from THIS request's provider counter. Real
916
1191
  * money, checkable against the provider invoice.
1192
+ * - `reference` — MEASURED BY REFERENCE: the call was avoided (a repeat
1193
+ * served without calling the provider) and a byte-identical
1194
+ * request had already run through the layer untouched. The
1195
+ * baseline is the provider's own counter for that request,
1196
+ * valued at the provider's cached-input rate. Output is
1197
+ * credited only under deterministic sampling, on a model
1198
+ * with a list price in the catalog that does not reason and
1199
+ * honors that setting, when the call that ran carried no
1200
+ * hidden reasoning (zero reasoning tokens reported, or no
1201
+ * reasoning block where the provider does not break them
1202
+ * out), and then at 95% of its counter. With an output
1203
+ * schema, only when the schema traveled exactly as sent, in
1204
+ * strict mode, in the provider's native response-format
1205
+ * parameter. Valued low on purpose.
1206
+ * Enters the commission base; `cache.reference` carries the
1207
+ * arithmetic and the id of the request that ran.
917
1208
  * - `estimated` — the call was AVOIDED, so there never was a provider
918
1209
  * counter and the baseline comes from the local estimator.
919
1210
  * The best figure available, but not measured.
@@ -1361,7 +1652,12 @@ interface CachedAnswer<T = unknown> {
1361
1652
  rendered?: string;
1362
1653
  /** Epoch ms when stored; used to compute age and honor TTL. */
1363
1654
  storedAtMs: number;
1364
- /** Optional metadata carried alongside the answer. */
1655
+ /**
1656
+ * Optional metadata carried alongside the answer.
1657
+ *
1658
+ * `meta.reference` carries the provider counter of the call that produced this
1659
+ * answer, when that call ran untouched; readers that do not know it ignore it.
1660
+ */
1365
1661
  meta?: Record<string, unknown>;
1366
1662
  }
1367
1663
  interface ExactCache {
@@ -1409,6 +1705,11 @@ declare class Singleflight {
1409
1705
  value: T;
1410
1706
  coalesced: boolean;
1411
1707
  }>;
1708
+ /**
1709
+ * The in-flight execution under `key`, if one is running, joined as a
1710
+ * follower; `undefined` when nothing is in flight. Never starts one.
1711
+ */
1712
+ join<T>(key: string): Promise<T> | undefined;
1412
1713
  get activeKeys(): number;
1413
1714
  }
1414
1715
 
@@ -2259,6 +2560,27 @@ interface ProviderUsage {
2259
2560
  * bill INCREASE as a saving.
2260
2561
  */
2261
2562
  cacheWriteInputTokens?: number;
2563
+ /**
2564
+ * Output tokens the provider billed for hidden reasoning, already included in
2565
+ * `outputTokens` (OpenAI `completion_tokens_details.reasoning_tokens`).
2566
+ *
2567
+ * `0` states that the answer carried no hidden reasoning. Where the provider
2568
+ * does not break reasoning out (Anthropic's Messages API), the built-in
2569
+ * adapter reports `0` only when the answer has no `thinking` or
2570
+ * `redacted_thinking` block, and leaves this absent otherwise. Absent means
2571
+ * nobody said, which is NOT the same as 0: a repeat served by reference only
2572
+ * credits output when the call that ran reported exactly 0, because reasoning
2573
+ * tokens are neither visible nor deterministic. A custom provider should set
2574
+ * it only from its provider's own counter or an equally direct signal.
2575
+ */
2576
+ reasoningTokens?: number;
2577
+ /**
2578
+ * False when the provider did not report both counters for this call (for a
2579
+ * stream: its final usage event never arrived), so the adapter filled the
2580
+ * missing ones with 0. Absent means complete. A request with any such call is
2581
+ * reported as `unmeasured`.
2582
+ */
2583
+ complete?: boolean;
2262
2584
  }
2263
2585
  interface ProviderResponse {
2264
2586
  text: string;
@@ -2320,9 +2642,32 @@ interface ProviderResponse {
2320
2642
  requestedServiceTier?: string;
2321
2643
  raw?: unknown;
2322
2644
  }
2645
+ /** One event of a provider-level stream. */
2646
+ type ProviderStreamEvent = {
2647
+ type: "text";
2648
+ delta: string;
2649
+ } | {
2650
+ type: "done";
2651
+ response: ProviderResponse;
2652
+ };
2653
+ /** Options for {@link Provider.generate} and {@link Provider.generateStream}. */
2654
+ interface ProviderStreamOptions {
2655
+ /** Cancels the HTTP request. */
2656
+ signal?: AbortSignal;
2657
+ }
2323
2658
  interface Provider {
2324
2659
  readonly id: string;
2325
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2660
+ generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
2661
+ /**
2662
+ * Optional streaming variant of generate(). It must yield `text` events in
2663
+ * order and end with exactly one `done` whose `response` is what generate()
2664
+ * would have returned for the same answer: `response.text` equals the
2665
+ * concatenated deltas, and tool calls travel only in `response.toolCalls`.
2666
+ * When the provider's final usage event is missing, both counters are 0 and
2667
+ * `usage.complete` is false. A body that ends before the provider signalled
2668
+ * completion must throw, never yield `done`.
2669
+ */
2670
+ generateStream?(req: ProviderRequest, options?: ProviderStreamOptions): AsyncIterable<ProviderStreamEvent>;
2326
2671
  }
2327
2672
 
2328
2673
  /**
@@ -2357,12 +2702,19 @@ interface MockProviderOptions {
2357
2702
  data?: unknown;
2358
2703
  /** Simulated provider-cached input tokens (ledger 4). */
2359
2704
  cachedInputTokens?: number;
2705
+ /** Characters per text event in generateStream (default 8). */
2706
+ streamChunkChars?: number;
2360
2707
  }
2361
2708
  declare class MockProvider implements Provider {
2362
2709
  readonly id = "mock";
2363
2710
  private readonly options;
2364
2711
  constructor(options?: MockProviderOptions);
2365
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2712
+ generate(req: ProviderRequest, _options?: ProviderStreamOptions): Promise<ProviderResponse>;
2713
+ /**
2714
+ * Streaming variant of generate(): the same answer, cut into text events of
2715
+ * `streamChunkChars` characters, then one `done` with the generate() response.
2716
+ */
2717
+ generateStream(req: ProviderRequest): AsyncGenerator<ProviderStreamEvent, void, undefined>;
2366
2718
  }
2367
2719
  declare function createMockProvider(options?: MockProviderOptions): Provider;
2368
2720
 
@@ -2387,7 +2739,18 @@ declare function createMockProvider(options?: MockProviderOptions): Provider;
2387
2739
  interface OpenAICompatibleOptions {
2388
2740
  baseUrl: string;
2389
2741
  apiKey?: string;
2390
- /** Strip the "provider/" prefix from model ids before sending. */
2742
+ /**
2743
+ * Strip the `provider/` prefix from model ids before sending (`openai/gpt-4o-mini`
2744
+ * → `gpt-4o-mini`).
2745
+ *
2746
+ * Default (undefined): strip when `baseUrl` is OpenAI's own API or an Azure
2747
+ * OpenAI endpoint, keep otherwise. OpenAI rejects a prefixed id with a 404
2748
+ * ("model does not exist"), so the qualified ids the layer uses everywhere
2749
+ * else — pricing, cache keys, the report — would break the very first call
2750
+ * that reaches the provider. A gateway or a self-hosted server with a
2751
+ * namespaced model name usually WANTS the prefix, so it is kept there. Set `true`/`false` to decide explicitly; an explicit value
2752
+ * always wins.
2753
+ */
2391
2754
  stripProviderPrefix?: boolean;
2392
2755
  /** Extra headers (e.g. for a gateway). */
2393
2756
  headers?: Record<string, string>;
@@ -2417,15 +2780,32 @@ interface OpenAICompatibleOptions {
2417
2780
  * es una restricción de OpenAI que JSON Schema no tiene.
2418
2781
  */
2419
2782
  declare function isStrictCompatible(schema: unknown): boolean;
2783
+ /**
2784
+ * Decides the id that travels on the wire. Explicit option wins; otherwise the
2785
+ * endpoint decides: OpenAI/Azure get the bare id, everything else keeps what
2786
+ * the caller wrote. Exported for the pipeline-level test that pins the README
2787
+ * snippet; not part of the public API.
2788
+ */
2789
+ declare function quitarPrefijoDeProveedor(model: string, explicito: boolean | undefined, baseUrl: string): string;
2420
2790
  declare class OpenAICompatibleProvider implements Provider {
2421
2791
  readonly id: string;
2422
2792
  private readonly opts;
2423
2793
  private readonly fetchImpl;
2424
2794
  /** Models of this endpoint already known to reject `response_format`. */
2425
2795
  private readonly sinResponseFormat;
2796
+ private streamOptionsRejected;
2426
2797
  constructor(opts: OpenAICompatibleOptions);
2427
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2428
- /** Build the request body and post it. `nativo` selects the `response_format` path. */
2798
+ generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
2799
+ /**
2800
+ * Streaming variant of generate() over Chat Completions. It always asks for
2801
+ * the final usage chunk (`stream_options.include_usage`); an endpoint that
2802
+ * rejects that option is retried once without it and remembered, and its
2803
+ * streamed answers then report `usage.complete: false`. With `requireUsage`,
2804
+ * that rejection throws instead. Requests with an output contract or a
2805
+ * deferrable tier are not streamed: they are served by generate() and yielded
2806
+ * as one text event.
2807
+ */
2808
+ generateStream(req: ProviderRequest, options?: ProviderStreamOptions): AsyncGenerator<ProviderStreamEvent, void, undefined>;
2429
2809
  private enviar;
2430
2810
  /** Translate a 2xx endpoint response into the provider-neutral shape. */
2431
2811
  private leer;
@@ -2448,6 +2828,14 @@ interface AnthropicOptions {
2448
2828
  apiKey?: string;
2449
2829
  /** Defaults to the public API. Override for a gateway or a proxy. */
2450
2830
  baseUrl?: string;
2831
+ /**
2832
+ * Strip the `provider/` prefix from model ids before sending
2833
+ * (`anthropic/claude-sonnet-4-5` → `claude-sonnet-4-5`). Default (undefined):
2834
+ * strip when talking to `api.anthropic.com`, which rejects a prefixed id;
2835
+ * keep for a gateway or proxy, which usually routes on it. An explicit value
2836
+ * always wins.
2837
+ */
2838
+ stripProviderPrefix?: boolean;
2451
2839
  /** Wire version pinned in the `anthropic-version` header. */
2452
2840
  apiVersion?: string;
2453
2841
  /** Extra headers (e.g. for a gateway). */
@@ -2512,10 +2900,22 @@ declare class AnthropicProvider implements Provider {
2512
2900
  private readonly opts;
2513
2901
  private readonly fetchImpl;
2514
2902
  constructor(opts?: AnthropicOptions);
2515
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2903
+ /** The id Anthropic's API sees: bare at `api.anthropic.com`, as written elsewhere. */
2904
+ private modeloEnElCable;
2905
+ generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
2906
+ /**
2907
+ * Streaming variant of generate() over the Messages API. The final usage is
2908
+ * read from the closing `message_delta` event (the output counter in
2909
+ * `message_start` is provisional); a stream without it reports
2910
+ * `usage.complete: false`, and with `requireUsage` it throws instead. A body
2911
+ * that ends before the provider signalled completion throws. Requests with an
2912
+ * output contract or declared deferrable are served by generate() and yielded
2913
+ * as one text event.
2914
+ */
2915
+ generateStream(req: ProviderRequest, options?: ProviderStreamOptions): AsyncGenerator<ProviderStreamEvent, void, undefined>;
2916
+ private enviar;
2516
2917
  private construirCuerpo;
2517
2918
  private marcarCache;
2518
- /** Traduce una respuesta 2xx a la forma neutra del layer. */
2519
2919
  private leer;
2520
2920
  }
2521
2921
 
@@ -3193,7 +3593,7 @@ interface SavingsLayerConfig {
3193
3593
  /** Reject a signed list whose `asOf` is more than this far in the future, seconds (default 300). */
3194
3594
  maxSkewSeconds?: number;
3195
3595
  /**
3196
- * ONLINE staleness ceiling, seconds (default ~6h). If the SDK goes this long
3596
+ * ONLINE staleness ceiling, seconds (default 72h, `DEFAULT_MAX_STALENESS_SECONDS`). If the SDK goes this long
3197
3597
  * without a CONFIRMED authorized heartbeat (a 304 or a verified list), the LIVE
3198
3598
  * gate stops granting — closing the fail-open window where blocking egress keeps
3199
3599
  * a revoked license alive until natural expiry. Must exceed the poll cadence
@@ -3266,6 +3666,8 @@ interface SavingsLayerConfig {
3266
3666
  /** Optional org hint; the server derives the authoritative org from the key. */
3267
3667
  orgId?: string;
3268
3668
  };
3669
+ /** Opt-in A/B verification on your own traffic. Off by default. See VerificationSettings. */
3670
+ verification?: VerificationSettings;
3269
3671
  /**
3270
3672
  * Development-only escape hatch to run ungated, honored ONLY when building from
3271
3673
  * source (local dev, CI, benchmarks) — also via the `BVSALA_DEV_OPEN=1`
@@ -3335,6 +3737,16 @@ interface SavingsLayerConfig {
3335
3737
  * emitter and the revocation poller alike.
3336
3738
  */
3337
3739
  silenceWarnings?: boolean;
3740
+ /**
3741
+ * Load the exact OpenAI tokenizer (`gpt-tokenizer`, optional peer) into the
3742
+ * default registry when it is installed. ON by default: without it, every
3743
+ * saving where the layer removed something is reported as `estimated` and
3744
+ * never billed, and the SDK warns once. Set to `false` to keep the built-in
3745
+ * heuristic even when the package is present (tests calibrated on the
3746
+ * heuristic, or a process that must not spend the encoding's memory). Ignored
3747
+ * when you pass your own `tokenizer` registry: you decide what it contains.
3748
+ */
3749
+ exactTokenizer?: boolean;
3338
3750
  /** Injectable clock for deterministic tests. */
3339
3751
  now?: () => number;
3340
3752
  /** Injectable id generator for deterministic tests. */
@@ -3417,11 +3829,33 @@ declare const DEFAULT_MAX_STALENESS_SECONDS: number;
3417
3829
  /** The public handle returned by {@link createSavingsLayer}. */
3418
3830
  interface SavingsLayer {
3419
3831
  generate<T = unknown>(request: SavingsRequest): Promise<SavingsResult<T>>;
3832
+ /**
3833
+ * Same request as generate(), delivered as a stream of events.
3834
+ *
3835
+ * Iterate the returned object for `text` deltas as the provider writes them,
3836
+ * then one `tool_call` event per requested tool call, then exactly one `done`
3837
+ * event carrying the same SavingsResult that `result` resolves to. The report
3838
+ * follows the same basis rules as generate(): the provider's final usage is
3839
+ * read at the end of the stream, and a stream that ends without it is
3840
+ * reported as `unmeasured`.
3841
+ *
3842
+ * Requests with an output contract (`response.schema`) or declared
3843
+ * `deferrable`, and providers without streaming support, are produced exactly
3844
+ * as generate() would and delivered in one piece; `report.streaming` says how
3845
+ * the answer was delivered and why.
3846
+ *
3847
+ * Work starts immediately, so awaiting `result` without iterating is valid.
3848
+ * Concurrent identical streamed requests each call the provider; a streamed
3849
+ * request can still reuse an identical generate() call already in flight.
3850
+ * Await `result` before calling close(), or its usage record may be lost.
3851
+ */
3852
+ generateStream<T = unknown>(request: SavingsRequest, options?: SavingsStreamOptions): SavingsStream<T>;
3420
3853
  /**
3421
3854
  * Stop the background revocation poller (when the online call-home was enabled
3422
- * via `licenseServerUrl`) and abort any in-flight request. Always safe to call —
3423
- * a no-op when there is no poller. Call it when shutting down a long-running
3424
- * process so the interval timer and any pending fetch are released.
3855
+ * via `licenseServerUrl`) and abort any in-flight request, and settle every
3856
+ * verification check still in flight (recorded as abandoned). Always safe to
3857
+ * call — a no-op when there is no poller. Call it when shutting down a
3858
+ * long-running process so the interval timer and any pending fetch are released.
3425
3859
  */
3426
3860
  close(): void;
3427
3861
  }
@@ -3447,4 +3881,17 @@ interface SavingsLayer {
3447
3881
  */
3448
3882
  declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"> & Partial<Pick<SavingsLayerConfig, "tokenizer">>): SavingsLayer;
3449
3883
 
3450
- export { type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactRowsDecodeOptions, type CompactRowsOptions, type CompactVerification, type CompressionEconomics, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, DEFAULT_MAX_STALENESS_SECONDS, type DeferredExecutionReport, type DeterministicResolver, EMBEDDED_PUBLIC_KEY, type EmbeddingFn, type EvidenceDoc, type ExactCache, ExactTokenizer, type ExecutionClass, ExecutionRouter, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, InMemoryToolSetFreezer, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyShape, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type ModelCascadeMode, type ModelCascadeReport, type ModelCascadeSettings, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, PRECIOS_AL_DIA, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderUsage, QUALITY_STATUSES, type QualityStatus, type RequestContext, type Reranker, type ResolvedDeps, type ResolvedGate, type ResolvedLicense, ResolverRegistry, type ResolverResult, type RetrievalOptions, type RetrievalOutcome, type RetrievalPlan, type Retriever, type RevocationListBody, type RevocationListVerification, type RevocationPollEvent, RevocationPoller, type RevocationPollerOptions, RevocationSnapshot, type RouteDecision, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsReport, type SavingsRequest, type SavingsResult, type SavingsToggles, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, type ToolSetFreezer, TraceCollector, type TraceEvent, UMBRAL_LOCAL, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, compresionFiel, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, embebedorLocal, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, licenseKeyShape, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeLicenseKey, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };
3884
+ /**
3885
+ * Rejection of `SavingsStream.result` when the stream was aborted before the
3886
+ * provider finished its answer. Nothing was cached and no usage was recorded.
3887
+ */
3888
+ declare class StreamAbortedError extends Error {
3889
+ /**
3890
+ * True when a request had already been sent to the provider: the provider may
3891
+ * bill the tokens it generated before the cancel, and that cost is in no report.
3892
+ */
3893
+ readonly requestSent: boolean;
3894
+ constructor(requestSent: boolean);
3895
+ }
3896
+
3897
+ export { type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactRowsDecodeOptions, type CompactRowsOptions, type CompactVerification, type CompressionEconomics, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, DEFAULT_MAX_STALENESS_SECONDS, type DeferredExecutionReport, type DeterministicResolver, EMBEDDED_PUBLIC_KEY, type EmbeddingFn, type EvidenceDoc, type ExactCache, ExactTokenizer, type ExecutionClass, ExecutionRouter, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, InMemoryToolSetFreezer, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyShape, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type ModelCascadeMode, type ModelCascadeReport, type ModelCascadeSettings, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, PRECIOS_AL_DIA, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderStreamEvent, type ProviderStreamOptions, type ProviderUsage, QUALITY_STATUSES, type QualityStatus, REFERENCE_MAX_AGE_MS, REFERENCE_MESSAGE_ALLOWANCE, REFERENCE_OUTPUT_SHARE, type ReferenceCredit, type ReferenceRejection, type RequestContext, type Reranker, type ResolvedDeps, type ResolvedGate, type ResolvedLicense, ResolverRegistry, type ResolverResult, type RetrievalOptions, type RetrievalOutcome, type RetrievalPlan, type Retriever, type RevocationListBody, type RevocationListVerification, type RevocationPollEvent, RevocationPoller, type RevocationPollerOptions, RevocationSnapshot, type RouteDecision, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsReport, type SavingsRequest, type SavingsResult, type SavingsStream, type SavingsStreamEvent, type SavingsStreamOptions, type SavingsToggles, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, StreamAbortedError, type StreamingReport, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, type ToolSetFreezer, TraceCollector, type TraceEvent, UMBRAL_LOCAL, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, type VerificationCheck, type VerificationExclusion, type VerificationOutcome, type VerificationReport, type VerificationSettings, type VerificationSide, type VerificationStratum, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, compresionFiel, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, embebedorLocal, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, licenseKeyShape, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeLicenseKey, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, quitarPrefijoDeProveedor, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };