import { readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join, resolve } from "node:path"; export interface PiEnabledModelsResult { ids: string[]; warnings: string[]; source?: string; } interface LoadOptions { harnessDir: string; agentDir?: string; read?: (path: string) => string; } type Settings = Record; const INVALID = Symbol("invalid-settings"); function readSettings( path: string, optional: boolean, read: (path: string) => string, warnings: string[], ): Settings | undefined | typeof INVALID { try { const value = JSON.parse(read(path)); if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(); return value as Settings; } catch (error) { if (optional && (error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined; warnings.push(`Pi settings are unreadable or invalid: ${path}`); return INVALID; } } function isExactCompositeId(value: unknown): value is string { if (typeof value !== "string" || /[\s*?:]/.test(value)) return false; const parts = value.split("/"); return parts.length === 2 && parts.every((part) => part.length > 0); } export function loadPiEnabledModels(opts: LoadOptions): PiEnabledModelsResult { const warnings: string[] = []; const read = opts.read ?? ((path: string) => readFileSync(path, "utf8")); const agentDir = opts.agentDir ?? resolve(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent")); const globalPath = join(agentDir, "settings.json"); const projectPath = join(opts.harnessDir, ".pi", "settings.json"); const globalSettings = readSettings(globalPath, false, read, warnings); const projectSettings = readSettings(projectPath, true, read, warnings); if (globalSettings === INVALID || projectSettings === INVALID) return { ids: [], warnings }; const projectDefinesScope = projectSettings ? Object.prototype.hasOwnProperty.call(projectSettings, "enabledModels") : false; const settings = projectDefinesScope ? projectSettings : globalSettings; const source = projectDefinesScope ? projectPath : globalPath; const raw = settings?.enabledModels; if (!Array.isArray(raw) || raw.length === 0) { warnings.push(`Pi enabledModels is missing or empty: ${source}`); return { ids: [], warnings, source }; } const ids: string[] = []; const seen = new Set(); raw.forEach((value, index) => { if (!isExactCompositeId(value)) { warnings.push(`Ignoring non-exact enabledModels entry at index ${index}: ${source}`); return; } if (!seen.has(value)) { seen.add(value); ids.push(value); } }); if (ids.length === 0) warnings.push(`Pi enabledModels contains no exact model IDs: ${source}`); return { ids, warnings, source }; }