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/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 6fce90f4f58..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", diff --git a/docs/tools/web.md b/docs/tools/web.md index 8d5b6bff5f1..7fc7f7d33d1 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, 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 @@ -33,6 +33,7 @@ See [Brave Search setup](/tools/brave-search), [Perplexity Search setup](/tools/ | 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` | @@ -45,13 +46,14 @@ See [Brave Search setup](/tools/brave-search), [Perplexity Search setup](/tools/ 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` -7. **Tavily** — `TAVILY_API_KEY` env var or `plugins.entries.tavily.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` +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). @@ -65,6 +67,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/) @@ -94,6 +102,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` @@ -106,6 +115,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` @@ -118,6 +128,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 @@ -355,6 +391,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` 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..14f6f432ee0 --- /dev/null +++ b/extensions/baidu/openclaw.plugin.json @@ -0,0 +1,29 @@ +{ + "id": "baidu", + "providerAuthEnvVars": { + "brave": ["BAIDU_SEARCH_API_KEY"] + }, + "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..9229ca8100c --- /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: "baidu", + tookMs: Date.now() - start, + externalContent: { + untrusted: true, + source: "web_search", + provider: "baidu", + 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 7f438d0a2e3..b623a42b3df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,6 +245,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 71f7929d673..c9ca80e7970 100644 --- a/src/config/legacy-web-search.ts +++ b/src/config/legacy-web-search.ts @@ -12,6 +12,7 @@ const GENERIC_WEB_SEARCH_KEYS = new Set([ ]); const LEGACY_PROVIDER_MAP = { + baidu: "baidu", brave: "brave", firecrawl: "firecrawl", gemini: "google", @@ -213,7 +214,14 @@ function normalizeLegacyWebSearchConfigRecord( }); } - 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.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", diff --git a/src/plugins/web-search-providers.test.ts b/src/plugins/web-search-providers.test.ts index 85339014380..694a9cc6f28 100644 --- a/src/plugins/web-search-providers.test.ts +++ b/src/plugins/web-search-providers.test.ts @@ -6,6 +6,7 @@ describe("resolveBundledPluginWebSearchProviders", () => { const providers = resolveBundledPluginWebSearchProviders({}); expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([ + "baidu:baidu", "brave:brave", "google:gemini", "xai:grok", @@ -15,6 +16,7 @@ describe("resolveBundledPluginWebSearchProviders", () => { "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", @@ -42,6 +44,7 @@ describe("resolveBundledPluginWebSearchProviders", () => { }); expect(providers.map((provider) => provider.pluginId)).toEqual([ + "baidu", "brave", "google", "xai", @@ -96,6 +99,7 @@ describe("resolveBundledPluginWebSearchProviders", () => { }); expect(providers.map((provider) => `${provider.pluginId}:${provider.id}`)).toEqual([ + "baidu:baidu", "brave:brave", "google:gemini", "xai:grok", 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"); } diff --git a/src/secrets/target-registry-data.ts b/src/secrets/target-registry-data.ts index 7d1a7854867..03374b47244 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",