feat(core): publish tool.result on the bus for the activity panel
This commit is contained in:
@@ -29,7 +29,9 @@ from claude_agent_sdk import (
|
|||||||
AssistantMessage,
|
AssistantMessage,
|
||||||
ResultMessage,
|
ResultMessage,
|
||||||
StreamEvent,
|
StreamEvent,
|
||||||
|
ToolResultBlock,
|
||||||
ToolUseBlock,
|
ToolUseBlock,
|
||||||
|
UserMessage,
|
||||||
fork_session_via_store,
|
fork_session_via_store,
|
||||||
project_key_for_directory,
|
project_key_for_directory,
|
||||||
)
|
)
|
||||||
@@ -963,6 +965,20 @@ class Conversations:
|
|||||||
name=block.name,
|
name=block.name,
|
||||||
input=block.input,
|
input=block.input,
|
||||||
)
|
)
|
||||||
|
elif isinstance(message, UserMessage):
|
||||||
|
blocks = message.content if isinstance(message.content, list) else ()
|
||||||
|
for block in blocks:
|
||||||
|
if isinstance(block, ToolResultBlock):
|
||||||
|
self._bus.publish(
|
||||||
|
"tool.result",
|
||||||
|
conversation_id=conversation_id,
|
||||||
|
turn_id=turn_id,
|
||||||
|
origin=origin,
|
||||||
|
parent_tool_use_id=parent,
|
||||||
|
tool_use_id=block.tool_use_id,
|
||||||
|
is_error=bool(block.is_error),
|
||||||
|
content=_result_preview(block.content),
|
||||||
|
)
|
||||||
elif isinstance(message, ResultMessage) and parent is None:
|
elif isinstance(message, ResultMessage) and parent is None:
|
||||||
self._bus.publish(
|
self._bus.publish(
|
||||||
"result",
|
"result",
|
||||||
@@ -1055,6 +1071,24 @@ def _iso(value: datetime | None) -> str | None:
|
|||||||
return _aware(value).isoformat(timespec="seconds") if value is not None else None
|
return _aware(value).isoformat(timespec="seconds") if value is not None else None
|
||||||
|
|
||||||
|
|
||||||
|
def _result_preview(
|
||||||
|
content: str | list[dict[str, Any]] | None, limit: int = 400
|
||||||
|
) -> str:
|
||||||
|
"""Short text of a tool result for the panel: the transcript keeps the whole."""
|
||||||
|
if content is None:
|
||||||
|
return ""
|
||||||
|
text = (
|
||||||
|
content
|
||||||
|
if isinstance(content, str)
|
||||||
|
else "\n".join(
|
||||||
|
str(part.get("text", ""))
|
||||||
|
for part in content
|
||||||
|
if isinstance(part, dict) and part.get("type") == "text"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return text if len(text) <= limit else text[:limit] + "…"
|
||||||
|
|
||||||
|
|
||||||
def _usage_dict(capture: TurnCapture) -> dict[str, Any] | None:
|
def _usage_dict(capture: TurnCapture) -> dict[str, Any] | None:
|
||||||
usage = capture.usage
|
usage = capture.usage
|
||||||
if usage is None:
|
if usage is None:
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ from claude_agent_sdk import (
|
|||||||
ResultMessage,
|
ResultMessage,
|
||||||
StreamEvent,
|
StreamEvent,
|
||||||
TextBlock,
|
TextBlock,
|
||||||
|
ToolResultBlock,
|
||||||
|
ToolUseBlock,
|
||||||
|
UserMessage,
|
||||||
project_key_for_directory,
|
project_key_for_directory,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -655,3 +658,40 @@ def test_agent_kinds_follow_prompts(tmp_path: Path) -> None:
|
|||||||
kinds=("master", "deep"),
|
kinds=("master", "deep"),
|
||||||
prompts=Prompts(master=()),
|
prompts=Prompts(master=()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_observer_publishes_tool_results_for_the_panel(world: World) -> None:
|
||||||
|
seen: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
async def collect() -> None:
|
||||||
|
async for event in world.bus.stream(conversation_id="c1"):
|
||||||
|
seen.append(event)
|
||||||
|
|
||||||
|
task = asyncio.create_task(collect())
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
observe = world.conversations._observer("c1", "t1", "user")
|
||||||
|
observe(
|
||||||
|
AssistantMessage(
|
||||||
|
content=[ToolUseBlock(id="toolu_1", name="Bash", input={"command": "ls"})],
|
||||||
|
model="m",
|
||||||
|
parent_tool_use_id="toolu_0",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
observe(
|
||||||
|
UserMessage(
|
||||||
|
content=[
|
||||||
|
ToolResultBlock(tool_use_id="toolu_1", content="a\nb", is_error=False)
|
||||||
|
],
|
||||||
|
parent_tool_use_id="toolu_0",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
observe(UserMessage(content="plain text, no tool result"))
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
task.cancel()
|
||||||
|
assert [e["type"] for e in seen] == ["tool", "tool.result"]
|
||||||
|
tool, result = seen
|
||||||
|
assert tool["tool_use_id"] == "toolu_1" and tool["parent_tool_use_id"] == "toolu_0"
|
||||||
|
assert result["tool_use_id"] == "toolu_1"
|
||||||
|
assert result["parent_tool_use_id"] == "toolu_0"
|
||||||
|
assert result["turn_id"] == "t1" and result["is_error"] is False
|
||||||
|
assert result["content"] == "a\nb"
|
||||||
|
|||||||
Reference in New Issue
Block a user