mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:10:45 +00:00
refactor: centralize plugin cache primitives
This commit is contained in:
@@ -13,6 +13,7 @@ import type { PluginLoadOptions } from "./loader.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { unwrapDefaultModuleExport } from "./module-export.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
@@ -196,7 +197,7 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
|
||||
const env = params.env ?? process.env;
|
||||
const pluginIds = new Set(params.pluginIds);
|
||||
const registry = createEmptyPluginRegistry();
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
|
||||
const getModuleLoader = (modulePath: string) => {
|
||||
const tryNative =
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
PluginManifestChannelConfig,
|
||||
} from "./manifest.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
@@ -35,7 +36,7 @@ type ChannelConfigSurface = {
|
||||
runtime?: ChannelConfigRuntimeSchema;
|
||||
};
|
||||
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
|
||||
function isBuiltChannelConfigSchema(value: unknown): value is ChannelConfigSurface {
|
||||
if (!value || typeof value !== "object") {
|
||||
|
||||
@@ -28,4 +28,18 @@ describe("resolveConfigScopedRuntimeCacheValue", () => {
|
||||
expect(resolveConfigScopedRuntimeCacheValue({ cache, key: "demo", load })).toBe("loaded");
|
||||
expect(load).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("caches undefined values by key", () => {
|
||||
const cache: ConfigScopedRuntimeCache<string | undefined> = new WeakMap();
|
||||
const config = {} as OpenClawConfig;
|
||||
const load = vi.fn(() => undefined);
|
||||
|
||||
expect(resolveConfigScopedRuntimeCacheValue({ cache, config, key: "missing", load })).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(resolveConfigScopedRuntimeCacheValue({ cache, config, key: "missing", load })).toBe(
|
||||
undefined,
|
||||
);
|
||||
expect(load).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,26 +1,4 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
|
||||
export type ConfigScopedRuntimeCache<T> = WeakMap<OpenClawConfig, Map<string, T>>;
|
||||
|
||||
export function resolveConfigScopedRuntimeCacheValue<T>(params: {
|
||||
cache: ConfigScopedRuntimeCache<T>;
|
||||
config?: OpenClawConfig;
|
||||
key: string;
|
||||
load: () => T;
|
||||
}): T {
|
||||
if (!params.config) {
|
||||
return params.load();
|
||||
}
|
||||
let configCache = params.cache.get(params.config);
|
||||
if (!configCache) {
|
||||
configCache = new Map();
|
||||
params.cache.set(params.config, configCache);
|
||||
}
|
||||
const cached = configCache.get(params.key);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
const loaded = params.load();
|
||||
configCache.set(params.key, loaded);
|
||||
return loaded;
|
||||
}
|
||||
export {
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./plugin-cache-primitives.js";
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { OpenClawConfig } from "../config/types.js";
|
||||
import { asNullableRecord } from "../shared/record-coerce.js";
|
||||
import type { PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
@@ -39,7 +40,7 @@ type PluginDoctorContractEntry = {
|
||||
|
||||
type PluginManifestRegistryRecord = PluginManifestRegistry["plugins"][number];
|
||||
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
|
||||
function loadPluginDoctorContractModule(modulePath: string): PluginDoctorContractModule {
|
||||
return getCachedPluginModuleLoader({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PluginLruCache } from "./plugin-lru-cache.js";
|
||||
import { PluginLruCache } from "./plugin-cache-primitives.js";
|
||||
|
||||
export class PluginLoadReentryError extends Error {
|
||||
readonly cacheKey: string;
|
||||
|
||||
@@ -108,6 +108,7 @@ import {
|
||||
} from "./plugin-control-plane-context.js";
|
||||
import { withProfile } from "./plugin-load-profile.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginSourceModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
@@ -463,7 +464,7 @@ function runPluginRegisterSync(
|
||||
}
|
||||
|
||||
function createPluginModuleLoader(options: Pick<PluginLoadOptions, "pluginSdkResolution">) {
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
const loadSourceModule = (modulePath: string) => {
|
||||
return getCachedPluginSourceModuleLoader({
|
||||
cache: moduleLoaders,
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type PluginManifestCommandAlias,
|
||||
} from "./manifest-command-aliases.js";
|
||||
import type { PluginConfigUiHint } from "./manifest-types.js";
|
||||
import { createPluginCacheKey, PluginLruCache } from "./plugin-cache-primitives.js";
|
||||
import type { PluginKind } from "./plugin-kind.types.js";
|
||||
|
||||
export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
|
||||
@@ -42,7 +43,9 @@ type PluginManifestLoadCacheEntry = {
|
||||
ctimeMs: number;
|
||||
};
|
||||
|
||||
const pluginManifestLoadCache = new Map<string, PluginManifestLoadCacheEntry>();
|
||||
const pluginManifestLoadCache = new PluginLruCache<PluginManifestLoadCacheEntry>(
|
||||
MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES,
|
||||
);
|
||||
|
||||
export function clearPluginManifestLoadCache(): void {
|
||||
pluginManifestLoadCache.clear();
|
||||
@@ -1227,12 +1230,14 @@ function buildPluginManifestLoadCacheKey(params: {
|
||||
rootRealPath?: string;
|
||||
stats: fs.Stats;
|
||||
}): string {
|
||||
return JSON.stringify([
|
||||
path.resolve(params.manifestPath),
|
||||
params.rejectHardlinks,
|
||||
params.rootRealPath ?? "",
|
||||
params.stats.dev,
|
||||
params.stats.ino,
|
||||
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,
|
||||
@@ -1252,8 +1257,6 @@ function getCachedPluginManifestLoadResult(
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
pluginManifestLoadCache.delete(key);
|
||||
pluginManifestLoadCache.set(key, entry);
|
||||
return entry.result;
|
||||
}
|
||||
|
||||
@@ -1268,13 +1271,6 @@ function setCachedPluginManifestLoadResult(
|
||||
mtimeMs: stats.mtimeMs,
|
||||
ctimeMs: stats.ctimeMs,
|
||||
});
|
||||
if (pluginManifestLoadCache.size <= MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES) {
|
||||
return;
|
||||
}
|
||||
const oldestKey = pluginManifestLoadCache.keys().next().value;
|
||||
if (typeof oldestKey === "string") {
|
||||
pluginManifestLoadCache.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
||||
function parsePluginKind(raw: unknown): PluginKind | PluginKind[] | undefined {
|
||||
|
||||
118
src/plugins/plugin-cache-primitives.ts
Normal file
118
src/plugins/plugin-cache-primitives.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
|
||||
export type PluginLruCacheResult<T> = { hit: true; value: T } | { hit: false };
|
||||
|
||||
export class PluginLruCache<T> {
|
||||
readonly #defaultMaxEntries: number;
|
||||
#maxEntries: number;
|
||||
readonly #entries = new Map<string, T>();
|
||||
|
||||
constructor(defaultMaxEntries: number) {
|
||||
this.#defaultMaxEntries = normalizeMaxEntries(defaultMaxEntries, 1);
|
||||
this.#maxEntries = this.#defaultMaxEntries;
|
||||
}
|
||||
|
||||
get maxEntries(): number {
|
||||
return this.#maxEntries;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#entries.size;
|
||||
}
|
||||
|
||||
setMaxEntriesForTest(value?: number): void {
|
||||
this.#maxEntries =
|
||||
typeof value === "number"
|
||||
? normalizeMaxEntries(value, this.#defaultMaxEntries)
|
||||
: this.#defaultMaxEntries;
|
||||
this.#evictOldestEntries();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#entries.clear();
|
||||
}
|
||||
|
||||
get(cacheKey: string): T | undefined {
|
||||
const cached = this.getResult(cacheKey);
|
||||
return cached.hit ? cached.value : undefined;
|
||||
}
|
||||
|
||||
getResult(cacheKey: string): PluginLruCacheResult<T> {
|
||||
if (!this.#entries.has(cacheKey)) {
|
||||
return { hit: false };
|
||||
}
|
||||
const cached = this.#entries.get(cacheKey) as T;
|
||||
this.#entries.delete(cacheKey);
|
||||
this.#entries.set(cacheKey, cached);
|
||||
return { hit: true, value: cached };
|
||||
}
|
||||
|
||||
set(cacheKey: string, value: T): void {
|
||||
if (this.#entries.has(cacheKey)) {
|
||||
this.#entries.delete(cacheKey);
|
||||
}
|
||||
this.#entries.set(cacheKey, value);
|
||||
this.#evictOldestEntries();
|
||||
}
|
||||
|
||||
#evictOldestEntries(): void {
|
||||
while (this.#entries.size > this.#maxEntries) {
|
||||
const oldestEntry = this.#entries.keys().next();
|
||||
if (oldestEntry.done) {
|
||||
break;
|
||||
}
|
||||
this.#entries.delete(oldestEntry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ConfigScopedRuntimeCache<T> = WeakMap<OpenClawConfig, Map<string, T>>;
|
||||
|
||||
export function resolveConfigScopedRuntimeCacheValue<T>(params: {
|
||||
cache: ConfigScopedRuntimeCache<T>;
|
||||
config?: OpenClawConfig;
|
||||
key: string;
|
||||
load: () => T;
|
||||
}): T {
|
||||
if (!params.config) {
|
||||
return params.load();
|
||||
}
|
||||
let configCache = params.cache.get(params.config);
|
||||
if (!configCache) {
|
||||
configCache = new Map();
|
||||
params.cache.set(params.config, configCache);
|
||||
}
|
||||
if (configCache.has(params.key)) {
|
||||
return configCache.get(params.key) as T;
|
||||
}
|
||||
const loaded = params.load();
|
||||
configCache.set(params.key, loaded);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
export function createPluginCacheKey(parts: readonly unknown[]): string {
|
||||
return JSON.stringify(parts);
|
||||
}
|
||||
|
||||
export type FileSystemIdentity = {
|
||||
path: string;
|
||||
size: number;
|
||||
mtimeMs: number;
|
||||
ctimeMs?: number;
|
||||
};
|
||||
|
||||
export function createFileSystemIdentityCacheKey(identity: FileSystemIdentity): string {
|
||||
return createPluginCacheKey([
|
||||
identity.path,
|
||||
identity.size,
|
||||
identity.mtimeMs,
|
||||
identity.ctimeMs ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
function normalizeMaxEntries(value: number, fallback: number): number {
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.max(1, Math.floor(value));
|
||||
}
|
||||
@@ -1,72 +1 @@
|
||||
export type PluginLruCacheResult<T> = { hit: true; value: T } | { hit: false };
|
||||
|
||||
export class PluginLruCache<T> {
|
||||
readonly #defaultMaxEntries: number;
|
||||
#maxEntries: number;
|
||||
readonly #entries = new Map<string, T>();
|
||||
|
||||
constructor(defaultMaxEntries: number) {
|
||||
this.#defaultMaxEntries = normalizeMaxEntries(defaultMaxEntries, 1);
|
||||
this.#maxEntries = this.#defaultMaxEntries;
|
||||
}
|
||||
|
||||
get maxEntries(): number {
|
||||
return this.#maxEntries;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#entries.size;
|
||||
}
|
||||
|
||||
setMaxEntriesForTest(value?: number): void {
|
||||
this.#maxEntries =
|
||||
typeof value === "number"
|
||||
? normalizeMaxEntries(value, this.#defaultMaxEntries)
|
||||
: this.#defaultMaxEntries;
|
||||
this.#evictOldestEntries();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.#entries.clear();
|
||||
}
|
||||
|
||||
get(cacheKey: string): T | undefined {
|
||||
const cached = this.getResult(cacheKey);
|
||||
return cached.hit ? cached.value : undefined;
|
||||
}
|
||||
|
||||
getResult(cacheKey: string): PluginLruCacheResult<T> {
|
||||
if (!this.#entries.has(cacheKey)) {
|
||||
return { hit: false };
|
||||
}
|
||||
const cached = this.#entries.get(cacheKey) as T;
|
||||
this.#entries.delete(cacheKey);
|
||||
this.#entries.set(cacheKey, cached);
|
||||
return { hit: true, value: cached };
|
||||
}
|
||||
|
||||
set(cacheKey: string, value: T): void {
|
||||
if (this.#entries.has(cacheKey)) {
|
||||
this.#entries.delete(cacheKey);
|
||||
}
|
||||
this.#entries.set(cacheKey, value);
|
||||
this.#evictOldestEntries();
|
||||
}
|
||||
|
||||
#evictOldestEntries(): void {
|
||||
while (this.#entries.size > this.#maxEntries) {
|
||||
const oldestEntry = this.#entries.keys().next();
|
||||
if (oldestEntry.done) {
|
||||
break;
|
||||
}
|
||||
this.#entries.delete(oldestEntry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMaxEntries(value: number, fallback: number): number {
|
||||
if (!Number.isFinite(value) || value <= 0) {
|
||||
return fallback;
|
||||
}
|
||||
return Math.max(1, Math.floor(value));
|
||||
}
|
||||
export { PluginLruCache, type PluginLruCacheResult } from "./plugin-cache-primitives.js";
|
||||
|
||||
@@ -48,6 +48,39 @@ describe("getCachedPluginModuleLoader", () => {
|
||||
expect(cache.size).toBe(1);
|
||||
});
|
||||
|
||||
it("creates bounded loader caches", async () => {
|
||||
const { createJiti, getCachedPluginModuleLoader } =
|
||||
await loadCachedPluginModuleLoader("bounded-loader-cache");
|
||||
const { createPluginModuleLoaderCache } = await importFreshModule<
|
||||
typeof import("./plugin-module-loader-cache.js")
|
||||
>(import.meta.url, "./plugin-module-loader-cache.js?scope=bounded-loader-cache-factory");
|
||||
|
||||
const cache = createPluginModuleLoaderCache(1);
|
||||
const first = getCachedPluginModuleLoader({
|
||||
cache,
|
||||
modulePath: "/repo/extensions/demo-a/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
loaderFilename: "/repo/extensions/demo-a/index.ts",
|
||||
});
|
||||
getCachedPluginModuleLoader({
|
||||
cache,
|
||||
modulePath: "/repo/extensions/demo-b/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
loaderFilename: "/repo/extensions/demo-b/index.ts",
|
||||
});
|
||||
const reloadedFirst = getCachedPluginModuleLoader({
|
||||
cache,
|
||||
modulePath: "/repo/extensions/demo-a/index.ts",
|
||||
importerUrl: "file:///repo/src/plugins/loader.ts",
|
||||
loaderFilename: "/repo/extensions/demo-a/index.ts",
|
||||
});
|
||||
|
||||
expect(cache.size).toBe(1);
|
||||
expect(reloadedFirst).not.toBe(first);
|
||||
reloadedFirst("/repo/extensions/demo-a/index.ts");
|
||||
expect(createJiti).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("keeps loader caches scoped by loader filename and dist preference", async () => {
|
||||
const { createJiti, getCachedPluginModuleLoader } =
|
||||
await loadCachedPluginModuleLoader("filename-scope");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createJiti } from "jiti";
|
||||
import { toSafeImportPath } from "../shared/import-specifier.js";
|
||||
import { tryNativeRequireJavaScriptModule } from "./native-module-require.js";
|
||||
import { PluginLruCache } from "./plugin-cache-primitives.js";
|
||||
import {
|
||||
buildPluginLoaderJitiOptions,
|
||||
createPluginLoaderModuleCacheKey,
|
||||
@@ -10,7 +11,18 @@ import {
|
||||
|
||||
export type PluginModuleLoader = ReturnType<typeof createJiti>;
|
||||
export type PluginModuleLoaderFactory = typeof createJiti;
|
||||
export type PluginModuleLoaderCache = Map<string, PluginModuleLoader>;
|
||||
export type PluginModuleLoaderCache = Pick<
|
||||
PluginLruCache<PluginModuleLoader>,
|
||||
"clear" | "get" | "set" | "size"
|
||||
>;
|
||||
|
||||
const DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES = 128;
|
||||
|
||||
export function createPluginModuleLoaderCache(
|
||||
maxEntries = DEFAULT_PLUGIN_MODULE_LOADER_CACHE_ENTRIES,
|
||||
): PluginModuleLoaderCache {
|
||||
return new PluginLruCache<PluginModuleLoader>(maxEntries);
|
||||
}
|
||||
|
||||
export function getCachedPluginModuleLoader(params: {
|
||||
cache: PluginModuleLoaderCache;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
resolveConfigScopedRuntimeCacheValue,
|
||||
type ConfigScopedRuntimeCache,
|
||||
} from "./config-scoped-runtime-cache.js";
|
||||
import { resolvePluginControlPlaneFingerprint } from "./plugin-control-plane-context.js";
|
||||
import { resolveProviderConfigApiOwnerHint } from "./provider-config-owner.js";
|
||||
import { isPluginProvidersLoadInFlight, resolvePluginProviders } from "./providers.runtime.js";
|
||||
@@ -15,10 +19,7 @@ import type {
|
||||
ProviderWrapStreamFnContext,
|
||||
} from "./types.js";
|
||||
|
||||
const providerRuntimePluginCache = new WeakMap<
|
||||
OpenClawConfig,
|
||||
Map<string, ProviderPlugin | null>
|
||||
>();
|
||||
const providerRuntimePluginCache: ConfigScopedRuntimeCache<ProviderPlugin | null> = new WeakMap();
|
||||
|
||||
type ProviderRuntimePluginLookupParams = {
|
||||
provider: string;
|
||||
@@ -60,20 +61,6 @@ function resolveProviderRuntimePluginCacheKey(params: ProviderRuntimePluginLooku
|
||||
});
|
||||
}
|
||||
|
||||
function resolveProviderRuntimePluginCache(
|
||||
params: ProviderRuntimePluginLookupParams,
|
||||
): Map<string, ProviderPlugin | null> | undefined {
|
||||
if (!params.config || (params.env && params.env !== process.env)) {
|
||||
return undefined;
|
||||
}
|
||||
let cache = providerRuntimePluginCache.get(params.config);
|
||||
if (!cache) {
|
||||
cache = new Map();
|
||||
providerRuntimePluginCache.set(params.config, cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
function matchesProviderLiteralId(provider: ProviderPlugin, providerId: string): boolean {
|
||||
const normalized = normalizeLowercaseStringOrEmpty(providerId);
|
||||
return !!normalized && normalizeLowercaseStringOrEmpty(provider.id) === normalized;
|
||||
@@ -119,33 +106,38 @@ export function resolveProviderPluginsForHooks(params: {
|
||||
export function resolveProviderRuntimePlugin(
|
||||
params: ProviderRuntimePluginLookupParams,
|
||||
): ProviderPlugin | undefined {
|
||||
const cache = resolveProviderRuntimePluginCache(params);
|
||||
const cacheKey = cache ? resolveProviderRuntimePluginCacheKey(params) : "";
|
||||
if (cache?.has(cacheKey)) {
|
||||
return cache.get(cacheKey) ?? undefined;
|
||||
}
|
||||
const apiOwnerHint = resolveProviderConfigApiOwnerHint({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
});
|
||||
const plugin = resolveProviderPluginsForHooks({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState(),
|
||||
env: params.env,
|
||||
providerRefs: apiOwnerHint ? [params.provider, apiOwnerHint] : [params.provider],
|
||||
applyAutoEnable: params.applyAutoEnable,
|
||||
bundledProviderAllowlistCompat: params.bundledProviderAllowlistCompat,
|
||||
bundledProviderVitestCompat: params.bundledProviderVitestCompat,
|
||||
}).find((plugin) => {
|
||||
if (apiOwnerHint) {
|
||||
const cacheConfig = params.env && params.env !== process.env ? undefined : params.config;
|
||||
const plugin = resolveConfigScopedRuntimeCacheValue({
|
||||
cache: providerRuntimePluginCache,
|
||||
config: cacheConfig,
|
||||
key: resolveProviderRuntimePluginCacheKey(params),
|
||||
load: () => {
|
||||
const apiOwnerHint = resolveProviderConfigApiOwnerHint({
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
});
|
||||
return (
|
||||
matchesProviderLiteralId(plugin, params.provider) || matchesProviderId(plugin, apiOwnerHint)
|
||||
resolveProviderPluginsForHooks({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir ?? getActivePluginRegistryWorkspaceDirFromState(),
|
||||
env: params.env,
|
||||
providerRefs: apiOwnerHint ? [params.provider, apiOwnerHint] : [params.provider],
|
||||
applyAutoEnable: params.applyAutoEnable,
|
||||
bundledProviderAllowlistCompat: params.bundledProviderAllowlistCompat,
|
||||
bundledProviderVitestCompat: params.bundledProviderVitestCompat,
|
||||
}).find((plugin) => {
|
||||
if (apiOwnerHint) {
|
||||
return (
|
||||
matchesProviderLiteralId(plugin, params.provider) ||
|
||||
matchesProviderId(plugin, apiOwnerHint)
|
||||
);
|
||||
}
|
||||
return matchesProviderId(plugin, params.provider);
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
return matchesProviderId(plugin, params.provider);
|
||||
},
|
||||
});
|
||||
cache?.set(cacheKey, plugin ?? null);
|
||||
return plugin;
|
||||
return plugin ?? undefined;
|
||||
}
|
||||
|
||||
export function resolveProviderHookPlugin(params: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { sameFileIdentity } from "../infra/file-identity.js";
|
||||
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
@@ -26,7 +27,7 @@ const publicSurfaceLocationCache = new Map<
|
||||
boundaryRoot: string;
|
||||
} | null
|
||||
>();
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
|
||||
function isSourceArtifactPath(modulePath: string): boolean {
|
||||
switch (path.extname(modulePath).toLowerCase()) {
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
optimizeImageToJpeg as optimizeImageToJpegImpl,
|
||||
} from "../../media/web-media.js";
|
||||
import type { PollInput } from "../../polls.js";
|
||||
import type { PluginModuleLoaderCache } from "../plugin-module-loader-cache.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "../plugin-module-loader-cache.js";
|
||||
import type { PluginOrigin } from "../plugin-origin.types.js";
|
||||
import {
|
||||
loadPluginBoundaryModule,
|
||||
@@ -115,7 +118,7 @@ const webChannelRuntimeModuleCache = new Map<
|
||||
CachedWebChannelRuntimeModule
|
||||
>();
|
||||
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
|
||||
function resolveWebChannelPluginRecord(): WebChannelPluginRecord {
|
||||
return resolvePluginRuntimeRecordByEntryBaseNames(["light-runtime-api", "runtime-api"], () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ErrorObject, ValidateFunction } from "ajv";
|
||||
import { appendAllowedValuesHint, summarizeAllowedValues } from "../config/allowed-values.js";
|
||||
import type { JsonSchemaObject } from "../shared/json-schema.types.js";
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
import { PluginLruCache } from "./plugin-cache-primitives.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
type AjvLike = {
|
||||
@@ -52,7 +53,7 @@ type CachedValidator = {
|
||||
schema: JsonSchemaObject;
|
||||
};
|
||||
|
||||
const schemaCache = new Map<string, CachedValidator>();
|
||||
const schemaCache = new PluginLruCache<CachedValidator>(512);
|
||||
|
||||
function cloneValidationValue<T>(value: T): T {
|
||||
if (value === undefined || value === null) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { PluginLruCache } from "./plugin-lru-cache.js";
|
||||
import { PluginLruCache } from "./plugin-cache-primitives.js";
|
||||
|
||||
type PluginSdkAliasCandidateKind = "dist" | "src";
|
||||
export type PluginSdkResolutionPreference = "auto" | "dist" | "src";
|
||||
|
||||
@@ -7,6 +7,7 @@ import { buildPluginApi } from "./api-builder.js";
|
||||
import { collectPluginConfigContractMatches } from "./config-contracts.js";
|
||||
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginModuleLoader,
|
||||
type PluginModuleLoaderCache,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
@@ -85,7 +86,7 @@ const NOOP_LOGGER: PluginLogger = {
|
||||
error() {},
|
||||
};
|
||||
|
||||
const moduleLoaders: PluginModuleLoaderCache = new Map();
|
||||
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
||||
|
||||
export function clearPluginSetupRegistryCache(): void {
|
||||
moduleLoaders.clear();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { withProfile } from "./plugin-load-profile.js";
|
||||
import type { PluginModuleLoaderCache } from "./plugin-module-loader-cache.js";
|
||||
import { getCachedPluginSourceModuleLoader } from "./plugin-module-loader-cache.js";
|
||||
import {
|
||||
createPluginModuleLoaderCache,
|
||||
getCachedPluginSourceModuleLoader,
|
||||
} from "./plugin-module-loader-cache.js";
|
||||
|
||||
export type PluginSourceLoader = (modulePath: string) => unknown;
|
||||
|
||||
export function createPluginSourceLoader(): PluginSourceLoader {
|
||||
const loaders: PluginModuleLoaderCache = new Map();
|
||||
const loaders = createPluginModuleLoaderCache();
|
||||
return (modulePath) => {
|
||||
const sourceLoader = getCachedPluginSourceModuleLoader({
|
||||
cache: loaders,
|
||||
|
||||
Reference in New Issue
Block a user