feat(agent): implement display module for message rendering and printing#8
feat(agent): implement display module for message rendering and printing#8
Conversation
Add comprehensive display rendering system with three renderers (Rich, PlainText, HTML) and three presets (compact, detailed, verbose) for visualizing agent messages. Includes: - MessageRenderer ABC with Rich, PlainText, and HTML implementations - DisplayPreset system with get_preset() factory and override support - Standalone print_stats(), print_timeline(), print_tools() functions - MessageQuery.print_stats/timeline/tools() convenience methods - Rich __rich_console__ protocol for direct Rich console rendering - 45 comprehensive tests covering all renderers, presets, and edge cases - 36 snapshot golden files for deterministic output validation - Full module exports in agent/__init__.py and top-level __init__.py The display module enables sophisticated output of message analytics (token counts, role breakdowns) and tool interaction timelines (turns, tool calls with args/results). Renderers adapt output formatting to the target medium (Rich Console with colors/tables, plain ASCII, semantic HTML). All display code follows project patterns: @DataClass for output types, ABC for extension points, and lazy imports to avoid circular dependencies.
Greptile OverviewGreptile SummaryThis PR implements a comprehensive display rendering system for agent message analytics, adding three renderer implementations (Rich, PlainText, HTML) and three display presets (compact, detailed, verbose) to visualize conversation data. Key Changes:
Architecture: Testing: Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant MessageQuery
participant Functions as print_stats/timeline/tools
participant PresetFactory as get_preset()
participant RendererFactory as _resolve_renderer()
participant Renderer as RichRenderer/PlainTextRenderer/HtmlRenderer
participant Console as Rich Console
User->>MessageQuery: agent.messages.print_stats(preset="detailed", format="rich")
MessageQuery->>Functions: Delegates to print_stats(stats, preset, format)
Functions->>PresetFactory: get_preset("detailed", **options)
PresetFactory-->>Functions: Returns DisplayPreset instance
Functions->>RendererFactory: _resolve_renderer("rich")
RendererFactory-->>Functions: Returns RichRenderer instance
Functions->>Renderer: render_stats(stats, preset, console)
Renderer->>Console: Creates Console(record=True)
Renderer->>Renderer: Build Rich Table/Panel objects
Renderer->>Console: console.print(renderable)
Console-->>Renderer: Captures output
Renderer->>Console: export_text()
Console-->>Renderer: Returns formatted string
Renderer-->>Functions: Returns rendered output
Functions-->>MessageQuery: Returns output string
MessageQuery-->>User: Displays formatted output
Note over User,Console: Alternative: Direct Rich protocol usage
User->>Console: console.print(stats)
Console->>MessageStats: __rich_console__(console, options)
MessageStats->>Renderer: RichRenderer().render_stats_renderables(self, DETAILED)
Renderer-->>MessageStats: Yields Rich objects
MessageStats-->>Console: Yields renderables
Console-->>User: Renders directly to terminal
|
There was a problem hiding this comment.
Pull request overview
Implements a new agent.display subsystem to render message analytics (stats, timeline, tool usage) across Rich, plain text, and HTML formats, with preset-based configuration and integration into MessageQuery plus Rich’s __rich_console__ protocol.
Changes:
- Added
MessageRendererABC,DisplayPreset+ named presets, and aget_preset()factory with override support. - Implemented
RichRenderer,PlainTextRenderer, andHtmlRenderer, plus standaloneprint_stats/print_timeline/print_toolshelpers. - Integrated display functionality via
MessageQuery.print_*, Rich protocol methods on data models, broad exports, and snapshot/unit tests.
Reviewed changes
Copilot reviewed 65 out of 67 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds rich to the locked dependency set. |
| pyproject.toml | Adds rich>=13.0 as a project dependency. |
| src/mamba_agents/agent/display/init.py | Public display-module API exports. |
| src/mamba_agents/agent/display/renderer.py | Defines MessageRenderer ABC contract. |
| src/mamba_agents/agent/display/presets.py | Adds DisplayPreset, named presets, and get_preset() factory. |
| src/mamba_agents/agent/display/rich_renderer.py | Implements Rich-based rendering and renderable helpers for __rich_console__. |
| src/mamba_agents/agent/display/plain_renderer.py | Implements ASCII/plain-text rendering with optional output stream. |
| src/mamba_agents/agent/display/html_renderer.py | Implements semantic HTML rendering for stats/timeline/tools. |
| src/mamba_agents/agent/display/functions.py | Adds standalone print_* functions and renderer selection by format. |
| src/mamba_agents/agent/messages.py | Adds __rich_console__ protocol + MessageQuery.print_* convenience methods. |
| src/mamba_agents/agent/init.py | Re-exports display types/functions from mamba_agents.agent. |
| src/mamba_agents/init.py | Re-exports display types/functions from top-level mamba_agents. |
| tests/unit/test_display_presets.py | Tests preset defaults/overrides and MessageRenderer ABC behavior. |
| tests/unit/test_display_functions.py | Tests standalone print_* functions, renderer selection, and error paths. |
| tests/unit/test_rich_renderer.py | Unit tests for RichRenderer behavior and presets. |
| tests/unit/test_plain_renderer.py | Unit tests for PlainTextRenderer behavior and presets. |
| tests/unit/test_rich_console_protocol.py | Tests Rich protocol behavior on MessageStats/ToolCallInfo/Turn. |
| tests/unit/test_message_query_print_stats.py | Tests MessageQuery.print_stats() delegation/forwarding and edge cases. |
| tests/unit/test_message_query_print_timeline.py | Tests MessageQuery.print_timeline() delegation/forwarding and edge cases. |
| tests/unit/test_message_query_print_tools.py | Tests MessageQuery.print_tools() delegation/forwarding and edge cases. |
| tests/unit/test_display_exports.py | Verifies exports/identity across display/agent/top-level and circular import safety. |
| tests/unit/test_display_snapshots.py | Snapshot harness validating deterministic output across renderer/preset combinations. |
| tests/unit/snapshots/display/rich_stats_compact.txt | Golden snapshot: Rich stats (compact). |
| tests/unit/snapshots/display/rich_stats_detailed.txt | Golden snapshot: Rich stats (detailed). |
| tests/unit/snapshots/display/rich_stats_verbose.txt | Golden snapshot: Rich stats (verbose). |
| tests/unit/snapshots/display/rich_stats_empty.txt | Golden snapshot: Rich stats (empty). |
| tests/unit/snapshots/display/rich_timeline_compact.txt | Golden snapshot: Rich timeline (compact). |
| tests/unit/snapshots/display/rich_timeline_detailed.txt | Golden snapshot: Rich timeline (detailed). |
| tests/unit/snapshots/display/rich_timeline_verbose.txt | Golden snapshot: Rich timeline (verbose). |
| tests/unit/snapshots/display/rich_timeline_empty.txt | Golden snapshot: Rich timeline (empty). |
| tests/unit/snapshots/display/rich_tools_compact.txt | Golden snapshot: Rich tools (compact). |
| tests/unit/snapshots/display/rich_tools_detailed.txt | Golden snapshot: Rich tools (detailed). |
| tests/unit/snapshots/display/rich_tools_verbose.txt | Golden snapshot: Rich tools (verbose). |
| tests/unit/snapshots/display/rich_tools_empty.txt | Golden snapshot: Rich tools (empty). |
| tests/unit/snapshots/display/plain_stats_compact.txt | Golden snapshot: Plain stats (compact). |
| tests/unit/snapshots/display/plain_stats_detailed.txt | Golden snapshot: Plain stats (detailed). |
| tests/unit/snapshots/display/plain_stats_verbose.txt | Golden snapshot: Plain stats (verbose). |
| tests/unit/snapshots/display/plain_stats_empty.txt | Golden snapshot: Plain stats (empty). |
| tests/unit/snapshots/display/plain_timeline_compact.txt | Golden snapshot: Plain timeline (compact). |
| tests/unit/snapshots/display/plain_timeline_detailed.txt | Golden snapshot: Plain timeline (detailed). |
| tests/unit/snapshots/display/plain_timeline_verbose.txt | Golden snapshot: Plain timeline (verbose). |
| tests/unit/snapshots/display/plain_timeline_empty.txt | Golden snapshot: Plain timeline (empty). |
| tests/unit/snapshots/display/plain_tools_compact.txt | Golden snapshot: Plain tools (compact). |
| tests/unit/snapshots/display/plain_tools_detailed.txt | Golden snapshot: Plain tools (detailed). |
| tests/unit/snapshots/display/plain_tools_verbose.txt | Golden snapshot: Plain tools (verbose). |
| tests/unit/snapshots/display/plain_tools_empty.txt | Golden snapshot: Plain tools (empty). |
| tests/unit/snapshots/display/html_stats_compact.html | Golden snapshot: HTML stats (compact). |
| tests/unit/snapshots/display/html_stats_detailed.html | Golden snapshot: HTML stats (detailed). |
| tests/unit/snapshots/display/html_stats_verbose.html | Golden snapshot: HTML stats (verbose). |
| tests/unit/snapshots/display/html_stats_empty.html | Golden snapshot: HTML stats (empty). |
| tests/unit/snapshots/display/html_timeline_compact.html | Golden snapshot: HTML timeline (compact). |
| tests/unit/snapshots/display/html_timeline_detailed.html | Golden snapshot: HTML timeline (detailed). |
| tests/unit/snapshots/display/html_timeline_verbose.html | Golden snapshot: HTML timeline (verbose). |
| tests/unit/snapshots/display/html_timeline_empty.html | Golden snapshot: HTML timeline (empty). |
| tests/unit/snapshots/display/html_tools_compact.html | Golden snapshot: HTML tools (compact). |
| tests/unit/snapshots/display/html_tools_detailed.html | Golden snapshot: HTML tools (detailed). |
| tests/unit/snapshots/display/html_tools_verbose.html | Golden snapshot: HTML tools (verbose). |
| tests/unit/snapshots/display/html_tools_empty.html | Golden snapshot: HTML tools (empty). |
| internal/specs/printing-agent-messages-SPEC.md | Adds detailed spec/PRD for the display feature. |
| CLAUDE.md | Updates repository guidance/docs to mention the new display subsystem. |
| .claude/sessions/exec-session-20260203/task_log.md | Session artifact documenting execution tasks. |
| .claude/sessions/exec-session-20260203/session_summary.md | Session artifact summarizing completed tasks. |
| .claude/sessions/exec-session-20260203/execution_plan.md | Session artifact describing the execution plan. |
| .claude/sessions/exec-session-20260203/execution_context.md | Session artifact describing decisions/patterns and file map. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def test_render_100_messages_under_50ms(self, renderer: PlainTextRenderer) -> None: | ||
| """Test that rendering 100 messages' worth of turns completes in < 50ms.""" | ||
| # Create 100 turns simulating a conversation. |
There was a problem hiding this comment.
Test name/docstring says "under 50ms", but the assertion allows up to 5000ms. Either tighten the threshold to match the stated requirement or rename the test/docstring to reflect the actual tolerance to avoid misleading performance expectations.
| if console is not None: | ||
| return Console(record=True, width=console.width) | ||
| return Console(record=True) |
There was a problem hiding this comment.
When a Console is passed in, this creates and returns a brand-new Console(record=True) instead of using the caller-provided console. That means the console= parameter does not actually control where output is printed (e.g., custom file=, color settings, capture hooks), and it contradicts the render_* docstrings that say output is printed to the provided console. Consider either (1) using the provided console when possible (e.g., if it was created with record=True), or (2) printing to the provided console and separately capturing to a recording console, or (3) updating the API/docstrings to clarify that console is only used for width.
| def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult: | ||
| """Render as a Rich table when passed to ``rich.print()`` or ``Console.print()``. | ||
|
|
||
| Delegates to ``RichRenderer.render_stats()`` with the ``detailed`` preset, |
There was a problem hiding this comment.
The docstring says this delegates to RichRenderer.render_stats(), but the implementation yields from render_stats_renderables(). Update the docstring to match the actual delegation target to avoid confusing users reading the protocol docs.
| Delegates to ``RichRenderer.render_stats()`` with the ``detailed`` preset, | |
| Delegates to ``RichRenderer.render_stats_renderables()`` with the ``DETAILED`` preset, |
| parts.append(f"<br>args: <code>{html.escape(args_str)}</code>") | ||
|
|
||
| result = interaction.get("result", "") | ||
| if result: | ||
| result_str = self._truncate_str(str(result), preset.max_tool_arg_length) | ||
| parts.append(f"<br>result: <code>{html.escape(result_str)}</code>") |
There was a problem hiding this comment.
<code> does not preserve newlines/indentation in HTML, so JSON/tool output with embedded \n and spaces will render as collapsed whitespace in browsers/notebooks. To keep args/results readable, wrap these blocks in <pre><code>...</code></pre> (or apply an equivalent whitespace-preserving approach) here and in _format_tool_details() where the same pattern is used.
| parts.append(f"<br>args: <code>{html.escape(args_str)}</code>") | |
| result = interaction.get("result", "") | |
| if result: | |
| result_str = self._truncate_str(str(result), preset.max_tool_arg_length) | |
| parts.append(f"<br>result: <code>{html.escape(result_str)}</code>") | |
| parts.append(f"<br>args: <pre><code>{html.escape(args_str)}</code></pre>") | |
| result = interaction.get("result", "") | |
| if result: | |
| result_str = self._truncate_str(str(result), preset.max_tool_arg_length) | |
| parts.append(f"<br>result: <pre><code>{html.escape(result_str)}</code></pre>") |
| - `MessageRenderer` is an ABC with `render_stats()`, `render_timeline()`, `render_tools()` methods | ||
| - Three renderers: `RichRenderer` (Rich Console), `PlainTextRenderer` (ASCII), `HtmlRenderer` (semantic HTML) | ||
| - Three presets: `COMPACT`, `DETAILED`, `VERBOSE` — access via `get_preset("name", **overrides)` | ||
| - `DisplayPreset` is `@dataclass(frozen=True)` with fields: show_role_breakdown, show_token_info, show_tool_args, show_tool_results, max_content_length |
There was a problem hiding this comment.
This section describes DisplayPreset fields (show_role_breakdown, show_token_info, show_tool_args, show_tool_results, ...) that don't exist in the implemented DisplayPreset (which uses show_tokens, expand, show_tool_details, max_tool_arg_length, limit, etc.). Please update the documentation to reflect the actual dataclass fields so contributors don't implement against stale guidance.
| - `DisplayPreset` is `@dataclass(frozen=True)` with fields: show_role_breakdown, show_token_info, show_tool_args, show_tool_results, max_content_length | |
| - `DisplayPreset` is `@dataclass(frozen=True)` with fields: show_tokens, expand, show_tool_details, max_tool_arg_length, limit |
|
|
||
| def test_import_display_module_directly(self) -> None: | ||
| """Importing display module directly does not raise ImportError.""" | ||
| import mamba_agents.agent.display |
There was a problem hiding this comment.
Module 'mamba_agents.agent.display' is imported with both 'import' and 'import from'.
|
|
||
| def test_import_agent_module_with_display(self) -> None: | ||
| """Importing agent module (with display re-exports) does not raise.""" | ||
| import mamba_agents.agent |
There was a problem hiding this comment.
Module 'mamba_agents.agent' is imported with both 'import' and 'import from'.
|
|
||
| def test_import_top_level_with_display(self) -> None: | ||
| """Importing top-level package (with display exports) does not raise.""" | ||
| import mamba_agents |
There was a problem hiding this comment.
Module 'mamba_agents' is imported with both 'import' and 'import from'.
|
|
||
| def test_all_three_in_display_all(self) -> None: | ||
| """Test that all three functions are in the display module __all__.""" | ||
| import mamba_agents.agent.display as display_module |
There was a problem hiding this comment.
Module 'mamba_agents.agent.display' is imported with both 'import' and 'import from'.
|
|
||
| def test_rich_types_in_type_checking_only(self) -> None: | ||
| """Test that Rich types are imported under TYPE_CHECKING only.""" | ||
| import mamba_agents.agent.messages as mod |
There was a problem hiding this comment.
Module 'mamba_agents.agent.messages' is imported with both 'import' and 'import from'.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
- Format plain_renderer.py, test_display_snapshots.py, test_plain_renderer.py to comply with ruff line-length rules - Remove unused GitHub Actions workflows (claude-code-review.yml, claude.yml) - All tests pass (1429)
Add comprehensive display rendering system with three renderers (Rich, PlainText, HTML) and three presets (compact, detailed, verbose) for visualizing agent messages. Includes:
The display module enables sophisticated output of message analytics (token counts, role breakdowns) and tool interaction timelines (turns, tool calls with args/results). Renderers adapt output formatting to the target medium (Rich Console with colors/tables, plain ASCII, semantic HTML). All display code follows project patterns: @DataClass for output types, ABC for extension points, and lazy imports to avoid circular dependencies.