UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,253 lines 55.8 kB
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 };