feat(backend): read Pi enabled model scope
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } 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<string, unknown>;
|
||||
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 ?? 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<string>();
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user