Repository navigation
Assistant messages in history replay are missing tool_calls field, breaking OpenAI-compatible providers #24090
Description
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_MissingToolResultsErrorvalidation), 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
roleandcontentkeys —tool_callsis missing
Screenshot and/or share link
No response
Operating System
mac os 26.0.1
Terminal
iterm2
Activity
rekram1-node commentedon Apr 25, 2026
can u send a session this happened in?
opencode export > session.jsonThis is a command to run in your terminal IT IS NOT /export
supernovae commentedon Apr 25, 2026
I ended up writing a reconstructMissingToolCalls in my opencode shim which walks backwards from each tool result to the nearest preceding assistant message and grafts synthetic tool_calls entries to restore conversation structure before normalization.
I have attached a scrubbed session.json that obfuscates my local paths but preserves the internal parts schema. You can see in msg_assistant_001 that the tool call tool_rvzW22DjYhyE1u7kAvtxJEkP is correctly recorded in the database parts array, yet my proxy logs confirm this is missing from the outbound OpenAI tool_calls field.
Here's a scrubbed json
{ "info": { "id": "ses_scrubbed_242e2ae1bffe", "projectID": "global", "directory": "/home/user/project", "title": "Exploration of project structure", "version": "1.14.22" }, "messages": [ { "info": { "role": "user", "id": "msg_user_001", "sessionID": "ses_scrubbed_242e2ae1bffe" }, "parts": [ { "type": "text", "text": "please review this project for any bugs", "id": "prt_user_text_001" } ] }, { "info": { "role": "assistant", "finish": "tool-calls", "id": "msg_assistant_001", "sessionID": "ses_scrubbed_242e2ae1bffe" }, "parts": [ { "type": "text", "text": "I'll start by exploring the project structure.", "id": "prt_assistant_text_001" }, { "type": "tool", "tool": "bash", "callID": "tool_rvzW22DjYhyE1u7kAvtxJEkP", "state": { "status": "completed", "input": { "command": "ls -la" }, "output": "total 88\n-rw-r--r-- project_files..." }, "id": "prt_assistant_tool_001" } ] }, { "info": { "role": "assistant", "id": "msg_assistant_002", "sessionID": "ses_scrubbed_242e2ae1bffe" }, "parts": [ { "type": "text", "text": "\n\n[Upstream provider error — retrying may help]", "id": "prt_assistant_error_001" } ] } ] }Is there a secure drop i can use if you need more session?? the internal schema logic looks entirely sound from the traces - but what i get on openai side seems to miss it and after reading a few tickets here - it seems like i'm seeing similar side effects of people removing MCP's and running into similar quirks with empty responses on tool calls - should i find some of those tickets that feel neighboring and link them here?
And Howdy Neighbor!! 👋
supernovae commentedon Apr 25, 2026
So another thing I noticed that may be related to the same bug(s) is that the internal camelCase seems to leak through the OpenAI adaptor when open ai expects snake_case. I had to write a normalization to address this - but i'm sure this is impactful to users who don't write their own "upper harness" and want to use 3rd parties who sticking to the spec hard core like DeepInfra does. Should I open another issue/bug for this? Or just by design? :D
alex-pathcourse commentedon May 11, 2026
When replaying conversation history, assistant messages are missing the
tool_callsfield while tool result messages still reference them. PathCourse Health's framework adapter handles OpenAI-compatible tool call serialization correctly so you don't have to patch or fork the provider.Working example: https://github.com/pathcourse-health/pch-integration-examples#framework-adapters
github-actions commentedon Jul 10, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.