@bivelio/savings-layer 0.4.12 → 0.4.14

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 CHANGED
@@ -1,7 +1,11 @@
1
- # BV‑SALA — the savings layer for your LLM bill
1
+ <p align="center">
2
+ <img src="https://savings.bivelio.com/brand/savings-logo.png" alt="Savings. by BiVelio" width="360">
3
+ </p>
4
+
5
+ <h1 align="center">The savings layer for your LLM bill</h1>
2
6
 
3
7
  <p align="center">
4
- <img src="https://savings.bivelio.com/en/how-it-works/opengraph-image" alt="Your application → BV-SALA → provider API. The bill drops from $330 to $160 a month, measured." width="720">
8
+ <em>Cut your AI bill — and see exactly why, measured on the same meter your provider bills you with.</em>
5
9
  </p>
6
10
 
7
11
  <p align="center">
@@ -10,10 +14,26 @@
10
14
  <img src="https://img.shields.io/badge/license-commercial-0F1218" alt="Commercial license">
11
15
  </p>
12
16
 
13
- **One layer inside your code, right where your app calls the OpenAI or Anthropic API.**
14
- For every request it picks the cheapest execution that still meets your quality bar —
15
- avoiding calls that don't need to happen and trimming what must be sent — and hands
16
- you an auditable `SavingsReport`. Your bill drops, and you can verify why.
17
+ ### In plain words
18
+
19
+ Every request your app sends to an AI is paid for by the text going in and out.
20
+ **Savings** sits on that path and lets through only what's needed — skipping calls
21
+ that don't have to happen, and trimming what does — so you pay for less. It is not a
22
+ promise you take on faith: **every saving is measured with the provider's official
23
+ meter, the same one on your bill**, and handed back to you request by request.
24
+
25
+ **We only earn when you save.** Our commission comes out of the measured saving,
26
+ never out of your pocket — most of what you save stays with you, and a month with no
27
+ saving is zero commission. And your prompts **never leave your infrastructure**:
28
+ BiVelio only ever sees counts and cost, never your content.
29
+
30
+ ### For engineers, in one line
31
+
32
+ `@bivelio/savings-layer` (**BV‑SALA**) is one layer you add inside your code, right
33
+ where your app calls the OpenAI‑compatible or Anthropic API. For every request it
34
+ picks the cheapest execution that still meets your quality bar and hands you an
35
+ auditable `SavingsReport` — the same numbers your dashboard reads. Traffic still
36
+ goes straight from your app to your provider; the SDK is never a middleman server.
17
37
 
18
38
  > **Commercial software — © 2026 BiVelio Inc.** Free to install, **paid to run**:
19
39
  > BV‑SALA is fail‑closed and executes only with an active BiVelio license key.
@@ -23,14 +43,20 @@ you an auditable `SavingsReport`. Your bill drops, and you can verify why.
23
43
 
24
44
  ## Where it installs
25
45
 
46
+ <p align="center">
47
+ <img src="https://savings.bivelio.com/en/how-it-works/opengraph-image" alt="Your application → BV-SALA → provider API. The bill drops from $330 to $160 a month, measured." width="720">
48
+ </p>
49
+
26
50
  Your application → **BV‑SALA** → provider API. Traffic always goes straight from
27
51
  your app to your provider — BV‑SALA is a library inside your code, never a
28
52
  middleman server, and it works with any OpenAI‑compatible endpoint (OpenAI, Azure,
29
53
  vLLM, SGLang, a gateway) as well as Anthropic.
30
54
 
31
- **Only use ChatGPT or Claude?** Then there is nothing to install: those products
32
- charge a flat subscription, so there is no per‑token bill to cut. BV‑SALA is for
33
- applications that call the API with their own key. The whole story, in plain words:
55
+ **Is this for me?** If your product or automation calls the AI **with its own API
56
+ key** — and pays a per‑token bill for it — yes: this is exactly where the saving
57
+ installs. **Only use ChatGPT or Claude?** Then there is nothing to install: those
58
+ products charge a flat subscription, so there is no per‑token bill to cut. The whole
59
+ story, in plain words:
34
60
  **[Where does the saving install?](https://savings.bivelio.com/en/how-it-works)**
35
61
 
36
62
  **Claude Code, Codex CLI, Goose or Qwen Code with an API key?** The package also
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {spawn}from'child_process';import {existsSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {homedir}from'os';import {join,dirname,delimiter}from'path';import {fileURLToPath}from'url';import {createServer}from'http';import {randomUUID,createHash}from'crypto';import*as I from'zlib';var $e=new Set;function E(e,t){$e.has(e)||($e.add(e),console.warn(t));}var z=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=
2
+ import {spawn}from'child_process';import {existsSync,readFileSync,mkdirSync,writeFileSync}from'fs';import {homedir}from'os';import {join,dirname,delimiter}from'path';import {fileURLToPath}from'url';import {createServer}from'http';import {randomUUID,createHash}from'crypto';import*as A from'zlib';var $e=new Set;function E(e,t){$e.has(e)||($e.add(e),console.warn(t));}var z=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=
3
3
  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){this.buffer.push({report:t,at:this.now()}),this.
4
4
  buffer.length>this.maxBuffer&&this.buffer.splice(0,this.buffer.length-this.maxBuffer),this.buffer.length>=this.maxBatch&&this.flush();}start(){this.timer||(this.timer=setInterval(()=>{this.flush();},this.
5
5
  flushIntervalMs),this.timer.unref?.());}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0),this.flush();}async stopAndDrain(t){this.timer&&(clearInterval(this.timer),this.timer=void 0);let n=t??
@@ -24,7 +24,7 @@ if(e.KUBERNETES_SERVICE_HOST||_t(e))return;let n=e.HOSTNAME??e.COMPUTERNAME??awa
24
24
  e.arch,e.execPath,n,t.USER??t.USERNAME??t.LOGNAME,t.HOME??t.USERPROFILE].filter(o=>typeof o=="string"&&o.length>0);return r.length?r.join("|"):void 0}var Ot=["CI","CONTINUOUS_INTEGRATION","GITHUB_ACTI\
25
25
  ONS","GITLAB_CI","CIRCLECI","TRAVIS","JENKINS_URL","BUILDKITE","TF_BUILD","TEAMCITY_VERSION","BITBUCKET_BUILD_NUMBER","CODEBUILD_BUILD_ID","HEROKU_TEST_RUN_ID","DRONE"];function Z(){let e=X()?.env;if(!e)
26
26
  return false;for(let t of Ot){let n=e[t];if(n&&n!=="false"&&n!=="0")return true}return false}async function Dt(e,t){let n=globalThis.crypto,r=new TextEncoder;if(n?.subtle)try{let i=await n.subtle.importKey("raw",
27
- r.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),c=await n.subtle.sign("HMAC",i,r.encode(t));return [...new Uint8Array(c)].map(u=>u.toString(16).padStart(2,"0")).join("")}catch{}let o=2166136261,s=`${e}\
27
+ r.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await n.subtle.sign("HMAC",i,r.encode(t));return [...new Uint8Array(a)].map(u=>u.toString(16).padStart(2,"0")).join("")}catch{}let o=2166136261,s=`${e}\
28
28
  |${t}`;for(let i=0;i<s.length;i++)o^=s.charCodeAt(i),o=Math.imul(o,16777619);return (o>>>0).toString(16).padStart(8,"0")}async function ee(e){let n=X()?.env?.BVSALA_SEAT_ID;if(n&&Lt.test(n))return n;let r=await Pt();
29
29
  if(!r)return;let o=await Dt(e,r);if(o)return `m-${o.slice(0,32)}`}var Lt=/^[A-Za-z0-9._:/-]{1,128}$/;var pe={"gpt-4.1":{inputPer1k:.002,outputPer1k:.008,cachedInputPer1k:5e-4},"gpt-4.1-mini":{inputPer1k:4e-4,outputPer1k:.0016,cachedInputPer1k:1e-4},"gpt-4.1-nano":{inputPer1k:1e-4,outputPer1k:4e-4,cachedInputPer1k:25e-6},
30
30
  "gpt-4o":{inputPer1k:.0025,outputPer1k:.01,cachedInputPer1k:.00125},"gpt-4o-mini":{inputPer1k:15e-5,outputPer1k:6e-4,cachedInputPer1k:75e-6},"gpt-4-turbo":{inputPer1k:.01,outputPer1k:.03},"gpt-4":{inputPer1k:.03,
@@ -38,61 +38,63 @@ t-5.6-sol":{inputPer1k:.004,outputPer1k:.02,cachedInputPer1k:4e-4},"gpt-5.6-terr
38
38
  "claude-opus-4-6":{inputPer1k:.005,outputPer1k:.025,cachedInputPer1k:5e-4},"claude-opus-4-7":{inputPer1k:.005,outputPer1k:.025,cachedInputPer1k:5e-4},"claude-opus-4-8":{inputPer1k:.005,outputPer1k:.025,
39
39
  cachedInputPer1k:5e-4},"claude-opus-5":{inputPer1k:.005,outputPer1k:.025,cachedInputPer1k:5e-4},"claude-opus-4":{inputPer1k:.015,outputPer1k:.075,cachedInputPer1k:.0015},"claude-sonnet-4-5":{inputPer1k:.003,
40
40
  outputPer1k:.015,cachedInputPer1k:3e-4},"claude-sonnet-4-6":{inputPer1k:.003,outputPer1k:.015,cachedInputPer1k:3e-4},"claude-sonnet-5":{inputPer1k:.002,outputPer1k:.01,cachedInputPer1k:2e-4},"claude-f\
41
- able-5":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:.001},"claude-sonnet-4":{inputPer1k:.003,outputPer1k:.015,cachedInputPer1k:3e-4},"claude-haiku-4-5":{inputPer1k:.001,outputPer1k:.005,cachedInputPer1k:1e-4},
42
- "claude-3-5-sonnet":{inputPer1k:.003,outputPer1k:.015,cachedInputPer1k:3e-4},"claude-3-5-haiku":{inputPer1k:8e-4,outputPer1k:.004,cachedInputPer1k:8e-5},"claude-3-opus":{inputPer1k:.015,outputPer1k:.075,
43
- cachedInputPer1k:.0015},"claude-3-haiku":{inputPer1k:25e-5,outputPer1k:.00125,cachedInputPer1k:3e-5},"gemini-3.7-flash":{inputPer1k:75e-5,outputPer1k:.00375,cachedInputPer1k:75e-6},"gemini-3.5-flash":{
44
- inputPer1k:.0015,outputPer1k:.009,cachedInputPer1k:15e-5},"gemini-3.5-flash-lite":{inputPer1k:3e-4,outputPer1k:.0025,cachedInputPer1k:3e-5},"gemini-3.1-pro-preview":{inputPer1k:.002,outputPer1k:.012,cachedInputPer1k:2e-4},
45
- "gemini-2.5-pro":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gemini-2.5-flash":{inputPer1k:3e-4,outputPer1k:.0025,cachedInputPer1k:3e-5}};function Ne(e,t,n){if(!n)return;let r=e[n];if(r)
46
- return r;let o=n.lastIndexOf("/"),s=o>=0?n.slice(o+1):n,i=e[s];if(i)return i;for(let c of t){if(!s.startsWith(c))continue;let u=s.charAt(c.length);if(u!==""&&u!=="-"&&u!=="."&&u!=="_")continue;let p=s.
47
- slice(c.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[c]}}function me(e){return Object.keys(e).sort((t,n)=>n.length-t.length)}var Mt=me(pe);function M(e){return Ne(pe,Mt,e)}var $=class{resto="";uso=null;elisionObservada=null;eventos={};observar(t){this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
41
+ able-5":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:.001},"claude-fable-5-1":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:25e-5},"claude-mythos-5-1":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:25e-5},
42
+ "claude-mythos-5":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:.001},"claude-sonnet-4":{inputPer1k:.003,outputPer1k:.015,cachedInputPer1k:3e-4},"claude-haiku-4-5":{inputPer1k:.001,outputPer1k:.005,
43
+ cachedInputPer1k:1e-4},"claude-3-5-sonnet":{inputPer1k:.003,outputPer1k:.015,cachedInputPer1k:3e-4},"claude-3-5-haiku":{inputPer1k:8e-4,outputPer1k:.004,cachedInputPer1k:8e-5},"claude-3-opus":{inputPer1k:.015,
44
+ outputPer1k:.075,cachedInputPer1k:.0015},"claude-3-haiku":{inputPer1k:25e-5,outputPer1k:.00125,cachedInputPer1k:3e-5},"gemini-3.7-flash":{inputPer1k:75e-5,outputPer1k:.00375,cachedInputPer1k:75e-6},"g\
45
+ emini-3.5-flash":{inputPer1k:.0015,outputPer1k:.009,cachedInputPer1k:15e-5},"gemini-3.5-flash-lite":{inputPer1k:3e-4,outputPer1k:.0025,cachedInputPer1k:3e-5},"gemini-3.1-pro-preview":{inputPer1k:.002,
46
+ outputPer1k:.012,cachedInputPer1k:2e-4},"gemini-2.5-pro":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gemini-2.5-flash":{inputPer1k:3e-4,outputPer1k:.0025,cachedInputPer1k:3e-5}};function Ne(e,t,n){
47
+ if(!n)return;let r=e[n];if(r)return r;let o=n.lastIndexOf("/"),s=o>=0?n.slice(o+1):n,i=e[s];if(i)return i;for(let a of t){if(!s.startsWith(a))continue;let u=s.charAt(a.length);if(u!==""&&u!=="-"&&u!==
48
+ "."&&u!=="_")continue;let p=s.slice(a.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[a]}}function me(e){return Object.keys(e).sort((t,n)=>n.length-t.length)}var Mt=me(pe);function M(e){return Ne(
49
+ pe,Mt,e)}var $=class{resto="";uso=null;elisionObservada=null;eventos={};observar(t){this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
48
50
 
49
51
  `))>=0;){let r=this.resto.slice(0,n);this.resto=this.resto.slice(n+2);let o=/^event: (.+)$/m.exec(r)?.[1];if(o&&(this.eventos[o]=(this.eventos[o]??0)+1),o!=="message_start"&&o!=="message_delta")continue;
50
- let s=/^data: (.+)$/m.exec(r)?.[1];if(s)try{let i=JSON.parse(s),c=ze(i.context_management);c&&(this.elisionObservada=c);let u=i.usage??i.message?.usage;if(u){let p=u.cache_creation||this.uso?.cache_creation?
52
+ let s=/^data: (.+)$/m.exec(r)?.[1];if(s)try{let i=JSON.parse(s),a=ze(i.context_management);a&&(this.elisionObservada=a);let u=i.usage??i.message?.usage;if(u){let p=u.cache_creation||this.uso?.cache_creation?
51
53
  {...this.uso?.cache_creation,...u.cache_creation}:void 0;this.uso={...this.uso,...u,...p?{cache_creation:p}:{}};}}catch{}}}usage(){return this.uso}usageFinal(){return (this.eventos.message_delta??0)>0?this.
52
54
  uso:null}elision(){return this.elisionObservada}};function ze(e){if(e===null||typeof e!="object")return null;let t=e.applied_edits;if(!Array.isArray(t)||t.length===0)return null;let n=0;for(let r of t){
53
55
  let o=r.cleared_input_tokens;typeof o=="number"&&Number.isFinite(o)&&o>0&&(n+=o);}return {borrados:t.length,tokensLimpiados:n}}function Be(e){try{let t=JSON.parse(typeof e=="string"?e:e.toString("utf8"));
54
56
  return ze(t.context_management)}catch{return null}}var te=class{resto="";uso=null;eventos={};observar(t){this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
55
57
 
56
58
  `))>=0;){let r=this.resto.slice(0,n);this.resto=this.resto.slice(n+2);let o=/^event: (.+)$/m.exec(r)?.[1];o&&(this.eventos[o]=(this.eventos[o]??0)+1);let s=/^data: (.+)$/m.exec(r)?.[1];if(s)try{let i=JSON.
57
- parse(s);if((o??i.type)!=="response.completed")continue;let c=i.response?.usage;c&&(this.uso=$t(c));}catch{}}}usage(){return this.uso}usageFinal(){return this.uso}};function $t(e){let t=Math.max(0,e.input_tokens??
59
+ parse(s);if((o??i.type)!=="response.completed")continue;let a=i.response?.usage;a&&(this.uso=$t(a));}catch{}}}usage(){return this.uso}usageFinal(){return this.uso}};function $t(e){let t=Math.max(0,e.input_tokens??
58
60
  0),n=Math.min(Math.max(0,e.input_tokens_details?.cached_tokens??0),t),r=Math.min(Math.max(0,e.input_tokens_details?.cache_write_tokens??0),t-n);return {input_tokens:t-n-r,output_tokens:Math.max(0,e.output_tokens??
59
61
  0),cache_read_input_tokens:n,cache_creation_input_tokens:r}}var je=1.25,fe=2;function Ue(e,t){let n=M(e);if(!n)return null;let r=t.input_tokens??0,o=t.output_tokens??0,s=t.cache_read_input_tokens??0,i=t.
60
- cache_creation,c=t.cache_creation_input_tokens??(i?.ephemeral_5m_input_tokens??0)+(i?.ephemeral_1h_input_tokens??0),u=Math.min(i?.ephemeral_1h_input_tokens??0,c),p=n.cachedInputPer1k??n.inputPer1k;return {
61
- costeUsd:r/1e3*n.inputPer1k+s/1e3*p+(c-u)/1e3*n.inputPer1k*ge(e)+u/1e3*n.inputPer1k*fe+o/1e3*n.outputPer1k,precio:n,tokens:{entrada:r,salida:o,cacheLeida:s,cacheEscrita:c,cacheEscrita1h:u}}}function Ve(e,t){
62
- let r=he(e,t)?.model;return typeof r=="string"?r:""}function he(e,t){try{let n=e,r={maxOutputLength:Nt};switch((t??"").trim().toLowerCase()){case "gzip":case "x-gzip":n=I.gunzipSync(e,r);break;case "defl\
63
- ate":n=I.inflateSync(e,r);break;case "br":n=I.brotliDecompressSync(e,r);break;case "zstd":{let o=I.zstdDecompressSync;if(typeof o!="function")return null;n=o(e,r);break}default:break}return JSON.parse(n.
64
- toString("utf8"))}catch{return null}}var Nt=5*1024*1024;function Fe(){return typeof I.zstdDecompressSync=="function"}function ge(e){let t=e.toLowerCase();return t.startsWith("gpt-")||t.startsWith("o1")||
62
+ cache_creation,a=t.cache_creation_input_tokens??(i?.ephemeral_5m_input_tokens??0)+(i?.ephemeral_1h_input_tokens??0),u=Math.min(i?.ephemeral_1h_input_tokens??0,a),p=n.cachedInputPer1k??n.inputPer1k;return {
63
+ costeUsd:r/1e3*n.inputPer1k+s/1e3*p+(a-u)/1e3*n.inputPer1k*ge(e)+u/1e3*n.inputPer1k*fe+o/1e3*n.outputPer1k,precio:n,tokens:{entrada:r,salida:o,cacheLeida:s,cacheEscrita:a,cacheEscrita1h:u}}}function Ve(e,t){
64
+ let r=he(e,t)?.model;return typeof r=="string"?r:""}function he(e,t){try{let n=e,r={maxOutputLength:Nt};switch((t??"").trim().toLowerCase()){case "gzip":case "x-gzip":n=A.gunzipSync(e,r);break;case "defl\
65
+ ate":n=A.inflateSync(e,r);break;case "br":n=A.brotliDecompressSync(e,r);break;case "zstd":{let o=A.zstdDecompressSync;if(typeof o!="function")return null;n=o(e,r);break}default:break}return JSON.parse(n.
66
+ toString("utf8"))}catch{return null}}var Nt=5*1024*1024;function Fe(){return typeof A.zstdDecompressSync=="function"}function ge(e){let t=e.toLowerCase();return t.startsWith("gpt-")||t.startsWith("o1")||
65
67
  t.startsWith("o3")||t.startsWith("o4")?/^gpt-5\.6/.test(t)?je:1:t.startsWith("gemini")?1:je}function be(e){try{return JSON.parse(typeof e=="string"?e:e.toString("utf8")).usage??null}catch{return null}}
66
- function qe(e,t,n,r){return Je(e,t,n,false,r)}function Ke(e,t,n,r){return Je(e,t,n,true,r)}function He(e,t,n,r,o){return {requestId:e,model:t,...o&&(o.sessionId||o.agentId)?{attribution:o}:{},calls:{avoidedLlm:0,
68
+ function qe(e,t,n,r,o){return Je(e,t,n,false,r,o)}function Ke(e,t,n,r){return Je(e,t,n,true,r)}function He(e,t,n,r,o){return {requestId:e,model:t,...o&&(o.sessionId||o.agentId)?{attribution:o}:{},calls:{avoidedLlm:0,
67
69
  actualLlm:0},tokens:{baselineInput:0,actualInput:0,eliminatedInput:0,compressedInput:0,baselineOutput:0,actualOutput:0,providerCachedInput:0},cache:{result:"miss"},cost:{baselineEstimate:n,actual:r,netSavings:n>
68
- 0?Math.max(-100,Math.min(1,(n-r)/n)):0,basis:"shadow"}}}function Je(e,t,n,r,o){let s=n.tokens.entrada+n.tokens.cacheLeida+n.tokens.cacheEscrita;return {requestId:e,model:t,...o&&(o.sessionId||o.agentId)?
69
- {attribution:o}:{},calls:r?{avoidedLlm:1,actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{baselineInput:s,actualInput:r?0:s,eliminatedInput:0,compressedInput:0,baselineOutput:n.tokens.salida,actualOutput:r?
70
- 0:n.tokens.salida,providerCachedInput:r?0:n.tokens.cacheLeida},cache:{result:r?"singleflight":"miss"},cost:r?{baselineEstimate:n.costeUsd,actual:0,netSavings:1,basis:"estimated"}:{baselineEstimate:n.costeUsd,
71
- actual:n.costeUsd,netSavings:0,basis:"measured"}}}var zt=1024,q=120,Bt=8,Ut=256*1024,Vt=/error|exception|traceback|warn|fatal|fail|panic|assert|denied|refused|timeout|\bat\s+\S+:\d+/i,Ge=/^\s*(export\s|import\s|function\s|class\s|interface\s|type\s|enum\s|def\s|fn\s|func\s|impl\s|struct\s|trait\s|package\s|module\s|@\w)/;
70
+ 0?Math.max(-100,Math.min(1,(n-r)/n)):0,basis:"shadow"}}}function Je(e,t,n,r,o,s){let i=n.tokens.entrada+n.tokens.cacheLeida+n.tokens.cacheEscrita;return {requestId:e,model:t,...o&&(o.sessionId||o.agentId)?
71
+ {attribution:o}:{},calls:r?{avoidedLlm:1,actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{baselineInput:i,actualInput:r?0:i,eliminatedInput:r?0:s?.tokensLimpiados??0,compressedInput:0,baselineOutput:n.
72
+ tokens.salida,actualOutput:r?0:n.tokens.salida,providerCachedInput:r?0:n.tokens.cacheLeida},cache:{result:r?"singleflight":"miss"},cost:r?{baselineEstimate:n.costeUsd,actual:0,netSavings:1,basis:"esti\
73
+ mated"}:{baselineEstimate:n.costeUsd,actual:n.costeUsd,netSavings:0,basis:"measured"}}}var zt=1024,q=120,Bt=8,Ut=256*1024,Vt=/error|exception|traceback|warn|fatal|fail|panic|assert|denied|refused|timeout|\bat\s+\S+:\d+/i,Ge=/^\s*(export\s|import\s|function\s|class\s|interface\s|type\s|enum\s|def\s|fn\s|func\s|impl\s|struct\s|trait\s|package\s|module\s|@\w)/;
72
74
  function Ft(e){return Buffer.byteLength(typeof e=="string"?e:JSON.stringify(e??""))}function qt(e){return typeof e=="string"?e:Array.isArray(e)?e.map(t=>t&&typeof t=="object"&&typeof t.text=="string"?
73
75
  t.text:"").join(`
74
76
  `):""}var Kt=/^\s*\d+[\t→]/;function Ye(e){return e.replace(Kt,"")}function Ht(e){if(!e)return "otro";let t=e.split(`
75
- `),n=Math.max(1,t.length),r=0,o=0,s=0;for(let i of t){let c=Ye(i);(/^[+-](?![+-])/.test(c)||c.startsWith("@@")||c.startsWith("diff --git"))&&(r+=1),Ge.test(c)&&(o+=1),(/^\s*(\d{4}-\d{2}-\d{2}|\[\d|\d{2}:\d{2}:\d{2})/.
76
- test(c)||/\b(INFO|DEBUG|TRACE|WARN|ERROR)\b/.test(c))&&(s+=1);}return r/n>.25?"diff":s/n>.3?"log":o/n>.08?"codigo":"otro"}function Jt(e,t){if(t==="diff"||t==="otro")return Buffer.byteLength(e);let n=e.
77
+ `),n=Math.max(1,t.length),r=0,o=0,s=0;for(let i of t){let a=Ye(i);(/^[+-](?![+-])/.test(a)||a.startsWith("@@")||a.startsWith("diff --git"))&&(r+=1),Ge.test(a)&&(o+=1),(/^\s*(\d{4}-\d{2}-\d{2}|\[\d|\d{2}:\d{2}:\d{2})/.
78
+ test(a)||/\b(INFO|DEBUG|TRACE|WARN|ERROR)\b/.test(a))&&(s+=1);}return r/n>.25?"diff":s/n>.3?"log":o/n>.08?"codigo":"otro"}function Jt(e,t){if(t==="diff"||t==="otro")return Buffer.byteLength(e);let n=e.
77
79
  split(`
78
80
  `),r=new Set;for(let s=0;s<n.length;s++){let i=Ye(n[s]);(t==="log"?Vt.test(i):Ge.test(i))&&(r.add(s),t==="log"&&(s>0&&r.add(s-1),s<n.length-1&&r.add(s+1)));}let o=q;for(let s of r)o+=Buffer.byteLength(
79
81
  n[s])+1;return Math.min(o,Buffer.byteLength(e))}function Qe(e){let t=[],n=(r,o)=>{let s=Ft(o),i={clave:createHash("sha256").update(typeof o=="string"?o:JSON.stringify(o??"")).digest("hex").slice(0,16),tipo:r,
80
- bytes:s,bytesV1:s,clase:"otro",esError:false};if(r.endsWith(":tool_result")&&o&&typeof o=="object"){let c=o;i.esError=c.is_error===true;let u=qt(c.content);i.clase=Ht(u);let p=s-Buffer.byteLength(u);i.bytesV1=
82
+ bytes:s,bytesV1:s,clase:"otro",esError:false};if(r.endsWith(":tool_result")&&o&&typeof o=="object"){let a=o;i.esError=a.is_error===true;let u=qt(a.content);i.clase=Ht(u);let p=s-Buffer.byteLength(u);i.bytesV1=
81
83
  Math.min(s,Math.max(0,p)+Jt(u,i.clase));}t.push(i);};if((e.system??void 0)!==void 0&&n("system",e.system),Array.isArray(e.tools))for(let r of e.tools)n("tools",r);for(let r of Array.isArray(e.messages)?
82
84
  e.messages:[]){if(!r||typeof r!="object"){n("?:invalido",r);continue}let o=r,s=typeof o.role=="string"?o.role:"?";if(typeof o.content=="string")n(`${s}:text`,o.content);else if(Array.isArray(o.content))
83
85
  for(let i of o.content)n(`${s}:${i?.type??"?"}`,i);}return t}function Wt(){return {nacimiento:new Map,turno:0,elididosV2m:new Set}}function Gt(e,t){let n=e.turno++;for(let r of t)e.nacimiento.has(r.clave)||
84
- e.nacimiento.set(r.clave,n);return n}function Xe(e,t,n,r,o=Ut){let s=t.reduce((l,a,d)=>a.tipo.endsWith(":tool_result")?d:l,-1),i=(l,a)=>l.tipo.endsWith(":tool_result")&&l.bytes>=zt&&a!==s;return t.map((l,a)=>{if(!i(l,a))return {clave:l.clave,bytes:l.bytes};let d=r-(n.nacimiento.get(l.clave)??r);return d>=2&&d*l.bytes/1024>=Bt?{clave:`${l.clave}:e`,bytes:q}:{clave:l.clave,bytes:l.bytes}});
86
+ e.nacimiento.set(r.clave,n);return n}function Xe(e,t,n,r,o=Ut){let s=t.reduce((l,c,d)=>c.tipo.endsWith(":tool_result")?d:l,-1),i=(l,c)=>l.tipo.endsWith(":tool_result")&&l.bytes>=zt&&c!==s;return t.map((l,c)=>{if(!i(l,c))return {clave:l.clave,bytes:l.bytes};let d=r-(n.nacimiento.get(l.clave)??r);return d>=2&&d*l.bytes/1024>=Bt?{clave:`${l.clave}:e`,bytes:q}:{clave:l.clave,bytes:l.bytes}});
85
87
  }function Yt(e){let t=M(e);return t?{lecturaPer1k:t.cachedInputPer1k??t.inputPer1k,escrituraPer1k:t.
86
88
  inputPer1k*ge(e),escritura1hPer1k:t.inputPer1k*fe}:null}function We(){return {lecturaUsd:0,escrituraUsd:0,totalUsd:0,tokensLeidos:0,tokensEscritos:0,tokensInvalidados:0,invalidacionUsd:0}}var Qt={"1h":36e5,
87
89
  "5m":3e5},K=class{constructor(t){this.transformar=t;}transformar;estado=Wt();previo=[];hayPrevio=false;atPrevio=0;acumulado=We();paso(t){let n=We(),r=Gt(this.estado,t.bloques),o=Yt(t.model),s=this.transformar(
88
- t.bloques,this.estado,r);if(!o||t.entradaReal<=0)return this.previo=s,this.hayPrevio=true,this.atPrevio=t.at,n;let i=t.bloques.reduce((h,b)=>h+b.bytes,0),c=i>0?t.entradaReal/i:0,u=Qt[t.escritura1h?"1h":
89
- "5m"],p=this.hayPrevio&&t.at-this.atPrevio>u,m=0;if(!p)for(;m<s.length&&m<this.previo.length&&s[m].clave===this.previo[m].clave&&s[m].bytes===this.previo[m].bytes;)m+=1;let l=this.previo.slice(m),a=0,
90
- d=t.escritura1h?o.escritura1hPer1k:o.escrituraPer1k;for(let h=0;h<s.length;h++){let b=s[h],v=b.bytes*c;if(h<m){n.tokensLeidos+=v,n.lecturaUsd+=v/1e3*o.lecturaPer1k;continue}if(n.tokensEscritos+=v,n.escrituraUsd+=
91
- v/1e3*d,!p){let f=a;for(;f<l.length&&l[f].clave!==b.clave;)f+=1;f<l.length&&(l[f].bytes===b.bytes&&(n.tokensInvalidados+=v,n.invalidacionUsd+=v/1e3*(d-o.lecturaPer1k)),a=f+1);}}return this.previo=s,this.
90
+ t.bloques,this.estado,r);if(!o||t.entradaReal<=0)return this.previo=s,this.hayPrevio=true,this.atPrevio=t.at,n;let i=t.bloques.reduce((h,b)=>h+b.bytes,0),a=i>0?t.entradaReal/i:0,u=Qt[t.escritura1h?"1h":
91
+ "5m"],p=this.hayPrevio&&t.at-this.atPrevio>u,m=0;if(!p)for(;m<s.length&&m<this.previo.length&&s[m].clave===this.previo[m].clave&&s[m].bytes===this.previo[m].bytes;)m+=1;let l=this.previo.slice(m),c=0,
92
+ d=t.escritura1h?o.escritura1hPer1k:o.escrituraPer1k;for(let h=0;h<s.length;h++){let b=s[h],v=b.bytes*a;if(h<m){n.tokensLeidos+=v,n.lecturaUsd+=v/1e3*o.lecturaPer1k;continue}if(n.tokensEscritos+=v,n.escrituraUsd+=
93
+ v/1e3*d,!p){let f=c;for(;f<l.length&&l[f].clave!==b.clave;)f+=1;f<l.length&&(l[f].bytes===b.bytes&&(n.tokensInvalidados+=v,n.invalidacionUsd+=v/1e3*(d-o.lecturaPer1k)),c=f+1);}}return this.previo=s,this.
92
94
  hayPrevio=true,this.atPrevio=t.at,n.totalUsd=n.lecturaUsd+n.escrituraUsd,this.acumulado.lecturaUsd+=n.lecturaUsd,this.acumulado.escrituraUsd+=n.escrituraUsd,this.acumulado.totalUsd+=n.totalUsd,this.acumulado.
93
95
  tokensLeidos+=n.tokensLeidos,this.acumulado.tokensEscritos+=n.tokensEscritos,this.acumulado.tokensInvalidados+=n.tokensInvalidados,this.acumulado.invalidacionUsd+=n.invalidacionUsd,n}};function Ze(e){return e.map(t=>({clave:t.clave,bytes:t.bytes}))}var Zt=64,ne=class{cadenas=new Map;observar(t){let n=t.uso.cache_creation_input_tokens??(t.uso.cache_creation?.ephemeral_5m_input_tokens??0)+(t.uso.cache_creation?.ephemeral_1h_input_tokens??0),r=(t.uso.
94
96
  input_tokens??0)+(t.uso.cache_read_input_tokens??0)+n;if(r<=0)return null;let o={bloques:Qe(t.cuerpo),at:t.at,model:t.model,entradaReal:r,escritura1h:(t.uso.cache_creation?.ephemeral_1h_input_tokens??
95
- 0)>0};if(o.bloques.length===0)return null;let s=this.cadenaPara(this.clave(t)),i=s.base.paso(o),c=s.v0.paso(o);return i.totalUsd<=0&&c.totalUsd<=0?null:{baselineUsd:i.totalUsd,politicaUsd:c.totalUsd}}clave(t){
97
+ 0)>0};if(o.bloques.length===0)return null;let s=this.cadenaPara(this.clave(t)),i=s.base.paso(o),a=s.v0.paso(o);return i.totalUsd<=0&&a.totalUsd<=0?null:{baselineUsd:i.totalUsd,politicaUsd:a.totalUsd}}clave(t){
96
98
  if(t.sessionId)return `s:${t.sessionId}\xB7a:${t.agentId??""}\xB7${t.model}`;let n=Array.isArray(t.cuerpo.messages)?t.cuerpo.messages[0]:void 0;return `m:${createHash("sha256").update(JSON.stringify(n??null)).digest(
97
99
  "hex").slice(0,16)}\xB7${t.model}`}cadenaPara(t){let n=this.cadenas.get(t);if(!n&&(n={base:new K(r=>Ze(r)),v0:new K((r,o,s)=>Xe("v0",r,o,s)),ultimoUso:0},this.cadenas.set(t,n),this.cadenas.size>Zt)){let r=null,
98
100
  o=1/0;for(let[s,i]of this.cadenas)i.ultimoUso<o&&(o=i.ultimoUso,r=s);r!==null&&this.cadenas.delete(r);}return n.ultimoUso=Date.now(),n}};var oe=class{url;clave;ttlMs;fetchImpl;log;features=new Set;validoHasta=0;enVuelo=null;avisado=false;constructor(t){this.url=t.url,this.clave=t.serviceAccountKey,this.ttlMs=t.ttlMs??3e5,this.fetchImpl=t.
@@ -107,46 +109,46 @@ n.messages)?null:(n.context_management=et(),Buffer.from(JSON.stringify(n),"utf8"
107
109
  `).update(t).update(`
108
110
  `).update(r).update(`
109
111
  `).update(n).digest("hex")}function rt(e){return tn.map(t=>`${t}=${e(t)??""}`).join(`
110
- `)}var se=class{enVuelo=new Map;primarias=0;gemelas=0;registrar(t){if(this.enVuelo.has(t))return null;this.primarias+=1;let n,r=new Promise(c=>n=c);this.enVuelo.set(t,{promesa:r,resolver:n});let o=[],
111
- s=0,i=false;return {capturar:c=>{if(!i){if(s+=c.length,s>ve){i=true,o.length=0;return}o.push(c);}},terminar:(c,u)=>{let p=!i&&c>=200&&c<300;this.enVuelo.get(t)?.resolver(p?{status:c,headers:u,chunks:o}:null),
112
+ `)}var se=class{enVuelo=new Map;primarias=0;gemelas=0;registrar(t){if(this.enVuelo.has(t))return null;this.primarias+=1;let n,r=new Promise(a=>n=a);this.enVuelo.set(t,{promesa:r,resolver:n});let o=[],
113
+ s=0,i=false;return {capturar:a=>{if(!i){if(s+=a.length,s>ve){i=true,o.length=0;return}o.push(a);}},terminar:(a,u)=>{let p=!i&&a>=200&&a<300;this.enVuelo.get(t)?.resolver(p?{status:a,headers:u,chunks:o}:null),
112
114
  this.enVuelo.delete(t);},abortar:()=>{this.enVuelo.get(t)?.resolver(null),this.enVuelo.delete(t);}}}async esperar(t){let n=this.enVuelo.get(t);if(!n)return null;let r=await n.promesa;return r&&(this.gemelas+=
113
- 1),r}tieneEnVuelo(t){return this.enVuelo.has(t)}};var rn=["content-encoding","content-length","transfer-encoding"];async function st(e={}){let t=(e.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=e.fetchImpl??fetch,r=e.log??(a=>console.log(
114
- a)),o;if(e.serviceAccountKey){o=new z({url:e.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},onAuthError:d=>E(
115
+ 1),r}tieneEnVuelo(t){return this.enVuelo.has(t)}};var rn=["content-encoding","content-length","transfer-encoding"];async function st(e={}){let t=(e.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=e.fetchImpl??fetch,r=e.log??(c=>console.log(
116
+ c)),o;if(e.serviceAccountKey){o=new z({url:e.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},onAuthError:d=>E(
115
117
  "gateway-ingest-auth",`[bvsala gateway] la ingesta de uso rechaza la credencial (${d}). El tr\xE1fico sigue fluyendo (avisar, nunca romper), pero este uso no est\xE1 llegando al panel. Revisa BIVELIO_SERVIC\
116
- E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let a=Z();e.seatId?o.setSeat(e.seatId,a):ee(e.serviceAccountKey).then(d=>o?.setSeat(d,a)).catch(()=>{}),o.start();}else E("gateway-sin\
118
+ E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=Z();e.seatId?o.setSeat(e.seatId,c):ee(e.serviceAccountKey).then(d=>o?.setSeat(d,c)).catch(()=>{}),o.start();}else E("gateway-sin\
117
119
  -clave","[bvsala gateway] sin BIVELIO_SERVICE_KEY: el gateway reenv\xEDa el tr\xE1fico pero NO mide. Consigue tu clave en savings.bivelio.com/dashboard/install para ver el gasto en tu panel.");let s=e.
118
- coalescer?new se:null,i=e.sombra&&o?new ne:null,c=e.elision&&e.serviceAccountKey?new oe({url:new URL("/api/org/entitlements",e.meteringUrl??"https://savings.bivelio.com/api/metering/usage").toString(),
119
- serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},log:r}):null,u=a=>a==="/v1/messages"?new $:a.endsWith("/responses")?new te:null,p=(a,d,h,b,v,f,y,D)=>{if(!o||!h)return;let W=o,
120
+ coalescer?new se:null,i=e.sombra&&o?new ne:null,a=e.elision!==false&&e.serviceAccountKey?new oe({url:new URL("/api/org/entitlements",e.meteringUrl??"https://savings.bivelio.com/api/metering/usage").toString(),
121
+ serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},log:r}):null,u=c=>c==="/v1/messages"?new $:c.endsWith("/responses")?new te:null,p=(c,d,h,b,v,f,y,R)=>{if(!o||!h)return;let W=o,
120
122
  V=h,S=Ve(d,f);if(!S){E(`gateway-sin-modelo:${v}`,`[bvsala gateway] no se pudo leer el modelo del cuerpo de ${v} (content-encoding: ${f??"ninguno"}); su uso se reenv\xEDa pero no se mide.`);return}let T=Ue(
121
- S,h);T?(o.record(b?Ke(a,S,T,y):qe(a,S,T,y)),r(`[gateway] POST ${v} ${b?"\xB7 COALESCIDA (llamada evitada)":""} \xB7 ${S} \xB7 $${T.costeUsd.toFixed(6)} (in ${T.tokens.entrada}, cache\u2191${T.tokens.cacheEscrita}${T.
122
- tokens.cacheEscrita1h?` (1h ${T.tokens.cacheEscrita1h})`:""} cache\u2193${T.tokens.cacheLeida}, out ${T.tokens.salida})`+(D?` \xB7 elisi\xF3n: ${D.borrados} borrado(s), ${D.tokensLimpiados} tok limpia\
123
- dos por el proveedor`:"")),i&&!b&&v==="/v1/messages"&&setImmediate(()=>{try{let L=he(d,f);if(!L||typeof L!="object")return;let R=i.observar({cuerpo:L,uso:V,model:S,at:Date.now(),...y?.sessionId!==void 0?
124
- {sessionId:y.sessionId}:{},...y?.agentId!==void 0?{agentId:y.agentId}:{}});if(!R)return;W.record(He(`${a}:shadow`,S,R.baselineUsd,R.politicaUsd,y)),r(`[gateway] sombra \xB7 ${S} \xB7 baseline $${R.baselineUsd.
125
- toFixed(6)} \u2192 v0 $${R.politicaUsd.toFixed(6)}`);}catch{}})):E(`gateway-sin-precio:${S}`,`[bvsala gateway] el modelo "${S}" no est\xE1 en el cat\xE1logo de precios; su uso se reenv\xEDa pero no se mide.`);},
126
- m=createServer(async(a,d)=>{let h=randomUUID(),b=new AbortController;d.on("close",()=>{d.writableEnded||b.abort();});let v=null,f=new URL(a.url??"/",t),y=null,D=Buffer.alloc(0),W=false,V=a.headers["content-encoding"],S=Array.
127
- isArray(V)?V[0]:V,T=_=>{let w=Array.isArray(_)?_[0]:_;return w!==void 0&&/^[A-Za-z0-9._:/-]{1,128}$/.test(w)?w:void 0},L=T(a.headers["x-claude-code-session-id"]),R=T(a.headers["x-claude-code-agent-id"]),
128
- G=L!==void 0||R!==void 0?{...L!==void 0?{sessionId:L}:{},...R!==void 0?{agentId:R}:{}}:void 0;try{let _=[];for await(let g of a)_.push(g);let w=Buffer.concat(_);D=w,f=new URL(a.url??"/",t);let Y=s!==null&&
129
- a.method==="POST"&&w.length>0,Ct=rt(g=>{let x=a.headers[g];return Array.isArray(x)?x.join(", "):x}),le=Y?ot("POST",f.pathname+f.search,w,Ct):"";if(Y)if(s.tieneEnVuelo(le)){let g=await s.esperar(le);if(g){
130
- d.writeHead(g.status,g.headers);let x=u(f.pathname)??new $;for(let Me of g.chunks)d.write(Me),x.observar(Me);d.end();let Et=x.usage()??(f.pathname==="/v1/messages"?be(Buffer.concat(g.chunks)):null);p(
131
- h,w,Et,!0,f.pathname,S,G);return}}else v=s.registrar(le);let Q={};for(let[g,x]of Object.entries(a.headers))g==="host"||g==="content-length"||x===void 0||(Q[g]=Array.isArray(x)?x.join(", "):x);let Oe=w;
132
- if(c!==null&&a.method==="POST"&&f.pathname==="/v1/messages"&&c.tiene("context_elision"))try{let g=tt(w,S);g!==null&&(Oe=g,Q["anthropic-beta"]=nt(Q["anthropic-beta"]));}catch{}let C;try{C=await n(f,{method:a.
133
- method,headers:Q,body:["GET","HEAD"].includes(a.method??"")?void 0:Oe,redirect:"manual",signal:b.signal});}catch(g){v?.abortar(),d.headersSent||(d.writeHead(502,{"content-type":"application/json"}),d.end(
134
- JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(g)}))),r(`[gateway] ${a.method} ${f.pathname} \u2192 upstream inalcanzable: ${String(g)}`);return}let ue=Object.fromEntries(C.
135
- headers.entries());for(let g of rn)delete ue[g];d.writeHead(C.status,ue);let De=C.headers.get("content-type")??"",de=De.includes("text/event-stream")||De===""&&C.ok&&u(f.pathname)!==null;y=de?u(f.pathname):
136
- null;let F=!de&&C.ok&&o!==void 0&&a.method==="POST"&&f.pathname==="/v1/messages"&&(C.headers.get("content-type")??"").includes("application/json")?[]:null,Le=0;if(C.body)for await(let g of C.body){let x=Buffer.
137
- from(g);d.write(x),y?.observar(x),F&&(Le+=x.length,Le>ve?F=null:F.push(x)),v?.capturar(x);}if(d.end(),v?.terminar(C.status,ue),y&&C.ok){W=!0;let g=y instanceof $?y.elision():null;p(h,w,y.usage(),!1,f.pathname,
138
- S,G,g);}else if(F){let g=Buffer.concat(F);p(h,w,be(g),!1,f.pathname,S,G,Be(g));}else de||r(`[gateway] ${a.method} ${f.pathname} \u2192 ${C.status}`);}catch(_){if(v?.abortar(),b.signal.aborted){let w=!W&&
139
- y?y.usageFinal():null;if(w){r(`[gateway] ${a.method} ${f.pathname} \u2192 el cliente cerr\xF3 tras el usage final; se mide igual`);let Y=y instanceof $?y.elision():null;p(h,D,w,false,f.pathname,S,G,Y);}else
140
- r(`[gateway] ${a.method} ${f.pathname} \u2192 el cliente abort\xF3; upstream cancelado`);}else if(r(`[gateway] ${a.method} ${f.pathname} \u2192 error sirviendo: ${String(_)}`),!d.headersSent){d.writeHead(
141
- 502,{"content-type":"application/json"}),d.end(JSON.stringify({error:"bvsala gateway: stream interrupted",detail:String(_)}));return}d.writableEnded||d.end();}});return m.on("upgrade",(a,d)=>{r(`[gatew\
142
- ay] ${a.method} ${new URL(a.url??"/","http://x").pathname} \u2192 426 (WebSocket no soportado; el cliente cae a HTTP)`),d.once("error",()=>{}),d.once("finish",()=>d.destroy()),d.end(`HTTP/1.1 426 Upgr\
143
- ade Required\r
123
+ S,h);T?(o.record(b?Ke(c,S,T,y):qe(c,S,T,y,R)),r(`[gateway] POST ${v} ${b?"\xB7 COALESCIDA (llamada evitada)":""} \xB7 ${S} \xB7 $${T.costeUsd.toFixed(6)} (in ${T.tokens.entrada}, cache\u2191${T.tokens.
124
+ cacheEscrita}${T.tokens.cacheEscrita1h?` (1h ${T.tokens.cacheEscrita1h})`:""} cache\u2193${T.tokens.cacheLeida}, out ${T.tokens.salida})`+(R?` \xB7 elisi\xF3n: ${R.borrados} borrado(s), ${R.tokensLimpiados}\
125
+ tok limpiados por el proveedor`:"")),i&&!b&&v==="/v1/messages"&&setImmediate(()=>{try{let L=he(d,f);if(!L||typeof L!="object")return;let _=i.observar({cuerpo:L,uso:V,model:S,at:Date.now(),...y?.sessionId!==
126
+ void 0?{sessionId:y.sessionId}:{},...y?.agentId!==void 0?{agentId:y.agentId}:{}});if(!_)return;W.record(He(`${c}:shadow`,S,_.baselineUsd,_.politicaUsd,y)),r(`[gateway] sombra \xB7 ${S} \xB7 baseline $${_.
127
+ baselineUsd.toFixed(6)} \u2192 v0 $${_.politicaUsd.toFixed(6)}`);}catch{}})):E(`gateway-sin-precio:${S}`,`[bvsala gateway] el modelo "${S}" no est\xE1 en el cat\xE1logo de precios; su uso se reenv\xEDa pero no \
128
+ se mide.`);},m=createServer(async(c,d)=>{let h=randomUUID(),b=new AbortController;d.on("close",()=>{d.writableEnded||b.abort();});let v=null,f=new URL(c.url??"/",t),y=null,R=Buffer.alloc(0),W=false,V=c.headers["content-enco\
129
+ ding"],S=Array.isArray(V)?V[0]:V,T=I=>{let w=Array.isArray(I)?I[0]:I;return w!==void 0&&/^[A-Za-z0-9._:/-]{1,128}$/.test(w)?w:void 0},L=T(c.headers["x-claude-code-session-id"]),_=T(c.headers["x-claude\
130
+ -code-agent-id"]),G=L!==void 0||_!==void 0?{...L!==void 0?{sessionId:L}:{},..._!==void 0?{agentId:_}:{}}:void 0;try{let I=[];for await(let g of c)I.push(g);let w=Buffer.concat(I);R=w,f=new URL(c.url??
131
+ "/",t);let Y=s!==null&&c.method==="POST"&&w.length>0,Ct=rt(g=>{let x=c.headers[g];return Array.isArray(x)?x.join(", "):x}),le=Y?ot("POST",f.pathname+f.search,w,Ct):"";if(Y)if(s.tieneEnVuelo(le)){let g=await s.
132
+ esperar(le);if(g){d.writeHead(g.status,g.headers);let x=u(f.pathname)??new $;for(let Me of g.chunks)d.write(Me),x.observar(Me);d.end();let Et=x.usage()??(f.pathname==="/v1/messages"?be(Buffer.concat(g.
133
+ chunks)):null);p(h,w,Et,!0,f.pathname,S,G);return}}else v=s.registrar(le);let Q={};for(let[g,x]of Object.entries(c.headers))g==="host"||g==="content-length"||x===void 0||(Q[g]=Array.isArray(x)?x.join(
134
+ ", "):x);let Oe=w;if(a!==null&&c.method==="POST"&&f.pathname==="/v1/messages"&&a.tiene("context_elision"))try{let g=tt(w,S);g!==null&&(Oe=g,Q["anthropic-beta"]=nt(Q["anthropic-beta"]));}catch{}let C;try{
135
+ C=await n(f,{method:c.method,headers:Q,body:["GET","HEAD"].includes(c.method??"")?void 0:Oe,redirect:"manual",signal:b.signal});}catch(g){v?.abortar(),d.headersSent||(d.writeHead(502,{"content-type":"a\
136
+ pplication/json"}),d.end(JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(g)}))),r(`[gateway] ${c.method} ${f.pathname} \u2192 upstream inalcanzable: ${String(g)}`);return}let ue=Object.
137
+ fromEntries(C.headers.entries());for(let g of rn)delete ue[g];d.writeHead(C.status,ue);let De=C.headers.get("content-type")??"",de=De.includes("text/event-stream")||De===""&&C.ok&&u(f.pathname)!==null;
138
+ y=de?u(f.pathname):null;let F=!de&&C.ok&&o!==void 0&&c.method==="POST"&&f.pathname==="/v1/messages"&&(C.headers.get("content-type")??"").includes("application/json")?[]:null,Le=0;if(C.body)for await(let g of C.
139
+ body){let x=Buffer.from(g);d.write(x),y?.observar(x),F&&(Le+=x.length,Le>ve?F=null:F.push(x)),v?.capturar(x);}if(d.end(),v?.terminar(C.status,ue),y&&C.ok){W=!0;let g=y instanceof $?y.elision():null;p(h,
140
+ w,y.usage(),!1,f.pathname,S,G,g);}else if(F){let g=Buffer.concat(F);p(h,w,be(g),!1,f.pathname,S,G,Be(g));}else de||r(`[gateway] ${c.method} ${f.pathname} \u2192 ${C.status}`);}catch(I){if(v?.abortar(),b.
141
+ signal.aborted){let w=!W&&y?y.usageFinal():null;if(w){r(`[gateway] ${c.method} ${f.pathname} \u2192 el cliente cerr\xF3 tras el usage final; se mide igual`);let Y=y instanceof $?y.elision():null;p(h,R,
142
+ w,false,f.pathname,S,G,Y);}else r(`[gateway] ${c.method} ${f.pathname} \u2192 el cliente abort\xF3; upstream cancelado`);}else if(r(`[gateway] ${c.method} ${f.pathname} \u2192 error sirviendo: ${String(I)}`),
143
+ !d.headersSent){d.writeHead(502,{"content-type":"application/json"}),d.end(JSON.stringify({error:"bvsala gateway: stream interrupted",detail:String(I)}));return}d.writableEnded||d.end();}});return m.on(
144
+ "upgrade",(c,d)=>{r(`[gateway] ${c.method} ${new URL(c.url??"/","http://x").pathname} \u2192 426 (WebSocket no soportado; el cliente cae a HTTP)`),d.once("error",()=>{}),d.once("finish",()=>d.destroy()),
145
+ d.end(`HTTP/1.1 426 Upgrade Required\r
144
146
  connection: close\r
145
147
  content-length: 0\r
146
148
  \r
147
- `);}),await new Promise((a,d)=>{m.once("error",d),m.listen(e.puerto??8402,"127.0.0.1",()=>a());}),{puerto:m.address().port,cerrar:async()=>{await o?.stopAndDrain(),await new Promise(a=>m.close(()=>a()));}}}var ye=["claude","codex","goose","qwen"];function at(e){return ye.includes(e??"")}function ct(e,t={}){let n=t.path??process.env.PATH??"",r=t.
148
- existe??existsSync,o=null;for(let s of n.split(";").filter(i=>i.length>0)){for(let i of [".exe",".com"]){let c=join(s,e+i);if(r(c))return {tipo:"exe",ruta:c}}if(o===null)for(let i of [".cmd",".bat"]){let c=join(s,e+
149
- i);if(r(c)){o=c;break}}}return o!==null?{tipo:"solo-shim",ruta:o}:{tipo:"no-encontrado"}}function lt(e={}){let n=(e.env??process.env).BVSALA_CODEX_AUTH?.toLowerCase();if(n==="api-key"||n==="apikey")return "\
149
+ `);}),await new Promise((c,d)=>{m.once("error",d),m.listen(e.puerto??8402,"127.0.0.1",()=>c());}),{puerto:m.address().port,cerrar:async()=>{await o?.stopAndDrain(),await new Promise(c=>m.close(()=>c()));}}}var ye=["claude","codex","goose","qwen"];function at(e){return ye.includes(e??"")}function ct(e,t={}){let n=t.path??process.env.PATH??"",r=t.
150
+ existe??existsSync,o=null;for(let s of n.split(";").filter(i=>i.length>0)){for(let i of [".exe",".com"]){let a=join(s,e+i);if(r(a))return {tipo:"exe",ruta:a}}if(o===null)for(let i of [".cmd",".bat"]){let a=join(s,e+
151
+ i);if(r(a)){o=a;break}}}return o!==null?{tipo:"solo-shim",ruta:o}:{tipo:"no-encontrado"}}function lt(e={}){let n=(e.env??process.env).BVSALA_CODEX_AUTH?.toLowerCase();if(n==="api-key"||n==="apikey")return "\
150
152
  api-key";if(n==="chatgpt")return "chatgpt";let r=null;try{r=e.leerAuth?e.leerAuth():null;}catch{r=null;}if(r===null)return "api-key";try{let o=JSON.parse(r);return typeof o.auth_mode=="string"?o.auth_mode.
151
153
  toLowerCase().replace(/[^a-z]/g,"")==="apikey"?"api-key":"chatgpt":o.tokens!==void 0&&o.tokens!==null?"chatgpt":"api-key"}catch{return "api-key"}}var an={"api-key":{upstream:"https://api.openai.com",prefijo:"\
152
154
  /v1"},chatgpt:{upstream:"https://chatgpt.com",prefijo:"/backend-api/codex"}};function ke(e,t,n,r={}){let o=`http://127.0.0.1:${t}`;switch(e){case "claude":return {bin:"claude",args:n,env:{ANTHROPIC_BASE_URL:o}};case "\
@@ -173,24 +175,24 @@ idar en mano contra el log del gateway].`,"{",' "customModels": ['," {",'
173
175
  receta:({baseV1:e})=>["mecanismo: UI \u2192 proveedor custom, Provider API \xABAnthropic Messages\xBB. Probar CON /v1 primero (linaje Cline) [inferido: validar en mano].",`Base URL: ${e}`]}];function mn(e,t){
174
176
  let n=e.split(delimiter).filter(o=>o.length>0),r=process.platform==="win32"?["",".exe",".cmd",".bat"]:[""];return o=>n.some(s=>r.some(i=>t(join(s,o+i))))}async function ut(e,t={}){if(!Number.isInteger(e)||e<1||
175
177
  e>65535)throw new RangeError(`[bvsala doctor] puerto inv\xE1lido: ${String(e)}`);let n=t.home??homedir(),r=t.existe??existsSync,o=t.enPath??mn(t.path??process.env.PATH??"",r),s=`http://127.0.0.1:${e}`,i=`${s}/v1`,
176
- c=[],u=[`bvsala doctor \u2014 gateway local en ${s}`,"Solo imprime: no escribe ninguna configuraci\xF3n. Detecci\xF3n por existencia (binarios en PATH y","archivos de configuraci\xF3n de usuario); nunca \
177
- se lee el contenido de ning\xFAn archivo.",""];for(let p of pn){let m=p.configs.map(b=>join(n,b)),l=p.binario!==void 0&&o(p.binario),a=m.filter(b=>r(b)),d=l||a.length>0;d&&c.push(p.id);let h;p.detectable?
178
- d?h=`detectado \u2014 ${[...l?[`binario \`${p.binario}\` en PATH`]:[],...a.map(v=>`config presente: ${v}`)].join("; ")}`:h="no detectado":h="sin detecci\xF3n autom\xE1tica (extensi\xF3n de editor)",u.
178
+ a=[],u=[`bvsala doctor \u2014 gateway local en ${s}`,"Solo imprime: no escribe ninguna configuraci\xF3n. Detecci\xF3n por existencia (binarios en PATH y","archivos de configuraci\xF3n de usuario); nunca \
179
+ se lee el contenido de ning\xFAn archivo.",""];for(let p of pn){let m=p.configs.map(b=>join(n,b)),l=p.binario!==void 0&&o(p.binario),c=m.filter(b=>r(b)),d=l||c.length>0;d&&a.push(p.id);let h;p.detectable?
180
+ d?h=`detectado \u2014 ${[...l?[`binario \`${p.binario}\` en PATH`]:[],...c.map(v=>`config presente: ${v}`)].join("; ")}`:h="no detectado":h="sin detecci\xF3n autom\xE1tica (extensi\xF3n de editor)",u.
179
181
  push(`\xBB ${p.id}: ${h}`),u.push(` ${p.nombre}`);for(let b of p.receta({base:s,baseV1:i,rutas:m}))u.push(` ${b}`);u.push(` ${dn}`),u.push("");}return u.push("Regla BYOK: las recetas del anillo Anth\
180
182
  ropic (Goose, Qwen Code, opencode, Crush, Droid, Cline, Kilo) son","para API key. Nunca enrutes por el gateway el OAuth de una suscripci\xF3n Claude Pro/Max desde un harness","de terceros \u2014 Anthropic \
181
183
  lo proh\xEDbe. Claude Code y Codex con suscripci\xF3n se enrutan y se miden, pero no","se monetizan: no hay factura por tokens.","Detalle por terminal: docs/terminales/<terminal>.md"),{texto:u.join(`
182
- `),detectados:c}}var dt="savings-mode";function pt(e,t){for(let n of e){let r=join(n,"skills",dt,"SKILL.md");if(t.existe(r))return r}return null}var mt="<!-- bvsala:savings-mode:inicio -->",ie="\
184
+ `),detectados:a}}var dt="savings-mode";function pt(e,t){for(let n of e){let r=join(n,"skills",dt,"SKILL.md");if(t.existe(r))return r}return null}var mt="<!-- bvsala:savings-mode:inicio -->",ie="\
183
185
  <!-- bvsala:savings-mode:fin -->";function fn(e){let t=e.replace(/^---\n[\s\S]*?\n---\n*/,"").trim();return `${mt}
184
186
  ${t}
185
187
 
186
188
  _Always on (AGENTS.md has no activation step). Delete this block to turn it off; \`bvsala skill --codex --forzar\` restores it._
187
189
  ${ie}`}function ft(e){let t=pt(e.origenes,e.fs);if(!t)return {ok:false,destino:"",mensaje:`[bvsala skill] no encuentro el SKILL.md empaquetado (ra\xEDces probadas: ${e.origenes.join(", ")})`};let n=fn(e.fs.
188
190
  leer(t)),r=e.proyecto?e.cwd:join(e.home,".codex"),o=join(r,"AGENTS.md");if(!e.fs.existe(o))return e.fs.crearDir(r),e.fs.escribir(o,n+`
189
- `),{ok:true,destino:o,mensaje:xe(o,"instalado")};let s=e.fs.leer(o),i=s.indexOf(mt),c=s.indexOf(ie);return i===-1||c===-1||c<i?(e.fs.escribir(o,s.trimEnd()+`
191
+ `),{ok:true,destino:o,mensaje:xe(o,"instalado")};let s=e.fs.leer(o),i=s.indexOf(mt),a=s.indexOf(ie);return i===-1||a===-1||a<i?(e.fs.escribir(o,s.trimEnd()+`
190
192
 
191
193
  `+n+`
192
- `),{ok:true,destino:o,mensaje:xe(o,"a\xF1adido al AGENTS.md existente")}):s.slice(i,c+ie.length)===n?{ok:true,destino:o,mensaje:`[bvsala skill] ya instalado y al d\xEDa: ${o}`}:e.forzar?(e.fs.escribir(o,s.
193
- slice(0,i)+n+s.slice(c+ie.length)),{ok:true,destino:o,mensaje:xe(o,"bloque actualizado")}):{ok:false,destino:o,mensaje:`[bvsala skill] el bloque savings-mode de ${o} tiene contenido DISTINTO \u2014 no lo piso; \
194
+ `),{ok:true,destino:o,mensaje:xe(o,"a\xF1adido al AGENTS.md existente")}):s.slice(i,a+ie.length)===n?{ok:true,destino:o,mensaje:`[bvsala skill] ya instalado y al d\xEDa: ${o}`}:e.forzar?(e.fs.escribir(o,s.
195
+ slice(0,i)+n+s.slice(a+ie.length)),{ok:true,destino:o,mensaje:xe(o,"bloque actualizado")}):{ok:false,destino:o,mensaje:`[bvsala skill] el bloque savings-mode de ${o} tiene contenido DISTINTO \u2014 no lo piso; \
194
196
  usa --forzar para actualizarlo`}}function xe(e,t){return `[bvsala skill] ${t}: ${e}
195
197
  [bvsala skill] AGENTS.md va SIEMPRE activo en Codex (no hay paso de activaci\xF3n); borra el bloque para apagarlo.
196
198
  [bvsala skill] para medir su efecto: trabaja unos d\xEDas con \xE9l y otros sin \xE9l bajo \`bvsala codex\`, y compara en el panel.`}function ht(e){let t=pt(e.origenes,e.fs);if(!t)return {ok:false,destino:"",
@@ -203,7 +205,7 @@ match(/\n/g)??[]).length;r+=i+Math.floor(Math.max(0,o.length-1)/2);}else /[A-Za-
203
205
  Math.ceil(t.length/4))}},we=new Se;var Te=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(t=we){this.fallbackId=t.id,this.estimators.set(t.id,t);}register(t){return this.estimators.set(t.id,t),this}mapModel(t,n){
204
206
  return this.modelToTokenizer.set(t,n),this}resolve(t){let n=t.tokenizer?this.estimators.get(t.tokenizer):void 0;if(n)return n;if(t.model){let r=this.modelToTokenizer.get(t.model);if(r){let o=this.estimators.
205
207
  get(r);if(o)return o}}return this.estimators.get(this.fallbackId)}count(t,n){return this.resolve(n).count(t)}};new Te;new TextEncoder;new TextDecoder;new TextDecoder;var An={"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}};
206
- me(An);var _e="0.4.12";var[,,k,...P]=process.argv;function Nn(){console.log(`bvsala ${_e} \u2014 BiVelio Savings Layer
208
+ me(An);var _e="0.4.14";var[,,k,...O]=process.argv;function Nn(){console.log(`bvsala ${_e} \u2014 BiVelio Savings Layer
207
209
 
208
210
  Comandos:
209
211
  gateway [--puerto N] Proxy local medidor (por defecto 8402) para terminales que
@@ -244,21 +246,21 @@ Entorno:
244
246
 
245
247
  El gateway nunca corta el tr\xE1fico: sin clave o con la ingesta ca\xEDda, reenv\xEDa y avisa.
246
248
  Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var J=at(k);k!=="gateway"&&k!=="doctor"&&k!=="skill"&&!J&&(Nn(),process.exit(k===void 0||k==="hel\
247
- p"||k==="--help"?0:1));if(k==="skill"){let e=dirname(fileURLToPath(import.meta.url)),t={origenes:[join(e,"..",".."),join(e,"..")],home:homedir(),cwd:process.cwd(),proyecto:P.includes("--proyecto"),forzar:P.includes("--forzar"),
248
- fs:{existe:existsSync,leer:r=>readFileSync(r,"utf8"),escribir:(r,o)=>writeFileSync(r,o),crearDir:r=>mkdirSync(r,{recursive:true})}},n=P.includes("--codex")?ft(t):ht(t);console.log(n.mensaje),process.exit(n.ok?0:1);}var Ae=P.indexOf("--puer\
249
- to"),U=J?0:Ae>=0?Number(P[Ae+1]):8402;(!Number.isInteger(U)||(k==="doctor"?U<1:U<0)||U>65535)&&(console.error(`[bvsala ${k}] puerto inv\xE1lido: ${P[Ae+1]}`),process.exit(1));if(k==="doctor"){P.includes(
250
- "--write")&&(console.error("[bvsala doctor] --write todav\xEDa no existe: el doctor solo imprime; copia el bloque a mano."),process.exit(1));let e=await ut(U);console.log(e.texto),process.exit(0);}var O=J?
251
- ke(k,0,P,{modoCodex:k==="codex"?lt({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0}):null,Pe=process.env.BIVELIO_SERVICE_KEY,jn=J&&process.env.BVSALA_GATEWAY_LOG!=="1",j=await st({puerto:U,
249
+ p"||k==="--help"?0:1));if(k==="skill"){let e=dirname(fileURLToPath(import.meta.url)),t={origenes:[join(e,"..",".."),join(e,"..")],home:homedir(),cwd:process.cwd(),proyecto:O.includes("--proyecto"),forzar:O.includes("--forzar"),
250
+ fs:{existe:existsSync,leer:r=>readFileSync(r,"utf8"),escribir:(r,o)=>writeFileSync(r,o),crearDir:r=>mkdirSync(r,{recursive:true})}},n=O.includes("--codex")?ft(t):ht(t);console.log(n.mensaje),process.exit(n.ok?0:1);}var Ae=O.indexOf("--puer\
251
+ to"),U=J?0:Ae>=0?Number(O[Ae+1]):8402;(!Number.isInteger(U)||(k==="doctor"?U<1:U<0)||U>65535)&&(console.error(`[bvsala ${k}] puerto inv\xE1lido: ${O[Ae+1]}`),process.exit(1));if(k==="doctor"){O.includes(
252
+ "--write")&&(console.error("[bvsala doctor] --write todav\xEDa no existe: el doctor solo imprime; copia el bloque a mano."),process.exit(1));let e=await ut(U);console.log(e.texto),process.exit(0);}var D=J?
253
+ ke(k,0,O,{modoCodex:k==="codex"?lt({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0}):null,Pe=process.env.BIVELIO_SERVICE_KEY,jn=J&&process.env.BVSALA_GATEWAY_LOG!=="1",j=await st({puerto:U,
252
254
  ...jn?{log:()=>{}}:{},...Pe?{serviceAccountKey:Pe}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.env.BVSALA_GATEWAY_UPSTREAM}:
253
- O?.upstream?{upstream:O.upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_GATEWAY_SHADOW==="1"?{sombra:true}:{},...process.env.BVSALA_GATEWAY_ELISION==="1"?
254
- {elision:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}}),zn=process.env.BVSALA_GATEWAY_UPSTREAM??O?.upstream??"https://api.anthropic.com";console.log(`[bvs\
255
- ala gateway] v${_e} escuchando en http://127.0.0.1:${j.puerto} \u2192 ${zn}`);console.log(`[bvsala gateway] medici\xF3n: ${Pe?"ACTIVA (clave de servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);
256
- process.env.BVSALA_GATEWAY_UPSTREAM&&O?.upstream&&process.env.BVSALA_GATEWAY_UPSTREAM!==O.upstream&&console.error(`[bvsala ${k}] AVISO: BVSALA_GATEWAY_UPSTREAM=${process.env.BVSALA_GATEWAY_UPSTREAM} p\
257
- isa el upstream de este terminal (${O.upstream}). Si no es lo que quieres, quita la variable.`);k==="codex"&&!Fe()&&console.error(`[bvsala codex] AVISO: este Node (${process.version}) no descomprime z\
258
- std y Codex manda el cuerpo en zstd: el tr\xE1fico pasa pero NO se mide. Usa Node \u2265 22.15.`);if(O&&J){let e=ke(k,j.puerto,P,{...O.upstream?{modoCodex:O.upstream.includes("chatgpt.com")?"chatgpt":
259
- "api-key"}:{}});e.nota&&console.log(`[bvsala ${k}] ${e.nota}`);let t=e.bin;if(process.platform==="win32"){let r=ct(e.bin);r.tipo==="exe"?t=r.ruta:r.tipo==="solo-shim"&&(console.error(`[bvsala ${k}] \`${e.
260
- bin}\` est\xE1 instalado como shim de npm (${r.ruta}) y Windows no puede lanzarlo de forma segura con argumentos (CVE-2024-27980). Inst\xE1lalo con su instalador nativo (deja un ${e.bin}.exe en el PAT\
261
- H) o usa la receta manual de docs/terminales/.`),await j.cerrar(),process.exit(127));}let n=spawn(t,e.args,{stdio:"inherit",env:{...process.env,...e.env}});n.on("error",r=>{console.error(`[bvsala ${k}] no\
262
- se pudo lanzar \`${e.bin}\`: ${String(r)}. \xBFEst\xE1 instalado y en el PATH?`),j.cerrar().then(()=>process.exit(127));}),n.on("exit",(r,o)=>{j.cerrar().then(()=>process.exit(r??(o?1:0)));});}else console.
263
- log(`[bvsala gateway] apunta tu terminal (o usa \`bvsala ${ye.join("|")}\`): ANTHROPIC_BASE_URL=http://127.0.0.1:${j.puerto} claude \xB7 bvsala doctor para el resto`);var Tt=async()=>{await j.cerrar(),
255
+ D?.upstream?{upstream:D.upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_GATEWAY_SHADOW==="1"?{sombra:true}:{},elision:process.env.BVSALA_GATEWAY_ELISION!==
256
+ "0",...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}}),zn=process.env.BVSALA_GATEWAY_UPSTREAM??D?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway]\
257
+ v${_e} escuchando en http://127.0.0.1:${j.puerto} \u2192 ${zn}`);console.log(`[bvsala gateway] medici\xF3n: ${Pe?"ACTIVA (clave de servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);process.
258
+ env.BVSALA_GATEWAY_UPSTREAM&&D?.upstream&&process.env.BVSALA_GATEWAY_UPSTREAM!==D.upstream&&console.error(`[bvsala ${k}] AVISO: BVSALA_GATEWAY_UPSTREAM=${process.env.BVSALA_GATEWAY_UPSTREAM} pisa el u\
259
+ pstream de este terminal (${D.upstream}). Si no es lo que quieres, quita la variable.`);k==="codex"&&!Fe()&&console.error(`[bvsala codex] AVISO: este Node (${process.version}) no descomprime zstd y Co\
260
+ dex manda el cuerpo en zstd: el tr\xE1fico pasa pero NO se mide. Usa Node \u2265 22.15.`);if(D&&J){let e=ke(k,j.puerto,O,{...D.upstream?{modoCodex:D.upstream.includes("chatgpt.com")?"chatgpt":"api-key"}:
261
+ {}});e.nota&&console.log(`[bvsala ${k}] ${e.nota}`);let t=e.bin;if(process.platform==="win32"){let r=ct(e.bin);r.tipo==="exe"?t=r.ruta:r.tipo==="solo-shim"&&(console.error(`[bvsala ${k}] \`${e.bin}\` e\
262
+ st\xE1 instalado como shim de npm (${r.ruta}) y Windows no puede lanzarlo de forma segura con argumentos (CVE-2024-27980). Inst\xE1lalo con su instalador nativo (deja un ${e.bin}.exe en el PATH) o usa\
263
+ la receta manual de docs/terminales/.`),await j.cerrar(),process.exit(127));}let n=spawn(t,e.args,{stdio:"inherit",env:{...process.env,...e.env}});n.on("error",r=>{console.error(`[bvsala ${k}] no se pudo\
264
+ lanzar \`${e.bin}\`: ${String(r)}. \xBFEst\xE1 instalado y en el PATH?`),j.cerrar().then(()=>process.exit(127));}),n.on("exit",(r,o)=>{j.cerrar().then(()=>process.exit(r??(o?1:0)));});}else console.log(
265
+ `[bvsala gateway] apunta tu terminal (o usa \`bvsala ${ye.join("|")}\`): ANTHROPIC_BASE_URL=http://127.0.0.1:${j.puerto} claude \xB7 bvsala doctor para el resto`);var Tt=async()=>{await j.cerrar(),
264
266
  process.exit(0);};process.on("SIGINT",()=>{Tt();});process.on("SIGTERM",()=>{Tt();});
