Itinerary

An ordered day plan or route as a list of stops, with times and thumbnails. Optionally lets the user drop stops and add them back.

Pattern

≈ 1,293 tokens per use (tool 589 · guidance 238 · typical payload 466)How token costs are estimated

Example: Itinerary

Plan a day in San Francisco.

A day in San Francisco

  1. Ferry Building clock tower
    9:00 AM1 hr · Marketplace on the Embarcadero
  2. Coit Tower on its hill
    10:30 AM45 min · Tower on Telegraph Hill
  3. Fishing boat by a pier
    12:30 PM1.5 hr · Lunch on the waterfront
  4. Golden Gate Bridge towers
    3:45 PM1 hr · Walk out from the south end
  5. Row of Victorian houses
    5:30 PM30 min · Victorian houses on Alamo Square

When to use

  • A day plan or route in order: a day in a city, a museum afternoon, a food crawl.
  • The user may want to trim the plan. Set editable so they can drop stops and add them back.
  • Next to a Map in itinerary mode: a stop has the same shape, so the host can pass the same stops to both.

When not to use

  • Where the stops are matters more than their order. Use the Map, or both with the same stops.
  • A trip over several days. Use the map's itinerary mode, which filters by day.
  • Steps the user carries out and ticks off. Use a Checklist.

How the agent should use it

  • Send stops in visiting order, with a time each when the plan has a schedule ("9:00 AM") and a duration when it helps ("1 hr").
  • Take places, coordinates and links from a tool result, never from memory.
  • Keep description to one plain fact ("Tower on Telegraph Hill"). No ratings, prices or opening hours that a tool didn't return.
  • Give each stop a kind when one fits. Without an image, the kind's icon fills the thumbnail.
  • Mark stops the user asked for with added. They show a small "Added" tag.
  • On change, treat the removed stops as dropped and re-plan the times if the user asks.
  • Lifecycle: a plan is a snapshot, so none of expiresAt or fetchedAt is needed. When the agent sends a revised plan, mark the old call superseded by the new one.

Interaction

  • With editable, each stop has a round button at the right: remove (−), then add back (↶). Its label names the stop, e.g. "Remove Coit Tower from the route".
  • A removed stop stays in place, dimmed and struck through, with a grayscale thumbnail, so it's easy to bring back.
  • When something is removed, a footer shows the count ("2 removed") and a Reset button. Changes are announced to screen readers.
  • A stop with a url has its name as a link, opened in a new tab.
  • The list never locks. While streaming, stops fade in one by one and the buttons are disabled until the call finishes.

Installation

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

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { itineraryTool } from "@/components/genui/itinerary.schema"

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

The plan is useful on its own, so give the tool an execute that returns immediately and send onResult calls to the agent as messages. With editable, it sends change on every remove, add-back or reset, with the full list of removed stops:

{ action: "change", removed: ["coit-tower", "painted-ladies"] }

// After opening a stop's link
{ action: "open", stopId: "golden-gate-bridge" }

Syncing with a map

Pass removed to control the list from the host, for example to hide the same stops on a Map. When set, it wins over the list's own state; update it from change results:

const [removed, setRemoved] = useState<string[]>([])
const kept = stops.filter((s) => !removed.includes(s.id))

<Itinerary
  title="A day in San Francisco"
  stops={stops}
  editable
  removed={removed}
  onResult={(r) => r.action === "change" && setRemoved(r.removed)}
/>
<MapView title="Route" mode="itinerary" stops={kept} />

With LangGraph

Push it from a graph node with ui.push({ name: "show_itinerary", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

When a conversation is reloaded, pass the last stored result back as result. A change result restores the removed stops. The list stays usable, but it never calls onResult again, so nothing is sent twice:

<Itinerary
  {...part.input}
  result={lastResult}
/>

Schema

PropTypeNotes
titlestringe.g. "Your route"
stopsStop[]1–30, in visiting order. Data-bound
editablebooleanEach stop gets a remove / add-back button. Default false

Stop

FieldTypeNotes
idstringReturned in the result
namestring
timestring?Shown above the name, e.g. "9:00 AM". Up to 16 characters
durationstring?Shown in the line under the name, e.g. "1 hr". Up to 16 characters
descriptionstring?One line, up to 120 characters
image{ src, alt }?Square thumbnail. src is an http(s) or data:image URL
kindstring?Icon when there's no image. Same set as the map
lat, lngnumber?Not shown. Lets the same stop be passed to the map
urlstring?http(s). The name becomes a link
addedboolean?The user asked for this stop. Shows an "Added" tag

A stop's kind is one of beach, mountain, city, forest, lake, waterfall, harbor, museum, food, hotel, viewpoint or activity, with the same icons as the map's markers. The schema exports them as ITINERARY_STOP_KINDS.

Host props: status (streaming, ready), onResult, result, removed (controlled removed ids) and className.

Source

On this page