npx tool-agent "integrate the message draft component to review and approve messages before sending"npx shadcn@latest add @tool-ui/message-draftAn AI sending a message on your behalf without asking is a trust violation. MessageDraft shows the full email or Slack message for review, gives you a Send button with an undo grace period, and collapses into a compact receipt once sent or cancelled. The agent never sends silently.
Role: Decision. For choices that return to the assistant. See Design Guidelines for how component roles work.
Run this once from your project root.
npx tool-agent "integrate the message draft component to review and approve messages before sending"npx shadcn@latest add @tool-ui/message-draftRender MessageDraft in your UI with tool-compatible props.
import { MessageDraft } from "@/components/tool-ui/message-draft";export function Example() { return ( <MessageDraft id="message-draft-example" channel="email" subject="Q4 Planning Follow-up" to={["sarah.chen@company.com"]} body={`Hi Sarah,Thanks for joining the planning meeting today. I've attached the updated timeline.Best,Alex`} onSend={() => console.log("Sent")} onCancel={() => console.log("Cancelled")} /> );}Register this renderer so tool results display as MessageDraft.
"use client";import { type Toolkit } from "@assistant-ui/react";import { MessageDraft } from "@/components/tool-ui/message-draft";import { safeParseSerializableMessageDraft, SerializableMessageDraftSchema,} from "@/components/tool-ui/message-draft/schema";export const toolkit: Toolkit = { draftMessage: { description: "Draft a message for the user to review before sending.", parameters: SerializableMessageDraftSchema, render: ({ args, toolCallId, result, addResult }) => { const parsedArgs = safeParseSerializableMessageDraft({ ...args, id: args?.id ?? `message-draft-${toolCallId}`, }); if (!parsedArgs) { return null; } return result ? ( <MessageDraft {...parsedArgs} outcome={result} /> ) : ( <MessageDraft {...parsedArgs} onSend={async () => { // Perform actual send logic here await sendMessage(parsedArgs); await addResult?.("sent"); }} onCancel={() => addResult?.("cancelled")} /> ); }, },};Nothing sends until the user clicks Send
Configurable countdown after Send where the user can still cancel
Email shows subject/recipients, Slack shows channel or DM target
Collapses to a one-line confirmation after send or cancel
Two channel types, each with its own metadata layout.
Shows subject line, To/CC/BCC recipients, and message body.
Shows channel or DM target with the Slack icon.
Clicking Send starts a countdown. The user can undo before it completes:
Configure the countdown length with undoGracePeriod (in milliseconds).
Pass outcome="sent" to render a one-line confirmation receipt. outcome="cancelled" hides the component entirely.
Prop
Type
Prop
Type
Prop
Type
article with aria-labelledby for the draft contentTab to move between buttonsEnter or Space to activate focused buttonEscape triggers cancel while in review statearia-live="polite"role="status" for screen reader announcements