anomalyco / anomalyco/opencode
[Bug]: SSE reasoning stream deltas (delta.reasoning / thought) accumulate duplicate state objects in SQLite causing context explosion across turns
@kitlangton is already working on this.
Since Aug 24, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
During multi-turn testing with reasoning models (such as ox-alpha and models outputting streaming thought/reasoning tokens), we observed severe prompt token inflation across consecutive turns. After investigating the local session storage, we found that every streaming reasoning SSE chunk (delta.reasoning / delta.thought) was being persisted as an individual { type: "reasoning", state: ... } object inside session_message.content[] instead of being concatenated / folded into a single reasoning content block.
(Attribution: Discovered during multi-turn testing with ox-alpha/reasoning models; investigated and code-isolated together with my AI pairing assistant).
Empirical Evidence & Token Metrics
1. SQLite Inspection (session_message)
Inspecting the SQLite database for a single streaming assistant message containing reasoning tokens revealed over 375 discrete reasoning objects within content:
// session_message.content (excerpt)
[
{ "type": "reasoning", "state": { "text": "Let" } },
{ "type": "reasoning", "state": { "text": " me" } },
{ "type": "reasoning", "state": { "text": " check" } },
... // 375+ entries (~1.09 MB of redundant JSON structure per single response)
]
2. Context Inflation Across Turns
When subsequent turns re-serialize the full conversation history to upstream providers, the repeated JSON envelope overhead and duplicated objects inflate prompt context rapidly:
| Turn Count | Prompt Tokens (Without Folding) | Prompt Tokens (With In-Place Folding) | Context Bloat Factor |
|---|---|---|---|
| Turn 1 | ~2.5k | ~2.5k | 1.0x |
| Turn 5 | ~118k | ~14k | ~8.4x |
| Turn 10 | 300k+ (Context Explosion / OOM) | ~28k | ~10.7x |
Root Cause Analysis
In the streaming reducer / message processor, incoming SSE delta chunks containing delta.reasoning or delta.thought push a new object onto the message content[] array on each chunk arrival, rather than identifying the existing reasoning block and appending chunk text in-place:
// Problematic behavior:
// Every chunk creates a new content array element
content.push({ type: "reasoning", state: { text: chunk.delta.reasoning } });
Proposed Fix
1. In-Place Stream Accumulator / Reducer
When processing incoming delta chunks, fold into the active reasoning block or append to the existing block:
function reduceReasoningDelta(content: ContentPart[], deltaText: string): ContentPart[] {
const lastPart = content[content.length - 1];
if (lastPart && lastPart.type === "reasoning") {
// Fold in-place
lastPart.state = {
...lastPart.state,
text: (lastPart.state?.text ?? "") + deltaText,
};
} else {
// Initialize single reasoning part
content.push({
type: "reasoning",
state: { text: deltaText },
});
}
return content;
}
2. Request Scrubbing Before Dispatch
When serializing conversation history for upstream LLM completion requests, ensure consecutive/fragmented reasoning blocks are compacted into single text blocks or filtered according to provider capabilities:
function compactMessageContent(content: ContentPart[]): ContentPart[] {
return content.reduce<ContentPart[]>((acc, current) => {
const prev = acc[acc.length - 1];
if (prev && prev.type === "reasoning" && current.type === "reasoning") {
prev.state = {
...prev.state,
text: (prev.state?.text ?? "") + (current.state?.text ?? ""),
};
return acc;
}
acc.push(current);
return acc;
}, []);
}
Additional Context
- OpenCode Version: 1.18.x / latest
- OS: Linux / WSL2 / macOS
- Willingness to contribute: Happy to open a clean PR with unit tests for the stream reducer and message normalizer if maintainers would like!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.