@bivelio/savings-layer 0.2.2 → 0.3.1

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
@@ -1,13 +1,3 @@
1
- /**
2
- * Environment-agnostic UUID generation.
3
- * ---------------------------------------------------------------------------
4
- * Trace IDs only need to be unique, not cryptographically unpredictable. We
5
- * prefer the platform `crypto.randomUUID` (present in modern Node, browsers,
6
- * Deno and edge runtimes) and fall back to a v4-shaped id when it is absent, so
7
- * BV-SALA runs everywhere without a `node:crypto` import that breaks bundlers.
8
- */
9
- declare function randomId(): string;
10
-
11
1
  /**
12
2
  * The four-ledger savings accounting.
13
3
  *
@@ -75,6 +65,39 @@ declare class SavingsLedger {
75
65
  * cost; work the layer does to fix its own output has no place in it.
76
66
  */
77
67
  repairInput: number;
68
+ /**
69
+ * INPUT of the schema-on-demand discovery probe. Held apart exactly like
70
+ * {@link repairInput}, and for the same reason.
71
+ *
72
+ * The layer ADDS this call; the naive path would never make it. Letting its
73
+ * tokens into the reconstructed baseline would make the probe pay for itself,
74
+ * and a request that cost MORE than going without the layer would publish a
75
+ * positive saving. It still counts in full towards `actualInput` — that is
76
+ * money genuinely spent — it just cannot inflate what that is compared against.
77
+ */
78
+ discoveryInput: number;
79
+ /** OUTPUT of the discovery probe. Our cost, kept out of the baseline. */
80
+ discoveryOutput: number;
81
+ /**
82
+ * Already-priced cost of a call the model cascade made to a cheaper model
83
+ * whose answer was NOT served. Added to `actual`, never to the baseline.
84
+ */
85
+ extraCostUsd: number;
86
+ /** Input tokens of that call. Published by the pipeline, not priced here. */
87
+ cascadeInput: number;
88
+ /** Output tokens of that call. Published by the pipeline, not priced here. */
89
+ cascadeOutput: number;
90
+ /**
91
+ * Record a call to the cheap model that did not produce the served answer:
92
+ * the one discarded on an escalation, or a shadow-mode sample.
93
+ *
94
+ * The caller raises `actualLlm`, because the call happened and the report owes
95
+ * the reader that count.
96
+ */
97
+ cascadeAttempt(usage: {
98
+ inputTokens: number;
99
+ outputTokens: number;
100
+ }, cheapPrice: CostModel): void;
78
101
  /**
79
102
  * Lo que de verdad hizo la compresión, para publicarlo en
80
103
  * `SavingsReport.compression`. El campo estaba declarado en el contrato y no lo
@@ -113,6 +136,53 @@ declare class SavingsLedger {
113
136
  };
114
137
  }
115
138
 
139
+ /**
140
+ * LIST PRICES PER MODEL.
141
+ *
142
+ * The layer reports savings in money, and money needs a price. This is the
143
+ * built-in catalogue of published list prices, so the dollar figures mean
144
+ * something without any configuration.
145
+ *
146
+ * ⚠️ A WRONG PRICE HERE IS WORSE THAN NO CATALOGUE. A model that is ABSENT falls
147
+ * back to the illustrative default AND warns, so the customer knows that column
148
+ * is not their invoice. A model that is PRESENT with the wrong price silences
149
+ * that warning and hands over a confident, wrong number. So only models whose
150
+ * published price is known with certainty belong here, and {@link PRECIOS_AL_DIA}
151
+ * records when they were last checked.
152
+ *
153
+ * Prices are USD per 1,000 tokens — the unit of `CostModel` — even though
154
+ * providers publish per million: 1M to 1K is a division by a thousand.
155
+ *
156
+ * `costModels` on the layer config ALWAYS wins over this. A customer with volume
157
+ * discounts, negotiated rates or a different region sets their own and this
158
+ * catalogue stays out of the way.
159
+ */
160
+ /** When these prices were last checked against the providers' public rates. */
161
+ declare const PRECIOS_AL_DIA = "2026-08-13";
162
+ /**
163
+ * Key = the model id as the provider publishes it, without a vendor prefix.
164
+ * Lookup tries the exact id, then the id without its provider prefix, then the
165
+ * LONGEST matching prefix — so `gpt-4o-2024-08-06` inherits `gpt-4o` without
166
+ * having to enumerate every dated snapshot.
167
+ */
168
+ declare const CATALOGO: Readonly<Record<string, CostModel>>;
169
+ /**
170
+ * The list price for a model, or `undefined` when it is not in the catalogue.
171
+ *
172
+ * @param model the id as the caller passes it, with or without a vendor prefix.
173
+ */
174
+ declare function precioDeLista(model: string): CostModel | undefined;
175
+
176
+ /**
177
+ * Environment-agnostic UUID generation.
178
+ * ---------------------------------------------------------------------------
179
+ * Trace IDs only need to be unique, not cryptographically unpredictable. We
180
+ * prefer the platform `crypto.randomUUID` (present in modern Node, browsers,
181
+ * Deno and edge runtimes) and fall back to a v4-shaped id when it is absent, so
182
+ * BV-SALA runs everywhere without a `node:crypto` import that breaks bundlers.
183
+ */
184
+ declare function randomId(): string;
185
+
116
186
  /**
117
187
  * Core contracts for BV-SALA (bivelio-savings-layer).
118
188
  *
@@ -126,8 +196,8 @@ declare class SavingsLedger {
126
196
  * - provider_cached_tokens tokens still in the prompt that the provider may
127
197
  * bill or process differently (prefix/KV caching).
128
198
  *
129
- * A cache hit eliminates a whole call. TOON re-encodes a structured payload.
130
- * LLMLingua removes redundant words. A provider prefix cache makes tokens
199
+ * A cache hit eliminates a whole call. A compact encoding re-encodes a structured payload.
200
+ * A context compressor removes redundant words. A provider prefix cache makes tokens
131
201
  * cheaper without removing them. Mixing these produces misleading metrics.
132
202
  */
133
203
  /** Roles understood by the provider-neutral chat interface. */
@@ -187,7 +257,7 @@ interface OutputContract {
187
257
  version?: string;
188
258
  }
189
259
  /** Serialization formats the format-selector can choose between. */
