From 989e6a609b9667c3c01f1c53912c63d5b0b5f7a8 Mon Sep 17 00:00:00 2001 From: dsgn93-creator Date: Tue, 28 Apr 2026 21:08:26 +0300 Subject: [PATCH] fix(messages): preserve Claude 4.x version suffix and convert to dot form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot exposes models as claude-opus-4.7 / claude-opus-4.6 / claude-sonnet-4.6 (dot-versioned), but Claude Code sends dash-versioned IDs with date suffixes like claude-opus-4-7-20251030. The previous translateModelName regex stripped the entire suffix and rewrote everything to claude-opus-4 / claude-sonnet-4, which (a) loses the 4.5/4.6/4.7 capability tier and (b) routes vision requests to a fallback model that may not have vision enabled — producing an internal server error when image_url parts are present even though copilot-vision-request: true is sent. Convert dash-versioned 4.x families to their dot form (claude-opus-4-7-20251030 -> claude-opus-4.7) so vision-capable variants route correctly. Falls back to the previous behavior for unknown shapes. --- bun.lock | 1 + src/routes/messages/non-stream-translation.ts | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 20e895e7f..9ece87578 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "copilot-api", diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts index dc41e6382..7bae454d4 100644 --- a/src/routes/messages/non-stream-translation.ts +++ b/src/routes/messages/non-stream-translation.ts @@ -47,7 +47,18 @@ export function translateToOpenAI( } function translateModelName(model: string): string { - // Subagent requests use a specific model number which Copilot doesn't support + // Claude Code sends dash-versioned model IDs with date suffixes + // (e.g. "claude-opus-4-7-20251030"), but Copilot exposes dot-versioned + // IDs (e.g. "claude-opus-4.7"). Convert dash → dot for known 4.x families + // so that vision-capable models route correctly instead of falling back. + const versioned = model.match( + /^claude-(opus|sonnet|haiku)-(\d+)-(\d+)(?:-\d{6,8})?$/, + ) + if (versioned) { + return `claude-${versioned[1]}-${versioned[2]}.${versioned[3]}` + } + + // Legacy fallback: subagent requests with unsupported numeric suffixes if (model.startsWith("claude-sonnet-4-")) { return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4") } else if (model.startsWith("claude-opus-")) {