UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

517 lines (516 loc) 21.7 kB
import { t as PluginLruCache } from "./plugin-cache-primitives-BaxqicKH.js"; import { n as buildPluginLoaderJitiOptions, p as resolvePluginLoaderModuleConfig, r as createPluginLoaderModuleCacheKey, s as listWorkspacePackageExportAliasEntries, t as buildPluginLoaderAliasMap } from "./sdk-alias-ZgondhPg.js"; import Module, { createRequire } from "node:module"; import { fileURLToPath, pathToFileURL } from "node:url"; import fs from "node:fs"; 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/native-module-require.ts const nodeRequire = createRequire(import.meta.url); const moduleWithResolver$1 = Module; /** True for file extensions Node can load through the native JS module loader. */ function isJavaScriptModulePath(modulePath) { return [ ".js", ".mjs", ".cjs" ].includes(path.extname(modulePath).toLowerCase()); } function isMissingTargetModuleError(error, modulePath) { if (error.code !== "MODULE_NOT_FOUND" || typeof error.message !== "string") return false; const firstLine = error.message.split("\n", 1)[0] ?? ""; return firstLine.includes(`'${modulePath}'`) || firstLine.includes(`"${modulePath}"`); } function isSourceTransformFallbackError(error, modulePath) { if (!error || typeof error !== "object") return false; const candidate = error; const code = candidate.code; return code === "ERR_REQUIRE_ESM" || code === "ERR_REQUIRE_ASYNC_MODULE" || isMissingTargetModuleError(candidate, modulePath); } /** Attempts native require before falling back to source transform paths. */ function tryNativeRequireJavaScriptModule(modulePath, options = {}) { if (process.platform === "win32" && options.allowWindows !== true) return { ok: false }; if (!isJavaScriptModulePath(modulePath)) return { ok: false }; try { return { ok: true, moduleExport: requireWithOptionalAliases(modulePath, options.aliasMap) }; } catch (error) { const code = error && typeof error === "object" ? error.code : void 0; if (isSourceTransformFallbackError(error, modulePath) || options.fallbackOnNativeError || options.fallbackOnMissingDependency === true && (code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND")) return { ok: false }; throw error; } } /** Clears a native-loaded module and dependency subtree under the plugin dependency root. */ function clearNativeRequireJavaScriptModuleCache(modulePath, options = {}) { if (!isJavaScriptModulePath(modulePath)) return; try { const resolved = nodeRequire.resolve(modulePath); clearRequireCacheSubtree(resolved, resolveRequireCachePath(options.dependencyRoot ?? path.dirname(resolved)), /* @__PURE__ */ new Set()); } catch {} } function resolveRequireCachePath(targetPath) { try { return fs.realpathSync.native(targetPath); } catch { return path.resolve(targetPath); } } function clearRequireCacheSubtree(resolvedPath, dependencyRoot, seen) { if (seen.has(resolvedPath)) return; seen.add(resolvedPath); const cached = nodeRequire.cache[resolvedPath]; if (cached) { for (const child of cached.children) if (isPathInsideOrSame(dependencyRoot, child.id)) clearRequireCacheSubtree(child.id, dependencyRoot, seen); } delete nodeRequire.cache[resolvedPath]; } function isPathInsideOrSame(root, target) { const relative = path.relative(root, target); return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative); } function requireWithOptionalAliases(modulePath, aliasMap) { return withNativeRequireAliases(aliasMap, () => nodeRequire(modulePath)); } /** Runs a native require block with temporary CJS/ESM alias hooks and restores both afterward. */ function withNativeRequireAliases(aliasMap, run) { if (!aliasMap || Object.keys(aliasMap).length === 0 || !moduleWithResolver$1["_resolveFilename"]) return run(); const originalResolveFilename = moduleWithResolver$1["_resolveFilename"]; const esmHooks = moduleWithResolver$1.registerHooks?.({ resolve(specifier, context, nextResolve) { const aliasTarget = aliasMap[specifier]; if (aliasTarget) return { shortCircuit: true, url: pathToFileURL(aliasTarget).href }; return nextResolve(specifier, context); } }); moduleWithResolver$1["_resolveFilename"] = ((request, parent, isMain, options) => { const aliasTarget = aliasMap[request]; if (aliasTarget) return aliasTarget; return originalResolveFilename(request, parent, isMain, options); }); try { return run(); } finally { moduleWithResolver$1["_resolveFilename"] = originalResolveFilename; esmHooks?.deregister(); } } //#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 PLUGIN_SDK_PACKAGE_PREFIXES = ["openclaw/plugin-sdk", "@openclaw/plugin-sdk"]; const INTERNAL_CORE_PACKAGE_ALIASES = [ { packageName: "@openclaw/normalization-core", packageDir: "normalization-core", subpaths: [ ["", "index.ts"], ["number-coercion", "number-coercion.ts"], ["record-coerce", "record-coerce.ts"], ["string-coerce", "string-coerce.ts"], ["string-normalization", "string-normalization.ts"] ] }, { packageName: "@openclaw/media-core", packageDir: "media-core", subpaths: [ ["", "index.ts"], ["base64", "base64.ts"], ["constants", "constants.ts"], ["content-length", "content-length.ts"], ["file-name", "file-name.ts"], ["inbound-path-policy", "inbound-path-policy.ts"], ["inline-image-data-url", "inline-image-data-url.ts"], ["media-source-url", "media-source-url.ts"], ["mime", "mime.ts"], ["read-byte-stream-with-limit", "read-byte-stream-with-limit.ts"], ["read-response-with-limit", "read-response-with-limit.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"] ] } ]; const pluginSdkNativeAliases = /* @__PURE__ */ new Map(); let installed = false; let previousResolveFilename; function resolveLoaderModulePath(options) { return options.modulePath ?? fileURLToPath(options.moduleUrl ?? import.meta.url); } function isPluginSdkAliasSpecifier(specifier) { return PLUGIN_SDK_PACKAGE_PREFIXES.some((prefix) => specifier === prefix || specifier.startsWith(`${prefix}/`)); } function isNativeLoadableSdkTarget(targetPath) { switch (path.extname(targetPath)) { case ".cjs": case ".js": case ".mjs": return true; default: return false; } } function normalizePathForBoundary(candidate) { try { return fs.realpathSync(candidate); } catch { return path.resolve(candidate); } } function findNearestPackageRoot(modulePath) { let cursor = path.dirname(path.resolve(modulePath)); for (let i = 0; i < 12; i += 1) { if (fs.existsSync(path.join(cursor, "package.json"))) return cursor; const parent = path.dirname(cursor); if (parent === cursor) break; cursor = parent; } return path.dirname(path.resolve(modulePath)); } 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); const relative = path.relative(bundledRoot, resolvedModulePath); if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) continue; const [pluginId] = relative.split(path.sep); if (pluginId) return path.join(bundledRoot, pluginId); } } function resolveLoaderPackageRootFromModulePath(modulePath) { let cursor = path.dirname(path.resolve(modulePath)); for (let i = 0; i < 12; i += 1) { const packageJsonPath = path.join(cursor, "package.json"); if (fs.existsSync(packageJsonPath)) try { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); if (packageJson.name === "openclaw" || typeof packageJson.bin === "object" && packageJson.bin !== null && typeof packageJson.bin.openclaw === "string") return cursor; } catch {} const parent = path.dirname(cursor); if (parent === cursor) break; cursor = parent; } return findNearestPackageRoot(modulePath); } function resolveAllowedParentRoot(modulePath) { return findBundledPluginRoot(modulePath) ?? findNearestPackageRoot(modulePath); } 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) { const relative = path.relative(root, normalizePathForBoundary(candidate)); return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative); } 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 entries = pluginSdkNativeAliases.get(request); if (!entries || !parentFilename) return; return entries.find((entry) => isWithinRoot(parentFilename, entry.parentRoot))?.target; } function listPluginSdkNativeAliases(options) { const modulePath = options.pluginModulePath ?? resolveLoaderModulePath(options); return Object.entries(buildPluginLoaderAliasMap(modulePath, options.argv1 ?? process.argv[1], options.moduleUrl, "dist", options.devSourceRoot)).filter(([specifier]) => isPluginSdkAliasSpecifier(specifier)).filter(([, target]) => isNativeLoadableSdkTarget(target)).flatMap(([specifier, target]) => { if (specifier.endsWith(".js")) return [[specifier, target]]; return [[specifier, target], [`${specifier}.js`, target]]; }); } function listInternalCorePackageNativeAliases(options) { const packageRoot = resolveLoaderPackageRootFromModulePath(resolveLoaderModulePath(options)); const parentRoots = [ "src", "scripts", "packages", "test" ].map((segment) => path.join(packageRoot, segment)).filter((candidate) => fs.existsSync(candidate)).map(normalizePathForBoundary); if (parentRoots.length === 0) return []; const aliases = []; const internalCorePackageAliases = [...INTERNAL_CORE_PACKAGE_ALIASES, { packageName: "@openclaw/acp-core", packageDir: "acp-core", subpaths: listWorkspacePackageExportAliasEntries({ packageRoot, packageName: "@openclaw/acp-core", packageDir: "acp-core" }).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 (fs.existsSync(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 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 [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 installOpenClawPluginSdkNativeResolver(options = {}) { const parentRoots = resolveAllowedParentRoots(options); clearNativeAliasesForParentRoots(parentRoots); for (const [specifier, target] of listPluginSdkNativeAliases(options)) registerNativeAlias({ request: specifier, target, parentRoots }); for (const alias of listInternalCorePackageNativeAliases(options)) registerNativeAlias(alias); installResolver(); return [...pluginSdkNativeAliases.keys()].toSorted(); } function installOpenClawInternalCorePackageNativeResolver(options = {}) { for (const alias of listInternalCorePackageNativeAliases(options)) registerNativeAlias(alias); installResolver(); return [...pluginSdkNativeAliases.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 DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES = 128; 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 createPluginModuleLoaderCache(maxEntries = DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES) { return new PluginLruCache(maxEntries); } function toSourceTransformImportPath(specifier) { if (process.platform === "win32" && path.isAbsolute(specifier)) return pathToFileURL(specifier).href; return toSafeImportPath(specifier); } function resolveDefaultPluginModuleLoaderConfig(params) { return resolvePluginLoaderModuleConfig({ modulePath: params.modulePath, argv1: params.argvEntry ?? process.argv[1], moduleUrl: params.importerUrl, devSourceRoot: params.devSourceRoot, ...params.preferBuiltDist ? { preferBuiltDist: true } : {}, ...params.pluginSdkResolution ? { pluginSdkResolution: params.pluginSdkResolution } : {} }); } function resolvePluginModuleLoaderCacheEntry(params) { const loaderFilename = toSafeImportPath(params.loaderFilename ?? params.modulePath); const hasAliasOverride = Boolean(params.aliasMap); const hasTryNativeOverride = typeof params.tryNative === "boolean"; const defaultConfig = hasAliasOverride || hasTryNativeOverride ? resolveDefaultPluginModuleLoaderConfig(params) : null; const canReuseDefaultCacheKey = defaultConfig !== null && (!hasAliasOverride || params.aliasMap === defaultConfig.aliasMap) && (!hasTryNativeOverride || params.tryNative === defaultConfig.tryNative); const resolved = defaultConfig ? { tryNative: params.tryNative ?? defaultConfig.tryNative, aliasMap: params.aliasMap ?? defaultConfig.aliasMap, cacheKey: canReuseDefaultCacheKey ? defaultConfig.cacheKey : void 0 } : resolveDefaultPluginModuleLoaderConfig(params); const { tryNative, aliasMap } = resolved; const cacheKey = resolved.cacheKey ?? createPluginLoaderModuleCacheKey({ tryNative, aliasMap }); return { loaderFilename, aliasMap, tryNative, cacheKey, scopedCacheKey: `${loaderFilename}::${params.sharedCacheScopeKey ?? (params.cacheScopeKey ? `${params.cacheScopeKey}::${cacheKey}` : cacheKey)}` }; } function createLazySourceTransformLoader(params) { let loadWithSourceTransform; return () => { if (loadWithSourceTransform) return loadWithSourceTransform; const jitiLoader = (params.createLoader ?? loadCreateJitiLoaderFactory())(params.loaderFilename, { ...buildPluginLoaderJitiOptions(params.aliasMap, { modulePath: params.loaderFilename }), tryNative: params.sourceTransformTryNative }); loadWithSourceTransform = new Proxy(jitiLoader, { apply(target, thisArg, argArray) { const [first, ...rest] = argArray; if (typeof first === "string") return Reflect.apply(target, thisArg, [toSourceTransformImportPath(first), ...rest]); return Reflect.apply(target, thisArg, argArray); } }); return loadWithSourceTransform; }; } function createPluginModuleLoader(params) { const getLoadWithSourceTransform = createLazySourceTransformLoader({ ...params, sourceTransformTryNative: params.tryNative }); const loadedTargetExports = /* @__PURE__ */ new Map(); const loadCachedTarget = (target, rest, load) => { if (rest.length > 0) return load(); if (loadedTargetExports.has(target)) return loadedTargetExports.get(target); const loaded = load(); loadedTargetExports.set(target, loaded); return loaded; }; if (!params.tryNative) return ((target, ...rest) => { return loadCachedTarget(target, rest, () => { pluginModuleLoaderStats.calls += 1; pluginModuleLoaderStats.sourceTransformForced += 1; recordSourceTransformTarget(target); return getLoadWithSourceTransform()(target, ...rest); }); }); return ((target, ...rest) => { return loadCachedTarget(target, rest, () => { pluginModuleLoaderStats.calls += 1; const native = tryNativeRequireJavaScriptModule(target, { allowWindows: true, aliasMap: params.aliasMap, fallbackOnMissingDependency: true, fallbackOnNativeError: true }); if (native.ok) { pluginModuleLoaderStats.nativeHits += 1; return native.moduleExport; } pluginModuleLoaderStats.nativeMisses += 1; pluginModuleLoaderStats.sourceTransformFallbacks += 1; recordSourceTransformTarget(target); return getLoadWithSourceTransform()(target, ...rest); }); }); } function getCachedPluginModuleLoader(params) { installOpenClawInternalCorePackageNativeResolver({ moduleUrl: params.importerUrl }); const cacheEntry = resolvePluginModuleLoaderCacheEntry(params); const cached = params.cache.get(cacheEntry.scopedCacheKey); if (cached) return cached; const loader = createPluginModuleLoader({ loaderFilename: cacheEntry.loaderFilename, aliasMap: cacheEntry.aliasMap, tryNative: cacheEntry.tryNative, ...params.createLoader ? { createLoader: params.createLoader } : {} }); params.cache.set(cacheEntry.scopedCacheKey, loader); return loader; } function getCachedPluginSourceModuleLoader(params) { return getCachedPluginModuleLoader({ ...params, tryNative: false }); } //#endregion export { installOpenClawInternalCorePackageNativeResolver as a, isJavaScriptModulePath as c, getPluginModuleLoaderStats as i, tryNativeRequireJavaScriptModule as l, getCachedPluginModuleLoader as n, installOpenClawPluginSdkNativeResolver as o, getCachedPluginSourceModuleLoader as r, clearNativeRequireJavaScriptModuleCache as s, createPluginModuleLoaderCache as t, toSafeImportPath as u };