Skip to content

bug(model): OpenAI-compatible streaming fails after tool calls when assistant content is omitted #263

Description

@DemonswhisperYe

Problem

OpenAI-compatible streaming requests can fail on the second turn after a tool call when the historical assistant tool-call message omits content.

Observed provider error:

{"error":{"message":"Instructions are required","type":"invalid_request_error"}}

The error is misleading: the request does include a system message/instructions. The failure is triggered by the serialized assistant tool-call history shape.

Reproduction shape

A multi-turn streaming request after one tool call can contain a historical assistant message like this:

{
  "role": "assistant",
  "tool_calls": [
    {
      "id": "call_test_1",
      "type": "function",
      "function": {
        "name": "noop",
        "arguments": "{}"
      }
    }
  ]
}

When sent to an OpenAI-compatible provider in stream: true mode, this can fail with HTTP 400 and Instructions are required.

Adding an explicit empty content field fixes the same request:

{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_test_1",
      "type": "function",
      "function": {
        "name": "noop",
        "arguments": "{}"
      }
    }
  ]
}

Local verification

With the same OpenAI-compatible provider and request body:

  • stream: true + assistant tool_calls + content: ""200 OK
  • stream: true + assistant tool_calls without content400 Instructions are required

Non-streaming mode may be more permissive, which makes this easy to miss if only non-streaming compatibility is tested.

Root cause

src/model/providers/openai/request.ts serializes assistant messages that only contain tool calls without setting content.

Current behavior is effectively:

content: normalContent.length > 0
  ? toOpenAIContent(normalContent)
  : (message.role === "assistant" && thinkingBlocks.length > 0 ? "" : undefined),

So assistant messages with assistantToolCalls.length > 0 and no normal text content get content: undefined.

Expected behavior

For OpenAI-compatible request serialization, assistant messages with tool_calls should include explicit empty string content:

"content": ""

This is more compatible across OpenAI-compatible providers and avoids streaming failures after tool calls.

Suggested fix

Set content: "" for assistant messages when either thinking blocks or tool calls are present but normal content is empty, for example:

content: normalContent.length > 0
  ? toOpenAIContent(normalContent)
  : (message.role === "assistant" && (thinkingBlocks.length > 0 || assistantToolCalls.length > 0) ? "" : undefined),

Impact

This affects any agent flow that performs a tool call and then continues with a streaming OpenAI-compatible provider. In Always-On discovery, the run failed immediately after a simple glob tool call because the follow-up streaming request hit this provider validation error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions