Skip to content
Open
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
4 changes: 2 additions & 2 deletions patchwork/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from patchwork.synth_definitions import load_synth_definitions

console = Console()
stderr_console = Console(stderr=True)


def _make_event_handler(verbose: bool, logger: logging.Logger):
Expand All @@ -25,15 +26,14 @@ async def handle_events(ctx: RunContext[PatchworkDeps], events: AsyncIterable) -
async for event in events:
if isinstance(event, FunctionToolCallEvent):
tool_name = event.part.tool_name
logger.info("tool call: %s", tool_name)
stderr_console.print(f"[dim]\U0001f6e0\ufe0f tool call: {tool_name}[/dim]")

if verbose:
try:
args = event.part.args_as_dict()
except Exception:
args = event.part.args
logger.debug("tool args: %s %s", tool_name, json.dumps(args, default=str))
console.print(f"[dim]⚙ {tool_name}[/dim]")

return handle_events

Expand Down
23 changes: 12 additions & 11 deletions tests/test_tool_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ def logger():

class TestToolCallEventHandler:
@pytest.mark.asyncio
async def test_logs_tool_name(self, logger, caplog):
async def test_logs_tool_name_to_stderr(self, logger, capsys):
handler = _make_event_handler(verbose=False, logger=logger)
event = _make_mock_tool_call_event("send_cc")
ctx = MagicMock()

with caplog.at_level(logging.INFO, logger=logger.name):
await handler(ctx, _to_async_iterable([event]))
await handler(ctx, _to_async_iterable([event]))

assert any("send_cc" in record.message for record in caplog.records)
captured = capsys.readouterr()
assert "send_cc" in captured.err

@pytest.mark.asyncio
async def test_verbose_logs_args(self, logger, caplog):
Expand Down Expand Up @@ -72,27 +72,28 @@ async def test_ignores_non_tool_events(self, logger, caplog):
assert len(tool_records) == 0

@pytest.mark.asyncio
async def test_handles_multiple_events(self, logger, caplog):
async def test_handles_multiple_events(self, logger, capsys):
handler = _make_event_handler(verbose=False, logger=logger)
events = [
_make_mock_tool_call_event("list_synths"),
_make_mock_tool_call_event("send_cc"),
]
ctx = MagicMock()

with caplog.at_level(logging.INFO, logger=logger.name):
await handler(ctx, _to_async_iterable(events))
await handler(ctx, _to_async_iterable(events))

tool_records = [r for r in caplog.records if "tool call" in r.message]
assert len(tool_records) == 2
captured = capsys.readouterr()
assert "list_synths" in captured.err
assert "send_cc" in captured.err

@pytest.mark.asyncio
async def test_non_verbose_does_not_print_tool_indicator(self, logger, capsys):
async def test_tool_indicator_prints_to_stderr(self, logger, capsys):
handler = _make_event_handler(verbose=False, logger=logger)
event = _make_mock_tool_call_event("send_cc")
ctx = MagicMock()

await handler(ctx, _to_async_iterable([event]))

captured = capsys.readouterr()
assert "⚙" not in captured.out
assert "\U0001f6e0\ufe0f" not in captured.out
assert "send_cc" in captured.err
Loading