mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-21 22:21:33 +00:00
feat: migrate core search providers to bundled plugins
This commit is contained in:
12
extensions/search-brave/index.ts
Normal file
12
extensions/search-brave/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createBundledBuiltinSearchProvider, type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
|
||||
const plugin = {
|
||||
id: "search-brave",
|
||||
name: "Brave Search",
|
||||
description: "Bundled Brave web search provider for OpenClaw.",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerSearchProvider(createBundledBuiltinSearchProvider("brave"));
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
4
extensions/search-brave/openclaw.plugin.json
Normal file
4
extensions/search-brave/openclaw.plugin.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": "search-brave",
|
||||
"provides": ["providers.search.brave"]
|
||||
}
|
||||
12
extensions/search-brave/package.json
Normal file
12
extensions/search-brave/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/search-brave",
|
||||
"version": "2026.3.12",
|
||||
"private": true,
|
||||
"description": "OpenClaw bundled Brave search provider plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
extensions/search-gemini/index.ts
Normal file
12
extensions/search-gemini/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createBundledBuiltinSearchProvider, type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
|
||||
const plugin = {
|
||||
id: "search-gemini",
|
||||
name: "Gemini Search",
|
||||
description: "Bundled Gemini web search provider for OpenClaw.",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerSearchProvider(createBundledBuiltinSearchProvider("gemini"));
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
4
extensions/search-gemini/openclaw.plugin.json
Normal file
4
extensions/search-gemini/openclaw.plugin.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": "search-gemini",
|
||||
"provides": ["providers.search.gemini"]
|
||||
}
|
||||
12
extensions/search-gemini/package.json
Normal file
12
extensions/search-gemini/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/search-gemini",
|
||||
"version": "2026.3.12",
|
||||
"private": true,
|
||||
"description": "OpenClaw bundled Gemini search provider plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
extensions/search-grok/index.ts
Normal file
12
extensions/search-grok/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createBundledBuiltinSearchProvider, type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
|
||||
const plugin = {
|
||||
id: "search-grok",
|
||||
name: "Grok Search",
|
||||
description: "Bundled xAI Grok web search provider for OpenClaw.",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerSearchProvider(createBundledBuiltinSearchProvider("grok"));
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
4
extensions/search-grok/openclaw.plugin.json
Normal file
4
extensions/search-grok/openclaw.plugin.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": "search-grok",
|
||||
"provides": ["providers.search.grok"]
|
||||
}
|
||||
12
extensions/search-grok/package.json
Normal file
12
extensions/search-grok/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/search-grok",
|
||||
"version": "2026.3.12",
|
||||
"private": true,
|
||||
"description": "OpenClaw bundled Grok search provider plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
extensions/search-kimi/index.ts
Normal file
12
extensions/search-kimi/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createBundledBuiltinSearchProvider, type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
|
||||
const plugin = {
|
||||
id: "search-kimi",
|
||||
name: "Kimi Search",
|
||||
description: "Bundled Kimi web search provider for OpenClaw.",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerSearchProvider(createBundledBuiltinSearchProvider("kimi"));
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
4
extensions/search-kimi/openclaw.plugin.json
Normal file
4
extensions/search-kimi/openclaw.plugin.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": "search-kimi",
|
||||
"provides": ["providers.search.kimi"]
|
||||
}
|
||||
12
extensions/search-kimi/package.json
Normal file
12
extensions/search-kimi/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/search-kimi",
|
||||
"version": "2026.3.12",
|
||||
"private": true,
|
||||
"description": "OpenClaw bundled Kimi search provider plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
extensions/search-perplexity/index.ts
Normal file
12
extensions/search-perplexity/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createBundledBuiltinSearchProvider, type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||
|
||||
const plugin = {
|
||||
id: "search-perplexity",
|
||||
name: "Perplexity Search",
|
||||
description: "Bundled Perplexity web search provider for OpenClaw.",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerSearchProvider(createBundledBuiltinSearchProvider("perplexity"));
|
||||
},
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
4
extensions/search-perplexity/openclaw.plugin.json
Normal file
4
extensions/search-perplexity/openclaw.plugin.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": "search-perplexity",
|
||||
"provides": ["providers.search.perplexity"]
|
||||
}
|
||||
12
extensions/search-perplexity/package.json
Normal file
12
extensions/search-perplexity/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@openclaw/search-perplexity",
|
||||
"version": "2026.3.12",
|
||||
"private": true,
|
||||
"description": "OpenClaw bundled Perplexity search provider plugin",
|
||||
"type": "module",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,18 @@ export const BUILTIN_WEB_SEARCH_PROVIDER_IDS = [
|
||||
|
||||
export type BuiltinWebSearchProviderId = (typeof BUILTIN_WEB_SEARCH_PROVIDER_IDS)[number];
|
||||
|
||||
export const MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_IDS = BUILTIN_WEB_SEARCH_PROVIDER_IDS;
|
||||
|
||||
export type MigratedBundledWebSearchProviderId =
|
||||
(typeof MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_IDS)[number];
|
||||
|
||||
export const bundledCoreWebSearchPluginId = (providerId: BuiltinWebSearchProviderId): string =>
|
||||
`search-${providerId}`;
|
||||
|
||||
export const MIGRATED_BUNDLED_WEB_SEARCH_PLUGIN_IDS = MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_IDS.map(
|
||||
bundledCoreWebSearchPluginId,
|
||||
);
|
||||
|
||||
export type BuiltinWebSearchProviderEntry = {
|
||||
value: BuiltinWebSearchProviderId;
|
||||
label: string;
|
||||
|
||||
@@ -2461,10 +2461,56 @@ function getBuiltinSearchProviders(search?: WebSearchConfig): SearchProviderPlug
|
||||
];
|
||||
}
|
||||
|
||||
export function createBundledBuiltinSearchProvider(
|
||||
providerId: BuiltinWebSearchProviderId,
|
||||
): SearchProviderPlugin {
|
||||
const providers = getBuiltinSearchProviders();
|
||||
switch (providerId) {
|
||||
case "brave":
|
||||
return {
|
||||
...providers[0],
|
||||
builtinProviderId: "brave",
|
||||
};
|
||||
case "gemini":
|
||||
return {
|
||||
...providers[1],
|
||||
builtinProviderId: "gemini",
|
||||
};
|
||||
case "grok":
|
||||
return {
|
||||
...providers[2],
|
||||
builtinProviderId: "grok",
|
||||
};
|
||||
case "kimi":
|
||||
return {
|
||||
...providers[3],
|
||||
builtinProviderId: "kimi",
|
||||
};
|
||||
case "perplexity":
|
||||
return {
|
||||
...providers[4],
|
||||
builtinProviderId: "perplexity",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getPluginSearchProviders(): SearchProviderPlugin[] {
|
||||
return getActivePluginRegistry()?.searchProviders.map((entry) => entry.provider) ?? [];
|
||||
}
|
||||
|
||||
function resolveBuiltinSchemaProviderId(
|
||||
provider: SearchProviderPlugin,
|
||||
): BuiltinWebSearchProviderId | undefined {
|
||||
if (provider.builtinProviderId && isBuiltinSearchProviderId(provider.builtinProviderId)) {
|
||||
return provider.builtinProviderId;
|
||||
}
|
||||
if (!provider.pluginId) {
|
||||
const candidate = normalizeSearchProviderId(provider.id);
|
||||
return isBuiltinSearchProviderId(candidate) ? candidate : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveConfiguredSearchProviderId(params: {
|
||||
config?: OpenClawConfig;
|
||||
search?: WebSearchConfig;
|
||||
@@ -2582,8 +2628,8 @@ function createSearchProviderSchema(params: {
|
||||
search?: WebSearchConfig;
|
||||
runtimeWebSearch?: RuntimeWebSearchMetadata;
|
||||
}) {
|
||||
const providerId = normalizeSearchProviderId(params.provider.id);
|
||||
if (!params.provider.pluginId && isBuiltinSearchProviderId(providerId)) {
|
||||
const providerId = resolveBuiltinSchemaProviderId(params.provider);
|
||||
if (providerId) {
|
||||
const perplexityTransport =
|
||||
params.runtimeWebSearch?.selectedProvider === "perplexity"
|
||||
? params.runtimeWebSearch.perplexityTransport
|
||||
@@ -2719,36 +2765,35 @@ export function createWebSearchTool(options?: {
|
||||
});
|
||||
}
|
||||
|
||||
const providerId = normalizeSearchProviderId(provider.id);
|
||||
const builtinProviderId = resolveBuiltinSchemaProviderId(provider);
|
||||
logVerbose(formatWebSearchExecutionLog(provider));
|
||||
const result =
|
||||
!provider.pluginId && isBuiltinSearchProviderId(providerId)
|
||||
? await executeBuiltinSearchProvider({
|
||||
provider: providerId,
|
||||
request,
|
||||
context: {
|
||||
config: options?.config ?? {},
|
||||
timeoutSeconds: resolveTimeoutSeconds(
|
||||
search?.timeoutSeconds,
|
||||
DEFAULT_TIMEOUT_SECONDS,
|
||||
),
|
||||
cacheTtlMs: resolveCacheTtlMs(search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
|
||||
pluginConfig: resolveSearchProviderPluginConfig(options?.config, provider),
|
||||
},
|
||||
})
|
||||
: await executePluginSearchProvider({
|
||||
provider,
|
||||
request,
|
||||
context: {
|
||||
config: options?.config ?? {},
|
||||
timeoutSeconds: resolveTimeoutSeconds(
|
||||
search?.timeoutSeconds,
|
||||
DEFAULT_TIMEOUT_SECONDS,
|
||||
),
|
||||
cacheTtlMs: resolveCacheTtlMs(search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
|
||||
pluginConfig: resolveSearchProviderPluginConfig(options?.config, provider),
|
||||
},
|
||||
});
|
||||
const result = builtinProviderId
|
||||
? await executeBuiltinSearchProvider({
|
||||
provider: builtinProviderId,
|
||||
request,
|
||||
context: {
|
||||
config: options?.config ?? {},
|
||||
timeoutSeconds: resolveTimeoutSeconds(
|
||||
search?.timeoutSeconds,
|
||||
DEFAULT_TIMEOUT_SECONDS,
|
||||
),
|
||||
cacheTtlMs: resolveCacheTtlMs(search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
|
||||
pluginConfig: resolveSearchProviderPluginConfig(options?.config, provider),
|
||||
},
|
||||
})
|
||||
: await executePluginSearchProvider({
|
||||
provider,
|
||||
request,
|
||||
context: {
|
||||
config: options?.config ?? {},
|
||||
timeoutSeconds: resolveTimeoutSeconds(
|
||||
search?.timeoutSeconds,
|
||||
DEFAULT_TIMEOUT_SECONDS,
|
||||
),
|
||||
cacheTtlMs: resolveCacheTtlMs(search?.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES),
|
||||
pluginConfig: resolveSearchProviderPluginConfig(options?.config, provider),
|
||||
},
|
||||
});
|
||||
return jsonResult(result);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -210,6 +210,75 @@ describe("web tools defaults", () => {
|
||||
});
|
||||
|
||||
describe("web_search plugin providers", () => {
|
||||
it.each(["brave", "perplexity", "grok", "gemini", "kimi"] as const)(
|
||||
"resolves configured built-in provider %s through bundled plugin registrations when available",
|
||||
async (providerId) => {
|
||||
const registry = createEmptyPluginRegistry();
|
||||
registry.searchProviders.push({
|
||||
pluginId: `search-${providerId}`,
|
||||
source: `/plugins/search-${providerId}`,
|
||||
provider: {
|
||||
id: providerId,
|
||||
name: `${providerId} bundled provider`,
|
||||
pluginId: `search-${providerId}`,
|
||||
builtinProviderId: providerId,
|
||||
isAvailable: () => true,
|
||||
search: async () => ({ content: "unused" }),
|
||||
},
|
||||
});
|
||||
setActivePluginRegistry(registry);
|
||||
|
||||
const mockFetch = installMockFetch(createProviderSuccessPayload(providerId));
|
||||
const provider = webSearchTesting.resolveRegisteredSearchProvider({
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
search:
|
||||
providerId === "perplexity"
|
||||
? { provider: providerId, perplexity: { apiKey: "pplx-config-test" } }
|
||||
: providerId === "grok"
|
||||
? { provider: providerId, grok: { apiKey: "xai-config-test" } }
|
||||
: providerId === "gemini"
|
||||
? { provider: providerId, gemini: { apiKey: "gemini-config-test" } }
|
||||
: providerId === "kimi"
|
||||
? { provider: providerId, kimi: { apiKey: "moonshot-config-test" } }
|
||||
: { provider: providerId, apiKey: "brave-config-test" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(provider.pluginId).toBe(`search-${providerId}`);
|
||||
|
||||
const tool = createWebSearchTool({
|
||||
config: {
|
||||
tools: {
|
||||
web: {
|
||||
search:
|
||||
providerId === "perplexity"
|
||||
? { provider: providerId, perplexity: { apiKey: "pplx-config-test" } }
|
||||
: providerId === "grok"
|
||||
? { provider: providerId, grok: { apiKey: "xai-config-test" } }
|
||||
: providerId === "gemini"
|
||||
? { provider: providerId, gemini: { apiKey: "gemini-config-test" } }
|
||||
: providerId === "kimi"
|
||||
? { provider: providerId, kimi: { apiKey: "moonshot-config-test" } }
|
||||
: { provider: providerId, apiKey: "brave-config-test" },
|
||||
},
|
||||
},
|
||||
},
|
||||
sandboxed: true,
|
||||
});
|
||||
|
||||
const result = await tool?.execute?.(`call-bundled-${providerId}`, {
|
||||
query: `bundled ${providerId}`,
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalled();
|
||||
expect((result?.details as { provider?: string } | undefined)?.provider).toBe(providerId);
|
||||
},
|
||||
);
|
||||
|
||||
it("prefers an explicitly configured plugin provider over a built-in provider with the same id", async () => {
|
||||
const searchMock = vi.fn(async () => ({
|
||||
results: [
|
||||
|
||||
@@ -335,7 +335,7 @@ describe("runConfigureWizard", () => {
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(mocks.writeConfigFile.mock.calls[0]?.[0]?.tools?.web?.search?.provider).toBeUndefined();
|
||||
expect(mocks.writeConfigFile.mock.calls[0]?.[0]?.tools?.web?.search?.provider).toBe("brave");
|
||||
});
|
||||
|
||||
it("re-prompts invalid plugin config values during configure", async () => {
|
||||
|
||||
@@ -242,6 +242,64 @@ describe("setupSearch", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["brave", "Brave Search"],
|
||||
["gemini", "Gemini (Google Search)"],
|
||||
["grok", "Grok (xAI)"],
|
||||
["kimi", "Kimi (Moonshot)"],
|
||||
["perplexity", "Perplexity Search"],
|
||||
] as const)(
|
||||
"does not duplicate built-in provider %s when a bundled search plugin registers the same provider id",
|
||||
async (providerId, providerLabel) => {
|
||||
loadOpenClawPlugins.mockReturnValue({
|
||||
searchProviders: [
|
||||
{
|
||||
pluginId: `search-${providerId}`,
|
||||
provider: {
|
||||
id: providerId,
|
||||
name: providerLabel,
|
||||
description: `Bundled ${providerLabel} provider`,
|
||||
pluginId: `search-${providerId}`,
|
||||
builtinProviderId: providerId,
|
||||
isAvailable: () => true,
|
||||
search: async () => ({ content: "ok" }),
|
||||
},
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
{
|
||||
id: `search-${providerId}`,
|
||||
name: providerLabel,
|
||||
description: `Bundled ${providerLabel} provider`,
|
||||
origin: "bundled",
|
||||
source: `/tmp/bundled/search-${providerId}`,
|
||||
configJsonSchema: undefined,
|
||||
configUiHints: undefined,
|
||||
},
|
||||
],
|
||||
typedHooks: [],
|
||||
});
|
||||
loadPluginManifestRegistry.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
|
||||
const cfg: OpenClawConfig = {};
|
||||
const { prompter } = createPrompter({ selectValue: "__skip__" });
|
||||
await setupSearch(cfg, runtime, prompter);
|
||||
|
||||
const providerSelectCall = (prompter.select as ReturnType<typeof vi.fn>).mock.calls.find(
|
||||
(call) => call[0]?.message === "Choose active web search provider",
|
||||
);
|
||||
const matchingOptions =
|
||||
providerSelectCall?.[0]?.options?.filter(
|
||||
(option: { value?: string }) => option.value === providerId,
|
||||
) ?? [];
|
||||
expect(matchingOptions).toHaveLength(1);
|
||||
expect(matchingOptions[0]?.hint).toContain("Bundled plugin");
|
||||
},
|
||||
);
|
||||
|
||||
it("uses the updated configure-or-install action label", async () => {
|
||||
vi.stubEnv("BRAVE_API_KEY", "BSA-test-key");
|
||||
loadOpenClawPlugins.mockReturnValue({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BUILTIN_WEB_SEARCH_PROVIDER_OPTIONS,
|
||||
MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_IDS,
|
||||
type BuiltinWebSearchProviderEntry,
|
||||
type BuiltinWebSearchProviderId,
|
||||
isBuiltinWebSearchProviderId,
|
||||
@@ -49,6 +50,10 @@ const SEARCH_PROVIDER_SKIP_SENTINEL = "__skip__" as const;
|
||||
const SEARCH_PROVIDER_SWITCH_ACTIVE_SENTINEL = "__switch_active__" as const;
|
||||
const SEARCH_PROVIDER_CONFIGURE_SENTINEL = "__configure_provider__" as const;
|
||||
|
||||
const MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_ID_SET = new Set<string>(
|
||||
MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_IDS,
|
||||
);
|
||||
|
||||
type PluginSearchProviderEntry = {
|
||||
kind: "plugin";
|
||||
value: string;
|
||||
@@ -517,7 +522,9 @@ export async function resolveSearchProviderPickerEntries(
|
||||
config: OpenClawConfig,
|
||||
workspaceDir?: string,
|
||||
): Promise<SearchProviderPickerEntry[]> {
|
||||
const builtins: SearchProviderPickerEntry[] = SEARCH_PROVIDER_OPTIONS.map((entry) => ({
|
||||
const builtins: SearchProviderPickerEntry[] = SEARCH_PROVIDER_OPTIONS.filter(
|
||||
(entry) => !MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_ID_SET.has(entry.value),
|
||||
).map((entry) => ({
|
||||
...entry,
|
||||
kind: "builtin",
|
||||
configured: hasExistingKey(config, entry.value) || hasKeyInEnv(entry),
|
||||
@@ -568,6 +575,15 @@ export async function resolveSearchProviderPickerEntries(
|
||||
configUiHints: pluginRecord.configUiHints,
|
||||
};
|
||||
})
|
||||
.filter((entry) => {
|
||||
if (!entry) {
|
||||
return false;
|
||||
}
|
||||
return !(
|
||||
entry.origin === "bundled" &&
|
||||
!MIGRATED_BUNDLED_WEB_SEARCH_PROVIDER_ID_SET.has(entry.value)
|
||||
);
|
||||
})
|
||||
.filter(Boolean) as PluginSearchProviderEntry[];
|
||||
pluginEntries = resolvedPluginEntries.toSorted((left, right) =>
|
||||
left.label.localeCompare(right.label),
|
||||
|
||||
@@ -139,6 +139,7 @@ describe("plugin-sdk exports", () => {
|
||||
"formatInboundFromLabel",
|
||||
"resolveRuntimeGroupPolicy",
|
||||
"emptyPluginConfigSchema",
|
||||
"createBundledBuiltinSearchProvider",
|
||||
"normalizePluginHttpPath",
|
||||
"registerPluginHttpRoute",
|
||||
"buildBaseAccountStatusSnapshot",
|
||||
|
||||
@@ -125,6 +125,7 @@ export type {
|
||||
export { normalizePluginHttpPath } from "../plugins/http-path.js";
|
||||
export { registerPluginHttpRoute } from "../plugins/http-registry.js";
|
||||
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
|
||||
export { createBundledBuiltinSearchProvider } from "../agents/tools/web-search.js";
|
||||
export type { OpenClawConfig } from "../config/config.js";
|
||||
/** @deprecated Use OpenClawConfig instead */
|
||||
export type { OpenClawConfig as ClawdbotConfig } from "../config/config.js";
|
||||
|
||||
@@ -114,6 +114,32 @@ describe("resolveEffectiveEnableState", () => {
|
||||
});
|
||||
expect(state).toEqual({ enabled: false, reason: "disabled in config" });
|
||||
});
|
||||
|
||||
it("enables bundled search provider plugins by default", () => {
|
||||
const normalized = normalizePluginsConfig({
|
||||
enabled: true,
|
||||
});
|
||||
const state = resolveEffectiveEnableState({
|
||||
id: "search-brave",
|
||||
origin: "bundled",
|
||||
config: normalized,
|
||||
rootConfig: {},
|
||||
});
|
||||
expect(state).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it("enables other migrated bundled search provider plugins by default", () => {
|
||||
const normalized = normalizePluginsConfig({
|
||||
enabled: true,
|
||||
});
|
||||
const state = resolveEffectiveEnableState({
|
||||
id: "search-gemini",
|
||||
origin: "bundled",
|
||||
config: normalized,
|
||||
rootConfig: {},
|
||||
});
|
||||
expect(state).toEqual({ enabled: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveEnableState", () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MIGRATED_BUNDLED_WEB_SEARCH_PLUGIN_IDS } from "../agents/tools/web-search-provider-catalog.js";
|
||||
import { normalizeChatChannelId } from "../channels/registry.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { PluginRecord } from "./registry.js";
|
||||
@@ -28,6 +29,7 @@ export const BUNDLED_ENABLED_BY_DEFAULT = new Set<string>([
|
||||
"ollama",
|
||||
"phone-control",
|
||||
"sglang",
|
||||
...MIGRATED_BUNDLED_WEB_SEARCH_PLUGIN_IDS,
|
||||
"talk-voice",
|
||||
"vllm",
|
||||
]);
|
||||
|
||||
@@ -148,21 +148,28 @@ function getHooksForNameWithoutPluginIds<K extends PluginHookName>(params: {
|
||||
);
|
||||
}
|
||||
|
||||
type SearchProviderAliasDescriptor<
|
||||
TGenericName extends
|
||||
| "before_provider_configure"
|
||||
| "after_provider_configure"
|
||||
| "after_provider_activate",
|
||||
TLegacyName extends
|
||||
| "before_search_provider_configure"
|
||||
| "after_search_provider_configure"
|
||||
| "after_search_provider_activate",
|
||||
TGenericEvent,
|
||||
TLegacyEvent,
|
||||
> = {
|
||||
genericHookName: TGenericName;
|
||||
legacyHookName: TLegacyName;
|
||||
toLegacyEvent: (event: TGenericEvent) => TLegacyEvent;
|
||||
type SearchBeforeProviderAliasDescriptor = {
|
||||
genericHookName: "before_provider_configure";
|
||||
legacyHookName: "before_search_provider_configure";
|
||||
toLegacyEvent: (
|
||||
event: PluginHookBeforeProviderConfigureEvent,
|
||||
) => PluginHookBeforeSearchProviderConfigureEvent;
|
||||
};
|
||||
|
||||
type SearchAfterProviderConfigureAliasDescriptor = {
|
||||
genericHookName: "after_provider_configure";
|
||||
legacyHookName: "after_search_provider_configure";
|
||||
toLegacyEvent: (
|
||||
event: PluginHookAfterProviderConfigureEvent,
|
||||
) => PluginHookAfterSearchProviderConfigureEvent;
|
||||
};
|
||||
|
||||
type SearchAfterProviderActivateAliasDescriptor = {
|
||||
genericHookName: "after_provider_activate";
|
||||
legacyHookName: "after_search_provider_activate";
|
||||
toLegacyEvent: (
|
||||
event: PluginHookAfterProviderActivateEvent,
|
||||
) => PluginHookAfterSearchProviderActivateEvent;
|
||||
};
|
||||
|
||||
const SEARCH_PROVIDER_ALIAS_HOOKS = {
|
||||
@@ -180,12 +187,7 @@ const SEARCH_PROVIDER_ALIAS_HOOKS = {
|
||||
activeProviderId: event.activeProviderId,
|
||||
configured: event.configured,
|
||||
}),
|
||||
} satisfies SearchProviderAliasDescriptor<
|
||||
"before_provider_configure",
|
||||
"before_search_provider_configure",
|
||||
PluginHookBeforeProviderConfigureEvent,
|
||||
PluginHookBeforeSearchProviderConfigureEvent
|
||||
>,
|
||||
} satisfies SearchBeforeProviderAliasDescriptor,
|
||||
afterConfigure: {
|
||||
genericHookName: "after_provider_configure",
|
||||
legacyHookName: "after_search_provider_configure",
|
||||
@@ -200,12 +202,7 @@ const SEARCH_PROVIDER_ALIAS_HOOKS = {
|
||||
activeProviderId: event.activeProviderId,
|
||||
configured: event.configured,
|
||||
}),
|
||||
} satisfies SearchProviderAliasDescriptor<
|
||||
"after_provider_configure",
|
||||
"after_search_provider_configure",
|
||||
PluginHookAfterProviderConfigureEvent,
|
||||
PluginHookAfterSearchProviderConfigureEvent
|
||||
>,
|
||||
} satisfies SearchAfterProviderConfigureAliasDescriptor,
|
||||
afterActivate: {
|
||||
genericHookName: "after_provider_activate",
|
||||
legacyHookName: "after_search_provider_activate",
|
||||
@@ -219,12 +216,7 @@ const SEARCH_PROVIDER_ALIAS_HOOKS = {
|
||||
previousProviderId: event.previousProviderId,
|
||||
intent: event.intent,
|
||||
}),
|
||||
} satisfies SearchProviderAliasDescriptor<
|
||||
"after_provider_activate",
|
||||
"after_search_provider_activate",
|
||||
PluginHookAfterProviderActivateEvent,
|
||||
PluginHookAfterSearchProviderActivateEvent
|
||||
>,
|
||||
} satisfies SearchAfterProviderActivateAliasDescriptor,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -409,16 +401,12 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
return result;
|
||||
}
|
||||
|
||||
function getSearchProviderLegacyHooks<
|
||||
TGenericName extends
|
||||
| "before_provider_configure"
|
||||
| "after_provider_configure"
|
||||
| "after_provider_activate",
|
||||
TLegacyName extends
|
||||
| "before_search_provider_configure"
|
||||
| "after_search_provider_configure"
|
||||
| "after_search_provider_activate",
|
||||
>(descriptor: SearchProviderAliasDescriptor<TGenericName, TLegacyName, unknown, unknown>) {
|
||||
function getSearchProviderLegacyHooks(
|
||||
descriptor:
|
||||
| SearchBeforeProviderAliasDescriptor
|
||||
| SearchAfterProviderConfigureAliasDescriptor
|
||||
| SearchAfterProviderActivateAliasDescriptor,
|
||||
) {
|
||||
const genericHooks = getHooksForName(registry, descriptor.genericHookName);
|
||||
const genericPluginIds = new Set(genericHooks.map((hook) => hook.pluginId));
|
||||
return getHooksForNameWithoutPluginIds({
|
||||
@@ -512,7 +500,7 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
PluginHookBeforeSearchProviderConfigureResult
|
||||
>(
|
||||
aliasDescriptor.legacyHookName,
|
||||
legacyHooks,
|
||||
legacyHooks as PluginHookRegistration<"before_search_provider_configure">[],
|
||||
aliasDescriptor.toLegacyEvent(event),
|
||||
ctx,
|
||||
mergeBeforeSearchProviderConfigure,
|
||||
@@ -538,7 +526,7 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
const legacyHooks = getSearchProviderLegacyHooks(aliasDescriptor);
|
||||
await runVoidHookRegistrations(
|
||||
aliasDescriptor.legacyHookName,
|
||||
legacyHooks,
|
||||
legacyHooks as PluginHookRegistration<"after_search_provider_configure">[],
|
||||
aliasDescriptor.toLegacyEvent(event),
|
||||
ctx,
|
||||
);
|
||||
@@ -557,7 +545,7 @@ export function createHookRunner(registry: PluginRegistry, options: HookRunnerOp
|
||||
const legacyHooks = getSearchProviderLegacyHooks(aliasDescriptor);
|
||||
await runVoidHookRegistrations(
|
||||
aliasDescriptor.legacyHookName,
|
||||
legacyHooks,
|
||||
legacyHooks as PluginHookRegistration<"after_search_provider_activate">[],
|
||||
aliasDescriptor.toLegacyEvent(event),
|
||||
ctx,
|
||||
);
|
||||
|
||||
@@ -299,6 +299,7 @@ export type SearchProviderPlugin = {
|
||||
name: string;
|
||||
description?: string;
|
||||
pluginId?: string;
|
||||
builtinProviderId?: string;
|
||||
docsUrl?: string;
|
||||
configFieldOrder?: string[];
|
||||
isAvailable?: (config?: OpenClawConfig) => boolean;
|
||||
|
||||
Reference in New Issue
Block a user