import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { getAgentDir } from "@earendil-works/pi-coding-agent"; import type { RunnerBackend } from "./types.js"; export interface RunnerConfig { runnerBackend: RunnerBackend; } const DEFAULT_RUNNER_CONFIG: RunnerConfig = { runnerBackend: "in-process", }; function parseRunnerBackend(value: unknown, path: string): RunnerBackend { if (value === "in-process" || value === "herdr") { return value; } throw new Error( `Invalid runnerBackend in ${path}: expected "in-process" or "herdr".` ); } function readConfigFile(path: string): RunnerConfig | undefined { if (!existsSync(path)) { return undefined; } let raw: unknown; try { raw = JSON.parse(readFileSync(path, "utf-8")); } catch (err) { throw new Error( `Invalid runner config JSON in ${path}: ${err instanceof Error ? err.message : String(err)}` ); } if (raw == null || typeof raw !== "object" || Array.isArray(raw)) { throw new Error( `Invalid runner config in ${path}: expected a JSON object.` ); } const config = raw as { runnerBackend?: unknown }; if (config.runnerBackend == null) { return undefined; } return { runnerBackend: parseRunnerBackend(config.runnerBackend, path), }; } export function resolveRunnerConfig(cwd: string): RunnerConfig { const userPath = join(getAgentDir(), "subagents", "config.json"); const projectPath = join(cwd, ".pi", "subagents", "config.json"); return { ...DEFAULT_RUNNER_CONFIG, ...readConfigFile(userPath), ...readConfigFile(projectPath), }; }