Skip to content

[FEATURE] Expose contentBlockIndex in typed stream events (TextStreamEvent, ToolUseStreamEvent, etc.) #1747

@zacharyejohnson

Description

@zacharyejohnson

Problem Statement

The Strands SDK's typed stream events (TextStreamEvent, ToolUseStreamEvent, etc.) do not include contentBlockIndex, even though:

  1. The AWS Bedrock ConverseStream API sends contentBlockIndex as a required field on every ContentBlockDeltaEvent, ContentBlockStartEvent, and
    ContentBlockStopEvent (API docs)
  2. The Strands SDK's own TypedDicts already declare contentBlockIndex: Optional[int] in strands/types/streaming.py

The field is present in the raw Bedrock chunks but silently dropped during event processing — it never makes it into the typed events that consumers receive via
agent.stream_async().

Where the gap is:

  • handle_content_block_delta() in strands/event_loop/streaming.py only reads event["delta"] — never accesses event.get("contentBlockIndex")
  • handle_content_block_start() similarly only reads event["start"]
  • TextStreamEvent emits {"data": text, "delta": delta} — no index
  • ToolUseStreamEvent similarly has no index
  • BedrockModel._stream() passes raw chunks unmodified — the data is available upstream, just never extracted

Proposed Solution

Non-breaking, additive change across 2 files:

strands/event_loop/streaming.py — extract the index from the raw event:

# In handle_content_block_delta():
delta_content = event["delta"]
content_block_index = event.get("contentBlockIndex")  # NEW
# Pass content_block_index to typed event constructors

strands/types/_events.py — add an optional field to typed events:

class TextStreamEvent(ModelStreamEvent):
def __init__(self, delta, text, content_block_index=None):  # NEW param
super().__init__({
  "data": text,
  "delta": delta,
  "content_block_index": content_block_index,  # NEW field
})

Same pattern for ToolUseStreamEvent and other relevant event types. Existing consumers that don't use content_block_index are completely unaffected.

Use Case

When a model generates pre-tool preamble text ("Let me look that up...") before calling a tool, then produces a final response after the tool returns, both are emitted as identical {"data": text} events. Consumers using agent.stream_async() have no way to distinguish pre-tool text from post-tool text.

contentBlockIndex is the only signal available to solve this. The Bedrock API increments the index per content block within a turn:

Block Index Content
text (preamble) 0 "Let me look that up..."
tool_use 1 {"name": "search", ...}
text (response) 2 "Here's what I found..."

Without this index, consumers must either buffer the entire response or parse raw chunks from ModelStreamChunkEvent and correlate them manually.

Alternatives Solutions

ModelStreamChunkEvent (yielded in process_stream()) contains the raw chunk, which includes contentBlockIndex. Consumers could manually parse raw chunks and correlate them with typed events - but this defeats the purpose of having typed events and is fragile.

Additional Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions