@kianwoon/modelweaver 0.3.16 → 0.3.17
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/{chunk-SCUI4R77.js → chunk-BXLSPBXT.js} +2 -2
- package/dist/{chunk-ZF23JS2D.js → chunk-F72SPY6C.js} +2 -2
- package/dist/{chunk-ZF23JS2D.js.map → chunk-F72SPY6C.js.map} +1 -1
- package/dist/config-CE7DQIUM.js +3 -0
- package/dist/{daemon-27GXX25D.js → daemon-WP5LZ7DG.js} +2 -2
- package/dist/index.js +11 -11
- package/dist/index.js.map +1 -1
- package/dist/{init-2GWQ546N.js → init-V5J734NZ.js} +3 -3
- package/package.json +1 -1
- package/dist/config-P34YQCFG.js +0 -3
- /package/dist/{chunk-SCUI4R77.js.map → chunk-BXLSPBXT.js.map} +0 -0
- /package/dist/{config-P34YQCFG.js.map → config-CE7DQIUM.js.map} +0 -0
- /package/dist/{daemon-27GXX25D.js.map → daemon-WP5LZ7DG.js.map} +0 -0
- /package/dist/{init-2GWQ546N.js.map → init-V5J734NZ.js.map} +0 -0
|
@@ -3,5 +3,5 @@ import{spawn as L,execFile as W,execFileSync as I}from"child_process";import{acc
|
|
|
3
3
|
`,{flag:"wx"})}catch(t){if(t.code==="EEXIST")return;throw t}}async function h(){let n=k();try{let t=await E(n,"utf-8"),e=parseInt(t.trim(),10);return isNaN(e)?null:e}catch{return null}}async function f(){let n=k();try{await y(n)}catch{}}function b(){return g(p,"modelweaver.worker.pid")}async function Z(n){await R(),await M(b(),`${n}
|
|
4
4
|
`)}async function w(){let n=b();try{let t=await E(n,"utf-8"),e=parseInt(t.trim(),10);return isNaN(e)?null:e}catch{return null}}async function m(){let n=b();try{await y(n)}catch{}}function s(n){try{return process.kill(n,0),!0}catch{return!1}}function S(n){return new Promise(t=>{if(d()){try{W("netstat",["-ano"],{encoding:"utf-8",timeout:3e3},(e,o)=>{if(e){t([]);return}let r=[];for(let i of(o||"").split(`
|
|
5
5
|
`))if(i.includes("LISTENING")&&i.includes(`:${n}`)){let l=i.trim().split(/\s+/),a=parseInt(l[l.length-1],10);!isNaN(a)&&a>0&&r.push(a)}t(r)})}catch{t([]);return}return}W("lsof",["-ti",`:${n}`,"-sTCP:LISTEN"],{encoding:"utf-8",timeout:3e3},(e,o)=>{if(e){t([]);return}let r=(o||"").trim();t(r?r.split(`
|
|
6
|
-
`).map(Number).filter(i=>!isNaN(i)):[])})})}async function D(n){if(v!==null)return v;try{let{loadConfig:t}=await import("./config-
|
|
7
|
-
//# sourceMappingURL=chunk-
|
|
6
|
+
`).map(Number).filter(i=>!isNaN(i)):[])})})}async function D(n){if(v!==null)return v;try{let{loadConfig:t}=await import("./config-CE7DQIUM.js"),{config:e}=await t(n??void 0);return e.server.port}catch{return 3456}}async function $(n,t=5e3){for(let o of n)try{process.kill(-o,"SIGTERM")}catch{try{process.kill(o,"SIGTERM")}catch{}}let e=Date.now()+t;for(;Date.now()<e;){if(n.every(o=>!s(o)))return!0;await new Promise(o=>setTimeout(o,200))}for(let o of n)if(d())try{I("taskkill",["/F","/PID",String(o),"/T"],{stdio:"ignore"})}catch{}else try{process.kill(-o,"SIGKILL")}catch{try{process.kill(o,"SIGKILL")}catch{}}return!0}async function O(n){let t=await h();if(t===null){let e=n??await D();if(e!==null&&e>0){let o=await S(e);if(o.length>0){let r=o.filter(i=>s(i));if(r.length>0)return{running:!0,pid:r[0],message:`ModelWeaver is running (PID ${r[0]}, detected on port ${e}; PID file missing)`}}}return{running:!1,message:"ModelWeaver is not running (no PID file found)"}}return s(t)?{running:!0,pid:t,message:`ModelWeaver is running (PID ${t})`}:(await f(),{running:!1,message:"ModelWeaver is not running (stale PID file cleaned up)"})}function C(n){return new Promise(t=>{let e=K();e.once("error",o=>{o.code==="EADDRINUSE"?t(!0):t(!1)}),e.once("listening",()=>{e.close(()=>t(!1))}),e.listen(n)})}async function ee(n,t,e){let o=await O(t);if(o.running)return{success:!1,pid:o.pid,message:`ModelWeaver is already running (PID ${o.pid})`,logPath:u()};let r=t??await D()??3456;if(await C(r))return{success:!1,message:`Port ${r} is already in use. Is ModelWeaver or another process running on it?`,logPath:u()};let i=_(import.meta.url),l=U(i),c=[g(l,"index.js"),"--monitor"];n&&c.push("--config",n),t&&c.push("--port",String(t)),e&&c.push("--verbose"),L(process.execPath,c,{detached:!0,stdio:"ignore",env:{...process.env}}).unref();let P;for(let x=0;x<20;x++){let T=await h();if(T!==null){P=T;break}await new Promise(F=>setTimeout(F,100))}return P?{success:!0,pid:P,message:`ModelWeaver started in background (PID ${P})`,logPath:u()}:{success:!1,message:"Daemon started but PID file was not created. Check logs at "+u(),logPath:u()}}async function A(n){let t=await h();if(t===null){let r=n??await D();if(r!==null&&r>0){let l=(await S(r)).filter(a=>s(a));if(l.length>0){let a=await w(),c=[...l];return a!==null&&s(a)&&!c.includes(a)&&c.push(a),await $(c),await m(),{success:!0,message:`ModelWeaver stopped (found on port ${r}, PIDs ${l.join(", ")}; PID file was missing)`}}}return{success:!1,message:"ModelWeaver is not running (no PID file found)"}}if(!s(t)){let r=await w();return r!==null&&s(r)&&(await $([r]),await m()),await f(),{success:!1,message:"ModelWeaver is not running (stale PID file cleaned up)"}}let e=await w();if(e!==null&&s(e))try{process.kill(e,"SIGTERM")}catch{}try{process.kill(t,"SIGTERM")}catch{return{success:!1,message:`Failed to stop daemon (PID ${t})`}}let o=Date.now()+5e3;for(;Date.now()<o;){let r=!s(t),i=e===null||!s(e);if(r&&i)return await f(),await m(),{success:!0,message:`ModelWeaver stopped (monitor PID ${t}, worker PID ${e})`};await new Promise(l=>setTimeout(l,100))}if(e!==null&&s(e))try{d()?I("taskkill",["/F","/PID",String(e),"/T"],{stdio:"ignore"}):process.kill(e,"SIGKILL")}catch{}if(s(t))try{d()?I("taskkill",["/F","/PID",String(t),"/T"],{stdio:"ignore"}):process.kill(t,"SIGKILL")}catch{}return await f(),await m(),{success:!0,message:`ModelWeaver force-stopped (monitor PID ${t}, worker PID ${e})`}}async function j(){let n=u();try{await y(n)}catch{}}async function te(){let n=await A();return await j(),await m(),{success:n.success||n.message.includes("not running"),message:n.success?"ModelWeaver stopped and cleaned up (PID file + log file removed)":n.message.includes("not running")?"ModelWeaver is not running. Log file cleaned up.":n.message}}async function ne(n){let t=await h();if(t===null){let e=n??await D();if(e!==null&&e>0){let r=(await S(e)).filter(i=>s(i));if(r.length>0){for(let i of r)try{d()?console.log(` Windows detected \u2014 reload signal not supported for PID ${i}. Use 'modelweaver stop && modelweaver start' instead.`):process.kill(i,"SIGHUP")}catch{}console.log(` Sent reload signal to ${r.length} process(es) on port ${e}.`);return}}console.log(" Daemon is not running.");return}if(!s(t)){await f(),console.log(" Daemon is not running (stale PID file cleaned up).");return}try{if(d()){let e=await w();e!==null?(process.kill(e,"SIGTERM"),console.log(` Killed worker (PID ${e}) on Windows \u2014 monitor will restart it.`)):console.log(" No worker PID file found \u2014 cannot reload.")}else process.kill(t,"SIGHUP"),console.log(` Sent reload signal to daemon (PID ${t}).`)}catch{console.log(" Failed to send reload signal \u2014 daemon may not be running.")}}function re(n,t=300){let e=null;return{reload(){e&&clearTimeout(e),e=setTimeout(()=>{e=null,n()},t)},dispose(){e&&(clearTimeout(e),e=null)}}}export{Q as a,k as b,u as c,R as d,Y as e,h as f,f as g,b as h,Z as i,w as j,m as k,s as l,S as m,D as n,O as o,C as p,ee as q,A as r,j as s,te as t,ne as u,re as v};
|
|
7
|
+
//# sourceMappingURL=chunk-BXLSPBXT.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{existsSync as k,readFileSync as I}from"fs";import{readFile as x,stat as j,access as E}from"fs/promises";import{join as P}from"path";import{parse as C}from"yaml";import{z as e}from"zod";import{Agent as $}from"undici";var A={failureThreshold:3,windowSeconds:60,cooldownSeconds:30},b=class{state="closed";failureTimestamps=[];openedAt=null;halfOpenInProgress=!1;halfOpenProbeId=null;nextProbeId=0;config;constructor(t={}){this.config={...A,...t}}canProceed(){if(this.state==="closed")return{allowed:!0,probeId:0};if(this.state==="open"){if(this.openedAt&&Date.now()-this.openedAt>=this.config.cooldownSeconds*1e3){this.state="half-open";let t=this.nextProbeId++;return this.halfOpenInProgress=!0,this.halfOpenProbeId=t,{allowed:!0,probeId:t}}return{allowed:!1,probeId:0}}if(!this.halfOpenInProgress){let t=this.nextProbeId++;return this.halfOpenInProgress=!0,this.halfOpenProbeId=t,{allowed:!0,probeId:t}}return{allowed:!1,probeId:0}}recordResult(t,n){if(this.halfOpenInProgress&&(n===void 0||n===this.halfOpenProbeId)&&(this.halfOpenInProgress=!1,this.halfOpenProbeId=null),t>=200&&t<300){this.state="closed",this.failureTimestamps=[],this.openedAt=null;return}if(t!==429&&t<500)return;let i=Date.now();if(this.failureTimestamps.push(i),this.pruneOldFailures(i),this.state==="half-open"){this.state="open",this.openedAt=i;return}this.failureTimestamps.length>=this.config.failureThreshold&&(this.state="open",this.openedAt=i)}getState(){return this.state}getStatus(){return{state:this.state,failures:this.failureTimestamps.length,lastFailure:this.failureTimestamps.length>0?this.failureTimestamps[this.failureTimestamps.length-1]:null}}pruneOldFailures(t){let n=t-this.config.windowSeconds*1e3;this.failureTimestamps=this.failureTimestamps.filter(i=>i>=n)}};var B=e.object({maxOutputTokens:e.number().int().positive()}).optional(),U=e.object({baseUrl:e.string().url().refine(r=>/^https?:\/\//.test(r),"baseUrl must use http:// or https://"),apiKey:e.string().min(1,"apiKey is required"),timeout:e.number().positive().default(3e4),ttfbTimeout:e.number().positive().default(15e3),stallTimeout:e.number().positive().default(
|
|
3
|
-
//# sourceMappingURL=chunk-
|
|
2
|
+
import{existsSync as k,readFileSync as I}from"fs";import{readFile as x,stat as j,access as E}from"fs/promises";import{join as P}from"path";import{parse as C}from"yaml";import{z as e}from"zod";import{Agent as $}from"undici";var A={failureThreshold:3,windowSeconds:60,cooldownSeconds:30},b=class{state="closed";failureTimestamps=[];openedAt=null;halfOpenInProgress=!1;halfOpenProbeId=null;nextProbeId=0;config;constructor(t={}){this.config={...A,...t}}canProceed(){if(this.state==="closed")return{allowed:!0,probeId:0};if(this.state==="open"){if(this.openedAt&&Date.now()-this.openedAt>=this.config.cooldownSeconds*1e3){this.state="half-open";let t=this.nextProbeId++;return this.halfOpenInProgress=!0,this.halfOpenProbeId=t,{allowed:!0,probeId:t}}return{allowed:!1,probeId:0}}if(!this.halfOpenInProgress){let t=this.nextProbeId++;return this.halfOpenInProgress=!0,this.halfOpenProbeId=t,{allowed:!0,probeId:t}}return{allowed:!1,probeId:0}}recordResult(t,n){if(this.halfOpenInProgress&&(n===void 0||n===this.halfOpenProbeId)&&(this.halfOpenInProgress=!1,this.halfOpenProbeId=null),t>=200&&t<300){this.state="closed",this.failureTimestamps=[],this.openedAt=null;return}if(t!==429&&t<500)return;let i=Date.now();if(this.failureTimestamps.push(i),this.pruneOldFailures(i),this.state==="half-open"){this.state="open",this.openedAt=i;return}this.failureTimestamps.length>=this.config.failureThreshold&&(this.state="open",this.openedAt=i)}getState(){return this.state}getStatus(){return{state:this.state,failures:this.failureTimestamps.length,lastFailure:this.failureTimestamps.length>0?this.failureTimestamps[this.failureTimestamps.length-1]:null}}pruneOldFailures(t){let n=t-this.config.windowSeconds*1e3;this.failureTimestamps=this.failureTimestamps.filter(i=>i>=n)}};var B=e.object({maxOutputTokens:e.number().int().positive()}).optional(),U=e.object({baseUrl:e.string().url().refine(r=>/^https?:\/\//.test(r),"baseUrl must use http:// or https://"),apiKey:e.string().min(1,"apiKey is required"),timeout:e.number().positive().default(3e4),ttfbTimeout:e.number().positive().default(15e3),stallTimeout:e.number().positive().default(6e4),authType:e.enum(["anthropic","bearer"]).default("anthropic"),modelLimits:B,concurrentLimit:e.number().int().min(1).optional(),poolSize:e.number().int().min(1).max(100).optional(),circuitBreaker:e.object({failureThreshold:e.number().int().min(1).optional(),windowSeconds:e.number().int().min(1).optional(),cooldownSeconds:e.number().int().min(1).optional()}).optional()}),R=e.object({provider:e.string(),model:e.string().optional()}),M=e.object({server:e.object({port:e.number().int().min(1).max(65535).default(3456),host:e.string().default("localhost")}).default({port:3456,host:"localhost"}),providers:e.record(e.string(),U),routing:e.record(e.string(),e.array(R)).default({}),tierPatterns:e.record(e.string(),e.array(e.string())).default({}),modelRouting:e.record(e.string(),e.array(R)).default({})});function L(r){return r.replace(/\$\{([^}]+)\}/g,(t,n)=>{let i=process.env[n];if(i===void 0)throw new Error(`Missing environment variable: ${n}`);return i})}function S(r){if(typeof r=="string")return L(r);if(Array.isArray(r))return r.map(S);if(r!==null&&typeof r=="object"){let t={};for(let[n,i]of Object.entries(r))t[n]=S(i);return t}return r}function T(r=process.cwd(),{skipGlobal:t=!1}={}){let n=P(r,"modelweaver.yaml");if(k(n))return n;if(!t){let i=P(process.env.HOME||process.env.USERPROFILE||"",".modelweaver","config.yaml");if(k(i))return i}return null}function G(r){let t=T(r);if(!t)return null;let n=I(t,"utf-8"),i=C(n),w=i?.providers??{},d=new Map;for(let[c,u]of Object.entries(w)){let y=String(u.apiKey??"").match(/^\$\{([^}]+)\}$/),s=y?y[1]:"";d.set(c,{baseUrl:String(u.baseUrl??""),envKey:s,authType:String(u.authType??"anthropic"),timeout:Number(u.timeout??3e4)})}let a=i?.server,p=a?{port:Number(a.port??3456),host:String(a.host??"localhost")}:null,f=new Map,m=i?.modelRouting??{};for(let[c,u]of Object.entries(m))Array.isArray(u)&&f.set(c,u.map(g=>({provider:String(g.provider??""),model:String(g.model??c)})));return{configPath:t,providers:d,server:p,modelRouting:f}}async function F(r,t){let n=null;if(r)try{await E(r),(await j(r)).isDirectory()?n=T(r):n=r}catch{n=r}if(n||(n=T(t)),!n)throw new Error("No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml");let i=await x(n,"utf-8"),w=C(i,{customTags:[]}),d=S(w),a=M.parse(d),p=new Set(Object.keys(a.providers));for(let[s,o]of Object.entries(a.routing)){for(let l of o)if(!p.has(l.provider))throw new Error(`Routing tier "${s}" references unknown provider "${l.provider}". Available: ${[...p].join(", ")}`);if(!a.tierPatterns[s])throw new Error(`Routing tier "${s}" has no entry in tierPatterns. Add patterns for this tier.`)}for(let[s,o]of Object.entries(a.modelRouting))for(let l of o)if(!p.has(l.provider))throw new Error(`modelRouting for model "${s}" references unknown provider "${l.provider}". Available: ${[...p].join(", ")}`);let f=new Map;for(let[s,o]of Object.entries(a.providers)){let l={name:s,baseUrl:o.baseUrl,apiKey:o.apiKey,timeout:o.timeout,ttfbTimeout:o.ttfbTimeout,stallTimeout:o.stallTimeout,authType:o.authType,modelLimits:o.modelLimits?{maxOutputTokens:o.modelLimits.maxOutputTokens}:void 0,concurrentLimit:o.concurrentLimit};try{let v=new URL(o.baseUrl);l._cachedHost=v.host,l._cachedOrigin=`${v.protocol}//${v.host}`,l._cachedPathname=v.pathname.replace(/\/+$/,"")}catch{}let O=o.poolSize;l._agent=new $({keepAliveTimeout:3e4,keepAliveMaxTimeout:6e4,connections:O??10,allowH2:!0}),l.poolSize=O??10;let h=o.circuitBreaker;l._circuitBreaker=new b(h?{failureThreshold:h.failureThreshold,windowSeconds:h.windowSeconds,cooldownSeconds:h.cooldownSeconds}:void 0),f.set(s,l)}let m=new Map;for(let[s,o]of Object.entries(a.routing))m.set(s,o);let c=new Map;for(let[s,o]of Object.entries(a.tierPatterns))c.set(s,o);let u=new Map;if(a.modelRouting)for(let[s,o]of Object.entries(a.modelRouting))u.set(s,o);return{config:{server:{port:a.server.port,host:a.server.host},providers:f,routing:m,tierPatterns:c,modelRouting:u},configPath:n}}async function Y(r){let{config:t}=await F(r);return t}export{L as a,T as b,G as c,F as d,Y as e};
|
|
3
|
+
//# sourceMappingURL=chunk-F72SPY6C.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/circuit-breaker.ts"],"sourcesContent":["// src/config.ts\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, stat, access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport { Agent } from \"undici\";\nimport { CircuitBreaker } from \"./circuit-breaker.js\";\nimport type { AppConfig, ProviderConfig, RoutingEntry, ServerConfig } from \"./types.js\";\n\n// --- Zod schemas for raw (pre-resolution) config ---\n\nconst modelLimitsSchema = z.object({\n maxOutputTokens: z.number().int().positive(),\n}).optional();\n\nconst providerSchema = z.object({\n baseUrl: z.string().url().refine(\n (url) => /^https?:\\/\\//.test(url),\n \"baseUrl must use http:// or https://\"\n ),\n apiKey: z.string().min(1, \"apiKey is required\"),\n timeout: z.number().positive().default(30000),\n ttfbTimeout: z.number().positive().default(15000),\n stallTimeout: z.number().positive().default(30000),\n authType: z.enum([\"anthropic\", \"bearer\"]).default(\"anthropic\"),\n modelLimits: modelLimitsSchema,\n concurrentLimit: z.number().int().min(1).optional(),\n poolSize: z.number().int().min(1).max(100).optional(),\n circuitBreaker: z.object({\n failureThreshold: z.number().int().min(1).optional(),\n windowSeconds: z.number().int().min(1).optional(),\n cooldownSeconds: z.number().int().min(1).optional(),\n }).optional(),\n});\n\nconst routingEntrySchema = z.object({\n provider: z.string(),\n model: z.string().optional(),\n});\n\nconst rawConfigSchema = z.object({\n server: z\n .object({\n port: z.number().int().min(1).max(65535).default(3456),\n host: z.string().default(\"localhost\"),\n })\n .default({ port: 3456, host: \"localhost\" }),\n providers: z.record(z.string(), providerSchema),\n routing: z.record(z.string(), z.array(routingEntrySchema)).default({}),\n tierPatterns: z.record(z.string(), z.array(z.string())).default({}),\n modelRouting: z.record(z.string(), z.array(routingEntrySchema)).default({}),\n});\n\n// --- Env var resolution ---\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, varName) => {\n const envValue = process.env[varName];\n if (envValue === undefined) {\n throw new Error(`Missing environment variable: ${varName}`);\n }\n return envValue;\n });\n}\n\nfunction resolveAllEnvStrings(obj: unknown): unknown {\n if (typeof obj === \"string\") return resolveEnvVars(obj);\n if (Array.isArray(obj)) return obj.map(resolveAllEnvStrings);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n result[key] = resolveAllEnvStrings(val);\n }\n return result;\n }\n return obj;\n}\n\n// --- Config file discovery ---\n\nexport function findConfigFile(cwd: string = process.cwd(), { skipGlobal = false } = {}): string | null {\n const localPath = join(cwd, \"modelweaver.yaml\");\n if (existsSync(localPath)) return localPath;\n if (!skipGlobal) {\n const globalPath = join(\n process.env.HOME || process.env.USERPROFILE || \"\",\n \".modelweaver\",\n \"config.yaml\"\n );\n if (existsSync(globalPath)) return globalPath;\n }\n return null;\n}\n\n// --- Lightweight peek (no env resolution, no Zod validation) ---\n\n/** Peek at existing config to extract provider metadata without resolving env vars or validating.\n * Used by init wizard to show existing providers and offer add/edit. */\nexport function peekConfig(\n cwd?: string,\n): { configPath: string; providers: Map<string, { baseUrl: string; envKey: string; authType: \"anthropic\" | \"bearer\"; timeout: number }>; server: { port: number; host: string } | null; modelRouting: Map<string, { provider: string; model: string }[]> } | null {\n const configPath = findConfigFile(cwd);\n if (!configPath) return null;\n\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = parseYaml(raw) as Record<string, unknown>;\n const providersRaw = (parsed?.providers ?? {}) as Record<string, Record<string, unknown>>;\n\n const providers = new Map<string, { baseUrl: string; envKey: string; authType: \"anthropic\" | \"bearer\"; timeout: number }>();\n\n for (const [id, config] of Object.entries(providersRaw)) {\n const apiKey = String(config.apiKey ?? \"\");\n const envMatch = apiKey.match(/^\\$\\{([^}]+)\\}$/);\n const envKey = envMatch ? envMatch[1] : \"\";\n\n providers.set(id, {\n baseUrl: String(config.baseUrl ?? \"\"),\n envKey,\n authType: String(config.authType ?? \"anthropic\") as \"anthropic\" | \"bearer\",\n timeout: Number(config.timeout ?? 30000),\n });\n }\n\n const serverRaw = parsed?.server as Record<string, unknown> | undefined;\n const server = serverRaw ? {\n port: Number(serverRaw.port ?? 3456),\n host: String(serverRaw.host ?? \"localhost\"),\n } : null;\n\n // Parse modelRouting (alias -> provider chain)\n const modelRouting = new Map<string, { provider: string; model: string }[]>();\n const modelRoutingRaw = (parsed?.modelRouting ?? {}) as Record<string, { provider: string; model: string }[]>;\n for (const [alias, entries] of Object.entries(modelRoutingRaw)) {\n if (Array.isArray(entries)) {\n modelRouting.set(alias, entries.map(e => ({ provider: String(e.provider ?? \"\"), model: String(e.model ?? alias) })));\n }\n }\n\n return { configPath, providers, server, modelRouting };\n}\n\n// --- Load & validate ---\n\nexport async function loadConfig(configPath?: string, cwd?: string): Promise<{ config: AppConfig; configPath: string }> {\n let path: string | null = null;\n if (configPath) {\n // If configPath is a directory, search for config file within it\n try {\n await access(configPath);\n const fileStat = await stat(configPath);\n if (fileStat.isDirectory()) {\n path = findConfigFile(configPath);\n } else {\n path = configPath;\n }\n } catch {\n path = configPath;\n }\n }\n if (!path) {\n path = findConfigFile(cwd);\n }\n if (!path) {\n throw new Error(\n \"No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml\"\n );\n }\n\n const raw = await readFile(path, \"utf-8\");\n const parsed = parseYaml(raw, { customTags: [] });\n\n // Resolve ${VAR} references in all string values\n const resolved = resolveAllEnvStrings(parsed) as z.infer<typeof rawConfigSchema>;\n\n const validated = rawConfigSchema.parse(resolved);\n\n // Cross-validation\n const providerNames = new Set(Object.keys(validated.providers));\n\n for (const [tier, entries] of Object.entries(validated.routing)) {\n for (const entry of entries) {\n if (!providerNames.has(entry.provider)) {\n throw new Error(\n `Routing tier \"${tier}\" references unknown provider \"${entry.provider}\". Available: ${[...providerNames].join(\", \")}`\n );\n }\n }\n\n if (!validated.tierPatterns[tier]) {\n throw new Error(\n `Routing tier \"${tier}\" has no entry in tierPatterns. Add patterns for this tier.`\n );\n }\n }\n\n // Cross-validate modelRouting provider references\n for (const [modelName, entries] of Object.entries(validated.modelRouting)) {\n for (const entry of entries) {\n if (!providerNames.has(entry.provider)) {\n throw new Error(\n `modelRouting for model \"${modelName}\" references unknown provider \"${entry.provider}\". Available: ${[...providerNames].join(\", \")}`\n );\n }\n }\n }\n\n // Build typed config — cache parsed URL components per provider (avoids per-request URL parsing)\n const providers = new Map<string, ProviderConfig>();\n for (const [name, p] of Object.entries(validated.providers)) {\n const providerConfig: ProviderConfig = {\n name,\n baseUrl: p.baseUrl,\n apiKey: p.apiKey,\n timeout: p.timeout,\n ttfbTimeout: p.ttfbTimeout,\n stallTimeout: p.stallTimeout,\n authType: p.authType,\n modelLimits: p.modelLimits ? { maxOutputTokens: p.modelLimits.maxOutputTokens } : undefined,\n concurrentLimit: p.concurrentLimit,\n };\n try {\n const parsedUrl = new URL(p.baseUrl);\n providerConfig._cachedHost = parsedUrl.host;\n providerConfig._cachedOrigin = `${parsedUrl.protocol}//${parsedUrl.host}`;\n providerConfig._cachedPathname = parsedUrl.pathname.replace(/\\/+$/, \"\");\n } catch {\n // If baseUrl is invalid, skip caching — buildOutboundHeaders will fall back gracefully\n }\n // Create per-provider connection pool for HTTP keep-alive reuse\n const poolSize = p.poolSize;\n providerConfig._agent = new Agent({\n keepAliveTimeout: 30000,\n keepAliveMaxTimeout: 60000,\n connections: poolSize ?? 10,\n allowH2: true,\n });\n providerConfig.poolSize = poolSize ?? 10;\n // Create per-provider circuit breaker\n const cbConfig = p.circuitBreaker;\n providerConfig._circuitBreaker = new CircuitBreaker(cbConfig ? {\n failureThreshold: cbConfig.failureThreshold,\n windowSeconds: cbConfig.windowSeconds,\n cooldownSeconds: cbConfig.cooldownSeconds,\n } : undefined);\n providers.set(name, providerConfig);\n }\n\n const routing = new Map<string, RoutingEntry[]>();\n for (const [tier, entries] of Object.entries(validated.routing)) {\n routing.set(tier, entries);\n }\n\n const tierPatterns = new Map<string, string[]>();\n for (const [tier, patterns] of Object.entries(validated.tierPatterns)) {\n tierPatterns.set(tier, patterns);\n }\n\n const modelRouting = new Map<string, RoutingEntry[]>();\n if (validated.modelRouting) {\n for (const [model, entries] of Object.entries(validated.modelRouting)) {\n modelRouting.set(model, entries);\n }\n }\n\n const server: ServerConfig = {\n port: validated.server.port,\n host: validated.server.host,\n };\n\n const config: AppConfig = { server, providers, routing, tierPatterns, modelRouting };\n return { config, configPath: path };\n}\n\n// --- Reload helper ---\n\nexport async function reloadConfig(configPath: string): Promise<AppConfig> {\n const { config } = await loadConfig(configPath);\n return config;\n}\n","// src/circuit-breaker.ts\nexport type BreakerState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface BreakerConfig {\n failureThreshold: number;\n windowSeconds: number;\n cooldownSeconds: number;\n}\n\nexport interface BreakerStatus {\n state: BreakerState;\n failures: number;\n lastFailure: number | null;\n}\n\nconst DEFAULT_CONFIG: BreakerConfig = {\n failureThreshold: 3,\n windowSeconds: 60,\n cooldownSeconds: 30,\n};\n\nexport class CircuitBreaker {\n private state: BreakerState = \"closed\";\n private failureTimestamps: number[] = [];\n private openedAt: number | null = null;\n private halfOpenInProgress: boolean = false;\n private halfOpenProbeId: number | null = null;\n private nextProbeId: number = 0;\n private readonly config: BreakerConfig;\n\n constructor(config: Partial<BreakerConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Returns a probe ID if the caller is allowed to proceed in half-open state,\n * or null if blocked. For closed state, always returns 0 (no probe tracking needed).\n */\n canProceed(): { allowed: boolean; probeId: number } {\n if (this.state === \"closed\") return { allowed: true, probeId: 0 };\n if (this.state === \"open\") {\n // Check if cooldown has elapsed\n if (this.openedAt && Date.now() - this.openedAt >= this.config.cooldownSeconds * 1000) {\n this.state = \"half-open\";\n const probeId = this.nextProbeId++;\n this.halfOpenInProgress = true;\n this.halfOpenProbeId = probeId;\n return { allowed: true, probeId };\n }\n return { allowed: false, probeId: 0 };\n }\n // half-open: allow exactly one probe at a time\n if (!this.halfOpenInProgress) {\n const probeId = this.nextProbeId++;\n this.halfOpenInProgress = true;\n this.halfOpenProbeId = probeId;\n return { allowed: true, probeId };\n }\n return { allowed: false, probeId: 0 };\n }\n\n recordResult(status: number, probeId?: number): void {\n // Only reset half-open flag if this is the probe that triggered it\n if (this.halfOpenInProgress && (probeId === undefined || probeId === this.halfOpenProbeId)) {\n this.halfOpenInProgress = false;\n this.halfOpenProbeId = null;\n }\n\n if (status >= 200 && status < 300) {\n // Success — reset to closed\n this.state = \"closed\";\n this.failureTimestamps = [];\n this.openedAt = null;\n return;\n }\n\n // Only count retriable errors (429, 5xx) as failures\n if (status !== 429 && status < 500) return;\n\n const now = Date.now();\n this.failureTimestamps.push(now);\n this.pruneOldFailures(now);\n\n if (this.state === \"half-open\") {\n // Any failure in half-open → back to open\n this.state = \"open\";\n this.openedAt = now;\n return;\n }\n\n // Check if threshold exceeded\n if (this.failureTimestamps.length >= this.config.failureThreshold) {\n this.state = \"open\";\n this.openedAt = now;\n }\n }\n\n getState(): BreakerState {\n return this.state;\n }\n\n getStatus(): BreakerStatus {\n return {\n state: this.state,\n failures: this.failureTimestamps.length,\n lastFailure: this.failureTimestamps.length > 0\n ? this.failureTimestamps[this.failureTimestamps.length - 1]\n : null,\n };\n }\n\n private pruneOldFailures(now: number): void {\n const cutoff = now - this.config.windowSeconds * 1000;\n this.failureTimestamps = this.failureTimestamps.filter((t) => t >= cutoff);\n }\n}\n"],"mappings":";AACA,OAAS,cAAAA,EAAY,gBAAAC,MAAoB,KACzC,OAAS,YAAAC,EAAU,QAAAC,EAAM,UAAAC,MAAc,cACvC,OAAS,QAAAC,MAAY,OACrB,OAAS,SAASC,MAAiB,OACnC,OAAS,KAAAC,MAAS,MAClB,OAAS,SAAAC,MAAa,SCStB,IAAMC,EAAgC,CACpC,iBAAkB,EAClB,cAAe,GACf,gBAAiB,EACnB,EAEaC,EAAN,KAAqB,CAClB,MAAsB,SACtB,kBAA8B,CAAC,EAC/B,SAA0B,KAC1B,mBAA8B,GAC9B,gBAAiC,KACjC,YAAsB,EACb,OAEjB,YAAYC,EAAiC,CAAC,EAAG,CAC/C,KAAK,OAAS,CAAE,GAAGF,EAAgB,GAAGE,CAAO,CAC/C,CAMA,YAAoD,CAClD,GAAI,KAAK,QAAU,SAAU,MAAO,CAAE,QAAS,GAAM,QAAS,CAAE,EAChE,GAAI,KAAK,QAAU,OAAQ,CAEzB,GAAI,KAAK,UAAY,KAAK,IAAI,EAAI,KAAK,UAAY,KAAK,OAAO,gBAAkB,IAAM,CACrF,KAAK,MAAQ,YACb,IAAMC,EAAU,KAAK,cACrB,YAAK,mBAAqB,GAC1B,KAAK,gBAAkBA,EAChB,CAAE,QAAS,GAAM,QAAAA,CAAQ,CAClC,CACA,MAAO,CAAE,QAAS,GAAO,QAAS,CAAE,CACtC,CAEA,GAAI,CAAC,KAAK,mBAAoB,CAC5B,IAAMA,EAAU,KAAK,cACrB,YAAK,mBAAqB,GAC1B,KAAK,gBAAkBA,EAChB,CAAE,QAAS,GAAM,QAAAA,CAAQ,CAClC,CACA,MAAO,CAAE,QAAS,GAAO,QAAS,CAAE,CACtC,CAEA,aAAaC,EAAgBD,EAAwB,CAOnD,GALI,KAAK,qBAAuBA,IAAY,QAAaA,IAAY,KAAK,mBACxE,KAAK,mBAAqB,GAC1B,KAAK,gBAAkB,MAGrBC,GAAU,KAAOA,EAAS,IAAK,CAEjC,KAAK,MAAQ,SACb,KAAK,kBAAoB,CAAC,EAC1B,KAAK,SAAW,KAChB,MACF,CAGA,GAAIA,IAAW,KAAOA,EAAS,IAAK,OAEpC,IAAMC,EAAM,KAAK,IAAI,EAIrB,GAHA,KAAK,kBAAkB,KAAKA,CAAG,EAC/B,KAAK,iBAAiBA,CAAG,EAErB,KAAK,QAAU,YAAa,CAE9B,KAAK,MAAQ,OACb,KAAK,SAAWA,EAChB,MACF,CAGI,KAAK,kBAAkB,QAAU,KAAK,OAAO,mBAC/C,KAAK,MAAQ,OACb,KAAK,SAAWA,EAEpB,CAEA,UAAyB,CACvB,OAAO,KAAK,KACd,CAEA,WAA2B,CACzB,MAAO,CACL,MAAO,KAAK,MACZ,SAAU,KAAK,kBAAkB,OACjC,YAAa,KAAK,kBAAkB,OAAS,EACzC,KAAK,kBAAkB,KAAK,kBAAkB,OAAS,CAAC,EACxD,IACN,CACF,CAEQ,iBAAiBA,EAAmB,CAC1C,IAAMC,EAASD,EAAM,KAAK,OAAO,cAAgB,IACjD,KAAK,kBAAoB,KAAK,kBAAkB,OAAQE,GAAMA,GAAKD,CAAM,CAC3E,CACF,EDvGA,IAAME,EAAoBC,EAAE,OAAO,CACjC,gBAAiBA,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAC7C,CAAC,EAAE,SAAS,EAENC,EAAiBD,EAAE,OAAO,CAC9B,QAASA,EAAE,OAAO,EAAE,IAAI,EAAE,OACvBE,GAAQ,eAAe,KAAKA,CAAG,EAChC,sCACF,EACA,OAAQF,EAAE,OAAO,EAAE,IAAI,EAAG,oBAAoB,EAC9C,QAASA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,GAAK,EAC5C,YAAaA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,IAAK,EAChD,aAAcA,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,GAAK,EACjD,SAAUA,EAAE,KAAK,CAAC,YAAa,QAAQ,CAAC,EAAE,QAAQ,WAAW,EAC7D,YAAaD,EACb,gBAAiBC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAClD,SAAUA,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,EACpD,eAAgBA,EAAE,OAAO,CACvB,iBAAkBA,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EACnD,cAAeA,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAChD,gBAAiBA,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,CACpD,CAAC,EAAE,SAAS,CACd,CAAC,EAEKG,EAAqBH,EAAE,OAAO,CAClC,SAAUA,EAAE,OAAO,EACnB,MAAOA,EAAE,OAAO,EAAE,SAAS,CAC7B,CAAC,EAEKI,EAAkBJ,EAAE,OAAO,CAC/B,OAAQA,EACL,OAAO,CACN,KAAMA,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,EAAE,QAAQ,IAAI,EACrD,KAAMA,EAAE,OAAO,EAAE,QAAQ,WAAW,CACtC,CAAC,EACA,QAAQ,CAAE,KAAM,KAAM,KAAM,WAAY,CAAC,EAC5C,UAAWA,EAAE,OAAOA,EAAE,OAAO,EAAGC,CAAc,EAC9C,QAASD,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,MAAMG,CAAkB,CAAC,EAAE,QAAQ,CAAC,CAAC,EACrE,aAAcH,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,MAAMA,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAClE,aAAcA,EAAE,OAAOA,EAAE,OAAO,EAAGA,EAAE,MAAMG,CAAkB,CAAC,EAAE,QAAQ,CAAC,CAAC,CAC5E,CAAC,EAIM,SAASE,EAAeC,EAAuB,CACpD,OAAOA,EAAM,QAAQ,iBAAkB,CAACC,EAAGC,IAAY,CACrD,IAAMC,EAAW,QAAQ,IAAID,CAAO,EACpC,GAAIC,IAAa,OACf,MAAM,IAAI,MAAM,iCAAiCD,CAAO,EAAE,EAE5D,OAAOC,CACT,CAAC,CACH,CAEA,SAASC,EAAqBC,EAAuB,CACnD,GAAI,OAAOA,GAAQ,SAAU,OAAON,EAAeM,CAAG,EACtD,GAAI,MAAM,QAAQA,CAAG,EAAG,OAAOA,EAAI,IAAID,CAAoB,EAC3D,GAAIC,IAAQ,MAAQ,OAAOA,GAAQ,SAAU,CAC3C,IAAMC,EAAkC,CAAC,EACzC,OAAW,CAACC,EAAKC,CAAG,IAAK,OAAO,QAAQH,CAAG,EACzCC,EAAOC,CAAG,EAAIH,EAAqBI,CAAG,EAExC,OAAOF,CACT,CACA,OAAOD,CACT,CAIO,SAASI,EAAeC,EAAc,QAAQ,IAAI,EAAG,CAAE,WAAAC,EAAa,EAAM,EAAI,CAAC,EAAkB,CACtG,IAAMC,EAAYC,EAAKH,EAAK,kBAAkB,EAC9C,GAAII,EAAWF,CAAS,EAAG,OAAOA,EAClC,GAAI,CAACD,EAAY,CACf,IAAMI,EAAaF,EACjB,QAAQ,IAAI,MAAQ,QAAQ,IAAI,aAAe,GAC/C,eACA,aACF,EACA,GAAIC,EAAWC,CAAU,EAAG,OAAOA,CACrC,CACA,OAAO,IACT,CAMO,SAASC,EACdN,EACgQ,CAChQ,IAAMO,EAAaR,EAAeC,CAAG,EACrC,GAAI,CAACO,EAAY,OAAO,KAExB,IAAMC,EAAMC,EAAaF,EAAY,OAAO,EACtCG,EAASC,EAAUH,CAAG,EACtBI,EAAgBF,GAAQ,WAAa,CAAC,EAEtCG,EAAY,IAAI,IAEtB,OAAW,CAACC,EAAIC,CAAM,IAAK,OAAO,QAAQH,CAAY,EAAG,CAEvD,IAAMI,EADS,OAAOD,EAAO,QAAU,EAAE,EACjB,MAAM,iBAAiB,EACzCE,EAASD,EAAWA,EAAS,CAAC,EAAI,GAExCH,EAAU,IAAIC,EAAI,CAChB,QAAS,OAAOC,EAAO,SAAW,EAAE,EACpC,OAAAE,EACA,SAAU,OAAOF,EAAO,UAAY,WAAW,EAC/C,QAAS,OAAOA,EAAO,SAAW,GAAK,CACzC,CAAC,CACH,CAEA,IAAMG,EAAYR,GAAQ,OACpBS,EAASD,EAAY,CACzB,KAAM,OAAOA,EAAU,MAAQ,IAAI,EACnC,KAAM,OAAOA,EAAU,MAAQ,WAAW,CAC5C,EAAI,KAGEE,EAAe,IAAI,IACnBC,EAAmBX,GAAQ,cAAgB,CAAC,EAClD,OAAW,CAACY,EAAOC,CAAO,IAAK,OAAO,QAAQF,CAAe,EACvD,MAAM,QAAQE,CAAO,GACvBH,EAAa,IAAIE,EAAOC,EAAQ,IAAIC,IAAM,CAAE,SAAU,OAAOA,EAAE,UAAY,EAAE,EAAG,MAAO,OAAOA,EAAE,OAASF,CAAK,CAAE,EAAE,CAAC,EAIvH,MAAO,CAAE,WAAAf,EAAY,UAAAM,EAAW,OAAAM,EAAQ,aAAAC,CAAa,CACvD,CAIA,eAAsBK,EAAWlB,EAAqBP,EAAkE,CACtH,IAAI0B,EAAsB,KAC1B,GAAInB,EAEF,GAAI,CACF,MAAMoB,EAAOpB,CAAU,GACN,MAAMqB,EAAKrB,CAAU,GACzB,YAAY,EACvBmB,EAAO3B,EAAeQ,CAAU,EAEhCmB,EAAOnB,CAEX,MAAQ,CACNmB,EAAOnB,CACT,CAKF,GAHKmB,IACHA,EAAO3B,EAAeC,CAAG,GAEvB,CAAC0B,EACH,MAAM,IAAI,MACR,kGACF,EAGF,IAAMlB,EAAM,MAAMqB,EAASH,EAAM,OAAO,EAClChB,EAASC,EAAUH,EAAK,CAAE,WAAY,CAAC,CAAE,CAAC,EAG1CsB,EAAWpC,EAAqBgB,CAAM,EAEtCqB,EAAY3C,EAAgB,MAAM0C,CAAQ,EAG1CE,EAAgB,IAAI,IAAI,OAAO,KAAKD,EAAU,SAAS,CAAC,EAE9D,OAAW,CAACE,EAAMV,CAAO,IAAK,OAAO,QAAQQ,EAAU,OAAO,EAAG,CAC/D,QAAWG,KAASX,EAClB,GAAI,CAACS,EAAc,IAAIE,EAAM,QAAQ,EACnC,MAAM,IAAI,MACR,iBAAiBD,CAAI,kCAAkCC,EAAM,QAAQ,iBAAiB,CAAC,GAAGF,CAAa,EAAE,KAAK,IAAI,CAAC,EACrH,EAIJ,GAAI,CAACD,EAAU,aAAaE,CAAI,EAC9B,MAAM,IAAI,MACR,iBAAiBA,CAAI,6DACvB,CAEJ,CAGA,OAAW,CAACE,EAAWZ,CAAO,IAAK,OAAO,QAAQQ,EAAU,YAAY,EACtE,QAAWG,KAASX,EAClB,GAAI,CAACS,EAAc,IAAIE,EAAM,QAAQ,EACnC,MAAM,IAAI,MACR,2BAA2BC,CAAS,kCAAkCD,EAAM,QAAQ,iBAAiB,CAAC,GAAGF,CAAa,EAAE,KAAK,IAAI,CAAC,EACpI,EAMN,IAAMnB,EAAY,IAAI,IACtB,OAAW,CAACuB,EAAMC,CAAC,IAAK,OAAO,QAAQN,EAAU,SAAS,EAAG,CAC3D,IAAMO,EAAiC,CACrC,KAAAF,EACA,QAASC,EAAE,QACX,OAAQA,EAAE,OACV,QAASA,EAAE,QACX,YAAaA,EAAE,YACf,aAAcA,EAAE,aAChB,SAAUA,EAAE,SACZ,YAAaA,EAAE,YAAc,CAAE,gBAAiBA,EAAE,YAAY,eAAgB,EAAI,OAClF,gBAAiBA,EAAE,eACrB,EACA,GAAI,CACF,IAAME,EAAY,IAAI,IAAIF,EAAE,OAAO,EACnCC,EAAe,YAAcC,EAAU,KACvCD,EAAe,cAAgB,GAAGC,EAAU,QAAQ,KAAKA,EAAU,IAAI,GACvED,EAAe,gBAAkBC,EAAU,SAAS,QAAQ,OAAQ,EAAE,CACxE,MAAQ,CAER,CAEA,IAAMC,EAAWH,EAAE,SACnBC,EAAe,OAAS,IAAIG,EAAM,CAChC,iBAAkB,IAClB,oBAAqB,IACrB,YAAaD,GAAY,GACzB,QAAS,EACX,CAAC,EACDF,EAAe,SAAWE,GAAY,GAEtC,IAAME,EAAWL,EAAE,eACnBC,EAAe,gBAAkB,IAAIK,EAAeD,EAAW,CAC7D,iBAAkBA,EAAS,iBAC3B,cAAeA,EAAS,cACxB,gBAAiBA,EAAS,eAC5B,EAAI,MAAS,EACb7B,EAAU,IAAIuB,EAAME,CAAc,CACpC,CAEA,IAAMM,EAAU,IAAI,IACpB,OAAW,CAACX,EAAMV,CAAO,IAAK,OAAO,QAAQQ,EAAU,OAAO,EAC5Da,EAAQ,IAAIX,EAAMV,CAAO,EAG3B,IAAMsB,EAAe,IAAI,IACzB,OAAW,CAACZ,EAAMa,CAAQ,IAAK,OAAO,QAAQf,EAAU,YAAY,EAClEc,EAAa,IAAIZ,EAAMa,CAAQ,EAGjC,IAAM1B,EAAe,IAAI,IACzB,GAAIW,EAAU,aACZ,OAAW,CAACgB,EAAOxB,CAAO,IAAK,OAAO,QAAQQ,EAAU,YAAY,EAClEX,EAAa,IAAI2B,EAAOxB,CAAO,EAUnC,MAAO,CAAE,OADiB,CAAE,OALC,CAC3B,KAAMQ,EAAU,OAAO,KACvB,KAAMA,EAAU,OAAO,IACzB,EAEoC,UAAAlB,EAAW,QAAA+B,EAAS,aAAAC,EAAc,aAAAzB,CAAa,EAClE,WAAYM,CAAK,CACpC,CAIA,eAAsBsB,EAAazC,EAAwC,CACzE,GAAM,CAAE,OAAAQ,CAAO,EAAI,MAAMU,EAAWlB,CAAU,EAC9C,OAAOQ,CACT","names":["existsSync","readFileSync","readFile","stat","access","join","parseYaml","z","Agent","DEFAULT_CONFIG","CircuitBreaker","config","probeId","status","now","cutoff","t","modelLimitsSchema","z","providerSchema","url","routingEntrySchema","rawConfigSchema","resolveEnvVars","value","_","varName","envValue","resolveAllEnvStrings","obj","result","key","val","findConfigFile","cwd","skipGlobal","localPath","join","existsSync","globalPath","peekConfig","configPath","raw","readFileSync","parsed","parseYaml","providersRaw","providers","id","config","envMatch","envKey","serverRaw","server","modelRouting","modelRoutingRaw","alias","entries","e","loadConfig","path","access","stat","readFile","resolved","validated","providerNames","tier","entry","modelName","name","p","providerConfig","parsedUrl","poolSize","Agent","cbConfig","CircuitBreaker","routing","tierPatterns","patterns","model","reloadConfig"]}
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/circuit-breaker.ts"],"sourcesContent":["// src/config.ts\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readFile, stat, access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport { Agent } from \"undici\";\nimport { CircuitBreaker } from \"./circuit-breaker.js\";\nimport type { AppConfig, ProviderConfig, RoutingEntry, ServerConfig } from \"./types.js\";\n\n// --- Zod schemas for raw (pre-resolution) config ---\n\nconst modelLimitsSchema = z.object({\n maxOutputTokens: z.number().int().positive(),\n}).optional();\n\nconst providerSchema = z.object({\n baseUrl: z.string().url().refine(\n (url) => /^https?:\\/\\//.test(url),\n \"baseUrl must use http:// or https://\"\n ),\n apiKey: z.string().min(1, \"apiKey is required\"),\n timeout: z.number().positive().default(30000),\n ttfbTimeout: z.number().positive().default(15000),\n stallTimeout: z.number().positive().default(60000),\n authType: z.enum([\"anthropic\", \"bearer\"]).default(\"anthropic\"),\n modelLimits: modelLimitsSchema,\n concurrentLimit: z.number().int().min(1).optional(),\n poolSize: z.number().int().min(1).max(100).optional(),\n circuitBreaker: z.object({\n failureThreshold: z.number().int().min(1).optional(),\n windowSeconds: z.number().int().min(1).optional(),\n cooldownSeconds: z.number().int().min(1).optional(),\n }).optional(),\n});\n\nconst routingEntrySchema = z.object({\n provider: z.string(),\n model: z.string().optional(),\n});\n\nconst rawConfigSchema = z.object({\n server: z\n .object({\n port: z.number().int().min(1).max(65535).default(3456),\n host: z.string().default(\"localhost\"),\n })\n .default({ port: 3456, host: \"localhost\" }),\n providers: z.record(z.string(), providerSchema),\n routing: z.record(z.string(), z.array(routingEntrySchema)).default({}),\n tierPatterns: z.record(z.string(), z.array(z.string())).default({}),\n modelRouting: z.record(z.string(), z.array(routingEntrySchema)).default({}),\n});\n\n// --- Env var resolution ---\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, varName) => {\n const envValue = process.env[varName];\n if (envValue === undefined) {\n throw new Error(`Missing environment variable: ${varName}`);\n }\n return envValue;\n });\n}\n\nfunction resolveAllEnvStrings(obj: unknown): unknown {\n if (typeof obj === \"string\") return resolveEnvVars(obj);\n if (Array.isArray(obj)) return obj.map(resolveAllEnvStrings);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n result[key] = resolveAllEnvStrings(val);\n }\n return result;\n }\n return obj;\n}\n\n// --- Config file discovery ---\n\nexport function findConfigFile(cwd: string = process.cwd(), { skipGlobal = false } = {}): string | null {\n const localPath = join(cwd, \"modelweaver.yaml\");\n if (existsSync(localPath)) return localPath;\n if (!skipGlobal) {\n const globalPath = join(\n process.env.HOME || process.env.USERPROFILE || \"\",\n \".modelweaver\",\n \"config.yaml\"\n );\n if (existsSync(globalPath)) return globalPath;\n }\n return null;\n}\n\n// --- Lightweight peek (no env resolution, no Zod validation) ---\n\n/** Peek at existing config to extract provider metadata without resolving env vars or validating.\n * Used by init wizard to show existing providers and offer add/edit. */\nexport function peekConfig(\n cwd?: string,\n): { configPath: string; providers: Map<string, { baseUrl: string; envKey: string; authType: \"anthropic\" | \"bearer\"; timeout: number }>; server: { port: number; host: string } | null; modelRouting: Map<string, { provider: string; model: string }[]> } | null {\n const configPath = findConfigFile(cwd);\n if (!configPath) return null;\n\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = parseYaml(raw) as Record<string, unknown>;\n const providersRaw = (parsed?.providers ?? {}) as Record<string, Record<string, unknown>>;\n\n const providers = new Map<string, { baseUrl: string; envKey: string; authType: \"anthropic\" | \"bearer\"; timeout: number }>();\n\n for (const [id, config] of Object.entries(providersRaw)) {\n const apiKey = String(config.apiKey ?? \"\");\n const envMatch = apiKey.match(/^\\$\\{([^}]+)\\}$/);\n const envKey = envMatch ? envMatch[1] : \"\";\n\n providers.set(id, {\n baseUrl: String(config.baseUrl ?? \"\"),\n envKey,\n authType: String(config.authType ?? \"anthropic\") as \"anthropic\" | \"bearer\",\n timeout: Number(config.timeout ?? 30000),\n });\n }\n\n const serverRaw = parsed?.server as Record<string, unknown> | undefined;\n const server = serverRaw ? {\n port: Number(serverRaw.port ?? 3456),\n host: String(serverRaw.host ?? \"localhost\"),\n } : null;\n\n // Parse modelRouting (alias -> provider chain)\n const modelRouting = new Map<string, { provider: string; model: string }[]>();\n const modelRoutingRaw = (parsed?.modelRouting ?? {}) as Record<string, { provider: string; model: string }[]>;\n for (const [alias, entries] of Object.entries(modelRoutingRaw)) {\n if (Array.isArray(entries)) {\n modelRouting.set(alias, entries.map(e => ({ provider: String(e.provider ?? \"\"), model: String(e.model ?? alias) })));\n }\n }\n\n return { configPath, providers, server, modelRouting };\n}\n\n// --- Load & validate ---\n\nexport async function loadConfig(configPath?: string, cwd?: string): Promise<{ config: AppConfig; configPath: string }> {\n let path: string | null = null;\n if (configPath) {\n // If configPath is a directory, search for config file within it\n try {\n await access(configPath);\n const fileStat = await stat(configPath);\n if (fileStat.isDirectory()) {\n path = findConfigFile(configPath);\n } else {\n path = configPath;\n }\n } catch {\n path = configPath;\n }\n }\n if (!path) {\n path = findConfigFile(cwd);\n }\n if (!path) {\n throw new Error(\n \"No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml\"\n );\n }\n\n const raw = await readFile(path, \"utf-8\");\n const parsed = parseYaml(raw, { customTags: [] });\n\n // Resolve ${VAR} references in all string values\n const resolved = resolveAllEnvStrings(parsed) as z.infer<typeof rawConfigSchema>;\n\n const validated = rawConfigSchema.parse(resolved);\n\n // Cross-validation\n const providerNames = new Set(Object.keys(validated.providers));\n\n for (const [tier, entries] of Object.entries(validated.routing)) {\n for (const entry of entries) {\n if (!providerNames.has(entry.provider)) {\n throw new Error(\n `Routing tier \"${tier}\" references unknown provider \"${entry.provider}\". Available: ${[...providerNames].join(\", \")}`\n );\n }\n }\n\n if (!validated.tierPatterns[tier]) {\n throw new Error(\n `Routing tier \"${tier}\" has no entry in tierPatterns. Add patterns for this tier.`\n );\n }\n }\n\n // Cross-validate modelRouting provider references\n for (const [modelName, entries] of Object.entries(validated.modelRouting)) {\n for (const entry of entries) {\n if (!providerNames.has(entry.provider)) {\n throw new Error(\n `modelRouting for model \"${modelName}\" references unknown provider \"${entry.provider}\". Available: ${[...providerNames].join(\", \")}`\n );\n }\n }\n }\n\n // Build typed config — cache parsed URL components per provider (avoids per-request URL parsing)\n const providers = new Map<string, ProviderConfig>();\n for (const [name, p] of Object.entries(validated.providers)) {\n const providerConfig: ProviderConfig = {\n name,\n baseUrl: p.baseUrl,\n apiKey: p.apiKey,\n timeout: p.timeout,\n ttfbTimeout: p.ttfbTimeout,\n stallTimeout: p.stallTimeout,\n authType: p.authType,\n modelLimits: p.modelLimits ? { maxOutputTokens: p.modelLimits.maxOutputTokens } : undefined,\n concurrentLimit: p.concurrentLimit,\n };\n try {\n const parsedUrl = new URL(p.baseUrl);\n providerConfig._cachedHost = parsedUrl.host;\n providerConfig._cachedOrigin = `${parsedUrl.protocol}//${parsedUrl.host}`;\n providerConfig._cachedPathname = parsedUrl.pathname.replace(/\\/+$/, \"\");\n } catch {\n // If baseUrl is invalid, skip caching — buildOutboundHeaders will fall back gracefully\n }\n // Create per-provider connection pool for HTTP keep-alive reuse\n const poolSize = p.poolSize;\n providerConfig._agent = new Agent({\n keepAliveTimeout: 30000,\n keepAliveMaxTimeout: 60000,\n connections: poolSize ?? 10,\n allowH2: true,\n });\n providerConfig.poolSize = poolSize ?? 10;\n // Create per-provider circuit breaker\n const cbConfig = p.circuitBreaker;\n providerConfig._circuitBreaker = new CircuitBreaker(cbConfig ? {\n failureThreshold: cbConfig.failureThreshold,\n windowSeconds: cbConfig.windowSeconds,\n cooldownSeconds: cbConfig.cooldownSeconds,\n } : undefined);\n providers.set(name, providerConfig);\n }\n\n const routing = new Map<string, RoutingEntry[]>();\n for (const [tier, entries] of Object.entries(validated.routing)) {\n routing.set(tier, entries);\n }\n\n const tierPatterns = new Map<string, string[]>();\n for (const [tier, patterns] of Object.entries(validated.tierPatterns)) {\n tierPatterns.set(tier, patterns);\n }\n\n const modelRouting = new Map<string, RoutingEntry[]>();\n if (validated.modelRouting) {\n for (const [model, entries] of Object.entries(validated.modelRouting)) {\n modelRouting.set(model, entries);\n }\n }\n\n const server: ServerConfig = {\n port: validated.server.port,\n host: validated.server.host,\n };\n\n const config: AppConfig = { server, providers, routing, tierPatterns, modelRouting };\n return { config, configPath: path };\n}\n\n// --- Reload helper ---\n\nexport async function reloadConfig(configPath: string): Promise<AppConfig> {\n const { config } = await loadConfig(configPath);\n return config;\n}\n","// src/circuit-breaker.ts\nexport type BreakerState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface BreakerConfig {\n failureThreshold: number;\n windowSeconds: number;\n cooldownSeconds: number;\n}\n\nexport interface BreakerStatus {\n state: BreakerState;\n failures: number;\n lastFailure: number | null;\n}\n\nconst DEFAULT_CONFIG: BreakerConfig = {\n failureThreshold: 3,\n windowSeconds: 60,\n cooldownSeconds: 30,\n};\n\nexport class CircuitBreaker {\n private state: BreakerState = \"closed\";\n private failureTimestamps: number[] = [];\n private openedAt: number | null = null;\n private halfOpenInProgress: boolean = false;\n private halfOpenProbeId: number | null = null;\n private nextProbeId: number = 0;\n private readonly config: BreakerConfig;\n\n constructor(config: Partial<BreakerConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /**\n * Returns a probe ID if the caller is allowed to proceed in half-open state,\n * or null if blocked. For closed state, always returns 0 (no probe tracking needed).\n */\n canProceed(): { allowed: boolean; probeId: number } {\n if (this.state === \"closed\") return { allowed: true, probeId: 0 };\n if (this.state === \"open\") {\n // Check if cooldown has elapsed\n if (this.openedAt && Date.now() - this.openedAt >= this.config.cooldownSeconds * 1000) {\n this.state = \"half-open\";\n const probeId = this.nextProbeId++;\n this.halfOpenInProgress = true;\n this.halfOpenProbeId = probeId;\n return { allowed: true, probeId };\n }\n return { allowed: false, probeId: 0 };\n }\n // half-open: allow exactly one probe at a time\n if (!this.halfOpenInProgress) {\n const probeId = this.nextProbeId++;\n this.halfOpenInProgress = true;\n this.halfOpenProbeId = probeId;\n return { allowed: true, probeId };\n }\n return { allowed: false, probeId: 0 };\n }\n\n recordResult(status: number, probeId?: number): void {\n // Only reset half-open flag if this is the probe that triggered it\n if (this.halfOpenInProgress && (probeId === undefined || probeId === this.halfOpenProbeId)) {\n this.halfOpenInProgress = false;\n this.halfOpenProbeId = null;\n }\n\n if (status >= 200 && status < 300) {\n // Success — reset to closed\n this.state = \"closed\";\n this.failureTimestamps = [];\n this.openedAt = null;\n return;\n }\n\n // Only count retriable errors (429, 5xx) as failures\n if (status !== 429 && status < 500) return;\n\n const now = Date.now();\n this.failureTimestamps.push(now);\n this.pruneOldFailures(now);\n\n if (this.state === \"half-open\") {\n // Any failure in half-open → back to open\n this.state = \"open\";\n this.openedAt = now;\n return;\n }\n\n // Check if threshold exceeded\n if (this.failureTimestamps.length >= this.config.failureThreshold) {\n this.state = \"open\";\n this.openedAt = now;\n }\n }\n\n getState(): BreakerState {\n return this.state;\n }\n\n getStatus(): BreakerStatus {\n return {\n state: this.state,\n failures: this.failureTimestamps.length,\n lastFailure: this.failureTimestamps.length > 0\n ? this.failureTimestamps[this.failureTimestamps.length - 1]\n : null,\n };\n }\n\n private pruneOldFailures(now: number): void {\n const cutoff = now - this.config.windowSeconds * 1000;\n this.failureTimestamps = this.failureTimestamps.filter((t) => t >= cutoff);\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","readFile","stat","access","join","parseYaml","z","Agent","DEFAULT_CONFIG","CircuitBreaker","config","probeId","status","now","cutoff","t","modelLimitsSchema","z","providerSchema","url","routingEntrySchema","rawConfigSchema","resolveEnvVars","value","_","varName","envValue","resolveAllEnvStrings","obj","result","key","val","findConfigFile","cwd","skipGlobal","localPath","join","existsSync","globalPath","peekConfig","configPath","raw","readFileSync","parsed","parseYaml","providersRaw","providers","id","config","envMatch","envKey","serverRaw","server","modelRouting","modelRoutingRaw","alias","entries","e","loadConfig","path","access","stat","readFile","resolved","validated","providerNames","tier","entry","modelName","name","p","providerConfig","parsedUrl","poolSize","Agent","cbConfig","CircuitBreaker","routing","tierPatterns","patterns","model","reloadConfig"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v}from"./chunk-
|
|
3
|
-
//# sourceMappingURL=daemon-
|
|
2
|
+
import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v}from"./chunk-BXLSPBXT.js";export{a as _setConfigPortOverride,v as createDebouncedReload,d as ensureDir,m as findPidsOnPort,n as getConfigPort,c as getLogPath,b as getPidPath,h as getWorkerPidPath,p as isPortInUse,l as isProcessAlive,f as readPidFile,j as readWorkerPidFile,u as reloadDaemon,t as removeDaemon,s as removeLogFile,g as removePidFile,k as removeWorkerPidFile,q as startDaemon,o as statusDaemon,r as stopDaemon,e as writePidFile,i as writeWorkerPidFile};
|
|
3
|
+
//# sourceMappingURL=daemon-WP5LZ7DG.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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 re}from"./chunk-SCUI4R77.js";import{createAdaptorServer as ve}from"@hono/node-server";import{readFileSync as nt}from"fs";import{Hono as He}from"hono";var $=new Map;function oe(){$.clear()}function ke(t,e){for(let[r,n]of e)for(let d of n)if(t.includes(d))return r;return null}function Se(t,e){return e.get(t)||[]}function se(t,e,r,n){let d=$.get(t);if(d)return $.delete(t),$.set(t,d),{requestId:e,model:t,tier:d.tier,providerChain:d.providerChain,startTime:Date.now(),rawBody:n};let u,a,o=r.modelRouting.get(t);if(o&&o.length>0)u="(modelRouting)",a=o;else{let c=ke(t,r.tierPatterns);if(!c)return null;u=c,a=Se(u,r.routing)}if($.size>=200){let c=$.keys().next().value;c!==void 0&&$.delete(c)}return $.set(t,{tier:u,providerChain:a}),{requestId:e,model:t,tier:u,providerChain:a,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,r){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:r,timestamp:Date.now()}),n.length>this.maxSize&&n.splice(0,n.length-this.maxSize)}getCV(e){let r=this.samples.get(e);if(!r||r.length<3)return 0;let n=r.map(a=>a.ttfbMs),d=n.reduce((a,o)=>a+o,0)/n.length;if(d===0)return 0;let u=n.reduce((a,o)=>a+(o-d)**2,0)/n.length;return Math.sqrt(u)/d}getStats(e){let r=this.samples.get(e);if(!r||r.length===0)return{count:0,mean:0,cv:0};let n=r.map(u=>u.ttfbMs),d=n.reduce((u,a)=>u+a,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 r=new Set(e);for(let n of this.samples.keys())r.has(n)||this.samples.delete(n)}},H=class{counts=new Map;increment(e){let r=(this.counts.get(e)??0)+1;return this.counts.set(e,r),r}decrement(e){let r=Math.max(0,(this.counts.get(e)??0)-1);return this.counts.set(e,r),r}get(e){return this.counts.get(e)??0}},N=new U,L=new H;function ie(t){let e=N.getCV(t.name),r=L.get(t.name),n=t.concurrentLimit??1,d=Math.max(1,n-r),u=Math.max(1,Math.floor(e*2+.5));return Math.min(u,d)}import{WebSocketServer as Re}from"ws";var Te=3e4,_e=2,ue=64*1024,Ce=500,Me=500,xe=1e4,ae=new WeakMap,J=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 z(t,e){let r=new Re({server:t,path:"/ws"});J=r,r.on("connection",n=>{let u={type:"summary",data:e.getSummary()};n.send(JSON.stringify(u));let a,o=0,c=()=>n.readyState===n.OPEN,m=e.onRecord(f=>{if(c()){if(n.bufferedAmount>ue){i(),de("metrics");return}setImmediate(()=>{if(!c())return;let g={type:"request",data:f};n.send(JSON.stringify(g))}),i()}});function i(){a||(a=setTimeout(()=>{if(a=void 0,!c())return;let f={type:"summary",data:e.getSummary()};n.send(JSON.stringify(f))},Ce))}let s=setInterval(()=>{if(!c()){clearInterval(s);return}if(o>=_e){l(),n.terminate();return}n.ping(),o++},Te);n.on("pong",()=>{o=0});let p=!1,l=()=>{p||(p=!0,clearInterval(s),a&&clearTimeout(a),m())};n.on("close",l),n.on("error",l)})}function P(t){if(!J)return;let e=JSON.stringify({type:"stream",data:t}),r=t.state==="streaming",n=t.state==="complete"||t.state==="error",d=Date.now();for(let u of J.clients)if(u.readyState===u.OPEN){if(r){let a=ae.get(u)??0;if(d-a<Me)continue;ae.set(u,d)}if(u.bufferedAmount>ue){if(n){let a=()=>{u.readyState===u.OPEN&&u.send(e)};u.once("drain",a),setTimeout(()=>{u.removeListener("drain",a),u.readyState===u.OPEN&&u.send(e)},5e3).unref();continue}le++,de("stream");continue}setImmediate(()=>{u.readyState===u.OPEN&&u.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+)/,q=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 r=e.toLowerCase();return Le.some(n=>r.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 r=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(r,{status:400,headers:{"content-type":"application/json"}})}function je(t,e){let r="",n=t,d=t.indexOf("/",t.indexOf("//")+2);d!==-1&&(n=t.substring(0,d),r=t.substring(d));let u="",a=e,o=e.indexOf("?");o!==-1&&(a=e.substring(0,o),u=e.substring(o));let c;return r.endsWith("/v1")&&a.startsWith("/v1")?c=r+a.substring(3):c=r+a,c=c.replace(Oe,"/"),n+c+u}function We(t,e,r){let n=new Headers;for(let u of Ae){let a=t.get(u);a&&n.set(u,a)}e.authType==="bearer"?n.set("Authorization",`Bearer ${e.apiKey}`):n.set("x-api-key",e.apiKey),n.set("x-request-id",r);let d=e._cachedHost;if(d)n.set("host",d);else try{let u=new URL(e.baseUrl);n.set("host",u.host)}catch{}return n}function he(t){let e=t.messages;if(!Array.isArray(e))return;let r=new Set,n=new Set;for(let c=0;c<e.length;c++){let m=e[c];if(Array.isArray(m.content))for(let i of m.content)i.type==="tool_use"&&i.id?r.add(String(i.id)):i.type==="tool_result"&&i.tool_use_id&&n.add(String(i.tool_use_id))}let d=new Set,u=new Set;for(let c of r)n.has(c)||d.add(c);for(let c of n)r.has(c)||u.add(c);if(d.size===0&&u.size===0)return;let a=!1,o=e.map(c=>{if(!Array.isArray(c.content))return c;let m=c.content.filter(i=>!(i.type==="tool_use"&&d.has(String(i.id))||i.type==="tool_result"&&u.has(String(i.tool_use_id))));return m.length<c.content.length?(a=!0,{...c,content:m}):c});a&&(t.messages=o)}function Ue(t,e,r,n,d){if(d){let w=structuredClone(n);if(e.model&&(w.model=e.model),he(w),r.modelLimits){let{maxOutputTokens:M}=r.modelLimits,h=typeof w.max_tokens=="number"?w.max_tokens:M;(w.max_tokens===void 0||h>M)&&(w.max_tokens=Math.min(h,M))}return JSON.stringify(w)}let u=!!(e.model&&n.model!==e.model),a=!1,o=!1,c=0;if(r.modelLimits){c=r.modelLimits.maxOutputTokens;let w=j.exec(t);w?a=parseInt(w[1],10)>c:typeof n.max_tokens!="number"&&(o=!0)}if(!u&&!a&&!o)return t;if(o){let w={...n};return e.model&&(w.model=e.model),w.max_tokens=c,JSON.stringify(w)}let m=[];u&&m.push(F.source),a&&m.push(j.source);let i=new RegExp(m.join("|"),"g"),s=u?`"model":"${e.model}"`:null,p=a?`"max_tokens":${c}`:null,l=n.model,f=!1;return t.replace(i,w=>s&&F.test(w)?(F.lastIndex=0,!f&&l&&(console.warn(`Routing override: ${l} -> ${e.model} via ${r.name}`),f=!0),s):p&&j.test(w)?(j.lastIndex=0,p):w)}async function fe(t,e,r,n,d,u=0){let a=n.url.replace(qe,"");e.model&&(r.actualModel=e.model);let o=je(t.baseUrl,a),c;if((n.headers.get("content-type")||"").includes("application/json"))try{let y=r.parsedBody??JSON.parse(r.rawBody),b=!1;e.model&&y.model!==e.model&&(b=!0);let k=u>0;if(k&&(b=!0),t.modelLimits){let{maxOutputTokens:S}=t.modelLimits,C=typeof y.max_tokens=="number"?y.max_tokens:S;(y.max_tokens===void 0||C>S)&&(b=!0)}if(b)if(u===0&&!k)c=Ue(r.rawBody,e,t,y,!1);else{let S=structuredClone(y);if(e.model){let C=S.model;S.model=e.model,C&&C!==e.model&&console.warn(`Routing override: ${C} -> ${e.model} via ${t.name}`)}if(k&&he(S),t.modelLimits){let{maxOutputTokens:C}=t.modelLimits,R=typeof S.max_tokens=="number"?S.max_tokens:C;(S.max_tokens===void 0||R>C)&&(S.max_tokens=Math.min(R,C))}c=JSON.stringify(S)}else c=r.rawBody}catch{c=r.rawBody}else c=r.rawBody;let i=We(n.headers,t,r.requestId);i.set("content-length",Buffer.byteLength(c,"utf-8").toString());let s=new AbortController,p=setTimeout(()=>s.abort(),t.timeout),l=t.ttfbTimeout??15e3,f=!1,g=null,w=new Promise((y,b)=>{g=setTimeout(()=>{f=!0,s.abort(),b(new Error(`TTFB timeout after ${l}ms`))},l)}),M,h;if(d){if(d.aborted){clearTimeout(p),g&&clearTimeout(g);let b=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(b,{status:502,headers:{"content-type":"application/json","content-length":q.encode(b).byteLength.toString()}})}let y=()=>{clearTimeout(p),g&&clearTimeout(g),setImmediate(()=>{if(h&&!h.destroyed&&!h.readableEnded)try{h.destroy().catch?.(()=>{})}catch{}})};d.addEventListener("abort",y,{once:!0}),M=()=>d.removeEventListener("abort",y)}try{let y=await Promise.race([Ee(o,{method:"POST",headers:i,body:c,signal:s.signal,dispatcher:t._agent}),w]);if(g&&clearTimeout(g),h=y.body,h.on("error",()=>{clearTimeout(C)}),y.statusCode>=400){clearTimeout(p);let A=await y.body.text();return new Response(A,{status:y.statusCode,statusText:y.statusText,headers:y.headers})}let b=t.stallTimeout??3e4,k=new Ie,S=`Body stalled: no data after ${b}ms`,C=setTimeout(()=>{P({requestId:r.requestId,model:String(r.actualModel??e.model??""),tier:"",state:"error",message:S,timestamp:Date.now()});try{(h?.destroy(new Error(S))).catch?.(()=>{})}catch{}k.destroy(new Error(S))},b);k.on("data",()=>{clearTimeout(C),C=setTimeout(()=>{P({requestId:r.requestId,model:String(r.actualModel??e.model??""),tier:"",state:"error",message:S,timestamp:Date.now()});try{(h?.destroy(new Error(S))).catch?.(()=>{})}catch{}k.destroy(new Error(S))},b)}),k.on("end",()=>{clearTimeout(C)}),k.on("error",()=>{clearTimeout(C)}),y.body.pipe(k);let R=new Response(k,{status:y.statusCode,headers:y.headers});return clearTimeout(p),R}catch(y){clearTimeout(p),g&&clearTimeout(g);let b=f?`Provider "${t.name}" timed out waiting for first byte after ${l}ms`:y instanceof DOMException&&y.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${y.message}`,k=JSON.stringify({type:"error",error:{type:"overloaded_error",message:b}});return new Response(k,{status:502,headers:{"content-type":"application/json","content-length":q.encode(k).byteLength.toString()}})}finally{M?.()}}async function ge(t,e,r,n,d,u,a){let o=ie(t);if(o<=1){L.increment(t.name);let l=Date.now();try{let f=await fe(t,e,r,n,d,u);return N.record(t.name,Date.now()-l),f}finally{L.decrement(t.name)}}a?.warn("Hedging request",{requestId:r.requestId,provider:t.name,count:o,cv:Math.round(N.getCV(t.name)*100)/100,inFlight:L.get(t.name),maxConcurrent:t.concurrentLimit});let c=Date.now(),m=[];for(let l=0;l<o;l++)L.increment(t.name),m.push(fe(t,e,r,n,d,u).finally(()=>L.decrement(t.name)));let i=m.map((l,f)=>l.then(g=>({response:g,hedgeIndex:f}))),s=new Set,p=[];try{for(;s.size<i.length;){let l=i.filter((g,w)=>!s.has(w));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){N.record(t.name,Date.now()-c);for(let g=0;g<i.length;g++)s.has(g)||(t._circuitBreaker&&t._circuitBreaker.recordResult(502),i[g].then(w=>{try{w.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,r,n,d,u){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":q.encode(f).byteLength.toString()}})}if(p._circuitBreaker&&!p._circuitBreaker.canProceed().allowed){u?.warn("Provider skipped by circuit breaker",{requestId:r.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":q.encode(g).byteLength.toString()}})}return d?.(s.provider,0),await ge(p,s,r,n,void 0,0,u)}let a=new AbortController,o=new Set,c=[];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":q.encode(g).byteLength.toString()}}),index:s}}let f;if(l._circuitBreaker){let g=l._circuitBreaker.canProceed();if(!g.allowed){u?.warn("Provider skipped by circuit breaker",{requestId:r.requestId,provider:p.provider});let w=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${p.provider}" skipped by circuit breaker`}});return{response:new Response(w,{status:502,headers:{"content-type":"application/json","content-length":q.encode(w).byteLength.toString()}}),index:s}}f=g.probeId}d?.(p.provider,s);try{return{response:await ge(l,p,r,n,a.signal,s,u),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":q.encode(g).byteLength.toString()}}),index:s}}}let i=[];for(let s=0;s<e.length;s++)s===0?i.push(m(0)):i.push(new Promise(p=>{setTimeout(()=>{if(a.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(;o.size<i.length;){let p=i.filter((f,g)=>!o.has(g));if(p.length===0)break;let l=await Promise.race(p);if(o.add(l.index),l.response.status>=200&&l.response.status<300){a.abort();for(let f of c)try{f.response.body?.cancel()}catch{}return l.response}if(!Ne(l.response.status)){if(a.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}c.push(l)}if(a.abort(),c.length>0)return c[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":q.encode(s).byteLength.toString()}})}catch{a.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":q.encode(s).byteLength.toString()}})}}import{randomUUID as Je}from"crypto";import{gzip as ze}from"zlib";import{promisify as Fe}from"util";var Ge=Fe(ze),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,r]of Object.entries(G))if(t.startsWith(e))return r;return 0}function X(t,e,r){let n=r+t+e;return n<=0?0:Math.round(t/n*1e3)/10}function V(t,e,r,n,d){if(d<=0)return 0;let u=t+e+r+n;return Math.round(u/d*1e3)/10}function Q(t,e,r){return new Response(JSON.stringify({type:"error",error:{type:t,message:e}}),{status:502,headers:{"content-type":"application/json","x-request-id":r}})}function Ke(t){let e=t.message?.usage??t.usage;if(!e)return{inputTokens:0,outputTokens:0,cacheReadTokens:0,cacheCreationTokens:0};let r=e.input_tokens??e.prompt_tokens??0,n=e.output_tokens??e.completion_tokens??0,d=e.cache_read_input_tokens??0,u=e.cache_creation_input_tokens??0;return{inputTokens:r,outputTokens:n,cacheReadTokens:d,cacheCreationTokens:u}}function Xe(t,e,r,n,d,u){let a=new TextDecoder,o={input:0,output:0,cacheRead:0,cacheCreation:0},c="",m="",i=4096,s=0,p=0,l=0,f=0,g="",w=null,M=250,h=0,y=!0,b="",k=100,S=T=>{for(let E of T.split(`
|
|
2
|
+
import{a as ee}from"./chunk-OMWFRIHF.js";import{d as te}from"./chunk-F72SPY6C.js";import{b as ne,e as re,g as D,k as oe}from"./chunk-BXLSPBXT.js";import{createAdaptorServer as Te}from"@hono/node-server";import{readFileSync as ot}from"fs";import{Hono as Je}from"hono";var $=new Map;function se(){$.clear()}function Re(t,e){for(let[o,n]of e)for(let l of n)if(t.includes(l))return o;return null}function Se(t,e){return e.get(t)||[]}function ie(t,e,o,n){let l=$.get(t);if(l)return $.delete(t),$.set(t,l),{requestId:e,model:t,tier:l.tier,providerChain:l.providerChain,startTime:Date.now(),rawBody:n};let c,a,r=o.modelRouting.get(t);if(r&&r.length>0)c="(modelRouting)",a=r;else{let u=Re(t,o.tierPatterns);if(!u)return null;c=u,a=Se(c,o.routing)}if($.size>=200){let u=$.keys().next().value;u!==void 0&&$.delete(u)}return $.set(t,{tier:c,providerChain:a}),{requestId:e,model:t,tier:c,providerChain:a,startTime:Date.now(),rawBody:n}}import{request as Ie}from"undici";import{PassThrough as Ae}from"stream";import me from"fs";import fe from"path";import Oe from"os";var z=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 l=this.samples.keys().next().value;l!==void 0&&this.samples.delete(l)}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(a=>a.ttfbMs),l=n.reduce((a,r)=>a+r,0)/n.length;if(l===0)return 0;let c=n.reduce((a,r)=>a+(r-l)**2,0)/n.length;return Math.sqrt(c)/l}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),l=n.reduce((c,a)=>c+a,0)/n.length;return{count:n.length,mean:Math.round(l),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}},N=new z,L=new H;function ae(t){let e=N.getCV(t.name),o=L.get(t.name),n=t.concurrentLimit??1,l=Math.max(1,n-o),c=Math.max(1,Math.floor(e*2+.5));return Math.min(c,l)}import{WebSocketServer as _e}from"ws";var Me=3e4,Ce=2,le=64*1024,xe=500,Ee=500,Pe=1e4,ce=new WeakMap,B=new WeakMap,J=null,de=0,ue=0;function pe(t){let e=Date.now();e-ue>=Pe&&(console.warn(`[ws] Backpressure: dropping ${t} events (total dropped stream events: ${de})`),ue=e)}function F(t,e){let o=new _e({server:t,path:"/ws"});J=o,o.on("connection",n=>{let c={type:"summary",data:e.getSummary()};n.send(JSON.stringify(c));let a,r=0,u=()=>n.readyState===n.OPEN,f=e.onRecord(m=>{if(u()){if(n.bufferedAmount>le){s(),pe("metrics");return}setImmediate(()=>{if(!u())return;let g={type:"request",data:m};n.send(JSON.stringify(g))}),s()}});function s(){a||(a=setTimeout(()=>{if(a=void 0,!u())return;let m={type:"summary",data:e.getSummary()};n.send(JSON.stringify(m))},xe))}let i=setInterval(()=>{if(!u()){clearInterval(i);return}if(r>=Ce){d(),n.terminate();return}n.ping(),r++},Me);n.on("pong",()=>{r=0});let p=!1,d=()=>{if(p)return;p=!0,clearInterval(i),a&&clearTimeout(a);let m=B.get(n);m&&(clearTimeout(m.timer),B.delete(n)),f()};n.on("close",d),n.on("error",d)})}function A(t){if(!J)return;let e=t.state==="streaming",o=t.state==="complete"||t.state==="error",n=Date.now(),l;for(let c of J.clients){if(c.readyState!==c.OPEN)continue;if(e){let r=ce.get(c)??0;if(n-r<Ee)continue;ce.set(c,n)}l||(l=JSON.stringify({type:"stream",data:t}));let a=l;if(c.bufferedAmount>le){if(o){let r=B.get(c);if(r)r.queue.push(a);else{let u=[a],f=()=>{if(B.delete(c),c.readyState===c.OPEN)for(let i of u)c.send(i)},s=setTimeout(()=>{if(B.delete(c),c.removeListener("drain",f),c.readyState===c.OPEN)for(let i of u)c.send(i)},5e3).unref();B.set(c,{timer:s,queue:u}),c.once("drain",f)}continue}de++,pe("stream");continue}setImmediate(()=>{c.readyState===c.OPEN&&c.send(a)})}}function ye(t){let e={...t};return Array.isArray(e.messages)&&(e.messages=[...e.messages]),e}var qe=new Set(["anthropic-version","anthropic-beta","content-type","accept"]),$e=/\/+/g,Ne=/^https?:\/\/[^/]+/,G=/"model"\s*:\s*"([^"]*)"/,W=/"max_tokens"\s*:\s*(\d+)/,q=new TextEncoder,Le=3e3;function De(t){return t===429||t>=500}var Be=["context window","context_limit","token limit","prompt is too long","max tokens","input too large","too many tokens"];function je(t,e){if(t!==400)return!1;let o=e.toLowerCase();return Be.some(n=>o.includes(n))}function We(t,e){if(!je(t,e))return null;console.warn("[context-compact] Upstream context window limit detected");try{let n=fe.join(Oe.homedir(),".claude","state");me.mkdirSync(n,{recursive:!0}),me.writeFileSync(fe.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 Ue(t,e){let o="",n=t,l=t.indexOf("/",t.indexOf("//")+2);l!==-1&&(n=t.substring(0,l),o=t.substring(l));let c="",a=e,r=e.indexOf("?");r!==-1&&(a=e.substring(0,r),c=e.substring(r));let u;return o.endsWith("/v1")&&a.startsWith("/v1")?u=o+a.substring(3):u=o+a,u=u.replace($e,"/"),n+u+c}function ze(t,e,o){let n=new Headers;for(let c of qe){let a=t.get(c);a&&n.set(c,a)}e.authType==="bearer"?n.set("Authorization",`Bearer ${e.apiKey}`):n.set("x-api-key",e.apiKey),n.set("x-request-id",o);let l=e._cachedHost;if(l)n.set("host",l);else try{let c=new URL(e.baseUrl);n.set("host",c.host)}catch{}return n}function we(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 f=e[u];if(Array.isArray(f.content))for(let s of f.content)s.type==="tool_use"&&s.id?o.add(String(s.id)):s.type==="tool_result"&&s.tool_use_id&&n.add(String(s.tool_use_id))}let l=new Set,c=new Set;for(let u of o)n.has(u)||l.add(u);for(let u of n)o.has(u)||c.add(u);if(l.size===0&&c.size===0)return;let a=!1,r=e.map(u=>{if(!Array.isArray(u.content))return u;let f=u.content.filter(s=>!(s.type==="tool_use"&&l.has(String(s.id))||s.type==="tool_result"&&c.has(String(s.tool_use_id))));return f.length<u.content.length?(a=!0,{...u,content:f}):u});a&&(t.messages=r)}function He(t,e,o,n,l){if(l){let y=ye(n);if(e.model&&(y.model=e.model),we(y),o.modelLimits){let{maxOutputTokens:S}=o.modelLimits,h=typeof y.max_tokens=="number"?y.max_tokens:S;(y.max_tokens===void 0||h>S)&&(y.max_tokens=Math.min(h,S))}return JSON.stringify(y)}let c=!!(e.model&&n.model!==e.model),a=!1,r=!1,u=0;if(o.modelLimits){u=o.modelLimits.maxOutputTokens;let y=W.exec(t);y?a=parseInt(y[1],10)>u:typeof n.max_tokens!="number"&&(r=!0)}if(!c&&!a&&!r)return t;if(r){let y={...n};return e.model&&(y.model=e.model),y.max_tokens=u,JSON.stringify(y)}let f=[];c&&f.push(G.source),a&&f.push(W.source);let s=new RegExp(f.join("|"),"g"),i=c?`"model":"${e.model}"`:null,p=a?`"max_tokens":${u}`:null,d=n.model,m=!1;return t.replace(s,y=>i&&G.test(y)?(G.lastIndex=0,!m&&d&&(console.warn(`Routing override: ${d} -> ${e.model} via ${o.name}`),m=!0),i):p&&W.test(y)?(W.lastIndex=0,p):y)}async function ge(t,e,o,n,l,c=0){let a=n.url.replace(Ne,"");e.model&&(o.actualModel=e.model);let r=Ue(t.baseUrl,a),u;if((n.headers.get("content-type")||"").includes("application/json"))try{let w=o.parsedBody??JSON.parse(o.rawBody),_=!1;e.model&&w.model!==e.model&&(_=!0);let M=c>0;if(M&&(_=!0),t.modelLimits){let{maxOutputTokens:T}=t.modelLimits,x=typeof w.max_tokens=="number"?w.max_tokens:T;(w.max_tokens===void 0||x>T)&&(_=!0)}if(_)if(c===0&&!M)u=He(o.rawBody,e,t,w,!1);else{let T=ye(w);if(e.model){let x=T.model;T.model=e.model,x&&x!==e.model&&console.warn(`Routing override: ${x} -> ${e.model} via ${t.name}`)}if(M&&we(T),t.modelLimits){let{maxOutputTokens:x}=t.modelLimits,R=typeof T.max_tokens=="number"?T.max_tokens:x;(T.max_tokens===void 0||R>x)&&(T.max_tokens=Math.min(R,x))}u=JSON.stringify(T)}else u=o.rawBody}catch{u=o.rawBody}else u=o.rawBody;let s=ze(n.headers,t,o.requestId);s.set("content-length",Buffer.byteLength(u,"utf-8").toString());let i=new AbortController,p=setTimeout(()=>i.abort(),t.timeout),d=t.ttfbTimeout??15e3,m=!1,g=null,y=new Promise((w,_)=>{g=setTimeout(()=>{m=!0,i.abort(),_(new Error(`TTFB timeout after ${d}ms`))},d)}),S,h,b,v;if(l){if(l.aborted){clearTimeout(p),g&&clearTimeout(g);let M=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(M,{status:502,headers:{"content-type":"application/json","content-length":q.encode(M).byteLength.toString()}})}let w=l.getMaxListeners?.()??10;l.setMaxListeners?.(w+1);let _=()=>{clearTimeout(p),g&&clearTimeout(g),v&&clearTimeout(v),setImmediate(()=>{if(h&&!h.destroyed&&!h.readableEnded)try{h.destroy().catch?.(()=>{})}catch{}b&&!b.destroyed&&b.destroy(new Error("Cancelled"))})};l.addEventListener("abort",_,{once:!0}),S=()=>{l.removeEventListener("abort",_),l.setMaxListeners?.(l.getMaxListeners?.()-1)}}try{let w=await Promise.race([Ie(r,{method:"POST",headers:s,body:u,signal:i.signal,dispatcher:t._agent}),y]);if(g&&clearTimeout(g),h=w.body,h.on("error",()=>{clearTimeout(v)}),w.statusCode>=400){clearTimeout(p);let x=await w.body.text();return new Response(x,{status:w.statusCode,statusText:w.statusText,headers:w.headers})}let _=t.stallTimeout??3e4;b=new Ae;let M=`Body stalled: no data after ${_}ms`;v=setTimeout(()=>{A({requestId:o.requestId,model:String(o.actualModel??e.model??""),tier:"",state:"error",message:M,timestamp:Date.now()});try{(h?.destroy(new Error(M))).catch?.(()=>{})}catch{}b.destroy(new Error(M))},_),b.on("data",()=>{clearTimeout(v),v=setTimeout(()=>{A({requestId:o.requestId,model:String(o.actualModel??e.model??""),tier:"",state:"error",message:M,timestamp:Date.now()});try{(h?.destroy(new Error(M))).catch?.(()=>{})}catch{}b.destroy(new Error(M))},_)}),b.on("end",()=>{clearTimeout(v)}),b.on("error",()=>{clearTimeout(v);try{b.destroy()}catch{}}),w.body.pipe(b);let T=new Response(b,{status:w.statusCode,headers:w.headers});return clearTimeout(p),T}catch(w){clearTimeout(p),g&&clearTimeout(g),v&&clearTimeout(v);let _=m?`Provider "${t.name}" timed out waiting for first byte after ${d}ms`:w instanceof DOMException&&w.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${w.message}`,M=JSON.stringify({type:"error",error:{type:"overloaded_error",message:_}});return new Response(M,{status:502,headers:{"content-type":"application/json","content-length":q.encode(M).byteLength.toString()}})}finally{S?.()}}async function he(t,e,o,n,l,c,a){let r=ae(t);if(r<=1){L.increment(t.name);let m=Date.now();try{let g=await ge(t,e,o,n,l,c);return N.record(t.name,Date.now()-m),g}finally{L.decrement(t.name)}}a?.warn("Hedging request",{requestId:o.requestId,provider:t.name,count:r,cv:Math.round(N.getCV(t.name)*100)/100,inFlight:L.get(t.name),maxConcurrent:t.concurrentLimit});let u=Date.now(),f=[],s=new AbortController;for(let m=0;m<r;m++){L.increment(t.name);let g=l?AbortSignal.any([l,s.signal]):s.signal;f.push(ge(t,e,o,n,g,c).finally(()=>L.decrement(t.name)))}let i=f.map((m,g)=>m.then(y=>({response:y,hedgeIndex:g}))),p=new Set,d=[];try{for(;p.size<i.length;){let m=i.filter((y,S)=>!p.has(S));if(m.length===0)break;let g=await Promise.race(m);if(p.add(g.hedgeIndex),t._circuitBreaker&&t._circuitBreaker.recordResult(g.response.status),g.response.status>=200&&g.response.status<300){N.record(t.name,Date.now()-u),s.abort();for(let y=0;y<i.length;y++)p.has(y)||(t._circuitBreaker&&t._circuitBreaker.recordResult(502),i[y].then(S=>{try{S.response.body?.cancel()}catch{}}));for(let y of d)try{y.body?.cancel()}catch{}return g.response}d.push(g.response)}s.abort();for(let m of d)try{m.body?.cancel()}catch{}return d[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{s.abort();for(let m of d)try{m.body?.cancel()}catch{}return d[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 be(t,e,o,n,l,c){if(e.length<=1){let i=e[0],p=t.get(i.provider);if(!p){let m=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${i.provider}`}});return new Response(m,{status:502,headers:{"content-type":"application/json","content-length":q.encode(m).byteLength.toString()}})}if(p._circuitBreaker&&!p._circuitBreaker.canProceed().allowed){c?.warn("Provider skipped by circuit breaker",{requestId:o.requestId,provider:i.provider});let g=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${i.provider}" skipped by circuit breaker`}});return new Response(g,{status:502,headers:{"content-type":"application/json","content-length":q.encode(g).byteLength.toString()}})}return l?.(i.provider,0),await he(p,i,o,n,void 0,0,c)}let a=new AbortController,r=new Set,u=[];async function f(i){let p=e[i],d=t.get(p.provider);if(!d){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":q.encode(g).byteLength.toString()}}),index:i}}let m;if(d._circuitBreaker){let g=d._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":q.encode(y).byteLength.toString()}}),index:i}}m=g.probeId}l?.(p.provider,i);try{return{response:await he(d,p,o,n,a.signal,i,c),index:i}}catch{d._circuitBreaker&&d._circuitBreaker.recordResult(502,m);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":q.encode(g).byteLength.toString()}}),index:i}}}let s=[];for(let i=0;i<e.length;i++)i===0?s.push(f(0)):s.push(new Promise(p=>{setTimeout(()=>{if(a.signal.aborted){let d=JSON.stringify({type:"error",error:{type:"api_error",message:"Cancelled by race winner"}});p({response:new Response(d,{status:502,headers:{"content-type":"application/json"}}),index:i});return}f(i).then(p)},Le)}));try{for(;r.size<s.length;){let p=s.filter((m,g)=>!r.has(g));if(p.length===0)break;let d=await Promise.race(p);if(r.add(d.index),d.response.status>=200&&d.response.status<300){a.abort();for(let m of u)try{m.response.body?.cancel()}catch{}return d.response}if(!De(d.response.status)){if(a.abort(),d.response.status===400&&d.response.body)try{let m=await d.response.text(),g=We(d.response.status,m);return g||new Response(m,{status:d.response.status,statusText:d.response.statusText,headers:d.response.headers})}catch{return d.response}return d.response}u.push(d)}if(a.abort(),u.length>0)return u[0].response;let i=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(i,{status:502,headers:{"content-type":"application/json","content-length":q.encode(i).byteLength.toString()}})}catch{a.abort();let i=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(i,{status:502,headers:{"content-type":"application/json","content-length":q.encode(i).byteLength.toString()}})}}import{randomUUID as Fe}from"crypto";import{gzip as Ge}from"zlib";import{promisify as Ke}from"util";var Ve=Ke(Ge),K={"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 V(t){if(K[t])return K[t];for(let[e,o]of Object.entries(K))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 Z(t,e,o,n,l){if(l<=0)return 0;let c=t+e+o+n;return Math.round(c/l*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 Xe(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,l=e.cache_read_input_tokens??0,c=e.cache_creation_input_tokens??0;return{inputTokens:o,outputTokens:n,cacheReadTokens:l,cacheCreationTokens:c}}function Ze(t,e,o,n,l,c){let a=new TextDecoder,r={input:0,output:0,cacheRead:0,cacheCreation:0},u="",f="",s=4096,i=0,p=0,d=0,m=0,g="",y=null,S=250,h=0,b=!0,v="",w=100,_=R=>{for(let P of R.split(`
|
|
3
3
|
|
|
4
|
-
`)){if(!
|
|
5
|
-
`).find(
|
|
6
|
-
`).replace(/\\"/g,'"').replace(/\\\\/g,"\\");
|
|
7
|
-
`).replace(/\\"/g,'"').replace(/\\\\/g,"\\");
|
|
8
|
-
`);
|
|
9
|
-
`:"")+I;E&&m.trim()&&S(m);let _=Date.now();if(y||_-h>=M){h=_,y=!1;let I=K(t.actualModel||t.model);setImmediate(()=>{P({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:o.output,timestamp:_,preview:b,cacheHitRate:X(o.cacheRead,o.cacheCreation,o.input),contextPercent:V(o.input,o.cacheRead,o.cacheCreation,o.output,I),contextWindowSize:I||void 0})})}E&&R(o.input,o.output,o.cacheRead,o.cacheCreation)}else{g+=T,g.length>i&&(g=g.slice(-i)),C(g);let x=Date.now();if(y||x-h>=M){h=x,y=!1;let _=K(t.actualModel||t.model);setImmediate(()=>{P({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:f,timestamp:x,preview:b,cacheHitRate:X(p,l,s),contextPercent:V(s,p,l,f,_),contextWindowSize:_||void 0})})}E&&R(s,f,p,l)}};return new TransformStream({transform(T,E){E.enqueue(T),A(a.decode(T,{stream:!0}),!1)},flush(){A("",!0)}})}function we(t){let e=t._cachedOrigin,r=t.poolSize??10;return`${e??"unknown"}:${r}`}function Y(t,e,r){let n=t,d=Z(e),u=new He;return u.onError((a,o)=>(console.error(`[server] Unhandled error: ${a.message}`),o.json({type:"error",error:{type:"api_error",message:"Internal proxy error"}},{status:500,headers:{"content-type":"application/json"}}))),u.use("/api/*",async(a,o)=>{a.header("Access-Control-Allow-Origin","*"),await o()}),u.options("/api/*",a=>(a.header("Access-Control-Allow-Origin","*"),a.header("Access-Control-Allow-Methods","GET, POST, OPTIONS"),a.header("Access-Control-Allow-Headers","Content-Type, Authorization, anthropic-version, x-api-key"),a.body("",200))),u.post("/v1/messages",async a=>{let o=Je(),c,m;try{m=await a.req.text(),c=JSON.parse(m)}catch{return Q("invalid_request_error","Invalid JSON body",o)}let i=c.model;if(!i)return Q("invalid_request_error","Missing 'model' field in request body",o);let s=se(i,o,n,m);if(s&&(s.parsedBody=c),!s){d.info("No tier match",{requestId:o,model:i});let h=n.modelRouting.size>0?` Configured model routes: ${[...n.modelRouting.keys()].join(", ")}.`:"";return Q("invalid_request_error",`No route matches model "${i}". Configured tiers: ${[...n.tierPatterns.keys()].join(", ")}.${h}`,o)}d.info("Routing request",{requestId:o,model:i,tier:s.tier,providers:s.providerChain.map(h=>h.provider)}),P({requestId:o,model:i,tier:s.tier,state:"start",provider:s.providerChain[0]?.provider??"unknown",timestamp:Date.now()});let p="unknown",l;try{if(l=await ye(n.providers,s.providerChain,s,a.req.raw,(h,y)=>{d.info("Attempting provider",{requestId:o,provider:h,index:y,tier:s.tier}),p||(p=h)},d),l.status<400){let h=17;l.headers.forEach((y,b)=>{h+=b.length+y.length+4}),h+=2,setImmediate(()=>{P({requestId:o,model:i,tier:s.tier,state:"ttfb",status:l.status,headerSize:h,timestamp:Date.now()})})}}catch(h){let y=h instanceof Error?h.message:String(h);return d.error("Forward failed",{requestId:o,error:y}),setImmediate(()=>{P({requestId:o,model:i,tier:s.tier,state:"error",status:502,message:y,timestamp:Date.now()})}),a.json({type:"error",error:{type:"api_error",message:"Upstream request failed: "+y}},502)}l.status>=400&&setImmediate(()=>{P({requestId:o,model:i,tier:s.tier,state:"error",status:l.status,message:`HTTP ${l.status}`,timestamp:Date.now()})});let f=l.body;if(l.body&&l.status>=200&&l.status<300&&r){let h=s.providerChain.length>0?s.providerChain[0].provider:p,y=Xe(s,p,h,r,l.status,l.headers.get("content-type")||"");f=l.body.pipeThrough(y)}let g=new Headers(l.headers);g.set("x-request-id",o);let w=new Response(f,{status:l.status,statusText:l.statusText,headers:g}),M=Date.now()-s.startTime;return d.info("Request completed",{requestId:o,model:i,tier:s.tier,status:w.status,latencyMs:M}),w}),u.get("/api/metrics/summary",async a=>{if(!r)return a.json({error:"Metrics not enabled"},503);let o=r.getSummary(),c=JSON.stringify(o);if((a.req.header("accept-encoding")||"").includes("gzip")&&c.length>=1024){let i=await Ge(Buffer.from(c));return new Response(i,{status:200,headers:{"content-type":"application/json","content-encoding":"gzip",vary:"accept-encoding"}})}return a.json(o)}),u.get("/api/circuit-breaker",a=>{let o={};for(let[c,m]of n.providers){let i=m._circuitBreaker;if(i){let s=i.getStatus();o[c]={state:s.state,failures:s.failures,lastFailure:s.lastFailure?new Date(s.lastFailure).toISOString():null}}}return a.json(o)}),{app:u,getConfig:()=>n,setConfig:a=>{let o=new Map;for(let m of n.providers.values())m._agent&&o.set(we(m),m._agent);let c=new Set;for(let m of a.providers.values()){let i=we(m),s=o.get(i);s&&(m._agent=s,c.add(i))}for(let[m,i]of o)c.has(m)||i.close();n=a,oe()}}}var W=class{buffer;maxSize;head=0;count=0;subscribers;createdAt;_totalInputTokens=0;_totalOutputTokens=0;_totalTokensPerSec=0;_totalCacheReadTokens=0;_totalCacheCreationTokens=0;_modelMap=new Map;_providerMap=new Map;constructor(e=1e3){this.buffer=new Array(e).fill(null),this.maxSize=e,this.subscribers=new Set,this.createdAt=Date.now()}recordRequest(e){let r=this.head%this.maxSize,n=this.count>=this.maxSize?this.buffer[r]:null;if(n!==null){this._totalInputTokens-=n.inputTokens??0,this._totalOutputTokens-=n.outputTokens??0,this._totalTokensPerSec-=n.tokensPerSec??0,this._totalCacheReadTokens-=n.cacheReadTokens??0,this._totalCacheCreationTokens-=n.cacheCreationTokens??0;let o=n.model,c=this._modelMap.get(o);c&&(c.count--,c.count<=0&&this._modelMap.delete(o));let m=n.targetProvider??n.provider,i=this._providerMap.get(m)??0;i<=1?this._providerMap.delete(m):this._providerMap.set(m,i-1)}this._totalInputTokens+=e.inputTokens??0,this._totalOutputTokens+=e.outputTokens??0,this._totalTokensPerSec+=e.tokensPerSec??0,this._totalCacheReadTokens+=e.cacheReadTokens??0,this._totalCacheCreationTokens+=e.cacheCreationTokens??0;let d=e.model,u=this._modelMap.get(d);u?(u.count++,e.timestamp>u.lastSeen&&(u.lastSeen=e.timestamp),u.actualModel=e.actualModel):this._modelMap.set(d,{actualModel:e.actualModel,count:1,lastSeen:e.timestamp});let a=e.targetProvider??e.provider;this._providerMap.set(a,(this._providerMap.get(a)??0)+1),this.buffer[r]=e,this.head++,this.count<this.maxSize&&this.count++;for(let o of this.subscribers)try{o(e)}catch{}}getSummary(){let e=this.getRecentRequests(),r=[...this._modelMap.entries()].map(([a,{actualModel:o,count:c,lastSeen:m}])=>({model:a,actualModel:o,count:c,lastSeen:m})).sort((a,o)=>o.count-a.count),n=[...this._providerMap.entries()].map(([a,o])=>({provider:a,count:o})).sort((a,o)=>o.count-a.count),d=0,u=0;for(let a of e){let o=(a.inputTokens??0)+(a.cacheReadTokens??0)+(a.cacheCreationTokens??0);o>0&&(a.cacheReadTokens??0)>0&&(d+=a.cacheReadTokens/o*100,u++)}return{totalRequests:this.count,totalInputTokens:this._totalInputTokens,totalOutputTokens:this._totalOutputTokens,avgTokensPerSec:this.count>0?Math.round(this._totalTokensPerSec/this.count*10)/10:0,totalCacheReadTokens:this._totalCacheReadTokens,totalCacheCreationTokens:this._totalCacheCreationTokens,avgCacheHitRate:u>0?Math.round(d/u*10)/10:0,activeModels:r,providerDistribution:n,recentRequests:e,uptimeSeconds:Math.floor((Date.now()-this.createdAt)/1e3)}}onRecord(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}getRecentRequests(){if(this.count===0)return[];let e=Math.min(this.count,50),r=[];for(let n=0;n<e;n++){let d=((this.head-1-n)%this.maxSize+this.maxSize)%this.maxSize,u=this.buffer[d];u!==null&&r.push(u)}return r.reverse(),r}};import{spawn as Ve}from"child_process";import{existsSync as Qe,unlinkSync as Ye}from"fs";import{dirname as Ze,join as et}from"path";import{fileURLToPath as tt}from"url";async function be(t){let e=te();Qe(e)&&Ye(e),await ne(process.pid);let r=process.argv[1]||et(Ze(tt(import.meta.url)),"index.js");process.on("uncaughtException",f=>{console.error(`[monitor] Uncaught exception: ${f.message}`)}),process.on("unhandledRejection",f=>{console.error(`[monitor] Unhandled rejection: ${f}`)});let n=10,d=1e3,u=3e4,a=6e4,o=0,c=null,m=null,i=!1,s=!1,p=null;async function l(){let f=await import("net");if(await new Promise(M=>{let h=f.createServer();h.once("error",()=>M(!0)),h.once("listening",()=>{h.close(()=>M(!1))}),h.listen(t.port??3456)})){console.error(`[monitor] Port ${t.port??3456} already in use, skipping worker spawn`);return}let w=[r,"--daemon"];t.config&&w.push("--config",t.config),t.port&&w.push("--port",String(t.port)),t.verbose&&w.push("--verbose"),p=Ve(process.execPath,w,{detached:!0,stdio:"ignore",env:{...process.env}}),c&&clearTimeout(c),c=setTimeout(()=>{o>0&&console.error(`[monitor] Worker stable for ${a}ms, resetting restart counter`),o=0,c=null},a),p.on("exit",async M=>{p=null,c&&(clearTimeout(c),c=null),await re(),M===0&&!s&&(await B(),process.exit(0)),s=!1,i&&(console.error("[monitor] Worker exited during shutdown, monitor exiting"),await B(),process.exit(0));let h=o;h>=n&&(console.error(`[monitor] Max restart attempts exhausted (${n}), monitor exiting`),await B(),process.exit(1));let y=Math.min(d*2**h,u);o++,console.error(`[monitor] Worker died (code ${M}), restarting in ${y}ms (attempt ${o}/${n})`),m=setTimeout(l,y)})}process.on("SIGTERM",()=>{if(i=!0,m&&(clearTimeout(m),m=null),c&&(clearTimeout(c),c=null),p){try{p.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else B().then(()=>process.exit(0))}),process.on("SIGINT",()=>{if(i=!0,m&&(clearTimeout(m),m=null),c&&(clearTimeout(c),c=null),p){try{p.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else B().then(()=>process.exit(0))}),process.on("SIGHUP",()=>{if(console.log("[monitor] Received reload signal, restarting worker..."),s=!0,m&&(clearTimeout(m),m=null),p)try{p.kill("SIGTERM")}catch{}o=0}),l()}var rt=JSON.parse(nt(new URL("../package.json",import.meta.url),"utf-8")).version;function ot(t){let e={verbose:!1,help:!1,daemon:!1,monitor:!1,gui:!1};for(let r=2;r<t.length;r++)switch(t[r]){case"-p":case"--port":let n=t[++r];(!n||isNaN(parseInt(n,10)))&&(console.error("Error: -p/--port requires a number"),process.exit(1)),e.port=parseInt(n,10);break;case"-c":case"--config":let d=t[++r];d||(console.error("Error: -c/--config requires a path"),process.exit(1)),e.config=d;break;case"-v":case"--verbose":e.verbose=!0;break;case"-h":case"--help":e.help=!0;break;case"--daemon":e.daemon=!0;break;case"--monitor":e.monitor=!0;break}return e}function st(){console.log(`
|
|
4
|
+
`)){if(!P)continue;let E=P.split(`
|
|
5
|
+
`).find(C=>C.startsWith("data:"));if(E)try{let C=JSON.parse(E.slice(5));if(E.includes('"usage"')){let k=Xe(C);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 I=C.delta;I&&typeof I.text=="string"&&(v+=I.text,v.length>w&&(v=v.slice(-w)));let O=C.choices;if(O?.[0]){let k=O[0].delta;k&&typeof k.content=="string"&&(v+=k.content,v.length>w&&(v=v.slice(-w)))}}catch{}}},M=R=>{if(!R.includes('"usage"')){let k=[...R.matchAll(/"text"\s*:\s*"((?:[^"\\]|\\.)*)"/g)];if(k.length>0){let j=k[k.length-1][1].replace(/\\n/g,`
|
|
6
|
+
`).replace(/\\"/g,'"').replace(/\\\\/g,"\\");v+=j,v.length>w&&(v=v.slice(-w))}return}let P=[...R.matchAll(/"(?:input_tokens|prompt_tokens)"\s*:\s*(\d+)/g)],E=[...R.matchAll(/"cache_read_input_tokens"\s*:\s*(\d+)/g)],C=[...R.matchAll(/"cache_creation_input_tokens"\s*:\s*(\d+)/g)],I=[...R.matchAll(/"(?:output_tokens|completion_tokens)"\s*:\s*(\d+)/g)];if(P.length>0){let k=parseInt(P[P.length-1][1],10);k>i&&(i=k)}if(E.length>0){let k=parseInt(E[E.length-1][1],10);k>p&&(p=k)}if(C.length>0){let k=parseInt(C[C.length-1][1],10);k>d&&(d=k)}if(I.length>0){let k=parseInt(I[I.length-1][1],10);k>m&&(m=k)}let O=[...R.matchAll(/"text"\s*:\s*"((?:[^"\\]|\\.)*)"/g)];if(O.length>0){let k=O[O.length-1][1].replace(/\\n/g,`
|
|
7
|
+
`).replace(/\\"/g,'"').replace(/\\\\/g,"\\");v+=k,v.length>w&&(v=v.slice(-w))}},T=(R,P,E=0,C=0)=>{try{let I=Date.now()-t.startTime,O=I/1e3,k=O>0?P/O:0;n.recordRequest({requestId:t.requestId,model:t.model,actualModel:t.actualModel||t.model,tier:t.tier,provider:e,targetProvider:o,status:l,inputTokens:R,outputTokens:P,latencyMs:I,tokensPerSec:Math.round(k*10)/10,timestamp:Date.now(),fallbackMode:t.fallbackMode,cacheReadTokens:E,cacheCreationTokens:C});let j=V(t.actualModel||t.model);setImmediate(()=>{A({requestId:t.requestId,model:t.model,tier:t.tier,state:"complete",status:l,latencyMs:Date.now()-t.startTime,inputTokens:R,outputTokens:P,tokensPerSec:Math.round(k*10)/10,timestamp:Date.now(),cacheReadTokens:E,cacheCreationTokens:C,cacheHitRate:X(E,C,R),contextPercent:Z(R,E,C,P,j),contextWindowSize:j||void 0})})}catch{}},x=(R,P)=>{if(y===null&&(y=c.includes("text/event-stream")||R.startsWith("event:")),y){u+=R;let E=u.split(`
|
|
8
|
+
`);u=E.pop();for(let I of E)I===""?f&&(_(f),f=""):f+=(f?`
|
|
9
|
+
`:"")+I;P&&f.trim()&&_(f);let C=Date.now();if(b||C-h>=S){h=C,b=!1;let I=V(t.actualModel||t.model);setImmediate(()=>{A({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:r.output,timestamp:C,preview:v,cacheHitRate:X(r.cacheRead,r.cacheCreation,r.input),contextPercent:Z(r.input,r.cacheRead,r.cacheCreation,r.output,I),contextWindowSize:I||void 0})})}P&&T(r.input,r.output,r.cacheRead,r.cacheCreation)}else{g+=R,g.length>s&&(g=g.slice(-s)),M(g);let E=Date.now();if(b||E-h>=S){h=E,b=!1;let C=V(t.actualModel||t.model);setImmediate(()=>{A({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:m,timestamp:E,preview:v,cacheHitRate:X(p,d,i),contextPercent:Z(i,p,d,m,C),contextWindowSize:C||void 0})})}P&&T(i,m,p,d)}};return new TransformStream({transform(R,P){P.enqueue(R),x(a.decode(R,{stream:!0}),!1)},flush(){x("",!0)}})}function ve(t){let e=t._cachedOrigin,o=t.poolSize??10;return`${e??"unknown"}:${o}`}function Y(t,e,o){let n=t,l=ee(e),c=new Je;return c.onError((a,r)=>(console.error(`[server] Unhandled error: ${a.message}`),r.json({type:"error",error:{type:"api_error",message:"Internal proxy error"}},{status:500,headers:{"content-type":"application/json"}}))),c.use("/api/*",async(a,r)=>{a.header("Access-Control-Allow-Origin","*"),await r()}),c.options("/api/*",a=>(a.header("Access-Control-Allow-Origin","*"),a.header("Access-Control-Allow-Methods","GET, POST, OPTIONS"),a.header("Access-Control-Allow-Headers","Content-Type, Authorization, anthropic-version, x-api-key"),a.body("",200))),c.post("/v1/messages",async a=>{let r=Fe(),u,f;try{f=await a.req.text(),u=JSON.parse(f)}catch{return Q("invalid_request_error","Invalid JSON body",r)}let s=u.model;if(!s)return Q("invalid_request_error","Missing 'model' field in request body",r);let i=ie(s,r,n,f);if(i&&(i.parsedBody=u),!i){l.info("No tier match",{requestId:r,model:s});let h=n.modelRouting.size>0?` Configured model routes: ${[...n.modelRouting.keys()].join(", ")}.`:"";return Q("invalid_request_error",`No route matches model "${s}". Configured tiers: ${[...n.tierPatterns.keys()].join(", ")}.${h}`,r)}l.info("Routing request",{requestId:r,model:s,tier:i.tier,providers:i.providerChain.map(h=>h.provider)}),A({requestId:r,model:s,tier:i.tier,state:"start",provider:i.providerChain[0]?.provider??"unknown",timestamp:Date.now()});let p="unknown",d;try{if(d=await be(n.providers,i.providerChain,i,a.req.raw,(h,b)=>{l.info("Attempting provider",{requestId:r,provider:h,index:b,tier:i.tier}),p||(p=h)},l),d.status<400){let h=17;d.headers.forEach((b,v)=>{h+=v.length+b.length+4}),h+=2,setImmediate(()=>{A({requestId:r,model:s,tier:i.tier,state:"ttfb",status:d.status,headerSize:h,timestamp:Date.now()})})}}catch(h){let b=h instanceof Error?h.message:String(h);return l.error("Forward failed",{requestId:r,error:b}),setImmediate(()=>{A({requestId:r,model:s,tier:i.tier,state:"error",status:502,message:b,timestamp:Date.now()})}),a.json({type:"error",error:{type:"api_error",message:"Upstream request failed: "+b}},502)}d.status>=400&&setImmediate(()=>{A({requestId:r,model:s,tier:i.tier,state:"error",status:d.status,message:`HTTP ${d.status}`,timestamp:Date.now()})});let m=d.body;if(d.body&&d.status>=200&&d.status<300&&o){let h=i.providerChain.length>0?i.providerChain[0].provider:p,b=Ze(i,p,h,o,d.status,d.headers.get("content-type")||"");m=d.body.pipeThrough(b)}let g=new Headers(d.headers);g.set("x-request-id",r);let y=new Response(m,{status:d.status,statusText:d.statusText,headers:g}),S=Date.now()-i.startTime;return l.info("Request completed",{requestId:r,model:s,tier:i.tier,status:y.status,latencyMs:S}),y}),c.get("/api/metrics/summary",async a=>{if(!o)return a.json({error:"Metrics not enabled"},503);let r=o.getSummary(),u=JSON.stringify(r);if((a.req.header("accept-encoding")||"").includes("gzip")&&u.length>=1024){let s=await Ve(Buffer.from(u));return new Response(s,{status:200,headers:{"content-type":"application/json","content-encoding":"gzip",vary:"accept-encoding"}})}return a.json(r)}),c.get("/api/circuit-breaker",a=>{let r={};for(let[u,f]of n.providers){let s=f._circuitBreaker;if(s){let i=s.getStatus();r[u]={state:i.state,failures:i.failures,lastFailure:i.lastFailure?new Date(i.lastFailure).toISOString():null}}}return a.json(r)}),{app:c,getConfig:()=>n,setConfig:async a=>{let r=new Map;for(let s of n.providers.values())s._agent&&r.set(ve(s),s._agent);let u=new Set;for(let s of a.providers.values()){let i=ve(s),p=r.get(i);p&&(s._agent=p,u.add(i))}let f=[];for(let[s,i]of r)u.has(s)||f.push(i.close().catch(()=>{}));n=a,se(),await Promise.all(f)},closeAgents:async()=>{let a=[];for(let r of n.providers.values())r._agent&&a.push(r._agent.close().catch(()=>{}));await Promise.all(a)}}}var U=class t{static MAX_MAP_SIZE=200;buffer;maxSize;head=0;count=0;subscribers;createdAt;_totalInputTokens=0;_totalOutputTokens=0;_totalTokensPerSec=0;_totalCacheReadTokens=0;_totalCacheCreationTokens=0;_modelMap=new Map;_providerMap=new Map;constructor(e=1e3){this.buffer=new Array(e).fill(null),this.maxSize=e,this.subscribers=new Set,this.createdAt=Date.now()}pruneMap(e,o){if(e.size<=t.MAX_MAP_SIZE)return;let n="",l=1/0;for(let[c,a]of e){let r=o(a);r<l&&(l=r,n=c)}n&&e.delete(n)}recordRequest(e){let o=this.head%this.maxSize,n=this.count>=this.maxSize?this.buffer[o]:null;if(n!==null){this._totalInputTokens-=n.inputTokens??0,this._totalOutputTokens-=n.outputTokens??0,this._totalTokensPerSec-=n.tokensPerSec??0,this._totalCacheReadTokens-=n.cacheReadTokens??0,this._totalCacheCreationTokens-=n.cacheCreationTokens??0;let r=n.model,u=this._modelMap.get(r);u&&(u.count--,u.count<=0&&this._modelMap.delete(r));let f=n.targetProvider??n.provider,s=this._providerMap.get(f)??0;s<=1?this._providerMap.delete(f):this._providerMap.set(f,s-1)}this._totalInputTokens+=e.inputTokens??0,this._totalOutputTokens+=e.outputTokens??0,this._totalTokensPerSec+=e.tokensPerSec??0,this._totalCacheReadTokens+=e.cacheReadTokens??0,this._totalCacheCreationTokens+=e.cacheCreationTokens??0;let l=e.model,c=this._modelMap.get(l);c?(c.count++,e.timestamp>c.lastSeen&&(c.lastSeen=e.timestamp),c.actualModel=e.actualModel):this._modelMap.set(l,{actualModel:e.actualModel,count:1,lastSeen:e.timestamp});let a=e.targetProvider??e.provider;this._providerMap.set(a,(this._providerMap.get(a)??0)+1),this.pruneMap(this._modelMap,r=>r.count),this.pruneMap(this._providerMap,r=>r),this.buffer[o]=e,this.head++,this.count<this.maxSize&&this.count++;for(let r of this.subscribers)try{r(e)}catch{}}getSummary(){let e=this.getRecentRequests(),o=[...this._modelMap.entries()].map(([a,{actualModel:r,count:u,lastSeen:f}])=>({model:a,actualModel:r,count:u,lastSeen:f})).sort((a,r)=>r.count-a.count),n=[...this._providerMap.entries()].map(([a,r])=>({provider:a,count:r})).sort((a,r)=>r.count-a.count),l=0,c=0;for(let a of e){let r=(a.inputTokens??0)+(a.cacheReadTokens??0)+(a.cacheCreationTokens??0);r>0&&(a.cacheReadTokens??0)>0&&(l+=a.cacheReadTokens/r*100,c++)}return{totalRequests:this.count,totalInputTokens:this._totalInputTokens,totalOutputTokens:this._totalOutputTokens,avgTokensPerSec:this.count>0?Math.round(this._totalTokensPerSec/this.count*10)/10:0,totalCacheReadTokens:this._totalCacheReadTokens,totalCacheCreationTokens:this._totalCacheCreationTokens,avgCacheHitRate:c>0?Math.round(l/c*10)/10:0,activeModels:o,providerDistribution:n,recentRequests:e,uptimeSeconds:Math.floor((Date.now()-this.createdAt)/1e3)}}onRecord(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}getRecentRequests(){if(this.count===0)return[];let e=Math.min(this.count,50),o=[];for(let n=0;n<e;n++){let l=((this.head-1-n)%this.maxSize+this.maxSize)%this.maxSize,c=this.buffer[l];c!==null&&o.push(c)}return o.reverse(),o}};import{spawn as Qe}from"child_process";import{existsSync as Ye,unlinkSync as et}from"fs";import{dirname as tt,join as nt}from"path";import{fileURLToPath as rt}from"url";async function ke(t){let e=ne();Ye(e)&&et(e),await re(process.pid);let o=process.argv[1]||nt(tt(rt(import.meta.url)),"index.js");process.on("uncaughtException",m=>{console.error(`[monitor] Uncaught exception: ${m.message}`)}),process.on("unhandledRejection",m=>{console.error(`[monitor] Unhandled rejection: ${m}`)});let n=10,l=1e3,c=3e4,a=6e4,r=0,u=null,f=null,s=!1,i=!1,p=null;async function d(){let m=await import("net");if(await new Promise(S=>{let h=m.createServer();h.once("error",()=>S(!0)),h.once("listening",()=>{h.close(()=>S(!1))}),h.listen(t.port??3456)})){console.error(`[monitor] Port ${t.port??3456} already in use, skipping worker spawn`);return}let y=[o,"--daemon"];t.config&&y.push("--config",t.config),t.port&&y.push("--port",String(t.port)),t.verbose&&y.push("--verbose"),p=Qe(process.execPath,y,{detached:!0,stdio:"ignore",env:{...process.env}}),u&&clearTimeout(u),u=setTimeout(()=>{r>0&&console.error(`[monitor] Worker stable for ${a}ms, resetting restart counter`),r=0,u=null},a),p.on("exit",async S=>{p=null,u&&(clearTimeout(u),u=null),await oe(),S===0&&!i&&(await D(),process.exit(0)),i=!1,s&&(console.error("[monitor] Worker exited during shutdown, monitor exiting"),await D(),process.exit(0));let h=r;h>=n&&(console.error(`[monitor] Max restart attempts exhausted (${n}), monitor exiting`),await D(),process.exit(1));let b=Math.min(l*2**h,c);r++,console.error(`[monitor] Worker died (code ${S}), restarting in ${b}ms (attempt ${r}/${n})`),f=setTimeout(d,b)})}process.on("SIGTERM",()=>{if(s=!0,f&&(clearTimeout(f),f=null),u&&(clearTimeout(u),u=null),p){try{p.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else D().then(()=>process.exit(0))}),process.on("SIGINT",()=>{if(s=!0,f&&(clearTimeout(f),f=null),u&&(clearTimeout(u),u=null),p){try{p.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else D().then(()=>process.exit(0))}),process.on("SIGHUP",()=>{if(console.log("[monitor] Received reload signal, restarting worker..."),i=!0,f&&(clearTimeout(f),f=null),p)try{p.kill("SIGTERM")}catch{}r=0}),d()}var st=JSON.parse(ot(new URL("../package.json",import.meta.url),"utf-8")).version;function it(t){let e={verbose:!1,help:!1,daemon:!1,monitor:!1,gui:!1};for(let o=2;o<t.length;o++)switch(t[o]){case"-p":case"--port":let n=t[++o];(!n||isNaN(parseInt(n,10)))&&(console.error("Error: -p/--port requires a number"),process.exit(1)),e.port=parseInt(n,10);break;case"-c":case"--config":let l=t[++o];l||(console.error("Error: -c/--config requires a path"),process.exit(1)),e.config=l;break;case"-v":case"--verbose":e.verbose=!0;break;case"-h":case"--help":e.help=!0;break;case"--daemon":e.daemon=!0;break;case"--monitor":e.monitor=!0;break}return e}function at(){console.log(`
|
|
10
10
|
ModelWeaver \u2014 Multi-provider model orchestration proxy for Claude Code
|
|
11
11
|
|
|
12
12
|
Usage: modelweaver [command] [options]
|
|
@@ -31,8 +31,8 @@ Options:
|
|
|
31
31
|
Config locations (first found wins):
|
|
32
32
|
./modelweaver.yaml
|
|
33
33
|
~/.modelweaver/config.yaml
|
|
34
|
-
`)}async function
|
|
35
|
-
ModelWeaver v${
|
|
36
|
-
`),console.log(" Routes:");for(let[i
|
|
37
|
-
Shutting down...`),process.exit(0)};process.on("SIGTERM",
|
|
34
|
+
`)}async function ct(){let t=it(process.argv);try{let s=await import("dotenv"),{existsSync:i}=await import("fs"),{join:p}=await import("path"),d=process.env.HOME||process.env.USERPROFILE||"",m=[p(process.cwd(),".env"),p(d,".modelweaver",".env"),p(d,".env")];for(let g of m)if(i(g)){s.config({path:g});break}}catch{}if(process.argv[2]==="init"){let s=process.argv.includes("--quick")||process.argv.includes("-q"),{runInit:i}=await import("./init-V5J734NZ.js");await i({quick:s}),process.exit(0)}if(process.argv[2]==="start"){let{startDaemon:s}=await import("./daemon-WP5LZ7DG.js"),i=await s(t.config,t.port,t.verbose);console.log(` ${i.message}`),console.log(` Log file: ${i.logPath}`),process.exit(i.success?0:1)}if(process.argv[2]==="stop"){let{stopDaemon:s}=await import("./daemon-WP5LZ7DG.js"),i=await s();console.log(` ${i.message}`),process.exit(i.success?0:1)}if(process.argv[2]==="status"){let{statusDaemon:s}=await import("./daemon-WP5LZ7DG.js"),i=await s();console.log(` ${i.message}`);try{let{getService:p}=await import("./service-6EQTZJEG.js"),m=(await p()).isInstalled();console.log(m?" Service: installed":' Service: not installed (run "modelweaver install" to enable auto-start)')}catch(p){console.log(` Service: ${p instanceof Error?p.message:String(p)}`)}process.exit(0)}if(process.argv[2]==="remove"){let{removeDaemon:s}=await import("./daemon-WP5LZ7DG.js"),i=await s();console.log(` ${i.message}`),process.exit(i.success?0:1)}if(process.argv[2]==="install"){try{let{getService:s}=await import("./service-6EQTZJEG.js");await(await s()).install()}catch(s){console.error(` Error: ${s instanceof Error?s.message:String(s)}`),process.exit(1)}process.exit(0)}if(process.argv[2]==="uninstall"){try{let{getService:s}=await import("./service-6EQTZJEG.js");(await s()).uninstall()}catch(s){console.error(` Error: ${s instanceof Error?s.message:String(s)}`),process.exit(1)}process.exit(0)}if(process.argv[2]==="gui"){let{launchGui:s}=await import("./gui-launcher-ZVOVTD6C.js");await s(),process.exit(0)}if(process.argv[2]==="reload"){let{reloadDaemon:s}=await import("./daemon-WP5LZ7DG.js");await s(t.port),process.exit(0)}t.help&&(at(),process.exit(0));let e,o;try{let s=await te(t.config);e=s.config,o=s.configPath}catch(s){console.error(`Config error: ${s.message}`),process.exit(1)}let n=t.port||e.server.port,l=e.server.host,c=t.verbose?"debug":"info",a=new U;if(t.monitor){await ke(t);return}if(t.daemon){let{removeWorkerPidFile:s,writeWorkerPidFile:i,createDebouncedReload:p,getLogPath:d}=await import("./daemon-WP5LZ7DG.js"),{reloadConfig:m}=await import("./config-CE7DQIUM.js"),{createWriteStream:g,watch:y}=await import("fs"),{createLogger:S}=await import("./logger-UA2A2DVX.js"),h=S(c);process.on("uncaughtException",T=>{h.error("Uncaught exception (daemon survived)",{error:T.message,stack:T.stack})}),process.on("unhandledRejection",T=>{h.error("Unhandled rejection (daemon survived)",{reason:String(T)})}),await i(process.pid);let b=g(d(),{flags:"a"});b.on("error",()=>{}),process.stdout.write=b.write.bind(b),process.stderr.write=b.write.bind(b);let v=Y(e,c,a),w=null;if(o){let T=p(async()=>{try{let x=await m(o);await v.setConfig(x),N.prune([...x.providers.keys()]),h.info("Config reloaded",{path:o})}catch(x){h.error("Config reload failed \u2014 keeping old config",{error:x.message})}},300);try{w=y(o,()=>{T.reload()}),w.on("error",()=>{w&&(w.close(),w=null)})}catch{}}process.on("SIGUSR1",async()=>{try{let T=await m(o);await v.setConfig(T),N.prune([...T.providers.keys()]),h.info("Config reloaded (SIGUSR1)",{path:o})}catch(T){h.error("Config reload failed (SIGUSR1)",{error:T.message})}});let _=Te({fetch:v.app.fetch,hostname:l,port:n,serverOptions:{requestTimeout:3e5,headersTimeout:1e4,keepAliveTimeout:3e4}});_.on("error",T=>{T.code==="EADDRINUSE"&&(h.error(`Port ${n} already in use, exiting for monitor restart`,{port:n}),process.exit(1))}),_.listen(n,l),F(_,a);let M=async()=>{w&&(w.close(),w=null),await v.closeAgents(),await s(),b.end(),process.exit(0)};process.on("SIGTERM",M),process.on("SIGINT",M);return}let r=Y(e,c,a);console.log(`
|
|
35
|
+
ModelWeaver v${st}`),console.log(` Listening: http://${l}:${n}`),console.log(` Config: ${o}
|
|
36
|
+
`),console.log(" Routes:");for(let[s,i]of e.routing){let p=i.map((d,m)=>`${d.provider}${m===0?" (primary)":" (fallback)"}`).join(", ");console.log(` ${s.padEnd(8)} \u2192 ${p}`)}if(console.log(),e.modelRouting.size>0){console.log(" Model Routes:");for(let[s,i]of e.modelRouting){let p=i.map((d,m)=>`${d.provider}${m===0?" (primary)":" (fallback)"}`).join(", ");console.log(` ${s.padEnd(20)} \u2192 ${p}`)}console.log()}let u=Te({fetch:r.app.fetch,hostname:l,port:n,serverOptions:{requestTimeout:3e5,headersTimeout:1e4,keepAliveTimeout:3e4}});u.on("error",s=>{s.code==="EADDRINUSE"&&(console.error(`Port ${n} already in use, exiting for monitor restart`),process.exit(1))}),u.listen(n,l),F(u,a);let f=async()=>{console.log(`
|
|
37
|
+
Shutting down...`),await r.closeAgents(),process.exit(0)};process.on("SIGTERM",f),process.on("SIGINT",f)}ct();
|
|
38
38
|
//# sourceMappingURL=index.js.map
|