@bivelio/savings-layer 0.4.28 → 0.4.29

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
@@ -311,7 +311,7 @@ declare function precioDeLista(model: string, at?: Date | number): CostModel | u
311
311
  * on the customer's machine. `test/version-declarada.test.ts` ties this
312
312
  * constant to package.json so the two can never drift.
313
313
  */
314
- declare const BV_SALA_VERSION = "0.4.28";
314
+ declare const BV_SALA_VERSION = "0.4.29";
315
315
 
316
316
  /**
317
317
  * Environment-agnostic UUID generation.
@@ -483,7 +483,12 @@ interface ChatMessage {
483
483
  * ids included.
484
484
  */
485
485
  toolCalls?: ToolCallRequest[];
486
- /** Opaque provider metadata; never used for the exact-cache key. */
486
+ /**
487
+ * Opaque provider metadata; never used for the exact-cache key. The one
488
+ * exception is `tool_call_id` on a `role: "tool"` message: the OpenAI
489
+ * adapter still sends it as the call id when `toolCallId` is absent,
490
+ * so the key reads it as that same field.
491
+ */
487
492
  metadata?: Record<string, unknown>;
488
493
  }
489
494
  /** How a request is classified by the execution router. */
@@ -1121,6 +1126,14 @@ interface SavingsResult<T = unknown> {
1121
1126
  * (caché/resolver), que solo sirven respuestas finales completas.
1122
1127
  */
1123
1128
  finishReason?: string;
1129
+ /**
1130
+ * The refusal message the model returned instead of an answer (OpenAI
1131
+ * Structured Outputs: `message.refusal`, with `finish_reason: "stop"` and no
1132
+ * content). When present, the request was refused: `text` is empty and no
1133
+ * output contract was met. It is measured like any call, and it is never
1134
+ * repaired, cached or replayed.
1135
+ */
1136
+ refusal?: string;
1124
1137
  report: SavingsReport;
1125
1138
  }
1126
1139
  /** One event of a streamed answer. */
