@bivelio/savings-layer 0.4.28 → 0.4.30
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 +21 -1
- package/dist/adapters/anthropic-sdk.js +69 -66
- package/dist/gateway/bin.js +659 -647
- package/dist/index.d.ts +97 -7
- package/dist/index.js +1186 -1167
- 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.30";
|
|
315
329
|
|
|
316
330
|
/**
|
|
317
331
|
* Environment-agnostic UUID generation.
|
|
@@ -483,7 +497,12 @@ interface ChatMessage {
|
|
|
483
497
|
* ids included.
|
|
484
498
|
*/
|
|
485
499
|
toolCalls?: ToolCallRequest[];
|
|
486
|
-
/**
|
|
500
|
+
/**
|
|
501
|
+
* Opaque provider metadata; never used for the exact-cache key. The one
|
|
502
|
+
* exception is `tool_call_id` on a `role: "tool"` message: the OpenAI
|
|
503
|
+
* adapter still sends it as the call id when `toolCallId` is absent,
|
|
504
|
+
* so the key reads it as that same field.
|
|
505
|
+
*/
|
|
487
506
|
metadata?: Record<string, unknown>;
|
|
488
507
|
}
|
|
489
508
|
/** How a request is classified by the execution router. */
|
|
@@ -1121,6 +1140,14 @@ interface SavingsResult<T = unknown> {
|
|
|
1121
1140
|
* (caché/resolver), que solo sirven respuestas finales completas.
|
|
1122
1141
|
*/
|
|
1123
1142
|
finishReason?: string;
|
|
1143
|
+
/**
|
|
1144
|
+
* The refusal message the model returned instead of an answer (OpenAI
|
|
1145
|
+
* Structured Outputs: `message.refusal`, with `finish_reason: "stop"` and no
|
|
1146
|
+
* content). When present, the request was refused: `text` is empty and no
|
|
1147
|
+
* output contract was met. It is measured like any call, and it is never
|
|
1148
|
+
* repaired, cached or replayed.
|
|
1149
|
+
*/
|
|
1150
|
+
refusal?: string;
|
|
1124
1151
|
report: SavingsReport;
|
|
1125
1152
|
}
|
|
1126
1153
|
/** One event of a streamed answer. */
|
|
@@ -1296,8 +1323,9 @@ interface QualityRepairCheck {
|
|
|
1296
1323
|
* - `decode_time_constraint`: generation was already constrained, so a retry
|
|
1297
1324
|
* would not change the outcome.
|
|
1298
1325
|
* - `provider_refusal`: the provider refused the request (`finishReason:
|
|
1299
|
-
* "content_filter"`, Anthropic's `stop_reason: "refusal"
|
|
1300
|
-
* same model would be refused, and may
|
|
1326
|
+
* "content_filter"`, Anthropic's `stop_reason: "refusal"`, or OpenAI's
|
|
1327
|
+
* `message.refusal`); a repair on the same model would be refused, and may
|
|
1328
|
+
* be billed, again.
|
|
1301
1329
|
*/
|
|
1302
1330
|
skipReason?: "output_truncated" | "schema_defect" | "would_cost_more_than_no_layer" | "decode_time_constraint" | "provider_refusal";
|
|
1303
1331
|
}
|
|
@@ -1849,6 +1877,25 @@ interface CanonicalRequest {
|
|
|
1849
1877
|
normalizedText: string;
|
|
1850
1878
|
systemPromptHash: string;
|
|
1851
1879
|
toolsetHash: string;
|
|
1880
|
+
/**
|
|
1881
|
+
* Hash of the tool-loop fields of the conversation, the part of each turn
|
|
1882
|
+
* that `normalizedText` does not carry: the calls on an assistant turn (name,
|
|
1883
|
+
* arguments and id) and, on a `role: "tool"` message, the call it answers,
|
|
1884
|
+
* its error flag and its name. Part of the exact key, the in-flight key and
|
|
1885
|
+
* the semantic filter, so two conversations that differ only there never
|
|
1886
|
+
* share an answer.
|
|
1887
|
+
*
|
|
1888
|
+
* Call ids count by what they pair, not by their value: results matched to
|
|
1889
|
+
* other calls give another hash, while the same conversation with its ids
|
|
1890
|
+
* consistently renamed (a new run of the same agent loop gets fresh ids from
|
|
1891
|
+
* the provider) keeps it.
|
|
1892
|
+
*
|
|
1893
|
+
* Absent when the request carries none of them: the keys are then built from
|
|
1894
|
+
* exactly the same material as before this field existed. A field that is
|
|
1895
|
+
* present but empty or false (`toolCalls: []`, `isError: false`, `name: ""`)
|
|
1896
|
+
* reaches the provider exactly as an absent one, and counts as absent.
|
|
1897
|
+
*/
|
|
1898
|
+
toolTurnsHash?: string;
|
|
1852
1899
|
/** Hash of the structured payload; part of the cache key so different data never collides. */
|
|
1853
1900
|
structuredDataHash: string;
|
|
1854
1901
|
schemaVersion: string;
|
|
@@ -2122,6 +2169,13 @@ interface SemanticRecord {
|
|
|
2122
2169
|
safetyPolicyVersion: string;
|
|
2123
2170
|
/** Hash of the declared tool catalog (ids, risk and parameters). */
|
|
2124
2171
|
toolsetHash: string;
|
|
2172
|
+
/**
|
|
2173
|
+
* Hash of the tool-loop fields of the conversation (tool calls, the call each
|
|
2174
|
+
* tool result answers, its error flag and name); absent when it had none.
|
|
2175
|
+
* Compared for equality like the other identity fields: similarity is scored
|
|
2176
|
+
* on text only, and never decides between two tool loops.
|
|
2177
|
+
*/
|
|
2178
|
+
toolTurnsHash?: string;
|
|
2125
2179
|
/** Sampling temperature of the request, or null when it set none. */
|
|
2126
2180
|
temperature: number | null;
|
|
2127
2181
|
/** Sampling seed of the request, or null when it set none. */
|
|
@@ -3138,6 +3192,30 @@ interface ProviderResponse {
|
|
|
3138
3192
|
* podía distinguirlo y hacía exactamente eso.
|
|
3139
3193
|
*/
|
|
3140
3194
|
finishReason?: "stop" | "length" | "content_filter" | "tool_calls" | (string & {});
|
|
3195
|
+
/**
|
|
3196
|
+
* The refusal message the model returned INSTEAD of an answer, exactly as the
|
|
3197
|
+
* provider wrote it: OpenAI's `message.refusal` (Chat Completions with
|
|
3198
|
+
* Structured Outputs), which arrives with `finish_reason: "stop"` and no
|
|
3199
|
+
* `content`. Present only when non-empty.
|
|
3200
|
+
*
|
|
3201
|
+
* The layer treats it like `finishReason: "content_filter"`: the call is
|
|
3202
|
+
* measured, and the answer is never repaired against its contract, cached, or
|
|
3203
|
+
* replayed by reference. `text` and `finishReason` stay what the provider said.
|
|
3204
|
+
*/
|
|
3205
|
+
refusal?: string;
|
|
3206
|
+
/**
|
|
3207
|
+
* True when this answer is a refusal the provider documents as NOT billed:
|
|
3208
|
+
* Anthropic's Messages API refusal that arrives before any output, in a
|
|
3209
|
+
* category its published table marks as not billed (`cyber`,
|
|
3210
|
+
* `general_harms`, or a `null` category). See
|
|
3211
|
+
* https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback
|
|
3212
|
+
* («How refusals are billed»).
|
|
3213
|
+
*
|
|
3214
|
+
* The layer then measures this call at cost 0 and credits no saving on the
|
|
3215
|
+
* request at all (measured, estimated or by reference). Absent means billed
|
|
3216
|
+
* like any other call. Set it only from the provider's own response.
|
|
3217
|
+
*/
|
|
3218
|
+
unbilledRefusal?: boolean;
|
|
3141
3219
|
/**
|
|
3142
3220
|
* Llamadas a herramienta que pidió el modelo, tal cual las devolvió el
|
|
3143
3221
|
* proveedor.
|
|
@@ -3724,7 +3802,19 @@ interface SavingsLayerConfig {
|
|
|
3724
3802
|
* revocation list going stale. Nothing here throws and nothing here changes
|
|
3725
3803
|
* what the layer does — turning it on only removes the console noise, so set
|
|
3726
3804
|
* it when you have your own observability. Applies to the pipeline, the usage
|
|
3727
|
-
* emitter
|
|
3805
|
+
* emitter, the revocation poller and the provider adapters shipped in this
|
|
3806
|
+
* package alike.
|
|
3807
|
+
*
|
|
3808
|
+
* **One warning is NOT silenced: a rejected renewal credential.** When the
|
|
3809
|
+
* automatic pickup of the renewed license gets HTTP 401, 403 or 404 (the
|
|
3810
|
+
* `serviceAccountKey` was rotated, belongs to another organization, or
|
|
3811
|
+
* `licenseServerUrl` is wrong), the SDK warns even with this flag on. That is
|
|
3812
|
+
* not a degraded saving: the token will not renew and the layer will stop
|
|
3813
|
+
* operating when the grace period ends, and the failure happens inside the SDK
|
|
3814
|
+
* where your own observability cannot see it. It is printed once per status
|
|
3815
|
+
* code per layer, never on every renewal attempt, and it never contains the
|
|
3816
|
+
* key or any part of it. Fix the credential, or turn the pickup off with
|
|
3817
|
+
* {@link licenseRefresh}`.enabled = false`, to make it go away.
|
|
3728
3818
|
*/
|
|
3729
3819
|
silenceWarnings?: boolean;
|
|
3730
3820
|
/**
|
|
@@ -4104,7 +4194,8 @@ declare const registrarTokenizadorExacto: typeof registerExactTokenizer;
|
|
|
4104
4194
|
*
|
|
4105
4195
|
* A hash of the user text alone is NOT a safe cache key. Two requests may share
|
|
4106
4196
|
* text but differ in tenant, permission scope, model version, system prompt,
|
|
4107
|
-
* schema, toolset,
|
|
4197
|
+
* schema, toolset, tool-loop fields (tool calls, the call a tool result answers,
|
|
4198
|
+
* its error flag), locale, sampling, knowledge epoch or safety policy — any of
|
|
4108
4199
|
* which can change the correct answer. The exact key includes all of them.
|
|
4109
4200
|
*
|
|
4110
4201
|
* `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, not the one the
|
|
@@ -4902,7 +4993,6 @@ declare class RevocationPoller {
|
|
|
4902
4993
|
private consecutiveFailures;
|
|
4903
4994
|
private stopped;
|
|
4904
4995
|
private started;
|
|
4905
|
-
private warnedNoFetch;
|
|
4906
4996
|
/** ETag of the last 200; sent as If-None-Match so an unchanged list 304s (cheap no-op). */
|
|
4907
4997
|
private lastEtag;
|
|
4908
4998
|
/** 304 seguidos; al llegar al tope se fuerza una descarga firmada. */
|