ParsedBetaContentBlock (the union used by ParsedBetaMessage.content in the streaming accumulator) is missing several content block types that the sibling BetaContentBlock union does include. In particular, BetaToolSearchToolResultBlock is absent, which causes stream.get_final_message() to silently drop those blocks.
Code comparison:
anthropic/types/beta/parsed_beta_message.py:39-56:
ParsedBetaContentBlock: TypeAlias = Annotated[
Union[
ParsedBetaTextBlock[ResponseFormatT],
BetaThinkingBlock,
BetaRedactedThinkingBlock,
BetaToolUseBlock,
BetaServerToolUseBlock,
BetaWebSearchToolResultBlock,
BetaCodeExecutionToolResultBlock,
BetaBashCodeExecutionToolResultBlock,
BetaTextEditorCodeExecutionToolResultBlock,
BetaMCPToolUseBlock,
BetaMCPToolResultBlock,
BetaContainerUploadBlock,
BetaCompactionBlock,
],
PropertyInfo(discriminator="type"),
]
anthropic/types/beta/beta_content_block.py:25-44 (the non-parsed union, for comparison):
BetaContentBlock: TypeAlias = Annotated[
Union[
BetaTextBlock,
BetaThinkingBlock,
BetaRedactedThinkingBlock,
BetaToolUseBlock,
BetaServerToolUseBlock,
BetaWebSearchToolResultBlock,
BetaWebFetchToolResultBlock, # <-- missing in ParsedBetaContentBlock
BetaCodeExecutionToolResultBlock,
BetaBashCodeExecutionToolResultBlock,
BetaTextEditorCodeExecutionToolResultBlock,
BetaToolSearchToolResultBlock, # <-- missing in ParsedBetaContentBlock
BetaMCPToolUseBlock,
BetaMCPToolResultBlock,
BetaContainerUploadBlock,
BetaCompactionBlock,
],
PropertyInfo(discriminator="type"),
]
Impact: When the accumulator (accumulate_event in lib/streaming/_beta_messages.py) calls construct_type(type_=ParsedBetaContentBlock, value=event.content_block.to_dict()) for a content_block_start carrying a tool_search_tool_result or web_fetch_tool_result block, pydantic's discriminated-union resolution fails to match and the block is silently coerced or dropped. The block never appears in stream.get_final_message().content, even though it was present on the wire.
Repro sketch: Stream a response that uses the tool_search_tool_regex_20251119 server-side tool. Observe that content_block_start events for tool_search_tool_result flow through the raw event stream, but (await stream.get_final_message()).content is missing the result block — you'll see only the preceding server_tool_use block and surrounding text blocks.
Suggested fix: Add BetaToolSearchToolResultBlock and BetaWebFetchToolResultBlock to the ParsedBetaContentBlock union so it stays in sync with BetaContentBlock.
SDK version: 0.92.0
Related: #1421 (different issue, same test setup).
ParsedBetaContentBlock(the union used byParsedBetaMessage.contentin the streaming accumulator) is missing several content block types that the siblingBetaContentBlockunion does include. In particular,BetaToolSearchToolResultBlockis absent, which causesstream.get_final_message()to silently drop those blocks.Code comparison:
anthropic/types/beta/parsed_beta_message.py:39-56:anthropic/types/beta/beta_content_block.py:25-44(the non-parsed union, for comparison):Impact: When the accumulator (
accumulate_eventinlib/streaming/_beta_messages.py) callsconstruct_type(type_=ParsedBetaContentBlock, value=event.content_block.to_dict())for acontent_block_startcarrying atool_search_tool_resultorweb_fetch_tool_resultblock, pydantic's discriminated-union resolution fails to match and the block is silently coerced or dropped. The block never appears instream.get_final_message().content, even though it was present on the wire.Repro sketch: Stream a response that uses the
tool_search_tool_regex_20251119server-side tool. Observe thatcontent_block_startevents fortool_search_tool_resultflow through the raw event stream, but(await stream.get_final_message()).contentis missing the result block — you'll see only the precedingserver_tool_useblock and surrounding text blocks.Suggested fix: Add
BetaToolSearchToolResultBlockandBetaWebFetchToolResultBlockto theParsedBetaContentBlockunion so it stays in sync withBetaContentBlock.SDK version: 0.92.0
Related: #1421 (different issue, same test setup).