feat: enhance tool result handling with tool_call_id for concurrent execution
This commit is contained in:
@@ -208,10 +208,11 @@ class SubAgentWidget(Vertical):
|
||||
widget.set_success(content)
|
||||
else:
|
||||
widget.set_error(content)
|
||||
# Move from running to completed
|
||||
# Move from running to completed (dedup guards against repeat
|
||||
# deliveries of the same tool result inflating the collapse summary).
|
||||
if matched_key and matched_key in self._running_ids:
|
||||
self._running_ids.remove(matched_key)
|
||||
if matched_key:
|
||||
if matched_key and matched_key not in self._completed_ids:
|
||||
self._completed_ids.append(matched_key)
|
||||
self._update_visibility()
|
||||
|
||||
|
||||
@@ -82,7 +82,11 @@ class StreamEventEmitter:
|
||||
|
||||
@staticmethod
|
||||
def subagent_tool_result(
|
||||
subagent: str, name: str, content: str, success: bool = True
|
||||
subagent: str,
|
||||
name: str,
|
||||
content: str,
|
||||
success: bool = True,
|
||||
tool_call_id: str = "",
|
||||
) -> StreamEvent:
|
||||
"""Tool result from inside a sub-agent."""
|
||||
return StreamEvent(
|
||||
@@ -93,6 +97,7 @@ class StreamEventEmitter:
|
||||
"name": name,
|
||||
"content": content,
|
||||
"success": success,
|
||||
"id": tool_call_id,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -801,11 +801,12 @@ async def stream_agent_events(
|
||||
info.id,
|
||||
).data
|
||||
name = getattr(msg, "name", "unknown")
|
||||
tool_call_id = getattr(msg, "tool_call_id", "") or ""
|
||||
raw_content, _is_img = _extract_tool_content(msg)
|
||||
content = raw_content[: DisplayLimits.TOOL_RESULT_MAX]
|
||||
success = is_success(content)
|
||||
yield emitter.subagent_tool_result(
|
||||
subagent, name, content, success
|
||||
subagent, name, content, success, tool_call_id
|
||||
).data
|
||||
else:
|
||||
for ev in _process_tool_result(msg, emitter, main_tracker):
|
||||
|
||||
@@ -42,17 +42,34 @@ class SubAgentState:
|
||||
return
|
||||
self.tool_calls.append(tc_data)
|
||||
|
||||
def add_tool_result(self, name: str, content: str, success: bool = True):
|
||||
result = {"name": name, "content": content, "success": success}
|
||||
def add_tool_result(
|
||||
self,
|
||||
name: str,
|
||||
content: str,
|
||||
success: bool = True,
|
||||
tool_call_id: str = "",
|
||||
):
|
||||
result = {
|
||||
"name": name,
|
||||
"content": content,
|
||||
"success": success,
|
||||
"tool_call_id": tool_call_id,
|
||||
}
|
||||
self.tool_results.append(result)
|
||||
# Try to match result to the first unmatched tool call with same name
|
||||
# Preferred: exact id match (correct under concurrent same-name tools).
|
||||
if tool_call_id:
|
||||
for tc in self.tool_calls:
|
||||
if tc.get("id") == tool_call_id:
|
||||
self._result_map[tool_call_id] = result
|
||||
return
|
||||
# Fallback: first unmatched tool call with same name.
|
||||
for tc in self.tool_calls:
|
||||
tc_id = tc.get("id", "")
|
||||
tc_name = tc.get("name", "")
|
||||
if tc_id and tc_id not in self._result_map and tc_name == name:
|
||||
self._result_map[tc_id] = result
|
||||
return
|
||||
# Fallback: match first unmatched tool call
|
||||
# Last resort: first unmatched tool call regardless of name.
|
||||
for tc in self.tool_calls:
|
||||
tc_id = tc.get("id", "")
|
||||
if tc_id and tc_id not in self._result_map:
|
||||
@@ -257,6 +274,7 @@ class StreamState:
|
||||
event.get("name", "unknown"),
|
||||
event.get("content", ""),
|
||||
event.get("success", True),
|
||||
event.get("id", ""),
|
||||
)
|
||||
|
||||
elif event_type == "subagent_end":
|
||||
|
||||
Reference in New Issue
Block a user