190
- type SerializationFormat = "json-compact" | "toon" | "csv";
260
+ type SerializationFormat = "json-compact" | "compact-rows" | "csv";
191
261
  interface RetrievalOptions {
192
262
  mode: "off" | "auto" | "single" | "multi";
193
263
  maxRounds?: number;
@@ -201,6 +271,338 @@ interface SavingsToggles {
201
271
  singleflight?: boolean;
202
272
  deterministicResolvers?: boolean;
203
273
  router?: boolean;
274
+ /**
275
+ * OPT-IN, AND WHETHER IT SAVES OR COSTS DEPENDS ON THE MODEL. Read this whole
276
+ * entry before switching it on.
277
+ *
278
+ * It sends the provider a compacted form of your output contract — short
279
+ * property keys, short enum codes — and expands the reply back to YOUR shape
280
+ * before it is validated, cached or returned. What you receive in `data`,
281
+ * `text` and `rendered` is identical either way, and the layer proves that
282
+ * round trip is exact before it applies anything.
283
+ *
284
+ * WHAT LIVE A/Bs MEASURED, same triage contract, same prompts, 8 paired
285
+ * repetitions each. Input fell identically on both models, from 184 to 158
286
+ * tokens — that part is deterministic, it is the smaller contract. Output did
287
+ * not: `gpt-4o` wrote 3 tokens FEWER and the request came out 8.0% cheaper,
288
+ * while `gpt-4o-mini` wrote 16 tokens MORE and the request came out 8.2% more
289
+ * expensive. Short field names appear to give a smaller model less to go on,
290
+ * so it fills in more, and output is billed around 4x input on that family.
291
+ *
292
+ * SO THE LEVER APPLIES ONLY WHERE THAT DELTA HAS BEEN MEASURED. It cannot be
293
+ * read off a schema — the schema was identical in both runs — so the layer
294
+ * does not guess. It uses, in order: the delta you measured and passed in
295
+ * {@link SavingsToggles.outputCodebookMeasuredOutputDeltaTokens}; the same
296
+ * delta remembered from an earlier request for that contract and model; the
297
+ * delta BiVelio has published for that model. With none of those it skips and
298
+ * says so in `report.traces`, including the exact break-even for your contract
299
+ * so you know what a measurement would have to beat.
300
+ */
301
+ outputCodebook?: boolean;
302
+ /**
303
+ * The output-token delta you MEASURED for the contract on this request, with
304
+ * {@link SavingsToggles.outputCodebook} on versus off, against the model you
305
+ * are sending it to. POSITIVE means the compact contract made the model write
306
+ * FEWER output tokens; negative means it wrote more.
307
+ *
308
+ * This is what lets the layer apply the codebook on a model it has not
309
+ * published a figure for, and it overrides the published one when it has —
310
+ * your traffic beats our benchmark. A negative delta switches the lever off
311
+ * for that model: the input win is capped by the size of your schema while the
312
+ * output loss grows with the answer, so no schema is large enough to buy it
313
+ * back.
314
+ *
315
+ * It belongs to ONE contract against ONE model, which is why it travels on the
316
+ * request rather than the layer config: a delta measured on your triage
317
+ * contract says nothing about your extraction contract, and one measured on
318
+ * `gpt-4o` says nothing about `gpt-4o-mini` — those two came out with opposite
319
+ * signs. Measure it with a paired A/B over the same prompts, which is what
320
+ * `benchmarks/ab-live.ts` in this package does, and measure it again when you
321
+ * change model, contract or prompt.
322
+ */
323
+ outputCodebookMeasuredOutputDeltaTokens?: number;
324
+ /**
325
+ * OPT-IN, and it changes the latency contract: where it applies, one request
326
+ * becomes TWO provider calls, one after the other.
327
+ *
328
+ * With a large tool catalogue the full JSON schema of every candidate travels
329
+ * on every request, even though the model will use one tool or none. With this
330
+ * on, the layer may first send lightweight descriptors (name + one line, no
331
+ * `parameters`), read which tools the model asks for, and only then send those
332
+ * full schemas.
333
+ *
334
+ * It is applied only when the token arithmetic for THIS request, priced with
335
+ * your cost model, says the second call pays for itself with margin — so a
336
+ * small catalogue silently keeps the single-call path.
337
+ *
338
+ * THE MARGIN IS NOT A GUARANTEE THAT EVERY REQUEST GETS CHEAPER. When the
339
+ * model names no tool in the first call, the request still goes out with every
340
+ * schema and the first call is pure added cost: that request costs MORE, and
341
+ * the report says so (`cost.netSavings` goes negative). The margin required is
342
+ * exactly the cost of that outcome, so the pattern breaks even while it
343
+ * happens on at most one request in two. If your traffic asks for a tool less
344
+ * often than that — a chat assistant with a big catalogue answers most turns
345
+ * directly — leave this off. Only an A/B against your own traffic settles it.
346
+ *
347
+ * IT ALSO GIVES UP THE PROVIDER'S PREFIX CACHE, on every request, not just the
348
+ * ones where it applies. Tools render FIRST in a provider request, and with
349
+ * this on, which schemas travel is decided by a probe whose answer is not
350
+ * deterministic — so the front of the prompt varies between requests that
351
+ * would otherwise share a cacheable prefix. Marking one would pay the write
352
+ * premium (1.25x on Anthropic) for a prefix that cannot hit. If your prompts
353
+ * carry a long fixed system header and your traffic repeats it, that discount
354
+ * is very likely worth more than this lever: measure both before choosing.
355
+ * `report.traces` says which one is off and why.
356
+ *
357
+ * When the second call happened, `report.calls.actualLlm` is 2. The extra
358
+ * call's tokens are counted as spend and never credited to your baseline.
359
+ * Write and destructive tools always travel with their full schema, so no
360
+ * action can be lost to a discovery step.
361
+ */
362
+ toolDiscovery?: boolean;
363
+ /**
364
+ * Send the request to a CHEAPER model of the same family, check the answer
365
+ * with a deterministic gate, and fall back to the model you asked for only
366
+ * when that gate rejects it. Defaults to `"off"`.
367
+ *
368
+ * It is the only lever here that changes the price of the WHOLE bill — input
369
+ * and output — instead of one axis of it. Read {@link ModelCascadeMode} for
370
+ * what each mode does, and {@link ModelCascadeReport} for the failure this
371
+ * cannot protect you from. Start at `"shadow"`.
372
+ */
373
+ modelCascade?: ModelCascadeMode;
374
+ /**
375
+ * Declare that this answer is NOT needed immediately, so the layer may ask the
376
+ * provider for a cheaper, slower processing tier. Defaults to `false`.
377
+ *
378
+ * YOU DECLARE IT; THE LAYER NEVER GUESSES. Whether a request can wait is a
379
+ * property of your business, not of the text: the same prompt is deferrable in
380
+ * a nightly enrichment job and not deferrable behind a user's cursor. There is
381
+ * no heuristic that can tell those apart, so there is no default that could be
382
+ * right.
383
+ *
384
+ * IT CHANGES YOUR LATENCY, AND THAT IS THE WHOLE POINT. Where a cheaper tier
385
+ * exists it is slower and its latency is variable, so a request on a
386
+ * user-facing path must not carry this. `report.deferred.latency` states what
387
+ * the provider documents. Nothing else about the request changes: same model,
388
+ * same prompt, same sampling controls, so the answer is the answer you would
389
+ * have got.
390
+ *
391
+ * WHAT IT IS WORTH IS READ BACK, NEVER ASSUMED. A provider short on capacity
392
+ * may serve the standard tier and bill the standard rate without saying so in
393
+ * the response body's shape. The layer therefore reads the tier the provider
394
+ * reports having SERVED and credits only that — see
395
+ * {@link DeferredExecutionReport}. On a provider with no cheaper synchronous
396
+ * tier the request is sent unchanged and the report says so.
397
+ */
398
+ deferrable?: boolean;
399
+ }
400
+ /**
401
+ * What asking for a cheaper processing tier actually got you on this request.
402
+ *
403
+ * Present whenever `savings.deferrable` is `true`, INCLUDING when it earned
404
+ * nothing — "we asked and did not get it" is the answer you need most often, and
405
+ * it is the one a report that only spoke on success would hide.
406
+ *
407
+ * ⚠️ THE FAILURE THIS EXISTS TO PREVENT. Asking for a discounted tier is not
408
+ * getting one. Providers document that a request may be served at a different
409
+ * tier from the one it asked for and billed at that tier's rate, and the
410
+ * downgrade is silent: the answer comes back looking exactly the same. A layer
411
+ * that credited the tier it REQUESTED would publish a discount your invoice does
412
+ * not carry, on every request the provider quietly downgraded.
413
+ *
414
+ * So {@link servedTier} is read from the provider's response and it is the only
415
+ * thing {@link credited} is allowed to depend on. When the provider reports no
416
+ * tier at all, nothing is credited: an absent value means "cannot be known", not
417
+ * "standard".
418
+ */
419
+ interface DeferredExecutionReport {
420
+ /** The tier the layer asked the provider for, absent if it asked for none. */
421
+ requestedTier?: string;
422
+ /**
423
+ * The tier the provider reports having SERVED, exactly as it named it. Absent
424
+ * when the provider reported none.
425
+ */
426
+ servedTier?: string;
427
+ /**
428
+ * Whether the served tier bills BELOW the standard rate card, which is the
429
+ * only condition under which a discount is claimed here.
430
+ */
431
+ credited: boolean;
432
+ /**
433
+ * Fraction of the standard list price the served tier bills — `0.5` for a tier
434
+ * documented at half price. Present only when {@link credited}.
435
+ */
436
+ discountRate?: number;
437
+ /**
438
+ * What the tier was worth on this request, in your currency unit.
439
+ *
440
+ * A COUNTERFACTUAL priced from the standard rate card: this request was not
441
+ * also run at standard price, so it is `(1 - discountRate)` applied to the
442
+ * standard-rate cost of THE ONE CALL whose {@link servedTier} was read.
443
+ *
444
+ * ⚠️ IT IS NOT `cost.actual * (1 - discountRate)`, and the difference is not
445
+ * rounding. `cost.actual` is the whole request: it can also carry a repair
446
+ * retry, a tool-discovery probe, or a model-cascade attempt that was tried and
447
+ * thrown away. No tier was read back for any of those, so none of them is
448
+ * credited here even though they very likely got the discount too. Compute the
449
+ * product yourself and you will get a LARGER number than this field — this
450
+ * field is the part that was actually observed.
451
+ *
452
+ * ⚠️ `cost.actual` prices this request at the STANDARD rate card, so when this
453
+ * field is present your invoice line will be LOWER than `cost.actual` by at
454
+ * least this amount. The bias is deliberate and it is the safe one: the layer
455
+ * under-reports the saving rather than promising one it cannot prove. The
456
+ * reason it is not folded in is {@link creditedLedger}.
457
+ */
458
+ estimatedTierSaving?: number;
459
+ /**
460
+ * Which of the four ledgers this credits: none, and that is the honest answer.
461
+ * Deferring avoids no call (ledger 1), removes no tokens (2), re-encodes
462
+ * nothing (3) and is not provider caching (4). It changes the PRICE of the
463
+ * tokens, exactly as the model cascade does, and there is no ledger for that.
464
+ *
465
+ * `cost.netSavings` therefore does not move: it is a ratio of baseline to
466
+ * actual priced from a SINGLE rate card, and a discount that scales both sides
467
+ * cannot change it. Folding this in would require a baseline priced from the
468
+ * other card — a request nobody ran.
469
+ */
470
+ creditedLedger: "none";
471
+ /**
472
+ * What the provider documents about this tier's latency, in one sentence.
473
+ *
474
+ * It is a quotation of what is published, not an estimate. Where a provider
475
+ * commits to no latency figure, this says so rather than inventing one.
476
+ */
477
+ latency: string;
478
+ /** Why it did what it did, in one sentence. Never empty. */
479
+ reason: string;
480
+ }
481
+ /**
482
+ * How much authority the model cascade has over your requests.
483
+ *
484
+ * - `"off"` (default) it neither measures nor decides. Nothing changes.
485
+ * - `"shadow"` it MEASURES and reports, and never changes your model. Every
486
+ * request still goes to the model you asked for. The report gains
487
+ * a {@link ModelCascadeReport} saying which cheaper model was
488
+ * eligible, what the arithmetic says about it, and what the swap
489
+ * would have been worth on THIS request. Costs nothing extra
490
+ * unless you also set `modelCascade.shadowSampleRate` on the
491
+ * layer, which is a separate, priced opt-in.
492
+ * - `"on"` it routes for real. Opt-in, never silent: the report always
493
+ * declares the model that served the answer, and a request served
494
+ * by a cheaper model is labelled `bounded_degradation`.
495
+ *
496
+ * `"on"` carries a gate: when a workload's measured escalation rate reaches the
497
+ * break-even, the cascade turns ITSELF off for that workload and says so. That
498
+ * gate protects your BILL. It cannot protect quality — see
499
+ * {@link ModelCascadeReport}.
500
+ */
501
+ type ModelCascadeMode = "off" | "shadow" | "on";
502
+ /**
503
+ * What the model cascade did, or would have done, on this request.
504
+ *
505
+ * Present whenever `savings.modelCascade` is not `"off"` — including when the
506
+ * request was not eligible, because "we did not route this and here is why" is
507
+ * the answer you need most often.
508
+ *
509
+ * ⚠️ READ THIS BEFORE YOU TURN THE CASCADE ON.
510
+ *
511
+ * The gate that decides whether a cheap answer is good enough is DETERMINISTIC,
512
+ * which is what makes the saving real: an LLM judge would cost the very money
513
+ * this lever exists to save. But a deterministic gate measures FORM, not
514
+ * MEANING. An answer with impeccable JSON and the wrong content passes it.
515
+ *
516
+ * That failure is invisible by construction. It lowers the escalation rate, it
517
+ * raises the reported saving, and the automatic shut-off never fires — because
518
+ * the shut-off watches the escalation rate, and the escalation rate looks
519
+ * excellent. It has the same signature as success, and it surfaces weeks later
520
+ * as customer complaints.
521
+ *
522
+ * So: `escalationRate` is a MONEY figure. Its complement is not a quality score,
523
+ * and this report deliberately does not publish one. If you need evidence about
524
+ * quality, run an A/B on your own traffic. `divergedOnSample` (from shadow
525
+ * sampling) is a hint that something is wrong, never proof that nothing is.
526
+ */
527
+ interface ModelCascadeReport {
528
+ mode: ModelCascadeMode;
529
+ /** The model the caller asked for. */
530
+ requestedModel: string;
531
+ /**
532
+ * The model that actually produced the answer. In `"shadow"` this always
533
+ * equals {@link requestedModel}; in `"on"` it is the cheap model when its
534
+ * answer was accepted, and the requested one when it was escalated.
535
+ */
536
+ servedModel: string;
537
+ /** The cheaper model this request was (or would have been) routed to. */
538
+ candidateModel?: string;
539
+ /** Whether the cascade was allowed to consider this request at all. */
540
+ eligible: boolean;
541
+ /** Why it did what it did, in one sentence. Never empty. */
542
+ reason: string;
543
+ /** What the deterministic gate is able to check here. */
544
+ verifier: "none" | "structural" | "value-constrained";
545
+ /**
546
+ * cost(cheap) / cost(strong), worst case over any token mix. The saving on
547
+ * routed traffic is `1 - r - escalationRate`.
548
+ */
549
+ costRatio?: number;
550
+ /** Escalation rate above which routing costs MORE than not routing (`1 - r`). */
551
+ breakEvenEscalationRate?: number;
552
+ /** Escalation rate above which routing stops hitting the target capture. */
553
+ targetEscalationRate?: number;
554
+ /** Opaque, stable id of the workload these rates are measured over. */
555
+ workload?: string;
556
+ /** Measured escalation rate for this workload, or absent if nothing measured yet. */
557
+ escalationRate?: number;
558
+ /** How many requests that rate is measured over. */
559
+ escalationSamples?: number;
560
+ /** Whether THIS request was escalated to the requested model after the gate rejected it. */
561
+ escalated?: boolean;
562
+ /**
563
+ * What swapping the model was worth on this request, in your currency unit.
564
+ *
565
+ * A COUNTERFACTUAL: the strong model was not run, so this is priced from the
566
+ * measured token counts at both list prices. It is deliberately NOT part of
567
+ * `cost.netSavings` — that number stays a comparison at a single price, and
568
+ * this one would inflate it with a call nobody made. Negative on an escalated
569
+ * request, which is exactly what an escalation costs you.
570
+ */
571
+ estimatedSwapSaving?: number;
572
+ /**
573
+ * Which of the four ledgers this credits: none, and that is the honest answer.
574
+ * Routing avoids no call (ledger 1), removes no tokens (2), re-encodes nothing
575
+ * (3) and is not provider caching (4). It changes the PRICE of the tokens, and
576
+ * there is no ledger for that.
577
+ */
578
+ creditedLedger: "none";
579
+ /**
580
+ * Input tokens of the cheap call whose answer was NOT served — the attempt
581
+ * discarded on an escalation, or a shadow-mode sample.
582
+ *
583
+ * They are deliberately absent from `tokens.actualInput`: another model billed
584
+ * them at another price, and that block is priced with a single cost model, so
585
+ * folding them in would price them wrong. Their MONEY is in `cost.actual`.
586
+ * These two fields are what closes the gap, so
587
+ * `tokens.actualInput * inputPer1k + tokens.actualOutput * outputPer1k` plus
588
+ * the cheap model's price applied here accounts for `cost.actual` in full.
589
+ */
590
+ cascadeInputTokens?: number;
591
+ /** Output tokens of that same unserved call. See {@link cascadeInputTokens}. */
592
+ cascadeOutputTokens?: number;
593
+ /** Whether this shadow request paid for an extra cheap call to measure the rate. */
594
+ sampled?: boolean;
595
+ /** What that extra call cost, in your currency unit. */
596
+ sampleCost?: number;
597
+ /**
598
+ * On a sampled request where BOTH answers satisfied the contract: whether they
599
+ * disagree on a hard value (a number, a boolean, a short code).
600
+ *
601
+ * A hint about false valids, and nothing more. It only covers sampled traffic,
602
+ * disagreement is not proof the cheap answer is the wrong one, and agreement
603
+ * is not proof either answer is right.
604
+ */
605
+ divergedOnSample?: boolean;
204
606
  }
205
607
  interface RequestContext {
206
608
  tenantId?: string;
@@ -266,8 +668,61 @@ interface SavingsResult<T = unknown> {
266
668
  report: SavingsReport;
267
669
  }
268
670
  type CacheOutcome = "miss" | "exact" | "semantic" | "singleflight" | "deterministic";
671
+ /**
672
+ * The quality claims this layer is able to DEMONSTRATE for the answer it served.
673
+ *
674
+ * A boolean cannot carry this. `constraints.structurallyValid` puts an answer
675
+ * computed by a deterministic resolver — exact by construction — in the same box
676
+ * as one the model wrote while nobody checked anything but its shape. Those two
677
+ * are the difference between "this is the result" and "this is what the model
678
+ * said", and a product that claims savings owes the reader that distinction.
679
+ *
680
+ * THE RULE BEHIND THIS VOCABULARY: never report "no loss" on the strength of
681
+ * cosine similarity, BLEU, ROUGE or another model's judgement. Those scores stay
682
+ * high while a figure, a negation or an exception quietly disappears — precisely
683
+ * the fields that decide a legal or financial document. A claim of equivalence
684
+ * rests on a deterministic gate, or it is not made.
685
+ *
686
+ * - `exact` exact by construction: a deterministic resolver computed
687
+ * the answer, or an exact-cache hit replayed the answer
688
+ * already produced for this very same request.
689
+ * - `task_equivalent` not byte-identical, but it cleared a deterministic gate
690
+ * before being served — numbers and dates as a multiset,
691
+ * negation parity and a key-entity floor all had to agree,
692
+ * on top of any similarity score.
693
+ * - `not_verified` the model was called and nothing beyond the shape of the
694
+ * answer was checked. The honest default, and the common case.
695
+ * - `bounded_degradation`
696
+ * the answer was produced by a model CHEAPER than the one
697
+ * you asked for, and it cleared the deterministic gate for
698
+ * its output contract before being served. The bound is
699
+ * exactly what that gate checks — shape, and whatever value
700
+ * constraints your contract declares. NOT meaning: a
701
+ * well-formed answer with wrong content earns this label
702
+ * too. See {@link ModelCascadeReport}.
703
+ * - `rejected` what was served does not satisfy its output contract, and
704
+ * a repair either failed or would have been a wasted call.
705
+ * The answer is still returned: this is the layer's verdict
706
+ * on it, not a refusal to hand it over.
707
+ *
708
+ * The list is deliberately short. A status nobody produces is a promise the
709
+ * reader cannot check, so levers that do not exist yet bring their own status
710
+ * the day they can demonstrate it. Read this as an OPEN set — keep a default
711
+ * branch, exactly as with a licence status.
712
+ */
713
+ declare const QUALITY_STATUSES: readonly ["exact", "task_equivalent", "not_verified", "bounded_degradation", "rejected"];
714
+ type QualityStatus = (typeof QUALITY_STATUSES)[number];
269
715
  interface SavingsReport {
270
716
  requestId: string;
717
+ /**
718
+ * The model that produced this answer.
719
+ *
720
+ * Equal to the model you asked for, EXCEPT when `savings.modelCascade` is
721
+ * `"on"` and a cheaper model's answer cleared the deterministic gate — then
722
+ * this names the cheaper model, because that is what ran and what your bill
723
+ * will show. `report.modelCascade.requestedModel` always holds what you asked
724
+ * for. Changing your model is never silent.
725
+ */
271
726
  model: string;
272
727
  calls: {
273
728
  baselineLlm: number;
@@ -328,8 +783,38 @@ interface SavingsReport {
328
783
  */
329
784
  structurallyValid?: boolean;
330
785
  };
786
+ /**
787
+ * How the served answer earned its place, in one word. Optional and additive:
788
+ * a report built by an older SDK simply does not carry it, so treat an absent
789
+ * value as "unknown", never as a pass.
790
+ *
791
+ * It only ever DEGRADES what the rest of the report claims. An answer that
792
+ * fails its output contract is `rejected` however it was produced — a resolver
793
+ * that computes the right number for the wrong contract does not get to keep
794
+ * the `exact` label.
795
+ */
796
+ qualityStatus?: QualityStatus;
797
+ /**
798
+ * What the model cascade did, or would have done. Present whenever
799
+ * `savings.modelCascade` is not `"off"`. Read its docstring before turning the
800
+ * cascade on: it documents a failure mode no gate in this layer can catch.
801
+ */
802
+ modelCascade?: ModelCascadeReport;
803
+ /**
804
+ * What asking for a cheaper processing tier got you. Present whenever
805
+ * `savings.deferrable` is `true`, including when it got you nothing.
806
+ */
807
+ deferred?: DeferredExecutionReport;
331
808
  quality: {
332
809
  taskPassed?: boolean;
810
+ /**
811
+ * Similarity score of the retained answer, when one was computed.
812
+ *
813
+ * A number here is NOT evidence of equivalence: see {@link QualityStatus}.
814
+ * Cosine (and BLEU, ROUGE, or an LLM judge) can sit above 0.95 across a
815
+ * changed figure or a dropped negation, so it informs a decision — it never
816
+ * licenses a "no loss" claim.
817
+ */
333
818
  semanticScore?: number;
334
819
  freshnessPassed?: boolean;
335
820
  };
@@ -364,10 +849,14 @@ interface SavingsReport {
364
849
  * Structural license gate the pipeline consults before enabling a managed
365
850
  * feature (semantic cache, learned thresholds, long-context compression, …).
366
851
  *
367
- * It is intentionally a bare shape — the SDK never imports the license package.
368
- * `@bivelio/license-client`'s `createLicenseGate()` returns a compatible object,
369
- * but any implementation of this interface works. When no gate is configured the
370
- * pipeline behaves as an ungated self-managed deployment (backwards compatible).
852
+ * It is intentionally a bare shape, so any object with these methods works and
853
+ * the SDK never has to import a licensing package.
854
+ *
855
+ * There is no ungated mode: with no gate and no `licenseKey`, `generate()`
856
+ * throws {@link LicenseError} on entry. In the published npm package an injected
857
+ * gate is ignored entirely — a `licenseKey` verified against BiVelio's embedded
858
+ * public key is the only accepted license there, and this interface is what that
859
+ * key is turned into internally.
371
860
  */
372
861
  interface LicenseGate {
373
862
  isEntitled(feature: string): boolean;
@@ -410,7 +899,7 @@ interface TraceEvent {
410
899
  * be re-explained in free-form natural language at every hop. BV-SALA replaces
411
900
  * that with a compact, typed handoff object. Each consumer receives a
412
901
  * projection of the state, not the whole conversation. The serializer then
413
- * decides whether to send it as compact JSON or TOON.
902
+ * decides whether to send it as compact JSON or compact rows.
414
903
  */
415
904
  interface HandoffFact {
416
905
  id: string;
@@ -460,7 +949,7 @@ interface TokenEstimator {
460
949
  * El heurístico vale para RANKEAR formatos candidatos (le basta con acertar el
461
950
  * orden). No vale para restar tokens entre formatos distintos: cobra cada
462
951
  * símbolo suelto como un token entero, así que infla el JSON denso en símbolos
463
- * mucho más que el TOON, y la diferencia se acredita como ahorro inexistente.
952
+ * mucho más que el the compact encoder, y la diferencia se acredita como ahorro inexistente.
464
953
  */
465
954
  readonly exact: boolean;
466
955
  /** Estimate the number of tokens in a string. */
@@ -517,18 +1006,9 @@ declare const defaultTokenizerRegistry: TokenizerRegistry;
517
1006
  * contamos aquí. Con el estimador heurístico esos dos sumandos vienen de reglas
518
1007
  * distintas, y la diferencia NO es ruido: el heurístico cobra cada símbolo suelto
519
1008
  * como un token entero, así que infla el JSON (40% de símbolos) mucho más que el
520
- * TOON (16%). Medido en el escenario de la tabla: publicaba un baseline 3,68x el
521
- * naive real, o sea un +47,4% de ahorro donde el proveedor midió −49,6%.
522
- *
523
- * Cómo se instala. `gpt-tokenizer` son ~53 MB desempaquetados, así que NO es
524
- * dependencia dura: imponérsela a todo el que instale el SDK, cuando la mayoría
525
- * no necesita contabilidad medida, sería abusivo. Es peerDependency opcional:
526
- *
527
- * pnpm add gpt-tokenizer
528
- *
529
- * Sin ella el layer sigue funcionando con el heurístico, y el `basis` de cada
530
- * informe lo dice — "estimated" en vez de "measured". Esa es la diferencia
531
- * importante: el producto no deja de funcionar, deja de AFIRMAR de más.
1009
+ * An estimator that is not calibrated for the destination model can miss by
1010
+ * enough to publish a baseline several times the real one, which would credit a
1011
+ * saving that never existed. That is why `basis` degrades instead of claiming.
532
1012
  */
533
1013
 
534
1014
  /** Lo que necesitamos de gpt-tokenizer. Tipado aquí para no depender de sus tipos. */
@@ -588,7 +1068,11 @@ interface CanonicalRequest {
588
1068
  model: string;
589
1069
  modelFamily: string;
590
1070
  messages: ChatMessage[];
591
- /** Whitespace/case-normalized text of the user-visible turns. */
1071
+ /**
1072
+ * Text of the user-visible turns, normalized so that trivial near-duplicates
1073
+ * collide: Unicode form, case, whitespace, typographic marks and sentence
1074
+ * punctuation. Numbers, dates, quantities and negations are preserved exactly.
1075
+ */
592
1076
  normalizedText: string;
593
1077
  systemPromptHash: string;
594
1078
  toolsetHash: string;
@@ -616,10 +1100,26 @@ interface CanonicalRequest {
616
1100
  tokenizer: string | undefined;
617
1101
  original: SavingsRequest;
618
1102
  }
1103
+ /**
1104
+ * Normalized form of a turn's text, as it enters the cache key.
1105
+ *
1106
+ * Folded away: Unicode form, case, whitespace, typographic marks, Latin
1107
+ * diacritics and sentence punctuation. Preserved exactly: numbers, decimal and
1108
+ * thousands separators, times, dates, units, negations, math operators and any
1109
+ * combining mark outside the Latin alphabet.
1110
+ */
619
1111
  declare function normalizeText(text: string): string;
620
1112
  declare function sha256(input: string): string;
621
1113
  /** Stable JSON stringify with sorted keys so hashes are order-independent. */
622
1114
  declare function stableStringify(value: unknown): string;
1115
+ /**
1116
+ * The version-and-size-independent family of a model id.
1117
+ *
1118
+ * Exported because the cache key is now built for the model that will actually
1119
+ * ANSWER, which is not always the one the caller named. Deriving the family a
1120
+ * second time somewhere else is how two rulers start to disagree.
1121
+ */
1122
+ declare function modelFamilyOf(model: string): string;
623
1123
  declare function canonicalize(request: SavingsRequest): CanonicalRequest;
624
1124
 
625
1125
  /**
@@ -629,8 +1129,21 @@ declare function canonicalize(request: SavingsRequest): CanonicalRequest;
629
1129
  * text but differ in tenant, permission scope, model version, system prompt,
630
1130
  * schema, toolset, locale, sampling, knowledge epoch or safety policy — any of
631
1131
  * which can change the correct answer. The exact key includes all of them.
1132
+ *
1133
+ * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, not the one the
1134
+ * caller named, and that distinction is what stops the model cascade from
1135
+ * poisoning the cache. Routing to a cheap model and storing its answer under the
1136
+ * STRONG model's key would hand that answer to the next request — one with the
1137
+ * cascade off, or from another process entirely — while the report claims the
1138
+ * requested model served it. That is a silent model swap, deferred, which is the
1139
+ * worst kind of silent model swap.
1140
+ *
1141
+ * `modelFamily` is derived from whichever id is used, for the same reason: it is
1142
+ * part of the identity of whoever answers.
632
1143
  */
633
- declare function buildExactKey(c: CanonicalRequest): string;
1144
+ declare function buildExactKey(c: CanonicalRequest, opciones?: {
1145
+ servedModel?: string;
1146
+ }): string;
634
1147
  /**
635
1148
  * In-flight (singleflight) key. Coalesces concurrent, equivalent requests onto
636
1149
  * a single execution. It can be slightly looser than the exact key (it does not
@@ -641,8 +1154,37 @@ declare function buildExactKey(c: CanonicalRequest): string;
641
1154
  * two concurrent calls that differ only in `schemaVersion` are different
642
1155
  * requests and must never coalesce. All three doors — exact key, in-flight key
643
1156
  * and semantic filter — key on the same identity, by design.
1157
+ *
1158
+ * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, for the same reason
1159
+ * {@link buildExactKey} does, and this door is the one that leaks FIRST. The
1160
+ * exact key protects the answer AFTER it is stored; this one protects it while
1161
+ * it is still being written, and a follower does not re-run anything — it is
1162
+ * handed the leader's text verbatim.
1163
+ *
1164
+ * The leak it closes, measured: two concurrent requests for the same text, one
1165
+ * with `savings.modelCascade: "on"` and one with the cascade OFF, coalesced onto
1166
+ * one execution. The routed leader called the cheap model, and the caller who
1167
+ * never opted in — whose own decision was "do not route" — was served the cheap
1168
+ * model's answer. Same shape inside a single uniformly-configured deployment:
1169
+ * the automatic shut-off can latch between two concurrent requests, so one is
1170
+ * routed and the other is not, and coalescing hands one of them the other's
1171
+ * model. Whoever the layer decided must go to the strong model has to actually
1172
+ * go there.
1173
+ *
1174
+ * `deferrable` CLOSES THE SAME HOLE ON THE LATENCY AXIS, which is why it belongs
1175
+ * here and NOT in {@link buildExactKey}. The two doors do not risk the same
1176
+ * thing: the exact key hands out an answer that is ALREADY WRITTEN, where the
1177
+ * processing tier only picked a queue and changes neither the text nor the wait
1178
+ * of whoever receives it. This one binds a follower to a call that HAS NOT COME
1179
+ * BACK YET, so it imposes the leader's wait on them.
1180
+ *
1181
+ * A caller who never set `savings.deferrable` must not be made to wait for one
1182
+ * who did.
644
1183
  */
645
- declare function buildInflightKey(c: CanonicalRequest): string;
1184
+ declare function buildInflightKey(c: CanonicalRequest, opciones?: {
1185
+ servedModel?: string;
1186
+ deferrable?: boolean;
1187
+ }): string;
646
1188
 
647
1189
  /**
648
1190
  * Policy engine: risk and freshness.
@@ -821,7 +1363,7 @@ declare function cosine(a: number[], b: number[]): number;
821
1363
  declare function keyTokensMatch(a: string, b: string): boolean;
822
1364
  /**
823
1365
  * In-memory reference implementation. A production build would back this with a
824
- * real vector store (pgvector, Qdrant, etc.) and metadata filters pushed down
1366
+ * real vector store and metadata filters pushed down
825
1367
  * to the store. The verification logic — the part that makes it *safe* — stays
826
1368
  * identical regardless of the backend.
827
1369
  */
@@ -936,6 +1478,17 @@ interface ToolSelection {
936
1478
  tools: ToolDescriptor[];
937
1479
  droppedIds: string[];
938
1480
  reason: string;
1481
+ /**
1482
+ * Whether the SET of tools is determined by something stable across requests,
1483
+ * rather than by this request's text.
1484
+ *
1485
+ * It is not cosmetic. Tools render FIRST in a provider request (tools ->
1486
+ * system -> messages), so a tool set that changes with the question moves the
1487
+ * very front of the prompt and invalidates the whole prefix cache behind it.
1488
+ * A caller whose prompt caching silently never hits, on traffic with tools, is
1489
+ * usually looking at this.
1490
+ */
1491
+ setIsStable: boolean;
939
1492
  }
940
1493
  interface ToolSelectorOptions {
941
1494
  /** Max tools to include (schema-token budget guard). */
@@ -1072,7 +1625,7 @@ interface Serializer {
1072
1625
  encode(value: unknown): string;
1073
1626
  /**
1074
1627
  * The instruction text a model needs to interpret this format. For JSON this
1075
- * is empty; for TOON/CSV it is the per-request cost that must be amortized
1628
+ * is empty; for row/CSV it is the per-request cost that must be amortized
1076
1629
  * before the format is worthwhile.
1077
1630
  */
1078
1631
  instruction(value: unknown): string;
@@ -1090,60 +1643,60 @@ interface Serializer {
1090
1643
  * Compact JSON: the lossless baseline every other format is measured against.
1091
1644
  * No indentation, no incidental whitespace. This is frequently the *winner* for
1092
1645
  * deeply nested or non-uniform data, which is exactly why it is the baseline and
1093
- * TOON is only ever a challenger.
1646
+ * The compact encoder is only ever a challenger.
1094
1647
  */
1095
1648
  declare const jsonCompactSerializer: Serializer;
1096
1649
 
1097
1650
  /**
1098
- * TOON (Token-Oriented Object Notation) adapter.
1651
+ * Compact row encoder.
1099
1652
  *
1100
- * TOON's strength is UNIFORM ARRAYS OF FLAT OBJECTS: it declares the column
1653
+ * Its strength is UNIFORM ARRAYS OF FLAT OBJECTS: it declares the column
1101
1654
  * names once and then emits one row per object, which removes the repeated keys
1102
1655
  * that make JSON expensive for tabular data. On deeply nested or non-uniform
1103
- * data TOON can be *larger* than compact JSON — so this is only ever a candidate
1656
+ * data it can be *larger* than compact JSON — so this is only ever a candidate
1104
1657
  * the selector may pick, never a default.
1105
1658
  *
1106
1659
  * This is an in-house, pinned encoder placed behind the {@link Serializer}
1107
1660
  * interface (as recommended: do not couple the system to an upstream `main`).
1108
- * It emits a well-defined subset and ships a matching decoder so the JSON<->TOON
1661
+ * It emits a well-defined subset and ships a matching decoder so the JSON<->rows
1109
1662
  * round-trip is deterministic and testable.
1110
1663
  */
1111
- interface ToonOptions {
1664
+ interface CompactRowsOptions {
1112
1665
  /** Field delimiter within tabular/inline rows. */
1113
1666
  delimiter?: "," | "\t";
1114
1667
  /** Indentation unit. */
1115
1668
  indent?: string;
1116
1669
  }
1117
- declare function encodeToon(value: unknown, options?: ToonOptions): string;
1670
+ declare function encodeCompactRows(value: unknown, options?: CompactRowsOptions): string;
1118
1671
  /** Return shared column names iff every element is a flat, scalar-valued object with identical keys. */
1119
1672
  declare function uniformColumns(arr: unknown[]): string[] | null;
1120
1673
  /**
1121
- * The instruction a model needs to read TOON. Kept short on purpose — this text
1122
- * is the per-request overhead the selector must amortize before TOON wins.
1674
+ * The instruction a model needs to read the compact encoder. Kept short on purpose — this text
1675
+ * is the per-request overhead the selector must amortize before it wins.
1123
1676
  */
1124
- declare const TOON_INSTRUCTION: string;
1125
- declare function createToonSerializer(options?: ToonOptions): Serializer;
1126
- declare const toonSerializer: Serializer;
1677
+ declare const COMPACT_ROWS_INSTRUCTION: string;
1678
+ declare function createCompactRowsSerializer(options?: CompactRowsOptions): Serializer;
1679
+ declare const compactRowsSerializer: Serializer;
1127
1680
 
1128
1681
  /**
1129
- * TOON decoder for the subset emitted by {@link encodeToon}.
1682
+ * Decoder for the subset emitted by {@link encodeCompactRows}.
1130
1683
  *
1131
1684
  * Supports: scalar fields, nested objects, empty arrays, inline scalar arrays
1132
1685
  * (`key[N]: a,b,c`) and tabular object arrays (`key[N]{c1,c2}:` + rows). This
1133
- * covers the cases the format selector actually chooses TOON for, and makes the
1134
- * JSON<->TOON round-trip deterministic and testable. List-form arrays of nested
1686
+ * covers the cases the format selector actually chooses it for, and makes the
1687
+ * JSON<->rows round-trip deterministic and testable. List-form arrays of nested
1135
1688
  * objects are intentionally out of scope for decoding (the selector never picks
1136
- * TOON for those, since JSON wins there anyway).
1689
+ * it for those, since JSON wins there anyway).
1137
1690
  */
1138
- interface ToonDecodeOptions {
1691
+ interface CompactRowsDecodeOptions {
1139
1692
  delimiter?: "," | "\t";
1140
1693
  indent?: string;
1141
1694
  }
1142
- declare function decodeToon(text: string, options?: ToonDecodeOptions): unknown;
1695
+ declare function decodeCompactRows(text: string, options?: CompactRowsDecodeOptions): unknown;
1143
1696
 
1144
1697
  /**
1145
1698
  * CSV serializer — only valid for genuinely tabular data (a uniform array of
1146
- * flat objects). CSV can be marginally smaller than TOON on pure tables, but it
1699
+ * flat objects). CSV can be marginally smaller than the compact rows on pure tables, but it
1147
1700
  * does NOT preserve the JSON data model in general, so `supports()` is strict.
1148
1701
  */
1149
1702
  declare const csvSerializer: Serializer;
@@ -1151,7 +1704,7 @@ declare const csvSerializer: Serializer;
1151
1704
  /**
1152
1705
  * Format selector.
1153
1706
  *
1154
- * TOON is a candidate, never a default. For each request we count, USING THE
1707
+ * It is a candidate, never a default. For each request we count, USING THE
1155
1708
  * DESTINATION MODEL'S TOKENIZER:
1156
1709
  *
1157
1710
  * tokens(encoded payload) + tokens(instruction to understand it)
@@ -1170,7 +1723,7 @@ interface FormatSelectorOptions {
1170
1723
  minimumSavingsRatio?: number;
1171
1724
  /**
1172
1725
  * Per-(model,format) quality penalty in tokens. Encodes "this model is worse
1173
- * at reading TOON" as an additive cost, learned from production traces.
1726
+ * at reading the compact rows" as an additive cost, learned from production traces.
1174
1727
  */
1175
1728
  qualityPenalty?: (model: string, format: SerializationFormat) => number;
1176
1729
  }
@@ -1274,7 +1827,7 @@ declare function resolveConstraintBackend(model: string, schema: JsonSchema, bac
1274
1827
  declare function providerNativeBackend(isSupported?: (model: string, schema: JsonSchema) => boolean): ConstraintBackend;
1275
1828
 
1276
1829
  /**
1277
- * Context compression (LLMLingua-style), as an OPTIONAL sidecar.
1830
+ * Context compression, as an OPTIONAL sidecar.
1278
1831
  *
1279
1832
  * Two rules make this safe and worthwhile:
1280
1833
  *
@@ -1343,9 +1896,60 @@ declare function shouldCompress(input: CompressionPolicyInput): {
1343
1896
  * worse than no compressor at all.
1344
1897
  */
1345
1898
  declare function withProtectedSpans(inner: Compressor): Compressor;
1346
- /** No-op compressor: the safe default until a real LLMLingua sidecar is wired in. */
1899
+ /** No-op compressor: the safe default until a real compressor sidecar is wired in. */
1347
1900
  declare const noopCompressor: Compressor;
1348
1901
 
1902
+ /** What the layer needs to know about a provider's prefix cache to price a decision. */
1903
+ interface CacheTier {
1904
+ /**
1905
+ * Minimum prompt tokens before the provider caches the prefix at all;
1906
+ * `null` means it never does.
1907
+ *
1908
+ * The built-in tiers assume 1024 for every model. That is an ASSUMPTION, not a
1909
+ * verified figure: real minimums differ per model and family. Set your model's
1910
+ * own minimum here when you know it.
1911
+ */
1912
+ minimumCacheTokens: number | null;
1913
+ /** Price of a cache-read token as a fraction of a normal one (e.g. 0.5, 0.1). */
1914
+ cacheReadMultiplier: number;
1915
+ /** Surcharge for WRITING the cache the first time; some providers bill above 1. */
1916
+ cacheWriteMultiplier: number;
1917
+ }
1918
+
1919
+ /** Tunes when compression is worth its money. Every field is optional. */
1920
+ interface CompressionEconomics {
1921
+ /**
1922
+ * The provider's prefix-cache tier. Derived from the request's model when
1923
+ * omitted; set it to state your model's real cache minimum and multipliers.
1924
+ */
1925
+ cacheTier?: CacheTier;
1926
+ /**
1927
+ * YOUR MEASURED prefix-cache economics: how many prompt tokens are a stable
1928
+ * prefix across requests, and how often that prefix arrives already warm
1929
+ * (`report.tokens.providerCachedInput` is the numerator for the rate).
1930
+ *
1931
+ * Both are required together — a hit rate over an unknown prefix says nothing
1932
+ * about how much money is at stake. Declaring them unlocks the full decision,
1933
+ * including knowingly trading the prefix cache for a smaller prompt when that
1934
+ * is the cheaper side. Left out, the layer refuses to compress a prompt out of
1935
+ * a cacheable tier rather than assume a rate on your behalf.
1936
+ */
1937
+ prefix?: {
1938
+ stableTokens: number;
1939
+ hitRate: number;
1940
+ };
1941
+ /**
1942
+ * What running the compressor costs, in equivalent tokens (default 0, which
1943
+ * fits a local deterministic compressor). A compressor that is itself another
1944
+ * model call is not free, and unaccounted spend shows up nowhere else.
1945
+ */
1946
+ compressorCostTokens?: number;
1947
+ /** Do not compress contexts below this many tokens (default 2000). */
1948
+ minimumContextTokens?: number;
1949
+ /** Do not apply a compression that saves fewer net tokens than this (default 200). */
1950
+ minimumNetTokens?: number;
1951
+ }
1952
+
1349
1953
  /**
1350
1954
  * Provider-neutral generation interface. Every provider (OpenAI-compatible,
1351
1955
  * Anthropic-compatible, Google-compatible, self-hosted) is adapted to this
@@ -1362,17 +1966,65 @@ interface ProviderRequest {
1362
1966
  seed?: number;
1363
1967
  /** Backend id selected by the constraints layer, for observability. */
1364
1968
  constraintBackend?: string;
1969
+ /**
1970
+ * How many LEADING messages form the stable, cacheable prefix — the part the
1971
+ * provider may reuse across requests. `undefined` claims nothing and adapters
1972
+ * mark no cache breakpoint.
1973
+ *
1974
+ * Adapters that need EXPLICIT breakpoints (Anthropic) place them at this
1975
+ * boundary; adapters that cache automatically (OpenAI) may ignore it. The
1976
+ * count is a claim about byte-for-byte stability, so a caller that rewrites an
1977
+ * earlier turn must lower it.
1978
+ */
1979
+ stablePrefixMessages?: number;
1980
+ /**
1981
+ * Routing hint so requests sharing a prefix land on the same cache shard
1982
+ * (OpenAI `prompt_cache_key`). It is a hint, never an identity: it must not
1983
+ * carry tenant or user data, because it does not isolate anything.
1984
+ */
1985
+ promptCacheKey?: string;
1986
+ /**
1987
+ * The caller declares this answer is not needed immediately, so the adapter
1988
+ * may ask for a cheaper, slower processing tier.
1989
+ *
1990
+ * It is DECLARED, never inferred: whether a request can wait is a property of
1991
+ * the business, not of the text. An adapter whose provider has no cheaper
1992
+ * synchronous tier leaves the request unchanged.
1993
+ *
1994
+ * Asking is not getting. A provider may serve a downgraded tier and bill it at
1995
+ * the standard rate, so the discount is only ever read back from
1996
+ * {@link ProviderResponse.servedServiceTier}.
1997
+ */
1998
+ deferrable?: boolean;
1365
1999
  }
1366
2000
  interface ProviderUsage {
1367
2001
  inputTokens: number;
1368
2002
  outputTokens: number;
1369
2003
  /** Provider-reported prefix/KV-cached input tokens (ledger 4), if any. */
1370
2004
  cachedInputTokens?: number;
2005
+ /**
2006
+ * Input tokens billed at a PREMIUM to WRITE the provider's prefix cache
2007
+ * (Anthropic `cache_creation_input_tokens`: 1.25x the normal rate).
2008
+ *
2009
+ * This is a COST, not a discount, and it is reported separately for exactly
2010
+ * that reason: `cachedInputTokens` is a saving to publish in ledger 4, this
2011
+ * is the price paid to obtain it. Folding the two together would report a
2012
+ * bill INCREASE as a saving.
2013
+ */
2014
+ cacheWriteInputTokens?: number;
1371
2015
  }
1372
2016
  interface ProviderResponse {
1373
2017
  text: string;
1374
2018
  usage: ProviderUsage;
1375
- /** Whether the provider enforced the schema natively. */
2019
+ /**
2020
+ * Whether the provider enforced the schema natively.
2021
+ *
2022
+ * An explicit `false` is a claim about the guarantee that was obtained, not a
2023
+ * detail: the pipeline reads it and reports `constraints.backend: "none"`, so
2024
+ * an adapter that had to ask for JSON in the prompt (for instance because the
2025
+ * model rejects `response_format`) must set it. Leaving it undefined asserts
2026
+ * nothing and keeps the planned backend.
2027
+ */
1376
2028
  structuredNatively?: boolean;
1377
2029
  /**
1378
2030
  * Por qué paró de generar el modelo. `"length"` significa que se quedó sin
@@ -1395,6 +2047,30 @@ interface ProviderResponse {
1395
2047
  * all the way to the caller.
1396
2048
  */
1397
2049
  toolCalls?: ToolCallRequest[];
2050
+ /**
2051
+ * The processing tier the provider reports having SERVED this request, exactly
2052
+ * as it named it (OpenAI `service_tier`, Anthropic `usage.service_tier`).
2053
+ *
2054
+ * This is the only field a discount may be credited from, and the reason is
2055
+ * that it is not always what was asked for: a provider short on capacity may
2056
+ * serve the standard tier and bill the standard rate while the request said
2057
+ * otherwise. Crediting the REQUESTED tier would publish a saving the invoice
2058
+ * does not carry.
2059
+ *
2060
+ * Absent means the provider said nothing, which is NOT the same as "standard":
2061
+ * it means the tier cannot be known, so nothing is credited.
2062
+ */
2063
+ servedServiceTier?: string;
2064
+ /**
2065
+ * The processing tier this adapter ASKED the provider for, when it asked for
2066
+ * one. Absent means it asked for nothing — which is the honest answer for a
2067
+ * provider that has no cheaper synchronous tier to ask for.
2068
+ *
2069
+ * Reported by the adapter rather than assumed by the caller: only the adapter
2070
+ * knows its provider's vocabulary, and a caller that hardcoded a tier name
2071
+ * would be describing a request some other adapter never made.
2072
+ */
2073
+ requestedServiceTier?: string;
1398
2074
  raw?: unknown;
1399
2075
  }
1400
2076
  interface Provider {
@@ -1410,6 +2086,22 @@ interface Provider {
1410
2086
  * demonstrating the constrained-decoding contract end to end. Token usage is
1411
2087
  * derived from the heuristic estimator so SavingsReport numbers are meaningful
1412
2088
  * in examples and tests.
2089
+ *
2090
+ * ⚠️ WHAT THIS PROVIDER CANNOT MEASURE, and it has already cost money once.
2091
+ * It fabricates the answer FROM THE SCHEMA it receives, so a smaller schema
2092
+ * always produces a smaller answer. Any lever that shrinks the contract sent to
2093
+ * the provider therefore appears to shrink the OUTPUT too — automatically, on
2094
+ * every run, whatever model id is being simulated. That is a property of this
2095
+ * file, not of any model.
2096
+ *
2097
+ * The output codebook is exactly that shape of lever, and against this provider
2098
+ * the benchmark reported it saving 24.8%. Measured live it saves 8.0% on
2099
+ * `gpt-4o` and COSTS 8.2% on `gpt-4o-mini`, because a real model decides how
2100
+ * much to WRITE and a smaller model writes more when the field names carry less
2101
+ * meaning. So: never take an output-token saving measured here as evidence of
2102
+ * anything. Prove those with the paired A/B in `benchmarks/ab-live.ts`, and
2103
+ * write tests for output-side effects against a provider that models the
2104
+ * behaviour you are claiming — see `test/codebook-de-salida.test.ts`.
1413
2105
  */
1414
2106
  interface MockProviderOptions {
1415
2107
  /** Fixed text to return when no schema is present. */
@@ -1436,6 +2128,13 @@ declare function createMockProvider(options?: MockProviderOptions): Provider;
1436
2128
  * with a JSON schema when a contract is present, which is the provider-native
1437
2129
  * constrained-decoding path preferred by the layer.
1438
2130
  *
2131
+ * Models that predate structured outputs reject `response_format` with a 400. The
2132
+ * adapter degrades for those: it repeats the request once with the schema carried
2133
+ * in the prompt instead, remembers the model so later requests skip the rejected
2134
+ * attempt, and reports `structuredNatively: false` so the savings report states
2135
+ * `constraints.backend: "none"` rather than claiming a guarantee the provider
2136
+ * never gave.
2137
+ *
1439
2138
  * Network calls use global `fetch` (Node >= 18) so there is no SDK dependency.
1440
2139
  */
1441
2140
  interface OpenAICompatibleOptions {
@@ -1475,8 +2174,74 @@ declare class OpenAICompatibleProvider implements Provider {
1475
2174
  readonly id: string;
1476
2175
  private readonly opts;
1477
2176
  private readonly fetchImpl;
2177
+ /** Models of this endpoint already known to reject `response_format`. */
2178
+ private readonly sinResponseFormat;
1478
2179
  constructor(opts: OpenAICompatibleOptions);
1479
2180
  generate(req: ProviderRequest): Promise<ProviderResponse>;
2181
+ /** Build the request body and post it. `nativo` selects the `response_format` path. */
2182
+ private enviar;
2183
+ /** Translate a 2xx endpoint response into the provider-neutral shape. */
2184
+ private leer;
2185
+ }
2186
+
2187
+ /**
2188
+ * Anthropic Messages API adapter.
2189
+ *
2190
+ * Speaks `POST /v1/messages`: the system prompt travels in its own top-level
2191
+ * `system` field (not as a message), tools are `{name, description,
2192
+ * input_schema}` with no `function` wrapper, and prefix caching is requested
2193
+ * with EXPLICIT `cache_control` breakpoints — Anthropic does not cache on its
2194
+ * own. A cached read is billed at 0.1x the input rate, which is the largest
2195
+ * single lever on the input side of a customer's bill.
2196
+ *
2197
+ * Network calls use global `fetch` (Node >= 18) so there is no SDK dependency.
2198
+ */
2199
+ /** Render order of the request, which is also the order the cache prefix grows in. */
2200
+ interface AnthropicOptions {
2201
+ apiKey?: string;
2202
+ /** Defaults to the public API. Override for a gateway or a proxy. */
2203
+ baseUrl?: string;
2204
+ /** Wire version pinned in the `anthropic-version` header. */
2205
+ apiVersion?: string;
2206
+ /** Extra headers (e.g. for a gateway). */
2207
+ headers?: Record<string, string>;
2208
+ /** Injectable fetch for testing. */
2209
+ fetchImpl?: typeof fetch;
2210
+ /**
2211
+ * `max_tokens` is REQUIRED by this API — unlike the OpenAI shape, where it is
2212
+ * optional. A request without one is a 400, so the adapter has to supply a
2213
+ * value. It is deliberately generous: a low default would truncate answers
2214
+ * and the truncation would look like a model failure rather than an adapter
2215
+ * default.
2216
+ */
2217
+ defaultMaxOutputTokens?: number;
2218
+ /**
2219
+ * When true (recommended for measurement), a response whose `usage` block is
2220
+ * missing its counters is treated as a HARD ERROR instead of being coerced to
2221
+ * 0 tokens. An absent usage payload otherwise becomes `actual cost = 0` → a
2222
+ * fabricated 100% saving.
2223
+ */
2224
+ requireUsage?: boolean;
2225
+ /**
2226
+ * How many `cache_control` breakpoints the adapter may spend.
2227
+ *
2228
+ * The API caps them at 4 per request. The adapter never needs more than 2
2229
+ * (end of system, end of the stable message prefix) and the cap is exposed so
2230
+ * a gateway with a stricter limit can lower it. Raising it above 4 is
2231
+ * pointless: the extra breakpoints are rejected by the API, not ignored.
2232
+ */
2233
+ maxCacheBreakpoints?: number;
2234
+ }
2235
+ declare class AnthropicProvider implements Provider {
2236
+ readonly id: string;
2237
+ private readonly opts;
2238
+ private readonly fetchImpl;
2239
+ constructor(opts?: AnthropicOptions);
2240
+ generate(req: ProviderRequest): Promise<ProviderResponse>;
2241
+ private construirCuerpo;
2242
+ private marcarCache;
2243
+ /** Traduce una respuesta 2xx a la forma neutra del layer. */
2244
+ private leer;
1480
2245
  }
1481
2246
 
1482
2247
  /**
@@ -1500,7 +2265,19 @@ declare class TraceCollector {
1500
2265
  * unlicensed, rather than silently serving requests for free.
1501
2266
  */
1502
2267
  declare class LicenseError extends Error {
1503
- /** Resolved license status, when known: expired | invalid | revoked | none. */
2268
+ /**
2269
+ * Resolved license status — the stable handle for branching on the cause,
2270
+ * since the message text is rewritten whenever it can be made clearer:
2271
+ *
2272
+ * - `none` — nothing was configured (no `licenseKey`, no gate).
2273
+ * - `invalid` — a key was configured but does not verify.
2274
+ * - `expired` / `grace` — it verifies, but the term is over.
2275
+ * - `revoked` — cancelled by the control plane.
2276
+ * - `stale` — paid and current, but unconfirmed: a connectivity problem.
2277
+ *
2278
+ * The same value is repeated at the end of the message (`license status: X`)
2279
+ * because an uncaught error prints nothing else.
2280
+ */
1504
2281
  readonly status?: string;
1505
2282
  constructor(message: string, status?: string);
1506
2283
  }
@@ -1509,10 +2286,10 @@ declare class LicenseError extends Error {
1509
2286
  * Offline license verification for the paid SDK.
1510
2287
  *
1511
2288
  * A license is a compact signed token `base64url(header).base64url(payload).
1512
- * base64url(sig)` (the exact format the licensing service mints in
1513
- * `@bivelio/license-client`'s `signLicense`). We verify the Ed25519 signature
1514
- * against BiVelio's public key, decode the payload, and apply the same grace
1515
- * policy the license-client uses. Verification is **browser-safe**: it uses
2289
+ * base64url(sig)`, the exact format BiVelio's licensing service mints. We verify
2290
+ * the Ed25519 signature against BiVelio's public key, decode the payload, and
2291
+ * apply the same grace policy the issuer applies. Verification is
2292
+ * **browser-safe**: it uses
1516
2293
  * `@noble/curves` (pure JS) + Web primitives (`atob`, `TextEncoder`) rather than
1517
2294
  * `node:crypto`, so the shared entry point still compiles into the browser
1518
2295
  * demo bundle. The private signing key never leaves the control plane; only the
@@ -1610,8 +2387,8 @@ declare function deriveStatus(payload: {
1610
2387
  grace_days: number;
1611
2388
  }, nowSeconds: number, revoked?: ReadonlySet<string>): LicenseStatus;
1612
2389
  /**
1613
- * Verify + apply the grace policy → a resolved status. Mirrors
1614
- * `@bivelio/license-client`'s `LicenseClient.resolve`. Bad signature / malformed
2390
+ * Verify + apply the grace policy → a resolved status. Mirrors what the issuing
2391
+ * control plane resolves for the same token. Bad signature / malformed
1615
2392
  * → invalid; otherwise the status comes from {@link deriveStatus}.
1616
2393
  */
1617
2394
  declare function resolveLicense(token: string, publicKey: string | LicenseKeyset, opts?: {
@@ -1874,6 +2651,13 @@ interface UsageEmitterOptions {
1874
2651
  * exactamente lo mismo que se ve cuando no hay tráfico—.
1875
2652
  */
1876
2653
  onAuthError?: (status: number) => void;
2654
+ /**
2655
+ * Silence the console warnings. They are ON by default, exactly as in the
2656
+ * revocation poller: metering is best-effort, so a broken ingest breaks
2657
+ * nothing visible — usage simply stops arriving, silently, and the customer's
2658
+ * dashboard stays empty forever.
2659
+ */
2660
+ silenceWarnings?: boolean;
1877
2661
  }
1878
2662
  /**
1879
2663
  * Buffers counts-only usage and flushes it to the control plane. DETECTIVE
@@ -1893,6 +2677,13 @@ declare class UsageEmitter {
1893
2677
  private ci;
1894
2678
  private timer;
1895
2679
  private flushing;
2680
+ /**
2681
+ * CONSECUTIVE failed flushes. Warn on the third and then every twelfth: one
2682
+ * lost batch is normal, a whole hour is not. Same cadence as the revocation
2683
+ * poller on purpose — two different rhythms for the same class of failure
2684
+ * only confuse whoever reads the logs.
2685
+ */
2686
+ private fallosSeguidos;
1896
2687
  constructor(options: UsageEmitterOptions);
1897
2688
  /** Set the resolved seat identity (derivation is async; records emitted before
1898
2689
  * it resolves are stamped at flush time, so none are lost). */
@@ -1908,6 +2699,19 @@ declare class UsageEmitter {
1908
2699
  * since retrying a rejected payload would loop forever.
1909
2700
  */
1910
2701
  flush(): Promise<void>;
2702
+ /**
2703
+ * THE INGEST HAS BEEN FAILING FOR A WHILE AND NOBODY NOTICED.
2704
+ *
2705
+ * Metering is best-effort by design — it must never take down the customer's
2706
+ * request — and that turns every failure into SILENT data loss. Without this,
2707
+ * the only symptom is an empty dashboard, which is indistinguishable from
2708
+ * "you haven't sent anything yet": the symptom points the opposite way from
2709
+ * the cause.
2710
+ *
2711
+ * Warns on the third consecutive failure and then every twelfth. Not the
2712
+ * first — a lost request is normal — and not every one, which becomes noise.
2713
+ */
2714
+ private avisarDeFallo;
1911
2715
  private requeue;
1912
2716
  private toWire;
1913
2717
  }
@@ -1953,8 +2757,24 @@ interface SavingsLayerConfig {
1953
2757
  schemaRegistry?: SchemaRegistry;
1954
2758
  constraintBackends?: ConstraintBackend[];
1955
2759
  compressor?: Compressor;
2760
+ /**
2761
+ * The numbers behind "compress only when it pays for itself" — in money, not
2762
+ * in tokens.
2763
+ *
2764
+ * All optional, and the default never invents a saving: the layer compresses
2765
+ * when the net token saving justifies it, and holds back when compressing
2766
+ * would push the prompt below the provider's cacheable minimum — where fewer
2767
+ * tokens can mean a larger bill. Declare {@link CompressionEconomics.prefix}
2768
+ * with your own measurements to unlock the full decision in both directions.
2769
+ */
2770
+ compressionEconomics?: CompressionEconomics;
1956
2771
  retriever?: Retriever;
1957
2772
  reranker?: Reranker;
2773
+ /**
2774
+ * Tuning for the model cascade (`savings.modelCascade`). All optional; the
2775
+ * defaults measure and never spend.
2776
+ */
2777
+ modelCascade?: ModelCascadeSettings;
1958
2778
  /**
1959
2779
  * Signed BiVelio license token (from your paid subscription). This is the
1960
2780
  * primary way to license the SDK: `createSavingsLayer` verifies it offline
@@ -1972,10 +2792,11 @@ interface SavingsLayerConfig {
1972
2792
  */
1973
2793
  licensePublicKey?: string | LicenseKeyset;
1974
2794
  /**
1975
- * Pre-built license gate. Usually you pass {@link licenseKey} instead and let
1976
- * the SDK construct this. Advanced/self-managed callers can inject their own
1977
- * gate (e.g. `@bivelio/license-client`'s `createLicenseGate()`). When both are
1978
- * given, `licenseGate` wins.
2795
+ * Pre-built license gate. **Ignored in the published npm package**, where a
2796
+ * {@link licenseKey} verified against the embedded BiVelio public key is the
2797
+ * only accepted license — passing a gate there logs a warning and changes
2798
+ * nothing. It exists for builds from source (this repo's own tests and
2799
+ * benchmarks), where it takes precedence over {@link licenseKey}.
1979
2800
  */
1980
2801
  licenseGate?: LicenseGate;
1981
2802
  /**
@@ -2106,11 +2927,73 @@ interface SavingsLayerConfig {
2106
2927
  * in tests/benchmarks that want internal errors to surface.
2107
2928
  */
2108
2929
  failOpen?: boolean;
2930
+ /**
2931
+ * Silence BV-SALA's console warnings. They are ON by default, and they only
2932
+ * ever report a condition whose symptom is otherwise invisible: a saving knob
2933
+ * asked for but not wired up, usage that stops reaching the control plane, a
2934
+ * revocation list going stale. Nothing here throws and nothing here changes
2935
+ * what the layer does — turning it on only removes the console noise, so set
2936
+ * it when you have your own observability. Applies to the pipeline, the usage
2937
+ * emitter and the revocation poller alike.
2938
+ */
2939
+ silenceWarnings?: boolean;
2109
2940
  /** Injectable clock for deterministic tests. */
2110
2941
  now?: () => number;
2111
2942
  /** Injectable id generator for deterministic tests. */
2112
2943
  generateId?: () => string;
2113
2944
  }
2945
+ /**
2946
+ * Tuning for the model cascade. Everything here is optional, and the defaults
2947
+ * cost nothing: with no settings at all, `modelCascade: "shadow"` measures what
2948
+ * it can measure for free and makes no extra provider call.
2949
+ */
2950
+ interface ModelCascadeSettings {
2951
+ /**
2952
+ * Your own strong → cheap pairs, replacing the built-in table.
2953
+ *
2954
+ * Keys and values are model ids WITHOUT a vendor prefix (`"gpt-4o"`, not
2955
+ * `"openai/gpt-4o"`); the caller's prefix is carried over automatically, and a
2956
+ * dated snapshot inherits from its base id. A pair whose economics do not work
2957
+ * out at your prices is refused with a reason in the report — declaring it here
2958
+ * does not override the arithmetic.
2959
+ */
2960
+ pairs?: Record<string, string>;
2961
+ /**
2962
+ * How many requests of a workload are routed before the automatic shut-off
2963
+ * judges the measured escalation rate (default 20).
2964
+ *
2965
+ * The rate is only observable on requests that were actually routed, so there
2966
+ * is no way to know without spending a bounded amount to find out. Worst case
2967
+ * (every one of them escalates) that exploration costs `n x r` requests'
2968
+ * worth of extra spend, once per workload per process — with a ratio of 0.06
2969
+ * and the default, about 1.2 requests.
2970
+ */
2971
+ exploration?: number;
2972
+ /**
2973
+ * PRICED OPT-IN, default 0 (off). Fraction of `"shadow"` requests on which the
2974
+ * layer also calls the cheap model, purely to measure.
2975
+ *
2976
+ * Shadow mode is free precisely because it does not run the cheap model: it
2977
+ * reports eligibility, the arithmetic, and what the swap would have been worth.
2978
+ * What it cannot know for free is the one number that decides everything — how
2979
+ * often the deterministic gate would have REJECTED the cheap answer. That
2980
+ * answer does not exist until the cheap model writes one.
2981
+ *
2982
+ * So this buys it, and the price is exact: each sampled request costs one extra
2983
+ * cheap call, i.e. `r` of what the request already cost — about 6% for
2984
+ * gpt-4o → gpt-4o-mini. At `0.05` that is roughly 0.3% on top of your bill, and
2985
+ * every sampled request reports its own `modelCascade.sampleCost`. It also
2986
+ * DOUBLES that request's provider latency, because the extra call is sequential.
2987
+ *
2988
+ * The sampled request's tokens count as spend and are held out of the baseline,
2989
+ * exactly like the repair retry: measurement must never pay for itself.
2990
+ *
2991
+ * Which requests get sampled is derived from the request's own cache key, so it
2992
+ * is deterministic and stable — the same request is always sampled or always
2993
+ * not, and no `Math.random` decides how your money is spent.
2994
+ */
2995
+ shadowSampleRate?: number;
2996
+ }
2114
2997
  declare const DEFAULT_COST_MODEL: CostModel;
2115
2998
 
2116
2999
  /** Fully-resolved, non-optional dependency set the pipeline runs against. */
@@ -2147,12 +3030,18 @@ interface SavingsLayer {
2147
3030
  * Create a savings layer. The only hard requirement is a `provider`; a default
2148
3031
  * heuristic tokenizer and in-memory implementations of every other component
2149
3032
  * are wired automatically. Replace any of them for production (Redis exact
2150
- * cache, pgvector/Qdrant semantic cache, a real tokenizer, an LLMLingua sidecar,
3033
+ * cache, a vector-backed semantic cache, a real tokenizer, a compressor sidecar,
2151
3034
  * XGrammar/llguidance backends, etc.).
2152
3035
  *
3036
+ * A `licenseKey` is not optional in practice: `generate()` is fail-closed and
3037
+ * throws {@link LicenseError} without one, so it belongs in the smallest example
3038
+ * there is.
3039
+ *
2153
3040
  * @example
2154
- * const layer = createSavingsLayer({ provider: createMockProvider() });
2155
- * const { data, report } = await layer.generate({
3041
+ * // `licenseKey` is the signed token from savings.bivelio.com/dashboard/install,
3042
+ * // read from wherever you keep secrets (BIVELIO_LICENSE_KEY in the README).
3043
+ * const layer = createSavingsLayer({ provider, licenseKey });
3044
+ * const { text, report } = await layer.generate({
2156
3045
  * model: "openai/gpt-4o-mini",
2157
3046
  * messages: [{ role: "user", content: "What is 2 + 2 * 3?" }],
2158
3047
  * });
@@ -2185,6 +3074,7 @@ declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"
2185
3074
  * on the customer's machine. `test/version-declarada.test.ts` ties this
2186
3075
  * constant to package.json so the two can never drift.
2187
3076
  */
2188
- declare const BV_SALA_VERSION = "0.2.2";
2189
3077
 
2190
- export { BV_SALA_VERSION, type BivelioHandoff, type CacheOutcome, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactVerification, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, 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, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderUsage, 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, TOON_INSTRUCTION, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, type ToonDecodeOptions, type ToonOptions, TraceCollector, type TraceEvent, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, computeNextDelay, cosine, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, createToonSerializer, csvSerializer, decodeToon, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, emptyState, encodeToon, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, liveGateFromKey, mergeDelta, noopCompressor, normalizeText, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, toonSerializer, uniformColumns, validateAgainstSchema, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans };
3078
+ declare const BV_SALA_VERSION = "0.3.1";
3079
+
3080
+ 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, 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, type JsonSchema, LicenseError, type LicenseGate, 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, TraceCollector, type TraceEvent, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, liveGateFromKey, mergeDelta, modelFamilyOf, noopCompressor, normalizeText, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans };