From 80e2ca7c392b345d759dca58e05c2b8944bb73fe Mon Sep 17 00:00:00 2001 From: Dylan Haskins Date: Sat, 19 Sep 2026 15:04:42 +1200 Subject: [PATCH] fix: don't blanket-disable Claude tool-name prefix for non-Anthropic providers (#13856) * fix: don't blanket-disable Claude tool-name prefix for non-Anthropic providers The Issue #199/#618 fix that disables the proxy_ tool-name prefix for requests landing in the general (non-CC-bridge, non-passthrough) translation branch was scoped to "targetFormat === Claude", but that condition is true for any provider translating into Claude's wire format, not just genuine first-party Anthropic traffic. In practice this let ordinary third-party tool names (e.g. GitHub Copilot's own client-executed "web_fetch" function tool) pass through unprefixed and collide with Claude's reserved tool namespace, getting rejected upstream ("rejected tool(s): web_fetch") for any gh/claude-* model. #618's actual traffic was real Claude Code (provider "claude") landing in this fallback branch, so scoping the disable to provider === "claude" (the same check already used a few lines above in the sibling isClaudePassthrough branch) keeps that fix intact while restoring correct prefixing for every other provider that merely targets Claude's wire format. Fixes #13835. * docs: add changelog fragment for #13856 --- ...56-github-copilot-web-fetch-tool-prefix.md | 1 + open-sse/handlers/chatCore.ts | 15 +++++++- tests/unit/chatcore-translation-paths.test.ts | 34 +++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 changelog.d/fixes/13856-github-copilot-web-fetch-tool-prefix.md diff --git a/changelog.d/fixes/13856-github-copilot-web-fetch-tool-prefix.md b/changelog.d/fixes/13856-github-copilot-web-fetch-tool-prefix.md new file mode 100644 index 0000000000..762205297e --- /dev/null +++ b/changelog.d/fixes/13856-github-copilot-web-fetch-tool-prefix.md @@ -0,0 +1 @@ +- **fix(translator):** Third-party tool names (e.g. GitHub Copilot's own `web_fetch` function tool) are no longer sent unprefixed to Claude-wire-format providers outside genuine first-party Anthropic traffic, fixing a `rejected tool(s): web_fetch` 400 for any `gh/claude-*` model ([#13856](https://github.com/diegosouzapw/OmniRoute/pull/13856)) — thanks @dylanhaskins diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 098eb3a26d..7434983c79 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2494,8 +2494,21 @@ export async function handleChatCore({ // conflicts with Claude OAuth tools, but in the passthrough path the tools // are already in Claude format. Applying the prefix turns "Bash" into // "proxy_Bash", which Claude rejects ("No such tool available: proxy_Bash"). + // + // #618's actual traffic was real Claude Code talking to first-party Anthropic + // (provider "claude") reaching this fallback branch instead of the dedicated + // Claude Code bridge/passthrough branches above. Scoping the disable to + // `provider === "claude"` keeps that fix intact while no longer blanket-applying + // it to every other provider that merely targets Claude's wire format — a + // third-party provider's own ordinary (non-Claude-native) tool names, e.g. + // GitHub Copilot's own client-executed "web_fetch" tool, were passing through + // unprefixed here and colliding with Claude's reserved tool namespace, since + // they were never "already in Claude format" the way this comment assumes. + // See #13835. if (targetFormat === FORMATS.CLAUDE) { - translatedBody._disableToolPrefix = true; + if (provider === "claude") { + translatedBody._disableToolPrefix = true; + } normalizeClaudeUpstreamMessages(translatedBody); } diff --git a/tests/unit/chatcore-translation-paths.test.ts b/tests/unit/chatcore-translation-paths.test.ts index 1d5a4eea21..12bab97f0f 100644 --- a/tests/unit/chatcore-translation-paths.test.ts +++ b/tests/unit/chatcore-translation-paths.test.ts @@ -1832,6 +1832,40 @@ test("chatCore sets Claude tool prefix disabling, strips empty Anthropic text bl ["hello"] ); }); +// #13835: a third-party provider's own ordinary tool name (GitHub Copilot's client-executed +// "web_fetch" function tool) must still get the proxy_ prefix even though this request lands +// in the same general (non-claude-passthrough) branch as the "claude" provider test above — +// only genuine first-party Anthropic traffic (provider "claude") should skip prefixing. +test("chatCore still prefixes ordinary third-party tool names for non-Anthropic providers targeting Claude", async () => { + const { call } = await invokeChatCore({ + provider: "github", + model: "claude-haiku-4.5", + endpoint: "/v1/chat/completions", + credentials: { apiKey: "gh-key", providerSpecificData: {} }, + body: { + model: "github/claude-haiku-4.5", + messages: [{ role: "user", content: "fetch a url" }], + tools: [ + { + type: "function", + function: { + name: "web_fetch", + description: "Fetches a URL from the internet.", + parameters: { + type: "object", + properties: { url: { type: "string" } }, + required: ["url"], + }, + }, + }, + ], + }, + responseFormat: "claude", + }); + + assert.equal(call.body.tools[0].name, "proxy_web_fetch"); + assert.equal(call.body._toolNameMap, undefined); +}); test("chatCore restores prefixed Claude passthrough tool names in upstream responses", async () => { const { result } = await invokeChatCore({ provider: "claude",