@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 +24 -1
- package/README.md +5 -0
- package/dist/gateway/bin.js +17 -17
- package/dist/index.d.ts +30 -4
- package/dist/index.js +1411 -1394
- package/package.json +3 -1
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
|
|
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) |
|
package/dist/gateway/bin.js
CHANGED
|
@@ -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:()=>
|
|
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(
|
|
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:
|
|
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
|
|
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,
|
|
67
|
-
Jn,Fs,zs,Kn,
|
|
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")}],
|
|
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.
|
|
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
|
|
1371
|
-
argv.slice(2));function
|
|
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"&&(
|
|
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
|
|
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
|
|
1533
|
-
process.exit(1));}var
|
|
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,
|
|
1536
|
-
upstream?null:lu(()=>readFileSync(process.env.BVSALA_ANILLO2_FICHERO??join(homedir(),".bvsala","anillo2.json"),"utf8")),Qn=uu({enrolamiento:qs,espiaVivo:qs!==null&&await
|
|
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,...
|
|
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.
|
|
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
|
-
*
|
|
5285
|
-
*
|
|
5286
|
-
*
|
|
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
|