@@ -1296,8 +1309,9 @@ interface QualityRepairCheck {
1296
1309
  * - `decode_time_constraint`: generation was already constrained, so a retry
1297
1310
  * would not change the outcome.
1298
1311
  * - `provider_refusal`: the provider refused the request (`finishReason:
1299
- * "content_filter"`, Anthropic's `stop_reason: "refusal"`); a repair on the
1300
- * same model would be refused, and may be billed, again.
1312
+ * "content_filter"`, Anthropic's `stop_reason: "refusal"`, or OpenAI's
1313
+ * `message.refusal`); a repair on the same model would be refused, and may
1314
+ * be billed, again.
1301
1315
  */
1302
1316
  skipReason?: "output_truncated" | "schema_defect" | "would_cost_more_than_no_layer" | "decode_time_constraint" | "provider_refusal";
1303
1317
  }
@@ -1849,6 +1863,25 @@ interface CanonicalRequest {
1849
1863
  normalizedText: string;
1850
1864
  systemPromptHash: string;
1851
1865
  toolsetHash: string;
1866
+ /**
1867
+ * Hash of the tool-loop fields of the conversation, the part of each turn
1868
+ * that `normalizedText` does not carry: the calls on an assistant turn (name,
1869
+ * arguments and id) and, on a `role: "tool"` message, the call it answers,
1870
+ * its error flag and its name. Part of the exact key, the in-flight key and
1871
+ * the semantic filter, so two conversations that differ only there never
1872
+ * share an answer.
1873
+ *
1874
+ * Call ids count by what they pair, not by their value: results matched to
1875
+ * other calls give another hash, while the same conversation with its ids
1876
+ * consistently renamed (a new run of the same agent loop gets fresh ids from
1877
+ * the provider) keeps it.
1878
+ *
1879
+ * Absent when the request carries none of them: the keys are then built from
1880
+ * exactly the same material as before this field existed. A field that is
1881
+ * present but empty or false (`toolCalls: []`, `isError: false`, `name: ""`)
1882
+ * reaches the provider exactly as an absent one, and counts as absent.
1883
+ */
1884
+ toolTurnsHash?: string;
1852
1885
  /** Hash of the structured payload; part of the cache key so different data never collides. */
1853
1886
  structuredDataHash: string;
1854
1887
  schemaVersion: string;
@@ -2122,6 +2155,13 @@ interface SemanticRecord {
2122
2155
  safetyPolicyVersion: string;
2123
2156
  /** Hash of the declared tool catalog (ids, risk and parameters). */
2124
2157
  toolsetHash: string;
2158
+ /**
2159
+ * Hash of the tool-loop fields of the conversation (tool calls, the call each
2160
+ * tool result answers, its error flag and name); absent when it had none.
2161
+ * Compared for equality like the other identity fields: similarity is scored
2162
+ * on text only, and never decides between two tool loops.
2163
+ */
2164
+ toolTurnsHash?: string;
2125
2165
  /** Sampling temperature of the request, or null when it set none. */
2126
2166
  temperature: number | null;
2127
2167
  /** Sampling seed of the request, or null when it set none. */
@@ -3138,6 +3178,17 @@ interface ProviderResponse {
3138
3178
  * podía distinguirlo y hacía exactamente eso.
3139
3179
  */
3140
3180
  finishReason?: "stop" | "length" | "content_filter" | "tool_calls" | (string & {});
3181
+ /**
3182
+ * The refusal message the model returned INSTEAD of an answer, exactly as the
3183
+ * provider wrote it: OpenAI's `message.refusal` (Chat Completions with
3184
+ * Structured Outputs), which arrives with `finish_reason: "stop"` and no
3185
+ * `content`. Present only when non-empty.
3186
+ *
3187
+ * The layer treats it like `finishReason: "content_filter"`: the call is
3188
+ * measured, and the answer is never repaired against its contract, cached, or
3189
+ * replayed by reference. `text` and `finishReason` stay what the provider said.
3190
+ */
3191
+ refusal?: string;
3141
3192
  /**
3142
3193
  * Llamadas a herramienta que pidió el modelo, tal cual las devolvió el
3143
3194
  * proveedor.
@@ -3724,7 +3775,8 @@ interface SavingsLayerConfig {
3724
3775
  * revocation list going stale. Nothing here throws and nothing here changes
3725
3776
  * what the layer does — turning it on only removes the console noise, so set
3726
3777
  * it when you have your own observability. Applies to the pipeline, the usage
3727
- * emitter and the revocation poller alike.
3778
+ * emitter, the revocation poller and the provider adapters shipped in this
3779
+ * package alike.
3728
3780
  */
3729
3781
  silenceWarnings?: boolean;
3730
3782
  /**
@@ -4104,7 +4156,8 @@ declare const registrarTokenizadorExacto: typeof registerExactTokenizer;
4104
4156
  *
4105
4157
  * A hash of the user text alone is NOT a safe cache key. Two requests may share
4106
4158
  * text but differ in tenant, permission scope, model version, system prompt,
4107
- * schema, toolset, locale, sampling, knowledge epoch or safety policy — any of
4159
+ * schema, toolset, tool-loop fields (tool calls, the call a tool result answers,
4160
+ * its error flag), locale, sampling, knowledge epoch or safety policy — any of
4108
4161
  * which can change the correct answer. The exact key includes all of them.
4109
4162
  *
4110
4163
  * `servedModel` CARRIES THE MODEL THAT WILL ACTUALLY ANSWER, not the one the
@@ -4902,7 +4955,6 @@ declare class RevocationPoller {
4902
4955
  private consecutiveFailures;
4903
4956
  private stopped;
4904
4957
  private started;
4905
- private warnedNoFetch;
4906
4958
  /** ETag of the last 200; sent as If-None-Match so an unchanged list 304s (cheap no-op). */
4907
4959
  private lastEtag;
4908
4960
  /** 304 seguidos; al llegar al tope se fuerza una descarga firmada. */