bug(model): OpenAI-compatible streaming fails after tool calls when assistant content is omitted
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4k
- Forks
- 453
- Avg merge
- 12h 30m
- Merged PRs (30d)
- 46
Description
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+ assistanttool_calls+content: ""→200 OKstream: true+ assistanttool_callswithoutcontent→400 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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/model/providers/openai/request.ts and inspect how assistant messages with tool calls are serialized when normal content is empty. Reproduce the streaming request shape described in the issue, then verify that assistant tool-call messages include explicit empty content and that the follow-up request succeeds with the OpenAI-compatible provider.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100