Skip to content

refactor(cli): migrate core tools to native ToolDisplay property and fix UI rendering#25186

Merged
mbleigh merged 22 commits intomainfrom
mb/atui/01-ui-rendering
May 6, 2026
Merged

refactor(cli): migrate core tools to native ToolDisplay property and fix UI rendering#25186
mbleigh merged 22 commits intomainfrom
mb/atui/01-ui-rendering

Conversation

@mbleigh
Copy link
Copy Markdown
Contributor

@mbleigh mbleigh commented Apr 11, 2026

Summary

This PR migrates the core CLI tools and rendering pipeline to emit and consume a native ToolDisplay object, deprecating the legacy returnDisplay adapter. By shifting control of the display schema directly to the tools themselves, the UI can now render tool responses more predictably across both compact (DenseToolMessage) and full-width (ToolMessage) views.

Fixes #25046

Details

1. Core Tool Migrations

The following core tools have been updated to populate and emit their own ToolDisplay objects (including name, description, resultSummary, and result), bypassing the legacy translation layer:

  • edit
  • grep / ripGrep
  • ls
  • shell
  • read-file
  • write-file

2. Event Pipeline & Type Updates

  • Updated ToolCallResponseInfo, event-translator, and tool-executor to process the new ToolDisplay property.
  • Fixed a bug in populateToolDisplay (tool-display-utils.ts) where legacy JSON payloads were indiscriminately overwriting explicitly defined result: null values from tools like ripGrep.

3. UI & Rendering Fixes

  • State mapping: useAgentStream now seamlessly bridges the new display property into IndividualToolCallDisplay.
  • Compact View Allowance: ToolGroupMessage now checks tool.originalRequestName instead of just tool.name to determine if a tool supports the compact view. This allows subagent-aliased tools (like SearchText) to correctly render as compact DenseToolMessage components.
  • Summary Deduplication: Fixed an issue in ToolMessage and ToolShared where tool summaries were duplicated in both the header line and the result box. If a tool explicitly sets display.result to null, the summary is rendered in the box, and automatically hidden from the header.

Related Issues

Fixes #25046

How to Validate

  1. Run an agent flow invoking migrated tools (e.g. ls, read_file, grep_search).
  2. Verify that tools supporting compact output render on a single line with DenseToolMessage.
  3. Check that aliased tools (like SearchText in subagents) successfully trigger the compact view.
  4. Ensure that tools returning result: null (like grep_search) cleanly render their summary in the main box when forced into a non-compact view, without duplicating the summary in the header.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

mbleigh and others added 7 commits April 10, 2026 11:00
…tadata overwrite

This addresses PR feedback by:
- Creating a `renderDisplayDiff` utility to render `DisplayDiff` objects.
- Creating a `displayContentToString` utility to safely extract text from any `DisplayContent`.
- Updating non-interactive CLI to use `displayContentToString` to avoid data loss on non-text output.
- Updating `useAgentStream` to use `displayContentToString` to avoid stale UI state for non-text output.
- Shallow merging the `display` object in `useAgentStream` rather than replacing it, preventing loss of display metadata.
@mbleigh mbleigh requested review from a team as code owners April 11, 2026 16:22
@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli Bot commented Apr 11, 2026

Hi @mbleigh, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.

We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: Improving Our Contribution Process & Introducing New Guidelines.

Key Update: Starting January 26, 2026, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.

Thank you for your understanding and for being a part of our community!

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the UI rendering logic for tool outputs by introducing a more robust ToolDisplay protocol. By allowing tools to explicitly define their display name, summary, and result payload, the system now provides a more consistent and cleaner user experience. The changes also address specific bugs related to compact view detection for aliased tools and prevent redundant text duplication in the UI header.

Highlights

  • ToolDisplay Protocol Enhancement: Introduced a structured ToolDisplay object across the tool execution pipeline to allow tools to explicitly control their UI representation, including summaries and result payloads.
  • Compact View Fixes: Corrected the isCompactTool logic to check originalRequestName instead of the aliased name, ensuring tools like grep_search correctly render in compact mode.
  • UI Deduplication: Added a hideSummary property to ToolInfo to prevent redundant text display when a tool provides both a summary and a result.
  • Legacy Bridge Improvements: Updated populateToolDisplay to respect explicit null values in display.result, preventing legacy fallback logic from overwriting intentional empty results.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@mbleigh mbleigh changed the title fix(ui): correct ToolDisplay payload mapping and compact views refactor(cli): migrate core tools to native ToolDisplay property and fix UI rendering Apr 11, 2026
