@gakr-gakr/brave-plugin 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ import { isDiagnosticFlagEnabled } from "autobot/plugin-sdk/diagnostic-runtime";
2
+ import { createWebSearchProviderContractFields } from "autobot/plugin-sdk/provider-web-search-config-contract";
3
+ //#region extensions/brave/src/brave-web-search-provider.ts
4
+ const BRAVE_CREDENTIAL_PATH = "plugins.entries.brave.config.webSearch.apiKey";
5
+ let braveWebSearchRuntimePromise;
6
+ function loadBraveWebSearchRuntime() {
7
+ braveWebSearchRuntimePromise ??= import("./brave-web-search-provider.runtime-BYbqFmsw.js");
8
+ return braveWebSearchRuntimePromise;
9
+ }
10
+ const BraveSearchSchema = {
11
+ type: "object",
12
+ properties: {
13
+ query: {
14
+ type: "string",
15
+ description: "Search query string."
16
+ },
17
+ count: {
18
+ type: "number",
19
+ description: "Number of results to return (1-10).",
20
+ minimum: 1,
21
+ maximum: 10
22
+ },
23
+ country: {
24
+ type: "string",
25
+ description: "2-letter country code for region-specific results (e.g., 'DE', 'US', 'ALL'). Default: 'US'."
26
+ },
27
+ language: {
28
+ type: "string",
29
+ description: "ISO 639-1 language code for results (e.g., 'en', 'de', 'fr')."
30
+ },
31
+ freshness: {
32
+ type: "string",
33
+ description: "Filter by time: 'day' (24h), 'week', 'month', or 'year'."
34
+ },
35
+ date_after: {
36
+ type: "string",
37
+ description: "Only results published after this date (YYYY-MM-DD)."
38
+ },
39
+ date_before: {
40
+ type: "string",
41
+ description: "Only results published before this date (YYYY-MM-DD)."
42
+ },
43
+ search_lang: {
44
+ type: "string",
45
+ description: "Brave language code for search results (e.g., 'en', 'de', 'en-gb', 'zh-hans', 'zh-hant', 'pt-br')."
46
+ },
47
+ ui_lang: {
48
+ type: "string",
49
+ description: "Locale code for UI elements in language-region format (e.g., 'en-US', 'de-DE', 'fr-FR', 'tr-TR'). Must include region subtag."
50
+ }
51
+ }
52
+ };
53
+ function isRecord(value) {
54
+ return typeof value === "object" && value !== null && !Array.isArray(value);
55
+ }
56
+ function resolveProviderWebSearchPluginConfig(config, pluginId) {
57
+ if (!isRecord(config)) return;
58
+ const plugins = isRecord(config.plugins) ? config.plugins : void 0;
59
+ const entries = isRecord(plugins?.entries) ? plugins.entries : void 0;
60
+ const entry = isRecord(entries?.[pluginId]) ? entries[pluginId] : void 0;
61
+ const pluginConfig = isRecord(entry?.config) ? entry.config : void 0;
62
+ return isRecord(pluginConfig?.webSearch) ? pluginConfig.webSearch : void 0;
63
+ }
64
+ function resolveLegacyTopLevelBraveCredential(config) {
65
+ if (!isRecord(config)) return;
66
+ const tools = isRecord(config.tools) ? config.tools : void 0;
67
+ const web = isRecord(tools?.web) ? tools.web : void 0;
68
+ const search = isRecord(web?.search) ? web.search : void 0;
69
+ if (!search || !("apiKey" in search)) return;
70
+ return {
71
+ path: "tools.web.search.apiKey",
72
+ value: search.apiKey
73
+ };
74
+ }
75
+ function resolveConfiguredBraveCredential(config) {
76
+ return resolveProviderWebSearchPluginConfig(config, "brave")?.apiKey ?? resolveLegacyTopLevelBraveCredential(config)?.value;
77
+ }
78
+ function mergeScopedSearchConfig(searchConfig, key, pluginConfig, options) {
79
+ if (!pluginConfig) return searchConfig;
80
+ const currentScoped = isRecord(searchConfig?.[key]) ? searchConfig?.[key] : {};
81
+ const next = {
82
+ ...searchConfig,
83
+ [key]: {
84
+ ...currentScoped,
85
+ ...pluginConfig
86
+ }
87
+ };
88
+ if (options?.mirrorApiKeyToTopLevel && pluginConfig.apiKey !== void 0) next.apiKey = pluginConfig.apiKey;
89
+ return next;
90
+ }
91
+ function resolveBraveMode(searchConfig) {
92
+ return (isRecord(searchConfig?.brave) ? searchConfig.brave : void 0)?.mode === "llm-context" ? "llm-context" : "web";
93
+ }
94
+ function createBraveToolDefinition(searchConfig, config) {
95
+ const braveMode = resolveBraveMode(searchConfig);
96
+ const diagnosticsEnabled = isDiagnosticFlagEnabled("brave.http", config);
97
+ return {
98
+ 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.",
99
+ parameters: BraveSearchSchema,
100
+ execute: async (args) => {
101
+ const { executeBraveSearch } = await loadBraveWebSearchRuntime();
102
+ return await executeBraveSearch(args, searchConfig, { diagnosticsEnabled });
103
+ }
104
+ };
105
+ }
106
+ function createBraveWebSearchProvider() {
107
+ return {
108
+ id: "brave",
109
+ label: "Brave Search",
110
+ hint: "Structured results · country/language/time filters",
111
+ onboardingScopes: ["text-inference"],
112
+ credentialLabel: "Brave Search API key",
113
+ envVars: ["BRAVE_API_KEY"],
114
+ placeholder: "BSA...",
115
+ signupUrl: "https://brave.com/search/api/",
116
+ docsUrl: "https://docs.openclaw.ai/tools/brave-search",
117
+ autoDetectOrder: 10,
118
+ credentialPath: BRAVE_CREDENTIAL_PATH,
119
+ ...createWebSearchProviderContractFields({
120
+ credentialPath: BRAVE_CREDENTIAL_PATH,
121
+ searchCredential: { type: "top-level" },
122
+ configuredCredential: { pluginId: "brave" }
123
+ }),
124
+ getConfiguredCredentialValue: resolveConfiguredBraveCredential,
125
+ getConfiguredCredentialFallback: resolveLegacyTopLevelBraveCredential,
126
+ createTool: (ctx) => createBraveToolDefinition(mergeScopedSearchConfig(ctx.searchConfig, "brave", resolveProviderWebSearchPluginConfig(ctx.config, "brave"), { mirrorApiKeyToTopLevel: true }), ctx.config)
127
+ };
128
+ }
129
+ //#endregion
130
+ export { createBraveWebSearchProvider as t };
@@ -0,0 +1,361 @@
1
+ import { a as resolveBraveMode, i as resolveBraveConfig, n as normalizeBraveCountry, r as normalizeBraveLanguageParams, t as mapBraveLlmContextResults } from "./brave-web-search-provider.shared-Clg4pRUt.js";
2
+ import { readProviderJsonResponse } from "autobot/plugin-sdk/provider-http";
3
+ import { DEFAULT_SEARCH_COUNT, buildSearchCacheKey, formatCliCommand, normalizeFreshness, parseIsoDateRange, readCachedSearchPayload, readConfiguredSecretString, readNumberParam, readProviderEnvValue, readStringParam, resolveSearchCacheTtlMs, resolveSearchCount, resolveSearchTimeoutSeconds, resolveSiteName, withSelfHostedWebSearchEndpoint, withTrustedWebSearchEndpoint, wrapWebContent, writeCachedSearchPayload } from "autobot/plugin-sdk/provider-web-search";
4
+ import { createSubsystemLogger } from "autobot/plugin-sdk/runtime-env";
5
+ import { assertHttpUrlTargetsPrivateNetwork, isBlockedHostnameOrIp, isPrivateIpAddress, resolvePinnedHostnameWithPolicy } from "autobot/plugin-sdk/ssrf-runtime";
6
+ //#region extensions/brave/src/brave-web-search-provider.runtime.ts
7
+ const DEFAULT_BRAVE_BASE_URL = "https://api.search.brave.com";
8
+ const BRAVE_SEARCH_ENDPOINT_PATH = "/res/v1/web/search";
9
+ const BRAVE_LLM_CONTEXT_ENDPOINT_PATH = "/res/v1/llm/context";
10
+ const braveHttpLogger = createSubsystemLogger("brave/http");
11
+ function logBraveHttp(diagnostics, event, meta) {
12
+ if (!diagnostics?.enabled) return;
13
+ braveHttpLogger.info(`brave http ${event}`, meta);
14
+ }
15
+ function describeBraveRequestUrl(url) {
16
+ return {
17
+ url: url.toString(),
18
+ query: url.searchParams.get("q") ?? "",
19
+ params: Object.fromEntries(url.searchParams.entries())
20
+ };
21
+ }
22
+ function resolveBraveApiKey(searchConfig) {
23
+ return readConfiguredSecretString(searchConfig?.apiKey, "tools.web.search.apiKey") ?? readProviderEnvValue(["BRAVE_API_KEY"]);
24
+ }
25
+ function resolveBraveBaseUrl(braveConfig) {
26
+ return readConfiguredSecretString(braveConfig?.baseUrl, "plugins.entries.brave.config.webSearch.baseUrl")?.replace(/\/+$/u, "") || DEFAULT_BRAVE_BASE_URL;
27
+ }
28
+ function buildBraveEndpointUrl(params) {
29
+ const url = new URL(params.baseUrl);
30
+ url.pathname = `${url.pathname.replace(/\/+$/u, "")}${params.endpointPath}`;
31
+ url.search = "";
32
+ return url;
33
+ }
34
+ async function braveEndpointTargetsPrivateNetwork(url) {
35
+ if (isBlockedHostnameOrIp(url.hostname)) return true;
36
+ try {
37
+ return (await resolvePinnedHostnameWithPolicy(url.hostname, { policy: {
38
+ allowPrivateNetwork: true,
39
+ allowRfc2544BenchmarkRange: true
40
+ } })).addresses.every((address) => isPrivateIpAddress(address));
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ async function validateBraveBaseUrl(baseUrl) {
46
+ let parsed;
47
+ try {
48
+ parsed = new URL(baseUrl);
49
+ } catch {
50
+ throw new Error("Brave Search base URL must be a valid http:// or https:// URL.");
51
+ }
52
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("Brave Search base URL must use http:// or https://.");
53
+ if (parsed.protocol === "http:") {
54
+ await assertHttpUrlTargetsPrivateNetwork(parsed.toString(), {
55
+ dangerouslyAllowPrivateNetwork: true,
56
+ errorMessage: "Brave Search HTTP base URL must target a trusted private or loopback host. Use https:// for public hosts."
57
+ });
58
+ return "selfHosted";
59
+ }
60
+ return await braveEndpointTargetsPrivateNetwork(parsed) ? "selfHosted" : "strict";
61
+ }
62
+ function missingBraveKeyPayload() {
63
+ return {
64
+ error: "missing_brave_api_key",
65
+ message: `web_search (brave) needs a Brave Search API key. Run \`${formatCliCommand("autobot 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.`,
66
+ docs: "https://docs.openclaw.ai/tools/web"
67
+ };
68
+ }
69
+ async function runBraveLlmContextSearch(params) {
70
+ const url = buildBraveEndpointUrl({
71
+ baseUrl: params.baseUrl,
72
+ endpointPath: BRAVE_LLM_CONTEXT_ENDPOINT_PATH
73
+ });
74
+ url.searchParams.set("q", params.query);
75
+ if (params.country) url.searchParams.set("country", params.country);
76
+ if (params.search_lang) url.searchParams.set("search_lang", params.search_lang);
77
+ if (params.freshness) url.searchParams.set("freshness", params.freshness);
78
+ else if (params.dateAfter && params.dateBefore) url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
79
+ else if (params.dateAfter) url.searchParams.set("freshness", `${params.dateAfter}to${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
80
+ logBraveHttp(params.diagnostics, "request", {
81
+ mode: "llm-context",
82
+ ...describeBraveRequestUrl(url)
83
+ });
84
+ const startedAt = Date.now();
85
+ return (params.endpointMode === "selfHosted" ? withSelfHostedWebSearchEndpoint : withTrustedWebSearchEndpoint)({
86
+ url: url.toString(),
87
+ timeoutSeconds: params.timeoutSeconds,
88
+ init: {
89
+ method: "GET",
90
+ headers: {
91
+ Accept: "application/json",
92
+ "X-Subscription-Token": params.apiKey
93
+ }
94
+ }
95
+ }, async (response) => {
96
+ logBraveHttp(params.diagnostics, "response", {
97
+ mode: "llm-context",
98
+ status: response.status,
99
+ ok: response.ok,
100
+ durationMs: Date.now() - startedAt
101
+ });
102
+ if (!response.ok) {
103
+ const detail = await response.text();
104
+ throw new Error(`Brave LLM Context API error (${response.status}): ${detail || response.statusText}`);
105
+ }
106
+ const data = await readProviderJsonResponse(response, "Brave LLM Context API error");
107
+ return {
108
+ results: mapBraveLlmContextResults(data),
109
+ sources: data.sources
110
+ };
111
+ });
112
+ }
113
+ async function runBraveWebSearch(params) {
114
+ const url = buildBraveEndpointUrl({
115
+ baseUrl: params.baseUrl,
116
+ endpointPath: BRAVE_SEARCH_ENDPOINT_PATH
117
+ });
118
+ url.searchParams.set("q", params.query);
119
+ url.searchParams.set("count", String(params.count));
120
+ if (params.country) url.searchParams.set("country", params.country);
121
+ if (params.search_lang) url.searchParams.set("search_lang", params.search_lang);
122
+ if (params.ui_lang) url.searchParams.set("ui_lang", params.ui_lang);
123
+ if (params.freshness) url.searchParams.set("freshness", params.freshness);
124
+ else if (params.dateAfter && params.dateBefore) url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
125
+ else if (params.dateAfter) url.searchParams.set("freshness", `${params.dateAfter}to${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
126
+ else if (params.dateBefore) url.searchParams.set("freshness", `1970-01-01to${params.dateBefore}`);
127
+ logBraveHttp(params.diagnostics, "request", {
128
+ mode: "web",
129
+ ...describeBraveRequestUrl(url)
130
+ });
131
+ const startedAt = Date.now();
132
+ return (params.endpointMode === "selfHosted" ? withSelfHostedWebSearchEndpoint : withTrustedWebSearchEndpoint)({
133
+ url: url.toString(),
134
+ timeoutSeconds: params.timeoutSeconds,
135
+ init: {
136
+ method: "GET",
137
+ headers: {
138
+ Accept: "application/json",
139
+ "X-Subscription-Token": params.apiKey
140
+ }
141
+ }
142
+ }, async (response) => {
143
+ logBraveHttp(params.diagnostics, "response", {
144
+ mode: "web",
145
+ status: response.status,
146
+ ok: response.ok,
147
+ durationMs: Date.now() - startedAt
148
+ });
149
+ if (!response.ok) {
150
+ const detail = await response.text();
151
+ throw new Error(`Brave Search API error (${response.status}): ${detail || response.statusText}`);
152
+ }
153
+ const data = await readProviderJsonResponse(response, "Brave Search API error");
154
+ return (Array.isArray(data.web?.results) ? data.web?.results ?? [] : []).map((entry) => {
155
+ const description = entry.description ?? "";
156
+ const title = entry.title ?? "";
157
+ const url = entry.url ?? "";
158
+ return {
159
+ title: title ? wrapWebContent(title, "web_search") : "",
160
+ url,
161
+ description: description ? wrapWebContent(description, "web_search") : "",
162
+ published: entry.age || void 0,
163
+ siteName: resolveSiteName(url) || void 0
164
+ };
165
+ });
166
+ });
167
+ }
168
+ async function executeBraveSearch(args, searchConfig, options) {
169
+ const apiKey = resolveBraveApiKey(searchConfig);
170
+ if (!apiKey) return missingBraveKeyPayload();
171
+ const braveConfig = resolveBraveConfig(searchConfig);
172
+ const braveMode = resolveBraveMode(braveConfig);
173
+ const braveBaseUrl = resolveBraveBaseUrl(braveConfig);
174
+ const braveEndpointMode = await validateBraveBaseUrl(braveBaseUrl);
175
+ const query = readStringParam(args, "query", { required: true });
176
+ const count = readNumberParam(args, "count", { integer: true }) ?? searchConfig?.maxResults ?? void 0;
177
+ const country = normalizeBraveCountry(readStringParam(args, "country"));
178
+ const language = readStringParam(args, "language");
179
+ const search_lang = readStringParam(args, "search_lang");
180
+ const ui_lang = readStringParam(args, "ui_lang");
181
+ const normalizedLanguage = normalizeBraveLanguageParams({
182
+ search_lang: search_lang || language,
183
+ ui_lang
184
+ });
185
+ if (normalizedLanguage.invalidField === "search_lang") return {
186
+ error: "invalid_search_lang",
187
+ message: "search_lang must be a Brave-supported language code like 'en', 'en-gb', 'zh-hans', or 'zh-hant'.",
188
+ docs: "https://docs.openclaw.ai/tools/web"
189
+ };
190
+ if (normalizedLanguage.invalidField === "ui_lang") return {
191
+ error: "invalid_ui_lang",
192
+ message: "ui_lang must be a language-region locale like 'en-US'.",
193
+ docs: "https://docs.openclaw.ai/tools/web"
194
+ };
195
+ if (normalizedLanguage.ui_lang && braveMode === "llm-context") return {
196
+ error: "unsupported_ui_lang",
197
+ message: "ui_lang is not supported by Brave llm-context mode. Remove ui_lang or use Brave web mode for locale-based UI hints.",
198
+ docs: "https://docs.openclaw.ai/tools/web"
199
+ };
200
+ const rawFreshness = readStringParam(args, "freshness");
201
+ const freshness = rawFreshness ? normalizeFreshness(rawFreshness, "brave") : void 0;
202
+ if (rawFreshness && !freshness) return {
203
+ error: "invalid_freshness",
204
+ message: "freshness must be day, week, month, or year.",
205
+ docs: "https://docs.openclaw.ai/tools/web"
206
+ };
207
+ const rawDateAfter = readStringParam(args, "date_after");
208
+ const rawDateBefore = readStringParam(args, "date_before");
209
+ if (rawFreshness && (rawDateAfter || rawDateBefore)) return {
210
+ error: "conflicting_time_filters",
211
+ message: "freshness and date_after/date_before cannot be used together. Use either freshness (day/week/month/year) or a date range (date_after/date_before), not both.",
212
+ docs: "https://docs.openclaw.ai/tools/web"
213
+ };
214
+ const parsedDateRange = parseIsoDateRange({
215
+ rawDateAfter,
216
+ rawDateBefore,
217
+ invalidDateAfterMessage: "date_after must be YYYY-MM-DD format.",
218
+ invalidDateBeforeMessage: "date_before must be YYYY-MM-DD format.",
219
+ invalidDateRangeMessage: "date_after must be before date_before."
220
+ });
221
+ if ("error" in parsedDateRange) return parsedDateRange;
222
+ const { dateAfter, dateBefore } = parsedDateRange;
223
+ if (braveMode === "llm-context") {
224
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
225
+ if (dateAfter && !dateBefore && dateAfter > today) return {
226
+ error: "invalid_date_range",
227
+ message: "date_after cannot be in the future for Brave llm-context mode.",
228
+ docs: "https://docs.openclaw.ai/tools/web"
229
+ };
230
+ if (dateBefore && !dateAfter) return {
231
+ error: "unsupported_date_filter",
232
+ message: "Brave llm-context mode requires date_after when date_before is set. Use a bounded date range or freshness.",
233
+ docs: "https://docs.openclaw.ai/tools/web"
234
+ };
235
+ }
236
+ const llmContextDateEnd = braveMode === "llm-context" && dateAfter ? dateBefore ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) : dateBefore;
237
+ const cacheKey = buildSearchCacheKey(braveMode === "llm-context" ? [
238
+ "brave",
239
+ braveMode,
240
+ braveBaseUrl,
241
+ query,
242
+ country,
243
+ normalizedLanguage.search_lang,
244
+ freshness,
245
+ dateAfter,
246
+ llmContextDateEnd
247
+ ] : [
248
+ "brave",
249
+ braveMode,
250
+ braveBaseUrl,
251
+ query,
252
+ resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
253
+ country,
254
+ normalizedLanguage.search_lang,
255
+ normalizedLanguage.ui_lang,
256
+ freshness,
257
+ dateAfter,
258
+ dateBefore
259
+ ]);
260
+ const diagnostics = { enabled: options?.diagnosticsEnabled === true };
261
+ const cached = readCachedSearchPayload(cacheKey);
262
+ if (cached) {
263
+ logBraveHttp(diagnostics, "cache hit", {
264
+ mode: braveMode,
265
+ query,
266
+ cacheKey
267
+ });
268
+ return cached;
269
+ }
270
+ logBraveHttp(diagnostics, "cache miss", {
271
+ mode: braveMode,
272
+ query,
273
+ cacheKey
274
+ });
275
+ const start = Date.now();
276
+ const timeoutSeconds = resolveSearchTimeoutSeconds(searchConfig);
277
+ const cacheTtlMs = resolveSearchCacheTtlMs(searchConfig);
278
+ if (braveMode === "llm-context") {
279
+ const { results, sources } = await runBraveLlmContextSearch({
280
+ baseUrl: braveBaseUrl,
281
+ endpointMode: braveEndpointMode,
282
+ query,
283
+ apiKey,
284
+ timeoutSeconds,
285
+ diagnostics,
286
+ country: country ?? void 0,
287
+ search_lang: normalizedLanguage.search_lang,
288
+ freshness,
289
+ dateAfter,
290
+ dateBefore
291
+ });
292
+ const payload = {
293
+ query,
294
+ provider: "brave",
295
+ mode: "llm-context",
296
+ count: results.length,
297
+ tookMs: Date.now() - start,
298
+ externalContent: {
299
+ untrusted: true,
300
+ source: "web_search",
301
+ provider: "brave",
302
+ wrapped: true
303
+ },
304
+ results: results.map((entry) => ({
305
+ title: entry.title ? wrapWebContent(entry.title, "web_search") : "",
306
+ url: entry.url,
307
+ snippets: entry.snippets.map((snippet) => wrapWebContent(snippet, "web_search")),
308
+ siteName: entry.siteName
309
+ })),
310
+ sources
311
+ };
312
+ writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
313
+ logBraveHttp(diagnostics, "cache write", {
314
+ mode: "llm-context",
315
+ query,
316
+ cacheKey,
317
+ ttlMs: cacheTtlMs,
318
+ count: results.length
319
+ });
320
+ return payload;
321
+ }
322
+ const results = await runBraveWebSearch({
323
+ baseUrl: braveBaseUrl,
324
+ endpointMode: braveEndpointMode,
325
+ query,
326
+ count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
327
+ apiKey,
328
+ timeoutSeconds,
329
+ diagnostics,
330
+ country: country ?? void 0,
331
+ search_lang: normalizedLanguage.search_lang,
332
+ ui_lang: normalizedLanguage.ui_lang,
333
+ freshness,
334
+ dateAfter,
335
+ dateBefore
336
+ });
337
+ const payload = {
338
+ query,
339
+ provider: "brave",
340
+ count: results.length,
341
+ tookMs: Date.now() - start,
342
+ externalContent: {
343
+ untrusted: true,
344
+ source: "web_search",
345
+ provider: "brave",
346
+ wrapped: true
347
+ },
348
+ results
349
+ };
350
+ writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
351
+ logBraveHttp(diagnostics, "cache write", {
352
+ mode: "web",
353
+ query,
354
+ cacheKey,
355
+ ttlMs: cacheTtlMs,
356
+ count: results.length
357
+ });
358
+ return payload;
359
+ }
360
+ //#endregion
361
+ export { executeBraveSearch };
@@ -0,0 +1,172 @@
1
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "autobot/plugin-sdk/string-coerce-runtime";
2
+ //#region extensions/brave/src/brave-web-search-provider.shared.ts
3
+ const BRAVE_COUNTRY_CODES = new Set([
4
+ "AR",
5
+ "AU",
6
+ "AT",
7
+ "BE",
8
+ "BR",
9
+ "CA",
10
+ "CL",
11
+ "DK",
12
+ "FI",
13
+ "FR",
14
+ "DE",
15
+ "GR",
16
+ "HK",
17
+ "IN",
18
+ "ID",
19
+ "IT",
20
+ "JP",
21
+ "KR",
22
+ "MY",
23
+ "MX",
24
+ "NL",
25
+ "NZ",
26
+ "NO",
27
+ "CN",
28
+ "PL",
29
+ "PT",
30
+ "PH",
31
+ "RU",
32
+ "SA",
33
+ "ZA",
34
+ "ES",
35
+ "SE",
36
+ "CH",
37
+ "TW",
38
+ "TR",
39
+ "GB",
40
+ "US",
41
+ "ALL"
42
+ ]);
43
+ const BRAVE_SEARCH_LANG_CODES = new Set([
44
+ "ar",
45
+ "eu",
46
+ "bn",
47
+ "bg",
48
+ "ca",
49
+ "zh-hans",
50
+ "zh-hant",
51
+ "hr",
52
+ "cs",
53
+ "da",
54
+ "nl",
55
+ "en",
56
+ "en-gb",
57
+ "et",
58
+ "fi",
59
+ "fr",
60
+ "gl",
61
+ "de",
62
+ "el",
63
+ "gu",
64
+ "he",
65
+ "hi",
66
+ "hu",
67
+ "is",
68
+ "it",
69
+ "jp",
70
+ "kn",
71
+ "ko",
72
+ "lv",
73
+ "lt",
74
+ "ms",
75
+ "ml",
76
+ "mr",
77
+ "nb",
78
+ "pl",
79
+ "pt-br",
80
+ "pt-pt",
81
+ "pa",
82
+ "ro",
83
+ "ru",
84
+ "sr",
85
+ "sk",
86
+ "sl",
87
+ "es",
88
+ "sv",
89
+ "ta",
90
+ "te",
91
+ "th",
92
+ "tr",
93
+ "uk",
94
+ "vi"
95
+ ]);
96
+ const BRAVE_SEARCH_LANG_ALIASES = {
97
+ ja: "jp",
98
+ zh: "zh-hans",
99
+ "zh-cn": "zh-hans",
100
+ "zh-hk": "zh-hant",
101
+ "zh-sg": "zh-hans",
102
+ "zh-tw": "zh-hant"
103
+ };
104
+ const BRAVE_UI_LANG_LOCALE = /^([a-z]{2})-([a-z]{2})$/i;
105
+ function normalizeBraveSearchLang(value) {
106
+ if (!value) return;
107
+ const trimmed = value.trim();
108
+ if (!trimmed) return;
109
+ const lower = normalizeLowercaseStringOrEmpty(trimmed);
110
+ const canonical = BRAVE_SEARCH_LANG_ALIASES[lower] ?? lower;
111
+ if (!BRAVE_SEARCH_LANG_CODES.has(canonical)) return;
112
+ return canonical;
113
+ }
114
+ function normalizeBraveCountry(value) {
115
+ if (!value) return;
116
+ const trimmed = value.trim();
117
+ if (!trimmed) return;
118
+ const canonical = trimmed.toUpperCase();
119
+ return BRAVE_COUNTRY_CODES.has(canonical) ? canonical : "ALL";
120
+ }
121
+ function normalizeBraveUiLang(value) {
122
+ if (!value) return;
123
+ const trimmed = value.trim();
124
+ if (!trimmed) return;
125
+ const match = trimmed.match(BRAVE_UI_LANG_LOCALE);
126
+ if (!match) return;
127
+ const [, language, region] = match;
128
+ return `${normalizeLowercaseStringOrEmpty(language)}-${region.toUpperCase()}`;
129
+ }
130
+ function resolveBraveConfig(searchConfig) {
131
+ const brave = searchConfig?.brave;
132
+ return brave && typeof brave === "object" && !Array.isArray(brave) ? brave : {};
133
+ }
134
+ function resolveBraveMode(brave) {
135
+ return brave?.mode === "llm-context" ? "llm-context" : "web";
136
+ }
137
+ function normalizeBraveLanguageParams(params) {
138
+ const rawSearchLang = normalizeOptionalString(params.search_lang);
139
+ const rawUiLang = normalizeOptionalString(params.ui_lang);
140
+ let searchLangCandidate = rawSearchLang;
141
+ let uiLangCandidate = rawUiLang;
142
+ if (normalizeBraveUiLang(rawSearchLang) && normalizeBraveSearchLang(rawUiLang)) {
143
+ searchLangCandidate = rawUiLang;
144
+ uiLangCandidate = rawSearchLang;
145
+ }
146
+ const search_lang = normalizeBraveSearchLang(searchLangCandidate);
147
+ if (searchLangCandidate && !search_lang) return { invalidField: "search_lang" };
148
+ const ui_lang = normalizeBraveUiLang(uiLangCandidate);
149
+ if (uiLangCandidate && !ui_lang) return { invalidField: "ui_lang" };
150
+ return {
151
+ search_lang,
152
+ ui_lang
153
+ };
154
+ }
155
+ function resolveSiteName(url) {
156
+ if (!url) return;
157
+ try {
158
+ return new URL(url).hostname;
159
+ } catch {
160
+ return;
161
+ }
162
+ }
163
+ function mapBraveLlmContextResults(data) {
164
+ return (Array.isArray(data.grounding?.generic) ? data.grounding.generic : []).map((entry) => ({
165
+ url: entry.url ?? "",
166
+ title: entry.title ?? "",
167
+ snippets: (entry.snippets ?? []).filter((snippet) => typeof snippet === "string" && snippet.length > 0),
168
+ siteName: resolveSiteName(entry.url) || void 0
169
+ }));
170
+ }
171
+ //#endregion
172
+ export { resolveBraveMode as a, resolveBraveConfig as i, normalizeBraveCountry as n, normalizeBraveLanguageParams as r, mapBraveLlmContextResults as t };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import { t as createBraveWebSearchProvider } from "./brave-web-search-provider-CW80aaFg.js";
2
+ import { definePluginEntry } from "autobot/plugin-sdk/plugin-entry";
3
+ //#region extensions/brave/index.ts
4
+ var brave_default = definePluginEntry({
5
+ id: "brave",
6
+ name: "Brave Plugin",
7
+ description: "Bundled Brave plugin",
8
+ register(api) {
9
+ api.registerWebSearchProvider(createBraveWebSearchProvider());
10
+ }
11
+ });
12
+ //#endregion
13
+ export { brave_default as default };
@@ -0,0 +1,10 @@
1
+ import { a as resolveBraveMode, n as normalizeBraveCountry, r as normalizeBraveLanguageParams, t as mapBraveLlmContextResults } from "./brave-web-search-provider.shared-Clg4pRUt.js";
2
+ //#region extensions/brave/test-api.ts
3
+ const testing = {
4
+ normalizeBraveCountry,
5
+ normalizeBraveLanguageParams,
6
+ resolveBraveMode,
7
+ mapBraveLlmContextResults
8
+ };
9
+ //#endregion
10
+ export { testing as __testing, testing };