Result Receipt
What the agent just did, with undo while it's still possible.
≈ 1,081 tokens per use (tool 767 · guidance 230 · typical payload 84)How token costs are estimated
Example: Result Receipt
Pause the three worst ads from the fall sale.
Paused 3 ads
Meta ad account: Acme Retail
When to use
- The agent just completed an action in an external system (paused ads, updated a record, sent an invoice), and the user should see exactly what changed.
- The system lets the action be reversed for a while, and the user should be able to undo it.
It's the counterpart of Change Preview: the preview asks before acting, the receipt reports after.
When not to use
- The action hasn't run yet. Use a Change Preview to ask first.
- The action failed. Say what went wrong in text, or use a Callout.
- Nothing changed. Say so in a sentence.
How the agent should use it
- Only show it after the action's tool returned success, and fill
itemsfrom that result. - Write the title in the past tense, with the count: "Paused 3 ads".
- Only set
undowhen the system can really reverse the action, and setuntilto the system's real deadline. - On
undo, reverse the action with the system's own undo, then confirm in text.
Undo
With undo.until, the Undo button shows the time left ("Undo 4:59"), updating every second. At zero the button is disabled and the footer says "Undo expired". Without until, Undo stays available. After a press, the footer shows "Undo requested" until the host reports back.
The countdown isn't announced every second: screen readers hear it as the Undo button's description, and only "Undo expired" and "Undo requested" are announced.
Installation
npx shadcn@latest add https://your-site.com/r/result-receipt.json
Usage
import { tool } from "ai"
import { resultReceiptTool } from "@/components/genui/result-receipt.schema"
const tools = {
// Without undo, add `execute: async () => ({ shown: true })`.
[resultReceiptTool.name]: tool({
description: resultReceiptTool.description,
inputSchema: resultReceiptTool.inputSchema,
}),
}<ResultReceipt
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({ tool: "show_receipt", toolCallId: part.toolCallId, output: result })
}
/>When the user presses Undo, the agent receives:
{ action: "undo" }With LangGraph
Push it from a graph node with ui.push({ name: "show_receipt", 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 receipt shows "Undo requested" and never calls onResult again. A receipt reloaded after its until deadline, without an undo, shows "Undo expired".
<ResultReceipt
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string | Past tense: "Paused 3 ads" |
target | { label, kind? }? | Data-bound. The system or record that changed. kind is read to screen readers only |
items | { label, before?, after?, detail? }[] | 1–20. Data-bound. Shown as before → after; detail is one quiet line under the label (an address, an ID), not a reason |
completedAt | string | Data-bound. ISO 8601 |
undo | { label?, until? }? | Only when the action can really be reversed. until is ISO 8601 |
link | { label, url }? | http(s) only. Opens in a new tab |
Host props: status (streaming, ready, undone), onResult, result and className.