openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
680 lines (679 loc) • 29.7 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs";
import "./src-SskUVPJG.mjs";
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { t as stableStringify } from "./stable-stringify-CkQ0IEj1.mjs";
import { t as appendConfigPathSegment } from "./dot-path-CVD5Jolm.mjs";
import "./utils-DUAwk2p5.mjs";
import { o as createConfigRuntimeEnv, r as cloneEnvWithPlatformSemantics } from "./config-env-vars-oFV7TqIg.mjs";
import { r as normalizeProviderId } from "./provider-id-BIyeXQag.mjs";
import { d as resolveAgentWorkspaceDir, h as resolveDefaultAgentDir, p as resolveAmbientOwnerAgentId } from "./agent-scope-config-BU4qc9bA.mjs";
import { f as resolveConfigSecretRef } from "./resolution-facts-CYe39-eJ.mjs";
import { a as resolveInstalledManifestRegistryIndexFingerprint } from "./plugin-control-plane-context-BhsvNWYe.mjs";
import { u as resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot-D_ni58BI.mjs";
import "./agent-scope-DWB4x52T.mjs";
import { d as hashRuntimeConfigValue, l as getRuntimeConfigSourceSnapshot } from "./runtime-snapshot-CifcQ780.mjs";
import { t as MODEL_APIS } from "./model-config-vocabulary-DKKMQG9F.mjs";
import { n as projectConfigOntoRuntimeSourceSnapshot } from "./runtime-source-projection-CAFQ9dqa.mjs";
import "./types.models-Cp8mc9VC.mjs";
import "./env-vars-B0Mhe0wI.mjs";
import { t as captureRuntimeConfigAsyncReader } from "./io.runtime-FunTy0y1.mjs";
import "./config-B-SAEPyK.mjs";
import { h as resolveAuthProfileDatabasePath } from "./sqlite-EkUBHrKK.mjs";
import { r as resolveNonEnvSecretRefApiKeyMarker } from "./provider-credential-values-Cf7LMsFg.mjs";
import { d as resolveNonEnvSecretRefHeaderValueMarker, u as resolveEnvSecretRefHeaderValueMarker } from "./model-auth-markers-_1hub14I.mjs";
import { n as ensureAuthProfileStore } from "./store-runtime-CGMnCh1h.mjs";
import { S as resolveProviderConfigApiKeyWithPlugin, d as normalizeProviderConfigWithPlugin } from "./provider-runtime-hWkyx6xy.mjs";
import { a as mergeWithExistingProviderSecrets, i as mergeProviders, n as isWritableProviderConfig, o as normalizeProviderMapKeys, t as buildSourceModelFields } from "./models-config.merge-DkFHtHYW.mjs";
import { n as normalizeProviderCatalogModelsForConfig, t as materializeConfiguredProviderCatalogModels } from "./models-config.providers.catalog-CIgrjv6J.mjs";
import { a as loadPersistedPluginModelCatalogsReadOnly, c as replacePersistedPluginModelCatalogs, f as repairPluginModelCatalogTransportMetadata, l as resolvePluginModelCatalogOwnerPluginId, n as encodePluginModelCatalogRelativePath, r as filterGeneratedPluginModelCatalogProviders, t as decodePluginModelCatalogRelativePathPluginId, u as PLUGIN_MODEL_CATALOG_GENERATED_BY } from "./plugin-model-catalog-BnegMABH.mjs";
import { n as resolveImplicitProviders } from "./models-config.providers.implicit-CRHZC0rx.mjs";
import { c as resolveMissingProviderApiKey, i as normalizeResolvedEnvApiKey, n as normalizeConfiguredProviderApiKey, o as resolveApiKeyFromProfiles, r as normalizeHeaderValues } from "./models-config.providers.secret-helpers-D6N-YAt6.mjs";
import { t as privateFileStore } from "./private-file-store-DM0i141q.mjs";
import { t as MODELS_JSON_STATE } from "./models-config-state-TumWTRCu.mjs";
import path from "node:path";
import fs from "node:fs/promises";
import { createHash } from "node:crypto";
//#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",
"google-interactions"
]);
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: `${appendConfigPathSegment("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 = params.sourceProvider.providerConfig.headers;
if (!isRecord(sourceHeaders)) return {};
const markers = {};
for (const [headerName, headerValue] of Object.entries(sourceHeaders)) {
const sourceHeaderRef = resolveConfigSecretRef({
config: params.sourceConfig,
path: appendConfigPathSegment(`${appendConfigPathSegment("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;
const sourceApiKeyMarker = resolveSourceManagedApiKeyMarker({
sourceProvider,
sourceConfig: params.sourceConfigForSecrets
});
if (sourceApiKeyMarker) {
params.secretRefManagedProviders?.add(canonicalProviderKey);
if (nextProvider.apiKey !== sourceApiKeyMarker) nextProvider = {
...nextProvider,
apiKey: sourceApiKeyMarker
};
}
const sourceHeaderMarkers = resolveSourceManagedHeaderMarkers({
sourceProvider,
sourceConfig: params.sourceConfigForSecrets
});
const currentHeaders = isRecord(nextProvider.headers) ? nextProvider.headers : void 0;
if (Object.entries(sourceHeaderMarkers).some(([headerName, marker]) => currentHeaders?.[headerName] !== marker)) nextProvider = {
...nextProvider,
headers: {
...currentHeaders,
...sourceHeaderMarkers
}
};
if (nextProvider === provider) continue;
nextProviders ??= { ...providers };
nextProviders[providerKey] = nextProvider;
}
return nextProviders ?? providers;
}
//#endregion
//#region src/agents/models-config.providers.normalize.ts
/** Normalizes provider settings and resolves current credential sources. */
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: `${appendConfigPathSegment("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);
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
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`]));
}
/** Resolves providers for models.json. */
async function resolveProvidersForModelsJson(params) {
const { context } = params;
const { agentDir, env } = context;
const explicitProviders = stripBlankProviderBaseUrls(materializeConfiguredProviderCatalogModels(context.cfg.models?.providers, { manifestPlugins: context.pluginMetadataSnapshot }) ?? {});
const cfg = context.cfg.models?.providers ? {
...context.cfg,
models: {
...context.cfg.models,
providers: explicitProviders
}
} : context.cfg;
const sourceModelFields = buildSourceModelFields(explicitProviders);
if (cfg.models?.mode === "replace") return mergeProviders({
implicit: {},
explicit: explicitProviders
});
const implicitProviders = await 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
});
}
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 filterWritableProviders(providers) {
const next = Object.fromEntries(Object.entries(providers).filter(([, provider]) => isWritableProviderConfig(provider)));
return Object.keys(next).length === Object.keys(providers).length ? providers : next;
}
/** Recovers only generated providers; manual root declarations never enter this source. */
function collectGeneratedCatalogProviders(params) {
const providers = {};
for (const { pluginId, contents } of params.catalogs) {
let catalog;
try {
catalog = JSON.parse(contents);
} catch {
continue;
}
if (!isRecord(catalog) || !isRecord(catalog.providers)) continue;
Object.assign(providers, filterGeneratedPluginModelCatalogProviders({
catalogPluginId: pluginId,
config: params.context.cfg,
parsedCatalog: catalog,
pluginMetadataSnapshot: params.context.pluginMetadataSnapshot,
providers: catalog.providers
}));
}
return providers;
}
/** Plans root and plugin-owned model catalog writes for the current runtime. */
async function planOpenClawModelsJson(params) {
const { context } = params;
const { cfg, agentDir, env } = context;
const providers = await resolveProvidersForModelsJson({
context,
...params.authStore ? { authStore: params.authStore } : {}
});
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 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,
...providerPolicyManifestRegistry ? { manifestRegistry: providerPolicyManifestRegistry } : {}
}) ?? providers;
const mergedProviders = resolveProvidersForMode({
mode,
existingParsed: { providers: collectGeneratedCatalogProviders({
catalogs: params.pluginCatalogs ?? [],
context
}) },
providers: normalizedProviders,
secretRefManagedProviders
});
const finalizeProviders = (candidateProviders) => {
const normalized = normalizeProviderCatalogModelsForConfig(candidateProviders) ?? candidateProviders;
return filterWritableProviders(enforceSourceManagedProviderSecrets({
providers: normalized,
sourceConfigForSecrets: context.sourceConfigForSecrets,
secretRefManagedProviders
}) ?? normalized);
};
const splitProviders = splitProvidersByPluginOwner({
providers: finalizeProviders(mergedProviders),
pluginMetadataSnapshot: context.pluginMetadataSnapshot
});
const pluginCatalogWrites = buildPluginCatalogWrites(splitProviders.pluginProviders);
const rootProviders = resolveProvidersForMode({
mode,
existingParsed: params.existingParsed,
providers: splitProviders.rootProviders,
secretRefManagedProviders
});
const nextContents = `${JSON.stringify({ providers: finalizeProviders(rootProviders) }, null, 2)}\n`;
if (params.existingRaw === nextContents && Object.keys(pluginCatalogWrites).length === 0) return {
action: "noop",
pluginCatalogWrites
};
return {
action: "write",
contents: nextContents,
pluginCatalogWrites
};
}
//#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.
*/
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(loadPersistedPluginModelCatalogsReadOnly(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(() => {});
}
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 (!runtimeSource) return {
config,
discoveryAuthConfig: config,
sourceConfigForSecrets: config
};
const projected = projectConfigOntoRuntimeSourceSnapshot(config);
return {
config: projected,
discoveryAuthConfig: config,
sourceConfigForSecrets: projected === config ? runtimeSource : projected
};
}
async function prepareModelsConfigContext(config, agentDirOverride, options = {}) {
let ambientEnv = process.env;
let capturedOptions = options;
let resolved;
if (config) resolved = resolveModelsConfigInput(config);
else {
capturedOptions = {
...options,
...options.env ? { env: cloneEnvWithPlatformSemantics(options.env) } : {},
...options.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: [...options.providerDiscoveryProviderIds] } : {}
};
const captured = await captureRuntimeConfigAsyncReader({ capture: true })();
ambientEnv = captured.env;
const source = projectConfigOntoRuntimeSourceSnapshot(captured.config);
resolved = {
config: source,
discoveryAuthConfig: captured.config,
sourceConfigForSecrets: source
};
}
const cfg = resolved.config;
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveDefaultAgentDir(cfg, ambientEnv);
const workspaceDir = capturedOptions.workspaceDir ?? (agentDirOverride?.trim() ? void 0 : resolveAgentWorkspaceDir(cfg, resolveAmbientOwnerAgentId(cfg), ambientEnv));
const fingerprintEnv = createConfigRuntimeEnv(cfg, capturedOptions.env ?? {});
const env = capturedOptions.env ? fingerprintEnv : createConfigRuntimeEnv(cfg, ambientEnv);
const providerScopedDiscovery = Boolean(capturedOptions.providerDiscoveryProviderIds?.length);
const pluginMetadataSnapshot = capturedOptions.pluginMetadataSnapshot ?? resolvePluginMetadataSnapshot({
config: cfg,
env,
...workspaceDir ? { workspaceDir } : {},
...providerScopedDiscovery ? { preferPersisted: false } : {}
});
return {
cfg,
discoveryAuthConfig: resolved.discoveryAuthConfig,
discoveryAuthEnv: capturedOptions.env ?? ambientEnv,
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
agentDir,
env,
envFingerprint: capturedOptions.env ? hashRuntimeConfigValue(fingerprintEnv) : fingerprintEnv,
...workspaceDir ? { workspaceDir } : {},
...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {},
...capturedOptions.preparedStaticProviderCatalog ? { preparedStaticProviderCatalog: capturedOptions.preparedStaticProviderCatalog } : {},
...capturedOptions.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: capturedOptions.providerDiscoveryProviderIds } : {},
...capturedOptions.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: capturedOptions.providerDiscoveryTimeoutMs } : {},
...capturedOptions.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {},
...capturedOptions.onProviderCatalogOutcome ? { onProviderCatalogOutcome: capturedOptions.onProviderCatalogOutcome } : {}
};
}
/** Ensures models.json and the agent SQLite catalog cache are current. */
async function ensureOpenClawModelsJson(config, agentDirOverride, options = {}) {
const context = await prepareModelsConfigContext(config, agentDirOverride, options);
const { agentDir } = context;
const targetPath = path.join(agentDir, "models.json");
const cacheKey = modelsJsonReadyCacheKey(targetPath, await buildModelsJsonFingerprint(context));
const cached = MODELS_JSON_STATE.readyCache.get(cacheKey);
if (cached && !context.onProviderCatalogOutcome) {
const settled = await cached;
await ensureModelsFileModeForModelsJson(targetPath);
return { ...settled };
}
const pending = MODELS_JSON_STATE.writeQueue.enqueue(targetPath, async () => {
const existingModelsFile = await readExistingModelsFile(targetPath);
const plan = await planOpenClawModelsJson({
context,
existingRaw: existingModelsFile.raw,
existingParsed: existingModelsFile.parsed,
pluginCatalogs: loadPersistedPluginModelCatalogsReadOnly(agentDir)
});
if (plan.action !== "write") {
const wrotePluginCatalog = writePluginCatalogsForModelsJson({
agentDir,
pluginCatalogWrites: plan.pluginCatalogWrites
});
if (plan.action === "noop") await ensureModelsFileModeForModelsJson(targetPath);
return {
agentDir,
wrote: wrotePluginCatalog
};
}
await fs.mkdir(agentDir, {
recursive: true,
mode: 448
});
const wroteRoot = existingModelsFile.raw !== plan.contents;
if (wroteRoot) {
await privateFileStore(path.dirname(targetPath)).writeText("models.json", plan.contents);
MODELS_JSON_STATE.costCache.delete(agentDir);
}
await ensureModelsFileModeForModelsJson(targetPath);
const wrotePluginCatalog = writePluginCatalogsForModelsJson({
agentDir,
pluginCatalogWrites: plan.pluginCatalogWrites
});
return {
agentDir,
wrote: wroteRoot || wrotePluginCatalog
};
});
MODELS_JSON_STATE.readyCache.set(cacheKey, pending);
try {
const settled = await pending;
const refreshedCacheKey = modelsJsonReadyCacheKey(targetPath, await buildModelsJsonFingerprint(context));
if (refreshedCacheKey !== cacheKey) {
MODELS_JSON_STATE.readyCache.delete(cacheKey);
MODELS_JSON_STATE.readyCache.set(refreshedCacheKey, Promise.resolve(settled));
}
return { ...settled };
} 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 { authStore } = options;
const context = await prepareModelsConfigContext(config, agentDirOverride, options);
const { agentDir } = context;
const existingModelsFile = await readExistingModelsFile(path.join(agentDir, "models.json"));
const existingPluginCatalogs = loadPersistedPluginModelCatalogsReadOnly(agentDir);
const plan = await planOpenClawModelsJson({
context,
...authStore ? { authStore } : {},
existingRaw: existingModelsFile.raw,
existingParsed: existingModelsFile.parsed,
pluginCatalogs: existingPluginCatalogs
});
return {
agentDir,
modelsJsonContents: plan.action === "write" ? plan.contents : existingModelsFile.raw || null,
pluginCatalogs: plan.pluginCatalogWrites === void 0 ? existingPluginCatalogs : materializePlannedPluginCatalogs(plan.pluginCatalogWrites)
};
}
//#endregion
export { planOpenClawModelsJsonSource as n, ensureOpenClawModelsJson as t };