-
Notifications
You must be signed in to change notification settings - Fork 3k
Description
Summary
Every follow-up message to google/gemini-3.1-pro-preview (added in v3.50.0) via OpenRouter fails with one of:
"Unexpected API Response: The language model did not provide any assistant messages.""The language model returned an empty response without any text or tool calls."
The first turn always works. Any second turn (follow-up) always fails. Retry also fails.
Environment
| Field | Value |
|---|---|
| Roo Code version | 3.50.1 |
| API Provider | OpenRouter (with custom base URL) |
| Model | google/gemini-3.1-pro-preview |
| OS | Windows (client) |
| Enable reasoning | ✅ enabled, Max Thinking Tokens: 20480 |
| Max Tokens | 59392 (= 65536 in wire format) |
Steps to Reproduce
- Configure Roo with API Provider = OpenRouter, model =
google/gemini-3.1-pro-preview - Send any first message (e.g.
"test") - Roo responds correctly (Turn 1 works)
- Send any follow-up message (e.g.
"another test") - → Error: "The language model did not provide any assistant messages."
Reproducible: 100% of the time.
Root Cause (Confirmed via Proxy Request Logging)
The bug was confirmed by intercepting the exact HTTP request Roo sends to OpenRouter.
Actual structure of the Turn 2 API request (what Roo sends):
msg[0]: role=system, content="<system prompt>", reasoning_details=[]
msg[1]: role=user, content=[text, text], reasoning_details=[] ← original prompt + env_details
msg[2]: role=assistant, content=[], reasoning_details=[encrypted] ← BUG: tool_use IS MISSING
msg[3]: role=tool, content="<tool result>", reasoning_details=[] ← tool_result has no matching tool_call_id
msg[4]: role=user, content=[text], reasoning_details=[] ← retry notice
The problem: msg[2].content is [] (empty). The tool_use block (e.g. attempt_completion) is stripped/lost when Roo converts the conversation history to the OpenRouter wire format. Only reasoning_details is preserved.
The subsequent role=tool message (msg[3]) references a tool_use_id that doesn't exist in msg[2] (because the tool_use block was stripped). OpenRouter/Gemini sees a tool result without a matching tool call → returns empty content or 400 error.
Verification: Manual API calls to OpenRouter prove the fix:
# FAILS (content=[]):
POST /api/v1/chat/completions
{
"messages": [
{"role": "user", "content": "test"},
{"role": "assistant", "content": [], "reasoning_details": [{"type": "reasoning.encrypted", "data": "..."}]},
{"role": "user", "content": "follow-up"}
]
}
# → {"error": {"message": "Internal Server Error", "code": 500}}
# WORKS (content=[tool_use...]):
POST /api/v1/chat/completions
{
"messages": [
{"role": "user", "content": "test"},
{"role": "assistant", "content": [{"type": "tool_use", "id": "tc1", "name": "attempt_completion", "input": {...}}], "reasoning_details": [...]},
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tc1", ...}, ...]}
]
}
# → HTTP 200, "content": "Test erfolgreich bestanden! ..."Secondary Bug: reasoning.text stored as reasoning.encrypted
When the Gemini response contains both a reasoning.text item and a reasoning.encrypted item in reasoning_details, Roo stores both with type: "reasoning.encrypted". The reasoning.text item (which has a text field) is incorrectly labeled:
What the API returns:
"reasoning_details": [
{"type": "reasoning.text", "text": "**Initiating the Analysis**..."},
{"type": "reasoning.encrypted", "data": "EtwECtkEAb4+9vu..."}
]What Roo stores in history:
"reasoning_details": [
{"type": "reasoning.encrypted", "text": "**Initiating the Analysis**...", "id": "...", "format": "google-gemini-v1", "index": 0}, ← WRONG type
{"type": "reasoning.encrypted", "data": "EtwECtkEAb4+9vu...", "id": "...", "format": "google-gemini-v1", "index": 0} ← correct
]When Roo builds Turn 2, it sends only the correctly-typed encrypted item (1 item), dropping the mislabeled text item. This is a secondary bug but may have additional effects.
Debug JSON (from Roo Debug Mode)
Roo's internal history correctly shows the tool_use in the assistant content:
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "tool_attempt_completion_BI9Id6PH8KtbhYBnuB4I",
"name": "attempt_completion",
"input": {"result": "Test #3 received successfully."}
}
],
"reasoning_details": [
{
"type": "reasoning.encrypted",
"text": "**Acknowledging the Input**\n\nI see this as a straightforward test message...",
"id": "tool_attempt_completion_BI9Id6PH8KtbhYBnuB4I",
"format": "google-gemini-v1",
"index": 0
},
{
"type": "reasoning.encrypted",
"data": "EvEDCu4DAb4+9vu+3tsIWQYYKUIkx+qC3vmyhKRk5W...(truncated)...",
"id": "tool_attempt_completion_BI9Id6PH8KtbhYBnuB4I",
"format": "google-gemini-v1",
"index": 0
}
]
}But the actual wire request (captured via proxy logging) sends content: []:
[DEBUG-GEMINI] msg[2] role=assistant content_types=[] reasoning_details=[('reasoning.encrypted', 'data')]
msg[2].content_types=[] → the tool_use block is missing in the outbound request.
Likely Affected Code Path
google/gemini-3.1-pro-preview was added in v3.50.0 (PR #11608). The bug is in the OpenRouter API handler's message conversion logic — specifically where it converts Roo's internal tool_use content blocks to the Gemini/OpenRouter native format. The conversion strips the content array but preserves reasoning_details.
Relevant prior fixes that may be related:
- v3.40.0 PR fix: correct Gemini 3 thought signature injection format via OpenRouter #10640: "Fix: Correct Gemini 3 thought signature injection format via OpenRouter"
- v3.39.2 PR fix: ensure assistant message content is never undefined for Gemini compatibility #10559: "Fix: Ensure assistant message content is never undefined for Gemini compatibility"
- v3.41.0 PR fix(litellm): inject dummy thought signatures on ALL tool calls for Gemini #10743: "Fix: Inject dummy thought signatures on ALL tool calls for Gemini models"
None of these appear to have handled the specific case of content=[] (completely empty list) when a tool_use block should be present.
Expected Behavior
The assistant message sent to OpenRouter should preserve the tool_use content block:
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "tool_attempt_completion_BI9Id6PH8KtbhYBnuB4I",
"name": "attempt_completion",
"input": {"result": "Test #3 received successfully."}
}
],
"reasoning_details": [
{"type": "reasoning.encrypted", "data": "...", "format": "google-gemini-v1", "index": 0}
]
}Workaround
None available. The model google/gemini-3.1-pro-preview is unusable in Roo v3.50.1 for any multi-turn conversation.
Using google/gemini-2.5-pro or anthropic/claude-sonnet-4.6 via OpenRouter as alternatives until fixed.
Related Issues
- [BUG] Gemini 3 pro causes provider errors #11492 — "[BUG] Gemini 3 pro causes provider errors" (similar error message, but different model/provider/version and intermittent, not 100% reproducible; our bug has a confirmed wire-format root cause)
- [BUG] Gemini 3 on OpenRouter API tool result error - All Tools Broken #10307 — "[BUG] Gemini 3 on OpenRouter API tool result error" (closed; was about missing thought_signature, different error)
Notes for Roo Team
- The bug is not in the OpenRouter proxy or the model itself — confirmed by manual API calls.
- The bug is in Roo's wire format construction for Turn 2+ with Gemini models that use
reasoning_details. - The
reasoning.texttype mislabeling (stored asreasoning.encrypted) is a secondary bug worth fixing to avoid future confusion.