UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

570 lines (569 loc) 24.3 kB
import { a as getPluginCache, f as withPluginCache, n as bindPluginCacheRoot, o as getPluginCacheRoot, p as getPluginSdkHostFacts, s as getPluginCacheSource } from "./plugin-cache-DGWspMEc.js"; import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { s as openRootFileSync } from "./boundary-file-read-uaJcf6X6.js"; import { I as sameFileIdentity } from "./fs-safe-B6pvPGnf.js"; import { i as isPathStrictlyInside, r as isPathInside } from "./path-guards-Cp-mGr3-.js"; import { a as pluginCacheRealpathSync, r as pluginCacheExistsSync } from "./plugin-cache-files-DLPF_Tw2.js"; import { a as listWorkspacePackageExportAliasEntries, c as resolvePluginLoaderTryNative, d as clearPluginModuleRequireCache, h as tryNativeRequireModule, i as isPluginSdkAliasSpecifier, m as tryNativeRequireJavaScriptModule, n as buildPluginLoaderJitiOptions, o as preparePluginLoaderAliases, r as createPluginLoaderModuleCacheKey } from "./sdk-alias-BMTmaiMP.js"; import Module, { createRequire } from "node:module"; import fs from "node:fs"; import { fileURLToPath, pathToFileURL } from "node:url"; import path from "node:path"; //#region src/shared/import-specifier.ts /** * On Windows, Node's ESM loader requires absolute paths to be expressed as * file:// URLs. Raw drive-letter paths like C:\... are parsed as URL schemes. */ function toSafeImportPath(specifier) { if (process.platform !== "win32") return specifier; if (specifier.startsWith("file://")) return specifier; if (path.win32.isAbsolute(specifier)) return pathToFileURL(specifier, { windows: true }).href; return specifier; } //#endregion //#region src/plugins/plugin-sdk-native-resolver.ts /** Installs native Node resolution aliases so plugins can import the OpenClaw SDK in dev and tests. */ const moduleWithResolver = Module; const nodeResolveFilenameProperty = "_resolveFilename"; const INTERNAL_CORE_PACKAGE_ALIASES = [ { packageName: "@openclaw/markdown-core", packageDir: "markdown-core", subpaths: [ ["", "index.ts"], ["code-spans", "code-spans.ts"], ["fences", "fences.ts"], ["frontmatter", "frontmatter.ts"], ["ir", "ir.ts"], ["render", "render.ts"], ["render-aware-chunking", "render-aware-chunking.ts"], ["tables", "tables.ts"], ["types", "types.ts"] ] }, { packageName: "@openclaw/ai", packageDir: "ai", subpaths: [ ["", "index.ts"], ["providers", "providers.ts"], ["transports", "transports.ts"], ["diagnostics", path.join("utils", "diagnostics.ts")], ["event-stream", path.join("utils", "event-stream.ts")], ["types", "types.ts"], ["validation", "validation.ts"], ["internal/anthropic", path.join("internal", "anthropic.ts")], ["internal/openai", path.join("internal", "openai.ts")], ["internal/openai-responses-payload-policy", path.join("internal", "openai-responses-payload-policy.ts")], ["internal/retry-after", path.join("internal", "retry-after.ts")], ["internal/runtime", path.join("internal", "runtime.ts")], ["internal/shared", path.join("internal", "shared.ts")] ] }, { packageName: "@openclaw/llm-core", packageDir: "llm-core", subpaths: [ ["", "index.ts"], ["diagnostics", path.join("utils", "diagnostics.ts")], ["event-stream", path.join("utils", "event-stream.ts")], ["types", "types.ts"], ["validation", "validation.ts"] ] } ]; let installed = false; let previousResolveFilename; function resolveLoaderModulePath(options) { return options.modulePath ?? fileURLToPath(options.moduleUrl ?? import.meta.url); } function isNativeLoadableSdkTarget(targetPath) { switch (path.extname(targetPath)) { case ".cjs": case ".js": case ".mjs": return true; default: return false; } } const normalizePathForBoundary = (targetPath) => pluginCacheRealpathSync(targetPath) ?? path.resolve(targetPath); function findNearestPackageRoot(modulePath) { const normalizedModulePath = path.resolve(modulePath); const roots = getPluginCache().sdk.native.nearestPackageRoots; const cached = roots.get(normalizedModulePath); if (cached) return cached; let cursor = path.dirname(normalizedModulePath); for (let i = 0; i < 12; i += 1) { if (pluginCacheExistsSync(path.join(cursor, "package.json"))) { roots.set(normalizedModulePath, cursor); return cursor; } const parent = path.dirname(cursor); if (parent === cursor) break; cursor = parent; } const fallback = path.dirname(normalizedModulePath); roots.set(normalizedModulePath, fallback); return fallback; } function findBundledPluginRoot(modulePath) { const resolvedModulePath = normalizePathForBoundary(modulePath); const packageRoot = normalizePathForBoundary(resolveLoaderPackageRootFromModulePath(modulePath)); for (const relativeRoot of [ "extensions", "dist/extensions", "dist-runtime/extensions" ]) { const bundledRoot = path.join(packageRoot, relativeRoot); if (!isPathStrictlyInside(bundledRoot, resolvedModulePath)) continue; const [pluginId] = path.relative(bundledRoot, resolvedModulePath).split(path.sep); if (pluginId) return path.join(bundledRoot, pluginId); } } function resolveLoaderPackageRootFromModulePath(modulePath) { const normalizedModulePath = path.resolve(modulePath); const roots = getPluginCache().sdk.native.loaderPackageRoots; const cached = roots.get(normalizedModulePath); if (cached) return cached; let cursor = path.dirname(normalizedModulePath); for (let i = 0; i < 12; i += 1) { const packageJsonPath = path.join(cursor, "package.json"); if (pluginCacheExistsSync(packageJsonPath)) { const facts = getPluginSdkHostFacts(getPluginCache().sdk, cursor); if (facts.nativePackage === void 0) try { const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); facts.nativePackage = isRecord(parsed) ? { ...typeof parsed.name === "string" ? { name: parsed.name } : {}, hasOpenClawBin: isRecord(parsed.bin) && typeof parsed.bin.openclaw === "string" } : null; } catch { facts.nativePackage = null; } if (facts.nativePackage?.name === "openclaw" || facts.nativePackage?.hasOpenClawBin) { roots.set(normalizedModulePath, cursor); return cursor; } } const parent = path.dirname(cursor); if (parent === cursor) break; cursor = parent; } const fallback = findNearestPackageRoot(modulePath); roots.set(normalizedModulePath, fallback); return fallback; } function resolveInternalCorePackageHostRoot(modulePath) { const normalizedModulePath = path.resolve(modulePath); const internalCorePackageHostRoots = getPluginCache().sdk.native.hostRoots; const cached = internalCorePackageHostRoots.get(normalizedModulePath); if (cached) return cached; const packageRoot = normalizePathForBoundary(resolveLoaderPackageRootFromModulePath(normalizedModulePath)); internalCorePackageHostRoots.set(normalizedModulePath, packageRoot); return packageRoot; } function resolveAllowedParentRoot(modulePath) { const roots = getPluginCache().sdk.native.allowedParentRoots; const key = path.resolve(modulePath); const cached = roots.get(key); if (cached) return cached; const root = findBundledPluginRoot(modulePath) ?? findNearestPackageRoot(modulePath); roots.set(key, root); return root; } function resolveAllowedParentRoots(options) { const roots = /* @__PURE__ */ new Set(); if (options.pluginModulePath) roots.add(normalizePathForBoundary(resolveAllowedParentRoot(options.pluginModulePath))); for (const root of options.allowedParentRoots ?? []) roots.add(normalizePathForBoundary(root)); return [...roots]; } function isWithinRoot(candidate, root) { return isPathInside(root, normalizePathForBoundary(candidate)); } function resolveAliasTargetForParent(request, parent) { return resolveAliasTargetForParentPath(request, parent?.filename); } function resolveAliasTargetForParentUrl(request, parentUrl) { if (!parentUrl?.startsWith("file:")) return; try { return resolveAliasTargetForParentPath(request, fileURLToPath(parentUrl)); } catch { return; } } function resolveAliasTargetForParentPath(request, parentFilename) { const native = getPluginCache().sdk.native; if (parentFilename && isPluginSdkAliasSpecifier(request)) { for (const [root, prepare] of native.sdkProviders) if (isWithinRoot(parentFilename, root)) prepare(); } const entries = native.aliases.get(request); if (!entries || !parentFilename) return; return entries.find((entry) => isWithinRoot(parentFilename, entry.parentRoot))?.target; } function listPluginSdkNativeAliases(aliasMap) { const pluginSdkNativeAliasesByMap = getPluginCache().sdk.native.aliasesByMap; const cached = pluginSdkNativeAliasesByMap.get(aliasMap); if (cached) return cached; const aliases = Object.entries(aliasMap).filter(([specifier]) => isPluginSdkAliasSpecifier(specifier)).filter(([, target]) => isNativeLoadableSdkTarget(target)).flatMap(([specifier, target]) => { if (specifier.endsWith(".js")) return [[specifier, target]]; return [[specifier, target], [`${specifier}.js`, target]]; }); pluginSdkNativeAliasesByMap.set(aliasMap, aliases); return aliases; } function listInternalCorePackageNativeAliases(options, packageRoot = resolveInternalCorePackageHostRoot(resolveLoaderModulePath(options))) { const parentRoots = [ "src", "scripts", "packages", "test" ].map((segment) => path.join(packageRoot, segment)).filter((candidate) => pluginCacheExistsSync(candidate)).map(normalizePathForBoundary); if (parentRoots.length === 0) return []; const aliases = []; const internalCorePackageAliases = [...INTERNAL_CORE_PACKAGE_ALIASES, ...[ "media-core", "normalization-core", "acp-core" ].map((packageDir) => ({ packageName: `@openclaw/${packageDir}`, packageDir, subpaths: listWorkspacePackageExportAliasEntries({ packageRoot, packageName: `@openclaw/${packageDir}`, packageDir }).map((entry) => [entry.subpath, entry.srcFile]) }))]; for (const entry of internalCorePackageAliases) for (const [subpath, srcFile] of entry.subpaths) { const request = subpath ? `${entry.packageName}/${subpath}` : entry.packageName; const target = path.join(packageRoot, "packages", entry.packageDir, "src", srcFile); if (pluginCacheExistsSync(target)) aliases.push({ request, target, parentRoots }); } return aliases; } function installResolver() { if (installed || !moduleWithResolver[nodeResolveFilenameProperty]) return; previousResolveFilename = moduleWithResolver[nodeResolveFilenameProperty]; moduleWithResolver[nodeResolveFilenameProperty] = ((request, parent, isMain, options) => { const aliasTarget = resolveAliasTargetForParent(request, parent); if (aliasTarget) return aliasTarget; return previousResolveFilename?.(request, parent, isMain, options) ?? request; }); moduleWithResolver.registerHooks?.({ resolve(specifier, context, nextResolve) { const aliasTarget = resolveAliasTargetForParentUrl(specifier, context.parentURL); if (aliasTarget) return { shortCircuit: true, url: pathToFileURL(aliasTarget).href }; return nextResolve(specifier, context); } }); installed = true; } function registerNativeAlias(params) { const pluginSdkNativeAliases = getPluginCache().sdk.native.aliases; const entries = pluginSdkNativeAliases.get(params.request) ?? []; for (const parentRoot of params.parentRoots) { const existingIndex = entries.findIndex((entry) => entry.parentRoot === parentRoot); if (existingIndex !== -1) { entries[existingIndex] = { parentRoot, target: params.target }; continue; } entries.push({ parentRoot, target: params.target }); } if (entries.length > 0) pluginSdkNativeAliases.set(params.request, entries); } function clearNativeAliasesForParentRoots(parentRoots) { if (parentRoots.length === 0) return; const parentRootSet = new Set(parentRoots); for (const root of parentRoots) getPluginCache().sdk.native.sdkProviders.delete(root); const pluginSdkNativeAliases = getPluginCache().sdk.native.aliases; for (const [request, entries] of pluginSdkNativeAliases) { const nextEntries = entries.filter((entry) => !parentRootSet.has(entry.parentRoot)); if (nextEntries.length === 0) pluginSdkNativeAliases.delete(request); else pluginSdkNativeAliases.set(request, nextEntries); } } function registerInternalCorePackageNativeAliases(options) { const packageRoot = resolveInternalCorePackageHostRoot(resolveLoaderModulePath(options)); const registeredInternalCorePackageHosts = getPluginCache().sdk.native.registeredHosts; if (registeredInternalCorePackageHosts.has(packageRoot)) return; for (const alias of listInternalCorePackageNativeAliases(options, packageRoot)) registerNativeAlias(alias); registeredInternalCorePackageHosts.add(packageRoot); } function installOpenClawPluginSdkNativeResolver(options = {}) { const parentRoots = resolveAllowedParentRoots(options); clearNativeAliasesForParentRoots(parentRoots); const aliases = preparePluginLoaderAliases({ modulePath: options.pluginModulePath ?? resolveLoaderModulePath(options), argv1: options.argv1 ?? process.argv[1], moduleUrl: options.moduleUrl, pluginSdkResolution: "dist", devSourceRoot: options.devSourceRoot }); const native = getPluginCache().sdk.native; for (const parentRoot of parentRoots) native.sdkProviders.set(parentRoot, () => { const resolved = listPluginSdkNativeAliases(aliases.getAliasMap()); native.sdkProviders.delete(parentRoot); for (const [request, target] of resolved) registerNativeAlias({ request, target, parentRoots: [parentRoot] }); }); registerInternalCorePackageNativeAliases(options); installResolver(); } function installOpenClawInternalCorePackageNativeResolver(options = {}) { registerInternalCorePackageNativeAliases(options); installResolver(); return [...getPluginCache().sdk.native.aliases.keys()].toSorted(); } //#endregion //#region src/plugins/plugin-module-loader-cache.ts /** Caches plugin module loaders and native-load stats for runtime/source module imports. */ const MAX_TRACKED_SOURCE_TRANSFORM_TARGETS = 24; const requireForJiti = createRequire(import.meta.url); let createJitiLoaderFactory; const pluginModuleLoaderStats = { calls: 0, nativeHits: 0, nativeMisses: 0, sourceTransformForced: 0, sourceTransformFallbacks: 0, sourceTransformTargets: /* @__PURE__ */ new Map() }; function recordSourceTransformTarget(target) { const current = pluginModuleLoaderStats.sourceTransformTargets.get(target) ?? 0; pluginModuleLoaderStats.sourceTransformTargets.set(target, current + 1); if (pluginModuleLoaderStats.sourceTransformTargets.size <= MAX_TRACKED_SOURCE_TRANSFORM_TARGETS) return; let leastUsedTarget; let leastUsedCount = Number.POSITIVE_INFINITY; for (const [candidate, count] of pluginModuleLoaderStats.sourceTransformTargets) if (count < leastUsedCount) { leastUsedTarget = candidate; leastUsedCount = count; } if (leastUsedTarget) pluginModuleLoaderStats.sourceTransformTargets.delete(leastUsedTarget); } /** Returns process-local plugin module loader stats for diagnostics and tests. */ function getPluginModuleLoaderStats() { return { calls: pluginModuleLoaderStats.calls, nativeHits: pluginModuleLoaderStats.nativeHits, nativeMisses: pluginModuleLoaderStats.nativeMisses, sourceTransformForced: pluginModuleLoaderStats.sourceTransformForced, sourceTransformFallbacks: pluginModuleLoaderStats.sourceTransformFallbacks, topSourceTransformTargets: [...pluginModuleLoaderStats.sourceTransformTargets].toSorted((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).slice(0, 8).map(([target, count]) => ({ target, count })) }; } function loadCreateJitiLoaderFactory() { if (createJitiLoaderFactory) return createJitiLoaderFactory; const loaded = requireForJiti("jiti"); if (typeof loaded.createJiti !== "function") throw new Error("jiti module did not export createJiti"); createJitiLoaderFactory = loaded.createJiti; return createJitiLoaderFactory; } function retainModuleLifecycle(cache) { cache.disposeModules ??= () => { for (const [modulePath, source] of cache.sources) { const rootDir = source.boundaryRoot; if (!rootDir) continue; const extensionsDir = path.basename(rootDir) === "extensions" ? rootDir : path.dirname(rootDir); const distDir = path.dirname(extensionsDir); const dependencyRoot = path.basename(extensionsDir) === "extensions" && path.basename(distDir) === "dist" ? distDir : rootDir; clearPluginModuleRequireCache(modulePath, { dependencyRoot }); } }; } /** Direct native imports share the generation's dependency cleanup with transformed modules. */ function recordPluginModuleRoot(modulePath, rootDir) { const cache = getPluginCache(); getPluginCacheSource(modulePath, cache).boundaryRoot = rootDir; retainModuleLifecycle(cache); } function toSourceTransformImportPath(specifier) { if (process.platform === "win32" && path.isAbsolute(specifier)) return pathToFileURL(specifier).href; return toSafeImportPath(specifier); } function resolvePluginModuleLoaderCacheEntry(params) { const loaderFilename = toSafeImportPath(params.loaderFilename ?? params.modulePath); const tryNative = params.tryNative ?? resolvePluginLoaderTryNative(params.modulePath, params); const explicit = params.aliasMap ? { ...params.aliasMap } : void 0; const aliases = explicit ? { cacheKey: createPluginLoaderModuleCacheKey({ tryNative, aliasMap: explicit }), getAliasMap: () => explicit, resolveAlias: (specifier) => explicit[specifier] } : preparePluginLoaderAliases({ modulePath: params.modulePath, argv1: params.argvEntry ?? process.argv[1], moduleUrl: params.importerUrl, devSourceRoot: params.devSourceRoot, pluginSdkResolution: params.pluginSdkResolution }); const moduleConfigCacheKey = `${tryNative ? "native" : "transform"}\0${aliases.cacheKey}`; const transformOpenClawDependencies = params.transformOpenClawDependencies ?? tryNative; const cacheKey = `${moduleConfigCacheKey}\0transform-openclaw=${transformOpenClawDependencies ? "1" : "0"}`; const scopedCacheKey = `${loaderFilename}::${params.sharedCacheScopeKey ?? (params.cacheScopeKey ? `${params.cacheScopeKey}::${cacheKey}` : cacheKey)}`; return { loaderFilename, getAliasMap: aliases.getAliasMap, resolveAlias: aliases.resolveAlias, tryNative, transformOpenClawDependencies, cacheKey, scopedCacheKey }; } function createLazySourceTransformLoader(params) { let loadWithSourceTransform; return () => { if (loadWithSourceTransform) return loadWithSourceTransform; const jitiOptions = buildPluginLoaderJitiOptions(params.getAliasMap(), { modulePath: params.loaderFilename }); const jitiLoader = (params.createLoader ?? loadCreateJitiLoaderFactory())(params.loaderFilename, { ...jitiOptions, virtualModules: params.transformOpenClawDependencies ? void 0 : new Proxy({}, { has(_target, key) { return typeof key === "string" && isPluginSdkAliasSpecifier(key) && Boolean(params.resolveAlias(key)); }, get(_target, key) { const target = typeof key === "string" ? params.resolveAlias(key) : void 0; if (!target) return; const native = tryNativeRequireModule(target, { allowWindows: true, fallbackOnMissingDependency: true }); return native.ok ? native.moduleExport : jitiLoader(target); } }), nativeModules: params.transformOpenClawDependencies ? jitiOptions.nativeModules.filter((moduleName) => moduleName !== "openclaw") : jitiOptions.nativeModules, tryNative: false }); loadWithSourceTransform = (target) => jitiLoader(toSourceTransformImportPath(target)); return loadWithSourceTransform; }; } function createPluginModuleLoader(params) { const getLoadWithSourceTransform = createLazySourceTransformLoader({ ...params }); const loadCachedTarget = (target, load) => { const source = getPluginCacheSource(target, params.cache); const cached = source.variants.get(params.cacheKey)?.exports; if (cached) return cached.value; source.boundaryRoot = params.rootDir ?? source.boundaryRoot ?? path.dirname(target.startsWith("file:") ? fileURLToPath(target) : target); const loaded = withPluginCache(params.cache, load); source.variants.set(params.cacheKey, { exports: { value: loaded } }); return loaded; }; if (!params.tryNative) return (target) => loadCachedTarget(target, () => { pluginModuleLoaderStats.calls += 1; pluginModuleLoaderStats.sourceTransformForced += 1; recordSourceTransformTarget(target); return getLoadWithSourceTransform()(target); }); return (target) => loadCachedTarget(target, () => { pluginModuleLoaderStats.calls += 1; const native = tryNativeRequireJavaScriptModule(target, { allowWindows: true, aliasMap: params.resolveAlias, fallbackOnMissingDependency: true }); if (native.ok) { pluginModuleLoaderStats.nativeHits += 1; return native.moduleExport; } pluginModuleLoaderStats.nativeMisses += 1; pluginModuleLoaderStats.sourceTransformFallbacks += 1; recordSourceTransformTarget(target); return getLoadWithSourceTransform()(target); }); } function getCachedPluginModuleLoader(params) { const cacheEntry = resolvePluginModuleLoaderCacheEntry(params); const cache = getPluginCache(); const cached = cache.moduleLoaders.get(cacheEntry.scopedCacheKey); if (cached) return cached; installOpenClawInternalCorePackageNativeResolver({ moduleUrl: params.importerUrl }); retainModuleLifecycle(cache); const loader = createPluginModuleLoader({ cache, cacheKey: cacheEntry.scopedCacheKey, rootDir: params.rootDir, loaderFilename: cacheEntry.loaderFilename, getAliasMap: cacheEntry.getAliasMap, resolveAlias: cacheEntry.resolveAlias, tryNative: cacheEntry.tryNative, transformOpenClawDependencies: cacheEntry.transformOpenClawDependencies, ...params.createLoader ? { createLoader: params.createLoader } : {} }); cache.moduleLoaders.set(cacheEntry.scopedCacheKey, loader); return loader; } /** Validates an entry once per generation without changing its module export shape. */ function preparePluginModule(params) { const cache = getPluginCache(); let source = getPluginCacheSource(params.modulePath, cache); const boundaryKey = `${getPluginCacheRoot(params.boundaryRoot).rootDir}\0${params.rejectHardlinks}`; if (source.validatedBoundaries.has(boundaryKey)) return { source, modulePath: source.modulePath ?? params.modulePath }; const opened = openRootFileSync({ absolutePath: params.modulePath, rootPath: params.boundaryRoot, boundaryLabel: params.boundaryLabel, rejectHardlinks: params.rejectHardlinks }); if (!opened.ok) throw new Error(`Unable to open ${params.surfaceLabel}`, { cause: opened.error }); fs.closeSync(opened.fd); if (!sameFileIdentity(opened.stat, fs.statSync(opened.path))) throw new Error(`${params.surfaceLabel} changed after validation`); const root = bindPluginCacheRoot(params.boundaryRoot, opened.rootRealPath); root.publicSurfaceBoundary ??= { boundaryLabel: params.boundaryLabel, rejectHardlinks: params.rejectHardlinks }; cache.sourceAliases.set(path.resolve(params.modulePath), opened.path); source = getPluginCacheSource(opened.path, cache); source.modulePath = opened.path; source.validatedBoundaries.add(`${opened.rootRealPath}\0${params.rejectHardlinks}`); retainModuleLifecycle(cache); return { source, modulePath: opened.path }; } /** Public artifacts and SDK facades share one validated module, including circular imports. */ function loadPluginPublicSurfaceModuleSync(params) { const { source, modulePath } = preparePluginModule(params); const cached = source.publicSurface?.exports; if (cached) return cached; const sentinel = {}; source.publicSurface = { exports: sentinel }; source.boundaryRoot = params.boundaryRoot; try { Object.assign(sentinel, params.loadModule(modulePath)); return sentinel; } catch (error) { delete source.publicSurface; source.validatedBoundaries.clear(); throw error; } } function getCachedPluginSourceModuleLoader(params) { return getCachedPluginModuleLoader({ ...params, tryNative: false }); } //#endregion export { preparePluginModule as a, installOpenClawPluginSdkNativeResolver as c, loadPluginPublicSurfaceModuleSync as i, toSafeImportPath as l, getCachedPluginSourceModuleLoader as n, recordPluginModuleRoot as o, getPluginModuleLoaderStats as r, installOpenClawInternalCorePackageNativeResolver as s, getCachedPluginModuleLoader as t };