Repository navigation
fix(sse): drop reasoning alias once reasoning_content carries the replay - #16646
Merged
diegosouzapw merged 2 commits intoOct 15, 2026
Merged
Conversation
Reasoning-replay targets (z-ai GLM thinking, Moonshot Kimi) use serde schemas that alias `reasoning` to `reasoning_content`. An assistant turn carrying both keys - sent by clients that cover either name, or produced when OmniRoute promotes `reasoning` into `reasoning_content` - was forwarded with both and rejected upstream with 400 "duplicate field `reasoning_content`". replayOpenAIReasoningMessage now deletes a string `reasoning` once `reasoning_content` is non-empty, keeping `reasoning_content` as the single canonical carrier. Reasoning-only turns are still promoted. Reported-by: likongkong666 (decolua/9router#4841)
Contributor
CI Coverage Report
Coverage artifact was not available for this run. |
diegosouzapw
deleted the
fix/port-issue-4841-reasoning-alias-collision
branch
October 18, 2026 23:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reasoning-replay targets such as z-ai GLM thinking models and Moonshot Kimi (
kimi-k2.x,kimi-k3) use Rust/serde request schemas that declarereasoningas an alias ofreasoning_content. When an assistant turn in the replayed history carried both keys, OmniRoute forwarded both and the upstream rejected the request with400 Failed to deserialize the JSON body ... duplicate field reasoning_content.Both keys reached the upstream in two ways:
reasoning.replayOpenAIReasoningMessagecopied it intoreasoning_contentand leftreasoningin place.filterToOpenAIFormatkeeps tool-call assistant turns intact whenpreserveReasoningContentis set. TheOPENAI_INCOMPATIBLE_ECHO_FIELDSstrip only runs for non-reasoner targets. Nothing removed the alias on the reasoner path.The reasoning input policy (
open-sse/services/reasoningInputPolicy.ts) only deletes fields, and it runs beforetranslateRequest. For plaintext transports it leaves both keys untouched, so those requests also hit this replay step and are covered by the same fix. This likely also fixes the related duplicate-field report about GLM-5.x thinking combos.Fix
In
replayOpenAIReasoningMessage(open-sse/translator/index.ts), right after thereasoning→reasoning_contentpromotion: ifreasoning_contentis non-empty andreasoningis a string, deletereasoning.reasoning_contentbecomes the single canonical carrier. Turns that only carryreasoningare still promoted, exactly as before. Partial (prefix) Moonshot messages and non-reasoner targets are unchanged.Attribution
Thanks to @likongkong666 for the original report.
Test plan
tests/unit/translator-reasoning-alias-collision-4841.test.ts(glmglm-4.6-thinking, moonshotkimi-k2.5, moonshotkimi-k3; both-keys and reasoning-only tool-call turns): 6/6 fail on base, 6/6 pass after the fixtests/unit+tests/integrationnode:test file touchingtranslateRequest/translator/index/reasoningInputPolicy/reasoningCachewith reasoning fields: 756/758 pass. The 2 failures aretests/integration/video-retention-http-12150.test.ts→ "network guard blocks other loopback ports" (ECONNREFUSEDvsUND_MOCK_ERR_MOCK_NOT_MATCHED) and its parent. They fail the same way with the basetranslator/index.ts, so they are pre-existing and environmental.npx eslintandnpx prettier --checkon the changed filesnpm run typecheck:core