When opencode replays conversation history to an OpenAI-compatible API endpoint, assistant messages that originally contained tool calls are sent with only {role: "assistant", content: "..."} — the tool_calls field is completely absent. The corresponding role: "tool" result messages are present with valid tool_call_id values, creating orphaned tool results with no matching tool call.
This breaks any OpenAI-compatible proxy, gateway, or provider that relies on the tool_calls ↔ tool_call_id pairing to reconstruct the conversation structure.
Expected behavior
When the model generates a tool call and the result is returned, the next request to the API should replay the full conversation including:
{
"role": "assistant",
"content": "I'll explore the project structure.",
"tool_calls": [
{
"id": "tool_rvzW22DjYhyE1u7kAvtxJEkP",
"type": "function",
"function": {
"name": "bash",
"arguments": "{\"cmd\": \"ls -la\"}"
}
}
]
}
followed by:
{
"role": "tool",
"tool_call_id": "tool_rvzW22DjYhyE1u7kAvtxJEkP",
"content": "total 88\ndrwxr-xr-x@ 10 bymiller staff 320 Apr 21 19:20 .\n..."
}
Actual behavior
The assistant message is sent without tool_calls:
{
"role": "assistant",
"content": "I'll start by exploring the project structure and examining the key files."
}
The tool result message IS correctly sent:
{
"role": "tool",
"tool_call_id": "tool_rvzW22DjYhyE1u7kAvtxJEkP",
"content": "/Users/bymiller/src/task\ntotal 88\ndrwxr-xr-x@ 10 bymiller staff 320 Apr 21 19:20 .\n..."
}
Diagnostic evidence
I added logging at the entry point of our OpenAI-compatible proxy to inspect the raw messages from opencode. Here is exactly what we see:
Assistant messages (sampled first 3):
[
{
"keys": ["role", "content"],
"hasToolCalls": false,
"hasFunctionCall": false,
"contentType": "string",
"contentIsArray": false,
"contentSnippet": "I'll start by exploring the project structure and examining the key files."
},
{
"keys": ["role", "content"],
"hasToolCalls": false,
"hasFunctionCall": false,
"contentType": "string",
"contentIsArray": false,
"contentSnippet": ""
},
{
"keys": ["role", "content"],
"hasToolCalls": false,
"hasFunctionCall": false,
"contentType": "string",
"contentIsArray": false,
"contentSnippet": "\n\n[Upstream provider error — retrying may help]"
}
]
Tool result messages (sampled first 2):
[
{
"keys": ["role", "content", "tool_call_id"],
"tool_call_id": "tool_rvzW22DjYhyE1u7kAvtxJEkP",
"contentSnippet": "/Users/bymiller/src/task\ntotal 88\ndrwxr-xr-x@ 10 bymiller staff 320 Apr 21 19:20 ."
},
{
"keys": ["role", "content", "tool_call_id"],
"tool_call_id": "tool_iGCywJow7YLARoc0Z7BzJDxL",
"contentSnippet": "The glob tool was called with invalid arguments: [..."
}
]
Aggregate counts across the full request (192 messages):
role counts: system=1, user=6, assistant=66, tool=119
assistant with tool_calls: 0 ← should be ~40-50
tool messages: 119 ← all orphaned, no matching call
tool_call IDs found: 0
tool_result IDs found: 119
orphaned results: 119 (all of them)
Every single tool result is orphaned because zero assistant messages carry the tool_calls field.
Impact
Any OpenAI-compatible backend that validates tool-call/result pair integrity will either:
- Strip all tool results as orphaned (what our proxy does to stay compliant with the Vercel AI SDK's
AI_MissingToolResultsError validation), leaving the model with zero tool history — it then loops endlessly calling tools but never seeing results
- Reject the request outright as malformed
Environment
❯ opencode --version
1.14.22
- Provider: OpenAI-compatible custom endpoint
- Model: Any (tested with MiniMax M2.7 and Kimi)
Possible location in source
Based on the web search, opencode uses the Vercel AI SDK internally. The conversion from internal ModelMessage format (where tool calls are content: [{type: "tool-call", toolCallId, toolName, input}]) to OpenAI wire format (where tool calls should be in the tool_calls field) may be incomplete. The history replay path likely serializes assistant messages without extracting the tool_calls from the AI SDK's content array format.
PR #14456 (repairAssistantMessages) and PR #16531 (OpenAI-compatible provider layer) are related but don't appear to address this specific issue of tool_calls being absent in the outbound history replay.
Plugins
No response
OpenCode version
1.14.22
Steps to reproduce
How to reproduce
- Configure opencode to use a custom OpenAI-compatible endpoint (e.g. a local proxy that logs raw requests)
- Start a session that triggers tool use (e.g. "list the files in this directory")
- Let the model call a tool and receive a result
- On the next request (turn 2+), inspect the raw JSON body sent to the endpoint
- Observe that assistant messages have only
role and content keys — tool_calls is missing
Screenshot and/or share link
No response
Operating System
mac os 26.0.1
Terminal
iterm2
When opencode replays conversation history to an OpenAI-compatible API endpoint, assistant messages that originally contained tool calls are sent with only
{role: "assistant", content: "..."}— thetool_callsfield is completely absent. The correspondingrole: "tool"result messages are present with validtool_call_idvalues, creating orphaned tool results with no matching tool call.This breaks any OpenAI-compatible proxy, gateway, or provider that relies on the
tool_calls↔tool_call_idpairing to reconstruct the conversation structure.Expected behavior
When the model generates a tool call and the result is returned, the next request to the API should replay the full conversation including:
{ "role": "assistant", "content": "I'll explore the project structure.", "tool_calls": [ { "id": "tool_rvzW22DjYhyE1u7kAvtxJEkP", "type": "function", "function": { "name": "bash", "arguments": "{\"cmd\": \"ls -la\"}" } } ] }followed by:
{ "role": "tool", "tool_call_id": "tool_rvzW22DjYhyE1u7kAvtxJEkP", "content": "total 88\ndrwxr-xr-x@ 10 bymiller staff 320 Apr 21 19:20 .\n..." }Actual behavior
The assistant message is sent without
tool_calls:{ "role": "assistant", "content": "I'll start by exploring the project structure and examining the key files." }The tool result message IS correctly sent:
{ "role": "tool", "tool_call_id": "tool_rvzW22DjYhyE1u7kAvtxJEkP", "content": "/Users/bymiller/src/task\ntotal 88\ndrwxr-xr-x@ 10 bymiller staff 320 Apr 21 19:20 .\n..." }Diagnostic evidence
I added logging at the entry point of our OpenAI-compatible proxy to inspect the raw messages from opencode. Here is exactly what we see:
Assistant messages (sampled first 3):
[ { "keys": ["role", "content"], "hasToolCalls": false, "hasFunctionCall": false, "contentType": "string", "contentIsArray": false, "contentSnippet": "I'll start by exploring the project structure and examining the key files." }, { "keys": ["role", "content"], "hasToolCalls": false, "hasFunctionCall": false, "contentType": "string", "contentIsArray": false, "contentSnippet": "" }, { "keys": ["role", "content"], "hasToolCalls": false, "hasFunctionCall": false, "contentType": "string", "contentIsArray": false, "contentSnippet": "\n\n[Upstream provider error — retrying may help]" } ]Tool result messages (sampled first 2):
[ { "keys": ["role", "content", "tool_call_id"], "tool_call_id": "tool_rvzW22DjYhyE1u7kAvtxJEkP", "contentSnippet": "/Users/bymiller/src/task\ntotal 88\ndrwxr-xr-x@ 10 bymiller staff 320 Apr 21 19:20 ." }, { "keys": ["role", "content", "tool_call_id"], "tool_call_id": "tool_iGCywJow7YLARoc0Z7BzJDxL", "contentSnippet": "The glob tool was called with invalid arguments: [..." } ]Aggregate counts across the full request (192 messages):
Every single tool result is orphaned because zero assistant messages carry the
tool_callsfield.Impact
Any OpenAI-compatible backend that validates tool-call/result pair integrity will either:
AI_MissingToolResultsErrorvalidation), leaving the model with zero tool history — it then loops endlessly calling tools but never seeing resultsEnvironment
❯ opencode --version
1.14.22
Possible location in source
Based on the web search, opencode uses the Vercel AI SDK internally. The conversion from internal
ModelMessageformat (where tool calls arecontent: [{type: "tool-call", toolCallId, toolName, input}]) to OpenAI wire format (where tool calls should be in thetool_callsfield) may be incomplete. The history replay path likely serializes assistant messages without extracting thetool_callsfrom the AI SDK's content array format.PR #14456 (
repairAssistantMessages) and PR #16531 (OpenAI-compatible provider layer) are related but don't appear to address this specific issue oftool_callsbeing absent in the outbound history replay.Plugins
No response
OpenCode version
1.14.22
Steps to reproduce
How to reproduce
roleandcontentkeys —tool_callsis missingScreenshot and/or share link
No response
Operating System
mac os 26.0.1
Terminal
iterm2