Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -977,8 +977,11 @@ def flush_pending_msg():

# Handle complete messages
if isinstance(message, (AssistantMessage, UserMessage)):
# msg_id (used below and passed to handle_tool_use_block()) —
# not current_message_id, which is only valid inside the
# streaming loop and is already None here.
msg_id = current_message_id or str(uuid.uuid4())
if isinstance(message, AssistantMessage):
msg_id = current_message_id or str(uuid.uuid4())
agui_msg = build_agui_assistant_message(message, msg_id)
if agui_msg:
upsert_message(agui_msg)
Expand All @@ -991,7 +994,7 @@ def flush_pending_msg():
continue
updated_state, tool_events = await handle_tool_use_block(
block, message, thread_id, run_id, self._per_thread_state.get(thread_id),
parent_message_id=current_message_id,
parent_message_id=msg_id,
)
if tool_id:
processed_tool_ids.add(tool_id)
Expand Down
25 changes: 25 additions & 0 deletions integrations/claude-agent-sdk/python/tests/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from ag_ui_claude_sdk.utils import extract_tool_names

from claude_agent_sdk import AssistantMessage, ToolUseBlock

from .conftest import stream_event, aiter


Expand Down Expand Up @@ -109,6 +111,29 @@ async def test_backend_tool_call_sequence(self, make_input):
# exactly one END for the one tool call
assert types.count(EventType.TOOL_CALL_END) == 1

@pytest.mark.asyncio
async def test_subagent_tool_call_gets_parent_message_id(self, make_input):
# A complete AssistantMessage, not a StreamEvent, is how a subagent's
# own turn arrives — this hits the fallback branch (#2118).
adapter = ClaudeAgentAdapter(name="t")
subagent_message = AssistantMessage(
content=[ToolUseBlock(id="tc-subagent-1", name="mcp__ui__render_ui", input={})],
model="claude-haiku-4-5",
parent_tool_use_id="tc-agent-call",
)
events = await _drive(adapter, [subagent_message], make_input)
types = _types(events)
assert EventType.TOOL_CALL_START in types
start = next(e for e in events if e.type == EventType.TOOL_CALL_START)
assert start.tool_call_name == "render_ui" # prefix stripped
assert start.parent_message_id is not None
assert start.parent_message_id != "tc-agent-call" # not the SDK's parent_tool_use_id

# The id must also be the one reported in the run's MESSAGES_SNAPSHOT,
# i.e. it's a real, stable message id, not an arbitrary placeholder.
snapshot = next(e for e in events if e.type == EventType.MESSAGES_SNAPSHOT)
assert any(getattr(m, "id", None) == start.parent_message_id for m in snapshot.messages)

@pytest.mark.asyncio
async def test_frontend_tool_halts_stream(self, make_input):
adapter = ClaudeAgentAdapter(name="t")
Expand Down
Loading