Skip to content

[BUG] google/gemini-3.1-pro-preview via OpenRouter: assistant content[] stripped on Turn 2 → "no assistant messages" on every follow-up #11629

@sewasti

Description

@sewasti

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

  1. Configure Roo with API Provider = OpenRouter, model = google/gemini-3.1-pro-preview
  2. Send any first message (e.g. "test")
  3. Roo responds correctly (Turn 1 works)
  4. Send any follow-up message (e.g. "another test")
  5. → 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:

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


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.text type mislabeling (stored as reasoning.encrypted) is a secondary bug worth fixing to avoid future confusion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions