@bivelio/savings-layer 0.4.7 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway/bin.js +32 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/terminales/claude-code.md +7 -1
- package/package.json +1 -1
package/dist/gateway/bin.js
CHANGED
|
@@ -17,15 +17,15 @@ length>this.maxBuffer&&(this.buffer.length=this.maxBuffer);}toWire(t){let n=t.re
|
|
|
17
17
|
model:n.model,at:t.at,avoidedLlm:n.calls.avoidedLlm,actualLlm:n.calls.actualLlm,baselineInputTokens:n.tokens.baselineInput,actualInputTokens:n.tokens.actualInput,eliminatedInputTokens:n.tokens.eliminatedInput,
|
|
18
18
|
compressedInputTokens:n.tokens.compressedInput,baselineOutputTokens:n.tokens.baselineOutput,actualOutputTokens:n.tokens.actualOutput,providerCachedInputTokens:n.tokens.providerCachedInput??0,cacheResult:n.
|
|
19
19
|
cache.result,baselineCost:n.cost.baselineEstimate,actualCost:n.cost.actual,netSavings:n.cost.netSavings,...n.cost.basis?{basis:n.cost.basis}:{},...n.attribution?.sessionId?{sessionId:n.attribution.sessionId}:
|
|
20
|
-
{},...n.attribution?.agentId?{agentId:n.attribution.agentId}:{}}}};function
|
|
20
|
+
{},...n.attribution?.agentId?{agentId:n.attribution.agentId}:{}}}};function F(){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
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(
|
|
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=
|
|
22
|
+
e.NETLIFY)}async function Ge(){if(F()?.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=F();if(!e||!e.env)return;let t=e.env,n=await Ye(t),o=[e.platform,
|
|
24
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
|
|
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 U(){let e=F()?.env;if(!e)
|
|
26
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",
|
|
27
27
|
o.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await n.subtle.sign("HMAC",i,o.encode(t));return [...new Uint8Array(a)].map(l=>l.toString(16).padStart(2,"0")).join("")}catch{}let r=2166136261,s=`${e}\
|
|
28
|
-
|${t}`;for(let i=0;i<s.length;i++)r^=s.charCodeAt(i),r=Math.imul(r,16777619);return (r>>>0).toString(16).padStart(8,"0")}async function
|
|
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 q(e){let n=F()?.env?.BVSALA_SEAT_ID;if(n&&et.test(n))return n;let o=await Qe();
|
|
29
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},
|
|
30
30
|
"gpt-4o":{inputPer1k:.0025,outputPer1k:.01,cachedInputPer1k:.00125},"gpt-4o-mini":{inputPer1k:15e-5,outputPer1k:6e-4,cachedInputPer1k:75e-6},"gpt-4-turbo":{inputPer1k:.01,outputPer1k:.03},"gpt-4":{inputPer1k:.03,
|
|
31
31
|
outputPer1k:.06},"gpt-3.5-turbo":{inputPer1k:5e-4,outputPer1k:.0015},"gpt-5":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},"gpt-5-codex":{inputPer1k:.00125,outputPer1k:.01,cachedInputPer1k:125e-6},
|
|
@@ -48,7 +48,7 @@ slice(a.length+1);if(!(/^\d/.test(p)&&!/^\d{4}/.test(p)))return e[a]}}function t
|
|
|
48
48
|
|
|
49
49
|
`))>=0;){let o=this.resto.slice(0,n);this.resto=this.resto.slice(n+2);let r=/^event: (.+)$/m.exec(o)?.[1];if(r&&(this.eventos[r]=(this.eventos[r]??0)+1),r!=="message_start"&&r!=="message_delta")continue;
|
|
50
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;
|
|
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}},
|
|
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}},K=class{resto="";uso=null;eventos={};observar(t){
|
|
52
52
|
this.resto+=typeof t=="string"?t:t.toString("utf8");let n;for(;(n=this.resto.indexOf(`
|
|
53
53
|
|
|
54
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.
|
|
@@ -68,31 +68,31 @@ measured"}}}var oe=5*1024*1024,at=["authorization","x-api-key","anthropic-versio
|
|
|
68
68
|
`).update(t).update(`
|
|
69
69
|
`).update(o).update(`
|
|
70
70
|
`).update(n).digest("hex")}function Ae(e){return at.map(t=>`${t}=${e(t)??""}`).join(`
|
|
71
|
-
`)}var
|
|
71
|
+
`)}var H=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
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.
|
|
73
73
|
enVuelo.delete(t);},abortar:()=>{this.enVuelo.get(t)?.resolver(null),this.enVuelo.delete(t);}}}async esperar(t){let n=this.enVuelo.get(t);if(!n)return null;let o=await n.promesa;return o&&(this.gemelas+=
|
|
74
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
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(
|
|
76
76
|
"gateway-ingest-auth",`[bvsala gateway] la ingesta de uso rechaza la credencial (${u}). El tr\xE1fico sigue fluyendo (avisar, nunca romper), pero este uso no est\xE1 llegando al panel. Revisa BIVELIO_SERVIC\
|
|
77
|
-
E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=
|
|
77
|
+
E_KEY y el estado de la suscripci\xF3n en savings.bivelio.com.`)});let c=U();e.seatId?r.setSeat(e.seatId,c):q(e.serviceAccountKey).then(u=>r?.setSeat(u,c)).catch(()=>{}),r.start();}else T("gateway-sin-\
|
|
78
78
|
clave","[bvsala gateway] sin BIVELIO_SERVICE_KEY: el gateway reenv\xEDa el tr\xE1fico pero NO mide. Consigue tu clave en savings.bivelio.com/dashboard/install para ver el gasto en tu panel.");let s=e.
|
|
79
|
-
coalescer?new
|
|
79
|
+
coalescer?new H:null,i=c=>c==="/v1/messages"?new j:c.endsWith("/responses")?new K: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
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
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
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"],
|
|
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"],B=Array.
|
|
84
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"]),
|
|
85
|
+
id"]),V=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
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
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,
|
|
88
|
+
null);a(f,x,He,!0,d.pathname,B,V);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
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
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
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
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
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
|
-
|
|
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,
|
|
94
|
+
B,V)):M?a(f,x,ne(Buffer.concat(M)),!1,d.pathname,B,V):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,B,V)):o(`[gateway] ${c.method} ${d.pathname} \u2192 el cliente abort\xF3; upstream cance\
|
|
96
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
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
98
|
soportado; el cliente cae a HTTP)`),u.once("error",()=>{}),u.once("finish",()=>u.destroy()),u.end(`HTTP/1.1 426 Upgrade Required\r
|
|
@@ -139,7 +139,7 @@ match(/\n/g)??[]).length;o+=i+Math.floor(Math.max(0,r.length-1)/2);}else /[A-Za-
|
|
|
139
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){
|
|
140
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.
|
|
141
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.
|
|
142
|
+
te(Lt);var pe="0.4.8";var[,,v,...D]=process.argv;function zt(){console.log(`bvsala ${pe} \u2014 BiVelio Savings Layer
|
|
143
143
|
|
|
144
144
|
Comandos:
|
|
145
145
|
gateway [--puerto N] Proxy local medidor (por defecto 8402) para terminales que
|
|
@@ -157,22 +157,24 @@ Entorno:
|
|
|
157
157
|
BVSALA_GATEWAY_UPSTREAM Upstream alternativo (por defecto el del terminal).
|
|
158
158
|
BVSALA_GATEWAY_COALESCE =1 enciende la coalescencia de duplicados (F2, experimental).
|
|
159
159
|
BVSALA_CODEX_AUTH api-key | chatgpt \u2014 fuerza el modo de auth de Codex.
|
|
160
|
+
BVSALA_GATEWAY_LOG =1 ense\xF1a el log por petici\xF3n tambi\xE9n dentro de los envoltorios
|
|
161
|
+
(por defecto va silenciado para no pisar el TUI; mira el panel).
|
|
160
162
|
CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 (Claude Code, solo API key) tus modelos en /model.
|
|
161
163
|
|
|
162
164
|
El gateway nunca corta el tr\xE1fico: sin clave o con la ingesta ca\xEDda, reenv\xEDa y avisa.
|
|
163
|
-
Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var
|
|
164
|
-
lp"?0:1));var me=D.indexOf("--puerto"),O=
|
|
165
|
+
Tus prompts jam\xE1s salen de tu m\xE1quina \u2014 solo cuentas y coste. M\xE1s: savings.bivelio.com`);}var z=Le(v);v!=="gateway"&&v!=="doctor"&&!z&&(zt(),process.exit(v===void 0||v==="help"||v==="--he\
|
|
166
|
+
lp"?0:1));var me=D.indexOf("--puerto"),O=z?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
167
|
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=
|
|
167
|
-
{serviceAccountKey:fe}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.
|
|
168
|
-
upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:
|
|
169
|
-
I?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway] v${pe} escuchando en http://127.0.0.1:${P.puerto} \u2192 ${
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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"){
|
|
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
|
|
175
|
-
|
|
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.
|
|
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
|
|
178
|
-
|
|
168
|
+
log(e.texto),process.exit(0);}var I=z?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,Bt=z&&process.env.BVSALA_GATEWAY_LOG!==
|
|
169
|
+
"1",P=await Oe({puerto:O,...Bt?{log:()=>{}}:{},...fe?{serviceAccountKey:fe}:{},...process.env.BVSALA_SEAT_ID?{seatId:process.env.BVSALA_SEAT_ID}:{},...process.env.BVSALA_GATEWAY_UPSTREAM?{upstream:process.
|
|
170
|
+
env.BVSALA_GATEWAY_UPSTREAM}:I?.upstream?{upstream:I.upstream}:{},...process.env.BVSALA_GATEWAY_COALESCE==="1"?{coalescer:true}:{},...process.env.BVSALA_METERING_URL?{meteringUrl:process.env.BVSALA_METERING_URL}:
|
|
171
|
+
{}}),Vt=process.env.BVSALA_GATEWAY_UPSTREAM??I?.upstream??"https://api.anthropic.com";console.log(`[bvsala gateway] v${pe} escuchando en http://127.0.0.1:${P.puerto} \u2192 ${Vt}`);console.log(`[bvsal\
|
|
172
|
+
a gateway] medici\xF3n: ${fe?"ACTIVA (clave de servicio presente)":"APAGADA (falta BIVELIO_SERVICE_KEY)"}`);process.env.BVSALA_GATEWAY_UPSTREAM&&I?.upstream&&process.env.BVSALA_GATEWAY_UPSTREAM!==I.upstream&&
|
|
173
|
+
console.error(`[bvsala ${v}] AVISO: BVSALA_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===
|
|
174
|
+
"codex"&&!Re()&&console.error(`[bvsala codex] AVISO: este Node (${process.version}) no descomprime zstd y Codex manda el cuerpo en zstd: el tr\xE1fico pasa pero NO se mide. Usa Node \u2265 22.15.`);if(I&&
|
|
175
|
+
z){let e=se(v,P.puerto,D,{...I.upstream?{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"){
|
|
176
|
+
let o=Me(e.bin);o.tipo==="exe"?t=o.ruta: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 \
|
|
177
|
+
argumentos (CVE-2024-27980). Inst\xE1lalo con su instalador nativo (deja un ${e.bin}.exe en el PATH) o usa la receta manual de docs/terminales/.`),await P.cerrar(),process.exit(127));}let n=spawn(t,e.args,
|
|
178
|
+
{stdio:"inherit",env:{...process.env,...e.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.
|
|
179
|
+
exit(127));}),n.on("exit",(o,r)=>{P.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\
|
|
180
|
+
.0.0.1:${P.puerto} claude \xB7 bvsala 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
|
@@ -3250,6 +3250,6 @@ declare function createSavingsLayer(config: Omit<SavingsLayerConfig, "tokenizer"
|
|
|
3250
3250
|
* constant to package.json so the two can never drift.
|
|
3251
3251
|
*/
|
|
3252
3252
|
|
|
3253
|
-
declare const BV_SALA_VERSION = "0.4.
|
|
3253
|
+
declare const BV_SALA_VERSION = "0.4.8";
|
|
3254
3254
|
|
|
3255
3255
|
export { type AnthropicOptions, AnthropicProvider, BV_SALA_VERSION, type BivelioHandoff, CATALOGO, COMPACT_ROWS_INSTRUCTION, type CacheOutcome, type CacheTier, type CachedAnswer, type CanonicalRequest, type ChatMessage, type CompactRowsDecodeOptions, type CompactRowsOptions, type CompactVerification, type CompressionEconomics, type CompressionPolicyInput, type CompressionRequest, type CompressionResult, type Compressor, type ConstraintBackend, type ConstraintBackendId, type ConstraintPlan, type ConversationState, type CostBasis, type CostModel, DEFAULT_COST_MODEL, DEFAULT_MAX_STALENESS_SECONDS, type DeferredExecutionReport, type DeterministicResolver, EMBEDDED_PUBLIC_KEY, type EmbeddingFn, type EvidenceDoc, type ExactCache, ExactTokenizer, type ExecutionClass, ExecutionRouter, type FormatChoice, FormatSelector, type FormatSelectorOptions, type HandoffFact, HeuristicEstimator, InMemoryExactCache, InMemorySemanticCache, type JsonSchema, LicenseError, type LicenseGate, type LicenseKeyset, type LicenseStatus, type LicenseTokenPayload, type LocalClassifier, MODELOS_POR_CODIFICACION, MODELS_BY_ENCODING, type MemoryDelta, type MessageRole, MockProvider, type MockProviderOptions, type ModelCascadeMode, type ModelCascadeReport, type ModelCascadeSettings, type OpenAICompatibleOptions, OpenAICompatibleProvider, type OutputContract, PRECIOS_AL_DIA, type Policy, type ProtectedSpan, type Provider, type ProviderRequest, type ProviderResponse, type ProviderUsage, QUALITY_STATUSES, type QualityStatus, type RequestContext, type Reranker, type ResolvedDeps, type ResolvedGate, type ResolvedLicense, ResolverRegistry, type ResolverResult, type RetrievalOptions, type RetrievalOutcome, type RetrievalPlan, type Retriever, type RevocationListBody, type RevocationListVerification, type RevocationPollEvent, RevocationPoller, type RevocationPollerOptions, RevocationSnapshot, type RouteDecision, type SavingsLayer, type SavingsLayerConfig, SavingsLedger, type SavingsReport, type SavingsRequest, type SavingsResult, type SavingsToggles, SchemaRegistry, type SemanticCacheOptions, type SemanticHit, type SemanticRecord, type SemanticVerifier, type SerializationFormat, type Serializer, Singleflight, type TokenEstimator, TokenizerRegistry, type ToolCallRequest, type ToolDescriptor, type ToolRisk, type ToolSelection, ToolSelector, type ToolSelectorOptions, TraceCollector, type TraceEvent, UMBRAL_LOCAL, UsageEmitter, type UsageEmitterOptions, type ValidationError, type ValidationResult, adaptiveK, arithmeticResolver, buildExactKey, buildInflightKey, canonicalize, compactRowsSerializer, compresionFiel, computeNextDelay, cosine, createCompactRowsSerializer, createDefaultResolvers, createHandoff, createMockProvider, createSavingsLayer, csvSerializer, decodeCompactRows, dedupe, defaultTokenizerRegistry, deriveSeatId, deriveStatus, detectCi, embebedorLocal, emptyState, encodeCompactRows, evalArithmetic, evaluatePolicy, executeRetrievalPlan, gateFromKey, heuristicEstimator, isKeyset, isStrictCompatible, jsonCompactSerializer, keyTokensMatch, liveGateFromKey, mergeDelta, modelFamilyOf, multisetEqual, negationCount, noopCompressor, normalizeText, numericTokens, precioDeLista, projectContext, providerNativeBackend, randomId, registerExactTokenizer, registrarTokenizadorExacto, renderState, resolveConstraintBackend, resolveDependencies, resolveLicense, sha256, shouldCompress, stableStringify, uniformColumns, validateAgainstSchema, vectorLocal, verifyCompactSignature, verifyLicenseToken, verifyRevocationList, withProtectedSpans, words };
|
package/dist/index.js
CHANGED
|
@@ -782,7 +782,7 @@ ng/usage`:void 0);if(a){i=new dt({url:a,serviceAccountKey:t.serviceAccountKey,..
|
|
|
782
782
|
{},...t.metering?.maxBatch!=null?{maxBatch:t.metering.maxBatch}:{},...t.metering?.maxBuffer!=null?{maxBuffer:t.metering.maxBuffer}:{},...t.meteringFetch?{fetchImpl:t.meteringFetch}:t.licenseFetch?{fetchImpl:t.
|
|
783
783
|
licenseFetch}:{},...t.now?{now:t.now}:{},...t.metering?.ci!=null?{ci:t.metering.ci}:{},...t.silenceWarnings!=null?{silenceWarnings:t.silenceWarnings}:{}});let c=t.metering?.ci??cr();if(t.metering?.seatId)
|
|
784
784
|
i.setSeat(t.metering.seatId,c);else {let l=i;lr(t.serviceAccountKey).then(u=>l.setSeat(u,c)).catch(()=>{});}i.start();}}return {generate:async a=>{let c=await s.generate(a);if(i)try{i.record(c.report);}catch{}
|
|
785
|
-
return c},close:()=>{r?.stop(),o?.stop(),i?.stop();}}}var af="0.4.
|
|
785
|
+
return c},close:()=>{r?.stop(),o?.stop(),i?.stop();}}}var af="0.4.8";/*! Bundled license information:
|
|
786
786
|
|
|
787
787
|
@noble/curves/utils.js:
|
|
788
788
|
@noble/curves/abstract/modular.js:
|
|
@@ -42,7 +42,13 @@ Claude Code añade `/v1/messages` por su cuenta. Sin barra final.
|
|
|
42
42
|
|
|
43
43
|
## Comprobar que funciona
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
**Con `bvsala claude` (el envoltorio), la comprobación es tu dashboard**: el
|
|
46
|
+
log por petición va silenciado por defecto desde la 0.4.8 — imprimirlo pisaba
|
|
47
|
+
el TUI de Claude Code (las palabras se montaban sobre los diálogos). Si lo
|
|
48
|
+
quieres ver igualmente, `BVSALA_GATEWAY_LOG=1 bvsala claude`.
|
|
49
|
+
|
|
50
|
+
Con `bvsala gateway` a secas (dos terminales), el log sí es la comprobación,
|
|
51
|
+
en este orden:
|
|
46
52
|
|
|
47
53
|
```
|
|
48
54
|
[gateway] HEAD /api/hello → 200
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bivelio/savings-layer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
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",
|