@bivelio/savings-layer 0.4.9 → 0.4.10

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.
@@ -1,32 +1,32 @@
1
1
  #!/usr/bin/env node
2
- import {spawn}from'child_process';import {existsSync,mkdirSync,writeFileSync,readFileSync}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 R from'zlib';var we=new Set;function T(e,t){we.has(e)||(we.add(e),console.warn(t));}var A=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 R from'zlib';var Ce=new Set;function T(e,t){Ce.has(e)||(Ce.add(e),console.warn(t));}var O=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??
6
- Math.ceil(this.maxBuffer/this.maxBatch)+4,o=0;for(let r=0;r<n&&this.buffer.length>0;r++){let s=this.buffer.length;if(await this.flush(),o=this.buffer.length>=s?o+1:0,o>=2)break}}async flush(){if(this.
6
+ Math.ceil(this.maxBuffer/this.maxBatch)+4,r=0;for(let o=0;o<n&&this.buffer.length>0;o++){let s=this.buffer.length;if(await this.flush(),r=this.buffer.length>=s?r+1:0,r>=2)break}}async flush(){if(this.
7
7
  inflight)return this.inflight;if(this.buffer.length!==0){this.inflight=this.hacerFlush();try{await this.inflight;}finally{this.inflight=null;}}}async hacerFlush(){let t=this.options.fetchImpl??globalThis.
8
- fetch;if(!t)return;let n=this.buffer.splice(0,this.maxBatch);try{let o=n.map(s=>this.toWire(s)),r=await t(this.options.url,{method:"POST",headers:{"content-type":"application/json",authorization:`Bear\
9
- er ${this.options.serviceAccountKey}`},body:JSON.stringify(o)});r.ok?this.fallosSeguidos=0:r.status>=500?(this.fallosSeguidos++,this.requeue(n),this.avisarDeFallo(`el servidor respondi\xF3 ${r.status}`)):
10
- r.status===401||r.status===403?(T(`metering-auth-${r.status}`,`[bv-sala] el env\xEDo de uso est\xE1 siendo rechazado con HTTP ${r.status} en ${this.options.url}. Revisa 'serviceAccountKey' (\xBFrotada? \xBF\
11
- revocada? \xBFde otra organizaci\xF3n?). Mientras no funcione NO se reporta uso: el panel dir\xE1 que no ve tr\xE1fico y los asientos facturables saldr\xE1n a cero.`),this.options.onAuthError?.(r.status)):
12
- (this.fallosSeguidos++,this.avisarDeFallo(r.status===404||r.status===405?`HTTP ${r.status}: la URL de env\xEDo parece equivocada`:`el servidor rechaz\xF3 el lote con HTTP ${r.status}`));}catch(o){this.
13
- fallosSeguidos++,this.requeue(n),this.avisarDeFallo(o instanceof Error?o.message:"error de red");}}avisarDeFallo(t){if(this.options.silenceWarnings)return;let n=this.fallosSeguidos;n<3||(n-3)%12!==0||console.
8
+ fetch;if(!t)return;let n=this.buffer.splice(0,this.maxBatch);try{let r=n.map(s=>this.toWire(s)),o=await t(this.options.url,{method:"POST",headers:{"content-type":"application/json",authorization:`Bear\
9
+ er ${this.options.serviceAccountKey}`},body:JSON.stringify(r)});o.ok?this.fallosSeguidos=0:o.status>=500?(this.fallosSeguidos++,this.requeue(n),this.avisarDeFallo(`el servidor respondi\xF3 ${o.status}`)):
10
+ o.status===401||o.status===403?(T(`metering-auth-${o.status}`,`[bv-sala] el env\xEDo de uso est\xE1 siendo rechazado con HTTP ${o.status} en ${this.options.url}. Revisa 'serviceAccountKey' (\xBFrotada? \xBF\
11
+ revocada? \xBFde otra organizaci\xF3n?). Mientras no funcione NO se reporta uso: el panel dir\xE1 que no ve tr\xE1fico y los asientos facturables saldr\xE1n a cero.`),this.options.onAuthError?.(o.status)):
12
+ (this.fallosSeguidos++,this.avisarDeFallo(o.status===404||o.status===405?`HTTP ${o.status}: la URL de env\xEDo parece equivocada`:`el servidor rechaz\xF3 el lote con HTTP ${o.status}`));}catch(r){this.
13
+ fallosSeguidos++,this.requeue(n),this.avisarDeFallo(r instanceof Error?r.message:"error de red");}}avisarDeFallo(t){if(this.options.silenceWarnings)return;let n=this.fallosSeguidos;n<3||(n-3)%12!==0||console.
14
14
  warn(`[bv-sala] el env\xEDo de uso lleva ${n} intentos seguidos fallando contra ${this.options.url} (${t}). Las peticiones SIGUEN funcionando, pero el uso no est\xE1 llegando: el panel dir\xE1 que no ve tr\xE1\
15
15
  fico y los asientos facturables saldr\xE1n a cero. Comprueba 'meteringUrl' \u2014 por defecto es \`\${licenseServerUrl}/api/metering/usage\`.`);}requeue(t){this.buffer=[...t,...this.buffer],this.buffer.
16
16
  length>this.maxBuffer&&(this.buffer.length=this.maxBuffer);}toWire(t){let n=t.report;return {orgId:this.options.orgId??"self",...this.seatId?{seatId:this.seatId}:{},...this.ci?{ci:true}:{},requestId:n.requestId,
17
17
  model:n.model,at:t.at,avoidedLlm:n.calls.avoidedLlm,actualLlm:n.calls.actualLlm,baselineInputTokens:n.tokens.baselineInput,actualInputTokens:n.tokens.actualInput,eliminatedInputTokens:n.tokens.eliminatedInput,
18
18
  compressedInputTokens:n.tokens.compressedInput,baselineOutputTokens:n.tokens.baselineOutput,actualOutputTokens:n.tokens.actualOutput,providerCachedInputTokens:n.tokens.providerCachedInput??0,cacheResult:n.
19
19
  cache.result,baselineCost:n.cost.baselineEstimate,actualCost:n.cost.actual,netSavings:n.cost.netSavings,...n.cost.basis?{basis:n.cost.basis}:{},...n.attribution?.sessionId?{sessionId:n.attribution.sessionId}:
20
- {},...n.attribution?.agentId?{agentId:n.attribution.agentId}:{}}}};function U(){return globalThis.process}function Ze(e){if(/^[0-9a-f]{12}$/.test(e)||/^\d{1,3}(\.\d{1,3}){3}$/.test(e)||/^ip-\d{1,3}(-\d{1,3}){3}$/i.test(e))return true;let t="[bcdfghjklmnpqrstvwxz2456789]";
21
- return new RegExp(`-${t}{8,10}-${t}{5}$`).test(e)}function et(e){return !!(e.AWS_LAMBDA_FUNCTION_NAME??e.LAMBDA_TASK_ROOT??e.VERCEL??e.VERCEL_ENV??e.K_SERVICE??e.FUNCTION_TARGET??e.FUNCTIONS_WORKER_RUNTIME??
22
- e.NETLIFY)}async function tt(){if(U()?.execPath)try{let t=await import(["node","os"].join(":")),n=typeof t.hostname=="function"?t.hostname():"";return n.length>0?n:void 0}catch{return}}async function nt(e,t=tt){
23
- if(e.KUBERNETES_SERVICE_HOST||et(e))return;let n=e.HOSTNAME??e.COMPUTERNAME??await t();if(!(!n||Ze(n)))return n}async function ot(){let e=U();if(!e||!e.env)return;let t=e.env,n=await nt(t),o=[e.platform,
24
- e.arch,e.execPath,n,t.USER??t.USERNAME??t.LOGNAME,t.HOME??t.USERPROFILE].filter(r=>typeof r=="string"&&r.length>0);return o.length?o.join("|"):void 0}var rt=["CI","CONTINUOUS_INTEGRATION","GITHUB_ACTI\
20
+ {},...n.attribution?.agentId?{agentId:n.attribution.agentId}:{}}}};function U(){return globalThis.process}function rt(e){if(/^[0-9a-f]{12}$/.test(e)||/^\d{1,3}(\.\d{1,3}){3}$/.test(e)||/^ip-\d{1,3}(-\d{1,3}){3}$/i.test(e))return true;let t="[bcdfghjklmnpqrstvwxz2456789]";
21
+ return new RegExp(`-${t}{8,10}-${t}{5}$`).test(e)}function st(e){return !!(e.AWS_LAMBDA_FUNCTION_NAME??e.LAMBDA_TASK_ROOT??e.VERCEL??e.VERCEL_ENV??e.K_SERVICE??e.FUNCTION_TARGET??e.FUNCTIONS_WORKER_RUNTIME??
22
+ e.NETLIFY)}async function it(){if(U()?.execPath)try{let t=await import(["node","os"].join(":")),n=typeof t.hostname=="function"?t.hostname():"";return n.length>0?n:void 0}catch{return}}async function at(e,t=it){
23
+ if(e.KUBERNETES_SERVICE_HOST||st(e))return;let n=e.HOSTNAME??e.COMPUTERNAME??await t();if(!(!n||rt(n)))return n}async function ct(){let e=U();if(!e||!e.env)return;let t=e.env,n=await at(t),r=[e.platform,
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 lt=["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 q(){let e=U()?.env;if(!e)
26
- return false;for(let t of rt){let n=e[t];if(n&&n!=="false"&&n!=="0")return true}return false}async function st(e,t){let n=globalThis.crypto,o=new TextEncoder;if(n?.subtle)try{let i=await n.subtle.importKey("raw",
27
- o.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await n.subtle.sign("HMAC",i,o.encode(t));return [...new Uint8Array(a)].map(l=>l.toString(16).padStart(2,"0")).join("")}catch{}let r=2166136261,s=`${e}\
28
- |${t}`;for(let i=0;i<s.length;i++)r^=s.charCodeAt(i),r=Math.imul(r,16777619);return (r>>>0).toString(16).padStart(8,"0")}async function K(e){let n=U()?.env?.BVSALA_SEAT_ID;if(n&&it.test(n))return n;let o=await ot();
29
- if(!o)return;let r=await st(e,o);if(r)return `m-${r.slice(0,32)}`}var it=/^[A-Za-z0-9._:/-]{1,128}$/;var te={"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},
26
+ return false;for(let t of lt){let n=e[t];if(n&&n!=="false"&&n!=="0")return true}return false}async function ut(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"]),a=await n.subtle.sign("HMAC",i,r.encode(t));return [...new Uint8Array(a)].map(l=>l.toString(16).padStart(2,"0")).join("")}catch{}let o=2166136261,s=`${e}\
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 K(e){let n=U()?.env?.BVSALA_SEAT_ID;if(n&&dt.test(n))return n;let r=await ct();
29
+ if(!r)return;let o=await ut(e,r);if(o)return `m-${o.slice(0,32)}`}var dt=/^[A-Za-z0-9._:/-]{1,128}$/;var ne={"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,
31
31
  outputPer1k:.06},"gpt-3.5-turbo":{inputPer1k:5e-4,outputPer1k:.0015},"gpt-5":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gpt-5-codex":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},
32
32
  "gpt-5-mini":{inputPer1k:25e-5,outputPer1k:.002,cachedInputPer1k:25e-6},"gpt-5-nano":{inputPer1k:5e-5,outputPer1k:4e-4,cachedInputPer1k:5e-6},"gpt-5.1-codex":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},
@@ -42,74 +42,74 @@ able-5":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:.001},"claude-sonnet-4"
42
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
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
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 Te(e,t,n){if(!n)return;let o=e[n];if(o)
46
- return o;let r=n.lastIndexOf("/"),s=r>=0?n.slice(r+1):n,i=e[s];if(i)return i;for(let a of t){if(!s.startsWith(a))continue;let l=s.charAt(a.length);if(l!==""&&l!=="-"&&l!=="."&&l!=="_")continue;let p=s.
47
- slice(a.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[a]}}function ne(e){return Object.keys(e).sort((t,n)=>n.length-t.length)}var at=ne(te);function $(e){return Te(te,at,e)}var j=class{resto="";uso=null;eventos={};observar(t){this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
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 Re(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 a of t){if(!s.startsWith(a))continue;let l=s.charAt(a.length);if(l!==""&&l!=="-"&&l!=="."&&l!=="_")continue;let p=s.
47
+ slice(a.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[a]}}function oe(e){return Object.keys(e).sort((t,n)=>n.length-t.length)}var pt=oe(ne);function j(e){return Re(ne,pt,e)}var N=class{resto="";uso=null;eventos={};observar(t){this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
48
48
 
49
- `))>=0;){let o=this.resto.slice(0,n);this.resto=this.resto.slice(n+2);let r=/^event: (.+)$/m.exec(o)?.[1];if(r&&(this.eventos[r]=(this.eventos[r]??0)+1),r!=="message_start"&&r!=="message_delta")continue;
50
- let s=/^data: (.+)$/m.exec(o)?.[1];if(s)try{let i=JSON.parse(s),a=i.usage??i.message?.usage;if(a){let l=a.cache_creation||this.uso?.cache_creation?{...this.uso?.cache_creation,...a.cache_creation}:void 0;
49
+ `))>=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),a=i.usage??i.message?.usage;if(a){let l=a.cache_creation||this.uso?.cache_creation?{...this.uso?.cache_creation,...a.cache_creation}:void 0;
51
51
  this.uso={...this.uso,...a,...l?{cache_creation:l}:{}};}}catch{}}}usage(){return this.uso}usageFinal(){return (this.eventos.message_delta??0)>0?this.uso:null}},H=class{resto="";uso=null;eventos={};observar(t){
52
52
  this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
53
53
 
54
- `))>=0;){let o=this.resto.slice(0,n);this.resto=this.resto.slice(n+2);let r=/^event: (.+)$/m.exec(o)?.[1];r&&(this.eventos[r]=(this.eventos[r]??0)+1);let s=/^data: (.+)$/m.exec(o)?.[1];if(s)try{let i=JSON.
55
- parse(s);if((r??i.type)!=="response.completed")continue;let a=i.response?.usage;a&&(this.uso=ct(a));}catch{}}}usage(){return this.uso}usageFinal(){return this.uso}};function ct(e){let t=Math.max(0,e.input_tokens??
56
- 0),n=Math.min(Math.max(0,e.input_tokens_details?.cached_tokens??0),t),o=Math.min(Math.max(0,e.input_tokens_details?.cache_write_tokens??0),t-n);return {input_tokens:t-n-o,output_tokens:Math.max(0,e.output_tokens??
57
- 0),cache_read_input_tokens:n,cache_creation_input_tokens:o}}var Ce=1.25,lt=2;function Re(e,t){let n=$(e);if(!n)return null;let o=t.input_tokens??0,r=t.output_tokens??0,s=t.cache_read_input_tokens??0,i=t.
54
+ `))>=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.
55
+ parse(s);if((o??i.type)!=="response.completed")continue;let a=i.response?.usage;a&&(this.uso=mt(a));}catch{}}}usage(){return this.uso}usageFinal(){return this.uso}};function mt(e){let t=Math.max(0,e.input_tokens??
56
+ 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??
57
+ 0),cache_read_input_tokens:n,cache_creation_input_tokens:r}}var Ee=1.25,ft=2;function Ie(e,t){let n=j(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.
58
58
  cache_creation,a=t.cache_creation_input_tokens??(i?.ephemeral_5m_input_tokens??0)+(i?.ephemeral_1h_input_tokens??0),l=Math.min(i?.ephemeral_1h_input_tokens??0,a),p=n.cachedInputPer1k??n.inputPer1k;return {
59
- costeUsd:o/1e3*n.inputPer1k+s/1e3*p+(a-l)/1e3*n.inputPer1k*dt(e)+l/1e3*n.inputPer1k*lt+r/1e3*n.outputPer1k,precio:n,tokens:{entrada:o,salida:r,cacheLeida:s,cacheEscrita:a,cacheEscrita1h:l}}}function Ee(e,t){
60
- try{let n=e,o={maxOutputLength:ut};switch((t??"").trim().toLowerCase()){case "gzip":case "x-gzip":n=R.gunzipSync(e,o);break;case "deflate":n=R.inflateSync(e,o);break;case "br":n=R.brotliDecompressSync(e,o);
61
- break;case "zstd":{let s=R.zstdDecompressSync;if(typeof s!="function")return "";n=s(e,o);break}default:break}let r=JSON.parse(n.toString("utf8")).model;return typeof r=="string"?r:""}catch{return ""}}var ut=5*
62
- 1024*1024;function Ie(){return typeof R.zstdDecompressSync=="function"}function dt(e){let t=e.toLowerCase();return t.startsWith("gpt-")||t.startsWith("o1")||t.startsWith("o3")||t.startsWith("o4")?/^gpt-5\.6/.
63
- test(t)?Ce:1:t.startsWith("gemini")?1:Ce}function oe(e){try{return JSON.parse(typeof e=="string"?e:e.toString("utf8")).usage??null}catch{return null}}function Pe(e,t,n,o){return Ae(e,t,n,false,o)}function _e(e,t,n,o){
64
- return Ae(e,t,n,true,o)}function Ae(e,t,n,o,r){let s=n.tokens.entrada+n.tokens.cacheLeida+n.tokens.cacheEscrita;return {requestId:e,model:t,...r&&(r.sessionId||r.agentId)?{attribution:r}:{},calls:o?{avoidedLlm:1,
65
- actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{baselineInput:s,actualInput:o?0:s,eliminatedInput:0,compressedInput:0,baselineOutput:n.tokens.salida,actualOutput:o?0:n.tokens.salida,providerCachedInput:o?
66
- 0:n.tokens.cacheLeida},cache:{result:o?"singleflight":"miss"},cost:o?{baselineEstimate:n.costeUsd,actual:0,netSavings:1,basis:"estimated"}:{baselineEstimate:n.costeUsd,actual:n.costeUsd,netSavings:0,basis:"\
67
- measured"}}}var re=5*1024*1024,mt=["authorization","x-api-key","anthropic-version","anthropic-beta"];function Oe(e,t,n,o=""){return createHash("sha256").update(e).update(`
59
+ costeUsd:r/1e3*n.inputPer1k+s/1e3*p+(a-l)/1e3*n.inputPer1k*gt(e)+l/1e3*n.inputPer1k*ft+o/1e3*n.outputPer1k,precio:n,tokens:{entrada:r,salida:o,cacheLeida:s,cacheEscrita:a,cacheEscrita1h:l}}}function Pe(e,t){
60
+ try{let n=e,r={maxOutputLength:ht};switch((t??"").trim().toLowerCase()){case "gzip":case "x-gzip":n=R.gunzipSync(e,r);break;case "deflate":n=R.inflateSync(e,r);break;case "br":n=R.brotliDecompressSync(e,r);
61
+ break;case "zstd":{let s=R.zstdDecompressSync;if(typeof s!="function")return "";n=s(e,r);break}default:break}let o=JSON.parse(n.toString("utf8")).model;return typeof o=="string"?o:""}catch{return ""}}var ht=5*
62
+ 1024*1024;function Ae(){return typeof R.zstdDecompressSync=="function"}function gt(e){let t=e.toLowerCase();return t.startsWith("gpt-")||t.startsWith("o1")||t.startsWith("o3")||t.startsWith("o4")?/^gpt-5\.6/.
63
+ test(t)?Ee:1:t.startsWith("gemini")?1:Ee}function re(e){try{return JSON.parse(typeof e=="string"?e:e.toString("utf8")).usage??null}catch{return null}}function _e(e,t,n,r){return De(e,t,n,false,r)}function Oe(e,t,n,r){
64
+ return De(e,t,n,true,r)}function De(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)?{attribution:o}:{},calls:r?{avoidedLlm:1,
65
+ actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{baselineInput:s,actualInput:r?0:s,eliminatedInput:0,compressedInput:0,baselineOutput:n.tokens.salida,actualOutput:r?0:n.tokens.salida,providerCachedInput:r?
66
+ 0:n.tokens.cacheLeida},cache:{result:r?"singleflight":"miss"},cost:r?{baselineEstimate:n.costeUsd,actual:0,netSavings:1,basis:"estimated"}:{baselineEstimate:n.costeUsd,actual:n.costeUsd,netSavings:0,basis:"\
67
+ measured"}}}var se=5*1024*1024,vt=["authorization","x-api-key","anthropic-version","anthropic-beta"];function Le(e,t,n,r=""){return createHash("sha256").update(e).update(`
68
68
  `).update(t).update(`
69
- `).update(o).update(`
70
- `).update(n).digest("hex")}function De(e){return mt.map(t=>`${t}=${e(t)??""}`).join(`
71
- `)}var J=class{enVuelo=new Map;primarias=0;gemelas=0;registrar(t){if(this.enVuelo.has(t))return null;this.primarias+=1;let n,o=new Promise(a=>n=a);this.enVuelo.set(t,{promesa:o,resolver:n});let r=[],s=0,
72
- i=false;return {capturar:a=>{if(!i){if(s+=a.length,s>re){i=true,r.length=0;return}r.push(a);}},terminar:(a,l)=>{let p=!i&&a>=200&&a<300;this.enVuelo.get(t)?.resolver(p?{status:a,headers:l,chunks:r}:null),this.
73
- 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 o=await n.promesa;return o&&(this.gemelas+=
74
- 1),o}tieneEnVuelo(t){return this.enVuelo.has(t)}};var gt=["content-encoding","content-length","transfer-encoding"];async function Le(e={}){let t=(e.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=e.fetchImpl??fetch,o=e.log??(c=>console.log(
75
- c)),r;if(e.serviceAccountKey){r=new A({url:e.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},onAuthError:u=>T(
69
+ `).update(r).update(`
70
+ `).update(n).digest("hex")}function Me(e){return vt.map(t=>`${t}=${e(t)??""}`).join(`
71
+ `)}var J=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=[],s=0,
72
+ i=false;return {capturar:a=>{if(!i){if(s+=a.length,s>se){i=true,o.length=0;return}o.push(a);}},terminar:(a,l)=>{let p=!i&&a>=200&&a<300;this.enVuelo.get(t)?.resolver(p?{status:a,headers:l,chunks:o}:null),this.
73
+ 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+=
74
+ 1),r}tieneEnVuelo(t){return this.enVuelo.has(t)}};var xt=["content-encoding","content-length","transfer-encoding"];async function $e(e={}){let t=(e.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=e.fetchImpl??fetch,r=e.log??(c=>console.log(
75
+ c)),o;if(e.serviceAccountKey){o=new O({url:e.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},onAuthError:u=>T(
76
76
  "gateway-ingest-auth",`[bvsala gateway] la ingesta de uso rechaza la credencial (${u}). El tr\xE1fico sigue fluyendo (avisar, nunca romper), pero este uso no est\xE1 llegando al panel. Revisa BIVELIO_SERVIC\
77
- E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=q();e.seatId?r.setSeat(e.seatId,c):K(e.serviceAccountKey).then(u=>r?.setSeat(u,c)).catch(()=>{}),r.start();}else T("gateway-sin-\
77
+ E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=q();e.seatId?o.setSeat(e.seatId,c):K(e.serviceAccountKey).then(u=>o?.setSeat(u,c)).catch(()=>{}),o.start();}else T("gateway-sin-\
78
78
  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.
79
- coalescer?new J:null,i=c=>c==="/v1/messages"?new j:c.endsWith("/responses")?new H:null,a=(c,u,f,b,m,d,y)=>{if(!r||!f)return;let S=Ee(u,d);if(!S){T(`gateway-sin-modelo:${m}`,`[bvsala gateway] no se pud\
80
- o leer el modelo del cuerpo de ${m} (content-encoding: ${d??"ninguno"}); su uso se reenv\xEDa pero no se mide.`);return}let k=Re(S,f);k?(r.record(b?_e(c,S,k,y):Pe(c,S,k,y)),o(`[gateway] POST ${m} ${b?
79
+ coalescer?new J:null,i=c=>c==="/v1/messages"?new N:c.endsWith("/responses")?new H:null,a=(c,u,f,b,m,d,y)=>{if(!o||!f)return;let S=Pe(u,d);if(!S){T(`gateway-sin-modelo:${m}`,`[bvsala gateway] no se pud\
80
+ o leer el modelo del cuerpo de ${m} (content-encoding: ${d??"ninguno"}); su uso se reenv\xEDa pero no se mide.`);return}let k=Ie(S,f);k?(o.record(b?Oe(c,S,k,y):_e(c,S,k,y)),r(`[gateway] POST ${m} ${b?
81
81
  "\xB7 COALESCIDA (llamada evitada)":""} \xB7 ${S} \xB7 $${k.costeUsd.toFixed(6)} (in ${k.tokens.entrada}, cache\u2191${k.tokens.cacheEscrita}${k.tokens.cacheEscrita1h?` (1h ${k.tokens.cacheEscrita1h})`:
82
82
  ""} cache\u2193${k.tokens.cacheLeida}, out ${k.tokens.salida})`)):T(`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.`);},
83
- l=createServer(async(c,u)=>{let f=randomUUID(),b=new AbortController;u.on("close",()=>{u.writableEnded||b.abort();});let m=null,d=new URL(c.url??"/",t),y=null,S=Buffer.alloc(0),k=false,L=c.headers["content-encoding"],V=Array.
84
- isArray(L)?L[0]:L,be=C=>{let x=Array.isArray(C)?C[0]:C;return x!==void 0&&/^[A-Za-z0-9._:/-]{1,128}$/.test(x)?x:void 0},Y=be(c.headers["x-claude-code-session-id"]),Q=be(c.headers["x-claude-code-agent-\
85
- id"]),F=Y!==void 0||Q!==void 0?{...Y!==void 0?{sessionId:Y}:{},...Q!==void 0?{agentId:Q}:{}}:void 0;try{let C=[];for await(let h of c)C.push(h);let x=Buffer.concat(C);S=x,d=new URL(c.url??"/",t);let ve=s!==
86
- null&&c.method==="POST"&&x.length>0,Qe=De(h=>{let v=c.headers[h];return Array.isArray(v)?v.join(", "):v}),X=ve?Oe("POST",d.pathname+d.search,x,Qe):"";if(ve)if(s.tieneEnVuelo(X)){let h=await s.esperar(
87
- X);if(h){u.writeHead(h.status,h.headers);let v=i(d.pathname)??new j;for(let Se of h.chunks)u.write(Se),v.observar(Se);u.end();let Xe=v.usage()??(d.pathname==="/v1/messages"?oe(Buffer.concat(h.chunks)):
88
- null);a(f,x,Xe,!0,d.pathname,V,F);return}}else m=s.registrar(X);let ye={};for(let[h,v]of Object.entries(c.headers))h==="host"||h==="content-length"||v===void 0||(ye[h]=Array.isArray(v)?v.join(", "):v);
89
- let w;try{w=await n(d,{method:c.method,headers:ye,body:["GET","HEAD"].includes(c.method??"")?void 0:x,redirect:"manual",signal:b.signal});}catch(h){m?.abortar(),u.headersSent||(u.writeHead(502,{"conten\
90
- t-type":"application/json"}),u.end(JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(h)}))),o(`[gateway] ${c.method} ${d.pathname} \u2192 upstream inalcanzable: ${String(h)}`);
91
- return}let Z=Object.fromEntries(w.headers.entries());for(let h of gt)delete Z[h];u.writeHead(w.status,Z);let ke=w.headers.get("content-type")??"",ee=ke.includes("text/event-stream")||ke===""&&w.ok&&i(
92
- d.pathname)!==null;y=ee?i(d.pathname):null;let M=!ee&&w.ok&&r!==void 0&&c.method==="POST"&&d.pathname==="/v1/messages"&&(w.headers.get("content-type")??"").includes("application/json")?[]:null,xe=0;if(w.
93
- body)for await(let h of w.body){let v=Buffer.from(h);u.write(v),y?.observar(v),M&&(xe+=v.length,xe>re?M=null:M.push(v)),m?.capturar(v);}u.end(),m?.terminar(w.status,Z),y&&w.ok?(k=!0,a(f,x,y.usage(),!1,
94
- d.pathname,V,F)):M?a(f,x,oe(Buffer.concat(M)),!1,d.pathname,V,F):ee||o(`[gateway] ${c.method} ${d.pathname} \u2192 ${w.status}`);}catch(C){if(m?.abortar(),b.signal.aborted){let x=!k&&y?y.usageFinal():null;
95
- x?(o(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente cerr\xF3 tras el usage final; se mide igual`),a(f,S,x,false,d.pathname,V,F)):o(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente abort\xF3; upstre\
96
- am cancelado`);}else if(o(`[gateway] ${c.method} ${d.pathname} \u2192 error sirviendo: ${String(C)}`),!u.headersSent){u.writeHead(502,{"content-type":"application/json"}),u.end(JSON.stringify({error:"b\
97
- vsala gateway: stream interrupted",detail:String(C)}));return}u.writableEnded||u.end();}});return l.on("upgrade",(c,u)=>{o(`[gateway] ${c.method} ${new URL(c.url??"/","http://x").pathname} \u2192 426 (WebSo\
98
- cket no soportado; el cliente cae a HTTP)`),u.once("error",()=>{}),u.once("finish",()=>u.destroy()),u.end(`HTTP/1.1 426 Upgrade Required\r
83
+ l=createServer(async(c,u)=>{let f=randomUUID(),b=new AbortController;u.on("close",()=>{u.writableEnded||b.abort();});let m=null,d=new URL(c.url??"/",t),y=null,S=Buffer.alloc(0),k=false,M=c.headers["content-encoding"],V=Array.
84
+ isArray(M)?M[0]:M,ye=C=>{let x=Array.isArray(C)?C[0]:C;return x!==void 0&&/^[A-Za-z0-9._:/-]{1,128}$/.test(x)?x:void 0},Q=ye(c.headers["x-claude-code-session-id"]),X=ye(c.headers["x-claude-code-agent-\
85
+ id"]),F=Q!==void 0||X!==void 0?{...Q!==void 0?{sessionId:Q}:{},...X!==void 0?{agentId:X}:{}}:void 0;try{let C=[];for await(let h of c)C.push(h);let x=Buffer.concat(C);S=x,d=new URL(c.url??"/",t);let ke=s!==
86
+ null&&c.method==="POST"&&x.length>0,nt=Me(h=>{let v=c.headers[h];return Array.isArray(v)?v.join(", "):v}),Z=ke?Le("POST",d.pathname+d.search,x,nt):"";if(ke)if(s.tieneEnVuelo(Z)){let h=await s.esperar(
87
+ Z);if(h){u.writeHead(h.status,h.headers);let v=i(d.pathname)??new N;for(let Te of h.chunks)u.write(Te),v.observar(Te);u.end();let ot=v.usage()??(d.pathname==="/v1/messages"?re(Buffer.concat(h.chunks)):
88
+ null);a(f,x,ot,!0,d.pathname,V,F);return}}else m=s.registrar(Z);let xe={};for(let[h,v]of Object.entries(c.headers))h==="host"||h==="content-length"||v===void 0||(xe[h]=Array.isArray(v)?v.join(", "):v);
89
+ let w;try{w=await n(d,{method:c.method,headers:xe,body:["GET","HEAD"].includes(c.method??"")?void 0:x,redirect:"manual",signal:b.signal});}catch(h){m?.abortar(),u.headersSent||(u.writeHead(502,{"conten\
90
+ t-type":"application/json"}),u.end(JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(h)}))),r(`[gateway] ${c.method} ${d.pathname} \u2192 upstream inalcanzable: ${String(h)}`);
91
+ return}let ee=Object.fromEntries(w.headers.entries());for(let h of xt)delete ee[h];u.writeHead(w.status,ee);let Se=w.headers.get("content-type")??"",te=Se.includes("text/event-stream")||Se===""&&w.ok&&
92
+ i(d.pathname)!==null;y=te?i(d.pathname):null;let $=!te&&w.ok&&o!==void 0&&c.method==="POST"&&d.pathname==="/v1/messages"&&(w.headers.get("content-type")??"").includes("application/json")?[]:null,we=0;
93
+ if(w.body)for await(let h of w.body){let v=Buffer.from(h);u.write(v),y?.observar(v),$&&(we+=v.length,we>se?$=null:$.push(v)),m?.capturar(v);}u.end(),m?.terminar(w.status,ee),y&&w.ok?(k=!0,a(f,x,y.usage(),
94
+ !1,d.pathname,V,F)):$?a(f,x,re(Buffer.concat($)),!1,d.pathname,V,F):te||r(`[gateway] ${c.method} ${d.pathname} \u2192 ${w.status}`);}catch(C){if(m?.abortar(),b.signal.aborted){let x=!k&&y?y.usageFinal():
95
+ null;x?(r(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente cerr\xF3 tras el usage final; se mide igual`),a(f,S,x,false,d.pathname,V,F)):r(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente abort\xF3; u\
96
+ pstream cancelado`);}else if(r(`[gateway] ${c.method} ${d.pathname} \u2192 error sirviendo: ${String(C)}`),!u.headersSent){u.writeHead(502,{"content-type":"application/json"}),u.end(JSON.stringify({error:"\
97
+ bvsala gateway: stream interrupted",detail:String(C)}));return}u.writableEnded||u.end();}});return l.on("upgrade",(c,u)=>{r(`[gateway] ${c.method} ${new URL(c.url??"/","http://x").pathname} \u2192 426 (WebS\
98
+ ocket no soportado; el cliente cae a HTTP)`),u.once("error",()=>{}),u.once("finish",()=>u.destroy()),u.end(`HTTP/1.1 426 Upgrade Required\r
99
99
  connection: close\r
100
100
  content-length: 0\r
101
101
  \r
102
- `);}),await new Promise((c,u)=>{l.once("error",u),l.listen(e.puerto??8402,"127.0.0.1",()=>c());}),{puerto:l.address().port,cerrar:async()=>{await r?.stopAndDrain(),await new Promise(c=>l.close(()=>c()));}}}var se=["claude","codex","goose","qwen"];function $e(e){return se.includes(e??"")}function je(e,t={}){let n=t.path??process.env.PATH??"",o=t.
103
- existe??existsSync,r=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(o(a))return {tipo:"exe",ruta:a}}if(r===null)for(let i of [".cmd",".bat"]){let a=join(s,e+
104
- i);if(o(a)){r=a;break}}}return r!==null?{tipo:"solo-shim",ruta:r}:{tipo:"no-encontrado"}}function Ne(e={}){let n=(e.env??process.env).BVSALA_CODEX_AUTH?.toLowerCase();if(n==="api-key"||n==="apikey")return "\
105
- api-key";if(n==="chatgpt")return "chatgpt";let o=null;try{o=e.leerAuth?e.leerAuth():null;}catch{o=null;}if(o===null)return "api-key";try{let r=JSON.parse(o);return typeof r.auth_mode=="string"?r.auth_mode.
106
- toLowerCase().replace(/[^a-z]/g,"")==="apikey"?"api-key":"chatgpt":r.tokens!==void 0&&r.tokens!==null?"chatgpt":"api-key"}catch{return "api-key"}}var vt={"api-key":{upstream:"https://api.openai.com",prefijo:"\
107
- /v1"},chatgpt:{upstream:"https://chatgpt.com",prefijo:"/backend-api/codex"}};function ie(e,t,n,o={}){let r=`http://127.0.0.1:${t}`;switch(e){case "claude":return {bin:"claude",args:n,env:{ANTHROPIC_BASE_URL:r}};case "\
108
- goose":return {bin:"goose",args:n,env:{ANTHROPIC_HOST:r}};case "qwen":return {bin:"qwen",args:n,env:{ANTHROPIC_BASE_URL:r},nota:'Qwen Code solo enruta por aqu\xED con `security.auth.selectedType: "anthropic\
109
- "` en ~/.qwen/settings.json (ver `bvsala doctor`).'};case "codex":{let s=o.modoCodex??"api-key",i=vt[s];return {bin:"codex",args:["-c",`openai_base_url="${r}${i.prefijo}"`,...n],env:{},upstream:i.upstream,
102
+ `);}),await new Promise((c,u)=>{l.once("error",u),l.listen(e.puerto??8402,"127.0.0.1",()=>c());}),{puerto:l.address().port,cerrar:async()=>{await o?.stopAndDrain(),await new Promise(c=>l.close(()=>c()));}}}var ie=["claude","codex","goose","qwen"];function Ne(e){return ie.includes(e??"")}function ze(e,t={}){let n=t.path??process.env.PATH??"",r=t.
103
+ 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+
104
+ i);if(r(a)){o=a;break}}}return o!==null?{tipo:"solo-shim",ruta:o}:{tipo:"no-encontrado"}}function Be(e={}){let n=(e.env??process.env).BVSALA_CODEX_AUTH?.toLowerCase();if(n==="api-key"||n==="apikey")return "\
105
+ 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.
106
+ toLowerCase().replace(/[^a-z]/g,"")==="apikey"?"api-key":"chatgpt":o.tokens!==void 0&&o.tokens!==null?"chatgpt":"api-key"}catch{return "api-key"}}var wt={"api-key":{upstream:"https://api.openai.com",prefijo:"\
107
+ /v1"},chatgpt:{upstream:"https://chatgpt.com",prefijo:"/backend-api/codex"}};function ae(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 "\
108
+ goose":return {bin:"goose",args:n,env:{ANTHROPIC_HOST:o}};case "qwen":return {bin:"qwen",args:n,env:{ANTHROPIC_BASE_URL:o},nota:'Qwen Code solo enruta por aqu\xED con `security.auth.selectedType: "anthropic\
109
+ "` en ~/.qwen/settings.json (ver `bvsala doctor`).'};case "codex":{let s=r.modoCodex??"api-key",i=wt[s];return {bin:"codex",args:["-c",`openai_base_url="${o}${i.prefijo}"`,...n],env:{},upstream:i.upstream,
110
110
  nota:s==="chatgpt"?"Auth ChatGPT detectada: el gateway enruta a chatgpt.com/backend-api/codex. El tr\xE1fico de suscripci\xF3n se mide (equivalente a precio de lista) pero no se monetiza: no hay factura por\
111
- tokens.":"Auth por API key: el gateway enruta a api.openai.com/v1."}}}}var St="comprobar: en el log del gateway debe aparecer `[gateway] POST /v1/messages \u2026`; si ve\
112
- s `/v1/v1/messages`, sobra un /v1; si ves `POST /messages` a secas, falta uno.",wt=[{id:"claude",nombre:"Claude Code",binario:"claude",configs:[],detectable:true,receta:({base:e})=>["usa `bvsala claude`\
111
+ tokens.":"Auth por API key: el gateway enruta a api.openai.com/v1."}}}}var Et="comprobar: en el log del gateway debe aparecer `[gateway] POST /v1/messages \u2026`; si ve\
112
+ s `/v1/v1/messages`, sobra un /v1; si ves `POST /messages` a secas, falta uno.",It=[{id:"claude",nombre:"Claude Code",binario:"claude",configs:[],detectable:true,receta:({base:e})=>["usa `bvsala claude`\
113
113
  \u2014 arranca el gateway en un puerto ef\xEDmero, lanza `claude` apunt\xE1ndole y lo apaga al salir.",`a mano: ANTHROPIC_BASE_URL=${e} claude (sin /v1: el cliente a\xF1ade /v1/messages)`]},{id:"co\
114
114
  dex",nombre:"Codex CLI",binario:"codex",configs:[join(".codex","config.toml"),join(".codex","auth.json")],detectable:true,receta:({base:e,rutas:t})=>["usa `bvsala codex` \u2014 el prefijo del base URL depende del \
115
115
  modo de auth y lo resuelve el envoltorio:",` API key \u2192 openai_base_url=${e}/v1`,` suscripci\xF3n ChatGPT \u2192 openai_base_url=${e}/backend-api/codex (tiene que terminar exactament\
@@ -125,26 +125,40 @@ Droid (Factory)",binario:"droid",configs:[join(".factory","settings.json")],dete
125
125
  idar en mano contra el log del gateway].`,"{",' "customModels": ['," {",' "model": "<id de modelo Anthropic>",',' "displayName": "<nombre visible>",',' "provider": "anthropic",',` \
126
126
  "baseUrl": "${e}",`,' "apiKey": "<tu API key de Anthropic>"'," }"," ]","}"]},{id:"cline",nombre:"Cline",configs:[],detectable:false,receta:({baseV1:e})=>["mecanismo: UI \u2192 proveedor Anthropic\
127
127
  \u2192 marca \xABUse custom base URL\xBB. CON /v1 (Cline fija https://api.anthropic.com/v1 y solo normaliza la URL oficial exacta).",`Base URL: ${e}`]},{id:"kilo",nombre:"Kilo Code",configs:[],detectable:false,
128
- 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 Tt(e,t){
129
- let n=e.split(delimiter).filter(r=>r.length>0),o=process.platform==="win32"?["",".exe",".cmd",".bat"]:[""];return r=>n.some(s=>o.some(i=>t(join(s,r+i))))}async function ze(e,t={}){if(!Number.isInteger(e)||e<1||
130
- e>65535)throw new RangeError(`[bvsala doctor] puerto inv\xE1lido: ${String(e)}`);let n=t.home??homedir(),o=t.existe??existsSync,r=t.enPath??Tt(t.path??process.env.PATH??"",o),s=`http://127.0.0.1:${e}`,i=`${s}/v1`,
128
+ 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 Pt(e,t){
129
+ 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 Ve(e,t={}){if(!Number.isInteger(e)||e<1||
130
+ e>65535)throw new RangeError(`[bvsala doctor] puerto inv\xE1lido: ${String(e)}`);let n=t.home??homedir(),r=t.existe??existsSync,o=t.enPath??Pt(t.path??process.env.PATH??"",r),s=`http://127.0.0.1:${e}`,i=`${s}/v1`,
131
131
  a=[],l=[`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 \
132
- se lee el contenido de ning\xFAn archivo.",""];for(let p of wt){let c=p.configs.map(d=>join(n,d)),u=p.binario!==void 0&&r(p.binario),f=c.filter(d=>o(d)),b=u||f.length>0;b&&a.push(p.id);let m;p.detectable?
132
+ se lee el contenido de ning\xFAn archivo.",""];for(let p of It){let c=p.configs.map(d=>join(n,d)),u=p.binario!==void 0&&o(p.binario),f=c.filter(d=>r(d)),b=u||f.length>0;b&&a.push(p.id);let m;p.detectable?
133
133
  b?m=`detectado \u2014 ${[...u?[`binario \`${p.binario}\` en PATH`]:[],...f.map(y=>`config presente: ${y}`)].join("; ")}`:m="no detectado":m="sin detecci\xF3n autom\xE1tica (extensi\xF3n de editor)",l.
134
- push(`\xBB ${p.id}: ${m}`),l.push(` ${p.nombre}`);for(let d of p.receta({base:s,baseV1:i,rutas:c}))l.push(` ${d}`);l.push(` ${St}`),l.push("");}return l.push("Regla BYOK: las recetas del anillo Anth\
134
+ push(`\xBB ${p.id}: ${m}`),l.push(` ${p.nombre}`);for(let d of p.receta({base:s,baseV1:i,rutas:c}))l.push(` ${d}`);l.push(` ${Et}`),l.push("");}return l.push("Regla BYOK: las recetas del anillo Anth\
135
135
  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 \
136
136
  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:l.join(`
137
- `),detectados:a}}var Be="savings-mode";function Ct(e,t){for(let n of e){let o=join(n,"skills",Be,"SKILL.md");if(t.existe(o))return o}return null}function Ve(e){let t=Ct(e.origenes,e.fs);if(!t)
138
- return {ok:false,destino:"",mensaje:`[bvsala skill] no encuentro el SKILL.md empaquetado (ra\xEDces probadas: ${e.origenes.join(", ")})`};let n=e.proyecto?join(e.cwd,".claude","skills"):join(e.home,".claude","s\
139
- kills"),o=join(n,Be),r=join(o,"SKILL.md"),s=e.fs.leer(t);if(e.fs.existe(r)&&!e.forzar){let i=e.fs.leer(r)===s;return {ok:i,destino:r,mensaje:i?`[bvsala skill] ya instalado y al d\xEDa: ${r}`:`[bvsala skill] \
140
- ya existe ${r} con contenido DISTINTO \u2014 no lo piso; usa --forzar para sobrescribir`}}return e.fs.crearDir(o),e.fs.escribir(r,s),{ok:true,destino:r,mensaje:`[bvsala skill] instalado: ${r}
137
+ `),detectados:a}}var Fe="savings-mode";function Ue(e,t){for(let n of e){let r=join(n,"skills",Fe,"SKILL.md");if(t.existe(r))return r}return null}var qe="<!-- bvsala:savings-mode:inicio -->",W="\
138
+ <!-- bvsala:savings-mode:fin -->";function At(e){let t=e.replace(/^---\n[\s\S]*?\n---\n*/,"").trim();return `${qe}
139
+ ${t}
140
+
141
+ _Always on (AGENTS.md has no activation step). Delete this block to turn it off; \`bvsala skill --codex --forzar\` restores it._
142
+ ${W}`}function Ke(e){let t=Ue(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=At(e.fs.
143
+ 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+`
144
+ `),{ok:true,destino:o,mensaje:ce(o,"instalado")};let s=e.fs.leer(o),i=s.indexOf(qe),a=s.indexOf(W);return i===-1||a===-1||a<i?(e.fs.escribir(o,s.trimEnd()+`
145
+
146
+ `+n+`
147
+ `),{ok:true,destino:o,mensaje:ce(o,"a\xF1adido al AGENTS.md existente")}):s.slice(i,a+W.length)===n?{ok:true,destino:o,mensaje:`[bvsala skill] ya instalado y al d\xEDa: ${o}`}:e.forzar?(e.fs.escribir(o,s.
148
+ slice(0,i)+n+s.slice(a+W.length)),{ok:true,destino:o,mensaje:ce(o,"bloque actualizado")}):{ok:false,destino:o,mensaje:`[bvsala skill] el bloque savings-mode de ${o} tiene contenido DISTINTO \u2014 no lo piso; u\
149
+ sa --forzar para actualizarlo`}}function ce(e,t){return `[bvsala skill] ${t}: ${e}
150
+ [bvsala skill] AGENTS.md va SIEMPRE activo en Codex (no hay paso de activaci\xF3n); borra el bloque para apagarlo.
151
+ [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 He(e){let t=Ue(e.origenes,e.fs);if(!t)return {ok:false,destino:"",
152
+ mensaje:`[bvsala skill] no encuentro el SKILL.md empaquetado (ra\xEDces probadas: ${e.origenes.join(", ")})`};let n=e.proyecto?join(e.cwd,".claude","skills"):join(e.home,".claude","skills"),r=join(n,Fe),o=join(r,
153
+ "SKILL.md"),s=e.fs.leer(t);if(e.fs.existe(o)&&!e.forzar){let i=e.fs.leer(o)===s;return {ok:i,destino:o,mensaje:i?`[bvsala skill] ya instalado y al d\xEDa: ${o}`:`[bvsala skill] ya existe ${o} con conte\
154
+ nido DISTINTO \u2014 no lo piso; usa --forzar para sobrescribir`}}return e.fs.crearDir(r),e.fs.escribir(o,s),{ok:true,destino:o,mensaje:`[bvsala skill] instalado: ${o}
141
155
  [bvsala skill] arranca (o reinicia) Claude Code y p\xEDdele \xABsavings mode\xBB \u2014 o deja que se active solo al hablar de l\xEDmites.
142
- [bvsala skill] para medir su efecto de verdad: trabaja unos d\xEDas con \xE9l y otros sin \xE9l bajo \`bvsala claude\`, y compara en el panel.`}}var ae=class{id;exact=false;constructor(t="heuristic"){this.id=t;}count(t){if(t.length===0)return 0;let n=t.match(/[A-Za-z]+|\d+|\s+|[^\sA-Za-z\d]/g)??[],o=0;for(let r of n){let s=r[0];if(/\s/.test(s)){let i=(r.
143
- match(/\n/g)??[]).length;o+=i+Math.floor(Math.max(0,r.length-1)/2);}else /[A-Za-z]/.test(s)?o+=Math.max(1,Math.ceil(r.length/4)):/\d/.test(s)?o+=Math.max(1,Math.ceil(r.length/2)):o+=1;}return Math.max(o,
144
- Math.ceil(t.length/4))}},ce=new ae;var le=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(t=ce){this.fallbackId=t.id,this.estimators.set(t.id,t);}register(t){return this.estimators.set(t.id,t),this}mapModel(t,n){
145
- 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 o=this.modelToTokenizer.get(t.model);if(o){let r=this.estimators.
146
- get(o);if(r)return r}}return this.estimators.get(this.fallbackId)}count(t,n){return this.resolve(n).count(t)}};new le;new TextEncoder;new TextDecoder;new TextDecoder;var Vt={"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}};
147
- ne(Vt);var me="0.4.9";var[,,g,...P]=process.argv;function Wt(){console.log(`bvsala ${me} \u2014 BiVelio Savings Layer
156
+ [bvsala skill] para medir su efecto de verdad: trabaja unos d\xEDas con \xE9l y otros sin \xE9l bajo \`bvsala claude\`, y compara en el panel.`}}var le=class{id;exact=false;constructor(t="heuristic"){this.id=t;}count(t){if(t.length===0)return 0;let n=t.match(/[A-Za-z]+|\d+|\s+|[^\sA-Za-z\d]/g)??[],r=0;for(let o of n){let s=o[0];if(/\s/.test(s)){let i=(o.
157
+ match(/\n/g)??[]).length;r+=i+Math.floor(Math.max(0,o.length-1)/2);}else /[A-Za-z]/.test(s)?r+=Math.max(1,Math.ceil(o.length/4)):/\d/.test(s)?r+=Math.max(1,Math.ceil(o.length/2)):r+=1;}return Math.max(r,
158
+ Math.ceil(t.length/4))}},ue=new le;var de=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(t=ue){this.fallbackId=t.id,this.estimators.set(t.id,t);}register(t){return this.estimators.set(t.id,t),this}mapModel(t,n){
159
+ 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.
160
+ get(r);if(o)return o}}return this.estimators.get(this.fallbackId)}count(t,n){return this.resolve(n).count(t)}};new de;new TextEncoder;new TextDecoder;new TextDecoder;var Ht={"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}};
161
+ oe(Ht);var he="0.4.10";var[,,g,...I]=process.argv;function Zt(){console.log(`bvsala ${he} \u2014 BiVelio Savings Layer
148
162
 
149
163
  Comandos:
150
164
  gateway [--puerto N] Proxy local medidor (por defecto 8402) para terminales que
@@ -155,11 +169,14 @@ Comandos:
155
169
  goose [args\u2026] Lanza Goose (ANTHROPIC_HOST) a trav\xE9s del gateway.
156
170
  qwen [args\u2026] Lanza Qwen Code (ANTHROPIC_BASE_URL) a trav\xE9s del gateway.
157
171
  doctor [--puerto N] Detecta terminales instalados e imprime su configuraci\xF3n exacta.
158
- skill [--proyecto] [--forzar]
172
+ skill [--codex] [--proyecto] [--forzar]
159
173
  Instala el skill \xABsavings-mode\xBB de Claude Code (~/.claude/skills;
160
174
  con --proyecto, en .claude/skills del directorio actual): salida
161
175
  tersa + herramientas quir\xFArgicas para estirar los topes de
162
- suscripci\xF3n. Su efecto se mide con el gateway, no se promete.
176
+ suscripci\xF3n. Con --codex, la misma disciplina como bloque marcado
177
+ de AGENTS.md (~/.codex; con --proyecto, ./AGENTS.md) \u2014 Codex no
178
+ tiene skills y AGENTS.md va siempre activo. Su efecto se mide con
179
+ el gateway, no se promete.
163
180
 
164
181
  Entorno:
165
182
  BIVELIO_SERVICE_KEY Clave de servicio (bvk_\u2026): habilita metering + asiento.
@@ -172,21 +189,21 @@ Entorno:
172
189
  CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 (Claude Code, solo API key) tus modelos en /model.
173
190
 
174
191
  El gateway nunca corta el tr\xE1fico: sin clave o con la ingesta ca\xEDda, reenv\xEDa y avisa.
175
- Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var B=$e(g);g!=="gateway"&&g!=="doctor"&&g!=="skill"&&!B&&(Wt(),process.exit(g===void 0||g==="hel\
176
- p"||g==="--help"?0:1));if(g==="skill"){let e=dirname(fileURLToPath(import.meta.url)),t=Ve({origenes:[join(e,"..",".."),join(e,"..")],home:homedir(),cwd:process.cwd(),proyecto:P.includes("--proyecto"),forzar:P.includes("--forz\
177
- ar"),fs:{existe:existsSync,leer:n=>readFileSync(n,"utf8"),escribir:(n,o)=>writeFileSync(n,o),crearDir:n=>mkdirSync(n,{recursive:true})}});console.log(t.mensaje),process.exit(t.ok?0:1);}var he=P.indexOf("--puerto"),D=B?0:he>=0?Number(P[he+1]):
178
- 8402;(!Number.isInteger(D)||(g==="doctor"?D<1:D<0)||D>65535)&&(console.error(`[bvsala ${g}] puerto inv\xE1lido: ${P[he+1]}`),process.exit(1));if(g==="doctor"){P.includes("--write")&&(console.error("[b\
179
- vsala doctor] --write todav\xEDa no existe: el doctor solo imprime; copia el bloque a mano."),process.exit(1));let e=await ze(D);console.log(e.texto),process.exit(0);}var I=B?ie(g,0,P,{modoCodex:g==="c\
180
- odex"?Ne({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0}):null,ge=process.env.BIVELIO_SERVICE_KEY,Gt=B&&process.env.BVSALA_GATEWAY_LOG!=="1",_=await Le({puerto:D,...Gt?{log:()=>{}}:{},
181
- ...ge?{serviceAccountKey:ge}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.env.BVSALA_GATEWAY_UPSTREAM}:I?.upstream?{
182
- upstream:I.upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}}),Yt=process.env.BVSALA_GATEWAY_UPSTREAM??
183
- I?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway] v${me} escuchando en http://127.0.0.1:${_.puerto} \u2192 ${Yt}`);console.log(`[bvsala gateway] medici\xF3n: ${ge?"ACTIVA (clave d\
184
- e servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);process.env.BVSALA_GATEWAY_UPSTREAM&&I?.upstream&&process.env.BVSALA_GATEWAY_UPSTREAM!==I.upstream&&console.error(`[bvsala ${g}] AVISO: B\
185
- VSALA_GATEWAY_UPSTREAM=${process.env.BVSALA_GATEWAY_UPSTREAM} pisa el upstream de este terminal (${I.upstream}). Si no es lo que quieres, quita la variable.`);g==="codex"&&!Ie()&&console.error(`[bvsal\
186
- a codex] AVISO: este Node (${process.version}) no descomprime zstd y Codex manda el cuerpo en zstd: el tr\xE1fico pasa pero NO se mide. Usa Node \u2265 22.15.`);if(I&&B){let e=ie(g,_.puerto,P,{...I.upstream?
187
- {modoCodex:I.upstream.includes("chatgpt.com")?"chatgpt":"api-key"}:{}});e.nota&&console.log(`[bvsala ${g}] ${e.nota}`);let t=e.bin;if(process.platform==="win32"){let o=je(e.bin);o.tipo==="exe"?t=o.ruta:
188
- o.tipo==="solo-shim"&&(console.error(`[bvsala ${g}] \`${e.bin}\` est\xE1 instalado como shim de npm (${o.ruta}) y Windows no puede lanzarlo de forma segura con argumentos (CVE-2024-27980). Inst\xE1lalo c\
189
- on su instalador nativo (deja un ${e.bin}.exe en el PATH) o usa la receta manual de docs/terminales/.`),await _.cerrar(),process.exit(127));}let n=spawn(t,e.args,{stdio:"inherit",env:{...process.env,...e.
190
- env}});n.on("error",o=>{console.error(`[bvsala ${g}] no se pudo lanzar \`${e.bin}\`: ${String(o)}. \xBFEst\xE1 instalado y en el PATH?`),_.cerrar().then(()=>process.exit(127));}),n.on("exit",(o,r)=>{_.
191
- cerrar().then(()=>process.exit(o??(r?1:0)));});}else console.log(`[bvsala gateway] apunta tu terminal (o usa \`bvsala ${se.join("|")}\`): ANTHROPIC_BASE_URL=http://127.0.0.1:${_.puerto} claude \xB7 bv\
192
- sala doctor para el resto`);var Ye=async()=>{await _.cerrar(),process.exit(0);};process.on("SIGINT",()=>{Ye();});process.on("SIGTERM",()=>{Ye();});
192
+ Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var B=Ne(g);g!=="gateway"&&g!=="doctor"&&g!=="skill"&&!B&&(Zt(),process.exit(g===void 0||g==="hel\
193
+ p"||g==="--help"?0:1));if(g==="skill"){let e=dirname(fileURLToPath(import.meta.url)),t={origenes:[join(e,"..",".."),join(e,"..")],home:homedir(),cwd:process.cwd(),proyecto:I.includes("--proyecto"),forzar:I.includes("--forzar"),
194
+ fs:{existe:existsSync,leer:r=>readFileSync(r,"utf8"),escribir:(r,o)=>writeFileSync(r,o),crearDir:r=>mkdirSync(r,{recursive:true})}},n=I.includes("--codex")?Ke(t):He(t);console.log(n.mensaje),process.exit(n.ok?0:1);}var be=I.indexOf("--puer\
195
+ to"),L=B?0:be>=0?Number(I[be+1]):8402;(!Number.isInteger(L)||(g==="doctor"?L<1:L<0)||L>65535)&&(console.error(`[bvsala ${g}] puerto inv\xE1lido: ${I[be+1]}`),process.exit(1));if(g==="doctor"){I.includes(
196
+ "--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 Ve(L);console.log(e.texto),process.exit(0);}var P=B?
197
+ ae(g,0,I,{modoCodex:g==="codex"?Be({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0}):null,ve=process.env.BIVELIO_SERVICE_KEY,en=B&&process.env.BVSALA_GATEWAY_LOG!=="1",_=await $e({puerto:L,
198
+ ...en?{log:()=>{}}:{},...ve?{serviceAccountKey:ve}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.env.BVSALA_GATEWAY_UPSTREAM}:
199
+ P?.upstream?{upstream:P.upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}}),tn=process.env.
200
+ BVSALA_GATEWAY_UPSTREAM??P?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway] v${he} escuchando en http://127.0.0.1:${_.puerto} \u2192 ${tn}`);console.log(`[bvsala gateway] medici\xF3n:\
201
+ ${ve?"ACTIVA (clave de servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);process.env.BVSALA_GATEWAY_UPSTREAM&&P?.upstream&&process.env.BVSALA_GATEWAY_UPSTREAM!==P.upstream&&console.error(`\
202
+ [bvsala ${g}] AVISO: BVSALA_GATEWAY_UPSTREAM=${process.env.BVSALA_GATEWAY_UPSTREAM} pisa el upstream de este terminal (${P.upstream}). Si no es lo que quieres, quita la variable.`);g==="codex"&&!Ae()&&
203
+ console.error(`[bvsala codex] AVISO: este Node (${process.version}) no descomprime zstd y Codex manda el cuerpo en zstd: el tr\xE1fico pasa pero NO se mide. Usa Node \u2265 22.15.`);if(P&&B){let e=ae(
204
+ g,_.puerto,I,{...P.upstream?{modoCodex:P.upstream.includes("chatgpt.com")?"chatgpt":"api-key"}:{}});e.nota&&console.log(`[bvsala ${g}] ${e.nota}`);let t=e.bin;if(process.platform==="win32"){let r=ze(e.
205
+ bin);r.tipo==="exe"?t=r.ruta:r.tipo==="solo-shim"&&(console.error(`[bvsala ${g}] \`${e.bin}\` est\xE1 instalado como shim de npm (${r.ruta}) y Windows no puede lanzarlo de forma segura con argumentos \
206
+ (CVE-2024-27980). Inst\xE1lalo con su instalador nativo (deja un ${e.bin}.exe en el PATH) o usa la receta manual de docs/terminales/.`),await _.cerrar(),process.exit(127));}let n=spawn(t,e.args,{stdio:"in\
207
+ herit",env:{...process.env,...e.env}});n.on("error",r=>{console.error(`[bvsala ${g}] no se pudo lanzar \`${e.bin}\`: ${String(r)}. \xBFEst\xE1 instalado y en el PATH?`),_.cerrar().then(()=>process.exit(
208
+ 127));}),n.on("exit",(r,o)=>{_.cerrar().then(()=>process.exit(r??(o?1:0)));});}else console.log(`[bvsala gateway] apunta tu terminal (o usa \`bvsala ${ie.join("|")}\`): ANTHROPIC_BASE_URL=http://127.0.0.\
209
+ 1:${_.puerto} claude \xB7 bvsala doctor para el resto`);var tt=async()=>{await _.cerrar(),process.exit(0);};process.on("SIGINT",()=>{tt();});process.on("SIGTERM",()=>{tt();});
package/dist/index.d.ts CHANGED
@@ -3250,6 +3250,6 @@ declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"
3250
3250
  * constant to package.json so the two can never drift.
3251
3251
  */
3252
3252
 
3253
- declare const BV_SALA_VERSION = "0.4.9";
3253
+ declare const BV_SALA_VERSION = "0.4.10";
3254
3254
 
3255
3255
  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 };
package/dist/index.js CHANGED
@@ -782,7 +782,7 @@ ng/usage`:void 0);if(a){i=new dt({url:a,serviceAccountKey:t.serviceAccountKey,..
782
782
  {},...t.metering?.maxBatch!=null?{maxBatch:t.metering.maxBatch}:{},...t.metering?.maxBuffer!=null?{maxBuffer:t.metering.maxBuffer}:{},...t.meteringFetch?{fetchImpl:t.meteringFetch}:t.licenseFetch?{fetchImpl:t.
783
783
  licenseFetch}:{},...t.now?{now:t.now}:{},...t.metering?.ci!=null?{ci:t.metering.ci}:{},...t.silenceWarnings!=null?{silenceWarnings:t.silenceWarnings}:{}});let c=t.metering?.ci??cr();if(t.metering?.seatId)
784
784
  i.setSeat(t.metering.seatId,c);else {let l=i;lr(t.serviceAccountKey).then(u=>l.setSeat(u,c)).catch(()=>{});}i.start();}}return {generate:async a=>{let c=await s.generate(a);if(i)try{i.record(c.report);}catch{}
785
- return c},close:()=>{r?.stop(),o?.stop(),i?.stop();}}}var af="0.4.9";/*! Bundled license information:
785
+ return c},close:()=>{r?.stop(),o?.stop(),i?.stop();}}}var af="0.4.10";/*! Bundled license information:
786
786
 
787
787
  @noble/curves/utils.js:
788
788
  @noble/curves/abstract/modular.js:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bivelio/savings-layer",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "description": "BV-SALA — provider-neutral LLM execution optimizer. A decision layer in front of any LLM provider that cuts the cost of every request while preserving required quality, and reports the saving per call with token-level accounting you can check against your bill. Proprietary and confidential — property of BiVelio Inc.",
5
5
  "keywords": [
6
6
  "llm",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: savings-mode
3
- description: Stretch Claude subscription usage limits (5-hour and weekly caps) by cutting token burn — terse output, surgical tool calls, lean context — without sacrificing correctness. Use when the user mentions usage limits, running out of quota, or asks to save tokens.
3
+ description: Stretch Claude subscription usage limits (5-hour and weekly caps): terse output, surgical tool calls, lean context — never at the cost of correctness. Use when limits, quota or saving tokens come up.
4
4
  ---
5
5
 
6
6
  # Savings Mode — by BiVelio