openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
226 lines (225 loc) • 9.13 kB
JavaScript
import { r as asNullableRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as normalizeProviderId } from "./provider-id-D1VImdKk.mjs";
import { r as isModelThinkingFormat } from "./model-config-vocabulary-Cb5jWCSz.mjs";
import "./types.models-BqvzicE8.mjs";
import { n as modelTransportRoutesMatch, o as resolveUniqueCatalogModelRoute } from "./model-compat-catalog-Dfj3P1T4.mjs";
import { n as planManifestModelCatalogRows } from "./manifest-planner-C-nOh69Z.mjs";
import { t as listOpenClawPluginManifestMetadata } from "./manifest-metadata-scan-CKbivtze.mjs";
import { a as resolveProviderModelRoutes } from "./provider-model-routes-C609Pdvh.mjs";
import { isDeepStrictEqual } from "node:util";
//#region src/commands/doctor/shared/legacy-config-migrations.runtime.models.catalog.ts
const STALE_CONTEXT_WINDOW_FIXES = {
"deepseek/deepseek-v4-flash": {
stale: 2e5,
correct: 1e6
},
"xai/grok-4.20-0309-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-0309-non-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-beta-latest-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-beta-latest-non-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-experimental-beta-0304-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-experimental-beta-0304-non-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-reasoning": {
stale: 2e6,
correct: 1e6
},
"xai/grok-4.20-non-reasoning": {
stale: 2e6,
correct: 1e6
}
};
const DEAD_MODEL_COMPAT_KEYS = ["nativeWebSearchTool", "requiresMistralToolIds"];
function* providerModelEntries(providers) {
for (const [providerId, value] of Object.entries(asNullableRecord(providers) ?? {})) {
const provider = asNullableRecord(value);
if (!provider || !Array.isArray(provider.models)) continue;
for (const [modelIndex, modelValue] of provider.models.entries()) {
const model = asNullableRecord(modelValue);
if (model) yield {
providerId,
provider,
modelIndex,
model
};
}
}
}
function normalizedCatalogModelKey(provider, modelId) {
const normalizedProvider = normalizeProviderId(provider);
const normalizedId = modelId.trim().toLowerCase();
const providerPrefix = `${normalizedProvider}/`;
return `${normalizedProvider}::${normalizedId.startsWith(providerPrefix) ? normalizedId.slice(providerPrefix.length) : normalizedId}`;
}
const modelCompatCatalogRowsByProvider = /* @__PURE__ */ new Map();
let modelCompatCatalogPlugins;
function getModelCompatCatalogPlugins() {
modelCompatCatalogPlugins ??= listOpenClawPluginManifestMetadata().flatMap(({ manifest }) => {
const id = typeof manifest.id === "string" ? manifest.id.trim() : "";
const modelCatalog = asNullableRecord(manifest.modelCatalog);
if (!id || !modelCatalog) return [];
return [{
id,
providers: Array.isArray(manifest.providers) ? manifest.providers.filter((value) => typeof value === "string") : [],
modelCatalog
}];
});
return modelCompatCatalogPlugins;
}
function buildConfiguredProviderCatalogRows(providers) {
const rows = /* @__PURE__ */ new Map();
for (const providerId of Object.keys(providers)) {
const normalizedProviderId = normalizeProviderId(providerId);
let providerRows = modelCompatCatalogRowsByProvider.get(normalizedProviderId);
if (!providerRows) {
providerRows = planManifestModelCatalogRows({
registry: { plugins: getModelCompatCatalogPlugins() },
providerFilter: normalizedProviderId
}).rows;
modelCompatCatalogRowsByProvider.set(normalizedProviderId, providerRows);
}
for (const row of providerRows) {
const key = normalizedCatalogModelKey(row.provider, row.id);
const variants = rows.get(key) ?? [];
variants.push(row);
rows.set(key, variants);
}
}
return rows;
}
/** Resolves one catalog identity and whether its configured route remains provider-owned. */
function resolveConfiguredModelCatalogOwnership(params) {
const modelId = typeof params.model.id === "string" ? params.model.id : "";
if (!modelId) return;
const rows = buildConfiguredProviderCatalogRows({ [params.providerId]: params.provider }).get(normalizedCatalogModelKey(params.providerId, modelId));
const catalogRow = rows?.length === 1 ? rows[0] : void 0;
if (!catalogRow) return;
const configuredRoute = {
api: params.model.api ?? params.provider.api,
baseUrl: params.model.baseUrl ?? params.provider.baseUrl
};
const exactCatalogRoute = modelTransportRoutesMatch(catalogRow, configuredRoute);
const providerRoutes = resolveProviderModelRoutes({
provider: params.providerId,
modelId,
env: {}
});
const providerOwnedRoute = providerRoutes?.kind === "routes" && providerRoutes.routes.some((route) => modelTransportRoutesMatch(route, configuredRoute));
return {
catalogRow,
ownsRoute: exactCatalogRoute || providerOwnedRoute
};
}
function* inspectModelCompatOverrides(providersValue) {
const providers = asNullableRecord(providersValue);
if (!providers) return;
const entries = [...providerModelEntries(providers)];
if (!entries.some(({ model }) => asNullableRecord(model.compat))) return;
const catalogRows = buildConfiguredProviderCatalogRows(providers);
for (const { providerId, provider, modelIndex, model } of entries) {
const compat = asNullableRecord(model.compat);
const modelId = typeof model.id === "string" ? model.id : "";
if (!compat || !modelId) continue;
const catalogRow = resolveUniqueCatalogModelRoute(catalogRows.get(normalizedCatalogModelKey(providerId, modelId)), {
api: model.api ?? provider.api,
baseUrl: model.baseUrl ?? provider.baseUrl
});
const dead = DEAD_MODEL_COMPAT_KEYS.filter((key) => Object.hasOwn(compat, key));
const matching = [];
const divergent = [];
if (catalogRow) {
const catalogCompat = catalogRow.compat ?? {};
for (const [key, value] of Object.entries(compat)) {
if (DEAD_MODEL_COMPAT_KEYS.includes(key)) continue;
(isDeepStrictEqual(value, catalogCompat[key]) ? matching : divergent).push(key);
}
}
yield {
compat,
model,
modelIndex,
providerId,
dead,
matching,
divergent
};
}
}
function hasModelCompatOverrides(providers, kind) {
for (const entry of inspectModelCompatOverrides(providers)) if (entry[kind].length > 0) return true;
return false;
}
const MODEL_COMPAT_CATALOG_RULES = [
{
path: ["models", "providers"],
message: "nativeWebSearchTool and requiresMistralToolIds are unused and retired; run \"openclaw doctor --fix\" to remove them.",
match: (value) => hasModelCompatOverrides(value, "dead")
},
{
path: ["models", "providers"],
message: "Catalog-known model compat values are provider-owned; run \"openclaw doctor --fix\" to remove matching config overrides.",
match: (value) => hasModelCompatOverrides(value, "matching")
},
{
path: ["models", "providers"],
message: "Catalog-known model compat differs from the provider catalog and was preserved for review. Use a distinct custom route when the endpoint really has different capabilities.",
match: (value) => hasModelCompatOverrides(value, "divergent")
}
];
function migrateModelCompatCatalogOwnership(raw, changes) {
const providers = asNullableRecord(asNullableRecord(raw.models)?.providers);
for (const { compat, model, modelIndex, providerId, dead, matching } of inspectModelCompatOverrides(providers)) {
const removed = [...dead, ...matching];
if (removed.length === 0) continue;
for (const key of removed) delete compat[key];
if (Object.keys(compat).length === 0) delete model.compat;
changes.push(`Removed models.providers.${providerId}.models.${modelIndex}.compat catalog/dead overrides: ${removed.toSorted().join(", ")}.`);
}
}
function resolveStaleContextWindowFix(params) {
const providerId = params.providerId.trim().toLowerCase();
const modelId = params.modelId.trim().toLowerCase();
const providerPrefix = `${providerId}/`;
const scopedModelId = `${providerId}/${modelId.startsWith(providerPrefix) ? modelId.slice(providerPrefix.length) : modelId}`;
const fix = STALE_CONTEXT_WINDOW_FIXES[scopedModelId];
return fix && params.contextWindow === fix.stale ? fix : void 0;
}
function hasStaleContextWindowValue(providers) {
for (const { providerId, model } of providerModelEntries(providers)) {
const modelId = typeof model.id === "string" ? model.id : void 0;
const contextWindow = model.contextWindow;
if (!modelId || typeof contextWindow !== "number" || !Number.isFinite(contextWindow)) continue;
if (resolveStaleContextWindowFix({
providerId,
modelId,
contextWindow
})) return true;
}
return false;
}
function hasInvalidThinkingFormat(providers) {
for (const { model } of providerModelEntries(providers)) {
const thinkingFormat = asNullableRecord(model.compat)?.thinkingFormat;
if (typeof thinkingFormat === "string" && !isModelThinkingFormat(thinkingFormat)) return true;
}
return false;
}
//#endregion
export { providerModelEntries as a, migrateModelCompatCatalogOwnership as i, hasInvalidThinkingFormat as n, resolveConfiguredModelCatalogOwnership as o, hasStaleContextWindowValue as r, resolveStaleContextWindowFix as s, MODEL_COMPAT_CATALOG_RULES as t };