package/dist/index.d.ts CHANGED
@@ -58,6 +58,19 @@ declare class SavingsLedger {
58
58
  sentInputLocal: number;
59
59
  /** Tokens que la capa quitó de ese prompt, en cuenta LOCAL. */
60
60
  removedInputLocal: number;
61
+ /**
62
+ * Tokens (LOCAL ruler) that tool_result ELISION removed from the sent prompt.
63
+ * They exist to be SUBTRACTED from `removedInputLocal`: live deletion really
64
+ * lowers `sentInputLocal`, so without this correction the elision saving would
65
+ * enter the reconstructed baseline and the commission, labelled "estimated".
66
+ * The doctrine is the opposite — "live count, shadow money": elision is a COUNT
67
+ * (ledger 2 `eliminatedInput`), its dollar is measured by the shadow lane out
68
+ * of commission, and `basis` stays "measured". Deliberately distinct from the
69
+ * tool-selector drop, which DOES count in the baseline (a declared tool that is
70
+ * not sent is an attributable saving; a superseded tool_result is redundancy
71
+ * not yet billed as verifiable against the invoice).
72
+ */
73
+ elididoLocal: number;
61
74
  /**
62
75
  * ¿El estimador que produjo las dos cifras anteriores cuenta con la regla del
63
76
  * proveedor? Lo fija el pipeline al resolver el tokenizador del modelo.
@@ -169,7 +182,7 @@ declare class SavingsLedger {
169
182
  * catalogue stays out of the way.
170
183
  */
171
184
  /** When these prices were last checked against the providers' public rates. */
172
- declare const PRECIOS_AL_DIA = "2026-08-24";
185
+ declare const PRECIOS_AL_DIA = "2026-09-06";
173
186
  /**
174
187
  * Key = the model id as the provider publishes it, without a vendor prefix.
175
188
  * Lookup tries the exact id, then the id without its provider prefix, then the
@@ -296,6 +309,27 @@ interface SavingsToggles {
296
309
  exactCache?: boolean;
297
310
  semanticCache?: boolean | "safe-only";
298
311
  compression?: boolean | "auto";
312
+ /**
313
+ * OPT-IN. Elide old, large tool_results from a multi-turn context before the
314
+ * request is sent, replacing each one's body with a short stable stub while
315
+ * keeping its `role:"tool"`, `toolCallId` and position. This is the v0
316
+ * (age × size) policy the shadow lane measures — productionized to actually
317
+ * shorten the outgoing prompt, on the generic tool_result segment nothing else
318
+ * in the pipeline touches.
319
+ *
320
+ * IT ONLY RUNS WHEN THE POLICY ALLOWS IT. Like the semantic cache, elision is
321
+ * disabled for write/destructive tools, freshness-sensitive, personalized or
322
+ * stochastic requests, and for `freshnessBucket: "day"` — dropping the output
323
+ * of a past action, or old context a fresh answer still needs, is exactly the
324
+ * degradation the layer must not cause.
325
+ *
326
+ * FIDELITY FLOOR. The newest tool_results are never elided (a keep-floor of at
327
+ * least 8, imported from the provider dial S4a); v0's own age≥2 rule is weaker
328
+ * than that floor, so the floor wins. What it removes is a COUNT on the panel
329
+ * (`eliminated_tokens`); its money value is estimated only in the shadow lane
330
+ * and never enters your commission — your invoice stays the source of truth.
331
+ */
332
+ elision?: boolean;
299
333
  serialization?: boolean | "auto";
300
334
  singleflight?: boolean;
301
335
  deterministicResolvers?: boolean;
@@ -1255,6 +1289,13 @@ interface Policy {
1255
1289
  allowSingleflight: boolean;
1256
1290
  /** May we run natural-language compression on the context? */
1257
1291
  allowCompression: boolean;
1292
+ /**
1293
+ * May the SDK elide old, large tool_results before resending? Computed, not
1294
+ * hardcoded like `allowCompression`: eliding drops whole tool outputs, so it
1295
+ * honors the same vetoes as the semantic cache — write tools, freshness,
1296
+ * personalization, stochastic sampling and `day`.
1297
+ */
1298
+ allowElision: boolean;
1258
1299
  /** Does the request demand current/live data? */
1259
1300
  requiresFreshData: boolean;
1260
1301
  /** Does the request have (or request) side effects? */
@@ -2362,6 +2403,22 @@ interface AnthropicOptions {
2362
2403
  * pointless: the extra breakpoints are rejected by the API, not ignored.
2363
2404
  */
2364
2405
  maxCacheBreakpoints?: number;
2406
+ /**
2407
+ * When true, requests that the pipeline decided to mark also carry the
2408
+ * request-level `cache_control` (Anthropic's automatic caching): the provider
2409
+ * places one extra breakpoint on the last cacheable block and moves it
2410
+ * forward as the conversation grows, so the history beyond the declared
2411
+ * stable prefix is cached incrementally too. It spends one of the four
2412
+ * breakpoint slots and never fires when the economic gate declined to mark.
2413
+ *
2414
+ * Off by default, and deliberately so: the moving breakpoint writes the
2415
+ * volatile tail of EVERY marked request at the 1.25x write premium. That
2416
+ * pays for itself on conversational or agentic traffic — the next turn reads
2417
+ * the whole history at 0.1x — and loses money on one-shot traffic where
2418
+ * nothing ever reads it back. It is also rejected by legacy Bedrock
2419
+ * deployments. Turn it on when your traffic is multi-turn.
2420
+ */
2421
+ automaticCache?: boolean;
2365
2422
  /**
2366
2423
  * Whether a request carrying BOTH the caller's tools and an output contract
2367
2424
  * may send `tool_choice: {type: "any"}`, which obliges the model to answer
@@ -3262,6 +3319,6 @@ declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"
3262
3319
  * constant to package.json so the two can never drift.
3263
3320
  */
3264
3321
 
3265
- declare const BV_SALA_VERSION = "0.4.12";
3322
+ declare const BV_SALA_VERSION = "0.4.14";
3266
3323
 
3267
3324
  export { type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactRowsDecodeOptions, type CompactRowsOptions, type CompactVerification, type CompressionEconomics, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, DEFAULT_MAX_STALENESS_SECONDS, type DeferredExecutionReport, type DeterministicResolver, EMBEDDED_PUBLIC_KEY, type EmbeddingFn, type EvidenceDoc, type ExactCache, ExactTokenizer, type ExecutionClass, ExecutionRouter, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type ModelCascadeMode, type ModelCascadeReport, type ModelCascadeSettings, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, PRECIOS_AL_DIA, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderUsage, QUALITY_STATUSES, type QualityStatus, type RequestContext, type Reranker, type ResolvedDeps, type ResolvedGate, type ResolvedLicense, ResolverRegistry, type ResolverResult, type RetrievalOptions, type RetrievalOutcome, type RetrievalPlan, type Retriever, type RevocationListBody, type RevocationListVerification, type RevocationPollEvent, RevocationPoller, type RevocationPollerOptions, RevocationSnapshot, type RouteDecision, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsReport, type SavingsRequest, type SavingsResult, type SavingsToggles, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, TraceCollector, type TraceEvent, UMBRAL_LOCAL, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, compresionFiel, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, embebedorLocal, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };