Galadri

Rich Results

Render Galadri chat results as maps, cards, routes, and data mutation cards.

Galadri chat streams return both assistant text and structured result data. Your UI should render the assistant text normally, then render the selected result IDs as cards, maps, or route views where the assistant places display directives.

Chat surfaces only

Rich results are a chat-surface feature. Email, SMS, and voice channels receive finished plain text. They do not expose maps, cards, suggested prompts, mutation cards, or display directives.

Implementation Contract

Build your renderer around these four pieces:

  • Structured result events: SSE events with type: "tool_result" contain the full data your UI renders.
  • Stable result IDs: Renderable items include IDs such as place_0, station_1, or route_ab12cd.
  • Display directives: Assistant text can include invisible HTML comments that tell your UI which IDs to render and which layout to use.
  • Data mutation events: data_saved events describe records created, updated, or deleted during the turn.

SSE event fields

FieldTypeDescription
type"tool_result"Event type for structured result data.
idstringInternal event correlation ID. Treat as opaque.
resultsToolResult[]One or more capability results produced during the turn.
results[].toolstringTool slug, such as google-maps-search, traffic, telemetry, or vehicle-search.
results[].status"success" | "error" | "initiated"Success contains data. Error contains an error message. Initiated means background work started.
results[].dataobjectFull renderable payload when status is success.
results[].errorstringError message when status is error.
Types to model in your client
type ToolResultStatus = "success" | "error" | "initiated";
type DisplayLayout = "cards" | "map" | "cards+map";

interface ToolResult {
  tool: string;
  status: ToolResultStatus;
  data?: Record<string, unknown>;
  error?: string;
}

interface ToolResultEvent {
  type: "tool_result";
  id: string;
  results: ToolResult[];
}

interface DisplayDirective {
  items: string[];
  layout?: DisplayLayout;
}

interface RichResultItem {
  toolSlug: string;
  data: Record<string, unknown>;
}

Display Directives

Display directives are embedded in assistant content as HTML comments. Markdown renderers hide the comments, so text-only clients can strip or ignore them. Rich clients should parse them and render the referenced items exactly where they appear in the message.

Directives can be single-line or pretty-printed across multiple lines. Your parser should capture everything between <!--galadri:display and -->, then parse the captured JSON.

Directive syntax
<!--galadri:display {"items":["station_0","station_1"],"layout":"cards+map"}-->

Directive fields

FieldTypeDescription
itemsstring[]Required. Stable IDs previously received in structured result events.
layout"cards" | "map" | "cards+map"Optional. If omitted, use the best default renderer for the item type.
Assistant content with an inline directive
I found two good charging options near you. ChargePoint is closer, while Electrify America has more fast connectors.

<!--galadri:display {"items":["station_0","station_1"],"layout":"cards+map"}-->

Want me to route you to one of them?
Minimal directive parser
const directivePattern = /<!--galadri:display\s+([\s\S]*?)-->/g;

function parseDisplayDirectives(content: string) {
  const segments: Array<
    | { type: "text"; text: string }
    | { type: "display"; directive: DisplayDirective }
  > = [];
  let lastIndex = 0;

  for (const match of content.matchAll(directivePattern)) {
    const start = match.index ?? 0;
    if (start > lastIndex) {
      segments.push({ type: "text", text: content.slice(lastIndex, start) });
    }

    try {
      const directive = JSON.parse(match[1]) as DisplayDirective;
      if (Array.isArray(directive.items) && directive.items.length > 0) {
        segments.push({ type: "display", directive });
      }
    } catch {
      // Ignore malformed directives and keep rendering the text.
    }

    lastIndex = start + match[0].length;
  }

  if (lastIndex < content.length) {
    segments.push({ type: "text", text: content.slice(lastIndex) });
  }

  return segments;
}

function stripDisplayDirectives(content: string) {
  return content.replace(directivePattern, "");
}

Do not auto-render every result

Maps and cards should render when the assistant emits a directive or when your own product intentionally adds a deterministic surface. Do not append every structured result to the bottom of the message. That creates duplicated maps and makes mixed route plus place answers hard to read.

Result Registry

As structured result events arrive, register every renderable item by ID. Later content chunks can reference those IDs in display directives.

Renderable item sources

FieldTypeDescription
vehicle-searchdata.listings[]Vehicle listing cards. Items use listing_N IDs.
google-maps-searchdata.results[]Place cards and map markers. Items use place_N IDs.
gas-price-lookupdata.stations[]Gas station cards and map markers. Items use station_N IDs.
ev-charger-lookupdata.stations[]EV charger cards and map markers. Items use station_N IDs.
ev-charger-advanceddata.stations[]EV charger cards with availability details. Items use station_N IDs.
repair-bookingdata.shops[]Repair shop cards when public rollout is enabled. Items use shop_N IDs.
traffic.nearby_incidentsdata.incidents[]Traffic incident markers. Items use incident_N IDs.
traffic.route_trafficdata.route_idRoute polyline item for route_traffic responses. Register the full data object by route_id.
fleet-routingdata.route_idOptimized route polyline and stop markers. Register the full data object by route_id.
telemetry.routesdata.routes[]Trip route map items. Items use telemetry_route_* IDs.
telemetry.map_itemdata.map_itemCurrent or parked vehicle location map item.
Registering rich result items
const arrayKeys: Record<string, string> = {
  "vehicle-search": "listings",
  "google-maps-search": "results",
  "gas-price-lookup": "stations",
  "ev-charger-lookup": "stations",
  "ev-charger-advanced": "stations",
  "repair-booking": "shops",
  "traffic": "incidents",
  "telemetry": "routes",
};

const registry = new Map<string, RichResultItem>();

function registerToolResult(toolSlug: string, data: Record<string, unknown>) {
  const mapItem = data.map_item as Record<string, unknown> | undefined;
  if (mapItem && typeof mapItem._id === "string") {
    registry.set(mapItem._id, { toolSlug, data: mapItem });
  }

  if (
    typeof data.route_id === "string" &&
    Array.isArray(data.route_points) &&
    data.route_points.length > 0
  ) {
    registry.set(data.route_id, { toolSlug, data });
  }

  const arrayKey = arrayKeys[toolSlug];
  const items = arrayKey ? data[arrayKey] : undefined;
  if (!Array.isArray(items)) return;

  for (const item of items) {
    if (item && typeof item === "object" && typeof item._id === "string") {
      registry.set(item._id, { toolSlug, data: item as Record<string, unknown> });
    }
  }
}

Complete Message Flow

This is the shape a frontend can build against. The route and the destination place arrive as structured result data. The assistant then places two directives, so the UI can render a route map and a place card in order.

SSE flow with a place and a traffic route
data: {"type":"session","session_id":"550e8400-e29b-41d4-a716-446655440000","is_new":true}

data: {"type":"tool_result","id":"tc_1","results":[{"tool":"google-maps-search","status":"success","data":{"results":[{"_id":"place_0","name":"Valvoline Instant Oil Change","place_id":"ChIJ123","address":"123 Camelback Rd, Phoenix, AZ","latitude":33.5092,"longitude":-112.0148,"phone":"(602) 555-0100","rating":"4.6 stars (218 reviews)","open_now":true,"website":"https://example.com","types":["car_repair","point_of_interest","establishment"],"fuel_prices":null,"ev_charging":null}]}}]}

data: {"type":"tool_result","id":"tc_2","results":[{"tool":"traffic","status":"success","data":{"route_id":"route_f82a1c","origin":"Phoenix, AZ","destination":"Valvoline Instant Oil Change","origin_lat":33.4484,"origin_lng":-112.074,"destination_lat":33.5092,"destination_lng":-112.0148,"travel_time_minutes":14,"travel_time_no_traffic_minutes":12,"delay_minutes":2,"distance_km":11.4,"distance_miles":7.1,"steps_summary":["Take N 7th St (3.2 mi)","Turn right on E Camelback Rd (0.4 mi)"],"route_points":[{"lat":33.4484,"lng":-112.074},{"lat":33.4691,"lng":-112.0651},{"lat":33.5092,"lng":-112.0148}],"google_maps_url":"https://www.google.com/maps/dir/?api=1&origin=Phoenix%2C%20AZ&destination=Valvoline"}}]}

data: {"type":"content","content":"Valvoline is the closest strong option. Traffic adds about 2 minutes right now.\n\n<!--galadri:display {\"items\":[\"route_f82a1c\"],\"layout\":\"map\"}-->\n\n<!--galadri:display {\"items\":[\"place_0\"],\"layout\":\"cards\"}-->"}

data: {"type":"usage","prompt_tokens":1420,"completion_tokens":220,"thinking_tokens":0,"cost_micro_usd":391000,"cost_cents":39}

data: [DONE]

Multiple maps are valid

One assistant message can contain more than one display directive. For example, a route map and a separate destination place card are both valid. Render directives in message order and do not suppress a later map just because an earlier map already rendered.

Rendering Order

Recommended order for each assistant message:

  1. Register every successful structured result event into your result registry.
  2. Concatenate content chunks into the assistant message string.
  3. Parse the message into text and display directive segments.
  4. Render text segments as normal assistant copy.
  5. For each directive segment, resolve item IDs from the registry and render the requested layout.
  6. Render data mutation cards from data_saved events if your chat UI supports them.
  7. Ignore unknown event types and missing item IDs without failing the message.

Layout recommendations

FieldTypeDescription
cardslayoutRender cards only. Use for vehicle listings, places, stations, shops, and mutation confirmations.
maplayoutRender map only. Use for routes, trip polylines, vehicle location, or location-heavy answers.
cards+maplayoutRender a map plus corresponding cards. Keep marker labels and card order aligned.
Rendering a directive segment
function resolveDirective(directive: DisplayDirective) {
  return directive.items
    .map((id) => registry.get(id))
    .filter((item): item is RichResultItem => Boolean(item));
}

function renderDirective(directive: DisplayDirective) {
  const items = resolveDirective(directive);
  if (items.length === 0) return null;

  switch (directive.layout) {
    case "map":
      return <ResultMap items={items} />;
    case "cards+map":
      return (
        <>
          <ResultMap items={items} />
          <ResultCards items={items} />
        </>
      );
    case "cards":
    default:
      return <ResultCards items={items} />;
  }
}

Result Schemas

These are the renderable fields your UI should expect. Treat extra fields as additive and ignore unknown fields for forward compatibility.

Inventory searches return data.listings[]. When a search inspects a larger server-side inventory pool, data.inventory_pool_rows reports the number of provider listings considered before shortlisting. To continue beyond that pool, send another search with start set to the previous start plus inventory_pool_rows and keep the same pooled-search intent.

FieldTypeDescription
_idstringStable ID, for example "listing_0".
candidate_indexintegerZero-based index within the returned shortlist.
listing_idstring | nullProvider listing identifier when available.
yearintegerModel year.
makestringVehicle make.
modelstringVehicle model.
trimstring | nullTrim level when available.
body_typestring | nullBody style such as SUV, Sedan, Coupe, or Minivan.
price_usdnumber | nullListing price.
milesnumber | nullOdometer reading.
exterior_colorstring | nullExterior color when available.
interior_colorstring | nullInterior color when available.
drivetrainstring | nullDrivetrain such as AWD, 4WD, RWD, or FWD.
transmissionstring | nullTransmission when available.
fuel_typestring | nullFuel or powertrain type when available.
enginestring | nullEngine description when available.
doorsnumber | nullDoor count when available.
seatingnumber | nullStandard seating capacity when available.
car_typestring | nullInventory type such as new, used, or certified.
is_certifiedbooleanWhether the listing is certified pre-owned.
days_on_marketnumber | nullDays the active listing has been on market.
vehicle_statusstring | nullInventory lifecycle status when provided.
photo_urlstring | nullPrimary photo URL.
listing_urlstring | nullDealer listing URL.
dealer_idstring | nullMarketCheck legacy dealer ID when available.
mc_dealer_idstring | nullMarketCheck dealer ID for exact dealer inventory filters.
mc_location_idstring | nullMarketCheck location ID when available.
mc_rooftop_idstring | nullMarketCheck rooftop ID when available.
mc_website_idstring | nullMarketCheck website ID when available.
dealer_namestring | nullDealer name.
dealer_citystring | nullDealer city.
dealer_statestring | nullDealer state.
dealer_zipstring | nullDealer ZIP/postal code when available.

Dealer lookup searches return data.dealers[] so the agent can choose a dealership identifier before requesting exact inventory.

FieldTypeDescription
_idstringStable ID, for example "dealer_0".
seller_namestring | nullDealership display name.
inventory_urlstring | nullDealer inventory or website domain.
statusstring | nullMarketCheck dealer status.
dealer_typestring | nullDealer type, such as franchise or independent.
mc_dealer_idstring | nullMarketCheck dealer ID for inventory filtering.
mc_location_idstring | nullMarketCheck location ID for inventory filtering.
mc_rooftop_idstring | nullMarketCheck rooftop ID for inventory filtering.
mc_website_idstring | nullMarketCheck website ID for inventory filtering.
streetstring | nullStreet address.
citystring | nullCity.
statestring | nullState or province.
zipstring | nullZIP/postal code.
phonestring | nullDealer phone number.
name_match_scorenumber | nullServer-side name match score when a dealer_query was provided.

google-maps-search

Returns data.results[].

FieldTypeDescription
_idstringStable ID, for example "place_0".
namestringPlace name.
place_idstring | nullGoogle place ID when available.
addressstring | nullFormatted address.
latitudenumber | nullLatitude.
longitudenumber | nullLongitude.
phonestring | nullNational phone number.
ratingstring | nullFormatted rating string, for example "4.6 stars (218 reviews)".
open_nowboolean | nullCurrent open status when Google returns it.
websitestring | nullWebsite URL.
typesstring[]Up to three Google place types.
fuel_prices{ type: string; price: string }[] | nullFuel prices when Google returns fuel data for the place.
ev_charging{ connectors: number; types: string[] } | nullEV connector summary when Google returns charger data.
Google Maps result item
{
  "_id": "place_0",
  "name": "Valvoline Instant Oil Change",
  "place_id": "ChIJ123",
  "address": "123 Camelback Rd, Phoenix, AZ",
  "latitude": 33.5092,
  "longitude": -112.0148,
  "phone": "(602) 555-0100",
  "rating": "4.6 stars (218 reviews)",
  "open_now": true,
  "website": "https://example.com",
  "types": ["car_repair", "point_of_interest", "establishment"],
  "fuel_prices": null,
  "ev_charging": null
}

gas-price-lookup

Returns data.stations[] plus cheapest available prices by fuel type.

FieldTypeDescription
_idstringStable ID, for example "station_0".
brandstring | nullGas station brand.
namestring | nullStation name.
addressstring | nullStation address when available.
latitudenumber | nullLatitude.
longitudenumber | nullLongitude.
distance_milesnumber | nullDistance from search location.
prices{ fuel_type: string; price_per_gallon: number }[]Fuel prices returned by the provider.
cheapest_by_fuel_type{ fuel_type: string; station_name: string; price_per_gallon: number }[]Cheapest returned station for each fuel grade with an available price.
cheapest_requested_fuelobject | nullCheapest result for the requested fuel grade when the call filters by fuel type.

ev-charger-lookup and ev-charger-advanced

Returns data.stations[].

FieldTypeDescription
_idstringStable ID, for example "station_0".
namestringStation name.
addressstring | nullStation address.
networkstring | nullCharging network.
latitudenumber | nullLatitude.
longitudenumber | nullLongitude.
distance_milesnumber | nullDistance from search location.
portsobject | nullBasic port counts, such as dc_fast and level_2.
available_pointsinteger | nullCurrently available charging points in advanced results.
total_charging_pointsinteger | nullTotal charging points in advanced results.
connectorsarray | nullConnector details such as type and max_power_kw.

traffic

Nearby incidents return data.incidents[]. Route traffic returns a top-level route object registered by data.route_id.

Incident fields

FieldTypeDescription
_idstringStable ID, for example "incident_0".
categorystring | nullIncident type.
descriptionstring | nullIncident description.
severityinteger | nullSeverity where 1 is minor and 4 is severe.
latitudenumber | nullLatitude.
longitudenumber | nullLongitude.
delay_secondsnumber | nullEstimated delay.
fromstring | nullRoad or location name.

Route fields