@gemini-cli gemini-cli Bot added the area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality label Apr 11, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a new ToolDisplay protocol to standardize how tool execution results are presented across the CLI. It updates core tools like Edit, Grep, and Shell to provide structured display metadata and modifies UI components, including DenseToolMessage and ToolInfo, to render this information. Feedback suggests flattening multiline summaries in the dense view to maintain layout consistency and adjusting the merge logic in populateToolDisplay to ensure that tool-provided metadata correctly overrides default values.

Comment on lines +337 to +345
const summaryText = display.resultSummary ? (
<Text color={theme.text.accent} wrap="truncate-end">
→ {display.resultSummary}
</Text>
) : status === CoreToolCallStatus.Error ? (
<Text color={theme.status.error} wrap="truncate-end">
→ {typeof resultDisplay === 'string' ? resultDisplay : 'Failed'}
</Text>
) : undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The summary text for both successful results and errors should be flattened (newlines replaced with spaces) to maintain the single-line compact layout expected in the dense view. This ensures layout consistency by preventing variable item heights within the list, which is preferred over dynamic calculations.

Suggested change
const summaryText = display.resultSummary ? (
<Text color={theme.text.accent} wrap="truncate-end">
{display.resultSummary}
</Text>
) : status === CoreToolCallStatus.Error ? (
<Text color={theme.status.error} wrap="truncate-end">
{typeof resultDisplay === 'string' ? resultDisplay : 'Failed'}
</Text>
) : undefined;
const summaryText = display.resultSummary ? (
<Text color={theme.text.accent} wrap="truncate-end">
{display.resultSummary.replace(/\n/g, ' ')}
</Text>
) : status === CoreToolCallStatus.Error ? (
<Text color={theme.status.error} wrap="truncate-end">
{typeof resultDisplay === 'string' ? resultDisplay.replace(/\n/g, ' ').trim() : 'Failed'}
</Text>
) : undefined;
References
  1. To ensure layout consistency in a list, it is preferable to make all items have a fixed height rather than dynamically calculating variable item heights.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, let's do it.

Comment thread packages/core/src/agent/tool-display-utils.ts
mbleigh added 3 commits April 11, 2026 19:01
This change completes the transition of the interactive agent session (`useAgentStream`)
to a declarative-first tool rendering system.

Key changes:
- Reverted experimental `ToolDisplay` logic from legacy UI components (`DenseToolMessage`, etc.)
  to establish a clean baseline.
- Introduced `HistoryItemToolDisplayGroup` and `ToolGroupDisplay` component in CLI.
- Added `display` property to `ToolCallRequestInfo` to carry declarative UI info natively.
- Populated tool request display information at the source (`Turn.ts` and `Scheduler.ts`)
  using dynamic descriptions from tool invocations.
- Updated `useAgentStream` to emit the new history item type, providing a standalone
  rendering path for interactive sessions.
- Ensured tool descriptions are updated when arguments are modified during confirmation.
Key enhancements:
- Updated `UpdateTopicTool` to provide declarative 'notice' display info, using dynamic
  descriptions for high-fidelity output.
- Refined `ToolGroupDisplay` to 'hoist' notice-format tools to the top of the group.
- Implemented conditional boxing in `ToolGroupDisplay`: borders are now suppressed in
  compact mode, matching the standard CLI view.
- Added support for `resultSummary` rendering at the bottom of text results in boxed mode.
- Improved `useAgentStream` to wait for turn completion before pushing tools to history,
  ensuring all notices for a turn are correctly grouped and hoisted together.
- Fixed margin and border logic to handle seamless transitions between notices and tool boxes.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Size Change: +13.7 kB (+0.04%)

Total Size: 34 MB

