Skip to content

fix(streaming): preserve BetaCompactionBlock type during streaming accumulation#1200

Open
bledden wants to merge 1 commit intoanthropics:mainfrom
bledden:bledden/fix-compaction-block-streaming
Open

fix(streaming): preserve BetaCompactionBlock type during streaming accumulation#1200
bledden wants to merge 1 commit intoanthropics:mainfrom
bledden:bledden/fix-compaction-block-streaming

Conversation

@bledden
Copy link

@bledden bledden commented Feb 24, 2026

Summary

When using the beta streaming API with compaction enabled, the final message from get_final_message() incorrectly types BetaCompactionBlock content blocks as ParsedBetaTextBlock. The block has type='compaction' but isinstance(block, BetaCompactionBlock) returns False, and parasitic fields like text, citations, and parsed_output appear as None.

The issue is in the streaming accumulator's content_block_start handling. It converts every content block to a dict and reconstructs it through the ParsedBetaContentBlock discriminated union via construct_type(). On some Python versions (reported on 3.12), the generic ParsedBetaTextBlock[ResponseFormatT] variant in that union breaks Pydantic's discriminator resolution, causing all blocks to fall through to the first union variant (ParsedBetaTextBlock).

The fix: only text blocks need reconstruction through construct_type (they're the only type that needs wrapping in ParsedBetaTextBlock to add the parsed_output field). Non-text blocks like BetaCompactionBlock, BetaToolUseBlock, BetaThinkingBlock, etc. are already the correct type from the streaming event and can be appended directly.

Added a test fixture simulating a compaction streaming response and two tests (sync + async) that verify BetaCompactionBlock instances are preserved in the final message. All 14 beta streaming tests pass.

Fixes #1175

…cumulation

When the beta streaming accumulator builds the final message snapshot, it
reconstructs each content block by converting it to a dict and passing it
through construct_type with the ParsedBetaContentBlock discriminated
union. On some Python versions, the generic ParsedBetaTextBlock[T]
variant in that union breaks Pydantic's discriminator resolution, causing
all non-text blocks (including BetaCompactionBlock) to be incorrectly
deserialized as ParsedBetaTextBlock.

The fix: only text blocks need reconstruction through construct_type (to
wrap them in ParsedBetaTextBlock which adds the parsed_output field).
Non-text blocks are already the correct type from the event and can be
appended directly to the snapshot.

Fixes anthropics#1175
@bledden bledden requested a review from a team as a code owner February 24, 2026 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Opus 4.6] BetaCompactionBlock deserialized as ParsedBetaTextBlock during streaming accumulation

1 participant