UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

272 lines (268 loc) 8.06 kB
import { St as shouldLogVerbose, _ as defaultRuntime, bt as logVerboseConsole, gt as info, ht as danger, kt as getLogger, o as createSubsystemLogger, wt as warn } from "./entry.js"; import { execFile, spawn } from "node:child_process"; import path from "node:path"; import { promisify } from "node:util"; //#region src/logger.ts const subsystemPrefixRe = /^([a-z][a-z0-9-]{1,20}):\s+(.*)$/i; function splitSubsystem(message) { const match = message.match(subsystemPrefixRe); if (!match) return null; const [, subsystem, rest] = match; return { subsystem, rest }; } function logInfo(message, runtime = defaultRuntime) { const parsed = runtime === defaultRuntime ? splitSubsystem(message) : null; if (parsed) { createSubsystemLogger(parsed.subsystem).info(parsed.rest); return; } runtime.log(info(message)); getLogger().info(message); } function logWarn(message, runtime = defaultRuntime) { const parsed = runtime === defaultRuntime ? splitSubsystem(message) : null; if (parsed) { createSubsystemLogger(parsed.subsystem).warn(parsed.rest); return; } runtime.log(warn(message)); getLogger().warn(message); } function logError(message, runtime = defaultRuntime) { const parsed = runtime === defaultRuntime ? splitSubsystem(message) : null; if (parsed) { createSubsystemLogger(parsed.subsystem).error(parsed.rest); return; } runtime.error(danger(message)); getLogger().error(message); } function logDebug(message) { getLogger().debug(message); logVerboseConsole(message); } //#endregion //#region src/process/spawn-utils.ts const DEFAULT_RETRY_CODES = ["EBADF"]; function resolveCommandStdio(params) { return [ params.hasInput ? "pipe" : params.preferInherit ? "inherit" : "pipe", "pipe", "pipe" ]; } function shouldRetry(err, codes) { const code = err && typeof err === "object" && "code" in err ? String(err.code) : ""; return code.length > 0 && codes.includes(code); } async function spawnAndWaitForSpawn(spawnImpl, argv, options) { const child = spawnImpl(argv[0], argv.slice(1), options); return await new Promise((resolve, reject) => { let settled = false; const cleanup = () => { child.removeListener("error", onError); child.removeListener("spawn", onSpawn); }; const finishResolve = () => { if (settled) return; settled = true; cleanup(); resolve(child); }; const onError = (err) => { if (settled) return; settled = true; cleanup(); reject(err); }; const onSpawn = () => { finishResolve(); }; child.once("error", onError); child.once("spawn", onSpawn); process.nextTick(() => { if (typeof child.pid === "number") finishResolve(); }); }); } async function spawnWithFallback(params) { const spawnImpl = params.spawnImpl ?? spawn; const retryCodes = params.retryCodes ?? DEFAULT_RETRY_CODES; const baseOptions = { ...params.options }; const fallbacks = params.fallbacks ?? []; const attempts = [{ options: baseOptions }, ...fallbacks.map((fallback) => ({ label: fallback.label, options: { ...baseOptions, ...fallback.options } }))]; let lastError; for (let index = 0; index < attempts.length; index += 1) { const attempt = attempts[index]; try { return { child: await spawnAndWaitForSpawn(spawnImpl, params.argv, attempt.options), usedFallback: index > 0, fallbackLabel: attempt.label }; } catch (err) { lastError = err; const nextFallback = fallbacks[index]; if (!nextFallback || !shouldRetry(err, retryCodes)) throw err; params.onFallback?.(err, nextFallback); } } throw lastError; } //#endregion //#region src/process/exec.ts const execFileAsync = promisify(execFile); /** * Resolves a command for Windows compatibility. * On Windows, non-.exe commands (like npm, pnpm) require their .cmd extension. */ function resolveCommand(command) { if (process.platform !== "win32") return command; const basename = path.basename(command).toLowerCase(); if (path.extname(basename)) return command; if ([ "npm", "pnpm", "yarn", "npx" ].includes(basename)) return `${command}.cmd`; return command; } function shouldSpawnWithShell(params) { return false; } async function runExec(command, args, opts = 1e4) { const options = typeof opts === "number" ? { timeout: opts, encoding: "utf8" } : { timeout: opts.timeoutMs, maxBuffer: opts.maxBuffer, encoding: "utf8" }; try { const { stdout, stderr } = await execFileAsync(resolveCommand(command), args, options); if (shouldLogVerbose()) { if (stdout.trim()) logDebug(stdout.trim()); if (stderr.trim()) logError(stderr.trim()); } return { stdout, stderr }; } catch (err) { if (shouldLogVerbose()) logError(danger(`Command failed: ${command} ${args.join(" ")}`)); throw err; } } async function runCommandWithTimeout(argv, optionsOrTimeout) { const options = typeof optionsOrTimeout === "number" ? { timeoutMs: optionsOrTimeout } : optionsOrTimeout; const { timeoutMs, cwd, input, env, noOutputTimeoutMs } = options; const { windowsVerbatimArguments } = options; const hasInput = input !== void 0; const shouldSuppressNpmFund = (() => { const cmd = path.basename(argv[0] ?? ""); if (cmd === "npm" || cmd === "npm.cmd" || cmd === "npm.exe") return true; if (cmd === "node" || cmd === "node.exe") return (argv[1] ?? "").includes("npm-cli.js"); return false; })(); const mergedEnv = env ? { ...process.env, ...env } : { ...process.env }; const resolvedEnv = Object.fromEntries(Object.entries(mergedEnv).filter(([, value]) => value !== void 0).map(([key, value]) => [key, String(value)])); if (shouldSuppressNpmFund) { if (resolvedEnv.NPM_CONFIG_FUND == null) resolvedEnv.NPM_CONFIG_FUND = "false"; if (resolvedEnv.npm_config_fund == null) resolvedEnv.npm_config_fund = "false"; } const stdio = resolveCommandStdio({ hasInput, preferInherit: true }); const resolvedCommand = resolveCommand(argv[0] ?? ""); const child = spawn(resolvedCommand, argv.slice(1), { stdio, cwd, env: resolvedEnv, windowsVerbatimArguments, ...shouldSpawnWithShell({ resolvedCommand, platform: process.platform }) ? { shell: true } : {} }); return await new Promise((resolve, reject) => { let stdout = ""; let stderr = ""; let settled = false; let timedOut = false; let noOutputTimedOut = false; let noOutputTimer = null; const shouldTrackOutputTimeout = typeof noOutputTimeoutMs === "number" && Number.isFinite(noOutputTimeoutMs) && noOutputTimeoutMs > 0; const clearNoOutputTimer = () => { if (!noOutputTimer) return; clearTimeout(noOutputTimer); noOutputTimer = null; }; const armNoOutputTimer = () => { if (!shouldTrackOutputTimeout || settled) return; clearNoOutputTimer(); noOutputTimer = setTimeout(() => { if (settled) return; noOutputTimedOut = true; if (typeof child.kill === "function") child.kill("SIGKILL"); }, Math.floor(noOutputTimeoutMs)); }; const timer = setTimeout(() => { timedOut = true; if (typeof child.kill === "function") child.kill("SIGKILL"); }, timeoutMs); armNoOutputTimer(); if (hasInput && child.stdin) { child.stdin.write(input ?? ""); child.stdin.end(); } child.stdout?.on("data", (d) => { stdout += d.toString(); armNoOutputTimer(); }); child.stderr?.on("data", (d) => { stderr += d.toString(); armNoOutputTimer(); }); child.on("error", (err) => { if (settled) return; settled = true; clearTimeout(timer); clearNoOutputTimer(); reject(err); }); child.on("close", (code, signal) => { if (settled) return; settled = true; clearTimeout(timer); clearNoOutputTimer(); const termination = noOutputTimedOut ? "no-output-timeout" : timedOut ? "timeout" : signal != null ? "signal" : "exit"; resolve({ pid: child.pid ?? void 0, stdout, stderr, code, signal, killed: child.killed, termination, noOutputTimedOut }); }); }); } //#endregion export { logError as a, logDebug as i, runExec as n, logInfo as o, spawnWithFallback as r, logWarn as s, runCommandWithTimeout as t };