@kianwoon/modelweaver 0.3.0 → 0.3.2
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/README.md +18 -18
- package/dist/chunk-SSPYP7NE.js +3 -0
- package/dist/chunk-SSPYP7NE.js.map +1 -0
- package/dist/{chunk-P2HBWASF.js → chunk-XLHN6X4A.js} +2 -2
- package/dist/config-UO2KSCGU.js +3 -0
- package/dist/{daemon-MGMHTCQC.js → daemon-I5WKQHTA.js} +2 -2
- package/dist/index.js +9 -9
- package/dist/index.js.map +1 -1
- package/dist/{init-YTOVLWSL.js → init-JE7HXV7I.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-ZGVJ4FUB.js +0 -3
- package/dist/chunk-ZGVJ4FUB.js.map +0 -1
- package/dist/config-FYJATRN4.js +0 -3
- /package/dist/{chunk-P2HBWASF.js.map → chunk-XLHN6X4A.js.map} +0 -0
- /package/dist/{config-FYJATRN4.js.map → config-UO2KSCGU.js.map} +0 -0
- /package/dist/{daemon-MGMHTCQC.js.map → daemon-I5WKQHTA.js.map} +0 -0
- /package/dist/{init-YTOVLWSL.js.map → init-JE7HXV7I.js.map} +0 -0
package/README.md
CHANGED
|
@@ -55,17 +55,17 @@ Claude Code ──→ ModelWeaver ──→ Anthropic (primary)
|
|
|
55
55
|
ModelWeaver requires no permanent install — `npx` downloads and runs it on the fly. But if you prefer a global install:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
npm install -g modelweaver
|
|
58
|
+
npm install -g @kianwoon/modelweaver
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
After that, replace `npx modelweaver` with `modelweaver` in all commands below.
|
|
61
|
+
After that, replace `npx @kianwoon/modelweaver` with `modelweaver` in all commands below.
|
|
62
62
|
|
|
63
63
|
## Quick Start
|
|
64
64
|
|
|
65
65
|
### 1. Run the setup wizard
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
npx modelweaver init
|
|
68
|
+
npx @kianwoon/modelweaver init
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The wizard guides you through:
|
|
@@ -78,10 +78,10 @@ The wizard guides you through:
|
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
80
|
# Foreground (see logs in terminal)
|
|
81
|
-
npx modelweaver
|
|
81
|
+
npx @kianwoon/modelweaver
|
|
82
82
|
|
|
83
83
|
# Background daemon (auto-restarts on crash)
|
|
84
|
-
npx modelweaver start
|
|
84
|
+
npx @kianwoon/modelweaver start
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### 3. Point Claude Code to ModelWeaver
|
|
@@ -95,13 +95,13 @@ claude
|
|
|
95
95
|
## CLI Commands
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
npx modelweaver init # Interactive setup wizard
|
|
99
|
-
npx modelweaver start # Start as background daemon
|
|
100
|
-
npx modelweaver stop # Stop background daemon
|
|
101
|
-
npx modelweaver status # Show daemon status
|
|
102
|
-
npx modelweaver remove # Stop daemon + remove PID and log files
|
|
103
|
-
npx modelweaver gui # Launch desktop GUI (auto-downloads binary)
|
|
104
|
-
npx modelweaver [options] # Run in foreground
|
|
98
|
+
npx @kianwoon/modelweaver init # Interactive setup wizard
|
|
99
|
+
npx @kianwoon/modelweaver start # Start as background daemon
|
|
100
|
+
npx @kianwoon/modelweaver stop # Stop background daemon
|
|
101
|
+
npx @kianwoon/modelweaver status # Show daemon status
|
|
102
|
+
npx @kianwoon/modelweaver remove # Stop daemon + remove PID and log files
|
|
103
|
+
npx @kianwoon/modelweaver gui # Launch desktop GUI (auto-downloads binary)
|
|
104
|
+
npx @kianwoon/modelweaver [options] # Run in foreground
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
### CLI Options
|
|
@@ -118,10 +118,10 @@ npx modelweaver [options] # Run in foreground
|
|
|
118
118
|
Run ModelWeaver as a background process that survives terminal closure and auto-recovers from crashes.
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
|
-
npx modelweaver start # Start (forks monitor + daemon)
|
|
122
|
-
npx modelweaver status # Check if running
|
|
123
|
-
npx modelweaver stop # Graceful stop (SIGTERM → SIGKILL after 5s)
|
|
124
|
-
npx modelweaver remove # Stop + remove PID file + log file
|
|
121
|
+
npx @kianwoon/modelweaver start # Start (forks monitor + daemon)
|
|
122
|
+
npx @kianwoon/modelweaver status # Check if running
|
|
123
|
+
npx @kianwoon/modelweaver stop # Graceful stop (SIGTERM → SIGKILL after 5s)
|
|
124
|
+
npx @kianwoon/modelweaver remove # Stop + remove PID file + log file
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
**How it works**: `start` forks a lightweight monitor process that owns the PID file. The monitor spawns the actual daemon worker. If the worker crashes, the monitor auto-restarts it after a 2-second delay (up to 5 restarts per 60-second window to prevent crash loops).
|
|
@@ -141,7 +141,7 @@ modelweaver.pid → Monitor process (handles signals, watches child)
|
|
|
141
141
|
ModelWeaver ships a native desktop GUI built with Tauri. No Rust toolchain needed — the binary is auto-downloaded from GitHub Releases.
|
|
142
142
|
|
|
143
143
|
```bash
|
|
144
|
-
npx modelweaver gui
|
|
144
|
+
npx @kianwoon/modelweaver gui
|
|
145
145
|
```
|
|
146
146
|
|
|
147
147
|
First run downloads the latest binary for your platform (~10-30 MB). Subsequent launches use the cached version.
|
|
@@ -250,7 +250,7 @@ In daemon mode, ModelWeaver watches the config file for changes and reloads auto
|
|
|
250
250
|
kill -SIGUSR1 $(cat ~/.modelweaver/modelweaver.pid)
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
Or just re-run `npx modelweaver init` — it automatically signals the running daemon to reload.
|
|
253
|
+
Or just re-run `npx @kianwoon/modelweaver init` — it automatically signals the running daemon to reload.
|
|
254
254
|
|
|
255
255
|
## API
|
|
256
256
|
|
|
@@ -0,0 +1,3 @@
|
|
|
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,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[c,u]of Object.entries(b)){let y=String(u.apiKey??"").match(/^\$\{([^}]+)\}$/),i=y?y[1]:"";f.set(c,{baseUrl:String(u.baseUrl??""),envKey:i,authType:String(u.authType??"anthropic"),timeout:Number(u.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[c,u]of Object.entries(m))Array.isArray(u)&&d.set(c,u.map(g=>({provider:String(g.provider??""),model:String(g.model??c)})));return{configPath:n,providers:f,server:p,modelRouting:d}}function K(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};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 c=new Map;for(let[i,o]of Object.entries(a.tierPatterns))c.set(i,o);let u=new Map;if(a.modelRouting)for(let[i,o]of Object.entries(a.modelRouting))u.set(i,o);return{config:{server:{port:a.server.port,host:a.server.host},providers:d,routing:m,tierPatterns:c,modelRouting:u},configPath:r}}function V(t){let{config:n}=K(t);return n}export{P as a,k as b,I as c,K as d,V as e};
|
|
3
|
+
//# sourceMappingURL=chunk-SSPYP7NE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 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 };\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":"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","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"]}
|
|
@@ -3,5 +3,5 @@ import{spawn as N,execFile as W,execFileSync as $}from"child_process";import{acc
|
|
|
3
3
|
`,{flag:"wx"})}catch(e){if(e.code==="EEXIST")return;throw e}}async function w(){let n=y();try{let e=await E(n,"utf-8"),t=parseInt(e.trim(),10);return isNaN(t)?null:t}catch{return null}}async function u(){let n=y();try{await I(n)}catch{}}function b(){return f(m,"modelweaver.worker.pid")}async function Z(n){await R(),await M(b(),`${n}
|
|
4
4
|
`)}async function v(){let n=b();try{let e=await E(n,"utf-8"),t=parseInt(e.trim(),10);return isNaN(t)?null:t}catch{return null}}async function P(){let n=b();try{await I(n)}catch{}}function a(n){try{return process.kill(n,0),!0}catch{return!1}}function x(n){return new Promise(e=>{if(d()){try{W("netstat",["-ano"],{encoding:"utf-8",timeout:3e3},(t,r)=>{if(t){e([]);return}let i=[];for(let o of(r||"").split(`
|
|
5
5
|
`))if(o.includes("LISTENING")&&o.includes(`:${n}`)){let s=o.trim().split(/\s+/),l=parseInt(s[s.length-1],10);!isNaN(l)&&l>0&&i.push(l)}e(i)})}catch{e([]);return}return}W("lsof",["-ti",`:${n}`,"-sTCP:LISTEN"],{encoding:"utf-8",timeout:3e3},(t,r)=>{if(t){e([]);return}let i=(r||"").trim();e(i?i.split(`
|
|
6
|
-
`).map(Number).filter(o=>!isNaN(o)):[])})})}async function h(n){if(D!==null)return D;try{let{loadConfig:e}=await import("./config-
|
|
7
|
-
//# sourceMappingURL=chunk-
|
|
6
|
+
`).map(Number).filter(o=>!isNaN(o)):[])})})}async function h(n){if(D!==null)return D;try{let{loadConfig:e}=await import("./config-UO2KSCGU.js"),{config:t}=e(n??void 0);return t.server.port}catch{return 3456}}async function T(n,e=5e3){for(let r of n)try{process.kill(r,"SIGTERM")}catch{}let t=Date.now()+e;for(;Date.now()<t;){if(n.every(r=>!a(r)))return!0;await new Promise(r=>setTimeout(r,200))}for(let r of n)if(d())try{$("taskkill",["/F","/PID",String(r),"/T"],{stdio:"ignore"})}catch{}else try{process.kill(r,"SIGKILL")}catch{}return!0}async function C(n){let e=await w();if(e===null){let t=n??await h();if(t!==null&&t>0){let r=await x(t);if(r.length>0){let i=r.filter(o=>a(o));if(i.length>0)return{running:!0,pid:i[0],message:`ModelWeaver is running (PID ${i[0]}, detected on port ${t}; PID file missing)`}}}return{running:!1,message:"ModelWeaver is not running (no PID file found)"}}return a(e)?{running:!0,pid:e,message:`ModelWeaver is running (PID ${e})`}:(await u(),{running:!1,message:"ModelWeaver is not running (stale PID file cleaned up)"})}function A(n){return new Promise(e=>{let t=O();t.once("error",r=>{r.code==="EADDRINUSE"?e(!0):e(!1)}),t.once("listening",()=>{t.close(()=>e(!1))}),t.listen(n)})}async function ee(n,e,t){let r=await C(e);if(r.running)return{success:!1,pid:r.pid,message:`ModelWeaver is already running (PID ${r.pid})`,logPath:c()};let i=e??await h()??3456;if(await A(i))return{success:!1,message:`Port ${i} is already in use. Is ModelWeaver or another process running on it?`,logPath:c()};let o=_(import.meta.url),s=U(o),p=[f(s,"index.js"),"--monitor"];n&&p.push("--config",n),e&&p.push("--port",String(e)),t&&p.push("--verbose"),N(process.execPath,p,{detached:!0,stdio:"ignore",env:{...process.env}}).unref();let g;for(let S=0;S<20;S++){let k=await w();if(k!==null){g=k;break}await new Promise(F=>setTimeout(F,100))}return g?{success:!0,pid:g,message:`ModelWeaver started in background (PID ${g})`,logPath:c()}:{success:!1,message:"Daemon started but PID file was not created. Check logs at "+c(),logPath:c()}}async function K(n){let e=await w();if(e===null){let r=n??await h();if(r!==null&&r>0){let o=(await x(r)).filter(s=>a(s));if(o.length>0){let s=await v(),l=[...o];return s!==null&&a(s)&&!l.includes(s)&&l.push(s),await T(l),await P(),{success:!0,message:`ModelWeaver stopped (found on port ${r}, PIDs ${o.join(", ")}; PID file was missing)`}}}return{success:!1,message:"ModelWeaver is not running (no PID file found)"}}if(!a(e)){let r=await v();return r!==null&&a(r)&&(await T([r]),await P()),await u(),{success:!1,message:"ModelWeaver is not running (stale PID file cleaned up)"}}try{process.kill(e,"SIGTERM")}catch{return{success:!1,message:`Failed to stop daemon (PID ${e})`}}let t=Date.now()+5e3;for(;Date.now()<t;){if(!a(e))return await u(),{success:!0,message:`ModelWeaver stopped (PID ${e})`};await new Promise(r=>setTimeout(r,100))}try{d()?$("taskkill",["/F","/PID",String(e),"/T"],{stdio:"ignore"}):process.kill(e,"SIGKILL")}catch{}return await u(),await P(),{success:!0,message:`ModelWeaver force-stopped (PID ${e})`}}async function j(){let n=c();try{await I(n)}catch{}}async function ne(){let n=await K();return await j(),await P(),{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 te(n){let e=await w();if(e===null){let t=n??await h();if(t!==null&&t>0){let i=(await x(t)).filter(o=>a(o));if(i.length>0){for(let o of i)try{d()?console.log(` Windows detected \u2014 reload signal not supported for PID ${o}. Use 'modelweaver stop && modelweaver start' instead.`):process.kill(o,"SIGHUP")}catch{}console.log(` Sent reload signal to ${i.length} process(es) on port ${t}.`);return}}console.log(" Daemon is not running.");return}if(!a(e)){await u(),console.log(" Daemon is not running (stale PID file cleaned up).");return}try{if(d()){let t=await v();t!==null?(process.kill(t,"SIGTERM"),console.log(` Killed worker (PID ${t}) on Windows \u2014 monitor will restart it.`)):console.log(" No worker PID file found \u2014 cannot reload.")}else process.kill(e,"SIGHUP"),console.log(` Sent reload signal to daemon (PID ${e}).`)}catch{console.log(" Failed to send reload signal \u2014 daemon may not be running.")}}function re(n,e=300){let t=null;return{reload(){t&&clearTimeout(t),t=setTimeout(()=>{t=null,n()},e)},dispose(){t&&(clearTimeout(t),t=null)}}}export{Q as a,y as b,c,R as d,Y as e,w as f,u as g,b as h,Z as i,v as j,P as k,a as l,x as m,h as n,C as o,A as p,ee as q,K as r,j as s,ne as t,te as u,re as v};
|
|
7
|
+
//# sourceMappingURL=chunk-XLHN6X4A.js.map
|
|
@@ -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-XLHN6X4A.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-I5WKQHTA.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as j}from"./chunk-OMWFRIHF.js";import{d as B}from"./chunk-ZGVJ4FUB.js";import{b as D,e as U,g as C,k as W}from"./chunk-P2HBWASF.js";import{serve as oe}from"@hono/node-server";import{readFileSync as qe}from"fs";import{Hono as be}from"hono";var x=new Map;function J(){x.clear()}function re(t,e){for(let[c,i]of e)for(let d of i)if(t.includes(d))return c;return null}function ne(t,e){return e.get(t)||[]}function H(t,e,c,i){let d=x.get(t);if(d)return x.delete(t),x.set(t,d),{requestId:e,model:t,tier:d.tier,providerChain:d.providerChain,startTime:Date.now(),rawBody:i};let p,o,r=c.modelRouting.get(t);if(r&&r.length>0)p="(modelRouting)",o=r;else{let n=re(t,c.tierPatterns);if(!n)return null;p=n,o=ne(p,c.routing)}if(x.size>=200){let n=x.keys().next().value;n!==void 0&&x.delete(n)}return x.set(t,{tier:p,providerChain:o}),{requestId:e,model:t,tier:p,providerChain:o,startTime:Date.now(),rawBody:i}}import{request as se}from"undici";import G from"fs";import z from"path";import ie from"os";var ae=new Set(["anthropic-version","anthropic-beta","content-type","accept"]),ce=/\/+/g,ue=/^https?:\/\/[^/]+/,F=/"model"\s*:\s*"([^"]*)"/,K=/"max_tokens"\s*:\s*(\d+)/,O=new TextEncoder;function X(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 pe(t,e){if(t!==400)return!1;let c=e.toLowerCase();return le.some(i=>c.includes(i))}function Q(t,e){if(!pe(t,e))return null;console.warn("[context-compact] Upstream context window limit detected");try{let i=z.join(ie.homedir(),".claude","state");G.mkdirSync(i,{recursive:!0}),G.writeFileSync(z.join(i,"context-compact-needed"),Date.now().toString())}catch{}let c=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(c,{status:400,headers:{"content-type":"application/json"}})}function de(t,e){let c="",i=t,d=t.indexOf("/",t.indexOf("//")+2);d!==-1&&(i=t.substring(0,d),c=t.substring(d));let p="",o=e,r=e.indexOf("?");r!==-1&&(o=e.substring(0,r),p=e.substring(r));let n;return c.endsWith("/v1")&&o.startsWith("/v1")?n=c+o.substring(3):n=c+o,n=n.replace(ce,"/"),i+n+p}function me(t,e,c){let i=new Headers;for(let p of ae){let o=t.get(p);o&&i.set(p,o)}e.authType==="bearer"?i.set("Authorization",`Bearer ${e.apiKey}`):i.set("x-api-key",e.apiKey),i.set("x-request-id",c);let d=e._cachedHost;if(d)i.set("host",d);else try{let p=new URL(e.baseUrl);i.set("host",p.host)}catch{}return i}function Z(t){let e=t.messages;if(!Array.isArray(e))return;let c=new Set,i=new Set,d=new Map;for(let o=0;o<e.length;o++){let r=e[o];if(Array.isArray(r.content)){if(r.role==="assistant"){let n=!1;for(let l of r.content)l.type==="tool_use"&&l.id&&(c.add(String(l.id)),i.has(String(l.id))||(n=!0))}else if(r.role==="user"){let n=!1;for(let l of r.content)l.type==="tool_result"&&l.tool_use_id&&(i.add(String(l.tool_use_id)),c.has(String(l.tool_use_id))||(n=!0));n&&d.set(o,"user")}}}for(let o=0;o<e.length;o++){let r=e[o];r.role==="assistant"&&Array.isArray(r.content)&&r.content.some(l=>l.type==="tool_use"&&!i.has(String(l.id)))&&d.set(o,"assistant")}if(d.size===0)return;t.messages=e.map((o,r)=>{let n=d.get(r);if(n&&Array.isArray(o.content)){let l=o.content.filter(a=>n==="user"?!(a.type==="tool_result"&&!c.has(String(a.tool_use_id))):!(a.type==="tool_use"&&!i.has(String(a.id))));return l.length===o.content.length?o:{...o,content:l}}return o});let p=new Set;for(let o of t.messages)if(Array.isArray(o.content)&&o.role==="assistant")for(let r of o.content)r.type==="tool_use"&&r.id&&p.add(String(r.id));t.messages=t.messages.map(o=>{if(o.role==="user"&&Array.isArray(o.content)){let r=o.content.filter(n=>!(n.type==="tool_result"&&!p.has(String(n.tool_use_id))));return r.length===o.content.length?o:{...o,content:r}}return o})}function fe(t,e,c,i,d){if(d){let o=structuredClone(i);if(e.model&&(o.model=e.model),Z(o),c.modelLimits){let{maxOutputTokens:r}=c.modelLimits,n=typeof o.max_tokens=="number"?o.max_tokens:r;(o.max_tokens===void 0||n>r)&&(o.max_tokens=Math.min(n,r))}return JSON.stringify(o)}let p=t;if(e.model&&i.model!==e.model){let o=F.exec(p);o&&(p=p.replace(F,`"model":"${e.model}"`),console.warn(`Routing override: ${o[1]} -> ${e.model} via ${c.name}`))}if(c.modelLimits){let{maxOutputTokens:o}=c.modelLimits,r=K.exec(p);if(r)parseInt(r[1],10)>o&&(p=p.replace(K,`"max_tokens":${o}`));else if(typeof i.max_tokens!="number"){let n={...i};return e.model&&(n.model=e.model),n.max_tokens=o,JSON.stringify(n)}}return p}async function V(t,e,c,i,d,p=0){let o=i.url.replace(ue,"");e.model&&(c.actualModel=e.model);let r=de(t.baseUrl,o),n;if((i.headers.get("content-type")||"").includes("application/json"))try{let u=c.parsedBody??JSON.parse(c.rawBody),f=!1;e.model&&u.model!==e.model&&(f=!0);let g=p>0;if(g&&(f=!0),t.modelLimits){let{maxOutputTokens:v}=t.modelLimits,S=typeof u.max_tokens=="number"?u.max_tokens:v;(u.max_tokens===void 0||S>v)&&(f=!0)}if(f)if(p===0&&!g)n=fe(c.rawBody,e,t,u,!1);else{let v=structuredClone(u);if(e.model){let S=v.model;v.model=e.model,S&&S!==e.model&&console.warn(`Routing override: ${S} -> ${e.model} via ${t.name}`)}if(g&&Z(v),t.modelLimits){let{maxOutputTokens:S}=t.modelLimits,h=typeof v.max_tokens=="number"?v.max_tokens:S;(v.max_tokens===void 0||h>S)&&(v.max_tokens=Math.min(h,S))}n=JSON.stringify(v)}else n=c.rawBody}catch{n=c.rawBody}else n=c.rawBody;let a=me(i.headers,t,c.requestId);a.set("content-length",Buffer.byteLength(n,"utf-8").toString());let s=new AbortController,m=setTimeout(()=>s.abort(),t.timeout);if(d){if(d.aborted){clearTimeout(m);let f=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(f,{status:502,headers:{"content-type":"application/json","content-length":O.encode(f).byteLength.toString()}})}let u=()=>{clearTimeout(m)};d.addEventListener("abort",u,{once:!0})}try{let u=await se(r,{method:"POST",headers:a,body:n,signal:s.signal,dispatcher:t._agent}),f=new Response(u.body,{status:u.statusCode,headers:u.headers});return clearTimeout(m),f}catch(u){clearTimeout(m);let f=u instanceof DOMException&&u.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${u.message}`,g=JSON.stringify({type:"error",error:{type:"overloaded_error",message:f}});return new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}})}}async function ge(t,e,c,i,d,p,o=0){let r=new AbortController,n=t.map(async(s,m)=>{let u=e.get(s.provider);if(!u){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${s.provider}`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json"}}),index:m}}if(u._circuitBreaker&&!u._circuitBreaker.canProceed()){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${s.provider}" skipped by circuit breaker`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json"}}),index:m}}d?.(s.provider,m);try{let f=await V(u,s,c,i,r.signal,m+o);return u._circuitBreaker&&u._circuitBreaker.recordResult(f.status),{response:f,index:m}}catch{u._circuitBreaker&&u._circuitBreaker.recordResult(502);let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${s.provider}" failed`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json"}}),index:m}}}),l=new Set,a=[];try{for(;l.size<n.length;){let m=n.filter(f=>!l.has(f));if(m.length===0)break;let u=await Promise.race(m);if(l.add(n[u.index]??n[0]),u.response.status>=200&&u.response.status<300){r.abort();for(let f of a)try{f.response.body?.cancel()}catch{}return u.response}if(!X(u.response.status)){if(r.abort(),u.response.status===400&&u.response.body)try{let f=await u.response.text(),g=Q(u.response.status,f);return g||new Response(f,{status:u.response.status,statusText:u.response.statusText,headers:u.response.headers})}catch{return u.response}return u.response}a.push(u)}if(r.abort(),a.length>0)return a[0].response;let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers in race failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json"}})}catch{r.abort();let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers in race failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json"}})}}async function Y(t,e,c,i,d,p){let o=null;for(let n=0;n<e.length;n++){let l=e[n],a=t.get(l.provider);if(!a){let m=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${l.provider}`}});o=new Response(m,{status:502,headers:{"content-type":"application/json","content-length":O.encode(m).byteLength.toString()}});continue}if(a._circuitBreaker&&!a._circuitBreaker.canProceed()){p?.warn("Provider skipped by circuit breaker",{requestId:c.requestId,provider:l.provider});continue}d?.(l.provider,n);let s=await V(a,l,c,i,void 0,n);if(o=s,a._circuitBreaker&&a._circuitBreaker.recordResult(s.status),s.status>=200&&s.status<300)return s;if(!X(s.status)){if(s.status===400&&s.body)try{let m=await s.text(),u=Q(s.status,m);return u||new Response(m,{status:s.status,statusText:s.statusText,headers:s.headers})}catch{return s}return s}if(n<e.length-1){if(await s.body?.cancel(),s.status===429&&n+1<e.length){c.fallbackMode="race";let m=e.slice(n+1);return ge(m,t,c,i,d,p,n+1)}continue}return s}if(o)return o;let r=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers exhausted"}});return new Response(r,{status:502,headers:{"content-type":"application/json","content-length":O.encode(r).byteLength.toString()}})}import{randomUUID as ke}from"crypto";import{gzip as Re}from"zlib";import{promisify as _e}from"util";import{WebSocketServer as he}from"ws";var ye=3e4,ve=2,we=64*1024,Se=500,q=null;function $(t,e){let c=new he({server:t,path:"/ws"});q=c,c.on("connection",i=>{let p={type:"summary",data:e.getSummary()};i.send(JSON.stringify(p));let o,r=0,n=()=>i.readyState===i.OPEN,l=e.onRecord(f=>{if(n()){if(i.bufferedAmount>we){a();return}setImmediate(()=>{if(!n())return;let g={type:"request",data:f};i.send(JSON.stringify(g))}),a()}});function a(){o||(o=setTimeout(()=>{if(o=void 0,!n())return;let f={type:"summary",data:e.getSummary()};i.send(JSON.stringify(f))},Se))}let s=setInterval(()=>{if(!n()){clearInterval(s);return}if(r>=ve){u(),i.terminate();return}i.ping(),r++},ye);i.on("pong",()=>{r=0});let m=!1,u=()=>{m||(m=!0,clearInterval(s),o&&clearTimeout(o),l())};i.on("close",u),i.on("error",u)})}function E(t){if(!q)return;let e=JSON.stringify({type:"stream",data:t});for(let c of q.clients)c.readyState===c.OPEN&&setImmediate(()=>{c.readyState===c.OPEN&&c.send(e)})}var Te=_e(Re);function N(t,e,c){return new Response(JSON.stringify({type:"error",error:{type:t,message:e}}),{status:502,headers:{"content-type":"application/json","x-request-id":c}})}function Me(t){let e=t.message?.usage??t.usage;if(!e)return{inputTokens:0,outputTokens:0};let c=e.input_tokens??e.prompt_tokens??0,i=e.output_tokens??e.completion_tokens??0,d=e.cache_read_input_tokens??0,p=e.cache_creation_input_tokens??0;return{inputTokens:c+d+p,outputTokens:i}}function xe(t,e,c,i,d,p){let o=new TextDecoder,r={input:0,output:0},n="",l="",a=4096,s=0,m=0,u=0,f=0,g="",v=null,S=250,h=0,k=b=>{for(let y of b.split(`
|
|
2
|
+
import{a as L}from"./chunk-OMWFRIHF.js";import{d as j}from"./chunk-SSPYP7NE.js";import{b as D,e as U,g as P,k as W}from"./chunk-XLHN6X4A.js";import{serve as ee}from"@hono/node-server";import{readFileSync as qe}from"fs";import{Hono as Se}from"hono";var I=new Map;function J(){I.clear()}function te(t,e){for(let[i,s]of e)for(let m of s)if(t.includes(m))return i;return null}function re(t,e){return e.get(t)||[]}function H(t,e,i,s){let m=I.get(t);if(m)return I.delete(t),I.set(t,m),{requestId:e,model:t,tier:m.tier,providerChain:m.providerChain,startTime:Date.now(),rawBody:s};let l,r,o=i.modelRouting.get(t);if(o&&o.length>0)l="(modelRouting)",r=o;else{let a=te(t,i.tierPatterns);if(!a)return null;l=a,r=re(l,i.routing)}if(I.size>=200){let a=I.keys().next().value;a!==void 0&&I.delete(a)}return I.set(t,{tier:l,providerChain:r}),{requestId:e,model:t,tier:l,providerChain:r,startTime:Date.now(),rawBody:s}}import{request as oe}from"undici";import z from"fs";import G from"path";import ne from"os";var se=new Set(["anthropic-version","anthropic-beta","content-type","accept"]),ie=/\/+/g,ae=/^https?:\/\/[^/]+/,F=/"model"\s*:\s*"([^"]*)"/,K=/"max_tokens"\s*:\s*(\d+)/,E=new TextEncoder,ce=3e3;function ue(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 pe(t,e){if(t!==400)return!1;let i=e.toLowerCase();return le.some(s=>i.includes(s))}function de(t,e){if(!pe(t,e))return null;console.warn("[context-compact] Upstream context window limit detected");try{let s=G.join(ne.homedir(),".claude","state");z.mkdirSync(s,{recursive:!0}),z.writeFileSync(G.join(s,"context-compact-needed"),Date.now().toString())}catch{}let i=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(i,{status:400,headers:{"content-type":"application/json"}})}function me(t,e){let i="",s=t,m=t.indexOf("/",t.indexOf("//")+2);m!==-1&&(s=t.substring(0,m),i=t.substring(m));let l="",r=e,o=e.indexOf("?");o!==-1&&(r=e.substring(0,o),l=e.substring(o));let a;return i.endsWith("/v1")&&r.startsWith("/v1")?a=i+r.substring(3):a=i+r,a=a.replace(ie,"/"),s+a+l}function fe(t,e,i){let s=new Headers;for(let l of se){let r=t.get(l);r&&s.set(l,r)}e.authType==="bearer"?s.set("Authorization",`Bearer ${e.apiKey}`):s.set("x-api-key",e.apiKey),s.set("x-request-id",i);let m=e._cachedHost;if(m)s.set("host",m);else try{let l=new URL(e.baseUrl);s.set("host",l.host)}catch{}return s}function Q(t){let e=t.messages;if(!Array.isArray(e))return;let i=new Set,s=new Set,m=new Map;for(let r=0;r<e.length;r++){let o=e[r];if(Array.isArray(o.content)){if(o.role==="assistant"){let a=!1;for(let p of o.content)p.type==="tool_use"&&p.id&&(i.add(String(p.id)),s.has(String(p.id))||(a=!0))}else if(o.role==="user"){let a=!1;for(let p of o.content)p.type==="tool_result"&&p.tool_use_id&&(s.add(String(p.tool_use_id)),i.has(String(p.tool_use_id))||(a=!0));a&&m.set(r,"user")}}}for(let r=0;r<e.length;r++){let o=e[r];o.role==="assistant"&&Array.isArray(o.content)&&o.content.some(p=>p.type==="tool_use"&&!s.has(String(p.id)))&&m.set(r,"assistant")}if(m.size===0)return;t.messages=e.map((r,o)=>{let a=m.get(o);if(a&&Array.isArray(r.content)){let p=r.content.filter(c=>a==="user"?!(c.type==="tool_result"&&!i.has(String(c.tool_use_id))):!(c.type==="tool_use"&&!s.has(String(c.id))));return p.length===r.content.length?r:{...r,content:p}}return r});let l=new Set;for(let r of t.messages)if(Array.isArray(r.content)&&r.role==="assistant")for(let o of r.content)o.type==="tool_use"&&o.id&&l.add(String(o.id));t.messages=t.messages.map(r=>{if(r.role==="user"&&Array.isArray(r.content)){let o=r.content.filter(a=>!(a.type==="tool_result"&&!l.has(String(a.tool_use_id))));return o.length===r.content.length?r:{...r,content:o}}return r})}function ge(t,e,i,s,m){if(m){let r=structuredClone(s);if(e.model&&(r.model=e.model),Q(r),i.modelLimits){let{maxOutputTokens:o}=i.modelLimits,a=typeof r.max_tokens=="number"?r.max_tokens:o;(r.max_tokens===void 0||a>o)&&(r.max_tokens=Math.min(a,o))}return JSON.stringify(r)}let l=t;if(e.model&&s.model!==e.model){let r=F.exec(l);r&&(l=l.replace(F,`"model":"${e.model}"`),console.warn(`Routing override: ${r[1]} -> ${e.model} via ${i.name}`))}if(i.modelLimits){let{maxOutputTokens:r}=i.modelLimits,o=K.exec(l);if(o)parseInt(o[1],10)>r&&(l=l.replace(K,`"max_tokens":${r}`));else if(typeof s.max_tokens!="number"){let a={...s};return e.model&&(a.model=e.model),a.max_tokens=r,JSON.stringify(a)}}return l}async function X(t,e,i,s,m,l=0){let r=s.url.replace(ae,"");e.model&&(i.actualModel=e.model);let o=me(t.baseUrl,r),a;if((s.headers.get("content-type")||"").includes("application/json"))try{let y=i.parsedBody??JSON.parse(i.rawBody),g=!1;e.model&&y.model!==e.model&&(g=!0);let w=l>0;if(w&&(g=!0),t.modelLimits){let{maxOutputTokens:R}=t.modelLimits,b=typeof y.max_tokens=="number"?y.max_tokens:R;(y.max_tokens===void 0||b>R)&&(g=!0)}if(g)if(l===0&&!w)a=ge(i.rawBody,e,t,y,!1);else{let R=structuredClone(y);if(e.model){let b=R.model;R.model=e.model,b&&b!==e.model&&console.warn(`Routing override: ${b} -> ${e.model} via ${t.name}`)}if(w&&Q(R),t.modelLimits){let{maxOutputTokens:b}=t.modelLimits,C=typeof R.max_tokens=="number"?R.max_tokens:b;(R.max_tokens===void 0||C>b)&&(R.max_tokens=Math.min(C,b))}a=JSON.stringify(R)}else a=i.rawBody}catch{a=i.rawBody}else a=i.rawBody;let c=fe(s.headers,t,i.requestId);c.set("content-length",Buffer.byteLength(a,"utf-8").toString());let n=new AbortController,d=setTimeout(()=>n.abort(),t.timeout),u=t.ttfbTimeout??1e4,f=!1,h=null,M=new Promise((y,g)=>{h=setTimeout(()=>{f=!0,n.abort(),g(new Error(`TTFB timeout after ${u}ms`))},u)});if(m){if(m.aborted){clearTimeout(d),h&&clearTimeout(h);let g=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(g,{status:502,headers:{"content-type":"application/json","content-length":E.encode(g).byteLength.toString()}})}let y=()=>{clearTimeout(d),h&&clearTimeout(h)};m.addEventListener("abort",y,{once:!0})}try{let y=await Promise.race([oe(o,{method:"POST",headers:c,body:a,signal:n.signal,dispatcher:t._agent}),M]);h&&clearTimeout(h);let g=new Response(y.body,{status:y.statusCode,headers:y.headers});return clearTimeout(d),g}catch(y){clearTimeout(d),h&&clearTimeout(h);let g=f?`Provider "${t.name}" timed out waiting for first byte after ${u}ms`:y instanceof DOMException&&y.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${y.message}`,w=JSON.stringify({type:"error",error:{type:"overloaded_error",message:g}});return new Response(w,{status:502,headers:{"content-type":"application/json","content-length":E.encode(w).byteLength.toString()}})}}async function V(t,e,i,s,m,l){if(e.length<=1){let n=e[0],d=t.get(n.provider);if(!d){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${n.provider}`}});return new Response(f,{status:502,headers:{"content-type":"application/json","content-length":E.encode(f).byteLength.toString()}})}if(d._circuitBreaker&&!d._circuitBreaker.canProceed()){l?.warn("Provider skipped by circuit breaker",{requestId:i.requestId,provider:n.provider});let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${n.provider}" skipped by circuit breaker`}});return new Response(f,{status:502,headers:{"content-type":"application/json","content-length":E.encode(f).byteLength.toString()}})}m?.(n.provider,0);let u=await X(d,n,i,s,void 0,0);return d._circuitBreaker&&d._circuitBreaker.recordResult(u.status),u}let r=new AbortController,o=new Set,a=[];async function p(n){let d=e[n],u=t.get(d.provider);if(!u){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${d.provider}`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json","content-length":E.encode(f).byteLength.toString()}}),index:n}}if(u._circuitBreaker&&!u._circuitBreaker.canProceed()){l?.warn("Provider skipped by circuit breaker",{requestId:i.requestId,provider:d.provider});let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${d.provider}" skipped by circuit breaker`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json","content-length":E.encode(f).byteLength.toString()}}),index:n}}m?.(d.provider,n);try{let f=await X(u,d,i,s,r.signal,n);return u._circuitBreaker&&u._circuitBreaker.recordResult(f.status),{response:f,index:n}}catch{u._circuitBreaker&&u._circuitBreaker.recordResult(502);let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${d.provider}" failed`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json","content-length":E.encode(f).byteLength.toString()}}),index:n}}}let c=[];for(let n=0;n<e.length;n++)n===0?c.push(p(0)):c.push(new Promise(d=>{setTimeout(()=>{if(r.signal.aborted){let u=JSON.stringify({type:"error",error:{type:"api_error",message:"Cancelled by race winner"}});d({response:new Response(u,{status:502,headers:{"content-type":"application/json"}}),index:n});return}p(n).then(d)},ce)}));try{for(;o.size<c.length;){let d=c.filter((f,h)=>!o.has(h));if(d.length===0)break;let u=await Promise.race(d);if(o.add(u.index),u.response.status>=200&&u.response.status<300){r.abort();for(let f of a)try{f.response.body?.cancel()}catch{}return u.response}if(!ue(u.response.status)){if(r.abort(),u.response.status===400&&u.response.body)try{let f=await u.response.text(),h=de(u.response.status,f);return h||new Response(f,{status:u.response.status,statusText:u.response.statusText,headers:u.response.headers})}catch{return u.response}return u.response}a.push(u)}if(r.abort(),a.length>0)return a[0].response;let n=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(n,{status:502,headers:{"content-type":"application/json","content-length":E.encode(n).byteLength.toString()}})}catch{r.abort();let n=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers failed"}});return new Response(n,{status:502,headers:{"content-type":"application/json","content-length":E.encode(n).byteLength.toString()}})}}import{randomUUID as ke}from"crypto";import{gzip as Re}from"zlib";import{promisify as _e}from"util";import{WebSocketServer as he}from"ws";var ye=3e4,ve=2,we=64*1024,be=500,q=null;function $(t,e){let i=new he({server:t,path:"/ws"});q=i,i.on("connection",s=>{let l={type:"summary",data:e.getSummary()};s.send(JSON.stringify(l));let r,o=0,a=()=>s.readyState===s.OPEN,p=e.onRecord(f=>{if(a()){if(s.bufferedAmount>we){c();return}setImmediate(()=>{if(!a())return;let h={type:"request",data:f};s.send(JSON.stringify(h))}),c()}});function c(){r||(r=setTimeout(()=>{if(r=void 0,!a())return;let f={type:"summary",data:e.getSummary()};s.send(JSON.stringify(f))},be))}let n=setInterval(()=>{if(!a()){clearInterval(n);return}if(o>=ve){u(),s.terminate();return}s.ping(),o++},ye);s.on("pong",()=>{o=0});let d=!1,u=()=>{d||(d=!0,clearInterval(n),r&&clearTimeout(r),p())};s.on("close",u),s.on("error",u)})}function A(t){if(!q)return;let e=JSON.stringify({type:"stream",data:t});for(let i of q.clients)i.readyState===i.OPEN&&setImmediate(()=>{i.readyState===i.OPEN&&i.send(e)})}var Te=_e(Re);function N(t,e,i){return new Response(JSON.stringify({type:"error",error:{type:t,message:e}}),{status:502,headers:{"content-type":"application/json","x-request-id":i}})}function Me(t){let e=t.message?.usage??t.usage;if(!e)return{inputTokens:0,outputTokens:0};let i=e.input_tokens??e.prompt_tokens??0,s=e.output_tokens??e.completion_tokens??0,m=e.cache_read_input_tokens??0,l=e.cache_creation_input_tokens??0;return{inputTokens:i+m+l,outputTokens:s}}function xe(t,e,i,s,m,l){let r=new TextDecoder,o={input:0,output:0},a="",p="",c=4096,n=0,d=0,u=0,f=0,h="",M=null,y=250,g=0,w=k=>{for(let v of k.split(`
|
|
3
3
|
|
|
4
|
-
`)){if(!
|
|
5
|
-
`).find(
|
|
6
|
-
`);
|
|
7
|
-
`:"")+
|
|
4
|
+
`)){if(!v)continue;let S=v.split(`
|
|
5
|
+
`).find(_=>_.startsWith("data:"));if(!(!S||!S.includes('"usage"')))try{let _=JSON.parse(S.slice(5)),T=Me(_);T.inputTokens>o.input&&(o.input=T.inputTokens),T.outputTokens>o.output&&(o.output=T.outputTokens)}catch{}}},R=k=>{let v=[...k.matchAll(/"(?:input_tokens|prompt_tokens)"\s*:\s*(\d+)/g)],S=[...k.matchAll(/"cache_read_input_tokens"\s*:\s*(\d+)/g)],_=[...k.matchAll(/"cache_creation_input_tokens"\s*:\s*(\d+)/g)],T=[...k.matchAll(/"(?:output_tokens|completion_tokens)"\s*:\s*(\d+)/g)];if(v.length>0){let x=parseInt(v[v.length-1][1],10);x>n&&(n=x)}if(S.length>0){let x=parseInt(S[S.length-1][1],10);x>d&&(d=x)}if(_.length>0){let x=parseInt(_[_.length-1][1],10);x>u&&(u=x)}if(T.length>0){let x=parseInt(T[T.length-1][1],10);x>f&&(f=x)}},b=(k,v)=>{try{let S=Date.now()-t.startTime,_=S/1e3,T=_>0?v/_:0;s.recordRequest({requestId:t.requestId,model:t.model,actualModel:t.actualModel||t.model,tier:t.tier,provider:e,targetProvider:i,status:m,inputTokens:k,outputTokens:v,latencyMs:S,tokensPerSec:Math.round(T*10)/10,timestamp:Date.now(),fallbackMode:t.fallbackMode}),setImmediate(()=>{A({requestId:t.requestId,model:t.model,tier:t.tier,state:"complete",status:m,latencyMs:Date.now()-t.startTime,inputTokens:k,outputTokens:v,tokensPerSec:Math.round(T*10)/10,timestamp:Date.now()})})}catch{}},C=(k,v)=>{if(M===null&&(M=l.includes("text/event-stream")||k.startsWith("event:")),M){a+=k;let S=a.split(`
|
|
6
|
+
`);a=S.pop();for(let T of S)T===""?p&&(w(p),p=""):p+=(p?`
|
|
7
|
+
`:"")+T;v&&p.trim()&&w(p);let _=Date.now();_-g>=y&&o.output>0&&(g=_,setImmediate(()=>{A({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:o.output,timestamp:_})})),v&&b(o.input,o.output)}else{h+=k,h.length>c&&(h=h.slice(-c)),R(h);let S=Date.now();if(S-g>=y&&f>0&&(g=S,setImmediate(()=>{A({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:f,timestamp:S})})),v){let _=n+d+u;b(_,f)}}};return new TransformStream({transform(k,v){v.enqueue(k),C(r.decode(k,{stream:!0}),!1)},flush(){C("",!0)},cancel(){C("",!0)}})}function Y(t){let e=t._cachedOrigin,i=t.poolSize??10;return`${e??"unknown"}:${i}`}function B(t,e,i){let s=t,m=L(e),l=new Se;return l.onError((r,o)=>(console.error(`[server] Unhandled error: ${r.message}`),o.json({type:"error",error:{type:"api_error",message:"Internal proxy error"}},{status:500,headers:{"content-type":"application/json"}}))),l.use("/api/*",async(r,o)=>{r.header("Access-Control-Allow-Origin","*"),await o()}),l.options("/api/*",r=>(r.header("Access-Control-Allow-Origin","*"),r.header("Access-Control-Allow-Methods","GET, POST, OPTIONS"),r.header("Access-Control-Allow-Headers","Content-Type, Authorization, anthropic-version, x-api-key"),r.body("",200))),l.post("/v1/messages",async r=>{let o=ke(),a,p;try{p=await r.req.text(),a=JSON.parse(p)}catch{return N("invalid_request_error","Invalid JSON body",o)}let c=a.model;if(!c)return N("invalid_request_error","Missing 'model' field in request body",o);let n=H(c,o,s,p);if(n&&(n.parsedBody=a),!n){m.info("No tier match",{requestId:o,model:c});let g=s.modelRouting.size>0?` Configured model routes: ${[...s.modelRouting.keys()].join(", ")}.`:"";return N("invalid_request_error",`No route matches model "${c}". Configured tiers: ${[...s.tierPatterns.keys()].join(", ")}.${g}`,o)}m.info("Routing request",{requestId:o,model:c,tier:n.tier,providers:n.providerChain.map(g=>g.provider)}),A({requestId:o,model:c,tier:n.tier,state:"start",provider:n.providerChain[0]?.provider??"unknown",timestamp:Date.now()});let d="unknown",u;try{u=await V(s.providers,n.providerChain,n,r.req.raw,(g,w)=>{m.info("Attempting provider",{requestId:o,provider:g,index:w,tier:n.tier}),d||(d=g)},m)}catch(g){let w=g instanceof Error?g.message:String(g);return m.error("Forward failed",{requestId:o,error:w}),setImmediate(()=>{A({requestId:o,model:c,tier:n.tier,state:"error",status:502,message:w,timestamp:Date.now()})}),r.json({type:"error",error:{type:"api_error",message:"Upstream request failed: "+w}},502)}u.status>=400&&setImmediate(()=>{A({requestId:o,model:c,tier:n.tier,state:"error",status:u.status,message:`HTTP ${u.status}`,timestamp:Date.now()})});let f=u.body;if(u.body&&u.status>=200&&u.status<300&&i){let g=n.providerChain.length>0?n.providerChain[0].provider:d,w=xe(n,d,g,i,u.status,u.headers.get("content-type")||"");f=u.body.pipeThrough(w)}let h=new Headers(u.headers);h.set("x-request-id",o);let M=new Response(f,{status:u.status,statusText:u.statusText,headers:h}),y=Date.now()-n.startTime;return m.info("Request completed",{requestId:o,model:c,tier:n.tier,status:M.status,latencyMs:y}),M}),l.get("/api/metrics/summary",async r=>{if(!i)return r.json({error:"Metrics not enabled"},503);let o=i.getSummary(),a=JSON.stringify(o);if((r.req.header("accept-encoding")||"").includes("gzip")&&a.length>=1024){let c=await Te(Buffer.from(a));return new Response(c,{status:200,headers:{"content-type":"application/json","content-encoding":"gzip",vary:"accept-encoding"}})}return r.json(o)}),l.get("/api/circuit-breaker",r=>{let o={};for(let[a,p]of s.providers){let c=p._circuitBreaker;if(c){let n=c.getStatus();o[a]={state:n.state,failures:n.failures,lastFailure:n.lastFailure?new Date(n.lastFailure).toISOString():null}}}return r.json(o)}),{app:l,getConfig:()=>s,setConfig:r=>{let o=new Map;for(let p of s.providers.values())p._agent&&o.set(Y(p),p._agent);let a=new Set;for(let p of r.providers.values()){let c=Y(p),n=o.get(c);n&&(p._agent=n,a.add(c))}for(let[p,c]of o)a.has(p)||c.close();s=r,J()}}}var O=class{buffer;maxSize;head=0;count=0;subscribers;createdAt;_totalInputTokens=0;_totalOutputTokens=0;_totalTokensPerSec=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 i=this.head%this.maxSize,s=this.count>=this.maxSize?this.buffer[i]:null;if(s!==null){this._totalInputTokens-=s.inputTokens??0,this._totalOutputTokens-=s.outputTokens??0,this._totalTokensPerSec-=s.tokensPerSec??0;let o=s.model,a=this._modelMap.get(o);a&&(a.count--,a.count<=0&&this._modelMap.delete(o));let p=s.targetProvider??s.provider,c=this._providerMap.get(p)??0;c<=1?this._providerMap.delete(p):this._providerMap.set(p,c-1)}this._totalInputTokens+=e.inputTokens??0,this._totalOutputTokens+=e.outputTokens??0,this._totalTokensPerSec+=e.tokensPerSec??0;let m=e.model,l=this._modelMap.get(m);l?(l.count++,e.timestamp>l.lastSeen&&(l.lastSeen=e.timestamp),l.actualModel=e.actualModel):this._modelMap.set(m,{actualModel:e.actualModel,count:1,lastSeen:e.timestamp});let r=e.targetProvider??e.provider;this._providerMap.set(r,(this._providerMap.get(r)??0)+1),this.buffer[i]=e,this.head++,this.count<this.maxSize&&this.count++;for(let o of this.subscribers)try{o(e)}catch{}}getSummary(){let e=this.getRecentRequests(),i=[...this._modelMap.entries()].map(([m,{actualModel:l,count:r,lastSeen:o}])=>({model:m,actualModel:l,count:r,lastSeen:o})).sort((m,l)=>l.count-m.count),s=[...this._providerMap.entries()].map(([m,l])=>({provider:m,count:l})).sort((m,l)=>l.count-m.count);return{totalRequests:this.count,totalInputTokens:this._totalInputTokens,totalOutputTokens:this._totalOutputTokens,avgTokensPerSec:this.count>0?Math.round(this._totalTokensPerSec/this.count*10)/10:0,activeModels:i,providerDistribution:s,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),i=[];for(let s=0;s<e;s++){let m=((this.head-1-s)%this.maxSize+this.maxSize)%this.maxSize,l=this.buffer[m];l!==null&&i.push(l)}return i.reverse(),i}};import{spawn as Ee}from"child_process";import{existsSync as Ce,unlinkSync as Ie}from"fs";import{dirname as Ae,join as Pe}from"path";import{fileURLToPath as Oe}from"url";async function Z(t){let e=D();Ce(e)&&Ie(e),await U(process.pid);let i=process.argv[1]||Pe(Ae(Oe(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 s=10,m=1e3,l=3e4,r=6e4,o=0,a=null,p=null,c=!1,n=!1,d=null;function u(){let f=[i,"--daemon"];t.config&&f.push("--config",t.config),t.port&&f.push("--port",String(t.port)),t.verbose&&f.push("--verbose"),d=Ee(process.execPath,f,{detached:!0,stdio:"ignore",env:{...process.env}}),a&&clearTimeout(a),a=setTimeout(()=>{o>0&&console.error(`[monitor] Worker stable for ${r}ms, resetting restart counter`),o=0,a=null},r),d.on("exit",async h=>{d=null,a&&(clearTimeout(a),a=null),await W(),h===0&&!n&&(await P(),process.exit(0)),n=!1,c&&(console.error("[monitor] Worker exited during shutdown, monitor exiting"),await P(),process.exit(0));let M=o;M>=s&&(console.error(`[monitor] Max restart attempts exhausted (${s}), monitor exiting`),await P(),process.exit(1));let y=Math.min(m*2**M,l);o++,console.error(`[monitor] Worker died (code ${h}), restarting in ${y}ms (attempt ${o}/${s})`),p=setTimeout(u,y)})}process.on("SIGTERM",()=>{if(c=!0,p&&(clearTimeout(p),p=null),a&&(clearTimeout(a),a=null),d){try{d.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else P().then(()=>process.exit(0))}),process.on("SIGINT",()=>{if(c=!0,p&&(clearTimeout(p),p=null),a&&(clearTimeout(a),a=null),d){try{d.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else P().then(()=>process.exit(0))}),process.on("SIGHUP",()=>{if(console.log("[monitor] Received reload signal, restarting worker..."),n=!0,p&&(clearTimeout(p),p=null),d)try{d.kill("SIGTERM")}catch{}o=0}),u()}var $e=JSON.parse(qe(new URL("../package.json",import.meta.url),"utf-8")).version;function Ne(t){let e={verbose:!1,help:!1,daemon:!1,monitor:!1,gui:!1};for(let i=2;i<t.length;i++)switch(t[i]){case"-p":case"--port":let s=t[++i];(!s||isNaN(parseInt(s,10)))&&(console.error("Error: -p/--port requires a number"),process.exit(1)),e.port=parseInt(s,10);break;case"-c":case"--config":let m=t[++i];m||(console.error("Error: -c/--config requires a path"),process.exit(1)),e.config=m;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 Be(){console.log(`
|
|
8
8
|
ModelWeaver \u2014 Multi-provider model orchestration proxy for Claude Code
|
|
9
9
|
|
|
10
10
|
Usage: modelweaver [command] [options]
|
|
@@ -29,8 +29,8 @@ Options:
|
|
|
29
29
|
Config locations (first found wins):
|
|
30
30
|
./modelweaver.yaml
|
|
31
31
|
~/.modelweaver/config.yaml
|
|
32
|
-
`)}async function
|
|
33
|
-
ModelWeaver v${$e}`),console.log(` Listening: http://${
|
|
34
|
-
`),console.log(" Routes:");for(let[
|
|
35
|
-
Shutting down...`),process.exit(0)};process.on("SIGTERM",
|
|
32
|
+
`)}async function Le(){let t=Ne(process.argv);try{let c=await import("dotenv"),{existsSync:n}=await import("fs"),{join:d}=await import("path"),u=process.env.HOME||process.env.USERPROFILE||"",f=[d(process.cwd(),".env"),d(u,".modelweaver",".env"),d(u,".env")];for(let h of f)if(n(h)){c.config({path:h});break}}catch{}if(process.argv[2]==="init"){let c=process.argv.includes("--quick")||process.argv.includes("-q"),{runInit:n}=await import("./init-JE7HXV7I.js");await n({quick:c}),process.exit(0)}if(process.argv[2]==="start"){let{startDaemon:c}=await import("./daemon-I5WKQHTA.js"),n=await c(t.config,t.port,t.verbose);console.log(` ${n.message}`),console.log(` Log file: ${n.logPath}`),process.exit(n.success?0:1)}if(process.argv[2]==="stop"){let{stopDaemon:c}=await import("./daemon-I5WKQHTA.js"),n=await c();console.log(` ${n.message}`),process.exit(n.success?0:1)}if(process.argv[2]==="status"){let{statusDaemon:c}=await import("./daemon-I5WKQHTA.js"),n=await c();console.log(` ${n.message}`);try{let{getService:d}=await import("./service-6EQTZJEG.js"),f=(await d()).isInstalled();console.log(f?" Service: installed":' Service: not installed (run "modelweaver install" to enable auto-start)')}catch(d){console.log(` Service: ${d instanceof Error?d.message:String(d)}`)}process.exit(0)}if(process.argv[2]==="remove"){let{removeDaemon:c}=await import("./daemon-I5WKQHTA.js"),n=await c();console.log(` ${n.message}`),process.exit(n.success?0:1)}if(process.argv[2]==="install"){try{let{getService:c}=await import("./service-6EQTZJEG.js");await(await c()).install()}catch(c){console.error(` Error: ${c instanceof Error?c.message:String(c)}`),process.exit(1)}process.exit(0)}if(process.argv[2]==="uninstall"){try{let{getService:c}=await import("./service-6EQTZJEG.js");(await c()).uninstall()}catch(c){console.error(` Error: ${c instanceof Error?c.message:String(c)}`),process.exit(1)}process.exit(0)}if(process.argv[2]==="gui"){let{launchGui:c}=await import("./gui-launcher-ZVOVTD6C.js");await c(),process.exit(0)}if(process.argv[2]==="reload"){let{reloadDaemon:c}=await import("./daemon-I5WKQHTA.js");await c(t.port),process.exit(0)}t.help&&(Be(),process.exit(0));let e,i;try{let c=j(t.config);e=c.config,i=c.configPath}catch(c){console.error(`Config error: ${c.message}`),process.exit(1)}let s=t.port||e.server.port,m=e.server.host,l=t.verbose?"debug":"info",r=new O;if(t.monitor){await Z(t);return}if(t.daemon){let{removeWorkerPidFile:c,writeWorkerPidFile:n,createDebouncedReload:d,getLogPath:u}=await import("./daemon-I5WKQHTA.js"),{reloadConfig:f}=await import("./config-UO2KSCGU.js"),{createWriteStream:h,watch:M}=await import("fs"),{createLogger:y}=await import("./logger-UA2A2DVX.js"),g=y(l);process.on("uncaughtException",v=>{g.error("Uncaught exception (daemon survived)",{error:v.message,stack:v.stack})}),process.on("unhandledRejection",v=>{g.error("Unhandled rejection (daemon survived)",{reason:String(v)})}),await n(process.pid);let w=h(u(),{flags:"a"});w.on("error",()=>{}),process.stdout.write=w.write.bind(w),process.stderr.write=w.write.bind(w);let R=B(e,l,r),b=null;if(i){let v=d(()=>{try{let S=f(i);R.setConfig(S),g.info("Config reloaded",{path:i})}catch(S){g.error("Config reload failed \u2014 keeping old config",{error:S.message})}},300);try{b=M(i,()=>{v.reload()}),b.on("error",()=>{b&&(b.close(),b=null)})}catch{}}process.on("SIGUSR1",()=>{try{let v=f(i);R.setConfig(v),g.info("Config reloaded (SIGUSR1)",{path:i})}catch(v){g.error("Config reload failed (SIGUSR1)",{error:v.message})}});let C=ee({fetch:R.app.fetch,hostname:m,port:s});$(C,r);let k=async()=>{b&&(b.close(),b=null),await c(),w.end(),process.exit(0)};process.on("SIGTERM",k),process.on("SIGINT",k);return}let o=B(e,l,r);console.log(`
|
|
33
|
+
ModelWeaver v${$e}`),console.log(` Listening: http://${m}:${s}`),console.log(` Config: ${i}
|
|
34
|
+
`),console.log(" Routes:");for(let[c,n]of e.routing){let d=n.map((u,f)=>`${u.provider}${f===0?" (primary)":" (fallback)"}`).join(", ");console.log(` ${c.padEnd(8)} \u2192 ${d}`)}if(console.log(),e.modelRouting.size>0){console.log(" Model Routes:");for(let[c,n]of e.modelRouting){let d=n.map((u,f)=>`${u.provider}${f===0?" (primary)":" (fallback)"}`).join(", ");console.log(` ${c.padEnd(20)} \u2192 ${d}`)}console.log()}let a=ee({fetch:o.app.fetch,hostname:m,port:s});$(a,r);let p=()=>{console.log(`
|
|
35
|
+
Shutting down...`),process.exit(0)};process.on("SIGTERM",p),process.on("SIGINT",p)}Le();
|
|
36
36
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { attachWebSocket } from \"./ws.js\";\nimport { startMonitor } from \"./monitor.js\";\n\n// Read version from package.json at startup\nconst VERSION: string = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\")).version;\n\nfunction parseArgs(argv: string[]): { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } {\n const args: { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } = { verbose: false, help: false, daemon: false, monitor: false, gui: false };\n for (let i = 2; i < argv.length; i++) {\n switch (argv[i]) {\n case \"-p\":\n case \"--port\":\n const portStr = argv[++i];\n if (!portStr || isNaN(parseInt(portStr, 10))) {\n console.error(\"Error: -p/--port requires a number\");\n process.exit(1);\n }\n args.port = parseInt(portStr, 10);\n break;\n case \"-c\":\n case \"--config\":\n const configPath = argv[++i];\n if (!configPath) {\n console.error(\"Error: -c/--config requires a path\");\n process.exit(1);\n }\n args.config = configPath;\n break;\n case \"-v\":\n case \"--verbose\":\n args.verbose = true;\n break;\n case \"-h\":\n case \"--help\":\n args.help = true;\n break;\n case \"--daemon\":\n args.daemon = true;\n break;\n case \"--monitor\":\n args.monitor = true;\n break;\n }\n }\n return args;\n}\n\nfunction printHelp() {\n console.log(`\nModelWeaver — Multi-provider model orchestration proxy for Claude Code\n\nUsage: modelweaver [command] [options]\n\nCommands:\n init [--quick] Run interactive setup wizard (--quick for express mode)\n start Start as background daemon\n stop Stop background daemon\n status Show daemon status\n remove Stop daemon and remove PID + log files\n reload Reload daemon worker (load fresh code after build)\n install Install launchd service (auto-start at login)\n uninstall Uninstall launchd service\n gui Launch the GUI (downloads if needed)\n\nOptions:\n -p, --port <number> Server port (default: from config)\n -c, --config <path> Config file path (auto-detected)\n -v, --verbose Enable debug logging (default: off)\n -h, --help Show this help\n\nConfig locations (first found wins):\n ./modelweaver.yaml\n ~/.modelweaver/config.yaml\n`);\n}\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n // Load .env file if present (created by modelweaver init)\n try {\n const dotenv = await import('dotenv');\n const { existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const home = process.env.HOME || process.env.USERPROFILE || '';\n // Try cwd/.env first, then ~/.modelweaver/.env, then ~/.env\n const paths = [\n join(process.cwd(), '.env'),\n join(home, '.modelweaver', '.env'),\n join(home, '.env'),\n ];\n for (const p of paths) {\n if (existsSync(p)) {\n dotenv.config({ path: p });\n break;\n }\n }\n } catch {\n // dotenv not installed or .env not present — continue without it\n }\n\n // Handle 'init' subcommand — dynamic import to avoid loading prompts for normal startup\n if (process.argv[2] === 'init') {\n const quick = process.argv.includes('--quick') || process.argv.includes('-q');\n const { runInit } = await import('./init.js');\n await runInit({ quick });\n process.exit(0);\n }\n\n // Handle 'start' subcommand\n if (process.argv[2] === 'start') {\n const { startDaemon } = await import('./daemon.js');\n const result = await startDaemon(args.config, args.port, args.verbose);\n console.log(` ${result.message}`);\n console.log(` Log file: ${result.logPath}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'stop' subcommand\n if (process.argv[2] === 'stop') {\n const { stopDaemon } = await import('./daemon.js');\n const result = await stopDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'status' subcommand\n if (process.argv[2] === 'status') {\n const { statusDaemon } = await import('./daemon.js');\n const result = await statusDaemon();\n console.log(` ${result.message}`);\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n const installed = svc.isInstalled();\n if (installed) {\n console.log(` Service: installed`);\n } else {\n console.log(` Service: not installed (run \"modelweaver install\" to enable auto-start)`);\n }\n } catch (err) {\n console.log(` Service: ${err instanceof Error ? err.message : String(err)}`);\n }\n process.exit(0);\n }\n\n // Handle 'remove' subcommand — stop + clean up PID and log files\n if (process.argv[2] === 'remove') {\n const { removeDaemon } = await import('./daemon.js');\n const result = await removeDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'install' subcommand — install platform service\n if (process.argv[2] === 'install') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n await svc.install();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'uninstall' subcommand — uninstall platform service\n if (process.argv[2] === 'uninstall') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n svc.uninstall();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'gui' subcommand\n if (process.argv[2] === 'gui') {\n const { launchGui } = await import('./gui-launcher.js');\n await launchGui();\n process.exit(0);\n }\n\n // Handle 'reload' subcommand\n if (process.argv[2] === 'reload') {\n const { reloadDaemon } = await import('./daemon.js');\n await reloadDaemon(args.port);\n process.exit(0);\n }\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n // Load config\n let config;\n let configPath;\n try {\n const result = loadConfig(args.config);\n config = result.config;\n configPath = result.configPath;\n } catch (error) {\n console.error(`Config error: ${(error as Error).message}`);\n process.exit(1);\n }\n\n // CLI port override\n const port = args.port || config.server.port;\n const host = config.server.host;\n const logLevel: LogLevel = args.verbose ? \"debug\" : \"info\";\n\n // Initialize metrics store\n const metricsStore = new MetricsStore();\n\n // --- Monitor mode (spawns daemon child, auto-restarts on crash) ---\n if (args.monitor) {\n await startMonitor(args);\n return;\n }\n\n // --- Daemon mode ---\n if (args.daemon) {\n const { removeWorkerPidFile, writeWorkerPidFile, createDebouncedReload, getLogPath } = await import('./daemon.js');\n const { reloadConfig } = await import('./config.js');\n const { createWriteStream, watch } = await import('node:fs');\n const { createLogger } = await import('./logger.js');\n const logger = createLogger(logLevel);\n\n // Prevent silent crashes from killing the daemon worker\n process.on('uncaughtException', (err) => {\n logger.error('Uncaught exception (daemon survived)', { error: err.message, stack: err.stack });\n });\n process.on('unhandledRejection', (reason) => {\n logger.error('Unhandled rejection (daemon survived)', { reason: String(reason) });\n });\n\n // Write worker PID file (monitor owns modelweaver.pid)\n await writeWorkerPidFile(process.pid);\n\n // Redirect stdout/stderr to log file\n const logStream = createWriteStream(getLogPath(), { flags: 'a' });\n logStream.on('error', () => { /* ignore write errors to log file */ });\n process.stdout.write = logStream.write.bind(logStream) as typeof process.stdout.write;\n process.stderr.write = logStream.write.bind(logStream) as typeof process.stderr.write;\n\n // Create app with mutable config\n const handle = createApp(config, logLevel, metricsStore);\n\n // Hot-reload: watch config file for changes\n let configWatcher: ReturnType<typeof watch> | null = null;\n if (configPath) {\n const debounced = createDebouncedReload(() => {\n try {\n const newConfig = reloadConfig(configPath);\n handle.setConfig(newConfig);\n logger.info(\"Config reloaded\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed — keeping old config\", { error: (err as Error).message });\n }\n }, 300);\n\n try {\n configWatcher = watch(configPath, () => {\n debounced.reload();\n });\n configWatcher.on('error', () => {\n // fs.watch failed — silently disable hot-reload\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n });\n } catch {\n // fs.watch not available — hot-reload disabled\n }\n }\n\n // SIGUSR1 triggers config hot-reload\n // Note: SIGUSR1 is POSIX-only; this handler is a no-op on Windows.\n process.on('SIGUSR1', () => {\n try {\n const newConfig = reloadConfig(configPath!);\n handle.setConfig(newConfig);\n logger.info(\"Config reloaded (SIGUSR1)\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed (SIGUSR1)\", { error: (err as Error).message });\n }\n });\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = async () => {\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n await removeWorkerPidFile();\n logStream.end();\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return; // Don't fall through to foreground mode\n }\n\n // --- Foreground mode ---\n const handle = createApp(config, logLevel, metricsStore);\n\n // Print startup info\n console.log(`\\n ModelWeaver v${VERSION}`);\n console.log(` Listening: http://${host}:${port}`);\n console.log(` Config: ${configPath}\\n`);\n\n console.log(\" Routes:\");\n for (const [tier, entries] of config.routing) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${tier.padEnd(8)} → ${providerList}`);\n }\n console.log();\n\n if (config.modelRouting.size > 0) {\n console.log(\" Model Routes:\");\n for (const [model, entries] of config.modelRouting) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${model.padEnd(20)} → ${providerList}`);\n }\n console.log();\n }\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nmain();\n","// src/server.ts\nimport { Hono } from \"hono\";\nimport { resolveRequest, clearRoutingCache } from \"./router.js\";\nimport { forwardWithFallback } from \"./proxy.js\";\nimport { createLogger, type LogLevel } from \"./logger.js\";\nimport type { AppConfig, ProviderConfig, RequestContext } from \"./types.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nimport type { MetricsStore } from \"./metrics.js\";\nimport { broadcastStreamEvent } from \"./ws.js\";\nimport type { StreamEvent } from \"./types.js\";\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp + cacheRead + cacheCreation, outputTokens: out };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine || !dataLine.includes('\"usage\"')) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n };\n\n const recordMetrics = (inp: number, out: number) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n });\n\n // Broadcast completion event\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (now - lastStreamEmit >= STREAM_THROTTLE_MS && tokens.output > 0) {\n lastStreamEmit = now;\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (nowJson - lastStreamEmit >= STREAM_THROTTLE_MS && outputTokens > 0) {\n lastStreamEmit = nowJson;\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n });\n });\n }\n\n if (isFinal) {\n const totalInput = inputTokens + cacheReadTokens + cacheCreationTokens;\n recordMetrics(totalInput, outputTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n cancel() {\n // Stream was cancelled (client disconnected) — emit metrics with what we have\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/** Headers forwarded as-is to upstream */\nconst FORWARD_HEADERS = new Set([\n \"anthropic-version\",\n \"anthropic-beta\",\n \"content-type\",\n \"accept\",\n]);\n\n/** Pre-compiled regex for normalizing duplicate slashes in URL paths */\nconst MULTI_SLASH = /\\/+/g;\n\n/** Pre-compiled regex for stripping origin from URLs */\nconst STRIP_ORIGIN = /^https?:\\/\\/[^/]+/;\n\n/** Pre-compiled regexes for targeted body replacements (preserve prompt caching) */\nconst MODEL_KEY_REGEX = /\"model\"\\s*:\\s*\"([^\"]*)\"/;\nconst MAX_TOKENS_REGEX = /\"max_tokens\"\\s*:\\s*(\\d+)/;\n\n/** Module-level TextEncoder — avoids per-request allocation */\nconst textEncoder = new TextEncoder();\n\nexport function isRetriable(status: number): boolean {\n return status === 429 || status >= 500;\n}\n\nconst CONTEXT_WINDOW_PATTERNS = [\n 'context window', 'context_limit', 'token limit',\n 'prompt is too long', 'max tokens', 'input too large', 'too many tokens',\n];\n\nfunction isContextWindowError(status: number, body: string): boolean {\n if (status !== 400) return false;\n const lower = body.toLowerCase();\n return CONTEXT_WINDOW_PATTERNS.some(p => lower.includes(p));\n}\n\nfunction handleContextWindowError(status: number, body: string): Response | null {\n if (!isContextWindowError(status, body)) return null;\n\n console.warn('[context-compact] Upstream context window limit detected');\n try {\n const flagDir = path.join(os.homedir(), '.claude', 'state');\n fs.mkdirSync(flagDir, { recursive: true });\n fs.writeFileSync(path.join(flagDir, 'context-compact-needed'), Date.now().toString());\n } catch {\n // Best-effort flag write\n }\n\n const enhanced = JSON.stringify({\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: \"Context window limit reached. Run /compact to reduce conversation size, then retry.\",\n },\n });\n return new Response(enhanced, {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nexport function buildOutboundUrl(baseUrl: string, incomingPath: string): string {\n let basePath = \"\";\n let origin = baseUrl;\n const slashIndex = baseUrl.indexOf('/', baseUrl.indexOf('//') + 2);\n if (slashIndex !== -1) {\n origin = baseUrl.substring(0, slashIndex);\n basePath = baseUrl.substring(slashIndex);\n }\n\n let incomingQuery = \"\";\n let incomingOnly = incomingPath;\n const qIndex = incomingPath.indexOf('?');\n if (qIndex !== -1) {\n incomingOnly = incomingPath.substring(0, qIndex);\n incomingQuery = incomingPath.substring(qIndex);\n }\n\n // Deduplicate /v1 when base URL path already ends with it and incoming path starts with it.\n // e.g. baseUrl=\"https://api.fireworks.ai/inference/v1\" + path=\"/v1/chat/completions\"\n // → \"/inference/v1/chat/completions\" (not \"/inference/v1/v1/chat/completions\")\n let resolvedPath;\n if (basePath.endsWith('/v1') && incomingOnly.startsWith('/v1')) {\n resolvedPath = basePath + incomingOnly.substring(3);\n } else {\n resolvedPath = basePath + incomingOnly;\n }\n\n // Normalize duplicate slashes\n resolvedPath = resolvedPath.replace(MULTI_SLASH, \"/\");\n\n return origin + resolvedPath + incomingQuery;\n}\n\nexport function buildOutboundHeaders(\n incomingHeaders: Headers,\n provider: ProviderConfig,\n requestId: string\n): Headers {\n const headers = new Headers();\n\n // Forward select headers as-is\n for (const name of FORWARD_HEADERS) {\n const value = incomingHeaders.get(name);\n if (value) headers.set(name, value);\n }\n\n // Rewrite auth headers based on provider authType\n if (provider.authType === \"bearer\") {\n headers.set(\"Authorization\", `Bearer ${provider.apiKey}`);\n } else {\n headers.set(\"x-api-key\", provider.apiKey);\n }\n headers.set(\"x-request-id\", requestId);\n\n // Set host to provider hostname (use cached components when available)\n const cachedHost = provider._cachedHost;\n if (cachedHost) {\n headers.set(\"host\", cachedHost);\n } else {\n try {\n const url = new URL(provider.baseUrl);\n headers.set(\"host\", url.host);\n } catch {\n // If baseUrl is not a valid URL, skip host rewrite\n }\n }\n\n return headers;\n}\n\n/**\n * Remove orphaned tool_use/tool_result pairs from the messages array.\n *\n * In Anthropic's format:\n * - tool_use blocks live inside assistant message content: { role: \"assistant\", content: [{ type: \"tool_use\", id: \"call_xxx\", ... }] }\n * - tool_result blocks live inside user message content: { role: \"user\", content: [{ type: \"tool_result\", tool_use_id: \"call_xxx\", ... }] }\n *\n * A tool_result is orphaned if its tool_use_id references a tool_use not in any assistant content block.\n * A tool_use is orphaned if its id has no matching tool_result in any user content block.\n */\nfunction cleanOrphanedToolMessages(body: Record<string, unknown>): void {\n const messages = body.messages;\n if (!Array.isArray(messages)) return;\n\n // Pass 1: Collect tool_use IDs and tool_result IDs in a single pass,\n // and record which message indices have orphaned blocks\n const toolUseIds = new Set<string>();\n const toolResultIds = new Set<string>();\n const needsFiltering = new Map<number, \"user\" | \"assistant\">();\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!Array.isArray(msg.content)) continue;\n\n if (msg.role === \"assistant\") {\n let hasOrphan = false;\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.id) {\n toolUseIds.add(String(block.id));\n if (!toolResultIds.has(String(block.id))) hasOrphan = true;\n }\n }\n // Note: toolResultIds may not be fully populated yet, so we defer judgment\n // on assistant orphans to after the full pass.\n } else if (msg.role === \"user\") {\n let hasOrphan = false;\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && block.tool_use_id) {\n toolResultIds.add(String(block.tool_use_id));\n if (!toolUseIds.has(String(block.tool_use_id))) hasOrphan = true;\n }\n }\n if (hasOrphan) needsFiltering.set(i, \"user\");\n }\n }\n\n // Check assistant messages for orphans now that toolResultIds is complete\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n const hasOrphan = msg.content.some(\n (block: Record<string, unknown>) => block.type === \"tool_use\" && !toolResultIds.has(String(block.id))\n );\n if (hasOrphan) needsFiltering.set(i, \"assistant\");\n }\n }\n\n if (needsFiltering.size === 0) return;\n\n // Pass 2: Filter out orphaned tool references from content arrays\n body.messages = messages.map((msg: Record<string, unknown>, i: number) => {\n const filterType = needsFiltering.get(i);\n if (filterType && Array.isArray(msg.content)) {\n const filtered = msg.content.filter((block: Record<string, unknown>) => {\n if (filterType === \"user\") {\n return !(block.type === \"tool_result\" && !toolUseIds.has(String(block.tool_use_id)));\n }\n return !(block.type === \"tool_use\" && !toolResultIds.has(String(block.id)));\n });\n if (filtered.length === msg.content.length) return msg; // nothing was actually filtered\n return { ...msg, content: filtered };\n }\n return msg;\n });\n\n // Pass 3: Re-check user messages after assistant cleanup.\n // After Pass 2 removed orphaned tool_use blocks from assistant messages, some\n // user tool_result blocks may now reference tool_use IDs that no longer exist.\n // Rebuild valid IDs from the cleaned messages and strip dangling user tool_results.\n const validToolUseIds = new Set<string>();\n for (const msg of body.messages as Record<string, unknown>[]) {\n if (!Array.isArray(msg.content)) continue;\n if (msg.role === \"assistant\") {\n for (const block of msg.content as Record<string, unknown>[]) {\n if (block.type === \"tool_use\" && block.id) validToolUseIds.add(String(block.id));\n }\n }\n }\n\n body.messages = (body.messages as Record<string, unknown>[]).map((msg) => {\n if (msg.role === \"user\" && Array.isArray(msg.content)) {\n const filtered = msg.content.filter(\n (block: Record<string, unknown>) =>\n !(block.type === \"tool_result\" && !validToolUseIds.has(String(block.tool_use_id)))\n );\n if (filtered.length === msg.content.length) return msg;\n return { ...msg, content: filtered };\n }\n return msg;\n });\n}\n\n/**\n * Apply targeted string replacements to rawBody to preserve prompt caching.\n * On the primary attempt (chainIndex === 0), we avoid full JSON.stringify which\n * breaks Anthropic's cache breakpoints (position-sensitive, whitespace/order-sensitive).\n * Falls back to full JSON parse/mutate/stringify when structural changes are needed.\n */\nfunction applyTargetedReplacements(\n rawBody: string,\n entry: RoutingEntry,\n provider: ProviderConfig,\n parsed: Record<string, unknown>,\n needsOrphanClean: boolean,\n): string {\n // If orphan cleaning is needed, we must do full JSON parse (structural changes to messages)\n if (needsOrphanClean) {\n // deep clone required: cleanOrphanedToolMessages mutates the messages array in-place\n const mutable = structuredClone(parsed);\n if (entry.model) mutable.model = entry.model;\n cleanOrphanedToolMessages(mutable);\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requested = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requested > maxOutputTokens) {\n mutable.max_tokens = Math.min(requested, maxOutputTokens);\n }\n }\n return JSON.stringify(mutable);\n }\n\n // Targeted replacement path -- only model override and/or max_tokens clamping\n let body = rawBody;\n\n // Model override via regex (no JSON.parse/stringify)\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n const modelMatch = MODEL_KEY_REGEX.exec(body);\n if (modelMatch) {\n body = body.replace(MODEL_KEY_REGEX, `\"model\":\"${entry.model}\"`);\n console.warn(\n `Routing override: ${modelMatch[1]} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n // max_tokens clamping\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const maxTokensMatch = MAX_TOKENS_REGEX.exec(body);\n if (maxTokensMatch) {\n const current = parseInt(maxTokensMatch[1], 10);\n if (current > maxOutputTokens) {\n body = body.replace(MAX_TOKENS_REGEX, `\"max_tokens\":${maxOutputTokens}`);\n }\n } else if (typeof parsed.max_tokens !== \"number\") {\n // max_tokens not present in body -- need to add it. Shallow clone suffices\n // since only top-level properties (model, max_tokens) are mutated.\n const mutable = { ...parsed };\n if (entry.model) mutable.model = entry.model;\n mutable.max_tokens = maxOutputTokens;\n return JSON.stringify(mutable);\n }\n }\n\n return body;\n}\n\n/**\n * Forward a request to a single provider.\n * Uses ctx.parsedBody when available (avoids re-parsing); falls back to ctx.rawBody.\n * incomingRequest is used for metadata only (url, headers).\n * Returns the Response object — caller decides fallback logic.\n */\nexport async function forwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n externalSignal?: AbortSignal,\n chainIndex: number = 0,\n): Promise<Response> {\n const outgoingPath = incomingRequest.url.replace(STRIP_ORIGIN, \"\");\n\n // Set actualModel early so metrics always record the routed model,\n // even if body parsing or the fetch itself fails\n if (entry.model) {\n ctx.actualModel = entry.model;\n }\n\n // Build outbound URL from provider base URL and request path\n const url = buildOutboundUrl(provider.baseUrl, outgoingPath);\n\n // Prepare body — prefer raw passthrough to preserve upstream prompt caching.\n // Only parse and re-serialize when a modification is actually required,\n // because Anthropic's cache breakpoints are position-sensitive and\n // JSON.stringify changes whitespace / key order, breaking cache hits.\n let body: string;\n const contentType = incomingRequest.headers.get(\"content-type\") || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n const parsed = (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody\n ?? JSON.parse(ctx.rawBody);\n\n // Determine whether any body modification is needed\n let needsModification = false;\n\n // Check 1: Model override needed?\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n needsModification = true;\n }\n\n // Check 2: Orphan cleaning needed? (only for fallback attempts, not primary)\n // On the primary attempt (index 0), conversation history is intact.\n // Only when falling back (index > 0) do cross-provider orphans appear.\n const needsOrphanClean = chainIndex > 0;\n if (needsOrphanClean) needsModification = true;\n\n // Check 3: max_tokens clamping needed?\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof parsed.max_tokens === \"number\" ? parsed.max_tokens : maxOutputTokens;\n if (parsed.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n needsModification = true;\n }\n }\n\n if (needsModification) {\n // On primary attempt (chainIndex === 0) without orphan cleaning, use targeted\n // string replacements to preserve prompt caching. Anthropic's cache breakpoints\n // are position-sensitive -- JSON.stringify changes whitespace/order, breaking hits.\n if (chainIndex === 0 && !needsOrphanClean) {\n body = applyTargetedReplacements(ctx.rawBody, entry, provider, parsed, false);\n } else {\n // Fallback attempts: full JSON parse/mutate/stringify (caching already broken)\n // deep clone required: cleanOrphanedToolMessages may mutate the messages array in-place\n const mutable = structuredClone(parsed);\n\n if (entry.model) {\n const originalModel = mutable.model as string | undefined;\n mutable.model = entry.model;\n if (originalModel && originalModel !== entry.model) {\n console.warn(\n `Routing override: ${originalModel} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n if (needsOrphanClean) {\n cleanOrphanedToolMessages(mutable);\n }\n\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n mutable.max_tokens = Math.min(requestedMaxTokens, maxOutputTokens);\n }\n }\n\n body = JSON.stringify(mutable);\n }\n } else {\n // No modifications needed — passthrough raw body to preserve prompt caching\n body = ctx.rawBody;\n }\n } catch {\n // If body can't be parsed, send it as-is without model override\n body = ctx.rawBody;\n }\n } else {\n body = ctx.rawBody;\n }\n\n const headers = buildOutboundHeaders(incomingRequest.headers, provider, ctx.requestId);\n headers.set(\"content-length\", Buffer.byteLength(body, 'utf-8').toString());\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), provider.timeout);\n\n // Listen for external abort (from race cancellation) to abort this request\n if (externalSignal) {\n if (externalSignal.aborted) {\n // Already aborted — don't even start the request\n clearTimeout(timeout);\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: `Provider \"${provider.name}\" cancelled by race winner` },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n const onExternalAbort = () => {\n clearTimeout(timeout);\n };\n externalSignal.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n try {\n const undiciResponse = await undiciRequest(url, {\n method: \"POST\",\n headers,\n body,\n signal: controller.signal,\n dispatcher: provider._agent,\n });\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n undiciResponse.body as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n // Network errors / timeouts — return a synthetic 502\n const message = error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n}\n\n/**\n * Race multiple providers simultaneously. Returns the first successful response.\n * Aborts all remaining requests once a winner is found.\n */\nasync function raceProviders(\n chain: RoutingEntry[],\n providers: Map<string, ProviderConfig>,\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void },\n chainOffset: number = 0,\n): Promise<Response> {\n const sharedController = new AbortController();\n\n const races = chain.map(async (entry, index): Promise<{ response: Response; index: number }> => {\n const provider = providers.get(entry.provider);\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n // Check circuit breaker\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, sharedController.signal, index + chainOffset);\n // Record for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status);\n }\n return { response, index };\n } catch {\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(502);\n }\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n });\n\n // Track completed promises to avoid double-processing\n const completed = new Set<Promise<{ response: Response; index: number }>>();\n const failures: { response: Response; index: number }[] = [];\n\n try {\n while (completed.size < races.length) {\n const pending = races.filter(r => !completed.has(r));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(races[winner.index] ?? races[0]);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n // Cancel bodies of already-completed losing responses to free resources\n for (const f of failures) {\n try { f.response.body?.cancel(); } catch { /* ignore */ }\n }\n return winner.response;\n }\n\n // Non-retriable error — check for context window limit before propagating\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n // Retriable but not success — record and continue waiting\n failures.push(winner);\n }\n\n // All providers returned retriable errors — return the first failure\n sharedController.abort();\n if (failures.length > 0) {\n return failures[0].response;\n }\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n let lastResponse: Response | null = null;\n\n for (let i = 0; i < chain.length; i++) {\n const entry = chain[i];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n lastResponse = new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n continue;\n }\n\n // Check circuit breaker before attempting provider\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n logger?.warn(\"Provider skipped by circuit breaker\", { requestId: ctx.requestId, provider: entry.provider });\n continue;\n }\n\n onAttempt?.(entry.provider, i);\n\n // forwardRequest uses ctx.rawBody, so body can be re-read on each attempt\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, undefined, i);\n lastResponse = response;\n\n // Record result for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status);\n }\n\n // Success — return immediately\n if (response.status >= 200 && response.status < 300) {\n return response;\n }\n\n // Non-retriable error — check for context window limit before passing through\n if (!isRetriable(response.status)) {\n if (response.status === 400 && response.body) {\n try {\n const errBody = await response.text();\n const handled = handleContextWindowError(response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n } catch {\n return response;\n }\n }\n return response;\n }\n\n // Retriable error — if there are more providers, drain body and try next\n if (i < chain.length - 1) {\n await response.body?.cancel();\n\n // On 429: race remaining providers simultaneously\n if (response.status === 429 && i + 1 < chain.length) {\n ctx.fallbackMode = \"race\";\n const remaining = chain.slice(i + 1);\n return raceProviders(remaining, providers, ctx, incomingRequest, onAttempt, logger, i + 1);\n }\n continue;\n }\n // Last provider in chain — return the error as-is (body still readable)\n return response;\n }\n\n // All providers exhausted — return the last real error response if available\n if (lastResponse) {\n return lastResponse;\n }\n\n const fallbackBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers exhausted\" },\n });\n return new Response(fallbackBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(fallbackBody).byteLength.toString(),\n },\n });\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\n\nlet wssInstance: InstanceType<typeof import(\"ws\").WebSocketServer> | null = null;\n\nexport function attachWebSocket(server: Server, metricsStore: MetricsStore): void {\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n wssInstance = wss;\n\n wss.on(\"connection\", (ws) => {\n // Send current summary as initial state\n const summary = metricsStore.getSummary();\n const initialMsg: WsMessage = { type: \"summary\", data: summary };\n ws.send(JSON.stringify(initialMsg));\n\n let pendingSummaryTimer: ReturnType<typeof setTimeout> | undefined;\n let missedPongs = 0;\n const alive = () => ws.readyState === ws.OPEN;\n\n // Subscribe to new metrics with backpressure check and debounced summary\n const unsubscribe = metricsStore.onRecord((metrics: RequestMetrics) => {\n if (!alive()) return;\n\n // Backpressure: skip send if outbound buffer is too large\n if (ws.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n // Schedule a summary update instead so the client eventually catches up\n scheduleSummaryUpdate();\n return;\n }\n\n // Defer JSON.stringify + send off the critical path\n setImmediate(() => {\n if (!alive()) return;\n const msg: WsMessage = { type: \"request\", data: metrics };\n ws.send(JSON.stringify(msg));\n });\n\n scheduleSummaryUpdate();\n });\n\n function scheduleSummaryUpdate(): void {\n if (pendingSummaryTimer) return; // already scheduled\n pendingSummaryTimer = setTimeout(() => {\n pendingSummaryTimer = undefined;\n if (!alive()) return;\n const msg: WsMessage = { type: \"summary\", data: metricsStore.getSummary() };\n ws.send(JSON.stringify(msg));\n }, SUMMARY_DEBOUNCE_MS);\n }\n\n // Ping/pong heartbeat for liveness tracking\n const pingTimer = setInterval(() => {\n if (!alive()) {\n clearInterval(pingTimer);\n return;\n }\n // Terminate if client missed too many pongs\n if (missedPongs >= MAX_MISSED_PONGS) {\n cleanup(); // ensure timers and subscriber are cleaned up\n ws.terminate();\n return;\n }\n ws.ping();\n missedPongs++;\n }, PING_INTERVAL_MS);\n\n ws.on(\"pong\", () => {\n missedPongs = 0; // reset on successful pong\n });\n\n let cleanedUp = false;\n const cleanup = () => {\n if (cleanedUp) return;\n cleanedUp = true;\n clearInterval(pingTimer);\n if (pendingSummaryTimer) clearTimeout(pendingSummaryTimer);\n unsubscribe();\n };\n\n ws.on(\"close\", cleanup);\n ws.on(\"error\", cleanup);\n });\n}\n\nexport function broadcastStreamEvent(data: StreamEvent): void {\n if (!wssInstance) return;\n const msg = JSON.stringify({ type: \"stream\", data });\n for (const client of wssInstance.clients) {\n if (client.readyState === client.OPEN) {\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":"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","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","fs","path","os","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","toolUseIds","toolResultIds","needsFiltering","i","msg","hasOrphan","block","filterType","filtered","validToolUseIds","applyTargetedReplacements","rawBody","entry","parsed","needsOrphanClean","mutable","maxOutputTokens","requested","modelMatch","maxTokensMatch","forwardRequest","ctx","incomingRequest","externalSignal","chainIndex","outgoingPath","needsModification","requestedMaxTokens","originalModel","controller","timeout","onExternalAbort","undiciResponse","response","error","message","raceProviders","chain","providers","onAttempt","logger","chainOffset","sharedController","races","index","errBody","completed","failures","pending","r","winner","handled","forwardWithFallback","lastResponse","remaining","fallbackBody","randomUUID","gzip","promisify","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","wssInstance","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","client","gzipAsync","promisify","gzip","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","cacheRead","cacheCreation","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","drainEvents","eventText","event","dataLine","l","scanWindow","text","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","totalInput","chunk","controller","agentKey","origin","size","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","model","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","newConfig","oldAgents","reusedKeys","key","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","callback","cap","result","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","i","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","model"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { attachWebSocket } from \"./ws.js\";\nimport { startMonitor } from \"./monitor.js\";\n\n// Read version from package.json at startup\nconst VERSION: string = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\")).version;\n\nfunction parseArgs(argv: string[]): { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } {\n const args: { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } = { verbose: false, help: false, daemon: false, monitor: false, gui: false };\n for (let i = 2; i < argv.length; i++) {\n switch (argv[i]) {\n case \"-p\":\n case \"--port\":\n const portStr = argv[++i];\n if (!portStr || isNaN(parseInt(portStr, 10))) {\n console.error(\"Error: -p/--port requires a number\");\n process.exit(1);\n }\n args.port = parseInt(portStr, 10);\n break;\n case \"-c\":\n case \"--config\":\n const configPath = argv[++i];\n if (!configPath) {\n console.error(\"Error: -c/--config requires a path\");\n process.exit(1);\n }\n args.config = configPath;\n break;\n case \"-v\":\n case \"--verbose\":\n args.verbose = true;\n break;\n case \"-h\":\n case \"--help\":\n args.help = true;\n break;\n case \"--daemon\":\n args.daemon = true;\n break;\n case \"--monitor\":\n args.monitor = true;\n break;\n }\n }\n return args;\n}\n\nfunction printHelp() {\n console.log(`\nModelWeaver — Multi-provider model orchestration proxy for Claude Code\n\nUsage: modelweaver [command] [options]\n\nCommands:\n init [--quick] Run interactive setup wizard (--quick for express mode)\n start Start as background daemon\n stop Stop background daemon\n status Show daemon status\n remove Stop daemon and remove PID + log files\n reload Reload daemon worker (load fresh code after build)\n install Install launchd service (auto-start at login)\n uninstall Uninstall launchd service\n gui Launch the GUI (downloads if needed)\n\nOptions:\n -p, --port <number> Server port (default: from config)\n -c, --config <path> Config file path (auto-detected)\n -v, --verbose Enable debug logging (default: off)\n -h, --help Show this help\n\nConfig locations (first found wins):\n ./modelweaver.yaml\n ~/.modelweaver/config.yaml\n`);\n}\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n // Load .env file if present (created by modelweaver init)\n try {\n const dotenv = await import('dotenv');\n const { existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const home = process.env.HOME || process.env.USERPROFILE || '';\n // Try cwd/.env first, then ~/.modelweaver/.env, then ~/.env\n const paths = [\n join(process.cwd(), '.env'),\n join(home, '.modelweaver', '.env'),\n join(home, '.env'),\n ];\n for (const p of paths) {\n if (existsSync(p)) {\n dotenv.config({ path: p });\n break;\n }\n }\n } catch {\n // dotenv not installed or .env not present — continue without it\n }\n\n // Handle 'init' subcommand — dynamic import to avoid loading prompts for normal startup\n if (process.argv[2] === 'init') {\n const quick = process.argv.includes('--quick') || process.argv.includes('-q');\n const { runInit } = await import('./init.js');\n await runInit({ quick });\n process.exit(0);\n }\n\n // Handle 'start' subcommand\n if (process.argv[2] === 'start') {\n const { startDaemon } = await import('./daemon.js');\n const result = await startDaemon(args.config, args.port, args.verbose);\n console.log(` ${result.message}`);\n console.log(` Log file: ${result.logPath}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'stop' subcommand\n if (process.argv[2] === 'stop') {\n const { stopDaemon } = await import('./daemon.js');\n const result = await stopDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'status' subcommand\n if (process.argv[2] === 'status') {\n const { statusDaemon } = await import('./daemon.js');\n const result = await statusDaemon();\n console.log(` ${result.message}`);\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n const installed = svc.isInstalled();\n if (installed) {\n console.log(` Service: installed`);\n } else {\n console.log(` Service: not installed (run \"modelweaver install\" to enable auto-start)`);\n }\n } catch (err) {\n console.log(` Service: ${err instanceof Error ? err.message : String(err)}`);\n }\n process.exit(0);\n }\n\n // Handle 'remove' subcommand — stop + clean up PID and log files\n if (process.argv[2] === 'remove') {\n const { removeDaemon } = await import('./daemon.js');\n const result = await removeDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'install' subcommand — install platform service\n if (process.argv[2] === 'install') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n await svc.install();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'uninstall' subcommand — uninstall platform service\n if (process.argv[2] === 'uninstall') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n svc.uninstall();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'gui' subcommand\n if (process.argv[2] === 'gui') {\n const { launchGui } = await import('./gui-launcher.js');\n await launchGui();\n process.exit(0);\n }\n\n // Handle 'reload' subcommand\n if (process.argv[2] === 'reload') {\n const { reloadDaemon } = await import('./daemon.js');\n await reloadDaemon(args.port);\n process.exit(0);\n }\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n // Load config\n let config;\n let configPath;\n try {\n const result = loadConfig(args.config);\n config = result.config;\n configPath = result.configPath;\n } catch (error) {\n console.error(`Config error: ${(error as Error).message}`);\n process.exit(1);\n }\n\n // CLI port override\n const port = args.port || config.server.port;\n const host = config.server.host;\n const logLevel: LogLevel = args.verbose ? \"debug\" : \"info\";\n\n // Initialize metrics store\n const metricsStore = new MetricsStore();\n\n // --- Monitor mode (spawns daemon child, auto-restarts on crash) ---\n if (args.monitor) {\n await startMonitor(args);\n return;\n }\n\n // --- Daemon mode ---\n if (args.daemon) {\n const { removeWorkerPidFile, writeWorkerPidFile, createDebouncedReload, getLogPath } = await import('./daemon.js');\n const { reloadConfig } = await import('./config.js');\n const { createWriteStream, watch } = await import('node:fs');\n const { createLogger } = await import('./logger.js');\n const logger = createLogger(logLevel);\n\n // Prevent silent crashes from killing the daemon worker\n process.on('uncaughtException', (err) => {\n logger.error('Uncaught exception (daemon survived)', { error: err.message, stack: err.stack });\n });\n process.on('unhandledRejection', (reason) => {\n logger.error('Unhandled rejection (daemon survived)', { reason: String(reason) });\n });\n\n // Write worker PID file (monitor owns modelweaver.pid)\n await writeWorkerPidFile(process.pid);\n\n // Redirect stdout/stderr to log file\n const logStream = createWriteStream(getLogPath(), { flags: 'a' });\n logStream.on('error', () => { /* ignore write errors to log file */ });\n process.stdout.write = logStream.write.bind(logStream) as typeof process.stdout.write;\n process.stderr.write = logStream.write.bind(logStream) as typeof process.stderr.write;\n\n // Create app with mutable config\n const handle = createApp(config, logLevel, metricsStore);\n\n // Hot-reload: watch config file for changes\n let configWatcher: ReturnType<typeof watch> | null = null;\n if (configPath) {\n const debounced = createDebouncedReload(() => {\n try {\n const newConfig = reloadConfig(configPath);\n handle.setConfig(newConfig);\n logger.info(\"Config reloaded\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed — keeping old config\", { error: (err as Error).message });\n }\n }, 300);\n\n try {\n configWatcher = watch(configPath, () => {\n debounced.reload();\n });\n configWatcher.on('error', () => {\n // fs.watch failed — silently disable hot-reload\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n });\n } catch {\n // fs.watch not available — hot-reload disabled\n }\n }\n\n // SIGUSR1 triggers config hot-reload\n // Note: SIGUSR1 is POSIX-only; this handler is a no-op on Windows.\n process.on('SIGUSR1', () => {\n try {\n const newConfig = reloadConfig(configPath!);\n handle.setConfig(newConfig);\n logger.info(\"Config reloaded (SIGUSR1)\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed (SIGUSR1)\", { error: (err as Error).message });\n }\n });\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = async () => {\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n await removeWorkerPidFile();\n logStream.end();\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return; // Don't fall through to foreground mode\n }\n\n // --- Foreground mode ---\n const handle = createApp(config, logLevel, metricsStore);\n\n // Print startup info\n console.log(`\\n ModelWeaver v${VERSION}`);\n console.log(` Listening: http://${host}:${port}`);\n console.log(` Config: ${configPath}\\n`);\n\n console.log(\" Routes:\");\n for (const [tier, entries] of config.routing) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${tier.padEnd(8)} → ${providerList}`);\n }\n console.log();\n\n if (config.modelRouting.size > 0) {\n console.log(\" Model Routes:\");\n for (const [model, entries] of config.modelRouting) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${model.padEnd(20)} → ${providerList}`);\n }\n console.log();\n }\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nmain();\n","// src/server.ts\nimport { Hono } from \"hono\";\nimport { resolveRequest, clearRoutingCache } from \"./router.js\";\nimport { forwardWithFallback } from \"./proxy.js\";\nimport { createLogger, type LogLevel } from \"./logger.js\";\nimport type { AppConfig, ProviderConfig, RequestContext } from \"./types.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nimport type { MetricsStore } from \"./metrics.js\";\nimport { broadcastStreamEvent } from \"./ws.js\";\nimport type { StreamEvent } from \"./types.js\";\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp + cacheRead + cacheCreation, outputTokens: out };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine || !dataLine.includes('\"usage\"')) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n };\n\n const recordMetrics = (inp: number, out: number) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n });\n\n // Broadcast completion event\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (now - lastStreamEmit >= STREAM_THROTTLE_MS && tokens.output > 0) {\n lastStreamEmit = now;\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (nowJson - lastStreamEmit >= STREAM_THROTTLE_MS && outputTokens > 0) {\n lastStreamEmit = nowJson;\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n });\n });\n }\n\n if (isFinal) {\n const totalInput = inputTokens + cacheReadTokens + cacheCreationTokens;\n recordMetrics(totalInput, outputTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n cancel() {\n // Stream was cancelled (client disconnected) — emit metrics with what we have\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/** Headers forwarded as-is to upstream */\nconst FORWARD_HEADERS = new Set([\n \"anthropic-version\",\n \"anthropic-beta\",\n \"content-type\",\n \"accept\",\n]);\n\n/** Pre-compiled regex for normalizing duplicate slashes in URL paths */\nconst MULTI_SLASH = /\\/+/g;\n\n/** Pre-compiled regex for stripping origin from URLs */\nconst STRIP_ORIGIN = /^https?:\\/\\/[^/]+/;\n\n/** Pre-compiled regexes for targeted body replacements (preserve prompt caching) */\nconst MODEL_KEY_REGEX = /\"model\"\\s*:\\s*\"([^\"]*)\"/;\nconst MAX_TOKENS_REGEX = /\"max_tokens\"\\s*:\\s*(\\d+)/;\n\n/** Module-level TextEncoder — avoids per-request allocation */\nconst textEncoder = new TextEncoder();\n\n/** Delay (ms) before starting backup providers in staggered race */\nconst SPECULATIVE_DELAY = 3000;\n\nexport function isRetriable(status: number): boolean {\n return status === 429 || status >= 500;\n}\n\nconst CONTEXT_WINDOW_PATTERNS = [\n 'context window', 'context_limit', 'token limit',\n 'prompt is too long', 'max tokens', 'input too large', 'too many tokens',\n];\n\nfunction isContextWindowError(status: number, body: string): boolean {\n if (status !== 400) return false;\n const lower = body.toLowerCase();\n return CONTEXT_WINDOW_PATTERNS.some(p => lower.includes(p));\n}\n\nfunction handleContextWindowError(status: number, body: string): Response | null {\n if (!isContextWindowError(status, body)) return null;\n\n console.warn('[context-compact] Upstream context window limit detected');\n try {\n const flagDir = path.join(os.homedir(), '.claude', 'state');\n fs.mkdirSync(flagDir, { recursive: true });\n fs.writeFileSync(path.join(flagDir, 'context-compact-needed'), Date.now().toString());\n } catch {\n // Best-effort flag write\n }\n\n const enhanced = JSON.stringify({\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: \"Context window limit reached. Run /compact to reduce conversation size, then retry.\",\n },\n });\n return new Response(enhanced, {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nexport function buildOutboundUrl(baseUrl: string, incomingPath: string): string {\n let basePath = \"\";\n let origin = baseUrl;\n const slashIndex = baseUrl.indexOf('/', baseUrl.indexOf('//') + 2);\n if (slashIndex !== -1) {\n origin = baseUrl.substring(0, slashIndex);\n basePath = baseUrl.substring(slashIndex);\n }\n\n let incomingQuery = \"\";\n let incomingOnly = incomingPath;\n const qIndex = incomingPath.indexOf('?');\n if (qIndex !== -1) {\n incomingOnly = incomingPath.substring(0, qIndex);\n incomingQuery = incomingPath.substring(qIndex);\n }\n\n // Deduplicate /v1 when base URL path already ends with it and incoming path starts with it.\n // e.g. baseUrl=\"https://api.fireworks.ai/inference/v1\" + path=\"/v1/chat/completions\"\n // → \"/inference/v1/chat/completions\" (not \"/inference/v1/v1/chat/completions\")\n let resolvedPath;\n if (basePath.endsWith('/v1') && incomingOnly.startsWith('/v1')) {\n resolvedPath = basePath + incomingOnly.substring(3);\n } else {\n resolvedPath = basePath + incomingOnly;\n }\n\n // Normalize duplicate slashes\n resolvedPath = resolvedPath.replace(MULTI_SLASH, \"/\");\n\n return origin + resolvedPath + incomingQuery;\n}\n\nexport function buildOutboundHeaders(\n incomingHeaders: Headers,\n provider: ProviderConfig,\n requestId: string\n): Headers {\n const headers = new Headers();\n\n // Forward select headers as-is\n for (const name of FORWARD_HEADERS) {\n const value = incomingHeaders.get(name);\n if (value) headers.set(name, value);\n }\n\n // Rewrite auth headers based on provider authType\n if (provider.authType === \"bearer\") {\n headers.set(\"Authorization\", `Bearer ${provider.apiKey}`);\n } else {\n headers.set(\"x-api-key\", provider.apiKey);\n }\n headers.set(\"x-request-id\", requestId);\n\n // Set host to provider hostname (use cached components when available)\n const cachedHost = provider._cachedHost;\n if (cachedHost) {\n headers.set(\"host\", cachedHost);\n } else {\n try {\n const url = new URL(provider.baseUrl);\n headers.set(\"host\", url.host);\n } catch {\n // If baseUrl is not a valid URL, skip host rewrite\n }\n }\n\n return headers;\n}\n\n/**\n * Remove orphaned tool_use/tool_result pairs from the messages array.\n *\n * In Anthropic's format:\n * - tool_use blocks live inside assistant message content: { role: \"assistant\", content: [{ type: \"tool_use\", id: \"call_xxx\", ... }] }\n * - tool_result blocks live inside user message content: { role: \"user\", content: [{ type: \"tool_result\", tool_use_id: \"call_xxx\", ... }] }\n *\n * A tool_result is orphaned if its tool_use_id references a tool_use not in any assistant content block.\n * A tool_use is orphaned if its id has no matching tool_result in any user content block.\n */\nfunction cleanOrphanedToolMessages(body: Record<string, unknown>): void {\n const messages = body.messages;\n if (!Array.isArray(messages)) return;\n\n // Pass 1: Collect tool_use IDs and tool_result IDs in a single pass,\n // and record which message indices have orphaned blocks\n const toolUseIds = new Set<string>();\n const toolResultIds = new Set<string>();\n const needsFiltering = new Map<number, \"user\" | \"assistant\">();\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!Array.isArray(msg.content)) continue;\n\n if (msg.role === \"assistant\") {\n let hasOrphan = false;\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.id) {\n toolUseIds.add(String(block.id));\n if (!toolResultIds.has(String(block.id))) hasOrphan = true;\n }\n }\n // Note: toolResultIds may not be fully populated yet, so we defer judgment\n // on assistant orphans to after the full pass.\n } else if (msg.role === \"user\") {\n let hasOrphan = false;\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && block.tool_use_id) {\n toolResultIds.add(String(block.tool_use_id));\n if (!toolUseIds.has(String(block.tool_use_id))) hasOrphan = true;\n }\n }\n if (hasOrphan) needsFiltering.set(i, \"user\");\n }\n }\n\n // Check assistant messages for orphans now that toolResultIds is complete\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n const hasOrphan = msg.content.some(\n (block: Record<string, unknown>) => block.type === \"tool_use\" && !toolResultIds.has(String(block.id))\n );\n if (hasOrphan) needsFiltering.set(i, \"assistant\");\n }\n }\n\n if (needsFiltering.size === 0) return;\n\n // Pass 2: Filter out orphaned tool references from content arrays\n body.messages = messages.map((msg: Record<string, unknown>, i: number) => {\n const filterType = needsFiltering.get(i);\n if (filterType && Array.isArray(msg.content)) {\n const filtered = msg.content.filter((block: Record<string, unknown>) => {\n if (filterType === \"user\") {\n return !(block.type === \"tool_result\" && !toolUseIds.has(String(block.tool_use_id)));\n }\n return !(block.type === \"tool_use\" && !toolResultIds.has(String(block.id)));\n });\n if (filtered.length === msg.content.length) return msg; // nothing was actually filtered\n return { ...msg, content: filtered };\n }\n return msg;\n });\n\n // Pass 3: Re-check user messages after assistant cleanup.\n // After Pass 2 removed orphaned tool_use blocks from assistant messages, some\n // user tool_result blocks may now reference tool_use IDs that no longer exist.\n // Rebuild valid IDs from the cleaned messages and strip dangling user tool_results.\n const validToolUseIds = new Set<string>();\n for (const msg of body.messages as Record<string, unknown>[]) {\n if (!Array.isArray(msg.content)) continue;\n if (msg.role === \"assistant\") {\n for (const block of msg.content as Record<string, unknown>[]) {\n if (block.type === \"tool_use\" && block.id) validToolUseIds.add(String(block.id));\n }\n }\n }\n\n body.messages = (body.messages as Record<string, unknown>[]).map((msg) => {\n if (msg.role === \"user\" && Array.isArray(msg.content)) {\n const filtered = msg.content.filter(\n (block: Record<string, unknown>) =>\n !(block.type === \"tool_result\" && !validToolUseIds.has(String(block.tool_use_id)))\n );\n if (filtered.length === msg.content.length) return msg;\n return { ...msg, content: filtered };\n }\n return msg;\n });\n}\n\n/**\n * Apply targeted string replacements to rawBody to preserve prompt caching.\n * On the primary attempt (chainIndex === 0), we avoid full JSON.stringify which\n * breaks Anthropic's cache breakpoints (position-sensitive, whitespace/order-sensitive).\n * Falls back to full JSON parse/mutate/stringify when structural changes are needed.\n */\nfunction applyTargetedReplacements(\n rawBody: string,\n entry: RoutingEntry,\n provider: ProviderConfig,\n parsed: Record<string, unknown>,\n needsOrphanClean: boolean,\n): string {\n // If orphan cleaning is needed, we must do full JSON parse (structural changes to messages)\n if (needsOrphanClean) {\n // deep clone required: cleanOrphanedToolMessages mutates the messages array in-place\n const mutable = structuredClone(parsed);\n if (entry.model) mutable.model = entry.model;\n cleanOrphanedToolMessages(mutable);\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requested = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requested > maxOutputTokens) {\n mutable.max_tokens = Math.min(requested, maxOutputTokens);\n }\n }\n return JSON.stringify(mutable);\n }\n\n // Targeted replacement path -- only model override and/or max_tokens clamping\n let body = rawBody;\n\n // Model override via regex (no JSON.parse/stringify)\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n const modelMatch = MODEL_KEY_REGEX.exec(body);\n if (modelMatch) {\n body = body.replace(MODEL_KEY_REGEX, `\"model\":\"${entry.model}\"`);\n console.warn(\n `Routing override: ${modelMatch[1]} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n // max_tokens clamping\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const maxTokensMatch = MAX_TOKENS_REGEX.exec(body);\n if (maxTokensMatch) {\n const current = parseInt(maxTokensMatch[1], 10);\n if (current > maxOutputTokens) {\n body = body.replace(MAX_TOKENS_REGEX, `\"max_tokens\":${maxOutputTokens}`);\n }\n } else if (typeof parsed.max_tokens !== \"number\") {\n // max_tokens not present in body -- need to add it. Shallow clone suffices\n // since only top-level properties (model, max_tokens) are mutated.\n const mutable = { ...parsed };\n if (entry.model) mutable.model = entry.model;\n mutable.max_tokens = maxOutputTokens;\n return JSON.stringify(mutable);\n }\n }\n\n return body;\n}\n\n/**\n * Forward a request to a single provider.\n * Uses ctx.parsedBody when available (avoids re-parsing); falls back to ctx.rawBody.\n * incomingRequest is used for metadata only (url, headers).\n * Returns the Response object — caller decides fallback logic.\n */\nexport async function forwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n externalSignal?: AbortSignal,\n chainIndex: number = 0,\n): Promise<Response> {\n const outgoingPath = incomingRequest.url.replace(STRIP_ORIGIN, \"\");\n\n // Set actualModel early so metrics always record the routed model,\n // even if body parsing or the fetch itself fails\n if (entry.model) {\n ctx.actualModel = entry.model;\n }\n\n // Build outbound URL from provider base URL and request path\n const url = buildOutboundUrl(provider.baseUrl, outgoingPath);\n\n // Prepare body — prefer raw passthrough to preserve upstream prompt caching.\n // Only parse and re-serialize when a modification is actually required,\n // because Anthropic's cache breakpoints are position-sensitive and\n // JSON.stringify changes whitespace / key order, breaking cache hits.\n let body: string;\n const contentType = incomingRequest.headers.get(\"content-type\") || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n const parsed = (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody\n ?? JSON.parse(ctx.rawBody);\n\n // Determine whether any body modification is needed\n let needsModification = false;\n\n // Check 1: Model override needed?\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n needsModification = true;\n }\n\n // Check 2: Orphan cleaning needed? (only for fallback attempts, not primary)\n // On the primary attempt (index 0), conversation history is intact.\n // Only when falling back (index > 0) do cross-provider orphans appear.\n const needsOrphanClean = chainIndex > 0;\n if (needsOrphanClean) needsModification = true;\n\n // Check 3: max_tokens clamping needed?\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof parsed.max_tokens === \"number\" ? parsed.max_tokens : maxOutputTokens;\n if (parsed.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n needsModification = true;\n }\n }\n\n if (needsModification) {\n // On primary attempt (chainIndex === 0) without orphan cleaning, use targeted\n // string replacements to preserve prompt caching. Anthropic's cache breakpoints\n // are position-sensitive -- JSON.stringify changes whitespace/order, breaking hits.\n if (chainIndex === 0 && !needsOrphanClean) {\n body = applyTargetedReplacements(ctx.rawBody, entry, provider, parsed, false);\n } else {\n // Fallback attempts: full JSON parse/mutate/stringify (caching already broken)\n // deep clone required: cleanOrphanedToolMessages may mutate the messages array in-place\n const mutable = structuredClone(parsed);\n\n if (entry.model) {\n const originalModel = mutable.model as string | undefined;\n mutable.model = entry.model;\n if (originalModel && originalModel !== entry.model) {\n console.warn(\n `Routing override: ${originalModel} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n if (needsOrphanClean) {\n cleanOrphanedToolMessages(mutable);\n }\n\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n mutable.max_tokens = Math.min(requestedMaxTokens, maxOutputTokens);\n }\n }\n\n body = JSON.stringify(mutable);\n }\n } else {\n // No modifications needed — passthrough raw body to preserve prompt caching\n body = ctx.rawBody;\n }\n } catch {\n // If body can't be parsed, send it as-is without model override\n body = ctx.rawBody;\n }\n } else {\n body = ctx.rawBody;\n }\n\n const headers = buildOutboundHeaders(incomingRequest.headers, provider, ctx.requestId);\n headers.set(\"content-length\", Buffer.byteLength(body, 'utf-8').toString());\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), provider.timeout);\n\n // TTFB timeout: fail if no response headers received within ttfbTimeout ms\n const ttfbTimeout = provider.ttfbTimeout ?? 10000;\n let ttfbTimedOut = false;\n let ttfbTimer: ReturnType<typeof setTimeout> | null = null;\n\n const ttfbPromise = new Promise<never>((_, reject) => {\n ttfbTimer = setTimeout(() => {\n ttfbTimedOut = true;\n controller.abort();\n reject(new Error(`TTFB timeout after ${ttfbTimeout}ms`));\n }, ttfbTimeout);\n });\n\n // Listen for external abort (from race cancellation) to abort this request\n if (externalSignal) {\n if (externalSignal.aborted) {\n // Already aborted — don't even start the request\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: `Provider \"${provider.name}\" cancelled by race winner` },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n const onExternalAbort = () => {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n };\n externalSignal.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n try {\n const undiciResponse = await Promise.race([\n undiciRequest(url, {\n method: \"POST\",\n headers,\n body,\n signal: controller.signal,\n dispatcher: provider._agent,\n }),\n ttfbPromise,\n ]);\n\n // TTFB succeeded — cancel TTFB timer\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n undiciResponse.body as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Network errors / timeouts — return a synthetic 502\n const message = ttfbTimedOut\n ? `Provider \"${provider.name}\" timed out waiting for first byte after ${ttfbTimeout}ms`\n : error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n}\n\n/**\n * Race multiple providers simultaneously. Returns the first successful response.\n * Aborts all remaining requests once a winner is found.\n */\nasync function raceProviders(\n chain: RoutingEntry[],\n providers: Map<string, ProviderConfig>,\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void },\n chainOffset: number = 0,\n): Promise<Response> {\n const sharedController = new AbortController();\n\n const races = chain.map(async (entry, index): Promise<{ response: Response; index: number }> => {\n const provider = providers.get(entry.provider);\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n // Check circuit breaker\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, sharedController.signal, index + chainOffset);\n // Record for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status);\n }\n return { response, index };\n } catch {\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(502);\n }\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n });\n\n // Track completed promises to avoid double-processing\n const completed = new Set<Promise<{ response: Response; index: number }>>();\n const failures: { response: Response; index: number }[] = [];\n\n try {\n while (completed.size < races.length) {\n const pending = races.filter(r => !completed.has(r));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(races[winner.index] ?? races[0]);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n // Cancel bodies of already-completed losing responses to free resources\n for (const f of failures) {\n try { f.response.body?.cancel(); } catch { /* ignore */ }\n }\n return winner.response;\n }\n\n // Non-retriable error — check for context window limit before propagating\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n // Retriable but not success — record and continue waiting\n failures.push(winner);\n }\n\n // All providers returned retriable errors — return the first failure\n sharedController.abort();\n if (failures.length > 0) {\n return failures[0].response;\n }\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n // Single provider — no racing needed\n if (chain.length <= 1) {\n const entry = chain[0];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n logger?.warn(\"Provider skipped by circuit breaker\", { requestId: ctx.requestId, provider: entry.provider });\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n\n onAttempt?.(entry.provider, 0);\n\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, undefined, 0);\n\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status);\n }\n\n return response;\n }\n\n // Multiple providers — staggered race\n const sharedController = new AbortController();\n const completed = new Set<number>();\n const failures: { response: Response; index: number }[] = [];\n\n async function attemptProvider(\n index: number,\n ): Promise<{ response: Response; index: number }> {\n const entry = chain[index];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n logger?.warn(\"Provider skipped by circuit breaker\", {\n requestId: ctx.requestId,\n provider: entry.provider,\n });\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await forwardRequest(\n provider,\n entry,\n ctx,\n incomingRequest,\n sharedController.signal,\n index,\n );\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(response.status);\n return { response, index };\n } catch {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n }\n\n // Build staggered race promises:\n // Provider 0 starts immediately\n // Provider 1+ start after SPECULATIVE_DELAY (if race not already won)\n const races: Promise<{ response: Response; index: number }>[] = [];\n\n for (let i = 0; i < chain.length; i++) {\n if (i === 0) {\n races.push(attemptProvider(0));\n } else {\n races.push(\n new Promise<{ response: Response; index: number }>((resolve) => {\n setTimeout(() => {\n if (sharedController.signal.aborted) {\n // Race already won — resolve with a cancelled placeholder\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: \"Cancelled by race winner\" },\n });\n resolve({\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index: i,\n });\n return;\n }\n attemptProvider(i).then(resolve);\n }, SPECULATIVE_DELAY);\n }),\n );\n }\n }\n\n // Pick winner — same logic as raceProviders\n try {\n while (completed.size < races.length) {\n const pending = races.filter((_, idx) => !completed.has(idx));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.index);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n for (const f of failures) {\n try {\n f.response.body?.cancel();\n } catch {\n /* ignore */\n }\n }\n return winner.response;\n }\n\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n failures.push(winner);\n }\n\n sharedController.abort();\n if (failures.length > 0) return failures[0].response;\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\n\nlet wssInstance: InstanceType<typeof import(\"ws\").WebSocketServer> | null = null;\n\nexport function attachWebSocket(server: Server, metricsStore: MetricsStore): void {\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n wssInstance = wss;\n\n wss.on(\"connection\", (ws) => {\n // Send current summary as initial state\n const summary = metricsStore.getSummary();\n const initialMsg: WsMessage = { type: \"summary\", data: summary };\n ws.send(JSON.stringify(initialMsg));\n\n let pendingSummaryTimer: ReturnType<typeof setTimeout> | undefined;\n let missedPongs = 0;\n const alive = () => ws.readyState === ws.OPEN;\n\n // Subscribe to new metrics with backpressure check and debounced summary\n const unsubscribe = metricsStore.onRecord((metrics: RequestMetrics) => {\n if (!alive()) return;\n\n // Backpressure: skip send if outbound buffer is too large\n if (ws.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n // Schedule a summary update instead so the client eventually catches up\n scheduleSummaryUpdate();\n return;\n }\n\n // Defer JSON.stringify + send off the critical path\n setImmediate(() => {\n if (!alive()) return;\n const msg: WsMessage = { type: \"request\", data: metrics };\n ws.send(JSON.stringify(msg));\n });\n\n scheduleSummaryUpdate();\n });\n\n function scheduleSummaryUpdate(): void {\n if (pendingSummaryTimer) return; // already scheduled\n pendingSummaryTimer = setTimeout(() => {\n pendingSummaryTimer = undefined;\n if (!alive()) return;\n const msg: WsMessage = { type: \"summary\", data: metricsStore.getSummary() };\n ws.send(JSON.stringify(msg));\n }, SUMMARY_DEBOUNCE_MS);\n }\n\n // Ping/pong heartbeat for liveness tracking\n const pingTimer = setInterval(() => {\n if (!alive()) {\n clearInterval(pingTimer);\n return;\n }\n // Terminate if client missed too many pongs\n if (missedPongs >= MAX_MISSED_PONGS) {\n cleanup(); // ensure timers and subscriber are cleaned up\n ws.terminate();\n return;\n }\n ws.ping();\n missedPongs++;\n }, PING_INTERVAL_MS);\n\n ws.on(\"pong\", () => {\n missedPongs = 0; // reset on successful pong\n });\n\n let cleanedUp = false;\n const cleanup = () => {\n if (cleanedUp) return;\n cleanedUp = true;\n clearInterval(pingTimer);\n if (pendingSummaryTimer) clearTimeout(pendingSummaryTimer);\n unsubscribe();\n };\n\n ws.on(\"close\", cleanup);\n ws.on(\"error\", cleanup);\n });\n}\n\nexport function broadcastStreamEvent(data: StreamEvent): void {\n if (!wssInstance) return;\n const msg = JSON.stringify({ type: \"stream\", data });\n for (const client of wssInstance.clients) {\n if (client.readyState === client.OPEN) {\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":";6IACA,OAAS,SAAAA,OAAa,oBACtB,OAAS,gBAAAC,OAAoB,KCD7B,OAAS,QAAAC,OAAY,OCarB,IAAMC,EAAe,IAAI,IAKlB,SAASC,GAA0B,CACxCD,EAAa,MAAM,CACrB,CAMO,SAASE,GACdC,EACAC,EACe,CACf,OAAW,CAACC,EAAMC,CAAQ,IAAKF,EAC7B,QAAWG,KAAWD,EACpB,GAAIH,EAAU,SAASI,CAAO,EAC5B,OAAOF,EAIb,OAAO,IACT,CAKO,SAASG,GACdH,EACAI,EACgB,CAChB,OAAOA,EAAQ,IAAIJ,CAAI,GAAK,CAAC,CAC/B,CASO,SAASK,EACdC,EACAC,EACAC,EACAC,EACuB,CAEvB,IAAMC,EAASf,EAAa,IAAIW,CAAK,EACrC,GAAII,EAEF,OAAAf,EAAa,OAAOW,CAAK,EACzBX,EAAa,IAAIW,EAAOI,CAAM,EACvB,CACL,UAAAH,EACA,MAAAD,EACA,KAAMI,EAAO,KACb,cAAeA,EAAO,cACtB,UAAW,KAAK,IAAI,EACpB,QAAAD,CACF,EAGF,IAAIT,EACAW,EAGEC,EAAaJ,EAAO,aAAa,IAAIF,CAAK,EAChD,GAAIM,GAAcA,EAAW,OAAS,EACpCZ,EAAO,iBACPW,EAAgBC,MACX,CAEL,IAAMC,EAAchB,GAAUS,EAAOE,EAAO,YAAY,EACxD,GAAI,CAACK,EAAa,OAAO,KACzBb,EAAOa,EACPF,EAAgBR,GAAkBH,EAAMQ,EAAO,OAAO,CACxD,CAGA,GAAIb,EAAa,MAAQ,IAAwB,CAE/C,IAAMmB,EAAYnB,EAAa,KAAK,EAAE,KAAK,EAAE,MACzCmB,IAAc,QAAWnB,EAAa,OAAOmB,CAAS,CAC5D,CACA,OAAAnB,EAAa,IAAIW,EAAO,CAAE,KAAAN,EAAM,cAAAW,CAAc,CAAC,EAExC,CACL,UAAAJ,EACA,MAAAD,EACA,KAAAN,EACA,cAAAW,EACA,UAAW,KAAK,IAAI,EACpB,QAAAF,CACF,CACF,CC9GA,OAAS,WAAWM,OAAqB,SACzC,OAAOC,MAAQ,KACf,OAAOC,MAAU,OACjB,OAAOC,OAAQ,KAGf,IAAMC,GAAkB,IAAI,IAAI,CAC9B,oBACA,iBACA,eACA,QACF,CAAC,EAGKC,GAAc,OAGdC,GAAe,oBAGfC,EAAkB,0BAClBC,EAAmB,2BAGnBC,EAAc,IAAI,YAGlBC,GAAoB,IAEnB,SAASC,GAAYC,EAAyB,CACnD,OAAOA,IAAW,KAAOA,GAAU,GACrC,CAEA,IAAMC,GAA0B,CAC9B,iBAAkB,gBAAiB,cACnC,qBAAsB,aAAc,kBAAmB,iBACzD,EAEA,SAASC,GAAqBF,EAAgBG,EAAuB,CACnE,GAAIH,IAAW,IAAK,MAAO,GAC3B,IAAMI,EAAQD,EAAK,YAAY,EAC/B,OAAOF,GAAwB,KAAKI,GAAKD,EAAM,SAASC,CAAC,CAAC,CAC5D,CAEA,SAASC,GAAyBN,EAAgBG,EAA+B,CAC/E,GAAI,CAACD,GAAqBF,EAAQG,CAAI,EAAG,OAAO,KAEhD,QAAQ,KAAK,0DAA0D,EACvE,GAAI,CACF,IAAMI,EAAUjB,EAAK,KAAKC,GAAG,QAAQ,EAAG,UAAW,OAAO,EAC1DF,EAAG,UAAUkB,EAAS,CAAE,UAAW,EAAK,CAAC,EACzClB,EAAG,cAAcC,EAAK,KAAKiB,EAAS,wBAAwB,EAAG,KAAK,IAAI,EAAE,SAAS,CAAC,CACtF,MAAQ,CAER,CAEA,IAAMC,EAAW,KAAK,UAAU,CAC9B,KAAM,QACN,MAAO,CACL,KAAM,wBACN,QAAS,qFACX,CACF,CAAC,EACD,OAAO,IAAI,SAASA,EAAU,CAC5B,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAChD,CAAC,CACH,CAEO,SAASC,GAAiBC,EAAiBC,EAA8B,CAC9E,IAAIC,EAAW,GACXC,EAASH,EACPI,EAAaJ,EAAQ,QAAQ,IAAKA,EAAQ,QAAQ,IAAI,EAAI,CAAC,EAC7DI,IAAe,KACjBD,EAASH,EAAQ,UAAU,EAAGI,CAAU,EACxCF,EAAWF,EAAQ,UAAUI,CAAU,GAGzC,IAAIC,EAAgB,GAChBC,EAAeL,EACbM,EAASN,EAAa,QAAQ,GAAG,EACnCM,IAAW,KACbD,EAAeL,EAAa,UAAU,EAAGM,CAAM,EAC/CF,EAAgBJ,EAAa,UAAUM,CAAM,GAM/C,IAAIC,EACJ,OAAIN,EAAS,SAAS,KAAK,GAAKI,EAAa,WAAW,KAAK,EAC3DE,EAAeN,EAAWI,EAAa,UAAU,CAAC,EAElDE,EAAeN,EAAWI,EAI5BE,EAAeA,EAAa,QAAQzB,GAAa,GAAG,EAE7CoB,EAASK,EAAeH,CACjC,CAEO,SAASI,GACdC,EACAC,EACAC,EACS,CACT,IAAMC,EAAU,IAAI,QAGpB,QAAWC,KAAQhC,GAAiB,CAClC,IAAMiC,EAAQL,EAAgB,IAAII,CAAI,EAClCC,GAAOF,EAAQ,IAAIC,EAAMC,CAAK,CACpC,CAGIJ,EAAS,WAAa,SACxBE,EAAQ,IAAI,gBAAiB,UAAUF,EAAS,MAAM,EAAE,EAExDE,EAAQ,IAAI,YAAaF,EAAS,MAAM,EAE1CE,EAAQ,IAAI,eAAgBD,CAAS,EAGrC,IAAMI,EAAaL,EAAS,YAC5B,GAAIK,EACFH,EAAQ,IAAI,OAAQG,CAAU,MAE9B,IAAI,CACF,IAAMC,EAAM,IAAI,IAAIN,EAAS,OAAO,EACpCE,EAAQ,IAAI,OAAQI,EAAI,IAAI,CAC9B,MAAQ,CAER,CAGF,OAAOJ,CACT,CAYA,SAASK,EAA0BzB,EAAqC,CACtE,IAAM0B,EAAW1B,EAAK,SACtB,GAAI,CAAC,MAAM,QAAQ0B,CAAQ,EAAG,OAI9B,IAAMC,EAAa,IAAI,IACjBC,EAAgB,IAAI,IACpBC,EAAiB,IAAI,IAE3B,QAASC,EAAI,EAAGA,EAAIJ,EAAS,OAAQI,IAAK,CACxC,IAAMC,EAAML,EAASI,CAAC,EACtB,GAAK,MAAM,QAAQC,EAAI,OAAO,GAE9B,GAAIA,EAAI,OAAS,YAAa,CAC5B,IAAIC,EAAY,GAChB,QAAWC,KAASF,EAAI,QAClBE,EAAM,OAAS,YAAcA,EAAM,KACrCN,EAAW,IAAI,OAAOM,EAAM,EAAE,CAAC,EAC1BL,EAAc,IAAI,OAAOK,EAAM,EAAE,CAAC,IAAGD,EAAY,IAK5D,SAAWD,EAAI,OAAS,OAAQ,CAC9B,IAAIC,EAAY,GAChB,QAAWC,KAASF,EAAI,QAClBE,EAAM,OAAS,eAAiBA,EAAM,cACxCL,EAAc,IAAI,OAAOK,EAAM,WAAW,CAAC,EACtCN,EAAW,IAAI,OAAOM,EAAM,WAAW,CAAC,IAAGD,EAAY,KAG5DA,GAAWH,EAAe,IAAIC,EAAG,MAAM,CAC7C,EACF,CAGA,QAASA,EAAI,EAAGA,EAAIJ,EAAS,OAAQI,IAAK,CACxC,IAAMC,EAAML,EAASI,CAAC,EAClBC,EAAI,OAAS,aAAe,MAAM,QAAQA,EAAI,OAAO,GACrCA,EAAI,QAAQ,KAC3BE,GAAmCA,EAAM,OAAS,YAAc,CAACL,EAAc,IAAI,OAAOK,EAAM,EAAE,CAAC,CACtG,GACeJ,EAAe,IAAIC,EAAG,WAAW,CAEpD,CAEA,GAAID,EAAe,OAAS,EAAG,OAG/B7B,EAAK,SAAW0B,EAAS,IAAI,CAACK,EAA8BD,IAAc,CACxE,IAAMI,EAAaL,EAAe,IAAIC,CAAC,EACvC,GAAII,GAAc,MAAM,QAAQH,EAAI,OAAO,EAAG,CAC5C,IAAMI,EAAWJ,EAAI,QAAQ,OAAQE,GAC/BC,IAAe,OACV,EAAED,EAAM,OAAS,eAAiB,CAACN,EAAW,IAAI,OAAOM,EAAM,WAAW,CAAC,GAE7E,EAAEA,EAAM,OAAS,YAAc,CAACL,EAAc,IAAI,OAAOK,EAAM,EAAE,CAAC,EAC1E,EACD,OAAIE,EAAS,SAAWJ,EAAI,QAAQ,OAAeA,EAC5C,CAAE,GAAGA,EAAK,QAASI,CAAS,CACrC,CACA,OAAOJ,CACT,CAAC,EAMD,IAAMK,EAAkB,IAAI,IAC5B,QAAWL,KAAO/B,EAAK,SACrB,GAAK,MAAM,QAAQ+B,EAAI,OAAO,GAC1BA,EAAI,OAAS,YACf,QAAWE,KAASF,EAAI,QAClBE,EAAM,OAAS,YAAcA,EAAM,IAAIG,EAAgB,IAAI,OAAOH,EAAM,EAAE,CAAC,EAKrFjC,EAAK,SAAYA,EAAK,SAAuC,IAAK+B,GAAQ,CACxE,GAAIA,EAAI,OAAS,QAAU,MAAM,QAAQA,EAAI,OAAO,EAAG,CACrD,IAAMI,EAAWJ,EAAI,QAAQ,OAC1BE,GACC,EAAEA,EAAM,OAAS,eAAiB,CAACG,EAAgB,IAAI,OAAOH,EAAM,WAAW,CAAC,EACpF,EACA,OAAIE,EAAS,SAAWJ,EAAI,QAAQ,OAAeA,EAC5C,CAAE,GAAGA,EAAK,QAASI,CAAS,CACrC,CACA,OAAOJ,CACT,CAAC,CACH,CAQA,SAASM,GACPC,EACAC,EACArB,EACAsB,EACAC,EACQ,CAER,GAAIA,EAAkB,CAEpB,IAAMC,EAAU,gBAAgBF,CAAM,EAGtC,GAFID,EAAM,QAAOG,EAAQ,MAAQH,EAAM,OACvCd,EAA0BiB,CAAO,EAC7BxB,EAAS,YAAa,CACxB,GAAM,CAAE,gBAAAyB,CAAgB,EAAIzB,EAAS,YAC/B0B,EAAY,OAAOF,EAAQ,YAAe,SAAWA,EAAQ,WAAaC,GAC5ED,EAAQ,aAAe,QAAaE,EAAYD,KAClDD,EAAQ,WAAa,KAAK,IAAIE,EAAWD,CAAe,EAE5D,CACA,OAAO,KAAK,UAAUD,CAAO,CAC/B,CAGA,IAAI1C,EAAOsC,EAGX,GAAIC,EAAM,OAAUC,EAAO,QAAiCD,EAAM,MAAO,CACvE,IAAMM,EAAarD,EAAgB,KAAKQ,CAAI,EACxC6C,IACF7C,EAAOA,EAAK,QAAQR,EAAiB,YAAY+C,EAAM,KAAK,GAAG,EAC/D,QAAQ,KACN,qBAAqBM,EAAW,CAAC,CAAC,OAAON,EAAM,KAAK,QAAQrB,EAAS,IAAI,EAC3E,EAEJ,CAGA,GAAIA,EAAS,YAAa,CACxB,GAAM,CAAE,gBAAAyB,CAAgB,EAAIzB,EAAS,YAC/B4B,EAAiBrD,EAAiB,KAAKO,CAAI,EACjD,GAAI8C,EACc,SAASA,EAAe,CAAC,EAAG,EAAE,EAChCH,IACZ3C,EAAOA,EAAK,QAAQP,EAAkB,gBAAgBkD,CAAe,EAAE,WAEhE,OAAOH,EAAO,YAAe,SAAU,CAGhD,IAAME,EAAU,CAAE,GAAGF,CAAO,EAC5B,OAAID,EAAM,QAAOG,EAAQ,MAAQH,EAAM,OACvCG,EAAQ,WAAaC,EACd,KAAK,UAAUD,CAAO,CAC/B,CACF,CAEA,OAAO1C,CACT,CAQA,eAAsB+C,EACpB7B,EACAqB,EACAS,EACAC,EACAC,EACAC,EAAqB,EACF,CACnB,IAAMC,EAAeH,EAAgB,IAAI,QAAQ1D,GAAc,EAAE,EAI7DgD,EAAM,QACRS,EAAI,YAAcT,EAAM,OAI1B,IAAMf,EAAMlB,GAAiBY,EAAS,QAASkC,CAAY,EAMvDpD,EAGJ,IAFoBiD,EAAgB,QAAQ,IAAI,cAAc,GAAK,IAEnD,SAAS,kBAAkB,EACzC,GAAI,CACF,IAAMT,EAAUQ,EAAkE,YAC7E,KAAK,MAAMA,EAAI,OAAO,EAGvBK,EAAoB,GAGpBd,EAAM,OAAUC,EAAO,QAAiCD,EAAM,QAChEc,EAAoB,IAMtB,IAAMZ,EAAmBU,EAAa,EAItC,GAHIV,IAAkBY,EAAoB,IAGtCnC,EAAS,YAAa,CACxB,GAAM,CAAE,gBAAAyB,CAAgB,EAAIzB,EAAS,YAC/BoC,EAAqB,OAAOd,EAAO,YAAe,SAAWA,EAAO,WAAaG,GACnFH,EAAO,aAAe,QAAac,EAAqBX,KAC1DU,EAAoB,GAExB,CAEA,GAAIA,EAIF,GAAIF,IAAe,GAAK,CAACV,EACvBzC,EAAOqC,GAA0BW,EAAI,QAAST,EAAOrB,EAAUsB,EAAQ,EAAK,MACvE,CAGL,IAAME,EAAU,gBAAgBF,CAAM,EAEtC,GAAID,EAAM,MAAO,CACf,IAAMgB,EAAgBb,EAAQ,MAC9BA,EAAQ,MAAQH,EAAM,MAClBgB,GAAiBA,IAAkBhB,EAAM,OAC3C,QAAQ,KACN,qBAAqBgB,CAAa,OAAOhB,EAAM,KAAK,QAAQrB,EAAS,IAAI,EAC3E,CAEJ,CAMA,GAJIuB,GACFhB,EAA0BiB,CAAO,EAG/BxB,EAAS,YAAa,CACxB,GAAM,CAAE,gBAAAyB,CAAgB,EAAIzB,EAAS,YAC/BoC,EAAqB,OAAOZ,EAAQ,YAAe,SAAWA,EAAQ,WAAaC,GACrFD,EAAQ,aAAe,QAAaY,EAAqBX,KAC3DD,EAAQ,WAAa,KAAK,IAAIY,EAAoBX,CAAe,EAErE,CAEA3C,EAAO,KAAK,UAAU0C,CAAO,CAC/B,MAGA1C,EAAOgD,EAAI,OAEf,MAAQ,CAENhD,EAAOgD,EAAI,OACb,MAEAhD,EAAOgD,EAAI,QAGb,IAAM5B,EAAUJ,GAAqBiC,EAAgB,QAAS/B,EAAU8B,EAAI,SAAS,EACrF5B,EAAQ,IAAI,iBAAkB,OAAO,WAAWpB,EAAM,OAAO,EAAE,SAAS,CAAC,EAEzE,IAAMwD,EAAa,IAAI,gBACjBC,EAAU,WAAW,IAAMD,EAAW,MAAM,EAAGtC,EAAS,OAAO,EAG/DwC,EAAcxC,EAAS,aAAe,IACxCyC,EAAe,GACfC,EAAkD,KAEhDC,EAAc,IAAI,QAAe,CAACC,EAAGC,IAAW,CACpDH,EAAY,WAAW,IAAM,CAC3BD,EAAe,GACfH,EAAW,MAAM,EACjBO,EAAO,IAAI,MAAM,sBAAsBL,CAAW,IAAI,CAAC,CACzD,EAAGA,CAAW,CAChB,CAAC,EAGD,GAAIR,EAAgB,CAClB,GAAIA,EAAe,QAAS,CAE1B,aAAaO,CAAO,EAChBG,GAAW,aAAaA,CAAS,EACrC,IAAM5D,EAAO,KAAK,UAAU,CACxB,KAAM,QACN,MAAO,CAAE,KAAM,mBAAoB,QAAS,aAAakB,EAAS,IAAI,4BAA6B,CACrG,CAAC,EACH,OAAO,IAAI,SAASlB,EAAM,CACxB,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBN,EAAY,OAAOM,CAAI,EAAE,WAAW,SAAS,CACjE,CACF,CAAC,CACH,CACA,IAAMgE,EAAkB,IAAM,CAC5B,aAAaP,CAAO,EAChBG,GAAW,aAAaA,CAAS,CACvC,EACAV,EAAe,iBAAiB,QAASc,EAAiB,CAAE,KAAM,EAAK,CAAC,CAC1E,CAEA,GAAI,CACF,IAAMC,EAAiB,MAAM,QAAQ,KAAK,CACxChF,GAAcuC,EAAK,CACjB,OAAQ,OACR,QAAAJ,EACA,KAAApB,EACA,OAAQwD,EAAW,OACnB,WAAYtC,EAAS,MACvB,CAAC,EACD2C,CACF,CAAC,EAGGD,GAAW,aAAaA,CAAS,EAGrC,IAAMM,EAAW,IAAI,SACnBD,EAAe,KACf,CACE,OAAQA,EAAe,WACvB,QAASA,EAAe,OAC1B,CACF,EAEA,oBAAaR,CAAO,EACbS,CACT,OAASC,EAAO,CACd,aAAaV,CAAO,EAChBG,GAAW,aAAaA,CAAS,EAGrC,IAAMQ,EAAUT,EACZ,aAAazC,EAAS,IAAI,4CAA4CwC,CAAW,KACjFS,aAAiB,cAAgBA,EAAM,OAAS,aAC9C,aAAajD,EAAS,IAAI,qBAAqBA,EAAS,OAAO,KAC/D,aAAaA,EAAS,IAAI,wBAAyBiD,EAAgB,OAAO,GAE1EnE,EAAO,KAAK,UAAU,CACxB,KAAM,QACN,MAAO,CAAE,KAAM,mBAAoB,QAAAoE,CAAQ,CAC7C,CAAC,EACH,OAAO,IAAI,SAASpE,EAAM,CACxB,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBN,EAAY,OAAOM,CAAI,EAAE,WAAW,SAAS,CACjE,CACF,CAAC,CACH,CACF,CAwJA,eAAsBqE,EACpBC,EACAC,EACAC,EACAC,EACAC,EACAC,EACmB,CAEnB,GAAIJ,EAAM,QAAU,EAAG,CACrB,IAAMK,EAAQL,EAAM,CAAC,EACfM,EAAWP,EAAU,IAAIM,EAAM,QAAQ,EAE7C,GAAI,CAACC,EAAU,CACb,IAAMC,EAAU,KAAK,UAAU,CAC3B,KAAM,QACN,MAAO,CAAE,KAAM,YAAa,QAAS,qBAAqBF,EAAM,QAAQ,EAAG,CAC7E,CAAC,EACH,OAAO,IAAI,SAASE,EAAS,CAC3B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,CACH,CAEA,GAAID,EAAS,iBAAmB,CAACA,EAAS,gBAAgB,WAAW,EAAG,CACtEF,GAAQ,KAAK,sCAAuC,CAAE,UAAWH,EAAI,UAAW,SAAUI,EAAM,QAAS,CAAC,EAC1G,IAAME,EAAU,KAAK,UAAU,CAC3B,KAAM,QACN,MAAO,CAAE,KAAM,YAAa,QAAS,aAAaF,EAAM,QAAQ,8BAA+B,CACjG,CAAC,EACH,OAAO,IAAI,SAASE,EAAS,CAC3B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,CACH,CAEAJ,IAAYE,EAAM,SAAU,CAAC,EAE7B,IAAMI,EAAW,MAAMC,EAAeJ,EAAUD,EAAOJ,EAAKC,EAAiB,OAAW,CAAC,EAEzF,OAAII,EAAS,iBACXA,EAAS,gBAAgB,aAAaG,EAAS,MAAM,EAGhDA,CACT,CAGA,IAAME,EAAmB,IAAI,gBACvBC,EAAY,IAAI,IAChBC,EAAoD,CAAC,EAE3D,eAAeC,EACbC,EACgD,CAChD,IAAMV,EAAQL,EAAMe,CAAK,EACnBT,EAAWP,EAAU,IAAIM,EAAM,QAAQ,EAE7C,GAAI,CAACC,EAAU,CACb,IAAMC,EAAU,KAAK,UAAU,CAC7B,KAAM,QACN,MAAO,CAAE,KAAM,YAAa,QAAS,qBAAqBF,EAAM,QAAQ,EAAG,CAC7E,CAAC,EACD,MAAO,CACL,SAAU,IAAI,SAASE,EAAS,CAC9B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,EACD,MAAAQ,CACF,CACF,CAEA,GAAIT,EAAS,iBAAmB,CAACA,EAAS,gBAAgB,WAAW,EAAG,CACtEF,GAAQ,KAAK,sCAAuC,CAClD,UAAWH,EAAI,UACf,SAAUI,EAAM,QAClB,CAAC,EACD,IAAME,EAAU,KAAK,UAAU,CAC7B,KAAM,QACN,MAAO,CAAE,KAAM,YAAa,QAAS,aAAaF,EAAM,QAAQ,8BAA+B,CACjG,CAAC,EACD,MAAO,CACL,SAAU,IAAI,SAASE,EAAS,CAC9B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,EACD,MAAAQ,CACF,CACF,CAEAZ,IAAYE,EAAM,SAAUU,CAAK,EAEjC,GAAI,CACF,IAAMN,EAAW,MAAMC,EACrBJ,EACAD,EACAJ,EACAC,EACAS,EAAiB,OACjBI,CACF,EACA,OAAIT,EAAS,iBAAiBA,EAAS,gBAAgB,aAAaG,EAAS,MAAM,EAC5E,CAAE,SAAAA,EAAU,MAAAM,CAAM,CAC3B,MAAQ,CACFT,EAAS,iBAAiBA,EAAS,gBAAgB,aAAa,GAAG,EACvE,IAAMC,EAAU,KAAK,UAAU,CAC7B,KAAM,QACN,MAAO,CAAE,KAAM,YAAa,QAAS,aAAaF,EAAM,QAAQ,UAAW,CAC7E,CAAC,EACD,MAAO,CACL,SAAU,IAAI,SAASE,EAAS,CAC9B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,EACD,MAAAQ,CACF,CACF,CACF,CAKA,IAAMC,EAA0D,CAAC,EAEjE,QAASC,EAAI,EAAGA,EAAIjB,EAAM,OAAQiB,IAC5BA,IAAM,EACRD,EAAM,KAAKF,EAAgB,CAAC,CAAC,EAE7BE,EAAM,KACJ,IAAI,QAAgDE,GAAY,CAC9D,WAAW,IAAM,CACf,GAAIP,EAAiB,OAAO,QAAS,CAEnC,IAAMJ,EAAU,KAAK,UAAU,CAC7B,KAAM,QACN,MAAO,CAAE,KAAM,YAAa,QAAS,0BAA2B,CAClE,CAAC,EACDW,EAAQ,CACN,SAAU,IAAI,SAASX,EAAS,CAC9B,OAAQ,IACR,QAAS,CAAE,eAAgB,kBAAmB,CAChD,CAAC,EACD,MAAOU,CACT,CAAC,EACD,MACF,CACAH,EAAgBG,CAAC,EAAE,KAAKC,CAAO,CACjC,EAAGC,EAAiB,CACtB,CAAC,CACH,EAKJ,GAAI,CACF,KAAOP,EAAU,KAAOI,EAAM,QAAQ,CACpC,IAAMI,EAAUJ,EAAM,OAAO,CAACK,EAAGC,IAAQ,CAACV,EAAU,IAAIU,CAAG,CAAC,EAC5D,GAAIF,EAAQ,SAAW,EAAG,MAE1B,IAAMG,EAAS,MAAM,QAAQ,KAAKH,CAAO,EAGzC,GAFAR,EAAU,IAAIW,EAAO,KAAK,EAEtBA,EAAO,SAAS,QAAU,KAAOA,EAAO,SAAS,OAAS,IAAK,CACjEZ,EAAiB,MAAM,EACvB,QAAW,KAAKE,EACd,GAAI,CACF,EAAE,SAAS,MAAM,OAAO,CAC1B,MAAQ,CAER,CAEF,OAAOU,EAAO,QAChB,CAEA,GAAI,CAACC,GAAYD,EAAO,SAAS,MAAM,EAAG,CAExC,GADAZ,EAAiB,MAAM,EACnBY,EAAO,SAAS,SAAW,KAAOA,EAAO,SAAS,KACpD,GAAI,CACF,IAAMhB,EAAU,MAAMgB,EAAO,SAAS,KAAK,EACrCE,EAAUC,GAAyBH,EAAO,SAAS,OAAQhB,CAAO,EACxE,OAAIkB,GACG,IAAI,SAASlB,EAAS,CAC3B,OAAQgB,EAAO,SAAS,OACxB,WAAYA,EAAO,SAAS,WAC5B,QAASA,EAAO,SAAS,OAC3B,CAAC,CACH,MAAQ,CACN,OAAOA,EAAO,QAChB,CAEF,OAAOA,EAAO,QAChB,CAEAV,EAAS,KAAKU,CAAM,CACtB,CAGA,GADAZ,EAAiB,MAAM,EACnBE,EAAS,OAAS,EAAG,OAAOA,EAAS,CAAC,EAAE,SAE5C,IAAMN,EAAU,KAAK,UAAU,CAC7B,KAAM,QACN,MAAO,CAAE,KAAM,mBAAoB,QAAS,sBAAuB,CACrE,CAAC,EACD,OAAO,IAAI,SAASA,EAAS,CAC3B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,CACH,MAAQ,CACNI,EAAiB,MAAM,EACvB,IAAMJ,EAAU,KAAK,UAAU,CAC7B,KAAM,QACN,MAAO,CAAE,KAAM,mBAAoB,QAAS,sBAAuB,CACrE,CAAC,EACD,OAAO,IAAI,SAASA,EAAS,CAC3B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,iBAAkBC,EAAY,OAAOD,CAAO,EAAE,WAAW,SAAS,CACpE,CACF,CAAC,CACH,CACF,CF73BA,OAAS,cAAAoB,OAAkB,SAC3B,OAAS,QAAAC,OAAY,OACrB,OAAS,aAAAC,OAAiB,OGP1B,OAAS,mBAAAC,OAAuB,KAUhC,IAAMC,GAAmB,IACnBC,GAAmB,EACnBC,GAAyB,GAAK,KAC9BC,GAAsB,IAExBC,EAAwE,KAErE,SAASC,EAAgBC,EAAgBC,EAAkC,CAChF,IAAMC,EAAM,IAAIT,GAAgB,CAAE,OAAAO,EAAQ,KAAM,KAAM,CAAC,EACvDF,EAAcI,EAEdA,EAAI,GAAG,aAAeC,GAAO,CAG3B,IAAMC,EAAwB,CAAE,KAAM,UAAW,KADjCH,EAAa,WAAW,CACuB,EAC/DE,EAAG,KAAK,KAAK,UAAUC,CAAU,CAAC,EAElC,IAAIC,EACAC,EAAc,EACZC,EAAQ,IAAMJ,EAAG,aAAeA,EAAG,KAGnCK,EAAcP,EAAa,SAAUQ,GAA4B,CACrE,GAAKF,EAAM,EAGX,IAAIJ,EAAG,eAAiBP,GAAwB,CAE9Cc,EAAsB,EACtB,MACF,CAGA,aAAa,IAAM,CACjB,GAAI,CAACH,EAAM,EAAG,OACd,IAAMI,EAAiB,CAAE,KAAM,UAAW,KAAMF,CAAQ,EACxDN,EAAG,KAAK,KAAK,UAAUQ,CAAG,CAAC,CAC7B,CAAC,EAEDD,EAAsB,EACxB,CAAC,EAED,SAASA,GAA8B,CACjCL,IACJA,EAAsB,WAAW,IAAM,CAErC,GADAA,EAAsB,OAClB,CAACE,EAAM,EAAG,OACd,IAAMI,EAAiB,CAAE,KAAM,UAAW,KAAMV,EAAa,WAAW,CAAE,EAC1EE,EAAG,KAAK,KAAK,UAAUQ,CAAG,CAAC,CAC7B,EAAGd,EAAmB,EACxB,CAGA,IAAMe,EAAY,YAAY,IAAM,CAClC,GAAI,CAACL,EAAM,EAAG,CACZ,cAAcK,CAAS,EACvB,MACF,CAEA,GAAIN,GAAeX,GAAkB,CACnCkB,EAAQ,EACRV,EAAG,UAAU,EACb,MACF,CACAA,EAAG,KAAK,EACRG,GACF,EAAGZ,EAAgB,EAEnBS,EAAG,GAAG,OAAQ,IAAM,CAClBG,EAAc,CAChB,CAAC,EAED,IAAIQ,EAAY,GACVD,EAAU,IAAM,CAChBC,IACJA,EAAY,GACZ,cAAcF,CAAS,EACnBP,GAAqB,aAAaA,CAAmB,EACzDG,EAAY,EACd,EAEAL,EAAG,GAAG,QAASU,CAAO,EACtBV,EAAG,GAAG,QAASU,CAAO,CACxB,CAAC,CACH,CAEO,SAASE,EAAqBC,EAAyB,CAC5D,GAAI,CAAClB,EAAa,OAClB,IAAMa,EAAM,KAAK,UAAU,CAAE,KAAM,SAAU,KAAAK,CAAK,CAAC,EACnD,QAAWC,KAAUnB,EAAY,QAC3BmB,EAAO,aAAeA,EAAO,MAC/B,aAAa,IAAM,CACbA,EAAO,aAAeA,EAAO,MAC/BA,EAAO,KAAKN,CAAG,CAEnB,CAAC,CAGP,CHnGA,IAAMO,GAAYC,GAAUC,EAAI,EAKhC,SAASC,EAAeC,EAAcC,EAAiBC,EAA6B,CAClF,OAAO,IAAI,SACT,KAAK,UAAU,CAAE,KAAM,QAAS,MAAO,CAAE,KAAAF,EAAM,QAAAC,CAAQ,CAAE,CAAC,EAC1D,CACE,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,eAAgBC,CAClB,CACF,CACF,CACF,CAMA,SAASC,GAAmBC,EAA8E,CACxG,IAAMC,EAASD,EAAK,SAAiD,OAChEA,EAAK,MACV,GAAI,CAACC,EAAO,MAAO,CAAE,YAAa,EAAG,aAAc,CAAE,EAErD,IAAMC,EAAOD,EAAM,cAAwCA,EAAM,eAAwC,EACnGE,EAAOF,EAAM,eAAyCA,EAAM,mBAA4C,EACxGG,EAAaH,EAAM,yBAAkD,EACrEI,EAAiBJ,EAAM,6BAAsD,EAEnF,MAAO,CAAE,YAAaC,EAAME,EAAYC,EAAe,aAAcF,CAAI,CAC3E,CAQA,SAASG,GACPC,EACAC,EACAC,EACAC,EACAC,EACAC,EACyC,CACzC,IAAMC,EAAK,IAAI,YAGTC,EAAS,CAAE,MAAO,EAAG,OAAQ,CAAE,EACjCC,EAAU,GACVC,EAAW,GAGTC,EAAc,KAChBC,EAAc,EACdC,EAAkB,EAClBC,EAAsB,EACtBC,EAAe,EACfC,EAAY,GAGZC,EAAwB,KAGtBC,EAAqB,IACvBC,EAAiB,EAEfC,EAAeC,GAAsB,CACzC,QAAWC,KAASD,EAAU,MAAM;AAAA;AAAA,CAAM,EAAG,CAC3C,GAAI,CAACC,EAAO,SACZ,IAAMC,EAAWD,EAAM,MAAM;AAAA,CAAI,EAAE,KAAKE,GAAKA,EAAE,WAAW,OAAO,CAAC,EAClE,GAAI,GAACD,GAAY,CAACA,EAAS,SAAS,SAAS,GAC7C,GAAI,CACF,IAAM7B,EAAO,KAAK,MAAM6B,EAAS,MAAM,CAAC,CAAC,EACnC5B,EAAQF,GAAmBC,CAAI,EACjCC,EAAM,YAAca,EAAO,QAAOA,EAAO,MAAQb,EAAM,aACvDA,EAAM,aAAea,EAAO,SAAQA,EAAO,OAASb,EAAM,aAChE,MAAQ,CAAuB,CACjC,CACF,EAEM8B,EAAcC,GAAiB,CACnC,IAAMC,EAAe,CAAC,GAAGD,EAAK,SAAS,+CAA+C,CAAC,EACjFE,EAAmB,CAAC,GAAGF,EAAK,SAAS,wCAAwC,CAAC,EAC9EG,EAAuB,CAAC,GAAGH,EAAK,SAAS,4CAA4C,CAAC,EACtFI,EAAgB,CAAC,GAAGJ,EAAK,SAAS,oDAAoD,CAAC,EAE7F,GAAIC,EAAa,OAAS,EAAG,CAC3B,IAAMI,EAAM,SAASJ,EAAaA,EAAa,OAAS,CAAC,EAAE,CAAC,EAAG,EAAE,EAC7DI,EAAMnB,IAAaA,EAAcmB,EACvC,CACA,GAAIH,EAAiB,OAAS,EAAG,CAC/B,IAAMG,EAAM,SAASH,EAAiBA,EAAiB,OAAS,CAAC,EAAE,CAAC,EAAG,EAAE,EACrEG,EAAMlB,IAAiBA,EAAkBkB,EAC/C,CACA,GAAIF,EAAqB,OAAS,EAAG,CACnC,IAAME,EAAM,SAASF,EAAqBA,EAAqB,OAAS,CAAC,EAAE,CAAC,EAAG,EAAE,EAC7EE,EAAMjB,IAAqBA,EAAsBiB,EACvD,CACA,GAAID,EAAc,OAAS,EAAG,CAC5B,IAAMC,EAAM,SAASD,EAAcA,EAAc,OAAS,CAAC,EAAE,CAAC,EAAG,EAAE,EAC/DC,EAAMhB,IAAcA,EAAegB,EACzC,CACF,EAEMC,EAAgB,CAACpC,EAAaC,IAAgB,CAClD,GAAI,CACF,IAAMoC,EAAY,KAAK,IAAI,EAAIhC,EAAI,UAC7BiC,EAAaD,EAAY,IACzBE,EAAMD,EAAa,EAAIrC,EAAMqC,EAAa,EAEhD9B,EAAa,cAAc,CACzB,UAAWH,EAAI,UACf,MAAOA,EAAI,MACX,YAAaA,EAAI,aAAeA,EAAI,MACpC,KAAMA,EAAI,KACV,SAAAC,EACA,eAAAC,EACA,OAAAE,EACA,YAAaT,EACb,aAAcC,EACd,UAAAoC,EACA,aAAc,KAAK,MAAME,EAAM,EAAE,EAAI,GACrC,UAAW,KAAK,IAAI,EACpB,aAAclC,EAAI,YACpB,CAAC,EAGD,aAAa,IAAM,CACjBmC,EAAqB,CACnB,UAAWnC,EAAI,UACf,MAAOA,EAAI,MACX,KAAMA,EAAI,KACV,MAAO,WACP,OAAAI,EACA,UAAW,KAAK,IAAI,EAAIJ,EAAI,UAC5B,YAAaL,EACb,aAAcC,EACd,aAAc,KAAK,MAAMsC,EAAM,EAAE,EAAI,GACrC,UAAW,KAAK,IAAI,CACtB,CAAC,CACH,CAAC,CACH,MAAQ,CAER,CACF,EAEME,EAAe,CAACC,EAAiBC,IAAqB,CAM1D,GALItB,IAAU,OAEZA,EAAQX,EAAY,SAAS,mBAAmB,GAAKgC,EAAQ,WAAW,QAAQ,GAG9ErB,EAAO,CACTR,GAAW6B,EACX,IAAME,EAAQ/B,EAAQ,MAAM;AAAA,CAAI,EAChCA,EAAU+B,EAAM,IAAI,EAEpB,QAAWC,KAAQD,EACbC,IAAS,GACP/B,IACFU,EAAYV,CAAQ,EACpBA,EAAW,IAGbA,IAAaA,EAAW;AAAA,EAAO,IAAM+B,EAIrCF,GAAW7B,EAAS,KAAK,GAAGU,EAAYV,CAAQ,EAGpD,IAAMgC,EAAM,KAAK,IAAI,EACjBA,EAAMvB,GAAkBD,GAAsBV,EAAO,OAAS,IAChEW,EAAiBuB,EACjB,aAAa,IAAM,CACjBN,EAAqB,CACnB,UAAWnC,EAAI,UACf,MAAOA,EAAI,MACX,KAAMA,EAAI,KACV,MAAO,YACP,aAAcO,EAAO,OACrB,UAAWkC,CACb,CAAC,CACH,CAAC,GAGCH,GACFP,EAAcxB,EAAO,MAAOA,EAAO,MAAM,CAE7C,KAAO,CACLQ,GAAasB,EACTtB,EAAU,OAASL,IACrBK,EAAYA,EAAU,MAAM,CAACL,CAAW,GAE1Cc,EAAWT,CAAS,EAGpB,IAAM2B,EAAU,KAAK,IAAI,EAezB,GAdIA,EAAUxB,GAAkBD,GAAsBH,EAAe,IACnEI,EAAiBwB,EACjB,aAAa,IAAM,CACjBP,EAAqB,CACnB,UAAWnC,EAAI,UACf,MAAOA,EAAI,MACX,KAAMA,EAAI,KACV,MAAO,YACP,aAAAc,EACA,UAAW4B,CACb,CAAC,CACH,CAAC,GAGCJ,EAAS,CACX,IAAMK,EAAahC,EAAcC,EAAkBC,EACnDkB,EAAcY,EAAY7B,CAAY,CACxC,CACF,CACF,EAEA,OAAO,IAAI,gBAAgB,CACzB,UAAU8B,EAAOC,EAAY,CAC3BA,EAAW,QAAQD,CAAK,EACxBR,EAAa9B,EAAG,OAAOsC,EAAO,CAAE,OAAQ,EAAK,CAAC,EAAG,EAAK,CACxD,EACA,OAAQ,CACNR,EAAa,GAAI,EAAI,CACvB,EACA,QAAS,CAEPA,EAAa,GAAI,EAAI,CACvB,CACF,CAAC,CACH,CAQA,SAASU,EAAS7C,EAAkC,CAClD,IAAM8C,EAAS9C,EAAS,cAClB+C,EAAO/C,EAAS,UAAY,GAClC,MAAO,GAAG8C,GAAU,SAAS,IAAIC,CAAI,EACvC,CAEO,SAASC,EAAUC,EAAuBC,EAAoBhD,EAAwC,CAC3G,IAAIiD,EAAoBF,EAClBG,EAASC,EAAaH,CAAQ,EAC9BI,EAAM,IAAIC,GAGhB,OAAAD,EAAI,QAAQ,CAACE,EAAKC,KAChB,QAAQ,MAAM,6BAA6BD,EAAI,OAAO,EAAE,EACjDC,EAAE,KACP,CAAE,KAAM,QAAS,MAAO,CAAE,KAAM,YAAa,QAAS,sBAAuB,CAAE,EAC/E,CAAE,OAAQ,IAAK,QAAS,CAAE,eAAgB,kBAAmB,CAAE,CACjE,EACD,EAGDH,EAAI,IAAI,SAAU,MAAOG,EAAGC,IAAS,CACnCD,EAAE,OAAO,8BAA+B,GAAG,EAC3C,MAAMC,EAAK,CACb,CAAC,EAEDJ,EAAI,QAAQ,SAAWG,IACrBA,EAAE,OAAO,8BAA+B,GAAG,EAC3CA,EAAE,OAAO,+BAAgC,oBAAoB,EAC7DA,EAAE,OAAO,+BAAgC,2DAA2D,EAC7FA,EAAE,KAAK,GAAI,GAAG,EACtB,EAEDH,EAAI,KAAK,eAAgB,MAAOG,GAAM,CACpC,IAAMnE,EAAYqE,GAAW,EAGzBC,EACAC,EACJ,GAAI,CACFA,EAAU,MAAMJ,EAAE,IAAI,KAAK,EAC3BG,EAAO,KAAK,MAAMC,CAAO,CAC3B,MAAQ,CACN,OAAO1E,EAAe,wBAAyB,oBAAqBG,CAAS,CAC/E,CAEA,IAAMwE,EAAQF,EAAK,MACnB,GAAI,CAACE,EACH,OAAO3E,EAAe,wBAAyB,wCAAyCG,CAAS,EAGnG,IAAMS,EAAMgE,EAAeD,EAAOxE,EAAW6D,EAAQU,CAAO,EAI5D,GAHI9D,IACDA,EAAkE,WAAa6D,GAE9E,CAAC7D,EAAK,CACRqD,EAAO,KAAK,gBAAiB,CAAE,UAAA9D,EAAW,MAAAwE,CAAM,CAAC,EACjD,IAAME,EAAmBb,EAAO,aAAa,KAAO,EAChD,6BAA6B,CAAC,GAAGA,EAAO,aAAa,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,IACvE,GACJ,OAAOhE,EACL,wBACA,2BAA2B2E,CAAK,wBAAwB,CAAC,GAAGX,EAAO,aAAa,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,IAAIa,CAAgB,GACtH1E,CACF,CACF,CAEA8D,EAAO,KAAK,kBAAmB,CAC7B,UAAA9D,EACA,MAAAwE,EACA,KAAM/D,EAAI,KACV,UAAWA,EAAI,cAAc,IAAKkE,GAAMA,EAAE,QAAQ,CACpD,CAAC,EAGD/B,EAAqB,CACnB,UAAA5C,EACA,MAAAwE,EACA,KAAM/D,EAAI,KACV,MAAO,QACP,SAAUA,EAAI,cAAc,CAAC,GAAG,UAAY,UAC5C,UAAW,KAAK,IAAI,CACtB,CAAC,EAGD,IAAImE,EAAqB,UACrBC,EACJ,GAAI,CACFA,EAAW,MAAMC,EACfjB,EAAO,UACPpD,EAAI,cACJA,EACA0D,EAAE,IAAI,IACN,CAACzD,EAAUqE,IAAU,CACnBjB,EAAO,KAAK,sBAAuB,CAAE,UAAA9D,EAAW,SAAAU,EAAU,MAAAqE,EAAO,KAAMtE,EAAI,IAAK,CAAC,EAG5EmE,IAAoBA,EAAqBlE,EAChD,EACAoD,CACF,CACF,OAASI,EAAK,CACZ,IAAMc,EAASd,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAC9D,OAAAJ,EAAO,MAAM,iBAAkB,CAAE,UAAA9D,EAAW,MAAOgF,CAAO,CAAC,EAC3D,aAAa,IAAM,CACjBpC,EAAqB,CACnB,UAAA5C,EACA,MAAAwE,EACA,KAAM/D,EAAI,KACV,MAAO,QACP,OAAQ,IACR,QAASuE,EACT,UAAW,KAAK,IAAI,CACtB,CAAC,CACH,CAAC,EACMb,EAAE,KACP,CAAE,KAAM,QAAS,MAAO,CAAE,KAAM,YAAa,QAAS,4BAA8Ba,CAAO,CAAE,EAC7F,GACF,CACF,CAGIH,EAAS,QAAU,KACrB,aAAa,IAAM,CACjBjC,EAAqB,CACnB,UAAA5C,EACA,MAAAwE,EACA,KAAM/D,EAAI,KACV,MAAO,QACP,OAAQoE,EAAS,OACjB,QAAS,QAAQA,EAAS,MAAM,GAChC,UAAW,KAAK,IAAI,CACtB,CAAC,CACH,CAAC,EAIH,IAAII,EAAkDJ,EAAS,KAC/D,GAAIA,EAAS,MAAQA,EAAS,QAAU,KAAOA,EAAS,OAAS,KAAOjE,EAAc,CACpF,IAAMD,EAAiBF,EAAI,cAAc,OAAS,EAAIA,EAAI,cAAc,CAAC,EAAE,SAAWmE,EAChFM,EAAY1E,GAAuBC,EAAKmE,EAAoBjE,EAAgBC,EAAciE,EAAS,OAAQA,EAAS,QAAQ,IAAI,cAAc,GAAK,EAAE,EAC3JI,EAAeJ,EAAS,KAAK,YAAYK,CAAS,CACpD,CAGA,IAAMC,EAAa,IAAI,QAAQN,EAAS,OAAO,EAC/CM,EAAW,IAAI,eAAgBnF,CAAS,EACxC,IAAMoF,EAAgB,IAAI,SAASH,EAAc,CAC/C,OAAQJ,EAAS,OACjB,WAAYA,EAAS,WACrB,QAASM,CACX,CAAC,EAEKE,EAAU,KAAK,IAAI,EAAI5E,EAAI,UACjC,OAAAqD,EAAO,KAAK,oBAAqB,CAC/B,UAAA9D,EACA,MAAAwE,EACA,KAAM/D,EAAI,KACV,OAAQ2E,EAAc,OACtB,UAAWC,CACb,CAAC,EAEMD,CACT,CAAC,EAIDpB,EAAI,IAAI,uBAAwB,MAAOG,GAAM,CAC3C,GAAI,CAACvD,EAAc,OAAOuD,EAAE,KAAK,CAAE,MAAO,qBAAsB,EAAG,GAAG,EACtE,IAAMjE,EAAOU,EAAa,WAAW,EAC/B0E,EAAO,KAAK,UAAUpF,CAAI,EAGhC,IADuBiE,EAAE,IAAI,OAAO,iBAAiB,GAAK,IACvC,SAAS,MAAM,GAAKmB,EAAK,QAAU,KAAM,CAC1D,IAAMC,EAAa,MAAM7F,GAAU,OAAO,KAAK4F,CAAI,CAAC,EACpD,OAAO,IAAI,SAASC,EAAY,CAC9B,OAAQ,IACR,QAAS,CACP,eAAgB,mBAChB,mBAAoB,OACpB,KAAQ,iBACV,CACF,CAAC,CACH,CAEA,OAAOpB,EAAE,KAAKjE,CAAI,CACpB,CAAC,EAGD8D,EAAI,IAAI,uBAAyBG,GAAM,CACrC,IAAMtD,EAA0F,CAAC,EACjG,OAAW,CAAC2E,EAAM9E,CAAQ,IAAKmD,EAAO,UAAW,CAC/C,IAAM4B,EAAU/E,EAAS,gBACzB,GAAI+E,EAAS,CACX,IAAMC,EAAID,EAAQ,UAAU,EAC5B5E,EAAO2E,CAAI,EAAI,CACb,MAAOE,EAAE,MACT,SAAUA,EAAE,SACZ,YAAaA,EAAE,YAAc,IAAI,KAAKA,EAAE,WAAW,EAAE,YAAY,EAAI,IACvE,CACF,CACF,CACA,OAAOvB,EAAE,KAAKtD,CAAM,CACtB,CAAC,EAEM,CACL,IAAAmD,EACA,UAAW,IAAMH,EACjB,UAAY8B,GAAyB,CAEnC,IAAMC,EAAY,IAAI,IACtB,QAAWlF,KAAYmD,EAAO,UAAU,OAAO,EACzCnD,EAAS,QACXkF,EAAU,IAAIrC,EAAS7C,CAAQ,EAAGA,EAAS,MAAM,EAKrD,IAAMmF,EAAa,IAAI,IACvB,QAAWnF,KAAYiF,EAAU,UAAU,OAAO,EAAG,CACnD,IAAMG,EAAMvC,EAAS7C,CAAQ,EACvBqF,EAAgBH,EAAU,IAAIE,CAAG,EACnCC,IAEFrF,EAAS,OAASqF,EAClBF,EAAW,IAAIC,CAAG,EAGtB,CAGA,OAAW,CAACA,EAAKE,CAAK,IAAKJ,EACpBC,EAAW,IAAIC,CAAG,GACrBE,EAAM,MAAM,EAIhBnC,EAAS8B,EACTM,EAAkB,CACpB,CACF,CACF,CIneO,IAAMC,EAAN,KAAmB,CAChB,OACA,QACA,KAAO,EACP,MAAQ,EACR,YACA,UAGA,kBAAoB,EACpB,mBAAqB,EACrB,mBAAqB,EACrB,UAAY,IAAI,IAChB,aAAe,IAAI,IAE3B,YAAYC,EAAkB,IAAM,CAClC,KAAK,OAAS,IAAI,MAAMA,CAAO,EAAE,KAAK,IAAI,EAC1C,KAAK,QAAUA,EACf,KAAK,YAAc,IAAI,IACvB,KAAK,UAAY,KAAK,IAAI,CAC5B,CAEA,cAAcC,EAA+B,CAC3C,IAAMC,EAAQ,KAAK,KAAO,KAAK,QACzBC,EAAU,KAAK,OAAS,KAAK,QAAU,KAAK,OAAOD,CAAK,EAAI,KAGlE,GAAIC,IAAY,KAAM,CACpB,KAAK,mBAAqBA,EAAQ,aAAe,EACjD,KAAK,oBAAsBA,EAAQ,cAAgB,EACnD,KAAK,oBAAsBA,EAAQ,cAAgB,EAEnD,IAAMC,EAAOD,EAAQ,MACfE,EAAS,KAAK,UAAU,IAAID,CAAI,EAClCC,IACFA,EAAO,QACHA,EAAO,OAAS,GAAG,KAAK,UAAU,OAAOD,CAAI,GAGnD,IAAME,EAAOH,EAAQ,gBAAkBA,EAAQ,SACzCI,EAAS,KAAK,aAAa,IAAID,CAAI,GAAK,EAC1CC,GAAU,EAAG,KAAK,aAAa,OAAOD,CAAI,EACzC,KAAK,aAAa,IAAIA,EAAMC,EAAS,CAAC,CAC7C,CAGA,KAAK,mBAAqBN,EAAQ,aAAe,EACjD,KAAK,oBAAsBA,EAAQ,cAAgB,EACnD,KAAK,oBAAsBA,EAAQ,cAAgB,EAEnD,IAAMG,EAAOH,EAAQ,MACfO,EAAW,KAAK,UAAU,IAAIJ,CAAI,EACpCI,GACFA,EAAS,QACLP,EAAQ,UAAYO,EAAS,WAAUA,EAAS,SAAWP,EAAQ,WAEvEO,EAAS,YAAcP,EAAQ,aAE/B,KAAK,UAAU,IAAIG,EAAM,CAAE,YAAaH,EAAQ,YAAa,MAAO,EAAG,SAAUA,EAAQ,SAAU,CAAC,EAGtG,IAAMK,EAAOL,EAAQ,gBAAkBA,EAAQ,SAC/C,KAAK,aAAa,IAAIK,GAAO,KAAK,aAAa,IAAIA,CAAI,GAAK,GAAK,CAAC,EAGlE,KAAK,OAAOJ,CAAK,EAAID,EACrB,KAAK,OACD,KAAK,MAAQ,KAAK,SAAS,KAAK,QAGpC,QAAWQ,KAAM,KAAK,YACpB,GAAI,CACFA,EAAGR,CAAO,CACZ,MAAQ,CAER,CAEJ,CAEA,YAA6B,CAC3B,IAAMS,EAAW,KAAK,kBAAkB,EAElCC,EAAe,CAAC,GAAG,KAAK,UAAU,QAAQ,CAAC,EAC9C,IAAI,CAAC,CAACC,EAAO,CAAE,YAAAC,EAAa,MAAAC,EAAO,SAAAC,CAAS,CAAC,KAAO,CAAE,MAAAH,EAAO,YAAAC,EAAa,MAAAC,EAAO,SAAAC,CAAS,EAAE,EAC5F,KAAK,CAACC,EAAGC,IAAMA,EAAE,MAAQD,EAAE,KAAK,EAE7BE,EAAuB,CAAC,GAAG,KAAK,aAAa,QAAQ,CAAC,EACzD,IAAI,CAAC,CAACC,EAAUL,CAAK,KAAO,CAAE,SAAAK,EAAU,MAAAL,CAAM,EAAE,EAChD,KAAK,CAACE,EAAGC,IAAMA,EAAE,MAAQD,EAAE,KAAK,EAGnC,MAAO,CACL,cAAe,KAAK,MACpB,iBAAkB,KAAK,kBACvB,kBAAmB,KAAK,mBACxB,gBAAiB,KAAK,MAAQ,EAAI,KAAK,MAAO,KAAK,mBAAqB,KAAK,MAAS,EAAE,EAAI,GAAK,EACjG,aAAAL,EACA,qBAAAO,EACA,eAAgBR,EAChB,cAAe,KAAK,OAAO,KAAK,IAAI,EAAI,KAAK,WAAa,GAAI,CAChE,CACF,CAEA,SAASU,EAAkC,CACzC,YAAK,YAAY,IAAIA,CAAQ,EACtB,IAAM,CACX,KAAK,YAAY,OAAOA,CAAQ,CAClC,CACF,CAEQ,mBAAsC,CAC5C,GAAI,KAAK,QAAU,EAAG,MAAO,CAAC,EAG9B,IAAMC,EAAM,KAAK,IAAI,KAAK,MAAO,EAAsB,EACjDC,EAA2B,CAAC,EAElC,QAASC,EAAI,EAAGA,EAAIF,EAAKE,IAAK,CAC5B,IAAMrB,IAAU,KAAK,KAAO,EAAIqB,GAAK,KAAK,QAAU,KAAK,SAAW,KAAK,QACnEC,EAAQ,KAAK,OAAOtB,CAAK,EAC3BsB,IAAU,MACZF,EAAO,KAAKE,CAAK,CAErB,CAEA,OAAAF,EAAO,QAAQ,EACRA,CACT,CACF,EC5IA,OAAS,SAAAG,OAAa,gBACtB,OAAS,cAAAC,GAAY,cAAAC,OAAkB,KACvC,OAAS,WAAAC,GAAS,QAAQC,OAAgB,OAC1C,OAAS,iBAAAC,OAAqB,MAG9B,eAAsBC,EAAaC,EAIjB,CAGhB,IAAMC,EAAUC,EAAW,EACvBC,GAAWF,CAAO,GACpBG,GAAWH,CAAO,EAEpB,MAAMI,EAAa,QAAQ,GAAG,EAE9B,IAAMC,EACJ,QAAQ,KAAK,CAAC,GAAKC,GAASC,GAAQC,GAAc,YAAY,GAAG,CAAC,EAAG,UAAU,EAGjF,QAAQ,GAAG,oBAAsBC,GAAQ,CACvC,QAAQ,MAAM,iCAAiCA,EAAI,OAAO,EAAE,CAC9D,CAAC,EACD,QAAQ,GAAG,qBAAuBC,GAAW,CAC3C,QAAQ,MAAM,kCAAkCA,CAAM,EAAE,CAC1D,CAAC,EAED,IAAMC,EAAuB,GACvBC,EAAqB,IACrBC,EAAiB,IACjBC,EAAgB,IAClBC,EAAe,EACfC,EAAoD,KACpDC,EAAqD,KACrDC,EAAe,GACfC,EAAY,GACZC,EAAyC,KAE7C,SAASC,GAAoB,CAC3B,IAAMC,EAAsB,CAACjB,EAAa,UAAU,EAChDN,EAAK,QAAQuB,EAAU,KAAK,WAAYvB,EAAK,MAAM,EACnDA,EAAK,MAAMuB,EAAU,KAAK,SAAU,OAAOvB,EAAK,IAAI,CAAC,EACrDA,EAAK,SAASuB,EAAU,KAAK,WAAW,EAE5CF,EAAQG,GAAM,QAAQ,SAAUD,EAAW,CACzC,SAAU,GACV,MAAO,SACP,IAAK,CAAE,GAAG,QAAQ,GAAI,CACxB,CAAC,EAIGN,GAAa,aAAaA,CAAW,EACzCA,EAAc,WAAW,IAAM,CACzBD,EAAe,GACjB,QAAQ,MACN,+BAA+BD,CAAa,+BAC9C,EAEFC,EAAe,EACfC,EAAc,IAChB,EAAGF,CAAa,EAEhBM,EAAM,GAAG,OAAQ,MAAOI,GAAS,CAC/BJ,EAAQ,KAGJJ,IACF,aAAaA,CAAW,EACxBA,EAAc,MAGhB,MAAMS,EAAoB,EACtBD,IAAS,GAAK,CAACL,IAEjB,MAAMO,EAAc,EACpB,QAAQ,KAAK,CAAC,GAEhBP,EAAY,GAGRD,IACF,QAAQ,MAAM,0DAA0D,EACxE,MAAMQ,EAAc,EACpB,QAAQ,KAAK,CAAC,GAIhB,IAAMC,EAAUZ,EACZY,GAAWhB,IACb,QAAQ,MACN,6CAA6CA,CAAoB,oBACnE,EACA,MAAMe,EAAc,EACpB,QAAQ,KAAK,CAAC,GAGhB,IAAME,EAAU,KAAK,IAAIhB,EAAqB,GAAKe,EAASd,CAAc,EAC1EE,IACA,QAAQ,MACN,+BAA+BS,CAAI,oBAAoBI,CAAO,eAAeb,CAAY,IAAIJ,CAAoB,GACnH,EAEAM,EAAe,WAAWI,EAAaO,CAAO,CAChD,CAAC,CACH,CAMA,QAAQ,GAAG,UAAW,IAAM,CAU1B,GATAV,EAAe,GACXD,IACF,aAAaA,CAAY,EACzBA,EAAe,MAEbD,IACF,aAAaA,CAAW,EACxBA,EAAc,MAEZI,EAAO,CACT,GAAI,CACFA,EAAM,KAAK,SAAS,CACtB,MAAQ,CAER,CAGA,WAAW,IAAM,CACf,QAAQ,MAAM,uDAAuD,EACrE,QAAQ,KAAK,CAAC,CAChB,EAAG,GAAI,CACT,MAEEM,EAAc,EAAE,KAAK,IAAM,QAAQ,KAAK,CAAC,CAAC,CAE9C,CAAC,EAGD,QAAQ,GAAG,SAAU,IAAM,CAUzB,GATAR,EAAe,GACXD,IACF,aAAaA,CAAY,EACzBA,EAAe,MAEbD,IACF,aAAaA,CAAW,EACxBA,EAAc,MAEZI,EAAO,CACT,GAAI,CACFA,EAAM,KAAK,SAAS,CACtB,MAAQ,CAER,CAGA,WAAW,IAAM,CACf,QAAQ,MAAM,uDAAuD,EACrE,QAAQ,KAAK,CAAC,CAChB,EAAG,GAAI,CACT,MAEEM,EAAc,EAAE,KAAK,IAAM,QAAQ,KAAK,CAAC,CAAC,CAE9C,CAAC,EAID,QAAQ,GAAG,SAAU,IAAM,CAOzB,GANA,QAAQ,IAAI,wDAAwD,EACpEP,EAAY,GACRF,IACF,aAAaA,CAAY,EACzBA,EAAe,MAEbG,EACF,GAAI,CACFA,EAAM,KAAK,SAAS,CACtB,MAAQ,CAER,CAGFL,EAAe,CACjB,CAAC,EAEDM,EAAY,CACd,CNtLA,IAAMQ,GAAkB,KAAK,MAAMC,GAAa,IAAI,IAAI,kBAAmB,YAAY,GAAG,EAAG,OAAO,CAAC,EAAE,QAEvG,SAASC,GAAUC,EAAsI,CACvJ,IAAMC,EAA6H,CAAE,QAAS,GAAO,KAAM,GAAO,OAAQ,GAAO,QAAS,GAAO,IAAK,EAAM,EAC5M,QAAS,EAAI,EAAG,EAAID,EAAK,OAAQ,IAC/B,OAAQA,EAAK,CAAC,EAAG,CACf,IAAK,KACL,IAAK,SACH,IAAME,EAAUF,EAAK,EAAE,CAAC,GACpB,CAACE,GAAW,MAAM,SAASA,EAAS,EAAE,CAAC,KACzC,QAAQ,MAAM,oCAAoC,EAClD,QAAQ,KAAK,CAAC,GAEhBD,EAAK,KAAO,SAASC,EAAS,EAAE,EAChC,MACF,IAAK,KACL,IAAK,WACH,IAAMC,EAAaH,EAAK,EAAE,CAAC,EACtBG,IACH,QAAQ,MAAM,oCAAoC,EAClD,QAAQ,KAAK,CAAC,GAEhBF,EAAK,OAASE,EACd,MACF,IAAK,KACL,IAAK,YACHF,EAAK,QAAU,GACf,MACF,IAAK,KACL,IAAK,SACHA,EAAK,KAAO,GACZ,MACF,IAAK,WACHA,EAAK,OAAS,GACd,MACF,IAAK,YACHA,EAAK,QAAU,GACf,KACJ,CAEF,OAAOA,CACT,CAEA,SAASG,IAAY,CACnB,QAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAyBb,CACD,CAEA,eAAeC,IAAO,CACpB,IAAMJ,EAAOF,GAAU,QAAQ,IAAI,EAGnC,GAAI,CACF,IAAMO,EAAS,KAAM,QAAO,QAAQ,EAC9B,CAAE,WAAAC,CAAW,EAAI,KAAM,QAAO,IAAS,EACvC,CAAE,KAAAC,CAAK,EAAI,KAAM,QAAO,MAAW,EACnCC,EAAO,QAAQ,IAAI,MAAQ,QAAQ,IAAI,aAAe,GAEtDC,EAAQ,CACZF,EAAK,QAAQ,IAAI,EAAG,MAAM,EAC1BA,EAAKC,EAAM,eAAgB,MAAM,EACjCD,EAAKC,EAAM,MAAM,CACnB,EACA,QAAWE,KAAKD,EACd,GAAIH,EAAWI,CAAC,EAAG,CACjBL,EAAO,OAAO,CAAE,KAAMK,CAAE,CAAC,EACzB,KACF,CAEJ,MAAQ,CAER,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,OAAQ,CAC9B,IAAMC,EAAQ,QAAQ,KAAK,SAAS,SAAS,GAAK,QAAQ,KAAK,SAAS,IAAI,EACtE,CAAE,QAAAC,CAAQ,EAAI,KAAM,QAAO,oBAAW,EAC5C,MAAMA,EAAQ,CAAE,MAAAD,CAAM,CAAC,EACvB,QAAQ,KAAK,CAAC,CAChB,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,QAAS,CAC/B,GAAM,CAAE,YAAAE,CAAY,EAAI,KAAM,QAAO,sBAAa,EAC5CC,EAAS,MAAMD,EAAYb,EAAK,OAAQA,EAAK,KAAMA,EAAK,OAAO,EACrE,QAAQ,IAAI,KAAKc,EAAO,OAAO,EAAE,EACjC,QAAQ,IAAI,eAAeA,EAAO,OAAO,EAAE,EAC3C,QAAQ,KAAKA,EAAO,QAAU,EAAI,CAAC,CACrC,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,OAAQ,CAC9B,GAAM,CAAE,WAAAC,CAAW,EAAI,KAAM,QAAO,sBAAa,EAC3CD,EAAS,MAAMC,EAAW,EAChC,QAAQ,IAAI,KAAKD,EAAO,OAAO,EAAE,EACjC,QAAQ,KAAKA,EAAO,QAAU,EAAI,CAAC,CACrC,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,SAAU,CAChC,GAAM,CAAE,aAAAE,CAAa,EAAI,KAAM,QAAO,sBAAa,EAC7CF,EAAS,MAAME,EAAa,EAClC,QAAQ,IAAI,KAAKF,EAAO,OAAO,EAAE,EACjC,GAAI,CACF,GAAM,CAAE,WAAAG,CAAW,EAAI,KAAM,QAAO,uBAAc,EAE5CC,GADM,MAAMD,EAAW,GACP,YAAY,EAEhC,QAAQ,IADNC,EACU,uBAEA,2EAFsB,CAItC,OAASC,EAAK,CACZ,QAAQ,IAAI,cAAcA,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CAAC,EAAE,CAC9E,CACA,QAAQ,KAAK,CAAC,CAChB,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,SAAU,CAChC,GAAM,CAAE,aAAAC,CAAa,EAAI,KAAM,QAAO,sBAAa,EAC7CN,EAAS,MAAMM,EAAa,EAClC,QAAQ,IAAI,KAAKN,EAAO,OAAO,EAAE,EACjC,QAAQ,KAAKA,EAAO,QAAU,EAAI,CAAC,CACrC,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,UAAW,CACjC,GAAI,CACF,GAAM,CAAE,WAAAG,CAAW,EAAI,KAAM,QAAO,uBAAc,EAElD,MADY,MAAMA,EAAW,GACnB,QAAQ,CACpB,OAASE,EAAK,CACZ,QAAQ,MAAM,YAAYA,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CAAC,EAAE,EAC5E,QAAQ,KAAK,CAAC,CAChB,CACA,QAAQ,KAAK,CAAC,CAChB,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,YAAa,CACnC,GAAI,CACF,GAAM,CAAE,WAAAF,CAAW,EAAI,KAAM,QAAO,uBAAc,GACtC,MAAMA,EAAW,GACzB,UAAU,CAChB,OAASE,EAAK,CACZ,QAAQ,MAAM,YAAYA,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CAAC,EAAE,EAC5E,QAAQ,KAAK,CAAC,CAChB,CACA,QAAQ,KAAK,CAAC,CAChB,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,MAAO,CAC7B,GAAM,CAAE,UAAAE,CAAU,EAAI,KAAM,QAAO,4BAAmB,EACtD,MAAMA,EAAU,EAChB,QAAQ,KAAK,CAAC,CAChB,CAGA,GAAI,QAAQ,KAAK,CAAC,IAAM,SAAU,CAChC,GAAM,CAAE,aAAAC,CAAa,EAAI,KAAM,QAAO,sBAAa,EACnD,MAAMA,EAAatB,EAAK,IAAI,EAC5B,QAAQ,KAAK,CAAC,CAChB,CAEIA,EAAK,OACPG,GAAU,EACV,QAAQ,KAAK,CAAC,GAIhB,IAAIoB,EACArB,EACJ,GAAI,CACF,IAAMY,EAASU,EAAWxB,EAAK,MAAM,EACrCuB,EAAST,EAAO,OAChBZ,EAAaY,EAAO,UACtB,OAASW,EAAO,CACd,QAAQ,MAAM,iBAAkBA,EAAgB,OAAO,EAAE,EACzD,QAAQ,KAAK,CAAC,CAChB,CAGA,IAAMC,EAAO1B,EAAK,MAAQuB,EAAO,OAAO,KAClCI,EAAOJ,EAAO,OAAO,KACrBK,EAAqB5B,EAAK,QAAU,QAAU,OAG9C6B,EAAe,IAAIC,EAGzB,GAAI9B,EAAK,QAAS,CAChB,MAAM+B,EAAa/B,CAAI,EACvB,MACF,CAGA,GAAIA,EAAK,OAAQ,CACf,GAAM,CAAE,oBAAAgC,EAAqB,mBAAAC,EAAoB,sBAAAC,EAAuB,WAAAC,CAAW,EAAI,KAAM,QAAO,sBAAa,EAC3G,CAAE,aAAAC,CAAa,EAAI,KAAM,QAAO,sBAAa,EAC7C,CAAE,kBAAAC,EAAmB,MAAAC,CAAM,EAAI,KAAM,QAAO,IAAS,EACrD,CAAE,aAAAC,CAAa,EAAI,KAAM,QAAO,sBAAa,EAC7CC,EAASD,EAAaX,CAAQ,EAGpC,QAAQ,GAAG,oBAAsBT,GAAQ,CACvCqB,EAAO,MAAM,uCAAwC,CAAE,MAAOrB,EAAI,QAAS,MAAOA,EAAI,KAAM,CAAC,CAC/F,CAAC,EACD,QAAQ,GAAG,qBAAuBsB,GAAW,CAC3CD,EAAO,MAAM,wCAAyC,CAAE,OAAQ,OAAOC,CAAM,CAAE,CAAC,CAClF,CAAC,EAGD,MAAMR,EAAmB,QAAQ,GAAG,EAGpC,IAAMS,EAAYL,EAAkBF,EAAW,EAAG,CAAE,MAAO,GAAI,CAAC,EAChEO,EAAU,GAAG,QAAS,IAAM,CAAwC,CAAC,EACrE,QAAQ,OAAO,MAAQA,EAAU,MAAM,KAAKA,CAAS,EACrD,QAAQ,OAAO,MAAQA,EAAU,MAAM,KAAKA,CAAS,EAGrD,IAAMC,EAASC,EAAUrB,EAAQK,EAAUC,CAAY,EAGnDgB,EAAiD,KACrD,GAAI3C,EAAY,CACd,IAAM4C,EAAYZ,EAAsB,IAAM,CAC5C,GAAI,CACF,IAAMa,EAAYX,EAAalC,CAAU,EACzCyC,EAAO,UAAUI,CAAS,EAC1BP,EAAO,KAAK,kBAAmB,CAAE,KAAMtC,CAAW,CAAC,CACrD,OAASiB,EAAK,CACZqB,EAAO,MAAM,iDAA6C,CAAE,MAAQrB,EAAc,OAAQ,CAAC,CAC7F,CACF,EAAG,GAAG,EAEN,GAAI,CACF0B,EAAgBP,EAAMpC,EAAY,IAAM,CACtC4C,EAAU,OAAO,CACnB,CAAC,EACDD,EAAc,GAAG,QAAS,IAAM,CAE1BA,IACFA,EAAc,MAAM,EACpBA,EAAgB,KAEpB,CAAC,CACH,MAAQ,CAER,CACF,CAIA,QAAQ,GAAG,UAAW,IAAM,CAC1B,GAAI,CACF,IAAME,EAAYX,EAAalC,CAAW,EAC1CyC,EAAO,UAAUI,CAAS,EAC1BP,EAAO,KAAK,4BAA6B,CAAE,KAAMtC,CAAW,CAAC,CAC/D,OAASiB,EAAK,CACZqB,EAAO,MAAM,iCAAkC,CAAE,MAAQrB,EAAc,OAAQ,CAAC,CAClF,CACF,CAAC,EAGD,IAAM6B,EAASC,GAAM,CAAE,MAAON,EAAO,IAAI,MAAO,SAAUhB,EAAM,KAAAD,CAAK,CAAC,EACtEwB,EAAgBF,EAAenB,CAAY,EAG3C,IAAMsB,EAAW,SAAY,CACvBN,IACFA,EAAc,MAAM,EACpBA,EAAgB,MAElB,MAAMb,EAAoB,EAC1BU,EAAU,IAAI,EACd,QAAQ,KAAK,CAAC,CAChB,EACA,QAAQ,GAAG,UAAWS,CAAQ,EAC9B,QAAQ,GAAG,SAAUA,CAAQ,EAE7B,MACF,CAGA,IAAMR,EAASC,EAAUrB,EAAQK,EAAUC,CAAY,EAGvD,QAAQ,IAAI;AAAA,iBAAoBjC,EAAO,EAAE,EACzC,QAAQ,IAAI,uBAAuB+B,CAAI,IAAID,CAAI,EAAE,EACjD,QAAQ,IAAI,aAAaxB,CAAU;AAAA,CAAI,EAEvC,QAAQ,IAAI,WAAW,EACvB,OAAW,CAACkD,EAAMC,CAAO,IAAK9B,EAAO,QAAS,CAC5C,IAAM+B,EAAeD,EAClB,IAAI,CAACE,EAAGC,IAAM,GAAGD,EAAE,QAAQ,GAAGC,IAAM,EAAI,aAAe,aAAa,EAAE,EACtE,KAAK,IAAI,EACZ,QAAQ,IAAI,OAAOJ,EAAK,OAAO,CAAC,CAAC,WAAME,CAAY,EAAE,CACvD,CAGA,GAFA,QAAQ,IAAI,EAER/B,EAAO,aAAa,KAAO,EAAG,CAChC,QAAQ,IAAI,iBAAiB,EAC7B,OAAW,CAACkC,EAAOJ,CAAO,IAAK9B,EAAO,aAAc,CAClD,IAAM+B,EAAeD,EAClB,IAAI,CAACE,EAAGC,IAAM,GAAGD,EAAE,QAAQ,GAAGC,IAAM,EAAI,aAAe,aAAa,EAAE,EACtE,KAAK,IAAI,EACZ,QAAQ,IAAI,OAAOC,EAAM,OAAO,EAAE,CAAC,WAAMH,CAAY,EAAE,CACzD,CACA,QAAQ,IAAI,CACd,CAGA,IAAMN,EAASC,GAAM,CAAE,MAAON,EAAO,IAAI,MAAO,SAAUhB,EAAM,KAAAD,CAAK,CAAC,EACtEwB,EAAgBF,EAAenB,CAAY,EAG3C,IAAMsB,EAAW,IAAM,CACrB,QAAQ,IAAI;AAAA,mBAAsB,EAClC,QAAQ,KAAK,CAAC,CAChB,EACA,QAAQ,GAAG,UAAWA,CAAQ,EAC9B,QAAQ,GAAG,SAAUA,CAAQ,CAC/B,CAEA/C,GAAK","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","fs","path","os","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","SPECULATIVE_DELAY","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","toolUseIds","toolResultIds","needsFiltering","i","msg","hasOrphan","block","filterType","filtered","validToolUseIds","applyTargetedReplacements","rawBody","entry","parsed","needsOrphanClean","mutable","maxOutputTokens","requested","modelMatch","maxTokensMatch","forwardRequest","ctx","incomingRequest","externalSignal","chainIndex","outgoingPath","needsModification","requestedMaxTokens","originalModel","controller","timeout","ttfbTimeout","ttfbTimedOut","ttfbTimer","ttfbPromise","_","reject","onExternalAbort","undiciResponse","response","error","message","forwardWithFallback","providers","chain","ctx","incomingRequest","onAttempt","logger","entry","provider","errBody","textEncoder","response","forwardRequest","sharedController","completed","failures","attemptProvider","index","races","i","resolve","SPECULATIVE_DELAY","pending","_","idx","winner","isRetriable","handled","handleContextWindowError","randomUUID","gzip","promisify","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","wssInstance","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","client","gzipAsync","promisify","gzip","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","cacheRead","cacheCreation","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","drainEvents","eventText","event","dataLine","l","scanWindow","text","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","totalInput","chunk","controller","agentKey","origin","size","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","model","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","s","newConfig","oldAgents","reusedKeys","key","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","callback","cap","result","i","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","i","model"]}
|
|
@@ -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-
|
|
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-UO2KSCGU.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-
|
|
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-I5WKQHTA.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-UO2KSCGU.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-
|
|
63
|
+
//# sourceMappingURL=init-JE7HXV7I.js.map
|
package/package.json
CHANGED
package/dist/chunk-ZGVJ4FUB.js
DELETED
|
@@ -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 t=Date.now();if(this.failureTimestamps.push(t),this.pruneOldFailures(t),this.state==="half-open"){this.state="open",this.openedAt=t;return}this.failureTimestamps.length>=this.config.failureThreshold&&(this.state="open",this.openedAt=t)}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 t=n-this.config.windowSeconds*1e3;this.failureTimestamps=this.failureTimestamps.filter(s=>s>=t)}};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().default(3e4),authType:e.enum(["anthropic","bearer"]).default("anthropic"),modelLimits:B,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(r){return r.replace(/\$\{([^}]+)\}/g,(n,t)=>{let s=process.env[t];if(s===void 0)throw new Error(`Missing environment variable: ${t}`);return s})}function k(r){if(typeof r=="string")return P(r);if(Array.isArray(r))return r.map(k);if(r!==null&&typeof r=="object"){let n={};for(let[t,s]of Object.entries(r))n[t]=k(s);return n}return r}function R(r=process.cwd(),{skipGlobal:n=!1}={}){let t=C(r,"modelweaver.yaml");if(S(t))return t;if(!n){let s=C(process.env.HOME||process.env.USERPROFILE||"",".modelweaver","config.yaml");if(S(s))return s}return null}function I(r){let n=R(r);if(!n)return null;let t=O(n,"utf-8"),s=x(t),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 K(r,n){let t=null;if(r)if(S(r))try{E(r).isDirectory()?t=R(r):t=r}catch{t=r}else t=r;if(t||(t=R(n)),!t)throw new Error("No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml");let s=O(t,"utf-8"),b=x(s,{customTags:[]}),f=k(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,authType:o.authType,modelLimits:o.modelLimits?{maxOutputTokens:o.modelLimits.maxOutputTokens}:void 0};try{let v=new URL(o.baseUrl);l._cachedHost=v.host,l._cachedOrigin=`${v.protocol}//${v.host}`,l._cachedPathname=v.pathname.replace(/\/+$/,"")}catch{}let T=o.poolSize;l._agent=new $({keepAliveTimeout:3e4,keepAliveMaxTimeout:6e4,connections:T??10,allowH2:!0}),l.poolSize=T??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:t}}function V(r){let{config:n}=K(r);return n}export{P as a,R as b,I as c,K as d,V as e};
|
|
3
|
-
//# sourceMappingURL=chunk-ZGVJ4FUB.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 authType: z.enum([\"anthropic\", \"bearer\"]).default(\"anthropic\"),\n modelLimits: modelLimitsSchema,\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 authType: p.authType,\n modelLimits: p.modelLimits ? { maxOutputTokens: p.modelLimits.maxOutputTokens } : undefined,\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":"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","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"]}
|
package/dist/config-FYJATRN4.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|