@bivelio/savings-layer 0.4.29 → 0.4.31
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/README.md +80 -6
- package/dist/adapters/anthropic-sdk.js +69 -66
- package/dist/gateway/bin.js +759 -698
- package/dist/index.d.ts +131 -17
- package/dist/index.js +1306 -1225
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -232,6 +232,20 @@ declare class SavingsLedger {
|
|
|
232
232
|
providerCached(tokens: number): void;
|
|
233
233
|
/** Record the premium for `tokens` of cache write the provider bills at `multiplier`x on its own. */
|
|
234
234
|
providerCacheWritePremium(tokens: number, multiplier: number, price: CostModel): void;
|
|
235
|
+
/** Input tokens of calls the provider documents as not billed; already included in `actualInput`. */
|
|
236
|
+
unbilledInput: number;
|
|
237
|
+
/** Output tokens of those calls; already included in `actualOutput`. */
|
|
238
|
+
unbilledOutput: number;
|
|
239
|
+
/** Provider-cached input tokens of those calls; already included in `providerCachedInput`. */
|
|
240
|
+
unbilledCachedInput: number;
|
|
241
|
+
/** True when the answer served is a refusal the provider does not bill: no saving is credited. */
|
|
242
|
+
servedUnbilledRefusal: boolean;
|
|
243
|
+
/** Record a call the provider documents as not billed: its tokens stay counted and are priced at 0. */
|
|
244
|
+
unbilledCall(usage: {
|
|
245
|
+
inputTokens: number;
|
|
246
|
+
outputTokens: number;
|
|
247
|
+
cachedInputTokens?: number;
|
|
248
|
+
}): void;
|
|
235
249
|
/** Set when an avoided call is valued against the provider counter of an identical call that ran. */
|
|
236
250
|
reference: ReferenceCredit | null;
|
|
237
251
|
/** Set when an avoided cache or coalesced hit could not be valued by reference: why it stays estimated. */
|
|
@@ -311,7 +325,7 @@ declare function precioDeLista(model: string, at?: Date | number): CostModel | u
|
|
|
311
325
|
* on the customer's machine. `test/version-declarada.test.ts` ties this
|
|
312
326
|
* constant to package.json so the two can never drift.
|
|
313
327
|
*/
|
|
314
|
-
declare const BV_SALA_VERSION = "0.4.
|
|
328
|
+
declare const BV_SALA_VERSION = "0.4.31";
|
|
315
329
|
|
|
316
330
|
/**
|
|
317
331
|
* Environment-agnostic UUID generation.
|
|
@@ -532,7 +546,11 @@ interface OutputContract {
|
|
|
532
546
|
/** Stable identifier, e.g. "research-answer:v3". */
|
|
533
547
|
contractId: string;
|
|
534
548
|
schema: JsonSchema;
|
|
535
|
-
/**
|
|
549
|
+
/**
|
|
550
|
+
* Hard ceiling on generated tokens for this contract. A free-text request (no
|
|
551
|
+
* schema) sets its ceiling with {@link SavingsRequest.maxOutputTokens}; when
|
|
552
|
+
* both are set, the smaller one is sent.
|
|
553
|
+
*/
|
|
536
554
|
maxOutputTokens?: number;
|
|
537
555
|
/** Version marker included in the exact-cache key. */
|
|
538
556
|
version?: string;
|
|
@@ -1089,6 +1107,19 @@ interface SavingsRequest {
|
|
|
1089
1107
|
/** Sampling controls that participate in the exact-cache key. */
|
|
1090
1108
|
temperature?: number;
|
|
1091
1109
|
seed?: number;
|
|
1110
|
+
/**
|
|
1111
|
+
* Hard ceiling on generated tokens for this request, with or without an
|
|
1112
|
+
* output contract. It travels to every adapter: Anthropic `max_tokens`,
|
|
1113
|
+
* Chat Completions `max_completion_tokens` on OpenAI's own API and
|
|
1114
|
+
* `max_tokens` on any other OpenAI-compatible endpoint (see
|
|
1115
|
+
* `OpenAICompatibleOptions.maxTokensParam`). When `response.maxOutputTokens`
|
|
1116
|
+
* is also set, the smaller of the two is sent. Unset, nothing changes.
|
|
1117
|
+
*
|
|
1118
|
+
* It is a limit you set, not a saving: it never raises the estimated output
|
|
1119
|
+
* of an avoided call and is never credited as output avoided. An answer cut
|
|
1120
|
+
* by it (`finishReason: "length"`) is not cached. Must be a positive integer.
|
|
1121
|
+
*/
|
|
1122
|
+
maxOutputTokens?: number;
|
|
1092
1123
|
/** Tokenizer id to cost against; defaults to a model-derived estimate. */
|
|
1093
1124
|
tokenizer?: string;
|
|
1094
1125
|
/** Arbitrary structured payload to be serialized into the prompt. */
|
|
@@ -1322,11 +1353,12 @@ interface QualityTruncationCheck {
|
|
|
1322
1353
|
/** The stop reason exactly as the provider named it; absent when it reported none. */
|
|
1323
1354
|
finishReason?: string;
|
|
1324
1355
|
/**
|
|
1325
|
-
* Whether you set the limit (`
|
|
1356
|
+
* Whether you set the limit (`maxOutputTokens` on the request or on
|
|
1357
|
+
* `response`). When false, any
|
|
1326
1358
|
* limit that applied was a provider or adapter default.
|
|
1327
1359
|
*/
|
|
1328
1360
|
capSetByCaller: boolean;
|
|
1329
|
-
/** The limit
|
|
1361
|
+
/** The limit that was sent (the smaller of the two when both were set). Present only when `capSetByCaller`. */
|
|
1330
1362
|
outputLimit?: number;
|
|
1331
1363
|
}
|
|
1332
1364
|
/** How a cache hit matched this request. */
|
|
@@ -2226,8 +2258,13 @@ interface SemanticCacheOptions {
|
|
|
2226
2258
|
*
|
|
2227
2259
|
* Pass your own for true semantics; the local one only sees LEXICAL closeness.
|
|
2228
2260
|
* Either way the deterministic guard below still rejects a candidate whose
|
|
2229
|
-
* numbers
|
|
2230
|
-
*
|
|
2261
|
+
* numbers (in digits or spelled out in en/es/ca: "three", "twice", "half"),
|
|
2262
|
+
* negation parity or content words differ beyond its overlap floor. It is a
|
|
2263
|
+
* lexical guard, not a proof: a one-word entity swap in a long query can
|
|
2264
|
+
* still clear the overlap floor, and through 0.4.30 a number written in words
|
|
2265
|
+
* cleared it too (GSM8K: 477 of 564 such variants got their original's
|
|
2266
|
+
* answer). Calibrate on your own traffic before trusting it with answers
|
|
2267
|
+
* that differ by one word.
|
|
2231
2268
|
*/
|
|
2232
2269
|
embed?: EmbeddingFn;
|
|
2233
2270
|
/**
|
|
@@ -2544,7 +2581,20 @@ interface ToolSelection {
|
|
|
2544
2581
|
setIsStable: boolean;
|
|
2545
2582
|
}
|
|
2546
2583
|
interface ToolSelectorOptions {
|
|
2547
|
-
/**
|
|
2584
|
+
/**
|
|
2585
|
+
* Max tools the selector keeps when it has a SIGNAL to select on: a catalogue
|
|
2586
|
+
* whose tools declare `intents` and/or `risk`, with the router on. Default 8.
|
|
2587
|
+
*
|
|
2588
|
+
* It never truncates a catalogue it cannot rank. A catalogue that declares no
|
|
2589
|
+
* `intents` and no `risk` (the usual case: MCP and OpenAI tool lists) and every
|
|
2590
|
+
* request with `savings: { router: false }` go to the provider WHOLE, exactly as
|
|
2591
|
+
* the caller sent them, and the layer credits nothing for the tool block:
|
|
2592
|
+
* cutting by position left the model unable to call the rest (on BFCL
|
|
2593
|
+
* multi-turn, gpt-4o-mini hit the reference call 8/10 times directly and 1/10
|
|
2594
|
+
* through the layer). When the selector does prune a typed catalogue, the
|
|
2595
|
+
* report's `tool-selector` trace lists what was left out and the SDK warns
|
|
2596
|
+
* (once per process, silenced by `silenceWarnings`).
|
|
2597
|
+
*/
|
|
2548
2598
|
maxTools?: number;
|
|
2549
2599
|
/** Tokenizer-aware scorer for tie-breaking (optional). */
|
|
2550
2600
|
scoreIntentMatch?: (text: string, tool: ToolDescriptor) => number;
|
|
@@ -2590,16 +2640,9 @@ declare class ToolSelector {
|
|
|
2590
2640
|
private readonly freezer;
|
|
2591
2641
|
constructor(opts?: ToolSelectorOptions);
|
|
2592
2642
|
/**
|
|
2593
|
-
*
|
|
2594
|
-
*
|
|
2595
|
-
*
|
|
2596
|
-
* Se usa cuando el llamante apaga el router (`savings:{router:false}`): sin
|
|
2597
|
-
* clasificación no hay de dónde derivar el riesgo, y filtrar sobre el
|
|
2598
|
-
* `defaultRoute()` tiraba en silencio las herramientas de escritura. Apagar el
|
|
2599
|
-
* router significa "yo elijo las herramientas", así que la relevancia pasa a
|
|
2600
|
-
* ser del llamante — pero el tope de presupuesto NO se levanta: mandar 200
|
|
2601
|
-
* esquemas al proveedor es un 400 seguro y una petición que antes funcionaba
|
|
2602
|
-
* pasaría a fallar en duro.
|
|
2643
|
+
* Every tool the caller passed, as passed: none left out, none credited.
|
|
2644
|
+
* Used when there is no signal to select on (`savings: { router: false }`,
|
|
2645
|
+
* or a catalogue with no `intents` and no `risk`).
|
|
2603
2646
|
*/
|
|
2604
2647
|
passthrough(tools: ToolDescriptor[] | undefined): ToolSelection;
|
|
2605
2648
|
select(text: string, tools: ToolDescriptor[] | undefined, execution: ExecutionClass,
|
|
@@ -3000,6 +3043,10 @@ interface ProviderRequest {
|
|
|
3000
3043
|
tools?: ToolDescriptor[];
|
|
3001
3044
|
/** Output contract to enforce via constrained decoding, when available. */
|
|
3002
3045
|
schema?: JsonSchema;
|
|
3046
|
+
/**
|
|
3047
|
+
* Output cap chosen by the caller: the smaller of `SavingsRequest.maxOutputTokens`
|
|
3048
|
+
* and `response.maxOutputTokens`. Each adapter maps it to its own field.
|
|
3049
|
+
*/
|
|
3003
3050
|
maxOutputTokens?: number;
|
|
3004
3051
|
temperature?: number;
|
|
3005
3052
|
seed?: number;
|
|
@@ -3189,6 +3236,19 @@ interface ProviderResponse {
|
|
|
3189
3236
|
* replayed by reference. `text` and `finishReason` stay what the provider said.
|
|
3190
3237
|
*/
|
|
3191
3238
|
refusal?: string;
|
|
3239
|
+
/**
|
|
3240
|
+
* True when this answer is a refusal the provider documents as NOT billed:
|
|
3241
|
+
* Anthropic's Messages API refusal that arrives before any output, in a
|
|
3242
|
+
* category its published table marks as not billed (`cyber`,
|
|
3243
|
+
* `general_harms`, or a `null` category). See
|
|
3244
|
+
* https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback
|
|
3245
|
+
* («How refusals are billed»).
|
|
3246
|
+
*
|
|
3247
|
+
* The layer then measures this call at cost 0 and credits no saving on the
|
|
3248
|
+
* request at all (measured, estimated or by reference). Absent means billed
|
|
3249
|
+
* like any other call. Set it only from the provider's own response.
|
|
3250
|
+
*/
|
|
3251
|
+
unbilledRefusal?: boolean;
|
|
3192
3252
|
/**
|
|
3193
3253
|
* Llamadas a herramienta que pidió el modelo, tal cual las devolvió el
|
|
3194
3254
|
* proveedor.
|
|
@@ -3477,7 +3537,28 @@ interface SavingsLayerConfig {
|
|
|
3477
3537
|
provider: Provider;
|
|
3478
3538
|
tokenizer: TokenizerRegistry;
|
|
3479
3539
|
exactCache?: ExactCache;
|
|
3540
|
+
/**
|
|
3541
|
+
* WHERE reworded questions are looked up and stored. Giving the layer a cache
|
|
3542
|
+
* does NOT switch it on: a request still has to ask for it with
|
|
3543
|
+
* `savings.semanticCache`, or you set {@link semanticCacheDefault} to ask for
|
|
3544
|
+
* it on every request. A layer that has a cache and a request that neither
|
|
3545
|
+
* asks for it nor turns it off gets a one-time console warning (silenced by
|
|
3546
|
+
* `silenceWarnings`, or by setting `semanticCacheDefault`, `false` included).
|
|
3547
|
+
*/
|
|
3480
3548
|
semanticCache?: InMemorySemanticCache;
|
|
3549
|
+
/**
|
|
3550
|
+
* Default for `savings.semanticCache` on every request of this layer. A
|
|
3551
|
+
* request's own value always wins, `false` included. Unset, the semantic cache
|
|
3552
|
+
* is used only by requests that ask for it.
|
|
3553
|
+
*
|
|
3554
|
+
* OPT-IN, and deliberately a separate field from {@link semanticCache}: the
|
|
3555
|
+
* cache is the backend, this is the decision to serve answers to reworded
|
|
3556
|
+
* questions, which can serve a wrong answer. Prefer it with a cache built with
|
|
3557
|
+
* `calibration`, which serves nothing until your own traffic certifies a
|
|
3558
|
+
* floor. `false` states that you switch it on per request, and silences the
|
|
3559
|
+
* warning about a cache that no request uses.
|
|
3560
|
+
*/
|
|
3561
|
+
semanticCacheDefault?: boolean | "safe-only" | "include-agentic";
|
|
3481
3562
|
singleflight?: Singleflight;
|
|
3482
3563
|
resolvers?: ResolverRegistry;
|
|
3483
3564
|
router?: ExecutionRouter;
|
|
@@ -3777,6 +3858,17 @@ interface SavingsLayerConfig {
|
|
|
3777
3858
|
* it when you have your own observability. Applies to the pipeline, the usage
|
|
3778
3859
|
* emitter, the revocation poller and the provider adapters shipped in this
|
|
3779
3860
|
* package alike.
|
|
3861
|
+
*
|
|
3862
|
+
* **One warning is NOT silenced: a rejected renewal credential.** When the
|
|
3863
|
+
* automatic pickup of the renewed license gets HTTP 401, 403 or 404 (the
|
|
3864
|
+
* `serviceAccountKey` was rotated, belongs to another organization, or
|
|
3865
|
+
* `licenseServerUrl` is wrong), the SDK warns even with this flag on. That is
|
|
3866
|
+
* not a degraded saving: the token will not renew and the layer will stop
|
|
3867
|
+
* operating when the grace period ends, and the failure happens inside the SDK
|
|
3868
|
+
* where your own observability cannot see it. It is printed once per status
|
|
3869
|
+
* code per layer, never on every renewal attempt, and it never contains the
|
|
3870
|
+
* key or any part of it. Fix the credential, or turn the pickup off with
|
|
3871
|
+
* {@link licenseRefresh}`.enabled = false`, to make it go away.
|
|
3780
3872
|
*/
|
|
3781
3873
|
silenceWarnings?: boolean;
|
|
3782
3874
|
/**
|
|
@@ -4549,6 +4641,25 @@ interface OpenAICompatibleOptions {
|
|
|
4549
4641
|
* saving. Benchmarks that make real cost claims must set this.
|
|
4550
4642
|
*/
|
|
4551
4643
|
requireUsage?: boolean;
|
|
4644
|
+
/**
|
|
4645
|
+
* Which Chat Completions field carries the output cap (`maxOutputTokens`).
|
|
4646
|
+
*
|
|
4647
|
+
* Default (undefined): `max_completion_tokens` when `baseUrl` is OpenAI's own
|
|
4648
|
+
* API (https://api.openai.com), `max_tokens` everywhere else. OpenAI
|
|
4649
|
+
* deprecated `max_tokens` when it introduced `max_completion_tokens`
|
|
4650
|
+
* (2024-09-12) and its reasoning models (o-series, gpt-5.x) reject it with a
|
|
4651
|
+
* 400; `max_completion_tokens` is accepted by every current OpenAI chat
|
|
4652
|
+
* model. OpenAI-compatible servers (proxies, self-hosted inference servers,
|
|
4653
|
+
* routers, Azure with an older api-version, a local gateway) understand
|
|
4654
|
+
* `max_tokens`, and some silently IGNORE a field they do not know, which
|
|
4655
|
+
* would drop the cap without an error.
|
|
4656
|
+
*
|
|
4657
|
+
* With the default, a 400/422 that names the field sent as unsupported is
|
|
4658
|
+
* retried once with the other one (it is rejected before any token is
|
|
4659
|
+
* generated, so nothing is billed twice) and the model is remembered. An
|
|
4660
|
+
* explicit value always wins and is never switched.
|
|
4661
|
+
*/
|
|
4662
|
+
maxTokensParam?: "max_tokens" | "max_completion_tokens";
|
|
4552
4663
|
}
|
|
4553
4664
|
/**
|
|
4554
4665
|
* ¿Cumple este esquema las reglas del modo `strict` de OpenAI?
|
|
@@ -4578,6 +4689,7 @@ declare class OpenAICompatibleProvider implements Provider {
|
|
|
4578
4689
|
/** Models of this endpoint already known to reject `response_format`. */
|
|
4579
4690
|
private readonly sinResponseFormat;
|
|
4580
4691
|
private streamOptionsRejected;
|
|
4692
|
+
private readonly campoDeTopeAprendido;
|
|
4581
4693
|
private readonly origen;
|
|
4582
4694
|
constructor(opts: OpenAICompatibleOptions);
|
|
4583
4695
|
generate(req: ProviderRequest, options?: ProviderStreamOptions): Promise<ProviderResponse>;
|
|
@@ -4591,6 +4703,8 @@ declare class OpenAICompatibleProvider implements Provider {
|
|
|
4591
4703
|
* as one text event.
|
|
4592
4704
|
*/
|
|
4593
4705
|
generateStream(req: ProviderRequest, options?: ProviderStreamOptions): AsyncGenerator<ProviderStreamEvent, void, undefined>;
|
|
4706
|
+
private campoDeTope;
|
|
4707
|
+
private corregirCampoDeTope;
|
|
4594
4708
|
private enviar;
|
|
4595
4709
|
private construirCuerpo;
|
|
4596
4710
|
/** Translate a 2xx endpoint response into the provider-neutral shape. */
|