@kianwoon/modelweaver 0.3.7 → 0.3.9

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.
@@ -10,7 +10,7 @@ ${"\u2500".repeat(56)}
10
10
  `)&&(e+=`
11
11
  `);let n=[];for(let s of i){let r=s.envKey.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),a=new RegExp(`^${r}=.*$`,"m"),u=s.apiKey.includes('"')?s.apiKey.includes("'")?`'${s.apiKey.replace(/'/g,"'\\''")}'`:`'${s.apiKey}'`:`"${s.apiKey}"`;a.test(e)?e=e.replace(a,`${s.envKey}=${u}`):n.push(`${s.envKey}=${u}`)}te(o,e+n.join(`
12
12
  `)+(n.length>0?`
13
- `:""),{mode:384})}async function Ae(){process.stdin.isTTY||(console.error("Error: modelweaver init --quick requires an interactive terminal."),process.exit(1));let{peekConfig:i}=await import("./config-KXAEHIC2.js"),t=i(),o=t?.providers??new Map,e=t?.modelRouting??new Map,n=[],s=[],r,a,u=x(1,!0);for(;;){F(),console.log(`
13
+ `:""),{mode:384})}async function Ae(){process.stdin.isTTY||(console.error("Error: modelweaver init --quick requires an interactive terminal."),process.exit(1));let{peekConfig:i}=await import("./config-AUGJR65F.js"),t=i(),o=t?.providers??new Map,e=t?.modelRouting??new Map,n=[],s=[],r,a,u=x(1,!0);for(;;){F(),console.log(`
14
14
  ${M}${m}\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2500
15
15
  \u2502 Welcome to ModelWeaver! \u2501 Quick Setup \u2502
16
16
  \u2502 \u2502
@@ -24,7 +24,7 @@ ${M} Generated configuration:${d}
24
24
  `).map($=>` ${$}`).join(`
25
25
  `));let{confirm:v}=await p({type:"confirm",name:"confirm",message:`[Step 2 of ${u}] Write this configuration?`,initial:!0},g);if(v)break;console.log(`
26
26
  Restarting quick setup...
