mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:20:44 +00:00
refactor: extract plugin tool factory cache
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { setBundledPluginsDirOverrideForTest } from "../plugins/bundled-dir.js";
|
||||
import {
|
||||
@@ -10,6 +10,7 @@ import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plug
|
||||
import type { InstalledPluginIndexRecord } from "../plugins/installed-plugin-index.js";
|
||||
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import { clearSecretsRuntimeSnapshot } from "../secrets/runtime.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
import { __testing, createOpenClawTools } from "./openclaw-tools.js";
|
||||
|
||||
@@ -141,8 +142,13 @@ function installSnapshot(
|
||||
}
|
||||
|
||||
describe("optional media tool factory planning", () => {
|
||||
beforeEach(() => {
|
||||
clearSecretsRuntimeSnapshot();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
clearSecretsRuntimeSnapshot();
|
||||
setBundledPluginsDirOverrideForTest(undefined);
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
@@ -500,6 +506,7 @@ describe("optional media tool factory planning", () => {
|
||||
|
||||
it("does not count unresolved SecretRef config signals as configured", () => {
|
||||
vi.stubEnv("COMFY_TEST_API_KEY", "");
|
||||
const workspaceDir = process.cwd();
|
||||
const config: OpenClawConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
@@ -525,29 +532,35 @@ describe("optional media tool factory planning", () => {
|
||||
required: ["promptNodeId", "apiKey"],
|
||||
},
|
||||
];
|
||||
installSnapshot(config, [
|
||||
createPlugin({
|
||||
id: "comfy",
|
||||
contracts: {
|
||||
imageGenerationProviders: ["comfy"],
|
||||
videoGenerationProviders: ["comfy"],
|
||||
musicGenerationProviders: ["comfy"],
|
||||
},
|
||||
imageGenerationProviderMetadata: {
|
||||
comfy: { configSignals },
|
||||
},
|
||||
videoGenerationProviderMetadata: {
|
||||
comfy: { configSignals },
|
||||
},
|
||||
musicGenerationProviderMetadata: {
|
||||
comfy: { configSignals },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
installSnapshot(
|
||||
config,
|
||||
[
|
||||
createPlugin({
|
||||
id: "comfy",
|
||||
contracts: {
|
||||
imageGenerationProviders: ["comfy"],
|
||||
videoGenerationProviders: ["comfy"],
|
||||
musicGenerationProviders: ["comfy"],
|
||||
},
|
||||
imageGenerationProviderMetadata: {
|
||||
comfy: { configSignals },
|
||||
},
|
||||
videoGenerationProviderMetadata: {
|
||||
comfy: { configSignals },
|
||||
},
|
||||
musicGenerationProviderMetadata: {
|
||||
comfy: { configSignals },
|
||||
},
|
||||
}),
|
||||
],
|
||||
undefined,
|
||||
workspaceDir,
|
||||
);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
workspaceDir,
|
||||
authStore: createAuthStore(),
|
||||
}),
|
||||
).toEqual({
|
||||
@@ -559,6 +572,7 @@ describe("optional media tool factory planning", () => {
|
||||
expect(
|
||||
createOpenClawTools({
|
||||
config,
|
||||
workspaceDir,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
}).map((tool) => tool.name),
|
||||
|
||||
102
src/plugins/tool-factory-cache.ts
Normal file
102
src/plugins/tool-factory-cache.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { AnyAgentTool } from "../agents/tools/common.js";
|
||||
import { resolveRuntimeConfigCacheKey } from "../config/runtime-snapshot.js";
|
||||
import type { PluginLoadOptions } from "./loader.js";
|
||||
import type { OpenClawPluginToolContext, OpenClawPluginToolFactory } from "./types.js";
|
||||
|
||||
const PLUGIN_TOOL_FACTORY_CACHE_LIMIT_PER_FACTORY = 64;
|
||||
|
||||
export type PluginToolFactoryResult = AnyAgentTool | AnyAgentTool[] | null | undefined;
|
||||
|
||||
let pluginToolFactoryCache = new WeakMap<
|
||||
OpenClawPluginToolFactory,
|
||||
Map<string, PluginToolFactoryResult>
|
||||
>();
|
||||
let pluginToolFactoryCacheObjectIds = new WeakMap<object, number>();
|
||||
let nextPluginToolFactoryCacheObjectId = 1;
|
||||
|
||||
export function resetPluginToolFactoryCache(): void {
|
||||
pluginToolFactoryCache = new WeakMap();
|
||||
pluginToolFactoryCacheObjectIds = new WeakMap();
|
||||
nextPluginToolFactoryCacheObjectId = 1;
|
||||
}
|
||||
|
||||
function getPluginToolFactoryCacheObjectId(value: object | null | undefined): number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const existing = pluginToolFactoryCacheObjectIds.get(value);
|
||||
if (existing !== undefined) {
|
||||
return existing;
|
||||
}
|
||||
const next = nextPluginToolFactoryCacheObjectId++;
|
||||
pluginToolFactoryCacheObjectIds.set(value, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
function getPluginToolFactoryConfigCacheKey(
|
||||
value: PluginLoadOptions["config"] | null | undefined,
|
||||
): string | number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return resolveRuntimeConfigCacheKey(value);
|
||||
} catch {
|
||||
return getPluginToolFactoryCacheObjectId(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPluginToolFactoryCacheKey(params: {
|
||||
ctx: OpenClawPluginToolContext;
|
||||
currentRuntimeConfig?: PluginLoadOptions["config"] | null;
|
||||
}): string {
|
||||
const { ctx } = params;
|
||||
return JSON.stringify({
|
||||
config: getPluginToolFactoryConfigCacheKey(ctx.config),
|
||||
runtimeConfig: getPluginToolFactoryConfigCacheKey(ctx.runtimeConfig),
|
||||
currentRuntimeConfig: getPluginToolFactoryConfigCacheKey(params.currentRuntimeConfig),
|
||||
fsPolicy: ctx.fsPolicy ?? null,
|
||||
workspaceDir: ctx.workspaceDir ?? null,
|
||||
agentDir: ctx.agentDir ?? null,
|
||||
agentId: ctx.agentId ?? null,
|
||||
sessionKey: ctx.sessionKey ?? null,
|
||||
sessionId: ctx.sessionId ?? null,
|
||||
browser: ctx.browser ?? null,
|
||||
messageChannel: ctx.messageChannel ?? null,
|
||||
agentAccountId: ctx.agentAccountId ?? null,
|
||||
deliveryContext: ctx.deliveryContext ?? null,
|
||||
requesterSenderId: ctx.requesterSenderId ?? null,
|
||||
senderIsOwner: ctx.senderIsOwner ?? null,
|
||||
sandboxed: ctx.sandboxed ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
export function readCachedPluginToolFactoryResult(params: {
|
||||
factory: OpenClawPluginToolFactory;
|
||||
cacheKey: string;
|
||||
}): { hit: boolean; result: PluginToolFactoryResult } {
|
||||
const cache = pluginToolFactoryCache.get(params.factory);
|
||||
if (!cache || !cache.has(params.cacheKey)) {
|
||||
return { hit: false, result: undefined };
|
||||
}
|
||||
return { hit: true, result: cache.get(params.cacheKey) };
|
||||
}
|
||||
|
||||
export function writeCachedPluginToolFactoryResult(params: {
|
||||
factory: OpenClawPluginToolFactory;
|
||||
cacheKey: string;
|
||||
result: PluginToolFactoryResult;
|
||||
}): void {
|
||||
let cache = pluginToolFactoryCache.get(params.factory);
|
||||
if (!cache) {
|
||||
cache = new Map();
|
||||
pluginToolFactoryCache.set(params.factory, cache);
|
||||
}
|
||||
if (!cache.has(params.cacheKey) && cache.size >= PLUGIN_TOOL_FACTORY_CACHE_LIMIT_PER_FACTORY) {
|
||||
const oldestKey = cache.keys().next().value;
|
||||
if (oldestKey !== undefined) {
|
||||
cache.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
cache.set(params.cacheKey, params.result);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { normalizeToolName } from "../agents/tool-policy.js";
|
||||
import type { AnyAgentTool } from "../agents/tools/common.js";
|
||||
import { resolveRuntimeConfigCacheKey } from "../config/runtime-snapshot.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { applyTestPluginDefaults, normalizePluginsConfig } from "./config-state.js";
|
||||
import { resolveRuntimePluginRegistry, type PluginLoadOptions } from "./loader.js";
|
||||
@@ -20,7 +19,15 @@ import {
|
||||
resolvePluginRuntimeLoadContext,
|
||||
} from "./runtime/load-context.js";
|
||||
import { findUndeclaredPluginToolNames } from "./tool-contracts.js";
|
||||
import type { OpenClawPluginToolContext, OpenClawPluginToolFactory } from "./types.js";
|
||||
import {
|
||||
buildPluginToolFactoryCacheKey,
|
||||
readCachedPluginToolFactoryResult,
|
||||
type PluginToolFactoryResult,
|
||||
writeCachedPluginToolFactoryResult,
|
||||
} from "./tool-factory-cache.js";
|
||||
import type { OpenClawPluginToolContext } from "./types.js";
|
||||
|
||||
export { resetPluginToolFactoryCache } from "./tool-factory-cache.js";
|
||||
|
||||
export type PluginToolMeta = {
|
||||
pluginId: string;
|
||||
@@ -43,106 +50,9 @@ const log = createSubsystemLogger("plugins/tools");
|
||||
const PLUGIN_TOOL_FACTORY_WARN_TOTAL_MS = 5_000;
|
||||
const PLUGIN_TOOL_FACTORY_WARN_FACTORY_MS = 1_000;
|
||||
const PLUGIN_TOOL_FACTORY_SUMMARY_LIMIT = 20;
|
||||
const PLUGIN_TOOL_FACTORY_CACHE_LIMIT_PER_FACTORY = 64;
|
||||
|
||||
type PluginToolFactoryResult = AnyAgentTool | AnyAgentTool[] | null | undefined;
|
||||
|
||||
let pluginToolFactoryCache = new WeakMap<
|
||||
OpenClawPluginToolFactory,
|
||||
Map<string, PluginToolFactoryResult>
|
||||
>();
|
||||
let pluginToolFactoryCacheObjectIds = new WeakMap<object, number>();
|
||||
let nextPluginToolFactoryCacheObjectId = 1;
|
||||
|
||||
const pluginToolMeta = new WeakMap<AnyAgentTool, PluginToolMeta>();
|
||||
|
||||
export function resetPluginToolFactoryCache(): void {
|
||||
pluginToolFactoryCache = new WeakMap();
|
||||
pluginToolFactoryCacheObjectIds = new WeakMap();
|
||||
nextPluginToolFactoryCacheObjectId = 1;
|
||||
}
|
||||
|
||||
function getPluginToolFactoryCacheObjectId(value: object | null | undefined): number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const existing = pluginToolFactoryCacheObjectIds.get(value);
|
||||
if (existing !== undefined) {
|
||||
return existing;
|
||||
}
|
||||
const next = nextPluginToolFactoryCacheObjectId++;
|
||||
pluginToolFactoryCacheObjectIds.set(value, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
function getPluginToolFactoryConfigCacheKey(
|
||||
value: PluginLoadOptions["config"] | null | undefined,
|
||||
): string | number | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return resolveRuntimeConfigCacheKey(value);
|
||||
} catch {
|
||||
return getPluginToolFactoryCacheObjectId(value);
|
||||
}
|
||||
}
|
||||
|
||||
function buildPluginToolFactoryCacheKey(params: {
|
||||
ctx: OpenClawPluginToolContext;
|
||||
currentRuntimeConfig?: PluginLoadOptions["config"] | null;
|
||||
}): string {
|
||||
const { ctx } = params;
|
||||
return JSON.stringify({
|
||||
config: getPluginToolFactoryConfigCacheKey(ctx.config),
|
||||
runtimeConfig: getPluginToolFactoryConfigCacheKey(ctx.runtimeConfig),
|
||||
currentRuntimeConfig: getPluginToolFactoryConfigCacheKey(params.currentRuntimeConfig),
|
||||
fsPolicy: ctx.fsPolicy ?? null,
|
||||
workspaceDir: ctx.workspaceDir ?? null,
|
||||
agentDir: ctx.agentDir ?? null,
|
||||
agentId: ctx.agentId ?? null,
|
||||
sessionKey: ctx.sessionKey ?? null,
|
||||
sessionId: ctx.sessionId ?? null,
|
||||
browser: ctx.browser ?? null,
|
||||
messageChannel: ctx.messageChannel ?? null,
|
||||
agentAccountId: ctx.agentAccountId ?? null,
|
||||
deliveryContext: ctx.deliveryContext ?? null,
|
||||
requesterSenderId: ctx.requesterSenderId ?? null,
|
||||
senderIsOwner: ctx.senderIsOwner ?? null,
|
||||
sandboxed: ctx.sandboxed ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
function readCachedPluginToolFactoryResult(params: {
|
||||
factory: OpenClawPluginToolFactory;
|
||||
cacheKey: string;
|
||||
}): { hit: boolean; result: PluginToolFactoryResult } {
|
||||
const cache = pluginToolFactoryCache.get(params.factory);
|
||||
if (!cache || !cache.has(params.cacheKey)) {
|
||||
return { hit: false, result: undefined };
|
||||
}
|
||||
return { hit: true, result: cache.get(params.cacheKey) };
|
||||
}
|
||||
|
||||
function writeCachedPluginToolFactoryResult(params: {
|
||||
factory: OpenClawPluginToolFactory;
|
||||
cacheKey: string;
|
||||
result: PluginToolFactoryResult;
|
||||
}): void {
|
||||
let cache = pluginToolFactoryCache.get(params.factory);
|
||||
if (!cache) {
|
||||
cache = new Map();
|
||||
pluginToolFactoryCache.set(params.factory, cache);
|
||||
}
|
||||
if (!cache.has(params.cacheKey) && cache.size >= PLUGIN_TOOL_FACTORY_CACHE_LIMIT_PER_FACTORY) {
|
||||
const oldestKey = cache.keys().next().value;
|
||||
if (oldestKey !== undefined) {
|
||||
cache.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
cache.set(params.cacheKey, params.result);
|
||||
}
|
||||
|
||||
export function setPluginToolMeta(tool: AnyAgentTool, meta: PluginToolMeta): void {
|
||||
pluginToolMeta.set(tool, meta);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user