refactor: simplify web provider plugin discovery

This commit is contained in:
Peter Steinberger
2026-04-05 08:49:05 +01:00
parent c863ee1b86
commit 23275edef1
33 changed files with 420 additions and 1037 deletions

View File

@@ -11,30 +11,15 @@ const { resolvePluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const { resolveBundledPluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolveBundledPluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const { resolvePluginWebFetchProvidersMock } = vi.hoisted(() => ({
resolvePluginWebFetchProvidersMock: vi.fn(() => buildTestWebFetchProviders()),
}));
const { resolveBundledPluginWebFetchProvidersMock } = vi.hoisted(() => ({
resolveBundledPluginWebFetchProvidersMock: vi.fn(() => buildTestWebFetchProviders()),
}));
let bundledWebSearchProviders: typeof import("../plugins/web-search-providers.js");
let runtimeWebSearchProviders: typeof import("../plugins/web-search-providers.runtime.js");
let bundledWebFetchProviders: typeof import("../plugins/web-fetch-providers.js");
let runtimeWebFetchProviders: typeof import("../plugins/web-fetch-providers.runtime.js");
let secretResolve: typeof import("./resolve.js");
let createResolverContext: typeof import("./runtime-shared.js").createResolverContext;
let resolveRuntimeWebTools: typeof import("./runtime-web-tools.js").resolveRuntimeWebTools;
vi.mock("../plugins/web-search-providers.js", () => ({
resolveBundledPluginWebSearchProviders: resolveBundledPluginWebSearchProvidersMock,
}));
vi.mock("../plugins/web-search-providers.runtime.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/web-search-providers.runtime.js")>(
"../plugins/web-search-providers.runtime.js",
@@ -45,10 +30,6 @@ vi.mock("../plugins/web-search-providers.runtime.js", async () => {
};
});
vi.mock("../plugins/web-fetch-providers.js", () => ({
resolveBundledPluginWebFetchProviders: resolveBundledPluginWebFetchProvidersMock,
}));
vi.mock("../plugins/web-fetch-providers.runtime.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/web-fetch-providers.runtime.js")>(
"../plugins/web-fetch-providers.runtime.js",
@@ -264,9 +245,7 @@ function expectInactiveWebFetchProviderSecretRef(params: {
describe("runtime web tools resolution", () => {
beforeAll(async () => {
bundledWebSearchProviders = await import("../plugins/web-search-providers.js");
runtimeWebSearchProviders = await import("../plugins/web-search-providers.runtime.js");
bundledWebFetchProviders = await import("../plugins/web-fetch-providers.js");
runtimeWebFetchProviders = await import("../plugins/web-fetch-providers.runtime.js");
secretResolve = await import("./resolve.js");
({ createResolverContext } = await import("./runtime-shared.js"));
@@ -275,9 +254,7 @@ describe("runtime web tools resolution", () => {
beforeEach(() => {
runtimeWebSearchProviders.__testing.resetWebSearchProviderSnapshotCacheForTests();
vi.mocked(bundledWebSearchProviders.resolveBundledPluginWebSearchProviders).mockClear();
vi.mocked(runtimeWebSearchProviders.resolvePluginWebSearchProviders).mockClear();
vi.mocked(bundledWebFetchProviders.resolveBundledPluginWebFetchProviders).mockClear();
vi.mocked(runtimeWebFetchProviders.resolvePluginWebFetchProviders).mockClear();
});
@@ -665,9 +642,8 @@ describe("runtime web tools resolution", () => {
);
});
it("uses bundled provider resolution for configured bundled providers", async () => {
const bundledSpy = vi.mocked(bundledWebSearchProviders.resolveBundledPluginWebSearchProviders);
const genericSpy = vi.mocked(runtimeWebSearchProviders.resolvePluginWebSearchProviders);
it("uses bundled-only runtime provider resolution for configured bundled providers", async () => {
const runtimeSpy = vi.mocked(runtimeWebSearchProviders.resolvePluginWebSearchProviders);
const { metadata } = await runRuntimeWebTools({
config: asConfig({
@@ -698,13 +674,13 @@ describe("runtime web tools resolution", () => {
});
expect(metadata.search.selectedProvider).toBe("gemini");
expect(bundledSpy).toHaveBeenCalledWith(
expect(runtimeSpy).toHaveBeenCalledWith(
expect.objectContaining({
bundledAllowlistCompat: true,
onlyPluginIds: ["google"],
origin: "bundled",
}),
);
expect(genericSpy).not.toHaveBeenCalled();
});
it("does not resolve web fetch provider SecretRef when web fetch is inactive", async () => {
@@ -955,7 +931,6 @@ describe("runtime web tools resolution", () => {
});
it("keeps web fetch provider discovery bundled-only during runtime secret resolution", async () => {
const bundledSpy = vi.mocked(bundledWebFetchProviders.resolveBundledPluginWebFetchProviders);
const runtimeSpy = vi.mocked(runtimeWebFetchProviders.resolvePluginWebFetchProviders);
const { metadata } = await runRuntimeWebTools({
@@ -986,7 +961,11 @@ describe("runtime web tools resolution", () => {
});
expect(metadata.fetch.selectedProvider).toBe("firecrawl");
expect(bundledSpy).toHaveBeenCalled();
expect(runtimeSpy).not.toHaveBeenCalled();
expect(runtimeSpy).toHaveBeenCalledWith(
expect.objectContaining({
bundledAllowlistCompat: true,
origin: "bundled",
}),
);
});
});

View File

@@ -1,17 +1,17 @@
import type { OpenClawConfig } from "../config/config.js";
import { resolveSecretInputRef } from "../config/types.secrets.js";
import { resolveBundledWebFetchPluginId } from "../plugins/bundled-web-fetch-provider-ids.js";
import { listBundledWebSearchPluginIds } from "../plugins/bundled-web-search-ids.js";
import { resolveBundledWebSearchPluginId } from "../plugins/bundled-web-search-provider-ids.js";
import {
resolveManifestContractOwnerPluginId,
resolveManifestContractPluginIds,
} from "../plugins/manifest-registry.js";
import type {
PluginWebFetchProviderEntry,
PluginWebSearchProviderEntry,
WebFetchCredentialResolutionSource,
WebSearchCredentialResolutionSource,
} from "../plugins/types.js";
import { resolveBundledPluginWebFetchProviders } from "../plugins/web-fetch-providers.js";
import { resolvePluginWebFetchProviders } from "../plugins/web-fetch-providers.runtime.js";
import { sortWebFetchProvidersForAutoDetect } from "../plugins/web-fetch-providers.shared.js";
import { resolveBundledPluginWebSearchProviders } from "../plugins/web-search-providers.js";
import { resolvePluginWebSearchProviders } from "../plugins/web-search-providers.runtime.js";
import { sortWebSearchProvidersForAutoDetect } from "../plugins/web-search-providers.shared.js";
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
@@ -101,7 +101,14 @@ function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean {
return true;
}
const bundledPluginIds = new Set<string>(listBundledWebSearchPluginIds());
const bundledPluginIds = new Set<string>(
resolveManifestContractPluginIds({
contract: "webSearchProviders",
origin: "bundled",
config,
env: process.env,
}),
);
const hasNonBundledPluginId = (pluginId: string) => !bundledPluginIds.has(pluginId.trim());
if (Array.isArray(plugins.allow) && plugins.allow.some(hasNonBundledPluginId)) {
return true;
@@ -364,7 +371,13 @@ export async function resolveRuntimeWebTools(params: {
const search = isRecord(web?.search) ? web.search : undefined;
const rawProvider =
typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : "";
const configuredBundledPluginId = resolveBundledWebSearchPluginId(rawProvider);
const configuredBundledPluginId = resolveManifestContractOwnerPluginId({
contract: "webSearchProviders",
value: rawProvider,
origin: "bundled",
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
});
const searchMetadata: RuntimeWebSearchMetadata = {
providerSource: "none",
@@ -373,17 +386,19 @@ export async function resolveRuntimeWebTools(params: {
const searchProviders = sortWebSearchProvidersForAutoDetect(
configuredBundledPluginId
? resolveBundledPluginWebSearchProviders({
? resolvePluginWebSearchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
onlyPluginIds: [configuredBundledPluginId],
origin: "bundled",
})
: !hasCustomWebSearchPluginRisk(params.sourceConfig)
? resolveBundledPluginWebSearchProviders({
? resolvePluginWebSearchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
origin: "bundled",
})
: resolvePluginWebSearchProviders({
config: params.sourceConfig,
@@ -664,23 +679,31 @@ export async function resolveRuntimeWebTools(params: {
const fetch = isRecord(web?.fetch) ? (web.fetch as FetchConfig) : undefined;
const rawFetchProvider =
typeof fetch?.provider === "string" ? fetch.provider.trim().toLowerCase() : "";
const configuredBundledFetchPluginId = resolveBundledWebFetchPluginId(rawFetchProvider);
const configuredBundledFetchPluginId = resolveManifestContractOwnerPluginId({
contract: "webFetchProviders",
value: rawFetchProvider,
origin: "bundled",
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
});
const fetchMetadata: RuntimeWebFetchMetadata = {
providerSource: "none",
diagnostics: [],
};
const fetchProviders = sortWebFetchProvidersForAutoDetect(
configuredBundledFetchPluginId
? resolveBundledPluginWebFetchProviders({
? resolvePluginWebFetchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
onlyPluginIds: [configuredBundledFetchPluginId],
origin: "bundled",
})
: resolveBundledPluginWebFetchProviders({
: resolvePluginWebFetchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
origin: "bundled",
}),
);
const hasConfiguredFetchSurface =

View File

@@ -11,32 +11,20 @@ import { listSecretTargetRegistryEntries } from "./target-registry.js";
type SecretRegistryEntry = ReturnType<typeof listSecretTargetRegistryEntries>[number];
const { resolveBundledPluginWebSearchProvidersMock, resolvePluginWebSearchProvidersMock } =
vi.hoisted(() => ({
resolveBundledPluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const { resolveBundledPluginWebFetchProvidersMock, resolvePluginWebFetchProvidersMock } =
vi.hoisted(() => ({
resolveBundledPluginWebFetchProvidersMock: vi.fn(() => buildTestWebFetchProviders()),
resolvePluginWebFetchProvidersMock: vi.fn(() => buildTestWebFetchProviders()),
}));
const { resolvePluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const { resolvePluginWebFetchProvidersMock } = vi.hoisted(() => ({
resolvePluginWebFetchProvidersMock: vi.fn(() => buildTestWebFetchProviders()),
}));
let clearSecretsRuntimeSnapshot: typeof import("./runtime.js").clearSecretsRuntimeSnapshot;
let prepareSecretsRuntimeSnapshot: typeof import("./runtime.js").prepareSecretsRuntimeSnapshot;
vi.mock("../plugins/web-search-providers.js", () => ({
resolveBundledPluginWebSearchProviders: resolveBundledPluginWebSearchProvidersMock,
}));
vi.mock("../plugins/web-search-providers.runtime.js", () => ({
resolvePluginWebSearchProviders: resolvePluginWebSearchProvidersMock,
}));
vi.mock("../plugins/web-fetch-providers.js", () => ({
resolveBundledPluginWebFetchProviders: resolveBundledPluginWebFetchProvidersMock,
}));
vi.mock("../plugins/web-fetch-providers.runtime.js", () => ({
resolvePluginWebFetchProviders: resolvePluginWebFetchProvidersMock,
}));
@@ -343,9 +331,7 @@ describe("secrets runtime target coverage", () => {
afterEach(() => {
clearSecretsRuntimeSnapshot();
resolveBundledPluginWebSearchProvidersMock.mockReset();
resolvePluginWebSearchProvidersMock.mockReset();
resolveBundledPluginWebFetchProvidersMock.mockReset();
resolvePluginWebFetchProvidersMock.mockReset();
});

View File

@@ -10,14 +10,8 @@ import type { PluginWebSearchProviderEntry } from "../plugins/types.js";
type WebProviderUnderTest = "brave" | "gemini" | "grok" | "kimi" | "perplexity" | "firecrawl";
const { resolveBundledPluginWebSearchProvidersMock, resolvePluginWebSearchProvidersMock } =
vi.hoisted(() => ({
resolveBundledPluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
vi.mock("../plugins/web-search-providers.js", () => ({
resolveBundledPluginWebSearchProviders: resolveBundledPluginWebSearchProvidersMock,
const { resolvePluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
vi.mock("../plugins/web-search-providers.runtime.js", () => ({
@@ -132,8 +126,6 @@ describe("secrets runtime snapshot", () => {
});
beforeEach(() => {
resolveBundledPluginWebSearchProvidersMock.mockReset();
resolveBundledPluginWebSearchProvidersMock.mockReturnValue(buildTestWebSearchProviders());
resolvePluginWebSearchProvidersMock.mockReset();
resolvePluginWebSearchProvidersMock.mockReturnValue(buildTestWebSearchProviders());
});