fix: keep static provider entries out of live discovery

This commit is contained in:
Shakker
2026-04-24 04:58:03 +01:00
committed by Shakker
parent 4737a86071
commit 6f04eee2a1
2 changed files with 110 additions and 3 deletions

View File

@@ -0,0 +1,100 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { PluginManifestRecord } from "./manifest-registry.js";
import type { ProviderPlugin } from "./types.js";
const mocks = vi.hoisted(() => ({
loadPluginManifestRegistry: vi.fn(),
resolveDiscoveredProviderPluginIds: vi.fn(),
resolvePluginProviders: vi.fn(),
loadSource: vi.fn(),
}));
vi.mock("./manifest-registry.js", () => ({
loadPluginManifestRegistry: mocks.loadPluginManifestRegistry,
}));
vi.mock("./providers.js", () => ({
resolveDiscoveredProviderPluginIds: mocks.resolveDiscoveredProviderPluginIds,
}));
vi.mock("./providers.runtime.js", () => ({
resolvePluginProviders: mocks.resolvePluginProviders,
}));
vi.mock("./source-loader.js", () => ({
createPluginSourceLoader: () => mocks.loadSource,
}));
import { resolvePluginDiscoveryProvidersRuntime } from "./provider-discovery.runtime.js";
function createManifestPlugin(id: string): PluginManifestRecord {
return {
id,
enabledByDefault: true,
channels: [],
providers: [id],
cliBackends: [],
skills: [],
hooks: [],
origin: "bundled",
rootDir: `/tmp/${id}`,
source: "bundled",
manifestPath: `/tmp/${id}/openclaw.plugin.json`,
providerDiscoverySource: `/tmp/${id}/provider-discovery.ts`,
};
}
function createProvider(params: { id: string; mode: "static" | "catalog" }): ProviderPlugin {
const hook = {
run: async () => ({
provider: {
baseUrl: "https://example.test/v1",
models: [],
},
}),
};
return {
id: params.id,
label: params.id,
auth: [],
...(params.mode === "static" ? { staticCatalog: hook } : { catalog: hook }),
};
}
describe("resolvePluginDiscoveryProvidersRuntime", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.resolveDiscoveredProviderPluginIds.mockReturnValue(["deepseek"]);
mocks.loadPluginManifestRegistry.mockReturnValue({
plugins: [createManifestPlugin("deepseek")],
diagnostics: [],
});
});
it("falls back to full provider plugins when discovery entries only expose static catalogs", () => {
const fullProvider = createProvider({ id: "deepseek", mode: "catalog" });
mocks.loadSource.mockReturnValue(createProvider({ id: "deepseek", mode: "static" }));
mocks.resolvePluginProviders.mockReturnValue([fullProvider]);
expect(resolvePluginDiscoveryProvidersRuntime({})).toEqual([fullProvider]);
expect(mocks.resolvePluginProviders).toHaveBeenCalledWith(
expect.objectContaining({
bundledProviderAllowlistCompat: true,
}),
);
});
it("returns static-only discovery entries for callers that explicitly request them", () => {
const staticProvider = createProvider({ id: "deepseek", mode: "static" });
mocks.loadSource.mockReturnValue(staticProvider);
expect(resolvePluginDiscoveryProvidersRuntime({ discoveryEntriesOnly: true })).toEqual([
expect.objectContaining({
id: "deepseek",
pluginId: "deepseek",
staticCatalog: staticProvider.staticCatalog,
}),
]);
expect(mocks.resolvePluginProviders).not.toHaveBeenCalled();
});
});

View File

@@ -37,6 +37,12 @@ function normalizeDiscoveryModule(value: ProviderDiscoveryModule): ProviderPlugi
return [];
}
function hasLiveProviderDiscoveryHook(provider: ProviderPlugin): boolean {
return (
typeof provider.catalog?.run === "function" || typeof provider.discovery?.run === "function"
);
}
function resolveProviderDiscoveryEntryPlugins(params: {
config?: OpenClawConfig;
workspaceDir?: string;
@@ -86,11 +92,12 @@ export function resolvePluginDiscoveryProvidersRuntime(params: {
discoveryEntriesOnly?: boolean;
}): ProviderPlugin[] {
const entryProviders = resolveProviderDiscoveryEntryPlugins(params);
if (entryProviders.length > 0) {
if (params.discoveryEntriesOnly === true) {
return entryProviders;
}
if (params.discoveryEntriesOnly === true) {
return [];
const liveEntryProviders = entryProviders.filter(hasLiveProviderDiscoveryHook);
if (liveEntryProviders.length > 0) {
return liveEntryProviders;
}
return resolvePluginProviders({
...params,