@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.
- package/CONTRIBUTING.md +126 -0
- package/LICENSE +201 -0
- package/README.md +289 -0
- package/dist/chunk-OMWFRIHF.js +4 -0
- package/dist/chunk-OMWFRIHF.js.map +1 -0
- package/dist/chunk-P2HBWASF.js +7 -0
- package/dist/chunk-P2HBWASF.js.map +1 -0
- package/dist/chunk-ZGVJ4FUB.js +3 -0
- package/dist/chunk-ZGVJ4FUB.js.map +1 -0
- package/dist/config-FYJATRN4.js +3 -0
- package/dist/config-FYJATRN4.js.map +1 -0
- package/dist/daemon-MGMHTCQC.js +3 -0
- package/dist/daemon-MGMHTCQC.js.map +1 -0
- package/dist/gui-launcher-ZVOVTD6C.js +7 -0
- package/dist/gui-launcher-ZVOVTD6C.js.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/init-YTOVLWSL.js +63 -0
- package/dist/init-YTOVLWSL.js.map +1 -0
- package/dist/logger-UA2A2DVX.js +3 -0
- package/dist/logger-UA2A2DVX.js.map +1 -0
- package/dist/service-6EQTZJEG.js +3 -0
- package/dist/service-6EQTZJEG.js.map +1 -0
- package/dist/service-darwin-VTVNZFTU.js +26 -0
- package/dist/service-darwin-VTVNZFTU.js.map +1 -0
- package/dist/service-linux-Y635GIUK.js +14 -0
- package/dist/service-linux-Y635GIUK.js.map +1 -0
- package/dist/service-win32-SQPTG45G.js +9 -0
- package/dist/service-win32-SQPTG45G.js.map +1 -0
- package/modelweaver.example.yaml +45 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon.ts"],"sourcesContent":["// src/daemon.ts — Daemon lifecycle management for background mode\nimport { spawn, execFile, execFileSync } from \"node:child_process\";\nimport { access, readFile, writeFile, unlink, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { createServer } from \"node:net\";\n\nfunction isWindows(): boolean {\n return process.platform === \"win32\";\n}\n\n// ---------------------------------------------------------------------------\n// Paths\n// ---------------------------------------------------------------------------\n\nconst MODELWEAVER_DIR = join(\n process.env.HOME || process.env.USERPROFILE || \"\",\n \".modelweaver\"\n);\n\n/** Override for getConfigPort — used by tests to prevent port scanning */\nlet _configPortOverride: number | null = null;\n\n/** Set a config port override (0 = skip port-based discovery). Used by tests. */\nexport function _setConfigPortOverride(port: number | null): void {\n _configPortOverride = port;\n}\n\nexport function getPidPath(): string {\n return join(MODELWEAVER_DIR, \"modelweaver.pid\");\n}\n\nexport function getLogPath(): string {\n return join(MODELWEAVER_DIR, \"modelweaver.log\");\n}\n\n// ---------------------------------------------------------------------------\n// Directory & PID helpers\n// ---------------------------------------------------------------------------\n\nexport async function ensureDir(): Promise<void> {\n try {\n await access(MODELWEAVER_DIR);\n } catch {\n await mkdir(MODELWEAVER_DIR, { recursive: true });\n }\n}\n\nexport async function writePidFile(pid: number): Promise<void> {\n await ensureDir();\n try {\n await writeFile(getPidPath(), `${pid}\\n`, { flag: 'wx' });\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') {\n // PID file already exists — another instance is likely running\n return;\n }\n throw err;\n }\n}\n\nexport async function readPidFile(): Promise<number | null> {\n const pidPath = getPidPath();\n try {\n const content = await readFile(pidPath, \"utf-8\");\n const pid = parseInt(content.trim(), 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\nexport async function removePidFile(): Promise<void> {\n const pidPath = getPidPath();\n try {\n await unlink(pidPath);\n } catch {\n // File doesn't exist — nothing to do\n }\n}\n\n// ---------------------------------------------------------------------------\n// Worker PID helpers (used by monitor to track daemon child)\n// ---------------------------------------------------------------------------\n\nexport function getWorkerPidPath(): string {\n return join(MODELWEAVER_DIR, \"modelweaver.worker.pid\");\n}\n\nexport async function writeWorkerPidFile(pid: number): Promise<void> {\n await ensureDir();\n await writeFile(getWorkerPidPath(), `${pid}\\n`);\n}\n\nexport async function readWorkerPidFile(): Promise<number | null> {\n const pidPath = getWorkerPidPath();\n try {\n const content = await readFile(pidPath, \"utf-8\");\n const pid = parseInt(content.trim(), 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\nexport async function removeWorkerPidFile(): Promise<void> {\n const pidPath = getWorkerPidPath();\n try {\n await unlink(pidPath);\n } catch {\n // File doesn't exist — nothing to do\n }\n}\n\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0); // Signal 0 = check existence without sending signal\n return true;\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Port-based process discovery (fallback when PID file is missing)\n// ---------------------------------------------------------------------------\n\n/** Find PIDs of processes listening on the given TCP port via lsof (async).\n * NOTE: lsof is unavailable on Windows — port-based discovery silently skips on that platform.\n */\nexport function findPidsOnPort(port: number): Promise<number[]> {\n return new Promise((resolve) => {\n if (isWindows()) {\n try {\n execFile(\"netstat\", [\"-ano\"], { encoding: \"utf-8\", timeout: 3000 }, (err, out) => {\n if (err) { resolve([]); return; }\n const pids: number[] = [];\n for (const line of (out || \"\").split(\"\\n\")) {\n if (line.includes(\"LISTENING\") && line.includes(`:${port}`)) {\n const parts = line.trim().split(/\\s+/);\n const pid = parseInt(parts[parts.length - 1], 10);\n if (!isNaN(pid) && pid > 0) pids.push(pid);\n }\n }\n resolve(pids);\n });\n } catch {\n resolve([]);\n return;\n }\n return;\n }\n execFile(\"lsof\", [\"-ti\", `:${port}`, \"-sTCP:LISTEN\"], {\n encoding: \"utf-8\",\n timeout: 3000,\n }, (err, out) => {\n if (err) {\n // lsof returns non-zero when nothing is listening on the port\n resolve([]);\n return;\n }\n const trimmed = (out || \"\").trim();\n resolve(trimmed ? trimmed.split(\"\\n\").map(Number).filter((n) => !isNaN(n)) : []);\n });\n });\n}\n\n/** Attempt to load the configured port from the config file (dynamic import to avoid circular deps). */\nexport async function getConfigPort(configPath?: string | null): Promise<number | null> {\n if (_configPortOverride !== null) return _configPortOverride;\n try {\n const { loadConfig } = await import(\"./config.js\");\n const { config } = loadConfig(configPath ?? undefined);\n return config.server.port;\n } catch {\n // Config file missing or invalid — fall back to default\n return 3456;\n }\n}\n\n/**\n * Kill a process tree: send SIGTERM, wait up to `timeoutMs`, then SIGKILL.\n * Handles the process and any known child (worker PID file).\n */\nasync function killProcessTree(pids: number[], timeoutMs: number = 5000): Promise<boolean> {\n for (const pid of pids) {\n try { process.kill(pid, \"SIGTERM\"); } catch { /* already dead */ }\n }\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (pids.every((p) => !isProcessAlive(p))) return true;\n await new Promise((r) => setTimeout(r, 200));\n }\n // Force kill anything still alive\n for (const pid of pids) {\n if (isWindows()) {\n try {\n execFileSync(\"taskkill\", [\"/F\", \"/PID\", String(pid), \"/T\"], { stdio: \"ignore\" });\n } catch {\n // taskkill may fail if process already exited\n }\n } else {\n try { process.kill(pid, \"SIGKILL\"); } catch { /* already dead */ }\n }\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Daemon status\n// ---------------------------------------------------------------------------\n\nexport interface DaemonStatus {\n running: boolean;\n pid?: number;\n message: string;\n}\n\nexport async function statusDaemon(portOverride?: number): Promise<DaemonStatus> {\n const pid = await readPidFile();\n if (pid === null) {\n // PID file missing — try to find the process by configured port\n const port = portOverride ?? await getConfigPort();\n if (port !== null && port > 0) {\n const portPids = await findPidsOnPort(port);\n if (portPids.length > 0) {\n const livePids = portPids.filter((p) => isProcessAlive(p));\n if (livePids.length > 0) {\n return {\n running: true,\n pid: livePids[0],\n message: `ModelWeaver is running (PID ${livePids[0]}, detected on port ${port}; PID file missing)`,\n };\n }\n }\n }\n return { running: false, message: \"ModelWeaver is not running (no PID file found)\" };\n }\n if (isProcessAlive(pid)) {\n return { running: true, pid, message: `ModelWeaver is running (PID ${pid})` };\n }\n // Stale PID file — process is dead but file remains\n await removePidFile();\n return { running: false, message: `ModelWeaver is not running (stale PID file cleaned up)` };\n}\n\n// ---------------------------------------------------------------------------\n// Daemon start\n// ---------------------------------------------------------------------------\n\nexport interface DaemonStartResult {\n success: boolean;\n pid?: number;\n message: string;\n logPath: string;\n}\n\nexport function isPortInUse(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n if (err.code === \"EADDRINUSE\") {\n resolve(true);\n } else {\n resolve(false);\n }\n });\n server.once(\"listening\", () => {\n server.close(() => resolve(false));\n });\n server.listen(port);\n });\n}\n\nexport async function startDaemon(\n configPath?: string,\n port?: number,\n verbose?: boolean,\n): Promise<DaemonStartResult> {\n // Check if already running (now uses port-based fallback too)\n const currentStatus = await statusDaemon(port);\n if (currentStatus.running) {\n return {\n success: false,\n pid: currentStatus.pid,\n message: `ModelWeaver is already running (PID ${currentStatus.pid})`,\n logPath: getLogPath(),\n };\n }\n\n // Check if port is already in use\n const effectivePort = port ?? await getConfigPort() ?? 3456;\n if (await isPortInUse(effectivePort)) {\n return {\n success: false,\n message: `Port ${effectivePort} is already in use. Is ModelWeaver or another process running on it?`,\n logPath: getLogPath(),\n };\n }\n\n // Resolve the entry script path\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // Use dist/index.js (built output) — works with both npx and direct node\n const entryScript = join(__dirname, \"index.js\");\n\n // Build args — spawn a monitor process; monitor spawns the actual daemon child\n const childArgs: string[] = [entryScript, \"--monitor\"];\n if (configPath) childArgs.push(\"--config\", configPath);\n if (port) childArgs.push(\"--port\", String(port));\n if (verbose) childArgs.push(\"--verbose\");\n\n const child = spawn(process.execPath, childArgs, {\n detached: true,\n stdio: \"ignore\",\n env: { ...process.env },\n });\n\n // Allow parent to exit independently\n child.unref();\n\n // Wait briefly for child to start and write PID file\n // (child process writes PID file in its startup sequence)\n let pid: number | undefined;\n for (let i = 0; i < 20; i++) {\n const checkPid = await readPidFile();\n if (checkPid !== null) {\n pid = checkPid;\n break;\n }\n // Sleep 100ms\n await new Promise(r => setTimeout(r, 100));\n }\n\n if (!pid) {\n return {\n success: false,\n message: \"Daemon started but PID file was not created. Check logs at \" + getLogPath(),\n logPath: getLogPath(),\n };\n }\n\n return {\n success: true,\n pid,\n message: `ModelWeaver started in background (PID ${pid})`,\n logPath: getLogPath(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Daemon stop\n// ---------------------------------------------------------------------------\n\nexport interface DaemonStopResult {\n success: boolean;\n message: string;\n}\n\nexport async function stopDaemon(portOverride?: number): Promise<DaemonStopResult> {\n const pid = await readPidFile();\n if (pid === null) {\n // PID file missing — try to find the process by configured port\n const port = portOverride ?? await getConfigPort();\n if (port !== null && port > 0) {\n const portPids = await findPidsOnPort(port);\n const livePids = portPids.filter((p) => isProcessAlive(p));\n if (livePids.length > 0) {\n // Also include the worker PID file if present\n const workerPid = await readWorkerPidFile();\n const pidsToKill = [...livePids];\n if (workerPid !== null && isProcessAlive(workerPid) && !pidsToKill.includes(workerPid)) {\n pidsToKill.push(workerPid);\n }\n await killProcessTree(pidsToKill);\n await removeWorkerPidFile();\n return {\n success: true,\n message: `ModelWeaver stopped (found on port ${port}, PIDs ${livePids.join(\", \")}; PID file was missing)`,\n };\n }\n }\n return { success: false, message: \"ModelWeaver is not running (no PID file found)\" };\n }\n\n if (!isProcessAlive(pid)) {\n // Monitor is dead — check for orphaned worker and kill it\n const workerPid = await readWorkerPidFile();\n if (workerPid !== null && isProcessAlive(workerPid)) {\n await killProcessTree([workerPid]);\n await removeWorkerPidFile();\n }\n await removePidFile();\n return { success: false, message: \"ModelWeaver is not running (stale PID file cleaned up)\" };\n }\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return { success: false, message: `Failed to stop daemon (PID ${pid})` };\n }\n\n // Wait up to 5 seconds for graceful shutdown\n const deadline = Date.now() + 5000;\n while (Date.now() < deadline) {\n if (!isProcessAlive(pid)) {\n await removePidFile();\n return { success: true, message: `ModelWeaver stopped (PID ${pid})` };\n }\n await new Promise(r => setTimeout(r, 100));\n }\n\n // Force kill if still running\n try {\n if (isWindows()) {\n execFileSync(\"taskkill\", [\"/F\", \"/PID\", String(pid), \"/T\"], { stdio: \"ignore\" });\n } else {\n process.kill(pid, \"SIGKILL\");\n }\n } catch {\n // Already dead\n }\n\n await removePidFile();\n await removeWorkerPidFile();\n return { success: true, message: `ModelWeaver force-stopped (PID ${pid})` };\n}\n\n// ---------------------------------------------------------------------------\n// Remove (stop + cleanup)\n// ---------------------------------------------------------------------------\n\nexport async function removeLogFile(): Promise<void> {\n const logPath = getLogPath();\n try {\n await unlink(logPath);\n } catch {\n // File doesn't exist — nothing to do\n }\n}\n\nexport async function removeDaemon(): Promise<DaemonStopResult> {\n const stopResult = await stopDaemon();\n await removeLogFile();\n await removeWorkerPidFile();\n return {\n success: stopResult.success || stopResult.message.includes(\"not running\"),\n message: stopResult.success\n ? \"ModelWeaver stopped and cleaned up (PID file + log file removed)\"\n : stopResult.message.includes(\"not running\")\n ? \"ModelWeaver is not running. Log file cleaned up.\"\n : stopResult.message,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Daemon reload (SIGHUP to monitor → restart worker with fresh code)\n// ---------------------------------------------------------------------------\n\nexport async function reloadDaemon(portOverride?: number): Promise<void> {\n const pid = await readPidFile();\n if (pid === null) {\n // PID file missing — try to find the process by configured port\n const port = portOverride ?? await getConfigPort();\n if (port !== null && port > 0) {\n const portPids = await findPidsOnPort(port);\n const livePids = portPids.filter((p) => isProcessAlive(p));\n if (livePids.length > 0) {\n for (const p of livePids) {\n try {\n if (isWindows()) {\n // SIGHUP not available on Windows — just inform user\n console.log(` Windows detected — reload signal not supported for PID ${p}. Use 'modelweaver stop && modelweaver start' instead.`);\n } else {\n process.kill(p, \"SIGHUP\");\n }\n } catch { /* ignore */ }\n }\n console.log(` Sent reload signal to ${livePids.length} process(es) on port ${port}.`);\n return;\n }\n }\n console.log(\" Daemon is not running.\");\n return;\n }\n\n if (!isProcessAlive(pid)) {\n await removePidFile();\n console.log(\" Daemon is not running (stale PID file cleaned up).\");\n return;\n }\n\n try {\n if (isWindows()) {\n // Windows has no SIGHUP — kill the worker directly so monitor restarts it\n const workerPid = await readWorkerPidFile();\n if (workerPid !== null) {\n process.kill(workerPid, \"SIGTERM\");\n console.log(` Killed worker (PID ${workerPid}) on Windows — monitor will restart it.`);\n } else {\n console.log(\" No worker PID file found — cannot reload.\");\n }\n } else {\n process.kill(pid, \"SIGHUP\");\n console.log(` Sent reload signal to daemon (PID ${pid}).`);\n }\n } catch {\n console.log(\" Failed to send reload signal — daemon may not be running.\");\n }\n}\n\n// ---------------------------------------------------------------------------\n// Debounced watcher\n// ---------------------------------------------------------------------------\n\nexport function createDebouncedReload(\n callback: () => void,\n debounceMs: number = 300,\n): { reload: () => void; dispose: () => void } {\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n return {\n reload() {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n callback();\n }, debounceMs);\n },\n dispose() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n };\n}\n"],"mappings":"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","names":["spawn","execFile","execFileSync","access","readFile","writeFile","unlink","mkdir","join","dirname","fileURLToPath","createServer","isWindows","MODELWEAVER_DIR","_configPortOverride","_setConfigPortOverride","port","getPidPath","getLogPath","ensureDir","writePidFile","pid","err","readPidFile","pidPath","content","removePidFile","getWorkerPidPath","writeWorkerPidFile","readWorkerPidFile","removeWorkerPidFile","isProcessAlive","findPidsOnPort","resolve","out","pids","line","parts","trimmed","n","getConfigPort","configPath","loadConfig","config","killProcessTree","timeoutMs","deadline","p","statusDaemon","portOverride","portPids","livePids","isPortInUse","server","startDaemon","verbose","currentStatus","effectivePort","__filename","__dirname","childArgs","i","checkPid","r","stopDaemon","workerPid","pidsToKill","removeLogFile","logPath","removeDaemon","stopResult","reloadDaemon","createDebouncedReload","callback","debounceMs","timer"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{readFileSync as O,existsSync as S,statSync as E}from"fs";import{join as C}from"path";import{parse as x}from"yaml";import{z as e}from"zod";import{Agent as $}from"undici";var j={failureThreshold:3,windowSeconds:60,cooldownSeconds:30},w=class{state="closed";failureTimestamps=[];openedAt=null;config;constructor(n={}){this.config={...j,...n}}canProceed(){return this.state==="closed"?!0:this.state==="open"?this.openedAt&&Date.now()-this.openedAt>=this.config.cooldownSeconds*1e3?(this.state="half-open",!0):!1:!0}recordResult(n){if(n>=200&&n<300){this.state="closed",this.failureTimestamps=[],this.openedAt=null;return}if(n!==429&&n<500)return;let t=Date.now();if(this.failureTimestamps.push(t),this.pruneOldFailures(t),this.state==="half-open"){this.state="open",this.openedAt=t;return}this.failureTimestamps.length>=this.config.failureThreshold&&(this.state="open",this.openedAt=t)}getState(){return this.state}getStatus(){return{state:this.state,failures:this.failureTimestamps.length,lastFailure:this.failureTimestamps.length>0?this.failureTimestamps[this.failureTimestamps.length-1]:null}}pruneOldFailures(n){let t=n-this.config.windowSeconds*1e3;this.failureTimestamps=this.failureTimestamps.filter(s=>s>=t)}};var B=e.object({maxOutputTokens:e.number().int().positive()}).optional(),U=e.object({baseUrl:e.string().url().refine(r=>/^https?:\/\//.test(r),"baseUrl must use http:// or https://"),apiKey:e.string().min(1,"apiKey is required"),timeout:e.number().default(3e4),authType:e.enum(["anthropic","bearer"]).default("anthropic"),modelLimits:B,poolSize:e.number().int().min(1).max(100).optional(),circuitBreaker:e.object({failureThreshold:e.number().int().min(1).optional(),windowSeconds:e.number().int().min(1).optional(),cooldownSeconds:e.number().int().min(1).optional()}).optional()}),A=e.object({provider:e.string(),model:e.string().optional()}),M=e.object({server:e.object({port:e.number().int().min(1).max(65535).default(3456),host:e.string().default("localhost")}).default({port:3456,host:"localhost"}),providers:e.record(e.string(),U),routing:e.record(e.string(),e.array(A)).default({}),tierPatterns:e.record(e.string(),e.array(e.string())).default({}),modelRouting:e.record(e.string(),e.array(A)).default({})});function P(r){return r.replace(/\$\{([^}]+)\}/g,(n,t)=>{let s=process.env[t];if(s===void 0)throw new Error(`Missing environment variable: ${t}`);return s})}function k(r){if(typeof r=="string")return P(r);if(Array.isArray(r))return r.map(k);if(r!==null&&typeof r=="object"){let n={};for(let[t,s]of Object.entries(r))n[t]=k(s);return n}return r}function R(r=process.cwd(),{skipGlobal:n=!1}={}){let t=C(r,"modelweaver.yaml");if(S(t))return t;if(!n){let s=C(process.env.HOME||process.env.USERPROFILE||"",".modelweaver","config.yaml");if(S(s))return s}return null}function I(r){let n=R(r);if(!n)return null;let t=O(n,"utf-8"),s=x(t),b=s?.providers??{},f=new Map;for(let[u,c]of Object.entries(b)){let y=String(c.apiKey??"").match(/^\$\{([^}]+)\}$/),i=y?y[1]:"";f.set(u,{baseUrl:String(c.baseUrl??""),envKey:i,authType:String(c.authType??"anthropic"),timeout:Number(c.timeout??3e4)})}let a=s?.server,p=a?{port:Number(a.port??3456),host:String(a.host??"localhost")}:null,d=new Map,m=s?.modelRouting??{};for(let[u,c]of Object.entries(m))Array.isArray(c)&&d.set(u,c.map(g=>({provider:String(g.provider??""),model:String(g.model??u)})));return{configPath:n,providers:f,server:p,modelRouting:d}}function K(r,n){let t=null;if(r)if(S(r))try{E(r).isDirectory()?t=R(r):t=r}catch{t=r}else t=r;if(t||(t=R(n)),!t)throw new Error("No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml");let s=O(t,"utf-8"),b=x(s,{customTags:[]}),f=k(b),a=M.parse(f),p=new Set(Object.keys(a.providers));for(let[i,o]of Object.entries(a.routing)){for(let l of o)if(!p.has(l.provider))throw new Error(`Routing tier "${i}" references unknown provider "${l.provider}". Available: ${[...p].join(", ")}`);if(!a.tierPatterns[i])throw new Error(`Routing tier "${i}" has no entry in tierPatterns. Add patterns for this tier.`)}for(let[i,o]of Object.entries(a.modelRouting))for(let l of o)if(!p.has(l.provider))throw new Error(`modelRouting for model "${i}" references unknown provider "${l.provider}". Available: ${[...p].join(", ")}`);let d=new Map;for(let[i,o]of Object.entries(a.providers)){let l={name:i,baseUrl:o.baseUrl,apiKey:o.apiKey,timeout:o.timeout,authType:o.authType,modelLimits:o.modelLimits?{maxOutputTokens:o.modelLimits.maxOutputTokens}:void 0};try{let v=new URL(o.baseUrl);l._cachedHost=v.host,l._cachedOrigin=`${v.protocol}//${v.host}`,l._cachedPathname=v.pathname.replace(/\/+$/,"")}catch{}let T=o.poolSize;l._agent=new $({keepAliveTimeout:3e4,keepAliveMaxTimeout:6e4,connections:T??10,allowH2:!0}),l.poolSize=T??10;let h=o.circuitBreaker;l._circuitBreaker=new w(h?{failureThreshold:h.failureThreshold,windowSeconds:h.windowSeconds,cooldownSeconds:h.cooldownSeconds}:void 0),d.set(i,l)}let m=new Map;for(let[i,o]of Object.entries(a.routing))m.set(i,o);let u=new Map;for(let[i,o]of Object.entries(a.tierPatterns))u.set(i,o);let c=new Map;if(a.modelRouting)for(let[i,o]of Object.entries(a.modelRouting))c.set(i,o);return{config:{server:{port:a.server.port,host:a.server.host},providers:d,routing:m,tierPatterns:u,modelRouting:c},configPath:t}}function V(r){let{config:n}=K(r);return n}export{P as a,R as b,I as c,K as d,V as e};
|
|
3
|
+
//# sourceMappingURL=chunk-ZGVJ4FUB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/circuit-breaker.ts"],"sourcesContent":["// src/config.ts\nimport { readFileSync, existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { z } from \"zod\";\nimport { Agent } from \"undici\";\nimport { CircuitBreaker } from \"./circuit-breaker.js\";\nimport type { AppConfig, ProviderConfig, RoutingEntry, ServerConfig } from \"./types.js\";\n\n// --- Zod schemas for raw (pre-resolution) config ---\n\nconst modelLimitsSchema = z.object({\n maxOutputTokens: z.number().int().positive(),\n}).optional();\n\nconst providerSchema = z.object({\n baseUrl: z.string().url().refine(\n (url) => /^https?:\\/\\//.test(url),\n \"baseUrl must use http:// or https://\"\n ),\n apiKey: z.string().min(1, \"apiKey is required\"),\n timeout: z.number().default(30000),\n authType: z.enum([\"anthropic\", \"bearer\"]).default(\"anthropic\"),\n modelLimits: modelLimitsSchema,\n poolSize: z.number().int().min(1).max(100).optional(),\n circuitBreaker: z.object({\n failureThreshold: z.number().int().min(1).optional(),\n windowSeconds: z.number().int().min(1).optional(),\n cooldownSeconds: z.number().int().min(1).optional(),\n }).optional(),\n});\n\nconst routingEntrySchema = z.object({\n provider: z.string(),\n model: z.string().optional(),\n});\n\nconst rawConfigSchema = z.object({\n server: z\n .object({\n port: z.number().int().min(1).max(65535).default(3456),\n host: z.string().default(\"localhost\"),\n })\n .default({ port: 3456, host: \"localhost\" }),\n providers: z.record(z.string(), providerSchema),\n routing: z.record(z.string(), z.array(routingEntrySchema)).default({}),\n tierPatterns: z.record(z.string(), z.array(z.string())).default({}),\n modelRouting: z.record(z.string(), z.array(routingEntrySchema)).default({}),\n});\n\n// --- Env var resolution ---\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, varName) => {\n const envValue = process.env[varName];\n if (envValue === undefined) {\n throw new Error(`Missing environment variable: ${varName}`);\n }\n return envValue;\n });\n}\n\nfunction resolveAllEnvStrings(obj: unknown): unknown {\n if (typeof obj === \"string\") return resolveEnvVars(obj);\n if (Array.isArray(obj)) return obj.map(resolveAllEnvStrings);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n result[key] = resolveAllEnvStrings(val);\n }\n return result;\n }\n return obj;\n}\n\n// --- Config file discovery ---\n\nexport function findConfigFile(cwd: string = process.cwd(), { skipGlobal = false } = {}): string | null {\n const localPath = join(cwd, \"modelweaver.yaml\");\n if (existsSync(localPath)) return localPath;\n if (!skipGlobal) {\n const globalPath = join(\n process.env.HOME || process.env.USERPROFILE || \"\",\n \".modelweaver\",\n \"config.yaml\"\n );\n if (existsSync(globalPath)) return globalPath;\n }\n return null;\n}\n\n// --- Lightweight peek (no env resolution, no Zod validation) ---\n\n/** Peek at existing config to extract provider metadata without resolving env vars or validating.\n * Used by init wizard to show existing providers and offer add/edit. */\nexport function peekConfig(\n cwd?: string,\n): { configPath: string; providers: Map<string, { baseUrl: string; envKey: string; authType: \"anthropic\" | \"bearer\"; timeout: number }>; server: { port: number; host: string } | null; modelRouting: Map<string, { provider: string; model: string }[]> } | null {\n const configPath = findConfigFile(cwd);\n if (!configPath) return null;\n\n const raw = readFileSync(configPath, \"utf-8\");\n const parsed = parseYaml(raw) as Record<string, unknown>;\n const providersRaw = (parsed?.providers ?? {}) as Record<string, Record<string, unknown>>;\n\n const providers = new Map<string, { baseUrl: string; envKey: string; authType: \"anthropic\" | \"bearer\"; timeout: number }>();\n\n for (const [id, config] of Object.entries(providersRaw)) {\n const apiKey = String(config.apiKey ?? \"\");\n const envMatch = apiKey.match(/^\\$\\{([^}]+)\\}$/);\n const envKey = envMatch ? envMatch[1] : \"\";\n\n providers.set(id, {\n baseUrl: String(config.baseUrl ?? \"\"),\n envKey,\n authType: String(config.authType ?? \"anthropic\") as \"anthropic\" | \"bearer\",\n timeout: Number(config.timeout ?? 30000),\n });\n }\n\n const serverRaw = parsed?.server as Record<string, unknown> | undefined;\n const server = serverRaw ? {\n port: Number(serverRaw.port ?? 3456),\n host: String(serverRaw.host ?? \"localhost\"),\n } : null;\n\n // Parse modelRouting (alias -> provider chain)\n const modelRouting = new Map<string, { provider: string; model: string }[]>();\n const modelRoutingRaw = (parsed?.modelRouting ?? {}) as Record<string, { provider: string; model: string }[]>;\n for (const [alias, entries] of Object.entries(modelRoutingRaw)) {\n if (Array.isArray(entries)) {\n modelRouting.set(alias, entries.map(e => ({ provider: String(e.provider ?? \"\"), model: String(e.model ?? alias) })));\n }\n }\n\n return { configPath, providers, server, modelRouting };\n}\n\n// --- Load & validate ---\n\nexport function loadConfig(configPath?: string, cwd?: string): { config: AppConfig; configPath: string } {\n let path: string | null = null;\n if (configPath) {\n // If configPath is a directory, search for config file within it\n if (existsSync(configPath)) {\n try {\n const stat = statSync(configPath);\n if (stat.isDirectory()) {\n path = findConfigFile(configPath);\n } else {\n path = configPath;\n }\n } catch {\n path = configPath;\n }\n } else {\n path = configPath;\n }\n }\n if (!path) {\n path = findConfigFile(cwd);\n }\n if (!path) {\n throw new Error(\n \"No config file found. Create modelweaver.yaml in your project root or ~/.modelweaver/config.yaml\"\n );\n }\n\n const raw = readFileSync(path, \"utf-8\");\n const parsed = parseYaml(raw, { customTags: [] });\n\n // Resolve ${VAR} references in all string values\n const resolved = resolveAllEnvStrings(parsed) as z.infer<typeof rawConfigSchema>;\n\n const validated = rawConfigSchema.parse(resolved);\n\n // Cross-validation\n const providerNames = new Set(Object.keys(validated.providers));\n\n for (const [tier, entries] of Object.entries(validated.routing)) {\n for (const entry of entries) {\n if (!providerNames.has(entry.provider)) {\n throw new Error(\n `Routing tier \"${tier}\" references unknown provider \"${entry.provider}\". Available: ${[...providerNames].join(\", \")}`\n );\n }\n }\n\n if (!validated.tierPatterns[tier]) {\n throw new Error(\n `Routing tier \"${tier}\" has no entry in tierPatterns. Add patterns for this tier.`\n );\n }\n }\n\n // Cross-validate modelRouting provider references\n for (const [modelName, entries] of Object.entries(validated.modelRouting)) {\n for (const entry of entries) {\n if (!providerNames.has(entry.provider)) {\n throw new Error(\n `modelRouting for model \"${modelName}\" references unknown provider \"${entry.provider}\". Available: ${[...providerNames].join(\", \")}`\n );\n }\n }\n }\n\n // Build typed config — cache parsed URL components per provider (avoids per-request URL parsing)\n const providers = new Map<string, ProviderConfig>();\n for (const [name, p] of Object.entries(validated.providers)) {\n const providerConfig: ProviderConfig = {\n name,\n baseUrl: p.baseUrl,\n apiKey: p.apiKey,\n timeout: p.timeout,\n authType: p.authType,\n modelLimits: p.modelLimits ? { maxOutputTokens: p.modelLimits.maxOutputTokens } : undefined,\n };\n try {\n const parsedUrl = new URL(p.baseUrl);\n providerConfig._cachedHost = parsedUrl.host;\n providerConfig._cachedOrigin = `${parsedUrl.protocol}//${parsedUrl.host}`;\n providerConfig._cachedPathname = parsedUrl.pathname.replace(/\\/+$/, \"\");\n } catch {\n // If baseUrl is invalid, skip caching — buildOutboundHeaders will fall back gracefully\n }\n // Create per-provider connection pool for HTTP keep-alive reuse\n const poolSize = (p as Record<string, unknown>).poolSize as number | undefined;\n providerConfig._agent = new Agent({\n keepAliveTimeout: 30000,\n keepAliveMaxTimeout: 60000,\n connections: poolSize ?? 10,\n allowH2: true,\n });\n providerConfig.poolSize = poolSize ?? 10;\n // Create per-provider circuit breaker\n const cbConfig = (p as Record<string, unknown>).circuitBreaker as Record<string, number> | undefined;\n providerConfig._circuitBreaker = new CircuitBreaker(cbConfig ? {\n failureThreshold: cbConfig.failureThreshold,\n windowSeconds: cbConfig.windowSeconds,\n cooldownSeconds: cbConfig.cooldownSeconds,\n } : undefined);\n providers.set(name, providerConfig);\n }\n\n const routing = new Map<string, RoutingEntry[]>();\n for (const [tier, entries] of Object.entries(validated.routing)) {\n routing.set(tier, entries);\n }\n\n const tierPatterns = new Map<string, string[]>();\n for (const [tier, patterns] of Object.entries(validated.tierPatterns)) {\n tierPatterns.set(tier, patterns);\n }\n\n const modelRouting = new Map<string, RoutingEntry[]>();\n if (validated.modelRouting) {\n for (const [model, entries] of Object.entries(validated.modelRouting)) {\n modelRouting.set(model, entries);\n }\n }\n\n const server: ServerConfig = {\n port: validated.server.port,\n host: validated.server.host,\n };\n\n const config: AppConfig = { server, providers, routing, tierPatterns, modelRouting };\n return { config, configPath: path };\n}\n\n// --- Reload helper ---\n\nexport function reloadConfig(configPath: string): AppConfig {\n const { config } = loadConfig(configPath);\n return config;\n}\n","// src/circuit-breaker.ts\nexport type BreakerState = \"closed\" | \"open\" | \"half-open\";\n\nexport interface BreakerConfig {\n failureThreshold: number;\n windowSeconds: number;\n cooldownSeconds: number;\n}\n\nexport interface BreakerStatus {\n state: BreakerState;\n failures: number;\n lastFailure: number | null;\n}\n\nconst DEFAULT_CONFIG: BreakerConfig = {\n failureThreshold: 3,\n windowSeconds: 60,\n cooldownSeconds: 30,\n};\n\nexport class CircuitBreaker {\n private state: BreakerState = \"closed\";\n private failureTimestamps: number[] = [];\n private openedAt: number | null = null;\n private readonly config: BreakerConfig;\n\n constructor(config: Partial<BreakerConfig> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n canProceed(): boolean {\n if (this.state === \"closed\") return true;\n if (this.state === \"open\") {\n // Check if cooldown has elapsed\n if (this.openedAt && Date.now() - this.openedAt >= this.config.cooldownSeconds * 1000) {\n this.state = \"half-open\";\n return true; // Allow one probe request\n }\n return false;\n }\n // half-open: allow one probe\n return true;\n }\n\n recordResult(status: number): void {\n if (status >= 200 && status < 300) {\n // Success — reset to closed\n this.state = \"closed\";\n this.failureTimestamps = [];\n this.openedAt = null;\n return;\n }\n\n // Only count retriable errors (429, 5xx) as failures\n if (status !== 429 && status < 500) return;\n\n const now = Date.now();\n this.failureTimestamps.push(now);\n this.pruneOldFailures(now);\n\n if (this.state === \"half-open\") {\n // Any failure in half-open → back to open\n this.state = \"open\";\n this.openedAt = now;\n return;\n }\n\n // Check if threshold exceeded\n if (this.failureTimestamps.length >= this.config.failureThreshold) {\n this.state = \"open\";\n this.openedAt = now;\n }\n }\n\n getState(): BreakerState {\n return this.state;\n }\n\n getStatus(): BreakerStatus {\n return {\n state: this.state,\n failures: this.failureTimestamps.length,\n lastFailure: this.failureTimestamps.length > 0\n ? this.failureTimestamps[this.failureTimestamps.length - 1]\n : null,\n };\n }\n\n private pruneOldFailures(now: number): void {\n const cutoff = now - this.config.windowSeconds * 1000;\n this.failureTimestamps = this.failureTimestamps.filter((t) => t >= cutoff);\n }\n}\n"],"mappings":"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","names":["readFileSync","existsSync","statSync","join","parseYaml","z","Agent","DEFAULT_CONFIG","CircuitBreaker","config","status","now","cutoff","t","modelLimitsSchema","z","providerSchema","url","routingEntrySchema","rawConfigSchema","resolveEnvVars","value","_","varName","envValue","resolveAllEnvStrings","obj","result","key","val","findConfigFile","cwd","skipGlobal","localPath","join","existsSync","globalPath","peekConfig","configPath","raw","readFileSync","parsed","parseYaml","providersRaw","providers","id","config","envMatch","envKey","serverRaw","server","modelRouting","modelRoutingRaw","alias","entries","e","loadConfig","path","statSync","resolved","validated","providerNames","tier","entry","modelName","name","p","providerConfig","parsedUrl","poolSize","Agent","cbConfig","CircuitBreaker","routing","tierPatterns","patterns","model","reloadConfig"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v}from"./chunk-P2HBWASF.js";export{a as _setConfigPortOverride,v as createDebouncedReload,d as ensureDir,m as findPidsOnPort,n as getConfigPort,c as getLogPath,b as getPidPath,h as getWorkerPidPath,p as isPortInUse,l as isProcessAlive,f as readPidFile,j as readWorkerPidFile,u as reloadDaemon,t as removeDaemon,s as removeLogFile,g as removePidFile,k as removeWorkerPidFile,q as startDaemon,o as statusDaemon,r as stopDaemon,e as writePidFile,i as writeWorkerPidFile};
|
|
3
|
+
//# sourceMappingURL=daemon-MGMHTCQC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createWriteStream as y,mkdirSync as m,existsSync as h,readFileSync as v,writeFileSync as x,chmodSync as C,unlinkSync as $}from"fs";import{join as u,dirname as f}from"path";import{homedir as S,platform as b,arch as I}from"os";import{get as d}from"https";import{spawn as l,execSync as i}from"child_process";var g=u(S(),".modelweaver","gui"),c=u(g,".version"),U="kianwoon/modelweaver",_=`https://api.github.com/repos/${U}/releases/latest`;function G(){let r=b(),n=I();if(r==="darwin")return n==="arm64"?"darwin-aarch64":"darwin-x64";if(r==="win32")return"windows-x64";if(r==="linux")return"linux-x64";throw new Error(`Unsupported platform: ${r} ${n}`)}function P(r,n){let s={"darwin-aarch64":e=>e.endsWith(".dmg")&&e.includes("aarch64"),"darwin-x64":e=>e.endsWith(".dmg")&&(e.includes("x64")||e.includes("x86_64")),"linux-x64":e=>e.endsWith(".AppImage"),"windows-x64":e=>e.endsWith(".msi")}[n];if(!s)return null;let o=r.find(e=>s(e.name));return o?{name:o.name,url:o.browser_download_url}:null}function W(){try{if(h(c))return v(c,"utf-8").trim()}catch{}return null}function A(r){m(f(c),{recursive:!0}),x(c,r,"utf-8")}function p(r,n=5){return new Promise((a,s)=>{d(r,{headers:{"User-Agent":"modelweaver"}},o=>{if(o.statusCode&&o.statusCode>=300&&o.statusCode<400&&o.headers.location){if(n<=0){s(new Error("Too many redirects"));return}p(o.headers.location,n-1).then(a).catch(s);return}if(o.statusCode!==200){s(new Error(`HTTP ${o.statusCode} for ${r}`));return}let e=[];o.on("data",t=>e.push(t)),o.on("end",()=>{try{a(JSON.parse(Buffer.concat(e).toString()))}catch(t){s(t)}})}).on("error",s)})}async function k(r,n){return m(f(n),{recursive:!0}),new Promise((a,s)=>{let o=y(n);d(r,{headers:{"User-Agent":"modelweaver"}},e=>{if(e.statusCode&&e.statusCode>=300&&e.statusCode<400&&e.headers.location){d(e.headers.location,t=>{if(!t.statusCode||t.statusCode!==200){s(new Error(`Download failed: HTTP ${t.statusCode}`));return}t.pipe(o),o.on("finish",()=>{o.close(),a()})}).on("error",s);return}e.pipe(o),o.on("finish",()=>{o.close(),a()})}).on("error",e=>{try{$(n)}catch{}s(e)})})}function E(r,n){switch(r){case"darwin-aarch64":case"darwin-x64":console.log(" Opening GUI..."),i(`hdiutil attach "${n}" -nobrowse -quiet 2>/dev/null || true`);try{let o=i('hdiutil info | grep -A1 "image-path" | grep "/Volumes" | sed "s/.*\\(\\/Volumes.*\\)/\\1/"').toString().trim().split(`
|
|
3
|
+
`).filter(Boolean),e=o[o.length-1];if(e){let t=i(`ls -d "${e}/"*.app 2>/dev/null || true`).toString().trim().split(`
|
|
4
|
+
`)[0];if(t){i(`cp -R "${t}" /Applications/ 2>/dev/null || true`);let w=t.split("/").pop();l("open",["-a",w],{detached:!0,stdio:"ignore"}).unref(),i(`hdiutil detach "${e}" -quiet 2>/dev/null || true`),console.log(" ModelWeaver GUI launched!");return}}}catch{}i('hdiutil detach "/Volumes/ModelWeaver" -quiet 2>/dev/null || true'),l("open",[n],{detached:!0,stdio:"ignore"}).unref(),console.log(" ModelWeaver GUI launched (DMG opened)!");break;case"linux-x64":{C(n,493),l(n,[],{detached:!0,stdio:"ignore"}).unref(),console.log(" ModelWeaver GUI launched!");break}case"windows-x64":l("msiexec",["/i",n,"/passive"],{detached:!0,stdio:"ignore",shell:!0}).unref(),console.log(" ModelWeaver GUI installer launched!");break}}async function O(){console.log(`
|
|
5
|
+
ModelWeaver GUI Launcher`),console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6
|
+
`);let r=G();console.log(` Platform: ${r}`);let n=W();n&&console.log(` Cached version: ${n}`),console.log(" Checking for latest release...");let a;try{a=await p(_)}catch(t){console.error(` Failed to fetch release info: ${t.message}`),console.error(" Make sure you have internet access."),process.exit(1)}let s=a.tag_name;console.log(` Latest version: ${s}`);let o=P(a.assets,r);if(!o){console.error(` No binary found for platform: ${r}`),console.error(" Available assets:");for(let t of a.assets)console.error(` - ${t.name}`);process.exit(1)}let e=u(g,o.name);if(n===s&&h(e))console.log(` Using cached binary: ${o.name}`);else{console.log(` Downloading: ${o.name}`),console.log(` Size: ~${o.name.length>30?"large":"small"} download`);try{await k(o.url,e),A(s),console.log(" Download complete!")}catch(t){console.error(` Download failed: ${t.message}`),process.exit(1)}}E(r,e)}export{O as launchGui};
|
|
7
|
+
//# sourceMappingURL=gui-launcher-ZVOVTD6C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gui-launcher.ts"],"sourcesContent":["// src/gui-launcher.ts\nimport { createWriteStream, mkdirSync, existsSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { homedir, platform, arch } from 'node:os';\nimport { get } from 'node:https';\nimport { createUnzip } from 'node:zlib';\nimport { spawn, execSync, type ChildProcess } from 'node:child_process';\nimport { pipeline } from 'node:stream/promises';\nimport { createInterface } from 'node:readline';\n\nconst GUI_DIR = join(homedir(), '.modelweaver', 'gui');\nconst VERSION_FILE = join(GUI_DIR, '.version');\nconst REPO = 'kianwoon/modelweaver';\nconst API_URL = `https://api.github.com/repos/${REPO}/releases/latest`;\n\ninterface GitHubRelease {\n tag_name: string;\n assets: Array<{ name: string; browser_download_url: string }>;\n}\n\nfunction detectPlatform(): string {\n const p = platform();\n const a = arch();\n if (p === 'darwin') return a === 'arm64' ? 'darwin-aarch64' : 'darwin-x64';\n if (p === 'win32') return 'windows-x64';\n if (p === 'linux') return 'linux-x64';\n throw new Error(`Unsupported platform: ${p} ${a}`);\n}\n\nfunction matchAsset(assets: Array<{ name: string; browser_download_url: string }>, platformId: string): { name: string; url: string } | null {\n const patterns: Record<string, (name: string) => boolean> = {\n 'darwin-aarch64': (n) => n.endsWith('.dmg') && n.includes('aarch64'),\n 'darwin-x64': (n) => n.endsWith('.dmg') && (n.includes('x64') || n.includes('x86_64')),\n 'linux-x64': (n) => n.endsWith('.AppImage'),\n 'windows-x64': (n) => n.endsWith('.msi'),\n };\n\n const matcher = patterns[platformId];\n if (!matcher) return null;\n\n const asset = assets.find((a) => matcher(a.name));\n return asset ? { name: asset.name, url: asset.browser_download_url } : null;\n}\n\nfunction getCachedVersion(): string | null {\n try {\n if (existsSync(VERSION_FILE)) {\n return readFileSync(VERSION_FILE, 'utf-8').trim();\n }\n } catch { /* ignore */ }\n return null;\n}\n\nfunction setCachedVersion(version: string): void {\n mkdirSync(dirname(VERSION_FILE), { recursive: true });\n writeFileSync(VERSION_FILE, version, 'utf-8');\n}\n\nfunction fetchJSON(url: string, maxRedirects = 5): Promise<any> {\n return new Promise((resolve, reject) => {\n get(url, { headers: { 'User-Agent': 'modelweaver' } }, (res) => {\n if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {\n if (maxRedirects <= 0) {\n reject(new Error(\"Too many redirects\"));\n return;\n }\n fetchJSON(res.headers.location, maxRedirects - 1).then(resolve).catch(reject);\n return;\n }\n if (res.statusCode !== 200) {\n reject(new Error(`HTTP ${res.statusCode} for ${url}`));\n return;\n }\n const chunks: Buffer[] = [];\n res.on('data', (chunk: Buffer) => chunks.push(chunk));\n res.on('end', () => {\n try { resolve(JSON.parse(Buffer.concat(chunks).toString())); }\n catch (e) { reject(e); }\n });\n }).on('error', reject);\n });\n}\n\nasync function downloadFile(url: string, dest: string): Promise<void> {\n mkdirSync(dirname(dest), { recursive: true });\n\n return new Promise((resolve, reject) => {\n const file = createWriteStream(dest);\n get(url, { headers: { 'User-Agent': 'modelweaver' } }, (res) => {\n if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {\n // Follow redirect\n get(res.headers.location, (redirectRes) => {\n if (!redirectRes.statusCode || redirectRes.statusCode !== 200) {\n reject(new Error(`Download failed: HTTP ${redirectRes.statusCode}`));\n return;\n }\n redirectRes.pipe(file);\n file.on('finish', () => { file.close(); resolve(); });\n }).on('error', reject);\n return;\n }\n res.pipe(file);\n file.on('finish', () => { file.close(); resolve(); });\n }).on('error', (err) => {\n // Clean up partial file on error\n try { unlinkSync(dest); } catch { /* already gone */ }\n reject(err);\n });\n });\n}\n\nfunction launchBinary(platformId: string, assetPath: string): void {\n switch (platformId) {\n case 'darwin-aarch64':\n case 'darwin-x64':\n // macOS: mount DMG, copy .app to /Applications, launch it\n console.log(' Opening GUI...');\n execSync(`hdiutil attach \"${assetPath}\" -nobrowse -quiet 2>/dev/null || true`);\n // Try to find and copy the .app\n try {\n const mounts = execSync('hdiutil info | grep -A1 \"image-path\" | grep \"/Volumes\" | sed \"s/.*\\\\(\\\\/Volumes.*\\\\)/\\\\1/\"').toString().trim().split('\\n').filter(Boolean);\n const mountPoint = mounts[mounts.length - 1];\n if (mountPoint) {\n const appPath = execSync(`ls -d \"${mountPoint}/\"*.app 2>/dev/null || true`).toString().trim().split('\\n')[0];\n if (appPath) {\n execSync(`cp -R \"${appPath}\" /Applications/ 2>/dev/null || true`);\n const appName = appPath.split('/').pop()!;\n const child = spawn('open', ['-a', appName], { detached: true, stdio: 'ignore' } as any);\n child.unref();\n execSync(`hdiutil detach \"${mountPoint}\" -quiet 2>/dev/null || true`);\n console.log(' ModelWeaver GUI launched!');\n return;\n }\n }\n } catch {\n // Fallback: just open the DMG file\n }\n execSync(`hdiutil detach \"/Volumes/ModelWeaver\" -quiet 2>/dev/null || true`);\n const dmgChild = spawn('open', [assetPath], { detached: true, stdio: 'ignore' } as any);\n dmgChild.unref();\n console.log(' ModelWeaver GUI launched (DMG opened)!');\n break;\n\n case 'linux-x64': {\n chmodSync(assetPath, 0o755);\n const linuxChild = spawn(assetPath, [], { detached: true, stdio: 'ignore' } as any);\n linuxChild.unref();\n console.log(' ModelWeaver GUI launched!');\n break;\n }\n\n case 'windows-x64':\n const winChild = spawn('msiexec', ['/i', assetPath, '/passive'], { detached: true, stdio: 'ignore', shell: true } as any);\n winChild.unref();\n console.log(' ModelWeaver GUI installer launched!');\n break;\n }\n}\n\nexport async function launchGui(): Promise<void> {\n console.log('\\n ModelWeaver GUI Launcher');\n console.log(' ──────────────────────\\n');\n\n const platformId = detectPlatform();\n console.log(` Platform: ${platformId}`);\n\n // Check cache\n const cachedVersion = getCachedVersion();\n if (cachedVersion) {\n console.log(` Cached version: ${cachedVersion}`);\n }\n\n // Fetch latest release info\n console.log(' Checking for latest release...');\n let release: GitHubRelease;\n try {\n release = await fetchJSON(API_URL);\n } catch (err) {\n console.error(` Failed to fetch release info: ${(err as Error).message}`);\n console.error(' Make sure you have internet access.');\n process.exit(1);\n }\n\n const latestVersion = release.tag_name;\n console.log(` Latest version: ${latestVersion}`);\n\n // Find matching asset\n const asset = matchAsset(release.assets, platformId);\n if (!asset) {\n console.error(` No binary found for platform: ${platformId}`);\n console.error(' Available assets:');\n for (const a of release.assets) {\n console.error(` - ${a.name}`);\n }\n process.exit(1);\n }\n\n const assetPath = join(GUI_DIR, asset.name);\n\n // Check if we need to download\n if (cachedVersion === latestVersion && existsSync(assetPath)) {\n console.log(` Using cached binary: ${asset.name}`);\n } else {\n console.log(` Downloading: ${asset.name}`);\n console.log(` Size: ~${(asset.name.length > 30 ? 'large' : 'small')} download`);\n\n try {\n await downloadFile(asset.url, assetPath);\n setCachedVersion(latestVersion);\n console.log(' Download complete!');\n } catch (err) {\n console.error(` Download failed: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n\n // Launch the binary\n launchBinary(platformId, assetPath);\n}\n"],"mappings":"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","names":["createWriteStream","mkdirSync","existsSync","readFileSync","writeFileSync","chmodSync","unlinkSync","join","dirname","homedir","platform","arch","get","spawn","execSync","GUI_DIR","VERSION_FILE","REPO","API_URL","detectPlatform","p","a","matchAsset","assets","platformId","matcher","n","asset","getCachedVersion","setCachedVersion","version","fetchJSON","url","maxRedirects","resolve","reject","res","chunks","chunk","e","downloadFile","dest","file","redirectRes","err","launchBinary","assetPath","mounts","mountPoint","appPath","appName","launchGui","cachedVersion","release","latestVersion"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{a as j}from"./chunk-OMWFRIHF.js";import{d as B}from"./chunk-ZGVJ4FUB.js";import{b as D,e as U,g as C,k as W}from"./chunk-P2HBWASF.js";import{serve as oe}from"@hono/node-server";import{readFileSync as qe}from"fs";import{Hono as be}from"hono";var x=new Map;function J(){x.clear()}function re(t,e){for(let[c,i]of e)for(let d of i)if(t.includes(d))return c;return null}function ne(t,e){return e.get(t)||[]}function H(t,e,c,i){let d=x.get(t);if(d)return x.delete(t),x.set(t,d),{requestId:e,model:t,tier:d.tier,providerChain:d.providerChain,startTime:Date.now(),rawBody:i};let p,o,r=c.modelRouting.get(t);if(r&&r.length>0)p="(modelRouting)",o=r;else{let n=re(t,c.tierPatterns);if(!n)return null;p=n,o=ne(p,c.routing)}if(x.size>=200){let n=x.keys().next().value;n!==void 0&&x.delete(n)}return x.set(t,{tier:p,providerChain:o}),{requestId:e,model:t,tier:p,providerChain:o,startTime:Date.now(),rawBody:i}}import{request as se}from"undici";import G from"fs";import z from"path";import ie from"os";var ae=new Set(["anthropic-version","anthropic-beta","content-type","accept"]),ce=/\/+/g,ue=/^https?:\/\/[^/]+/,F=/"model"\s*:\s*"([^"]*)"/,K=/"max_tokens"\s*:\s*(\d+)/,O=new TextEncoder;function X(t){return t===429||t>=500}var le=["context window","context_limit","token limit","prompt is too long","max tokens","input too large","too many tokens"];function pe(t,e){if(t!==400)return!1;let c=e.toLowerCase();return le.some(i=>c.includes(i))}function Q(t,e){if(!pe(t,e))return null;console.warn("[context-compact] Upstream context window limit detected");try{let i=z.join(ie.homedir(),".claude","state");G.mkdirSync(i,{recursive:!0}),G.writeFileSync(z.join(i,"context-compact-needed"),Date.now().toString())}catch{}let c=JSON.stringify({type:"error",error:{type:"invalid_request_error",message:"Context window limit reached. Run /compact to reduce conversation size, then retry."}});return new Response(c,{status:400,headers:{"content-type":"application/json"}})}function de(t,e){let c="",i=t,d=t.indexOf("/",t.indexOf("//")+2);d!==-1&&(i=t.substring(0,d),c=t.substring(d));let p="",o=e,r=e.indexOf("?");r!==-1&&(o=e.substring(0,r),p=e.substring(r));let n;return c.endsWith("/v1")&&o.startsWith("/v1")?n=c+o.substring(3):n=c+o,n=n.replace(ce,"/"),i+n+p}function me(t,e,c){let i=new Headers;for(let p of ae){let o=t.get(p);o&&i.set(p,o)}e.authType==="bearer"?i.set("Authorization",`Bearer ${e.apiKey}`):i.set("x-api-key",e.apiKey),i.set("x-request-id",c);let d=e._cachedHost;if(d)i.set("host",d);else try{let p=new URL(e.baseUrl);i.set("host",p.host)}catch{}return i}function Z(t){let e=t.messages;if(!Array.isArray(e))return;let c=new Set,i=new Set,d=new Map;for(let o=0;o<e.length;o++){let r=e[o];if(Array.isArray(r.content)){if(r.role==="assistant"){let n=!1;for(let l of r.content)l.type==="tool_use"&&l.id&&(c.add(String(l.id)),i.has(String(l.id))||(n=!0))}else if(r.role==="user"){let n=!1;for(let l of r.content)l.type==="tool_result"&&l.tool_use_id&&(i.add(String(l.tool_use_id)),c.has(String(l.tool_use_id))||(n=!0));n&&d.set(o,"user")}}}for(let o=0;o<e.length;o++){let r=e[o];r.role==="assistant"&&Array.isArray(r.content)&&r.content.some(l=>l.type==="tool_use"&&!i.has(String(l.id)))&&d.set(o,"assistant")}if(d.size===0)return;t.messages=e.map((o,r)=>{let n=d.get(r);if(n&&Array.isArray(o.content)){let l=o.content.filter(a=>n==="user"?!(a.type==="tool_result"&&!c.has(String(a.tool_use_id))):!(a.type==="tool_use"&&!i.has(String(a.id))));return l.length===o.content.length?o:{...o,content:l}}return o});let p=new Set;for(let o of t.messages)if(Array.isArray(o.content)&&o.role==="assistant")for(let r of o.content)r.type==="tool_use"&&r.id&&p.add(String(r.id));t.messages=t.messages.map(o=>{if(o.role==="user"&&Array.isArray(o.content)){let r=o.content.filter(n=>!(n.type==="tool_result"&&!p.has(String(n.tool_use_id))));return r.length===o.content.length?o:{...o,content:r}}return o})}function fe(t,e,c,i,d){if(d){let o=structuredClone(i);if(e.model&&(o.model=e.model),Z(o),c.modelLimits){let{maxOutputTokens:r}=c.modelLimits,n=typeof o.max_tokens=="number"?o.max_tokens:r;(o.max_tokens===void 0||n>r)&&(o.max_tokens=Math.min(n,r))}return JSON.stringify(o)}let p=t;if(e.model&&i.model!==e.model){let o=F.exec(p);o&&(p=p.replace(F,`"model":"${e.model}"`),console.warn(`Routing override: ${o[1]} -> ${e.model} via ${c.name}`))}if(c.modelLimits){let{maxOutputTokens:o}=c.modelLimits,r=K.exec(p);if(r)parseInt(r[1],10)>o&&(p=p.replace(K,`"max_tokens":${o}`));else if(typeof i.max_tokens!="number"){let n={...i};return e.model&&(n.model=e.model),n.max_tokens=o,JSON.stringify(n)}}return p}async function V(t,e,c,i,d,p=0){let o=i.url.replace(ue,"");e.model&&(c.actualModel=e.model);let r=de(t.baseUrl,o),n;if((i.headers.get("content-type")||"").includes("application/json"))try{let u=c.parsedBody??JSON.parse(c.rawBody),f=!1;e.model&&u.model!==e.model&&(f=!0);let g=p>0;if(g&&(f=!0),t.modelLimits){let{maxOutputTokens:v}=t.modelLimits,S=typeof u.max_tokens=="number"?u.max_tokens:v;(u.max_tokens===void 0||S>v)&&(f=!0)}if(f)if(p===0&&!g)n=fe(c.rawBody,e,t,u,!1);else{let v=structuredClone(u);if(e.model){let S=v.model;v.model=e.model,S&&S!==e.model&&console.warn(`Routing override: ${S} -> ${e.model} via ${t.name}`)}if(g&&Z(v),t.modelLimits){let{maxOutputTokens:S}=t.modelLimits,h=typeof v.max_tokens=="number"?v.max_tokens:S;(v.max_tokens===void 0||h>S)&&(v.max_tokens=Math.min(h,S))}n=JSON.stringify(v)}else n=c.rawBody}catch{n=c.rawBody}else n=c.rawBody;let a=me(i.headers,t,c.requestId);a.set("content-length",Buffer.byteLength(n,"utf-8").toString());let s=new AbortController,m=setTimeout(()=>s.abort(),t.timeout);if(d){if(d.aborted){clearTimeout(m);let f=JSON.stringify({type:"error",error:{type:"overloaded_error",message:`Provider "${t.name}" cancelled by race winner`}});return new Response(f,{status:502,headers:{"content-type":"application/json","content-length":O.encode(f).byteLength.toString()}})}let u=()=>{clearTimeout(m)};d.addEventListener("abort",u,{once:!0})}try{let u=await se(r,{method:"POST",headers:a,body:n,signal:s.signal,dispatcher:t._agent}),f=new Response(u.body,{status:u.statusCode,headers:u.headers});return clearTimeout(m),f}catch(u){clearTimeout(m);let f=u instanceof DOMException&&u.name==="AbortError"?`Provider "${t.name}" timed out after ${t.timeout}ms`:`Provider "${t.name}" connection failed: ${u.message}`,g=JSON.stringify({type:"error",error:{type:"overloaded_error",message:f}});return new Response(g,{status:502,headers:{"content-type":"application/json","content-length":O.encode(g).byteLength.toString()}})}}async function ge(t,e,c,i,d,p,o=0){let r=new AbortController,n=t.map(async(s,m)=>{let u=e.get(s.provider);if(!u){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${s.provider}`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json"}}),index:m}}if(u._circuitBreaker&&!u._circuitBreaker.canProceed()){let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${s.provider}" skipped by circuit breaker`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json"}}),index:m}}d?.(s.provider,m);try{let f=await V(u,s,c,i,r.signal,m+o);return u._circuitBreaker&&u._circuitBreaker.recordResult(f.status),{response:f,index:m}}catch{u._circuitBreaker&&u._circuitBreaker.recordResult(502);let f=JSON.stringify({type:"error",error:{type:"api_error",message:`Provider "${s.provider}" failed`}});return{response:new Response(f,{status:502,headers:{"content-type":"application/json"}}),index:m}}}),l=new Set,a=[];try{for(;l.size<n.length;){let m=n.filter(f=>!l.has(f));if(m.length===0)break;let u=await Promise.race(m);if(l.add(n[u.index]??n[0]),u.response.status>=200&&u.response.status<300){r.abort();for(let f of a)try{f.response.body?.cancel()}catch{}return u.response}if(!X(u.response.status)){if(r.abort(),u.response.status===400&&u.response.body)try{let f=await u.response.text(),g=Q(u.response.status,f);return g||new Response(f,{status:u.response.status,statusText:u.response.statusText,headers:u.response.headers})}catch{return u.response}return u.response}a.push(u)}if(r.abort(),a.length>0)return a[0].response;let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers in race failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json"}})}catch{r.abort();let s=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers in race failed"}});return new Response(s,{status:502,headers:{"content-type":"application/json"}})}}async function Y(t,e,c,i,d,p){let o=null;for(let n=0;n<e.length;n++){let l=e[n],a=t.get(l.provider);if(!a){let m=JSON.stringify({type:"error",error:{type:"api_error",message:`Unknown provider: ${l.provider}`}});o=new Response(m,{status:502,headers:{"content-type":"application/json","content-length":O.encode(m).byteLength.toString()}});continue}if(a._circuitBreaker&&!a._circuitBreaker.canProceed()){p?.warn("Provider skipped by circuit breaker",{requestId:c.requestId,provider:l.provider});continue}d?.(l.provider,n);let s=await V(a,l,c,i,void 0,n);if(o=s,a._circuitBreaker&&a._circuitBreaker.recordResult(s.status),s.status>=200&&s.status<300)return s;if(!X(s.status)){if(s.status===400&&s.body)try{let m=await s.text(),u=Q(s.status,m);return u||new Response(m,{status:s.status,statusText:s.statusText,headers:s.headers})}catch{return s}return s}if(n<e.length-1){if(await s.body?.cancel(),s.status===429&&n+1<e.length){c.fallbackMode="race";let m=e.slice(n+1);return ge(m,t,c,i,d,p,n+1)}continue}return s}if(o)return o;let r=JSON.stringify({type:"error",error:{type:"overloaded_error",message:"All providers exhausted"}});return new Response(r,{status:502,headers:{"content-type":"application/json","content-length":O.encode(r).byteLength.toString()}})}import{randomUUID as ke}from"crypto";import{gzip as Re}from"zlib";import{promisify as _e}from"util";import{WebSocketServer as he}from"ws";var ye=3e4,ve=2,we=64*1024,Se=500,q=null;function $(t,e){let c=new he({server:t,path:"/ws"});q=c,c.on("connection",i=>{let p={type:"summary",data:e.getSummary()};i.send(JSON.stringify(p));let o,r=0,n=()=>i.readyState===i.OPEN,l=e.onRecord(f=>{if(n()){if(i.bufferedAmount>we){a();return}setImmediate(()=>{if(!n())return;let g={type:"request",data:f};i.send(JSON.stringify(g))}),a()}});function a(){o||(o=setTimeout(()=>{if(o=void 0,!n())return;let f={type:"summary",data:e.getSummary()};i.send(JSON.stringify(f))},Se))}let s=setInterval(()=>{if(!n()){clearInterval(s);return}if(r>=ve){u(),i.terminate();return}i.ping(),r++},ye);i.on("pong",()=>{r=0});let m=!1,u=()=>{m||(m=!0,clearInterval(s),o&&clearTimeout(o),l())};i.on("close",u),i.on("error",u)})}function E(t){if(!q)return;let e=JSON.stringify({type:"stream",data:t});for(let c of q.clients)c.readyState===c.OPEN&&setImmediate(()=>{c.readyState===c.OPEN&&c.send(e)})}var Te=_e(Re);function N(t,e,c){return new Response(JSON.stringify({type:"error",error:{type:t,message:e}}),{status:502,headers:{"content-type":"application/json","x-request-id":c}})}function Me(t){let e=t.message?.usage??t.usage;if(!e)return{inputTokens:0,outputTokens:0};let c=e.input_tokens??e.prompt_tokens??0,i=e.output_tokens??e.completion_tokens??0,d=e.cache_read_input_tokens??0,p=e.cache_creation_input_tokens??0;return{inputTokens:c+d+p,outputTokens:i}}function xe(t,e,c,i,d,p){let o=new TextDecoder,r={input:0,output:0},n="",l="",a=4096,s=0,m=0,u=0,f=0,g="",v=null,S=250,h=0,k=b=>{for(let y of b.split(`
|
|
3
|
+
|
|
4
|
+
`)){if(!y)continue;let w=y.split(`
|
|
5
|
+
`).find(R=>R.startsWith("data:"));if(!(!w||!w.includes('"usage"')))try{let R=JSON.parse(w.slice(5)),_=Me(R);_.inputTokens>r.input&&(r.input=_.inputTokens),_.outputTokens>r.output&&(r.output=_.outputTokens)}catch{}}},I=b=>{let y=[...b.matchAll(/"(?:input_tokens|prompt_tokens)"\s*:\s*(\d+)/g)],w=[...b.matchAll(/"cache_read_input_tokens"\s*:\s*(\d+)/g)],R=[...b.matchAll(/"cache_creation_input_tokens"\s*:\s*(\d+)/g)],_=[...b.matchAll(/"(?:output_tokens|completion_tokens)"\s*:\s*(\d+)/g)];if(y.length>0){let M=parseInt(y[y.length-1][1],10);M>s&&(s=M)}if(w.length>0){let M=parseInt(w[w.length-1][1],10);M>m&&(m=M)}if(R.length>0){let M=parseInt(R[R.length-1][1],10);M>u&&(u=M)}if(_.length>0){let M=parseInt(_[_.length-1][1],10);M>f&&(f=M)}},T=(b,y)=>{try{let w=Date.now()-t.startTime,R=w/1e3,_=R>0?y/R:0;i.recordRequest({requestId:t.requestId,model:t.model,actualModel:t.actualModel||t.model,tier:t.tier,provider:e,targetProvider:c,status:d,inputTokens:b,outputTokens:y,latencyMs:w,tokensPerSec:Math.round(_*10)/10,timestamp:Date.now(),fallbackMode:t.fallbackMode}),setImmediate(()=>{E({requestId:t.requestId,model:t.model,tier:t.tier,state:"complete",status:d,latencyMs:Date.now()-t.startTime,inputTokens:b,outputTokens:y,tokensPerSec:Math.round(_*10)/10,timestamp:Date.now()})})}catch{}},A=(b,y)=>{if(v===null&&(v=p.includes("text/event-stream")||b.startsWith("event:")),v){n+=b;let w=n.split(`
|
|
6
|
+
`);n=w.pop();for(let _ of w)_===""?l&&(k(l),l=""):l+=(l?`
|
|
7
|
+
`:"")+_;y&&l.trim()&&k(l);let R=Date.now();R-h>=S&&r.output>0&&(h=R,setImmediate(()=>{E({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:r.output,timestamp:R})})),y&&T(r.input,r.output)}else{g+=b,g.length>a&&(g=g.slice(-a)),I(g);let w=Date.now();if(w-h>=S&&f>0&&(h=w,setImmediate(()=>{E({requestId:t.requestId,model:t.model,tier:t.tier,state:"streaming",outputTokens:f,timestamp:w})})),y){let R=s+m+u;T(R,f)}}};return new TransformStream({transform(b,y){y.enqueue(b),A(o.decode(b,{stream:!0}),!1)},flush(){A("",!0)},cancel(){A("",!0)}})}function ee(t){let e=t._cachedOrigin,c=t.poolSize??10;return`${e??"unknown"}:${c}`}function L(t,e,c){let i=t,d=j(e),p=new be;return p.onError((o,r)=>(console.error(`[server] Unhandled error: ${o.message}`),r.json({type:"error",error:{type:"api_error",message:"Internal proxy error"}},{status:500,headers:{"content-type":"application/json"}}))),p.use("/api/*",async(o,r)=>{o.header("Access-Control-Allow-Origin","*"),await r()}),p.options("/api/*",o=>(o.header("Access-Control-Allow-Origin","*"),o.header("Access-Control-Allow-Methods","GET, POST, OPTIONS"),o.header("Access-Control-Allow-Headers","Content-Type, Authorization, anthropic-version, x-api-key"),o.body("",200))),p.post("/v1/messages",async o=>{let r=ke(),n,l;try{l=await o.req.text(),n=JSON.parse(l)}catch{return N("invalid_request_error","Invalid JSON body",r)}let a=n.model;if(!a)return N("invalid_request_error","Missing 'model' field in request body",r);let s=H(a,r,i,l);if(s&&(s.parsedBody=n),!s){d.info("No tier match",{requestId:r,model:a});let h=i.modelRouting.size>0?` Configured model routes: ${[...i.modelRouting.keys()].join(", ")}.`:"";return N("invalid_request_error",`No route matches model "${a}". Configured tiers: ${[...i.tierPatterns.keys()].join(", ")}.${h}`,r)}d.info("Routing request",{requestId:r,model:a,tier:s.tier,providers:s.providerChain.map(h=>h.provider)}),E({requestId:r,model:a,tier:s.tier,state:"start",provider:s.providerChain[0]?.provider??"unknown",timestamp:Date.now()});let m="unknown",u;try{u=await Y(i.providers,s.providerChain,s,o.req.raw,(h,k)=>{d.info("Attempting provider",{requestId:r,provider:h,index:k,tier:s.tier}),m||(m=h)},d)}catch(h){let k=h instanceof Error?h.message:String(h);return d.error("Forward failed",{requestId:r,error:k}),setImmediate(()=>{E({requestId:r,model:a,tier:s.tier,state:"error",status:502,message:k,timestamp:Date.now()})}),o.json({type:"error",error:{type:"api_error",message:"Upstream request failed: "+k}},502)}u.status>=400&&setImmediate(()=>{E({requestId:r,model:a,tier:s.tier,state:"error",status:u.status,message:`HTTP ${u.status}`,timestamp:Date.now()})});let f=u.body;if(u.body&&u.status>=200&&u.status<300&&c){let h=s.providerChain.length>0?s.providerChain[0].provider:m,k=xe(s,m,h,c,u.status,u.headers.get("content-type")||"");f=u.body.pipeThrough(k)}let g=new Headers(u.headers);g.set("x-request-id",r);let v=new Response(f,{status:u.status,statusText:u.statusText,headers:g}),S=Date.now()-s.startTime;return d.info("Request completed",{requestId:r,model:a,tier:s.tier,status:v.status,latencyMs:S}),v}),p.get("/api/metrics/summary",async o=>{if(!c)return o.json({error:"Metrics not enabled"},503);let r=c.getSummary(),n=JSON.stringify(r);if((o.req.header("accept-encoding")||"").includes("gzip")&&n.length>=1024){let a=await Te(Buffer.from(n));return new Response(a,{status:200,headers:{"content-type":"application/json","content-encoding":"gzip",vary:"accept-encoding"}})}return o.json(r)}),p.get("/api/circuit-breaker",o=>{let r={};for(let[n,l]of i.providers){let a=l._circuitBreaker;if(a){let s=a.getStatus();r[n]={state:s.state,failures:s.failures,lastFailure:s.lastFailure?new Date(s.lastFailure).toISOString():null}}}return o.json(r)}),{app:p,getConfig:()=>i,setConfig:o=>{let r=new Map;for(let l of i.providers.values())l._agent&&r.set(ee(l),l._agent);let n=new Set;for(let l of o.providers.values()){let a=ee(l),s=r.get(a);s&&(l._agent=s,n.add(a))}for(let[l,a]of r)n.has(l)||a.close();i=o,J()}}}var P=class{buffer;maxSize;head=0;count=0;subscribers;createdAt;_totalInputTokens=0;_totalOutputTokens=0;_totalTokensPerSec=0;_modelMap=new Map;_providerMap=new Map;constructor(e=1e3){this.buffer=new Array(e).fill(null),this.maxSize=e,this.subscribers=new Set,this.createdAt=Date.now()}recordRequest(e){let c=this.head%this.maxSize,i=this.count>=this.maxSize?this.buffer[c]:null;if(i!==null){this._totalInputTokens-=i.inputTokens??0,this._totalOutputTokens-=i.outputTokens??0,this._totalTokensPerSec-=i.tokensPerSec??0;let r=i.model,n=this._modelMap.get(r);n&&(n.count--,n.count<=0&&this._modelMap.delete(r));let l=i.targetProvider??i.provider,a=this._providerMap.get(l)??0;a<=1?this._providerMap.delete(l):this._providerMap.set(l,a-1)}this._totalInputTokens+=e.inputTokens??0,this._totalOutputTokens+=e.outputTokens??0,this._totalTokensPerSec+=e.tokensPerSec??0;let d=e.model,p=this._modelMap.get(d);p?(p.count++,e.timestamp>p.lastSeen&&(p.lastSeen=e.timestamp),p.actualModel=e.actualModel):this._modelMap.set(d,{actualModel:e.actualModel,count:1,lastSeen:e.timestamp});let o=e.targetProvider??e.provider;this._providerMap.set(o,(this._providerMap.get(o)??0)+1),this.buffer[c]=e,this.head++,this.count<this.maxSize&&this.count++;for(let r of this.subscribers)try{r(e)}catch{}}getSummary(){let e=this.getRecentRequests(),c=[...this._modelMap.entries()].map(([d,{actualModel:p,count:o,lastSeen:r}])=>({model:d,actualModel:p,count:o,lastSeen:r})).sort((d,p)=>p.count-d.count),i=[...this._providerMap.entries()].map(([d,p])=>({provider:d,count:p})).sort((d,p)=>p.count-d.count);return{totalRequests:this.count,totalInputTokens:this._totalInputTokens,totalOutputTokens:this._totalOutputTokens,avgTokensPerSec:this.count>0?Math.round(this._totalTokensPerSec/this.count*10)/10:0,activeModels:c,providerDistribution:i,recentRequests:e,uptimeSeconds:Math.floor((Date.now()-this.createdAt)/1e3)}}onRecord(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}getRecentRequests(){if(this.count===0)return[];let e=Math.min(this.count,50),c=[];for(let i=0;i<e;i++){let d=((this.head-1-i)%this.maxSize+this.maxSize)%this.maxSize,p=this.buffer[d];p!==null&&c.push(p)}return c.reverse(),c}};import{spawn as Ee}from"child_process";import{existsSync as Ce,unlinkSync as Ie}from"fs";import{dirname as Ae,join as Oe}from"path";import{fileURLToPath as Pe}from"url";async function te(t){let e=D();Ce(e)&&Ie(e),await U(process.pid);let c=process.argv[1]||Oe(Ae(Pe(import.meta.url)),"index.js");process.on("uncaughtException",f=>{console.error(`[monitor] Uncaught exception: ${f.message}`)}),process.on("unhandledRejection",f=>{console.error(`[monitor] Unhandled rejection: ${f}`)});let i=10,d=1e3,p=3e4,o=6e4,r=0,n=null,l=null,a=!1,s=!1,m=null;function u(){let f=[c,"--daemon"];t.config&&f.push("--config",t.config),t.port&&f.push("--port",String(t.port)),t.verbose&&f.push("--verbose"),m=Ee(process.execPath,f,{detached:!0,stdio:"ignore",env:{...process.env}}),n&&clearTimeout(n),n=setTimeout(()=>{r>0&&console.error(`[monitor] Worker stable for ${o}ms, resetting restart counter`),r=0,n=null},o),m.on("exit",async g=>{m=null,n&&(clearTimeout(n),n=null),await W(),g===0&&!s&&(await C(),process.exit(0)),s=!1,a&&(console.error("[monitor] Worker exited during shutdown, monitor exiting"),await C(),process.exit(0));let v=r;v>=i&&(console.error(`[monitor] Max restart attempts exhausted (${i}), monitor exiting`),await C(),process.exit(1));let S=Math.min(d*2**v,p);r++,console.error(`[monitor] Worker died (code ${g}), restarting in ${S}ms (attempt ${r}/${i})`),l=setTimeout(u,S)})}process.on("SIGTERM",()=>{if(a=!0,l&&(clearTimeout(l),l=null),n&&(clearTimeout(n),n=null),m){try{m.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else C().then(()=>process.exit(0))}),process.on("SIGINT",()=>{if(a=!0,l&&(clearTimeout(l),l=null),n&&(clearTimeout(n),n=null),m){try{m.kill("SIGTERM")}catch{}setTimeout(()=>{console.error("[monitor] Child did not exit within 5 s, forcing exit"),process.exit(0)},5e3)}else C().then(()=>process.exit(0))}),process.on("SIGHUP",()=>{if(console.log("[monitor] Received reload signal, restarting worker..."),s=!0,l&&(clearTimeout(l),l=null),m)try{m.kill("SIGTERM")}catch{}r=0}),u()}var $e=JSON.parse(qe(new URL("../package.json",import.meta.url),"utf-8")).version;function Ne(t){let e={verbose:!1,help:!1,daemon:!1,monitor:!1,gui:!1};for(let c=2;c<t.length;c++)switch(t[c]){case"-p":case"--port":let i=t[++c];(!i||isNaN(parseInt(i,10)))&&(console.error("Error: -p/--port requires a number"),process.exit(1)),e.port=parseInt(i,10);break;case"-c":case"--config":let d=t[++c];d||(console.error("Error: -c/--config requires a path"),process.exit(1)),e.config=d;break;case"-v":case"--verbose":e.verbose=!0;break;case"-h":case"--help":e.help=!0;break;case"--daemon":e.daemon=!0;break;case"--monitor":e.monitor=!0;break}return e}function Le(){console.log(`
|
|
8
|
+
ModelWeaver \u2014 Multi-provider model orchestration proxy for Claude Code
|
|
9
|
+
|
|
10
|
+
Usage: modelweaver [command] [options]
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
init [--quick] Run interactive setup wizard (--quick for express mode)
|
|
14
|
+
start Start as background daemon
|
|
15
|
+
stop Stop background daemon
|
|
16
|
+
status Show daemon status
|
|
17
|
+
remove Stop daemon and remove PID + log files
|
|
18
|
+
reload Reload daemon worker (load fresh code after build)
|
|
19
|
+
install Install launchd service (auto-start at login)
|
|
20
|
+
uninstall Uninstall launchd service
|
|
21
|
+
gui Launch the GUI (downloads if needed)
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
-p, --port <number> Server port (default: from config)
|
|
25
|
+
-c, --config <path> Config file path (auto-detected)
|
|
26
|
+
-v, --verbose Enable debug logging (default: off)
|
|
27
|
+
-h, --help Show this help
|
|
28
|
+
|
|
29
|
+
Config locations (first found wins):
|
|
30
|
+
./modelweaver.yaml
|
|
31
|
+
~/.modelweaver/config.yaml
|
|
32
|
+
`)}async function je(){let t=Ne(process.argv);try{let a=await import("dotenv"),{existsSync:s}=await import("fs"),{join:m}=await import("path"),u=process.env.HOME||process.env.USERPROFILE||"",f=[m(process.cwd(),".env"),m(u,".modelweaver",".env"),m(u,".env")];for(let g of f)if(s(g)){a.config({path:g});break}}catch{}if(process.argv[2]==="init"){let a=process.argv.includes("--quick")||process.argv.includes("-q"),{runInit:s}=await import("./init-YTOVLWSL.js");await s({quick:a}),process.exit(0)}if(process.argv[2]==="start"){let{startDaemon:a}=await import("./daemon-MGMHTCQC.js"),s=await a(t.config,t.port,t.verbose);console.log(` ${s.message}`),console.log(` Log file: ${s.logPath}`),process.exit(s.success?0:1)}if(process.argv[2]==="stop"){let{stopDaemon:a}=await import("./daemon-MGMHTCQC.js"),s=await a();console.log(` ${s.message}`),process.exit(s.success?0:1)}if(process.argv[2]==="status"){let{statusDaemon:a}=await import("./daemon-MGMHTCQC.js"),s=await a();console.log(` ${s.message}`);try{let{getService:m}=await import("./service-6EQTZJEG.js"),f=(await m()).isInstalled();console.log(f?" Service: installed":' Service: not installed (run "modelweaver install" to enable auto-start)')}catch(m){console.log(` Service: ${m instanceof Error?m.message:String(m)}`)}process.exit(0)}if(process.argv[2]==="remove"){let{removeDaemon:a}=await import("./daemon-MGMHTCQC.js"),s=await a();console.log(` ${s.message}`),process.exit(s.success?0:1)}if(process.argv[2]==="install"){try{let{getService:a}=await import("./service-6EQTZJEG.js");await(await a()).install()}catch(a){console.error(` Error: ${a instanceof Error?a.message:String(a)}`),process.exit(1)}process.exit(0)}if(process.argv[2]==="uninstall"){try{let{getService:a}=await import("./service-6EQTZJEG.js");(await a()).uninstall()}catch(a){console.error(` Error: ${a instanceof Error?a.message:String(a)}`),process.exit(1)}process.exit(0)}if(process.argv[2]==="gui"){let{launchGui:a}=await import("./gui-launcher-ZVOVTD6C.js");await a(),process.exit(0)}if(process.argv[2]==="reload"){let{reloadDaemon:a}=await import("./daemon-MGMHTCQC.js");await a(t.port),process.exit(0)}t.help&&(Le(),process.exit(0));let e,c;try{let a=B(t.config);e=a.config,c=a.configPath}catch(a){console.error(`Config error: ${a.message}`),process.exit(1)}let i=t.port||e.server.port,d=e.server.host,p=t.verbose?"debug":"info",o=new P;if(t.monitor){await te(t);return}if(t.daemon){let{removeWorkerPidFile:a,writeWorkerPidFile:s,createDebouncedReload:m,getLogPath:u}=await import("./daemon-MGMHTCQC.js"),{reloadConfig:f}=await import("./config-FYJATRN4.js"),{createWriteStream:g,watch:v}=await import("fs"),{createLogger:S}=await import("./logger-UA2A2DVX.js"),h=S(p);process.on("uncaughtException",y=>{h.error("Uncaught exception (daemon survived)",{error:y.message,stack:y.stack})}),process.on("unhandledRejection",y=>{h.error("Unhandled rejection (daemon survived)",{reason:String(y)})}),await s(process.pid);let k=g(u(),{flags:"a"});k.on("error",()=>{}),process.stdout.write=k.write.bind(k),process.stderr.write=k.write.bind(k);let I=L(e,p,o),T=null;if(c){let y=m(()=>{try{let w=f(c);I.setConfig(w),h.info("Config reloaded",{path:c})}catch(w){h.error("Config reload failed \u2014 keeping old config",{error:w.message})}},300);try{T=v(c,()=>{y.reload()}),T.on("error",()=>{T&&(T.close(),T=null)})}catch{}}process.on("SIGUSR1",()=>{try{let y=f(c);I.setConfig(y),h.info("Config reloaded (SIGUSR1)",{path:c})}catch(y){h.error("Config reload failed (SIGUSR1)",{error:y.message})}});let A=oe({fetch:I.app.fetch,hostname:d,port:i});$(A,o);let b=async()=>{T&&(T.close(),T=null),await a(),k.end(),process.exit(0)};process.on("SIGTERM",b),process.on("SIGINT",b);return}let r=L(e,p,o);console.log(`
|
|
33
|
+
ModelWeaver v${$e}`),console.log(` Listening: http://${d}:${i}`),console.log(` Config: ${c}
|
|
34
|
+
`),console.log(" Routes:");for(let[a,s]of e.routing){let m=s.map((u,f)=>`${u.provider}${f===0?" (primary)":" (fallback)"}`).join(", ");console.log(` ${a.padEnd(8)} \u2192 ${m}`)}if(console.log(),e.modelRouting.size>0){console.log(" Model Routes:");for(let[a,s]of e.modelRouting){let m=s.map((u,f)=>`${u.provider}${f===0?" (primary)":" (fallback)"}`).join(", ");console.log(` ${a.padEnd(20)} \u2192 ${m}`)}console.log()}let n=oe({fetch:r.app.fetch,hostname:d,port:i});$(n,o);let l=()=>{console.log(`
|
|
35
|
+
Shutting down...`),process.exit(0)};process.on("SIGTERM",l),process.on("SIGINT",l)}je();
|
|
36
|
+
//# sourceMappingURL=index.js.map
|