27
- `)}await ae(n,s,r,a,u,!0,!!t)}async function ae(i,t,o,e,n,s,r){let a=N(process.env.HOME||process.env.USERPROFILE||"",".modelweaver");ne(a,{recursive:!0});let u=N(a,"config.yaml");oe(u)&&r?_(`Updating existing config at ${u}`):_(`Writing new config to ${u}`),te(u,e),Ee(i);try{let{readPidFile:c,isProcessAlive:v}=await import("./daemon-C7DJNEY7.js"),$=await c();if($&&v($)){if(process.platform!=="win32")try{process.kill($,"SIGUSR1")}catch{}else console.log(" Windows does not support SIGUSR1 \u2014 run 'modelweaver reload' to pick up new config.");_("ModelWeaver daemon reloaded with new config")}}catch{}let l=await xe(t);if(l){let c=o.host==="localhost"?`http://localhost:${o.port}`:`http://${o.host}:${o.port}`;V()&&console.log(" Backed up existing settings to settings.json.bak");let $=z(),h=J($,{baseUrl:c,defaultModel:l.defaultModel,availableModels:l.availableModels});Q(h),_(`Claude Code settings updated at ${Y()}`),console.log(` Proxy endpoint: ${c}`),console.log(` Default model: ${l.defaultModel}`),console.log(` Available models: ${l.availableModels.join(", ")}`),console.log(),console.log(` ${ie}Restart Claude Code to apply changes.${d}`)}return l}var B=0,L=1,D=2,ee=3;async function Ge(i){if(i?.quick)return Ae();process.stdin.isTTY||(console.error("Error: modelweaver init requires an interactive terminal."),process.exit(1));let{peekConfig:t}=await import("./config-KXAEHIC2.js"),o=t(),e=o?.providers??new Map,n=o?.modelRouting??new Map,s=e.size>0,r=[],a=[],u=[],l={port:3456,host:"localhost"},c,v=null,$=s,h=B;for(;;){if(h<=B){if(F(),console.log(`
27
+ `)}await ae(n,s,r,a,u,!0,!!t)}async function ae(i,t,o,e,n,s,r){let a=N(process.env.HOME||process.env.USERPROFILE||"",".modelweaver");ne(a,{recursive:!0});let u=N(a,"config.yaml");oe(u)&&r?_(`Updating existing config at ${u}`):_(`Writing new config to ${u}`),te(u,e),Ee(i);try{let{readPidFile:c,isProcessAlive:v}=await import("./daemon-7ML7R6OM.js"),$=await c();if($&&v($)){if(process.platform!=="win32")try{process.kill($,"SIGUSR1")}catch{}else console.log(" Windows does not support SIGUSR1 \u2014 run 'modelweaver reload' to pick up new config.");_("ModelWeaver daemon reloaded with new config")}}catch{}let l=await xe(t);if(l){let c=o.host==="localhost"?`http://localhost:${o.port}`:`http://${o.host}:${o.port}`;V()&&console.log(" Backed up existing settings to settings.json.bak");let $=z(),h=J($,{baseUrl:c,defaultModel:l.defaultModel,availableModels:l.availableModels});Q(h),_(`Claude Code settings updated at ${Y()}`),console.log(` Proxy endpoint: ${c}`),console.log(` Default model: ${l.defaultModel}`),console.log(` Available models: ${l.availableModels.join(", ")}`),console.log(),console.log(` ${ie}Restart Claude Code to apply changes.${d}`)}return l}var B=0,L=1,D=2,ee=3;async function Ge(i){if(i?.quick)return Ae();process.stdin.isTTY||(console.error("Error: modelweaver init requires an interactive terminal."),process.exit(1));let{peekConfig:t}=await import("./config-AUGJR65F.js"),o=t(),e=o?.providers??new Map,n=o?.modelRouting??new Map,s=e.size>0,r=[],a=[],u=[],l={port:3456,host:"localhost"},c,v=null,$=s,h=B;for(;;){if(h<=B){if(F(),console.log(`
28
28
  ${M}${m}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\u2500
29
29
  \u2551 Welcome to ModelWeaver! \u2551
30
30
  \u2551 \u2551
@@ -60,4 +60,4 @@ ${v?`\u2551 Claude Code settings have been updated. \u2551
60
60
  \u2551 claude \u2551`}
