Environment
- aimock: 1.19.1
- strands-agents: 1.38.0
- Python: 3.13
- boto3 / botocore: 1.43.6
Steps to reproduce
1. Create a fixture file
// fixtures/agent.json
{
"fixtures": [
{
"match": { "userMessage": "hello" },
"response": { "content": "Hello! How can I help you today?" }
}
]
}
2. Start aimock
npx -p @copilotkit/aimock llmock -f ./fixtures --port 4010
3. Run the agent
# main.py
from strands import Agent
from strands.models import BedrockModel
model = BedrockModel(endpoint_url="http://localhost:4010")
agent = Agent(model=model)
agent("hello")
Actual output:
Traceback (most recent call last):
...
File ".../strands/event_loop/streaming.py", line 436, in process_stream
state["message"] = handle_message_start(chunk["messageStart"], state["message"])
File ".../strands/event_loop/streaming.py", line 169, in handle_message_start
message["role"] = event["role"]
KeyError: 'role'
Expected output:
The agent runs without error and returns a response.
Root cause
In src/bedrock-converse.ts, Converse stream event payloads are double-wrapped:
// current (incorrect) — buildBedrockStreamTextEvents
{ eventType: "messageStart", payload: { messageStart: { role: "assistant" } } }
// ^^^^^^^^^^^^ redundant outer key
Strands Agents uses boto3 internally to call Bedrock. The problem unfolds as follows:
- aimock encodes a binary Event Stream frame with
:event-type: messageStart header and JSON payload {"messageStart": {"role": "assistant"}}
- botocore receives the frame and reads the
:event-type header → uses "messageStart" as the dict key
- botocore tries to map the JSON payload to the
MessageStartEvent shape, which expects {"role": "assistant"} at the top level
- The actual payload is
{"messageStart": {"role": "assistant"}} — no fields match the shape → botocore silently returns {}
- boto3 returns
{"messageStart": {}} to Strands — this is botocore's BaseEventStreamParser._do_parse behavior (botocore/parsers.py):
event_type = response['headers'].get(':event-type') # "messageStart"
event_shape = shape.members.get(event_type) # MessageStartEvent shape
final_parsed[event_type] = self._do_parse(response, event_shape)
# payload {"messageStart": {"role": "assistant"}} doesn't match the shape → {}
- Strands'
process_stream function (via handle_message_start) in src/strands/event_loop/streaming.py reads chunk["messageStart"]["role"] → {}["role"] → KeyError: 'role'
Suggested fix
The payload should not be wrapped with the event type name — the :event-type header already carries that information:
// before (incorrect)
{ eventType: "messageStart", payload: { messageStart: { role: "assistant" } } }
// after (correct)
{ eventType: "messageStart", payload: { role: "assistant" } }
The same double-wrapping exists in all Converse stream event builders:
| Event type |
Affected function |
messageStart, contentBlockStart, contentBlockDelta, contentBlockStop, messageStop, metadata |
buildBedrockStreamTextEvents |
| same + tool events |
buildBedrockStreamToolCallEvents, buildBedrockStreamContentWithToolCallsEvents |
Environment
Steps to reproduce
1. Create a fixture file
2. Start aimock
3. Run the agent
Actual output:
Expected output:
The agent runs without error and returns a response.
Root cause
In
src/bedrock-converse.ts, Converse stream event payloads are double-wrapped:Strands Agents uses boto3 internally to call Bedrock. The problem unfolds as follows:
:event-type: messageStartheader and JSON payload{"messageStart": {"role": "assistant"}}:event-typeheader → uses"messageStart"as the dict keyMessageStartEventshape, which expects{"role": "assistant"}at the top level{"messageStart": {"role": "assistant"}}— no fields match the shape → botocore silently returns{}{"messageStart": {}}to Strands — this is botocore'sBaseEventStreamParser._do_parsebehavior (botocore/parsers.py):process_streamfunction (viahandle_message_start) insrc/strands/event_loop/streaming.pyreadschunk["messageStart"]["role"]→{}["role"]→KeyError: 'role'Suggested fix
The payload should not be wrapped with the event type name — the
:event-typeheader already carries that information:The same double-wrapping exists in all Converse stream event builders:
messageStart,contentBlockStart,contentBlockDelta,contentBlockStop,messageStop,metadatabuildBedrockStreamTextEventsbuildBedrockStreamToolCallEvents,buildBedrockStreamContentWithToolCallsEvents