Skip to content

feat(proxy): run web search locally when upstream lacks it - #127

Merged
orangeboyChen merged 3 commits into
mainfrom
feat/local-websearch
Sep 15, 2026
Merged

orangeboyChen merged 3 commits into
mainfrom
feat/local-websearch

Conversation

@orangeboyChen

@orangeboyChen orangeboyChen commented Sep 15, 2026

Copy link
Copy Markdown
Owner

What this does

Anthropic (web_search_20260209) and the Responses API (web_search_preview) both delegate web search to the provider. CodeBuddy has no equivalent, so requests carrying those tools either failed or silently lost the tool.

When a client declares a search tool and the new setting is on, the proxy swaps it for a plain web_search function, runs the query through a local search backend, and appends the results as a tool message. The model answers normally and the client never sees the substitution.

Settings

Adds 启用本地 WebSearch / Enable local web search to the console, described as performing WebSearch locally in Codebuddy2API. It renders as a switch rather than a text input.

The option only appears when SEARXNG_URL is set — hiding the label keeps it out of both the rendered form and the save payload, so an unconfigured deployment cannot enable a feature with nothing behind it. getActiveConfig re-checks per request, so a deployment that later drops the variable stops advertising the tool.

Pluggable backends

Search sits behind a WebSearchProvider interface (lib/server/search/) with SearXNG as the first implementation. Adding another backend means adding one provider file and one entry in resolveProvider — the proxy loop does not change. Configuration is env-only, since a search instance is deployment infrastructure rather than a per-request preference:

SEARXNG_URL, SEARXNG_API_KEY, SEARXNG_ENGINES,
SEARXNG_LANGUAGE, SEARXNG_MAX_RESULTS, SEARXNG_TIMEOUT_MS

Behavior notes

  • Anthropic's dated server-tool types, Responses' web_search_preview, and a plain function named web_search are all recognised.
  • The loop buffers upstream responses, so a streaming client gets a synthesized SSE stream once the loop finishes; usage is summed across iterations.
  • Guards against runaway loops: iteration cap, and forced tool_choice is relaxed to auto after the first turn. If the budget is spent while the model still wants to search, the tool is withdrawn for one final call so the request terminates with an answer instead of an unresolved tool call.
  • Turns that mix a search call with an unrelated one are handed back to the client, which resolves the other call and re-submits — the proxy can only produce a result for the search.
  • Search failures become tool-result text rather than exceptions, so the model still sees the turn and can say the search failed.

Verification

lint, format:check, typecheck, build, and the full suite (344 tests, 23 files) pass. Coverage 94.2% statements / 87.9% branches against the 90/90/70 thresholds; patch branch coverage 93.0% against the 90% target.

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.77039% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.01%. Comparing base (80bb2b8) to head (73e3e59).
⚠️ Report is 2 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #127      +/-   ##
==========================================
+ Coverage   94.95%   95.01%   +0.05%     
==========================================
  Files          22       26       +4     
  Lines        4620     4950     +330     
  Branches     1325     1432     +107     
==========================================
+ Hits         4387     4703     +316     
- Misses        233      247      +14     
Flag Coverage Δ
unittests 95.01% <95.77%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eff62e1695

ℹ️ 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".

Comment thread lib/server/proxy/codebuddy.ts
Comment thread lib/server/proxy/web-search-loop.ts Outdated
@orangeboyChen
orangeboyChen force-pushed the feat/local-websearch branch 3 times, most recently from 7da1e69 to 907f336 Compare September 15, 2026 15:19
CodeBuddy has no equivalent of the server-side search tools that Anthropic
(web_search_20260209 / web_search_20250305) and the Responses API
(web_search_preview) expose, so clients declaring them got no search at all.
The proxy now swaps those declarations for a plain web_search function,
executes the query itself, and feeds the findings back; the model answers
normally and the client never learns the search ran locally.

Search runs through a pluggable provider registry with SearXNG as the first
implementation. Adding a backend means one provider file and one entry in the
factory list, with no changes to the proxy loop. SearXNG is configured through
the environment, since a search instance is deployment infrastructure rather
than a per-request preference.

The "Enable local web search" console setting only appears when a backend is
configured, and stays forced off otherwise so a deployment cannot advertise a
tool it cannot execute.

Two subtleties the wire protocol imposes:
- web_search_preview has no function schema, so Responses tool translation
  drops it before the loop ever runs. It is now emitted as a function when a
  backend is configured, including inside namespaces.
- A turn mixing a search call with client-owned calls cannot be continued
  locally, since re-issuing it with only search results leaves those calls
  unanswered and upstream rejects the transcript. The findings are folded into
  the message text and the outstanding calls are handed back to the client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@orangeboyChen
orangeboyChen enabled auto-merge (squash) September 15, 2026 15:55
@orangeboyChen
orangeboyChen merged commit 6cddd59 into main Sep 15, 2026
3 checks passed
@orangeboyChen
orangeboyChen deleted the feat/local-websearch branch September 15, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant