mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix: cache capability provider manifest ids
This commit is contained in:
@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/startup: carry the Gateway `PluginLookUpTable` into deferred channel full-runtime reloads so post-listen startup does not rebuild manifest metadata after the provisional setup-runtime load. Thanks @shakkernerd.
|
||||
- Gateway/startup: extend `OPENCLAW_GATEWAY_STARTUP_TRACE=1` with per-phase event-loop delay plus plugin lookup-table timing and count metrics for installed-index, manifest, startup-plan, and owner-map work, and include the new timing fields in startup benchmark summaries. Thanks @shakkernerd.
|
||||
- Plugins/channels: resolve read-only channel command defaults from one plugin index plus manifest pass instead of reloading plugin metadata while checking candidate plugin enablement. Thanks @shakkernerd.
|
||||
- Plugins/capabilities: cache manifest-derived capability provider plugin IDs per config snapshot so repeated TTS, media, realtime, memory, image, video, and music provider resolution avoids redundant manifest scans. Thanks @shakkernerd.
|
||||
- Plugins/contracts: resolve runtime manifest-contract plugin owners from one plugin index plus manifest pass instead of rebuilding manifest metadata separately for all owners and enabled owners. Thanks @shakkernerd.
|
||||
- Plugins/extractors: reuse one manifest registry pass while resolving bundled document and web-content extractor plugins instead of rereading manifests for compatibility and enablement filtering. Thanks @shakkernerd.
|
||||
- Plugins/registry: resolve lookup-table owner maps for providers, CLI backends, setup providers, command aliases, model catalogs, channel configs, and manifest contracts while preserving setup-only CLI backend ownership. Thanks @shakkernerd.
|
||||
|
||||
@@ -502,6 +502,27 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses manifest-derived capability plugin ids for the same config snapshot", () => {
|
||||
const { cfg, enablementCompat } = createCompatChainConfig();
|
||||
setBundledCapabilityFixture("mediaUnderstandingProviders");
|
||||
mocks.withBundledPluginEnablementCompat.mockReturnValue(enablementCompat);
|
||||
mocks.withBundledPluginVitestCompat.mockReturnValue(enablementCompat);
|
||||
|
||||
expectNoResolvedCapabilityProviders(
|
||||
resolvePluginCapabilityProviders({ key: "mediaUnderstandingProviders", cfg }),
|
||||
);
|
||||
expectNoResolvedCapabilityProviders(
|
||||
resolvePluginCapabilityProviders({ key: "mediaUnderstandingProviders", cfg }),
|
||||
);
|
||||
|
||||
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledOnce();
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledWith({
|
||||
config: cfg,
|
||||
pluginIds: ["openai"],
|
||||
});
|
||||
});
|
||||
|
||||
it("reuses a compatible active registry even when the capability list is empty", () => {
|
||||
const active = createEmptyPluginRegistry();
|
||||
mocks.resolveRuntimePluginRegistry.mockReturnValue(active);
|
||||
|
||||
@@ -4,6 +4,11 @@ import {
|
||||
withBundledPluginEnablementCompat,
|
||||
withBundledPluginVitestCompat,
|
||||
} from "./bundled-compat.js";
|
||||
import {
|
||||
buildPluginSnapshotCacheEnvKey,
|
||||
resolvePluginSnapshotCacheTtlMs,
|
||||
shouldUsePluginSnapshotCache,
|
||||
} from "./cache-controls.js";
|
||||
import { hasExplicitPluginConfig } from "./config-policy.js";
|
||||
import { resolveRuntimePluginRegistry } from "./loader.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
@@ -32,6 +37,11 @@ type CapabilityContractKey =
|
||||
type CapabilityProviderForKey<K extends CapabilityProviderRegistryKey> =
|
||||
PluginRegistry[K][number] extends { provider: infer T } ? T : never;
|
||||
|
||||
type CapabilityProviderPluginIdCacheEntry = {
|
||||
expiresAt: number;
|
||||
pluginIds: string[];
|
||||
};
|
||||
|
||||
const CAPABILITY_CONTRACT_KEY: Record<CapabilityProviderRegistryKey, CapabilityContractKey> = {
|
||||
memoryEmbeddingProviders: "memoryEmbeddingProviders",
|
||||
speechProviders: "speechProviders",
|
||||
@@ -43,15 +53,86 @@ const CAPABILITY_CONTRACT_KEY: Record<CapabilityProviderRegistryKey, CapabilityC
|
||||
musicGenerationProviders: "musicGenerationProviders",
|
||||
};
|
||||
|
||||
const capabilityProviderPluginIdCache = new WeakMap<
|
||||
OpenClawConfig,
|
||||
WeakMap<NodeJS.ProcessEnv, Map<string, CapabilityProviderPluginIdCacheEntry>>
|
||||
>();
|
||||
|
||||
function buildCapabilityProviderPluginIdCacheKey(params: {
|
||||
key: CapabilityProviderRegistryKey;
|
||||
env: NodeJS.ProcessEnv;
|
||||
providerId?: string;
|
||||
}): string {
|
||||
return JSON.stringify({
|
||||
key: params.key,
|
||||
providerId: params.providerId ?? "",
|
||||
env: buildPluginSnapshotCacheEnvKey(params.env),
|
||||
});
|
||||
}
|
||||
|
||||
function getCachedCapabilityProviderPluginIds(params: {
|
||||
key: CapabilityProviderRegistryKey;
|
||||
cfg?: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
providerId?: string;
|
||||
}): string[] | undefined {
|
||||
if (!params.cfg || !shouldUsePluginSnapshotCache(params.env)) {
|
||||
return undefined;
|
||||
}
|
||||
const envCache = capabilityProviderPluginIdCache.get(params.cfg)?.get(params.env);
|
||||
const cached = envCache?.get(buildCapabilityProviderPluginIdCacheKey(params));
|
||||
if (!cached || cached.expiresAt <= Date.now()) {
|
||||
return undefined;
|
||||
}
|
||||
return [...cached.pluginIds];
|
||||
}
|
||||
|
||||
function memoizeCapabilityProviderPluginIds(params: {
|
||||
key: CapabilityProviderRegistryKey;
|
||||
cfg?: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
providerId?: string;
|
||||
pluginIds: string[];
|
||||
}): void {
|
||||
if (!params.cfg || !shouldUsePluginSnapshotCache(params.env)) {
|
||||
return;
|
||||
}
|
||||
let configCache = capabilityProviderPluginIdCache.get(params.cfg);
|
||||
if (!configCache) {
|
||||
configCache = new WeakMap<
|
||||
NodeJS.ProcessEnv,
|
||||
Map<string, CapabilityProviderPluginIdCacheEntry>
|
||||
>();
|
||||
capabilityProviderPluginIdCache.set(params.cfg, configCache);
|
||||
}
|
||||
let envCache = configCache.get(params.env);
|
||||
if (!envCache) {
|
||||
envCache = new Map<string, CapabilityProviderPluginIdCacheEntry>();
|
||||
configCache.set(params.env, envCache);
|
||||
}
|
||||
envCache.set(buildCapabilityProviderPluginIdCacheKey(params), {
|
||||
expiresAt: Date.now() + resolvePluginSnapshotCacheTtlMs(params.env),
|
||||
pluginIds: [...params.pluginIds],
|
||||
});
|
||||
}
|
||||
|
||||
function resolveBundledCapabilityCompatPluginIds(params: {
|
||||
key: CapabilityProviderRegistryKey;
|
||||
cfg?: OpenClawConfig;
|
||||
providerId?: string;
|
||||
}): string[] {
|
||||
const env = process.env;
|
||||
const cached = getCachedCapabilityProviderPluginIds({
|
||||
...params,
|
||||
env,
|
||||
});
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const contractKey = CAPABILITY_CONTRACT_KEY[params.key];
|
||||
return loadPluginManifestRegistryForPluginRegistry({
|
||||
const pluginIds = loadPluginManifestRegistryForPluginRegistry({
|
||||
config: params.cfg,
|
||||
env: process.env,
|
||||
env,
|
||||
includeDisabled: true,
|
||||
})
|
||||
.plugins.filter(
|
||||
@@ -62,6 +143,12 @@ function resolveBundledCapabilityCompatPluginIds(params: {
|
||||
)
|
||||
.map((plugin) => plugin.id)
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
memoizeCapabilityProviderPluginIds({
|
||||
...params,
|
||||
env,
|
||||
pluginIds,
|
||||
});
|
||||
return pluginIds;
|
||||
}
|
||||
|
||||
function resolveCapabilityProviderConfig(params: {
|
||||
|
||||
Reference in New Issue
Block a user