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

Confirmation

Asks for approval before an agent runs a tool, then shows the call as a step once the person answers.

"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"
import {

Confirmation asks the person for a yes or no before a tool runs. The question is an Item with Deny and Approve buttons. Once the person answers, the call becomes a Tool row.

A pending approval is a prompt, not activity, so it sits outside the Trace.

Installation

Confirmation is built with the Item, Button and Trace components.

$ pnpm dlx shadcn@latest add @esuiss/item @esuiss/button @esuiss/trace

Or use the suiss/ui CLI:

$ pnpm dlx @esuiss/ui@latest add item button trace

Usage

<Item variant="outline" size="sm">
  <ItemMedia variant="icon">
    <TerminalIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Run {command}?</ItemTitle>
    <ItemDescription>The command runs in this project.</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button size="sm" variant="outline" onClick={deny}>
      Deny
    </Button>
    <Button size="sm" onClick={approve}>
      Approve
    </Button>
  </ItemActions>
</Item>
<Item variant="outline" size="sm">
  <ItemMedia variant="icon">
    <TerminalIcon />
  </ItemMedia>
  <ItemContent>
    <ItemTitle>Run {command}?</ItemTitle>
    <ItemDescription>The command runs in this project.</ItemDescription>
  </ItemContent>
  <ItemActions>
    <Button size="sm" variant="outline" onPress={deny}>
      Deny
    </Button>
    <Button size="sm" onPress={approve}>
      Approve
    </Button>
  </ItemActions>
</Item>

After the answer, replace the item with a row that says what happened: "Ran rm -rf dist" or "Denied rm -rf dist".

AI SDK

While the part is approval-requested, answer with the approval id. Pass sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses so the chat continues once every approval has an answer.

addToolApprovalResponse({
  id: part.approval.id,
  approved: true,
})
useChat({
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
})

Approve streams the tool output. Deny streams output-denied. Either way, the part becomes a Tool row. Set needsApproval on the tool on the server to ask first.

API Reference

See Item, Button and Trace.

Accessibility

  • Put the command in the item title, so the question names what will run.
  • Deny comes before Approve, so the safe answer is reached first with Tab.
  • Once answered, the buttons are removed and the row title says the outcome in words.
import { ShieldWarningIcon, TerminalIcon } from "phosphor-react-native"
import * as React from "react"
import { View } from "react-native"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Button } from "@/components/ui/button"
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, TraceIcon, TraceLine, TraceTitle } from "@/components/ui/trace"

type Decision = "pending" | "approved" | "denied"

function ConfirmationExample() {
  const [decision, setDecision] = React.useState<Decision>("pending")
  return (
import EsuissUI
import PhosphorSwift
import SwiftUI

enum ConfirmationDecision {
  case pending, approved, denied
}

struct ConfirmationExample: View {
  @State private var decision = ConfirmationDecision.pending

  var body: some View {
    VStack(alignment: .leading, spacing: 16) {
      Message(align: .end) {
        MessageContent {
          Bubble(variant: .muted, align: .end) { BubbleContent("Clean up the build folder.") }
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.ShieldWarning
import com.adamglin.phosphoricons.regular.Terminal
import com.esuiss.ui.components.Bubble

Confirmation on mobile is built from the same parts as on the web: Trace, Code Block, Button, 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 button message bubble
$ pnpm dlx @esuiss/ui@latest add trace code-block button message bubble --platform swift
$ pnpm dlx @esuiss/ui@latest add trace code-block button message bubble --platform kotlin

API Reference

See Trace, Code Block, Button, Message, Bubble.

NextTaskPreviousTool

On this page

  1. 01Installation
  2. 02Usage
  3. 03AI SDK
  4. 04API Reference
  5. 05Accessibility
  6. 06Installation
  7. 07API Reference