@bivelio/savings-layer 0.5.0-rc.1 → 0.5.0
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 +9 -1
- package/dist/gateway/bin.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/terminales/README.md +2 -2
- package/docs/terminales/codex.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -170,6 +170,9 @@ and the free **Measure** license, which runs the layer in shadow mode on your ow
|
|
|
170
170
|
traffic and shows what it would have saved, valued with your provider's counter. The verifier that replays the optimizer on your own data
|
|
171
171
|
lives in your dashboard once you sign in.
|
|
172
172
|
|
|
173
|
+
**Upgrading from 0.4?** Every breaking change of 0.5, with a before and after for
|
|
174
|
+
each, is in [Migrating from 0.4 to 0.5](https://savings.bivelio.com/en/migrate-to-0.5).
|
|
175
|
+
|
|
173
176
|
## Set it up in one command
|
|
174
177
|
|
|
175
178
|
```bash
|
|
@@ -914,7 +917,7 @@ Ask for a response contract and you also get `data`, validated against your sche
|
|
|
914
917
|
```ts
|
|
915
918
|
const { data, text } = await layer.generate<{ answer: number }>({
|
|
916
919
|
model: "openai/gpt-4o-mini",
|
|
917
|
-
messages: [{ role: "user", content: "
|
|
920
|
+
messages: [{ role: "user", content: "How many legs does a spider have?" }],
|
|
918
921
|
response: {
|
|
919
922
|
contractId: "answer:v1",
|
|
920
923
|
schema: {
|
|
@@ -930,6 +933,11 @@ const { data, text } = await layer.generate<{ answer: number }>({
|
|
|
930
933
|
console.log(data.answer); // 8
|
|
931
934
|
```
|
|
932
935
|
|
|
936
|
+
A question the layer answers on its own (the arithmetic of the first example)
|
|
937
|
+
never reaches the model, so its `data` is the resolver's `{ value: 8 }`, not
|
|
938
|
+
your schema's shape: `report.qualityLedger` says the contract was not met.
|
|
939
|
+
Ask the model for anything you want in your contract's shape.
|
|
940
|
+
|
|
933
941
|
</details>
|
|
934
942
|
|
|
935
943
|
<details>
|
package/dist/gateway/bin.js
CHANGED
|
@@ -79,7 +79,7 @@ td.test(n.betas))return null;let r=ct(e,t);if(r===null||nd(r))return null;let o=
|
|
|
79
79
|
let r=Object.entries(ks).map(([o,i])=>`${o}\u2192${i}`).join(", ");return e!=="1"?{linea:"sibling model routing: OFF (BVSALA_GATEWAY_SIBLING_MODEL=1 turns it on)",aviso:false}:n?{linea:`sibling model r\
|
|
80
80
|
outing: ARMED (${r}; ON if your licence includes sibling_model_routing; fail-closed). Changes the model you see in the response; cache reads cost 0.025\xD7 instead of 0.1\xD7 (list price). Turning it on mid\
|
|
81
81
|
-conversation pays one cold prefix write, once. If the provider rejects the rewritten request (400), it is re-sent unchanged once`,aviso:false}:{linea:"sibling model routing: NOT ARMED - BVSALA_GATEWAY_E\
|
|
82
|
-
LISION=0 turns off the levers",aviso:true}}var xs=new Set;function ee(e,t){xs.has(e)||(xs.add(e),console.warn(t));}var ye="0.5.0
|
|
82
|
+
LISION=0 turns off the levers",aviso:true}}var xs=new Set;function ee(e,t){xs.has(e)||(xs.add(e),console.warn(t));}var ye="0.5.0",fo="x-bvsala-version";var Ut=class{options;now;flushIntervalMs;maxBatch;maxBuffer;buffer=[];seatId;ci;timer;inflight=null;fallosSeguidos=0;constructor(t){this.options=t,this.now=t.now??(()=>Date.now()),this.flushIntervalMs=
|
|
83
83
|
t.flushIntervalMs??15e3,this.maxBatch=t.maxBatch??50,this.maxBuffer=t.maxBuffer??1e3,this.ci=t.ci??false;}setSeat(t,n){t&&(this.seatId=t),this.ci=n;}record(t){let n=this.now();this.buffer.push({report:t,at:n});
|
|
84
84
|
let r=rd(t);r&&this.buffer.push({report:r,at:n}),this.buffer.length>this.maxBuffer&&this.buffer.splice(0,this.buffer.length-this.maxBuffer),this.buffer.length>=this.maxBatch&&this.flush();}start(){this.
|
|
85
85
|
timer||(this.timer=setInterval(()=>{this.flush();},this.flushIntervalMs),this.timer.unref?.());}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0),this.flush();}async stopAndDrain(t){this.timer&&
|
package/dist/index.d.ts
CHANGED
|
@@ -154,7 +154,7 @@ declare function precioDeLista(model: string, at?: Date | number): CostModel | u
|
|
|
154
154
|
* on the customer's machine. `test/version-declarada.test.ts` ties this
|
|
155
155
|
* constant to package.json so the two can never drift.
|
|
156
156
|
*/
|
|
157
|
-
declare const BV_SALA_VERSION = "0.5.0
|
|
157
|
+
declare const BV_SALA_VERSION = "0.5.0";
|
|
158
158
|
|
|
159
159
|
/** How the layer served a request that is eligible for a verification check. */
|
|
160
160
|
type VerificationStratum =
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ for(let n of Yp[t]??[])if(!(n.vigenteDesde!==void 0&&e<n.vigenteDesde)&&!(n.vige
|
|
|
28
28
|
p-research","o4-mini-deep-research","gpt-5.5-pro","gpt-5.4-pro","gpt-5.2-pro","gpt-5-pro","gpt-5.5-cyber","gemini-3.8-flash-tts","gemini-3.8-flash-lite-tts","gemini-3.1-flash-lite-image"],Xo={...qt,...Object.
|
|
29
29
|
fromEntries(Qp.map(t=>[t,null]))},Xp=ti(Xo);function gn(t,e){let n=Ut(t);return n===void 0?void 0:Ka(n,Ja(e))}var ef=Object.fromEntries(Object.keys(Xo).map(t=>[t,Xo[t]===null?null:t]));function Ut(t){
|
|
30
30
|
return ei(ef,Xp,t)??void 0}var tf=/^\d{4}-?\d{2}-?\d{2}$/;function st(t,e){let n=Ut(t);if(n===void 0)return;let r=t.lastIndexOf("/"),o=r>=0?t.slice(r+1):t;if(!(n!==t&&n!==o&&!tf.test(o.slice(n.length+
|
|
31
|
-
1))))return Ka(n,Ja(e))}var He="0.5.0
|
|
31
|
+
1))))return Ka(n,Ja(e))}var He="0.5.0",fr="x-bvsala-version";var nf=["exact","task_equivalent","not_verified","bounded_degradation","rejected"];function rf(t){return {v:1,task:t.task,goal:t.goal,rules:t.rules??[],facts:t.facts??[],refs:t.refs??[],...t.delta?{delta:t.delta}:{},outputContract:t.outputContract}}var mr=class{id;exact=false;constructor(e="heuristic"){this.id=e;}count(e){if(e.length===0)return 0;let n=e.match(/[A-Za-z]+|\d+|\s+|[^\sA-Za-z\d]/g)??[],r=0;for(let o of n){let i=o[0];if(/\s/.test(i)){let s=(o.
|
|
32
32
|
match(/\n/g)??[]).length;r+=s+Math.floor(Math.max(0,o.length-1)/2);}else /[A-Za-z]/.test(i)?r+=Math.max(1,Math.ceil(o.length/4)):/\d/.test(i)?r+=Math.max(1,Math.ceil(o.length/2)):r+=1;}return Math.max(r,
|
|
33
33
|
Math.ceil(e.length/4))}},St=new mr;var hr=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(e=St){this.fallbackId=e.id,this.estimators.set(e.id,e);}register(e){return this.estimators.set(e.id,e),this}mapModel(e,n){
|
|
34
34
|
return this.modelToTokenizer.set(e,n),this}resolve(e){let n=e.tokenizer?this.estimators.get(e.tokenizer):void 0;if(n)return n;if(e.model){let r=this.modelToTokenizer.get(e.model);if(r){let o=this.estimators.
|
|
@@ -302,8 +302,8 @@ would fail it.
|
|
|
302
302
|
| Lever | Applies to | What changes | What does not |
|
|
303
303
|
|---|---|---|---|
|
|
304
304
|
| Cache TTL (`cache_ttl_tuning`, on by default with PRO) | `/v1/messages` requests where a large part of the prompt is new (session start, compaction); a chained turn travels untouched | every `cache_control` with `ttl: "1h"` becomes `"5m"`, the top-level one included (a 1-hour top-level marker behind 5-minute block markers would be a 400) | a key named `cache_control` inside a tool call's `input` or a tool's `input_schema` is content and stays as it is; so does the advisor's `caching`; headers are untouched |
|
|
305
|
-
| Per-turn effort (`
|
|
306
|
-
| Sibling model (`
|
|
305
|
+
| Per-turn effort (`BVSALA_GATEWAY_EFFORT=low` or `medium`) | `/v1/messages` for `claude-fable-5-1`, `claude-mythos-5-1`, `claude-opus-5-5` and `claude-opus-5`, when the level is below the model's default effort (`high`; `medium` on `claude-opus-5-5`) and the request carries no `output_config` of its own (top level or in a message); a level at or above the default leaves the request untouched | one message appended at the end of `messages`, `{"role":"system","content":[],"output_config":{"effort":…}}`, and `mid-conversation-output-config-2026-07-01` appended after your `anthropic-beta` value | your `anthropic-beta` value is kept verbatim in front; nothing earlier in `messages` moves |
|
|
306
|
+
| Sibling model (`BVSALA_GATEWAY_SIBLING_MODEL=1`) | `/v1/messages` and `/v1/messages/count_tokens` for `claude-fable-5`, except with a forced `tool_choice` or the 1M-context beta | `model` becomes `claude-fable-5-1` | headers are untouched |
|
|
307
307
|
| Context-editing dials (`BVSALA_GATEWAY_DIALES_ELISION=1`; measured as a net loss, off) | `/v1/messages` | your `clear_tool_uses_20250919` edit in `context_management` is replaced by ours | your other edits are kept, `clear_thinking_20251015` first; your `anthropic-beta` value is kept verbatim, with `context-management-2025-06-27` appended if it was not there |
|
|
308
308
|
| Usage in Chat Completions streams (`BVSALA_GATEWAY_USAGE_INJECT=1`, off) | streaming `…/chat/completions` requests without `stream_options` of their own | `"stream_options":{"include_usage":true},` inserted as text right after the opening `{` | the rest of the body, byte for byte (nothing is re-serialized); headers are untouched; a 400 or 422 gets your original request once more |
|
|
309
309
|
|
package/docs/terminales/codex.md
CHANGED
|
@@ -47,7 +47,7 @@ codex -c openai_base_url=http://127.0.0.1:8402/v1 # ventana 2
|
|
|
47
47
|
|
|
48
48
|
Codex añade `/responses` (`POST {base_url}/responses`, siempre `stream: true`)
|
|
49
49
|
[V]. Si en el 8402 ya tienes el gateway de Anthropic para otros terminales,
|
|
50
|
-
arranca este en otro puerto (`--
|
|
50
|
+
arranca este en otro puerto (`--port 8403`) y apunta ahí: un gateway habla con
|
|
51
51
|
un solo upstream.
|
|
52
52
|
|
|
53
53
|
No lo escribas en `~/.codex/config.toml`: ahí se quedaría fijo y Codex fallaría
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bivelio/savings-layer",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The savings layer for your LLM bill: a library inside your code that lets through only what has to be paid for and returns a SavingsReport per call, measured with your provider's own meter and kept apart from estimates. Commercial license, paid per seat, commission only on measured savings. Your prompts never leave your infrastructure.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|