自动同步: 2026-07-01 02:10
This commit is contained in:
Generated
Vendored
+81
@@ -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 };
|
||||
Generated
Vendored
+529
@@ -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 };
|
||||
Generated
Vendored
+18
@@ -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 };
|
||||
Generated
Vendored
+11
@@ -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 };
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
import { t as createBraveWebSearchProvider } from "./brave-web-search-provider-6mNi77fe.js";
|
||||
export { createBraveWebSearchProvider };
|
||||
Generated
Vendored
+58
@@ -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 };
|
||||
Reference in New Issue
Block a user