Skip to content

fix(openai): parse MiniMax thinking tags#3677

Merged
wenshao merged 6 commits into
QwenLM:mainfrom
shenyankm:sheny/fix-minimax-thinking-tags
May 5, 2026
Merged

fix(openai): parse MiniMax thinking tags#3677
wenshao merged 6 commits into
QwenLM:mainfrom
shenyankm:sheny/fix-minimax-thinking-tags

Conversation

@shenyankm

Copy link
Copy Markdown
Contributor

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:

    • Added a MiniMax OpenAI-compatible provider selected only for *.minimaxi.com endpoints.
    • Added provider-gated tagged thinking parsing for MiniMax <think> / <thinking> content.
    • Converted tagged MiniMax thinking content into thought: true parts.
    • Preserved existing reasoning_content / reasoning thought handling.
    • Prevented leading blank streaming chunks from rendering an empty assistant row in the CLI TUI.
  • Why it changed:

    • MiniMax OpenAI-compatible /v1 responses can place thinking content inside normal content using <think> or <thinking> tags.
    • The existing OpenAI-compatible converter only recognized reasoning_content / reasoning, so MiniMax thinking text leaked into the visible assistant response.
    • Some MiniMax streams also begin visible output with blank chunks, which could render the assistant icon on a separate empty row.
  • Reviewer focus:

    • Confirm tagged thinking parsing is enabled only for MiniMax *.minimaxi.com OpenAI-compatible endpoints.
    • Confirm non-MiniMax OpenAI-compatible providers still preserve normal XML/HTML-like text.
    • Confirm streaming parsing handles tags split across chunks.
    • Confirm leading blank stream chunks no longer create empty assistant rows.

Validation

  • Commands run:

    cd packages/core && npx vitest run src/core/openaiContentGenerator/converter.test.ts src/core/openaiContentGenerator/provider/minimax.test.ts src/core/openaiContentGenerator/pipeline.test.ts
    cd packages/cli && npx vitest run src/ui/hooks/useGeminiStream.test.tsx
    cd packages/core && npm run lint && npx tsc --noEmit
    cd packages/cli && npm run lint && npx tsc --noEmit
    npm run build
    npm run typecheck
    git diff --check
  • Prompts / inputs used:

    原神怎么你了?
    
  • Expected result:

    • MiniMax OpenAI-compatible <think> / <thinking> content is parsed as thought content.
    • Raw thinking tags are not shown in the final visible assistant response.
    • Non-MiniMax OpenAI-compatible providers keep tagged text unchanged.
    • The assistant icon aligns with the first visible response line instead of appearing on an empty row.
  • Observed result:

    • MiniMax tagged thinking content is split into thought parts.
    • Visible assistant output no longer contains raw <think> / <thinking> tags.
    • Streaming tags split across chunks, such as <thi + nk>, are handled correctly.
    • CLI output no longer renders an empty assistant row before the response.
  • Quickest reviewer verification path:

    cd packages/core && npx vitest run src/core/openaiContentGenerator/converter.test.ts src/core/openaiContentGenerator/provider/minimax.test.ts
    cd ../cli && npx vitest run src/ui/hooks/useGeminiStream.test.tsx
  • Evidence (output, logs, screenshots, video, JSON, before/after, etc.):

    Before:

image

After:
image

Test results:

  • packages/core: targeted OpenAI converter/provider/pipeline tests passed.
  • packages/cli: useGeminiStream.test.tsx passed.
  • npm run build: passed.
  • npm run typecheck: passed.
  • git diff --check: passed.

Scope / Risk

  • Main risk or tradeoff:

    • Tagged thinking parsing is intentionally gated to MiniMax *.minimaxi.com endpoints to avoid changing behavior for other OpenAI-compatible providers.
    • The CLI display fix drops leading blank lines before the first visible assistant/thought chunk, which avoids empty assistant rows and matches existing visible rendering expectations.
  • Not covered / not validated:

    • Full automated network E2E against the MiniMax API is not included in CI.
    • Manual MiniMax OpenAI-compatible runtime validation was performed locally with npm run dev.
    • Docker, Podman, Seatbelt, macOS, and Linux runtime paths were not tested for this change.
  • Breaking changes / migration notes:

    • None.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️ ⚠️
