mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
refactor(web): use canonical provider contracts (#130334)
This commit is contained in:
committed by
GitHub
parent
1fd3c071d8
commit
57aa2232fa
@@ -1,14 +1 @@
|
||||
/**
|
||||
* Brave Search contract provider. It exposes provider metadata without creating
|
||||
* the runtime search tool.
|
||||
*/
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import { buildBraveWebSearchProviderBase } from "./web-search-shared.js";
|
||||
|
||||
/** Create the Brave provider descriptor for contract checks. */
|
||||
export function createBraveWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...buildBraveWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createBraveWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
// Duckduckgo API module exposes the plugin public contract.
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import { createDuckDuckGoWebSearchProviderBase } from "./src/ddg-search-provider.shared.js";
|
||||
|
||||
export function createDuckDuckGoWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...createDuckDuckGoWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createDuckDuckGoWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -138,7 +138,7 @@ describe("exa web search provider", () => {
|
||||
expect(pluginEntry.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the lightweight contract surface aligned with provider metadata", () => {
|
||||
it("keeps the contract export aligned with provider metadata", () => {
|
||||
const provider = createExaWebSearchProvider();
|
||||
const contractProvider = createContractExaWebSearchProvider();
|
||||
if (!contractProvider.applySelectionConfig) {
|
||||
@@ -171,7 +171,13 @@ describe("exa web search provider", () => {
|
||||
autoDetectOrder: provider.autoDetectOrder,
|
||||
credentialPath: provider.credentialPath,
|
||||
});
|
||||
expect(contractProvider.createTool({ config: {}, searchConfig: {} })).toBeNull();
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch");
|
||||
try {
|
||||
expect(contractProvider.createTool({ config: {}, searchConfig: {} })).not.toBeNull();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
fetchMock.mockRestore();
|
||||
}
|
||||
const pluginEntry = applied.plugins?.entries?.exa;
|
||||
if (!pluginEntry) {
|
||||
throw new Error("expected contract Exa plugin entry");
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
// Exa API module exposes the plugin public contract.
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import { createExaWebSearchProviderBase } from "./src/exa-web-search-provider.shared.js";
|
||||
|
||||
export function createExaWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...createExaWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createExaWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
// Firecrawl API module exposes the plugin public contract.
|
||||
import {
|
||||
enablePluginInConfig,
|
||||
type WebFetchProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/provider-web-fetch-contract";
|
||||
import { FIRECRAWL_WEB_FETCH_PROVIDER_SHARED } from "./src/firecrawl-fetch-provider-shared.js";
|
||||
|
||||
export function createFirecrawlWebFetchProvider(): WebFetchProviderPlugin {
|
||||
return {
|
||||
...FIRECRAWL_WEB_FETCH_PROVIDER_SHARED,
|
||||
applySelectionConfig: (config) => enablePluginInConfig(config, "firecrawl").config,
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createFirecrawlWebFetchProvider } from "./web-fetch-provider.js";
|
||||
|
||||
@@ -1,20 +1,2 @@
|
||||
// Firecrawl API module exposes the plugin public contract.
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import {
|
||||
buildFirecrawlFreeWebSearchProviderBase,
|
||||
buildFirecrawlWebSearchProviderBase,
|
||||
} from "./web-search-shared.js";
|
||||
|
||||
export function createFirecrawlWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...buildFirecrawlWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createFirecrawlFreeWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...buildFirecrawlFreeWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createFirecrawlFreeWebSearchProvider } from "./web-search-provider.js";
|
||||
export { createFirecrawlWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -1,36 +1 @@
|
||||
// Minimax API module exposes the plugin public contract.
|
||||
import {
|
||||
createWebSearchProviderContractFields,
|
||||
type WebSearchProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
|
||||
const MINIMAX_TOKEN_PLAN_ENV_VARS = [
|
||||
"MINIMAX_CODE_PLAN_KEY",
|
||||
"MINIMAX_CODING_API_KEY",
|
||||
"MINIMAX_OAUTH_TOKEN",
|
||||
] as const;
|
||||
const MINIMAX_WEB_SEARCH_ENV_VARS = [...MINIMAX_TOKEN_PLAN_ENV_VARS, "MINIMAX_API_KEY"] as const;
|
||||
|
||||
export function createMiniMaxWebSearchProvider(): WebSearchProviderPlugin {
|
||||
const credentialPath = "plugins.entries.minimax.config.webSearch.apiKey";
|
||||
|
||||
return {
|
||||
id: "minimax",
|
||||
label: "MiniMax Search",
|
||||
hint: "Structured results via MiniMax Token Plan search API",
|
||||
onboardingScopes: ["text-inference"],
|
||||
credentialLabel: "MiniMax Token Plan key or OAuth token",
|
||||
envVars: [...MINIMAX_WEB_SEARCH_ENV_VARS],
|
||||
placeholder: "sk-cp-...",
|
||||
signupUrl: "https://platform.minimax.io/user-center/basic-information/interface-key",
|
||||
docsUrl: "https://docs.openclaw.ai/tools/minimax-search",
|
||||
autoDetectOrder: 15,
|
||||
credentialPath,
|
||||
...createWebSearchProviderContractFields({
|
||||
credentialPath,
|
||||
searchCredential: { type: "top-level" },
|
||||
configuredCredential: { pluginId: "minimax" },
|
||||
}),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createMiniMaxWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -1,29 +1 @@
|
||||
// Moonshot API module exposes the plugin public contract.
|
||||
import {
|
||||
createWebSearchProviderContractFields,
|
||||
type WebSearchProviderPlugin,
|
||||
} from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
|
||||
export function createKimiWebSearchProvider(): WebSearchProviderPlugin {
|
||||
const credentialPath = "plugins.entries.moonshot.config.webSearch.apiKey";
|
||||
|
||||
return {
|
||||
id: "kimi",
|
||||
label: "Kimi (Moonshot)",
|
||||
hint: "Requires Moonshot / Kimi API key · Moonshot web search",
|
||||
onboardingScopes: ["text-inference"],
|
||||
credentialLabel: "Moonshot / Kimi API key",
|
||||
envVars: ["KIMI_API_KEY", "MOONSHOT_API_KEY"],
|
||||
placeholder: "sk-...",
|
||||
signupUrl: "https://platform.moonshot.cn/",
|
||||
docsUrl: "https://docs.openclaw.ai/tools/web",
|
||||
autoDetectOrder: 40,
|
||||
credentialPath,
|
||||
...createWebSearchProviderContractFields({
|
||||
credentialPath,
|
||||
searchCredential: { type: "scoped", scopeId: "kimi" },
|
||||
configuredCredential: { pluginId: "moonshot" },
|
||||
}),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createKimiWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -143,7 +143,7 @@ describe("parallel web search provider", () => {
|
||||
const countParam = (paidTool({}).parameters as ToolParameters).properties.count;
|
||||
expect(countParam).toMatchObject({ type: "integer", minimum: 1, maximum: 40 });
|
||||
});
|
||||
it("keeps the lightweight contract surface aligned with provider metadata", () => {
|
||||
it("keeps the contract export aligned with provider metadata", () => {
|
||||
const provider = createParallelWebSearchProvider();
|
||||
const contractProvider = createContractParallelWebSearchProvider();
|
||||
const applied = expectDefined(
|
||||
@@ -166,7 +166,8 @@ describe("parallel web search provider", () => {
|
||||
expect(Object.fromEntries(keys.map((key) => [key, contractProvider[key]]))).toEqual(
|
||||
Object.fromEntries(keys.map((key) => [key, provider[key]])),
|
||||
);
|
||||
expect(contractProvider.createTool({ config: {}, searchConfig: {} })).toBeNull();
|
||||
expect(contractProvider.createTool({ config: {}, searchConfig: {} })).not.toBeNull();
|
||||
expect(endpointMockState.calls).toHaveLength(0);
|
||||
expect(expectDefined(applied.plugins?.entries?.parallel, "contract plugin entry").enabled).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -1,17 +1,2 @@
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-contract";
|
||||
import { createParallelFreeWebSearchProviderBase } from "./src/parallel-free-web-search-provider.shared.js";
|
||||
import { createParallelWebSearchProviderBase } from "./src/parallel-web-search-provider.shared.js";
|
||||
|
||||
export function createParallelWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...createParallelWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createParallelFreeWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...createParallelFreeWebSearchProviderBase(),
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createParallelFreeWebSearchProvider } from "./web-search-provider.js";
|
||||
export { createParallelWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
// Perplexity API module exposes the plugin public contract.
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import {
|
||||
createPerplexityWebSearchProviderBase,
|
||||
resolvePerplexityWebSearchRuntimeMetadata,
|
||||
} from "./src/perplexity-web-search-provider.shared.js";
|
||||
|
||||
export function createPerplexityWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...createPerplexityWebSearchProviderBase(),
|
||||
resolveRuntimeMetadata: resolvePerplexityWebSearchRuntimeMetadata,
|
||||
createTool: () => null,
|
||||
};
|
||||
}
|
||||
export { createPerplexityWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -1,32 +1 @@
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
// Tavily API module exposes the plugin public contract.
|
||||
import type { WebSearchProviderPlugin } from "openclaw/plugin-sdk/provider-web-search-config-contract";
|
||||
import {
|
||||
buildTavilyWebSearchProviderBase,
|
||||
TAVILY_GENERIC_SEARCH_DESCRIPTION,
|
||||
TAVILY_GENERIC_SEARCH_SCHEMA,
|
||||
} from "./web-search-shared.js";
|
||||
|
||||
const loadTavilySearchProviderModule = createLazyRuntimeModule(
|
||||
() => import("./src/tavily-search-provider.js"),
|
||||
);
|
||||
|
||||
export function createTavilyWebSearchProvider(): WebSearchProviderPlugin {
|
||||
return {
|
||||
...buildTavilyWebSearchProviderBase(),
|
||||
createTool: (ctx) => ({
|
||||
description: TAVILY_GENERIC_SEARCH_DESCRIPTION,
|
||||
parameters: TAVILY_GENERIC_SEARCH_SCHEMA,
|
||||
execute: async (args, executionContext) => {
|
||||
executionContext?.signal?.throwIfAborted();
|
||||
const { createTavilyWebSearchProvider: createRuntimeProvider } =
|
||||
await loadTavilySearchProviderModule();
|
||||
const tool = createRuntimeProvider().createTool(ctx);
|
||||
if (!tool) {
|
||||
throw new Error("Tavily web_search provider did not create a runtime tool.");
|
||||
}
|
||||
return await tool.execute(args, executionContext);
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
export { createTavilyWebSearchProvider } from "./web-search-provider.js";
|
||||
|
||||
@@ -376,6 +376,46 @@ describe("resolvePluginWebSearchProviders", () => {
|
||||
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("preserves Moonshot region and model setup without activating the plugin", async () => {
|
||||
loadInstalledPluginManifestRegistryMock.mockReturnValueOnce({
|
||||
plugins: [createWebSearchManifestRecord({ id: "moonshot", providerId: "kimi" })],
|
||||
diagnostics: [],
|
||||
});
|
||||
const config = { plugins: { allow: ["moonshot"] } };
|
||||
const providers = resolvePluginWebSearchProviders({ config, mode: "setup", activate: false });
|
||||
const provider = providers[0];
|
||||
if (!provider?.runSetup) {
|
||||
throw new Error("Expected Moonshot web-search setup from the public artifact");
|
||||
}
|
||||
const select = vi.fn(async (params: { initialValue?: unknown }) => params.initialValue);
|
||||
|
||||
const next = await provider.runSetup({
|
||||
config,
|
||||
runtime: {} as never,
|
||||
prompter: { select } as never,
|
||||
});
|
||||
|
||||
expect(loadOpenClawPluginsMock).not.toHaveBeenCalled();
|
||||
expect(select.mock.calls.map(([params]) => requireRecord(params).message)).toEqual([
|
||||
"Kimi API region",
|
||||
"Kimi web search model",
|
||||
]);
|
||||
expect(next).toMatchObject({
|
||||
plugins: {
|
||||
entries: {
|
||||
moonshot: {
|
||||
config: {
|
||||
webSearch: {
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
model: "kimi-k2.6",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("loads plugin web-search providers from the auto-enabled config snapshot", () => {
|
||||
const rawConfig = createBraveAllowConfig();
|
||||
const autoEnabledConfig = {
|
||||
|
||||
Reference in New Issue
Block a user