Repository navigation
Conversation
…its format A model-level `targetFormat` overrides the transport chosen for the client's format, but `credentials.runtimeTransport` kept pointing at the sourceFormat match. The transport decides the URL, the headers and the auth scheme, so an OpenAI-format request for a model pinned to `claude` was serialized as a Claude body and posted to /v1/chat/completions with bearer auth. Upstreams accept what they can parse and drop the rest, so this fails quietly: image blocks vanish (decolua#3418) and Claude tool schemas come back as `invalid tool type (2013)` (decolua#3439). Resolve the format and the transport together so they cannot diverge. Three models are pinned today — minimax/MiniMax-M3, minimax-cn/MiniMax-M3 and xiaomi-tokenplan/mimo-v2.5-pro-claude — and all three now reach the Anthropic endpoint their body is written for. The per-model transport guard is unchanged: a model that does not declare a format still never gets that transport.
|
Closing this: This PR pinned the endpoint to a model's declared // A source-format-matched endpoint keeps the request lossless. Prefer it
// over a model-level targetFormat, which is only the fallback for clients
// whose wire format has no supported transport (for example MiniMax-M3:
// OpenAI clients should stay on /chat/completions; other clients can fall
// back to its declared Claude target).
const targetFormat = useTransport?.format || modelTargetFormat || getTargetFormat(provider, credentials);
The per-model |
Closes #3418. Closes #3439.
Problem
chatCorepicks the upstream transport from the client'ssourceFormat, then lets a model-leveltargetFormatoverride the format the body is written in — but not the transport:runtimeTransportis whatDefaultExecutor.buildUrlandbuildHeadersread for the URL, the headers and the auth descriptor. So for an OpenAI-format request to a model pinned toclaude, 9Router serializes a Claude body and posts it to the provider's OpenAI endpoint with that endpoint's bearer auth.Upstreams parse what they recognize and drop the rest, so the failure is quiet and looks like a model problem:
minimax/MiniMax-M3answersI don't see any image attached. The reporter verified the same image and key work against bothPOST /v1/chat/completions(OpenAI content) andPOST /anthropic/v1/messages(Anthropic content) directly, which rules out the image, the credentials and M3's vision support.invalid params, invalid tool type: (2013), because Claude'sinput_schemareaches MiniMax's OpenAI endpoint whereparameterswas expected.Fix
Resolve the format and the transport in one place so they cannot disagree, and let the transport follow
targetFormatwhen a model pins one:Three models declare
targetFormaton a provider that has more than one endpoint:minimax/MiniMax-M3/v1/chat/completions/anthropic/v1/messagesminimax-cn/MiniMax-M3/v1/chat/completions/anthropic/v1/messagesxiaomi-tokenplan/mimo-v2.5-pro-claude/chat/completions, bearer/anthropic/v1/messages,x-api-keyNothing else changes: without a model-level
targetFormatthe expression is the previoususeTransport, and the per-modelsupportedFormatsguard is untouched — a model that does not declare a format still never gets that transport.I did not take the other half of #3439's proposal (dropping
targetFormat: "claude"from MiniMax-M3). That is a product decision about which wire format M3 should speak, and it is not needed to fix either report — the Anthropic endpoint handles both the image and the tool schema, as the #3418 control tests show. Same reasoning formimo-v2.5-pro-claude, whose whole point is to be Claude-native.Verification
tests/unit/upstream-route-alignment.test.jsderives the model list from the registry rather than hardcoding it, so a future pinned model is covered automatically (with a guard test so the table can't pass vacuously).Reverting the one-line fix to
const transport = useTransport;fails exactly the three alignment cases:Full offline suite (
vitest run unit auth translator,CI=true, excludingreal/), same command on both sides:origin/master(699edac)The failure sets are identical —
common the sorted full test names returns nothing in either direction. The 95 are pre-existing (windsurf/kimi-coding registry drift and their snapshots) and unrelated to this change.eslintclean on all three files.I have no MiniMax or Xiaomi Token Plan credentials, so I have not run a live request; what is verified here is that the body format and the endpoint now agree, which is the mechanism both issues identify.