Bug description
When using OpenRouter with certain models (e.g. google/gemini-3.1-flash-lite-preview), the SDK throws:
[API Error: Model stream ended with empty response text.]
turns: 1
This happens when a tool call is made — pure text responses work fine.
Root cause
OpenRouter sends two consecutive SSE chunks with finish_reason: "tool_calls":
chunk[3] finish=tool_calls tool_calls=false delta={content:"", role:"assistant"}
chunk[4] finish=tool_calls tool_calls=false (duplicate, empty)
The streaming pipeline in pipeline.ts handles the first finish chunk (chunk[3]) correctly:
convertOpenAIChunkToGemini calls getCompletedToolCalls() → gets accumulated tool call → adds functionCall part ✓
streamingToolCallParser.reset() is called
handleChunkMerging stores the response as pendingFinishResponse ✓
Then chunk[4] arrives:
convertOpenAIChunkToGemini — parser was already reset → getCompletedToolCalls() returns [] → no functionCall parts ✗
handleChunkMerging overwrites pendingFinishResponse with the empty chunk[4] ✗
After the loop, the empty chunk[4] is yielded. processStreamResponse sees hasToolCall=false + hasFinishReason=true + empty contentText → throws InvalidStreamError("Model stream ended with empty response text.", "NO_RESPONSE_TEXT").
Affected code
packages/core/src/core/openaiContentGenerator/pipeline.ts, handleChunkMerging:
if (isFinishChunk) {
collectedGeminiResponses.push(response);
setPendingFinish(response); // ← always overwrites, even with empty duplicate
return false;
}
Fix
When a second finish chunk arrives and hasPendingFinish is already true, merge only usageMetadata and keep the candidates (with functionCall parts) from the first finish chunk.
See PR #… for the fix.
Reproduction
Configure the SDK with OpenRouter and a model that performs tool calls:
query({
prompt: "create a task",
options: {
authType: "openai",
model: "google/gemini-3.1-flash-lite-preview",
env: {
OPENAI_API_KEY: "sk-or-v1-...",
OPENAI_BASE_URL: "https://openrouter.ai/api/v1",
},
mcpServers: { /* any MCP server with tools */ },
},
});
// → [API Error: Model stream ended with empty response text.]
Bug description
When using OpenRouter with certain models (e.g.
google/gemini-3.1-flash-lite-preview), the SDK throws:This happens when a tool call is made — pure text responses work fine.
Root cause
OpenRouter sends two consecutive SSE chunks with
finish_reason: "tool_calls":The streaming pipeline in
pipeline.tshandles the first finish chunk (chunk[3]) correctly:convertOpenAIChunkToGeminicallsgetCompletedToolCalls()→ gets accumulated tool call → addsfunctionCallpart ✓streamingToolCallParser.reset()is calledhandleChunkMergingstores the response aspendingFinishResponse✓Then
chunk[4]arrives:convertOpenAIChunkToGemini— parser was already reset →getCompletedToolCalls()returns[]→ nofunctionCallparts ✗handleChunkMergingoverwritespendingFinishResponsewith the empty chunk[4] ✗After the loop, the empty
chunk[4]is yielded.processStreamResponseseeshasToolCall=false+hasFinishReason=true+ emptycontentText→ throwsInvalidStreamError("Model stream ended with empty response text.", "NO_RESPONSE_TEXT").Affected code
packages/core/src/core/openaiContentGenerator/pipeline.ts,handleChunkMerging:Fix
When a second finish chunk arrives and
hasPendingFinishis already true, merge onlyusageMetadataand keep thecandidates(withfunctionCallparts) from the first finish chunk.See PR #… for the fix.
Reproduction
Configure the SDK with OpenRouter and a model that performs tool calls: