Skip to content

feat(ui): Rich tool result rendering with auto-detection and custom renderers #250

@mmogr

Description

@mmogr

Summary

Tool call results in the chat UI are displayed as raw JSON data. There are no tool-specific renderers that format results for human readability. For example, a web search tool might return a JSON array of results, but the user sees [{"title":"...","url":"...","snippet":"..."},...] instead of a formatted list with clickable links.

Current State

Tool call parts in messages have this structure (from types.ts):

// In GglibContent (assistant-ui parts):
{
  type: 'tool-call',
  toolCallId: string,
  toolName: string,
  args: Record<string, unknown>,
  argsText: string,
  result?: unknown,      // Raw JSON — rendered as-is
  isError?: boolean,
}

The rendering is generic — args and results are stringified JSON.

Proposed Solution

Phase 1: Tool result renderer extension point

Add a resultRenderer field to RegisteredTool:

export type ToolResultRenderer = (result: unknown, args: Record<string, unknown>) => {
  /** Summary text (always shown) */
  summary: string;
  /** Formatted detail (shown on expand, supports markdown) */
  detail?: string;
  /** Whether to show as collapsible (default: true for large results) */
  collapsible?: boolean;
};

export interface RegisteredTool {
  definition: ToolDefinition;
  executor: ToolExecutor;
  source: ToolSource;
  resultRenderer?: ToolResultRenderer;  // NEW
}

Phase 2: Default renderers for common patterns

Create sensible default renderers that auto-detect common result shapes:

Result Shape Rendering
string Inline text or markdown
{ error: string } Red error badge with message
Array of objects Table or formatted list
{ url: string, title: string } Clickable link
Large JSON (>500 chars) Collapsible with summary
{ content: string, ... } Rendered content with metadata badge

Phase 3: MCP tool renderers

MCP tools can provide hints via their JSON Schema descriptions. Use these to infer rendering:

  • If result has url fields → render as links
  • If result is a list → render as numbered list
  • If schema describes markdown or text content → render as formatted text

Files to Modify/Create

File Change
src/services/tools/types.ts Add ToolResultRenderer type, resultRenderer field
src/services/tools/registry.ts Support resultRenderer in registration
src/services/tools/renderers/ New directory for default renderers
src/services/tools/renderers/defaultRenderer.ts Auto-detection renderer
src/services/tools/renderers/index.ts Barrel exports
src/components/ Update tool call display component to use renderers

Acceptance Criteria

  • RegisteredTool supports optional resultRenderer field
  • Default renderer auto-detects common result patterns (string, array, error, URL)
  • Large results are collapsible with a visible summary
  • Error results display distinctly (red styling, error icon)
  • Built-in tools can register custom renderers
  • MCP tools use the default renderer (with future extensibility for schema-driven rendering)
  • Renderer output supports markdown formatting
  • No regression — tools without renderers display as before (JSON fallback)

Sub-issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions