!feat(plugins): add web fetch provider boundary (#59465)

* feat(plugins): add web fetch provider boundary

* feat(plugins): add web fetch provider modules

* refactor(web-fetch): remove remaining core firecrawl fetch config

* fix(web-fetch): address review follow-ups

* fix(web-fetch): harden provider runtime boundaries

* fix(web-fetch): restore firecrawl compare helper

* fix(web-fetch): restore env-based provider autodetect

* fix(web-fetch): tighten provider hardening

* fix(web-fetch): restore fetch autodetect and compat args

* chore(changelog): note firecrawl fetch config break
This commit is contained in:
Vincent Koc
2026-04-02 20:25:19 +09:00
committed by GitHub
parent 82d5e6a2f7
commit 38d2faee20
72 changed files with 3425 additions and 1119 deletions

View File

@@ -22,6 +22,7 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
registerSpeechProvider() {},
registerMediaUnderstandingProvider() {},
registerImageGenerationProvider() {},
registerWebFetchProvider() {},
registerWebSearchProvider() {},
registerInteractiveHandler() {},
onConversationBindingResolved() {},

View File

@@ -10,6 +10,7 @@ import { loadPluginManifestRegistry } from "../../../src/plugins/manifest-regist
type PluginRegistrationContractParams = {
pluginId: string;
providerIds?: string[];
webFetchProviderIds?: string[];
webSearchProviderIds?: string[];
speechProviderIds?: string[];
mediaUnderstandingProviderIds?: string[];
@@ -104,6 +105,14 @@ export function describePluginRegistrationContract(params: PluginRegistrationCon
});
}
if (params.webFetchProviderIds) {
it("keeps bundled web fetch ownership explicit", () => {
expect(findRegistration(params.pluginId).webFetchProviderIds).toEqual(
params.webFetchProviderIds,
);
});
}
if (params.speechProviderIds) {
it("keeps bundled speech ownership explicit", () => {
expect(findRegistration(params.pluginId).speechProviderIds).toEqual(

View File

@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import {
pluginRegistrationContractRegistry,
resolveWebFetchProviderContractEntriesForPluginId,
} from "../../../src/plugins/contracts/registry.js";
import { installWebFetchProviderContractSuite } from "../../../src/plugins/contracts/suites.js";
export function describeWebFetchProviderContracts(pluginId: string) {
const providerIds =
pluginRegistrationContractRegistry.find((entry) => entry.pluginId === pluginId)
?.webFetchProviderIds ?? [];
const resolveProviders = () => resolveWebFetchProviderContractEntriesForPluginId(pluginId);
describe(`${pluginId} web fetch provider contract registry load`, () => {
it("loads bundled web fetch providers", () => {
expect(resolveProviders().length).toBeGreaterThan(0);
});
});
for (const providerId of providerIds) {
describe(`${pluginId}:${providerId} web fetch contract`, () => {
installWebFetchProviderContractSuite({
provider: () => {
const entry = resolveProviders().find((provider) => provider.provider.id === providerId);
if (!entry) {
throw new Error(
`web fetch provider contract entry missing for ${pluginId}:${providerId}`,
);
}
return entry.provider;
},
credentialValue: () => {
const entry = resolveProviders().find((provider) => provider.provider.id === providerId);
if (!entry) {
throw new Error(
`web fetch provider contract entry missing for ${pluginId}:${providerId}`,
);
}
return entry.credentialValue;
},
});
});
}
}