UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,066 lines (1,065 loc) 47.2 kB
import { c as isRecord } from "./utils-CCC-BEJH.js"; import { t as isBlockedObjectKey } from "./prototype-keys-D2nJOZIy.js"; import { i as normalizeProviderId } from "./provider-id-Dq06Bcx6.js"; import { t as splitTrailingAuthProfile } from "./model-ref-profile-BIKs-96s.js"; import { t as isSafeExecutableValue } from "./exec-safety-DtLGRBJm.js"; import { r as isModelThinkingFormat } from "./types.models-C6-aKREc.js"; //#region src/config/legacy.shared.ts const getRecord = (value) => isRecord(value) ? value : null; const ensureRecord = (root, key) => { const existing = root[key]; if (isRecord(existing)) return existing; const next = {}; root[key] = next; return next; }; const mergeMissing = (target, source) => { for (const [key, value] of Object.entries(source)) { if (value === void 0 || isBlockedObjectKey(key)) continue; const existing = target[key]; if (existing === void 0) { target[key] = value; continue; } if (isRecord(existing) && isRecord(value)) mergeMissing(existing, value); } }; const mapLegacyAudioTranscription = (value) => { const transcriber = getRecord(value); const command = Array.isArray(transcriber?.command) ? transcriber?.command : null; if (!command || command.length === 0) return null; if (typeof command[0] !== "string") return null; if (!command.every((part) => typeof part === "string")) return null; const rawExecutable = command[0].trim(); if (!rawExecutable) return null; if (!isSafeExecutableValue(rawExecutable)) return null; const args = command.slice(1).map((part) => part.replace(/\{input\}/g, "{{MediaPath}}")); const timeoutSeconds = typeof transcriber?.timeoutSeconds === "number" ? transcriber?.timeoutSeconds : void 0; const result = { command: rawExecutable, type: "cli" }; if (args.length > 0) result.args = args; if (timeoutSeconds !== void 0) result.timeoutSeconds = timeoutSeconds; return result; }; const defineLegacyConfigMigration = (migration) => migration; //#endregion //#region src/commands/doctor/shared/legacy-models-add-metadata.ts const LEGACY_MODELS_ADD_CODEX_MODEL_IDS = new Set(["gpt-5.5", "gpt-5.5-pro"]); const LEGACY_MODELS_ADD_CODEX_APIS = new Set(["openai-codex-responses", "openai-chatgpt-responses"]); /** Return true when a model entry matches the legacy Codex `/models add` default shape. */ function isLegacyModelsAddCodexMetadataModel(params) { const model = params.model; if (normalizeProviderId(params.provider) !== "openai-codex" || !model) return false; const id = model.id?.trim().toLowerCase(); if (!id || !LEGACY_MODELS_ADD_CODEX_MODEL_IDS.has(id)) return false; return typeof model.api === "string" && LEGACY_MODELS_ADD_CODEX_APIS.has(model.api) && model.reasoning === true && Array.isArray(model.input) && model.input.length === 2 && model.input[0] === "text" && model.input[1] === "image" && model.cost?.input === 5 && model.cost.output === 30 && model.cost.cacheRead === .5 && model.cost.cacheWrite === 0 && model.contextWindow === 4e5 && model.contextTokens === 272e3 && model.maxTokens === 128e3; } //#endregion //#region src/commands/doctor/shared/legacy-config-migrations.runtime.models.ts const STALE_CONTEXT_WINDOW_FIXES = { "deepseek/deepseek-v4-flash": { stale: 2e5, correct: 1e6 } }; function resolveStaleContextWindowFix(params) { if (params.providerId !== "deepseek") return; const fix = STALE_CONTEXT_WINDOW_FIXES[params.modelId.includes("/") ? params.modelId : `deepseek/${params.modelId}`]; return fix && params.contextWindow === fix.stale ? fix : void 0; } function hasStaleContextWindowValue(providers) { const providersRecord = getRecord(providers); if (!providersRecord) return false; for (const [providerId, provider] of Object.entries(providersRecord)) { const models = getRecord(provider)?.models; if (!Array.isArray(models)) continue; for (const model of models) { const modelRecord = getRecord(model); const modelId = typeof modelRecord?.id === "string" ? modelRecord.id : void 0; const contextWindow = modelRecord?.contextWindow; if (!modelId || typeof contextWindow !== "number" || !Number.isFinite(contextWindow)) continue; if (resolveStaleContextWindowFix({ providerId, modelId, contextWindow })) return true; } } return false; } function hasInvalidThinkingFormat(providers) { const providersRecord = getRecord(providers); if (!providersRecord) return false; for (const provider of Object.values(providersRecord)) { const models = getRecord(provider)?.models; if (!Array.isArray(models)) continue; for (const model of models) { const thinkingFormat = getRecord(getRecord(model)?.compat)?.thinkingFormat; if (typeof thinkingFormat === "string" && !isModelThinkingFormat(thinkingFormat)) return true; } } return false; } const LEGACY_VLLM_QWEN_THINKING_FORMAT_KEYS = ["qwenThinkingFormat", "qwen_thinking_format"]; function normalizeLegacyVllmQwenThinkingFormat(value) { if (typeof value !== "string") return; switch (value.trim().toLowerCase().replace(/[_\s]+/g, "-")) { case "chat-template": case "chat-template-argument": case "chat-template-arguments": case "chat-template-kwarg": case "chat-template-kwargs": case "qwen-chat-template": return "qwen-chat-template"; case "enable-thinking": case "qwen": case "request-body": case "top-level": return "qwen"; default: return; } } function getLegacyVllmQwenThinkingFormat(params) { for (const key of LEGACY_VLLM_QWEN_THINKING_FORMAT_KEYS) if (Object.hasOwn(params, key)) return { key, value: params[key], compat: normalizeLegacyVllmQwenThinkingFormat(params[key]) }; } function parseVllmAgentModelKey(key) { const trimmed = splitTrailingAuthProfile(key).model.trim(); const slashIndex = trimmed.indexOf("/"); if (slashIndex <= 0) return; if (normalizeProviderId(trimmed.slice(0, slashIndex)) !== "vllm") return; const modelId = trimmed.slice(slashIndex + 1).trim(); return modelId && modelId !== "*" ? modelId : void 0; } function hasLegacyVllmQwenThinkingFormat(defaultModels) { const models = getRecord(defaultModels); if (!models) return false; for (const [key, entry] of Object.entries(models)) { if (!parseVllmAgentModelKey(key)) continue; const params = getRecord(getRecord(entry)?.params); if (params && getLegacyVllmQwenThinkingFormat(params)) return true; } return false; } function hasLegacyVllmQwenThinkingProviderParams(provider) { const params = getRecord(getRecord(provider)?.params); return Boolean(params && getLegacyVllmQwenThinkingFormat(params)); } function hasLegacyVllmQwenThinkingModelParams(provider) { const models = getRecord(provider)?.models; if (!Array.isArray(models)) return false; return models.some((model) => { const params = getRecord(getRecord(model)?.params); return Boolean(params && getLegacyVllmQwenThinkingFormat(params)); }); } function hasLegacyVllmQwenThinkingParams(params) { const record = getRecord(params); return Boolean(record && getLegacyVllmQwenThinkingFormat(record)); } function hasLegacyVllmQwenThinkingAgentParams(agents) { const list = getRecord(agents)?.list; if (!Array.isArray(list)) return false; return list.some((agent) => hasLegacyVllmQwenThinkingParams(getRecord(agent)?.params)); } function findOrCreateVllmModelEntry(raw, modelId) { const modelsRoot = getOrCreateRecord(raw, "models"); const providers = modelsRoot ? getOrCreateRecord(modelsRoot, "providers") : void 0; const vllm = providers ? getOrCreateVllmProvider(providers) : void 0; if (!vllm) return; if (vllm.models !== void 0 && !Array.isArray(vllm.models)) return; const models = Array.isArray(vllm.models) ? vllm.models : []; vllm.models = models; const providerModelId = `vllm/${modelId}`; for (const [index, model] of models.entries()) { const record = getRecord(model); if (record?.id === modelId || record?.id === providerModelId) return { model: record, index }; } const model = { id: modelId, name: modelId }; models.push(model); return { model, index: models.length - 1 }; } function listExistingVllmModelTargets(raw) { const models = findVllmProvider(getRecord(getRecord(raw.models)?.providers))?.models; if (!Array.isArray(models)) return []; return models.flatMap((model, index) => { const record = getRecord(model); return record ? [{ model: record, index }] : []; }); } function collectVllmModelIdsFromSelection(value) { if (typeof value === "string") { const modelId = parseVllmAgentModelKey(value); return modelId ? [modelId] : []; } const record = getRecord(value); if (!record) return []; const ids = []; if (typeof record.primary === "string") { const primary = parseVllmAgentModelKey(record.primary); if (primary) ids.push(primary); } if (Array.isArray(record.fallbacks)) for (const fallback of record.fallbacks) { if (typeof fallback !== "string") continue; const modelId = parseVllmAgentModelKey(fallback); if (modelId) ids.push(modelId); } return ids; } function collectVllmModelIdsFromAgentModelMap(value) { const models = getRecord(value); if (!models) return []; return Object.keys(models).flatMap((key) => { const modelId = parseVllmAgentModelKey(key); return modelId ? [modelId] : []; }); } function createVllmModelTargets(raw, modelIds) { const targets = []; const seen = /* @__PURE__ */ new Set(); for (const modelId of modelIds) { const target = findOrCreateVllmModelEntry(raw, modelId); if (!target || seen.has(target.model)) continue; seen.add(target.model); targets.push(target); } return targets; } function combineVllmModelTargets(...groups) { const targets = []; const seen = /* @__PURE__ */ new Set(); for (const group of groups) for (const target of group) { if (seen.has(target.model)) continue; seen.add(target.model); targets.push(target); } return targets; } function collectVllmModelIdsFromAgentList(value) { if (!Array.isArray(value)) return []; return value.flatMap((agent) => { const record = getRecord(agent); return record ? [...collectVllmModelIdsFromSelection(record.model), ...collectVllmModelIdsFromAgentModelMap(record.models)] : []; }); } function getOrCreateRecord(root, key) { if (root[key] === void 0) { const next = {}; root[key] = next; return next; } return getRecord(root[key]) ?? void 0; } function findVllmProvider(providers) { if (!providers) return; const key = Object.keys(providers).find((entry) => normalizeProviderId(entry) === "vllm"); return key ? getRecord(providers[key]) ?? void 0 : void 0; } function getOrCreateVllmProvider(providers) { const key = Object.keys(providers).find((entry) => normalizeProviderId(entry) === "vllm"); if (key) return getRecord(providers[key]) ?? void 0; return getOrCreateRecord(providers, "vllm"); } function hasLegacyVllmQwenThinkingNormalizedProvider(providers) { const providersRecord = getRecord(providers); if (!providersRecord || getRecord(providersRecord.vllm)) return false; const vllmProvider = findVllmProvider(providersRecord); return hasLegacyVllmQwenThinkingProviderParams(vllmProvider) || hasLegacyVllmQwenThinkingModelParams(vllmProvider); } function preserveMigratedVllmQwenReasoning(model) { if (model.reasoning === void 0) model.reasoning = true; } function removeLegacyVllmQwenThinkingParams(params) { for (const key of LEGACY_VLLM_QWEN_THINKING_FORMAT_KEYS) delete params[key]; } function applyLegacyVllmQwenThinkingFormat(params) { if (!params.legacyFormat.compat) { removeLegacyVllmQwenThinkingParams(params.legacyParams); params.changes.push(`Removed ${params.sourcePath}.${params.legacyFormat.key} (unrecognized value ${JSON.stringify(params.legacyFormat.value)}; configure models.providers.vllm.models[].compat.thinkingFormat if needed).`); return true; } preserveMigratedVllmQwenReasoning(params.target.model); const compat = ensureRecord(params.target.model, "compat"); const currentThinkingFormat = compat.thinkingFormat; if (typeof currentThinkingFormat === "string" && isModelThinkingFormat(currentThinkingFormat)) { removeLegacyVllmQwenThinkingParams(params.legacyParams); params.changes.push(`Removed ${params.sourcePath}.${params.legacyFormat.key}; models.providers.vllm.models[${params.target.index}].compat.thinkingFormat is already ${JSON.stringify(currentThinkingFormat)}.`); return true; } compat.thinkingFormat = params.legacyFormat.compat; removeLegacyVllmQwenThinkingParams(params.legacyParams); params.changes.push(`Moved ${params.sourcePath}.${params.legacyFormat.key} to models.providers.vllm.models[${params.target.index}].compat.thinkingFormat (${JSON.stringify(params.legacyFormat.compat)}).`); return true; } function removeUntargetedLegacyVllmQwenThinkingFormat(params) { removeLegacyVllmQwenThinkingParams(params.legacyParams); params.changes.push(`Removed ${params.sourcePath}.${params.legacyFormat.key}; no concrete vLLM model row or agent model ref exists, so configure models.providers.vllm.models[].compat.thinkingFormat on each Qwen model that needs it.`); } const LEGACY_VLLM_QWEN_AGENT_THINKING_FORMAT_RULE = { path: [ "agents", "defaults", "models" ], message: "agents.defaults.models.<vllm-model>.params.qwenThinkingFormat is legacy; run \"openclaw doctor --fix\" to move it to models.providers.vllm.models[].compat.thinkingFormat.", match: (value) => hasLegacyVllmQwenThinkingFormat(value) }; const LEGACY_VLLM_QWEN_PROVIDER_THINKING_FORMAT_RULE = { path: [ "models", "providers", "vllm", "params" ], message: "models.providers.vllm.params.qwenThinkingFormat is legacy; run \"openclaw doctor --fix\" to move it to models.providers.vllm.models[].compat.thinkingFormat.", match: (value) => hasLegacyVllmQwenThinkingProviderParams({ params: value }) }; const LEGACY_VLLM_QWEN_PROVIDER_MODEL_THINKING_FORMAT_RULE = { path: [ "models", "providers", "vllm", "models" ], message: "models.providers.vllm.models[*].params.qwenThinkingFormat is legacy; run \"openclaw doctor --fix\" to move it to models.providers.vllm.models[].compat.thinkingFormat.", match: (value) => hasLegacyVllmQwenThinkingModelParams({ models: value }) }; const LEGACY_VLLM_QWEN_NORMALIZED_PROVIDER_THINKING_FORMAT_RULE = { path: ["models", "providers"], message: "models.providers.<vllm>.params.qwenThinkingFormat is legacy; run \"openclaw doctor --fix\" to move it to models.providers.<vllm>.models[].compat.thinkingFormat.", match: (value) => hasLegacyVllmQwenThinkingNormalizedProvider(value) }; const LEGACY_VLLM_QWEN_DEFAULT_PARAMS_THINKING_FORMAT_RULE = { path: [ "agents", "defaults", "params" ], message: "agents.defaults.params.qwenThinkingFormat is legacy; run \"openclaw doctor --fix\" to move it to models.providers.vllm.models[].compat.thinkingFormat.", match: (value) => hasLegacyVllmQwenThinkingParams(value) }; const LEGACY_VLLM_QWEN_AGENT_PARAMS_THINKING_FORMAT_RULE = { path: ["agents"], message: "agents.list[].params.qwenThinkingFormat is legacy; run \"openclaw doctor --fix\" to move it to models.providers.vllm.models[].compat.thinkingFormat.", match: (value) => hasLegacyVllmQwenThinkingAgentParams(value) }; const INVALID_THINKING_FORMAT_RULE = { path: ["models", "providers"], message: "models.providers.<id>.models[*].compat.thinkingFormat has an unrecognized value; run \"openclaw doctor --fix\" to remove it and restore the runtime default.", match: (value) => hasInvalidThinkingFormat(value) }; const STALE_CONTEXT_WINDOW_RULE = { path: ["models", "providers"], message: "models.providers.<id>.models[*].contextWindow has a stale catalog value; run \"openclaw doctor --fix\" to repair it.", match: (value) => hasStaleContextWindowValue(value) }; function normalizeString(value) { return typeof value === "string" ? value.trim().toLowerCase() : ""; } function preferredClaudeSeparator(provider) { return provider === "github-copilot" || provider === "copilot-proxy" ? "." : "-"; } function claudeTargetModelId(family, separator, provider) { const version = family === "opus" && provider !== "venice" && provider !== "vercel-ai-gateway" ? "4.7" : "4.6"; return `claude-${family}-${separator === "." ? version : version.replace(".", "-")}`; } function shouldUpgradeClaudeProvider(provider) { return !provider || provider === "anthropic" || provider === "github-copilot" || provider === "copilot-proxy" || provider === "venice" || provider === "vercel-ai-gateway"; } function upgradeRetiredGroqModelId(model) { switch (normalizeString(model)) { case "deepseek-r1-distill-llama-70b": return "llama-3.3-70b-versatile"; case "gemma2-9b-it": case "llama3-8b-8192": return "llama-3.1-8b-instant"; case "llama3-70b-8192": return "llama-3.3-70b-versatile"; case "meta-llama/llama-4-maverick-17b-128e-instruct": case "moonshotai/kimi-k2-instruct": case "moonshotai/kimi-k2-instruct-0905": return "openai/gpt-oss-120b"; case "mistral-saba-24b": case "qwen-qwq-32b": return "qwen/qwen3-32b"; default: return null; } } function upgradeRetiredXaiModelId(model) { switch (normalizeString(model)) { case "grok-code-fast": case "grok-code-fast-1": case "grok-code-fast-1-0825": return "grok-build-0.1"; case "grok-4-fast-reasoning": case "grok-4-1-fast-reasoning": return "grok-4.3"; default: return null; } } function upgradeRetiredOpenAiModelId(model, provider) { const normalized = normalizeString(model); const codexProvider = provider === "openai-codex"; if (codexProvider && normalized === "gpt-5.2") return "gpt-5.5"; if (normalized === "gpt-5.2-codex" || normalized === "gpt-5.1-codex" || normalized === "gpt-5-codex") return codexProvider ? "gpt-5.5" : "gpt-5.3-codex"; if (normalized === "gpt-5-pro" || normalized === "gpt-5.2-pro") return "gpt-5.5-pro"; if (normalized === "gpt-4.1-nano" || normalized === "gpt-5-nano") { if (codexProvider) return "gpt-5.4-mini"; return "gpt-5.4-nano"; } if (normalized === "gpt-4.1-mini" || normalized === "gpt-4o-mini" || normalized === "gpt-5.1-codex-mini" || normalized === "gpt-5-mini") return "gpt-5.4-mini"; if (normalized === "gpt-4" || normalized === "gpt-4-turbo" || normalized === "gpt-4.1" || normalized === "gpt-4o" || normalized === "gpt-4o-2024-05-13" || normalized === "gpt-4o-2024-08-06" || normalized === "gpt-4o-2024-11-20" || normalized === "gpt-5" || normalized === "gpt-5-chat-latest" || normalized === "gpt-5.1" || normalized === "gpt-5.1-chat-latest" || normalized === "gpt-5.1-codex-max" || normalized === "gpt-5.2" || normalized === "gpt-5.2-chat-latest") return "gpt-5.5"; return null; } function hasRetiredVersionPrefix(normalized, prefix) { if (normalized === prefix) return true; if (!normalized.startsWith(prefix)) return false; const next = normalized[prefix.length]; return next === "-" || next === "." || next === ":" || next === "@"; } function hasAnyRetiredVersionPrefix(normalized, prefixes) { return prefixes.some((prefix) => hasRetiredVersionPrefix(normalized, prefix)); } function upgradeOldClaudeToken(token, separator, provider) { const normalized = normalizeString(token); if (!normalized) return null; const opusTarget = claudeTargetModelId("opus", separator, provider); const sonnetTarget = claudeTargetModelId("sonnet", separator, provider); if (normalized.startsWith("claude-opus-4-7") || normalized.startsWith("claude-opus-4.7") || normalized.startsWith("claude-opus-4-6") || normalized.startsWith("claude-opus-4.6") || normalized.startsWith("claude-sonnet-4-6") || normalized.startsWith("claude-sonnet-4.6")) return null; if (normalized.startsWith("claude-haiku-4-5") || normalized.startsWith("claude-haiku-4.5")) return null; if (normalized === "claude-opus-4" || hasAnyRetiredVersionPrefix(normalized, [ "claude-opus-4-5", "claude-opus-4.5", "claude-opus-4-1", "claude-opus-4.1", "claude-opus-4-0", "claude-opus-4.0" ]) || /^claude-opus-4-20\d{6}/.test(normalized)) return opusTarget; if (normalized === "claude-sonnet-4" || hasAnyRetiredVersionPrefix(normalized, [ "claude-sonnet-4-5", "claude-sonnet-4.5", "claude-sonnet-4-1", "claude-sonnet-4.1", "claude-sonnet-4-0", "claude-sonnet-4.0" ]) || /^claude-sonnet-4-20\d{6}/.test(normalized)) return sonnetTarget; if (normalized.startsWith("claude-3") && normalized.includes("opus")) return opusTarget; if (normalized.startsWith("claude-3") && (normalized.includes("sonnet") || normalized.includes("haiku"))) return sonnetTarget; if (normalized.startsWith("anthropic.claude-opus-")) { if (provider === "amazon-bedrock" || provider === "amazon-bedrock-mantle") return null; if (normalized.startsWith("anthropic.claude-opus-4-7") || normalized.startsWith("anthropic.claude-opus-4-6")) return null; return `anthropic.${claudeTargetModelId("opus", "-", provider)}`; } if (normalized.startsWith("anthropic.claude-sonnet-") || normalized.startsWith("anthropic.claude-haiku-")) { if (provider === "amazon-bedrock" || provider === "amazon-bedrock-mantle") return null; if (normalized.startsWith("anthropic.claude-sonnet-4-6")) return null; return `anthropic.${claudeTargetModelId("sonnet", "-", provider)}`; } if (normalized === "opus-4.5" || normalized === "opus-4.1" || normalized === "opus-4" || normalized === "opus-3") return opusTarget; if (normalized === "sonnet-4.5" || normalized === "sonnet-4.1" || normalized === "sonnet-4.0" || normalized === "sonnet-4" || normalized === "sonnet-3.7" || normalized === "sonnet-3.5" || normalized === "sonnet-3" || normalized === "haiku-3.5" || normalized === "haiku-3") return sonnetTarget; return null; } function upgradeOldClaudeModelPart(model, provider) { const separator = preferredClaudeSeparator(provider); const slashParts = model.split("/"); const lastPart = slashParts.at(-1); if (lastPart) { const upgraded = upgradeOldClaudeToken(lastPart, separator, provider); if (upgraded) return [...slashParts.slice(0, -1), upgraded].join("/"); } return upgradeOldClaudeToken(model, separator, provider); } function upgradeRetiredModelRef(value) { const trimmed = value.trim(); if (!trimmed) return null; const split = splitTrailingAuthProfile(trimmed); const modelRef = split.model; const slash = modelRef.indexOf("/"); const provider = slash > 0 ? modelRef.slice(0, slash).trim() : void 0; const model = slash > 0 ? modelRef.slice(slash + 1).trim() : modelRef; const normalizedProvider = normalizeString(provider); const normalizedModel = normalizeString(model); const retiredOwnerModel = normalizedProvider === "groq" ? upgradeRetiredGroqModelId(model) : normalizedProvider === "xai" ? upgradeRetiredXaiModelId(model) : normalizedProvider === "openai" || normalizedProvider === "openai-codex" || normalizedProvider === "github-copilot" ? upgradeRetiredOpenAiModelId(model, normalizedProvider) : void 0; if (retiredOwnerModel) return `${provider}/${retiredOwnerModel}${split.profile ? `@${split.profile}` : ""}`; if ((normalizedProvider === "github-copilot" || normalizedProvider === "copilot-proxy") && normalizedModel === "grok-code-fast-1") return `${provider}/gpt-5.4-mini${split.profile ? `@${split.profile}` : ""}`; if (!shouldUpgradeClaudeProvider(normalizedProvider || void 0)) return null; const upgradedModel = upgradeOldClaudeModelPart(model, normalizedProvider || void 0); if (!upgradedModel || upgradedModel === model) return null; return `${provider ? `${provider}/${upgradedModel}` : upgradedModel}${split.profile ? `@${split.profile}` : ""}`; } const MODEL_REF_STRING_KEYS = new Set([ "model", "primary", "summaryModel", "imageModel", "imageGenerationModel", "musicGenerationModel", "pdfModel", "videoGenerationModel" ]); const MODEL_REF_ARRAY_KEYS = new Set([ "fallback", "fallbacks", "allowedModels", "modelFallbacks", "imageModelFallbacks" ]); const MODEL_REF_MAP_KEYS = new Set(["models"]); function pathKey(path) { return path.slice(path.lastIndexOf(".") + 1); } function isChannelModelOverridePath(path) { return path.includes(".modelByChannel."); } function scanKnownModelRefs(value, key, path = "") { if (typeof value === "string") return Boolean(key && (MODEL_REF_STRING_KEYS.has(key) || isChannelModelOverridePath(path)) && upgradeRetiredModelRef(value)); if (Array.isArray(value)) return value.some((entry, index) => typeof entry === "string" && key && MODEL_REF_ARRAY_KEYS.has(key) ? Boolean(upgradeRetiredModelRef(entry)) : scanKnownModelRefs(entry, void 0, `${path}.${index}`)); const record = getRecord(value); if (!record) return false; if (key && MODEL_REF_MAP_KEYS.has(key)) return Object.keys(record).some((entryKey) => Boolean(upgradeRetiredModelRef(entryKey))); return Object.entries(record).some(([childKey, child]) => scanKnownModelRefs(child, childKey, `${path}.${childKey}`)); } function rewriteModelRefString(value, path, changes) { const upgraded = upgradeRetiredModelRef(value); if (!upgraded) return value; changes.push(`Upgraded ${path} from ${JSON.stringify(value)} to ${JSON.stringify(upgraded)}.`); return upgraded; } function rewriteModelRefMapKeys(record, path, changes) { let changed = false; const next = {}; for (const [key, child] of Object.entries(record)) { const upgradedKey = upgradeRetiredModelRef(key); const nextKey = upgradedKey ?? key; if (upgradedKey) { changes.push(`Upgraded ${path} key from ${JSON.stringify(key)} to ${JSON.stringify(upgradedKey)}.`); changed = true; } if (nextKey in next && upgradedKey) continue; next[nextKey] = child; } return { value: changed ? next : record, changed }; } function rewriteKnownModelRefs(value, path, changes) { const key = pathKey(path); if (typeof value === "string") { if (!MODEL_REF_STRING_KEYS.has(key) && !isChannelModelOverridePath(path)) return { value, changed: false }; const next = rewriteModelRefString(value, path, changes); return { value: next, changed: next !== value }; } if (Array.isArray(value)) { let changed = false; const next = value.map((entry, index) => { if (typeof entry === "string" && MODEL_REF_ARRAY_KEYS.has(key)) { const rewritten = rewriteModelRefString(entry, `${path}.${index}`, changes); changed ||= rewritten !== entry; return rewritten; } const rewritten = rewriteKnownModelRefs(entry, `${path}.${index}`, changes); changed ||= rewritten.changed; return rewritten.value; }); return { value: changed ? next : value, changed }; } const record = getRecord(value); if (!record) return { value, changed: false }; let working = record; let changed = false; if (MODEL_REF_MAP_KEYS.has(key)) { const rewrittenKeys = rewriteModelRefMapKeys(record, path, changes); working = rewrittenKeys.value; changed ||= rewrittenKeys.changed; } const next = {}; for (const [childKey, child] of Object.entries(working)) { const rewritten = rewriteKnownModelRefs(child, `${path}.${childKey}`, changes); changed ||= rewritten.changed; next[childKey] = rewritten.value; } return { value: changed ? next : value, changed }; } const RETIRED_MODEL_REF_MESSAGE = "Configured retired model refs are no longer in the bundled catalogs; run \"openclaw doctor --fix\" to upgrade them."; const LEGACY_OPENAI_CODEX_PROVIDER_ID = "openai-codex"; const LEGACY_OPENAI_CODEX_RESPONSES_API = "openai-codex-responses"; const OPENAI_PROVIDER_ID = "openai"; const OPENAI_CHATGPT_RESPONSES_API = "openai-chatgpt-responses"; const MODEL_UNSCOPED_PROVIDER_DEFAULT_KEYS = [ "apiKey", "auth", "request", "timeoutSeconds", "region", "injectNumCtxForOpenAICompat", "localService", "headers", "authHeader" ]; const CANONICAL_PROVIDER_MODEL_LEAK_KEYS = [ "apiKey", "auth", "contextWindow", "contextTokens", "maxTokens", "timeoutSeconds", "region", "injectNumCtxForOpenAICompat", "params", "agentRuntime", "localService", "headers", "authHeader", "request" ]; function hasCanonicalOpenAIProvider(providers) { return Object.keys(providers).some((providerId) => normalizeProviderId(providerId) === OPENAI_PROVIDER_ID); } function normalizeLegacyOpenAIResponsesApi(providerId, provider, changes) { let changed = false; const next = { ...provider }; if (next.api === LEGACY_OPENAI_CODEX_RESPONSES_API) { next.api = OPENAI_CHATGPT_RESPONSES_API; changes.push(`Moved models.providers.${providerId}.api "${LEGACY_OPENAI_CODEX_RESPONSES_API}" → "${OPENAI_CHATGPT_RESPONSES_API}".`); changed = true; } if (Array.isArray(provider.models)) { let modelsChanged = false; const nextModels = provider.models.map((model, index) => { const modelRecord = getRecord(model); if (!modelRecord || modelRecord.api !== LEGACY_OPENAI_CODEX_RESPONSES_API) return model; modelsChanged = true; changes.push(`Moved models.providers.${providerId}.models[${index}].api "${LEGACY_OPENAI_CODEX_RESPONSES_API}" → "${OPENAI_CHATGPT_RESPONSES_API}".`); return { ...modelRecord, api: OPENAI_CHATGPT_RESPONSES_API }; }); if (modelsChanged) { next.models = nextModels; changed = true; } } return { value: next, changed }; } function hasOwnDefinedProperty(record, key) { return Object.hasOwn(record, key) && record[key] !== void 0; } function collectModelMergeBlockers(params) { const blockers = []; for (const key of MODEL_UNSCOPED_PROVIDER_DEFAULT_KEYS) if (hasOwnDefinedProperty(params.legacy, key)) blockers.push(`models.providers.${LEGACY_OPENAI_CODEX_PROVIDER_ID}.${key}`); for (const key of CANONICAL_PROVIDER_MODEL_LEAK_KEYS) if (hasOwnDefinedProperty(params.canonical, key)) blockers.push(`models.providers.${OPENAI_PROVIDER_ID}.${key}`); return blockers; } function getCanonicalOpenAIProviderEntry(providers) { const key = Object.keys(providers).find((k) => normalizeProviderId(k) === OPENAI_PROVIDER_ID); const value = key ? getRecord(providers[key]) : void 0; return key && value ? { key, value } : void 0; } function getMergeableLegacyOpenAIModels(params) { const legacyModels = Array.isArray(params.legacy.models) ? params.legacy.models : []; const canonicalModels = Array.isArray(params.canonical.models) ? params.canonical.models : []; const canonicalModelIds = /* @__PURE__ */ new Set(); const canonicalModelNames = /* @__PURE__ */ new Set(); for (const m of canonicalModels) { const mr = getRecord(m); if (typeof mr?.id === "string" && mr.id) canonicalModelIds.add(mr.id); if (typeof mr?.name === "string" && mr.name) canonicalModelNames.add(mr.name); } return legacyModels.filter((m) => { const mr = getRecord(m); if (!mr) return false; const id = typeof mr.id === "string" ? mr.id : void 0; const name = typeof mr.name === "string" ? mr.name : void 0; if (!id && !name) return false; return id ? !canonicalModelIds.has(id) : name ? !canonicalModelNames.has(name) : false; }); } function hasAutoFixableLegacyOpenAICodexProvider(providersValue) { const providers = getRecord(providersValue); if (!providers) return false; const canonicalEntry = getCanonicalOpenAIProviderEntry(providers); for (const [providerId, providerValue] of Object.entries(providers)) { const provider = getRecord(providerValue); if (!provider || normalizeProviderId(providerId) !== LEGACY_OPENAI_CODEX_PROVIDER_ID) continue; const normalized = normalizeLegacyOpenAIResponsesApi(providerId, provider, []); if (normalized.changed || !canonicalEntry) return true; if (getMergeableLegacyOpenAIModels({ canonical: canonicalEntry.value, legacy: normalized.value }).length === 0) return true; if (collectModelMergeBlockers({ canonical: canonicalEntry.value, legacy: normalized.value }).length === 0) return true; } return false; } function collectBlockedLegacyOpenAICodexProviderWarnings(raw) { const providers = getRecord(getRecord(getRecord(raw)?.models)?.providers); const canonicalEntry = providers ? getCanonicalOpenAIProviderEntry(providers) : void 0; if (!providers || !canonicalEntry) return []; const warnings = []; for (const [providerId, providerValue] of Object.entries(providers)) { const provider = getRecord(providerValue); if (!provider || normalizeProviderId(providerId) !== LEGACY_OPENAI_CODEX_PROVIDER_ID) continue; const normalized = normalizeLegacyOpenAIResponsesApi(providerId, provider, []); if (normalized.changed) continue; if (getMergeableLegacyOpenAIModels({ canonical: canonicalEntry.value, legacy: normalized.value }).length === 0) continue; const mergeBlockers = collectModelMergeBlockers({ canonical: canonicalEntry.value, legacy: normalized.value }); if (mergeBlockers.length === 0) continue; warnings.push(`models.providers.${providerId} cannot be merged automatically into models.providers.${canonicalEntry.key} because provider-level defaults cannot be represented safely on merged models: ${mergeBlockers.join(", ")}. Move the affected model/provider defaults manually before removing models.providers.${providerId}.`); } return warnings; } function buildMergedLegacyOpenAIModel(model, legacyProvider) { const modelRecord = getRecord(model); if (!modelRecord) return model; const patch = {}; const legacyBaseUrl = typeof legacyProvider.baseUrl === "string" ? legacyProvider.baseUrl : void 0; const legacyApi = typeof legacyProvider.api === "string" ? legacyProvider.api : void 0; const legacyParams = getRecord(legacyProvider.params); const legacyAgentRuntime = getRecord(legacyProvider.agentRuntime); if (legacyBaseUrl && !modelRecord.baseUrl) patch.baseUrl = legacyBaseUrl; if (legacyApi && !modelRecord.api) patch.api = legacyApi; for (const key of [ "contextWindow", "contextTokens", "maxTokens" ]) if (typeof legacyProvider[key] === "number" && modelRecord[key] === void 0) patch[key] = legacyProvider[key]; if (legacyParams) { const modelParams = getRecord(modelRecord.params); if (modelParams) patch.params = { ...legacyParams, ...modelParams }; else if (modelRecord.params === void 0) patch.params = legacyParams; } if (legacyAgentRuntime && modelRecord.agentRuntime === void 0) patch.agentRuntime = legacyAgentRuntime; if (modelRecord.metadataSource === void 0 && isLegacyModelsAddCodexMetadataModel({ provider: LEGACY_OPENAI_CODEX_PROVIDER_ID, model: modelRecord })) patch.metadataSource = "models-add"; return Object.keys(patch).length > 0 ? Object.assign({}, modelRecord, patch) : model; } function migrateLegacyOpenAICodexProvider(raw, changes) { const models = getRecord(raw.models); const providers = getRecord(models?.providers); if (!models || !providers) return; let providersChanged = false; for (const [providerId, providerValue] of Object.entries({ ...providers })) { const provider = getRecord(providers[providerId]) ?? getRecord(providerValue); if (!provider) continue; const normalized = normalizeLegacyOpenAIResponsesApi(providerId, provider, changes); if (normalizeProviderId(providerId) !== LEGACY_OPENAI_CODEX_PROVIDER_ID) { if (normalized.changed) { providers[providerId] = normalized.value; providersChanged = true; } continue; } if (!hasCanonicalOpenAIProvider(providers)) { providers[OPENAI_PROVIDER_ID] = normalized.value; changes.push(`Moved models.providers.${LEGACY_OPENAI_CODEX_PROVIDER_ID} → models.providers.${OPENAI_PROVIDER_ID}.`); } else { const canonicalEntry = getCanonicalOpenAIProviderEntry(providers); const canonicalKey = canonicalEntry?.key ?? OPENAI_PROVIDER_ID; const canonical = canonicalEntry?.value ?? {}; const canonicalModels = Array.isArray(canonical.models) ? canonical.models : []; const modelsToMerge = getMergeableLegacyOpenAIModels({ canonical, legacy: normalized.value }); const mergeBlockers = modelsToMerge.length > 0 ? collectModelMergeBlockers({ canonical, legacy: normalized.value }) : []; if (mergeBlockers.length > 0) { if (normalized.changed) { providers[providerId] = normalized.value; providersChanged = true; changes.push(`Skipped merging models.providers.${LEGACY_OPENAI_CODEX_PROVIDER_ID} into models.providers.${OPENAI_PROVIDER_ID} because provider-level defaults cannot be represented safely on merged models: ${mergeBlockers.join(", ")}.`); } continue; } const stamped = modelsToMerge.map((m) => buildMergedLegacyOpenAIModel(m, normalized.value)); if (stamped.length > 0) { providers[canonicalKey] = { ...canonical, models: [...canonicalModels, ...stamped] }; const mergedIds = stamped.map((m) => { const mr = getRecord(m); return typeof mr?.id === "string" && mr.id ? mr.id : typeof mr?.name === "string" && mr.name ? mr.name : "unknown"; }).join(", "); changes.push(`Merged ${stamped.length} model(s) from models.providers.${LEGACY_OPENAI_CODEX_PROVIDER_ID} into models.providers.${OPENAI_PROVIDER_ID}: ${mergedIds}.`); } else changes.push(`Removed models.providers.${LEGACY_OPENAI_CODEX_PROVIDER_ID} because models.providers.${OPENAI_PROVIDER_ID} already exists.`); } delete providers[providerId]; providersChanged = true; } if (providersChanged) models.providers = providers; } const RETIRED_MODEL_REF_RULES = [ "agents", "plugins", "messages", "tools", "hooks", "channels", "models" ].map((section) => ({ path: [section], message: RETIRED_MODEL_REF_MESSAGE, match: (value) => scanKnownModelRefs(value) })); /** Legacy config migration specs for model/provider runtime config compatibility. */ const LEGACY_CONFIG_MIGRATIONS_RUNTIME_MODELS = [ defineLegacyConfigMigration({ id: "models.providers.openai-codex->models.providers.openai", describe: "Move legacy OpenAI Codex provider config to canonical OpenAI provider config", legacyRules: [{ path: ["models", "providers"], message: "models.providers.openai-codex is legacy; run \"openclaw doctor --fix\" to move it to models.providers.openai.", match: (value) => hasAutoFixableLegacyOpenAICodexProvider(value) }, { path: ["models", "providers"], message: "openai-codex-responses is legacy; run \"openclaw doctor --fix\" to use openai-chatgpt-responses.", match: (value) => { const providers = getRecord(value); return providers ? Object.values(providers).some((providerValue) => { const provider = getRecord(providerValue); return provider?.api === LEGACY_OPENAI_CODEX_RESPONSES_API || Array.isArray(provider?.models) && provider.models.some((model) => getRecord(model)?.api === LEGACY_OPENAI_CODEX_RESPONSES_API); }) : false; } }], apply: migrateLegacyOpenAICodexProvider }), defineLegacyConfigMigration({ id: "models.retired-model-refs", describe: "Upgrade retired model refs to current catalog entries", legacyRules: RETIRED_MODEL_REF_RULES, apply: (raw, changes) => { const rewritten = rewriteKnownModelRefs(raw, "config", changes); if (!rewritten.changed || !getRecord(rewritten.value)) return; for (const key of Object.keys(raw)) delete raw[key]; Object.assign(raw, rewritten.value); } }), defineLegacyConfigMigration({ id: "agents.defaults.models.vllm.params.qwenThinkingFormat->models.providers.vllm.models.compat.thinkingFormat", describe: "Move legacy vLLM Qwen thinking params to model compat metadata", legacyRules: [ LEGACY_VLLM_QWEN_AGENT_THINKING_FORMAT_RULE, LEGACY_VLLM_QWEN_PROVIDER_THINKING_FORMAT_RULE, LEGACY_VLLM_QWEN_PROVIDER_MODEL_THINKING_FORMAT_RULE, LEGACY_VLLM_QWEN_NORMALIZED_PROVIDER_THINKING_FORMAT_RULE, LEGACY_VLLM_QWEN_DEFAULT_PARAMS_THINKING_FORMAT_RULE, LEGACY_VLLM_QWEN_AGENT_PARAMS_THINKING_FORMAT_RULE ], apply: (raw, changes) => { const agentsDefaults = getRecord(getRecord(raw.agents)?.defaults); const defaultModels = getRecord(agentsDefaults?.models); if (defaultModels) for (const [key, entry] of Object.entries(defaultModels)) { const modelId = parseVllmAgentModelKey(key); const entryRecord = getRecord(entry); const params = getRecord(entryRecord?.params); if (!modelId || !entryRecord || !params) continue; const legacyFormat = getLegacyVllmQwenThinkingFormat(params); if (!legacyFormat) continue; const target = legacyFormat.compat ? findOrCreateVllmModelEntry(raw, modelId) : void 0; if (legacyFormat.compat && !target) continue; applyLegacyVllmQwenThinkingFormat({ sourcePath: `agents.defaults.models.${JSON.stringify(key)}.params`, legacyParams: params, target: target ?? { model: {}, index: -1 }, legacyFormat, changes }); if (Object.keys(params).length === 0) delete entryRecord.params; } const vllmProvider = findVllmProvider(getRecord(getRecord(raw.models)?.providers)); const vllmModels = vllmProvider?.models; if (Array.isArray(vllmModels)) for (const [index, model] of vllmModels.entries()) { const modelRecord = getRecord(model); const params = getRecord(modelRecord?.params); if (!modelRecord || !params) continue; const legacyFormat = getLegacyVllmQwenThinkingFormat(params); if (!legacyFormat) continue; applyLegacyVllmQwenThinkingFormat({ sourcePath: `models.providers.vllm.models[${index}].params`, legacyParams: params, target: { model: modelRecord, index }, legacyFormat, changes }); if (Object.keys(params).length === 0) delete modelRecord.params; } const providerParams = getRecord(vllmProvider?.params); if (providerParams) { const providerLegacyFormat = getLegacyVllmQwenThinkingFormat(providerParams); if (providerLegacyFormat) { const providerModelIds = [ ...collectVllmModelIdsFromSelection(agentsDefaults?.model), ...collectVllmModelIdsFromAgentModelMap(defaultModels), ...collectVllmModelIdsFromAgentList(getRecord(raw.agents)?.list) ]; const targets = combineVllmModelTargets(listExistingVllmModelTargets(raw), createVllmModelTargets(raw, providerModelIds)); if (targets.length === 0) removeUntargetedLegacyVllmQwenThinkingFormat({ sourcePath: "models.providers.vllm.params", legacyParams: providerParams, legacyFormat: providerLegacyFormat, changes }); else for (const target of targets) applyLegacyVllmQwenThinkingFormat({ sourcePath: "models.providers.vllm.params", legacyParams: providerParams, target, legacyFormat: providerLegacyFormat, changes }); if (Object.keys(providerParams).length === 0) delete vllmProvider?.params; } } const defaultParams = getRecord(agentsDefaults?.params); if (defaultParams) { const defaultLegacyFormat = getLegacyVllmQwenThinkingFormat(defaultParams); if (defaultLegacyFormat) { const defaultModelIds = [...collectVllmModelIdsFromSelection(agentsDefaults?.model), ...collectVllmModelIdsFromAgentModelMap(defaultModels)]; const targets = defaultModelIds.length > 0 ? createVllmModelTargets(raw, defaultModelIds) : listExistingVllmModelTargets(raw); if (targets.length === 0) removeUntargetedLegacyVllmQwenThinkingFormat({ sourcePath: "agents.defaults.params", legacyParams: defaultParams, legacyFormat: defaultLegacyFormat, changes }); else for (const target of targets) applyLegacyVllmQwenThinkingFormat({ sourcePath: "agents.defaults.params", legacyParams: defaultParams, target, legacyFormat: defaultLegacyFormat, changes }); if (Object.keys(defaultParams).length === 0) delete agentsDefaults?.params; } } const agentList = getRecord(raw.agents)?.list; if (!Array.isArray(agentList)) return; for (const [index, agent] of agentList.entries()) { const agentRecord = getRecord(agent); const agentParams = getRecord(agentRecord?.params); const agentLegacyFormat = agentParams ? getLegacyVllmQwenThinkingFormat(agentParams) : void 0; if (!agentRecord || !agentParams || !agentLegacyFormat) continue; const explicitAgentModelIds = [...collectVllmModelIdsFromSelection(agentRecord.model), ...collectVllmModelIdsFromAgentModelMap(agentRecord.models)]; const inheritedDefaultModelIds = [...collectVllmModelIdsFromSelection(agentsDefaults?.model), ...collectVllmModelIdsFromAgentModelMap(defaultModels)]; const agentModelIds = explicitAgentModelIds.length > 0 ? explicitAgentModelIds : inheritedDefaultModelIds; const targets = agentModelIds.length > 0 ? createVllmModelTargets(raw, agentModelIds) : listExistingVllmModelTargets(raw); if (targets.length === 0) removeUntargetedLegacyVllmQwenThinkingFormat({ sourcePath: `agents.list[${index}].params`, legacyParams: agentParams, legacyFormat: agentLegacyFormat, changes }); else for (const target of targets) applyLegacyVllmQwenThinkingFormat({ sourcePath: `agents.list[${index}].params`, legacyParams: agentParams, target, legacyFormat: agentLegacyFormat, changes }); if (Object.keys(agentParams).length === 0) delete agentRecord.params; } } }), defineLegacyConfigMigration({ id: "models.providers.*.models.*.compat.thinkingFormat-invalid", describe: "Remove unrecognized compat.thinkingFormat values from provider model entries", legacyRules: [INVALID_THINKING_FORMAT_RULE], apply: (raw, changes) => { const providers = getRecord(getRecord(raw.models)?.providers); if (!providers) return; for (const [providerId, provider] of Object.entries(providers)) { const models = getRecord(provider)?.models; if (!Array.isArray(models)) continue; for (const [index, model] of models.entries()) { const compat = getRecord(getRecord(model)?.compat); if (!compat) continue; const thinkingFormat = compat.thinkingFormat; if (typeof thinkingFormat !== "string" || isModelThinkingFormat(thinkingFormat)) continue; delete compat.thinkingFormat; changes.push(`Removed models.providers.${providerId}.models.${index}.compat.thinkingFormat (unrecognized value ${JSON.stringify(thinkingFormat)}; runtime default applies).`); } } } }), defineLegacyConfigMigration({ id: "models.providers.*.models.*.contextWindow-stale", describe: "Repair stale contextWindow values to match catalog defaults", legacyRules: [STALE_CONTEXT_WINDOW_RULE], apply: (raw, changes) => { const providers = getRecord(getRecord(raw.models)?.providers); if (!providers) return; for (const [providerId, provider] of Object.entries(providers)) { const models = getRecord(provider)?.models; if (!Array.isArray(models)) continue; for (const [index, model] of models.entries()) { if (!getRecord(model)) continue; const modelId = typeof model.id === "string" ? model.id : void 0; if (!modelId) continue; const contextWindow = model.contextWindow; if (typeof contextWindow !== "number" || !Number.isFinite(contextWindow)) continue; const fix = resolveStaleContextWindowFix({ providerId, modelId, contextWindow }); if (!fix) continue; model.contextWindow = fix.correct; changes.push(`Repaired models.providers.${providerId}.models[${index}].${modelId}.contextWindow (${contextWindow}${fix.correct} to match catalog default).`); } } } }) ]; //#endregion export { ensureRecord as a, mergeMissing as c, defineLegacyConfigMigration as i, collectBlockedLegacyOpenAICodexProviderWarnings as n, getRecord as o, isLegacyModelsAddCodexMetadataModel as r, mapLegacyAudioTranscription as s, LEGACY_CONFIG_MIGRATIONS_RUNTIME_MODELS as t };