@bivelio/savings-layer 1.0.0 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,11 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 — savings levers reach the batch surface, cache audit, cascade 2026
4
+
5
+ ### Added
6
+
7
+ - **Cache-break audit.** The layer detects per-request content (ISO/RFC 2822
8
+ timestamps, UUID v4, epoch ms/s) in the cacheable `system` prefix and reports it
9
+ as a `cache-audit` trace plus a one-per-process warning: the diagnosis of a
10
+ prompt cache that never hits, without rewriting the caller's prompt.
11
+ - **Levers inside the batch.** `layer.batches.submit()` now runs the same pure,
12
+ lossless request-body levers as `generate()`: elision by supersession
13
+ (default-on, `savings.elision: false` opts out) and tool-output compaction
14
+ (opt-in, same as `generate()`). A batch item's prompt byte-equals the prompt
15
+ `generate()` sends for the same request and toggle.
16
+ - **Intra-batch dedup** (opt-in `submit(…, { deduplicate: true })`): requests whose
17
+ final provider body is identical are sent once and replayed from the first
18
+ result; the summary counts the money once, over the sent executions, and reports
19
+ a `deduplicated` count.
20
+ - **Cascade 2026.** The default model-cascade table now covers the current
21
+ frontier's same-family cheap siblings (`gpt-5/5.2/5.4 → *-mini`,
22
+ `claude-opus-5/-5-5 → claude-haiku-4-5`); `claude-sonnet-5/-5-5` stay without a
23
+ default pair on purpose (r = 0.50 exact leaves no quality margin).
24
+
3
25
  ## 1.0.0 — the open-core release (Community mode)
4
26
 
5
27
  ### License
6
28
 
7
29
  - The npm package is now **Apache-2.0** (LICENSE); the optional cloud services
