diff --git a/agent/bedrock_adapter.py b/agent/bedrock_adapter.py index 3fdf5a92cd..a23b3ec164 100644 --- a/agent/bedrock_adapter.py +++ b/agent/bedrock_adapter.py @@ -829,12 +829,10 @@ def stream_converse_with_callbacks( def block_index(payload: Dict[str, Any], *, new_block: bool = False) -> int: """Index of the block a contentBlock* event addresses. Without ``contentBlockIndex`` (test doubles, proxies) a start opens a fresh slot and a delta/stop continues the current one.""" - nonlocal current_block_index idx = payload.get("contentBlockIndex") - if not isinstance(idx, int): - idx = len(stream_blocks) if new_block or current_block_index is None else current_block_index - current_block_index = idx - return idx + if isinstance(idx, int): + return idx + return len(stream_blocks) if new_block or current_block_index is None else current_block_index def flush_text() -> None: if current_text_buffer: @@ -849,7 +847,7 @@ def stream_converse_with_callbacks( break if "contentBlockStart" in event: start_event = event["contentBlockStart"] - idx = block_index(start_event, new_block=True) + idx = current_block_index = block_index(start_event, new_block=True) start = start_event.get("start", {}) if "toolUse" in start: has_tool_use = True @@ -860,7 +858,7 @@ def stream_converse_with_callbacks( on_tool_start(current_tool["name"]) elif "contentBlockDelta" in event: delta_event = event["contentBlockDelta"] - idx = block_index(delta_event) + idx = current_block_index = block_index(delta_event) delta = delta_event.get("delta", {}) if "text" in delta: text = delta["text"] diff --git a/tests/agent/test_bedrock_adapter.py b/tests/agent/test_bedrock_adapter.py index dfc0481cce..636f75a612 100644 --- a/tests/agent/test_bedrock_adapter.py +++ b/tests/agent/test_bedrock_adapter.py @@ -508,7 +508,7 @@ class TestNormalizeConverseStreamEvents: the text into one block per delta and let the toolUse start (index 1) overwrite the second fragment and sort into the middle — a ``[text, toolUse, text...]`` sidecar Claude 5 on Bedrock rejects on replay as "does not support assistant message prefill".""" - from agent.bedrock_adapter import convert_messages_to_converse, normalize_converse_stream_events + from agent.bedrock_adapter import normalize_converse_stream_events result = normalize_converse_stream_events({"stream": list(self._LIVE_TEXT_THEN_TOOL_EVENTS)}) msg = result.choices[0].message assert msg.content == "I'll echo banana now." @@ -517,15 +517,6 @@ class TestNormalizeConverseStreamEvents: {"toolUse": {"toolUseId": "tooluse_1", "name": "echo", "input": {"s": "banana"}}}, ] assert [tc.function.name for tc in msg.tool_calls] == ["echo"] - # The sidecar is authoritative on replay: the next turn must go out as text THEN toolUse, nothing after. - _, converse = convert_messages_to_converse([ - {"role": "user", "content": "echo banana"}, - {"role": "assistant", "content": msg.content, "bedrock_content_blocks": msg.bedrock_content_blocks, - "tool_calls": [{"id": "tooluse_1", "type": "function", "function": {"name": "echo", "arguments": '{"s": "banana"}'}}]}, - {"role": "tool", "tool_call_id": "tooluse_1", "content": "banana"}, - ]) - assert [list(b)[0] for b in converse[1]["content"]] == ["text", "toolUse"] - assert converse[1]["content"][0]["text"] == "I'll echo banana now." def test_events_without_content_block_index_fall_back_to_arrival_order(self): """Proxies/test doubles may omit contentBlockIndex: deltas continue the current block, a start opens a