mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:10:44 +00:00
refactor: reuse plugin metadata snapshots
This commit is contained in:
@@ -202,6 +202,77 @@ describe("model-pricing-cache", () => {
|
||||
expect(fetchImpl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses a provided metadata registry view without rebuilding manifest metadata", async () => {
|
||||
const manifestRegistry = {
|
||||
diagnostics: [],
|
||||
plugins: [
|
||||
createManifestRecord({
|
||||
id: "search-plugin",
|
||||
contracts: { webSearchProviders: ["search-plugin"] },
|
||||
}),
|
||||
],
|
||||
};
|
||||
const config = {
|
||||
plugins: {
|
||||
entries: {
|
||||
"search-plugin": {
|
||||
config: {
|
||||
webSearch: {
|
||||
model: "local-search/search-model",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
"local-search": {
|
||||
baseUrl: "http://127.0.0.1:43210/v1",
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
{
|
||||
id: "search-model",
|
||||
cost: { input: 0.2, output: 0.4 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
const fetchImpl = vi.fn<typeof fetch>();
|
||||
|
||||
await refreshGatewayModelPricingCache({
|
||||
config,
|
||||
fetchImpl,
|
||||
pluginMetadataSnapshot: {
|
||||
index: {
|
||||
plugins: [
|
||||
{
|
||||
pluginId: "search-plugin",
|
||||
origin: "global",
|
||||
enabled: true,
|
||||
enabledByDefault: true,
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
manifestRegistry,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
pluginManifestRegistryMocks.loadPluginManifestRegistryForInstalledIndex,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(fetchImpl).not.toHaveBeenCalled();
|
||||
expect(
|
||||
getCachedGatewayModelPricing({ provider: "local-search", model: "search-model" }),
|
||||
).toEqual({
|
||||
input: 0.2,
|
||||
output: 0.4,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not load plugin manifests for pricing when plugins are globally disabled", async () => {
|
||||
const config = {
|
||||
plugins: {
|
||||
|
||||
@@ -13,18 +13,15 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { planManifestModelCatalogRows, type ModelCatalogCost } from "../model-catalog/index.js";
|
||||
import { isInstalledPluginEnabled } from "../plugins/installed-plugin-index.js";
|
||||
import { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
|
||||
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
|
||||
import type {
|
||||
PluginManifestModelPricingModelIdTransform,
|
||||
PluginManifestModelPricingProvider,
|
||||
PluginManifestModelPricingSource,
|
||||
} from "../plugins/manifest.js";
|
||||
import type { PluginLookUpTable } from "../plugins/plugin-lookup-table.js";
|
||||
import {
|
||||
loadPluginRegistrySnapshot,
|
||||
type PluginRegistrySnapshot,
|
||||
} from "../plugins/plugin-registry.js";
|
||||
import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js";
|
||||
import { normalizeOptionalString, resolvePrimaryStringValue } from "../shared/string-coerce.js";
|
||||
import {
|
||||
clearGatewayModelPricingCacheState,
|
||||
@@ -400,15 +397,19 @@ function filterActiveManifestRegistry(params: {
|
||||
|
||||
function resolveModelPricingManifestMetadata(params: {
|
||||
config: OpenClawConfig;
|
||||
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
pluginLookUpTable?: PluginMetadataRegistryView;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): ModelPricingManifestMetadata {
|
||||
if (params.pluginLookUpTable) {
|
||||
const metadataSnapshot = params.pluginMetadataSnapshot ?? params.pluginLookUpTable;
|
||||
if (metadataSnapshot) {
|
||||
return {
|
||||
allRegistry: params.pluginLookUpTable.manifestRegistry,
|
||||
allRegistry: metadataSnapshot.manifestRegistry,
|
||||
activeRegistry: filterActiveManifestRegistry({
|
||||
registry: params.pluginLookUpTable.manifestRegistry,
|
||||
index: params.pluginLookUpTable.index,
|
||||
registry: metadataSnapshot.manifestRegistry,
|
||||
index: metadataSnapshot.index,
|
||||
config: params.config,
|
||||
}),
|
||||
};
|
||||
@@ -426,17 +427,16 @@ function resolveModelPricingManifestMetadata(params: {
|
||||
activeRegistry: emptyRegistry,
|
||||
};
|
||||
}
|
||||
const index = loadPluginRegistrySnapshot({ config: params.config });
|
||||
const allRegistry = loadPluginManifestRegistryForInstalledIndex({
|
||||
index,
|
||||
const snapshot = loadPluginMetadataSnapshot({
|
||||
config: params.config,
|
||||
includeDisabled: true,
|
||||
env: params.env ?? process.env,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
return {
|
||||
allRegistry,
|
||||
allRegistry: snapshot.manifestRegistry,
|
||||
activeRegistry: filterActiveManifestRegistry({
|
||||
registry: allRegistry,
|
||||
index,
|
||||
registry: snapshot.manifestRegistry,
|
||||
index: snapshot.index,
|
||||
config: params.config,
|
||||
}),
|
||||
};
|
||||
@@ -1102,8 +1102,11 @@ function collectSeededPricing(params: {
|
||||
|
||||
export async function refreshGatewayModelPricingCache(params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
fetchImpl?: typeof fetch;
|
||||
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
|
||||
workspaceDir?: string;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
pluginLookUpTable?: PluginMetadataRegistryView;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): Promise<void> {
|
||||
if (!isGatewayModelPricingEnabled(params.config)) {
|
||||
@@ -1117,6 +1120,9 @@ export async function refreshGatewayModelPricingCache(params: {
|
||||
inFlightRefresh = (async () => {
|
||||
const manifestMetadata = resolveModelPricingManifestMetadata({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
workspaceDir: params.workspaceDir,
|
||||
pluginMetadataSnapshot: params.pluginMetadataSnapshot,
|
||||
pluginLookUpTable: params.pluginLookUpTable,
|
||||
manifestRegistry: params.manifestRegistry,
|
||||
});
|
||||
@@ -1251,8 +1257,11 @@ export async function refreshGatewayModelPricingCache(params: {
|
||||
|
||||
export function startGatewayModelPricingRefresh(params: {
|
||||
config: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
fetchImpl?: typeof fetch;
|
||||
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
|
||||
workspaceDir?: string;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
pluginLookUpTable?: PluginMetadataRegistryView;
|
||||
manifestRegistry?: PluginManifestRegistry;
|
||||
}): () => void {
|
||||
if (!isGatewayModelPricingEnabled(params.config)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { isVitestRuntimeEnv } from "../infra/env.js";
|
||||
import { startHeartbeatRunner, type HeartbeatRunner } from "../infra/heartbeat-runner.js";
|
||||
import type { PluginLookUpTable } from "../plugins/plugin-lookup-table.js";
|
||||
import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import type { ChannelHealthMonitor } from "./channel-health-monitor.js";
|
||||
import { startChannelHealthMonitor } from "./channel-health-monitor.js";
|
||||
import { isGatewayModelPricingEnabled } from "./model-pricing-config.js";
|
||||
@@ -91,7 +91,7 @@ function recoverPendingSessionDeliveries(params: {
|
||||
|
||||
function startGatewayModelPricingRefreshOnDemand(params: {
|
||||
config: OpenClawConfig;
|
||||
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
|
||||
pluginLookUpTable?: PluginMetadataRegistryView;
|
||||
log: GatewayRuntimeServiceLogger;
|
||||
}): () => void {
|
||||
if (!isGatewayModelPricingEnabled(params.config)) {
|
||||
@@ -125,7 +125,7 @@ export function startGatewayRuntimeServices(params: {
|
||||
cfgAtStart: OpenClawConfig;
|
||||
channelManager: GatewayChannelManager;
|
||||
log: GatewayRuntimeServiceLogger;
|
||||
pluginLookUpTable?: Pick<PluginLookUpTable, "index" | "manifestRegistry">;
|
||||
pluginLookUpTable?: PluginMetadataRegistryView;
|
||||
}): {
|
||||
heartbeatRunner: HeartbeatRunner;
|
||||
channelHealthMonitor: ChannelHealthMonitor | null;
|
||||
|
||||
66
src/plugins/manifest-contract-eligibility.test.ts
Normal file
66
src/plugins/manifest-contract-eligibility.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getCurrentPluginMetadataSnapshot: vi.fn(),
|
||||
loadPluginMetadataSnapshot: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./current-plugin-metadata-snapshot.js", () => ({
|
||||
getCurrentPluginMetadataSnapshot: mocks.getCurrentPluginMetadataSnapshot,
|
||||
}));
|
||||
|
||||
vi.mock("./plugin-metadata-snapshot.js", () => ({
|
||||
loadPluginMetadataSnapshot: mocks.loadPluginMetadataSnapshot,
|
||||
}));
|
||||
|
||||
import { loadManifestContractSnapshot } from "./manifest-contract-eligibility.js";
|
||||
|
||||
describe("loadManifestContractSnapshot", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.getCurrentPluginMetadataSnapshot.mockReturnValue(undefined);
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: { plugins: [] },
|
||||
plugins: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("checks the current metadata snapshot with env and workspace scope", () => {
|
||||
const env = { HOME: "/home/snapshot" } as NodeJS.ProcessEnv;
|
||||
const current = {
|
||||
index: { plugins: [] },
|
||||
plugins: [],
|
||||
};
|
||||
mocks.getCurrentPluginMetadataSnapshot.mockReturnValue(current);
|
||||
|
||||
expect(loadManifestContractSnapshot({ config: {}, workspaceDir: "/workspace", env })).toBe(
|
||||
current,
|
||||
);
|
||||
|
||||
expect(mocks.getCurrentPluginMetadataSnapshot).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
env,
|
||||
workspaceDir: "/workspace",
|
||||
});
|
||||
expect(mocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to the shared metadata snapshot loader", () => {
|
||||
const env = { HOME: "/home/fallback" } as NodeJS.ProcessEnv;
|
||||
const snapshot = {
|
||||
index: { plugins: [{ pluginId: "demo" }] },
|
||||
plugins: [{ id: "demo" }],
|
||||
};
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue(snapshot);
|
||||
|
||||
expect(loadManifestContractSnapshot({ config: {}, env })).toEqual({
|
||||
index: snapshot.index,
|
||||
plugins: snapshot.plugins,
|
||||
});
|
||||
|
||||
expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
env,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
|
||||
import { isInstalledPluginEnabled } from "./installed-plugin-index.js";
|
||||
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
||||
import type { PluginManifestContractListKey, PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.types.js";
|
||||
import { loadPluginRegistrySnapshot } from "./plugin-registry.js";
|
||||
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import type {
|
||||
PluginMetadataManifestView,
|
||||
PluginMetadataSnapshot,
|
||||
} from "./plugin-metadata-snapshot.types.js";
|
||||
|
||||
export function isManifestPluginAvailableForControlPlane(params: {
|
||||
snapshot: Pick<PluginMetadataSnapshot, "index">;
|
||||
@@ -65,28 +67,23 @@ export function loadManifestContractSnapshot(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Pick<PluginMetadataSnapshot, "index" | "plugins"> {
|
||||
}): PluginMetadataManifestView {
|
||||
const env = params.env ?? process.env;
|
||||
const current = getCurrentPluginMetadataSnapshot({
|
||||
config: params.config,
|
||||
env,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
if (current) {
|
||||
return current;
|
||||
}
|
||||
const env = params.env ?? process.env;
|
||||
const index = loadPluginRegistrySnapshot({
|
||||
config: params.config,
|
||||
const snapshot = loadPluginMetadataSnapshot({
|
||||
config: params.config ?? {},
|
||||
env,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
return {
|
||||
index,
|
||||
plugins: loadPluginManifestRegistryForInstalledIndex({
|
||||
index,
|
||||
config: params.config,
|
||||
env,
|
||||
includeDisabled: true,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
}).plugins,
|
||||
index: snapshot.index,
|
||||
plugins: snapshot.plugins,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import { createPluginRegistryIdNormalizer } from "./plugin-registry-id-normalize
|
||||
import { loadPluginRegistrySnapshotWithMetadata } from "./plugin-registry-snapshot.js";
|
||||
export type {
|
||||
LoadPluginMetadataSnapshotParams,
|
||||
PluginMetadataManifestView,
|
||||
PluginMetadataRegistryView,
|
||||
PluginMetadataSnapshot,
|
||||
PluginMetadataSnapshotMetrics,
|
||||
PluginMetadataSnapshotOwnerMaps,
|
||||
@@ -146,6 +148,12 @@ function buildPluginMetadataOwnerMaps(
|
||||
};
|
||||
}
|
||||
|
||||
export function listPluginOriginsFromMetadataSnapshot(
|
||||
snapshot: Pick<PluginMetadataSnapshot, "plugins">,
|
||||
): ReadonlyMap<string, PluginManifestRecord["origin"]> {
|
||||
return new Map(snapshot.plugins.map((record) => [record.id, record.origin]));
|
||||
}
|
||||
|
||||
export function loadPluginMetadataSnapshot(
|
||||
params: LoadPluginMetadataSnapshotParams,
|
||||
): PluginMetadataSnapshot {
|
||||
|
||||
@@ -48,6 +48,10 @@ export type PluginMetadataSnapshot = {
|
||||
metrics: PluginMetadataSnapshotMetrics;
|
||||
};
|
||||
|
||||
export type PluginMetadataRegistryView = Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
|
||||
|
||||
export type PluginMetadataManifestView = Pick<PluginMetadataSnapshot, "index" | "plugins">;
|
||||
|
||||
export type LoadPluginMetadataSnapshotParams = {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
|
||||
@@ -3,20 +3,19 @@ import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { ProviderPlugin } from "./types.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
loadPluginRegistrySnapshot: vi.fn(),
|
||||
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
|
||||
loadPluginMetadataSnapshot: vi.fn(),
|
||||
resolveDiscoveredProviderPluginIds: vi.fn(),
|
||||
resolvePluginProviders: vi.fn(),
|
||||
loadSource: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./plugin-registry.js", () => ({
|
||||
loadPluginRegistrySnapshot: mocks.loadPluginRegistrySnapshot,
|
||||
}));
|
||||
|
||||
vi.mock("./manifest-registry-installed.js", () => ({
|
||||
loadPluginManifestRegistryForInstalledIndex: mocks.loadPluginManifestRegistryForInstalledIndex,
|
||||
}));
|
||||
vi.mock("./plugin-metadata-snapshot.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./plugin-metadata-snapshot.js")>();
|
||||
return {
|
||||
...actual,
|
||||
loadPluginMetadataSnapshot: mocks.loadPluginMetadataSnapshot,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./providers.js", () => ({
|
||||
resolveDiscoveredProviderPluginIds: mocks.resolveDiscoveredProviderPluginIds,
|
||||
@@ -82,11 +81,13 @@ function createProvider(params: { id: string; mode: "static" | "catalog" }): Pro
|
||||
describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
|
||||
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["deepseek"]);
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
|
||||
plugins: [createManifestPlugin("deepseek")],
|
||||
diagnostics: [],
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: { plugins: [] },
|
||||
manifestRegistry: {
|
||||
plugins: [createManifestPlugin("deepseek")],
|
||||
diagnostics: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,20 +117,23 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
"kilocode",
|
||||
"unused",
|
||||
]);
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
|
||||
plugins: [
|
||||
createManifestPlugin("codex"),
|
||||
createManifestPlugin("deepseek"),
|
||||
createManifestPluginWithoutDiscovery({
|
||||
id: "kilocode",
|
||||
providerAuthEnvVars: { kilocode: ["KILOCODE_API_KEY"] },
|
||||
}),
|
||||
createManifestPluginWithoutDiscovery({
|
||||
id: "unused",
|
||||
providerAuthEnvVars: { unused: ["UNUSED_API_KEY"] },
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: { plugins: [] },
|
||||
manifestRegistry: {
|
||||
plugins: [
|
||||
createManifestPlugin("codex"),
|
||||
createManifestPlugin("deepseek"),
|
||||
createManifestPluginWithoutDiscovery({
|
||||
id: "kilocode",
|
||||
providerAuthEnvVars: { kilocode: ["KILOCODE_API_KEY"] },
|
||||
}),
|
||||
createManifestPluginWithoutDiscovery({
|
||||
id: "unused",
|
||||
providerAuthEnvVars: { unused: ["UNUSED_API_KEY"] },
|
||||
}),
|
||||
],
|
||||
diagnostics: [],
|
||||
},
|
||||
});
|
||||
mocks.loadSource.mockImplementation((modulePath: string) =>
|
||||
modulePath.includes("/codex/")
|
||||
@@ -150,27 +154,25 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shares one registry snapshot and manifest registry between provider id discovery and entry loading", () => {
|
||||
it("shares one metadata snapshot between provider id discovery and entry loading", () => {
|
||||
const registry = { plugins: [] };
|
||||
const manifestRegistry = {
|
||||
plugins: [createManifestPlugin("deepseek")],
|
||||
diagnostics: [],
|
||||
};
|
||||
mocks.loadPluginRegistrySnapshot.mockReturnValue(registry);
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(manifestRegistry);
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: registry,
|
||||
manifestRegistry,
|
||||
});
|
||||
mocks.loadSource.mockReturnValue(createProvider({ id: "deepseek", mode: "catalog" }));
|
||||
|
||||
resolvePluginDiscoveryProvidersRuntime({ config: {}, env: {} as NodeJS.ProcessEnv });
|
||||
|
||||
expect(mocks.loadPluginRegistrySnapshot).toHaveBeenCalledOnce();
|
||||
expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledWith({
|
||||
index: registry,
|
||||
expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledWith({
|
||||
config: {},
|
||||
workspaceDir: undefined,
|
||||
env: {},
|
||||
includeDisabled: true,
|
||||
});
|
||||
expect(mocks.loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
|
||||
expect(mocks.loadPluginMetadataSnapshot).toHaveBeenCalledOnce();
|
||||
expect(mocks.resolveDiscoveredProviderPluginIds).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
registry,
|
||||
@@ -203,8 +205,7 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(mocks.loadPluginRegistrySnapshot).not.toHaveBeenCalled();
|
||||
expect(mocks.loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
|
||||
expect(mocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
|
||||
expect(mocks.resolveDiscoveredProviderPluginIds).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
registry,
|
||||
@@ -228,9 +229,12 @@ describe("resolvePluginDiscoveryProvidersRuntime", () => {
|
||||
});
|
||||
|
||||
it("does not fall back to full plugin loading when discovery entries are requested only", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue({
|
||||
plugins: [createManifestPluginWithoutDiscovery({ id: "deepseek" })],
|
||||
diagnostics: [],
|
||||
mocks.loadPluginMetadataSnapshot.mockReturnValue({
|
||||
index: { plugins: [] },
|
||||
manifestRegistry: {
|
||||
plugins: [createManifestPluginWithoutDiscovery({ id: "deepseek" })],
|
||||
diagnostics: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolvePluginDiscoveryProvidersRuntime({ discoveryEntriesOnly: true })).toEqual([]);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
|
||||
import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import { loadPluginRegistrySnapshot } from "./plugin-registry.js";
|
||||
import { loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js";
|
||||
import { resolveDiscoveredProviderPluginIds } from "./providers.js";
|
||||
import { resolvePluginProviders } from "./providers.runtime.js";
|
||||
import { createPluginSourceLoader } from "./source-loader.js";
|
||||
@@ -77,18 +76,17 @@ function resolveProviderDiscoveryEntryPlugins(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}): ProviderDiscoveryEntryResult {
|
||||
const registry = params.pluginMetadataSnapshot?.index ?? loadPluginRegistrySnapshot(params);
|
||||
const manifestRegistry =
|
||||
params.pluginMetadataSnapshot?.manifestRegistry ??
|
||||
loadPluginManifestRegistryForInstalledIndex({
|
||||
index: registry,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
includeDisabled: true,
|
||||
const metadataSnapshot =
|
||||
params.pluginMetadataSnapshot ??
|
||||
loadPluginMetadataSnapshot({
|
||||
config: params.config ?? {},
|
||||
env: params.env ?? process.env,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
const registry = metadataSnapshot.index;
|
||||
const manifestRegistry = metadataSnapshot.manifestRegistry;
|
||||
const pluginIds = resolveDiscoveredProviderPluginIds({
|
||||
...params,
|
||||
registry,
|
||||
@@ -148,10 +146,10 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
}): ProviderPlugin[] {
|
||||
const env = params.env ?? process.env;
|
||||
const entryResult = resolveProviderDiscoveryEntryPlugins(params);
|
||||
const entryResult = resolveProviderDiscoveryEntryPlugins({ ...params, env });
|
||||
if (params.discoveryEntriesOnly === true) {
|
||||
return entryResult.providers;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import type { ModelProviderConfig } from "../config/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
import type { PluginMetadataRegistryView } from "./plugin-metadata-snapshot.types.js";
|
||||
import {
|
||||
listPluginContributionIds,
|
||||
loadPluginRegistrySnapshot,
|
||||
@@ -43,7 +43,7 @@ export type ResolveRuntimePluginDiscoveryProvidersParams = {
|
||||
includeUntrustedWorkspacePlugins?: boolean;
|
||||
requireCompleteDiscoveryEntryCoverage?: boolean;
|
||||
discoveryEntriesOnly?: boolean;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry">;
|
||||
pluginMetadataSnapshot?: PluginMetadataRegistryView;
|
||||
};
|
||||
|
||||
export type ResolveInstalledPluginProviderContributionIdsParams = LoadPluginRegistryParams & {
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export { loadPluginManifestRegistryForInstalledIndex } from "../plugins/manifest-registry-installed.js";
|
||||
export { loadPluginRegistrySnapshot } from "../plugins/plugin-registry.js";
|
||||
export {
|
||||
listPluginOriginsFromMetadataSnapshot,
|
||||
loadPluginMetadataSnapshot,
|
||||
} from "../plugins/plugin-metadata-snapshot.js";
|
||||
|
||||
@@ -2,25 +2,32 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { asConfig, setupSecretsRuntimeSnapshotTestHooks } from "./runtime.test-support.ts";
|
||||
|
||||
const manifestMocks = vi.hoisted(() => ({
|
||||
loadPluginManifestRegistryForInstalledIndex: vi.fn(),
|
||||
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
|
||||
listPluginOriginsFromMetadataSnapshot: vi.fn(
|
||||
(snapshot: { plugins: Array<{ id: string; origin: string }> }) =>
|
||||
new Map(snapshot.plugins.map((record) => [record.id, record.origin])),
|
||||
),
|
||||
loadPluginMetadataSnapshot: vi.fn<() => { plugins: Array<{ id: string; origin: string }> }>(
|
||||
() => ({
|
||||
plugins: [],
|
||||
}),
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("./runtime-manifest.runtime.js", () => ({
|
||||
loadPluginManifestRegistryForInstalledIndex:
|
||||
manifestMocks.loadPluginManifestRegistryForInstalledIndex,
|
||||
loadPluginRegistrySnapshot: manifestMocks.loadPluginRegistrySnapshot,
|
||||
listPluginOriginsFromMetadataSnapshot: manifestMocks.listPluginOriginsFromMetadataSnapshot,
|
||||
loadPluginMetadataSnapshot: manifestMocks.loadPluginMetadataSnapshot,
|
||||
}));
|
||||
|
||||
const { prepareSecretsRuntimeSnapshot } = setupSecretsRuntimeSnapshotTestHooks();
|
||||
|
||||
describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => {
|
||||
afterEach(() => {
|
||||
manifestMocks.loadPluginManifestRegistryForInstalledIndex.mockReset();
|
||||
manifestMocks.loadPluginRegistrySnapshot.mockReset();
|
||||
manifestMocks.listPluginOriginsFromMetadataSnapshot.mockClear();
|
||||
manifestMocks.loadPluginMetadataSnapshot.mockReset();
|
||||
manifestMocks.loadPluginMetadataSnapshot.mockReturnValue({ plugins: [] });
|
||||
});
|
||||
|
||||
it("skips manifest registry loading when plugin entries are absent", async () => {
|
||||
it("skips metadata snapshot loading when plugin entries are absent", async () => {
|
||||
await prepareSecretsRuntimeSnapshot({
|
||||
config: asConfig({
|
||||
models: {
|
||||
@@ -36,7 +43,42 @@ describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => {
|
||||
includeAuthStoreRefs: false,
|
||||
});
|
||||
|
||||
expect(manifestMocks.loadPluginManifestRegistryForInstalledIndex).not.toHaveBeenCalled();
|
||||
expect(manifestMocks.loadPluginRegistrySnapshot).not.toHaveBeenCalled();
|
||||
expect(manifestMocks.loadPluginMetadataSnapshot).not.toHaveBeenCalled();
|
||||
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("derives loadable plugin origins from the shared metadata snapshot", async () => {
|
||||
const snapshot = {
|
||||
plugins: [{ id: "demo", origin: "workspace" }],
|
||||
};
|
||||
manifestMocks.loadPluginMetadataSnapshot.mockReturnValue(snapshot);
|
||||
|
||||
await prepareSecretsRuntimeSnapshot({
|
||||
config: asConfig({
|
||||
plugins: {
|
||||
entries: {
|
||||
demo: {
|
||||
config: {
|
||||
apiKey: { source: "env", provider: "default", id: "DEMO_API_KEY" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
env: { HOME: "/home/demo", DEMO_API_KEY: "sk-demo" },
|
||||
includeAuthStoreRefs: false,
|
||||
});
|
||||
|
||||
expect(manifestMocks.loadPluginMetadataSnapshot).toHaveBeenCalledWith({
|
||||
config: expect.objectContaining({
|
||||
plugins: expect.any(Object),
|
||||
}),
|
||||
workspaceDir: expect.any(String),
|
||||
env: expect.objectContaining({
|
||||
HOME: "/home/demo",
|
||||
DEMO_API_KEY: "sk-demo",
|
||||
}),
|
||||
});
|
||||
expect(manifestMocks.listPluginOriginsFromMetadataSnapshot).toHaveBeenCalledWith(snapshot);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,21 +140,14 @@ async function resolveLoadablePluginOrigins(params: {
|
||||
params.config,
|
||||
resolveDefaultAgentId(params.config),
|
||||
);
|
||||
const { loadPluginManifestRegistryForInstalledIndex, loadPluginRegistrySnapshot } =
|
||||
const { listPluginOriginsFromMetadataSnapshot, loadPluginMetadataSnapshot } =
|
||||
await loadRuntimeManifestHelpers();
|
||||
const index = loadPluginRegistrySnapshot({
|
||||
const snapshot = loadPluginMetadataSnapshot({
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const manifestRegistry = loadPluginManifestRegistryForInstalledIndex({
|
||||
index,
|
||||
config: params.config,
|
||||
workspaceDir,
|
||||
env: params.env,
|
||||
includeDisabled: true,
|
||||
});
|
||||
return new Map(manifestRegistry.plugins.map((record) => [record.id, record.origin]));
|
||||
return listPluginOriginsFromMetadataSnapshot(snapshot);
|
||||
}
|
||||
|
||||
function mergeSecretsRuntimeEnv(
|
||||
|
||||
Reference in New Issue
Block a user