Problem Statement
The Strands SDK's typed stream events (TextStreamEvent, ToolUseStreamEvent, etc.) do not include contentBlockIndex, even though:
- The AWS Bedrock ConverseStream API sends
contentBlockIndex as a required field on every ContentBlockDeltaEvent, ContentBlockStartEvent, and
ContentBlockStopEvent (API docs)
- 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
Problem Statement
The Strands SDK's typed stream events (
TextStreamEvent,ToolUseStreamEvent, etc.) do not includecontentBlockIndex, even though:contentBlockIndexas a required field on everyContentBlockDeltaEvent,ContentBlockStartEvent, andContentBlockStopEvent(API docs)contentBlockIndex: Optional[int]instrands/types/streaming.pyThe 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()instrands/event_loop/streaming.pyonly readsevent["delta"]— never accessesevent.get("contentBlockIndex")handle_content_block_start()similarly only readsevent["start"]TextStreamEventemits{"data": text, "delta": delta}— no indexToolUseStreamEventsimilarly has no indexBedrockModel._stream()passes raw chunks unmodified — the data is available upstream, just never extractedProposed Solution
Non-breaking, additive change across 2 files:
strands/event_loop/streaming.py— extract the index from the raw event:strands/types/_events.py— add an optional field to typed events:Same pattern for
ToolUseStreamEventand other relevant event types. Existing consumers that don't usecontent_block_indexare 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 usingagent.stream_async()have no way to distinguish pre-tool text from post-tool text.contentBlockIndexis the only signal available to solve this. The Bedrock API increments the index per content block within a turn:{"name": "search", ...}Without this index, consumers must either buffer the entire response or parse raw chunks from
ModelStreamChunkEventand correlate them manually.Alternatives Solutions
ModelStreamChunkEvent(yielded inprocess_stream()) contains the raw chunk, which includescontentBlockIndex. 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
contentBlockStartevents for text blocks. That fix ensureshandle_content_block_startfires for text, but doesn't expose the index on typed events.