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
This commit is contained in:
Dylan Haskins
2026-09-19 15:04:42 +12:00
committed by GitHub
parent c299e6c180
commit 80e2ca7c39
3 changed files with 49 additions and 1 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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",