reopt designreopt design
DocsExploreToolsPricingBuilder
Start
Overview
Start
Core Concepts
Component Guide
Rich Parts
Styling
Build & Operate
Tool Approval UI
Custom Parts
Production readiness
Oopt-chat
reopt designreopt design

A design system for the AI era

  • Docs
  • Pricing
  • Releases
  • GitHub
  • Terms of Service
  • Privacy Policy

© 2026 reopt-ai. All rights reserved.

Build & Operate
  1. Docs
  2. /
  3. Build & Operate
  4. /
  5. Tool Approval UI

도구 승인

AI가 도구를 호출할 때 사용자에게 승인을 요청하고, 승인/거부 상태에 따라 다른 UI를 표시하는 패턴을 설명합니다.

reopt design · Updated Jun 26, 2026

1. 세션 설정

서버 도구가 승인을 요구하면 AI SDK는 tool part를 approval-requested 상태로 전달합니다. approveToolCall / denyToolCall은 이 part의 approval.id로 응답합니다.

tsx
"use client";

import { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
import {
  useChatSession,
  Conversation,
  ConversationContent,
  Message,
  MessageContent,
  MessageParts,
  PromptInput,
  PromptInputFooter,
  PromptInputSubmit,
  PromptInputTextarea,
} from "@reopt-ai/opt-chat";

export default function ChatWithApproval() {
  const session = useChatSession({
    transport: new DefaultChatTransport({ api: "/api/chat" }),
    // 승인 응답 후 다음 tool step을 자동 진행합니다.
    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
  });

  return (
    <div className="flex h-dvh flex-col">
      <Conversation>
        <ConversationContent>
          {session.messages.map((msg) => (
            <Message key={msg.id} from={msg.role}>
              <MessageContent>
                <MessageParts parts={msg.parts} />
              </MessageContent>
            </Message>
          ))}
        </ConversationContent>
      </Conversation>
      <PromptInput onSubmit={session.handleSubmit} isLoading={session.isLoading} onStop={session.stop}>
        <PromptInputTextarea />
        <PromptInputFooter>
          <div className="flex-1" />
          <PromptInputSubmit />
        </PromptInputFooter>
      </PromptInput>
    </div>
  );
}

2. Confirmation Compound 컴포넌트

Confirmation은 compound 패턴으로 상태별 콘텐츠를 분리합니다. 각 하위 컴포넌트는 현재 승인 상태에 따라 조건부로 렌더링됩니다.

컴포넌트역할
Confirmation도구 승인 UI의 루트 컨테이너. AI SDK part.state와 approvalId를 관리합니다.
ConfirmationContent도구 호출의 이름, 인자 등 상세 내용을 보여주는 영역입니다.
ConfirmationRequest승인 대기 상태에서 표시되는 콘텐츠. '이 도구를 실행할까요?' 메시지 등.
ConfirmationAccepted승인 후 표시되는 콘텐츠. 실행 중/완료 상태를 보여줍니다.
ConfirmationRejected거부 후 표시되는 콘텐츠. '사용자가 거부했습니다' 메시지 등.
ConfirmationActions승인/거부 버튼을 배치하는 영역. 기본 버튼 쌍을 제공합니다.
tsx
import {
  Confirmation,
  ConfirmationContent,
  ConfirmationRequest,
  ConfirmationAccepted,
  ConfirmationRejected,
  ConfirmationActions,
} from "@reopt-ai/opt-chat";

// renderPart에서 approval-requested tool part를 감지해 Confirmation을 렌더합니다.

<Confirmation
  state={part.state}
  approvalId={part.approval?.id}
  approved={part.approval?.approved}
  onApprove={session.approveToolCall}
  onDeny={session.denyToolCall}
>
  <ConfirmationContent>
    <p>도구: {part.type.replace("tool-", "")}</p>
    <pre>{JSON.stringify(part.input, null, 2)}</pre>
  </ConfirmationContent>

  <ConfirmationRequest>
    <p>이 도구를 실행할까요?</p>
    <ConfirmationActions />
  </ConfirmationRequest>

  <ConfirmationAccepted>
    <p>도구 실행이 승인되었습니다.</p>
  </ConfirmationAccepted>

  <ConfirmationRejected>
    <p>사용자가 도구 실행을 거부했습니다.</p>
  </ConfirmationRejected>
</Confirmation>

3. 수동 승인/거부

Confirmation 컴포넌트를 사용하지 않고 직접 승인/거부를 제어하려면, session의 approveToolCall과 denyToolCall 메서드를 호출합니다.

tsx
// 수동으로 승인/거부를 제어하는 경우
const session = useChatSession({
  transport: new DefaultChatTransport({ api: "/api/chat" }),
});

// AI SDK part 객체 또는 part.approval.id 문자열을 모두 받을 수 있습니다.
session.approveToolCall(part);
session.approveToolCall(part.approval.id);

session.denyToolCall(part, "사용자가 거부했습니다");
PreviousStylingCHAT_* 토큰 체계, opt-ui 테마 연동, 다크모드, 커스텀 스타일 오버라이드Core Concepts
Go to Styling
NextCustom PartsMessageParts의 renderPart 콜백으로 커스텀 파트를 추가하는 방법Build & Operate