feat(websearch): forward web_search tool requests to a configurable target model#4094
feat(websearch): forward web_search tool requests to a configurable target model#4094GreenTeodoro839 wants to merge 1 commit into
Conversation
…odel
Add a global `websearch-forward` config ({enable, model}) that reroutes
requests carrying an Anthropic web_search server tool (tools[].type with
the "web_search" prefix) to a configured target model. The request body
is forwarded unchanged; the existing translation/auth/header pipeline
handles the target. The response model field is rewritten back to the
client's originally requested model so forwarding stays transparent.
- Detection is a gjson prefix match on tools[].type, future-proofing
dated variants (web_search_20250305, web_search_20260209, ...).
- Routing override is applied in the three standard execute paths
(non-stream / count / stream). The client model is carried via a new
ForcedResponseModelMetadataKey and applied at the conductor's existing
aliasResult rewrite step (all 5 sites, streaming + non-streaming),
reusing rewriteForceMappedResponse / wrapStreamResult unchanged.
- Self-forward guard: skip forwarding when the target equals the
client-requested model (avoids target -> target loops).
- No target-capability validation; success or failure on the chosen
target is existing CLIProxyAPI behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This pull request targeted The base branch has been automatically changed to |
There was a problem hiding this comment.
Code Review
This pull request introduces a feature to transparently forward requests containing Anthropic's web_search server tool to a different configured model, rewriting the response model field back to the client's original request. The feedback suggests enhancing the self-forwarding loop guard in webSearchForwardTarget to resolve auto-models and parse thinking suffixes (e.g., deepseek(thinking)) before comparison, along with adding corresponding imports and unit tests.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if strings.EqualFold(target, strings.TrimSpace(requestedModel)) { | ||
| return "" | ||
| } |
There was a problem hiding this comment.
Comparing the raw target and requestedModel strings directly can lead to unintended forwarding loops or accidental stripping of model suffixes (such as (thinking)). For example, if the target model is deepseek and the client requests deepseek(thinking), they will not match under strings.EqualFold, causing the request to be forwarded to deepseek and stripping the thinking suffix from the upstream execution.
To prevent this, resolve auto-models and parse suffixes on both models before comparing their base names.
requestedBase := strings.TrimSpace(thinking.ParseSuffix(util.ResolveAutoModel(requestedModel)).ModelName)
targetBase := strings.TrimSpace(thinking.ParseSuffix(util.ResolveAutoModel(target)).ModelName)
if strings.EqualFold(targetBase, requestedBase) {
return ""
}| import ( | ||
| "strings" | ||
|
|
||
| "github.com/tidwall/gjson" | ||
| ) |
There was a problem hiding this comment.
Import thinking and util packages to support resolving auto-models and parsing suffixes in webSearchForwardTarget.
| import ( | |
| "strings" | |
| "github.com/tidwall/gjson" | |
| ) | |
| import ( | |
| "strings" | |
| "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" | |
| "github.com/router-for-me/CLIProxyAPI/v7/internal/util" | |
| "github.com/tidwall/gjson" | |
| ) |
| {"self-forward guard same model", true, "glm", "glm", wsBody, ""}, | ||
| {"self-forward guard case-insensitive", true, "DeepSeek", "deepseek", wsBody, ""}, | ||
| {"requested model trimmed before compare", true, "deepseek", " glm ", wsBody, "deepseek"}, |
There was a problem hiding this comment.
Add a test case to verify that the self-forward guard correctly handles models with thinking suffixes (e.g., deepseek(thinking) vs deepseek).
{"self-forward guard same model", true, "glm", "glm", wsBody, ""},
{"self-forward guard case-insensitive", true, "DeepSeek", "deepseek", wsBody, ""},
{"self-forward guard with thinking suffix", true, "deepseek", "deepseek(thinking)", wsBody, ""},
{"requested model trimmed before compare", true, "deepseek", " glm ", wsBody, "deepseek"},There was a problem hiding this comment.
Pull request overview
Adds a configurable routing override that transparently forwards Anthropic web_search_* tool requests to a chosen target model, while rewriting the response model back to the client’s originally requested model so clients don’t observe the reroute.
Changes:
- Introduces
websearch-forward: { enable, model }in globalSDKConfig, documents it inconfig.example.yaml, and adds config parsing tests. - Detects
web_search_*tools in request JSON and overrides routing to the configured target model, attaching metadata to force response-model rewrite. - Extends the auth conductor’s existing force-mapped response rewrite machinery to optionally apply a forced response model via new executor metadata, with unit tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/cliproxy/executor/types.go | Adds a metadata key for forcing response model rewriting. |
| sdk/cliproxy/auth/conductor.go | Applies forced-response-model metadata to alias rewrite results across execution paths. |
| sdk/cliproxy/auth/conductor_forced_response_model_test.go | Unit tests for forced response model application behavior. |
| sdk/api/handlers/web_search_forward.go | Adds web_search tool detection and forwarding target selection. |
| sdk/api/handlers/web_search_forward_test.go | Unit tests for tool detection and forwarding decision logic. |
| sdk/api/handlers/handlers.go | Integrates forwarding decision into execute/count/stream paths and sets forced-response metadata when rerouting. |
| internal/config/web_search_forward_test.go | Verifies YAML parsing defaults and configured values for websearch-forward. |
| internal/config/sdk_config.go | Adds WebSearchForwardConfig to SDKConfig. |
| config.example.yaml | Documents the new websearch-forward configuration block. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| toolType := strings.ToLower(strings.TrimSpace(tool.Get("type").String())) | ||
| if strings.HasPrefix(toolType, webSearchToolTypePrefix) { | ||
| return true | ||
| } |
| if model = strings.TrimSpace(model); model == "" { | ||
| return | ||
| } | ||
| aliasResult.ForceMapping = true | ||
| aliasResult.OriginalAlias = model |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a1c82f2b1
ℹ️ 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".
| } | ||
| for _, tool := range tools.Array() { | ||
| toolType := strings.ToLower(strings.TrimSpace(tool.Get("type").String())) | ||
| if strings.HasPrefix(toolType, webSearchToolTypePrefix) { |
There was a problem hiding this comment.
Exclude OpenAI web_search tools from forwarding
When websearch-forward is enabled, this prefix check also matches OpenAI/Codex built-in tools such as {"type":"web_search"} (the repo already preserves that shape in test/builtin_tools_translation_test.go lines 16 and 25), not just Anthropic's dated web_search_YYYYMMDD tools. Because the handler does not check the entry protocol before calling this function, OpenAI requests that intentionally use the existing built-in web search path will be rerouted to the configured target model and have their response model rewritten, which can send them to an incompatible provider.
Useful? React with 👍 / 👎.
Summary
Adds a global
websearch-forwardconfig ({enable, model}) that reroutes requests carrying an Anthropicweb_searchserver tool (anytools[].typestarting withweb_search, e.g.web_search_20250305) to a configurable target model. Forwarding is transparent: the request body is forwarded unchanged to the existing pipeline (translation, auth selection, headers, upstream model remapping), and the responsemodelfield is rewritten back to the client's originally requested model so the client never sees the reroute.No search shim is added — this is purely a routing override plus one response fix-up.
Motivation
Many upstreams don't support Anthropic's server-side web search. Today, when Claude Code sends a
/v1/messagesrequest with aweb_search_*tool to such an upstream, the tool is silently dropped or the call misbehaves. This lets operators reroute those requests to a model whose upstream can handle them (e.g. aclaude-api-keywhose upstream speaks the Anthropic protocol and can execute the tool), without building a search shim — just a routing decision.How it works
web_search_*tool viagjsonprefix match ontools[].type(future-proofs dated variants).ForcedResponseModelMetadataKey).aliasResultrewrite step, so both non-streaming (rewriteForceMappedResponse) and streaming (wrapStreamResult/StreamRewriter) responses report the client's model.The existing force-mapping rewrite machinery is reused unchanged — no signature changes to it.
Configuration
Global (
SDKConfig), hot-reloadable. A request whose model already equals the configured target is not forwarded (avoids a self-loop). No target-capability validation is performed — forwarding to a target that cannot handle the request is existing pipeline behavior.Changes
internal/config/sdk_config.go—WebSearchForwardConfig+ fieldconfig.example.yaml— documented blocksdk/cliproxy/executor/types.go—ForcedResponseModelMetadataKeysdk/api/handlers/web_search_forward.go(new) —hasWebSearchTool,webSearchForwardTargetsdk/api/handlers/handlers.go— routing override + metadata in the 3 execute pathssdk/cliproxy/auth/conductor.go—applyForcedResponseModelapplied at the 5aliasResultproduce sitesNo changes to
internal/translator/(the forwarded request's tool translation is existing behavior).Testing
gofmtclean;go build ./cmd/serverOKgo test ./...— all packages pass; new unit tests cover detection (dated variants, mixed tools, no-op cases), the self-forward guard, config round-trip, and the forced-response-model override (applied / no-op).