openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
961 lines (960 loc) • 44.3 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { d as normalizeTrimmedStringList } from "./string-normalization-WNUDCpXX.js";
import { i as openRootFileSync, n as matchRootFileOpenFailure } from "./root-file-jRMCpJW4.js";
import { c as isRecord } from "./utils-CCC-BEJH.js";
import { n as ENV_SECRET_REF_ID_RE } from "./types.secrets-_0JOMGE5.js";
import { t as isBlockedObjectKey } from "./prototype-keys-D2nJOZIy.js";
import "./boundary-file-read-CBe_wA_B.js";
import { i as normalizeModelCatalogProviderId, t as normalizeModelCatalog } from "./model-catalog-normalize-CkJjoy4K.js";
import { n as MANIFEST_KEY } from "./legacy-names-NIXaj2oi.js";
import { t as parseJsonWithJson5Fallback } from "./parse-json-compat-DvZKmwhP.js";
import { t as normalizeManifestCommandAliases } from "./manifest-command-aliases-cQ7bfA7Y.js";
import { r as createPluginCacheKey, t as PluginLruCache } from "./plugin-cache-primitives-BaxqicKH.js";
import fs from "node:fs";
import path from "node:path";
//#region src/plugins/manifest.ts
/** Loads and normalizes OpenClaw plugin manifests, including contracts and config schemas. */
/** Canonical plugin manifest filename inside plugin roots. */
const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
const PLUGIN_MANIFEST_FILENAMES = [PLUGIN_MANIFEST_FILENAME];
const MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024;
const MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES = 512;
const MAX_SECRET_PROVIDER_EXEC_ARGS = 128;
const MAX_SECRET_PROVIDER_EXEC_ARG_BYTES = 1024;
const MAX_SECRET_PROVIDER_EXEC_TIMEOUT_MS = 12e4;
const MAX_SECRET_PROVIDER_EXEC_OUTPUT_BYTES = 20 * 1024 * 1024;
const MAX_SECRET_PROVIDER_EXEC_PASS_ENV = 128;
const SECRET_PROVIDER_NODE_COMMAND_PLACEHOLDER = "${node}";
const pluginManifestLoadCache = new PluginLruCache(MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES);
function normalizeStringListRecord(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [key, rawValues] of Object.entries(value)) {
const providerId = normalizeOptionalString(key) ?? "";
if (!providerId || isBlockedObjectKey(providerId)) continue;
const values = normalizeTrimmedStringList(rawValues);
if (values.length === 0) continue;
normalized[providerId] = values;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeStringRecord(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [rawKey, rawValue] of Object.entries(value)) {
const key = normalizeOptionalString(rawKey) ?? "";
const valueLocal = normalizeOptionalString(rawValue) ?? "";
if (!key || isBlockedObjectKey(key) || !valueLocal) continue;
normalized[key] = valueLocal;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
const MEDIA_UNDERSTANDING_CAPABILITIES = new Set([
"image",
"audio",
"video"
]);
function normalizeMediaUnderstandingCapabilityRecord(value) {
if (!isRecord(value)) return;
const normalized = {};
for (const [rawKey, rawValue] of Object.entries(value)) {
if (!MEDIA_UNDERSTANDING_CAPABILITIES.has(rawKey)) continue;
const model = normalizeOptionalString(rawValue);
if (model) normalized[rawKey] = model;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeMediaUnderstandingPriorityRecord(value) {
if (!isRecord(value)) return;
const normalized = {};
for (const [rawKey, rawValue] of Object.entries(value)) {
if (!MEDIA_UNDERSTANDING_CAPABILITIES.has(rawKey) || typeof rawValue !== "number" || !Number.isFinite(rawValue)) continue;
normalized[rawKey] = rawValue;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeMediaUnderstandingCapabilities(value) {
const values = normalizeTrimmedStringList(value).filter((entry) => MEDIA_UNDERSTANDING_CAPABILITIES.has(entry));
return values.length > 0 ? values : void 0;
}
function normalizeMediaUnderstandingNativeDocumentInputs(value) {
const values = normalizeTrimmedStringList(value).filter((entry) => entry === "pdf");
return values.length > 0 ? values : void 0;
}
function normalizeMediaUnderstandingDocumentModels(value) {
if (!isRecord(value)) return;
const pdfRaw = value.pdf;
if (!isRecord(pdfRaw)) return;
const textExtraction = normalizeOptionalString(pdfRaw.textExtraction);
const image = pdfRaw.image === false ? false : normalizeOptionalString(pdfRaw.image);
const pdf = {
...textExtraction ? { textExtraction } : {},
...image !== void 0 ? { image } : {}
};
return Object.keys(pdf).length > 0 ? { pdf } : void 0;
}
function normalizeMediaUnderstandingProviderMetadata(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [rawProviderId, rawMetadata] of Object.entries(value)) {
const providerId = normalizeOptionalString(rawProviderId) ?? "";
if (!providerId || isBlockedObjectKey(providerId) || !isRecord(rawMetadata)) continue;
const capabilities = normalizeMediaUnderstandingCapabilities(rawMetadata.capabilities);
const defaultModels = normalizeMediaUnderstandingCapabilityRecord(rawMetadata.defaultModels);
const autoPriority = normalizeMediaUnderstandingPriorityRecord(rawMetadata.autoPriority);
const nativeDocumentInputs = normalizeMediaUnderstandingNativeDocumentInputs(rawMetadata.nativeDocumentInputs);
const documentModels = normalizeMediaUnderstandingDocumentModels(rawMetadata.documentModels);
const metadata = {
...capabilities ? { capabilities } : {},
...defaultModels ? { defaultModels } : {},
...autoPriority ? { autoPriority } : {},
...nativeDocumentInputs ? { nativeDocumentInputs } : {},
...documentModels ? { documentModels } : {}
};
if (Object.keys(metadata).length > 0) normalized[providerId] = metadata;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeProviderBaseUrlGuard(value) {
if (!isRecord(value)) return;
const provider = normalizeOptionalString(value.provider);
const allowedBaseUrls = normalizeTrimmedStringList(value.allowedBaseUrls);
if (!provider || allowedBaseUrls.length === 0) return;
const defaultBaseUrl = normalizeOptionalString(value.defaultBaseUrl);
return {
provider,
...defaultBaseUrl ? { defaultBaseUrl } : {},
allowedBaseUrls
};
}
function normalizeCapabilityProviderAuthSignals(value) {
if (!Array.isArray(value)) return;
const signals = [];
for (const rawSignal of value) {
if (!isRecord(rawSignal)) continue;
const provider = normalizeOptionalString(rawSignal.provider);
if (!provider) continue;
const providerBaseUrl = normalizeProviderBaseUrlGuard(rawSignal.providerBaseUrl);
signals.push({
provider,
...providerBaseUrl ? { providerBaseUrl } : {}
});
}
return signals.length > 0 ? signals : void 0;
}
function normalizeCapabilityProviderModeConfigSignal(value) {
if (!isRecord(value)) return;
const pathResult = normalizeOptionalString(value.path);
const defaultValue = normalizeOptionalString(value.default);
const allowed = normalizeTrimmedStringList(value.allowed);
const disallowed = normalizeTrimmedStringList(value.disallowed);
const signal = {
...pathResult ? { path: pathResult } : {},
...defaultValue ? { default: defaultValue } : {},
...allowed.length > 0 ? { allowed } : {},
...disallowed.length > 0 ? { disallowed } : {}
};
return Object.keys(signal).length > 0 ? signal : void 0;
}
function normalizeCapabilityProviderConfigSignals(value) {
if (!Array.isArray(value)) return;
const signals = [];
for (const rawSignal of value) {
if (!isRecord(rawSignal)) continue;
const rootPath = normalizeOptionalString(rawSignal.rootPath);
if (!rootPath) continue;
const overlayPath = normalizeOptionalString(rawSignal.overlayPath);
const overlayMapPath = normalizeOptionalString(rawSignal.overlayMapPath);
const required = normalizeTrimmedStringList(rawSignal.required);
const requiredAny = normalizeTrimmedStringList(rawSignal.requiredAny);
const mode = normalizeCapabilityProviderModeConfigSignal(rawSignal.mode);
const signal = {
rootPath,
...overlayPath ? { overlayPath } : {},
...overlayMapPath ? { overlayMapPath } : {},
...required.length > 0 ? { required } : {},
...requiredAny.length > 0 ? { requiredAny } : {},
...mode ? { mode } : {}
};
if (required.length > 0 || requiredAny.length > 0 || mode) signals.push(signal);
}
return signals.length > 0 ? signals : void 0;
}
function normalizeCapabilityProviderMetadataEntry(rawMetadata) {
const aliases = normalizeTrimmedStringList(rawMetadata.aliases);
const authProviders = normalizeTrimmedStringList(rawMetadata.authProviders);
const authSignals = normalizeCapabilityProviderAuthSignals(rawMetadata.authSignals);
const configSignals = normalizeCapabilityProviderConfigSignals(rawMetadata.configSignals);
const referenceAudioInputs = rawMetadata.referenceAudioInputs === true ? true : void 0;
const metadata = {
...aliases.length > 0 ? { aliases } : {},
...authProviders.length > 0 ? { authProviders } : {},
...authSignals ? { authSignals } : {},
...configSignals ? { configSignals } : {},
...referenceAudioInputs ? { referenceAudioInputs } : {}
};
return Object.keys(metadata).length > 0 ? metadata : void 0;
}
function normalizeCapabilityProviderMetadata(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [rawProviderId, rawMetadata] of Object.entries(value)) {
const providerId = normalizeOptionalString(rawProviderId) ?? "";
if (!providerId || isBlockedObjectKey(providerId) || !isRecord(rawMetadata)) continue;
const metadata = normalizeCapabilityProviderMetadataEntry(rawMetadata);
if (metadata) normalized[providerId] = metadata;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizePluginToolMetadata(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [rawToolName, rawMetadata] of Object.entries(value)) {
const toolName = normalizeOptionalString(rawToolName) ?? "";
if (!toolName || isBlockedObjectKey(toolName) || !isRecord(rawMetadata)) continue;
const metadata = {
...normalizeCapabilityProviderMetadataEntry(rawMetadata),
...rawMetadata.optional === true ? { optional: true } : {}
};
if (Object.keys(metadata).length > 0) normalized[toolName] = metadata;
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeManifestContracts(value) {
if (!isRecord(value)) return;
const embeddedExtensionFactories = normalizeTrimmedStringList(value.embeddedExtensionFactories);
const agentToolResultMiddleware = normalizeTrimmedStringList(value.agentToolResultMiddleware);
const trustedToolPolicies = normalizeTrimmedStringList(value.trustedToolPolicies);
const externalAuthProviders = normalizeTrimmedStringList(value.externalAuthProviders);
const embeddingProviders = normalizeTrimmedStringList(value.embeddingProviders);
const memoryEmbeddingProviders = normalizeTrimmedStringList(value.memoryEmbeddingProviders);
const speechProviders = normalizeTrimmedStringList(value.speechProviders);
const realtimeTranscriptionProviders = normalizeTrimmedStringList(value.realtimeTranscriptionProviders);
const realtimeVoiceProviders = normalizeTrimmedStringList(value.realtimeVoiceProviders);
const mediaUnderstandingProviders = normalizeTrimmedStringList(value.mediaUnderstandingProviders);
const transcriptSourceProviders = normalizeTrimmedStringList(value.transcriptSourceProviders);
const documentExtractors = normalizeTrimmedStringList(value.documentExtractors);
const imageGenerationProviders = normalizeTrimmedStringList(value.imageGenerationProviders);
const videoGenerationProviders = normalizeTrimmedStringList(value.videoGenerationProviders);
const musicGenerationProviders = normalizeTrimmedStringList(value.musicGenerationProviders);
const webContentExtractors = normalizeTrimmedStringList(value.webContentExtractors);
const webFetchProviders = normalizeTrimmedStringList(value.webFetchProviders);
const webSearchProviders = normalizeTrimmedStringList(value.webSearchProviders);
const migrationProviders = normalizeTrimmedStringList(value.migrationProviders);
const gatewayMethodDispatch = normalizeTrimmedStringList(value.gatewayMethodDispatch);
const tools = normalizeTrimmedStringList(value.tools);
const contracts = {
...embeddedExtensionFactories.length > 0 ? { embeddedExtensionFactories } : {},
...agentToolResultMiddleware.length > 0 ? { agentToolResultMiddleware } : {},
...trustedToolPolicies.length > 0 ? { trustedToolPolicies } : {},
...externalAuthProviders.length > 0 ? { externalAuthProviders } : {},
...embeddingProviders.length > 0 ? { embeddingProviders } : {},
...memoryEmbeddingProviders.length > 0 ? { memoryEmbeddingProviders } : {},
...speechProviders.length > 0 ? { speechProviders } : {},
...realtimeTranscriptionProviders.length > 0 ? { realtimeTranscriptionProviders } : {},
...realtimeVoiceProviders.length > 0 ? { realtimeVoiceProviders } : {},
...mediaUnderstandingProviders.length > 0 ? { mediaUnderstandingProviders } : {},
...transcriptSourceProviders.length > 0 ? { transcriptSourceProviders } : {},
...documentExtractors.length > 0 ? { documentExtractors } : {},
...imageGenerationProviders.length > 0 ? { imageGenerationProviders } : {},
...videoGenerationProviders.length > 0 ? { videoGenerationProviders } : {},
...musicGenerationProviders.length > 0 ? { musicGenerationProviders } : {},
...webContentExtractors.length > 0 ? { webContentExtractors } : {},
...webFetchProviders.length > 0 ? { webFetchProviders } : {},
...webSearchProviders.length > 0 ? { webSearchProviders } : {},
...migrationProviders.length > 0 ? { migrationProviders } : {},
...gatewayMethodDispatch.length > 0 ? { gatewayMethodDispatch } : {},
...tools.length > 0 ? { tools } : {}
};
return Object.keys(contracts).length > 0 ? contracts : void 0;
}
function isManifestConfigLiteral(value) {
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
}
function normalizeManifestDangerousConfigFlags(value) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (!isRecord(entry)) continue;
const pathValue = normalizeOptionalString(entry.path) ?? "";
if (!pathValue || !isManifestConfigLiteral(entry.equals)) continue;
normalized.push({
path: pathValue,
equals: entry.equals
});
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeManifestSecretInputPaths(value) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (!isRecord(entry)) continue;
const pathLocal = normalizeOptionalString(entry.path) ?? "";
if (!pathLocal) continue;
const expected = entry.expected === "string" ? entry.expected : void 0;
normalized.push({
path: pathLocal,
...expected ? { expected } : {}
});
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeManifestConfigContracts(value) {
if (!isRecord(value)) return;
const compatibilityMigrationPaths = normalizeTrimmedStringList(value.compatibilityMigrationPaths);
const compatibilityRuntimePaths = normalizeTrimmedStringList(value.compatibilityRuntimePaths);
const rawSecretInputs = isRecord(value.secretInputs) ? value.secretInputs : void 0;
const dangerousFlags = normalizeManifestDangerousConfigFlags(value.dangerousFlags);
const secretInputPaths = rawSecretInputs ? normalizeManifestSecretInputPaths(rawSecretInputs.paths) : void 0;
const secretInputs = secretInputPaths && secretInputPaths.length > 0 ? {
...rawSecretInputs?.bundledDefaultEnabled === true ? { bundledDefaultEnabled: true } : rawSecretInputs?.bundledDefaultEnabled === false ? { bundledDefaultEnabled: false } : {},
paths: secretInputPaths
} : void 0;
const configContracts = {
...compatibilityMigrationPaths.length > 0 ? { compatibilityMigrationPaths } : {},
...compatibilityRuntimePaths.length > 0 ? { compatibilityRuntimePaths } : {},
...dangerousFlags ? { dangerousFlags } : {},
...secretInputs ? { secretInputs } : {}
};
return Object.keys(configContracts).length > 0 ? configContracts : void 0;
}
function normalizeManifestModelSupport(value) {
if (!isRecord(value)) return;
const modelPrefixes = normalizeTrimmedStringList(value.modelPrefixes);
const modelPatterns = normalizeTrimmedStringList(value.modelPatterns);
const modelSupport = {
...modelPrefixes.length > 0 ? { modelPrefixes } : {},
...modelPatterns.length > 0 ? { modelPatterns } : {}
};
return Object.keys(modelSupport).length > 0 ? modelSupport : void 0;
}
function normalizeManifestModelPricingSource(value) {
if (value === false) return false;
if (!isRecord(value)) return;
const provider = normalizeModelCatalogProviderId(normalizeOptionalString(value.provider) ?? "");
const modelIdTransforms = normalizeTrimmedStringList(value.modelIdTransforms).filter((entry) => entry === "version-dots");
const source = {
...provider ? { provider } : {},
...value.passthroughProviderModel === true ? { passthroughProviderModel: true } : {},
...modelIdTransforms.length > 0 ? { modelIdTransforms } : {}
};
return Object.keys(source).length > 0 ? source : void 0;
}
function normalizeManifestModelPricingProvider(value) {
if (!isRecord(value)) return;
const openRouter = normalizeManifestModelPricingSource(value.openRouter);
const liteLLM = normalizeManifestModelPricingSource(value.liteLLM);
const policy = {
...typeof value.external === "boolean" ? { external: value.external } : {},
...openRouter !== void 0 ? { openRouter } : {},
...liteLLM !== void 0 ? { liteLLM } : {}
};
return Object.keys(policy).length > 0 ? policy : void 0;
}
function normalizeManifestModelPricing(value, params) {
if (!isRecord(value) || !isRecord(value.providers)) return;
const ownedProviders = new Set([...params.ownedProviders].map((provider) => normalizeModelCatalogProviderId(provider)).filter(Boolean));
const providers = {};
for (const [rawProviderId, rawPolicy] of Object.entries(value.providers)) {
const providerId = normalizeModelCatalogProviderId(rawProviderId);
if (!providerId || !ownedProviders.has(providerId)) continue;
const policy = normalizeManifestModelPricingProvider(rawPolicy);
if (policy) providers[providerId] = policy;
}
return Object.keys(providers).length > 0 ? { providers } : void 0;
}
function normalizeManifestModelIdPrefixRules(value) {
if (!Array.isArray(value)) return;
const rules = [];
for (const rawRule of value) {
if (!isRecord(rawRule)) continue;
const modelPrefix = normalizeOptionalString(rawRule.modelPrefix);
const prefix = normalizeOptionalString(rawRule.prefix);
if (!modelPrefix || !prefix) continue;
rules.push({
modelPrefix,
prefix
});
}
return rules.length > 0 ? rules : void 0;
}
function normalizeManifestModelIdNormalizationProvider(value) {
if (!isRecord(value)) return;
const aliases = {};
if (isRecord(value.aliases)) for (const [rawAlias, rawCanonical] of Object.entries(value.aliases)) {
const alias = normalizeModelCatalogProviderId(rawAlias);
const canonical = normalizeOptionalString(rawCanonical);
if (alias && canonical) aliases[alias] = canonical;
}
const stripPrefixes = normalizeTrimmedStringList(value.stripPrefixes);
const prefixWhenBare = normalizeOptionalString(value.prefixWhenBare);
const prefixWhenBareAfterAliasStartsWith = normalizeManifestModelIdPrefixRules(value.prefixWhenBareAfterAliasStartsWith);
const normalization = {
...Object.keys(aliases).length > 0 ? { aliases } : {},
...stripPrefixes.length > 0 ? { stripPrefixes } : {},
...prefixWhenBare ? { prefixWhenBare } : {},
...prefixWhenBareAfterAliasStartsWith ? { prefixWhenBareAfterAliasStartsWith } : {}
};
return Object.keys(normalization).length > 0 ? normalization : void 0;
}
function normalizeManifestModelIdNormalization(value, params) {
if (!isRecord(value) || !isRecord(value.providers)) return;
const ownedProviders = new Set([...params.ownedProviders].map((provider) => normalizeModelCatalogProviderId(provider)).filter(Boolean));
const providers = {};
for (const [rawProviderId, rawPolicy] of Object.entries(value.providers)) {
const providerId = normalizeModelCatalogProviderId(rawProviderId);
if (!providerId || !ownedProviders.has(providerId)) continue;
const policy = normalizeManifestModelIdNormalizationProvider(rawPolicy);
if (policy) providers[providerId] = policy;
}
return Object.keys(providers).length > 0 ? { providers } : void 0;
}
function normalizeManifestProviderEndpoints(value) {
if (!Array.isArray(value)) return;
const endpoints = [];
for (const rawEndpoint of value) {
if (!isRecord(rawEndpoint)) continue;
const endpointClass = normalizeOptionalString(rawEndpoint.endpointClass);
if (!endpointClass) continue;
const hosts = normalizeTrimmedStringList(rawEndpoint.hosts).map((host) => host.toLowerCase());
const hostSuffixes = normalizeTrimmedStringList(rawEndpoint.hostSuffixes).map((host) => host.toLowerCase());
const baseUrls = normalizeTrimmedStringList(rawEndpoint.baseUrls);
const googleVertexRegion = normalizeOptionalString(rawEndpoint.googleVertexRegion);
const googleVertexRegionHostSuffix = normalizeOptionalString(rawEndpoint.googleVertexRegionHostSuffix)?.toLowerCase();
if (hosts.length === 0 && hostSuffixes.length === 0 && baseUrls.length === 0) continue;
endpoints.push({
endpointClass,
...hosts.length > 0 ? { hosts } : {},
...hostSuffixes.length > 0 ? { hostSuffixes } : {},
...baseUrls.length > 0 ? { baseUrls } : {},
...googleVertexRegion ? { googleVertexRegion } : {},
...googleVertexRegionHostSuffix ? { googleVertexRegionHostSuffix } : {}
});
}
return endpoints.length > 0 ? endpoints : void 0;
}
function normalizeManifestProviderRequestProvider(value) {
if (!isRecord(value)) return;
const family = normalizeOptionalString(value.family);
const compatibilityFamily = normalizeOptionalString(value.compatibilityFamily) === "moonshot" ? "moonshot" : void 0;
const supportsStreamingUsage = isRecord(value.openAICompletions) ? value.openAICompletions.supportsStreamingUsage : void 0;
const openAICompletions = typeof supportsStreamingUsage === "boolean" ? { supportsStreamingUsage } : void 0;
const providerRequest = {
...family ? { family } : {},
...compatibilityFamily ? { compatibilityFamily } : {},
...openAICompletions && Object.keys(openAICompletions).length > 0 ? { openAICompletions } : {}
};
return Object.keys(providerRequest).length > 0 ? providerRequest : void 0;
}
function normalizeManifestProviderRequest(value, params) {
if (!isRecord(value) || !isRecord(value.providers)) return;
const ownedProviders = new Set([...params.ownedProviders].map((provider) => normalizeModelCatalogProviderId(provider)).filter(Boolean));
const providers = {};
for (const [rawProviderId, rawPolicy] of Object.entries(value.providers)) {
const providerId = normalizeModelCatalogProviderId(rawProviderId);
if (!providerId || !ownedProviders.has(providerId)) continue;
const policy = normalizeManifestProviderRequestProvider(rawPolicy);
if (policy) providers[providerId] = policy;
}
return Object.keys(providers).length > 0 ? { providers } : void 0;
}
function normalizeManifestStringArray(value, options) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (typeof entry !== "string") continue;
if (options?.maxLength !== void 0 && entry.length > options.maxLength) continue;
if (options?.pattern && !options.pattern.test(entry)) continue;
normalized.push(entry);
if (options?.maxItems !== void 0 && normalized.length >= options.maxItems) break;
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeManifestTrimmedStringArray(value, options) {
const normalized = normalizeTrimmedStringList(value).filter((entry) => !options?.pattern || options.pattern.test(entry));
const limited = options?.maxItems !== void 0 ? normalized.slice(0, options.maxItems) : normalized;
return limited.length > 0 ? limited : void 0;
}
function normalizeManifestPositiveInteger(value, max) {
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= max ? value : void 0;
}
function normalizeManifestSecretProviderIntegrations(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [rawId, rawIntegration] of Object.entries(value)) {
const id = normalizeOptionalString(rawId) ?? "";
if (!id || isBlockedObjectKey(id) || !isRecord(rawIntegration)) continue;
const command = normalizeOptionalString(rawIntegration.command);
if (rawIntegration.source !== "exec" || command !== SECRET_PROVIDER_NODE_COMMAND_PLACEHOLDER) continue;
const providerAlias = normalizeOptionalString(rawIntegration.providerAlias);
const displayName = normalizeOptionalString(rawIntegration.displayName);
const description = normalizeOptionalString(rawIntegration.description);
const args = normalizeManifestStringArray(rawIntegration.args, {
maxItems: MAX_SECRET_PROVIDER_EXEC_ARGS,
maxLength: MAX_SECRET_PROVIDER_EXEC_ARG_BYTES
});
const timeoutMs = normalizeManifestPositiveInteger(rawIntegration.timeoutMs, MAX_SECRET_PROVIDER_EXEC_TIMEOUT_MS);
const noOutputTimeoutMs = normalizeManifestPositiveInteger(rawIntegration.noOutputTimeoutMs, MAX_SECRET_PROVIDER_EXEC_TIMEOUT_MS);
const maxOutputBytes = normalizeManifestPositiveInteger(rawIntegration.maxOutputBytes, MAX_SECRET_PROVIDER_EXEC_OUTPUT_BYTES);
const env = normalizeStringRecord(rawIntegration.env);
const passEnv = normalizeManifestTrimmedStringArray(rawIntegration.passEnv, {
maxItems: MAX_SECRET_PROVIDER_EXEC_PASS_ENV,
pattern: ENV_SECRET_REF_ID_RE
});
normalized[id] = {
...providerAlias ? { providerAlias } : {},
...displayName ? { displayName } : {},
...description ? { description } : {},
source: "exec",
command,
...args ? { args } : {},
...timeoutMs !== void 0 ? { timeoutMs } : {},
...noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs } : {},
...maxOutputBytes !== void 0 ? { maxOutputBytes } : {},
...typeof rawIntegration.jsonOnly === "boolean" ? { jsonOnly: rawIntegration.jsonOnly } : {},
...env ? { env } : {},
...passEnv ? { passEnv } : {},
...rawIntegration.allowInsecurePath === true ? { allowInsecurePath: true } : {}
};
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeManifestActivation(value) {
if (!isRecord(value)) return;
const onProviders = normalizeTrimmedStringList(value.onProviders);
const onAgentHarnesses = normalizeTrimmedStringList(value.onAgentHarnesses);
const onCommands = normalizeTrimmedStringList(value.onCommands);
const onChannels = normalizeTrimmedStringList(value.onChannels);
const onRoutes = normalizeTrimmedStringList(value.onRoutes);
const onConfigPaths = normalizeTrimmedStringList(value.onConfigPaths);
const onStartup = typeof value.onStartup === "boolean" ? value.onStartup : void 0;
const onCapabilities = normalizeTrimmedStringList(value.onCapabilities).filter((capability) => capability === "provider" || capability === "channel" || capability === "tool" || capability === "hook");
const activation = {
...onStartup !== void 0 ? { onStartup } : {},
...onProviders.length > 0 ? { onProviders } : {},
...onAgentHarnesses.length > 0 ? { onAgentHarnesses } : {},
...onCommands.length > 0 ? { onCommands } : {},
...onChannels.length > 0 ? { onChannels } : {},
...onRoutes.length > 0 ? { onRoutes } : {},
...onConfigPaths.length > 0 ? { onConfigPaths } : {},
...onCapabilities.length > 0 ? { onCapabilities } : {}
};
return Object.keys(activation).length > 0 ? activation : void 0;
}
const MANIFEST_DEFAULT_ENABLEMENT_PLATFORMS = new Set([
"aix",
"android",
"darwin",
"freebsd",
"haiku",
"linux",
"openbsd",
"sunos",
"win32",
"cygwin",
"netbsd"
]);
function normalizeManifestDefaultPlatforms(value) {
return normalizeTrimmedStringList(value).filter((platform) => MANIFEST_DEFAULT_ENABLEMENT_PLATFORMS.has(platform));
}
function normalizeManifestSetupProviders(value) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (!isRecord(entry)) continue;
const id = normalizeOptionalString(entry.id) ?? "";
if (!id) continue;
const authMethods = normalizeTrimmedStringList(entry.authMethods);
const envVars = normalizeTrimmedStringList(entry.envVars);
const authEvidence = normalizeManifestSetupProviderAuthEvidence(entry.authEvidence);
normalized.push({
id,
...authMethods.length > 0 ? { authMethods } : {},
...envVars.length > 0 ? { envVars } : {},
...authEvidence ? { authEvidence } : {}
});
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeManifestSetupProviderAuthEvidence(value) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (!isRecord(entry) || entry.type !== "local-file-with-env") continue;
const credentialMarker = normalizeOptionalString(entry.credentialMarker);
if (!credentialMarker) continue;
const fileEnvVar = normalizeOptionalString(entry.fileEnvVar);
const fallbackPaths = normalizeTrimmedStringList(entry.fallbackPaths);
if (!fileEnvVar && fallbackPaths.length === 0) continue;
const requiresAnyEnv = normalizeTrimmedStringList(entry.requiresAnyEnv);
const requiresAllEnv = normalizeTrimmedStringList(entry.requiresAllEnv);
const source = normalizeOptionalString(entry.source);
normalized.push({
type: "local-file-with-env",
...fileEnvVar ? { fileEnvVar } : {},
...fallbackPaths.length > 0 ? { fallbackPaths } : {},
...requiresAnyEnv.length > 0 ? { requiresAnyEnv } : {},
...requiresAllEnv.length > 0 ? { requiresAllEnv } : {},
credentialMarker,
...source ? { source } : {}
});
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeManifestSetup(value) {
if (!isRecord(value)) return;
const providers = normalizeManifestSetupProviders(value.providers);
const cliBackends = normalizeTrimmedStringList(value.cliBackends);
const configMigrations = normalizeTrimmedStringList(value.configMigrations);
const requiresRuntime = typeof value.requiresRuntime === "boolean" ? value.requiresRuntime : void 0;
const setup = {
...providers ? { providers } : {},
...cliBackends.length > 0 ? { cliBackends } : {},
...configMigrations.length > 0 ? { configMigrations } : {},
...requiresRuntime !== void 0 ? { requiresRuntime } : {}
};
return Object.keys(setup).length > 0 ? setup : void 0;
}
function normalizeManifestQaRunners(value) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (!isRecord(entry)) continue;
const commandName = normalizeOptionalString(entry.commandName) ?? "";
if (!commandName) continue;
const description = normalizeOptionalString(entry.description) ?? "";
normalized.push({
commandName,
...description ? { description } : {}
});
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeProviderAuthChoices(value) {
if (!Array.isArray(value)) return;
const normalized = [];
for (const entry of value) {
if (!isRecord(entry)) continue;
const provider = normalizeOptionalString(entry.provider) ?? "";
const method = normalizeOptionalString(entry.method) ?? "";
const choiceId = normalizeOptionalString(entry.choiceId) ?? "";
if (!provider || !method || !choiceId) continue;
const choiceLabel = normalizeOptionalString(entry.choiceLabel) ?? "";
const choiceHint = normalizeOptionalString(entry.choiceHint) ?? "";
const assistantPriority = typeof entry.assistantPriority === "number" && Number.isFinite(entry.assistantPriority) ? entry.assistantPriority : void 0;
const assistantVisibility = entry.assistantVisibility === "manual-only" || entry.assistantVisibility === "visible" ? entry.assistantVisibility : void 0;
const deprecatedChoiceIds = normalizeTrimmedStringList(entry.deprecatedChoiceIds);
const groupId = normalizeOptionalString(entry.groupId) ?? "";
const groupLabel = normalizeOptionalString(entry.groupLabel) ?? "";
const groupHint = normalizeOptionalString(entry.groupHint) ?? "";
const onboardingFeatured = entry.onboardingFeatured === true;
const optionKey = normalizeOptionalString(entry.optionKey) ?? "";
const cliFlag = normalizeOptionalString(entry.cliFlag) ?? "";
const cliOption = normalizeOptionalString(entry.cliOption) ?? "";
const cliDescription = normalizeOptionalString(entry.cliDescription) ?? "";
const onboardingScopes = normalizeTrimmedStringList(entry.onboardingScopes).filter((scope) => scope === "text-inference" || scope === "image-generation" || scope === "music-generation");
normalized.push({
provider,
method,
choiceId,
...choiceLabel ? { choiceLabel } : {},
...choiceHint ? { choiceHint } : {},
...assistantPriority !== void 0 ? { assistantPriority } : {},
...assistantVisibility ? { assistantVisibility } : {},
...deprecatedChoiceIds.length > 0 ? { deprecatedChoiceIds } : {},
...groupId ? { groupId } : {},
...groupLabel ? { groupLabel } : {},
...groupHint ? { groupHint } : {},
...onboardingFeatured ? { onboardingFeatured: true } : {},
...optionKey ? { optionKey } : {},
...cliFlag ? { cliFlag } : {},
...cliOption ? { cliOption } : {},
...cliDescription ? { cliDescription } : {},
...onboardingScopes.length > 0 ? { onboardingScopes } : {}
});
}
return normalized.length > 0 ? normalized : void 0;
}
function normalizeChannelConfigs(value) {
if (!isRecord(value)) return;
const normalized = Object.create(null);
for (const [key, rawEntry] of Object.entries(value)) {
const channelId = normalizeOptionalString(key) ?? "";
if (!channelId || isBlockedObjectKey(channelId) || !isRecord(rawEntry)) continue;
const schema = isRecord(rawEntry.schema) ? rawEntry.schema : null;
if (!schema) continue;
const uiHints = isRecord(rawEntry.uiHints) ? rawEntry.uiHints : void 0;
const runtime = isRecord(rawEntry.runtime) && typeof rawEntry.runtime.safeParse === "function" ? rawEntry.runtime : void 0;
const label = normalizeOptionalString(rawEntry.label) ?? "";
const description = normalizeOptionalString(rawEntry.description) ?? "";
const preferOver = normalizeTrimmedStringList(rawEntry.preferOver);
const commandDefaults = normalizeManifestChannelCommandDefaults(rawEntry.commands);
normalized[channelId] = {
schema,
...uiHints ? { uiHints } : {},
...runtime ? { runtime } : {},
...label ? { label } : {},
...description ? { description } : {},
...preferOver.length > 0 ? { preferOver } : {},
...commandDefaults ? { commands: commandDefaults } : {}
};
}
return Object.keys(normalized).length > 0 ? normalized : void 0;
}
function normalizeManifestChannelCommandDefaults(value) {
if (!isRecord(value)) return;
const nativeCommandsAutoEnabled = typeof value.nativeCommandsAutoEnabled === "boolean" ? value.nativeCommandsAutoEnabled : void 0;
const nativeSkillsAutoEnabled = typeof value.nativeSkillsAutoEnabled === "boolean" ? value.nativeSkillsAutoEnabled : void 0;
return nativeCommandsAutoEnabled !== void 0 || nativeSkillsAutoEnabled !== void 0 ? {
...nativeCommandsAutoEnabled !== void 0 ? { nativeCommandsAutoEnabled } : {},
...nativeSkillsAutoEnabled !== void 0 ? { nativeSkillsAutoEnabled } : {}
} : void 0;
}
function resolvePluginManifestPath(rootDir) {
for (const filename of PLUGIN_MANIFEST_FILENAMES) {
const candidate = path.join(rootDir, filename);
if (fs.existsSync(candidate)) return candidate;
}
return path.join(rootDir, PLUGIN_MANIFEST_FILENAME);
}
function buildPluginManifestLoadCacheKey(params) {
return createPluginCacheKey([
[
path.resolve(params.manifestPath),
params.rejectHardlinks,
params.rootRealPath ?? "",
params.stats.dev,
params.stats.ino
],
params.stats.size,
params.stats.mtimeMs,
params.stats.ctimeMs
]);
}
function getCachedPluginManifestLoadResult(key, stats) {
const entry = pluginManifestLoadCache.get(key);
if (!entry || entry.size !== stats.size || entry.mtimeMs !== stats.mtimeMs || entry.ctimeMs !== stats.ctimeMs) return;
return entry.result;
}
function setCachedPluginManifestLoadResult(key, stats, result) {
pluginManifestLoadCache.set(key, {
result,
size: stats.size,
mtimeMs: stats.mtimeMs,
ctimeMs: stats.ctimeMs
});
}
function parsePluginKind(raw) {
if (typeof raw === "string") return raw;
if (Array.isArray(raw) && raw.length > 0 && raw.every((k) => typeof k === "string")) return raw.length === 1 ? raw[0] : raw;
}
function loadPluginManifest(rootDir, rejectHardlinks = true, rootRealPath) {
const manifestPath = resolvePluginManifestPath(rootDir);
const opened = openRootFileSync({
absolutePath: manifestPath,
rootPath: rootDir,
...rootRealPath !== void 0 ? { rootRealPath } : {},
boundaryLabel: "plugin root",
maxBytes: MAX_PLUGIN_MANIFEST_BYTES,
rejectHardlinks
});
if (!opened.ok) return matchRootFileOpenFailure(opened, {
path: () => ({
ok: false,
error: `plugin manifest not found: ${manifestPath}`,
manifestPath
}),
fallback: (failure) => ({
ok: false,
error: `unsafe plugin manifest path: ${manifestPath} (${failure.reason})`,
manifestPath
})
});
const stats = opened.stat;
const cacheKey = buildPluginManifestLoadCacheKey({
manifestPath,
rejectHardlinks,
...rootRealPath !== void 0 ? { rootRealPath } : {},
stats
});
const cached = getCachedPluginManifestLoadResult(cacheKey, stats);
if (cached) {
fs.closeSync(opened.fd);
return cached;
}
const cacheResult = (result) => {
setCachedPluginManifestLoadResult(cacheKey, stats, result);
return result;
};
let raw;
try {
raw = parseJsonWithJson5Fallback(fs.readFileSync(opened.fd, "utf-8"));
} catch (err) {
return cacheResult({
ok: false,
error: `failed to parse plugin manifest: ${String(err)}`,
manifestPath
});
} finally {
fs.closeSync(opened.fd);
}
if (!isRecord(raw)) return cacheResult({
ok: false,
error: "plugin manifest must be an object",
manifestPath
});
const id = normalizeOptionalString(raw.id) ?? "";
if (!id) return cacheResult({
ok: false,
error: "plugin manifest requires id",
manifestPath
});
const configSchema = isRecord(raw.configSchema) ? raw.configSchema : null;
if (!configSchema) return cacheResult({
ok: false,
error: "plugin manifest requires configSchema",
manifestPath
});
const requiresPlugins = normalizeTrimmedStringList(raw.requiresPlugins);
const kind = parsePluginKind(raw.kind);
const enabledByDefault = raw.enabledByDefault === true;
const enabledByDefaultOnPlatforms = normalizeManifestDefaultPlatforms(raw.enabledByDefaultOnPlatforms);
const legacyPluginIds = normalizeTrimmedStringList(raw.legacyPluginIds);
const autoEnableWhenConfiguredProviders = normalizeTrimmedStringList(raw.autoEnableWhenConfiguredProviders);
const name = normalizeOptionalString(raw.name);
const description = normalizeOptionalString(raw.description);
const version = normalizeOptionalString(raw.version);
const channels = normalizeTrimmedStringList(raw.channels);
const providers = normalizeTrimmedStringList(raw.providers);
const cliBackends = normalizeTrimmedStringList(raw.cliBackends);
const providerCatalogEntry = normalizeOptionalString(raw.providerCatalogEntry);
const modelSupport = normalizeManifestModelSupport(raw.modelSupport);
const modelCatalog = normalizeModelCatalog(raw.modelCatalog, { ownedProviders: new Set([...providers, ...cliBackends]) });
const modelPricing = normalizeManifestModelPricing(raw.modelPricing, { ownedProviders: new Set(providers) });
const modelIdNormalization = normalizeManifestModelIdNormalization(raw.modelIdNormalization, { ownedProviders: new Set(providers) });
const providerEndpoints = normalizeManifestProviderEndpoints(raw.providerEndpoints);
const providerRequest = normalizeManifestProviderRequest(raw.providerRequest, { ownedProviders: new Set(providers) });
const secretProviderIntegrations = normalizeManifestSecretProviderIntegrations(raw.secretProviderIntegrations);
const syntheticAuthRefs = normalizeTrimmedStringList(raw.syntheticAuthRefs);
const nonSecretAuthMarkers = normalizeTrimmedStringList(raw.nonSecretAuthMarkers);
const commandAliases = normalizeManifestCommandAliases(raw.commandAliases);
const providerAuthEnvVars = normalizeStringListRecord(raw.providerAuthEnvVars);
const providerAuthAliases = normalizeStringRecord(raw.providerAuthAliases);
const channelEnvVars = normalizeStringListRecord(raw.channelEnvVars);
const providerAuthChoices = normalizeProviderAuthChoices(raw.providerAuthChoices);
const activation = normalizeManifestActivation(raw.activation);
const setup = normalizeManifestSetup(raw.setup);
const qaRunners = normalizeManifestQaRunners(raw.qaRunners);
const skills = normalizeTrimmedStringList(raw.skills);
const contracts = normalizeManifestContracts(raw.contracts);
const mediaUnderstandingProviderMetadata = normalizeMediaUnderstandingProviderMetadata(raw.mediaUnderstandingProviderMetadata);
const imageGenerationProviderMetadata = normalizeCapabilityProviderMetadata(raw.imageGenerationProviderMetadata);
const videoGenerationProviderMetadata = normalizeCapabilityProviderMetadata(raw.videoGenerationProviderMetadata);
const musicGenerationProviderMetadata = normalizeCapabilityProviderMetadata(raw.musicGenerationProviderMetadata);
const toolMetadata = normalizePluginToolMetadata(raw.toolMetadata);
const configContracts = normalizeManifestConfigContracts(raw.configContracts);
const channelConfigs = normalizeChannelConfigs(raw.channelConfigs);
let uiHints;
if (isRecord(raw.uiHints)) uiHints = raw.uiHints;
return cacheResult({
ok: true,
manifest: {
id,
configSchema,
...requiresPlugins.length > 0 ? { requiresPlugins } : {},
...enabledByDefault ? { enabledByDefault } : {},
...enabledByDefaultOnPlatforms.length > 0 ? { enabledByDefaultOnPlatforms } : {},
...legacyPluginIds.length > 0 ? { legacyPluginIds } : {},
...autoEnableWhenConfiguredProviders.length > 0 ? { autoEnableWhenConfiguredProviders } : {},
kind,
channels,
providers,
providerCatalogEntry,
modelSupport,
modelCatalog,
modelPricing,
modelIdNormalization,
providerEndpoints,
providerRequest,
secretProviderIntegrations,
cliBackends,
syntheticAuthRefs,
nonSecretAuthMarkers,
commandAliases,
providerAuthEnvVars,
providerAuthAliases,
channelEnvVars,
providerAuthChoices,
activation,
setup,
qaRunners,
skills,
name,
description,
version,
uiHints,
contracts,
mediaUnderstandingProviderMetadata,
imageGenerationProviderMetadata,
videoGenerationProviderMetadata,
musicGenerationProviderMetadata,
toolMetadata,
configContracts,
channelConfigs
},
manifestPath
});
}
const DEFAULT_PLUGIN_ENTRY_CANDIDATES = [
"index.ts",
"index.js",
"index.mjs",
"index.cjs"
];
function getPackageManifestMetadata(manifest) {
if (!manifest) return;
return manifest[MANIFEST_KEY];
}
function resolvePackageExtensionEntries(manifest) {
const rawOpenClaw = manifest?.[MANIFEST_KEY];
if (rawOpenClaw === void 0 || rawOpenClaw === null) return {
status: "missing",
entries: []
};
if (!isRecord(rawOpenClaw)) return {
status: "invalid",
entries: [],
error: "package.json openclaw must be an object"
};
const raw = rawOpenClaw.extensions;
if (raw === void 0 || raw === null) return {
status: "missing",
entries: []
};
if (!Array.isArray(raw)) return {
status: "invalid",
entries: [],
error: "package.json openclaw.extensions must be an array"
};
const entries = [];
for (const [index, entry] of raw.entries()) {
const normalized = normalizeOptionalString(entry);
if (!normalized) return {
status: "invalid",
entries: [],
error: `package.json openclaw.extensions[${index}] must be a non-empty string`
};
entries.push(normalized);
}
if (entries.length === 0) return {
status: "empty",
entries: []
};
return {
status: "ok",
entries
};
}
//#endregion
export { normalizeManifestActivation as a, loadPluginManifest as i, PLUGIN_MANIFEST_FILENAME as n, resolvePackageExtensionEntries as o, getPackageManifestMetadata as r, DEFAULT_PLUGIN_ENTRY_CANDIDATES as t };