UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

1,421 lines (1,408 loc) 97.9 kB
import { D as colorize, F as CONFIG_PATH, O as isRich$1, k as theme, p as defaultRuntime } from "./entry.js"; import { A as modelKey, D as buildModelAliasIndex, F as resolveDefaultModelForAgent, L as resolveModelRefFromString, M as parseModelRef, P as resolveConfiguredModelRef, T as resolveOpenClawAgentDir, _ as setAuthProfileOrder, _t as DEFAULT_MODEL, at as resolveEnvApiKey, ft as shouldEnableShellEnvFallback, h as listProfilesForProvider, it as resolveAwsSdkEnvVarName, j as normalizeProviderId, o as resolveProfileUnusableUntilForDisplay, rt as resolveApiKeyForProvider, st as getShellEnvAppliedKeys, t as resolveAuthProfileOrder, tt as getCustomProviderApiKey, v as upsertAuthProfile, vt as DEFAULT_PROVIDER, w as resolveAuthStorePathForDisplay, y as ensureAuthProfileStore, yt as resolveAuthProfileDisplayLabel } from "./auth-profiles-CfFGCDJa.js"; import { t as formatCliCommand } from "./command-format-3xiXujG0.js"; import { a as resolveAgentModelPrimary, c as resolveDefaultAgentId, i as resolveAgentModelFallbacksOverride, r as resolveAgentDir, s as resolveAgentWorkspaceDir, w as resolveDefaultAgentWorkspaceDir } from "./agent-scope-jm0ZdXwM.js"; import { g as shortenHomePath } from "./utils-PmTbZoD1.js"; import "./exec-BIMFe4XS.js"; import "./github-copilot-token-rP-6QdKv.js"; import { i as discoverModels, r as discoverAuthStorage } from "./pi-model-discovery-CsRo-xMp.js"; import { a as readConfigFileSnapshot, h as parseDurationMs, r as loadConfig } from "./config-DCT1RAo6.js"; import "./manifest-registry-tuAcHxrV.js"; import "./server-context-CM_E6wD5.js"; import "./errors-DdT2Dtkb.js"; import "./control-service-Ds9ompnU.js"; import "./client-cU7Xg1MO.js"; import "./call-CfqL-4Nc.js"; import "./message-channel-CAFcg7mw.js"; import { t as formatDocsLink } from "./links-jGisPfXW.js"; import "./plugins-TrKFfrLt.js"; import "./logging-fywhKCmE.js"; import "./accounts-B5QZU96b.js"; import { Dt as runEmbeddedPiAgent, Jt as loadProviderUsageSummary, Kt as loadModelCatalog, On as describeFailoverError, Xt as formatUsageWindowSummary, Zt as resolveUsageProviderId, m as openUrl } from "./loader-BYWxo-_j.js"; import { r as withProgressTotals } from "./progress-Dn3kWpaL.js"; import { n as stylePromptMessage, r as stylePromptTitle, t as stylePromptHint } from "./prompt-style-D5D7b3cX.js"; import "./note-CBiVaqG7.js"; import { t as createClackPrompter } from "./clack-prompter-BJuVh97L.js"; import "./manager-rDmdE7O9.js"; import { a as resolveSessionTranscriptsDirForAgent, r as resolveSessionTranscriptPath } from "./paths-RvF0P6tQ.js"; import "./sqlite-B_L84oiu.js"; import "./routes-yI5QIzeL.js"; import "./pi-embedded-helpers-DJgCXZEz.js"; import "./deliver-eE21zdeQ.js"; import "./sandbox-Cnq9TXEn.js"; import "./channel-summary-BkqO8zZ9.js"; import "./wsl-DASmek7h.js"; import "./skills-DtwGIkTI.js"; import { _ as ensureOpenClawModelsJson } from "./image-CXg7Z0WD.js"; import "./redact-CDPAzwi8.js"; import "./tool-display-BMYWrp0L.js"; import "./restart-sentinel-DywisDen.js"; import "./channel-selection-BAwiO0li.js"; import "./commands-DMKDOFmC.js"; import "./pairing-store-DMex6WWe.js"; import "./login-qr-sEcxw1_U.js"; import { n as resolveOptionFromCommand, r as runCommandWithRuntime } from "./cli-utils-DFbPmfWB.js"; import "./pairing-labels-C6I3dD-m.js"; import { t as renderTable } from "./table-f0EgX-YI.js"; import { a as normalizeAlias, at as validateAnthropicSetupToken, c as updateConfig, d as resolvePluginProviders, i as formatTokenK, l as createVpsAwareOAuthHandlers, n as ensureFlagCompatibility, o as resolveKnownAgentId, r as formatMs, s as resolveModelTarget, t as githubCopilotLoginCommand, u as isRemoteEnvironment, v as applyAuthProfileConfig } from "./github-copilot-auth-BHLcQ1sN.js"; import { n as logConfigUpdated } from "./logging-BnUUuH3y.js"; import { i as redactSecrets } from "./format-BnjMmWWT.js"; import { n as buildAuthHealthSummary, r as formatRemainingShort, t as DEFAULT_OAUTH_WARN_MS } from "./auth-health-DcKoxhDo.js"; import path from "node:path"; import fs from "node:fs/promises"; import { complete, getEnvApiKey, getModel } from "@mariozechner/pi-ai"; import crypto from "node:crypto"; import { Type } from "@sinclair/typebox"; import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts"; //#region src/commands/models/aliases.ts async function modelsAliasesListCommand(opts, runtime) { ensureFlagCompatibility(opts); const models = loadConfig().agents?.defaults?.models ?? {}; const aliases = Object.entries(models).reduce((acc, [modelKey, entry]) => { const alias = entry?.alias?.trim(); if (alias) acc[alias] = modelKey; return acc; }, {}); if (opts.json) { runtime.log(JSON.stringify({ aliases }, null, 2)); return; } if (opts.plain) { for (const [alias, target] of Object.entries(aliases)) runtime.log(`${alias} ${target}`); return; } runtime.log(`Aliases (${Object.keys(aliases).length}):`); if (Object.keys(aliases).length === 0) { runtime.log("- none"); return; } for (const [alias, target] of Object.entries(aliases)) runtime.log(`- ${alias} -> ${target}`); } async function modelsAliasesAddCommand(aliasRaw, modelRaw, runtime) { const alias = normalizeAlias(aliasRaw); const resolved = resolveModelTarget({ raw: modelRaw, cfg: loadConfig() }); await updateConfig((cfg) => { const modelKey = `${resolved.provider}/${resolved.model}`; const nextModels = { ...cfg.agents?.defaults?.models }; for (const [key, entry] of Object.entries(nextModels)) { const existing = entry?.alias?.trim(); if (existing && existing === alias && key !== modelKey) throw new Error(`Alias ${alias} already points to ${key}.`); } nextModels[modelKey] = { ...nextModels[modelKey] ?? {}, alias }; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, models: nextModels } } }; }); logConfigUpdated(runtime); runtime.log(`Alias ${alias} -> ${resolved.provider}/${resolved.model}`); } async function modelsAliasesRemoveCommand(aliasRaw, runtime) { const alias = normalizeAlias(aliasRaw); const updated = await updateConfig((cfg) => { const nextModels = { ...cfg.agents?.defaults?.models }; let found = false; for (const [key, entry] of Object.entries(nextModels)) if (entry?.alias?.trim() === alias) { nextModels[key] = { ...entry, alias: void 0 }; found = true; break; } if (!found) throw new Error(`Alias not found: ${alias}`); return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, models: nextModels } } }; }); logConfigUpdated(runtime); if (!updated.agents?.defaults?.models || Object.values(updated.agents.defaults.models).every((entry) => !entry?.alias?.trim())) runtime.log("No aliases configured."); } //#endregion //#region src/commands/models/auth.ts const confirm$1 = (params) => confirm({ ...params, message: stylePromptMessage(params.message) }); const text$1 = (params) => text({ ...params, message: stylePromptMessage(params.message) }); const select$1 = (params) => select({ ...params, message: stylePromptMessage(params.message), options: params.options.map((opt) => opt.hint === void 0 ? opt : { ...opt, hint: stylePromptHint(opt.hint) }) }); function resolveTokenProvider(raw) { const trimmed = raw?.trim(); if (!trimmed) return null; if (normalizeProviderId(trimmed) === "anthropic") return "anthropic"; return "custom"; } function resolveDefaultTokenProfileId(provider) { return `${normalizeProviderId(provider)}:manual`; } async function modelsAuthSetupTokenCommand(opts, runtime) { const provider = resolveTokenProvider(opts.provider ?? "anthropic"); if (provider !== "anthropic") throw new Error("Only --provider anthropic is supported for setup-token."); if (!process.stdin.isTTY) throw new Error("setup-token requires an interactive TTY."); if (!opts.yes) { if (!await confirm$1({ message: "Have you run `claude setup-token` and copied the token?", initialValue: true })) return; } const tokenInput = await text$1({ message: "Paste Anthropic setup-token", validate: (value) => validateAnthropicSetupToken(String(value ?? "")) }); const token = String(tokenInput).trim(); const profileId = resolveDefaultTokenProfileId(provider); upsertAuthProfile({ profileId, credential: { type: "token", provider, token } }); await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId, provider, mode: "token" })); logConfigUpdated(runtime); runtime.log(`Auth profile: ${profileId} (${provider}/token)`); } async function modelsAuthPasteTokenCommand(opts, runtime) { const rawProvider = opts.provider?.trim(); if (!rawProvider) throw new Error("Missing --provider."); const provider = normalizeProviderId(rawProvider); const profileId = opts.profileId?.trim() || resolveDefaultTokenProfileId(provider); const tokenInput = await text$1({ message: `Paste token for ${provider}`, validate: (value) => value?.trim() ? void 0 : "Required" }); const token = String(tokenInput).trim(); const expires = opts.expiresIn?.trim() && opts.expiresIn.trim().length > 0 ? Date.now() + parseDurationMs(String(opts.expiresIn).trim(), { defaultUnit: "d" }) : void 0; upsertAuthProfile({ profileId, credential: { type: "token", provider, token, ...expires ? { expires } : {} } }); await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId, provider, mode: "token" })); logConfigUpdated(runtime); runtime.log(`Auth profile: ${profileId} (${provider}/token)`); } async function modelsAuthAddCommand(_opts, runtime) { const provider = await select$1({ message: "Token provider", options: [{ value: "anthropic", label: "anthropic" }, { value: "custom", label: "custom (type provider id)" }] }); const providerId = provider === "custom" ? normalizeProviderId(String(await text$1({ message: "Provider id", validate: (value) => value?.trim() ? void 0 : "Required" }))) : provider; if (await select$1({ message: "Token method", options: [...providerId === "anthropic" ? [{ value: "setup-token", label: "setup-token (claude)", hint: "Paste a setup-token from `claude setup-token`" }] : [], { value: "paste", label: "paste token" }] }) === "setup-token") { await modelsAuthSetupTokenCommand({ provider: providerId }, runtime); return; } const profileIdDefault = resolveDefaultTokenProfileId(providerId); await modelsAuthPasteTokenCommand({ provider: providerId, profileId: String(await text$1({ message: "Profile id", initialValue: profileIdDefault, validate: (value) => value?.trim() ? void 0 : "Required" })).trim(), expiresIn: await confirm$1({ message: "Does this token expire?", initialValue: false }) ? String(await text$1({ message: "Expires in (duration)", initialValue: "365d", validate: (value) => { try { parseDurationMs(String(value ?? ""), { defaultUnit: "d" }); return; } catch { return "Invalid duration (e.g. 365d, 12h, 30m)"; } } })).trim() : void 0 }, runtime); } function resolveProviderMatch(providers, rawProvider) { const raw = rawProvider?.trim(); if (!raw) return null; const normalized = normalizeProviderId(raw); return providers.find((provider) => normalizeProviderId(provider.id) === normalized) ?? providers.find((provider) => provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false) ?? null; } function pickAuthMethod(provider, rawMethod) { const raw = rawMethod?.trim(); if (!raw) return null; const normalized = raw.toLowerCase(); return provider.auth.find((method) => method.id.toLowerCase() === normalized) ?? provider.auth.find((method) => method.label.toLowerCase() === normalized) ?? null; } function isPlainRecord(value) { return Boolean(value && typeof value === "object" && !Array.isArray(value)); } function mergeConfigPatch(base, patch) { if (!isPlainRecord(base) || !isPlainRecord(patch)) return patch; const next = { ...base }; for (const [key, value] of Object.entries(patch)) { const existing = next[key]; if (isPlainRecord(existing) && isPlainRecord(value)) next[key] = mergeConfigPatch(existing, value); else next[key] = value; } return next; } function applyDefaultModel(cfg, model) { const models = { ...cfg.agents?.defaults?.models }; models[model] = models[model] ?? {}; const existingModel = cfg.agents?.defaults?.model; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, models, model: { ...existingModel && typeof existingModel === "object" && "fallbacks" in existingModel ? { fallbacks: existingModel.fallbacks } : void 0, primary: model } } } }; } function credentialMode(credential) { if (credential.type === "api_key") return "api_key"; if (credential.type === "token") return "token"; return "oauth"; } async function modelsAuthLoginCommand(opts, runtime) { if (!process.stdin.isTTY) throw new Error("models auth login requires an interactive TTY."); const snapshot = await readConfigFileSnapshot(); if (!snapshot.valid) { const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n"); throw new Error(`Invalid config at ${snapshot.path}\n${issues}`); } const config = snapshot.config; const defaultAgentId = resolveDefaultAgentId(config); const agentDir = resolveAgentDir(config, defaultAgentId); const workspaceDir = resolveAgentWorkspaceDir(config, defaultAgentId) ?? resolveDefaultAgentWorkspaceDir(); const providers = resolvePluginProviders({ config, workspaceDir }); if (providers.length === 0) throw new Error(`No provider plugins found. Install one via \`${formatCliCommand("openclaw plugins install")}\`.`); const prompter = createClackPrompter(); const selectedProvider = resolveProviderMatch(providers, opts.provider) ?? await prompter.select({ message: "Select a provider", options: providers.map((provider) => ({ value: provider.id, label: provider.label, hint: provider.docsPath ? `Docs: ${provider.docsPath}` : void 0 })) }).then((id) => resolveProviderMatch(providers, String(id))); if (!selectedProvider) throw new Error("Unknown provider. Use --provider <id> to pick a provider plugin."); const chosenMethod = pickAuthMethod(selectedProvider, opts.method) ?? (selectedProvider.auth.length === 1 ? selectedProvider.auth[0] : await prompter.select({ message: `Auth method for ${selectedProvider.label}`, options: selectedProvider.auth.map((method) => ({ value: method.id, label: method.label, hint: method.hint })) }).then((id) => selectedProvider.auth.find((method) => method.id === String(id)))); if (!chosenMethod) throw new Error("Unknown auth method. Use --method <id> to select one."); const isRemote = isRemoteEnvironment(); const result = await chosenMethod.run({ config, agentDir, workspaceDir, prompter, runtime, isRemote, openUrl: async (url) => { await openUrl(url); }, oauth: { createVpsAwareHandlers: (params) => createVpsAwareOAuthHandlers(params) } }); for (const profile of result.profiles) upsertAuthProfile({ profileId: profile.profileId, credential: profile.credential, agentDir }); await updateConfig((cfg) => { let next = cfg; if (result.configPatch) next = mergeConfigPatch(next, result.configPatch); for (const profile of result.profiles) next = applyAuthProfileConfig(next, { profileId: profile.profileId, provider: profile.credential.provider, mode: credentialMode(profile.credential) }); if (opts.setDefault && result.defaultModel) next = applyDefaultModel(next, result.defaultModel); return next; }); logConfigUpdated(runtime); for (const profile of result.profiles) runtime.log(`Auth profile: ${profile.profileId} (${profile.credential.provider}/${credentialMode(profile.credential)})`); if (result.defaultModel) runtime.log(opts.setDefault ? `Default model set to ${result.defaultModel}` : `Default model available: ${result.defaultModel} (use --set-default to apply)`); if (result.notes && result.notes.length > 0) await prompter.note(result.notes.join("\n"), "Provider notes"); } //#endregion //#region src/commands/models/auth-order.ts function resolveTargetAgent(cfg, raw) { const agentId = resolveKnownAgentId({ cfg, rawAgentId: raw }) ?? resolveDefaultAgentId(cfg); return { agentId, agentDir: resolveAgentDir(cfg, agentId) }; } function describeOrder(store, provider) { const providerKey = normalizeProviderId(provider); const order = store.order?.[providerKey]; return Array.isArray(order) ? order : []; } async function modelsAuthOrderGetCommand(opts, runtime) { const rawProvider = opts.provider?.trim(); if (!rawProvider) throw new Error("Missing --provider."); const provider = normalizeProviderId(rawProvider); const { agentId, agentDir } = resolveTargetAgent(loadConfig(), opts.agent); const order = describeOrder(ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }), provider); if (opts.json) { runtime.log(JSON.stringify({ agentId, agentDir, provider, authStorePath: shortenHomePath(`${agentDir}/auth-profiles.json`), order: order.length > 0 ? order : null }, null, 2)); return; } runtime.log(`Agent: ${agentId}`); runtime.log(`Provider: ${provider}`); runtime.log(`Auth file: ${shortenHomePath(`${agentDir}/auth-profiles.json`)}`); runtime.log(order.length > 0 ? `Order override: ${order.join(", ")}` : "Order override: (none)"); } async function modelsAuthOrderClearCommand(opts, runtime) { const rawProvider = opts.provider?.trim(); if (!rawProvider) throw new Error("Missing --provider."); const provider = normalizeProviderId(rawProvider); const { agentId, agentDir } = resolveTargetAgent(loadConfig(), opts.agent); if (!await setAuthProfileOrder({ agentDir, provider, order: null })) throw new Error("Failed to update auth-profiles.json (lock busy?)."); runtime.log(`Agent: ${agentId}`); runtime.log(`Provider: ${provider}`); runtime.log("Cleared per-agent order override."); } async function modelsAuthOrderSetCommand(opts, runtime) { const rawProvider = opts.provider?.trim(); if (!rawProvider) throw new Error("Missing --provider."); const provider = normalizeProviderId(rawProvider); const { agentId, agentDir } = resolveTargetAgent(loadConfig(), opts.agent); const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false }); const providerKey = normalizeProviderId(provider); const requested = (opts.order ?? []).map((entry) => String(entry).trim()).filter(Boolean); if (requested.length === 0) throw new Error("Missing profile ids. Provide one or more profile ids."); for (const profileId of requested) { const cred = store.profiles[profileId]; if (!cred) throw new Error(`Auth profile "${profileId}" not found in ${agentDir}.`); if (normalizeProviderId(cred.provider) !== providerKey) throw new Error(`Auth profile "${profileId}" is for ${cred.provider}, not ${provider}.`); } const updated = await setAuthProfileOrder({ agentDir, provider, order: requested }); if (!updated) throw new Error("Failed to update auth-profiles.json (lock busy?)."); runtime.log(`Agent: ${agentId}`); runtime.log(`Provider: ${provider}`); runtime.log(`Order override: ${describeOrder(updated, provider).join(", ")}`); } //#endregion //#region src/commands/models/fallbacks.ts async function modelsFallbacksListCommand(opts, runtime) { ensureFlagCompatibility(opts); const fallbacks = loadConfig().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}`); } async function modelsFallbacksAddCommand(modelRaw, runtime) { const updated = await updateConfig((cfg) => { const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const targetKey = modelKey(resolved.provider, resolved.model); const nextModels = { ...cfg.agents?.defaults?.models }; if (!nextModels[targetKey]) nextModels[targetKey] = {}; const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: DEFAULT_PROVIDER }); const existing = cfg.agents?.defaults?.model?.fallbacks ?? []; if (existing.map((entry) => resolveModelRefFromString({ raw: String(entry ?? ""), defaultProvider: DEFAULT_PROVIDER, aliasIndex })).filter((entry) => Boolean(entry)).map((entry) => modelKey(entry.ref.provider, entry.ref.model)).includes(targetKey)) return cfg; const existingModel = cfg.agents?.defaults?.model; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, model: { ...existingModel?.primary ? { primary: existingModel.primary } : void 0, fallbacks: [...existing, targetKey] }, models: nextModels } } }; }); logConfigUpdated(runtime); runtime.log(`Fallbacks: ${(updated.agents?.defaults?.model?.fallbacks ?? []).join(", ")}`); } async function modelsFallbacksRemoveCommand(modelRaw, runtime) { 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; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, model: { ...existingModel?.primary ? { primary: existingModel.primary } : void 0, fallbacks: filtered } } } }; }); logConfigUpdated(runtime); runtime.log(`Fallbacks: ${(updated.agents?.defaults?.model?.fallbacks ?? []).join(", ")}`); } async function modelsFallbacksClearCommand(runtime) { await updateConfig((cfg) => { const existingModel = cfg.agents?.defaults?.model; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, model: { ...existingModel?.primary ? { primary: existingModel.primary } : void 0, fallbacks: [] } } } }; }); logConfigUpdated(runtime); runtime.log("Fallback list cleared."); } //#endregion //#region src/commands/models/image-fallbacks.ts async function modelsImageFallbacksListCommand(opts, runtime) { ensureFlagCompatibility(opts); const fallbacks = loadConfig().agents?.defaults?.imageModel?.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(`Image fallbacks (${fallbacks.length}):`); if (fallbacks.length === 0) { runtime.log("- none"); return; } for (const entry of fallbacks) runtime.log(`- ${entry}`); } async function modelsImageFallbacksAddCommand(modelRaw, runtime) { const updated = await updateConfig((cfg) => { const resolved = resolveModelTarget({ raw: modelRaw, cfg }); const targetKey = modelKey(resolved.provider, resolved.model); const nextModels = { ...cfg.agents?.defaults?.models }; if (!nextModels[targetKey]) nextModels[targetKey] = {}; const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: DEFAULT_PROVIDER }); const existing = cfg.agents?.defaults?.imageModel?.fallbacks ?? []; if (existing.map((entry) => resolveModelRefFromString({ raw: String(entry ?? ""), defaultProvider: DEFAULT_PROVIDER, aliasIndex })).filter((entry) => Boolean(entry)).map((entry) => modelKey(entry.ref.provider, entry.ref.model)).includes(targetKey)) return cfg; const existingModel = cfg.agents?.defaults?.imageModel; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, imageModel: { ...existingModel?.primary ? { primary: existingModel.primary } : void 0, fallbacks: [...existing, targetKey] }, models: nextModels } } }; }); logConfigUpdated(runtime); runtime.log(`Image fallbacks: ${(updated.agents?.defaults?.imageModel?.fallbacks ?? []).join(", ")}`); } async function modelsImageFallbacksRemoveCommand(modelRaw, runtime) { 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?.imageModel?.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(`Image fallback not found: ${targetKey}`); const existingModel = cfg.agents?.defaults?.imageModel; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, imageModel: { ...existingModel?.primary ? { primary: existingModel.primary } : void 0, fallbacks: filtered } } } }; }); logConfigUpdated(runtime); runtime.log(`Image fallbacks: ${(updated.agents?.defaults?.imageModel?.fallbacks ?? []).join(", ")}`); } async function modelsImageFallbacksClearCommand(runtime) { await updateConfig((cfg) => { const existingModel = cfg.agents?.defaults?.imageModel; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, imageModel: { ...existingModel?.primary ? { primary: existingModel.primary } : void 0, fallbacks: [] } } } }; }); logConfigUpdated(runtime); runtime.log("Image fallback list cleared."); } //#endregion //#region src/commands/models/list.configured.ts function resolveConfiguredEntries(cfg) { const resolvedDefault = resolveConfiguredModelRef({ cfg, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL }); const aliasIndex = buildModelAliasIndex({ cfg, defaultProvider: DEFAULT_PROVIDER }); const order = []; const tagsByKey = /* @__PURE__ */ new Map(); const aliasesByKey = /* @__PURE__ */ new Map(); for (const [key, aliases] of aliasIndex.byKey.entries()) aliasesByKey.set(key, aliases); const addEntry = (ref, tag) => { const key = modelKey(ref.provider, ref.model); if (!tagsByKey.has(key)) { tagsByKey.set(key, /* @__PURE__ */ new Set()); order.push(key); } tagsByKey.get(key)?.add(tag); }; addEntry(resolvedDefault, "default"); const modelConfig = cfg.agents?.defaults?.model; const imageModelConfig = cfg.agents?.defaults?.imageModel; const modelFallbacks = typeof modelConfig === "object" ? modelConfig?.fallbacks ?? [] : []; const imageFallbacks = typeof imageModelConfig === "object" ? imageModelConfig?.fallbacks ?? [] : []; const imagePrimary = imageModelConfig?.primary?.trim() ?? ""; modelFallbacks.forEach((raw, idx) => { const resolved = resolveModelRefFromString({ raw: String(raw ?? ""), defaultProvider: DEFAULT_PROVIDER, aliasIndex }); if (!resolved) return; addEntry(resolved.ref, `fallback#${idx + 1}`); }); if (imagePrimary) { const resolved = resolveModelRefFromString({ raw: imagePrimary, defaultProvider: DEFAULT_PROVIDER, aliasIndex }); if (resolved) addEntry(resolved.ref, "image"); } imageFallbacks.forEach((raw, idx) => { const resolved = resolveModelRefFromString({ raw: String(raw ?? ""), defaultProvider: DEFAULT_PROVIDER, aliasIndex }); if (!resolved) return; addEntry(resolved.ref, `img-fallback#${idx + 1}`); }); for (const key of Object.keys(cfg.agents?.defaults?.models ?? {})) { const parsed = parseModelRef(String(key ?? ""), DEFAULT_PROVIDER); if (!parsed) continue; addEntry(parsed, "configured"); } return { entries: order.map((key) => { const slash = key.indexOf("/"); return { key, ref: { provider: slash === -1 ? key : key.slice(0, slash), model: slash === -1 ? "" : key.slice(slash + 1) }, tags: tagsByKey.get(key) ?? /* @__PURE__ */ new Set(), aliases: aliasesByKey.get(key) ?? [] }; }) }; } //#endregion //#region src/commands/models/list.registry.ts const isLocalBaseUrl = (baseUrl) => { try { const host = new URL(baseUrl).hostname.toLowerCase(); return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1" || host.endsWith(".local"); } catch { return false; } }; const hasAuthForProvider = (provider, cfg, authStore) => { if (listProfilesForProvider(authStore, provider).length > 0) return true; if (provider === "amazon-bedrock" && resolveAwsSdkEnvVarName()) return true; if (resolveEnvApiKey(provider)) return true; if (getCustomProviderApiKey(cfg, provider)) return true; return false; }; async function loadModelRegistry(cfg) { await ensureOpenClawModelsJson(cfg); const agentDir = resolveOpenClawAgentDir(); const registry = discoverModels(discoverAuthStorage(agentDir), agentDir); const models = registry.getAll(); const availableModels = registry.getAvailable(); return { registry, models, availableKeys: new Set(availableModels.map((model) => modelKey(model.provider, model.id))) }; } function toModelRow(params) { const { model, key, tags, aliases = [], availableKeys, cfg, authStore } = params; if (!model) return { key, name: key, input: "-", contextWindow: null, local: null, available: null, tags: [...tags, "missing"], missing: true }; const input = model.input.join("+") || "text"; const local = isLocalBaseUrl(model.baseUrl); const available = cfg && authStore ? hasAuthForProvider(model.provider, cfg, authStore) : availableKeys?.has(modelKey(model.provider, model.id)) ?? false; const aliasTags = aliases.length > 0 ? [`alias:${aliases.join(",")}`] : []; const mergedTags = new Set(tags); if (aliasTags.length > 0) { for (const tag of mergedTags) if (tag === "alias" || tag.startsWith("alias:")) mergedTags.delete(tag); for (const tag of aliasTags) mergedTags.add(tag); } return { key, name: model.name || model.id, input, contextWindow: model.contextWindow ?? null, local, available, tags: Array.from(mergedTags), missing: false }; } //#endregion //#region src/commands/models/list.format.ts const isRich = (opts) => Boolean(isRich$1() && !opts?.json && !opts?.plain); const pad$1 = (value, size) => value.padEnd(size); const formatTag = (tag, rich) => { if (!rich) return tag; if (tag === "default") return theme.success(tag); if (tag === "image") return theme.accentBright(tag); if (tag === "configured") return theme.accent(tag); if (tag === "missing") return theme.error(tag); if (tag.startsWith("fallback#")) return theme.warn(tag); if (tag.startsWith("img-fallback#")) return theme.warn(tag); if (tag.startsWith("alias:")) return theme.accentDim(tag); return theme.muted(tag); }; const truncate$1 = (value, max) => { if (value.length <= max) return value; if (max <= 3) return value.slice(0, max); return `${value.slice(0, max - 3)}...`; }; const maskApiKey = (value) => { const trimmed = value.trim(); if (!trimmed) return "missing"; if (trimmed.length <= 16) return trimmed; return `${trimmed.slice(0, 8)}...${trimmed.slice(-8)}`; }; //#endregion //#region src/commands/models/list.table.ts const MODEL_PAD$1 = 42; const INPUT_PAD = 10; const CTX_PAD$1 = 8; const LOCAL_PAD = 5; const AUTH_PAD = 5; function printModelTable(rows, runtime, opts = {}) { if (opts.json) { runtime.log(JSON.stringify({ count: rows.length, models: rows }, null, 2)); return; } if (opts.plain) { for (const row of rows) runtime.log(row.key); return; } const rich = isRich(opts); const header = [ pad$1("Model", MODEL_PAD$1), pad$1("Input", INPUT_PAD), pad$1("Ctx", CTX_PAD$1), pad$1("Local", LOCAL_PAD), pad$1("Auth", AUTH_PAD), "Tags" ].join(" "); runtime.log(rich ? theme.heading(header) : header); for (const row of rows) { const keyLabel = pad$1(truncate$1(row.key, MODEL_PAD$1), MODEL_PAD$1); const inputLabel = pad$1(row.input || "-", INPUT_PAD); const ctxLabel = pad$1(formatTokenK(row.contextWindow), CTX_PAD$1); const localLabel = pad$1(row.local === null ? "-" : row.local ? "yes" : "no", LOCAL_PAD); const authLabel = pad$1(row.available === null ? "-" : row.available ? "yes" : "no", AUTH_PAD); const tagsLabel = row.tags.length > 0 ? rich ? row.tags.map((tag) => formatTag(tag, rich)).join(",") : row.tags.join(",") : ""; const coloredInput = colorize(rich, row.input.includes("image") ? theme.accentBright : theme.info, inputLabel); const coloredLocal = colorize(rich, row.local === null ? theme.muted : row.local ? theme.success : theme.muted, localLabel); const coloredAuth = colorize(rich, row.available === null ? theme.muted : row.available ? theme.success : theme.error, authLabel); const line = [ rich ? theme.accent(keyLabel) : keyLabel, coloredInput, ctxLabel, coloredLocal, coloredAuth, tagsLabel ].join(" "); runtime.log(line); } } //#endregion //#region src/commands/models/list.list-command.ts async function modelsListCommand(opts, runtime) { ensureFlagCompatibility(opts); const cfg = loadConfig(); const authStore = ensureAuthProfileStore(); const providerFilter = (() => { const raw = opts.provider?.trim(); if (!raw) return; return parseModelRef(`${raw}/_`, DEFAULT_PROVIDER)?.provider ?? raw.toLowerCase(); })(); let models = []; let availableKeys; try { const loaded = await loadModelRegistry(cfg); models = loaded.models; availableKeys = loaded.availableKeys; } catch (err) { runtime.error(`Model registry unavailable: ${String(err)}`); } const modelByKey = new Map(models.map((model) => [modelKey(model.provider, model.id), model])); const { entries } = resolveConfiguredEntries(cfg); const configuredByKey = new Map(entries.map((entry) => [entry.key, entry])); const rows = []; const isLocalBaseUrl = (baseUrl) => { try { const host = new URL(baseUrl).hostname.toLowerCase(); return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1" || host.endsWith(".local"); } catch { return false; } }; if (opts.all) { const sorted = [...models].toSorted((a, b) => { const p = a.provider.localeCompare(b.provider); if (p !== 0) return p; return a.id.localeCompare(b.id); }); for (const model of sorted) { if (providerFilter && model.provider.toLowerCase() !== providerFilter) continue; if (opts.local && !isLocalBaseUrl(model.baseUrl)) continue; const key = modelKey(model.provider, model.id); const configured = configuredByKey.get(key); rows.push(toModelRow({ model, key, tags: configured ? Array.from(configured.tags) : [], aliases: configured?.aliases ?? [], availableKeys, cfg, authStore })); } } else for (const entry of entries) { if (providerFilter && entry.ref.provider.toLowerCase() !== providerFilter) continue; const model = modelByKey.get(entry.key); if (opts.local && model && !isLocalBaseUrl(model.baseUrl)) continue; if (opts.local && !model) continue; rows.push(toModelRow({ model, key: entry.key, tags: Array.from(entry.tags), aliases: entry.aliases, availableKeys, cfg, authStore })); } if (rows.length === 0) { runtime.log("No models found."); return; } printModelTable(rows, runtime, opts); } //#endregion //#region src/commands/models/list.auth-overview.ts function resolveProviderAuthOverview(params) { const { provider, cfg, store } = params; const now = Date.now(); const profiles = listProfilesForProvider(store, provider); const withUnusableSuffix = (base, profileId) => { const unusableUntil = resolveProfileUnusableUntilForDisplay(store, profileId); if (!unusableUntil || now >= unusableUntil) return base; const stats = store.usageStats?.[profileId]; return `${base} [${typeof stats?.disabledUntil === "number" && now < stats.disabledUntil ? `disabled${stats.disabledReason ? `:${stats.disabledReason}` : ""}` : "cooldown"} ${formatRemainingShort(unusableUntil - now)}]`; }; const labels = profiles.map((profileId) => { const profile = store.profiles[profileId]; if (!profile) return `${profileId}=missing`; if (profile.type === "api_key") return withUnusableSuffix(`${profileId}=${maskApiKey(profile.key ?? "")}`, profileId); if (profile.type === "token") return withUnusableSuffix(`${profileId}=token:${maskApiKey(profile.token)}`, profileId); const display = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); const suffix = display === profileId ? "" : display.startsWith(profileId) ? display.slice(profileId.length).trim() : `(${display})`; return withUnusableSuffix(`${profileId}=OAuth${suffix ? ` ${suffix}` : ""}`, profileId); }); const oauthCount = profiles.filter((id) => store.profiles[id]?.type === "oauth").length; const tokenCount = profiles.filter((id) => store.profiles[id]?.type === "token").length; const apiKeyCount = profiles.filter((id) => store.profiles[id]?.type === "api_key").length; const envKey = resolveEnvApiKey(provider); const customKey = getCustomProviderApiKey(cfg, provider); return { provider, effective: (() => { if (profiles.length > 0) return { kind: "profiles", detail: shortenHomePath(resolveAuthStorePathForDisplay()) }; if (envKey) return { kind: "env", detail: envKey.source.includes("OAUTH_TOKEN") || envKey.source.toLowerCase().includes("oauth") ? "OAuth (env)" : maskApiKey(envKey.apiKey) }; if (customKey) return { kind: "models.json", detail: maskApiKey(customKey) }; return { kind: "missing", detail: "missing" }; })(), profiles: { count: profiles.length, oauth: oauthCount, token: tokenCount, apiKey: apiKeyCount, labels }, ...envKey ? { env: { value: envKey.source.includes("OAUTH_TOKEN") || envKey.source.toLowerCase().includes("oauth") ? "OAuth (env)" : maskApiKey(envKey.apiKey), source: envKey.source } } : {}, ...customKey ? { modelsJson: { value: maskApiKey(customKey), source: `models.json: ${shortenHomePath(params.modelsPath)}` } } : {} }; } //#endregion //#region src/commands/models/list.probe.ts const PROBE_PROMPT = "Reply with OK. Do not use tools."; const toStatus = (reason) => { if (!reason) return "unknown"; if (reason === "auth") return "auth"; if (reason === "rate_limit") return "rate_limit"; if (reason === "billing") return "billing"; if (reason === "timeout") return "timeout"; if (reason === "format") return "format"; return "unknown"; }; function buildCandidateMap(modelCandidates) { const map = /* @__PURE__ */ new Map(); for (const raw of modelCandidates) { const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); if (!parsed) continue; const list = map.get(parsed.provider) ?? []; if (!list.includes(parsed.model)) list.push(parsed.model); map.set(parsed.provider, list); } return map; } function selectProbeModel(params) { const { provider, candidates, catalog } = params; const direct = candidates.get(provider); if (direct && direct.length > 0) return { provider, model: direct[0] }; const fromCatalog = catalog.find((entry) => entry.provider === provider); if (fromCatalog) return { provider: fromCatalog.provider, model: fromCatalog.id }; return null; } function buildProbeTargets(params) { const { cfg, providers, modelCandidates, options } = params; const store = ensureAuthProfileStore(); const providerFilter = options.provider?.trim(); const providerFilterKey = providerFilter ? normalizeProviderId(providerFilter) : null; const profileFilter = new Set((options.profileIds ?? []).map((id) => id.trim()).filter(Boolean)); return loadModelCatalog({ config: cfg }).then((catalog) => { const candidates = buildCandidateMap(modelCandidates); const targets = []; const results = []; for (const provider of providers) { const providerKey = normalizeProviderId(provider); if (providerFilterKey && providerKey !== providerFilterKey) continue; const model = selectProbeModel({ provider: providerKey, candidates, catalog }); const profileIds = listProfilesForProvider(store, providerKey); const explicitOrder = (() => { const order = store.order; if (order) { for (const [key, value] of Object.entries(order)) if (normalizeProviderId(key) === providerKey) return value; } const cfgOrder = cfg?.auth?.order; if (cfgOrder) { for (const [key, value] of Object.entries(cfgOrder)) if (normalizeProviderId(key) === providerKey) return value; } })(); const allowedProfiles = explicitOrder && explicitOrder.length > 0 ? new Set(resolveAuthProfileOrder({ cfg, store, provider: providerKey })) : null; const filteredProfiles = profileFilter.size ? profileIds.filter((id) => profileFilter.has(id)) : profileIds; if (filteredProfiles.length > 0) { for (const profileId of filteredProfiles) { const mode = store.profiles[profileId]?.type; const label = resolveAuthProfileDisplayLabel({ cfg, store, profileId }); if (explicitOrder && !explicitOrder.includes(profileId)) { results.push({ provider: providerKey, model: model ? `${model.provider}/${model.model}` : void 0, profileId, label, source: "profile", mode, status: "unknown", error: "Excluded by auth.order for this provider." }); continue; } if (allowedProfiles && !allowedProfiles.has(profileId)) { results.push({ provider: providerKey, model: model ? `${model.provider}/${model.model}` : void 0, profileId, label, source: "profile", mode, status: "unknown", error: "Auth profile credentials are missing or expired." }); continue; } if (!model) { results.push({ provider: providerKey, model: void 0, profileId, label, source: "profile", mode, status: "no_model", error: "No model available for probe" }); continue; } targets.push({ provider: providerKey, model, profileId, label, source: "profile", mode }); } continue; } if (profileFilter.size > 0) continue; const envKey = resolveEnvApiKey(providerKey); const customKey = getCustomProviderApiKey(cfg, providerKey); if (!envKey && !customKey) continue; const label = envKey ? "env" : "models.json"; const source = envKey ? "env" : "models.json"; const mode = envKey?.source.includes("OAUTH_TOKEN") ? "oauth" : "api_key"; if (!model) { results.push({ provider: providerKey, model: void 0, label, source, mode, status: "no_model", error: "No model available for probe" }); continue; } targets.push({ provider: providerKey, model, label, source, mode }); } return { targets, results }; }); } async function probeTarget(params) { const { cfg, agentId, agentDir, workspaceDir, sessionDir, target, timeoutMs, maxTokens } = params; if (!target.model) return { provider: target.provider, model: void 0, profileId: target.profileId, label: target.label, source: target.source, mode: target.mode, status: "no_model", error: "No model available for probe" }; const sessionId = `probe-${target.provider}-${crypto.randomUUID()}`; const sessionFile = resolveSessionTranscriptPath(sessionId, agentId); await fs.mkdir(sessionDir, { recursive: true }); const start = Date.now(); try { await runEmbeddedPiAgent({ sessionId, sessionFile, workspaceDir, agentDir, config: cfg, prompt: PROBE_PROMPT, provider: target.model.provider, model: target.model.model, authProfileId: target.profileId, authProfileIdSource: target.profileId ? "user" : void 0, timeoutMs, runId: `probe-${crypto.randomUUID()}`, lane: `auth-probe:${target.provider}:${target.profileId ?? target.source}`, thinkLevel: "off", reasoningLevel: "off", verboseLevel: "off", streamParams: { maxTokens } }); return { provider: target.provider, model: `${target.model.provider}/${target.model.model}`, profileId: target.profileId, label: target.label, source: target.source, mode: target.mode, status: "ok", latencyMs: Date.now() - start }; } catch (err) { const described = describeFailoverError(err); return { provider: target.provider, model: `${target.model.provider}/${target.model.model}`, profileId: target.profileId, label: target.label, source: target.source, mode: target.mode, status: toStatus(described.reason), error: redactSecrets(described.message), latencyMs: Date.now() - start }; } } async function runTargetsWithConcurrency(params) { const { cfg, targets, timeoutMs, maxTokens, onProgress } = params; const concurrency = Math.max(1, Math.min(targets.length || 1, params.concurrency)); const agentId = resolveDefaultAgentId(cfg); const agentDir = resolveOpenClawAgentDir(); const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId) ?? resolveDefaultAgentWorkspaceDir(); const sessionDir = resolveSessionTranscriptsDirForAgent(agentId); await fs.mkdir(workspaceDir, { recursive: true }); let completed = 0; const results = Array.from({ length: targets.length }); let cursor = 0; const worker = async () => { while (true) { const index = cursor; cursor += 1; if (index >= targets.length) return; const target = targets[index]; onProgress?.({ completed, total: targets.length, label: `Probing ${target.provider}${target.profileId ? ` (${target.label})` : ""}` }); results[index] = await probeTarget({ cfg, agentId, agentDir, workspaceDir, sessionDir, target, timeoutMs, maxTokens }); completed += 1; onProgress?.({ completed, total: targets.length }); } }; await Promise.all(Array.from({ length: concurrency }, () => worker())); return results.filter((entry) => Boolean(entry)); } async function runAuthProbes(params) { const startedAt = Date.now(); const plan = await buildProbeTargets({ cfg: params.cfg, providers: params.providers, modelCandidates: params.modelCandidates, options: params.options }); const totalTargets = plan.targets.length; params.onProgress?.({ completed: 0, total: totalTargets }); const results = totalTargets ? await runTargetsWithConcurrency({ cfg: params.cfg, targets: plan.targets, timeoutMs: params.options.timeoutMs, maxTokens: params.options.maxTokens, concurrency: params.options.concurrency, onProgress: params.onProgress }) : []; const finishedAt = Date.now(); return { startedAt, finishedAt, durationMs: finishedAt - startedAt, totalTargets, options: params.options, results: [...plan.results, ...results] }; } function formatProbeLatency(latencyMs) { if (!latencyMs && latencyMs !== 0) return "-"; return formatMs(latencyMs); } function sortProbeResults(results) { return results.slice().toSorted((a, b) => { const provider = a.provider.localeCompare(b.provider); if (provider !== 0) return provider; const aLabel = a.label || a.profileId || ""; const bLabel = b.label || b.profileId || ""; return aLabel.localeCompare(bLabel); }); } function describeProbeSummary(summary) { if (summary.totalTargets === 0) return "No probe targets."; return `Probed ${summary.totalTargets} target${summary.totalTargets === 1 ? "" : "s"} in ${formatMs(summary.durationMs)}`; } //#endregion //#region src/commands/models/list.status-command.ts async function modelsStatusCommand(opts, runtime) { ensureFlagCompatibility(opts); if (opts.plain && opts.probe) throw new Error("--probe cannot be used with --plain output."); const cfg = loadConfig(); const agentId = resolveKnownAgentId({ cfg, rawAgentId: opts.agent }); const agentDir = agentId ? resolveAgentDir(cfg, agentId) : resolveOpenClawAgentDir(); const agentModelPrimary = agentId ? resolveAgentModelPrimary(cfg, agentId) : void 0; const agentFallbacksOverride = agentId ? resolveAgentModelFallbacksOverride(cfg, agentId) : void 0; const resolved = agentId ? resolveDefaultModelForAgent({ cfg, agentId }) : resolveConfiguredModelRef({ cfg, defaultProvider: DEFAULT_PROVIDER, defaultModel: DEFAULT_MODEL }); const modelConfig = cfg.agents?.defaults?.model; const imageConfig = cfg.agents?.defaults?.imageModel; const rawDefaultsModel = typeof modelConfig === "string" ? modelConfig.trim() : modelConfig?.primary?.trim() ?? ""; const rawModel = agentModelPrimary ?? rawDefaultsModel; const resolvedLabel = `${resolved.provider}/${resolved.model}`; const defaultLabel = rawModel || resolvedLabel; const defaultsFallbacks = typeof modelConfig === "object" ? modelConfig?.fallbacks ?? [] : []; const fallbacks = agentFallbacksOverride ?? defaultsFallbacks; const imageModel = typeof imageConfig === "string" ? imageConfig.trim() : imageConfig?.primary?.trim() ?? ""; const imageFallbacks = typeof imageConfig === "object" ? imageConfig?.fallbacks ?? [] : []; const aliases = Obje