Uh oh!
There was an error while loading. Please reload this page.
fix(opencode): tolerate orphan reasoning/text stream-state parts - #39134
fix(opencode): tolerate orphan reasoning/text stream-state parts#39134DragonBaiMo wants to merge 1 commit into
Conversation
The AI SDK enqueues a non-fatal "reasoning part <id> not found" / "text part <id> not found" error part when a reasoning/text delta arrives with no preceding *-start block (common with OpenAI-compatible and Anthropic proxies). The adapter promoted every error part to a fatal Effect.fail, aborting the turn and spamming logs. Recognize these SDK-internal orphan stream-state errors and drop them instead of failing; genuine errors still fail.
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
The following comment was made by an LLM, it may be inaccurate: The search found one related PR worth noting: Related PR:
However, this is a different fix targeting a different issue. PR #39134 (the current PR) specifically addresses orphan reasoning/text stream-state errors from the Vercel AI SDK, while PR #26167 focuses on empty stream truncations and partial parts. No duplicate PRs found |
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
Problem
Sessions using reasoning-capable models behind OpenAI-compatible / Anthropic proxies repeatedly fail with:
Each occurrence aborts the assistant turn. In one local log this fired ~1500 times across multiple sessions, making such proxies effectively unusable.
Root cause
The Vercel AI SDK's
streamTextreasoning/text state machine enqueues a non-fatal in-band error part when areasoning-delta/text-delta(or its-end) arrives with no precedingreasoning-start/text-startblock:(
packages/ai/src/generate-text/stream-text.ts— text part L1171/L1190, reasoning part L1220/L1239). The SDKreturns and keeps streaming, i.e. it treats this as recoverable bookkeeping. Proxies that stream thinking deltas without emitting the start block trigger it constantly.opencode's AI SDK adapter promoted every
errorpart to a fatalEffect.fail:which
SessionProcessor.halt()logs (stack=undefined, because the payload is a plain string) and turns into an aborted turn +Session.Event.Error.Note that opencode already tolerates the same orphan condition on the delta path (
session/processor.ts: "silently drop orphan deltas (no preceding reasoning-start)"). The SDK-internal error part simply took a different code path that bypassed that existing tolerance.Fix
Recognize these narrow, SDK-internal orphan stream-state errors in the adapter (
session/llm/ai-sdk.ts) and drop them (debug log, no emitted events) instead of failing the turn. Matching is intentionally strict — prefixreasoning part/text partplus suffixnot found— so genuine provider errors (rate limits,Tool "x" not found, etc.) still fail as before. If the SDK ever changes the wording, the matcher simply stops matching and behavior reverts to the prior fatal path (fails loud, never swallows silently).Testing
bun run typecheck— cleanbun test test/session/ai-sdk.test.ts— 3 pass / 15 assertionsreasoning part …/text part …strings (incl. colon ids likers_abc:0) → dropped, turn continuesrate limit exceeded,Tool "foo" not found,Error) → still fail