Skip to content

fix: handle thinking model reasoning-only responses as valid content#11486

Open
neorrk wants to merge 4 commits intoRooCodeInc:mainfrom
neorrk:fix/thinking-model-empty-response-handling
Open

fix: handle thinking model reasoning-only responses as valid content#11486
neorrk wants to merge 4 commits intoRooCodeInc:mainfrom
neorrk:fix/thinking-model-empty-response-handling

Conversation

@neorrk
Copy link

@neorrk neorrk commented Feb 16, 2026

Summary

Thinking models (Kimi K2.5, DeepSeek-R1, QwQ, etc.) often produce responses containing only reasoning_content with no regular text content or tool_calls. This triggers the "The language model did not provide any assistant messages" error and unnecessary retries, even though the model did respond — just entirely in reasoning tokens.

This PR fixes five issues:

1. Reasoning-only responses treated as empty (Task.ts — first content gate)

When a thinking model returns only reasoning_content (no text, no tool calls), hasTextContent is false and hasToolUses is false, causing the response to be treated as completely empty. The fix adds hasReasoningContent to the validity check at line 3416 so reasoning-only responses are recognized as valid model output.

2. Second content gate missing hasReasoningContent (Task.ts — line 3567)

The first content gate was updated to include hasReasoningContent, but the second gate at line 3567 was not. This caused reasoning-only responses to save an assistant message to history but then fall through to the "no assistant messages" error path, incrementing consecutiveNoAssistantMessagesCount and leaving an orphaned assistant message in conversation history. Both gates now consistently check hasReasoningContent.

3. pWaitFor hang on reasoning-only responses (Task.ts)

When only reasoning content is present, assistantMessageContent is empty and presentAssistantMessage is never called, so userMessageContentReady stays false — causing pWaitFor to block forever. The fix sets userMessageContentReady = true directly when assistantMessageContent is empty, allowing the flow to proceed to the didToolUse check which will correctly prompt the model to use tools.

4. Missing reasoning field support in OpenAI streaming handler (openai.ts)

Ollama's /v1/chat/completions endpoint sends reasoning tokens under the reasoning field (not reasoning_content). The streaming handler only checked for reasoning_content, so users connecting through Ollama would lose all thinking output. The fix handles both field names.

5. Missing reasoning in non-streaming responses (openai.ts)

The non-streaming path didn't yield reasoning_content or reasoning at all, so thinking model responses via non-streaming mode would lose their reasoning output.

How It Was Discovered

Running Kimi K2.5 through an Ollama backend via the OpenAI Compatible provider. The model frequently returns reasoning + tool_calls with zero content text. Without this fix, every such response triggers the empty response error and retry loop, making the model unusable.

Test Plan

  • All 5233 existing tests pass (362 test files, 0 failures)
  • All pre-commit hooks pass (lint, prettier, type-check)
  • New unit tests for Ollama streaming patterns (NativeToolCallParser: 5 tests for single-chunk tool calls, finalization, multiple sequential calls)
  • New unit tests for OpenAI handler reasoning support (4 tests: reasoning_content, reasoning field, single-chunk tool call with non-standard ID, non-streaming reasoning + tool calls)
  • Verified both content gates (lines 3416 and 3567) consistently check hasReasoningContent
  • Verified pWaitFor does not hang when assistantMessageContent is empty (reasoning-only response)
  • Manual test with Kimi K2.5 via Ollama /v1/chat/completions — reasoning-only responses no longer trigger "no assistant messages" error or hang
  • Manual test with DeepSeek-R1 via OpenAI Compatible — reasoning is properly captured in both streaming and non-streaming modes

Fixes #10603
Related: #9959, #10064, #9551

🤖 Generated with Claude Code

Thinking models (Kimi K2.5, DeepSeek-R1, QwQ) may produce only
reasoning_content with no regular text content or tool calls. Previously
this was treated as an empty/failed response, triggering the "language
model did not provide any assistant messages" error and unnecessary
retries.

Changes:
- Task.ts: Include reasoningMessage in the content check so
  reasoning-only responses are recognized as valid model output
  instead of triggering the empty response error path
- openai.ts: Handle both "reasoning_content" (standard) and
  "reasoning" (Ollama /v1/) field names in streaming responses
- openai.ts: Yield reasoning content from non-streaming responses

Fixes RooCodeInc#10603
Related: RooCodeInc#9959, RooCodeInc#10064, RooCodeInc#9551

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Feb 16, 2026
Add comprehensive tests verifying correct handling of Ollama's
non-standard streaming format used by thinking models (kimi-k2.5, etc.):

- NativeToolCallParser: Test single-chunk tool calls with non-standard
  IDs (functions.read_file:0), finalization via both finalizeRawChunks
  and processFinishReason, multiple sequential tool calls
- OpenAI handler: Test reasoning_content and reasoning field extraction,
  single-chunk tool call with non-standard ID, non-streaming response
  with reasoning and tool calls

These tests verify the fix from the previous commit works correctly
with Ollama's /v1/ endpoint behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Feb 16, 2026
@roomote
Copy link
Contributor

roomote bot commented Feb 16, 2026

Rooviewer Clock   See task

Both previously flagged issues are now resolved. The pWaitFor hang is fixed by setting userMessageContentReady = true directly when assistantMessageContent is empty.

  • Task.ts: The second content gate at line 3567 (if (hasTextContent || hasToolUses)) is missing hasReasoningContent, so reasoning-only responses still enter the error/retry path and corrupt conversation history
  • Task.ts: Reasoning-only responses (no text, no tools) now enter the pWaitFor at line 3584, but presentAssistantMessage is never called when assistantMessageContent is empty, so userMessageContentReady stays false and the task hangs indefinitely
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

The first content gate (line 3416) was updated to include
hasReasoningContent, but the second gate (line 3567) was not.
This caused reasoning-only responses to save an assistant message
to history but then fall through to the "no assistant messages"
error path, incrementing the retry counter and leaving an orphaned
assistant message in conversation history.

Both gates now consistently check:
  if (hasTextContent || hasToolUses || hasReasoningContent)

Addresses review feedback from @roomote.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a thinking model returns only reasoning_content (no text, no
tool calls), assistantMessageContent is empty. Since no content
blocks exist, presentAssistantMessage is never called and
userMessageContentReady is never set to true, causing pWaitFor
to block indefinitely.

The fix sets userMessageContentReady = true directly when
assistantMessageContent is empty, allowing the flow to proceed
to the didToolUse check which will correctly prompt the model
to use tools.

Addresses second review from @roomote.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] kimi k2 thinking is not working on OpenAi compatible

1 participant

Comments