"""Renderers for tool_started and tool_call events."""
from __future__ import annotations
import json
from rich.console import RenderableType
from rich.json import JSON as RichJSON
from rich.box import ROUNDED
from rich.padding import Padding
from rich.panel import Panel
from rich.text import Text
from clients.terminal.tui.themes import get_active_theme
from .base import ContentRenderer
# Cap a generic tool result so a tool with huge output (non-filesystem — those
# have their own renderer) does not flood the chat bubble. Keep the tail, with
# a marker, mirroring shell_runner's truncation.
_RESULT_MAX_LINES = 200
def _truncate_result(text: str) -> str:
"""Keep the last ``_RESULT_MAX_LINES`` lines of a tool result, with a marker."""
lines = text.splitlines()
if len(lines) <= _RESULT_MAX_LINES:
return text
dropped = len(lines) - _RESULT_MAX_LINES
return f"... [{dropped} lines truncated]\n" + "\n".join(lines[-_RESULT_MAX_LINES:])
class ToolStartedRenderer(ContentRenderer):
"""Render a tool call start marker.
Sub-agent tool calls (``is_subagent=True``) are indented to read as nested
inside the parent spawn_agent card.
"""
def accepts(self, msg: dict) -> bool:
return msg.get("type") == "tool_started"
def render(self, msg: dict) -> RenderableType:
theme = get_active_theme()
tool = msg.get("tool", "?")
args = msg.get("args") or {}
title = f"→ {tool}"
if args:
try:
body = RichJSON(json.dumps(args))
except Exception:
body = Text(str(args), style=theme.text_dim.hex)
else:
body = Text("")
panel = Panel(
body,
title=title,
title_align="left",
border_style=theme.tool_border.hex,
box=ROUNDED,
)
if bool(msg.get("is_subagent", False)):
return Padding(panel, (0, 0, 0, 2))
return panel
class ToolResultRenderer(ContentRenderer):
"""Render a tool call result.
Sub-agent tool results (``is_subagent=True``) are indented to read as nested
inside the parent spawn_agent card.
"""
def accepts(self, msg: dict) -> bool:
return msg.get("type") == "tool_call"
def render(self, msg: dict) -> RenderableType:
theme = get_active_theme()
tool = msg.get("tool", "?")
success = msg.get("success", True)
result = msg.get("result")
color = theme.tool_success if success else theme.tool_error
title = f"← {tool} {'✓' if success else '✗'}"
body = Text(
_truncate_result(str(result) if result is not None else ""),
style=theme.text_dim.hex,
)
panel = Panel(
body,
title=title,
title_align="left",
border_style=color.hex,
box=ROUNDED,
)
if bool(msg.get("is_subagent", False)):
return Padding(panel, (0, 0, 0, 2))
return panel