@bivelio/savings-layer 0.4.3 → 0.4.4

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,11 +1,91 @@
1
1
  #!/usr/bin/env node
2
- import {spawn}from'child_process';import {createServer}from'http';import {randomUUID,createHash}from'crypto';var te=new Set;function v(t,e){te.has(t)||(te.add(t),console.warn(e));}var w=class{options;now;flushIntervalMs;maxBatch;maxBuffer;buffer=[];seatId;ci;timer;inflight=null;fallosSeguidos=0;constructor(e){this.options=e,this.now=e.now??(()=>Date.now()),this.flushIntervalMs=e.flushIntervalMs??15e3,this.maxBatch=e.maxBatch??50,this.maxBuffer=e.maxBuffer??1e3,this.ci=e.ci??false;}setSeat(e,n){e&&(this.seatId=e),this.ci=n;}record(e){this.buffer.push({report:e,at:this.now()}),this.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.flushIntervalMs),this.timer.unref?.());}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0),this.flush();}async stopAndDrain(e){this.timer&&(clearInterval(this.timer),this.timer=void 0);let n=e??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.inflight)return this.inflight;if(this.buffer.length!==0){this.inflight=this.hacerFlush();try{await this.inflight;}finally{this.inflight=null;}}}async hacerFlush(){let e=this.options.fetchImpl??globalThis.fetch;if(!e)return;let n=this.buffer.splice(0,this.maxBatch);try{let r=n.map(s=>this.toWire(s)),o=await e(this.options.url,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${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}`)):o.status===401||o.status===403?(v(`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? \xBFrevocada? \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)):(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.fallosSeguidos++,this.requeue(n),this.avisarDeFallo(r instanceof Error?r.message:"error de red");}}avisarDeFallo(e){if(this.options.silenceWarnings)return;let n=this.fallosSeguidos;n<3||(n-3)%12!==0||console.warn(`[bv-sala] el env\xEDo de uso lleva ${n} intentos seguidos fallando contra ${this.options.url} (${e}). Las peticiones SIGUEN funcionando, pero el uso no est\xE1 llegando: el panel dir\xE1 que no ve tr\xE1fico y los asientos facturables saldr\xE1n a cero. Comprueba 'meteringUrl' \u2014 por defecto es \`\${licenseServerUrl}/api/metering/usage\`.`);}requeue(e){this.buffer=[...e,...this.buffer],this.buffer.length>this.maxBuffer&&(this.buffer.length=this.maxBuffer);}toWire(e){let n=e.report;return {orgId:this.options.orgId??"self",...this.seatId?{seatId:this.seatId}:{},...this.ci?{ci:true}:{},requestId:n.requestId,model:n.model,at:e.at,avoidedLlm:n.calls.avoidedLlm,actualLlm:n.calls.actualLlm,baselineInputTokens:n.tokens.baselineInput,actualInputTokens:n.tokens.actualInput,eliminatedInputTokens:n.tokens.eliminatedInput,compressedInputTokens:n.tokens.compressedInput,baselineOutputTokens:n.tokens.baselineOutput,actualOutputTokens:n.tokens.actualOutput,providerCachedInputTokens:n.tokens.providerCachedInput??0,cacheResult:n.cache.result,baselineCost:n.cost.baselineEstimate,actualCost:n.cost.actual,netSavings:n.cost.netSavings,...n.cost.basis?{basis:n.cost.basis}:{}}}};function O(){return globalThis.process}function be(t){if(/^[0-9a-f]{12}$/.test(t)||/^\d{1,3}(\.\d{1,3}){3}$/.test(t)||/^ip-\d{1,3}(-\d{1,3}){3}$/i.test(t))return true;let e="[bcdfghjklmnpqrstvwxz2456789]";return new RegExp(`-${e}{8,10}-${e}{5}$`).test(t)}function ve(t){return !!(t.AWS_LAMBDA_FUNCTION_NAME??t.LAMBDA_TASK_ROOT??t.VERCEL??t.VERCEL_ENV??t.K_SERVICE??t.FUNCTION_TARGET??t.FUNCTIONS_WORKER_RUNTIME??t.NETLIFY)}async function ye(){if(O()?.execPath)try{let e=await import(["node","os"].join(":")),n=typeof e.hostname=="function"?e.hostname():"";return n.length>0?n:void 0}catch{return}}async function ke(t,e=ye){if(t.KUBERNETES_SERVICE_HOST||ve(t))return;let n=t.HOSTNAME??t.COMPUTERNAME??await e();if(!(!n||be(n)))return n}async function xe(){let t=O();if(!t||!t.env)return;let e=t.env,n=await ke(e),r=[t.platform,t.arch,t.execPath,n,e.USER??e.USERNAME??e.LOGNAME,e.HOME??e.USERPROFILE].filter(o=>typeof o=="string"&&o.length>0);return r.length?r.join("|"):void 0}var we=["CI","CONTINUOUS_INTEGRATION","GITHUB_ACTIONS","GITLAB_CI","CIRCLECI","TRAVIS","JENKINS_URL","BUILDKITE","TF_BUILD","TEAMCITY_VERSION","BITBUCKET_BUILD_NUMBER","CODEBUILD_BUILD_ID","HEROKU_TEST_RUN_ID","DRONE"];function D(){let t=O()?.env;if(!t)return false;for(let e of we){let n=t[e];if(n&&n!=="false"&&n!=="0")return true}return false}async function Se(t,e){let n=globalThis.crypto,r=new TextEncoder;if(n?.subtle)try{let i=await n.subtle.importKey("raw",r.encode(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),l=await n.subtle.sign("HMAC",i,r.encode(e));return [...new Uint8Array(l)].map(u=>u.toString(16).padStart(2,"0")).join("")}catch{}let o=2166136261,s=`${t}|${e}`;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 A(t){let n=O()?.env?.BVSALA_SEAT_ID;if(n&&Te.test(n))return n;let r=await xe();if(!r)return;let o=await Se(t,r);if(o)return `m-${o.slice(0,32)}`}var Te=/^[A-Za-z0-9._:/-]{1,128}$/;var F={"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},"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,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},"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},"gpt-5.1-codex-max":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gpt-5.1-codex-mini":{inputPer1k:25e-5,outputPer1k:.002,cachedInputPer1k:25e-6},"gpt-5.1-thinking":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gpt-5.2":{inputPer1k:.00175,outputPer1k:.014,cachedInputPer1k:175e-6},"gpt-5.2-codex":{inputPer1k:.00175,outputPer1k:.014,cachedInputPer1k:175e-6},"gpt-5.3-codex":{inputPer1k:.00175,outputPer1k:.014,cachedInputPer1k:175e-6},"gpt-5.4":{inputPer1k:.0025,outputPer1k:.015,cachedInputPer1k:25e-5},"gpt-5.4-mini":{inputPer1k:75e-5,outputPer1k:.0045,cachedInputPer1k:75e-6},"gpt-5.4-nano":{inputPer1k:2e-4,outputPer1k:.00125,cachedInputPer1k:2e-5},"gpt-5.5":{inputPer1k:.005,outputPer1k:.03,cachedInputPer1k:5e-4},"gpt-5.6-luna":{inputPer1k:2e-4,outputPer1k:.0012,cachedInputPer1k:2e-5},"gpt-5.6-sol":{inputPer1k:.004,outputPer1k:.02,cachedInputPer1k:4e-4},"gpt-5.6-terra":{inputPer1k:.002,outputPer1k:.012,cachedInputPer1k:2e-4},"claude-opus-4-5":{inputPer1k:.005,outputPer1k:.025,cachedInputPer1k:5e-4},"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,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,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-fable-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},"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,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":{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},"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(t,e,n){if(!n)return;let r=t[n];if(r)return r;let o=n.lastIndexOf("/"),s=o>=0?n.slice(o+1):n,i=t[s];if(i)return i;for(let l of e)if(s.startsWith(l)){let u=s.charAt(l.length);if(u===""||u==="-"||u==="."||u==="_")return t[l]}}function V(t){return Object.keys(t).sort((e,n)=>n.length-e.length)}var Ce=V(F);function E(t){return ne(F,Ce,t)}var I=class{resto="";uso=null;eventos={};observar(e){this.resto+=typeof e=="string"?e:e.toString("utf8");let n;for(;(n=this.resto.indexOf(`
2
+ import {spawn}from'child_process';import {createServer}from'http';import {randomUUID,createHash}from'crypto';var se=new Set;function y(t,e){se.has(t)||(se.add(t),console.warn(e));}var w=class{options;now;flushIntervalMs;maxBatch;maxBuffer;buffer=[];seatId;ci;timer;inflight=null;fallosSeguidos=0;constructor(e){this.options=e,this.now=e.now??(()=>Date.now()),this.flushIntervalMs=
3
+ e.flushIntervalMs??15e3,this.maxBatch=e.maxBatch??50,this.maxBuffer=e.maxBuffer??1e3,this.ci=e.ci??false;}setSeat(e,n){e&&(this.seatId=e),this.ci=n;}record(e){this.buffer.push({report:e,at:this.now()}),this.
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
+ flushIntervalMs),this.timer.unref?.());}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0),this.flush();}async stopAndDrain(e){this.timer&&(clearInterval(this.timer),this.timer=void 0);let n=e??
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
+ inflight)return this.inflight;if(this.buffer.length!==0){this.inflight=this.hacerFlush();try{await this.inflight;}finally{this.inflight=null;}}}async hacerFlush(){let e=this.options.fetchImpl??globalThis.
8
+ fetch;if(!e)return;let n=this.buffer.splice(0,this.maxBatch);try{let r=n.map(s=>this.toWire(s)),o=await e(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?(y(`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(e){if(this.options.silenceWarnings)return;let n=this.fallosSeguidos;n<3||(n-3)%12!==0||console.
14
+ warn(`[bv-sala] el env\xEDo de uso lleva ${n} intentos seguidos fallando contra ${this.options.url} (${e}). Las peticiones SIGUEN funcionando, pero el uso no est\xE1 llegando: el panel dir\xE1 que no ve tr\xE1\
15
+ fico y los asientos facturables saldr\xE1n a cero. Comprueba 'meteringUrl' \u2014 por defecto es \`\${licenseServerUrl}/api/metering/usage\`.`);}requeue(e){this.buffer=[...e,...this.buffer],this.buffer.
16
+ length>this.maxBuffer&&(this.buffer.length=this.maxBuffer);}toWire(e){let n=e.report;return {orgId:this.options.orgId??"self",...this.seatId?{seatId:this.seatId}:{},...this.ci?{ci:true}:{},requestId:n.requestId,
17
+ model:n.model,at:e.at,avoidedLlm:n.calls.avoidedLlm,actualLlm:n.calls.actualLlm,baselineInputTokens:n.tokens.baselineInput,actualInputTokens:n.tokens.actualInput,eliminatedInputTokens:n.tokens.eliminatedInput,
18
+ compressedInputTokens:n.tokens.compressedInput,baselineOutputTokens:n.tokens.baselineOutput,actualOutputTokens:n.tokens.actualOutput,providerCachedInputTokens:n.tokens.providerCachedInput??0,cacheResult:n.
19
+ cache.result,baselineCost:n.cost.baselineEstimate,actualCost:n.cost.actual,netSavings:n.cost.netSavings,...n.cost.basis?{basis:n.cost.basis}:{}}}};function O(){return globalThis.process}function we(t){if(/^[0-9a-f]{12}$/.test(t)||/^\d{1,3}(\.\d{1,3}){3}$/.test(t)||/^ip-\d{1,3}(-\d{1,3}){3}$/i.test(t))return true;let e="[bcdfghjklmnpqrstvwxz2456789]";
20
+ return new RegExp(`-${e}{8,10}-${e}{5}$`).test(t)}function Se(t){return !!(t.AWS_LAMBDA_FUNCTION_NAME??t.LAMBDA_TASK_ROOT??t.VERCEL??t.VERCEL_ENV??t.K_SERVICE??t.FUNCTION_TARGET??t.FUNCTIONS_WORKER_RUNTIME??
21
+ t.NETLIFY)}async function Te(){if(O()?.execPath)try{let e=await import(["node","os"].join(":")),n=typeof e.hostname=="function"?e.hostname():"";return n.length>0?n:void 0}catch{return}}async function Ce(t,e=Te){
22
+ if(t.KUBERNETES_SERVICE_HOST||Se(t))return;let n=t.HOSTNAME??t.COMPUTERNAME??await e();if(!(!n||we(n)))return n}async function Re(){let t=O();if(!t||!t.env)return;let e=t.env,n=await Ce(e),r=[t.platform,
23
+ t.arch,t.execPath,n,e.USER??e.USERNAME??e.LOGNAME,e.HOME??e.USERPROFILE].filter(o=>typeof o=="string"&&o.length>0);return r.length?r.join("|"):void 0}var Ee=["CI","CONTINUOUS_INTEGRATION","GITHUB_ACTI\
24
+ ONS","GITLAB_CI","CIRCLECI","TRAVIS","JENKINS_URL","BUILDKITE","TF_BUILD","TEAMCITY_VERSION","BITBUCKET_BUILD_NUMBER","CODEBUILD_BUILD_ID","HEROKU_TEST_RUN_ID","DRONE"];function D(){let t=O()?.env;if(!t)
25
+ return false;for(let e of Ee){let n=t[e];if(n&&n!=="false"&&n!=="0")return true}return false}async function Ie(t,e){let n=globalThis.crypto,r=new TextEncoder;if(n?.subtle)try{let i=await n.subtle.importKey("raw",
26
+ r.encode(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),c=await n.subtle.sign("HMAC",i,r.encode(e));return [...new Uint8Array(c)].map(u=>u.toString(16).padStart(2,"0")).join("")}catch{}let o=2166136261,s=`${t}\
27
+ |${e}`;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 A(t){let n=O()?.env?.BVSALA_SEAT_ID;if(n&&Pe.test(n))return n;let r=await Re();
28
+ if(!r)return;let o=await Ie(t,r);if(o)return `m-${o.slice(0,32)}`}var Pe=/^[A-Za-z0-9._:/-]{1,128}$/;var U={"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},
29
+ "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,
30
+ 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},
31
+ "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},
32
+ "gpt-5.1-codex-max":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gpt-5.1-codex-mini":{inputPer1k:25e-5,outputPer1k:.002,cachedInputPer1k:25e-6},"gpt-5.1-thinking":{inputPer1k:.00125,outputPer1k:.01,
33
+ cachedInputPer1k:125e-6},"gpt-5.2":{inputPer1k:.00175,outputPer1k:.014,cachedInputPer1k:175e-6},"gpt-5.2-codex":{inputPer1k:.00175,outputPer1k:.014,cachedInputPer1k:175e-6},"gpt-5.3-codex":{inputPer1k:.00175,
34
+ outputPer1k:.014,cachedInputPer1k:175e-6},"gpt-5.4":{inputPer1k:.0025,outputPer1k:.015,cachedInputPer1k:25e-5},"gpt-5.4-mini":{inputPer1k:75e-5,outputPer1k:.0045,cachedInputPer1k:75e-6},"gpt-5.4-nano":{
35
+ inputPer1k:2e-4,outputPer1k:.00125,cachedInputPer1k:2e-5},"gpt-5.5":{inputPer1k:.005,outputPer1k:.03,cachedInputPer1k:5e-4},"gpt-5.6-luna":{inputPer1k:2e-4,outputPer1k:.0012,cachedInputPer1k:2e-5},"gp\
36
+ t-5.6-sol":{inputPer1k:.004,outputPer1k:.02,cachedInputPer1k:4e-4},"gpt-5.6-terra":{inputPer1k:.002,outputPer1k:.012,cachedInputPer1k:2e-4},"claude-opus-4-5":{inputPer1k:.005,outputPer1k:.025,cachedInputPer1k:5e-4},
37
+ "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,
38
+ 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,
39
+ 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\
40
+ 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},
41
+ "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,
42
+ 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":{
43
+ 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},
44
+ "gemini-2.5-pro":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gemini-2.5-flash":{inputPer1k:3e-4,outputPer1k:.0025,cachedInputPer1k:3e-5}};function ie(t,e,n){if(!n)return;let r=t[n];if(r)
45
+ return r;let o=n.lastIndexOf("/"),s=o>=0?n.slice(o+1):n,i=t[s];if(i)return i;for(let c of e){if(!s.startsWith(c))continue;let u=s.charAt(c.length);if(u!==""&&u!=="-"&&u!=="."&&u!=="_")continue;let a=s.
46
+ slice(c.length+1);if(!(/^\d/.test(a)&&!/^\d{4}/.test(a)))return t[c]}}function q(t){return Object.keys(t).sort((e,n)=>n.length-e.length)}var _e=q(U);function I(t){return ie(U,_e,t)}var P=class{resto="";uso=null;eventos={};observar(e){this.resto+=typeof e=="string"?e:e.toString("utf8");let n;for(;(n=this.resto.indexOf(`
3
47
 
4
- `))>=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;let s=/^data: (.+)$/m.exec(r)?.[1];if(s)try{let i=JSON.parse(s),l=i.usage??i.message?.usage;l&&(this.uso={...this.uso,...l});}catch{}}}usage(){return this.uso}},Re=1.25;function oe(t,e){let n=E(t);if(!n)return null;let r=e.input_tokens??0,o=e.output_tokens??0,s=e.cache_read_input_tokens??0,i=e.cache_creation_input_tokens??0,l=n.cachedInputPer1k??n.inputPer1k;return {costeUsd:r/1e3*n.inputPer1k+s/1e3*l+i/1e3*n.inputPer1k*Re+o/1e3*n.outputPer1k,precio:n,tokens:{entrada:r,salida:o,cacheLeida:s,cacheEscrita:i}}}function re(t,e,n){return ie(t,e,n,false)}function se(t,e,n){return ie(t,e,n,true)}function ie(t,e,n,r){let o=n.tokens.entrada+n.tokens.cacheLeida+n.tokens.cacheEscrita;return {requestId:t,model:e,calls:r?{avoidedLlm:1,actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{baselineInput:o,actualInput:r?0:o,eliminatedInput:0,compressedInput:0,baselineOutput:n.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:"estimated"}:{baselineEstimate:n.costeUsd,actual:n.costeUsd,netSavings:0,basis:"measured"}}}var Ie=5*1024*1024,Pe=["authorization","x-api-key","anthropic-version","anthropic-beta"];function ae(t,e,n,r=""){return createHash("sha256").update(t).update(`
48
+ `))>=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;
49
+ let s=/^data: (.+)$/m.exec(r)?.[1];if(s)try{let i=JSON.parse(s),c=i.usage??i.message?.usage;if(c){let u=c.cache_creation||this.uso?.cache_creation?{...this.uso?.cache_creation,...c.cache_creation}:void 0;
50
+ this.uso={...this.uso,...c,...u?{cache_creation:u}:{}};}}catch{}}}usage(){return this.uso}},Oe=1.25,De=2;function ae(t,e){let n=I(t);if(!n)return null;let r=e.input_tokens??0,o=e.output_tokens??0,s=e.cache_read_input_tokens??
51
+ 0,i=e.cache_creation,c=e.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),a=n.cachedInputPer1k??n.inputPer1k;
52
+ return {costeUsd:r/1e3*n.inputPer1k+s/1e3*a+(c-u)/1e3*n.inputPer1k*Oe+u/1e3*n.inputPer1k*De+o/1e3*n.outputPer1k,precio:n,tokens:{entrada:r,salida:o,cacheLeida:s,cacheEscrita:c,cacheEscrita1h:u}}}function K(t){
53
+ try{return JSON.parse(typeof t=="string"?t:t.toString("utf8")).usage??null}catch{return null}}function ce(t,e,n){return ue(t,e,n,false)}function le(t,e,n){return ue(t,e,n,true)}function ue(t,e,n,r){let o=n.
54
+ tokens.entrada+n.tokens.cacheLeida+n.tokens.cacheEscrita;return {requestId:t,model:e,calls:r?{avoidedLlm:1,actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{baselineInput:o,actualInput:r?0:o,eliminatedInput:0,
55
+ compressedInput:0,baselineOutput:n.tokens.salida,actualOutput:r?0:n.tokens.salida,providerCachedInput:r?0:n.tokens.cacheLeida},cache:{result:r?"singleflight":"miss"},cost:r?{baselineEstimate:n.costeUsd,
56
+ actual:0,netSavings:1,basis:"estimated"}:{baselineEstimate:n.costeUsd,actual:n.costeUsd,netSavings:0,basis:"measured"}}}var J=5*1024*1024,Me=["authorization","x-api-key","anthropic-version","anthropic-beta"];function de(t,e,n,r=""){return createHash("sha256").update(t).update(`
5
57
  `).update(e).update(`
6
58
  `).update(r).update(`
7
- `).update(n).digest("hex")}function ce(t){return Pe.map(e=>`${e}=${t(e)??""}`).join(`
8
- `)}var _=class{enVuelo=new Map;primarias=0;gemelas=0;registrar(e){if(this.enVuelo.has(e))return null;this.primarias+=1;let n,r=new Promise(l=>n=l);this.enVuelo.set(e,{promesa:r,resolver:n});let o=[],s=0,i=false;return {capturar:l=>{if(!i){if(s+=l.length,s>Ie){i=true,o.length=0;return}o.push(l);}},terminar:(l,u)=>{let a=!i&&l>=200&&l<300;this.enVuelo.get(e)?.resolver(a?{status:l,headers:u,chunks:o}:null),this.enVuelo.delete(e);},abortar:()=>{this.enVuelo.get(e)?.resolver(null),this.enVuelo.delete(e);}}}async esperar(e){let n=this.enVuelo.get(e);if(!n)return null;let r=await n.promesa;return r&&(this.gemelas+=1),r}tieneEnVuelo(e){return this.enVuelo.has(e)}};var Ae=["content-encoding","content-length","transfer-encoding"];async function le(t={}){let e=(t.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=t.fetchImpl??fetch,r=t.log??(a=>console.log(a)),o;if(t.serviceAccountKey){o=new w({url:t.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:t.serviceAccountKey,...t.fetchImpl?{fetchImpl:t.fetchImpl}:{},onAuthError:c=>v("gateway-ingest-auth",`[bvsala gateway] la ingesta de uso rechaza la credencial (${c}). El tr\xE1fico sigue fluyendo (avisar, nunca romper), pero este uso no est\xE1 llegando al panel. Revisa BIVELIO_SERVICE_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let a=D();t.seatId?o.setSeat(t.seatId,a):A(t.serviceAccountKey).then(c=>o?.setSeat(c,a)).catch(()=>{}),o.start();}else v("gateway-sin-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=t.coalescer?new _:null,i=(a,c,m,f,g)=>{if(!o||!m)return;let d="";try{d=JSON.parse(c.toString("utf8")).model??"";}catch{}let p=d?oe(d,m):null;p?(o.record(f?se(a,d,p):re(a,d,p)),r(`[gateway] POST ${g} ${f?"\xB7 COALESCIDA (llamada evitada)":""} \xB7 ${d} \xB7 $${p.costeUsd.toFixed(6)} (in ${p.tokens.entrada}, cache\u2191${p.tokens.cacheEscrita} cache\u2193${p.tokens.cacheLeida}, out ${p.tokens.salida})`)):d&&v(`gateway-sin-precio:${d}`,`[bvsala gateway] el modelo "${d}" no est\xE1 en el cat\xE1logo de precios; su uso se reenv\xEDa pero no se mide.`);},l=createServer(async(a,c)=>{let m=randomUUID(),f=new AbortController;c.on("close",()=>{c.writableEnded||f.abort();});let g=null,d=new URL(a.url??"/",e);try{let p=[];for await(let h of a)p.push(h);let y=Buffer.concat(p);d=new URL(a.url??"/",e);let C=s!==null&&a.method==="POST"&&y.length>0,N=ce(h=>{let b=a.headers[h];return Array.isArray(b)?b.join(", "):b}),R=C?ae("POST",d.pathname+d.search,y,N):"";if(C)if(s.tieneEnVuelo(R)){let h=await s.esperar(R);if(h){c.writeHead(h.status,h.headers);let b=new I;for(let ee of h.chunks)c.write(ee),b.observar(ee);c.end(),i(m,y,b.usage(),!0,d.pathname);return}}else g=s.registrar(R);let X={};for(let[h,b]of Object.entries(a.headers))h==="host"||h==="content-length"||b===void 0||(X[h]=Array.isArray(b)?b.join(", "):b);let k;try{k=await n(d,{method:a.method,headers:X,body:["GET","HEAD"].includes(a.method??"")?void 0:y,redirect:"manual",signal:f.signal});}catch(h){g?.abortar(),c.headersSent||(c.writeHead(502,{"content-type":"application/json"}),c.end(JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(h)}))),r(`[gateway] ${a.method} ${d.pathname} \u2192 upstream inalcanzable: ${String(h)}`);return}let z=Object.fromEntries(k.headers.entries());for(let h of Ae)delete z[h];c.writeHead(k.status,z);let Z=(k.headers.get("content-type")??"").includes("text/event-stream"),B=Z?new I:null;if(k.body)for await(let h of k.body){let b=Buffer.from(h);c.write(b),B?.observar(b),g?.capturar(b);}c.end(),g?.terminar(k.status,z),B&&k.ok?i(m,y,B.usage(),!1,d.pathname):Z||r(`[gateway] ${a.method} ${d.pathname} \u2192 ${k.status}`);}catch(p){if(g?.abortar(),f.signal.aborted)r(`[gateway] ${a.method} ${d.pathname} \u2192 el cliente abort\xF3; upstream cancelado`);else if(r(`[gateway] ${a.method} ${d.pathname} \u2192 error sirviendo: ${String(p)}`),!c.headersSent){c.writeHead(502,{"content-type":"application/json"}),c.end(JSON.stringify({error:"bvsala gateway: stream interrupted",detail:String(p)}));return}c.writableEnded||c.end();}});return await new Promise((a,c)=>{l.once("error",c),l.listen(t.puerto??8402,"127.0.0.1",()=>a());}),{puerto:l.address().port,cerrar:async()=>{await o?.stopAndDrain(),await new Promise(a=>l.close(()=>a()));}}}var U=class{id;exact=false;constructor(e="heuristic"){this.id=e;}count(e){if(e.length===0)return 0;let n=e.match(/[A-Za-z]+|\d+|\s+|[^\sA-Za-z\d]/g)??[],r=0;for(let o of n){let s=o[0];if(/\s/.test(s)){let i=(o.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,Math.ceil(e.length/4))}},q=new U;var K=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(e=q){this.fallbackId=e.id,this.estimators.set(e.id,e);}register(e){return this.estimators.set(e.id,e),this}mapModel(e,n){return this.modelToTokenizer.set(e,n),this}resolve(e){let n=e.tokenizer?this.estimators.get(e.tokenizer):void 0;if(n)return n;if(e.model){let r=this.modelToTokenizer.get(e.model);if(r){let o=this.estimators.get(r);if(o)return o}}return this.estimators.get(this.fallbackId)}count(e,n){return this.resolve(n).count(e)}};new K;new TextEncoder;new TextDecoder;new TextDecoder;var We={"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}};V(We);var G="0.4.3";var[,,x,...j]=process.argv;function Ye(){console.log(`bvsala ${G} \u2014 BiVelio Savings Layer
59
+ `).update(n).digest("hex")}function pe(t){return Me.map(e=>`${e}=${t(e)??""}`).join(`
60
+ `)}var M=class{enVuelo=new Map;primarias=0;gemelas=0;registrar(e){if(this.enVuelo.has(e))return null;this.primarias+=1;let n,r=new Promise(c=>n=c);this.enVuelo.set(e,{promesa:r,resolver:n});let o=[],s=0,
61
+ i=false;return {capturar:c=>{if(!i){if(s+=c.length,s>J){i=true,o.length=0;return}o.push(c);}},terminar:(c,u)=>{let a=!i&&c>=200&&c<300;this.enVuelo.get(e)?.resolver(a?{status:c,headers:u,chunks:o}:null),this.
62
+ enVuelo.delete(e);},abortar:()=>{this.enVuelo.get(e)?.resolver(null),this.enVuelo.delete(e);}}}async esperar(e){let n=this.enVuelo.get(e);if(!n)return null;let r=await n.promesa;return r&&(this.gemelas+=
63
+ 1),r}tieneEnVuelo(e){return this.enVuelo.has(e)}};var je=["content-encoding","content-length","transfer-encoding"];async function me(t={}){let e=(t.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=t.fetchImpl??fetch,r=t.log??(a=>console.log(
64
+ a)),o;if(t.serviceAccountKey){o=new w({url:t.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:t.serviceAccountKey,...t.fetchImpl?{fetchImpl:t.fetchImpl}:{},onAuthError:l=>y(
65
+ "gateway-ingest-auth",`[bvsala gateway] la ingesta de uso rechaza la credencial (${l}). El tr\xE1fico sigue fluyendo (avisar, nunca romper), pero este uso no est\xE1 llegando al panel. Revisa BIVELIO_SERVIC\
66
+ E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let a=D();t.seatId?o.setSeat(t.seatId,a):A(t.serviceAccountKey).then(l=>o?.setSeat(l,a)).catch(()=>{}),o.start();}else y("gateway-sin-\
67
+ 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=t.
68
+ coalescer?new M:null,i=(a,l,m,h,b)=>{if(!o||!m)return;let d="";try{d=JSON.parse(l.toString("utf8")).model??"";}catch{}let p=d?ae(d,m):null;p?(o.record(h?le(a,d,p):ce(a,d,p)),r(`[gateway] POST ${b} ${h?
69
+ "\xB7 COALESCIDA (llamada evitada)":""} \xB7 ${d} \xB7 $${p.costeUsd.toFixed(6)} (in ${p.tokens.entrada}, cache\u2191${p.tokens.cacheEscrita}${p.tokens.cacheEscrita1h?` (1h ${p.tokens.cacheEscrita1h})`:
70
+ ""} cache\u2193${p.tokens.cacheLeida}, out ${p.tokens.salida})`)):d&&y(`gateway-sin-precio:${d}`,`[bvsala gateway] el modelo "${d}" no est\xE1 en el cat\xE1logo de precios; su uso se reenv\xEDa pero no se mide\
71
+ .`);},c=createServer(async(a,l)=>{let m=randomUUID(),h=new AbortController;l.on("close",()=>{l.writableEnded||h.abort();});let b=null,d=new URL(a.url??"/",e);try{let p=[];for await(let f of a)p.push(f);let k=Buffer.concat(
72
+ p);d=new URL(a.url??"/",e);let C=s!==null&&a.method==="POST"&&k.length>0,z=pe(f=>{let g=a.headers[f];return Array.isArray(g)?g.join(", "):g}),R=C?de("POST",d.pathname+d.search,k,z):"";if(C)if(s.tieneEnVuelo(
73
+ R)){let f=await s.esperar(R);if(f){l.writeHead(f.status,f.headers);let g=new P;for(let re of f.chunks)l.write(re),g.observar(re);l.end();let xe=g.usage()??(d.pathname==="/v1/messages"?K(Buffer.concat(
74
+ f.chunks)):null);i(m,k,xe,!0,d.pathname);return}}else b=s.registrar(R);let ne={};for(let[f,g]of Object.entries(a.headers))f==="host"||f==="content-length"||g===void 0||(ne[f]=Array.isArray(g)?g.join("\
75
+ , "):g);let v;try{v=await n(d,{method:a.method,headers:ne,body:["GET","HEAD"].includes(a.method??"")?void 0:k,redirect:"manual",signal:h.signal});}catch(f){b?.abortar(),l.headersSent||(l.writeHead(502,
76
+ {"content-type":"application/json"}),l.end(JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(f)}))),r(`[gateway] ${a.method} ${d.pathname} \u2192 upstream inalcanzable: ${String(
77
+ f)}`);return}let B=Object.fromEntries(v.headers.entries());for(let f of je)delete B[f];l.writeHead(v.status,B);let F=(v.headers.get("content-type")??"").includes("text/event-stream"),V=F?new P:null,E=!F&&
78
+ v.ok&&o!==void 0&&a.method==="POST"&&d.pathname==="/v1/messages"&&(v.headers.get("content-type")??"").includes("application/json")?[]:null,oe=0;if(v.body)for await(let f of v.body){let g=Buffer.from(f);
79
+ l.write(g),V?.observar(g),E&&(oe+=g.length,oe>J?E=null:E.push(g)),b?.capturar(g);}l.end(),b?.terminar(v.status,B),V&&v.ok?i(m,k,V.usage(),!1,d.pathname):E?i(m,k,K(Buffer.concat(E)),!1,d.pathname):F||r(
80
+ `[gateway] ${a.method} ${d.pathname} \u2192 ${v.status}`);}catch(p){if(b?.abortar(),h.signal.aborted)r(`[gateway] ${a.method} ${d.pathname} \u2192 el cliente abort\xF3; upstream cancelado`);else if(r(`\
81
+ [gateway] ${a.method} ${d.pathname} \u2192 error sirviendo: ${String(p)}`),!l.headersSent){l.writeHead(502,{"content-type":"application/json"}),l.end(JSON.stringify({error:"bvsala gateway: stream inte\
82
+ rrupted",detail:String(p)}));return}l.writableEnded||l.end();}});return await new Promise((a,l)=>{c.once("error",l),c.listen(t.puerto??8402,"127.0.0.1",()=>a());}),{puerto:c.address().port,cerrar:async()=>{
83
+ await o?.stopAndDrain(),await new Promise(a=>c.close(()=>a()));}}}var H=class{id;exact=false;constructor(e="heuristic"){this.id=e;}count(e){if(e.length===0)return 0;let n=e.match(/[A-Za-z]+|\d+|\s+|[^\sA-Za-z\d]/g)??[],r=0;for(let o of n){let s=o[0];if(/\s/.test(s)){let i=(o.
84
+ 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,
85
+ Math.ceil(e.length/4))}},W=new H;var G=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(e=W){this.fallbackId=e.id,this.estimators.set(e.id,e);}register(e){return this.estimators.set(e.id,e),this}mapModel(e,n){return this.
86
+ modelToTokenizer.set(e,n),this}resolve(e){let n=e.tokenizer?this.estimators.get(e.tokenizer):void 0;if(n)return n;if(e.model){let r=this.modelToTokenizer.get(e.model);if(r){let o=this.estimators.get(r);
87
+ if(o)return o}}return this.estimators.get(this.fallbackId)}count(e,n){return this.resolve(n).count(e)}};new G;new TextEncoder;new TextDecoder;new TextDecoder;var Ze={"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}};
88
+ q(Ze);var Z="0.4.4";var[,,x,...N]=process.argv;function tt(){console.log(`bvsala ${Z} \u2014 BiVelio Savings Layer
9
89
 
10
90
  Comandos:
11
91
  gateway [--puerto N] Proxy local medidor para Claude Code (ANTHROPIC_BASE_URL).
@@ -18,4 +98,11 @@ Entorno:
18
98
  BVSALA_GATEWAY_COALESCE =1 enciende la coalescencia de duplicados (F2, experimental).
19
99
 
20
100
  El gateway nunca corta el tr\xE1fico: sin clave o con la ingesta ca\xEDda, reenv\xEDa y avisa.
21
- Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}x!=="gateway"&&x!=="claude"&&(Ye(),process.exit(x===void 0||x==="help"||x==="--help"?0:1));var Y=j.indexOf("--puerto"),$=x==="claude"?0:Y>=0?Number(j[Y+1]):8402;(!Number.isInteger($)||$<0||$>65535)&&(console.error(`[bvsala gateway] puerto inv\xE1lido: ${j[Y+1]}`),process.exit(1));var Q=process.env.BIVELIO_SERVICE_KEY,T=await le({puerto:$,...Q?{serviceAccountKey:Q}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.env.BVSALA_GATEWAY_UPSTREAM}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}});console.log(`[bvsala gateway] v${G} escuchando en http://127.0.0.1:${T.puerto}`);console.log(`[bvsala gateway] medici\xF3n: ${Q?"ACTIVA (clave de servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);if(x==="claude"){let t=spawn("claude",j,{stdio:"inherit",env:{...process.env,ANTHROPIC_BASE_URL:`http://127.0.0.1:${T.puerto}`}});t.on("error",e=>{console.error(`[bvsala claude] no se pudo lanzar \`claude\`: ${String(e)}. \xBFEst\xE1 instalado y en el PATH?`),T.cerrar().then(()=>process.exit(127));}),t.on("exit",(e,n)=>{T.cerrar().then(()=>process.exit(e??(n?1:0)));});}else console.log(`[bvsala gateway] apunta tu herramienta: ANTHROPIC_BASE_URL=http://127.0.0.1:${T.puerto} claude`);var ge=async()=>{await T.cerrar(),process.exit(0);};process.on("SIGINT",()=>{ge();});process.on("SIGTERM",()=>{ge();});
101
+ Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}x!=="gateway"&&x!=="claude"&&(tt(),process.exit(x===void 0||x==="help"||x==="--help"?0:1));var ee=N.
102
+ indexOf("--puerto"),j=x==="claude"?0:ee>=0?Number(N[ee+1]):8402;(!Number.isInteger(j)||j<0||j>65535)&&(console.error(`[bvsala gateway] puerto inv\xE1lido: ${N[ee+1]}`),process.exit(1));var te=process.
103
+ env.BIVELIO_SERVICE_KEY,T=await me({puerto:j,...te?{serviceAccountKey:te}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.
104
+ env.BVSALA_GATEWAY_UPSTREAM}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}});console.log(`[bvsal\
105
+ a gateway] v${Z} escuchando en http://127.0.0.1:${T.puerto}`);console.log(`[bvsala gateway] medici\xF3n: ${te?"ACTIVA (clave de servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);if(x==="cla\
106
+ ude"){let t=spawn("claude",N,{stdio:"inherit",env:{...process.env,ANTHROPIC_BASE_URL:`http://127.0.0.1:${T.puerto}`}});t.on("error",e=>{console.error(`[bvsala claude] no se pudo lanzar \`claude\`: ${String(
107
+ e)}. \xBFEst\xE1 instalado y en el PATH?`),T.cerrar().then(()=>process.exit(127));}),t.on("exit",(e,n)=>{T.cerrar().then(()=>process.exit(e??(n?1:0)));});}else console.log(`[bvsala gateway] apunta tu her\
108
+ ramienta: ANTHROPIC_BASE_URL=http://127.0.0.1:${T.puerto} claude`);var ye=async()=>{await T.cerrar(),process.exit(0);};process.on("SIGINT",()=>{ye();});process.on("SIGTERM",()=>{ye();});
package/dist/index.d.ts CHANGED
@@ -45,6 +45,10 @@ declare class SavingsLedger {
45
45
  actualOutput: number;
46
46
  eliminatedOutput: number;
47
47
  providerCachedInput: number;
48
+ /** Tokens the provider billed as prefix-cache writes. */
49
+ cacheWriteInputTokens: number;
50
+ /** The premium on those writes, already priced in USD. Adds to `actual` only. */
51
+ cacheWritePremiumUsd: number;
48
52
  /** Cuenta LOCAL del prompt que de verdad se envió al proveedor. */
49
53
  sentInputLocal: number;
50
54
  /** Tokens que la capa quitó de ese prompt, en cuenta LOCAL. */
@@ -98,6 +102,8 @@ declare class SavingsLedger {
98
102
  inputTokens: number;
99
103
  outputTokens: number;
100
104
  }, cheapPrice: CostModel): void;
105
+ /** Record the premium for `tokens` of cache write billed at `multiplicador`x. */
106
+ cacheWritePremium(tokens: number, multiplicador: number, precioDelQueMarco: CostModel): void;
101
107
  /**
102
108
  * Lo que de verdad hizo la compresión, para publicarlo en
103
109
  * `SavingsReport.compression`. El campo estaba declarado en el contrato y no lo
@@ -207,6 +213,24 @@ interface ChatMessage {
207
213
  content: string;
208
214
  /** Optional name (e.g. tool name for role: "tool"). */
209
215
  name?: string;
216
+ /**
217
+ * For `role: "tool"`: the id of the tool call this message answers.
218
+ *
219
+ * The provider tool loop needs it: OpenAI's Chat Completions rejects a
220
+ * `role: "tool"` message that does not carry `tool_call_id` and does not follow
221
+ * an assistant turn with `tool_calls` (a 400). Pass the `id` from the
222
+ * {@link ToolCallRequest} the model asked for. Ignored by providers that model
223
+ * tool results differently.
224
+ */
225
+ toolCallId?: string;
226
+ /**
227
+ * For `role: "assistant"`: the tool calls the model requested on that turn.
228
+ *
229
+ * Send it back with the assistant turn when you continue a tool conversation,
230
+ * so the following `role: "tool"` messages are valid. These are the same
231
+ * {@link ToolCallRequest}s that came back in {@link SavingsResult.toolCalls}.
232
+ */
233
+ toolCalls?: ToolCallRequest[];
210
234
  /** Opaque provider metadata; never used for the exact-cache key. */
211
235
  metadata?: Record<string, unknown>;
212
236
  }
@@ -1375,6 +1399,10 @@ interface SemanticCacheOptions {
1375
1399
  now?: () => number;
1376
1400
  }
1377
1401
  declare function cosine(a: number[], b: number[]): number;
1402
+ declare function numericTokens(s: string): string[];
1403
+ declare function multisetEqual(a: string[], b: string[]): boolean;
1404
+ declare function words(s: string): string[];
1405
+ declare function negationCount(tokens: string[]): number;
1378
1406
  /** True when two normalized queries agree on the facts an embedding tends to blur. */
1379
1407
  declare function keyTokensMatch(a: string, b: string): boolean;
1380
1408
  /**
@@ -1971,6 +1999,11 @@ declare function shouldCompress(input: CompressionPolicyInput): {
1971
1999
  declare function withProtectedSpans(inner: Compressor): Compressor;
1972
2000
  /** No-op compressor: the safe default until a real compressor sidecar is wired in. */
1973
2001
  declare const noopCompressor: Compressor;
2002
+ /**
2003
+ * Did the compression keep the facts that must not change? Verifies the RESULT —
2004
+ * number/date multiset and negation parity — not the compressor's own claim.
2005
+ */
2006
+ declare function compresionFiel(original: string, comprimido: string): boolean;
1974
2007
 
1975
2008
  /** What the layer needs to know about a provider's prefix cache to price a decision. */
1976
2009
  interface CacheTier {
@@ -3204,6 +3237,6 @@ declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"
3204
3237
  * constant to package.json so the two can never drift.
3205
3238
  */
3206
3239
 
3207
- declare const BV_SALA_VERSION = "0.4.3";
3240
+ declare const BV_SALA_VERSION = "0.4.4";
3208
3241
 
3209
- 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, 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, noopCompressor, normalizeText, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans };
3242
+ 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 };