UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

374 lines (373 loc) 16.2 kB
import { c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js"; import { y as resolveStateDir } from "./paths-mvMm5bYV.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { _ as uniqueStrings, d as normalizeTrimmedStringList } from "./string-normalization-WNUDCpXX.js"; import { c as isBundledSourceOverlayPath } from "./discovery-BDSfxwT6.js"; import { c as isRecord$1 } from "./utils-CCC-BEJH.js"; import { t as createSubsystemLogger } from "./subsystem-BzXSmsuh.js"; import { n as getCachedPluginModuleLoader } from "./plugin-module-loader-cache-lz0qhu-X.js"; import { t as listChannelCatalogEntries } from "./channel-catalog-registry-C_kZJqje.js"; import { h as resolveExistingPluginModulePath, m as loadChannelPluginModule } from "./bundled-DcYUmgzC.js"; import { r as listBundledChannelIds } from "./bootstrap-registry-DdZI5Mh6.js"; import { i as hasNonEmptyString } from "./channel-target-ICEok4QR.js"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; //#region packages/model-catalog-core/src/configured-model-refs.ts /** Narrow unknown values to plain records. */ function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } /** Agent config keys that can contain direct model references. */ const AGENT_MODEL_CONFIG_KEYS = [ "model", "imageModel", "imageGenerationModel", "videoGenerationModel", "musicGenerationModel", "voiceModel", "pdfModel" ]; /** Collect configured model references from agents, channels, hooks, and message config. */ function collectConfiguredModelRefs(config, options = {}) { const refs = []; const pushModelRef = (path, value) => { if (typeof value === "string" && value.trim()) refs.push({ path, value: value.trim() }); }; const collectModelConfig = (path, value) => { if (typeof value === "string") { pushModelRef(path, value); return; } if (!isRecord(value)) return; pushModelRef(`${path}.primary`, value.primary); if (Array.isArray(value.fallbacks)) for (const [index, entry] of value.fallbacks.entries()) pushModelRef(`${path}.fallbacks.${index}`, entry); }; const collectFromAgent = (path, agent) => { if (!isRecord(agent)) return; for (const key of AGENT_MODEL_CONFIG_KEYS) collectModelConfig(`${path}.${key}`, agent[key]); pushModelRef(`${path}.heartbeat.model`, isRecord(agent.heartbeat) ? agent.heartbeat.model : void 0); collectModelConfig(`${path}.subagents.model`, isRecord(agent.subagents) ? agent.subagents.model : void 0); if (isRecord(agent.compaction)) { pushModelRef(`${path}.compaction.model`, agent.compaction.model); pushModelRef(`${path}.compaction.memoryFlush.model`, isRecord(agent.compaction.memoryFlush) ? agent.compaction.memoryFlush.model : void 0); } if (isRecord(agent.models)) for (const modelRef of Object.keys(agent.models)) pushModelRef(`${path}.models.${modelRef}`, modelRef); }; const root = isRecord(config) ? config : {}; const agents = isRecord(root.agents) ? root.agents : {}; collectFromAgent("agents.defaults", agents.defaults); if (Array.isArray(agents.list)) for (const [index, entry] of agents.list.entries()) collectFromAgent(`agents.list.${index}`, entry); if (options.includeChannelModelOverrides !== false) { const channels = isRecord(root.channels) ? root.channels : {}; const modelByChannel = isRecord(channels.modelByChannel) ? channels.modelByChannel : {}; for (const [channelId, channelMap] of Object.entries(modelByChannel)) { if (!isRecord(channelMap)) continue; for (const [targetId, modelRef] of Object.entries(channelMap)) pushModelRef(`channels.modelByChannel.${channelId}.${targetId}`, modelRef); } } const hooks = isRecord(root.hooks) ? root.hooks : {}; if (Array.isArray(hooks.mappings)) for (const [index, mapping] of hooks.mappings.entries()) pushModelRef(`hooks.mappings.${index}.model`, isRecord(mapping) ? mapping.model : void 0); pushModelRef("hooks.gmail.model", isRecord(hooks.gmail) ? hooks.gmail.model : void 0); pushModelRef("messages.tts.summaryModel", isRecord(root.messages) && isRecord(root.messages.tts) ? root.messages.tts.summaryModel : void 0); pushModelRef("channels.discord.voice.model", isRecord(root.channels) && isRecord(root.channels.discord) && isRecord(root.channels.discord.voice) ? root.channels.discord.voice.model : void 0); return refs; } /** Collect only configured model reference values. */ function collectConfiguredModelRefValues(config, options) { return collectConfiguredModelRefs(config, options).map((ref) => ref.value); } //#endregion //#region src/channels/plugins/package-state-probes.ts /** * Bundled channel package-state probes. * * Resolves lightweight configured/auth state checkers from package metadata and source overlays. */ const log = createSubsystemLogger("channels"); const sourcePackageStateLoaderCache = /* @__PURE__ */ new Map(); function isSourceModulePath(modulePath) { return /\.(?:c|m)?tsx?$/iu.test(modulePath); } function loadChannelPackageStateModule(params) { try { return loadChannelPluginModule(params); } catch (error) { if (!isSourceModulePath(params.modulePath)) throw error; return getCachedPluginModuleLoader({ cache: sourcePackageStateLoaderCache, modulePath: params.modulePath, importerUrl: import.meta.url, tryNative: true, cacheScopeKey: "channel-package-state" })(params.modulePath); } } function hasNonEmptyEnvValue(env, key) { return typeof env?.[key] === "string" && env[key].trim().length > 0; } function resolveSourceBundledPluginRoot(rootDir) { const pluginRoot = path.resolve(rootDir); const extensionsDir = path.dirname(pluginRoot); if (path.basename(extensionsDir) !== "extensions") return null; const packageRoot = path.dirname(extensionsDir); if (path.basename(packageRoot) === "dist" || path.basename(packageRoot) === "dist-runtime") return null; return { packageRoot, dirName: path.basename(pluginRoot) }; } function isBundledSourceOverlayPluginRoot(rootDir) { const pluginRoot = path.resolve(rootDir); return isBundledSourceOverlayPath({ sourcePath: pluginRoot }) || path.basename(path.dirname(pluginRoot)) === "extensions" && isBundledSourceOverlayPath({ sourcePath: path.dirname(pluginRoot) }); } function listBuiltBundledPackageStateModules(params) { if (isBundledSourceOverlayPluginRoot(params.rootDir)) return []; const sourceRoot = resolveSourceBundledPluginRoot(params.rootDir); if (!sourceRoot) return []; const locations = []; for (const rootDir of [path.join(sourceRoot.packageRoot, "dist", "extensions", sourceRoot.dirName), path.join(sourceRoot.packageRoot, "dist-runtime", "extensions", sourceRoot.dirName)]) { const modulePath = resolveExistingPluginModulePath(rootDir, params.specifier); if (fs.existsSync(modulePath) && !isSourceModulePath(modulePath)) locations.push({ modulePath, rootDir }); } return locations; } function resolveChannelPackageStateModuleLocation(params) { return { modulePath: resolveExistingPluginModulePath(params.entry.rootDir, params.specifier), rootDir: params.entry.rootDir }; } function listChannelPackageStateModuleLocations(params) { const source = resolveChannelPackageStateModuleLocation(params); return [...listBuiltBundledPackageStateModules({ rootDir: params.entry.rootDir, specifier: params.specifier }).filter((location) => location.modulePath !== source.modulePath), source]; } function resolveChannelPackageStateMetadata(entry, metadataKey) { const metadata = entry.channel[metadataKey]; if (!metadata || typeof metadata !== "object") return null; const specifier = normalizeOptionalString(metadata.specifier) ?? ""; const exportName = normalizeOptionalString(metadata.exportName) ?? ""; const envMetadata = "env" in metadata ? metadata.env : void 0; const allOf = normalizeTrimmedStringList(envMetadata?.allOf); const anyOf = normalizeTrimmedStringList(envMetadata?.anyOf); const env = allOf.length > 0 || anyOf.length > 0 ? { allOf, anyOf } : void 0; if ((!specifier || !exportName) && !env) return null; return { ...specifier ? { specifier } : {}, ...exportName ? { exportName } : {}, ...env ? { env } : {} }; } function listChannelPackageStateCatalog(metadataKey, discovery) { return listChannelCatalogEntries({ origin: "bundled", discovery }).filter((entry) => Boolean(resolveChannelPackageStateMetadata(entry, metadataKey))); } function resolveChannelPackageStateChecker(params) { const metadata = resolveChannelPackageStateMetadata(params.entry, params.metadataKey); if (!metadata) return null; if (metadata.env) return ({ env }) => { const allOf = metadata.env?.allOf ?? []; const anyOf = metadata.env?.anyOf ?? []; return allOf.every((key) => hasNonEmptyEnvValue(env, key)) && (anyOf.length === 0 || anyOf.some((key) => hasNonEmptyEnvValue(env, key))); }; let loadError; for (const location of listChannelPackageStateModuleLocations({ entry: params.entry, specifier: metadata.specifier })) try { const checker = loadChannelPackageStateModule({ modulePath: location.modulePath, rootDir: location.rootDir })[metadata.exportName]; if (typeof checker !== "function") throw new Error(`missing ${params.metadataKey} export ${metadata.exportName}`); return checker; } catch (error) { loadError = error; } if (loadError) { const detail = formatErrorMessage(loadError); log.warn(`[channels] failed to load ${params.metadataKey} checker for ${params.entry.pluginId}: ${detail}`); } return null; } function resolvePackageStateChannelId(entry) { return normalizeOptionalString(entry.channel.id); } /** * Lists bundled channel ids that declare the requested package-state metadata. */ function listBundledChannelIdsForPackageState(metadataKey, discovery) { return listChannelPackageStateCatalog(metadataKey, discovery).map((entry) => resolvePackageStateChannelId(entry)).filter((channelId) => Boolean(channelId)).toSorted((left, right) => left.localeCompare(right)); } /** * Returns whether a bundled channel reports configured/auth package state. */ function hasBundledChannelPackageState(params) { const requestedChannelId = normalizeOptionalString(params.channelId); const entry = listChannelPackageStateCatalog(params.metadataKey, params.discovery).find((candidate) => resolvePackageStateChannelId(candidate) === requestedChannelId); if (!entry) return false; const checker = resolveChannelPackageStateChecker({ entry, metadataKey: params.metadataKey }); return checker ? checker({ cfg: params.cfg, env: params.env }) : false; } //#endregion //#region src/channels/plugins/persisted-auth-state.ts /** * Lists bundled channels that declare persisted-auth state metadata. */ function listBundledChannelIdsWithPersistedAuthState(discovery) { return listBundledChannelIdsForPackageState("persistedAuthState", discovery); } /** * Returns whether a bundled channel reports persisted auth state. */ function hasBundledChannelPersistedAuthState(params) { return hasBundledChannelPackageState({ metadataKey: "persistedAuthState", channelId: params.channelId, cfg: params.cfg, env: params.env, discovery: params.discovery }); } //#endregion //#region src/channels/config-presence.ts /** * Channel configuration presence detection. * * Finds channels made available by config, env, persisted auth, or plugin discovery signals. */ const IGNORED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]); /** Returns true when a channel config entry contains settings beyond enabled/disabled state. */ function hasMeaningfulChannelConfig(value) { if (!isRecord$1(value)) return false; return Object.keys(value).some((key) => key !== "enabled"); } /** Lists channels explicitly disabled in config so activation logic can suppress auto-detection. */ function listExplicitlyDisabledChannelIdsForConfig(cfg) { const channels = isRecord$1(cfg.channels) ? cfg.channels : null; if (!channels) return []; return Object.entries(channels).filter(([, value]) => isRecord$1(value) && value.enabled === false).map(([channelId]) => normalizeOptionalLowercaseString(channelId)).filter((channelId) => Boolean(channelId)); } function listChannelEnvPrefixes(channelIds) { return channelIds.map((channelId) => [`${channelId.replace(/[^a-z0-9]+/gi, "_").toUpperCase()}_`, channelId]); } function hasPersistedChannelState(env) { return fs.existsSync(resolveStateDir(env, os.homedir)); } let persistedAuthStateChannelIds = null; function listPersistedAuthStateChannelIds(options) { const override = options.persistedAuthStateProbe?.listChannelIds(); if (override) return override; if (options.discovery) return listBundledChannelIdsWithPersistedAuthState(options.discovery); if (persistedAuthStateChannelIds) return persistedAuthStateChannelIds; persistedAuthStateChannelIds = listBundledChannelIdsWithPersistedAuthState(); return persistedAuthStateChannelIds; } function hasPersistedAuthState(params) { const override = params.options.persistedAuthStateProbe; if (override) return override.hasState(params); return hasBundledChannelPersistedAuthState({ channelId: params.channelId, cfg: params.cfg, env: params.env, discovery: params.options.discovery }); } /** Lists channel ids detected from config, env vars, or persisted auth state. */ function listPotentialConfiguredChannelIds(cfg, env = process.env, options = {}) { return uniqueStrings(listPotentialConfiguredChannelPresenceSignals(cfg, env, options).map((signal) => signal.channelId)); } /** Lists deduplicated channel presence signals with their detection source. */ function listPotentialConfiguredChannelPresenceSignals(cfg, env = process.env, options = {}) { const signals = []; const seenSignals = /* @__PURE__ */ new Set(); const addSignal = (channelId, source) => { const key = `${source}:${channelId}`; if (seenSignals.has(key)) return; seenSignals.add(key); signals.push({ channelId, source }); }; const configuredChannelIds = /* @__PURE__ */ new Set(); const channelEnvPrefixes = listChannelEnvPrefixes(options.channelIds ?? listBundledChannelIds(env, options.discovery)); const channels = isRecord$1(cfg.channels) ? cfg.channels : null; if (channels) for (const [key, value] of Object.entries(channels)) { if (IGNORED_CHANNEL_CONFIG_KEYS.has(key)) continue; if (hasMeaningfulChannelConfig(value)) { configuredChannelIds.add(key); addSignal(key, "config"); } } for (const [key, value] of Object.entries(env)) { if (!hasNonEmptyString(value)) continue; for (const [prefix, channelId] of channelEnvPrefixes) if (key.startsWith(prefix)) { configuredChannelIds.add(channelId); addSignal(channelId, "env"); } } if (options.includePersistedAuthState !== false && hasPersistedChannelState(env)) { for (const channelId of listPersistedAuthStateChannelIds(options)) if (hasPersistedAuthState({ channelId, cfg, env, options })) { configuredChannelIds.add(channelId); addSignal(channelId, "persisted-auth"); } } return signals.filter((signal) => configuredChannelIds.has(signal.channelId)); } //#endregion //#region src/secrets/channel-env-var-names.ts /** Ambient process env names that are too common to imply channel configuration. */ const UNSAFE_CHANNEL_ENV_VAR_TRIGGER_NAMES = new Set([ "CI", "HOME", "LANG", "LC_ALL", "LC_CTYPE", "LOGNAME", "NODE_ENV", "OLDPWD", "PATH", "PWD", "SHELL", "SSH_AUTH_SOCK", "TEMP", "TERM", "TMP", "TMPDIR", "USER" ]); /** * Returns whether a channel env var name is safe to treat as a credential/config trigger. */ function isSafeChannelEnvVarTriggerName(key) { const normalized = key.trim().toUpperCase(); return /^[A-Z][A-Z0-9_]*$/.test(normalized) && !UNSAFE_CHANNEL_ENV_VAR_TRIGGER_NAMES.has(normalized); } //#endregion export { listPotentialConfiguredChannelPresenceSignals as a, AGENT_MODEL_CONFIG_KEYS as c, listPotentialConfiguredChannelIds as i, collectConfiguredModelRefValues as l, hasMeaningfulChannelConfig as n, hasBundledChannelPackageState as o, listExplicitlyDisabledChannelIdsForConfig as r, listBundledChannelIdsForPackageState as s, isSafeChannelEnvVarTriggerName as t, collectConfiguredModelRefs as u };