From 2cb77bbca716481de7418a56b84b0d52ef7e11d6 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Tue, 4 Aug 2026 10:06:31 -0300 Subject: [PATCH] fix(translator): harden Claude format detection for model validation (#9253) * fix(translator): harden Claude format detection for model validation Co-authored-by: Ervareza Naurian Inspired-by: https://github.com/decolua/9router/pull/2949 * chore(changelog): fragment for #9253 --------- Co-authored-by: diegosouzapw Co-authored-by: Ervareza Naurian --- .../9253-translator-format-detection-2949.md | 1 + open-sse/services/provider.ts | 16 ++++++++++--- .../translator-format-detection-2949.test.ts | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 changelog.d/fixes/9253-translator-format-detection-2949.md create mode 100644 tests/unit/translator-format-detection-2949.test.ts diff --git a/changelog.d/fixes/9253-translator-format-detection-2949.md b/changelog.d/fixes/9253-translator-format-detection-2949.md new file mode 100644 index 0000000000..cec3893832 --- /dev/null +++ b/changelog.d/fixes/9253-translator-format-detection-2949.md @@ -0,0 +1 @@ +- **fix(translator):** harden Claude format detection for relative message endpoints and kebab-case version metadata. (thanks @ervareza) diff --git a/open-sse/services/provider.ts b/open-sse/services/provider.ts index 40c3dc2658..0e53730eb5 100644 --- a/open-sse/services/provider.ts +++ b/open-sse/services/provider.ts @@ -135,7 +135,17 @@ export function detectFormatFromEndpoint(body, endpointPath = "") { // Thin wrapper for call sites that only have the full request URL (not the bare endpoint // path chatCore already threads) — single source of truth stays detectFormatFromEndpoint. export function detectFormatFromUrl(body, requestUrl) { - return detectFormatFromEndpoint(body, new URL(requestUrl).pathname); + const rawUrl = typeof requestUrl === "string" ? requestUrl : ""; + let pathname = rawUrl; + try { + // Supplying a base URL keeps relative client endpoints (for example, + // `/v1/messages`) valid while preserving pathname-only detection. + pathname = new URL(rawUrl || "/", "http://omniroute.local").pathname; + } catch { + // Fall back to the raw value; detectFormatFromEndpoint is intentionally + // safe for unknown or malformed paths. + } + return detectFormatFromEndpoint(body, pathname); } // Detect request format from body structure @@ -193,7 +203,7 @@ export function detectFormat(body) { if (firstContent?.type === "text" && !body.model?.includes("/")) { // Could be Claude or OpenAI multimodal // Check for Claude-specific fields - if (body.system || body.anthropic_version) { + if (body.system || body.anthropic_version || body["anthropic-version"]) { return "claude"; } // Check if image format is Claude (source.type) vs OpenAI (image_url.url) @@ -216,7 +226,7 @@ export function detectFormat(body) { // If content is string, it's likely OpenAI (Claude also supports this) // Check for other Claude-specific indicators - if (body.system !== undefined || body.anthropic_version) { + if (body.system !== undefined || body.anthropic_version || body["anthropic-version"]) { return "claude"; } diff --git a/tests/unit/translator-format-detection-2949.test.ts b/tests/unit/translator-format-detection-2949.test.ts new file mode 100644 index 0000000000..234d8647ba --- /dev/null +++ b/tests/unit/translator-format-detection-2949.test.ts @@ -0,0 +1,24 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { detectFormat, detectFormatFromUrl } from "../../open-sse/services/provider.ts"; + +test("detectFormatFromUrl accepts a relative /messages endpoint", () => { + assert.equal( + detectFormatFromUrl( + { messages: [{ role: "user", content: "validate this model" }] }, + "/v1/messages" + ), + "claude" + ); +}); + +test("detectFormat recognizes the kebab-case anthropic-version body field", () => { + assert.equal( + detectFormat({ + messages: [{ role: "user", content: "validate this model" }], + "anthropic-version": "2023-06-01", + }), + "claude" + ); +});