@kianwoon/modelweaver 0.3.11 → 0.3.13
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/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as Z}from"./chunk-OMWFRIHF.js";import{d as ee}from"./chunk-ZF23JS2D.js";import{b as te,e as ne,g as B,k as oe}from"./chunk-DIRPR744.js";import{serve as ve}from"@hono/node-server";import{readFileSync as nt}from"fs";import{Hono as He}from"hono";var q=new Map;function re(){q.clear()}function ke(t,e){for(let[o,n]of e)for(let d of n)if(t.includes(d))return o;return null}function Se(t,e){return e.get(t)||[]}function se(t,e,o,n){let d=q.get(t);if(d)return q.delete(t),q.set(t,d),{requestId:e,model:t,tier:d.tier,providerChain:d.providerChain,startTime:Date.now(),rawBody:n};let c,i,r=o.modelRouting.get(t);if(r&&r.length>0)c="(modelRouting)",i=r;else{let u=ke(t,o.tierPatterns);if(!u)return null;c=u,i=Se(c,o.routing)}if(q.size>=200){let u=q.keys().next().value;u!==void 0&&q.delete(u)}return q.set(t,{tier:c,providerChain:i}),{requestId:e,model:t,tier:c,providerChain:i,startTime:Date.now(),rawBody:n}}import{request as Ee}from"undici";import{PassThrough as Ie}from"stream";import pe from"fs";import me from"path";import Pe from"os";var U=class{samples=new Map;maxSize;MAX_PROVIDERS=50;constructor(e=20){this.maxSize=e}record(e,o){if(this.samples.size>=this.MAX_PROVIDERS&&!this.samples.has(e)){let d=this.samples.keys().next().value;d!==void 0&&this.samples.delete(d)}let n=this.samples.get(e);n||(n=[],this.samples.set(e,n)),n.push({ttfbMs:o,timestamp:Date.now()}),n.length>this.maxSize&&n.splice(0,n.length-this.maxSize)}getCV(e){let o=this.samples.get(e);if(!o||o.length<3)return 0;let n=o.map(i=>i.ttfbMs),d=n.reduce((i,r)=>i+r,0)/n.length;if(d===0)return 0;let c=n.reduce((i,r)=>i+(r-d)**2,0)/n.length;return Math.sqrt(c)/d}getStats(e){let o=this.samples.get(e);if(!o||o.length===0)return{count:0,mean:0,cv:0};let n=o.map(c=>c.ttfbMs),d=n.reduce((c,i)=>c+i,0)/n.length;return{count:n.length,mean:Math.round(d),cv:Math.round(this.getCV(e)*100)/100}}clear(e){this.samples.delete(e)}prune(e){let o=new Set(e);for(let n of this.samples.keys())o.has(n)||this.samples.delete(n)}},H=class{counts=new Map;increment(e){let o=(this.counts.get(e)??0)+1;return this.counts.set(e,o),o}decrement(e){let o=Math.max(0,(this.counts.get(e)??0)-1);return this.counts.set(e,o),o}get(e){return this.counts.get(e)??0}},$=new U,L=new H;function ie(t){let e=$.getCV(t.name),o=L.get(t.name),n=t.concurrentLimit??1,d=Math.max(1,n-o),c=Math.max(1,Math.floor(e*2+.5));return Math.min(c,d)}import{WebSocketServer as Re}from"ws";var Te=3e4,_e=2,ue=64*1024,Ce=500,Me=500,xe=1e4,ae=new WeakMap,z=null,le=0,ce=0;function de(t){let e=Date.now();e-ce>=xe&&(console.warn(`[ws] Backpressure: dropping ${t} events (total dropped stream events: ${le})`),ce=e)}function J(t,e){let o=new Re({server:t,path:"/ws"});z=o,o.on("connection",n=>{let c={type:"summary",data:e.getSummary()};n.send(JSON.stringify(c));let i,r=0,u=()=>n.readyState===n.OPEN,m=e.onRecord(f=>{if(u()){if(n.bufferedAmount>ue){a(),de("metrics");return}setImmediate(()=>{if(!u())return;let g={type:"request",data:f};n.send(JSON.stringify(g))}),a()}});function a(){i||(i=setTimeout(()=>{if(i=void 0,!u())return;let f={type:"summary",data:e.getSummary()};n.send(JSON.stringify(f))},Ce))}let s=setInterval(()=>{if(!u()){clearInterval(s);return}if(r>=_e){l(),n.terminate();return}n.ping(),r++},Te);n.on("pong",()=>{r=0});let p=!1,l=()=>{p||(p=!0,clearInterval(s),i&&clearTimeout(i),m())};n.on("close",l),n.on("error",l)})}function I(t){if(!z)return;let e=JSON.stringify({type:"stream",data:t}),o=t.state==="streaming",n=t.state==="complete"||t.state==="error",d=Date.now();for(let c of z.clients)if(c.readyState===c.OPEN){if(o){let i=ae.get(c)??0;if(d-i<Me)continue;ae.set(c,d)}if(c.bufferedAmount>ue){if(n){let i=()=>{c.readyState===c.OPEN&&c.send(e)};c.once("drain",i),setTimeout(()=>{c.removeListener("drain",i),c.readyState===c.OPEN&&c.send(e)},5e3).unref();continue}le++,de("stream");continue}setImmediate(()=>{c.readyState===c.OPEN&&c.send(e)})}}var Ae=new Set(["anthropic-version","anthropic-beta","content-type","accept"]),Oe=/\/+/g,qe=/^https?:\/\/[^/]+/,F=/"model"\s*:\s*"([^"]*)"/,j=/"max_tokens"\s*:\s*(\d+)/,O=new TextEncoder,$e=3e3;function Ne(t){return t===429||t>=500}var Le=["context window","context_limit","token limit","prompt is too long","max tokens","input too large","too many tokens"];function Be(t,e){if(t!==400)return!1;let o=e.toLowerCase();return Le.some(n=>o.includes(n))}function De(t,e){if(!Be(t,e))return null;console.warn("[context-compact] Upstream context window limit detected");try{let n=me.join(Pe.homedir(),".claude","state");pe.mkdirSync(n,{recursive:!0}),pe.writeFileSync(me.join(n,"context-compact-needed"),Date.now().toString())}catch{}let o=JSON.stringify({type:"error",error:{type:"invalid_request_error",message:"Context window limit reached. Run /compact to reduce conversation size, then retry."}});return new Response(o,{status:400,headers:{"content-type":"application/json"}})}function je(t,e){let o="",n=t,d=t.indexOf("/",t.indexOf("//")+2);d!==-1&&(n=t.substring(0,d),o=t.substring(d));let c="",i=e,r=e.indexOf("?");r!==-1&&(i=e.substring(0,r),c=e.substring(r));let u;return o.endsWith("/v1")&&i.startsWith("/v1")?u=o+i.substring(3):u=o+i,u=u.replace(Oe,"/"),n+u+c}function We(t,e,o){let n=new Headers;for(let c of Ae){let i=t.get(c);i&&n.set(c,i)}e.authType==="bearer"?n.set("Authorization",`Bearer ${e.apiKey}`):n.set("x-api-key",e.apiKey),n.set("x-request-id",o);let d=e._cachedHost;if(d)n.set("host",d);else try{let c=new URL(e.baseUrl);n.set("host",c.host)}catch{}return n}function he(t){let e=t.messages;if(!Array.isArray(e))return;let o=new Set,n=new Set;for(let u=0;u<e.length;u++){let m=e[u];if(Array.isArray(m.content))for(let a of m.content)a.type==="tool_use"&&a.id?o.add(String(a.id)):a.type==="tool_result"&&a.tool_use_id&&n.add(String(a.tool_use_id))}let d=new Set,c=new Set;for(let u of o)n.has(u)||d.add(u);for(let u of n)o.has(u)||c.add(u);if(d.size===0&&c.size===0)return;let i=!1,r=e.map(u=>{if(!Array.isArray(u.content))return u;let m=u.content.filter(a=>!(a.type==="tool_use"&&d.has(String(a.id))||a.type==="tool_result"&&c.has(String(a.tool_use_id))));return m.length<u.content.length?(i=!0,{...u,content:m}):u});i&&(t.messages=r)}function Ue(t,e,o,n,d){if(d){let y=structuredClone(n);if(e.model&&(y.model=e.model),he(y),o.modelLimits){let{maxOutputTokens:M}=o.modelLimits,h=typeof y.max_tokens=="number"?y.max_tokens:M;(y.max_tokens===void 0||h>M)&&(y.max_tokens=Math.min(h,M))}return JSON.stringify(y)}let c=!!(e.model&&n.model!==e.model),i=!1,r=!1,u=0;if(o.modelLimits){u=o.modelLimits.maxOutputTokens;let y=j.exec(t);y?i=parseInt(y[1],10)>u:typeof n.max_tokens!="number"&&(r=!0)}if(!c&&!i&&!r)return t;if(r){let y={...n};return e.model&&(y.model=e.model),y.max_tokens=u,JSON.stringify(y)}let m=[];c&&m.push(F.source),i&&m.push(j.source);let a=new RegExp(m.join("|"),"g"),s=c?`"model":"${e.model}"`:null,p=i?`"max_tokens":${u}`:null,l=n.model,f=!1;return t.replace(a,y=>s&&F.test(y)?(F.lastIndex=0,!f&&l&&(console.warn(`Routing override: ${l} -> ${e.model} via ${o.name}`),f=!0),s):p&&j.test(y)?(j.lastIndex=0,p):y)}async function fe(t,e,o,n,d,c=0){let i=n.url.replace(qe,"");e.model&&(o.actualModel=e.model);let r=je(t.baseUrl,i),u;if((n.headers.get("content-type")||"").includes("application/json"))try{let h=o.parsedBody??JSON.parse(o.rawBody),w=!1;e.model&&h.model!==e.model&&(w=!0);let b=c>0;if(b&&(w=!0),t.modelLimits){let{maxOutputTokens:v}=t.modelLimits,T=typeof h.max_tokens=="number"?h.max_tokens:v;(h.max_tokens===void 0||T>v)&&(w=!0)}if(w)if(c===0&&!b)u=Ue(o.rawBody,e,t,h,!1);else{let v=structuredClone(h);if(e.model){let T=v.model;v.model=e.model,T&&T!==e.model&&console.warn(`Routing override: ${T} -> ${e.model} via ${t.name}`)}if(b&&he(v),t.modelLimits){let{maxOutputTokens:T}=t.modelLimits,P=typeof v.max_tokens=="number"?v.max_tokens:T;(v.max_tokens===void 0||P>T)&&(v.max_tokens=Math.min(P,T))}u=JSON.stringify(v)}else u=o.rawBody}catch{u=o.rawBody}else u=o.rawBody;let a=We(n.headers,t,o.requestId);a.set("content-length",Buffer.byteLength(u,"utf-8").toString());let s=new AbortController,p=setTimeout(()=>s.abort(),t.timeout),l=t.ttfbTimeout??15e3,f=!1,g=null,y=new Promise((h,w)=>{g=setTimeout(()=>{f=!0,s.abort(),w(new Error(`TTFB timeout after ${l}ms`))},l)}).catch(()=>{}),M;if(d){if(d.aborted){clearTimeout(p),g&&clearTimeout(g);let w=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(w,{status:502,headers:{"content-type":"application/json","content-length":O.encode(w).byteLength.toString()}})}let h=()=>{clearTimeout(p),g&&clearTimeout(g)};M=d.addEventListener("abort",h,{once:!0})}try{let h=await Promise.race([Ee(r,{method:"POST",headers:a,body:u,signal:s.signal,dispatcher:t._agent}),y]);g&&clearTimeout(g);let w=t.stallTimeout??3e4,b=new Ie,v=`Body stalled: no data after ${w}ms`,T=setTimeout(()=>{I({requestId:o.requestId,model:String(o.actualModel??e.model??""),tier:"",state:"error",message:v,timestamp:Date.now()}),b.destroy(new Error(v))},w);b.on("data",()=>{clearTimeout(T),T=setTimeout(()=>{I({requestId:o.requestId,model:String(o.actualModel??e.model??""),tier:"",state:"error",message:v,timestamp:Date.now()}),b.destroy(new Error(v))},w)}),b.on("end",()=>{clearTimeout(T)}),b.on("error",()=>{clearTimeout(T)}),h.body.pipe(b);let P=new Response(b,{status:h.statusCode,headers:h.headers});return clearTimeout(p),P}catch(h){clearTimeout(p),g&&clearTimeout(g);let w=f?`Provider "${t.name}" timed out waiting for first byte after ${l}ms`:h instanceof DOMException&&h.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${h.message}`,b=JSON.stringify({type:"error",error:{type:"overloaded_error",message:w}});return new Response(b,{status:502,headers:{"content-type":"application/json","content-length":O.encode(b).byteLength.toString()}})}finally{M?.()}}async function ge(t,e,o,n,d,c,i){let r=ie(t);if(r<=1){L.increment(t.name);let l=Date.now();try{let f=await fe(t,e,o,n,d,c);return $.record(t.name,Date.now()-l),f}finally{L.decrement(t.name)}}i?.warn("Hedging request",{requestId:o.requestId,provider:t.name,count:r,cv:Math.round($.getCV(t.name)*100)/100,inFlight:L.get(t.name),maxConcurrent:t.concurrentLimit});let u=Date.now(),m=[];for(let l=0;l<r;l++)L.increment(t.name),m.push(fe(t,e,o,n,d,c).finally(()=>L.decrement(t.name)));let a=m.map((l,f)=>l.then(g=>({response:g,hedgeIndex:f}))),s=new Set,p=[];try{for(;s.size<a.length;){let l=a.filter((g,y)=>!s.has(y));if(l.length===0)break;let f=await Promise.race(l);if(s.add(f.hedgeIndex),t._circuitBreaker&&t._circuitBreaker.recordResult(f.response.status),f.response.status>=200&&f.response.status<300){$.record(t.name,Date.now()-u);for(let g=0;g<a.length;g++)s.has(g)||(t._circuitBreaker&&t._circuitBreaker.recordResult(502),a[g].then(y=>{try{y.response.body?.cancel()}catch{}}));for(let g of p)try{g.body?.cancel()}catch{}return f.response}p.push(f.response)}for(let l of p)try{l.body?.cancel()}catch{}return p[0]??new Response(JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${t.name}" all hedged requests failed`}}),{status:502,headers:{"content-type":"application/json"}})}catch{for(let l of p)try{l.body?.cancel()}catch{}return p[0]??new Response(JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${t.name}" hedging failed`}}),{status:502,headers:{"content-type":"application/json"}})}}async function ye(t,e,o,n,d,c){if(e.length<=1){let s=e[0],p=t.get(s.provider);if(!p){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${s.provider}`}});return new Response(f,{status:502,headers:{"content-type":"application/json","content-length":O.encode(f).byteLength.toString()}})}if(p._circuitBreaker&&!p._circuitBreaker.canProceed().allowed){c?.warn("Provider skipped by circuit breaker",{requestId:o.requestId,provider:s.provider});let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${s.provider}" skipped by circuit breaker`}});return new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}})}return d?.(s.provider,0),await ge(p,s,o,n,void 0,0,c)}let i=new AbortController,r=new Set,u=[];async function m(s){let p=e[s],l=t.get(p.provider);if(!l){let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${p.provider}`}});return{response:new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}}),index:s}}let f;if(l._circuitBreaker){let g=l._circuitBreaker.canProceed();if(!g.allowed){c?.warn("Provider skipped by circuit breaker",{requestId:o.requestId,provider:p.provider});let y=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${p.provider}" skipped by circuit breaker`}});return{response:new Response(y,{status:502,headers:{"content-type":"application/json","content-length":O.encode(y).byteLength.toString()}}),index:s}}f=g.probeId}d?.(p.provider,s);try{return{response:await ge(l,p,o,n,i.signal,s,c),index:s}}catch{l._circuitBreaker&&l._circuitBreaker.recordResult(502,f);let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${p.provider}" failed`}});return{response:new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}}),index:s}}}let a=[];for(let s=0;s<e.length;s++)s===0?a.push(m(0)):a.push(new Promise(p=>{setTimeout(()=>{if(i.signal.aborted){let l=JSON.stringify({type:"error",error:{type:"api_error",message:"Cancelled by race winner"}});p({response:new Response(l,{status:502,headers:{"content-type":"application/json"}}),index:s});return}m(s).then(p)},$e)}));try{for(;r.size<a.length;){let p=a.filter((f,g)=>!r.has(g));if(p.length===0)break;let l=await Promise.race(p);if(r.add(l.index),l.response.status>=200&&l.response.status<300){i.abort();for(let f of u)try{f.response.body?.cancel()}catch{}return l.response}if(!Ne(l.response.status)){if(i.abort(),l.response.status===400&&l.response.body)try{let f=await l.response.text(),g=De(l.response.status,f);return g||new Response(f,{status:l.response.status,statusText:l.response.statusText,headers:l.response.headers})}catch{return l.response}return l.response}u.push(l)}if(i.abort(),u.length>0)return u[0].response;let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json","content-length":O.encode(s).byteLength.toString()}})}catch{i.abort();let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json","content-length":O.encode(s).byteLength.toString()}})}}import{randomUUID as ze}from"crypto";import{gzip as Je}from"zlib";import{promisify as Fe}from"util";var Ge=Fe(Je),G={"claude-opus-4-6":2e5,"claude-sonnet-4-6":2e5,"claude-haiku-4-5-20251001":2e5,"claude-3-5-sonnet":2e5,"claude-3-5-haiku":2e5,"glm-4.7":128e3,"glm-5-turbo":128e3};function K(t){if(G[t])return G[t];for(let[e,o]of Object.entries(G))if(t.startsWith(e))return o;return 0}function X(t,e,o){let n=o+t+e;return n<=0?0:Math.round(t/n*1e3)/10}function V(t,e,o,n,d){if(d<=0)return 0;let c=t+e+o+n;return Math.round(c/d*1e3)/10}function Q(t,e,o){return new Response(JSON.stringify({type:"error",error:{type:t,message:e}}),{status:502,headers:{"content-type":"application/json","x-request-id":o}})}function Ke(t){let e=t.message?.usage??t.usage;if(!e)return{inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0};let o=e.input_tokens??e.prompt_tokens??0,n=e.output_tokens??e.completion_tokens??0,d=e.cache_read_input_tokens??0,c=e.cache_creation_input_tokens??0;return{inputTokens:o,outputTokens:n,cacheReadTokens:d,cacheCreationTokens:c}}function Xe(t,e,o,n,d,c){let i=new TextDecoder,r={input:0,output:0,cacheRead:0,cacheCreation:0},u="",m="",a=4096,s=0,p=0,l=0,f=0,g="",y=null,M=250,h=0,w=!0,b="",v=100,T=S=>{for(let C of S.split(`
|
|
2
|
+
import{a as Z}from"./chunk-OMWFRIHF.js";import{d as ee}from"./chunk-ZF23JS2D.js";import{b as te,e as ne,g as B,k as oe}from"./chunk-DIRPR744.js";import{serve as ve}from"@hono/node-server";import{readFileSync as nt}from"fs";import{Hono as He}from"hono";var q=new Map;function re(){q.clear()}function ke(t,e){for(let[o,n]of e)for(let d of n)if(t.includes(d))return o;return null}function Se(t,e){return e.get(t)||[]}function se(t,e,o,n){let d=q.get(t);if(d)return q.delete(t),q.set(t,d),{requestId:e,model:t,tier:d.tier,providerChain:d.providerChain,startTime:Date.now(),rawBody:n};let c,i,r=o.modelRouting.get(t);if(r&&r.length>0)c="(modelRouting)",i=r;else{let u=ke(t,o.tierPatterns);if(!u)return null;c=u,i=Se(c,o.routing)}if(q.size>=200){let u=q.keys().next().value;u!==void 0&&q.delete(u)}return q.set(t,{tier:c,providerChain:i}),{requestId:e,model:t,tier:c,providerChain:i,startTime:Date.now(),rawBody:n}}import{request as Ee}from"undici";import{PassThrough as Ie}from"stream";import pe from"fs";import me from"path";import Pe from"os";var U=class{samples=new Map;maxSize;MAX_PROVIDERS=50;constructor(e=20){this.maxSize=e}record(e,o){if(this.samples.size>=this.MAX_PROVIDERS&&!this.samples.has(e)){let d=this.samples.keys().next().value;d!==void 0&&this.samples.delete(d)}let n=this.samples.get(e);n||(n=[],this.samples.set(e,n)),n.push({ttfbMs:o,timestamp:Date.now()}),n.length>this.maxSize&&n.splice(0,n.length-this.maxSize)}getCV(e){let o=this.samples.get(e);if(!o||o.length<3)return 0;let n=o.map(i=>i.ttfbMs),d=n.reduce((i,r)=>i+r,0)/n.length;if(d===0)return 0;let c=n.reduce((i,r)=>i+(r-d)**2,0)/n.length;return Math.sqrt(c)/d}getStats(e){let o=this.samples.get(e);if(!o||o.length===0)return{count:0,mean:0,cv:0};let n=o.map(c=>c.ttfbMs),d=n.reduce((c,i)=>c+i,0)/n.length;return{count:n.length,mean:Math.round(d),cv:Math.round(this.getCV(e)*100)/100}}clear(e){this.samples.delete(e)}prune(e){let o=new Set(e);for(let n of this.samples.keys())o.has(n)||this.samples.delete(n)}},H=class{counts=new Map;increment(e){let o=(this.counts.get(e)??0)+1;return this.counts.set(e,o),o}decrement(e){let o=Math.max(0,(this.counts.get(e)??0)-1);return this.counts.set(e,o),o}get(e){return this.counts.get(e)??0}},$=new U,L=new H;function ie(t){let e=$.getCV(t.name),o=L.get(t.name),n=t.concurrentLimit??1,d=Math.max(1,n-o),c=Math.max(1,Math.floor(e*2+.5));return Math.min(c,d)}import{WebSocketServer as Re}from"ws";var Te=3e4,_e=2,ue=64*1024,Ce=500,Me=500,xe=1e4,ae=new WeakMap,z=null,le=0,ce=0;function de(t){let e=Date.now();e-ce>=xe&&(console.warn(`[ws] Backpressure: dropping ${t} events (total dropped stream events: ${le})`),ce=e)}function J(t,e){let o=new Re({server:t,path:"/ws"});z=o,o.on("connection",n=>{let c={type:"summary",data:e.getSummary()};n.send(JSON.stringify(c));let i,r=0,u=()=>n.readyState===n.OPEN,m=e.onRecord(f=>{if(u()){if(n.bufferedAmount>ue){a(),de("metrics");return}setImmediate(()=>{if(!u())return;let g={type:"request",data:f};n.send(JSON.stringify(g))}),a()}});function a(){i||(i=setTimeout(()=>{if(i=void 0,!u())return;let f={type:"summary",data:e.getSummary()};n.send(JSON.stringify(f))},Ce))}let s=setInterval(()=>{if(!u()){clearInterval(s);return}if(r>=_e){l(),n.terminate();return}n.ping(),r++},Te);n.on("pong",()=>{r=0});let p=!1,l=()=>{p||(p=!0,clearInterval(s),i&&clearTimeout(i),m())};n.on("close",l),n.on("error",l)})}function I(t){if(!z)return;let e=JSON.stringify({type:"stream",data:t}),o=t.state==="streaming",n=t.state==="complete"||t.state==="error",d=Date.now();for(let c of z.clients)if(c.readyState===c.OPEN){if(o){let i=ae.get(c)??0;if(d-i<Me)continue;ae.set(c,d)}if(c.bufferedAmount>ue){if(n){let i=()=>{c.readyState===c.OPEN&&c.send(e)};c.once("drain",i),setTimeout(()=>{c.removeListener("drain",i),c.readyState===c.OPEN&&c.send(e)},5e3).unref();continue}le++,de("stream");continue}setImmediate(()=>{c.readyState===c.OPEN&&c.send(e)})}}var Ae=new Set(["anthropic-version","anthropic-beta","content-type","accept"]),Oe=/\/+/g,qe=/^https?:\/\/[^/]+/,F=/"model"\s*:\s*"([^"]*)"/,j=/"max_tokens"\s*:\s*(\d+)/,O=new TextEncoder,$e=3e3;function Ne(t){return t===429||t>=500}var Le=["context window","context_limit","token limit","prompt is too long","max tokens","input too large","too many tokens"];function Be(t,e){if(t!==400)return!1;let o=e.toLowerCase();return Le.some(n=>o.includes(n))}function De(t,e){if(!Be(t,e))return null;console.warn("[context-compact] Upstream context window limit detected");try{let n=me.join(Pe.homedir(),".claude","state");pe.mkdirSync(n,{recursive:!0}),pe.writeFileSync(me.join(n,"context-compact-needed"),Date.now().toString())}catch{}let o=JSON.stringify({type:"error",error:{type:"invalid_request_error",message:"Context window limit reached. Run /compact to reduce conversation size, then retry."}});return new Response(o,{status:400,headers:{"content-type":"application/json"}})}function je(t,e){let o="",n=t,d=t.indexOf("/",t.indexOf("//")+2);d!==-1&&(n=t.substring(0,d),o=t.substring(d));let c="",i=e,r=e.indexOf("?");r!==-1&&(i=e.substring(0,r),c=e.substring(r));let u;return o.endsWith("/v1")&&i.startsWith("/v1")?u=o+i.substring(3):u=o+i,u=u.replace(Oe,"/"),n+u+c}function We(t,e,o){let n=new Headers;for(let c of Ae){let i=t.get(c);i&&n.set(c,i)}e.authType==="bearer"?n.set("Authorization",`Bearer ${e.apiKey}`):n.set("x-api-key",e.apiKey),n.set("x-request-id",o);let d=e._cachedHost;if(d)n.set("host",d);else try{let c=new URL(e.baseUrl);n.set("host",c.host)}catch{}return n}function he(t){let e=t.messages;if(!Array.isArray(e))return;let o=new Set,n=new Set;for(let u=0;u<e.length;u++){let m=e[u];if(Array.isArray(m.content))for(let a of m.content)a.type==="tool_use"&&a.id?o.add(String(a.id)):a.type==="tool_result"&&a.tool_use_id&&n.add(String(a.tool_use_id))}let d=new Set,c=new Set;for(let u of o)n.has(u)||d.add(u);for(let u of n)o.has(u)||c.add(u);if(d.size===0&&c.size===0)return;let i=!1,r=e.map(u=>{if(!Array.isArray(u.content))return u;let m=u.content.filter(a=>!(a.type==="tool_use"&&d.has(String(a.id))||a.type==="tool_result"&&c.has(String(a.tool_use_id))));return m.length<u.content.length?(i=!0,{...u,content:m}):u});i&&(t.messages=r)}function Ue(t,e,o,n,d){if(d){let y=structuredClone(n);if(e.model&&(y.model=e.model),he(y),o.modelLimits){let{maxOutputTokens:M}=o.modelLimits,h=typeof y.max_tokens=="number"?y.max_tokens:M;(y.max_tokens===void 0||h>M)&&(y.max_tokens=Math.min(h,M))}return JSON.stringify(y)}let c=!!(e.model&&n.model!==e.model),i=!1,r=!1,u=0;if(o.modelLimits){u=o.modelLimits.maxOutputTokens;let y=j.exec(t);y?i=parseInt(y[1],10)>u:typeof n.max_tokens!="number"&&(r=!0)}if(!c&&!i&&!r)return t;if(r){let y={...n};return e.model&&(y.model=e.model),y.max_tokens=u,JSON.stringify(y)}let m=[];c&&m.push(F.source),i&&m.push(j.source);let a=new RegExp(m.join("|"),"g"),s=c?`"model":"${e.model}"`:null,p=i?`"max_tokens":${u}`:null,l=n.model,f=!1;return t.replace(a,y=>s&&F.test(y)?(F.lastIndex=0,!f&&l&&(console.warn(`Routing override: ${l} -> ${e.model} via ${o.name}`),f=!0),s):p&&j.test(y)?(j.lastIndex=0,p):y)}async function fe(t,e,o,n,d,c=0){let i=n.url.replace(qe,"");e.model&&(o.actualModel=e.model);let r=je(t.baseUrl,i),u;if((n.headers.get("content-type")||"").includes("application/json"))try{let h=o.parsedBody??JSON.parse(o.rawBody),w=!1;e.model&&h.model!==e.model&&(w=!0);let b=c>0;if(b&&(w=!0),t.modelLimits){let{maxOutputTokens:v}=t.modelLimits,T=typeof h.max_tokens=="number"?h.max_tokens:v;(h.max_tokens===void 0||T>v)&&(w=!0)}if(w)if(c===0&&!b)u=Ue(o.rawBody,e,t,h,!1);else{let v=structuredClone(h);if(e.model){let T=v.model;v.model=e.model,T&&T!==e.model&&console.warn(`Routing override: ${T} -> ${e.model} via ${t.name}`)}if(b&&he(v),t.modelLimits){let{maxOutputTokens:T}=t.modelLimits,P=typeof v.max_tokens=="number"?v.max_tokens:T;(v.max_tokens===void 0||P>T)&&(v.max_tokens=Math.min(P,T))}u=JSON.stringify(v)}else u=o.rawBody}catch{u=o.rawBody}else u=o.rawBody;let a=We(n.headers,t,o.requestId);a.set("content-length",Buffer.byteLength(u,"utf-8").toString());let s=new AbortController,p=setTimeout(()=>s.abort(),t.timeout),l=t.ttfbTimeout??15e3,f=!1,g=null,y=new Promise((h,w)=>{g=setTimeout(()=>{f=!0,s.abort(),w(new Error(`TTFB timeout after ${l}ms`))},l)}),M;if(d){if(d.aborted){clearTimeout(p),g&&clearTimeout(g);let w=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(w,{status:502,headers:{"content-type":"application/json","content-length":O.encode(w).byteLength.toString()}})}let h=()=>{clearTimeout(p),g&&clearTimeout(g)};M=d.addEventListener("abort",h,{once:!0})}try{let h=await Promise.race([Ee(r,{method:"POST",headers:a,body:u,signal:s.signal,dispatcher:t._agent}),y]);g&&clearTimeout(g);let w=t.stallTimeout??3e4,b=new Ie,v=`Body stalled: no data after ${w}ms`,T=setTimeout(()=>{I({requestId:o.requestId,model:String(o.actualModel??e.model??""),tier:"",state:"error",message:v,timestamp:Date.now()}),b.destroy(new Error(v))},w);b.on("data",()=>{clearTimeout(T),T=setTimeout(()=>{I({requestId:o.requestId,model:String(o.actualModel??e.model??""),tier:"",state:"error",message:v,timestamp:Date.now()}),b.destroy(new Error(v))},w)}),b.on("end",()=>{clearTimeout(T)}),b.on("error",()=>{clearTimeout(T)}),h.body.pipe(b);let P=new Response(b,{status:h.statusCode,headers:h.headers});return clearTimeout(p),P}catch(h){clearTimeout(p),g&&clearTimeout(g);let w=f?`Provider "${t.name}" timed out waiting for first byte after ${l}ms`:h instanceof DOMException&&h.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${h.message}`,b=JSON.stringify({type:"error",error:{type:"overloaded_error",message:w}});return new Response(b,{status:502,headers:{"content-type":"application/json","content-length":O.encode(b).byteLength.toString()}})}finally{M?.()}}async function ge(t,e,o,n,d,c,i){let r=ie(t);if(r<=1){L.increment(t.name);let l=Date.now();try{let f=await fe(t,e,o,n,d,c);return $.record(t.name,Date.now()-l),f}finally{L.decrement(t.name)}}i?.warn("Hedging request",{requestId:o.requestId,provider:t.name,count:r,cv:Math.round($.getCV(t.name)*100)/100,inFlight:L.get(t.name),maxConcurrent:t.concurrentLimit});let u=Date.now(),m=[];for(let l=0;l<r;l++)L.increment(t.name),m.push(fe(t,e,o,n,d,c).finally(()=>L.decrement(t.name)));let a=m.map((l,f)=>l.then(g=>({response:g,hedgeIndex:f}))),s=new Set,p=[];try{for(;s.size<a.length;){let l=a.filter((g,y)=>!s.has(y));if(l.length===0)break;let f=await Promise.race(l);if(s.add(f.hedgeIndex),t._circuitBreaker&&t._circuitBreaker.recordResult(f.response.status),f.response.status>=200&&f.response.status<300){$.record(t.name,Date.now()-u);for(let g=0;g<a.length;g++)s.has(g)||(t._circuitBreaker&&t._circuitBreaker.recordResult(502),a[g].then(y=>{try{y.response.body?.cancel()}catch{}}));for(let g of p)try{g.body?.cancel()}catch{}return f.response}p.push(f.response)}for(let l of p)try{l.body?.cancel()}catch{}return p[0]??new Response(JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${t.name}" all hedged requests failed`}}),{status:502,headers:{"content-type":"application/json"}})}catch{for(let l of p)try{l.body?.cancel()}catch{}return p[0]??new Response(JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${t.name}" hedging failed`}}),{status:502,headers:{"content-type":"application/json"}})}}async function ye(t,e,o,n,d,c){if(e.length<=1){let s=e[0],p=t.get(s.provider);if(!p){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${s.provider}`}});return new Response(f,{status:502,headers:{"content-type":"application/json","content-length":O.encode(f).byteLength.toString()}})}if(p._circuitBreaker&&!p._circuitBreaker.canProceed().allowed){c?.warn("Provider skipped by circuit breaker",{requestId:o.requestId,provider:s.provider});let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${s.provider}" skipped by circuit breaker`}});return new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}})}return d?.(s.provider,0),await ge(p,s,o,n,void 0,0,c)}let i=new AbortController,r=new Set,u=[];async function m(s){let p=e[s],l=t.get(p.provider);if(!l){let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${p.provider}`}});return{response:new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}}),index:s}}let f;if(l._circuitBreaker){let g=l._circuitBreaker.canProceed();if(!g.allowed){c?.warn("Provider skipped by circuit breaker",{requestId:o.requestId,provider:p.provider});let y=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${p.provider}" skipped by circuit breaker`}});return{response:new Response(y,{status:502,headers:{"content-type":"application/json","content-length":O.encode(y).byteLength.toString()}}),index:s}}f=g.probeId}d?.(p.provider,s);try{return{response:await ge(l,p,o,n,i.signal,s,c),index:s}}catch{l._circuitBreaker&&l._circuitBreaker.recordResult(502,f);let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${p.provider}" failed`}});return{response:new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}}),index:s}}}let a=[];for(let s=0;s<e.length;s++)s===0?a.push(m(0)):a.push(new Promise(p=>{setTimeout(()=>{if(i.signal.aborted){let l=JSON.stringify({type:"error",error:{type:"api_error",message:"Cancelled by race winner"}});p({response:new Response(l,{status:502,headers:{"content-type":"application/json"}}),index:s});return}m(s).then(p)},$e)}));try{for(;r.size<a.length;){let p=a.filter((f,g)=>!r.has(g));if(p.length===0)break;let l=await Promise.race(p);if(r.add(l.index),l.response.status>=200&&l.response.status<300){i.abort();for(let f of u)try{f.response.body?.cancel()}catch{}return l.response}if(!Ne(l.response.status)){if(i.abort(),l.response.status===400&&l.response.body)try{let f=await l.response.text(),g=De(l.response.status,f);return g||new Response(f,{status:l.response.status,statusText:l.response.statusText,headers:l.response.headers})}catch{return l.response}return l.response}u.push(l)}if(i.abort(),u.length>0)return u[0].response;let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json","content-length":O.encode(s).byteLength.toString()}})}catch{i.abort();let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json","content-length":O.encode(s).byteLength.toString()}})}}import{randomUUID as ze}from"crypto";import{gzip as Je}from"zlib";import{promisify as Fe}from"util";var Ge=Fe(Je),G={"claude-opus-4-6":2e5,"claude-sonnet-4-6":2e5,"claude-haiku-4-5-20251001":2e5,"claude-3-5-sonnet":2e5,"claude-3-5-haiku":2e5,"glm-4.7":128e3,"glm-5-turbo":128e3};function K(t){if(G[t])return G[t];for(let[e,o]of Object.entries(G))if(t.startsWith(e))return o;return 0}function X(t,e,o){let n=o+t+e;return n<=0?0:Math.round(t/n*1e3)/10}function V(t,e,o,n,d){if(d<=0)return 0;let c=t+e+o+n;return Math.round(c/d*1e3)/10}function Q(t,e,o){return new Response(JSON.stringify({type:"error",error:{type:t,message:e}}),{status:502,headers:{"content-type":"application/json","x-request-id":o}})}function Ke(t){let e=t.message?.usage??t.usage;if(!e)return{inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0};let o=e.input_tokens??e.prompt_tokens??0,n=e.output_tokens??e.completion_tokens??0,d=e.cache_read_input_tokens??0,c=e.cache_creation_input_tokens??0;return{inputTokens:o,outputTokens:n,cacheReadTokens:d,cacheCreationTokens:c}}function Xe(t,e,o,n,d,c){let i=new TextDecoder,r={input:0,output:0,cacheRead:0,cacheCreation:0},u="",m="",a=4096,s=0,p=0,l=0,f=0,g="",y=null,M=250,h=0,w=!0,b="",v=100,T=S=>{for(let C of S.split(`
|
|
3
3
|
|
|
4
4
|
`)){if(!C)continue;let _=C.split(`
|
|
5
5
|
`).find(R=>R.startsWith("data:"));if(_)try{let R=JSON.parse(_.slice(5));if(_.includes('"usage"')){let k=Ke(R);k.inputTokens>r.input&&(r.input=k.inputTokens),k.outputTokens>r.output&&(r.output=k.outputTokens),k.cacheReadTokens>r.cacheRead&&(r.cacheRead=k.cacheReadTokens),k.cacheCreationTokens>r.cacheCreation&&(r.cacheCreation=k.cacheCreationTokens)}let x=R.delta;x&&typeof x.text=="string"&&(b+=x.text,b.length>v&&(b=b.slice(-v)));let A=R.choices;if(A?.[0]){let k=A[0].delta;k&&typeof k.content=="string"&&(b+=k.content,b.length>v&&(b=b.slice(-v)))}}catch{}}},P=S=>{if(!S.includes('"usage"')){let k=[...S.matchAll(/"text"\s*:\s*"((?:[^"\\]|\\.)*)"/g)];if(k.length>0){let D=k[k.length-1][1].replace(/\\n/g,`
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/hedging.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { latencyTracker } from \"./hedging.js\";\nimport { attachWebSocket } from \"./ws.js\";\nimport { startMonitor } from \"./monitor.js\";\n\n// Read version from package.json at startup\nconst VERSION: string = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\")).version;\n\nfunction parseArgs(argv: string[]): { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } {\n const args: { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } = { verbose: false, help: false, daemon: false, monitor: false, gui: false };\n for (let i = 2; i < argv.length; i++) {\n switch (argv[i]) {\n case \"-p\":\n case \"--port\":\n const portStr = argv[++i];\n if (!portStr || isNaN(parseInt(portStr, 10))) {\n console.error(\"Error: -p/--port requires a number\");\n process.exit(1);\n }\n args.port = parseInt(portStr, 10);\n break;\n case \"-c\":\n case \"--config\":\n const configPath = argv[++i];\n if (!configPath) {\n console.error(\"Error: -c/--config requires a path\");\n process.exit(1);\n }\n args.config = configPath;\n break;\n case \"-v\":\n case \"--verbose\":\n args.verbose = true;\n break;\n case \"-h\":\n case \"--help\":\n args.help = true;\n break;\n case \"--daemon\":\n args.daemon = true;\n break;\n case \"--monitor\":\n args.monitor = true;\n break;\n }\n }\n return args;\n}\n\nfunction printHelp() {\n console.log(`\nModelWeaver — Multi-provider model orchestration proxy for Claude Code\n\nUsage: modelweaver [command] [options]\n\nCommands:\n init [--quick] Run interactive setup wizard (--quick for express mode)\n start Start as background daemon\n stop Stop background daemon\n status Show daemon status\n remove Stop daemon and remove PID + log files\n reload Reload daemon worker (load fresh code after build)\n install Install launchd service (auto-start at login)\n uninstall Uninstall launchd service\n gui Launch the GUI (downloads if needed)\n\nOptions:\n -p, --port <number> Server port (default: from config)\n -c, --config <path> Config file path (auto-detected)\n -v, --verbose Enable debug logging (default: off)\n -h, --help Show this help\n\nConfig locations (first found wins):\n ./modelweaver.yaml\n ~/.modelweaver/config.yaml\n`);\n}\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n // Load .env file if present (created by modelweaver init)\n try {\n const dotenv = await import('dotenv');\n const { existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const home = process.env.HOME || process.env.USERPROFILE || '';\n // Try cwd/.env first, then ~/.modelweaver/.env, then ~/.env\n const paths = [\n join(process.cwd(), '.env'),\n join(home, '.modelweaver', '.env'),\n join(home, '.env'),\n ];\n for (const p of paths) {\n if (existsSync(p)) {\n dotenv.config({ path: p });\n break;\n }\n }\n } catch {\n // dotenv not installed or .env not present — continue without it\n }\n\n // Handle 'init' subcommand — dynamic import to avoid loading prompts for normal startup\n if (process.argv[2] === 'init') {\n const quick = process.argv.includes('--quick') || process.argv.includes('-q');\n const { runInit } = await import('./init.js');\n await runInit({ quick });\n process.exit(0);\n }\n\n // Handle 'start' subcommand\n if (process.argv[2] === 'start') {\n const { startDaemon } = await import('./daemon.js');\n const result = await startDaemon(args.config, args.port, args.verbose);\n console.log(` ${result.message}`);\n console.log(` Log file: ${result.logPath}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'stop' subcommand\n if (process.argv[2] === 'stop') {\n const { stopDaemon } = await import('./daemon.js');\n const result = await stopDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'status' subcommand\n if (process.argv[2] === 'status') {\n const { statusDaemon } = await import('./daemon.js');\n const result = await statusDaemon();\n console.log(` ${result.message}`);\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n const installed = svc.isInstalled();\n if (installed) {\n console.log(` Service: installed`);\n } else {\n console.log(` Service: not installed (run \"modelweaver install\" to enable auto-start)`);\n }\n } catch (err) {\n console.log(` Service: ${err instanceof Error ? err.message : String(err)}`);\n }\n process.exit(0);\n }\n\n // Handle 'remove' subcommand — stop + clean up PID and log files\n if (process.argv[2] === 'remove') {\n const { removeDaemon } = await import('./daemon.js');\n const result = await removeDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'install' subcommand — install platform service\n if (process.argv[2] === 'install') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n await svc.install();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'uninstall' subcommand — uninstall platform service\n if (process.argv[2] === 'uninstall') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n svc.uninstall();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'gui' subcommand\n if (process.argv[2] === 'gui') {\n const { launchGui } = await import('./gui-launcher.js');\n await launchGui();\n process.exit(0);\n }\n\n // Handle 'reload' subcommand\n if (process.argv[2] === 'reload') {\n const { reloadDaemon } = await import('./daemon.js');\n await reloadDaemon(args.port);\n process.exit(0);\n }\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n // Load config\n let config;\n let configPath;\n try {\n const result = await loadConfig(args.config);\n config = result.config;\n configPath = result.configPath;\n } catch (error) {\n console.error(`Config error: ${(error as Error).message}`);\n process.exit(1);\n }\n\n // CLI port override\n const port = args.port || config.server.port;\n const host = config.server.host;\n const logLevel: LogLevel = args.verbose ? \"debug\" : \"info\";\n\n // Initialize metrics store\n const metricsStore = new MetricsStore();\n\n // --- Monitor mode (spawns daemon child, auto-restarts on crash) ---\n if (args.monitor) {\n await startMonitor(args);\n return;\n }\n\n // --- Daemon mode ---\n if (args.daemon) {\n const { removeWorkerPidFile, writeWorkerPidFile, createDebouncedReload, getLogPath } = await import('./daemon.js');\n const { reloadConfig } = await import('./config.js');\n const { createWriteStream, watch } = await import('node:fs');\n const { createLogger } = await import('./logger.js');\n const logger = createLogger(logLevel);\n\n // Prevent silent crashes from killing the daemon worker\n process.on('uncaughtException', (err) => {\n logger.error('Uncaught exception (daemon survived)', { error: err.message, stack: err.stack });\n });\n process.on('unhandledRejection', (reason) => {\n logger.error('Unhandled rejection (daemon survived)', { reason: String(reason) });\n });\n\n // Write worker PID file (monitor owns modelweaver.pid)\n await writeWorkerPidFile(process.pid);\n\n // Redirect stdout/stderr to log file\n const logStream = createWriteStream(getLogPath(), { flags: 'a' });\n logStream.on('error', () => { /* ignore write errors to log file */ });\n process.stdout.write = logStream.write.bind(logStream) as typeof process.stdout.write;\n process.stderr.write = logStream.write.bind(logStream) as typeof process.stderr.write;\n\n // Create app with mutable config\n const handle = createApp(config, logLevel, metricsStore);\n\n // Hot-reload: watch config file for changes\n let configWatcher: ReturnType<typeof watch> | null = null;\n if (configPath) {\n const debounced = createDebouncedReload(async () => {\n try {\n const newConfig = await reloadConfig(configPath);\n handle.setConfig(newConfig);\n latencyTracker.prune([...newConfig.providers.keys()]);\n logger.info(\"Config reloaded\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed — keeping old config\", { error: (err as Error).message });\n }\n }, 300);\n\n try {\n configWatcher = watch(configPath, () => {\n debounced.reload();\n });\n configWatcher.on('error', () => {\n // fs.watch failed — silently disable hot-reload\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n });\n } catch {\n // fs.watch not available — hot-reload disabled\n }\n }\n\n // SIGUSR1 triggers config hot-reload\n // Note: SIGUSR1 is POSIX-only; this handler is a no-op on Windows.\n process.on('SIGUSR1', async () => {\n try {\n const newConfig = await reloadConfig(configPath!);\n handle.setConfig(newConfig);\n latencyTracker.prune([...newConfig.providers.keys()]);\n logger.info(\"Config reloaded (SIGUSR1)\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed (SIGUSR1)\", { error: (err as Error).message });\n }\n });\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = async () => {\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n await removeWorkerPidFile();\n logStream.end();\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return; // Don't fall through to foreground mode\n }\n\n // --- Foreground mode ---\n const handle = createApp(config, logLevel, metricsStore);\n\n // Print startup info\n console.log(`\\n ModelWeaver v${VERSION}`);\n console.log(` Listening: http://${host}:${port}`);\n console.log(` Config: ${configPath}\\n`);\n\n console.log(\" Routes:\");\n for (const [tier, entries] of config.routing) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${tier.padEnd(8)} → ${providerList}`);\n }\n console.log();\n\n if (config.modelRouting.size > 0) {\n console.log(\" Model Routes:\");\n for (const [model, entries] of config.modelRouting) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${model.padEnd(20)} → ${providerList}`);\n }\n console.log();\n }\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nmain();\n","// src/server.ts\nimport { Hono } from \"hono\";\nimport { resolveRequest, clearRoutingCache } from \"./router.js\";\nimport { forwardWithFallback } from \"./proxy.js\";\nimport { createLogger, type LogLevel } from \"./logger.js\";\nimport type { AppConfig, ProviderConfig, RequestContext } from \"./types.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nimport type { MetricsStore } from \"./metrics.js\";\nimport { broadcastStreamEvent } from \"./ws.js\";\nimport type { StreamEvent } from \"./types.js\";\n\nconst MODEL_CONTEXT_WINDOWS: Record<string, number> = {\n 'claude-opus-4-6': 200000,\n 'claude-sonnet-4-6': 200000,\n 'claude-haiku-4-5-20251001': 200000,\n 'claude-3-5-sonnet': 200000,\n 'claude-3-5-haiku': 200000,\n 'glm-4.7': 128000,\n 'glm-5-turbo': 128000,\n};\n\nfunction getContextWindow(model: string): number {\n // Exact match first, then prefix match\n if (MODEL_CONTEXT_WINDOWS[model]) return MODEL_CONTEXT_WINDOWS[model];\n for (const [key, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {\n if (model.startsWith(key)) return size;\n }\n return 0;\n}\n\nfunction computeCacheHitRate(cacheRead: number, cacheCreation: number, input: number): number {\n const totalInput = input + cacheRead + cacheCreation;\n if (totalInput <= 0) return 0;\n return Math.round((cacheRead / totalInput) * 1000) / 10;\n}\n\nfunction computeContextPercent(input: number, cacheRead: number, cacheCreation: number, output: number, contextWindow: number): number {\n if (contextWindow <= 0) return 0;\n const total = input + cacheRead + cacheCreation + output;\n return Math.round((total / contextWindow) * 1000) / 10;\n}\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp, outputTokens: out, cacheReadTokens: cacheRead, cacheCreationTokens: cacheCreation };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n let firstChunk = true;\n\n // Response text preview (last 100 chars for progress bar tooltip)\n let responsePreview = \"\";\n const PREVIEW_MAX = 100;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n\n // Extract usage (token counts)\n if (dataLine.includes('\"usage\"')) {\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n if (usage.cacheReadTokens > tokens.cacheRead) tokens.cacheRead = usage.cacheReadTokens;\n if (usage.cacheCreationTokens > tokens.cacheCreation) tokens.cacheCreation = usage.cacheCreationTokens;\n }\n\n // Extract text content for preview\n // Anthropic format: content_block_delta with delta.text\n const delta = data.delta as Record<string, unknown> | undefined;\n if (delta && typeof delta.text === \"string\") {\n responsePreview += delta.text;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n // OpenAI format: choices[0].delta.content\n const choices = data.choices as Array<Record<string, unknown>> | undefined;\n if (choices?.[0]) {\n const choiceDelta = choices[0].delta as Record<string, unknown> | undefined;\n if (choiceDelta && typeof choiceDelta.content === \"string\") {\n responsePreview += choiceDelta.content;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n }\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n // Fast bailout: most chunks don't contain usage data\n if (!text.includes('\"usage\"')) {\n // Extract text content for preview from JSON responses even without usage\n const anthContent = [...text.matchAll(/\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g)];\n if (anthContent.length > 0) {\n const lastText = anthContent[anthContent.length - 1][1].replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n responsePreview += lastText;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n return;\n }\n\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n\n // Extract text content for preview from JSON responses\n // Anthropic format: \"content\":[{\"type\":\"text\",\"text\":\"...\"}]\n const anthContent = [...text.matchAll(/\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g)];\n if (anthContent.length > 0) {\n const lastText = anthContent[anthContent.length - 1][1].replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n responsePreview += lastText;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n };\n\n const recordMetrics = (inp: number, out: number, cacheRead: number = 0, cacheCreation: number = 0) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n });\n\n // Broadcast completion event\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n cacheHitRate: computeCacheHitRate(cacheRead, cacheCreation, inp),\n contextPercent: computeContextPercent(inp, cacheRead, cacheCreation, out, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (firstChunk || now - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = now;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(tokens.cacheRead, tokens.cacheCreation, tokens.input),\n contextPercent: computeContextPercent(tokens.input, tokens.cacheRead, tokens.cacheCreation, tokens.output, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output, tokens.cacheRead, tokens.cacheCreation);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (firstChunk || nowJson - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = nowJson;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(cacheReadTokens, cacheCreationTokens, inputTokens),\n contextPercent: computeContextPercent(inputTokens, cacheReadTokens, cacheCreationTokens, outputTokens, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n // Broadcast TTFB event — headers received from upstream (skip for error responses)\n if (response.status < 400) {\n let headerSize = 17; // approximate HTTP status line: \"HTTP/1.1 200 OK\\r\\n\"\n response.headers.forEach((v, k) => { headerSize += k.length + v.length + 4; });\n headerSize += 2; // trailing CRLF\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"ttfb\",\n status: response.status,\n headerSize,\n timestamp: Date.now(),\n });\n });\n }\n\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport { PassThrough } from \"node:stream\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { latencyTracker, inFlightCounter, computeHedgingCount } from './hedging.js';\nimport { broadcastStreamEvent } from './ws.js';\n\n/** Headers forwarded as-is to upstream */\nconst FORWARD_HEADERS = new Set([\n \"anthropic-version\",\n \"anthropic-beta\",\n \"content-type\",\n \"accept\",\n]);\n\n/** Pre-compiled regex for normalizing duplicate slashes in URL paths */\nconst MULTI_SLASH = /\\/+/g;\n\n/** Pre-compiled regex for stripping origin from URLs */\nconst STRIP_ORIGIN = /^https?:\\/\\/[^/]+/;\n\n/** Pre-compiled regexes for targeted body replacements (preserve prompt caching) */\nconst MODEL_KEY_REGEX = /\"model\"\\s*:\\s*\"([^\"]*)\"/;\nconst MAX_TOKENS_REGEX = /\"max_tokens\"\\s*:\\s*(\\d+)/;\n\n/** Module-level TextEncoder — avoids per-request allocation */\nconst textEncoder = new TextEncoder();\n\n/** Delay (ms) before starting backup providers in staggered race */\nconst SPECULATIVE_DELAY = 3000;\n\nexport function isRetriable(status: number): boolean {\n return status === 429 || status >= 500;\n}\n\nconst CONTEXT_WINDOW_PATTERNS = [\n 'context window', 'context_limit', 'token limit',\n 'prompt is too long', 'max tokens', 'input too large', 'too many tokens',\n];\n\nfunction isContextWindowError(status: number, body: string): boolean {\n if (status !== 400) return false;\n const lower = body.toLowerCase();\n return CONTEXT_WINDOW_PATTERNS.some(p => lower.includes(p));\n}\n\nfunction handleContextWindowError(status: number, body: string): Response | null {\n if (!isContextWindowError(status, body)) return null;\n\n console.warn('[context-compact] Upstream context window limit detected');\n try {\n const flagDir = path.join(os.homedir(), '.claude', 'state');\n fs.mkdirSync(flagDir, { recursive: true });\n fs.writeFileSync(path.join(flagDir, 'context-compact-needed'), Date.now().toString());\n } catch {\n // Best-effort flag write\n }\n\n const enhanced = JSON.stringify({\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: \"Context window limit reached. Run /compact to reduce conversation size, then retry.\",\n },\n });\n return new Response(enhanced, {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nexport function buildOutboundUrl(baseUrl: string, incomingPath: string): string {\n let basePath = \"\";\n let origin = baseUrl;\n const slashIndex = baseUrl.indexOf('/', baseUrl.indexOf('//') + 2);\n if (slashIndex !== -1) {\n origin = baseUrl.substring(0, slashIndex);\n basePath = baseUrl.substring(slashIndex);\n }\n\n let incomingQuery = \"\";\n let incomingOnly = incomingPath;\n const qIndex = incomingPath.indexOf('?');\n if (qIndex !== -1) {\n incomingOnly = incomingPath.substring(0, qIndex);\n incomingQuery = incomingPath.substring(qIndex);\n }\n\n // Deduplicate /v1 when base URL path already ends with it and incoming path starts with it.\n // e.g. baseUrl=\"https://api.fireworks.ai/inference/v1\" + path=\"/v1/chat/completions\"\n // → \"/inference/v1/chat/completions\" (not \"/inference/v1/v1/chat/completions\")\n let resolvedPath;\n if (basePath.endsWith('/v1') && incomingOnly.startsWith('/v1')) {\n resolvedPath = basePath + incomingOnly.substring(3);\n } else {\n resolvedPath = basePath + incomingOnly;\n }\n\n // Normalize duplicate slashes\n resolvedPath = resolvedPath.replace(MULTI_SLASH, \"/\");\n\n return origin + resolvedPath + incomingQuery;\n}\n\nexport function buildOutboundHeaders(\n incomingHeaders: Headers,\n provider: ProviderConfig,\n requestId: string\n): Headers {\n const headers = new Headers();\n\n // Forward select headers as-is\n for (const name of FORWARD_HEADERS) {\n const value = incomingHeaders.get(name);\n if (value) headers.set(name, value);\n }\n\n // Rewrite auth headers based on provider authType\n if (provider.authType === \"bearer\") {\n headers.set(\"Authorization\", `Bearer ${provider.apiKey}`);\n } else {\n headers.set(\"x-api-key\", provider.apiKey);\n }\n headers.set(\"x-request-id\", requestId);\n\n // Set host to provider hostname (use cached components when available)\n const cachedHost = provider._cachedHost;\n if (cachedHost) {\n headers.set(\"host\", cachedHost);\n } else {\n try {\n const url = new URL(provider.baseUrl);\n headers.set(\"host\", url.host);\n } catch {\n // If baseUrl is not a valid URL, skip host rewrite\n }\n }\n\n return headers;\n}\n\n/**\n * Remove orphaned tool_use/tool_result pairs from the messages array.\n *\n * In Anthropic's format:\n * - tool_use blocks live inside assistant message content: { role: \"assistant\", content: [{ type: \"tool_use\", id: \"call_xxx\", ... }] }\n * - tool_result blocks live inside user message content: { role: \"user\", content: [{ type: \"tool_result\", tool_use_id: \"call_xxx\", ... }] }\n *\n * A tool_result is orphaned if its tool_use_id references a tool_use not in any assistant content block.\n * A tool_use is orphaned if its id has no matching tool_result in any user content block.\n *\n * Single-pass: collects all IDs, computes orphans, then filters once.\n */\nfunction cleanOrphanedToolMessages(body: Record<string, unknown>): void {\n const messages = body.messages;\n if (!Array.isArray(messages)) return;\n\n const allToolUseIds = new Set<string>();\n const allToolResultIds = new Set<string>();\n\n // Single collection pass — gather every tool_use and tool_result ID\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!Array.isArray(msg.content)) continue;\n\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.id) {\n allToolUseIds.add(String(block.id));\n } else if (block.type === \"tool_result\" && block.tool_use_id) {\n allToolResultIds.add(String(block.tool_use_id));\n }\n }\n }\n\n // Compute orphaned IDs: tool_use without a matching tool_result, and vice versa.\n // A tool_use is kept if ANY tool_result references it (even if that tool_result\n // itself is orphaned — we only strip truly dangling references).\n const orphanedToolUseIds = new Set<string>();\n const orphanedToolResultIds = new Set<string>();\n for (const id of allToolUseIds) {\n if (!allToolResultIds.has(id)) orphanedToolUseIds.add(id);\n }\n for (const id of allToolResultIds) {\n if (!allToolUseIds.has(id)) orphanedToolResultIds.add(id);\n }\n\n if (orphanedToolUseIds.size === 0 && orphanedToolResultIds.size === 0) return;\n\n // Single filter pass — remove orphaned blocks from content arrays\n let changed = false;\n const cleaned = messages.map((msg: Record<string, unknown>) => {\n if (!Array.isArray(msg.content)) return msg;\n\n const filtered = msg.content.filter((block: Record<string, unknown>) => {\n if (block.type === \"tool_use\" && orphanedToolUseIds.has(String(block.id))) return false;\n if (block.type === \"tool_result\" && orphanedToolResultIds.has(String(block.tool_use_id))) return false;\n return true;\n });\n\n if (filtered.length < msg.content.length) {\n changed = true;\n return { ...msg, content: filtered };\n }\n return msg;\n });\n\n if (changed) {\n body.messages = cleaned;\n }\n}\n\n/**\n * Apply targeted string replacements to rawBody to preserve prompt caching.\n * On the primary attempt (chainIndex === 0), we avoid full JSON.stringify which\n * breaks Anthropic's cache breakpoints (position-sensitive, whitespace/order-sensitive).\n * Falls back to full JSON parse/mutate/stringify when structural changes are needed.\n */\nfunction applyTargetedReplacements(\n rawBody: string,\n entry: RoutingEntry,\n provider: ProviderConfig,\n parsed: Record<string, unknown>,\n needsOrphanClean: boolean,\n): string {\n // If orphan cleaning is needed, we must do full JSON parse (structural changes to messages)\n if (needsOrphanClean) {\n // deep clone required: cleanOrphanedToolMessages mutates the messages array in-place\n const mutable = structuredClone(parsed);\n if (entry.model) mutable.model = entry.model;\n cleanOrphanedToolMessages(mutable);\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requested = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requested > maxOutputTokens) {\n mutable.max_tokens = Math.min(requested, maxOutputTokens);\n }\n }\n return JSON.stringify(mutable);\n }\n\n // Targeted replacement path -- only model override and/or max_tokens clamping.\n // Single-pass: build a combined regex with alternation so the entire raw body\n // string is scanned and replaced in one call instead of per-pattern copies.\n const needsModel = !!(entry.model && (parsed.model as string | undefined) !== entry.model);\n let needsMaxTokensClamp = false;\n let needsMaxTokensAdd = false;\n let maxOutputTokens = 0;\n if (provider.modelLimits) {\n maxOutputTokens = provider.modelLimits.maxOutputTokens;\n const m = MAX_TOKENS_REGEX.exec(rawBody);\n if (m) {\n needsMaxTokensClamp = parseInt(m[1], 10) > maxOutputTokens;\n } else if (typeof parsed.max_tokens !== \"number\") {\n needsMaxTokensAdd = true;\n }\n }\n\n if (!needsModel && !needsMaxTokensClamp && !needsMaxTokensAdd) return rawBody;\n\n // max_tokens not in body at all — must add via JSON parse (structural change)\n if (needsMaxTokensAdd) {\n const mutable = { ...parsed };\n if (entry.model) mutable.model = entry.model;\n mutable.max_tokens = maxOutputTokens;\n return JSON.stringify(mutable);\n }\n\n // Build combined regex for single-pass replacement\n const patterns: string[] = [];\n if (needsModel) patterns.push(MODEL_KEY_REGEX.source);\n if (needsMaxTokensClamp) patterns.push(MAX_TOKENS_REGEX.source);\n const combinedRegex = new RegExp(patterns.join(\"|\"), \"g\");\n\n // Capture values for the replacer (avoid repeated property access)\n const modelRepl = needsModel ? `\"model\":\"${entry.model}\"` : null;\n const tokensRepl = needsMaxTokensClamp ? `\"max_tokens\":${maxOutputTokens}` : null;\n const origModel = parsed.model as string | undefined;\n let modelLogged = false;\n\n const result = rawBody.replace(combinedRegex, (match: string) => {\n if (modelRepl && MODEL_KEY_REGEX.test(match)) {\n MODEL_KEY_REGEX.lastIndex = 0;\n if (!modelLogged && origModel) {\n console.warn(`Routing override: ${origModel} -> ${entry.model} via ${provider.name}`);\n modelLogged = true;\n }\n return modelRepl;\n }\n if (tokensRepl && MAX_TOKENS_REGEX.test(match)) {\n MAX_TOKENS_REGEX.lastIndex = 0;\n return tokensRepl;\n }\n return match;\n });\n\n return result;\n}\n\n/**\n * Forward a request to a single provider.\n * Uses ctx.parsedBody when available (avoids re-parsing); falls back to ctx.rawBody.\n * incomingRequest is used for metadata only (url, headers).\n * Returns the Response object — caller decides fallback logic.\n */\nexport async function forwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n externalSignal?: AbortSignal,\n chainIndex: number = 0,\n): Promise<Response> {\n const outgoingPath = incomingRequest.url.replace(STRIP_ORIGIN, \"\");\n\n // Set actualModel early so metrics always record the routed model,\n // even if body parsing or the fetch itself fails\n if (entry.model) {\n ctx.actualModel = entry.model;\n }\n\n // Build outbound URL from provider base URL and request path\n const url = buildOutboundUrl(provider.baseUrl, outgoingPath);\n\n // Prepare body — prefer raw passthrough to preserve upstream prompt caching.\n // Only parse and re-serialize when a modification is actually required,\n // because Anthropic's cache breakpoints are position-sensitive and\n // JSON.stringify changes whitespace / key order, breaking cache hits.\n let body: string;\n const contentType = incomingRequest.headers.get(\"content-type\") || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n const parsed = (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody\n ?? JSON.parse(ctx.rawBody);\n\n // Determine whether any body modification is needed\n let needsModification = false;\n\n // Check 1: Model override needed?\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n needsModification = true;\n }\n\n // Check 2: Orphan cleaning needed? (only for fallback attempts, not primary)\n // On the primary attempt (index 0), conversation history is intact.\n // Only when falling back (index > 0) do cross-provider orphans appear.\n const needsOrphanClean = chainIndex > 0;\n if (needsOrphanClean) needsModification = true;\n\n // Check 3: max_tokens clamping needed?\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof parsed.max_tokens === \"number\" ? parsed.max_tokens : maxOutputTokens;\n if (parsed.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n needsModification = true;\n }\n }\n\n if (needsModification) {\n // On primary attempt (chainIndex === 0) without orphan cleaning, use targeted\n // string replacements to preserve prompt caching. Anthropic's cache breakpoints\n // are position-sensitive -- JSON.stringify changes whitespace/order, breaking hits.\n if (chainIndex === 0 && !needsOrphanClean) {\n body = applyTargetedReplacements(ctx.rawBody, entry, provider, parsed, false);\n } else {\n // Fallback attempts: full JSON parse/mutate/stringify (caching already broken)\n // deep clone required: cleanOrphanedToolMessages may mutate the messages array in-place\n const mutable = structuredClone(parsed);\n\n if (entry.model) {\n const originalModel = mutable.model as string | undefined;\n mutable.model = entry.model;\n if (originalModel && originalModel !== entry.model) {\n console.warn(\n `Routing override: ${originalModel} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n if (needsOrphanClean) {\n cleanOrphanedToolMessages(mutable);\n }\n\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n mutable.max_tokens = Math.min(requestedMaxTokens, maxOutputTokens);\n }\n }\n\n body = JSON.stringify(mutable);\n }\n } else {\n // No modifications needed — passthrough raw body to preserve prompt caching\n body = ctx.rawBody;\n }\n } catch {\n // If body can't be parsed, send it as-is without model override\n body = ctx.rawBody;\n }\n } else {\n body = ctx.rawBody;\n }\n\n const headers = buildOutboundHeaders(incomingRequest.headers, provider, ctx.requestId);\n headers.set(\"content-length\", Buffer.byteLength(body, 'utf-8').toString());\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), provider.timeout);\n\n // TTFB timeout: fail if no response headers received within ttfbTimeout ms\n const ttfbTimeout = provider.ttfbTimeout ?? 15000;\n let ttfbTimedOut = false;\n let ttfbTimer: ReturnType<typeof setTimeout> | null = null;\n\n const ttfbPromise = new Promise<never>((_, reject) => {\n ttfbTimer = setTimeout(() => {\n ttfbTimedOut = true;\n controller.abort();\n reject(new Error(`TTFB timeout after ${ttfbTimeout}ms`));\n }, ttfbTimeout);\n }).catch(() => {}); // Swallow orphaned rejection when undiciRequest wins the race\n\n // Listen for external abort (from race cancellation) to abort this request\n let removeAbortListener: (() => void) | undefined;\n if (externalSignal) {\n if (externalSignal.aborted) {\n // Already aborted — don't even start the request\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: `Provider \"${provider.name}\" cancelled by race winner` },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n const onExternalAbort = () => {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n };\n removeAbortListener = externalSignal.addEventListener(\"abort\", onExternalAbort, { once: true }) as () => void;\n }\n\n try {\n const undiciResponse = await Promise.race([\n undiciRequest(url, {\n method: \"POST\",\n headers,\n body,\n signal: controller.signal,\n dispatcher: provider._agent,\n }),\n ttfbPromise,\n ]);\n\n // TTFB succeeded — cancel TTFB timer\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Body stall detection: pipe through PassThrough to monitor for data without\n // interfering with undici's internal stream state (no flowing mode conflict).\n const stallTimeout = provider.stallTimeout ?? 30000;\n const passThrough = new PassThrough();\n\n const stallMsg = `Body stalled: no data after ${stallTimeout}ms`;\n let stallTimerRef = setTimeout(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: String(ctx.actualModel ?? entry.model ?? \"\"),\n tier: \"\",\n state: \"error\",\n message: stallMsg,\n timestamp: Date.now(),\n });\n passThrough.destroy(new Error(stallMsg));\n }, stallTimeout);\n\n // Monitor OUR PassThrough for every data event — re-arms stall timer on each chunk\n passThrough.on(\"data\", () => {\n clearTimeout(stallTimerRef);\n stallTimerRef = setTimeout(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: String(ctx.actualModel ?? entry.model ?? \"\"),\n tier: \"\",\n state: \"error\",\n message: stallMsg,\n timestamp: Date.now(),\n });\n passThrough.destroy(new Error(stallMsg));\n }, stallTimeout);\n });\n\n passThrough.on(\"end\", () => {\n clearTimeout(stallTimerRef);\n });\n\n passThrough.on(\"error\", () => {\n clearTimeout(stallTimerRef);\n });\n\n // Pipe undici body → PassThrough. Data flows through without mode conflicts.\n undiciResponse.body.pipe(passThrough);\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n passThrough as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Network errors / timeouts — return a synthetic 502\n const message = ttfbTimedOut\n ? `Provider \"${provider.name}\" timed out waiting for first byte after ${ttfbTimeout}ms`\n : error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n } finally {\n removeAbortListener?.();\n }\n}\n\n/**\n * Race multiple providers simultaneously. Returns the first successful response.\n * Aborts all remaining requests once a winner is found.\n */\nasync function raceProviders(\n chain: RoutingEntry[],\n providers: Map<string, ProviderConfig>,\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void },\n chainOffset: number = 0,\n): Promise<Response> {\n const sharedController = new AbortController();\n\n const races = chain.map(async (entry, index): Promise<{ response: Response; index: number }> => {\n const provider = providers.get(entry.provider);\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n // Check circuit breaker\n let cbProbeId: number | undefined;\n if (provider._circuitBreaker) {\n const cb = provider._circuitBreaker.canProceed();\n if (!cb.allowed) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n cbProbeId = cb.probeId;\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, sharedController.signal, index + chainOffset);\n // Record for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status, cbProbeId);\n }\n return { response, index };\n } catch {\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(502, cbProbeId);\n }\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n });\n\n // Track completed promises to avoid double-processing\n const completed = new Set<Promise<{ response: Response; index: number }>>();\n const failures: { response: Response; index: number }[] = [];\n\n try {\n while (completed.size < races.length) {\n const pending = races.filter(r => !completed.has(r));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(races[winner.index] ?? races[0]);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n // Drain/cancel in-flight loser response bodies BEFORE aborting shared controller\n // to prevent leaked stream chunks from mid-write cancellation.\n for (const r of pending) {\n if (r !== races[winner.index]) {\n r.then(({ response }) => {\n if (response.body) {\n try { response.body.cancel(); } catch { /* already consumed */ }\n }\n }).catch(() => { /* aborted */ });\n }\n }\n sharedController.abort();\n // Cancel bodies of already-completed losing responses to free resources\n for (const f of failures) {\n try { f.response.body?.cancel(); } catch { /* ignore */ }\n }\n return winner.response;\n }\n\n // Non-retriable error — check for context window limit before propagating\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n // Retriable but not success — record and continue waiting\n failures.push(winner);\n }\n\n // All providers returned retriable errors — return the first failure\n sharedController.abort();\n if (failures.length > 0) {\n return failures[0].response;\n }\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n}\n\n/**\n * Forward a request with optional adaptive hedging.\n * When latency variance is high, sends multiple copies and returns the fastest.\n */\nasync function hedgedForwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n chainSignal: AbortSignal | undefined,\n index: number,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n const count = computeHedgingCount(provider);\n\n if (count <= 1) {\n // No hedging — single request\n inFlightCounter.increment(provider.name);\n const start = Date.now();\n try {\n const r = await forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index);\n latencyTracker.record(provider.name, Date.now() - start);\n return r;\n } finally {\n inFlightCounter.decrement(provider.name);\n }\n }\n\n // Hedged path: send multiple copies, race for first success\n logger?.warn(\"Hedging request\", {\n requestId: ctx.requestId,\n provider: provider.name,\n count,\n cv: Math.round(latencyTracker.getCV(provider.name) * 100) / 100,\n inFlight: inFlightCounter.get(provider.name),\n maxConcurrent: provider.concurrentLimit,\n });\n\n const start = Date.now();\n const launched: Promise<Response>[] = [];\n\n for (let h = 0; h < count; h++) {\n inFlightCounter.increment(provider.name);\n launched.push(\n forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index)\n .finally(() => inFlightCounter.decrement(provider.name))\n );\n }\n\n // Race: first successful response wins, cancel the rest\n // Wrap each promise so we can identify which one completed by index\n const wrapped = launched.map((p, i) =>\n p.then(response => ({ response, hedgeIndex: i }))\n );\n\n const completed = new Set<number>();\n const failures: Response[] = [];\n\n try {\n while (completed.size < wrapped.length) {\n const pending = wrapped.filter((_, i) => !completed.has(i));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.hedgeIndex);\n\n // Record each hedged copy's result for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(winner.response.status);\n }\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n latencyTracker.record(provider.name, Date.now() - start);\n // Cancel remaining — record 502 for each cancelled copy\n for (let i = 0; i < wrapped.length; i++) {\n if (!completed.has(i)) {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n wrapped[i].then(r => { try { r.response.body?.cancel(); } catch {} });\n }\n }\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return winner.response;\n }\n\n failures.push(winner.response);\n }\n\n // All hedged copies returned errors — cancel bodies, return first failure\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" all hedged requests failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n } catch {\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" hedging failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n // Single provider — no racing needed\n if (chain.length <= 1) {\n const entry = chain[0];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n\n if (provider._circuitBreaker) {\n const cb = provider._circuitBreaker.canProceed();\n if (!cb.allowed) {\n logger?.warn(\"Provider skipped by circuit breaker\", { requestId: ctx.requestId, provider: entry.provider });\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n }\n\n onAttempt?.(entry.provider, 0);\n\n const response = await hedgedForwardRequest(provider, entry, ctx, incomingRequest, undefined, 0, logger);\n\n return response;\n }\n\n // Multiple providers — staggered race\n const sharedController = new AbortController();\n const completed = new Set<number>();\n const failures: { response: Response; index: number }[] = [];\n\n async function attemptProvider(\n index: number,\n ): Promise<{ response: Response; index: number }> {\n const entry = chain[index];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n\n let cbProbeId: number | undefined;\n if (provider._circuitBreaker) {\n const cb = provider._circuitBreaker.canProceed();\n if (!cb.allowed) {\n logger?.warn(\"Provider skipped by circuit breaker\", {\n requestId: ctx.requestId,\n provider: entry.provider,\n });\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n cbProbeId = cb.probeId;\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await hedgedForwardRequest(\n provider,\n entry,\n ctx,\n incomingRequest,\n sharedController.signal,\n index,\n logger,\n );\n return { response, index };\n } catch {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502, cbProbeId);\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n }\n\n // Build staggered race promises:\n // Provider 0 starts immediately\n // Provider 1+ start after SPECULATIVE_DELAY (if race not already won)\n const races: Promise<{ response: Response; index: number }>[] = [];\n\n for (let i = 0; i < chain.length; i++) {\n if (i === 0) {\n races.push(attemptProvider(0));\n } else {\n races.push(\n new Promise<{ response: Response; index: number }>((resolve) => {\n setTimeout(() => {\n if (sharedController.signal.aborted) {\n // Race already won — resolve with a cancelled placeholder\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: \"Cancelled by race winner\" },\n });\n resolve({\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index: i,\n });\n return;\n }\n attemptProvider(i).then(resolve);\n }, SPECULATIVE_DELAY);\n }),\n );\n }\n }\n\n // Pick winner — same logic as raceProviders\n try {\n while (completed.size < races.length) {\n const pending = races.filter((_, idx) => !completed.has(idx));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.index);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n for (const f of failures) {\n try {\n f.response.body?.cancel();\n } catch {\n /* ignore */\n }\n }\n return winner.response;\n }\n\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n failures.push(winner);\n }\n\n sharedController.abort();\n if (failures.length > 0) return failures[0].response;\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n}\n","/**\n * Adaptive request hedging — sends multiple copies of a request when\n * the provider shows high latency variance, returning the fastest response.\n */\n\nimport type { ProviderConfig } from './types.js';\n\ninterface LatencySample {\n ttfbMs: number;\n timestamp: number;\n}\n\nexport class LatencyTracker {\n private samples = new Map<string, LatencySample[]>();\n private readonly maxSize: number;\n private readonly MAX_PROVIDERS = 50;\n\n constructor(maxSize = 20) {\n this.maxSize = maxSize;\n }\n\n record(provider: string, ttfbMs: number): void {\n // Cap total tracked providers to prevent unbounded growth\n if (this.samples.size >= this.MAX_PROVIDERS && !this.samples.has(provider)) {\n // Remove the first (oldest) provider key\n const firstKey = this.samples.keys().next().value;\n if (firstKey !== undefined) this.samples.delete(firstKey);\n }\n\n let window = this.samples.get(provider);\n if (!window) {\n window = [];\n this.samples.set(provider, window);\n }\n window.push({ ttfbMs, timestamp: Date.now() });\n if (window.length > this.maxSize) {\n window.splice(0, window.length - this.maxSize);\n }\n }\n\n /** Coefficient of variation (stddev / mean). Returns 0 if insufficient data. */\n getCV(provider: string): number {\n const window = this.samples.get(provider);\n if (!window || window.length < 3) return 0;\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n if (mean === 0) return 0;\n const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;\n return Math.sqrt(variance) / mean;\n }\n\n getStats(provider: string): { count: number; mean: number; cv: number } {\n const window = this.samples.get(provider);\n if (!window || window.length === 0) return { count: 0, mean: 0, cv: 0 };\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n return { count: values.length, mean: Math.round(mean), cv: Math.round(this.getCV(provider) * 100) / 100 };\n }\n\n clear(provider: string): void {\n this.samples.delete(provider);\n }\n\n /** Remove entries for providers no longer in the current config. */\n prune(activeProviders: string[]): void {\n const active = new Set(activeProviders);\n for (const key of this.samples.keys()) {\n if (!active.has(key)) {\n this.samples.delete(key);\n }\n }\n }\n}\n\nexport class InFlightCounter {\n private counts = new Map<string, number>();\n\n increment(provider: string): number {\n const count = (this.counts.get(provider) ?? 0) + 1;\n this.counts.set(provider, count);\n return count;\n }\n\n decrement(provider: string): number {\n const count = Math.max(0, (this.counts.get(provider) ?? 0) - 1);\n this.counts.set(provider, count);\n return count;\n }\n\n get(provider: string): number {\n return this.counts.get(provider) ?? 0;\n }\n}\n\nexport const latencyTracker = new LatencyTracker();\nexport const inFlightCounter = new InFlightCounter();\n\n/**\n * Compute adaptive hedging count based on latency variance and available concurrency.\n *\n * CV (coefficient of variation) drives the count:\n * CV 0.0 → 1 (no hedging, provider is consistent)\n * CV 0.5 → 2\n * CV 1.0 → 3\n * CV 1.5+ → 4\n *\n * Clamped by available concurrency slots: maxConcurrent - inFlight.\n */\nexport function computeHedgingCount(provider: ProviderConfig): number {\n const cv = latencyTracker.getCV(provider.name);\n const inFlight = inFlightCounter.get(provider.name);\n const maxConcurrent = provider.concurrentLimit ?? 1;\n const available = Math.max(1, maxConcurrent - inFlight);\n\n const adaptive = Math.max(1, Math.floor(cv * 2 + 0.5));\n return Math.min(adaptive, available);\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\nconst STREAM_WS_THROTTLE_MS = 500; // caps stream event delivery to ~2 Hz per client\nconst BACKPRESSURE_LOG_INTERVAL_MS = 10_000; // throttle backpressure warnings to once per 10s\nconst clientStreamThrottle = new WeakMap<any, number>();\n\nlet wssInstance: InstanceType<typeof import(\"ws\").WebSocketServer> | null = null;\n\n// Module-level counters for dropped events (useful for monitoring)\nlet streamDroppedCount = 0;\nlet lastBackpressureWarnTime = 0;\n\nfunction maybeLogBackpressure(source: string): void {\n const now = Date.now();\n if (now - lastBackpressureWarnTime >= BACKPRESSURE_LOG_INTERVAL_MS) {\n console.warn(`[ws] Backpressure: dropping ${source} events (total dropped stream events: ${streamDroppedCount})`);\n lastBackpressureWarnTime = now;\n }\n}\n\nexport function attachWebSocket(server: Server, metricsStore: MetricsStore): void {\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n wssInstance = wss;\n\n wss.on(\"connection\", (ws) => {\n // Send current summary as initial state\n const summary = metricsStore.getSummary();\n const initialMsg: WsMessage = { type: \"summary\", data: summary };\n ws.send(JSON.stringify(initialMsg));\n\n let pendingSummaryTimer: ReturnType<typeof setTimeout> | undefined;\n let missedPongs = 0;\n const alive = () => ws.readyState === ws.OPEN;\n\n // Subscribe to new metrics with backpressure check and debounced summary\n const unsubscribe = metricsStore.onRecord((metrics: RequestMetrics) => {\n if (!alive()) return;\n\n // Backpressure: skip send if outbound buffer is too large\n if (ws.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n // Schedule a summary update instead so the client eventually catches up\n scheduleSummaryUpdate();\n maybeLogBackpressure(\"metrics\");\n return;\n }\n\n // Defer JSON.stringify + send off the critical path\n setImmediate(() => {\n if (!alive()) return;\n const msg: WsMessage = { type: \"request\", data: metrics };\n ws.send(JSON.stringify(msg));\n });\n\n scheduleSummaryUpdate();\n });\n\n function scheduleSummaryUpdate(): void {\n if (pendingSummaryTimer) return; // already scheduled\n pendingSummaryTimer = setTimeout(() => {\n pendingSummaryTimer = undefined;\n if (!alive()) return;\n const msg: WsMessage = { type: \"summary\", data: metricsStore.getSummary() };\n ws.send(JSON.stringify(msg));\n }, SUMMARY_DEBOUNCE_MS);\n }\n\n // Ping/pong heartbeat for liveness tracking\n const pingTimer = setInterval(() => {\n if (!alive()) {\n clearInterval(pingTimer);\n return;\n }\n // Terminate if client missed too many pongs\n if (missedPongs >= MAX_MISSED_PONGS) {\n cleanup(); // ensure timers and subscriber are cleaned up\n ws.terminate();\n return;\n }\n ws.ping();\n missedPongs++;\n }, PING_INTERVAL_MS);\n\n ws.on(\"pong\", () => {\n missedPongs = 0; // reset on successful pong\n });\n\n let cleanedUp = false;\n const cleanup = () => {\n if (cleanedUp) return;\n cleanedUp = true;\n clearInterval(pingTimer);\n if (pendingSummaryTimer) clearTimeout(pendingSummaryTimer);\n unsubscribe();\n };\n\n ws.on(\"close\", cleanup);\n ws.on(\"error\", cleanup);\n });\n}\n\nexport function broadcastStreamEvent(data: StreamEvent): void {\n if (!wssInstance) return;\n const msg = JSON.stringify({ type: \"stream\", data });\n const isStreaming = data.state === \"streaming\";\n const isCritical = data.state === \"complete\" || data.state === \"error\";\n const now = Date.now();\n for (const client of wssInstance.clients) {\n if (client.readyState !== client.OPEN) continue;\n // Throttle streaming events per client (non-streaming events always pass)\n if (isStreaming) {\n const lastEmit = clientStreamThrottle.get(client) ?? 0;\n if (now - lastEmit < STREAM_WS_THROTTLE_MS) continue;\n clientStreamThrottle.set(client, now);\n }\n // Backpressure: for critical events, use a callback to wait until drain; for others, drop\n if (client.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n if (isCritical) {\n // Critical events (complete/error) must not be silently dropped.\n // Wait for the drain event, then send. Use a one-time listener.\n const sendOnDrain = () => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n };\n // If the socket already has a pending drain (bufferAmount is decreasing),\n // the 'drain' event will fire. Otherwise send immediately on next tick.\n client.once('drain', sendOnDrain);\n // Safety timeout: if drain never fires within 5s, force-send anyway\n setTimeout(() => {\n client.removeListener('drain', sendOnDrain);\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n }, 5_000).unref();\n continue;\n }\n // Non-critical streaming event: drop and count\n streamDroppedCount++;\n maybeLogBackpressure(\"stream\");\n continue;\n }\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _totalCacheReadTokens = 0;\n private _totalCacheCreationTokens = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n this._totalCacheReadTokens -= evicted.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens -= evicted.cacheCreationTokens ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n this._totalCacheReadTokens += metrics.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens += metrics.cacheCreationTokens ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // Compute average cache hit rate across all requests with cache data\n let cacheHitRateSum = 0;\n let cacheHitRateCount = 0;\n for (const r of requests) {\n const totalInput = (r.inputTokens ?? 0) + (r.cacheReadTokens ?? 0) + (r.cacheCreationTokens ?? 0);\n if (totalInput > 0 && (r.cacheReadTokens ?? 0) > 0) {\n cacheHitRateSum += (r.cacheReadTokens! / totalInput) * 100;\n cacheHitRateCount++;\n }\n }\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n totalCacheReadTokens: this._totalCacheReadTokens,\n totalCacheCreationTokens: this._totalCacheCreationTokens,\n avgCacheHitRate: cacheHitRateCount > 0 ? Math.round((cacheHitRateSum / cacheHitRateCount) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":"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","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","PassThrough","fs","path","os","LatencyTracker","maxSize","provider","ttfbMs","firstKey","window","values","s","mean","a","b","variance","sum","v","activeProviders","active","key","InFlightCounter","count","latencyTracker","inFlightCounter","computeHedgingCount","cv","inFlight","maxConcurrent","available","adaptive","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","STREAM_WS_THROTTLE_MS","BACKPRESSURE_LOG_INTERVAL_MS","clientStreamThrottle","wssInstance","streamDroppedCount","lastBackpressureWarnTime","maybeLogBackpressure","source","now","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","isStreaming","isCritical","client","lastEmit","sendOnDrain","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","SPECULATIVE_DELAY","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","path","os","fs","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","allToolUseIds","allToolResultIds","i","msg","block","orphanedToolUseIds","orphanedToolResultIds","id","changed","cleaned","filtered","applyTargetedReplacements","rawBody","entry","parsed","needsOrphanClean","mutable","maxOutputTokens","requested","needsModel","needsMaxTokensClamp","needsMaxTokensAdd","m","patterns","combinedRegex","modelRepl","tokensRepl","origModel","modelLogged","match","forwardRequest","ctx","incomingRequest","externalSignal","chainIndex","outgoingPath","needsModification","requestedMaxTokens","originalModel","controller","timeout","ttfbTimeout","ttfbTimedOut","ttfbTimer","ttfbPromise","_","reject","removeAbortListener","onExternalAbort","undiciResponse","undiciRequest","stallTimeout","passThrough","PassThrough","stallMsg","stallTimerRef","broadcastStreamEvent","response","error","message","hedgedForwardRequest","provider","entry","ctx","incomingRequest","chainSignal","index","logger","count","computeHedgingCount","inFlightCounter","start","r","forwardRequest","latencyTracker","launched","h","wrapped","p","i","response","completed","failures","pending","_","winner","f","forwardWithFallback","providers","chain","onAttempt","errBody","textEncoder","sharedController","attemptProvider","cbProbeId","cb","races","resolve","SPECULATIVE_DELAY","idx","isRetriable","handled","handleContextWindowError","randomUUID","gzip","promisify","gzipAsync","promisify","gzip","MODEL_CONTEXT_WINDOWS","getContextWindow","model","key","size","computeCacheHitRate","cacheRead","cacheCreation","input","totalInput","computeContextPercent","output","contextWindow","total","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","firstChunk","responsePreview","PREVIEW_MAX","drainEvents","eventText","event","dataLine","l","delta","choices","choiceDelta","scanWindow","text","anthContent","lastText","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","chunk","controller","agentKey","origin","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","headerSize","v","k","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","newConfig","oldAgents","reusedKeys","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","cacheHitRateSum","cacheHitRateCount","r","totalInput","callback","cap","result","i","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","i","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","latencyTracker","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","model"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/hedging.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { latencyTracker } from \"./hedging.js\";\nimport { attachWebSocket } from \"./ws.js\";\nimport { startMonitor } from \"./monitor.js\";\n\n// Read version from package.json at startup\nconst VERSION: string = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\")).version;\n\nfunction parseArgs(argv: string[]): { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } {\n const args: { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } = { verbose: false, help: false, daemon: false, monitor: false, gui: false };\n for (let i = 2; i < argv.length; i++) {\n switch (argv[i]) {\n case \"-p\":\n case \"--port\":\n const portStr = argv[++i];\n if (!portStr || isNaN(parseInt(portStr, 10))) {\n console.error(\"Error: -p/--port requires a number\");\n process.exit(1);\n }\n args.port = parseInt(portStr, 10);\n break;\n case \"-c\":\n case \"--config\":\n const configPath = argv[++i];\n if (!configPath) {\n console.error(\"Error: -c/--config requires a path\");\n process.exit(1);\n }\n args.config = configPath;\n break;\n case \"-v\":\n case \"--verbose\":\n args.verbose = true;\n break;\n case \"-h\":\n case \"--help\":\n args.help = true;\n break;\n case \"--daemon\":\n args.daemon = true;\n break;\n case \"--monitor\":\n args.monitor = true;\n break;\n }\n }\n return args;\n}\n\nfunction printHelp() {\n console.log(`\nModelWeaver — Multi-provider model orchestration proxy for Claude Code\n\nUsage: modelweaver [command] [options]\n\nCommands:\n init [--quick] Run interactive setup wizard (--quick for express mode)\n start Start as background daemon\n stop Stop background daemon\n status Show daemon status\n remove Stop daemon and remove PID + log files\n reload Reload daemon worker (load fresh code after build)\n install Install launchd service (auto-start at login)\n uninstall Uninstall launchd service\n gui Launch the GUI (downloads if needed)\n\nOptions:\n -p, --port <number> Server port (default: from config)\n -c, --config <path> Config file path (auto-detected)\n -v, --verbose Enable debug logging (default: off)\n -h, --help Show this help\n\nConfig locations (first found wins):\n ./modelweaver.yaml\n ~/.modelweaver/config.yaml\n`);\n}\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n // Load .env file if present (created by modelweaver init)\n try {\n const dotenv = await import('dotenv');\n const { existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const home = process.env.HOME || process.env.USERPROFILE || '';\n // Try cwd/.env first, then ~/.modelweaver/.env, then ~/.env\n const paths = [\n join(process.cwd(), '.env'),\n join(home, '.modelweaver', '.env'),\n join(home, '.env'),\n ];\n for (const p of paths) {\n if (existsSync(p)) {\n dotenv.config({ path: p });\n break;\n }\n }\n } catch {\n // dotenv not installed or .env not present — continue without it\n }\n\n // Handle 'init' subcommand — dynamic import to avoid loading prompts for normal startup\n if (process.argv[2] === 'init') {\n const quick = process.argv.includes('--quick') || process.argv.includes('-q');\n const { runInit } = await import('./init.js');\n await runInit({ quick });\n process.exit(0);\n }\n\n // Handle 'start' subcommand\n if (process.argv[2] === 'start') {\n const { startDaemon } = await import('./daemon.js');\n const result = await startDaemon(args.config, args.port, args.verbose);\n console.log(` ${result.message}`);\n console.log(` Log file: ${result.logPath}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'stop' subcommand\n if (process.argv[2] === 'stop') {\n const { stopDaemon } = await import('./daemon.js');\n const result = await stopDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'status' subcommand\n if (process.argv[2] === 'status') {\n const { statusDaemon } = await import('./daemon.js');\n const result = await statusDaemon();\n console.log(` ${result.message}`);\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n const installed = svc.isInstalled();\n if (installed) {\n console.log(` Service: installed`);\n } else {\n console.log(` Service: not installed (run \"modelweaver install\" to enable auto-start)`);\n }\n } catch (err) {\n console.log(` Service: ${err instanceof Error ? err.message : String(err)}`);\n }\n process.exit(0);\n }\n\n // Handle 'remove' subcommand — stop + clean up PID and log files\n if (process.argv[2] === 'remove') {\n const { removeDaemon } = await import('./daemon.js');\n const result = await removeDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'install' subcommand — install platform service\n if (process.argv[2] === 'install') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n await svc.install();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'uninstall' subcommand — uninstall platform service\n if (process.argv[2] === 'uninstall') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n svc.uninstall();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'gui' subcommand\n if (process.argv[2] === 'gui') {\n const { launchGui } = await import('./gui-launcher.js');\n await launchGui();\n process.exit(0);\n }\n\n // Handle 'reload' subcommand\n if (process.argv[2] === 'reload') {\n const { reloadDaemon } = await import('./daemon.js');\n await reloadDaemon(args.port);\n process.exit(0);\n }\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n // Load config\n let config;\n let configPath;\n try {\n const result = await loadConfig(args.config);\n config = result.config;\n configPath = result.configPath;\n } catch (error) {\n console.error(`Config error: ${(error as Error).message}`);\n process.exit(1);\n }\n\n // CLI port override\n const port = args.port || config.server.port;\n const host = config.server.host;\n const logLevel: LogLevel = args.verbose ? \"debug\" : \"info\";\n\n // Initialize metrics store\n const metricsStore = new MetricsStore();\n\n // --- Monitor mode (spawns daemon child, auto-restarts on crash) ---\n if (args.monitor) {\n await startMonitor(args);\n return;\n }\n\n // --- Daemon mode ---\n if (args.daemon) {\n const { removeWorkerPidFile, writeWorkerPidFile, createDebouncedReload, getLogPath } = await import('./daemon.js');\n const { reloadConfig } = await import('./config.js');\n const { createWriteStream, watch } = await import('node:fs');\n const { createLogger } = await import('./logger.js');\n const logger = createLogger(logLevel);\n\n // Prevent silent crashes from killing the daemon worker\n process.on('uncaughtException', (err) => {\n logger.error('Uncaught exception (daemon survived)', { error: err.message, stack: err.stack });\n });\n process.on('unhandledRejection', (reason) => {\n logger.error('Unhandled rejection (daemon survived)', { reason: String(reason) });\n });\n\n // Write worker PID file (monitor owns modelweaver.pid)\n await writeWorkerPidFile(process.pid);\n\n // Redirect stdout/stderr to log file\n const logStream = createWriteStream(getLogPath(), { flags: 'a' });\n logStream.on('error', () => { /* ignore write errors to log file */ });\n process.stdout.write = logStream.write.bind(logStream) as typeof process.stdout.write;\n process.stderr.write = logStream.write.bind(logStream) as typeof process.stderr.write;\n\n // Create app with mutable config\n const handle = createApp(config, logLevel, metricsStore);\n\n // Hot-reload: watch config file for changes\n let configWatcher: ReturnType<typeof watch> | null = null;\n if (configPath) {\n const debounced = createDebouncedReload(async () => {\n try {\n const newConfig = await reloadConfig(configPath);\n handle.setConfig(newConfig);\n latencyTracker.prune([...newConfig.providers.keys()]);\n logger.info(\"Config reloaded\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed — keeping old config\", { error: (err as Error).message });\n }\n }, 300);\n\n try {\n configWatcher = watch(configPath, () => {\n debounced.reload();\n });\n configWatcher.on('error', () => {\n // fs.watch failed — silently disable hot-reload\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n });\n } catch {\n // fs.watch not available — hot-reload disabled\n }\n }\n\n // SIGUSR1 triggers config hot-reload\n // Note: SIGUSR1 is POSIX-only; this handler is a no-op on Windows.\n process.on('SIGUSR1', async () => {\n try {\n const newConfig = await reloadConfig(configPath!);\n handle.setConfig(newConfig);\n latencyTracker.prune([...newConfig.providers.keys()]);\n logger.info(\"Config reloaded (SIGUSR1)\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed (SIGUSR1)\", { error: (err as Error).message });\n }\n });\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = async () => {\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n await removeWorkerPidFile();\n logStream.end();\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return; // Don't fall through to foreground mode\n }\n\n // --- Foreground mode ---\n const handle = createApp(config, logLevel, metricsStore);\n\n // Print startup info\n console.log(`\\n ModelWeaver v${VERSION}`);\n console.log(` Listening: http://${host}:${port}`);\n console.log(` Config: ${configPath}\\n`);\n\n console.log(\" Routes:\");\n for (const [tier, entries] of config.routing) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${tier.padEnd(8)} → ${providerList}`);\n }\n console.log();\n\n if (config.modelRouting.size > 0) {\n console.log(\" Model Routes:\");\n for (const [model, entries] of config.modelRouting) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${model.padEnd(20)} → ${providerList}`);\n }\n console.log();\n }\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nmain();\n","// src/server.ts\nimport { Hono } from \"hono\";\nimport { resolveRequest, clearRoutingCache } from \"./router.js\";\nimport { forwardWithFallback } from \"./proxy.js\";\nimport { createLogger, type LogLevel } from \"./logger.js\";\nimport type { AppConfig, ProviderConfig, RequestContext } from \"./types.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nimport type { MetricsStore } from \"./metrics.js\";\nimport { broadcastStreamEvent } from \"./ws.js\";\nimport type { StreamEvent } from \"./types.js\";\n\nconst MODEL_CONTEXT_WINDOWS: Record<string, number> = {\n 'claude-opus-4-6': 200000,\n 'claude-sonnet-4-6': 200000,\n 'claude-haiku-4-5-20251001': 200000,\n 'claude-3-5-sonnet': 200000,\n 'claude-3-5-haiku': 200000,\n 'glm-4.7': 128000,\n 'glm-5-turbo': 128000,\n};\n\nfunction getContextWindow(model: string): number {\n // Exact match first, then prefix match\n if (MODEL_CONTEXT_WINDOWS[model]) return MODEL_CONTEXT_WINDOWS[model];\n for (const [key, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {\n if (model.startsWith(key)) return size;\n }\n return 0;\n}\n\nfunction computeCacheHitRate(cacheRead: number, cacheCreation: number, input: number): number {\n const totalInput = input + cacheRead + cacheCreation;\n if (totalInput <= 0) return 0;\n return Math.round((cacheRead / totalInput) * 1000) / 10;\n}\n\nfunction computeContextPercent(input: number, cacheRead: number, cacheCreation: number, output: number, contextWindow: number): number {\n if (contextWindow <= 0) return 0;\n const total = input + cacheRead + cacheCreation + output;\n return Math.round((total / contextWindow) * 1000) / 10;\n}\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp, outputTokens: out, cacheReadTokens: cacheRead, cacheCreationTokens: cacheCreation };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n let firstChunk = true;\n\n // Response text preview (last 100 chars for progress bar tooltip)\n let responsePreview = \"\";\n const PREVIEW_MAX = 100;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n\n // Extract usage (token counts)\n if (dataLine.includes('\"usage\"')) {\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n if (usage.cacheReadTokens > tokens.cacheRead) tokens.cacheRead = usage.cacheReadTokens;\n if (usage.cacheCreationTokens > tokens.cacheCreation) tokens.cacheCreation = usage.cacheCreationTokens;\n }\n\n // Extract text content for preview\n // Anthropic format: content_block_delta with delta.text\n const delta = data.delta as Record<string, unknown> | undefined;\n if (delta && typeof delta.text === \"string\") {\n responsePreview += delta.text;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n // OpenAI format: choices[0].delta.content\n const choices = data.choices as Array<Record<string, unknown>> | undefined;\n if (choices?.[0]) {\n const choiceDelta = choices[0].delta as Record<string, unknown> | undefined;\n if (choiceDelta && typeof choiceDelta.content === \"string\") {\n responsePreview += choiceDelta.content;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n }\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n // Fast bailout: most chunks don't contain usage data\n if (!text.includes('\"usage\"')) {\n // Extract text content for preview from JSON responses even without usage\n const anthContent = [...text.matchAll(/\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g)];\n if (anthContent.length > 0) {\n const lastText = anthContent[anthContent.length - 1][1].replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n responsePreview += lastText;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n return;\n }\n\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n\n // Extract text content for preview from JSON responses\n // Anthropic format: \"content\":[{\"type\":\"text\",\"text\":\"...\"}]\n const anthContent = [...text.matchAll(/\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g)];\n if (anthContent.length > 0) {\n const lastText = anthContent[anthContent.length - 1][1].replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n responsePreview += lastText;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n };\n\n const recordMetrics = (inp: number, out: number, cacheRead: number = 0, cacheCreation: number = 0) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n });\n\n // Broadcast completion event\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n cacheHitRate: computeCacheHitRate(cacheRead, cacheCreation, inp),\n contextPercent: computeContextPercent(inp, cacheRead, cacheCreation, out, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (firstChunk || now - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = now;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(tokens.cacheRead, tokens.cacheCreation, tokens.input),\n contextPercent: computeContextPercent(tokens.input, tokens.cacheRead, tokens.cacheCreation, tokens.output, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output, tokens.cacheRead, tokens.cacheCreation);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (firstChunk || nowJson - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = nowJson;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(cacheReadTokens, cacheCreationTokens, inputTokens),\n contextPercent: computeContextPercent(inputTokens, cacheReadTokens, cacheCreationTokens, outputTokens, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n // Broadcast TTFB event — headers received from upstream (skip for error responses)\n if (response.status < 400) {\n let headerSize = 17; // approximate HTTP status line: \"HTTP/1.1 200 OK\\r\\n\"\n response.headers.forEach((v, k) => { headerSize += k.length + v.length + 4; });\n headerSize += 2; // trailing CRLF\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"ttfb\",\n status: response.status,\n headerSize,\n timestamp: Date.now(),\n });\n });\n }\n\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport { PassThrough } from \"node:stream\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { latencyTracker, inFlightCounter, computeHedgingCount } from './hedging.js';\nimport { broadcastStreamEvent } from './ws.js';\n\n/** Headers forwarded as-is to upstream */\nconst FORWARD_HEADERS = new Set([\n \"anthropic-version\",\n \"anthropic-beta\",\n \"content-type\",\n \"accept\",\n]);\n\n/** Pre-compiled regex for normalizing duplicate slashes in URL paths */\nconst MULTI_SLASH = /\\/+/g;\n\n/** Pre-compiled regex for stripping origin from URLs */\nconst STRIP_ORIGIN = /^https?:\\/\\/[^/]+/;\n\n/** Pre-compiled regexes for targeted body replacements (preserve prompt caching) */\nconst MODEL_KEY_REGEX = /\"model\"\\s*:\\s*\"([^\"]*)\"/;\nconst MAX_TOKENS_REGEX = /\"max_tokens\"\\s*:\\s*(\\d+)/;\n\n/** Module-level TextEncoder — avoids per-request allocation */\nconst textEncoder = new TextEncoder();\n\n/** Delay (ms) before starting backup providers in staggered race */\nconst SPECULATIVE_DELAY = 3000;\n\nexport function isRetriable(status: number): boolean {\n return status === 429 || status >= 500;\n}\n\nconst CONTEXT_WINDOW_PATTERNS = [\n 'context window', 'context_limit', 'token limit',\n 'prompt is too long', 'max tokens', 'input too large', 'too many tokens',\n];\n\nfunction isContextWindowError(status: number, body: string): boolean {\n if (status !== 400) return false;\n const lower = body.toLowerCase();\n return CONTEXT_WINDOW_PATTERNS.some(p => lower.includes(p));\n}\n\nfunction handleContextWindowError(status: number, body: string): Response | null {\n if (!isContextWindowError(status, body)) return null;\n\n console.warn('[context-compact] Upstream context window limit detected');\n try {\n const flagDir = path.join(os.homedir(), '.claude', 'state');\n fs.mkdirSync(flagDir, { recursive: true });\n fs.writeFileSync(path.join(flagDir, 'context-compact-needed'), Date.now().toString());\n } catch {\n // Best-effort flag write\n }\n\n const enhanced = JSON.stringify({\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: \"Context window limit reached. Run /compact to reduce conversation size, then retry.\",\n },\n });\n return new Response(enhanced, {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nexport function buildOutboundUrl(baseUrl: string, incomingPath: string): string {\n let basePath = \"\";\n let origin = baseUrl;\n const slashIndex = baseUrl.indexOf('/', baseUrl.indexOf('//') + 2);\n if (slashIndex !== -1) {\n origin = baseUrl.substring(0, slashIndex);\n basePath = baseUrl.substring(slashIndex);\n }\n\n let incomingQuery = \"\";\n let incomingOnly = incomingPath;\n const qIndex = incomingPath.indexOf('?');\n if (qIndex !== -1) {\n incomingOnly = incomingPath.substring(0, qIndex);\n incomingQuery = incomingPath.substring(qIndex);\n }\n\n // Deduplicate /v1 when base URL path already ends with it and incoming path starts with it.\n // e.g. baseUrl=\"https://api.fireworks.ai/inference/v1\" + path=\"/v1/chat/completions\"\n // → \"/inference/v1/chat/completions\" (not \"/inference/v1/v1/chat/completions\")\n let resolvedPath;\n if (basePath.endsWith('/v1') && incomingOnly.startsWith('/v1')) {\n resolvedPath = basePath + incomingOnly.substring(3);\n } else {\n resolvedPath = basePath + incomingOnly;\n }\n\n // Normalize duplicate slashes\n resolvedPath = resolvedPath.replace(MULTI_SLASH, \"/\");\n\n return origin + resolvedPath + incomingQuery;\n}\n\nexport function buildOutboundHeaders(\n incomingHeaders: Headers,\n provider: ProviderConfig,\n requestId: string\n): Headers {\n const headers = new Headers();\n\n // Forward select headers as-is\n for (const name of FORWARD_HEADERS) {\n const value = incomingHeaders.get(name);\n if (value) headers.set(name, value);\n }\n\n // Rewrite auth headers based on provider authType\n if (provider.authType === \"bearer\") {\n headers.set(\"Authorization\", `Bearer ${provider.apiKey}`);\n } else {\n headers.set(\"x-api-key\", provider.apiKey);\n }\n headers.set(\"x-request-id\", requestId);\n\n // Set host to provider hostname (use cached components when available)\n const cachedHost = provider._cachedHost;\n if (cachedHost) {\n headers.set(\"host\", cachedHost);\n } else {\n try {\n const url = new URL(provider.baseUrl);\n headers.set(\"host\", url.host);\n } catch {\n // If baseUrl is not a valid URL, skip host rewrite\n }\n }\n\n return headers;\n}\n\n/**\n * Remove orphaned tool_use/tool_result pairs from the messages array.\n *\n * In Anthropic's format:\n * - tool_use blocks live inside assistant message content: { role: \"assistant\", content: [{ type: \"tool_use\", id: \"call_xxx\", ... }] }\n * - tool_result blocks live inside user message content: { role: \"user\", content: [{ type: \"tool_result\", tool_use_id: \"call_xxx\", ... }] }\n *\n * A tool_result is orphaned if its tool_use_id references a tool_use not in any assistant content block.\n * A tool_use is orphaned if its id has no matching tool_result in any user content block.\n *\n * Single-pass: collects all IDs, computes orphans, then filters once.\n */\nfunction cleanOrphanedToolMessages(body: Record<string, unknown>): void {\n const messages = body.messages;\n if (!Array.isArray(messages)) return;\n\n const allToolUseIds = new Set<string>();\n const allToolResultIds = new Set<string>();\n\n // Single collection pass — gather every tool_use and tool_result ID\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!Array.isArray(msg.content)) continue;\n\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.id) {\n allToolUseIds.add(String(block.id));\n } else if (block.type === \"tool_result\" && block.tool_use_id) {\n allToolResultIds.add(String(block.tool_use_id));\n }\n }\n }\n\n // Compute orphaned IDs: tool_use without a matching tool_result, and vice versa.\n // A tool_use is kept if ANY tool_result references it (even if that tool_result\n // itself is orphaned — we only strip truly dangling references).\n const orphanedToolUseIds = new Set<string>();\n const orphanedToolResultIds = new Set<string>();\n for (const id of allToolUseIds) {\n if (!allToolResultIds.has(id)) orphanedToolUseIds.add(id);\n }\n for (const id of allToolResultIds) {\n if (!allToolUseIds.has(id)) orphanedToolResultIds.add(id);\n }\n\n if (orphanedToolUseIds.size === 0 && orphanedToolResultIds.size === 0) return;\n\n // Single filter pass — remove orphaned blocks from content arrays\n let changed = false;\n const cleaned = messages.map((msg: Record<string, unknown>) => {\n if (!Array.isArray(msg.content)) return msg;\n\n const filtered = msg.content.filter((block: Record<string, unknown>) => {\n if (block.type === \"tool_use\" && orphanedToolUseIds.has(String(block.id))) return false;\n if (block.type === \"tool_result\" && orphanedToolResultIds.has(String(block.tool_use_id))) return false;\n return true;\n });\n\n if (filtered.length < msg.content.length) {\n changed = true;\n return { ...msg, content: filtered };\n }\n return msg;\n });\n\n if (changed) {\n body.messages = cleaned;\n }\n}\n\n/**\n * Apply targeted string replacements to rawBody to preserve prompt caching.\n * On the primary attempt (chainIndex === 0), we avoid full JSON.stringify which\n * breaks Anthropic's cache breakpoints (position-sensitive, whitespace/order-sensitive).\n * Falls back to full JSON parse/mutate/stringify when structural changes are needed.\n */\nfunction applyTargetedReplacements(\n rawBody: string,\n entry: RoutingEntry,\n provider: ProviderConfig,\n parsed: Record<string, unknown>,\n needsOrphanClean: boolean,\n): string {\n // If orphan cleaning is needed, we must do full JSON parse (structural changes to messages)\n if (needsOrphanClean) {\n // deep clone required: cleanOrphanedToolMessages mutates the messages array in-place\n const mutable = structuredClone(parsed);\n if (entry.model) mutable.model = entry.model;\n cleanOrphanedToolMessages(mutable);\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requested = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requested > maxOutputTokens) {\n mutable.max_tokens = Math.min(requested, maxOutputTokens);\n }\n }\n return JSON.stringify(mutable);\n }\n\n // Targeted replacement path -- only model override and/or max_tokens clamping.\n // Single-pass: build a combined regex with alternation so the entire raw body\n // string is scanned and replaced in one call instead of per-pattern copies.\n const needsModel = !!(entry.model && (parsed.model as string | undefined) !== entry.model);\n let needsMaxTokensClamp = false;\n let needsMaxTokensAdd = false;\n let maxOutputTokens = 0;\n if (provider.modelLimits) {\n maxOutputTokens = provider.modelLimits.maxOutputTokens;\n const m = MAX_TOKENS_REGEX.exec(rawBody);\n if (m) {\n needsMaxTokensClamp = parseInt(m[1], 10) > maxOutputTokens;\n } else if (typeof parsed.max_tokens !== \"number\") {\n needsMaxTokensAdd = true;\n }\n }\n\n if (!needsModel && !needsMaxTokensClamp && !needsMaxTokensAdd) return rawBody;\n\n // max_tokens not in body at all — must add via JSON parse (structural change)\n if (needsMaxTokensAdd) {\n const mutable = { ...parsed };\n if (entry.model) mutable.model = entry.model;\n mutable.max_tokens = maxOutputTokens;\n return JSON.stringify(mutable);\n }\n\n // Build combined regex for single-pass replacement\n const patterns: string[] = [];\n if (needsModel) patterns.push(MODEL_KEY_REGEX.source);\n if (needsMaxTokensClamp) patterns.push(MAX_TOKENS_REGEX.source);\n const combinedRegex = new RegExp(patterns.join(\"|\"), \"g\");\n\n // Capture values for the replacer (avoid repeated property access)\n const modelRepl = needsModel ? `\"model\":\"${entry.model}\"` : null;\n const tokensRepl = needsMaxTokensClamp ? `\"max_tokens\":${maxOutputTokens}` : null;\n const origModel = parsed.model as string | undefined;\n let modelLogged = false;\n\n const result = rawBody.replace(combinedRegex, (match: string) => {\n if (modelRepl && MODEL_KEY_REGEX.test(match)) {\n MODEL_KEY_REGEX.lastIndex = 0;\n if (!modelLogged && origModel) {\n console.warn(`Routing override: ${origModel} -> ${entry.model} via ${provider.name}`);\n modelLogged = true;\n }\n return modelRepl;\n }\n if (tokensRepl && MAX_TOKENS_REGEX.test(match)) {\n MAX_TOKENS_REGEX.lastIndex = 0;\n return tokensRepl;\n }\n return match;\n });\n\n return result;\n}\n\n/**\n * Forward a request to a single provider.\n * Uses ctx.parsedBody when available (avoids re-parsing); falls back to ctx.rawBody.\n * incomingRequest is used for metadata only (url, headers).\n * Returns the Response object — caller decides fallback logic.\n */\nexport async function forwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n externalSignal?: AbortSignal,\n chainIndex: number = 0,\n): Promise<Response> {\n const outgoingPath = incomingRequest.url.replace(STRIP_ORIGIN, \"\");\n\n // Set actualModel early so metrics always record the routed model,\n // even if body parsing or the fetch itself fails\n if (entry.model) {\n ctx.actualModel = entry.model;\n }\n\n // Build outbound URL from provider base URL and request path\n const url = buildOutboundUrl(provider.baseUrl, outgoingPath);\n\n // Prepare body — prefer raw passthrough to preserve upstream prompt caching.\n // Only parse and re-serialize when a modification is actually required,\n // because Anthropic's cache breakpoints are position-sensitive and\n // JSON.stringify changes whitespace / key order, breaking cache hits.\n let body: string;\n const contentType = incomingRequest.headers.get(\"content-type\") || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n const parsed = (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody\n ?? JSON.parse(ctx.rawBody);\n\n // Determine whether any body modification is needed\n let needsModification = false;\n\n // Check 1: Model override needed?\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n needsModification = true;\n }\n\n // Check 2: Orphan cleaning needed? (only for fallback attempts, not primary)\n // On the primary attempt (index 0), conversation history is intact.\n // Only when falling back (index > 0) do cross-provider orphans appear.\n const needsOrphanClean = chainIndex > 0;\n if (needsOrphanClean) needsModification = true;\n\n // Check 3: max_tokens clamping needed?\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof parsed.max_tokens === \"number\" ? parsed.max_tokens : maxOutputTokens;\n if (parsed.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n needsModification = true;\n }\n }\n\n if (needsModification) {\n // On primary attempt (chainIndex === 0) without orphan cleaning, use targeted\n // string replacements to preserve prompt caching. Anthropic's cache breakpoints\n // are position-sensitive -- JSON.stringify changes whitespace/order, breaking hits.\n if (chainIndex === 0 && !needsOrphanClean) {\n body = applyTargetedReplacements(ctx.rawBody, entry, provider, parsed, false);\n } else {\n // Fallback attempts: full JSON parse/mutate/stringify (caching already broken)\n // deep clone required: cleanOrphanedToolMessages may mutate the messages array in-place\n const mutable = structuredClone(parsed);\n\n if (entry.model) {\n const originalModel = mutable.model as string | undefined;\n mutable.model = entry.model;\n if (originalModel && originalModel !== entry.model) {\n console.warn(\n `Routing override: ${originalModel} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n if (needsOrphanClean) {\n cleanOrphanedToolMessages(mutable);\n }\n\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n mutable.max_tokens = Math.min(requestedMaxTokens, maxOutputTokens);\n }\n }\n\n body = JSON.stringify(mutable);\n }\n } else {\n // No modifications needed — passthrough raw body to preserve prompt caching\n body = ctx.rawBody;\n }\n } catch {\n // If body can't be parsed, send it as-is without model override\n body = ctx.rawBody;\n }\n } else {\n body = ctx.rawBody;\n }\n\n const headers = buildOutboundHeaders(incomingRequest.headers, provider, ctx.requestId);\n headers.set(\"content-length\", Buffer.byteLength(body, 'utf-8').toString());\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), provider.timeout);\n\n // TTFB timeout: fail if no response headers received within ttfbTimeout ms\n const ttfbTimeout = provider.ttfbTimeout ?? 15000;\n let ttfbTimedOut = false;\n let ttfbTimer: ReturnType<typeof setTimeout> | null = null;\n\n const ttfbPromise = new Promise<never>((_, reject) => {\n ttfbTimer = setTimeout(() => {\n ttfbTimedOut = true;\n controller.abort();\n reject(new Error(`TTFB timeout after ${ttfbTimeout}ms`));\n }, ttfbTimeout);\n });\n\n // Listen for external abort (from race cancellation) to abort this request\n let removeAbortListener: (() => void) | undefined;\n if (externalSignal) {\n if (externalSignal.aborted) {\n // Already aborted — don't even start the request\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: `Provider \"${provider.name}\" cancelled by race winner` },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n const onExternalAbort = () => {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n };\n removeAbortListener = externalSignal.addEventListener(\"abort\", onExternalAbort, { once: true }) as unknown as () => void;\n }\n\n try {\n const undiciResponse = await Promise.race([\n undiciRequest(url, {\n method: \"POST\",\n headers,\n body,\n signal: controller.signal,\n dispatcher: provider._agent,\n }),\n ttfbPromise,\n ]);\n\n // TTFB succeeded — cancel TTFB timer\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Body stall detection: pipe through PassThrough to monitor for data without\n // interfering with undici's internal stream state (no flowing mode conflict).\n const stallTimeout = provider.stallTimeout ?? 30000;\n const passThrough = new PassThrough();\n\n const stallMsg = `Body stalled: no data after ${stallTimeout}ms`;\n let stallTimerRef = setTimeout(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: String(ctx.actualModel ?? entry.model ?? \"\"),\n tier: \"\",\n state: \"error\",\n message: stallMsg,\n timestamp: Date.now(),\n });\n passThrough.destroy(new Error(stallMsg));\n }, stallTimeout);\n\n // Monitor OUR PassThrough for every data event — re-arms stall timer on each chunk\n passThrough.on(\"data\", () => {\n clearTimeout(stallTimerRef);\n stallTimerRef = setTimeout(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: String(ctx.actualModel ?? entry.model ?? \"\"),\n tier: \"\",\n state: \"error\",\n message: stallMsg,\n timestamp: Date.now(),\n });\n passThrough.destroy(new Error(stallMsg));\n }, stallTimeout);\n });\n\n passThrough.on(\"end\", () => {\n clearTimeout(stallTimerRef);\n });\n\n passThrough.on(\"error\", () => {\n clearTimeout(stallTimerRef);\n });\n\n // Pipe undici body → PassThrough. Data flows through without mode conflicts.\n undiciResponse.body.pipe(passThrough);\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n passThrough as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Network errors / timeouts — return a synthetic 502\n const message = ttfbTimedOut\n ? `Provider \"${provider.name}\" timed out waiting for first byte after ${ttfbTimeout}ms`\n : error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n } finally {\n removeAbortListener?.();\n }\n}\n\n/**\n * Race multiple providers simultaneously. Returns the first successful response.\n * Aborts all remaining requests once a winner is found.\n */\nasync function raceProviders(\n chain: RoutingEntry[],\n providers: Map<string, ProviderConfig>,\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void },\n chainOffset: number = 0,\n): Promise<Response> {\n const sharedController = new AbortController();\n\n const races = chain.map(async (entry, index): Promise<{ response: Response; index: number }> => {\n const provider = providers.get(entry.provider);\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n // Check circuit breaker\n let cbProbeId: number | undefined;\n if (provider._circuitBreaker) {\n const cb = provider._circuitBreaker.canProceed();\n if (!cb.allowed) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n cbProbeId = cb.probeId;\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, sharedController.signal, index + chainOffset);\n // Record for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status, cbProbeId);\n }\n return { response, index };\n } catch {\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(502, cbProbeId);\n }\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n });\n\n // Track completed promises to avoid double-processing\n const completed = new Set<Promise<{ response: Response; index: number }>>();\n const failures: { response: Response; index: number }[] = [];\n\n try {\n while (completed.size < races.length) {\n const pending = races.filter(r => !completed.has(r));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(races[winner.index] ?? races[0]);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n // Drain/cancel in-flight loser response bodies BEFORE aborting shared controller\n // to prevent leaked stream chunks from mid-write cancellation.\n for (const r of pending) {\n if (r !== races[winner.index]) {\n r.then(({ response }) => {\n if (response.body) {\n try { response.body.cancel(); } catch { /* already consumed */ }\n }\n }).catch(() => { /* aborted */ });\n }\n }\n sharedController.abort();\n // Cancel bodies of already-completed losing responses to free resources\n for (const f of failures) {\n try { f.response.body?.cancel(); } catch { /* ignore */ }\n }\n return winner.response;\n }\n\n // Non-retriable error — check for context window limit before propagating\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n // Retriable but not success — record and continue waiting\n failures.push(winner);\n }\n\n // All providers returned retriable errors — return the first failure\n sharedController.abort();\n if (failures.length > 0) {\n return failures[0].response;\n }\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n}\n\n/**\n * Forward a request with optional adaptive hedging.\n * When latency variance is high, sends multiple copies and returns the fastest.\n */\nasync function hedgedForwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n chainSignal: AbortSignal | undefined,\n index: number,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n const count = computeHedgingCount(provider);\n\n if (count <= 1) {\n // No hedging — single request\n inFlightCounter.increment(provider.name);\n const start = Date.now();\n try {\n const r = await forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index);\n latencyTracker.record(provider.name, Date.now() - start);\n return r;\n } finally {\n inFlightCounter.decrement(provider.name);\n }\n }\n\n // Hedged path: send multiple copies, race for first success\n logger?.warn(\"Hedging request\", {\n requestId: ctx.requestId,\n provider: provider.name,\n count,\n cv: Math.round(latencyTracker.getCV(provider.name) * 100) / 100,\n inFlight: inFlightCounter.get(provider.name),\n maxConcurrent: provider.concurrentLimit,\n });\n\n const start = Date.now();\n const launched: Promise<Response>[] = [];\n\n for (let h = 0; h < count; h++) {\n inFlightCounter.increment(provider.name);\n launched.push(\n forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index)\n .finally(() => inFlightCounter.decrement(provider.name))\n );\n }\n\n // Race: first successful response wins, cancel the rest\n // Wrap each promise so we can identify which one completed by index\n const wrapped = launched.map((p, i) =>\n p.then(response => ({ response, hedgeIndex: i }))\n );\n\n const completed = new Set<number>();\n const failures: Response[] = [];\n\n try {\n while (completed.size < wrapped.length) {\n const pending = wrapped.filter((_, i) => !completed.has(i));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.hedgeIndex);\n\n // Record each hedged copy's result for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(winner.response.status);\n }\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n latencyTracker.record(provider.name, Date.now() - start);\n // Cancel remaining — record 502 for each cancelled copy\n for (let i = 0; i < wrapped.length; i++) {\n if (!completed.has(i)) {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n wrapped[i].then(r => { try { r.response.body?.cancel(); } catch {} });\n }\n }\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return winner.response;\n }\n\n failures.push(winner.response);\n }\n\n // All hedged copies returned errors — cancel bodies, return first failure\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" all hedged requests failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n } catch {\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" hedging failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n // Single provider — no racing needed\n if (chain.length <= 1) {\n const entry = chain[0];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n\n if (provider._circuitBreaker) {\n const cb = provider._circuitBreaker.canProceed();\n if (!cb.allowed) {\n logger?.warn(\"Provider skipped by circuit breaker\", { requestId: ctx.requestId, provider: entry.provider });\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n }\n\n onAttempt?.(entry.provider, 0);\n\n const response = await hedgedForwardRequest(provider, entry, ctx, incomingRequest, undefined, 0, logger);\n\n return response;\n }\n\n // Multiple providers — staggered race\n const sharedController = new AbortController();\n const completed = new Set<number>();\n const failures: { response: Response; index: number }[] = [];\n\n async function attemptProvider(\n index: number,\n ): Promise<{ response: Response; index: number }> {\n const entry = chain[index];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n\n let cbProbeId: number | undefined;\n if (provider._circuitBreaker) {\n const cb = provider._circuitBreaker.canProceed();\n if (!cb.allowed) {\n logger?.warn(\"Provider skipped by circuit breaker\", {\n requestId: ctx.requestId,\n provider: entry.provider,\n });\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n cbProbeId = cb.probeId;\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await hedgedForwardRequest(\n provider,\n entry,\n ctx,\n incomingRequest,\n sharedController.signal,\n index,\n logger,\n );\n return { response, index };\n } catch {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502, cbProbeId);\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n }\n\n // Build staggered race promises:\n // Provider 0 starts immediately\n // Provider 1+ start after SPECULATIVE_DELAY (if race not already won)\n const races: Promise<{ response: Response; index: number }>[] = [];\n\n for (let i = 0; i < chain.length; i++) {\n if (i === 0) {\n races.push(attemptProvider(0));\n } else {\n races.push(\n new Promise<{ response: Response; index: number }>((resolve) => {\n setTimeout(() => {\n if (sharedController.signal.aborted) {\n // Race already won — resolve with a cancelled placeholder\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: \"Cancelled by race winner\" },\n });\n resolve({\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index: i,\n });\n return;\n }\n attemptProvider(i).then(resolve);\n }, SPECULATIVE_DELAY);\n }),\n );\n }\n }\n\n // Pick winner — same logic as raceProviders\n try {\n while (completed.size < races.length) {\n const pending = races.filter((_, idx) => !completed.has(idx));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.index);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n for (const f of failures) {\n try {\n f.response.body?.cancel();\n } catch {\n /* ignore */\n }\n }\n return winner.response;\n }\n\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n failures.push(winner);\n }\n\n sharedController.abort();\n if (failures.length > 0) return failures[0].response;\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n}\n","/**\n * Adaptive request hedging — sends multiple copies of a request when\n * the provider shows high latency variance, returning the fastest response.\n */\n\nimport type { ProviderConfig } from './types.js';\n\ninterface LatencySample {\n ttfbMs: number;\n timestamp: number;\n}\n\nexport class LatencyTracker {\n private samples = new Map<string, LatencySample[]>();\n private readonly maxSize: number;\n private readonly MAX_PROVIDERS = 50;\n\n constructor(maxSize = 20) {\n this.maxSize = maxSize;\n }\n\n record(provider: string, ttfbMs: number): void {\n // Cap total tracked providers to prevent unbounded growth\n if (this.samples.size >= this.MAX_PROVIDERS && !this.samples.has(provider)) {\n // Remove the first (oldest) provider key\n const firstKey = this.samples.keys().next().value;\n if (firstKey !== undefined) this.samples.delete(firstKey);\n }\n\n let window = this.samples.get(provider);\n if (!window) {\n window = [];\n this.samples.set(provider, window);\n }\n window.push({ ttfbMs, timestamp: Date.now() });\n if (window.length > this.maxSize) {\n window.splice(0, window.length - this.maxSize);\n }\n }\n\n /** Coefficient of variation (stddev / mean). Returns 0 if insufficient data. */\n getCV(provider: string): number {\n const window = this.samples.get(provider);\n if (!window || window.length < 3) return 0;\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n if (mean === 0) return 0;\n const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;\n return Math.sqrt(variance) / mean;\n }\n\n getStats(provider: string): { count: number; mean: number; cv: number } {\n const window = this.samples.get(provider);\n if (!window || window.length === 0) return { count: 0, mean: 0, cv: 0 };\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n return { count: values.length, mean: Math.round(mean), cv: Math.round(this.getCV(provider) * 100) / 100 };\n }\n\n clear(provider: string): void {\n this.samples.delete(provider);\n }\n\n /** Remove entries for providers no longer in the current config. */\n prune(activeProviders: string[]): void {\n const active = new Set(activeProviders);\n for (const key of this.samples.keys()) {\n if (!active.has(key)) {\n this.samples.delete(key);\n }\n }\n }\n}\n\nexport class InFlightCounter {\n private counts = new Map<string, number>();\n\n increment(provider: string): number {\n const count = (this.counts.get(provider) ?? 0) + 1;\n this.counts.set(provider, count);\n return count;\n }\n\n decrement(provider: string): number {\n const count = Math.max(0, (this.counts.get(provider) ?? 0) - 1);\n this.counts.set(provider, count);\n return count;\n }\n\n get(provider: string): number {\n return this.counts.get(provider) ?? 0;\n }\n}\n\nexport const latencyTracker = new LatencyTracker();\nexport const inFlightCounter = new InFlightCounter();\n\n/**\n * Compute adaptive hedging count based on latency variance and available concurrency.\n *\n * CV (coefficient of variation) drives the count:\n * CV 0.0 → 1 (no hedging, provider is consistent)\n * CV 0.5 → 2\n * CV 1.0 → 3\n * CV 1.5+ → 4\n *\n * Clamped by available concurrency slots: maxConcurrent - inFlight.\n */\nexport function computeHedgingCount(provider: ProviderConfig): number {\n const cv = latencyTracker.getCV(provider.name);\n const inFlight = inFlightCounter.get(provider.name);\n const maxConcurrent = provider.concurrentLimit ?? 1;\n const available = Math.max(1, maxConcurrent - inFlight);\n\n const adaptive = Math.max(1, Math.floor(cv * 2 + 0.5));\n return Math.min(adaptive, available);\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\nconst STREAM_WS_THROTTLE_MS = 500; // caps stream event delivery to ~2 Hz per client\nconst BACKPRESSURE_LOG_INTERVAL_MS = 10_000; // throttle backpressure warnings to once per 10s\nconst clientStreamThrottle = new WeakMap<any, number>();\n\nlet wssInstance: InstanceType<typeof import(\"ws\").WebSocketServer> | null = null;\n\n// Module-level counters for dropped events (useful for monitoring)\nlet streamDroppedCount = 0;\nlet lastBackpressureWarnTime = 0;\n\nfunction maybeLogBackpressure(source: string): void {\n const now = Date.now();\n if (now - lastBackpressureWarnTime >= BACKPRESSURE_LOG_INTERVAL_MS) {\n console.warn(`[ws] Backpressure: dropping ${source} events (total dropped stream events: ${streamDroppedCount})`);\n lastBackpressureWarnTime = now;\n }\n}\n\nexport function attachWebSocket(server: Server, metricsStore: MetricsStore): void {\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n wssInstance = wss;\n\n wss.on(\"connection\", (ws) => {\n // Send current summary as initial state\n const summary = metricsStore.getSummary();\n const initialMsg: WsMessage = { type: \"summary\", data: summary };\n ws.send(JSON.stringify(initialMsg));\n\n let pendingSummaryTimer: ReturnType<typeof setTimeout> | undefined;\n let missedPongs = 0;\n const alive = () => ws.readyState === ws.OPEN;\n\n // Subscribe to new metrics with backpressure check and debounced summary\n const unsubscribe = metricsStore.onRecord((metrics: RequestMetrics) => {\n if (!alive()) return;\n\n // Backpressure: skip send if outbound buffer is too large\n if (ws.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n // Schedule a summary update instead so the client eventually catches up\n scheduleSummaryUpdate();\n maybeLogBackpressure(\"metrics\");\n return;\n }\n\n // Defer JSON.stringify + send off the critical path\n setImmediate(() => {\n if (!alive()) return;\n const msg: WsMessage = { type: \"request\", data: metrics };\n ws.send(JSON.stringify(msg));\n });\n\n scheduleSummaryUpdate();\n });\n\n function scheduleSummaryUpdate(): void {\n if (pendingSummaryTimer) return; // already scheduled\n pendingSummaryTimer = setTimeout(() => {\n pendingSummaryTimer = undefined;\n if (!alive()) return;\n const msg: WsMessage = { type: \"summary\", data: metricsStore.getSummary() };\n ws.send(JSON.stringify(msg));\n }, SUMMARY_DEBOUNCE_MS);\n }\n\n // Ping/pong heartbeat for liveness tracking\n const pingTimer = setInterval(() => {\n if (!alive()) {\n clearInterval(pingTimer);\n return;\n }\n // Terminate if client missed too many pongs\n if (missedPongs >= MAX_MISSED_PONGS) {\n cleanup(); // ensure timers and subscriber are cleaned up\n ws.terminate();\n return;\n }\n ws.ping();\n missedPongs++;\n }, PING_INTERVAL_MS);\n\n ws.on(\"pong\", () => {\n missedPongs = 0; // reset on successful pong\n });\n\n let cleanedUp = false;\n const cleanup = () => {\n if (cleanedUp) return;\n cleanedUp = true;\n clearInterval(pingTimer);\n if (pendingSummaryTimer) clearTimeout(pendingSummaryTimer);\n unsubscribe();\n };\n\n ws.on(\"close\", cleanup);\n ws.on(\"error\", cleanup);\n });\n}\n\nexport function broadcastStreamEvent(data: StreamEvent): void {\n if (!wssInstance) return;\n const msg = JSON.stringify({ type: \"stream\", data });\n const isStreaming = data.state === \"streaming\";\n const isCritical = data.state === \"complete\" || data.state === \"error\";\n const now = Date.now();\n for (const client of wssInstance.clients) {\n if (client.readyState !== client.OPEN) continue;\n // Throttle streaming events per client (non-streaming events always pass)\n if (isStreaming) {\n const lastEmit = clientStreamThrottle.get(client) ?? 0;\n if (now - lastEmit < STREAM_WS_THROTTLE_MS) continue;\n clientStreamThrottle.set(client, now);\n }\n // Backpressure: for critical events, use a callback to wait until drain; for others, drop\n if (client.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n if (isCritical) {\n // Critical events (complete/error) must not be silently dropped.\n // Wait for the drain event, then send. Use a one-time listener.\n const sendOnDrain = () => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n };\n // If the socket already has a pending drain (bufferAmount is decreasing),\n // the 'drain' event will fire. Otherwise send immediately on next tick.\n client.once('drain', sendOnDrain);\n // Safety timeout: if drain never fires within 5s, force-send anyway\n setTimeout(() => {\n client.removeListener('drain', sendOnDrain);\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n }, 5_000).unref();\n continue;\n }\n // Non-critical streaming event: drop and count\n streamDroppedCount++;\n maybeLogBackpressure(\"stream\");\n continue;\n }\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _totalCacheReadTokens = 0;\n private _totalCacheCreationTokens = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n this._totalCacheReadTokens -= evicted.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens -= evicted.cacheCreationTokens ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n this._totalCacheReadTokens += metrics.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens += metrics.cacheCreationTokens ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // Compute average cache hit rate across all requests with cache data\n let cacheHitRateSum = 0;\n let cacheHitRateCount = 0;\n for (const r of requests) {\n const totalInput = (r.inputTokens ?? 0) + (r.cacheReadTokens ?? 0) + (r.cacheCreationTokens ?? 0);\n if (totalInput > 0 && (r.cacheReadTokens ?? 0) > 0) {\n cacheHitRateSum += (r.cacheReadTokens! / totalInput) * 100;\n cacheHitRateCount++;\n }\n }\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n totalCacheReadTokens: this._totalCacheReadTokens,\n totalCacheCreationTokens: this._totalCacheCreationTokens,\n avgCacheHitRate: cacheHitRateCount > 0 ? Math.round((cacheHitRateSum / cacheHitRateCount) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":"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","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","PassThrough","fs","path","os","LatencyTracker","maxSize","provider","ttfbMs","firstKey","window","values","s","mean","a","b","variance","sum","v","activeProviders","active","key","InFlightCounter","count","latencyTracker","inFlightCounter","computeHedgingCount","cv","inFlight","maxConcurrent","available","adaptive","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","STREAM_WS_THROTTLE_MS","BACKPRESSURE_LOG_INTERVAL_MS","clientStreamThrottle","wssInstance","streamDroppedCount","lastBackpressureWarnTime","maybeLogBackpressure","source","now","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","isStreaming","isCritical","client","lastEmit","sendOnDrain","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","SPECULATIVE_DELAY","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","path","os","fs","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","allToolUseIds","allToolResultIds","i","msg","block","orphanedToolUseIds","orphanedToolResultIds","id","changed","cleaned","filtered","applyTargetedReplacements","rawBody","entry","parsed","needsOrphanClean","mutable","maxOutputTokens","requested","needsModel","needsMaxTokensClamp","needsMaxTokensAdd","m","patterns","combinedRegex","modelRepl","tokensRepl","origModel","modelLogged","match","forwardRequest","ctx","incomingRequest","externalSignal","chainIndex","outgoingPath","needsModification","requestedMaxTokens","originalModel","controller","timeout","ttfbTimeout","ttfbTimedOut","ttfbTimer","ttfbPromise","_","reject","removeAbortListener","onExternalAbort","undiciResponse","undiciRequest","stallTimeout","passThrough","PassThrough","stallMsg","stallTimerRef","broadcastStreamEvent","response","error","message","hedgedForwardRequest","provider","entry","ctx","incomingRequest","chainSignal","index","logger","count","computeHedgingCount","inFlightCounter","start","r","forwardRequest","latencyTracker","launched","h","wrapped","p","i","response","completed","failures","pending","_","winner","f","forwardWithFallback","providers","chain","onAttempt","errBody","textEncoder","sharedController","attemptProvider","cbProbeId","cb","races","resolve","SPECULATIVE_DELAY","idx","isRetriable","handled","handleContextWindowError","randomUUID","gzip","promisify","gzipAsync","promisify","gzip","MODEL_CONTEXT_WINDOWS","getContextWindow","model","key","size","computeCacheHitRate","cacheRead","cacheCreation","input","totalInput","computeContextPercent","output","contextWindow","total","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","firstChunk","responsePreview","PREVIEW_MAX","drainEvents","eventText","event","dataLine","l","delta","choices","choiceDelta","scanWindow","text","anthContent","lastText","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","chunk","controller","agentKey","origin","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","headerSize","v","k","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","newConfig","oldAgents","reusedKeys","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","cacheHitRateSum","cacheHitRateCount","r","totalInput","callback","cap","result","i","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","i","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","latencyTracker","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","model"]}
|