Filename Size Change
./bundle/chunk-BGZRZKLY.js 0 B -3.43 kB (removed) 🏆
./bundle/chunk-C4SLYRES.js 0 B -14.8 MB (removed) 🏆
./bundle/chunk-FI4736AL.js 0 B -12.5 kB (removed) 🏆
./bundle/chunk-G5J6VH7A.js 0 B -19.5 kB (removed) 🏆
./bundle/chunk-GCHQPOAY.js 0 B -2.78 MB (removed) 🏆
./bundle/chunk-MHV3EN7R.js 0 B -658 kB (removed) 🏆
./bundle/chunk-MWDYWR7A.js 0 B -3.8 kB (removed) 🏆
./bundle/chunk-WCKEEU4H.js 0 B -49.2 kB (removed) 🏆
./bundle/core-RVUBJC3J.js 0 B -48.8 kB (removed) 🏆
./bundle/devtoolsService-WU6PBJUD.js 0 B -28 kB (removed) 🏆
./bundle/gemini-E3SYYOCC.js 0 B -584 kB (removed) 🏆
./bundle/interactiveCli-SR4WNISY.js 0 B -1.29 MB (removed) 🏆
./bundle/liteRtServerManager-B3R6VESW.js 0 B -2.11 kB (removed) 🏆
./bundle/oauth2-provider-U4RI3IJV.js 0 B -9.16 kB (removed) 🏆
./bundle/chunk-7XJVCSIH.js 3.43 kB +3.43 kB (new file) 🆕
./bundle/chunk-CT7HEQBM.js 12.5 kB +12.5 kB (new file) 🆕
./bundle/chunk-IKUSZM7S.js 3.8 kB +3.8 kB (new file) 🆕
./bundle/chunk-QWMFKDE3.js 14.8 MB +14.8 MB (new file) 🆕
./bundle/chunk-SSL7MJOZ.js 49.2 kB +49.2 kB (new file) 🆕
./bundle/chunk-U37PPEPM.js 2.78 MB +2.78 MB (new file) 🆕
./bundle/chunk-Z7LDHGMG.js 658 kB +658 kB (new file) 🆕
./bundle/chunk-ZSERKVMR.js 19.5 kB +19.5 kB (new file) 🆕
./bundle/core-4A4G74Q3.js 48.8 kB +48.8 kB (new file) 🆕
./bundle/devtoolsService-DSX5D5EL.js 28 kB +28 kB (new file) 🆕
./bundle/gemini-IFKS5DLT.js 584 kB +584 kB (new file) 🆕
./bundle/interactiveCli-IJ7HDGFK.js 1.3 MB +1.3 MB (new file) 🆕
./bundle/liteRtServerManager-TWMKVNKM.js 2.11 kB +2.11 kB (new file) 🆕
./bundle/oauth2-provider-VQWWXY2A.js 9.16 kB +9.16 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
./bundle/bundled/third_party/index.js 8 MB 0 B
./bundle/chunk-34MYV7JD.js 2.45 kB 0 B
./bundle/chunk-5AUYMPVF.js 858 B 0 B
./bundle/chunk-5PS3AYFU.js 1.18 kB 0 B
./bundle/chunk-664ZODQF.js 124 kB 0 B
./bundle/chunk-DAHVX5MI.js 206 kB 0 B
./bundle/chunk-IUUIT4SU.js 56.5 kB 0 B
./bundle/chunk-RJTRUG2J.js 39.8 kB 0 B
./bundle/chunk-VJSUVOZ4.js 1.97 MB 0 B
./bundle/cleanup-L5KBUICC.js 0 B -932 B (removed) 🏆
./bundle/devtools-36NN55EP.js 696 kB 0 B
./bundle/dist-T73EYRDX.js 356 B 0 B
./bundle/events-XB7DADIJ.js 418 B 0 B
./bundle/examples/hooks/scripts/on-start.js 188 B 0 B
./bundle/examples/mcp-server/example.js 1.43 kB 0 B
./bundle/gemini.js 5.1 kB 0 B
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB 0 B
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB 0 B
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB 0 B
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB 0 B
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB 0 B
./bundle/memoryDiscovery-NGHTMHWQ.js 980 B 0 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 222 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 229 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 13.4 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B 0 B
./bundle/sandbox-macos-permissive-open.sb 890 B 0 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.82 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB 0 B
./bundle/src-QVCVGIUX.js 47 kB 0 B
./bundle/start-TC4MGNAM.js 0 B -652 B (removed) 🏆
./bundle/tree-sitter-7U6MW5PS.js 274 kB 0 B
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB 0 B
./bundle/cleanup-7R7SXSZR.js 932 B +932 B (new file) 🆕
./bundle/start-JR5CS26F.js 652 B +652 B (new file) 🆕

compressed-size-action

@gundermanc
Copy link
Copy Markdown
Member

@gemini-cli Please help me understand this pull request by deeply reviewing the code, the linked work item, and it's parent and children.

I'm curious how this change factors into the overall effort, what other alternatives were considered, etc.

@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli Bot commented May 5, 2026

Deep Review: PR #25186 - Refactor CLI Tool Rendering

This pull request represents a significant architectural shift in how Gemini CLI handles tool execution feedback and UI rendering. Below is a deep dive into the changes, their rationale, and how they factor into the broader project roadmap.


1. Architectural Shift: "Display-Native Tools"

Historically, Gemini CLI relied on a central adapter layer (primarily ToolGroupMessage and DenseToolMessage) to interpret raw tool results and decide how to render them. This led to "leaky abstractions" where the UI needed specific knowledge about every tool's output format.

