fix(session): repair orphan reasoning/text stream parts instead of failing the turn - #39287
Open
DragonBaiMo wants to merge 2 commits into
Open
fix(session): repair orphan reasoning/text stream parts instead of failing the turn#39287DragonBaiMo wants to merge 2 commits into
DragonBaiMo wants to merge 2 commits 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.
Proxies that stream deltas without a preceding *-start caused the processor to drop the delta and any providerMetadata riding on it, including Anthropic thinking signatures. Repair the stream in the adapter by emitting the missing start before the delta.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes #33934
Also reported as #36241 (same error shape, macOS/CLI instead of Windows/Desktop).
Type of change
What does this PR do?
Sessions using reasoning models behind OpenAI-compatible / Anthropic proxies fail with
reasoning part <id> not found(ortext part <id> not found). There are two distinctproblems behind that one message, and this PR fixes both.
1. The error was promoted to a fatal turn failure (commit 1)
In the AI SDK's
streamTextevent processor, areasoning-delta/reasoning-endwhose id hasno matching
reasoning-startenqueues an in-band part{ type: "error", error: "reasoning part <id> not found" }and thenreturns, leaving thestream running. The raw chunk itself was already forwarded downstream unconditionally at the
top of the same
transform, so this part is bookkeeping about a missing start block, not aprovider failure. The same applies to the text path.
The adapter mapped every
errorpart toEffect.fail(event.error), so this recoverable partaborted the turn:
SessionProcessor.halt()logged it (withstack=undefined, since thepayload is a plain string) and emitted a session error. Matching is deliberately strict --
prefix
reasoning part/text partand suffixnot found-- so genuine provider errors(rate limits,
Tool "x" not found, etc.) still fail exactly as before. If the SDK rewords themessage the matcher stops matching and behavior reverts to the previous fatal path, so this
fails loud rather than silently swallowing errors.
2. The reasoning text and its signature were being dropped (commit 2)
Tolerating the error is not sufficient, because the underlying delta is still lost.
SessionProcessorrequires a start block before it will create a part --if (!ctx.currentText) returnfor text,if (!(value.id in ctx.reasoningMap)) returnforreasoning -- so an orphan delta and any
providerMetadatariding on it are discarded.That matters beyond the missing text.
MessageV2replays a reasoning part as{ type: "reasoning", text, providerMetadata }, and the Anthropic transform keeps a reasoningpart only when
part.text.trim().length > 0 || providerOptions?.anthropic?.signature != null(or
redactedData). A dropped orphan delta means the part is never created, so its text andits thinking signature are both absent from replayed history, breaking thinking-block replay on
subsequent requests.
The fix belongs in the adapter rather than the processor. The adapter is the
protocol-normalization layer and already synthesizes block ids via
currentTextID/currentReasoningID; it simply never synthesized the missing start event.The processor applies one consistent "no start, no part" contract to both text and reasoning,
and it is the wrong place to reconstruct protocol the provider omitted. So the adapter now
tracks which block ids have emitted a start and, on an orphan delta or end, emits the missing
start (carrying that event's
providerMetadata) before it. The processor is unchanged, andstreams that do emit starts are passed through identically.
This is the pattern the native runtime already uses, so the change aligns the AI SDK adapter
with existing behavior rather than introducing a new convention. In
packages/llm/src/protocols/utils/lifecycle.ts,reasoningDeltaunconditionally routesthrough
reasoningStartfirst, andreasoningStartis idempotent viaif (state.reasoning.has(id)) return state(textDeltadoes the same for text, tracking idsin a
Set). The AI SDK path was the only runtime missing that guarantee.How did you verify your code works?
bun test test/session/ai-sdk.test.ts-- 8 pass / 36 assertions. Covers: orphanreasoning part .../text part ...errors (including colon ids likers_abc:0) are droppedand the turn continues; genuine errors still fail; an orphan
reasoning-deltagets exactlyone synthesized
reasoning-startand subsequent deltas do not re-synthesize;providerMetadata(e.g. an Anthropicsignature) is carried onto the synthesized start;orphan
text-deltaand orphanreasoning-endare likewise repaired; and a well-formedstart/delta/end stream is emitted unchanged.
bun test test/session/-- 47 pass vs 42 on the unpatched baseline (the +5 are the newtests). The 16 pre-existing failures / 9 errors are identical with and without this patch.
bun run typecheckinpackages/opencode-- the pre-existingsrc/diagnostic count is 33both with and without this patch, and no diagnostic mentions the changed file.
Screenshots / recordings
Not a UI change.
Checklist