import { buildModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js"; import { loadConfig } from "../../config/config.js"; import { logConfigUpdated } from "../../config/logging.js"; import type { RuntimeEnv } from "../../runtime.js"; import { DEFAULT_PROVIDER, ensureFlagCompatibility, modelKey, resolveModelTarget, updateConfig, validateModelInCatalog, } from "./shared.js"; export async function modelsFallbacksListCommand( opts: { json?: boolean; plain?: boolean }, runtime: RuntimeEnv, ) { ensureFlagCompatibility(opts); const cfg = loadConfig(); const fallbacks = cfg.agents?.defaults?.model?.fallbacks ?? []; if (opts.json) { runtime.log(JSON.stringify({ fallbacks }, null, 2)); return; } if (opts.plain) { for (const entry of fallbacks) runtime.log(entry); return; } runtime.log(`Fallbacks (${fallbacks.length}):`); if (fallbacks.length === 0) { runtime.log("- none"); return; } for (const entry of fallbacks) runtime.log(`- ${entry}`); } export async function modelsFallbacksAddCommand( modelRaw: string, runtime: RuntimeEnv, opts?: { force?: boolean }, ) { const cfg = loadConfig(); const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const targetKey = modelKey(resolved.provider, resolved.model); // Validate model exists in catalog unless --force is used const validation = await validateModelInCatalog(resolved.provider, resolved.model); if (!validation.valid) { if (opts?.force) { runtime.log(`⚠️ Model not found in catalog: ${targetKey}. Proceeding anyway (--force).`); } else { const suggestionText = validation.suggestions && validation.suggestions.length > 0 ? `\nDid you mean: ${validation.suggestions.join(", ")}?` : ""; throw new Error( `Unknown model: ${targetKey}${suggestionText}\nUse --force to skip validation.`, ); } } const updated = await updateConfig((cfgSnapshot) => { const nextModels = { ...cfgSnapshot.agents?.defaults?.models }; if (!nextModels[targetKey]) nextModels[targetKey] = {}; const aliasIndex = buildModelAliasIndex({ cfg: cfgSnapshot, defaultProvider: DEFAULT_PROVIDER, }); const existing = cfgSnapshot.agents?.defaults?.model?.fallbacks ?? []; const existingKeys = existing .map((entry) => resolveModelRefFromString({ raw: String(entry ?? ""), defaultProvider: DEFAULT_PROVIDER, aliasIndex, }), ) .filter((entry): entry is NonNullable => Boolean(entry)) .map((entry) => modelKey(entry.ref.provider, entry.ref.model)); if (existingKeys.includes(targetKey)) return cfgSnapshot; const existingModel = cfgSnapshot.agents?.defaults?.model as | { primary?: string; fallbacks?: string[] } | undefined; return { ...cfgSnapshot, agents: { ...cfgSnapshot.agents, defaults: { ...cfgSnapshot.agents?.defaults, model: { ...(existingModel?.primary ? { primary: existingModel.primary } : undefined), fallbacks: [...existing, targetKey], }, models: nextModels, }, }, }; }); logConfigUpdated(runtime); runtime.log(`Fallbacks: ${(updated.agents?.defaults?.model?.fallbacks ?? []).join(", ")}`); } export async function modelsFallbacksRemoveCommand(modelRaw: string, runtime: RuntimeEnv) { const updated = await updateConfig((cfg) => { const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const targetKey = modelKey(resolved.provider, resolved.model); const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: DEFAULT_PROVIDER, }); const existing = cfg.agents?.defaults?.model?.fallbacks ?? []; const filtered = existing.filter((entry) => { const resolvedEntry = resolveModelRefFromString({ raw: String(entry ?? ""), defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); if (!resolvedEntry) return true; return modelKey(resolvedEntry.ref.provider, resolvedEntry.ref.model) !== targetKey; }); if (filtered.length === existing.length) { throw new Error(`Fallback not found: ${targetKey}`); } const existingModel = cfg.agents?.defaults?.model as | { primary?: string; fallbacks?: string[] } | undefined; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, model: { ...(existingModel?.primary ? { primary: existingModel.primary } : undefined), fallbacks: filtered, }, }, }, }; }); logConfigUpdated(runtime); runtime.log(`Fallbacks: ${(updated.agents?.defaults?.model?.fallbacks ?? []).join(", ")}`); } export async function modelsFallbacksClearCommand(runtime: RuntimeEnv) { await updateConfig((cfg) => { const existingModel = cfg.agents?.defaults?.model as | { primary?: string; fallbacks?: string[] } | undefined; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, model: { ...(existingModel?.primary ? { primary: existingModel.primary } : undefined), fallbacks: [], }, }, }, }; }); logConfigUpdated(runtime); runtime.log("Fallback list cleared."); }