Skip to content

fix(providers): resolve untagged claude-* ids on GitHub/GHE Copilot to native targetFormat (#14575) - #14758

Merged
diegosouzapw merged 3 commits into
release/v3.8.51from
fix/14575-copilot-tool-choice
Sep 25, 2026
Merged

diegosouzapw merged 3 commits into
release/v3.8.51from
fix/14575-copilot-tool-choice

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Closes #14575

Root cause

GitHub Copilot's new claude-opus-5.5 id is absent from OmniRoute's curated GitHub Copilot
catalog. GithubExecutor.buildUrl() already routes any claude-named model (registered or
not) to Copilot's Anthropic-native /v1/messages endpoint via an unconditional /claude/i name
match. But getModelTargetFormat("gh", model) only recognized registered claude ids, so an
untagged id like claude-opus-5.5 fell through to the provider's base openai format. The
request body stayed OpenAI-shaped (tool_choice as a string, tools as
{type:"function",...}) while being dispatched to the Anthropic-native endpoint, which rejects
it with tool_choice: Input should be an object and tools.0: Input tag 'function' found....

Fix

open-sse/config/providerModels.ts::getModelTargetFormat(): after the curated-catalog lookup
misses, added a heuristic scoped to the gh/ghe-copilot aliases — any model id containing
claude (case-insensitive) resolves targetFormat: "claude", mirroring the routing decision
buildUrl() already makes unconditionally. This is the same pattern already shipped for the
openai "-pro" heuristic (#5842). Curated catalog entries still return before this heuristic
is reached, so no existing routing changes for registered models.

Regression test

tests/unit/github-copilot-claude-untagged-model-14575.test.ts (new):

  • RED (pre-fix): resolveChatCoreTargetFormat({provider:"github", resolvedModel:"claude-opus-5.5", sourceFormat:"openai", ...}).targetFormat resolved to "openai" while GithubExecutor.buildUrl("claude-opus-5.5", ...) returned the Anthropic-native messagesUrl — assertion 'openai' !== 'claude' failed.
  • GREEN (post-fix): targetFormat resolves to "claude", matching the endpoint buildUrl() already routes to. Also added a control test for a curated model (no regression) and a parity test for the ghe-copilot alias.

Existing tests

Two pre-existing tests encoded the OLD (buggy) premise that an unregistered claude-named id on
gh would stay on the /chat/completions path (getModelTargetFormat("gh", ...) === null).
With this fix that premise no longer holds — any claude-named id is now native, matching
buildUrl()'s own routing — so those tests were realigned to the corrected behavior (not
weakened; assertions changed to match what is now the correct contract):

  • tests/unit/t27-github-copilot-response-format.test.ts — "T27: Claude + response_format=json_object injects system instruction..." renamed and now asserts response_format passes through untouched for an untagged claude id (native path skips the /chat/completions-only workaround), consistent with the already-existing github-copilot-claude-native-messages.test.ts coverage for registered ids.
  • tests/unit/executor-github.test.ts — two tests updated similarly: (1) reasoning-field stripping still applies for claude models, but response_format is no longer injected as a system message (native path); (2) the Anthropic-shape content-part sanitization test now uses a non-claude model id (gpt-4o) to exercise the /chat/completions-only sanitization path, since that path is now unreachable for any claude-named id.

All three files pass after the change. Also ran and confirmed green (no regressions):
provider-registry-github-copilot-targetformat.test.ts, ghe-copilot-targetformat-parity.test.ts,
github-copilot-custom-model-target-format.test.ts, openai-responses-only-models-5842.test.ts,
provider-models-target-format-scoping.test.ts, copilot-claude-always-v1-messages.test.ts,
github-copilot-claude-native-messages.test.ts, chatcore-target-format.test.ts,
custom-model-target-format.test.ts, provider-models-config.test.ts.

Gates run

  • node --import tsx/esm --test tests/unit/github-copilot-claude-untagged-model-14575.test.ts — RED then GREEN (4/4 pass post-fix)
  • npm run typecheck:core — 1 pre-existing error in src/lib/services/cliproxyAccountHealth.ts (untouched by this PR; already fixed on the current origin/release/v3.8.51 tip past this branch's base commit beec76d5 — inherited, not introduced by this diff)
  • node scripts/check/check-open-sse-typecheck.mjs — 2 pre-existing errors in open-sse/executors/auggie.ts (untouched by this PR; same situation — already fixed on the current tip past this branch's base)
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json open-sse/config/providerModels.ts tests/unit/github-copilot-claude-untagged-model-14575.test.ts — clean
  • node scripts/check/check-file-size.mjs — no violations on files this diff touches (10 pre-existing violations on unrelated frozen files)
  • node scripts/check/check-complexity-ratchets.mjs --base-ref origin/release/v3.8.51 — OK (0 changed source files in ratchet scope)
  • node scripts/check/check-mutation-test-coverage.mjs --strict — pre-existing drift on 9 unrelated modules (none touched by this diff; providerModels.ts is not a stryker-mutated module)
  • node scripts/check/check-changelog-integrity.mjs — OK
  • npm run check:provider-consistency — OK (276 registry entries, 0 registry-only exceptions)

⚠️ base-red inherited: the cliproxyAccountHealth.ts and auggie.ts typecheck errors above are
pre-existing on this branch's cut point (beec76d5) and already fixed on the current
origin/release/v3.8.51 tip by later, unrelated commits — not introduced by this diff.

Plan-file: _tasks/pipeline/bugs/2-implementing/14575-fix-github-copilot-tool-choice-error-in-claude-opus-5-5-on-git.plan.md

…14575 made native (#14575)

Since #14575 every claude-* id on the gh alias resolves targetFormat "claude"
(native /v1/messages, which honors assistant prefill), so the unregistered
"claude-sonnet-4" this test used no longer reaches the /chat/completions path it
covers. The drop still applies to every non-Claude model there, so the test now
uses gpt-4.1 (targetFormat null). Same assertions; the GitHub executor + targetFormat
suite is 392/392.
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.

fix(providers): tool_choice error in Claude Opus 5.5 on github copilot

1 participant