@kianwoon/modelweaver 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IBXPMXGY.js → chunk-BXB6B3CE.js} +2 -2
- package/dist/chunk-RFKUMVBO.js +3 -0
- package/dist/chunk-RFKUMVBO.js.map +1 -0
- package/dist/config-AUGJR65F.js +3 -0
- package/dist/{daemon-SKIY7KO7.js → daemon-7ML7R6OM.js} +2 -2
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-4EMFKPKX.js → init-CGVMZ7ZK.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-B6XONLAG.js +0 -3
- package/dist/chunk-B6XONLAG.js.map +0 -1
- package/dist/config-XMFRVR3M.js +0 -3
- /package/dist/{chunk-IBXPMXGY.js.map → chunk-BXB6B3CE.js.map} +0 -0
- /package/dist/{config-XMFRVR3M.js.map → config-AUGJR65F.js.map} +0 -0
- /package/dist/{daemon-SKIY7KO7.js.map → daemon-7ML7R6OM.js.map} +0 -0
- /package/dist/{init-4EMFKPKX.js.map → init-CGVMZ7ZK.js.map} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/hedging.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { 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\nconst MODEL_CONTEXT_WINDOWS: Record<string, number> = {\n 'claude-opus-4-6': 200000,\n 'claude-sonnet-4-6': 200000,\n 'claude-haiku-4-5-20251001': 200000,\n 'claude-3-5-sonnet': 200000,\n 'claude-3-5-haiku': 200000,\n 'glm-4.7': 128000,\n 'glm-5-turbo': 128000,\n};\n\nfunction getContextWindow(model: string): number {\n // Exact match first, then prefix match\n if (MODEL_CONTEXT_WINDOWS[model]) return MODEL_CONTEXT_WINDOWS[model];\n for (const [key, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {\n if (model.startsWith(key)) return size;\n }\n return 0;\n}\n\nfunction computeCacheHitRate(cacheRead: number, cacheCreation: number, input: number): number {\n const totalInput = input + cacheRead + cacheCreation;\n if (totalInput <= 0) return 0;\n return Math.round((cacheRead / totalInput) * 1000) / 10;\n}\n\nfunction computeContextPercent(input: number, cacheRead: number, cacheCreation: number, output: number, contextWindow: number): number {\n if (contextWindow <= 0) return 0;\n const total = input + cacheRead + cacheCreation + output;\n return Math.round((total / contextWindow) * 1000) / 10;\n}\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp + cacheRead + cacheCreation, outputTokens: out, cacheReadTokens: cacheRead, cacheCreationTokens: cacheCreation };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n let firstChunk = true;\n\n // Response text preview (last 100 chars for progress bar tooltip)\n let responsePreview = \"\";\n const PREVIEW_MAX = 100;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n\n // Extract usage (token counts)\n if (dataLine.includes('\"usage\"')) {\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n if (usage.cacheReadTokens > tokens.cacheRead) tokens.cacheRead = usage.cacheReadTokens;\n if (usage.cacheCreationTokens > tokens.cacheCreation) tokens.cacheCreation = usage.cacheCreationTokens;\n }\n\n // Extract text content for preview\n // Anthropic format: content_block_delta with delta.text\n const delta = data.delta as Record<string, unknown> | undefined;\n if (delta && typeof delta.text === \"string\") {\n responsePreview += delta.text;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n // OpenAI format: choices[0].delta.content\n const choices = data.choices as Array<Record<string, unknown>> | undefined;\n if (choices?.[0]) {\n const choiceDelta = choices[0].delta as Record<string, unknown> | undefined;\n if (choiceDelta && typeof choiceDelta.content === \"string\") {\n responsePreview += choiceDelta.content;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n }\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n\n // Extract text content for preview from JSON responses\n // Anthropic format: \"content\":[{\"type\":\"text\",\"text\":\"...\"}]\n const anthContent = [...text.matchAll(/\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g)];\n if (anthContent.length > 0) {\n const lastText = anthContent[anthContent.length - 1][1].replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n responsePreview += lastText;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n };\n\n const recordMetrics = (inp: number, out: number, cacheRead: number = 0, cacheCreation: number = 0) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n });\n\n // Broadcast completion event\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n cacheHitRate: computeCacheHitRate(cacheRead, cacheCreation, inp),\n contextPercent: computeContextPercent(inp, cacheRead, cacheCreation, out, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (firstChunk || now - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = now;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(tokens.cacheRead, tokens.cacheCreation, tokens.input),\n contextPercent: computeContextPercent(tokens.input, tokens.cacheRead, tokens.cacheCreation, tokens.output, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output, tokens.cacheRead, tokens.cacheCreation);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (firstChunk || nowJson - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = nowJson;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(cacheReadTokens, cacheCreationTokens, inputTokens),\n contextPercent: computeContextPercent(inputTokens, cacheReadTokens, cacheCreationTokens, outputTokens, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n const totalInput = inputTokens + cacheReadTokens + cacheCreationTokens;\n recordMetrics(totalInput, outputTokens, cacheReadTokens, cacheCreationTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n // Broadcast TTFB event — headers received from upstream (skip for error responses)\n if (response.status < 400) {\n let headerSize = 17; // approximate HTTP status line: \"HTTP/1.1 200 OK\\r\\n\"\n response.headers.forEach((v, k) => { headerSize += k.length + v.length + 4; });\n headerSize += 2; // trailing CRLF\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"ttfb\",\n status: response.status,\n headerSize,\n timestamp: Date.now(),\n });\n });\n }\n\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport { PassThrough } from \"node:stream\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { latencyTracker, inFlightCounter, computeHedgingCount } from './hedging.js';\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 ?? 15000;\n let ttfbTimedOut = false;\n let ttfbTimer: ReturnType<typeof setTimeout> | null = null;\n\n const ttfbPromise = new Promise<never>((_, reject) => {\n ttfbTimer = setTimeout(() => {\n ttfbTimedOut = true;\n controller.abort();\n reject(new Error(`TTFB timeout after ${ttfbTimeout}ms`));\n }, ttfbTimeout);\n });\n\n // Listen for external abort (from race cancellation) to abort this request\n 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 // Body stall detection: pipe through PassThrough to monitor for data without\n // interfering with undici's internal stream state (no flowing mode conflict).\n const stallTimeout = provider.stallTimeout ?? 30000;\n const passThrough = new PassThrough();\n\n let stallTimerRef = setTimeout(() => {\n passThrough.destroy(new Error(`Body stalled: no data after ${stallTimeout}ms`));\n }, stallTimeout);\n\n // Monitor OUR PassThrough for every data event — re-arms stall timer on each chunk\n passThrough.on(\"data\", () => {\n clearTimeout(stallTimerRef);\n stallTimerRef = setTimeout(() => {\n passThrough.destroy(new Error(`Body stalled: no data after ${stallTimeout}ms`));\n }, stallTimeout);\n });\n\n passThrough.on(\"end\", () => {\n clearTimeout(stallTimerRef);\n });\n\n passThrough.on(\"error\", () => {\n clearTimeout(stallTimerRef);\n });\n\n // Pipe undici body → PassThrough. Data flows through without mode conflicts.\n undiciResponse.body.pipe(passThrough);\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n passThrough as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Network errors / timeouts — return a synthetic 502\n const message = ttfbTimedOut\n ? `Provider \"${provider.name}\" timed out waiting for first byte after ${ttfbTimeout}ms`\n : error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\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 * Forward a request with optional adaptive hedging.\n * When latency variance is high, sends multiple copies and returns the fastest.\n */\nasync function hedgedForwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n chainSignal: AbortSignal | undefined,\n index: number,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n const count = computeHedgingCount(provider);\n\n if (count <= 1) {\n // No hedging — single request\n inFlightCounter.increment(provider.name);\n const start = Date.now();\n try {\n const r = await forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index);\n latencyTracker.record(provider.name, Date.now() - start);\n return r;\n } finally {\n inFlightCounter.decrement(provider.name);\n }\n }\n\n // Hedged path: send multiple copies, race for first success\n logger?.warn(\"Hedging request\", {\n requestId: ctx.requestId,\n provider: provider.name,\n count,\n cv: Math.round(latencyTracker.getCV(provider.name) * 100) / 100,\n inFlight: inFlightCounter.get(provider.name),\n maxConcurrent: provider.concurrentLimit,\n });\n\n const start = Date.now();\n const launched: Promise<Response>[] = [];\n\n for (let h = 0; h < count; h++) {\n inFlightCounter.increment(provider.name);\n launched.push(\n forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index)\n .finally(() => inFlightCounter.decrement(provider.name))\n );\n }\n\n // Race: first successful response wins, cancel the rest\n // Wrap each promise so we can identify which one completed by index\n const wrapped = launched.map((p, i) =>\n p.then(response => ({ response, hedgeIndex: i }))\n );\n\n const completed = new Set<number>();\n const failures: Response[] = [];\n\n try {\n while (completed.size < wrapped.length) {\n const pending = wrapped.filter((_, i) => !completed.has(i));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.hedgeIndex);\n\n // Record each hedged copy's result for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(winner.response.status);\n }\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n latencyTracker.record(provider.name, Date.now() - start);\n // Cancel remaining — record 502 for each cancelled copy\n for (let i = 0; i < wrapped.length; i++) {\n if (!completed.has(i)) {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n wrapped[i].then(r => { try { r.response.body?.cancel(); } catch {} });\n }\n }\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return winner.response;\n }\n\n failures.push(winner.response);\n }\n\n // All hedged copies returned errors — cancel bodies, return first failure\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" all hedged requests failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n } catch {\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" hedging failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n // Single provider — no racing needed\n if (chain.length <= 1) {\n const entry = chain[0];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n\n if (provider._circuitBreaker && !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 hedgedForwardRequest(provider, entry, ctx, incomingRequest, undefined, 0, logger);\n\n return response;\n }\n\n // Multiple providers — staggered race\n const sharedController = new AbortController();\n const completed = new Set<number>();\n const failures: { response: Response; index: number }[] = [];\n\n async function attemptProvider(\n index: number,\n ): Promise<{ response: Response; index: number }> {\n const entry = chain[index];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n\n 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 hedgedForwardRequest(\n provider,\n entry,\n ctx,\n incomingRequest,\n sharedController.signal,\n index,\n logger,\n );\n return { response, index };\n } catch {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n }\n\n // Build staggered race promises:\n // Provider 0 starts immediately\n // Provider 1+ start after SPECULATIVE_DELAY (if race not already won)\n const races: Promise<{ response: Response; index: number }>[] = [];\n\n for (let i = 0; i < chain.length; i++) {\n if (i === 0) {\n races.push(attemptProvider(0));\n } else {\n races.push(\n new Promise<{ response: Response; index: number }>((resolve) => {\n setTimeout(() => {\n if (sharedController.signal.aborted) {\n // Race already won — resolve with a cancelled placeholder\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: \"Cancelled by race winner\" },\n });\n resolve({\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index: i,\n });\n return;\n }\n attemptProvider(i).then(resolve);\n }, SPECULATIVE_DELAY);\n }),\n );\n }\n }\n\n // Pick winner — same logic as raceProviders\n try {\n while (completed.size < races.length) {\n const pending = races.filter((_, idx) => !completed.has(idx));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.index);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n for (const f of failures) {\n try {\n f.response.body?.cancel();\n } catch {\n /* ignore */\n }\n }\n return winner.response;\n }\n\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n failures.push(winner);\n }\n\n sharedController.abort();\n if (failures.length > 0) return failures[0].response;\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n}\n","/**\n * Adaptive request hedging — sends multiple copies of a request when\n * the provider shows high latency variance, returning the fastest response.\n */\n\nimport type { ProviderConfig } from './types.js';\n\ninterface LatencySample {\n ttfbMs: number;\n timestamp: number;\n}\n\nexport class LatencyTracker {\n private samples = new Map<string, LatencySample[]>();\n private readonly maxSize: number;\n private readonly MAX_PROVIDERS = 50;\n\n constructor(maxSize = 20) {\n this.maxSize = maxSize;\n }\n\n record(provider: string, ttfbMs: number): void {\n // Cap total tracked providers to prevent unbounded growth\n if (this.samples.size >= this.MAX_PROVIDERS && !this.samples.has(provider)) {\n // Remove the first (oldest) provider key\n const firstKey = this.samples.keys().next().value;\n if (firstKey !== undefined) this.samples.delete(firstKey);\n }\n\n let window = this.samples.get(provider);\n if (!window) {\n window = [];\n this.samples.set(provider, window);\n }\n window.push({ ttfbMs, timestamp: Date.now() });\n if (window.length > this.maxSize) {\n window.splice(0, window.length - this.maxSize);\n }\n }\n\n /** Coefficient of variation (stddev / mean). Returns 0 if insufficient data. */\n getCV(provider: string): number {\n const window = this.samples.get(provider);\n if (!window || window.length < 3) return 0;\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n if (mean === 0) return 0;\n const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;\n return Math.sqrt(variance) / mean;\n }\n\n getStats(provider: string): { count: number; mean: number; cv: number } {\n const window = this.samples.get(provider);\n if (!window || window.length === 0) return { count: 0, mean: 0, cv: 0 };\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n return { count: values.length, mean: Math.round(mean), cv: Math.round(this.getCV(provider) * 100) / 100 };\n }\n\n clear(provider: string): void {\n this.samples.delete(provider);\n }\n}\n\nexport class InFlightCounter {\n private counts = new Map<string, number>();\n\n increment(provider: string): number {\n const count = (this.counts.get(provider) ?? 0) + 1;\n this.counts.set(provider, count);\n return count;\n }\n\n decrement(provider: string): number {\n const count = Math.max(0, (this.counts.get(provider) ?? 0) - 1);\n this.counts.set(provider, count);\n return count;\n }\n\n get(provider: string): number {\n return this.counts.get(provider) ?? 0;\n }\n}\n\nexport const latencyTracker = new LatencyTracker();\nexport const inFlightCounter = new InFlightCounter();\n\n/**\n * Compute adaptive hedging count based on latency variance and available concurrency.\n *\n * CV (coefficient of variation) drives the count:\n * CV 0.0 → 1 (no hedging, provider is consistent)\n * CV 0.5 → 2\n * CV 1.0 → 3\n * CV 1.5+ → 4\n *\n * Clamped by available concurrency slots: maxConcurrent - inFlight.\n */\nexport function computeHedgingCount(provider: ProviderConfig): number {\n const cv = latencyTracker.getCV(provider.name);\n const inFlight = inFlightCounter.get(provider.name);\n const maxConcurrent = provider.concurrentLimit ?? 1;\n const available = Math.max(1, maxConcurrent - inFlight);\n\n const adaptive = Math.max(1, Math.floor(cv * 2 + 0.5));\n return Math.min(adaptive, available);\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\nconst STREAM_WS_THROTTLE_MS = 500; // caps stream event delivery to ~2 Hz per client\nconst clientStreamThrottle = new WeakMap<any, number>();\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 const isStreaming = data.state === \"streaming\";\n const now = Date.now();\n for (const client of wssInstance.clients) {\n if (client.readyState !== client.OPEN) continue;\n // Backpressure: skip if outbound buffer is too large\n if (client.bufferedAmount > BACKPRESSURE_THRESHOLD) continue;\n // Throttle streaming events per client (non-streaming events always pass)\n if (isStreaming) {\n const lastEmit = clientStreamThrottle.get(client) ?? 0;\n if (now - lastEmit < STREAM_WS_THROTTLE_MS) continue;\n clientStreamThrottle.set(client, now);\n }\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _totalCacheReadTokens = 0;\n private _totalCacheCreationTokens = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n this._totalCacheReadTokens -= evicted.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens -= evicted.cacheCreationTokens ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n this._totalCacheReadTokens += metrics.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens += metrics.cacheCreationTokens ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // Compute average cache hit rate across all requests with cache data\n let cacheHitRateSum = 0;\n let cacheHitRateCount = 0;\n for (const r of requests) {\n const totalInput = (r.inputTokens ?? 0) + (r.cacheReadTokens ?? 0) + (r.cacheCreationTokens ?? 0);\n if (totalInput > 0 && (r.cacheReadTokens ?? 0) > 0) {\n cacheHitRateSum += (r.cacheReadTokens! / totalInput) * 100;\n cacheHitRateCount++;\n }\n }\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n totalCacheReadTokens: this._totalCacheReadTokens,\n totalCacheCreationTokens: this._totalCacheCreationTokens,\n avgCacheHitRate: cacheHitRateCount > 0 ? Math.round((cacheHitRateSum / cacheHitRateCount) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":"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","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","PassThrough","fs","path","os","LatencyTracker","maxSize","provider","ttfbMs","firstKey","window","values","s","mean","a","b","variance","sum","v","InFlightCounter","count","latencyTracker","inFlightCounter","computeHedgingCount","cv","inFlight","maxConcurrent","available","adaptive","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","SPECULATIVE_DELAY","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","path","os","fs","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","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","undiciRequest","stallTimeout","passThrough","PassThrough","stallTimerRef","response","error","message","hedgedForwardRequest","provider","entry","ctx","incomingRequest","chainSignal","index","logger","count","computeHedgingCount","inFlightCounter","start","r","forwardRequest","latencyTracker","launched","h","wrapped","p","i","response","completed","failures","pending","_","winner","f","forwardWithFallback","providers","chain","onAttempt","errBody","textEncoder","sharedController","attemptProvider","races","resolve","SPECULATIVE_DELAY","idx","isRetriable","handled","handleContextWindowError","randomUUID","gzip","promisify","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","STREAM_WS_THROTTLE_MS","clientStreamThrottle","wssInstance","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","isStreaming","now","client","lastEmit","gzipAsync","promisify","gzip","MODEL_CONTEXT_WINDOWS","getContextWindow","model","key","size","computeCacheHitRate","cacheRead","cacheCreation","input","totalInput","computeContextPercent","output","contextWindow","total","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","firstChunk","responsePreview","PREVIEW_MAX","drainEvents","eventText","event","dataLine","l","delta","choices","choiceDelta","scanWindow","text","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","anthContent","lastText","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","chunk","controller","agentKey","origin","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","headerSize","v","k","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","s","newConfig","oldAgents","reusedKeys","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","cacheHitRateSum","cacheHitRateCount","r","totalInput","callback","cap","result","i","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","i","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","model"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/hedging.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { 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\nconst MODEL_CONTEXT_WINDOWS: Record<string, number> = {\n 'claude-opus-4-6': 200000,\n 'claude-sonnet-4-6': 200000,\n 'claude-haiku-4-5-20251001': 200000,\n 'claude-3-5-sonnet': 200000,\n 'claude-3-5-haiku': 200000,\n 'glm-4.7': 128000,\n 'glm-5-turbo': 128000,\n};\n\nfunction getContextWindow(model: string): number {\n // Exact match first, then prefix match\n if (MODEL_CONTEXT_WINDOWS[model]) return MODEL_CONTEXT_WINDOWS[model];\n for (const [key, size] of Object.entries(MODEL_CONTEXT_WINDOWS)) {\n if (model.startsWith(key)) return size;\n }\n return 0;\n}\n\nfunction computeCacheHitRate(cacheRead: number, cacheCreation: number, input: number): number {\n const totalInput = input + cacheRead + cacheCreation;\n if (totalInput <= 0) return 0;\n return Math.round((cacheRead / totalInput) * 1000) / 10;\n}\n\nfunction computeContextPercent(input: number, cacheRead: number, cacheCreation: number, output: number, contextWindow: number): number {\n if (contextWindow <= 0) return 0;\n const total = input + cacheRead + cacheCreation + output;\n return Math.round((total / contextWindow) * 1000) / 10;\n}\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreationTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp + cacheRead + cacheCreation, outputTokens: out, cacheReadTokens: cacheRead, cacheCreationTokens: cacheCreation };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n let firstChunk = true;\n\n // Response text preview (last 100 chars for progress bar tooltip)\n let responsePreview = \"\";\n const PREVIEW_MAX = 100;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n\n // Extract usage (token counts)\n if (dataLine.includes('\"usage\"')) {\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n if (usage.cacheReadTokens > tokens.cacheRead) tokens.cacheRead = usage.cacheReadTokens;\n if (usage.cacheCreationTokens > tokens.cacheCreation) tokens.cacheCreation = usage.cacheCreationTokens;\n }\n\n // Extract text content for preview\n // Anthropic format: content_block_delta with delta.text\n const delta = data.delta as Record<string, unknown> | undefined;\n if (delta && typeof delta.text === \"string\") {\n responsePreview += delta.text;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n // OpenAI format: choices[0].delta.content\n const choices = data.choices as Array<Record<string, unknown>> | undefined;\n if (choices?.[0]) {\n const choiceDelta = choices[0].delta as Record<string, unknown> | undefined;\n if (choiceDelta && typeof choiceDelta.content === \"string\") {\n responsePreview += choiceDelta.content;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n }\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n\n // Extract text content for preview from JSON responses\n // Anthropic format: \"content\":[{\"type\":\"text\",\"text\":\"...\"}]\n const anthContent = [...text.matchAll(/\"text\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/g)];\n if (anthContent.length > 0) {\n const lastText = anthContent[anthContent.length - 1][1].replace(/\\\\n/g, \"\\n\").replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n responsePreview += lastText;\n if (responsePreview.length > PREVIEW_MAX) {\n responsePreview = responsePreview.slice(-PREVIEW_MAX);\n }\n }\n };\n\n const recordMetrics = (inp: number, out: number, cacheRead: number = 0, cacheCreation: number = 0) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n });\n\n // Broadcast completion event\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n cacheReadTokens: cacheRead,\n cacheCreationTokens: cacheCreation,\n cacheHitRate: computeCacheHitRate(cacheRead, cacheCreation, inp),\n contextPercent: computeContextPercent(inp, cacheRead, cacheCreation, out, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (firstChunk || now - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = now;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(tokens.cacheRead, tokens.cacheCreation, tokens.input),\n contextPercent: computeContextPercent(tokens.input, tokens.cacheRead, tokens.cacheCreation, tokens.output, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output, tokens.cacheRead, tokens.cacheCreation);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (firstChunk || nowJson - lastStreamEmit >= STREAM_THROTTLE_MS) {\n lastStreamEmit = nowJson;\n firstChunk = false;\n const contextWindow = getContextWindow(ctx.actualModel || ctx.model);\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n preview: responsePreview,\n cacheHitRate: computeCacheHitRate(cacheReadTokens, cacheCreationTokens, inputTokens),\n contextPercent: computeContextPercent(inputTokens, cacheReadTokens, cacheCreationTokens, outputTokens, contextWindow),\n contextWindowSize: contextWindow || undefined,\n });\n });\n }\n\n if (isFinal) {\n const totalInput = inputTokens + cacheReadTokens + cacheCreationTokens;\n recordMetrics(totalInput, outputTokens, cacheReadTokens, cacheCreationTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n // Broadcast TTFB event — headers received from upstream (skip for error responses)\n if (response.status < 400) {\n let headerSize = 17; // approximate HTTP status line: \"HTTP/1.1 200 OK\\r\\n\"\n response.headers.forEach((v, k) => { headerSize += k.length + v.length + 4; });\n headerSize += 2; // trailing CRLF\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"ttfb\",\n status: response.status,\n headerSize,\n timestamp: Date.now(),\n });\n });\n }\n\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport { PassThrough } from \"node:stream\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { latencyTracker, inFlightCounter, computeHedgingCount } from './hedging.js';\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 ?? 15000;\n let ttfbTimedOut = false;\n let ttfbTimer: ReturnType<typeof setTimeout> | null = null;\n\n const ttfbPromise = new Promise<never>((_, reject) => {\n ttfbTimer = setTimeout(() => {\n ttfbTimedOut = true;\n controller.abort();\n reject(new Error(`TTFB timeout after ${ttfbTimeout}ms`));\n }, ttfbTimeout);\n }).catch(() => {}); // Swallow orphaned rejection when undiciRequest wins the race\n\n // Listen for external abort (from race cancellation) to abort this request\n let removeAbortListener: (() => void) | undefined;\n if (externalSignal) {\n if (externalSignal.aborted) {\n // Already aborted — don't even start the request\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: `Provider \"${provider.name}\" cancelled by race winner` },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n const onExternalAbort = () => {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n };\n removeAbortListener = externalSignal.addEventListener(\"abort\", onExternalAbort, { once: true }) as () => void;\n }\n\n try {\n const undiciResponse = await Promise.race([\n undiciRequest(url, {\n method: \"POST\",\n headers,\n body,\n signal: controller.signal,\n dispatcher: provider._agent,\n }),\n ttfbPromise,\n ]);\n\n // TTFB succeeded — cancel TTFB timer\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Body stall detection: pipe through PassThrough to monitor for data without\n // interfering with undici's internal stream state (no flowing mode conflict).\n const stallTimeout = provider.stallTimeout ?? 30000;\n const passThrough = new PassThrough();\n\n let stallTimerRef = setTimeout(() => {\n broadcastStreamEvent(requestId, { state: \"error\", error: `Body stalled: no data after ${stallTimeout}ms` });\n passThrough.destroy(new Error(`Body stalled: no data after ${stallTimeout}ms`));\n }, stallTimeout);\n\n // Monitor OUR PassThrough for every data event — re-arms stall timer on each chunk\n passThrough.on(\"data\", () => {\n clearTimeout(stallTimerRef);\n stallTimerRef = setTimeout(() => {\n broadcastStreamEvent(requestId, { state: \"error\", error: `Body stalled: no data after ${stallTimeout}ms` });\n passThrough.destroy(new Error(`Body stalled: no data after ${stallTimeout}ms`));\n }, stallTimeout);\n });\n\n passThrough.on(\"end\", () => {\n clearTimeout(stallTimerRef);\n });\n\n passThrough.on(\"error\", () => {\n clearTimeout(stallTimerRef);\n });\n\n // Pipe undici body → PassThrough. Data flows through without mode conflicts.\n undiciResponse.body.pipe(passThrough);\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n passThrough as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n if (ttfbTimer) clearTimeout(ttfbTimer);\n\n // Network errors / timeouts — return a synthetic 502\n const message = ttfbTimedOut\n ? `Provider \"${provider.name}\" timed out waiting for first byte after ${ttfbTimeout}ms`\n : error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n } finally {\n removeAbortListener?.();\n }\n}\n\n/**\n * Race multiple providers simultaneously. Returns the first successful response.\n * Aborts all remaining requests once a winner is found.\n */\nasync function raceProviders(\n chain: RoutingEntry[],\n providers: Map<string, ProviderConfig>,\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void },\n chainOffset: number = 0,\n): Promise<Response> {\n const sharedController = new AbortController();\n\n const races = chain.map(async (entry, index): Promise<{ response: Response; index: number }> => {\n const provider = providers.get(entry.provider);\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n // Check circuit breaker\n 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 * Forward a request with optional adaptive hedging.\n * When latency variance is high, sends multiple copies and returns the fastest.\n */\nasync function hedgedForwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n chainSignal: AbortSignal | undefined,\n index: number,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n const count = computeHedgingCount(provider);\n\n if (count <= 1) {\n // No hedging — single request\n inFlightCounter.increment(provider.name);\n const start = Date.now();\n try {\n const r = await forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index);\n latencyTracker.record(provider.name, Date.now() - start);\n return r;\n } finally {\n inFlightCounter.decrement(provider.name);\n }\n }\n\n // Hedged path: send multiple copies, race for first success\n logger?.warn(\"Hedging request\", {\n requestId: ctx.requestId,\n provider: provider.name,\n count,\n cv: Math.round(latencyTracker.getCV(provider.name) * 100) / 100,\n inFlight: inFlightCounter.get(provider.name),\n maxConcurrent: provider.concurrentLimit,\n });\n\n const start = Date.now();\n const launched: Promise<Response>[] = [];\n\n for (let h = 0; h < count; h++) {\n inFlightCounter.increment(provider.name);\n launched.push(\n forwardRequest(provider, entry, ctx, incomingRequest, chainSignal, index)\n .finally(() => inFlightCounter.decrement(provider.name))\n );\n }\n\n // Race: first successful response wins, cancel the rest\n // Wrap each promise so we can identify which one completed by index\n const wrapped = launched.map((p, i) =>\n p.then(response => ({ response, hedgeIndex: i }))\n );\n\n const completed = new Set<number>();\n const failures: Response[] = [];\n\n try {\n while (completed.size < wrapped.length) {\n const pending = wrapped.filter((_, i) => !completed.has(i));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.hedgeIndex);\n\n // Record each hedged copy's result for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(winner.response.status);\n }\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n latencyTracker.record(provider.name, Date.now() - start);\n // Cancel remaining — record 502 for each cancelled copy\n for (let i = 0; i < wrapped.length; i++) {\n if (!completed.has(i)) {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n wrapped[i].then(r => { try { r.response.body?.cancel(); } catch {} });\n }\n }\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return winner.response;\n }\n\n failures.push(winner.response);\n }\n\n // All hedged copies returned errors — cancel bodies, return first failure\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" all hedged requests failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n } catch {\n for (const f of failures) { try { f.body?.cancel(); } catch {} }\n return failures[0] ?? new Response(\n JSON.stringify({ type: \"error\", error: { type: \"api_error\", message: `Provider \"${provider.name}\" hedging failed` } }),\n { status: 502, headers: { \"content-type\": \"application/json\" } }\n );\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n // Single provider — no racing needed\n if (chain.length <= 1) {\n const entry = chain[0];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n\n if (provider._circuitBreaker && !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 hedgedForwardRequest(provider, entry, ctx, incomingRequest, undefined, 0, logger);\n\n return response;\n }\n\n // Multiple providers — staggered race\n const sharedController = new AbortController();\n const completed = new Set<number>();\n const failures: { response: Response; index: number }[] = [];\n\n async function attemptProvider(\n index: number,\n ): Promise<{ response: Response; index: number }> {\n const entry = chain[index];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n\n 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 hedgedForwardRequest(\n provider,\n entry,\n ctx,\n incomingRequest,\n sharedController.signal,\n index,\n logger,\n );\n return { response, index };\n } catch {\n if (provider._circuitBreaker) provider._circuitBreaker.recordResult(502);\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n }),\n index,\n };\n }\n }\n\n // Build staggered race promises:\n // Provider 0 starts immediately\n // Provider 1+ start after SPECULATIVE_DELAY (if race not already won)\n const races: Promise<{ response: Response; index: number }>[] = [];\n\n for (let i = 0; i < chain.length; i++) {\n if (i === 0) {\n races.push(attemptProvider(0));\n } else {\n races.push(\n new Promise<{ response: Response; index: number }>((resolve) => {\n setTimeout(() => {\n if (sharedController.signal.aborted) {\n // Race already won — resolve with a cancelled placeholder\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: \"Cancelled by race winner\" },\n });\n resolve({\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index: i,\n });\n return;\n }\n attemptProvider(i).then(resolve);\n }, SPECULATIVE_DELAY);\n }),\n );\n }\n }\n\n // Pick winner — same logic as raceProviders\n try {\n while (completed.size < races.length) {\n const pending = races.filter((_, idx) => !completed.has(idx));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(winner.index);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n for (const f of failures) {\n try {\n f.response.body?.cancel();\n } catch {\n /* ignore */\n }\n }\n return winner.response;\n }\n\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n failures.push(winner);\n }\n\n sharedController.abort();\n if (failures.length > 0) return failures[0].response;\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n }\n}\n","/**\n * Adaptive request hedging — sends multiple copies of a request when\n * the provider shows high latency variance, returning the fastest response.\n */\n\nimport type { ProviderConfig } from './types.js';\n\ninterface LatencySample {\n ttfbMs: number;\n timestamp: number;\n}\n\nexport class LatencyTracker {\n private samples = new Map<string, LatencySample[]>();\n private readonly maxSize: number;\n private readonly MAX_PROVIDERS = 50;\n\n constructor(maxSize = 20) {\n this.maxSize = maxSize;\n }\n\n record(provider: string, ttfbMs: number): void {\n // Cap total tracked providers to prevent unbounded growth\n if (this.samples.size >= this.MAX_PROVIDERS && !this.samples.has(provider)) {\n // Remove the first (oldest) provider key\n const firstKey = this.samples.keys().next().value;\n if (firstKey !== undefined) this.samples.delete(firstKey);\n }\n\n let window = this.samples.get(provider);\n if (!window) {\n window = [];\n this.samples.set(provider, window);\n }\n window.push({ ttfbMs, timestamp: Date.now() });\n if (window.length > this.maxSize) {\n window.splice(0, window.length - this.maxSize);\n }\n }\n\n /** Coefficient of variation (stddev / mean). Returns 0 if insufficient data. */\n getCV(provider: string): number {\n const window = this.samples.get(provider);\n if (!window || window.length < 3) return 0;\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n if (mean === 0) return 0;\n const variance = values.reduce((sum, v) => sum + (v - mean) ** 2, 0) / values.length;\n return Math.sqrt(variance) / mean;\n }\n\n getStats(provider: string): { count: number; mean: number; cv: number } {\n const window = this.samples.get(provider);\n if (!window || window.length === 0) return { count: 0, mean: 0, cv: 0 };\n const values = window.map(s => s.ttfbMs);\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n return { count: values.length, mean: Math.round(mean), cv: Math.round(this.getCV(provider) * 100) / 100 };\n }\n\n clear(provider: string): void {\n this.samples.delete(provider);\n }\n}\n\nexport class InFlightCounter {\n private counts = new Map<string, number>();\n\n increment(provider: string): number {\n const count = (this.counts.get(provider) ?? 0) + 1;\n this.counts.set(provider, count);\n return count;\n }\n\n decrement(provider: string): number {\n const count = Math.max(0, (this.counts.get(provider) ?? 0) - 1);\n this.counts.set(provider, count);\n return count;\n }\n\n get(provider: string): number {\n return this.counts.get(provider) ?? 0;\n }\n}\n\nexport const latencyTracker = new LatencyTracker();\nexport const inFlightCounter = new InFlightCounter();\n\n/**\n * Compute adaptive hedging count based on latency variance and available concurrency.\n *\n * CV (coefficient of variation) drives the count:\n * CV 0.0 → 1 (no hedging, provider is consistent)\n * CV 0.5 → 2\n * CV 1.0 → 3\n * CV 1.5+ → 4\n *\n * Clamped by available concurrency slots: maxConcurrent - inFlight.\n */\nexport function computeHedgingCount(provider: ProviderConfig): number {\n const cv = latencyTracker.getCV(provider.name);\n const inFlight = inFlightCounter.get(provider.name);\n const maxConcurrent = provider.concurrentLimit ?? 1;\n const available = Math.max(1, maxConcurrent - inFlight);\n\n const adaptive = Math.max(1, Math.floor(cv * 2 + 0.5));\n return Math.min(adaptive, available);\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\nconst STREAM_WS_THROTTLE_MS = 500; // caps stream event delivery to ~2 Hz per client\nconst clientStreamThrottle = new WeakMap<any, number>();\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 const isStreaming = data.state === \"streaming\";\n const now = Date.now();\n for (const client of wssInstance.clients) {\n if (client.readyState !== client.OPEN) continue;\n // Backpressure: skip if outbound buffer is too large\n if (client.bufferedAmount > BACKPRESSURE_THRESHOLD) continue;\n // Throttle streaming events per client (non-streaming events always pass)\n if (isStreaming) {\n const lastEmit = clientStreamThrottle.get(client) ?? 0;\n if (now - lastEmit < STREAM_WS_THROTTLE_MS) continue;\n clientStreamThrottle.set(client, now);\n }\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _totalCacheReadTokens = 0;\n private _totalCacheCreationTokens = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n this._totalCacheReadTokens -= evicted.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens -= evicted.cacheCreationTokens ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n this._totalCacheReadTokens += metrics.cacheReadTokens ?? 0;\n this._totalCacheCreationTokens += metrics.cacheCreationTokens ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // Compute average cache hit rate across all requests with cache data\n let cacheHitRateSum = 0;\n let cacheHitRateCount = 0;\n for (const r of requests) {\n const totalInput = (r.inputTokens ?? 0) + (r.cacheReadTokens ?? 0) + (r.cacheCreationTokens ?? 0);\n if (totalInput > 0 && (r.cacheReadTokens ?? 0) > 0) {\n cacheHitRateSum += (r.cacheReadTokens! / totalInput) * 100;\n cacheHitRateCount++;\n }\n }\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n totalCacheReadTokens: this._totalCacheReadTokens,\n totalCacheCreationTokens: this._totalCacheCreationTokens,\n avgCacheHitRate: cacheHitRateCount > 0 ? Math.round((cacheHitRateSum / cacheHitRateCount) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":"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","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","PassThrough","fs","path","os","LatencyTracker","maxSize","provider","ttfbMs","firstKey","window","values","s","mean","a","b","variance","sum","v","InFlightCounter","count","latencyTracker","inFlightCounter","computeHedgingCount","cv","inFlight","maxConcurrent","available","adaptive","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","SPECULATIVE_DELAY","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","path","os","fs","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","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","removeAbortListener","onExternalAbort","undiciResponse","undiciRequest","stallTimeout","passThrough","PassThrough","stallTimerRef","response","error","message","hedgedForwardRequest","provider","entry","ctx","incomingRequest","chainSignal","index","logger","count","computeHedgingCount","inFlightCounter","start","r","forwardRequest","latencyTracker","launched","h","wrapped","p","i","response","completed","failures","pending","_","winner","f","forwardWithFallback","providers","chain","onAttempt","errBody","textEncoder","sharedController","attemptProvider","races","resolve","SPECULATIVE_DELAY","idx","isRetriable","handled","handleContextWindowError","randomUUID","gzip","promisify","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","STREAM_WS_THROTTLE_MS","clientStreamThrottle","wssInstance","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","isStreaming","now","client","lastEmit","gzipAsync","promisify","gzip","MODEL_CONTEXT_WINDOWS","getContextWindow","model","key","size","computeCacheHitRate","cacheRead","cacheCreation","input","totalInput","computeContextPercent","output","contextWindow","total","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","firstChunk","responsePreview","PREVIEW_MAX","drainEvents","eventText","event","dataLine","l","delta","choices","choiceDelta","scanWindow","text","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","anthContent","lastText","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","chunk","controller","agentKey","origin","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","headerSize","v","k","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","s","newConfig","oldAgents","reusedKeys","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","cacheHitRateSum","cacheHitRateCount","r","totalInput","callback","cap","result","i","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","i","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","model"]}
|