PR #25186 migrates the system to a "Display-Native" model:

  • Distributed Control: Tools now explicitly define their own display metadata via a native display property (ToolDisplay interface).
  • Rich Schema: The ToolDisplay object includes:
    • name and description: Human-readable identifiers.
    • resultSummary: A concise, one-line outcome (e.g., "15 files found").
    • result: A structured DisplayContent object (supporting text, diff, terminal, or agent types).
  • Migration Scope: The core tools (edit, grep/ripGrep, ls, shell, read-file, write-file) have been fully migrated to this pattern, bypassing the legacy returnDisplay translation layer.

2. UI & Rendering Predictability

The introduction of ToolGroupDisplay.tsx in the CLI package is the most visible change. It fixes several long-standing UX issues:

  • Compact View for Aliases: Previously, aliased tools (e.g., SearchText in a subagent) wouldn't trigger the compact DenseToolMessage because the UI only looked at the original name. This PR fixes this by checking originalRequestName.
  • Summary Deduplication: Fixed a bug where summaries were duplicated in both the header and the result box. Now, if a tool returns result: null, the summary is rendered in the box and suppressed from the header to avoid redundancy.
  • Improved Dense Layout: Tools supporting compact output now render more reliably on a single line, improving the "flow" of agent activity.

3. Key Bug Fix: populateToolDisplay

A critical fix was applied to packages/core/src/agent/tool-display-utils.ts.

  • The Issue: Legacy JSON payloads were indiscriminately overwriting explicitly defined result: null values.
  • The Impact: This was particularly problematic for tools like ripGrep that use null to signal "no content but check the summary." The fix ensures that if a tool explicitly sets a result (even null), the bridge won't overwrite it with a stringified fallback.

4. Overall Effort: The "atui" Series

This PR is marked as Step 1 (mb/atui/01-ui-rendering) in a larger effort to modernize the Agent Tool UI (atui).

  • Parent/Context: The effort is tracked under Issue [AgentProtocol] Implement tool-controlled display for useAgentSession #25046, which focuses on improving the predictability of tool outputs in the CLI.
  • Children/Follow-ups: The branch mb/atui/02-tool-state (visible in remote branches) suggests the next phase will focus on more granular state tracking (e.g., progress bars, interactive sub-states) within the tool execution lifecycle.

5. Alternatives Considered

  • Enhancing the Legacy Adapter: One alternative was to keep the central ToolGroupMessage and add more special-case logic for diffs and terminal output. This was rejected because it would continue to bloat the CLI package with tool-specific logic, making it harder to add new MCP or dynamic tools.
  • Standardizing on Markdown: Another alternative was to force all tools to return Markdown and let the UI render it. While simpler, this lacks the semantic richness needed for specialized UI components like side-by-side diffs or interactive terminal buffers.

Conclusion

This PR is a foundational cleanup that empowers tools to be self-describing. By shifting the burden of "how I should look" from the CLI UI to the tool implementation itself, the project gains significantly better maintainability and a more consistent user experience across different tool types.

@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli Bot commented May 6, 2026

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

Comment thread packages/cli/src/ui/components/messages/ToolGroupDisplay.tsx
Copy link
Copy Markdown
Contributor

@jacob314 jacob314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

- Revert unnecessary comment in ToolGroupDisplay.tsx.

- Use "Shell" as the display name for the Shell tool and add TODO for ANSI support.

- Restore visibility of matched lines in Grep and RipGrep tool displays by including them in display metadata.

- Confirmed existing newline flattening in DenseToolMessage.tsx.
@mbleigh mbleigh enabled auto-merge May 6, 2026 20:20
@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli Bot commented May 6, 2026

This pull request is being closed as it has been open for 14 days without a 'help wanted' designation. We encourage you to find and contribute to existing 'help wanted' issues in our backlog! Thank you for your understanding.

@gemini-cli gemini-cli Bot closed this May 6, 2026
auto-merge was automatically disabled May 6, 2026 20:38

Pull request was closed

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

75 tests passed successfully on gemini-3-flash-preview.

🧠 Model Steering Guidance

This PR modifies files that affect the model's behavior (prompts, tools, or instructions).

  • ⚠️ Consider adding Evals: No behavioral evaluations (evals/*.eval.ts) were added or updated in this PR. Consider adding a test case to verify the new behavior and prevent regressions.
  • 🚀 Maintainer Reminder: Please ensure that these changes do not regress results on benchmark evals before merging.

This is an automated guidance message triggered by steering logic signatures.

@mbleigh mbleigh reopened this May 6, 2026
@mbleigh mbleigh enabled auto-merge May 6, 2026 21:02
@mbleigh mbleigh added this pull request to the merge queue May 6, 2026
Merged via the queue into main with commit 90304b2 May 6, 2026
29 checks passed
@mbleigh mbleigh deleted the mb/atui/01-ui-rendering branch May 6, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[AgentProtocol] Implement tool-controlled display for useAgentSession

4 participants