Description
During compaction, Anthropic API returns:
messages.9: all messages must have non-empty content except for the optional final assistant message
Root Cause Analysis
After investigating the storage data, found the following message structure in the problematic session:
[0] user | parts: text
[1] assistant | parts: step-start, tool (completed), step-finish ← NO TEXT
[2] assistant | parts: step-start, tool (completed), step-finish ← NO TEXT, consecutive
[3] assistant | parts: step-start, tool (completed), step-finish ← NO TEXT, consecutive
[4] assistant | parts: step-start, text, step-finish
[5] user | parts: text
...
Key observations:
- Messages [1], [2], [3] are consecutive assistant messages with only tool calls (no text)
step-finish part type is not handled in toModelMessage() (lines 480-537)
- The filter on line 541 passes these messages because they have
tool-* parts (not just step-start)
The Conversion Pipeline
In packages/opencode/src/session/message-v2.ts:
// Line 541
return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")))
The convertToModelMessages from AI SDK (ai package) then converts UIMessage[] to ModelMessage[].
Hypothesis: When convertToModelMessages processes consecutive assistant messages with tool calls, it may:
- Create synthetic user messages for tool results
- These synthetic messages might end up empty in certain edge cases
- Or the merging logic creates an empty content array
Affected Code
packages/opencode/src/session/message-v2.ts:421-542 — toModelMessage() function
- Called from
packages/opencode/src/session/compaction.ts
Questions
- How does
convertToModelMessages handle consecutive assistant messages with tool calls?
- Should
toModelMessage() merge consecutive assistant messages before passing to convertToModelMessages?
- Should there be validation after
convertToModelMessages to filter out empty messages?
Reproduction
Session with multiple consecutive assistant messages containing only tool calls (no text parts) followed by compaction trigger.
OpenCode version
1.0.209
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
Xubuntu 22.04
Terminal
ghostty
Description
During compaction, Anthropic API returns:
Root Cause Analysis
After investigating the storage data, found the following message structure in the problematic session:
Key observations:
step-finishpart type is not handled intoModelMessage()(lines 480-537)tool-*parts (not juststep-start)The Conversion Pipeline
In
packages/opencode/src/session/message-v2.ts:The
convertToModelMessagesfrom AI SDK (aipackage) then convertsUIMessage[]toModelMessage[].Hypothesis: When
convertToModelMessagesprocesses consecutive assistant messages with tool calls, it may:Affected Code
packages/opencode/src/session/message-v2.ts:421-542—toModelMessage()functionpackages/opencode/src/session/compaction.tsQuestions
convertToModelMessageshandle consecutive assistant messages with tool calls?toModelMessage()merge consecutive assistant messages before passing toconvertToModelMessages?convertToModelMessagesto filter out empty messages?Reproduction
Session with multiple consecutive assistant messages containing only tool calls (no text parts) followed by compaction trigger.
OpenCode version
1.0.209
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
Xubuntu 22.04
Terminal
ghostty