@openclaw/brave-plugin 2026.5.1-beta.1 → 2026.5.2-beta.2
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.
- package/openclaw.plugin.json +7 -0
- package/package.json +3 -3
- package/src/brave-web-search-provider.runtime.ts +232 -36
- package/src/brave-web-search-provider.shared.ts +1 -0
- package/src/brave-web-search-provider.test.ts +453 -0
- package/src/brave-web-search-provider.ts +6 -2
- package/web-search-contract-api.ts +1 -1
- package/dist/.boundary-tsc.stamp +0 -1
- package/dist/.boundary-tsc.tsbuildinfo +0 -1
package/openclaw.plugin.json
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"webSearch.mode": {
|
|
17
17
|
"label": "Brave Search Mode",
|
|
18
18
|
"help": "Brave Search mode: web or llm-context."
|
|
19
|
+
},
|
|
20
|
+
"webSearch.baseUrl": {
|
|
21
|
+
"label": "Brave Search Base URL",
|
|
22
|
+
"help": "Optional Brave-compatible API base URL for trusted proxies. Defaults to https://api.search.brave.com."
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"contracts": {
|
|
@@ -38,6 +42,9 @@
|
|
|
38
42
|
"mode": {
|
|
39
43
|
"type": "string",
|
|
40
44
|
"enum": ["web", "llm-context"]
|
|
45
|
+
},
|
|
46
|
+
"baseUrl": {
|
|
47
|
+
"type": ["string", "object"]
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/brave-plugin",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.2-beta.2",
|
|
4
4
|
"description": "OpenClaw Brave plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"minHostVersion": ">=2026.4.10"
|
|
21
21
|
},
|
|
22
22
|
"compat": {
|
|
23
|
-
"pluginApi": ">=2026.
|
|
23
|
+
"pluginApi": ">=2026.5.2-beta.2"
|
|
24
24
|
},
|
|
25
25
|
"build": {
|
|
26
|
-
"openclawVersion": "2026.5.
|
|
26
|
+
"openclawVersion": "2026.5.2-beta.2"
|
|
27
27
|
},
|
|
28
28
|
"release": {
|
|
29
29
|
"publishToClawHub": true,
|
|
@@ -14,10 +14,18 @@ import {
|
|
|
14
14
|
resolveSearchCount,
|
|
15
15
|
resolveSearchTimeoutSeconds,
|
|
16
16
|
resolveSiteName,
|
|
17
|
+
withSelfHostedWebSearchEndpoint,
|
|
17
18
|
withTrustedWebSearchEndpoint,
|
|
18
19
|
wrapWebContent,
|
|
19
20
|
writeCachedSearchPayload,
|
|
20
21
|
} from "openclaw/plugin-sdk/provider-web-search";
|
|
22
|
+
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
23
|
+
import {
|
|
24
|
+
assertHttpUrlTargetsPrivateNetwork,
|
|
25
|
+
isBlockedHostnameOrIp,
|
|
26
|
+
isPrivateIpAddress,
|
|
27
|
+
resolvePinnedHostnameWithPolicy,
|
|
28
|
+
} from "openclaw/plugin-sdk/ssrf-runtime";
|
|
21
29
|
import {
|
|
22
30
|
type BraveLlmContextResponse,
|
|
23
31
|
mapBraveLlmContextResults,
|
|
@@ -27,8 +35,11 @@ import {
|
|
|
27
35
|
resolveBraveMode,
|
|
28
36
|
} from "./brave-web-search-provider.shared.js";
|
|
29
37
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
38
|
+
const DEFAULT_BRAVE_BASE_URL = "https://api.search.brave.com";
|
|
39
|
+
const BRAVE_SEARCH_ENDPOINT_PATH = "/res/v1/web/search";
|
|
40
|
+
const BRAVE_LLM_CONTEXT_ENDPOINT_PATH = "/res/v1/llm/context";
|
|
41
|
+
const braveHttpLogger = createSubsystemLogger("brave/http");
|
|
42
|
+
type BraveEndpointMode = "selfHosted" | "strict";
|
|
32
43
|
|
|
33
44
|
type BraveSearchResult = {
|
|
34
45
|
title?: string;
|
|
@@ -43,6 +54,33 @@ type BraveSearchResponse = {
|
|
|
43
54
|
};
|
|
44
55
|
};
|
|
45
56
|
|
|
57
|
+
type BraveHttpDiagnostics = {
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function logBraveHttp(
|
|
62
|
+
diagnostics: BraveHttpDiagnostics | undefined,
|
|
63
|
+
event: string,
|
|
64
|
+
meta?: Record<string, unknown>,
|
|
65
|
+
): void {
|
|
66
|
+
if (!diagnostics?.enabled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
braveHttpLogger.info(`brave http ${event}`, meta);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function describeBraveRequestUrl(url: URL): {
|
|
73
|
+
url: string;
|
|
74
|
+
query: string;
|
|
75
|
+
params: Record<string, string>;
|
|
76
|
+
} {
|
|
77
|
+
return {
|
|
78
|
+
url: url.toString(),
|
|
79
|
+
query: url.searchParams.get("q") ?? "",
|
|
80
|
+
params: Object.fromEntries(url.searchParams.entries()),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
46
84
|
function resolveBraveApiKey(searchConfig?: SearchConfigRecord): string | undefined {
|
|
47
85
|
return (
|
|
48
86
|
readConfiguredSecretString(searchConfig?.apiKey, "tools.web.search.apiKey") ??
|
|
@@ -50,21 +88,83 @@ function resolveBraveApiKey(searchConfig?: SearchConfigRecord): string | undefin
|
|
|
50
88
|
);
|
|
51
89
|
}
|
|
52
90
|
|
|
91
|
+
function resolveBraveBaseUrl(braveConfig: { baseUrl?: unknown } | undefined): string {
|
|
92
|
+
const configured = readConfiguredSecretString(
|
|
93
|
+
braveConfig?.baseUrl,
|
|
94
|
+
"plugins.entries.brave.config.webSearch.baseUrl",
|
|
95
|
+
);
|
|
96
|
+
return configured?.replace(/\/+$/u, "") || DEFAULT_BRAVE_BASE_URL;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildBraveEndpointUrl(params: { baseUrl: string; endpointPath: string }): URL {
|
|
100
|
+
const url = new URL(params.baseUrl);
|
|
101
|
+
const basePath = url.pathname.replace(/\/+$/u, "");
|
|
102
|
+
url.pathname = `${basePath}${params.endpointPath}`;
|
|
103
|
+
url.search = "";
|
|
104
|
+
return url;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function braveEndpointTargetsPrivateNetwork(url: URL): Promise<boolean> {
|
|
108
|
+
if (isBlockedHostnameOrIp(url.hostname)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const pinned = await resolvePinnedHostnameWithPolicy(url.hostname, {
|
|
113
|
+
policy: {
|
|
114
|
+
allowPrivateNetwork: true,
|
|
115
|
+
allowRfc2544BenchmarkRange: true,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
return pinned.addresses.every((address) => isPrivateIpAddress(address));
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function validateBraveBaseUrl(baseUrl: string): Promise<BraveEndpointMode> {
|
|
125
|
+
let parsed: URL;
|
|
126
|
+
try {
|
|
127
|
+
parsed = new URL(baseUrl);
|
|
128
|
+
} catch {
|
|
129
|
+
throw new Error("Brave Search base URL must be a valid http:// or https:// URL.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
133
|
+
throw new Error("Brave Search base URL must use http:// or https://.");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (parsed.protocol === "http:") {
|
|
137
|
+
await assertHttpUrlTargetsPrivateNetwork(parsed.toString(), {
|
|
138
|
+
dangerouslyAllowPrivateNetwork: true,
|
|
139
|
+
errorMessage:
|
|
140
|
+
"Brave Search HTTP base URL must target a trusted private or loopback host. Use https:// for public hosts.",
|
|
141
|
+
});
|
|
142
|
+
return "selfHosted";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (await braveEndpointTargetsPrivateNetwork(parsed)) ? "selfHosted" : "strict";
|
|
146
|
+
}
|
|
147
|
+
|
|
53
148
|
function missingBraveKeyPayload() {
|
|
54
149
|
return {
|
|
55
150
|
error: "missing_brave_api_key",
|
|
56
|
-
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.`,
|
|
151
|
+
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.`,
|
|
57
152
|
docs: "https://docs.openclaw.ai/tools/web",
|
|
58
153
|
};
|
|
59
154
|
}
|
|
60
155
|
|
|
61
156
|
async function runBraveLlmContextSearch(params: {
|
|
157
|
+
baseUrl: string;
|
|
158
|
+
endpointMode: BraveEndpointMode;
|
|
62
159
|
query: string;
|
|
63
160
|
apiKey: string;
|
|
64
161
|
timeoutSeconds: number;
|
|
162
|
+
diagnostics?: BraveHttpDiagnostics;
|
|
65
163
|
country?: string;
|
|
66
164
|
search_lang?: string;
|
|
67
165
|
freshness?: string;
|
|
166
|
+
dateAfter?: string;
|
|
167
|
+
dateBefore?: string;
|
|
68
168
|
}): Promise<{
|
|
69
169
|
results: Array<{
|
|
70
170
|
url: string;
|
|
@@ -74,7 +174,10 @@ async function runBraveLlmContextSearch(params: {
|
|
|
74
174
|
}>;
|
|
75
175
|
sources?: BraveLlmContextResponse["sources"];
|
|
76
176
|
}> {
|
|
77
|
-
const url =
|
|
177
|
+
const url = buildBraveEndpointUrl({
|
|
178
|
+
baseUrl: params.baseUrl,
|
|
179
|
+
endpointPath: BRAVE_LLM_CONTEXT_ENDPOINT_PATH,
|
|
180
|
+
});
|
|
78
181
|
url.searchParams.set("q", params.query);
|
|
79
182
|
if (params.country) {
|
|
80
183
|
url.searchParams.set("country", params.country);
|
|
@@ -84,9 +187,25 @@ async function runBraveLlmContextSearch(params: {
|
|
|
84
187
|
}
|
|
85
188
|
if (params.freshness) {
|
|
86
189
|
url.searchParams.set("freshness", params.freshness);
|
|
190
|
+
} else if (params.dateAfter && params.dateBefore) {
|
|
191
|
+
url.searchParams.set("freshness", `${params.dateAfter}to${params.dateBefore}`);
|
|
192
|
+
} else if (params.dateAfter) {
|
|
193
|
+
url.searchParams.set(
|
|
194
|
+
"freshness",
|
|
195
|
+
`${params.dateAfter}to${new Date().toISOString().slice(0, 10)}`,
|
|
196
|
+
);
|
|
87
197
|
}
|
|
88
198
|
|
|
89
|
-
|
|
199
|
+
logBraveHttp(params.diagnostics, "request", {
|
|
200
|
+
mode: "llm-context",
|
|
201
|
+
...describeBraveRequestUrl(url),
|
|
202
|
+
});
|
|
203
|
+
const startedAt = Date.now();
|
|
204
|
+
const withEndpoint =
|
|
205
|
+
params.endpointMode === "selfHosted"
|
|
206
|
+
? withSelfHostedWebSearchEndpoint
|
|
207
|
+
: withTrustedWebSearchEndpoint;
|
|
208
|
+
return withEndpoint(
|
|
90
209
|
{
|
|
91
210
|
url: url.toString(),
|
|
92
211
|
timeoutSeconds: params.timeoutSeconds,
|
|
@@ -99,6 +218,12 @@ async function runBraveLlmContextSearch(params: {
|
|
|
99
218
|
},
|
|
100
219
|
},
|
|
101
220
|
async (response) => {
|
|
221
|
+
logBraveHttp(params.diagnostics, "response", {
|
|
222
|
+
mode: "llm-context",
|
|
223
|
+
status: response.status,
|
|
224
|
+
ok: response.ok,
|
|
225
|
+
durationMs: Date.now() - startedAt,
|
|
226
|
+
});
|
|
102
227
|
if (!response.ok) {
|
|
103
228
|
const detail = await response.text();
|
|
104
229
|
throw new Error(
|
|
@@ -113,10 +238,13 @@ async function runBraveLlmContextSearch(params: {
|
|
|
113
238
|
}
|
|
114
239
|
|
|
115
240
|
async function runBraveWebSearch(params: {
|
|
241
|
+
baseUrl: string;
|
|
242
|
+
endpointMode: BraveEndpointMode;
|
|
116
243
|
query: string;
|
|
117
244
|
count: number;
|
|
118
245
|
apiKey: string;
|
|
119
246
|
timeoutSeconds: number;
|
|
247
|
+
diagnostics?: BraveHttpDiagnostics;
|
|
120
248
|
country?: string;
|
|
121
249
|
search_lang?: string;
|
|
122
250
|
ui_lang?: string;
|
|
@@ -124,7 +252,10 @@ async function runBraveWebSearch(params: {
|
|
|
124
252
|
dateAfter?: string;
|
|
125
253
|
dateBefore?: string;
|
|
126
254
|
}): Promise<Array<Record<string, unknown>>> {
|
|
127
|
-
const url =
|
|
255
|
+
const url = buildBraveEndpointUrl({
|
|
256
|
+
baseUrl: params.baseUrl,
|
|
257
|
+
endpointPath: BRAVE_SEARCH_ENDPOINT_PATH,
|
|
258
|
+
});
|
|
128
259
|
url.searchParams.set("q", params.query);
|
|
129
260
|
url.searchParams.set("count", String(params.count));
|
|
130
261
|
if (params.country) {
|
|
@@ -149,7 +280,16 @@ async function runBraveWebSearch(params: {
|
|
|
149
280
|
url.searchParams.set("freshness", `1970-01-01to${params.dateBefore}`);
|
|
150
281
|
}
|
|
151
282
|
|
|
152
|
-
|
|
283
|
+
logBraveHttp(params.diagnostics, "request", {
|
|
284
|
+
mode: "web",
|
|
285
|
+
...describeBraveRequestUrl(url),
|
|
286
|
+
});
|
|
287
|
+
const startedAt = Date.now();
|
|
288
|
+
const withEndpoint =
|
|
289
|
+
params.endpointMode === "selfHosted"
|
|
290
|
+
? withSelfHostedWebSearchEndpoint
|
|
291
|
+
: withTrustedWebSearchEndpoint;
|
|
292
|
+
return withEndpoint(
|
|
153
293
|
{
|
|
154
294
|
url: url.toString(),
|
|
155
295
|
timeoutSeconds: params.timeoutSeconds,
|
|
@@ -162,6 +302,12 @@ async function runBraveWebSearch(params: {
|
|
|
162
302
|
},
|
|
163
303
|
},
|
|
164
304
|
async (response) => {
|
|
305
|
+
logBraveHttp(params.diagnostics, "response", {
|
|
306
|
+
mode: "web",
|
|
307
|
+
status: response.status,
|
|
308
|
+
ok: response.ok,
|
|
309
|
+
durationMs: Date.now() - startedAt,
|
|
310
|
+
});
|
|
165
311
|
if (!response.ok) {
|
|
166
312
|
const detail = await response.text();
|
|
167
313
|
throw new Error(
|
|
@@ -190,6 +336,9 @@ async function runBraveWebSearch(params: {
|
|
|
190
336
|
export async function executeBraveSearch(
|
|
191
337
|
args: Record<string, unknown>,
|
|
192
338
|
searchConfig?: SearchConfigRecord,
|
|
339
|
+
options?: {
|
|
340
|
+
diagnosticsEnabled?: boolean;
|
|
341
|
+
},
|
|
193
342
|
): Promise<Record<string, unknown>> {
|
|
194
343
|
const apiKey = resolveBraveApiKey(searchConfig);
|
|
195
344
|
if (!apiKey) {
|
|
@@ -198,6 +347,8 @@ export async function executeBraveSearch(
|
|
|
198
347
|
|
|
199
348
|
const braveConfig = resolveBraveConfig(searchConfig);
|
|
200
349
|
const braveMode = resolveBraveMode(braveConfig);
|
|
350
|
+
const braveBaseUrl = resolveBraveBaseUrl(braveConfig);
|
|
351
|
+
const braveEndpointMode = await validateBraveBaseUrl(braveBaseUrl);
|
|
201
352
|
const query = readStringParam(args, "query", { required: true });
|
|
202
353
|
const count =
|
|
203
354
|
readNumberParam(args, "count", { integer: true }) ?? searchConfig?.maxResults ?? undefined;
|
|
@@ -235,14 +386,6 @@ export async function executeBraveSearch(
|
|
|
235
386
|
}
|
|
236
387
|
|
|
237
388
|
const rawFreshness = readStringParam(args, "freshness");
|
|
238
|
-
if (rawFreshness && braveMode === "llm-context") {
|
|
239
|
-
return {
|
|
240
|
-
error: "unsupported_freshness",
|
|
241
|
-
message:
|
|
242
|
-
"freshness filtering is not supported by Brave llm-context mode. Remove freshness or use Brave web mode.",
|
|
243
|
-
docs: "https://docs.openclaw.ai/tools/web",
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
389
|
const freshness = rawFreshness ? normalizeFreshness(rawFreshness, "brave") : undefined;
|
|
247
390
|
if (rawFreshness && !freshness) {
|
|
248
391
|
return {
|
|
@@ -262,15 +405,6 @@ export async function executeBraveSearch(
|
|
|
262
405
|
docs: "https://docs.openclaw.ai/tools/web",
|
|
263
406
|
};
|
|
264
407
|
}
|
|
265
|
-
if ((rawDateAfter || rawDateBefore) && braveMode === "llm-context") {
|
|
266
|
-
return {
|
|
267
|
-
error: "unsupported_date_filter",
|
|
268
|
-
message:
|
|
269
|
-
"date_after/date_before filtering is not supported by Brave llm-context mode. Use Brave web mode for date filters.",
|
|
270
|
-
docs: "https://docs.openclaw.ai/tools/web",
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
408
|
const parsedDateRange = parseIsoDateRange({
|
|
275
409
|
rawDateAfter,
|
|
276
410
|
rawDateBefore,
|
|
@@ -283,22 +417,62 @@ export async function executeBraveSearch(
|
|
|
283
417
|
}
|
|
284
418
|
|
|
285
419
|
const { dateAfter, dateBefore } = parsedDateRange;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
dateAfter
|
|
296
|
-
|
|
297
|
-
|
|
420
|
+
if (braveMode === "llm-context") {
|
|
421
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
422
|
+
if (dateAfter && !dateBefore && dateAfter > today) {
|
|
423
|
+
return {
|
|
424
|
+
error: "invalid_date_range",
|
|
425
|
+
message: "date_after cannot be in the future for Brave llm-context mode.",
|
|
426
|
+
docs: "https://docs.openclaw.ai/tools/web",
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (dateBefore && !dateAfter) {
|
|
430
|
+
return {
|
|
431
|
+
error: "unsupported_date_filter",
|
|
432
|
+
message:
|
|
433
|
+
"Brave llm-context mode requires date_after when date_before is set. Use a bounded date range or freshness.",
|
|
434
|
+
docs: "https://docs.openclaw.ai/tools/web",
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const llmContextDateEnd =
|
|
439
|
+
braveMode === "llm-context" && dateAfter
|
|
440
|
+
? (dateBefore ?? new Date().toISOString().slice(0, 10))
|
|
441
|
+
: dateBefore;
|
|
442
|
+
const cacheKey = buildSearchCacheKey(
|
|
443
|
+
braveMode === "llm-context"
|
|
444
|
+
? [
|
|
445
|
+
"brave",
|
|
446
|
+
braveMode,
|
|
447
|
+
braveBaseUrl,
|
|
448
|
+
query,
|
|
449
|
+
country,
|
|
450
|
+
normalizedLanguage.search_lang,
|
|
451
|
+
freshness,
|
|
452
|
+
dateAfter,
|
|
453
|
+
llmContextDateEnd,
|
|
454
|
+
]
|
|
455
|
+
: [
|
|
456
|
+
"brave",
|
|
457
|
+
braveMode,
|
|
458
|
+
braveBaseUrl,
|
|
459
|
+
query,
|
|
460
|
+
resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
|
|
461
|
+
country,
|
|
462
|
+
normalizedLanguage.search_lang,
|
|
463
|
+
normalizedLanguage.ui_lang,
|
|
464
|
+
freshness,
|
|
465
|
+
dateAfter,
|
|
466
|
+
dateBefore,
|
|
467
|
+
],
|
|
468
|
+
);
|
|
469
|
+
const diagnostics: BraveHttpDiagnostics = { enabled: options?.diagnosticsEnabled === true };
|
|
298
470
|
const cached = readCachedSearchPayload(cacheKey);
|
|
299
471
|
if (cached) {
|
|
472
|
+
logBraveHttp(diagnostics, "cache hit", { mode: braveMode, query, cacheKey });
|
|
300
473
|
return cached;
|
|
301
474
|
}
|
|
475
|
+
logBraveHttp(diagnostics, "cache miss", { mode: braveMode, query, cacheKey });
|
|
302
476
|
|
|
303
477
|
const start = Date.now();
|
|
304
478
|
const timeoutSeconds = resolveSearchTimeoutSeconds(searchConfig);
|
|
@@ -306,12 +480,17 @@ export async function executeBraveSearch(
|
|
|
306
480
|
|
|
307
481
|
if (braveMode === "llm-context") {
|
|
308
482
|
const { results, sources } = await runBraveLlmContextSearch({
|
|
483
|
+
baseUrl: braveBaseUrl,
|
|
484
|
+
endpointMode: braveEndpointMode,
|
|
309
485
|
query,
|
|
310
486
|
apiKey,
|
|
311
487
|
timeoutSeconds,
|
|
488
|
+
diagnostics,
|
|
312
489
|
country: country ?? undefined,
|
|
313
490
|
search_lang: normalizedLanguage.search_lang,
|
|
314
491
|
freshness,
|
|
492
|
+
dateAfter,
|
|
493
|
+
dateBefore,
|
|
315
494
|
});
|
|
316
495
|
const payload = {
|
|
317
496
|
query,
|
|
@@ -334,14 +513,24 @@ export async function executeBraveSearch(
|
|
|
334
513
|
sources,
|
|
335
514
|
};
|
|
336
515
|
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
|
|
516
|
+
logBraveHttp(diagnostics, "cache write", {
|
|
517
|
+
mode: "llm-context",
|
|
518
|
+
query,
|
|
519
|
+
cacheKey,
|
|
520
|
+
ttlMs: cacheTtlMs,
|
|
521
|
+
count: results.length,
|
|
522
|
+
});
|
|
337
523
|
return payload;
|
|
338
524
|
}
|
|
339
525
|
|
|
340
526
|
const results = await runBraveWebSearch({
|
|
527
|
+
baseUrl: braveBaseUrl,
|
|
528
|
+
endpointMode: braveEndpointMode,
|
|
341
529
|
query,
|
|
342
530
|
count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
|
|
343
531
|
apiKey,
|
|
344
532
|
timeoutSeconds,
|
|
533
|
+
diagnostics,
|
|
345
534
|
country: country ?? undefined,
|
|
346
535
|
search_lang: normalizedLanguage.search_lang,
|
|
347
536
|
ui_lang: normalizedLanguage.ui_lang,
|
|
@@ -363,5 +552,12 @@ export async function executeBraveSearch(
|
|
|
363
552
|
results,
|
|
364
553
|
};
|
|
365
554
|
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
|
|
555
|
+
logBraveHttp(diagnostics, "cache write", {
|
|
556
|
+
mode: "web",
|
|
557
|
+
query,
|
|
558
|
+
cacheKey,
|
|
559
|
+
ttlMs: cacheTtlMs,
|
|
560
|
+
count: results.length,
|
|
561
|
+
});
|
|
366
562
|
return payload;
|
|
367
563
|
}
|