@bivelio/savings-layer 0.4.22 → 0.4.25

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;
@@ -54,6 +123,8 @@ declare class SavingsLedger {
54
123
  cacheWriteInputTokens: number;
55
124
  /** The premium on those writes, already priced in USD. Adds to `actual` only. */
56
125
  cacheWritePremiumUsd: number;
126
+ /** Provider-initiated cache-write premium, priced in USD. Adds to baseline and `actual` alike. */
127
+ providerCacheWritePremiumUsd: number;
57
128
  /** Cuenta LOCAL del prompt que de verdad se envió al proveedor. */
58
129
  sentInputLocal: number;
59
130
  /** Tokens que la capa quitó de ese prompt, en cuenta LOCAL. */
@@ -87,6 +158,13 @@ declare class SavingsLedger {
87
158
  * cost; work the layer does to fix its own output has no place in it.
88
159
  */
89
160
  repairInput: number;
161
+ /**
162
+ * Billed provider calls in this request that came back WITHOUT a complete
163
+ * usage counter (`usage.complete === false`). Any value above zero makes the
164
+ * request `unmeasured`: a figure built on a missing counter is not the
165
+ * provider's meter.
166
+ */
167
+ callsWithoutUsage: number;
90
168
  /**
91
169
  * INPUT of the schema-on-demand discovery probe. Held apart exactly like
92
170
  * {@link repairInput}, and for the same reason.
@@ -115,11 +193,15 @@ declare class SavingsLedger {
115
193
  *
116
194
  * The caller raises `actualLlm`, because the call happened and the report owes
117
195
  * the reader that count.
196
+ *
197
+ * @param providerWritePremium the multiplier the provider bills on its own
198
+ * for the call's cache writes (`usage.cacheWriteInputTokens`); 1 = none.
118
199
  */
119
200
  cascadeAttempt(usage: {
120
201
  inputTokens: number;
121
202
  outputTokens: number;
122
- }, cheapPrice: CostModel): void;
203
+ cacheWriteInputTokens?: number;
204
+ }, cheapPrice: CostModel, providerWritePremium?: number): void;
123
205
  /** Record the premium for `tokens` of cache write billed at `multiplicador`x. */
124
206
  cacheWritePremium(tokens: number, multiplicador: number, precioDelQueMarco: CostModel): void;
125
207
  /**
@@ -140,6 +222,14 @@ declare class SavingsLedger {
140
222
  compress(originalTokens: number, compressedTokens: number): void;
141
223
  /** Ledger 4: tokens still present but billed/processed as provider-cached. */
142
224
  providerCached(tokens: number): void;
225
+ /** Record the premium for `tokens` of cache write the provider bills at `multiplier`x on its own. */
226
+ providerCacheWritePremium(tokens: number, multiplier: number, price: CostModel): void;
227
+ /** Set when an avoided call is valued against the provider counter of an identical call that ran. */
228
+ reference: ReferenceCredit | null;
229
+ /** Set when an avoided cache or coalesced hit could not be valued by reference: why it stays estimated. */
230
+ referenceRejected: ReferenceRejection | null;
231
+ /** Record an avoided call valued by reference. */
232
+ avoidCallByReference(credit: ReferenceCredit): void;
143
233
  /** Record that a whole primary LLM call was avoided (cache / resolver hit). */
144
234
  avoidCall(baselineInput: number, baselineOutput: number): void;
145
235
  /**
@@ -182,20 +272,28 @@ declare class SavingsLedger {
182
272
  * catalogue stays out of the way.
183
273
  */
184
274
  /** When these prices were last checked against the providers' public rates. */
185
- declare const PRECIOS_AL_DIA = "2026-09-06";
275
+ declare const PRECIOS_AL_DIA = "2026-09-27";
186
276
  /**
187
277
  * Key = the model id as the provider publishes it, without a vendor prefix.
188
278
  * Lookup tries the exact id, then the id without its provider prefix, then the
189
279
  * LONGEST matching prefix — so `gpt-4o-2024-08-06` inherits `gpt-4o` without
190
280
  * having to enumerate every dated snapshot.
281
+ *
282
+ * These are the rows in force on {@link PRECIOS_AL_DIA}. A price change a
283
+ * provider has already announced is applied by {@link precioDeLista} from its
284
+ * effective date, so a request is always priced at the rate of its own day.
191
285
  */
192
286
  declare const CATALOGO: Readonly<Record<string, CostModel>>;
193
287
  /**
194
- * The list price for a model, or `undefined` when it is not in the catalogue.
288
+ * The list price for a model on the day of the request, or `undefined` when
289
+ * the catalogue has no price for it in force that day.
195
290
  *
196
291
  * @param model the id as the caller passes it, with or without a vendor prefix.
292
+ * @param at when the request was made (epoch ms or a `Date`); defaults to now.
293
+ * Prices resolve per UTC day, so a price change the provider has announced
294
+ * applies from its effective date.
197
295
  */
198
- declare function precioDeLista(model: string): CostModel | undefined;
296
+ declare function precioDeLista(model: string, at?: Date | number): CostModel | undefined;
199
297
 
200
298
  /**
201
299
  * The version the SDK reports for itself.
@@ -205,7 +303,7 @@ declare function precioDeLista(model: string): CostModel | undefined;
205
303
  * on the customer's machine. `test/version-declarada.test.ts` ties this
206
304
  * constant to package.json so the two can never drift.
207
305
  */
208
- declare const BV_SALA_VERSION = "0.4.22";
306
+ declare const BV_SALA_VERSION = "0.4.25";
209
307
 
210
308
  /**
211
309
  * Environment-agnostic UUID generation.
@@ -217,6 +315,110 @@ declare const BV_SALA_VERSION = "0.4.22";
217
315
  */
218
316
  declare function randomId(): string;
219
317
 
318
+ /** How the layer served a request that is eligible for a verification check. */
319
+ type VerificationStratum =
320
+ /** Answered without calling the provider (cache, resolver, identical request in flight). */
321
+ "avoided_call"
322
+ /** Answered by a cheaper model than the one requested. */
323
+ | "cheaper_model"
324
+ /** Sent with parts removed. */
325
+ | "tokens_removed"
326
+ /** Sent re-encoded with fewer tokens. */
327
+ | "tokens_compressed"
328
+ /** Sent without removing or re-encoding anything. */
329
+ | "unchanged";
330
+ /** Why a request is never checked. */
331
+ type VerificationExclusion = "fail_open" | "truncated" | "incomplete" | "contract_failed" | "unmeasured" | "no_price" | "over_cap" | "opted_out" | "retrieval";
332
+ /** How a check ended. */
333
+ type VerificationOutcome = "paired" | "check_failed" | "check_unmeasured" | "abandoned" | "dropped";
334
+ /**
335
+ * Opt-in A/B verification on your own traffic. Off unless `sampleRate` is above 0.
336
+ *
337
+ * For the sampled share of eligible requests, after the answer has been returned,
338
+ * the layer sends the same request once more to your provider exactly as it would
339
+ * go without the layer (the requested model, all your tools, your data inline, no
340
+ * cache marks) and discards that second answer. Each pair (what the unoptimized
341
+ * copy cost and what the optimized request cost, both from your provider's usage
342
+ * counters) is reported to your dashboard and to `onCheck`.
343
+ *
344
+ * Every copy is a real provider call that you pay for: roughly `sampleRate` times
345
+ * what the eligible traffic would cost without the layer. Pairs are informational:
346
+ * they never enter measured savings, your statement or any commission.
347
+ */
348
+ interface VerificationSettings {
349
+ /** Share of eligible requests to check, from 0 to 1. Any other value turns verification off. */
350
+ sampleRate: number;
351
+ /** Skip requests whose estimated unoptimized cost, in USD, is above this. An invalid value turns verification off. */
352
+ maxCheckCostUsd?: number;
353
+ /** Called once per sampled eligible request, when its check is settled. Errors thrown here are ignored. */
354
+ onCheck?: (check: VerificationCheck) => void;
355
+ /**
356
+ * Keeps a serverless runtime alive until a check is settled and reported, for
357
+ * example your platform's `waitUntil`. Receives one promise per copy actually
358
+ * sent (a check recorded as `dropped` is never sent and passes none); it never rejects.
359
+ */
360
+ waitUntil?: (promise: Promise<void>) => void;
361
+ }
362
+ /** Token counts and cost of one side of a pair, from the provider's usage counters. */
363
+ interface VerificationSide {
364
+ cost: number;
365
+ inputTokens: number;
366
+ cachedInputTokens: number;
367
+ outputTokens: number;
368
+ }
369
+ /** One settled verification check. */
370
+ interface VerificationCheck {
371
+ /** Id of the served request (its report.requestId). */
372
+ requestId: string;
373
+ /** Epoch ms when the served request was decided. */
374
+ at: number;
375
+ stratum: VerificationStratum;
376
+ /**
377
+ * - `paired`: both sides metered; `naive` is present.
378
+ * - `check_failed`: the provider returned an error for the copy, or it timed out.
379
+ * - `check_unmeasured`: the provider returned no usage counter for the copy.
380
+ * - `abandoned`: the copy was still running when `close()` was called.
381
+ * - `dropped`: not sent, because too many checks were already running.
382
+ */
383
+ outcome: VerificationOutcome;
384
+ requestedModel: string;
385
+ servedModel: string;
386
+ cacheResult: CacheOutcome;
387
+ /** The served request, as its report measured it. Zero on an avoided call. */
388
+ optimized: VerificationSide;
389
+ /**
390
+ * The unoptimized copy. Present only when `outcome` is `paired`. The dashboard
391
+ * compares a pair only when the copy finished the way a served request must to
392
+ * be checked: `finishReason` `stop`, `tool_calls` or absent, and a contract that
393
+ * was not `missed`. Other pairs are counted and exported, but left out of every figure.
394
+ */
395
+ naive?: VerificationSide & {
396
+ finishReason?: string;
397
+ /** Whether the copy met your output contract: `none` without a contract or on a tool-call turn. */
398
+ contract: "met" | "missed" | "none";
399
+ };
400
+ /** Rates used to price the copy, in USD per 1K tokens. */
401
+ prices: {
402
+ inputPer1k: number;
403
+ cachedInputPer1k?: number;
404
+ outputPer1k: number;
405
+ };
406
+ }
407
+ /**
408
+ * Whether this request was drawn for verification sampling, and how it counts.
409
+ * Present only when `verification` is enabled on the layer and its pairs have
410
+ * somewhere to go (your dashboard or `onCheck`). Absent after `close()`, and on
411
+ * the rare internal error of the sampler, which never affects the request itself.
412
+ */
413
+ interface VerificationReport {
414
+ /** Drawn for a check. A copy is sent only when the request is also eligible. */
415
+ sampled: boolean;
416
+ /** How the layer served this request, when it is eligible for a check. */
417
+ stratum?: VerificationStratum;
418
+ /** Why this request is never checked. Present exactly when `stratum` is absent. */
419
+ excluded?: VerificationExclusion;
420
+ }
421
+
220
422
  /**
221
423
  * Core contracts for BV-SALA (bivelio-savings-layer).
222
424
  *
@@ -317,7 +519,28 @@ interface RetrievalOptions {
317
519
  }
318
520
  interface SavingsToggles {
319
521
  exactCache?: boolean;
320
- semanticCache?: boolean | "safe-only";
522
+ /**
523
+ * OPT-IN. Reuse the answer to an earlier, reworded version of this request.
524
+ * Needs a semantic cache on the layer (`createSavingsLayer({ semanticCache })`)
525
+ * and a license that includes it.
526
+ *
527
+ * - `true` and `"safe-only"` are the same setting: single-shot requests only.
528
+ * - `"include-agentic"` also admits agentic requests: those that declare tools
529
+ * (read-only ones included), carry tool calls or tool results, or hold more
530
+ * than one user turn.
531
+ *
532
+ * WHY AGENTIC TRAFFIC IS LEFT OUT BY DEFAULT. Two runs of an agent loop can
533
+ * differ only in what a tool returned. The deterministic guard under the
534
+ * similarity score compares figures and dates, negation and word overlap; it
535
+ * cannot tell that a tool said something else in words. Measure it on your own
536
+ * traffic before you include it.
537
+ *
538
+ * Whatever you set, requests with write or destructive tools, freshness-sensitive
539
+ * requests (including any freshness bucket other than `any`), personalized
540
+ * requests and stochastic sampling never use this cache. A hit is reported as
541
+ * `cache.result: "semantic"` and `qualityStatus: "task_equivalent"`.
542
+ */
543
+ semanticCache?: boolean | "safe-only" | "include-agentic";
321
544
  compression?: boolean | "auto";
322
545
  /**
323
546
  * OPT-IN. Elide old, large tool_results from a multi-turn context before the
@@ -469,6 +692,21 @@ interface SavingsToggles {
469
692
  * tier the request is sent unchanged and the report says so.
470
693
  */
471
694
  deferrable?: boolean;
695
+ /**
696
+ * Declare this request STANDALONE: nothing after its stable prefix will be sent again as
697
+ * the start of a later request (it is not a conversation turn you will continue).
698
+ * Defaults to false. Only acts on OpenAI models that bill prompt-cache writes above the
699
+ * input rate, on OpenAI's own API: the provider may then write its cache only at the
700
+ * stable prefix, or nowhere, instead of at the end of your message. Trade-off: repeats
701
+ * and continuations of this exact prompt lose the cache reads the provider default would
702
+ * give them. See report.cacheWrites. Never changes the answer.
703
+ */
704
+ standalone?: boolean;
705
+ /**
706
+ * Set to `false` to keep this request out of verification sampling: it is
707
+ * counted as excluded and never sent twice. No effect when verification is off.
708
+ */
709
+ verification?: boolean;
472
710
  }
473
711
  /**
474
712
  * What asking for a cheaper processing tier actually got you on this request.
@@ -503,32 +741,33 @@ interface DeferredExecutionReport {
503
741
  */
504
742
  credited: boolean;
505
743
  /**
506
- * Fraction of the standard list price the served tier bills — `0.5` for a tier
507
- * documented at half price. Present only when {@link credited}.
744
+ * The effective fraction of the standard rate card this call was billed at:
745
+ * `tierSaving.appliedCost / tierSaving.standardCost` on the one call whose tier
746
+ * was read. `0.5` when every component of the call bills at half price; higher
747
+ * when some component (typically cached input) is discounted less or not at
748
+ * all. Present only when {@link credited}.
508
749
  */
509
750
  discountRate?: number;
751
+ /** @deprecated Use `tierSaving.amount`; same value. Kept for compatibility. */
752
+ estimatedTierSaving?: number;
510
753
  /**
511
- * What the tier was worth on this request, in your currency unit.
512
- *
513
- * A COUNTERFACTUAL priced from the standard rate card: this request was not
514
- * also run at standard price, so it is `(1 - discountRate)` applied to the
515
- * standard-rate cost of THE ONE CALL whose {@link servedTier} was read.
516
- *
517
- * ⚠️ IT IS NOT `cost.actual * (1 - discountRate)`, and the difference is not
518
- * rounding. `cost.actual` is the whole request: it can also carry a repair
519
- * retry, a tool-discovery probe, or a model-cascade attempt that was tried and
520
- * thrown away. No tier was read back for any of those, so none of them is
521
- * credited here even though they very likely got the discount too. Compute the
522
- * product yourself and you will get a LARGER number than this field — this
523
- * field is the part that was actually observed.
754
+ * What the served tier was worth on the one call whose tier was read: that call's
755
+ * provider-reported usage at the standard rate card minus the same usage at the rate
756
+ * card of the tier that served it, component by component. Present only when credited.
524
757
  *
525
- * ⚠️ `cost.actual` prices this request at the STANDARD rate card, so when this
526
- * field is present your invoice line will be LOWER than `cost.actual` by at
527
- * least this amount. The bias is deliberate and it is the safe one: the layer
528
- * under-reports the saving rather than promising one it cannot prove. The
529
- * reason it is not folded in is {@link creditedLedger}.
758
+ * It is NOT `cost.actual` times a discount. `cost.actual` is the whole request and
759
+ * can also carry a repair retry, a tool-discovery probe or a model-cascade attempt;
760
+ * no tier was read back for those, so none of them is credited here. And
761
+ * `cost.actual` prices the request at the STANDARD rate card, so when this is
762
+ * present your invoice line is lower than `cost.actual` by at least this amount.
530
763
  */
531
- estimatedTierSaving?: number;
764
+ tierSaving?: PriceLeverSaving;
765
+ /**
766
+ * True when the cheaper tier had no capacity and the request was repeated once at
767
+ * the standard tier (layer setting `priceLevers.onCapacityError: "standard"`).
768
+ * Nothing is credited then, and the refused attempt is not counted as a call.
769
+ */
770
+ capacityFallback?: boolean;
532
771
  /**
533
772
  * Which of the four ledgers this credits: none, and that is the honest answer.
534
773
  * Deferring avoids no call (ledger 1), removes no tokens (2), re-encodes
@@ -551,6 +790,44 @@ interface DeferredExecutionReport {
551
790
  /** Why it did what it did, in one sentence. Never empty. */
552
791
  reason: string;
553
792
  }
793
+ /**
794
+ * What a provider price lever was worth on one call, in your currency unit.
795
+ *
796
+ * It never enters `cost.*` and is never billed: it is reported next to the four
797
+ * ledgers, with its own basis.
798
+ */
799
+ interface PriceLeverSaving {
800
+ /** In your currency unit. Never more than the difference between the two published rate cards on this usage. */
801
+ amount: number;
802
+ /**
803
+ * "measured": the provider's own counter for this call at two published rate cards,
804
+ * the tier read back from the provider's official API, and the counter reporting how
805
+ * much of the input was read from (or, where billed apart, written to) the prompt
806
+ * cache. "estimated": anything short of that; `basisReason` says what.
807
+ */
808
+ basis: Extract<CostBasis, "measured" | "estimated">;
809
+ /** This call's provider-reported usage at the standard rate card. */
810
+ standardCost: number;
811
+ /** The same usage at the rate card that applied. */
812
+ appliedCost: number;
813
+ /** YYYY-MM-DD: when these rate cards were last checked against the provider's published prices. */
814
+ pricesCheckedOn: string;
815
+ /** One sentence. Never empty. */
816
+ basisReason: string;
817
+ }
818
+ /** Present whenever `savings.standalone` is true, including when it did nothing. */
819
+ interface CacheWriteReport {
820
+ mode: "explicit" | "provider-default";
821
+ /**
822
+ * Cache breakpoints the layer placed: 0 or 1. With 0 in explicit mode the provider
823
+ * neither reads nor writes its prefix cache.
824
+ */
825
+ breakpoints: number;
826
+ /** Always basis "estimated": the provider default was not run for this request. */
827
+ avoidedWritePremium?: PriceLeverSaving;
828
+ /** Why, in one sentence, including what it costs you. Never empty. */
829
+ reason: string;
830
+ }
554
831
  /**
555
832
  * How much authority the model cascade has over your requests.
556
833
  *
@@ -740,6 +1017,79 @@ interface SavingsResult<T = unknown> {
740
1017
  finishReason?: string;
741
1018
  report: SavingsReport;
742
1019
  }
1020
+ /** One event of a streamed answer. */
1021
+ type SavingsStreamEvent<T = unknown> =
1022
+ /** A piece of the answer text, as the provider wrote it. Never empty. */
1023
+ {
1024
+ type: "text";
1025
+ delta: string;
1026
+ }
1027
+ /** One complete tool call the model requested, in the order of `result.toolCalls`. */
1028
+ | {
1029
+ type: "tool_call";
1030
+ call: ToolCallRequest;
1031
+ }
1032
+ /** Always the last event, exactly once. `result` is the same object `SavingsStream.result` resolves to. */
1033
+ | {
1034
+ type: "done";
1035
+ result: SavingsResult<T>;
1036
+ };
1037
+ /** Options for `generateStream`. */
1038
+ interface SavingsStreamOptions {
1039
+ /** Aborting it has the same effect as calling `abort()` on the stream. */
1040
+ signal?: AbortSignal;
1041
+ }
1042
+ /**
1043
+ * The handle returned by `generateStream`. Iterate it (once) for the events, or
1044
+ * await `result` for the final SavingsResult; both work on their own, because
1045
+ * the request starts as soon as the handle is created.
1046
+ *
1047
+ * Invariants: the concatenation of every `text` delta equals `result.text`; the
1048
+ * `tool_call` events, in order, equal `result.toolCalls`; `done` is the last
1049
+ * event and `result` is already resolved when it arrives.
1050
+ */
1051
+ interface SavingsStream<T = unknown> extends AsyncIterable<SavingsStreamEvent<T>> {
1052
+ /**
1053
+ * Resolves with the final SavingsResult (the same object the `done` event
1054
+ * carries). Rejects with the provider or license error that ended the stream,
1055
+ * or with StreamAbortedError when the stream was aborted before the provider
1056
+ * finished its answer.
1057
+ */
1058
+ readonly result: Promise<SavingsResult<T>>;
1059
+ /**
1060
+ * Stops the stream. Iteration ends at once, without an error. While the
1061
+ * provider is still writing a live answer, its request is cancelled: `result`
1062
+ * rejects with StreamAbortedError, nothing is cached and nothing is reported.
1063
+ * Once the provider's answer is complete, or when it is produced in one piece
1064
+ * (output contract, deferrable request), nothing is cancelled: `result`
1065
+ * resolves with the full report and it is recorded like any other request.
1066
+ * Breaking out of a for-await loop calls abort(). No-op after `done`.
1067
+ */
1068
+ abort(reason?: unknown): void;
1069
+ }
1070
+ /** How a streamed request reached the caller. Present only on results of `generateStream`. */
1071
+ interface StreamingReport {
1072
+ /**
1073
+ * "live": the text was handed over as the provider produced it.
1074
+ * "whole": the complete answer arrived as one `text` event.
1075
+ */
1076
+ delivery: "live" | "whole";
1077
+ /**
1078
+ * Why the answer arrived whole. Absent when `delivery` is "live".
1079
+ * - "output-contract": the request carried `response.schema`; it was validated first.
1080
+ * - "deferrable": the request was declared `deferrable`.
1081
+ * - "provider-without-streaming": the provider has no `generateStream`.
1082
+ * - "shortcut": answered without a provider call (cache, deterministic resolver, or an identical request in flight).
1083
+ * - "fail-open": an internal error sent the request down the direct fallback path.
1084
+ */
1085
+ wholeReason?: "output-contract" | "deferrable" | "provider-without-streaming" | "shortcut" | "fail-open";
1086
+ /**
1087
+ * Milliseconds from the `generateStream` call to the first `text` or
1088
+ * `tool_call` event handed to the caller. Present only when `delivery` is
1089
+ * "live" and such an event exists.
1090
+ */
1091
+ firstEventMs?: number;
1092
+ }
743
1093
  type CacheOutcome = "miss" | "exact" | "semantic" | "singleflight" | "deterministic";
744
1094
  /**
745
1095
  * The quality claims this layer is able to DEMONSTRATE for the answer it served.
@@ -785,6 +1135,155 @@ type CacheOutcome = "miss" | "exact" | "semantic" | "singleflight" | "determinis
785
1135
  */
786
1136
  declare const QUALITY_STATUSES: readonly ["exact", "task_equivalent", "not_verified", "bounded_degradation", "rejected"];
787
1137
  type QualityStatus = (typeof QUALITY_STATUSES)[number];
1138
+ /** Where the answer you received came from on this request. */
1139
+ type AnswerSource =
1140
+ /** Your provider generated it for this request. */
1141
+ "provider"
1142
+ /** A replay of the answer already produced for this very request. */
1143
+ | "exact_cache"
1144
+ /** A reuse of the answer to a reworded version of this request. */
1145
+ | "semantic_cache"
1146
+ /** Shared with an identical request that was running at the same time. */
1147
+ | "coalesced"
1148
+ /** Computed locally; no model was involved. */
1149
+ | "deterministic"
1150
+ /** A direct provider call after an internal error; nothing was optimized. */
1151
+ | "fail_open";
1152
+ /** The output-contract check on the answer that was served. */
1153
+ interface QualitySchemaCheck {
1154
+ /**
1155
+ * `passed` or `failed`: the served answer was validated against
1156
+ * `response.schema`. `not_applicable`: the model asked to run tools, so there
1157
+ * is no final answer yet.
1158
+ */
1159
+ outcome: "passed" | "failed" | "not_applicable";
1160
+ /**
1161
+ * Whether the layer relied on a decode-time constraint for this answer (the
1162
+ * backend named in `constraints.backend`). The answer is validated either way.
1163
+ * Always false on cache and resolver answers. A coalesced answer carries the
1164
+ * value of the request it joined, which is the one that generated it.
1165
+ */
1166
+ decodeTimeConstraint: boolean;
1167
+ /** Validation errors in the served answer. 0 when it passed. */
1168
+ errorCount: number;
1169
+ /**
1170
+ * The model wrapped valid JSON in prose or a code fence, and it was extracted
1171
+ * locally instead of paying for a repair call.
1172
+ */
1173
+ unwrapped?: true;
1174
+ }
1175
+ /** What happened when the first answer failed its output contract. */
1176
+ interface QualityRepairCheck {
1177
+ /**
1178
+ * `repaired`: one repair call was made and its answer passed. `still_invalid`:
1179
+ * one repair call was made and its answer failed too. `skipped`: no repair
1180
+ * call was made; `skipReason` says why.
1181
+ */
1182
+ outcome: "repaired" | "still_invalid" | "skipped";
1183
+ /**
1184
+ * - `output_truncated`: the answer hit the output limit, and the same limit
1185
+ * would cut the repair off again.
1186
+ * - `schema_defect`: no answer can satisfy the schema (for example, a pattern
1187
+ * that is not a valid regular expression).
1188
+ * - `would_cost_more_than_no_layer`: repairing would have cost more than
1189
+ * sending the request without the layer (opt-in, `skipRepairAboveBaseline`).
1190
+ * - `decode_time_constraint`: generation was already constrained, so a retry
1191
+ * would not change the outcome.
1192
+ */
1193
+ skipReason?: "output_truncated" | "schema_defect" | "would_cost_more_than_no_layer" | "decode_time_constraint";
1194
+ }
1195
+ /** Whether the provider stopped because it reached the output limit. */
1196
+ interface QualityTruncationCheck {
1197
+ /** True when the provider reported `finishReason: "length"`. A truncated answer is never cached. */
1198
+ truncated: boolean;
1199
+ /** The stop reason exactly as the provider named it; absent when it reported none. */
1200
+ finishReason?: string;
1201
+ /**
1202
+ * Whether you set the limit (`response.maxOutputTokens`). When false, any
1203
+ * limit that applied was a provider or adapter default.
1204
+ */
1205
+ capSetByCaller: boolean;
1206
+ /** The limit you set, in output tokens. Present only when `capSetByCaller`. */
1207
+ outputLimit?: number;
1208
+ }
1209
+ /** How a cache hit matched this request. */
1210
+ type QualityCacheMatch = {
1211
+ kind: "exact";
1212
+ /** Age of the replayed answer, in milliseconds. */
1213
+ ageMs?: number;
1214
+ } | {
1215
+ kind: "semantic";
1216
+ /** Age of the reused answer, in milliseconds. */
1217
+ ageMs?: number;
1218
+ /**
1219
+ * Similarity between this request and the stored one. It is not evidence
1220
+ * of equivalence: see {@link QualityStatus}.
1221
+ */
1222
+ similarity: number;
1223
+ /** The similarity a candidate had to reach to be considered at all. */
1224
+ similarityFloor: number;
1225
+ /**
1226
+ * The confidence the hit had to reach to be served. Confidence is the
1227
+ * lower of `similarity` and `verifierScore` (`cache.confidence`).
1228
+ */
1229
+ acceptFloor: number;
1230
+ /** Your verifier's score, present only when you configured one. */
1231
+ verifierScore?: number;
1232
+ /** `built_in`: the lexical embedder shipped with the SDK. `custom`: yours. */
1233
+ embedder: "built_in" | "custom";
1234
+ /**
1235
+ * The deterministic guard passed, turn by turn: figures and dates,
1236
+ * negation parity and content-word overlap. A hit is never served without it.
1237
+ */
1238
+ factGuard: "passed";
1239
+ };
1240
+ /** What the model cascade decided on this request. See {@link ModelCascadeReport}. */
1241
+ interface QualityCascadeCheck {
1242
+ mode: "shadow" | "on";
1243
+ /** What the deterministic gate was able to check (`modelCascade.verifier`). */
1244
+ gate: "none" | "structural" | "value-constrained";
1245
+ /**
1246
+ * - `accepted`: a cheaper model answered and its answer cleared the gate.
1247
+ * - `escalated`: the gate rejected the cheaper answer and the model you asked for answered.
1248
+ * - `not_routed`: the cascade is on but this request was not routed (see `modelCascade.reason`).
1249
+ * - `not_applied`: shadow mode. The cascade only evaluated this request and
1250
+ * the model you asked for answered. On a sampled request
1251
+ * (`modelCascade.sampled`) the cheaper model also ran, and its answer was
1252
+ * not served.
1253
+ */
1254
+ outcome: "accepted" | "escalated" | "not_routed" | "not_applied";
1255
+ /**
1256
+ * Shadow sampling only: both answers satisfied the contract and disagreed on
1257
+ * a hard value. A hint, never proof.
1258
+ */
1259
+ sampleDiverged?: boolean;
1260
+ }
1261
+ /**
1262
+ * What the layer checked on the answer it served, and what came of it.
1263
+ *
1264
+ * It holds no money and no token counts. It is never added to the four savings
1265
+ * ledgers or to `cost`, and it never leaves your process: usage reporting does
1266
+ * not carry it. `qualityStatus` is the one-word verdict; this is the evidence
1267
+ * behind it. A block is present only when its check applies to this request.
1268
+ * With `source: "coalesced"`, the blocks describe the shared answer as the
1269
+ * request it joined produced it: nothing was run again. An absent ledger means
1270
+ * unknown (an older SDK, or a report built elsewhere).
1271
+ */
1272
+ interface QualityLedger {
1273
+ source: AnswerSource;
1274
+ /** Only with `source: "coalesced"`: where the shared answer came from for the request it joined. */
1275
+ sharedFrom?: Exclude<AnswerSource, "coalesced">;
1276
+ /** Present when the request carried `response.schema`. */
1277
+ schema?: QualitySchemaCheck;
1278
+ /** Present when a provider answer on this request failed its first validation. */
1279
+ repair?: QualityRepairCheck;
1280
+ /** Present when a provider generated the served answer. */
1281
+ truncation?: QualityTruncationCheck;
1282
+ /** Present when the answer came from the exact or the semantic cache. */
1283
+ cacheMatch?: QualityCacheMatch;
1284
+ /** Present when `savings.modelCascade` is not `"off"`. */
1285
+ cascade?: QualityCascadeCheck;
1286
+ }
788
1287
  interface SavingsReport {
789
1288
  requestId: string;
790
1289
  /**
@@ -837,6 +1336,10 @@ interface SavingsReport {
837
1336
  result: CacheOutcome;
838
1337
  confidence?: number;
839
1338
  ageMs?: number;
1339
+ /** Present when this avoided call is valued by reference; see ReferenceCredit. */
1340
+ reference?: ReferenceCredit;
1341
+ /** Present on an exact-cache or coalesced hit that stays estimated: why no reference applied. Semantic-cache hits never carry it. */
1342
+ referenceRejected?: ReferenceRejection;
840
1343
  };
