@kianwoon/modelweaver 0.3.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/router.ts","../src/proxy.ts","../src/ws.ts","../src/metrics.ts","../src/monitor.ts"],"sourcesContent":["// src/index.ts\nimport { serve } from \"@hono/node-server\";\nimport { readFileSync } from \"node:fs\";\nimport { createApp } from \"./server.js\";\nimport { loadConfig } from \"./config.js\";\nimport type { LogLevel } from \"./logger.js\";\nimport { MetricsStore } from \"./metrics.js\";\nimport { attachWebSocket } from \"./ws.js\";\nimport { startMonitor } from \"./monitor.js\";\n\n// Read version from package.json at startup\nconst VERSION: string = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf-8\")).version;\n\nfunction parseArgs(argv: string[]): { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } {\n const args: { port?: number; config?: string; verbose: boolean; help: boolean; daemon: boolean; monitor: boolean; gui: boolean } = { verbose: false, help: false, daemon: false, monitor: false, gui: false };\n for (let i = 2; i < argv.length; i++) {\n switch (argv[i]) {\n case \"-p\":\n case \"--port\":\n const portStr = argv[++i];\n if (!portStr || isNaN(parseInt(portStr, 10))) {\n console.error(\"Error: -p/--port requires a number\");\n process.exit(1);\n }\n args.port = parseInt(portStr, 10);\n break;\n case \"-c\":\n case \"--config\":\n const configPath = argv[++i];\n if (!configPath) {\n console.error(\"Error: -c/--config requires a path\");\n process.exit(1);\n }\n args.config = configPath;\n break;\n case \"-v\":\n case \"--verbose\":\n args.verbose = true;\n break;\n case \"-h\":\n case \"--help\":\n args.help = true;\n break;\n case \"--daemon\":\n args.daemon = true;\n break;\n case \"--monitor\":\n args.monitor = true;\n break;\n }\n }\n return args;\n}\n\nfunction printHelp() {\n console.log(`\nModelWeaver — Multi-provider model orchestration proxy for Claude Code\n\nUsage: modelweaver [command] [options]\n\nCommands:\n init [--quick] Run interactive setup wizard (--quick for express mode)\n start Start as background daemon\n stop Stop background daemon\n status Show daemon status\n remove Stop daemon and remove PID + log files\n reload Reload daemon worker (load fresh code after build)\n install Install launchd service (auto-start at login)\n uninstall Uninstall launchd service\n gui Launch the GUI (downloads if needed)\n\nOptions:\n -p, --port <number> Server port (default: from config)\n -c, --config <path> Config file path (auto-detected)\n -v, --verbose Enable debug logging (default: off)\n -h, --help Show this help\n\nConfig locations (first found wins):\n ./modelweaver.yaml\n ~/.modelweaver/config.yaml\n`);\n}\n\nasync function main() {\n const args = parseArgs(process.argv);\n\n // Load .env file if present (created by modelweaver init)\n try {\n const dotenv = await import('dotenv');\n const { existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const home = process.env.HOME || process.env.USERPROFILE || '';\n // Try cwd/.env first, then ~/.modelweaver/.env, then ~/.env\n const paths = [\n join(process.cwd(), '.env'),\n join(home, '.modelweaver', '.env'),\n join(home, '.env'),\n ];\n for (const p of paths) {\n if (existsSync(p)) {\n dotenv.config({ path: p });\n break;\n }\n }\n } catch {\n // dotenv not installed or .env not present — continue without it\n }\n\n // Handle 'init' subcommand — dynamic import to avoid loading prompts for normal startup\n if (process.argv[2] === 'init') {\n const quick = process.argv.includes('--quick') || process.argv.includes('-q');\n const { runInit } = await import('./init.js');\n await runInit({ quick });\n process.exit(0);\n }\n\n // Handle 'start' subcommand\n if (process.argv[2] === 'start') {\n const { startDaemon } = await import('./daemon.js');\n const result = await startDaemon(args.config, args.port, args.verbose);\n console.log(` ${result.message}`);\n console.log(` Log file: ${result.logPath}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'stop' subcommand\n if (process.argv[2] === 'stop') {\n const { stopDaemon } = await import('./daemon.js');\n const result = await stopDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'status' subcommand\n if (process.argv[2] === 'status') {\n const { statusDaemon } = await import('./daemon.js');\n const result = await statusDaemon();\n console.log(` ${result.message}`);\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n const installed = svc.isInstalled();\n if (installed) {\n console.log(` Service: installed`);\n } else {\n console.log(` Service: not installed (run \"modelweaver install\" to enable auto-start)`);\n }\n } catch (err) {\n console.log(` Service: ${err instanceof Error ? err.message : String(err)}`);\n }\n process.exit(0);\n }\n\n // Handle 'remove' subcommand — stop + clean up PID and log files\n if (process.argv[2] === 'remove') {\n const { removeDaemon } = await import('./daemon.js');\n const result = await removeDaemon();\n console.log(` ${result.message}`);\n process.exit(result.success ? 0 : 1);\n }\n\n // Handle 'install' subcommand — install platform service\n if (process.argv[2] === 'install') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n await svc.install();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'uninstall' subcommand — uninstall platform service\n if (process.argv[2] === 'uninstall') {\n try {\n const { getService } = await import('./service.js');\n const svc = await getService();\n svc.uninstall();\n } catch (err) {\n console.error(` Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n process.exit(0);\n }\n\n // Handle 'gui' subcommand\n if (process.argv[2] === 'gui') {\n const { launchGui } = await import('./gui-launcher.js');\n await launchGui();\n process.exit(0);\n }\n\n // Handle 'reload' subcommand\n if (process.argv[2] === 'reload') {\n const { reloadDaemon } = await import('./daemon.js');\n await reloadDaemon(args.port);\n process.exit(0);\n }\n\n if (args.help) {\n printHelp();\n process.exit(0);\n }\n\n // Load config\n let config;\n let configPath;\n try {\n const result = loadConfig(args.config);\n config = result.config;\n configPath = result.configPath;\n } catch (error) {\n console.error(`Config error: ${(error as Error).message}`);\n process.exit(1);\n }\n\n // CLI port override\n const port = args.port || config.server.port;\n const host = config.server.host;\n const logLevel: LogLevel = args.verbose ? \"debug\" : \"info\";\n\n // Initialize metrics store\n const metricsStore = new MetricsStore();\n\n // --- Monitor mode (spawns daemon child, auto-restarts on crash) ---\n if (args.monitor) {\n await startMonitor(args);\n return;\n }\n\n // --- Daemon mode ---\n if (args.daemon) {\n const { removeWorkerPidFile, writeWorkerPidFile, createDebouncedReload, getLogPath } = await import('./daemon.js');\n const { reloadConfig } = await import('./config.js');\n const { createWriteStream, watch } = await import('node:fs');\n const { createLogger } = await import('./logger.js');\n const logger = createLogger(logLevel);\n\n // Prevent silent crashes from killing the daemon worker\n process.on('uncaughtException', (err) => {\n logger.error('Uncaught exception (daemon survived)', { error: err.message, stack: err.stack });\n });\n process.on('unhandledRejection', (reason) => {\n logger.error('Unhandled rejection (daemon survived)', { reason: String(reason) });\n });\n\n // Write worker PID file (monitor owns modelweaver.pid)\n await writeWorkerPidFile(process.pid);\n\n // Redirect stdout/stderr to log file\n const logStream = createWriteStream(getLogPath(), { flags: 'a' });\n logStream.on('error', () => { /* ignore write errors to log file */ });\n process.stdout.write = logStream.write.bind(logStream) as typeof process.stdout.write;\n process.stderr.write = logStream.write.bind(logStream) as typeof process.stderr.write;\n\n // Create app with mutable config\n const handle = createApp(config, logLevel, metricsStore);\n\n // Hot-reload: watch config file for changes\n let configWatcher: ReturnType<typeof watch> | null = null;\n if (configPath) {\n const debounced = createDebouncedReload(() => {\n try {\n const newConfig = reloadConfig(configPath);\n handle.setConfig(newConfig);\n logger.info(\"Config reloaded\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed — keeping old config\", { error: (err as Error).message });\n }\n }, 300);\n\n try {\n configWatcher = watch(configPath, () => {\n debounced.reload();\n });\n configWatcher.on('error', () => {\n // fs.watch failed — silently disable hot-reload\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n });\n } catch {\n // fs.watch not available — hot-reload disabled\n }\n }\n\n // SIGUSR1 triggers config hot-reload\n // Note: SIGUSR1 is POSIX-only; this handler is a no-op on Windows.\n process.on('SIGUSR1', () => {\n try {\n const newConfig = reloadConfig(configPath!);\n handle.setConfig(newConfig);\n logger.info(\"Config reloaded (SIGUSR1)\", { path: configPath });\n } catch (err) {\n logger.error(\"Config reload failed (SIGUSR1)\", { error: (err as Error).message });\n }\n });\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = async () => {\n if (configWatcher) {\n configWatcher.close();\n configWatcher = null;\n }\n await removeWorkerPidFile();\n logStream.end();\n process.exit(0);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return; // Don't fall through to foreground mode\n }\n\n // --- Foreground mode ---\n const handle = createApp(config, logLevel, metricsStore);\n\n // Print startup info\n console.log(`\\n ModelWeaver v${VERSION}`);\n console.log(` Listening: http://${host}:${port}`);\n console.log(` Config: ${configPath}\\n`);\n\n console.log(\" Routes:\");\n for (const [tier, entries] of config.routing) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${tier.padEnd(8)} → ${providerList}`);\n }\n console.log();\n\n if (config.modelRouting.size > 0) {\n console.log(\" Model Routes:\");\n for (const [model, entries] of config.modelRouting) {\n const providerList = entries\n .map((e, i) => `${e.provider}${i === 0 ? \" (primary)\" : \" (fallback)\"}`)\n .join(\", \");\n console.log(` ${model.padEnd(20)} → ${providerList}`);\n }\n console.log();\n }\n\n // Start server\n const server = serve({ fetch: handle.app.fetch, hostname: host, port });\n attachWebSocket(server as any, metricsStore);\n\n // Graceful shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n process.exit(0);\n };\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n}\n\nmain();\n","// src/server.ts\nimport { Hono } from \"hono\";\nimport { resolveRequest, clearRoutingCache } from \"./router.js\";\nimport { forwardWithFallback } from \"./proxy.js\";\nimport { createLogger, type LogLevel } from \"./logger.js\";\nimport type { AppConfig, ProviderConfig, RequestContext } from \"./types.js\";\nimport { randomUUID } from \"node:crypto\";\nimport { gzip } from \"node:zlib\";\nimport { promisify } from \"node:util\";\n\nconst gzipAsync = promisify(gzip);\nimport type { MetricsStore } from \"./metrics.js\";\nimport { broadcastStreamEvent } from \"./ws.js\";\nimport type { StreamEvent } from \"./types.js\";\n\nfunction anthropicError(type: string, message: string, requestId: string): Response {\n return new Response(\n JSON.stringify({ type: \"error\", error: { type, message } }),\n {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"x-request-id\": requestId,\n },\n }\n );\n}\n\n/**\n * Parse token counts from an SSE data line's JSON payload.\n * Supports both Anthropic (input_tokens/output_tokens) and OpenAI (prompt_tokens/completion_tokens) formats.\n */\nfunction parseUsageFromData(data: Record<string, unknown>): { inputTokens: number; outputTokens: number } {\n const usage = (data.message as Record<string, unknown> | undefined)?.usage as Record<string, unknown> | undefined\n ?? data.usage as Record<string, unknown> | undefined;\n if (!usage) return { inputTokens: 0, outputTokens: 0 };\n\n const inp = (usage.input_tokens as number | undefined) ?? (usage.prompt_tokens as number | undefined) ?? 0;\n const out = (usage.output_tokens as number | undefined) ?? (usage.completion_tokens as number | undefined) ?? 0;\n const cacheRead = (usage.cache_read_input_tokens as number | undefined) ?? 0;\n const cacheCreation = (usage.cache_creation_input_tokens as number | undefined) ?? 0;\n\n return { inputTokens: inp + cacheRead + cacheCreation, outputTokens: out };\n}\n\n/**\n * Creates a TransformStream that forwards chunks unchanged while extracting\n * token counts for metrics inline (no tee() or separate reader needed).\n * For SSE responses, extracts token counts from usage events incrementally.\n * For non-streaming JSON responses, uses a bounded sliding-window regex scan.\n */\nfunction createMetricsTransform(\n ctx: { requestId: string; model: string; actualModel?: string; tier: string; startTime: number; fallbackMode?: \"sequential\" | \"race\" },\n provider: string,\n targetProvider: string,\n metricsStore: MetricsStore,\n status: number,\n contentType: string,\n): TransformStream<Uint8Array, Uint8Array> {\n const td = new TextDecoder();\n\n // --- SSE state ---\n const tokens = { input: 0, output: 0 };\n let lineBuf = \"\";\n let eventBuf = \"\";\n\n // --- JSON state ---\n const WINDOW_SIZE = 4096;\n let inputTokens = 0;\n let cacheReadTokens = 0;\n let cacheCreationTokens = 0;\n let outputTokens = 0;\n let windowBuf = \"\";\n\n // Detection: resolved after the first chunk arrives\n let isSSE: boolean | null = null;\n\n // Stream event throttling (~4 Hz)\n const STREAM_THROTTLE_MS = 250;\n let lastStreamEmit = 0;\n\n const drainEvents = (eventText: string) => {\n for (const event of eventText.split(\"\\n\\n\")) {\n if (!event) continue;\n const dataLine = event.split(\"\\n\").find(l => l.startsWith(\"data:\"));\n if (!dataLine || !dataLine.includes('\"usage\"')) continue;\n try {\n const data = JSON.parse(dataLine.slice(5)) as Record<string, unknown>;\n const usage = parseUsageFromData(data);\n if (usage.inputTokens > tokens.input) tokens.input = usage.inputTokens;\n if (usage.outputTokens > tokens.output) tokens.output = usage.outputTokens;\n } catch { /* skip malformed */ }\n }\n };\n\n const scanWindow = (text: string) => {\n const inputMatches = [...text.matchAll(/\"(?:input_tokens|prompt_tokens)\"\\s*:\\s*(\\d+)/g)];\n const cacheReadMatches = [...text.matchAll(/\"cache_read_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const cacheCreationMatches = [...text.matchAll(/\"cache_creation_input_tokens\"\\s*:\\s*(\\d+)/g)];\n const outputMatches = [...text.matchAll(/\"(?:output_tokens|completion_tokens)\"\\s*:\\s*(\\d+)/g)];\n\n if (inputMatches.length > 0) {\n const val = parseInt(inputMatches[inputMatches.length - 1][1], 10);\n if (val > inputTokens) inputTokens = val;\n }\n if (cacheReadMatches.length > 0) {\n const val = parseInt(cacheReadMatches[cacheReadMatches.length - 1][1], 10);\n if (val > cacheReadTokens) cacheReadTokens = val;\n }\n if (cacheCreationMatches.length > 0) {\n const val = parseInt(cacheCreationMatches[cacheCreationMatches.length - 1][1], 10);\n if (val > cacheCreationTokens) cacheCreationTokens = val;\n }\n if (outputMatches.length > 0) {\n const val = parseInt(outputMatches[outputMatches.length - 1][1], 10);\n if (val > outputTokens) outputTokens = val;\n }\n };\n\n const recordMetrics = (inp: number, out: number) => {\n try {\n const latencyMs = Date.now() - ctx.startTime;\n const latencySec = latencyMs / 1000;\n const tps = latencySec > 0 ? out / latencySec : 0;\n\n metricsStore.recordRequest({\n requestId: ctx.requestId,\n model: ctx.model,\n actualModel: ctx.actualModel || ctx.model,\n tier: ctx.tier,\n provider,\n targetProvider,\n status,\n inputTokens: inp,\n outputTokens: out,\n latencyMs,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n fallbackMode: ctx.fallbackMode,\n });\n\n // Broadcast completion event\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"complete\",\n status,\n latencyMs: Date.now() - ctx.startTime,\n inputTokens: inp,\n outputTokens: out,\n tokensPerSec: Math.round(tps * 10) / 10,\n timestamp: Date.now(),\n });\n });\n } catch {\n // Metrics recording errors must not affect the response stream\n }\n };\n\n const processChunk = (decoded: string, isFinal: boolean) => {\n if (isSSE === null) {\n // First chunk — detect format\n isSSE = contentType.includes(\"text/event-stream\") || decoded.startsWith(\"event:\");\n }\n\n if (isSSE) {\n lineBuf += decoded;\n const lines = lineBuf.split(\"\\n\");\n lineBuf = lines.pop()!;\n\n for (const line of lines) {\n if (line === \"\") {\n if (eventBuf) {\n drainEvents(eventBuf);\n eventBuf = \"\";\n }\n } else {\n eventBuf += (eventBuf ? \"\\n\" : \"\") + line;\n }\n }\n\n if (isFinal && eventBuf.trim()) drainEvents(eventBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const now = Date.now();\n if (now - lastStreamEmit >= STREAM_THROTTLE_MS && tokens.output > 0) {\n lastStreamEmit = now;\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens: tokens.output,\n timestamp: now,\n });\n });\n }\n\n if (isFinal) {\n recordMetrics(tokens.input, tokens.output);\n }\n } else {\n windowBuf += decoded;\n if (windowBuf.length > WINDOW_SIZE) {\n windowBuf = windowBuf.slice(-WINDOW_SIZE);\n }\n scanWindow(windowBuf);\n\n // Emit streaming progress (throttled ~4 Hz)\n const nowJson = Date.now();\n if (nowJson - lastStreamEmit >= STREAM_THROTTLE_MS && outputTokens > 0) {\n lastStreamEmit = nowJson;\n setImmediate(() => {\n broadcastStreamEvent({\n requestId: ctx.requestId,\n model: ctx.model,\n tier: ctx.tier,\n state: \"streaming\",\n outputTokens,\n timestamp: nowJson,\n });\n });\n }\n\n if (isFinal) {\n const totalInput = inputTokens + cacheReadTokens + cacheCreationTokens;\n recordMetrics(totalInput, outputTokens);\n }\n }\n };\n\n return new TransformStream({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n processChunk(td.decode(chunk, { stream: true }), false);\n },\n flush() {\n processChunk(\"\", true);\n },\n cancel() {\n // Stream was cancelled (client disconnected) — emit metrics with what we have\n processChunk(\"\", true);\n },\n });\n}\n\nexport interface AppHandle {\n app: Hono;\n getConfig: () => AppConfig;\n setConfig: (config: AppConfig) => void;\n}\n\nfunction agentKey(provider: ProviderConfig): string {\n const origin = provider._cachedOrigin;\n const size = provider.poolSize ?? 10;\n return `${origin ?? \"unknown\"}:${size}`;\n}\n\nexport function createApp(initConfig: AppConfig, logLevel: LogLevel, metricsStore?: MetricsStore): AppHandle {\n let config: AppConfig = initConfig;\n const logger = createLogger(logLevel);\n const app = new Hono();\n\n // Global error handler — returns Anthropic-compatible JSON error responses\n app.onError((err, c) => {\n console.error(`[server] Unhandled error: ${err.message}`);\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Internal proxy error\" } },\n { status: 500, headers: { \"content-type\": \"application/json\" } }\n );\n });\n\n // CORS for GUI (Tauri WebView has origin tauri://localhost)\n app.use(\"/api/*\", async (c, next) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n await next();\n });\n // Handle CORS preflight for API routes only (GUI needs CORS; proxy endpoint does not)\n app.options(\"/api/*\", (c) => {\n c.header(\"Access-Control-Allow-Origin\", \"*\");\n c.header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n c.header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization, anthropic-version, x-api-key\");\n return c.body(\"\", 200);\n });\n\n app.post(\"/v1/messages\", async (c) => {\n const requestId = randomUUID();\n\n // Read raw body once, then parse — avoids double serialization\n let body: { model?: string };\n let rawBody: string;\n try {\n rawBody = await c.req.text();\n body = JSON.parse(rawBody);\n } catch {\n return anthropicError(\"invalid_request_error\", \"Invalid JSON body\", requestId);\n }\n\n const model = body.model;\n if (!model) {\n return anthropicError(\"invalid_request_error\", \"Missing 'model' field in request body\", requestId);\n }\n\n const ctx = resolveRequest(model, requestId, config, rawBody);\n if (ctx) {\n (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody = body as Record<string, unknown>;\n }\n if (!ctx) {\n logger.info(\"No tier match\", { requestId, model });\n const configuredModels = config.modelRouting.size > 0\n ? ` Configured model routes: ${[...config.modelRouting.keys()].join(\", \")}.`\n : \"\";\n return anthropicError(\n \"invalid_request_error\",\n `No route matches model \"${model}\". Configured tiers: ${[...config.tierPatterns.keys()].join(\", \")}.${configuredModels}`,\n requestId\n );\n }\n\n logger.info(\"Routing request\", {\n requestId,\n model,\n tier: ctx.tier,\n providers: ctx.providerChain.map((e) => e.provider),\n });\n\n // Broadcast stream start event\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"start\",\n provider: ctx.providerChain[0]?.provider ?? \"unknown\",\n timestamp: Date.now(),\n });\n\n // Forward with fallback chain\n let successfulProvider = \"unknown\";\n let response: Response;\n try {\n response = await forwardWithFallback(\n config.providers,\n ctx.providerChain,\n ctx,\n c.req.raw,\n (provider, index) => {\n logger.info(\"Attempting provider\", { requestId, provider, index, tier: ctx.tier });\n // Only capture first attempted provider; accurate winner tracking requires\n // an onSuccess callback in proxy.ts (handled separately).\n if (!successfulProvider) successfulProvider = provider;\n },\n logger\n );\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logger.error(\"Forward failed\", { requestId, error: errMsg });\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: 502,\n message: errMsg,\n timestamp: Date.now(),\n });\n });\n return c.json(\n { type: \"error\", error: { type: \"api_error\", message: \"Upstream request failed: \" + errMsg } },\n 502\n );\n }\n\n // Broadcast error event for non-2xx responses\n if (response.status >= 400) {\n setImmediate(() => {\n broadcastStreamEvent({\n requestId,\n model,\n tier: ctx.tier,\n state: \"error\",\n status: response.status,\n message: `HTTP ${response.status}`,\n timestamp: Date.now(),\n });\n });\n }\n\n // Extract tokens via inline TransformStream for successful responses\n let responseBody: ReadableStream<Uint8Array> | null = response.body;\n if (response.body && response.status >= 200 && response.status < 300 && metricsStore) {\n const targetProvider = ctx.providerChain.length > 0 ? ctx.providerChain[0].provider : successfulProvider;\n const transform = createMetricsTransform(ctx, successfulProvider, targetProvider, metricsStore, response.status, response.headers.get(\"content-type\") || \"\");\n responseBody = response.body.pipeThrough(transform) as typeof responseBody;\n }\n\n // Add request ID to response (responses from fetch have immutable headers, so create new)\n const newHeaders = new Headers(response.headers);\n newHeaders.set(\"x-request-id\", requestId);\n const finalResponse = new Response(responseBody, {\n status: response.status,\n statusText: response.statusText,\n headers: newHeaders,\n });\n\n const latency = Date.now() - ctx.startTime;\n logger.info(\"Request completed\", {\n requestId,\n model,\n tier: ctx.tier,\n status: finalResponse.status,\n latencyMs: latency,\n });\n\n return finalResponse;\n });\n\n // REST endpoint for metrics summary (used by GUI on connect)\n // Returns gzip-compressed JSON when client supports it\n app.get(\"/api/metrics/summary\", async (c) => {\n if (!metricsStore) return c.json({ error: \"Metrics not enabled\" }, 503);\n const data = metricsStore.getSummary();\n const json = JSON.stringify(data);\n\n const acceptEncoding = c.req.header(\"accept-encoding\") || \"\";\n if (acceptEncoding.includes(\"gzip\") && json.length >= 1024) {\n const compressed = await gzipAsync(Buffer.from(json));\n return new Response(compressed, {\n status: 200,\n headers: {\n \"content-type\": \"application/json\",\n \"content-encoding\": \"gzip\",\n \"vary\": \"accept-encoding\",\n },\n });\n }\n\n return c.json(data);\n });\n\n // Circuit breaker status endpoint\n app.get(\"/api/circuit-breaker\", (c) => {\n const status: Record<string, { state: string; failures: number; lastFailure: string | null }> = {};\n for (const [name, provider] of config.providers) {\n const breaker = provider._circuitBreaker;\n if (breaker) {\n const s = breaker.getStatus();\n status[name] = {\n state: s.state,\n failures: s.failures,\n lastFailure: s.lastFailure ? new Date(s.lastFailure).toISOString() : null,\n };\n }\n }\n return c.json(status);\n });\n\n return {\n app,\n getConfig: () => config,\n setConfig: (newConfig: AppConfig) => {\n // Build key → agent map from old config for reuse lookup\n const oldAgents = new Map<string, import(\"undici\").Agent>();\n for (const provider of config.providers.values()) {\n if (provider._agent) {\n oldAgents.set(agentKey(provider), provider._agent);\n }\n }\n\n // For each new provider, check if we can reuse an existing agent\n const reusedKeys = new Set<string>();\n for (const provider of newConfig.providers.values()) {\n const key = agentKey(provider);\n const existingAgent = oldAgents.get(key);\n if (existingAgent) {\n // Reuse: the origin and poolSize haven't changed\n provider._agent = existingAgent;\n reusedKeys.add(key);\n }\n // else: loadConfig() already created a fresh agent for this provider\n }\n\n // Close agents that are no longer needed (removed or changed origin/poolSize)\n for (const [key, agent] of oldAgents) {\n if (!reusedKeys.has(key)) {\n agent.close();\n }\n }\n\n config = newConfig;\n clearRoutingCache();\n },\n };\n}\n","// src/router.ts\nimport type { RoutingEntry, AppConfig, RequestContext } from \"./types.js\";\n\nconst ROUTING_CACHE_MAX_SIZE = 200;\n\ninterface RoutingCacheEntry {\n tier: string;\n providerChain: RoutingEntry[];\n}\n\n/**\n * LRU cache for model-to-(tier, providerChain) lookups.\n * Map insertion order serves as LRU ordering (first = oldest).\n */\nconst routingCache = new Map<string, RoutingCacheEntry>();\n\n/**\n * Invalidate the routing cache. Called on config hot-reload.\n */\nexport function clearRoutingCache(): void {\n routingCache.clear();\n}\n\n/**\n * Match a model name to a tier using case-sensitive substring matching.\n * First tier whose patterns contain any match wins (config order = priority).\n */\nexport function matchTier(\n modelName: string,\n tierPatterns: Map<string, string[]>\n): string | null {\n for (const [tier, patterns] of tierPatterns) {\n for (const pattern of patterns) {\n if (modelName.includes(pattern)) {\n return tier;\n }\n }\n }\n return null;\n}\n\n/**\n * Get the ordered routing chain for a tier.\n */\nexport function buildRoutingChain(\n tier: string,\n routing: Map<string, RoutingEntry[]>\n): RoutingEntry[] {\n return routing.get(tier) || [];\n}\n\n/**\n * Build a RequestContext from an incoming model name and raw body.\n * Priority 1: exact model name match in modelRouting.\n * Priority 2: substring match via tierPatterns.\n * Uses an LRU cache to skip repeated resolution for the same model.\n * Returns null if no route matches.\n */\nexport function resolveRequest(\n model: string,\n requestId: string,\n config: AppConfig,\n rawBody: string\n): RequestContext | null {\n // Check LRU cache first\n const cached = routingCache.get(model);\n if (cached) {\n // Move to most-recently-used position (delete + re-insert)\n routingCache.delete(model);\n routingCache.set(model, cached);\n return {\n requestId,\n model,\n tier: cached.tier,\n providerChain: cached.providerChain,\n startTime: Date.now(),\n rawBody,\n };\n }\n\n let tier: string;\n let providerChain: RoutingEntry[];\n\n // Priority 1: exact model name match in modelRouting\n const modelChain = config.modelRouting.get(model);\n if (modelChain && modelChain.length > 0) {\n tier = \"(modelRouting)\";\n providerChain = modelChain;\n } else {\n // Priority 2: substring match via tierPatterns (existing behavior)\n const matchedTier = matchTier(model, config.tierPatterns);\n if (!matchedTier) return null;\n tier = matchedTier;\n providerChain = buildRoutingChain(tier, config.routing);\n }\n\n // Cache the resolved tier + providerChain\n if (routingCache.size >= ROUTING_CACHE_MAX_SIZE) {\n // Evict the oldest entry (first key in Map)\n const oldestKey = routingCache.keys().next().value;\n if (oldestKey !== undefined) routingCache.delete(oldestKey);\n }\n routingCache.set(model, { tier, providerChain });\n\n return {\n requestId,\n model,\n tier,\n providerChain,\n startTime: Date.now(),\n rawBody,\n };\n}\n","// src/proxy.ts\nimport type { ProviderConfig, RoutingEntry, RequestContext } from \"./types.js\";\nimport { request as undiciRequest } from \"undici\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\n/** Headers forwarded as-is to upstream */\nconst FORWARD_HEADERS = new Set([\n \"anthropic-version\",\n \"anthropic-beta\",\n \"content-type\",\n \"accept\",\n]);\n\n/** Pre-compiled regex for normalizing duplicate slashes in URL paths */\nconst MULTI_SLASH = /\\/+/g;\n\n/** Pre-compiled regex for stripping origin from URLs */\nconst STRIP_ORIGIN = /^https?:\\/\\/[^/]+/;\n\n/** Pre-compiled regexes for targeted body replacements (preserve prompt caching) */\nconst MODEL_KEY_REGEX = /\"model\"\\s*:\\s*\"([^\"]*)\"/;\nconst MAX_TOKENS_REGEX = /\"max_tokens\"\\s*:\\s*(\\d+)/;\n\n/** Module-level TextEncoder — avoids per-request allocation */\nconst textEncoder = new TextEncoder();\n\nexport function isRetriable(status: number): boolean {\n return status === 429 || status >= 500;\n}\n\nconst CONTEXT_WINDOW_PATTERNS = [\n 'context window', 'context_limit', 'token limit',\n 'prompt is too long', 'max tokens', 'input too large', 'too many tokens',\n];\n\nfunction isContextWindowError(status: number, body: string): boolean {\n if (status !== 400) return false;\n const lower = body.toLowerCase();\n return CONTEXT_WINDOW_PATTERNS.some(p => lower.includes(p));\n}\n\nfunction handleContextWindowError(status: number, body: string): Response | null {\n if (!isContextWindowError(status, body)) return null;\n\n console.warn('[context-compact] Upstream context window limit detected');\n try {\n const flagDir = path.join(os.homedir(), '.claude', 'state');\n fs.mkdirSync(flagDir, { recursive: true });\n fs.writeFileSync(path.join(flagDir, 'context-compact-needed'), Date.now().toString());\n } catch {\n // Best-effort flag write\n }\n\n const enhanced = JSON.stringify({\n type: \"error\",\n error: {\n type: \"invalid_request_error\",\n message: \"Context window limit reached. Run /compact to reduce conversation size, then retry.\",\n },\n });\n return new Response(enhanced, {\n status: 400,\n headers: { \"content-type\": \"application/json\" },\n });\n}\n\nexport function buildOutboundUrl(baseUrl: string, incomingPath: string): string {\n let basePath = \"\";\n let origin = baseUrl;\n const slashIndex = baseUrl.indexOf('/', baseUrl.indexOf('//') + 2);\n if (slashIndex !== -1) {\n origin = baseUrl.substring(0, slashIndex);\n basePath = baseUrl.substring(slashIndex);\n }\n\n let incomingQuery = \"\";\n let incomingOnly = incomingPath;\n const qIndex = incomingPath.indexOf('?');\n if (qIndex !== -1) {\n incomingOnly = incomingPath.substring(0, qIndex);\n incomingQuery = incomingPath.substring(qIndex);\n }\n\n // Deduplicate /v1 when base URL path already ends with it and incoming path starts with it.\n // e.g. baseUrl=\"https://api.fireworks.ai/inference/v1\" + path=\"/v1/chat/completions\"\n // → \"/inference/v1/chat/completions\" (not \"/inference/v1/v1/chat/completions\")\n let resolvedPath;\n if (basePath.endsWith('/v1') && incomingOnly.startsWith('/v1')) {\n resolvedPath = basePath + incomingOnly.substring(3);\n } else {\n resolvedPath = basePath + incomingOnly;\n }\n\n // Normalize duplicate slashes\n resolvedPath = resolvedPath.replace(MULTI_SLASH, \"/\");\n\n return origin + resolvedPath + incomingQuery;\n}\n\nexport function buildOutboundHeaders(\n incomingHeaders: Headers,\n provider: ProviderConfig,\n requestId: string\n): Headers {\n const headers = new Headers();\n\n // Forward select headers as-is\n for (const name of FORWARD_HEADERS) {\n const value = incomingHeaders.get(name);\n if (value) headers.set(name, value);\n }\n\n // Rewrite auth headers based on provider authType\n if (provider.authType === \"bearer\") {\n headers.set(\"Authorization\", `Bearer ${provider.apiKey}`);\n } else {\n headers.set(\"x-api-key\", provider.apiKey);\n }\n headers.set(\"x-request-id\", requestId);\n\n // Set host to provider hostname (use cached components when available)\n const cachedHost = provider._cachedHost;\n if (cachedHost) {\n headers.set(\"host\", cachedHost);\n } else {\n try {\n const url = new URL(provider.baseUrl);\n headers.set(\"host\", url.host);\n } catch {\n // If baseUrl is not a valid URL, skip host rewrite\n }\n }\n\n return headers;\n}\n\n/**\n * Remove orphaned tool_use/tool_result pairs from the messages array.\n *\n * In Anthropic's format:\n * - tool_use blocks live inside assistant message content: { role: \"assistant\", content: [{ type: \"tool_use\", id: \"call_xxx\", ... }] }\n * - tool_result blocks live inside user message content: { role: \"user\", content: [{ type: \"tool_result\", tool_use_id: \"call_xxx\", ... }] }\n *\n * A tool_result is orphaned if its tool_use_id references a tool_use not in any assistant content block.\n * A tool_use is orphaned if its id has no matching tool_result in any user content block.\n */\nfunction cleanOrphanedToolMessages(body: Record<string, unknown>): void {\n const messages = body.messages;\n if (!Array.isArray(messages)) return;\n\n // Pass 1: Collect tool_use IDs and tool_result IDs in a single pass,\n // and record which message indices have orphaned blocks\n const toolUseIds = new Set<string>();\n const toolResultIds = new Set<string>();\n const needsFiltering = new Map<number, \"user\" | \"assistant\">();\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (!Array.isArray(msg.content)) continue;\n\n if (msg.role === \"assistant\") {\n let hasOrphan = false;\n for (const block of msg.content) {\n if (block.type === \"tool_use\" && block.id) {\n toolUseIds.add(String(block.id));\n if (!toolResultIds.has(String(block.id))) hasOrphan = true;\n }\n }\n // Note: toolResultIds may not be fully populated yet, so we defer judgment\n // on assistant orphans to after the full pass.\n } else if (msg.role === \"user\") {\n let hasOrphan = false;\n for (const block of msg.content) {\n if (block.type === \"tool_result\" && block.tool_use_id) {\n toolResultIds.add(String(block.tool_use_id));\n if (!toolUseIds.has(String(block.tool_use_id))) hasOrphan = true;\n }\n }\n if (hasOrphan) needsFiltering.set(i, \"user\");\n }\n }\n\n // Check assistant messages for orphans now that toolResultIds is complete\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n const hasOrphan = msg.content.some(\n (block: Record<string, unknown>) => block.type === \"tool_use\" && !toolResultIds.has(String(block.id))\n );\n if (hasOrphan) needsFiltering.set(i, \"assistant\");\n }\n }\n\n if (needsFiltering.size === 0) return;\n\n // Pass 2: Filter out orphaned tool references from content arrays\n body.messages = messages.map((msg: Record<string, unknown>, i: number) => {\n const filterType = needsFiltering.get(i);\n if (filterType && Array.isArray(msg.content)) {\n const filtered = msg.content.filter((block: Record<string, unknown>) => {\n if (filterType === \"user\") {\n return !(block.type === \"tool_result\" && !toolUseIds.has(String(block.tool_use_id)));\n }\n return !(block.type === \"tool_use\" && !toolResultIds.has(String(block.id)));\n });\n if (filtered.length === msg.content.length) return msg; // nothing was actually filtered\n return { ...msg, content: filtered };\n }\n return msg;\n });\n\n // Pass 3: Re-check user messages after assistant cleanup.\n // After Pass 2 removed orphaned tool_use blocks from assistant messages, some\n // user tool_result blocks may now reference tool_use IDs that no longer exist.\n // Rebuild valid IDs from the cleaned messages and strip dangling user tool_results.\n const validToolUseIds = new Set<string>();\n for (const msg of body.messages as Record<string, unknown>[]) {\n if (!Array.isArray(msg.content)) continue;\n if (msg.role === \"assistant\") {\n for (const block of msg.content as Record<string, unknown>[]) {\n if (block.type === \"tool_use\" && block.id) validToolUseIds.add(String(block.id));\n }\n }\n }\n\n body.messages = (body.messages as Record<string, unknown>[]).map((msg) => {\n if (msg.role === \"user\" && Array.isArray(msg.content)) {\n const filtered = msg.content.filter(\n (block: Record<string, unknown>) =>\n !(block.type === \"tool_result\" && !validToolUseIds.has(String(block.tool_use_id)))\n );\n if (filtered.length === msg.content.length) return msg;\n return { ...msg, content: filtered };\n }\n return msg;\n });\n}\n\n/**\n * Apply targeted string replacements to rawBody to preserve prompt caching.\n * On the primary attempt (chainIndex === 0), we avoid full JSON.stringify which\n * breaks Anthropic's cache breakpoints (position-sensitive, whitespace/order-sensitive).\n * Falls back to full JSON parse/mutate/stringify when structural changes are needed.\n */\nfunction applyTargetedReplacements(\n rawBody: string,\n entry: RoutingEntry,\n provider: ProviderConfig,\n parsed: Record<string, unknown>,\n needsOrphanClean: boolean,\n): string {\n // If orphan cleaning is needed, we must do full JSON parse (structural changes to messages)\n if (needsOrphanClean) {\n // deep clone required: cleanOrphanedToolMessages mutates the messages array in-place\n const mutable = structuredClone(parsed);\n if (entry.model) mutable.model = entry.model;\n cleanOrphanedToolMessages(mutable);\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requested = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requested > maxOutputTokens) {\n mutable.max_tokens = Math.min(requested, maxOutputTokens);\n }\n }\n return JSON.stringify(mutable);\n }\n\n // Targeted replacement path -- only model override and/or max_tokens clamping\n let body = rawBody;\n\n // Model override via regex (no JSON.parse/stringify)\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n const modelMatch = MODEL_KEY_REGEX.exec(body);\n if (modelMatch) {\n body = body.replace(MODEL_KEY_REGEX, `\"model\":\"${entry.model}\"`);\n console.warn(\n `Routing override: ${modelMatch[1]} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n // max_tokens clamping\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const maxTokensMatch = MAX_TOKENS_REGEX.exec(body);\n if (maxTokensMatch) {\n const current = parseInt(maxTokensMatch[1], 10);\n if (current > maxOutputTokens) {\n body = body.replace(MAX_TOKENS_REGEX, `\"max_tokens\":${maxOutputTokens}`);\n }\n } else if (typeof parsed.max_tokens !== \"number\") {\n // max_tokens not present in body -- need to add it. Shallow clone suffices\n // since only top-level properties (model, max_tokens) are mutated.\n const mutable = { ...parsed };\n if (entry.model) mutable.model = entry.model;\n mutable.max_tokens = maxOutputTokens;\n return JSON.stringify(mutable);\n }\n }\n\n return body;\n}\n\n/**\n * Forward a request to a single provider.\n * Uses ctx.parsedBody when available (avoids re-parsing); falls back to ctx.rawBody.\n * incomingRequest is used for metadata only (url, headers).\n * Returns the Response object — caller decides fallback logic.\n */\nexport async function forwardRequest(\n provider: ProviderConfig,\n entry: RoutingEntry,\n ctx: RequestContext,\n incomingRequest: Request,\n externalSignal?: AbortSignal,\n chainIndex: number = 0,\n): Promise<Response> {\n const outgoingPath = incomingRequest.url.replace(STRIP_ORIGIN, \"\");\n\n // Set actualModel early so metrics always record the routed model,\n // even if body parsing or the fetch itself fails\n if (entry.model) {\n ctx.actualModel = entry.model;\n }\n\n // Build outbound URL from provider base URL and request path\n const url = buildOutboundUrl(provider.baseUrl, outgoingPath);\n\n // Prepare body — prefer raw passthrough to preserve upstream prompt caching.\n // Only parse and re-serialize when a modification is actually required,\n // because Anthropic's cache breakpoints are position-sensitive and\n // JSON.stringify changes whitespace / key order, breaking cache hits.\n let body: string;\n const contentType = incomingRequest.headers.get(\"content-type\") || \"\";\n\n if (contentType.includes(\"application/json\")) {\n try {\n const parsed = (ctx as RequestContext & { parsedBody?: Record<string, unknown> }).parsedBody\n ?? JSON.parse(ctx.rawBody);\n\n // Determine whether any body modification is needed\n let needsModification = false;\n\n // Check 1: Model override needed?\n if (entry.model && (parsed.model as string | undefined) !== entry.model) {\n needsModification = true;\n }\n\n // Check 2: Orphan cleaning needed? (only for fallback attempts, not primary)\n // On the primary attempt (index 0), conversation history is intact.\n // Only when falling back (index > 0) do cross-provider orphans appear.\n const needsOrphanClean = chainIndex > 0;\n if (needsOrphanClean) needsModification = true;\n\n // Check 3: max_tokens clamping needed?\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof parsed.max_tokens === \"number\" ? parsed.max_tokens : maxOutputTokens;\n if (parsed.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n needsModification = true;\n }\n }\n\n if (needsModification) {\n // On primary attempt (chainIndex === 0) without orphan cleaning, use targeted\n // string replacements to preserve prompt caching. Anthropic's cache breakpoints\n // are position-sensitive -- JSON.stringify changes whitespace/order, breaking hits.\n if (chainIndex === 0 && !needsOrphanClean) {\n body = applyTargetedReplacements(ctx.rawBody, entry, provider, parsed, false);\n } else {\n // Fallback attempts: full JSON parse/mutate/stringify (caching already broken)\n // deep clone required: cleanOrphanedToolMessages may mutate the messages array in-place\n const mutable = structuredClone(parsed);\n\n if (entry.model) {\n const originalModel = mutable.model as string | undefined;\n mutable.model = entry.model;\n if (originalModel && originalModel !== entry.model) {\n console.warn(\n `Routing override: ${originalModel} -> ${entry.model} via ${provider.name}`\n );\n }\n }\n\n if (needsOrphanClean) {\n cleanOrphanedToolMessages(mutable);\n }\n\n if (provider.modelLimits) {\n const { maxOutputTokens } = provider.modelLimits;\n const requestedMaxTokens = typeof mutable.max_tokens === \"number\" ? mutable.max_tokens : maxOutputTokens;\n if (mutable.max_tokens === undefined || requestedMaxTokens > maxOutputTokens) {\n mutable.max_tokens = Math.min(requestedMaxTokens, maxOutputTokens);\n }\n }\n\n body = JSON.stringify(mutable);\n }\n } else {\n // No modifications needed — passthrough raw body to preserve prompt caching\n body = ctx.rawBody;\n }\n } catch {\n // If body can't be parsed, send it as-is without model override\n body = ctx.rawBody;\n }\n } else {\n body = ctx.rawBody;\n }\n\n const headers = buildOutboundHeaders(incomingRequest.headers, provider, ctx.requestId);\n headers.set(\"content-length\", Buffer.byteLength(body, 'utf-8').toString());\n\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), provider.timeout);\n\n // Listen for external abort (from race cancellation) to abort this request\n if (externalSignal) {\n if (externalSignal.aborted) {\n // Already aborted — don't even start the request\n clearTimeout(timeout);\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: `Provider \"${provider.name}\" cancelled by race winner` },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n const onExternalAbort = () => {\n clearTimeout(timeout);\n };\n externalSignal.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n try {\n const undiciResponse = await undiciRequest(url, {\n method: \"POST\",\n headers,\n body,\n signal: controller.signal,\n dispatcher: provider._agent,\n });\n\n // Wrap undici response as a standard Web Response for downstream compatibility\n const response = new Response(\n undiciResponse.body as unknown as BodyInit,\n {\n status: undiciResponse.statusCode,\n headers: undiciResponse.headers as unknown as HeadersInit,\n }\n );\n\n clearTimeout(timeout);\n return response;\n } catch (error) {\n clearTimeout(timeout);\n // Network errors / timeouts — return a synthetic 502\n const message = error instanceof DOMException && error.name === \"AbortError\"\n ? `Provider \"${provider.name}\" timed out after ${provider.timeout}ms`\n : `Provider \"${provider.name}\" connection failed: ${(error as Error).message}`;\n\n const body = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message },\n });\n return new Response(body, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(body).byteLength.toString(),\n },\n });\n }\n}\n\n/**\n * Race multiple providers simultaneously. Returns the first successful response.\n * Aborts all remaining requests once a winner is found.\n */\nasync function raceProviders(\n chain: RoutingEntry[],\n providers: Map<string, ProviderConfig>,\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void },\n chainOffset: number = 0,\n): Promise<Response> {\n const sharedController = new AbortController();\n\n const races = chain.map(async (entry, index): Promise<{ response: Response; index: number }> => {\n const provider = providers.get(entry.provider);\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n // Check circuit breaker\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" skipped by circuit breaker` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n\n onAttempt?.(entry.provider, index);\n\n try {\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, sharedController.signal, index + chainOffset);\n // Record for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status);\n }\n return { response, index };\n } catch {\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(502);\n }\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Provider \"${entry.provider}\" failed` },\n });\n return {\n response: new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n }),\n index,\n };\n }\n });\n\n // Track completed promises to avoid double-processing\n const completed = new Set<Promise<{ response: Response; index: number }>>();\n const failures: { response: Response; index: number }[] = [];\n\n try {\n while (completed.size < races.length) {\n const pending = races.filter(r => !completed.has(r));\n if (pending.length === 0) break;\n\n const winner = await Promise.race(pending);\n completed.add(races[winner.index] ?? races[0]);\n\n if (winner.response.status >= 200 && winner.response.status < 300) {\n sharedController.abort();\n // Cancel bodies of already-completed losing responses to free resources\n for (const f of failures) {\n try { f.response.body?.cancel(); } catch { /* ignore */ }\n }\n return winner.response;\n }\n\n // Non-retriable error — check for context window limit before propagating\n if (!isRetriable(winner.response.status)) {\n sharedController.abort();\n if (winner.response.status === 400 && winner.response.body) {\n try {\n const errBody = await winner.response.text();\n const handled = handleContextWindowError(winner.response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: winner.response.status,\n statusText: winner.response.statusText,\n headers: winner.response.headers,\n });\n } catch {\n return winner.response;\n }\n }\n return winner.response;\n }\n\n // Retriable but not success — record and continue waiting\n failures.push(winner);\n }\n\n // All providers returned retriable errors — return the first failure\n sharedController.abort();\n if (failures.length > 0) {\n return failures[0].response;\n }\n\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n } catch {\n sharedController.abort();\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers in race failed\" },\n });\n return new Response(errBody, {\n status: 502,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n}\n\n/**\n * Try forwarding through a chain of providers.\n * Returns the first successful response, or 502 if all fail.\n */\nexport async function forwardWithFallback(\n providers: Map<string, ProviderConfig>,\n chain: RoutingEntry[],\n ctx: RequestContext,\n incomingRequest: Request,\n onAttempt?: (provider: string, index: number) => void,\n logger?: { warn: (msg: string, meta?: Record<string, unknown>) => void }\n): Promise<Response> {\n let lastResponse: Response | null = null;\n\n for (let i = 0; i < chain.length; i++) {\n const entry = chain[i];\n const provider = providers.get(entry.provider);\n\n if (!provider) {\n const errBody = JSON.stringify({\n type: \"error\",\n error: { type: \"api_error\", message: `Unknown provider: ${entry.provider}` },\n });\n lastResponse = new Response(errBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(errBody).byteLength.toString(),\n },\n });\n continue;\n }\n\n // Check circuit breaker before attempting provider\n if (provider._circuitBreaker && !provider._circuitBreaker.canProceed()) {\n logger?.warn(\"Provider skipped by circuit breaker\", { requestId: ctx.requestId, provider: entry.provider });\n continue;\n }\n\n onAttempt?.(entry.provider, i);\n\n // forwardRequest uses ctx.rawBody, so body can be re-read on each attempt\n const response = await forwardRequest(provider, entry, ctx, incomingRequest, undefined, i);\n lastResponse = response;\n\n // Record result for circuit breaker\n if (provider._circuitBreaker) {\n provider._circuitBreaker.recordResult(response.status);\n }\n\n // Success — return immediately\n if (response.status >= 200 && response.status < 300) {\n return response;\n }\n\n // Non-retriable error — check for context window limit before passing through\n if (!isRetriable(response.status)) {\n if (response.status === 400 && response.body) {\n try {\n const errBody = await response.text();\n const handled = handleContextWindowError(response.status, errBody);\n if (handled) return handled;\n // Not a context error — re-create response with buffered body\n return new Response(errBody, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n } catch {\n return response;\n }\n }\n return response;\n }\n\n // Retriable error — if there are more providers, drain body and try next\n if (i < chain.length - 1) {\n await response.body?.cancel();\n\n // On 429: race remaining providers simultaneously\n if (response.status === 429 && i + 1 < chain.length) {\n ctx.fallbackMode = \"race\";\n const remaining = chain.slice(i + 1);\n return raceProviders(remaining, providers, ctx, incomingRequest, onAttempt, logger, i + 1);\n }\n continue;\n }\n // Last provider in chain — return the error as-is (body still readable)\n return response;\n }\n\n // All providers exhausted — return the last real error response if available\n if (lastResponse) {\n return lastResponse;\n }\n\n const fallbackBody = JSON.stringify({\n type: \"error\",\n error: { type: \"overloaded_error\", message: \"All providers exhausted\" },\n });\n return new Response(fallbackBody, {\n status: 502,\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": textEncoder.encode(fallbackBody).byteLength.toString(),\n },\n });\n}\n","// src/ws.ts\nimport { WebSocketServer } from \"ws\";\nimport type { Server } from \"node:http\";\nimport type { MetricsStore } from \"./metrics.js\";\nimport type { RequestMetrics, MetricsSummary, StreamEvent } from \"./types.js\";\n\ninterface WsMessage {\n type: \"request\" | \"summary\";\n data: RequestMetrics | MetricsSummary;\n}\n\nconst PING_INTERVAL_MS = 30_000; // 30 seconds\nconst MAX_MISSED_PONGS = 2;\nconst BACKPRESSURE_THRESHOLD = 64 * 1024; // 64KB\nconst SUMMARY_DEBOUNCE_MS = 500;\n\nlet wssInstance: InstanceType<typeof import(\"ws\").WebSocketServer> | null = null;\n\nexport function attachWebSocket(server: Server, metricsStore: MetricsStore): void {\n const wss = new WebSocketServer({ server, path: \"/ws\" });\n wssInstance = wss;\n\n wss.on(\"connection\", (ws) => {\n // Send current summary as initial state\n const summary = metricsStore.getSummary();\n const initialMsg: WsMessage = { type: \"summary\", data: summary };\n ws.send(JSON.stringify(initialMsg));\n\n let pendingSummaryTimer: ReturnType<typeof setTimeout> | undefined;\n let missedPongs = 0;\n const alive = () => ws.readyState === ws.OPEN;\n\n // Subscribe to new metrics with backpressure check and debounced summary\n const unsubscribe = metricsStore.onRecord((metrics: RequestMetrics) => {\n if (!alive()) return;\n\n // Backpressure: skip send if outbound buffer is too large\n if (ws.bufferedAmount > BACKPRESSURE_THRESHOLD) {\n // Schedule a summary update instead so the client eventually catches up\n scheduleSummaryUpdate();\n return;\n }\n\n // Defer JSON.stringify + send off the critical path\n setImmediate(() => {\n if (!alive()) return;\n const msg: WsMessage = { type: \"request\", data: metrics };\n ws.send(JSON.stringify(msg));\n });\n\n scheduleSummaryUpdate();\n });\n\n function scheduleSummaryUpdate(): void {\n if (pendingSummaryTimer) return; // already scheduled\n pendingSummaryTimer = setTimeout(() => {\n pendingSummaryTimer = undefined;\n if (!alive()) return;\n const msg: WsMessage = { type: \"summary\", data: metricsStore.getSummary() };\n ws.send(JSON.stringify(msg));\n }, SUMMARY_DEBOUNCE_MS);\n }\n\n // Ping/pong heartbeat for liveness tracking\n const pingTimer = setInterval(() => {\n if (!alive()) {\n clearInterval(pingTimer);\n return;\n }\n // Terminate if client missed too many pongs\n if (missedPongs >= MAX_MISSED_PONGS) {\n cleanup(); // ensure timers and subscriber are cleaned up\n ws.terminate();\n return;\n }\n ws.ping();\n missedPongs++;\n }, PING_INTERVAL_MS);\n\n ws.on(\"pong\", () => {\n missedPongs = 0; // reset on successful pong\n });\n\n let cleanedUp = false;\n const cleanup = () => {\n if (cleanedUp) return;\n cleanedUp = true;\n clearInterval(pingTimer);\n if (pendingSummaryTimer) clearTimeout(pendingSummaryTimer);\n unsubscribe();\n };\n\n ws.on(\"close\", cleanup);\n ws.on(\"error\", cleanup);\n });\n}\n\nexport function broadcastStreamEvent(data: StreamEvent): void {\n if (!wssInstance) return;\n const msg = JSON.stringify({ type: \"stream\", data });\n for (const client of wssInstance.clients) {\n if (client.readyState === client.OPEN) {\n setImmediate(() => {\n if (client.readyState === client.OPEN) {\n client.send(msg);\n }\n });\n }\n }\n}\n","// src/metrics.ts\nimport type { RequestMetrics, MetricsSummary } from \"./types.js\";\n\ntype Subscriber = (metrics: RequestMetrics) => void;\n\nconst WS_RECENT_REQUESTS_CAP = 50;\n\ninterface ModelEntry {\n actualModel?: string;\n count: number;\n lastSeen: number;\n}\n\nexport class MetricsStore {\n private buffer: (RequestMetrics | null)[];\n private maxSize: number;\n private head = 0;\n private count = 0;\n private subscribers: Set<Subscriber>;\n private createdAt: number;\n\n // Running counters — updated incrementally in recordRequest()\n private _totalInputTokens = 0;\n private _totalOutputTokens = 0;\n private _totalTokensPerSec = 0;\n private _modelMap = new Map<string, ModelEntry>();\n private _providerMap = new Map<string, number>();\n\n constructor(maxSize: number = 1000) {\n this.buffer = new Array(maxSize).fill(null);\n this.maxSize = maxSize;\n this.subscribers = new Set();\n this.createdAt = Date.now();\n }\n\n recordRequest(metrics: RequestMetrics): void {\n const index = this.head % this.maxSize;\n const evicted = this.count >= this.maxSize ? this.buffer[index] : null;\n\n // Decrement counters for evicted entry\n if (evicted !== null) {\n this._totalInputTokens -= evicted.inputTokens ?? 0;\n this._totalOutputTokens -= evicted.outputTokens ?? 0;\n this._totalTokensPerSec -= evicted.tokensPerSec ?? 0;\n\n const mKey = evicted.model;\n const mEntry = this._modelMap.get(mKey);\n if (mEntry) {\n mEntry.count--;\n if (mEntry.count <= 0) this._modelMap.delete(mKey);\n }\n\n const pKey = evicted.targetProvider ?? evicted.provider;\n const pCount = this._providerMap.get(pKey) ?? 0;\n if (pCount <= 1) this._providerMap.delete(pKey);\n else this._providerMap.set(pKey, pCount - 1);\n }\n\n // Increment counters for new entry\n this._totalInputTokens += metrics.inputTokens ?? 0;\n this._totalOutputTokens += metrics.outputTokens ?? 0;\n this._totalTokensPerSec += metrics.tokensPerSec ?? 0;\n\n const mKey = metrics.model;\n const existing = this._modelMap.get(mKey);\n if (existing) {\n existing.count++;\n if (metrics.timestamp > existing.lastSeen) existing.lastSeen = metrics.timestamp;\n // Update actualModel to latest seen for the grouped model\n existing.actualModel = metrics.actualModel;\n } else {\n this._modelMap.set(mKey, { actualModel: metrics.actualModel, count: 1, lastSeen: metrics.timestamp });\n }\n\n const pKey = metrics.targetProvider ?? metrics.provider;\n this._providerMap.set(pKey, (this._providerMap.get(pKey) ?? 0) + 1);\n\n // Ring buffer: overwrite oldest entry when full\n this.buffer[index] = metrics;\n this.head++;\n if (this.count < this.maxSize) this.count++;\n\n // Notify subscribers (catch errors to prevent breaking recording)\n for (const cb of this.subscribers) {\n try {\n cb(metrics);\n } catch {\n // Swallow subscriber errors — recording must not break\n }\n }\n }\n\n getSummary(): MetricsSummary {\n const requests = this.getRecentRequests();\n\n const activeModels = [...this._modelMap.entries()]\n .map(([model, { actualModel, count, lastSeen }]) => ({ model, actualModel, count, lastSeen }))\n .sort((a, b) => b.count - a.count);\n\n const providerDistribution = [...this._providerMap.entries()]\n .map(([provider, count]) => ({ provider, count }))\n .sort((a, b) => b.count - a.count);\n\n // getRecentRequests() already caps at WS_RECENT_REQUESTS_CAP\n return {\n totalRequests: this.count,\n totalInputTokens: this._totalInputTokens,\n totalOutputTokens: this._totalOutputTokens,\n avgTokensPerSec: this.count > 0 ? Math.round((this._totalTokensPerSec / this.count) * 10) / 10 : 0,\n activeModels,\n providerDistribution,\n recentRequests: requests,\n uptimeSeconds: Math.floor((Date.now() - this.createdAt) / 1000),\n };\n }\n\n onRecord(callback: Subscriber): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private getRecentRequests(): RequestMetrics[] {\n if (this.count === 0) return [];\n\n // Collect only the last WS_RECENT_REQUESTS_CAP entries in reverse (newest first)\n const cap = Math.min(this.count, WS_RECENT_REQUESTS_CAP);\n const result: RequestMetrics[] = [];\n // Start from the most recently written slot and walk backward\n for (let i = 0; i < cap; i++) {\n const index = ((this.head - 1 - i) % this.maxSize + this.maxSize) % this.maxSize;\n const entry = this.buffer[index];\n if (entry !== null) {\n result.push(entry);\n }\n }\n // Reverse to get chronological order (oldest first, newest last)\n result.reverse();\n return result;\n }\n}\n","// src/monitor.ts — Monitor mode: spawns daemon child, auto-restarts on crash\nimport { spawn } from \"node:child_process\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { dirname, join as pathJoin } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { writePidFile, removePidFile, removeWorkerPidFile, getPidPath } from \"./daemon.js\";\n\nexport async function startMonitor(args: {\n config?: string;\n port?: number;\n verbose: boolean;\n}): Promise<void> {\n // Monitor writes its own PID to modelweaver.pid\n // Clean up any stale PID file left by a previous run\n const pidPath = getPidPath();\n if (existsSync(pidPath)) {\n unlinkSync(pidPath);\n }\n await writePidFile(process.pid);\n\n const entryScript =\n process.argv[1] || pathJoin(dirname(fileURLToPath(import.meta.url)), \"index.js\");\n\n // Prevent monitor from crashing on unexpected errors\n process.on(\"uncaughtException\", (err) => {\n console.error(`[monitor] Uncaught exception: ${err.message}`);\n });\n process.on(\"unhandledRejection\", (reason) => {\n console.error(`[monitor] Unhandled rejection: ${reason}`);\n });\n\n const MAX_RESTART_ATTEMPTS = 10;\n const INITIAL_BACKOFF_MS = 1000;\n const MAX_BACKOFF_MS = 30000;\n const STABLE_RUN_MS = 60000;\n let restartCount = 0;\n let stableTimer: ReturnType<typeof setTimeout> | null = null;\n let restartTimer: ReturnType<typeof setTimeout> | null = null;\n let shuttingDown = false;\n let reloading = false;\n let child: ReturnType<typeof spawn> | null = null;\n\n function spawnDaemon(): void {\n const childArgs: string[] = [entryScript, \"--daemon\"];\n if (args.config) childArgs.push(\"--config\", args.config);\n if (args.port) childArgs.push(\"--port\", String(args.port));\n if (args.verbose) childArgs.push(\"--verbose\");\n\n child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n // NOTE: do NOT child.unref() here — the monitor must stay alive to watch the child\n\n // Start stability timer — if worker lives this long, reset restart counter\n if (stableTimer) clearTimeout(stableTimer);\n stableTimer = setTimeout(() => {\n if (restartCount > 0) {\n console.error(\n `[monitor] Worker stable for ${STABLE_RUN_MS}ms, resetting restart counter`,\n );\n }\n restartCount = 0;\n stableTimer = null;\n }, STABLE_RUN_MS);\n\n child.on(\"exit\", async (code) => {\n child = null;\n\n // Clear stability timer — worker died before becoming stable\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n\n await removeWorkerPidFile();\n if (code === 0 && !reloading) {\n // Clean shutdown — monitor exits too\n await removePidFile();\n process.exit(0);\n }\n reloading = false;\n\n // Don't restart if we're shutting down\n if (shuttingDown) {\n console.error(\"[monitor] Worker exited during shutdown, monitor exiting\");\n await removePidFile();\n process.exit(0);\n }\n\n // Crash — apply exponential backoff restart\n const attempt = restartCount;\n if (attempt >= MAX_RESTART_ATTEMPTS) {\n console.error(\n `[monitor] Max restart attempts exhausted (${MAX_RESTART_ATTEMPTS}), monitor exiting`,\n );\n await removePidFile();\n process.exit(1);\n }\n\n const backoff = Math.min(INITIAL_BACKOFF_MS * 2 ** attempt, MAX_BACKOFF_MS);\n restartCount++;\n console.error(\n `[monitor] Worker died (code ${code}), restarting in ${backoff}ms (attempt ${restartCount}/${MAX_RESTART_ATTEMPTS})`,\n );\n\n restartTimer = setTimeout(spawnDaemon, backoff);\n });\n }\n\n // SIGTERM from `stop` → kill child, then exit cleanly\n // Does NOT register a second `exit` listener on the child. Instead, relies on\n // the existing child exit handler (registered in spawnDaemon) which already\n // checks `shuttingDown` and performs cleanup + process.exit(0).\n process.on(\"SIGTERM\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGINT (Ctrl-C) — same pattern as SIGTERM.\n process.on(\"SIGINT\", () => {\n shuttingDown = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (stableTimer) {\n clearTimeout(stableTimer);\n stableTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n // Child exit handler will clean up pid files and call process.exit(0).\n // Safety: if child doesn't exit within 5 s, force exit.\n setTimeout(() => {\n console.error(\"[monitor] Child did not exit within 5 s, forcing exit\");\n process.exit(0);\n }, 5000);\n } else {\n // Child already dead — clean up and exit.\n removePidFile().then(() => process.exit(0));\n }\n });\n\n // SIGHUP from `reload` → gracefully kill current worker so monitor restarts it\n // Note: SIGHUP is POSIX-only; this handler is a no-op on Windows.\n process.on(\"SIGHUP\", () => {\n console.log(\"[monitor] Received reload signal, restarting worker...\");\n reloading = true;\n if (restartTimer) {\n clearTimeout(restartTimer);\n restartTimer = null;\n }\n if (child) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n // Reset restart count — this is an intentional restart, not a crash\n restartCount = 0;\n });\n\n spawnDaemon();\n}\n"],"mappings":"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","names":["serve","readFileSync","Hono","routingCache","clearRoutingCache","matchTier","modelName","tierPatterns","tier","patterns","pattern","buildRoutingChain","routing","resolveRequest","model","requestId","config","rawBody","cached","providerChain","modelChain","matchedTier","oldestKey","undiciRequest","fs","path","os","FORWARD_HEADERS","MULTI_SLASH","STRIP_ORIGIN","MODEL_KEY_REGEX","MAX_TOKENS_REGEX","textEncoder","isRetriable","status","CONTEXT_WINDOW_PATTERNS","isContextWindowError","body","lower","p","handleContextWindowError","flagDir","enhanced","buildOutboundUrl","baseUrl","incomingPath","basePath","origin","slashIndex","incomingQuery","incomingOnly","qIndex","resolvedPath","buildOutboundHeaders","incomingHeaders","provider","requestId","headers","name","value","cachedHost","url","cleanOrphanedToolMessages","messages","toolUseIds","toolResultIds","needsFiltering","i","msg","hasOrphan","block","filterType","filtered","validToolUseIds","applyTargetedReplacements","rawBody","entry","parsed","needsOrphanClean","mutable","maxOutputTokens","requested","modelMatch","maxTokensMatch","forwardRequest","ctx","incomingRequest","externalSignal","chainIndex","outgoingPath","needsModification","requestedMaxTokens","originalModel","controller","timeout","onExternalAbort","undiciResponse","response","error","message","raceProviders","chain","providers","onAttempt","logger","chainOffset","sharedController","races","index","errBody","completed","failures","pending","r","winner","handled","forwardWithFallback","lastResponse","remaining","fallbackBody","randomUUID","gzip","promisify","WebSocketServer","PING_INTERVAL_MS","MAX_MISSED_PONGS","BACKPRESSURE_THRESHOLD","SUMMARY_DEBOUNCE_MS","wssInstance","attachWebSocket","server","metricsStore","wss","ws","initialMsg","pendingSummaryTimer","missedPongs","alive","unsubscribe","metrics","scheduleSummaryUpdate","msg","pingTimer","cleanup","cleanedUp","broadcastStreamEvent","data","client","gzipAsync","promisify","gzip","anthropicError","type","message","requestId","parseUsageFromData","data","usage","inp","out","cacheRead","cacheCreation","createMetricsTransform","ctx","provider","targetProvider","metricsStore","status","contentType","td","tokens","lineBuf","eventBuf","WINDOW_SIZE","inputTokens","cacheReadTokens","cacheCreationTokens","outputTokens","windowBuf","isSSE","STREAM_THROTTLE_MS","lastStreamEmit","drainEvents","eventText","event","dataLine","l","scanWindow","text","inputMatches","cacheReadMatches","cacheCreationMatches","outputMatches","val","recordMetrics","latencyMs","latencySec","tps","broadcastStreamEvent","processChunk","decoded","isFinal","lines","line","now","nowJson","totalInput","chunk","controller","agentKey","origin","size","createApp","initConfig","logLevel","config","logger","createLogger","app","Hono","err","c","next","randomUUID","body","rawBody","model","resolveRequest","configuredModels","e","successfulProvider","response","forwardWithFallback","index","errMsg","responseBody","transform","newHeaders","finalResponse","latency","json","compressed","name","breaker","newConfig","oldAgents","reusedKeys","key","existingAgent","agent","clearRoutingCache","MetricsStore","maxSize","metrics","index","evicted","mKey","mEntry","pKey","pCount","existing","cb","requests","activeModels","model","actualModel","count","lastSeen","a","b","providerDistribution","provider","callback","cap","result","entry","spawn","existsSync","unlinkSync","dirname","pathJoin","fileURLToPath","startMonitor","args","pidPath","getPidPath","existsSync","unlinkSync","writePidFile","entryScript","pathJoin","dirname","fileURLToPath","err","reason","MAX_RESTART_ATTEMPTS","INITIAL_BACKOFF_MS","MAX_BACKOFF_MS","STABLE_RUN_MS","restartCount","stableTimer","restartTimer","shuttingDown","reloading","child","spawnDaemon","childArgs","spawn","code","removeWorkerPidFile","removePidFile","attempt","backoff","VERSION","readFileSync","parseArgs","argv","args","i","portStr","configPath","printHelp","main","dotenv","existsSync","join","home","paths","p","quick","runInit","startDaemon","result","stopDaemon","statusDaemon","getService","installed","err","removeDaemon","launchGui","reloadDaemon","config","loadConfig","error","port","host","logLevel","metricsStore","MetricsStore","startMonitor","removeWorkerPidFile","writeWorkerPidFile","createDebouncedReload","getLogPath","reloadConfig","createWriteStream","watch","createLogger","logger","reason","logStream","handle","createApp","configWatcher","debounced","newConfig","server","serve","attachWebSocket","shutdown","tier","entries","providerList","e","model"]}
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import p from"prompts";var j=[{id:"anthropic",name:"Anthropic",baseUrl:"https://api.anthropic.com",envKey:"ANTHROPIC_API_KEY",authType:"anthropic",testPath:"/v1/messages",models:{sonnet:"claude-sonnet-4-20250514",opus:"claude-opus-4-20250514",haiku:"claude-haiku-4-5-20251001"}},{id:"openrouter",name:"OpenRouter",baseUrl:"https://openrouter.ai/api",envKey:"OPENROUTER_API_KEY",authType:"bearer",testPath:"/v1/chat/completions",models:{sonnet:"anthropic/claude-sonnet-4",opus:"anthropic/claude-opus-4",haiku:"anthropic/claude-haiku-4"}},{id:"together",name:"Together AI",baseUrl:"https://api.together.xyz",envKey:"TOGETHER_API_KEY",authType:"bearer",testPath:"/v1/chat/completions",models:{sonnet:"meta-llama/Llama-3.3-70B-Instruct-Turbo",opus:"meta-llama/Llama-3.3-70B-Instruct-Turbo",haiku:"meta-llama/Llama-3.3-70B-Instruct-Turbo"}},{id:"glm",name:"GLM (Z.ai)",baseUrl:"https://api.z.ai/api/anthropic",envKey:"GLM_API_KEY",authType:"anthropic",testPath:"/v1/messages",models:{sonnet:"claude-sonnet-4-20250514",opus:"claude-opus-4-20250514",haiku:"claude-haiku-4-5-20251001"}},{id:"minimax",name:"Minimax",baseUrl:"https://api.minimax.io/anthropic",envKey:"MINIMAX_API_KEY",authType:"anthropic",testPath:"/v1/messages",models:{sonnet:"MiniMax-M2.7",opus:"MiniMax-M2.7",haiku:"MiniMax-M2.7"}},{id:"fireworks",name:"Fireworks",baseUrl:"https://api.fireworks.ai/inference/v1",envKey:"FIREWORKS_API_KEY",authType:"bearer",testPath:"/chat/completions",models:{sonnet:"accounts/fireworks/models/claude-sonnet-4",opus:"accounts/fireworks/models/claude-opus-4",haiku:"accounts/fireworks/models/claude-haiku-4"}}];function G(){return j}function W(i){return j.find(t=>t.id===i)}import{stringify as $e}from"yaml";import{writeFileSync as te,existsSync as oe,readFileSync as be,mkdirSync as ne}from"fs";import{join as N}from"path";import{readFileSync as le,writeFileSync as ce,existsSync as H,copyFileSync as de,mkdirSync as pe,renameSync as ge,unlinkSync as me}from"fs";import{join as O,dirname as fe}from"path";import{homedir as ve}from"os";var q=O(ve(),".claude"),C=O(q,"settings.json"),he=O(q,"settings.json.bak");function Y(){return C}function z(){if(!H(C))return{};let i=le(C,"utf-8");try{return JSON.parse(i)}catch(t){throw console.error("[settings] Failed to parse settings.json:",t),t}}function V(){return H(C)?(console.log("[settings] Backing up existing settings to .bak"),de(C,he),!0):!1}function J(i,t){let o={...i};o.env={...i.env||{}};let e={ANTHROPIC_BASE_URL:t.baseUrl};for(let[n,s]of Object.entries(e))o.env[n]=s;if(t.tierModels){let n={sonnet:"ANTHROPIC_DEFAULT_SONNET_MODEL",opus:"ANTHROPIC_DEFAULT_OPUS_MODEL",haiku:"ANTHROPIC_DEFAULT_HAIKU_MODEL"};for(let[s,r]of Object.entries(n)){let a=t.tierModels[s];a&&(o.env[r]=a)}}return t.defaultModel&&(o.model=t.defaultModel),t.availableModels&&t.availableModels.length>0&&(o.availableModels=t.availableModels),o}function Q(i){pe(fe(C),{recursive:!0});let t=C+".tmp";try{ce(t,JSON.stringify(i,null,2)+`
3
+ `,"utf-8"),ge(t,C)}catch(o){console.error("[settings] Failed to write settings.json:",o);try{me(t)}catch{}throw o}}import ye from"net";var g={onCancel:()=>{console.log(`
4
+ Setup cancelled. No files were changed.`),process.exit(0)}},ie="\x1B[32m",E="\x1B[31m",m="\x1B[36m",M="\x1B[1m",d="\x1B[0m";function _(i){console.log(` ${ie}\u2713${d} ${i}`)}function ke(i){console.log(` ${E}\u2717${d} ${i}`)}function F(){console.log(`
5
+ ${"\u2500".repeat(56)}
6
+ `)}async function X(i,t,o){let e=new AbortController,n=setTimeout(()=>e.abort(),5e3),s=o.authType==="anthropic"?{"x-api-key":t,"anthropic-version":"2023-06-01","anthropic-beta":"interleaved-thinking-2025-05-14","content-type":"application/json"}:{Authorization:`Bearer ${t}`,"content-type":"application/json"};try{let r=await fetch(`${i}${o.testPath}`,{method:"POST",headers:s,body:JSON.stringify({model:o.models.sonnet,max_tokens:1,messages:[{role:"user",content:"hi"}]}),signal:e.signal});if(r.status===401||r.status===403)return{ok:!1,error:"Invalid API key"};if(r.status===200||r.status===400||r.status===429)return{ok:!0};try{if((await r.json()).error?.message?.includes("insufficient balance"))return{ok:!0}}catch{}return{ok:!1,error:`Unexpected status ${r.status}`}}catch(r){return r.name==="AbortError"?{ok:!1,error:"Request timed out"}:{ok:!1,error:"Network error \u2014 endpoint unreachable"}}finally{clearTimeout(n)}}function Z(i){let t=process.env[i.envKey];return t&&t.trim()?{found:!0,key:t.trim(),source:"environment"}:{found:!1,key:"",source:""}}function x(i,t){return t?3:i+1+1+1}function re(i,t,o){let e=[];e.push(`${m}\u250C${"\u2500".repeat(56)}\u2510${d}`),e.push(`${m}\u2502${d}${M} ModelWeaver Configuration Summary${"".padEnd(23)}${m}\u2502${d}`),e.push(`${m}\u251C${"\u2500".repeat(56)}\u2524${d}`),e.push(`${m}\u2502${d} ${M}Server:${d} ${o.host}:${o.port}${"".padEnd(48-`${o.host}:${o.port}`.length)}${m}\u2502${d}`),e.push(`${m}\u251C${"\u2500".repeat(56)}\u2524${d}`);for(let s of t){let r=i.find(l=>l.id===s.provider),a=r?r.name:s.provider,u=`${s.alias.padEnd(16)} ${a} \u2192 ${s.model}`;e.push(`${m}\u2502${d} ${u}${"".padEnd(Math.max(0,56-u.length-2))}${m}\u2502${d}`);for(let l of s.fallbacks){let c=i.find(h=>h.id===l.provider),$=` fallback: ${c?c.name:l.provider} \u2192 ${l.model}`;e.push(`${m}\u2502${d} ${$}${"".padEnd(Math.max(0,56-$.length-2))}${m}\u2502${d}`)}}return e.push(`${m}\u2514${"\u2500".repeat(56)}\u2518${d}`),e.join(`
7
+ `)}function we(i){let t=[],e=[...i.entries()];t.push(`${m}${"\u250C"+"\u2500".repeat(56)+"\u2510"}${d}`),t.push(`${m}\u2502${d}${M} Currently Configured Providers${"".padEnd(26)}${m}\u2502${d}`),t.push(`${m}\u251C${"\u2500".repeat(56)}\u2524${d}`);for(let[n,s]of e){t.push(`${m}\u2502${d} ${M}${n}${d}${"".padEnd(Math.max(0,20-n.length))} ${s.baseUrl}${"".padEnd(Math.max(0,34-s.baseUrl.length))}${m}\u2502${d}`);let r=s.envKey||"(hardcoded key)",a=Math.max(0,34-r.length-s.authType.length);t.push(`${m}\u2502${d} env: ${r} auth: ${s.authType}${"".padEnd(a)}${m}\u2502${d}`)}return t.push(`${m}${"\u2514"+"\u2500".repeat(56)+"\u2518"}${d}`),t.join(`
8
+ `)}function Pe(i,t){let o=new Set(i.map(n=>n.id)),e=[...i];for(let[n,s]of t.entries())if(!o.has(n)){let r=W(n);e.push({id:n,name:r?.name??n,baseUrl:s.baseUrl,envKey:s.envKey,apiKey:"",authType:s.authType,models:r?.models??{sonnet:"",opus:"",haiku:""}})}return e}async function U(i){let t=G();if(i?.excludeIds&&(t=t.filter(e=>!i.excludeIds.has(e.id))),t.length===0)return[];if(i?.singleSelect){let{providerId:e}=await p({type:"select",name:"providerId",message:"Select a provider:",choices:[{title:"\u2B05 Go back",value:"__back__"},...t.map(n=>({title:n.name,value:n.id,description:n.baseUrl}))]},g);return[e]}let{providerIds:o}=await p({type:"multiselect",name:"providerIds",message:"Select providers to configure:",choices:t.map(e=>({title:e.name,value:e.id,description:e.baseUrl})),min:1},g);return o}async function R(i,t,o){let e=W(i);if(!e)return console.error(` Error: Unknown provider "${i}". Skipping.`),null;let n=o?.envKey??e.envKey,s=o?.baseUrl??e.baseUrl,r=o?.authType??e.authType,a=Z(e);if(o?.envKey&&o.envKey!==e.envKey){let c=Z({...e,envKey:o.envKey});c.found&&(a=c)}if(a.found){process.stdout.write(` Testing API key for ${e.name} (from ${a.source})...`);let c=await X(s,a.key,e);if(process.stdout.write("\r"+" ".repeat(60)+"\r"),c.ok)return _(`${e.name}: using existing ${n} (${a.source})`),{id:i,name:e.name,baseUrl:s,envKey:n,apiKey:a.key,authType:r,models:e.models};console.log(` ${E}\u26A0${d} Existing ${n} (${a.source}) is invalid, please provide a new key.`)}let u=t?`[Step ${t.current} of ${t.total}] `:"",l=3;for(let c=0;c<l;c++){let{baseUrl:v}=await p({type:"text",name:"baseUrl",message:`${u}[${e.name}] Base URL:`,initial:s},g);try{new URL(v)}catch{if(console.log(" Invalid URL format. Please try again."),c<l-1)continue;let{retry:A}=await p({type:"confirm",name:"retry",message:`Retry with a valid URL? (${c+1}/${l-1} retries used)`,initial:!0},g);if(!A)break;continue}let{apiKey:$}=await p({type:"password",name:"apiKey",message:`${u}[${e.name}] API key:`},g);process.stdout.write(` Testing API key for ${e.name}...`);let h=await X(v,$,e);if(process.stdout.write("\r"+" ".repeat(60)+"\r"),h.ok)return _(`${e.name} API key accepted`),{id:i,name:e.name,baseUrl:v,envKey:n,apiKey:$,authType:r,models:e.models};if(ke(`${e.name}: ${h.error}`),c<l-1){let{retry:A}=await p({type:"confirm",name:"retry",message:`Retry? (${c+1}/${l-1} retries used)`,initial:!0},g);if(!A)break}}return console.log(` ${E}Warning:${d} ${e.name} will be skipped \u2014 max retries (${l}) exceeded.`),null}async function K(i,t,o,e=[]){let n=[...e];for(;;){let{addFallback:s}=await p({type:"confirm",name:"addFallback",message:n.length===0?`Add a fallback provider for ${i}?`:`Add another fallback provider for ${i}?`,initial:!1},g);if(!s)break;let r=o.filter(v=>v.id!==t);if(r.length===0){console.log(` ${E}No other providers available for fallback.${d}`);break}let a=r.map(v=>({title:v.name,value:v.id})),{providerId:u}=await p({type:"select",name:"providerId",message:`Select fallback provider for ${i}:`,choices:[{title:"\u2B05 Go back",value:"__back__"},...a]},g);if(u==="__back__")continue;let l=o.find(v=>v.id===u),{modelName:c}=await p({type:"text",name:"modelName",message:`[${l.name}] Fallback model name:`,initial:""},g);n.push({provider:l.id,model:c.trim()}),_(`Added fallback: ${l.id} \u2192 ${c.trim()}`)}return n}function Se(i){let t=[],e=[...i.entries()];if(t.push(`${m}${"\u250C"+"\u2500".repeat(56)+"\u2510"}${d}`),t.push(`${m}\u2502${d}${M} Currently Configured Models${"".padEnd(28)}${m}\u2502${d}`),t.push(`${m}\u251C${"\u2500".repeat(56)}\u2524${d}`),e.length===0)t.push(`${m}\u2502${d} (none)${"".padEnd(49)}${m}\u2502${d}`);else for(let[n,s]of e){let r=s[0],a=`${n.padEnd(20)} ${r.provider} \u2192 ${r.model}`;t.push(`${m}\u2502${d} ${a}${"".padEnd(Math.max(0,56-a.length-2))}${m}\u2502${d}`);for(let u=1;u<s.length;u++){let l=` fallback: ${s[u].provider} \u2192 ${s[u].model}`;t.push(`${m}\u2502${d} ${l}${"".padEnd(Math.max(0,56-l.length-2))}${m}\u2502${d}`)}}return t.push(`${m}${"\u2514"+"\u2500".repeat(56)+"\u2518"}${d}`),t.join(`
9
+ `)}async function _e(i,t){let o=[],e=t&&t.size>0;if(e)for(let[n,s]of t.entries()){let r=s[0];o.push({alias:n,provider:r.provider,model:r.model,fallbacks:s.slice(1)})}if(console.log(),console.log(" Configure models \u2014 each model gets an alias that appears in Claude Code's /model picker."),console.log(" The proxy will route requests matching the alias to the chosen provider."),console.log(),e)for(;;){console.log(Se(t)),console.log();let{action:n}=await p({type:"select",name:"action",message:"What would you like to do?",choices:[{title:"Add new model",value:"add",description:"Add a model alias routed to a provider"},...o.length>0?[{title:"Edit existing model",value:"edit",description:"Change alias, provider, or model name"}]:[],...o.length>0?[{title:"Delete model",value:"delete",description:"Remove a model alias"}]:[],{title:"Done",value:"done",description:"Continue to server configuration"}]},g);if(n==="done")break;if(n==="add"){let s=i.map(v=>({title:v.name,value:v.id})),{providerId:r}=await p({type:"select",name:"providerId",message:"Select provider:",choices:[{title:"\u2B05 Go back",value:"__back__"},...s]},g);if(r==="__back__")continue;let a=i.find(v=>v.id===r),{alias:u}=await p({type:"text",name:"alias",message:`[${a.name}] Model alias (name in /model picker):`,initial:""},g),{modelName:l}=await p({type:"text",name:"modelName",message:`[${a.name}] Actual model name (sent to provider API):`,initial:u},g),c={alias:u.trim(),provider:a.id,model:l.trim(),fallbacks:[]};c.fallbacks=await K(c.alias,c.provider,i,c.fallbacks),o.push(c),t.set(c.alias,[{provider:c.provider,model:c.model},...c.fallbacks]),_(`Added model: ${c.alias}`)}if(n==="edit"){let s=o.map(f=>({title:f.alias,value:f.alias,description:`${f.provider} \u2192 ${f.model}`})),{editAlias:r}=await p({type:"select",name:"editAlias",message:"Select model to edit:",choices:[{title:"\u2B05 Go back",value:"__back__"},...s]},g);if(r==="__back__")continue;let a=o.findIndex(f=>f.alias===r);if(a===-1)continue;let u=o[a],l=[...u.fallbacks],{alias:c}=await p({type:"text",name:"alias",message:"Model alias:",initial:u.alias},g),v=i.map(f=>({title:f.name,value:f.id})),{providerId:$}=await p({type:"select",name:"providerId",message:"Select provider:",choices:[{title:"\u2B05 Go back",value:"__back__"},...v]},g);if($==="__back__")continue;let h=i.find(f=>f.id===$),{modelName:A}=await p({type:"text",name:"modelName",message:"Actual model name:",initial:u.model},g),b=c.trim(),y=A.trim();for(;;){if(l.length===0)console.log(` ${m}No fallbacks configured.${d}`);else{console.log(` ${m}Current fallbacks:${d}`);for(let w=0;w<l.length;w++){let S=l[w],P=i.find(T=>T.id===S.provider),I=P?P.name:S.provider;console.log(` ${w+1}. ${I} \u2192 ${S.model}`)}}console.log();let f=[{title:"Add fallback",value:"add_fb"},...l.length>0?[{title:"Remove fallback",value:"remove_fb"}]:[],{title:"Done editing fallbacks",value:"done_fb"}],{fallbackAction:k}=await p({type:"select",name:"fallbackAction",message:"Manage fallbacks:",choices:f},g);if(k==="done_fb")break;if(k==="add_fb"&&(l=await K(b,h.id,i,l)),k==="remove_fb"){let w=l.map((P,I)=>{let T=i.find(ue=>ue.id===P.provider);return{title:`${T?T.name:P.provider} \u2192 ${P.model}`,value:I}}),{removeIdx:S}=await p({type:"select",name:"removeIdx",message:"Select fallback to remove:",choices:[{title:"\u2B05 Go back",value:"__back__"},...w]},g);if(S!=="__back__"){let P=l.splice(S,1);_(`Removed fallback: ${P[0].provider} \u2192 ${P[0].model}`)}}}b!==u.alias&&t.delete(u.alias),o[a]={alias:b,provider:h.id,model:y,fallbacks:l},t.set(b,[{provider:h.id,model:y},...l]),_(`Updated model: ${b} (${h.id} \u2192 ${y})`)}if(n==="delete"){let s=o.map(u=>({title:u.alias,value:u.alias,description:`${u.provider} \u2192 ${u.model}`})),{deleteAlias:r}=await p({type:"select",name:"deleteAlias",message:"Select model to delete:",choices:[{title:"\u2B05 Go back",value:"__back__"},...s]},g);if(r==="__back__")continue;let a=o.findIndex(u=>u.alias===r);a!==-1&&(o.splice(a,1),t.delete(r),_(`Deleted model: ${r}`))}F()}else{for(let n of i){let{addModel:s}=await p({type:"confirm",name:"addModel",message:`Add a model for ${n.name}?`,initial:!0},g);if(s){let r=n.models?.sonnet||n.models?.opus||n.models?.haiku||"",{alias:a}=await p({type:"text",name:"alias",message:`[${n.name}] Model alias (name in /model picker):`,initial:r||n.id},g),{modelName:u}=await p({type:"text",name:"modelName",message:`[${n.name}] Actual model name (sent to provider API):`,initial:r},g),l={alias:a.trim(),provider:n.id,model:u.trim(),fallbacks:[]};l.fallbacks=await K(l.alias,l.provider,i,l.fallbacks),o.push(l)}}for(;;){console.log();let{addMore:n}=await p({type:"confirm",name:"addMore",message:o.length===0?"Add a model?":"Add another model?",initial:o.length===0},g);if(!n)break;let s=i.map(v=>({title:v.name,value:v.id})),{providerId:r}=await p({type:"select",name:"providerId",message:"Select provider:",choices:s},g),a=i.find(v=>v.id===r),{alias:u}=await p({type:"text",name:"alias",message:`[${a.name}] Model alias:`,initial:""},g),{modelName:l}=await p({type:"text",name:"modelName",message:`[${a.name}] Actual model name:`,initial:u},g),c={alias:u.trim(),provider:a.id,model:l.trim(),fallbacks:[]};c.fallbacks=await K(c.alias,c.provider,i,c.fallbacks),o.push(c)}}return o.length===0?console.log(" No models configured."):_(`${o.length} model(s) configured`),o}function Me(i,t){return new Promise(o=>{let e=ye.createServer();e.once("error",()=>{e.close(),o(!0)}),e.once("listening",()=>{e.close(),o(!1)}),e.listen(i,t)})}async function Ce(i){if(i?.useDefaults)return{port:3456,host:"localhost"};let t=i?.stepInfo?`[Step ${i.stepInfo.current} of ${i.stepInfo.total}] `:"",{port:o}=await p({type:"number",name:"port",message:`${t}Server port:`,initial:3456},g),{host:e}=await p({type:"text",name:"host",message:`${t}Server host:`,initial:"localhost"},g);if(await Me(o,e)){console.log(` ${E}\u26A0 Warning:${d} Port ${o} is already in use on ${e}.`);let{proceed:n}=await p({type:"confirm",name:"proceed",message:"Use this port anyway?",initial:!1},g);n||(console.log(" Please choose a different port and try again."),process.exit(1))}return{port:o,host:e}}async function xe(i){if(i.length===0)return null;console.log();let{configure:t}=await p({type:"confirm",name:"configure",message:"Configure Claude Code to use ModelWeaver automatically?",initial:!0},g);if(!t)return null;let{defaultModel:o}=await p({type:"select",name:"defaultModel",message:"Select default model for Claude Code:",choices:i.map(n=>({title:n.alias,description:`${n.provider} \u2192 ${n.model}`,value:n.alias}))},g),e=i.map(n=>n.alias);return{defaultModel:o,availableModels:e}}function se(i,t,o,e,n){let s={};for(let a of t)s[a.alias]=[{provider:a.provider,model:a.model},...a.fallbacks];if(n)for(let[a,u]of n.entries())s[a]||(s[a]=u.map(l=>({provider:l.provider,model:l.model})));let r={server:o,providers:{},modelRouting:s};for(let a of i){let u={baseUrl:a.baseUrl,apiKey:`\${${a.envKey}}`,timeout:3e4};a.authType==="bearer"&&(u.authType="bearer"),r.providers[a.id]=u}if(e){let a=new Set(i.map(u=>u.id));for(let[u,l]of e.entries())if(!a.has(u)){let c={baseUrl:l.baseUrl,apiKey:l.envKey?`\${${l.envKey}}`:"",timeout:l.timeout};l.authType==="bearer"&&(c.authType="bearer"),r.providers[u]=c}}return $e(r)}function Ee(i){let t=N(process.env.HOME||process.env.USERPROFILE||"",".modelweaver"),o=N(t,".env");ne(t,{recursive:!0});let e="";oe(o)&&(e=be(o,"utf-8")),e&&!e.endsWith(`
10
+ `)&&(e+=`
11
+ `);let n=[];for(let s of i){let r=s.envKey.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),a=new RegExp(`^${r}=.*$`,"m"),u=s.apiKey.includes('"')?s.apiKey.includes("'")?`'${s.apiKey.replace(/'/g,"'\\''")}'`:`'${s.apiKey}'`:`"${s.apiKey}"`;a.test(e)?e=e.replace(a,`${s.envKey}=${u}`):n.push(`${s.envKey}=${u}`)}te(o,e+n.join(`
12
+ `)+(n.length>0?`
13
+ `:""),{mode:384})}async function Ae(){process.stdin.isTTY||(console.error("Error: modelweaver init --quick requires an interactive terminal."),process.exit(1));let{peekConfig:i}=await import("./config-FYJATRN4.js"),t=i(),o=t?.providers??new Map,e=t?.modelRouting??new Map,n=[],s=[],r,a,u=x(1,!0);for(;;){F(),console.log(`
14
+ ${M}${m}\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u2500
15
+ \u2502 Welcome to ModelWeaver! \u2501 Quick Setup \u2502
16
+ \u2502 \u2502
17
+ \u2501 ~${String(u).padEnd(3)} quick steps to get started \u2501
18
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518${d}
19
+ `);let l=await U({singleSelect:!0});if(l.length===1&&l[0]==="__back__")continue;n=[];for(let $ of l){let h=await R($,{current:1,total:u});h&&n.push(h)}n.length===0&&(console.log(`
20
+ ${E}No providers configured. Exiting.${d}
21
+ `),process.exit(1));let c=n[0];s=[];for(let[$,h]of Object.entries(c.models))h&&s.push({alias:h,provider:c.id,model:h,fallbacks:[]});r={port:3456,host:"localhost"},a=se(n,s,r,o,e),console.log(`
22
+ ${M} Generated configuration:${d}
23
+ `),console.log(re(n,s,r)),console.log(),console.log(a.split(`
24
+ `).map($=>` ${$}`).join(`
25
+ `));let{confirm:v}=await p({type:"confirm",name:"confirm",message:`[Step 2 of ${u}] Write this configuration?`,initial:!0},g);if(v)break;console.log(`
26
+ Restarting quick setup...
27
+ `)}await ae(n,s,r,a,u,!0,!!t)}async function ae(i,t,o,e,n,s,r){let a=N(process.env.HOME||process.env.USERPROFILE||"",".modelweaver");ne(a,{recursive:!0});let u=N(a,"config.yaml");oe(u)&&r?_(`Updating existing config at ${u}`):_(`Writing new config to ${u}`),te(u,e),Ee(i);try{let{readPidFile:c,isProcessAlive:v}=await import("./daemon-MGMHTCQC.js"),$=await c();if($&&v($)){if(process.platform!=="win32")try{process.kill($,"SIGUSR1")}catch{}else console.log(" Windows does not support SIGUSR1 \u2014 run 'modelweaver reload' to pick up new config.");_("ModelWeaver daemon reloaded with new config")}}catch{}let l=await xe(t);if(l){let c=o.host==="localhost"?`http://localhost:${o.port}`:`http://${o.host}:${o.port}`;V()&&console.log(" Backed up existing settings to settings.json.bak");let $=z(),h=J($,{baseUrl:c,defaultModel:l.defaultModel,availableModels:l.availableModels});Q(h),_(`Claude Code settings updated at ${Y()}`),console.log(` Proxy endpoint: ${c}`),console.log(` Default model: ${l.defaultModel}`),console.log(` Available models: ${l.availableModels.join(", ")}`),console.log(),console.log(` ${ie}Restart Claude Code to apply changes.${d}`)}return l}var B=0,L=1,D=2,ee=3;async function Ge(i){if(i?.quick)return Ae();process.stdin.isTTY||(console.error("Error: modelweaver init requires an interactive terminal."),process.exit(1));let{peekConfig:t}=await import("./config-FYJATRN4.js"),o=t(),e=o?.providers??new Map,n=o?.modelRouting??new Map,s=e.size>0,r=[],a=[],u=[],l={port:3456,host:"localhost"},c,v=null,$=s,h=B;for(;;){if(h<=B){if(F(),console.log(`
28
+ ${M}${m}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\u2500
29
+ \u2551 Welcome to ModelWeaver! \u2551
30
+ \u2551 \u2551
31
+ \u2551 This wizard will help you configure \u2551
32
+ \u2551 your multi-provider model proxy. \u2551
33
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${d}
34
+ `),s){console.log(`
35
+ ${we(e)}
36
+ `);let{action:b}=await p({type:"select",name:"action",message:"What would you like to do?",choices:[{title:"Add new provider(s)",value:"add",description:"Keep existing, add more"},{title:"Edit existing provider",value:"edit",description:"Modify settings of a configured provider"},{title:"Reconfigure all from scratch",value:"reset",description:"Start over (existing providers will be replaced)"}]},g);if(b==="add"){let y=new Set(e.keys()),f=await U({excludeIds:y});f.length===0&&console.log(" No additional providers selected. All existing providers preserved.");let k=x(e.size+f.length,!1);r=[];for(let w=0;w<f.length;w++){let S=await R(f[w],{current:1+w,total:k});S&&r.push(S)}}else if(b==="edit"){let y=[...e.keys()],{editId:f}=await p({type:"select",name:"editId",message:"Select provider to edit:",choices:[{title:"\u2B05 Go back",value:"__back__"},...y.map(P=>{let I=e.get(P);return{title:P,value:P,description:I.baseUrl}})]},g);if(f==="__back__")continue;let k=e.get(f),w=x(e.size,!1),S=await R(f,{current:1,total:w},k);r=S?[S]:[]}else{e.clear(),n.clear(),$=!1;let y=await U(),f=x(y.length,!1);r=[];for(let k=0;k<y.length;k++){let w=await R(y[k],{current:1+k,total:f});w&&r.push(w)}}r.length===0&&e.size===0&&(console.log(`
37
+ ${E}No providers configured. Exiting.${d}
38
+ `),process.exit(1))}else{let b=await U(),y=x(b.length,!1);r=[];for(let f=0;f<b.length;f++){let k=await R(b[f],{current:1+f,total:y});k&&r.push(k)}r.length===0&&(console.log(`
39
+ ${E}No providers configured. Exiting.${d}
40
+ `),process.exit(1))}u=Pe(r,e),h=L}if(h<=L){a=await _e(u,$?n:void 0);let{nav:b}=await p({type:"select",name:"nav",message:"Next step:",choices:[{title:"Continue to server configuration",value:"next",description:"Configure port and host"},{title:"Go back to provider configuration",value:"back",description:"Reconfigure providers"}]},g);if(b==="back"){h=B;continue}h=D}if(h<=D){console.log();let b=x(u.length,!1),y=u.length+2;l=await Ce({stepInfo:{current:y,total:b}});let{nav:f}=await p({type:"select",name:"nav",message:"Next step:",choices:[{title:"Review and write configuration",value:"next",description:"Confirm and save"},{title:"Go back to model configuration",value:"back",description:"Reconfigure models"}]},g);if(f==="back"){h=L;continue}h=ee}if(h<=ee){c=se(u,a,l,e,n),console.log(`
41
+ ${M} Generated configuration:${d}
42
+ `),console.log(re(u,a,l)),console.log(),console.log(c.split(`
43
+ `).map(k=>` ${k}`).join(`
44
+ `));let b=x(u.length,!1),y=u.length+3,{confirm:f}=await p({type:"confirm",name:"confirm",message:`[Step ${y} of ${b}] Write this configuration?`,initial:!0},g);if(f)break;console.log(`
45
+ Returning to server configuration...
46
+ `),h=D;continue}}let A=x(r.length,!1);v=await ae(r,a,l,c,A,!1,s),console.log(`
47
+ ${M}${m}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
48
+ \u2551 ModelWeaver is configured! \u2551
49
+ \u2551 \u2551
50
+ ${v?`\u2551 Claude Code settings have been updated. \u2551
51
+ \u2551 \u2551
52
+ \u2551 Just restart Claude Code to get started. \u2551`:`\u2551 To use with Claude Code: \u2551
53
+ \u2551 \u2551
54
+ \u2551 Terminal 1: \u2551
55
+ \u2551 modelweaver \u2551
56
+ \u2551 \u2551
57
+ \u2551 Terminal 2: \u2551
58
+ \u2551 export ANTHROPIC_BASE_URL=\\ \u2551
59
+ \u2551 http://localhost:${String(l.port).padEnd(25)}\u2551
60
+ \u2551 claude \u2551`}
61
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${d}
62
+ `)}export{Ge as runInit,X as testApiKey};
63
+ //# sourceMappingURL=init-YTOVLWSL.js.map