perf(plugin-sdk): add web fetch contract artifacts

This commit is contained in:
Vincent Koc
2026-04-07 08:34:52 +01:00
parent 2ceafbafcc
commit 49fbecbf16
9 changed files with 103 additions and 3 deletions

View File

@@ -1,2 +1,2 @@
2375f50cfc8f29df7de7ec84d72ea654ef6eb203204f295fc287a11d63ed2cdb plugin-sdk-api-baseline.json
103ee7cf995fe6a0a3d109ad4bb785d342fa1eba83e3295dee3a5ee0bb315b4a plugin-sdk-api-baseline.jsonl
8592ce4554f44cd5325f44f86d37b3057548853cc9bc543b0611e5d9000f262e plugin-sdk-api-baseline.json
35393eceb6733d966430369c116511bb15a4b83eec93ebfd3b9a3e8f9ee29cec plugin-sdk-api-baseline.jsonl

View File

@@ -133,6 +133,7 @@ explicitly promotes one as public.
| `plugin-sdk/provider-model-shared` | `ProviderReplayFamily`, `buildProviderReplayFamilyHooks`, `normalizeModelCompat`, shared replay-policy builders, provider-endpoint helpers, and model-id normalization helpers such as `normalizeNativeXaiModelId` |
| `plugin-sdk/provider-catalog-shared` | `findCatalogTemplate`, `buildSingleProviderApiKeyCatalog`, `supportsNativeStreamingUsageCompat`, `applyProviderNativeStreamingUsageCompat` |
| `plugin-sdk/provider-http` | Generic provider HTTP/endpoint capability helpers |
| `plugin-sdk/provider-web-fetch-contract` | Narrow web-fetch config/selection contract helpers such as `enablePluginInConfig` and `WebFetchProviderPlugin` |
| `plugin-sdk/provider-web-fetch` | Web-fetch provider registration/cache helpers |
| `plugin-sdk/provider-web-search-contract` | Narrow web-search config/credential contract helpers such as `enablePluginInConfig`, `resolveProviderWebSearchPluginConfig`, and scoped credential setters/getters |
| `plugin-sdk/provider-web-search` | Web-search provider registration/cache/runtime helpers |

View File

@@ -0,0 +1,65 @@
import {
enablePluginInConfig,
type WebFetchProviderPlugin,
} from "openclaw/plugin-sdk/provider-web-fetch-contract";
function ensureRecord(target: Record<string, unknown>, key: string): Record<string, unknown> {
const current = target[key];
if (current && typeof current === "object" && !Array.isArray(current)) {
return current as Record<string, unknown>;
}
const next: Record<string, unknown> = {};
target[key] = next;
return next;
}
export function createFirecrawlWebFetchProvider(): WebFetchProviderPlugin {
return {
id: "firecrawl",
label: "Firecrawl",
hint: "Fetch pages with Firecrawl for JS-heavy or bot-protected sites.",
envVars: ["FIRECRAWL_API_KEY"],
placeholder: "fc-...",
signupUrl: "https://www.firecrawl.dev/",
docsUrl: "https://docs.firecrawl.dev",
autoDetectOrder: 50,
credentialPath: "plugins.entries.firecrawl.config.webFetch.apiKey",
inactiveSecretPaths: [
"plugins.entries.firecrawl.config.webFetch.apiKey",
"tools.web.fetch.firecrawl.apiKey",
],
getCredentialValue: (fetchConfig) => {
if (!fetchConfig || typeof fetchConfig !== "object") {
return undefined;
}
const legacy = fetchConfig.firecrawl;
if (!legacy || typeof legacy !== "object" || Array.isArray(legacy)) {
return undefined;
}
if ((legacy as { enabled?: boolean }).enabled === false) {
return undefined;
}
return (legacy as { apiKey?: unknown }).apiKey;
},
setCredentialValue: (fetchConfigTarget, value) => {
const firecrawl = ensureRecord(fetchConfigTarget, "firecrawl");
firecrawl.apiKey = value;
},
getConfiguredCredentialValue: (config) =>
(
config?.plugins?.entries?.firecrawl?.config as
| { webFetch?: { apiKey?: unknown } }
| undefined
)?.webFetch?.apiKey,
setConfiguredCredentialValue: (configTarget, value) => {
const plugins = ensureRecord(configTarget as Record<string, unknown>, "plugins");
const entries = ensureRecord(plugins, "entries");
const firecrawlEntry = ensureRecord(entries, "firecrawl");
const pluginConfig = ensureRecord(firecrawlEntry, "config");
const webFetch = ensureRecord(pluginConfig, "webFetch");
webFetch.apiKey = value;
},
applySelectionConfig: (config) => enablePluginInConfig(config, "firecrawl").config,
createTool: () => null,
};
}

View File

@@ -896,6 +896,10 @@
"types": "./dist/plugin-sdk/provider-usage.d.ts",
"default": "./dist/plugin-sdk/provider-usage.js"
},
"./plugin-sdk/provider-web-fetch-contract": {
"types": "./dist/plugin-sdk/provider-web-fetch-contract.d.ts",
"default": "./dist/plugin-sdk/provider-web-fetch-contract.js"
},
"./plugin-sdk/provider-web-fetch": {
"types": "./dist/plugin-sdk/provider-web-fetch.d.ts",
"default": "./dist/plugin-sdk/provider-web-fetch.js"

View File

@@ -213,6 +213,7 @@
"provider-stream",
"provider-tools",
"provider-usage",
"provider-web-fetch-contract",
"provider-web-fetch",
"provider-web-search-contract",
"provider-web-search",

View File

@@ -0,0 +1,6 @@
// Narrow shared exports for web-fetch contract surfaces.
import type { WebFetchProviderPlugin } from "../plugins/types.js";
export { enablePluginInConfig } from "../plugins/enable.js";
export type { WebFetchProviderPlugin };

View File

@@ -351,6 +351,15 @@ describe("plugin-sdk subpath exports", () => {
"resolveCitationRedirectUrl",
],
});
expectSourceContract("provider-web-fetch-contract", {
mentions: ["enablePluginInConfig", "WebFetchProviderPlugin"],
omits: [
"withTrustedWebToolsEndpoint",
"readResponseText",
"resolveCacheTtlMs",
"wrapExternalContent",
],
});
expectSourceMentions("compat", [
"createPluginRuntimeStore",
"createScopedChannelConfigAdapter",

View File

@@ -41,4 +41,14 @@ describe("web provider public artifacts", () => {
}),
);
});
it("prefers lightweight bundled web fetch contract artifacts", () => {
const provider = resolveBundledWebFetchProvidersFromPublicArtifacts({
bundledAllowlistCompat: true,
onlyPluginIds: ["firecrawl"],
})?.[0];
expect(provider?.pluginId).toBe("firecrawl");
expect(provider?.createTool({ config: {} as never })).toBeNull();
});
});

View File

@@ -18,7 +18,11 @@ const WEB_SEARCH_ARTIFACT_CANDIDATES = [
"web-search-provider.js",
"web-search.js",
] as const;
const WEB_FETCH_ARTIFACT_CANDIDATES = ["web-fetch-provider.js", "web-fetch.js"] as const;
const WEB_FETCH_ARTIFACT_CANDIDATES = [
"web-fetch-contract-api.js",
"web-fetch-provider.js",
"web-fetch.js",
] as const;
type BundledWebProviderPublicArtifactParams = {
config?: PluginLoadOptions["config"];