suiss/ui
  • Docs
  • Components
  • Patterns
  • Interactions
  • Charts
  • Blocks
  • AI
GitHubCreate
DocsComponentsPatternsInteractionsChartsBlocksAICreate
AI21
  • Overview
Conversation
  • Chat
  • Message
  • Prompt Input
  • Suggestion
  • Model Selector
  • Context
  • Checkpoint
  • Inline Citation
Agent steps
  • Chain of Thought
  • Reasoning
  • Tool
  • Confirmation
  • Task
  • Plan
  • Queue
  • Subagent
  • Sources
Output
  • Terminal
  • Stack Trace
  • Test Results
  • File Tree

suiss/ui · AI

Tool

A tool call the agent makes, from input to output, including errors, approvals and output in a sheet.

import { TerminalIcon } from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

const calls = [
  { command: "node --version", output: "v24.18.0" },

Tool is a tool call the agent makes, such as running a command or reading a file. The line follows the call from input to output, including errors and calls that wait for approval.

It is built with Trace: a plain TraceLine while the call runs, and a TraceCollapsible with a Code Block once its output arrives. For several calls under one summary, see Chain of Thought.

Installation

Tool is built with Trace and Code Block. The approval prompt uses Item and Button, and long output can open in a Sheet.

$ pnpm dlx shadcn@latest add @esuiss/trace @esuiss/code-block @esuiss/item @esuiss/button @esuiss/sheet

Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add trace code-block item button sheet

Usage

import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"
<Trace>
  <TraceCollapsible>
    <TraceCollapsibleTrigger>
      <TraceLine>
        <TraceIcon>
          <TerminalIcon />
        </TraceIcon>
        <TraceTitle>Ran git status --short</TraceTitle>
      </TraceLine>
    </TraceCollapsibleTrigger>
    <TraceCollapsibleContent>
      <CodeBlock size="sm">
        <CodeBlockContent wrap className="max-h-64">
          {output}
        </CodeBlockContent>
      </CodeBlock>
    </TraceCollapsibleContent>
  </TraceCollapsible>
</Trace>

Examples

States

A finished call, a failed call, a denied call and a running call.

import { TerminalIcon } from "@phosphor-icons/react"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import {
  Trace,
  TraceCollapsible,
  TraceCollapsibleContent,
  TraceCollapsibleTrigger,
  TraceIcon,
  TraceLine,
  TraceTitle,
} from "@/components/ui/trace"

export default function ToolStates() {
  return (
    <Trace className="max-w-sm">
      <TraceCollapsible>

Map each tool call's state to a status and a title:

Tool callstatusTitle
Input streaming or ready, while the chat streams"running"Running …
Input streaming or ready, after the chat stops"error"Failed to run …
Waiting for approval—Not a line. An Item outside Trace with Approve and Deny buttons.
Approved and running"running"Running …
Output availablenoneRan …
Output error"error"Failed to run …
DeniednoneDenied …

Output in a sheet

For long output, wrap the TraceLine in a SheetTrigger and show the output in the sheet.

import { CaretRightIcon, FileTextIcon } from "@phosphor-icons/react"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { Trace, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

const PACKAGE_JSON = `{
  "name": "acme-chat",

Approval

A pending approval is a question for the person, not a step the agent took, so it sits outside Trace as an Item. Once the person answers, the call goes back to being a line. See Confirmation.

"use client"

import { CheckCircleIcon, TerminalIcon } from "@phosphor-icons/react"
import * as React from "react"

import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
import {
  Item,
  ItemActions,
  ItemContent,
  ItemDescription,
  ItemMedia,
  ItemTitle,
} from "@/components/ui/item"
import { Message, MessageContent } from "@/components/ui/message"

AI SDK

Render each tool part as a line. A call is a plain TraceLine while it runs, and a TraceCollapsible once its output arrives.

function ToolLine({ part, streaming }: { part: ToolPart; streaming: boolean }) {
  const command = part.input?.command

  if (
    part.state === "output-denied" ||
    (part.state === "approval-responded" && !part.approval.approved)
  ) {
    return (
      <TraceLine>
        <TraceIcon>
          <TerminalIcon />
        </TraceIcon>
        <TraceTitle>Denied {command}</TraceTitle>
      </TraceLine>
    )
  }

  if (part.state === "output-available" || part.state === "output-error") {
    const failed = part.state === "output-error"

    return (
      <TraceCollapsible>
        <TraceCollapsibleTrigger>
          <TraceLine status={failed ? "error" : undefined}>
            <TraceIcon>
              <TerminalIcon />
            </TraceIcon>
            <TraceTitle>
              {failed ? "Failed to run" : "Ran"} {command}
            </TraceTitle>
          </TraceLine>
        </TraceCollapsibleTrigger>
        <TraceCollapsibleContent>
          <CodeBlock size="sm">
            <CodeBlockContent wrap className="max-h-64">
              {failed ? part.errorText : part.output.stdout}
            </CodeBlockContent>
          </CodeBlock>
        </TraceCollapsibleContent>
      </TraceCollapsible>
    )
  }

  return (
    <TraceLine status={streaming ? "running" : "error"}>
      <TraceIcon>
        <TerminalIcon />
      </TraceIcon>
      <TraceTitle>
        {streaming ? "Running" : "Failed to run"} {command}
      </TraceTitle>
    </TraceLine>
  )
}

For approvals, render the approval-requested state as an Item and answer it with addToolApprovalResponse:

if (part.state === "approval-requested") {
  return (
    <Item variant="outline" size="sm">
      <ItemMedia variant="icon">
        <TerminalIcon />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>Run {part.input.command}?</ItemTitle>
      </ItemContent>
      <ItemActions>
        <Button
          size="sm"
          variant="outline"
          onClick={() =>
            addToolApprovalResponse({ id: part.approval.id, approved: false })
          }
        >
          Deny
        </Button>
        <Button
          size="sm"
          onClick={() =>
            addToolApprovalResponse({ id: part.approval.id, approved: true })
          }
        >
          Approve
        </Button>
      </ItemActions>
    </Item>
  )
}

Pass sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses to useChat so the chat continues once every approval has an answer. A tool that runs on the client stays input-available after the chat stops until you call addToolOutput; treat it as waiting, not failed.

API Reference

See Trace, Code Block, Item and Sheet.

Accessibility

  • A call with output is a button with aria-expanded; a call in a sheet takes the sheet trigger's focus and Escape behavior.
  • Write the state into the title, such as "Failed to run pnpm dev", so it does not rely on color.
  • The approval buttons are regular buttons with visible labels, and they are not inside the Trace row.
import { CheckCircleIcon, WarningCircleIcon } from "phosphor-react-native"
import { View } from "react-native"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { CodeBlock, CodeBlockContent } from "@/components/ui/code-block"
import { Icon } from "@/components/ui/icon"
import { Message, MessageContent } from "@/components/ui/message"
import { Text } from "@/components/ui/text"
import { Trace, TraceCollapsible, TraceCollapsibleContent, TraceCollapsibleTrigger, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

export function AiToolExample() {
  return (
    <View className="w-full gap-4">
      <Message align="end">
        <MessageContent>
          <Bubble variant="muted">
            <BubbleContent>Which package managers are installed?</BubbleContent>
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ToolExample: View {
  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Message(align: .end) {
        MessageContent {
          Bubble(variant: .muted, align: .end) { BubbleContent("Which package managers are installed?") }
        }
      }
      Trace {
        TraceCollapsible {
          TraceCollapsibleTrigger {
            TraceLine {
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.adamglin.PhosphorIcons
import com.adamglin.phosphoricons.Regular
import com.adamglin.phosphoricons.regular.CheckCircle
import com.adamglin.phosphoricons.regular.WarningCircle
import com.esuiss.ui.components.Bubble
import com.esuiss.ui.components.BubbleContent
import com.esuiss.ui.components.BubbleVariant
import com.esuiss.ui.components.CodeBlock
import com.esuiss.ui.components.CodeBlockContent
import com.esuiss.ui.components.CodeBlockSize

Tool on mobile is built from the same parts as on the web: Trace, Code Block, Message, Bubble. The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

Installation

$ pnpm dlx @esuiss/ui@latest add trace code-block message bubble
$ pnpm dlx @esuiss/ui@latest add trace code-block message bubble --platform swift
$ pnpm dlx @esuiss/ui@latest add trace code-block message bubble --platform kotlin

API Reference

See Trace, Code Block, Message, Bubble.

NextConfirmationPreviousReasoning

On this page

  1. 01Installation
  2. 02Usage
  3. 03Examples
    • States
    • Output in a sheet
    • Approval
  4. 04AI SDK
  5. 05API Reference
  6. 06Accessibility
  7. 07Installation
  8. 08API Reference