import { execFileSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import { arch, cpus, hostname, platform, release, totalmem, userInfo } from "node:os"; import type { NodeMetadata } from "@autoscholar/schemas"; import { WORKER_PROTOCOL_VERSION } from "@autoscholar/schemas"; const require = createRequire(import.meta.url); function tryExec(cmd: string, args: string[]): string | undefined { try { return execFileSync(cmd, args, { encoding: "utf8", timeout: 8000, stdio: ["ignore", "pipe", "ignore"], }).trim(); } catch { return undefined; } } export function sdkVersion(): string { for (const p of [ new URL("../node_modules/@anthropic-ai/claude-agent-sdk/package.json", import.meta.url), new URL("../../../node_modules/@anthropic-ai/claude-agent-sdk/package.json", import.meta.url), ]) { try { return (JSON.parse(readFileSync(p, "utf8")) as { version: string }).version; } catch { /* try next */ } } try { const entry = require.resolve("@anthropic-ai/claude-agent-sdk"); const m = entry.match(/claude-agent-sdk@(\d+\.\d+\.\d+)/); if (m) return m[1]!; } catch { /* unknown */ } return "unknown"; } export function workerVersion(): string { try { return ( JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as { version: string } ).version; } catch { return "0.0.0"; } } export function machineId(): string { const ioreg = tryExec("/usr/sbin/ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]); const m = ioreg?.match(/"IOPlatformUUID" = "([^"]+)"/); return m?.[1] ?? `${hostname()}-${arch()}`; } export function collectNodeMetadata(opts: { simulated: boolean; profiles: string[] }): NodeMetadata { const tools: Record = {}; const probe = (name: string, cmd: string, args: string[]) => { const v = tryExec(cmd, args); if (v) tools[name] = v.split("\n")[0]!.slice(0, 80); }; probe("node", "node", ["--version"]); probe("python3", "/opt/homebrew/bin/python3", ["--version"]); probe("uv", "/opt/homebrew/bin/uv", ["--version"]); probe("tectonic", process.env.TECTONIC_BIN ?? "/opt/homebrew/bin/tectonic", ["--version"]); probe("pdftoppm", "/opt/homebrew/bin/pdftoppm", ["-v"]); probe("git", "git", ["--version"]); probe("R", "/opt/homebrew/bin/Rscript", ["--version"]); probe("julia", "/opt/homebrew/bin/julia", ["--version"]); tools["claude-agent-sdk"] = sdkVersion(); const sandboxAvailable = platform() === "darwin" ? !!tryExec("/usr/bin/sandbox-exec", ["-n", "no-network", "/usr/bin/true"]) || tryExec("/bin/ls", ["/usr/bin/sandbox-exec"]) === "/usr/bin/sandbox-exec" : false; return { machineId: machineId(), hostname: hostname(), platform: platform(), arch: arch(), cpuCores: cpus().length, memoryBytes: totalmem(), osVersion: tryExec("/usr/bin/sw_vers", ["-productVersion"]) ?? release(), workerVersion: workerVersion(), protocolVersion: WORKER_PROTOCOL_VERSION, sdkVersion: sdkVersion(), tools, profiles: opts.profiles, user: userInfo().username, sandboxAvailable: opts.simulated ? false : sandboxAvailable, ...(opts.simulated ? { simulated: true } : {}), } as NodeMetadata; } export function loadAverage(): { load1: number; memFreeBytes: number; diskFreeBytes: number } { const load1 = (() => { try { const { loadavg } = require("node:os") as typeof import("node:os"); return loadavg()[0] ?? 0; } catch { return 0; } })(); const { freemem } = require("node:os") as typeof import("node:os"); let diskFreeBytes = 0; const df = tryExec("/bin/df", ["-k", process.env.WORKER_ROOT ?? "/"]); const line = df?.split("\n")[1]; if (line) diskFreeBytes = Number(line.split(/\s+/)[3] ?? 0) * 1024; return { load1, memFreeBytes: freemem(), diskFreeBytes }; }