openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,253 lines • 55.8 kB
JavaScript
import "./src-vebZIeLe.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { t as stableStringify } from "./stable-stringify-C8X7niaI.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import "./utils-P__uGsPB.js";
import { g as resolveAmbientOwnerAgentId, m as resolveAgentWorkspaceDir, v as resolveDefaultAgentDir } from "./agent-scope-config-DcbEhP0R.js";
import { n as findNormalizedProviderValue, r as normalizeProviderId } from "./provider-id-DMd-TDFp.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { t as MODEL_APIS } from "./types.models-Z6EPRVI_.js";
import { n as resolveInstalledManifestRegistryIndexFingerprint } from "./manifest-registry-installed-DdhpzefH.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 { o as createConfigRuntimeEnv } from "./config-env-vars-DUfQlcAk.js";
import { u as resolveConfigSecretRef } from "./resolution-facts-Dks1tbik.js";
import { d as hashRuntimeConfigValue, l as getRuntimeConfigSourceSnapshot } from "./runtime-snapshot-BaQikjTR.js";
import { t as projectConfigOntoRuntimeSourceSnapshot } from "./runtime-source-projection-DOHmC11j.js";
import { f as parseConfiguredModelVisibilityEntries } from "./model-selection-shared-BlLyx1r2.js";
import "./agent-scope-DbtJyKUL.js";
import "./env-vars-CyiONVe6.js";
import { t as privateFileStore } from "./private-file-store-CaBdNw0y.js";
import { t as mergeModelCost } from "./model-cost-BNQWuFdo.js";
import { t as KeyedAsyncQueue } from "./keyed-async-queue-CTreGrmR.js";
import "./config-Cs0XXL3x.js";
import { h as resolveAuthProfileDatabasePath } from "./sqlite-MN_7y26V.js";
import { d as isTrustedSecretSurfaceUnavailableError } from "./runtime-degraded-state-D5EZZ925.js";
import { n as matchesProviderPluginRef } from "./provider-registry-shared-DVSyKRD5.js";
import { f as resolveOwningPluginIdsForProviderRef } from "./providers-DCe4UB6a.js";
import { r as ensureAuthProfileStore } from "./store-F1B2duCT.js";
import { c as isNonSecretApiKeyMarker, d as resolveEnvSecretRefHeaderValueMarker, f as resolveNonEnvSecretRefApiKeyMarker, p as resolveNonEnvSecretRefHeaderValueMarker } from "./model-auth-markers-jBKQn38x.js";
import { S as resolveProviderConfigApiKeyWithPlugin, _ as prepareProviderExternalAuthWithPlugin, d as normalizeProviderConfigWithPlugin } from "./provider-runtime-BRJDPNgk.js";
import { n as resolveManifestSyntheticAuthProviderRefState } from "./synthetic-auth.runtime.js";
import { d as PLUGIN_MODEL_CATALOG_GENERATED_BY, f as repairPluginModelCatalogTransportMetadata, i as isGeneratedPluginModelCatalog, l as replacePersistedPluginModelCatalogs, n as encodePluginModelCatalogRelativePath, o as loadPersistedPluginModelCatalogs, s as loadPersistedPluginModelCatalogsReadOnly, t as decodePluginModelCatalogRelativePathPluginId, u as resolvePluginModelCatalogOwnerPluginId } from "./plugin-model-catalog-MRmefceV.js";
import { a as resolveRuntimePluginDiscoveryProviders, i as prepareProviderStaticCatalog, n as normalizePluginDiscoveryResult, o as runProviderCatalog, s as runProviderStaticCatalog, t as groupPluginDiscoveryProvidersByOrder } from "./provider-discovery-BPqo7Vo1.js";
import { i as normalizeProviderMapKeys, n as mergeProviders, r as mergeWithExistingProviderSecrets, t as mergeProviderModels } from "./models-config.merge-CDamwbMN.js";
import { c as resolveMissingProviderApiKey, i as normalizeResolvedEnvApiKey, n as normalizeConfiguredProviderApiKey, o as resolveApiKeyFromProfiles, r as normalizeHeaderValues } from "./models-config.providers.secret-helpers-DC_SfKyR.js";
import { r as createProviderAuthResolver, t as createProviderApiKeyResolver } from "./models-config.providers.secrets-CzQJN999.js";
import path from "node:path";
import fs from "node:fs/promises";
import { createHash } from "node:crypto";
//#region src/agents/models-config-state.ts
const MODELS_JSON_STATE_KEY = Symbol.for("openclaw.modelsJsonState");
const MODELS_JSON_STATE = (() => {
const globalState = globalThis;
if (!globalState[MODELS_JSON_STATE_KEY]) globalState[MODELS_JSON_STATE_KEY] = {
writeQueue: new KeyedAsyncQueue(),
readyCache: /* @__PURE__ */ new Map()
};
return globalState[MODELS_JSON_STATE_KEY];
})();
//#endregion
//#region src/agents/models-config.providers.discovery-scope.ts
/** Resolves the plugin-owned provider scope for configured and live catalog discovery. */
function resolveProviderDiscoveryScope(params) {
const { config, workspaceDir, env } = params;
const scopedProviderIds = params.providerIds !== void 0 ? normalizeStringEntries([...params.providerIds]).map(normalizeProviderId).filter(Boolean) : void 0;
if (scopedProviderIds) return buildProviderDiscoveryScope({
providerIds: scopedProviderIds,
config,
workspaceDir,
env,
resolveOwners: params.resolveOwners
});
if (!(env.OPENCLAW_LIVE_TEST === "1" || env.OPENCLAW_LIVE_GATEWAY === "1" || env.LIVE === "1")) return;
const rawValues = [env.OPENCLAW_LIVE_PROVIDERS?.trim(), env.OPENCLAW_LIVE_GATEWAY_PROVIDERS?.trim()].filter((value) => Boolean(value && value !== "all"));
if (rawValues.length === 0) return;
const ids = normalizeStringEntries(rawValues.flatMap((value) => value.split(","))).map(normalizeProviderId).filter(Boolean);
if (ids.length === 0) return;
return buildProviderDiscoveryScope({
providerIds: ids,
config,
workspaceDir,
env,
resolveOwners: params.resolveOwners
});
}
function buildProviderDiscoveryScope(params) {
const providerIds = [...new Set(params.providerIds)];
const providerIdsByPluginId = /* @__PURE__ */ new Map();
for (const id of providerIds) {
const owners = params.resolveOwners?.(id) ?? resolveOwningPluginIdsForProviderRef({
provider: id,
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env
}) ?? [];
for (const pluginId of owners.length > 0 ? owners : [id]) {
const ownedProviderIds = providerIdsByPluginId.get(pluginId) ?? [];
if (!ownedProviderIds.includes(id)) {
ownedProviderIds.push(id);
providerIdsByPluginId.set(pluginId, ownedProviderIds);
}
}
}
return new Map([...providerIdsByPluginId.entries()].toSorted(([left], [right]) => left.localeCompare(right)));
}
function resolvePluginMetadataProviderOwners(pluginMetadataSnapshot, provider) {
if (!pluginMetadataSnapshot) return;
const normalizedProvider = normalizeProviderId(provider);
if (!normalizedProvider) return;
const owners = /* @__PURE__ */ new Set();
appendNormalizedPluginMetadataOwners(owners, pluginMetadataSnapshot.owners.providers ?? /* @__PURE__ */ new Map(), provider, normalizedProvider);
appendNormalizedPluginMetadataOwners(owners, pluginMetadataSnapshot.owners.modelCatalogProviders ?? /* @__PURE__ */ new Map(), provider, normalizedProvider);
appendNormalizedPluginMetadataOwners(owners, pluginMetadataSnapshot.owners.setupProviders ?? /* @__PURE__ */ new Map(), provider, normalizedProvider);
appendNormalizedPluginMetadataOwners(owners, pluginMetadataSnapshot.owners.cliBackends ?? /* @__PURE__ */ new Map(), provider, normalizedProvider);
return owners.size > 0 ? [...owners].toSorted((left, right) => left.localeCompare(right)) : void 0;
}
function appendNormalizedPluginMetadataOwners(target, ownerMap, provider, normalizedProvider) {
for (const owner of ownerMap.get(provider) ?? []) target.add(owner);
if (normalizedProvider !== provider) for (const owner of ownerMap.get(normalizedProvider) ?? []) target.add(owner);
for (const [ownedId, owners] of ownerMap.entries()) if (ownedId !== provider && ownedId !== normalizedProvider && normalizeProviderId(ownedId) === normalizedProvider) for (const owner of owners) target.add(owner);
}
function resolveImplicitProviderDiscoveryScope(params) {
return resolveProviderDiscoveryScope({
config: params.config,
workspaceDir: params.workspaceDir,
env: params.env ?? process.env,
resolveOwners: params.pluginMetadataSnapshot ? (provider) => resolvePluginMetadataProviderOwners(params.pluginMetadataSnapshot, provider) : void 0,
providerIds: params.providerDiscoveryProviderIds
});
}
//#endregion
//#region src/agents/models-config.providers.implicit.ts
/**
* Discovers implicit model-provider config from plugin provider catalogs and
* static catalogs. It merges discovered provider models with explicit config
* while preserving user-controlled provider fields.
*/
const log = createSubsystemLogger("agents/model-providers");
const PROVIDER_IMPLICIT_MERGERS = { ollama: ({ implicit }) => implicit };
const PLUGIN_DISCOVERY_ORDERS = [
"simple",
"profile",
"paired",
"late"
];
function resolveLiveProviderCatalogTimeoutMs(env) {
if (!(env.OPENCLAW_LIVE_TEST === "1" || env.OPENCLAW_LIVE_GATEWAY === "1" || env.LIVE === "1")) return null;
const raw = env.OPENCLAW_LIVE_PROVIDER_DISCOVERY_TIMEOUT_MS?.trim();
if (!raw) return 15e3;
const parsed = Number(raw);
return /^[+]?\d+$/.test(raw) && Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 15e3;
}
function mergeImplicitProviderSet(target, additions) {
if (!additions) return;
for (const [key, value] of Object.entries(additions)) target[key] = value;
}
function mergeImplicitProviderConfig(params) {
const { providerId, existing, implicit } = params;
if (!existing) return implicit;
const merge = PROVIDER_IMPLICIT_MERGERS[providerId];
if (merge) return merge({
existing,
implicit
});
return mergeProviderModels(implicit, existing, {
providerId,
sourceModelFields: params.sourceModelFields,
manifestPlugins: params.manifestPlugins,
preserveConfiguredModelMembership: !params.dynamicProviderModels && Array.isArray(existing.models) && existing.models.length > 0
});
}
function resolveImplicitProviderAuthMarker(params) {
return resolveMissingProviderApiKey({
providerKey: params.providerId,
provider: params.provider,
env: params.ctx.env,
profileApiKey: void 0
});
}
function resolveConfiguredImplicitProvider(params) {
for (const providerId of params.providerIds) {
const configured = findNormalizedProviderValue(params.configuredProviders ?? void 0, providerId);
if (configured) return configured;
}
}
function resolveExistingImplicitProviderFromContext(params) {
return resolveConfiguredImplicitProvider({
configuredProviders: params.ctx.explicitProviders,
providerIds: params.providerIds
}) ?? resolveConfiguredImplicitProvider({
configuredProviders: params.ctx.config?.models?.providers,
providerIds: params.providerIds
});
}
function hasProviderWildcardVisibility(params) {
return parseConfiguredModelVisibilityEntries({ cfg: params.config }).providerWildcards.has(normalizeProviderId(params.providerId));
}
function hasRuntimeProviderCatalog(provider) {
return typeof provider.catalog?.run === "function";
}
async function resolvePluginImplicitProviders(ctx, providers, order, preparedStaticResults) {
const byOrder = groupPluginDiscoveryProvidersByOrder(providers);
const discovered = {};
const catalogConfig = buildPluginCatalogConfig(ctx);
const selectedProviderIds = ctx.providerDiscoveryScope ? new Set([...ctx.providerDiscoveryScope.values()].flat()) : void 0;
const catalogCountsByPluginId = /* @__PURE__ */ new Map();
for (const provider of providers) {
if (!provider.catalog && !provider.staticCatalog) continue;
const pluginId = provider.pluginId ?? normalizeProviderId(provider.id);
catalogCountsByPluginId.set(pluginId, (catalogCountsByPluginId.get(pluginId) ?? 0) + 1);
}
for (const provider of byOrder[order]) {
const pluginId = provider.pluginId ?? normalizeProviderId(provider.id);
const ownerProviderIds = ctx.providerDiscoveryScope?.get(pluginId);
const providerIds = ctx.providerDiscoveryScope === void 0 ? void 0 : catalogCountsByPluginId.get(pluginId) === 1 ? ownerProviderIds ?? [] : (ownerProviderIds ?? []).filter((id) => matchesProviderPluginRef(provider, id));
if (providerIds?.length === 0) continue;
const resolveCatalogProviderApiKey = (providerId) => {
const resolvedProviderId = providerId?.trim() || provider.id;
const resolved = ctx.resolveProviderApiKey(resolvedProviderId);
if (resolved.apiKey) return resolved;
if (!findNormalizedProviderValue({
[provider.id]: true,
...Object.fromEntries((provider.aliases ?? []).map((alias) => [alias, true])),
...Object.fromEntries((provider.hookAliases ?? []).map((alias) => [alias, true]))
}, resolvedProviderId)) return resolved;
const syntheticApiKey = (provider.resolveSyntheticAuth?.({
config: catalogConfig,
provider: resolvedProviderId,
providerConfig: catalogConfig.models?.providers?.[resolvedProviderId]
}))?.apiKey?.trim();
if (!syntheticApiKey) return resolved;
return {
apiKey: isNonSecretApiKeyMarker(syntheticApiKey) ? syntheticApiKey : resolveNonEnvSecretRefApiKeyMarker("file"),
discoveryApiKey: void 0
};
};
if (ctx.providerDiscoveryEntriesOnly === true && !provider.staticCatalog) continue;
const useStaticCatalog = Boolean(provider.staticCatalog) && (ctx.providerDiscoveryEntriesOnly === true || !hasRuntimeProviderCatalog(provider));
const hasPreparedStaticResult = preparedStaticResults?.has(provider) === true;
let result;
if (useStaticCatalog) result = hasPreparedStaticResult ? preparedStaticResults.get(provider) : await runProviderStaticCatalog({ provider });
else result = await runProviderCatalogWithTimeout({
provider,
...providerIds !== void 0 ? { providerIds } : {},
config: catalogConfig,
agentDir: ctx.agentDir,
workspaceDir: ctx.workspaceDir,
env: ctx.env,
resolveProviderApiKey: resolveCatalogProviderApiKey,
resolveProviderAuth: (providerId, options) => ctx.resolveProviderAuth(providerId?.trim() || provider.id, options),
reportCatalogOutcome: ctx.onProviderCatalogOutcome,
timeoutMs: ctx.providerDiscoveryTimeoutMs ?? resolveLiveProviderCatalogTimeoutMs(ctx.env)
});
if (!result && !useStaticCatalog && provider.staticCatalog) result = await runProviderStaticCatalog({ provider });
if (!result) continue;
const normalizedResult = normalizePluginDiscoveryResult({
provider,
result
});
for (const [providerId, implicitProvider] of Object.entries(normalizedResult)) {
if (selectedProviderIds && !selectedProviderIds.has(normalizeProviderId(providerId))) continue;
discovered[providerId] = resolveImplicitProviderAuthMarker({
ctx,
providerId,
provider: mergeImplicitProviderConfig({
providerId,
existing: discovered[providerId] ?? resolveExistingImplicitProviderFromContext({
ctx,
providerIds: [
providerId,
provider.id,
...provider.aliases ?? [],
...provider.hookAliases ?? []
]
}),
implicit: implicitProvider,
dynamicProviderModels: hasProviderWildcardVisibility({
config: ctx.config,
providerId
}),
sourceModelFields: ctx.sourceModelFields,
manifestPlugins: ctx.pluginMetadataSnapshot
})
});
}
}
return Object.keys(discovered).length > 0 ? discovered : void 0;
}
function buildPluginCatalogConfig(ctx) {
if (!ctx.explicitProviders || Object.keys(ctx.explicitProviders).length === 0) return ctx.config ?? {};
return {
...ctx.config,
models: {
...ctx.config?.models,
providers: {
...ctx.config?.models?.providers,
...ctx.explicitProviders
}
}
};
}
async function runProviderCatalogWithTimeout(params) {
const timeoutMs = params.timeoutMs ?? void 0;
const timeoutError = /* @__PURE__ */ new Error(`provider catalog timed out after ${timeoutMs}ms: ${params.provider.id}`);
let timer;
try {
if (!timeoutMs) return await runProviderCatalog(params);
const catalogRun = runProviderCatalog(params);
return await Promise.race([catalogRun, new Promise((_, reject) => {
timer = setTimeout(() => {
reject(timeoutError);
}, timeoutMs);
timer.unref?.();
})]);
} catch (error) {
if (isTrustedSecretSurfaceUnavailableError(error)) {
params.reportCatalogOutcome?.({
provider: params.provider.id,
status: "unavailable"
});
return;
}
if (error === timeoutError) {
const message = formatErrorMessage(error);
params.reportCatalogOutcome?.({
provider: params.provider.id,
status: "unavailable"
});
log.warn(`${message}; skipping provider discovery`);
return;
}
throw error;
} finally {
if (timer) clearTimeout(timer);
}
}
/** Prepares sterile provider catalog results for one workspace/config generation. */
async function prepareImplicitProviderStaticCatalog(params) {
const env = params.env ?? process.env;
const discoveryScope = resolveImplicitProviderDiscoveryScope(params);
const providers = await resolveRuntimePluginDiscoveryProviders({
config: params.config,
workspaceDir: params.workspaceDir,
env,
onlyPluginIds: discoveryScope ? [...discoveryScope.keys()] : void 0,
...params.pluginMetadataSnapshot ? { pluginMetadataSnapshot: params.pluginMetadataSnapshot } : {},
discoveryEntriesOnly: true,
includeSyntheticAuthProviders: true
});
const staticCatalogProviderIds = params.staticCatalogProviderIds ? new Set(params.staticCatalogProviderIds.map((provider) => normalizeProviderId(provider))) : void 0;
const prepared = await prepareProviderStaticCatalog({ providers: staticCatalogProviderIds ? providers.filter((provider) => {
if ([...staticCatalogProviderIds].some((id) => matchesProviderPluginRef(provider, id))) return true;
return (provider.pluginId ? discoveryScope?.get(provider.pluginId) : void 0)?.some((id) => staticCatalogProviderIds.has(id)) === true && providers.filter((candidate) => candidate.pluginId === provider.pluginId && candidate.staticCatalog).length === 1;
}) : providers });
return Object.freeze({
providers: Object.freeze(providers),
entries: prepared.entries
});
}
/** Resolve all implicit provider configs contributed by runtime plugin discovery. */
async function resolveImplicitProviders(params) {
const providers = {};
const env = params.env ?? process.env;
let authStore = params.authStore;
const getAuthStore = () => authStore ??= ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
externalCliProviderIds: params.providerDiscoveryProviderIds
});
const discoveryScope = resolveImplicitProviderDiscoveryScope(params);
const discoveryPluginIds = discoveryScope ? [...discoveryScope.keys()] : void 0;
const discoveryAuthConfig = params.discoveryAuthConfig ?? params.config;
const discoveryAuthEnv = params.discoveryAuthEnv ?? env;
const authInputs = [
env,
getAuthStore,
discoveryAuthConfig,
params.providerDiscoveryEntriesOnly ? void 0 : params.sourceConfigForSecrets ?? params.config,
params.workspaceDir,
discoveryAuthEnv
];
const context = {
...params,
get authStore() {
return getAuthStore();
},
env,
...discoveryScope ? { providerDiscoveryScope: discoveryScope } : {},
resolveProviderApiKey: createProviderApiKeyResolver(...authInputs),
resolveProviderAuth: createProviderAuthResolver(...authInputs)
};
const preparedStaticEntries = params.preparedStaticProviderCatalog ? params.preparedStaticProviderCatalog.entries.filter(({ provider }) => discoveryPluginIds === void 0 || provider.pluginId !== void 0 && discoveryPluginIds.includes(provider.pluginId)) : void 0;
const preparedProviders = params.providerDiscoveryEntriesOnly === true && params.preparedStaticProviderCatalog?.providers ? params.preparedStaticProviderCatalog.providers.filter((provider) => discoveryPluginIds === void 0 || provider.pluginId !== void 0 && discoveryPluginIds.includes(provider.pluginId)) : [];
const preparedPluginIds = new Set(preparedProviders.flatMap((provider) => provider.pluginId ? [provider.pluginId] : []));
const missingDiscoveryPluginIds = discoveryPluginIds?.filter((pluginId) => !preparedPluginIds.has(pluginId)) ?? (preparedProviders.length > 0 ? void 0 : discoveryPluginIds);
const resolvedProviders = missingDiscoveryPluginIds === void 0 || missingDiscoveryPluginIds.length > 0 ? await resolveRuntimePluginDiscoveryProviders({
config: params.config,
workspaceDir: params.workspaceDir,
env,
onlyPluginIds: missingDiscoveryPluginIds,
...params.pluginMetadataSnapshot ? { pluginMetadataSnapshot: params.pluginMetadataSnapshot } : {},
...params.providerDiscoveryEntriesOnly === true ? { discoveryEntriesOnly: true } : {}
}) : [];
const discoveryProviders = [...new Map([...resolvedProviders, ...preparedProviders].map((provider) => [`${provider.pluginId ?? ""}\0${normalizeProviderId(provider.id)}`, provider])).values()];
const syntheticRefs = resolveManifestSyntheticAuthProviderRefState({
config: discoveryAuthConfig,
env,
workspaceDir: params.workspaceDir,
...params.pluginMetadataSnapshot ? { index: params.pluginMetadataSnapshot.index } : {}
}).refs.map(normalizeProviderId);
const syntheticProviders = discoveryProviders.filter((provider) => syntheticRefs.some((ref) => matchesProviderPluginRef(provider, ref)));
const configuredSyntheticRefs = Object.entries(discoveryAuthConfig?.models?.providers ?? {}).flatMap(([provider, { api }]) => api && (syntheticRefs.includes(normalizeProviderId(api)) || syntheticProviders.some((candidate) => matchesProviderPluginRef(candidate, api))) ? [normalizeProviderId(provider)] : []);
const scopedRefs = discoveryScope ? new Set([...discoveryScope.values()].flat()) : void 0;
for (const provider of /* @__PURE__ */ new Set([...syntheticRefs, ...configuredSyntheticRefs])) {
if (scopedRefs && !scopedRefs.has(provider)) continue;
await prepareProviderExternalAuthWithPlugin({
config: discoveryAuthConfig,
env: discoveryAuthEnv,
workspaceDir: params.workspaceDir,
provider,
context: {
config: discoveryAuthConfig,
provider,
providerConfig: findNormalizedProviderValue(discoveryAuthConfig?.models?.providers, provider)
}
});
}
if (params.providerDiscoveryEntriesOnly !== true && discoveryProviders.some(hasRuntimeProviderCatalog)) {
const { prepareProviderDiscoveryAuth } = await import("./models-config.providers.discovery-auth.runtime.js");
Object.assign(context, await prepareProviderDiscoveryAuth(context, discoveryAuthConfig));
}
const preparedStaticResultsByProvider = new Map(preparedStaticEntries?.map(({ provider, result }) => [`${provider.pluginId ?? ""}\0${normalizeProviderId(provider.id)}`, result]) ?? []);
const preparedStaticResults = params.preparedStaticProviderCatalog ? new Map(discoveryProviders.flatMap((provider) => {
const key = `${provider.pluginId ?? ""}\0${normalizeProviderId(provider.id)}`;
return preparedStaticResultsByProvider.has(key) ? [[provider, preparedStaticResultsByProvider.get(key)]] : [];
})) : void 0;
for (const order of PLUGIN_DISCOVERY_ORDERS) mergeImplicitProviderSet(providers, await resolvePluginImplicitProviders(context, discoveryProviders, order, preparedStaticResults));
return providers;
}
//#endregion
//#region src/agents/models-config.providers.policy.lookup.ts
/**
* Resolves provider plugin lookup keys from provider config aliases.
*/
const GENERIC_PROVIDER_APIS = /* @__PURE__ */ new Set([
"openai-completions",
"openai-responses",
"anthropic-messages",
"google-generative-ai"
]);
function resolveProviderPluginLookupKey(providerKey, provider) {
const api = normalizeOptionalString(provider?.api) ?? "";
if (providerKey === "google-antigravity" || providerKey === "google-vertex" || api === "google-generative-ai") return "google";
if (Array.isArray(provider?.models) && provider.models.some((model) => normalizeOptionalString(model.api) === "google-generative-ai")) return "google";
if (api && MODEL_APIS.includes(api) && !GENERIC_PROVIDER_APIS.has(api)) return api;
return providerKey;
}
//#endregion
//#region src/agents/models-config.providers.policy.ts
/**
* Provider-specific config policy adapters.
*
* These helpers call plugin hooks without triggering runtime plugin loading
* from config assembly.
*/
/** Normalizes a provider config according to provider-specific runtime policy. */
function normalizeProviderSpecificConfig(providerKey, provider, manifestRegistry) {
const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider);
return normalizeProviderConfigWithPlugin({
provider: runtimeProviderKey,
allowRuntimePluginLoad: false,
...manifestRegistry ? { manifestRegistry } : {},
context: {
provider: providerKey,
providerConfig: provider
}
}) ?? provider;
}
/** Resolves a provider-specific API key env lookup policy when one exists. */
function resolveProviderConfigApiKeyResolver(providerKey, provider, manifestRegistry) {
const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider).trim();
return (env) => resolveProviderConfigApiKeyWithPlugin({
provider: runtimeProviderKey,
allowRuntimePluginLoad: false,
...manifestRegistry ? { manifestRegistry } : {},
context: {
provider: providerKey,
env
}
});
}
//#endregion
//#region src/agents/models-config.providers.source-managed.ts
/**
* Enforces source-managed provider secret ownership rules.
*/
function normalizeSourceProviderLookup(providers) {
if (!providers) return /* @__PURE__ */ new Map();
const validProviders = Object.fromEntries(Object.entries(providers).filter(([, provider]) => isRecord(provider)).map(([providerKey, providerConfig]) => [providerKey, {
providerKey,
providerConfig
}]));
return new Map(Object.entries(normalizeProviderMapKeys(validProviders)));
}
function resolveSourceManagedApiKeyMarker(params) {
const sourceApiKeyRef = resolveConfigSecretRef({
config: params.sourceConfig,
path: `models.providers.${params.sourceProvider.providerKey}.apiKey`,
value: params.sourceProvider.providerConfig.apiKey,
defaults: params.sourceConfig?.secrets?.defaults
});
if (!sourceApiKeyRef || !sourceApiKeyRef.id.trim()) return;
return sourceApiKeyRef.source === "env" ? sourceApiKeyRef.id.trim() : resolveNonEnvSecretRefApiKeyMarker(sourceApiKeyRef.source);
}
function resolveSourceManagedHeaderMarkers(params) {
const sourceHeaders = isRecord(params.sourceProvider.providerConfig.headers) ? params.sourceProvider.providerConfig.headers : void 0;
if (!sourceHeaders) return {};
const markers = {};
for (const [headerName, headerValue] of Object.entries(sourceHeaders)) {
const sourceHeaderRef = resolveConfigSecretRef({
config: params.sourceConfig,
path: `models.providers.${params.sourceProvider.providerKey}.headers.${headerName}`,
value: headerValue,
defaults: params.sourceConfig?.secrets?.defaults
});
if (!sourceHeaderRef || !sourceHeaderRef.id.trim()) continue;
markers[headerName] = sourceHeaderRef.source === "env" ? resolveEnvSecretRefHeaderValueMarker(sourceHeaderRef.id) : resolveNonEnvSecretRefHeaderValueMarker(sourceHeaderRef.source);
}
return markers;
}
/** Preserves source-managed apiKey/header markers from the original provider config. */
function enforceSourceManagedProviderSecrets(params) {
const { providers } = params;
if (!providers) return providers;
const sourceProvidersByKey = normalizeSourceProviderLookup(params.sourceConfigForSecrets?.models?.providers);
if (sourceProvidersByKey.size === 0) return providers;
let nextProviders = null;
for (const [providerKey, provider] of Object.entries(providers)) {
if (!isRecord(provider)) continue;
const canonicalProviderKey = normalizeProviderId(providerKey);
const sourceProvider = sourceProvidersByKey.get(canonicalProviderKey);
if (!sourceProvider) continue;
let nextProvider = provider;
let providerMutated = false;
const sourceApiKeyMarker = resolveSourceManagedApiKeyMarker({
sourceProvider,
sourceConfig: params.sourceConfigForSecrets
});
if (sourceApiKeyMarker) {
params.secretRefManagedProviders?.add(canonicalProviderKey);
if (nextProvider.apiKey !== sourceApiKeyMarker) {
providerMutated = true;
nextProvider = {
...nextProvider,
apiKey: sourceApiKeyMarker
};
}
}
const sourceHeaderMarkers = resolveSourceManagedHeaderMarkers({
sourceProvider,
sourceConfig: params.sourceConfigForSecrets
});
if (Object.keys(sourceHeaderMarkers).length > 0) {
const currentHeaders = isRecord(nextProvider.headers) ? nextProvider.headers : void 0;
const nextHeaders = { ...currentHeaders };
let headersMutated = !currentHeaders;
for (const [headerName, marker] of Object.entries(sourceHeaderMarkers)) {
if (nextHeaders[headerName] === marker) continue;
headersMutated = true;
nextHeaders[headerName] = marker;
}
if (headersMutated) {
providerMutated = true;
nextProvider = {
...nextProvider,
headers: nextHeaders
};
}
}
if (!providerMutated) continue;
if (!nextProviders) nextProviders = { ...providers };
nextProviders[providerKey] = nextProvider;
}
return nextProviders ?? providers;
}
//#endregion
//#region src/agents/models-config.providers.normalize.ts
/**
* Normalizes configured provider model rows for runtime/discovery use.
*/
function getProviderModelId(model) {
return typeof model.id === "string" && model.id.trim() ? model.id : void 0;
}
function normalizeModelCostForCatalog(model) {
const cost = model.cost;
if (!cost || [
"input",
"output",
"cacheRead",
"cacheWrite"
].every((key) => cost[key] !== void 0)) return model;
return {
...model,
cost: {
...model.cost,
input: cost.input ?? 0,
output: cost.output ?? 0,
cacheRead: cost.cacheRead ?? 0,
cacheWrite: cost.cacheWrite ?? 0
}
};
}
function mergeNormalizedProviderModel(existing, incoming) {
const cost = mergeModelCost(incoming.cost, existing.cost);
return {
...incoming,
...existing,
...cost ? { cost } : {}
};
}
function normalizeProviderModelsForConfig(providerKey, provider, options = {}, completeCatalogCosts = false) {
if (!Array.isArray(provider.models) || provider.models.length === 0) return provider;
const normalizeModelId = createConfiguredProviderCatalogModelIdNormalizer(options);
let mutated = false;
const nextModels = [];
const seenById = /* @__PURE__ */ new Map();
for (const model of provider.models) {
const rawId = getProviderModelId(model);
const normalizedId = rawId ? normalizeModelId(providerKey, rawId) : rawId;
const normalizedModel = normalizedId && normalizedId !== rawId ? {
...model,
id: normalizedId
} : model;
if (normalizedModel !== model) mutated = true;
const id = getProviderModelId(normalizedModel);
if (id) {
const existingIndex = seenById.get(id);
if (existingIndex !== void 0) {
mutated = true;
const existing = nextModels.at(existingIndex);
if (existing) nextModels[existingIndex] = mergeNormalizedProviderModel(existing, normalizedModel);
continue;
}
seenById.set(id, nextModels.length);
}
nextModels.push(normalizedModel);
}
if (completeCatalogCosts) for (const [index, model] of nextModels.entries()) {
const normalized = normalizeModelCostForCatalog(model);
if (normalized !== model) {
nextModels[index] = normalized;
mutated = true;
}
}
return mutated ? {
...provider,
models: nextModels
} : provider;
}
function normalizeProviderCatalogModelsForConfig(providers, options = {}) {
if (!providers) return providers;
let mutated = false;
const next = {};
for (const [providerKey, provider] of Object.entries(providers)) {
const normalized = normalizeProviderModelsForConfig(providerKey, provider, options, true);
mutated ||= normalized !== provider;
next[providerKey] = normalized;
}
return mutated ? next : providers;
}
function normalizeProviders(params) {
const { providers } = params;
if (!providers) return providers;
const env = params.env ?? process.env;
const sourceProviders = normalizeSourceProviderLookup(params.sourceConfigForSecrets?.models?.providers);
let authStore;
const resolveProfileApiKey = (providerKey) => {
authStore ??= ensureAuthProfileStore(params.agentDir, { allowKeychainPrompt: false });
return resolveApiKeyFromProfiles({
provider: providerKey,
store: authStore,
env
});
};
let mutated = false;
const next = {};
for (const [key, provider] of Object.entries(providers)) {
const normalizedKey = key.trim();
if (!normalizedKey) {
mutated = true;
continue;
}
if (normalizedKey !== key) mutated = true;
const sourceProvider = sourceProviders.get(normalizeProviderId(normalizedKey));
const source = sourceProvider && params.sourceConfigForSecrets ? {
config: params.sourceConfigForSecrets,
providerKey: sourceProvider.providerKey
} : void 0;
let normalizedProvider = provider;
const normalizedHeaders = normalizeHeaderValues({
headers: normalizedProvider.headers,
secretDefaults: params.secretDefaults,
source
});
if (normalizedHeaders.mutated) normalizedProvider = {
...normalizedProvider,
headers: normalizedHeaders.headers
};
const sourceInput = sourceProvider?.providerConfig.apiKey !== void 0 ? {
config: params.sourceConfigForSecrets,
path: `models.providers.${sourceProvider.providerKey}.apiKey`,
value: sourceProvider.providerConfig.apiKey,
defaults: params.sourceConfigForSecrets?.secrets?.defaults
} : void 0;
normalizedProvider = normalizeConfiguredProviderApiKey({
providerKey: normalizedKey,
sourceInput,
provider: normalizedProvider,
secretDefaults: params.secretDefaults,
profileApiKey: void 0,
secretRefManagedProviders: params.secretRefManagedProviders
});
normalizedProvider = normalizeResolvedEnvApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
env,
secretRefManagedProviders: params.secretRefManagedProviders
});
const needsProfileApiKey = Array.isArray(normalizedProvider.models) && normalizedProvider.models.length > 0 && !(typeof normalizedProvider.apiKey === "string" && normalizedProvider.apiKey.trim() || normalizedProvider.apiKey);
const profileApiKey = needsProfileApiKey ? resolveProfileApiKey(normalizedKey) : void 0;
const providerApiKeyResolver = needsProfileApiKey ? resolveProviderConfigApiKeyResolver(normalizedKey, void 0, params.manifestRegistry) : void 0;
normalizedProvider = resolveMissingProviderApiKey({
providerKey: normalizedKey,
provider: normalizedProvider,
env,
profileApiKey,
secretRefManagedProviders: params.secretRefManagedProviders,
providerApiKeyResolver
});
normalizedProvider = normalizeProviderSpecificConfig(normalizedKey, normalizedProvider, params.manifestRegistry);
normalizedProvider = normalizeProviderModelsForConfig(normalizedKey, normalizedProvider, { manifestPlugins: params.manifestPlugins });
mutated ||= normalizedProvider !== provider;
const existing = next[normalizedKey];
if (existing) {
mutated = true;
next[normalizedKey] = {
...existing,
...normalizedProvider,
models: normalizedProvider.models ?? existing.models
};
continue;
}
next[normalizedKey] = normalizedProvider;
}
return enforceSourceManagedProviderSecrets({
providers: mutated ? next : providers,
sourceConfigForSecrets: params.sourceConfigForSecrets,
secretRefManagedProviders: params.secretRefManagedProviders
});
}
//#endregion
//#region src/agents/models-config.plan.ts
/**
* Plans root and plugin-owned model catalog writes. Setup and doctor flows use
* this module to merge implicit provider discovery, explicit config, and
* preserved secrets before touching models.json.
*/
function splitProvidersByPluginOwner(params) {
const rootProviders = {};
const pluginProviders = {};
for (const [providerId, provider] of Object.entries(params.providers)) {
const pluginId = resolvePluginModelCatalogOwnerPluginId({
providerId,
pluginMetadataSnapshot: params.pluginMetadataSnapshot
});
if (!pluginId) {
rootProviders[providerId] = provider;
continue;
}
const pluginCatalog = pluginProviders[pluginId] ??= {};
pluginCatalog[providerId] = provider;
}
return {
rootProviders,
pluginProviders
};
}
function buildPluginCatalogWrites(pluginProviders) {
return Object.fromEntries(Object.entries(pluginProviders).map(([pluginId, providers]) => [encodePluginModelCatalogRelativePath(pluginId), `${JSON.stringify({
generatedBy: PLUGIN_MODEL_CATALOG_GENERATED_BY,
providers
}, null, 2)}\n`]));
}
function buildSourceModelFields(sourceProviders, manifestPlugins) {
const normalizeModelId = createConfiguredProviderCatalogModelIdNormalizer({ manifestPlugins });
const fields = /* @__PURE__ */ new Map();
for (const [providerId, provider] of Object.entries(normalizeProviderMapKeys(sourceProviders))) for (const model of provider.models ?? []) {
const key = modelKey(providerId, normalizeModelId(providerId, model.id));
const existing = fields.get(key);
fields.set(key, {
inputOmitted: existing?.inputOmitted || !Object.hasOwn(model, "input"),
cost: mergeModelCost(model.cost, existing?.cost)
});
}
return fields;
}
/** Resolves providers for models.json with injectable implicit-provider discovery. */
async function resolveProvidersForModelsJsonWithDeps(params, deps) {
const { context } = params;
const { agentDir, env } = context;
const explicitProviders = stripBlankProviderBaseUrls(context.cfg.models?.providers ?? {});
const cfg = context.cfg.models?.providers ? {
...context.cfg,
models: {
...context.cfg.models,
providers: explicitProviders
}
} : context.cfg;
const manifestPlugins = context.pluginMetadataSnapshot;
const sourceModelFields = buildSourceModelFields(context.cfg.models?.providers, manifestPlugins);
if (cfg.models?.mode === "replace") return mergeProviders({
implicit: {},
explicit: explicitProviders
});
const implicitProviders = await (deps?.resolveImplicitProviders ?? resolveImplicitProviders)({
agentDir,
...params.authStore ? { authStore: params.authStore } : {},
config: cfg,
discoveryAuthConfig: context.discoveryAuthConfig,
discoveryAuthEnv: context.discoveryAuthEnv,
sourceConfigForSecrets: context.sourceConfigForSecrets,
env,
...context.workspaceDir ? { workspaceDir: context.workspaceDir } : {},
explicitProviders,
sourceModelFields,
...context.pluginMetadataSnapshot ? { pluginMetadataSnapshot: context.pluginMetadataSnapshot } : {},
...context.preparedStaticProviderCatalog ? { preparedStaticProviderCatalog: context.preparedStaticProviderCatalog } : {},
...context.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: context.providerDiscoveryProviderIds } : {},
...context.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: context.providerDiscoveryTimeoutMs } : {},
...context.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {},
...context.onProviderCatalogOutcome ? { onProviderCatalogOutcome: context.onProviderCatalogOutcome } : {}
});
return mergeProviders({
implicit: implicitProviders,
explicit: explicitProviders,
sourceModelFields,
manifestPlugins
});
}
function stripBlankProviderBaseUrls(providers) {
let mutated = false;
const next = {};
for (const [key, provider] of Object.entries(providers)) {
if (typeof provider?.baseUrl === "string" && provider.baseUrl.trim() === "") {
const { baseUrl: _blank, ...rest } = provider;
next[key] = rest;
mutated = true;
continue;
}
next[key] = provider;
}
return mutated ? next : providers;
}
function resolveProvidersForMode(params) {
if (params.mode !== "merge") return params.providers;
const existing = params.existingParsed;
if (!isRecord(existing) || !isRecord(existing.providers)) return params.providers;
const existingProviders = existing.providers;
return mergeWithExistingProviderSecrets({
nextProviders: params.providers,
existingProviders,
secretRefManagedProviders: params.secretRefManagedProviders
});
}
function isWritableProviderConfig(provider) {
if (!Array.isArray(provider.models) || provider.models.length === 0) return true;
return Boolean(provider.baseUrl?.trim() && (provider.apiKey === void 0 || provider.apiKey));
}
function filterWritableProviders(providers) {
const next = Object.fromEntries(Object.entries(providers).filter(([, provider]) => isWritableProviderConfig(provider)));
return Object.keys(next).length === Object.keys(providers).length ? providers : next;
}
/** Plans root and plugin-owned model catalog writes with injectable provider discovery. */
async function planOpenClawModelsJsonWithDeps(params, deps) {
const { context } = params;
const { cfg, agentDir, env } = context;
const providers = await resolveProvidersForModelsJsonWithDeps({
context,
...params.authStore ? { authStore: params.authStore } : {}
}, deps);
if (Object.keys(providers).length === 0) {
if (cfg.models?.mode === "replace") return {
action: "write",
contents: `${JSON.stringify({ providers: {} }, null, 2)}\n`,
pluginCatalogWrites: {}
};
return { action: "skip" };
}
const mode = cfg.models?.mode ?? "merge";
const secretRefManagedProviders = /* @__PURE__ */ new Set();
const manifestPlugins = context.pluginMetadataSnapshot;
const providerPolicyManifestRegistry = context.pluginMetadataSnapshot?.pluginIds === void 0 ? context.pluginMetadataSnapshot?.manifestRegistry : void 0;
const normalizedProviders = normalizeProviders({
providers,
agentDir,
env,
secretDefaults: cfg.secrets?.defaults,
sourceConfigForSecrets: context.sourceConfigForSecrets,
secretRefManagedProviders,
manifestPlugins,
...providerPolicyManifestRegistry ? { manifestRegistry: providerPolicyManifestRegistry } : {}
}) ?? providers;
const mergedProviders = resolveProvidersForMode({
mode,
existingParsed: params.existingParsed,
providers: normalizedProviders,
secretRefManagedProviders
});
const normalizedMergedProviders = normalizeProviderCatalogModelsForConfig(mergedProviders, { manifestPlugins }) ?? mergedProviders;
const splitProviders = splitProvidersByPluginOwner({
providers: filterWritableProviders(enforceSourceManagedProviderSecrets({
providers: normalizedMergedProviders,
sourceConfigForSecrets: context.sourceConfigForSecrets,
secretRefManagedProviders
}) ?? normalizedMergedProviders),
pluginMetadataSnapshot: context.pluginMetadataSnapshot
});
const pluginCatalogWrites = buildPluginCatalogWrites(splitProviders.pluginProviders);
const nextContents = `${JSON.stringify({ providers: splitProviders.rootProviders }, null, 2)}\n`;
if (params.existingRaw === nextContents && Object.keys(pluginCatalogWrites).length === 0) return {
action: "noop",
pluginCatalogWrites
};
return {
action: "write",
contents: nextContents,
pluginCatalogWrites
};
}
/** Plans root and plugin-owned model catalog writes for the current runtime. */
async function planOpenClawModelsJson(params) {
return planOpenClawModelsJsonWithDeps(params);
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.modelsConfigPlanTestApi")] = {
planOpenClawModelsJsonWithDeps,
resolveProvidersForModelsJsonWithDeps
};
//#endregion
//#region src/agents/models-config.ts
/**
* Ensures agent-local models.json and the SQLite-backed plugin model catalog
* match runtime config, discovered providers, auth-profile state, and
* generated catalog ownership.
*/
function listPreparedPluginModelCatalogs(agentDir) {
const { catalogs, warnings } = loadPersistedPluginModelCatalogs(agentDir);
if (warnings.length > 0) throw new Error(`Cannot safely prepare provider models until legacy catalog migration succeeds: ${warnings.join("; ")}. Run openclaw doctor --fix.`);
return catalogs;
}
async function readFileMtimeMs(pathname) {
try {
const stat = await fs.stat(pathname);
return Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : null;
} catch {
return null;
}
}
async function buildModelsJsonFingerprint(context) {
const authProfilesSqlitePath = resolveAuthProfileDatabasePath(context.agentDir);
const authProfilesMtimeMs = await readFileMtimeMs(authProfilesSqlitePath);
const authProfilesWalMtimeMs = await readFileMtimeMs(`${authProfilesSqlitePath}-wal`);
const modelsFileMtimeMs = await readFileMtimeMs(path.join(context.agentDir, "models.json"));
const pluginCatalogFingerprint = createHash("sha256").update(stableStringify(listPreparedPluginModelCatalogs(context.agentDir))).digest("base64url");
const pluginMetadataSnapshotIndexFingerprint = context.pluginMetadataSnapshot ? resolveInstalledManifestRegistryIndexFingerprint(context.pluginMetadataSnapshot.index) : void 0;
return stableStringify({
config: context.cfg,
discoveryAuthConfigHash: hashRuntimeConfigValue(context.discoveryAuthConfig),
sourceConfigForSecrets: context.sourceConfigForSecrets,
envShape: context.envFingerprint,
authProfilesMtimeMs,
authProfilesWalMtimeMs,
modelsFileMtimeMs,
pluginCatalogFingerprint,
workspaceDir: context.workspaceDir,
pluginMetadataSnapshotIndexFingerprint,
pluginMetadataSnapshotPluginIds: context.pluginMetadataSnapshot?.pluginIds === void 0 ? null : context.pluginMetadataSnapshot.pluginIds.toSorted(),
providerDiscoveryProviderIds: context.providerDiscoveryProviderIds,
providerDiscoveryTimeoutMs: context.providerDiscoveryTimeoutMs,
providerDiscoveryEntriesOnly: context.providerDiscoveryEntriesOnly === true
});
}
function modelsJsonReadyCacheKey(targetPath, fingerprint) {
return `${targetPath}\0${fingerprint}`;
}
async function readExistingModelsFile(pathname) {
try {
const raw = await privateFileStore(path.dirname(pathname)).readTextIfExists(path.basename(pathname));
if (raw === null) return {
raw: "",
parsed: null
};
return {
raw,
parsed: JSON.parse(raw)
};
} catch {
return {
raw: "",
parsed: null
};
}
}
/** Best-effort chmod for the user-visible generated models.json file. */
async function ensureModelsFileModeForModelsJson(pathname) {
await fs.chmod(pathname, 384).catch(() => {});
}
/** Atomic private-file-store write used by models.json generation. */
async function writeModelsFileAtomicForModelsJson(targetPath, contents) {
await privateFileStore(path.dirname(targetPath)).writeText(path.basename(targetPath), contents);
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.modelsConfigTestApi")] = {
ensureModelsFileModeForModelsJson,
writeModelsFileAtomicForModelsJson
};
async function mergeGeneratedPluginCatalogProvidersIntoExistingParsed(params) {
const root = isRecord(params.existingParsed) ? params.existingParsed : {};
const providers = isRecord(root.providers) ? { ...root.providers } : {};
let changed = false;
const pluginCatalogs = params.pluginCatalogs ?? listPreparedPluginModelCatalogs(params.agentDir);
for (const { pluginId: catalogPluginId, contents } of pluginCatalogs) {
let catalog;
try {
catalog = JSON.parse(contents);
} catch {
continue;
}
if (!isGeneratedPluginModelCatalog(catalog) || !isRecord(catalog) || !isRecord(catalog.providers)) continue;
for (const [providerId, provider] of Object.entries(catalog.providers)) {
if (resolvePluginModelCatalogOwnerPluginId({
providerId,
pluginMetadataSnapshot: params.pluginMetadataSnapshot
}) !== catalogPluginId) continue;
providers[providerId] = provider;
changed = true;
}
}
if (!changed) return params.existingParsed;
return {
...root,
providers
};
}
function materializePlannedPluginCatalogs(pluginCatalogWrites) {
return Object.entries(pluginCatalogWrites).map(([relativePath, contents]) => {
const pluginId = decodePluginModelCatalogRelativePathPluginId(relativePath);
if (!pluginId) throw new Error(`Invalid generated plugin model catalog key: ${relativePath}`);
return {
pluginId,
contents: repairPluginModelCatalogTransportMetadata(contents).contents
};
}).toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));
}
function writePluginCatalogsForModelsJson(params) {
if (!params.pluginCatalogWrites) return false;
return replacePersistedPluginModelCatalogs({
agentDir: params.agentDir,
pluginCatalogWrites: params.pluginCatalogWrites
});
}
function resolveModelsConfigInput(config) {
const runtimeSource = getRuntimeConfigSourceSnapshot();
if (!config) {
const loaded = getRuntimeConfig();
return {
config: runtimeSource ?? loaded,
discoveryAuthConfig: loaded,
sourceConfigForSecrets: runtimeSource ?? loaded
};
}
if (!runtimeSource) return {
config,
discoveryAuthConfig: config,
sourceConfigForSecrets: config
};
const projected = projectConfigOntoRuntimeSourceSnapshot(config);
return {
config: projected,
discoveryAuthConfig: config,
sourceConfigForSecrets: projected === config ? runtimeSource : projected
};
}
function prepareModelsConfigContext(config, agentDirOverride, options = {}) {
const resolved = resolveModelsConfigInput(config);
const cfg = resolved.config;
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveDefaultAgentDir(cfg);
const workspaceDir = options.workspaceDir ?? (agentDirOverride?.trim() ? void 0 : resolveAgentWorkspaceDir(cfg, resolveAmbientOwnerAgentId(cfg)));
const fingerprintEnv = createConfigRuntimeEnv(cfg, options.env ?? {});
const env = options.env ? fingerprintEnv : createConfigRuntimeEnv(cfg);
const providerScopedDiscovery = Boolean(options.providerDiscoveryProviderIds?.length);
const pluginMetadataSnapshot = options.pluginMetadataSnapshot ?? resolvePluginMetadataSnapshot({
config: cfg,
env,
...workspaceDir ? { workspaceDir } : {},
...providerScopedDiscovery ? { preferPersisted: false } : {}
});
return {
cfg,
discoveryAuthConfig: resolved.discoveryAuthConfig,
discoveryAuthEnv: options.env ?? process.env,
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
agentDir,
env,
envFingerprint: options.env ? hashRuntimeConfigValue(fingerprintEnv) : fingerprintEnv,
...workspaceDir ? { workspaceDir } : {},
...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {},
...options.preparedStaticProviderCatalog ? { preparedStaticProviderCatalog: options.preparedStaticProviderCatalog } : {},
...options.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: options.providerDiscoveryProviderIds } : {},
...options.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: options.providerDiscoveryTimeoutMs } : {},
...options.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {},
...options.onProviderCatalogOutcome ? { onProviderCatalogOutcome: options.onProviderCatalogOutcome } : {}
};
}
async function withModelsJsonWriteLock(targetPath, run) {
return await MODELS_JSON_STATE.writeQueue.enqueue(targetPath, run);
}
/** Ensures models.json and the agent SQLite catalog cache are current. */
async function prepareOpenClawModelsJsonSource(config, agentDirOverride, options = {}) {
const context = prepareModelsConfigContext(config, agentDirOverride, options);
const { agentDir, pluginMetadataSnapshot, workspaceDir } = context;
const targetPath = path.join(agentDir, "models.json");
const fingerprint = await buildModelsJsonFingerprint(context);
const cacheKey = modelsJsonReadyCacheKey(targetPath, fingerprint);
const cached = MODELS_JSON_STATE.readyCache.get(cacheKey);
if (cached && !options.onProviderCatalogOutcome) {
const settled = await cached;
await ensureModelsFileModeForModelsJson(targetPath);
return {
...settled.result,
fingerprint: settled.fingerprint,
...workspaceDir ? { workspaceDir } : {}
};
}
const pending = withModelsJsonWriteLock(targetPath, async () => {
const existingModelsFile = await readExistingModelsFile(targetPath);
const existingParsedForMerge = await mergeGeneratedPluginCatalogProvidersIntoExistingParsed({
agentDir,
existingParsed: existingModelsFile.parsed,
...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}
});
const plan = await planOpenClawModelsJson({
context,
existingRaw: existingModelsFile.raw,
existingParsed: existingParsedForMerge
});
if (plan.action === "skip") {
const wrotePluginCatalog = writePluginCatalogsForModelsJson({
agentDir,
pluginCatalogWrites: plan.pluginCatalogWrites
});
return {
fingerprint,
result: {
agentDir,
wrote: wrotePluginCatalog
}
};
}
if (plan.action === "noop") {
const wrotePluginCatalog = writePluginCatalogsForModelsJson({
agentDir,
pluginCatalogWrites: plan.pluginCatalogWrites
});
await ensureModelsFileModeForModelsJson(targetPath);
return {
fingerprint,
result: {
agentDir,
wrote: wrotePluginCatalog
}
};
}
await fs.mkdir(agentDir, {
recursive: true,
mode: 448
});
const wroteRoot = existingModelsFile.raw !== plan.contents;
if (wroteRoot) await writeModelsFileAtomicForModelsJson(targetPath, plan.contents);
await ensureModelsFileModeForModelsJson(targetPath);
const wrotePluginCatalog = writePluginCatalogsForModelsJson({
agentDir,
pluginCatalogWrites: plan.pluginCatalogWrites
});
return {
fingerprint,
result: {
agentDir,
wrote: wroteRoot || wrotePluginCatalog
}
};
});
MODELS_JSON_STATE.readyCache.set(cacheKey, pending);
try {
const settled = await pending;
const refreshedFingerprint = await buildModelsJsonFingerprint(context);
const refreshedCacheKey = modelsJsonReadyCacheKey(targetPath, refreshedFingerprint);
if (refreshedCacheKey !== cacheKey) {
MODELS_JSON_STATE.readyCache.delete(cacheKey);
MODELS_JSON_STATE.readyCache.set(refreshedCacheKey, Promise.resolve({
fingerprint: refreshedFingerprint,
result: settled.result
}));
}
return {
...settled.result,
fingerprint: refreshedFingerprint,
...workspaceDir ? { workspaceDir } : {}
};
} catch (error) {
if (MODELS_JSON_STATE.readyCache.get(cacheKey) === pending) MODELS_JSON_STATE.readyCache.delete(cacheKey);
throw error;
}
}
/**
* Plans the complete root/plugin catalog generation without mutating agent-owned state.
* Control-plane inventory reads use this when their lifecycle generation may be superseded.
*/
async function planOpenClawModelsJsonSource(config, agentDirOverride, options = {}) {
const context = prepareModelsConfigContext(config, agentDirOverride, options);
const { agentDir, pluginMetadataSnapshot } = context;
const existingModelsFile = await readExistingModelsFile(path.join(agentDir, "models.json"));
const existingPluginCatalogs = loadPersistedPluginModelCatalogsReadOnly(agentDir);
const existingParsedForMerge = await mergeGeneratedPluginCatalogProvidersIntoExistingParsed({
agentDir,
existingParsed: existingModelsFile.parsed,
pluginCatalogs: existingPluginCatalogs,
...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}
});
const plan = await planOpenClawModelsJson({
context,
...options.authStore ? { authStore: options.authStore } : {},
existingRaw: existingModelsFile.raw,
existingParsed: existingParsedForMerge
});
return {
agentDir,
modelsJsonContents: plan.action === "write" ? plan.contents : existingModelsFile.raw || null,
pluginCatalogs: plan.pluginCatalogWrites === void 0 ? existingPluginCatalogs : materializePlannedPluginCatalogs(plan.pluginCatalogWrites)
};
}
/** Ensures models.json and the agent SQLite catalog cache are current. */
async function ensureOpenClawModelsJson(config, agentDirOverride, options = {}) {
const prepared = await prepareOpenClawModelsJsonSource(config, agentDirOverride, options);
return {
agentDir: prepared.agentDir,
wrote: prepared.wrote
};
}
//#endregion
export { resolveImplicitProviderDiscoveryScope as i, planOpenClawModelsJsonSource as n, prepareImplicitProviderStaticCatalog as r, ensureOpenClawModelsJson as t };