自动同步: 2026-07-01 02:10

This commit is contained in:
2026-07-01 02:10:07 +08:00
parent d20bd4cfab
commit 489a0835e9
4162 changed files with 1010609 additions and 12590 deletions
@@ -0,0 +1,21 @@
{
"name": "openclaw-brave-plugin-11fe9e3aa3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@openclaw/brave-plugin": {
"version": "2026.6.10",
"resolved": "https://registry.npmjs.org/@openclaw/brave-plugin/-/brave-plugin-2026.6.10.tgz",
"integrity": "sha512-DDRnb4reL99O8kbISNbRFyk/xoUPYHsXG3UGikKAsVs+zIldYYA0hY0d3Z2aWoE+0vfda27mJUByCo7Xr15qdw==",
"hasShrinkwrap": true,
"peerDependencies": {
"openclaw": ">=2026.6.10"
},
"peerDependenciesMeta": {
"openclaw": {
"optional": true
}
}
}
}
}
@@ -0,0 +1,36 @@
# @openclaw/brave-plugin
Official Brave Search provider plugin for OpenClaw.
This plugin registers Brave as a `web_search` provider. It supports normal Brave web search and Brave LLM Context API mode.
## Install
```bash
openclaw plugins install @openclaw/brave-plugin
```
Restart the Gateway after installing or updating the plugin.
## Configure
Store a Brave Search API key in plugin config or expose `BRAVE_API_KEY` to the Gateway:
```bash
openclaw config set plugins.entries.brave.enabled true
openclaw config set tools.web.search.provider brave
```
Provider-specific options live under `plugins.entries.brave.config.webSearch.*`.
## Docs
Full setup, config examples, search modes, and tool parameters:
- https://docs.openclaw.ai/tools/brave-search
## Package
- Plugin id: `brave`
- Package: `@openclaw/brave-plugin`
- Minimum OpenClaw host: `2026.4.10`
@@ -0,0 +1,81 @@
import { buildBraveWebSearchProviderBase } from "./web-search-shared.js";
import { isDiagnosticFlagEnabled } from "openclaw/plugin-sdk/diagnostic-runtime";
import { mergeScopedSearchConfig, resolveProviderWebSearchPluginConfig } from "openclaw/plugin-sdk/provider-web-search-config-contract";
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
//#region extensions/brave/src/brave-web-search-provider.ts
/**
* Brave web-search provider factory. It builds the agent tool definition and
* lazy-loads HTTP execution only when a search is run.
*/
let braveWebSearchRuntimePromise;
function loadBraveWebSearchRuntime() {
braveWebSearchRuntimePromise ??= import("./brave-web-search-provider.runtime-D_z2neAi.js");
return braveWebSearchRuntimePromise;
}
const BraveSearchSchema = {
type: "object",
properties: {
query: {
type: "string",
description: "Search query string."
},
count: {
type: "integer",
description: "Number of results to return (1-10).",
minimum: 1,
maximum: 10
},
country: {
type: "string",
description: "2-letter country code for region-specific results (e.g., 'DE', 'US', 'ALL'). Default: 'US'."
},
language: {
type: "string",
description: "ISO 639-1 language code for results (e.g., 'en', 'de', 'fr')."
},
freshness: {
type: "string",
description: "Filter by time: 'day' (24h), 'week', 'month', or 'year'."
},
date_after: {
type: "string",
description: "Only results published after this date (YYYY-MM-DD)."
},
date_before: {
type: "string",
description: "Only results published before this date (YYYY-MM-DD)."
},
search_lang: {
type: "string",
description: "Brave language code for search results (e.g., 'en', 'de', 'en-gb', 'zh-hans', 'zh-hant', 'pt-br')."
},
ui_lang: {
type: "string",
description: "Locale code for UI elements in language-region format (e.g., 'en-US', 'de-DE', 'fr-FR', 'tr-TR'). Must include region subtag."
}
}
};
function resolveBraveMode(searchConfig) {
return (isRecord(searchConfig?.brave) ? searchConfig.brave : void 0)?.mode === "llm-context" ? "llm-context" : "web";
}
function createBraveToolDefinition(searchConfig, config) {
const braveMode = resolveBraveMode(searchConfig);
const diagnosticsEnabled = isDiagnosticFlagEnabled("brave.http", config);
return {
description: braveMode === "llm-context" ? "Search the web using Brave Search LLM Context API. Returns pre-extracted page content (text chunks, tables, code blocks) optimized for LLM grounding." : "Search the web using Brave Search API. Supports region-specific and localized search via country and language parameters. Returns titles, URLs, and snippets for fast research.",
parameters: BraveSearchSchema,
execute: async (args) => {
const { executeBraveSearch } = await loadBraveWebSearchRuntime();
return await executeBraveSearch(args, searchConfig, { diagnosticsEnabled });
}
};
}
/** Create the runtime Brave Search provider descriptor. */
function createBraveWebSearchProvider() {
return {
...buildBraveWebSearchProviderBase(),
createTool: (ctx) => createBraveToolDefinition(mergeScopedSearchConfig(ctx.searchConfig, "brave", resolveProviderWebSearchPluginConfig(ctx.config, "brave"), { mirrorApiKeyToTopLevel: true }), ctx.config)
};
}
//#endregion
export { createBraveWebSearchProvider as t };
@@ -0,0 +1,529 @@
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import { assertOkOrThrowProviderError, readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
import { DEFAULT_SEARCH_COUNT, MAX_SEARCH_COUNT, buildSearchCacheKey, formatCliCommand, parseWebSearchTimeFilters, readCachedSearchPayload, readConfiguredSecretString, readPositiveIntegerParam, readProviderEnvValue, readStringParam, resolveSearchCacheTtlMs, resolveSearchCount, resolveSearchTimeoutSeconds, resolveSiteName, withSelfHostedWebSearchEndpoint, withTrustedWebSearchEndpoint, wrapWebContent, writeCachedSearchPayload } from "openclaw/plugin-sdk/provider-web-search";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { assertHttpUrlTargetsPrivateNetwork, isBlockedHostnameOrIp, isPrivateIpAddress, resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
//#region extensions/brave/src/brave-web-search-provider.shared.ts
/**
* Brave Search request normalization and result mapping. It validates Brave
* country/language params and converts LLM-context responses into web results.
*/
const BRAVE_COUNTRY_CODES = new Set([
"AR",
"AU",
"AT",
"BE",
"BR",
"CA",
"CL",
"DK",
"FI",
"FR",
"DE",
"GR",
"HK",
"IN",
"ID",
"IT",
"JP",
"KR",
"MY",
"MX",
"NL",
"NZ",
"NO",
"CN",
"PL",
"PT",
"PH",
"RU",
"SA",
"ZA",
"ES",
"SE",
"CH",
"TW",
"TR",
"GB",
"US",
"ALL"
]);
const BRAVE_SEARCH_LANG_CODES = new Set([
"ar",
"eu",
"bn",
"bg",
"ca",
"zh-hans",
"zh-hant",
"hr",
"cs",
"da",
"nl",
"en",
"en-gb",
"et",
"fi",
"fr",
"gl",
"de",
"el",
"gu",
"he",
"hi",
"hu",
"is",
"it",
"jp",
"kn",
"ko",
"lv",
"lt",
"ms",
"ml",
"mr",
"nb",
"pl",
"pt-br",
"pt-pt",
"pa",
"ro",
"ru",
"sr",
"sk",
"sl",
"es",
"sv",
"ta",
"te",
"th",
"tr",
"uk",
"vi"
]);
const BRAVE_SEARCH_LANG_ALIASES = {
ja: "jp",
zh: "zh-hans",
"zh-cn": "zh-hans",
"zh-hk": "zh-hant",
"zh-sg": "zh-hans",
"zh-tw": "zh-hant"
};
const BRAVE_UI_LANG_LOCALE = /^([a-z]{2})-([a-z]{2})$/i;
function normalizeBraveSearchLang(value) {
if (!value) return;
const trimmed = value.trim();
if (!trimmed) return;
const lower = normalizeLowercaseStringOrEmpty(trimmed);
const canonical = BRAVE_SEARCH_LANG_ALIASES[lower] ?? lower;
if (!BRAVE_SEARCH_LANG_CODES.has(canonical)) return;
return canonical;
}
/** Normalize Brave country filter values. */
function normalizeBraveCountry(value) {
if (!value) return;
const trimmed = value.trim();
if (!trimmed) return;
const canonical = trimmed.toUpperCase();
return BRAVE_COUNTRY_CODES.has(canonical) ? canonical : "ALL";
}
function normalizeBraveUiLang(value) {
if (!value) return;
const trimmed = value.trim();
if (!trimmed) return;
const match = trimmed.match(BRAVE_UI_LANG_LOCALE);
if (!match) return;
const [, language, region] = match;
return `${normalizeLowercaseStringOrEmpty(language)}-${region.toUpperCase()}`;
}
/** Resolve Brave-specific web-search config from scoped search config. */
function resolveBraveConfig(searchConfig) {
const brave = searchConfig?.brave;
return brave && typeof brave === "object" && !Array.isArray(brave) ? brave : {};
}
/** Resolve whether Brave should use web search or LLM Context API mode. */
function resolveBraveMode(brave) {
return brave?.mode === "llm-context" ? "llm-context" : "web";
}
/** Normalize Brave search and UI language params, detecting swapped fields. */
function normalizeBraveLanguageParams(params) {
const rawSearchLang = normalizeOptionalString(params.search_lang);
const rawUiLang = normalizeOptionalString(params.ui_lang);
let searchLangCandidate = rawSearchLang;
let uiLangCandidate = rawUiLang;
if (normalizeBraveUiLang(rawSearchLang) && normalizeBraveSearchLang(rawUiLang)) {
searchLangCandidate = rawUiLang;
uiLangCandidate = rawSearchLang;
}
const search_lang = normalizeBraveSearchLang(searchLangCandidate);
if (searchLangCandidate && !search_lang) return { invalidField: "search_lang" };
const ui_lang = normalizeBraveUiLang(uiLangCandidate);
if (uiLangCandidate && !ui_lang) return { invalidField: "ui_lang" };
return {
search_lang,
ui_lang
};
}
function resolveSiteName$1(url) {
if (!url) return;
try {
return new URL(url).hostname;
} catch {
return;
}
}
/** Map Brave LLM Context API grounding results into web-search result rows. */
function mapBraveLlmContextResults(data) {
return (Array.isArray(data.grounding?.generic) ? data.grounding.generic : []).map((entry) => ({
url: entry.url ?? "",
title: entry.title ?? "",
snippets: (entry.snippets ?? []).filter((snippet) => typeof snippet === "string" && snippet.length > 0),
siteName: resolveSiteName$1(entry.url) || void 0
}));
}
//#endregion
//#region extensions/brave/src/brave-web-search-provider.runtime.ts
/**
* Brave Search HTTP runtime. It resolves credentials, enforces endpoint safety,
* applies caching, and maps Brave web/LLM-context API responses.
*/
const DEFAULT_BRAVE_BASE_URL = "https://api.search.brave.com";
const BRAVE_SEARCH_ENDPOINT_PATH = "/res/v1/web/search";
const BRAVE_LLM_CONTEXT_ENDPOINT_PATH = "/res/v1/llm/context";
const braveHttpLogger = createSubsystemLogger("brave/http");
function logBraveHttp(diagnostics, event, meta) {
if (!diagnostics?.enabled) return;
braveHttpLogger.info(`brave http ${event}`, meta);
}
function describeBraveRequestUrl(url) {
return {
url: url.toString(),
query: url.searchParams.get("q") ?? "",
params: Object.fromEntries(url.searchParams.entries())
};
}
function resolveBraveApiKey(searchConfig) {
return readConfiguredSecretString(searchConfig?.apiKey, "tools.web.search.apiKey") ?? readProviderEnvValue(["BRAVE_API_KEY"]);
}
function resolveBraveBaseUrl(braveConfig) {
return readConfiguredSecretString(braveConfig?.baseUrl, "plugins.entries.brave.config.webSearch.baseUrl")?.replace(/\/+$/u, "") || DEFAULT_BRAVE_BASE_URL;
}
function buildBraveEndpointUrl(params) {
const url = new URL(params.baseUrl);
url.pathname = `${url.pathname.replace(/\/+$/u, "")}${params.endpointPath}`;
url.search = "";
return url;
}
async function braveEndpointTargetsPrivateNetwork(url) {
if (isBlockedHostnameOrIp(url.hostname)) return true;
try {
return (await resolvePinnedHostnameWithPolicy(url.hostname, { policy: {
allowPrivateNetwork: true,
allowRfc2544BenchmarkRange: true
} })).addresses.every((address) => isPrivateIpAddress(address));
} catch {
return false;
}
}
async function validateBraveBaseUrl(baseUrl) {
let parsed;
try {
parsed = new URL(baseUrl);
} catch {
throw new Error("Brave Search base URL must be a valid http:// or https:// URL.");
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("Brave Search base URL must use http:// or https://.");
if (parsed.protocol === "http:") {
await assertHttpUrlTargetsPrivateNetwork(parsed.toString(), {
dangerouslyAllowPrivateNetwork: true,
errorMessage: "Brave Search HTTP base URL must target a trusted private or loopback host. Use https:// for public hosts."
});
return "selfHosted";
}
return await braveEndpointTargetsPrivateNetwork(parsed) ? "selfHosted" : "strict";
}
function missingBraveKeyPayload() {
return {
error: "missing_brave_api_key",
message: `web_search (brave) needs a Brave Search API key. Run \`${formatCliCommand("openclaw configure --section web")}\` to store it, or set BRAVE_API_KEY in the Gateway environment. If you do not want to configure a search API key, use web_fetch for a specific URL or the browser tool for interactive pages.`,
docs: "https://docs.openclaw.ai/tools/web"
};
}
function setBraveSearchUrlParams(url, params) {
url.searchParams.set("q", params.query);
if (params.country) url.searchParams.set("country", params.country);
if (params.search_lang) url.searchParams.set("search_lang", params.search_lang);
if (params.freshness) url.searchParams.set("freshness", params.freshness);
else if (params.dateAfter && params.dateBefore) url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
else if (params.dateAfter) url.searchParams.set("freshness", `${params.dateAfter}to${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
else if (params.allowDateBeforeOnly && params.dateBefore) url.searchParams.set("freshness", `1970-01-01to${params.dateBefore}`);
}
async function runBraveJsonRequest(params, errorLabel) {
const url = buildBraveEndpointUrl({
baseUrl: params.baseUrl,
endpointPath: params.endpointPath
});
params.configureUrl(url);
logBraveHttp(params.diagnostics, "request", {
mode: params.mode,
...describeBraveRequestUrl(url)
});
const startedAt = Date.now();
return (params.endpointMode === "selfHosted" ? withSelfHostedWebSearchEndpoint : withTrustedWebSearchEndpoint)({
url: url.toString(),
timeoutSeconds: params.timeoutSeconds,
init: {
method: "GET",
headers: {
Accept: "application/json",
"X-Subscription-Token": params.apiKey
}
}
}, async (response) => {
logBraveHttp(params.diagnostics, "response", {
mode: params.mode,
status: response.status,
ok: response.ok,
durationMs: Date.now() - startedAt
});
await assertOkOrThrowProviderError(response, errorLabel);
return readProviderJsonResponse(response, errorLabel);
});
}
async function runBraveLlmContextSearch(params) {
const data = await runBraveJsonRequest({
baseUrl: params.baseUrl,
endpointPath: BRAVE_LLM_CONTEXT_ENDPOINT_PATH,
mode: "llm-context",
endpointMode: params.endpointMode,
apiKey: params.apiKey,
timeoutSeconds: params.timeoutSeconds,
diagnostics: params.diagnostics,
configureUrl: (url) => {
setBraveSearchUrlParams(url, params);
}
}, "Brave LLM Context API error");
return {
results: mapBraveLlmContextResults(data),
sources: data.sources
};
}
async function runBraveWebSearch(params) {
const data = await runBraveJsonRequest({
baseUrl: params.baseUrl,
endpointPath: BRAVE_SEARCH_ENDPOINT_PATH,
mode: "web",
endpointMode: params.endpointMode,
apiKey: params.apiKey,
timeoutSeconds: params.timeoutSeconds,
diagnostics: params.diagnostics,
configureUrl: (url) => {
setBraveSearchUrlParams(url, {
...params,
allowDateBeforeOnly: true
});
url.searchParams.set("count", String(params.count));
if (params.ui_lang) url.searchParams.set("ui_lang", params.ui_lang);
}
}, "Brave Search API error");
return (Array.isArray(data.web?.results) ? data.web?.results ?? [] : []).map((entry) => {
const description = entry.description ?? "";
const title = entry.title ?? "";
const url = entry.url ?? "";
return {
title: title ? wrapWebContent(title, "web_search") : "",
url,
description: description ? wrapWebContent(description, "web_search") : "",
published: entry.age || void 0,
siteName: resolveSiteName(url) || void 0
};
});
}
/** Execute one Brave Search request using web or LLM-context mode. */
async function executeBraveSearch(args, searchConfig, options) {
const apiKey = resolveBraveApiKey(searchConfig);
if (!apiKey) return missingBraveKeyPayload();
const braveConfig = resolveBraveConfig(searchConfig);
const braveMode = resolveBraveMode(braveConfig);
const braveBaseUrl = resolveBraveBaseUrl(braveConfig);
const braveEndpointMode = await validateBraveBaseUrl(braveBaseUrl);
const query = readStringParam(args, "query", { required: true });
const count = readPositiveIntegerParam(args, "count", {
max: MAX_SEARCH_COUNT,
message: `count must be an integer from 1 to ${MAX_SEARCH_COUNT}.`
}) ?? searchConfig?.maxResults ?? void 0;
const country = normalizeBraveCountry(readStringParam(args, "country"));
const language = readStringParam(args, "language");
const search_lang = readStringParam(args, "search_lang");
const ui_lang = readStringParam(args, "ui_lang");
const normalizedLanguage = normalizeBraveLanguageParams({
search_lang: search_lang || language,
ui_lang
});
if (normalizedLanguage.invalidField === "search_lang") return {
error: "invalid_search_lang",
message: "search_lang must be a Brave-supported language code like 'en', 'en-gb', 'zh-hans', or 'zh-hant'.",
docs: "https://docs.openclaw.ai/tools/web"
};
if (normalizedLanguage.invalidField === "ui_lang") return {
error: "invalid_ui_lang",
message: "ui_lang must be a language-region locale like 'en-US'.",
docs: "https://docs.openclaw.ai/tools/web"
};
if (normalizedLanguage.ui_lang && braveMode === "llm-context") return {
error: "unsupported_ui_lang",
message: "ui_lang is not supported by Brave llm-context mode. Remove ui_lang or use Brave web mode for locale-based UI hints.",
docs: "https://docs.openclaw.ai/tools/web"
};
const rawFreshness = readStringParam(args, "freshness");
const parsedTimeFilters = parseWebSearchTimeFilters({
rawDateAfter: readStringParam(args, "date_after"),
rawDateBefore: readStringParam(args, "date_before"),
rawFreshness,
freshnessProvider: "brave",
invalidFreshnessMessage: "freshness must be day, week, month, or year.",
invalidDateAfterMessage: "date_after must be YYYY-MM-DD format.",
invalidDateBeforeMessage: "date_before must be YYYY-MM-DD format.",
invalidDateRangeMessage: "date_after must be before date_before."
});
if ("error" in parsedTimeFilters) return parsedTimeFilters;
const { freshness, dateAfter, dateBefore } = parsedTimeFilters;
if (braveMode === "llm-context") {
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
if (dateAfter && !dateBefore && dateAfter > today) return {
error: "invalid_date_range",
message: "date_after cannot be in the future for Brave llm-context mode.",
docs: "https://docs.openclaw.ai/tools/web"
};
if (dateBefore && !dateAfter) return {
error: "unsupported_date_filter",
message: "Brave llm-context mode requires date_after when date_before is set. Use a bounded date range or freshness.",
docs: "https://docs.openclaw.ai/tools/web"
};
}
const llmContextDateEnd = braveMode === "llm-context" && dateAfter ? dateBefore ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) : dateBefore;
const cacheKey = buildSearchCacheKey(braveMode === "llm-context" ? [
"brave",
braveMode,
braveBaseUrl,
query,
country,
normalizedLanguage.search_lang,
freshness,
dateAfter,
llmContextDateEnd
] : [
"brave",
braveMode,
braveBaseUrl,
query,
resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
country,
normalizedLanguage.search_lang,
normalizedLanguage.ui_lang,
freshness,
dateAfter,
dateBefore
]);
const diagnostics = { enabled: options?.diagnosticsEnabled === true };
const cached = readCachedSearchPayload(cacheKey);
if (cached) {
logBraveHttp(diagnostics, "cache hit", {
mode: braveMode,
query,
cacheKey
});
return cached;
}
logBraveHttp(diagnostics, "cache miss", {
mode: braveMode,
query,
cacheKey
});
const start = Date.now();
const timeoutSeconds = resolveSearchTimeoutSeconds(searchConfig);
const cacheTtlMs = resolveSearchCacheTtlMs(searchConfig);
if (braveMode === "llm-context") {
const { results, sources } = await runBraveLlmContextSearch({
baseUrl: braveBaseUrl,
endpointMode: braveEndpointMode,
query,
apiKey,
timeoutSeconds,
diagnostics,
country: country ?? void 0,
search_lang: normalizedLanguage.search_lang,
freshness,
dateAfter,
dateBefore
});
const payload = {
query,
provider: "brave",
mode: "llm-context",
count: results.length,
tookMs: Date.now() - start,
externalContent: {
untrusted: true,
source: "web_search",
provider: "brave",
wrapped: true
},
results: results.map((entry) => ({
title: entry.title ? wrapWebContent(entry.title, "web_search") : "",
url: entry.url,
snippets: entry.snippets.map((snippet) => wrapWebContent(snippet, "web_search")),
siteName: entry.siteName
})),
sources
};
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
logBraveHttp(diagnostics, "cache write", {
mode: "llm-context",
query,
cacheKey,
ttlMs: cacheTtlMs,
count: results.length
});
return payload;
}
const results = await runBraveWebSearch({
baseUrl: braveBaseUrl,
endpointMode: braveEndpointMode,
query,
count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
apiKey,
timeoutSeconds,
diagnostics,
country: country ?? void 0,
search_lang: normalizedLanguage.search_lang,
ui_lang: normalizedLanguage.ui_lang,
freshness,
dateAfter,
dateBefore
});
const payload = {
query,
provider: "brave",
count: results.length,
tookMs: Date.now() - start,
externalContent: {
untrusted: true,
source: "web_search",
provider: "brave",
wrapped: true
},
results
};
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
logBraveHttp(diagnostics, "cache write", {
mode: "web",
query,
cacheKey,
ttlMs: cacheTtlMs,
count: results.length
});
return payload;
}
//#endregion
export { executeBraveSearch };
@@ -0,0 +1,18 @@
import { t as createBraveWebSearchProvider } from "./brave-web-search-provider-6mNi77fe.js";
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
//#region extensions/brave/index.ts
/**
* Brave Search plugin entry. It registers the Brave web-search provider and
* keeps runtime HTTP execution lazy.
*/
/** Plugin entry for Brave Search. */
var brave_default = definePluginEntry({
id: "brave",
name: "Brave Plugin",
description: "Bundled Brave plugin",
register(api) {
api.registerWebSearchProvider(createBraveWebSearchProvider());
}
});
//#endregion
export { brave_default as default };
@@ -0,0 +1,11 @@
import { buildBraveWebSearchProviderBase } from "./web-search-shared.js";
//#region extensions/brave/web-search-contract-api.ts
/** Create the Brave provider descriptor for contract checks. */
function createBraveWebSearchProvider() {
return {
...buildBraveWebSearchProviderBase(),
createTool: () => null
};
}
//#endregion
export { createBraveWebSearchProvider };
@@ -0,0 +1,2 @@
import { t as createBraveWebSearchProvider } from "./brave-web-search-provider-6mNi77fe.js";
export { createBraveWebSearchProvider };
@@ -0,0 +1,58 @@
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-config-contract";
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
//#region extensions/brave/web-search-shared.ts
/**
* Shared Brave Search provider metadata and credential lookup. Contract tests
* and runtime provider creation both use this lightweight descriptor.
*/
/** Canonical config path for the Brave Search API key. */
const BRAVE_CREDENTIAL_PATH = "plugins.entries.brave.config.webSearch.apiKey";
/** Resolve legacy top-level Brave credentials from old web-search config. */
function resolveLegacyTopLevelBraveCredential(config) {
if (!isRecord(config)) return;
const tools = isRecord(config.tools) ? config.tools : void 0;
const web = isRecord(tools?.web) ? tools.web : void 0;
const search = isRecord(web?.search) ? web.search : void 0;
if (!search || !("apiKey" in search)) return;
return {
path: "tools.web.search.apiKey",
value: search.apiKey
};
}
function resolveBraveWebSearchPluginConfig(config) {
if (!isRecord(config)) return;
const plugins = isRecord(config.plugins) ? config.plugins : void 0;
const entries = isRecord(plugins?.entries) ? plugins.entries : void 0;
const entry = isRecord(entries?.brave) ? entries.brave : void 0;
const pluginConfig = isRecord(entry?.config) ? entry.config : void 0;
return isRecord(pluginConfig?.webSearch) ? pluginConfig.webSearch : void 0;
}
/** Resolve Brave credentials from current plugin config or legacy fallback. */
function resolveConfiguredBraveCredential(config) {
return resolveBraveWebSearchPluginConfig(config)?.apiKey ?? resolveLegacyTopLevelBraveCredential(config)?.value;
}
/** Build the common Brave provider metadata without the runtime tool executor. */
function buildBraveWebSearchProviderBase() {
return {
id: "brave",
label: "Brave Search",
hint: "Structured results · country/language/time filters",
onboardingScopes: ["text-inference"],
credentialLabel: "Brave Search API key",
envVars: ["BRAVE_API_KEY"],
placeholder: "BSA...",
signupUrl: "https://brave.com/search/api/",
docsUrl: "https://docs.openclaw.ai/tools/brave-search",
autoDetectOrder: 10,
credentialPath: BRAVE_CREDENTIAL_PATH,
...createWebSearchProviderContractFields({
credentialPath: BRAVE_CREDENTIAL_PATH,
searchCredential: { type: "top-level" },
configuredCredential: { pluginId: "brave" }
}),
getConfiguredCredentialValue: resolveConfiguredBraveCredential,
getConfiguredCredentialFallback: resolveLegacyTopLevelBraveCredential
};
}
//#endregion
export { BRAVE_CREDENTIAL_PATH, buildBraveWebSearchProviderBase, resolveConfiguredBraveCredential, resolveLegacyTopLevelBraveCredential };
@@ -0,0 +1 @@
/home/starmen1265/.npm-global/lib/node_modules/openclaw
@@ -0,0 +1,12 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.6.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/brave-plugin",
"version": "2026.6.10"
}
}
}
@@ -0,0 +1,61 @@
{
"id": "brave",
"name": "Brave",
"description": "OpenClaw Brave Search provider plugin for web search.",
"activation": {
"onStartup": false
},
"setup": {
"providers": [
{
"id": "brave",
"authMethods": ["api-key"],
"envVars": ["BRAVE_API_KEY"]
}
]
},
"uiHints": {
"webSearch.apiKey": {
"label": "Brave Search API Key",
"help": "Brave Search API key (fallback: BRAVE_API_KEY env var).",
"sensitive": true,
"placeholder": "BSA..."
},
"webSearch.mode": {
"label": "Brave Search Mode",
"help": "Brave Search mode: web or llm-context."
},
"webSearch.baseUrl": {
"label": "Brave Search Base URL",
"help": "Optional Brave-compatible API base URL for trusted proxies. Defaults to https://api.search.brave.com."
}
},
"contracts": {
"webSearchProviders": ["brave"]
},
"configContracts": {
"compatibilityRuntimePaths": ["tools.web.search.apiKey"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"webSearch": {
"type": "object",
"additionalProperties": false,
"properties": {
"apiKey": {
"type": ["string", "object"]
},
"mode": {
"type": "string",
"enum": ["web", "llm-context"]
},
"baseUrl": {
"type": ["string", "object"]
}
}
}
}
}
}
@@ -0,0 +1,49 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.6.10",
"description": "OpenClaw Brave Search provider plugin for web search.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"openclaw": {
"extensions": [
"./index.ts"
],
"install": {
"npmSpec": "@openclaw/brave-plugin",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10",
"allowInvalidConfigRecovery": true
},
"compat": {
"pluginApi": ">=2026.6.10"
},
"build": {
"openclawVersion": "2026.6.10"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
},
"runtimeExtensions": [
"./dist/index.js"
]
},
"files": [
"dist/**",
"openclaw.plugin.json",
"npm-shrinkwrap.json",
"README.md"
],
"peerDependencies": {
"openclaw": ">=2026.6.10"
},
"peerDependenciesMeta": {
"openclaw": {
"optional": true
}
},
"bundledDependencies": []
}
@@ -0,0 +1,26 @@
{
"name": "openclaw-brave-plugin-11fe9e3aa3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@openclaw/brave-plugin": "2026.6.10"
}
},
"node_modules/@openclaw/brave-plugin": {
"version": "2026.6.10",
"resolved": "https://registry.npmjs.org/@openclaw/brave-plugin/-/brave-plugin-2026.6.10.tgz",
"integrity": "sha512-DDRnb4reL99O8kbISNbRFyk/xoUPYHsXG3UGikKAsVs+zIldYYA0hY0d3Z2aWoE+0vfda27mJUByCo7Xr15qdw==",
"hasShrinkwrap": true,
"peerDependencies": {
"openclaw": ">=2026.6.10"
},
"peerDependenciesMeta": {
"openclaw": {
"optional": true
}
}
}
}
}
@@ -0,0 +1,68 @@
{
"private": true,
"dependencies": {
"@openclaw/brave-plugin": "2026.6.10"
},
"overrides": {
"@anthropic-ai/sdk": "0.100.1",
"@opentelemetry/core": "2.8.0",
"@aws-sdk/core": "3.974.13",
"@aws-sdk/credential-provider-node": "3.972.52",
"@aws-sdk/xml-builder": "3.972.25",
"hono": "4.12.25",
"@hono/node-server": "1.19.14",
"axios": "1.16.0",
"fast-uri": "3.1.2",
"follow-redirects": "1.16.0",
"defu": "6.1.5",
"fast-xml-parser": "5.7.0",
"request": "npm:@cypress/request@3.0.10",
"request-promise": "npm:@cypress/request-promise@5.0.0",
"basic-ftp": "6.0.1",
"file-type": "22.0.1",
"form-data": "2.5.6",
"ip-address": "10.2.0",
"minimatch": "10.2.5",
"path-to-regexp": "8.4.0",
"qs": "6.15.2",
"node-domexception": "npm:@nolyfill/domexception@1.0.28",
"typebox": "1.1.39",
"tar": "7.5.16",
"tough-cookie": "4.1.3",
"yauzl": "3.2.1",
"protobufjs": "7.6.3",
"uuid": "14.0.0"
},
"openclaw": {
"managedOverrides": [
"@anthropic-ai/sdk",
"@aws-sdk/core",
"@aws-sdk/credential-provider-node",
"@aws-sdk/xml-builder",
"@hono/node-server",
"@opentelemetry/core",
"axios",
"basic-ftp",
"defu",
"fast-uri",
"fast-xml-parser",
"file-type",
"follow-redirects",
"form-data",
"hono",
"ip-address",
"minimatch",
"node-domexception",
"path-to-regexp",
"protobufjs",
"qs",
"request",
"request-promise",
"tar",
"tough-cookie",
"typebox",
"uuid",
"yauzl"
]
}
}