Password Reveal

Shows a secret the user is entitled to see, masked until they reveal it, or generates a strong password in the browser.

Pattern

≈ 1,067 tokens per use (tool 678 · guidance 359 · typical payload 30)How token costs are estimated

Example: Password Reveal

What's the guest Wi-Fi password?

Guest Wi-Fi password

Password

Network: Acme Guest

Hidden

When to use

  • The user asks for a secret they're entitled to see and a tool returned it: a shared Wi-Fi password, backup codes, an API key they just created, a door code.
  • The user wants a new strong password. Use kind: "generated" so it's made in their browser and the agent never sees it.

When not to use

  • The agent hasn't checked that the user may see the secret. Check access first; never reveal someone else's credentials.
  • The agent doesn't have the secret from a tool result. It never invents, guesses or reconstructs one.
  • The value isn't secret (a booking reference, an order number). Show it as text.
  • The user wants a guest network as a scannable code. Use the QR Code.

Security rules

These are enforced in the component, not left to the agent:

RuleHow
Masked by defaultThe card opens showing a fixed 10-dot mask. While masked, the secret is not in the DOM at all, and the mask has the same length for every secret, so it doesn't hint at the real length
Reveal is temporaryReveal (aria-pressed) shows the secret for revealSeconds (default 30) with a visible "Hides in 28 s" countdown, then hides it. It also hides as soon as the tab is hidden (visibilitychange)
Nothing leaksThe secret never goes into results, analytics, aria-live text, document.title, URLs or a title attribute. Announcements are fixed text ("Copied", "Hidden again")
Results are actions only{ action: "revealed" | "copied" | "rotated-request" | "dismiss" }, never the value
Marked privateThe revealed value sits in a monospace element with data-private (honored by session-replay and analytics tools that support it), translate="no" (no machine translation), spellcheck="false", autocomplete="off" and password-manager ignore hints
One-time secretsWith oneTime, the first reveal ends for good: once it hides, the component drops the value and shows "This code was shown once."
Generated locallykind: "generated" makes the password in the browser with crypto.getRandomValues, uniformly (rejection sampling, never Math.random). The agent never sees it
Clipboard clears (best effort)After a copy, the component waits clearClipboardAfter seconds (default 60). If the browser already lets the page read the clipboard without prompting and the clipboard still holds the secret, it writes an empty string. Otherwise it leaves the clipboard alone and tells the user to clear it themselves

Best effort means best effort. Most browsers don't grant clipboard read without a prompt, so on most devices the component can't clear the clipboard and says so ("Copied. Clear your clipboard when you're done."). It never prompts for clipboard access and never overwrites something else the user copied since. Clipboard history tools and synced clipboards can keep a copy regardless.

Keep the secret out of the model. The masking protects the screen, not the transcript. Where you can, have the host's reveal_secret tool fetch the secret server-side and pass value to the UI only, rather than having the model copy it into the tool call. Don't persist value in chat history you replay to the model, and drop it from stored history for one-time codes.

How the agent should use it

  • Pass the value exactly as the tool returned it. value is data-bound: it comes from a tool result, never from model free text. Never repeat it in the reply, a summary or another tool call.
  • For a new password, use kind: "generated" with generate options (length 12–64, symbols, numbers, uppercase, exclude for characters the target site rejects). Don't put a password the model made up in value.
  • Set oneTime for recovery and one-time codes, rotatable when the agent can issue a replacement, and expiresAt when the secret lapses (shown as "Expires in 23 h").
  • Put where to use it in hint (the network, the door, the service), never the secret.
  • On rotated-request, issue a new secret and show a new card. The old card stays hidden.
  • Lifecycle: when the secret itself lapses (a temporary door code, a short-lived token), set expiresAt on the call and pass the same value as the expiresAt prop, so the card shows "Expires in 23 h" and the Renderer turns it inert afterwards. When a rotation replaces it, mark the old call superseded by the new one. fetchedAt isn't needed.

Interaction

ControlDoesReports
Reveal (eye icon)Shows the secret for revealSeconds; press again to hide earlyrevealed
CopyCopies the secret (codes one per line) and schedules the best-effort clearcopied
Regenerate (icon)Makes a new password with the current options (generated only)nothing
Length, Uppercase, Numbers, SymbolsChange the options and regenerate (generated only); the strength meter shows "Strong · ~126 bits" (length × log2 of the character set, rounded down)nothing
Request newCloses the card and asks for a replacement (rotatable only)rotated-request
Done (✕ icon)Hides the secret, forgets it and closes the carddismiss

Icon-only controls have full accessible names ("Reveal Guest Wi-Fi password") and fixed-text tooltips, never the secret. The kind (Password, Codes, …) is read to screen readers; one-time cards add one line, "Shown once. Save it before it hides."

Codes (kind: "codes") are revealed as a numbered two-column grid. revealed and copied can repeat; rotated-request and dismiss end the card.

Installation

npx shadcn@latest add https://your-site.com/r/password-reveal.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { passwordRevealTool } from "@/components/genui/password-reveal.schema"

const tools = {
  [passwordRevealTool.name]: tool({
    description: passwordRevealTool.description,
    inputSchema: passwordRevealTool.inputSchema,
  }),
}

onResult receives an action, never the secret:

{ action: "revealed" } // or "copied", "rotated-request", "dismiss"

Only rotated-request needs the agent's attention; the others are for your analytics.

With LangGraph

Push it from a graph node with ui.push({ name: "reveal_secret", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer. Fetch the secret inside the node and put it in props only, not in the message the model reads.

Restoring from history

Pass the stored result back as result. dismiss and rotated-request restore the closed card, and any result on a oneTime secret restores it as already shown. The secret is never shown again from history, and onResult isn't called.

<PasswordReveal
  {...part.input}
  result={part.state === "output-available" ? part.output : undefined}
/>

Schema

PropTypeNotes
labelstringWhat it is: "Guest Wi-Fi password"
kind"password" | "code" | "codes" | "api-key" | "pin" | "generated"codes is a list; generated makes one locally
valuestring | string[]?Data-bound. The secret, a list for codes. Omit for generated
hintstring?Where to use it. Never the secret
revealSecondsnumber5–300. Default 30
oneTimebooleanGone after the first reveal. Default false
clearClipboardAfternumberSeconds, 0–600; 0 turns it off. Default 60
generate{ length, symbols, numbers, uppercase, exclude? }?For generated. Length 12–64, default 20; classes default on
rotatablebooleanShows "Request new". Default false
expiresAtstring?Data-bound. ISO 8601; shown as "Expires in 23 h"

Host props: status (streaming, ready, consumed, dismissed, rotation-requested), onResult, result and className.

Source

On this page