UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

448 lines (447 loc) 20.5 kB
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { n as createLazyPromise } from "./lazy-promise-DGqyc4Y4.js"; import { r as normalizeProviderId } from "./provider-id-DMd-TDFp.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import { t as isDiagnosticFlagEnabled } from "./diagnostic-flags-qVPAqdVk.js"; import { n as getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot-CmSX4G3W.js"; import { c as resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot-w-4EjxI4.js"; import { t as createConfiguredProviderCatalogModelIdNormalizer } from "./model-ref-shared-Dz7QU0Lx.js"; import { t as modelKey } from "./model-key-CMdQNkZf.js"; import { n as resolveCatalogOwnedModelCompat } from "./model-compat-catalog-BNBUeFnX.js"; import { S as modelSupportsInput, r as buildConfiguredModelCatalog } from "./model-selection-shared-BlLyx1r2.js"; import { n as isManifestPluginAvailableForControlPlane } from "./manifest-contract-eligibility-BbV7X6pV.js"; import { t as planEffectiveModelCatalogRows } from "./model-catalog-BiOJ2EJd.js"; import { t as augmentModelCatalogWithProviderPlugins } from "./provider-runtime.runtime.js"; import { s as resolveClaudeFable5ModelIdentity } from "./anthropic-DfKaEfcc.js"; import "./src-g7CpceXJ.js"; import { t as modelCatalogRowToEntry } from "./model-catalog-entry-vto08Vpf.js"; //#region src/agents/model-catalog-order.ts /** * Provider catalogs declare models strongest-first. Preserve that owner order * after registry/config merges instead of falling back to alphabetical names. */ function assignProviderModelOrder(entries, existingEntries = [], options = {}) { const orderByModel = /* @__PURE__ */ new Map(); const nextOrderByProvider = /* @__PURE__ */ new Map(); for (const entry of existingEntries) { if (entry.providerOrder === void 0) continue; const provider = normalizeProviderId(entry.provider); const key = `${provider}/${entry.id.trim().toLowerCase()}`; orderByModel.set(key, entry.providerOrder); nextOrderByProvider.set(provider, Math.max(nextOrderByProvider.get(provider) ?? 0, entry.providerOrder + 1)); } return entries.map((entry) => { const provider = normalizeProviderId(entry.provider); const key = `${provider}/${entry.id.trim().toLowerCase()}`; const existingOrder = orderByModel.get(key); if (existingOrder !== void 0) return { ...entry, providerOrder: existingOrder }; if (options.appendUnknown === false) return entry; const providerOrder = nextOrderByProvider.get(provider) ?? 0; nextOrderByProvider.set(provider, providerOrder + 1); orderByModel.set(key, providerOrder); return { ...entry, providerOrder }; }); } function compareModelCatalogEntries(a, b) { const providerComparison = normalizeProviderId(a.provider).localeCompare(normalizeProviderId(b.provider)); if (providerComparison !== 0) return providerComparison; return (a.providerOrder ?? Number.MAX_SAFE_INTEGER) - (b.providerOrder ?? Number.MAX_SAFE_INTEGER) || a.id.localeCompare(b.id) || a.name.localeCompare(b.name); } //#endregion //#region src/agents/model-catalog.ts /** * Loads bundled, manifest, and discovered model catalog entries. */ const log = createSubsystemLogger("model-catalog"); let hasLoggedModelCatalogError = false; let manifestModelCatalogCache = /* @__PURE__ */ new WeakMap(); const loadModelSuppression = createLazyPromise(() => import("./model-suppression-14KQjQ3n.js")); const loadProviderApiKeyResolver = createLazyPromise(() => import("./models-config.providers.secrets-DvGiZhX3.js")); function resetModelCatalogBuilderCacheForTest() { manifestModelCatalogCache = /* @__PURE__ */ new WeakMap(); hasLoggedModelCatalogError = false; } /** Prepares provider aliases once for one captured catalog metadata generation. */ function createPreparedModelCatalogProviderNormalizer(metadataSnapshot) { let aliases; return (provider) => { const normalizedProvider = normalizeProviderId(provider); if (!aliases) { aliases = /* @__PURE__ */ new Map(); for (const plugin of metadataSnapshot.manifestRegistry.plugins) for (const [alias, target] of Object.entries(plugin.modelCatalog?.aliases ?? {})) { const key = normalizeProviderId(alias); const canonicalProvider = normalizeProviderId(target.provider); if (canonicalProvider && !aliases.has(key)) aliases.set(key, canonicalProvider); } } return aliases.get(normalizedProvider) ?? normalizedProvider; }; } function catalogEntryDedupeKey(provider, id) { const normalizedProvider = normalizeProviderId(provider); return normalizeLowercaseStringOrEmpty(modelKey(normalizedProvider, id)); } function mergeCatalogCompat(base, override) { if (!base) return override; if (!override) return base; return { ...base, ...override }; } function mergeCatalogParams(base, override) { if (!base) return override; if (!override) return base; return { ...base, ...override }; } function normalizeCatalogRouteBaseUrl(value) { if (!value) return; try { const url = new URL(value); url.pathname = url.pathname.replace(/\/+$/u, "") || "/"; return url.toString(); } catch { return value.replace(/\/+$/u, ""); } } function catalogRouteChanges(base, overlay) { if (overlay.api === void 0 && overlay.baseUrl === void 0) return false; return overlay.api !== void 0 && base.api !== void 0 && overlay.api !== base.api || overlay.baseUrl !== void 0 && base.baseUrl !== void 0 && normalizeCatalogRouteBaseUrl(overlay.baseUrl) !== normalizeCatalogRouteBaseUrl(base.baseUrl); } function clearRouteBoundCatalogMetadata(entry) { const { contextWindow: _contextWindow, contextWindows: _contextWindows, contextWindowDefault: _contextWindowDefault, contextTokens: _contextTokens, reasoning: _reasoning, thinkingLevelMap: _thinkingLevelMap, input: _input, params: _params, compat: _compat, mediaInput: _mediaInput, ...routeNeutral } = entry; return routeNeutral; } function overlayCatalogMetadata(base, overlay, options) { const routeChanged = catalogRouteChanges(base, overlay); const routeBase = routeChanged ? clearRouteBoundCatalogMetadata(base) : base; const params = mergeCatalogParams(routeBase.params, overlay.params); const thinkingLevelMap = overlay.thinkingLevelMap ?? options?.catalogRoute?.thinkingLevelMap; const { contextWindows: _baseContextWindows, contextWindowDefault: _baseContextWindowDefault, ...selectionNeutralBase } = routeBase; const contextWindowSelection = overlay.contextWindows !== void 0 ? { contextWindows: overlay.contextWindows, ...overlay.contextWindowDefault !== void 0 ? { contextWindowDefault: overlay.contextWindowDefault } : {} } : { ...routeBase.contextWindows !== void 0 ? { contextWindows: routeBase.contextWindows } : {}, ...overlay.contextWindowDefault ?? routeBase.contextWindowDefault ? { contextWindowDefault: overlay.contextWindowDefault ?? routeBase.contextWindowDefault } : {} }; return { ...selectionNeutralBase, ...contextWindowSelection, ...routeChanged && !options?.preserveBaseName ? { name: overlay.name } : {}, ...overlay.api !== void 0 ? { api: overlay.api } : {}, ...overlay.baseUrl !== void 0 ? { baseUrl: overlay.baseUrl } : {}, ...overlay.contextWindow !== void 0 ? { contextWindow: overlay.contextWindow } : {}, ...overlay.contextTokens !== void 0 ? { contextTokens: overlay.contextTokens } : {}, ...overlay.reasoning !== void 0 ? { reasoning: overlay.reasoning } : {}, ...thinkingLevelMap ? { thinkingLevelMap } : {}, ...overlay.input !== void 0 ? { input: overlay.input } : {}, ...params ? { params } : {}, ...overlay.mediaInput !== void 0 ? { mediaInput: overlay.mediaInput } : {}, ...overlay.providerOrder !== void 0 ? { providerOrder: overlay.providerOrder } : {}, ...overlay.status !== void 0 ? { status: overlay.status } : {}, ...overlay.statusReason !== void 0 ? { statusReason: overlay.statusReason } : {}, ...overlay.replaces !== void 0 ? { replaces: overlay.replaces } : {}, ...overlay.replacedBy !== void 0 ? { replacedBy: overlay.replacedBy } : {}, compat: options?.preserveBaseCompat ? resolveCatalogOwnedModelCompat({ catalogRoute: options.catalogRoute ?? base, catalogCompat: (options.catalogRoute ?? base).compat, configuredRoute: { api: overlay.api ?? base.api, baseUrl: overlay.baseUrl ?? base.baseUrl }, configuredCompat: overlay.compat }) : mergeCatalogCompat(routeBase.compat, overlay.compat) }; } function normalizeCatalogEntryContract(entry) { if (entry.api === "anthropic-messages" && resolveClaudeFable5ModelIdentity({ id: entry.id, params: entry.params })) return { ...entry, reasoning: true }; return entry; } function mergeCatalogEntries(models, entries, options) { const indexByKey = new Map(models.map((entry, index) => [catalogEntryDedupeKey(entry.provider, entry.id), index])); for (const entry of entries) { const key = catalogEntryDedupeKey(entry.provider, entry.id); const existingIndex = indexByKey.get(key); if (existingIndex === void 0) { models.push(entry); indexByKey.set(key, models.length - 1); continue; } const existing = models.at(existingIndex); if (existing) { const routes = options?.catalogRoutes; const routeIndex = options?.preserveBaseCompat ? routes?.indexByKey.get(catalogRouteVariantKey(entry)) : void 0; const catalogRoute = routeIndex === void 0 ? void 0 : routes?.entries[routeIndex]; models[existingIndex] = overlayCatalogMetadata(existing, entry, { ...options, catalogRoute }); } } } function catalogRouteVariantKey(entry) { return [ catalogEntryDedupeKey(entry.provider, entry.id), entry.api ?? "", normalizeCatalogRouteBaseUrl(entry.baseUrl) ?? "" ].join("\0"); } function createModelCatalogRouteVariantCollector() { return { entries: [], indexByKey: /* @__PURE__ */ new Map() }; } function mergeCatalogRouteVariants(collector, entries, options) { for (const entry of entries) { const key = catalogRouteVariantKey(entry); const existingIndex = collector.indexByKey.get(key); if (existingIndex === void 0) { collector.entries.push(entry); collector.indexByKey.set(key, collector.entries.length - 1); continue; } const existingEntry = collector.entries[existingIndex]; if (existingEntry === void 0) continue; collector.entries[existingIndex] = overlayCatalogMetadata(existingEntry, entry, options); } } function createModelCatalogSnapshot(entries, routeVariants) { return { entries: sortModelCatalogEntries(entries), routeVariants: sortModelCatalogEntries(routeVariants.entries) }; } function resolveEligibleManifestCatalogPlugins(snapshot, config) { return snapshot.plugins.filter((plugin) => plugin.modelCatalog && isManifestPluginAvailableForControlPlane({ snapshot, plugin, config })); } function loadManifestModelCatalog(params) { const resolvedSnapshot = params.metadataSnapshot ?? (params.fallbackToMetadataScan === false ? getCurrentPluginMetadataSnapshot({ config: params.config, env: params.env, ...params.workspaceDir !== void 0 ? { workspaceDir: params.workspaceDir } : {}, ...params.workspaceDir === void 0 ? { allowWorkspaceScopedSnapshot: true } : {} }) : resolvePluginMetadataSnapshot({ config: params.config, ...params.workspaceDir !== void 0 ? { workspaceDir: params.workspaceDir } : {}, env: params.env ?? process.env, allowWorkspaceScopedCurrent: params.workspaceDir === void 0 })); if (!resolvedSnapshot) return []; const cached = manifestModelCatalogCache.get(params.config); if (cached?.snapshot === resolvedSnapshot) return cached.rows; const plugins = resolveEligibleManifestCatalogPlugins(resolvedSnapshot, params.config); const plan = planEffectiveModelCatalogRows({ registry: { plugins }, config: params.config }); const providerOrderByKey = /* @__PURE__ */ new Map(); for (const plugin of plugins) for (const [provider, providerCatalog] of Object.entries(plugin.modelCatalog?.providers ?? {})) providerCatalog.models.forEach((model, providerOrder) => { const key = catalogEntryDedupeKey(provider, model.id); if (!providerOrderByKey.has(key)) providerOrderByKey.set(key, providerOrder); }); const rows = plan.rows.map((row) => { const entry = modelCatalogRowToEntry(row); const providerOrder = providerOrderByKey.get(catalogEntryDedupeKey(row.provider, row.id)); if (providerOrder !== void 0) entry.providerOrder = providerOrder; return entry; }); manifestModelCatalogCache.set(params.config, { snapshot: resolvedSnapshot, rows }); return rows; } function sortModelCatalogEntries(entries) { return entries.map(normalizeCatalogEntryContract).toSorted(compareModelCatalogEntries); } /** Builds the catalog once for a lifecycle generation. No request-time discovery or cache IO. */ async function buildPreparedModelCatalogSnapshot(params) { const models = []; const routeVariants = createModelCatalogRouteVariantCollector(); const cfg = params.config; const env = params.env ?? process.env; const timingEnabled = isDiagnosticFlagEnabled("ingress.timing", cfg); const startMs = timingEnabled ? Date.now() : 0; const logStage = (stage, extra) => { if (!timingEnabled) return; const suffix = extra ? ` ${extra}` : ""; log.info(`model-catalog stage=${stage} elapsedMs=${Date.now() - startMs}${suffix}`); }; try { const workspaceDir = params.workspaceDir; const manifestMetadataSnapshot = params.metadataSnapshot; const normalizeModelId = createConfiguredProviderCatalogModelIdNormalizer({ manifestPlugins: manifestMetadataSnapshot }); const normalizeProvider = createPreparedModelCatalogProviderNormalizer(manifestMetadataSnapshot); const { buildShouldSuppressBuiltInModelCore } = await loadModelSuppression(); logStage("catalog-deps-ready"); const entries = params.modelRegistry.getAll(); const declaredManifestModels = loadManifestModelCatalog({ config: cfg, env, metadataSnapshot: manifestMetadataSnapshot }); logStage("registry-read", `entries=${entries.length}`); const shouldSuppressBuiltInModel = buildShouldSuppressBuiltInModelCore({ config: cfg }); logStage("suppress-resolver-ready"); for (const entry of entries) { const rawId = normalizeOptionalString(entry?.id) ?? ""; if (!rawId) continue; const rawProvider = normalizeOptionalString(entry?.provider) ?? ""; if (!rawProvider) continue; const provider = normalizeProvider(rawProvider); const id = normalizeModelId(provider, rawId); const baseUrl = normalizeOptionalString(entry?.baseUrl); if (shouldSuppressBuiltInModel({ provider, id, baseUrl })) continue; const name = normalizeOptionalString(entry?.name ?? id) || id; const contextWindow = typeof entry?.contextWindow === "number" && entry.contextWindow > 0 ? entry.contextWindow : void 0; const contextTokens = typeof entry?.contextTokens === "number" && entry.contextTokens > 0 ? entry.contextTokens : void 0; const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : void 0; const api = typeof entry?.api === "string" ? entry.api : void 0; const input = Array.isArray(entry?.input) ? entry.input : void 0; const modelParams = entry?.params && typeof entry.params === "object" ? entry.params : void 0; const compat = entry?.compat && typeof entry.compat === "object" ? entry.compat : void 0; const model = { id, name, provider, ...api ? { api } : {}, ...baseUrl ? { baseUrl } : {}, contextWindow, ...contextTokens !== void 0 ? { contextTokens } : {}, reasoning, ...entry.thinkingLevelMap ? { thinkingLevelMap: entry.thinkingLevelMap } : {}, input, ...modelParams ? { params: modelParams } : {}, compat }; models.push(model); } const orderedRegistryModels = assignProviderModelOrder(models, declaredManifestModels, { appendUnknown: false }); models.splice(0, models.length, ...orderedRegistryModels); mergeCatalogRouteVariants(routeVariants, orderedRegistryModels); const supplementalManifestPlan = planEffectiveModelCatalogRows({ registry: { plugins: resolveEligibleManifestCatalogPlugins(manifestMetadataSnapshot, cfg) }, config: cfg, selection: "supplemental" }); const supplementalManifestKeys = new Set(supplementalManifestPlan.rows.map((entry) => catalogEntryDedupeKey(entry.provider, entry.id))); const runtimeDiscoveryProviders = new Set(supplementalManifestPlan.entries.flatMap((entry) => entry.discovery === "runtime" ? [normalizeProviderId(entry.provider)] : [])); const manifestModels = declaredManifestModels.filter((entry) => supplementalManifestKeys.has(catalogEntryDedupeKey(entry.provider, entry.id))); mergeCatalogRouteVariants(routeVariants, manifestModels); mergeCatalogEntries(models, manifestModels); logStage("manifest-models-merged", `entries=${models.length}`); const configuredModels = buildConfiguredModelCatalog({ cfg, manifestPlugins: manifestMetadataSnapshot }); logStage("configured-models-prepared", `entries=${models.length}`); if (!params.readOnly && params.includeProviderPluginAugmentation !== false) { const augmentEntries = [...models]; if (configuredModels.length > 0) mergeCatalogEntries(augmentEntries, configuredModels, { catalogRoutes: routeVariants, preserveBaseCompat: true, preserveBaseName: true }); const { createProviderApiKeyResolverFromPreparedCredentials } = await loadProviderApiKeyResolver(); const resolveProviderApiKeyForProvider = createProviderApiKeyResolverFromPreparedCredentials(env, params.authCredentials, cfg, workspaceDir); const resolveProviderApiKey = (providerId) => providerId?.trim() ? resolveProviderApiKeyForProvider(providerId) : { apiKey: void 0, discoveryApiKey: void 0 }; const supplemental = await augmentModelCatalogWithProviderPlugins({ config: cfg, workspaceDir, env, metadataSnapshot: manifestMetadataSnapshot, context: { config: cfg, agentDir: params.agentDir, workspaceDir, env, resolveProviderApiKey, entries: augmentEntries } }); if (supplemental.length > 0) { const accountVisibleModelKeys = new Set([...models, ...configuredModels].map((entry) => catalogEntryDedupeKey(entry.provider, normalizeModelId(entry.provider, entry.id)))); const normalizedSupplemental = []; for (const entry of supplemental) { const provider = normalizeProvider(entry.provider); const id = normalizeModelId(provider, entry.id); if (runtimeDiscoveryProviders.has(normalizeProviderId(provider)) && !accountVisibleModelKeys.has(catalogEntryDedupeKey(provider, id))) continue; normalizedSupplemental.push({ ...entry, provider, id }); } const orderedSupplemental = assignProviderModelOrder(normalizedSupplemental, [...declaredManifestModels, ...models]); mergeCatalogRouteVariants(routeVariants, orderedSupplemental); mergeCatalogEntries(models, orderedSupplemental); } } logStage("plugin-models-merged", `entries=${models.length}`); if (configuredModels.length > 0) { mergeCatalogRouteVariants(routeVariants, configuredModels, { preserveBaseCompat: true }); routeVariants.indexByKey.clear(); routeVariants.entries.forEach((entry, index) => { const key = catalogRouteVariantKey(entry); if (!routeVariants.indexByKey.has(key)) routeVariants.indexByKey.set(key, index); }); mergeCatalogEntries(models, configuredModels, { catalogRoutes: routeVariants, preserveBaseCompat: true, preserveBaseName: true }); } logStage("configured-models-finalized", `entries=${models.length}`); const snapshot = createModelCatalogSnapshot(models, routeVariants); logStage("complete", `entries=${snapshot.entries.length}`); return snapshot; } catch (error) { if (!hasLoggedModelCatalogError) { hasLoggedModelCatalogError = true; log.warn(`Failed to load model catalog: ${String(error)}`); } throw error; } } /** * Check if a model supports image input based on its catalog entry. */ function modelSupportsVision(entry) { return modelSupportsInput(entry, "image"); } /** * Check if a model supports native document/PDF input based on its catalog entry. */ function modelSupportsDocument(entry) { return modelSupportsInput(entry, "document"); } //#endregion export { modelSupportsVision as a, modelSupportsDocument as i, createPreparedModelCatalogProviderNormalizer as n, resetModelCatalogBuilderCacheForTest as o, loadManifestModelCatalog as r, compareModelCatalogEntries as s, buildPreparedModelCatalogSnapshot as t };