npx ⚠️ ⚠️
Docker ⚠️ ⚠️ ⚠️
Podman ⚠️ N/A N/A
Seatbelt ⚠️ N/A N/A

Testing matrix notes:

  • Validated on Windows with targeted npx vitest run commands, package-level npm run lint, package-level npx tsc --noEmit, root npm run build, and root npm run typecheck.
  • macOS and Linux were not tested locally.
  • Docker, Podman, and Seatbelt were not tested because this change is limited to TypeScript response conversion and CLI rendering logic.

Linked Issues / Bugs

Fixes #3669
Fixes #3228
Fixes #3387

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.
@shenyankm

Copy link
Copy Markdown
Contributor Author

I merged the latest upstream/main into this branch to resolve the merge conflict.

The conflict was in RequestContext, and the resolution keeps both sides:

  • MiniMax thinking-tags parsing fields from this PR
  • splitToolMedia support from upstream main

I also added support for the MiniMax international OpenAI-compatible endpoint:

  • China: https://api.minimaxi.com/v1
  • International: https://api.minimax.io/v1

Validation completed:

  • provider/minimax.test.ts and converter.test.ts passed
  • npm run lint passed
  • npm run build passed
  • npm run typecheck passed

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Downgraded from Approve to Comment: CI has 1 failing check (Qwen Code CI/Test on macos-latest, 24.x).

Comment thread packages/core/src/core/openaiContentGenerator/converter.ts
…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
@shenyankm shenyankm requested a review from wenshao May 4, 2026 06:51
Bojun-Vvibe added a commit to Bojun-Vvibe/oss-contributions that referenced this pull request May 4, 2026
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 wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@shenyankm

Copy link
Copy Markdown
Contributor Author

TaggedThinkingParser 缺少独立单元测试

@wenshao Thanks for the review! The suggested standalone unit test has been added in 15520c4:

  • Created \packages/core/src/core/openaiContentGenerator/taggedThinkingParser.test.ts\ with 23 test cases directly testing the 108-line state machine class
  • Covered all three requested boundary scenarios:
    • Empty tag content: <think>\ / <thinking>\
    • Close tags in text mode: </think>\ treated as normal text when no opening tag precedes it
    • Pure partial-tag-prefix chunk: <thi\ buffered across chunks, full-chunk prefix returning []\
  • Extra coverage: case insensitivity, 3+ chunk tag splitting, final flag flush behavior, static convenience method
  • All 23 new tests + 86 existing tests pass; TypeScript compilation clean

Ready for re-review.

@shenyankm shenyankm requested a review from wenshao May 4, 2026 13:55

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] TaggedThinkingParser 完全缺少可观测性 — 解析器在没有任何 debug 日志的情况下静默运行。当它消费 <think> / <thinking> 标签内容并将其转换为对用户不可见的 thought: true parts 时,没有任何日志记录;在流截断时刷新未关闭的 thought 缓冲区也没有警告。建议至少添加 debugLogger 调用来记录标签检测、parts 计数和未关闭缓冲区的刷新。

[Suggestion] reasoning_content / reasoningtaggedThinkingTags 的隐式耦合: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

Comment thread packages/core/src/core/openaiContentGenerator/provider/minimax.ts
…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)
@shenyankm

Copy link
Copy Markdown
Contributor Author

@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.

@shenyankm shenyankm requested a review from wenshao May 4, 2026 18:30

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@wenshao wenshao merged commit 5984540 into QwenLM:main May 5, 2026
13 checks passed
@shenyankm shenyankm deleted the sheny/fix-minimax-thinking-tags branch May 6, 2026 03:38
DragonnZhang pushed a commit that referenced this pull request May 8, 2026
* 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)
xaelistic pushed a commit to xaelistic/qwen-code that referenced this pull request Jun 7, 2026
* 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/bug Something isn't working as expected

Projects

None yet

3 participants