fix(openai): parse MiniMax thinking tags#3677
Conversation
Add a MiniMax OpenAI-compatible provider that opts into tagged thinking parsing for minimaxi.com endpoints. Split MiniMax <think>/<thinking> content into thought parts while preserving default OpenAI-compatible behavior, and avoid rendering leading blank stream chunks as empty assistant rows.
Merge upstream/main into the MiniMax thinking-tags branch. Resolve the RequestContext conflict by preserving both MiniMax response parsing fields and upstream splitToolMedia support. Recognize both official MiniMax OpenAI-compatible API hosts: api.minimaxi.com and api.minimax.io.
|
I merged the latest The conflict was in
I also added support for the MiniMax international OpenAI-compatible endpoint:
Validation completed:
|
wenshao
left a comment
There was a problem hiding this comment.
…thinking parser - perf(taggedThinkingParser): pre-compute lowercase buffer once per parse() call, use startsWith(tag, offset) to avoid O(N²) per-char slice+toLowerCase allocations - feat(minimax): expand host matching from exact Set to *.minimaxi.com / *.minimax.io wildcard suffix, covering gateway / custom subdomains - docs(converter): add comment clarifying finish_reason flush of buffered tagged-thinking content on stream end
QwenLM/qwen-code#3677 (minimax thinking tag parser + blank-chunk UI fix), aaif-goose/goose#8984 (officeqa/minimax blog). INDEX.md backfills drip-333 (which was missed) plus drip-334.
wenshao
left a comment
There was a problem hiding this comment.
Follow-up review on 814ea0d: previous review findings (O(N²) tag matching, finish_reason flush comment) have been addressed. All CI checks pass.
Remaining suggestion:
- TaggedThinkingParser 缺少独立单元测试 — 107 行的状态机类仅通过 converter.test.ts 间接测试。建议按项目约定创建
taggedThinkingParser.test.ts,覆盖:空标签内容、text 模式下遇</think>(应为普通文本)、纯 partial-tag-prefix chunk 等边界场景。
— deepseek-v4-pro via Qwen Code /review
Add taggedThinkingParser.test.ts with 23 test cases covering the boundary scenarios requested in PR review #4219047370: empty tag content, close tags in text mode, pure partial-tag-prefix chunks, multi-chunk splitting, final flag flush behavior, and case insensitivity.
@wenshao Thanks for the review! The suggested standalone unit test has been added in 15520c4:
Ready for re-review. |
wenshao
left a comment
There was a problem hiding this comment.
[Critical] TaggedThinkingParser 完全缺少可观测性 — 解析器在没有任何 debug 日志的情况下静默运行。当它消费 <think> / <thinking> 标签内容并将其转换为对用户不可见的 thought: true parts 时,没有任何日志记录;在流截断时刷新未关闭的 thought 缓冲区也没有警告。建议至少添加 debugLogger 调用来记录标签检测、parts 计数和未关闭缓冲区的刷新。
[Suggestion] reasoning_content / reasoning 与 taggedThinkingTags 的隐式耦合:convertOpenAIChunkToGemini 中现有的 reasoning_content 提取(约第 1019 行)无条件添加 thought parts,而新增的 convertOpenAITextToParts 独立从标签中解析 thought parts。如果未来有 provider 同时启用 taggedThinkingTags 并返回 reasoning_content,thought 内容会静默重复。建议在提取 reasoning_content 前检查 !requestContext.responseParsingOptions?.taggedThinkingTags。
— deepseek-v4-pro via Qwen Code /review
…feguard for MiniMax tagged thinking - Add debugLogger.warn on unclosed thought flush (observability) - Add debugLogger.debug for tag detection and parts count - Add cross-matching tag comment explaining binary mode toggle - Add MINIMAX_KNOWN_HOSTS exact match layer with suffix fallback - Add reasoning_content guard to avoid duplicating thought parts - Add cross-matching and unclosed flush tests (+4 cases) - Add known host exact match and custom proxy tests (+4 cases)
|
@wenshao All review comments from #4221337642 have been addressed in commit 30d5d66: Observability (Critical): Added debugLogger.warn on unclosed thought flush + debugLogger.debug for tag detection and parts count in taggedThinkingParser.ts. Cross-matching comment: Added comment explaining the binary mode toggle design at L12-14. MiniMax host detection: Added MINIMAX_KNOWN_HOSTS exact match layer (api.minimaxi.com, api.minimax.io) before suffix fallback in minimax.ts. reasoning_content safeguard: Added guard in both convertOpenAIResponseToGemini and convertOpenAIChunkToGemini to skip reasoning_content extraction when taggedThinkingTags is enabled, avoiding duplicate thought parts. Test coverage: +8 tests (cross-matching x2, unclosed flush x2, known hosts x2, custom proxy x2). All 116 tests pass, typecheck clean. Ready for re-review. |
wenshao
left a comment
There was a problem hiding this comment.
No issues found in the PR diff. LGTM! ✅
Note: tsc --noEmit reports 2 pre-existing type errors (ServerGeminiEventType.Text → should be Content) in useGeminiStream.test.tsx:3067,3373, but these are inherited from the merge-base (commit 431a87c) and not introduced by this PR.
— deepseek-v4-pro via Qwen Code /review
* fix(openai): parse MiniMax thinking tags Add a MiniMax OpenAI-compatible provider that opts into tagged thinking parsing for minimaxi.com endpoints. Split MiniMax <think>/<thinking> content into thought parts while preserving default OpenAI-compatible behavior, and avoid rendering leading blank stream chunks as empty assistant rows. * fix(openai): address PR #3677 review — optimize MiniMax tagged thinking parser - perf(taggedThinkingParser): pre-compute lowercase buffer once per parse() call, use startsWith(tag, offset) to avoid O(N²) per-char slice+toLowerCase allocations - feat(minimax): expand host matching from exact Set to *.minimaxi.com / *.minimax.io wildcard suffix, covering gateway / custom subdomains - docs(converter): add comment clarifying finish_reason flush of buffered tagged-thinking content on stream end * test(openai): add standalone unit tests for TaggedThinkingParser Add taggedThinkingParser.test.ts with 23 test cases covering the boundary scenarios requested in PR review #4219047370: empty tag content, close tags in text mode, pure partial-tag-prefix chunks, multi-chunk splitting, final flag flush behavior, and case insensitivity. * fix(openai): add observability, host guards, and reasoning_content safeguard for MiniMax tagged thinking - Add debugLogger.warn on unclosed thought flush (observability) - Add debugLogger.debug for tag detection and parts count - Add cross-matching tag comment explaining binary mode toggle - Add MINIMAX_KNOWN_HOSTS exact match layer with suffix fallback - Add reasoning_content guard to avoid duplicating thought parts - Add cross-matching and unclosed flush tests (+4 cases) - Add known host exact match and custom proxy tests (+4 cases)
* fix(openai): parse MiniMax thinking tags Add a MiniMax OpenAI-compatible provider that opts into tagged thinking parsing for minimaxi.com endpoints. Split MiniMax <think>/<thinking> content into thought parts while preserving default OpenAI-compatible behavior, and avoid rendering leading blank stream chunks as empty assistant rows. * fix(openai): address PR QwenLM#3677 review — optimize MiniMax tagged thinking parser - perf(taggedThinkingParser): pre-compute lowercase buffer once per parse() call, use startsWith(tag, offset) to avoid O(N²) per-char slice+toLowerCase allocations - feat(minimax): expand host matching from exact Set to *.minimaxi.com / *.minimax.io wildcard suffix, covering gateway / custom subdomains - docs(converter): add comment clarifying finish_reason flush of buffered tagged-thinking content on stream end * test(openai): add standalone unit tests for TaggedThinkingParser Add taggedThinkingParser.test.ts with 23 test cases covering the boundary scenarios requested in PR review #4219047370: empty tag content, close tags in text mode, pure partial-tag-prefix chunks, multi-chunk splitting, final flag flush behavior, and case insensitivity. * fix(openai): add observability, host guards, and reasoning_content safeguard for MiniMax tagged thinking - Add debugLogger.warn on unclosed thought flush (observability) - Add debugLogger.debug for tag detection and parts count - Add cross-matching tag comment explaining binary mode toggle - Add MINIMAX_KNOWN_HOSTS exact match layer with suffix fallback - Add reasoning_content guard to avoid duplicating thought parts - Add cross-matching and unclosed flush tests (+4 cases) - Add known host exact match and custom proxy tests (+4 cases)
Add a MiniMax OpenAI-compatible provider that opts into tagged thinking parsing for minimaxi.com endpoints.
Split MiniMax
<think>/<thinking>content into thought parts while preserving default OpenAI-compatible behavior, and avoid rendering leading blank stream chunks as empty assistant rows.Summary
What changed:
*.minimaxi.comendpoints.<think>/<thinking>content.thought: trueparts.reasoning_content/reasoningthought handling.✦row in the CLI TUI.Why it changed:
/v1responses can place thinking content inside normalcontentusing<think>or<thinking>tags.reasoning_content/reasoning, so MiniMax thinking text leaked into the visible assistant response.Reviewer focus:
*.minimaxi.comOpenAI-compatible endpoints.Validation
Commands run:
Prompts / inputs used:
Expected result:
<think>/<thinking>content is parsed as thought content.✦aligns with the first visible response line instead of appearing on an empty row.Observed result:
<think>/<thinking>tags.<thi+nk>, are handled correctly.Quickest reviewer verification path:
Evidence (output, logs, screenshots, video, JSON, before/after, etc.):
Before:
After:

Test results:
packages/core: targeted OpenAI converter/provider/pipeline tests passed.packages/cli:useGeminiStream.test.tsxpassed.npm run build: passed.npm run typecheck: passed.git diff --check: passed.Scope / Risk
Main risk or tradeoff:
*.minimaxi.comendpoints to avoid changing behavior for other OpenAI-compatible providers.Not covered / not validated:
npm run dev.Breaking changes / migration notes:
Testing Matrix
Testing matrix notes:
npx vitest runcommands, package-levelnpm run lint, package-levelnpx tsc --noEmit, rootnpm run build, and rootnpm run typecheck.Linked Issues / Bugs
Fixes #3669
Fixes #3228
Fixes #3387