From 918df6c542754329d6c3d046664adb1c802a51f7 Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 12:56:23 +0800 Subject: [PATCH 1/9] tools: add badiu as web search provider --- extensions/baidu/index.ts | 11 + extensions/baidu/openclaw.plugin.json | 26 ++ extensions/baidu/package.json | 12 + .../baidu/src/baidu-web-search-provider.ts | 259 ++++++++++++++++++ pnpm-lock.yaml | 2 + src/config/legacy-web-search.ts | 10 +- src/plugins/bundled-web-search.ts | 14 + src/plugins/contracts/registry.ts | 2 + src/secrets/target-registry-data.ts | 11 + 9 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 extensions/baidu/index.ts create mode 100644 extensions/baidu/openclaw.plugin.json create mode 100644 extensions/baidu/package.json create mode 100644 extensions/baidu/src/baidu-web-search-provider.ts diff --git a/extensions/baidu/index.ts b/extensions/baidu/index.ts new file mode 100644 index 00000000000..979c842e2ed --- /dev/null +++ b/extensions/baidu/index.ts @@ -0,0 +1,11 @@ +import { definePluginEntry } from "openclaw/plugin-sdk/core"; +import { createBaiduWebSearchProvider } from "./src/baidu-web-search-provider.ts"; + +export default definePluginEntry({ + id: "baidu", + name: "Baidu Plugin", + description: "Bundled Baidu plugin", + register(api) { + api.registerWebSearchProvider(createBaiduWebSearchProvider()); + }, +}); diff --git a/extensions/baidu/openclaw.plugin.json b/extensions/baidu/openclaw.plugin.json new file mode 100644 index 00000000000..8fc2d3314ca --- /dev/null +++ b/extensions/baidu/openclaw.plugin.json @@ -0,0 +1,26 @@ +{ + "id": "baidu", + "uiHints": { + "webSearch.apiKey": { + "label": "Baidu Search API Key", + "help": "Baidu Search API key (fallback: BAIDU_SEARCH_API_KEY env var).", + "sensitive": true, + "placeholder": "bce-..." + } + }, + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": { + "webSearch": { + "type": "object", + "additionalProperties": false, + "properties": { + "apiKey": { + "type": ["string", "object"] + } + } + } + } + } +} diff --git a/extensions/baidu/package.json b/extensions/baidu/package.json new file mode 100644 index 00000000000..483af23cb5a --- /dev/null +++ b/extensions/baidu/package.json @@ -0,0 +1,12 @@ +{ + "name": "@openclaw/baidu-plugin", + "version": "2026.3.14", + "private": true, + "description": "OpenClaw Baidu plugin", + "type": "module", + "openclaw": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/extensions/baidu/src/baidu-web-search-provider.ts b/extensions/baidu/src/baidu-web-search-provider.ts new file mode 100644 index 00000000000..e8315a4607b --- /dev/null +++ b/extensions/baidu/src/baidu-web-search-provider.ts @@ -0,0 +1,259 @@ +import { Type } from "@sinclair/typebox"; +import { + buildSearchCacheKey, + DEFAULT_SEARCH_COUNT, + MAX_SEARCH_COUNT, + readCachedSearchPayload, + readConfiguredSecretString, + readNumberParam, + readProviderEnvValue, + readStringParam, + resolveSearchCacheTtlMs, + resolveSearchCount, + resolveSearchTimeoutSeconds, + resolveProviderWebSearchPluginConfig, + setProviderWebSearchPluginConfigValue, + type SearchConfigRecord, + type WebSearchProviderPlugin, + type WebSearchProviderToolDefinition, + withTrustedWebSearchEndpoint, + wrapWebContent, + writeCachedSearchPayload, +} from "openclaw/plugin-sdk/provider-web-search"; + +const BAIDU_SEARCH_API_ENDPOINT = "https://qianfan.baidubce.com/v2/ai_search/web_search"; + +type BaiduConfig = { + apiKey?: string; +}; + +type BaiduSearchResult = { + title?: string; + url?: string; + snippet?: string; + date?: string; + website?: string; +}; + +type BaiduSearchResponse = { + references?: BaiduSearchResult[]; +}; + +function createBaiduSchema() { + return Type.Object({ + query: Type.String({ description: "Search query string." }), + count: Type.Optional( + Type.Number({ + description: "Number of results to return (1-20).", + minimum: 1, + maximum: MAX_SEARCH_COUNT, + }), + ), + country: Type.Optional(Type.String({ description: "Not supported by Baidu." })), + language: Type.Optional(Type.String({ description: "Not supported by Baidu." })), + freshness: Type.Optional(Type.String({ description: "Not supported by Baidu." })), + date_after: Type.Optional(Type.String({ description: "Not supported by Baidu." })), + date_before: Type.Optional(Type.String({ description: "Not supported by Baidu." })), + }); +} + +function resolveBaiduConfig(searchConfig?: SearchConfigRecord): BaiduConfig { + const baidu = searchConfig?.baidu; + return baidu && typeof baidu === "object" && !Array.isArray(baidu) ? (baidu as BaiduConfig) : {}; +} + +function resolveBaiduApiKey(baidu?: BaiduConfig): string | undefined { + return ( + readConfiguredSecretString(baidu?.apiKey, "tools.web.search.baidu.apiKey") ?? + readProviderEnvValue(["BAIDU_SEARCH_API_KEY"]) + ); +} + +async function runBaiduSearch(params: { + query: string; + apiKey: string; + timeoutSeconds: number; + count: number; +}): Promise<{ results: BaiduSearchResult[] }> { + const body: Record = { + resource_type_filter: [ + { type: "web", top_k: params.count > 0 ? params.count : DEFAULT_SEARCH_COUNT }, + ], + messages: [ + { + role: "user", + content: params.query, + }, + ], + }; + return withTrustedWebSearchEndpoint( + { + url: BAIDU_SEARCH_API_ENDPOINT, + timeoutSeconds: params.timeoutSeconds, + init: { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${params.apiKey}`, + "X-Appbuilder-From": "openclaw", + }, + body: JSON.stringify(body), + }, + }, + async (res) => { + if (!res.ok) { + const detail = await res.text(); + throw new Error(`Baidu API error (${res.status}): ${detail || res.statusText}`); + } + const data = (await res.json()) as BaiduSearchResponse; + const results = Array.isArray(data.references) ? data.references : []; + const mapped = results.map((entry) => { + const snippet = entry.snippet ?? ""; + const title = entry.title ?? ""; + const url = entry.url ?? ""; + const site = entry.website ?? ""; + return { + title: title ? wrapWebContent(title, "web_search") : "", + url, // Keep raw for tool chaining + snippet: snippet ? wrapWebContent(snippet, "web_search") : "", + date: entry.date || undefined, + site: site, + }; + }); + return { results: mapped }; + }, + ); +} + +function createBaiduToolDefinition( + searchConfig?: SearchConfigRecord, +): WebSearchProviderToolDefinition { + return { + description: "Search the web using Baidu Search", + parameters: createBaiduSchema(), + execute: async (args) => { + const params = args as Record; + for (const name of ["country", "language", "freshness", "date_after", "date_before"]) { + if (readStringParam(params, name)) { + const label = + name === "country" + ? "country filtering" + : name === "language" + ? "language filtering" + : name === "freshness" + ? "freshness filtering" + : "date_after/date_before filtering"; + return { + error: name.startsWith("date_") ? "unsupported_date_filter" : `unsupported_${name}`, + message: `${label} is not supported by the baidu provider. Only Brave and Perplexity support ${name === "country" ? "country filtering" : name === "language" ? "language filtering" : name === "freshness" ? "freshness" : "date filtering"}.`, + docs: "https://docs.openclaw.ai/tools/web", + }; + } + } + + const baiduConfig = resolveBaiduConfig(searchConfig); + const apiKey = resolveBaiduApiKey(baiduConfig); + if (!apiKey) { + return { + error: "missing_baidu_search_api_key", + message: + "web_search (baidu) needs a Baidu Search API key. Set BAIDU_SEARCH_API_KEY in the Gateway environment, or configure plugins.entries.baidu.config.webSearch.apiKey.", + docs: "https://docs.openclaw.ai/tools/web", + }; + } + const query = readStringParam(params, "query", { required: true }); + const count = + readNumberParam(params, "count", { integer: true }) ?? + searchConfig?.maxResults ?? + undefined; + const cacheKey = buildSearchCacheKey([ + "baidu", + query, + resolveSearchCount(count, DEFAULT_SEARCH_COUNT), + ]); + const cached = readCachedSearchPayload(cacheKey); + if (cached) { + return cached; + } + + const start = Date.now(); + const { results } = await runBaiduSearch({ + query, + apiKey, + timeoutSeconds: resolveSearchTimeoutSeconds(searchConfig), + count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT), + }); + const payload = { + query: params.query, + provider: params.provider, + tookMs: Date.now() - start, + externalContent: { + untrusted: true, + source: "web_search", + provider: params.provider, + wrapped: true, + }, + results: results, + }; + writeCachedSearchPayload(cacheKey, payload, resolveSearchCacheTtlMs(searchConfig)); + return payload; + }, + }; +} + +export function createBaiduWebSearchProvider(): WebSearchProviderPlugin { + return { + id: "baidu", + label: "Baidu Search", + hint: "Structured results", + envVars: ["BAIDU_SEARCH_API_KEY"], + placeholder: "bce...", + signupUrl: "https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey", + docsUrl: "https://docs.openclaw.ai/tools/web", + autoDetectOrder: 5, + credentialPath: "plugins.entries.baidu.config.webSearch.apiKey", + inactiveSecretPaths: ["plugins.entries.baidu.config.webSearch.apiKey"], + getCredentialValue: (searchConfig) => { + const baidu = searchConfig?.baidu; + return baidu && typeof baidu === "object" && !Array.isArray(baidu) + ? (baidu as Record).apiKey + : undefined; + }, + setCredentialValue: (searchConfigTarget, value) => { + const scoped = searchConfigTarget.baidu; + if (!scoped || typeof scoped !== "object" || Array.isArray(scoped)) { + searchConfigTarget.baidu = { apiKey: value }; + return; + } + (scoped as Record).apiKey = value; + }, + getConfiguredCredentialValue: (config) => + resolveProviderWebSearchPluginConfig(config, "baidu")?.apiKey, + setConfiguredCredentialValue: (configTarget, value) => { + setProviderWebSearchPluginConfigValue(configTarget, "baidu", "apiKey", value); + }, + createTool: (ctx) => + createBaiduToolDefinition( + (() => { + const searchConfig = ctx.searchConfig as SearchConfigRecord | undefined; + const pluginConfig = resolveProviderWebSearchPluginConfig(ctx.config, "baidu"); + if (!pluginConfig) { + return searchConfig; + } + return { + ...(searchConfig ?? {}), + baidu: { + ...resolveBaiduConfig(searchConfig), + ...pluginConfig, + }, + } as SearchConfigRecord; + })(), + ), + }; +} + +export const __testing = { + resolveBaiduConfig, + resolveBaiduApiKey, + runBaiduSearch, +} as const; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0d503f2346..1949aa94c41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,6 +242,8 @@ importers: extensions/anthropic: {} + extensions/baidu: {} + extensions/bluebubbles: dependencies: zod: diff --git a/src/config/legacy-web-search.ts b/src/config/legacy-web-search.ts index 4b42eca8311..4aaef33fc95 100644 --- a/src/config/legacy-web-search.ts +++ b/src/config/legacy-web-search.ts @@ -11,6 +11,7 @@ const GENERIC_WEB_SEARCH_KEYS = new Set([ ]); const LEGACY_PROVIDER_MAP = { + baidu: "baidu", brave: "brave", firecrawl: "firecrawl", gemini: "google", @@ -122,7 +123,14 @@ export function normalizeLegacyWebSearchConfig(raw: T): T { setPluginWebSearchConfig(nextRoot, LEGACY_PROVIDER_MAP.brave, braveConfig); } - for (const providerId of ["firecrawl", "gemini", "grok", "kimi", "perplexity"] as const) { + for (const providerId of [ + "baidu", + "firecrawl", + "gemini", + "grok", + "kimi", + "perplexity", + ] as const) { const scoped = copyLegacyProviderConfig(search, providerId); if (!scoped || Object.keys(scoped).length === 0) { continue; diff --git a/src/plugins/bundled-web-search.ts b/src/plugins/bundled-web-search.ts index d1f2ce342f8..cc1668a634e 100644 --- a/src/plugins/bundled-web-search.ts +++ b/src/plugins/bundled-web-search.ts @@ -104,6 +104,20 @@ function resolvePerplexityRuntimeMetadata( } const BUNDLED_WEB_SEARCH_PROVIDER_DESCRIPTORS = [ + { + pluginId: "baidu", + id: "baidu", + label: "Baidu Search", + hint: "Structured results", + envVars: ["BAIDU_SEARCH_API_KEY"], + placeholder: "bce...", + signupUrl: "https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey", + docsUrl: "https://docs.openclaw.ai/tools/web", + autoDetectOrder: 5, + credentialPath: "plugins.entries.baidu.config.webSearch.apiKey", + inactiveSecretPaths: ["plugins.entries.baidu.config.webSearch.apiKey"], + credentialScope: { kind: "scoped", key: "baidu" }, + }, { pluginId: "brave", id: "brave", diff --git a/src/plugins/contracts/registry.ts b/src/plugins/contracts/registry.ts index 60d6f96dc3d..b26d4568711 100644 --- a/src/plugins/contracts/registry.ts +++ b/src/plugins/contracts/registry.ts @@ -1,5 +1,6 @@ import amazonBedrockPlugin from "../../../extensions/amazon-bedrock/index.js"; import anthropicPlugin from "../../../extensions/anthropic/index.js"; +import baiduPlugin from "../../../extensions/baidu/index.js"; import bravePlugin from "../../../extensions/brave/index.js"; import byteplusPlugin from "../../../extensions/byteplus/index.js"; import chutesPlugin from "../../../extensions/chutes/index.js"; @@ -79,6 +80,7 @@ type PluginRegistrationContractEntry = { }; const bundledWebSearchPlugins: Array = [ + { ...baiduPlugin, credentialValue: "bce-test" }, { ...bravePlugin, credentialValue: "BSA-test" }, { ...firecrawlPlugin, credentialValue: "fc-test" }, { ...googlePlugin, credentialValue: "AIza-test" }, diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index 30aa096004b..f09dd9d8bf8 100644 --- a/src/secrets/target-registry-data.ts +++ b/src/secrets/target-registry-data.ts @@ -733,6 +733,17 @@ const SECRET_TARGET_REGISTRY: SecretTargetRegistryEntry[] = [ includeInConfigure: true, includeInAudit: true, }, + { + id: "plugins.entries.baidu.config.webSearch.apiKey", + targetType: "plugins.entries.baidu.config.webSearch.apiKey", + configFile: "openclaw.json", + pathPattern: "plugins.entries.baidu.config.webSearch.apiKey", + secretShape: SECRET_INPUT_SHAPE, + expectedResolvedValue: "string", + includeInPlan: true, + includeInConfigure: true, + includeInAudit: true, + }, { id: "plugins.entries.brave.config.webSearch.apiKey", targetType: "plugins.entries.brave.config.webSearch.apiKey", From 8630650abf577cceedf8c2e0066dd685ebb9ff8c Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 12:56:23 +0800 Subject: [PATCH 2/9] tools: add badiu as web search provider --- docs/tools/web.md | 51 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/docs/tools/web.md b/docs/tools/web.md index 313e709c32f..8172385d1ba 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -11,7 +11,7 @@ title: "Web Tools" OpenClaw ships two lightweight web tools: -- `web_search` — Search the web using Brave Search API, Firecrawl Search, Gemini with Google Search grounding, Grok, Kimi, or Perplexity Search API. +- `web_search` — Search the web using Baidu Search API, Brave Search API, Firecrawl Search, Gemini with Google Search grounding, Grok, Kimi, or Perplexity Search API. - `web_fetch` — HTTP fetch + readable extraction (HTML → markdown/text). These are **not** browser automation. For JS-heavy sites or logins, use the @@ -32,6 +32,7 @@ See [Brave Search setup](/tools/brave-search) and [Perplexity Search setup](/too | Provider | Result shape | Provider-specific filters | Notes | API key | | ------------------------- | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------- | +| **Baidu** | Structured results with snippets | — | Uses Baidu Search | `BAIDU_SEARCH_API_KEY` | | **Brave Search API** | Structured results with snippets | `country`, `language`, `ui_lang`, time | Supports Brave `llm-context` mode | `BRAVE_API_KEY` | | **Firecrawl Search** | Structured results with snippets | Use `firecrawl_search` for Firecrawl-specific search options | Best for pairing search with Firecrawl scraping/extraction | `FIRECRAWL_API_KEY` | | **Gemini** | AI-synthesized answers + citations | — | Uses Google Search grounding | `GEMINI_API_KEY` | @@ -43,12 +44,13 @@ See [Brave Search setup](/tools/brave-search) and [Perplexity Search setup](/too The table above is alphabetical. If no `provider` is explicitly set, runtime auto-detection checks providers in this order: -1. **Brave** — `BRAVE_API_KEY` env var or `plugins.entries.brave.config.webSearch.apiKey` -2. **Gemini** — `GEMINI_API_KEY` env var or `plugins.entries.google.config.webSearch.apiKey` -3. **Grok** — `XAI_API_KEY` env var or `plugins.entries.xai.config.webSearch.apiKey` -4. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `plugins.entries.moonshot.config.webSearch.apiKey` -5. **Perplexity** — `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey` -6. **Firecrawl** — `FIRECRAWL_API_KEY` env var or `plugins.entries.firecrawl.config.webSearch.apiKey` +1. **Baidu** — `BAIDU_SEARCH_API_KEY` env var or `plugins.entries.baidu.config.webSearch.apiKey` +2. **Brave** — `BRAVE_API_KEY` env var or `plugins.entries.brave.config.webSearch.apiKey` +3. **Gemini** — `GEMINI_API_KEY` env var or `plugins.entries.google.config.webSearch.apiKey` +4. **Grok** — `XAI_API_KEY` env var or `plugins.entries.xai.config.webSearch.apiKey` +5. **Kimi** — `KIMI_API_KEY` / `MOONSHOT_API_KEY` env var or `plugins.entries.moonshot.config.webSearch.apiKey` +6. **Perplexity** — `PERPLEXITY_API_KEY`, `OPENROUTER_API_KEY`, or `plugins.entries.perplexity.config.webSearch.apiKey` +7. **Firecrawl** — `FIRECRAWL_API_KEY` env var or `plugins.entries.firecrawl.config.webSearch.apiKey` If no keys are found, it falls back to Brave (you'll get a missing-key error prompting you to configure one). @@ -62,6 +64,12 @@ Runtime SecretRef behavior: Use `openclaw configure --section web` to set up your API key and choose a provider. +### Baidu Search + +1. Visit the [Baidu AI Search Console](https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey) +2. Generate a new API key or select an existing one(format: `bce-v3/ALTAK-...`) +3. Copy the API key and use it with OpenClaw + ### Brave Search 1. Create a Brave Search API account at [brave.com/search/api](https://brave.com/search/api/) @@ -91,6 +99,7 @@ See [Perplexity Search API Docs](https://docs.perplexity.ai/guides/search-quicks **Via config:** run `openclaw configure --section web`. It stores the key under the provider-specific config path: +- Baidu: `plugins.entries.baidu.config.webSearch.apiKey` - Brave: `plugins.entries.brave.config.webSearch.apiKey` - Firecrawl: `plugins.entries.firecrawl.config.webSearch.apiKey` - Gemini: `plugins.entries.google.config.webSearch.apiKey` @@ -102,6 +111,7 @@ All of these fields also support SecretRef objects. **Via environment:** set provider env vars in the Gateway process environment: +- Baidu: `BAIDU_SEARCH_API_KEY` - Brave: `BRAVE_API_KEY` - Firecrawl: `FIRECRAWL_API_KEY` - Gemini: `GEMINI_API_KEY` @@ -113,6 +123,32 @@ For a gateway install, put these in `~/.openclaw/.env` (or your service environm ### Config examples +**Baidu Search:** + +```json5 +{ + plugins: { + entries: { + baidu: { + config: { + webSearch: { + apiKey: "YOUR_BAIDU_API_KEY", // optional if BRAVE_API_KEY is set // pragma: allowlist secret + }, + }, + }, + }, + }, + tools: { + web: { + search: { + enabled: true, + provider: "baidu", + }, + }, + }, +} +``` + **Brave Search:** ```json5 @@ -320,6 +356,7 @@ Search the web using your configured provider. - `tools.web.search.enabled` must not be `false` (default: enabled) - API key for your chosen provider: + - **Baidu**: `BAIDU_SEARCH_API_KEY` or `plugins.entries.baidu.config.webSearch.apiKey` - **Brave**: `BRAVE_API_KEY` or `plugins.entries.brave.config.webSearch.apiKey` - **Firecrawl**: `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webSearch.apiKey` - **Gemini**: `GEMINI_API_KEY` or `plugins.entries.google.config.webSearch.apiKey` From f76f7403031757e85809ee5b6bbc4245b8d4665f Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 13:59:58 +0800 Subject: [PATCH 3/9] fix provider --- extensions/baidu/src/baidu-web-search-provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/baidu/src/baidu-web-search-provider.ts b/extensions/baidu/src/baidu-web-search-provider.ts index e8315a4607b..9229ca8100c 100644 --- a/extensions/baidu/src/baidu-web-search-provider.ts +++ b/extensions/baidu/src/baidu-web-search-provider.ts @@ -185,12 +185,12 @@ function createBaiduToolDefinition( }); const payload = { query: params.query, - provider: params.provider, + provider: "baidu", tookMs: Date.now() - start, externalContent: { untrusted: true, source: "web_search", - provider: params.provider, + provider: "baidu", wrapped: true, }, results: results, From 7ba3182e38d52f994d662dadc764de70d0f3e334 Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 14:10:17 +0800 Subject: [PATCH 4/9] optimize doc --- .github/labeler.yml | 4 ++++ docs/tools/web.md | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 67a74985465..c1c0e74c333 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -325,3 +325,7 @@ - changed-files: - any-glob-to-any-file: - "extensions/fal/**" +"extensions: baidu": + - changed-files: + - any-glob-to-any-file: + - "extensions/baidu/**" diff --git a/docs/tools/web.md b/docs/tools/web.md index 11daaa6b0ea..9f3d3afec62 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -11,7 +11,7 @@ title: "Web Tools" OpenClaw ships two lightweight web tools: -- `web_search` — Search the web using Baidu Search API, Brave Search API, Firecrawl Search, Gemini with Google Search grounding, Grok, Kimi, Perplexity Search API, or Tavily Search API. +- `web_search` — Search the web using Baidu Search API, Brave Search API, Firecrawl Search, Gemini with Google Search grounding, Grok, Kimi, Perplexity Search API, or Tavily Search API. - `web_fetch` — HTTP fetch + readable extraction (HTML → markdown/text). These are **not** browser automation. For JS-heavy sites or logins, use the @@ -55,7 +55,6 @@ The table above is alphabetical. If no `provider` is explicitly set, runtime aut 7. **Firecrawl** — `FIRECRAWL_API_KEY` env var or `plugins.entries.firecrawl.config.webSearch.apiKey` 8. **Tavily** — `TAVILY_API_KEY` env var or `plugins.entries.tavily.config.webSearch.apiKey` - If no keys are found, it falls back to Brave (you'll get a missing-key error prompting you to configure one). Runtime SecretRef behavior: From 2c96261f7cb5a2b5c1bbb10d2afb84173c4f4ea6 Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 14:15:51 +0800 Subject: [PATCH 5/9] fix format --- docs/tools/web.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tools/web.md b/docs/tools/web.md index 9f3d3afec62..7fc7f7d33d1 100644 --- a/docs/tools/web.md +++ b/docs/tools/web.md @@ -69,9 +69,9 @@ Use `openclaw configure --section web` to set up your API key and choose a provi ### Baidu Search -1. Visit the [Baidu AI Search Console](https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey) -2. Generate a new API key or select an existing one(format: `bce-v3/ALTAK-...`) -3. Copy the API key and use it with OpenClaw +1. Visit the [Baidu AI Search Console](https://console.bce.baidu.com/ai-search/qianfan/ais/console/apiKey) +2. Generate a new API key or select an existing one(format: `bce-v3/ALTAK-...`) +3. Copy the API key and use it with OpenClaw ### Brave Search From 48792bd182b71c6357aa44efe99b52011866d0be Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 16:09:14 +0800 Subject: [PATCH 6/9] fix test --- .../reference/secretref-credential-surface.md | 1 + ...tref-user-supplied-credentials-matrix.json | 21 ++++++++++++------- src/plugins/web-search-providers.test.ts | 5 +++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/reference/secretref-credential-surface.md b/docs/reference/secretref-credential-surface.md index d0a11bc68ef..a8fd8e30c23 100644 --- a/docs/reference/secretref-credential-surface.md +++ b/docs/reference/secretref-credential-surface.md @@ -39,6 +39,7 @@ Scope intent: - `plugins.entries.perplexity.config.webSearch.apiKey` - `plugins.entries.firecrawl.config.webSearch.apiKey` - `plugins.entries.tavily.config.webSearch.apiKey` +- `plugins.entries.baidu.config.webSearch.apiKey` - `tools.web.search.apiKey` - `tools.web.search.gemini.apiKey` - `tools.web.search.grok.apiKey` diff --git a/docs/reference/secretref-user-supplied-credentials-matrix.json b/docs/reference/secretref-user-supplied-credentials-matrix.json index cca7bb38c4b..e35095b6eef 100644 --- a/docs/reference/secretref-user-supplied-credentials-matrix.json +++ b/docs/reference/secretref-user-supplied-credentials-matrix.json @@ -447,6 +447,13 @@ "secretShape": "secret_input", "optIn": true }, + { + "id": "plugins.entries.baidu.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.baidu.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, { "id": "plugins.entries.brave.config.webSearch.apiKey", "configFile": "openclaw.json", @@ -482,6 +489,13 @@ "secretShape": "secret_input", "optIn": true }, + { + "id": "plugins.entries.tavily.config.webSearch.apiKey", + "configFile": "openclaw.json", + "path": "plugins.entries.tavily.config.webSearch.apiKey", + "secretShape": "secret_input", + "optIn": true + }, { "id": "plugins.entries.xai.config.webSearch.apiKey", "configFile": "openclaw.json", @@ -551,13 +565,6 @@ "path": "tools.web.search.perplexity.apiKey", "secretShape": "secret_input", "optIn": true - }, - { - "id": "plugins.entries.tavily.config.webSearch.apiKey", - "configFile": "openclaw.json", - "path": "plugins.entries.tavily.config.webSearch.apiKey", - "secretShape": "secret_input", - "optIn": true } ] } diff --git a/src/plugins/web-search-providers.test.ts b/src/plugins/web-search-providers.test.ts index 87a4da1973c..420ad8e4ab7 100644 --- a/src/plugins/web-search-providers.test.ts +++ b/src/plugins/web-search-providers.test.ts @@ -9,6 +9,7 @@ import { } from "./web-search-providers.js"; const BUNDLED_WEB_SEARCH_PROVIDERS = [ + { pluginId: "baidu", id: "baidu", order: 5 }, { pluginId: "brave", id: "brave", order: 10 }, { pluginId: "google", id: "gemini", order: 20 }, { pluginId: "xai", id: "grok", order: 30 }, @@ -91,6 +92,7 @@ describe("resolvePluginWebSearchProviders", () => { const providers = resolvePluginWebSearchProviders({}); expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([ + "baidu:baidu", "brave:brave", "google:gemini", "xai:grok", @@ -100,6 +102,7 @@ describe("resolvePluginWebSearchProviders", () => { "tavily:tavily", ]); expect(providers.map((provider) => provider.credentialPath)).toEqual([ + "plugins.entries.baidu.config.webSearch.apiKey", "plugins.entries.brave.config.webSearch.apiKey", "plugins.entries.google.config.webSearch.apiKey", "plugins.entries.xai.config.webSearch.apiKey", @@ -127,6 +130,7 @@ describe("resolvePluginWebSearchProviders", () => { }); expect(providers.map((provider) => provider.pluginId)).toEqual([ + "baidu", "brave", "google", "xai", @@ -181,6 +185,7 @@ describe("resolvePluginWebSearchProviders", () => { }); expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([ + "baidu:baidu", "brave:brave", "google:gemini", "xai:grok", From 4a98b360edfe903e79fd1ce58edd1ce6f4a45db5 Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 16:14:30 +0800 Subject: [PATCH 7/9] fix env var --- extensions/baidu/openclaw.plugin.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/extensions/baidu/openclaw.plugin.json b/extensions/baidu/openclaw.plugin.json index 8fc2d3314ca..14f6f432ee0 100644 --- a/extensions/baidu/openclaw.plugin.json +++ b/extensions/baidu/openclaw.plugin.json @@ -1,5 +1,8 @@ { "id": "baidu", + "providerAuthEnvVars": { + "brave": ["BAIDU_SEARCH_API_KEY"] + }, "uiHints": { "webSearch.apiKey": { "label": "Baidu Search API Key", From 48ba314cf82adea78999e746b9c67f46b3d223e4 Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 16:26:58 +0800 Subject: [PATCH 8/9] fix test --- src/plugins/bundled-web-search.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/bundled-web-search.test.ts b/src/plugins/bundled-web-search.test.ts index b8d5c6142ad..d663e423e45 100644 --- a/src/plugins/bundled-web-search.test.ts +++ b/src/plugins/bundled-web-search.test.ts @@ -66,6 +66,7 @@ describe("bundled web search metadata", () => { it("keeps bundled web search compat ids aligned with bundled manifests", () => { expect(resolveBundledWebSearchPluginIds({})).toEqual([ + "baidu", "brave", "firecrawl", "google", From a648f1623f3a15028521f6d081274570209c4ab1 Mon Sep 17 00:00:00 2001 From: ideoutrea Date: Fri, 20 Mar 2026 21:13:40 +0800 Subject: [PATCH 9/9] fix test --- src/secrets/runtime.coverage.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/secrets/runtime.coverage.test.ts b/src/secrets/runtime.coverage.test.ts index bce2911b88f..965873a1cc2 100644 --- a/src/secrets/runtime.coverage.test.ts +++ b/src/secrets/runtime.coverage.test.ts @@ -23,7 +23,7 @@ vi.mock("../plugins/web-search-providers.runtime.js", () => ({ })); function createTestProvider(params: { - id: "brave" | "gemini" | "grok" | "kimi" | "perplexity" | "firecrawl" | "tavily"; + id: "baidu" | "brave" | "gemini" | "grok" | "kimi" | "perplexity" | "firecrawl" | "tavily"; pluginId: string; order: number; }): PluginWebSearchProviderEntry { @@ -49,7 +49,7 @@ function createTestProvider(params: { getCredentialValue: readSearchConfigKey, setCredentialValue: (searchConfigTarget, value) => { const providerConfig = - params.id === "brave" || params.id === "firecrawl" + params.id === "brave" || params.id === "firecrawl" || params.id === "baidu" ? searchConfigTarget : ((searchConfigTarget[params.id] ??= {}) as { apiKey?: unknown }); providerConfig.apiKey = value; @@ -77,6 +77,7 @@ function createTestProvider(params: { function buildTestWebSearchProviders(): PluginWebSearchProviderEntry[] { return [ + createTestProvider({ id: "baidu", pluginId: "baidu", order: 5 }), createTestProvider({ id: "brave", pluginId: "brave", order: 10 }), createTestProvider({ id: "gemini", pluginId: "google", order: 20 }), createTestProvider({ id: "grok", pluginId: "xai", order: 30 }), @@ -168,6 +169,9 @@ function buildConfigForOpenClawTarget(entry: SecretRegistryEntry, envId: string) "webhook", ); } + if (entry.id === "plugins.entries.baidu.config.webSearch.apiKey") { + setPathCreateStrict(config, ["tools", "web", "search", "provider"], "baidu"); + } if (entry.id === "plugins.entries.brave.config.webSearch.apiKey") { setPathCreateStrict(config, ["tools", "web", "search", "provider"], "brave"); }