@bivelio/savings-layer 0.4.6 → 0.4.7

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import {spawn}from'child_process';import {existsSync,readFileSync}from'fs';import {homedir}from'os';import {join,delimiter}from'path';import {createServer}from'http';import {randomUUID,createHash}from'crypto';import*as T from'zlib';var be=new Set;function w(e,t){be.has(e)||(be.add(e),console.warn(t));}var I=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}from'fs';import {homedir}from'os';import {join,delimiter}from'path';import {createServer}from'http';import {randomUUID,createHash}from'crypto';import*as R from'zlib';var xe=new Set;function T(e,t){xe.has(e)||(xe.add(e),console.warn(t));}var _=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??
@@ -7,7 +7,7 @@ Math.ceil(this.maxBuffer/this.maxBatch)+4,o=0;for(let r=0;r<n&&this.buffer.lengt
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
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
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?(w(`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\
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
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
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
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.
@@ -16,16 +16,17 @@ fico y los asientos facturables saldr\xE1n a cero. Comprueba 'meteringUrl' \u201
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
- cache.result,baselineCost:n.cost.baselineEstimate,actualCost:n.cost.actual,netSavings:n.cost.netSavings,...n.cost.basis?{basis:n.cost.basis}:{}}}};function B(){return globalThis.process}function Ue(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]";
20
- return new RegExp(`-${t}{8,10}-${t}{5}$`).test(e)}function qe(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??
21
- e.NETLIFY)}async function Ke(){if(B()?.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 He(e,t=Ke){
22
- if(e.KUBERNETES_SERVICE_HOST||qe(e))return;let n=e.HOSTNAME??e.COMPUTERNAME??await t();if(!(!n||Ue(n)))return n}async function Je(){let e=B();if(!e||!e.env)return;let t=e.env,n=await He(t),o=[e.platform,
23
- 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 We=["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 V(){let e=B()?.env;if(!e)
25
- return false;for(let t of We){let n=e[t];if(n&&n!=="false"&&n!=="0")return true}return false}async function Ge(e,t){let n=globalThis.crypto,o=new TextEncoder;if(n?.subtle)try{let i=await n.subtle.importKey("raw",
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 V(){return globalThis.process}function Je(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 We(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 Ge(){if(V()?.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 Ye(e,t=Ge){
23
+ if(e.KUBERNETES_SERVICE_HOST||We(e))return;let n=e.HOSTNAME??e.COMPUTERNAME??await t();if(!(!n||Je(n)))return n}async function Qe(){let e=V();if(!e||!e.env)return;let t=e.env,n=await Ye(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 Xe=["CI","CONTINUOUS_INTEGRATION","GITHUB_ACTI\
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 F(){let e=V()?.env;if(!e)
26
+ return false;for(let t of Xe){let n=e[t];if(n&&n!=="false"&&n!=="0")return true}return false}async function Ze(e,t){let n=globalThis.crypto,o=new TextEncoder;if(n?.subtle)try{let i=await n.subtle.importKey("raw",
26
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}\
27
- |${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 F(e){let n=B()?.env?.BVSALA_SEAT_ID;if(n&&Ye.test(n))return n;let o=await Je();
28
- if(!o)return;let r=await Ge(e,o);if(r)return `m-${r.slice(0,32)}`}var Ye=/^[A-Za-z0-9._:/-]{1,128}$/;var Q={"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},
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 U(e){let n=V()?.env?.BVSALA_SEAT_ID;if(n&&et.test(n))return n;let o=await Qe();
29
+ if(!o)return;let r=await Ze(e,o);if(r)return `m-${r.slice(0,32)}`}var et=/^[A-Za-z0-9._:/-]{1,128}$/;var ee={"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
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,
30
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},
31
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},
@@ -41,72 +42,74 @@ able-5":{inputPer1k:.01,outputPer1k:.05,cachedInputPer1k:.001},"claude-sonnet-4"
41
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,
42
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":{
43
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},
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 ve(e,t,n){if(!n)return;let o=e[n];if(o)
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 we(e,t,n){if(!n)return;let o=e[n];if(o)
45
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.
46
- slice(a.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[a]}}function X(e){return Object.keys(e).sort((t,n)=>n.length-t.length)}var Qe=X(Q);function M(e){return ve(Q,Qe,e)}var $=class{resto="";uso=null;eventos={};observar(t){this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
47
+ slice(a.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[a]}}function te(e){return Object.keys(e).sort((t,n)=>n.length-t.length)}var tt=te(ee);function $(e){return we(ee,tt,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(`
47
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;
49
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;
50
- 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}},U=class{resto="";uso=null;eventos={};observar(t){
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}},q=class{resto="";uso=null;eventos={};observar(t){
51
52
  this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
52
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.
54
- parse(s);if((r??i.type)!=="response.completed")continue;let a=i.response?.usage;a&&(this.uso=Xe(a));}catch{}}}usage(){return this.uso}usageFinal(){return this.uso}};function Xe(e){let t=Math.max(0,e.input_tokens??
55
+ parse(s);if((r??i.type)!=="response.completed")continue;let a=i.response?.usage;a&&(this.uso=nt(a));}catch{}}}usage(){return this.uso}usageFinal(){return this.uso}};function nt(e){let t=Math.max(0,e.input_tokens??
55
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??
56
- 0),cache_read_input_tokens:n,cache_creation_input_tokens:o}}var ye=1.25,Ze=2;function ke(e,t){let n=M(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.
57
+ 0),cache_read_input_tokens:n,cache_creation_input_tokens:o}}var Se=1.25,ot=2;function Te(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.
57
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 {
58
- costeUsd:o/1e3*n.inputPer1k+s/1e3*p+(a-l)/1e3*n.inputPer1k*tt(e)+l/1e3*n.inputPer1k*Ze+r/1e3*n.outputPer1k,precio:n,tokens:{entrada:o,salida:r,cacheLeida:s,cacheEscrita:a,cacheEscrita1h:l}}}function xe(e,t){
59
- try{let n=e,o={maxOutputLength:et};switch((t??"").trim().toLowerCase()){case "gzip":case "x-gzip":n=T.gunzipSync(e,o);break;case "deflate":n=T.inflateSync(e,o);break;case "br":n=T.brotliDecompressSync(e,o);
60
- break;case "zstd":{let s=T.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 et=5*
61
- 1024*1024;function we(){return typeof T.zstdDecompressSync=="function"}function tt(e){let t=e.toLowerCase();return t.startsWith("gpt-")||t.startsWith("o1")||t.startsWith("o3")||t.startsWith("o4")?/^gpt-5\.6/.
62
- test(t)?ye:1:t.startsWith("gemini")?1:ye}function Z(e){try{return JSON.parse(typeof e=="string"?e:e.toString("utf8")).usage??null}catch{return null}}function Se(e,t,n){return Ce(e,t,n,false)}function Te(e,t,n){
63
- return Ce(e,t,n,true)}function Ce(e,t,n,o){let r=n.tokens.entrada+n.tokens.cacheLeida+n.tokens.cacheEscrita;return {requestId:e,model:t,calls:o?{avoidedLlm:1,actualLlm:0}:{avoidedLlm:0,actualLlm:1},tokens:{
64
- baselineInput:r,actualInput:o?0:r,eliminatedInput:0,compressedInput:0,baselineOutput:n.tokens.salida,actualOutput:o?0:n.tokens.salida,providerCachedInput:o?0:n.tokens.cacheLeida},cache:{result:o?"sing\
65
- leflight":"miss"},cost:o?{baselineEstimate:n.costeUsd,actual:0,netSavings:1,basis:"estimated"}:{baselineEstimate:n.costeUsd,actual:n.costeUsd,netSavings:0,basis:"measured"}}}var ee=5*1024*1024,ot=["authorization","x-api-key","anthropic-version","anthropic-beta"];function Re(e,t,n,o=""){return createHash("sha256").update(e).update(`
59
+ costeUsd:o/1e3*n.inputPer1k+s/1e3*p+(a-l)/1e3*n.inputPer1k*st(e)+l/1e3*n.inputPer1k*ot+r/1e3*n.outputPer1k,precio:n,tokens:{entrada:o,salida:r,cacheLeida:s,cacheEscrita:a,cacheEscrita1h:l}}}function Ce(e,t){
60
+ try{let n=e,o={maxOutputLength:rt};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 rt=5*
62
+ 1024*1024;function Re(){return typeof R.zstdDecompressSync=="function"}function st(e){let t=e.toLowerCase();return t.startsWith("gpt-")||t.startsWith("o1")||t.startsWith("o3")||t.startsWith("o4")?/^gpt-5\.6/.
63
+ test(t)?Se:1:t.startsWith("gemini")?1:Se}function ne(e){try{return JSON.parse(typeof e=="string"?e:e.toString("utf8")).usage??null}catch{return null}}function Ee(e,t,n,o){return Pe(e,t,n,false,o)}function Ie(e,t,n,o){
64
+ return Pe(e,t,n,true,o)}function Pe(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 oe=5*1024*1024,at=["authorization","x-api-key","anthropic-version","anthropic-beta"];function _e(e,t,n,o=""){return createHash("sha256").update(e).update(`
66
68
  `).update(t).update(`
67
69
  `).update(o).update(`
68
- `).update(n).digest("hex")}function Ee(e){return ot.map(t=>`${t}=${e(t)??""}`).join(`
69
- `)}var q=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,
70
- i=false;return {capturar:a=>{if(!i){if(s+=a.length,s>ee){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.
70
+ `).update(n).digest("hex")}function Ae(e){return at.map(t=>`${t}=${e(t)??""}`).join(`
71
+ `)}var K=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>oe){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.
71
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+=
72
- 1),o}tieneEnVuelo(t){return this.enVuelo.has(t)}};var it=["content-encoding","content-length","transfer-encoding"];async function Ie(e={}){let t=(e.upstream??"https://api.anthropic.com").replace(/\/+$/,""),n=e.fetchImpl??fetch,o=e.log??(c=>console.log(
73
- c)),r;if(e.serviceAccountKey){r=new I({url:e.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},onAuthError:u=>w(
74
+ 1),o}tieneEnVuelo(t){return this.enVuelo.has(t)}};var ut=["content-encoding","content-length","transfer-encoding"];async function Oe(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 _({url:e.meteringUrl??"https://savings.bivelio.com/api/metering/usage",serviceAccountKey:e.serviceAccountKey,...e.fetchImpl?{fetchImpl:e.fetchImpl}:{},onAuthError:u=>T(
74
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\
75
- E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=V();e.seatId?r.setSeat(e.seatId,c):F(e.serviceAccountKey).then(u=>r?.setSeat(u,c)).catch(()=>{}),r.start();}else w("gateway-sin-\
77
+ E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=F();e.seatId?r.setSeat(e.seatId,c):U(e.serviceAccountKey).then(u=>r?.setSeat(u,c)).catch(()=>{}),r.start();}else T("gateway-sin-\
76
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.
77
- coalescer?new q:null,i=c=>c==="/v1/messages"?new $:c.endsWith("/responses")?new U:null,a=(c,u,f,b,m,d)=>{if(!r||!f)return;let h=xe(u,d);if(!h){w(`gateway-sin-modelo:${m}`,`[bvsala gateway] no se pudo \
78
- leer el modelo del cuerpo de ${m} (content-encoding: ${d??"ninguno"}); su uso se reenv\xEDa pero no se mide.`);return}let k=ke(h,f);k?(r.record(b?Te(c,h,k):Se(c,h,k)),o(`[gateway] POST ${m} ${b?"\xB7 COA\
79
- LESCIDA (llamada evitada)":""} \xB7 ${h} \xB7 $${k.costeUsd.toFixed(6)} (in ${k.tokens.entrada}, cache\u2191${k.tokens.cacheEscrita}${k.tokens.cacheEscrita1h?` (1h ${k.tokens.cacheEscrita1h})`:""} cac\
80
- he\u2193${k.tokens.cacheLeida}, out ${k.tokens.salida})`)):w(`gateway-sin-precio:${h}`,`[bvsala gateway] el modelo "${h}" no est\xE1 en el cat\xE1logo de precios; su uso se reenv\xEDa pero no se mide.`);},
81
- 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),h=null,k=Buffer.alloc(0),N=false,O=c.headers["content-encoding"],z=Array.
82
- isArray(O)?O[0]:O;try{let D=[];for await(let g of c)D.push(g);let S=Buffer.concat(D);k=S,d=new URL(c.url??"/",t);let pe=s!==null&&c.method==="POST"&&S.length>0,Ve=Ee(g=>{let v=c.headers[g];return Array.
83
- isArray(v)?v.join(", "):v}),W=pe?Re("POST",d.pathname+d.search,S,Ve):"";if(pe)if(s.tieneEnVuelo(W)){let g=await s.esperar(W);if(g){u.writeHead(g.status,g.headers);let v=i(d.pathname)??new $;for(let ge of g.
84
- chunks)u.write(ge),v.observar(ge);u.end();let Fe=v.usage()??(d.pathname==="/v1/messages"?Z(Buffer.concat(g.chunks)):null);a(f,S,Fe,!0,d.pathname,z);return}}else m=s.registrar(W);let me={};for(let[g,v]of Object.
85
- entries(c.headers))g==="host"||g==="content-length"||v===void 0||(me[g]=Array.isArray(v)?v.join(", "):v);let x;try{x=await n(d,{method:c.method,headers:me,body:["GET","HEAD"].includes(c.method??"")?void 0:
86
- S,redirect:"manual",signal:b.signal});}catch(g){m?.abortar(),u.headersSent||(u.writeHead(502,{"content-type":"application/json"}),u.end(JSON.stringify({error:"bvsala gateway: upstream unreachable",detail:String(
87
- g)}))),o(`[gateway] ${c.method} ${d.pathname} \u2192 upstream inalcanzable: ${String(g)}`);return}let G=Object.fromEntries(x.headers.entries());for(let g of it)delete G[g];u.writeHead(x.status,G);let fe=x.
88
- headers.get("content-type")??"",Y=fe.includes("text/event-stream")||fe===""&&x.ok&&i(d.pathname)!==null;h=Y?i(d.pathname):null;let L=!Y&&x.ok&&r!==void 0&&c.method==="POST"&&d.pathname==="/v1/messages"&&
89
- (x.headers.get("content-type")??"").includes("application/json")?[]:null,he=0;if(x.body)for await(let g of x.body){let v=Buffer.from(g);u.write(v),h?.observar(v),L&&(he+=v.length,he>ee?L=null:L.push(v)),
90
- m?.capturar(v);}u.end(),m?.terminar(x.status,G),h&&x.ok?(N=!0,a(f,S,h.usage(),!1,d.pathname,z)):L?a(f,S,Z(Buffer.concat(L)),!1,d.pathname,z):Y||o(`[gateway] ${c.method} ${d.pathname} \u2192 ${x.status}`);}catch(D){
91
- if(m?.abortar(),b.signal.aborted){let S=!N&&h?h.usageFinal():null;S?(o(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente cerr\xF3 tras el usage final; se mide igual`),a(f,k,S,false,d.pathname,z)):o(
92
- `[gateway] ${c.method} ${d.pathname} \u2192 el cliente abort\xF3; upstream cancelado`);}else if(o(`[gateway] ${c.method} ${d.pathname} \u2192 error sirviendo: ${String(D)}`),!u.headersSent){u.writeHead(
93
- 502,{"content-type":"application/json"}),u.end(JSON.stringify({error:"bvsala gateway: stream interrupted",detail:String(D)}));return}u.writableEnded||u.end();}});return l.on("upgrade",(c,u)=>{o(`[gatew\
94
- ay] ${c.method} ${new URL(c.url??"/","http://x").pathname} \u2192 426 (WebSocket no soportado; el cliente cae a HTTP)`),u.once("error",()=>{}),u.once("finish",()=>u.destroy()),u.end(`HTTP/1.1 426 Upgr\
95
- ade Required\r
79
+ coalescer?new K:null,i=c=>c==="/v1/messages"?new j:c.endsWith("/responses")?new q:null,a=(c,u,f,g,m,d,y)=>{if(!r||!f)return;let w=Ce(u,d);if(!w){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=Te(w,f);k?(r.record(g?Ie(c,w,k,y):Ee(c,w,k,y)),o(`[gateway] POST ${m} ${g?
81
+ "\xB7 COALESCIDA (llamada evitada)":""} \xB7 ${w} \xB7 $${k.costeUsd.toFixed(6)} (in ${k.tokens.entrada}, cache\u2191${k.tokens.cacheEscrita}${k.tokens.cacheEscrita1h?` (1h ${k.tokens.cacheEscrita1h})`:
82
+ ""} cache\u2193${k.tokens.cacheLeida}, out ${k.tokens.salida})`)):T(`gateway-sin-precio:${w}`,`[bvsala gateway] el modelo "${w}" 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(),g=new AbortController;u.on("close",()=>{u.writableEnded||g.abort();});let m=null,d=new URL(c.url??"/",t),y=null,w=Buffer.alloc(0),k=false,L=c.headers["content-encoding"],z=Array.
84
+ isArray(L)?L[0]:L,he=C=>{let x=Array.isArray(C)?C[0]:C;return x!==void 0&&/^[A-Za-z0-9._:/-]{1,128}$/.test(x)?x:void 0},G=he(c.headers["x-claude-code-session-id"]),Y=he(c.headers["x-claude-code-agent-\
85
+ id"]),B=G!==void 0||Y!==void 0?{...G!==void 0?{sessionId:G}:{},...Y!==void 0?{agentId:Y}:{}}:void 0;try{let C=[];for await(let h of c)C.push(h);let x=Buffer.concat(C);w=x,d=new URL(c.url??"/",t);let ge=s!==
86
+ null&&c.method==="POST"&&x.length>0,Ke=Ae(h=>{let b=c.headers[h];return Array.isArray(b)?b.join(", "):b}),Q=ge?_e("POST",d.pathname+d.search,x,Ke):"";if(ge)if(s.tieneEnVuelo(Q)){let h=await s.esperar(
87
+ Q);if(h){u.writeHead(h.status,h.headers);let b=i(d.pathname)??new j;for(let ke of h.chunks)u.write(ke),b.observar(ke);u.end();let He=b.usage()??(d.pathname==="/v1/messages"?ne(Buffer.concat(h.chunks)):
88
+ null);a(f,x,He,!0,d.pathname,z,B);return}}else m=s.registrar(Q);let be={};for(let[h,b]of Object.entries(c.headers))h==="host"||h==="content-length"||b===void 0||(be[h]=Array.isArray(b)?b.join(", "):b);
89
+ let S;try{S=await n(d,{method:c.method,headers:be,body:["GET","HEAD"].includes(c.method??"")?void 0:x,redirect:"manual",signal:g.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 X=Object.fromEntries(S.headers.entries());for(let h of ut)delete X[h];u.writeHead(S.status,X);let ve=S.headers.get("content-type")??"",Z=ve.includes("text/event-stream")||ve===""&&S.ok&&i(d.
92
+ pathname)!==null;y=Z?i(d.pathname):null;let M=!Z&&S.ok&&r!==void 0&&c.method==="POST"&&d.pathname==="/v1/messages"&&(S.headers.get("content-type")??"").includes("application/json")?[]:null,ye=0;if(S.body)
93
+ for await(let h of S.body){let b=Buffer.from(h);u.write(b),y?.observar(b),M&&(ye+=b.length,ye>oe?M=null:M.push(b)),m?.capturar(b);}u.end(),m?.terminar(S.status,X),y&&S.ok?(k=!0,a(f,x,y.usage(),!1,d.pathname,
94
+ z,B)):M?a(f,x,ne(Buffer.concat(M)),!1,d.pathname,z,B):Z||o(`[gateway] ${c.method} ${d.pathname} \u2192 ${S.status}`);}catch(C){if(m?.abortar(),g.signal.aborted){let x=!k&&y?y.usageFinal():null;x?(o(`[g\
95
+ ateway] ${c.method} ${d.pathname} \u2192 el cliente cerr\xF3 tras el usage final; se mide igual`),a(f,w,x,false,d.pathname,z,B)):o(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente abort\xF3; upstream cance\
96
+ lado`);}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:"bvsala ga\
97
+ teway: 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 (WebSocket no \
98
+ soportado; el cliente cae a HTTP)`),u.once("error",()=>{}),u.once("finish",()=>u.destroy()),u.end(`HTTP/1.1 426 Upgrade Required\r
96
99
  connection: close\r
97
100
  content-length: 0\r
98
101
  \r
99
- `);}),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 te=["claude","codex","goose","qwen"];function _e(e){return te.includes(e??"")}function Ae(e,t={}){let n=t.path??process.env.PATH??"",o=t.
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 re=["claude","codex","goose","qwen"];function Le(e){return re.includes(e??"")}function Me(e,t={}){let n=t.path??process.env.PATH??"",o=t.
100
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+
101
- i);if(o(a)){r=a;break}}}return r!==null?{tipo:"solo-shim",ruta:r}:{tipo:"no-encontrado"}}function Oe(e={}){let n=(e.env??process.env).BVSALA_CODEX_AUTH?.toLowerCase();if(n==="api-key"||n==="apikey")return "\
104
+ i);if(o(a)){r=a;break}}}return r!==null?{tipo:"solo-shim",ruta:r}:{tipo:"no-encontrado"}}function $e(e={}){let n=(e.env??process.env).BVSALA_CODEX_AUTH?.toLowerCase();if(n==="api-key"||n==="apikey")return "\
102
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.
103
- toLowerCase().replace(/[^a-z]/g,"")==="apikey"?"api-key":"chatgpt":r.tokens!==void 0&&r.tokens!==null?"chatgpt":"api-key"}catch{return "api-key"}}var ct={"api-key":{upstream:"https://api.openai.com",prefijo:"\
104
- /v1"},chatgpt:{upstream:"https://chatgpt.com",prefijo:"/backend-api/codex"}};function ne(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 "\
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 pt={"api-key":{upstream:"https://api.openai.com",prefijo:"\
107
+ /v1"},chatgpt:{upstream:"https://chatgpt.com",prefijo:"/backend-api/codex"}};function se(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 "\
105
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\
106
- "` en ~/.qwen/settings.json (ver `bvsala doctor`).'};case "codex":{let s=o.modoCodex??"api-key",i=ct[s];return {bin:"codex",args:["-c",`openai_base_url="${r}${i.prefijo}"`,...n],env:{},upstream:i.upstream,
109
+ "` en ~/.qwen/settings.json (ver `bvsala doctor`).'};case "codex":{let s=o.modoCodex??"api-key",i=pt[s];return {bin:"codex",args:["-c",`openai_base_url="${r}${i.prefijo}"`,...n],env:{},upstream:i.upstream,
107
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\
108
- tokens.":"Auth por API key: el gateway enruta a api.openai.com/v1."}}}}var pt="comprobar: en el log del gateway debe aparecer `[gateway] POST /v1/messages \u2026`; si ve\
109
- s `/v1/v1/messages`, sobra un /v1; si ves `POST /messages` a secas, falta uno.",mt=[{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 gt="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.",bt=[{id:"claude",nombre:"Claude Code",binario:"claude",configs:[],detectable:true,receta:({base:e})=>["usa `bvsala claude`\
110
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\
111
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 \
112
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\
@@ -122,21 +125,21 @@ Droid (Factory)",binario:"droid",configs:[join(".factory","settings.json")],dete
122
125
  idar en mano contra el log del gateway].`,"{",' "customModels": ['," {",' "model": "<id de modelo Anthropic>",',' "displayName": "<nombre visible>",',' "provider": "anthropic",',` \
123
126
  "baseUrl": "${e}",`,' "apiKey": "<tu API key de Anthropic>"'," }"," ]","}"]},{id:"cline",nombre:"Cline",configs:[],detectable:false,receta:({baseV1:e})=>["mecanismo: UI \u2192 proveedor Anthropic\
124
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,
125
- 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 ft(e,t){
126
- 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 De(e,t={}){if(!Number.isInteger(e)||e<1||
127
- e>65535)throw new RangeError(`[bvsala doctor] puerto inv\xE1lido: ${String(e)}`);let n=t.home??homedir(),o=t.existe??existsSync,r=t.enPath??ft(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 vt(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 je(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??vt(t.path??process.env.PATH??"",o),s=`http://127.0.0.1:${e}`,i=`${s}/v1`,
128
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 \
129
- se lee el contenido de ning\xFAn archivo.",""];for(let p of mt){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?
130
- b?m=`detectado \u2014 ${[...u?[`binario \`${p.binario}\` en PATH`]:[],...f.map(h=>`config presente: ${h}`)].join("; ")}`:m="no detectado":m="sin detecci\xF3n autom\xE1tica (extensi\xF3n de editor)",l.
131
- 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(` ${pt}`),l.push("");}return l.push("Regla BYOK: las recetas del anillo Anth\
132
+ se lee el contenido de ning\xFAn archivo.",""];for(let p of bt){let c=p.configs.map(d=>join(n,d)),u=p.binario!==void 0&&r(p.binario),f=c.filter(d=>o(d)),g=u||f.length>0;g&&a.push(p.id);let m;p.detectable?
133
+ g?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(` ${gt}`),l.push("");}return l.push("Regla BYOK: las recetas del anillo Anth\
132
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 \
133
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(`
134
- `),detectados:a}}var oe=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.
137
+ `),detectados:a}}var ie=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.
135
138
  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,
136
- Math.ceil(t.length/4))}},re=new oe;var se=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(t=re){this.fallbackId=t.id,this.estimators.set(t.id,t);}register(t){return this.estimators.set(t.id,t),this}mapModel(t,n){
139
+ Math.ceil(t.length/4))}},ae=new ie;var ce=class{estimators=new Map;modelToTokenizer=new Map;fallbackId;constructor(t=ae){this.fallbackId=t.id,this.estimators.set(t.id,t);}register(t){return this.estimators.set(t.id,t),this}mapModel(t,n){
137
140
  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.
138
- get(o);if(r)return r}}return this.estimators.get(this.fallbackId)}count(t,n){return this.resolve(n).count(t)}};new se;new TextEncoder;new TextDecoder;new TextDecoder;var _t={"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}};
139
- X(_t);var le="0.4.6";var[,,y,...A]=process.argv;function Mt(){console.log(`bvsala ${le} \u2014 BiVelio Savings Layer
141
+ get(o);if(r)return r}}return this.estimators.get(this.fallbackId)}count(t,n){return this.resolve(n).count(t)}};new ce;new TextEncoder;new TextDecoder;new TextDecoder;var Lt={"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}};
142
+ te(Lt);var pe="0.4.7";var[,,v,...D]=process.argv;function zt(){console.log(`bvsala ${pe} \u2014 BiVelio Savings Layer
140
143
 
141
144
  Comandos:
142
145
  gateway [--puerto N] Proxy local medidor (por defecto 8402) para terminales que
@@ -157,19 +160,19 @@ Entorno:
157
160
  CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 (Claude Code, solo API key) tus modelos en /model.
158
161
 
159
162
  El gateway nunca corta el tr\xE1fico: sin clave o con la ingesta ca\xEDda, reenv\xEDa y avisa.
160
- Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var J=_e(y);y!=="gateway"&&y!=="doctor"&&!J&&(Mt(),process.exit(y===void 0||y==="help"||y==="--he\
161
- lp"?0:1));var ue=A.indexOf("--puerto"),_=J?0:ue>=0?Number(A[ue+1]):8402;(!Number.isInteger(_)||(y==="doctor"?_<1:_<0)||_>65535)&&(console.error(`[bvsala ${y}] puerto inv\xE1lido: ${A[ue+1]}`),process.
162
- exit(1));if(y==="doctor"){A.includes("--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 De(_);console.
163
- log(e.texto),process.exit(0);}var R=J?ne(y,0,A,{modoCodex:y==="codex"?Oe({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0}):null,de=process.env.BIVELIO_SERVICE_KEY,E=await Ie({puerto:_,...de?
164
- {serviceAccountKey:de}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.env.BVSALA_GATEWAY_UPSTREAM}:R?.upstream?{upstream:R.
165
- upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}}),$t=process.env.BVSALA_GATEWAY_UPSTREAM??
166
- R?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway] v${le} escuchando en http://127.0.0.1:${E.puerto} \u2192 ${$t}`);console.log(`[bvsala gateway] medici\xF3n: ${de?"ACTIVA (clave d\
167
- e servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);process.env.BVSALA_GATEWAY_UPSTREAM&&R?.upstream&&process.env.BVSALA_GATEWAY_UPSTREAM!==R.upstream&&console.error(`[bvsala ${y}] AVISO: B\
168
- VSALA_GATEWAY_UPSTREAM=${process.env.BVSALA_GATEWAY_UPSTREAM} pisa el upstream de este terminal (${R.upstream}). Si no es lo que quieres, quita la variable.`);y==="codex"&&!we()&&console.error(`[bvsal\
169
- 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(R&&J){let e=ne(y,E.puerto,A,{...R.upstream?
170
- {modoCodex:R.upstream.includes("chatgpt.com")?"chatgpt":"api-key"}:{}});e.nota&&console.log(`[bvsala ${y}] ${e.nota}`);let t=e.bin;if(process.platform==="win32"){let o=Ae(e.bin);o.tipo==="exe"?t=o.ruta:
171
- o.tipo==="solo-shim"&&(console.error(`[bvsala ${y}] \`${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\
172
- on su instalador nativo (deja un ${e.bin}.exe en el PATH) o usa la receta manual de docs/terminales/.`),await E.cerrar(),process.exit(127));}let n=spawn(t,e.args,{stdio:"inherit",env:{...process.env,...e.
173
- env}});n.on("error",o=>{console.error(`[bvsala ${y}] no se pudo lanzar \`${e.bin}\`: ${String(o)}. \xBFEst\xE1 instalado y en el PATH?`),E.cerrar().then(()=>process.exit(127));}),n.on("exit",(o,r)=>{E.
174
- cerrar().then(()=>process.exit(o??(r?1:0)));});}else console.log(`[bvsala gateway] apunta tu terminal (o usa \`bvsala ${te.join("|")}\`): ANTHROPIC_BASE_URL=http://127.0.0.1:${E.puerto} claude \xB7 bv\
175
- sala doctor para el resto`);var Be=async()=>{await E.cerrar(),process.exit(0);};process.on("SIGINT",()=>{Be();});process.on("SIGTERM",()=>{Be();});
163
+ Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var W=Le(v);v!=="gateway"&&v!=="doctor"&&!W&&(zt(),process.exit(v===void 0||v==="help"||v==="--he\
164
+ lp"?0:1));var me=D.indexOf("--puerto"),O=W?0:me>=0?Number(D[me+1]):8402;(!Number.isInteger(O)||(v==="doctor"?O<1:O<0)||O>65535)&&(console.error(`[bvsala ${v}] puerto inv\xE1lido: ${D[me+1]}`),process.
165
+ exit(1));if(v==="doctor"){D.includes("--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 je(O);console.
166
+ log(e.texto),process.exit(0);}var I=W?se(v,0,D,{modoCodex:v==="codex"?$e({leerAuth:()=>readFileSync(join(homedir(),".codex","auth.json"),"utf8")}):void 0}):null,fe=process.env.BIVELIO_SERVICE_KEY,P=await Oe({puerto:O,...fe?
167
+ {serviceAccountKey:fe}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.env.BVSALA_GATEWAY_UPSTREAM}:I?.upstream?{upstream:I.
168
+ upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:{}}),Bt=process.env.BVSALA_GATEWAY_UPSTREAM??
169
+ I?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway] v${pe} escuchando en http://127.0.0.1:${P.puerto} \u2192 ${Bt}`);console.log(`[bvsala gateway] medici\xF3n: ${fe?"ACTIVA (clave d\
170
+ 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 ${v}] AVISO: B\
171
+ 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.`);v==="codex"&&!Re()&&console.error(`[bvsal\
172
+ 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&&W){let e=se(v,P.puerto,D,{...I.upstream?
173
+ {modoCodex:I.upstream.includes("chatgpt.com")?"chatgpt":"api-key"}:{}});e.nota&&console.log(`[bvsala ${v}] ${e.nota}`);let t=e.bin;if(process.platform==="win32"){let o=Me(e.bin);o.tipo==="exe"?t=o.ruta:
174
+ o.tipo==="solo-shim"&&(console.error(`[bvsala ${v}] \`${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\
175
+ on su instalador nativo (deja un ${e.bin}.exe en el PATH) o usa la receta manual de docs/terminales/.`),await P.cerrar(),process.exit(127));}let n=spawn(t,e.args,{stdio:"inherit",env:{...process.env,...e.
176
+ env}});n.on("error",o=>{console.error(`[bvsala ${v}] no se pudo lanzar \`${e.bin}\`: ${String(o)}. \xBFEst\xE1 instalado y en el PATH?`),P.cerrar().then(()=>process.exit(127));}),n.on("exit",(o,r)=>{P.
177
+ cerrar().then(()=>process.exit(o??(r?1:0)));});}else console.log(`[bvsala gateway] apunta tu terminal (o usa \`bvsala ${re.join("|")}\`): ANTHROPIC_BASE_URL=http://127.0.0.1:${P.puerto} claude \xB7 bv\
178
+ sala doctor para el resto`);var qe=async()=>{await P.cerrar(),process.exit(0);};process.on("SIGINT",()=>{qe();});process.on("SIGTERM",()=>{qe();});
package/dist/index.d.ts CHANGED
@@ -748,6 +748,19 @@ interface SavingsReport {
748
748
  * for. Changing your model is never silent.
749
749
  */
750
750
  model: string;
751
+ /**
752
+ * Optional terminal attribution (gateway): the session and subagent that
753
+ * originated this request, taken from the `x-claude-code-session-id` /
754
+ * `x-claude-code-agent-id` headers Claude Code already sends. These are
755
+ * OPAQUE session/agent identities — they must never be treated as a user
756
+ * identifier (Anthropic's gateway docs forbid it) — and they travel under
757
+ * the same identity charset gate as `seatId`: counts and ids only, never
758
+ * content.
759
+ */
760
+ attribution?: {
761
+ sessionId?: string;
762
+ agentId?: string;
763
+ };
751
764
  calls: {
752
765
  baselineLlm: number;
753
766
  actualLlm: number;
@@ -3237,6 +3250,6 @@ declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"
3237
3250
  * constant to package.json so the two can never drift.
3238
3251
  */
3239
3252
 
3240
- declare const BV_SALA_VERSION = "0.4.6";
3253
+ declare const BV_SALA_VERSION = "0.4.7";
3241
3254
 
3242
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
@@ -445,7 +445,8 @@ fico y los asientos facturables saldr\xE1n a cero. Comprueba 'meteringUrl' \u201
445
445
  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,
446
446
  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,
447
447
  compressedInputTokens:n.tokens.compressedInput,baselineOutputTokens:n.tokens.baselineOutput,actualOutputTokens:n.tokens.actualOutput,providerCachedInputTokens:n.tokens.providerCachedInput??0,cacheResult:n.
448
- cache.result,baselineCost:n.cost.baselineEstimate,actualCost:n.cost.actual,netSavings:n.cost.netSavings,...n.cost.basis?{basis:n.cost.basis}:{}}}};function Gt(){return globalThis.process}function Ma(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\
448
+ 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}:
449
+ {},...n.attribution?.agentId?{agentId:n.attribution.agentId}:{}}}};function Gt(){return globalThis.process}function Ma(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\
449
450
  ]";return new RegExp(`-${e}{8,10}-${e}{5}$`).test(t)}function Ba(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??
450
451
  t.NETLIFY)}async function ja(){if(Gt()?.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 Na(t,e=ja){
451
452
  if(t.KUBERNETES_SERVICE_HOST||Ba(t))return;let n=t.HOSTNAME??t.COMPUTERNAME??await e();if(!(!n||Ma(n)))return n}async function $a(){let t=Gt();if(!t||!t.env)return;let e=t.env,n=await Na(e),r=[t.platform,
@@ -781,7 +782,7 @@ ng/usage`:void 0);if(a){i=new dt({url:a,serviceAccountKey:t.serviceAccountKey,..
781
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.
782
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)
783
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{}
784
- return c},close:()=>{r?.stop(),o?.stop(),i?.stop();}}}var af="0.4.6";/*! Bundled license information:
785
+ return c},close:()=>{r?.stop(),o?.stop(),i?.stop();}}}var af="0.4.7";/*! Bundled license information:
785
786
 
786
787
  @noble/curves/utils.js:
787
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.6",
3
+ "version": "0.4.7",
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",