841
1344
  routing: {
842
1345
  executionClass: ExecutionClass;
@@ -880,6 +1383,12 @@ interface SavingsReport {
880
1383
  * the `exact` label.
881
1384
  */
882
1385
  qualityStatus?: QualityStatus;
1386
+ /**
1387
+ * The evidence behind `qualityStatus`: where the answer came from and which
1388
+ * checks it went through, with no money in it. See {@link QualityLedger}.
1389
+ * Optional and additive: absent means unknown.
1390
+ */
1391
+ qualityLedger?: QualityLedger;
883
1392
  /**
884
1393
  * What the model cascade did, or would have done. Present whenever
885
1394
  * `savings.modelCascade` is not `"off"`. Read its docstring before turning the
@@ -891,6 +1400,18 @@ interface SavingsReport {
891
1400
  * `savings.deferrable` is `true`, including when it got you nothing.
892
1401
  */
893
1402
  deferred?: DeferredExecutionReport;
1403
+ /**
1404
+ * Where the provider was allowed to write its prompt cache. Present whenever
1405
+ * `savings.standalone` is `true`, including when it changed nothing.
1406
+ */
1407
+ cacheWrites?: CacheWriteReport;
1408
+ /** How a streamed request was delivered. Present only on results of `generateStream`. */
1409
+ streaming?: StreamingReport;
1410
+ /**
1411
+ * Whether this request was drawn for verification sampling, and how it counts.
1412
+ * Present only when `verification` is enabled on the layer.
1413
+ */
1414
+ verification?: VerificationReport;
894
1415
  quality: {
895
1416
  taskPassed?: boolean;
896
1417
  /**
@@ -914,6 +1435,22 @@ interface SavingsReport {
914
1435
  *
915
1436
  * - `measured` — reconstructed from THIS request's provider counter. Real
916
1437
  * money, checkable against the provider invoice.
1438
+ * - `reference` — MEASURED BY REFERENCE: the call was avoided (a repeat
1439
+ * served without calling the provider) and a byte-identical
1440
+ * request had already run through the layer untouched. The
1441
+ * baseline is the provider's own counter for that request,
1442
+ * valued at the provider's cached-input rate. Output is
1443
+ * credited only under deterministic sampling, on a model
1444
+ * with a list price in the catalog that does not reason and
1445
+ * honors that setting, when the call that ran carried no
1446
+ * hidden reasoning (zero reasoning tokens reported, or no
1447
+ * reasoning block where the provider does not break them
1448
+ * out), and then at 95% of its counter. With an output
1449
+ * schema, only when the schema traveled exactly as sent, in
1450
+ * strict mode, in the provider's native response-format
1451
+ * parameter. Valued low on purpose.
1452
+ * Enters the commission base; `cache.reference` carries the
1453
+ * arithmetic and the id of the request that ran.
917
1454
  * - `estimated` — the call was AVOIDED, so there never was a provider
918
1455
  * counter and the baseline comes from the local estimator.
919
1456
  * The best figure available, but not measured.
@@ -1115,64 +1652,50 @@ declare class TokenizerRegistry {
1115
1652
  declare const defaultTokenizerRegistry: TokenizerRegistry;
1116
1653
 
1117
1654
  /**
1118
- * Tokenizador EXACTO — la regla del proveedor, no una aproximación.
1655
+ * Exact cache.
1119
1656
  *
1120
- * Por qué existe. El baseline de una llamada que se ejecutó se reconstruye como
1121
- * "lo enviado + lo quitado". Lo enviado lo mide el proveedor; lo quitado lo
1122
- * contamos aquí. Con el estimador heurístico esos dos sumandos vienen de reglas
1123
- * distintas, y la diferencia NO es ruido: el heurístico cobra cada símbolo suelto
1124
- * como un token entero, así que infla el JSON (40% de símbolos) mucho más que el
1125
- * An estimator that is not calibrated for the destination model can miss by
1126
- * enough to publish a baseline several times the real one, which would credit a
1127
- * saving that never existed. That is why `basis` degrades instead of claiming.
1657
+ * When the canonical key represents the request completely, a hit removes 100%
1658
+ * of input tokens, 100% of output tokens, and the whole provider call. This is
1659
+ * the single highest-value, lowest-risk saving in the layer. The interface is
1660
+ * deliberately minimal (get/set with TTL) so any store — Redis, a KV, an LRU —
1661
+ * can back it.
1128
1662
  */
1129
-
1130
- /** Lo que necesitamos de gpt-tokenizer. Tipado aquí para no depender de sus tipos. */
1131
- interface BpeEncoder {
1132
- encode(text: string): number[] | {
1133
- length: number;
1134
- };
1663
+ interface CachedAnswer<T = unknown> {
1664
+ text: string;
1665
+ data?: T;
1666
+ rendered?: string;
1667
+ /** Epoch ms when stored; used to compute age and honor TTL. */
1668
+ storedAtMs: number;
1669
+ /**
1670
+ * Optional metadata carried alongside the answer.
1671
+ *
1672
+ * `meta.reference` carries the provider counter of the call that produced this
1673
+ * answer, when that call ran untouched; readers that do not know it ignore it.
1674
+ */
1675
+ meta?: Record<string, unknown>;
1135
1676
  }
1136
- /**
1137
- * Estimador exacto sobre una codificación BPE ya cargada.
1138
- *
1139
- * Se construye con el encoder ya resuelto en vez de importarlo dentro: así
1140
- * este módulo no obliga a nadie a tener el paquete, y quien lo tiene decide qué
1141
- * codificación carga (o200k_base para gpt-4o/4.1/5, cl100k_base para 3.5/4).
1142
- */
1143
- declare class ExactTokenizer implements TokenEstimator {
1144
- private readonly encoder;
1145
- readonly id: string;
1146
- readonly exact = true;
1147
- constructor(id: string, encoder: BpeEncoder);
1148
- count(text: string): number;
1677
+ interface ExactCache {
1678
+ get<T = unknown>(key: string): Promise<CachedAnswer<T> | null>;
1679
+ set<T = unknown>(key: string, value: CachedAnswer<T>, ttlMs?: number): Promise<void>;
1680
+ delete(key: string): Promise<void>;
1149
1681
  }
1150
- /** Modelos que usan cada codificación. Sirve para mapear el registro de una vez. */
1151
- declare const MODELS_BY_ENCODING: Readonly<Record<string, readonly string[]>>;
1152
- /**
1153
- * Carga `gpt-tokenizer` si está instalado y registra el tokenizador exacto para
1154
- * los modelos de OpenAI. Devuelve `false` —sin lanzar— cuando no está: la
1155
- * ausencia del paquete opcional no es un error, solo significa que la
1156
- * contabilidad se publicará como estimada.
1157
- *
1158
- * Se importa solo la codificación pedida, no el paquete entero: `gpt-tokenizer`
1159
- * expone un punto de entrada por codificación y cargarlas todas serían decenas
1160
- * de MB en memoria sin motivo.
1161
- */
1162
- declare function registerExactTokenizer(registro: {
1163
- register(e: TokenEstimator): unknown;
1164
- mapModel(model: string, tokenizerId: string): unknown;
1165
- }, opciones?: {
1166
- codificaciones?: readonly string[];
1167
- }): Promise<boolean>;
1168
1682
  /**
1169
- * @deprecated Spanish-named aliases kept for one minor version. Use
1170
- * {@link MODELS_BY_ENCODING} and {@link registerExactTokenizer}. The public
1171
- * surface of this package is English-only; these leaked and are on the way out.
1683
+ * In-memory exact cache with TTL and a size bound (LRU-ish by insertion order).
1684
+ * Suitable for tests and single-process deployments; swap for Redis in prod.
1172
1685
  */
1173
- declare const MODELOS_POR_CODIFICACION: Readonly<Record<string, readonly string[]>>;
1174
- /** @deprecated Use {@link registerExactTokenizer}. */
1175
- declare const registrarTokenizadorExacto: typeof registerExactTokenizer;
1686
+ declare class InMemoryExactCache implements ExactCache {
1687
+ private readonly store;
1688
+ private readonly maxEntries;
1689
+ private readonly now;
1690
+ constructor(opts?: {
1691
+ maxEntries?: number;
1692
+ now?: () => number;
1693
+ });
1694
+ get<T = unknown>(key: string): Promise<CachedAnswer<T> | null>;
1695
+ set<T = unknown>(key: string, value: CachedAnswer<T>, ttlMs?: number): Promise<void>;
1696
+ delete(key: string): Promise<void>;
1697
+ get size(): number;
1698
+ }
1176
1699
 
1177
1700
  /**
1178
1701
  * Canonicalization turns a raw request into a normalized, hashable form. This
@@ -1239,188 +1762,14 @@ declare function modelFamilyOf(model: string): string;
1239
1762
  declare function canonicalize(request: SavingsRequest): CanonicalRequest;
1240
1763
 
1241
1764
  /**
1242
- * Exact-cache key.
1243
- *
1244
- * A hash of the user text alone is NOT a safe cache key. Two requests may share
1245
- * text but differ in tenant, permission scope, model version, system prompt,
1246
- * schema, toolset, locale, sampling, knowledge epoch or safety policy — any of
1247
- * which can change the correct answer. The exact key includes all of them.
1248
- *
1249
- * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, not the one the
1250
- * caller named, and that distinction is what stops the model cascade from
1251
- * poisoning the cache. Routing to a cheap model and storing its answer under the
1252
- * STRONG model's key would hand that answer to the next request — one with the
1253
- * cascade off, or from another process entirely — while the report claims the
1254
- * requested model served it. That is a silent model swap, deferred, which is the
1255
- * worst kind of silent model swap.
1765
+ * Semantic cache (safe-by-construction).
1256
1766
  *
1257
- * `modelFamily` is derived from whichever id is used, for the same reason: it is
1258
- * part of the identity of whoever answers.
1259
- */
1260
- declare function buildExactKey(c: CanonicalRequest, opciones?: {
1261
- servedModel?: string;
1262
- }): string;
1263
- /**
1264
- * In-flight (singleflight) key. Coalesces concurrent, equivalent requests onto
1265
- * a single execution. It can be slightly looser than the exact key (it does not
1266
- * need freshness bucketing, since the work is happening *now*), but must still
1267
- * isolate tenants and permissions.
1268
- *
1269
- * `schema` is REQUIRED here, even though `contract` is already part of the key:
1270
- * two concurrent calls that differ only in `schemaVersion` are different
1271
- * requests and must never coalesce. All three doors — exact key, in-flight key
1272
- * and semantic filter — key on the same identity, by design.
1273
- *
1274
- * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, for the same reason
1275
- * {@link buildExactKey} does, and this door is the one that leaks FIRST. The
1276
- * exact key protects the answer AFTER it is stored; this one protects it while
1277
- * it is still being written, and a follower does not re-run anything — it is
1278
- * handed the leader's text verbatim.
1279
- *
1280
- * The leak it closes, measured: two concurrent requests for the same text, one
1281
- * with `savings.modelCascade: "on"` and one with the cascade OFF, coalesced onto
1282
- * one execution. The routed leader called the cheap model, and the caller who
1283
- * never opted in — whose own decision was "do not route" — was served the cheap
1284
- * model's answer. Same shape inside a single uniformly-configured deployment:
1285
- * the automatic shut-off can latch between two concurrent requests, so one is
1286
- * routed and the other is not, and coalescing hands one of them the other's
1287
- * model. Whoever the layer decided must go to the strong model has to actually
1288
- * go there.
1289
- *
1290
- * `deferrable` CLOSES THE SAME HOLE ON THE LATENCY AXIS, which is why it belongs
1291
- * here and NOT in {@link buildExactKey}. The two doors do not risk the same
1292
- * thing: the exact key hands out an answer that is ALREADY WRITTEN, where the
1293
- * processing tier only picked a queue and changes neither the text nor the wait
1294
- * of whoever receives it. This one binds a follower to a call that HAS NOT COME
1295
- * BACK YET, so it imposes the leader's wait on them.
1296
- *
1297
- * A caller who never set `savings.deferrable` must not be made to wait for one
1298
- * who did.
1299
- */
1300
- declare function buildInflightKey(c: CanonicalRequest, opciones?: {
1301
- servedModel?: string;
1302
- deferrable?: boolean;
1303
- toolDiscovery?: boolean;
1304
- maxOutputTokens?: number;
1305
- }): string;
1306
-
1307
- /**
1308
- * Policy engine: risk and freshness.
1309
- *
1310
- * This is the gate that decides which savings are *safe* for a given request.
1311
- * The most dangerous failure of a savings layer is not a cache miss — it is
1312
- * confidently returning a wrong answer (a stale price, another tenant's data, a
1313
- * reused answer for a side-effecting action). The policy engine exists to make
1314
- * those outcomes structurally impossible, not merely unlikely.
1315
- */
1316
- interface Policy {
1317
- /** May the exact cache be read/written for this request? */
1318
- allowExactCache: boolean;
1319
- /** May the semantic cache even be *considered*? (still verified later) */
1320
- allowSemanticCache: boolean;
1321
- /** May concurrent equivalent requests be coalesced? */
1322
- allowSingleflight: boolean;
1323
- /** May we run natural-language compression on the context? */
1324
- allowCompression: boolean;
1325
- /**
1326
- * May the SDK elide old, large tool_results before resending? Computed, not
1327
- * hardcoded like `allowCompression`: eliding drops whole tool outputs, so it
1328
- * honors the same vetoes as the semantic cache — write tools, freshness,
1329
- * personalization, stochastic sampling and `day`.
1330
- */
1331
- allowElision: boolean;
1332
- /** Does the request demand current/live data? */
1333
- requiresFreshData: boolean;
1334
- /** Does the request have (or request) side effects? */
1335
- hasSideEffects: boolean;
1336
- /** Is the response personalized to a user/account? */
1337
- isPersonalized: boolean;
1338
- /**
1339
- * Techo de antigüedad para una entrada de caché exacta, derivado del
1340
- * `freshnessBucket`. `null` = sin techo propio (manda el TTL por defecto).
1341
- * Existe porque el bucket NO particiona la clave —va crudo al hash y no rota—,
1342
- * así que sin esto "hour" y "day" no significaban nada operativamente.
1343
- */
1344
- maxCacheAgeMs: number | null;
1345
- reasons: string[];
1346
- }
1347
- declare function evaluatePolicy(c: CanonicalRequest): Policy;
1348
-
1349
- /**
1350
- * Exact cache.
1351
- *
1352
- * When the canonical key represents the request completely, a hit removes 100%
1353
- * of input tokens, 100% of output tokens, and the whole provider call. This is
1354
- * the single highest-value, lowest-risk saving in the layer. The interface is
1355
- * deliberately minimal (get/set with TTL) so any store — Redis, a KV, an LRU —
1356
- * can back it.
1357
- */
1358
- interface CachedAnswer<T = unknown> {
1359
- text: string;
1360
- data?: T;
1361
- rendered?: string;
1362
- /** Epoch ms when stored; used to compute age and honor TTL. */
1363
- storedAtMs: number;
1364
- /** Optional metadata carried alongside the answer. */
1365
- meta?: Record<string, unknown>;
1366
- }
1367
- interface ExactCache {
1368
- get<T = unknown>(key: string): Promise<CachedAnswer<T> | null>;
1369
- set<T = unknown>(key: string, value: CachedAnswer<T>, ttlMs?: number): Promise<void>;
1370
- delete(key: string): Promise<void>;
1371
- }
1372
- /**
1373
- * In-memory exact cache with TTL and a size bound (LRU-ish by insertion order).
1374
- * Suitable for tests and single-process deployments; swap for Redis in prod.
1375
- */
1376
- declare class InMemoryExactCache implements ExactCache {
1377
- private readonly store;
1378
- private readonly maxEntries;
1379
- private readonly now;
1380
- constructor(opts?: {
1381
- maxEntries?: number;
1382
- now?: () => number;
1383
- });
1384
- get<T = unknown>(key: string): Promise<CachedAnswer<T> | null>;
1385
- set<T = unknown>(key: string, value: CachedAnswer<T>, ttlMs?: number): Promise<void>;
1386
- delete(key: string): Promise<void>;
1387
- get size(): number;
1388
- }
1389
-
1390
- /**
1391
- * Singleflight (in-flight coalescing).
1392
- *
1393
- * When N equivalent requests arrive at once, exactly one executes the work and
1394
- * the other N-1 await the same promise. This is NOT a historical cache — it is
1395
- * de-duplication of concurrent work, so it carries no staleness risk and needs
1396
- * no TTL. On traffic spikes it can eliminate a large fraction of duplicate
1397
- * provider calls for free.
1398
- */
1399
- declare class Singleflight {
1400
- private readonly inflight;
1401
- /** How many callers are currently sharing an in-flight execution. */
1402
- private readonly waiters;
1403
- /**
1404
- * Run `fn` under `key`. If a call with the same key is already running, share
1405
- * its result instead of starting a new one. Returns the shared result plus
1406
- * whether this caller was a coalesced follower (a "hit").
1407
- */
1408
- run<T>(key: string, fn: () => Promise<T>): Promise<{
1409
- value: T;
1410
- coalesced: boolean;
1411
- }>;
1412
- get activeKeys(): number;
1413
- }
1414
-
1415
- /**
1416
- * Semantic cache (safe-by-construction).
1417
- *
1418
- * A semantic cache reuses a previous answer for a *similar* query. Its danger is
1419
- * the false positive: confidently returning a valid answer to a nearby-but-wrong
1420
- * question. Therefore the headline metric here is NOT hit ratio — it is the
1421
- * PRECISION of hits. Every candidate must pass: tenant/permission filter,
1422
- * knowledge-epoch filter, a similarity threshold, a stronger local verifier, a
1423
- * freshness check, and an output-contract compatibility check.
1767
+ * A semantic cache reuses a previous answer for a *similar* query. Its danger is
1768
+ * the false positive: confidently returning a valid answer to a nearby-but-wrong
1769
+ * question. Therefore the headline metric here is NOT hit ratio — it is the
1770
+ * PRECISION of hits. Every candidate must pass: tenant/permission filter,
1771
+ * knowledge-epoch filter, a similarity threshold, a stronger local verifier, a
1772
+ * freshness check, and an output-contract compatibility check.
1424
1773
  */
1425
1774
  interface EmbeddingFn {
1426
1775
  (text: string): Promise<number[]> | number[];
@@ -1452,10 +1801,14 @@ interface SemanticRecord {
1452
1801
  /**
1453
1802
  * The COMPLETE identity of the request that produced this answer.
1454
1803
  *
1455
- * Every field here is a hard filter, checked before similarity is scored, and
1456
- * the set is identical to the one the exact cache keys on. Two questions
1457
- * worded alike but carrying different attached data, a different model or a
1458
- * different system prompt are different questions, and never match.
1804
+ * Every field here is a hard filter, checked before similarity is scored.
1805
+ * Together with the isolation fields above they are the identity the exact
1806
+ * cache keys on, except for the text itself, which is what similarity
1807
+ * compares. Freshness is covered by construction: the semantic cache only
1808
+ * runs on requests whose freshness bucket is `any`, the default. Two questions worded
1809
+ * alike but carrying different attached data, a different model, a different
1810
+ * system prompt, a different tool catalog or different sampling controls are
1811
+ * different questions, and never match.
1459
1812
  */
1460
1813
  model: string;
1461
1814
  systemPromptHash: string;
@@ -1463,14 +1816,30 @@ interface SemanticRecord {
1463
1816
  locale: string;
1464
1817
  schemaVersion: string;
1465
1818
  safetyPolicyVersion: string;
1819
+ /** Hash of the declared tool catalog (ids, risk and parameters). */
1820
+ toolsetHash: string;
1821
+ /** Sampling temperature of the request, or null when it set none. */
1822
+ temperature: number | null;
1823
+ /** Sampling seed of the request, or null when it set none. */
1824
+ seed: number | null;
1466
1825
  storedAtMs: number;
1467
1826
  }
1468
1827
  interface SemanticHit {
1469
1828
  record: SemanticRecord;
1470
1829
  similarity: number;
1830
+ /** Your verifier's score, or the similarity itself when no verifier is configured (see `verifierRan`). */
1471
1831
  verifierScore: number;
1832
+ /** The lower of `similarity` and `verifierScore`; a hit is served only at or above `acceptFloor`. */
1472
1833
  confidence: number;
1473
1834
  ageMs: number;
1835
+ /** Similarity a candidate had to reach to be considered at all. */
1836
+ similarityFloor: number;
1837
+ /** Confidence a candidate had to reach to be served. */
1838
+ acceptFloor: number;
1839
+ /** `built_in`: the lexical embedder shipped with the SDK. `custom`: the one you passed as `embed`. */
1840
+ embedder: "built_in" | "custom";
1841
+ /** Whether a verifier scored this hit. When false, `verifierScore` equals `similarity`. */
1842
+ verifierRan: boolean;
1474
1843
  }
1475
1844
  interface SemanticCacheOptions {
1476
1845
  /**
@@ -1519,6 +1888,7 @@ declare class InMemorySemanticCache {
1519
1888
  private readonly ttlMs;
1520
1889
  private readonly maxRecords;
1521
1890
  private readonly now;
1891
+ private readonly embebedorPropio;
1522
1892
  constructor(opts?: SemanticCacheOptions);
1523
1893
  /**
1524
1894
  * Find a safe hit for a canonical request, or null. Metadata filters run
@@ -1533,61 +1903,34 @@ declare class InMemorySemanticCache {
1533
1903
  }
1534
1904
 
1535
1905
  /**
1536
- * A LOCAL EMBEDDING FUNCTION — no network, no API key, no per-call cost.
1537
- *
1538
- * The semantic cache needs a way to compare meaning. Wiring it to a hosted
1539
- * embedding endpoint is the obvious answer and a bad default: it makes every
1540
- * miss cost a network round-trip and money, and it forces a provider choice onto
1541
- * a customer who never asked for one. So the cache shipped with no embedder at
1542
- * all, which means it never matched anything.
1543
- *
1544
- * This is the third option: a deterministic bag-of-features vector computed in
1545
- * microseconds from the text itself. It captures LEXICAL closeness — the same
1546
- * question asked with different filler words — which is the overwhelming majority
1547
- * of real repeat traffic. It does NOT capture deep semantics: "what is a CDN"
1548
- * and "explain content delivery networks" share no tokens and will not match.
1549
- *
1550
- * WHY THAT IS SAFE, AND THIS IS THE PART THAT MATTERS. A semantic cache's one
1551
- * unforgivable failure is confidently answering a NEARBY BUT DIFFERENT question.
1552
- * This vector never gets the chance: the cache runs a deterministic precision
1553
- * guard beneath the embedding — matching numbers and dates, negation parity and a
1554
- * content-overlap floor — and every isolation field (tenant, principal, model,
1555
- * schema, attached data) is a hard filter applied before similarity is scored.
1556
- * A weak embedding can only cause a MISS, which costs one real call. It cannot
1557
- * produce a wrong answer.
1558
- *
1559
- * Pass your own `embed` to {@link InMemorySemanticCache} to get true semantics.
1560
- * This one exists so that the feature is worth something with zero setup.
1561
- */
1562
- /**
1563
- * The vector for a piece of text.
1564
- *
1565
- * Features are unigrams plus adjacent bigrams: bigrams are what keep word ORDER
1566
- * from being free. Without them "does A cause B" and "does B cause A" are the
1567
- * same point in space.
1568
- *
1569
- * Term weight is sublinear (`1 + log tf`) so a word repeated forty times does not
1570
- * dominate the direction of the vector, and the result is L2-normalised so cosine
1571
- * similarity is a plain dot product.
1572
- */
1573
- declare function vectorLocal(texto: string): number[];
1574
- /**
1575
- * The default embedding function for {@link InMemorySemanticCache}.
1576
- *
1577
- * Synchronous by design — it returns the array, not a promise — so a cache lookup
1578
- * adds no await and no latency to the request path.
1579
- */
1580
- declare function embebedorLocal(texto: string): number[];
1581
- /**
1582
- * The similarity floor this embedder is calibrated for.
1906
+ * Singleflight (in-flight coalescing).
1583
1907
  *
1584
- * A lexical vector sits lower than a sentence-transformer on genuine paraphrases,
1585
- * so the 0.9 default that suits a hosted embedder would reject almost everything
1586
- * here. This value was measured on paraphrase pairs against near-miss pairs, not
1587
- * guessed — see `test/embebedor-local.test.ts`, which fails if the two
1588
- * populations stop being separable.
1908
+ * When N equivalent requests arrive at once, exactly one executes the work and
1909
+ * the other N-1 await the same promise. This is NOT a historical cache — it is
1910
+ * de-duplication of concurrent work, so it carries no staleness risk and needs
1911
+ * no TTL. On traffic spikes it can eliminate a large fraction of duplicate
1912
+ * provider calls for free.
1589
1913
  */
1590
- declare const UMBRAL_LOCAL = 0.72;
1914
+ declare class Singleflight {
1915
+ private readonly inflight;
1916
+ /** How many callers are currently sharing an in-flight execution. */
1917
+ private readonly waiters;
1918
+ /**
1919
+ * Run `fn` under `key`. If a call with the same key is already running, share
1920
+ * its result instead of starting a new one. Returns the shared result plus
1921
+ * whether this caller was a coalesced follower (a "hit").
1922
+ */
1923
+ run<T>(key: string, fn: () => Promise<T>): Promise<{
1924
+ value: T;
1925
+ coalesced: boolean;
1926
+ }>;
1927
+ /**
1928
+ * The in-flight execution under `key`, if one is running, joined as a
1929
+ * follower; `undefined` when nothing is in flight. Never starts one.
1930
+ */
1931
+ join<T>(key: string): Promise<T> | undefined;
1932
+ get activeKeys(): number;
1933
+ }
1591
1934
 
1592
1935
  /**
1593
1936
  * Deterministic resolvers.
@@ -1759,106 +2102,9 @@ declare class ToolSelector {
1759
2102
  }
1760
2103
 
1761
2104
  /**
1762
- * Retrieval planning: retrieve -> dedupe -> rerank -> adaptive-k.
1763
- *
1764
- * Eliminating irrelevant context is cheaper and safer than compressing it, so
1765
- * retrieval prunes aggressively BEFORE anything reaches the prompt. Documents
1766
- * are kept out of the prompt where possible and passed as references/hashes;
1767
- * only the selected, trimmed evidence is projected in.
1768
- */
1769
- interface EvidenceDoc {
1770
- id: string;
1771
- text: string;
1772
- /** Optional pre-computed relevance score from the retriever. */
1773
- score?: number;
1774
- source?: string;
1775
- /** Content hash, so large docs can be referenced instead of inlined. */
1776
- hash?: string;
1777
- }
1778
- interface Retriever {
1779
- retrieve(query: string, k: number): Promise<EvidenceDoc[]>;
1780
- }
1781
- interface Reranker {
1782
- rerank(query: string, docs: EvidenceDoc[]): Promise<EvidenceDoc[]>;
1783
- }
1784
- interface RetrievalPlan {
1785
- execution: ExecutionClass;
1786
- query: string;
1787
- maxRounds: number;
1788
- maxDocuments: number;
1789
- }
1790
- interface RetrievalOutcome {
1791
- docs: EvidenceDoc[];
1792
- rounds: number;
1793
- retrievedCount: number;
1794
- keptCount: number;
1795
- }
1796
- /** Remove exact and near-duplicate docs by normalized text / hash. */
1797
- declare function dedupe(docs: EvidenceDoc[]): EvidenceDoc[];
1798
- /**
1799
- * Adaptive-k: choose how many documents to keep based on the score distribution
1800
- * rather than a fixed cutoff. When the top document dominates, keep fewer; when
1801
- * scores are flat, keep up to the cap. This directly trims input tokens.
1802
- */
1803
- declare function adaptiveK(docs: EvidenceDoc[], opts?: {
1804
- maxDocuments: number;
1805
- minDocuments?: number;
1806
- }): EvidenceDoc[];
1807
- /**
1808
- * Execute a retrieval plan. NO_RETRIEVAL / TOOL_ACTION classes retrieve nothing.
1809
- * Otherwise: retrieve -> dedupe -> rerank -> adaptive-k, bounded by maxRounds.
1810
- */
1811
- declare function executeRetrievalPlan(plan: RetrievalPlan, deps: {
1812
- retriever?: Retriever;
1813
- reranker?: Reranker;
1814
- }): Promise<RetrievalOutcome>;
1815
-
1816
- /**
1817
- * Conversational memory by deltas.
1818
- *
1819
- * A major source of wasted input tokens is resending the entire chat history
1820
- * every turn. Instead we keep a compact structured state and, each turn, apply a
1821
- * small delta produced BY THE MAIN GENERATION (not a separate summarization
1822
- * call). The next prompt receives: compact relevant state + the last 1-3 raw
1823
- * turns + the current message — never the whole conversation.
1824
- */
1825
- interface ConversationState {
1826
- goals: string[];
1827
- confirmedFacts: Record<string, unknown>;
1828
- userPreferences: Record<string, unknown>;
1829
- decisions: string[];
1830
- openItems: string[];
1831
- evidenceRefs: string[];
1832
- }
1833
- interface MemoryDelta {
1834
- set?: Record<string, unknown>;
1835
- append?: Record<string, unknown[]>;
1836
- remove?: string[];
1837
- }
1838
- declare function emptyState(): ConversationState;
1839
- /**
1840
- * Merge a delta into state locally. `set` updates confirmedFacts/preferences,
1841
- * `append` pushes onto list fields, `remove` deletes fact keys or list entries.
1842
- * The merge is deterministic and side-effect-free (returns a new object).
1843
- */
1844
- declare function mergeDelta(state: ConversationState, delta: MemoryDelta): ConversationState;
1845
- /**
1846
- * Project the state + recent turns into the minimal message set to send. Only
1847
- * relevant, non-empty state is serialized, then the last `rawTurns` messages,
1848
- * then the current message.
1849
- */
1850
- declare function projectContext(opts: {
1851
- state: ConversationState;
1852
- history: ChatMessage[];
1853
- current: ChatMessage;
1854
- rawTurns?: number;
1855
- }): ChatMessage[];
1856
- declare function renderState(state: ConversationState): string;
1857
-
1858
- /**
1859
- * A serializer encodes a structured payload into prompt text, and reports the
1860
- * instruction overhead a model needs to *understand* that encoding. The format
1861
- * selector compares `tokens(encode) + tokens(instruction)` across serializers.
2105
+ * A serializer encodes a structured payload into prompt text, and reports the
2106
+ * instruction overhead a model needs to *understand* that encoding. The format
2107
+ * selector compares `tokens(encode) + tokens(instruction)` across serializers.
1862
2108
  */
1863
2109
  interface Serializer {
1864
2110
  readonly format: SerializationFormat;
@@ -1881,68 +2127,6 @@ interface Serializer {
1881
2127
  decode?(text: string): unknown;
1882
2128
  }
1883
2129
 
1884
- /**
1885
- * Compact JSON: the lossless baseline every other format is measured against.
1886
- * No indentation, no incidental whitespace. This is frequently the *winner* for
1887
- * deeply nested or non-uniform data, which is exactly why it is the baseline and
1888
- * The compact encoder is only ever a challenger.
1889
- */
1890
- declare const jsonCompactSerializer: Serializer;
1891
-
1892
- /**
1893
- * Compact row encoder.
1894
- *
1895
- * Its strength is UNIFORM ARRAYS OF FLAT OBJECTS: it declares the column
1896
- * names once and then emits one row per object, which removes the repeated keys
1897
- * that make JSON expensive for tabular data. On deeply nested or non-uniform
1898
- * data it can be *larger* than compact JSON — so this is only ever a candidate
1899
- * the selector may pick, never a default.
1900
- *
1901
- * This is an in-house, pinned encoder placed behind the {@link Serializer}
1902
- * interface (as recommended: do not couple the system to an upstream `main`).
1903
- * It emits a well-defined subset and ships a matching decoder so the JSON<->rows
1904
- * round-trip is deterministic and testable.
1905
- */
1906
- interface CompactRowsOptions {
1907
- /** Field delimiter within tabular/inline rows. */
1908
- delimiter?: "," | "\t";
1909
- /** Indentation unit. */
1910
- indent?: string;
1911
- }
1912
- declare function encodeCompactRows(value: unknown, options?: CompactRowsOptions): string;
1913
- /** Return shared column names iff every element is a flat, scalar-valued object with identical keys. */
1914
- declare function uniformColumns(arr: unknown[]): string[] | null;
1915
- /**
1916
- * The instruction a model needs to read the compact encoder. Kept short on purpose — this text
1917
- * is the per-request overhead the selector must amortize before it wins.
1918
- */
1919
- declare const COMPACT_ROWS_INSTRUCTION: string;
1920
- declare function createCompactRowsSerializer(options?: CompactRowsOptions): Serializer;
1921
- declare const compactRowsSerializer: Serializer;
1922
-
1923
- /**
1924
- * Decoder for the subset emitted by {@link encodeCompactRows}.
1925
- *
1926
- * Supports: scalar fields, nested objects, empty arrays, inline scalar arrays
1927
- * (`key[N]: a,b,c`) and tabular object arrays (`key[N]{c1,c2}:` + rows). This
1928
- * covers the cases the format selector actually chooses it for, and makes the
1929
- * JSON<->rows round-trip deterministic and testable. List-form arrays of nested
1930
- * objects are intentionally out of scope for decoding (the selector never picks
1931
- * it for those, since JSON wins there anyway).
1932
- */
1933
- interface CompactRowsDecodeOptions {
1934
- delimiter?: "," | "\t";
1935
- indent?: string;
1936
- }
1937
- declare function decodeCompactRows(text: string, options?: CompactRowsDecodeOptions): unknown;
1938
-
1939
- /**
1940
- * CSV serializer — only valid for genuinely tabular data (a uniform array of
1941
- * flat objects). CSV can be marginally smaller than the compact rows on pure tables, but it
1942
- * does NOT preserve the JSON data model in general, so `supports()` is strict.
1943
- */
1944
- declare const csvSerializer: Serializer;
1945
-
1946
2130
  /**
1947
2131
  * Format selector.
1948
2132
  *
@@ -1990,26 +2174,6 @@ declare class FormatSelector {
1990
2174
  }): FormatChoice;
1991
2175
  }
1992
2176
 
1993
- /**
1994
- * Structural validation against the JsonSchema subset the layer uses.
1995
- *
1996
- * Constrained decoding should prevent invalid structures at generation time,
1997
- * but a provider without native support can still return free text. This
1998
- * validator is the deterministic gate that decides whether a response satisfies
1999
- * its output contract — and, when it does not, whether a single repair is
2000
- * warranted. NOTE: structural validity is NOT semantic validity; risky tasks
2001
- * still need a semantic check on top.
2002
- */
2003
- interface ValidationError {
2004
- path: string;
2005
- message: string;
2006
- }
2007
- interface ValidationResult {
2008
- valid: boolean;
2009
- errors: ValidationError[];
2010
- }
2011
- declare function validateAgainstSchema(value: unknown, schema: JsonSchema, path?: string): ValidationResult;
2012
-
2013
2177
  /**
2014
2178
  * Schema registry.
2015
2179
  *
@@ -2197,6 +2361,61 @@ interface CompressionEconomics {
2197
2361
  minimumNetTokens?: number;
2198
2362
  }
2199
2363
 
2364
+ /**
2365
+ * Retrieval planning: retrieve -> dedupe -> rerank -> adaptive-k.
2366
+ *
2367
+ * Eliminating irrelevant context is cheaper and safer than compressing it, so
2368
+ * retrieval prunes aggressively BEFORE anything reaches the prompt. Documents
2369
+ * are kept out of the prompt where possible and passed as references/hashes;
2370
+ * only the selected, trimmed evidence is projected in.
2371
+ */
2372
+ interface EvidenceDoc {
2373
+ id: string;
2374
+ text: string;
2375
+ /** Optional pre-computed relevance score from the retriever. */
2376
+ score?: number;
2377
+ source?: string;
2378
+ /** Content hash, so large docs can be referenced instead of inlined. */
2379
+ hash?: string;
2380
+ }
2381
+ interface Retriever {
2382
+ retrieve(query: string, k: number): Promise<EvidenceDoc[]>;
2383
+ }
2384
+ interface Reranker {
2385
+ rerank(query: string, docs: EvidenceDoc[]): Promise<EvidenceDoc[]>;
2386
+ }
2387
+ interface RetrievalPlan {
2388
+ execution: ExecutionClass;
2389
+ query: string;
2390
+ maxRounds: number;
2391
+ maxDocuments: number;
2392
+ }
2393
+ interface RetrievalOutcome {
2394
+ docs: EvidenceDoc[];
2395
+ rounds: number;
2396
+ retrievedCount: number;
2397
+ keptCount: number;
2398
+ }
2399
+ /** Remove exact and near-duplicate docs by normalized text / hash. */
2400
+ declare function dedupe(docs: EvidenceDoc[]): EvidenceDoc[];
2401
+ /**
2402
+ * Adaptive-k: choose how many documents to keep based on the score distribution
2403
+ * rather than a fixed cutoff. When the top document dominates, keep fewer; when
2404
+ * scores are flat, keep up to the cap. This directly trims input tokens.
2405
+ */
2406
+ declare function adaptiveK(docs: EvidenceDoc[], opts?: {
2407
+ maxDocuments: number;
2408
+ minDocuments?: number;
2409
+ }): EvidenceDoc[];
2410
+ /**
2411
+ * Execute a retrieval plan. NO_RETRIEVAL / TOOL_ACTION classes retrieve nothing.
2412
+ * Otherwise: retrieve -> dedupe -> rerank -> adaptive-k, bounded by maxRounds.
2413
+ */
2414
+ declare function executeRetrievalPlan(plan: RetrievalPlan, deps: {
2415
+ retriever?: Retriever;
2416
+ reranker?: Reranker;
2417
+ }): Promise<RetrievalOutcome>;
2418
+
2200
2419
  /**
2201
2420
  * Provider-neutral generation interface. Every provider (OpenAI-compatible,
2202
2421
  * Anthropic-compatible, Google-compatible, self-hosted) is adapted to this
@@ -2243,6 +2462,12 @@ interface ProviderRequest {
2243
2462
  * {@link ProviderResponse.servedServiceTier}.
2244
2463
  */
2245
2464
  deferrable?: boolean;
2465
+ /**
2466
+ * Adapters with explicit prompt-cache control write the provider's cache only
2467
+ * at `stablePrefixMessages` (nowhere when it is undefined) instead of
2468
+ * letting the provider choose. Others ignore it.
2469
+ */
2470
+ explicitCacheWrites?: boolean;
2246
2471
  }
2247
2472
  interface ProviderUsage {
2248
2473
  inputTokens: number;
@@ -2251,7 +2476,9 @@ interface ProviderUsage {
2251
2476
  cachedInputTokens?: number;
2252
2477
  /**
2253
2478
  * Input tokens billed at a PREMIUM to WRITE the provider's prefix cache
2254
- * (Anthropic `cache_creation_input_tokens`: 1.25x the normal rate).
2479
+ * (Anthropic `cache_creation_input_tokens`, and OpenAI
2480
+ * `prompt_tokens_details.cache_write_tokens` on GPT-5.6 and later: 1.25x the
2481
+ * normal rate).
2255
2482
  *
2256
2483
  * This is a COST, not a discount, and it is reported separately for exactly
2257
2484
  * that reason: `cachedInputTokens` is a saving to publish in ledger 4, this
@@ -2259,6 +2486,27 @@ interface ProviderUsage {
2259
2486
  * bill INCREASE as a saving.
2260
2487
  */
2261
2488
  cacheWriteInputTokens?: number;
2489
+ /**
2490
+ * Output tokens the provider billed for hidden reasoning, already included in
2491
+ * `outputTokens` (OpenAI `completion_tokens_details.reasoning_tokens`).
2492
+ *
2493
+ * `0` states that the answer carried no hidden reasoning. Where the provider
2494
+ * does not break reasoning out (Anthropic's Messages API), the built-in
2495
+ * adapter reports `0` only when the answer has no `thinking` or
2496
+ * `redacted_thinking` block, and leaves this absent otherwise. Absent means
2497
+ * nobody said, which is NOT the same as 0: a repeat served by reference only
2498
+ * credits output when the call that ran reported exactly 0, because reasoning
2499
+ * tokens are neither visible nor deterministic. A custom provider should set
2500
+ * it only from its provider's own counter or an equally direct signal.
2501
+ */
2502
+ reasoningTokens?: number;
2503
+ /**
2504
+ * False when the provider did not report both counters for this call (for a
2505
+ * stream: its final usage event never arrived), so the adapter filled the
2506
+ * missing ones with 0. Absent means complete. A request with any such call is
2507
+ * reported as `unmeasured`.
2508
+ */
2509
+ complete?: boolean;
2262
2510
  }
2263
2511
  interface ProviderResponse {
2264
2512
  text: string;
@@ -2318,1109 +2566,1605 @@ interface ProviderResponse {
2318
2566
  * would be describing a request some other adapter never made.
2319
2567
  */
2320
2568
  requestedServiceTier?: string;
2569
+ /**
2570
+ * Whether this answer can be handed to a later identical request (exact cache) or to a
2571
+ * concurrent identical request. `false` when it carries content the layer keeps no copy of,
2572
+ * such as generated files, cited sources, results of tools the provider ran itself, or
2573
+ * reasoning the provider expects back: it is served to this caller only, it is not written
2574
+ * to the exact cache, and a concurrent identical request makes its own call. Absent means
2575
+ * replayable.
2576
+ */
2577
+ replayable?: boolean;
2321
2578
  raw?: unknown;
2322
2579
  }
2580
+ /** One event of a provider-level stream. */
2581
+ type ProviderStreamEvent = {
2582
+ type: "text";
2583
+ delta: string;
2584
+ } | {
2585
+ type: "done";
2586
+ response: ProviderResponse;
2587
+ };
2588
+ /** Options for {@link Provider.generate} and {@link Provider.generateStream}. */
2589
+ interface ProviderStreamOptions {
2590
+ /** Cancels the HTTP request. */
2591
+ signal?: AbortSignal;
2592
+ }
2323
2593
  interface Provider {
2324
2594
  readonly id: string;
2325
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2595
+ generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
2596
+ /**
2597
+ * Optional streaming variant of generate(). It must yield `text` events in
2598
+ * order and end with exactly one `done` whose `response` is what generate()
2599
+ * would have returned for the same answer: `response.text` equals the
2600
+ * concatenated deltas, and tool calls travel only in `response.toolCalls`.
2601
+ * When the provider's final usage event is missing, both counters are 0 and
2602
+ * `usage.complete` is false. A body that ends before the provider signalled
2603
+ * completion must throw, never yield `done`.
2604
+ */
2605
+ generateStream?(req: ProviderRequest, options?: ProviderStreamOptions): AsyncIterable<ProviderStreamEvent>;
2326
2606
  }
2327
2607
 
2328
2608
  /**
2329
- * Deterministic mock provider.
2609
+ * Offline license verification for the paid SDK.
2330
2610
  *
2331
- * Lets the whole layer run — and be tested — with no network and no
2332
- * credentials. When a schema is supplied it fabricates a schema-valid object,
2333
- * demonstrating the constrained-decoding contract end to end. Token usage is
2334
- * derived from the heuristic estimator so SavingsReport numbers are meaningful
2335
- * in examples and tests.
2611
+ * A license is a compact signed token `base64url(header).base64url(payload).
2612
+ * base64url(sig)`, the exact format BiVelio's licensing service mints. We verify
2613
+ * the Ed25519 signature against BiVelio's public key, decode the payload, and
2614
+ * apply the same grace policy the issuer applies. Verification is
2615
+ * **browser-safe**: it uses
2616
+ * `@noble/curves` (pure JS) + Web primitives (`atob`, `TextEncoder`) rather than
2617
+ * `node:crypto`, so the shared entry point still compiles into the browser
2618
+ * demo bundle. The private signing key never leaves the control plane; only the
2619
+ * public key is embedded here, so a token cannot be forged or tampered with.
2620
+ */
2621
+ /**
2622
+ * `stale` NO es una licencia caducada: es una licencia PAGADA Y VIGENTE que no ha
2623
+ * podido confirmar su estado contra el control plane dentro de la ventana de
2624
+ * frescura — típicamente porque la salida de red está bloqueada.
2336
2625
  *
2337
- * ⚠️ WHAT THIS PROVIDER CANNOT MEASURE, and it has already cost money once.
2338
- * It fabricates the answer FROM THE SCHEMA it receives, so a smaller schema
2339
- * always produces a smaller answer. Any lever that shrinks the contract sent to
2340
- * the provider therefore appears to shrink the OUTPUT too — automatically, on
2341
- * every run, whatever model id is being simulated. That is a property of this
2342
- * file, not of any model.
2626
+ * Existe porque antes ese caso reutilizaba `expired` "para que el union y todos
2627
+ * los consumidores siguieran igual", y el ahorro de tipo se pagaba con el peor
2628
+ * mensaje posible del producto: a un cliente al día, cuyo único problema era un
2629
+ * cortafuegos, se le decía *"license is not active (expired). Renew your
2630
+ * subscription"*. Le mandábamos a pagar otra vez algo que ya había pagado, y a
2631
+ * mirar donde no estaba el problema.
2343
2632
  *
2344
- * The output codebook is exactly that shape of lever, and against this provider
2345
- * the benchmark reported it saving 24.8%. Measured live it saves 8.0% on
2346
- * `gpt-4o` and COSTS 8.2% on `gpt-4o-mini`, because a real model decides how
2347
- * much to WRITE and a smaller model writes more when the field names carry less
2348
- * meaning. So: never take an output-token saving measured here as evidence of
2349
- * anything. Prove those with the paired A/B in `benchmarks/ab-live.ts`, and
2350
- * write tests for output-side effects against a provider that models the
2351
- * behaviour you are claiming — see `test/codebook-de-salida.test.ts`.
2633
+ * El comportamiento no cambia —sigue siendo fail-closed, `granted()` no lo
2634
+ * concede— pero ahora el mensaje puede decir la verdad.
2635
+ *
2636
+ * `none` means "there is no key": what is left after `normalizeLicenseKey` is
2637
+ * EMPTY. It is the same value `LicenseError.status` already carries when nothing
2638
+ * was configured, because it is the same problem with the same fix: a
2639
+ * `licenseKey: " "` (an empty environment variable, a blank secret file) used
2640
+ * to tell the customer their key "does not verify" — there was no key.
2352
2641
  */
2353
- interface MockProviderOptions {
2354
- /** Fixed text to return when no schema is present. */
2355
- answer?: string;
2356
- /** Override the fabricated structured object. */
2357
- data?: unknown;
2358
- /** Simulated provider-cached input tokens (ledger 4). */
2359
- cachedInputTokens?: number;
2642
+ type LicenseStatus = "active" | "grace" | "expired" | "invalid" | "revoked" | "stale" | "none";
2643
+ /** Decoded license token body (mirror of `@bivelio/protocol`'s LicensePayload,
2644
+ * declared locally so the SDK stays free of a control-plane dependency). */
2645
+ interface LicenseTokenPayload {
2646
+ jti: string;
2647
+ org_id: string;
2648
+ plan: string;
2649
+ seats: number;
2650
+ features: string[];
2651
+ issued_at: number;
2652
+ expires_at: number;
2653
+ grace_days: number;
2654
+ iss?: string;
2360
2655
  }
2361
- declare class MockProvider implements Provider {
2362
- readonly id = "mock";
2363
- private readonly options;
2364
- constructor(options?: MockProviderOptions);
2365
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2656
+ interface ResolvedLicense {
2657
+ status: LicenseStatus;
2658
+ payload?: LicenseTokenPayload;
2659
+ reason?: string;
2660
+ }
2661
+ declare const EMBEDDED_PUBLIC_KEY: string;
2662
+ /** Result of verifying a compact `head.body.sig` token's signature. */
2663
+ interface CompactVerification {
2664
+ /** True only if the signature is valid for the given public key. */
2665
+ ok: boolean;
2666
+ /** Decoded header (present when `ok`), e.g. `{ alg, typ, v }`. */
2667
+ header?: Record<string, unknown>;
2668
+ /** Raw payload bytes (present when `ok`); the caller decodes/parses them. */
2669
+ payloadBytes?: Uint8Array;
2670
+ reason?: string;
2366
2671
  }
2367
- declare function createMockProvider(options?: MockProviderOptions): Provider;
2368
-
2369
2672
  /**
2370
- * OpenAI-compatible provider adapter (chat/completions shape).
2673
+ * A pinned set of Ed25519 public keys addressed by key-id (`kid`), enabling signing
2674
+ * key ROTATION without breaking already-deployed SDKs: new keys are added as extra
2675
+ * map entries, and tokens carry the `kid` they were signed with. `defaultKid` is the
2676
+ * key used for legacy tokens that carry NO `kid` (so today's single-key tokens keep
2677
+ * verifying unchanged). Verification FAILS CLOSED on a `kid` not in the map.
2678
+ */
2679
+ interface LicenseKeyset {
2680
+ /** kid → SPKI PEM. */
2681
+ keys: Record<string, string>;
2682
+ /** kid used for tokens without a `kid` header (the current/legacy key). */
2683
+ defaultKid: string;
2684
+ }
2685
+ /** Narrow a verification key to a {@link LicenseKeyset} (vs a bare SPKI PEM string). */
2686
+ declare function isKeyset(k: string | LicenseKeyset): k is LicenseKeyset;
2687
+ /**
2688
+ * Normalize a license key the way it arrives from the real world — ONCE, at the
2689
+ * entry point, never inside the verifier.
2371
2690
  *
2372
- * Works with any endpoint that speaks the OpenAI Chat Completions API — OpenAI,
2373
- * Azure, vLLM, SGLang, TGI, a local gateway, or Vercel AI Gateway with a
2374
- * "provider/model" string. Structured output is requested via `response_format`
2375
- * with a JSON schema when a contract is present, which is the provider-native
2376
- * constrained-decoding path preferred by the layer.
2691
+ * The README says `export BIVELIO_LICENSE_KEY=…` and dotenv trims, so that path
2692
+ * never showed it. But anyone reading the key from a file (`readFileSync`), from
2693
+ * a Docker/Kubernetes secret (mounted as a file, trailing newline included) or
2694
+ * copying it out of an `Authorization` header handed over a CORRECT key with a
2695
+ * `\n` after it, a space before it, quotes around it or a `Bearer ` in front —
2696
+ * and the SDK answered "does not verify, check you pasted the WHOLE key". A
2697
+ * wrong diagnosis on a good key is the worst possible hint: it sends the
2698
+ * customer to look where the problem is not.
2377
2699
  *
2378
- * Models that predate structured outputs reject `response_format` with a 400. The
2379
- * adapter degrades for those: it repeats the request once with the schema carried
2380
- * in the prompt instead, remembers the model so later requests skip the rejected
2381
- * attempt, and reports `structuredNatively: false` so the savings report states
2382
- * `constraints.backend: "none"` rather than claiming a guarantee the provider
2383
- * never gave.
2700
+ * Worse, it was INCONSISTENT: `tok + "\r\n"` passed and `tok + "\n"` did not,
2701
+ * because `atob` ignores ASCII whitespace while the padding was computed from a
2702
+ * length that included it, and two extra characters give the same remainder
2703
+ * modulo 4 as zero. Behavior that depends on which editor saved the file is not
2704
+ * behavior.
2384
2705
  *
2385
- * Network calls use global `fetch` (Node >= 18) so there is no SDK dependency.
2706
+ * Only OUTER wrapping is removed — whitespace, one pair of single or double
2707
+ * quotes, a `Bearer` prefix in any capitalization — in that order, in one pass.
2708
+ * Anything INSIDE the key is left alone: an interior space is not wrapping, it
2709
+ * is a broken key, and {@link licenseKeyShape} reports it as `malformed`.
2386
2710
  */
2387
- interface OpenAICompatibleOptions {
2388
- baseUrl: string;
2389
- apiKey?: string;
2390
- /**
2391
- * Strip the `provider/` prefix from model ids before sending (`openai/gpt-4o-mini`
2392
- * → `gpt-4o-mini`).
2393
- *
2394
- * Default (undefined): strip when `baseUrl` is OpenAI's own API or an Azure
2395
- * OpenAI endpoint, keep otherwise. OpenAI rejects a prefixed id with a 404
2396
- * ("model does not exist"), so the qualified ids the layer uses everywhere
2397
- * else — pricing, cache keys, the report — would break the very first call
2398
- * that reaches the provider. A gateway or a self-hosted server with a
2399
- * namespaced model name usually WANTS the prefix, so it is kept there. Set `true`/`false` to decide explicitly; an explicit value
2400
- * always wins.
2401
- */
2402
- stripProviderPrefix?: boolean;
2403
- /** Extra headers (e.g. for a gateway). */
2404
- headers?: Record<string, string>;
2405
- /** Injectable fetch for testing. */
2406
- fetchImpl?: typeof fetch;
2407
- /**
2408
- * When true (recommended for measurement), a response whose `usage` block is
2409
- * missing or reports zero prompt/completion tokens for a call that actually
2410
- * ran is treated as a HARD ERROR instead of being silently coerced to
2411
- * 0 tokens. This closes the single most dishonest failure mode: an absent
2412
- * usage payload otherwise becomes `actual cost = 0` → a fabricated 100%
2413
- * saving. Benchmarks that make real cost claims must set this.
2414
- */
2415
- requireUsage?: boolean;
2416
- }
2711
+ declare function normalizeLicenseKey(raw: string): string;
2417
2712
  /**
2418
- * ¿Cumple este esquema las reglas del modo `strict` de OpenAI?
2713
+ * The SHAPE of an already-normalized key, so the error message can tell apart
2714
+ * three problems that used to get the same text:
2419
2715
  *
2420
- * Son dos, y aplican a TODO objeto anidado:
2421
- * 1. `additionalProperties: false`.
2422
- * 2. `required` tiene que listar TODAS las claves de `properties` — no solo
2423
- * las obligatorias. En modo strict no existe el campo opcional.
2716
+ * · `empty` — there is no key (only whitespace, or nothing at all).
2717
+ * · `malformed` — not even the shape of a token: not three dot-separated
2718
+ * base64url segments. This is where the `bvk_` service key
2719
+ * pasted where the license goes lands, as does arbitrary text
2720
+ * or a token with a line break in the middle.
2721
+ * · `token` — has the shape; whether it verifies is up to the signature.
2424
2722
  *
2425
- * Un esquema perfectamente válido como
2426
- * { properties: { answer, refs }, required: ["answer"] }
2427
- * incumple la segunda y el proveedor responde 400. No es un fallo del cliente:
2428
- * es una restricción de OpenAI que JSON Schema no tiene.
2723
+ * It is the SAME shape rule {@link verifyCompactSignature} enforces, so that "has
2724
+ * the shape" and "verification was attempted" mean the same thing.
2429
2725
  */
2430
- declare function isStrictCompatible(schema: unknown): boolean;
2726
+ type LicenseKeyShape = "empty" | "malformed" | "token";
2727
+ /** Classify a normalized key. See {@link LicenseKeyShape}. */
2728
+ declare function licenseKeyShape(normalizedKey: string): LicenseKeyShape;
2431
2729
  /**
2432
- * Decides the id that travels on the wire. Explicit option wins; otherwise the
2433
- * endpoint decides: OpenAI/Azure get the bare id, everything else keeps what
2434
- * the caller wrote. Exported for the pipeline-level test that pins the README
2435
- * snippet; not part of the public API.
2730
+ * Verify the Ed25519 signature of any BV-SALA compact token
2731
+ * (`base64url(header).base64url(body).base64url(sig)`, sig over `${head}.${body}`)
2732
+ * and return the decoded header + raw payload bytes. This is the single shared
2733
+ * crypto primitive: license tokens AND signed revocation lists both go through it,
2734
+ * so the browser (`@noble/curves`) and node (`node:crypto`) sides agree byte-for-byte
2735
+ * and there is no bespoke per-message verification path to keep in sync. It does NOT
2736
+ * interpret the payload or the header `typ` — callers do that.
2737
+ *
2738
+ * Shape is checked SEGMENT by segment, not just by counting dots. Three parts
2739
+ * used to be enough and the rest was left to `atob`, which ignores whitespace:
2740
+ * that is how a token with `\r\n` after it verified while the same token with
2741
+ * `\n` did not (see {@link normalizeLicenseKey}). The shared primitive must give
2742
+ * the same answer to the same input, always; a caller that wants to tolerate
2743
+ * wrapping normalizes BEFORE calling.
2436
2744
  */
2437
- declare function quitarPrefijoDeProveedor(model: string, explicito: boolean | undefined, baseUrl: string): string;
2438
- declare class OpenAICompatibleProvider implements Provider {
2439
- readonly id: string;
2440
- private readonly opts;
2441
- private readonly fetchImpl;
2442
- /** Models of this endpoint already known to reject `response_format`. */
2443
- private readonly sinResponseFormat;
2444
- constructor(opts: OpenAICompatibleOptions);
2445
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2446
- /** Build the request body and post it. `nativo` selects the `response_format` path. */
2447
- private enviar;
2448
- /** Translate a 2xx endpoint response into the provider-neutral shape. */
2449
- private leer;
2450
- }
2451
-
2745
+ declare function verifyCompactSignature(token: string, publicKey: string | LicenseKeyset): CompactVerification;
2746
+ /** Verify a token's signature and decode its payload. Does not check expiry. */
2747
+ declare function verifyLicenseToken(token: string, publicKey: string | LicenseKeyset): {
2748
+ signatureValid: boolean;
2749
+ payload?: LicenseTokenPayload;
2750
+ reason?: string;
2751
+ };
2452
2752
  /**
2453
- * Anthropic Messages API adapter.
2454
- *
2455
- * Speaks `POST /v1/messages`: the system prompt travels in its own top-level
2456
- * `system` field (not as a message), tools are `{name, description,
2457
- * input_schema}` with no `function` wrapper, and prefix caching is requested
2458
- * with EXPLICIT `cache_control` breakpoints — Anthropic does not cache on its
2459
- * own. A cached read is billed at 0.1x the input rate, which is the largest
2460
- * single lever on the input side of a customer's bill.
2753
+ * Apply the grace + revocation policy to an already-decoded payload → a status.
2754
+ * Pure and clock-injected, so a LIVE gate can call it on every request to pick up
2755
+ * both revocation (jti in the set) AND expiry transitions without re-verifying the
2756
+ * signature. Revocation takes precedence over the time-based status.
2757
+ * jti on the revocation set → revoked
2758
+ * now <= expires_at → active
2759
+ * expires_at < now <= +grace_days → grace (still entitled)
2760
+ * now > expires_at + grace_days → expired
2761
+ */
2762
+ declare function deriveStatus(payload: {
2763
+ jti: string;
2764
+ expires_at: number;
2765
+ grace_days: number;
2766
+ }, nowSeconds: number, revoked?: ReadonlySet<string>): LicenseStatus;
2767
+ /**
2768
+ * Verify + apply the grace policy → a resolved status. Mirrors what the issuing
2769
+ * control plane resolves for the same token. Bad signature / malformed
2770
+ * → invalid; otherwise the status comes from {@link deriveStatus}.
2461
2771
  *
2462
- * Network calls use global `fetch` (Node >= 18) so there is no SDK dependency.
2772
+ * A public entry point, so it normalizes like the others (gate and refresher):
2773
+ * the same key must resolve the same whichever way it comes in.
2463
2774
  */
2464
- /** Render order of the request, which is also the order the cache prefix grows in. */
2465
- interface AnthropicOptions {
2466
- apiKey?: string;
2467
- /** Defaults to the public API. Override for a gateway or a proxy. */
2468
- baseUrl?: string;
2775
+ declare function resolveLicense(token: string, publicKey: string | LicenseKeyset, opts?: {
2776
+ nowSeconds?: number;
2777
+ revoked?: ReadonlySet<string>;
2778
+ }): ResolvedLicense;
2779
+
2780
+ /**
2781
+ * Everything the pipeline depends on. All fields except `provider` and
2782
+ * `tokenizer` are optional; sensible in-memory defaults are wired by
2783
+ * {@link createSavingsLayer} so the layer works out of the box and every
2784
+ * component is independently swappable.
2785
+ */
2786
+ interface SavingsLayerConfig {
2469
2787
  /**
2470
- * Strip the `provider/` prefix from model ids before sending
2471
- * (`anthropic/claude-sonnet-4-5` → `claude-sonnet-4-5`). Default (undefined):
2472
- * strip when talking to `api.anthropic.com`, which rejects a prefixed id;
2473
- * keep for a gateway or proxy, which usually routes on it. An explicit value
2474
- * always wins.
2788
+ * Contratos a los que ya se les incumplió una restricción de valor.
2789
+ *
2790
+ * La capa lo aprende sola, pero lo aprendido muere con la instancia. En
2791
+ * serverless —una instancia por petición— eso significa no aprender NUNCA:
2792
+ * cada llamada estrena y paga su reparación. Pasar aquí un conjunto
2793
+ * compartido (o respaldado por Redis, con la API de Set) hace que se aprenda
2794
+ * una vez para todo el despliegue.
2795
+ *
2796
+ * Solo guarda identificadores de contrato, nunca contenido de peticiones.
2475
2797
  */
2476
- stripProviderPrefix?: boolean;
2477
- /** Wire version pinned in the `anthropic-version` header. */
2478
- apiVersion?: string;
2479
- /** Extra headers (e.g. for a gateway). */
2480
- headers?: Record<string, string>;
2481
- /** Injectable fetch for testing. */
2482
- fetchImpl?: typeof fetch;
2798
+ constraintMemory?: Set<string>;
2799
+ provider: Provider;
2800
+ tokenizer: TokenizerRegistry;
2801
+ exactCache?: ExactCache;
2802
+ semanticCache?: InMemorySemanticCache;
2803
+ singleflight?: Singleflight;
2804
+ resolvers?: ResolverRegistry;
2805
+ router?: ExecutionRouter;
2806
+ toolSelector?: ToolSelector;
2807
+ formatSelector?: FormatSelector;
2808
+ schemaRegistry?: SchemaRegistry;
2809
+ constraintBackends?: ConstraintBackend[];
2810
+ compressor?: Compressor;
2483
2811
  /**
2484
- * `max_tokens` is REQUIRED by this API — unlike the OpenAI shape, where it is
2485
- * optional. A request without one is a 400, so the adapter has to supply a
2486
- * value. It is deliberately generous: a low default would truncate answers
2487
- * and the truncation would look like a model failure rather than an adapter
2488
- * default.
2812
+ * The numbers behind "compress only when it pays for itself" — in money, not
2813
+ * in tokens.
2814
+ *
2815
+ * All optional, and the default never invents a saving: the layer compresses
2816
+ * when the net token saving justifies it, and holds back when compressing
2817
+ * would push the prompt below the provider's cacheable minimum — where fewer
2818
+ * tokens can mean a larger bill. Declare {@link CompressionEconomics.prefix}
2819
+ * with your own measurements to unlock the full decision in both directions.
2489
2820
  */
2490
- defaultMaxOutputTokens?: number;
2821
+ compressionEconomics?: CompressionEconomics;
2822
+ retriever?: Retriever;
2823
+ reranker?: Reranker;
2491
2824
  /**
2492
- * When true (recommended for measurement), a response whose `usage` block is
2493
- * missing its counters is treated as a HARD ERROR instead of being coerced to
2494
- * 0 tokens. An absent usage payload otherwise becomes `actual cost = 0` → a
2495
- * fabricated 100% saving.
2825
+ * Tuning for the model cascade (`savings.modelCascade`). All optional; the
2826
+ * defaults measure and never spend.
2496
2827
  */
2497
- requireUsage?: boolean;
2828
+ modelCascade?: ModelCascadeSettings;
2498
2829
  /**
2499
- * How many `cache_control` breakpoints the adapter may spend.
2830
+ * Layer-wide defaults for the provider price levers (`savings.deferrable`,
2831
+ * `savings.standalone`) and what to do when the cheaper tier has no capacity.
2832
+ * All off by default; a request's own `savings` value always wins.
2833
+ */
2834
+ priceLevers?: PriceLeverSettings;
2835
+ /**
2836
+ * Signed BiVelio license token (from your paid subscription). This is the
2837
+ * primary way to license the SDK: `createSavingsLayer` verifies it offline
2838
+ * against the embedded public key and builds a {@link licenseGate} from it.
2839
+ * Without a valid, active license (and without {@link devUnlicensed}), the
2840
+ * pipeline refuses to run — get a key at https://savings.bivelio.com.
2500
2841
  *
2501
- * The API caps them at 4 per request. The adapter never needs more than 2
2502
- * (end of system, end of the stable message prefix) and the cap is exposed so
2503
- * a gateway with a stricter limit can lower it. Raising it above 4 is
2504
- * pointless: the extra breakpoints are rejected by the API, not ignored.
2842
+ * Surrounding whitespace (the trailing newline of a file or mounted secret),
2843
+ * wrapping quotes and a `Bearer ` prefix are stripped before verification, so
2844
+ * a key read with `readFileSync` works as-is. Anything INSIDE the key is not
2845
+ * touched: a key with a space in the middle is a broken key.
2505
2846
  */
2506
- maxCacheBreakpoints?: number;
2847
+ licenseKey?: string;
2507
2848
  /**
2508
- * When true, requests that the pipeline decided to mark also carry the
2509
- * request-level `cache_control` (Anthropic's automatic caching): the provider
2510
- * places one extra breakpoint on the last cacheable block and moves it
2511
- * forward as the conversation grows, so the history beyond the declared
2512
- * stable prefix is cached incrementally too. It spends one of the four
2513
- * breakpoint slots and never fires when the economic gate declined to mark.
2849
+ * Override the public key used to verify {@link licenseKey}. An SPKI PEM string,
2850
+ * or a kid-addressed {@link LicenseKeyset} (`{ keys: {kid: pem}, defaultKid }`)
2851
+ * to support signing-key rotation — legacy no-kid tokens use `defaultKid`, and
2852
+ * an unknown kid fails closed. For self-hosting/self-testing; defaults to the
2853
+ * build-embedded BiVelio key.
2854
+ */
2855
+ licensePublicKey?: string | LicenseKeyset;
2856
+ /**
2857
+ * Pre-built license gate. **Ignored in the published npm package**, where a
2858
+ * {@link licenseKey} verified against the embedded BiVelio public key is the
2859
+ * only accepted license — passing a gate there logs a warning and changes
2860
+ * nothing. It exists for builds from source (this repo's own tests and
2861
+ * benchmarks), where it takes precedence over {@link licenseKey}.
2862
+ */
2863
+ licenseGate?: LicenseGate;
2864
+ /**
2865
+ * OPT-IN online revocation call-home. When set (alongside {@link licenseKey} and
2866
+ * with no explicit {@link licenseGate}), the SDK polls this base's
2867
+ * `GET /licenses/revocations` for a signed revocation list and enforces it via a
2868
+ * LIVE gate — a license cancelled mid-term stops working without a restart.
2514
2869
  *
2515
- * Off by default, and deliberately so: the moving breakpoint writes the
2516
- * volatile tail of EVERY marked request at the 1.25x write premium. That
2517
- * pays for itself on conversational or agentic traffic — the next turn reads
2518
- * the whole history at 0.1x — and loses money on one-shot traffic where
2519
- * nothing ever reads it back. It is also rejected by legacy Bedrock
2520
- * deployments. Turn it on when your traffic is multi-turn.
2870
+ * NON-BLOCKING: the customer request path never blocks on the fetch; the warm poll
2871
+ * is fired but not awaited, and any failed/unreachable poll is a no-op that keeps
2872
+ * the last-known-good snapshot. Leaving this unset keeps the fully-offline,
2873
+ * zero-network default path. Pass the base URL of the licensing service, e.g.
2874
+ * `https://savings.bivelio.com`.
2521
2875
  */
2522
- automaticCache?: boolean;
2876
+ licenseServerUrl?: string;
2523
2877
  /**
2524
- * Whether a request carrying BOTH the caller's tools and an output contract
2525
- * may send `tool_choice: {type: "any"}`, which obliges the model to answer
2526
- * through SOME tool — its pick — instead of writing prose.
2878
+ * Steady-state ceiling between revocation polls, in seconds (default 900 = 15 min).
2879
+ * The effective cadence tightens near license expiry and has a hard floor.
2880
+ */
2881
+ licensePollIntervalSeconds?: number;
2882
+ /** Injectable `fetch` for the revocation poller (defaults to the global). Testing/proxy. */
2883
+ licenseFetch?: typeof fetch;
2884
+ /** Advanced tuning of the revocation poller cadence + skew tolerance. */
2885
+ licenseRevocation?: {
2886
+ /** Hard floor between polls, seconds (default 300). */
2887
+ floorSeconds?: number;
2888
+ /** Backoff ceiling during a control-plane outage, seconds (default 3600). */
2889
+ maxIntervalSeconds?: number;
2890
+ /** Jitter fraction ± (default 0.1). */
2891
+ jitterRatio?: number;
2892
+ /** Reject a signed list whose `asOf` is more than this far in the future, seconds (default 300). */
2893
+ maxSkewSeconds?: number;
2894
+ /**
2895
+ * ONLINE staleness ceiling, seconds (default 72h, `DEFAULT_MAX_STALENESS_SECONDS`). If the SDK goes this long
2896
+ * without a CONFIRMED authorized heartbeat (a 304 or a verified list), the LIVE
2897
+ * gate stops granting — closing the fail-open window where blocking egress keeps
2898
+ * a revoked license alive until natural expiry. Must exceed the poll cadence
2899
+ * with margin. Applies ONLY on the online path ({@link licenseServerUrl} set);
2900
+ * it is NOT a zero-network opt-out — use the offline path for that.
2901
+ */
2902
+ maxStalenessSeconds?: number;
2903
+ };
2904
+ /**
2905
+ * Service-account key (`bvk_…`) — your machine credential. It does TWO things,
2906
+ * and the second one is why a long-running deployment needs it:
2527
2907
  *
2528
- * Defaults to `true`, and the default is the point: without it this exact
2529
- * combination measured 62% MORE expensive than sending no layer at all, every
2530
- * single time, because prose forces a second full call to repair. Turning it
2531
- * off restores the old behaviour for a caller who genuinely needs a plain-text
2532
- * turn to be reachable while a contract is attached.
2908
+ * 1. **Licence renewal pickup.** Each billing period mints a NEW licence token;
2909
+ * the one you deployed expires at the end of the period (+ grace). With this
2910
+ * key the SDK fetches the renewed token from the control plane and swaps it
2911
+ * in without a restart. WITHOUT it there is no machine pickup, so somebody
2912
+ * must paste the new token by hand every cycle or the deployment stops with
2913
+ * `LicenseError` once the grace period ends — while the subscription is paid
2914
+ * and current. Disable with {@link licenseRefresh}`.enabled = false`.
2915
+ * 2. **DETECTIVE usage metering.** Usage figures after each request —
2916
+ * request id, model id, cache outcome, token counts and cost figures, tagged
2917
+ * with an opaque machine seat id (section 5 of the LICENSE lists every field) —
2918
+ * NEVER prompts, completions or provider credentials. Disable with
2919
+ * {@link metering}`.enabled = false` (which does NOT disable renewal pickup).
2920
+ *
2921
+ * Both are best-effort and non-blocking: a slow or unreachable control plane
2922
+ * never blocks or breaks `generate()`. Get the key from your dashboard.
2533
2923
  */
2534
- forceAnswerThroughTool?: boolean;
2535
- }
2536
- declare class AnthropicProvider implements Provider {
2537
- readonly id: string;
2538
- private readonly opts;
2539
- private readonly fetchImpl;
2540
- constructor(opts?: AnthropicOptions);
2541
- /** The id Anthropic's API sees: bare at `api.anthropic.com`, as written elsewhere. */
2542
- private modeloEnElCable;
2543
- generate(req: ProviderRequest): Promise<ProviderResponse>;
2544
- private construirCuerpo;
2545
- private marcarCache;
2546
- /** Traduce una respuesta 2xx a la forma neutra del layer. */
2547
- private leer;
2548
- }
2549
-
2550
- /**
2551
- * Trace collector: an ordered log of pipeline decisions with elapsed timings.
2552
- * Every stage records why it did what it did, so a SavingsReport can be audited
2553
- * and so ablations can attribute savings to a specific module.
2554
- */
2555
- declare class TraceCollector {
2556
- private readonly events;
2557
- private readonly start;
2558
- private readonly now;
2559
- constructor(now?: () => number);
2560
- record(stage: string, outcome: string, detail?: Record<string, unknown>): void;
2561
- elapsed(): number;
2562
- all(): TraceEvent[];
2563
- }
2564
-
2565
- /**
2566
- * Thrown when the pipeline is asked to run without a valid, active BiVelio
2567
- * license. This is the fail-closed contract: a paid SDK must refuse to operate
2568
- * unlicensed, rather than silently serving requests for free.
2569
- */
2570
- declare class LicenseError extends Error {
2924
+ serviceAccountKey?: string;
2571
2925
  /**
2572
- * Resolved license status — the stable handle for branching on the cause,
2573
- * since the message text is rewritten whenever it can be made clearer:
2926
+ * Automatic licence-renewal pickup (on by default when {@link serviceAccountKey}
2927
+ * and {@link licenseServerUrl} are both set).
2574
2928
  *
2575
- * - `none` — nothing was configured (no `licenseKey`, no gate).
2576
- * - `invalid` — a key was configured but does not verify.
2577
- * - `expired` / `grace` — it verifies, but the term is over.
2578
- * - `revoked` — cancelled by the control plane.
2579
- * - `stale` — paid and current, but unconfirmed: a connectivity problem.
2929
+ * Turning it OFF means you take responsibility for deploying the renewed token
2930
+ * each billing period; if nobody does, the SDK stops at the end of the grace
2931
+ * period even though the subscription is paid.
2932
+ */
2933
+ licenseRefresh?: {
2934
+ enabled?: boolean;
2935
+ /** Check cadence in ms (default 3600000 = 1 h). */
2936
+ intervalMs?: number;
2937
+ /** How long before expiry to start looking, seconds (default 7 days). */
2938
+ leadSeconds?: number;
2939
+ };
2940
+ /**
2941
+ * Usage-ingest URL. Defaults to `${licenseServerUrl}/api/metering/usage`. Set
2942
+ * explicitly to point metering at a different host than the revocation poller.
2943
+ */
2944
+ meteringUrl?: string;
2945
+ /** Injectable `fetch` for metering (defaults to {@link licenseFetch}, then global). */
2946
+ meteringFetch?: typeof fetch;
2947
+ /** DETECTIVE metering tuning (all optional). */
2948
+ metering?: {
2949
+ /**
2950
+ * Off switch for USAGE REPORTING even when {@link serviceAccountKey} is set
2951
+ * (default: on). It does NOT disable licence-renewal pickup — that has its
2952
+ * own switch, {@link licenseRefresh}`.enabled`, because turning off telemetry
2953
+ * must not silently stop your licence from renewing.
2954
+ */
2955
+ enabled?: boolean;
2956
+ /** Flush cadence in ms (default 15000). */
2957
+ flushIntervalMs?: number;
2958
+ /** Records per POST (default 50). */
2959
+ maxBatch?: number;
2960
+ /** Hard in-memory cap; oldest records dropped past it (default 1000). */
2961
+ maxBuffer?: number;
2962
+ /** Override the auto-derived machine seat id. */
2963
+ seatId?: string;
2964
+ /** Override CI auto-detection (CI/ephemeral seats are excluded from billable seats). */
2965
+ ci?: boolean;
2966
+ /** Optional org hint; the server derives the authoritative org from the key. */
2967
+ orgId?: string;
2968
+ };
2969
+ /** Opt-in A/B verification on your own traffic. Off by default. See VerificationSettings. */
2970
+ verification?: VerificationSettings;
2971
+ /**
2972
+ * Development-only escape hatch to run ungated, honored ONLY when building from
2973
+ * source (local dev, CI, benchmarks) — also via the `BVSALA_DEV_OPEN=1`
2974
+ * environment variable.
2580
2975
  *
2581
- * The same value is repeated at the end of the message (`license status: X`)
2582
- * because an uncaught error prints nothing else.
2976
+ * IMPORTANT: this flag is **disabled in the published npm package**. The
2977
+ * distributed build hard-codes the licensing check on, so setting
2978
+ * `devUnlicensed` (or `BVSALA_DEV_OPEN`) there has NO effect — the layer still
2979
+ * requires a valid `licenseKey`. It is not a way to run the product for free.
2583
2980
  */
2584
- readonly status?: string;
2585
- constructor(message: string, status?: string);
2586
- }
2587
-
2588
- /**
2589
- * Offline license verification for the paid SDK.
2590
- *
2591
- * A license is a compact signed token `base64url(header).base64url(payload).
2592
- * base64url(sig)`, the exact format BiVelio's licensing service mints. We verify
2593
- * the Ed25519 signature against BiVelio's public key, decode the payload, and
2594
- * apply the same grace policy the issuer applies. Verification is
2595
- * **browser-safe**: it uses
2596
- * `@noble/curves` (pure JS) + Web primitives (`atob`, `TextEncoder`) rather than
2597
- * `node:crypto`, so the shared entry point still compiles into the browser
2598
- * demo bundle. The private signing key never leaves the control plane; only the
2599
- * public key is embedded here, so a token cannot be forged or tampered with.
2600
- */
2981
+ devUnlicensed?: boolean;
2982
+ /** Per-model cost models; falls back to `defaultCostModel`. */
2983
+ costModels?: Record<string, CostModel>;
2984
+ defaultCostModel?: CostModel;
2985
+ /** TTL for exact-cache writes, in ms. */
2986
+ exactCacheTtlMs?: number;
2987
+ /**
2988
+ * Refuse the single repair retry when paying for it would push this request's
2989
+ * cost above what it would have cost WITHOUT the layer. The invalid answer is
2990
+ * then returned as-is with `constraints.structurallyValid: false`, plus a
2991
+ * warning naming the three figures.
2992
+ *
2993
+ * OFF by default, and the default is a deliberate, measured choice rather than
2994
+ * caution — read this before turning it on.
2995
+ *
2996
+ * The bound sounds narrow ("only the worst case") and is not. The baseline of
2997
+ * a call that ran is reconstructed EXCLUDING the repair (see `estimateCost`:
2998
+ * `actualInput - repairInput`), precisely so a retry cannot pay for itself in
2999
+ * the report. That makes the threshold unreachable by construction: the repair
3000
+ * only ever moves `actual`, never the baseline, so ANY repair breaches it
3001
+ * whenever the layer's other books came out flat. On a request where nothing
3002
+ * was compressed, cached or eliminated, `baseline === actual` exactly and the
3003
+ * gate suppresses every repair, however cheap.
3004
+ *
3005
+ * So this is not "cap the tail", it is "stop repairing unless the layer has
3006
+ * already banked more than the repair will cost". That is a real quality
3007
+ * policy, not a rounding rule, and it is the caller's contract to decide on —
3008
+ * hence opt-in.
3009
+ *
3010
+ * There is no threshold that fires on the pathological case and spares the
3011
+ * flat one; they are the same arithmetic at different magnitudes. And there is
3012
+ * a genuine argument on the other side: an answer that misses its contract is
3013
+ * a MODEL failure, so the no-layer path would have had to pay for its own
3014
+ * second call too — charging the whole retry against the layer compares one
3015
+ * valid answer to one invalid answer.
3016
+ *
3017
+ * Turn it on when a hard per-request ceiling matters more than contract
3018
+ * compliance — a batch backfill, a cost-capped tenant. Leave it off when the
3019
+ * caller relies on the shape being right.
3020
+ */
3021
+ skipRepairAboveBaseline?: boolean;
3022
+ /**
3023
+ * Reliability contract. When true (the default), ANY internal optimizer error
3024
+ * (a resolver, cache, format selector, compressor, or accounting bug) is
3025
+ * caught and the request falls open to a plain provider call — BV-SALA must
3026
+ * never add a crash point to the customer's request path. Set to `false` only
3027
+ * in tests/benchmarks that want internal errors to surface.
3028
+ */
3029
+ failOpen?: boolean;
3030
+ /**
3031
+ * Silence BV-SALA's console warnings. They are ON by default, and they only
3032
+ * ever report a condition whose symptom is otherwise invisible: a saving knob
3033
+ * asked for but not wired up, usage that stops reaching the control plane, a
3034
+ * revocation list going stale. Nothing here throws and nothing here changes
3035
+ * what the layer does — turning it on only removes the console noise, so set
3036
+ * it when you have your own observability. Applies to the pipeline, the usage
3037
+ * emitter and the revocation poller alike.
3038
+ */
3039
+ silenceWarnings?: boolean;
3040
+ /**
3041
+ * Load the exact OpenAI tokenizer (`gpt-tokenizer`, optional peer) into the
3042
+ * default registry when it is installed. ON by default: without it, every
3043
+ * saving where the layer removed something is reported as `estimated` and
3044
+ * never billed, and the SDK warns once. Set to `false` to keep the built-in
3045
+ * heuristic even when the package is present (tests calibrated on the
3046
+ * heuristic, or a process that must not spend the encoding's memory). Ignored
3047
+ * when you pass your own `tokenizer` registry: you decide what it contains.
3048
+ */
3049
+ exactTokenizer?: boolean;
3050
+ /** Injectable clock for deterministic tests. */
3051
+ now?: () => number;
3052
+ /** Injectable id generator for deterministic tests. */
3053
+ generateId?: () => string;
3054
+ }
2601
3055
  /**
2602
- * `stale` NO es una licencia caducada: es una licencia PAGADA Y VIGENTE que no ha
2603
- * podido confirmar su estado contra el control plane dentro de la ventana de
2604
- * frescura — típicamente porque la salida de red está bloqueada.
2605
- *
2606
- * Existe porque antes ese caso reutilizaba `expired` "para que el union y todos
2607
- * los consumidores siguieran igual", y el ahorro de tipo se pagaba con el peor
2608
- * mensaje posible del producto: a un cliente al día, cuyo único problema era un
2609
- * cortafuegos, se le decía *"license is not active (expired). Renew your
2610
- * subscription"*. Le mandábamos a pagar otra vez algo que ya había pagado, y a
2611
- * mirar donde no estaba el problema.
2612
- *
2613
- * El comportamiento no cambia —sigue siendo fail-closed, `granted()` no lo
2614
- * concede— pero ahora el mensaje puede decir la verdad.
2615
- *
2616
- * `none` means "there is no key": what is left after `normalizeLicenseKey` is
2617
- * EMPTY. It is the same value `LicenseError.status` already carries when nothing
2618
- * was configured, because it is the same problem with the same fix: a
2619
- * `licenseKey: " "` (an empty environment variable, a blank secret file) used
2620
- * to tell the customer their key "does not verify" — there was no key.
3056
+ * Tuning for the model cascade. Everything here is optional, and the defaults
3057
+ * cost nothing: with no settings at all, `modelCascade: "shadow"` measures what
3058
+ * it can measure for free and makes no extra provider call.
2621
3059
  */
2622
- type LicenseStatus = "active" | "grace" | "expired" | "invalid" | "revoked" | "stale" | "none";
2623
- /** Decoded license token body (mirror of `@bivelio/protocol`'s LicensePayload,
2624
- * declared locally so the SDK stays free of a control-plane dependency). */
2625
- interface LicenseTokenPayload {
2626
- jti: string;
2627
- org_id: string;
2628
- plan: string;
2629
- seats: number;
2630
- features: string[];
2631
- issued_at: number;
2632
- expires_at: number;
2633
- grace_days: number;
2634
- iss?: string;
2635
- }
2636
- interface ResolvedLicense {
2637
- status: LicenseStatus;
2638
- payload?: LicenseTokenPayload;
2639
- reason?: string;
2640
- }
2641
- declare const EMBEDDED_PUBLIC_KEY: string;
2642
- /** Result of verifying a compact `head.body.sig` token's signature. */
2643
- interface CompactVerification {
2644
- /** True only if the signature is valid for the given public key. */
2645
- ok: boolean;
2646
- /** Decoded header (present when `ok`), e.g. `{ alg, typ, v }`. */
2647
- header?: Record<string, unknown>;
2648
- /** Raw payload bytes (present when `ok`); the caller decodes/parses them. */
2649
- payloadBytes?: Uint8Array;
2650
- reason?: string;
3060
+ interface ModelCascadeSettings {
3061
+ /**
3062
+ * Your own strong → cheap pairs, replacing the built-in table.
3063
+ *
3064
+ * Keys and values are model ids WITHOUT a vendor prefix (`"gpt-4o"`, not
3065
+ * `"openai/gpt-4o"`); the caller's prefix is carried over automatically, and a
3066
+ * dated snapshot inherits from its base id. A pair whose economics do not work
3067
+ * out at your prices is refused with a reason in the report — declaring it here
3068
+ * does not override the arithmetic.
3069
+ */
3070
+ pairs?: Record<string, string>;
3071
+ /**
3072
+ * How many requests of a workload are routed before the automatic shut-off
3073
+ * judges the measured escalation rate (default 20).
3074
+ *
3075
+ * The rate is only observable on requests that were actually routed, so there
3076
+ * is no way to know without spending a bounded amount to find out. Worst case
3077
+ * (every one of them escalates) that exploration costs `n x r` requests'
3078
+ * worth of extra spend, once per workload per process — with a ratio of 0.06
3079
+ * and the default, about 1.2 requests.
3080
+ */
3081
+ exploration?: number;
3082
+ /**
3083
+ * PRICED OPT-IN, default 0 (off). Fraction of `"shadow"` requests on which the
3084
+ * layer also calls the cheap model, purely to measure.
3085
+ *
3086
+ * Shadow mode is free precisely because it does not run the cheap model: it
3087
+ * reports eligibility, the arithmetic, and what the swap would have been worth.
3088
+ * What it cannot know for free is the one number that decides everything — how
3089
+ * often the deterministic gate would have REJECTED the cheap answer. That
3090
+ * answer does not exist until the cheap model writes one.
3091
+ *
3092
+ * So this buys it, and the price is exact: each sampled request costs one extra
3093
+ * cheap call, i.e. `r` of what the request already cost — about 6% for
3094
+ * gpt-4o → gpt-4o-mini. At `0.05` that is roughly 0.3% on top of your bill, and
3095
+ * every sampled request reports its own `modelCascade.sampleCost`. It also
3096
+ * DOUBLES that request's provider latency, because the extra call is sequential.
3097
+ *
3098
+ * The sampled request's tokens count as spend and are held out of the baseline,
3099
+ * exactly like the repair retry: measurement must never pay for itself.
3100
+ *
3101
+ * Which requests get sampled is derived from the request's own cache key, so it
3102
+ * is deterministic and stable — the same request is always sampled or always
3103
+ * not, and no `Math.random` decides how your money is spent.
3104
+ */
3105
+ shadowSampleRate?: number;
2651
3106
  }
2652
3107
  /**
2653
- * A pinned set of Ed25519 public keys addressed by key-id (`kid`), enabling signing
2654
- * key ROTATION without breaking already-deployed SDKs: new keys are added as extra
2655
- * map entries, and tokens carry the `kid` they were signed with. `defaultKid` is the
2656
- * key used for legacy tokens that carry NO `kid` (so today's single-key tokens keep
2657
- * verifying unchanged). Verification FAILS CLOSED on a `kid` not in the map.
3108
+ * Defaults for the provider price levers. Everything here is off unless you set it,
3109
+ * and a request's own `savings` value always wins over it.
2658
3110
  */
2659
- interface LicenseKeyset {
2660
- /** kid → SPKI PEM. */
2661
- keys: Record<string, string>;
2662
- /** kid used for tokens without a `kid` header (the current/legacy key). */
2663
- defaultKid: string;
3111
+ interface PriceLeverSettings {
3112
+ /** Default for `savings.deferrable` on every request of this layer. A request's own value wins. Defaults to false. */
3113
+ deferrable?: boolean;
3114
+ /** Default for `savings.standalone`. A request's own value wins. Defaults to false. */
3115
+ standalone?: boolean;
3116
+ /**
3117
+ * When a deferrable request finds no capacity on the cheaper tier (HTTP 429 or 503):
3118
+ * "throw" (default) surfaces the error unchanged; "standard" repeats it once at the
3119
+ * standard tier and price.
3120
+ */
3121
+ onCapacityError?: "throw" | "standard";
2664
3122
  }
2665
- /** Narrow a verification key to a {@link LicenseKeyset} (vs a bare SPKI PEM string). */
2666
- declare function isKeyset(k: string | LicenseKeyset): k is LicenseKeyset;
3123
+ declare const DEFAULT_COST_MODEL: CostModel;
3124
+
2667
3125
  /**
2668
- * Normalize a license key the way it arrives from the real world — ONCE, at the
2669
- * entry point, never inside the verifier.
2670
- *
2671
- * The README says `export BIVELIO_LICENSE_KEY=…` and dotenv trims, so that path
2672
- * never showed it. But anyone reading the key from a file (`readFileSync`), from
2673
- * a Docker/Kubernetes secret (mounted as a file, trailing newline included) or
2674
- * copying it out of an `Authorization` header handed over a CORRECT key with a
2675
- * `\n` after it, a space before it, quotes around it or a `Bearer ` in front —
2676
- * and the SDK answered "does not verify, check you pasted the WHOLE key". A
2677
- * wrong diagnosis on a good key is the worst possible hint: it sends the
2678
- * customer to look where the problem is not.
2679
- *
2680
- * Worse, it was INCONSISTENT: `tok + "\r\n"` passed and `tok + "\n"` did not,
2681
- * because `atob` ignores ASCII whitespace while the padding was computed from a
2682
- * length that included it, and two extra characters give the same remainder
2683
- * modulo 4 as zero. Behavior that depends on which editor saved the file is not
2684
- * behavior.
2685
- *
2686
- * Only OUTER wrapping is removed — whitespace, one pair of single or double
2687
- * quotes, a `Bearer` prefix in any capitalization — in that order, in one pass.
2688
- * Anything INSIDE the key is left alone: an interior space is not wrapping, it
2689
- * is a broken key, and {@link licenseKeyShape} reports it as `malformed`.
3126
+ * Options shared by every framework adapter. Each one has the same meaning as in
3127
+ * `createSavingsLayer`; the adapter supplies the provider itself, which is the
3128
+ * framework's own model call, forwarded unchanged.
2690
3129
  */
2691
- declare function normalizeLicenseKey(raw: string): string;
3130
+ type SavingsAdapterOptions = Pick<SavingsLayerConfig, "licenseKey" | "licenseServerUrl" | "serviceAccountKey" | "licenseRefresh" | "licenseRevocation" | "licensePollIntervalSeconds" | "licenseFetch" | "metering" | "meteringUrl" | "meteringFetch" | "costModels" | "defaultCostModel" | "exactCache" | "exactCacheTtlMs" | "resolvers" | "exactTokenizer" | "silenceWarnings" | "failOpen"> & {
3131
+ /**
3132
+ * Tools without side effects, by name, or a function that says whether a tool
3133
+ * has none. Any other tool is treated as having side effects: a request that
3134
+ * offers it is never answered from a cache and never shares an answer with an
3135
+ * identical request in flight, because the action has to run.
3136
+ */
3137
+ readOnlyTools?: readonly string[] | ((toolName: string) => boolean);
3138
+ /**
3139
+ * Switch off, for every call, the levers that answer without calling the
3140
+ * model. All three are on by default.
3141
+ */
3142
+ savings?: SavingsAdapterLevers;
3143
+ /**
3144
+ * Called once per model call with its report. An error thrown here is caught
3145
+ * and never reaches the call.
3146
+ */
3147
+ onReport?: (report: SavingsReport) => void;
3148
+ };
2692
3149
  /**
2693
- * The SHAPE of an already-normalized key, so the error message can tell apart
2694
- * three problems that used to get the same text:
2695
- *
2696
- * · `empty` — there is no key (only whitespace, or nothing at all).
2697
- * · `malformed` — not even the shape of a token: not three dot-separated
2698
- * base64url segments. This is where the `bvk_` service key
2699
- * pasted where the license goes lands, as does arbitrary text
2700
- * or a token with a line break in the middle.
2701
- * · `token` — has the shape; whether it verifies is up to the signature.
2702
- *
2703
- * It is the SAME shape rule {@link verifyCompactSignature} enforces, so that "has
2704
- * the shape" and "verification was attempted" mean the same thing.
3150
+ * The levers an adapter can use, all of them answers given without calling the
3151
+ * model: an exact repeat, an identical request already in flight, and a
3152
+ * computed answer (arithmetic). They can only be switched off.
2705
3153
  */
2706
- type LicenseKeyShape = "empty" | "malformed" | "token";
2707
- /** Classify a normalized key. See {@link LicenseKeyShape}. */
2708
- declare function licenseKeyShape(normalizedKey: string): LicenseKeyShape;
3154
+ type SavingsAdapterLevers = Pick<SavingsToggles, "exactCache" | "singleflight" | "deterministicResolvers">;
3155
+ /** Per-call options, where the framework has a place to pass them. */
3156
+ interface SavingsCallOptions {
3157
+ /** Cache identity for this call (tenant, session…). It wins over the adapter's `context` option. */
3158
+ context?: RequestContext;
3159
+ /** Switch off levers for this call only. */
3160
+ savings?: SavingsAdapterLevers;
3161
+ }
3162
+
2709
3163
  /**
2710
- * Verify the Ed25519 signature of any BV-SALA compact token
2711
- * (`base64url(header).base64url(body).base64url(sig)`, sig over `${head}.${body}`)
2712
- * and return the decoded header + raw payload bytes. This is the single shared
2713
- * crypto primitive: license tokens AND signed revocation lists both go through it,
2714
- * so the browser (`@noble/curves`) and node (`node:crypto`) sides agree byte-for-byte
2715
- * and there is no bespoke per-message verification path to keep in sync. It does NOT
2716
- * interpret the payload or the header `typ` — callers do that.
3164
+ * Tokenizador EXACTO — la regla del proveedor, no una aproximación.
2717
3165
  *
2718
- * Shape is checked SEGMENT by segment, not just by counting dots. Three parts
2719
- * used to be enough and the rest was left to `atob`, which ignores whitespace:
2720
- * that is how a token with `\r\n` after it verified while the same token with
2721
- * `\n` did not (see {@link normalizeLicenseKey}). The shared primitive must give
2722
- * the same answer to the same input, always; a caller that wants to tolerate
2723
- * wrapping normalizes BEFORE calling.
3166
+ * Por qué existe. El baseline de una llamada que se ejecutó se reconstruye como
3167
+ * "lo enviado + lo quitado". Lo enviado lo mide el proveedor; lo quitado lo
3168
+ * contamos aquí. Con el estimador heurístico esos dos sumandos vienen de reglas
3169
+ * distintas, y la diferencia NO es ruido: el heurístico cobra cada símbolo suelto
3170
+ * como un token entero, así que infla el JSON (40% de símbolos) mucho más que el
3171
+ * An estimator that is not calibrated for the destination model can miss by
3172
+ * enough to publish a baseline several times the real one, which would credit a
3173
+ * saving that never existed. That is why `basis` degrades instead of claiming.
2724
3174
  */
2725
- declare function verifyCompactSignature(token: string, publicKey: string | LicenseKeyset): CompactVerification;
2726
- /** Verify a token's signature and decode its payload. Does not check expiry. */
2727
- declare function verifyLicenseToken(token: string, publicKey: string | LicenseKeyset): {
2728
- signatureValid: boolean;
2729
- payload?: LicenseTokenPayload;
2730
- reason?: string;
2731
- };
3175
+
3176
+ /** Lo que necesitamos de gpt-tokenizer. Tipado aquí para no depender de sus tipos. */
3177
+ interface BpeEncoder {
3178
+ encode(text: string): number[] | {
3179
+ length: number;
3180
+ };
3181
+ }
2732
3182
  /**
2733
- * Apply the grace + revocation policy to an already-decoded payload → a status.
2734
- * Pure and clock-injected, so a LIVE gate can call it on every request to pick up
2735
- * both revocation (jti in the set) AND expiry transitions without re-verifying the
2736
- * signature. Revocation takes precedence over the time-based status.
2737
- * jti on the revocation set → revoked
2738
- * now <= expires_at → active
2739
- * expires_at < now <= +grace_days → grace (still entitled)
2740
- * now > expires_at + grace_days → expired
3183
+ * Estimador exacto sobre una codificación BPE ya cargada.
3184
+ *
3185
+ * Se construye con el encoder ya resuelto en vez de importarlo dentro: así
3186
+ * este módulo no obliga a nadie a tener el paquete, y quien lo tiene decide qué
3187
+ * codificación carga (o200k_base para gpt-4o/4.1/5, cl100k_base para 3.5/4).
2741
3188
  */
2742
- declare function deriveStatus(payload: {
2743
- jti: string;
2744
- expires_at: number;
2745
- grace_days: number;
2746
- }, nowSeconds: number, revoked?: ReadonlySet<string>): LicenseStatus;
3189
+ declare class ExactTokenizer implements TokenEstimator {
3190
+ private readonly encoder;
3191
+ readonly id: string;
3192
+ readonly exact = true;
3193
+ constructor(id: string, encoder: BpeEncoder);
3194
+ count(text: string): number;
3195
+ }
3196
+ /** Modelos que usan cada codificación. Sirve para mapear el registro de una vez. */
3197
+ declare const MODELS_BY_ENCODING: Readonly<Record<string, readonly string[]>>;
2747
3198
  /**
2748
- * Verify + apply the grace policy → a resolved status. Mirrors what the issuing
2749
- * control plane resolves for the same token. Bad signature / malformed
2750
- * → invalid; otherwise the status comes from {@link deriveStatus}.
3199
+ * Carga `gpt-tokenizer` si está instalado y registra el tokenizador exacto para
3200
+ * los modelos de OpenAI. Devuelve `false` —sin lanzar— cuando no está: la
3201
+ * ausencia del paquete opcional no es un error, solo significa que la
3202
+ * contabilidad se publicará como estimada.
2751
3203
  *
2752
- * A public entry point, so it normalizes like the others (gate and refresher):
2753
- * the same key must resolve the same whichever way it comes in.
3204
+ * Se importa solo la codificación pedida, no el paquete entero: `gpt-tokenizer`
3205
+ * expone un punto de entrada por codificación y cargarlas todas serían decenas
3206
+ * de MB en memoria sin motivo.
2754
3207
  */
2755
- declare function resolveLicense(token: string, publicKey: string | LicenseKeyset, opts?: {
2756
- nowSeconds?: number;
2757
- revoked?: ReadonlySet<string>;
2758
- }): ResolvedLicense;
3208
+ declare function registerExactTokenizer(registro: {
3209
+ register(e: TokenEstimator): unknown;
3210
+ mapModel(model: string, tokenizerId: string): unknown;
3211
+ }, opciones?: {
3212
+ codificaciones?: readonly string[];
3213
+ }): Promise<boolean>;
3214
+ /**
3215
+ * @deprecated Spanish-named aliases kept for one minor version. Use
3216
+ * {@link MODELS_BY_ENCODING} and {@link registerExactTokenizer}. The public
3217
+ * surface of this package is English-only; these leaked and are on the way out.
3218
+ */
3219
+ declare const MODELOS_POR_CODIFICACION: Readonly<Record<string, readonly string[]>>;
3220
+ /** @deprecated Use {@link registerExactTokenizer}. */
3221
+ declare const registrarTokenizadorExacto: typeof registerExactTokenizer;
2759
3222
 
2760
- /** Decoded revocation-list body (mirror of @bivelio/protocol's RevocationListPayload). */
2761
- interface RevocationListBody {
2762
- v: number;
2763
- asOf: number;
2764
- seq: number;
2765
- revoked: string[];
2766
- iss?: string;
2767
- }
2768
- interface RevocationListVerification {
2769
- /** True only if signature + typ + monotonic-seq + skew all pass. */
2770
- ok: boolean;
2771
- /** List revision (monotonic). -1 when not ok. */
2772
- seq: number;
2773
- /** Unix seconds the list was minted. 0 when not ok. */
2774
- asOf: number;
2775
- /** Revoked jti in this list (empty when not ok). */
2776
- revoked: Set<string>;
2777
- reason?: string;
2778
- }
2779
3223
  /**
2780
- * Verify a signed revocation-list token offline and enforce the client-side
2781
- * policy: (1) valid Ed25519 signature, (2) header `typ` is a revocation list
2782
- * (a license token can't be replayed here), (3) `seq >= minSeq` — reject a
2783
- * stale/rolled-back list, (4) `asOf <= now + maxSkew` — reject a future-dated list.
2784
- * ANY failure returns `ok:false` with an empty set; the poller then keeps its
2785
- * sticky last-known-good (fail-open), never dropping known revocations.
3224
+ * Exact-cache key.
3225
+ *
3226
+ * A hash of the user text alone is NOT a safe cache key. Two requests may share
3227
+ * text but differ in tenant, permission scope, model version, system prompt,
3228
+ * schema, toolset, locale, sampling, knowledge epoch or safety policy — any of
3229
+ * which can change the correct answer. The exact key includes all of them.
3230
+ *
3231
+ * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, not the one the
3232
+ * caller named, and that distinction is what stops the model cascade from
3233
+ * poisoning the cache. Routing to a cheap model and storing its answer under the
3234
+ * STRONG model's key would hand that answer to the next request — one with the
3235
+ * cascade off, or from another process entirely — while the report claims the
3236
+ * requested model served it. That is a silent model swap, deferred, which is the
3237
+ * worst kind of silent model swap.
3238
+ *
3239
+ * `modelFamily` is derived from whichever id is used, for the same reason: it is
3240
+ * part of the identity of whoever answers.
2786
3241
  */
2787
- declare function verifyRevocationList(token: string, publicKey: string | LicenseKeyset, opts?: {
2788
- minSeq?: number;
2789
- nowSeconds?: number;
2790
- maxSkewSeconds?: number;
2791
- }): RevocationListVerification;
3242
+ declare function buildExactKey(c: CanonicalRequest, opciones?: {
3243
+ servedModel?: string;
3244
+ }): string;
2792
3245
  /**
2793
- * The SDK's live, sticky, monotonic view of the revocation set. It only ever
2794
- * GROWS (a jti, once seen revoked, stays revoked for the process lifetime) and
2795
- * only advances on a higher-or-equal `seq` — so a stale, rolled-back, or empty
2796
- * list can never un-revoke a license. The live gate reads {@link snapshot} on
2797
- * every request; the poller feeds it via {@link apply}.
3246
+ * In-flight (singleflight) key. Coalesces concurrent, equivalent requests onto
3247
+ * a single execution. It can be slightly looser than the exact key (it does not
3248
+ * need freshness bucketing, since the work is happening *now*), but must still
3249
+ * isolate tenants and permissions.
3250
+ *
3251
+ * `schema` is REQUIRED here, even though `contract` is already part of the key:
3252
+ * two concurrent calls that differ only in `schemaVersion` are different
3253
+ * requests and must never coalesce. All three doors — exact key, in-flight key
3254
+ * and semantic filter — key on the same identity, by design.
3255
+ *
3256
+ * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, for the same reason
3257
+ * {@link buildExactKey} does, and this door is the one that leaks FIRST. The
3258
+ * exact key protects the answer AFTER it is stored; this one protects it while
3259
+ * it is still being written, and a follower does not re-run anything — it is
3260
+ * handed the leader's text verbatim.
3261
+ *
3262
+ * The leak it closes, measured: two concurrent requests for the same text, one
3263
+ * with `savings.modelCascade: "on"` and one with the cascade OFF, coalesced onto
3264
+ * one execution. The routed leader called the cheap model, and the caller who
3265
+ * never opted in — whose own decision was "do not route" — was served the cheap
3266
+ * model's answer. Same shape inside a single uniformly-configured deployment:
3267
+ * the automatic shut-off can latch between two concurrent requests, so one is
3268
+ * routed and the other is not, and coalescing hands one of them the other's
3269
+ * model. Whoever the layer decided must go to the strong model has to actually
3270
+ * go there.
3271
+ *
3272
+ * `deferrable` CLOSES THE SAME HOLE ON THE LATENCY AXIS, which is why it belongs
3273
+ * here and NOT in {@link buildExactKey}. The two doors do not risk the same
3274
+ * thing: the exact key hands out an answer that is ALREADY WRITTEN, where the
3275
+ * processing tier only picked a queue and changes neither the text nor the wait
3276
+ * of whoever receives it. This one binds a follower to a call that HAS NOT COME
3277
+ * BACK YET, so it imposes the leader's wait on them.
3278
+ *
3279
+ * A caller who never set `savings.deferrable` must not be made to wait for one
3280
+ * who did.
2798
3281
  */
2799
- declare class RevocationSnapshot {
2800
- private _seq;
2801
- private _asOf;
2802
- private _lastContactAt;
2803
- private readonly set;
2804
- get seq(): number;
2805
- get asOf(): number;
2806
- /** Unix seconds of the last CONFIRMED authorized heartbeat (a 304 or a verified
2807
- * list). Feeds the live gate's online staleness check; 0 until the first one. */
2808
- get lastContactAt(): number;
2809
- /** Record a confirmed authorized heartbeat from the control plane (monotonic). */
2810
- markContact(nowSeconds: number): void;
2811
- has(jti: string): boolean;
2812
- /** Live view of the revoked set (the same Set instance — read-only to callers). */
2813
- snapshot(): ReadonlySet<string>;
3282
+ declare function buildInflightKey(c: CanonicalRequest, opciones?: {
3283
+ servedModel?: string;
3284
+ deferrable?: boolean;
3285
+ toolDiscovery?: boolean;
3286
+ maxOutputTokens?: number;
3287
+ }): string;
3288
+
3289
+ /**
3290
+ * Policy engine: risk and freshness.
3291
+ *
3292
+ * This is the gate that decides which savings are *safe* for a given request.
3293
+ * The most dangerous failure of a savings layer is not a cache miss — it is
3294
+ * confidently returning a wrong answer (a stale price, another tenant's data, a
3295
+ * reused answer for a side-effecting action). The policy engine exists to make
3296
+ * those outcomes structurally impossible, not merely unlikely.
3297
+ */
3298
+ interface Policy {
3299
+ /** May the exact cache be read/written for this request? */
3300
+ allowExactCache: boolean;
3301
+ /** May the semantic cache even be *considered*? (still verified later) */
3302
+ allowSemanticCache: boolean;
3303
+ /** May concurrent equivalent requests be coalesced? */
3304
+ allowSingleflight: boolean;
3305
+ /** May we run natural-language compression on the context? */
3306
+ allowCompression: boolean;
2814
3307
  /**
2815
- * Merge an already-verified list. Accepts only `seq >= current seq`; UNIONS the
2816
- * jti (never removes); advances seq/asOf monotonically. Returns whether the
2817
- * revoked set grew or the revision advanced.
3308
+ * May the SDK elide old, large tool_results before resending? Computed, not
3309
+ * hardcoded like `allowCompression`: eliding drops whole tool outputs, so it
3310
+ * honors the same vetoes as the semantic cache — write tools, freshness,
3311
+ * personalization, stochastic sampling and `day`.
2818
3312
  */
2819
- apply(list: {
2820
- seq: number;
2821
- asOf: number;
2822
- revoked: Iterable<string>;
2823
- }): boolean;
2824
- }
2825
-
2826
- /** A resolved gate that also exposes overall status + a refresh horizon. */
2827
- interface ResolvedGate extends LicenseGate {
2828
- isActive(): boolean;
2829
- status(): LicenseStatus;
3313
+ allowElision: boolean;
3314
+ /** Does the request demand current/live data? */
3315
+ requiresFreshData: boolean;
3316
+ /** Does the request have (or request) side effects? */
3317
+ hasSideEffects: boolean;
3318
+ /** Is the response personalized to a user/account? */
3319
+ isPersonalized: boolean;
2830
3320
  /**
2831
- * Why the key does not open, when it does not; `undefined` when it verified.
2832
- * Under `status() === "invalid"` it tells a key that does NOT EVEN HAVE THE
2833
- * SHAPE of a token (`"malformed"`: the `bvk_` pasted where the license goes,
2834
- * arbitrary text) from a well-formed one whose signature fails (the verifier's
2835
- * reason, e.g. `"bad signature"`). Both are `invalid` — the stable handle for
2836
- * branching does not change — but the advice given to the customer differs.
3321
+ * Is this agentic traffic? True when the request declares tools (read-only
3322
+ * ones included), carries tool calls or tool results, or holds more than one
3323
+ * user turn.
3324
+ *
3325
+ * Agentic traffic is kept out of the semantic cache by default, and only by
3326
+ * default: `savings.semanticCache: "include-agentic"` lets it in. Two runs of
3327
+ * an agent loop can differ only in what a tool returned, and the
3328
+ * deterministic guard compares figures, negation and word overlap, not
3329
+ * whether a tool said something else in words. The exact cache and elision
3330
+ * are not affected by this flag.
2837
3331
  */
2838
- reason(): string | undefined;
2839
- /** Unix seconds after which the online status should be re-checked (poller anchor). */
2840
- refreshAfter?(): number | undefined;
3332
+ isAgentic: boolean;
3333
+ /**
3334
+ * Techo de antigüedad para una entrada de caché exacta, derivado del
3335
+ * `freshnessBucket`. `null` = sin techo propio (manda el TTL por defecto).
3336
+ * Existe porque el bucket NO particiona la clave —va crudo al hash y no rota—,
3337
+ * así que sin esto "hour" y "day" no significaban nada operativamente.
3338
+ */
3339
+ maxCacheAgeMs: number | null;
3340
+ reasons: string[];
2841
3341
  }
3342
+ declare function evaluatePolicy(c: CanonicalRequest): Policy;
3343
+
2842
3344
  /**
2843
- * Build a LIVE license gate from a signed key. The token is verified + decoded
2844
- * ONCE (no per-request Ed25519 verify — hot-path safe); each `isActive()` /
2845
- * `status()` / `isEntitled()` then re-derives the status from the cached payload +
2846
- * the LIVE clock + the (mutable, sticky) {@link RevocationSnapshot}. This is what
2847
- * lets a revocation — or an expiry crossed mid-process — take effect without a
2848
- * restart, while the customer request path never touches the network (it reads an
2849
- * in-memory Set).
3345
+ * A LOCAL EMBEDDING FUNCTION — no network, no API key, no per-call cost.
2850
3346
  *
2851
- * The offline path passes a fresh, never-updated snapshot (live clock, zero
2852
- * network); the online path passes a snapshot a {@link RevocationPoller} feeds.
2853
- */
2854
- declare function liveGateFromKey(licenseKey: string, publicKey: string | LicenseKeyset, opts: {
2855
- revocation: RevocationSnapshot;
2856
- now?: () => number;
2857
- /** Online staleness ceiling, seconds. If the gate goes this long without a
2858
- * confirmed authorized heartbeat, it stops granting (closes the block-egress
2859
- * fail-open). Undefined = check disabled (the offline path never sets it). */
2860
- maxStalenessSeconds?: number;
2861
- /** Freshness anchor (unix seconds); defaults to construction time (boot). */
2862
- startedAt?: number;
2863
- }): ResolvedGate;
3347
+ * The semantic cache needs a way to compare meaning. Wiring it to a hosted
3348
+ * embedding endpoint is the obvious answer and a bad default: it makes every
3349
+ * miss cost a network round-trip and money, and it forces a provider choice onto
3350
+ * a customer who never asked for one. So the cache shipped with no embedder at
3351
+ * all, which means it never matched anything.
3352
+ *
3353
+ * This is the third option: a deterministic bag-of-features vector computed in
3354
+ * microseconds from the text itself. It captures LEXICAL closeness — the same
3355
+ * question asked with different filler words — which is the overwhelming majority
3356
+ * of real repeat traffic. It does NOT capture deep semantics: "what is a CDN"
3357
+ * and "explain content delivery networks" share no tokens and will not match.
3358
+ *
3359
+ * WHY THAT IS SAFE, AND THIS IS THE PART THAT MATTERS. A semantic cache's one
3360
+ * unforgivable failure is confidently answering a NEARBY BUT DIFFERENT question.
3361
+ * This vector never gets the chance: the cache runs a deterministic precision
3362
+ * guard beneath the embedding — matching numbers and dates, negation parity and a
3363
+ * content-overlap floor — and every isolation field (tenant, principal, model,
3364
+ * schema, attached data) is a hard filter applied before similarity is scored.
3365
+ * A weak embedding can only cause a MISS, which costs one real call. It cannot
3366
+ * produce a wrong answer.
3367
+ *
3368
+ * Pass your own `embed` to {@link InMemorySemanticCache} to get true semantics.
3369
+ * This one exists so that the feature is worth something with zero setup.
3370
+ */
3371
+ /**
3372
+ * The vector for a piece of text.
3373
+ *
3374
+ * Features are unigrams plus adjacent bigrams: bigrams are what keep word ORDER
3375
+ * from being free. Without them "does A cause B" and "does B cause A" are the
3376
+ * same point in space.
3377
+ *
3378
+ * Term weight is sublinear (`1 + log tf`) so a word repeated forty times does not
3379
+ * dominate the direction of the vector, and the result is L2-normalised so cosine
3380
+ * similarity is a plain dot product.
3381
+ */
3382
+ declare function vectorLocal(texto: string): number[];
3383
+ /**
3384
+ * The default embedding function for {@link InMemorySemanticCache}.
3385
+ *
3386
+ * Synchronous by design — it returns the array, not a promise — so a cache lookup
3387
+ * adds no await and no latency to the request path.
3388
+ */
3389
+ declare function embebedorLocal(texto: string): number[];
3390
+ /**
3391
+ * The similarity floor this embedder is calibrated for.
3392
+ *
3393
+ * A lexical vector sits lower than a sentence-transformer on genuine paraphrases,
3394
+ * so the 0.9 default that suits a hosted embedder would reject almost everything
3395
+ * here. This value was measured on paraphrase pairs against near-miss pairs, not
3396
+ * guessed — see `test/embebedor-local.test.ts`, which fails if the two
3397
+ * populations stop being separable.
3398
+ */
3399
+ declare const UMBRAL_LOCAL = 0.72;
2864
3400
 
2865
3401
  /**
2866
- * Build the SDK's structural {@link import("../contracts/types.js").LicenseGate}
2867
- * from a signed license key — the object `createSavingsLayer({ licenseKey })`
2868
- * injects on the OFFLINE path (no `licenseServerUrl`).
3402
+ * Conversational memory by deltas.
2869
3403
  *
2870
- * It is a LIVE gate over an EMPTY, never-updated revocation snapshot: zero network,
2871
- * but the clock is LIVE, so a license that crosses its expiry (past grace) stops
2872
- * working mid-process instead of only after a restart — closing the frozen-clock
2873
- * hole the old one-shot resolution left open. `isActive()` is true while active or
2874
- * in grace; `isEntitled` additionally requires the feature in the token's grant.
2875
- * Online revocation is added by pairing {@link liveGateFromKey} with a
2876
- * poller-fed snapshot, which `createSavingsLayer` wires up for you.
3404
+ * A major source of wasted input tokens is resending the entire chat history
3405
+ * every turn. Instead we keep a compact structured state and, each turn, apply a
3406
+ * small delta produced BY THE MAIN GENERATION (not a separate summarization
3407
+ * call). The next prompt receives: compact relevant state + the last 1-3 raw
3408
+ * turns + the current message — never the whole conversation.
2877
3409
  */
2878
- declare function gateFromKey(licenseKey: string, publicKey: string | LicenseKeyset, now?: () => number): ResolvedGate;
3410
+ interface ConversationState {
3411
+ goals: string[];
3412
+ confirmedFacts: Record<string, unknown>;
3413
+ userPreferences: Record<string, unknown>;
3414
+ decisions: string[];
3415
+ openItems: string[];
3416
+ evidenceRefs: string[];
3417
+ }
3418
+ interface MemoryDelta {
3419
+ set?: Record<string, unknown>;
3420
+ append?: Record<string, unknown[]>;
3421
+ remove?: string[];
3422
+ }
3423
+ declare function emptyState(): ConversationState;
3424
+ /**
3425
+ * Merge a delta into state locally. `set` updates confirmedFacts/preferences,
3426
+ * `append` pushes onto list fields, `remove` deletes fact keys or list entries.
3427
+ * The merge is deterministic and side-effect-free (returns a new object).
3428
+ */
3429
+ declare function mergeDelta(state: ConversationState, delta: MemoryDelta): ConversationState;
3430
+ /**
3431
+ * Project the state + recent turns into the minimal message set to send. Only
3432
+ * relevant, non-empty state is serialized, then the last `rawTurns` messages,
3433
+ * then the current message.
3434
+ */
3435
+ declare function projectContext(opts: {
3436
+ state: ConversationState;
3437
+ history: ChatMessage[];
3438
+ current: ChatMessage;
3439
+ rawTurns?: number;
3440
+ }): ChatMessage[];
3441
+ declare function renderState(state: ConversationState): string;
2879
3442
 
2880
- type RevocationPollEvent = {
2881
- type: "applied";
2882
- seq: number;
2883
- size: number;
2884
- } | {
2885
- type: "unchanged";
2886
- seq: number;
2887
- } | {
2888
- type: "rejected";
2889
- reason: string;
2890
- } | {
2891
- type: "error";
2892
- error: unknown;
2893
- } | {
2894
- type: "no-fetch";
2895
- };
2896
- interface RevocationPollerOptions {
2897
- /** Absolute URL of GET /licenses/revocations. */
2898
- url: string;
2899
- /** SPKI PEM (or a kid-addressed {@link LicenseKeyset}) used to verify the signed
2900
- * list — the same key/keyset that verifies license tokens. */
2901
- publicKeyPem: string | LicenseKeyset;
2902
- /** The sticky snapshot to feed. */
2903
- snapshot: RevocationSnapshot;
2904
- /** Anchor cadence to the license refresh horizon (unix seconds). */
2905
- getRefreshAfter?: () => number | undefined;
2906
- /** Injectable clock (unix seconds). */
2907
- nowSeconds?: () => number;
2908
- /** Injectable fetch (defaults to the global). */
2909
- fetchImpl?: typeof fetch;
2910
- pollIntervalSeconds?: number;
2911
- floorSeconds?: number;
2912
- maxIntervalSeconds?: number;
2913
- jitterRatio?: number;
2914
- maxSkewSeconds?: number;
2915
- requestTimeoutMs?: number;
2916
- /** Injectable RNG in [0,1) for jitter. Default Math.random. */
2917
- random?: () => number;
2918
- /** Optional observer (telemetry/tests). */
2919
- onEvent?: (e: RevocationPollEvent) => void;
2920
- /**
2921
- * Silencia los avisos por consola. Por defecto están ENCENDIDOS, y ese defecto
2922
- * es la corrección: el sondeo es fail-open, así que un call-home roto no rompe
2923
- * nada visible — la lista de revocaciones simplemente se queda vieja, en
2924
- * silencio, durante días. Quien tenga observabilidad propia puede apagarlos;
2925
- * quien no la tenga, que es casi todo el mundo, se entera igual.
2926
- */
2927
- silenceWarnings?: boolean;
2928
- /**
2929
- * @deprecated Spanish name that leaked into the public options. Use
2930
- * {@link RevocationPollerOptions.silenceWarnings}. Honoured for one minor
2931
- * version so nobody gets their warnings back by surprise.
3443
+ /**
3444
+ * Compact JSON: the lossless baseline every other format is measured against.
3445
+ * No indentation, no incidental whitespace. This is frequently the *winner* for
3446
+ * deeply nested or non-uniform data, which is exactly why it is the baseline and
3447
+ * The compact encoder is only ever a challenger.
3448
+ */
3449
+ declare const jsonCompactSerializer: Serializer;
3450
+
3451
+ /**
3452
+ * Compact row encoder.
3453
+ *
3454
+ * Its strength is UNIFORM ARRAYS OF FLAT OBJECTS: it declares the column
3455
+ * names once and then emits one row per object, which removes the repeated keys
3456
+ * that make JSON expensive for tabular data. On deeply nested or non-uniform
3457
+ * data it can be *larger* than compact JSON — so this is only ever a candidate
3458
+ * the selector may pick, never a default.
3459
+ *
3460
+ * This is an in-house, pinned encoder placed behind the {@link Serializer}
3461
+ * interface (as recommended: do not couple the system to an upstream `main`).
3462
+ * It emits a well-defined subset and ships a matching decoder so the JSON<->rows
3463
+ * round-trip is deterministic and testable.
3464
+ */
3465
+ interface CompactRowsOptions {
3466
+ /** Field delimiter within tabular/inline rows. */
3467
+ delimiter?: "," | "\t";
3468
+ /** Indentation unit. */
3469
+ indent?: string;
3470
+ }
3471
+ declare function encodeCompactRows(value: unknown, options?: CompactRowsOptions): string;
3472
+ /** Return shared column names iff every element is a flat, scalar-valued object with identical keys. */
3473
+ declare function uniformColumns(arr: unknown[]): string[] | null;
3474
+ /**
3475
+ * The instruction a model needs to read the compact encoder. Kept short on purpose — this text
3476
+ * is the per-request overhead the selector must amortize before it wins.
3477
+ */
3478
+ declare const COMPACT_ROWS_INSTRUCTION: string;
3479
+ declare function createCompactRowsSerializer(options?: CompactRowsOptions): Serializer;
3480
+ declare const compactRowsSerializer: Serializer;
3481
+
3482
+ /**
3483
+ * Decoder for the subset emitted by {@link encodeCompactRows}.
3484
+ *
3485
+ * Supports: scalar fields, nested objects, empty arrays, inline scalar arrays
3486
+ * (`key[N]: a,b,c`) and tabular object arrays (`key[N]{c1,c2}:` + rows). This
3487
+ * covers the cases the format selector actually chooses it for, and makes the
3488
+ * JSON<->rows round-trip deterministic and testable. List-form arrays of nested
3489
+ * objects are intentionally out of scope for decoding (the selector never picks
3490
+ * it for those, since JSON wins there anyway).
3491
+ */
3492
+ interface CompactRowsDecodeOptions {
3493
+ delimiter?: "," | "\t";
3494
+ indent?: string;
3495
+ }
3496
+ declare function decodeCompactRows(text: string, options?: CompactRowsDecodeOptions): unknown;
3497
+
3498
+ /**
3499
+ * CSV serializer — only valid for genuinely tabular data (a uniform array of
3500
+ * flat objects). CSV can be marginally smaller than the compact rows on pure tables, but it
3501
+ * does NOT preserve the JSON data model in general, so `supports()` is strict.
3502
+ */
3503
+ declare const csvSerializer: Serializer;
3504
+
3505
+ /**
3506
+ * Structural validation against the JsonSchema subset the layer uses.
3507
+ *
3508
+ * Constrained decoding should prevent invalid structures at generation time,
3509
+ * but a provider without native support can still return free text. This
3510
+ * validator is the deterministic gate that decides whether a response satisfies
3511
+ * its output contract — and, when it does not, whether a single repair is
3512
+ * warranted. NOTE: structural validity is NOT semantic validity; risky tasks
3513
+ * still need a semantic check on top.
3514
+ */
3515
+ interface ValidationError {
3516
+ path: string;
3517
+ message: string;
3518
+ }
3519
+ interface ValidationResult {
3520
+ valid: boolean;
3521
+ errors: ValidationError[];
3522
+ }
3523
+ declare function validateAgainstSchema(value: unknown, schema: JsonSchema, path?: string): ValidationResult;
3524
+
3525
+ /**
3526
+ * Deterministic mock provider.
3527
+ *
3528
+ * Lets the whole layer run — and be tested — with no network and no
3529
+ * credentials. When a schema is supplied it fabricates a schema-valid object,
3530
+ * demonstrating the constrained-decoding contract end to end. Token usage is
3531
+ * derived from the heuristic estimator so SavingsReport numbers are meaningful
3532
+ * in examples and tests.
3533
+ *
3534
+ * ⚠️ WHAT THIS PROVIDER CANNOT MEASURE, and it has already cost money once.
3535
+ * It fabricates the answer FROM THE SCHEMA it receives, so a smaller schema
3536
+ * always produces a smaller answer. Any lever that shrinks the contract sent to
3537
+ * the provider therefore appears to shrink the OUTPUT too — automatically, on
3538
+ * every run, whatever model id is being simulated. That is a property of this
3539
+ * file, not of any model.
3540
+ *
3541
+ * The output codebook is exactly that shape of lever, and against this provider
3542
+ * the benchmark reported it saving 24.8%. Measured live it saves 8.0% on
3543
+ * `gpt-4o` and COSTS 8.2% on `gpt-4o-mini`, because a real model decides how
3544
+ * much to WRITE and a smaller model writes more when the field names carry less
3545
+ * meaning. So: never take an output-token saving measured here as evidence of
3546
+ * anything. Prove those with the paired A/B in `benchmarks/ab-live.ts`, and
3547
+ * write tests for output-side effects against a provider that models the
3548
+ * behaviour you are claiming — see `test/codebook-de-salida.test.ts`.
3549
+ */
3550
+ interface MockProviderOptions {
3551
+ /** Fixed text to return when no schema is present. */
3552
+ answer?: string;
3553
+ /** Override the fabricated structured object. */
3554
+ data?: unknown;
3555
+ /** Simulated provider-cached input tokens (ledger 4). */
3556
+ cachedInputTokens?: number;
3557
+ /** Characters per text event in generateStream (default 8). */
3558
+ streamChunkChars?: number;
3559
+ }
3560
+ declare class MockProvider implements Provider {
3561
+ readonly id = "mock";
3562
+ private readonly options;
3563
+ constructor(options?: MockProviderOptions);
3564
+ generate(req: ProviderRequest, _options?: ProviderStreamOptions): Promise<ProviderResponse>;
3565
+ /**
3566
+ * Streaming variant of generate(): the same answer, cut into text events of
3567
+ * `streamChunkChars` characters, then one `done` with the generate() response.
2932
3568
  */
2933
- silenciarAvisos?: boolean;
3569
+ generateStream(req: ProviderRequest): AsyncGenerator<ProviderStreamEvent, void, undefined>;
2934
3570
  }
3571
+ declare function createMockProvider(options?: MockProviderOptions): Provider;
3572
+
2935
3573
  /**
2936
- * PURE, unit-testable next-delay policy (seconds). Steady state anchors to the
2937
- * license refresh horizon, clamped to [floor, ceiling] and jittered. After a
2938
- * failure it backs off exponentially (a revocation can't land during an outage
2939
- * anyway), capped, still jittered, never below the floor.
3574
+ * OpenAI-compatible provider adapter (chat/completions shape).
3575
+ *
3576
+ * Works with any endpoint that speaks the OpenAI Chat Completions API — OpenAI,
3577
+ * Azure, vLLM, SGLang, TGI, a local gateway, or Vercel AI Gateway with a
3578
+ * "provider/model" string. Structured output is requested via `response_format`
3579
+ * with a JSON schema when a contract is present, which is the provider-native
3580
+ * constrained-decoding path preferred by the layer.
3581
+ *
3582
+ * Models that predate structured outputs reject `response_format` with a 400. The
3583
+ * adapter degrades for those: it repeats the request once with the schema carried
3584
+ * in the prompt instead, remembers the model so later requests skip the rejected
3585
+ * attempt, and reports `structuredNatively: false` so the savings report states
3586
+ * `constraints.backend: "none"` rather than claiming a guarantee the provider
3587
+ * never gave.
3588
+ *
3589
+ * Network calls use global `fetch` (Node >= 18) so there is no SDK dependency.
2940
3590
  */
2941
- declare function computeNextDelay(opts: {
2942
- refreshAfter?: number;
2943
- nowSeconds: number;
2944
- consecutiveFailures: number;
2945
- floorSeconds: number;
2946
- pollIntervalSeconds: number;
2947
- maxIntervalSeconds: number;
2948
- jitterRatio: number;
2949
- random?: () => number;
2950
- }): number;
2951
- declare class RevocationPoller {
2952
- private readonly opts;
2953
- private timer;
2954
- private controller;
2955
- private consecutiveFailures;
2956
- private stopped;
2957
- private started;
2958
- private warnedNoFetch;
2959
- /** ETag of the last 200; sent as If-None-Match so an unchanged list 304s (cheap no-op). */
2960
- private lastEtag;
2961
- /** 304 seguidos; al llegar al tope se fuerza una descarga firmada. */
2962
- private consecutive304;
2963
- constructor(opts: RevocationPollerOptions);
2964
- private now;
2965
- private resolveFetch;
2966
- /**
2967
- * One poll cycle: fetch → verify → apply. NEVER throws (fail-open). A reachable
2968
- * CP resets the failure backoff even if the returned list is rejected — we keep
2969
- * last-known-good. Only a transport/abort/parse error counts as a failure.
2970
- */
2971
- pollOnce(): Promise<void>;
2972
- /** El interruptor, aceptando todavía el nombre viejo. */
2973
- private callado;
2974
- /** Un aviso por causa y por proceso, respetando el interruptor. */
2975
- private avisar;
3591
+ interface OpenAICompatibleOptions {
3592
+ baseUrl: string;
3593
+ apiKey?: string;
2976
3594
  /**
2977
- * THE CONTROL PLANE HAS BEEN UNREACHABLE FOR A WHILE.
2978
- *
2979
- * Polling is fail-open by design: a network outage must never take down the
2980
- * customer's production traffic. The trade-off is that an unreachable control
2981
- * plane has no visible symptom — the license keeps working and requests keep
2982
- * flowing while the revocation list quietly goes stale.
3595
+ * Strip the `provider/` prefix from model ids before sending (`openai/gpt-4o-mini`
3596
+ * → `gpt-4o-mini`).
2983
3597
  *
2984
- * So the warning goes to the console BY DEFAULT, not only through the optional
2985
- * `onEvent` observer. It fires on the third consecutive failure and then every
2986
- * twelfth: a lost request is normal, a week-long outage must keep reminding.
2987
- * Silence it with `silenceWarnings` if you have your own observability.
3598
+ * Default (undefined): strip when `baseUrl` is OpenAI's own API or an Azure
3599
+ * OpenAI endpoint, keep otherwise. OpenAI rejects a prefixed id with a 404
3600
+ * ("model does not exist"), so the qualified ids the layer uses everywhere
3601
+ * else — pricing, cache keys, the report — would break the very first call
3602
+ * that reaches the provider. A gateway or a self-hosted server with a
3603
+ * namespaced model name usually WANTS the prefix, so it is kept there. Set `true`/`false` to decide explicitly; an explicit value
3604
+ * always wins.
2988
3605
  */
2989
- private avisarFalloDeRed;
2990
- /** Fire one non-blocking warm poll, then self-schedule. Idempotent. */
2991
- start(): void;
2992
- private schedule;
2993
- /** Stop polling and abort any in-flight request. Idempotent. */
2994
- stop(): void;
2995
- }
2996
-
2997
- interface UsageEmitterOptions {
2998
- /** Full ingest URL, e.g. `https://savings.bivelio.com/api/metering/usage`. */
2999
- url: string;
3000
- /** Service-account key (`bvk_…`) sent as a Bearer token; the server derives the org from it. */
3001
- serviceAccountKey: string;
3002
- /** Optional org hint; the server always derives the authoritative org from the key. */
3003
- orgId?: string;
3004
- /** Flush cadence in ms (default 15000). */
3005
- flushIntervalMs?: number;
3006
- /** Records per POST (default 50). */
3007
- maxBatch?: number;
3008
- /** Hard in-memory cap; oldest records are dropped past it (default 1000). */
3009
- maxBuffer?: number;
3010
- /** Injectable fetch (defaults to the global). */
3606
+ stripProviderPrefix?: boolean;
3607
+ /** Extra headers (e.g. for a gateway). */
3608
+ headers?: Record<string, string>;
3609
+ /** Injectable fetch for testing. */
3011
3610
  fetchImpl?: typeof fetch;
3012
- /** Injectable clock. */
3013
- now?: () => number;
3014
- /** Whether this process is a CI/ephemeral runner (excluded from billable seats). */
3015
- ci?: boolean;
3016
- /**
3017
- * Which surface owns this process: the local gateway (`bvsala claude` and
3018
- * friends) or the in-process SDK. Sent with the version in the batch's
3019
- * `x-bvsala-version` header (`gateway/0.4.16`, `sdk/0.4.16`) so the control
3020
- * plane can tell a gateway that lacks a lever from an SDK that never had it.
3021
- * Defaults to `"sdk"`; the gateway sets `"gateway"`.
3022
- */
3023
- surface?: "gateway" | "sdk";
3024
- /**
3025
- * Se llama cuando el ingest rechaza la credencial (401/403). Existe para que un
3026
- * despliegue con observabilidad pueda ENTERARSE: el aviso por consola sirve en
3027
- * un portátil, pero en producción los `console.warn` no despiertan a nadie, y
3028
- * este fallo concreto es invisible por definición —deja de llegar uso, que es
3029
- * exactamente lo mismo que se ve cuando no hay tráfico—.
3030
- */
3031
- onAuthError?: (status: number) => void;
3032
3611
  /**
3033
- * Silence the console warnings. They are ON by default, exactly as in the
3034
- * revocation poller: metering is best-effort, so a broken ingest breaks
3035
- * nothing visible — usage simply stops arriving, silently, and the customer's
3036
- * dashboard stays empty forever.
3612
+ * When true (recommended for measurement), a response whose `usage` block is
3613
+ * missing or reports zero prompt/completion tokens for a call that actually
3614
+ * ran is treated as a HARD ERROR instead of being silently coerced to
3615
+ * 0 tokens. This closes the single most dishonest failure mode: an absent
3616
+ * usage payload otherwise becomes `actual cost = 0` → a fabricated 100%
3617
+ * saving. Benchmarks that make real cost claims must set this.
3037
3618
  */
3038
- silenceWarnings?: boolean;
3619
+ requireUsage?: boolean;
3039
3620
  }
3040
3621
  /**
3041
- * Buffers counts-only usage and flushes it to the control plane. DETECTIVE
3042
- * metering: strictly best-effort and NON-BLOCKING. `record()` is O(1) and
3043
- * `flush()` never throws, so a slow, unreachable, or erroring control plane can
3044
- * neither block nor crash the customer's request path. The buffer is hard-capped
3045
- * so a sustained outage can never grow memory without bound.
3622
+ * ¿Cumple este esquema las reglas del modo `strict` de OpenAI?
3623
+ *
3624
+ * Son dos, y aplican a TODO objeto anidado:
3625
+ * 1. `additionalProperties: false`.
3626
+ * 2. `required` tiene que listar TODAS las claves de `properties` — no solo
3627
+ * las obligatorias. En modo strict no existe el campo opcional.
3628
+ *
3629
+ * Un esquema perfectamente válido como
3630
+ * { properties: { answer, refs }, required: ["answer"] }
3631
+ * incumple la segunda y el proveedor responde 400. No es un fallo del cliente:
3632
+ * es una restricción de OpenAI que JSON Schema no tiene.
3046
3633
  */
3047
- declare class UsageEmitter {
3048
- private readonly options;
3049
- private readonly now;
3050
- private readonly flushIntervalMs;
3051
- private readonly maxBatch;
3052
- private readonly maxBuffer;
3053
- private buffer;
3054
- private seatId;
3055
- private ci;
3056
- private timer;
3057
- private inflight;
3058
- /**
3059
- * CONSECUTIVE failed flushes. Warn on the third and then every twelfth: one
3060
- * lost batch is normal, a whole hour is not. Same cadence as the revocation
3061
- * poller on purpose — two different rhythms for the same class of failure
3062
- * only confuse whoever reads the logs.
3063
- */
3064
- private fallosSeguidos;
3065
- constructor(options: UsageEmitterOptions);
3066
- /** Set the resolved seat identity (derivation is async; records emitted before
3067
- * it resolves are stamped at flush time, so none are lost). */
3068
- setSeat(seatId: string | undefined, ci: boolean): void;
3069
- /** Record one request's usage. Never blocks; O(1). */
3070
- record(report: SavingsReport): void;
3071
- start(): void;
3072
- /** Stop the timer and attempt a final flush. Safe to call more than once.
3073
- * Fire-and-forget: for a CLEAN shutdown that must not lose the last batch,
3074
- * use `stopAndDrain` and await it. */
3075
- stop(): void;
3076
- /** Clean shutdown: stop the timer and DRAIN the buffer, awaiting each POST.
3077
- * Unlike `stop()` (fire-and-forget), this resolves only once the final batch
3078
- * has actually been sent, so a graceful exit doesn't lose the last session's
3079
- * usage. Bounded, so an unreachable control plane can't block shutdown. */
3080
- stopAndDrain(maxIntentos?: number): Promise<void>;
3081
- /**
3082
- * Flush one batch. Never throws. A transient server error (5xx) or network
3083
- * failure re-queues the batch (capped); a 4xx (bad request / bad key) drops it,
3084
- * since retrying a rejected payload would loop forever.
3085
- */
3086
- flush(): Promise<void>;
3087
- private hacerFlush;
3634
+ declare function isStrictCompatible(schema: unknown): boolean;
3635
+ /**
3636
+ * Decides the id that travels on the wire. Explicit option wins; otherwise the
3637
+ * endpoint decides: OpenAI/Azure get the bare id, everything else keeps what
3638
+ * the caller wrote. Exported for the pipeline-level test that pins the README
3639
+ * snippet; not part of the public API.
3640
+ */
3641
+ declare function quitarPrefijoDeProveedor(model: string, explicito: boolean | undefined, baseUrl: string): string;
3642
+ declare class OpenAICompatibleProvider implements Provider {
3643
+ readonly id: string;
3644
+ private readonly opts;
3645
+ private readonly fetchImpl;
3646
+ /** Models of this endpoint already known to reject `response_format`. */
3647
+ private readonly sinResponseFormat;
3648
+ private streamOptionsRejected;
3649
+ private readonly origen;
3650
+ constructor(opts: OpenAICompatibleOptions);
3651
+ generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
3088
3652
  /**
3089
- * THE INGEST HAS BEEN FAILING FOR A WHILE AND NOBODY NOTICED.
3090
- *
3091
- * Metering is best-effort by design — it must never take down the customer's
3092
- * request — and that turns every failure into SILENT data loss. Without this,
3093
- * the only symptom is an empty dashboard, which is indistinguishable from
3094
- * "you haven't sent anything yet": the symptom points the opposite way from
3095
- * the cause.
3096
- *
3097
- * Warns on the third consecutive failure and then every twelfth. Not the
3098
- * first — a lost request is normal — and not every one, which becomes noise.
3653
+ * Streaming variant of generate() over Chat Completions. It always asks for
3654
+ * the final usage chunk (`stream_options.include_usage`); an endpoint that
3655
+ * rejects that option is retried once without it and remembered, and its
3656
+ * streamed answers then report `usage.complete: false`. With `requireUsage`,
3657
+ * that rejection throws instead. Requests with an output contract or a
3658
+ * deferrable tier are not streamed: they are served by generate() and yielded
3659
+ * as one text event.
3099
3660
  */
3100
- private avisarDeFallo;
3101
- private requeue;
3102
- private toWire;
3661
+ generateStream(req: ProviderRequest, options?: ProviderStreamOptions): AsyncGenerator<ProviderStreamEvent, void, undefined>;
3662
+ private enviar;
3663
+ /** Translate a 2xx endpoint response into the provider-neutral shape. */
3664
+ private leer;
3103
3665
  }
3104
3666
 
3105
- /** True when running in a CI / ephemeral environment. */
3106
- declare function detectCi(): boolean;
3107
- /**
3108
- * Derive the machine seat id for a service-account key. Resolves to `undefined`
3109
- * when no machine fingerprint is available (e.g. a browser) — the caller then
3110
- * reports no seatId and the server counts the request under an "unknown" seat.
3111
- * The id (`m-<32 hex>`) matches the control plane's identity charset.
3112
- */
3113
- declare function deriveSeatId(serviceAccountKey: string): Promise<string | undefined>;
3114
-
3115
3667
  /**
3116
- * Everything the pipeline depends on. All fields except `provider` and
3117
- * `tokenizer` are optional; sensible in-memory defaults are wired by
3118
- * {@link createSavingsLayer} so the layer works out of the box and every
3119
- * component is independently swappable.
3668
+ * Anthropic Messages API adapter.
3669
+ *
3670
+ * Speaks `POST /v1/messages`: the system prompt travels in its own top-level
3671
+ * `system` field (not as a message), tools are `{name, description,
3672
+ * input_schema}` with no `function` wrapper, and prefix caching is requested
3673
+ * with EXPLICIT `cache_control` breakpoints — Anthropic does not cache on its
3674
+ * own. A cached read is billed at 0.1x the input rate, which is the largest
3675
+ * single lever on the input side of a customer's bill.
3676
+ *
3677
+ * Network calls use global `fetch` (Node >= 18) so there is no SDK dependency.
3120
3678
  */
3121
- interface SavingsLayerConfig {
3679
+ /** Render order of the request, which is also the order the cache prefix grows in. */
3680
+ interface AnthropicOptions {
3681
+ apiKey?: string;
3682
+ /** Defaults to the public API. Override for a gateway or a proxy. */
3683
+ baseUrl?: string;
3122
3684
  /**
3123
- * Contratos a los que ya se les incumplió una restricción de valor.
3124
- *
3125
- * La capa lo aprende sola, pero lo aprendido muere con la instancia. En
3126
- * serverless —una instancia por petición— eso significa no aprender NUNCA:
3127
- * cada llamada estrena y paga su reparación. Pasar aquí un conjunto
3128
- * compartido (o respaldado por Redis, con la API de Set) hace que se aprenda
3129
- * una vez para todo el despliegue.
3130
- *
3131
- * Solo guarda identificadores de contrato, nunca contenido de peticiones.
3685
+ * Strip the `provider/` prefix from model ids before sending
3686
+ * (`anthropic/claude-sonnet-4-5` → `claude-sonnet-4-5`). Default (undefined):
3687
+ * strip when talking to `api.anthropic.com`, which rejects a prefixed id;
3688
+ * keep for a gateway or proxy, which usually routes on it. An explicit value
3689
+ * always wins.
3132
3690
  */
3133
- constraintMemory?: Set<string>;
3134
- provider: Provider;
3135
- tokenizer: TokenizerRegistry;
3136
- exactCache?: ExactCache;
3137
- semanticCache?: InMemorySemanticCache;
3138
- singleflight?: Singleflight;
3139
- resolvers?: ResolverRegistry;
3140
- router?: ExecutionRouter;
3141
- toolSelector?: ToolSelector;
3142
- formatSelector?: FormatSelector;
3143
- schemaRegistry?: SchemaRegistry;
3144
- constraintBackends?: ConstraintBackend[];
3145
- compressor?: Compressor;
3691
+ stripProviderPrefix?: boolean;
3692
+ /** Wire version pinned in the `anthropic-version` header. */
3693
+ apiVersion?: string;
3694
+ /** Extra headers (e.g. for a gateway). */
3695
+ headers?: Record<string, string>;
3696
+ /** Injectable fetch for testing. */
3697
+ fetchImpl?: typeof fetch;
3146
3698
  /**
3147
- * The numbers behind "compress only when it pays for itself" — in money, not
3148
- * in tokens.
3149
- *
3150
- * All optional, and the default never invents a saving: the layer compresses
3151
- * when the net token saving justifies it, and holds back when compressing
3152
- * would push the prompt below the provider's cacheable minimum — where fewer
3153
- * tokens can mean a larger bill. Declare {@link CompressionEconomics.prefix}
3154
- * with your own measurements to unlock the full decision in both directions.
3699
+ * `max_tokens` is REQUIRED by this API — unlike the OpenAI shape, where it is
3700
+ * optional. A request without one is a 400, so the adapter has to supply a
3701
+ * value. It is deliberately generous: a low default would truncate answers
3702
+ * and the truncation would look like a model failure rather than an adapter
3703
+ * default.
3155
3704
  */
3156
- compressionEconomics?: CompressionEconomics;
3157
- retriever?: Retriever;
3158
- reranker?: Reranker;
3705
+ defaultMaxOutputTokens?: number;
3159
3706
  /**
3160
- * Tuning for the model cascade (`savings.modelCascade`). All optional; the
3161
- * defaults measure and never spend.
3707
+ * When true (recommended for measurement), a response whose `usage` block is
3708
+ * missing its counters is treated as a HARD ERROR instead of being coerced to
3709
+ * 0 tokens. An absent usage payload otherwise becomes `actual cost = 0` → a
3710
+ * fabricated 100% saving.
3162
3711
  */
3163
- modelCascade?: ModelCascadeSettings;
3712
+ requireUsage?: boolean;
3164
3713
  /**
3165
- * Signed BiVelio license token (from your paid subscription). This is the
3166
- * primary way to license the SDK: `createSavingsLayer` verifies it offline
3167
- * against the embedded public key and builds a {@link licenseGate} from it.
3168
- * Without a valid, active license (and without {@link devUnlicensed}), the
3169
- * pipeline refuses to run — get a key at https://savings.bivelio.com.
3714
+ * How many `cache_control` breakpoints the adapter may spend.
3170
3715
  *
3171
- * Surrounding whitespace (the trailing newline of a file or mounted secret),
3172
- * wrapping quotes and a `Bearer ` prefix are stripped before verification, so
3173
- * a key read with `readFileSync` works as-is. Anything INSIDE the key is not
3174
- * touched: a key with a space in the middle is a broken key.
3716
+ * The API caps them at 4 per request. The adapter never needs more than 2
3717
+ * (end of system, end of the stable message prefix) and the cap is exposed so
3718
+ * a gateway with a stricter limit can lower it. Raising it above 4 is
3719
+ * pointless: the extra breakpoints are rejected by the API, not ignored.
3175
3720
  */
3176
- licenseKey?: string;
3721
+ maxCacheBreakpoints?: number;
3177
3722
  /**
3178
- * Override the public key used to verify {@link licenseKey}. An SPKI PEM string,
3179
- * or a kid-addressed {@link LicenseKeyset} (`{ keys: {kid: pem}, defaultKid }`)
3180
- * to support signing-key rotation — legacy no-kid tokens use `defaultKid`, and
3181
- * an unknown kid fails closed. For self-hosting/self-testing; defaults to the
3182
- * build-embedded BiVelio key.
3723
+ * When true, requests that the pipeline decided to mark also carry the
3724
+ * request-level `cache_control` (Anthropic's automatic caching): the provider
3725
+ * places one extra breakpoint on the last cacheable block and moves it
3726
+ * forward as the conversation grows, so the history beyond the declared
3727
+ * stable prefix is cached incrementally too. It spends one of the four
3728
+ * breakpoint slots and never fires when the economic gate declined to mark.
3729
+ *
3730
+ * Off by default, and deliberately so: the moving breakpoint writes the
3731
+ * volatile tail of EVERY marked request at the 1.25x write premium. That
3732
+ * pays for itself on conversational or agentic traffic — the next turn reads
3733
+ * the whole history at 0.1x — and loses money on one-shot traffic where
3734
+ * nothing ever reads it back. It is also rejected by legacy Bedrock
3735
+ * deployments. Turn it on when your traffic is multi-turn.
3183
3736
  */
3184
- licensePublicKey?: string | LicenseKeyset;
3737
+ automaticCache?: boolean;
3185
3738
  /**
3186
- * Pre-built license gate. **Ignored in the published npm package**, where a
3187
- * {@link licenseKey} verified against the embedded BiVelio public key is the
3188
- * only accepted license — passing a gate there logs a warning and changes
3189
- * nothing. It exists for builds from source (this repo's own tests and
3190
- * benchmarks), where it takes precedence over {@link licenseKey}.
3739
+ * Whether a request carrying BOTH the caller's tools and an output contract
3740
+ * may send `tool_choice: {type: "any"}`, which obliges the model to answer
3741
+ * through SOME tool — its pick — instead of writing prose.
3742
+ *
3743
+ * Defaults to `true`, and the default is the point: without it this exact
3744
+ * combination measured 62% MORE expensive than sending no layer at all, every
3745
+ * single time, because prose forces a second full call to repair. Turning it
3746
+ * off restores the old behaviour for a caller who genuinely needs a plain-text
3747
+ * turn to be reachable while a contract is attached.
3191
3748
  */
3192
- licenseGate?: LicenseGate;
3749
+ forceAnswerThroughTool?: boolean;
3750
+ }
3751
+ declare class AnthropicProvider implements Provider {
3752
+ readonly id: string;
3753
+ private readonly opts;
3754
+ private readonly fetchImpl;
3755
+ constructor(opts?: AnthropicOptions);
3756
+ /** The id Anthropic's API sees: bare at `api.anthropic.com`, as written elsewhere. */
3757
+ private modeloEnElCable;
3758
+ generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
3193
3759
  /**
3194
- * OPT-IN online revocation call-home. When set (alongside {@link licenseKey} and
3195
- * with no explicit {@link licenseGate}), the SDK polls this base's
3196
- * `GET /licenses/revocations` for a signed revocation list and enforces it via a
3197
- * LIVE gate — a license cancelled mid-term stops working without a restart.
3760
+ * Streaming variant of generate() over the Messages API. The final usage is
3761
+ * read from the closing `message_delta` event (the output counter in
3762
+ * `message_start` is provisional); a stream without it reports
3763
+ * `usage.complete: false`, and with `requireUsage` it throws instead. A body
3764
+ * that ends before the provider signalled completion throws. Requests with an
3765
+ * output contract or declared deferrable are served by generate() and yielded
3766
+ * as one text event.
3767
+ */
3768
+ generateStream(req: ProviderRequest, options?: ProviderStreamOptions): AsyncGenerator<ProviderStreamEvent, void, undefined>;
3769
+ private enviar;
3770
+ private construirCuerpo;
3771
+ private marcarCache;
3772
+ private leer;
3773
+ }
3774
+
3775
+ /**
3776
+ * Trace collector: an ordered log of pipeline decisions with elapsed timings.
3777
+ * Every stage records why it did what it did, so a SavingsReport can be audited
3778
+ * and so ablations can attribute savings to a specific module.
3779
+ */
3780
+ declare class TraceCollector {
3781
+ private readonly events;
3782
+ private readonly start;
3783
+ private readonly now;
3784
+ constructor(now?: () => number);
3785
+ record(stage: string, outcome: string, detail?: Record<string, unknown>): void;
3786
+ elapsed(): number;
3787
+ all(): TraceEvent[];
3788
+ }
3789
+
3790
+ /**
3791
+ * Thrown when the pipeline is asked to run without a valid, active BiVelio
3792
+ * license. This is the fail-closed contract: a paid SDK must refuse to operate
3793
+ * unlicensed, rather than silently serving requests for free.
3794
+ */
3795
+ declare class LicenseError extends Error {
3796
+ /**
3797
+ * Resolved license status — the stable handle for branching on the cause,
3798
+ * since the message text is rewritten whenever it can be made clearer:
3198
3799
  *
3199
- * NEVER-BRICK: the customer request path never blocks on the fetch; the warm poll
3200
- * is fired but not awaited, and any failed/unreachable poll is a no-op that keeps
3201
- * the last-known-good snapshot. Leaving this unset keeps the fully-offline,
3202
- * zero-network default path. Pass the base URL of the licensing service, e.g.
3203
- * `https://savings.bivelio.com`.
3800
+ * - `none` — nothing was configured (no `licenseKey`, no gate).
3801
+ * - `invalid` — a key was configured but does not verify.
3802
+ * - `expired` / `grace` — it verifies, but the term is over.
3803
+ * - `revoked` — cancelled by the control plane.
3804
+ * - `stale` — paid and current, but unconfirmed: a connectivity problem.
3805
+ *
3806
+ * The same value is repeated at the end of the message (`license status: X`)
3807
+ * because an uncaught error prints nothing else.
3204
3808
  */
3205
- licenseServerUrl?: string;
3809
+ readonly status?: string;
3810
+ constructor(message: string, status?: string);
3811
+ }
3812
+
3813
+ /** Decoded revocation-list body (mirror of @bivelio/protocol's RevocationListPayload). */
3814
+ interface RevocationListBody {
3815
+ v: number;
3816
+ asOf: number;
3817
+ seq: number;
3818
+ revoked: string[];
3819
+ iss?: string;
3820
+ }
3821
+ interface RevocationListVerification {
3822
+ /** True only if signature + typ + monotonic-seq + skew all pass. */
3823
+ ok: boolean;
3824
+ /** List revision (monotonic). -1 when not ok. */
3825
+ seq: number;
3826
+ /** Unix seconds the list was minted. 0 when not ok. */
3827
+ asOf: number;
3828
+ /** Revoked jti in this list (empty when not ok). */
3829
+ revoked: Set<string>;
3830
+ reason?: string;
3831
+ }
3832
+ /**
3833
+ * Verify a signed revocation-list token offline and enforce the client-side
3834
+ * policy: (1) valid Ed25519 signature, (2) header `typ` is a revocation list
3835
+ * (a license token can't be replayed here), (3) `seq >= minSeq` — reject a
3836
+ * stale/rolled-back list, (4) `asOf <= now + maxSkew` — reject a future-dated list.
3837
+ * ANY failure returns `ok:false` with an empty set; the poller then keeps its
3838
+ * sticky last-known-good (fail-open), never dropping known revocations.
3839
+ */
3840
+ declare function verifyRevocationList(token: string, publicKey: string | LicenseKeyset, opts?: {
3841
+ minSeq?: number;
3842
+ nowSeconds?: number;
3843
+ maxSkewSeconds?: number;
3844
+ }): RevocationListVerification;
3845
+ /**
3846
+ * The SDK's live, sticky, monotonic view of the revocation set. It only ever
3847
+ * GROWS (a jti, once seen revoked, stays revoked for the process lifetime) and
3848
+ * only advances on a higher-or-equal `seq` — so a stale, rolled-back, or empty
3849
+ * list can never un-revoke a license. The live gate reads {@link snapshot} on
3850
+ * every request; the poller feeds it via {@link apply}.
3851
+ */
3852
+ declare class RevocationSnapshot {
3853
+ private _seq;
3854
+ private _asOf;
3855
+ private _lastContactAt;
3856
+ private readonly set;
3857
+ get seq(): number;
3858
+ get asOf(): number;
3859
+ /** Unix seconds of the last CONFIRMED authorized heartbeat (a 304 or a verified
3860
+ * list). Feeds the live gate's online staleness check; 0 until the first one. */
3861
+ get lastContactAt(): number;
3862
+ /** Record a confirmed authorized heartbeat from the control plane (monotonic). */
3863
+ markContact(nowSeconds: number): void;
3864
+ has(jti: string): boolean;
3865
+ /** Live view of the revoked set (the same Set instance — read-only to callers). */
3866
+ snapshot(): ReadonlySet<string>;
3206
3867
  /**
3207
- * Steady-state ceiling between revocation polls, in seconds (default 900 = 15 min).
3208
- * The effective cadence tightens near license expiry and has a hard floor.
3868
+ * Merge an already-verified list. Accepts only `seq >= current seq`; UNIONS the
3869
+ * jti (never removes); advances seq/asOf monotonically. Returns whether the
3870
+ * revoked set grew or the revision advanced.
3209
3871
  */
3210
- licensePollIntervalSeconds?: number;
3211
- /** Injectable `fetch` for the revocation poller (defaults to the global). Testing/proxy. */
3212
- licenseFetch?: typeof fetch;
3213
- /** Advanced tuning of the revocation poller cadence + skew tolerance. */
3214
- licenseRevocation?: {
3215
- /** Hard floor between polls, seconds (default 300). */
3216
- floorSeconds?: number;
3217
- /** Backoff ceiling during a control-plane outage, seconds (default 3600). */
3218
- maxIntervalSeconds?: number;
3219
- /** Jitter fraction ± (default 0.1). */
3220
- jitterRatio?: number;
3221
- /** Reject a signed list whose `asOf` is more than this far in the future, seconds (default 300). */
3222
- maxSkewSeconds?: number;
3223
- /**
3224
- * ONLINE staleness ceiling, seconds (default ~6h). If the SDK goes this long
3225
- * without a CONFIRMED authorized heartbeat (a 304 or a verified list), the LIVE
3226
- * gate stops granting — closing the fail-open window where blocking egress keeps
3227
- * a revoked license alive until natural expiry. Must exceed the poll cadence
3228
- * with margin. Applies ONLY on the online path ({@link licenseServerUrl} set);
3229
- * it is NOT a zero-network opt-out — use the offline path for that.
3230
- */
3231
- maxStalenessSeconds?: number;
3232
- };
3872
+ apply(list: {
3873
+ seq: number;
3874
+ asOf: number;
3875
+ revoked: Iterable<string>;
3876
+ }): boolean;
3877
+ }
3878
+
3879
+ /** A resolved gate that also exposes overall status + a refresh horizon. */
3880
+ interface ResolvedGate extends LicenseGate {
3881
+ isActive(): boolean;
3882
+ status(): LicenseStatus;
3233
3883
  /**
3234
- * Service-account key (`bvk_…`) — your machine credential. It does TWO things,
3235
- * and the second one is why a long-running deployment needs it:
3236
- *
3237
- * 1. **Licence renewal pickup.** Each billing period mints a NEW licence token;
3238
- * the one you deployed expires at the end of the period (+ grace). With this
3239
- * key the SDK fetches the renewed token from the control plane and swaps it
3240
- * in without a restart. WITHOUT it there is no machine pickup, so somebody
3241
- * must paste the new token by hand every cycle or the deployment stops with
3242
- * `LicenseError` once the grace period ends — while the subscription is paid
3243
- * and current. Disable with {@link licenseRefresh}`.enabled = false`.
3244
- * 2. **DETECTIVE usage metering.** COUNTS-ONLY usage after each request —
3245
- * request id, model, cache outcome, and token/cost counts, tagged with an
3246
- * auto-derived machine seat id — NEVER prompts or completions. Disable with
3247
- * {@link metering}`.enabled = false` (which does NOT disable renewal pickup).
3248
- *
3249
- * Both are best-effort and non-blocking: a slow or unreachable control plane
3250
- * never blocks or breaks `generate()`. Get the key from your dashboard.
3884
+ * Why the key does not open, when it does not; `undefined` when it verified.
3885
+ * Under `status() === "invalid"` it tells a key that does NOT EVEN HAVE THE
3886
+ * SHAPE of a token (`"malformed"`: the `bvk_` pasted where the license goes,
3887
+ * arbitrary text) from a well-formed one whose signature fails (the verifier's
3888
+ * reason, e.g. `"bad signature"`). Both are `invalid` — the stable handle for
3889
+ * branching does not change — but the advice given to the customer differs.
3251
3890
  */
3252
- serviceAccountKey?: string;
3891
+ reason(): string | undefined;
3892
+ /** Unix seconds after which the online status should be re-checked (poller anchor). */
3893
+ refreshAfter?(): number | undefined;
3894
+ }
3895
+ /**
3896
+ * Build a LIVE license gate from a signed key. The token is verified + decoded
3897
+ * ONCE (no per-request Ed25519 verify — hot-path safe); each `isActive()` /
3898
+ * `status()` / `isEntitled()` then re-derives the status from the cached payload +
3899
+ * the LIVE clock + the (mutable, sticky) {@link RevocationSnapshot}. This is what
3900
+ * lets a revocation — or an expiry crossed mid-process — take effect without a
3901
+ * restart, while the customer request path never touches the network (it reads an
3902
+ * in-memory Set).
3903
+ *
3904
+ * The offline path passes a fresh, never-updated snapshot (live clock, zero
3905
+ * network); the online path passes a snapshot a {@link RevocationPoller} feeds.
3906
+ */
3907
+ declare function liveGateFromKey(licenseKey: string, publicKey: string | LicenseKeyset, opts: {
3908
+ revocation: RevocationSnapshot;
3909
+ now?: () => number;
3910
+ /** Online staleness ceiling, seconds. If the gate goes this long without a
3911
+ * confirmed authorized heartbeat, it stops granting (closes the block-egress
3912
+ * fail-open). Undefined = check disabled (the offline path never sets it). */
3913
+ maxStalenessSeconds?: number;
3914
+ /** Freshness anchor (unix seconds); defaults to construction time (boot). */
3915
+ startedAt?: number;
3916
+ }): ResolvedGate;
3917
+
3918
+ /**
3919
+ * Build the SDK's structural {@link import("../contracts/types.js").LicenseGate}
3920
+ * from a signed license key — the object `createSavingsLayer({ licenseKey })`
3921
+ * injects on the OFFLINE path (no `licenseServerUrl`).
3922
+ *
3923
+ * It is a LIVE gate over an EMPTY, never-updated revocation snapshot: zero network,
3924
+ * but the clock is LIVE, so a license that crosses its expiry (past grace) stops
3925
+ * working mid-process instead of only after a restart — closing the frozen-clock
3926
+ * hole the old one-shot resolution left open. `isActive()` is true while active or
3927
+ * in grace; `isEntitled` additionally requires the feature in the token's grant.
3928
+ * Online revocation is added by pairing {@link liveGateFromKey} with a
3929
+ * poller-fed snapshot, which `createSavingsLayer` wires up for you.
3930
+ */
3931
+ declare function gateFromKey(licenseKey: string, publicKey: string | LicenseKeyset, now?: () => number): ResolvedGate;
3932
+
3933
+ type RevocationPollEvent = {
3934
+ type: "applied";
3935
+ seq: number;
3936
+ size: number;
3937
+ } | {
3938
+ type: "unchanged";
3939
+ seq: number;
3940
+ } | {
3941
+ type: "rejected";
3942
+ reason: string;
3943
+ } | {
3944
+ type: "error";
3945
+ error: unknown;
3946
+ } | {
3947
+ type: "no-fetch";
3948
+ };
3949
+ interface RevocationPollerOptions {
3950
+ /** Absolute URL of GET /licenses/revocations. */
3951
+ url: string;
3952
+ /** SPKI PEM (or a kid-addressed {@link LicenseKeyset}) used to verify the signed
3953
+ * list — the same key/keyset that verifies license tokens. */
3954
+ publicKeyPem: string | LicenseKeyset;
3955
+ /** The sticky snapshot to feed. */
3956
+ snapshot: RevocationSnapshot;
3957
+ /** Anchor cadence to the license refresh horizon (unix seconds). */
3958
+ getRefreshAfter?: () => number | undefined;
3959
+ /** Injectable clock (unix seconds). */
3960
+ nowSeconds?: () => number;
3961
+ /** Injectable fetch (defaults to the global). */
3962
+ fetchImpl?: typeof fetch;
3963
+ pollIntervalSeconds?: number;
3964
+ floorSeconds?: number;
3965
+ maxIntervalSeconds?: number;
3966
+ jitterRatio?: number;
3967
+ maxSkewSeconds?: number;
3968
+ requestTimeoutMs?: number;
3969
+ /** Injectable RNG in [0,1) for jitter. Default Math.random. */
3970
+ random?: () => number;
3971
+ /** Optional observer (telemetry/tests). */
3972
+ onEvent?: (e: RevocationPollEvent) => void;
3253
3973
  /**
3254
- * Automatic licence-renewal pickup (on by default when {@link serviceAccountKey}
3255
- * and {@link licenseServerUrl} are both set).
3256
- *
3257
- * Turning it OFF means you take responsibility for deploying the renewed token
3258
- * each billing period; if nobody does, the SDK stops at the end of the grace
3259
- * period even though the subscription is paid.
3974
+ * Silencia los avisos por consola. Por defecto están ENCENDIDOS, y ese defecto
3975
+ * es la corrección: el sondeo es fail-open, así que un call-home roto no rompe
3976
+ * nada visible — la lista de revocaciones simplemente se queda vieja, en
3977
+ * silencio, durante días. Quien tenga observabilidad propia puede apagarlos;
3978
+ * quien no la tenga, que es casi todo el mundo, se entera igual.
3260
3979
  */
3261
- licenseRefresh?: {
3262
- enabled?: boolean;
3263
- /** Check cadence in ms (default 3600000 = 1 h). */
3264
- intervalMs?: number;
3265
- /** How long before expiry to start looking, seconds (default 7 days). */
3266
- leadSeconds?: number;
3267
- };
3980
+ silenceWarnings?: boolean;
3268
3981
  /**
3269
- * Usage-ingest URL. Defaults to `${licenseServerUrl}/api/metering/usage`. Set
3270
- * explicitly to point metering at a different host than the revocation poller.
3982
+ * @deprecated Spanish name that leaked into the public options. Use
3983
+ * {@link RevocationPollerOptions.silenceWarnings}. Honoured for one minor
3984
+ * version so nobody gets their warnings back by surprise.
3271
3985
  */
3272
- meteringUrl?: string;
3273
- /** Injectable `fetch` for metering (defaults to {@link licenseFetch}, then global). */
3274
- meteringFetch?: typeof fetch;
3275
- /** DETECTIVE metering tuning (all optional). */
3276
- metering?: {
3277
- /**
3278
- * Off switch for USAGE REPORTING even when {@link serviceAccountKey} is set
3279
- * (default: on). It does NOT disable licence-renewal pickup — that has its
3280
- * own switch, {@link licenseRefresh}`.enabled`, because turning off telemetry
3281
- * must not silently stop your licence from renewing.
3282
- */
3283
- enabled?: boolean;
3284
- /** Flush cadence in ms (default 15000). */
3285
- flushIntervalMs?: number;
3286
- /** Records per POST (default 50). */
3287
- maxBatch?: number;
3288
- /** Hard in-memory cap; oldest records dropped past it (default 1000). */
3289
- maxBuffer?: number;
3290
- /** Override the auto-derived machine seat id. */
3291
- seatId?: string;
3292
- /** Override CI auto-detection (CI/ephemeral seats are excluded from billable seats). */
3293
- ci?: boolean;
3294
- /** Optional org hint; the server derives the authoritative org from the key. */
3295
- orgId?: string;
3296
- };
3986
+ silenciarAvisos?: boolean;
3987
+ }
3988
+ /**
3989
+ * PURE, unit-testable next-delay policy (seconds). Steady state anchors to the
3990
+ * license refresh horizon, clamped to [floor, ceiling] and jittered. After a
3991
+ * failure it backs off exponentially (a revocation can't land during an outage
3992
+ * anyway), capped, still jittered, never below the floor.
3993
+ */
3994
+ declare function computeNextDelay(opts: {
3995
+ refreshAfter?: number;
3996
+ nowSeconds: number;
3997
+ consecutiveFailures: number;
3998
+ floorSeconds: number;
3999
+ pollIntervalSeconds: number;
4000
+ maxIntervalSeconds: number;
4001
+ jitterRatio: number;
4002
+ random?: () => number;
4003
+ }): number;
4004
+ declare class RevocationPoller {
4005
+ private readonly opts;
4006
+ private timer;
4007
+ private controller;
4008
+ private consecutiveFailures;
4009
+ private stopped;
4010
+ private started;
4011
+ private warnedNoFetch;
4012
+ /** ETag of the last 200; sent as If-None-Match so an unchanged list 304s (cheap no-op). */
4013
+ private lastEtag;
4014
+ /** 304 seguidos; al llegar al tope se fuerza una descarga firmada. */
4015
+ private consecutive304;
4016
+ constructor(opts: RevocationPollerOptions);
4017
+ private now;
4018
+ private resolveFetch;
3297
4019
  /**
3298
- * Development-only escape hatch to run ungated, honored ONLY when building from
3299
- * source (local dev, CI, benchmarks) — also via the `BVSALA_DEV_OPEN=1`
3300
- * environment variable.
3301
- *
3302
- * IMPORTANT: this flag is **disabled in the published npm package**. The
3303
- * distributed build hard-codes the licensing check on, so setting
3304
- * `devUnlicensed` (or `BVSALA_DEV_OPEN`) there has NO effect — the layer still
3305
- * requires a valid `licenseKey`. It is not a way to run the product for free.
4020
+ * One poll cycle: fetch → verify → apply. NEVER throws (fail-open). A reachable
4021
+ * CP resets the failure backoff even if the returned list is rejected — we keep
4022
+ * last-known-good. Only a transport/abort/parse error counts as a failure.
3306
4023
  */
3307
- devUnlicensed?: boolean;
3308
- /** Per-model cost models; falls back to `defaultCostModel`. */
3309
- costModels?: Record<string, CostModel>;
3310
- defaultCostModel?: CostModel;
3311
- /** TTL for exact-cache writes, in ms. */
3312
- exactCacheTtlMs?: number;
4024
+ pollOnce(): Promise<void>;
4025
+ /** El interruptor, aceptando todavía el nombre viejo. */
4026
+ private callado;
4027
+ /** Un aviso por causa y por proceso, respetando el interruptor. */
4028
+ private avisar;
3313
4029
  /**
3314
- * Refuse the single repair retry when paying for it would push this request's
3315
- * cost above what it would have cost WITHOUT the layer. The invalid answer is
3316
- * then returned as-is with `constraints.structurallyValid: false`, plus a
3317
- * warning naming the three figures.
3318
- *
3319
- * OFF by default, and the default is a deliberate, measured choice rather than
3320
- * caution — read this before turning it on.
3321
- *
3322
- * The bound sounds narrow ("only the worst case") and is not. The baseline of
3323
- * a call that ran is reconstructed EXCLUDING the repair (see `estimateCost`:
3324
- * `actualInput - repairInput`), precisely so a retry cannot pay for itself in
3325
- * the report. That makes the threshold unreachable by construction: the repair
3326
- * only ever moves `actual`, never the baseline, so ANY repair breaches it
3327
- * whenever the layer's other books came out flat. On a request where nothing
3328
- * was compressed, cached or eliminated, `baseline === actual` exactly and the
3329
- * gate suppresses every repair, however cheap.
3330
- *
3331
- * So this is not "cap the tail", it is "stop repairing unless the layer has
3332
- * already banked more than the repair will cost". That is a real quality
3333
- * policy, not a rounding rule, and it is the caller's contract to decide on —
3334
- * hence opt-in.
4030
+ * THE CONTROL PLANE HAS BEEN UNREACHABLE FOR A WHILE.
3335
4031
  *
3336
- * There is no threshold that fires on the pathological case and spares the
3337
- * flat one; they are the same arithmetic at different magnitudes. And there is
3338
- * a genuine argument on the other side: an answer that misses its contract is
3339
- * a MODEL failure, so the no-layer path would have had to pay for its own
3340
- * second call too — charging the whole retry against the layer compares one
3341
- * valid answer to one invalid answer.
4032
+ * Polling is fail-open by design: a network outage must never take down the
4033
+ * customer's production traffic. The trade-off is that an unreachable control
4034
+ * plane has no visible symptom — the license keeps working and requests keep
4035
+ * flowing while the revocation list quietly goes stale.
3342
4036
  *
3343
- * Turn it on when a hard per-request ceiling matters more than contract
3344
- * compliance — a batch backfill, a cost-capped tenant. Leave it off when the
3345
- * caller relies on the shape being right.
4037
+ * So the warning goes to the console BY DEFAULT, not only through the optional
4038
+ * `onEvent` observer. It fires on the third consecutive failure and then every
4039
+ * twelfth: a lost request is normal, a week-long outage must keep reminding.
4040
+ * Silence it with `silenceWarnings` if you have your own observability.
4041
+ */
4042
+ private avisarFalloDeRed;
4043
+ /** Fire one non-blocking warm poll, then self-schedule. Idempotent. */
4044
+ start(): void;
4045
+ private schedule;
4046
+ /** Stop polling and abort any in-flight request. Idempotent. */
4047
+ stop(): void;
4048
+ }
4049
+
4050
+ interface UsageEmitterOptions {
4051
+ /** Full ingest URL, e.g. `https://savings.bivelio.com/api/metering/usage`. */
4052
+ url: string;
4053
+ /** Service-account key (`bvk_…`) sent as a Bearer token; the server derives the org from it. */
4054
+ serviceAccountKey: string;
4055
+ /** Optional org hint; the server always derives the authoritative org from the key. */
4056
+ orgId?: string;
4057
+ /** Flush cadence in ms (default 15000). */
4058
+ flushIntervalMs?: number;
4059
+ /** Records per POST (default 50). */
4060
+ maxBatch?: number;
4061
+ /** Hard in-memory cap; oldest records are dropped past it (default 1000). */
4062
+ maxBuffer?: number;
4063
+ /** Injectable fetch (defaults to the global). */
4064
+ fetchImpl?: typeof fetch;
4065
+ /** Injectable clock. */
4066
+ now?: () => number;
4067
+ /** Whether this process is a CI/ephemeral runner (excluded from billable seats). */
4068
+ ci?: boolean;
4069
+ /**
4070
+ * Which surface owns this process: the local gateway (`bvsala claude` and
4071
+ * friends) or the in-process SDK. Sent with the version in the batch's
4072
+ * `x-bvsala-version` header (`gateway/0.4.16`, `sdk/0.4.16`) so the control
4073
+ * plane can tell a gateway that lacks a lever from an SDK that never had it.
4074
+ * Defaults to `"sdk"`; the gateway sets `"gateway"`.
3346
4075
  */
3347
- skipRepairAboveBaseline?: boolean;
4076
+ surface?: "gateway" | "sdk";
3348
4077
  /**
3349
- * Reliability contract. When true (the default), ANY internal optimizer error
3350
- * (a resolver, cache, format selector, compressor, or accounting bug) is
3351
- * caught and the request falls open to a plain provider call — BV-SALA must
3352
- * never add a crash point to the customer's request path. Set to `false` only
3353
- * in tests/benchmarks that want internal errors to surface.
4078
+ * Se llama cuando el ingest rechaza la credencial (401/403). Existe para que un
4079
+ * despliegue con observabilidad pueda ENTERARSE: el aviso por consola sirve en
4080
+ * un portátil, pero en producción los `console.warn` no despiertan a nadie, y
4081
+ * este fallo concreto es invisible por definición —deja de llegar uso, que es
4082
+ * exactamente lo mismo que se ve cuando no hay tráfico—.
3354
4083
  */
3355
- failOpen?: boolean;
4084
+ onAuthError?: (status: number) => void;
3356
4085
  /**
3357
- * Silence BV-SALA's console warnings. They are ON by default, and they only
3358
- * ever report a condition whose symptom is otherwise invisible: a saving knob
3359
- * asked for but not wired up, usage that stops reaching the control plane, a
3360
- * revocation list going stale. Nothing here throws and nothing here changes
3361
- * what the layer does — turning it on only removes the console noise, so set
3362
- * it when you have your own observability. Applies to the pipeline, the usage
3363
- * emitter and the revocation poller alike.
4086
+ * Silence the console warnings. They are ON by default, exactly as in the
4087
+ * revocation poller: metering is best-effort, so a broken ingest breaks
4088
+ * nothing visible — usage simply stops arriving, silently, and the customer's
4089
+ * dashboard stays empty forever.
3364
4090
  */
3365
4091
  silenceWarnings?: boolean;
3366
- /** Injectable clock for deterministic tests. */
3367
- now?: () => number;
3368
- /** Injectable id generator for deterministic tests. */
3369
- generateId?: () => string;
3370
4092
  }
3371
4093
  /**
3372
- * Tuning for the model cascade. Everything here is optional, and the defaults
3373
- * cost nothing: with no settings at all, `modelCascade: "shadow"` measures what
3374
- * it can measure for free and makes no extra provider call.
4094
+ * Buffers usage records (counts, cost figures and opaque identifiers, never
4095
+ * content) and flushes them to the control plane. DETECTIVE
4096
+ * metering: strictly best-effort and NON-BLOCKING. `record()` is O(1) and
4097
+ * `flush()` never throws, so a slow, unreachable, or erroring control plane can
4098
+ * neither block nor crash the customer's request path. The buffer is hard-capped
4099
+ * so a sustained outage can never grow memory without bound.
3375
4100
  */
3376
- interface ModelCascadeSettings {
4101
+ declare class UsageEmitter {
4102
+ private readonly options;
4103
+ private readonly now;
4104
+ private readonly flushIntervalMs;
4105
+ private readonly maxBatch;
4106
+ private readonly maxBuffer;
4107
+ private buffer;
4108
+ private seatId;
4109
+ private ci;
4110
+ private timer;
4111
+ private inflight;
3377
4112
  /**
3378
- * Your own strong → cheap pairs, replacing the built-in table.
3379
- *
3380
- * Keys and values are model ids WITHOUT a vendor prefix (`"gpt-4o"`, not
3381
- * `"openai/gpt-4o"`); the caller's prefix is carried over automatically, and a
3382
- * dated snapshot inherits from its base id. A pair whose economics do not work
3383
- * out at your prices is refused with a reason in the report — declaring it here
3384
- * does not override the arithmetic.
4113
+ * CONSECUTIVE failed flushes. Warn on the third and then every twelfth: one
4114
+ * lost batch is normal, a whole hour is not. Same cadence as the revocation
4115
+ * poller on purpose — two different rhythms for the same class of failure
4116
+ * only confuse whoever reads the logs.
3385
4117
  */
3386
- pairs?: Record<string, string>;
4118
+ private fallosSeguidos;
4119
+ constructor(options: UsageEmitterOptions);
4120
+ /** Set the resolved seat identity (derivation is async; records emitted before
4121
+ * it resolves are stamped at flush time, so none are lost). */
4122
+ setSeat(seatId: string | undefined, ci: boolean): void;
4123
+ /** Record one request's usage. Never blocks; O(1). */
4124
+ record(report: SavingsReport): void;
4125
+ start(): void;
4126
+ /** Stop the timer and attempt a final flush. Safe to call more than once.
4127
+ * Fire-and-forget: for a CLEAN shutdown that must not lose the last batch,
4128
+ * use `stopAndDrain` and await it. */
4129
+ stop(): void;
4130
+ /** Clean shutdown: stop the timer and DRAIN the buffer, awaiting each POST.
4131
+ * Unlike `stop()` (fire-and-forget), this resolves only once the final batch
4132
+ * has actually been sent, so a graceful exit doesn't lose the last session's
4133
+ * usage. Bounded, so an unreachable control plane can't block shutdown. */
4134
+ stopAndDrain(maxIntentos?: number): Promise<void>;
3387
4135
  /**
3388
- * How many requests of a workload are routed before the automatic shut-off
3389
- * judges the measured escalation rate (default 20).
3390
- *
3391
- * The rate is only observable on requests that were actually routed, so there
3392
- * is no way to know without spending a bounded amount to find out. Worst case
3393
- * (every one of them escalates) that exploration costs `n x r` requests'
3394
- * worth of extra spend, once per workload per process — with a ratio of 0.06
3395
- * and the default, about 1.2 requests.
4136
+ * Flush one batch. Never throws. A transient server error (5xx) or network
4137
+ * failure re-queues the batch (capped); a 4xx (bad request / bad key) drops it,
4138
+ * since retrying a rejected payload would loop forever.
3396
4139
  */
3397
- exploration?: number;
4140
+ flush(): Promise<void>;
4141
+ private hacerFlush;
3398
4142
  /**
3399
- * PRICED OPT-IN, default 0 (off). Fraction of `"shadow"` requests on which the
3400
- * layer also calls the cheap model, purely to measure.
3401
- *
3402
- * Shadow mode is free precisely because it does not run the cheap model: it
3403
- * reports eligibility, the arithmetic, and what the swap would have been worth.
3404
- * What it cannot know for free is the one number that decides everything — how
3405
- * often the deterministic gate would have REJECTED the cheap answer. That
3406
- * answer does not exist until the cheap model writes one.
3407
- *
3408
- * So this buys it, and the price is exact: each sampled request costs one extra
3409
- * cheap call, i.e. `r` of what the request already cost — about 6% for
3410
- * gpt-4o → gpt-4o-mini. At `0.05` that is roughly 0.3% on top of your bill, and
3411
- * every sampled request reports its own `modelCascade.sampleCost`. It also
3412
- * DOUBLES that request's provider latency, because the extra call is sequential.
4143
+ * THE INGEST HAS BEEN FAILING FOR A WHILE AND NOBODY NOTICED.
3413
4144
  *
3414
- * The sampled request's tokens count as spend and are held out of the baseline,
3415
- * exactly like the repair retry: measurement must never pay for itself.
4145
+ * Metering is best-effort by design — it must never take down the customer's
4146
+ * request — and that turns every failure into SILENT data loss. Without this,
4147
+ * the only symptom is an empty dashboard, which is indistinguishable from
4148
+ * "you haven't sent anything yet": the symptom points the opposite way from
4149
+ * the cause.
3416
4150
  *
3417
- * Which requests get sampled is derived from the request's own cache key, so it
3418
- * is deterministic and stable — the same request is always sampled or always
3419
- * not, and no `Math.random` decides how your money is spent.
4151
+ * Warns on the third consecutive failure and then every twelfth. Not the
4152
+ * first — a lost request is normal — and not every one, which becomes noise.
3420
4153
  */
3421
- shadowSampleRate?: number;
4154
+ private avisarDeFallo;
4155
+ private requeue;
4156
+ private toWire;
3422
4157
  }
3423
- declare const DEFAULT_COST_MODEL: CostModel;
4158
+
4159
+ /** True when running in a CI / ephemeral environment. */
4160
+ declare function detectCi(): boolean;
4161
+ /**
4162
+ * Derive the machine seat id for a service-account key. Resolves to `undefined`
4163
+ * when no machine fingerprint is available (e.g. a browser) — the caller then
4164
+ * reports no seatId and the server counts the request under an "unknown" seat.
4165
+ * The id (`m-<32 hex>`) matches the control plane's identity charset.
4166
+ */
4167
+ declare function deriveSeatId(serviceAccountKey: string): Promise<string | undefined>;
3424
4168
 
3425
4169
  /** Fully-resolved, non-optional dependency set the pipeline runs against. */
3426
4170
  interface ResolvedDeps {
@@ -3445,11 +4189,33 @@ declare const DEFAULT_MAX_STALENESS_SECONDS: number;
3445
4189
  /** The public handle returned by {@link createSavingsLayer}. */
3446
4190
  interface SavingsLayer {
3447
4191
  generate<T = unknown>(request: SavingsRequest): Promise<SavingsResult<T>>;
4192
+ /**
4193
+ * Same request as generate(), delivered as a stream of events.
4194
+ *
4195
+ * Iterate the returned object for `text` deltas as the provider writes them,
4196
+ * then one `tool_call` event per requested tool call, then exactly one `done`
4197
+ * event carrying the same SavingsResult that `result` resolves to. The report
4198
+ * follows the same basis rules as generate(): the provider's final usage is
4199
+ * read at the end of the stream, and a stream that ends without it is
4200
+ * reported as `unmeasured`.
4201
+ *
4202
+ * Requests with an output contract (`response.schema`) or declared
4203
+ * `deferrable`, and providers without streaming support, are produced exactly
4204
+ * as generate() would and delivered in one piece; `report.streaming` says how
4205
+ * the answer was delivered and why.
4206
+ *
4207
+ * Work starts immediately, so awaiting `result` without iterating is valid.
4208
+ * Concurrent identical streamed requests each call the provider; a streamed
4209
+ * request can still reuse an identical generate() call already in flight.
4210
+ * Await `result` before calling close(), or its usage record may be lost.
4211
+ */
4212
+ generateStream<T = unknown>(request: SavingsRequest, options?: SavingsStreamOptions): SavingsStream<T>;
3448
4213
  /**
3449
4214
  * Stop the background revocation poller (when the online call-home was enabled
3450
- * via `licenseServerUrl`) and abort any in-flight request. Always safe to call —
3451
- * a no-op when there is no poller. Call it when shutting down a long-running
3452
- * process so the interval timer and any pending fetch are released.
4215
+ * via `licenseServerUrl`) and abort any in-flight request, and settle every
4216
+ * verification check still in flight (recorded as abandoned). Always safe to
4217
+ * call — a no-op when there is no poller. Call it when shutting down a
4218
+ * long-running process so the interval timer and any pending fetch are released.
3453
4219
  */
3454
4220
  close(): void;
3455
4221
  }
@@ -3475,4 +4241,386 @@ interface SavingsLayer {
3475
4241
  */
3476
4242
  declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"> & Partial<Pick<SavingsLayerConfig, "tokenizer">>): SavingsLayer;
3477
4243
 
3478
- 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, quitarPrefijoDeProveedor, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };
4244
+ /**
4245
+ * Rejection of `SavingsStream.result` when the stream was aborted before the
4246
+ * provider finished its answer. Nothing was cached and no usage was recorded.
4247
+ */
4248
+ declare class StreamAbortedError extends Error {
4249
+ /**
4250
+ * True when a request had already been sent to the provider: the provider may
4251
+ * bill the tokens it generated before the cancel, and that cost is in no report.
4252
+ */
4253
+ readonly requestSent: boolean;
4254
+ constructor(requestSent: boolean);
4255
+ }
4256
+
4257
+ /**
4258
+ * The parts of a SavingsReport the exporters read. A full SavingsReport
4259
+ * satisfies it, and so does a stored copy without `traces`: the exporters never
4260
+ * read `traces`, messages, answers or tool arguments.
4261
+ */
4262
+ type SavingsReportForExport = Pick<SavingsReport, "requestId" | "model" | "calls" | "tokens" | "cache" | "cost"> & Partial<Pick<SavingsReport, "qualityStatus" | "modelCascade" | "latencyMs" | "streaming">>;
4263
+ /**
4264
+ * One report and when its request was made: an ISO 8601 string, epoch
4265
+ * milliseconds or a Date. A SavingsReport carries no timestamp of its own, so
4266
+ * record `at` when the report comes back (`Date.now()` is fine).
4267
+ */
4268
+ interface SavingsRecord {
4269
+ at: string | number | Date;
4270
+ report: SavingsReportForExport;
4271
+ }
4272
+ /** An attribute value these exporters write: never null, never an object. */
4273
+ type SpanAttributeValue = string | number | boolean | string[];
4274
+ /** The attributes these exporters write on a span. */
4275
+ type SavingsSpanAttributes = Record<string, SpanAttributeValue>;
4276
+ /**
4277
+ * The part of an OpenTelemetry `Span` these exporters use. A `Span` from
4278
+ * `@opentelemetry/api` satisfies it.
4279
+ */
4280
+ interface SpanLike {
4281
+ setAttributes(attributes: SavingsSpanAttributes): unknown;
4282
+ setStatus?(status: {
4283
+ code: number;
4284
+ message?: string;
4285
+ }): unknown;
4286
+ end(): void;
4287
+ isRecording?(): boolean;
4288
+ }
4289
+ /** The part of OpenTelemetry's `SpanOptions` these exporters pass. */
4290
+ interface SpanStartOptionsLike {
4291
+ kind?: number;
4292
+ attributes?: SavingsSpanAttributes;
4293
+ }
4294
+ /** The part of an OpenTelemetry `Tracer` these exporters use. */
4295
+ interface TracerLike {
4296
+ startActiveSpan<T>(name: string, options: SpanStartOptionsLike, fn: (span: SpanLike) => T): T;
4297
+ }
4298
+ /** `trace` from @opentelemetry/api, or any TracerProvider. */
4299
+ interface TracerProviderLike {
4300
+ getTracer(name: string, version?: string): TracerLike;
4301
+ }
4302
+ /** Options shared by every function that writes span attributes. */
4303
+ interface SavingsAttributeOptions {
4304
+ /**
4305
+ * ISO 4217 code of the report's money. Default "USD", the unit of the
4306
+ * built-in price catalog. If you configured `costModels` in another unit, say
4307
+ * so here: the report itself does not carry a currency.
4308
+ */
4309
+ currency?: string;
4310
+ /**
4311
+ * Mirror the figures as filterable Langfuse metadata
4312
+ * (`langfuse.observation.metadata.bivelio_*`, string values). `cost` also sets
4313
+ * `langfuse.observation.cost_details` to the report's actual cost, which
4314
+ * Langfuse then prefers over its own estimate; Langfuse reads that figure as
4315
+ * USD, so it is only written when `currency` is "USD". Both off by default.
4316
+ */
4317
+ langfuse?: {
4318
+ metadata?: boolean;
4319
+ cost?: boolean;
4320
+ };
4321
+ /**
4322
+ * Mirror the figures in `_dd.ml_obs.metadata` for Datadog LLM Observability,
4323
+ * as a JSON object under the key `bivelio`. Off by default: without it the
4324
+ * `com.bivelio.*` attributes still arrive in Datadog as tags.
4325
+ */
4326
+ datadog?: {
4327
+ metadata?: boolean;
4328
+ };
4329
+ }
4330
+ /** Options for {@link recordSavingsOnSpan}. */
4331
+ interface SavingsSpanOptions extends SavingsAttributeOptions {
4332
+ /**
4333
+ * Also write the GenAI usage attributes and `gen_ai.response.model`.
4334
+ * Default false: the span you pass may be an HTTP or job span, and marking it
4335
+ * as an LLM call would misclassify it in tools that key on `gen_ai.*`.
4336
+ */
4337
+ genAi?: boolean;
4338
+ }
4339
+ /** Options for {@link instrumentSavingsLayer}. */
4340
+ interface SavingsTracingOptions extends SavingsAttributeOptions {
4341
+ /** `trace` from @opentelemetry/api, a TracerProvider, or a Tracer. */
4342
+ tracer: TracerLike | TracerProviderLike;
4343
+ /**
4344
+ * The `gen_ai.provider.name` value. Default: derived from the model id prefix
4345
+ * (`openai/` gives "openai"); omitted when the model id has no prefix.
4346
+ */
4347
+ providerName?: string;
4348
+ }
4349
+ /**
4350
+ * What to pass to a Langfuse generation's `update()`. `usageDetails` uses
4351
+ * Langfuse's exclusive buckets: `input` does not include
4352
+ * `cache_read_input_tokens`.
4353
+ */
4354
+ interface LangfuseGenerationUpdate {
4355
+ model: string;
4356
+ usageDetails?: Record<string, number>;
4357
+ costDetails?: Record<string, number>;
4358
+ metadata: Record<string, string | number>;
4359
+ }
4360
+ /** Options for {@link toSavingsSeries}. */
4361
+ interface SavingsSeriesOptions {
4362
+ /** Length of each period, in UTC. Default "day". */
4363
+ granularity?: "hour" | "day";
4364
+ /** ISO 4217 code of the reports' money. Default "USD". */
4365
+ currency?: string;
4366
+ /**
4367
+ * Extra grouping labels for each record (team, feature, environment). Keys
4368
+ * must not repeat a column of {@link SavingsSeriesRow}.
4369
+ */
4370
+ dimensions?: (record: SavingsRecord) => Record<string, string> | undefined;
4371
+ }
4372
+ /**
4373
+ * One period, model and basis of the savings series. Money is in `currency`;
4374
+ * each saving column holds money of its own basis only, so summing one column
4375
+ * never mixes measured and estimated figures.
4376
+ */
4377
+ type SavingsSeriesRow = {
4378
+ /** Start of the period, inclusive (`YYYY-MM-DDTHH:mm:ssZ`, UTC). */
4379
+ periodStart: string;
4380
+ /** End of the period, exclusive (`YYYY-MM-DDTHH:mm:ssZ`, UTC). */
4381
+ periodEnd: string;
4382
+ /** The UTC date of `periodStart` (`YYYY-MM-DD`). */
4383
+ date: string;
4384
+ /** The model id as the report names it (the model that served the answer). */
4385
+ model: string;
4386
+ /** Where the money in this row comes from. `shadow` reports are never exported. */
4387
+ basis: Exclude<CostBasis, "shadow">;
4388
+ currency: string;
4389
+ /** Reports in this row. */
4390
+ requests: number;
4391
+ /** LLM calls that reached the provider. */
4392
+ providerCalls: number;
4393
+ /** LLM calls that never executed. */
4394
+ avoidedCalls: number;
4395
+ /** Sum of `cost.baselineEstimate`; null when basis is "unmeasured". */
4396
+ baselineCost: number | null;
4397
+ /** Sum of `cost.actual`; null when basis is "unmeasured". */
4398
+ actualCost: number | null;
4399
+ /** Sum of `baselineEstimate - actual` on "measured" rows; null on every other row. */
4400
+ measuredSavings: number | null;
4401
+ /** Sum of `baselineEstimate - actual` on "reference" rows; null on every other row. */
4402
+ referenceSavings: number | null;
4403
+ /** Sum of `baselineEstimate - actual` on "estimated" rows; null on every other row. */
4404
+ estimatedSavings: number | null;
4405
+ /** Sum of `cost.eliminatedInputFloorCost`, a separate lane; null when no report carried it or basis is "unmeasured". */
4406
+ eliminatedInputFloorCost: number | null;
4407
+ baselineInputTokens: number;
4408
+ actualInputTokens: number;
4409
+ eliminatedInputTokens: number;
4410
+ compressedInputTokens: number;
4411
+ /** Sum of `tokens.providerCachedInput`; null when no report in the row carried it. */
4412
+ providerCachedInputTokens: number | null;
4413
+ baselineOutputTokens: number;
4414
+ actualOutputTokens: number;
4415
+ eliminatedOutputTokens: number;
4416
+ /** The labels returned by `options.dimensions`, empty without it. */
4417
+ dimensions: Readonly<Record<string, string>>;
4418
+ };
4419
+ /** Options for {@link toFocusRows}. */
4420
+ interface FocusExportOptions {
4421
+ /**
4422
+ * Your billing account id with the LLM provider (for example your OpenAI
4423
+ * organization id), so these rows join the provider's own cost rows.
4424
+ * Required: the SDK does not know it.
4425
+ */
4426
+ billingAccountId: string | ((record: SavingsRecord) => string);
4427
+ /** Display name of that billing account, when the provider has one. */
4428
+ billingAccountName?: string | ((record: SavingsRecord) => string | undefined);
4429
+ /** The provider sub account (project, workspace) a record belongs to. */
4430
+ subAccount?: (record: SavingsRecord) => {
4431
+ id: string;
4432
+ name: string;
4433
+ } | undefined;
4434
+ /**
4435
+ * FOCUS `ServiceProviderName`. Default: derived from the model id prefix
4436
+ * (`openai/` gives "OpenAI"). A model id whose provider cannot be derived
4437
+ * throws, because FOCUS does not allow a placeholder here.
4438
+ */
4439
+ serviceProviderName?: string | ((model: string) => string);
4440
+ /** FOCUS `InvoiceIssuerName` (a cloud marketplace or gateway that bills you). Default: the service provider. */
4441
+ invoiceIssuerName?: string | ((model: string) => string);
4442
+ /** FOCUS `HostProviderName` (the cloud hosting the model). Default: the service provider. */
4443
+ hostProviderName?: string | ((model: string) => string);
4444
+ /** FOCUS `ServiceName`. Default "BV-SALA savings ledger". */
4445
+ serviceName?: string;
4446
+ /**
4447
+ * The billing period a charge period belongs to. Default: the calendar month
4448
+ * in UTC. Set it if your provider invoices on another cycle.
4449
+ */
4450
+ billingPeriod?: (chargePeriodStart: Date) => {
4451
+ start: Date;
4452
+ end: Date;
4453
+ };
4454
+ /** Your own tags for a record, added to `Tags`. Keys starting with "bivelio/" are reserved. */
4455
+ tags?: (record: SavingsRecord) => Record<string, string> | undefined;
4456
+ /** Length of each charge period, in UTC. Default "day". */
4457
+ granularity?: "hour" | "day";
4458
+ /** ISO 4217 code of the reports' money. Default "USD". */
4459
+ currency?: string;
4460
+ }
4461
+ /**
4462
+ * A FOCUS 1.4 Cost and Usage row. Never a charge: every cost column is 0, so
4463
+ * joining these rows with your provider's bill adds savings columns and never
4464
+ * adds spend. The savings live in the `x_BiVelio*` columns, one per basis.
4465
+ */
4466
+ type FocusRow = {
4467
+ BilledCost: 0;
4468
+ EffectiveCost: 0;
4469
+ ListCost: 0;
4470
+ ContractedCost: 0;
4471
+ BillingCurrency: string;
4472
+ BillingAccountId: string;
4473
+ BillingAccountName: string | null;
4474
+ BillingPeriodStart: string;
4475
+ BillingPeriodEnd: string;
4476
+ ChargePeriodStart: string;
4477
+ ChargePeriodEnd: string;
4478
+ ChargeCategory: "Adjustment";
4479
+ ChargeClass: null;
4480
+ ChargeDescription: string;
4481
+ ChargeFrequency: "Usage-Based";
4482
+ InvoiceIssuerName: string;
4483
+ ServiceProviderName: string;
4484
+ HostProviderName: string;
4485
+ ServiceName: string;
4486
+ ServiceCategory: "AI and Machine Learning";
4487
+ ServiceSubcategory: "Generative AI";
4488
+ PricingQuantity: null;
4489
+ PricingUnit: null;
4490
+ SubAccountId: string | null;
4491
+ SubAccountName: string | null;
4492
+ /** JSON object: `bivelio/basis`, `bivelio/model` and your own tags. */
4493
+ Tags: string;
4494
+ x_BiVelioBasis: Exclude<CostBasis, "shadow">;
4495
+ x_BiVelioModelId: string;
4496
+ x_BiVelioRequestCount: number;
4497
+ x_BiVelioProviderCallCount: number;
4498
+ x_BiVelioAvoidedCallCount: number;
4499
+ x_BiVelioBaselineCost: number | null;
4500
+ x_BiVelioActualCost: number | null;
4501
+ x_BiVelioMeasuredSavings: number | null;
4502
+ x_BiVelioReferenceSavings: number | null;
4503
+ x_BiVelioEstimatedSavings: number | null;
4504
+ x_BiVelioEliminatedInputFloorCost: number | null;
4505
+ x_BiVelioBaselineInputTokens: number;
4506
+ x_BiVelioActualInputTokens: number;
4507
+ x_BiVelioEliminatedInputTokens: number;
4508
+ x_BiVelioCompressedInputTokens: number;
4509
+ x_BiVelioProviderCachedInputTokens: number | null;
4510
+ x_BiVelioBaselineOutputTokens: number;
4511
+ x_BiVelioActualOutputTokens: number;
4512
+ x_BiVelioEliminatedOutputTokens: number;
4513
+ };
4514
+ /** A value {@link toCsv} can write. null and undefined become an empty field. */
4515
+ type CsvValue = string | number | boolean | null | undefined;
4516
+
4517
+ /**
4518
+ * The `com.bivelio.*` attributes for one report: the four ledgers, the basis,
4519
+ * the cost and the ids. Money is in `options.currency` (default "USD"), and the
4520
+ * money saved goes in one key per basis (`com.bivelio.cost.saved.measured`,
4521
+ * `.reference`, `.estimated`), so a sum over one key never mixes measured and
4522
+ * estimated money. `unmeasured` and `shadow` reports carry their counters and
4523
+ * no money. Nothing here is content: only ids, counts, money and enums.
4524
+ */
4525
+ declare function savingsSpanAttributes(report: SavingsReportForExport, options?: SavingsAttributeOptions): SavingsSpanAttributes;
4526
+
4527
+ /**
4528
+ * Write the savings figures of one report on a span you already have (for
4529
+ * example `trace.getActiveSpan()`), as `com.bivelio.*` attributes. It does not
4530
+ * end the span, never throws and does nothing on a span that is not recording.
4531
+ *
4532
+ * It writes no `gen_ai.*` attribute unless `genAi: true`, and then only the
4533
+ * usage and `gen_ai.response.model`. Tools that key on `gen_ai.*` (Datadog LLM
4534
+ * Observability drops spans without one) need the span from
4535
+ * {@link instrumentSavingsLayer} instead.
4536
+ */
4537
+ declare function recordSavingsOnSpan(span: SpanLike, report: SavingsReportForExport, options?: SavingsSpanOptions): void;
4538
+ /**
4539
+ * Returns a SavingsLayer that opens one OpenTelemetry span per request, with
4540
+ * the same results and the same errors: `generate()` resolves to the very
4541
+ * result object, `generateStream()` returns the very stream.
4542
+ *
4543
+ * The span is named `chat {model}`, has kind CLIENT and is active while the
4544
+ * request runs, so HTTP spans of your provider call nest under it. It carries
4545
+ * the GenAI semantic conventions (as published in OpenTelemetry semantic
4546
+ * conventions v1.41.1: operation, provider, request model and parameters,
4547
+ * response model, usage) and the `com.bivelio.*` savings attributes of
4548
+ * {@link savingsSpanAttributes}. An avoided call is still a span, with zero
4549
+ * usage: no token was billed.
4550
+ *
4551
+ * Nothing is sent anywhere by this function: spans go to the exporters you
4552
+ * configured in your OpenTelemetry SDK. No prompt, answer, tool argument or
4553
+ * error message is written; a failed request records only `error.type`.
4554
+ *
4555
+ * Telemetry never changes a request: if the tracer fails, the request runs
4556
+ * without a span. An invalid `tracer` throws a TypeError here, at startup.
4557
+ */
4558
+ declare function instrumentSavingsLayer(layer: SavingsLayer, options: SavingsTracingOptions): SavingsLayer;
4559
+
4560
+ /**
4561
+ * The fields to pass to a Langfuse generation's `update()` when you trace with
4562
+ * `@langfuse/tracing` by hand: `generation.update(toLangfuseGeneration(report))`.
4563
+ *
4564
+ * - `model` drops a known provider prefix (`openai/gpt-4o-mini` becomes
4565
+ * `gpt-4o-mini`), so Langfuse can match it to its price table.
4566
+ * - `usageDetails` uses exclusive buckets: `input` excludes
4567
+ * `cache_read_input_tokens`. Cache writes are not broken out in the report, so
4568
+ * they stay inside `input`. Left out when the provider reported no usage.
4569
+ * - `costDetails` is `{ total: report.cost.actual }` only with
4570
+ * `includeCost: true`, when `currency` is "USD" (Langfuse reads it as USD) and
4571
+ * the basis has money. It includes the cache-write premium; Langfuse then
4572
+ * uses it instead of its own estimate.
4573
+ * - `metadata` holds the savings figures (`bivelio_*`), one saving key per basis.
4574
+ */
4575
+ declare function toLangfuseGeneration(report: SavingsReportForExport, options?: {
4576
+ currency?: string;
4577
+ includeCost?: boolean;
4578
+ }): LangfuseGenerationUpdate;
4579
+
4580
+ /**
4581
+ * Savings per period (UTC day by default), model, basis and your own
4582
+ * dimensions: the series to load as a business metric or telemetry stream in a
4583
+ * FinOps tool. Same aggregation as {@link toFocusRows}.
4584
+ *
4585
+ * Each saving column holds money of its own basis only, and `shadow` reports
4586
+ * are left out. For a metric that should match your provider invoice, use
4587
+ * `measuredSavings` and `referenceSavings`; `estimatedSavings` has its own
4588
+ * column on purpose. Throws a TypeError on an invalid `at`, currency or
4589
+ * dimension.
4590
+ */
4591
+ declare function toSavingsSeries(records: Iterable<SavingsRecord>, options?: SavingsSeriesOptions): SavingsSeriesRow[];
4592
+
4593
+ /**
4594
+ * Your SavingsReports as FOCUS 1.4 Cost and Usage rows, one per charge period
4595
+ * (UTC day by default), model, basis, billing account, sub account and tag set.
4596
+ *
4597
+ * Every cost column (`BilledCost`, `EffectiveCost`, `ListCost`,
4598
+ * `ContractedCost`) is 0 by design: your provider's bill already carries the
4599
+ * spend, so joining these rows with it adds savings and never adds cost.
4600
+ * `ChargeCategory` is "Adjustment" and nothing here invents a SKU or a price.
4601
+ *
4602
+ * The savings are in custom columns, one per basis: `x_BiVelioMeasuredSavings`,
4603
+ * `x_BiVelioReferenceSavings` and `x_BiVelioEstimatedSavings`; only the column
4604
+ * of the row's basis holds a value. `unmeasured` rows carry counts and null
4605
+ * money, and `shadow` reports are left out. `Tags` carries `bivelio/basis` and
4606
+ * `bivelio/model` plus your own tags.
4607
+ *
4608
+ * Throws a TypeError on an invalid `at`, currency or option, on a user tag
4609
+ * starting with "bivelio/", and when the provider of a model cannot be
4610
+ * derived and `serviceProviderName` is not given.
4611
+ */
4612
+ declare function toFocusRows(records: Iterable<SavingsRecord>, options: FocusExportOptions): FocusRow[];
4613
+
4614
+ /**
4615
+ * Rows to RFC 4180 CSV: CRLF line breaks, quoted fields where needed, null as
4616
+ * an empty field and numbers as plain decimals (no exponent, at most 10
4617
+ * decimal places). The header is `options.columns` or the keys of the rows in
4618
+ * first-seen order. `toCsv([])` is an empty string; with `columns` it is the
4619
+ * header alone. A value that is not a string, number, boolean or null throws a
4620
+ * TypeError: map each row to flat columns first.
4621
+ */
4622
+ declare function toCsv(rows: readonly Readonly<Record<string, CsvValue>>[], options?: {
4623
+ columns?: readonly string[];
4624
+ }): string;
4625
+
4626
+ export { type AnswerSource, type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CacheWriteReport, 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, type CsvValue, 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 FocusExportOptions, type FocusRow, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, InMemoryToolSetFreezer, type JsonSchema, type LangfuseGenerationUpdate, 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 PriceLeverSaving, type PriceLeverSettings, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderStreamEvent, type ProviderStreamOptions, type ProviderUsage, QUALITY_STATUSES, type QualityCacheMatch, type QualityCascadeCheck, type QualityLedger, type QualityRepairCheck, type QualitySchemaCheck, type QualityStatus, type QualityTruncationCheck, 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 SavingsAdapterLevers, type SavingsAdapterOptions, type SavingsAttributeOptions, type SavingsCallOptions, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsRecord, type SavingsReport, type SavingsReportForExport, type SavingsRequest, type SavingsResult, type SavingsSeriesOptions, type SavingsSeriesRow, type SavingsSpanAttributes, type SavingsSpanOptions, type SavingsStream, type SavingsStreamEvent, type SavingsStreamOptions, type SavingsToggles, type SavingsTracingOptions, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, type SpanAttributeValue, type SpanLike, type SpanStartOptionsLike, StreamAbortedError, type StreamingReport, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, type ToolSetFreezer, TraceCollector, type TraceEvent, type TracerLike, type TracerProviderLike, 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, instrumentSavingsLayer, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, licenseKeyShape, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeLicenseKey, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, quitarPrefijoDeProveedor, randomId, recordSavingsOnSpan, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, savingsSpanAttributes, sha256, shouldCompress, stableStringify, toCsv, toFocusRows, toLangfuseGeneration, toSavingsSeries, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };