feat(tools): add Brave llm-context mode for web_search#39906
Conversation
PR SummaryMedium Risk Overview When Written by Cursor Bugbot for commit 2600331. This will update automatically on new commits. Configure here. |
Add support for Brave's LLM Context API endpoint (/res/v1/llm/context) as an optional mode for the web_search tool. When configured with tools.web.search.brave.mode set to llm-context, the tool returns pre-extracted page content optimized for LLM grounding instead of standard URL/snippet results. The llm-context cache key excludes count and ui_lang parameters that the LLM Context API does not accept, preventing unnecessary cache misses. Closes #14992 Co-Authored-By: Claude Opus 4.6 <[email protected]>
8e643c7 to
2600331
Compare
|
Landed via temp rebase onto main. Thanks @thirumaleshp! |
Greptile SummaryThis PR adds an opt-in Key changes:
Confidence Score: 4/5
Last reviewed commit: 2600331 |
| async function runBraveLlmContextSearch(params: { | ||
| query: string; | ||
| apiKey: string; | ||
| timeoutSeconds: number; | ||
| country?: string; | ||
| search_lang?: string; | ||
| freshness?: string; | ||
| }): Promise<{ | ||
| results: Array<{ | ||
| url: string; | ||
| title: string; | ||
| snippets: string[]; | ||
| siteName?: string; | ||
| }>; | ||
| sources?: BraveLlmContextResponse["sources"]; | ||
| }> { | ||
| const url = new URL(BRAVE_LLM_CONTEXT_ENDPOINT); | ||
| 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); | ||
| } | ||
|
|
||
| return withTrustedWebSearchEndpoint( | ||
| { | ||
| url: url.toString(), | ||
| timeoutSeconds: params.timeoutSeconds, | ||
| init: { |
There was a problem hiding this comment.
runBraveLlmContextSearch declares freshness?: string in its parameter signature (line 1249) and forwards it to the Brave LLM Context endpoint (lines 1267–1269), even though the /res/v1/llm/context API doesn't support this parameter. The tool-level guard in createWebSearchTool (line 1701) prevents freshness from ever being non-undefined by the time runBraveLlmContextSearch is called in llm-context mode, so there's no user-facing bug today. However, the function signature misleadingly suggests the parameter is supported, which could cause confusion in future refactoring.
To make the API contract explicit and prevent accidental misuse, consider removing the freshness parameter from the function signature:
| async function runBraveLlmContextSearch(params: { | |
| query: string; | |
| apiKey: string; | |
| timeoutSeconds: number; | |
| country?: string; | |
| search_lang?: string; | |
| freshness?: string; | |
| }): Promise<{ | |
| results: Array<{ | |
| url: string; | |
| title: string; | |
| snippets: string[]; | |
| siteName?: string; | |
| }>; | |
| sources?: BraveLlmContextResponse["sources"]; | |
| }> { | |
| const url = new URL(BRAVE_LLM_CONTEXT_ENDPOINT); | |
| 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); | |
| } | |
| return withTrustedWebSearchEndpoint( | |
| { | |
| url: url.toString(), | |
| timeoutSeconds: params.timeoutSeconds, | |
| init: { | |
| async function runBraveLlmContextSearch(params: { | |
| query: string; | |
| apiKey: string; | |
| timeoutSeconds: number; | |
| country?: string; | |
| search_lang?: string; | |
| }): Promise<{ |
Then remove the corresponding if (params.freshness) block (lines 1267–1269) and drop the freshness: params.freshness argument at the call site (line 1474).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/web-search.ts
Line: 1243-1275
Comment:
`runBraveLlmContextSearch` declares `freshness?: string` in its parameter signature (line 1249) and forwards it to the Brave LLM Context endpoint (lines 1267–1269), even though the `/res/v1/llm/context` API doesn't support this parameter. The tool-level guard in `createWebSearchTool` (line 1701) prevents `freshness` from ever being non-`undefined` by the time `runBraveLlmContextSearch` is called in llm-context mode, so there's no user-facing bug today. However, the function signature misleadingly suggests the parameter is supported, which could cause confusion in future refactoring.
To make the API contract explicit and prevent accidental misuse, consider removing the `freshness` parameter from the function signature:
```suggestion
async function runBraveLlmContextSearch(params: {
query: string;
apiKey: string;
timeoutSeconds: number;
country?: string;
search_lang?: string;
}): Promise<{
```
Then remove the corresponding `if (params.freshness)` block (lines 1267–1269) and drop the `freshness: params.freshness` argument at the call site (line 1474).
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e643c7a1d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const mapped = llmResults.map((entry) => ({ | ||
| title: entry.title ? wrapWebContent(entry.title, "web_search") : "", | ||
| url: entry.url, | ||
| snippets: entry.snippets.map((s) => wrapWebContent(s, "web_search")), |
There was a problem hiding this comment.
Respect count limit in Brave llm-context mode
The new llm-context branch ignores the caller’s count constraint even though count is part of the web_search contract. In this path all upstream results are returned (llmResults.map(...)) and count is reported as mapped.length, so a request like count: 1 can still return many extracted chunks, increasing response size and token/cost usage unexpectedly. Apply params.count in this mode (either pass it to Brave if supported, or locally truncate before building the payload).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| `${params.provider}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || params.language || "default"}:${params.ui_lang || "default"}:${params.freshness || "default"}:${params.dateAfter || "default"}:${params.dateBefore || "default"}:${params.searchDomainFilter?.join(",") || "default"}:${params.maxTokens || "default"}:${params.maxTokensPerPage || "default"}:${providerSpecificKey}`, | ||
| params.provider === "brave" && effectiveBraveMode === "llm-context" | ||
| ? `${params.provider}:llm-context:${params.query}:${params.country || "default"}:${params.search_lang || params.language || "default"}:${params.freshness || "default"}` | ||
| : `${params.provider}:${effectiveBraveMode}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || params.language || "default"}:${params.ui_lang || "default"}:${params.freshness || "default"}:${params.dateAfter || "default"}:${params.dateBefore || "default"}:${params.searchDomainFilter?.join(",") || "default"}:${params.maxTokens || "default"}:${params.maxTokensPerPage || "default"}:${providerSpecificKey}`, |
There was a problem hiding this comment.
Cache key leaks brave mode into non-brave providers
Low Severity
The else branch of the cache key ternary embeds ${effectiveBraveMode} (always "web") into cache keys for every provider — perplexity, grok, gemini, kimi, and brave-web — even though the brave mode concept is meaningless for non-brave providers. This silently changes the key format from e.g. perplexity:<query>:… to perplexity:web:<query>:… for all providers.
| } | ||
| if (params.freshness) { | ||
| url.searchParams.set("freshness", params.freshness); | ||
| } |
There was a problem hiding this comment.
Dead freshness parameter in LLM context function
Low Severity
runBraveLlmContextSearch accepts a freshness parameter and would pass it to the Brave LLM Context API URL, but the validation layer in execute always rejects freshness before this function is reached. This makes the parameter, the if (params.freshness) guard, and the freshness: params.freshness call-site argument all dead code. The function's interface falsely implies freshness is supported, which could mislead future callers.


Summary
tools.web.search.brave.mode(web|llm-context) and wire it through config types/schema/help/labelsweb_searchusing/res/v1/llm/contextui_lang,freshness, anddate_after/date_beforefilters inllm-contextmodeLinked Issues
Validation
pnpm checkpnpm buildpnpm test