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 content → 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:
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.
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: truemode, this can fail with HTTP 400 andInstructions 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 requiredNon-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.tsserializes assistant messages that only contain tool calls without settingcontent.Current behavior is effectively:
So assistant messages with
assistantToolCalls.length > 0and no normal text content getcontent: undefined.Expected behavior
For OpenAI-compatible request serialization, assistant messages with
tool_callsshould include explicit empty string 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: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
globtool call because the follow-up streaming request hit this provider validation error.