Skip to content

ParsedBetaContentBlock union missing tool_search/web_fetch result blocks — stream accumulator drops them from get_final_message() #1422

@sirianni

Description

@sirianni

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions