Draft Options

Two to four alternative drafts. The user picks one, edits it if they like, and applies it.

Pattern

≈ 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

Headline options

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 original when rewriting it, so they can see what changed.
  • Use the returned text verbatim. The user may have edited it; edited tells you.
  • On regenerate, write new drafts that follow the note, if there is one. On cancel, 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

app/api/chat/route.ts
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,
  }),
}
components/message.tsx
<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

PropTypeNotes
titlestringWhat is being drafted
originalstring?The text being rewritten, verbatim. Enables the diff
options{ id, label, text }[]2–4 drafts. label up to 24 characters
applyLabelstring?Verb for the apply button. Default "Apply"

Host props: status (streaming, ready, submitting, applied, regenerated, cancelled), onResult, result and className.

Source

On this page