UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,310 lines 53.7 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { _ as uniqueStrings, l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js";
import { c as isRecord } from "./utils-CCC-BEJH.js";
import { r as createConfigRuntimeEnv } from "./config-env-vars-DeC7to2c.js";
import { m as resolveSecretInputRef } from "./types.secrets-_0JOMGE5.js";
import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js";
import { i as normalizeProviderId, n as findNormalizedProviderValue } from "./provider-id-Dq06Bcx6.js";
import "./agent-scope-MrLta7Pq.js";
import { c as resolveDefaultAgentId, o as resolveAgentWorkspaceDir, s as resolveDefaultAgentDir } from "./agent-scope-config-CgCYpZfK.js";
import { n as resolveInstalledManifestRegistryIndexFingerprint } from "./manifest-registry-installed-DSyeeqEt.js";
import { t as privateFileStore } from "./private-file-store-BAvApZYp.js";
import { a as resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot-Ctk9Oarq.js";
import { i as getRuntimeConfig, s as projectConfigOntoRuntimeSourceSnapshot } from "./io-Gi7-pyU-.js";
import { t as MODEL_APIS } from "./types.models-C6-aKREc.js";
import { s as getRuntimeConfigSourceSnapshot } from "./runtime-snapshot-D93_HOsR.js";
import "./config-C9RxTsn1.js";
import { n as ensureAuthProfileStore } from "./store-C8spD0DG.js";
import { g as resolveOwningPluginIdsForProviderRef } from "./plugin-auto-enable-pAtqig-B.js";
import { T as resolveProviderConfigApiKeyWithPlugin, n as applyProviderNativeStreamingUsageCompatWithPlugin, p as normalizeProviderConfigWithPlugin } from "./provider-runtime-CJtW0nDR.js";
import { a as resolveAuthProfileDatabasePath } from "./sqlite-nSLfAcoh.js";
import { f as parseConfiguredModelVisibilityEntries } from "./model-selection-shared-b32cUYts.js";
import { r as normalizeConfiguredProviderCatalogModelId } from "./model-ref-shared-CqcindZs.js";
import { _ as resolveNonEnvSecretRefHeaderValueMarker, d as isNonSecretApiKeyMarker, g as resolveNonEnvSecretRefApiKeyMarker, h as resolveEnvSecretRefHeaderValueMarker } from "./model-auth-markers-Sq8HdQFX.js";
import { a as runProviderStaticCatalog, i as runProviderCatalog, n as normalizePluginDiscoveryResult, r as resolveRuntimePluginDiscoveryProviders, t as groupPluginDiscoveryProvidersByOrder } from "./provider-discovery-Cuk49qKB.js";
import { a as normalizeConfiguredProviderApiKey, f as resolveMissingProviderApiKey, l as resolveApiKeyFromProfiles, n as createProviderAuthResolver, o as normalizeHeaderValues, s as normalizeResolvedEnvApiKey, t as createProviderApiKeyResolver } from "./models-config.providers.secrets-CK7mTB4U.js";
import { a as isGeneratedPluginModelCatalog, c as listPluginModelCatalogRelativePaths, l as resolvePluginModelCatalogOwnerPluginId, n as decodePluginModelCatalogRelativePathPluginId, o as isPluginModelCatalogRelativePath, r as encodePluginModelCatalogRelativePath, t as PLUGIN_MODEL_CATALOG_GENERATED_BY } from "./plugin-model-catalog-C26wDCJp.js";
import { t as stableStringify } from "./stable-stringify-BL8fDhrH.js";
import path from "node:path";
import fs from "node:fs/promises";
//#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] = {
		writeLocks: /* @__PURE__ */ new Map(),
		readyCache: /* @__PURE__ */ new Map()
	};
	return globalState[MODELS_JSON_STATE_KEY];
})();
//#endregion
//#region src/agents/models-config.merge.ts
/**
* Merges generated model-provider config with explicit user config and
* preserved secret fields. Setup and doctor flows use this boundary to update
* model catalogs without discarding existing credentials.
*/
function isPositiveFiniteTokenLimit(value) {
	return typeof value === "number" && Number.isFinite(value) && value > 0;
}
function resolvePreferredTokenLimit(params) {
	if (params.explicitPresent && isPositiveFiniteTokenLimit(params.explicitValue)) return params.explicitValue;
	if (isPositiveFiniteTokenLimit(params.implicitValue)) return params.implicitValue;
	return isPositiveFiniteTokenLimit(params.explicitValue) ? params.explicitValue : void 0;
}
function getProviderModelId$1(model) {
	if (!model || typeof model !== "object") return "";
	const id = model.id;
	return normalizeOptionalString(id) ?? "";
}
/** Merges implicit provider models with explicit config while preserving explicit fields. */
function mergeProviderModels(implicit, explicit) {
	const implicitModels = Array.isArray(implicit.models) ? implicit.models : [];
	const explicitModels = Array.isArray(explicit.models) ? explicit.models : [];
	const implicitHeaders = implicit.headers && typeof implicit.headers === "object" && !Array.isArray(implicit.headers) ? implicit.headers : void 0;
	const explicitHeaders = explicit.headers && typeof explicit.headers === "object" && !Array.isArray(explicit.headers) ? explicit.headers : void 0;
	if (implicitModels.length === 0) return {
		...implicit,
		...explicit,
		...implicitHeaders || explicitHeaders ? { headers: {
			...implicitHeaders,
			...explicitHeaders
		} } : {}
	};
	const implicitById = new Map(implicitModels.map((model) => [getProviderModelId$1(model), model]).filter(([id]) => Boolean(id)));
	const seen = /* @__PURE__ */ new Set();
	const mergedModels = explicitModels.map((explicitModel) => {
		const id = getProviderModelId$1(explicitModel);
		if (!id) return explicitModel;
		seen.add(id);
		const implicitModel = implicitById.get(id);
		if (!implicitModel) return explicitModel;
		const contextWindow = resolvePreferredTokenLimit({
			explicitPresent: "contextWindow" in explicitModel,
			explicitValue: explicitModel.contextWindow,
			implicitValue: implicitModel.contextWindow
		});
		const contextTokens = resolvePreferredTokenLimit({
			explicitPresent: "contextTokens" in explicitModel,
			explicitValue: explicitModel.contextTokens,
			implicitValue: implicitModel.contextTokens
		});
		const maxTokens = resolvePreferredTokenLimit({
			explicitPresent: "maxTokens" in explicitModel,
			explicitValue: explicitModel.maxTokens,
			implicitValue: implicitModel.maxTokens
		});
		return Object.assign({}, explicitModel, {
			input: "input" in explicitModel ? explicitModel.input : implicitModel.input,
			reasoning: `reasoning` in explicitModel ? explicitModel.reasoning : implicitModel.reasoning
		}, contextWindow === void 0 ? {} : { contextWindow }, contextTokens === void 0 ? {} : { contextTokens }, maxTokens === void 0 ? {} : { maxTokens });
	});
	for (const implicitModel of implicitModels) {
		const id = getProviderModelId$1(implicitModel);
		if (!id || seen.has(id)) continue;
		seen.add(id);
		mergedModels.push(implicitModel);
	}
	return {
		...implicit,
		...explicit,
		...implicitHeaders || explicitHeaders ? { headers: {
			...implicitHeaders,
			...explicitHeaders
		} } : {},
		models: mergedModels
	};
}
/** Merges implicit and explicit provider config maps by provider id. */
function mergeProviders(params) {
	const out = params.implicit ? { ...params.implicit } : {};
	for (const [key, explicit] of Object.entries(params.explicit ?? {})) {
		const providerKey = normalizeOptionalString(key) ?? "";
		if (!providerKey) continue;
		const implicit = out[providerKey];
		out[providerKey] = implicit ? mergeProviderModels(implicit, explicit) : explicit;
	}
	return out;
}
function resolveProviderApi(entry) {
	return normalizeOptionalString(entry?.api);
}
function resolveModelApiSurface(entry) {
	if (!Array.isArray(entry?.models)) return;
	const apis = entry.models.flatMap((model) => {
		if (!model || typeof model !== "object") return [];
		const api = model.api;
		const normalized = normalizeOptionalString(api);
		return normalized ? [normalized] : [];
	}).toSorted();
	return apis.length > 0 ? JSON.stringify(apis) : void 0;
}
function resolveProviderApiSurface(entry) {
	return resolveProviderApi(entry) ?? resolveModelApiSurface(entry);
}
function shouldPreserveExistingApiKey(params) {
	const { providerKey, existing, nextEntry, secretRefManagedProviders } = params;
	const nextApiKey = typeof nextEntry.apiKey === "string" ? nextEntry.apiKey : "";
	if (nextApiKey && isNonSecretApiKeyMarker(nextApiKey)) return false;
	return !secretRefManagedProviders.has(providerKey) && typeof existing.apiKey === "string" && existing.apiKey.length > 0 && !isNonSecretApiKeyMarker(existing.apiKey, { includeEnvVarName: false });
}
function shouldPreserveExistingBaseUrl(params) {
	const { existing, nextEntry } = params;
	if (typeof existing.baseUrl !== "string" || existing.baseUrl.length === 0) return false;
	const existingApi = resolveProviderApiSurface(existing);
	const nextApi = resolveProviderApiSurface(nextEntry);
	return !existingApi || !nextApi || existingApi === nextApi;
}
function isExistingProviderSelfContained(entry) {
	if (!Array.isArray(entry.models) || entry.models.length === 0) return true;
	return Boolean(entry.baseUrl?.trim() && entry.apiKey);
}
/** Merges generated provider config with existing secrets safe to preserve. */
function mergeWithExistingProviderSecrets(params) {
	const { nextProviders, existingProviders, secretRefManagedProviders } = params;
	const mergedProviders = {};
	for (const [key, entry] of Object.entries(existingProviders)) {
		if (!isExistingProviderSelfContained(entry)) continue;
		mergedProviders[key] = entry;
	}
	for (const [key, newEntry] of Object.entries(nextProviders)) {
		const existing = existingProviders[key];
		if (!existing) {
			mergedProviders[key] = newEntry;
			continue;
		}
		const preserved = {};
		if (shouldPreserveExistingApiKey({
			providerKey: key,
			existing,
			nextEntry: newEntry,
			secretRefManagedProviders
		})) preserved.apiKey = existing.apiKey;
		if (shouldPreserveExistingBaseUrl({
			existing,
			nextEntry: newEntry
		})) preserved.baseUrl = existing.baseUrl;
		mergedProviders[key] = {
			...newEntry,
			...preserved
		};
	}
	return mergedProviders;
}
//#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 resolveProviderDiscoveryFilter(params) {
	const { config, workspaceDir, env } = params;
	const testRaw = env.OPENCLAW_TEST_ONLY_PROVIDER_PLUGIN_IDS?.trim();
	if (testRaw) {
		const ids = normalizeStringEntries(testRaw.split(","));
		return ids.length > 0 ? uniqueStrings(ids) : void 0;
	}
	const scopedProviderIds = params.providerIds ? normalizeStringEntries([...params.providerIds]) : void 0;
	if (scopedProviderIds) return resolveProviderPluginScopeFromProviderIds({
		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(",")));
	if (ids.length === 0) return;
	return resolveProviderPluginScopeFromProviderIds({
		providerIds: ids,
		config,
		workspaceDir,
		env,
		resolveOwners: params.resolveOwners
	});
}
function resolveProviderPluginScopeFromProviderIds(params) {
	const pluginIds = /* @__PURE__ */ new Set();
	for (const id of params.providerIds) {
		const owners = params.resolveOwners?.(id) ?? resolveOwningPluginIdsForProviderRef({
			provider: id,
			config: params.config,
			workspaceDir: params.workspaceDir,
			env: params.env
		}) ?? [];
		if (owners.length > 0) {
			for (const owner of owners) pluginIds.add(owner);
			continue;
		}
		pluginIds.add(id);
	}
	return [...pluginIds].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 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
	});
	if (params.dynamicProviderModels) return mergeProviderModels(implicit, existing);
	return {
		...implicit,
		...existing,
		models: Array.isArray(existing.models) && existing.models.length > 0 ? existing.models : implicit.models
	};
}
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" || typeof provider.discovery?.run === "function";
}
async function resolvePluginImplicitProviders(ctx, providers, order) {
	const byOrder = groupPluginDiscoveryProvidersByOrder(providers);
	const discovered = {};
	const catalogConfig = buildPluginCatalogConfig(ctx);
	for (const provider of byOrder[order]) {
		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
			};
		};
		const useStaticCatalog = Boolean(provider.staticCatalog) && (ctx.providerDiscoveryEntriesOnly === true || !hasRuntimeProviderCatalog(provider));
		let result = useStaticCatalog ? await runProviderStaticCatalog({
			provider,
			config: catalogConfig,
			agentDir: ctx.agentDir,
			workspaceDir: ctx.workspaceDir,
			env: ctx.env
		}) : await runProviderCatalogWithTimeout({
			provider,
			config: catalogConfig,
			agentDir: ctx.agentDir,
			workspaceDir: ctx.workspaceDir,
			env: ctx.env,
			resolveProviderApiKey: resolveCatalogProviderApiKey,
			resolveProviderAuth: (providerId, options) => ctx.resolveProviderAuth(providerId?.trim() || provider.id, options),
			timeoutMs: ctx.providerDiscoveryTimeoutMs ?? resolveLiveProviderCatalogTimeoutMs(ctx.env)
		});
		if (!result && !useStaticCatalog && provider.staticCatalog) result = await runProviderStaticCatalog({
			provider,
			config: catalogConfig,
			agentDir: ctx.agentDir,
			workspaceDir: ctx.workspaceDir,
			env: ctx.env
		});
		if (!result) continue;
		const normalizedResult = normalizePluginDiscoveryResult({
			provider,
			result
		});
		for (const [providerId, implicitProvider] of Object.entries(normalizedResult)) 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
				})
			})
		});
	}
	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 catalogRun = runProviderCatalog(params);
	const timeoutMs = params.timeoutMs ?? void 0;
	if (!timeoutMs) return await catalogRun;
	let timer;
	try {
		return await Promise.race([catalogRun, new Promise((_, reject) => {
			timer = setTimeout(() => {
				reject(/* @__PURE__ */ new Error(`provider catalog timed out after ${timeoutMs}ms: ${params.provider.id}`));
			}, timeoutMs);
			timer.unref?.();
		})]);
	} catch (error) {
		const message = formatErrorMessage(error);
		if (message.includes("provider catalog timed out after")) {
			log.warn(`${message}; skipping provider discovery`);
			return;
		}
		throw error;
	} finally {
		if (timer) clearTimeout(timer);
	}
}
/** Resolve all implicit provider configs contributed by runtime plugin discovery. */
async function resolveImplicitProviders(params) {
	const providers = {};
	const env = params.env ?? process.env;
	let authStore;
	const getAuthStore = () => authStore ??= ensureAuthProfileStore(params.agentDir, {
		allowKeychainPrompt: false,
		externalCliProviderIds: params.providerDiscoveryProviderIds
	});
	const context = {
		...params,
		get authStore() {
			return getAuthStore();
		},
		env,
		resolveProviderApiKey: createProviderApiKeyResolver(env, getAuthStore, params.config),
		resolveProviderAuth: createProviderAuthResolver(env, getAuthStore, params.config)
	};
	const discoveryProviders = await resolveRuntimePluginDiscoveryProviders({
		config: params.config,
		workspaceDir: params.workspaceDir,
		env,
		onlyPluginIds: resolveProviderDiscoveryFilter({
			config: params.config,
			workspaceDir: params.workspaceDir,
			env,
			resolveOwners: params.pluginMetadataSnapshot ? (provider) => resolvePluginMetadataProviderOwners(params.pluginMetadataSnapshot, provider) : void 0,
			providerIds: params.providerDiscoveryProviderIds
		}),
		...params.pluginMetadataSnapshot ? { pluginMetadataSnapshot: params.pluginMetadataSnapshot } : {},
		...params.providerDiscoveryEntriesOnly === true ? { discoveryEntriesOnly: true } : {}
	});
	for (const order of PLUGIN_DISCOVERY_ORDERS) mergeImplicitProviderSet(providers, await resolvePluginImplicitProviders(context, discoveryProviders, order));
	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 = 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.runtime.ts
/**
* Runtime-policy bridge for provider config normalization. These helpers call
* plugin hooks without triggering runtime plugin loading from config assembly.
*/
/** Apply provider native-streaming usage compatibility policy. */
function applyProviderNativeStreamingUsagePolicy(providerKey, provider) {
	return applyProviderNativeStreamingUsageCompatWithPlugin({
		provider: resolveProviderPluginLookupKey(providerKey, provider),
		allowRuntimePluginLoad: false,
		context: {
			provider: providerKey,
			providerConfig: provider
		}
	}) ?? provider;
}
/** Normalize provider config through any already-available plugin policy hook. */
function normalizeProviderConfigPolicy(providerKey, provider) {
	return normalizeProviderConfigWithPlugin({
		provider: resolveProviderPluginLookupKey(providerKey, provider),
		allowRuntimePluginLoad: false,
		context: {
			provider: providerKey,
			providerConfig: provider
		}
	}) ?? provider;
}
/** Resolve a provider API-key policy function from already-available plugin hooks. */
function resolveProviderConfigApiKeyPolicy(providerKey, provider) {
	const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider).trim();
	return (env) => resolveProviderConfigApiKeyWithPlugin({
		provider: runtimeProviderKey,
		allowRuntimePluginLoad: false,
		context: {
			provider: providerKey,
			env
		}
	});
}
//#endregion
//#region src/agents/models-config.providers.policy.ts
/**
* Applies provider plugin policy to configured model provider settings.
*/
/**
* Provider-specific config policy adapters.
*
* Runtime policy rules live in the sibling runtime module; this file exposes the
* small stable API used by models-config loading and tests.
*/
/** Applies native-streaming usage compatibility policy to the provider map. */
function applyNativeStreamingUsageCompat(providers) {
	let changed = false;
	const nextProviders = {};
	for (const [providerKey, provider] of Object.entries(providers)) {
		const nextProvider = applyProviderNativeStreamingUsagePolicy(providerKey, provider);
		nextProviders[providerKey] = nextProvider;
		changed ||= nextProvider !== provider;
	}
	return changed ? nextProviders : providers;
}
/** Normalizes a provider config according to provider-specific runtime policy. */
function normalizeProviderSpecificConfig(providerKey, provider) {
	const normalized = normalizeProviderConfigPolicy(providerKey, provider);
	if (normalized && normalized !== provider) return normalized;
	return provider;
}
/** Resolves a provider-specific API key env lookup policy when one exists. */
function resolveProviderConfigApiKeyResolver(providerKey, provider) {
	return resolveProviderConfigApiKeyPolicy(providerKey, provider);
}
//#endregion
//#region src/agents/models-config.providers.source-managed.ts
function normalizeSourceProviderLookup(providers) {
	if (!providers) return {};
	const out = {};
	for (const [key, provider] of Object.entries(providers)) {
		const normalizedKey = key.trim();
		if (!normalizedKey || !isRecord(provider)) continue;
		out[normalizedKey] = provider;
	}
	return out;
}
function resolveSourceManagedApiKeyMarker(params) {
	const sourceApiKeyRef = resolveSecretInputRef({
		value: params.sourceProvider?.apiKey,
		defaults: params.sourceSecretDefaults
	}).ref;
	if (!sourceApiKeyRef || !sourceApiKeyRef.id.trim()) return;
	return sourceApiKeyRef.source === "env" ? sourceApiKeyRef.id.trim() : resolveNonEnvSecretRefApiKeyMarker(sourceApiKeyRef.source);
}
function resolveSourceManagedHeaderMarkers(params) {
	const sourceHeaders = isRecord(params.sourceProvider?.headers) ? params.sourceProvider.headers : void 0;
	if (!sourceHeaders) return {};
	const markers = {};
	for (const [headerName, headerValue] of Object.entries(sourceHeaders)) {
		const sourceHeaderRef = resolveSecretInputRef({
			value: headerValue,
			defaults: params.sourceSecretDefaults
		}).ref;
		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.sourceProviders);
	if (Object.keys(sourceProvidersByKey).length === 0) return providers;
	let nextProviders = null;
	for (const [providerKey, provider] of Object.entries(providers)) {
		if (!isRecord(provider)) continue;
		const sourceProvider = sourceProvidersByKey[providerKey.trim()];
		if (!sourceProvider) continue;
		let nextProvider = provider;
		let providerMutated = false;
		const sourceApiKeyMarker = resolveSourceManagedApiKeyMarker({
			sourceProvider,
			sourceSecretDefaults: params.sourceSecretDefaults
		});
		if (sourceApiKeyMarker) {
			params.secretRefManagedProviders?.add(providerKey.trim());
			if (nextProvider.apiKey !== sourceApiKeyMarker) {
				providerMutated = true;
				nextProvider = {
					...nextProvider,
					apiKey: sourceApiKeyMarker
				};
			}
		}
		const sourceHeaderMarkers = resolveSourceManagedHeaderMarkers({
			sourceProvider,
			sourceSecretDefaults: params.sourceSecretDefaults
		});
		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
function getProviderModelId(model) {
	return typeof model.id === "string" && model.id.trim() ? model.id : void 0;
}
function mergeNormalizedProviderModel(existing, incoming) {
	return {
		...incoming,
		...existing,
		...existing.cost || incoming.cost ? { cost: {
			...incoming.cost,
			...existing.cost
		} } : void 0
	};
}
function normalizeProviderModelsForConfig(providerKey, provider, options = {}) {
	if (!Array.isArray(provider.models) || provider.models.length === 0) return {
		provider,
		mutated: false
	};
	let mutated = false;
	const nextModels = [];
	const seenById = /* @__PURE__ */ new Map();
	for (const model of provider.models) {
		const rawId = getProviderModelId(model);
		const normalizedId = rawId ? normalizeConfiguredProviderCatalogModelId(providerKey, rawId, { manifestPlugins: options.manifestPlugins }) : 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;
				nextModels[existingIndex] = mergeNormalizedProviderModel(nextModels[existingIndex], normalizedModel);
				continue;
			}
			seenById.set(id, nextModels.length);
		}
		nextModels.push(normalizedModel);
	}
	return mutated ? {
		provider: {
			...provider,
			models: nextModels
		},
		mutated
	} : {
		provider,
		mutated
	};
}
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);
		if (normalized.mutated) mutated = true;
		next[providerKey] = normalized.provider;
	}
	return mutated ? next : providers;
}
function normalizeProviders(params) {
	const { providers } = params;
	if (!providers) return providers;
	const env = params.env ?? process.env;
	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;
		let normalizedProvider = provider;
		const normalizedHeaders = normalizeHeaderValues({
			headers: normalizedProvider.headers,
			secretDefaults: params.secretDefaults
		});
		if (normalizedHeaders.mutated) {
			mutated = true;
			normalizedProvider = {
				...normalizedProvider,
				headers: normalizedHeaders.headers
			};
		}
		const providerWithConfiguredApiKey = normalizeConfiguredProviderApiKey({
			providerKey: normalizedKey,
			provider: normalizedProvider,
			secretDefaults: params.secretDefaults,
			profileApiKey: void 0,
			secretRefManagedProviders: params.secretRefManagedProviders
		});
		if (providerWithConfiguredApiKey !== normalizedProvider) {
			mutated = true;
			normalizedProvider = providerWithConfiguredApiKey;
		}
		const providerWithResolvedEnvApiKey = normalizeResolvedEnvApiKey({
			providerKey: normalizedKey,
			provider: normalizedProvider,
			env,
			secretRefManagedProviders: params.secretRefManagedProviders
		});
		if (providerWithResolvedEnvApiKey !== normalizedProvider) {
			mutated = true;
			normalizedProvider = providerWithResolvedEnvApiKey;
		}
		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;
		const providerWithApiKey = resolveMissingProviderApiKey({
			providerKey: normalizedKey,
			provider: normalizedProvider,
			env,
			profileApiKey,
			secretRefManagedProviders: params.secretRefManagedProviders,
			providerApiKeyResolver
		});
		if (providerWithApiKey !== normalizedProvider) {
			mutated = true;
			normalizedProvider = providerWithApiKey;
		}
		const providerSpecificNormalized = normalizeProviderSpecificConfig(normalizedKey, normalizedProvider);
		if (providerSpecificNormalized !== normalizedProvider) {
			mutated = true;
			normalizedProvider = providerSpecificNormalized;
		}
		const providerWithNormalizedModels = normalizeProviderModelsForConfig(normalizedKey, normalizedProvider, { manifestPlugins: params.manifestPlugins });
		if (providerWithNormalizedModels.mutated) {
			mutated = true;
			normalizedProvider = providerWithNormalizedModels.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,
		sourceProviders: params.sourceProviders,
		sourceSecretDefaults: params.sourceSecretDefaults,
		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 with injectable implicit-provider discovery. */
async function resolveProvidersForModelsJsonWithDeps(params, deps) {
	const { agentDir, env } = params;
	const explicitProviders = stripBlankProviderBaseUrls(params.cfg.models?.providers ?? {});
	const cfg = params.cfg.models?.providers ? {
		...params.cfg,
		models: {
			...params.cfg.models,
			providers: explicitProviders
		}
	} : params.cfg;
	return mergeProviders({
		implicit: await (deps?.resolveImplicitProviders ?? resolveImplicitProviders)({
			agentDir,
			config: cfg,
			env,
			...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {},
			explicitProviders,
			...params.pluginMetadataSnapshot ? { pluginMetadataSnapshot: params.pluginMetadataSnapshot } : {},
			...params.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: params.providerDiscoveryProviderIds } : {},
			...params.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs } : {},
			...params.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {}
		}),
		explicit: explicitProviders
	});
}
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);
}
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 { cfg, agentDir, env } = params;
	const providers = await resolveProvidersForModelsJsonWithDeps({
		cfg,
		agentDir,
		env,
		...params.workspaceDir ? { workspaceDir: params.workspaceDir } : {},
		...params.pluginMetadataSnapshot ? { pluginMetadataSnapshot: params.pluginMetadataSnapshot } : {},
		...params.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: params.providerDiscoveryProviderIds } : {},
		...params.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs } : {},
		...params.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {}
	}, deps);
	if (Object.keys(providers).length === 0) {
		if (params.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 = params.pluginMetadataSnapshot?.manifestRegistry.plugins;
	const normalizedProviders = normalizeProviders({
		providers,
		agentDir,
		env,
		secretDefaults: cfg.secrets?.defaults,
		sourceProviders: params.sourceConfigForSecrets?.models?.providers,
		sourceSecretDefaults: params.sourceConfigForSecrets?.secrets?.defaults,
		secretRefManagedProviders,
		manifestPlugins
	}) ?? providers;
	const mergedProviders = resolveProvidersForMode({
		mode,
		existingParsed: params.existingParsed,
		providers: normalizedProviders,
		secretRefManagedProviders
	});
	const normalizedMergedProviders = normalizeProviderCatalogModelsForConfig(mergedProviders, { manifestPlugins }) ?? mergedProviders;
	const splitProviders = splitProvidersByPluginOwner({
		providers: applyNativeStreamingUsageCompat(filterWritableProviders(enforceSourceManagedProviderSecrets({
			providers: normalizedMergedProviders,
			sourceProviders: params.sourceConfigForSecrets?.models?.providers,
			sourceSecretDefaults: params.sourceConfigForSecrets?.secrets?.defaults,
			secretRefManagedProviders
		}) ?? normalizedMergedProviders)),
		pluginMetadataSnapshot: params.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);
}
//#endregion
//#region src/agents/models-config.ts
/**
* Ensures the agent-local models.json and plugin model catalog sidecars 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 readPluginCatalogMtimes(agentDir) {
	return (await Promise.all(listPluginModelCatalogRelativePaths(agentDir).map(async (relativePath) => {
		return [relativePath, await readFileMtimeMs(path.join(agentDir, relativePath))];
	}))).toSorted(([left], [right]) => left.localeCompare(right));
}
async function buildModelsJsonFingerprint(params) {
	const authProfilesSqlitePath = resolveAuthProfileDatabasePath(params.agentDir);
	const authProfilesMtimeMs = await readFileMtimeMs(authProfilesSqlitePath);
	const authProfilesWalMtimeMs = await readFileMtimeMs(`${authProfilesSqlitePath}-wal`);
	const modelsFileMtimeMs = await readFileMtimeMs(path.join(params.agentDir, "models.json"));
	const pluginCatalogMtimes = await readPluginCatalogMtimes(params.agentDir);
	const envShape = createConfigRuntimeEnv(params.config, {});
	const pluginMetadataSnapshotIndexFingerprint = params.pluginMetadataSnapshot ? resolveInstalledManifestRegistryIndexFingerprint(params.pluginMetadataSnapshot.index) : void 0;
	return stableStringify({
		config: params.config,
		sourceConfigForSecrets: params.sourceConfigForSecrets,
		envShape,
		authProfilesMtimeMs,
		authProfilesWalMtimeMs,
		modelsFileMtimeMs,
		pluginCatalogMtimes,
		workspaceDir: params.workspaceDir,
		pluginMetadataSnapshotIndexFingerprint,
		providerDiscoveryProviderIds: params.providerDiscoveryProviderIds,
		providerDiscoveryTimeoutMs: params.providerDiscoveryTimeoutMs,
		providerDiscoveryEntriesOnly: params.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 generated models.json and plugin catalog files. */
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);
}
async function isGeneratedPluginCatalogFile(targetPath) {
	return await readGeneratedPluginCatalog(targetPath) !== void 0;
}
async function readGeneratedPluginCatalog(targetPath) {
	const parsed = (await readExistingModelsFile(targetPath)).parsed;
	return isGeneratedPluginModelCatalog(parsed) ? parsed : void 0;
}
function isRecordLike(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
async function mergeGeneratedPluginCatalogProvidersIntoExistingParsed(params) {
	const root = isRecordLike(params.existingParsed) ? params.existingParsed : {};
	const providers = isRecordLike(root.providers) ? { ...root.providers } : {};
	let changed = false;
	for (const relativePath of listPluginModelCatalogRelativePaths(params.agentDir)) {
		const catalogPluginId = decodePluginModelCatalogRelativePathPluginId(relativePath);
		if (!catalogPluginId) continue;
		const catalog = await readGeneratedPluginCatalog(path.join(params.agentDir, relativePath));
		if (!isRecordLike(catalog) || !isRecordLike(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
	};
}
async function removeStalePluginCatalogs(params) {
	let wrote = false;
	for (const relativePath of listPluginModelCatalogRelativePaths(params.agentDir)) {
		if (params.activeRelativePaths.has(path.normalize(relativePath))) continue;
		const targetPath = path.join(params.agentDir, relativePath);
		if (!await isGeneratedPluginCatalogFile(targetPath)) continue;
		await fs.unlink(targetPath).catch((error) => {
			if (error.code === "ENOENT") return;
			throw error;
		});
		wrote = true;
	}
	return wrote;
}
async function writePluginCatalogsForModelsJson(params) {
	if (!params.pluginCatalogWrites) return false;
	let wrote = false;
	const activeRelativePaths = /* @__PURE__ */ new Set();
	for (const [relativePath, contents] of Object.entries(params.pluginCatalogWrites)) {
		if (!isPluginModelCatalogRelativePath(relativePath)) continue;
		activeRelativePaths.add(path.normalize(relativePath));
		const targetPath = path.join(params.agentDir, relativePath);
		if ((await readExistingModelsFile(targetPath)).raw === contents) {
			await ensureModelsFileModeForModelsJson(targetPath);
			continue;
		}
		await fs.mkdir(path.dirname(targetPath), {
			recursive: true,
			mode: 448
		});
		await writeModelsFileAtomicForModelsJson(targetPath, contents);
		await ensureModelsFileModeForModelsJson(targetPath);
		wrote = true;
	}
	const removedStale = await removeStalePluginCatalogs({
		agentDir: params.agentDir,
		activeRelativePaths
	});
	return wrote || removedStale;
}
function resolveModelsConfigInput(config) {
	const runtimeSource = getRuntimeConfigSourceSnapshot();
	if (!config) {
		const loaded = getRuntimeConfig();
		return {
			config: runtimeSource ?? loaded,
			sourceConfigForSecrets: runtimeSource ?? loaded
		};
	}
	if (!runtimeSource) return {
		config,
		sourceConfigForSecrets: config
	};
	const projected = projectConfigOntoRuntimeSourceSnapshot(config);
	return {
		config: projected,
		sourceConfigForSecrets: projected === config ? runtimeSource : projected
	};
}
/** Builds the canonical source freshness fingerprint for generated model catalogs. */
async function buildModelsJsonSourceFingerprint(config, agentDirOverride, options = {}) {
	const resolved = resolveModelsConfigInput(config);
	const cfg = resolved.config;
	const workspaceDir = options.workspaceDir ?? (agentDirOverride?.trim() ? void 0 : resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)));
	const providerScopedDiscovery = Boolean(options.providerDiscoveryProviderIds?.length);
	const pluginMetadataSnapshot = options.pluginMetadataSnapshot ?? resolvePluginMetadataSnapshot({
		config: cfg,
		env: createConfigRuntimeEnv(cfg),
		...workspaceDir ? { workspaceDir } : {},
		...providerScopedDiscovery ? { preferPersisted: false } : {}
	});
	const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveDefaultAgentDir(cfg);
	return {
		agentDir,
		fingerprint: await buildModelsJsonFingerprint({
			config: cfg,
			sourceConfigForSecrets: resolved.sourceConfigForSecrets,
			agentDir,
			...workspaceDir ? { workspaceDir } : {},
			...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {},
			...options.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: options.providerDiscoveryProviderIds } : {},
			...options.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: options.providerDiscoveryTimeoutMs } : {},
			...options.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {}
		}),
		...workspaceDir ? { workspaceDir } : {}
	};
}
async function withModelsJsonWriteLock(targetPath, run) {
	const prior = MODELS_JSON_STATE.writeLocks.get(targetPath) ?? Promise.resolve();
	let release = () => {};
	const gate = new Promise((resolve) => {
		release = resolve;
	});
	const pending = prior.then(() => gate);
	MODELS_JSON_STATE.writeLocks.set(targetPath, pending);
	try {
		await prior;
		return await run();
	} finally {
		release();
		if (MODELS_JSON_STATE.writeLocks.get(targetPath) === pending) MODELS_JSON_STATE.writeLocks.delete(targetPath);
	}
}
/** Ensures models.json and plugin catalog sidecars are current for an agent. */
async function prepareOpenClawModelsJsonSource(config, agentDirOverride, options = {}) {
	const resolved = resolveModelsConfigInput(config);
	const cfg = resolved.config;
	const sourceFingerprint = await buildModelsJsonSourceFingerprint(config, agentDirOverride, options);
	const workspaceDir = sourceFingerprint.workspaceDir;
	const pluginMetadataSnapshot = options.pluginMetadataSnapshot ?? resolvePluginMetadataSnapshot({
		config: cfg,
		env: createConfigRuntimeEnv(cfg),
		...workspaceDir ? { workspaceDir } : {},
		...options.providerDiscoveryProviderIds?.length ? { preferPersisted: false } : {}
	});
	const agentDir = sourceFingerprint.agentDir;
	const targetPath = path.join(agentDir, "models.json");
	const fingerprint = sourceFingerprint.fingerprint;
	const cacheKey = modelsJsonReadyCacheKey(targetPath, fingerprint);
	const cached = MODELS_JSON_STATE.readyCache.get(cacheKey);
	if (cached) {
		const settled = await cached;
		await ensureModelsFileModeForModelsJson(targetPath);
		return {
			...settled.result,
			fingerprint: settled.fingerprint,
			...workspaceDir ? { workspaceDir } : {}
		};
	}
	const pending = withModelsJsonWriteLock(targetPath, async () => {
		const env = createConfigRuntimeEnv(cfg);
		const existingModelsFile = await readExistingModelsFile(targetPath);
		const existingParsedForMerge = await mergeGeneratedPluginCatalogProvidersIntoExistingParsed({
			agentDir,
			existingParsed: existingModelsFile.parsed,
			...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}
		});
		const plan = await planOpenClawModelsJson({
			cfg,
			sourceConfigForSecrets: resolved.sourceConfigForSecrets,
			agentDir,
			env,
			...workspaceDir ? { workspaceDir } : {},
			existingRaw: existingModelsFile.raw,
			existingParsed: existingParsedForMerge,
			...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {},
			...options.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: options.providerDiscoveryProviderIds } : {},
			...options.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: options.providerDiscoveryTimeoutMs } : {},
			...options.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {}
		});
		if (plan.action === "skip") return {
			fingerprint,
			result: {
				agentDir,
				wrote: await writePluginCatalogsForModelsJson({
					agentDir,
					pluginCatalogWrites: plan.pluginCatalogWrites
				})
			}
		};
		if (plan.action === "noop") {
			const wrotePluginCatalog = await 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 = await 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({
			config: cfg,
			sourceConfigForSecrets: resolved.sourceConfigForSecrets,
			agentDir,
			...workspaceDir ? { workspaceDir } : {},
			...pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {},
			...options.providerDiscoveryProviderIds ? { providerDiscoveryProviderIds: options.providerDiscoveryProviderIds } : {},
			...options.providerDiscoveryTimeoutMs !== void 0 ? { providerDiscoveryTimeoutMs: options.providerDiscoveryTimeoutMs } : {},
			...options.providerDiscoveryEntriesOnly === true ? { providerDiscoveryEntriesOnly: true } : {}
		});
		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;
	}
}
/** Ensures models.json and plugin catalog sidecars are current for an agent. */
async function ensureOpenClawModelsJson(config, agentDirOverride, options = {}) {
	const prepared = await prepareOpenClawModelsJsonSource(config, agentDirOverride, options);
	return {
		agentDir: prepared.agentDir,
		wrote: prepared.wrote
	};
}
//#endregion
export { writeModelsFileAtomicForModelsJson as a, prepareOpenClawModelsJsonSource as i, ensureModelsFileModeForModelsJson as n, ensureOpenClawModelsJson as r, buildModelsJsonSourceFingerprint as t };