import fs from "node:fs/promises"; import path from "node:path"; import { type ClawdbotConfig, loadConfig } from "../config/config.js"; import { resolveClawdbotAgentDir } from "./agent-paths.js"; type ModelsConfig = NonNullable; const DEFAULT_MODE: NonNullable = "merge"; function isRecord(value: unknown): value is Record { return Boolean(value && typeof value === "object" && !Array.isArray(value)); } async function readJson(pathname: string): Promise { try { const raw = await fs.readFile(pathname, "utf8"); return JSON.parse(raw) as unknown; } catch { return null; } } export async function ensureClawdbotModelsJson( config?: ClawdbotConfig, agentDirOverride?: string, ): Promise<{ agentDir: string; wrote: boolean }> { const cfg = config ?? loadConfig(); const providers = cfg.models?.providers; if (!providers || Object.keys(providers).length === 0) { const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveClawdbotAgentDir(); return { agentDir, wrote: false }; } const mode = cfg.models?.mode ?? DEFAULT_MODE; const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveClawdbotAgentDir(); const targetPath = path.join(agentDir, "models.json"); let mergedProviders = providers; let existingRaw = ""; if (mode === "merge") { const existing = await readJson(targetPath); if (isRecord(existing) && isRecord(existing.providers)) { const existingProviders = existing.providers as Record< string, NonNullable[string] >; mergedProviders = { ...existingProviders, ...providers }; } } const next = `${JSON.stringify({ providers: mergedProviders }, null, 2)}\n`; try { existingRaw = await fs.readFile(targetPath, "utf8"); } catch { existingRaw = ""; } if (existingRaw === next) { return { agentDir, wrote: false }; } await fs.mkdir(agentDir, { recursive: true, mode: 0o700 }); await fs.writeFile(targetPath, next, { mode: 0o600 }); return { agentDir, wrote: true }; }