import { spawn, type ChildProcess } from "node:child_process"; export type ProviderProcessInput = Readonly<{ executable: string; arguments: readonly string[]; environment: NodeJS.ProcessEnv; timeoutMs: number; }>; type ProviderChild = Pick; export async function runProviderProcess( input: ProviderProcessInput, dependencies: Readonly<{ spawnChild?: (input: ProviderProcessInput) => ProviderChild; setTimer?: (callback: () => void, milliseconds: number) => ReturnType; clearTimer?: (timer: ReturnType) => void; killProcessGroup?: (child: ProviderChild) => void; }> = {}, ): Promise { if (!Number.isSafeInteger(input.timeoutMs) || input.timeoutMs <= 0) { throw new TypeError("provider process timeout must be a positive integer"); } const child = (dependencies.spawnChild ?? defaultSpawn)(input); const setTimer = dependencies.setTimer ?? setTimeout; const clearTimer = dependencies.clearTimer ?? clearTimeout; const killProcessGroup = dependencies.killProcessGroup ?? defaultKillProcessGroup; await new Promise((resolve, reject) => { let settled = false; let timedOut = false; const killFailures: unknown[] = []; const settle = (error?: Error): void => { if (settled) return; settled = true; clearTimer(timer); error ? reject(error) : resolve(); }; const timer = setTimer(() => { timedOut = true; try { killProcessGroup(child); } catch (groupError) { killFailures.push(groupError); try { child.kill("SIGKILL"); } catch (fallbackError) { killFailures.push(fallbackError); } } }, input.timeoutMs); child.once("error", (error: Error) => { if (!timedOut) settle(error); }); child.once("close", (code: number | null, signal: NodeJS.Signals | null) => { if (timedOut) { const timeoutError = new Error( "sandboxed external provider command timed out after process close", ); settle( killFailures.length === 0 ? timeoutError : new AggregateError( [timeoutError, ...killFailures], "sandboxed external provider timed out and process-group kill failed before close", { cause: killFailures.at(-1) }, ), ); } else if (code === 0 && signal === null) { settle(); } else { settle( new Error( `sandboxed external provider failed: exit=${code ?? "none"}, signal=${signal ?? "none"}`, ), ); } }); }); } function defaultSpawn(input: ProviderProcessInput): ProviderChild { return spawn(input.executable, [...input.arguments], { env: input.environment, stdio: "inherit", detached: true, }); } function defaultKillProcessGroup(child: ProviderChild): void { if (child.pid && child.pid > 0) { try { process.kill(-child.pid, "SIGKILL"); return; } catch (error) { if (!hasErrorCode(error, "ESRCH")) throw error; } } child.kill("SIGKILL"); } function hasErrorCode(error: unknown, code: string): boolean { return Boolean(error && typeof error === "object" && "code" in error && error.code === code); }