FieldTypeDescription
route_idstringStable route ID used in map display directives.
originstringOrigin label.
destinationstringDestination label.
origin_latnumberOrigin latitude.
origin_lngnumberOrigin longitude.
destination_latnumberDestination latitude.
destination_lngnumberDestination longitude.
travel_time_minutesnumberTravel time with current traffic.
travel_time_no_traffic_minutesnumberTravel time without current traffic.
delay_minutesnumberTraffic delay in minutes.
distance_kmnumberRoute distance in kilometers.
distance_milesnumberRoute distance in miles.
steps_summarystring[] | undefinedShort turn-by-turn step summary.
waypoints{ lat: number; lng: number }[] | undefinedSampled intermediate points for lightweight previews.
route_points{ lat: number; lng: number }[]Polyline points for map rendering.
google_maps_urlstring | undefinedExternal Google Maps directions link when available.

fleet-routing optimize_route

Returns a route object registered by data.route_id.

FieldTypeDescription
route_idstringStable route ID used in map display directives.
route_points{ lat: number; lng: number }[]Polyline points for map rendering.
optimized_orderarrayStops in optimized visit order. Render as numbered markers.
originstringRoute origin label.
destinationstringRoute destination label.
travel_time_minutesnumberGoogle-rendered route travel time.
distance_kmnumberGoogle-rendered route distance in kilometers.
distance_milesnumberGoogle-rendered route distance in miles.
optimization_travel_time_minutesnumber | nullTomTom optimization summary travel time.
optimization_distance_kmnumber | nullTomTom optimization distance in kilometers.
optimization_distance_milesnumber | nullTomTom optimization distance in miles.
fix_startbooleanWhether the first waypoint was fixed as the start.
round_tripbooleanWhether the route returns to the start.
live_trafficbooleanWhether live traffic was used.
google_maps_urlstring | undefinedExternal navigation link when URL limits allow it.

telemetry maps

Vehicle location can return data.map_item. Trip history with routes can return data.routes[].

Telemetry map item fields

FieldTypeDescription
_idstringStable item ID for display directives.
labelstringMarker label.
latitudenumberLatitude.
longitudenumberLongitude.
subtitlestring | undefinedSecondary marker copy when available.
timestamp_utcstring | undefinedTelemetry timestamp in UTC.

Telemetry route fields

FieldTypeDescription
_idstringSame value as route_id.
route_idstringStable route ID for display directives.
route_points{ lat: number; lng: number }[]Trip polyline points.
originstringTrip start label.
destinationstringTrip end label.
origin_latnumberStart latitude.
origin_lngnumberStart longitude.
destination_latnumberEnd latitude.
destination_lngnumberEnd longitude.
routeMarkersarrayRoute marker details for start, end, and notable events.
trip_mapobjectTrip metadata including source trip ID, local times, point count, and marker count.

data_saved mutation cards

Data mutations stream separately from structured result events.

FieldTypeDescription
type"data_saved"Event type.
mutationsManageDataMutation[]One or more data records changed by the agent.
mutations[].action"create" | "update" | "delete"Mutation action.
mutations[].tablestringData table, such as vehicles, documents, milestones, or schedules.
mutations[].recordobjectSaved record after the mutation.
mutations[].previousRecordobject | undefinedPrevious record for updates or deletes when available.
mutations[].changedFieldsstring[] | undefinedFields changed by the mutation.
Data mutation event
{
  "type": "data_saved",
  "mutations": [
    {
      "action": "update",
      "table": "vehicles",
      "record": {
        "id": "106fec6e-f2db-49e7-938f-4a61b4d90a7c",
        "make": "Ford",
        "model": "F-150",
        "nickname": "Work truck"
      },
      "previousRecord": {
        "id": "106fec6e-f2db-49e7-938f-4a61b4d90a7c",
        "make": "Ford",
        "model": "F-150",
        "nickname": null
      },
      "changedFields": ["nickname"]
    }
  ]
}

Edge Cases

FieldTypeDescription
Missing IDrenderingSkip that item and continue rendering the rest of the message.
Malformed directiverenderingTreat it as invisible and render surrounding text.
Unknown layoutrenderingFall back to cards if the item type supports cards, otherwise skip.
Unknown tool slugrenderingKeep the raw data available for debugging, but do not show raw JSON to end users.
Text-only clientrenderingStrip display directives and render assistant text only.
Duplicate item IDrenderingUse the latest received result for that ID. This matches Galadri's current registry behavior.

See also: Chat API streaming and Tools, Skills, and Hooks