61
61
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${d}
62
62
  `)}export{Ge as runInit,X as testApiKey};
63
- //# sourceMappingURL=init-AYMFXI3W.js.map
63
+ //# sourceMappingURL=init-CGVMZ7ZK.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kianwoon/modelweaver",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Multi-provider model orchestration proxy for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import{readFileSync as O,existsSync as S,statSync as E}from"fs";import{join as C}from"path";import{parse as x}from"yaml";import{z as e}from"zod";import{Agent as $}from"undici";var j={failureThreshold:3,windowSeconds:60,cooldownSeconds:30},w=class{state="closed";failureTimestamps=[];openedAt=null;config;constructor(n={}){this.config={...j,...n}}canProceed(){return this.state==="closed"?!0:this.state==="open"?this.openedAt&&Date.now()-this.openedAt>=this.config.cooldownSeconds*1e3?(this.state="half-open",!0):!1:!0}recordResult(n){if(n>=200&&n<300){this.state="closed",this.failureTimestamps=[],this.openedAt=null;return}if(n!==429&&n<500)return;let r=Date.now();if(this.failureTimestamps.push(r),this.pruneOldFailures(r),this.state==="half-open"){this.state="open",this.openedAt=r;return}this.failureTimestamps.length>=this.config.failureThreshold&&(this.state="open",this.openedAt=r)}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(n){let r=n-this.config.windowSeconds*1e3;this.failureTimestamps=this.failureTimestamps.filter(s=>s>=r)}};var B=e.object({maxOutputTokens:e.number().int().positive()}).optional(),U=e.object({baseUrl:e.string().url().refine(t=>/^https?:\/\//.test(t),"baseUrl must use http:// or https://"),apiKey:e.string().min(1,"apiKey is required"),timeout:e.number().default(3e4),ttfbTimeout:e.number().default(15e3),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()}),A=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(A)).default({}),tierPatterns:e.record(e.string(),e.array(e.string())).default({}),modelRouting:e.record(e.string(),e.array(A)).default({})});function P(t){return t.replace(/\$\{([^}]+)\}/g,(n,r)=>{let s=process.env[r];if(s===void 0)throw new Error(`Missing environment variable: ${r}`);return s})}function T(t){if(typeof t=="string")return P(t);if(Array.isArray(t))return t.map(T);if(t!==null&&typeof t=="object"){let n={};for(let[r,s]of Object.entries(t))n[r]=T(s);return n}return t}function k(t=process.cwd(),{skipGlobal:n=!1}={}){let r=C(t,"modelweaver.yaml");if(S(r))return r;if(!n){let s=C(process.env.HOME||process.env.USERPROFILE||"",".modelweaver","config.yaml");if(S(s))return s}return null}function I(t){let n=k(t);if(!n)return null;let r=O(n,"utf-8"),s=x(r),b=s?.providers??{},f=new Map;for(let[u,c]of Object.entries(b)){let y=String(c.apiKey??"").match(/^\$\{([^}]+)\}$/),i=y?y[1]:"";f.set(u,{baseUrl:String(c.baseUrl??""),envKey:i,authType:String(c.authType??"anthropic"),timeout:Number(c.timeout??3e4)})}let a=s?.server,p=a?{port:Number(a.port??3456),host:String(a.host??"localhost")}:null,d=new Map,m=s?.modelRouting??{};for(let[u,c]of Object.entries(m))Array.isArray(c)&&d.set(u,c.map(g=>({provider:String(g.provider??""),model:String(g.model??u)})));return{configPath:n,providers:f,server:p,modelRouting:d}}function L(t,n){let r=null;if(t)if(S(t))try{E(t).isDirectory()?r=k(t):r=t}catch{r=t}else r=t;if(r||(r=k(n)),!r)throw new Error("No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml");let s=O(r,"utf-8"),b=x(s,{customTags:[]}),f=T(b),a=M.parse(f),p=new Set(Object.keys(a.providers));for(let[i,o]of Object.entries(a.routing)){for(let l of o)if(!p.has(l.provider))throw new Error(`Routing tier "${i}" references unknown provider "${l.provider}". Available: ${[...p].join(", ")}`);if(!a.tierPatterns[i])throw new Error(`Routing tier "${i}" has no entry in tierPatterns. Add patterns for this tier.`)}for(let[i,o]of Object.entries(a.modelRouting))for(let l of o)if(!p.has(l.provider))throw new Error(`modelRouting for model "${i}" references unknown provider "${l.provider}". Available: ${[...p].join(", ")}`);let d=new Map;for(let[i,o]of Object.entries(a.providers)){let l={name:i,baseUrl:o.baseUrl,apiKey:o.apiKey,timeout:o.timeout,ttfbTimeout:o.ttfbTimeout,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 R=o.poolSize;l._agent=new $({keepAliveTimeout:3e4,keepAliveMaxTimeout:6e4,connections:R??10,allowH2:!0}),l.poolSize=R??10;let h=o.circuitBreaker;l._circuitBreaker=new w(h?{failureThreshold:h.failureThreshold,windowSeconds:h.windowSeconds,cooldownSeconds:h.cooldownSeconds}:void 0),d.set(i,l)}let m=new Map;for(let[i,o]of Object.entries(a.routing))m.set(i,o);let u=new Map;for(let[i,o]of Object.entries(a.tierPatterns))u.set(i,o);let c=new Map;if(a.modelRouting)for(let[i,o]of Object.entries(a.modelRouting))c.set(i,o);return{config:{server:{port:a.server.port,host:a.server.host},providers:d,routing:m,tierPatterns:u,modelRouting:c},configPath:r}}function V(t){let{config:n}=L(t);return n}export{P as a,k as b,I as c,L as d,V as e};
3
- //# sourceMappingURL=chunk-T2AB2JE4.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts","../src/circuit-breaker.ts"],"sourcesContent":["// src/config.ts\nimport { readFileSync, existsSync, statSync } from \"node:fs\";\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().default(30000),\n ttfbTimeout: z.number().default(15000),\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 function loadConfig(configPath?: string, cwd?: string): { 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 if (existsSync(configPath)) {\n try {\n const stat = statSync(configPath);\n if (stat.isDirectory()) {\n path = findConfigFile(configPath);\n } else {\n path = configPath;\n }\n } catch {\n path = configPath;\n }\n } else {\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 = readFileSync(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 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 as Record<string, unknown>).poolSize as number | undefined;\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 as Record<string, unknown>).circuitBreaker as Record<string, number> | undefined;\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 function reloadConfig(configPath: string): AppConfig {\n const { config } = 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 readonly config: BreakerConfig;\n\n constructor(config: Partial<BreakerConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n canProceed(): boolean {\n if (this.state === \"closed\") return true;\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 return true; // Allow one probe request\n }\n return false;\n }\n // half-open: allow one probe\n return true;\n }\n\n recordResult(status: number): void {\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,gBAAAA,EAAc,cAAAC,EAAY,YAAAC,MAAgB,KACnD,OAAS,QAAAC,MAAY,OACrB,OAAS,SAASC,MAAiB,OACnC,OAAS,KAAAC,MAAS,MAClB,OAAS,SAAAC,MAAa,SCUtB,IAAMC,EAAgC,CACpC,iBAAkB,EAClB,cAAe,GACf,gBAAiB,EACnB,EAEaC,EAAN,KAAqB,CAClB,MAAsB,SACtB,kBAA8B,CAAC,EAC/B,SAA0B,KACjB,OAEjB,YAAYC,EAAiC,CAAC,EAAG,CAC/C,KAAK,OAAS,CAAE,GAAGF,EAAgB,GAAGE,CAAO,CAC/C,CAEA,YAAsB,CACpB,OAAI,KAAK,QAAU,SAAiB,GAChC,KAAK,QAAU,OAEb,KAAK,UAAY,KAAK,IAAI,EAAI,KAAK,UAAY,KAAK,OAAO,gBAAkB,KAC/E,KAAK,MAAQ,YACN,IAEF,GAGF,EACT,CAEA,aAAaC,EAAsB,CACjC,GAAIA,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,EDlFA,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,QAAQ,GAAK,EACjC,YAAaA,EAAE,OAAO,EAAE,QAAQ,IAAK,EACrC,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,CAIO,SAASK,EAAWlB,EAAqBP,EAAyD,CACvG,IAAI0B,EAAsB,KAC1B,GAAInB,EAEF,GAAIH,EAAWG,CAAU,EACvB,GAAI,CACWoB,EAASpB,CAAU,EACvB,YAAY,EACnBmB,EAAO3B,EAAeQ,CAAU,EAEhCmB,EAAOnB,CAEX,MAAQ,CACNmB,EAAOnB,CACT,MAEAmB,EAAOnB,EAMX,GAHKmB,IACHA,EAAO3B,EAAeC,CAAG,GAEvB,CAAC0B,EACH,MAAM,IAAI,MACR,kGACF,EAGF,IAAMlB,EAAMC,EAAaiB,EAAM,OAAO,EAChChB,EAASC,EAAUH,EAAK,CAAE,WAAY,CAAC,CAAE,CAAC,EAG1CoB,EAAWlC,EAAqBgB,CAAM,EAEtCmB,EAAYzC,EAAgB,MAAMwC,CAAQ,EAG1CE,EAAgB,IAAI,IAAI,OAAO,KAAKD,EAAU,SAAS,CAAC,EAE9D,OAAW,CAACE,EAAMR,CAAO,IAAK,OAAO,QAAQM,EAAU,OAAO,EAAG,CAC/D,QAAWG,KAAST,EAClB,GAAI,CAACO,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,EAAWV,CAAO,IAAK,OAAO,QAAQM,EAAU,YAAY,EACtE,QAAWG,KAAST,EAClB,GAAI,CAACO,EAAc,IAAIE,EAAM,QAAQ,EACnC,MAAM,IAAI,MACR,2BAA2BC,CAAS,kCAAkCD,EAAM,QAAQ,iBAAiB,CAAC,GAAGF,CAAa,EAAE,KAAK,IAAI,CAAC,EACpI,EAMN,IAAMjB,EAAY,IAAI,IACtB,OAAW,CAACqB,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,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,EAAYH,EAA8B,SAChDC,EAAe,OAAS,IAAIG,EAAM,CAChC,iBAAkB,IAClB,oBAAqB,IACrB,YAAaD,GAAY,GACzB,QAAS,EACX,CAAC,EACDF,EAAe,SAAWE,GAAY,GAEtC,IAAME,EAAYL,EAA8B,eAChDC,EAAe,gBAAkB,IAAIK,EAAeD,EAAW,CAC7D,iBAAkBA,EAAS,iBAC3B,cAAeA,EAAS,cACxB,gBAAiBA,EAAS,eAC5B,EAAI,MAAS,EACb3B,EAAU,IAAIqB,EAAME,CAAc,CACpC,CAEA,IAAMM,EAAU,IAAI,IACpB,OAAW,CAACX,EAAMR,CAAO,IAAK,OAAO,QAAQM,EAAU,OAAO,EAC5Da,EAAQ,IAAIX,EAAMR,CAAO,EAG3B,IAAMoB,EAAe,IAAI,IACzB,OAAW,CAACZ,EAAMa,CAAQ,IAAK,OAAO,QAAQf,EAAU,YAAY,EAClEc,EAAa,IAAIZ,EAAMa,CAAQ,EAGjC,IAAMxB,EAAe,IAAI,IACzB,GAAIS,EAAU,aACZ,OAAW,CAACgB,EAAOtB,CAAO,IAAK,OAAO,QAAQM,EAAU,YAAY,EAClET,EAAa,IAAIyB,EAAOtB,CAAO,EAUnC,MAAO,CAAE,OADiB,CAAE,OALC,CAC3B,KAAMM,EAAU,OAAO,KACvB,KAAMA,EAAU,OAAO,IACzB,EAEoC,UAAAhB,EAAW,QAAA6B,EAAS,aAAAC,EAAc,aAAAvB,CAAa,EAClE,WAAYM,CAAK,CACpC,CAIO,SAASoB,EAAavC,EAA+B,CAC1D,GAAM,CAAE,OAAAQ,CAAO,EAAIU,EAAWlB,CAAU,EACxC,OAAOQ,CACT","names":["readFileSync","existsSync","statSync","join","parseYaml","z","Agent","DEFAULT_CONFIG","CircuitBreaker","config","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","statSync","resolved","validated","providerNames","tier","entry","modelName","name","p","providerConfig","parsedUrl","poolSize","Agent","cbConfig","CircuitBreaker","routing","tierPatterns","patterns","model","reloadConfig"]}
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import{a,b,c,d,e}from"./chunk-T2AB2JE4.js";export{b as findConfigFile,d as loadConfig,c as peekConfig,e as reloadConfig,a as resolveEnvVars};
3
- //# sourceMappingURL=config-KXAEHIC2.js.map