Draft Options
Two to four alternative drafts. The user picks one, edits it if they like, and applies it.
≈ 658 tokens per use (tool 320 · guidance 221 · typical payload 117)How token costs are estimated
Example: Draft Options
This headline is too long for the ad. Give me a few options.
Headline options
Original: Northpeak running shoes for every kind of runner, now on sale
When to use
- The user asked for short copy to be written or rewritten (a headline, subject line, email intro, post), and the right tone or length is a matter of taste.
- A few real alternatives save a round of back-and-forth.
When not to use
- There's one obvious answer. Write it.
- The text is long, like a full document. Use a Change Preview instead.
- The drafts would differ only by a word or two. Pick the best one.
How the agent should use it
- Offer 2–4 drafts that differ in a way you can name in the label: shorter, warmer, more direct.
- Pass the user's text verbatim as
originalwhen rewriting it, so they can see what changed. - Use the returned
textverbatim. The user may have edited it;editedtells you. - On
regenerate, write new drafts that follow the note, if there is one. Oncancel, drop it.
Choosing, editing and comparing
The drafts are a radio group: arrow keys move between them, and the first one is selected when they arrive. Edit opens the selected draft in a text area; edits are kept per draft, marked "Edited", and can be undone.
With original, the selected draft renders as a word-level diff against it: removed words are struck through and muted, added words are underlined. The original itself isn't repeated on screen (the diff carries it); screen readers get the original, the plain draft, and a count of words added and removed. The diff is a small pure function, diffWords(before, after), in draft-options-diff.ts.
None of these asks for new drafts, with an optional note. The ✕ button cancels.
Installation
npx shadcn@latest add https://your-site.com/r/draft-options.json
Usage
import { tool } from "ai"
import { draftOptionsTool } from "@/components/genui/draft-options.schema"
const tools = {
// No execute(): the call is completed by the user's choice in the UI.
[draftOptionsTool.name]: tool({
description: draftOptionsTool.description,
inputSchema: draftOptionsTool.inputSchema,
}),
}<DraftOptions
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({ tool: "offer_drafts", toolCallId: part.toolCallId, output: result })
}
/>The agent receives one of:
{ action: "apply", optionId: "shorter", text: "Northpeak running shoes, now on sale", edited: false }
{ action: "regenerate", note: "Mention free returns" }
{ action: "cancel" }With LangGraph
Push it from a graph node with ui.push({ name: "offer_drafts", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result. The component shows the applied draft (with the user's edits), the regenerate note or the cancellation, and never calls onResult again.
<DraftOptions
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string | What is being drafted |
original | string? | The text being rewritten, verbatim. Enables the diff |
options | { id, label, text }[] | 2–4 drafts. label up to 24 characters |
applyLabel | string? | Verb for the apply button. Default "Apply" |
Host props: status (streaming, ready, submitting, applied, regenerated, cancelled), onResult, result and className.