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, orroute_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_savedevents describe records created, updated, or deleted during the turn.
SSE event fields
| Field | Type | Description |
|---|---|---|
type | "tool_result" | Event type for structured result data. |
id | string | Internal event correlation ID. Treat as opaque. |
results | ToolResult[] | One or more capability results produced during the turn. |
results[].tool | string | Tool 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[].data | object | Full renderable payload when status is success. |
results[].error | string | Error message when status is error. |
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.
<!--galadri:display {"items":["station_0","station_1"],"layout":"cards+map"}-->Directive fields
| Field | Type | Description |
|---|---|---|
items | string[] | 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. |
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?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
| Field | Type | Description |
|---|---|---|
vehicle-search | data.listings[] | Vehicle listing cards. Items use listing_N IDs. |
google-maps-search | data.results[] | Place cards and map markers. Items use place_N IDs. |
gas-price-lookup | data.stations[] | Gas station cards and map markers. Items use station_N IDs. |
ev-charger-lookup | data.stations[] | EV charger cards and map markers. Items use station_N IDs. |
ev-charger-advanced | data.stations[] | EV charger cards with availability details. Items use station_N IDs. |
repair-booking | data.shops[] | Repair shop cards when public rollout is enabled. Items use shop_N IDs. |
traffic.nearby_incidents | data.incidents[] | Traffic incident markers. Items use incident_N IDs. |
traffic.route_traffic | data.route_id | Route polyline item for route_traffic responses. Register the full data object by route_id. |
fleet-routing | data.route_id | Optimized route polyline and stop markers. Register the full data object by route_id. |
telemetry.routes | data.routes[] | Trip route map items. Items use telemetry_route_* IDs. |
telemetry.map_item | data.map_item | Current or parked vehicle location map item. |
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.
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:
- Register every successful structured result event into your result registry.
- Concatenate content chunks into the assistant message string.
- Parse the message into text and display directive segments.
- Render text segments as normal assistant copy.
- For each directive segment, resolve item IDs from the registry and render the requested layout.
- Render data mutation cards from data_saved events if your chat UI supports them.
- Ignore unknown event types and missing item IDs without failing the message.
Layout recommendations
| Field | Type | Description |
|---|---|---|
cards | layout | Render cards only. Use for vehicle listings, places, stations, shops, and mutation confirmations. |
map | layout | Render map only. Use for routes, trip polylines, vehicle location, or location-heavy answers. |
cards+map | layout | Render a map plus corresponding cards. Keep marker labels and card order aligned. |
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.
vehicle-search
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.
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID, for example "listing_0". |
candidate_index | integer | Zero-based index within the returned shortlist. |
listing_id | string | null | Provider listing identifier when available. |
year | integer | Model year. |
make | string | Vehicle make. |
model | string | Vehicle model. |
trim | string | null | Trim level when available. |
body_type | string | null | Body style such as SUV, Sedan, Coupe, or Minivan. |
price_usd | number | null | Listing price. |
miles | number | null | Odometer reading. |
exterior_color | string | null | Exterior color when available. |
interior_color | string | null | Interior color when available. |
drivetrain | string | null | Drivetrain such as AWD, 4WD, RWD, or FWD. |
transmission | string | null | Transmission when available. |
fuel_type | string | null | Fuel or powertrain type when available. |
engine | string | null | Engine description when available. |
doors | number | null | Door count when available. |
seating | number | null | Standard seating capacity when available. |
car_type | string | null | Inventory type such as new, used, or certified. |
is_certified | boolean | Whether the listing is certified pre-owned. |
days_on_market | number | null | Days the active listing has been on market. |
vehicle_status | string | null | Inventory lifecycle status when provided. |
photo_url | string | null | Primary photo URL. |
listing_url | string | null | Dealer listing URL. |
dealer_id | string | null | MarketCheck legacy dealer ID when available. |
mc_dealer_id | string | null | MarketCheck dealer ID for exact dealer inventory filters. |
mc_location_id | string | null | MarketCheck location ID when available. |
mc_rooftop_id | string | null | MarketCheck rooftop ID when available. |
mc_website_id | string | null | MarketCheck website ID when available. |
dealer_name | string | null | Dealer name. |
dealer_city | string | null | Dealer city. |
dealer_state | string | null | Dealer state. |
dealer_zip | string | null | Dealer ZIP/postal code when available. |
Dealer lookup searches return data.dealers[] so the agent can choose a dealership identifier before requesting exact inventory.
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID, for example "dealer_0". |
seller_name | string | null | Dealership display name. |
inventory_url | string | null | Dealer inventory or website domain. |
status | string | null | MarketCheck dealer status. |
dealer_type | string | null | Dealer type, such as franchise or independent. |
mc_dealer_id | string | null | MarketCheck dealer ID for inventory filtering. |
mc_location_id | string | null | MarketCheck location ID for inventory filtering. |
mc_rooftop_id | string | null | MarketCheck rooftop ID for inventory filtering. |
mc_website_id | string | null | MarketCheck website ID for inventory filtering. |
street | string | null | Street address. |
city | string | null | City. |
state | string | null | State or province. |
zip | string | null | ZIP/postal code. |
phone | string | null | Dealer phone number. |
name_match_score | number | null | Server-side name match score when a dealer_query was provided. |
google-maps-search
Returns data.results[].
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID, for example "place_0". |
name | string | Place name. |
place_id | string | null | Google place ID when available. |
address | string | null | Formatted address. |
latitude | number | null | Latitude. |
longitude | number | null | Longitude. |
phone | string | null | National phone number. |
rating | string | null | Formatted rating string, for example "4.6 stars (218 reviews)". |
open_now | boolean | null | Current open status when Google returns it. |
website | string | null | Website URL. |
types | string[] | Up to three Google place types. |
fuel_prices | { type: string; price: string }[] | null | Fuel prices when Google returns fuel data for the place. |
ev_charging | { connectors: number; types: string[] } | null | EV connector summary when Google returns charger data. |
{
"_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.
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID, for example "station_0". |
brand | string | null | Gas station brand. |
name | string | null | Station name. |
address | string | null | Station address when available. |
latitude | number | null | Latitude. |
longitude | number | null | Longitude. |
distance_miles | number | null | Distance 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_fuel | object | null | Cheapest result for the requested fuel grade when the call filters by fuel type. |
ev-charger-lookup and ev-charger-advanced
Returns data.stations[].
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID, for example "station_0". |
name | string | Station name. |
address | string | null | Station address. |
network | string | null | Charging network. |
latitude | number | null | Latitude. |
longitude | number | null | Longitude. |
distance_miles | number | null | Distance from search location. |
ports | object | null | Basic port counts, such as dc_fast and level_2. |
available_points | integer | null | Currently available charging points in advanced results. |
total_charging_points | integer | null | Total charging points in advanced results. |
connectors | array | null | Connector 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
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID, for example "incident_0". |
category | string | null | Incident type. |
description | string | null | Incident description. |
severity | integer | null | Severity where 1 is minor and 4 is severe. |
latitude | number | null | Latitude. |
longitude | number | null | Longitude. |
delay_seconds | number | null | Estimated delay. |
from | string | null | Road or location name. |
Route fields
| Field | Type | Description |
|---|---|---|
route_id | string | Stable route ID used in map display directives. |
origin | string | Origin label. |
destination | string | Destination label. |
origin_lat | number | Origin latitude. |
origin_lng | number | Origin longitude. |
destination_lat | number | Destination latitude. |
destination_lng | number | Destination longitude. |
travel_time_minutes | number | Travel time with current traffic. |
travel_time_no_traffic_minutes | number | Travel time without current traffic. |
delay_minutes | number | Traffic delay in minutes. |
distance_km | number | Route distance in kilometers. |
distance_miles | number | Route distance in miles. |
steps_summary | string[] | undefined | Short turn-by-turn step summary. |
waypoints | { lat: number; lng: number }[] | undefined | Sampled intermediate points for lightweight previews. |
route_points | { lat: number; lng: number }[] | Polyline points for map rendering. |
google_maps_url | string | undefined | External Google Maps directions link when available. |
fleet-routing optimize_route
Returns a route object registered by data.route_id.
| Field | Type | Description |
|---|---|---|
route_id | string | Stable route ID used in map display directives. |
route_points | { lat: number; lng: number }[] | Polyline points for map rendering. |
optimized_order | array | Stops in optimized visit order. Render as numbered markers. |
origin | string | Route origin label. |
destination | string | Route destination label. |
travel_time_minutes | number | Google-rendered route travel time. |
distance_km | number | Google-rendered route distance in kilometers. |
distance_miles | number | Google-rendered route distance in miles. |
optimization_travel_time_minutes | number | null | TomTom optimization summary travel time. |
optimization_distance_km | number | null | TomTom optimization distance in kilometers. |
optimization_distance_miles | number | null | TomTom optimization distance in miles. |
fix_start | boolean | Whether the first waypoint was fixed as the start. |
round_trip | boolean | Whether the route returns to the start. |
live_traffic | boolean | Whether live traffic was used. |
google_maps_url | string | undefined | External 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
| Field | Type | Description |
|---|---|---|
_id | string | Stable item ID for display directives. |
label | string | Marker label. |
latitude | number | Latitude. |
longitude | number | Longitude. |
subtitle | string | undefined | Secondary marker copy when available. |
timestamp_utc | string | undefined | Telemetry timestamp in UTC. |
Telemetry route fields
| Field | Type | Description |
|---|---|---|
_id | string | Same value as route_id. |
route_id | string | Stable route ID for display directives. |
route_points | { lat: number; lng: number }[] | Trip polyline points. |
origin | string | Trip start label. |
destination | string | Trip end label. |
origin_lat | number | Start latitude. |
origin_lng | number | Start longitude. |
destination_lat | number | End latitude. |
destination_lng | number | End longitude. |
routeMarkers | array | Route marker details for start, end, and notable events. |
trip_map | object | Trip metadata including source trip ID, local times, point count, and marker count. |
data_saved mutation cards
Data mutations stream separately from structured result events.
| Field | Type | Description |
|---|---|---|
type | "data_saved" | Event type. |
mutations | ManageDataMutation[] | One or more data records changed by the agent. |
mutations[].action | "create" | "update" | "delete" | Mutation action. |
mutations[].table | string | Data table, such as vehicles, documents, milestones, or schedules. |
mutations[].record | object | Saved record after the mutation. |
mutations[].previousRecord | object | undefined | Previous record for updates or deletes when available. |
mutations[].changedFields | string[] | undefined | Fields changed by the mutation. |
{
"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
| Field | Type | Description |
|---|---|---|
Missing ID | rendering | Skip that item and continue rendering the rest of the message. |
Malformed directive | rendering | Treat it as invisible and render surrounding text. |
Unknown layout | rendering | Fall back to cards if the item type supports cards, otherwise skip. |
Unknown tool slug | rendering | Keep the raw data available for debugging, but do not show raw JSON to end users. |
Text-only client | rendering | Strip display directives and render assistant text only. |
Duplicate item ID | rendering | Use the latest received result for that ID. This matches Galadri's current registry behavior. |
See also: Chat API streaming and Tools, Skills, and Hooks