Doctor Appointment
Book a medical appointment from a clinic's real availability, or show an upcoming one.
Experience · Shares patterns with Slot Picker, Entity Picker
≈ 2,563 tokens per use (tool 1,282 · guidance 459 · typical payload 822)How token costs are estimated
Example: Doctor Appointment
Book a GP appointment this week
Harbor Family Health
88 Harbor Street, Suite 210, Boston, MA 02110
When to use
- The user wants to book a medical, dental or therapy appointment and the agent has the clinic's real availability: "book a GP appointment this week", "find a dermatologist who takes my insurance".
- The user asks about an upcoming appointment (
mode: "confirmation"): "when is my dentist appointment?".
When not to use
- The user describes urgent symptoms (chest pain, trouble breathing, severe bleeding, thoughts of self-harm). The agent tells them to call their local emergency number or go to the nearest emergency department, in text. No booking card.
- The user asks what's wrong or what to do about a symptom. The agent doesn't triage, diagnose or advise; it suggests talking to a clinician.
- The agent hasn't fetched availability. Never invent providers or times.
- It's a generic meeting, not a clinic. Use the Slot Picker.
How the agent should use it
- Pass providers, visit types and slots exactly as the scheduling API returned them, each slot's
startas an ISO instant with offset. Up to 6 providers and 60 slots. - Only include
genderwhen the user asked to filter by it. - Set
insurance.selectedonly when the user's plan is known, and passcoverageNoteonly as the source states it. - Keep
reasonsneutral ("Check-up", "Follow-up"), never symptoms or conditions. - On
book, start the host's booking flow with exactly the returnedproviderId,visitTypeIdandstart. Never treat it as booked until the clinic confirms. reasonandnoteare health details. Send them to the clinic only. Never repeat them in chat summaries, titles, calendar events or URLs.- On
reschedule, fetch fresh availability and show select mode again. Oncancel-appointment, cancel through the scheduling tool and confirm in one line. Ondismiss, do nothing. - Lifecycle: slots are live data, so set
fetchedAtandstaleAfterMs(about 5 minutes) on select-mode calls. When the scheduling API holds a slot for the user, setexpiresAtto the end of the hold. When the agent fetches availability again (another provider, another week, after a reschedule), mark the old call superseded by the new one. A confirmation is a record and needs none of these. See States.
Safety
The card never triages, diagnoses or advises. It always shows one static line, "Emergency? Call your local emergency number.", that the agent can't change or remove. Urgent requests are the agent's job, in text, before any card is shown.
Health details are kept to a minimum. The reason chips and note are optional and sit behind a closed Add a reason or note disclosure. They're only sent in the book result: the card's summary, the booked record and the calendar file leave them out (the calendar event names the provider and clinic, not the specialty). Nothing goes in a URL. Screen readers hear this when they open the disclosure; it isn't printed on the card.
Interaction
Select mode. One row of filters: a Provider menu (Any provider first; each provider's specialty, languages and "Not accepting new patients" show inside the menu, not on the card), the visit types as a segmented choice with durations, and Earliest: Tue 9:40 AM with Dr. Chen, which jumps to the first open time. The days strip lists every day with availability; days with nothing for the current provider and visit type are struck through and disabled. Time chips name the provider when Any provider is on. Times are always in the clinic's zone; the zone ("Times in America/New_York (GMT-4)", with the offset for that day, so a DST week reads correctly) is shown only when the viewer's clock is on a different offset, and screen readers always hear it with the times. Once a time is picked it shows big under the form ("9:40 AM", with the day, provider and visit type). The insurance select lists the accepted plans and says Accepted (with the coverage note) or Not listed ("You may pay out of pocket"). Reason and note are folded away (see Safety); the note's character count appears near the 300 limit. Book appointment stays disabled until a time is picked and never books anything by itself: it hands the choice to the host.
Confirmation mode. Leads with the time, big, under the date and how far away it is ("In 3 days", from the viewer's clock after mount or the now prop), then the provider and visit type, and the booking state. Below: either the location with a Directions link (OpenStreetMap) or Join video visit, the confirmation number (with copy) and intake forms as links. The video link turns on checkInOpensMinutesBefore minutes before the start ("Opens 15 min before" until then) and off once the visit is over. Reschedule is the primary action and Cancel appointment asks for a second, explicit click; Add to calendar (saves an .ics file in the browser) and Call clinic (a tel: link) are labelled icon buttons.
Installation
npx shadcn@latest add https://your-site.com/r/doctor-appointment.json
Usage
import { tool } from "ai"
import { doctorAppointmentTool } from "@/components/genui/doctor-appointment.schema"
const tools = {
// No execute(): the call is completed by the user's choice in the UI.
[doctorAppointmentTool.name]: tool({
description: doctorAppointmentTool.description,
inputSchema: doctorAppointmentTool.inputSchema,
}),
}import { DoctorAppointment } from "@/components/genui/doctor-appointment"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-book_doctor_appointment") return null
return (
<DoctorAppointment
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "book_doctor_appointment",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}The agent receives one of:
{ action: "book", providerId: "chen", visitTypeId: "office", start: "2026-10-13T09:40:00-04:00", insurance: "Northwind Health PPO", reason: "Follow-up", note: "Bringing my blood test results" }
{ action: "dismiss" }
{ action: "reschedule" } // confirmation mode
{ action: "add-to-calendar" } // confirmation mode, after the .ics is saved
{ action: "cancel-appointment" } // confirmation mode, after the second clickinsurance, reason and note are left out when the user didn't choose them.
With LangGraph
Push it from a graph node with ui.push({ name: "book_doctor_appointment", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
Pass the stored result back as result (or an array of results, for several confirmation actions). Select mode shows what was sent ("Sent. Not booked until the clinic confirms." or "Nothing was booked."), without the reason or note; confirmation mode marks the used actions ("Cancellation requested"). It never calls onResult again.
<DoctorAppointment
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
mode | "select" | "confirmation" | |
clinic | { name, address?, phone?, timeZone }? | Data-bound. timeZone is IANA; every time is shown in it |
providers | Provider[] | Data-bound. 1–6 |
visitTypes | { id, label, durationMin? }[] | Data-bound. e.g. "In person", "Video", "Phone" |
slots | { providerId, visitTypeId, start }[] | Data-bound. Select mode, up to 60. start is an ISO instant with offset |
insurance | { accepted, selected?, coverageNote? }? | Data-bound. accepted is plan names |
reasons | string[]? | Up to 8 neutral reasons, shown as chips in the reason-and-note disclosure |
forms | { label, url }[]? | Data-bound. Intake forms. http(s) only |
confirmation | Appointment? | Data-bound. Confirmation mode |
Provider
| Field | Type | Notes |
|---|---|---|
id, name, specialty | string | e.g. "Dr. Maya Chen", "Family medicine" |
languages | string[]? | |
acceptingNew | boolean? | false shows "Not accepting new patients" in the provider menu |
gender | string? | Only when the user asked to filter by it |
Appointment
| Field | Type | Notes |
|---|---|---|
number | string | Confirmation number |
bookingState | "confirmed" | "pending" | "cancelled" | |
providerId, visitTypeId | string | Ids from providers and visitTypes |
start | string | ISO instant with offset |
location | string? | Defaults to the clinic address. Adds Directions |
videoUrl | string? | http(s) only. Adds Join video visit |
checkInOpensMinutesBefore | number? | When the video link turns on |
Host props: status (streaming, ready, submitting, booked, dismissed, done, cancel-requested), onResult, result, now and className.