8
- keep their own terms in LICENSE-CLOUD.md (§5 fields, §6 fees) and a NOTICE`n covers trademarks and attribution.
30
+ keep their own terms in LICENSE-CLOUD.md (§5 fields, §6 fees) and a NOTICE
31
+ covers trademarks and attribution.
9
32
 
10
33
  ### Changed (breaking for the license contract)
11
34
 
package/README.md CHANGED
@@ -97,6 +97,11 @@ console.log(report.cost.basis); // "estimated": nothing ran, so th
97
97
  installed coding agent. The published benchmark, trial by trial, with the raw CSV,
98
98
  is at **[savings.bivelio.com/en/how-it-works](https://savings.bivelio.com/en/how-it-works)**.
99
99
 
100
+ 📄 **The paper:** [Verifiable Measurement of Cost Savings in LLM Applications
101
+ (open-core edition, v2)](./docs/PAPER.md) — live double-runs, percentile cluster
102
+ bootstrap, four ledgers, and every limitation stated. Also at
103
+ **[savings.bivelio.com/paper](https://savings.bivelio.com/paper)**.
104
+
100
105
  ### Community vs Cloud
101
106
 
102
107
  | | Community (default, no keys) | Cloud (optional `BIVELIO_*` keys) |
@@ -33,12 +33,12 @@ n,o]of this.entradas){if(o.abiertaMs>=t)break;this.quitar(n);}}avisar(){for(let
33
33
  if(s!==null){this.id=s,this.resto="";return}}catch{}}this.leidos>262144&&(this.resto="");}valor(){return this.id}},wg=()=>({requests:0,actualUsd:0,baselineUsd:0,netSavingsUsd:0}),Nn=e=>typeof e=="numbe\
34
34
  r"&&Number.isFinite(e)?e:0,jn=(e,t=120)=>{if(typeof e!="string")return null;let n=e.replace(/[\u0000-\u001f]/g," ").trim();return n===""?null:n.slice(0,t)},gs=(e,t,n=1)=>{e[t]=(e[t]??0)+n;};Sg=new Set(
35
35
  ["127.0.0.1","localhost","[::1]"]);});var kd={};Md(kd,{ERROR_METODO:()=>vd,ERROR_PARAMETROS:()=>Jn,ERROR_PARSEO:()=>bd,ERROR_PETICION:()=>Wn,HERRAMIENTAS:()=>Kn,MAX_BYTES_POR_MENSAJE:()=>Yn,PUERTO_POR_DEFECTO:()=>gd,VERSIONES_MCP:()=>Vs,crearServidorMcp:()=>yd,
36
- servirPorStdio:()=>xv,urlDelGateway:()=>wv});function hd(e){return typeof e=="string"||typeof e=="number"&&Number.isFinite(e)}function wv(e){let t=`http://127.0.0.1:${gd}`;for(let o=0;o<e.length;o++){
36
+ servirPorStdio:()=>_v,urlDelGateway:()=>Ev});function hd(e){return typeof e=="string"||typeof e=="number"&&Number.isFinite(e)}function Ev(e){let t=`http://127.0.0.1:${gd}`;for(let o=0;o<e.length;o++){
37
37
  let r=e[o],i=e[o+1];if(r==="--gateway"&&i!==void 0)t=i,o++;else if(r==="--port"&&i!==void 0){let s=Number(i);if(!Number.isInteger(s)||s<1||s>65535)return {ok:false,mensaje:`--port needs a port number, got\
38
38
  ${JSON.stringify(i)}`};t=`http://127.0.0.1:${s}`,o++;}else return {ok:false,mensaje:`unknown option: ${r}
39
39
  usage: bvsala mcp [--gateway http://127.0.0.1:<port> | --port <port>]`}}let n;try{n=new URL(t);}catch{return {ok:false,mensaje:`--gateway is not a URL: ${JSON.stringify(t)}`}}return n.protocol!=="http:"||![
40
40
  "127.0.0.1","localhost","[::1]"].includes(n.hostname)||n.username||n.password?{ok:false,mensaje:"bvsala mcp reads only the local gateway: --gateway must be http://127.0.0.1:<port> (or localhost), without\
41
- credentials."}:{ok:true,url:n.origin}}function yd(e){let t=e.fetch??fetch;async function n(c){let d;try{d=await t(`${e.gateway}${c}`,{method:"GET",signal:AbortSignal.timeout(yv),headers:{accept:"applic\
41
+ credentials."}:{ok:true,url:n.origin}}function yd(e){let t=e.fetch??fetch;async function n(c){let d;try{d=await t(`${e.gateway}${c}`,{method:"GET",signal:AbortSignal.timeout(xv),headers:{accept:"applic\
42
42
  ation/json"}});}catch{return {ok:false,mensaje:`the local gateway at ${e.gateway} is not answering. Start it (bvsala gateway --upstream anthropic) or pass --gateway/--port.`}}let u;try{u=await d.json();}catch{
43
43
  return {ok:false,mensaje:`the local gateway answered ${d.status} without JSON on ${c}`}}if(!d.ok){let f=u?.error?.message;return {ok:false,mensaje:`the local gateway answered ${d.status} on ${c}${typeof f=="s\
44
44
  tring"?`: ${f}`:""}`}}return {ok:true,json:u}}let o=c=>({content:[{type:"text",text:JSON.stringify(c)}],structuredContent:c,isError:false}),r=c=>({content:[{type:"text",text:c}],isError:true}),i={gateway:e.gateway,
@@ -46,7 +46,7 @@ readOnly:true,note:"metadata of this local gateway only: no request ids, no cont
46
46
  mensaje:`the local gateway does not serve ${or} (an older bvsala?): update it to use this tool.`}:{ok:true,r:d}}async function a(c){if(c==="gateway_status"){let m=await n("/_bvsala/v1/status");return m.
47
47
  ok?o({scope:i,status:m.json}):r(m.mensaje)}if(c==="session_costs"){let m=await s();return m.ok?o({scope:i,window:m.r.window,sessions:m.r.sessions,note:"Only requests sent with the header x-bvsala-repo\
48
48
  rt: 1 are in the mailbox (the last N, one hour at most), so this is not your whole bill. Money is grouped by cost basis and never added across bases: measured and estimated are different things."}):r(
49
- m.mensaje)}let d=await s();if(!d.ok)return r(d.mensaje);let u=d.r.toolOutputCompaction,f=Object.entries(u.byReason).sort((m,h)=>h[1]-m[1]).map(([m,h])=>({reason:m,toolOutputs:h,meaning:kv[m]??"unknown\
49
+ m.mensaje)}let d=await s();if(!d.ok)return r(d.mensaje);let u=d.r.toolOutputCompaction,f=Object.entries(u.byReason).sort((m,h)=>h[1]-m[1]).map(([m,h])=>({reason:m,toolOutputs:h,meaning:Sv[m]??"unknown\
50
50
  reason code."}));return o({scope:i,window:d.r.window,toolOutputs:u.decisions,applied:u.applied,coverage:u.decisions===0?null:Math.round(u.applied/u.decisions*1e3)/1e3,estimatedTokens:{before:u.appliedTokensBefore,
51
51
  after:u.appliedTokensAfter,basis:u.tokenBasis},opportunities:f,note:u.decisions===0?"No tool-output decisions in this window. lossless-data runs where the request is built by the SDK (createSavingsLay\
52
52
  er with toolOutputCompaction: { profile: 'lossless-data' }); the gateway forwards bytes and does not compact tool outputs. Nothing here is a saving.":"Token counts are local estimates, never a verifie\
@@ -60,18 +60,18 @@ m+md),v=m+md;return fr(u,{tools:h,...v<Kn.length?{nextCursor:`p${v}`}:{}})}case
60
60
  ng"?m.slice(0,80):String(m)}`);let h=f.arguments;return h!==void 0&&(h===null||typeof h!="object"||Array.isArray(h)||Object.keys(h).length>0)?Ye(u,Jn,`${m} takes no arguments`):fr(u,await a(m))}default:
61
61
  return Ye(u,vd,`method not found: ${c.method.slice(0,80)}`)}}return {async manejar(c){let d=c.replace(/\r$/,"");if(d.trim()==="")return null;if(Buffer.byteLength(d,"utf8")>Yn)return JSON.stringify(Ye(null,
62
62
  Wn,`message larger than ${Yn} bytes`));let u;try{u=JSON.parse(d);}catch{return JSON.stringify(Ye(null,bd,"parse error"))}if(Array.isArray(u))return JSON.stringify(Ye(null,Wn,"batches are not supported"));
63
- if(u===null||typeof u!="object")return JSON.stringify(Ye(null,Wn,"invalid JSON-RPC 2.0 request"));let f=await l(u);return f===null?null:JSON.stringify(f)}}}function xv(e,t,n){let o=yd(n),r="",i=false,s=Promise.
63
+ if(u===null||typeof u!="object")return JSON.stringify(Ye(null,Wn,"invalid JSON-RPC 2.0 request"));let f=await l(u);return f===null?null:JSON.stringify(f)}}}function _v(e,t,n){let o=yd(n),r="",i=false,s=Promise.
64
64
  resolve(),a=l=>{s=s.then(async()=>{let c=await o.manejar(l);c!==null&&t.write(`${c}
65
65
  `);});};return new Promise(l=>{e.setEncoding?.("utf8"),e.on("data",c=>{r+=typeof c=="string"?c:c.toString("utf8");let d;for(;(d=r.indexOf(`
66
- `))>=0;){let u=r.slice(0,d);if(r=r.slice(d+1),i){i=false;continue}a(u);}r.length>Yn&&(r="",i||(i=true,a("x".repeat(Yn+1))));}),e.on("end",()=>{r!==""&&!i&&a(r),s.then(()=>l());});})}var Vs,gd,Yn,md,yv,bd,Wn,vd,
67
- Jn,Fs,zs,Kn,kv,fr,Ye,wd=Zs(()=>{"use strict";ys();Vs=["2025-06-18","2025-03-26","2024-11-05"],gd=8402,Yn=1024*1024,md=2,yv=5e3,bd=-32700,Wn=-32600,vd=-32601,Jn=-32602,Fs={type:"object",properties:{},additionalProperties:!1},
66
+ `))>=0;){let u=r.slice(0,d);if(r=r.slice(d+1),i){i=false;continue}a(u);}r.length>Yn&&(r="",i||(i=true,a("x".repeat(Yn+1))));}),e.on("end",()=>{r!==""&&!i&&a(r),s.then(()=>l());});})}var Vs,gd,Yn,md,xv,bd,Wn,vd,
67
+ Jn,Fs,zs,Kn,Sv,fr,Ye,wd=Zs(()=>{"use strict";ys();Vs=["2025-06-18","2025-03-26","2024-11-05"],gd=8402,Yn=1024*1024,md=2,xv=5e3,bd=-32700,Wn=-32600,vd=-32601,Jn=-32602,Fs={type:"object",properties:{},additionalProperties:!1},
68
68
  zs=e=>({title:e,readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!1}),Kn=[{name:"gateway_status",title:"Local gateway status",description:"Status of the local bvsala gateway: version\
69
69
  , upstream origin, license state and plan, whether metering is on, and the report mailbox headers. No keys, no content.",inputSchema:Fs,annotations:zs("Local gateway status")},{name:"session_costs",title:"\
70
70
  Cost per session",description:"Cost per Claude Code session of the requests this gateway's report mailbox holds (the last N, one hour at most): requests, metered or not and why, and money per cost bas\
71
71
  is (measured, reference, estimated\u2026) kept apart, never added together. Counts and USD only: no request ids, no content.",inputSchema:Fs,annotations:zs("Cost per session")},{name:"compaction_oppor\
72
72
  tunities",title:"Tool-output compaction opportunities",description:"What the lossless-data tool-output compaction decided on the requests in this gateway's mailbox: how many tool outputs were compacte\
73
73
  d, how many were bypassed and why (reason codes), and the ESTIMATED tokens before and after. Estimates, never a verified saving. Read-only: it proposes nothing and changes nothing.",inputSchema:Fs,annotations:zs(
74
- "Compaction opportunities")}],kv={applied:"already compacted: whitespace removed from strict JSON.",not_smaller:"already compact: removing whitespace would not save a token. Nothing to gain.",below_threshold:"\
74
+ "Compaction opportunities")}],Sv={applied:"already compacted: whitespace removed from strict JSON.",not_smaller:"already compact: removing whitespace would not save a token. Nothing to gain.",below_threshold:"\
75
75
  would save fewer characters than the minimum. Nothing worth changing.",not_json:"not a JSON object or array. Out of scope for lossless-data.",invalid_json:"looks like JSON but is not strict JSON. Left\
76
76
  as is; fixing the tool's output is the only lossless way.",duplicate_keys:"JSON with a repeated key: parsers keep only one value, so it cannot be compacted without changing what it says. Fix the tool\
77
77
  .",invalid_unicode:"contains an unpaired surrogate. Left as is.",protected:"a file view or an excepted tool: kept verbatim on purpose (agents edit files by exact match)."},fr=(e,t)=>({jsonrpc:"2.0",id:e,
@@ -168,7 +168,7 @@ bp.test(n.betas))return null;let o=gt(e,t);if(o===null||vp(o))return null;let r=
168
168
  let o=Object.entries(ka).map(([r,i])=>`${r}\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\
169
169
  outing: ARMED (${o}; 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\
170
170
  -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\
171
- LISION=0 turns off the levers",aviso:true}}var xa=new Set;function Z(e,t){xa.has(e)||(xa.add(e),console.warn(t));}var ye="1.0.0",Ur="x-bvsala-version";var Xt=class{options;now;flushIntervalMs;maxBatch;maxBuffer;buffer=[];seatId;ci;timer;inflight=null;fallosSeguidos=0;contadores={sent:0,requeued:0,discarded:0,dropped:0};constructor(t){this.options=t,
171
+ LISION=0 turns off the levers",aviso:true}}var xa=new Set;function Z(e,t){xa.has(e)||(xa.add(e),console.warn(t));}var ye="1.1.0",Ur="x-bvsala-version";var Xt=class{options;now;flushIntervalMs;maxBatch;maxBuffer;buffer=[];seatId;ci;timer;inflight=null;fallosSeguidos=0;contadores={sent:0,requeued:0,discarded:0,dropped:0};constructor(t){this.options=t,
172
172
  this.now=t.now??(()=>Date.now()),this.flushIntervalMs=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;}stats(){
173
173
  return {...this.contadores}}record(t){let n=this.now();this.buffer.push({report:t,at:n});let o=yp(t);if(o&&this.buffer.push({report:o,at:n}),this.buffer.length>this.maxBuffer){let r=this.buffer.length-
174
174
  this.maxBuffer;this.contadores.dropped+=r,this.buffer.splice(0,r);}this.buffer.length>=this.maxBatch&&this.flush();}start(){this.timer||(this.timer=setInterval(()=>{this.flush();},this.flushIntervalMs),this.
@@ -1367,8 +1367,8 @@ uanta","seixanta","setanta","vuitanta","noranta"].forEach((e,t)=>A(e,{k:"decena"
1367
1367
  segona segons segones",2],["terc tercos",3],["quart quarta quarts quartes",4],["cinque cinquena",5],["sise sisena",6],["sete setena",7],["vuite vuitena",8],["nove novena",9],["dese desena",10]].forEach(
1368
1368
  ([e,t])=>A(e,{k:"ordinal",v:t}));A("meitat meitats mig mitja",{k:"token",t:"f:1/2"});A("dotzena dotzenes",{k:"token",t:"c:12"});A("parell parells",{k:"token",t:"c:2"});A("vegades vegada",{k:"veces"});
1369
1369
  A("i",{k:"conector"});var Wb={"gpt-4o-mini":{deltaTokens:-16,salidaDelBrazoA:72,contrato:"triage (6 fields, long enums)",pares:8},"gpt-4o":{deltaTokens:3,salidaDelBrazoA:75,contrato:"triage (6 fields, long enums)",pares:8}};
1370
- jr(Wb);new Set(Object.values(Os).flatMap(e=>e.flatMap(t=>[t,`openai/${t}`])));function Cv(e,t=300){return new Promise(n=>{let o=connect({port:e,host:"127.0.0.1"}),r=i=>{o.destroy(),n(i);};o.setTimeout(t,()=>r(false)),o.once("connect",()=>r(true)),o.once("error",()=>r(false));})}var Xn=iu(process.
1371
- argv.slice(2));function Tv(){console.log(`bvsala ${ye} \u2014 BiVelio Savings Layer
1370
+ jr(Wb);new Set(Object.values(Os).flatMap(e=>e.flatMap(t=>[t,`openai/${t}`])));function Av(e,t=300){return new Promise(n=>{let o=connect({port:e,host:"127.0.0.1"}),r=i=>{o.destroy(),n(i);};o.setTimeout(t,()=>r(false)),o.once("connect",()=>r(true)),o.once("error",()=>r(false));})}var Xn=iu(process.
1371
+ argv.slice(2));function Iv(){console.log(`bvsala ${ye} \u2014 BiVelio Savings Layer
1372
1372
 
1373
1373
  Commands:
1374
1374
  gateway [--port N] [--upstream U]
@@ -1514,7 +1514,7 @@ there is no per-token invoice: that traffic is metered at list-price equivalent
1514
1514
  carries commission.
1515
1515
  Your prompts, completions and provider keys never reach BiVelio: what it receives is per-request
1516
1516
  usage (token counts, cost, the model id and opaque ids); LICENSE-CLOUD.md \xA75 lists every field.
1517
- More: savings.bivelio.com`);}Xn.tipo==="ayuda"&&(Tv(),process.exit(Xn.salida));Xn.tipo==="version"&&(console.log(ye),process.exit(0));var{comando:W}=Xn,_t=Un(W),we=Xn.resto,xd=cu(_t?[]:we,process.env,{
1517
+ More: savings.bivelio.com`);}Xn.tipo==="ayuda"&&(Iv(),process.exit(Xn.salida));Xn.tipo==="version"&&(console.log(ye),process.exit(0));var{comando:W}=Xn,_t=Un(W),we=Xn.resto,xd=cu(_t?[]:we,process.env,{
1518
1518
  checkEnv:_t||W==="gateway"});if(xd.length>0){for(let e of xd)console.error(e);process.exit(1);}var Hs=process.env.BVSALA_GATEWAY_EFFORT,Rd=process.env.BVSALA_GATEWAY_SIBLING_MODEL;if(W==="mcp"){let{servirPorStdio:e,
1519
1519
  urlDelGateway:t}=await Promise.resolve().then(()=>(wd(),kd)),n=t(we);n.ok||(console.error(`[bvsala mcp] ${n.mensaje}`),process.exit(1)),await e(process.stdin,process.stdout,{gateway:n.url,version:ye}),
1520
1520
  process.exit(0);}if(W==="init"){let e=Au(we,process.cwd());e.ok||(console.error(`[bvsala init] ${e.mensaje}`),process.exit(1));let t=e.valor,n={leer:l=>{try{return readFileSync(l,"utf8")}catch{return}},existe:existsSync},
@@ -1526,16 +1526,16 @@ let c=s({input:process.stdin,output:process.stdout});try{return await c.question
1526
1526
  "--project"),forzar:we.includes("--force"),fs:{existe:existsSync,leer:o=>readFileSync(o,"utf8"),escribir:(o,r)=>writeFileSync(o,r),crearDir:o=>mkdirSync(o,{recursive:true})}},n=we.includes("--codex")?Fu(t):zu(t);console.log(n.mensaje),process.
1527
1527
  exit(n.ok?0:1);}var Gs=we.indexOf("--port"),Vt=_t?0:Gs>=0?Number(we[Gs+1]):8402;(!Number.isInteger(Vt)||(W==="doctor"?Vt<1:Vt<0)||Vt>65535)&&(console.error(`[bvsala ${W}] invalid port: ${we[Gs+1]}`),process.
1528
1528
  exit(1));if(W==="doctor"){we.includes("--write")&&(console.error("[bvsala doctor] --write does not exist yet: the doctor only prints; copy the block by hand."),process.exit(1));let e=await du(Vt);console.
1529
- log(e.texto),process.exit(0);}function Rv(e,t){Un(e)||process.exit(1);let n=ir(e,0,[],{}).bin;if(process.platform==="win32"){let r=Ss(n);r.tipo==="exe"?n=r.ruta:r.tipo==="solo-shim"&&(console.error(`[b\
1529
+ log(e.texto),process.exit(0);}function Dv(e,t){Un(e)||process.exit(1);let n=ir(e,0,[],{}).bin;if(process.platform==="win32"){let r=Ss(n);r.tipo==="exe"?n=r.ruta:r.tipo==="solo-shim"&&(console.error(`[b\
1530
1530
  vsala ${e}] \`${n}\` is installed as an npm shim (${r.ruta}) and Windows cannot launch it safely with arguments (CVE-2024-27980). Install it with its native installer (which leaves a ${n}.exe on the P\
1531
1531
  ATH) or use the manual recipe in docs/terminales/.`),process.exit(127));}let o=spawn(n,t,{stdio:"inherit"});return o.on("error",r=>{console.error(`[bvsala ${e}] could not launch \`${n}\`: ${String(r)}. Is\
1532
- it installed and on the PATH?`),process.exit(127);}),o.on("exit",(r,i)=>process.exit(r??(i?1:0))),new Promise(()=>{})}_t&&su(we)&&await Rv(W,we);if(_t){let e=nu(W);e&&(console.error(`[bvsala ${W}] ${e}`),
1533
- process.exit(1));}var Av=W==="gateway"?Al(we):void 0,me=Il({flag:Av,env:process.env.BVSALA_GATEWAY_UPSTREAM});me.ok||(console.error(me.error),process.exit(1));var gr=process.env.BIVELIO_SERVICE_KEY?.trim()||
1532
+ it installed and on the PATH?`),process.exit(127);}),o.on("exit",(r,i)=>process.exit(r??(i?1:0))),new Promise(()=>{})}_t&&su(we)&&await Dv(W,we);if(_t){let e=nu(W);e&&(console.error(`[bvsala ${W}] ${e}`),
1533
+ process.exit(1));}var Pv=W==="gateway"?Al(we):void 0,me=Il({flag:Pv,env:process.env.BVSALA_GATEWAY_UPSTREAM});me.ok||(console.error(me.error),process.exit(1));var gr=process.env.BIVELIO_SERVICE_KEY?.trim()||
1534
1534
  void 0,Et=await yl({serviceAccountKey:gr,licenseKey:process.env.BIVELIO_LICENSE_KEY,licenseServerUrl:us});Et.ok||(console.error(`[bvsala ${W}] ${Et.message}`),process.exit(1));var Qe=_t?ir(
1535
- W,0,we,{modoCodex:W==="codex"?Xl({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0,...me.upstream?{upstreamElegido:me.upstream}:{}}):null,Iv=_t&&process.env.BVSALA_GATEWAY_LOG!=="1",qs=Qe?.
1536
- upstream?null:lu(()=>readFileSync(process.env.BVSALA_ANILLO2_FICHERO??join(homedir(),".bvsala","anillo2.json"),"utf8")),Qn=uu({enrolamiento:qs,espiaVivo:qs!==null&&await Cv(qs.espia),upstreamEnv:me.upstream,...me.origen!==
1535
+ W,0,we,{modoCodex:W==="codex"?Xl({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0,...me.upstream?{upstreamElegido:me.upstream}:{}}):null,Ov=_t&&process.env.BVSALA_GATEWAY_LOG!=="1",qs=Qe?.
1536
+ upstream?null:lu(()=>readFileSync(process.env.BVSALA_ANILLO2_FICHERO??join(homedir(),".bvsala","anillo2.json"),"utf8")),Qn=uu({enrolamiento:qs,espiaVivo:qs!==null&&await Av(qs.espia),upstreamEnv:me.upstream,...me.origen!==
1537
1537
  "none"?{upstreamOrigen:me.origen}:{},elisionEnv:process.env.BVSALA_GATEWAY_ELISION,hoyUtcIso:new Date().toISOString().slice(0,10)}),Ws=me.upstream??Qn.upstream,mr=process.env.BVSALA_GATEWAY_ELISION??Qn.
1538
- elision,De;try{De=await Jl({puerto:Vt,...Iv?{log:()=>{},avisosDeNoMedido:!1}:{},...Et.license?{license:{token:Et.license.token,publicKey:Lt,licenseServerUrl:us,checkOnStart:Et.source==="offline"}}:{},
1538
+ elision,De;try{De=await Jl({puerto:Vt,...Ov?{log:()=>{},avisosDeNoMedido:!1}:{},...Et.license?{license:{token:Et.license.token,publicKey:Lt,licenseServerUrl:us,checkOnStart:Et.source==="offline"}}:{},
1539
1539
  warn:e=>console.error(e),...gr?{serviceAccountKey:gr}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...Ws?{upstream:Ws}:Qe?.upstream?{upstream:Qe.upstream}:{},...process.env.
1540
1540
  BVSALA_GATEWAY_COALESCE==="1"?{coalescer:!0}:{},...process.env.BVSALA_GATEWAY_USAGE_INJECT==="1"?{usageInject:!0}:{},...process.env.BVSALA_GATEWAY_SHADOW==="1"?{sombra:!0}:{},elision:mr!=="0",...process.
1541
1541
  env.BVSALA_GATEWAY_DIALES_ELISION==="1"?{dialesElision:!0}:{},...Rd==="1"?{modeloHermano:!0}:{},...Hs?{esfuerzo:Hs}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:
package/dist/index.d.ts CHANGED
@@ -1799,7 +1799,7 @@ declare function precioDeLista(model: string, at?: Date | number): CostModel | u
1799
1799
  * on the customer's machine. `test/version-declarada.test.ts` ties this
1800
1800
  * constant to package.json so the two can never drift.
1801
1801
  */
1802
- declare const BV_SALA_VERSION = "1.0.0";
1802
+ declare const BV_SALA_VERSION = "1.1.0";
1803
1803
 
1804
1804
  /**
1805
1805
  * Typed intermediate representation for LLM -> LLM handoffs.
@@ -3945,6 +3945,16 @@ interface BatchSubmitOptions {
3945
3945
  * OpenAI, by model).
3946
3946
  */
3947
3947
  maxRequestsPerBatch?: number;
3948
+ /**
3949
+ * Deduplicate requests whose final provider body is byte-identical within THIS
3950
+ * submission: the first is sent and the rest are replayed from its result, so the
3951
+ * provider bills each unique request once. Defaults to false. Only turn it on when
3952
+ * identical bodies imply identical answers — deterministic sampling (temperature 0 or
3953
+ * unset), or a seed you control. With temperature > 0 and no seed the provider would
3954
+ * have sampled each copy independently, and deduplication hands every copy the first
3955
+ * one's answer.
3956
+ */
3957
+ deduplicate?: boolean;
3948
3958
  /** Cancels the HTTP calls of this submission. Batches already created stay created. */
3949
3959
  signal?: AbortSignal;
3950
3960
  }
@@ -3974,6 +3984,17 @@ interface BatchJob {
3974
3984
  batch: number;
3975
3985
  schema?: number;
3976
3986
  }[];
3987
+ /**
3988
+ * Requests that were NOT sent because an identical one (same final provider body) was
3989
+ * already in this submission. Each entry names its own id, the id it duplicates, and
3990
+ * its model. `results()` replays the duplicate from the source's result; nothing about
3991
+ * the answer is stored here. Present only when at least one request was deduplicated.
3992
+ */
3993
+ duplicates?: {
3994
+ customId: string;
3995
+ sameAs: string;
3996
+ model: string;
3997
+ }[];
3977
3998
  /** The distinct output schemas of the job, stored once each. */
3978
3999
  schemas: JsonSchema[];
3979
4000
  }
@@ -4063,6 +4084,8 @@ interface BatchSummary {
4063
4084
  missing: number;
4064
4085
  /** Requests whose provider batch has not ended yet: not in `items`. */
4065
4086
  pending: number;
4087
+ /** Requests dropped at submit() because an identical one was already in the job. */
4088
+ deduplicated?: number;
4066
4089
  /** Succeeded requests with a `tierSaving`. */
4067
4090
  credited: number;
4068
4091
  /** Sum of every `tierSaving.amount`. Never more than the sum of each request's published difference. */
@@ -5281,9 +5304,12 @@ declare function toCsv(rows: readonly Readonly<Record<string, CsvValue>>[], opti
5281
5304
  /**
5282
5305
  * bivelio-savings-layer (BV-SALA)
5283
5306
  * ---------------------------------------------------------------------------
5284
- * PROPRIETARY AND CONFIDENTIAL. Copyright (c) 2026 BiVelio Inc.
5285
- * All Rights Reserved. Unauthorized copying, distribution, or use of this
5286
- * software, in whole or in part, is strictly prohibited. See LICENSE.
5307
+ * Copyright (c) 2026 BiVelio Inc.
5308
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5309
+ * use this file except in compliance with the License. You may obtain a copy
5310
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0 — the full
5311
+ * text ships in this repository's LICENSE file. The optional BiVelio cloud
5312
+ * services keep their own terms in LICENSE-CLOUD.md.
5287
5313
  * ---------------------------------------------------------------------------
5288
5314
  *
5289
5315
  * A provider-neutral LLM execution optimizer. For every request it answers, in