fix(providers): MiniMax-M3 reasoning leaks into delta.content instead of reasoning_content (#13558) - #13799
Merged
diegosouzapw merged 2 commits intoSep 16, 2026
Conversation
… of reasoning_content (#13558) MiniMax-M3's Anthropic-compatible endpoint (minimax/minimax-cn, targetFormat: "claude") puts its reasoning inline as <think>...</think> inside ordinary text/text_delta blocks instead of a structured thinking block. Neither Claude->OpenAI translator (claude-to-openai.ts streaming, responseTranslator.ts non-streaming) had any <think>-tag awareness for plain text content, and isTextualReasoningTagNativeRoute() explicitly excluded the minimax/minimax-cn provider ids from the textual-reasoning gate on the false assumption that their wire format meant reasoning already arrived natively. Wires the existing think-tag parser (thinkTagParser.ts) into both translators, gated by shouldParseTextualReasoningTags(), and stops excluding minimax/minimax-cn from the M3 route match. Regression test: tests/unit/issue-13558-minimax-m3-think-leak.test.ts Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
… of reasoning_content (diegosouzapw#13558) (diegosouzapw#13799) Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
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.
Closes #13558
Root cause
MiniMax-M3's Anthropic-compatible endpoint (
minimax/minimax-cn,targetFormat: "claude") putsits reasoning inline as
<think>...</think>inside ordinarytext/text_deltacontent blocksinstead of a structured Anthropic
thinking/thinking_deltablock. Two problems compounded this:<think>-tag awareness for plaintextcontent —they only special-cased the structured
thinkingblock type(
open-sse/translator/response/claude-to-openai.tsfor streaming,open-sse/handlers/responseTranslator.tsfor non-streaming).isTextualReasoningTagNativeRoute()(open-sse/handlers/responseSanitizer/reasoning.ts)explicitly excluded the
minimax/minimax-cnprovider ids from the textual-reasoning-taggate, on the assumption that speaking Claude's wire format meant reasoning already arrived
natively as a
thinkingblock — true for other MiniMax models, false for M3.The issue's own proposed fix (adding
minimax-cn/MiniMax-M3to the route list consulted byclaude-to-openai.ts) would have been a no-op: that list only feedsthinkTagParser'spassthrough-mode gate, and minimax/minimax-cn never run in passthrough mode — they're on the
Claude-wire-format translate path.
Fix
open-sse/handlers/responseSanitizer/reasoning.ts:isTextualReasoningTagNativeRoute()nolonger excludes
minimax/minimax-cnfrom the M3 pattern — only non-M3 minimax models stayunaffected now.
open-sse/translator/response/claude-to-openai.ts(streaming): reuses the existinginitThinkState/applyThinkTag/flushThinkBuffertextual think-tag parser (already used by thepassthrough path) on
text_deltachunks, gated byshouldParseTextualReasoningTags(provider, model). Buffers correctly across a<think>/</think>tag split over multiple deltas, andflushes any tail left in the buffer at
content_block_stop.open-sse/handlers/responseTranslator.ts(non-streaming,targetFormat === FORMATS.CLAUDEblock): runs the accumulated text content through
extractThinkingFromContent(), gated the sameway, merging any extracted reasoning into
thinkingContentalongside the existingstructured-
thinking-block accumulation.Scope note vs. the plan-file: the plan suggested threading
provideras a new parameter throughtranslateNonStreamingResponse()(touching 5 call sites) so the non-streaming gate could see thereal provider id. That thread isn't needed for correctness here —
isTextualReasoningTagNativeRoute'snon-exclusion clause already matches on the model-scoped
minimax-m3pattern regardless ofprovider identity (verified: with
providerunset it behaves identically to a realminimax/minimax-cnid for this model pattern), so the fix stays self-contained in the twotranslators without expanding the call-site surface.
Regression test
tests/unit/issue-13558-minimax-m3-think-leak.test.ts— 3 cases: non-streaming translation,streaming translation (chunk-by-chunk), and a streaming case where the
<think>/</think>tagsare split across two deltas (buffering boundary).
RED (on unfixed code, all 3 fail):
GREEN (with the fix):
Gates run
npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>→ exit 0, no new warnings.npm run check:open-sse-typecheck→ clean (all touched files are underopen-sse/).node scripts/check/check-file-size.mjs→ no violations on touched files.node scripts/check/check-complexity.mjs/node scripts/check/check-cognitive-complexity.mjs→ clean on touched files.node scripts/check/check-test-discovery.mjs→ new test file discovered ([test-discovery] OK).claude-to-openai.ts/responseTranslator.ts/thinkTagParser.ts/reasoning.ts(20 files, 272 cases) — all green after one alignment (below).Existing tests aligned
tests/unit/responsesanitizer-reasoning-split.test.tshad a describe block titled "MiniMax M3 fixregression guards" that asserted
isTextualReasoningTagNativeRoute("minimax"/"minimax-cn", "minimax-m3")wasfalse— i.e. it encoded the exact old/buggy exclusion this PR fixes. Flippedboth assertions to
true(renamed theit()titles to match), updated the surrounding comment,and added one new case confirming non-M3 minimax models on those same tiers stay unaffected. No
assertion was weakened or removed — the two flipped assertions now match the corrected contract,
and the file's other MiniMax-M3-on-OpenAI-format-tier cases are untouched.
Sibling issues checked
#9155 / #12132 (referenced in the original issue) are about the request side
(
thinking.typeacceptance) and are untouched by this response-side-only fix.