| import { buildModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js"; |
| import type { OpenClawConfig } from "../../config/config.js"; |
| import { logConfigUpdated } from "../../config/logging.js"; |
| import { resolveAgentModelFallbackValues, toAgentModelListLike } from "../../config/model-input.js"; |
| import type { AgentModelEntryConfig } from "../../config/types.agent-defaults.js"; |
| import type { RuntimeEnv } from "../../runtime.js"; |
| import { loadModelsConfig } from "./load-config.js"; |
| import { |
| DEFAULT_PROVIDER, |
| ensureFlagCompatibility, |
| mergePrimaryFallbackConfig, |
| modelKey, |
| resolveModelTarget, |
| resolveModelKeysFromEntries, |
| upsertCanonicalModelConfigEntry, |
| updateConfig, |
| } from "./shared.js"; |
|
|
| type DefaultsFallbackKey = "model" | "imageModel"; |
|
|
| function getFallbacks(cfg: OpenClawConfig, key: DefaultsFallbackKey): string[] { |
| return resolveAgentModelFallbackValues(cfg.agents?.defaults?.[key]); |
| } |
|
|
| function patchDefaultsFallbacks( |
| cfg: OpenClawConfig, |
| params: { key: DefaultsFallbackKey; fallbacks: string[]; models?: Record<string, unknown> }, |
| ): OpenClawConfig { |
| const existing = toAgentModelListLike(cfg.agents?.defaults?.[params.key]); |
| return { |
| ...cfg, |
| agents: { |
| ...cfg.agents, |
| defaults: { |
| ...cfg.agents?.defaults, |
| [params.key]: mergePrimaryFallbackConfig(existing, { fallbacks: params.fallbacks }), |
| ...(params.models ? { models: params.models as never } : undefined), |
| }, |
| }, |
| }; |
| } |
|
|
| export async function listFallbacksCommand( |
| params: { label: string; key: DefaultsFallbackKey }, |
| opts: { json?: boolean; plain?: boolean }, |
| runtime: RuntimeEnv, |
| ) { |
| ensureFlagCompatibility(opts); |
| const cfg = await loadModelsConfig({ commandName: `models ${params.key} list`, runtime }); |
| const fallbacks = getFallbacks(cfg, params.key); |
|
|
| 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(`${params.label} (${fallbacks.length}):`); |
| if (fallbacks.length === 0) { |
| runtime.log("- none"); |
| return; |
| } |
| for (const entry of fallbacks) { |
| runtime.log(`- ${entry}`); |
| } |
| } |
|
|
| export async function addFallbackCommand( |
| params: { |
| label: string; |
| key: DefaultsFallbackKey; |
| logPrefix: string; |
| }, |
| modelRaw: string, |
| runtime: RuntimeEnv, |
| ) { |
| const updated = await updateConfig((cfg) => { |
| const resolved = resolveModelTarget({ raw: modelRaw, cfg }); |
| const nextModels = { |
| ...cfg.agents?.defaults?.models, |
| } as Record<string, AgentModelEntryConfig>; |
| const targetKey = upsertCanonicalModelConfigEntry(nextModels, resolved); |
| const existing = getFallbacks(cfg, params.key); |
| const existingKeys = resolveModelKeysFromEntries({ cfg, entries: existing }); |
| if (existingKeys.includes(targetKey)) { |
| return cfg; |
| } |
|
|
| return patchDefaultsFallbacks(cfg, { |
| key: params.key, |
| fallbacks: [...existing, targetKey], |
| models: nextModels, |
| }); |
| }); |
|
|
| logConfigUpdated(runtime); |
| runtime.log(`${params.logPrefix}: ${getFallbacks(updated, params.key).join(", ")}`); |
| } |
|
|
| export async function removeFallbackCommand( |
| params: { |
| label: string; |
| key: DefaultsFallbackKey; |
| notFoundLabel: string; |
| logPrefix: string; |
| }, |
| 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 = getFallbacks(cfg, params.key); |
| 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(`${params.notFoundLabel} not found: ${targetKey}`); |
| } |
|
|
| return patchDefaultsFallbacks(cfg, { key: params.key, fallbacks: filtered }); |
| }); |
|
|
| logConfigUpdated(runtime); |
| runtime.log(`${params.logPrefix}: ${getFallbacks(updated, params.key).join(", ")}`); |
| } |
|
|
| export async function clearFallbacksCommand( |
| params: { key: DefaultsFallbackKey; clearedMessage: string }, |
| runtime: RuntimeEnv, |
| ) { |
| await updateConfig((cfg) => { |
| return patchDefaultsFallbacks(cfg, { key: params.key, fallbacks: [] }); |
| }); |
|
|
| logConfigUpdated(runtime); |
| runtime.log(params.clearedMessage); |
| } |
|
|