mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 03:00:34 +00:00
perf(gateway): cache manifest model catalog rows
This commit is contained in:
@@ -111,6 +111,46 @@ function modelIdNormalizationSnapshot() {
|
||||
};
|
||||
}
|
||||
|
||||
function manifestModelCatalogSnapshot(model: {
|
||||
id: string;
|
||||
name?: string;
|
||||
input?: Array<"text" | "image">;
|
||||
reasoning?: boolean;
|
||||
contextWindow?: number;
|
||||
}) {
|
||||
return {
|
||||
policyHash: "policy",
|
||||
index: {
|
||||
policyHash: "policy",
|
||||
plugins: [
|
||||
{
|
||||
pluginId: "external-provider",
|
||||
enabled: true,
|
||||
origin: "global",
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: "external-provider",
|
||||
origin: "global",
|
||||
modelCatalog: {
|
||||
providers: {
|
||||
external: {
|
||||
models: [
|
||||
{
|
||||
name: model.id,
|
||||
...model,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function configuredModel(id: string) {
|
||||
return {
|
||||
id,
|
||||
@@ -896,40 +936,13 @@ describe("loadModelCatalog", () => {
|
||||
});
|
||||
|
||||
it("loads manifest catalog rows from the current metadata snapshot without provider runtime", () => {
|
||||
const snapshot = {
|
||||
policyHash: "policy",
|
||||
index: {
|
||||
policyHash: "policy",
|
||||
plugins: [
|
||||
{
|
||||
pluginId: "external-provider",
|
||||
enabled: true,
|
||||
origin: "global",
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
id: "external-provider",
|
||||
origin: "global",
|
||||
modelCatalog: {
|
||||
providers: {
|
||||
external: {
|
||||
models: [
|
||||
{
|
||||
id: "external-fast",
|
||||
name: "External Fast",
|
||||
input: ["text", "image"],
|
||||
reasoning: true,
|
||||
contextWindow: 32000,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const snapshot = manifestModelCatalogSnapshot({
|
||||
id: "external-fast",
|
||||
name: "External Fast",
|
||||
input: ["text", "image"],
|
||||
reasoning: true,
|
||||
contextWindow: 32000,
|
||||
});
|
||||
currentPluginMetadataSnapshotMock.mockReturnValue(snapshot);
|
||||
|
||||
const result = loadManifestModelCatalog({ config: {} as OpenClawConfig });
|
||||
@@ -948,6 +961,41 @@ describe("loadModelCatalog", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("reuses planned manifest catalog rows for the same config and metadata snapshot", () => {
|
||||
const config = {} as OpenClawConfig;
|
||||
const snapshot = manifestModelCatalogSnapshot({ id: "external-fast" });
|
||||
currentPluginMetadataSnapshotMock.mockReturnValue(snapshot);
|
||||
|
||||
const first = loadManifestModelCatalog({ config });
|
||||
const second = loadManifestModelCatalog({ config });
|
||||
|
||||
expect(second).toBe(first);
|
||||
expect(first).toEqual([
|
||||
{
|
||||
provider: "external",
|
||||
id: "external-fast",
|
||||
name: "external-fast",
|
||||
input: ["text"],
|
||||
reasoning: false,
|
||||
},
|
||||
]);
|
||||
expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("refreshes manifest catalog rows when the metadata snapshot changes", () => {
|
||||
const config = {} as OpenClawConfig;
|
||||
currentPluginMetadataSnapshotMock
|
||||
.mockReturnValueOnce(manifestModelCatalogSnapshot({ id: "external-fast" }))
|
||||
.mockReturnValue(manifestModelCatalogSnapshot({ id: "external-slow" }));
|
||||
|
||||
const first = loadManifestModelCatalog({ config });
|
||||
const second = loadManifestModelCatalog({ config });
|
||||
|
||||
expect(second).not.toBe(first);
|
||||
expect(first[0]?.id).toBe("external-fast");
|
||||
expect(second[0]?.id).toBe("external-slow");
|
||||
});
|
||||
|
||||
it("lets read-only manifest catalog reuse the current workspace-scoped snapshot", () => {
|
||||
loadManifestModelCatalog({
|
||||
config: {} as OpenClawConfig,
|
||||
|
||||
@@ -69,6 +69,11 @@ let hasLoggedModelCatalogError = false;
|
||||
let hasLoggedReadOnlyStaticCatalogError = false;
|
||||
const defaultImportPiSdk = () => import("./pi-model-discovery-runtime.js");
|
||||
let importPiSdk = defaultImportPiSdk;
|
||||
type ManifestModelCatalogCacheEntry = {
|
||||
snapshot: PluginMetadataSnapshot;
|
||||
rows: ModelCatalogEntry[];
|
||||
};
|
||||
let manifestModelCatalogCache = new WeakMap<OpenClawConfig, ManifestModelCatalogCacheEntry>();
|
||||
const modelSuppressionLoader = createLazyImportLoader(
|
||||
() => import("./model-suppression.runtime.js"),
|
||||
);
|
||||
@@ -83,6 +88,7 @@ function loadModelSuppression() {
|
||||
|
||||
export function resetModelCatalogCache() {
|
||||
modelCatalogPromise = null;
|
||||
manifestModelCatalogCache = new WeakMap();
|
||||
hasLoggedModelCatalogError = false;
|
||||
hasLoggedReadOnlyStaticCatalogError = false;
|
||||
}
|
||||
@@ -203,6 +209,10 @@ export function loadManifestModelCatalog(params: {
|
||||
if (!resolvedSnapshot) {
|
||||
return [];
|
||||
}
|
||||
const cached = manifestModelCatalogCache.get(params.config);
|
||||
if (cached?.snapshot === resolvedSnapshot) {
|
||||
return cached.rows;
|
||||
}
|
||||
const eligiblePlugins = resolvedSnapshot.plugins.filter(
|
||||
(plugin) =>
|
||||
plugin.modelCatalog &&
|
||||
@@ -215,7 +225,7 @@ export function loadManifestModelCatalog(params: {
|
||||
const plan = planManifestModelCatalogRows({
|
||||
registry: { plugins: eligiblePlugins },
|
||||
});
|
||||
return plan.rows.map((row) => {
|
||||
const rows = plan.rows.map((row) => {
|
||||
const entry: ModelCatalogEntry = {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
@@ -239,6 +249,8 @@ export function loadManifestModelCatalog(params: {
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
manifestModelCatalogCache.set(params.config, { snapshot: resolvedSnapshot, rows });
|
||||
return rows;
|
||||
}
|
||||
|
||||
function sortModelCatalogEntries(entries: ModelCatalogEntry[]): ModelCatalogEntry[] {
|
||||
|
||||
@@ -50,25 +50,6 @@ describe("resolveGatewayPluginConfig", () => {
|
||||
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("refreshes the cached config when env object changes", async () => {
|
||||
const { resolveGatewayPluginConfig } = await import("./runtime-plugin-config.js");
|
||||
const config = { channels: { telegram: { botToken: "token" } } } as OpenClawConfig;
|
||||
const snapshot = { manifestRegistry: { plugins: [], diagnostics: [] } };
|
||||
mocks.getCurrentPluginMetadataSnapshot.mockReturnValue(snapshot);
|
||||
mocks.applyPluginAutoEnable
|
||||
.mockReturnValueOnce({ config: { ...config, first: true }, changes: [] })
|
||||
.mockReturnValueOnce({ config: { ...config, second: true }, changes: [] });
|
||||
|
||||
expect(resolveGatewayPluginConfig({ config, env: { A: "1" } })).toMatchObject({
|
||||
first: true,
|
||||
});
|
||||
expect(resolveGatewayPluginConfig({ config, env: { A: "2" } })).toMatchObject({
|
||||
second: true,
|
||||
});
|
||||
|
||||
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("does not cache without a current metadata snapshot", async () => {
|
||||
const { resolveGatewayPluginConfig } = await import("./runtime-plugin-config.js");
|
||||
const config = {} as OpenClawConfig;
|
||||
|
||||
@@ -4,41 +4,33 @@ import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-meta
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
|
||||
type CachedGatewayPluginConfig = {
|
||||
env: NodeJS.ProcessEnv;
|
||||
snapshot: PluginMetadataSnapshot;
|
||||
config: OpenClawConfig;
|
||||
};
|
||||
|
||||
const gatewayPluginConfigCache = new WeakMap<OpenClawConfig, CachedGatewayPluginConfig>();
|
||||
|
||||
export function resolveGatewayPluginConfig(params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): OpenClawConfig {
|
||||
const env = params.env ?? process.env;
|
||||
export function resolveGatewayPluginConfig(params: { config: OpenClawConfig }): OpenClawConfig {
|
||||
const currentSnapshot = getCurrentPluginMetadataSnapshot({
|
||||
config: params.config,
|
||||
env,
|
||||
allowWorkspaceScopedSnapshot: true,
|
||||
});
|
||||
if (!currentSnapshot) {
|
||||
return applyPluginAutoEnable({
|
||||
config: params.config,
|
||||
env,
|
||||
}).config;
|
||||
}
|
||||
|
||||
const cached = gatewayPluginConfigCache.get(params.config);
|
||||
if (cached?.snapshot === currentSnapshot && cached.env === env) {
|
||||
if (cached?.snapshot === currentSnapshot) {
|
||||
return cached.config;
|
||||
}
|
||||
|
||||
const config = applyPluginAutoEnable({
|
||||
config: params.config,
|
||||
env,
|
||||
manifestRegistry: currentSnapshot.manifestRegistry,
|
||||
discovery: currentSnapshot.discovery,
|
||||
}).config;
|
||||
gatewayPluginConfigCache.set(params.config, { env, snapshot: currentSnapshot, config });
|
||||
gatewayPluginConfigCache.set(params.config, { snapshot: currentSnapshot, config });
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,6 @@ describe("channelsHandlers channels.start", () => {
|
||||
|
||||
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
env: process.env,
|
||||
});
|
||||
expect(startChannel).toHaveBeenCalledWith("whatsapp", "default-account");
|
||||
expect(respond).toHaveBeenCalledWith(
|
||||
|
||||
@@ -143,7 +143,6 @@ describe("channelsHandlers channels.status", () => {
|
||||
|
||||
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
env: process.env,
|
||||
});
|
||||
const snapshotArgs = requireRecord(requireFirstCallArg(mocks.buildChannelAccountSnapshot));
|
||||
expect(snapshotArgs.cfg).toBe(autoEnabledConfig);
|
||||
|
||||
@@ -304,7 +304,6 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
const runtimeConfig = context.getRuntimeConfig();
|
||||
const cfg = resolveGatewayPluginConfig({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
const runtime = context.getRuntimeSnapshot();
|
||||
const plugins = listChannelPlugins();
|
||||
@@ -583,7 +582,6 @@ export const channelsHandlers: GatewayRequestHandlers = {
|
||||
const runtimeConfig = context.getRuntimeConfig();
|
||||
const cfg = resolveGatewayPluginConfig({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
const payload = await startChannelAccount({
|
||||
channelId,
|
||||
|
||||
@@ -652,7 +652,6 @@ describe("gateway send mirroring", () => {
|
||||
|
||||
expect(mocks.applyPluginAutoEnable).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
env: process.env,
|
||||
});
|
||||
expect(mocks.resolveMessageChannelSelection).toHaveBeenCalledWith({
|
||||
cfg: autoEnabledConfig,
|
||||
|
||||
@@ -134,7 +134,6 @@ async function resolveRequestedChannel(params: {
|
||||
const runtimeConfig = params.context.getRuntimeConfig();
|
||||
const cfg = resolveGatewayPluginConfig({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
let channel = normalizedChannel;
|
||||
if (!channel) {
|
||||
|
||||
@@ -841,7 +841,6 @@ export async function startGatewayServer(
|
||||
const runtimeConfig = getRuntimeConfig();
|
||||
return resolveGatewayPluginConfig({
|
||||
config: runtimeConfig,
|
||||
env: process.env,
|
||||
});
|
||||
},
|
||||
channelLogs,
|
||||
|
||||
Reference in New Issue
Block a user