Skip to content

Tool renderers collapsed/expanded#3291

Draft
rumpl wants to merge 10 commits into
docker:mainfrom
rumpl:tool-renderers
Draft

Tool renderers collapsed/expanded#3291
rumpl wants to merge 10 commits into
docker:mainfrom
rumpl:tool-renderers

Conversation

@rumpl

@rumpl rumpl commented Jun 28, 2026

Copy link
Copy Markdown
Member

Still WIP, our tool call rendering is not really great for an expanded view

Tool renderer previews

Generated with go run /tmp/docker-agent-tool-renderer-preview.go. ANSI styling is stripped so the layout is readable in Markdown.

API / fetch

Expanded

  ✓ Fetch  (https://example.com/api/widgets?limit=10): Received 280B

Collapsed

  ✓ Fetch  (https://example.com/api/widgets?limit=10)

Default fallback

Expanded

  ✓ Unknown Tool
  query:
  status by region

  regions:
  [
    "us-east",
    "eu-west"
  ]

  limit:
  5
  {
    "items": [
      {
        "name": "alpha"
      },
      {
        "name": "beta"
      }
    ],
    "status": "ok"
  …

Collapsed

  ✓ Unknown Tool  query=status by region (+2 more)

Directory tree

Expanded

  ✓ Directory Tree  /workspace/project 2 files, 2 dirs
  {
    "children": [
      {
        "name": "main.go",
        "type": "file"
      },
      {
        "children": [
          {
            "name": "render.go",
  …

Collapsed

  ✓ Directory Tree  /workspace/project

Edit file

Expanded

  ? Edit File  /var/folders/z_/bqk9099j3rgg9gg96vbh62_00000gp/T/tool-renderer-preview.go
     1 package main
     2
     3 func main() {
     4     fmt.Println("old")
     4     fmt.Println("new")
     5 }

Collapsed

  ? Edit File  /var/folders/z_/bqk9099j3rgg9gg96vbh62_00000gp/T/tool-renderer-preview.go +1 / -1

List directory

Expanded

  ✓ List Directory  /workspace/project/pkg 2 files and 1 directory
  DIR  internal
  FILE main.go
  FILE render.go

Collapsed

  ✓ List Directory  /workspace/project/pkg

Read file

Expanded

  ✓ Read File  /workspace/project/README.md 14 lines
  …
  read line 05
  read line 06
  read line 07
  read line 08
  read line 09
  read line 10
  read line 11
  read line 12
  read line 13
  read line 14

Collapsed

  ✓ Read File  /workspace/project/README.md

Read multiple files

Expanded

  ✓ Read  /workspace/project/README.md  42 lines
  ✗ Read  /workspace/project/missing.md  not found

Collapsed

  ✓ Read Multiple Files  1 file read, 1 file failed

Search files content

Expanded

  ✓ Search Files  /workspace/project (render) 3 matches in 3 files
  pkg/tui/components/tool/readfile/readfile.go:28:10: render(msg *types.Message)
  pkg/tui/components/tool/writefile/writefile.go:24:10: render(msg *types.Message)
  pkg/tui/components/tool/todotool/component.go:22:10: render(msg *types.Message)

Collapsed

  ✓ Search Files  /workspace/project (render)

Shell

Expanded

  ✓ Shell  go test ./pkg/tui/components/tool/...
  go test ./pkg/tui/components/tool/...
  ok defaulttool
  ok editfile
  ok shell

Collapsed

  ✓ Shell  go test ./pkg/tui/components/tool/...

Todo

Expanded

  ✓ Create Todos
  ✓ Wire collapsed renderer (1)
  ◯ Preview expanded todo details (2)

Collapsed

  ✓ Create Todos

Write file

Expanded

  ⠋ Write File  /workspace/project/output.md
  …
  write line 05
  write line 06
  write line 07
  write line 08
  write line 09
  write line 10
  write line 11
  write line 12
  write line 13
  write line 14

Collapsed

  ⠋ Write File  /workspace/project/output.md

rumpl added 10 commits June 28, 2026 12:32
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>

@docker-agent docker-agent left a comment

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.

Assessment: 🟢 APPROVE

The collapsed renderer implementations are clean and well-structured. The refactoring correctly extracts shared logic (e.g. filteredToolArgs, readMultipleFilesMeta, extractCommand) and the collapsed/expanded split follows a consistent pattern across all tool components. Two minor observations noted as inline comments.

return header + "\n" + styles.ToolCallResult.Render(details)
}

func renderDetails(msg *types.Message, width int) string {

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.

[LOW] renderDetails may show stale "pending" status after the tool has already completed

When msg.Content is non-empty (the tool finished) but renderOutputDetails fails to parse the JSON output (e.g. the output is not valid JSON, or is unexpectedly shaped), it returns "" and the function falls through to the arg-based rendering path. That path constructs a todo with Status: "pending" directly from the tool-call arguments, so a completed todo is briefly displayed as pending.

The realistic trigger is a malformed or empty-object JSON result from the todo tool backend; impact is a cosmetic display glitch — the status shown in the TUI won't match the actual outcome until the view is refreshed or re-rendered.

A targeted fix would be to check the tool completion status before falling through:

if msg.Content != "" {
    if details := renderOutputDetails(msg, width); details != "" {
        return details
    }
    // Output parsing failed but tool completed — show nothing rather than stale args
    return ""
}

func render(msg *types.Message, s spinner.Spinner, sessionState service.SessionStateReader, width, _ int) string {
path := toolcommon.ExtractField(func(a filesystem.ReadFileArgs) string { return pathx.ShortenHome(a.Path) })(msg.ToolCall.Function.Arguments)
header := toolcommon.RenderTool(msg, s, path, extractResult(msg), width, sessionState.HideToolResults())
if sessionState.HideToolResults() || msg.ToolStatus == types.ToolStatusError {

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.

[LOW] Read-file preview shown for in-progress / pending status — inconsistent with writefile

The new render function skips the preview only when HideToolResults() is true or the status is ToolStatusError:

if sessionState.HideToolResults() || msg.ToolStatus == types.ToolStatusError {
    return header
}
preview := formatLastLines(msg.Content, width)

If msg.Content is ever non-empty while the tool is still running (e.g. during streaming), a partial preview would be surfaced. The sibling writefile renderer guards against this explicitly:

if msg.ToolStatus == types.ToolStatusCompleted || msg.ToolStatus == types.ToolStatusError {
    result = msg.Content
}

If read_file is guaranteed to only produce content after completion this is fine as-is, but adding a ToolStatusCompleted guard would make the intent explicit and guard against future streaming changes:

if sessionState.HideToolResults() || msg.ToolStatus != types.ToolStatusCompleted {
    return header
}

@aheritier aheritier added area/tui For features/issues/fixes related to the TUI kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Jun 28, 2026
@rumpl rumpl changed the title feat(tui): collapse api tool renderer Tool renderers collapsed/expanded Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/tui For features/issues/fixes related to the TUI kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants