@tomtom-org/maps-sdk-plugin-agent-toolkit 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","names":[],"sources":["../src/resolve-tools.ts","../src/state.ts","../src/system-prompt.ts","../src/tools/utilities/calculate-bbox.ts","../src/tools/utilities/format-distance.ts","../src/tools/utilities/format-duration.ts","../src/tools/shared-output-schemas.ts","../src/tools/utilities/get-current-location.ts","../src/tools/utilities/get-route-progress.ts","../src/tools/utilities/get-section-bbox.ts","../src/tools/utilities/get-section-progress.ts","../src/tools/tool-tags.ts","../src/tools/utilities/help.ts","../src/tool-setup.ts","../src/tools/maplibre/execute-maplibre-code.ts","../src/tools/shared/location-input.ts","../src/tools/shared/resolve-poi-categories.ts","../src/tools/shared/schema.ts","../src/tools/shared/show-places-on-map.ts","../src/tools/shared/viewport-bias.ts","../src/tools/maplibre/fly-to.ts","../src/tools/maplibre/get-map-style-layers.ts","../src/tools/maplibre/get-viewport.ts","../src/tools/maplibre/set-layout-properties.ts","../src/tools/maplibre/set-paint-properties.ts","../src/tools/maplibre/set-pitch-bearing.ts","../src/tools/maplibre/zoom-in-or-out.ts","../src/utils/intent-classifier.ts","../src/utils/state-labels.ts","../src/utils/summarize.ts","../src/tools/services/locate-place.ts","../src/tools/services/resolve-location-input.ts","../src/tools/services/set-route-locations.ts","../src/tools/services/add-stop-to-route.ts","../src/tools/services/discover-places.ts","../src/tools/services/find-reachable-area.ts","../src/tools/services/geocode.ts","../src/tools/services/get-poi-category-codes.ts","../src/tools/services/get-traffic-area-analytics.ts","../src/tools/services/get-traffic-incidents.ts","../src/tools/services/query-traffic-analytics.ts","../src/tools/services/remove-stop-from-route.ts","../src/tools/services/reverse-geocode.ts","../src/tools/services/search-along-route.ts","../src/tools/services/set-route-parameters.ts","../src/tools/state/get-current-waypoints.ts","../src/tools/state/recall-places.ts","../src/tools/state/recall-ranges.ts","../src/tools/state/recall-routes.ts","../src/tools/tomtom-map/clear-map.ts","../src/tools/tomtom-map/fit-route-section.ts","../src/tools/tomtom-map/get-shown-incidents.ts","../src/tools/tomtom-map/get-shown-places.ts","../src/tools/tomtom-map/get-shown-route-sections.ts","../src/tools/tomtom-map/get-shown-route-traffic-incidents.ts","../src/tools/tomtom-map/get-shown-routes.ts","../src/tools/tomtom-map/get-shown-waypoints.ts","../src/tools/tomtom-map/get-standard-map-styles.ts","../src/tools/tomtom-map/manage-places.ts","../src/tools/tomtom-map/set-language.ts","../src/tools/tomtom-map/set-map-standard-style.ts","../src/tools/tomtom-map/set-route-theme.ts","../src/tools/tomtom-map/show-places.ts","../src/tools/tomtom-map/show-routes.ts","../src/tools/tomtom-map/show-traffic-area-analytics.ts","../src/tools/tomtom-map/show-waypoints.ts","../src/tools/tomtom-map/toggle-base-map-layer-groups.ts","../src/tools/tomtom-map/toggle-pois.ts","../src/tools/tomtom-map/toggle-traffic.ts","../src/tools/tool-registry.ts","../src/create-map-agent.ts"],"sourcesContent":["import type { ToolEntry, ToolNameHint, ToolState } from './types';\n\n/**\n * Merges default tools with user overrides.\n *\n * - `ToolEntry` values add or replace tools in `defaults`.\n * - `false` values remove tools from `defaults`.\n * - Returns a new object — `defaults` is never mutated.\n *\n * @param defaults - Base tool record (typically DEFAULT_TOOLS or empty).\n * @param overrides - User-provided additions, replacements, and exclusions.\n * @returns Resolved tool record ready for setupTools.\n *\n * @group Agent Toolkit\n */\nexport const resolveTools = <S extends ToolState>(\n defaults: Record<string, ToolEntry<S>>,\n overrides?: { [K in ToolNameHint]?: ToolEntry<S> | false | undefined },\n): Record<string, ToolEntry<S>> => {\n const result = { ...defaults };\n\n if (!overrides) return result;\n\n for (const [name, entry] of Object.entries(overrides)) {\n if (entry) {\n result[name] = entry;\n } else {\n delete result[name];\n }\n }\n\n return result;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { Place, Places, Routes, TrafficAreaAnalytics, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport {\n BaseMapModule,\n GeometriesModule,\n type GeometryTheme,\n HillshadeModule,\n PlacesModule,\n PlanningWaypoint,\n POIsModule,\n RoutingModule,\n reachableRangeGeometryConfig,\n type TomTomMap,\n type TrafficAreaAnalyticsConfig,\n TrafficAreaAnalyticsModule,\n TrafficFlowModule,\n TrafficIncidentsModule,\n} from '@tomtom-org/maps-sdk/map';\nimport { Map } from 'maplibre-gl';\nimport type { ToolState } from './types';\nimport type { PlacesEntry, RangeEntry, RouteParams, RoutesEntry, StateSlice } from './types/state';\n\nTomTomConfig.instance.put({ language: 'en-GB' });\n\n/**\n * State for place search, geocoding, and geometries.\n *\n * Holds lazy-initialized PlacesModule and GeometriesModule alongside an\n * append-only history of place and search results produced during the session.\n *\n * @group Agent Toolkit\n */\nexport class PlacesState implements StateSlice {\n private _placesModule?: PlacesModule;\n\n private _entries: PlacesEntry[] = [];\n\n /** IDs of entries currently rendered on the map. */\n private _shownEntryIds: Set<PlacesEntry['id']> = new Set();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getters (lazy initialization)\n\n async getPlacesModule(): Promise<PlacesModule> {\n this._placesModule ??= await PlacesModule.get(this._ttMap);\n return this._placesModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get placesModule(): PlacesModule | undefined {\n return this._placesModule;\n }\n\n // History\n\n get entries(): readonly PlacesEntry[] {\n return this._entries;\n }\n\n /**\n * This is used purely to pass data between tools in a context-dependent way,\n * so we assume the most recent entry is the relevant one to show on the map.\n */\n get latestPlace(): Place[] | undefined {\n return this._entries.at(-1)?.data;\n }\n\n addPlaceResult(place: Place | Places, label: string): string {\n const id = `places-${this._entries.length}`;\n this._entries.push({\n id,\n timestamp: Date.now(),\n label,\n data: 'features' in place ? place.features : [place],\n });\n return id;\n }\n\n /** IDs of entries currently shown on the map. */\n get shownEntryIds(): ReadonlySet<PlacesEntry['id']> {\n return this._shownEntryIds;\n }\n\n /** Return the union of all features belonging to currently-shown entries. */\n getShownFeatures(): Place[] {\n const out: Place[] = [];\n for (const entry of this._entries) {\n if (this._shownEntryIds.has(entry.id)) {\n out.push(...entry.data);\n }\n }\n return out;\n }\n\n /** Replace the shown set with exactly these ids and re-render. */\n async setShownEntries(ids: readonly PlacesEntry['id'][]): Promise<Place[]> {\n const entryIds = new Set(this._entries.map((entry) => entry.id));\n this._shownEntryIds = new Set(ids.filter((id) => entryIds.has(id)));\n return this._renderShown();\n }\n\n /** Add ids to the shown set and re-render. */\n async addShownEntries(ids: readonly PlacesEntry['id'][]): Promise<Place[]> {\n const entryIds = new Set(this._entries.map((entry) => entry.id));\n let changed = false;\n for (const id of ids) {\n if (entryIds.has(id) && !this._shownEntryIds.has(id)) {\n this._shownEntryIds.add(id);\n changed = true;\n }\n }\n return changed ? this._renderShown() : this.getShownFeatures();\n }\n\n /** Remove ids from the shown set and re-render. */\n async removeShownEntries(ids: readonly PlacesEntry['id'][]): Promise<Place[]> {\n let changed = false;\n for (const id of ids) {\n if (this._shownEntryIds.delete(id)) changed = true;\n }\n return changed ? this._renderShown() : this.getShownFeatures();\n }\n\n /** Clear everything from the map (does not touch entries history). */\n async clearShownEntries(): Promise<void> {\n if (this._shownEntryIds.size === 0) return;\n this._shownEntryIds.clear();\n const placesModule = await this.getPlacesModule();\n await placesModule.clear();\n }\n\n private async _renderShown(): Promise<Place[]> {\n const features = this.getShownFeatures();\n const placesModule = await this.getPlacesModule();\n await placesModule.show({ type: 'FeatureCollection', features } as Places);\n return features;\n }\n\n reset(): void {\n this._placesModule = undefined;\n this._entries = [];\n this._shownEntryIds.clear();\n }\n}\n\n/**\n * State for the map POI layer (points of interest overlay).\n *\n * Holds the lazy-initialized POIsModule for showing, hiding, and filtering\n * the built-in POI categories rendered on the base map.\n *\n * @group Agent Toolkit\n */\nexport class MapPOIsState implements StateSlice {\n private _poisModule?: POIsModule;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getter (lazy initialization)\n\n async getPOIsModule(): Promise<POIsModule> {\n this._poisModule ??= await POIsModule.get(this._ttMap);\n return this._poisModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get poisModule(): POIsModule | undefined {\n return this._poisModule;\n }\n\n reset(): void {\n this._poisModule = undefined;\n }\n}\n\n/**\n * State for route calculation, waypoint management, and route planning parameters.\n *\n * Holds the lazy-initialized RoutingModule alongside an append-only history of\n * calculated routes, sparse planning slots (being assembled before a calculation),\n * and current route parameters.\n *\n * @group Agent Toolkit\n */\nexport class RoutingState implements StateSlice {\n private _routingModule?: RoutingModule;\n private _entries: RoutesEntry[] = [];\n private _planningSlots: PlanningWaypoint[] = [];\n private _params: RouteParams = {};\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n async getRoutingModule(): Promise<RoutingModule> {\n this._routingModule ??= await RoutingModule.get(this._ttMap);\n return this._routingModule;\n }\n\n get routingModule(): RoutingModule | undefined {\n return this._routingModule;\n }\n\n // History\n\n get entries(): readonly RoutesEntry[] {\n return this._entries;\n }\n\n get currentRoutes(): Routes | undefined {\n return this._entries.at(-1)?.data;\n }\n\n get currentWaypoints(): WaypointLike[] | undefined {\n return this._entries.at(-1)?.waypoints;\n }\n\n /** Sparse nullable slots being assembled before triggering a route calculation. */\n get planningSlots(): PlanningWaypoint[] {\n return this._planningSlots;\n }\n\n get params(): RouteParams {\n return this._params;\n }\n\n addRoutes(routes: Routes, waypoints: WaypointLike[], label: string): void {\n this._entries.push({\n id: `routes-${this._entries.length}`,\n timestamp: Date.now(),\n label,\n data: routes,\n waypoints,\n params: { ...this._params },\n });\n this._planningSlots = [...waypoints];\n }\n\n setWaypointAt(index: number, waypoint: WaypointLike): void {\n const minSize = Math.max(2, index + 1);\n while (this._planningSlots.length < minSize) {\n this._planningSlots.push(null);\n }\n this._planningSlots[index] = waypoint;\n }\n\n setParams(params: Partial<RouteParams>): void {\n this._params = { ...this._params, ...params };\n }\n\n reset(): void {\n this._routingModule = undefined;\n this._entries = [];\n this._planningSlots = [];\n this._params = {};\n }\n}\n\n/**\n * State for base map display: style, language, viewport, layers, and hillshade.\n *\n * Provides direct access to the TomTomMap and MapLibre instances alongside\n * lazy-initialized BaseMapModule and HillshadeModule.\n *\n * @group Agent Toolkit\n */\nexport class BaseMapState implements StateSlice {\n private _baseMapModule?: BaseMapModule;\n private _hillshadeModule?: HillshadeModule;\n\n constructor(public readonly ttMap: TomTomMap) {}\n\n get mapLibreMap(): Map {\n return this.ttMap.mapLibreMap;\n }\n\n // Module getters (lazy initialization)\n\n async getBaseMapModule(): Promise<BaseMapModule> {\n this._baseMapModule ??= await BaseMapModule.get(this.ttMap);\n return this._baseMapModule;\n }\n\n async getHillshadeModule(): Promise<HillshadeModule> {\n this._hillshadeModule ??= await HillshadeModule.get(this.ttMap);\n return this._hillshadeModule;\n }\n\n reset(): void {\n this._baseMapModule = undefined;\n this._hillshadeModule = undefined;\n }\n}\n\n/**\n * State for traffic: flow layer, incident overlay, and area analytics modules.\n *\n * @group Agent Toolkit\n */\nexport class TrafficState implements StateSlice {\n private _trafficFlowModule?: TrafficFlowModule;\n private _trafficIncidentsModule?: TrafficIncidentsModule;\n private _trafficAreaAnalyticsModule?: TrafficAreaAnalyticsModule;\n private _lastAreaAnalytics?: TrafficAreaAnalytics;\n private _currentAnalyticsConfig?: TrafficAreaAnalyticsConfig;\n private _configChangeUnsub?: () => void;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getters (lazy initialization)\n\n async getTrafficFlowModule(): Promise<TrafficFlowModule> {\n this._trafficFlowModule ??= await TrafficFlowModule.get(this._ttMap);\n return this._trafficFlowModule;\n }\n\n async getTrafficIncidentsModule(): Promise<TrafficIncidentsModule> {\n this._trafficIncidentsModule ??= await TrafficIncidentsModule.get(this._ttMap);\n return this._trafficIncidentsModule;\n }\n\n async getTrafficAreaAnalyticsModule(): Promise<TrafficAreaAnalyticsModule> {\n if (!this._trafficAreaAnalyticsModule) {\n this._trafficAreaAnalyticsModule = await TrafficAreaAnalyticsModule.get(this._ttMap);\n // Wire config change events so agent state stays in sync with module\n this._configChangeUnsub = this._trafficAreaAnalyticsModule.events.on('config-change', (config) => {\n this._currentAnalyticsConfig = config;\n });\n }\n return this._trafficAreaAnalyticsModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get trafficAreaAnalyticsModule(): TrafficAreaAnalyticsModule | undefined {\n return this._trafficAreaAnalyticsModule;\n }\n\n // Area analytics result caching\n\n get lastAreaAnalytics(): TrafficAreaAnalytics | undefined {\n return this._lastAreaAnalytics;\n }\n\n setLastAreaAnalytics(result: TrafficAreaAnalytics): void {\n this._lastAreaAnalytics = result;\n }\n\n /** Current visualization config, updated by module configChange events. */\n get currentAnalyticsConfig(): TrafficAreaAnalyticsConfig | undefined {\n return this._currentAnalyticsConfig;\n }\n\n // Optional hooks for the host application to show/hide its own analytics UI.\n // The plugin does not manage any UI panel — wire these up in the consuming app.\n onAnalyticsShown?: (analytics: TrafficAreaAnalytics, module: TrafficAreaAnalyticsModule) => void;\n onAnalyticsCleared?: () => void;\n\n reset(): void {\n this._configChangeUnsub?.();\n this.onAnalyticsCleared?.();\n this._trafficFlowModule = undefined;\n this._trafficIncidentsModule = undefined;\n this._trafficAreaAnalyticsModule = undefined;\n this._lastAreaAnalytics = undefined;\n this._currentAnalyticsConfig = undefined;\n this._configChangeUnsub = undefined;\n }\n}\n\n/**\n * State for reachable range results.\n *\n * Stores semantic summaries of calculated ranges (origin, budgets).\n * Also retains raw polygon geometry internally for use in geometry searches\n * (e.g. discoverPlaces withinRange). Displayed on the map via GeometriesModule.\n *\n * @group Agent Toolkit\n */\nexport class RangeState implements StateSlice {\n private _entries: RangeEntry[] = [];\n private _placesModule?: PlacesModule;\n private _geometriesModule?: GeometriesModule;\n private _currentTheme?: GeometryTheme;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n async getPlacesModule(): Promise<PlacesModule> {\n this._placesModule ??= await PlacesModule.get(this._ttMap);\n return this._placesModule;\n }\n\n async getGeometriesModule(theme: GeometryTheme): Promise<GeometriesModule> {\n if (!this._geometriesModule) {\n this._geometriesModule = await GeometriesModule.get(\n this._ttMap,\n reachableRangeGeometryConfig(undefined, theme),\n );\n this._currentTheme = theme;\n } else if (this._currentTheme !== theme) {\n this._geometriesModule.applyConfig(reachableRangeGeometryConfig(undefined, theme));\n this._currentTheme = theme;\n }\n return this._geometriesModule;\n }\n\n get geometriesModule(): GeometriesModule | undefined {\n return this._geometriesModule;\n }\n\n get entries(): readonly RangeEntry[] {\n return this._entries;\n }\n\n get latestEntry(): RangeEntry | undefined {\n return this._entries.at(-1);\n }\n\n addEntry(entry: Omit<RangeEntry, 'id' | 'timestamp'>): string {\n const id = `ranges-${this._entries.length}`;\n this._entries.push({\n id,\n timestamp: Date.now(),\n ...entry,\n });\n return id;\n }\n\n reset(): void {\n this._entries = [];\n this._geometriesModule = undefined;\n this._currentTheme = undefined;\n }\n}\n\n/**\n * Factory that creates a fully initialized ToolState from a TomTomMap instance.\n * Optionally merges custom state slices alongside built-in state.\n *\n * @param map - TomTomMap instance for module initialization\n * @param customSlices - Additional state slices accessible by custom tools\n *\n * @group Agent Toolkit\n */\nexport const createToolState = <T extends Record<string, unknown> = Record<string, never>>(\n map: TomTomMap,\n customSlices?: T,\n): ToolState & T => {\n const base: ToolState = {\n places: new PlacesState(map),\n mapPOIs: new MapPOIsState(map),\n routing: new RoutingState(map),\n baseMap: new BaseMapState(map),\n traffic: new TrafficState(map),\n ranges: new RangeState(map),\n };\n if (customSlices) {\n Object.assign(base, customSlices);\n }\n return base as ToolState & T;\n};\n","/**\n * @module agent-toolkit-system-prompt\n */\n\n/**\n * The base system prompt that teaches the LLM how to use the agent toolkit tools.\n *\n * @remarks\n * Export this constant so consumers can reference or extend it when providing\n * a custom system prompt via `MapAgentOptions.systemPrompt`.\n *\n * @example\n * ```typescript\n * import { createMapAgent, BASE_SYSTEM_PROMPT } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n *\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * systemPrompt: BASE_SYSTEM_PROMPT + '\\n\\nAlways respond in Spanish.'\n * });\n * ```\n *\n * @group Agent Toolkit\n */\nexport const BASE_SYSTEM_PROMPT = `You are a helpful map assistant with access to a TomTom interactive map and location services.\n\nCOORDINATE ORDER:\n- All coordinates are [longitude, latitude] (GeoJSON standard)\n- All bounding boxes are [minLng, minLat, maxLng, maxLat]\n\nTOOL EXECUTION:\n- Call context-independent tools in the same step (parallel execution).\n- Many service tools have a show or showOnMap parameter — use it to display results on the map in one step instead of calling a separate show tool afterward.\n- Use standalone showPlaces / showRoute / showWaypoints only when displaying results already in context from a previous step.\n\nLOCATION REFERENCE:\n- \"near me\" / \"my location\" / \"where I am\" → getCurrentLocation (user's physical GPS position, may prompt permission)\n- \"in this area\" / \"here on the map\" / \"near the map center\" → getViewport (map's current view)\n- If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point\n\nRESPONSE FORMATTING:\n- Use markdown in all responses\n- Show key information in bold\n- Use bullet lists when presenting multiple items or results\n- Keep responses concise — lead with the most relevant information\n\n`;\n\n/**\n * Builds the complete system prompt for the agent toolkit.\n *\n * @param customPrompt - Optional complete prompt that replaces the base prompt\n * @param suffix - Optional additional prompt text to append to base prompt (ignored if customPrompt provided)\n * @returns Complete system prompt string\n *\n * @internal\n * @ignore\n */\nexport const buildSystemPrompt = (customPrompt?: string, suffix?: string): string => {\n if (customPrompt) {\n return customPrompt;\n }\n if (suffix) {\n return `${BASE_SYSTEM_PROMPT}\\n\\nADDITIONAL INSTRUCTIONS:\\n${suffix}`;\n }\n return BASE_SYSTEM_PROMPT;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for calculating a bounding box from GeoJSON.\n */\nexport const calculateBBoxSchema = z.object({\n geoJson: z\n .union([z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))])\n .describe('GeoJSON object or array of GeoJSON objects'),\n});\n\nexport const calculateBBoxDescription =\n 'Calculate a [minLng, minLat, maxLng, maxLat] bbox from GeoJSON. Context-independent. ' +\n 'Returns bounding box as [minLng, minLat, maxLng, maxLat] — longitude before latitude.';\n\n/** Execute function for calculateBBox — usable with ToolEntry format. */\nexport const executeCalculateBBox = async (params: z.infer<typeof calculateBBoxSchema>, _state: ToolState) => {\n const { geoJson } = params;\n try {\n const bbox = bboxFromGeoJSON(geoJson as any);\n\n if (!bbox) {\n return { error: 'Could not calculate bounding box - input may be empty or invalid GeoJSON' };\n }\n\n return {\n bbox,\n minLng: bbox[0],\n minLat: bbox[1],\n maxLng: bbox[2],\n maxLat: bbox[3],\n };\n } catch (error) {\n return {\n error: `Failed to calculate bbox: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { formatDistance } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for formatting distance.\n */\nexport const formatDistanceSchema = z.object({\n meters: z.number().describe('Distance in meters to format'),\n unitType: z.enum(['metric', 'imperial_us', 'imperial_uk']).optional().describe('default: metric'),\n});\n\nexport const formatDistanceDescription =\n 'Format a distance in meters into a human-readable string (e.g. \"2.5 km\", \"1.5 mi\"). ' +\n 'Use after getSectionProgress, recallRoutes, or any tool that returns distances in meters.';\n\n/** Execute function for formatDistance — usable with ToolEntry format. */\nexport const executeFormatDistance = async (params: z.infer<typeof formatDistanceSchema>, _state: ToolState) => {\n const { meters, unitType } = params;\n try {\n const formatted = formatDistance(meters, unitType ? { type: unitType } : undefined);\n return {\n formatted,\n input: {\n meters,\n unitType: unitType ?? 'metric',\n },\n };\n } catch (error) {\n return {\n error: `Failed to format distance: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { formatDuration } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for formatting duration.\n */\nexport const formatDurationSchema = z.object({\n seconds: z.number().describe('Duration in seconds to format'),\n});\n\nexport const formatDurationDescription =\n 'Format a duration in seconds into a human-readable time string (e.g. \"1 h 30 min\"). ' +\n 'Use after getSectionProgress, recallRoutes, or any tool that returns durations in seconds.';\n\n/** Execute function for formatDuration — usable with ToolEntry format. */\nexport const executeFormatDuration = async (params: z.infer<typeof formatDurationSchema>, _state: ToolState) => {\n const { seconds } = params;\n try {\n const formatted = formatDuration(seconds);\n\n if (formatted === undefined) {\n return {\n formatted: undefined,\n message: '< 1 min',\n input: { seconds },\n };\n }\n\n return {\n formatted,\n input: { seconds },\n };\n } catch (error) {\n return {\n error: `Failed to format duration: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared output schema building blocks reused across multiple tools.\n * Tool-specific output schemas live next to their respective tool definitions.\n */\n\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// Common\n// ---------------------------------------------------------------------------\n\n/** Returned by every tool when execution fails. */\nexport const toolErrorSchema = z.object({\n error: z.string(),\n});\n\n// ---------------------------------------------------------------------------\n// Place (compact summary) — used by geocode, reverse-geocode, search, etc.\n// ---------------------------------------------------------------------------\n\n/** Token-efficient summary of a single place. */\nexport const placeOutputSchema = z\n .object({\n name: z.string().optional().describe('POI name if available'),\n address: z.string().optional().describe('Human-readable address'),\n position: z.array(z.number()).length(2).describe('[longitude, latitude]'),\n })\n .describe('Compact place summary');\n\n/** Token-efficient summary of a places collection. */\nexport const placesOutputSchema = z\n .object({\n count: z.number(),\n features: z.array(placeOutputSchema),\n })\n .describe('Compact places summary');\n\n// ---------------------------------------------------------------------------\n// Route (summarized — geometry, progress, and guidance stripped)\n// ---------------------------------------------------------------------------\n\nconst summarySchema = z.object({\n travelTimeInSeconds: z.number(),\n lengthInMeters: z.number(),\n trafficDelayInSeconds: z.number().optional(),\n trafficLengthInMeters: z.number().optional(),\n departureTime: z.string().optional(),\n arrivalTime: z.string().optional(),\n noTrafficTravelTimeInSeconds: z.number().optional(),\n historicTrafficTravelTimeInSeconds: z.number().optional(),\n liveTrafficIncidentsTravelTimeInSeconds: z.number().optional(),\n});\n\n/**\n * A single summarized route — just the route summary and optional per-leg summaries.\n */\nexport const summarizedRouteSchema = z.object({\n index: z.number().describe('0=best, 1+=alternative'),\n summary: summarySchema,\n legSummaries: z.array(summarySchema).optional().describe('Present when route has multiple legs'),\n});\n\n/** Summary of one or more calculated routes (coordinates stripped). */\nexport const routesOutputSchema = z.object({\n count: z.number(),\n routes: z.array(summarizedRouteSchema),\n});\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Tool schema for retrieving browser current location.\n */\nexport const getCurrentLocationSchema = z.object({});\n\n/**\n * Output schema for current location tool.\n */\nexport const getCurrentLocationOutputSchema = z.union([\n z.object({\n position: z.array(z.number()).min(2).max(2).describe('[longitude, latitude]'),\n accuracy: z.number().describe('Accuracy of the location in meters'),\n timestamp: z.number().describe('Timestamp of the location data'),\n }),\n toolErrorSchema,\n]);\n\nconst getLocationErrorMessage = (error: unknown): string | undefined => {\n if (typeof error !== 'object' || error === null || !('code' in error)) {\n return undefined;\n }\n\n const code = (error as { code: number }).code;\n\n if (code === 1) {\n return 'Location permission denied by the user';\n }\n\n if (code === 2) {\n return 'Current location is unavailable';\n }\n\n if (code === 3) {\n return 'Timed out while trying to get current location';\n }\n\n return undefined;\n};\n\nexport const getCurrentLocationDescription =\n \"Get the user's physical location from the browser geolocation API. \" +\n 'Use for queries referencing the user\\'s body: \"near me\", \"where I am\", \"my location\", \"nearby\". ' +\n 'May prompt the user for location permission. ' +\n 'Does NOT return the map viewport center — use getViewport for map-based reference. ' +\n 'Returns: position [longitude, latitude].';\n\n/** Execute function for getCurrentLocation — usable with ToolEntry format. */\nexport const executeGetCurrentLocation = async (\n _params: z.infer<typeof getCurrentLocationSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getCurrentLocationOutputSchema>> => {\n if (globalThis.window === undefined || globalThis.navigator === undefined) {\n return { error: 'Current location is only available in a browser environment' };\n }\n\n if (!('geolocation' in globalThis.navigator)) {\n return { error: 'Geolocation is not supported by this browser' };\n }\n\n try {\n const position = await new Promise<GeolocationPosition>((resolve, reject) => {\n globalThis.navigator.geolocation.getCurrentPosition(resolve, reject, {\n enableHighAccuracy: true,\n timeout: 15000,\n maximumAge: 0,\n });\n });\n\n return {\n position: [position.coords.longitude, position.coords.latitude],\n accuracy: position.coords.accuracy,\n timestamp: position.timestamp,\n };\n } catch (error) {\n const locationErrorMessage = getLocationErrorMessage(error);\n if (locationErrorMessage) {\n return { error: locationErrorMessage };\n }\n\n return {\n error: `Failed to get current location: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n getCoordinateAtRouteProgress,\n type RouteCoordinateAtProgress,\n type RouteProgressQuery,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\nconst routeIndexField = z.number().int().min(0).optional().describe('default: 0');\n\n/**\n * Tool schema for getting a coordinate position along a route.\n * The three progress inputs are mutually exclusive — provide exactly one.\n */\nexport const getRouteProgressSchema = z\n .object({\n routeIndex: routeIndexField,\n traveledTimeInSeconds: z.number().min(0).optional().describe('seconds from start'),\n traveledDistanceInMeters: z.number().min(0).optional().describe('meters from start'),\n clockTime: z.string().optional().describe('ISO 8601; elapsed time derived from departure time'),\n })\n .refine(\n ({ traveledTimeInSeconds, traveledDistanceInMeters, clockTime }) =>\n [traveledTimeInSeconds, traveledDistanceInMeters, clockTime].filter((v) => v !== undefined).length === 1,\n { message: 'Provide exactly one of: traveledTimeInSeconds, traveledDistanceInMeters, or clockTime.' },\n );\n\n/**\n * Maps the tool params to a {@link RouteProgressQuery} for the core utility.\n * Returns an error string when the clockTime string is invalid.\n */\nconst toRouteProgressQuery = (\n params: z.infer<typeof getRouteProgressSchema>,\n): RouteProgressQuery | { error: string } => {\n if (params.traveledTimeInSeconds !== undefined) {\n return { traveledTimeInSeconds: params.traveledTimeInSeconds };\n }\n\n if (params.traveledDistanceInMeters !== undefined) {\n return { traveledDistanceInMeters: params.traveledDistanceInMeters };\n }\n\n const clockTimeMs = new Date(params.clockTime as string).getTime();\n if (Number.isNaN(clockTimeMs)) {\n return { error: `Invalid clockTime value: \"${params.clockTime}\". Use ISO 8601 format.` };\n }\n\n return { clockTime: new Date(params.clockTime as string) };\n};\n\nexport const getRouteProgressDescription =\n 'Return the geographic coordinates of the point along the last shown route at a given traveled time (seconds), traveled distance (meters), or absolute clock time (ISO 8601). Use for route progress tracking (e.g. route-point-progress-playground example).';\n\n/** Execute function for getRouteProgress — usable with ToolEntry format. */\nexport const executeGetRouteProgress = async (params: z.infer<typeof getRouteProgressSchema>, state: ToolState) => {\n const routeIndex = params.routeIndex ?? 0;\n\n try {\n const lastRoutes = (await state.routing.getRoutingModule()).getShown().mainLines;\n if (!lastRoutes || lastRoutes.features.length === 0) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n\n const route = lastRoutes.features[routeIndex];\n if (!route) {\n return {\n error: `Route index ${routeIndex} is out of range. There are ${lastRoutes.features.length} route(s) available.`,\n };\n }\n\n if (!route.properties.progress || route.properties.progress.length === 0) {\n return {\n error: 'The route does not contain progress data. Re-calculate the route to ensure progress data is included.',\n };\n }\n\n const query = toRouteProgressQuery(params);\n if ('error' in query) return query;\n\n const result: RouteCoordinateAtProgress | undefined = getCoordinateAtRouteProgress(route, query);\n\n if (result === undefined) {\n return {\n error: 'Could not interpolate a position. The progress data may be incomplete or the requested value is out of range.',\n };\n }\n\n return {\n longitude: result.position[0],\n latitude: result.position[1],\n travelTimeInSeconds: Math.round(result.travelTimeInSeconds),\n distanceInMeters: Math.round(result.distanceInMeters),\n };\n } catch (error) {\n return {\n error: `Failed to get route progress: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { getSectionBBox, type SectionProps, type SectionType, sectionTypes } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-section-bbox tool. */\nexport const getSectionBBoxOutputSchema = z.union([\n z.object({\n bbox: z.array(z.number()).describe('GeoJSON bbox [minLng, minLat, maxLng, maxLat]'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the get-section-bbox tool. */\nexport const getSectionBBoxSchema = z.object({\n sectionType: z.enum(sectionTypes as [SectionType, ...SectionType[]]).describe('e.g. country|traffic|motorway|toll'),\n id: z.string().describe('Section ID from getShownRouteSections or getSectionProgress'),\n});\n\nexport const getSectionBBoxDescription =\n 'Calculate the bounding box [minLng, minLat, maxLng, maxLat] for a specific section of the shown route. Use with flyTo to frame the section, or to query traffic incidents within it.';\n\n/** Execute function for getSectionBBox — usable with ToolEntry format. */\nexport const executeGetSectionBBox = async (\n params: z.infer<typeof getSectionBBoxSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getSectionBBoxOutputSchema>> => {\n const { sectionType, id } = params;\n try {\n const lastRoutes = state.routing.currentRoutes;\n\n if (!lastRoutes || lastRoutes.features.length === 0) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n\n for (const route of lastRoutes.features) {\n const sections = route.properties.sections[sectionType as SectionType];\n if (!sections || !Array.isArray(sections)) continue;\n\n const section = sections.find((s) => s.id === id);\n if (section && typeof section.startPointIndex === 'number' && typeof section.endPointIndex === 'number') {\n const bbox = getSectionBBox(route, section as SectionProps);\n\n if (!bbox) {\n return {\n error: 'Could not calculate bounding box for the section. The section may have invalid indices.',\n };\n }\n\n return { bbox };\n }\n }\n\n return {\n error: `Could not find a section with type \"${sectionType}\" and id \"${id}\" in the last calculated routes.`,\n };\n } catch (error) {\n return {\n error: `Failed to get section bbox: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n getRouteProgressForSection,\n type SectionProps,\n type SectionType,\n sectionTypes,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for getting section progress.\n */\nexport const getSectionProgressSchema = z.object({\n sectionType: z.enum(sectionTypes as [SectionType, ...SectionType[]]).describe('e.g. country|traffic|motorway|toll'),\n ids: z\n .array(z.string())\n .optional()\n .describe('Section IDs to query. If omitted, all sections of the given sectionType are returned.'),\n});\n\nexport const getSectionProgressDescription =\n 'Calculate the distance and travel time for one or more sections of the shown route. If ids is omitted, all sections of the given type are returned.';\n\n/** Execute function for getSectionProgress — usable with ToolEntry format. */\nexport const executeGetSectionProgress = async (params: z.infer<typeof getSectionProgressSchema>, state: ToolState) => {\n const { sectionType, ids } = params;\n\n try {\n const lastRoutes = state.routing.currentRoutes;\n\n if (!lastRoutes || lastRoutes.features.length === 0) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n\n const results: object[] = [];\n\n for (const route of lastRoutes.features) {\n const sections = route.properties.sections[sectionType as SectionType];\n if (!sections) continue;\n\n for (const section of sections) {\n if (ids && ids.length > 0 && !ids.includes(section.id)) continue;\n\n const progress = getRouteProgressForSection(route, section as SectionProps);\n\n if (!progress) {\n results.push({\n sectionId: section.id,\n error: 'Could not calculate progress for this section. The route may be missing progress data.',\n });\n continue;\n }\n\n results.push({\n sectionId: section.id,\n startPointIndex: section.startPointIndex,\n endPointIndex: section.endPointIndex,\n start: progress.start,\n end: progress.end,\n delta: progress.delta,\n });\n }\n }\n\n if (ids && ids.length > 0) {\n const foundIds = new Set(results.map((r) => (r as { sectionId: string }).sectionId));\n for (const id of ids) {\n if (!foundIds.has(id)) {\n results.push({\n sectionId: id,\n error: `Could not find a section with type \"${sectionType}\" and id \"${id}\" in the last calculated routes.`,\n });\n }\n }\n }\n\n if (results.length === 0) {\n return { error: `No sections of type \"${sectionType}\" found in the last calculated routes.` };\n }\n\n return { sectionType, sections: results };\n } catch (error) {\n return {\n error: `Failed to get section progress: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\n/**\n * All valid tool tags for categorizing tools.\n *\n * @ignore\n */\nexport const TOOL_TAGS = [\n 'location',\n 'route',\n 'waypoint',\n 'place',\n 'locate',\n 'discover',\n 'traffic',\n 'map style',\n 'utilities',\n 'search',\n 'detour',\n 'along-route',\n 'reachable-range',\n 'isochrone',\n 'range',\n 'EV',\n 'coverage',\n 'recall',\n 'state',\n // Route section types (from SDK sectionTypes)\n 'carpool',\n 'carTrain',\n 'country',\n 'ferry',\n 'importantRoadStretch',\n 'lanes',\n 'leg',\n 'lowEmissionZone',\n 'motorway',\n 'pedestrian',\n 'roadShields',\n 'speedLimit',\n 'toll',\n 'tollVignette',\n 'tunnel',\n 'unpaved',\n 'urban',\n 'vehicleRestricted',\n] as const;\n\n/**\n * Union of all valid tool tags, derived from {@link TOOL_TAGS}.\n *\n * @ignore\n */\nexport type ToolTag = (typeof TOOL_TAGS)[number];\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Tool, tool } from 'ai';\nimport { z } from 'zod';\nimport type { ToolMetadata } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { TOOL_TAGS, type ToolTag } from '../tool-tags';\n\nconst detailToolSchema = z.object({\n name: z.string(),\n description: z.string(),\n examplePrompts: z.array(z.string()),\n relatedTools: z.array(z.string()).optional(),\n});\n\nexport const helpOutputSchema = z.union([\n z.object({\n mode: z.literal('summary'),\n examplePrompts: z.array(z.string()),\n }),\n z.object({\n mode: z.literal('detail'),\n query: z.string().optional(),\n tag: z.enum(TOOL_TAGS).optional(),\n matchedTools: z.array(detailToolSchema),\n totalMatched: z.number(),\n }),\n toolErrorSchema,\n]);\n\nexport const helpSchema = z.object({\n mode: z\n .enum(['summary', 'detail'])\n .describe(\n '\"summary\": overview of all capabilities with example prompts mixed from all tools. ' +\n '\"detail\": searchable full tool info — use when you need to find a specific tool.',\n ),\n query: z\n .string()\n .optional()\n .describe('Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags.'),\n tag: z.enum(TOOL_TAGS).optional().describe('Detail mode only. Filter results to tools with a specific tag.'),\n});\n\nexport const helpDescription =\n 'Discover what this agent can do. Returns all capability example prompts mixed together (summary mode) ' +\n 'or searchable per-tool details with descriptions and examples (detail mode). ' +\n 'Call this when the user asks what you can do, or when you need to find the right tool for a task.';\n\n/**\n * Create the help tool.\n *\n * @param getToolsMetadata - Lazy getter for resolved tool metadata.\n * Called at execution time, not at creation time, so metadata can be\n * populated after all tools are set up.\n */\nexport const createHelpTool = (getToolsMetadata: () => Record<string, ToolMetadata>): Tool => {\n return tool({\n description: helpDescription,\n inputSchema: helpSchema,\n outputSchema: helpOutputSchema,\n execute: async (params): Promise<z.infer<typeof helpOutputSchema>> => {\n const { mode, query, tag } = params;\n\n try {\n const allTools = Object.values(getToolsMetadata());\n\n if (mode === 'summary') {\n const examplePrompts = allTools.flatMap((toolMetadata) => toolMetadata.examplePrompts ?? []);\n return { mode: 'summary', examplePrompts };\n }\n\n // detail mode\n let tools = allTools;\n\n if (tag) {\n tools = tools.filter(\n (toolMetadata) => (toolMetadata.tags as readonly ToolTag[] | undefined)?.includes(tag) ?? false,\n );\n }\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n tools = tools.filter(\n (toolMetadata) =>\n toolMetadata.name.toLowerCase().includes(lowerQuery) ||\n toolMetadata.description.toLowerCase().includes(lowerQuery) ||\n (toolMetadata.tags ?? []).some((toolTag) => toolTag.toLowerCase().includes(lowerQuery)) ||\n (toolMetadata.examplePrompts ?? []).some((prompt) =>\n prompt.toLowerCase().includes(lowerQuery),\n ),\n );\n }\n\n return {\n mode: 'detail',\n query,\n tag,\n matchedTools: tools.map((toolMetadata) => ({\n name: toolMetadata.name,\n description: toolMetadata.description,\n examplePrompts: toolMetadata.examplePrompts ?? [],\n relatedTools: toolMetadata.relatedTools ?? [],\n })),\n totalMatched: tools.length,\n };\n } catch (error) {\n return {\n error: `Failed to load help: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n },\n });\n};\n","import type { Tool } from 'ai';\nimport { createHelpTool } from './tools/utilities';\nimport type { ToolEntry, ToolMetadata, ToolState } from './types';\n\n/** Converts a ToolEntry to an AI SDK Tool, binding state to execute. */\nconst toAiTool = <S extends ToolState>(entry: ToolEntry<S>, state: S, includeOutputSchema: boolean): Tool =>\n ({\n description: entry.description,\n inputSchema: entry.inputSchema,\n outputSchema: includeOutputSchema ? entry.outputSchema : undefined,\n execute: (input: any) => entry.execute(input, state),\n }) as Tool;\n\n/** Extracts ToolMetadata from a ToolEntry, adding the name. */\nconst toMetadata = <S extends ToolState>(name: string, entry: ToolEntry<S>): ToolMetadata => ({\n name,\n description: entry.description,\n classificationPrompt: entry.classificationPrompt,\n tags: entry.tags,\n examples: entry.examples,\n examplePrompts: entry.examplePrompts,\n relatedTools: entry.relatedTools,\n dependsOn: entry.dependsOn,\n});\n\n/**\n * Converts a composed tool record into AI SDK tools and metadata.\n * Rebinds the help tool with resolved metadata.\n *\n * @ignore\n */\nexport const setupTools = <S extends ToolState>(\n toolEntries: Record<string, ToolEntry<S>>,\n state: S,\n options?: { outputSchemas?: boolean },\n): { tools: Record<string, Tool>; toolsMetadata: Record<string, ToolMetadata> } => {\n const includeOutputSchema = options?.outputSchemas !== false;\n const tools: Record<string, Tool> = {};\n const toolsMetadata: Record<string, ToolMetadata> = {};\n\n for (const [name, entry] of Object.entries(toolEntries)) {\n tools[name] = toAiTool(entry, state, includeOutputSchema);\n toolsMetadata[name] = toMetadata(name, entry);\n }\n\n // Rebind help tool with resolved metadata\n if (tools.help) {\n tools.help = createHelpTool(() => toolsMetadata);\n }\n\n if (Object.keys(tools).length === 0) {\n throw new Error('MapAgent requires at least one tool.');\n }\n\n return { tools, toolsMetadata };\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst sourceDiffSchema = z.object({\n added: z.array(z.string()).describe('Source IDs added by the executed code.'),\n removed: z.array(z.string()).describe('Source IDs removed by the executed code.'),\n updated: z.array(z.string()).describe('Source IDs whose definition changed.'),\n});\n\nconst layerDiffSchema = z.object({\n added: z.array(z.string()).describe('Layer IDs added by the executed code.'),\n removed: z.array(z.string()).describe('Layer IDs removed by the executed code.'),\n updated: z.array(z.string()).describe('Layer IDs whose properties changed.'),\n});\n\n/** Input schema for the executeMaplibreCode tool. */\nexport const executeMaplibreCodeSchema = z.object({\n code: z\n .string()\n .describe(\n 'Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. ' +\n 'Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. ' +\n 'Example: await a map event with `await new Promise(r => map.once(\"idle\", r))`. ' +\n 'Return a value with `return` to include it in the result.',\n ),\n});\n\n/** Output schema for the executeMaplibreCode tool. */\nexport const executeMaplibreCodeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n result: z.unknown().optional().describe('Return value of the executed code, if any.'),\n sources: sourceDiffSchema,\n layers: layerDiffSchema,\n }),\n toolErrorSchema,\n]);\n\nexport const executeMaplibreCodeDescription =\n 'Execute any MapLibre JS code for maximum flexibility. ' +\n '`map` is the live MapLibre Map instance — the full API is available. ' +\n 'Use for anything: custom sources and layers, camera control, style mutations, event-driven sequences, animations, or any operation not covered by other tools. ' +\n 'Full JS is supported: loops, conditionals, helper functions, top-level `await`, event listeners. ' +\n 'Returns which sources and layers were added, removed, or updated.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype Snapshot = Map<string, string>;\n\nconst snapshotSources = (mapLibreMap: MapLibreMap): Snapshot => {\n const sources = mapLibreMap.getStyle()?.sources ?? {};\n const snapshot: Snapshot = new Map();\n\n for (const [sourceId, source] of Object.entries(sources)) {\n snapshot.set(sourceId, JSON.stringify(source));\n }\n\n return snapshot;\n};\n\nconst snapshotLayers = (mapLibreMap: MapLibreMap): Snapshot => {\n const layers = mapLibreMap.getStyle()?.layers ?? [];\n const snapshot: Snapshot = new Map();\n\n for (const layer of layers) {\n snapshot.set(layer.id, JSON.stringify(layer));\n }\n\n return snapshot;\n};\n\nconst diffSnapshots = (\n before: Snapshot,\n after: Snapshot,\n): { added: string[]; removed: string[]; updated: string[] } => {\n const added: string[] = [];\n const removed: string[] = [];\n const updated: string[] = [];\n\n for (const [identifier, afterJson] of after) {\n const beforeJson = before.get(identifier);\n\n if (beforeJson === undefined) {\n added.push(identifier);\n } else if (beforeJson !== afterJson) {\n updated.push(identifier);\n }\n }\n\n for (const identifier of before.keys()) {\n if (!after.has(identifier)) {\n removed.push(identifier);\n }\n }\n\n return { added, removed, updated };\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/** Execute function for executeMaplibreCode — usable with ToolEntry format. */\nexport const executeExecuteMaplibreCode = async (\n params: z.infer<typeof executeMaplibreCodeSchema>,\n state: ToolState,\n) => {\n const { code } = params;\n try {\n const mapLibreMap = state.baseMap.mapLibreMap;\n const beforeSources = snapshotSources(mapLibreMap);\n const beforeLayers = snapshotLayers(mapLibreMap);\n\n // eslint-disable-next-line no-new-func\n const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new (\n ...args: string[]\n ) => (map: MapLibreMap) => Promise<unknown>;\n const userFunction = new AsyncFunction('map', code);\n const result = await userFunction(mapLibreMap);\n\n const sources = diffSnapshots(beforeSources, snapshotSources(mapLibreMap));\n const layers = diffSnapshots(beforeLayers, snapshotLayers(mapLibreMap));\n\n return { success: true, result: result ?? undefined, sources, layers };\n } catch (error) {\n return {\n error: `Code execution failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\n\n/** @ignore */\nexport type ResolvedLocation = {\n /** Resolved place: a Place (with address metadata) or a bare [lng, lat] tuple. */\n place: WaypointLike;\n position: Position;\n name: string;\n /** Original query string, when the input was resolved from text. */\n query?: string;\n};\n\n/** @ignore */\nexport const locationInputSchema = z.union([\n z.object({\n query: z.string().describe('Location string to resolve.'),\n locationType: z.enum(['poi', 'default']).describe('\"poi\" for landmarks and venues; \"default\" for addresses.'),\n }),\n z.object({\n position: z\n .object({ lng: z.number(), lat: z.number() })\n .describe('Explicit coordinates — use when you already have a position.'),\n }),\n]);\n\n/** @ignore */\nexport type LocationInput = z.infer<typeof locationInputSchema>;\n","import { poiCategories as knownPoiCategories, POICategory } from '@tomtom-org/maps-sdk/core';\nimport { getPOICategoryCodes } from '@tomtom-org/maps-sdk/services';\n\nconst knownPoiCategoriesSet = new Set<string>(knownPoiCategories);\n\n/** @ignore */\nexport const resolvePoiCategories = async (poiCategories: string[] | undefined): Promise<POICategory[] | undefined> => {\n if (!poiCategories) {\n return undefined;\n }\n const known = poiCategories.filter((c) => knownPoiCategoriesSet.has(c)) as POICategory[];\n const unrecognized = poiCategories.filter((c) => !knownPoiCategoriesSet.has(c));\n\n let resolvedUnrecognized: POICategory[] = [];\n if (unrecognized.length) {\n resolvedUnrecognized = await getPOICategoryCodes({ filters: unrecognized });\n }\n\n const merged = [...new Set([...known, ...resolvedUnrecognized])];\n return merged.length ? merged : undefined;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared input schema building blocks reused across multiple tools.\n */\n\nimport { z } from 'zod';\n\n/** @ignore */\nexport const geoJsonBBoxSchema = z\n .union([z.array(z.number()).refine((arr) => arr.length === 4, { message: 'BBox must have 4 elements' })])\n .describe('[minLng, minLat, maxLng, maxLat] or [W,S,E,N]');\n\n/** @ignore */\nexport const hasBBoxSchema = z.union([geoJsonBBoxSchema, z.object({ bbox: geoJsonBBoxSchema })]);\n\n/** @ignore */\nexport const showPlacesSchema = z.object({\n markerType: z\n .enum(['pin', 'none'])\n .describe(\n 'Marker type to show on the map. Show a marker only for addresses and small POIs. Use \"none\" towns, cities, or any areas or regions.',\n ),\n zoomMode: z\n .enum(['auto', 'none'])\n .describe('How to zoom the map into the places. Use \"none\" to keep the map still.'),\n mode: z\n .enum(['add', 'replace'])\n .optional()\n .describe(\n 'Display mode. Default: \"replace\" (clears existing pins, shows only these). ' +\n 'Use \"add\" when the user wants to STACK these results on top of what is already shown (\"also show\", \"additionally\", \"and X\").',\n ),\n});\n\n/** @ignore */\nexport const shownSchema = z.object({\n markerType: z.boolean(),\n zoomMode: z.boolean(),\n});\n\n/** @ignore */\nexport const whereSchema = z.union([\n z.object({ position: z.array(z.number()).length(2) }).describe('Bias toward a specific [lng, lat] point.'),\n z.object({ boundingBox: hasBBoxSchema }).describe('Consider only within a BBox [W,S,E,N] or GeoJSON with bbox.'),\n z.literal('within-map-bounds').describe('Consider within the current map viewport bounds.'),\n z.literal('nearby-map-center').describe('Bias toward the current map center.'),\n z.literal('global').describe('No geographic bias — global search.'),\n z\n .string()\n .describe(\n 'Place name or area (e.g. \"Paris\", \"Manhattan\") — geocoded first, then its bbox or position is used as bias.',\n ),\n]);\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type Place, type Places } from '@tomtom-org/maps-sdk/core';\nimport type { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { shownSchema, showPlacesSchema } from './schema';\n\n/** @ignore */\nexport const showResultsOnMap = async (\n state: ToolState,\n result: Place | Places,\n placesEntryId: string,\n show: z.infer<typeof showPlacesSchema>,\n): Promise<z.infer<typeof shownSchema>> => {\n const shown: z.infer<typeof shownSchema> = { markerType: false, zoomMode: false };\n if (show.markerType === 'pin') {\n if (show.mode === 'add') {\n await state.places.addShownEntries([placesEntryId]);\n } else {\n await state.places.setShownEntries([placesEntryId]);\n }\n shown.markerType = true;\n }\n if (show.zoomMode === 'auto') {\n const bbox = bboxFromGeoJSON(result);\n if (bbox) state.baseMap.mapLibreMap.fitBounds(bbox, { padding: 50 });\n shown.zoomMode = true;\n }\n return shown;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Utility for deriving a search bias from the current map viewport.\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport type { BaseMapState } from '../../state';\n\n/** @ignore */\nexport const MIN_VIEWPORT_BIAS_ZOOM = 6;\n\n/** @ignore */\nexport const getViewportBias = (map: BaseMapState, minZoom = MIN_VIEWPORT_BIAS_ZOOM): Position | undefined => {\n const zoom = map.mapLibreMap.getZoom();\n if (zoom < minZoom) return undefined;\n const center = map.mapLibreMap.getCenter();\n return [center.lng, center.lat];\n};\n\n/** @ignore */\nexport const getViewportBoundingBox = (map: BaseMapState): BBox => {\n const bounds = map.mapLibreMap.getBounds();\n return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type HasBBox } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { hasBBoxSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the fly-to tool. */\nexport const flyToOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for fly-to. */\nexport const flyToSchema = z.object({\n where: z.union([\n z\n .object({\n boundingBox: hasBBoxSchema.describe('BBox [W,S,E,N] or GeoJSON object with bbox.'),\n padding: z.number().optional().describe('Padding in pixels. Default: 50.'),\n })\n .describe('Fit the camera to a bounding box.'),\n z\n .object({\n position: z.array(z.number()).length(2).describe('[lng, lat] to fly to.'),\n zoom: z.number().optional().describe('Camera zoom level. Default: 14.'),\n })\n .describe('Fly the camera to a specific position.'),\n ]),\n});\n\nexport const flyToDescription =\n 'Move the map camera to a bounding box or a specific position. ' +\n 'Use with boundingBox to frame a region or set of results. Use with position to center on a point.';\n\n/** Execute function for flyTo — usable with ToolEntry format. */\nexport const executeFlyTo = async (params: z.infer<typeof flyToSchema>, state: ToolState) => {\n const { where } = params;\n try {\n if ('boundingBox' in where) {\n const bbox = bboxFromGeoJSON(where.boundingBox as HasBBox);\n if (!bbox) {\n return { error: 'Invalid boundingBox — could not extract a bounding box.' };\n }\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding: where.padding ?? 50 });\n } else {\n state.baseMap.mapLibreMap.flyTo({\n center: where.position as [number, number],\n zoom: where.zoom ?? 14,\n });\n }\n return { success: true };\n } catch (error) {\n return {\n error: `flyTo failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-map-style-layers tool. */\nexport const getMapStyleLayersOutputSchema = z.union([\n z.object({\n layers: z.array(\n z.object({\n id: z.string(),\n layout: z.unknown().optional(),\n paint: z.unknown().optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting map style layers.\n */\nexport const getMapStyleLayersSchema = z.object({\n layerIdQuery: z\n .string()\n .optional()\n .describe('Case-insensitive partial match on layer IDs. If omitted, all layers are returned.'),\n include: z\n .array(z.enum(['paint', 'layout']))\n .describe(\n 'Which properties to include in the response. Pass only what you need: \"paint\" for visual properties like colors and opacity, \"layout\" for structural properties like sizes, positions, and visibility.',\n ),\n});\n\nexport const getMapStyleLayersDescription =\n 'Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayoutProperty/setPaintProperty for custom styling.';\n\n/** Execute function for getMapStyleLayers — usable with ToolEntry format. */\nexport const executeGetMapStyleLayers = async (params: z.infer<typeof getMapStyleLayersSchema>, state: ToolState) => {\n const { layerIdQuery, include } = params;\n try {\n const style = state.baseMap.mapLibreMap.getStyle();\n let layers = style.layers ?? [];\n\n // Filter by layerIdQuery if provided (case-insensitive partial match)\n if (layerIdQuery) {\n layers = layers.filter((layer) => layer.id.toLowerCase().includes(layerIdQuery.toLowerCase()));\n if (layers.length === 0) {\n return {\n error: `No layers found matching query: \"${layerIdQuery}\"`,\n };\n }\n }\n\n return {\n layers: layers.map((layer) => ({\n id: layer.id,\n ...(include.includes('paint') && { paint: layer.paint }),\n ...(include.includes('layout') && { layout: layer.layout }),\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get style: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-viewport tool. */\nexport const getViewportOutputSchema = z.union([\n z.object({\n center: z.array(z.number()).describe('[lng, lat]'),\n zoom: z.number().describe('0–22'),\n bbox: z.array(z.number()).describe('[W, S, E, N]'),\n pitch: z.number().describe('degrees, 0=top-down, 85=near-horizontal'),\n bearing: z.number().describe('degrees, 0=north, 90=east'),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting viewport.\n */\nexport const getViewportSchema = z.object({});\n\nexport const getViewportDescription =\n 'Get the current map viewport: center coordinates [longitude, latitude], zoom level, and bounding box [minLng, minLat, maxLng, maxLat]. ' +\n 'Use for queries referencing the map view: \"in this area\", \"what I can see\", \"near the map center\". ' +\n 'Does NOT return the user\\'s physical location — use getCurrentLocation for \"near me\" queries. ' +\n 'Reads map state. Does not call any service.';\n\n/** Execute function for getViewport — usable with ToolEntry format. */\nexport const executeGetViewport = async (_params: z.infer<typeof getViewportSchema>, state: ToolState) => {\n try {\n const mapLibreMap = state.baseMap.mapLibreMap;\n return {\n center: mapLibreMap.getCenter().toArray(),\n zoom: mapLibreMap.getZoom(),\n bbox: state.baseMap.ttMap.getBBox(),\n pitch: mapLibreMap.getPitch(),\n bearing: mapLibreMap.getBearing(),\n };\n } catch (error) {\n return {\n error: `Failed to get viewport: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst changeResultSchema = z.union([\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n value: z.unknown(),\n success: z.literal(true),\n }),\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n error: z.string(),\n }),\n]);\n\n/** Output schema for the setLayoutProperties tool. */\nexport const setLayoutPropertiesOutputSchema = z.union([\n z.object({\n results: z.array(changeResultSchema),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the setLayoutProperties tool. */\nexport const setLayoutPropertiesSchema = z\n .object({\n changes: z.array(\n z.object({\n layerId: z.string().describe('MapLibre layer ID from getMapStyleLayers'),\n propertyName: z.string().describe('e.g. visibility|text-field|icon-size'),\n value: z.any(),\n }),\n ),\n })\n .describe('List of layout property changes to apply in one go.');\n\nexport const setLayoutPropertiesDescription =\n 'Set one or more layout properties on MapLibre layers (visibility, text-field, icon-size). ' +\n 'Use getMapStyleLayers to discover valid layer IDs first. ' +\n 'Layout properties control structure and visibility — use setPaintProperties for colors and widths.';\n\n/** Execute function for setLayoutProperties — usable with ToolEntry format. */\nexport const executeSetLayoutProperties = async (\n params: z.infer<typeof setLayoutPropertiesSchema>,\n state: ToolState,\n) => {\n const { changes } = params;\n try {\n const results = changes.map(({ layerId, propertyName, value }) => {\n try {\n state.baseMap.mapLibreMap.setLayoutProperty(layerId, propertyName, value);\n return { layerId, propertyName, value, success: true as const };\n } catch (error) {\n return {\n layerId,\n propertyName,\n error: `Failed to set layout property: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n\n return { results };\n } catch (error) {\n return {\n error: `Failed to set layout properties: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst changeResultSchema = z.union([\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n value: z.unknown(),\n success: z.literal(true),\n }),\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n error: z.string(),\n }),\n]);\n\n/** Output schema for the setPaintProperties tool. */\nexport const setPaintPropertiesOutputSchema = z.union([\n z.object({\n results: z.array(changeResultSchema),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the setPaintProperties tool. */\nexport const setPaintPropertiesSchema = z.object({\n changes: z\n .array(\n z.object({\n layerId: z.string().describe('MapLibre layer ID from getMapStyleLayers'),\n propertyName: z.string().describe('e.g. fill-color|line-width|text-color'),\n value: z.any(),\n }),\n )\n .describe('List of paint property changes to apply in one go.'),\n});\n\nexport const setPaintPropertiesDescription =\n 'Set one or more paint properties on MapLibre layers (colors, widths, opacity). ' +\n 'Use getMapStyleLayers to discover valid layer IDs first. ' +\n 'Paint properties control visual appearance — use setLayoutProperties for visibility and structural properties. ' +\n 'For route styling, prefer setRouteTheme.';\n\n/** Execute function for setPaintProperties — usable with ToolEntry format. */\nexport const executeSetPaintProperties = async (params: z.infer<typeof setPaintPropertiesSchema>, state: ToolState) => {\n const { changes } = params;\n try {\n const results = changes.map(({ layerId, propertyName, value }) => {\n try {\n state.baseMap.mapLibreMap.setPaintProperty(layerId, propertyName, value);\n return { layerId, propertyName, value, success: true as const };\n } catch (error) {\n return {\n layerId,\n propertyName,\n error: `Failed to set paint property: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n\n return { results };\n } catch (error) {\n return {\n error: `Failed to set paint properties: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the set-pitch-bearing tool. */\nexport const setPitchBearingOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting pitch and bearing.\n */\nexport const setPitchBearingSchema = z.object({\n pitch: z.number().min(0).max(85).optional().describe('0=top-down, 85=near-horizontal'),\n bearing: z.number().min(-180).max(180).optional().describe('0=north, 90=east'),\n});\n\nexport const setPitchBearingDescription =\n 'Tilt and/or rotate the map camera. Pitch 0 = top-down view, 85 = near-horizontal (3D effect). ' +\n 'Bearing 0 = north up, 90 = east up. Provide at least one of pitch or bearing.';\n\n/** Execute function for setPitchBearing — usable with ToolEntry format. */\nexport const executeSetPitchBearing = async (params: z.infer<typeof setPitchBearingSchema>, state: ToolState) => {\n const { pitch, bearing } = params;\n\n if (pitch === undefined && bearing === undefined) {\n return { error: 'Provide at least one of pitch or bearing' };\n }\n\n try {\n const map = state.baseMap.mapLibreMap;\n map.easeTo({\n ...(pitch !== undefined && { pitch }),\n ...(bearing !== undefined && { bearing }),\n });\n\n return { success: true };\n } catch (error) {\n return {\n error: `Failed to set pitch/bearing: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the zoom-in-or-out tool. */\nexport const zoomInOrOutOutputSchema = z.union([\n z.object({\n zoom: z.number().describe('Resulting zoom level after the change'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for zoom-in-or-out. */\nexport const zoomInOrOutSchema = z.object({\n delta: z\n .number()\n .describe(\n 'Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.',\n ),\n});\n\nexport const zoomInOrOutDescription =\n 'Zoom the map in or out by a given amount relative to the current zoom level. ' +\n 'Positive delta zooms in (closer), negative delta zooms out (further away). ' +\n 'Returns the resulting zoom level.';\n\n/** Execute function for zoomInOrOut — usable with ToolEntry format. */\nexport const executeZoomInOrOut = async (params: z.infer<typeof zoomInOrOutSchema>, state: ToolState) => {\n const { delta } = params;\n try {\n const map = state.baseMap.mapLibreMap;\n const targetZoom = map.getZoom() + delta;\n map.zoomTo(targetZoom);\n return { zoom: targetZoom };\n } catch (error) {\n return {\n error: `Zoom failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-classifier\n *\n * Query intent classification for automatic tool selection.\n *\n * Uses LLM-based structured generation to map a user query directly to the\n * minimal set of tool names needed to answer it.\n *\n * The classifier prompt is built dynamically from each tool's `relatedTools`\n * in the tool metadata, so it stays in sync automatically when tools are\n * added or renamed.\n */\n\nimport { generateText, type LanguageModel, type ModelMessage, Output, type TextPart } from 'ai';\nimport { z } from 'zod';\nimport type { Classifier, ClassifierContext, ToolMetadata } from '../types';\nimport type { ClassificationResult, ClassifierOptions } from './types';\n\n/**\n * A single turn in the conversation passed to {@link classifyUserIntent}.\n *\n * @ignore\n */\nexport type ConversationMessage = { role: 'user' | 'assistant'; content: string };\n\n/** Max character length for each history message when compacting for the classifier. */\nconst MAX_HISTORY_MESSAGE_LENGTH = 300;\n\n/** Number of most-recent history messages (excluding the last user message) passed to the classifier. */\nconst MAX_CLASSIFIER_HISTORY_MESSAGES = 8;\n\n/** Formats a single tool's metadata into a compact classifier prompt entry. */\nconst formatToolEntry = (name: string, entry: ToolMetadata): string => {\n const classificationPrompt = entry.classificationPrompt ? ` ${entry.classificationPrompt}.` : '';\n // const related = entry.relatedTools?.length ? ` Related: ${entry.relatedTools.join(', ')}.` : '';\n const depends = entry.dependsOn?.length ? ` Depends on: ${entry.dependsOn.join(', ')}.` : '';\n return `${name}${classificationPrompt}${depends}`;\n};\n\n/**\n * Builds the system prompt listing available tools for the classifier.\n *\n * @ignore\n */\nexport const buildClassifySystemPrompt = (toolsMetadata: Record<string, ToolMetadata>): string => {\n const toolEntries = Object.entries(toolsMetadata).map(([name, entry]) => formatToolEntry(name, entry));\n\n return [\n 'You are a tool selector for a map assistant.',\n 'You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.',\n `LOCATION REFERENCE:\n - \"near me\" / \"my location\" / \"where I am\" → getCurrentLocation (user's physical GPS position, may prompt permission)\n - \"in this area\" / \"here on the map\" / \"near the map center\" → getViewport (map's current view)\n - If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point`,\n '',\n 'Available tools:',\n ...toolEntries,\n ].join('\\n');\n};\n\nconst classifySchema = z.object({\n tools: z\n .array(z.string()) // using string to save tokens, since tool names already included in system prompt\n .min(1)\n .describe('Tool names needed to fulfill the query.'),\n});\n\n/** Calls the LLM with structured output to select tools for the conversation. */\nconst classifyQueryToTools = async (\n conversation: ConversationMessage[],\n model: LanguageModel,\n toolsMetadata: Record<string, ToolMetadata>,\n): Promise<{ tools: string[]; usage: ClassificationResult['usage'] }> => {\n const generated = await generateText({\n model,\n output: Output.object({ schema: classifySchema }),\n system: buildClassifySystemPrompt(toolsMetadata),\n messages: conversation,\n maxOutputTokens: 200,\n });\n\n return {\n tools: generated.output.tools,\n usage: {\n inputTokens: generated.usage?.inputTokens ?? 0,\n outputTokens: generated.usage?.outputTokens ?? 0,\n totalTokens: generated.usage?.totalTokens ?? 0,\n },\n };\n};\n\n/**\n * Classify a user conversation to determine which tools to activate.\n *\n * The `conversation` should contain the last user message plus a compaction\n * of the previous chat history (each truncated to save tokens).\n *\n * @example\n * ```typescript\n * const result = await classifyUserIntent(conversation, {\n * chatModel: openai('gpt-4o-mini'),\n * toolsMetadata,\n * });\n * // result.activeToolNames contains the selected tools\n * ```\n *\n * @group Agent Toolkit\n */\nexport const classifyUserIntent = async (\n conversation: ConversationMessage[],\n options: ClassifierOptions,\n): Promise<ClassificationResult> => {\n const { chatModel, toolsMetadata } = options;\n const startTime = performance.now();\n\n const { tools, usage } = await classifyQueryToTools(conversation, chatModel, toolsMetadata);\n\n const activeToolNames = tools;\n\n const timeMs = performance.now() - startTime;\n\n return {\n activeToolNames,\n timeMs,\n usage,\n };\n};\n\n/** Type predicate that narrows a message content part to a TextPart. */\nconst isTextPart = (part: { type: string }): part is TextPart => part.type === 'text';\n\n/**\n * Extracts plain text from a message's content (string or structured content array).\n *\n * @ignore\n */\nexport const extractMessageText = (content: string | Array<{ type: string }>): string | null => {\n if (typeof content === 'string') return content;\n\n const textParts = content.filter(isTextPart);\n if (textParts.length > 0) return textParts.map((part) => part.text).join('');\n\n return null;\n};\n\n/**\n * Extracts the text content of the last user message in a message array.\n * Returns null if no user message is found or if content has no text.\n *\n * @group Agent Toolkit\n */\nexport const extractLastUserText = (messages: ReadonlyArray<ModelMessage>): string | null => {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n return extractMessageText(messages[i].content);\n }\n }\n return null;\n};\n\n/**\n * Creates the default LLM-based classifier. Uses structured generation to select\n * the minimal tool set for each user message.\n *\n * @param options.model - Language model for classification (ideally fast/cheap like gpt-4o-mini)\n * @param options.maxHistoryMessages - Max prior turns for context. Default: 8\n * @param options.maxHistoryMessageLength - Max chars per history message. Default: 300\n *\n * @group Agent Toolkit\n */\nexport const createDefaultClassifier = (options: {\n model: LanguageModel;\n maxHistoryMessages?: number;\n maxHistoryMessageLength?: number;\n}): Classifier => {\n const { model, maxHistoryMessages, maxHistoryMessageLength } = options;\n\n return async (context: ClassifierContext): Promise<ClassificationResult | null> => {\n const { messages, toolsMetadata } = context;\n if (!messages?.length) return null;\n\n const historyLimit = maxHistoryMessages ?? MAX_CLASSIFIER_HISTORY_MESSAGES;\n const messageCharLimit = maxHistoryMessageLength ?? MAX_HISTORY_MESSAGE_LENGTH;\n\n // Find last user message index and extract text in one pass\n let lastUserIndex = -1;\n let lastUserText: string | null = null;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n lastUserIndex = i;\n lastUserText = extractMessageText(messages[i].content);\n break;\n }\n }\n if (!lastUserText) return null;\n\n // Compact history\n const conversation: ConversationMessage[] = [];\n const historyStartIndex = Math.max(0, lastUserIndex - historyLimit);\n\n for (let i = historyStartIndex; i < lastUserIndex; i++) {\n const message = messages[i];\n if (message.role !== 'user' && message.role !== 'assistant') continue;\n\n const text = extractMessageText(message.content);\n if (!text) continue;\n\n const compacted = text.length > messageCharLimit ? text.slice(0, messageCharLimit) + '…' : text;\n conversation.push({ role: message.role, content: compacted });\n }\n\n conversation.push({ role: 'user', content: lastUserText });\n\n try {\n return await classifyUserIntent(conversation, { chatModel: model, toolsMetadata });\n } catch {\n return null;\n }\n };\n};\n","import type { Place, Places, Routes, WaypointLike } from '@tomtom-org/maps-sdk/core';\n\n/**\n * Optional search context used to enrich a places history label.\n *\n * @ignore\n */\nexport type PlacesLabelContext = {\n query?: string;\n poiCategories?: string[];\n where?: string;\n /** Route label when results come from an along-route search (e.g. \"Amsterdam to Utrecht\"). */\n routeLabel?: string;\n};\n\nconst placeName = (place: Place): string | undefined => place.properties?.poi?.name;\n\nconst placeAddress = (place: Place): string | undefined => place.properties?.address?.freeformAddress;\n\nconst waypointAddress = (wp: WaypointLike): string | undefined => {\n if (Array.isArray(wp)) return undefined;\n return (wp as any).properties?.address?.freeformAddress;\n};\n\nconst formatDuration = (seconds: number): string => `${Math.round(seconds / 60)} min`;\n\nconst formatDistance = (meters: number): string => `${(meters / 1000).toFixed(1)} km`;\n\n/** Builds a label for a Places FeatureCollection from its count and search context. */\nconst makeCollectionLabel = (count: number, context?: PlacesLabelContext): string => {\n const parts: string[] = [];\n\n if (context?.query) parts.push(`\"${context.query}\"`);\n if (context?.poiCategories?.length) parts.push(context.poiCategories.join(', '));\n if (context?.routeLabel) parts.push(`along ${context.routeLabel}`);\n else if (context?.where) parts.push(`in ${context.where}`);\n\n const countStr = `${count} ${count === 1 ? 'place' : 'places'}`;\n return parts.length ? `${parts.join(' ')} (${countStr})` : countStr;\n};\n\n/**\n * Generate a human-readable label for a places history entry.\n *\n * For a single Place the label is derived from result properties (name / address),\n * falling back to the query when properties are sparse.\n *\n * For a Places collection (FeatureCollection) the label is built from search\n * context (query, categories, location) plus the result count.\n *\n * @ignore\n */\nexport const makePlacesLabel = (data: Place | Places, context?: PlacesLabelContext): string => {\n if ('features' in data) return makeCollectionLabel(data.features.length, context);\n\n const name = placeName(data);\n const address = placeAddress(data);\n\n if (name && address) return `${name}, ${address}`;\n if (name) return name;\n if (address) return address;\n if (context?.query) return context.query;\n return 'Place';\n};\n\n/**\n * Generate a human-readable label for a routes history entry.\n *\n * @ignore\n */\nexport const makeRoutesLabel = (routes: Routes, waypoints: WaypointLike[]): string => {\n const summary = routes.features[0]?.properties?.summary;\n const timeDist = summary\n ? ` (${formatDuration(summary.travelTimeInSeconds)}, ${formatDistance(summary.lengthInMeters)})`\n : '';\n\n const addresses = waypoints.map(waypointAddress).filter(Boolean) as string[];\n\n if (addresses.length >= 2) {\n const origin = addresses[0];\n const destination = addresses.at(-1);\n const vias = addresses.slice(1, -1);\n const path =\n vias.length > 0 ? `${origin} via ${vias.join(', ')} to ${destination}` : `${origin} to ${destination}`;\n return `${path}${timeDist}`;\n }\n\n return `Route${timeDist}`;\n};\n\n/** Budget type → display unit suffix. */\nconst RANGE_BUDGET_UNITS: Record<string, string> = {\n timeMinutes: 'min',\n distanceKM: 'km',\n remainingChargeCPT: '% remaining',\n spentChargePCT: '% spent',\n spentFuelLiters: 'L',\n};\n\n/**\n * Generate a human-readable label for a range history entry.\n * e.g. \"10/20/30 min from Amsterdam\" or \"50 km from [4.9, 52.3]\"\n *\n * @ignore\n */\nexport const makeRangeLabel = (budgets: Array<{ type: string; value: number }>, originName: string): string => {\n const sorted = [...budgets].sort((a, b) => a.value - b.value);\n const budgetStr = sorted.map((b) => `${b.value} ${RANGE_BUDGET_UNITS[b.type] ?? b.type}`).join('/');\n return `${budgetStr} from ${originName}`;\n};\n","/**\n * @module agent-toolkit-utils\n */\n\nimport {\n type CommonPlaceProps,\n getPosition,\n type Place,\n type Places,\n type Route,\n type Routes,\n type Waypoint,\n type WaypointLike,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\n\n/** @ignore */\nexport type PlaceSummary = { name?: string; address?: string; position: [number, number] };\n\n/** @ignore */\nexport type PlacesSummary = { count: number; features: PlaceSummary[] };\n\ntype RouteSummary = Route['properties']['summary'];\ntype LegSummary = Route['properties']['sections']['leg'][number]['summary'];\n\n// Maps Date fields to string for LLM-safe serialization (distributive so Date | undefined → string | undefined).\ntype StringDateValue<T> = T extends Date ? string : T;\n\n/** @ignore */\nexport type StringDates<T> = { [K in keyof T]: StringDateValue<T[K]> };\n\n/** @ignore */\nexport type SummarizedRoute = {\n index: number;\n summary: StringDates<RouteSummary>;\n /** Per-leg summaries; only present when the route has more than one leg. */\n legSummaries?: StringDates<LegSummary>[];\n};\n\n/** @ignore */\nexport type SummarizedRoutes = { count: number; routes: SummarizedRoute[] };\n\n/**\n * Converts a Place to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizePlace = (place: Place): PlaceSummary => {\n const coordinates = place.geometry?.coordinates as [number, number] | undefined;\n return {\n name: place.properties?.poi?.name,\n address: place.properties?.address?.freeformAddress,\n position: coordinates ?? [0, 0],\n };\n};\n\n/**\n * Converts Places to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizePlaces = (places: Places | Place[]): PlacesSummary => {\n const features = Array.isArray(places) ? places : places.features;\n return {\n count: features.length,\n features: features.map((feature) => summarizePlace(feature)),\n };\n};\n\n/**\n * Converts a Route to a token-efficient summary for LLM consumption.\n * Returns only the route summary and per-leg summaries (when more than one leg).\n *\n * @ignore\n */\nexport const summarizeRoute = (route: Route): SummarizedRoute => {\n const { summary, sections, index } = route.properties;\n const legs = sections.leg;\n\n const stringifyDates = <T extends { arrivalTime: Date; departureTime: Date }>(summary: T): StringDates<T> =>\n ({\n ...summary,\n arrivalTime: summary.arrivalTime.toISOString(),\n departureTime: summary.departureTime.toISOString(),\n }) as StringDates<T>;\n\n return {\n index,\n summary: stringifyDates(summary),\n ...(legs.length > 1 && { legSummaries: legs.map((leg) => stringifyDates(leg.summary)) }),\n };\n};\n\n/**\n * Converts Routes to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizeRoutes = (routes: Routes): SummarizedRoutes => {\n const count = routes.features.length;\n\n return {\n count,\n routes: routes.features.map(summarizeRoute),\n };\n};\n\n/** @ignore */\nexport const waypointSummarySchema = z.object({\n slotIndex: z.number(),\n isFilled: z.boolean(),\n address: z.string().optional(),\n position: z.array(z.number()).length(2).optional().describe('[longitude, latitude]'),\n});\n\n/**\n * Token-efficient summary of a waypoint for LLM consumption.\n *\n * @ignore\n */\nexport const summarizeWaypoint = (\n waypoint: WaypointLike | null | undefined,\n): z.infer<typeof waypointSummarySchema> | null => {\n if (waypoint === null || waypoint === undefined) {\n return null;\n }\n\n const position = getPosition(waypoint);\n let address: string | undefined;\n if (!Array.isArray(waypoint)) {\n address = (waypoint as Waypoint<CommonPlaceProps>).properties?.address?.freeformAddress;\n }\n\n return {\n slotIndex: -1,\n isFilled: true,\n ...(address && { address }),\n ...(position && { position }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type BBox, getPosition, type Place, type WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { geocode, geocodeOne, search, searchOne } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel } from '../../utils/state-labels';\nimport { summarizePlace } from '../../utils/summarize';\nimport { getViewportBias, getViewportBoundingBox, shownSchema, showPlacesSchema, whereSchema } from '../shared';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\ntype LocateBias = { position: Position } | { boundingBox: BBox };\n\nexport const locatePlace = async (\n query: string,\n locationType: 'poi' | 'default',\n bias?: LocateBias,\n): Promise<Place | null> => {\n const [searchFn, searchOneFn] = locationType === 'poi' ? [search, searchOne] : [geocode, geocodeOne];\n\n if (bias && 'position' in bias) {\n const results = await searchFn({ query, position: bias.position, limit: 1 });\n return results.features[0] ?? null;\n }\n if (bias && 'boundingBox' in bias) {\n const results = await searchFn({ query, boundingBox: bias.boundingBox, limit: 1 });\n return results.features[0] ?? null;\n }\n return (await searchOneFn(query)) ?? null;\n};\n\nexport const locatePlaceSchema = z.object({\n query: z\n .string()\n .describe('Location string to resolve for downstream workflows such as routes or further searches.'),\n waypointIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\n 'Route location slot to stage. Ordered as [origin, ...stops, destination]. Use it if this is an origin, stop or destination for a route.',\n ),\n locationType: z\n .enum(['poi', 'default'])\n .describe(\n '\"poi\" for venues, landmarks, and businesses; \"default\" for places to geocode: addresses, parks, post codes, and geographies.',\n ),\n where: whereSchema.describe('Geographic scope for the search.'),\n show: showPlacesSchema\n .optional()\n .describe(\n 'Optional map display actions to perform after resolving the place. Skip if this is an intermediate calculation for another step.',\n ),\n});\n\nexport const locatePlaceOutputSchema = z.union([\n placeOutputSchema.extend({\n waypointIndex: z.number().optional(),\n shown: shownSchema.optional(),\n }),\n toolErrorSchema,\n]);\n\nexport const locatePlaceDescription =\n 'Resolve one user-provided location string into a normalized place summary for downstream workflows without changing the map or camera. ' +\n 'Search-first: accepts landmarks, stations, venues, cities, neighborhoods, and full addresses. ' +\n 'Can optionally stage a waypoint slot.';\n\nconst resolveLocateBias = async (\n where: z.infer<typeof whereSchema>,\n state: ToolState,\n): Promise<LocateBias | undefined> => {\n if (where === 'nearby-map-center') {\n const position = getViewportBias(state.baseMap);\n return position ? { position } : undefined;\n }\n if (where === 'within-map-bounds') {\n return { boundingBox: getViewportBoundingBox(state.baseMap) };\n }\n if (where === 'global') {\n return undefined;\n }\n if (typeof where === 'string') {\n const geoResult = await geocodeOne(where);\n if (geoResult?.bbox) return { boundingBox: geoResult.bbox };\n if (geoResult) {\n const position = getPosition(geoResult);\n if (position) return { position };\n }\n return undefined;\n }\n if ('position' in where) {\n return { position: where.position as Position };\n }\n return { boundingBox: where.boundingBox as BBox };\n};\n\nconst showLocateResult = async (\n state: ToolState,\n result: Place,\n placesEntryId: string,\n show: z.infer<typeof showPlacesSchema>,\n): Promise<z.infer<typeof shownSchema>> => {\n const shown: z.infer<typeof shownSchema> = { markerType: false, zoomMode: false };\n if (show.markerType === 'pin') {\n if (show.mode === 'add') {\n await state.places.addShownEntries([placesEntryId]);\n } else {\n await state.places.setShownEntries([placesEntryId]);\n }\n shown.markerType = true;\n }\n if (show.zoomMode === 'auto') {\n if (result.bbox) {\n state.baseMap.mapLibreMap.fitBounds(result.bbox);\n } else {\n const pos = getPosition(result);\n if (pos) state.baseMap.mapLibreMap.flyTo({ center: pos as [number, number], zoom: 13 });\n }\n shown.zoomMode = true;\n }\n return shown;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeLocatePlace = async (\n params: z.infer<typeof locatePlaceSchema>,\n state: ToolState,\n): Promise<z.infer<typeof locatePlaceOutputSchema>> => {\n const { query, locationType, waypointIndex, where, show } = params;\n\n try {\n const bias = await resolveLocateBias(where, state);\n const result = await locatePlace(query, locationType, bias);\n\n if (!result) {\n return { error: `No result found for \"${query}\"` };\n }\n\n const placesEntryId = state.places.addPlaceResult(result, makePlacesLabel(result, { query }));\n\n if (waypointIndex !== undefined) {\n state.routing.setWaypointAt(waypointIndex, result as unknown as WaypointLike);\n }\n\n const shown = show ? await showLocateResult(state, result, placesEntryId, show) : undefined;\n\n return {\n ...summarizePlace(result),\n ...(waypointIndex !== undefined && { waypointIndex }),\n ...(shown && { shown }),\n };\n } catch (error) {\n return {\n error: `Location resolution failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { getPosition } from '@tomtom-org/maps-sdk/core';\nimport type { LocationInput, ResolvedLocation } from '../shared';\nimport { locatePlace } from './locate-place';\n\n/** @ignore */\nexport const resolveLocationInput = async (input: LocationInput): Promise<ResolvedLocation | null> => {\n if ('position' in input) {\n const { lng, lat } = input.position;\n return {\n place: [lng, lat],\n position: [lng, lat],\n name: `[${lng}, ${lat}]`,\n };\n }\n\n const resolved = await locatePlace(input.query, input.locationType);\n if (!resolved) return null;\n\n const pos = getPosition(resolved);\n if (!pos) return null;\n\n return {\n place: resolved,\n position: pos,\n name: resolved.properties?.address?.freeformAddress ?? input.query,\n query: input.query,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type HasBBox, type Routes, type WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { type CostModel, calculateRoute, type MaxNumberOfAlternatives } from '@tomtom-org/maps-sdk/services';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { RouteParams, ToolState } from '../../types';\nimport { makeRoutesLabel, summarizeRoutes } from '../../utils';\nimport { locationInputSchema } from '../shared';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\n/** Output schema for the set-route-locations tool. */\nexport const setRouteLocationsOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/** Input schema for set-route-locations. */\nexport const setRouteLocationsSchema = z.object({\n locations: z\n .array(locationInputSchema)\n .min(2)\n .describe('Ordered list: [origin, ...stops, destination]. Minimum 2.'),\n showOnMap: z.boolean().describe('Show the route and waypoints on the map after calculation.'),\n});\n\nexport const setRouteLocationsDescription =\n 'Set the route waypoints by resolving location queries. ' +\n 'Always triggers a route recalculation once all locations are resolved (minimum: origin + destination). ' +\n 'Replaces all current waypoints. Uses route parameters from setRouteParameters if already set. ' +\n 'Can be called in parallel with setRouteParameters. ' +\n 'Only car/driving routes are supported.';\n\n/**\n * Filters out null waypoints and returns the array only if there are at least 2 valid entries.\n * Returns null if the condition is not met, preventing a route calculation call.\n */\nexport const resolveRouteWaypoints = (waypoints: (WaypointLike | null)[]): WaypointLike[] | null => {\n const valid = waypoints.filter((w): w is WaypointLike => w !== null);\n return valid.length >= 2 ? valid : null;\n};\n\nexport const buildCalculateRouteParams = (routeParams: RouteParams) => {\n const { maxAlternatives = 0, costModel, when } = routeParams;\n return {\n ...(maxAlternatives > 0 && { maxAlternatives: maxAlternatives as MaxNumberOfAlternatives }),\n ...(costModel && {\n costModel: {\n ...costModel,\n ...(costModel.avoidAreas && {\n avoidAreas: costModel.avoidAreas as unknown as HasBBox[],\n }),\n } as CostModel,\n }),\n ...(when && { when: { option: when.option, date: new Date(when.date) } }),\n };\n};\n\nexport const showRouteOnMap = async (state: ToolState, routes: Routes, waypoints: WaypointLike[]): Promise<void> => {\n const routingModule = await state.routing.getRoutingModule();\n await routingModule.showRoutes(routes);\n await routingModule.showWaypoints(waypoints);\n const bbox = bboxFromGeoJSON(routes);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50 });\n }\n};\n\n/**\n * Create the set-route-locations tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeSetRouteLocations = async (\n params: z.infer<typeof setRouteLocationsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setRouteLocationsOutputSchema>> => {\n const { locations, showOnMap } = params;\n try {\n const resolved = await Promise.all(locations.map(resolveLocationInput));\n\n const unresolved = locations.filter((loc, i) => resolved[i] === null && 'query' in loc) as Array<{\n query: string;\n }>;\n if (unresolved.length > 0) {\n return { error: `Could not resolve: ${unresolved.map((l) => `\"${l.query}\"`).join(', ')}` };\n }\n\n const waypoints = resolveRouteWaypoints(resolved.map((r) => r?.place ?? null));\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n const routes = await calculateRoute({\n locations: waypoints,\n ...buildCalculateRouteParams(state.routing.params),\n });\n\n state.routing.addRoutes(routes, waypoints, makeRoutesLabel(routes, waypoints));\n\n if (showOnMap) {\n await showRouteOnMap(state, routes, waypoints);\n }\n\n return summarizeRoutes(routes);\n } catch (error) {\n return {\n error: `Route calculation failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place } from '@tomtom-org/maps-sdk/core';\nimport { withInsertedWaypoint } from '@tomtom-org/maps-sdk/core';\nimport { calculateRoute } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makeRoutesLabel, summarizeRoutes } from '../../utils';\nimport { locationInputSchema } from '../shared';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\nimport { buildCalculateRouteParams, resolveRouteWaypoints, showRouteOnMap } from './set-route-locations';\n\n/** Output schema for the add-stop-to-route tool. */\nexport const addStopToRouteOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for adding a stop to existing route.\n */\nexport const addStopToRouteSchema = z.object({\n location: locationInputSchema.optional().describe('The stop to add. Omit to use the last stored place in context.'),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n});\n\nexport const addStopToRouteDescription =\n 'Add a stop to the current route. Inserts the new waypoint at the optimal position and always triggers a route recalculation. ' +\n 'If location is provided, it is resolved search-first. ' +\n 'Requires an existing route (call setRouteLocations first).';\n\nconst validateExistingRoute = async (state: ToolState) => {\n const shownRoutes = (await state.routing.getRoutingModule()).getShown();\n const shownRouteLines = shownRoutes?.mainLines;\n const shownWaypoints = shownRoutes?.waypoints;\n if (!shownRouteLines || !shownRouteLines.features.length) {\n return 'No existing route available. Use calculate-route first.';\n }\n if (!shownWaypoints || shownWaypoints.features.length < 2) {\n return 'No existing waypoints available. Use calculate-route first to create a route with waypoints.';\n }\n return { shownRouteLines, shownWaypoints };\n};\n\nconst resolveNewWaypoint = async (\n location: z.infer<typeof locationInputSchema> | undefined,\n state: ToolState,\n): Promise<{ place: Place | [number, number] } | { error: string }> => {\n if (location) {\n const resolved = await resolveLocationInput(location);\n if (!resolved) {\n const label = 'query' in location ? `\"${location.query}\"` : JSON.stringify(location.position);\n return { error: `Could not resolve location: ${label}` };\n }\n return { place: resolved.place as Place | [number, number] };\n }\n const fallback = state.places.latestPlace?.at(0);\n if (!fallback) {\n return {\n error: 'No location provided and no last place available in context. Provide a location or resolve a place first.',\n };\n }\n return { place: fallback };\n};\n\n/**\n * Create the add stop to route tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeAddStopToRoute = async (\n params: z.infer<typeof addStopToRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof addStopToRouteOutputSchema>> => {\n const { location, showOnMap } = params;\n try {\n const routeResult = await validateExistingRoute(state);\n if (typeof routeResult === 'string') return { error: routeResult };\n const { shownRouteLines, shownWaypoints } = routeResult;\n\n const waypointResult = await resolveNewWaypoint(location, state);\n if ('error' in waypointResult) return waypointResult;\n const newWaypoint = waypointResult.place;\n\n const routeToUse = shownRouteLines.features[0];\n\n // Use withInsertedWaypoint to find the best position and get updated waypoints\n const updatedWaypoints = withInsertedWaypoint(routeToUse, shownWaypoints.features, newWaypoint);\n\n const waypoints = resolveRouteWaypoints(updatedWaypoints);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n // Calculate new route with the updated waypoints and current route params\n const routes = await calculateRoute({\n locations: waypoints,\n ...buildCalculateRouteParams(state.routing.params),\n });\n\n state.routing.addRoutes(routes, waypoints, makeRoutesLabel(routes, waypoints));\n\n if (showOnMap) {\n await showRouteOnMap(state, routes, waypoints);\n }\n\n return summarizeRoutes(routes);\n } catch (error) {\n return {\n error: `Failed to add stop to route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, getPosition, type HasBBox } from '@tomtom-org/maps-sdk/core';\nimport { search, searchOne } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlaces } from '../../utils';\nimport {\n getViewportBias,\n getViewportBoundingBox,\n resolvePoiCategories,\n shownSchema,\n showPlacesSchema,\n showResultsOnMap,\n whereSchema,\n} from '../shared';\nimport { placesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the discover-places tool. */\nexport const discoverPlacesOutputSchema = z.union([\n placesOutputSchema.extend({\n shown: shownSchema.optional(),\n placesEntryId: z.string().optional(),\n label: z\n .string()\n .optional()\n .describe('Human-readable label stored with the entry (e.g. \"cafe\", \"CINEMA (50 places)\").'),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for discovering places.\n */\nexport const discoverPlacesSchema = z\n .object({\n query: z\n .string()\n .optional()\n .describe(\n 'Search query to filter places by name or address. For POI categories do not use query but use poiCategories.',\n ),\n where: whereSchema.optional().describe('Geographic scope for the search. Defaults to \"within-map-bounds\".'),\n radiusMeters: z.number().optional().describe('search radius in meters'),\n limit: z.number().max(100).optional().describe('default: 10 if query is supplied, 50 otherwise'),\n poiCategories: z\n .array(z.string())\n .optional()\n .describe(\n 'POI category codes for filtering in CONSTANT_CASE. Call getPoiCategoryCodes first to resolve natural language (e.g. \"gym\", \"restaurant\", \"aparcamiento\") into codes.',\n ),\n show: showPlacesSchema.optional().describe('Options to render the results on the map.'),\n withinRange: z\n .string()\n .optional()\n .describe(\n 'Range ID from findReachableArea to restrict search to that reachable area (e.g. \"ranges-0\"). Use recallRanges to find the ID. Mutually exclusive with where.',\n ),\n })\n // Runtime guard only — .refine() doesn't emit JSON Schema, so LLMs won't see this constraint.\n // The LLM-facing constraint is in the withinRange description (\"Mutually exclusive with where\").\n .refine((data) => !(data.withinRange && data.where), {\n message: 'withinRange and where are mutually exclusive — provide one or the other, not both.',\n });\n\nexport const discoverPlacesDescription =\n 'Search for multiple places by text query or POI category. ' +\n 'Stores results as a new places entry and returns its `placesEntryId`. ' +\n 'Pass `show` to render on the map — set `show.mode: \"add\"` when stacking on top of an existing display (\"also show cinemas\"), default `\"replace\"` for a fresh view. ' +\n 'Defaults to searching within the current map viewport; pass `where` to override. ' +\n 'Query is optional — omit it to search by categories only. ' +\n 'For a single specific place, use locatePlace instead.';\n\ntype WhereBias = { boundingBox?: HasBBox; position?: Position };\n\nconst searchInRange = async (\n state: ToolState,\n withinRange: string,\n query: string | undefined,\n limit: number | undefined,\n poiCategories: string[] | undefined,\n): Promise<{ result: Awaited<ReturnType<typeof search>>; placesEntryId: string } | { error: string }> => {\n const rangeEntry = state.ranges.entries.find((e) => e.id === withinRange);\n if (!rangeEntry?.polygon) {\n return { error: `Range \"${withinRange}\" not found. Use recallRanges to list available ranges.` };\n }\n const resolvedPoiCategories = await resolvePoiCategories(poiCategories);\n const result = await search({\n query,\n limit: limit ?? (query ? 10 : 50),\n poiCategories: resolvedPoiCategories,\n geometries: [rangeEntry.polygon],\n });\n const placesEntryId = state.places.addPlaceResult(result, makePlacesLabel(result, { query, poiCategories }));\n return { result, placesEntryId };\n};\n\nconst resolveWhereBias = async (\n where: z.infer<typeof whereSchema> | undefined,\n state: ToolState,\n): Promise<WhereBias> => {\n const resolvedWhere = where ?? 'within-map-bounds';\n\n if (resolvedWhere === 'within-map-bounds') {\n return { boundingBox: getViewportBoundingBox(state.baseMap) };\n }\n if (resolvedWhere === 'nearby-map-center') {\n const position = getViewportBias(state.baseMap);\n return position ? { position } : {};\n }\n if (resolvedWhere === 'global') {\n return {};\n }\n if (typeof resolvedWhere === 'string') {\n const geoResult = await searchOne(resolvedWhere);\n if (geoResult?.bbox) return { boundingBox: geoResult.bbox };\n if (geoResult) {\n const pos = getPosition(geoResult);\n if (pos) return { position: pos };\n }\n return {};\n }\n if ('position' in resolvedWhere) {\n return { position: resolvedWhere.position as Position };\n }\n return { boundingBox: bboxFromGeoJSON(resolvedWhere.boundingBox as HasBBox) as HasBBox };\n};\n\nconst resolveWhereLabel = (where: z.infer<typeof whereSchema> | undefined): string | undefined =>\n typeof where === 'string' && where !== 'within-map-bounds' && where !== 'nearby-map-center' && where !== 'global'\n ? where\n : undefined;\n\nconst searchWithBias = async (\n state: ToolState,\n query: string | undefined,\n where: z.infer<typeof whereSchema> | undefined,\n limit: number | undefined,\n poiCategories: string[] | undefined,\n radiusMeters: number | undefined,\n): Promise<{ result: Awaited<ReturnType<typeof search>>; placesEntryId: string }> => {\n const biasParams = await resolveWhereBias(where, state);\n const resolvedPoiCategories = await resolvePoiCategories(poiCategories);\n const result = await search({\n query,\n limit: limit ?? (query ? 10 : 50),\n poiCategories: resolvedPoiCategories,\n ...biasParams,\n radiusMeters,\n });\n const placesEntryId = state.places.addPlaceResult(\n result,\n makePlacesLabel(result, { query, poiCategories, where: resolveWhereLabel(where) }),\n );\n return { result, placesEntryId };\n};\n\n/**\n * Create the discover places tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeDiscoverPlaces = async (\n params: z.infer<typeof discoverPlacesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof discoverPlacesOutputSchema>> => {\n const { query, where, radiusMeters, limit, poiCategories, show, withinRange } = params;\n\n try {\n let result: Awaited<ReturnType<typeof search>>;\n let placesEntryId: string;\n\n if (withinRange) {\n const rangeResult = await searchInRange(state, withinRange, query, limit, poiCategories);\n if ('error' in rangeResult) return rangeResult;\n result = rangeResult.result;\n placesEntryId = rangeResult.placesEntryId;\n } else {\n const biasResult = await searchWithBias(state, query, where, limit, poiCategories, radiusMeters);\n result = biasResult.result;\n placesEntryId = biasResult.placesEntryId;\n }\n\n const shown = show ? await showResultsOnMap(state, result, placesEntryId, show) : undefined;\n const label = state.places.entries.find((entry) => entry.id === placesEntryId)?.label;\n\n return {\n ...summarizePlaces(result),\n placesEntryId,\n ...(label && { label }),\n ...(shown && { shown }),\n };\n } catch (error) {\n return {\n error: `Search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport type { GeometryTheme } from '@tomtom-org/maps-sdk/map';\nimport type { BudgetType, ReachableRangeParams } from '@tomtom-org/maps-sdk/services';\nimport { calculateReachableRanges } from '@tomtom-org/maps-sdk/services';\nimport { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makeRangeLabel } from '../../utils';\nimport { locationInputSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\nexport const budgetSchema = z.object({\n type: z\n .enum(['timeMinutes', 'distanceKM']) // TODO: 'remainingChargeCPT', 'spentChargePCT', 'spentFuelLiters' these need vehicle data\n .describe('Budget type including its unit.'),\n value: z.number().describe('Budget value. e.g. 30 for 30 minutes, 50 for 50 km.'),\n});\n\nexport const findReachableAreaSchema = z.object({\n origin: locationInputSchema.describe('Starting point for the range calculation.'),\n budgets: z\n .array(budgetSchema)\n .min(1)\n .describe(\n 'One or more budget constraints. Pass multiple for nested ranges (e.g. 10, 20, 30 min or km). Order does not matter — sorted internally.',\n ),\n theme: z\n .enum(['filled', 'inverted', 'outline'])\n .optional()\n .describe(\n 'Visual theme for the polygons. Default: outline (transparent fill with thick colored border). Use inverted for a donut (reachable highlighted, outside dimmed) or filled for solid polygons.',\n ),\n showOnMap: z.boolean().describe('Whether to render the range polygon(s) on the map.'),\n showOriginPin: z.boolean().optional().describe('Whether to drop a pin at the origin. Default: true.'),\n});\n\nconst rangeOutputSchema = z.object({\n rangeId: z.string(),\n originName: z.string(),\n budgets: z.array(budgetSchema),\n});\n\nexport const findReachableAreaOutputSchema = z.union([\n rangeOutputSchema,\n z.object({ status: z.enum(['not_found', 'no_results']) }),\n toolErrorSchema,\n]);\n\nexport const findReachableAreaDescription =\n 'Find the reachable area (isochrone/isodistance polygon) from an origin within time or distance budgets. ' +\n 'Pass multiple budgets for nested ranges (e.g. 10/20/30 min or 10/25/50 km). ' +\n 'Results stored in state; use recallRanges to retrieve. ' +\n 'showOnMap renders the polygons on the map. ' +\n 'status: not_found if origin cannot be resolved; no_results if API returns empty geometry.';\n\nconst computeReachableRanges = async (budgets: z.infer<typeof budgetSchema>[], origin: Position) => {\n const paramsArray: ReachableRangeParams[] = budgets.map((b) => ({\n origin,\n budget: { type: b.type as BudgetType, value: b.value },\n }));\n const result = await calculateReachableRanges(paramsArray);\n return result.features.length === 0 ? null : result;\n};\n\nconst displayRangeResults = async (\n state: ToolState,\n result: PolygonFeatures,\n theme: GeometryTheme,\n position: Position,\n originName: string,\n showOnMap: boolean,\n showOriginPin: boolean,\n) => {\n if (showOnMap) {\n const geomModule = await state.ranges.getGeometriesModule(theme);\n await geomModule.show(result);\n if (result.bbox) {\n state.baseMap.mapLibreMap.fitBounds(result.bbox, {\n padding: 50,\n });\n }\n }\n if (showOriginPin) {\n const originPlace: Place = {\n type: 'Feature',\n id: 'range-origin',\n geometry: { type: 'Point', coordinates: position },\n properties: {\n type: 'Point Address',\n address: { freeformAddress: originName },\n },\n };\n const placesModule = await state.ranges.getPlacesModule();\n await placesModule.show(originPlace);\n }\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeFindReachableArea = async (\n params: z.infer<typeof findReachableAreaSchema>,\n state: ToolState,\n): Promise<z.infer<typeof findReachableAreaOutputSchema>> => {\n const { origin, budgets, theme, showOnMap, showOriginPin = true } = params;\n\n try {\n const resolved = await resolveLocationInput(origin);\n if (!resolved) return { status: 'not_found' };\n const { position, name: originName, query: originQuery } = resolved;\n\n const sortedBudgets = [...budgets].sort((a, b) => b.value - a.value);\n\n const result = await computeReachableRanges(sortedBudgets, position);\n if (!result) return { status: 'no_results' };\n\n const resolvedTheme = theme ?? 'outline';\n\n const originEntry = originQuery ? { query: originQuery, position } : { position };\n const rangeId = state.ranges.addEntry({\n label: makeRangeLabel(sortedBudgets, originName),\n origin: originEntry,\n budgets: sortedBudgets,\n polygon: result,\n });\n\n await displayRangeResults(state, result, resolvedTheme, position, originName, showOnMap, showOriginPin);\n\n return { rangeId, originName, budgets: sortedBudgets };\n } catch (error) {\n return {\n error: `Reachable area calculation failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { geocodeOne } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel } from '../../utils/state-labels';\nimport { summarizePlace } from '../../utils/summarize';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Tool schema for geocoding (address to coordinates).\n */\nexport const geocodeSchema = z.object({\n query: z\n .string()\n .describe(\n 'Street or postal address to geocode. Do not use for landmarks, POIs, venues, stations, cities, neighborhoods, or general place-name queries.',\n ),\n waypointIndex: z.number().int().min(0).optional().describe('0=origin, last=destination, middle=stop'),\n});\n\n/** Output schema for the geocode tool. */\nexport const geocodeOutputSchema = z.union([\n placeOutputSchema.extend({\n waypointIndex: z.number().optional(),\n }),\n toolErrorSchema,\n]);\n\nexport const geocodeDescription =\n 'Convert an explicit street or postal address to geographic coordinates without showing anything on the map. ' +\n 'Use only for address-geocoding workflows. Do not use for landmarks, POIs, venues, stations, cities, neighborhoods, or general place-name queries. ' +\n 'For single-place display use locatePlace with show. For route or other non-display location resolution use locatePlace. ' +\n 'Optionally assigns the resolved address to a waypoint slot.';\n\n/**\n * Create the geocode tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGeocode = async (\n params: z.infer<typeof geocodeSchema>,\n state: ToolState,\n): Promise<z.infer<typeof geocodeOutputSchema>> => {\n const { query, waypointIndex } = params;\n try {\n const result = await geocodeOne(query);\n\n if (!result) {\n return { error: `No result found for \"${query}\"` };\n }\n\n state.places.addPlaceResult(result, makePlacesLabel(result, { query }));\n\n if (waypointIndex !== undefined) {\n state.routing.setWaypointAt(waypointIndex, result as unknown as WaypointLike);\n }\n\n return {\n ...summarizePlace(result),\n ...(waypointIndex !== undefined && { waypointIndex }),\n };\n } catch (error) {\n return { error: `Geocoding failed: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Language } from '@tomtom-org/maps-sdk/core';\nimport { getPOICategoryCodes } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-poi-category-codes tool. */\nexport const getPoiCategoryCodesOutputSchema = z.union([\n z.object({\n count: z.number().describe('Number of codes returned'),\n codes: z\n .array(z.string())\n .describe(\n 'POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. \"RESTAURANT\") also accepted by togglePOIs categories.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool input schema for fetching POI category codes.\n */\nexport const getPoiCategoryCodesSchema = z.object({\n filters: z\n .array(z.string())\n .optional()\n .describe(\n 'Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes.',\n ),\n language: z.string().describe('Locale e.g. \"en-GB\", \"fr-FR\", \"es-ES\", \"de-DE\".'),\n});\n\nexport const getPoiCategoryCodesDescription =\n 'Resolve natural language (e.g. \"gym\", \"italian food\", \"parking\") into POICategory codes for use in the discoverPlaces poiCategories parameter. Returns all codes when no filter is given.';\n\n/**\n * Create the get-poi-category-codes tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetPoiCategoryCodes = async (\n params: z.infer<typeof getPoiCategoryCodesSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getPoiCategoryCodesOutputSchema>> => {\n const { filters, language } = params;\n try {\n const codes = await getPOICategoryCodes({\n filters,\n language: language as Language,\n });\n return {\n count: codes.length,\n codes,\n };\n } catch (error) {\n return {\n error: `Failed to fetch POI category codes: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type Place, type TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport type { TrafficAreaAnalyticsParams } from '@tomtom-org/maps-sdk/services';\nimport { geometryData, trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';\nimport type { MultiPolygon, Polygon } from 'geojson';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { locationInputSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\n// ---------------------------------------------------------------------------\n// Constants (inline — the SDK only type-exports these)\n// ---------------------------------------------------------------------------\n\nconst METRICS = ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'] as const;\n\nconst FUNCTIONAL_ROAD_CLASSES = [\n 'MOTORWAY',\n 'MAJOR_ROAD',\n 'OTHER_MAJOR_ROAD',\n 'SECONDARY_ROAD',\n 'LOCAL_CONNECTING_ROAD',\n 'LOCAL_ROAD_HIGH_IMPORTANCE',\n 'LOCAL_ROAD',\n 'LOCAL_ROAD_MINOR_IMPORTANCE',\n 'OTHER_ROAD',\n] as const;\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nexport const metricsSchema = z.object({\n speed: z.number().optional(),\n freeFlowSpeed: z.number().optional(),\n congestionLevel: z.number().optional(),\n travelTime: z.number().optional(),\n networkLength: z.number().optional(),\n});\n\n/** Output schema for the get-traffic-area-analytics tool. */\nexport const getTrafficAreaAnalyticsOutputSchema = z.union([\n z.object({\n name: z.string().optional(),\n timezone: z.string().optional(),\n dateRange: z.object({ start: z.string(), end: z.string() }),\n baseData: metricsSchema,\n metrics: z.array(z.string()),\n tileCount: z.number(),\n availableGranularities: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fetching traffic area analytics.\n */\nexport const getTrafficAreaAnalyticsSchema = z.object({\n location: locationInputSchema\n .optional()\n .describe(\n 'Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Mutually exclusive with bbox and geometry.',\n ),\n bbox: z\n .array(z.number())\n .length(4)\n .optional()\n .describe(\n '[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Mutually exclusive with location.',\n ),\n showOnMap: z\n .boolean()\n .optional()\n .describe('Visualize the result immediately with default settings. Use showTrafficAreaAnalytics to customize.'),\n startDate: z\n .string()\n .optional()\n .describe(\"Start date 'YYYY-MM-DD'. Use with endDate for a continuous range (max 31 days).\"),\n endDate: z.string().optional().describe(\"End date 'YYYY-MM-DD' (inclusive). Defaults to today if omitted.\"),\n days: z\n .array(z.string())\n .optional()\n .describe(\n \"Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate.\",\n ),\n metrics: z\n .union([z.literal('all'), z.array(z.enum([...METRICS])).min(1)])\n .describe(\n \"Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'].\",\n ),\n functionalRoadClasses: z\n .union([z.literal('all'), z.array(z.enum([...FUNCTIONAL_ROAD_CLASSES]))])\n .optional()\n .describe(\"Road classes to include. 'all' (default) or array of specific classes.\"),\n hours: z\n .union([z.literal('all'), z.array(z.number().int().min(0).max(23))])\n .optional()\n .describe(\"Hours of the day (0-23) to include. 'all' (default) or array like [7,8,9,17,18] for rush hours.\"),\n});\n\nexport const getTrafficAreaAnalyticsDescription =\n 'Fetch historical traffic analytics for a geographic area (max 31 days). ' +\n 'Provide area via location (named city/region, resolved to boundary polygon) or bbox. ' +\n 'Fetch all metrics in a single call — visualization requires all metrics in one result. ' +\n 'Use city-level areas (~10km+) for meaningful data. ' +\n 'Returns period averages. Use showOnMap to visualize immediately, queryTrafficAnalytics for breakdowns.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Convert [minLng, minLat, maxLng, maxLat] to a GeoJSON Polygon.\nconst bboxToPolygon = (bbox: number[]): Polygon => {\n const [minLng, minLat, maxLng, maxLat] = bbox;\n return {\n type: 'Polygon',\n coordinates: [\n [\n [minLng, minLat],\n [maxLng, minLat],\n [maxLng, maxLat],\n [minLng, maxLat],\n [minLng, minLat],\n ],\n ],\n };\n};\n\n// Resolve the analytics area geometry from a location query or bbox.\nconst resolveGeometry = async (\n location: z.infer<typeof locationInputSchema> | undefined,\n bbox: number[] | undefined,\n): Promise<Polygon | MultiPolygon | { error: string }> => {\n if (location) {\n const resolved = await resolveLocationInput(location);\n if (!resolved) return { error: 'Could not resolve the provided location.' };\n if (Array.isArray(resolved.place)) {\n return {\n error: 'A bare position cannot define an analytics area. Provide a named location query instead.',\n };\n }\n // resolved.place is a Place at runtime (locatePlace always returns Place | null)\n const boundary = await geometryData({ geometries: [resolved.place as unknown as Place] });\n const boundaryGeometry = boundary?.features?.[0]?.geometry as Polygon | MultiPolygon | undefined;\n if (!boundaryGeometry) {\n return { error: `No boundary polygon found for \"${resolved.name}\". Try a bbox instead.` };\n }\n return boundaryGeometry;\n }\n if (!bbox) return { error: 'No area provided.' };\n return bboxToPolygon(bbox);\n};\n\n// Resolve which date parameters to forward to the SDK from the tool inputs.\nconst resolveDateParams = (\n days: string[] | undefined,\n startDate: string | undefined,\n endDate: string | undefined,\n): { startDate?: string; endDate?: string; days?: string[] } => {\n if (days && days.length > 0) return { days };\n if (startDate) return endDate ? { startDate, endDate } : { startDate };\n return {};\n};\n\n// Show analytics on the map with default settings, fitting the viewport.\nconst showAnalyticsOnMap = async (state: ToolState, result: TrafficAreaAnalytics): Promise<void> => {\n const analyticsModule = await state.traffic.getTrafficAreaAnalyticsModule();\n await analyticsModule.clear();\n await analyticsModule.show(result);\n const defaultMetric = result.properties?.metrics?.[0] ?? 'congestionLevel';\n analyticsModule.setMetric(defaultMetric);\n const bbox = bboxFromGeoJSON(result.features);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\n state.traffic.onAnalyticsShown?.(result, analyticsModule);\n};\n\n/** Format a Date to 'YYYY-MM-DD'. */\nexport const toDateString = (d: Date): string =>\n `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n\n/** Safely format a date value (Date object or string) to 'YYYY-MM-DD', or undefined if invalid. */\nexport const formatDate = (value: unknown): string | undefined => {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? undefined : toDateString(value);\n }\n if (typeof value === 'string') return value.slice(0, 10);\n return undefined;\n};\n\n/** Extract metrics from a timed entry, omitting undefined values. */\nexport const extractMetrics = (entry: Record<string, unknown>) => ({\n ...(entry.speed !== undefined && { speed: entry.speed as number }),\n ...(entry.freeFlowSpeed !== undefined && { freeFlowSpeed: entry.freeFlowSpeed as number }),\n ...(entry.congestionLevel !== undefined && { congestionLevel: entry.congestionLevel as number }),\n ...(entry.travelTime !== undefined && { travelTime: entry.travelTime as number }),\n ...(entry.networkLength !== undefined && { networkLength: entry.networkLength as number }),\n});\n\n/** Compact summary of the analytics result — just headline numbers + what's available for drill-down. */\nconst summarize = (\n result: TrafficAreaAnalytics,\n inputStartDate?: string,\n inputEndDate?: string,\n): z.infer<typeof getTrafficAreaAnalyticsOutputSchema> => {\n const region = result.features[0]?.properties;\n if (!region) {\n return {\n dateRange: { start: '', end: '' },\n baseData: {},\n metrics: [],\n tileCount: 0,\n availableGranularities: [],\n };\n }\n\n // SDK defaults to startDate=3 days ago, endDate=2 days ago when omitted\n const defaultStart = () => {\n const d = new Date();\n d.setDate(d.getDate() - 3);\n return toDateString(d);\n };\n const defaultEnd = () => {\n const d = new Date();\n d.setDate(d.getDate() - 2);\n return toDateString(d);\n };\n\n const start = inputStartDate ?? formatDate(result.properties?.startDate) ?? defaultStart();\n const end = inputEndDate ?? formatDate(result.properties?.endDate) ?? defaultEnd();\n\n const availableGranularities: string[] = [];\n if (region.timedData?.yearly?.length) availableGranularities.push('yearly');\n if (region.timedData?.monthly?.length) availableGranularities.push('monthly');\n if (region.timedData?.weekly?.length) availableGranularities.push('weekly');\n if (region.timedData?.daily?.length) availableGranularities.push('daily');\n if (region.timedData?.hourly?.length) availableGranularities.push('hourly');\n if (region.timedData?.average?.length) availableGranularities.push('average');\n\n return {\n ...(region.name && { name: region.name }),\n ...(region.timezone && { timezone: region.timezone }),\n dateRange: { start, end },\n baseData: region.baseData,\n metrics: result.properties?.metrics ?? [],\n tileCount: region.tiledData?.tiles?.length ?? 0,\n availableGranularities,\n };\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the get traffic area analytics tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetTrafficAreaAnalytics = async (\n params: z.infer<typeof getTrafficAreaAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getTrafficAreaAnalyticsOutputSchema>> => {\n const { location, bbox, showOnMap, startDate, endDate, days, metrics, functionalRoadClasses, hours } = params;\n\n if (!location && !bbox) {\n return { error: 'Provide a location or bbox to define the analytics area.' };\n }\n\n // Move Portal API key (different from standard TomTom key)\n const apiKey = process.env.MOVE_PORTAL_KEY;\n if (!apiKey) {\n return { error: 'MOVE_PORTAL_KEY environment variable is not set.' };\n }\n\n try {\n const resolvedGeometry = await resolveGeometry(location, bbox);\n if ('error' in resolvedGeometry) return resolvedGeometry;\n\n // Resolve dates — SDK defaults to startDate=3 days ago, endDate=2 days ago when omitted\n const dateParams = resolveDateParams(days, startDate, endDate);\n\n const result = await trafficAreaAnalytics({\n apiKey,\n ...dateParams,\n metrics,\n functionalRoadClasses: functionalRoadClasses ?? 'all',\n hours: hours ?? 'all',\n geometry: resolvedGeometry,\n } as TrafficAreaAnalyticsParams);\n\n state.traffic.setLastAreaAnalytics(result);\n\n if (showOnMap) {\n await showAnalyticsOnMap(state, result);\n }\n\n return summarize(result, dateParams.startDate, dateParams.endDate);\n } catch (error) {\n return {\n error: `Failed to get traffic area analytics: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport { trafficIncidentCategories } from '@tomtom-org/maps-sdk/core';\nimport { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-traffic-incidents tool. */\nexport const getTrafficIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n incidents: z.array(\n z.object({\n id: z.string(),\n category: z.string(),\n magnitudeOfDelay: z.string().describe('unknown|minor|moderate|major|indefinite'),\n timeValidity: z.string().describe('present|future'),\n events: z.array(\n z.object({\n description: z.string(),\n category: z.string(),\n }),\n ),\n from: z.string().optional(),\n to: z.string().optional(),\n roadNumbers: z.array(z.string()).optional(),\n lengthInMeters: z.number().optional(),\n delayInSeconds: z.number().optional(),\n startTime: z.date().optional(),\n endTime: z.date().optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fetching traffic incident details.\n */\nexport const getTrafficIncidentsSchema = z.object({\n bbox: z.optional(z.array(z.number())).describe('[minLng, minLat, maxLng, maxLat]'),\n ids: z.array(z.string()).optional().describe('Up to 100 incident IDs'),\n categoryFilter: z\n .array(z.enum([...trafficIncidentCategories]))\n .optional()\n .describe('Filter by incident category. If omitted, all categories are returned.'),\n timeValidityFilter: z\n .array(z.enum(['present', 'future']))\n .optional()\n .describe(\"default: ['present']\"),\n});\n\nexport const getTrafficIncidentsDescription =\n 'Fetch and analyze traffic incidents (present or future) by bbox or IDs without showing them on the map. Combine with toggleTrafficIncidents to highlight the most important ones in an area, or use getShownIncidents IDs to get full details of what is already rendered. Returns category, severity, delay, and road location.';\n\n/**\n * Create the get traffic incidents tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetTrafficIncidents = async (\n params: z.infer<typeof getTrafficIncidentsSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getTrafficIncidentsOutputSchema>> => {\n const { bbox, ids, categoryFilter, timeValidityFilter } = params;\n\n if (!bbox && !ids) {\n return { error: 'Provide either a bbox or a list of ids' };\n }\n\n try {\n const filters = {\n ...(categoryFilter && { categoryFilter }),\n ...(timeValidityFilter && { timeValidityFilter }),\n };\n const result = await trafficIncidentDetails(\n bbox ? { ...filters, bbox: bbox as BBox } : { ...filters, ids: ids as string[] },\n );\n\n if (!result.features.length) {\n return { count: 0, incidents: [] };\n }\n\n return {\n count: result.features.length,\n incidents: result.features.map((feature) => {\n const p = feature.properties;\n return {\n id: p.id,\n category: p.category,\n magnitudeOfDelay: p.magnitudeOfDelay,\n timeValidity: p.timeValidity,\n events: p.events.map((e) => ({ description: e.description, category: e.category })),\n from: p.from,\n to: p.to,\n roadNumbers: p.roadNumbers,\n lengthInMeters: p.lengthInMeters,\n delayInSeconds: p.delayInSeconds,\n startTime: p.startTime,\n endTime: p.endTime,\n };\n }),\n };\n } catch (error) {\n return {\n error: `Failed to get traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { extractMetrics, formatDate, metricsSchema, toDateString } from './get-traffic-area-analytics';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst timedEntrySchema = metricsSchema.extend({\n date: z.string().optional(),\n hour: z.number().optional(),\n year: z.number().optional(),\n month: z.number().optional(),\n week: z.number().optional(),\n day: z.number().optional(),\n});\n\nexport const queryTrafficAnalyticsSchema = z.object({\n granularity: z\n .enum(['daily', 'hourly', 'weekly', 'monthly', 'yearly', 'average'])\n .optional()\n .describe(\"Time granularity. Default: 'daily'. Use 'average' for day-of-week × hour patterns.\"),\n metric: z\n .enum(['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'])\n .optional()\n .describe('Filter to a single metric. Default: all requested metrics.'),\n startDate: z\n .string()\n .optional()\n .describe(\"Filter entries from this date (YYYY-MM-DD). Works with 'daily' and 'hourly'.\"),\n endDate: z\n .string()\n .optional()\n .describe(\"Filter entries up to this date (YYYY-MM-DD). Works with 'daily' and 'hourly'.\"),\n hourStart: z\n .number()\n .int()\n .min(0)\n .max(23)\n .optional()\n .describe('Filter hourly/average entries from this hour (0-23).'),\n hourEnd: z\n .number()\n .int()\n .min(0)\n .max(23)\n .optional()\n .describe('Filter hourly/average entries up to this hour (0-23).'),\n dayOfWeek: z\n .array(z.number().int().min(1).max(7))\n .optional()\n .describe(\"Filter 'average' entries by day of week (1=Mon…7=Sun).\"),\n});\n\n/** Output schema for the query-traffic-analytics tool. */\nexport const queryTrafficAnalyticsOutputSchema = z.union([\n z.object({\n granularity: z.string(),\n count: z.number(),\n entries: z.array(timedEntrySchema),\n currentVisualization: z\n .object({\n activeMetric: z.string().optional(),\n displayMode: z.string().optional(),\n })\n .optional(),\n }),\n toolErrorSchema,\n]);\n\nexport const queryTrafficAnalyticsDescription =\n 'Query cached traffic analytics (from getTrafficAreaAnalytics) without re-fetching. ' +\n 'Filter by granularity, date range, hour range, day of week, or metric. ' +\n 'Returns filtered entries plus the current visualization state (metric, mode displayed on map). ' +\n 'Use for follow-up questions: breakdowns, patterns, or checking what is currently shown.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Derive a date string for a daily/hourly entry using index fallback. */\nconst resolveDateStr = (\n entryDate: unknown,\n index: number,\n granularity: string,\n startDateStr?: string,\n): string | undefined => {\n const direct = formatDate(entryDate);\n if (direct) return direct;\n if (startDateStr && index >= 0) {\n const d = new Date(startDateStr);\n if (!Number.isNaN(d.getTime())) {\n const dayOffset = granularity === 'hourly' ? Math.floor(index / 24) : index;\n d.setDate(d.getDate() + dayOffset);\n return toDateString(d);\n }\n }\n return undefined;\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the query traffic analytics tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeQueryTrafficAnalytics = async (\n params: z.infer<typeof queryTrafficAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof queryTrafficAnalyticsOutputSchema>> => {\n const { granularity = 'daily', metric, startDate, endDate, hourStart, hourEnd, dayOfWeek } = params;\n\n const analytics = state.traffic.lastAreaAnalytics;\n if (!analytics) {\n return { error: 'No analytics data cached. Call getTrafficAreaAnalytics first.' };\n }\n\n const region = analytics.features[0]?.properties;\n if (!region) {\n return { error: 'Analytics result has no region data.' };\n }\n\n const timedData = region.timedData;\n const rawEntries = timedData?.[granularity as keyof typeof timedData];\n if (!rawEntries || !Array.isArray(rawEntries) || rawEntries.length === 0) {\n return {\n error: `No '${granularity}' data available. Available: ${Object.keys(timedData ?? {}).join(', ')}`,\n };\n }\n\n // Resolve start date for index-based date derivation\n const collectionStartDate = formatDate(analytics.properties?.startDate);\n\n // Map and filter entries\n let entries = rawEntries.map((entry, index) => {\n const mapped: Record<string, unknown> = {};\n\n // Temporal fields\n if (granularity === 'daily' || granularity === 'hourly') {\n const dateStr = resolveDateStr(entry.date, index, granularity, collectionStartDate);\n if (dateStr) mapped.date = dateStr;\n }\n if (entry.hour !== undefined) mapped.hour = entry.hour;\n if (entry.year !== undefined) mapped.year = entry.year;\n if (entry.month !== undefined) mapped.month = entry.month;\n if (entry.week !== undefined) mapped.week = entry.week;\n if (entry.day !== undefined) mapped.day = entry.day;\n\n // Metrics\n if (metric) {\n const val = (entry as Record<string, unknown>)[metric];\n if (val !== undefined) mapped[metric] = val;\n } else {\n Object.assign(mapped, extractMetrics(entry));\n }\n\n return mapped;\n });\n\n // Apply date range filter (daily/hourly)\n if (startDate) {\n entries = entries.filter((e) => !e.date || (e.date as string) >= startDate);\n }\n if (endDate) {\n entries = entries.filter((e) => !e.date || (e.date as string) <= endDate);\n }\n\n // Apply hour range filter (hourly/average)\n if (hourStart !== undefined) {\n entries = entries.filter((e) => e.hour === undefined || (e.hour as number) >= hourStart);\n }\n if (hourEnd !== undefined) {\n entries = entries.filter((e) => e.hour === undefined || (e.hour as number) <= hourEnd);\n }\n\n // Apply day-of-week filter (average)\n if (dayOfWeek && dayOfWeek.length > 0) {\n entries = entries.filter((e) => e.day === undefined || dayOfWeek.includes(e.day as number));\n }\n\n // Include current visualization state so LLM knows what's displayed\n const vizConfig = state.traffic.currentAnalyticsConfig;\n const currentVisualization = vizConfig\n ? {\n activeMetric: vizConfig.activeMetric,\n displayMode: vizConfig.displayMode,\n }\n : undefined;\n\n return {\n granularity,\n count: entries.length,\n entries: entries as z.infer<typeof timedEntrySchema>[],\n ...(currentVisualization && { currentVisualization }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { calculateRoute } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makeRoutesLabel } from '../../utils/state-labels';\nimport { summarizeRoutes } from '../../utils/summarize';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { buildCalculateRouteParams, resolveRouteWaypoints, showRouteOnMap } from './set-route-locations';\n\n/** Output schema for the remove-stop-from-route tool. */\nexport const removeStopFromRouteOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for removing a stop from an existing route.\n */\nexport const removeStopFromRouteSchema = z.object({\n stopIndex: z.number().int().describe('0=origin, last=destination; min 2 waypoints must remain'),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n});\n\nexport const removeStopFromRouteDescription =\n 'Remove a stop from the current route by index and always triggers a route recalculation. ' +\n 'Index 0 is the origin, last index is the destination — both must remain. ' +\n 'Requires an existing route (call setRouteLocations first).';\n\nconst validateRemoveStop = <T>(waypoints: T[] | null | undefined, stopIndex: number): string | T[] => {\n if (!waypoints || waypoints.length < 2) {\n return 'No existing waypoints available. Use calculate-route first to create a route with waypoints.';\n }\n if (stopIndex < 0 || stopIndex >= waypoints.length) {\n return `Invalid stop index ${stopIndex}. Valid range is 0 to ${waypoints.length - 1}.`;\n }\n if (waypoints.length <= 2) {\n return 'Cannot remove a stop: at least 2 waypoints (origin and destination) must remain.';\n }\n return waypoints;\n};\n\n/**\n * Create the remove stop from route tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeRemoveStopFromRoute = async (\n params: z.infer<typeof removeStopFromRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof removeStopFromRouteOutputSchema>> => {\n const { stopIndex, showOnMap } = params;\n try {\n const validated = validateRemoveStop(state.routing.currentWaypoints, stopIndex);\n if (typeof validated === 'string') return { error: validated };\n\n const updatedWaypointsPlaces = validated.filter((_, i) => i !== stopIndex);\n\n const waypoints = resolveRouteWaypoints(updatedWaypointsPlaces);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n const routes = await calculateRoute({\n locations: waypoints,\n ...buildCalculateRouteParams(state.routing.params),\n });\n\n state.routing.addRoutes(routes, waypoints, makeRoutesLabel(routes, waypoints));\n\n if (showOnMap) {\n await showRouteOnMap(state, routes, waypoints);\n }\n\n return summarizeRoutes(routes);\n } catch (error) {\n return {\n error: `Failed to remove stop from route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { reverseGeocode } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel } from '../../utils/state-labels';\nimport { summarizePlace } from '../../utils/summarize';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the reverse-geocode tool. */\nexport const reverseGeocodeOutputSchema = z.union([placeOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for reverse geocoding (coordinates to address).\n */\nexport const reverseGeocodeSchema = z.object({\n position: z.array(z.number()).length(2).describe('[longitude, latitude] GeoJSON position to reverse geocode'),\n});\n\nexport const reverseGeocodeDescription =\n 'Convert a [longitude, latitude] position to an address and normalized place summary. ' +\n 'Use after a map click, getViewport, getCurrentLocation, or any tool that returns coordinates. ' +\n 'Does NOT search by name — use locatePlace or discoverPlaces for text queries.';\n\n/**\n * Create the reverse geocode tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeReverseGeocode = async (\n params: z.infer<typeof reverseGeocodeSchema>,\n state: ToolState,\n): Promise<z.infer<typeof reverseGeocodeOutputSchema>> => {\n const { position } = params;\n const pos: Position = position as Position;\n try {\n const result = await reverseGeocode({ position: pos });\n\n if (result) {\n state.places.addPlaceResult(result, makePlacesLabel(result));\n return summarizePlace(result);\n }\n\n return { error: 'No result found for the given coordinates' };\n } catch (error) {\n return {\n error: `Reverse geocoding failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { alongRouteSearch } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel } from '../../utils/state-labels';\nimport { summarizePlaces } from '../../utils/summarize';\nimport { resolvePoiCategories, shownSchema, showPlacesSchema, showResultsOnMap } from '../shared';\nimport { placesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nconst noRouteSchema = z.object({ status: z.literal('no_route') });\n\nexport const searchAlongRouteOutputSchema = z.union([\n placesOutputSchema.extend({\n placesEntryId: z.string(),\n shown: shownSchema.optional(),\n }),\n noRouteSchema,\n toolErrorSchema,\n]);\n\nexport const searchAlongRouteSchema = z.object({\n query: z.string().optional().describe('Search query to filter POIs by name.'),\n poiCategories: z\n .array(z.string())\n .optional()\n .describe('POI category codes in CONSTANT_CASE. Call getPoiCategoryCodes to resolve natural-language names.'),\n routeId: z\n .string()\n .optional()\n .describe(\n 'Stable route entry ID (e.g. \"routes-1\"). Use recallRoutes to find the right ID; defaults to the most recent route.',\n ),\n maxDetourTimeSeconds: z.number().describe('Maximum detour time in seconds to reach a POI from the route.'),\n sortBy: z.enum(['detourTime', 'detourOffset']).optional().describe('Default: detourTime.'),\n limit: z.number().max(100).optional().describe('Max results. Default: 10.'),\n show: showPlacesSchema.optional().describe('Display options to render results on the map after search.'),\n});\n\nexport const searchAlongRouteDescription =\n 'Search for POIs along a calculated route, ranked by detour cost. ' +\n 'Reads the route from state — call setRouteLocations first. ' +\n 'Use recallRoutes to find the ID of a specific route; defaults to the most recent. ' +\n 'Stores results in places history; pass the returned placesEntryId to showPlaces({ id }) to display them. ' +\n 'status: no_route if no route exists in state.';\n\nconst resolveRouteEntry = (state: ToolState, routeId: string | undefined) => {\n const entries = state.routing.entries;\n if (entries.length === 0) return null;\n\n const entry = routeId ? entries.find((e) => e.id === routeId) : entries.at(-1);\n if (!entry) return `No route found with id \"${routeId}\". Use recallRoutes to list available routes.`;\n\n const routeFeature = entry.data.features[0];\n if (!routeFeature) return 'Route entry has no geometry.';\n\n return { entry, routeFeature };\n};\n\nconst executeAlongRouteSearch = async (\n params: {\n query?: string;\n poiCategories?: string[];\n maxDetourTimeSeconds: number;\n sortBy?: 'detourTime' | 'detourOffset';\n limit?: number;\n },\n routeFeature: Parameters<typeof alongRouteSearch>[0]['route'],\n) => {\n const { query, poiCategories, maxDetourTimeSeconds, sortBy, limit } = params;\n const resolvedPoiCategories = await resolvePoiCategories(poiCategories);\n return alongRouteSearch({\n route: routeFeature,\n maxDetourTimeSeconds,\n ...(query && { query }),\n ...(resolvedPoiCategories && { poiCategories: resolvedPoiCategories }),\n ...(sortBy && { sortBy }),\n ...(limit !== undefined && { limit }),\n });\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeSearchAlongRoute = async (\n params: z.infer<typeof searchAlongRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof searchAlongRouteOutputSchema>> => {\n const { query, poiCategories, routeId, maxDetourTimeSeconds, sortBy, limit, show } = params;\n\n const resolved = resolveRouteEntry(state, routeId);\n if (resolved === null) return { status: 'no_route' };\n if (typeof resolved === 'string') return { error: resolved };\n const { entry, routeFeature } = resolved;\n\n try {\n const result = await executeAlongRouteSearch(\n { query, poiCategories, maxDetourTimeSeconds, sortBy, limit },\n routeFeature,\n );\n\n const label = makePlacesLabel(result, { query, poiCategories, routeLabel: entry.label });\n const placesEntryId = state.places.addPlaceResult(result, label);\n\n const shown = show ? await showResultsOnMap(state, result, placesEntryId, show) : undefined;\n\n return {\n ...summarizePlaces(result),\n placesEntryId,\n ...(shown && { shown }),\n };\n } catch (error) {\n return {\n error: `Along-route search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Avoidable, avoidableTypes } from '@tomtom-org/maps-sdk/core';\nimport { calculateRoute, routeTypes } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makeRoutesLabel } from '../../utils/state-labels';\nimport { summarizeRoutes } from '../../utils/summarize';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { buildCalculateRouteParams, resolveRouteWaypoints, showRouteOnMap } from './set-route-locations';\n\nexport const costModelSchema = z.object({\n routeType: z.enum(routeTypes).optional().describe('fast|short|efficient|thrilling'),\n traffic: z.enum(['live', 'historical']).optional().describe('live|historical'),\n avoid: z\n .array(z.enum(avoidableTypes as unknown as [Avoidable, ...Avoidable[]]))\n .optional()\n .describe(\n 'tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones',\n ),\n avoidAreas: z\n .array(z.array(z.number()).describe('GeoJSON bbox [minLng, minLat, maxLng, maxLat]'))\n .max(10)\n .optional()\n .describe('Up to 10 bounding boxes to bypass.'),\n});\n\nexport const whenSchema = z.object({\n option: z.enum(['departAt', 'arriveBy']),\n date: z.string().describe('ISO 8601'),\n});\n\n/** Output schema for set-route-parameters. */\nexport const setRouteParametersOutputSchema = z.union([\n routesOutputSchema,\n z.object({ success: z.literal(true) }),\n toolErrorSchema,\n]);\n\n/** Input schema for set-route-parameters. */\nexport const setRouteParametersSchema = z.object({\n maxAlternatives: z.number().int().min(0).max(5).optional().describe('0–5, default 0'),\n costModel: costModelSchema.optional(),\n when: whenSchema.optional().describe('Omit to depart now.'),\n showOnMap: z.boolean().describe('Show the route and waypoints on the map after recalculation.'),\n});\n\nexport const setRouteParametersDescription =\n 'Set route planning parameters: number of alternatives, cost model (route type, traffic, avoidances), and departure/arrival time. ' +\n 'Triggers a route recalculation automatically if origin and destination are already set. ' +\n 'Can be called in parallel with setRouteLocations. ' +\n 'Only car/driving routes are supported.';\n\n/**\n * Create the set-route-parameters tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeSetRouteParameters = async (\n params: z.infer<typeof setRouteParametersSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setRouteParametersOutputSchema>> => {\n const { showOnMap, ...routeParams } = params;\n try {\n state.routing.setParams(routeParams);\n\n const waypoints = resolveRouteWaypoints(state.routing.planningSlots);\n if (waypoints) {\n const routes = await calculateRoute({\n locations: waypoints,\n ...buildCalculateRouteParams(state.routing.params),\n });\n state.routing.addRoutes(routes, waypoints, makeRoutesLabel(routes, waypoints));\n if (showOnMap) {\n await showRouteOnMap(state, routes, waypoints);\n }\n return summarizeRoutes(routes);\n }\n\n return { success: true };\n } catch (error) {\n return {\n error: `Failed to set route parameters: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeWaypoint, waypointSummarySchema } from '../../utils/summarize';\n\nexport const getCurrentWaypointsSchema = z.object({\n slotIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot.'),\n includeEmptySlots: z\n .boolean()\n .optional()\n .describe('Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses.'),\n offset: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Pagination offset within the filtered slot list. Ignored when slotIndex is provided.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(25)\n .optional()\n .describe('Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.'),\n});\n\nexport const getCurrentWaypointsOutputSchema = z.object({\n totalSlots: z.number().describe('Total waypoint slots currently staged'),\n filledSlots: z.number().describe('Number of staged slots that already contain coordinates'),\n matchedCount: z.number().describe('Total slots matching the request before pagination'),\n returnedCount: z.number().describe('Number of slots returned in this page'),\n offset: z.number().describe('Offset applied to the filtered slot list'),\n hasMore: z.boolean().describe('True when more slots can be fetched with a higher offset'),\n nextOffset: z.number().optional().describe('Offset to request the next page when hasMore=true'),\n waypoints: z.array(waypointSummarySchema),\n});\n\nexport const getCurrentWaypointsDescription =\n 'Read the waypoint slots currently staged for the next route calculation without recalculating anything. ' +\n 'Use for follow-up questions like \"what coordinates are staged now?\", \"which slots are filled?\", or \"what is destination slot 1?\". ' +\n 'Highly specific rules: slotIndex returns one known slot; otherwise results are paged, default to 5 slots, and empty slots are omitted unless includeEmptySlots=true to keep payloads compact. ' +\n 'Does not call any service.';\n\nexport const executeGetCurrentWaypoints = async (\n params: z.infer<typeof getCurrentWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getCurrentWaypointsOutputSchema>> => {\n const { slotIndex, includeEmptySlots = false, offset = 0, limit = 5 } = params;\n const slots = state.routing.planningSlots.map((waypoint, index) => {\n const normalized = summarizeWaypoint(waypoint);\n return normalized ? { ...normalized, slotIndex: index } : { slotIndex: index, isFilled: false };\n });\n const filteredSlots = includeEmptySlots ? slots : slots.filter((slot) => slot.isFilled);\n const hasExactSlot = slotIndex !== undefined;\n const selected = hasExactSlot\n ? filteredSlots.filter((slot) => slot.slotIndex === slotIndex)\n : filteredSlots.slice(offset, offset + limit);\n const nextOffset = offset + selected.length;\n\n return {\n totalSlots: slots.length,\n filledSlots: slots.filter((slot) => slot.isFilled).length,\n matchedCount: hasExactSlot ? selected.length : filteredSlots.length,\n returnedCount: selected.length,\n offset: hasExactSlot ? 0 : offset,\n hasMore: !hasExactSlot && nextOffset < filteredSlots.length,\n ...(!hasExactSlot && nextOffset < filteredSlots.length && { nextOffset }),\n waypoints: selected,\n };\n};\n","import { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils/summarize';\nimport { placesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallPlacesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"places-3\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n places: placesOutputSchema,\n});\n\nexport const recallPlacesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallPlacesDescription =\n 'Retrieve places and search results from session history. ' +\n 'ALWAYS use this when referencing places from earlier in the session — do not re-search by name. ' +\n 'Returns entries with stable IDs accepted by showPlaces, and exact coordinates usable in setRouteLocations. ' +\n 'Step 1: call with no parameters to list all entries and find the right ID. Do NOT guess IDs. ' +\n 'Step 2: call with id to retrieve a specific entry with coordinates. ' +\n 'Does not call any service.';\n\nexport const executeRecallPlaces = async (\n params: z.infer<typeof recallPlacesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallPlacesOutputSchema>> => {\n const { id } = params;\n\n if (!id) {\n const entries = [...state.places.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp }));\n return { entries };\n }\n\n const entry = state.places.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n const places = summarizePlaces(entry.data);\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n places,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallRangesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"ranges-0\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n origin: z.object({\n query: z.string().optional(),\n position: z.tuple([z.number(), z.number()]),\n }),\n budgets: z.array(z.object({ type: z.string(), value: z.number() })),\n});\n\nexport const recallRangesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallRangesDescription =\n 'Retrieve a stored reachable range result from session history. ' +\n 'Step 1: call with no parameters to list all entries and find the right ID. Do NOT guess IDs. ' +\n 'Step 2: call with id to retrieve origin and budgets. ' +\n 'Does not call any service.';\n\nexport const executeRecallRanges = async (\n params: z.infer<typeof recallRangesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallRangesOutputSchema>> => {\n const { id } = params;\n\n if (!id) {\n const entries = [...state.ranges.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp }));\n return { entries };\n }\n\n const entry = state.ranges.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n origin: { ...entry.origin, position: entry.origin.position as [number, number] },\n budgets: entry.budgets,\n };\n};\n","import { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeRoutes, summarizeWaypoint, waypointSummarySchema } from '../../utils/summarize';\nimport { costModelSchema, whenSchema } from '../services/set-route-parameters';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallRoutesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"routes-1\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n});\n\nconst routeParamsSchema = z.object({\n maxAlternatives: z.number().optional(),\n costModel: costModelSchema.optional(),\n when: whenSchema.optional(),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n routes: routesOutputSchema,\n waypoints: z.array(\n z.object({\n position: z.array(z.number()).length(2).describe('[longitude, latitude]'),\n }),\n ),\n params: routeParamsSchema,\n});\n\nexport const recallRoutesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallRoutesDescription =\n 'Retrieve calculated routes from session history. ' +\n 'ALWAYS use this when referencing routes from earlier in the session. ' +\n 'Entries have stable IDs that showRoute accepts. ' +\n 'Step 1: call with no parameters to list all entries and find the right ID. Do NOT guess IDs. ' +\n 'Step 2: call with id to retrieve a specific entry with route details and params. ' +\n 'Does not call any service.';\n\nexport const executeRecallRoutes = async (\n params: z.infer<typeof recallRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallRoutesOutputSchema>> => {\n const { id } = params;\n\n if (!id) {\n const entries = [...state.routing.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp }));\n return { entries };\n }\n\n const entry = state.routing.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n const summarized = summarizeRoutes(entry.data);\n const waypoints = entry.waypoints\n .map((wp) => summarizeWaypoint(wp))\n .filter((wp): wp is z.infer<typeof waypointSummarySchema> => wp !== null)\n .map((wp) => ({ position: wp.position ?? [0, 0] }));\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n routes: summarized,\n waypoints,\n params: entry.params as z.infer<typeof routeParamsSchema>,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the clear-map tool. */\nexport const clearMapOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for clearing map features.\n */\nexport const clearMapSchema = z.object({\n layers: z\n .array(z.enum(['places', 'routes', 'geometries', 'analytics']))\n .optional()\n .describe('Layers to clear (default: all). Includes traffic area analytics visualization.'),\n});\n\nexport const clearMapDescription =\n 'Remove displayed features from the map (places, routes, geometries, analytics). Use to clean up before showing new results or to reset the map.';\n\n/**\n * Execute clear map.\n */\nexport const executeClearMap = async (params: z.infer<typeof clearMapSchema>, state: ToolState) => {\n const { layers } = params;\n try {\n const clearAll = !layers || layers.length === 0;\n\n if (clearAll || layers.includes('places')) {\n await state.places.clearShownEntries();\n }\n\n if (clearAll || layers.includes('routes')) {\n if (state.routing.routingModule) {\n await state.routing.routingModule.clearRoutes();\n await state.routing.routingModule.clearWaypoints();\n }\n }\n\n if (clearAll || layers.includes('geometries')) {\n if (state.ranges.geometriesModule) {\n await state.ranges.geometriesModule.clear();\n }\n }\n\n if (clearAll || layers.includes('analytics')) {\n if (state.traffic.trafficAreaAnalyticsModule) {\n await state.traffic.trafficAreaAnalyticsModule.clear();\n }\n }\n\n return {\n success: true,\n cleared: layers || ['places', 'routes', 'geometries', 'analytics'],\n };\n } catch (error) {\n return {\n error: `Failed to clear map: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n getSectionBBox,\n type Route,\n type SectionProps,\n type SectionType,\n sectionTypes,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the fit-route-section tool. */\nexport const fitRouteSectionOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fitting route section bounds.\n */\nexport const fitRouteSectionSchema = z.object({\n sectionType: z.enum(sectionTypes as [SectionType, ...SectionType[]]).describe('e.g. country|traffic|motorway|toll'),\n id: z.string().describe('From a shown route section'),\n padding: z.number().optional().describe('pixels, default: 50'),\n});\n\nexport const fitRouteSectionDescription =\n 'Fit the map camera to show a specific route section by type and ID. ' +\n 'Use getShownRouteSections to obtain valid section IDs. ' +\n 'Requires a route to be shown on the map.';\n\n/**\n * Execute fit route section.\n */\nexport const executeFitRouteSection = async (params: z.infer<typeof fitRouteSectionSchema>, state: ToolState) => {\n const { sectionType, id, padding = 50 } = params;\n\n try {\n // Get the currently shown routes from the map\n if (!state.routing.routingModule) {\n return {\n error: 'No routing module initialized - no routes shown on map',\n };\n }\n\n const shown = state.routing.routingModule.getShown();\n\n if (!shown.mainLines || shown.mainLines.features.length === 0) {\n return {\n error: 'No routes are currently displayed on the map. Please show a route first before fitting to a section.',\n };\n }\n\n const shownRoutes = shown.mainLines.features;\n\n // Find the section in the shown routes\n let targetRoute: Route | undefined;\n let targetSection: { id: string; startPointIndex: number; endPointIndex: number } | undefined;\n\n for (const route of shownRoutes) {\n const sections = route.properties.sections[sectionType as SectionType];\n if (sections && Array.isArray(sections)) {\n const foundSection = sections.find((s) => s.id === id);\n if (\n foundSection &&\n typeof foundSection.startPointIndex === 'number' &&\n typeof foundSection.endPointIndex === 'number'\n ) {\n targetSection = foundSection as {\n id: string;\n startPointIndex: number;\n endPointIndex: number;\n };\n targetRoute = route;\n break;\n }\n }\n }\n\n if (!targetRoute || !targetSection) {\n return {\n error: `Could not find a section with type \"${sectionType}\" and id \"${id}\" in any displayed route. Check the section type and id are correct.`,\n };\n }\n\n // Calculate the bbox for the section\n const bbox = getSectionBBox(targetRoute, targetSection as SectionProps);\n\n if (!bbox) {\n return {\n error: 'Could not calculate bounding box for the section. The section may have invalid indices.',\n };\n }\n\n // Fit the map to the section bbox\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding });\n\n return { success: true };\n } catch (error) {\n return {\n error: `Failed to fit route section: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-incidents tool. */\nexport const getShownIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n trafficIncidents: z.array(\n z.object({\n id: z.string(),\n category: z.string(),\n magnitudeOfDelay: z.string().describe('unknown|minor|moderate|major|indefinite'),\n description: z.string().optional(),\n delayInSeconds: z.number().optional(),\n roadCategory: z.string().optional(),\n averageSpeedKmph: z.number().optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown traffic incidents on map.\n */\nexport const getShownIncidentsSchema = z.object({});\n\nexport const getShownIncidentsDescription =\n 'Get the real-time traffic incidents currently visible in the map viewport (not from service, but what is actually rendered on the map).';\n\n/**\n * Execute get shown incidents.\n */\nexport const executeGetShownIncidents = async (\n _params: z.infer<typeof getShownIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownIncidentsOutputSchema>> => {\n try {\n const trafficIncidentsModule = await state.traffic.getTrafficIncidentsModule();\n const shown = trafficIncidentsModule.getShown();\n\n if (shown.trafficIncidents.length === 0) {\n return { error: 'No traffic incidents currently shown on the map' };\n }\n\n return {\n count: shown.trafficIncidents.length,\n trafficIncidents: shown.trafficIncidents.map((incident) => ({\n id: incident.properties.id,\n category: incident.properties.category,\n magnitudeOfDelay: incident.properties.magnitudeOfDelay,\n description: incident.properties.description,\n delayInSeconds: incident.properties.delayInSeconds,\n roadCategory: incident.properties.roadCategory,\n averageSpeedKmph: incident.properties.averageSpeedKmph,\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get shown incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-places tool. */\nexport const getShownPlacesOutputSchema = z.union([\n z.object({\n count: z.number().describe('Total feature count across all shown entries.'),\n entries: z.array(\n z.object({\n id: z.string(),\n label: z.string(),\n featureCount: z.number(),\n features: z.array(\n z.object({\n name: z.string().optional(),\n address: z.string().optional(),\n position: z.array(z.number()).describe('[lng, lat]'),\n }),\n ),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown places on map.\n */\nexport const getShownPlacesSchema = z.object({});\n\nexport const getShownPlacesDescription =\n 'List the place entries currently shown on the map, grouped by `placesEntryId` with labels and a feature preview. ' +\n 'Use to check which categories are visible before calling `managePlaces`, or when the user asks \"what is on the map\". ' +\n 'Positions are [longitude, latitude].';\n\nconst FEATURE_PREVIEW_SIZE = 5;\n\n/**\n * Execute get shown places.\n */\nexport const executeGetShownPlaces = async (_params: z.infer<typeof getShownPlacesSchema>, state: ToolState) => {\n try {\n const shownIds = state.places.shownEntryIds;\n if (shownIds.size === 0) {\n return { count: 0, entries: [] };\n }\n\n const entries = state.places.entries\n .filter((entry) => shownIds.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n label: entry.label,\n featureCount: entry.data.length,\n features: entry.data.slice(0, FEATURE_PREVIEW_SIZE).map((feature) => ({\n name: feature.properties.poi?.name,\n address: feature.properties.address?.freeformAddress,\n position: feature.geometry.coordinates,\n })),\n }));\n\n return {\n count: entries.reduce((total, entry) => total + entry.featureCount, 0),\n entries,\n };\n } catch (error) {\n return {\n error: `Failed to get shown places: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { SectionsProps, SectionType } from '@tomtom-org/maps-sdk/core';\nimport { sectionTypes } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-route-sections tool. */\nexport const getShownRouteSectionsOutputSchema = z.union([\n z.object({\n sectionType: z.string(),\n routes: z.array(\n z.object({\n routeIndex: z.number().describe('0=primary, 1+=alternative'),\n sections: z.array(z.unknown()).describe('startPointIndex/endPointIndex omitted'),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the get-shown-route-sections tool. */\nexport const getShownRouteSectionsSchema = z.object({\n sectionType: z\n .enum(sectionTypes as [SectionType, ...SectionType[]])\n .describe(\n 'carpool|carTrain|country|ferry|importantRoadStretch|lanes|leg|lowEmissionZone|motorway|pedestrian|roadShields|speedLimit|toll|tollVignette|traffic|tunnel|unpaved|urban|vehicleRestricted',\n ),\n});\n\nexport const getShownRouteSectionsDescription =\n 'Get sections of a specific type (e.g. toll, motorway, country, traffic) from the currently shown routes. Returns section data without coordinate point indices.';\n\n/**\n * Execute get shown route sections.\n */\nexport const executeGetShownRouteSections = async (\n params: z.infer<typeof getShownRouteSectionsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownRouteSectionsOutputSchema>> => {\n const { sectionType } = params;\n try {\n if (!state.routing.routingModule) {\n return { error: 'No routing module initialized - no routes shown on map' };\n }\n\n const shown = state.routing.routingModule.getShown();\n\n if (!shown.mainLines || shown.mainLines.features.length === 0) {\n return { error: 'No routes currently shown on the map' };\n }\n\n const routes = shown.mainLines.features\n .map((route) => {\n const sectionArray = (route.properties.sections as SectionsProps)[sectionType as keyof SectionsProps];\n\n if (!sectionArray || sectionArray.length === 0) return null;\n\n return {\n routeIndex: route.properties.index,\n sections: sectionArray.map((section) => {\n const {\n startPointIndex: _s,\n endPointIndex: _e,\n ...rest\n } = section as Record<string, unknown> & {\n startPointIndex?: unknown;\n endPointIndex?: unknown;\n };\n return rest;\n }),\n };\n })\n .filter((r) => r !== null);\n\n if (routes.length === 0) {\n return { error: `No \"${sectionType}\" sections found on any currently shown route` };\n }\n\n return { sectionType, routes };\n } catch (error) {\n return {\n error: `Failed to get shown route sections: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-route-traffic-incidents tool. */\nexport const getShownRouteTrafficIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n trafficIncidents: z.array(\n z.object({\n id: z.string(),\n magnitudeOfDelay: z.string().describe('unknown|minor|moderate|major|indefinite'),\n title: z.string().optional(),\n delayInSeconds: z.number().optional(),\n effectiveSpeedInKmh: z.number().optional(),\n categories: z.array(z.string()).optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown route traffic incidents on map.\n */\nexport const getShownRouteTrafficIncidentsSchema = z.object({});\n\nexport const getShownRouteTrafficIncidentsDescription =\n 'Get the route traffic incidents currently displayed on the map (not from service, but what is actually shown). Each section has an id.';\n\n/**\n * Execute get shown route traffic incidents.\n */\nexport const executeGetShownRouteTrafficIncidents = async (\n _params: z.infer<typeof getShownRouteTrafficIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownRouteTrafficIncidentsOutputSchema>> => {\n try {\n if (!state.routing.routingModule) {\n return { error: 'No routing module initialized - no route traffic incidents shown on map' };\n }\n\n const shown = state.routing.routingModule.getShown();\n\n if (!shown.incidents || shown.incidents.features.length === 0) {\n return { error: 'No route traffic incidents currently shown on the map' };\n }\n\n return {\n count: shown.incidents.features.length,\n trafficIncidents: shown.incidents.features.map((incident) => ({\n id: incident.properties.id,\n magnitudeOfDelay: incident.properties.magnitudeOfDelay,\n title: incident.properties.title,\n delayInSeconds: incident.properties.delayInSeconds,\n effectiveSpeedInKmh: incident.properties.effectiveSpeedInKmh,\n categories: incident.properties.categories,\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get shown route traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeRoutes } from '../../utils/summarize';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-routes tool. */\nexport const getShownRoutesOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for getting shown routes on map.\n */\nexport const getShownRoutesSchema = z.object({});\n\nexport const getShownRoutesDescription =\n 'Get routes currently shown on the map. ' +\n 'Reads rendered map state (not plugin service history). Returns routes currently visible on map — use after showRoute.';\n\n/**\n * Execute get shown routes.\n */\nexport const executeGetShownRoutes = async (\n _params: z.infer<typeof getShownRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownRoutesOutputSchema>> => {\n try {\n if (!state.routing.routingModule) {\n return { count: 0, routes: [] };\n }\n\n const shown = state.routing.routingModule.getShown();\n\n if (!shown.mainLines || shown.mainLines.features.length === 0) {\n return { count: 0, routes: [] };\n }\n\n return summarizeRoutes(shown.mainLines);\n } catch (error) {\n return {\n error: `Failed to get shown routes: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-waypoints tool. */\nexport const getShownWaypointsOutputSchema = z.union([\n z.object({\n count: z.number(),\n waypoints: z.array(\n z.object({\n name: z.unknown().optional(),\n position: z.array(z.number()).describe('[lng, lat]'),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown waypoints on map.\n */\nexport const getShownWaypointsSchema = z.object({});\n\nexport const getShownWaypointsDescription =\n 'Get origin/stop/destination waypoints shown on the map. Context-dependent: requires waypoints shown on the map. ' +\n 'Returns positions as [longitude, latitude].';\n\n/**\n * Execute get shown waypoints.\n */\nexport const executeGetShownWaypoints = async (\n _params: z.infer<typeof getShownWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownWaypointsOutputSchema>> => {\n try {\n const shown = (await state.routing.getRoutingModule()).getShown();\n\n if (!shown.waypoints || shown.waypoints.features.length === 0) {\n return { error: 'No waypoints currently shown on the map' };\n }\n\n return {\n count: shown.waypoints.features.length,\n waypoints: shown.waypoints.features.map((w) => ({\n name: w.properties.name,\n position: w.geometry.coordinates,\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get shown waypoints: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-standard-map-styles tool. */\nexport const getStandardMapStylesOutputSchema = z.union([\n z.object({\n styles: z.array(z.string()).readonly(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting standard map styles.\n */\nexport const getStandardMapStylesSchema = z.object({});\n\nexport const getStandardMapStylesDescription =\n 'Get the list of available standard map style IDs. If you need more fine styling control, refer to getMapStyleLayers.';\n\n/**\n * Execute get standard map styles.\n */\nexport const executeGetStandardMapStyles = async (\n _params: z.infer<typeof getStandardMapStylesSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getStandardMapStylesOutputSchema>> => {\n try {\n return {\n styles: standardStyleIDs,\n };\n } catch (error) {\n return {\n error: `Failed to get standard map styles: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst shownEntrySummarySchema = z.object({\n id: z.string(),\n label: z.string(),\n featureCount: z.number(),\n});\n\nexport const managePlacesOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z.array(shownEntrySummarySchema).describe('Entries currently on the map, with their labels.'),\n actuallyAdded: z\n .array(z.string())\n .describe('Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op.'),\n actuallyRemoved: z\n .array(z.string())\n .describe('Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op.'),\n }),\n toolErrorSchema,\n]);\n\nexport const managePlacesSchema = z\n .object({\n add: z.array(z.string()).optional().describe('Entry ids to show (e.g. [\"places-1\"]).'),\n remove: z.array(z.string()).optional().describe('Entry ids to hide.'),\n addMatching: z.array(z.string()).optional().describe('Category labels to show.'),\n removeMatching: z.array(z.string()).optional().describe('Category labels to hide.'),\n clear: z.boolean().optional().describe('Hide every displayed entry.'),\n fitBounds: z.boolean().optional().describe('Pan/zoom to fit the resulting display. Default: false.'),\n })\n .refine(\n (data) =>\n data.add !== undefined ||\n data.remove !== undefined ||\n data.addMatching !== undefined ||\n data.removeMatching !== undefined ||\n data.clear === true,\n { message: 'managePlaces requires at least one of: add, remove, addMatching, removeMatching, clear.' },\n );\n\nexport const managePlacesDescription =\n 'Change which places entries are displayed on the map. ' +\n 'Target entries by id (`add` / `remove`) or by category label (`addMatching` / `removeMatching`). ' +\n '`clear` hides all. Does not delete from history.';\n\n/** Resolve category terms to entry ids by case-insensitive substring match on the entry label. */\nconst resolveMatchingIds = (state: ToolState, terms: readonly string[]): string[] => {\n if (terms.length === 0) return [];\n const needles = terms.map((term) => term.toLowerCase());\n const matched = new Set<string>();\n for (const entry of state.places.entries) {\n const label = entry.label.toLowerCase();\n if (needles.some((needle) => label.includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\nexport const executeManagePlaces = async (\n params: z.infer<typeof managePlacesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof managePlacesOutputSchema>> => {\n const { add, remove, addMatching, removeMatching, clear, fitBounds = false } = params;\n try {\n // Resolve label-matching selectors to concrete ids, merge with id-based selectors.\n const resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n const knownIds = new Set(state.places.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown places entry ids: ${unknownAdd.join(', ')}. Use recallPlaces to list available entries.`,\n };\n }\n\n // Snapshot the shown set BEFORE mutation so we can report diffs accurately.\n const beforeShown = new Set(state.places.shownEntryIds);\n\n if (clear) {\n await state.places.clearShownEntries();\n }\n if (resolvedRemove.length > 0) {\n await state.places.removeShownEntries(resolvedRemove);\n }\n if (resolvedAdd.length > 0) {\n await state.places.addShownEntries(resolvedAdd);\n }\n\n const afterShown = state.places.shownEntryIds;\n const actuallyAdded = resolvedAdd.filter((id) => !beforeShown.has(id) && afterShown.has(id));\n const actuallyRemoved = resolvedRemove.filter((id) => beforeShown.has(id) && !afterShown.has(id));\n\n const shownFeatures = state.places.getShownFeatures();\n if (fitBounds && shownFeatures.length > 0) {\n const bbox = bboxFromGeoJSON(shownFeatures);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding: 50 });\n }\n }\n\n const shownWithLabels = state.places.entries\n .filter((entry) => afterShown.has(entry.id))\n .map((entry) => ({ id: entry.id, label: entry.label, featureCount: entry.data.length }));\n\n return {\n success: true as const,\n totalShown: shownFeatures.length,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n };\n } catch (error) {\n return {\n error: `managePlaces failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { Language, TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the set-language tool. */\nexport const setLanguageOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n language: z.string(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting language.\n */\nexport const setLanguageSchema = z.object({\n language: z.string().describe('e.g. \"en-US\", \"fr-FR\"'),\n});\n\nexport const setLanguageDescription =\n 'Set the language used for map labels (place names, road names, POI labels) and for all subsequent service API calls (search results, geocoding, routing instructions, etc.). ' +\n 'Both the rendered map text and future service responses will use the new language.';\n\n/**\n * Execute set language.\n */\nexport const executeSetLanguage = async (params: z.infer<typeof setLanguageSchema>, state: ToolState) => {\n const { language } = params;\n try {\n TomTomConfig.instance.put({ language: language as Language });\n state.baseMap.ttMap.setLanguage(language as Language);\n\n return {\n success: true,\n language,\n };\n } catch (error) {\n return {\n error: `Failed to set language: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the setMapStandardStyle tool. */\nexport const setMapStandardStyleOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n style: z.string(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting a standard map style.\n */\nexport const setMapStandardStyleSchema = z.object({\n style: z.enum(standardStyleIDs).describe('Map visual theme'),\n});\n\nexport const setMapStandardStyleDescription =\n 'Set the map style to a standard preset (light, dark, mono, driving, satellite, etc). For detailed or incremental style tweaking use getMapStyleLayers, setLayoutProperties, and setPaintProperties.';\n\n/**\n * Execute set map standard style.\n */\nexport const executeSetMapStandardStyle = async (\n params: z.infer<typeof setMapStandardStyleSchema>,\n state: ToolState,\n) => {\n const { style } = params;\n try {\n state.baseMap.ttMap.setStyle(style);\n\n return {\n success: true,\n style,\n };\n } catch (error) {\n return {\n error: `Failed to set map style: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the setRouteTheme tool. */\nexport const setRouteThemeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n theme: z.object({\n mainColor: z.string().optional(),\n }),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting route theme.\n */\nexport const setRouteThemeSchema = z.object({\n mainColor: z\n .string()\n .optional()\n .describe(\n 'CSS color for the route line and waypoint icons. Prefer CSS named colors when they match (e.g. \"peachpuff\", \"coral\", \"steelblue\"), otherwise use hex (e.g. \"#FF0000\").',\n ),\n});\n\nexport const setRouteThemeDescription =\n 'Style the route line and waypoint icons with a color. ' +\n 'Prefer this over setPaintProperties for route styling — it applies the color consistently across all route and waypoint layers. ' +\n 'Requires a route to be shown on the map.';\n\nconst currentTheme: { mainColor?: string } = {};\n\n/**\n * Execute set route theme.\n */\nexport const executeSetRouteTheme = async (params: z.infer<typeof setRouteThemeSchema>, state: ToolState) => {\n try {\n if (params.mainColor !== undefined) currentTheme.mainColor = params.mainColor;\n\n const routingModule = await state.routing.getRoutingModule();\n routingModule.applyConfig({\n theme: {\n mainColor: currentTheme.mainColor,\n },\n });\n\n return {\n success: true as const,\n theme: { ...currentTheme },\n };\n } catch (error) {\n return {\n error: `Failed to set route theme: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the show-places tool. */\nexport const showPlacesOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n count: z.number(),\n shownEntryIds: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for showing places on the map.\n */\nexport const showPlacesSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('ID of a historical places entry (from recallPlaces). Omit to show the most recent.'),\n fitBounds: z.boolean().optional().describe('Pan/zoom to fit the shown features. Default: true.'),\n});\n\nexport const showPlacesDescription =\n 'Display a places entry on the map, replacing whatever was previously shown. ' +\n 'Pass an id (from recallPlaces) to show a specific entry, or omit to show the most recent. ' +\n 'To stack additional entries or hide specific ones, use `managePlaces` instead.';\n\n/**\n * Execute show places.\n */\nexport const executeShowPlaces = async (params: z.infer<typeof showPlacesSchema>, state: ToolState) => {\n const { id, fitBounds = true } = params;\n try {\n const targetId = id ?? state.places.entries.at(-1)?.id;\n\n if (!targetId) {\n return {\n error: 'No places available to display. Resolve a place or run a search first (e.g. discoverPlaces).',\n };\n }\n\n if (!state.places.entries.some((entry) => entry.id === targetId)) {\n return { error: `No places entry found with id \"${targetId}\"` };\n }\n\n await state.places.setShownEntries([targetId]);\n const shownFeatures = state.places.getShownFeatures();\n\n if (fitBounds && shownFeatures.length > 0) {\n const bbox = bboxFromGeoJSON(shownFeatures);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding: 50 });\n }\n }\n\n return {\n success: true as const,\n count: shownFeatures.length,\n shownEntryIds: [...state.places.shownEntryIds],\n };\n } catch (error) {\n return {\n error: `Failed to show places: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type Routes, type WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the show-route tool. */\nexport const showRouteOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n displayedRoute: z.number(),\n routeCount: z.number(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for showing routes on the map.\n */\nexport const showRouteSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('ID of a historical route entry (from recallRoutes). Omit to show the most recent route.'),\n selectedIndex: z.number().optional().describe('default: 0'),\n fitBounds: z.boolean().optional().describe('default: true'),\n});\n\nexport const showRouteDescription =\n 'Show routes and waypoints on the map and fit the camera to the route extent. ' +\n 'Pass an id (from recallRoutes) to show a historical route, or omit to show the most recent. ' +\n 'Mutates map display. Does not call any service.';\n\n/**\n * Execute show routes.\n */\nexport const executeShowRoutes = async (params: z.infer<typeof showRouteSchema>, state: ToolState) => {\n const { id, selectedIndex = 0, fitBounds = true } = params;\n try {\n let routes: Routes | undefined;\n let waypoints: WaypointLike[] | undefined;\n\n if (id) {\n const entry = state.routing.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No route entry found with id \"${id}\"` };\n }\n routes = entry.data;\n waypoints = entry.waypoints;\n } else {\n routes = state.routing.currentRoutes;\n waypoints = state.routing.planningSlots.filter((w): w is WaypointLike => w !== null);\n }\n\n if (!routes) {\n return { error: 'No routes available to display' };\n }\n\n const routingModule = await state.routing.getRoutingModule();\n await routingModule.showRoutes(routes, { selectedIndex });\n\n if (waypoints) {\n await routingModule.showWaypoints(waypoints);\n }\n\n // Fit bounds to show all routes if requested\n if (fitBounds) {\n const bbox = bboxFromGeoJSON(routes);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50 });\n }\n }\n\n return {\n success: true,\n displayedRoute: selectedIndex,\n routeCount: routes.features.length,\n };\n } catch (error) {\n return {\n error: `Failed to show route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Tool schema for showing traffic area analytics on the map.\n */\nexport const showTrafficAreaAnalyticsSchema = z.object({\n mode: z\n .enum(['hexgrid-3d', 'hexgrid-2d', 'square-3d', 'square-2d', 'heatmap'])\n .optional()\n .describe(\n \"Visualization mode. 'hexgrid-3d' (default): 3D hexagons. 'hexgrid-2d': flat hexagons. 'square-3d': 3D squares. 'square-2d': flat squares. 'heatmap': density layer.\",\n ),\n metric: z\n .enum(['congestionLevel', 'speed', 'travelTime'])\n .optional()\n .describe('Metric to visualize. Defaults to first fetched metric.'),\n colorTheme: z\n .enum(['trafficLight', 'heat', 'monochrome', 'viridis', 'plasma'])\n .optional()\n .describe(\n \"Preset color theme. 'trafficLight' (green→amber→red, default), 'heat' (blue→orange→red), 'monochrome' (grey), 'viridis' (purple→green→yellow), 'plasma' (purple→magenta→yellow).\",\n ),\n heightScale: z\n .number()\n .optional()\n .describe('Extrusion height multiplier / visual maximum in meters. Default: metric-dependent.'),\n scaleMode: z\n .enum(['raw', 'predefinedRange', 'currentRange'])\n .optional()\n .describe(\n \"Height scaling mode. 'raw': metric value × scale (default). 'predefinedRange': normalize to SDK predefined range so max = scale. 'currentRange': normalize to live data range so max = scale.\",\n ),\n filter: z\n .object({\n min: z.number().optional(),\n max: z.number().optional(),\n })\n .refine((filter) => filter.min !== undefined || filter.max !== undefined, {\n message: 'Filter must have at least one of min or max',\n })\n .optional()\n .describe('Filter visible tiles for the active metric by value range.'),\n visible: z.boolean().optional().describe('Set to false to clear the visualization. Default: true.'),\n fitBounds: z.boolean().optional().describe('Fit map viewport to the analytics area. Default: true.'),\n});\n\n/** Output schema for the show-traffic-area-analytics tool. */\nexport const showTrafficAreaAnalyticsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n mode: z.string(),\n metric: z.string(),\n colorTheme: z.string(),\n visible: z.boolean(),\n filtered: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const showTrafficAreaAnalyticsDescription =\n 'Show HISTORICAL traffic area analytics on the map (hexgrid/heatmap). ' +\n 'Not real-time — use toggleTrafficFlow for live traffic. ' +\n 'Configure metric, color theme, height, and tile filtering. ' +\n 'Use after getTrafficAreaAnalytics. Defaults to first fetched metric.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Execute show traffic area analytics.\n */\nexport const executeShowTrafficAreaAnalytics = async (\n params: z.infer<typeof showTrafficAreaAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof showTrafficAreaAnalyticsOutputSchema>> => {\n try {\n // Get stored analytics result\n const analyticsResult = state.traffic.lastAreaAnalytics;\n if (!analyticsResult) {\n return { error: 'No analytics data available. Call getTrafficAreaAnalytics first to fetch data.' };\n }\n\n // Default metric to first fetched metric\n const defaultMetric = analyticsResult.properties?.metrics?.[0] ?? 'congestionLevel';\n\n const {\n mode = 'hexgrid-3d',\n metric = defaultMetric,\n colorTheme = 'trafficLight',\n heightScale,\n scaleMode,\n filter,\n visible = true,\n fitBounds: shouldFitBounds = true,\n } = params;\n\n // Lazy-init TrafficAreaAnalyticsModule\n const analyticsModule = await state.traffic.getTrafficAreaAnalyticsModule();\n\n if (!visible) {\n await analyticsModule.clear();\n state.traffic.onAnalyticsCleared?.();\n return { success: true, mode, metric, colorTheme, visible: false, filtered: false };\n }\n\n // Clear previous visualization before showing new data\n await analyticsModule.clear();\n await analyticsModule.show(analyticsResult);\n\n // Apply mode and active metric\n analyticsModule.setMode(mode);\n analyticsModule.setMetric(metric);\n\n // Apply color theme\n analyticsModule.setColor(colorTheme);\n\n // Apply height config\n if (heightScale !== undefined || scaleMode !== undefined) {\n analyticsModule.setHeight({\n ...(heightScale !== undefined && { maxHeightMeters: heightScale }),\n ...(scaleMode !== undefined && { scaleMode }),\n });\n }\n\n // Apply tile filter for active metric\n const isFiltered = !!filter;\n if (filter) {\n analyticsModule.filter({ min: filter.min, max: filter.max });\n } else {\n analyticsModule.clearFilter();\n }\n\n // Fit map to analytics area\n if (shouldFitBounds) {\n const bbox = bboxFromGeoJSON(analyticsResult.features);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\n }\n\n state.traffic.onAnalyticsShown?.(analyticsResult, analyticsModule);\n\n return {\n success: true,\n mode,\n metric,\n colorTheme,\n visible: true,\n filtered: isFiltered,\n };\n } catch (error) {\n return {\n error: `Failed to show traffic area analytics: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the show-waypoints tool. */\nexport const showWaypointsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n count: z.number(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for showing waypoints on the map.\n */\nexport const showWaypointsSchema = z.object({});\n\nexport const showWaypointsDescription =\n 'Display origin, stop, and destination waypoints as markers on the map. ' +\n 'Context-dependent: requires setRouteLocations or locatePlace with waypointIndex to have been called first. ' +\n 'Does not recalculate the route. Prefer showRoute when you also need the route line drawn.';\n\n/**\n * Execute show waypoints.\n */\nexport const executeShowWaypoints = async (\n _params: z.infer<typeof showWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof showWaypointsOutputSchema>> => {\n try {\n // Prefer the current (possibly sparse) waypoints being assembled;\n // fall back to the last finalized waypoints from route history.\n const current = state.routing.planningSlots;\n\n const routingModule = await state.routing.getRoutingModule();\n await routingModule.showWaypoints(current);\n\n return { success: true, count: current.length };\n } catch (error) {\n return {\n error: `Failed to show waypoints: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { baseMapLayerGroupNames } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the toggle-base-map-layer-groups tool. */\nexport const toggleBaseMapLayerGroupsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n visible: z.boolean(),\n layerGroups: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for toggling base map layer groups.\n */\nexport const toggleBaseMapLayerGroupsSchema = z.object({\n visible: z.boolean(),\n layerGroups: z.array(z.enum(baseMapLayerGroupNames)).describe('The base map layer groups to show or hide.'),\n});\n\nexport const toggleBaseMapLayerGroupsDescription =\n 'Show or hide specific base map layer groups (water, buildings3D, roadLines, placeLabels, capitalLabels, etc.)';\n\n/**\n * Execute toggle base map layer groups.\n */\nexport const executeToggleBaseMapLayerGroups = async (\n params: z.infer<typeof toggleBaseMapLayerGroupsSchema>,\n state: ToolState,\n) => {\n const { visible, layerGroups } = params;\n try {\n // Lazy-init BaseMapModule\n const baseMapModule = await state.baseMap.getBaseMapModule();\n\n baseMapModule.setVisible(visible, {\n layerGroups: {\n mode: 'include',\n names: layerGroups,\n },\n });\n\n return {\n success: true,\n visible,\n layerGroups,\n };\n } catch (error) {\n return {\n error: `Failed to toggle layer groups: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the toggle-pois tool. */\nexport const togglePOIsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n allMapPOIsVisible: z.boolean().optional(),\n filterCategories: z\n .object({\n show: z.enum(['only', 'all_except']),\n values: z.array(z.string()),\n })\n .optional(),\n reset: z.boolean().optional(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for toggling POIs.\n */\nexport const togglePOIsSchema = z.object({\n reset: z\n .boolean()\n .optional()\n .describe(\n 'Set to true to reset all POI configuration to defaults (visibility and category filters). When true, all other parameters are ignored.',\n ),\n allMapPOIsVisible: z\n .boolean()\n .optional()\n .describe(\n 'Set the overall visibility of all built-in map POI icons. True to show all POIs, false to hide all POIs.',\n ),\n filterCategories: z\n .object({\n show: z\n .enum(['only', 'all_except'])\n .describe(\n '\"only\": show POIs only from the given categories. \"all_except\": show all POIs except those in the given categories.',\n ),\n values: z\n .array(z.string())\n .describe(\n 'MapStylePOICategory or POICategoryGroup values (e.g. \"RESTAURANT\", \"FOOD_DRINKS_GROUP\"). Overly specific codes like \"ITALIAN_RESTAURANT\" are not supported.',\n ),\n })\n .optional()\n .describe('Filter which built-in map POI categories are shown.'),\n});\n\nexport const togglePOIsDescription =\n 'Show or hide all built-in map POI icons (restaurants, hotels, gas stations, etc.) and optionally filter by category. ' +\n 'Use allMapPOIsVisible to control overall POI visibility, filterCategories to restrict which categories are shown or hidden, ' +\n 'or reset: true to restore all POI defaults.';\n\n/**\n * Execute toggle POIs.\n */\nexport const executeTogglePOIs = async (\n params: z.infer<typeof togglePOIsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof togglePOIsOutputSchema>> => {\n try {\n const poisModule = await state.mapPOIs.getPOIsModule();\n\n if (params.reset) {\n poisModule.resetConfig();\n return { success: true, reset: true };\n }\n\n const { allMapPOIsVisible, filterCategories } = params;\n\n if (allMapPOIsVisible !== undefined) poisModule.setVisible(allMapPOIsVisible);\n if (filterCategories) {\n poisModule.filterCategories({ show: filterCategories.show, values: filterCategories.values });\n }\n\n return { success: true, allMapPOIsVisible, filterCategories };\n } catch (error) {\n return {\n error: `Failed to toggle POIs: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Tool schema for toggling traffic flow.\n */\nexport const toggleTrafficFlowSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-traffic-flow tool. */\nexport const toggleTrafficFlowOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficFlowVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for toggling traffic incidents.\n */\nexport const toggleTrafficIncidentsSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-traffic-incidents tool. */\nexport const toggleTrafficIncidentsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficIncidentsVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const toggleTrafficFlowDescription =\n 'Toggle the REAL-TIME traffic flow layer — colored road segments showing live speed conditions. ' +\n 'This is NOT historical analytics; use showTrafficAreaAnalytics for historical data.';\n\nexport const toggleTrafficIncidentsDescription =\n 'Toggle REAL-TIME traffic incident markers on the map (jams, accidents, closures, roadworks). ' +\n 'This is NOT historical analytics. Use getTrafficIncidents for incident details.';\n\n/**\n * Execute toggle traffic flow.\n */\nexport const executeToggleTrafficFlow = async (params: z.infer<typeof toggleTrafficFlowSchema>, state: ToolState) => {\n const { visible } = params;\n try {\n // Lazy-init TrafficFlowModule\n const trafficFlowModule = await state.traffic.getTrafficFlowModule();\n\n trafficFlowModule.setVisible(visible);\n\n return {\n success: true,\n trafficFlowVisible: visible,\n };\n } catch (error) {\n return {\n error: `Failed to toggle traffic flow: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n/**\n * Execute toggle traffic incidents.\n */\nexport const executeToggleTrafficIncidents = async (\n params: z.infer<typeof toggleTrafficIncidentsSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n // Lazy-init TrafficIncidentsModule\n const trafficIncidentsModule = await state.traffic.getTrafficIncidentsModule();\n\n trafficIncidentsModule.setVisible(visible);\n\n return {\n success: true,\n trafficIncidentsVisible: visible,\n };\n } catch (error) {\n return {\n error: `Failed to toggle traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { sectionTypes } from '@tomtom-org/maps-sdk/core';\nimport type { ToolEntry } from '../types';\n\n// --- MapLibre tools ---\nimport {\n executeExecuteMaplibreCode,\n executeFlyTo,\n executeGetMapStyleLayers,\n executeGetViewport,\n executeMaplibreCodeDescription,\n executeMaplibreCodeOutputSchema,\n executeMaplibreCodeSchema,\n executeSetLayoutProperties,\n executeSetPaintProperties,\n executeSetPitchBearing,\n executeZoomInOrOut,\n flyToDescription,\n flyToOutputSchema,\n flyToSchema,\n getMapStyleLayersDescription,\n getMapStyleLayersOutputSchema,\n getMapStyleLayersSchema,\n getViewportDescription,\n getViewportOutputSchema,\n getViewportSchema,\n setLayoutPropertiesDescription,\n setLayoutPropertiesOutputSchema,\n setLayoutPropertiesSchema,\n setPaintPropertiesDescription,\n setPaintPropertiesOutputSchema,\n setPaintPropertiesSchema,\n setPitchBearingDescription,\n setPitchBearingOutputSchema,\n setPitchBearingSchema,\n zoomInOrOutDescription,\n zoomInOrOutOutputSchema,\n zoomInOrOutSchema,\n} from './maplibre';\n\n// --- Service tools ---\nimport {\n addStopToRouteDescription,\n addStopToRouteOutputSchema,\n addStopToRouteSchema,\n discoverPlacesDescription,\n discoverPlacesOutputSchema,\n discoverPlacesSchema,\n executeAddStopToRoute,\n executeDiscoverPlaces,\n executeFindReachableArea,\n executeGetPoiCategoryCodes,\n executeGetTrafficAreaAnalytics,\n executeGetTrafficIncidents,\n executeLocatePlace,\n executeQueryTrafficAnalytics,\n executeRemoveStopFromRoute,\n executeReverseGeocode,\n executeSearchAlongRoute,\n executeSetRouteLocations,\n executeSetRouteParameters,\n findReachableAreaDescription,\n findReachableAreaOutputSchema,\n findReachableAreaSchema,\n getPoiCategoryCodesDescription,\n getPoiCategoryCodesOutputSchema,\n getPoiCategoryCodesSchema,\n getTrafficAreaAnalyticsDescription,\n getTrafficAreaAnalyticsOutputSchema,\n getTrafficAreaAnalyticsSchema,\n getTrafficIncidentsDescription,\n getTrafficIncidentsOutputSchema,\n getTrafficIncidentsSchema,\n locatePlaceDescription,\n locatePlaceOutputSchema,\n locatePlaceSchema,\n queryTrafficAnalyticsDescription,\n queryTrafficAnalyticsOutputSchema,\n queryTrafficAnalyticsSchema,\n removeStopFromRouteDescription,\n removeStopFromRouteOutputSchema,\n removeStopFromRouteSchema,\n reverseGeocodeDescription,\n reverseGeocodeOutputSchema,\n reverseGeocodeSchema,\n searchAlongRouteDescription,\n searchAlongRouteOutputSchema,\n searchAlongRouteSchema,\n setRouteLocationsDescription,\n setRouteLocationsOutputSchema,\n setRouteLocationsSchema,\n setRouteParametersDescription,\n setRouteParametersOutputSchema,\n setRouteParametersSchema,\n} from './services';\n\n// --- State tools ---\nimport {\n executeGetCurrentWaypoints,\n executeRecallPlaces,\n executeRecallRanges,\n executeRecallRoutes,\n getCurrentWaypointsDescription,\n getCurrentWaypointsOutputSchema,\n getCurrentWaypointsSchema,\n recallPlacesDescription,\n recallPlacesOutputSchema,\n recallPlacesSchema,\n recallRangesDescription,\n recallRangesOutputSchema,\n recallRangesSchema,\n recallRoutesDescription,\n recallRoutesOutputSchema,\n recallRoutesSchema,\n} from './state';\n\n// --- TomTom Map tools ---\nimport {\n clearMapDescription,\n clearMapOutputSchema,\n clearMapSchema,\n executeClearMap,\n executeFitRouteSection,\n executeGetShownIncidents,\n executeGetShownPlaces,\n executeGetShownRouteSections,\n executeGetShownRoutes,\n executeGetShownRouteTrafficIncidents,\n executeGetShownWaypoints,\n executeGetStandardMapStyles,\n executeManagePlaces,\n executeSetLanguage,\n executeSetMapStandardStyle,\n executeSetRouteTheme,\n executeShowPlaces,\n executeShowRoutes,\n executeShowTrafficAreaAnalytics,\n executeShowWaypoints,\n executeToggleBaseMapLayerGroups,\n executeTogglePOIs,\n executeToggleTrafficFlow,\n executeToggleTrafficIncidents,\n fitRouteSectionDescription,\n fitRouteSectionOutputSchema,\n fitRouteSectionSchema,\n getShownIncidentsDescription,\n getShownIncidentsOutputSchema,\n getShownIncidentsSchema,\n getShownPlacesDescription,\n getShownPlacesOutputSchema,\n getShownPlacesSchema,\n getShownRouteSectionsDescription,\n getShownRouteSectionsOutputSchema,\n getShownRouteSectionsSchema,\n getShownRoutesDescription,\n getShownRoutesOutputSchema,\n getShownRoutesSchema,\n getShownRouteTrafficIncidentsDescription,\n getShownRouteTrafficIncidentsOutputSchema,\n getShownRouteTrafficIncidentsSchema,\n getShownWaypointsDescription,\n getShownWaypointsOutputSchema,\n getShownWaypointsSchema,\n getStandardMapStylesDescription,\n getStandardMapStylesOutputSchema,\n getStandardMapStylesSchema,\n managePlacesDescription,\n managePlacesOutputSchema,\n managePlacesSchema,\n setLanguageDescription,\n setLanguageOutputSchema,\n setLanguageSchema,\n setMapStandardStyleDescription,\n setMapStandardStyleOutputSchema,\n setMapStandardStyleSchema,\n setRouteThemeDescription,\n setRouteThemeOutputSchema,\n setRouteThemeSchema,\n showPlacesDescription,\n showPlacesOutputSchema,\n showPlacesSchema,\n showRouteDescription,\n showRouteOutputSchema,\n showRouteSchema,\n showTrafficAreaAnalyticsDescription,\n showTrafficAreaAnalyticsOutputSchema,\n showTrafficAreaAnalyticsSchema,\n showWaypointsDescription,\n showWaypointsOutputSchema,\n showWaypointsSchema,\n toggleBaseMapLayerGroupsDescription,\n toggleBaseMapLayerGroupsOutputSchema,\n toggleBaseMapLayerGroupsSchema,\n togglePOIsDescription,\n togglePOIsOutputSchema,\n togglePOIsSchema,\n toggleTrafficFlowDescription,\n toggleTrafficFlowOutputSchema,\n toggleTrafficFlowSchema,\n toggleTrafficIncidentsDescription,\n toggleTrafficIncidentsOutputSchema,\n toggleTrafficIncidentsSchema,\n} from './tomtom-map';\n\n// --- Utility tools ---\nimport {\n calculateBBoxDescription,\n calculateBBoxSchema,\n executeCalculateBBox,\n executeFormatDistance,\n executeFormatDuration,\n executeGetCurrentLocation,\n executeGetRouteProgress,\n executeGetSectionBBox,\n executeGetSectionProgress,\n formatDistanceDescription,\n formatDistanceSchema,\n formatDurationDescription,\n formatDurationSchema,\n getCurrentLocationDescription,\n getCurrentLocationOutputSchema,\n getCurrentLocationSchema,\n getRouteProgressDescription,\n getRouteProgressSchema,\n getSectionBBoxDescription,\n getSectionBBoxOutputSchema,\n getSectionBBoxSchema,\n getSectionProgressDescription,\n getSectionProgressSchema,\n helpDescription,\n helpOutputSchema,\n helpSchema,\n} from './utilities';\n\nconst defaultTools = {\n locatePlace: {\n description: locatePlaceDescription,\n classificationPrompt:\n 'Resolve a single location string (landmark, city, address) to a place without showing it; optionally stages a waypoint slot.',\n inputSchema: locatePlaceSchema,\n outputSchema: locatePlaceOutputSchema,\n execute: executeLocatePlace,\n tags: ['locate', 'place', 'location', 'waypoint'],\n examples: [\n 'locatePlace(\"Buckingham Palace\", where: \"within-map-bounds\", waypointIndex: 0)',\n 'locatePlace(\"Tower Bridge\", where: \"nearby-map-center\", waypointIndex: 1)',\n 'locatePlace(\"Westminster Abbey\", where: \"anywhere\")',\n 'locatePlace(\"Central Station\", where: { position: [4.9, 52.37] })',\n ],\n examplePrompts: ['Where is the Louvre?', 'Find the nearest train station', 'Locate Big Ben'],\n relatedTools: ['setRouteLocations', 'addStopToRoute', 'getCurrentWaypoints', 'recallPlaces'],\n },\n reverseGeocode: {\n description: reverseGeocodeDescription,\n classificationPrompt:\n 'Convert [lng, lat] coordinates to an address; use after map clicks or any tool that returns a position.',\n inputSchema: reverseGeocodeSchema,\n outputSchema: reverseGeocodeOutputSchema,\n execute: executeReverseGeocode,\n tags: ['location', 'place'],\n examples: ['reverseGeocode({ position: [4.9, 52.4] })'],\n examplePrompts: [\"What's the address at this point?\", 'What street is at these coordinates?'],\n relatedTools: ['getViewport', 'getCurrentLocation', 'showPlaces', 'locatePlace'],\n },\n discoverPlaces: {\n description: discoverPlacesDescription,\n classificationPrompt: 'Search for multiple places by text query or POI category within an area.',\n inputSchema: discoverPlacesSchema,\n outputSchema: discoverPlacesOutputSchema,\n execute: executeDiscoverPlaces,\n tags: ['discover', 'place', 'location'],\n examples: [\n 'discoverPlaces({ query: \"coffee\", where: { position: [4.9, 52.4] } })',\n 'discoverPlaces({ poiCategories: [\"ITALIAN_RESTAURANT\"] }) // category-only, no query needed',\n 'discoverPlaces({ query: \"hotel\", where: { boundingBox: [4.8, 52.3, 5.0, 52.5] } })',\n 'discoverPlaces({ query: \"parking\", where: \"Amsterdam\" }) // geocoded area',\n 'discoverPlaces({ query: \"coffee\", withinRange: \"ranges-0\" })',\n 'discoverPlaces({ poiCategories: [\"CAFE\"], show: { markerType: \"pin\", zoomMode: \"auto\" } })',\n ],\n examplePrompts: [\n 'Find coffee shops near me',\n 'Search for Italian restaurants in Amsterdam',\n 'Show me parking nearby',\n 'Find coffee shops within the range I just calculated',\n ],\n relatedTools: [\n 'getPOICategoryCodes',\n 'showPlaces',\n 'managePlaces',\n 'locatePlace',\n 'getViewport',\n 'findReachableArea',\n ],\n },\n setRouteLocations: {\n description: setRouteLocationsDescription,\n classificationPrompt:\n 'Set route waypoints by resolving location queries and trigger a recalculation (minimum: origin + destination).',\n inputSchema: setRouteLocationsSchema,\n outputSchema: setRouteLocationsOutputSchema,\n execute: executeSetRouteLocations,\n tags: ['route', 'waypoint', 'location'],\n examples: [\n 'setRouteLocations({ locations: [{ query: \"Amsterdam\" }, { query: \"Brussels\" }] })',\n 'setRouteLocations({ locations: [{ query: \"Eiffel Tower\" }, { query: \"Louvre Museum\" }, { query: \"Notre Dame\" }] })',\n 'setRouteLocations({ locations: [{ query: \"5th Ave, New York\", locationType: \"default\" }, { query: \"JFK Airport\", locationType: \"default\" }] })',\n ],\n examplePrompts: [\n 'Get directions from Paris to Lyon',\n 'Route me from Amsterdam to Brussels',\n 'Navigate from here to the airport',\n ],\n relatedTools: ['setRouteParameters', 'showRoute', 'addStopToRoute', 'removeStopFromRoute'],\n },\n setRouteParameters: {\n description: setRouteParametersDescription,\n classificationPrompt:\n 'Configure route options (alternatives, type, avoidances, departure/arrival time) and trigger recalculation if locations are set.',\n inputSchema: setRouteParametersSchema,\n outputSchema: setRouteParametersOutputSchema,\n execute: executeSetRouteParameters,\n tags: ['route'],\n examples: [\n 'setRouteParameters({ maxAlternatives: 2 })',\n 'setRouteParameters({ costModel: { routeType: \"fast\", avoid: [\"tollRoads\"] } })',\n 'setRouteParameters({ when: { option: \"departAt\", date: \"2025-06-01T09:00:00Z\" } })',\n ],\n examplePrompts: ['Avoid toll roads', 'Find the fastest route', 'Show 2 alternative routes', 'Depart at 9am'],\n relatedTools: ['setRouteLocations', 'showRoute'],\n },\n addStopToRoute: {\n description: addStopToRouteDescription,\n classificationPrompt: 'Insert a stop at the optimal position in the current route and recalculate.',\n inputSchema: addStopToRouteSchema,\n outputSchema: addStopToRouteOutputSchema,\n execute: executeAddStopToRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'addStopToRoute(location: \"Westminster Abbey\")',\n 'addStopToRoute(location: \"gas station near route\")',\n 'addStopToRoute() // use the most recently resolved or shown place',\n ],\n examplePrompts: ['Add a stop at Westminster Abbey', 'Add a coffee break to my route'],\n relatedTools: ['locatePlace', 'setRouteLocations', 'showRoute', 'removeStopFromRoute'],\n dependsOn: ['setRouteLocations'],\n },\n removeStopFromRoute: {\n description: removeStopFromRouteDescription,\n classificationPrompt: 'Remove a stop by index from the current route and recalculate.',\n inputSchema: removeStopFromRouteSchema,\n outputSchema: removeStopFromRouteOutputSchema,\n execute: executeRemoveStopFromRoute,\n tags: ['route', 'waypoint'],\n examples: ['removeStopFromRoute(1)', 'removeStopFromRoute(2, 1)'],\n examplePrompts: ['Remove the second stop from my route', 'Delete the Utrecht stop'],\n relatedTools: ['addStopToRoute', 'setRouteLocations', 'showRoute'],\n dependsOn: ['setRouteLocations'],\n },\n getPOICategoryCodes: {\n description: getPoiCategoryCodesDescription,\n classificationPrompt:\n 'Resolve natural-language category names (e.g. \"gym\", \"italian food\") to POICategory codes for discoverPlaces.',\n inputSchema: getPoiCategoryCodesSchema,\n outputSchema: getPoiCategoryCodesOutputSchema,\n execute: executeGetPoiCategoryCodes,\n tags: ['discover', 'place'],\n examples: [\n 'getPOICategoryCodes()',\n 'getPOICategoryCodes(filters: [\"gym\"])',\n 'getPOICategoryCodes(filters: [\"italian restaurant\"]) // multi-word phrase, spaces removed before matching',\n 'getPOICategoryCodes(filters: [\"restaurant\"], language: \"fr-FR\")',\n ],\n examplePrompts: ['What POI categories are available?', \"What's the code for gym?\"],\n relatedTools: ['discoverPlaces', 'togglePOIs'],\n },\n getTrafficIncidents: {\n description: getTrafficIncidentsDescription,\n classificationPrompt:\n 'Fetch traffic incidents by bbox or IDs without showing them; returns category, severity, and delay details.',\n inputSchema: getTrafficIncidentsSchema,\n outputSchema: getTrafficIncidentsOutputSchema,\n execute: executeGetTrafficIncidents,\n tags: ['traffic', 'location'],\n examples: [\n 'getTrafficIncidents({ bbox: [4.728, 52.278, 5.080, 52.479] })',\n 'getTrafficIncidents({ ids: [\"incident-id-1\", \"incident-id-2\"] })',\n 'getTrafficIncidents({ bbox: [...], categoryFilter: [\"accident\", \"jam\"] })',\n 'getTrafficIncidents({ bbox: [...], categoryFilter: [\"roadworks\"], timeValidityFilter: [\"future\"] })',\n ],\n examplePrompts: ['Fetch traffic incidents in Amsterdam', 'Any accidents in this area?'],\n relatedTools: ['getShownIncidents', 'toggleTrafficIncidents', 'calculateBBox', 'getViewport'],\n },\n getTrafficAreaAnalytics: {\n description: getTrafficAreaAnalyticsDescription,\n classificationPrompt:\n 'Fetch historical traffic analytics (speed, congestion, travel time) for an area; use queryTrafficAnalytics to drill down.',\n inputSchema: getTrafficAreaAnalyticsSchema,\n outputSchema: getTrafficAreaAnalyticsOutputSchema,\n execute: executeGetTrafficAreaAnalytics,\n tags: ['traffic', 'location'],\n examples: [\n \"getTrafficAreaAnalytics({ bbox: [4.728, 52.278, 5.080, 52.479], metrics: ['speed', 'congestionLevel'], startDate: '2025-03-01', endDate: '2025-03-07' })\",\n \"getTrafficAreaAnalytics({ bbox: [...], metrics: ['congestionLevel'], hours: [7,8,9,17,18] })\",\n ],\n examplePrompts: [\n 'What was the average congestion in Amsterdam last week?',\n 'Analyze traffic patterns during rush hour in this area',\n 'Show me historical speed data for the city center',\n ],\n relatedTools: ['queryTrafficAnalytics', 'showTrafficAreaAnalytics', 'calculateBBox', 'getViewport'],\n dependsOn: ['locatePlace'],\n },\n queryTrafficAnalytics: {\n description: queryTrafficAnalyticsDescription,\n classificationPrompt:\n 'Query cached traffic analytics data or check what metric/mode is currently displayed on the map.',\n inputSchema: queryTrafficAnalyticsSchema,\n outputSchema: queryTrafficAnalyticsOutputSchema,\n execute: executeQueryTrafficAnalytics,\n tags: ['traffic', 'location'],\n examples: [\n \"queryTrafficAnalytics({ granularity: 'daily' })\",\n \"queryTrafficAnalytics({ granularity: 'hourly', hourStart: 7, hourEnd: 9, metric: 'congestionLevel' })\",\n \"queryTrafficAnalytics({ granularity: 'average', dayOfWeek: [6, 7] })\",\n ],\n // NOTE: \"What am I looking at?\" awareness should be handled at a higher level\n // (e.g. system prompt or state summary injected into context), not per-tool examplePrompts.\n // Current approach works but doesn't scale — every stateful tool would need similar hints.\n examplePrompts: [\n 'Show me the daily breakdown',\n 'What was rush hour congestion?',\n 'Which day had the worst traffic?',\n 'What metric am I looking at?',\n 'What is currently shown on the map?',\n ],\n relatedTools: ['getTrafficAreaAnalytics', 'showTrafficAreaAnalytics'],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n showPlaces: {\n description: showPlacesDescription,\n classificationPrompt: 'Display a places entry on the map, replacing anything currently shown.',\n inputSchema: showPlacesSchema,\n outputSchema: showPlacesOutputSchema,\n execute: executeShowPlaces,\n tags: ['place', 'location'],\n examples: ['showPlaces()', 'showPlaces({ id: \"places-2\" })', 'showPlaces({ fitBounds: false })'],\n examplePrompts: ['Show the results on the map', 'Pin the search results', 'Display these places'],\n relatedTools: ['discoverPlaces', 'locatePlace', 'reverseGeocode', 'managePlaces', 'clearMap', 'flyTo'],\n dependsOn: ['discoverPlaces', 'locatePlace'],\n },\n managePlaces: {\n description: managePlacesDescription,\n classificationPrompt:\n 'Add more places on top of whatever is currently shown, or hide specific place entries from the map.',\n inputSchema: managePlacesSchema,\n outputSchema: managePlacesOutputSchema,\n execute: executeManagePlaces,\n tags: ['place', 'location', 'map style'],\n examples: [\n 'managePlaces({ add: [\"places-2\"] })',\n 'managePlaces({ remove: [\"places-1\"] })',\n 'managePlaces({ removeMatching: [\"cinema\"] }) // hide every entry labelled \"cinema\"',\n 'managePlaces({ addMatching: [\"cafe\"] })',\n 'managePlaces({ clear: true })',\n 'managePlaces({ add: [\"places-2\"], fitBounds: true })',\n ],\n examplePrompts: [\n 'Also show the cafes on the map',\n 'Additionally pin the pubs',\n 'Remove the banks from the map',\n 'Hide the cafes',\n ],\n relatedTools: ['discoverPlaces', 'showPlaces', 'recallPlaces', 'clearMap'],\n dependsOn: ['discoverPlaces', 'locatePlace'],\n },\n showRoute: {\n description: showRouteDescription,\n classificationPrompt: 'Render the most recently calculated route on the map and fit the camera to it.',\n inputSchema: showRouteSchema,\n outputSchema: showRouteOutputSchema,\n execute: executeShowRoutes,\n tags: ['route', 'waypoint'],\n examples: ['showRoute()', 'showRoute({ selectedIndex: 1 })', 'showRoute({ fitBounds: false })'],\n examplePrompts: ['Draw the route on the map', 'Show the directions', 'Display the calculated route'],\n relatedTools: ['setRouteLocations', 'getShownRouteSections', 'clearMap'],\n dependsOn: ['setRouteLocations'],\n },\n showTrafficAreaAnalytics: {\n description: showTrafficAreaAnalyticsDescription,\n classificationPrompt:\n 'Render traffic analytics as hexgrid, heatmap, or tiles with custom colors, filtering, height, and tooltip.',\n inputSchema: showTrafficAreaAnalyticsSchema,\n outputSchema: showTrafficAreaAnalyticsOutputSchema,\n execute: executeShowTrafficAreaAnalytics,\n tags: ['traffic', 'map style'],\n examples: [\n 'showTrafficAreaAnalytics()',\n \"showTrafficAreaAnalytics({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' })\",\n 'showTrafficAreaAnalytics({ filter: { min: 50 } })',\n \"showTrafficAreaAnalytics({ scaleMode: 'currentRange', heightScale: 200 })\",\n 'showTrafficAreaAnalytics({ visible: false })',\n ],\n examplePrompts: [\n 'Show it on the map',\n 'Visualize the congestion',\n 'Only show areas with congestion above 50%',\n 'Normalize height to the current data range',\n 'Switch to heatmap view',\n ],\n relatedTools: ['getTrafficAreaAnalytics', 'queryTrafficAnalytics', 'toggleTrafficFlow', 'clearMap'],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n showWaypoints: {\n description: showWaypointsDescription,\n classificationPrompt: 'Display staged origin/stop/destination markers without drawing the route line.',\n inputSchema: showWaypointsSchema,\n outputSchema: showWaypointsOutputSchema,\n execute: executeShowWaypoints,\n tags: ['waypoint', 'route'],\n examples: ['showWaypoints()'],\n examplePrompts: ['Show the waypoints on the map', 'Mark my route stops'],\n relatedTools: ['setRouteLocations', 'showRoute', 'getShownWaypoints', 'clearMap'],\n dependsOn: ['setRouteLocations', 'locatePlace'],\n },\n getShownPlaces: {\n description: getShownPlacesDescription,\n classificationPrompt:\n 'Return places currently displayed on the map (available after showPlaces or locatePlace).',\n inputSchema: getShownPlacesSchema,\n outputSchema: getShownPlacesOutputSchema,\n execute: executeGetShownPlaces,\n tags: ['place', 'location'],\n examples: ['getShownPlaces()'],\n examplePrompts: ['What places are on the map?', 'List the pinned locations'],\n relatedTools: ['showPlaces', 'clearMap', 'locatePlace', 'reverseGeocode'],\n dependsOn: ['showPlaces'],\n },\n getShownRoutes: {\n description: getShownRoutesDescription,\n classificationPrompt: 'Return routes currently rendered on the map (available after showRoute).',\n inputSchema: getShownRoutesSchema,\n outputSchema: getShownRoutesOutputSchema,\n execute: executeGetShownRoutes,\n tags: ['route'],\n examples: ['getShownRoutes()'],\n examplePrompts: ['What routes are displayed?', 'Is there a route on the map?'],\n relatedTools: ['showRoute', 'clearMap'],\n dependsOn: ['showRoute'],\n },\n getShownWaypoints: {\n description: getShownWaypointsDescription,\n classificationPrompt:\n 'Return waypoint markers currently shown on the map (available after showRoute or showWaypoints).',\n inputSchema: getShownWaypointsSchema,\n outputSchema: getShownWaypointsOutputSchema,\n execute: executeGetShownWaypoints,\n tags: ['waypoint', 'route'],\n examples: ['getShownWaypoints()'],\n examplePrompts: ['What waypoints are shown?', 'List the stops on the map'],\n relatedTools: ['showRoute', 'clearMap'],\n dependsOn: ['showRoute', 'showWaypoints'],\n },\n getShownRouteTrafficIncidents: {\n description: getShownRouteTrafficIncidentsDescription,\n classificationPrompt: 'Return traffic incidents overlaid on the shown route.',\n inputSchema: getShownRouteTrafficIncidentsSchema,\n outputSchema: getShownRouteTrafficIncidentsOutputSchema,\n execute: executeGetShownRouteTrafficIncidents,\n tags: ['traffic', 'route'],\n examples: ['getShownRouteTrafficIncidents()'],\n examplePrompts: ['What traffic incidents are on my route?', 'Any delays along the route?'],\n relatedTools: ['showRoute', 'clearMap', 'setRouteLocations'],\n dependsOn: ['showRoute'],\n },\n getShownRouteSections: {\n description: getShownRouteSectionsDescription,\n classificationPrompt:\n 'Return sections of a given type (toll, country, motorway, traffic) from the shown route.',\n inputSchema: getShownRouteSectionsSchema,\n outputSchema: getShownRouteSectionsOutputSchema,\n execute: executeGetShownRouteSections,\n tags: ['route', 'location', ...sectionTypes],\n examples: [\n 'getShownRouteSections(\"toll\")',\n 'getShownRouteSections(\"country\")',\n 'getShownRouteSections(\"traffic\")',\n 'getShownRouteSections(\"motorway\")',\n ],\n examplePrompts: [\n 'What countries does the route pass through?',\n 'Show me the toll sections',\n 'List the motorway sections on my route',\n ],\n relatedTools: ['showRoute', 'getShownRoutes', 'getShownRouteTrafficIncidents', 'fitRouteSection'],\n dependsOn: ['showRoute'],\n },\n getShownIncidents: {\n description: getShownIncidentsDescription,\n classificationPrompt:\n 'Return real-time incidents currently visible in the map viewport (rendered on map, not fetched from service).',\n inputSchema: getShownIncidentsSchema,\n outputSchema: getShownIncidentsOutputSchema,\n execute: executeGetShownIncidents,\n tags: ['traffic', 'location'],\n examples: ['getShownIncidents()'],\n examplePrompts: ['What traffic incidents are visible?', 'Any incidents in the current view?'],\n relatedTools: ['toggleTrafficIncidents', 'flyTo', 'getViewport'],\n dependsOn: ['toggleTrafficIncidents'],\n },\n clearMap: {\n description: clearMapDescription,\n classificationPrompt: 'Remove displayed places, routes, or all features from the map.',\n inputSchema: clearMapSchema,\n outputSchema: clearMapOutputSchema,\n execute: executeClearMap,\n tags: ['place', 'route', 'map style'],\n examples: ['clearMap()', 'clearMap([\"places\"])', 'clearMap([\"places\", \"routes\"])'],\n examplePrompts: ['Clear the map', 'Remove all markers', 'Start fresh'],\n relatedTools: ['showPlaces', 'showRoute'],\n },\n flyTo: {\n description: flyToDescription,\n classificationPrompt:\n 'Move the camera to a bounding box or specific position; use after locating places or computing bounds.',\n inputSchema: flyToSchema,\n outputSchema: flyToOutputSchema,\n execute: executeFlyTo,\n tags: ['location', 'map style'],\n examples: [\n 'flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5] } })',\n 'flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5], padding: 100 } })',\n 'flyTo({ where: { position: [4.9, 52.4], zoom: 14 } })',\n ],\n examplePrompts: ['Go to Berlin', 'Center the map on Rome', 'Fly to these coordinates'],\n relatedTools: ['getViewport', 'locatePlace', 'showPlaces', 'showRoute', 'calculateBBox'],\n },\n fitRouteSection: {\n description: fitRouteSectionDescription,\n classificationPrompt:\n 'Fit the camera to a specific route section by type and ID; use getShownRouteSections for valid IDs.',\n inputSchema: fitRouteSectionSchema,\n outputSchema: fitRouteSectionOutputSchema,\n execute: executeFitRouteSection,\n tags: ['route', 'map style', ...sectionTypes],\n examples: ['fitRouteSection(\"country\", \"NLD-section-1\")', 'fitRouteSection(\"traffic\", \"traffic-1\", 100)'],\n examplePrompts: ['Zoom to the toll section', 'Fit the Netherlands part of the route in view'],\n relatedTools: ['flyTo', 'showRoute', 'getShownRoutes', 'getShownRouteTrafficIncidents'],\n dependsOn: ['getShownRouteSections'],\n },\n getViewport: {\n description: getViewportDescription,\n classificationPrompt:\n 'Return the map center, zoom, and bounding box; use for area-relative queries like \"in this area\" or \"near the map center\".',\n inputSchema: getViewportSchema,\n outputSchema: getViewportOutputSchema,\n execute: executeGetViewport,\n tags: ['location', 'map style'],\n examples: ['getViewport()'],\n examplePrompts: [\n 'Where is the map centered?',\n \"What's the current map area?\",\n 'What are the current map bounds?',\n ],\n relatedTools: ['locatePlace', 'flyTo', 'discoverPlaces', 'reverseGeocode', 'getTrafficIncidents'],\n },\n recallPlaces: {\n description: recallPlacesDescription,\n classificationPrompt: 'Return the history of locatePlace/discoverPlaces calls made in this session.',\n inputSchema: recallPlacesSchema,\n outputSchema: recallPlacesOutputSchema,\n execute: executeRecallPlaces,\n tags: ['place', 'location'],\n examples: ['recallPlaces()', 'recallPlaces({ id: \"places-0\" })'],\n examplePrompts: [\n 'What places did I search for?',\n 'Show the coffee shops from earlier',\n 'What was the first place we looked up?',\n ],\n relatedTools: ['discoverPlaces', 'locatePlace', 'reverseGeocode', 'showPlaces'],\n dependsOn: ['locatePlace', 'discoverPlaces'],\n },\n recallRoutes: {\n description: recallRoutesDescription,\n classificationPrompt: 'Return previously calculated routes from this session.',\n inputSchema: recallRoutesSchema,\n outputSchema: recallRoutesOutputSchema,\n execute: executeRecallRoutes,\n tags: ['route'],\n examples: ['recallRoutes()', 'recallRoutes({ id: \"routes-0\" })'],\n examplePrompts: [\n 'What routes have I calculated?',\n 'Show the first route we planned',\n 'What was the route to Schiphol?',\n ],\n relatedTools: ['setRouteLocations', 'showRoute', 'getCurrentWaypoints'],\n dependsOn: ['setRouteLocations'],\n },\n getCurrentWaypoints: {\n description: getCurrentWaypointsDescription,\n classificationPrompt:\n 'Return the staged waypoint slots (origin, stops, destination) not yet committed to a route.',\n inputSchema: getCurrentWaypointsSchema,\n outputSchema: getCurrentWaypointsOutputSchema,\n execute: executeGetCurrentWaypoints,\n tags: ['waypoint', 'route'],\n examples: [\n 'getCurrentWaypoints()',\n 'getCurrentWaypoints({ slotIndex: 1 })',\n 'getCurrentWaypoints({ includeEmptySlots: true })',\n 'getCurrentWaypoints({ offset: 2, limit: 4 })',\n ],\n examplePrompts: ['What are my current route waypoints?', \"What's my destination?\", 'Show my staged stops'],\n relatedTools: ['locatePlace', 'setRouteLocations', 'showWaypoints', 'recallRoutes'],\n },\n getStandardMapStyles: {\n description: getStandardMapStylesDescription,\n classificationPrompt: 'List available standard map style IDs for use with setMapStandardStyle.',\n inputSchema: getStandardMapStylesSchema,\n outputSchema: getStandardMapStylesOutputSchema,\n execute: executeGetStandardMapStyles,\n tags: ['map style'],\n examples: ['getStandardMapStyles()'],\n examplePrompts: ['What map styles are available?', 'List the map themes'],\n relatedTools: ['setMapStandardStyle'],\n },\n setMapStandardStyle: {\n description: setMapStandardStyleDescription,\n classificationPrompt: 'Switch the map to a standard style preset (light, dark, satellite, driving, etc.).',\n inputSchema: setMapStandardStyleSchema,\n outputSchema: setMapStandardStyleOutputSchema,\n execute: executeSetMapStandardStyle,\n tags: ['map style'],\n examples: ['setMapStandardStyle(\"standardDark\")', 'setMapStandardStyle(\"satellite\")'],\n examplePrompts: ['Switch to dark mode', 'Use the satellite view', 'Change to the light theme'],\n relatedTools: ['getStandardMapStyles', 'setLanguage', 'toggleBaseMapLayerGroups', 'getMapStyleLayers'],\n },\n setRouteTheme: {\n description: setRouteThemeDescription,\n classificationPrompt:\n 'Color the route line and waypoint icons; prefer over setPaintProperties for route styling.',\n inputSchema: setRouteThemeSchema,\n outputSchema: setRouteThemeOutputSchema,\n execute: executeSetRouteTheme,\n tags: ['route', 'map style'],\n examples: [\n 'setRouteTheme({ mainColor: \"#FF0000\" }) // red route',\n 'setRouteTheme({ mainColor: \"coral\" })',\n 'setRouteTheme({ mainColor: \"steelblue\" })',\n ],\n examplePrompts: ['Make the route line red', 'Change the route color to blue', 'Style the route in coral'],\n relatedTools: ['showRoute', 'setRouteLocations', 'setPaintProperties'],\n dependsOn: ['showRoute'],\n },\n setLanguage: {\n description: setLanguageDescription,\n classificationPrompt: 'Change the language for map labels and all subsequent service API responses.',\n inputSchema: setLanguageSchema,\n outputSchema: setLanguageOutputSchema,\n execute: executeSetLanguage,\n tags: ['map style'],\n examples: ['setLanguage(\"fr-FR\")', 'setLanguage(\"de-DE\")'],\n examplePrompts: ['Show map labels in French', 'Switch the map to German', 'Use Spanish for place names'],\n relatedTools: ['setMapStandardStyle'],\n },\n toggleBaseMapLayerGroups: {\n description: toggleBaseMapLayerGroupsDescription,\n classificationPrompt:\n 'Show or hide named base map layer groups (buildings3D, roadLabels, water, placeLabels, etc.).',\n inputSchema: toggleBaseMapLayerGroupsSchema,\n outputSchema: toggleBaseMapLayerGroupsOutputSchema,\n execute: executeToggleBaseMapLayerGroups,\n tags: ['map style'],\n examples: [\n 'toggleBaseMapLayerGroups(false, [\"buildings3D\"])',\n 'toggleBaseMapLayerGroups(true, [\"roadLabels\"])',\n ],\n examplePrompts: ['Hide 3D buildings', 'Show road labels', 'Toggle the building layer'],\n relatedTools: ['togglePOIs', 'getMapStyleLayers', 'setMapStandardStyle'],\n },\n togglePOIs: {\n description: togglePOIsDescription,\n classificationPrompt: 'Show or hide built-in map POI icons with optional category filtering.',\n inputSchema: togglePOIsSchema,\n outputSchema: togglePOIsOutputSchema,\n execute: executeTogglePOIs,\n tags: ['place', 'map style'],\n examples: [\n 'togglePOIs({ allMapPOIsVisible: false })',\n 'togglePOIs({ allMapPOIsVisible: true, filterCategories: { show: \"only\", values: [\"RESTAURANT\", \"CAFE\"] } })',\n 'togglePOIs({ filterCategories: { show: \"all_except\", values: [\"PARKING_GARAGE\"] } })',\n 'togglePOIs({ reset: true })',\n ],\n examplePrompts: ['Hide all POI icons', 'Show only restaurant POIs', 'Turn off parking icons'],\n relatedTools: ['toggleBaseMapLayerGroups', 'discoverPlaces', 'setMapStandardStyle'],\n },\n toggleTrafficFlow: {\n description: toggleTrafficFlowDescription,\n classificationPrompt:\n 'Toggle REAL-TIME traffic flow (colored road segments for live speeds). Not historical analytics.',\n inputSchema: toggleTrafficFlowSchema,\n outputSchema: toggleTrafficFlowOutputSchema,\n execute: executeToggleTrafficFlow,\n tags: ['traffic', 'map style'],\n examples: ['toggleTrafficFlow(true)', 'toggleTrafficFlow(false)'],\n examplePrompts: ['Show traffic flow', 'Turn off traffic colors', 'Enable the congestion overlay'],\n relatedTools: ['toggleTrafficIncidents', 'setRouteLocations', 'getTrafficIncidents'],\n },\n toggleTrafficIncidents: {\n description: toggleTrafficIncidentsDescription,\n classificationPrompt:\n 'Toggle REAL-TIME traffic incident markers and lines (accidents, jams, closures). Not historical analytics.',\n inputSchema: toggleTrafficIncidentsSchema,\n outputSchema: toggleTrafficIncidentsOutputSchema,\n execute: executeToggleTrafficIncidents,\n tags: ['traffic', 'map style'],\n examples: ['toggleTrafficIncidents(true)', 'toggleTrafficIncidents(false)'],\n examplePrompts: ['Show traffic incidents', 'Hide accident icons', 'Enable incident markers'],\n relatedTools: ['toggleTrafficFlow', 'getTrafficIncidents', 'getShownIncidents', 'setRouteLocations'],\n },\n getMapStyleLayers: {\n description: getMapStyleLayersDescription,\n classificationPrompt: 'List MapLibre layer IDs with their paint/layout properties for custom styling.',\n inputSchema: getMapStyleLayersSchema,\n outputSchema: getMapStyleLayersOutputSchema,\n execute: executeGetMapStyleLayers,\n tags: ['map style'],\n examples: ['getMapStyleLayers()', 'getMapStyleLayers(\"road\")', 'getMapStyleLayers(\"water\")'],\n examplePrompts: ['What are the paint properties of the water layer?', 'Show me the road layer layout'],\n relatedTools: ['setLayoutProperties', 'setPaintProperties'],\n },\n setLayoutProperties: {\n description: setLayoutPropertiesDescription,\n classificationPrompt: 'Set MapLibre layout properties (visibility, text-field, icon-size) on named layers.',\n inputSchema: setLayoutPropertiesSchema,\n outputSchema: setLayoutPropertiesOutputSchema,\n execute: executeSetLayoutProperties,\n tags: ['map style'],\n examples: [\n 'setLayoutProperties([{ layerId: \"road-label\", propertyName: \"visibility\", value: \"none\" }])',\n 'setLayoutProperties([{ layerId: \"poi-label\", propertyName: \"text-field\", value: \"{name}\" }, { layerId: \"poi-label\", propertyName: \"icon-size\", value: 1.2 }])',\n ],\n examplePrompts: [\n 'Hide the road-label layer',\n 'Change the text size on building labels',\n 'Make the city labels bigger',\n ],\n relatedTools: ['getMapStyleLayers', 'setPaintProperties'],\n dependsOn: ['getMapStyleLayers'],\n },\n setPaintProperties: {\n description: setPaintPropertiesDescription,\n classificationPrompt: 'Set MapLibre paint properties (colors, widths, opacity) on named layers.',\n inputSchema: setPaintPropertiesSchema,\n outputSchema: setPaintPropertiesOutputSchema,\n execute: executeSetPaintProperties,\n tags: ['map style'],\n examples: [\n 'setPaintProperties([{ layerId: \"water\", propertyName: \"fill-color\", value: \"#0000ff\" }])',\n 'setPaintProperties([{ layerId: \"road\", propertyName: \"line-color\", value: \"#ff0000\" }, { layerId: \"road\", propertyName: \"line-width\", value: 3 }])',\n ],\n examplePrompts: [\n 'Make the water bright blue',\n 'Change the road color to red',\n 'Make buildings semi-transparent',\n 'Make the country borders pink',\n ],\n relatedTools: ['getMapStyleLayers', 'setLayoutProperties', 'setRouteTheme'],\n dependsOn: ['getMapStyleLayers'],\n },\n setPitchBearing: {\n description: setPitchBearingDescription,\n classificationPrompt: 'Tilt (pitch) and/or rotate (bearing) the map camera for a 3D perspective.',\n inputSchema: setPitchBearingSchema,\n outputSchema: setPitchBearingOutputSchema,\n execute: executeSetPitchBearing,\n tags: ['map style'],\n examples: [\n 'setPitchBearing({ pitch: 45 })',\n 'setPitchBearing({ bearing: 90 })',\n 'setPitchBearing({ pitch: 60, bearing: -45 })',\n ],\n examplePrompts: ['Tilt the map to 45 degrees', 'Rotate the map to face north', 'Set a 3D perspective view'],\n relatedTools: ['getViewport', 'locatePlace', 'flyTo'],\n },\n zoomInOrOut: {\n description: zoomInOrOutDescription,\n classificationPrompt: 'Adjust the map zoom level by a delta; positive zooms in, negative zooms out.',\n inputSchema: zoomInOrOutSchema,\n outputSchema: zoomInOrOutOutputSchema,\n execute: executeZoomInOrOut,\n tags: ['map style'],\n examples: ['zoomInOrOut({ delta: 2 })', 'zoomInOrOut({ delta: -1 })'],\n examplePrompts: ['Zoom in', 'Zoom out 2 levels', 'Get a wider view'],\n relatedTools: ['getViewport', 'flyTo'],\n },\n executeMaplibreCode: {\n description: executeMaplibreCodeDescription,\n classificationPrompt:\n 'Execute any MapLibre JS code against the live map for maximum flexibility — use when no other tool covers the required operation.',\n inputSchema: executeMaplibreCodeSchema,\n outputSchema: executeMaplibreCodeOutputSchema,\n execute: executeExecuteMaplibreCode,\n tags: ['map style', 'maplibre', 'layers', 'sources', 'animation'],\n examples: [\n \"executeMaplibreCode({ code: \\\"map.addSource('pts', { type: 'geojson', data: fc }); map.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-color': '#f00' } })\\\" })\",\n 'executeMaplibreCode({ code: \"map.easeTo({ center: [4.9, 52.37], zoom: 14, duration: 2000 })\" })',\n \"executeMaplibreCode({ code: \\\"for (let i = 0; i <= 20; i++) { map.setPaintProperty('fill', 'fill-opacity', i / 20); await new Promise(r => setTimeout(r, 50)); }\\\" })\",\n \"executeMaplibreCode({ code: \\\"map.setFog({ color: 'white', 'horizon-blend': 0.1 })\\\" })\",\n ],\n examplePrompts: [\n 'Add a GeoJSON layer to the map',\n 'Animate the map camera',\n 'Fade in a layer gradually',\n 'Set map fog or atmosphere',\n 'Add a raster tile overlay',\n ],\n relatedTools: ['getMapStyleLayers', 'setLayoutProperties', 'setPaintProperties', 'flyTo'],\n dependsOn: ['getMapStyleLayers'],\n },\n calculateBBox: {\n description: calculateBBoxDescription,\n classificationPrompt:\n 'Compute a [minLng, minLat, maxLng, maxLat] bounding box from GeoJSON features or tool results.',\n inputSchema: calculateBBoxSchema,\n execute: executeCalculateBBox,\n tags: ['utilities', 'location'],\n examples: ['calculateBBox(place)', 'calculateBBox(routes)', 'calculateBBox([place1, place2])'],\n examplePrompts: [\"What's the bounding box of these places?\", 'Calculate the extent of this route'],\n relatedTools: ['flyTo', 'getViewport', 'showPlaces', 'showRoute'],\n },\n formatDistance: {\n description: formatDistanceDescription,\n classificationPrompt:\n 'Format meters into a human-readable string (e.g. \"2.5 km\"); use after tools that return distances.',\n inputSchema: formatDistanceSchema,\n execute: executeFormatDistance,\n tags: ['utilities', 'route'],\n examples: ['formatDistance(2500)', 'formatDistance(5000, \"imperial_us\")'],\n examplePrompts: ['Format 5000 meters as a distance', 'Convert meters to a readable string'],\n relatedTools: ['formatDuration', 'setRouteLocations', 'getSectionProgress', 'getRouteProgress'],\n },\n formatDuration: {\n description: formatDurationDescription,\n classificationPrompt:\n 'Format seconds into a human-readable string (e.g. \"1 h 30 min\"); use after tools that return durations.',\n inputSchema: formatDurationSchema,\n execute: executeFormatDuration,\n tags: ['utilities', 'route'],\n examples: ['formatDuration(3600)', 'formatDuration(9000)'],\n examplePrompts: ['Format 3600 seconds as a duration', 'Convert seconds to hours and minutes'],\n relatedTools: ['formatDistance', 'setRouteLocations', 'getSectionProgress', 'getRouteProgress'],\n },\n getSectionProgress: {\n description: getSectionProgressDescription,\n classificationPrompt:\n 'Return the distance and travel time for one or more sections of the shown route by type.',\n inputSchema: getSectionProgressSchema,\n execute: executeGetSectionProgress,\n tags: ['utilities', 'route', ...sectionTypes],\n examples: [\n 'getSectionProgress({ sectionType: \"country\" }) // all country sections',\n 'getSectionProgress({ sectionType: \"traffic\", ids: [\"incident-id-1\"] })',\n 'getSectionProgress({ sectionType: \"toll\", ids: [\"toll-1\", \"toll-2\"] })',\n ],\n examplePrompts: ['How far is the toll section?', 'Travel time through France on this route'],\n relatedTools: ['setRouteLocations', 'fitRouteSection', 'formatDistance', 'formatDuration'],\n dependsOn: ['showRoute'],\n },\n getSectionBBox: {\n description: getSectionBBoxDescription,\n classificationPrompt: 'Return the bounding box of a specific route section; use with flyTo to frame it.',\n inputSchema: getSectionBBoxSchema,\n outputSchema: getSectionBBoxOutputSchema,\n execute: executeGetSectionBBox,\n tags: ['utilities', 'route', 'location', ...sectionTypes],\n examples: [\n 'getSectionBBox({ sectionType: \"country\", id: \"NLD-section-1\" })',\n 'getSectionBBox({ sectionType: \"traffic\", id: \"traffic-1\" })',\n 'getSectionBBox({ sectionType: \"toll\", id: \"toll-2\" })',\n ],\n examplePrompts: ['Get the extent of the Netherlands section', 'Bounding box of the traffic incident'],\n relatedTools: ['flyTo', 'fitRouteSection', 'getSectionProgress', 'getShownRouteSections'],\n dependsOn: ['getShownRouteSections'],\n },\n getRouteProgress: {\n description: getRouteProgressDescription,\n classificationPrompt:\n 'Return the position along the shown route at a given traveled time, distance, or clock time.',\n inputSchema: getRouteProgressSchema,\n execute: executeGetRouteProgress,\n tags: ['utilities', 'route', 'location'],\n examples: [\n 'getRouteProgress(traveledTimeInSeconds: 600)',\n 'getRouteProgress(traveledDistanceInMeters: 5000)',\n 'getRouteProgress(clockTime: \"2025-06-01T09:30:00Z\")',\n 'getRouteProgress(traveledTimeInSeconds: 300, routeIndex: 1)',\n ],\n examplePrompts: ['Where am I after 10 minutes of travel?', 'Position at 5km into the route'],\n relatedTools: ['setRouteLocations', 'getSectionProgress', 'formatDistance', 'formatDuration'],\n dependsOn: ['showRoute'],\n },\n getCurrentLocation: {\n description: getCurrentLocationDescription,\n classificationPrompt:\n 'Get the user\\'s physical GPS location from the browser; use for \"near me\" queries, not map center.',\n inputSchema: getCurrentLocationSchema,\n outputSchema: getCurrentLocationOutputSchema,\n execute: executeGetCurrentLocation,\n tags: ['utilities', 'location'],\n examples: ['getCurrentLocation()'],\n examplePrompts: ['Where am I?', 'Get my GPS location', 'Use my current position'],\n relatedTools: ['discoverPlaces', 'reverseGeocode', 'locatePlace'],\n },\n help: {\n description: helpDescription,\n classificationPrompt:\n 'Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.',\n inputSchema: helpSchema,\n outputSchema: helpOutputSchema,\n execute: async () => {\n throw new Error('help tool requires setupTools() — it needs resolved metadata from all tools.');\n },\n tags: ['utilities'],\n examples: [\n 'help({ mode: \"summary\" })',\n 'help({ mode: \"detail\", query: \"traffic\" })',\n 'help({ mode: \"detail\", tag: \"route\" })',\n 'help({ mode: \"detail\", query: \"route\", tag: \"utilities\" })',\n ],\n examplePrompts: ['What can you do?', 'Show me available tools', 'How do I search for places?'],\n },\n searchAlongRoute: {\n description: searchAlongRouteDescription,\n classificationPrompt: 'Search for POIs along a calculated route ranked by detour cost.',\n inputSchema: searchAlongRouteSchema,\n outputSchema: searchAlongRouteOutputSchema,\n execute: executeSearchAlongRoute,\n tags: ['search', 'route', 'place', 'detour', 'along-route'],\n examples: [\n 'searchAlongRoute({ maxDetourTimeSeconds: 300, query: \"coffee\" })',\n 'searchAlongRoute({ maxDetourTimeSeconds: 600, poiCategories: [\"ELECTRIC_VEHICLE_STATION\"], sortBy: \"detourOffset\" })',\n 'searchAlongRoute({ maxDetourTimeSeconds: 300, routeIndex: 0, show: { markerType: \"pin\", zoomMode: \"auto\" } })',\n ],\n examplePrompts: [\n 'Find gas stations along my route',\n 'Show me coffee shops within 5 minutes off the route',\n 'Any EV chargers I can reach from this route?',\n 'I need a coffee stop on the way to the airport',\n 'Find me a restaurant somewhere between here and Paris',\n ],\n relatedTools: ['setRouteLocations', 'recallRoutes', 'showPlaces', 'recallPlaces'],\n dependsOn: ['setRouteLocations'],\n },\n findReachableArea: {\n description: findReachableAreaDescription,\n classificationPrompt: 'Calculate isochrone/isodistance polygons showing how far you can travel from a point.',\n inputSchema: findReachableAreaSchema,\n outputSchema: findReachableAreaOutputSchema,\n execute: executeFindReachableArea,\n tags: ['reachable-range', 'isochrone', 'range', 'EV', 'coverage'],\n examples: [\n 'findReachableArea({ origin: { query: \"Amsterdam\", locationType: \"default\" }, budgets: [{ type: \"timeMinutes\", value: 30 }], showOnMap: true })',\n 'findReachableArea({ origin: { position: { lng: 4.9, lat: 52.3 } }, budgets: [{ type: \"timeMinutes\", value: 10 }, { type: \"timeMinutes\", value: 30 }], showOnMap: true, theme: \"inverted\" })',\n 'findReachableArea({ origin: { query: \"my location\", locationType: \"default\" }, budgets: [{ type: \"distanceKM\", value: 50 }], showOnMap: false })',\n ],\n examplePrompts: [\n 'Show me where I can reach in 30 minutes from Amsterdam',\n 'What area is within 50 km of Berlin?',\n 'Show 10, 20, 30 minute drive zones from here',\n ],\n relatedTools: ['recallRanges', 'locatePlace'],\n },\n recallRanges: {\n description: recallRangesDescription,\n classificationPrompt: 'Retrieve stored reachable range results from session history.',\n inputSchema: recallRangesSchema,\n outputSchema: recallRangesOutputSchema,\n execute: executeRecallRanges,\n tags: ['recall', 'reachable-range', 'state'],\n examples: ['recallRanges()', 'recallRanges({ id: \"ranges-0\" })'],\n examplePrompts: ['What range did I calculate earlier?', 'Recall the isochrone from Amsterdam'],\n relatedTools: ['findReachableArea'],\n },\n} satisfies Record<string, ToolEntry>;\n\n// Narrow the value type so the emitted d.ts captures only the keys, not the\n// inlined `z.infer<...>` signatures from each `execute` (which the d.ts bundler\n// flattens to invalid `infer<typeof X>` syntax). Must go through an intermediate\n// const so TypeScript resolves the mapped type before re-exporting.\nconst DEFAULT_TOOLS_TYPED: { [K in keyof typeof defaultTools]: ToolEntry } = defaultTools;\n\n/**\n * Default tool set for the agent toolkit.\n * Included automatically by createMapAgent unless includeDefaultTools is false.\n * Use resolveTools() for manual composition outside createMapAgent.\n *\n * @group Agent Toolkit\n */\nexport const DEFAULT_TOOLS = DEFAULT_TOOLS_TYPED;\n\n/**\n * Union of all default tool names.\n *\n * @group Agent Toolkit\n */\nexport type ToolName = keyof typeof DEFAULT_TOOLS;\n\n/**\n * All valid default tool names, derived from {@link DEFAULT_TOOLS}.\n *\n * @group Agent Toolkit\n */\nexport const TOOL_NAMES = Object.keys(DEFAULT_TOOLS) as ToolName[];\n","/**\n * @module agent-toolkit\n */\n\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { PrepareStepFunction, PrepareStepResult } from 'ai';\nimport { stepCountIs, ToolLoopAgent } from 'ai';\nimport { resolveTools } from './resolve-tools';\nimport { createToolState } from './state';\nimport { buildSystemPrompt } from './system-prompt';\nimport { setupTools } from './tool-setup';\nimport { DEFAULT_TOOLS } from './tools';\nimport type { Classifier, MapAgentInstance, MapAgentOptions, StateSlice, ToolEntry, ToolState } from './types';\nimport type { ClassificationResult } from './utils';\nimport { createDefaultClassifier } from './utils';\n\n/** Intersects two activeTools lists. Returns undefined if neither is set. */\nconst intersectActiveTools = (\n a: string[] | undefined,\n b: readonly (string | number | symbol)[] | undefined,\n): string[] | undefined => {\n if (!a) return b as string[] | undefined;\n if (!b) return a;\n const setB = new Set(b);\n return a.filter((name) => setB.has(name));\n};\n\n/** Type guard for StateSlice — checks for reset() method. */\nconst isStateSlice = (value: unknown): value is StateSlice =>\n typeof value === 'object' && value !== null && 'reset' in value && typeof (value as any).reset === 'function';\n\n/** Resets all state slices that implement StateSlice. */\nconst destroyState = (state: ToolState): void => {\n for (const slice of Object.values(state)) {\n if (isStateSlice(slice)) {\n slice.reset();\n }\n }\n};\n\n/**\n * Creates a conversational agent toolkit that gives an LLM control over a TomTom map.\n *\n * Returns a ToolLoopAgent instance (compatible with DirectChatTransport) with\n * `state` and `destroy` attached. Classification runs transparently inside\n * `prepareStep` — no wrapper type needed.\n *\n * @param map - The TomTomMap instance to control\n * @param options - Agent configuration options\n * @returns A ToolLoopAgent with state access and cleanup\n *\n * @example Basic usage\n * ```typescript\n * import { createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n * import { openai } from '@ai-sdk/openai';\n * import { DirectChatTransport } from 'ai/react';\n *\n * const agent = createMapAgent(map, { model: openai('gpt-4o') });\n *\n * // Works directly with DirectChatTransport — no .agent property needed\n * const { messages, sendMessage } = useChat({\n * transport: new DirectChatTransport({ agent }),\n * });\n * ```\n *\n * @example Custom tools + removal\n * ```typescript\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * tools: {\n * getCustomLocation: myLocationTool, // add custom\n * setLanguage: false, // remove default\n * },\n * });\n * ```\n * @group Agent Toolkit\n */\nexport const createMapAgent = <CS extends ToolState = ToolState>(\n map: TomTomMap,\n options: MapAgentOptions<CS>,\n): MapAgentInstance<CS> => {\n if (!options.model) {\n throw new Error('MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.');\n }\n\n const state = createToolState(map, options.state) as CS;\n\n // Safe: default tools only access base ToolState properties; CS extends ToolState.\n const defaults = options.includeDefaultTools === false ? {} : (DEFAULT_TOOLS as Record<string, ToolEntry<CS>>);\n const toolEntries = resolveTools(defaults, options.tools);\n\n const { tools, toolsMetadata } = setupTools(toolEntries, state, {\n outputSchemas: options.outputSchemas,\n });\n\n // Resolve classifier\n const classifier: Classifier | null =\n options.classifier === false ? null : (options.classifier ?? createDefaultClassifier({ model: options.model }));\n\n const systemPrompt = buildSystemPrompt(options.systemPrompt, options.systemPromptSuffix);\n\n // Classification state — cached per turn (reset on step 0)\n let lastClassification: ClassificationResult | null = null;\n\n const prepareStep: PrepareStepFunction = async (stepInfo) => {\n let activeTools: string[] | undefined;\n\n if (stepInfo.stepNumber === 0) {\n lastClassification = null;\n if (classifier) {\n try {\n lastClassification = await classifier({\n messages: stepInfo.messages,\n toolsMetadata,\n });\n } catch {\n lastClassification = null;\n }\n }\n options.onClassify?.(lastClassification);\n }\n\n if (lastClassification) {\n activeTools = lastClassification.activeToolNames;\n }\n\n // Compose with developer's prepareStep\n const userResult: PrepareStepResult | undefined = await options.prepareStep?.(stepInfo);\n\n if (userResult) {\n return {\n ...userResult,\n activeTools: intersectActiveTools(activeTools, userResult.activeTools),\n };\n }\n\n return activeTools ? { activeTools } : {};\n };\n\n const agent = new ToolLoopAgent({\n model: options.model,\n tools,\n instructions: systemPrompt,\n stopWhen: stepCountIs(options.maxSteps ?? 10),\n prepareStep,\n });\n\n return Object.assign(agent, {\n state,\n destroy: () => destroyState(state),\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/resolve-tools.ts","../src/state/base-map/state.ts","../src/state/events.ts","../src/state/custom-geometries/state.ts","../src/state/digest.ts","../src/state/map-pois/state.ts","../src/state/places/state.ts","../src/state/range/state.ts","../src/state/routing/state.ts","../src/state/traffic-area-analytics/state.ts","../src/tools/shared/fit-on-map.ts","../src/tools/shared/geometries-id.ts","../src/tools/shared/geometries-schema-doc.ts","../src/tools/shared/hide-previous-entries.ts","../src/tools/shared/location-input.ts","../src/tools/shared/places-schema-doc.ts","../src/tools/shared/resolve-poi-categories.ts","../src/tools/shared/routes-schema-doc.ts","../src/tools/shared/sandbox-code.ts","../src/tools/shared/schema.ts","../src/tools/shared/show-entry-geometries.ts","../src/tools/shared/show-places-on-map.ts","../src/tools/shared/state-inputs.ts","../src/tools/shared/viewport-bias.ts","../src/state/traffic-incidents/analysis.ts","../src/state/traffic-incidents/monitor/monitor.ts","../src/state/traffic-incidents/state.ts","../src/state/traffic-tiles/state.ts","../src/state/state.ts","../src/system-prompt.ts","../src/tools/utilities/calculate-bbox.ts","../src/tools/utilities/format-distance.ts","../src/tools/utilities/format-duration.ts","../src/tools/shared-output-schemas.ts","../src/tools/utilities/get-current-location.ts","../src/tools/utilities/get-route-progress.ts","../src/tools/utilities/get-section-bbox.ts","../src/tools/utilities/get-section-progress.ts","../src/tools/tool-tags.ts","../src/tools/utilities/help.ts","../src/tool-setup.ts","../src/tools/maplibre/execute-maplibre-code.ts","../src/tools/maplibre/fly-to.ts","../src/tools/maplibre/get-map-style-layers.ts","../src/tools/maplibre/get-viewport.ts","../src/tools/maplibre/set-layout-properties.ts","../src/tools/maplibre/set-paint-properties.ts","../src/tools/maplibre/set-pitch-bearing.ts","../src/tools/maplibre/zoom-in-or-out.ts","../src/utils/intent-classifier.ts","../src/utils/state-labels.ts","../src/utils/summarize.ts","../src/tools/services/locate-place.ts","../src/tools/services/resolve-location-input.ts","../src/tools/services/set-route.ts","../src/tools/services/add-waypoints-to-route.ts","../src/tools/services/discover-places.ts","../src/tools/services/find-reachable-areas.ts","../src/tools/services/get-poi-category-codes.ts","../src/tools/services/get-traffic-area-analytics.ts","../src/tools/services/get-traffic-incidents.ts","../src/tools/services/remove-waypoints-from-route.ts","../src/tools/services/replace-waypoint-in-route.ts","../src/tools/services/reverse-geocode.ts","../src/tools/state/analyse-geometries.ts","../src/tools/state/analyse-incidents.ts","../src/tools/state/analyse-places.ts","../src/tools/state/analyse-routes.ts","../src/tools/state/focus-incidents.ts","../src/tools/state/get-current-waypoints.ts","../src/tools/state/process-geometries.ts","../src/tools/state/process-places.ts","../src/tools/state/process-routes.ts","../src/tools/state/query-traffic-analytics.ts","../src/tools/state/recall-geometries.ts","../src/tools/state/recall-places.ts","../src/tools/state/recall-ranges.ts","../src/tools/state/recall-routes.ts","../src/tools/state/recall-state.ts","../src/tools/state/reset-state.ts","../src/tools/state/set-entry-mode.ts","../src/tools/state/start-traffic-incidents-monitor.ts","../src/tools/state/stop-traffic-incidents-monitor.ts","../src/tools/tomtom-map/clear-map.ts","../src/tools/tomtom-map/fit-route-section.ts","../src/tools/tomtom-map/get-shown-incidents.ts","../src/tools/tomtom-map/get-shown-places.ts","../src/tools/tomtom-map/get-shown-route-sections.ts","../src/tools/tomtom-map/get-shown-route-traffic-incidents.ts","../src/tools/tomtom-map/get-shown-routes.ts","../src/tools/tomtom-map/get-shown-waypoints.ts","../src/tools/tomtom-map/get-standard-map-styles.ts","../src/tools/tomtom-map/set-language.ts","../src/tools/tomtom-map/set-map-standard-style.ts","../src/tools/tomtom-map/set-route-theme.ts","../src/tools/tomtom-map/show-traffic-area-analytics.ts","../src/tools/tomtom-map/show-waypoints.ts","../src/tools/tomtom-map/toggle-base-map-layer-groups.ts","../src/tools/tomtom-map/toggle-pois.ts","../src/tools/tomtom-map/toggle-traffic.ts","../src/tools/tomtom-map/update-places-display.ts","../src/tools/tomtom-map/update-routes-display.ts","../src/tools/tool-registry.ts","../src/create-map-agent.ts"],"sourcesContent":["import type { ToolDefinition, ToolEntry, ToolNameHint, ToolState } from './types';\n\n/**\n * Merges default tools with user overrides.\n *\n * - `ToolEntry` (or builder) values add or replace tools in `defaults`.\n * - `false` values remove tools from `defaults`.\n * - Returns a new object — `defaults` is never mutated.\n *\n * Default entries may be {@link ToolEntryBuilder builders} that produce\n * their {@link ToolEntry} from build-time options. Builders pass through\n * untouched and are materialized later by `setupTools`.\n *\n * @param defaults - Base tool record (typically DEFAULT_TOOLS or empty).\n * @param overrides - User-provided additions, replacements, and exclusions.\n * @returns Resolved tool record ready for setupTools.\n *\n * @group Agent Toolkit\n */\nexport const resolveTools = <S extends ToolState>(\n defaults: Record<string, ToolDefinition<S>>,\n overrides?: { [K in ToolNameHint]?: ToolEntry<S> | false | undefined },\n): Record<string, ToolDefinition<S>> => {\n const result: Record<string, ToolDefinition<S>> = { ...defaults };\n\n if (!overrides) return result;\n\n for (const [name, entry] of Object.entries(overrides)) {\n if (entry) {\n result[name] = entry;\n } else {\n delete result[name];\n }\n }\n\n return result;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport { BaseMapModule, HillshadeModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport type { StateSlice } from '../../types';\n\n/**\n * State for base map display: style, language, viewport, layers, and hillshade.\n *\n * Provides direct access to the TomTomMap and MapLibre instances alongside\n * lazy-initialized BaseMapModule and HillshadeModule.\n *\n * @group Agent Toolkit\n */\nexport class BaseMapState implements StateSlice {\n private _baseMapModule?: BaseMapModule;\n private _hillshadeModule?: HillshadeModule;\n\n constructor(public readonly ttMap: TomTomMap) {}\n\n get mapLibreMap(): MapLibreMap {\n return this.ttMap.mapLibreMap;\n }\n\n // Module getters (lazy initialization)\n\n async getBaseMapModule(): Promise<BaseMapModule> {\n this._baseMapModule ??= await BaseMapModule.get(this.ttMap);\n return this._baseMapModule;\n }\n\n async getHillshadeModule(): Promise<HillshadeModule> {\n this._hillshadeModule ??= await HillshadeModule.get(this.ttMap);\n return this._hillshadeModule;\n }\n\n reset(): void {\n this._baseMapModule = undefined;\n this._hillshadeModule = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n *\n * Tiny typed event emitter used by every state slice (PlacesState, RoutingState, ...) to notify\n * subscribers when their internals change. The shape mirrors the SDK's `ModuleEvents` API\n * (`on(type, handler) => unsubscribe`, `off(type)`) so consumers see one consistent pattern across\n * the SDK and the agent toolkit. Designed to plug straight into React's `useSyncExternalStore`.\n *\n * @group Agent Toolkit\n */\n\n/**\n * Generic typed event bus. The `EventMap` parameter declares the set of event names this emitter\n * supports along with each event's payload type — `on` and `emit` are then strongly typed against it.\n *\n * Usage from outside the slice (subscribe-only):\n * ```ts\n * const unsub = state.places.events.on('entries-change', (entries) => render(entries));\n * // ...\n * unsub();\n * ```\n *\n * @group Agent Toolkit\n */\nexport class StateEvents<EventMap extends Record<string, unknown>> {\n private readonly handlers: { [K in keyof EventMap]?: ((payload: EventMap[K]) => void)[] } = {};\n\n /**\n * Subscribe to an event. Returns an unsubscribe function that removes only this handler.\n */\n on<K extends keyof EventMap>(type: K, handler: (payload: EventMap[K]) => void): () => void {\n const list = this.handlers[type] ?? (this.handlers[type] = []);\n list.push(handler);\n return () => {\n const current = this.handlers[type];\n if (!current) return;\n const idx = current.indexOf(handler);\n if (idx !== -1) current.splice(idx, 1);\n };\n }\n\n /** Remove all handlers for a given event type. Use the unsubscribe from `on` for single-handler removal. */\n off<K extends keyof EventMap>(type: K): void {\n if (this.handlers[type]) this.handlers[type]!.length = 0;\n }\n\n /**\n * Fire an event. Internal — only the owning slice should call this. We don't make it `protected`\n * because slices hold an instance via composition (not inheritance), and the alternative\n * (subclassing per slice) bloats the public API surface.\n *\n * @internal\n */\n emit<K extends keyof EventMap>(type: K, payload: EventMap[K]): void {\n const list = this.handlers[type];\n if (!list?.length) return;\n // Iterate over a snapshot so a handler that unsubscribes mid-dispatch can't cause\n // others on the same tick to be skipped.\n for (const handler of [...list]) handler(payload);\n }\n\n /** Drop every handler for every type. Used by slice `reset()` to detach lingering listeners. */\n clear(): void {\n for (const key of Object.keys(this.handlers) as (keyof EventMap)[]) {\n this.handlers[key] = [];\n }\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { CommonPlaceProps, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport { GeometriesModule, type GeometryTheme, type TomTomMap, themedGeometryConfig } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { CustomGeometriesAnalysis } from './analysis';\nimport type { CustomGeometriesEntry } from './entry';\n\n/**\n * Events fired by {@link CustomGeometriesState}. Subscribe via\n * `state.customGeometries.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type CustomGeometriesStateEvents = {\n /** History changed — entry added or cleared via `reset()`. */\n 'entries-change': readonly CustomGeometriesEntry[];\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** A new analysis was attached to (or replaced on) an entry. */\n 'analysis-added': { entryId: string; analysis: CustomGeometriesAnalysis };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n// Dedupe features by their string `id`. Features without a string id are kept as-is (cannot\n// be deduped against anything).\nconst dedupeById = (features: PolygonFeature<CommonPlaceProps>[]): PolygonFeature<CommonPlaceProps>[] => {\n const seen = new Set<string>();\n const out: PolygonFeature<CommonPlaceProps>[] = [];\n for (const feature of features) {\n const featureId = typeof feature.id === 'string' ? feature.id : undefined;\n if (featureId) {\n if (seen.has(featureId)) continue;\n seen.add(featureId);\n }\n out.push(feature);\n }\n return out;\n};\n\n// Replace the module's currently-rendered FeatureCollection with `features`. An empty list\n// clears the layer.\nconst renderReplace = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) {\n await module.clear();\n return;\n }\n await module.show({ type: 'FeatureCollection', features });\n};\n\n// Merge `features` with whatever the module is currently rendering, deduped by feature id.\n// Empty input is a no-op (leaves whatever is on the layer in place).\nconst renderMerged = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) return;\n const current = (module.getShown().geometry.features ?? []) as PolygonFeature<CommonPlaceProps>[];\n const merged = dedupeById([...current, ...features]);\n await module.show({ type: 'FeatureCollection', features: merged });\n};\n\n/**\n * State for derived / custom geometries — output of `processGeometries` and any future\n * polygon-producing tool whose result doesn't belong to a place or a reachable-range entry.\n *\n * Each entry owns a dedicated GeometriesModule, lazy-initialised on first show, mirroring\n * the per-entry display model used by {@link RangeState}. Provenance metadata captures the\n * input sources (tagged ids) and the operation that produced the polygons.\n *\n * @group Agent Toolkit\n */\nexport class CustomGeometriesState implements ShownEntriesSlice, StateSlice {\n private _entries: CustomGeometriesEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link CustomGeometriesStateEvents}. */\n readonly events = new StateEvents<CustomGeometriesStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly CustomGeometriesEntry[] {\n return this._entries;\n }\n\n get latestEntry(): CustomGeometriesEntry | undefined {\n return this._entries.at(-1);\n }\n\n /** Set of custom-geometries entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Display policy: `multiple` (default) lets several derived-geometry entries render side\n * by side; `single` enforces \"at most one entry on the map\" — switching to it auto-hides\n * older entries.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const latest = this._entries.at(-1);\n const dropped = this._entries.slice(0, -1);\n for (const entry of dropped) await this.hideEntry(entry.id);\n this._entries = latest ? [latest] : [];\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): CustomGeometriesEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new entry. If `explicitId` collides, the slice auto-suffixes (`-2`, `-3`, ...).\n * Returns the assigned id. Under `single` mode the new entry replaces every older one.\n */\n addEntry(\n features: PolygonFeature<CommonPlaceProps>[],\n provenance: CustomGeometriesEntry['provenance'],\n label: string,\n explicitId?: string,\n ): string {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n for (const entry of this._entries) void this.hideEntry(entry.id);\n this._entries = [];\n }\n const fallback = `geometries-${this._entries.length}`;\n const entryId = explicitId ? this._uniqueEntryId(explicitId) : fallback;\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n features,\n provenance,\n });\n this.events.emit('entries-change', this._entries);\n return entryId;\n }\n\n /** Attach an analysis to an existing entry (replace if a result of the same name exists). */\n addAnalysisToEntry(entryId: string, analysis: CustomGeometriesAnalysis): boolean {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n entry._analysis ??= [];\n const existingIdx = entry._analysis.findIndex((a) => a.name === analysis.name);\n if (existingIdx >= 0) {\n entry._analysis[existingIdx] = analysis;\n } else {\n entry._analysis.push(analysis);\n }\n this.events.emit('analysis-added', { entryId, analysis });\n this.events.emit('entries-change', this._entries);\n return true;\n }\n\n /** Lazy-init the entry's GeometriesModule, applying / re-applying the requested theme. */\n async getEntryGeometriesModule(entryId: string, theme: GeometryTheme = 'outline'): Promise<GeometriesModule> {\n const entry = this._requireEntry(entryId);\n // Module-level `theme` flows into `prepareGeometryForDisplay` and is used as the\n // effective theme for every feature that doesn't carry its own `properties.theme`.\n const config = { ...themedGeometryConfig(), theme };\n if (!entry._module) {\n entry._module = await GeometriesModule.get(this._ttMap, config);\n entry._shownTheme = theme;\n } else if (entry._shownTheme !== theme) {\n entry._module.applyConfig(config);\n entry._shownTheme = theme;\n }\n return entry._module;\n }\n\n /**\n * Render this entry's polygons. `mode` is scoped to this entry's own layer:\n * - `'replace'` (default) — show exactly `entry.features`, clearing whatever the\n * entry's module had rendered before.\n * - `'add'` — merge `entry.features` with the polygons currently shown on this entry's\n * layer (deduped by feature id). Mostly only meaningful when re-showing the same entry.\n *\n * Cross-entry visibility is handled separately: under `entryMode === 'single'` other\n * shown entries are hidden first; otherwise the caller uses `hideEntry` / the\n * tools' `hidePreviousEntries` option.\n */\n async showEntry(\n entryId: string,\n theme: GeometryTheme = 'outline',\n mode: 'add' | 'replace' = 'replace',\n ): Promise<void> {\n const entry = this._requireEntry(entryId);\n await this._hideOthersUnderSingleMode(entryId);\n const module = await this.getEntryGeometriesModule(entryId, theme);\n const features = entry.features as PolygonFeature<CommonPlaceProps>[];\n if (mode === 'add') {\n await renderMerged(module, features);\n } else {\n await renderReplace(module, features);\n }\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n private async _hideOthersUnderSingleMode(entryId: string): Promise<void> {\n if (this._entryMode !== 'single') return;\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry || !entry._shown) return;\n if (entry._module) await entry._module.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Drop a single entry from history (clears its module first). No-op when unknown. */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n }\n\n private _requireEntry(entryId: string): CustomGeometriesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`CustomGeometriesState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n private _uniqueEntryId(requested: string): string {\n const taken = new Set(this._entries.map((entry) => entry.id));\n if (!taken.has(requested)) return requested;\n for (let i = 2; ; i++) {\n const candidate = `${requested}-${i}`;\n if (!taken.has(candidate)) return candidate;\n }\n }\n\n reset(): void {\n for (const entry of this._entries) entry._module?.clear();\n this._entries = [];\n this.events.emit('entries-change', this._entries);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n *\n * Compact snapshots of user-visible state, used to inform the LLM about changes that happened\n * outside the conversation (e.g. the user clicked a recents shortcut in the demo's UI).\n *\n * Pattern of use:\n * 1. After every assistant turn, call `getStateDigest(state)` and stash the result.\n * 2. Before sending the next user prompt, call `getStateDigest` again and pass both into\n * `formatStateDigestDiff`. If a non-empty string comes back, prepend it to the prompt so\n * the LLM sees what changed since its last response.\n *\n * Attribution is implicit: anything the LLM's tool calls did happens during the assistant turn\n * (so it's already reflected in the post-turn snapshot). Anything that changes between turns is,\n * by construction, UI-driven.\n */\n\nimport type { ToolState } from '../types';\nimport type { EntryMode } from './state';\n\n/**\n * Compact snapshot of the user-visible parts of `ToolState`. Designed to be diffable: every field\n * is serialisable to JSON and stable for `===` comparison when the underlying state hasn't changed.\n *\n * @group Agent Toolkit\n */\nexport type StateDigest = {\n places: {\n /** Entry ids currently rendered as individual pins, sorted ascending. */\n shownAsPin: string[];\n /** Entry ids currently rendered as base-map POIs, sorted ascending. */\n shownAsBaseMap: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n routes: {\n /** Routes-entry ids currently drawn on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n ranges: {\n /** Range-entry ids currently drawn on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n traffic: {\n /** Whether a TrafficAreaAnalytics visualization is active. */\n analyticsActive: boolean;\n };\n incidents: {\n /** Traffic-incidents entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Entry ids currently being polled by the per-entry network monitor, sorted ascending. */\n monitored: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n};\n\n/**\n * Snapshot the user-visible state surface. Call after every assistant turn (to record the\n * \"agent-known\" baseline) and again before each user prompt (to detect what changed since).\n *\n * @group Agent Toolkit\n */\nexport const getStateDigest = (state: ToolState): StateDigest => ({\n places: {\n shownAsPin: [...state.places.shownAsPinIds].sort(),\n shownAsBaseMap: [...state.places.shownAsBaseMapIds].sort(),\n entryMode: state.places.entryMode,\n },\n routes: { shown: [...state.routing.shownEntryIds].sort(), entryMode: state.routing.entryMode },\n ranges: { shown: [...state.ranges.shownEntryIds].sort(), entryMode: state.ranges.entryMode },\n traffic: { analyticsActive: state.trafficAreaAnalytics.lastAreaAnalytics !== undefined },\n incidents: {\n shown: [...state.trafficIncidents.shownEntryIds].sort(),\n monitored: state.trafficIncidents.entries\n .filter((e) => e._monitor?.status === 'running')\n .map((e) => e.id)\n .sort(),\n entryMode: state.trafficIncidents.entryMode,\n },\n});\n\nconst arraysEqual = (a: readonly string[], b: readonly string[]): boolean => {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n};\n\nconst formatPlacesDiff = (prev: StateDigest['places'], next: StateDigest['places']): string | undefined => {\n const fragments: string[] = [];\n if (!arraysEqual(prev.shownAsPin, next.shownAsPin)) {\n fragments.push(\n next.shownAsPin.length === 0\n ? 'no places shown as pins'\n : `places shown as pins: ${next.shownAsPin.map((id) => `\"${id}\"`).join(', ')}`,\n );\n }\n if (!arraysEqual(prev.shownAsBaseMap, next.shownAsBaseMap)) {\n fragments.push(\n next.shownAsBaseMap.length === 0\n ? 'no places shown as base-map POIs'\n : `places shown as base-map POIs: ${next.shownAsBaseMap.map((id) => `\"${id}\"`).join(', ')}`,\n );\n }\n return fragments.length ? fragments.join('; ') : undefined;\n};\n\n// Diffs a sorted-array snapshot of \"currently shown\" entry ids and produces a one-line\n// summary. Used for routes / ranges where multiple entries can be on the map under\n// `multiple` mode — the digest can't be a scalar anymore.\nconst formatShownSetDiff = (label: string, prev: readonly string[], next: readonly string[]): string | undefined => {\n if (arraysEqual(prev, next)) return undefined;\n if (next.length === 0) return `${label} cleared`;\n return `${label} shown: ${next.map((id) => `\"${id}\"`).join(', ')}`;\n};\n\n/**\n * Produce a compact, LLM-readable summary of the differences between two digests, or `null` when\n * nothing changed. The output is short enough to prepend to a user prompt without bloating the\n * conversation; longer per-field diffs are deliberately omitted.\n *\n * @example\n * ```ts\n * const prev = getStateDigest(state);\n * // ... user clicks around in the UI ...\n * const diff = formatStateDigestDiff(prev, getStateDigest(state));\n * // → '[map state changed since last response: places shown as pins: \"amsterdam-cinemas\"; route now \"routes-2\"]'\n * ```\n *\n * @group Agent Toolkit\n */\nexport const formatStateDigestDiff = (prev: StateDigest, next: StateDigest): string | null => {\n const fragments: string[] = [];\n\n const placesFragment = formatPlacesDiff(prev.places, next.places);\n if (placesFragment) fragments.push(placesFragment);\n\n const routeFragment = formatShownSetDiff('routes', prev.routes.shown, next.routes.shown);\n if (routeFragment) fragments.push(routeFragment);\n\n const rangeFragment = formatShownSetDiff('ranges', prev.ranges.shown, next.ranges.shown);\n if (rangeFragment) fragments.push(rangeFragment);\n\n if (prev.traffic.analyticsActive !== next.traffic.analyticsActive) {\n fragments.push(next.traffic.analyticsActive ? 'traffic analytics now active' : 'traffic analytics cleared');\n }\n\n const incidentsShownFragment = formatShownSetDiff('incidents', prev.incidents.shown, next.incidents.shown);\n if (incidentsShownFragment) fragments.push(incidentsShownFragment);\n\n const monitoredFragment = formatShownSetDiff(\n 'incidents monitor',\n prev.incidents.monitored,\n next.incidents.monitored,\n );\n if (monitoredFragment) fragments.push(monitoredFragment);\n\n // Entry-mode flips are surfaced last so the LLM sees them as policy\n // changes, separate from \"what's on the map right now\".\n if (prev.places.entryMode !== next.places.entryMode) {\n fragments.push(`places entryMode → ${next.places.entryMode}`);\n }\n if (prev.routes.entryMode !== next.routes.entryMode) {\n fragments.push(`routes entryMode → ${next.routes.entryMode}`);\n }\n if (prev.ranges.entryMode !== next.ranges.entryMode) {\n fragments.push(`ranges entryMode → ${next.ranges.entryMode}`);\n }\n if (prev.incidents.entryMode !== next.incidents.entryMode) {\n fragments.push(`incidents entryMode → ${next.incidents.entryMode}`);\n }\n\n if (fragments.length === 0) return null;\n return `[map state changed since last response: ${fragments.join('; ')}]`;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport { POIsModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\n\n/**\n * State for the map POI layer (points of interest overlay).\n *\n * Holds the lazy-initialized POIsModule for showing, hiding, and filtering\n * the built-in POI categories rendered on the base map.\n *\n * @group Agent Toolkit\n */\nexport class MapPOIsState implements StateSlice {\n private _poisModule?: POIsModule;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getter (lazy initialization)\n\n async getPOIsModule(): Promise<POIsModule> {\n this._poisModule ??= await POIsModule.get(this._ttMap);\n return this._poisModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get poisModule(): POIsModule | undefined {\n return this._poisModule;\n }\n\n reset(): void {\n this._poisModule = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { CommonPlaceProps, Place, Places, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport {\n GeometriesModule,\n type GeometryTheme,\n type PlaceConnectionDisplay,\n PlacesModule,\n type TomTomMap,\n themedGeometryConfig,\n} from '@tomtom-org/maps-sdk/map';\nimport { geometryData } from '@tomtom-org/maps-sdk/services';\nimport type { StateSlice } from '../../types';\nimport { StateEvents } from '../events';\nimport type { EntryMode } from '../state';\nimport type { PlacesAnalysis } from './analysis';\nimport type { PlacesEntry } from './entry';\n\n// Geometry Data service accepts up to 20 ids per request; we intentionally cap our batches\n// at 5 to keep individual requests small (lower per-call latency, easier to fan out in\n// parallel without hitting rate limits). Any larger input set is split into multiple chunks.\nconst GEOMETRY_DATA_BATCH_SIZE = 5;\n\n/** How an entry is rendered on the map: as individual pins, base-map POIs, or clustered pins. */\nexport type PlacesMarkerType = 'pin' | 'base-map' | 'pin-clustered';\n\n/**\n * Events fired by {@link PlacesState}. Subscribe via `state.places.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type PlacesStateEvents = {\n /** History changed — entry added, replaced, or cleared via `reset()`. Payload: the full entries snapshot. */\n 'entries-change': readonly PlacesEntry[];\n /** Set of entries currently rendered on the map changed (or marker type swapped). */\n 'shown-change': ReadonlySet<string>;\n /** A new analysis was attached to (or replaced on) an entry. */\n 'analysis-added': { entryId: string; analysis: PlacesAnalysis };\n /** Boundary polygons were fetched and cached on an entry. */\n 'geometries-change': { entryId: string; count: number };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n// Maps the public `PlacesMarkerType` to the property name on `entry._modules`\n// where the corresponding PlacesModule lives. Centralised so the public string\n// values can include hyphens (`pin-clustered`) while the internal slot uses\n// camelCase (`pinClustered`).\nconst markerTypeSlot = (markerType: PlacesMarkerType): 'pin' | 'baseMap' | 'pinClustered' => {\n switch (markerType) {\n case 'pin':\n return 'pin';\n case 'base-map':\n return 'baseMap';\n case 'pin-clustered':\n return 'pinClustered';\n }\n};\n\n// Dedupe features by their string `id`. Features without a string id are kept as-is.\nconst dedupeById = (features: PolygonFeature<CommonPlaceProps>[]): PolygonFeature<CommonPlaceProps>[] => {\n const seen = new Set<string>();\n const out: PolygonFeature<CommonPlaceProps>[] = [];\n for (const feature of features) {\n const featureId = typeof feature.id === 'string' ? feature.id : undefined;\n if (featureId) {\n if (seen.has(featureId)) continue;\n seen.add(featureId);\n }\n out.push(feature);\n }\n return out;\n};\n\n// Replace the module's currently-rendered FeatureCollection with `features`. An empty list\n// clears the layer.\nconst renderReplace = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) {\n await module.clear();\n return;\n }\n await module.show({ type: 'FeatureCollection', features });\n};\n\n// Merge `features` with whatever the module is currently rendering, deduped by feature id.\n// Empty input is a no-op (leaves whatever is on the layer in place).\nconst renderMerged = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) return;\n const current = (module.getShown().geometry.features ?? []) as PolygonFeature<CommonPlaceProps>[];\n const merged = dedupeById([...current, ...features]);\n await module.show({ type: 'FeatureCollection', features: merged });\n};\n\n/**\n * State for the places layer: an append-only history of place results plus per-entry display\n * modules.\n *\n * Each entry owns its own PlacesModule (per marker theme: pin / base-map / pin-clustered) and\n * a dedicated GeometriesModule, lazy-initialised on first show. The slice no longer holds\n * shared modules — display state lives on the entry, so two entries can be rendered side by\n * side under different themes without sharing a single source.\n *\n * @group Agent Toolkit\n */\nexport class PlacesState implements StateSlice {\n private _entries: PlacesEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link PlacesStateEvents}. */\n readonly events = new StateEvents<PlacesStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n /**\n * Display policy: `multiple` (default) keeps every history entry available to render\n * simultaneously; `single` enforces \"at most one entry on the map at a time\" and trims\n * the history down to the latest entry whenever it's set.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every entry\n * older than the latest, drop them from history, and emit `entries-change` so listeners\n * (the recents rail, recall tools, …) re-render. No-op when the mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const latest = this._entries.at(-1);\n const dropped = this._entries.slice(0, -1);\n for (const entry of dropped) await this._hideEntry(entry);\n this._entries = latest ? [latest] : [];\n this.events.emit('entries-change', this._entries);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n this.events.emit('mode-change', mode);\n }\n\n /**\n * Lazy-init and return the entry's PlacesModule for the given marker theme. Each entry\n * keeps a separate module per theme so flipping themes for one entry doesn't disturb\n * another. Throws when the entry id is unknown.\n */\n async getEntryPlacesModule(entryId: string, markerType: PlacesMarkerType): Promise<PlacesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n const slot = markerTypeSlot(markerType);\n let module = entry._modules[slot];\n if (!module) {\n module = await PlacesModule.get(this._ttMap, { theme: markerType });\n entry._modules[slot] = module;\n }\n return module;\n }\n\n /**\n * Lazy-init and return the entry's place-geometries module (for POI footprints, municipality\n * outlines). Re-applies themed config when the theme changes. Throws on unknown entry id.\n */\n async getEntryGeometriesModule(entryId: string, theme: GeometryTheme = 'outline'): Promise<GeometriesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n // Module-level `theme` flows into `prepareGeometryForDisplay` and is used as the\n // effective theme for every feature that doesn't carry its own `properties.theme`.\n const config = { ...themedGeometryConfig(), theme };\n if (!entry._modules.geometries) {\n entry._modules.geometries = await GeometriesModule.get(this._ttMap, config);\n } else {\n entry._modules.geometries.applyConfig(config);\n }\n return entry._modules.geometries;\n }\n\n private _requireEntry(entryId: string): PlacesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`PlacesState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n get entries(): readonly PlacesEntry[] {\n return this._entries;\n }\n\n /**\n * This is used purely to pass data between tools in a context-dependent way,\n * so we assume the most recent entry is the relevant one to show on the map.\n */\n get latestPlace(): Place[] | undefined {\n return this._entries.at(-1)?.places;\n }\n\n addPlaceResult(\n place: Place | Places,\n label: string,\n explicitId?: string,\n connections?: PlaceConnectionDisplay[],\n geometries?: PolygonFeature<CommonPlaceProps>[],\n ): string {\n // Under `single` mode the new entry replaces everything older. Hide\n // those entries' modules synchronously where we can — the await is\n // done lazily inside `_hideEntry` (it returns a promise we can't\n // await here without making the whole API async). Listeners only see\n // the new entry in `entries-change`.\n if (this._entryMode === 'single' && this._entries.length > 0) {\n for (const entry of this._entries) void this._hideEntry(entry);\n this._entries = [];\n }\n const fallback = `places-${this._entries.length}`;\n const entryId = explicitId ? this._uniqueEntryId(explicitId) : fallback;\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n places: 'features' in place ? place.features : [place],\n ...(connections?.length && { connections }),\n ...(geometries?.length && { geometries }),\n });\n this.events.emit('entries-change', this._entries);\n // Surface geometries via the same `geometries-change` event consumers\n // already use for `fetchGeometriesForEntry`, so a place panel that\n // listens once picks them up regardless of which writer added them.\n if (geometries?.length) {\n this.events.emit('geometries-change', { entryId, count: geometries.length });\n }\n return entryId;\n }\n\n // Returns `requested` unchanged when free, otherwise appends `-2`, `-3`, ... until a free id\n // is found. Lets the LLM hand us a semantic id without worrying about collisions across calls.\n private _uniqueEntryId(requested: string): string {\n const taken = new Set(this._entries.map((entry) => entry.id));\n if (!taken.has(requested)) return requested;\n for (let i = 2; ; i++) {\n const candidate = `${requested}-${i}`;\n if (!taken.has(candidate)) return candidate;\n }\n }\n\n /**\n * Attach an analysis result to an existing entry. Analysis names are unique\n * within a single entry — adding one with an existing name replaces it.\n * Returns true on success, false if the entry doesn't exist.\n */\n addAnalysisToEntry(entryId: string, analysis: PlacesAnalysis): boolean {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n entry._analysis ??= [];\n const existingIdx = entry._analysis.findIndex((a) => a.name === analysis.name);\n if (existingIdx >= 0) {\n entry._analysis[existingIdx] = analysis;\n } else {\n entry._analysis.push(analysis);\n }\n this.events.emit('analysis-added', { entryId, analysis });\n // Entries-change tracks the entry array reference *and* its observable contents — analyses\n // mutate an entry's `_analysis` field, so re-emit so consumers re-render.\n this.events.emit('entries-change', this._entries);\n return true;\n }\n\n // Scans entries (newest first) for a Place by its feature id. Each entry\n // lazily caches an id → Place record on first miss so repeated lookups stay\n // O(1) per entry. Safe because entry.places is effectively immutable after\n // addPlaceResult — no mutation path invalidates the index.\n findPlaceById(placeId: string): { place: Place; entryId: string } | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n const entry = this._entries[i];\n if (!entry._byId) {\n entry._byId = {};\n for (const place of entry.places) entry._byId[place.id] = place;\n }\n const place = entry._byId[placeId];\n if (place) return { place, entryId: entry.id };\n }\n return undefined;\n }\n\n /**\n * Returns the cached boundary polygon for a place id, or undefined if no geometry has been\n * fetched for it yet. Builds a lazy place-id → feature index per entry on first lookup.\n */\n getGeometryForPlace(placeId: string): PolygonFeature<CommonPlaceProps> | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n const entry = this._entries[i];\n if (!entry.geometries?.length) continue;\n if (!entry._geometryByPlaceId) entry._geometryByPlaceId = this._buildGeometryIndex(entry);\n const geometry = entry._geometryByPlaceId[placeId];\n if (geometry) return geometry;\n }\n return undefined;\n }\n\n /**\n * Fetches the boundary polygon for a place from the Geometry Data service and stores it on\n * the owning entry. Returns the cached feature if already fetched, or undefined if the place\n * is unknown or has no geometry data source.\n */\n async fetchPlaceGeometry(placeId: string): Promise<PolygonFeature<CommonPlaceProps> | undefined> {\n const cached = this.getGeometryForPlace(placeId);\n if (cached) return cached;\n\n const lookup = this.findPlaceById(placeId);\n if (!lookup) return undefined;\n const geometryDataSourceId = lookup.place.properties.dataSources?.geometry?.id;\n if (!geometryDataSourceId) return undefined;\n\n const result = await geometryData({ geometries: [lookup.place] });\n const feature = result.features[0];\n if (!feature) return undefined;\n\n const entry = this._entries.find((e) => e.id === lookup.entryId);\n if (entry) {\n entry.geometries ??= [];\n entry.geometries.push(feature);\n entry._geometryByPlaceId ??= {};\n entry._geometryByPlaceId[placeId] = feature;\n this.events.emit('geometries-change', { entryId: entry.id, count: entry.geometries.length });\n }\n return feature;\n }\n\n /**\n * Batch-fetches boundary polygons for every place in an entry that has a geometry data source\n * but is not yet cached. Issues one Geometry Data request per chunk of {@link GEOMETRY_DATA_BATCH_SIZE}\n * place ids (the API caps each request) and stores the merged result on `entry.geometries`.\n * Returns the full geometries list for the entry, or an empty array if the entry is unknown.\n */\n async fetchGeometriesForEntry(entryId: string): Promise<PolygonFeature<CommonPlaceProps>[]> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return [];\n\n const alreadyFetched = new Set<string>();\n for (const feature of entry.geometries ?? []) {\n if (typeof feature.id === 'string') alreadyFetched.add(feature.id);\n }\n\n // Dedupe by geometry-data-source id so a place repeated in the entry (or sharing a footprint)\n // contributes one request, not several.\n const placesNeedingFetch: Place[] = [];\n const queuedGeometryIds = new Set<string>();\n for (const place of entry.places) {\n const geometryDataSourceId = place.properties.dataSources?.geometry?.id;\n if (!geometryDataSourceId) continue;\n if (alreadyFetched.has(geometryDataSourceId)) continue;\n if (queuedGeometryIds.has(geometryDataSourceId)) continue;\n queuedGeometryIds.add(geometryDataSourceId);\n placesNeedingFetch.push(place);\n }\n\n if (placesNeedingFetch.length === 0) return entry.geometries ?? [];\n\n const batches: Place[][] = [];\n for (let i = 0; i < placesNeedingFetch.length; i += GEOMETRY_DATA_BATCH_SIZE) {\n batches.push(placesNeedingFetch.slice(i, i + GEOMETRY_DATA_BATCH_SIZE));\n }\n const results = await Promise.all(batches.map((batch) => geometryData({ geometries: batch })));\n\n const merged: PolygonFeature<CommonPlaceProps>[] = [...(entry.geometries ?? [])];\n for (const result of results) merged.push(...result.features);\n\n entry.geometries = merged;\n // Invalidate the lazy place-id index so the next lookup rebuilds it from the fuller list.\n entry._geometryByPlaceId = undefined;\n this.events.emit('geometries-change', { entryId: entry.id, count: merged.length });\n return merged;\n }\n\n /**\n * Render the given polygons via the entry's GeometriesModule. The `mode` flag is\n * scoped to **this entry's layer only** — it does not touch other entries (use\n * the slice's per-entry hide/show methods for cross-entry visibility).\n *\n * - `mode: 'replace'` (default) — calls `module.show` with exactly these features, replacing\n * whatever was previously on this entry's layer. An empty array clears the layer.\n * - `mode: 'add'` — merges the new features with the polygons currently shown on this\n * entry's layer (deduped by `feature.id`) and re-renders the union. An empty array is a\n * no-op.\n *\n * `entryId` defaults to the latest entry that owns geometries — handy when callers only ever\n * render the most-recently produced footprints. Pass an explicit id to target an older one.\n */\n async showPlaceGeometries(\n features: PolygonFeature<CommonPlaceProps>[],\n theme: GeometryTheme = 'outline',\n mode: 'add' | 'replace' = 'replace',\n entryId?: string,\n ): Promise<void> {\n const targetId = entryId ?? this._latestGeometriesEntryId();\n if (!targetId) return;\n const module = await this.getEntryGeometriesModule(targetId, theme);\n if (mode === 'add') {\n await renderMerged(module, features);\n } else {\n await renderReplace(module, features);\n }\n }\n\n // Walks entries newest-first looking for the most recent one that has any\n // boundary polygons attached — that's the natural default for callers that\n // didn't pin a specific entry id (e.g. the legacy showPlaceGeometries surface).\n private _latestGeometriesEntryId(): string | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n if (this._entries[i].geometries?.length) return this._entries[i].id;\n }\n return this._entries.at(-1)?.id;\n }\n\n // Each entry's geometries are stored as an array (mirroring the geometryData response shape);\n // build an O(1) place-id → feature lookup on first read.\n private _buildGeometryIndex(entry: PlacesEntry): Record<string, PolygonFeature<CommonPlaceProps>> {\n const byGeometryId = new Map<string, PolygonFeature<CommonPlaceProps>>();\n for (const feature of entry.geometries ?? []) {\n if (typeof feature.id === 'string') byGeometryId.set(feature.id, feature);\n }\n const index: Record<string, PolygonFeature<CommonPlaceProps>> = {};\n for (const place of entry.places) {\n const geometryId = place.properties.dataSources?.geometry?.id;\n if (!geometryId) continue;\n const feature = byGeometryId.get(geometryId);\n if (feature) index[place.id] = feature;\n }\n return index;\n }\n\n /** IDs of all entries currently shown on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) {\n if (entry._shownAs) out.add(entry.id);\n }\n return out;\n }\n\n /** IDs of entries currently shown as individual pins. */\n get shownAsPinIds(): ReadonlySet<string> {\n return this._idsShownAs('pin');\n }\n\n /** IDs of entries currently shown as base-map POIs. */\n get shownAsBaseMapIds(): ReadonlySet<string> {\n return this._idsShownAs('base-map');\n }\n\n /** IDs of entries currently shown as clustered pins. */\n get shownAsPinClusteredIds(): ReadonlySet<string> {\n return this._idsShownAs('pin-clustered');\n }\n\n private _idsShownAs(markerType: PlacesMarkerType): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) {\n if (entry._shownAs === markerType) out.add(entry.id);\n }\n return out;\n }\n\n /** Marker type for a specific entry, or undefined if it is not shown. */\n getShownMarkerType(id: string): PlacesMarkerType | undefined {\n return this._entries.find((entry) => entry.id === id)?._shownAs;\n }\n\n /** Return the union of all features belonging to currently-shown entries. */\n getShownFeatures(): Place[] {\n const out: Place[] = [];\n for (const entry of this._entries) {\n if (entry._shownAs) out.push(...entry.places);\n }\n return out;\n }\n\n /**\n * Replace the shown set for `markerType` with exactly these ids and re-render.\n * Entries currently shown as `markerType` but absent from `ids` are hidden;\n * entries in `ids` are switched to `markerType` (lazy-creating their module).\n */\n async setShownEntries(ids: readonly string[], markerType: PlacesMarkerType): Promise<Place[]> {\n const idSet = new Set(ids);\n const targets: PlacesEntry[] = [];\n const toHide: PlacesEntry[] = [];\n for (const entry of this._entries) {\n if (idSet.has(entry.id)) {\n targets.push(entry);\n } else if (entry._shownAs === markerType) {\n toHide.push(entry);\n }\n }\n for (const entry of toHide) await this._hideEntry(entry);\n for (const entry of targets) await this._showEntryAs(entry, markerType);\n this.events.emit('shown-change', this.shownEntryIds);\n return this.getShownFeatures();\n }\n\n /**\n * Add ids to the shown set under `markerType`. Any id previously shown as\n * a different marker type is moved (its previous module is cleared, the\n * new theme's module renders).\n *\n * Under `entryMode === 'single'` everything else is hidden first, so the\n * effective behaviour collapses to \"show exactly the last id in the list\".\n */\n async addShownEntries(ids: readonly string[], markerType: PlacesMarkerType): Promise<Place[]> {\n if (this._entryMode === 'single') {\n const lastId = ids.at(-1);\n return lastId ? this.setShownEntries([lastId], markerType) : this.getShownFeatures();\n }\n const targets = ids.flatMap((id) => {\n const entry = this._entries.find((e) => e.id === id);\n return entry ? [entry] : [];\n });\n const changed = targets.filter((entry) => entry._shownAs !== markerType);\n for (const entry of changed) await this._showEntryAs(entry, markerType);\n if (changed.length > 0) this.events.emit('shown-change', this.shownEntryIds);\n return this.getShownFeatures();\n }\n\n /** Remove ids from the shown set and clear their modules. */\n async removeShownEntries(ids: readonly string[]): Promise<Place[]> {\n const targets = ids.flatMap((id) => {\n const entry = this._entries.find((e) => e.id === id);\n return entry?._shownAs ? [entry] : [];\n });\n for (const entry of targets) await this._hideEntry(entry);\n if (targets.length > 0) this.events.emit('shown-change', this.shownEntryIds);\n return this.getShownFeatures();\n }\n\n /** Clear everything from the map (does not touch entries history). */\n async clearShownEntries(): Promise<void> {\n const shown = this._entries.filter((entry) => entry._shownAs);\n if (shown.length === 0) return;\n for (const entry of shown) await this._hideEntry(entry);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n // Clears the rendered boundary polygons for the given entries (or every entry when `ids`\n // is omitted). Cached `entry.geometries` features are kept so re-showing is instant.\n async clearShownGeometries(ids?: readonly string[]): Promise<void> {\n const targets = ids\n ? ids.flatMap((id) => {\n const entry = this._entries.find((e) => e.id === id);\n return entry?._modules?.geometries ? [entry] : [];\n })\n : this._entries.filter((entry) => entry._modules?.geometries);\n if (targets.length === 0) return;\n for (const entry of targets) {\n // biome-ignore lint/style/noNonNullAssertion: filter guarantees the module exists\n await entry._modules!.geometries!.clear();\n this.events.emit('geometries-change', { entryId: entry.id, count: 0 });\n }\n }\n\n /**\n * Drop a single entry from history. Hides it from the map first (clears every theme module\n * it owns) then removes it from `_entries`. Emits `entries-change` and, when the entry was\n * shown, `shown-change`. No-op when the id is unknown.\n */\n async removeEntry(id: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === id);\n if (idx === -1) return;\n const entry = this._entries[idx];\n const wasShown = !!entry._shownAs;\n if (entry._modules) {\n await entry._modules.pin?.clear();\n await entry._modules.baseMap?.clear();\n await entry._modules.pinClustered?.clear();\n await entry._modules.geometries?.clear();\n }\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n if (wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n // Brings an entry up under `markerType`: clears the previous theme's module\n // (if any), lazy-inits the target theme's module, and pushes the entry's\n // places onto it. Idempotent — calling twice with the same theme just\n // re-pushes the same data.\n private async _showEntryAs(entry: PlacesEntry, markerType: PlacesMarkerType): Promise<void> {\n if (entry._shownAs && entry._shownAs !== markerType) {\n const prev = entry._modules?.[markerTypeSlot(entry._shownAs)];\n if (prev) await prev.clear();\n }\n const module = await this.getEntryPlacesModule(entry.id, markerType);\n await module.show({ type: 'FeatureCollection', features: entry.places } as Places);\n // Connections (when stored on the entry) re-render with the places.\n if (entry.connections?.length) {\n await module.showConnections(entry.connections);\n }\n entry._shownAs = markerType;\n }\n\n // Hides whichever theme is currently rendering this entry. No-op if\n // already hidden.\n private async _hideEntry(entry: PlacesEntry): Promise<void> {\n if (!entry._shownAs) return;\n const module = entry._modules?.[markerTypeSlot(entry._shownAs)];\n if (module) await module.clear();\n entry._shownAs = undefined;\n }\n\n reset(): void {\n // Modules can't be unmounted from the map without their `destroy()`\n // hook (not part of the SDK surface today), so we clear them so the\n // map looks empty before dropping the references. Layers stay parked\n // on the style — same trade-off the previous shared-module reset had.\n for (const entry of this._entries) {\n if (entry._modules) {\n entry._modules.pin?.clear();\n entry._modules.baseMap?.clear();\n entry._modules.pinClustered?.clear();\n entry._modules.geometries?.clear();\n }\n }\n this._entries = [];\n this.events.emit('entries-change', this._entries);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport {\n GeometriesModule,\n type GeometryTheme,\n PlacesModule,\n reachableRangeGeometryConfig,\n type TomTomMap,\n} from '@tomtom-org/maps-sdk/map';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { RangesEntry } from './entry';\n\n/**\n * Events fired by {@link RangeState}. Subscribe via `state.ranges.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type RangeStateEvents = {\n /** Range history changed — entry added or cleared via `reset()`. */\n 'entries-change': readonly RangesEntry[];\n /** Set of ranges entries currently rendered on the map changed (multi-show under `multiple`). */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * State for reachable range results.\n *\n * Stores semantic summaries of calculated ranges (origin, budgets).\n * Also retains raw polygon geometry internally for use in geometry searches\n * (e.g. discoverPlaces withinRange). Displayed on the map via GeometriesModule.\n *\n * @group Agent Toolkit\n */\nexport class RangeState implements ShownEntriesSlice {\n private _entries: RangesEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link RangeStateEvents}. */\n readonly events = new StateEvents<RangeStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n /** Set of ranges-entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Display policy: `multiple` (default) lets several reachable-area entries render side\n * by side; `single` forces \"at most one ranges entry on the map\" — `markEntryShown`\n * then hides any previously-shown entry first.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every\n * non-latest entry, drop them from history, and emit `entries-change`. No-op when the\n * mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const latest = this._entries.at(-1);\n const dropped = this._entries.slice(0, -1);\n for (const entry of dropped) await this.hideEntry(entry.id);\n this._entries = latest ? [latest] : [];\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n /**\n * Lazy-init the entry's GeometriesModule (for the isochrone/isodistance polygons),\n * applying the requested theme. Throws on unknown id.\n */\n async getEntryGeometriesModule(entryId: string, theme: GeometryTheme = 'outline'): Promise<GeometriesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n if (!entry._modules.geometries) {\n entry._modules.geometries = await GeometriesModule.get(\n this._ttMap,\n reachableRangeGeometryConfig(undefined, theme),\n );\n entry._shownTheme = theme;\n } else if (entry._shownTheme !== theme) {\n entry._modules.geometries.applyConfig(reachableRangeGeometryConfig(undefined, theme));\n entry._shownTheme = theme;\n }\n return entry._modules.geometries;\n }\n\n /**\n * Lazy-init the entry's origin-pin PlacesModule. Each entry maintains its own pin module\n * so multiple ranges entries can be on the map simultaneously without the pins of one\n * overwriting another.\n */\n async getEntryPlacesModule(entryId: string): Promise<PlacesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n if (!entry._modules.places) {\n entry._modules.places = await PlacesModule.get(this._ttMap);\n }\n return entry._modules.places;\n }\n\n /**\n * Mark this entry as shown — records `_shown = true` and emits `shown-change`. The\n * caller is expected to push features into the lazy modules itself; this method only\n * keeps the `_shown` flag in sync with what the user sees.\n *\n * Under `entryMode === 'single'` any other shown entry is hidden first so two range\n * layers don't overlap.\n */\n async markEntryShown(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n if (this._entryMode === 'single') {\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Hide every module on the entry (clear). No-op when the entry is unknown. */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry || !entry._shown) return;\n if (entry._modules?.geometries) await entry._modules.geometries.clear();\n if (entry._modules?.places) await entry._modules.places.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Drop a single entry from history. Hides it from the map first (clears its geometries +\n * origin-pin modules) then removes it from `_entries`. Emits `entries-change`. No-op when\n * the id is unknown.\n */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n }\n\n private _requireEntry(entryId: string): RangesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`RangeState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n get entries(): readonly RangesEntry[] {\n return this._entries;\n }\n\n get latestEntry(): RangesEntry | undefined {\n return this._entries.at(-1);\n }\n\n addEntry(entry: Omit<RangesEntry, 'id' | 'timestamp'>): string {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n for (const existing of this._entries) void this.hideEntry(existing.id);\n this._entries = [];\n }\n const id = `ranges-${this._entries.length}`;\n this._entries.push({\n id,\n timestamp: Date.now(),\n ...entry,\n });\n this.events.emit('entries-change', this._entries);\n return id;\n }\n\n reset(): void {\n for (const entry of this._entries) {\n entry._modules?.geometries?.clear();\n entry._modules?.places?.clear();\n }\n this._entries = [];\n this.events.emit('entries-change', this._entries);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { Routes, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { PlanningWaypoint, RoutingModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { RoutesAnalysis } from './analysis';\nimport type { RouteParams, RoutesEntry } from './entry';\n\n/**\n * Events fired by {@link RoutingState}. Subscribe via `state.routing.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type RoutingStateEvents = {\n /** Route history changed — new calculation stored, analysis attached, or cleared via `reset()`. */\n 'entries-change': readonly RoutesEntry[];\n /** Sparse waypoint planning slots changed (origin/stop/destination assignment). */\n 'planning-change': readonly PlanningWaypoint[];\n /** Route parameters (alternatives, cost model, depart/arrive time) changed. */\n 'params-change': RouteParams;\n /** A new analysis was attached to (or replaced on) a routes entry. */\n 'analysis-added': { entryId: string; analysis: RoutesAnalysis };\n /** Set of routes entries currently rendered on the map changed (multi-show under `multiple`). */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * State for route calculation, waypoint management, and route planning parameters.\n *\n * Each entry carries its own lazy-initialised RoutingModule (`entry._module`), so display\n * state lives on the entry instead of a shared slice-level module. Two routes can be rendered\n * concurrently by showing both entries, and switching the active route hides the previous\n * entry's module without disturbing its calculated data.\n *\n * @group Agent Toolkit\n */\nexport class RoutingState implements ShownEntriesSlice {\n private _entries: RoutesEntry[] = [];\n private _planningSlots: PlanningWaypoint[] = [];\n private _params: RouteParams = {};\n private _entryMode: EntryMode = 'multiple';\n /**\n * The most recently `.showEntry`-ed id. Tracks \"who was last brought up\" so tools that\n * operate on the focused module (`set-route-theme`, `get-shown-routes`, …) have a single\n * pointer to read; the authoritative shown set is `shownEntryIds` (derived from each\n * entry's `_shown` flag).\n */\n private _lastShownEntryId?: string;\n\n /** Subscribe to state changes — see {@link RoutingStateEvents}. */\n readonly events = new StateEvents<RoutingStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n /** Set of routes-entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Display policy: `multiple` (default) lets several entries render side-by-side;\n * `single` forces \"at most one route on the map\" — `showEntry` then hides any\n * previously-shown entry first.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every\n * non-latest entry, drop them from history, and emit `entries-change`. No-op when the\n * mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const latest = this._entries.at(-1);\n const dropped = this._entries.slice(0, -1);\n for (const entry of dropped) await this.hideEntry(entry.id);\n this._entries = latest ? [latest] : [];\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n /**\n * Lazy-init and return an entry's RoutingModule. Throws on unknown id. Callers that need\n * to show/hide should prefer the higher-level {@link showEntry} / {@link hideEntry}.\n */\n async getEntryRoutingModule(entryId: string): Promise<RoutingModule> {\n const entry = this._requireEntry(entryId);\n if (!entry._module) {\n entry._module = await RoutingModule.get(this._ttMap);\n }\n return entry._module;\n }\n\n /**\n * Render the given entry's routes + waypoints on its own RoutingModule.\n *\n * Under `entryMode === 'single'` any other shown entry is hidden first so two route\n * layers don't pile up. Under `entryMode === 'multiple'` previously-shown entries stay\n * rendered, letting the caller overlay several routes at once.\n *\n * Emits `shown-change` once the new entry is up.\n */\n async showEntry(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n if (this._entryMode === 'single') {\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n const module = await this.getEntryRoutingModule(entryId);\n await module.showRoutes(entry.data);\n if (entry.waypoints.length) await module.showWaypoints(entry.waypoints);\n const wasShown = entry._shown;\n entry._shown = true;\n this._lastShownEntryId = entryId;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Hide the entry's routes + waypoints on its module (clear; the module stays cached).\n * No-op when the entry is unknown or already hidden.\n */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry || !entry._shown) return;\n if (entry._module) {\n await entry._module.clearRoutes();\n await entry._module.clearWaypoints();\n }\n entry._shown = false;\n if (this._lastShownEntryId === entryId) {\n // Repoint to whichever entry is still rendering, or clear the\n // pointer if none is.\n this._lastShownEntryId = this._entries.findLast((e) => e._shown)?.id;\n }\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Drop a single entry from history. Hides it from the map first (clears its routing module)\n * then removes it from `_entries`. Emits `entries-change`. No-op when the id is unknown.\n */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n }\n\n private _requireEntry(entryId: string): RoutesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`RoutingState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * RoutingModule of the most-recently-shown entry, if any. Tools that operate on the\n * \"focused route\" (e.g. setRouteTheme, getShownRoutes) read off it. Returns undefined\n * when no entry is currently shown.\n */\n get currentEntryModule(): RoutingModule | undefined {\n if (!this._lastShownEntryId) return undefined;\n const entry = this._entries.find((e) => e.id === this._lastShownEntryId);\n return entry?._shown ? entry._module : undefined;\n }\n\n // History\n\n get entries(): readonly RoutesEntry[] {\n return this._entries;\n }\n\n get currentRoutes(): Routes | undefined {\n return this._entries.at(-1)?.data;\n }\n\n get currentWaypoints(): WaypointLike[] | undefined {\n return this._entries.at(-1)?.waypoints;\n }\n\n /** Sparse nullable slots being assembled before triggering a route calculation. */\n get planningSlots(): PlanningWaypoint[] {\n return this._planningSlots;\n }\n\n get params(): RouteParams {\n return this._params;\n }\n\n addRoutes(routes: Routes, waypoints: WaypointLike[], label: string): string {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n // Hide each existing entry's module so the map doesn't keep\n // showing the previous routes; we then drop them from history.\n for (const entry of this._entries) void this.hideEntry(entry.id);\n this._entries = [];\n }\n const id = `routes-${this._entries.length}`;\n this._entries.push({\n id,\n timestamp: Date.now(),\n label,\n data: routes,\n waypoints,\n params: { ...this._params },\n });\n this._planningSlots = [...waypoints];\n this.events.emit('entries-change', this._entries);\n this.events.emit('planning-change', this._planningSlots);\n return id;\n }\n\n /**\n * Attach an analysis result to an existing routes entry. Names are unique within a single\n * entry — adding one with an existing name replaces it. Returns true on success, false if\n * the entry doesn't exist.\n */\n addAnalysisToEntry(entryId: string, analysis: RoutesAnalysis): boolean {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n entry._analysis ??= [];\n const existingIdx = entry._analysis.findIndex((a) => a.name === analysis.name);\n if (existingIdx >= 0) {\n entry._analysis[existingIdx] = analysis;\n } else {\n entry._analysis.push(analysis);\n }\n this.events.emit('analysis-added', { entryId, analysis });\n // Same reasoning as PlacesState.addAnalysisToEntry — analyses are attached in place,\n // re-emit entries-change so consumers re-render.\n this.events.emit('entries-change', this._entries);\n return true;\n }\n\n setWaypointAt(index: number, waypoint: WaypointLike): void {\n const minSize = Math.max(2, index + 1);\n while (this._planningSlots.length < minSize) {\n this._planningSlots.push(null);\n }\n this._planningSlots[index] = waypoint;\n this.events.emit('planning-change', this._planningSlots);\n }\n\n setParams(params: Partial<RouteParams>): void {\n this._params = { ...this._params, ...params };\n this.events.emit('params-change', this._params);\n }\n\n reset(): void {\n // Clear every entry's module so the map looks empty before the\n // references go away. Layers persist on the style — same trade-off the\n // previous shared-module reset had.\n for (const entry of this._entries) {\n entry._module?.clearRoutes();\n entry._module?.clearWaypoints();\n }\n this._entries = [];\n this._planningSlots = [];\n this._params = {};\n this._lastShownEntryId = undefined;\n this.events.emit('entries-change', this._entries);\n this.events.emit('planning-change', this._planningSlots);\n this.events.emit('params-change', this._params);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport { type TomTomMap, type TrafficAreaAnalyticsConfig, TrafficAreaAnalyticsModule } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\nimport { StateEvents } from '../events';\n\n/**\n * Events fired by {@link TrafficAreaAnalyticsState}. Subscribe via\n * `state.trafficAreaAnalytics.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type TrafficAreaAnalyticsStateEvents = {\n /** Last fetched analytics result and its module are now active on the map. */\n 'analytics-shown': { analytics: TrafficAreaAnalytics; module: TrafficAreaAnalyticsModule };\n /** The active analytics visualization was cleared or replaced. */\n 'analytics-cleared': void;\n /** Module-level visualization config changed (mode, metric, scaleMode, ...). */\n 'config-change': TrafficAreaAnalyticsConfig | undefined;\n};\n\n/**\n * State for traffic area analytics — the lazy-init module, the last fetched\n * aggregation result, and its currently-applied visualization config. Tile\n * overlays (flow + incidents) live in {@link TrafficTilesState}.\n *\n * @group Agent Toolkit\n */\nexport class TrafficAreaAnalyticsState implements StateSlice {\n private _trafficAreaAnalyticsModule?: TrafficAreaAnalyticsModule;\n private _lastAreaAnalytics?: TrafficAreaAnalytics;\n private _currentAnalyticsConfig?: TrafficAreaAnalyticsConfig;\n private _configChangeUnsub?: () => void;\n\n /** Subscribe to state changes — see {@link TrafficAreaAnalyticsStateEvents}. */\n readonly events = new StateEvents<TrafficAreaAnalyticsStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getter (lazy initialization)\n\n async getTrafficAreaAnalyticsModule(): Promise<TrafficAreaAnalyticsModule> {\n if (!this._trafficAreaAnalyticsModule) {\n this._trafficAreaAnalyticsModule = await TrafficAreaAnalyticsModule.get(this._ttMap);\n // Wire config change events so agent state stays in sync with module\n this._configChangeUnsub = this._trafficAreaAnalyticsModule.events.on('config-change', (config) => {\n this._currentAnalyticsConfig = config;\n this.events.emit('config-change', config);\n });\n }\n return this._trafficAreaAnalyticsModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get trafficAreaAnalyticsModule(): TrafficAreaAnalyticsModule | undefined {\n return this._trafficAreaAnalyticsModule;\n }\n\n // Area analytics result caching\n\n get lastAreaAnalytics(): TrafficAreaAnalytics | undefined {\n return this._lastAreaAnalytics;\n }\n\n setLastAreaAnalytics(result: TrafficAreaAnalytics): void {\n this._lastAreaAnalytics = result;\n }\n\n /** Current visualization config, updated by module configChange events. */\n get currentAnalyticsConfig(): TrafficAreaAnalyticsConfig | undefined {\n return this._currentAnalyticsConfig;\n }\n\n /**\n * Notify subscribers that an analytics result is now active on the map. Called from\n * tool implementations after `module.show(...)` succeeds.\n */\n notifyAnalyticsShown(analytics: TrafficAreaAnalytics, module: TrafficAreaAnalyticsModule): void {\n this.events.emit('analytics-shown', { analytics, module });\n }\n\n /** Notify subscribers that the active analytics visualization was cleared. */\n notifyAnalyticsCleared(): void {\n this.events.emit('analytics-cleared', undefined);\n }\n\n reset(): void {\n this._configChangeUnsub?.();\n this.notifyAnalyticsCleared();\n this._trafficAreaAnalyticsModule = undefined;\n this._lastAreaAnalytics = undefined;\n this._currentAnalyticsConfig = undefined;\n this._configChangeUnsub = undefined;\n }\n}\n","/**\n * @module agent-toolkit-tools\n *\n * Shared `fitOnMap` directive for sandboxed `process-*` tools — the LLM-authored\n * code can return a bbox (or `{ bbox, padding?, animate? }`) and the tool will\n * fit the camera to it without persisting any state. Lets the model use a\n * process-* tool purely for \"compute → focus the map\" without first writing a\n * new entry.\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/** @ignore */\nexport const fitOnMapInputSchema = z\n .union([\n z.tuple([z.number(), z.number(), z.number(), z.number()]),\n z.object({\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]),\n padding: z.number().optional(),\n animate: z.boolean().optional(),\n }),\n ])\n .describe(\n 'Bounds to fit the camera to: a `[W, S, E, N]` BBox tuple, or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`. ' +\n 'Default padding: 50px.',\n );\n\n/** @ignore */\nexport const fittedReportSchema = z.object({\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]).describe('Bounds the camera was fit to.'),\n padding: z.number().describe('Padding (px) applied around the bbox.'),\n});\n\n/** @ignore */\nexport type FitOnMapInput = BBox | { bbox: BBox; padding?: number; animate?: boolean };\n\n/** Doc blurb to inline in sandboxed-code descriptions. @ignore */\nexport const FIT_ON_MAP_DOC =\n '`fitOnMap` (opt): `[W,S,E,N]` BBox or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }` ' +\n '(default padding 50). Camera fits these bounds after the tool runs. Returning ONLY `fitOnMap` skips ' +\n 'state writes (pure camera focus); combine with `places` / `geometries` to also write+focus. ' +\n 'Derive bboxes via `turf.bbox(feature | FeatureCollection)`.';\n\nconst isBBoxTuple = (value: unknown): value is BBox =>\n Array.isArray(value) && value.length === 4 && value.every((n) => typeof n === 'number' && Number.isFinite(n));\n\n/** @ignore */\nexport const isFitOnMapInput = (value: unknown): value is FitOnMapInput => {\n if (isBBoxTuple(value)) return true;\n if (!value || typeof value !== 'object') return false;\n const v = value as { bbox?: unknown; padding?: unknown; animate?: unknown };\n if (!isBBoxTuple(v.bbox)) return false;\n if (v.padding !== undefined && (typeof v.padding !== 'number' || !Number.isFinite(v.padding))) return false;\n return !(v.animate !== undefined && typeof v.animate !== 'boolean');\n};\n\n/**\n * Apply a sandbox-returned `fitOnMap` directive: move the camera to the bbox\n * with the requested padding/animation, and return a normalized record for\n * the tool's output payload.\n *\n * @ignore\n */\nexport const applyFitOnMap = (state: ToolState, fit: FitOnMapInput): z.infer<typeof fittedReportSchema> => {\n const bbox = isBBoxTuple(fit) ? fit : fit.bbox;\n const padding = (isBBoxTuple(fit) ? undefined : fit.padding) ?? 50;\n const animate = isBBoxTuple(fit) ? undefined : fit.animate;\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding, ...(animate !== undefined && { animate }) });\n return { bbox, padding };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Tagged identifier for the unified geometries namespace. Every polygon\n * source the toolkit knows about — place footprints, isochrone polygons,\n * derived custom-geometries entries — is referenced through a single\n * `{ kind, id }` object so consumers don't have to disambiguate by\n * parameter name (or guess where an id comes from).\n */\n\nimport { z } from 'zod';\n\n/** Possible kinds in a geometries id. */\nexport type GeometriesIdKind = 'place' | 'places' | 'ranges' | 'custom';\n\n/**\n * Tagged geometries id. The Zod schema is exposed so tools can plug it\n * straight into their input shape (`z.array(geometriesIdSchema).min(1)`)\n * — no string parsing on the way in.\n *\n * @group Agent Toolkit\n */\nexport const geometriesIdSchema = z\n .discriminatedUnion('kind', [\n z\n .object({\n kind: z.literal('place').describe('Use the exact literal \"place\".'),\n id: z.string().min(1).describe('Id of a single place; resolves to that place’s footprint.'),\n })\n .describe('A single place footprint.'),\n z\n .object({\n kind: z.literal('places').describe('Use the exact literal \"places\".'),\n id: z.string().min(1).describe('Id of a places-entry; resolves to every footprint in the entry.'),\n })\n .describe('Every footprint in a places entry.'),\n z\n .object({\n kind: z.literal('ranges').describe('Use the exact literal \"ranges\".'),\n id: z\n .string()\n .min(1)\n .describe('Id of a ranges-entry; resolves to every isochrone polygon in the entry.'),\n })\n .describe('Every isochrone polygon in a ranges entry.'),\n z\n .object({\n kind: z.literal('custom').describe('Use the exact literal \"custom\".'),\n id: z\n .string()\n .min(1)\n .describe('Id of a custom-geometries entry produced by a previous processGeometries.'),\n })\n .describe('A derived entry produced by a previous processGeometries.'),\n ])\n .describe(\n 'Tagged id `{ kind, id }`. `kind` must be exactly one of: \"place\" | \"places\" | \"ranges\" | \"custom\" ' +\n '(do NOT use variants like \"ranges-entry\", \"place-footprints\", etc.). ' +\n 'Use `recallGeometries` to list every available `{kind, id}` pair.',\n );\n\n/** Tagged id for any polygon source in session state. */\nexport type GeometriesId = z.infer<typeof geometriesIdSchema>;\n\n/**\n * Shared `geometriesEntryIDs` input schema for tools that read polygons from\n * session state (`processGeometries`, `analyseGeometries`). One canonical\n * description so the two tools stay aligned and the model sees the same\n * contract everywhere.\n *\n * @ignore\n */\nexport const geometriesEntryIDsSchema = z\n .array(geometriesIdSchema)\n .min(1)\n .describe(\n 'Tagged ids `{ kind, id }` of polygon sources to feed the run. `kind` must be exactly one of ' +\n '`\"place\"` | `\"places\"` | `\"ranges\"` | `\"custom\"` (no variants). Mix kinds freely (e.g. union ' +\n 'place footprints with isochrones). Use `recallGeometries` to list available ids.',\n );\n\n/**\n * One-liner describing the per-source variability of `geometry.properties`.\n * Shared between `processGeometries` and `analyseGeometries` `code` prompts.\n *\n * @ignore\n */\nexport const GEOMETRIES_PROPS_DOC =\n '`geometry.properties` shape varies by source — place footprints: `CommonPlaceProps`; ranges: ' +\n 'service response (`range.type`, `range.value`); custom: whatever the producing op set. Treat as ' +\n 'opaque unless you know the source. ' +\n 'Every feature also carries `properties._source: { kind, id }` (the tagged id it was passed in ' +\n 'under) — use it to filter / partition mixed inputs by role inside the sandbox.';\n\n/**\n * Shared description for `skipped` arrays in tool outputs.\n *\n * @ignore\n */\nexport const GEOMETRIES_SKIPPED_DESC =\n 'Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).';\n\n/**\n * Shared description for `sourceIds` arrays in tool outputs.\n *\n * @ignore\n */\nexport const GEOMETRIES_SOURCE_IDS_DESC =\n 'Tagged input ids that contributed at least one polygon to the run, in input order.';\n","/**\n * @module agent-toolkit-tools\n *\n * Compact TypeScript-like schema documentation for the geometry-array shape consumed and produced\n * by the LLM-authored sandbox code in `processGeometries` / `analyseGeometries`. Each polygon is\n * a `PolygonFeature<CommonPlaceProps>` (the same shape `geometryData` returns), so this doc is\n * deliberately small — the per-place properties carry over from `PLACES_SCHEMA_DOC`.\n */\n\n/** @ignore */\nexport const GEOMETRIES_SCHEMA_DOC =\n 'Schema (the `geometries` array shape):\\n' +\n '```ts\\n' +\n 'type Geometry = { // a polygon footprint for one place\\n' +\n ' type: \"Feature\";\\n' +\n ' id: string; // geometry-data-source id (matches place.properties.dataSources.geometry.id)\\n' +\n ' geometry: { type: \"Polygon\" | \"MultiPolygon\"; coordinates: number[][][] | number[][][][] };\\n' +\n ' bbox: [minLng, minLat, maxLng, maxLat];\\n' +\n ' properties?: { /* place props if available — usually empty here */ };\\n' +\n '};\\n' +\n 'type Geometries = Geometry[]; // ordered list, same length as the input place ids when fully populated\\n' +\n '```\\n' +\n 'Notes: every polygon is a closed GeoJSON ring (last point == first). Use `turf.area`, `turf.union`, ' +\n '`turf.intersect`, `turf.bbox`, `turf.centroid`, `turf.buffer`, etc. directly on these features. ' +\n 'For h3 work, convert with `turf.bbox(feature)` → cells, or iterate `feature.geometry.coordinates`.';\n","/**\n * @module agent-toolkit-tools\n *\n * Shared helper that applies the optional `hidePreviousEntries` selector to a\n * routing- or ranges-style state slice before a new entry is rendered.\n */\n\nimport type { ShownEntriesSlice } from '../../state';\n\n/**\n * Hide previously-shown entries on a slice that exposes `hideEntry(id)` and a\n * `shownEntryIds` set (routing, ranges). No-op under `entryMode === 'single'`\n * (the slice auto-hides others) or when the selector is `undefined`\n * (omit ⇒ stack on top of existing entries).\n *\n * @ignore\n */\nexport const hidePreviousShownEntries = async (\n slice: ShownEntriesSlice,\n keepEntryIds: readonly string[],\n selector: 'all' | readonly string[] | undefined,\n): Promise<void> => {\n if (slice.entryMode !== 'multiple' || selector === undefined) return;\n const keep = new Set(keepEntryIds);\n const toHide =\n selector === 'all'\n ? [...slice.shownEntryIds].filter((entryId) => !keep.has(entryId))\n : selector.filter((entryId) => !keep.has(entryId));\n for (const entryId of toHide) {\n await slice.hideEntry(entryId);\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\n\n/** @ignore */\nexport type ResolvedLocation = {\n /** Resolved place: a Place (with address metadata) or a bare [lng, lat] tuple. */\n place: WaypointLike;\n position: Position;\n name: string;\n /** Original query string, when the input was resolved from text. */\n query?: string;\n};\n\n/** @ignore */\nexport const locationInputSchema = z.union([\n z.object({\n query: z.string().describe('Location string to resolve.'),\n queryAs: z\n .enum(['poi', 'place'])\n .describe(\n '\"poi\" for venues / landmarks / businesses; \"place\" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.',\n ),\n }),\n z.object({\n position: z\n .object({ lng: z.number(), lat: z.number() })\n .describe('Explicit coordinates — use when you already have a position.'),\n }),\n z.object({\n placeId: z\n .string()\n .describe(\n 'ID of a place stored in session state (any previous places entry). ' +\n 'Resolves to the full Place with its POI name, address, and coordinates — ' +\n 'prefer this over re-searching by name when routing to a place the user has already seen.',\n ),\n }),\n]);\n\n/** @ignore */\nexport type LocationInput = z.infer<typeof locationInputSchema>;\n","/**\n * @module agent-toolkit-tools\n *\n * Compact TypeScript-like schema documentation for the `Place` / `Places`\n * shape, reused by every tool whose LLM-authored code reads or returns a\n * Places FeatureCollection (e.g. processPlaces, analysePlaces). Keep it\n * terse: it becomes part of the tool description prompt on every call.\n */\n\n/** @ignore */\nexport const PLACES_SCHEMA_DOC =\n 'Schema (the full `Place` / `Places` shape you can rely on):\\n' +\n '```ts\\n' +\n 'type Places = { type: \"FeatureCollection\"; features: Place[]; bbox?: [minLng, minLat, maxLng, maxLat] };\\n' +\n 'type Place = {\\n' +\n ' type: \"Feature\";\\n' +\n ' id: string;\\n' +\n ' geometry: { type: \"Point\"; coordinates: [lng, lat] };\\n' +\n ' bbox?: [minLng, minLat, maxLng, maxLat]; // present for areas (Street, Geography, ...)\\n' +\n ' properties: {\\n' +\n ' type: \"POI\" | \"Street\" | \"Geography\" | \"Point Address\" | \"Address Range\" | \"Cross Street\";\\n' +\n ' address: {\\n' +\n ' freeformAddress: string;\\n' +\n ' streetNumber?: string; streetName?: string;\\n' +\n ' municipality?: string; municipalitySubdivision?: string;\\n' +\n ' countrySubdivision?: string; countrySubdivisionName?: string;\\n' +\n ' countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\\n' +\n ' postalCode?: string; extendedPostalCode?: string;\\n' +\n ' countryCode?: string; countryCodeISO3?: string; country?: string;\\n' +\n ' localName?: string;\\n' +\n ' };\\n' +\n ' poi?: { // present when type === \"POI\"\\n' +\n ' name: string; phone?: string; url?: string;\\n' +\n ' brands?: string[];\\n' +\n ' categories: string[]; // POICategory enum values, e.g. \"RESTAURANT\", \"CAFE_PUB\", \"GAS_STATION\", \"HOTEL_MOTEL\", \"ELECTRIC_VEHICLE_STATION\"\\n' +\n ' localizedCategories: string[]; // human-readable, language-sensitive\\n' +\n ' openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\\n' +\n ' timeZone?: { name: string };\\n' +\n ' };\\n' +\n ' // Search results also carry:\\n' +\n ' score?: number; // relevance score (higher = better)\\n' +\n ' distance?: number; // meters from geoBias, when one was provided\\n' +\n ' info?: string;\\n' +\n ' // Geography results also carry:\\n' +\n ' geographyType?: Array<\"Country\"|\"CountrySubdivision\"|\"CountrySecondarySubdivision\"|\"CountryTertiarySubdivision\"|\"Municipality\"|\"MunicipalitySubdivision\"|\"Neighbourhood\"|\"PostalCodeArea\">;\\n' +\n ' // Misc optional fields: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\\n' +\n ' };\\n' +\n '};\\n' +\n '```\\n' +\n 'Notes: coordinates are always [lng, lat] (GeoJSON order); fields marked `?` may be undefined — guard with optional chaining before use.';\n","import { poiCategories as knownPoiCategories, POICategory } from '@tomtom-org/maps-sdk/core';\nimport { getPOICategoryCodes } from '@tomtom-org/maps-sdk/services';\n\nconst knownPoiCategoriesSet = new Set<string>(knownPoiCategories);\n\n/** Outcome of {@link resolvePoiCategories}. @ignore */\nexport type ResolvedPoiCategories = {\n /** Final search filter — known codes plus codes resolved from natural-language synonyms. */\n resolved: POICategory[] | undefined;\n /** Inputs that matched neither an exact code nor any synonym. */\n unresolved: string[];\n};\n\n/**\n * Resolve a mixed list of POI inputs to canonical {@link POICategory} codes.\n *\n * Each input is either an exact catalog code (kept as-is) or a natural-language term that is\n * resolved via the POI categories service. Inputs that produce zero matches are reported in\n * `unresolved` so callers can surface a precise error.\n *\n * @ignore\n */\nexport const resolvePoiCategories = async (poiCategories: string[] | undefined): Promise<ResolvedPoiCategories> => {\n if (!poiCategories) {\n return { resolved: undefined, unresolved: [] };\n }\n const known = poiCategories.filter((c) => knownPoiCategoriesSet.has(c)) as POICategory[];\n const unrecognized = poiCategories.filter((c) => !knownPoiCategoriesSet.has(c));\n\n if (unrecognized.length === 0) {\n return { resolved: known.length ? known : undefined, unresolved: [] };\n }\n\n // Pre-warm the SDK's in-memory POI-categories cache once. After this, every per-term\n // `getPOICategoryCodes({ filters: [term] })` below is served from memory — they share\n // the cached catalog and filter it locally. Without the warm-up, N concurrent first-time\n // calls would race the network even though only one fetch is needed.\n await getPOICategoryCodes();\n\n // One filter lookup per term so we can attribute zero-match results back to their input.\n // All cache hits after the warm-up above — no extra network calls.\n const lookups = await Promise.all(\n unrecognized.map(async (term) => ({ term, codes: await getPOICategoryCodes({ filters: [term] }) })),\n );\n\n const unresolved: string[] = [];\n const resolvedUnrecognized: POICategory[] = [];\n for (const { term, codes } of lookups) {\n if (codes.length === 0) unresolved.push(term);\n else resolvedUnrecognized.push(...codes);\n }\n\n const merged = [...new Set([...known, ...resolvedUnrecognized])];\n return { resolved: merged.length ? merged : undefined, unresolved };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Compact TypeScript-like schema documentation for the `Route` / `Routes`\n * shape, reused by every tool whose LLM-authored code reads or returns a\n * Routes FeatureCollection (e.g. analyseRoutes). Keep it terse: it becomes\n * part of the tool description prompt on every call.\n */\n\n/** @ignore */\nexport const ROUTES_SCHEMA_DOC =\n 'Schema (the full `Route` / `Routes` shape you can rely on):\\n' +\n '```ts\\n' +\n 'type Routes = { type: \"FeatureCollection\"; features: Route[]; bbox?: [minLng, minLat, maxLng, maxLat]; properties?: { requestId?: string; calculatedAt?: Date } };\\n' +\n 'type Route = {\\n' +\n ' type: \"Feature\";\\n' +\n ' id: string;\\n' +\n ' geometry: { type: \"LineString\"; coordinates: [lng, lat][] };\\n' +\n ' bbox: [minLng, minLat, maxLng, maxLat];\\n' +\n ' properties: {\\n' +\n ' index: number; // 0 = main route, 1+ = alternatives\\n' +\n ' summary: {\\n' +\n ' lengthInMeters: number;\\n' +\n ' travelTimeInSeconds: number; // includes traffic delay\\n' +\n ' noTrafficTravelTimeInSeconds?: number; // free-flow estimate\\n' +\n ' trafficDelayInSeconds: number;\\n' +\n ' trafficLengthInMeters: number;\\n' +\n ' departureTime: Date; arrivalTime: Date;\\n' +\n ' // EV-only (when applicable):\\n' +\n ' batteryConsumptionInkWh?: number; batteryConsumptionInPCT?: number;\\n' +\n ' remainingChargeAtArrivalInkWh?: number; remainingChargeAtArrivalInPCT?: number;\\n' +\n ' totalChargingTimeInSeconds?: number;\\n' +\n ' // Combustion-only:\\n' +\n ' fuelConsumptionInLiters?: number;\\n' +\n ' };\\n' +\n ' sections: { // segments of the route grouped by characteristic. Every entry has { id, startPointIndex, endPointIndex } unless noted; *Index values reference `geometry.coordinates`.\\n' +\n ' leg: Array<{ id; startPointIndex?; endPointIndex?; summary: RouteSummary }>; // ALWAYS present; one per non-circle waypoint pair\\n' +\n ' country?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // ISO 3166-1 alpha-3 — \"USA\",\"NLD\",\"DEU\"\\n' +\n ' tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // same shape as country\\n' +\n ' traffic?: Array<{\\n' +\n ' id; startPointIndex; endPointIndex;\\n' +\n ' categories: TrafficIncidentCategory[]; // e.g. \"Jam\",\"Accident\",\"RoadWorks\",\"Closure\"\\n' +\n ' magnitudeOfDelay: \"Unknown\"|\"Minor\"|\"Moderate\"|\"Major\"|\"Undefined\";\\n' +\n ' tec: { effectCode?: number; causes?: Array<{ mainCauseCode: number; subCauseCode?: number }> };\\n' +\n ' effectiveSpeedInKmh?: number; // actual avg speed through the incident\\n' +\n ' delayInSeconds?: number; // extra time vs free-flow\\n' +\n ' }>;\\n' +\n ' importantRoadStretch?: Array<{ id; startPointIndex; endPointIndex; index: number; streetName?: string; roadNumbers?: string[] }>;\\n' +\n ' lanes?: Array<{\\n' +\n ' id; startPointIndex; endPointIndex;\\n' +\n ' lanes: Array<{ directions: PossibleLaneDirection[]; follow?: PossibleLaneDirection }>;\\n' +\n ' laneSeparators: PossibleLaneSeparator[];\\n' +\n ' properties?: string[]; // e.g. \"IS_MANEUVER\"\\n' +\n ' }>;\\n' +\n ' speedLimit?: Array<{ id; startPointIndex; endPointIndex; maxSpeedLimitInKmh: number }>;\\n' +\n ' roadShields?: Array<{ id; startPointIndex; endPointIndex; roadShieldReferences: RoadShieldReference[] }>;\\n' +\n ' // Plain section ranges (id + startPointIndex + endPointIndex only):\\n' +\n ' ferry?: SectionProps[]; motorway?: SectionProps[]; toll?: SectionProps[]; tunnel?: SectionProps[];\\n' +\n ' unpaved?: SectionProps[]; urban?: SectionProps[]; pedestrian?: SectionProps[];\\n' +\n ' carpool?: SectionProps[]; carTrain?: SectionProps[];\\n' +\n ' lowEmissionZone?: SectionProps[]; vehicleRestricted?: SectionProps[];\\n' +\n ' };\\n' +\n ' progress?: Array<{ pointIndex: number; travelTimeInSeconds?: number; distanceInMeters?: number }>; // cumulative milestones along the geometry\\n' +\n ' guidance?: { instructions: Array<{ pointIndex: number; maneuver: string; street?: string; message?: string }> }; // turn-by-turn (when requested)\\n' +\n ' };\\n' +\n '};\\n' +\n '```\\n' +\n 'Notes: coordinates are [lng, lat] (GeoJSON order). `pointIndices` reference positions in `geometry.coordinates`. ' +\n 'Fields marked `?` may be undefined — guard with optional chaining. Use `summary.lengthInMeters` / `summary.travelTimeInSeconds` for \"how long / how far\" questions.';\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport type { ChartConfiguration } from 'chart.js';\n\nexport type AnalysisOutputFormat = 'json' | 'chart';\n\n// Sandboxed `new AsyncFunction` bodies take `places`, `h3`, `turf` as injected\n// parameters, but LLMs regularly prepend `const turf = require('@turf/turf')`\n// or `const h3 = require('h3-js')` out of habit — producing a parse-time\n// \"Identifier 'X' has already been declared\" error before the body even runs.\n// We defensively strip those redeclarations.\n//\n// Critical: only strip when the RHS is a `require(...)`/`import(...)` call.\n// A naive name-only filter would also nuke legitimate user variables that\n// happen to share a name with an injected one (e.g. `const geometries =\n// places.features.map(...)`), corrupting the body and yielding a far less\n// actionable \"Illegal return statement\" error downstream.\n/**\n * Compact sandbox-code blurb shared by `process-places`, `process-geometries`,\n * `analyse-places`, `analyse-geometries`, and `analyse-routes`. Keeps the\n * \"what's in scope / what's forbidden\" rules short and consistent across all\n * code-generation tools so the LLM is prompted the same way each time. Pass\n * the names of the injected identifiers (the params the AsyncFunction body\n * receives) so the rule reads naturally for each tool.\n *\n * @ignore\n */\nexport const buildSandboxCodePrompt = (injected: readonly string[]): string =>\n `In scope: ${injected.map((n) => `\\`${n}\\``).join(', ')}. Already injected as function parameters — use directly. ` +\n 'Do NOT `require()` / `import` / redeclare them; the body is a sandboxed async function (no Node module system). ' +\n '`h3` is h3-js (hex-grid math: `latLngToCell`, `polygonToCells`, `gridDisk`, `cellToBoundary`, …). ' +\n '`turf` is @turf/turf (GeoJSON: `area`, `bbox`, `centroid`, `union`, `intersect`, `buffer`, `distance`, ' +\n '`booleanPointInPolygon`, `pointsWithinPolygon`, `clustersDbscan`, `convex`, …; verify names at https://turfjs.org/docs/). ' +\n 'Neither library searches places or fetches data — call `discoverPlaces` BEFORE this tool to bring in new places. ' +\n 'Top-level `await`, locals, and helper consts are fine. End with `return …;`.';\n\n/** Chart.js chart types accepted by the analyse-* tools when `outputFormat: \"chart\"`. */\nexport const CHART_TYPES = new Set(['bar', 'line', 'pie', 'doughnut', 'radar', 'polarArea', 'scatter', 'bubble']);\n\n/** Lightweight structural check that a sandbox return value is a Chart.js config. */\nexport const isChartConfiguration = (value: unknown): value is ChartConfiguration => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { type?: unknown; data?: unknown };\n return typeof v.type === 'string' && CHART_TYPES.has(v.type) && typeof v.data === 'object' && v.data !== null;\n};\n\n/**\n * GeoJSON Feature with a Polygon or MultiPolygon geometry. Generic over\n * `properties` so callers that need a narrower property shape (e.g. place\n * footprints with `CommonPlaceProps`) get the typed result without a cast.\n *\n * @ignore\n */\nexport const isPolygonFeature = <P = unknown>(value: unknown): value is PolygonFeature<P> => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { type?: unknown; geometry?: unknown };\n if (v.type !== 'Feature') return false;\n const g = v.geometry as { type?: unknown; coordinates?: unknown } | undefined;\n return !!g && (g.type === 'Polygon' || g.type === 'MultiPolygon') && Array.isArray(g.coordinates);\n};\n\n/** @ignore */\nexport const isPolygonFeatureArray = <P = unknown>(value: unknown): value is PolygonFeature<P>[] =>\n Array.isArray(value) && value.every(isPolygonFeature);\n\n/** Shared `describe()` text for the analyse-* `outputFormat` enum field. */\nexport const ANALYSE_OUTPUT_FORMAT_DESCRIPTION =\n 'Shape of the returned value (default: \"json\"). ' +\n '`json` — compact JSON-serializable object for text display. ' +\n '`chart` — Chart.js `ChartConfiguration` rendered via chart.js in the chat UI. ' +\n 'Pick `chart` whenever the user asks for a \"chart\", \"graph\", \"plot\", \"histogram\", \"bar/pie/line chart\", etc.';\n\n/**\n * Builds the shared \"return value\" blurb for the analyse-* tools' `code` field.\n * Encodes the JSON-only contract, the Chart.js config shape, and the\n * `string[]`-label workaround for tooltip-callback use cases.\n *\n * @param jsonExamples Comma-separated examples of typical `json`-shape outputs\n * for the specific tool (e.g. \"counts, groupings, hex bins, centroids\").\n * @param chartLabelExample Optional inline code snippet illustrating a\n * multi-line `string[]` label using the tool's domain. Omitted when empty.\n */\nexport const buildAnalyseReturnPrompt = (jsonExamples: string, chartLabelExample = ''): string => {\n const example = chartLabelExample ? ` (e.g. ${chartLabelExample})` : '';\n return (\n 'Return value MUST be pure JSON — plain objects, arrays, strings, numbers, booleans, null. No functions, no classes, no DOM/Map refs (it crosses a JSON boundary). Keep it small for chat display.\\n' +\n `• \\`json\\` (default) — compact object (${jsonExamples}, …).\\n` +\n '• `chart` — Chart.js config: `{ type: \"bar\"|\"line\"|\"pie\"|\"doughnut\"|\"radar\"|\"polarArea\"|\"scatter\"|\"bubble\", data: { labels: (string | string[])[], datasets }, options? }`. ' +\n `For per-point detail you would normally put in a \\`tooltip.callbacks\\` function, use a \\`string[]\\` label — Chart.js renders it multi-line on the axis tick AND in the tooltip title${example}.`\n );\n};\n\n// Sandbox return values cross the AI SDK's `jsonValueSchema` boundary as part\n// of the tool-result message. That schema accepts `null` but NOT `undefined`,\n// so user code that produces sparse arrays (`arr[i] = v` skipping indices),\n// `NaN`, `Infinity`, or `undefined` object values silently looks JSON-fine\n// (those serialize to `null`/get dropped) yet poisons every subsequent turn —\n// the next prompt fails ModelMessage validation with no obvious culprit.\n// Round-tripping through `JSON.stringify`/`JSON.parse` normalizes every one of\n// those cases before the value leaves the tool. Circular refs (the only thing\n// `stringify` throws on) are surfaced as a clear LLM-facing error instead.\n/**\n * Validates the value returned by analyse-* sandboxed code: rejects\n * `undefined`, JSON-normalizes via `toJsonSafe`, and — when\n * `outputFormat: \"chart\"` — checks the result is a Chart.js\n * `ChartConfiguration`. Centralises the contract shared by analyse-places,\n * analyse-routes, and analyse-geometries.\n *\n * @ignore\n */\nexport const validateAnalysisResult = (\n analysis: unknown,\n outputFormat: AnalysisOutputFormat,\n): { value: unknown } | { error: string } => {\n if (analysis === undefined) {\n return {\n error:\n 'Analysis code must return a value (the aggregation result). ' +\n 'Hint: `code` is the function BODY, not a function expression. If you wrote ' +\n '`(args) => { ...; return result; }` (or `async (args) => { ... }`) as the whole code, ' +\n 'the arrow is created and discarded — drop the wrapper and have the body itself end with `return result;`.',\n };\n }\n\n const normalized = toJsonSafe(analysis);\n if ('error' in normalized) return { error: normalized.error };\n\n if (outputFormat === 'chart' && !isChartConfiguration(normalized.value)) {\n return {\n error:\n 'Analysis code with `outputFormat: \"chart\"` must return a Chart.js ChartConfiguration ' +\n '({ type: \"bar\"|\"line\"|\"pie\"|\"doughnut\"|\"radar\"|\"polarArea\"|\"scatter\"|\"bubble\", data: { labels, datasets }, options? }).',\n };\n }\n\n return { value: normalized.value };\n};\n\n/** @ignore */\nexport const toJsonSafe = (value: unknown): { value: unknown } | { error: string } => {\n try {\n return { value: JSON.parse(JSON.stringify(value)) };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n error:\n `Returned value is not JSON-serializable: ${message}. ` +\n 'Return only plain objects, arrays, strings, numbers, booleans, and null — ' +\n 'no functions, classes, circular references, or DOM/Map references.',\n };\n }\n};\n\n/**\n * Compiles `code` as the body of an `async` function that takes the named\n * `paramNames`, sanitises injected redeclarations, runs it with `args`, and\n * returns either `{ value }` or `{ error }`. Centralises the AsyncFunction\n * dance shared by every sandbox-running tool (analyse-* and process-*) so\n * each callsite only owns its inputs and result handling.\n *\n * `verb` is the human-readable action used in the error prefix (e.g.\n * `\"Analysis\"`, `\"Process\"`).\n *\n * @ignore\n */\nexport const runSandboxedFn = async <Result = unknown>(\n code: string,\n paramNames: readonly string[],\n args: readonly unknown[],\n verb: string,\n): Promise<{ value: Result } | { error: string }> => {\n try {\n const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new (\n ...names: string[]\n ) => (...callArgs: unknown[]) => Promise<Result> | Result;\n const sanitizedCode = stripInjectedRedeclarations(code, paramNames);\n const fn = new AsyncFunction(...paramNames, sanitizedCode);\n return { value: await fn(...args) };\n } catch (error) {\n return { error: formatSandboxExecutionError(verb, error) };\n }\n};\n\n/** @ignore */\nexport const stripInjectedRedeclarations = (code: string, identifiers: readonly string[]): string => {\n const names = identifiers.join('|');\n // ^ line start (m flag)\n // [ \\t]*(const|let|var) declarator\n // [ \\t]+(name) one of the injected identifiers\n // [ \\t]*= assignment\n // [ \\t]*(?:await\\s+)? optional `await` (covers `await import(...)`)\n // (?:require|import)\\s*\\( require(... or import(...\n // [^\\n]*\\n? swallow rest of the line\n const pattern = new RegExp(\n String.raw`^[ \\t]*(?:const|let|var)[ \\t]+(?:${names})[ \\t]*=[ \\t]*(?:await\\s+)?(?:require|import)\\s*\\([^\\n]*\\n?`,\n 'gm',\n );\n return code.replace(pattern, '');\n};\n\n// Hints appended to LLM-facing error strings when the runtime message matches\n// a known pitfall in model-generated sandbox code. Lets the model self-correct\n// on the next call instead of guessing what went wrong.\n//\n// Order matters — earlier patterns win. The library-specific\n// `h3.X is not a function` / `turf.X is not a function` rules are listed\n// before the generic `is not a function` so callers get the targeted redirect\n// (the libraries are pure-geometry — no spatial-search / no I/O — and the\n// generic hint doesn't say that).\nconst SANDBOX_ERROR_HINTS: ReadonlyArray<{ pattern: RegExp; hint: string }> = [\n {\n pattern: /Cannot read propert(?:y|ies) of undefined/i,\n hint: 'A property was read on `undefined`. Likely a `reduce` callback that does not `return` the accumulator, or a `reduce` called without an initial value — use `arr.reduce((acc, x) => { ...; return acc; }, {})`. Also check unguarded chains like `feature.properties.poi.categories` — use optional chaining (`?.`) and `??` defaults.',\n },\n {\n pattern: /Cannot read propert(?:y|ies) of null/i,\n hint: 'A property was read on `null`. Guard property access with `?.` and provide defaults with `??`.',\n },\n {\n pattern: /\\bh3\\.\\w+ is not a function/i,\n hint: '`h3` is [h3-js](https://github.com/uber/h3-js) — hex-grid math only. NO spatial search / place lookup / HTTP. Common: `latLngToCell`, `cellToLatLng`, `cellToBoundary`, `polygonToCells`, `gridDisk`, `cellArea`. For nearby places call `discoverPlaces` BEFORE this tool — sandbox cannot call other tools.',\n },\n {\n pattern: /\\bturf\\.\\w+ is not a function/i,\n hint: '`turf` is [@turf/turf](https://turfjs.org/) — GeoJSON geometry only. NO spatial search / place lookup / HTTP. Common: `area`, `length`, `bbox`, `centroid`, `union`, `intersect`, `buffer`, `distance`, `booleanPointInPolygon`, `pointsWithinPolygon`, `clustersDbscan`, `convex`. Verify names at https://turfjs.org/docs/. For nearby places call `discoverPlaces` BEFORE this tool.',\n },\n {\n pattern: /is not a function/i,\n hint: 'A method was called on a value that does not have it (typo, or the value is not the expected type). Verify the type before calling, e.g. `Array.isArray(x) ? x.map(...) : []`.',\n },\n {\n pattern: /Illegal return statement/i,\n hint: 'A `return` was placed outside the top-level function body — usually a missing brace or a callback that the parser thinks closed too early. Common cause: a multi-line `const X = arr.map((item) => { ... })` whose closing `})` was lost. Re-emit the code with balanced braces, keep `return` only at the top level of the body, and try again.',\n },\n {\n pattern: /Identifier ['\"]?\\w+['\"]? has already been declared/i,\n hint: 'A variable was declared twice — typically because the code prepends `const turf = require(...)` / `const h3 = require(...)` / `const places = ...`. Those names are already injected as parameters; remove the redeclaration line and use them directly.',\n },\n {\n pattern: /Unexpected (?:token|identifier|end of input)/i,\n hint: 'The code did not parse as a JavaScript async-function body. Common causes: unbalanced braces/parens/brackets, a stray template literal, or `import`/`export` statements (not allowed in sandbox bodies — `places`/`geometries`/`h3`/`turf` are already in scope). Re-emit a self-contained body that ends with `return ...;`.',\n },\n {\n pattern: /\\brequire is not defined\\b|\\bimport (?:is not defined|outside a module)\\b/i,\n hint: 'The sandbox is not a Node module — `require` and top-level `import` are not available. The libraries you need (`h3`, `turf`) and inputs (`places`, `geometries`) are already injected as parameters; drop the import lines and use them directly.',\n },\n {\n pattern:\n /\\b(?:functions|tools|agent|recallPlaces|recallRoutes|recallRanges|discoverPlaces|locatePlace) is not defined\\b/i,\n hint: 'The sandbox cannot call other tools — `functions.recallPlaces(...)`, `tools.discoverPlaces(...)`, etc. are NOT available. To use additional entries, list every needed id in `placesEntryIDs` (or `routesEntryIDs`/`rangesIds` for the analyse-* tools): each is exposed via the injected inputs. To bring in NEW places call `discoverPlaces` BEFORE this tool and pass the resulting entry id back in.',\n },\n {\n pattern: /is not iterable/i,\n hint: 'A value used in `for..of`, spread, or destructuring was not iterable. Default arrays with `?? []`, e.g. `for (const c of feature.properties.poi?.categories ?? []) { ... }`.',\n },\n {\n pattern: /Cannot access '.+?' before initialization/i,\n hint: 'A `let`/`const` was used before its declaration (temporal dead zone). Move the declaration above its first use.',\n },\n {\n pattern: /Assignment to constant variable/i,\n hint: 'A `const`-declared variable was reassigned. Either use `let`, or mutate the value in place.',\n },\n {\n pattern: /structuredClone|could not be cloned|DataCloneError/i,\n hint: 'Return value must be pure JSON — no functions or classes. The result crosses a JSON boundary before rendering. For per-point detail you would put in a Chart.js `tooltip.callbacks` function, use a `string[]` label instead — it renders multi-line in the axis tick and the tooltip title.',\n },\n];\n\n/**\n * Builds an LLM-facing error string for sandboxed code execution failures.\n * Appends a `Hint:` suffix when the error matches a common pattern so the\n * model can self-correct on retry. `verb` is the human-readable action used\n * in the prefix (e.g. `\"Analysis\"`, `\"Process\"`).\n */\n/** @ignore */\nexport const formatSandboxExecutionError = (verb: string, error: unknown): string => {\n const message = error instanceof Error ? error.message : String(error);\n const base = `${verb} code execution failed: ${message}`;\n if (error instanceof Error && error.name === 'SyntaxError') {\n return `${base} Hint: the provided \\`code\\` is not valid JavaScript — ensure it parses as an async function body and \\`return\\`s a value.`;\n }\n const match = SANDBOX_ERROR_HINTS.find(({ pattern }) => pattern.test(message));\n return match ? `${base} Hint: ${match.hint}` : base;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared input schema building blocks reused across multiple tools.\n */\n\nimport { z } from 'zod';\n\n/** @ignore */\nexport const geoJsonBBoxSchema = z\n .union([z.array(z.number()).refine((arr) => arr.length === 4, { message: 'BBox must have 4 elements' })])\n .describe('[minLng, minLat, maxLng, maxLat] or [W,S,E,N]');\n\n/** @ignore */\nexport const polygonInputSchema = z.object({\n type: z.literal('Polygon'),\n coordinates: z.array(z.array(z.array(z.number()))),\n});\n\n/** @ignore */\nexport const multiPolygonInputSchema = z.object({\n type: z.literal('MultiPolygon'),\n coordinates: z.array(z.array(z.array(z.array(z.number())))),\n});\n\n/** @ignore */\nexport const geometryInputSchema = z.union([polygonInputSchema, multiPolygonInputSchema]);\n\n/**\n * Shared factory for the optional `placesEntryIDs` input field — one or more\n * existing places-entry IDs that a tool should operate on. Each caller supplies\n * a `verb` (filter/analyse/display/…) and optional follow-up text so the LLM\n * sees a description tailored to that tool.\n *\n * @ignore\n */\nexport const placesEntryIDsSchema = ({ verb, extra }: { verb: string; extra?: string }) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing places entries to ${verb} (e.g. [\"places-2\"], [\"places-1\", \"places-2\"]). ` +\n `Use recallPlaces to list IDs. Omit to ${verb} the latest entry.` +\n (extra ? ` ${extra}` : ''),\n );\n\n/**\n * Shared factory for the optional `routesEntryIDs` input field — one or more existing routes-entry\n * IDs that a tool should operate on. Mirrors `placesEntryIDsSchema` for routes.\n *\n * @ignore\n */\nexport const routesEntryIDsSchema = ({ verb, extra }: { verb: string; extra?: string }) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing routes entries to ${verb} (e.g. [\"routes-0\"], [\"routes-0\", \"routes-1\"]). ` +\n `Use recallRoutes to list IDs. Omit to ${verb} the latest route.` +\n (extra ? ` ${extra}` : ''),\n );\n\n/**\n * Optional semantic id for a new places entry. Encourages the LLM to derive a compact,\n * memorable identifier from the user's prompt so later tool calls can reference the entry\n * without scanning a numeric history. The id is auto-suffixed (`-2`, `-3`, ...) on collision,\n * so the LLM never has to track uniqueness across calls itself.\n *\n * @ignore\n */\nexport const placesEntryIdHintSchema = z\n .string()\n .regex(/^[a-z0-9][a-z0-9-]{1,48}$/, 'Use lowercase letters, digits, and hyphens (2–49 chars).')\n .optional()\n .describe(\n 'Compact lowercase kebab-case id derived from the user prompt ' +\n '(e.g. \"vegan-cafes-amsterdam\", \"ev-stations-along-route\"). ' +\n 'Avoid generic names like \"places\" / \"results\". ' +\n 'Auto-suffixed on collision — do not track uniqueness yourself. Default: `places-N`.',\n );\n\n/**\n * Reusable `mode` field for show-on-map options that render polygons into an entry's\n * own `GeometriesModule`. Controls how the new polygons combine with what is **already\n * rendered on that single entry's layer** — intra-entry only. Cross-entry visibility\n * (hiding *other* entries before this one comes up) is a separate concern handled by\n * `hidePreviousEntries`.\n *\n * @ignore\n */\nexport const showModeSchema = z\n .enum(['add', 'replace'])\n .optional()\n .describe(\n 'WITHIN THE TARGETED ENTRY ONLY — controls how the new polygons combine with ' +\n \"what is already rendered on this entry's GeometriesModule layer. Does NOT \" +\n 'affect other entries (use `hidePreviousEntries` for that). ' +\n '\"replace\" (default) clears this entry\\'s layer first; ' +\n '\"add\" stacks (deduped by feature id).',\n );\n\n/**\n * Shared schema for the geometry-rendering option on tools that produce a places entry\n * (discoverPlaces, locatePlace, ...). When provided, the tool batch-fetches boundary\n * polygons via the Geometry Data service, caches them on the entry, and optionally renders.\n *\n * @ignore\n */\nexport const showPlaceGeometriesSchema = z\n .object({\n show: z.boolean().optional().describe('Also render the polygons on the map. Default: false.'),\n mode: showModeSchema,\n theme: z.enum(['filled', 'outline', 'inverted']).optional().describe('Visual theme. Default: \"outline\".'),\n })\n .optional()\n .describe(\n 'Batch-fetch boundary polygons for the resulting places and cache them on the entry. ' +\n 'Use when the user wants the *area / shape / boundary* (e.g. \"outline this neighbourhood\", \"show the city footprint\"). ' +\n 'Set `show: true` to also render. Skips places without a geometry source (most addresses/streets).',\n );\n\n/**\n * Schema for the geometry-rendering option on the places display tool\n * (`updatePlacesDisplay`). Presence of this object means \"render boundaries\";\n * there is no `show: boolean` toggle — that is reserved for the search-time\n * variant ({@link showPlaceGeometriesSchema}).\n *\n * @ignore\n */\nexport const geometryDisplayConfigSchema = z\n .object({\n theme: z.enum(['filled', 'outline', 'inverted']).optional().describe('Visual theme. Default: \"outline\".'),\n placeIds: z\n .array(z.string())\n .optional()\n .describe(\n 'Subset of place ids within the targeted entries — render boundaries only for these. ' +\n 'Ids outside the entries are ignored. Default: all places with a geometry source.',\n ),\n mode: showModeSchema,\n })\n .describe(\n 'Render boundary polygons for the targeted entries. ' +\n 'Use when the user wants the *area / shape / outline / footprint* (e.g. \"outline this neighbourhood\", \"show the city footprint\"). ' +\n 'Independent of `markerType` — set both to render pins AND boundaries.',\n );\n\n/**\n * Shared marker-type enum. Used as a required field in `showPlacesSchema` (the\n * nested `show` object for discover/filter/search tools) and as an optional\n * top-level field in `showPlaces` itself.\n *\n * @ignore\n */\nexport const markerTypeSchema = z\n .enum(['base-map', 'pin', 'pin-clustered', 'none'])\n .describe(\n '\"pin\" for few results; \"base-map\" for 10+; ' +\n '\"pin-clustered\" aggregates nearby pins under a count badge (best for dense city-scale POI sets — ' +\n 'clusters expose aggregated `placeTypes` / `poiCategories`); \"none\" for towns/cities/areas.',\n );\n\n/**\n * Shared factory for the optional `hidePreviousEntries` field — controls which previously-shown\n * entries get hidden before the new ones are rendered. `entryNoun` is the slice noun\n * (`'places'`, `'routes'`, `'ranges'`) used inside the description so the LLM sees a slice-specific\n * hint.\n *\n * @ignore\n */\nexport const hidePreviousEntriesSchema = (entryNoun: string) =>\n z\n .union([z.literal('all'), z.array(z.string())])\n .optional()\n .describe(\n `Which previously-shown ${entryNoun} entries to hide before showing the new ones. ` +\n `\\`\"all\"\\` clears every previous entry (use for the typical \"show X\" — replace existing); ` +\n 'an array hides only the listed entry IDs. ' +\n `Omit to stack on top of existing entries (use for \"also show\", \"additionally\", \"and X\"). ` +\n 'The entries being shown by this call are never hidden. ' +\n `Only applied when \\`entryMode === \"multiple\"\\` (in \\`single\\` mode every other entry is auto-hidden, so this prop is ignored).`,\n );\n\n/** @ignore */\nexport const showPlacesSchema = z.object({\n markerType: markerTypeSchema,\n zoomMode: z.enum(['auto', 'none']).describe('Camera behaviour. \"none\" keeps the map still.'),\n hidePreviousEntries: hidePreviousEntriesSchema('places'),\n});\n\n/** @ignore */\nexport const shownSchema = z.object({\n markerType: z.boolean(),\n zoomMode: z.boolean(),\n});\n\n// --- where schema (geographic scope) ---\n//\n// Discriminated by `mode`. Three modes are shared between locatePlace and discoverPlaces:\n// - within — one area-like input (bbox / viewport / geocoded query / placeId polygon)\n// - nearby — one point-like bias (position / viewport centre / geocoded query centroid)\n// - global — no bias\n// `within` is split between locatePlace (singular fields, defined here) and discoverPlaces\n// (plural fields, defined in discover-places.ts). Only `mode` and `viewport` are shared via\n// {@link withinSharedFields}. discoverPlaces also adds a fourth mode `maxDetour`.\n\n/**\n * Fields shared by both locatePlace's and discoverPlaces' `within` schemas — `mode` and the\n * `viewport` flag. Spread into each consumer's `z.object({...})`.\n * @ignore\n */\nexport const withinSharedFields = {\n mode: z.literal('within'),\n viewport: z\n .literal(true)\n .optional()\n .describe('When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.'),\n};\n\n/**\n * `queryAs` — what kind of thing the paired `query` string is. Drives whether resolution uses\n * POI search (venues/landmarks/businesses) or geocoding (addresses, cities, neighborhoods, parks,\n * postal codes, etc.). Optional everywhere; defaults to `'place'`.\n * @ignore\n */\nexport const queryAsSchema = z\n .enum(['poi', 'place'])\n .optional()\n .describe(\n 'Drives query resolution. ' +\n '`\"poi\"` for venues / landmarks / businesses (e.g. \"Eiffel Tower\"). ' +\n '`\"place\"` for addresses, cities, neighborhoods, parks, postal codes (e.g. \"De Jordaan, Amsterdam\", \"1600 Pennsylvania Ave\"). ' +\n 'Default: \"place\".',\n );\n\nconst locateWithinSingularKeys = ['boundingBox', 'viewport', 'query', 'placeId'] as const;\n\n/**\n * locatePlace's `within` schema — exactly one of viewport / boundingBox / query / placeId.\n * @ignore\n */\nexport const withinWhereSchema = z\n .object({\n ...withinSharedFields,\n boundingBox: geoJsonBBoxSchema\n .optional()\n .describe('Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId.'),\n query: z\n .string()\n .optional()\n .describe(\n 'Resolve a name/address/POI → its boundary polygon (or bbox) as search area ' +\n '(e.g. \"Paris\", \"De Jordaan, Amsterdam\"). ' +\n 'Pair with `queryAs` to disambiguate. EXCLUSIVE with boundingBox / viewport / placeId.',\n ),\n queryAs: queryAsSchema,\n placeId: z\n .string()\n .optional()\n .describe(\n 'ID of a session place — its boundary polygon (fetched on demand) is the search area. ' +\n 'EXCLUSIVE with boundingBox / viewport / query.',\n ),\n })\n .refine((data) => locateWithinSingularKeys.filter((k) => data[k] !== undefined).length === 1, {\n message: '`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId.',\n });\n\nconst nearbySingularKeys = ['position', 'viewport', 'query'] as const;\n\n/** @ignore */\nexport const nearbyWhereSchema = z\n .object({\n mode: z.literal('nearby'),\n position: z\n .array(z.number())\n .length(2)\n .optional()\n .describe('Bias point [lng, lat]. EXCLUSIVE with viewport / query.'),\n viewport: z\n .literal(true)\n .optional()\n .describe('When true, bias toward the current map viewport centre. EXCLUSIVE with position / query.'),\n query: z\n .string()\n .optional()\n .describe(\n 'Resolve a name/address/POI → its centroid as bias point. ' +\n 'Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport.',\n ),\n queryAs: queryAsSchema,\n radiusMeters: z\n .number()\n .positive()\n .optional()\n .describe('Hard radius cap (metres) around the bias point. Independent of bias source.'),\n })\n .refine((data) => nearbySingularKeys.filter((k) => data[k] !== undefined).length === 1, {\n message: '`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent).',\n });\n\n/** @ignore */\nexport const globalWhereSchema = z.object({\n mode: z.literal('global'),\n});\n\n/**\n * locatePlace's `where` schema — `within` (singular fields), `nearby`, or `global`.\n * discoverPlaces builds its own union in discover-places.ts using the plural-form within schema.\n * @ignore\n */\nexport const whereSchema = z.union([withinWhereSchema, nearbyWhereSchema, globalWhereSchema]);\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport type { showPlaceGeometriesSchema } from './schema';\n\n/** @ignore */\nexport type ShowEntryGeometriesResult = { fetched: number; shown: boolean };\n\n/**\n * Fetch boundary polygons for every place in an entry that has a geometry data source,\n * cache them on the entry, and optionally render them via the entry's GeometriesModule.\n *\n * Shared by tools that produce a single places entry (discoverPlaces, locatePlace) — they\n * differ only in how they shape the result into their output schemas.\n *\n * @ignore\n */\nexport const showEntryGeometries = async (\n state: ToolState,\n placesEntryId: string,\n config: NonNullable<z.infer<typeof showPlaceGeometriesSchema>>,\n): Promise<ShowEntryGeometriesResult> => {\n const fetched = await state.places.fetchGeometriesForEntry(placesEntryId);\n if (config.show && fetched.length > 0) {\n await state.places.showPlaceGeometries(fetched, config.theme ?? 'outline', config.mode ?? 'replace');\n return { fetched: fetched.length, shown: true };\n }\n return { fetched: fetched.length, shown: false };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport type { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { shownSchema, showPlacesSchema } from './schema';\n\n/** @ignore */\nexport const showResultsOnMap = async (\n state: ToolState,\n placesEntryIds: readonly string[],\n show: z.infer<typeof showPlacesSchema>,\n): Promise<z.infer<typeof shownSchema>> => {\n const shown: z.infer<typeof shownSchema> = { markerType: false, zoomMode: false };\n if (show.markerType !== 'none') {\n const isMultiple = state.places.entryMode === 'multiple';\n if (isMultiple && show.hidePreviousEntries !== 'all') {\n if (Array.isArray(show.hidePreviousEntries)) {\n const exclude = new Set(placesEntryIds);\n const toHide = show.hidePreviousEntries.filter((id) => !exclude.has(id));\n if (toHide.length > 0) await state.places.removeShownEntries(toHide);\n }\n await state.places.addShownEntries(placesEntryIds, show.markerType);\n } else {\n await state.places.setShownEntries(placesEntryIds, show.markerType);\n }\n shown.markerType = true;\n }\n if (show.zoomMode === 'auto') {\n const features = state.places.getShownFeatures();\n if (features.length > 0) {\n const bbox = bboxFromGeoJSON({ type: 'FeatureCollection', features });\n if (bbox) state.baseMap.mapLibreMap.fitBounds(bbox, { padding: 50 });\n }\n shown.zoomMode = true;\n }\n return shown;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, Places, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { type GeometriesId, geometriesIdSchema } from './geometries-id';\n\n// Per-source skip record. `source` carries the original tagged id so the\n// caller can present the failure with the same `{kind, id}` shape the agent\n// passed in, and `reason` is a free-form explanation.\n/** @ignore */\nexport const skippedSourceSchema = z.object({\n source: geometriesIdSchema,\n reason: z.string(),\n});\n\n/** @ignore */\nexport type SkippedSource = z.infer<typeof skippedSourceSchema>;\n\n/** @ignore */\nexport type AffectedEntry = {\n /** Unqualified entry id within the owning slice. */\n id: string;\n /** Which slice the entry lives in (only kinds that carry analyses are tracked). */\n kind: 'places' | 'custom';\n};\n\n/** @ignore */\nexport type CollectedGeometries = {\n geometries: PolygonFeature[];\n /** Place objects whose footprints contributed (used to seed processGeometries provenance). */\n sourcePlaces: Place[];\n /** Entries that should receive analyses (places + customGeometries; ranges don't carry analyses). */\n affectedEntries: AffectedEntry[];\n skipped: SkippedSource[];\n /** Tagged ids that contributed at least one polygon, in input order. */\n contributingSourceIds: GeometriesId[];\n};\n\ntype ParsedByKind = {\n place: GeometriesId[];\n places: GeometriesId[];\n ranges: GeometriesId[];\n custom: GeometriesId[];\n};\n\nconst groupByKind = (sources: readonly GeometriesId[]): ParsedByKind => {\n const out: ParsedByKind = { place: [], places: [], ranges: [], custom: [] };\n for (const source of sources) out[source.kind].push(source);\n return out;\n};\n\nconst formatSkipReason = (skip: SkippedSource): string => `${skip.source.kind}:${skip.source.id}: ${skip.reason}`;\n\n// Walks `places` inputs, validates each entry, and emits the place ids that\n// have a geometry data source. Returns a back-pointer map so per-place\n// outcomes can be attributed to the requesting entry afterwards.\nconst expandPlacesEntries = (\n sources: readonly GeometriesId[],\n state: ToolState,\n): { placeIds: string[]; placeIdToEntry: Map<string, string>; skipped: SkippedSource[] } => {\n const placeIds: string[] = [];\n const placeIdToEntry = new Map<string, string>();\n const skipped: SkippedSource[] = [];\n const seen = new Set<string>();\n\n for (const source of sources) {\n const entry = state.places.entries.find((e) => e.id === source.id);\n if (!entry) {\n skipped.push({\n source,\n reason: 'No places entry with this id in session state. Use `recallGeometries` to list available ids.',\n });\n continue;\n }\n let added = 0;\n for (const place of entry.places) {\n if (!place.properties.dataSources?.geometry?.id || typeof place.id !== 'string') continue;\n added++;\n if (!placeIdToEntry.has(place.id)) placeIdToEntry.set(place.id, source.id);\n if (seen.has(place.id)) continue;\n seen.add(place.id);\n placeIds.push(place.id);\n }\n if (added === 0) {\n skipped.push({\n source,\n reason: 'Places entry has no places with a geometry data source (e.g. addresses and streets often lack one).',\n });\n }\n }\n return { placeIds, placeIdToEntry, skipped };\n};\n\n// Shallow-clone a feature and stamp `properties._source` with the tagged id it came from.\n// The original feature lives in state (place-geometry cache, range polygons, custom-entry\n// features) — mutating it directly would corrupt subsequent reads. Cloning keeps the\n// per-sandbox-run tag scoped to the call.\nconst tagFeatureSource = (feature: PolygonFeature, source: GeometriesId): PolygonFeature => ({\n ...feature,\n properties: { ...(feature.properties ?? {}), _source: source },\n});\n\n// Resolves one place id to its boundary polygon. Returns `success` with the\n// fetched feature, or `skip` with a reason the caller records as a `place`\n// skip. Pulled out of `collectInputGeometries` so the orchestrator stays\n// under Biome's cognitive-complexity ceiling.\ntype PlaceResolution =\n | { kind: 'success'; place: Place; feature: PolygonFeature; ownerEntryId: string }\n | { kind: 'skip'; reason: string };\n\nconst resolvePlaceGeometry = async (placeId: string, state: ToolState): Promise<PlaceResolution> => {\n const lookup = state.places.findPlaceById(placeId);\n if (!lookup) {\n return {\n kind: 'skip',\n reason:\n 'No place with this id in session state. ' +\n 'Use `recallGeometries` to list known places ids and entries.',\n };\n }\n if (!lookup.place.properties.dataSources?.geometry?.id) {\n return {\n kind: 'skip',\n reason: 'Place has no geometry data source (e.g. addresses and streets often lack one).',\n };\n }\n try {\n const feature = await state.places.fetchPlaceGeometry(placeId);\n if (!feature) return { kind: 'skip', reason: 'Geometry Data service returned no feature for this place.' };\n return { kind: 'success', place: lookup.place, feature, ownerEntryId: lookup.entryId };\n } catch (error) {\n return {\n kind: 'skip',\n reason: `Geometry fetch failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n// Flatten range polygons from each `ranges` input. Self-contained so the\n// orchestrator stays under Biome's cognitive-complexity ceiling.\nconst collectRangePolygons = (\n sources: readonly GeometriesId[],\n state: ToolState,\n): { features: PolygonFeature[]; contributingIds: Set<string>; skipped: SkippedSource[] } => {\n const features: PolygonFeature[] = [];\n const contributingIds = new Set<string>();\n const skipped: SkippedSource[] = [];\n for (const source of sources) {\n const entry = state.ranges.entries.find((e) => e.id === source.id);\n if (!entry) {\n skipped.push({ source, reason: 'No ranges entry with this id in session state.' });\n continue;\n }\n const polygons = entry.ranges.flatMap((r) => r.polygon?.features ?? []);\n if (polygons.length === 0) {\n skipped.push({\n source,\n reason: 'Ranges entry has no polygons — recompute via findReachableAreas first.',\n });\n continue;\n }\n for (const polygon of polygons) features.push(tagFeatureSource(polygon, source));\n contributingIds.add(source.id);\n }\n return { features, contributingIds, skipped };\n};\n\n// Read polygons from each `custom` input.\nconst collectCustomGeometries = (\n sources: readonly GeometriesId[],\n state: ToolState,\n): { features: PolygonFeature[]; contributingIds: Set<string>; skipped: SkippedSource[] } => {\n const features: PolygonFeature[] = [];\n const contributingIds = new Set<string>();\n const skipped: SkippedSource[] = [];\n for (const source of sources) {\n const entry = state.customGeometries.findById(source.id);\n if (!entry) {\n skipped.push({\n source,\n reason: 'No custom-geometries entry with this id in session state. Use `recallGeometries` to list available ids.',\n });\n continue;\n }\n if (entry.features.length === 0) {\n skipped.push({ source, reason: 'Custom-geometries entry is empty.' });\n continue;\n }\n for (const feature of entry.features) features.push(tagFeatureSource(feature, source));\n contributingIds.add(source.id);\n }\n return { features, contributingIds, skipped };\n};\n\n/**\n * Resolves a heterogeneous list of tagged geometry ids into a single\n * `PolygonFeature[]`. The id's `kind` tells the helper which slice to\n * consult: `place` and `places` go through the per-place fetch pipeline\n * (Geometry Data service for footprints, lazy cached on the places-entry);\n * `ranges` flattens isochrone polygons already in memory; `custom` reads a\n * derived entry produced by a previous `processGeometries`.\n *\n * Validates the input precondition (at least one id) and the output\n * postcondition (at least one polygon collected). Returns `affectedEntries`\n * — the entries that should carry analyses computed from these geometries\n * (places-entries from `place`/`places` inputs, custom-entries from\n * `custom` inputs; ranges entries don't carry analyses, so they're not\n * included here).\n *\n * @ignore\n */\nexport const collectInputGeometries = async (\n sources: readonly GeometriesId[],\n state: ToolState,\n): Promise<{ value: CollectedGeometries } | { error: string }> => {\n if (sources.length === 0) {\n return {\n error:\n '`geometriesEntryIDs` must contain one or more tagged ids ' +\n \"(e.g. `{ kind: 'places', id: <entryId> }`, `{ kind: 'place', id: <placeId> }`, \" +\n \"`{ kind: 'ranges', id: <entryId> }`, `{ kind: 'custom', id: <entryId> }`). \" +\n 'Use `recallGeometries` to list available ids.',\n };\n }\n\n const grouped = groupByKind(sources);\n\n // Expand `places` entries into individual place ids, then merge them with\n // any explicit `place` inputs. The merged list runs through the single\n // per-place pipeline so fetches and skip semantics stay uniform.\n const expanded = expandPlacesEntries(grouped.places, state);\n const skipped: SkippedSource[] = [...expanded.skipped];\n\n const seenPlaceIds = new Set<string>();\n const mergedPlaceIds: string[] = [];\n const placeIdSourceQualified = new Map<string, GeometriesId>();\n for (const source of grouped.place) {\n if (seenPlaceIds.has(source.id)) continue;\n seenPlaceIds.add(source.id);\n mergedPlaceIds.push(source.id);\n placeIdSourceQualified.set(source.id, source);\n }\n for (const id of expanded.placeIds) {\n if (seenPlaceIds.has(id)) continue;\n seenPlaceIds.add(id);\n mergedPlaceIds.push(id);\n }\n\n const geometries: PolygonFeature[] = [];\n const sourcePlaces: Place[] = [];\n const affectedPlacesEntries = new Set<string>();\n const contributingPlaceIds = new Set<string>();\n const contributingPlacesEntries = new Set<string>();\n\n for (const placeId of mergedPlaceIds) {\n const result = await resolvePlaceGeometry(placeId, state);\n const explicitPlaceSource = placeIdSourceQualified.get(placeId);\n const fromPlacesEntry = expanded.placeIdToEntry.get(placeId);\n if (result.kind === 'skip') {\n // Per-place failure: report it as `place:<id>` regardless of how\n // the place was introduced. That points the agent at the exact\n // failing feature instead of blaming the parent entry.\n const skipSource: GeometriesId = explicitPlaceSource ?? { kind: 'place', id: placeId };\n skipped.push({ source: skipSource, reason: result.reason });\n continue;\n }\n // Tag with the source the agent passed in: a `place:<placeId>` direct input or a\n // `places:<entryId>` parent. Keeps the sandbox-side `_source` aligned with the input\n // shape rather than leaking the internal expansion.\n const featureSource: GeometriesId =\n explicitPlaceSource ??\n (fromPlacesEntry ? { kind: 'places', id: fromPlacesEntry } : { kind: 'place', id: placeId });\n geometries.push(tagFeatureSource(result.feature, featureSource));\n sourcePlaces.push(result.place);\n affectedPlacesEntries.add(result.ownerEntryId);\n if (explicitPlaceSource) contributingPlaceIds.add(explicitPlaceSource.id);\n if (fromPlacesEntry) contributingPlacesEntries.add(fromPlacesEntry);\n }\n\n const ranges = collectRangePolygons(grouped.ranges, state);\n geometries.push(...ranges.features);\n skipped.push(...ranges.skipped);\n\n const custom = collectCustomGeometries(grouped.custom, state);\n geometries.push(...custom.features);\n skipped.push(...custom.skipped);\n const affectedCustomEntries = custom.contributingIds;\n\n if (geometries.length === 0) {\n return {\n error: `No input geometries available. ${skipped.map(formatSkipReason).join(' | ')}`,\n };\n }\n\n const affectedEntries: AffectedEntry[] = [\n ...[...affectedPlacesEntries].map((id): AffectedEntry => ({ id, kind: 'places' })),\n ...[...affectedCustomEntries].map((id): AffectedEntry => ({ id, kind: 'custom' })),\n ];\n\n // Preserve the caller's input order in `contributingSourceIds`.\n const contributingSourceIds = sources.filter((source) => {\n switch (source.kind) {\n case 'place':\n return contributingPlaceIds.has(source.id);\n case 'places':\n return contributingPlacesEntries.has(source.id);\n case 'ranges':\n return ranges.contributingIds.has(source.id);\n case 'custom':\n return affectedCustomEntries.has(source.id);\n }\n });\n\n return {\n value: {\n geometries,\n sourcePlaces,\n affectedEntries,\n skipped,\n contributingSourceIds,\n },\n };\n};\n\n/**\n * Resolves `placesEntryIDs` into a `Record<entryId, Places>` map for\n * sandbox cross-referencing. Returns `{ value: undefined }` when no IDs\n * are supplied so callers can pass the result straight into the sandbox.\n * Mirrors the resolution used by analyse-routes and process-routes.\n *\n * @ignore\n */\nexport const findPlacesByEntry = (\n placesEntryIDs: readonly string[] | undefined,\n state: ToolState,\n): { value: Record<string, Places> | undefined } | { error: string } => {\n if (!placesEntryIDs?.length) return { value: undefined };\n const placesByEntry: Record<string, Places> = {};\n for (const id of placesEntryIDs) {\n const placesEntry = state.places.entries.find((entry) => entry.id === id);\n if (!placesEntry) {\n return { error: `No places entry found with id \"${id}\". Use recallPlaces to list available IDs.` };\n }\n placesByEntry[id] = { type: 'FeatureCollection', features: placesEntry.places } as Places;\n }\n return { value: placesByEntry };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Utility for deriving a search bias from the current map viewport.\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport type { BaseMapState } from '../../state';\n\n/** @ignore */\nexport const MIN_VIEWPORT_BIAS_ZOOM = 6;\n\n/** @ignore */\nexport const getViewportBias = (map: BaseMapState, minZoom = MIN_VIEWPORT_BIAS_ZOOM): Position | undefined => {\n const zoom = map.mapLibreMap.getZoom();\n if (zoom < minZoom) return undefined;\n const center = map.mapLibreMap.getCenter();\n return [center.lng, center.lat];\n};\n\n/** @ignore */\nexport const getViewportBoundingBox = (map: BaseMapState): BBox => {\n const bounds = map.mapLibreMap.getBounds();\n return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { AnalysisOutputFormat, runSandboxedFn, validateAnalysisResult } from '../../tools/shared';\n\n/**\n * A single aggregation/analysis result attached to a traffic-incidents entry.\n * Produced by the `analyseIncidents` tool; `data` is whatever the dynamic code returned.\n *\n * @group Agent Toolkit\n */\nexport type IncidentsAnalysis = {\n /** Unique name within the parent entry (used as a key for future UI). */\n name: string;\n timestamp: number;\n /** Optional human-readable description of what the analysis computed. */\n description?: string;\n /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */\n outputFormat: AnalysisOutputFormat;\n /** Arbitrary aggregation result returned by the dynamic code. */\n data: unknown;\n};\n\n/**\n * A registered, re-executing analysis. Produced by `analyseIncidents`; replayed by\n * an entry's {@link IncidentsAnalyses} on every monitor-tick of its source.\n *\n * @group Agent Toolkit\n */\nexport type IncidentsAnalysisSpec = {\n name: string;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n code: string;\n /** Source entry id the spec reads from. */\n source: string;\n};\n\n/**\n * Run a one-shot incidents analysis spec against an incidents snapshot. Used by\n * `analyseIncidents` for the initial result and by {@link IncidentsAnalyses}\n * for each replay tick.\n *\n * `sampledAt` is the canonical moment the data is from — it becomes the sandbox's\n * `now` so user code computing deltas/rates against `previous` sees a consistent\n * time axis across the tick that produced both this result and the entry data.\n *\n * Composes `runSandboxedFn` (compile + execute) with `validateAnalysisResult`\n * (JSON-normalize, undefined-check, chart-shape-check) so both call sites share\n * the same contract — including the `toJsonSafe` round-trip that prevents\n * `NaN` / sparse-array values from poisoning the next AI SDK turn.\n */\nexport const runIncidentSpec = async (\n spec: IncidentsAnalysisSpec,\n incidents: TrafficIncident[],\n previous: unknown,\n sampledAt: number,\n): Promise<{ value: unknown } | { error: string }> => {\n const sandbox = await runSandboxedFn(\n spec.code,\n ['incidents', 'h3', 'turf', 'now', 'log', 'previous'],\n [incidents, h3, turf, new Date(sampledAt), () => {}, previous],\n 'Analysis',\n );\n if ('error' in sandbox) return { error: sandbox.error };\n return validateAnalysisResult(sandbox.value, spec.outputFormat);\n};\n\n/**\n * Maximum number of historical results retained per analysis name. Older entries\n * are evicted as new ones land. At a 60s monitor tick this is ~4 hours of trend\n * data — enough for a chart, bounded for memory.\n */\nexport const MAX_HISTORY_PER_ANALYSIS = 240;\n\n/**\n * Per-entry analysis registry: owns re-executing specs *and* the timeline of\n * their results. One thing, three parts — name, recipe (spec), output history —\n * kept together instead of split across sibling fields.\n *\n * Each named analysis keeps a bounded chronological history (see\n * {@link MAX_HISTORY_PER_ANALYSIS}) so consumers can trend results over time;\n * `getResult(name)` returns the most recent, `history(name)` the full timeline.\n *\n * Errors from individual specs are dropped silently — replay should never break\n * the monitor tick that triggered it.\n *\n * @group Agent Toolkit\n */\nexport class IncidentsAnalyses {\n private _specs: IncidentsAnalysisSpec[] = [];\n private readonly _historyByName = new Map<string, IncidentsAnalysis[]>();\n\n get specs(): readonly IncidentsAnalysisSpec[] {\n return this._specs;\n }\n\n /**\n * Latest result for each registered spec, in registration order. Specs whose\n * history is empty (e.g. only failed runs) are skipped. UI layers that \"render\n * whatever analyses this entry has\" should iterate this instead of poking at\n * private history state.\n */\n get results(): readonly IncidentsAnalysis[] {\n const out: IncidentsAnalysis[] = [];\n for (const spec of this._specs) {\n const latest = this.getResult(spec.name);\n if (latest) out.push(latest);\n }\n return out;\n }\n\n /** Full timeline for a single named analysis, oldest first. Empty when unknown. */\n history(name: string): readonly IncidentsAnalysis[] {\n return this._historyByName.get(name) ?? [];\n }\n\n /** Most recent result for a given analysis name, or undefined. */\n getResult(name: string): IncidentsAnalysis | undefined {\n const history = this._historyByName.get(name);\n return history?.at(-1);\n }\n\n /**\n * Register a spec, or replace an existing one with the same name. When the new spec\n * differs from the existing one (code, outputFormat, or description), drops history\n * for that name — old results belong to a different analysis and must not leak into\n * `getResult` / `results`, which describe the *current* spec. Identical re-registers\n * are no-ops and preserve history (so trend continuity survives idempotent retries).\n */\n register(spec: IncidentsAnalysisSpec): void {\n const idx = this._specs.findIndex((s) => s.name === spec.name);\n if (idx >= 0) {\n const existing = this._specs[idx];\n const materiallyChanged =\n existing.code !== spec.code ||\n existing.outputFormat !== spec.outputFormat ||\n existing.description !== spec.description;\n this._specs[idx] = spec;\n if (materiallyChanged) this._historyByName.delete(spec.name);\n } else {\n this._specs.push(spec);\n }\n }\n\n /** Drop a spec by name. No-op when unknown. Result history is preserved. */\n unregister(name: string): void {\n this._specs = this._specs.filter((s) => s.name !== name);\n }\n\n /**\n * Append a result to the timeline for its name, evicting the oldest entry\n * when the per-name cap is hit. Used by the slice for one-shot tool runs and\n * by {@link replay} for each fresh tick.\n */\n attach(result: IncidentsAnalysis): void {\n let history = this._historyByName.get(result.name);\n if (!history) {\n history = [];\n this._historyByName.set(result.name, history);\n }\n history.push(result);\n if (history.length > MAX_HISTORY_PER_ANALYSIS) history.shift();\n }\n\n /**\n * Re-run every registered spec against `data`, threading the previous result\n * from the timeline. `sampledAt` is the canonical moment for this replay —\n * shared with the entry data and the sandbox's `now`, so consumers can join\n * across them safely. Each fresh result is appended before being returned.\n * Failed specs are skipped.\n */\n async replay(data: TrafficIncident[], sampledAt: number): Promise<IncidentsAnalysis[]> {\n const out: IncidentsAnalysis[] = [];\n for (const spec of this._specs) {\n const result = await runIncidentSpec(spec, data, this.getResult(spec.name)?.data, sampledAt);\n if ('error' in result) continue;\n const fresh: IncidentsAnalysis = {\n name: spec.name,\n timestamp: sampledAt,\n ...(spec.description && { description: spec.description }),\n outputFormat: spec.outputFormat,\n data: result.value,\n };\n this.attach(fresh);\n out.push(fresh);\n }\n return out;\n }\n}\n","import type { BBox, TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport { StateEvents } from '../../events';\nimport type { IncidentSnapshot, MonitoredArea, PollingStatus } from './types';\n\n/** Default interval between polling ticks (ms). Customizable via {@link IncidentMonitor.start}. */\nexport const INCIDENT_MONITOR_INTERVAL_MS = 60_000;\n\n/**\n * Dependency injection for an {@link IncidentMonitor}. Production code wires\n * `fetchIncidents` to `trafficIncidentDetails`; tests can pass a deterministic fake\n * and override the timer.\n *\n * The fetcher returns just the incidents — the monitor stamps `takenAt` itself\n * so every consumer of one tick (entry data, derived analyses, sandbox `now`)\n * agrees on the same canonical moment.\n *\n * @group Agent Toolkit\n */\nexport type IncidentMonitorDeps = {\n fetchIncidents: (bbox: BBox) => Promise<TrafficIncident[]>;\n /** Override for tests. Defaults to the global `setInterval`. */\n setInterval?: (fn: () => void, ms: number) => unknown;\n clearInterval?: (handle: unknown) => void;\n};\n\n/**\n * Events fired by {@link IncidentMonitor}. The slice subscribes once and re-emits\n * translated copies (`monitor-tick` / `monitor-error`) keyed by entry id.\n */\nexport type IncidentMonitorEvents = {\n /** A poll succeeded. Payload: the fresh snapshot. */\n tick: IncidentSnapshot;\n /** A poll failed — the monitor has cleared its timer and moved to `stopped-error`. */\n error: string;\n};\n\n/**\n * One-shot polling loop scoped to a single bbox. Owns its timer, status, error,\n * and captured area. Re-starting after `stop()` is allowed and resets all state.\n *\n * The monitor never knows about the entry that owns it — it surfaces ticks/errors\n * through `events`, and the caller decides what to do with the snapshot.\n *\n * @group Agent Toolkit\n */\nexport class IncidentMonitor {\n readonly events = new StateEvents<IncidentMonitorEvents>();\n\n private _status: PollingStatus = 'idle';\n private _area?: MonitoredArea;\n private _error?: string;\n private _intervalHandle?: unknown;\n private _clearInterval?: (handle: unknown) => void;\n private _fetchIncidents?: IncidentMonitorDeps['fetchIncidents'];\n\n constructor(private readonly _bbox: BBox) {}\n\n get status(): PollingStatus {\n return this._status;\n }\n\n get monitoredArea(): MonitoredArea | undefined {\n return this._area;\n }\n\n get error(): string | undefined {\n return this._error;\n }\n\n /**\n * True when the monitor is *currently* polling. Distinct from {@link isActive},\n * which also covers `stopped-error` — a state where the timer is gone but the\n * monitor still carries an unacknowledged error. Use this for \"is the entry\n * being polled right now\"; use `isActive` for \"is there anything to clean up\".\n */\n get isRunning(): boolean {\n return this._status === 'running';\n }\n\n /** True when the monitor has any polling activity (running or stopped-error). */\n get isActive(): boolean {\n return this._status !== 'idle';\n }\n\n /**\n * Begin polling. Captures `deps` for the lifetime of the run, fires the first tick\n * immediately, then schedules an interval. No-op when already running.\n */\n start(area: MonitoredArea, deps: IncidentMonitorDeps, intervalMs: number = INCIDENT_MONITOR_INTERVAL_MS): void {\n if (this._status === 'running') return;\n this._status = 'running';\n this._error = undefined;\n this._area = area;\n // Deps are NOT sticky across stop/start cycles — every restart (incl. recovery\n // from `stopped-error`) reads fresh `fetchIncidents` / timer overrides from the\n // caller. Tests that swap fakes between cycles rely on this.\n this._fetchIncidents = deps.fetchIncidents;\n this._clearInterval =\n deps.clearInterval ?? ((handle: unknown) => clearInterval(handle as ReturnType<typeof setInterval>));\n const setIntervalFn = deps.setInterval ?? setInterval;\n // Eager first tick — consumers shouldn't wait an interval for the initial snapshot.\n void this._tick();\n this._intervalHandle = setIntervalFn(() => void this._tick(), intervalMs);\n }\n\n /** Stop polling and reset state. No-op when idle. */\n stop(): void {\n if (this._status === 'idle') return;\n this._releaseTimer();\n this._status = 'idle';\n this._error = undefined;\n this._area = undefined;\n this._fetchIncidents = undefined;\n }\n\n // _tick is the canonical timestamp authority for one polling cycle. It stamps\n // `takenAt` after the fetch resolves so every downstream consumer (entry data,\n // derived analyses, sandbox `now`) shares one byte-equal moment.\n private async _tick(): Promise<void> {\n const fetchIncidents = this._fetchIncidents;\n if (!fetchIncidents) return;\n try {\n const incidents = await fetchIncidents(this._bbox);\n const snap: IncidentSnapshot = { takenAt: Date.now(), incidents };\n this.events.emit('tick', snap);\n } catch (err) {\n this._failed(err instanceof Error ? err.message : String(err));\n }\n }\n\n private _failed(message: string): void {\n this._releaseTimer();\n this._error = message;\n this._status = 'stopped-error';\n this._fetchIncidents = undefined;\n this.events.emit('error', message);\n }\n\n private _releaseTimer(): void {\n if (this._intervalHandle != null) {\n this._clearInterval?.(this._intervalHandle);\n this._intervalHandle = undefined;\n }\n this._clearInterval = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type BBox, bboxFromGeoJSON, type TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport { type TomTomMap, TrafficIncidentOverlayModule } from '@tomtom-org/maps-sdk/map';\nimport type { TrafficIncidentDetailsByBBoxParams } from '@tomtom-org/maps-sdk/services';\nimport type { StateSlice } from '../../types';\nimport { StateEvents } from '../events';\nimport type { EntryMode } from '../state';\nimport { IncidentsAnalyses, type IncidentsAnalysis, type IncidentsAnalysisSpec } from './analysis';\nimport { IncidentMonitor, type IncidentMonitorDeps } from './monitor/monitor';\nimport type { IncidentSnapshot, MonitoredArea } from './monitor/types';\n\nexport type { IncidentsAnalysis, IncidentsAnalysisSpec } from './analysis';\n\nexport type TrafficIncidentsEntry = {\n id: string;\n timestamp: number;\n label: string;\n data: TrafficIncident[];\n /**\n * The captured query that produced this entry. Replayed by the monitor on each tick so\n * polling stays in the same slice the entry was loaded with. Reuses the loader's own\n * params shape; the entry stores a resolved {@link BBox} tuple in `bbox` (the loader\n * normalises any GeoJSON value before handing the params off).\n */\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox };\n /**\n * Per-entry analysis registry: owns re-executing specs *and* their results.\n * Lazy-created on first {@link TrafficIncidentsState.setAnalysisSpec} or\n * {@link TrafficIncidentsState.addAnalysisToEntry}.\n */\n _analyses?: IncidentsAnalyses;\n /**\n * Per-entry incident-details module. Each entry owns its own module so two areas can be\n * rendered on the map at the same time without sharing a single source. Lazy-initialised\n * via `TrafficIncidentsState.getEntryModule` — undefined until first show. Subscribe to\n * clicks via `module.events.on('click', ...)` once retrieved.\n */\n _module?: TrafficIncidentOverlayModule;\n /** True while this entry is currently rendered on the map. */\n _shown?: boolean;\n /**\n * Focused subset on this entry — the agent's `focusIds` write-side. Drives the dim/highlight\n * affordance on `_module` and feeds `focusedFeatures(entryId)` for stepping through.\n */\n _focus?: { ids: Set<string>; reason?: string };\n /**\n * Per-entry polling loop. Lazy-created on first {@link TrafficIncidentsState.startMonitoring}\n * call; reused across stop/start cycles. Owns timer, status, error, captured area.\n */\n _monitor?: IncidentMonitor;\n /**\n * Monotonic counter incremented on every {@link TrafficIncidentsState.replaceEntryData}\n * call. Used to detect out-of-order ticks: if a slow `module.show()` from an earlier\n * tick resolves after a newer tick has already overwritten `data`, the late path bails\n * before reapplying focus / replaying analyses.\n */\n _tick?: number;\n};\n\n/**\n * Snapshot of an entry's focus subset. Returned by `getFocus` / payload on `focus-change`.\n *\n * @group Agent Toolkit\n */\nexport type IncidentFocus = { ids: Set<string>; reason?: string };\n\n/**\n * Events fired by {@link TrafficIncidentsState}. Subscribe via\n * `state.trafficIncidents.events.on(type, handler)` — returns an unsubscribe function.\n *\n * @group Agent Toolkit\n */\nexport type TrafficIncidentsStateEvents = {\n /** History changed — entry added, replaced, removed, or cleared via `reset()`. Payload: full entries snapshot. */\n 'entries-change': readonly TrafficIncidentsEntry[];\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<TrafficIncidentsEntry['id']>;\n /**\n * A fresh analysis result landed on an entry. Fires when a spec is first\n * registered, when it is replaced under the same name, AND on every monitor-tick\n * replay — the event is intentionally agnostic about whether the spec is new\n * or recomputed; subscribers re-render off the latest result either way.\n */\n 'analysis-change': { entryId: TrafficIncidentsEntry['id']; analysis: IncidentsAnalysis };\n /** Focus subset for an entry changed (or cleared, when `focus === null`). */\n 'focus-change': { entryId: TrafficIncidentsEntry['id']; focus: IncidentFocus | null };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n /** A monitored entry produced a fresh snapshot (per-tick). */\n 'monitor-tick': { entryId: TrafficIncidentsEntry['id']; snap: IncidentSnapshot };\n /** A monitored entry's poll failed — slice has cleared the timer. */\n 'monitor-error': { entryId: TrafficIncidentsEntry['id']; error: string };\n /**\n * Polling began on an entry (idle → running). Skipped when `startMonitoring`\n * was a no-op against an already-running monitor.\n */\n 'monitor-start': { entryId: TrafficIncidentsEntry['id'] };\n /**\n * Polling halted on an entry. `reason: 'manual'` covers explicit\n * `stopMonitoring` and entry teardown (remove / single-mode trim / reset).\n * `reason: 'error'` fires alongside `monitor-error` — error-only listeners\n * keep using `monitor-error`; lifecycle listeners can rely on this alone.\n */\n 'monitor-stop': { entryId: TrafficIncidentsEntry['id']; reason: 'manual' | 'error' };\n};\n\n/**\n * State for traffic incidents: entry history and map rendering via per-entry\n * TrafficIncidentOverlayModule. Each entry can be a separate area; under the default\n * `multiple` mode several entries can render at once.\n *\n * Each entry owns its own module (lazy-init on first show) plus an optional per-entry\n * background monitor. Subscribe to changes via `events.on('...')`.\n *\n * @group Agent Toolkit\n */\nexport class TrafficIncidentsState implements StateSlice {\n private _entries: TrafficIncidentsEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link TrafficIncidentsStateEvents}. */\n readonly events = new StateEvents<TrafficIncidentsStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly TrafficIncidentsEntry[] {\n return this._entries;\n }\n\n /**\n * Display policy: `multiple` (default) keeps every history entry available to render\n * simultaneously; `single` enforces \"at most one entry on the map at a time\" and trims\n * the history down to the latest entry whenever it's set.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every entry\n * older than the latest, drop them from history, and emit `entries-change`. No-op when the\n * mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const latest = this._entries.at(-1);\n const dropped = this._entries.slice(0, -1);\n await Promise.all(dropped.map((entry) => this._teardownEntry(entry)));\n this._entries = latest ? [latest] : [];\n this.events.emit('entries-change', this._entries);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n this.events.emit('mode-change', mode);\n }\n\n /** Set of entries currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<TrafficIncidentsEntry['id']> {\n const out = new Set<TrafficIncidentsEntry['id']>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Append a new entry to the history. Under `single` mode the new entry replaces every\n * older one (and tears their modules + monitors down). `sampledAt` is the moment the\n * data was captured — pass one canonical value per \"moment\" so future joins (entry\n * data ↔ derived analyses) are byte-equal. Returns the assigned id.\n */\n addIncidentsEntry(\n data: TrafficIncident[],\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n label: string,\n sampledAt: number,\n explicitId?: string,\n ): string {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n for (const entry of this._entries) void this._teardownEntry(entry);\n this._entries = [];\n }\n // Always dedupe — `incidents-${length}` is not collision-free across removes\n // (e.g. add a, add b, remove a → length=1, fallback `incidents-1` collides with surviving `b`).\n const id = this._uniqueEntryId(explicitId ?? `incidents-${this._entries.length}`);\n this._entries.push({ id, timestamp: sampledAt, label, data, params });\n this.events.emit('entries-change', this._entries);\n return id;\n }\n\n /**\n * Replace an entry's data in place and repaint its module without moving the camera.\n * Used by the per-entry monitor tick so incident geometries and any open detail\n * panel stay in sync with the freshest snapshot. `sampledAt` is the canonical moment\n * for this update — propagated to the entry timestamp and to every analysis the\n * registry replays, so all derived data shares one tick. Returns true on success,\n * false when the entry is unknown.\n */\n async replaceEntryData(\n entryId: TrafficIncidentsEntry['id'],\n data: TrafficIncident[],\n sampledAt: number,\n ): Promise<boolean> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n const tick = (entry._tick ?? 0) + 1;\n entry._tick = tick;\n entry.data = data;\n entry.timestamp = sampledAt;\n this.events.emit('entries-change', this._entries);\n if (entry._module) {\n await entry._module.show({ type: 'FeatureCollection', features: data });\n // A newer tick beat us across the await — its own pass will reconcile\n // focus and replay specs against fresher data, so bail.\n if (entry._tick !== tick) return true;\n if (entry._focus) {\n const present = new Set(data.map((f) => f.properties.id));\n const stillThere = [...entry._focus.ids].filter((id) => present.has(id));\n entry._module.setFocus(stillThere.length > 0 ? stillThere : null);\n if (stillThere.length === 0) {\n entry._focus = undefined;\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n }\n }\n if (entry._tick !== tick) return true;\n await this._rerunRegisteredSpecs(entryId, sampledAt);\n return true;\n }\n\n /**\n * Lazy-init and return the entry's TrafficIncidentOverlayModule. Consumers subscribe to\n * clicks directly on the returned module via `module.events.on('click', ...)`; use\n * `module.distinctIncidents(features)` inside the handler if a stacked-click count is\n * needed. Throws on unknown entry id.\n */\n async getEntryModule(entryId: TrafficIncidentsEntry['id']): Promise<TrafficIncidentOverlayModule> {\n const entry = this._requireEntry(entryId);\n entry._module ??= await TrafficIncidentOverlayModule.get(this._ttMap);\n return entry._module;\n }\n\n /**\n * Render an entry on the map. Lazy-inits its module, pushes the entry's incidents, and\n * (when `fitBounds` is true) fits the camera to the entry's bbox. Emits `shown-change`\n * once the entry transitions to shown.\n */\n async showEntry(entryId: TrafficIncidentsEntry['id'], fitBounds = true): Promise<void> {\n const entry = this._requireEntry(entryId);\n const module = await this.getEntryModule(entryId);\n await module.show({ type: 'FeatureCollection', features: entry.data });\n module.setVisible(true);\n if (fitBounds) {\n this._ttMap.mapLibreMap.fitBounds(entry.params.bbox as [number, number, number, number], {\n padding: 50,\n maxZoom: 14,\n });\n }\n const wasShown = !!entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Hide an entry — clears its module + drops focus. The module reference stays on the\n * entry for cheap re-show. No-op when the entry is unknown or already hidden.\n */\n async hideEntry(entryId: TrafficIncidentsEntry['id']): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry || !entry._shown) return;\n if (entry._focus) {\n entry._focus = undefined;\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n entry._module?.setVisible(false);\n await entry._module?.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Drop an entry from history. Hides it from the map and stops its monitor if any.\n * Emits `entries-change` and, when the entry was shown, `shown-change`. No-op when the\n * id is unknown.\n */\n async removeEntry(entryId: TrafficIncidentsEntry['id']): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n const entry = this._entries[idx];\n const wasShown = !!entry._shown;\n await this._teardownEntry(entry);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n if (wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Attach an analysis result to an existing entry. Names are unique within a single\n * entry — adding one with an existing name replaces it. Returns true on success, false\n * if the entry doesn't exist. Lazy-creates the entry's {@link IncidentsAnalyses}.\n */\n addAnalysisToEntry(entryId: TrafficIncidentsEntry['id'], analysis: IncidentsAnalysis): boolean {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n (entry._analyses ??= new IncidentsAnalyses()).attach(analysis);\n this.events.emit('analysis-change', { entryId: entry.id, analysis });\n // Re-emit entries-change so subscribers re-render — analyses mutate the entry in place.\n this.events.emit('entries-change', this._entries);\n return true;\n }\n\n /**\n * Register (or replace) a re-executing analysis spec on its source entry. Names are unique\n * per entry — adding one with an existing name replaces it. Lazy-creates the entry's\n * {@link IncidentsAnalyses} on first call.\n */\n setAnalysisSpec(spec: IncidentsAnalysisSpec): void {\n const entry = this._entries.find((e) => e.id === spec.source);\n if (!entry) return;\n (entry._analyses ??= new IncidentsAnalyses()).register(spec);\n }\n\n /** Remove a spec by name from every entry that carries it. */\n removeAnalysisSpec(name: string): void {\n for (const entry of this._entries) entry._analyses?.unregister(name);\n }\n\n /** Snapshot of an entry's focus subset, or null when no focus is active or the entry is unknown. */\n getFocus(entryId: TrafficIncidentsEntry['id']): IncidentFocus | null {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._focus) return null;\n return { ids: new Set(entry._focus.ids), reason: entry._focus.reason };\n }\n\n /**\n * Set the focus subset on a specific entry. Filters ids against the entry's current\n * incidents (drops unknown ones) and pushes the result onto the entry's module.\n * Returns the kept count and any dropped ids.\n */\n setFocus(\n entryId: TrafficIncidentsEntry['id'],\n ids: readonly string[],\n reason?: string,\n ): { focusedCount: number; droppedIds: string[] } {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return { focusedCount: 0, droppedIds: [...ids] };\n if (ids.length === 0) {\n this.clearFocus(entryId);\n return { focusedCount: 0, droppedIds: [] };\n }\n const present = new Set(entry.data.map((f) => f.properties.id as string));\n const keep: string[] = [];\n const dropped: string[] = [];\n for (const id of ids) {\n if (present.has(id)) keep.push(id);\n else dropped.push(id);\n }\n if (keep.length === 0) {\n return { focusedCount: 0, droppedIds: dropped };\n }\n entry._focus = { ids: new Set(keep), reason };\n entry._module?.setFocus(keep);\n this.events.emit('focus-change', { entryId: entry.id, focus: this.getFocus(entryId) });\n return { focusedCount: keep.length, droppedIds: dropped };\n }\n\n clearFocus(entryId: TrafficIncidentsEntry['id']): void {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._focus) return;\n entry._focus = undefined;\n entry._module?.setFocus(null);\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n\n /**\n * The focused incidents in the order passed to {@link setFocus}. Empty when no focus is\n * active on the entry or when the entry is unknown. Used by UI layers that step through\n * the focused subset (prev/next).\n */\n focusedFeatures(entryId: TrafficIncidentsEntry['id']): TrafficIncident[] {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._focus) return [];\n const byId: Record<string, TrafficIncident> = {};\n for (const f of entry.data) {\n const id = f.properties.id;\n if (typeof id === 'string') byId[id] = f;\n }\n const ordered: TrafficIncident[] = [];\n for (const id of entry._focus.ids) {\n const feat = byId[id];\n if (feat) ordered.push(feat);\n }\n return ordered;\n }\n\n /**\n * Fly the camera to the i-th focused incident on an entry (0-based). Wraps out-of-range\n * indices so callers can pass raw prev/next counters. No-op when the entry has no active\n * focus. Line geometries fitBounds; points flyTo with a sensible zoom.\n */\n navigateFocus(entryId: TrafficIncidentsEntry['id'], index: number): void {\n const features = this.focusedFeatures(entryId);\n if (features.length === 0) return;\n const i = ((index % features.length) + features.length) % features.length;\n const feature = features[i];\n const map = this._ttMap.mapLibreMap;\n if (feature.geometry.type === 'Point') {\n map.flyTo({ center: feature.geometry.coordinates as [number, number], zoom: 15, duration: 600 });\n return;\n }\n const bbox = bboxFromGeoJSON(feature);\n if (!bbox) return;\n map.fitBounds(bbox, { padding: 120, maxZoom: 16, duration: 600 });\n }\n\n /**\n * Walk entries newest-first to find which one currently holds an incident with this id.\n * Useful for UI layers that hold an incident id without remembering which entry produced it.\n */\n findEntryWithIncident(incidentId: string): TrafficIncidentsEntry | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n const entry = this._entries[i];\n if (entry.data.some((f) => f.properties.id === incidentId)) return entry;\n }\n return undefined;\n }\n\n // ---- Per-entry monitoring ----\n\n /**\n * Start polling for an entry. Lazy-creates an {@link IncidentMonitor}, wires its events\n * once, then delegates to {@link IncidentMonitor.start}. No-op when polling is already\n * running. Throws on unknown entry id.\n */\n startMonitoring(\n entryId: TrafficIncidentsEntry['id'],\n area: MonitoredArea,\n deps: IncidentMonitorDeps,\n opts?: { intervalMs?: number },\n ): void {\n const entry = this._requireEntry(entryId);\n const monitor = (entry._monitor ??= this._createMonitorForEntry(entry));\n // Capture the pre-start state so we fire `monitor-start` on every transition\n // *into* running — that includes recovery from `stopped-error`, not just from\n // idle. `monitor.start` is itself idempotent on running.\n const wasRunning = monitor.isRunning;\n monitor.start(area, deps, opts?.intervalMs);\n if (!wasRunning) this.events.emit('monitor-start', { entryId });\n }\n\n /** Stop polling. No-op when idle or unknown. */\n stopMonitoring(entryId: TrafficIncidentsEntry['id']): void {\n const monitor = this._entries.find((e) => e.id === entryId)?._monitor;\n if (!monitor?.isActive) return;\n monitor.stop();\n this.events.emit('monitor-stop', { entryId, reason: 'manual' });\n }\n\n /**\n * True when the entry is *currently* being polled. A monitor stuck in\n * `stopped-error` returns false here so the start tool can report a truthful\n * `alreadyRunning` and a fresh `start` call actually re-arms the timer\n * instead of silently being treated as a no-op.\n */\n isMonitored(entryId: TrafficIncidentsEntry['id']): boolean {\n return this._entries.find((e) => e.id === entryId)?._monitor?.isRunning ?? false;\n }\n\n private _createMonitorForEntry(entry: TrafficIncidentsEntry): IncidentMonitor {\n const monitor = new IncidentMonitor(entry.params.bbox);\n monitor.events.on('tick', (snap) => {\n void this.replaceEntryData(entry.id, snap.incidents, snap.takenAt);\n this.events.emit('monitor-tick', { entryId: entry.id, snap });\n });\n monitor.events.on('error', (error) => {\n this.events.emit('monitor-error', { entryId: entry.id, error });\n this.events.emit('monitor-stop', { entryId: entry.id, reason: 'error' });\n });\n return monitor;\n }\n\n // ---- Internal helpers ----\n\n private _requireEntry(entryId: TrafficIncidentsEntry['id']): TrafficIncidentsEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`TrafficIncidentsState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n // Returns `requested` unchanged when free, otherwise appends `-2`, `-3`, ... until a free id\n // is found. Lets the LLM hand us a semantic id without worrying about collisions across calls.\n private _uniqueEntryId(requested: string): string {\n const taken = new Set(this._entries.map((entry) => entry.id));\n if (!taken.has(requested)) return requested;\n for (let i = 2; ; i++) {\n const candidate = `${requested}-${i}`;\n if (!taken.has(candidate)) return candidate;\n }\n }\n\n // Replay all analysis specs registered on this entry. The registry stores each fresh\n // result internally; the slice's job is to surface the events so external subscribers\n // re-render. `sampledAt` propagates from the caller's tick so every fresh analysis\n // shares the entry's new timestamp.\n private async _rerunRegisteredSpecs(entryId: TrafficIncidentsEntry['id'], sampledAt: number): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._analyses?.specs.length) return;\n const fresh = await entry._analyses.replay(entry.data, sampledAt);\n for (const analysis of fresh) this.events.emit('analysis-change', { entryId: entry.id, analysis });\n if (fresh.length > 0) this.events.emit('entries-change', this._entries);\n }\n\n // Tear an entry down: stop its monitor and clear its module. Idempotent.\n // Used by removeEntry, single-mode trimming, and reset.\n //\n // Synchronous emits run before any await so callers that fire-and-forget\n // (notably `reset`, which calls `events.clear()` right after) still see them.\n private async _teardownEntry(entry: TrafficIncidentsEntry): Promise<void> {\n this.stopMonitoring(entry.id);\n if (entry._focus) {\n entry._focus = undefined;\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n if (entry._shown) entry._shown = false;\n if (entry._module) {\n entry._module.setVisible(false);\n await entry._module.clear();\n }\n entry._module = undefined;\n }\n\n reset(): void {\n // Tear every entry down synchronously where we can — the awaits inside _teardownEntry\n // are just module.clear()s; we run them concurrently and don't block reset() itself.\n for (const entry of this._entries) void this._teardownEntry(entry);\n const hadEntries = this._entries.length > 0;\n this._entries = [];\n if (hadEntries) {\n this.events.emit('entries-change', this._entries);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n this.events.clear();\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type TomTomMap, TrafficFlowModule, TrafficIncidentsModule } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\n\n/**\n * State for traffic tile overlays — flow tiles and incident overlay tiles. These are\n * raster/tile layers that the user toggles on top of the base map; result caching and\n * per-result rendering for incident *features* lives in {@link TrafficIncidentsState},\n * and analytics aggregations in {@link TrafficAreaAnalyticsState}.\n *\n * @group Agent Toolkit\n */\nexport class TrafficTilesState implements StateSlice {\n private _trafficFlowModule?: TrafficFlowModule;\n private _trafficIncidentsModule?: TrafficIncidentsModule;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getters (lazy initialization)\n\n async getTrafficFlowModule(): Promise<TrafficFlowModule> {\n this._trafficFlowModule ??= await TrafficFlowModule.get(this._ttMap);\n return this._trafficFlowModule;\n }\n\n async getTrafficIncidentsModule(): Promise<TrafficIncidentsModule> {\n this._trafficIncidentsModule ??= await TrafficIncidentsModule.get(this._ttMap);\n return this._trafficIncidentsModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get trafficFlowModule(): TrafficFlowModule | undefined {\n return this._trafficFlowModule;\n }\n\n get trafficIncidentsModule(): TrafficIncidentsModule | undefined {\n return this._trafficIncidentsModule;\n }\n\n reset(): void {\n this._trafficFlowModule = undefined;\n this._trafficIncidentsModule = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport { TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice, ToolState } from '../types';\nimport { BaseMapState } from './base-map';\nimport { CustomGeometriesState } from './custom-geometries';\nimport { MapPOIsState } from './map-pois';\nimport { PlacesState } from './places';\nimport { RangeState } from './range';\nimport { RoutingState } from './routing';\nimport { TrafficAreaAnalyticsState } from './traffic-area-analytics';\nimport { TrafficIncidentsState } from './traffic-incidents';\nimport { TrafficTilesState } from './traffic-tiles';\n\nexport { BaseMapState } from './base-map';\nexport { CustomGeometriesState, type CustomGeometriesStateEvents } from './custom-geometries/state';\nexport { MapPOIsState } from './map-pois';\nexport { type PlacesMarkerType, PlacesState, type PlacesStateEvents } from './places/state';\nexport { RangeState, type RangeStateEvents } from './range/state';\nexport { RoutingState, type RoutingStateEvents } from './routing/state';\nexport { TrafficAreaAnalyticsState, type TrafficAreaAnalyticsStateEvents } from './traffic-area-analytics/state';\nexport { type IncidentMonitorDeps } from './traffic-incidents/monitor/monitor';\nexport {\n type IncidentFocus,\n type IncidentsAnalysis,\n TrafficIncidentsState,\n type TrafficIncidentsStateEvents,\n} from './traffic-incidents/state';\nexport { TrafficTilesState } from './traffic-tiles/state';\n\nTomTomConfig.instance.put({ language: 'en-GB' });\n\n/**\n * Per-slice display policy. With `multiple` (default) several history entries can be on the\n * map at once; with `single` only the most recent one stays. Switching from `multiple` to\n * `single` automatically drops every entry except the latest (and clears their modules) so\n * the map matches the new policy immediately.\n *\n * @group Agent Toolkit\n */\nexport type EntryMode = 'single' | 'multiple';\n\n/**\n * State slice that owns a set of \"entries\" (routes, ranges) where each entry can be hidden\n * individually by id and the slice exposes the currently-shown set. Implemented by\n * {@link RoutingState} and {@link RangeState}; consumed by helpers that need to hide\n * previously-shown entries before rendering a new one (see `hidePreviousShownEntries`).\n *\n * Not implemented by {@link PlacesState}, which uses a wider per-entry marker-type model\n * (`removeShownEntries(ids)` rather than `hideEntry(id)`).\n *\n * @group Agent Toolkit\n */\nexport interface ShownEntriesSlice extends StateSlice {\n readonly entryMode: EntryMode;\n readonly shownEntryIds: ReadonlySet<string>;\n hideEntry(entryId: string): Promise<void>;\n}\n\n/**\n * Factory that creates a fully initialized ToolState from a TomTomMap instance.\n * Optionally merges custom state slices alongside built-in state.\n *\n * @param map - TomTomMap instance for module initialization\n * @param customSlices - Additional state slices accessible by custom tools\n *\n * @group Agent Toolkit\n */\nexport const createToolState = <T extends Record<string, unknown> = Record<string, never>>(\n map: TomTomMap,\n customSlices?: T,\n): ToolState & T => {\n const base: ToolState = {\n places: new PlacesState(map),\n mapPOIs: new MapPOIsState(map),\n routing: new RoutingState(map),\n baseMap: new BaseMapState(map),\n trafficTiles: new TrafficTilesState(map),\n trafficAreaAnalytics: new TrafficAreaAnalyticsState(map),\n trafficIncidents: new TrafficIncidentsState(map),\n ranges: new RangeState(map),\n customGeometries: new CustomGeometriesState(map),\n };\n if (customSlices) Object.assign(base, customSlices);\n return base as ToolState & T;\n};\n","/**\n * @module agent-toolkit-system-prompt\n *\n * Behavioral contract: persona, tone, dual-response, cadence — lives here.\n *\n * Tool mechanics: call shape, return shape, error handling, when-to-use vs.\n * which-tool-next — lives in the relevant tool's description string and Zod\n * schema. Each tool re-explains its own role; the LLM sees those alongside\n * this prompt.\n *\n * If you find yourself adding lines here to fix an LLM mistake, ask first\n * whether the fix belongs in a tool description or a tool error message.\n */\n\n/**\n * The base system prompt that teaches the LLM how to use the agent toolkit tools.\n *\n * @remarks\n * Export this constant so consumers can reference or extend it when providing\n * a custom system prompt via `MapAgentOptions.systemPrompt`.\n *\n * @example\n * ```typescript\n * import { createMapAgent, BASE_SYSTEM_PROMPT } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n *\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * systemPrompt: BASE_SYSTEM_PROMPT + '\\n\\nAlways respond in Spanish.'\n * });\n * ```\n *\n * @group Agent Toolkit\n */\nexport const BASE_SYSTEM_PROMPT = `You are a helpful map assistant with access to a TomTom interactive map and location services.\nIf a request is completely unrelated to geospatial topics, gracefully decline and steer the user back to map-related tasks you can help with.\n\nCOORDINATE ORDER:\n- All coordinates are [longitude, latitude] (GeoJSON standard)\n- All bounding boxes are [minLng, minLat, maxLng, maxLat]\n\nTOOL EXECUTION:\n- Call context-independent tools in the same step (parallel execution).\n- Many service tools have a show or showOnMap parameter — use it to display results on the map in one step instead of calling a separate show tool afterward.\n- Use standalone updatePlacesDisplay / updateRoutesDisplay / showWaypoints only when displaying results already in context from a previous step.\n\nSESSION STATE & ENTRIES:\n- Three append-only histories live in the session: \\`places\\`, \\`routes\\`, \\`ranges\\`. Each tool call that produces results stores a new ENTRY (id like \\`places-3\\`, \\`routes-1\\`, \\`ranges-0\\`) — these ids stay stable and are accepted by every show / recall / processX tool.\n- Each slice has an \\`entryMode\\`: \\`multiple\\` (default — overlay several entries on the map) or \\`single\\` (only the latest entry stays; switching to it auto-drops every older entry from history). Inspect via \\`recallState\\` / \\`recallPlaces\\` / \\`recallRoutes\\` / \\`recallRanges\\`. Flip with \\`setEntryMode({ slice, mode })\\` when the user asks to \"only show one at a time\" or \"let me overlay multiple\".\n- Mode is sticky across turns — under \\`single\\`, calling another \\`discoverPlaces\\` automatically replaces the previous places entry.\n- Always check \\`recallX\\` (or \\`recallState\\`) before guessing entry ids; never invent them.\n\nLOCATION REFERENCE:\n- \"near me\" / \"my location\" / \"where I am\" → getCurrentLocation (user's physical GPS position, may prompt permission)\n- \"in this area\" / \"here on the map\" / \"near the map center\" → getViewport (map's current view)\n- If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point\n\nRESPONSE FORMATTING:\n- Use markdown in all responses\n- Show key information in bold\n- Use bullet lists when presenting multiple items or results\n- Keep responses concise — lead with the most relevant information\n\n`;\n\n/**\n * Builds the complete system prompt for the agent toolkit.\n *\n * @param customPrompt - Optional complete prompt that replaces the base prompt\n * @param suffix - Optional additional prompt text to append to base prompt (ignored if customPrompt provided)\n * @returns Complete system prompt string\n *\n * @internal\n * @ignore\n */\nexport const buildSystemPrompt = (customPrompt?: string, suffix?: string): string => {\n if (customPrompt) {\n return customPrompt;\n }\n if (suffix) {\n return `${BASE_SYSTEM_PROMPT}\\n\\nADDITIONAL INSTRUCTIONS:\\n${suffix}`;\n }\n return BASE_SYSTEM_PROMPT;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for calculating a bounding box from GeoJSON.\n */\nexport const calculateBBoxSchema = z.object({\n geoJson: z\n .union([z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))])\n .describe('GeoJSON object or array of GeoJSON objects'),\n});\n\nexport const calculateBBoxDescription =\n 'Compute a `[minLng, minLat, maxLng, maxLat]` bbox from GeoJSON (longitude before latitude).';\n\n/** Execute function for calculateBBox — usable with ToolEntry format. */\nexport const executeCalculateBBox = async (params: z.infer<typeof calculateBBoxSchema>, _state: ToolState) => {\n const { geoJson } = params;\n try {\n const bbox = bboxFromGeoJSON(geoJson as any);\n\n if (!bbox) {\n return { error: 'Could not calculate bounding box - input may be empty or invalid GeoJSON' };\n }\n\n return {\n bbox,\n minLng: bbox[0],\n minLat: bbox[1],\n maxLng: bbox[2],\n maxLat: bbox[3],\n };\n } catch (error) {\n return {\n error: `Failed to calculate bbox: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { formatDistance } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for formatting distance.\n */\nexport const formatDistanceSchema = z.object({\n meters: z.number().describe('Distance in meters to format'),\n unitType: z.enum(['metric', 'imperial_us', 'imperial_uk']).optional().describe('default: metric'),\n});\n\nexport const formatDistanceDescription =\n 'Format a distance in meters into a human-readable string (e.g. \"2.5 km\", \"1.5 mi\"). ' +\n 'Use after getSectionProgress, recallRoutes, or any tool that returns distances in meters.';\n\n/** Execute function for formatDistance — usable with ToolEntry format. */\nexport const executeFormatDistance = async (params: z.infer<typeof formatDistanceSchema>, _state: ToolState) => {\n const { meters, unitType } = params;\n try {\n const formatted = formatDistance(meters, unitType ? { type: unitType } : undefined);\n return {\n formatted,\n input: {\n meters,\n unitType: unitType ?? 'metric',\n },\n };\n } catch (error) {\n return {\n error: `Failed to format distance: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { formatDuration } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for formatting duration.\n */\nexport const formatDurationSchema = z.object({\n seconds: z.number().describe('Duration in seconds to format'),\n});\n\nexport const formatDurationDescription =\n 'Format a duration in seconds into a human-readable time string (e.g. \"1 h 30 min\"). ' +\n 'Use after getSectionProgress, recallRoutes, or any tool that returns durations in seconds.';\n\n/** Execute function for formatDuration — usable with ToolEntry format. */\nexport const executeFormatDuration = async (params: z.infer<typeof formatDurationSchema>, _state: ToolState) => {\n const { seconds } = params;\n try {\n const formatted = formatDuration(seconds);\n\n if (formatted === undefined) {\n return {\n formatted: undefined,\n message: '< 1 min',\n input: { seconds },\n };\n }\n\n return {\n formatted,\n input: { seconds },\n };\n } catch (error) {\n return {\n error: `Failed to format duration: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared output schema building blocks reused across multiple tools.\n * Tool-specific output schemas live next to their respective tool definitions.\n */\n\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// Common\n// ---------------------------------------------------------------------------\n\n/** Returned by every tool when execution fails. */\nexport const toolErrorSchema = z.object({\n error: z.string(),\n});\n\n// ---------------------------------------------------------------------------\n// Place (compact summary) — used by geocode, reverse-geocode, search, etc.\n// ---------------------------------------------------------------------------\n\n/** Token-efficient summary of a single place. */\nexport const placeOutputSchema = z\n .object({\n id: z\n .string()\n .describe('Stable place feature ID; pass as `{ placeId }` to setRoute / addWaypointsToRoute / etc.'),\n name: z.string().optional().describe('POI name if available'),\n address: z.string().optional().describe('Human-readable address'),\n position: z.array(z.number()).length(2).describe('[longitude, latitude]'),\n })\n .describe('Compact place summary');\n\n/** Token-efficient summary of a places collection. */\nexport const placesOutputSchema = z\n .object({\n count: z.number(),\n features: z.array(placeOutputSchema),\n })\n .describe('Compact places summary');\n\n// ---------------------------------------------------------------------------\n// Route (summarized — geometry, progress, and guidance stripped)\n// ---------------------------------------------------------------------------\n\nconst summarySchema = z.object({\n travelTimeInSeconds: z.number(),\n lengthInMeters: z.number(),\n trafficDelayInSeconds: z.number().optional(),\n trafficLengthInMeters: z.number().optional(),\n departureTime: z.string().optional(),\n arrivalTime: z.string().optional(),\n noTrafficTravelTimeInSeconds: z.number().optional(),\n historicTrafficTravelTimeInSeconds: z.number().optional(),\n liveTrafficIncidentsTravelTimeInSeconds: z.number().optional(),\n});\n\n/**\n * A single summarized route — just the route summary and optional per-leg summaries.\n */\nexport const summarizedRouteSchema = z.object({\n index: z.number().describe('0=best, 1+=alternative'),\n summary: summarySchema,\n legSummaries: z.array(summarySchema).optional().describe('Present when route has multiple legs'),\n});\n\n/** Summary of one or more calculated routes (coordinates stripped). */\nexport const routesOutputSchema = z.object({\n count: z.number(),\n routes: z.array(summarizedRouteSchema),\n});\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Tool schema for retrieving browser current location.\n */\nexport const getCurrentLocationSchema = z.object({});\n\n/**\n * Output schema for current location tool.\n */\nexport const getCurrentLocationOutputSchema = z.union([\n z.object({\n position: z.array(z.number()).min(2).max(2).describe('[longitude, latitude]'),\n accuracy: z.number().describe('Accuracy of the location in meters'),\n timestamp: z.number().describe('Timestamp of the location data'),\n }),\n toolErrorSchema,\n]);\n\nconst getLocationErrorMessage = (error: unknown): string | undefined => {\n if (typeof error !== 'object' || error === null || !('code' in error)) {\n return undefined;\n }\n\n const code = (error as { code: number }).code;\n\n if (code === 1) {\n return 'Location permission denied by the user';\n }\n\n if (code === 2) {\n return 'Current location is unavailable';\n }\n\n if (code === 3) {\n return 'Timed out while trying to get current location';\n }\n\n return undefined;\n};\n\nexport const getCurrentLocationDescription =\n 'Browser geolocation — the user\\'s physical position [lng, lat] for \"near me\" / \"where I am\" queries (may prompt for permission). ' +\n 'For map-based reference use getViewport.';\n\n/** Execute function for getCurrentLocation — usable with ToolEntry format. */\nexport const executeGetCurrentLocation = async (\n _params: z.infer<typeof getCurrentLocationSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getCurrentLocationOutputSchema>> => {\n if (globalThis.window === undefined || globalThis.navigator === undefined) {\n return { error: 'Current location is only available in a browser environment' };\n }\n\n if (!('geolocation' in globalThis.navigator)) {\n return { error: 'Geolocation is not supported by this browser' };\n }\n\n try {\n const position = await new Promise<GeolocationPosition>((resolve, reject) => {\n globalThis.navigator.geolocation.getCurrentPosition(resolve, reject, {\n enableHighAccuracy: true,\n timeout: 15000,\n maximumAge: 0,\n });\n });\n\n return {\n position: [position.coords.longitude, position.coords.latitude],\n accuracy: position.coords.accuracy,\n timestamp: position.timestamp,\n };\n } catch (error) {\n const locationErrorMessage = getLocationErrorMessage(error);\n if (locationErrorMessage) {\n return { error: locationErrorMessage };\n }\n\n return {\n error: `Failed to get current location: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n getCoordinateAtRouteProgress,\n type RouteCoordinateAtProgress,\n type RouteProgressQuery,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\nconst routeIndexField = z.number().int().min(0).optional().describe('default: 0');\n\n/**\n * Tool schema for getting a coordinate position along a route.\n * The three progress inputs are mutually exclusive — provide exactly one.\n */\nexport const getRouteProgressSchema = z\n .object({\n routeIndex: routeIndexField,\n traveledTimeInSeconds: z.number().min(0).optional().describe('seconds from start'),\n traveledDistanceInMeters: z.number().min(0).optional().describe('meters from start'),\n clockTime: z.string().optional().describe('ISO 8601; elapsed time derived from departure time'),\n })\n .refine(\n ({ traveledTimeInSeconds, traveledDistanceInMeters, clockTime }) =>\n [traveledTimeInSeconds, traveledDistanceInMeters, clockTime].filter((v) => v !== undefined).length === 1,\n { message: 'Provide exactly one of: traveledTimeInSeconds, traveledDistanceInMeters, or clockTime.' },\n );\n\n/**\n * Maps the tool params to a {@link RouteProgressQuery} for the core utility.\n * Returns an error string when the clockTime string is invalid.\n */\nconst toRouteProgressQuery = (\n params: z.infer<typeof getRouteProgressSchema>,\n): RouteProgressQuery | { error: string } => {\n if (params.traveledTimeInSeconds !== undefined) {\n return { traveledTimeInSeconds: params.traveledTimeInSeconds };\n }\n\n if (params.traveledDistanceInMeters !== undefined) {\n return { traveledDistanceInMeters: params.traveledDistanceInMeters };\n }\n\n const clockTimeMs = new Date(params.clockTime as string).getTime();\n if (Number.isNaN(clockTimeMs)) {\n return { error: `Invalid clockTime value: \"${params.clockTime}\". Use ISO 8601 format.` };\n }\n\n return { clockTime: new Date(params.clockTime as string) };\n};\n\nexport const getRouteProgressDescription =\n 'Return the geographic coordinates of the point along the last shown route at a given traveled time (seconds), traveled distance (meters), or absolute clock time (ISO 8601). Use for route progress tracking (e.g. route-point-progress-playground example).';\n\n/** Execute function for getRouteProgress — usable with ToolEntry format. */\nexport const executeGetRouteProgress = async (params: z.infer<typeof getRouteProgressSchema>, state: ToolState) => {\n const routeIndex = params.routeIndex ?? 0;\n\n try {\n // Read from the currently-shown entry's RoutingModule (per-entry now).\n const module = state.routing.currentEntryModule;\n if (!module) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n const lastRoutes = module.getShown().mainLines;\n if (!lastRoutes || lastRoutes.features.length === 0) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n\n const route = lastRoutes.features[routeIndex];\n if (!route) {\n return {\n error: `Route index ${routeIndex} is out of range. There are ${lastRoutes.features.length} route(s) available.`,\n };\n }\n\n if (!route.properties.progress || route.properties.progress.length === 0) {\n return {\n error: 'The route does not contain progress data. Re-calculate the route to ensure progress data is included.',\n };\n }\n\n const query = toRouteProgressQuery(params);\n if ('error' in query) return query;\n\n const result: RouteCoordinateAtProgress | undefined = getCoordinateAtRouteProgress(route, query);\n\n if (result === undefined) {\n return {\n error: 'Could not interpolate a position. The progress data may be incomplete or the requested value is out of range.',\n };\n }\n\n return {\n longitude: result.position[0],\n latitude: result.position[1],\n travelTimeInSeconds: Math.round(result.travelTimeInSeconds),\n distanceInMeters: Math.round(result.distanceInMeters),\n };\n } catch (error) {\n return {\n error: `Failed to get route progress: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { getSectionBBox, type SectionProps, type SectionType, sectionTypes } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-section-bbox tool. */\nexport const getSectionBBoxOutputSchema = z.union([\n z.object({\n bbox: z.array(z.number()).describe('GeoJSON bbox [minLng, minLat, maxLng, maxLat]'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the get-section-bbox tool. */\nexport const getSectionBBoxSchema = z.object({\n sectionType: z.enum(sectionTypes as [SectionType, ...SectionType[]]).describe('e.g. country|traffic|motorway|toll'),\n id: z.string().describe('Section ID from getShownRouteSections or getSectionProgress'),\n});\n\nexport const getSectionBBoxDescription =\n 'Calculate the bounding box [minLng, minLat, maxLng, maxLat] for a specific section of the shown route. Use with flyTo to frame the section, or to query traffic incidents within it.';\n\n/** Execute function for getSectionBBox — usable with ToolEntry format. */\nexport const executeGetSectionBBox = async (\n params: z.infer<typeof getSectionBBoxSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getSectionBBoxOutputSchema>> => {\n const { sectionType, id } = params;\n try {\n const lastRoutes = state.routing.currentRoutes;\n\n if (!lastRoutes || lastRoutes.features.length === 0) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n\n for (const route of lastRoutes.features) {\n const sections = route.properties.sections[sectionType as SectionType];\n if (!sections || !Array.isArray(sections)) continue;\n\n const section = sections.find((s) => s.id === id);\n if (section && typeof section.startPointIndex === 'number' && typeof section.endPointIndex === 'number') {\n const bbox = getSectionBBox(route, section as SectionProps);\n\n if (!bbox) {\n return {\n error: 'Could not calculate bounding box for the section. The section may have invalid indices.',\n };\n }\n\n return { bbox };\n }\n }\n\n return {\n error: `Could not find a section with type \"${sectionType}\" and id \"${id}\" in the last calculated routes.`,\n };\n } catch (error) {\n return {\n error: `Failed to get section bbox: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n getRouteProgressForSection,\n type SectionProps,\n type SectionType,\n sectionTypes,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\n/**\n * Tool schema for getting section progress.\n */\nexport const getSectionProgressSchema = z.object({\n sectionType: z.enum(sectionTypes as [SectionType, ...SectionType[]]).describe('e.g. country|traffic|motorway|toll'),\n ids: z\n .array(z.string())\n .optional()\n .describe('Section IDs to query. If omitted, all sections of the given sectionType are returned.'),\n});\n\nexport const getSectionProgressDescription =\n 'Calculate the distance and travel time for one or more sections of the shown route. If ids is omitted, all sections of the given type are returned.';\n\n/** Execute function for getSectionProgress — usable with ToolEntry format. */\nexport const executeGetSectionProgress = async (params: z.infer<typeof getSectionProgressSchema>, state: ToolState) => {\n const { sectionType, ids } = params;\n\n try {\n const lastRoutes = state.routing.currentRoutes;\n\n if (!lastRoutes || lastRoutes.features.length === 0) {\n return { error: 'No routes available. Use calculate-route first.' };\n }\n\n const results: object[] = [];\n\n for (const route of lastRoutes.features) {\n const sections = route.properties.sections[sectionType as SectionType];\n if (!sections) continue;\n\n for (const section of sections) {\n if (ids && ids.length > 0 && !ids.includes(section.id)) continue;\n\n const progress = getRouteProgressForSection(route, section as SectionProps);\n\n if (!progress) {\n results.push({\n sectionId: section.id,\n error: 'Could not calculate progress for this section. The route may be missing progress data.',\n });\n continue;\n }\n\n results.push({\n sectionId: section.id,\n startPointIndex: section.startPointIndex,\n endPointIndex: section.endPointIndex,\n start: progress.start,\n end: progress.end,\n delta: progress.delta,\n });\n }\n }\n\n if (ids && ids.length > 0) {\n const foundIds = new Set(results.map((r) => (r as { sectionId: string }).sectionId));\n for (const id of ids) {\n if (!foundIds.has(id)) {\n results.push({\n sectionId: id,\n error: `Could not find a section with type \"${sectionType}\" and id \"${id}\" in the last calculated routes.`,\n });\n }\n }\n }\n\n if (results.length === 0) {\n return { error: `No sections of type \"${sectionType}\" found in the last calculated routes.` };\n }\n\n return { sectionType, sections: results };\n } catch (error) {\n return {\n error: `Failed to get section progress: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\n/**\n * All valid tool tags for categorizing tools.\n *\n * @ignore\n */\nexport const TOOL_TAGS = [\n 'location',\n 'route',\n 'waypoint',\n 'place',\n 'locate',\n 'discover',\n 'traffic',\n 'map style',\n 'utilities',\n 'search',\n 'detour',\n 'along-route',\n 'reachable-range',\n 'isochrone',\n 'range',\n 'EV',\n 'coverage',\n 'recall',\n 'state',\n // Route section types (from SDK sectionTypes)\n 'carpool',\n 'carTrain',\n 'country',\n 'ferry',\n 'importantRoadStretch',\n 'lanes',\n 'leg',\n 'lowEmissionZone',\n 'motorway',\n 'pedestrian',\n 'roadShields',\n 'speedLimit',\n 'toll',\n 'tollVignette',\n 'tunnel',\n 'unpaved',\n 'urban',\n 'vehicleRestricted',\n] as const;\n\n/**\n * Union of all valid tool tags, derived from {@link TOOL_TAGS}.\n *\n * @ignore\n */\nexport type ToolTag = (typeof TOOL_TAGS)[number];\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Tool } from 'ai';\nimport { z } from 'zod';\nimport type { ToolMetadata } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { TOOL_TAGS, type ToolTag } from '../tool-tags';\n\nconst detailToolSchema = z.object({\n name: z.string(),\n description: z.string(),\n examplePrompts: z.array(z.string()),\n relatedTools: z.array(z.string()).optional(),\n});\n\nexport const helpOutputSchema = z.union([\n z.object({\n mode: z.literal('summary'),\n examplePrompts: z.array(z.string()),\n }),\n z.object({\n mode: z.literal('detail'),\n query: z.string().optional(),\n tag: z.enum(TOOL_TAGS).optional(),\n matchedTools: z.array(detailToolSchema),\n totalMatched: z.number(),\n }),\n toolErrorSchema,\n]);\n\nexport const helpSchema = z.object({\n mode: z\n .enum(['summary', 'detail'])\n .describe(\n '\"summary\": overview of all capabilities with example prompts mixed from all tools. ' +\n '\"detail\": searchable full tool info — use when you need to find a specific tool.',\n ),\n query: z\n .string()\n .optional()\n .describe('Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags.'),\n tag: z.enum(TOOL_TAGS).optional().describe('Detail mode only. Filter results to tools with a specific tag.'),\n});\n\nexport const helpDescription =\n 'List capabilities. `mode: \"summary\"` returns mixed example prompts; `mode: \"detail\"` returns searchable per-tool descriptions and examples. Call when the user asks what the agent can do, or to find the right tool.';\n\n/**\n * Create the help tool.\n *\n * @param getToolsMetadata - Lazy getter for resolved tool metadata.\n * Called at execution time, not at creation time, so metadata can be\n * populated after all tools are set up.\n */\nexport const createHelpTool = (getToolsMetadata: () => Record<string, ToolMetadata>): Tool =>\n ({\n description: helpDescription,\n inputSchema: helpSchema,\n outputSchema: helpOutputSchema,\n execute: async (params: z.infer<typeof helpSchema>): Promise<z.infer<typeof helpOutputSchema>> => {\n const { mode, query, tag } = params;\n\n try {\n const allTools = Object.values(getToolsMetadata());\n\n if (mode === 'summary') {\n const examplePrompts = allTools.flatMap((toolMetadata) => toolMetadata.examplePrompts ?? []);\n return { mode: 'summary', examplePrompts };\n }\n\n // detail mode\n let tools = allTools;\n\n if (tag) {\n tools = tools.filter(\n (toolMetadata) => (toolMetadata.tags as readonly ToolTag[] | undefined)?.includes(tag) ?? false,\n );\n }\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n tools = tools.filter(\n (toolMetadata) =>\n toolMetadata.name.toLowerCase().includes(lowerQuery) ||\n toolMetadata.description.toLowerCase().includes(lowerQuery) ||\n (toolMetadata.tags ?? []).some((toolTag) => toolTag.toLowerCase().includes(lowerQuery)) ||\n (toolMetadata.examplePrompts ?? []).some((prompt) =>\n prompt.toLowerCase().includes(lowerQuery),\n ),\n );\n }\n\n return {\n mode: 'detail',\n query,\n tag,\n matchedTools: tools.map((toolMetadata) => ({\n name: toolMetadata.name,\n description: toolMetadata.description,\n examplePrompts: toolMetadata.examplePrompts ?? [],\n relatedTools: toolMetadata.relatedTools ?? [],\n })),\n totalMatched: tools.length,\n };\n } catch (error) {\n return {\n error: `Failed to load help: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n },\n }) as Tool;\n","import type { Tool } from 'ai';\nimport { createHelpTool } from './tools/utilities';\nimport type { ToolBuildOptions, ToolDefinition, ToolEntry, ToolMetadata, ToolState } from './types';\n\n/** Converts a ToolEntry to an AI SDK Tool, binding state to execute. */\nconst toAiTool = <S extends ToolState>(entry: ToolEntry<S>, state: S, includeOutputSchema: boolean): Tool =>\n ({\n description: entry.description,\n inputSchema: entry.inputSchema,\n outputSchema: includeOutputSchema ? entry.outputSchema : undefined,\n execute: (input: any) => entry.execute(input, state),\n }) as Tool;\n\n/** Extracts ToolMetadata from a ToolEntry, adding the name. */\nconst toMetadata = <S extends ToolState>(name: string, entry: ToolEntry<S>): ToolMetadata => ({\n name,\n description: entry.description,\n classificationPrompt: entry.classificationPrompt,\n tags: entry.tags,\n examples: entry.examples,\n examplePrompts: entry.examplePrompts,\n relatedTools: entry.relatedTools,\n dependsOn: entry.dependsOn,\n});\n\n// Materializes a builder entry with the given build options, or returns a static entry as-is.\nconst materializeEntry = <S extends ToolState>(\n entry: ToolDefinition<S>,\n buildOptions: ToolBuildOptions,\n): ToolEntry<S> => (typeof entry === 'function' ? entry(buildOptions) : entry);\n\n/**\n * Converts a composed tool record into AI SDK tools and metadata.\n *\n * Each input value may be a {@link ToolEntry} or a {@link ToolEntryBuilder};\n * builders are materialized with {@link ToolBuildOptions} built from\n * `featureFlags` (and any future build-time config) so each tool can decide\n * how its description, schema, or executor are shaped per agent instance.\n *\n * Rebinds the help tool with resolved metadata.\n *\n * @ignore\n */\nexport const setupTools = <S extends ToolState>(\n toolEntries: Record<string, ToolDefinition<S>>,\n state: S,\n options?: { outputSchemas?: boolean; featureFlags?: ToolBuildOptions['featureFlags'] },\n): { tools: Record<string, Tool>; toolsMetadata: Record<string, ToolMetadata> } => {\n const includeOutputSchema = options?.outputSchemas !== false;\n const buildOptions: ToolBuildOptions = { featureFlags: options?.featureFlags };\n const tools: Record<string, Tool> = {};\n const toolsMetadata: Record<string, ToolMetadata> = {};\n\n for (const [name, entryOrBuilder] of Object.entries(toolEntries)) {\n const entry = materializeEntry(entryOrBuilder, buildOptions);\n tools[name] = toAiTool(entry, state, includeOutputSchema);\n toolsMetadata[name] = toMetadata(name, entry);\n }\n\n // Rebind help tool with resolved metadata\n if (tools.help) {\n tools.help = createHelpTool(() => toolsMetadata);\n }\n\n if (Object.keys(tools).length === 0) {\n throw new Error('MapAgent requires at least one tool.');\n }\n\n return { tools, toolsMetadata };\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { formatSandboxExecutionError } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst sourceDiffSchema = z.object({\n added: z.array(z.string()).describe('Source IDs added by the executed code.'),\n removed: z.array(z.string()).describe('Source IDs removed by the executed code.'),\n updated: z.array(z.string()).describe('Source IDs whose definition changed.'),\n});\n\nconst layerDiffSchema = z.object({\n added: z.array(z.string()).describe('Layer IDs added by the executed code.'),\n removed: z.array(z.string()).describe('Layer IDs removed by the executed code.'),\n updated: z.array(z.string()).describe('Layer IDs whose properties changed.'),\n});\n\n/** Input schema for the executeMaplibreCode tool. */\nexport const executeMaplibreCodeSchema = z.object({\n code: z\n .string()\n .describe(\n 'Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. ' +\n 'Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. ' +\n 'Example: await a map event with `await new Promise(r => map.once(\"idle\", r))`. ' +\n 'Return a value with `return` to include it in the result.',\n ),\n});\n\n/** Output schema for the executeMaplibreCode tool. */\nexport const executeMaplibreCodeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n result: z.unknown().optional().describe('Return value of the executed code, if any.'),\n sources: sourceDiffSchema,\n layers: layerDiffSchema,\n }),\n toolErrorSchema,\n]);\n\nexport const executeMaplibreCodeDescription =\n 'Run arbitrary MapLibre JS against the live `map` for anything no other tool covers — custom sources/layers, camera, style mutations, animations, event sequences. ' +\n 'Full JS (loops, helpers, top-level `await`). Returns added/removed/updated sources & layers.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype Snapshot = Map<string, string>;\n\nconst snapshotSources = (mapLibreMap: MapLibreMap): Snapshot => {\n const sources = mapLibreMap.getStyle()?.sources ?? {};\n const snapshot: Snapshot = new Map();\n\n for (const [sourceId, source] of Object.entries(sources)) {\n snapshot.set(sourceId, JSON.stringify(source));\n }\n\n return snapshot;\n};\n\nconst snapshotLayers = (mapLibreMap: MapLibreMap): Snapshot => {\n const layers = mapLibreMap.getStyle()?.layers ?? [];\n const snapshot: Snapshot = new Map();\n\n for (const layer of layers) {\n snapshot.set(layer.id, JSON.stringify(layer));\n }\n\n return snapshot;\n};\n\nconst diffSnapshots = (\n before: Snapshot,\n after: Snapshot,\n): { added: string[]; removed: string[]; updated: string[] } => {\n const added: string[] = [];\n const removed: string[] = [];\n const updated: string[] = [];\n\n for (const [identifier, afterJson] of after) {\n const beforeJson = before.get(identifier);\n\n if (beforeJson === undefined) {\n added.push(identifier);\n } else if (beforeJson !== afterJson) {\n updated.push(identifier);\n }\n }\n\n for (const identifier of before.keys()) {\n if (!after.has(identifier)) {\n removed.push(identifier);\n }\n }\n\n return { added, removed, updated };\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/** Execute function for executeMaplibreCode — usable with ToolEntry format. */\nexport const executeExecuteMaplibreCode = async (\n params: z.infer<typeof executeMaplibreCodeSchema>,\n state: ToolState,\n) => {\n const { code } = params;\n try {\n const mapLibreMap = state.baseMap.mapLibreMap;\n const beforeSources = snapshotSources(mapLibreMap);\n const beforeLayers = snapshotLayers(mapLibreMap);\n\n // eslint-disable-next-line no-new-func\n const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new (\n ...args: string[]\n ) => (map: MapLibreMap) => Promise<unknown>;\n const userFunction = new AsyncFunction('map', code);\n const result = await userFunction(mapLibreMap);\n\n const sources = diffSnapshots(beforeSources, snapshotSources(mapLibreMap));\n const layers = diffSnapshots(beforeLayers, snapshotLayers(mapLibreMap));\n\n return { success: true, result: result ?? undefined, sources, layers };\n } catch (error) {\n return { error: formatSandboxExecutionError('Code', error) };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { geoJsonBBoxSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the fly-to tool. */\nexport const flyToOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for fly-to. */\nexport const flyToSchema = z.object({\n where: z.union([\n z\n .object({\n boundingBox: geoJsonBBoxSchema.describe('BBox [W,S,E,N].'),\n padding: z.number().optional().describe('Padding in pixels. Default: 50.'),\n })\n .describe('Fit the camera to a bounding box.'),\n z\n .object({\n position: z.array(z.number()).length(2).describe('[lng, lat] to fly to.'),\n zoom: z.number().optional().describe('Camera zoom level. Default: 14.'),\n })\n .describe('Fly the camera to a specific position.'),\n ]),\n});\n\nexport const flyToDescription =\n 'Move the map camera to a bounding box or a specific position. ' +\n 'Use with boundingBox to frame a region or set of results. Use with position to center on a point.';\n\n/** Execute function for flyTo — usable with ToolEntry format. */\nexport const executeFlyTo = async (params: z.infer<typeof flyToSchema>, state: ToolState) => {\n const { where } = params;\n try {\n if ('boundingBox' in where) {\n state.baseMap.mapLibreMap.fitBounds(where.boundingBox as [number, number, number, number], {\n padding: where.padding ?? 50,\n });\n } else {\n state.baseMap.mapLibreMap.flyTo({\n center: where.position as [number, number],\n zoom: where.zoom ?? 14,\n });\n }\n return { success: true };\n } catch (error) {\n return {\n error: `flyTo failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-map-style-layers tool. */\nexport const getMapStyleLayersOutputSchema = z.union([\n z.object({\n layers: z.array(\n z.object({\n id: z.string(),\n layout: z.unknown().optional(),\n paint: z.unknown().optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting map style layers.\n */\nexport const getMapStyleLayersSchema = z.object({\n layerIdQuery: z\n .string()\n .optional()\n .describe('Case-insensitive partial match on layer IDs. If omitted, all layers are returned.'),\n include: z\n .array(z.enum(['paint', 'layout']))\n .describe(\n 'Which properties to include in the response. Pass only what you need: \"paint\" for visual properties like colors and opacity, \"layout\" for structural properties like sizes, positions, and visibility.',\n ),\n});\n\nexport const getMapStyleLayersDescription =\n 'Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayoutProperty/setPaintProperty for custom styling.';\n\n/** Execute function for getMapStyleLayers — usable with ToolEntry format. */\nexport const executeGetMapStyleLayers = async (params: z.infer<typeof getMapStyleLayersSchema>, state: ToolState) => {\n const { layerIdQuery, include } = params;\n try {\n const style = state.baseMap.mapLibreMap.getStyle();\n let layers = style.layers ?? [];\n\n // Filter by layerIdQuery if provided (case-insensitive partial match)\n if (layerIdQuery) {\n layers = layers.filter((layer) => layer.id.toLowerCase().includes(layerIdQuery.toLowerCase()));\n if (layers.length === 0) {\n return {\n error: `No layers found matching query: \"${layerIdQuery}\"`,\n };\n }\n }\n\n return {\n layers: layers.map((layer) => ({\n id: layer.id,\n ...(include.includes('paint') && { paint: layer.paint }),\n ...(include.includes('layout') && { layout: layer.layout }),\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get style: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-viewport tool. */\nexport const getViewportOutputSchema = z.union([\n z.object({\n center: z.array(z.number()).describe('[lng, lat]'),\n zoom: z.number().describe('0–22'),\n bbox: z.array(z.number()).describe('[W, S, E, N]'),\n pitch: z.number().describe('degrees, 0=top-down, 85=near-horizontal'),\n bearing: z.number().describe('degrees, 0=north, 90=east'),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting viewport.\n */\nexport const getViewportSchema = z.object({});\n\nexport const getViewportDescription =\n 'Get the current map viewport: center coordinates [longitude, latitude], zoom level, and bounding box [minLng, minLat, maxLng, maxLat]. ' +\n 'Use for queries referencing the map view: \"in this area\", \"what I can see\", \"near the map center\". ' +\n 'Does NOT return the user\\'s physical location — use getCurrentLocation for \"near me\" queries. ' +\n 'Reads map state. Does not call any service.';\n\n/** Execute function for getViewport — usable with ToolEntry format. */\nexport const executeGetViewport = async (_params: z.infer<typeof getViewportSchema>, state: ToolState) => {\n try {\n const mapLibreMap = state.baseMap.mapLibreMap;\n return {\n center: mapLibreMap.getCenter().toArray(),\n zoom: mapLibreMap.getZoom(),\n bbox: state.baseMap.ttMap.getBBox(),\n pitch: mapLibreMap.getPitch(),\n bearing: mapLibreMap.getBearing(),\n };\n } catch (error) {\n return {\n error: `Failed to get viewport: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst changeResultSchema = z.union([\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n value: z.unknown(),\n success: z.literal(true),\n }),\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n error: z.string(),\n }),\n]);\n\n/** Output schema for the setLayoutProperties tool. */\nexport const setLayoutPropertiesOutputSchema = z.union([\n z.object({\n results: z.array(changeResultSchema),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the setLayoutProperties tool. */\nexport const setLayoutPropertiesSchema = z\n .object({\n changes: z.array(\n z.object({\n layerId: z.string().describe('MapLibre layer ID from getMapStyleLayers'),\n propertyName: z.string().describe('e.g. visibility|text-field|icon-size'),\n value: z.any(),\n }),\n ),\n })\n .describe('List of layout property changes to apply in one go.');\n\nexport const setLayoutPropertiesDescription =\n 'Set MapLibre layout properties (visibility, text-field, icon-size) on named layers — use getMapStyleLayers for valid IDs. ' +\n 'For colors/widths use setPaintProperties.';\n\n/** Execute function for setLayoutProperties — usable with ToolEntry format. */\nexport const executeSetLayoutProperties = async (\n params: z.infer<typeof setLayoutPropertiesSchema>,\n state: ToolState,\n) => {\n const { changes } = params;\n try {\n const results = changes.map(({ layerId, propertyName, value }) => {\n try {\n state.baseMap.mapLibreMap.setLayoutProperty(layerId, propertyName, value);\n return { layerId, propertyName, value, success: true as const };\n } catch (error) {\n return {\n layerId,\n propertyName,\n error: `Failed to set layout property: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n\n return { results };\n } catch (error) {\n return {\n error: `Failed to set layout properties: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst changeResultSchema = z.union([\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n value: z.unknown(),\n success: z.literal(true),\n }),\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n error: z.string(),\n }),\n]);\n\n/** Output schema for the setPaintProperties tool. */\nexport const setPaintPropertiesOutputSchema = z.union([\n z.object({\n results: z.array(changeResultSchema),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the setPaintProperties tool. */\nexport const setPaintPropertiesSchema = z.object({\n changes: z\n .array(\n z.object({\n layerId: z.string().describe('MapLibre layer ID from getMapStyleLayers'),\n propertyName: z.string().describe('e.g. fill-color|line-width|text-color'),\n value: z.any(),\n }),\n )\n .describe('List of paint property changes to apply in one go.'),\n});\n\nexport const setPaintPropertiesDescription =\n 'Set MapLibre paint properties (colors, widths, opacity) on named layers — use getMapStyleLayers for valid IDs. ' +\n 'For visibility/structure use setLayoutProperties; for route styling prefer setRouteTheme.';\n\n/** Execute function for setPaintProperties — usable with ToolEntry format. */\nexport const executeSetPaintProperties = async (params: z.infer<typeof setPaintPropertiesSchema>, state: ToolState) => {\n const { changes } = params;\n try {\n const results = changes.map(({ layerId, propertyName, value }) => {\n try {\n state.baseMap.mapLibreMap.setPaintProperty(layerId, propertyName, value);\n return { layerId, propertyName, value, success: true as const };\n } catch (error) {\n return {\n layerId,\n propertyName,\n error: `Failed to set paint property: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n\n return { results };\n } catch (error) {\n return {\n error: `Failed to set paint properties: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the set-pitch-bearing tool. */\nexport const setPitchBearingOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting pitch and bearing.\n */\nexport const setPitchBearingSchema = z.object({\n pitch: z.number().min(0).max(85).optional().describe('0=top-down, 85=near-horizontal'),\n bearing: z.number().min(-180).max(180).optional().describe('0=north, 90=east'),\n});\n\nexport const setPitchBearingDescription =\n 'Tilt and/or rotate the map camera. Pitch 0 = top-down view, 85 = near-horizontal (3D effect). ' +\n 'Bearing 0 = north up, 90 = east up. Provide at least one of pitch or bearing.';\n\n/** Execute function for setPitchBearing — usable with ToolEntry format. */\nexport const executeSetPitchBearing = async (params: z.infer<typeof setPitchBearingSchema>, state: ToolState) => {\n const { pitch, bearing } = params;\n\n if (pitch === undefined && bearing === undefined) {\n return { error: 'Provide at least one of pitch or bearing' };\n }\n\n try {\n const map = state.baseMap.mapLibreMap;\n map.easeTo({\n ...(pitch !== undefined && { pitch }),\n ...(bearing !== undefined && { bearing }),\n });\n\n return { success: true };\n } catch (error) {\n return {\n error: `Failed to set pitch/bearing: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the zoom-in-or-out tool. */\nexport const zoomInOrOutOutputSchema = z.union([\n z.object({\n zoom: z.number().describe('Resulting zoom level after the change'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for zoom-in-or-out. */\nexport const zoomInOrOutSchema = z.object({\n delta: z\n .number()\n .describe(\n 'Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.',\n ),\n});\n\nexport const zoomInOrOutDescription =\n 'Zoom the map in or out by a given amount relative to the current zoom level. ' +\n 'Positive delta zooms in (closer), negative delta zooms out (further away). ' +\n 'Returns the resulting zoom level.';\n\n/** Execute function for zoomInOrOut — usable with ToolEntry format. */\nexport const executeZoomInOrOut = async (params: z.infer<typeof zoomInOrOutSchema>, state: ToolState) => {\n const { delta } = params;\n try {\n const map = state.baseMap.mapLibreMap;\n const targetZoom = map.getZoom() + delta;\n map.zoomTo(targetZoom);\n return { zoom: targetZoom };\n } catch (error) {\n return {\n error: `Zoom failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-classifier\n *\n * Query intent classification for automatic tool selection.\n *\n * Uses LLM-based structured generation to map a user query directly to the\n * minimal set of tool names needed to answer it.\n *\n * The classifier prompt is built dynamically from each tool's `relatedTools`\n * in the tool metadata, so it stays in sync automatically when tools are\n * added or renamed.\n */\n\nimport { generateText, type LanguageModel, type ModelMessage, Output, type TextPart } from 'ai';\nimport { z } from 'zod';\nimport type { Classifier, ClassifierContext, ToolMetadata } from '../types';\nimport type { ClassificationResult, ClassifierOptions } from './types';\n\n/**\n * A single turn in the conversation passed to {@link classifyUserIntent}.\n *\n * @ignore\n */\nexport type ConversationMessage = { role: 'user' | 'assistant'; content: string };\n\n/** Max character length for each history message when compacting for the classifier. */\nconst MAX_HISTORY_MESSAGE_LENGTH = 300;\n\n/** Number of most-recent history messages (excluding the last user message) passed to the classifier. */\nconst MAX_CLASSIFIER_HISTORY_MESSAGES = 8;\n\n/** Formats a single tool's metadata into a compact classifier prompt entry. */\nconst formatToolEntry = (name: string, entry: ToolMetadata): string => {\n const classificationPrompt = entry.classificationPrompt ? ` ${entry.classificationPrompt}.` : '';\n // const related = entry.relatedTools?.length ? ` Related: ${entry.relatedTools.join(', ')}.` : '';\n const depends = entry.dependsOn?.length ? ` Depends on: ${entry.dependsOn.join(', ')}.` : '';\n return `${name}${classificationPrompt}${depends}`;\n};\n\n/**\n * Builds the system prompt listing available tools for the classifier.\n *\n * @ignore\n */\nexport const buildClassifySystemPrompt = (toolsMetadata: Record<string, ToolMetadata>): string => {\n const toolEntries = Object.entries(toolsMetadata).map(([name, entry]) => formatToolEntry(name, entry));\n\n return [\n 'You are a tool selector for a map assistant.',\n 'You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.',\n `LOCATION REFERENCE:\n - \"near me\" / \"my location\" / \"where I am\" → getCurrentLocation (user's physical GPS position, may prompt permission)\n - \"in this area\" / \"here on the map\" / \"near the map center\" → getViewport (map's current view)\n - If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point`,\n '',\n 'Available tools:',\n ...toolEntries,\n ].join('\\n');\n};\n\nconst classifySchema = z.object({\n tools: z\n .array(z.string()) // using string to save tokens, since tool names already included in system prompt\n .min(1)\n .describe('Tool names needed to fulfill the query.'),\n});\n\n/** Calls the LLM with structured output to select tools for the conversation. */\nconst classifyQueryToTools = async (\n conversation: ConversationMessage[],\n model: LanguageModel,\n toolsMetadata: Record<string, ToolMetadata>,\n): Promise<{ tools: string[]; usage: ClassificationResult['usage'] }> => {\n const generated = await generateText({\n model,\n output: Output.object({ schema: classifySchema }),\n system: buildClassifySystemPrompt(toolsMetadata),\n messages: conversation,\n maxOutputTokens: 200,\n });\n\n return {\n tools: generated.output.tools,\n usage: {\n inputTokens: generated.usage?.inputTokens ?? 0,\n outputTokens: generated.usage?.outputTokens ?? 0,\n totalTokens: generated.usage?.totalTokens ?? 0,\n },\n };\n};\n\n/**\n * Classify a user conversation to determine which tools to activate.\n *\n * The `conversation` should contain the last user message plus a compaction\n * of the previous chat history (each truncated to save tokens).\n *\n * @example\n * ```typescript\n * const result = await classifyUserIntent(conversation, {\n * chatModel: openai('gpt-4o-mini'),\n * toolsMetadata,\n * });\n * // result.activeToolNames contains the selected tools\n * ```\n *\n * @group Agent Toolkit\n */\nexport const classifyUserIntent = async (\n conversation: ConversationMessage[],\n options: ClassifierOptions,\n): Promise<ClassificationResult> => {\n const { chatModel, toolsMetadata } = options;\n const startTime = performance.now();\n\n const { tools, usage } = await classifyQueryToTools(conversation, chatModel, toolsMetadata);\n\n const activeToolNames = tools;\n\n const timeMs = performance.now() - startTime;\n\n return {\n activeToolNames,\n timeMs,\n usage,\n };\n};\n\n/** Type predicate that narrows a message content part to a TextPart. */\nconst isTextPart = (part: { type: string }): part is TextPart => part.type === 'text';\n\n/**\n * Extracts plain text from a message's content (string or structured content array).\n *\n * @ignore\n */\nexport const extractMessageText = (content: string | Array<{ type: string }>): string | null => {\n if (typeof content === 'string') return content;\n\n const textParts = content.filter(isTextPart);\n if (textParts.length > 0) return textParts.map((part) => part.text).join('');\n\n return null;\n};\n\n/**\n * Extracts the text content of the last user message in a message array.\n * Returns null if no user message is found or if content has no text.\n *\n * @group Agent Toolkit\n */\nexport const extractLastUserText = (messages: ReadonlyArray<ModelMessage>): string | null => {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n return extractMessageText(messages[i].content);\n }\n }\n return null;\n};\n\n/**\n * Creates the default LLM-based classifier. Uses structured generation to select\n * the minimal tool set for each user message.\n *\n * @param options.model - Language model for classification (ideally fast/cheap like gpt-4o-mini)\n * @param options.maxHistoryMessages - Max prior turns for context. Default: 8\n * @param options.maxHistoryMessageLength - Max chars per history message. Default: 300\n *\n * @group Agent Toolkit\n */\nexport const createDefaultClassifier = (options: {\n model: LanguageModel;\n maxHistoryMessages?: number;\n maxHistoryMessageLength?: number;\n}): Classifier => {\n const { model, maxHistoryMessages, maxHistoryMessageLength } = options;\n\n return async (context: ClassifierContext): Promise<ClassificationResult | null> => {\n const { messages, toolsMetadata } = context;\n if (!messages?.length) return null;\n\n const historyLimit = maxHistoryMessages ?? MAX_CLASSIFIER_HISTORY_MESSAGES;\n const messageCharLimit = maxHistoryMessageLength ?? MAX_HISTORY_MESSAGE_LENGTH;\n\n // Find last user message index and extract text in one pass\n let lastUserIndex = -1;\n let lastUserText: string | null = null;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n lastUserIndex = i;\n lastUserText = extractMessageText(messages[i].content);\n break;\n }\n }\n if (!lastUserText) return null;\n\n // Compact history\n const conversation: ConversationMessage[] = [];\n const historyStartIndex = Math.max(0, lastUserIndex - historyLimit);\n\n for (let i = historyStartIndex; i < lastUserIndex; i++) {\n const message = messages[i];\n if (message.role !== 'user' && message.role !== 'assistant') continue;\n\n const text = extractMessageText(message.content);\n if (!text) continue;\n\n const compacted = text.length > messageCharLimit ? text.slice(0, messageCharLimit) + '…' : text;\n conversation.push({ role: message.role, content: compacted });\n }\n\n conversation.push({ role: 'user', content: lastUserText });\n\n try {\n return await classifyUserIntent(conversation, { chatModel: model, toolsMetadata });\n } catch {\n return null;\n }\n };\n};\n","import type { Place, Places, Routes, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { formatDistance, formatDuration } from '@tomtom-org/maps-sdk/core';\n\n/**\n * Optional search context used to enrich a places history label.\n *\n * @ignore\n */\nexport type PlacesLabelContext = {\n query?: string;\n poiCategories?: string[];\n where?: string;\n /** Route label when results come from an along-route search (e.g. \"Amsterdam to Utrecht\"). */\n routeLabel?: string;\n};\n\nconst placeName = (place: Place): string | undefined => place.properties?.poi?.name;\n\nconst placeAddress = (place: Place): string | undefined => place.properties?.address?.freeformAddress;\n\nconst waypointAddress = (wp: WaypointLike): string | undefined => {\n if (Array.isArray(wp)) return undefined;\n return (wp as any).properties?.address?.freeformAddress;\n};\n\n/** Builds a label for a Places FeatureCollection from its count and search context. */\nconst makeCollectionLabel = (count: number, context?: PlacesLabelContext): string => {\n const parts: string[] = [];\n\n if (context?.query) parts.push(`\"${context.query}\"`);\n if (context?.poiCategories?.length) parts.push(context.poiCategories.join(', '));\n if (context?.routeLabel) parts.push(`along ${context.routeLabel}`);\n else if (context?.where) parts.push(`in ${context.where}`);\n\n const countStr = `${count} ${count === 1 ? 'place' : 'places'}`;\n return parts.length ? `${parts.join(' ')} (${countStr})` : countStr;\n};\n\n/**\n * Generate a human-readable label for a places history entry.\n *\n * For a single Place the label is derived from result properties (name / address),\n * falling back to the query when properties are sparse.\n *\n * For a Places collection (FeatureCollection) the label is built from search\n * context (query, categories, location) plus the result count.\n *\n * @ignore\n */\nexport const makePlacesLabel = (data: Place | Places, context?: PlacesLabelContext): string => {\n if ('features' in data) return makeCollectionLabel(data.features.length, context);\n\n const name = placeName(data);\n const address = placeAddress(data);\n\n if (name && address) return `${name}, ${address}`;\n if (name) return name;\n if (address) return address;\n if (context?.query) return context.query;\n return 'Place';\n};\n\n/**\n * Generate a human-readable label for a routes history entry.\n *\n * @ignore\n */\nexport const makeRoutesLabel = (routes: Routes, waypoints: WaypointLike[]): string => {\n const summary = routes.features[0]?.properties?.summary;\n const duration = summary ? formatDuration(summary.travelTimeInSeconds) : undefined;\n const distance = summary ? formatDistance(summary.lengthInMeters) : undefined;\n const timeDistParts = [duration, distance].filter(Boolean);\n const timeDist = timeDistParts.length ? ` (${timeDistParts.join(', ')})` : '';\n\n const addresses = waypoints.map(waypointAddress).filter(Boolean) as string[];\n\n if (addresses.length >= 2) {\n const origin = addresses[0];\n const destination = addresses.at(-1);\n const vias = addresses.slice(1, -1);\n const path =\n vias.length > 0 ? `${origin} via ${vias.join(', ')} to ${destination}` : `${origin} to ${destination}`;\n return `${path}${timeDist}`;\n }\n\n return `Route${timeDist}`;\n};\n\n/** Budget type → display unit suffix. */\nconst RANGE_BUDGET_UNITS: Record<string, string> = {\n timeMinutes: 'min',\n distanceKM: 'km',\n remainingChargeCPT: '% remaining',\n spentChargePCT: '% spent',\n spentFuelLiters: 'L',\n};\n\n/**\n * Generate a human-readable label for a ranges history entry.\n * e.g. \"10/20/30 min from Amsterdam\" (one origin),\n * \"30 min from Amsterdam, Rotterdam\" (two origins),\n * \"30 min from 3 origins\" (more than two).\n *\n * @ignore\n */\nexport const makeRangesLabel = (budgets: Array<{ type: string; value: number }>, originNames: string[]): string => {\n const sorted = [...budgets].sort((a, b) => a.value - b.value);\n const budgetStr = sorted.map((b) => `${b.value} ${RANGE_BUDGET_UNITS[b.type] ?? b.type}`).join('/');\n const originStr =\n originNames.length === 1\n ? originNames[0]\n : originNames.length === 2\n ? originNames.join(', ')\n : `${originNames.length} origins`;\n return `${budgetStr} from ${originStr}`;\n};\n","/**\n * @module agent-toolkit-utils\n */\n\nimport {\n type CommonPlaceProps,\n getPosition,\n type Place,\n type Places,\n type Route,\n type Routes,\n type Waypoint,\n type WaypointLike,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\n\n/** @ignore */\nexport type PlaceSummary = { id: string; name?: string; address?: string; position: [number, number] };\n\n/** @ignore */\nexport type PlacesSummary = { count: number; features: PlaceSummary[] };\n\n/** Max number of place summaries echoed back to the LLM by summarizePlaces. */\nconst PLACES_SUMMARY_LIMIT = 5;\n\ntype RouteSummary = Route['properties']['summary'];\ntype LegSummary = Route['properties']['sections']['leg'][number]['summary'];\n\n// Maps Date fields to string for LLM-safe serialization (distributive so Date | undefined → string | undefined).\ntype StringDateValue<T> = T extends Date ? string : T;\n\n/** @ignore */\nexport type StringDates<T> = { [K in keyof T]: StringDateValue<T[K]> };\n\n/** @ignore */\nexport type SummarizedRoute = {\n index: number;\n summary: StringDates<RouteSummary>;\n /** Per-leg summaries; only present when the route has more than one leg. */\n legSummaries?: StringDates<LegSummary>[];\n};\n\n/** @ignore */\nexport type SummarizedRoutes = { count: number; routes: SummarizedRoute[] };\n\n/**\n * Converts a Place to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizePlace = (place: Place): PlaceSummary => ({\n id: place.id,\n name: place.properties?.poi?.name,\n address: place.properties?.address?.freeformAddress,\n position: (place.geometry?.coordinates ?? [0, 0]) as [number, number],\n});\n\n/**\n * Converts Places to a token-efficient summary for LLM consumption.\n * Returns the total count plus the first {@link PLACES_SUMMARY_LIMIT} summaries\n * to keep the tool output bounded regardless of result size.\n *\n * @ignore\n */\nexport const summarizePlaces = (places: Places | Place[]): PlacesSummary => {\n const features = Array.isArray(places) ? places : places.features;\n return {\n count: features.length,\n features: features.slice(0, PLACES_SUMMARY_LIMIT).map((feature) => summarizePlace(feature)),\n };\n};\n\n/**\n * Converts a Route to a token-efficient summary for LLM consumption.\n * Returns only the route summary and per-leg summaries (when more than one leg).\n *\n * @ignore\n */\nexport const summarizeRoute = (route: Route): SummarizedRoute => {\n const { summary, sections, index } = route.properties;\n const legs = sections.leg;\n\n const stringifyDates = <T extends { arrivalTime: Date; departureTime: Date }>(summary: T): StringDates<T> =>\n ({\n ...summary,\n arrivalTime: summary.arrivalTime.toISOString(),\n departureTime: summary.departureTime.toISOString(),\n }) as StringDates<T>;\n\n return {\n index,\n summary: stringifyDates(summary),\n ...(legs.length > 1 && { legSummaries: legs.map((leg) => stringifyDates(leg.summary)) }),\n };\n};\n\n/**\n * Converts Routes to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizeRoutes = (routes: Routes): SummarizedRoutes => {\n const count = routes.features.length;\n\n return {\n count,\n routes: routes.features.map(summarizeRoute),\n };\n};\n\n/** @ignore */\nexport const waypointSummarySchema = z.object({\n slotIndex: z.number(),\n isFilled: z.boolean(),\n address: z.string().optional(),\n position: z.array(z.number()).length(2).optional().describe('[longitude, latitude]'),\n});\n\n/**\n * Token-efficient summary of a waypoint for LLM consumption.\n *\n * @ignore\n */\nexport const summarizeWaypoint = (\n waypoint: WaypointLike | null | undefined,\n): z.infer<typeof waypointSummarySchema> | null => {\n if (waypoint === null || waypoint === undefined) {\n return null;\n }\n\n const position = getPosition(waypoint);\n let address: string | undefined;\n if (!Array.isArray(waypoint)) {\n address = (waypoint as Waypoint<CommonPlaceProps>).properties?.address?.freeformAddress;\n }\n\n return {\n slotIndex: -1,\n isFilled: true,\n ...(address && { address }),\n ...(position && { position }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type BBox, getPosition, type Place, type WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { geocode, geocodeOne, search, searchOne } from '@tomtom-org/maps-sdk/services';\nimport * as turf from '@turf/turf';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlace } from '../../utils';\nimport {\n getViewportBias,\n getViewportBoundingBox,\n placesEntryIdHintSchema,\n showEntryGeometries,\n shownSchema,\n showPlaceGeometriesSchema,\n showPlacesSchema,\n showResultsOnMap,\n whereSchema,\n} from '../shared';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport type LocateBias = { position: Position } | { boundingBox: BBox };\n\nexport type QueryAs = 'poi' | 'place';\n\n/**\n * Resolves a query string to up to `limit` Places. POI search when `queryAs === 'poi'`, geocoding\n * otherwise. Optional `bias` narrows the search to a region.\n */\nexport const locatePlaces = async (\n query: string,\n queryAs: QueryAs,\n options: { limit: number; bias?: LocateBias },\n): Promise<Place[]> => {\n const [searchFn, searchOneFn] = queryAs === 'poi' ? [search, searchOne] : [geocode, geocodeOne];\n const { limit, bias } = options;\n\n if (bias && 'position' in bias) {\n const results = await searchFn({ query, position: bias.position, limit });\n return results.features;\n }\n if (bias && 'boundingBox' in bias) {\n const results = await searchFn({ query, boundingBox: bias.boundingBox, limit });\n return results.features;\n }\n if (limit === 1) {\n const result = await searchOneFn(query);\n return result ? [result] : [];\n }\n const results = await searchFn({ query, limit });\n return results.features;\n};\n\n/** Convenience wrapper around {@link locatePlaces} for the single-result case. */\nexport const locatePlace = async (query: string, queryAs: QueryAs, bias?: LocateBias): Promise<Place | null> => {\n const [first] = await locatePlaces(query, queryAs, { limit: 1, bias });\n return first ?? null;\n};\n\nexport const locatePlaceSchema = z.object({\n query: z.string().describe('Location string to resolve for downstream routes or searches.'),\n waypointIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination.'),\n queryAs: z\n .enum(['poi', 'place'])\n .describe(\n '\"poi\" for venues / landmarks / businesses; ' +\n '\"place\" for addresses, cities, neighborhoods, parks, postal codes, geographies.',\n ),\n where: whereSchema.describe('Geographic scope.'),\n show: showPlacesSchema\n .optional()\n .describe('Map display actions after resolving. Skip for intermediate calculations.'),\n entryId: placesEntryIdHintSchema,\n geometry: showPlaceGeometriesSchema,\n});\n\nexport const locatePlaceOutputSchema = z.union([\n placeOutputSchema.extend({\n waypointIndex: z.number().optional(),\n shown: shownSchema.optional(),\n geometryFetched: z\n .boolean()\n .optional()\n .describe('Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set).'),\n geometryShown: z.boolean().optional().describe('Whether the fetched polygon was rendered on the map.'),\n }),\n toolErrorSchema,\n]);\n\nexport const locatePlaceDescription =\n 'Resolve ONE named location (landmark, venue, city, neighborhood, address, POI) to a normalized place summary. ' +\n 'Use this for any SINGLE named place — including requests for its boundary / polygon / outline / footprint (via `geometry`), ' +\n 'pin or zoom display (via `show`), or staging as a routing waypoint. ' +\n 'For multi-place or category searches, use discoverPlaces.';\n\ntype ResolveBiasResult = { bias?: LocateBias } | { error: string };\n\nconst resolveWithinBias = async (\n where: Extract<z.infer<typeof whereSchema>, { mode: 'within' }>,\n state: ToolState,\n): Promise<ResolveBiasResult> => {\n if (where.viewport) return { bias: { boundingBox: getViewportBoundingBox(state.baseMap) } };\n if (where.boundingBox) return { bias: { boundingBox: where.boundingBox as BBox } };\n if (where.query) {\n const viewport = getViewportBias(state.baseMap);\n const place = await locatePlace(\n where.query,\n where.queryAs ?? 'place',\n viewport ? { position: viewport } : undefined,\n );\n if (!place) return { bias: undefined };\n if (place.bbox) return { bias: { boundingBox: place.bbox } };\n const position = getPosition(place);\n return { bias: position ? { position } : undefined };\n }\n if (where.placeId) {\n const lookup = state.places.findPlaceById(where.placeId);\n if (!lookup) {\n return { error: `Unknown placeId \"${where.placeId}\". Use recallPlaces to list available IDs.` };\n }\n if (!lookup.place.properties.dataSources?.geometry?.id) {\n return {\n error: `Place \"${where.placeId}\" has no geometry data source (most addresses and streets lack one).`,\n };\n }\n const feature = await state.places.fetchPlaceGeometry(where.placeId);\n if (!feature) {\n return { error: `Geometry Data service returned no feature for place \"${where.placeId}\".` };\n }\n const bbox = (feature.bbox as BBox | undefined) ?? (turf.bbox(feature) as BBox);\n return { bias: { boundingBox: bbox } };\n }\n return { bias: undefined };\n};\n\nconst resolveNearbyBias = async (\n where: Extract<z.infer<typeof whereSchema>, { mode: 'nearby' }>,\n state: ToolState,\n): Promise<ResolveBiasResult> => {\n if (where.viewport) {\n const position = getViewportBias(state.baseMap);\n return { bias: position ? { position } : undefined };\n }\n if (where.position) return { bias: { position: where.position as Position } };\n if (where.query) {\n const viewport = getViewportBias(state.baseMap);\n const place = await locatePlace(\n where.query,\n where.queryAs ?? 'place',\n viewport ? { position: viewport } : undefined,\n );\n if (!place) return { bias: undefined };\n const position = getPosition(place);\n return { bias: position ? { position } : undefined };\n }\n return { bias: undefined };\n};\n\nconst resolveLocateBias = async (where: z.infer<typeof whereSchema>, state: ToolState): Promise<ResolveBiasResult> => {\n if (where.mode === 'global') return { bias: undefined };\n if (where.mode === 'within') return resolveWithinBias(where, state);\n return resolveNearbyBias(where, state);\n};\n\n// Defer hide + marker rendering to the shared helper; only the zoom step is\n// locate-specific (the place's own bbox / centroid carries more meaning for a\n// single named place than fitting to its point representation).\nconst showLocateResult = async (\n state: ToolState,\n result: Place,\n placesEntryId: string,\n show: z.infer<typeof showPlacesSchema>,\n): Promise<z.infer<typeof shownSchema>> => {\n const shown = await showResultsOnMap(state, [placesEntryId], { ...show, zoomMode: 'none' });\n if (show.zoomMode === 'auto') {\n if (result.bbox) {\n state.baseMap.mapLibreMap.fitBounds(result.bbox);\n } else {\n const pos = getPosition(result);\n if (pos) state.baseMap.mapLibreMap.flyTo({ center: pos as [number, number], zoom: 13 });\n }\n shown.zoomMode = true;\n }\n return shown;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeLocatePlace = async (\n params: z.infer<typeof locatePlaceSchema>,\n state: ToolState,\n): Promise<z.infer<typeof locatePlaceOutputSchema>> => {\n const { query, queryAs, waypointIndex, where, show, entryId, geometry } = params;\n\n try {\n const resolved = await resolveLocateBias(where, state);\n if ('error' in resolved) return resolved;\n const result = await locatePlace(query, queryAs, resolved.bias);\n\n if (!result) {\n return { error: `No result found for \"${query}\"` };\n }\n\n const placesEntryId = state.places.addPlaceResult(result, makePlacesLabel(result, { query }), entryId);\n\n if (waypointIndex !== undefined) {\n state.routing.setWaypointAt(waypointIndex, result as unknown as WaypointLike);\n }\n\n const shown = show ? await showLocateResult(state, result, placesEntryId, show) : undefined;\n\n let geometryFetched: boolean | undefined;\n let geometryShown: boolean | undefined;\n if (geometry) {\n const geometryResult = await showEntryGeometries(state, placesEntryId, geometry);\n geometryFetched = geometryResult.fetched > 0;\n if (geometryResult.shown) geometryShown = true;\n }\n\n return {\n ...summarizePlace(result),\n ...(waypointIndex !== undefined && { waypointIndex }),\n ...(shown && { shown }),\n ...(geometryFetched !== undefined && { geometryFetched }),\n ...(geometryShown !== undefined && { geometryShown }),\n };\n } catch (error) {\n return {\n error: `Location resolution failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { getPosition } from '@tomtom-org/maps-sdk/core';\nimport type { ToolState } from '../../types';\nimport type { LocationInput, ResolvedLocation } from '../shared';\nimport { locatePlace } from './locate-place';\n\n/** @ignore */\nexport const resolveLocationInput = async (\n input: LocationInput,\n state: ToolState,\n): Promise<ResolvedLocation | null> => {\n if ('position' in input) {\n const { lng, lat } = input.position;\n return {\n place: [lng, lat],\n position: [lng, lat],\n name: `[${lng}, ${lat}]`,\n };\n }\n\n if ('placeId' in input) {\n const hit = state.places.findPlaceById(input.placeId);\n if (!hit) return null;\n const position = getPosition(hit.place);\n if (!position) return null;\n return {\n place: hit.place,\n position,\n name: hit.place.properties?.poi?.name ?? hit.place.properties?.address?.freeformAddress ?? input.placeId,\n };\n }\n\n const resolved = await locatePlace(input.query, input.queryAs);\n if (!resolved) return null;\n\n const pos = getPosition(resolved);\n if (!pos) return null;\n\n return {\n place: resolved,\n position: pos,\n name: resolved.properties?.address?.freeformAddress ?? input.query,\n query: input.query,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n type Avoidable,\n avoidableTypes,\n bboxFromGeoJSON,\n type HasBBox,\n type Routes,\n type WaypointLike,\n} from '@tomtom-org/maps-sdk/core';\nimport {\n type CostModel,\n calculateRoute,\n type MaxNumberOfAlternatives,\n routeTypes,\n} from '@tomtom-org/maps-sdk/services';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { RouteParams, ToolState } from '../../types';\nimport { makeRoutesLabel, summarizeRoutes } from '../../utils';\nimport { hidePreviousEntriesSchema, hidePreviousShownEntries, locationInputSchema } from '../shared';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\nexport const costModelSchema = z.object({\n routeType: z.enum(routeTypes).optional().describe('fast|short|efficient|thrilling'),\n traffic: z.enum(['live', 'historical']).optional().describe('live|historical'),\n avoid: z\n .array(z.enum(avoidableTypes as unknown as [Avoidable, ...Avoidable[]]))\n .optional()\n .describe(\n 'tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones',\n ),\n avoidAreas: z\n .array(z.array(z.number()).describe('GeoJSON bbox [minLng, minLat, maxLng, maxLat]'))\n .max(10)\n .optional()\n .describe('Up to 10 bounding boxes to bypass.'),\n});\n\nexport const whenSchema = z.object({\n option: z.enum(['departAt', 'arriveBy']),\n date: z.string().describe('ISO 8601'),\n});\n\nexport const routeParametersSchema = z.object({\n maxAlternatives: z.number().int().min(0).max(5).optional().describe('0–5, default 0'),\n costModel: costModelSchema.optional(),\n when: whenSchema.optional().describe('Omit to depart now.'),\n});\n\n/** Output schema for the set-route tool. */\nexport const setRouteOutputSchema = z.union([\n routesOutputSchema,\n z.object({ success: z.literal(true) }),\n toolErrorSchema,\n]);\n\n/** Input schema for set-route. */\nexport const setRouteSchema = z\n .object({\n locations: z\n .array(locationInputSchema)\n .min(2)\n .optional()\n .describe(\n 'Ordered [origin, ...stops, destination]. Minimum 2. ' +\n 'Provide to set/replace waypoints; omit to keep the current waypoints.',\n ),\n parameters: routeParametersSchema\n .optional()\n .describe('Routing options. Omitted fields keep their current values.'),\n showOnMap: z.boolean().describe('Show the route and waypoints on the map after calculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n })\n .refine((v) => v.locations !== undefined || v.parameters !== undefined, {\n message: 'Provide at least one of `locations` or `parameters`.',\n });\n\nexport const setRouteDescription =\n 'Calculate or recalculate a route. Provide `locations` to set/replace origin/stops/destination, ' +\n '`parameters` to update cost model / alternatives / when, or both in a single call. ' +\n 'At least one of `locations` or `parameters` MUST be provided. ' +\n 'If only `parameters` is given and waypoints already exist, recalculates with the new options; ' +\n 'if no waypoints exist yet, parameters are stored and no recalculation runs. Car/driving only. ' +\n 'Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallRoutes / ' +\n 'addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / getRouteProgress instead.';\n\n/**\n * Filters out null waypoints and returns the array only if there are at least 2 valid entries.\n * Returns null if the condition is not met, preventing a route calculation call.\n */\nexport const resolveRouteWaypoints = (waypoints: (WaypointLike | null)[]): WaypointLike[] | null => {\n const valid = waypoints.filter((w): w is WaypointLike => w !== null);\n return valid.length >= 2 ? valid : null;\n};\n\nexport const buildCalculateRouteParams = (routeParams: RouteParams) => {\n const { maxAlternatives = 0, costModel, when } = routeParams;\n return {\n ...(maxAlternatives > 0 && { maxAlternatives: maxAlternatives as MaxNumberOfAlternatives }),\n ...(costModel && {\n costModel: {\n ...costModel,\n ...(costModel.avoidAreas && {\n avoidAreas: costModel.avoidAreas as unknown as HasBBox[],\n }),\n } as CostModel,\n }),\n ...(when && { when: { option: when.option, date: new Date(when.date) } }),\n };\n};\n\nexport const showRouteOnMap = async (\n state: ToolState,\n entryId: string,\n routes: Routes,\n _waypoints: WaypointLike[],\n hidePreviousEntries?: 'all' | readonly string[],\n): Promise<void> => {\n // RoutingState.showEntry handles route + waypoints rendering on the\n // entry's own RoutingModule. Under entryMode 'multiple' previously-shown\n // entries stay rendered; the optional `hidePreviousEntries` selector lets\n // callers clear them (\"all\") or hide specific ids before the new entry\n // is shown.\n await hidePreviousShownEntries(state.routing, [entryId], hidePreviousEntries);\n await state.routing.showEntry(entryId);\n const bbox = bboxFromGeoJSON(routes);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50 });\n }\n};\n\n/**\n * Calculate a route from validated waypoints, register it in routing state,\n * optionally show it on the map, and return a summary.\n *\n * Shared by setRoute, addWaypointsToRoute, removeWaypointsFromRoute, and replaceWaypointInRoute.\n */\nexport const calculateAndAddRoute = async (\n state: ToolState,\n waypoints: WaypointLike[],\n showOnMap: boolean,\n hidePreviousEntries?: 'all' | readonly string[],\n) => {\n const routes = await calculateRoute({\n locations: waypoints,\n ...buildCalculateRouteParams(state.routing.params),\n });\n\n const entryId = state.routing.addRoutes(routes, waypoints, makeRoutesLabel(routes, waypoints));\n\n if (showOnMap) {\n await showRouteOnMap(state, entryId, routes, waypoints, hidePreviousEntries);\n }\n\n return summarizeRoutes(routes);\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeSetRoute = async (\n params: z.infer<typeof setRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setRouteOutputSchema>> => {\n const { locations, parameters, showOnMap, hidePreviousEntries } = params;\n try {\n if (parameters) state.routing.setParams(parameters);\n\n let waypoints: WaypointLike[] | null;\n if (locations) {\n const resolved = await Promise.all(locations.map((loc) => resolveLocationInput(loc, state)));\n\n const unresolved: string[] = [];\n for (let i = 0; i < locations.length; i++) {\n if (resolved[i] !== null) continue;\n const loc = locations[i];\n if ('query' in loc) unresolved.push(`\"${loc.query}\"`);\n else if ('placeId' in loc) unresolved.push(`placeId \"${loc.placeId}\"`);\n }\n if (unresolved.length > 0) {\n return { error: `Could not resolve: ${unresolved.join(', ')}` };\n }\n\n waypoints = resolveRouteWaypoints(resolved.map((r) => r?.place ?? null));\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n } else {\n waypoints = resolveRouteWaypoints(state.routing.planningSlots);\n if (!waypoints) {\n // No locations supplied and no current waypoints — params stored, nothing to calculate.\n return { success: true };\n }\n }\n\n return calculateAndAddRoute(state, waypoints, showOnMap, hidePreviousEntries);\n } catch (error) {\n return {\n error: `Route calculation failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { withInsertedWaypoints } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { hidePreviousEntriesSchema, locationInputSchema } from '../shared';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\nimport { calculateAndAddRoute, resolveRouteWaypoints } from './set-route';\n\n/** Output schema for the add-waypoints-to-route tool. */\nexport const addWaypointsToRouteOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for adding waypoints to an existing route.\n */\nexport const addWaypointsToRouteSchema = z\n .object({\n origin: locationInputSchema\n .optional()\n .describe(\n 'Prepend a new origin in front of the current origin. The previous origin is kept ' +\n 'as the first intermediate stop (it is not replaced).',\n ),\n destination: locationInputSchema\n .optional()\n .describe(\n 'Append a new destination after the current destination. The previous destination ' +\n 'is kept as an intermediate stop (it is not replaced).',\n ),\n stops: z\n .array(locationInputSchema)\n .min(1)\n .optional()\n .describe(\n 'Intermediate stops to insert at optimal mid-positions along the existing route. ' +\n 'Multiple stops settle into natural along-route order regardless of input order.',\n ),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n })\n .refine((v) => v.origin !== undefined || v.destination !== undefined || (v.stops && v.stops.length > 0), {\n message: 'Provide at least one of `origin`, `destination`, or `stops`.',\n });\n\nexport const addWaypointsToRouteDescription =\n 'Extend the current route — prepend a new origin in front of the existing one, append a new ' +\n 'destination after the existing one, and/or insert intermediate stops at optimal positions along ' +\n 'the existing route geometry. Existing origin/destination are preserved as intermediate stops, ' +\n 'never replaced. Always recalculates. At least one of `origin`, `destination`, or `stops` MUST ' +\n 'be provided. Requires an existing route. To outright replace waypoints, use setRoute instead.';\n\nconst validateExistingRoute = async (state: ToolState) => {\n // Read the currently-shown entry's module — modules are now per-entry, so\n // there's only \"a route on the map\" when an entry has been shown.\n const currentModule = state.routing.currentEntryModule;\n if (!currentModule) {\n return 'No existing route on the map. Use calculate-route or updateRoutesDisplay first.';\n }\n const shownRoutes = currentModule.getShown();\n const shownRouteLines = shownRoutes?.mainLines;\n const shownWaypoints = shownRoutes?.waypoints;\n if (!shownRouteLines || !shownRouteLines.features.length) {\n return 'No existing route available. Use calculate-route first.';\n }\n if (!shownWaypoints || shownWaypoints.features.length < 2) {\n return 'No existing waypoints available. Use calculate-route first to create a route with waypoints.';\n }\n return { shownRouteLines, shownWaypoints };\n};\n\nconst labelForLocation = (location: z.infer<typeof locationInputSchema>): string => {\n if ('query' in location) return `\"${location.query}\"`;\n if ('placeId' in location) return `placeId \"${location.placeId}\"`;\n return JSON.stringify(location.position);\n};\n\ntype ResolvedWaypoints = {\n origin?: Place | [number, number];\n destination?: Place | [number, number];\n stops: (Place | [number, number])[];\n};\n\nconst resolveAllInputs = async (\n params: {\n origin?: z.infer<typeof locationInputSchema>;\n destination?: z.infer<typeof locationInputSchema>;\n stops?: z.infer<typeof locationInputSchema>[];\n },\n state: ToolState,\n): Promise<ResolvedWaypoints | { error: string }> => {\n const { origin, destination, stops = [] } = params;\n const inputs = [\n ...(origin ? [{ kind: 'origin' as const, location: origin }] : []),\n ...(destination ? [{ kind: 'destination' as const, location: destination }] : []),\n ...stops.map((location) => ({ kind: 'stop' as const, location })),\n ];\n\n const resolved = await Promise.all(inputs.map(({ location }) => resolveLocationInput(location, state)));\n\n const unresolved: string[] = [];\n for (let i = 0; i < inputs.length; i++) {\n if (resolved[i] === null) unresolved.push(labelForLocation(inputs[i].location));\n }\n if (unresolved.length > 0) {\n return { error: `Could not resolve: ${unresolved.join(', ')}` };\n }\n\n const result: ResolvedWaypoints = { stops: [] };\n for (let i = 0; i < inputs.length; i++) {\n const place = resolved[i]?.place as Place | [number, number];\n if (inputs[i].kind === 'origin') result.origin = place;\n else if (inputs[i].kind === 'destination') result.destination = place;\n else result.stops.push(place);\n }\n return result;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeAddWaypointsToRoute = async (\n params: z.infer<typeof addWaypointsToRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof addWaypointsToRouteOutputSchema>> => {\n const { origin, destination, stops, showOnMap, hidePreviousEntries } = params;\n try {\n const routeResult = await validateExistingRoute(state);\n if (typeof routeResult === 'string') return { error: routeResult };\n const { shownRouteLines, shownWaypoints } = routeResult;\n\n const resolved = await resolveAllInputs({ origin, destination, stops }, state);\n if ('error' in resolved) return resolved;\n\n const baseWaypoints: WaypointLike[] = [\n ...(resolved.origin ? [resolved.origin] : []),\n ...shownWaypoints.features,\n ...(resolved.destination ? [resolved.destination] : []),\n ];\n\n const updatedWaypoints =\n resolved.stops.length > 0\n ? withInsertedWaypoints(shownRouteLines.features[0], baseWaypoints, resolved.stops)\n : baseWaypoints;\n\n const waypoints = resolveRouteWaypoints(updatedWaypoints);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n return calculateAndAddRoute(state, waypoints, showOnMap, hidePreviousEntries);\n } catch (error) {\n return {\n error: `Failed to add waypoints to route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { getPosition, type HasBBox, type Place, type POICategory } from '@tomtom-org/maps-sdk/core';\nimport { alongRouteSearch, explorationSearch, geometryData, search } from '@tomtom-org/maps-sdk/services';\nimport * as turf from '@turf/turf';\nimport type { Feature, MultiPolygon, Polygon, Position } from 'geojson';\nimport { z } from 'zod';\nimport type { FeatureFlags, ToolEntry, ToolEntryBuilder, ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlaces } from '../../utils';\nimport {\n geoJsonBBoxSchema,\n geometryInputSchema,\n getViewportBias,\n getViewportBoundingBox,\n globalWhereSchema,\n nearbyWhereSchema,\n placesEntryIdHintSchema,\n queryAsSchema,\n resolvePoiCategories,\n showEntryGeometries,\n shownSchema,\n showPlaceGeometriesSchema,\n showPlacesSchema,\n showResultsOnMap,\n withinSharedFields,\n} from '../shared';\nimport { placesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { locatePlace, locatePlaces, type QueryAs } from './locate-place';\n\n// Number of candidate places to consider when resolving `where.query` — a precise polygon often\n// lives a few results down the list (e.g. a street POI may rank above the neighbourhood it sits in).\nconst WITHIN_QUERY_CANDIDATE_LIMIT = 5;\n\n// `where.query` resolution for the within branch — geocodes (or POI-searches) the query, then\n// returns the first candidate that exposes a geometry data source (with its boundary polygon\n// fetched). Falls back to the top result when no candidate has a polygon. Discover-places owns\n// this helper because the polygon is consumed as a precise `geometries` filter on the search;\n// locate-place only needs a coarse bias bbox and skips the extra fetch.\nconst resolveQueryToWithinArea = async (\n query: string,\n queryAs: QueryAs,\n biasPosition?: Position,\n): Promise<{ place: Place; geometry?: Feature<Polygon | MultiPolygon> } | null> => {\n const places = await locatePlaces(query, queryAs, {\n limit: WITHIN_QUERY_CANDIDATE_LIMIT,\n bias: biasPosition ? { position: biasPosition } : undefined,\n });\n if (places.length === 0) return null;\n\n for (const place of places) {\n if (!place.properties.dataSources?.geometry?.id) continue;\n const result = await geometryData({ geometries: [place] });\n const feature = result.features[0];\n if (feature) return { place, geometry: feature as Feature<Polygon | MultiPolygon> };\n }\n\n return { place: places[0] };\n};\n\n/** Output schema for the discover-places tool. */\nexport const discoverPlacesOutputSchema = z.union([\n placesOutputSchema.extend({\n shown: shownSchema.optional(),\n placesEntryId: z.string().optional(),\n label: z\n .string()\n .optional()\n .describe('Human-readable label stored with the entry (e.g. \"cafe\", \"CINEMA (50 places)\").'),\n geometriesFetched: z\n .number()\n .optional()\n .describe('Count of boundary polygons fetched and cached on the entry (when `geometries` was set).'),\n geometriesShown: z.boolean().optional().describe('Whether the fetched polygons were rendered on the map.'),\n }),\n toolErrorSchema,\n]);\n\n// --- schema builders ---\n//\n// The `discoverPlaces` schema differs slightly between the stable default search backend and the\n// experimental exploration-search backend. To keep the two variants in lockstep, every shared\n// piece is defined ONCE below and composed into either variant via the `experimentalSearch` flag.\n// Only the explorationSearch-only fields (`municipalities`, `boundingBoxes`, top-level\n// `placeTypes`) live behind the flag.\n\n// `within` fields shared between both variants. Both consume `withinSharedFields` (`mode` +\n// `viewport`) plus the multi-region fields whose values resolve to geometries the default search\n// backend can also consume (`queries`, `placeIds`, `geometries`, `range`, `route`).\nconst sharedWithinFields = {\n ...withinSharedFields,\n queries: z\n .array(\n z.object({\n query: z\n .string()\n .describe(\n 'Name of a CONTAINING area to search within (e.g. \"Paris\", \"De Jordaan, Amsterdam\"). ' +\n \"NEVER the user's search subject — that goes in the top-level `query`.\",\n ),\n queryAs: queryAsSchema,\n }),\n )\n .optional()\n .describe(\n 'CONTAINING areas to search WITHIN — answers \"search where?\", NOT \"search for what?\". ' +\n \"Never put the user's search subject here. \" +\n 'Example: for \"cafes in Amsterdam\" → top-level `query`/`poiCategories` carry \"cafes\"; ' +\n '`where.queries: [{query: \"Amsterdam\"}]` carries the region. ' +\n 'Each entry resolves to a boundary polygon (or bbox); the union is searched. ' +\n 'Per-item `queryAs` disambiguates POI vs place. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n placeIds: z\n .array(z.string())\n .optional()\n .describe(\n 'IDs of session places — each polygon (fetched on demand) joins the search area. ' +\n 'Use recallPlaces to list IDs. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n geometries: z\n .array(geometryInputSchema)\n .optional()\n .describe(\n 'Direct GeoJSON Polygons / MultiPolygons (custom drawing, external data). ' +\n 'For named or stored places use `queries` / `placeIds`. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n range: z\n .string()\n .optional()\n .describe(\n 'Range ID from findReachableAreas (e.g. \"ranges-0\") — restricts to that entry; ' +\n 'multi-origin entries combine their polygons. Use recallRanges to list IDs. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n route: z\n .object({\n routeId: z\n .string()\n .optional()\n .describe('Route entry ID (e.g. \"routes-0\"). Use recallRoutes. Default: latest route.'),\n widthMeters: z\n .number()\n .positive()\n .describe(\n 'Total corridor width (metres) — widthMeters/2 each side. ' +\n 'Typical: 200–500 m (\"near the road\"), 2–5 km (\"broad area along the route\").',\n ),\n })\n .optional()\n .describe(\n 'Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. ' +\n 'BULK discovery (≤10000); for a SHORT ranked detour list use mode `maxDetour`. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n};\n\n// explorationSearch-only `within` fields. The default search backend has no equivalent for these.\nconst experimentalWithinFields = {\n boundingBoxes: z\n .array(geoJsonBBoxSchema)\n .optional()\n .describe(\n 'Bboxes [W,S,E,N] — results in the union. ' +\n 'Last-resort: prefer `municipalities` / `queries` / `placeIds` / `geometries` when polygons are resolvable. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n municipalities: z\n .array(z.string())\n .optional()\n .describe(\n 'Exact, case-sensitive municipality names (e.g. [\"Amsterdam\", \"Utrecht\"]). ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n};\n\n// `within` mode for discoverPlaces — plural-form fields. Either `viewport` alone, or any\n// combination of the multi-region fields. `viewport` is mutually exclusive with everything else.\nconst buildDiscoverWithinWhereSchema = (flags: FeatureFlags) =>\n z\n .object(flags.experimentalSearch ? { ...sharedWithinFields, ...experimentalWithinFields } : sharedWithinFields)\n .refine(\n (data) => {\n const hasViewport = !!data.viewport;\n const hasMulti =\n !!data.queries?.length ||\n !!data.placeIds?.length ||\n !!data.geometries?.length ||\n !!data.range ||\n !!data.route ||\n // Experimental-only fields — `data` is loosely typed because of the optional\n // intersection. Treat absent fields as empty.\n !!(data as { municipalities?: string[] }).municipalities?.length ||\n !!(data as { boundingBoxes?: unknown[] }).boundingBoxes?.length;\n if (hasViewport && hasMulti) return false;\n return hasViewport || hasMulti;\n },\n {\n message:\n '`within` requires EITHER `viewport: true` OR one or more multi-region fields ' +\n '(queries / placeIds / geometries / range / route' +\n (flags.experimentalSearch ? ' / boundingBoxes / municipalities' : '') +\n '), not both.',\n },\n );\n\n// Route-relative ranked-detour scope. Dispatches to a different SDK endpoint (alongRouteSearch).\nconst maxDetourWhereSchema = z.object({\n mode: z.literal('maxDetour'),\n routeId: z\n .string()\n .optional()\n .describe('Route entry ID (e.g. \"routes-0\"). Use recallRoutes. Default: latest route.'),\n maxDetourTimeSeconds: z\n .number()\n .positive()\n .describe('Max extra travel time (seconds) the user accepts to reach a POI.'),\n sortBy: z\n .enum(['detourTime', 'detourOffset'])\n .optional()\n .describe('\"detourTime\" (default) ranks by extra travel time; \"detourOffset\" by distance from the route line.'),\n limit: z\n .number()\n .max(100)\n .optional()\n .describe('Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.'),\n});\n\nconst buildDiscoverPlacesWhereSchema = (flags: FeatureFlags) => {\n const withinFieldList = flags.experimentalSearch\n ? 'boundingBoxes/queries/placeIds/municipalities/geometries/range/route'\n : 'queries/placeIds/geometries/range/route';\n return z\n .union([buildDiscoverWithinWhereSchema(flags), nearbyWhereSchema, maxDetourWhereSchema, globalWhereSchema])\n .describe(\n 'Geographic scope, by `mode`: ' +\n `\\`within\\` (area — \\`viewport\\` alone, OR any combo of ${withinFieldList}) | ` +\n '`nearby` (point bias — position/viewport/query, optional radiusMeters) | ' +\n '`maxDetour` (ranked detour list off a route) | `global` (no bias). ' +\n 'Default: `{ mode: \"within\", viewport: true }`.',\n );\n};\n\n/**\n * Tool schema for discovering places. Built from {@link FeatureFlags} so the LLM only sees\n * fields the underlying search backend actually supports.\n */\nexport const buildDiscoverPlacesSchema = (flags: FeatureFlags) => {\n const baseShape = {\n query: z\n .string()\n .optional()\n .describe(\n 'Free-text filter (name/address). Omit when searching by category — use `poiCategories` instead.',\n ),\n where: buildDiscoverPlacesWhereSchema(flags).optional(),\n poiCategories: z\n .array(z.string())\n .optional()\n .describe(\n 'CONSTANT_CASE category codes. ALWAYS batch all requested categories in ONE array — never one call per category. ' +\n 'Common: \"RESTAURANT\", \"CAFE\", \"BAR\", \"HOTEL\", \"PARK\", \"PARK_RECREATION_AREA\", ' +\n '\"ELECTRIC_VEHICLE_STATION\", \"GAS_STATION\", \"PARKING_GARAGE\", ' +\n '\"SUPERMARKETS_HYPERMARKETS\", \"PHARMACY\", \"ATM\". ' +\n 'For anything else, call getPoiCategoryCodes first to resolve natural-language terms (e.g. \"italian food\", \"gym\").',\n ),\n show: showPlacesSchema.optional().describe('Display the places and/or geometries on the map.'),\n entryId: placesEntryIdHintSchema,\n geometries: showPlaceGeometriesSchema,\n };\n\n const shape = flags.experimentalSearch\n ? {\n ...baseShape,\n placeTypes: z\n .array(z.enum(['POI', 'PointAddress', 'Street']))\n .optional()\n .describe(\n 'Restrict to record types (default: all). ' +\n 'POI = businesses/landmarks/amenities; PointAddress = numbered addresses; Street = unnumbered streets.',\n ),\n }\n : baseShape;\n\n return z.object(shape).refine((data) => !!data.query || !!data.poiCategories?.length, {\n message:\n 'discoverPlaces needs a search subject: set `query` (free-text) or `poiCategories`. ' +\n 'For a single named place (no filter), use locatePlace — including for its boundary polygon (`geometry`).',\n });\n};\n\nexport const buildDiscoverPlacesDescription = (flags: FeatureFlags): string => {\n const withinFieldList = flags.experimentalSearch\n ? 'boundingBoxes/queries/placeIds/municipalities/geometries/range/route'\n : 'queries/placeIds/geometries/range/route';\n const preferLine = flags.experimentalSearch\n ? 'Prefer `municipalities` / `queries` / `placeIds` (precise) over `boundingBoxes` (coarse). '\n : 'Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ';\n return (\n 'Search for MULTIPLE places in a region — REQUIRES a search subject (`query` and/or `poiCategories`). ' +\n 'NOT for a single named place — use locatePlace for that (including its polygon/boundary via `geometry`). ' +\n 'Stores an entry and returns `placesEntryId`. ' +\n 'CALL ONCE PER REQUEST — batch all `poiCategories` + areas in one call; split only for explicitly comparable result sets. ' +\n `\\`where.mode\\`: \\`within\\` (\\`viewport\\` OR any combo of ${withinFieldList}), ` +\n '`nearby` (position/viewport/query + optional radiusMeters), `maxDetour` (≤100 ranked detours off a route), `global`. ' +\n preferLine +\n 'Route: `within.route` = bulk corridor (≤10000); `maxDetour` = short ranked list (≤100). ' +\n 'Default: `{ mode: \"within\", viewport: true }`. ' +\n 'Never call this with empty `query` AND empty `poiCategories` — that is locatePlace territory.'\n );\n};\n\n// Default-flag (stable, non-experimental) instances — used by the registry's static\n// {@link DEFAULT_TOOLS} and re-exported for any direct consumers.\nconst DEFAULT_FLAGS: FeatureFlags = {};\n\n/**\n * Default-flag (`experimentalSearch: false`) input schema for the discover-places tool.\n * Use {@link buildDiscoverPlacesSchema} to build a flag-aware variant.\n */\nexport const discoverPlacesSchema = buildDiscoverPlacesSchema(DEFAULT_FLAGS);\n\n/** Default-flag (`experimentalSearch: false`) tool description. */\nexport const discoverPlacesDescription = buildDiscoverPlacesDescription(DEFAULT_FLAGS);\n\ntype WhereBias = { boundingBox?: HasBBox; position?: Position };\n\ntype MultiFilters = {\n municipalities?: string[];\n boundingBoxes?: HasBBox[];\n geometries?: (Polygon | MultiPolygon)[];\n // Not a geographic restriction — deliberately excluded from `hasMultiFilters`\n // so passing it alone still triggers the viewport-bounds default.\n placeTypes?: ('POI' | 'PointAddress' | 'Street')[];\n};\n\ntype DiscoverPlacesWhere = z.infer<ReturnType<typeof buildDiscoverPlacesWhereSchema>>;\ntype DiscoverWithinWhere = z.infer<ReturnType<typeof buildDiscoverWithinWhereSchema>>;\ntype NearbyWhere = z.infer<typeof nearbyWhereSchema>;\ntype MaxDetourWhere = z.infer<typeof maxDetourWhereSchema>;\n\n// Resolves a `withinRoute` ref to one corridor polygon per route alternative in the entry.\n// A route entry may carry multiple LineString features (main + alternatives); buffering only the\n// first would silently exclude POIs along alternatives. `widthMeters` is the *total* corridor\n// width — half is passed to turf.buffer as the radius.\nconst resolveWithinRoute = (\n state: ToolState,\n withinRoute: { routeId?: string; widthMeters: number },\n): { polygons: (Polygon | MultiPolygon)[]; routeLabel: string } | { error: string } => {\n const entries = state.routing.entries;\n if (entries.length === 0) {\n return { error: 'No routes in state. Calculate a route first via setRoute.' };\n }\n const entry = withinRoute.routeId ? entries.find((e) => e.id === withinRoute.routeId) : entries.at(-1);\n if (!entry) {\n return {\n error: `No route found with id \"${withinRoute.routeId}\". Use recallRoutes to list available routes.`,\n };\n }\n if (entry.data.features.length === 0) {\n return { error: `Route entry \"${entry.id}\" has no geometry.` };\n }\n const radiusMeters = withinRoute.widthMeters / 2;\n const polygons: (Polygon | MultiPolygon)[] = [];\n for (const routeFeature of entry.data.features) {\n const buffered = turf.buffer(routeFeature, radiusMeters, { units: 'meters' });\n const geometry = buffered?.geometry as Polygon | MultiPolygon | undefined;\n if (geometry) polygons.push(geometry);\n }\n if (polygons.length === 0) {\n return {\n error: `Failed to compute route corridor for \"${entry.id}\" (turf.buffer returned no geometry for any of the ${entry.data.features.length} route alternative(s)).`,\n };\n }\n return { polygons, routeLabel: entry.label };\n};\n\nconst searchByDetour = async (\n state: ToolState,\n detour: MaxDetourWhere,\n query: string | undefined,\n resolvedPoiCategories: POICategory[] | undefined,\n entryId: string | undefined,\n): Promise<{ result: Awaited<ReturnType<typeof alongRouteSearch>>; placesEntryId: string } | { error: string }> => {\n const entries = state.routing.entries;\n if (entries.length === 0) {\n return { error: 'No routes in state. Calculate a route first via setRoute.' };\n }\n const routeEntry = detour.routeId ? entries.find((e) => e.id === detour.routeId) : entries.at(-1);\n if (!routeEntry) {\n return { error: `No route found with id \"${detour.routeId}\". Use recallRoutes to list available routes.` };\n }\n const routeFeature = routeEntry.data.features[0];\n if (!routeFeature) {\n return { error: `Route entry \"${routeEntry.id}\" has no geometry.` };\n }\n\n const result = await alongRouteSearch({\n route: routeFeature,\n maxDetourTimeSeconds: detour.maxDetourTimeSeconds,\n ...(query && { query }),\n ...(resolvedPoiCategories && { poiCategories: resolvedPoiCategories }),\n ...(detour.sortBy && { sortBy: detour.sortBy }),\n ...(detour.limit !== undefined && { limit: detour.limit }),\n });\n const placesEntryId = state.places.addPlaceResult(\n result,\n makePlacesLabel(result, { query, poiCategories: resolvedPoiCategories, routeLabel: routeEntry.label }),\n entryId,\n );\n return { result, placesEntryId };\n};\n\n// Some `MultiFilters` keys (`municipalities`, `boundingBoxes`, `placeTypes`) only exist on\n// the experimental exploration-search backend. Strip them when calling the default `search`.\nconst stripExperimentalFilters = (filters: MultiFilters): { geometries?: (Polygon | MultiPolygon)[] } => {\n const { geometries } = filters;\n return geometries?.length ? { geometries } : {};\n};\n\n// Single dispatch point for the experimental vs stable search backends. Backends differ in:\n// - `limit` cap (10000 for explorationSearch, 100 for search);\n// - which MultiFilters keys are accepted (search only consumes `geometries`).\nconst dispatchPlacesSearch = (\n useExperimental: boolean,\n params: {\n query: string | undefined;\n poiCategories: POICategory[] | undefined;\n bias?: WhereBias;\n radiusMeters?: number;\n multiFilters: MultiFilters;\n },\n): Promise<Awaited<ReturnType<typeof explorationSearch>>> => {\n const { query, poiCategories, bias = {}, radiusMeters, multiFilters } = params;\n const common = {\n query,\n poiCategories,\n ...bias,\n ...(radiusMeters !== undefined && { radiusMeters }),\n };\n return useExperimental\n ? explorationSearch({ ...common, limit: 10000, ...multiFilters })\n : search({ ...common, limit: 100, ...stripExperimentalFilters(multiFilters) });\n};\n\nconst searchInRange = async (\n state: ToolState,\n range: string,\n query: string | undefined,\n resolvedPoiCategories: POICategory[] | undefined,\n multiFilters: MultiFilters,\n entryId: string | undefined,\n routeLabel: string | undefined,\n useExperimental: boolean,\n): Promise<{ result: Awaited<ReturnType<typeof explorationSearch>>; placesEntryId: string } | { error: string }> => {\n const rangesEntry = state.ranges.entries.find((e) => e.id === range);\n if (!rangesEntry || rangesEntry.ranges.length === 0) {\n return { error: `Range \"${range}\" not found. Use recallRanges to list available ranges.` };\n }\n // Combine every range's polygon into the search bias so multi-origin entries\n // search the union of their reachable areas. We keep one geometry per\n // origin's outermost polygon (entries store each origin's full polygon\n // FeatureCollection — feature[0] is the largest budget when multi-budget).\n const rangeGeometries = rangesEntry.ranges\n .map((r) => r.polygon?.features[0]?.geometry)\n .filter((g): g is NonNullable<typeof g> => !!g);\n if (rangeGeometries.length === 0) {\n return { error: `Range \"${range}\" has no polygons. Recompute via findReachableAreas.` };\n }\n const combinedGeometries = [...rangeGeometries, ...(multiFilters.geometries ?? [])];\n const result = await dispatchPlacesSearch(useExperimental, {\n query,\n poiCategories: resolvedPoiCategories,\n multiFilters: { ...multiFilters, geometries: combinedGeometries },\n });\n const placesEntryId = state.places.addPlaceResult(\n result,\n makePlacesLabel(result, {\n query,\n poiCategories: resolvedPoiCategories,\n ...(routeLabel && { routeLabel }),\n }),\n entryId,\n );\n return { result, placesEntryId };\n};\n\n// Resolves a discoverPlaces `within` mode to the search params it should run with. Either the\n// `viewport` bias path, or the multi-region path (any combo of boundingBoxes / queries / placeIds /\n// municipalities / geometries / range / route). The two paths are mutually exclusive at the schema\n// level — see the refinement on `buildDiscoverWithinWhereSchema`.\ntype WithinResolution = {\n bias: WhereBias;\n multiFilters: MultiFilters;\n range?: string;\n routeLabel?: string;\n};\n\n// Resolve `where.queries` entries to polygons (preferred) or bboxes. A failure on any single entry\n// aborts — better to surface a precise error than to silently search a smaller area.\nconst resolveQueriesToAreas = async (\n queries: { query: string; queryAs?: QueryAs }[],\n state: ToolState,\n): Promise<{ geometries: (Polygon | MultiPolygon)[]; boundingBoxes: HasBBox[] } | { error: string }> => {\n const biasPosition = getViewportBias(state.baseMap);\n const geometries: (Polygon | MultiPolygon)[] = [];\n const boundingBoxes: HasBBox[] = [];\n for (const q of queries) {\n const resolved = await resolveQueryToWithinArea(q.query, q.queryAs ?? 'place', biasPosition);\n if (!resolved) {\n return { error: `Could not resolve \"${q.query}\". Try a more specific query, a placeId, or a boundingBox.` };\n }\n if (resolved.geometry?.geometry) {\n geometries.push(resolved.geometry.geometry as Polygon | MultiPolygon);\n } else if (resolved.place.bbox) {\n boundingBoxes.push(resolved.place.bbox);\n } else {\n return {\n error:\n `\"${q.query}\" resolved to a place with no geometry or bbox — cannot use as a \"within\" area. ` +\n 'Use mode \"nearby\" with this query for a point bias instead.',\n };\n }\n }\n return { geometries, boundingBoxes };\n};\n\n// Resolve `where.placeIds` entries to their boundary polygons via the Geometry Data service.\nconst resolvePlaceIdsToGeometries = async (\n placeIds: string[],\n state: ToolState,\n): Promise<{ geometries: (Polygon | MultiPolygon)[] } | { error: string }> => {\n const geometries: (Polygon | MultiPolygon)[] = [];\n for (const placeId of placeIds) {\n const lookup = state.places.findPlaceById(placeId);\n if (!lookup) {\n return { error: `Unknown placeId \"${placeId}\". Use recallPlaces to list available IDs.` };\n }\n if (!lookup.place.properties.dataSources?.geometry?.id) {\n return { error: `Place \"${placeId}\" has no geometry data source (most addresses and streets lack one).` };\n }\n const feature = await state.places.fetchPlaceGeometry(placeId);\n if (!feature) {\n return { error: `Geometry Data service returned no feature for place \"${placeId}\".` };\n }\n geometries.push(feature.geometry as Polygon | MultiPolygon);\n }\n return { geometries };\n};\n\nconst resolveDiscoverWithin = async (\n where: DiscoverWithinWhere,\n state: ToolState,\n placeTypes: ('POI' | 'PointAddress' | 'Street')[] | undefined,\n): Promise<WithinResolution | { error: string }> => {\n const baseFilters: MultiFilters = placeTypes?.length ? { placeTypes } : {};\n\n if (where.viewport) {\n return {\n bias: { boundingBox: getViewportBoundingBox(state.baseMap) },\n multiFilters: baseFilters,\n };\n }\n\n const resolvedGeometries: (Polygon | MultiPolygon)[] = [];\n const resolvedBoundingBoxes: HasBBox[] = [];\n\n if (where.queries?.length) {\n const resolved = await resolveQueriesToAreas(where.queries, state);\n if ('error' in resolved) return resolved;\n resolvedGeometries.push(...resolved.geometries);\n resolvedBoundingBoxes.push(...resolved.boundingBoxes);\n }\n\n if (where.placeIds?.length) {\n const resolved = await resolvePlaceIdsToGeometries(where.placeIds, state);\n if ('error' in resolved) return resolved;\n resolvedGeometries.push(...resolved.geometries);\n }\n\n // `boundingBoxes` and `municipalities` only exist on the experimental schema; treat them\n // as absent on the default schema.\n const experimentalWhere = where as DiscoverWithinWhere & {\n boundingBoxes?: HasBBox[];\n municipalities?: string[];\n };\n\n const mergedBoundingBoxes: HasBBox[] = [\n ...((experimentalWhere.boundingBoxes ?? []) as unknown as HasBBox[]),\n ...resolvedBoundingBoxes,\n ];\n const mergedGeometries: (Polygon | MultiPolygon)[] = [\n ...((where.geometries ?? []) as (Polygon | MultiPolygon)[]),\n ...resolvedGeometries,\n ];\n\n const multiFilters: MultiFilters = {\n ...baseFilters,\n ...(experimentalWhere.municipalities?.length && { municipalities: experimentalWhere.municipalities }),\n ...(mergedBoundingBoxes.length && { boundingBoxes: mergedBoundingBoxes }),\n ...(mergedGeometries.length && { geometries: mergedGeometries }),\n };\n\n let routeLabel: string | undefined;\n if (where.route) {\n const resolved = resolveWithinRoute(state, where.route);\n if ('error' in resolved) return resolved;\n multiFilters.geometries = [...(multiFilters.geometries ?? []), ...resolved.polygons];\n routeLabel = resolved.routeLabel;\n }\n\n return { bias: {}, multiFilters, range: where.range, routeLabel };\n};\n\n// Resolves a `nearby` bias to a position (and optional radius cap).\nconst resolveNearbyBias = async (\n where: NearbyWhere,\n state: ToolState,\n): Promise<{ bias: WhereBias; radiusMeters?: number }> => {\n if (where.viewport) {\n const position = getViewportBias(state.baseMap);\n return { bias: position ? { position } : {}, radiusMeters: where.radiusMeters };\n }\n if (where.position) {\n return { bias: { position: where.position as Position }, radiusMeters: where.radiusMeters };\n }\n if (where.query) {\n const viewport = getViewportBias(state.baseMap);\n const place = await locatePlace(\n where.query,\n where.queryAs ?? 'place',\n viewport ? { position: viewport } : undefined,\n );\n if (place) {\n const pos = getPosition(place);\n if (pos) return { bias: { position: pos }, radiusMeters: where.radiusMeters };\n }\n return { bias: {}, radiusMeters: where.radiusMeters };\n }\n return { bias: {}, radiusMeters: where.radiusMeters };\n};\n\ntype ResolvedDiscoverBias = {\n bias: WhereBias;\n multiFilters: MultiFilters;\n radiusMeters?: number;\n range?: string;\n routeLabel?: string;\n};\n\n// Dispatch the `where` mode to its dedicated resolver. `maxDetour` is handled separately by the\n// caller (different SDK endpoint); `global` falls through with an empty bias.\nconst resolveDiscoverBias = async (\n effectiveWhere: DiscoverPlacesWhere,\n state: ToolState,\n placeTypes: ('POI' | 'PointAddress' | 'Street')[] | undefined,\n baseMultiFilters: MultiFilters,\n): Promise<ResolvedDiscoverBias | { error: string }> => {\n if (effectiveWhere.mode === 'nearby') {\n const nearby = await resolveNearbyBias(effectiveWhere, state);\n return { bias: nearby.bias, multiFilters: baseMultiFilters, radiusMeters: nearby.radiusMeters };\n }\n if (effectiveWhere.mode === 'within') {\n const resolved = await resolveDiscoverWithin(effectiveWhere, state, placeTypes);\n if ('error' in resolved) return resolved;\n return {\n bias: resolved.bias,\n multiFilters: resolved.multiFilters,\n range: resolved.range,\n routeLabel: resolved.routeLabel,\n };\n }\n // mode === 'global'\n return { bias: {}, multiFilters: baseMultiFilters };\n};\n\n// Geocoded place names produce a human-readable label suffix on the entry.\nconst resolveWhereLabel = (where: DiscoverPlacesWhere | undefined): string | undefined => {\n if (!where) return undefined;\n if (where.mode === 'within' && where.queries?.length) return where.queries.map((q) => q.query).join(', ');\n if (where.mode === 'nearby' && where.query) return where.query;\n return undefined;\n};\n\nconst searchWithBias = async (\n state: ToolState,\n query: string | undefined,\n bias: WhereBias,\n resolvedPoiCategories: POICategory[] | undefined,\n radiusMeters: number | undefined,\n multiFilters: MultiFilters,\n entryId: string | undefined,\n where: DiscoverPlacesWhere | undefined,\n routeLabel: string | undefined,\n useExperimental: boolean,\n): Promise<{ result: Awaited<ReturnType<typeof explorationSearch>>; placesEntryId: string }> => {\n const result = await dispatchPlacesSearch(useExperimental, {\n query,\n poiCategories: resolvedPoiCategories,\n bias,\n radiusMeters,\n multiFilters,\n });\n const placesEntryId = state.places.addPlaceResult(\n result,\n makePlacesLabel(result, {\n query,\n poiCategories: resolvedPoiCategories,\n where: resolveWhereLabel(where),\n ...(routeLabel && { routeLabel }),\n }),\n entryId,\n );\n return { result, placesEntryId };\n};\n\n// Render-on-map + boundary-geometry post-processing shared by every dispatch branch.\nconst finalizeDiscoverResult = async (\n state: ToolState,\n result: Awaited<ReturnType<typeof explorationSearch>>,\n placesEntryId: string,\n show: z.infer<ReturnType<typeof buildDiscoverPlacesSchema>>['show'],\n geometries: z.infer<ReturnType<typeof buildDiscoverPlacesSchema>>['geometries'],\n) => {\n const shown = show ? await showResultsOnMap(state, [placesEntryId], show) : undefined;\n\n let geometriesFetched: number | undefined;\n let geometriesShown: boolean | undefined;\n if (geometries) {\n const geometryResult = await showEntryGeometries(state, placesEntryId, geometries);\n geometriesFetched = geometryResult.fetched;\n if (geometryResult.shown) geometriesShown = true;\n }\n\n const label = state.places.entries.find((entry) => entry.id === placesEntryId)?.label;\n\n return {\n ...summarizePlaces(result),\n placesEntryId,\n ...(label && { label }),\n ...(shown && { shown }),\n ...(geometriesFetched !== undefined && { geometriesFetched }),\n ...(geometriesShown !== undefined && { geometriesShown }),\n };\n};\n\n/**\n * Build the discoverPlaces executor for a given {@link FeatureFlags} bag. When\n * `experimentalSearch` is on, the underlying search calls dispatch to {@link explorationSearch}\n * (the unstable backend with a richer input surface). Otherwise they go through the stable\n * {@link search} router.\n */\nexport const buildExecuteDiscoverPlaces = (flags: FeatureFlags) => {\n const useExperimental = flags.experimentalSearch === true;\n return async (\n params: z.infer<ReturnType<typeof buildDiscoverPlacesSchema>>,\n state: ToolState,\n ): Promise<z.infer<typeof discoverPlacesOutputSchema>> => {\n const { query, where, poiCategories, show, entryId, geometries } = params;\n // `placeTypes` only exists on the experimental schema.\n const placeTypes = (params as { placeTypes?: ('POI' | 'PointAddress' | 'Street')[] }).placeTypes;\n\n // Resolve once upstream — exact catalog codes pass through, natural-language terms are\n // synonym-resolved via getPoiCategoryCodes. We only error when the LLM passed inputs and\n // NONE matched anything, since a no-filter search would silently widen the result set.\n // Note: this trades a hard error for confusable inputs (e.g. \"PARKING_LOT\" → parking codes\n // when the user meant parks); we accept that and rely on the description hints to steer\n // the LLM toward the right codes up-front.\n const { resolved: resolvedPoiCategories, unresolved } = await resolvePoiCategories(poiCategories);\n if (poiCategories?.length && !resolvedPoiCategories) {\n return {\n error:\n `No POI categories matched: ${unresolved.map((c) => `\"${c}\"`).join(', ')}. ` +\n 'POI category codes must be exact CONSTANT_CASE values, or natural-language terms resolvable by the SDK catalog. ' +\n 'Call getPoiCategoryCodes to find valid codes for your search subject (e.g. \"italian food\", \"gym\").',\n };\n }\n\n // Default to viewport-bounded when no `where` was supplied.\n const effectiveWhere: DiscoverPlacesWhere = where ?? { mode: 'within', viewport: true };\n const baseMultiFilters: MultiFilters = placeTypes?.length ? { placeTypes } : {};\n\n try {\n // maxDetour dispatches to a different SDK endpoint and shares no params with the rest.\n if (effectiveWhere.mode === 'maxDetour') {\n const detourResult = await searchByDetour(state, effectiveWhere, query, resolvedPoiCategories, entryId);\n if ('error' in detourResult) return detourResult;\n return finalizeDiscoverResult(state, detourResult.result, detourResult.placesEntryId, show, geometries);\n }\n\n const resolved = await resolveDiscoverBias(effectiveWhere, state, placeTypes, baseMultiFilters);\n if ('error' in resolved) return resolved;\n const { bias, multiFilters, radiusMeters, range, routeLabel } = resolved;\n\n if (range) {\n const rangeResult = await searchInRange(\n state,\n range,\n query,\n resolvedPoiCategories,\n multiFilters,\n entryId,\n routeLabel,\n useExperimental,\n );\n if ('error' in rangeResult) return rangeResult;\n return finalizeDiscoverResult(state, rangeResult.result, rangeResult.placesEntryId, show, geometries);\n }\n\n const biasResult = await searchWithBias(\n state,\n query,\n bias,\n resolvedPoiCategories,\n radiusMeters,\n multiFilters,\n entryId,\n effectiveWhere,\n routeLabel,\n useExperimental,\n );\n return finalizeDiscoverResult(state, biasResult.result, biasResult.placesEntryId, show, geometries);\n } catch (error) {\n return { error: `Search failed: ${error instanceof Error ? error.message : String(error)}` };\n }\n };\n};\n\n/** Default-flag (`experimentalSearch: false`) executor for the discover-places tool. */\nexport const executeDiscoverPlaces = buildExecuteDiscoverPlaces(DEFAULT_FLAGS);\n\n/**\n * Build a complete {@link ToolEntry} for `discoverPlaces` for the given {@link FeatureFlags}.\n * Description, schema, and executor are all picked from the same flag set so they always agree.\n *\n * Anything other than schema/description/execute (tags, examples, examplePrompts, related/depends)\n * is supplied by the caller via `metadata` so the registry retains a single source of truth.\n */\nexport const buildDiscoverPlacesEntry = <S extends ToolState = ToolState>(\n flags: FeatureFlags,\n metadata: Omit<ToolEntry<S>, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>,\n): ToolEntry<S> => ({\n ...metadata,\n description: buildDiscoverPlacesDescription(flags),\n inputSchema: buildDiscoverPlacesSchema(flags),\n outputSchema: discoverPlacesOutputSchema,\n execute: buildExecuteDiscoverPlaces(flags) as ToolEntry<S>['execute'],\n});\n\n// Flag-agnostic metadata for the discover-places tool. Description, schema, and executor are\n// flag-aware and produced by the builder below.\nconst discoverPlacesMetadata = {\n classificationPrompt:\n 'Search multiple places by query/category in a region. BATCH categories+areas in one call. ' +\n '`where.mode`: within (area) / nearby (point bias) / maxDetour (route-relative ranked) / global. ' +\n 'For \"X in [named area]\", use `where.query` directly — it geocodes and prefers polygon geometry over bbox in one step, no precursor locatePlace needed. ' +\n 'Within-mode: route → corridor. Prefer geometries/municipalities/placeId over bbox. ' +\n 'Skip for already-stored places — use recallPlaces/processPlaces.',\n tags: ['discover', 'place', 'location'],\n examples: [\n 'discoverPlaces({ poiCategories: [\"RESTAURANT\", \"CAFE\", \"BAR\"], entryId: \"amsterdam-food-spots\" }) // batch many categories + semantic id',\n 'discoverPlaces({ query: \"coffee\", where: { mode: \"nearby\", position: [4.9, 52.4], radiusMeters: 500 } })',\n 'discoverPlaces({ query: \"parking\", where: { mode: \"within\", query: \"De Jordaan, Amsterdam\", queryAs: \"place\" } }) // resolves to neighbourhood polygon',\n 'discoverPlaces({ query: \"coffee\", where: { mode: \"nearby\", query: \"Schiphol Airport\", queryAs: \"poi\", radiusMeters: 2000 } }) // POI-resolved bias point',\n 'discoverPlaces({ poiCategories: [\"HOTEL\"], where: { mode: \"within\", municipalities: [\"Amsterdam\", \"Utrecht\"] }, entryId: \"ams-utrecht-hotels\" })',\n 'discoverPlaces({ query: \"bakery\", where: { mode: \"within\", range: \"ranges-0\" } })',\n 'discoverPlaces({ poiCategories: [\"CAFE\"], show: { markerType: \"pin\", zoomMode: \"auto\" } }) // omit `where` → defaults to viewport',\n 'discoverPlaces({ query: \"Amsterdam neighbourhood\", placeTypes: [\"Geography\"], geometries: { show: true, theme: \"outline\" } }) // outline boundaries',\n 'discoverPlaces({ poiCategories: [\"ELECTRIC_VEHICLE_STATION\"], where: { mode: \"within\", route: { routeId: \"routes-0\", widthMeters: 1000 } } }) // bulk corridor scan along the route',\n 'discoverPlaces({ query: \"coffee\", where: { mode: \"maxDetour\", maxDetourTimeSeconds: 300, limit: 5 } }) // top 5 coffee detours within 5 min off the latest route',\n 'discoverPlaces({ poiCategories: [\"ELECTRIC_VEHICLE_STATION\"], where: { mode: \"maxDetour\", routeId: \"routes-1\", maxDetourTimeSeconds: 600, sortBy: \"detourOffset\" } }) // ranked EV detours on a specific route',\n 'discoverPlaces({ query: \"bakery\", where: { mode: \"within\", placeId: \"G55fc4abe-...\" } }) // search inside a state place\\'s polygon (auto-fetched if needed)',\n 'discoverPlaces({ query: \"vegan\", where: { mode: \"global\" } })',\n ],\n examplePrompts: [\n 'Find coffee shops near me',\n 'Italian restaurants in Amsterdam',\n 'EV chargers in the current map area',\n 'Hotels in Amsterdam and Utrecht',\n 'Bakeries within the reachable range',\n 'Outline the Amsterdam neighbourhoods',\n 'Show the boundaries of these municipalities',\n 'Find all EV stations within 1 km of my route',\n 'Cafes in a 500 m corridor around the planned route',\n 'Best 5 coffee stops without losing more than 5 min off my route',\n 'EV chargers I can reach with under 10 min detour',\n ],\n relatedTools: [\n 'getPOICategoryCodes',\n 'updatePlacesDisplay',\n 'updatePlacesDisplay',\n 'locatePlace',\n 'getViewport',\n 'findReachableAreas',\n 'processPlaces',\n 'analysePlaces',\n ],\n dependsOn: ['getPOICategoryCodes', 'setRoute'],\n} satisfies Omit<ToolEntry, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>;\n\n/**\n * Builder for the `discoverPlaces` default tool entry. Reads {@link FeatureFlags}\n * from the build options to pick the matching description, schema, and executor;\n * static metadata (tags, examples, related/depends) is colocated here as the\n * single source of truth.\n */\nexport const discoverPlacesBuilder: ToolEntryBuilder = (options) =>\n buildDiscoverPlacesEntry(options.featureFlags ?? {}, discoverPlacesMetadata);\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport type { GeometryTheme } from '@tomtom-org/maps-sdk/map';\nimport type { BudgetType, ReachableRangeParams } from '@tomtom-org/maps-sdk/services';\nimport { calculateReachableRanges } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ReachableRange, ToolState } from '../../types';\nimport { makeRangesLabel } from '../../utils';\nimport { hidePreviousEntriesSchema, hidePreviousShownEntries, locationInputSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\nexport const budgetSchema = z.object({\n type: z\n .enum(['timeMinutes', 'distanceKM']) // TODO: 'remainingChargeCPT', 'spentChargePCT', 'spentFuelLiters' these need vehicle data\n .describe('Budget type including its unit.'),\n value: z.number().describe('Budget value. e.g. 30 for 30 minutes, 50 for 50 km.'),\n});\n\nexport const findReachableAreasSchema = z.object({\n origins: z\n .array(locationInputSchema)\n .min(1)\n .describe(\n 'One or more starting points; one reachable area is computed per origin under the same budgets and bundled into a single ranges entry. Pass a single-element array for the classic one-origin call.',\n ),\n budgets: z\n .array(budgetSchema)\n .min(1)\n .describe(\n 'One or more budget constraints applied to every origin. Pass multiple for nested ranges (e.g. 10, 20, 30 min or km). Order does not matter — sorted internally.',\n ),\n theme: z\n .enum(['filled', 'inverted', 'outline'])\n .optional()\n .describe(\n 'Visual theme for the polygons. Default: outline (transparent fill with thick colored border). Use inverted for a donut (reachable highlighted, outside dimmed) or filled for solid polygons.',\n ),\n showOnMap: z.boolean().describe('Whether to render the range polygon(s) on the map.'),\n showOriginPin: z.boolean().optional().describe('Whether to drop a pin at each origin. Default: true.'),\n hidePreviousEntries: hidePreviousEntriesSchema('ranges'),\n});\n\nconst rangeOutputSchema = z.object({\n rangesId: z.string(),\n ranges: z.array(\n z.object({\n originName: z.string(),\n budgets: z.array(budgetSchema),\n }),\n ),\n skipped: z\n .array(z.object({ origin: z.string(), reason: z.string() }))\n .optional()\n .describe('Origins that could not contribute (unresolved query, empty geometry).'),\n});\n\nexport const findReachableAreasOutputSchema = z.union([\n rangeOutputSchema,\n z.object({ status: z.enum(['no_results']) }),\n toolErrorSchema,\n]);\n\nexport const findReachableAreasDescription =\n 'Compute reachable-area polygons (isochrone/isodistance) from one or more origins under shared time/distance ' +\n 'budgets. Multiple origins → compared in one call (sequential to respect QPS); multiple budgets → nested ' +\n 'ranges. Stored in state (recallRanges); `showOnMap: true` renders. Per-origin failures land under ' +\n '`skipped` instead of aborting the whole call.';\n\nconst computeReachableRangesForOrigin = async (\n budgets: z.infer<typeof budgetSchema>[],\n origin: Position,\n): Promise<PolygonFeatures | null> => {\n const paramsArray: ReachableRangeParams[] = budgets.map((b) => ({\n origin,\n budget: { type: b.type as BudgetType, value: b.value },\n }));\n const result = await calculateReachableRanges(paramsArray);\n return result.features.length === 0 ? null : result;\n};\n\n// Combine every range polygon into a single FeatureCollection so the geometries\n// module can render them in one show() call and a single fitBounds covers them\n// all. Per-origin features keep their original `properties` from the service.\nconst mergeRangePolygons = (ranges: ReachableRange[]): PolygonFeatures | null => {\n const features = ranges.flatMap((r) => r.polygon?.features ?? []);\n if (features.length === 0) return null;\n return {\n type: 'FeatureCollection',\n features,\n } as PolygonFeatures;\n};\n\nconst formatOriginName = (range: ReachableRange): string =>\n range.origin.query ?? `${range.origin.position[1]}, ${range.origin.position[0]}`;\n\nconst showOriginsAndPolygons = async (\n state: ToolState,\n rangesId: string,\n ranges: ReachableRange[],\n theme: GeometryTheme,\n showOnMap: boolean,\n showOriginPin: boolean,\n) => {\n if (showOnMap) {\n const merged = mergeRangePolygons(ranges);\n if (merged) {\n // Per-entry GeometriesModule — each ranges entry owns its own\n // module so multiple entries can be on the map simultaneously.\n const geomModule = await state.ranges.getEntryGeometriesModule(rangesId, theme);\n await geomModule.show(merged);\n if (merged.bbox) {\n state.baseMap.mapLibreMap.fitBounds(merged.bbox, { padding: 50 });\n }\n }\n }\n if (showOriginPin) {\n const placesModule = await state.ranges.getEntryPlacesModule(rangesId);\n const originPlaces: Place[] = ranges.map((r, i) => ({\n type: 'Feature',\n id: `range-origin-${rangesId}-${i}`,\n geometry: { type: 'Point', coordinates: r.origin.position },\n properties: {\n type: 'Point Address',\n address: { freeformAddress: formatOriginName(r) },\n },\n }));\n await placesModule.show(originPlaces);\n }\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeFindReachableAreas = async (\n params: z.infer<typeof findReachableAreasSchema>,\n state: ToolState,\n): Promise<z.infer<typeof findReachableAreasOutputSchema>> => {\n const { origins, budgets, theme, showOnMap, showOriginPin = true, hidePreviousEntries } = params;\n\n try {\n const sortedBudgets = [...budgets].sort((a, b) => b.value - a.value);\n\n // Sequential — fan-out would race the service's per-key QPS limit when\n // callers pass many origins. The cost is latency, not correctness.\n const ranges: ReachableRange[] = [];\n const skipped: { origin: string; reason: string }[] = [];\n for (const origin of origins) {\n const fallbackLabel =\n 'query' in origin\n ? origin.query\n : 'position' in origin\n ? `${origin.position.lat}, ${origin.position.lng}`\n : 'placeId' in origin\n ? origin.placeId\n : 'unnamed origin';\n const resolved = await resolveLocationInput(origin, state);\n if (!resolved) {\n skipped.push({ origin: fallbackLabel, reason: 'origin not found' });\n continue;\n }\n const polygon = await computeReachableRangesForOrigin(sortedBudgets, resolved.position);\n if (!polygon) {\n skipped.push({ origin: resolved.name ?? fallbackLabel, reason: 'no reachable area returned' });\n continue;\n }\n ranges.push({\n origin: resolved.query\n ? { query: resolved.query, position: resolved.position }\n : { position: resolved.position },\n budgets: sortedBudgets,\n polygon,\n });\n }\n\n if (ranges.length === 0) return { status: 'no_results' };\n\n const resolvedTheme = theme ?? 'outline';\n const originNames = ranges.map(formatOriginName);\n const rangesId = state.ranges.addEntry({\n label: makeRangesLabel(sortedBudgets, originNames),\n ranges,\n });\n\n if (showOnMap || showOriginPin) {\n await hidePreviousShownEntries(state.ranges, [rangesId], hidePreviousEntries);\n }\n\n await showOriginsAndPolygons(state, rangesId, ranges, resolvedTheme, showOnMap, showOriginPin);\n\n // Track which entry is rendered so state-digest snapshots can surface it later.\n if (showOnMap || showOriginPin) await state.ranges.markEntryShown(rangesId);\n\n // All ranges in this entry share the same `sortedBudgets`; reusing\n // it keeps the response type aligned with the input schema's narrower\n // budget enum (`ReachableRangeBudget` widens it with EV/fuel types).\n return {\n rangesId,\n ranges: ranges.map((r) => ({\n originName: formatOriginName(r),\n budgets: sortedBudgets,\n })),\n ...(skipped.length && { skipped }),\n };\n } catch (error) {\n return {\n error: `Reachable area calculation failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Language } from '@tomtom-org/maps-sdk/core';\nimport { getPOICategoryCodes } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-poi-category-codes tool. */\nexport const getPoiCategoryCodesOutputSchema = z.union([\n z.object({\n count: z.number().describe('Number of codes returned'),\n codes: z\n .array(z.string())\n .describe(\n 'POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. \"RESTAURANT\") also accepted by togglePOIs categories.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool input schema for fetching POI category codes.\n */\nexport const getPoiCategoryCodesSchema = z.object({\n filters: z\n .array(z.string())\n .optional()\n .describe(\n 'Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes.',\n ),\n language: z.string().describe('Locale e.g. \"en-GB\", \"fr-FR\", \"es-ES\", \"de-DE\".'),\n});\n\nexport const getPoiCategoryCodesDescription =\n 'Resolve natural language (e.g. \"gym\", \"italian food\", \"bookstore\") into POICategory codes for use in the discoverPlaces poiCategories parameter. Returns all codes when no filter is given.';\n\n/**\n * Create the get-poi-category-codes tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetPoiCategoryCodes = async (\n params: z.infer<typeof getPoiCategoryCodesSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getPoiCategoryCodesOutputSchema>> => {\n const { filters, language } = params;\n try {\n const codes = await getPOICategoryCodes({\n filters,\n language: language as Language,\n });\n return {\n count: codes.length,\n codes,\n };\n } catch (error) {\n return {\n error: `Failed to fetch POI category codes: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type Place, type TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport type { TrafficAreaAnalyticsParams } from '@tomtom-org/maps-sdk/services';\nimport { geometryData, trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';\nimport type { MultiPolygon, Polygon } from 'geojson';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { locationInputSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\n// ---------------------------------------------------------------------------\n// Constants (inline — the SDK only type-exports these)\n// ---------------------------------------------------------------------------\n\nconst METRICS = ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'] as const;\n\nconst FUNCTIONAL_ROAD_CLASSES = [\n 'MOTORWAY',\n 'MAJOR_ROAD',\n 'OTHER_MAJOR_ROAD',\n 'SECONDARY_ROAD',\n 'LOCAL_CONNECTING_ROAD',\n 'LOCAL_ROAD_HIGH_IMPORTANCE',\n 'LOCAL_ROAD',\n 'LOCAL_ROAD_MINOR_IMPORTANCE',\n 'OTHER_ROAD',\n] as const;\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nexport const metricsSchema = z.object({\n speed: z.number().optional(),\n freeFlowSpeed: z.number().optional(),\n congestionLevel: z.number().optional(),\n travelTime: z.number().optional(),\n networkLength: z.number().optional(),\n});\n\n/** Output schema for the get-traffic-area-analytics tool. */\nexport const getTrafficAreaAnalyticsOutputSchema = z.union([\n z.object({\n name: z.string().optional(),\n timezone: z.string().optional(),\n dateRange: z.object({ start: z.string(), end: z.string() }),\n baseData: metricsSchema,\n metrics: z.array(z.string()),\n tileCount: z.number(),\n availableGranularities: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fetching traffic area analytics.\n */\nexport const getTrafficAreaAnalyticsSchema = z.object({\n location: locationInputSchema\n .optional()\n .describe(\n 'Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Mutually exclusive with bbox and geometry.',\n ),\n bbox: z\n .array(z.number())\n .length(4)\n .optional()\n .describe(\n '[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Mutually exclusive with location.',\n ),\n showOnMap: z\n .boolean()\n .optional()\n .describe('Visualize the result immediately with default settings. Use showTrafficAreaAnalytics to customize.'),\n startDate: z\n .string()\n .optional()\n .describe(\"Start date 'YYYY-MM-DD'. Use with endDate for a continuous range (max 31 days).\"),\n endDate: z.string().optional().describe(\"End date 'YYYY-MM-DD' (inclusive). Defaults to today if omitted.\"),\n days: z\n .array(z.string())\n .optional()\n .describe(\n \"Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate.\",\n ),\n metrics: z\n .union([z.literal('all'), z.array(z.enum([...METRICS])).min(1)])\n .describe(\n \"Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'].\",\n ),\n functionalRoadClasses: z\n .union([z.literal('all'), z.array(z.enum([...FUNCTIONAL_ROAD_CLASSES]))])\n .optional()\n .describe(\"Road classes to include. 'all' (default) or array of specific classes.\"),\n hours: z\n .union([z.literal('all'), z.array(z.number().int().min(0).max(23))])\n .optional()\n .describe(\"Hours of the day (0-23) to include. 'all' (default) or array like [7,8,9,17,18] for rush hours.\"),\n});\n\nexport const getTrafficAreaAnalyticsDescription =\n 'Fetch historical traffic analytics for an area (≤31 days) via location (named city/region) or bbox. ' +\n 'Fetch ALL metrics in ONE call (visualization needs them together); use city-scale areas (~10 km+) for meaningful data. ' +\n 'Returns period averages — `showOnMap` visualizes immediately; queryTrafficAnalytics for breakdowns.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Convert [minLng, minLat, maxLng, maxLat] to a GeoJSON Polygon.\nconst bboxToPolygon = (bbox: number[]): Polygon => {\n const [minLng, minLat, maxLng, maxLat] = bbox;\n return {\n type: 'Polygon',\n coordinates: [\n [\n [minLng, minLat],\n [maxLng, minLat],\n [maxLng, maxLat],\n [minLng, maxLat],\n [minLng, minLat],\n ],\n ],\n };\n};\n\n// Resolve the analytics area geometry from a location query or bbox.\nconst resolveGeometry = async (\n location: z.infer<typeof locationInputSchema> | undefined,\n bbox: number[] | undefined,\n state: ToolState,\n): Promise<Polygon | MultiPolygon | { error: string }> => {\n if (location) {\n const resolved = await resolveLocationInput(location, state);\n if (!resolved) return { error: 'Could not resolve the provided location.' };\n if (Array.isArray(resolved.place)) {\n return {\n error: 'A bare position cannot define an analytics area. Provide a named location query instead.',\n };\n }\n // resolved.place is a Place at runtime (locatePlace always returns Place | null)\n const boundary = await geometryData({ geometries: [resolved.place as unknown as Place] });\n const boundaryGeometry = boundary?.features?.[0]?.geometry as Polygon | MultiPolygon | undefined;\n if (!boundaryGeometry) {\n return { error: `No boundary polygon found for \"${resolved.name}\". Try a bbox instead.` };\n }\n return boundaryGeometry;\n }\n if (!bbox) return { error: 'No area provided.' };\n return bboxToPolygon(bbox);\n};\n\n// Resolve which date parameters to forward to the SDK from the tool inputs.\nconst resolveDateParams = (\n days: string[] | undefined,\n startDate: string | undefined,\n endDate: string | undefined,\n): { startDate?: string; endDate?: string; days?: string[] } => {\n if (days && days.length > 0) return { days };\n if (startDate) return endDate ? { startDate, endDate } : { startDate };\n return {};\n};\n\n// Show analytics on the map with default settings, fitting the viewport.\nconst showAnalyticsOnMap = async (state: ToolState, result: TrafficAreaAnalytics): Promise<void> => {\n const analyticsModule = await state.trafficAreaAnalytics.getTrafficAreaAnalyticsModule();\n await analyticsModule.clear();\n await analyticsModule.show(result);\n const defaultMetric = result.properties?.metrics?.[0] ?? 'congestionLevel';\n analyticsModule.setMetric(defaultMetric);\n const bbox = bboxFromGeoJSON(result.features);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\n state.trafficAreaAnalytics.notifyAnalyticsShown(result, analyticsModule);\n};\n\n/** Format a Date to 'YYYY-MM-DD'. */\nexport const toDateString = (d: Date): string =>\n `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;\n\n/** Safely format a date value (Date object or string) to 'YYYY-MM-DD', or undefined if invalid. */\nexport const formatDate = (value: unknown): string | undefined => {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? undefined : toDateString(value);\n }\n if (typeof value === 'string') return value.slice(0, 10);\n return undefined;\n};\n\n/** Extract metrics from a timed entry, omitting undefined values. */\nexport const extractMetrics = (entry: Record<string, unknown>) => ({\n ...(entry.speed !== undefined && { speed: entry.speed as number }),\n ...(entry.freeFlowSpeed !== undefined && { freeFlowSpeed: entry.freeFlowSpeed as number }),\n ...(entry.congestionLevel !== undefined && { congestionLevel: entry.congestionLevel as number }),\n ...(entry.travelTime !== undefined && { travelTime: entry.travelTime as number }),\n ...(entry.networkLength !== undefined && { networkLength: entry.networkLength as number }),\n});\n\n/** Compact summary of the analytics result — just headline numbers + what's available for drill-down. */\nconst summarize = (\n result: TrafficAreaAnalytics,\n inputStartDate?: string,\n inputEndDate?: string,\n): z.infer<typeof getTrafficAreaAnalyticsOutputSchema> => {\n const region = result.features[0]?.properties;\n if (!region) {\n return {\n dateRange: { start: '', end: '' },\n baseData: {},\n metrics: [],\n tileCount: 0,\n availableGranularities: [],\n };\n }\n\n // SDK defaults to startDate=3 days ago, endDate=2 days ago when omitted\n const defaultStart = () => {\n const d = new Date();\n d.setDate(d.getDate() - 3);\n return toDateString(d);\n };\n const defaultEnd = () => {\n const d = new Date();\n d.setDate(d.getDate() - 2);\n return toDateString(d);\n };\n\n const start = inputStartDate ?? formatDate(result.properties?.startDate) ?? defaultStart();\n const end = inputEndDate ?? formatDate(result.properties?.endDate) ?? defaultEnd();\n\n const availableGranularities: string[] = [];\n if (region.timedData?.yearly?.length) availableGranularities.push('yearly');\n if (region.timedData?.monthly?.length) availableGranularities.push('monthly');\n if (region.timedData?.weekly?.length) availableGranularities.push('weekly');\n if (region.timedData?.daily?.length) availableGranularities.push('daily');\n if (region.timedData?.hourly?.length) availableGranularities.push('hourly');\n if (region.timedData?.average?.length) availableGranularities.push('average');\n\n return {\n ...(region.name && { name: region.name }),\n ...(region.timezone && { timezone: region.timezone }),\n dateRange: { start, end },\n baseData: region.baseData,\n metrics: result.properties?.metrics ?? [],\n tileCount: region.tiledData?.tiles?.length ?? 0,\n availableGranularities,\n };\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the get traffic area analytics tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetTrafficAreaAnalytics = async (\n params: z.infer<typeof getTrafficAreaAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getTrafficAreaAnalyticsOutputSchema>> => {\n const { location, bbox, showOnMap, startDate, endDate, days, metrics, functionalRoadClasses, hours } = params;\n\n if (!location && !bbox) {\n return { error: 'Provide a location or bbox to define the analytics area.' };\n }\n\n // Move Portal API key (different from standard TomTom key)\n const apiKey = process.env.MOVE_PORTAL_KEY;\n if (!apiKey) {\n return { error: 'MOVE_PORTAL_KEY environment variable is not set.' };\n }\n\n try {\n const resolvedGeometry = await resolveGeometry(location, bbox, state);\n if ('error' in resolvedGeometry) return resolvedGeometry;\n\n // Resolve dates — SDK defaults to startDate=3 days ago, endDate=2 days ago when omitted\n const dateParams = resolveDateParams(days, startDate, endDate);\n\n const result = await trafficAreaAnalytics({\n apiKey,\n ...dateParams,\n metrics,\n functionalRoadClasses: functionalRoadClasses ?? 'all',\n hours: hours ?? 'all',\n geometry: resolvedGeometry,\n } as TrafficAreaAnalyticsParams);\n\n state.trafficAreaAnalytics.setLastAreaAnalytics(result);\n\n if (showOnMap) {\n await showAnalyticsOnMap(state, result);\n }\n\n return summarize(result, dateParams.startDate, dateParams.endDate);\n } catch (error) {\n return {\n error: `Failed to get traffic area analytics: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type BBox, trafficIncidentRequestCategories } from '@tomtom-org/maps-sdk/core';\nimport { type TrafficIncidentDetailsByBBoxParams, trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// Compact list of every entry currently held by `state.trafficIncidents`. Returned alongside\n// the freshly-fetched data so the LLM sees what's available without a separate recall call.\nconst entriesSummary = (state: ToolState) =>\n state.trafficIncidents.entries.map((e) => ({\n id: e.id,\n label: e.label,\n count: e.data.length,\n timestamp: e.timestamp,\n }));\n\n/**\n * Output schema for the get-traffic-incidents tool.\n *\n * By design this tool is a **loader**, not an aggregator. It fetches incidents into a new\n * (or already-loaded) entry, renders them on the map, and returns\n * `{ count, entryId, entries }` — `entries` is a compact summary of every entry currently\n * cached in `state.trafficIncidents`, so the agent can pick a follow-up target without a\n * separate recall call.\n *\n * Every aggregation (counts by category, severity, time-validity, top roads, top-N,\n * clustering, corridor group-by, proximity, spatial joins) is the job of `analyseIncidents`\n * (counts / charts / deltas — attaches to the source entry's `_analyses`) and `focusIncidents`\n * (highlight/filter a subset on an existing entry). Both inject the entry's full feature\n * set into the sandbox.\n *\n * Background polling is opt-in via the separate `startTrafficIncidentsMonitor` tool — keeps\n * the loader a single, predictable network call.\n *\n * Historic shortcuts like a built-in summary, `topIncidents`, and `includeIncidents` were\n * removed: they let the agent skip real aggregation and misrepresent individual top-delay\n * incidents as \"clusters\" or \"hotspots\". The contract now forces the agent through\n * analyseIncidents/focusIncidents for anything beyond `count`.\n */\nexport const getTrafficIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n entryId: z.string().optional(),\n entries: z.array(\n z.object({\n id: z.string(),\n label: z.string(),\n count: z.number(),\n timestamp: z.number(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fetching traffic incident details.\n */\nexport const getTrafficIncidentsSchema = z.object({\n bbox: z\n .optional(z.array(z.number()))\n .describe('[minLng, minLat, maxLng, maxLat]. If omitted, the current map viewport is used.'),\n categoryFilter: z\n .array(z.enum([...trafficIncidentRequestCategories]))\n .optional()\n .describe('Filter by incident category. If omitted, all categories are returned.'),\n timeValidityFilter: z\n .array(z.enum(['present', 'future']))\n .optional()\n .describe(\"default: ['present']\"),\n label: z\n .string()\n .optional()\n .describe(\n 'Short human-readable label for this fetch — name the place (\"Central London\", ' +\n '\"Amsterdam ring\", \"M25 corridor\"), not coordinates. Surfaced verbatim in the ' +\n 'monitor chip and on the entry chip in the UI; also used by analyseIncidents / ' +\n 'focusIncidents to disambiguate which entry to operate on. Always pass one when ' +\n 'silent is false; the bbox-coordinate fallback (\"bbox a,b → c,d\") is for silent ' +\n 'intermediate fetches only.',\n ),\n silent: z\n .boolean()\n .optional()\n .describe(\n 'Default: false. When true, suppress map rendering (use for intermediate calculations only). ' +\n 'Normally, fetched incidents auto-render on the map so the user can visually validate the agent is looking at the right area.',\n ),\n fitBounds: z.boolean().optional().describe('Default: true. Pass false to skip camera move after rendering.'),\n});\n\nexport const getTrafficIncidentsDescription =\n 'LOADER. Fetches traffic incidents (present or future) into a cached entry, renders them on the map, returns ' +\n '`{ count, entryId, entries }` (`entries` lists every cached entry so the agent can pick a follow-up target). ' +\n 'No per-incident details, no aggregation — those belong to analyseIncidents/focusIncidents. ' +\n '\\n\\n' +\n 'AREA: omit `bbox` to use the current map viewport (\"the network / right now / this area / here\"). ' +\n 'Pass explicit `bbox` only when the user names an area not already on screen. ' +\n '\\n\\n' +\n 'CALL ONCE PER AREA. Each call writes a new entry; re-calling burns steps. Pick the right follow-up instead: ' +\n 'analyseIncidents (counts, top-N, charts, clusters, trend via `previous`); focusIncidents (highlight a subset); ' +\n 'startTrafficIncidentsMonitor (keep the entry fresh in the background). ' +\n '\\n\\n' +\n '`silent: true` suppresses rendering (intermediate calcs only); `label` names the snapshot in the UI.';\n\nfunction defaultLabel(params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox }): string {\n const [minLng, minLat, maxLng, maxLat] = params.bbox;\n const bboxStr = `bbox ${minLng.toFixed(2)},${minLat.toFixed(2)} → ${maxLng.toFixed(2)},${maxLat.toFixed(2)}`;\n const filterParts: string[] = [];\n if (params.categoryFilter?.length) filterParts.push(`filtered: ${params.categoryFilter.join(',')}`);\n if (params.timeValidityFilter?.length) filterParts.push(`when: ${params.timeValidityFilter.join(',')}`);\n return filterParts.length ? `${bboxStr} — ${filterParts.join('; ')}` : bboxStr;\n}\n\n/** Standalone execute for ToolEntry format. */\nexport const executeGetTrafficIncidents = async (\n params: z.infer<typeof getTrafficIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getTrafficIncidentsOutputSchema>> => {\n const { bbox, categoryFilter, timeValidityFilter, label, silent = false, fitBounds = true } = params;\n\n // No bbox → fall back to the current map viewport so the agent can answer\n // \"the network / right now / this area\" without a separate getViewport round-trip.\n let effectiveBbox: BBox | undefined = bbox as BBox | undefined;\n if (!effectiveBbox) {\n try {\n effectiveBbox = state.baseMap.ttMap.getBBox() as BBox;\n } catch (error) {\n return {\n error: `No bbox provided and viewport fallback failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n }\n\n const filters = {\n ...(categoryFilter && { categoryFilter }),\n ...(timeValidityFilter && { timeValidityFilter }),\n };\n\n try {\n const result = await trafficIncidentDetails({ ...filters, bbox: effectiveBbox });\n\n if (!result.features.length) {\n return { count: 0, entries: entriesSummary(state) };\n }\n\n const entryParams: TrafficIncidentDetailsByBBoxParams & { bbox: BBox } = {\n bbox: effectiveBbox,\n ...filters,\n };\n const entryId = state.trafficIncidents.addIncidentsEntry(\n result.features,\n entryParams,\n label ?? defaultLabel(entryParams),\n Date.now(),\n );\n\n if (!silent) {\n await state.trafficIncidents.showEntry(entryId, fitBounds);\n }\n\n return {\n count: result.features.length,\n entryId,\n entries: entriesSummary(state),\n };\n } catch (error) {\n return {\n error: `Failed to get traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { hidePreviousEntriesSchema } from '../shared';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { calculateAndAddRoute, resolveRouteWaypoints } from './set-route';\n\n/** Output schema for the remove-waypoints-from-route tool. */\nexport const removeWaypointsFromRouteOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for removing waypoints from an existing route.\n */\nexport const removeWaypointsFromRouteSchema = z.object({\n waypointIndices: z\n .array(z.number().int())\n .min(1)\n .describe(\n 'Indices of waypoints to remove. 0=origin, last=destination. ' +\n 'Min 2 waypoints must remain. Duplicates are not allowed.',\n ),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n});\n\nexport const removeWaypointsFromRouteDescription =\n 'Remove one or more waypoints from the current route by index; always recalculates. ' +\n 'Index 0 = origin, last = destination (both protected when removing them would leave fewer than 2). ' +\n 'Requires an existing route.';\n\nconst validateRemoveWaypoints = <T>(\n waypoints: T[] | null | undefined,\n waypointIndices: readonly number[],\n): string | T[] => {\n if (!waypoints || waypoints.length < 2) {\n return 'No existing waypoints available. Use calculate-route first to create a route with waypoints.';\n }\n const unique = new Set(waypointIndices);\n if (unique.size !== waypointIndices.length) {\n return 'Duplicate waypoint indices are not allowed.';\n }\n for (const idx of waypointIndices) {\n if (idx < 0 || idx >= waypoints.length) {\n return `Invalid waypoint index ${idx}. Valid range is 0 to ${waypoints.length - 1}.`;\n }\n }\n if (waypoints.length - waypointIndices.length < 2) {\n return 'Cannot remove waypoints: at least 2 waypoints (origin and destination) must remain.';\n }\n return waypoints.filter((_, i) => !unique.has(i));\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeRemoveWaypointsFromRoute = async (\n params: z.infer<typeof removeWaypointsFromRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof removeWaypointsFromRouteOutputSchema>> => {\n const { waypointIndices, showOnMap, hidePreviousEntries } = params;\n try {\n const validated = validateRemoveWaypoints(state.routing.currentWaypoints, waypointIndices);\n if (typeof validated === 'string') return { error: validated };\n\n const waypoints = resolveRouteWaypoints(validated);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n return calculateAndAddRoute(state, waypoints, showOnMap, hidePreviousEntries);\n } catch (error) {\n return {\n error: `Failed to remove waypoints from route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { hidePreviousEntriesSchema, locationInputSchema } from '../shared';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\nimport { calculateAndAddRoute, resolveRouteWaypoints } from './set-route';\n\n/** Output schema for the replace-waypoint-in-route tool. */\nexport const replaceWaypointInRouteOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\nconst waypointTargetSchema = z\n .union([z.literal('origin'), z.literal('destination'), z.number().int()])\n .describe(\n \"Which waypoint to overwrite. Use 'origin' or 'destination' for the endpoints, \" +\n 'or a 0-based absolute index (0 = origin, last = destination, intermediate stops in between).',\n );\n\n/**\n * Tool schema for replacing a single waypoint in an existing route.\n */\nexport const replaceWaypointInRouteSchema = z.object({\n waypointIndex: waypointTargetSchema,\n location: locationInputSchema.describe('The new waypoint that replaces the targeted one.'),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n});\n\nexport const replaceWaypointInRouteDescription =\n 'Replace a single waypoint in the current route in place — origin, destination, or any ' +\n 'intermediate stop by 0-based index. Always recalculates. Other waypoints are preserved. ' +\n 'Requires an existing route. Use addWaypointsToRoute to extend the route, removeWaypointsFromRoute ' +\n 'to drop a waypoint, or setRoute to fully replace all waypoints.';\n\nconst resolveTargetIndex = (target: z.infer<typeof waypointTargetSchema>, waypointsLen: number): number | string => {\n if (target === 'origin') return 0;\n if (target === 'destination') return waypointsLen - 1;\n if (target < 0 || target >= waypointsLen) {\n return `Invalid waypoint index ${target}. Valid range is 0 to ${waypointsLen - 1}.`;\n }\n return target;\n};\n\nconst labelForLocation = (location: z.infer<typeof locationInputSchema>): string => {\n if ('query' in location) return `\"${location.query}\"`;\n if ('placeId' in location) return `placeId \"${location.placeId}\"`;\n return JSON.stringify(location.position);\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeReplaceWaypointInRoute = async (\n params: z.infer<typeof replaceWaypointInRouteSchema>,\n state: ToolState,\n): Promise<z.infer<typeof replaceWaypointInRouteOutputSchema>> => {\n const { waypointIndex, location, showOnMap, hidePreviousEntries } = params;\n try {\n const current = state.routing.currentWaypoints;\n if (!current || current.length < 2) {\n return {\n error: 'No existing waypoints available. Use calculate-route first to create a route with waypoints.',\n };\n }\n\n const idx = resolveTargetIndex(waypointIndex, current.length);\n if (typeof idx === 'string') return { error: idx };\n\n const resolved = await resolveLocationInput(location, state);\n if (!resolved) {\n return { error: `Could not resolve location: ${labelForLocation(location)}` };\n }\n\n const updated: WaypointLike[] = [...current];\n updated[idx] = resolved.place as Place | [number, number];\n\n const waypoints = resolveRouteWaypoints(updated);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n return calculateAndAddRoute(state, waypoints, showOnMap, hidePreviousEntries);\n } catch (error) {\n return {\n error: `Failed to replace waypoint in route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { reverseGeocode } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlace } from '../../utils';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the reverse-geocode tool. */\nexport const reverseGeocodeOutputSchema = z.union([placeOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for reverse geocoding (coordinates to address).\n */\nexport const reverseGeocodeSchema = z.object({\n position: z.array(z.number()).length(2).describe('[longitude, latitude] GeoJSON position to reverse geocode'),\n});\n\nexport const reverseGeocodeDescription =\n 'Convert [lng, lat] to an address and place summary. Use after a click / getViewport / getCurrentLocation. ' +\n 'Coordinates only — for text queries use locatePlace or discoverPlaces.';\n\n/**\n * Create the reverse geocode tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeReverseGeocode = async (\n params: z.infer<typeof reverseGeocodeSchema>,\n state: ToolState,\n): Promise<z.infer<typeof reverseGeocodeOutputSchema>> => {\n const { position } = params;\n const pos: Position = position as Position;\n try {\n const result = await reverseGeocode({ position: pos });\n\n if (result) {\n state.places.addPlaceResult(result, makePlacesLabel(result));\n return summarizePlace(result);\n }\n\n return { error: 'No result found for the given coordinates' };\n } catch (error) {\n return {\n error: `Reverse geocoding failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport {\n ANALYSE_OUTPUT_FORMAT_DESCRIPTION,\n buildAnalyseReturnPrompt,\n buildSandboxCodePrompt,\n collectInputGeometries,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SCHEMA_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n geometriesEntryIDsSchema,\n geometriesIdSchema,\n runSandboxedFn,\n skippedSourceSchema,\n validateAnalysisResult,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the analyse-geometries tool. */\nexport const analyseGeometriesOutputSchema = z.union([\n z.object({\n affectedEntries: z\n .array(z.object({ id: geometriesIdSchema.describe('Tagged id of the affected entry.') }))\n .describe(\n 'Entries the analysis was attached to: every contributing places entry plus every contributing ' +\n 'custom-geometries entry. Ranges entries feed the aggregation but do not carry analyses.',\n ),\n sourceIds: z.array(geometriesIdSchema).describe(GEOMETRIES_SOURCE_IDS_DESC),\n name: z.string().describe('Analysis name (unique within each affected entry).'),\n description: z.string().optional(),\n outputFormat: z\n .enum(['json', 'chart'])\n .describe('`json` = plain object for text rendering; `chart` = Chart.js `ChartConfiguration`.'),\n analysis: z\n .unknown()\n .describe('Aggregation result. JSON-serializable when `json`; Chart.js config when `chart`.'),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\n }),\n toolErrorSchema,\n]);\n\nconst ANALYSE_CODE_DOC =\n `Async JS that aggregates the injected \\`geometries\\` array and returns the result. ${GEOMETRIES_PROPS_DOC}\\n\\n` +\n `${buildSandboxCodePrompt(['geometries', 'h3', 'turf'])}\\n\\n` +\n `${buildAnalyseReturnPrompt('totals, per-feature breakdowns, hex bin counts, bboxes')}\\n\\n` +\n 'Examples:\\n' +\n '- total + average area: `const areas = geometries.map(turf.area); return { totalKm2: turf.round(areas.reduce((a,b)=>a+b,0)/1e6, 2), avgKm2: turf.round((areas.reduce((a,b)=>a+b,0)/areas.length)/1e6, 2), perFeature: areas.map((a,i) => ({ id: geometries[i].id, km2: turf.round(a/1e6, 2) })) };`\\n' +\n '- combined bbox: `return { bbox: turf.bbox({ type: \"FeatureCollection\", features: geometries }) };`\\n' +\n '- h3 coverage at res 8: `const cells = new Set(); for (const f of geometries) for (const c of h3.polygonToCells(f.geometry.coordinates, 8)) cells.add(c); return { resolution: 8, cellCount: cells.size };`\\n' +\n '- areas as bar chart: `return { type: \"bar\", data: { labels: geometries.map(f => f.id), datasets: [{ label: \"km²\", data: geometries.map(f => +(turf.area(f)/1e6).toFixed(2)) }] } };`\\n\\n' +\n `${GEOMETRIES_SCHEMA_DOC}`;\n\n/** Tool schema for analyse-geometries. */\nexport const analyseGeometriesSchema = z.object({\n geometriesEntryIDs: geometriesEntryIDsSchema,\n name: z\n .string()\n .describe(\n 'Unique name within each affected entry (e.g. \"total-area\", \"bbox-extent\", \"h3-coverage\"). ' +\n 'Reusing a name replaces the previous analysis on each entry.',\n ),\n description: z.string().optional().describe('Optional short description of what the analysis computes.'),\n outputFormat: z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION),\n code: z.string().describe(ANALYSE_CODE_DOC),\n});\n\nexport const analyseGeometriesDescription =\n 'Aggregate polygons (place footprints, isochrones, custom-entries) via JS — areas, perimeters, hex ' +\n 'coverage, bboxes, charts. Code `(geometries, h3, turf) => result`. Result attached as `_analysis[name]` ' +\n 'on every contributing places/custom entry (ranges entries do not carry analyses). Read-only.';\n\nexport const executeAnalyseGeometries = async (\n params: z.infer<typeof analyseGeometriesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof analyseGeometriesOutputSchema>> => {\n const { geometriesEntryIDs, name, description, code, outputFormat = 'json' } = params;\n\n const collected = await collectInputGeometries(geometriesEntryIDs, state);\n if ('error' in collected) return { error: collected.error };\n const { geometries, affectedEntries, skipped, contributingSourceIds } = collected.value;\n\n const sandboxResult = await runSandboxedFn(code, ['geometries', 'h3', 'turf'], [geometries, h3, turf], 'Analysis');\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n\n const validated = validateAnalysisResult(sandboxResult.value, outputFormat);\n if ('error' in validated) return { error: validated.error };\n const analysis = validated.value;\n\n const timestamp = Date.now();\n for (const entry of affectedEntries) {\n const slice = entry.kind === 'places' ? state.places : state.customGeometries;\n slice.addAnalysisToEntry(entry.id, {\n name,\n timestamp,\n ...(description && { description }),\n outputFormat,\n data: analysis,\n });\n }\n\n return {\n affectedEntries: affectedEntries.map((e): { id: GeometriesId } => ({ id: { kind: e.kind, id: e.id } })),\n sourceIds: contributingSourceIds,\n name,\n ...(description && { description }),\n outputFormat,\n analysis,\n ...(skipped.length && { skipped }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport { runIncidentSpec } from '../../state/traffic-incidents/analysis';\nimport type { ToolState } from '../../types';\nimport { ANALYSE_OUTPUT_FORMAT_DESCRIPTION, buildAnalyseReturnPrompt, buildSandboxCodePrompt } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the analyse-incidents tool. */\nexport const analyseIncidentsOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string().describe('ID of the source incidents entry the analysis was attached to.'),\n name: z.string().describe('Unique name of the analysis within the entry.'),\n description: z.string().optional(),\n outputFormat: z\n .enum(['json', 'chart'])\n .describe(\n 'How the consumer should render `analysis`: `json` = plain object to display as text; ' +\n '`chart` = Chart.js `ChartConfiguration` to feed directly into `new Chart(ctx, analysis)` in the chat message UI.',\n ),\n analysis: z\n .unknown()\n .describe(\n 'The aggregation result. Shape depends on `outputFormat`: ' +\n 'when `json`, any JSON-serializable value; when `chart`, a Chart.js `ChartConfiguration` ({ type, data, options? }).',\n ),\n focused: z\n .object({\n incidentsEntryID: z.string(),\n focusedCount: z.number(),\n droppedIds: z.array(z.string()),\n reason: z.string().optional(),\n })\n .optional()\n .describe(\n 'Present when the analysis result included a `focusIds` field — the tool applied that focus ' +\n 'as a side-effect on the source entry. The agent can read `focusedCount` to confirm.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for analyse-incidents. */\nexport const analyseIncidentsSchema = z.object({\n incidentsEntryID: z\n .string()\n .optional()\n .describe(\n 'ID of an existing incidents entry to analyse. Omit to analyse the most recently fetched entry. ' +\n 'The analysis is attached to that entry under `_analyses` with the given name; ' +\n 'the latest result is read via `_analyses.getResult(name)` and the timeline via `_analyses.history(name)`.',\n ),\n name: z\n .string()\n .describe(\n 'Unique name within the entry (e.g. \"by-category\", \"delay-by-road\", \"hex-density-8\"). Reusing a name ' +\n 'replaces the previous analysis on the same entry.',\n ),\n description: z.string().optional().describe('Optional short description of what the analysis computes.'),\n outputFormat: z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION),\n code: z\n .string()\n .describe(\n 'Async JS that aggregates the injected `incidents` and returns the result. ' +\n '`now: Date` is the call-start time (compare against `startTime`/`endTime`). ' +\n '`log(...)` writes into the response logs.\\n\\n' +\n `${buildSandboxCodePrompt(['incidents', 'h3', 'turf', 'now', 'log', 'previous'])}\\n\\n` +\n 'PREVIOUS: the 6th argument is `previous` — your last result for this spec on the source entry, ' +\n 'or `undefined` on the first run. Use it for id stability (e.g. reuse a previous group/cluster id when ' +\n 'memberIds overlap by ≥50%) and to surface short trend reads (\"growing\" / \"fading\" / \"steady\") in your ' +\n 'output. Always handle `previous === undefined` gracefully — it is `undefined` on every first run.\\n\\n' +\n `${buildAnalyseReturnPrompt('counts, top-N, groupings, hex bins, corridor totals', '`labels: incidents.map(i => [i.properties.from ?? \"?\", i.properties.events[0]?.description ?? \"\", \\\\`${(i.properties.delayInSeconds ?? 0)}s delay\\\\`])`')}\\n\\n` +\n 'TrafficIncident schema (input shape):\\n' +\n '```\\n' +\n 'type TrafficIncident = {\\n' +\n ' type: \"Feature\",\\n' +\n ' id: string,\\n' +\n ' geometry: { type: \"Point\", coordinates: [lng, lat] } | { type: \"LineString\", coordinates: [lng,lat][] },\\n' +\n ' properties: {\\n' +\n ' id: string,\\n' +\n ' category: \"accident\" | \"animals-on-road\" | \"broken-down-vehicle\" | \"danger\" | \"flooding\" | \"fog\" | \"frost\"\\n' +\n ' | \"jam\" | \"lane-closed\" | \"narrow-lanes\" | \"other\" | \"rain\" | \"road-closed\" | \"roadworks\" | \"wind\",\\n' +\n ' magnitudeOfDelay: \"unknown\" | \"minor\" | \"moderate\" | \"major\" | \"indefinite\",\\n' +\n ' timeValidity: \"present\" | \"future\",\\n' +\n ' delayInSeconds?: number,\\n' +\n ' lengthInMeters?: number,\\n' +\n ' roadNumbers?: string[],\\n' +\n ' from?: string,\\n' +\n ' to?: string,\\n' +\n ' startTime?: Date,\\n' +\n ' endTime?: Date,\\n' +\n ' events: { description: string }[],\\n' +\n ' },\\n' +\n '};\\n' +\n '```\\n\\n' +\n 'Examples (one canonical shape per kind — adapt rather than copy verbatim):\\n' +\n '- counts: `const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; return { total: incidents.length, byCategory: c };`\\n' +\n '- top-N: `return [...incidents].sort((a,b) => (b.properties.delayInSeconds ?? 0) - (a.properties.delayInSeconds ?? 0)).slice(0,5).map(i => ({ id: i.properties.id, delay: i.properties.delayInSeconds, road: i.properties.roadNumbers?.[0] }));`\\n' +\n \"- bar chart: `const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; const e = Object.entries(c).sort((a,b) => b[1]-a[1]); return { type: 'bar', data: { labels: e.map(x => x[0]), datasets: [{ label: 'Incidents', data: e.map(x => x[1]) }] } };`\\n\" +\n \"- h3 density: `const bins = {}; for (const i of incidents) { const c = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; bins[h3.latLngToCell(c[1], c[0], 8)] = (bins[h3.latLngToCell(c[1], c[0], 8)] ?? 0) + 1; } return { resolution: 8, bins };`\\n\" +\n '- spatial clusters (slowdown pockets, ranked corridors): produce ' +\n '`{ groups: [{ id, headline, body, memberIds, centroid, size, totalDelaySeconds, peakDelaySeconds, diameterKm, primaryRoads }] }` plus ' +\n '`focusIds` for the worst group. ' +\n 'CRITICAL: `id` must be `\"c1\"`, `\"c2\"`, … (rank, NOT centroid coords or TTI ids — the agent quotes these verbatim in chat). ' +\n '`headline` must be a road label like `` `${primaryRoads[0]} corridor` `` (the user reads this directly). ' +\n '`body` is the metric line `` `Trend ${trend} · total ${(totalDelay/60).toFixed(1)} min · ${size} incidents · ${diameterKm.toFixed(2)} km wide` ``. ' +\n 'Reference implementation (adapt eps / minPoints / cap as needed):\\n' +\n '```\\n' +\n 'const pts = incidents.filter(i => (i.properties.delayInSeconds ?? 0) > 60).map(i => {\\n' +\n \" const c = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates;\\n\" +\n ' return turf.point(c, { id: i.properties.id, delay: i.properties.delayInSeconds, road: i.properties.roadNumbers?.[0] ?? null, from: i.properties.from ?? null });\\n' +\n '});\\n' +\n 'if (pts.length === 0) return { groups: [], focusIds: [] };\\n' +\n \"const clustered = turf.clustersDbscan(turf.featureCollection(pts), 0.4, { units: 'kilometers', minPoints: 4 });\\n\" +\n 'const buckets = {};\\n' +\n 'for (const f of clustered.features) {\\n' +\n ' const cid = f.properties.cluster;\\n' +\n ' if (cid == null) continue;\\n' +\n ' (buckets[cid] ??= []).push(f);\\n' +\n '}\\n' +\n 'const ranked = Object.values(buckets).map(feats => {\\n' +\n ' const memberIds = feats.map(f => f.properties.id);\\n' +\n ' const totalDelaySeconds = feats.reduce((s, f) => s + f.properties.delay, 0);\\n' +\n ' const peakDelaySeconds = feats.reduce((m, f) => Math.max(m, f.properties.delay), 0);\\n' +\n ' const centroid = turf.centroid(turf.featureCollection(feats)).geometry.coordinates;\\n' +\n ' const bb = turf.bbox(turf.featureCollection(feats));\\n' +\n \" const diameterKm = turf.distance([bb[0], bb[1]], [bb[2], bb[3]], { units: 'kilometers' });\\n\" +\n ' const roadCount = {};\\n' +\n ' for (const f of feats) if (f.properties.road) roadCount[f.properties.road] = (roadCount[f.properties.road] ?? 0) + 1;\\n' +\n ' const primaryRoads = Object.entries(roadCount).sort((a,b) => b[1]-a[1]).slice(0,3).map(([r]) => r);\\n' +\n ' return { memberIds, centroid, size: feats.length, totalDelaySeconds, peakDelaySeconds, diameterKm, primaryRoads };\\n' +\n '}).filter(g => g.diameterKm <= 2)\\n' +\n ' .sort((a,b) => b.totalDelaySeconds - a.totalDelaySeconds)\\n' +\n ' .slice(0, 6)\\n' +\n ' .map((g, i) => {\\n' +\n \" const trend = 'new'; // optionally compare against `previous` here\\n\" +\n \" const road = g.primaryRoads[0] ?? 'cluster';\\n\" +\n ' const headline = `${road} corridor`;\\n' +\n ' const body = `Trend ${trend} · total ${(g.totalDelaySeconds/60).toFixed(1)} min · ${g.size} incidents · ${g.diameterKm.toFixed(2)} km wide`;\\n' +\n ' return { id: `c${i+1}`, headline, body, ...g };\\n' +\n ' });\\n' +\n 'return { groups: ranked, focusIds: ranked[0]?.memberIds ?? [], focusReason: ranked[0] ? `${ranked[0].headline}: worst total delay` : undefined };\\n' +\n '```\\n' +\n 'Knobs: eps 0.3–0.5 km (eps ≥ 1 km chains every corridor into one mega-cluster); pre-filter delay > 60s to drop zero-delay roadworks/closures; cap diameter ≤ 2 km for \"compact pocket\".\\n' +\n '- trend across ticks: when `previous` is non-undefined, reuse a prior group id when `memberIds` overlap ≥50%, then derive `growing`/`fading`/`steady` from `totalDelaySeconds` ratio against the matched prior (else `new`).',\n ),\n});\n\nexport const analyseIncidentsDescription =\n 'Aggregate an incidents entry via dynamic JS: counts, top-N, group-bys, hex density, charts, spatial clusters. ' +\n 'Registered against ONE entry — re-runs on every monitor-tick of that entry, with `previous` carrying the prior ' +\n 'result for trend reads. Reusing a name replaces the spec. Does NOT create new entries. ' +\n '`code` is an async function BODY (not a function expression) that uses `incidents`, `h3`, `turf`, `now`, `log`, `previous` ' +\n 'and ends with `return result;`. `outputFormat: \"json\"` (default) or `\"chart\"`. ' +\n 'FOCUS SIDE-EFFECT: when the result has `focusIds: string[]` (+ optional `focusReason`), setFocus is called once ' +\n 'on the source entry — monitor-tick replays do NOT re-apply it. For pure focus, use focusIncidents.';\n\nexport const executeAnalyseIncidents = async (\n params: z.infer<typeof analyseIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof analyseIncidentsOutputSchema>> => {\n const { incidentsEntryID, name, description, code, outputFormat = 'json' } = params;\n\n const allEntries = state.trafficIncidents.entries;\n const sourceEntry = incidentsEntryID\n ? allEntries.find((entry) => entry.id === incidentsEntryID)\n : allEntries[allEntries.length - 1];\n\n if (!sourceEntry) {\n return {\n error: incidentsEntryID\n ? `No incidents entry with id \"${incidentsEntryID}\". Call getTrafficIncidents first.`\n : 'No incidents entries available to analyse. Call getTrafficIncidents first.',\n };\n }\n\n const spec = {\n name,\n ...(description && { description }),\n outputFormat,\n code,\n source: sourceEntry.id,\n };\n const previous = sourceEntry._analyses?.getResult(name)?.data;\n\n // Register the spec *before* the first run so that even when the initial run\n // errors (and we surface the error to the agent), the spec stays available\n // for the next monitor tick to retry. Replay drops failures silently.\n state.trafficIncidents.setAnalysisSpec(spec);\n\n const sampledAt = sourceEntry.timestamp;\n const result = await runIncidentSpec(spec, sourceEntry.data, previous, sampledAt);\n\n if ('error' in result) {\n return { error: result.error };\n }\n\n const { value: analysis } = result;\n state.trafficIncidents.addAnalysisToEntry(sourceEntry.id, {\n name,\n timestamp: sampledAt,\n ...(description && { description }),\n outputFormat,\n data: analysis,\n });\n\n const focused = applyFocusSideEffect(analysis, sourceEntry.id, state);\n\n return {\n incidentsEntryID: sourceEntry.id,\n name,\n ...(description && { description }),\n outputFormat,\n analysis,\n ...(focused && { focused }),\n };\n};\n\ntype FocusSideEffect = {\n incidentsEntryID: string;\n focusedCount: number;\n droppedIds: string[];\n reason?: string;\n};\n\n/**\n * Honour `focusIds` / `focusReason` on the analysis result by calling `setFocus` against the\n * source entry. One-shot — re-runs from `_rerunRegisteredSpecs` skip this branch.\n */\nconst applyFocusSideEffect = (\n analysis: unknown,\n targetEntryID: string,\n state: ToolState,\n): FocusSideEffect | undefined => {\n if (!analysis || typeof analysis !== 'object') return undefined;\n const focusIds = (analysis as { focusIds?: unknown }).focusIds;\n if (!Array.isArray(focusIds) || focusIds.length === 0) return undefined;\n const ids = focusIds.filter((id): id is string => typeof id === 'string');\n if (ids.length === 0) return undefined;\n const reasonRaw = (analysis as { focusReason?: unknown }).focusReason;\n const reason = typeof reasonRaw === 'string' ? reasonRaw : undefined;\n const result = state.trafficIncidents.setFocus(targetEntryID, ids, reason);\n return {\n incidentsEntryID: targetEntryID,\n focusedCount: result.focusedCount,\n droppedIds: result.droppedIds,\n ...(reason && { reason }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Places } from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport {\n ANALYSE_OUTPUT_FORMAT_DESCRIPTION,\n buildAnalyseReturnPrompt,\n buildSandboxCodePrompt,\n PLACES_SCHEMA_DOC,\n placesEntryIDsSchema,\n runSandboxedFn,\n validateAnalysisResult,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the analyse-places tool. */\nexport const analysePlacesOutputSchema = z.union([\n z.object({\n placesEntryIDs: z.array(z.string()).describe('IDs of the source places entries the analysis was attached to.'),\n name: z.string().describe('Unique name of the analysis within the entry.'),\n description: z.string().optional(),\n outputFormat: z\n .enum(['json', 'chart'])\n .describe(\n 'How the consumer should render `analysis`: `json` = plain object to display as text; ' +\n '`chart` = Chart.js `ChartConfiguration` to feed directly into `new Chart(ctx, analysis)` in the chat message UI.',\n ),\n analysis: z\n .unknown()\n .describe(\n 'The aggregation result. Shape depends on `outputFormat`: ' +\n 'when `json`, any JSON-serializable value; when `chart`, a Chart.js `ChartConfiguration` ({ type, data, options? }).',\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for analyse-places.\n */\nexport const analysePlacesSchema = z.object({\n placesEntryIDs: placesEntryIDsSchema({\n verb: 'analyse',\n extra: 'Multiple IDs are merged into a single `places` input. The analysis is attached to EACH source entry under `_analysis` with the given name.',\n }),\n name: z\n .string()\n .describe(\n 'Unique name within the entry (e.g. \"by-category\", \"hex-density-8\"). Reusing a name replaces the previous analysis.',\n ),\n description: z.string().optional().describe('Optional short description of what the analysis computes.'),\n outputFormat: z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION),\n code: z\n .string()\n .describe(\n 'Async JS that aggregates the injected `places` FeatureCollection and returns the result. ' +\n '`places` is the merged set of every entry in `placesEntryIDs`; `placesByEntry[entryId]` exposes ' +\n 'each one separately for per-entry totals or set ops — list every needed id in `placesEntryIDs` ' +\n 'rather than trying to call `recallPlaces` (sandbox cannot call other tools).\\n\\n' +\n `${buildSandboxCodePrompt(['places', 'placesByEntry', 'h3', 'turf'])}\\n\\n` +\n `${buildAnalyseReturnPrompt('counts, groupings, hex bins, centroids, bboxes', '`labels: hexIds.map(h => [\\\\`Hex ${h}\\\\`, \\\\`${counts[h]} items\\\\`, ...names[h].slice(0, 5)])`')}\\n\\n` +\n `${PLACES_SCHEMA_DOC}\\n\\n`,\n ),\n});\n\nexport const analysePlacesDescription =\n 'Aggregate existing places entries via dynamic JS: counts, groupings, densities, summaries, charts. ' +\n 'Code: `(places, h3, turf) => result`; `outputFormat: \"json\"` (default, text) or `\"chart\"` (Chart.js config). ' +\n 'Result is returned and attached as `_analysis[name]` on each source entry. Does NOT create a new entry or touch the map (use processPlaces for that).';\n\nexport const executeAnalysePlaces = async (\n params: z.infer<typeof analysePlacesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof analysePlacesOutputSchema>> => {\n const { placesEntryIDs, name, description, code, outputFormat = 'json' } = params;\n\n const sourceEntries = placesEntryIDs?.length\n ? placesEntryIDs.map((id) => state.places.entries.find((entry) => entry.id === id))\n : [state.places.entries.at(-1)];\n\n for (let i = 0; i < sourceEntries.length; i++) {\n if (!sourceEntries[i]) {\n const requestedId = placesEntryIDs?.[i];\n return {\n error: requestedId\n ? `No entry found with id \"${requestedId}\". Use recallPlaces to list available IDs.`\n : 'No places entries available to analyse. Run discoverPlaces or processPlaces first.',\n };\n }\n }\n const resolvedEntries = sourceEntries as NonNullable<(typeof sourceEntries)[number]>[];\n\n const inputPlaces: Places = {\n type: 'FeatureCollection',\n features: resolvedEntries.flatMap((entry) => entry.places),\n } as Places;\n\n // Per-entry view so per-entry totals and set ops between entries don't\n // require leaving the sandbox to call `recallPlaces`. Mirrors the same\n // shape `process-places` exposes.\n const placesByEntry: Record<string, Places> = Object.fromEntries(\n resolvedEntries.map((entry) => [entry.id, { type: 'FeatureCollection', features: entry.places } as Places]),\n );\n\n const sandboxResult = await runSandboxedFn(\n code,\n ['places', 'placesByEntry', 'h3', 'turf'],\n [inputPlaces, placesByEntry, h3, turf],\n 'Analysis',\n );\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n\n const validated = validateAnalysisResult(sandboxResult.value, outputFormat);\n if ('error' in validated) return { error: validated.error };\n const analysis = validated.value;\n\n const timestamp = Date.now();\n for (const entry of resolvedEntries) {\n state.places.addAnalysisToEntry(entry.id, {\n name,\n timestamp,\n ...(description && { description }),\n outputFormat,\n data: analysis,\n });\n }\n\n return {\n placesEntryIDs: resolvedEntries.map((entry) => entry.id),\n name,\n ...(description && { description }),\n outputFormat,\n analysis,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Routes } from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport {\n ANALYSE_OUTPUT_FORMAT_DESCRIPTION,\n buildAnalyseReturnPrompt,\n buildSandboxCodePrompt,\n findPlacesByEntry,\n PLACES_SCHEMA_DOC,\n placesEntryIDsSchema,\n ROUTES_SCHEMA_DOC,\n routesEntryIDsSchema,\n runSandboxedFn,\n validateAnalysisResult,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the analyse-routes tool. */\nexport const analyseRoutesOutputSchema = z.union([\n z.object({\n routesEntryIDs: z.array(z.string()).describe('IDs of the source routes entries the analysis was attached to.'),\n name: z.string().describe('Unique name of the analysis within the entry.'),\n description: z.string().optional(),\n outputFormat: z\n .enum(['json', 'chart'])\n .describe(\n 'How the consumer should render `analysis`: `json` = plain object to display as text; ' +\n '`chart` = Chart.js `ChartConfiguration` to feed directly into `new Chart(ctx, analysis)` in the chat message UI.',\n ),\n analysis: z\n .unknown()\n .describe(\n 'The aggregation result. Shape depends on `outputFormat`: ' +\n 'when `json`, any JSON-serializable value; when `chart`, a Chart.js `ChartConfiguration`.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for analyse-routes. */\nexport const analyseRoutesSchema = z.object({\n routesEntryIDs: routesEntryIDsSchema({\n verb: 'analyse',\n extra: 'Multiple IDs are merged into a single `routes` input (features concatenated). The analysis is attached to EACH source entry under `_analysis` with the given name.',\n }),\n placesEntryIDs: placesEntryIDsSchema({\n verb: 'cross-reference',\n extra:\n 'Optional. When set, those entries are injected as `placesByEntry: Record<entryId, Places>` so the code can correlate ' +\n 'route geometry/sections with places (e.g. POIs near the route, places per country, hex co-occurrence).',\n }),\n name: z\n .string()\n .describe(\n 'Unique name within the entry (e.g. \"summary\", \"traffic-delay-by-country\", \"ev-stops-near-route\"). Reusing a name replaces the previous analysis.',\n ),\n description: z.string().optional().describe('Optional short description of what the analysis computes.'),\n outputFormat: z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION),\n code: z\n .string()\n .describe(\n 'Async JS that aggregates the injected `routes` FeatureCollection (and `placesByEntry` when `placesEntryIDs` is set, else `undefined`) and returns the result.\\n\\n' +\n `${buildSandboxCodePrompt(['routes', 'placesByEntry', 'h3', 'turf'])}\\n\\n` +\n `${buildAnalyseReturnPrompt('totals, per-section breakdowns, hex bins, place counts within X m of the route', '`labels: sections.map(s => [s.name, \\\\`${s.km} km\\\\`, \\\\`+${s.delaySec}s delay\\\\`])`')}\\n\\n` +\n 'For places-vs-route work: buffer the line with `turf.buffer(route, meters/1000, { units: \"kilometers\" })` and test with `turf.booleanPointInPolygon`, ' +\n 'or compute `turf.pointToLineDistance(place, route, { units: \"meters\" })`. ' +\n 'Iterate `Object.entries(placesByEntry)` to keep results grouped by entry.\\n\\n' +\n `${ROUTES_SCHEMA_DOC}\\n\\n` +\n `${PLACES_SCHEMA_DOC}\\n\\n`,\n ),\n});\n\nexport const analyseRoutesDescription =\n 'Aggregate routes entries via dynamic JS — totals, per-section breakdowns, traffic delays, charts; ' +\n 'cross-references places entries when `placesEntryIDs` is set (POIs near route, hex co-occurrence). ' +\n 'Code: `(routes, h3, turf, placesByEntry?) => result`; `outputFormat: \"json\"` (default) or `\"chart\"`. ' +\n 'Result attached as `_analysis[name]` on each source routes entry. Read-only.';\n\nexport const executeAnalyseRoutes = async (\n params: z.infer<typeof analyseRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof analyseRoutesOutputSchema>> => {\n const { routesEntryIDs, placesEntryIDs, name, description, code, outputFormat = 'json' } = params;\n\n const sourceEntries = routesEntryIDs?.length\n ? routesEntryIDs.map((id) => state.routing.entries.find((entry) => entry.id === id))\n : [state.routing.entries.at(-1)];\n\n for (let i = 0; i < sourceEntries.length; i++) {\n if (!sourceEntries[i]) {\n const requestedId = routesEntryIDs?.[i];\n return {\n error: requestedId\n ? `No entry found with id \"${requestedId}\". Use recallRoutes to list available IDs.`\n : 'No routes entries available to analyse. Run setRoute first.',\n };\n }\n }\n const resolvedEntries = sourceEntries as NonNullable<(typeof sourceEntries)[number]>[];\n\n const placesByEntryResult = findPlacesByEntry(placesEntryIDs, state);\n if ('error' in placesByEntryResult) return { error: placesByEntryResult.error };\n const placesByEntry = placesByEntryResult.value;\n\n const inputRoutes: Routes = {\n type: 'FeatureCollection',\n features: resolvedEntries.flatMap((entry) => entry.data.features),\n } as Routes;\n\n const sandboxResult = await runSandboxedFn(\n code,\n ['routes', 'h3', 'turf', 'placesByEntry'],\n [inputRoutes, h3, turf, placesByEntry],\n 'Analysis',\n );\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n\n const validated = validateAnalysisResult(sandboxResult.value, outputFormat);\n if ('error' in validated) return { error: validated.error };\n const analysis = validated.value;\n\n const timestamp = Date.now();\n for (const entry of resolvedEntries) {\n state.routing.addAnalysisToEntry(entry.id, {\n name,\n timestamp,\n ...(description && { description }),\n outputFormat,\n data: analysis,\n });\n }\n\n return {\n routesEntryIDs: resolvedEntries.map((entry) => entry.id),\n name,\n ...(description && { description }),\n outputFormat,\n analysis,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const focusIncidentsOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string().describe('The entry the focus was applied to.'),\n focusedCount: z.number().describe('How many of the requested ids ended up in the focus subset.'),\n droppedIds: z\n .array(z.string())\n .describe('Ids that were not present on the entry and were dropped from the focus.'),\n reason: z.string().optional().describe('Echo of the focus reason for downstream display.'),\n }),\n toolErrorSchema,\n]);\n\nexport const focusIncidentsSchema = z.object({\n incidentsEntryID: z\n .string()\n .describe(\n 'Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source).',\n ),\n incidentIds: z\n .array(z.string())\n .min(1)\n .describe('Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored.'),\n reason: z\n .string()\n .optional()\n .describe('Short label shown in the UI FocusChip — what this subset represents (e.g. \"top 3 by delay\").'),\n});\n\nexport const focusIncidentsDescription =\n 'Highlight a subset of incidents on an existing entry. Drives the dim/highlight on the map ' +\n 'and the FocusChip in the UI. Compose with `analyseIncidents` to compute the ids first ' +\n '(e.g. top-N by delay, members of a cluster). Unknown ids are dropped silently and ' +\n 'returned in `droppedIds`. Errors only when the entry is unknown or NONE of the ids resolve.';\n\nexport const executeFocusIncidents = async (\n params: z.infer<typeof focusIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof focusIncidentsOutputSchema>> => {\n const { incidentsEntryID, incidentIds, reason } = params;\n const entry = state.trafficIncidents.entries.find((e) => e.id === incidentsEntryID);\n if (!entry) {\n return {\n error: `No incidents entry with id \"${incidentsEntryID}\". Call getTrafficIncidents first.`,\n };\n }\n const result = state.trafficIncidents.setFocus(incidentsEntryID, incidentIds, reason);\n if (result.focusedCount === 0) {\n return {\n error:\n `None of the ${incidentIds.length} requested ids exist on entry \"${incidentsEntryID}\". ` +\n 'The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.',\n };\n }\n return {\n incidentsEntryID,\n focusedCount: result.focusedCount,\n droppedIds: result.droppedIds,\n ...(reason && { reason }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeWaypoint, waypointSummarySchema } from '../../utils';\n\nexport const getCurrentWaypointsSchema = z.object({\n slotIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot.'),\n includeEmptySlots: z\n .boolean()\n .optional()\n .describe('Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses.'),\n offset: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Pagination offset within the filtered slot list. Ignored when slotIndex is provided.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(25)\n .optional()\n .describe('Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.'),\n});\n\nexport const getCurrentWaypointsOutputSchema = z.object({\n totalSlots: z.number().describe('Total waypoint slots currently staged'),\n filledSlots: z.number().describe('Number of staged slots that already contain coordinates'),\n matchedCount: z.number().describe('Total slots matching the request before pagination'),\n returnedCount: z.number().describe('Number of slots returned in this page'),\n offset: z.number().describe('Offset applied to the filtered slot list'),\n hasMore: z.boolean().describe('True when more slots can be fetched with a higher offset'),\n nextOffset: z.number().optional().describe('Offset to request the next page when hasMore=true'),\n waypoints: z.array(waypointSummarySchema),\n});\n\nexport const getCurrentWaypointsDescription =\n 'Read staged waypoint slots for the next route calc, without recalculating. ' +\n '`slotIndex` for one slot; otherwise paged (default 5), empty slots omitted unless `includeEmptySlots: true`. No service call.';\n\nexport const executeGetCurrentWaypoints = async (\n params: z.infer<typeof getCurrentWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getCurrentWaypointsOutputSchema>> => {\n const { slotIndex, includeEmptySlots = false, offset = 0, limit = 5 } = params;\n const slots = state.routing.planningSlots.map((waypoint, index) => {\n const normalized = summarizeWaypoint(waypoint);\n return normalized ? { ...normalized, slotIndex: index } : { slotIndex: index, isFilled: false };\n });\n const filteredSlots = includeEmptySlots ? slots : slots.filter((slot) => slot.isFilled);\n const hasExactSlot = slotIndex !== undefined;\n const selected = hasExactSlot\n ? filteredSlots.filter((slot) => slot.slotIndex === slotIndex)\n : filteredSlots.slice(offset, offset + limit);\n const nextOffset = offset + selected.length;\n\n return {\n totalSlots: slots.length,\n filledSlots: slots.filter((slot) => slot.isFilled).length,\n matchedCount: hasExactSlot ? selected.length : filteredSlots.length,\n returnedCount: selected.length,\n offset: hasExactSlot ? 0 : offset,\n hasMore: !hasExactSlot && nextOffset < filteredSlots.length,\n ...(!hasExactSlot && nextOffset < filteredSlots.length && { nextOffset }),\n waypoints: selected,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { CommonPlaceProps, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport {\n applyFitOnMap,\n buildSandboxCodePrompt,\n collectInputGeometries,\n FIT_ON_MAP_DOC,\n type FitOnMapInput,\n fittedReportSchema,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SCHEMA_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n geometriesEntryIDsSchema,\n geometriesIdSchema,\n hidePreviousEntriesSchema,\n hidePreviousShownEntries,\n isFitOnMapInput,\n isPolygonFeature,\n isPolygonFeatureArray,\n placesEntryIdHintSchema,\n runSandboxedFn,\n showModeSchema,\n skippedSourceSchema,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the process-geometries tool. */\nexport const processGeometriesOutputSchema = z.union([\n z.object({\n geometriesEntryId: geometriesIdSchema.describe('Tagged id of the new entry (always `kind: \"custom\"`).'),\n label: z.string().describe('Human-readable label for the new entry.'),\n geometryCount: z.number().describe('Number of geometries returned by the user code.'),\n sourceIds: z.array(geometriesIdSchema).describe(GEOMETRIES_SOURCE_IDS_DESC),\n operation: z.string().optional().describe('Operation label stored on the new entry.'),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\n fitted: fittedReportSchema.optional().describe('Bounds the camera was fit to (when `fitOnMap` was set).'),\n shown: z.boolean().optional().describe('Whether the new entry was rendered on the map (when `show` was set).'),\n }),\n // Fit-only: code returned `fitOnMap` without geometries — camera moved, no entry written.\n z.object({ fitted: fittedReportSchema }),\n toolErrorSchema,\n]);\n\nconst PROCESS_CODE_DOC =\n 'Async JS over `geometries`. Return `PolygonFeature[]` (Polygon/MultiPolygon, stable `id` recommended; ' +\n 'empty = empty result) OR `{ geometries?, fitOnMap? }` for code-and-zoom; `{ fitOnMap }` alone skips ' +\n `state.\\n\\n${GEOMETRIES_PROPS_DOC}\\n\\n${buildSandboxCodePrompt(['geometries', 'h3', 'turf'])}\\n\\n` +\n `${FIT_ON_MAP_DOC}\\n\\n` +\n 'Turf v7: `union`/`intersect`/`difference` take a single `FeatureCollection` (NOT two features). ' +\n 'Wrap inputs with `turf.featureCollection([...])`.\\n\\n' +\n 'Each input feature is tagged with `properties._source: { kind, id }` reflecting the tagged id ' +\n 'it was passed in under. Filter by that to pick a specific input role inside the sandbox — e.g. for ' +\n '\"areas in Amsterdam not reachable from the hospitals\", first call `locatePlace` to fetch Amsterdam\\'s ' +\n 'polygon and `findReachableAreas` for the hospitals, then pass BOTH ids to `geometriesEntryIDs` and ' +\n 'select them in code via `_source`.\\n\\n' +\n 'Examples:\\n' +\n '- union: `return geometries.length ? [turf.union(turf.featureCollection(geometries))].filter(Boolean) : [];`\\n' +\n '- buffer each by 200m: `return geometries.map((f) => ({ ...turf.buffer(f, 200, { units: \"meters\" }), id: `${f.id}-buf` }));`\\n' +\n '- pairwise intersections: `const out = []; for (let i=0;i<geometries.length;i++) for (let j=i+1;j<geometries.length;j++) { const x = turf.intersect(turf.featureCollection([geometries[i], geometries[j]])); if (x) out.push({ ...x, id: `x-${i}-${j}` }); } return out;`\\n' +\n '- inverse within a named region (pass region polygon AND the inner polygons; pick by `_source`): `const outer = geometries.filter(g => g.properties._source.kind === \"place\"); const inner = geometries.filter(g => g.properties._source.kind === \"ranges\"); const env = turf.union(turf.featureCollection(outer)); const u = turf.union(turf.featureCollection(inner)); const inv = (env && u) ? turf.difference(turf.featureCollection([env, u])) : env; return inv ? [{ ...inv, id: \"inverse\" }] : [];`\\n' +\n '- inverse with bbox-envelope fallback (no containing region available): `const fc = turf.featureCollection(geometries); const [w,s,e,n] = turf.bbox(fc); const env = turf.bboxPolygon([w-0.05,s-0.05,e+0.05,n+0.05]); const u = turf.union(fc); const inv = u ? turf.difference(turf.featureCollection([env, u])) : env; return inv ? [{ ...inv, id: \"inverse\" }] : [];`\\n' +\n '- h3 hex coverage: `const cells = new Set(); for (const f of geometries) for (const c of h3.polygonToCells(f.geometry.coordinates, 8)) cells.add(c); return [...cells].map((c) => turf.polygon([h3.cellToBoundary(c, true)], { cell: c, type: \"h3-cell\" }));`\\n' +\n '- focus camera (no entry): `const u = geometries.length ? turf.union(turf.featureCollection(geometries)) : null; return { fitOnMap: u ? turf.bbox(u) : turf.bbox({type:\"FeatureCollection\",features:geometries}) };`\\n' +\n '- write entry AND zoom: `const out = geometries.map((f) => ({ ...turf.buffer(f, 200, { units: \"meters\" }), id: `${f.id}-buf` })); return { geometries: out, fitOnMap: { bbox: turf.bbox({type:\"FeatureCollection\",features:out}), padding: 80 } };`\\n\\n' +\n `${GEOMETRIES_SCHEMA_DOC}`;\n\n/** Tool schema for process-geometries. */\nexport const processGeometriesSchema = z.object({\n geometriesEntryIDs: geometriesEntryIDsSchema,\n code: z.string().describe(PROCESS_CODE_DOC),\n label: z.string().optional().describe('Short label for the new entry (default: \"processed geometries (<N>)\").'),\n operation: z\n .string()\n .optional()\n .describe(\n 'Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance. ' +\n 'Defaults to `custom`.',\n ),\n entryId: placesEntryIdHintSchema,\n show: z\n .object({\n theme: z\n .enum(['filled', 'outline', 'inverted'])\n .optional()\n .describe('Visual theme. Default: \"outline\". Use \"inverted\" for difference/inverse results.'),\n mode: showModeSchema,\n hidePreviousEntries: hidePreviousEntriesSchema('custom-geometries'),\n })\n .optional()\n .describe(\n 'Render the new custom-geometries entry on the map. ' +\n 'For derived results that supersede their inputs (inverse, difference, union of isochrones, …), ' +\n 'set `hidePreviousEntries: \"all\"` so the prior reachable / source polygons are cleared and only ' +\n 'the new entry is visible. Omit `show` to write the entry without rendering.',\n ),\n});\n\nexport const processGeometriesDescription =\n 'Aggregate polygons (place footprints, isochrones, custom-entries) via JS — union, intersect, buffer, ' +\n 'difference, h3 coverage, etc. — and/or fit the camera. Code ' +\n '`(geometries, h3, turf) => PolygonFeature[] | { geometries?, fitOnMap? }`. ' +\n 'Returning `geometries` writes a NEW custom-geometries entry; `{ fitOnMap }` alone skips state. ' +\n 'Each input feature is tagged with `properties._source: { kind, id }` so code can pick specific ' +\n 'roles by source — e.g. for inverse/difference within a NAMED region, pass the region polygon ' +\n '(via locatePlace/discoverPlaces) AND the inner polygons in `geometriesEntryIDs`, then filter by ' +\n '`_source` in code. ' +\n 'Set `show: { hidePreviousEntries: \"all\" }` for derived results that should replace their inputs ' +\n '(otherwise the new entry is stored but not rendered). Sources untouched.';\n\ntype ValidatedProcessResult = {\n geometries: PolygonFeature[];\n fitOnMap?: FitOnMapInput;\n};\n\n// Accepts either a bare `PolygonFeature[]` or `{ geometries?, fitOnMap? }`. Object form must set\n// at least one; `fitOnMap` alone is a valid \"focus camera, write nothing\" result.\nconst validateProcessOutput = (analysis: unknown): ValidatedProcessResult | string => {\n if (Array.isArray(analysis)) {\n if (!isPolygonFeatureArray(analysis)) {\n for (let i = 0; i < analysis.length; i++) {\n if (!isPolygonFeature(analysis[i])) {\n return `Process code returned an invalid feature at index ${i}: must be a GeoJSON Feature with a Polygon or MultiPolygon geometry.`;\n }\n }\n }\n return { geometries: analysis as PolygonFeature[] };\n }\n if (analysis && typeof analysis === 'object') {\n const v = analysis as { geometries?: unknown; fitOnMap?: unknown };\n const hasGeometries = v.geometries !== undefined;\n const hasFitOnMap = v.fitOnMap !== undefined;\n if (!hasGeometries && !hasFitOnMap) {\n return 'Process code returned an empty object — set at least one of `geometries` or `fitOnMap`.';\n }\n if (hasGeometries && !isPolygonFeatureArray(v.geometries)) {\n return '`geometries` must be a `PolygonFeature[]` (each a GeoJSON Feature with a Polygon or MultiPolygon geometry).';\n }\n if (hasFitOnMap && !isFitOnMapInput(v.fitOnMap)) {\n return '`fitOnMap` must be a `[W,S,E,N]` BBox tuple or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`.';\n }\n return {\n geometries: hasGeometries ? (v.geometries as PolygonFeature[]) : [],\n ...(hasFitOnMap && { fitOnMap: v.fitOnMap as FitOnMapInput }),\n };\n }\n return 'Process code must return either a `PolygonFeature[]` or an object `{ geometries?: PolygonFeature[], fitOnMap?: BBox | { bbox, padding?, animate? } }`.';\n};\n\nexport const executeProcessGeometries = async (\n params: z.infer<typeof processGeometriesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof processGeometriesOutputSchema>> => {\n const { geometriesEntryIDs, code, label, operation, entryId, show } = params;\n\n const collected = await collectInputGeometries(geometriesEntryIDs, state);\n if ('error' in collected) return { error: collected.error };\n const { geometries, skipped, contributingSourceIds } = collected.value;\n\n const sandboxResult = await runSandboxedFn(code, ['geometries', 'h3', 'turf'], [geometries, h3, turf], 'Process');\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n\n const validated = validateProcessOutput(sandboxResult.value);\n if (typeof validated === 'string') return { error: validated };\n\n // Fit-only: code returned `{ fitOnMap }` (or `{ geometries: [], fitOnMap }`) — move camera, skip entry.\n if (validated.geometries.length === 0 && validated.fitOnMap) {\n return { fitted: applyFitOnMap(state, validated.fitOnMap) };\n }\n\n const fallbackLabel = `processed geometries (${validated.geometries.length})`;\n const entryLabel = label ? `processed: ${label}` : fallbackLabel;\n // Range polygons carry different `properties` shapes than place footprints, but the\n // `customGeometries` slice stores them via the same `PolygonFeature<CommonPlaceProps>[]`\n // contract its display module expects — the cast is structurally safe (display reads\n // geometry, not properties) and avoids leaking generic-property variance to consumers.\n const newEntryId = state.customGeometries.addEntry(\n validated.geometries as PolygonFeature<CommonPlaceProps>[],\n { sourceIds: contributingSourceIds, ...(operation && { operation }) },\n entryLabel,\n entryId,\n );\n\n const fitted = validated.fitOnMap ? applyFitOnMap(state, validated.fitOnMap) : undefined;\n\n let shown = false;\n if (show) {\n await hidePreviousShownEntries(state.customGeometries, [newEntryId], show.hidePreviousEntries);\n await state.customGeometries.showEntry(newEntryId, show.theme ?? 'outline', show.mode ?? 'replace');\n shown = true;\n }\n\n const geometriesEntryIdOut: GeometriesId = { kind: 'custom', id: newEntryId };\n\n return {\n geometriesEntryId: geometriesEntryIdOut,\n label: entryLabel,\n geometryCount: validated.geometries.length,\n sourceIds: contributingSourceIds,\n ...(operation && { operation }),\n ...(skipped.length && { skipped }),\n ...(fitted && { fitted }),\n ...(shown && { shown }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { CommonPlaceProps, Places, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport type { PlaceConnectionDisplay } from '@tomtom-org/maps-sdk/map';\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport {\n applyFitOnMap,\n buildSandboxCodePrompt,\n FIT_ON_MAP_DOC,\n type FitOnMapInput,\n fittedReportSchema,\n GEOMETRIES_SCHEMA_DOC,\n isFitOnMapInput,\n isPolygonFeature,\n PLACES_SCHEMA_DOC,\n placesEntryIDsSchema,\n placesEntryIdHintSchema,\n runSandboxedFn,\n showModeSchema,\n shownSchema,\n showPlacesSchema,\n showResultsOnMap,\n} from '../shared';\nimport { placesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n// Render options for the produced `geometries`: mode and theme only — `show`\n// is implicit (the code returned the polygons, the caller asked to render\n// them by setting this object). Mirrors `showPlaceGeometriesSchema` but\n// without the fetch/`show` knob, since we already have the geometries.\nconst showProducedGeometriesSchema = z.object({\n mode: showModeSchema,\n theme: z\n .enum(['filled', 'outline', 'inverted'])\n .optional()\n .describe('Visual theme for the polygons. Default: \"outline\".'),\n});\n\nconst shownReportSchema = z.object({\n places: shownSchema.optional(),\n geometries: z\n .object({\n count: z.number(),\n theme: z.enum(['filled', 'outline', 'inverted']),\n mode: z.enum(['add', 'replace']),\n })\n .optional(),\n});\n\n/** Output schema for the process-places tool. */\nexport const processPlacesOutputSchema = z.union([\n placesOutputSchema.extend({\n shown: shownReportSchema.optional(),\n placesEntryId: z.string().optional(),\n label: z.string().optional(),\n connectionsRendered: z\n .number()\n .optional()\n .describe(\n 'Number of connection lines drawn between the output places (when `placeConnections` was returned).',\n ),\n geometryCount: z\n .number()\n .optional()\n .describe(\n 'Number of polygons stored on the new entry under `geometries` (when the code returned a `geometries` array).',\n ),\n fitted: fittedReportSchema\n .optional()\n .describe('Bounds the camera was fit to (when the code returned `fitOnMap`).'),\n }),\n // Fit-only branch: code returned `fitOnMap` without `places` — camera moved, no entry written.\n z.object({\n fitted: fittedReportSchema,\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for process-places. */\nexport const processPlacesSchema = z.object({\n placesEntryIDs: placesEntryIDsSchema({\n verb: 'process',\n extra: 'Multiple IDs are merged into a single `places` input. Sources are untouched; results go to a NEW entry.',\n }),\n code: z\n .string()\n .describe(\n 'Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap? }`. ' +\n 'At least one of `places` / `fitOnMap` required; `placeConnections` and `geometries` require `places` ' +\n '(they attach to the new entry). ' +\n '`places`: Places FeatureCollection (filter/merge/subset of `places.features`) — written as a NEW entry. ' +\n '`placeConnections`: `{ from, to, id?, ...}[]` between output places (endpoints: Place or place id). ' +\n '`geometries`: `PolygonFeature[]` (Polygon/MultiPolygon, stable `id` recommended). ' +\n '`fitOnMap`: see below. ' +\n 'Inputs: `places` is the merged set across `placesEntryIDs`; `placesByEntry[id]` keeps them separate ' +\n 'for set ops (diff/intersection/group-by-source) — list every needed id in `placesEntryIDs` (sandbox ' +\n \"can't call other tools). `geometries` arg is empty unless `withGeometries: true`.\\n\\n\" +\n `${buildSandboxCodePrompt(['places', 'placesByEntry', 'geometries', 'h3', 'turf'])}\\n\\n` +\n `${FIT_ON_MAP_DOC}\\n\\n` +\n `${PLACES_SCHEMA_DOC}\\n\\n` +\n `${GEOMETRIES_SCHEMA_DOC}\\n\\n` +\n 'Examples:\\n' +\n '- filter: `return { places: { type: \"FeatureCollection\", features: places.features.filter(p => p.properties.poi?.name?.toLowerCase().includes(\"vegan\")) } };`\\n' +\n '- diff between two entries (in `placesEntryIDs: [\"A\",\"B\"]`): `const aIds = new Set(placesByEntry[\"A\"].features.map(p => p.id)); return { places: { type: \"FeatureCollection\", features: placesByEntry[\"B\"].features.filter(p => !aIds.has(p.id)) } };`\\n' +\n '- filter + connect: `const f = places.features.filter(p => p.properties.poi?.categories?.includes(\"CAFE\")); return { places: { type: \"FeatureCollection\", features: f }, placeConnections: f.slice(1).map(p => ({ from: f[0], to: p })) };`\\n' +\n '- passthrough + star: `const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };`\\n' +\n '- dbscan clusters → convex hulls: `const c = turf.clustersDbscan({type:\"FeatureCollection\",features:places.features}, 0.5, {units:\"kilometers\"}); const groups = {}; for (const f of c.features) { const k = f.properties.cluster; if (k != null) (groups[k] ||= []).push(f); } const hulls = Object.entries(groups).map(([k, fs]) => { const h = turf.convex({type:\"FeatureCollection\",features:fs}); if (h) { h.id = `cluster-${k}`; h.properties = { cluster: k, count: fs.length }; } return h; }).filter(Boolean); return { places, geometries: hulls };`\\n' +\n '- buffer footprints (needs `withGeometries: true`): `return { places, geometries: geometries.map((f, i) => ({ ...turf.buffer(f, 250, { units: \"meters\" }), id: `${f.id}-buf` })) };`\\n' +\n '- union footprints (needs `withGeometries: true`): `const u = geometries.reduce((a, f) => a ? turf.union(a, f) : f, null); return { places, geometries: u ? [u] : [] };`\\n' +\n '- h3 hex coverage of result places: `const cells = new Set(); for (const p of places.features) cells.add(h3.latLngToCell(p.geometry.coordinates[1], p.geometry.coordinates[0], 8)); return { places, geometries: [...cells].map((c) => turf.polygon([h3.cellToBoundary(c, true)], { cell: c, type: \"h3-cell\" })) };`\\n' +\n '- focus camera on filtered places (no new entry): `const f = places.features.filter(p => p.properties.poi?.categories?.includes(\"EV_STATION\")); return { fitOnMap: turf.bbox({ type: \"FeatureCollection\", features: f }) };`\\n' +\n '- write entry AND zoom: `const f = places.features.filter(p => p.properties.poi?.name?.toLowerCase().includes(\"vegan\")); return { places: { type: \"FeatureCollection\", features: f }, fitOnMap: { bbox: turf.bbox({ type: \"FeatureCollection\", features: f }), padding: 80 } };`',\n ),\n label: z.string().optional().describe('Short label for the new entry (default: \"processed (<N> places)\").'),\n entryId: placesEntryIdHintSchema,\n show: z\n .object({\n places: showPlacesSchema\n .optional()\n .describe('Render the processed places (markerType / zoomMode / hidePreviousEntries).'),\n geometries: showProducedGeometriesSchema\n .optional()\n .describe(\n 'Render the polygons returned in `geometries` via the place GeometriesModule. ' +\n 'No-op when the code did not return any geometries.',\n ),\n })\n .optional()\n .describe(\n 'What to render on the map after processing. Set `show.places` to draw the processed places, ' +\n 'set `show.geometries` to draw the produced polygons; both can be set together. Omit to ' +\n 'store the new entry without touching the map.',\n ),\n connectMarkerType: z\n .enum(['pin', 'base-map'])\n .optional()\n .describe(\n 'Which PlacesModule renders `placeConnections`. Default: \"pin\". ' +\n 'Pick the module whose shown places the connection endpoints reference (ids only resolve against currently-shown places).',\n ),\n withGeometries: z\n .boolean()\n .optional()\n .describe(\n \"When true, fetch each input place's boundary polygon (where available) before running the code and \" +\n 'inject them as the `geometries` array. Required when the code reads `geometries` (e.g. to ' +\n 'aggregate footprints, compute h3 coverage, or buffer outlines). Defaults to false to keep the ' +\n 'tool light when only `places` are needed.',\n ),\n});\n\nexport const processPlacesDescription =\n 'Dynamic JS over place entries: filter/merge/subset/re-order, draw connections between output places ' +\n '(pairings, stars, nearest-neighbour graphs), compute polygons (union, buffer, h3 coverage), and/or fit ' +\n 'the camera. Code `(places, placesByEntry, geometries, h3, turf) => { places?, placeConnections?, geometries?, fitOnMap? }`. ' +\n 'Sources untouched; `places` writes a NEW entry (optionally rendered via `show.places` / `show.geometries` / ' +\n '`connectMarkerType`); ONLY `fitOnMap` skips state. Set `withGeometries: true` to pre-fetch boundary polygons.';\n\ntype ProcessResult = {\n places?: Places;\n placeConnections?: PlaceConnectionDisplay[];\n geometries?: PolygonFeature<CommonPlaceProps>[];\n fitOnMap?: FitOnMapInput;\n};\n\nconst isPlacesCollection = (value: unknown): value is Places => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { type?: unknown; features?: unknown };\n return v.type === 'FeatureCollection' && Array.isArray(v.features);\n};\n\nconst isConnectionArray = (value: unknown): value is PlaceConnectionDisplay[] => {\n if (!Array.isArray(value)) return false;\n const isEndpoint = (endpoint: unknown): boolean =>\n typeof endpoint === 'string' || (typeof endpoint === 'object' && endpoint !== null && 'geometry' in endpoint);\n return value.every((item) => {\n if (!item || typeof item !== 'object') return false;\n const connection = item as { from?: unknown; to?: unknown };\n return isEndpoint(connection.from) && isEndpoint(connection.to);\n });\n};\n\nconst isGeometryArray = (value: unknown): value is PolygonFeature<CommonPlaceProps>[] =>\n Array.isArray(value) && value.every((v) => isPolygonFeature<CommonPlaceProps>(v));\n\nconst isProcessResult = (value: unknown): value is ProcessResult => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { places?: unknown; placeConnections?: unknown; geometries?: unknown; fitOnMap?: unknown };\n if (v.places !== undefined && !isPlacesCollection(v.places)) return false;\n if (v.placeConnections !== undefined && !isConnectionArray(v.placeConnections)) return false;\n if (v.geometries !== undefined && !isGeometryArray(v.geometries)) return false;\n if (v.fitOnMap !== undefined && !isFitOnMapInput(v.fitOnMap)) return false;\n // Need at least one effect — either write a new entry (`places`) or move the camera (`fitOnMap`).\n if (v.places === undefined && v.fitOnMap === undefined) return false;\n // `placeConnections` and `geometries` hang off the produced entry; without `places` there is no entry.\n if (v.places === undefined && (v.placeConnections !== undefined || v.geometries !== undefined)) return false;\n return true;\n};\n\nconst runProcessCode = async (\n code: string,\n places: Places,\n placesByEntry: Record<string, Places>,\n geometries: PolygonFeature<CommonPlaceProps>[],\n): Promise<ProcessResult | { error: string }> => {\n const sandboxResult = await runSandboxedFn(\n code,\n ['places', 'placesByEntry', 'geometries', 'h3', 'turf'],\n [places, placesByEntry, geometries, h3, turf],\n 'Process',\n );\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n if (!isProcessResult(sandboxResult.value)) {\n return {\n error:\n 'Process code must return `{ places?, placeConnections?, geometries?, fitOnMap? }`. At least one ' +\n 'of `places`/`fitOnMap` required; `placeConnections` and `geometries` require `places`. ' +\n 'Geometries must be Polygon or MultiPolygon GeoJSON Features. ' +\n '`fitOnMap`: `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.',\n };\n }\n return sandboxResult.value;\n};\n\nconst renderConnections = async (\n entryId: string,\n connections: PlaceConnectionDisplay[],\n state: ToolState,\n markerType: 'pin' | 'base-map',\n): Promise<number | { error: string }> => {\n try {\n // Connections render on the new entry's PlacesModule for the chosen\n // marker theme — same module that draws the entry's pins, so the\n // lines snap to the rendered features.\n const placesModule = await state.places.getEntryPlacesModule(entryId, markerType);\n await placesModule.showConnections(connections);\n return connections.length;\n } catch (error) {\n return { error: `Failed to render connections: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\nexport const executeProcessPlaces = async (\n params: z.infer<typeof processPlacesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof processPlacesOutputSchema>> => {\n const { placesEntryIDs, code, label, show, connectMarkerType, entryId, withGeometries } = params;\n\n const sourceEntries = placesEntryIDs?.length\n ? placesEntryIDs.map((id) => state.places.entries.find((entry) => entry.id === id))\n : [state.places.entries.at(-1)];\n\n for (let i = 0; i < sourceEntries.length; i++) {\n if (!sourceEntries[i]) {\n const requestedId = placesEntryIDs?.[i];\n return {\n error: requestedId\n ? `No entry found with id \"${requestedId}\". Use recallPlaces to list available IDs.`\n : 'No places entries available to process. Run discoverPlaces first.',\n };\n }\n }\n const resolvedEntries = sourceEntries as NonNullable<(typeof sourceEntries)[number]>[];\n\n const inputPlaces: Places = {\n type: 'FeatureCollection',\n features: resolvedEntries.flatMap((entry) => entry.places),\n } as Places;\n\n // Per-entry view so set ops between entries (diff, intersection,\n // group-by-source) don't require leaving the sandbox to call\n // `recallPlaces`. The merged `places` collection is still the convenient\n // default; `placesByEntry[id]` is the escape hatch when the LLM needs\n // to keep entries distinct.\n const placesByEntry: Record<string, Places> = Object.fromEntries(\n resolvedEntries.map((entry) => [entry.id, { type: 'FeatureCollection', features: entry.places } as Places]),\n );\n\n // Pre-fetch geometries for every input entry when the caller opts in. Each\n // entry caches its own polygons via `fetchGeometriesForEntry`, so repeat\n // calls on the same entry are free; we then merge across entries to feed\n // the sandbox a single `geometries` array (matching `process-geometries`).\n let inputGeometries: PolygonFeature<CommonPlaceProps>[] = [];\n if (withGeometries) {\n try {\n const perEntry = await Promise.all(\n resolvedEntries.map((entry) => state.places.fetchGeometriesForEntry(entry.id)),\n );\n inputGeometries = perEntry.flat();\n } catch (error) {\n return {\n error: `Failed to fetch input geometries: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n }\n\n const runResult = await runProcessCode(code, inputPlaces, placesByEntry, inputGeometries);\n if ('error' in runResult) return runResult;\n const { places: processed, placeConnections, geometries: producedGeometries, fitOnMap } = runResult;\n\n // Fit-only path: the LLM only wants to focus the camera on a computed bbox — no entry written,\n // no rendering, no connections. Validator guarantees `fitOnMap` is set in this branch.\n if (!processed) {\n return { fitted: applyFitOnMap(state, fitOnMap!) };\n }\n\n const count = processed.features.length;\n const entryLabel = `${label ? `processed: ${label}` : 'processed'} (${count} ${count === 1 ? 'place' : 'places'})`;\n // When the LLM supplies a semantic `entryId`, use it directly — addPlaceResult enforces uniqueness\n // by suffixing on collision. Otherwise fall back to the source-derived `<sources>-process-N` id.\n let derivedId: string;\n if (entryId) {\n derivedId = entryId;\n } else {\n const sourceIds = resolvedEntries.map((entry) => entry.id);\n const prefix = `${sourceIds.join('+')}-process-`;\n const existingCount = state.places.entries.filter((entry) => entry.id.startsWith(prefix)).length;\n derivedId = `${prefix}${existingCount}`;\n }\n const newPlacesEntryId = state.places.addPlaceResult(\n processed,\n entryLabel,\n derivedId,\n placeConnections,\n producedGeometries,\n );\n\n // Render places and/or geometries based on the nested `show` knobs. Either,\n // both, or neither may be set; building `shown` field-by-field keeps it\n // truthful (we don't claim to have rendered something the caller didn't\n // ask for).\n const shown: z.infer<typeof shownReportSchema> = {};\n if (show?.places) {\n shown.places = await showResultsOnMap(state, [newPlacesEntryId], show.places);\n }\n if (show?.geometries && producedGeometries?.length) {\n const theme = show.geometries.theme ?? 'outline';\n const mode = show.geometries.mode ?? 'replace';\n // Per-entry geometries module — anchored to the new entry so it\n // doesn't reuse a sibling entry's GeometriesModule.\n await state.places.showPlaceGeometries(producedGeometries, theme, mode, newPlacesEntryId);\n shown.geometries = { count: producedGeometries.length, theme, mode };\n }\n const reportShown = shown.places || shown.geometries ? shown : undefined;\n const storedLabel = state.places.entries.find((entry) => entry.id === newPlacesEntryId)?.label;\n\n let connectionsRendered: number | undefined;\n if (placeConnections) {\n const rendered = await renderConnections(newPlacesEntryId, placeConnections, state, connectMarkerType ?? 'pin');\n if (typeof rendered === 'object') return rendered;\n connectionsRendered = rendered;\n }\n\n // Apply `fitOnMap` last so it overrides any `show.places` zoom that ran via `showResultsOnMap`.\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n\n return {\n ...summarizePlaces(processed),\n placesEntryId: newPlacesEntryId,\n ...(storedLabel && { label: storedLabel }),\n ...(reportShown && { shown: reportShown }),\n ...(connectionsRendered !== undefined && { connectionsRendered }),\n ...(producedGeometries?.length && { geometryCount: producedGeometries.length }),\n ...(fitted && { fitted }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n bboxFromGeoJSON,\n calculateProgressAtRoutePoint,\n getCoordinateAtRouteProgress,\n getProgressAtNearestRoutePoint,\n getRouteProgressBetween,\n getRouteProgressForSection,\n getSectionBBox,\n type Routes,\n} from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport {\n applyFitOnMap,\n buildSandboxCodePrompt,\n FIT_ON_MAP_DOC,\n type FitOnMapInput,\n findPlacesByEntry,\n fittedReportSchema,\n isFitOnMapInput,\n PLACES_SCHEMA_DOC,\n placesEntryIDsSchema,\n ROUTES_SCHEMA_DOC,\n routesEntryIDsSchema,\n runSandboxedFn,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// `routeUtils` namespace injected into the sandbox. Bundled once at module load\n// so each call is a stable reference (the LLM-visible API never shifts) and so\n// future additions are a one-line change here.\nconst routeUtils = {\n bboxFromGeoJSON,\n calculateProgressAtRoutePoint,\n getCoordinateAtRouteProgress,\n getProgressAtNearestRoutePoint,\n getRouteProgressBetween,\n getRouteProgressForSection,\n getSectionBBox,\n} as const;\n\nconst ROUTE_UTILS_DOC =\n '`routeUtils` (from @tomtom-org/maps-sdk/core):\\n' +\n '• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>` (`traffic`, `motorway`, `country`, `toll`, …).\\n' +\n '• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startPathIndex, endPathIndex)` → `{ start, end, delta: { distanceInMeters, travelTimeInSeconds } }`.\\n' +\n '• `calculateProgressAtRoutePoint(route, pathIndex)` → `{ distanceInMeters, travelTimeInSeconds }`.\\n' +\n '• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }` → `{ position, ... }`.\\n' +\n '• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point → projected position + cumulative distance/time.\\n' +\n '• `bboxFromGeoJSON(featureOrCollection)` — bbox of any GeoJSON.';\n\n/** Output schema for the process-routes tool. */\nexport const processRoutesOutputSchema = z.union([\n z.object({\n fitted: fittedReportSchema.describe('Bounds the camera was fit to.'),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for process-routes. */\nexport const processRoutesSchema = z.object({\n routesEntryIDs: routesEntryIDsSchema({\n verb: 'process',\n extra: 'Multiple IDs are merged into a single `routes` input (features concatenated).',\n }),\n placesEntryIDs: placesEntryIDsSchema({\n verb: 'cross-reference',\n extra:\n 'Optional. Injects `placesByEntry: Record<entryId, Places>` for correlating route sections/geometry ' +\n 'with places (e.g. \"fit the route AND every EV station within 500m\").',\n }),\n code: z\n .string()\n .describe(\n 'Async JS returning `{ fitOnMap }`. Read-only — no new entries. ' +\n 'Derive section/progress bboxes via `routeUtils.*`; fall back to `turf.bbox(routes.features[0])` ' +\n 'for the whole route.\\n\\n' +\n `${buildSandboxCodePrompt(['routes', 'placesByEntry', 'routeUtils', 'h3', 'turf'])}\\n\\n` +\n `${ROUTE_UTILS_DOC}\\n\\n` +\n `${FIT_ON_MAP_DOC}\\n\\n` +\n 'Examples:\\n' +\n '- focus on the worst traffic section: `const r = routes.features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };`\\n' +\n '- focus on the first toll section: `const r = routes.features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };`\\n' +\n '- focus on a 10–20 km segment: `const r = routes.features[0]; const a = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 10000 }); const b = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 20000 }); if (!a || !b) return { fitOnMap: turf.bbox(r) }; const fc = { type: \"FeatureCollection\", features: [turf.point(a.position), turf.point(b.position)] }; return { fitOnMap: { bbox: turf.bbox(fc), padding: 100 } };`\\n' +\n '- focus on the first leg only: `const r = routes.features[0]; const leg = r.properties.sections.leg?.[0]; if (!leg) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, leg) };`\\n' +\n '- focus on a country-section + nearby EV stations (cross-ref): `const r = routes.features[0]; const sec = r.properties.sections.country?.find(c => c.countryCodeISO3 === \"FRA\"); if (!sec) return { fitOnMap: turf.bbox(r) }; const ev = (placesByEntry?.[\"ev-stations\"]?.features ?? []).filter(p => turf.pointToLineDistance(p, r, { units: \"meters\" }) <= 1000); const fc = { type: \"FeatureCollection\", features: [...ev, turf.bboxPolygon(routeUtils.getSectionBBox(r, sec) ?? turf.bbox(r))] }; return { fitOnMap: { bbox: turf.bbox(fc), padding: 80 } };`\\n\\n' +\n `${ROUTES_SCHEMA_DOC}\\n\\n` +\n `${PLACES_SCHEMA_DOC}`,\n ),\n});\n\nexport const processRoutesDescription =\n 'Dynamic JS over routes entries: derive a bbox via `routeUtils` (sections, progress, snap-to-line) and fit ' +\n 'the camera. Read-only. Code `(routes, placesByEntry, routeUtils, h3, turf) => { fitOnMap }`. ' +\n 'Pass `placesEntryIDs` to cross-reference with places.';\n\ntype ProcessResult = {\n fitOnMap: FitOnMapInput;\n};\n\nconst isProcessResult = (value: unknown): value is ProcessResult => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { fitOnMap?: unknown };\n return v.fitOnMap !== undefined && isFitOnMapInput(v.fitOnMap);\n};\n\nexport const executeProcessRoutes = async (\n params: z.infer<typeof processRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof processRoutesOutputSchema>> => {\n const { routesEntryIDs, placesEntryIDs, code } = params;\n\n const sourceEntries = routesEntryIDs?.length\n ? routesEntryIDs.map((id) => state.routing.entries.find((entry) => entry.id === id))\n : [state.routing.entries.at(-1)];\n\n for (let i = 0; i < sourceEntries.length; i++) {\n if (!sourceEntries[i]) {\n const requestedId = routesEntryIDs?.[i];\n return {\n error: requestedId\n ? `No entry found with id \"${requestedId}\". Use recallRoutes to list available IDs.`\n : 'No routes entries available to process. Run setRoute first.',\n };\n }\n }\n const resolvedEntries = sourceEntries as NonNullable<(typeof sourceEntries)[number]>[];\n\n const placesByEntryResult = findPlacesByEntry(placesEntryIDs, state);\n if ('error' in placesByEntryResult) return { error: placesByEntryResult.error };\n const placesByEntry = placesByEntryResult.value;\n\n const inputRoutes: Routes = {\n type: 'FeatureCollection',\n features: resolvedEntries.flatMap((entry) => entry.data.features),\n } as Routes;\n\n const sandboxResult = await runSandboxedFn(\n code,\n ['routes', 'placesByEntry', 'routeUtils', 'h3', 'turf'],\n [inputRoutes, placesByEntry, routeUtils, h3, turf],\n 'Process',\n );\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n const result = sandboxResult.value;\n\n if (!isProcessResult(result)) {\n return {\n error:\n 'Process code must return `{ fitOnMap: [W,S,E,N] | { bbox: [W,S,E,N], padding?, animate? } }`. ' +\n 'Derive via `routeUtils.getSectionBBox(route, section)` or `turf.bbox(...)`.',\n };\n }\n\n return { fitted: applyFitOnMap(state, result.fitOnMap) };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { extractMetrics, formatDate, metricsSchema, toDateString } from '../services/get-traffic-area-analytics';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst timedEntrySchema = metricsSchema.extend({\n date: z.string().optional(),\n hour: z.number().optional(),\n year: z.number().optional(),\n month: z.number().optional(),\n week: z.number().optional(),\n day: z.number().optional(),\n});\n\nexport const queryTrafficAnalyticsSchema = z.object({\n granularity: z\n .enum(['daily', 'hourly', 'weekly', 'monthly', 'yearly', 'average'])\n .optional()\n .describe(\"Time granularity. Default: 'daily'. Use 'average' for day-of-week × hour patterns.\"),\n metric: z\n .enum(['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'])\n .optional()\n .describe('Filter to a single metric. Default: all requested metrics.'),\n startDate: z\n .string()\n .optional()\n .describe(\"Filter entries from this date (YYYY-MM-DD). Works with 'daily' and 'hourly'.\"),\n endDate: z\n .string()\n .optional()\n .describe(\"Filter entries up to this date (YYYY-MM-DD). Works with 'daily' and 'hourly'.\"),\n hourStart: z\n .number()\n .int()\n .min(0)\n .max(23)\n .optional()\n .describe('Filter hourly/average entries from this hour (0-23).'),\n hourEnd: z\n .number()\n .int()\n .min(0)\n .max(23)\n .optional()\n .describe('Filter hourly/average entries up to this hour (0-23).'),\n dayOfWeek: z\n .array(z.number().int().min(1).max(7))\n .optional()\n .describe(\"Filter 'average' entries by day of week (1=Mon…7=Sun).\"),\n});\n\n/** Output schema for the query-traffic-analytics tool. */\nexport const queryTrafficAnalyticsOutputSchema = z.union([\n z.object({\n granularity: z.string(),\n count: z.number(),\n entries: z.array(timedEntrySchema),\n currentVisualization: z\n .object({\n activeMetric: z.string().optional(),\n displayMode: z.string().optional(),\n })\n .optional(),\n }),\n toolErrorSchema,\n]);\n\nexport const queryTrafficAnalyticsDescription =\n 'Query cached traffic analytics (from getTrafficAreaAnalytics) without re-fetching — filter by granularity, date/hour range, day, metric. ' +\n 'Returns filtered entries plus the current map visualization state (metric, mode). Use for breakdowns and \"what is shown\" follow-ups.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Derive a date string for a daily/hourly entry using index fallback. */\nconst resolveDateStr = (\n entryDate: unknown,\n index: number,\n granularity: string,\n startDateStr?: string,\n): string | undefined => {\n const direct = formatDate(entryDate);\n if (direct) return direct;\n if (startDateStr && index >= 0) {\n const d = new Date(startDateStr);\n if (!Number.isNaN(d.getTime())) {\n const dayOffset = granularity === 'hourly' ? Math.floor(index / 24) : index;\n d.setDate(d.getDate() + dayOffset);\n return toDateString(d);\n }\n }\n return undefined;\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the query traffic analytics tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeQueryTrafficAnalytics = async (\n params: z.infer<typeof queryTrafficAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof queryTrafficAnalyticsOutputSchema>> => {\n const { granularity = 'daily', metric, startDate, endDate, hourStart, hourEnd, dayOfWeek } = params;\n\n const analytics = state.trafficAreaAnalytics.lastAreaAnalytics;\n if (!analytics) {\n return { error: 'No analytics data cached. Call getTrafficAreaAnalytics first.' };\n }\n\n const region = analytics.features[0]?.properties;\n if (!region) {\n return { error: 'Analytics result has no region data.' };\n }\n\n const timedData = region.timedData;\n const rawEntries = timedData?.[granularity as keyof typeof timedData];\n if (!rawEntries || !Array.isArray(rawEntries) || rawEntries.length === 0) {\n return {\n error: `No '${granularity}' data available. Available: ${Object.keys(timedData ?? {}).join(', ')}`,\n };\n }\n\n // Resolve start date for index-based date derivation\n const collectionStartDate = formatDate(analytics.properties?.startDate);\n\n // Map and filter entries\n let entries = rawEntries.map((entry, index) => {\n const mapped: Record<string, unknown> = {};\n\n // Temporal fields\n if (granularity === 'daily' || granularity === 'hourly') {\n const dateStr = resolveDateStr(entry.date, index, granularity, collectionStartDate);\n if (dateStr) mapped.date = dateStr;\n }\n if (entry.hour !== undefined) mapped.hour = entry.hour;\n if (entry.year !== undefined) mapped.year = entry.year;\n if (entry.month !== undefined) mapped.month = entry.month;\n if (entry.week !== undefined) mapped.week = entry.week;\n if (entry.day !== undefined) mapped.day = entry.day;\n\n // Metrics\n if (metric) {\n const val = (entry as Record<string, unknown>)[metric];\n if (val !== undefined) mapped[metric] = val;\n } else {\n Object.assign(mapped, extractMetrics(entry));\n }\n\n return mapped;\n });\n\n // Apply date range filter (daily/hourly)\n if (startDate) {\n entries = entries.filter((e) => !e.date || (e.date as string) >= startDate);\n }\n if (endDate) {\n entries = entries.filter((e) => !e.date || (e.date as string) <= endDate);\n }\n\n // Apply hour range filter (hourly/average)\n if (hourStart !== undefined) {\n entries = entries.filter((e) => e.hour === undefined || (e.hour as number) >= hourStart);\n }\n if (hourEnd !== undefined) {\n entries = entries.filter((e) => e.hour === undefined || (e.hour as number) <= hourEnd);\n }\n\n // Apply day-of-week filter (average)\n if (dayOfWeek && dayOfWeek.length > 0) {\n entries = entries.filter((e) => e.day === undefined || dayOfWeek.includes(e.day as number));\n }\n\n // Include current visualization state so LLM knows what's displayed\n const vizConfig = state.trafficAreaAnalytics.currentAnalyticsConfig;\n const currentVisualization = vizConfig\n ? {\n activeMetric: vizConfig.activeMetric,\n displayMode: vizConfig.displayMode,\n }\n : undefined;\n\n return {\n granularity,\n count: entries.length,\n entries: entries as z.infer<typeof timedEntrySchema>[],\n ...(currentVisualization && { currentVisualization }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { type GeometriesId, geometriesIdSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallGeometriesSchema = z.object({\n id: geometriesIdSchema\n .optional()\n .describe(\n 'Tagged id to inspect in detail (e.g. `{ kind: \"places\", id: \"ev-stations-oost\" }`, ' +\n '`{ kind: \"ranges\", id: \"ranges-0\" }`, `{ kind: \"custom\", id: \"bakery-candidates-gap-oost\" }`). ' +\n 'Omit to list every available id across all slices.',\n ),\n});\n\nconst analysisIndexSchema = z.object({\n name: z.string(),\n outputFormat: z.enum(['json', 'chart']),\n timestamp: z.number(),\n description: z.string().optional(),\n});\n\nconst refSchema = z.object({\n id: geometriesIdSchema.describe('Tagged id of this entry.'),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number(),\n operation: z.string().optional().describe('Operation label (custom-geometries entries only).'),\n sourceIds: z\n .array(geometriesIdSchema)\n .optional()\n .describe('Provenance: tagged ids of inputs that fed the producing op (custom only).'),\n analyses: z\n .array(analysisIndexSchema)\n .optional()\n .describe('Analyses already attached to this entry (places and custom only).'),\n});\n\nconst detailSchema = refSchema.extend({\n /**\n * GeoJSON-like structure: `{ type: 'FeatureCollection', features: PolygonFeature[] }`.\n * Schema kept loose because the toolkit serialises features verbatim.\n */\n features: z.unknown().describe(\"GeoJSON FeatureCollection of the entry's polygon features.\"),\n});\n\nexport const recallGeometriesOutputSchema = z.union([\n z.object({ entries: z.array(refSchema) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallGeometriesDescription =\n 'List or inspect every polygon source in session state — place footprints (cached on places ' +\n 'entries via `withGeometries: true`), reachable-area isochrones, and custom-geometries entries ' +\n 'produced by `processGeometries`. ALWAYS call this before passing ids to `processGeometries` or ' +\n '`analyseGeometries`; never guess. ' +\n 'No args = index across all three slices; pass `id` to retrieve features and provenance for one entry. ' +\n 'No service call.';\n\nconst summariseAnalyses = (\n analyses: { name: string; outputFormat: 'json' | 'chart'; timestamp: number; description?: string }[] | undefined,\n) =>\n analyses?.length\n ? analyses.map(({ name, outputFormat, timestamp, description }) => ({\n name,\n outputFormat,\n timestamp,\n ...(description && { description }),\n }))\n : undefined;\n\nconst indexEntries = (state: ToolState): z.infer<typeof refSchema>[] => {\n const refs: z.infer<typeof refSchema>[] = [];\n\n for (const entry of state.places.entries) {\n if (!entry.geometries?.length) continue;\n const analyses = summariseAnalyses(entry._analysis);\n refs.push({\n id: { kind: 'places', id: entry.id },\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.geometries.length,\n ...(analyses && { analyses }),\n });\n }\n\n for (const entry of state.ranges.entries) {\n const featureCount = entry.ranges.reduce((acc, r) => acc + (r.polygon?.features.length ?? 0), 0);\n if (featureCount === 0) continue;\n refs.push({\n id: { kind: 'ranges', id: entry.id },\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount,\n });\n }\n\n for (const entry of state.customGeometries.entries) {\n const analyses = summariseAnalyses(entry._analysis);\n refs.push({\n id: { kind: 'custom', id: entry.id },\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.features.length,\n ...(entry.provenance.operation && { operation: entry.provenance.operation }),\n ...(entry.provenance.sourceIds.length && { sourceIds: [...entry.provenance.sourceIds] }),\n ...(analyses && { analyses }),\n });\n }\n\n return refs.sort((a, b) => b.timestamp - a.timestamp);\n};\n\nconst detailFor = (id: GeometriesId, state: ToolState): z.infer<typeof detailSchema> | { error: string } => {\n if (id.kind === 'places') {\n const entry = state.places.entries.find((e) => e.id === id.id);\n if (!entry) return { error: `No places entry with id \"${id.id}\"` };\n if (!entry.geometries?.length) {\n return {\n error: `Places entry \"${id.id}\" has no cached footprints. Call discoverPlaces / locatePlace with \\`withGeometries: true\\` first.`,\n };\n }\n return {\n id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.geometries.length,\n features: { type: 'FeatureCollection', features: entry.geometries },\n ...(summariseAnalyses(entry._analysis) && { analyses: summariseAnalyses(entry._analysis) }),\n };\n }\n if (id.kind === 'ranges') {\n const entry = state.ranges.entries.find((e) => e.id === id.id);\n if (!entry) return { error: `No ranges entry with id \"${id.id}\"` };\n const features = entry.ranges.flatMap((r) => r.polygon?.features ?? []);\n if (features.length === 0) return { error: `Ranges entry \"${id.id}\" has no polygons.` };\n return {\n id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: features.length,\n features: { type: 'FeatureCollection', features },\n };\n }\n if (id.kind === 'custom') {\n const entry = state.customGeometries.findById(id.id);\n if (!entry) return { error: `No custom-geometries entry with id \"${id.id}\"` };\n return {\n id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.features.length,\n features: { type: 'FeatureCollection', features: entry.features },\n ...(entry.provenance.operation && { operation: entry.provenance.operation }),\n ...(entry.provenance.sourceIds.length && { sourceIds: [...entry.provenance.sourceIds] }),\n ...(summariseAnalyses(entry._analysis) && { analyses: summariseAnalyses(entry._analysis) }),\n };\n }\n return {\n error: '`{ kind: \"place\" }` is not a recallable detail target. Pass a places-entry id (`{ kind: \"places\", id }`) or use `recallPlaces` to inspect a single place.',\n };\n};\n\nexport const executeRecallGeometries = async (\n params: z.infer<typeof recallGeometriesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallGeometriesOutputSchema>> => {\n const { id } = params;\n if (!id) return { entries: indexEntries(state) };\n return detailFor(id, state);\n};\n","import { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport { placesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallPlacesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"places-3\"). Omit to list all entries.'),\n});\n\nconst analysisIndexSchema = z.object({\n name: z.string(),\n outputFormat: z.enum(['json', 'chart']),\n timestamp: z.number(),\n description: z.string().optional(),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number().describe('Number of Place features stored in this entry.'),\n analyses: z\n .array(analysisIndexSchema)\n .optional()\n .describe('Analyses already attached to this entry via analysePlaces (name + output format).'),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n places: placesOutputSchema,\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several entries render simultaneously; ' +\n '`single` enforces \"at most one entry on the map\" — switching to it auto-clears non-latest entries.',\n );\n\nexport const recallPlacesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallPlacesDescription =\n 'List / inspect places entries already in session state (from discoverPlaces / locatePlace / processPlaces) ' +\n '— the same entries every `placesEntryIDs` parameter accepts. ALWAYS call this before referencing past ' +\n 'places; never guess IDs. No args = index (id, label, timestamp, featureCount, analyses) + `entryMode`; ' +\n 'pass `id` for full Place coordinates. No service call.';\n\nexport const executeRecallPlaces = async (\n params: z.infer<typeof recallPlacesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallPlacesOutputSchema>> => {\n const { id } = params;\n const allEntries = state.places.entries;\n const entryMode = state.places.entryMode;\n\n if (!id) {\n const entries = [...allEntries]\n .sort((a, b) => b.timestamp - a.timestamp)\n .map(({ id, label, timestamp, places, _analysis }) => ({\n id,\n label,\n timestamp,\n featureCount: places.length,\n ...(_analysis?.length && {\n analyses: _analysis.map(({ name, outputFormat, timestamp, description }) => ({\n name,\n outputFormat,\n timestamp,\n ...(description && { description }),\n })),\n }),\n }));\n return { entries, entryMode };\n }\n\n const entry = allEntries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n const places = summarizePlaces(entry.places);\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n places,\n entryMode,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallRangesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"ranges-0\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n originCount: z.number(),\n});\n\nconst rangeSummarySchema = z.object({\n origin: z.object({\n query: z.string().optional(),\n position: z.tuple([z.number(), z.number()]),\n }),\n budgets: z.array(z.object({ type: z.string(), value: z.number() })),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n ranges: z.array(rangeSummarySchema),\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several reachable-range entries render at once; ' +\n '`single` enforces \"at most one entry on the map\" — switching to it auto-clears non-latest entries.',\n );\n\nexport const recallRangesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallRangesDescription =\n 'List stored reachable-range results, or retrieve one by `id` (returns each origin and its budgets). ' +\n 'Multi-origin entries surface every origin under `ranges`. Never guess IDs — list first. No service call.';\n\nexport const executeRecallRanges = async (\n params: z.infer<typeof recallRangesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallRangesOutputSchema>> => {\n const { id } = params;\n const entryMode = state.ranges.entryMode;\n\n if (!id) {\n const entries = [...state.ranges.entries].reverse().map(({ id, label, timestamp, ranges }) => ({\n id,\n label,\n timestamp,\n originCount: ranges.length,\n }));\n return { entries, entryMode };\n }\n\n const entry = state.ranges.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n ranges: entry.ranges.map((r) => ({\n origin: { ...r.origin, position: r.origin.position as [number, number] },\n budgets: r.budgets,\n })),\n entryMode,\n };\n};\n","import { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeRoutes, summarizeWaypoint, waypointSummarySchema } from '../../utils';\nimport { costModelSchema, whenSchema } from '../services/set-route';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallRoutesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"routes-1\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n});\n\nconst routeParamsSchema = z.object({\n maxAlternatives: z.number().optional(),\n costModel: costModelSchema.optional(),\n when: whenSchema.optional(),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n routes: routesOutputSchema,\n waypoints: z.array(\n z.object({\n position: z.array(z.number()).length(2).describe('[longitude, latitude]'),\n }),\n ),\n params: routeParamsSchema,\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several routes render at once; ' +\n '`single` enforces \"at most one route on the map\" — switching to it auto-clears non-latest entries.',\n );\n\nexport const recallRoutesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallRoutesDescription =\n 'List calculated routes from session history, or retrieve one by `id` (route details + params). ' +\n 'IDs are stable and accepted by updateRoutesDisplay. Never guess IDs — list first. No service call.';\n\nexport const executeRecallRoutes = async (\n params: z.infer<typeof recallRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallRoutesOutputSchema>> => {\n const { id } = params;\n const entryMode = state.routing.entryMode;\n\n if (!id) {\n const entries = [...state.routing.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp }));\n return { entries, entryMode };\n }\n\n const entry = state.routing.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n const summarized = summarizeRoutes(entry.data);\n const waypoints = entry.waypoints\n .map((wp) => summarizeWaypoint(wp))\n .filter((wp): wp is z.infer<typeof waypointSummarySchema> => wp !== null)\n .map((wp) => ({ position: wp.position ?? [0, 0] }));\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n routes: summarized,\n waypoints,\n params: entry.params as z.infer<typeof routeParamsSchema>,\n entryMode,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { StyleInput } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst idLabelSchema = z.object({\n id: z.string(),\n label: z.string(),\n});\n\nconst entryModeSchema = z.enum(['single', 'multiple']);\n\n/** Output schema for recall-state. */\nexport const recallStateOutputSchema = z.union([\n z.object({\n places: z.array(idLabelSchema).describe('Places history entries — newest first.'),\n routes: z.array(idLabelSchema).describe('Routes history entries — newest first.'),\n ranges: z.array(idLabelSchema).describe('Reachable-range history entries — newest first.'),\n entryModes: z\n .object({ places: entryModeSchema, routes: entryModeSchema, ranges: entryModeSchema })\n .describe(\n 'Per-slice display policy. `multiple` (default) lets several entries render at once; ' +\n '`single` keeps only the latest entry — set via setEntryMode tools.',\n ),\n mapStyle: z\n .object({\n id: z.string().optional().describe('Standard style id (e.g. \"standardLight\").'),\n })\n .nullable()\n .describe('Current map style. `null` when the map has no style assigned yet.'),\n mapPOIs: z\n .object({\n visible: z.boolean().optional(),\n filterMode: z.enum(['only', 'all_except']).optional(),\n categories: z.array(z.string()).optional(),\n })\n .nullable()\n .describe(\n 'Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session.',\n ),\n traffic: z\n .object({\n flow: z\n .object({ visible: z.boolean().optional() })\n .nullable()\n .describe('Traffic-flow layer config. `null` when the module is uninitialised.'),\n incidents: z\n .object({ visible: z.boolean().optional() })\n .nullable()\n .describe('Traffic-incidents layer config. `null` when the module is uninitialised.'),\n })\n .describe('Real-time traffic visualization configs.'),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for recall-state. */\nexport const recallStateSchema = z.object({});\n\nexport const recallStateDescription =\n 'Single-shot session snapshot: places / routes / ranges entries (id+label) + per-slice `entryMode` + ' +\n 'active map style + POIs / traffic visualization configs. Read-only. Use as orientation before making ' +\n 'decisions that depend on what is already loaded.';\n\n// Pulls the standard style id out of whatever `TomTomMap.getStyle()` returns:\n// a bare string id or a `StandardStyle` object with `{ id, ... }`. Anything\n// else (custom style) is reported as id-less.\nconst readStyleId = (style: StyleInput | undefined): string | undefined => {\n if (typeof style === 'string') return style;\n if (style && typeof style === 'object' && 'id' in style) {\n return (style as { id?: string }).id;\n }\n return undefined;\n};\n\nexport const executeRecallState = async (\n _params: z.infer<typeof recallStateSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallStateOutputSchema>> => {\n // Newest-first across each history. Same ordering recallPlaces / recallRoutes\n // / recallRanges already use, so the LLM doesn't have to re-sort.\n const byNewest = <T extends { timestamp: number }>(arr: readonly T[]): T[] =>\n [...arr].sort((a, b) => b.timestamp - a.timestamp);\n\n const places = byNewest(state.places.entries).map(({ id, label }) => ({ id, label }));\n const routes = byNewest(state.routing.entries).map(({ id, label }) => ({ id, label }));\n const ranges = byNewest(state.ranges.entries).map(({ id, label }) => ({ id, label }));\n\n const styleId = readStyleId(state.baseMap.ttMap.getStyle());\n const mapStyle = styleId === undefined ? null : { id: styleId };\n\n // Read POIs / traffic configs through the cached module accessors so we\n // don't lazy-init modules the user never touched (an uninitialised\n // module reports as `null`, which the LLM should read as \"default state,\n // nothing modified\").\n const poisModule = state.mapPOIs.poisModule;\n const poisConfig = poisModule?.getConfig();\n const mapPOIs = poisConfig\n ? {\n ...(typeof poisConfig.visible === 'boolean' && { visible: poisConfig.visible }),\n ...(poisConfig.filters?.categories?.show && {\n filterMode: poisConfig.filters.categories.show,\n }),\n ...(poisConfig.filters?.categories?.values && {\n categories: [...poisConfig.filters.categories.values],\n }),\n }\n : null;\n\n const flowConfig = state.trafficTiles.trafficFlowModule?.getConfig();\n const incidentsConfig = state.trafficTiles.trafficIncidentsModule?.getConfig();\n const traffic = {\n flow: flowConfig ? { ...(typeof flowConfig.visible === 'boolean' && { visible: flowConfig.visible }) } : null,\n incidents: incidentsConfig\n ? { ...(typeof incidentsConfig.visible === 'boolean' && { visible: incidentsConfig.visible }) }\n : null,\n };\n\n const entryModes = {\n places: state.places.entryMode,\n routes: state.routing.entryMode,\n ranges: state.ranges.entryMode,\n };\n\n return { places, routes, ranges, entryModes, mapStyle, mapPOIs, traffic };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst DEFAULT_STYLE_ID = 'standardLight';\n\n/** Output schema for reset-state. */\nexport const resetStateOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z\n .array(z.enum(['places', 'routes', 'ranges', 'mapPOIs', 'traffic', 'mapStyle']))\n .describe('Slices that were wiped / reset to defaults.'),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for reset-state. */\nexport const resetStateSchema = z.object({});\n\nexport const resetStateDescription =\n 'Wipe the session: drops every places/routes/ranges entry + their map renders, resets POIs + traffic ' +\n 'configs to defaults, reverts the map style to \"standardLight\". Destructive — use only when the user ' +\n 'explicitly asks for a clean slate (\"start over\", \"reset\", \"clear everything\").';\n\nexport const executeResetState = async (\n _params: z.infer<typeof resetStateSchema>,\n state: ToolState,\n): Promise<z.infer<typeof resetStateOutputSchema>> => {\n try {\n // 1. Visually clear every initialised module BEFORE the in-memory\n // state resets. With per-entry modules, we walk every entry on\n // every slice and clear whatever it has wired up. Without this\n // pass the MapLibre sources keep the last-rendered features even\n // after the entry history is gone.\n await state.places.clearShownEntries();\n for (const entry of state.places.entries) {\n await entry._modules?.geometries?.clear();\n }\n\n for (const entry of state.routing.entries) {\n if (entry._module) {\n await entry._module.clearRoutes();\n await entry._module.clearWaypoints();\n }\n }\n\n for (const entry of state.ranges.entries) {\n await entry._modules?.geometries?.clear();\n await entry._modules?.places?.clear();\n }\n\n await state.trafficAreaAnalytics.trafficAreaAnalyticsModule?.clear();\n\n // 2. POIs / traffic-flow / traffic-incidents — revert visualization\n // configs to defaults via the SDK base's `resetConfig()`. Only\n // touch modules that were actually initialised in this session;\n // hitting the lazy getter would create the module just to reset it.\n state.mapPOIs.poisModule?.resetConfig();\n state.trafficTiles.trafficFlowModule?.resetConfig();\n state.trafficTiles.trafficIncidentsModule?.resetConfig();\n\n // 3. Drop history + module references and re-emit change events so\n // the UI lights up empty.\n state.places.reset();\n state.routing.reset();\n state.ranges.reset();\n\n // 4. Map style: revert to the canonical default. `keepState: false`\n // so any sources/layers added after the last `setStyle` call are\n // dropped along with the previous style.\n state.baseMap.ttMap.setStyle(DEFAULT_STYLE_ID, { keepState: false });\n\n return {\n success: true,\n cleared: ['places', 'routes', 'ranges', 'mapPOIs', 'traffic', 'mapStyle'],\n };\n } catch (error) {\n return {\n error: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst sliceSchema = z.enum(['places', 'routes', 'ranges']);\nconst modeSchema = z.enum(['single', 'multiple']);\n\n/** Output schema for the setEntryMode tool. */\nexport const setEntryModeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n slice: sliceSchema,\n mode: modeSchema,\n droppedEntryIds: z\n .array(z.string())\n .describe('Entry IDs removed from history because the new mode is `single` and they were not the latest.'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for setEntryMode. */\nexport const setEntryModeSchema = z.object({\n slice: sliceSchema.describe('Which history to reconfigure: places / routes / ranges.'),\n mode: modeSchema.describe(\n '`multiple` (default) lets several entries render at once; ' +\n '`single` keeps only the latest entry — switching to it auto-clears every older entry.',\n ),\n});\n\nexport const setEntryModeDescription =\n 'Switch the display policy of one history slice (places, routes, or ranges) between `multiple` and `single`. ' +\n '`single` ⇒ at most one entry on the map, and any older entries get dropped from history immediately. ' +\n 'Use when the user asks for a \"clean view\" / \"only show one at a time\" or wants to compare overlays again.';\n\nexport const executeSetEntryMode = async (\n params: z.infer<typeof setEntryModeSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setEntryModeOutputSchema>> => {\n const { slice, mode } = params;\n try {\n // Snapshot ids the slice was about to drop so we can report them back —\n // setEntryMode runs the trim internally and emits an event, but the\n // caller benefits from a concrete list (e.g. to mention by id in the\n // chat reply).\n const beforeIds = (() => {\n switch (slice) {\n case 'places':\n return state.places.entries.map((e) => e.id);\n case 'routes':\n return state.routing.entries.map((e) => e.id);\n case 'ranges':\n return state.ranges.entries.map((e) => e.id);\n }\n })();\n\n switch (slice) {\n case 'places':\n await state.places.setEntryMode(mode);\n break;\n case 'routes':\n await state.routing.setEntryMode(mode);\n break;\n case 'ranges':\n await state.ranges.setEntryMode(mode);\n break;\n }\n\n const afterIds = (() => {\n switch (slice) {\n case 'places':\n return new Set(state.places.entries.map((e) => e.id));\n case 'routes':\n return new Set(state.routing.entries.map((e) => e.id));\n case 'ranges':\n return new Set(state.ranges.entries.map((e) => e.id));\n }\n })();\n const droppedEntryIds = beforeIds.filter((id) => !afterIds.has(id));\n\n return { success: true, slice, mode, droppedEntryIds };\n } catch (error) {\n return {\n error: `Failed to set entry mode: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const startTrafficIncidentsMonitorOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string(),\n alreadyRunning: z\n .boolean()\n .describe(\n 'True when the entry already had a running monitor — start is idempotent. ' +\n 'When true, any new intervalMs / filter context in this call was IGNORED; ' +\n 'the monitor keeps running with whatever it was started with. ' +\n 'Call stopTrafficIncidentsMonitor first if you need different settings.',\n ),\n }),\n toolErrorSchema,\n]);\n\nexport const startTrafficIncidentsMonitorSchema = z.object({\n incidentsEntryID: z.string().describe('Id of the incidents entry to monitor (returned by `getTrafficIncidents`).'),\n intervalMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Polling interval in milliseconds. Defaults to 60000 (60s).'),\n});\n\nexport const startTrafficIncidentsMonitorDescription =\n \"Start a background monitor for an existing incidents entry. Re-fetches the entry's bbox + filters every \" +\n 'interval (default 60s) and replaces its data; any registered analyses re-run on each tick. The first tick ' +\n 'fires immediately, so this duplicates the most recent getTrafficIncidents fetch — only call it when the user ' +\n 'wants the entry to stay current. ' +\n '\\n\\n' +\n 'Idempotent on a running monitor: a second call is a no-op and returns alreadyRunning: true. A new intervalMs ' +\n 'is NOT applied to a running monitor — stop first, then start again to change settings.';\n\nexport const executeStartTrafficIncidentsMonitor = async (\n params: z.infer<typeof startTrafficIncidentsMonitorSchema>,\n state: ToolState,\n): Promise<z.infer<typeof startTrafficIncidentsMonitorOutputSchema>> => {\n const { incidentsEntryID, intervalMs } = params;\n const entry = state.trafficIncidents.entries.find((e) => e.id === incidentsEntryID);\n if (!entry) {\n return {\n error: `No incidents entry with id \"${incidentsEntryID}\". Call getTrafficIncidents first.`,\n };\n }\n\n const alreadyRunning = state.trafficIncidents.isMonitored(incidentsEntryID);\n\n // Capture the entry's filters in the fetcher closure so every tick (not just the first)\n // re-issues the same category / time-validity filter the entry was loaded with.\n const { bbox, categoryFilter, timeValidityFilter } = entry.params;\n const filters = {\n ...(categoryFilter && { categoryFilter }),\n ...(timeValidityFilter && { timeValidityFilter }),\n };\n\n state.trafficIncidents.startMonitoring(\n incidentsEntryID,\n { bbox, capturedAt: entry.timestamp, label: entry.label },\n {\n fetchIncidents: async (b) => (await trafficIncidentDetails({ ...filters, bbox: b })).features,\n },\n intervalMs ? { intervalMs } : undefined,\n );\n\n return { incidentsEntryID, alreadyRunning };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const stopTrafficIncidentsMonitorOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string(),\n wasRunning: z.boolean().describe('True when a monitor was actually stopped; false when it was already idle.'),\n }),\n toolErrorSchema,\n]);\n\nexport const stopTrafficIncidentsMonitorSchema = z.object({\n incidentsEntryID: z.string().describe('Id of the incidents entry whose monitor should stop.'),\n});\n\nexport const stopTrafficIncidentsMonitorDescription =\n 'Stop the background monitor for an incidents entry. The entry itself stays in state with its last data — ' +\n 'use removeEntry if you also want to drop the entry. No-op when no monitor is running for the entry.';\n\nexport const executeStopTrafficIncidentsMonitor = async (\n params: z.infer<typeof stopTrafficIncidentsMonitorSchema>,\n state: ToolState,\n): Promise<z.infer<typeof stopTrafficIncidentsMonitorOutputSchema>> => {\n const { incidentsEntryID } = params;\n const entry = state.trafficIncidents.entries.find((e) => e.id === incidentsEntryID);\n if (!entry) {\n return {\n error: `No incidents entry with id \"${incidentsEntryID}\".`,\n };\n }\n\n const wasRunning = state.trafficIncidents.isMonitored(incidentsEntryID);\n state.trafficIncidents.stopMonitoring(incidentsEntryID);\n\n return { incidentsEntryID, wasRunning };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the clear-map tool. */\nexport const clearMapOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\nconst CLEAR_LAYERS = ['places', 'routes', 'geometries', 'analytics', 'incidents'] as const;\ntype ClearLayer = (typeof CLEAR_LAYERS)[number];\n\n/**\n * Tool schema for clearing map features.\n */\nexport const clearMapSchema = z.object({\n layers: z\n .array(z.enum(CLEAR_LAYERS))\n .optional()\n .describe(\n 'Layers to clear (default: all). `analytics` covers traffic area analytics; `incidents` covers fetched traffic incidents.',\n ),\n});\n\nexport const clearMapDescription =\n 'Remove displayed features from the map (places, routes, geometries, analytics, incidents). Use to clean up before showing new results or to reset the map.';\n\nconst clearPlaces = (state: ToolState): Promise<void> => state.places.clearShownEntries();\n\n// Each routing entry owns its own RoutingModule; walk them all so we don't leave stale\n// routes from a previously-shown entry.\nconst clearRoutes = async (state: ToolState): Promise<void> => {\n for (const entry of state.routing.entries) {\n if (entry._module) {\n await entry._module.clearRoutes();\n await entry._module.clearWaypoints();\n }\n }\n};\n\n// Polygon overlays come from three slices: cached place-footprint polygons on places entries,\n// isochrone polygons + origin pins on ranges entries (hideEntry clears both modules and resets\n// `_shown`), and derived custom-geometries entries. Sequential awaits — Promise.all here can\n// race the SDK/MapLibre.\nconst clearGeometries = async (state: ToolState): Promise<void> => {\n await state.places.clearShownGeometries();\n for (const entryId of state.ranges.shownEntryIds) {\n await state.ranges.hideEntry(entryId);\n }\n for (const entryId of state.customGeometries.shownEntryIds) {\n await state.customGeometries.hideEntry(entryId);\n }\n};\n\nconst clearAnalytics = async (state: ToolState): Promise<void> => {\n const module = state.trafficAreaAnalytics.trafficAreaAnalyticsModule;\n if (module) await module.clear();\n};\n\n// Per-entry modules now — hide every entry that's currently rendered. Each entry's module\n// clears itself on hide. Entries stay in history; this is a map-only clear. Sequential\n// awaits — concurrent hide/clear calls race the SDK/MapLibre state machine.\nconst clearIncidents = async (state: ToolState): Promise<void> => {\n for (const id of state.trafficIncidents.shownEntryIds) {\n await state.trafficIncidents.hideEntry(id);\n }\n};\n\n/**\n * Execute clear map.\n */\nexport const executeClearMap = async (params: z.infer<typeof clearMapSchema>, state: ToolState) => {\n const { layers } = params;\n try {\n const shouldClear = (layer: ClearLayer): boolean => !layers || layers.length === 0 || layers.includes(layer);\n\n if (shouldClear('places')) await clearPlaces(state);\n if (shouldClear('routes')) await clearRoutes(state);\n if (shouldClear('geometries')) await clearGeometries(state);\n if (shouldClear('analytics')) await clearAnalytics(state);\n if (shouldClear('incidents')) await clearIncidents(state);\n\n return {\n success: true,\n cleared: layers || [...CLEAR_LAYERS],\n };\n } catch (error) {\n return {\n error: `Failed to clear map: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n getSectionBBox,\n type Route,\n type SectionProps,\n type SectionType,\n sectionTypes,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the fit-route-section tool. */\nexport const fitRouteSectionOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fitting route section bounds.\n */\nexport const fitRouteSectionSchema = z.object({\n sectionType: z.enum(sectionTypes as [SectionType, ...SectionType[]]).describe('e.g. country|traffic|motorway|toll'),\n id: z.string().describe('From a shown route section'),\n padding: z.number().optional().describe('pixels, default: 50'),\n});\n\nexport const fitRouteSectionDescription =\n 'Fit the camera to a specific route section by type + ID (from getShownRouteSections). Requires a route on the map.';\n\n/**\n * Execute fit route section.\n */\nexport const executeFitRouteSection = async (params: z.infer<typeof fitRouteSectionSchema>, state: ToolState) => {\n const { sectionType, id, padding = 50 } = params;\n\n try {\n // Get the currently shown routes from the map\n if (!state.routing.currentEntryModule) {\n return {\n error: 'No routing module initialized - no routes shown on map',\n };\n }\n\n const shown = state.routing.currentEntryModule.getShown();\n\n if (!shown.mainLines || shown.mainLines.features.length === 0) {\n return {\n error: 'No routes are currently displayed on the map. Please show a route first before fitting to a section.',\n };\n }\n\n const shownRoutes = shown.mainLines.features;\n\n // Find the section in the shown routes\n let targetRoute: Route | undefined;\n let targetSection: { id: string; startPointIndex: number; endPointIndex: number } | undefined;\n\n for (const route of shownRoutes) {\n const sections = route.properties.sections[sectionType as SectionType];\n if (sections && Array.isArray(sections)) {\n const foundSection = sections.find((s) => s.id === id);\n if (\n foundSection &&\n typeof foundSection.startPointIndex === 'number' &&\n typeof foundSection.endPointIndex === 'number'\n ) {\n targetSection = foundSection as {\n id: string;\n startPointIndex: number;\n endPointIndex: number;\n };\n targetRoute = route;\n break;\n }\n }\n }\n\n if (!targetRoute || !targetSection) {\n return {\n error: `Could not find a section with type \"${sectionType}\" and id \"${id}\" in any displayed route. Check the section type and id are correct.`,\n };\n }\n\n // Calculate the bbox for the section\n const bbox = getSectionBBox(targetRoute, targetSection as SectionProps);\n\n if (!bbox) {\n return {\n error: 'Could not calculate bounding box for the section. The section may have invalid indices.',\n };\n }\n\n // Fit the map to the section bbox\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding });\n\n return { success: true };\n } catch (error) {\n return {\n error: `Failed to fit route section: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-incidents tool. */\nexport const getShownIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n trafficIncidents: z.array(\n z.object({\n id: z.string(),\n category: z.string(),\n magnitudeOfDelay: z.string().describe('unknown|minor|moderate|major|indefinite'),\n description: z.string().optional(),\n delayInSeconds: z.number().optional(),\n roadCategory: z.string().optional(),\n averageSpeedKmph: z.number().optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown traffic incidents on map.\n */\nexport const getShownIncidentsSchema = z.object({});\n\nexport const getShownIncidentsDescription =\n 'Get the real-time traffic incidents currently visible in the map viewport (not from service, but what is actually rendered on the map).';\n\n/**\n * Execute get shown incidents.\n */\nexport const executeGetShownIncidents = async (\n _params: z.infer<typeof getShownIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownIncidentsOutputSchema>> => {\n try {\n const trafficIncidentsModule = await state.trafficTiles.getTrafficIncidentsModule();\n const shown = trafficIncidentsModule.getShown();\n\n if (shown.trafficIncidents.length === 0) {\n return { error: 'No traffic incidents currently shown on the map' };\n }\n\n return {\n count: shown.trafficIncidents.length,\n trafficIncidents: shown.trafficIncidents.map((incident) => ({\n id: incident.properties.id,\n category: incident.properties.category,\n magnitudeOfDelay: incident.properties.magnitudeOfDelay,\n description: incident.properties.description,\n delayInSeconds: incident.properties.delayInSeconds,\n roadCategory: incident.properties.roadCategory,\n averageSpeedKmph: incident.properties.averageSpeedKmph,\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get shown incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-places tool. */\nexport const getShownPlacesOutputSchema = z.union([\n z.object({\n count: z.number().describe('Total feature count across all shown entries.'),\n entries: z.array(\n z.object({\n id: z.string(),\n label: z.string(),\n featureCount: z.number(),\n markerType: z.enum(['pin', 'base-map']).describe('How the entry is rendered on the map.'),\n features: z.array(\n z.object({\n name: z.string().optional(),\n address: z.string().optional(),\n position: z.array(z.number()).describe('[lng, lat]'),\n }),\n ),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown places on map.\n */\nexport const getShownPlacesSchema = z.object({});\n\nexport const getShownPlacesDescription =\n 'List the place entries currently shown on the map, grouped by `placesEntryId` with labels and a feature preview. ' +\n 'Use to check which categories are visible before calling `updatePlacesDisplay`, or when the user asks \"what is on the map\". ' +\n 'Positions are [longitude, latitude].';\n\nconst FEATURE_PREVIEW_SIZE = 5;\n\n/**\n * Execute get shown places.\n */\nexport const executeGetShownPlaces = async (_params: z.infer<typeof getShownPlacesSchema>, state: ToolState) => {\n try {\n const shownIds = state.places.shownEntryIds;\n if (shownIds.size === 0) {\n return { count: 0, entries: [] };\n }\n\n const entries = state.places.entries\n .filter((entry) => shownIds.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n label: entry.label,\n featureCount: entry.places.length,\n markerType: state.places.getShownMarkerType(entry.id) ?? 'pin',\n features: entry.places.slice(0, FEATURE_PREVIEW_SIZE).map((feature) => ({\n name: feature.properties.poi?.name,\n address: feature.properties.address?.freeformAddress,\n position: feature.geometry.coordinates,\n })),\n }));\n\n return {\n count: entries.reduce((total, entry) => total + entry.featureCount, 0),\n entries,\n };\n } catch (error) {\n return {\n error: `Failed to get shown places: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { SectionsProps, SectionType } from '@tomtom-org/maps-sdk/core';\nimport { sectionTypes } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-route-sections tool. */\nexport const getShownRouteSectionsOutputSchema = z.union([\n z.object({\n sectionType: z.string(),\n routes: z.array(\n z.object({\n routeIndex: z.number().describe('0=primary, 1+=alternative'),\n sections: z.array(z.unknown()).describe('startPointIndex/endPointIndex omitted'),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the get-shown-route-sections tool. */\nexport const getShownRouteSectionsSchema = z.object({\n sectionType: z\n .enum(sectionTypes as [SectionType, ...SectionType[]])\n .describe(\n 'carpool|carTrain|country|ferry|importantRoadStretch|lanes|leg|lowEmissionZone|motorway|pedestrian|roadShields|speedLimit|toll|tollVignette|traffic|tunnel|unpaved|urban|vehicleRestricted',\n ),\n});\n\nexport const getShownRouteSectionsDescription =\n 'Get sections of a specific type (e.g. toll, motorway, country, traffic) from the currently shown routes. Returns section data without coordinate point indices.';\n\n/**\n * Execute get shown route sections.\n */\nexport const executeGetShownRouteSections = async (\n params: z.infer<typeof getShownRouteSectionsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownRouteSectionsOutputSchema>> => {\n const { sectionType } = params;\n try {\n if (!state.routing.currentEntryModule) {\n return { error: 'No routing module initialized - no routes shown on map' };\n }\n\n const shown = state.routing.currentEntryModule.getShown();\n\n if (!shown.mainLines || shown.mainLines.features.length === 0) {\n return { error: 'No routes currently shown on the map' };\n }\n\n const routes = shown.mainLines.features\n .map((route) => {\n const sectionArray = (route.properties.sections as SectionsProps)[sectionType as keyof SectionsProps];\n\n if (!sectionArray || sectionArray.length === 0) return null;\n\n return {\n routeIndex: route.properties.index,\n sections: sectionArray.map((section) => {\n const {\n startPointIndex: _s,\n endPointIndex: _e,\n ...rest\n } = section as Record<string, unknown> & {\n startPointIndex?: unknown;\n endPointIndex?: unknown;\n };\n return rest;\n }),\n };\n })\n .filter((r) => r !== null);\n\n if (routes.length === 0) {\n return { error: `No \"${sectionType}\" sections found on any currently shown route` };\n }\n\n return { sectionType, routes };\n } catch (error) {\n return {\n error: `Failed to get shown route sections: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-route-traffic-incidents tool. */\nexport const getShownRouteTrafficIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n trafficIncidents: z.array(\n z.object({\n id: z.string(),\n magnitudeOfDelay: z.string().describe('unknown|minor|moderate|major|indefinite'),\n title: z.string().optional(),\n delayInSeconds: z.number().optional(),\n effectiveSpeedInKmh: z.number().optional(),\n categories: z.array(z.string()).optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown route traffic incidents on map.\n */\nexport const getShownRouteTrafficIncidentsSchema = z.object({});\n\nexport const getShownRouteTrafficIncidentsDescription =\n 'Get the route traffic incidents currently displayed on the map (not from service, but what is actually shown). Each section has an id.';\n\n/**\n * Execute get shown route traffic incidents.\n */\nexport const executeGetShownRouteTrafficIncidents = async (\n _params: z.infer<typeof getShownRouteTrafficIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownRouteTrafficIncidentsOutputSchema>> => {\n try {\n if (!state.routing.currentEntryModule) {\n return { error: 'No routing module initialized - no route traffic incidents shown on map' };\n }\n\n const shown = state.routing.currentEntryModule.getShown();\n\n if (!shown.incidents || shown.incidents.features.length === 0) {\n return { error: 'No route traffic incidents currently shown on the map' };\n }\n\n return {\n count: shown.incidents.features.length,\n trafficIncidents: shown.incidents.features.map((incident) => ({\n id: incident.properties.id,\n magnitudeOfDelay: incident.properties.magnitudeOfDelay,\n title: incident.properties.title,\n delayInSeconds: incident.properties.delayInSeconds,\n effectiveSpeedInKmh: incident.properties.effectiveSpeedInKmh,\n categories: incident.properties.categories,\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get shown route traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeRoutes } from '../../utils';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-routes tool. */\nexport const getShownRoutesOutputSchema = z.union([routesOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for getting shown routes on map.\n */\nexport const getShownRoutesSchema = z.object({});\n\nexport const getShownRoutesDescription =\n 'Return routes currently rendered on the map (reads map state, not history). Use after updateRoutesDisplay.';\n\n/**\n * Execute get shown routes.\n */\nexport const executeGetShownRoutes = async (\n _params: z.infer<typeof getShownRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownRoutesOutputSchema>> => {\n try {\n if (!state.routing.currentEntryModule) {\n return { count: 0, routes: [] };\n }\n\n const shown = state.routing.currentEntryModule.getShown();\n\n if (!shown.mainLines || shown.mainLines.features.length === 0) {\n return { count: 0, routes: [] };\n }\n\n return summarizeRoutes(shown.mainLines);\n } catch (error) {\n return {\n error: `Failed to get shown routes: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-shown-waypoints tool. */\nexport const getShownWaypointsOutputSchema = z.union([\n z.object({\n count: z.number(),\n waypoints: z.array(\n z.object({\n name: z.unknown().optional(),\n position: z.array(z.number()).describe('[lng, lat]'),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting shown waypoints on map.\n */\nexport const getShownWaypointsSchema = z.object({});\n\nexport const getShownWaypointsDescription =\n 'Get origin/stop/destination waypoints shown on the map. Context-dependent: requires waypoints shown on the map. ' +\n 'Returns positions as [longitude, latitude].';\n\n/**\n * Execute get shown waypoints.\n */\nexport const executeGetShownWaypoints = async (\n _params: z.infer<typeof getShownWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownWaypointsOutputSchema>> => {\n try {\n // Read off the currently-shown entry's RoutingModule (per-entry now).\n // No shown entry → no waypoints, no need to lazy-init anything.\n const module = state.routing.currentEntryModule;\n if (!module) return { error: 'No waypoints currently shown on the map' };\n const shown = module.getShown();\n\n if (!shown.waypoints || shown.waypoints.features.length === 0) {\n return { error: 'No waypoints currently shown on the map' };\n }\n\n return {\n count: shown.waypoints.features.length,\n waypoints: shown.waypoints.features.map((w) => ({\n name: w.properties.name,\n position: w.geometry.coordinates,\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get shown waypoints: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-standard-map-styles tool. */\nexport const getStandardMapStylesOutputSchema = z.union([\n z.object({\n styles: z.array(z.string()).readonly(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting standard map styles.\n */\nexport const getStandardMapStylesSchema = z.object({});\n\nexport const getStandardMapStylesDescription =\n 'Get the list of available standard map style IDs. If you need more fine styling control, refer to getMapStyleLayers.';\n\n/**\n * Execute get standard map styles.\n */\nexport const executeGetStandardMapStyles = async (\n _params: z.infer<typeof getStandardMapStylesSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getStandardMapStylesOutputSchema>> => {\n try {\n return {\n styles: standardStyleIDs,\n };\n } catch (error) {\n return {\n error: `Failed to get standard map styles: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { Language, TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the set-language tool. */\nexport const setLanguageOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n language: z.string(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting language.\n */\nexport const setLanguageSchema = z.object({\n language: z.string().describe('e.g. \"en-US\", \"fr-FR\"'),\n});\n\nexport const setLanguageDescription =\n 'Set the language for map labels and all subsequent service API responses (search, geocoding, routing instructions).';\n\n/**\n * Execute set language.\n */\nexport const executeSetLanguage = async (params: z.infer<typeof setLanguageSchema>, state: ToolState) => {\n const { language } = params;\n try {\n TomTomConfig.instance.put({ language: language as Language });\n state.baseMap.ttMap.setLanguage(language as Language);\n\n return {\n success: true,\n language,\n };\n } catch (error) {\n return {\n error: `Failed to set language: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the setMapStandardStyle tool. */\nexport const setMapStandardStyleOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n style: z.string(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting a standard map style.\n */\nexport const setMapStandardStyleSchema = z.object({\n style: z.enum(standardStyleIDs).describe('Map visual theme'),\n});\n\nexport const setMapStandardStyleDescription =\n 'Set the map style to a standard preset (light, dark, mono, driving, satellite, etc). For detailed or incremental style tweaking use getMapStyleLayers, setLayoutProperties, and setPaintProperties.';\n\n/**\n * Execute set map standard style.\n */\nexport const executeSetMapStandardStyle = async (\n params: z.infer<typeof setMapStandardStyleSchema>,\n state: ToolState,\n) => {\n const { style } = params;\n try {\n state.baseMap.ttMap.setStyle(style);\n\n return {\n success: true,\n style,\n };\n } catch (error) {\n return {\n error: `Failed to set map style: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the setRouteTheme tool. */\nexport const setRouteThemeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n theme: z.object({\n mainColor: z.string().optional(),\n }),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting route theme.\n */\nexport const setRouteThemeSchema = z.object({\n mainColor: z\n .string()\n .optional()\n .describe(\n 'CSS color for the route line and waypoint icons. Prefer CSS named colors when they match (e.g. \"peachpuff\", \"coral\", \"steelblue\"), otherwise use hex (e.g. \"#FF0000\").',\n ),\n});\n\nexport const setRouteThemeDescription =\n 'Color the route line + waypoint icons consistently across all route layers (preferred over setPaintProperties for route styling). Requires a route on the map.';\n\nconst currentTheme: { mainColor?: string } = {};\n\n/**\n * Execute set route theme.\n */\nexport const executeSetRouteTheme = async (params: z.infer<typeof setRouteThemeSchema>, state: ToolState) => {\n try {\n if (params.mainColor !== undefined) currentTheme.mainColor = params.mainColor;\n\n // RoutingModules are now per-entry. Apply the theme to whichever\n // entry is currently rendering — that's the one the user sees. If\n // nothing is on the map, surface a clear error so the LLM knows to\n // call updateRoutesDisplay first.\n const routingModule = state.routing.currentEntryModule;\n if (!routingModule) {\n return { error: 'No route currently shown — call updateRoutesDisplay or setRoute first.' };\n }\n routingModule.applyConfig({\n theme: {\n mainColor: currentTheme.mainColor,\n },\n });\n\n return {\n success: true as const,\n theme: { ...currentTheme },\n };\n } catch (error) {\n return {\n error: `Failed to set route theme: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Tool schema for showing traffic area analytics on the map.\n */\nexport const showTrafficAreaAnalyticsSchema = z.object({\n mode: z\n .enum(['hexgrid-3d', 'hexgrid-2d', 'square-3d', 'square-2d', 'heatmap'])\n .optional()\n .describe(\n \"Visualization mode. 'hexgrid-3d' (default): 3D hexagons. 'hexgrid-2d': flat hexagons. 'square-3d': 3D squares. 'square-2d': flat squares. 'heatmap': density layer.\",\n ),\n metric: z\n .enum(['congestionLevel', 'speed', 'travelTime'])\n .optional()\n .describe('Metric to visualize. Defaults to first fetched metric.'),\n colorTheme: z\n .enum(['trafficLight', 'heat', 'monochrome', 'viridis', 'plasma'])\n .optional()\n .describe(\n \"Preset color theme. 'trafficLight' (green→amber→red, default), 'heat' (blue→orange→red), 'monochrome' (grey), 'viridis' (purple→green→yellow), 'plasma' (purple→magenta→yellow).\",\n ),\n heightScale: z\n .number()\n .optional()\n .describe('Extrusion height multiplier / visual maximum in meters. Default: metric-dependent.'),\n scaleMode: z\n .enum(['raw', 'predefinedRange', 'currentRange'])\n .optional()\n .describe(\n \"Height scaling mode. 'raw': metric value × scale (default). 'predefinedRange': normalize to SDK predefined range so max = scale. 'currentRange': normalize to live data range so max = scale.\",\n ),\n filter: z\n .object({\n min: z.number().optional(),\n max: z.number().optional(),\n })\n .refine((filter) => filter.min !== undefined || filter.max !== undefined, {\n message: 'Filter must have at least one of min or max',\n })\n .optional()\n .describe('Filter visible tiles for the active metric by value range.'),\n visible: z.boolean().optional().describe('Set to false to clear the visualization. Default: true.'),\n fitBounds: z.boolean().optional().describe('Fit map viewport to the analytics area. Default: true.'),\n});\n\n/** Output schema for the show-traffic-area-analytics tool. */\nexport const showTrafficAreaAnalyticsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n mode: z.string(),\n metric: z.string(),\n colorTheme: z.string(),\n visible: z.boolean(),\n filtered: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const showTrafficAreaAnalyticsDescription =\n 'Render HISTORICAL traffic analytics (hexgrid/heatmap) on the map after getTrafficAreaAnalytics. ' +\n 'Configure metric, color theme, height, and tile filtering; defaults to the first fetched metric. ' +\n 'Not real-time — use toggleTrafficFlow for live traffic.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Execute show traffic area analytics.\n */\nexport const executeShowTrafficAreaAnalytics = async (\n params: z.infer<typeof showTrafficAreaAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof showTrafficAreaAnalyticsOutputSchema>> => {\n try {\n // Get stored analytics result\n const analyticsResult = state.trafficAreaAnalytics.lastAreaAnalytics;\n if (!analyticsResult) {\n return { error: 'No analytics data available. Call getTrafficAreaAnalytics first to fetch data.' };\n }\n\n // Default metric to first fetched metric\n const defaultMetric = analyticsResult.properties?.metrics?.[0] ?? 'congestionLevel';\n\n const {\n mode = 'hexgrid-3d',\n metric = defaultMetric,\n colorTheme = 'trafficLight',\n heightScale,\n scaleMode,\n filter,\n visible = true,\n fitBounds: shouldFitBounds = true,\n } = params;\n\n // Lazy-init TrafficAreaAnalyticsModule\n const analyticsModule = await state.trafficAreaAnalytics.getTrafficAreaAnalyticsModule();\n\n if (!visible) {\n await analyticsModule.clear();\n state.trafficAreaAnalytics.notifyAnalyticsCleared();\n return { success: true, mode, metric, colorTheme, visible: false, filtered: false };\n }\n\n // Clear previous visualization before showing new data\n await analyticsModule.clear();\n await analyticsModule.show(analyticsResult);\n\n // Apply mode and active metric\n analyticsModule.setMode(mode);\n analyticsModule.setMetric(metric);\n\n // Apply color theme\n analyticsModule.setColor(colorTheme);\n\n // Apply height config\n if (heightScale !== undefined || scaleMode !== undefined) {\n analyticsModule.setHeight({\n ...(heightScale !== undefined && { maxHeightMeters: heightScale }),\n ...(scaleMode !== undefined && { scaleMode }),\n });\n }\n\n // Apply tile filter for active metric\n const isFiltered = !!filter;\n if (filter) {\n analyticsModule.filter({ min: filter.min, max: filter.max });\n } else {\n analyticsModule.clearFilter();\n }\n\n // Fit map to analytics area\n if (shouldFitBounds) {\n const bbox = bboxFromGeoJSON(analyticsResult.features);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\n }\n\n state.trafficAreaAnalytics.notifyAnalyticsShown(analyticsResult, analyticsModule);\n\n return {\n success: true,\n mode,\n metric,\n colorTheme,\n visible: true,\n filtered: isFiltered,\n };\n } catch (error) {\n return {\n error: `Failed to show traffic area analytics: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the show-waypoints tool. */\nexport const showWaypointsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n count: z.number(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for showing waypoints on the map.\n */\nexport const showWaypointsSchema = z.object({});\n\nexport const showWaypointsDescription =\n 'Display origin/stop/destination markers (no route line; no recalculation). Requires staged waypoints from setRoute or locatePlace. ' +\n 'Use updateRoutesDisplay when you also need the route line drawn.';\n\n/**\n * Execute show waypoints.\n */\nexport const executeShowWaypoints = async (\n _params: z.infer<typeof showWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof showWaypointsOutputSchema>> => {\n try {\n // Prefer the current (possibly sparse) waypoints being assembled;\n // fall back to the last finalized waypoints from route history.\n const current = state.routing.planningSlots;\n\n // RoutingModules are per-entry now. Render planning waypoints on the\n // first currently-shown entry, or fall back to the latest entry in\n // history. If there's no history at all, surface a clear error.\n const firstShownId = [...state.routing.shownEntryIds][0];\n const latestEntry = state.routing.entries.at(-1);\n const targetEntryId = firstShownId ?? latestEntry?.id;\n if (!targetEntryId) {\n return { error: 'No route history yet — calculate a route via setRoute first.' };\n }\n const routingModule = await state.routing.getEntryRoutingModule(targetEntryId);\n await routingModule.showWaypoints(current);\n\n return { success: true, count: current.length };\n } catch (error) {\n return {\n error: `Failed to show waypoints: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { baseMapLayerGroupNames } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the toggle-base-map-layer-groups tool. */\nexport const toggleBaseMapLayerGroupsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n visible: z.boolean(),\n layerGroups: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for toggling base map layer groups.\n */\nexport const toggleBaseMapLayerGroupsSchema = z.object({\n visible: z.boolean(),\n layerGroups: z.array(z.enum(baseMapLayerGroupNames)).describe('The base map layer groups to show or hide.'),\n});\n\nexport const toggleBaseMapLayerGroupsDescription =\n 'Show or hide specific base map layer groups (water, buildings3D, roadLines, placeLabels, capitalLabels, etc.)';\n\n/**\n * Execute toggle base map layer groups.\n */\nexport const executeToggleBaseMapLayerGroups = async (\n params: z.infer<typeof toggleBaseMapLayerGroupsSchema>,\n state: ToolState,\n) => {\n const { visible, layerGroups } = params;\n try {\n // Lazy-init BaseMapModule\n const baseMapModule = await state.baseMap.getBaseMapModule();\n\n baseMapModule.setVisible(visible, {\n layerGroups: {\n mode: 'include',\n names: layerGroups,\n },\n });\n\n return {\n success: true,\n visible,\n layerGroups,\n };\n } catch (error) {\n return {\n error: `Failed to toggle layer groups: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the toggle-pois tool. */\nexport const togglePOIsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n allMapPOIsVisible: z.boolean().optional(),\n filterCategories: z\n .object({\n show: z.enum(['only', 'all_except']),\n values: z.array(z.string()),\n })\n .optional(),\n reset: z.boolean().optional(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for toggling POIs.\n */\nexport const togglePOIsSchema = z.object({\n reset: z\n .boolean()\n .optional()\n .describe(\n 'Set to true to reset all POI configuration to defaults (visibility and category filters). When true, all other parameters are ignored.',\n ),\n allMapPOIsVisible: z\n .boolean()\n .optional()\n .describe(\n 'Set the overall visibility of all built-in map POI icons. True to show all POIs, false to hide all POIs.',\n ),\n filterCategories: z\n .object({\n show: z\n .enum(['only', 'all_except'])\n .describe(\n '\"only\": show POIs only from the given categories. \"all_except\": show all POIs except those in the given categories.',\n ),\n values: z\n .array(z.string())\n .describe(\n 'MapStylePOICategory or POICategoryGroup values (e.g. \"RESTAURANT\", \"FOOD_DRINKS_GROUP\"). Overly specific codes like \"ITALIAN_RESTAURANT\" are not supported.',\n ),\n })\n .optional()\n .describe('Filter which built-in map POI categories are shown.'),\n});\n\nexport const togglePOIsDescription =\n 'Show/hide built-in map POI icons (restaurants, hotels, gas stations, etc.). ' +\n '`allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts which categories show; `reset: true` restores defaults.';\n\n/**\n * Execute toggle POIs.\n */\nexport const executeTogglePOIs = async (\n params: z.infer<typeof togglePOIsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof togglePOIsOutputSchema>> => {\n try {\n const poisModule = await state.mapPOIs.getPOIsModule();\n\n if (params.reset) {\n poisModule.resetConfig();\n return { success: true, reset: true };\n }\n\n const { allMapPOIsVisible, filterCategories } = params;\n\n if (allMapPOIsVisible !== undefined) poisModule.setVisible(allMapPOIsVisible);\n if (filterCategories) {\n poisModule.filterCategories({ show: filterCategories.show, values: filterCategories.values });\n }\n\n return { success: true, allMapPOIsVisible, filterCategories };\n } catch (error) {\n return {\n error: `Failed to toggle POIs: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Tool schema for toggling traffic flow.\n */\nexport const toggleTrafficFlowSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-traffic-flow tool. */\nexport const toggleTrafficFlowOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficFlowVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for toggling traffic incidents.\n */\nexport const toggleTrafficIncidentsSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-traffic-incidents tool. */\nexport const toggleTrafficIncidentsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficIncidentsVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const toggleTrafficFlowDescription =\n 'Toggle the REAL-TIME traffic flow layer — colored road segments showing live speed conditions. ' +\n 'This is NOT historical analytics; use showTrafficAreaAnalytics for historical data.';\n\nexport const toggleTrafficIncidentsDescription =\n 'Toggle REAL-TIME traffic incident markers on the map (jams, accidents, closures, roadworks). ' +\n 'This is NOT historical analytics. Use getTrafficIncidents for incident details.';\n\n/**\n * Execute toggle traffic flow.\n */\nexport const executeToggleTrafficFlow = async (params: z.infer<typeof toggleTrafficFlowSchema>, state: ToolState) => {\n const { visible } = params;\n try {\n // Lazy-init TrafficFlowModule\n const trafficFlowModule = await state.trafficTiles.getTrafficFlowModule();\n\n trafficFlowModule.setVisible(visible);\n\n return {\n success: true,\n trafficFlowVisible: visible,\n };\n } catch (error) {\n return {\n error: `Failed to toggle traffic flow: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n/**\n * Execute toggle traffic incidents.\n */\nexport const executeToggleTrafficIncidents = async (\n params: z.infer<typeof toggleTrafficIncidentsSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n // Lazy-init TrafficIncidentsModule\n const trafficIncidentsModule = await state.trafficTiles.getTrafficIncidentsModule();\n\n trafficIncidentsModule.setVisible(visible);\n\n return {\n success: true,\n trafficIncidentsVisible: visible,\n };\n } catch (error) {\n return {\n error: `Failed to toggle traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { geometryDisplayConfigSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\ntype GeometryDisplayConfig = z.infer<typeof geometryDisplayConfigSchema>;\ntype GeometrySkip = { placeId: string; reason: string };\n\n// Renders boundary polygons for the given entries via each entry's GeometriesModule.\n// Place ids outside the targeted entries are ignored — see `geometryDisplayConfigSchema`.\nconst applyGeometryDisplay = async (\n state: ToolState,\n entryIds: readonly string[],\n config: GeometryDisplayConfig,\n): Promise<{ rendered: number; skipped: GeometrySkip[] }> => {\n const theme = config.theme ?? 'outline';\n const mode = config.mode ?? 'replace';\n const placeIdFilter = config.placeIds ? new Set(config.placeIds) : undefined;\n\n const skipped: GeometrySkip[] = [];\n let rendered = 0;\n\n for (const entryId of entryIds) {\n const entry = state.places.entries.find((e) => e.id === entryId);\n if (!entry) continue;\n\n const targetPlaces = placeIdFilter ? entry.places.filter((p) => placeIdFilter.has(p.id)) : entry.places;\n\n const features = [];\n for (const place of targetPlaces) {\n if (!place.properties.dataSources?.geometry?.id) {\n skipped.push({\n placeId: place.id,\n reason: 'Place has no geometry data source (e.g. addresses and streets often lack one).',\n });\n continue;\n }\n try {\n const feature = await state.places.fetchPlaceGeometry(place.id);\n if (!feature) {\n skipped.push({\n placeId: place.id,\n reason: 'Geometry Data service returned no feature for this place.',\n });\n continue;\n }\n features.push(feature);\n } catch (error) {\n skipped.push({\n placeId: place.id,\n reason: `Geometry fetch failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n }\n\n if (features.length > 0) {\n await state.places.showPlaceGeometries(features, theme, mode, entryId);\n rendered += features.length;\n }\n }\n\n return { rendered, skipped };\n};\n\nconst shownEntrySummarySchema = z.object({\n id: z.string(),\n label: z.string(),\n featureCount: z.number(),\n markerType: z.enum(['pin', 'base-map', 'pin-clustered']).describe('How the entry is rendered on the map.'),\n});\n\nexport const updatePlacesDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z.array(shownEntrySummarySchema).describe('Entries currently on the map, with their labels.'),\n actuallyAdded: z\n .array(z.string())\n .describe('Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op.'),\n actuallyRemoved: z\n .array(z.string())\n .describe('Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op.'),\n geometriesRendered: z\n .number()\n .optional()\n .describe('Number of boundary polygons rendered when `geometry` was set.'),\n geometriesSkipped: z\n .array(z.object({ placeId: z.string(), reason: z.string() }))\n .optional()\n .describe('Places whose boundary could not be rendered (no geometry data source, fetch failure).'),\n }),\n toolErrorSchema,\n]);\n\nexport const updatePlacesDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe(\n 'Entry ids to show (e.g. [\"places-1\"]). ' +\n 'Omit while still setting `markerType` or `geometry` to target the most recently stored entry.',\n ),\n remove: z.array(z.string()).optional().describe('Entry ids to hide.'),\n addMatching: z.array(z.string()).optional().describe('Category labels to show.'),\n removeMatching: z.array(z.string()).optional().describe('Category labels to hide.'),\n clear: z.boolean().optional().describe('Hide every displayed entry.'),\n markerType: z\n .enum(['pin', 'base-map', 'pin-clustered'])\n .optional()\n .describe(\n 'How newly-added entries are rendered as markers: \"pin\", \"base-map\", or \"pin-clustered\" (pins with a count badge ' +\n 'where nearby ones aggregate). ' +\n 'Defaults to \"pin\" unless `geometry` is set without an explicit `markerType`, in which case markers are skipped. ' +\n 'Entries previously shown with another marker type are moved to this one.',\n ),\n geometry: geometryDisplayConfigSchema.optional(),\n fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.',\n ),\n })\n .refine(\n (data) =>\n data.add !== undefined ||\n data.remove !== undefined ||\n data.addMatching !== undefined ||\n data.removeMatching !== undefined ||\n data.clear === true ||\n data.markerType !== undefined ||\n data.geometry !== undefined,\n {\n message:\n 'updatePlacesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, markerType, geometry.',\n },\n );\n\nexport const updatePlacesDisplayDescription =\n 'Show, hide, and swap which places entries are displayed on the map in one atomic call. ' +\n 'Target entries by id (`add` / `remove`) or by category label (`addMatching` / `removeMatching`). ' +\n '`clear` hides every currently-shown entry; combine with `add` to swap the display (\"hide everything, show these\"). ' +\n 'Markers (`markerType`) and boundary polygons (`geometry`) are independent display channels — set either or both for the targeted entries. ' +\n 'Omit `add`/`addMatching` while still setting `markerType` or `geometry` to target the most recently stored entry. ' +\n 'Does not delete from history.';\n\n/** Resolve category terms to entry ids by case-insensitive substring match on the entry label. */\nconst resolveMatchingIds = (state: ToolState, terms: readonly string[]): string[] => {\n if (terms.length === 0) return [];\n const needles = terms.map((term) => term.toLowerCase());\n const matched = new Set<string>();\n for (const entry of state.places.entries) {\n const label = entry.label.toLowerCase();\n if (needles.some((needle) => label.includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\n// When markerType is implicit, leave entries that are already shown alone — otherwise an\n// \"add cafes\" call would demote a previously-clustered entry to pin. Explicit markerType\n// is treated as an intentional override and applies to every id.\nconst applyMarkerAdds = async (\n state: ToolState,\n addIds: readonly string[],\n markerType: 'pin' | 'base-map' | 'pin-clustered' | undefined,\n beforeShown: ReadonlySet<string>,\n): Promise<void> => {\n const markerIds = markerType !== undefined ? addIds : addIds.filter((id) => !beforeShown.has(id));\n if (markerIds.length > 0) {\n await state.places.addShownEntries(markerIds, markerType ?? 'pin');\n }\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\n// Resolves the user-provided selectors into the concrete entry-id sets we'll act on. Handles\n// label-matching, the implicit \"latest entry\" fallback when only a display signal is set, and\n// the unknown-id check that gates the rest of the executor.\nconst resolveSelectors = (state: ToolState, params: z.infer<typeof updatePlacesDisplaySchema>): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, markerType, geometry } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n const implicitLatest =\n add === undefined && addMatching === undefined && (markerType !== undefined || geometry !== undefined);\n if (implicitLatest) {\n const latestId = state.places.entries.at(-1)?.id;\n if (!latestId) {\n return {\n error: 'No places available to display. Resolve a place or run a search first (e.g. discoverPlaces).',\n };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.places.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown places entry ids: ${unknownAdd.join(', ')}. Use recallPlaces to list available entries.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\nexport const executeUpdatePlacesDisplay = async (\n params: z.infer<typeof updatePlacesDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updatePlacesDisplayOutputSchema>> => {\n const { clear, markerType, geometry, fitBounds } = params;\n try {\n const selectors = resolveSelectors(state, params);\n if ('error' in selectors) return selectors;\n const { add: resolvedAdd, remove: resolvedRemove } = selectors;\n\n // Snapshot the shown set BEFORE mutation so we can report diffs accurately.\n const beforeShown = new Set(state.places.shownEntryIds);\n\n if (clear) {\n await state.places.clearShownEntries();\n await state.places.clearShownGeometries();\n }\n if (resolvedRemove.length > 0) {\n await state.places.removeShownEntries(resolvedRemove);\n await state.places.clearShownGeometries(resolvedRemove);\n }\n // Markers default to 'pin' unless `geometry` was set with no explicit markerType,\n // in which case the user wants boundaries only.\n const renderMarkers = markerType !== undefined || !geometry;\n if (resolvedAdd.length > 0 && renderMarkers) {\n await applyMarkerAdds(state, resolvedAdd, markerType, beforeShown);\n }\n\n let geometryResult: { rendered: number; skipped: GeometrySkip[] } | undefined;\n if (geometry && resolvedAdd.length > 0) {\n geometryResult = await applyGeometryDisplay(state, resolvedAdd, geometry);\n }\n\n const afterShown = state.places.shownEntryIds;\n const actuallyAdded = resolvedAdd.filter((id) => !beforeShown.has(id) && afterShown.has(id));\n const actuallyRemoved = resolvedRemove.filter((id) => beforeShown.has(id) && !afterShown.has(id));\n\n const shouldFit = fitBounds ?? resolvedAdd.length > 0;\n const shownFeatures = state.places.getShownFeatures();\n if (shouldFit && shownFeatures.length > 0) {\n const bbox = bboxFromGeoJSON(shownFeatures);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox, { padding: 50 });\n }\n }\n\n const shownWithLabels = state.places.entries\n .filter((entry) => afterShown.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n label: entry.label,\n featureCount: entry.places.length,\n markerType: state.places.getShownMarkerType(entry.id) ?? 'pin',\n }));\n\n return {\n success: true as const,\n totalShown: shownFeatures.length,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n ...(geometryResult && { geometriesRendered: geometryResult.rendered }),\n ...(geometryResult?.skipped.length && { geometriesSkipped: geometryResult.skipped }),\n };\n } catch (error) {\n return {\n error: `updatePlacesDisplay failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type Routes } from '@tomtom-org/maps-sdk/core';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst shownRouteEntrySummarySchema = z.object({\n id: z.string(),\n label: z.string(),\n routeCount: z.number().describe('Number of route alternatives in the entry.'),\n selectedIndex: z.number().describe('Highlighted alternative index (0 unless overridden by this call).'),\n});\n\nexport const updateRoutesDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z\n .array(shownRouteEntrySummarySchema)\n .describe('Routes entries currently on the map, with their labels.'),\n actuallyAdded: z\n .array(z.string())\n .describe('Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op.'),\n actuallyRemoved: z\n .array(z.string())\n .describe('Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op.'),\n }),\n toolErrorSchema,\n]);\n\nexport const updateRoutesDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe(\n 'Entry ids to show (e.g. [\"routes-1\"]). ' +\n 'Omit while still setting `selectedIndex` to target the most recently stored entry.',\n ),\n remove: z.array(z.string()).optional().describe('Entry ids to hide.'),\n addMatching: z.array(z.string()).optional().describe('Label substrings to show.'),\n removeMatching: z.array(z.string()).optional().describe('Label substrings to hide.'),\n clear: z.boolean().optional().describe('Hide every displayed entry.'),\n selectedIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\n 'Which alternative variant to highlight on newly-added entries (default: 0). ' +\n 'Use to switch the selected alternative on a multi-alternative result. ' +\n 'Applied after the entry is shown — entries previously rendered with another index are re-rendered.',\n ),\n fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.',\n ),\n })\n .refine(\n (data) =>\n data.add !== undefined ||\n data.remove !== undefined ||\n data.addMatching !== undefined ||\n data.removeMatching !== undefined ||\n data.clear === true ||\n data.selectedIndex !== undefined,\n {\n message:\n 'updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex.',\n },\n );\n\nexport const updateRoutesDisplayDescription =\n 'Show, hide, and swap which routes entries are displayed on the map in one atomic call. ' +\n 'Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). ' +\n '`clear` hides every currently-shown entry; combine with `add` to swap the display (\"hide everything, show this\"). ' +\n '`selectedIndex` re-highlights an alternative variant on the newly-added entries. ' +\n 'Omit `add`/`addMatching` while still setting `selectedIndex` to target the most recently stored entry. ' +\n 'Does not delete from history. Does not call any service.';\n\nconst resolveMatchingIds = (state: ToolState, terms: readonly string[]): string[] => {\n if (terms.length === 0) return [];\n const needles = terms.map((term) => term.toLowerCase());\n const matched = new Set<string>();\n for (const entry of state.routing.entries) {\n const label = entry.label.toLowerCase();\n if (needles.some((needle) => label.includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\n// Resolves user-provided selectors into concrete entry-id sets. Handles label-matching, the\n// implicit \"latest entry\" fallback when only `selectedIndex` is set, and the unknown-id check\n// that gates the rest of the executor.\nconst resolveSelectors = (state: ToolState, params: z.infer<typeof updateRoutesDisplaySchema>): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, selectedIndex } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n const implicitLatest = add === undefined && addMatching === undefined && selectedIndex !== undefined;\n if (implicitLatest) {\n const latestId = state.routing.entries.at(-1)?.id;\n if (!latestId) {\n return { error: 'No routes available to display. Calculate a route first via setRoute.' };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.routing.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown routes entry ids: ${unknownAdd.join(', ')}. Use recallRoutes to list available entries.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\n// `state.routing.showEntry` always renders with selectedIndex 0. For a non-zero selection we\n// re-render directly on the entry's module after the show, mirroring the previous show-routes\n// behavior. Done sequentially per the SDK/MapLibre race constraints.\nconst applySelectedIndex = async (\n state: ToolState,\n entryIds: readonly string[],\n selectedIndex: number,\n): Promise<void> => {\n if (selectedIndex === 0) return;\n for (const id of entryIds) {\n const entry = state.routing.entries.find((e) => e.id === id);\n if (!entry) continue;\n const module = await state.routing.getEntryRoutingModule(id);\n await module.showRoutes(entry.data, { selectedIndex });\n if (entry.waypoints.length) await module.showWaypoints(entry.waypoints);\n }\n};\n\nexport const executeUpdateRoutesDisplay = async (\n params: z.infer<typeof updateRoutesDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateRoutesDisplayOutputSchema>> => {\n const { clear, selectedIndex, fitBounds } = params;\n try {\n const selectors = resolveSelectors(state, params);\n if ('error' in selectors) return selectors;\n const { add: resolvedAdd, remove: resolvedRemove } = selectors;\n\n const beforeShown = new Set(state.routing.shownEntryIds);\n\n if (clear) {\n for (const id of [...state.routing.shownEntryIds]) {\n await state.routing.hideEntry(id);\n }\n }\n for (const id of resolvedRemove) {\n await state.routing.hideEntry(id);\n }\n for (const id of resolvedAdd) {\n await state.routing.showEntry(id);\n }\n if (selectedIndex !== undefined) {\n await applySelectedIndex(state, resolvedAdd, selectedIndex);\n }\n\n const afterShown = state.routing.shownEntryIds;\n const actuallyAdded = resolvedAdd.filter((id) => !beforeShown.has(id) && afterShown.has(id));\n const actuallyRemoved = resolvedRemove.filter((id) => beforeShown.has(id) && !afterShown.has(id));\n\n const shouldFit = fitBounds ?? resolvedAdd.length > 0;\n if (shouldFit) {\n const features: Routes['features'] = [];\n for (const entry of state.routing.entries) {\n if (afterShown.has(entry.id)) features.push(...entry.data.features);\n }\n if (features.length > 0) {\n const bbox = bboxFromGeoJSON({ type: 'FeatureCollection', features });\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50 });\n }\n }\n }\n\n const shownWithLabels = state.routing.entries\n .filter((entry) => afterShown.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n label: entry.label,\n routeCount: entry.data.features.length,\n selectedIndex: actuallyAdded.includes(entry.id) ? (selectedIndex ?? 0) : 0,\n }));\n\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n };\n } catch (error) {\n return {\n error: `updateRoutesDisplay failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { ToolDefinition } from '../types';\n\n// --- MapLibre tools ---\nimport {\n executeExecuteMaplibreCode,\n executeFlyTo,\n executeGetMapStyleLayers,\n executeGetViewport,\n executeMaplibreCodeDescription,\n executeMaplibreCodeOutputSchema,\n executeMaplibreCodeSchema,\n executeSetLayoutProperties,\n executeSetPaintProperties,\n executeSetPitchBearing,\n executeZoomInOrOut,\n flyToDescription,\n flyToOutputSchema,\n flyToSchema,\n getMapStyleLayersDescription,\n getMapStyleLayersOutputSchema,\n getMapStyleLayersSchema,\n getViewportDescription,\n getViewportOutputSchema,\n getViewportSchema,\n setLayoutPropertiesDescription,\n setLayoutPropertiesOutputSchema,\n setLayoutPropertiesSchema,\n setPaintPropertiesDescription,\n setPaintPropertiesOutputSchema,\n setPaintPropertiesSchema,\n setPitchBearingDescription,\n setPitchBearingOutputSchema,\n setPitchBearingSchema,\n zoomInOrOutDescription,\n zoomInOrOutOutputSchema,\n zoomInOrOutSchema,\n} from './maplibre';\n\n// --- Service tools ---\nimport {\n addWaypointsToRouteDescription,\n addWaypointsToRouteOutputSchema,\n addWaypointsToRouteSchema,\n discoverPlacesBuilder,\n executeAddWaypointsToRoute,\n executeFindReachableAreas,\n executeGetPoiCategoryCodes,\n executeGetTrafficAreaAnalytics,\n executeGetTrafficIncidents,\n executeLocatePlace,\n executeRemoveWaypointsFromRoute,\n executeReplaceWaypointInRoute,\n executeReverseGeocode,\n executeSetRoute,\n findReachableAreasDescription,\n findReachableAreasOutputSchema,\n findReachableAreasSchema,\n getPoiCategoryCodesDescription,\n getPoiCategoryCodesOutputSchema,\n getPoiCategoryCodesSchema,\n getTrafficAreaAnalyticsDescription,\n getTrafficAreaAnalyticsOutputSchema,\n getTrafficAreaAnalyticsSchema,\n getTrafficIncidentsDescription,\n getTrafficIncidentsOutputSchema,\n getTrafficIncidentsSchema,\n locatePlaceDescription,\n locatePlaceOutputSchema,\n locatePlaceSchema,\n removeWaypointsFromRouteDescription,\n removeWaypointsFromRouteOutputSchema,\n removeWaypointsFromRouteSchema,\n replaceWaypointInRouteDescription,\n replaceWaypointInRouteOutputSchema,\n replaceWaypointInRouteSchema,\n reverseGeocodeDescription,\n reverseGeocodeOutputSchema,\n reverseGeocodeSchema,\n setRouteDescription,\n setRouteOutputSchema,\n setRouteSchema,\n} from './services';\n\n// --- State tools ---\nimport {\n analyseGeometriesDescription,\n analyseGeometriesOutputSchema,\n analyseGeometriesSchema,\n analyseIncidentsDescription,\n analyseIncidentsOutputSchema,\n analyseIncidentsSchema,\n analysePlacesDescription,\n analysePlacesOutputSchema,\n analysePlacesSchema,\n analyseRoutesDescription,\n analyseRoutesOutputSchema,\n analyseRoutesSchema,\n executeAnalyseGeometries,\n executeAnalyseIncidents,\n executeAnalysePlaces,\n executeAnalyseRoutes,\n executeFocusIncidents,\n executeGetCurrentWaypoints,\n executeProcessGeometries,\n executeProcessPlaces,\n executeProcessRoutes,\n executeQueryTrafficAnalytics,\n executeRecallGeometries,\n executeRecallPlaces,\n executeRecallRanges,\n executeRecallRoutes,\n executeRecallState,\n executeResetState,\n executeSetEntryMode,\n executeStartTrafficIncidentsMonitor,\n executeStopTrafficIncidentsMonitor,\n focusIncidentsDescription,\n focusIncidentsOutputSchema,\n focusIncidentsSchema,\n getCurrentWaypointsDescription,\n getCurrentWaypointsOutputSchema,\n getCurrentWaypointsSchema,\n processGeometriesDescription,\n processGeometriesOutputSchema,\n processGeometriesSchema,\n processPlacesDescription,\n processPlacesOutputSchema,\n processPlacesSchema,\n processRoutesDescription,\n processRoutesOutputSchema,\n processRoutesSchema,\n queryTrafficAnalyticsDescription,\n queryTrafficAnalyticsOutputSchema,\n queryTrafficAnalyticsSchema,\n recallGeometriesDescription,\n recallGeometriesOutputSchema,\n recallGeometriesSchema,\n recallPlacesDescription,\n recallPlacesOutputSchema,\n recallPlacesSchema,\n recallRangesDescription,\n recallRangesOutputSchema,\n recallRangesSchema,\n recallRoutesDescription,\n recallRoutesOutputSchema,\n recallRoutesSchema,\n recallStateDescription,\n recallStateOutputSchema,\n recallStateSchema,\n resetStateDescription,\n resetStateOutputSchema,\n resetStateSchema,\n setEntryModeDescription,\n setEntryModeOutputSchema,\n setEntryModeSchema,\n startTrafficIncidentsMonitorDescription,\n startTrafficIncidentsMonitorOutputSchema,\n startTrafficIncidentsMonitorSchema,\n stopTrafficIncidentsMonitorDescription,\n stopTrafficIncidentsMonitorOutputSchema,\n stopTrafficIncidentsMonitorSchema,\n} from './state';\n\n// --- TomTom Map tools ---\nimport {\n clearMapDescription,\n clearMapOutputSchema,\n clearMapSchema,\n executeClearMap,\n executeGetShownIncidents,\n executeGetShownPlaces,\n executeGetShownRoutes,\n executeGetShownRouteTrafficIncidents,\n executeGetShownWaypoints,\n executeGetStandardMapStyles,\n executeSetLanguage,\n executeSetMapStandardStyle,\n executeSetRouteTheme,\n executeShowTrafficAreaAnalytics,\n executeShowWaypoints,\n executeToggleBaseMapLayerGroups,\n executeTogglePOIs,\n executeToggleTrafficFlow,\n executeToggleTrafficIncidents,\n executeUpdatePlacesDisplay,\n executeUpdateRoutesDisplay,\n getShownIncidentsDescription,\n getShownIncidentsOutputSchema,\n getShownIncidentsSchema,\n getShownPlacesDescription,\n getShownPlacesOutputSchema,\n getShownPlacesSchema,\n getShownRoutesDescription,\n getShownRoutesOutputSchema,\n getShownRoutesSchema,\n getShownRouteTrafficIncidentsDescription,\n getShownRouteTrafficIncidentsOutputSchema,\n getShownRouteTrafficIncidentsSchema,\n getShownWaypointsDescription,\n getShownWaypointsOutputSchema,\n getShownWaypointsSchema,\n getStandardMapStylesDescription,\n getStandardMapStylesOutputSchema,\n getStandardMapStylesSchema,\n setLanguageDescription,\n setLanguageOutputSchema,\n setLanguageSchema,\n setMapStandardStyleDescription,\n setMapStandardStyleOutputSchema,\n setMapStandardStyleSchema,\n setRouteThemeDescription,\n setRouteThemeOutputSchema,\n setRouteThemeSchema,\n showTrafficAreaAnalyticsDescription,\n showTrafficAreaAnalyticsOutputSchema,\n showTrafficAreaAnalyticsSchema,\n showWaypointsDescription,\n showWaypointsOutputSchema,\n showWaypointsSchema,\n toggleBaseMapLayerGroupsDescription,\n toggleBaseMapLayerGroupsOutputSchema,\n toggleBaseMapLayerGroupsSchema,\n togglePOIsDescription,\n togglePOIsOutputSchema,\n togglePOIsSchema,\n toggleTrafficFlowDescription,\n toggleTrafficFlowOutputSchema,\n toggleTrafficFlowSchema,\n toggleTrafficIncidentsDescription,\n toggleTrafficIncidentsOutputSchema,\n toggleTrafficIncidentsSchema,\n updatePlacesDisplayDescription,\n updatePlacesDisplayOutputSchema,\n updatePlacesDisplaySchema,\n updateRoutesDisplayDescription,\n updateRoutesDisplayOutputSchema,\n updateRoutesDisplaySchema,\n} from './tomtom-map';\n\n// --- Utility tools ---\nimport {\n calculateBBoxDescription,\n calculateBBoxSchema,\n executeCalculateBBox,\n executeFormatDistance,\n executeFormatDuration,\n executeGetCurrentLocation,\n formatDistanceDescription,\n formatDistanceSchema,\n formatDurationDescription,\n formatDurationSchema,\n getCurrentLocationDescription,\n getCurrentLocationOutputSchema,\n getCurrentLocationSchema,\n helpDescription,\n helpOutputSchema,\n helpSchema,\n} from './utilities';\n\nconst defaultTools = {\n locatePlace: {\n description: locatePlaceDescription,\n classificationPrompt:\n 'Resolve a single location string (landmark, city, address) to a place without showing it; optionally stages a waypoint slot. ' +\n 'Do NOT call as a precursor to discoverPlaces — discoverPlaces resolves named areas itself via `where.query` (or `where.municipalities` / `where.placeId`).',\n inputSchema: locatePlaceSchema,\n outputSchema: locatePlaceOutputSchema,\n execute: executeLocatePlace,\n tags: ['locate', 'place', 'location', 'waypoint'],\n examples: [\n 'locatePlace(\"Buckingham Palace\", queryAs: \"poi\", where: { mode: \"within\", viewport: true }, waypointIndex: 0)',\n 'locatePlace(\"Tower Bridge\", queryAs: \"poi\", where: { mode: \"nearby\", viewport: true }, waypointIndex: 1)',\n 'locatePlace(\"Westminster Abbey\", queryAs: \"poi\", where: { mode: \"global\" })',\n 'locatePlace(\"10 Downing Street\", queryAs: \"place\", where: { mode: \"nearby\", position: [4.9, 52.37] })',\n 'locatePlace(\"Amsterdam\", queryAs: \"place\", where: { mode: \"global\" }, entryId: \"amsterdam-city\", geometry: { show: true }) // outline the city',\n 'locatePlace(\"Utrecht\", queryAs: \"place\", where: { mode: \"global\" }, entryId: \"utrecht-city\", geometry: { show: true, mode: \"add\" }) // also outline a second city',\n ],\n examplePrompts: [\n 'Where is the Louvre?',\n 'Pin Central Station on the map',\n 'What are the coordinates of Times Square?',\n 'Set my origin to Schiphol Airport',\n 'Outline Amsterdam on the map',\n 'Show the boundary of Berlin',\n ],\n relatedTools: ['setRoute', 'addWaypointsToRoute', 'getCurrentWaypoints', 'recallPlaces'],\n },\n reverseGeocode: {\n description: reverseGeocodeDescription,\n classificationPrompt:\n 'Convert [lng, lat] coordinates to an address; use after map clicks or any tool that returns a position.',\n inputSchema: reverseGeocodeSchema,\n outputSchema: reverseGeocodeOutputSchema,\n execute: executeReverseGeocode,\n tags: ['location', 'place'],\n examples: ['reverseGeocode({ position: [4.9, 52.4] })'],\n examplePrompts: [\n \"What's the address at this point?\",\n 'Which city is [4.9, 52.4] in?',\n 'Name the place where I clicked',\n ],\n relatedTools: ['getViewport', 'getCurrentLocation', 'updatePlacesDisplay', 'locatePlace'],\n },\n discoverPlaces: discoverPlacesBuilder,\n processPlaces: {\n description: processPlacesDescription,\n classificationPrompt:\n 'Run dynamic JS over existing places to filter/merge/subset/re-order and/or compute connections between them. Code returns `{ places, placeConnections? }`.',\n inputSchema: processPlacesSchema,\n outputSchema: processPlacesOutputSchema,\n execute: executeProcessPlaces,\n tags: ['place', 'location', 'discover', 'connections'],\n examples: [\n 'processPlaces({ placesEntryIDs: [\"places-2\"], code: \"return { places: { type: \\'FeatureCollection\\', features: places.features.filter(p => (p.properties.poi?.name ?? \\'\\').toLowerCase().includes(\\'vegan\\')) } };\", label: \"vegan only\" })',\n 'processPlaces({ placesEntryIDs: [\"places-1\"], code: \"const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };\" })',\n 'processPlaces({ placesEntryIDs: [\"places-1\"], code: \"const f = places.features.filter(p => p.properties.poi?.categories?.includes(\\'CAFE\\')); return { places: { type: \\'FeatureCollection\\', features: f }, placeConnections: f.slice(1).map(p => ({ from: f[0], to: p })) };\", show: { markerType: \"pin\", zoomMode: \"auto\" } })',\n ],\n examplePrompts: [\n 'Keep only the vegan restaurants from the last search',\n 'Merge these two entries and drop non-POIs',\n 'Show these places in relation to each other',\n 'Draw lines from the charging station to every nearby cafe',\n 'Connect each hotel to its closest restaurant',\n 'Link every place in this set to the map center',\n ],\n relatedTools: ['discoverPlaces', 'recallPlaces', 'analysePlaces', 'updatePlacesDisplay'],\n dependsOn: ['discoverPlaces', 'recallPlaces'],\n },\n processGeometries: {\n description: processGeometriesDescription,\n classificationPrompt:\n 'Aggregate polygons (place footprints, isochrones, custom-entries) via dynamic JS (h3, turf) — union/intersect/buffer/h3 coverage. Output writes a NEW custom-geometries entry.',\n inputSchema: processGeometriesSchema,\n outputSchema: processGeometriesOutputSchema,\n execute: executeProcessGeometries,\n tags: ['geometry', 'boundary', 'aggregation', 'turf', 'h3'],\n examples: [\n 'processGeometries({ geometriesEntryIDs: [{ kind: \"place\", id: \"amsterdam-id\" }, { kind: \"place\", id: \"rotterdam-id\" }], code: \"return [turf.union(turf.featureCollection(geometries))].filter(Boolean);\", label: \"ams ∪ rdam\", operation: \"union\" })',\n 'processGeometries({ geometriesEntryIDs: [{ kind: \"place\", id: \"paris-id\" }], code: \"return [turf.buffer(geometries[0], 5, { units: \\'kilometers\\' })];\", label: \"paris +5km\", operation: \"buffer\" })',\n 'processGeometries({ geometriesEntryIDs: [{ kind: \"places\", id: \"cities\" }], code: \"const out = []; for (let i=0;i<geometries.length;i++) for (let j=i+1;j<geometries.length;j++) { const x = turf.intersect(turf.featureCollection([geometries[i], geometries[j]])); if (x) out.push({ ...x, id: `x-${i}-${j}` }); } return out;\", label: \"pairwise overlaps\" })',\n 'processGeometries({ geometriesEntryIDs: [{ kind: \"ranges\", id: \"ranges-0\" }, { kind: \"places\", id: \"ev-stations\" }], code: \"const u = geometries.length ? turf.union(turf.featureCollection(geometries)) : null; return u ? [u] : [];\", label: \"isochrone ∪ ev coverage\" })',\n 'processGeometries({ geometriesEntryIDs: [{ kind: \"place\", id: \"amsterdam-id\" }, { kind: \"ranges\", id: \"ranges-0\" }], code: \"const outer = geometries.filter(g => g.properties._source.kind === \\'place\\'); const inner = geometries.filter(g => g.properties._source.kind === \\'ranges\\'); const env = turf.union(turf.featureCollection(outer)); const u = turf.union(turf.featureCollection(inner)); const inv = (env && u) ? turf.difference(turf.featureCollection([env, u])) : env; return inv ? [{ ...inv, id: \\'unreachable\\' }] : [];\", label: \"amsterdam minus reachable\", operation: \"difference\", show: { theme: \"inverted\", hidePreviousEntries: \"all\" } }) // areas in Amsterdam NOT reachable from the hospitals — fetch Amsterdam via locatePlace first, then filter by `_source` in code',\n ],\n examplePrompts: [\n 'Union the boundaries of these cities',\n 'Buffer each neighbourhood by 500 m',\n 'Find the overlapping areas of these districts',\n 'Compute the h3 hex coverage of these places combined',\n 'Combine the reachable area with the EV stations into one zone',\n 'Show the areas of Amsterdam not reachable from the hospitals',\n ],\n relatedTools: ['recallGeometries', 'discoverPlaces', 'recallPlaces', 'analyseGeometries'],\n dependsOn: ['discoverPlaces', 'locatePlace', 'findReachableAreas', 'recallGeometries'],\n },\n processRoutes: {\n description: processRoutesDescription,\n classificationPrompt:\n 'Dynamic JS over routes → bbox via `routeUtils` → fit camera. Read-only, returns `{ fitOnMap }`. Use when the user wants to focus on a specific PART of a route (worst traffic, toll section, km-10-to-20 segment).',\n inputSchema: processRoutesSchema,\n outputSchema: processRoutesOutputSchema,\n execute: executeProcessRoutes,\n tags: ['route', 'utilities'],\n examples: [\n 'processRoutes({ code: \"const r = routes.features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };\" })',\n 'processRoutes({ code: \"const r = routes.features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };\" })',\n 'processRoutes({ code: \"const r = routes.features[0]; const a = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 10000 }); const b = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 20000 }); if (!a || !b) return { fitOnMap: turf.bbox(r) }; const fc = { type: \\'FeatureCollection\\', features: [turf.point(a.position), turf.point(b.position)] }; return { fitOnMap: { bbox: turf.bbox(fc), padding: 100 } };\" })',\n \"processRoutes({ placesEntryIDs: [\\\"ev-stations\\\"], code: \\\"const r = routes.features[0]; const ev = (placesByEntry?.['ev-stations']?.features ?? []).filter(p => turf.pointToLineDistance(p, r, { units: 'meters' }) <= 1000); if (ev.length === 0) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: { bbox: turf.bbox({ type: 'FeatureCollection', features: [...ev, r] }), padding: 80 } };\\\" })\",\n ],\n examplePrompts: [\n 'Zoom into the worst traffic on the route',\n 'Show me the toll section',\n 'Focus on the segment between km 10 and 20',\n 'Frame the part of the route inside France',\n 'Zoom to the first leg only',\n 'Focus on the route plus every EV station within 1 km of it',\n ],\n relatedTools: ['recallRoutes', 'analyseRoutes'],\n dependsOn: ['setRoute', 'recallRoutes', 'recallPlaces'],\n },\n analyseGeometries: {\n description: analyseGeometriesDescription,\n classificationPrompt:\n 'Compute aggregate stats / charts over polygons (place footprints, isochrones, custom-entries) via dynamic JS (h3, turf). Result attaches to each contributing places-entry / custom-entry as `_analysis[name]`. `outputFormat`: `json` or `chart`.',\n inputSchema: analyseGeometriesSchema,\n outputSchema: analyseGeometriesOutputSchema,\n execute: executeAnalyseGeometries,\n tags: ['geometry', 'boundary', 'analysis', 'turf', 'h3'],\n examples: [\n 'analyseGeometries({ geometriesEntryIDs: [{ kind: \"places\", id: \"cities\" }], name: \"areas\", code: \"return geometries.map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));\" })',\n 'analyseGeometries({ geometriesEntryIDs: [{ kind: \"place\", id: \"amsterdam\" }], name: \"h3-coverage-8\", code: \"const cells = new Set(); for (const f of geometries) for (const c of h3.polygonToCells(f.geometry.coordinates, 8)) cells.add(c); return { resolution: 8, cellCount: cells.size };\" })',\n 'analyseGeometries({ geometriesEntryIDs: [{ kind: \"custom\", id: \"bakery-candidates-gap-oost\" }], name: \"candidate-area\", code: \"return { totalKm2: +(geometries.reduce((a,f)=>a+turf.area(f),0)/1e6).toFixed(2) };\" })',\n 'analyseGeometries({ geometriesEntryIDs: [{ kind: \"places\", id: \"cities\" }], name: \"area-bar\", outputFormat: \"chart\", code: \"return { type: \\'bar\\', data: { labels: geometries.map(f => f.id), datasets: [{ label: \\'km²\\', data: geometries.map(f => +(turf.area(f)/1e6).toFixed(2)) }] } };\" })',\n ],\n examplePrompts: [\n 'How big are these areas?',\n 'h3 hex coverage of this neighbourhood',\n 'Bar chart of city areas',\n 'Combined bbox of these places',\n 'How big is the candidate zone we just produced?',\n ],\n relatedTools: ['recallGeometries', 'discoverPlaces', 'recallPlaces', 'processGeometries'],\n dependsOn: ['discoverPlaces', 'locatePlace', 'findReachableAreas', 'recallGeometries'],\n },\n analysePlaces: {\n description: analysePlacesDescription,\n classificationPrompt:\n 'Aggregate / summarise / chart existing places via dynamic JS (h3, turf available). `outputFormat`: `json` (text) or `chart` (Chart.js).',\n inputSchema: analysePlacesSchema,\n outputSchema: analysePlacesOutputSchema,\n execute: executeAnalysePlaces,\n tags: ['place', 'location', 'utilities'],\n examples: [\n 'analysePlaces({ placesEntryIDs: [\"places-2\"], name: \"by-category\", code: \"const counts = {}; for (const p of places.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; return { total: places.features.length, byCategory: counts };\" })',\n 'analysePlaces({ placesEntryIDs: [\"places-1\"], name: \"hex-density-8\", description: \"h3 resolution 8 density\", code: \"const bins = {}; for (const p of places.features) { const [lng,lat] = p.geometry.coordinates; const cell = h3.latLngToCell(lat, lng, 8); bins[cell] = (bins[cell] ?? 0) + 1; } return { resolution: 8, bins };\" })',\n 'analysePlaces({ placesEntryIDs: [\"places-0\", \"places-1\"], name: \"extent\", code: \"return { bbox: turf.bbox(places), centroid: turf.centroid(places).geometry.coordinates };\" })',\n 'analysePlaces({ name: \"top-categories-bar\", outputFormat: \"chart\", code: \"const counts = {}; for (const p of places.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]).slice(0,10); return { type: \\'bar\\', data: { labels: entries.map(e => e[0]), datasets: [{ label: \\'Places\\', data: entries.map(e => e[1]) }] }, options: { plugins: { title: { display: true, text: \\'Top categories\\' } } } };\" })',\n 'analysePlaces({ name: \"brand-share\", outputFormat: \"chart\", code: \"const counts = {}; for (const p of places.features) { const n = p.properties.poi?.brands?.[0]?.name ?? \\'other\\'; counts[n] = (counts[n] ?? 0) + 1; } return { type: \\'doughnut\\', data: { labels: Object.keys(counts), datasets: [{ data: Object.values(counts) }] } };\" })',\n ],\n examplePrompts: [\n 'How many of each POI category are in these results?',\n 'h3 hex density of these places at resolution 8',\n 'Bounding box and centroid of these places',\n 'Bar chart of the top POI categories',\n 'Pie chart of the brand distribution',\n 'Which municipality has the most of these places?',\n ],\n relatedTools: ['discoverPlaces', 'recallPlaces', 'processPlaces'],\n dependsOn: ['discoverPlaces', 'recallPlaces'],\n },\n analyseRoutes: {\n description: analyseRoutesDescription,\n classificationPrompt:\n 'Aggregate / summarise / chart existing routes via dynamic JS (h3, turf). Optionally pass `placesEntryIDs` to cross-reference routes with places (POIs near route, per leg/country). `outputFormat`: `json` or `chart`.',\n inputSchema: analyseRoutesSchema,\n outputSchema: analyseRoutesOutputSchema,\n execute: executeAnalyseRoutes,\n tags: ['route', 'utilities'],\n examples: [\n 'analyseRoutes({ name: \"summary\", code: \"return routes.features.map(r => ({ index: r.properties.index, distanceKm: r.properties.summary.lengthInMeters / 1000, durationMin: r.properties.summary.travelTimeInSeconds / 60, trafficDelaySec: r.properties.summary.trafficDelayInSeconds }));\" })',\n 'analyseRoutes({ name: \"delay-by-country\", code: \"const byCountry = {}; for (const r of routes.features) for (const c of (r.properties.sections.country ?? [])) byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (r.properties.sections.traffic ?? []).filter(t => t.startPointIndex >= c.startPointIndex && t.endPointIndex <= c.endPointIndex).reduce((s, t) => s + (t.delayInSeconds ?? 0), 0); return byCountry;\" })',\n 'analyseRoutes({ name: \"alternative-comparison\", outputFormat: \"chart\", code: \"const r = routes.features; return { type: \\'bar\\', data: { labels: r.map(x => `Route ${x.properties.index}`), datasets: [{ label: \\'Travel time (min)\\', data: r.map(x => Math.round(x.properties.summary.travelTimeInSeconds / 60)) }, { label: \\'Distance (km)\\', data: r.map(x => Math.round(x.properties.summary.lengthInMeters / 1000)) }] } };\" })',\n 'analyseRoutes({ routesEntryIDs: [\"routes-0\"], name: \"hex-coverage-7\", code: \"const cells = new Set(); for (const r of routes.features) for (const [lng, lat] of r.geometry.coordinates) cells.add(h3.latLngToCell(lat, lng, 7)); return { resolution: 7, cellCount: cells.size, cells: [...cells] };\" })',\n 'analyseRoutes({ placesEntryIDs: [\"ev-stations\"], name: \"ev-near-route-500m\", code: \"const route = routes.features[0]; const out = {}; for (const [id, places] of Object.entries(placesByEntry)) out[id] = places.features.filter(p => turf.pointToLineDistance(p, route, { units: \\'meters\\' }) <= 500).length; return { withinMeters: 500, byEntry: out };\" })',\n 'analyseRoutes({ placesEntryIDs: [\"cafes\", \"hotels\"], name: \"stops-per-leg\", code: \"const route = routes.features[0]; const corridor = turf.buffer(route, 0.3, { units: \\'kilometers\\' }); const legs = route.properties.sections.leg; return Object.fromEntries(Object.entries(placesByEntry).map(([id, fc]) => [id, legs.map((leg, i) => ({ leg: i, count: fc.features.filter(p => leg.startPointIndex != null && turf.booleanPointInPolygon(p, corridor)).length }))])); \" })',\n ],\n examplePrompts: [\n 'How long is each route alternative?',\n 'Compare the travel times of these route options',\n 'Bar chart of distance and time per alternative',\n 'Total traffic delay grouped by country',\n 'h3 hex coverage of the route at resolution 7',\n 'How many EV stations are within 500 m of the route?',\n 'Count cafes and hotels per leg of the route',\n 'Which POIs from the search are close to the route?',\n ],\n relatedTools: ['recallRoutes', 'recallPlaces', 'setRoute', 'processRoutes', 'discoverPlaces'],\n dependsOn: ['setRoute', 'recallRoutes', 'recallPlaces'],\n },\n setRoute: {\n description: setRouteDescription,\n classificationPrompt:\n 'Use to define a new route or change route waypoints/options. Pass `locations` for a new origin/stops/destination, `parameters` to update cost model / alternatives / when, or both together. Do not use to show, query, or modify an existing displayed route.',\n inputSchema: setRouteSchema,\n outputSchema: setRouteOutputSchema,\n execute: executeSetRoute,\n tags: ['route', 'waypoint', 'location'],\n examples: [\n 'setRoute({ locations: [{ query: \"Amsterdam\" }, { query: \"Brussels\" }] })',\n 'setRoute({ locations: [{ query: \"Eiffel Tower\" }, { query: \"Louvre Museum\" }, { query: \"Notre Dame\" }] })',\n 'setRoute({ parameters: { maxAlternatives: 2 } })',\n 'setRoute({ parameters: { costModel: { routeType: \"fast\", avoid: [\"tollRoads\"] } } })',\n 'setRoute({ parameters: { when: { option: \"departAt\", date: \"2025-06-01T09:00:00Z\" } } })',\n 'setRoute({ locations: [{ query: \"Paris\" }, { query: \"Lyon\" }], parameters: { costModel: { routeType: \"short\" } } })',\n ],\n examplePrompts: [\n 'Get directions from Paris to Lyon',\n 'Route me from Amsterdam to Brussels',\n 'Plan a new trip to the airport',\n 'Change my origin to Schiphol',\n 'Re-route from a different starting point',\n 'Avoid toll roads',\n 'Find the fastest route',\n 'Show 2 alternative routes',\n 'Depart at 9am',\n ],\n relatedTools: [\n 'updateRoutesDisplay',\n 'addWaypointsToRoute',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n 'recallRoutes',\n ],\n },\n addWaypointsToRoute: {\n description: addWaypointsToRouteDescription,\n classificationPrompt:\n 'Extend the route by prepending a new origin and/or appending a new destination (preserving the existing endpoints as stops), and/or inserting intermediate stops at optimal positions, then recalculate. Use setRoute when the user wants to fully replace waypoints.',\n inputSchema: addWaypointsToRouteSchema,\n outputSchema: addWaypointsToRouteOutputSchema,\n execute: executeAddWaypointsToRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'addWaypointsToRoute({ stops: [{ query: \"Westminster Abbey\" }] }) // single intermediate stop',\n 'addWaypointsToRoute({ stops: [{ query: \"Reims\" }, { query: \"Metz\" }] }) // multiple stops, settle into along-route order',\n 'addWaypointsToRoute({ origin: { query: \"my house\" } }) // prepend new origin; previous origin becomes a stop',\n 'addWaypointsToRoute({ destination: { query: \"Heathrow Airport\" } }) // append new destination; previous destination becomes a stop',\n 'addWaypointsToRoute({ origin: { query: \"Schiphol\" }, destination: { query: \"Brussels\" } }) // extend on both ends',\n 'addWaypointsToRoute({ origin: { query: \"X\" }, stops: [{ query: \"Y\" }] }) // combine endpoint extension and stops',\n ],\n examplePrompts: [\n 'Add a stop at Westminster Abbey',\n 'Add stops at Reims and Metz on the way',\n 'Pick me up from my house first, then continue the trip',\n 'Continue past the destination to Heathrow',\n ],\n relatedTools: [\n 'locatePlace',\n 'setRoute',\n 'updateRoutesDisplay',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n ],\n dependsOn: ['setRoute'],\n },\n removeWaypointsFromRoute: {\n description: removeWaypointsFromRouteDescription,\n classificationPrompt: 'Remove one or more waypoints by index from the current route and recalculate.',\n inputSchema: removeWaypointsFromRouteSchema,\n outputSchema: removeWaypointsFromRouteOutputSchema,\n execute: executeRemoveWaypointsFromRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'removeWaypointsFromRoute({ waypointIndices: [1] }) // remove a single stop',\n 'removeWaypointsFromRoute({ waypointIndices: [1, 2] }) // remove multiple stops in one call',\n ],\n examplePrompts: [\n 'Remove the second stop from my route',\n 'Delete the Utrecht stop',\n 'Drop the last two intermediate stops',\n ],\n relatedTools: ['addWaypointsToRoute', 'replaceWaypointInRoute', 'setRoute', 'updateRoutesDisplay'],\n dependsOn: ['setRoute'],\n },\n replaceWaypointInRoute: {\n description: replaceWaypointInRouteDescription,\n classificationPrompt:\n \"Overwrite a single waypoint of the current route in place — by 'origin', 'destination', or numeric index — and recalculate. Other waypoints are kept.\",\n inputSchema: replaceWaypointInRouteSchema,\n outputSchema: replaceWaypointInRouteOutputSchema,\n execute: executeReplaceWaypointInRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'replaceWaypointInRoute({ waypointIndex: \"origin\", location: { query: \"Schiphol\" } })',\n 'replaceWaypointInRoute({ waypointIndex: \"destination\", location: { query: \"Heathrow\" } })',\n 'replaceWaypointInRoute({ waypointIndex: 1, location: { query: \"Utrecht\" } }) // overwrite the first intermediate stop',\n ],\n examplePrompts: [\n 'Change the origin to Schiphol',\n 'Make Heathrow my destination',\n 'Replace the second stop with Utrecht',\n ],\n relatedTools: ['addWaypointsToRoute', 'removeWaypointsFromRoute', 'setRoute', 'locatePlace'],\n dependsOn: ['setRoute'],\n },\n getPOICategoryCodes: {\n description: getPoiCategoryCodesDescription,\n classificationPrompt:\n 'Resolve natural-language category names (e.g. \"gym\", \"italian food\") to POICategory codes for discoverPlaces.',\n inputSchema: getPoiCategoryCodesSchema,\n outputSchema: getPoiCategoryCodesOutputSchema,\n execute: executeGetPoiCategoryCodes,\n tags: ['discover', 'place'],\n examples: [\n 'getPOICategoryCodes()',\n 'getPOICategoryCodes(filters: [\"gym\"])',\n 'getPOICategoryCodes(filters: [\"italian restaurant\"]) // multi-word phrase, spaces removed before matching',\n 'getPOICategoryCodes(filters: [\"restaurant\"], language: \"fr-FR\")',\n ],\n examplePrompts: ['What POI categories are available?', \"What's the code for gym?\"],\n relatedTools: ['discoverPlaces', 'togglePOIs'],\n },\n getTrafficIncidents: {\n description: getTrafficIncidentsDescription,\n classificationPrompt:\n 'Loader for traffic incidents — caches features in state, renders on the map, returns `{count, entryId}` only. ' +\n 'MANDATORY: always also pick startTrafficIncidentsMonitor (no exceptions — they are a pair, data goes stale without it). ' +\n 'Also co-pick analyseIncidents (counts / charts / top-N / clusters) and focusIncidents (highlight a subset) when the query implies them.',\n inputSchema: getTrafficIncidentsSchema,\n outputSchema: getTrafficIncidentsOutputSchema,\n execute: executeGetTrafficIncidents,\n tags: ['traffic', 'location'],\n examples: [\n 'getTrafficIncidents({ bbox: [4.728, 52.278, 5.080, 52.479] })',\n 'getTrafficIncidents({ bbox: [...], categoryFilter: [\"accident\", \"jam\"] })',\n 'getTrafficIncidents({ bbox: [...], categoryFilter: [\"roadworks\"], timeValidityFilter: [\"future\"] })',\n ],\n examplePrompts: ['Fetch traffic incidents in Amsterdam', 'Any accidents in this area?'],\n relatedTools: [\n 'calculateBBox',\n 'getViewport',\n 'analyseIncidents',\n 'focusIncidents',\n 'startTrafficIncidentsMonitor',\n ],\n },\n analyseIncidents: {\n description: analyseIncidentsDescription,\n classificationPrompt:\n 'Aggregate / chart / cluster an incidents entry via dynamic JS (h3, turf). Re-runs on every monitor-tick; ' +\n '`previous` carries the prior result for trend reads. Pick for counts, group-bys, top-N, charts, density, clusters.',\n inputSchema: analyseIncidentsSchema,\n outputSchema: analyseIncidentsOutputSchema,\n execute: executeAnalyseIncidents,\n tags: ['traffic', 'incident', 'analytics', 'state'],\n examples: [\n 'analyseIncidents({ name: \\'by-category\\', code: \"const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; return { total: incidents.length, byCategory: c };\" })',\n 'analyseIncidents({ name: \\'top-delays\\', code: \"return [...incidents].sort((a,b) => (b.properties.delayInSeconds ?? 0) - (a.properties.delayInSeconds ?? 0)).slice(0,5).map(i => ({ id: i.properties.id, delay: i.properties.delayInSeconds, road: i.properties.roadNumbers?.[0] }));\" })',\n \"analyseIncidents({ name: 'category-bar', outputFormat: 'chart', code: \\\"const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; const e = Object.entries(c).sort((a,b) => b[1]-a[1]); return { type: 'bar', data: { labels: e.map(x => x[0]), datasets: [{ label: 'Incidents', data: e.map(x => x[1]) }] } };\\\" })\",\n ],\n examplePrompts: [\n 'How many incidents of each category?',\n 'Top 5 incidents by delay',\n 'Total delay grouped by road',\n 'Which jams are growing vs fading on this monitored area?',\n 'Bar chart of incidents per category',\n 'Hex density of incidents at resolution 8',\n ],\n relatedTools: ['getTrafficIncidents', 'focusIncidents'],\n dependsOn: ['getTrafficIncidents'],\n },\n focusIncidents: {\n description: focusIncidentsDescription,\n classificationPrompt:\n 'Highlight a subset of incidents on an existing entry (drives map dim/highlight + FocusChip). ' +\n 'Pick for \"focus on…\", \"show only…\", \"highlight the…\". Compose with analyseIncidents to compute the ids.',\n inputSchema: focusIncidentsSchema,\n outputSchema: focusIncidentsOutputSchema,\n execute: executeFocusIncidents,\n tags: ['traffic', 'incident', 'state', 'ui'],\n examples: [\n \"focusIncidents({ incidentsEntryID: 'incidents-0', incidentIds: ['TTI-...', 'TTI-...'], reason: 'top 3 by delay' })\",\n ],\n examplePrompts: [\n 'Focus on the 3 worst incidents by delay',\n 'Highlight the closures',\n 'Show only the incidents on the A4',\n ],\n relatedTools: ['analyseIncidents', 'getTrafficIncidents'],\n dependsOn: ['getTrafficIncidents'],\n },\n startTrafficIncidentsMonitor: {\n description: startTrafficIncidentsMonitorDescription,\n classificationPrompt:\n 'Polls an existing incidents entry on a fixed interval (default 60s); registered analyses re-run on each tick. ' +\n 'ALWAYS pick this whenever getTrafficIncidents is picked — they are a mandatory pair, no exceptions. ' +\n 'Without it the loaded data is a frozen snapshot. Also pick on its own when the user says ' +\n '\"live\"/\"watch\"/\"keep updated\"/\"refresh every N seconds\" against an already-loaded entry.',\n inputSchema: startTrafficIncidentsMonitorSchema,\n outputSchema: startTrafficIncidentsMonitorOutputSchema,\n execute: executeStartTrafficIncidentsMonitor,\n tags: ['traffic', 'incident', 'state'],\n examples: [\n \"startTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0' })\",\n \"startTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', intervalMs: 30000 })\",\n ],\n examplePrompts: [\n 'Keep this updated',\n 'Refresh the incidents every 30 seconds',\n 'Watch this area for new accidents',\n ],\n relatedTools: ['getTrafficIncidents', 'stopTrafficIncidentsMonitor', 'analyseIncidents'],\n dependsOn: ['getTrafficIncidents'],\n },\n stopTrafficIncidentsMonitor: {\n description: stopTrafficIncidentsMonitorDescription,\n classificationPrompt:\n 'Stop polling an incidents entry; the entry stays in state with its last data. ' +\n 'Pick for \"stop refreshing\"/\"pause updates\" or when ending a watch.',\n inputSchema: stopTrafficIncidentsMonitorSchema,\n outputSchema: stopTrafficIncidentsMonitorOutputSchema,\n execute: executeStopTrafficIncidentsMonitor,\n tags: ['traffic', 'incident', 'state'],\n examples: [\"stopTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0' })\"],\n examplePrompts: ['Stop refreshing', 'Pause updates for this area'],\n relatedTools: ['startTrafficIncidentsMonitor', 'getTrafficIncidents'],\n dependsOn: ['startTrafficIncidentsMonitor'],\n },\n getTrafficAreaAnalytics: {\n description: getTrafficAreaAnalyticsDescription,\n classificationPrompt:\n 'Fetch historical traffic analytics (speed, congestion, travel time) for an area. queryTrafficAnalytics drills down without re-fetching.',\n inputSchema: getTrafficAreaAnalyticsSchema,\n outputSchema: getTrafficAreaAnalyticsOutputSchema,\n execute: executeGetTrafficAreaAnalytics,\n tags: ['traffic', 'location'],\n examples: [\n \"getTrafficAreaAnalytics({ bbox: [4.728, 52.278, 5.080, 52.479], metrics: ['speed', 'congestionLevel'], startDate: '2025-03-01', endDate: '2025-03-07' })\",\n \"getTrafficAreaAnalytics({ bbox: [...], metrics: ['congestionLevel'], hours: [7,8,9,17,18] })\",\n ],\n examplePrompts: [\n 'What was the average congestion in Amsterdam last week?',\n 'Analyze traffic patterns during rush hour in this area',\n 'Show me historical speed data for the city center',\n ],\n relatedTools: ['queryTrafficAnalytics', 'showTrafficAreaAnalytics', 'calculateBBox', 'getViewport'],\n dependsOn: ['locatePlace'],\n },\n queryTrafficAnalytics: {\n description: queryTrafficAnalyticsDescription,\n classificationPrompt:\n 'Query cached traffic analytics data or check what metric/mode is currently displayed on the map.',\n inputSchema: queryTrafficAnalyticsSchema,\n outputSchema: queryTrafficAnalyticsOutputSchema,\n execute: executeQueryTrafficAnalytics,\n tags: ['traffic', 'location'],\n examples: [\n \"queryTrafficAnalytics({ granularity: 'daily' })\",\n \"queryTrafficAnalytics({ granularity: 'hourly', hourStart: 7, hourEnd: 9, metric: 'congestionLevel' })\",\n \"queryTrafficAnalytics({ granularity: 'average', dayOfWeek: [6, 7] })\",\n ],\n // NOTE: \"What am I looking at?\" awareness should be handled at a higher level\n // (e.g. system prompt or state summary injected into context), not per-tool examplePrompts.\n // Current approach works but doesn't scale — every stateful tool would need similar hints.\n examplePrompts: [\n 'Show me the daily breakdown',\n 'What was rush hour congestion?',\n 'Which day had the worst traffic?',\n 'What metric am I looking at?',\n 'What is currently shown on the map?',\n ],\n relatedTools: ['getTrafficAreaAnalytics', 'showTrafficAreaAnalytics'],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n updatePlacesDisplay: {\n description: updatePlacesDisplayDescription,\n classificationPrompt:\n 'Show, hide, and swap places entries on the map — add some (as pins and/or boundary polygons), remove others, swap atomically, or render boundaries.',\n inputSchema: updatePlacesDisplaySchema,\n outputSchema: updatePlacesDisplayOutputSchema,\n execute: executeUpdatePlacesDisplay,\n tags: ['place', 'location', 'map style', 'boundary', 'outline'],\n examples: [\n 'updatePlacesDisplay({ add: [\"places-2\"] })',\n 'updatePlacesDisplay({ add: [\"places-1\", \"places-3\"], markerType: \"base-map\" })',\n 'updatePlacesDisplay({ remove: [\"places-1\"] })',\n 'updatePlacesDisplay({ removeMatching: [\"cinema\"] }) // hide every entry labelled \"cinema\"',\n 'updatePlacesDisplay({ addMatching: [\"cafe\"] })',\n 'updatePlacesDisplay({ clear: true })',\n 'updatePlacesDisplay({ clear: true, add: [\"places-2\"] }) // atomic swap: hide everything, show this',\n 'updatePlacesDisplay({ markerType: \"pin\" }) // re-pin the most recently stored entry',\n 'updatePlacesDisplay({ geometry: {} }) // outline the most recently stored entry',\n 'updatePlacesDisplay({ add: [\"places-2\"], geometry: { mode: \"add\" } }) // also outline on top of existing boundaries',\n 'updatePlacesDisplay({ add: [\"places-2\"], markerType: \"pin\", geometry: { theme: \"outline\" } }) // pin AND outline',\n 'updatePlacesDisplay({ add: [\"places-2\"], geometry: { placeIds: [\"G55fc4abe-...\"], theme: \"outline\" } }) // outline a subset',\n ],\n examplePrompts: [\n 'Show the results on the map',\n 'Pin the search results',\n 'Hide the banks',\n 'Also show the cafes on the map',\n 'Additionally pin the pubs',\n 'Remove the banks from the map',\n 'Drop every entry labelled \"parking\" from the map',\n 'Toggle off the hotels without clearing the rest',\n 'Replace the pins with the new set',\n 'Show entry places-3 as base-map POIs',\n 'Show the boundary of Amsterdam on the map',\n 'Outline these neighbourhoods',\n 'Highlight the shape of this place',\n 'Pin the cities and outline them too',\n 'Also outline Utrecht while keeping Amsterdam drawn',\n 'Add the neighbourhood boundaries on top of the pins',\n ],\n relatedTools: ['discoverPlaces', 'locatePlace', 'reverseGeocode', 'recallPlaces', 'clearMap', 'flyTo'],\n dependsOn: ['discoverPlaces', 'locatePlace', 'processPlaces'],\n },\n updateRoutesDisplay: {\n description: updateRoutesDisplayDescription,\n classificationPrompt:\n 'Show, hide, and swap routes entries on the map — add some, remove others, swap atomically, or switch the highlighted alternative.',\n inputSchema: updateRoutesDisplaySchema,\n outputSchema: updateRoutesDisplayOutputSchema,\n execute: executeUpdateRoutesDisplay,\n tags: ['route', 'waypoint'],\n examples: [\n 'updateRoutesDisplay({ add: [\"routes-1\"] })',\n 'updateRoutesDisplay({ add: [\"routes-0\", \"routes-2\"] })',\n 'updateRoutesDisplay({ remove: [\"routes-1\"] })',\n 'updateRoutesDisplay({ removeMatching: [\"Brussels\"] }) // hide every entry whose label mentions \"Brussels\"',\n 'updateRoutesDisplay({ addMatching: [\"Edinburgh\"] })',\n 'updateRoutesDisplay({ clear: true })',\n 'updateRoutesDisplay({ clear: true, add: [\"routes-2\"] }) // atomic swap: hide everything, show this',\n 'updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route',\n 'updateRoutesDisplay({ add: [\"routes-0\"], selectedIndex: 2, fitBounds: false })',\n ],\n examplePrompts: [\n 'Draw the route on the map',\n 'Show the directions',\n 'Display the calculated route',\n 'Show the Edinburgh route from earlier',\n 'Also show the Brussels route',\n 'Hide the Lyon route',\n 'Switch to the second alternative',\n 'Highlight alternative 2',\n 'Replace what is shown with this route',\n ],\n relatedTools: ['setRoute', 'processRoutes', 'recallRoutes', 'clearMap'],\n dependsOn: ['setRoute'],\n },\n showTrafficAreaAnalytics: {\n description: showTrafficAreaAnalyticsDescription,\n classificationPrompt:\n 'Render traffic analytics as hexgrid, heatmap, or tiles with custom colors, filtering, height, and tooltip.',\n inputSchema: showTrafficAreaAnalyticsSchema,\n outputSchema: showTrafficAreaAnalyticsOutputSchema,\n execute: executeShowTrafficAreaAnalytics,\n tags: ['traffic', 'map style'],\n examples: [\n 'showTrafficAreaAnalytics()',\n \"showTrafficAreaAnalytics({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' })\",\n 'showTrafficAreaAnalytics({ filter: { min: 50 } })',\n \"showTrafficAreaAnalytics({ scaleMode: 'currentRange', heightScale: 200 })\",\n 'showTrafficAreaAnalytics({ visible: false })',\n ],\n examplePrompts: [\n 'Show it on the map',\n 'Visualize the congestion',\n 'Only show areas with congestion above 50%',\n 'Normalize height to the current data range',\n 'Switch to heatmap view',\n ],\n relatedTools: ['getTrafficAreaAnalytics', 'queryTrafficAnalytics', 'toggleTrafficFlow', 'clearMap'],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n showWaypoints: {\n description: showWaypointsDescription,\n classificationPrompt: 'Display staged origin/stop/destination markers without drawing the route line.',\n inputSchema: showWaypointsSchema,\n outputSchema: showWaypointsOutputSchema,\n execute: executeShowWaypoints,\n tags: ['waypoint', 'route'],\n examples: ['showWaypoints()'],\n examplePrompts: ['Show the waypoints on the map', 'Mark my route stops'],\n relatedTools: ['setRoute', 'updateRoutesDisplay', 'getShownWaypoints', 'clearMap'],\n dependsOn: ['setRoute', 'locatePlace'],\n },\n getShownPlaces: {\n description: getShownPlacesDescription,\n classificationPrompt:\n 'Return places currently displayed on the map (available after updatePlacesDisplay or locatePlace).',\n inputSchema: getShownPlacesSchema,\n outputSchema: getShownPlacesOutputSchema,\n execute: executeGetShownPlaces,\n tags: ['place', 'location'],\n examples: ['getShownPlaces()'],\n examplePrompts: [\n 'What places are on the map?',\n 'Which of my search results are currently displayed?',\n 'How many places are visible right now?',\n ],\n relatedTools: ['updatePlacesDisplay', 'clearMap', 'locatePlace', 'reverseGeocode'],\n dependsOn: ['updatePlacesDisplay'],\n },\n getShownRoutes: {\n description: getShownRoutesDescription,\n classificationPrompt: 'Return routes currently rendered on the map (available after updateRoutesDisplay).',\n inputSchema: getShownRoutesSchema,\n outputSchema: getShownRoutesOutputSchema,\n execute: executeGetShownRoutes,\n tags: ['route'],\n examples: ['getShownRoutes()'],\n examplePrompts: ['What routes are displayed?', 'Is there a route on the map?'],\n relatedTools: ['updateRoutesDisplay', 'clearMap'],\n dependsOn: ['updateRoutesDisplay'],\n },\n getShownWaypoints: {\n description: getShownWaypointsDescription,\n classificationPrompt:\n 'Return waypoint markers currently shown on the map (available after updateRoutesDisplay or showWaypoints).',\n inputSchema: getShownWaypointsSchema,\n outputSchema: getShownWaypointsOutputSchema,\n execute: executeGetShownWaypoints,\n tags: ['waypoint', 'route'],\n examples: ['getShownWaypoints()'],\n examplePrompts: ['What waypoints are shown?', 'List the stops on the map'],\n relatedTools: ['updateRoutesDisplay', 'clearMap'],\n dependsOn: ['updateRoutesDisplay', 'showWaypoints'],\n },\n getShownRouteTrafficIncidents: {\n description: getShownRouteTrafficIncidentsDescription,\n classificationPrompt: 'Return traffic incidents overlaid on the shown route.',\n inputSchema: getShownRouteTrafficIncidentsSchema,\n outputSchema: getShownRouteTrafficIncidentsOutputSchema,\n execute: executeGetShownRouteTrafficIncidents,\n tags: ['traffic', 'route'],\n examples: ['getShownRouteTrafficIncidents()'],\n examplePrompts: ['What traffic incidents are on my route?', 'Any delays along the route?'],\n relatedTools: ['updateRoutesDisplay', 'clearMap', 'setRoute'],\n dependsOn: ['updateRoutesDisplay'],\n },\n getShownIncidents: {\n description: getShownIncidentsDescription,\n classificationPrompt:\n 'Return real-time incidents currently visible in the map viewport (rendered on map, not fetched from service).',\n inputSchema: getShownIncidentsSchema,\n outputSchema: getShownIncidentsOutputSchema,\n execute: executeGetShownIncidents,\n tags: ['traffic', 'location'],\n examples: ['getShownIncidents()'],\n examplePrompts: ['What traffic incidents are visible?', 'Any incidents in the current view?'],\n relatedTools: ['toggleTrafficIncidents', 'flyTo', 'getViewport'],\n dependsOn: ['toggleTrafficIncidents'],\n },\n clearMap: {\n description: clearMapDescription,\n classificationPrompt: 'Remove displayed places, routes, or all features from the map.',\n inputSchema: clearMapSchema,\n outputSchema: clearMapOutputSchema,\n execute: executeClearMap,\n tags: ['place', 'route', 'map style'],\n examples: ['clearMap()', 'clearMap([\"places\"])', 'clearMap([\"places\", \"routes\"])'],\n examplePrompts: ['Clear the map', 'Remove all markers', 'Start fresh'],\n relatedTools: ['updatePlacesDisplay', 'updateRoutesDisplay'],\n },\n flyTo: {\n description: flyToDescription,\n classificationPrompt:\n 'Move the camera to a bounding box or specific position; use after locating places or computing bounds.',\n inputSchema: flyToSchema,\n outputSchema: flyToOutputSchema,\n execute: executeFlyTo,\n tags: ['location', 'map style'],\n examples: [\n 'flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5] } })',\n 'flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5], padding: 100 } })',\n 'flyTo({ where: { position: [4.9, 52.4], zoom: 14 } })',\n ],\n examplePrompts: ['Go to Berlin', 'Center the map on Rome', 'Fly to these coordinates'],\n relatedTools: ['getViewport', 'locatePlace', 'updatePlacesDisplay', 'updateRoutesDisplay', 'calculateBBox'],\n },\n getViewport: {\n description: getViewportDescription,\n classificationPrompt:\n 'Return the map center, zoom, and bounding box; use for area-relative queries like \"in this area\" or \"near the map center\".',\n inputSchema: getViewportSchema,\n outputSchema: getViewportOutputSchema,\n execute: executeGetViewport,\n tags: ['location', 'map style'],\n examples: ['getViewport()'],\n examplePrompts: [\n 'Where is the map centered?',\n \"What's the current map area?\",\n 'What are the current map bounds?',\n ],\n relatedTools: ['locatePlace', 'flyTo', 'discoverPlaces', 'reverseGeocode', 'getTrafficIncidents'],\n },\n recallGeometries: {\n description: recallGeometriesDescription,\n classificationPrompt:\n 'List/inspect every polygon source in state across places footprints, ranges isochrones, and custom-geometries entries. Returns tagged ids (`{ kind, id }`) the geometry tools accept.',\n inputSchema: recallGeometriesSchema,\n outputSchema: recallGeometriesOutputSchema,\n execute: executeRecallGeometries,\n tags: ['geometry', 'boundary'],\n examples: [\n 'recallGeometries()',\n 'recallGeometries({ id: { kind: \"places\", id: \"ev-stations-oost\" } })',\n 'recallGeometries({ id: { kind: \"custom\", id: \"bakery-candidates-gap-oost\" } })',\n ],\n examplePrompts: [\n 'What polygons are in state right now?',\n 'Which IDs can I pass to processGeometries?',\n 'Recall the custom zone we just produced',\n 'What does the bakery-candidates entry contain?',\n ],\n relatedTools: ['processGeometries', 'analyseGeometries', 'recallPlaces', 'recallRanges'],\n },\n recallPlaces: {\n description: recallPlacesDescription,\n classificationPrompt:\n 'List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processPlaces, analysePlaces, updatePlacesDisplay).',\n inputSchema: recallPlacesSchema,\n outputSchema: recallPlacesOutputSchema,\n execute: executeRecallPlaces,\n tags: ['place', 'location'],\n examples: ['recallPlaces()', 'recallPlaces({ id: \"places-0\" })'],\n examplePrompts: [\n 'What places did I search for?',\n 'List every places entry with its ID',\n 'Which IDs can I pass to processPlaces or updatePlacesDisplay?',\n 'Recall the bakeries I discovered earlier',\n 'What is in entry places-2?',\n ],\n relatedTools: ['discoverPlaces', 'locatePlace', 'reverseGeocode', 'updatePlacesDisplay'],\n dependsOn: ['locatePlace', 'discoverPlaces'],\n },\n recallRoutes: {\n description: recallRoutesDescription,\n classificationPrompt: 'Return previously calculated routes from this session.',\n inputSchema: recallRoutesSchema,\n outputSchema: recallRoutesOutputSchema,\n execute: executeRecallRoutes,\n tags: ['route'],\n examples: ['recallRoutes()', 'recallRoutes({ id: \"routes-0\" })'],\n examplePrompts: [\n 'What routes have I calculated?',\n 'Show the first route we planned',\n 'What was the route to Schiphol?',\n ],\n relatedTools: ['setRoute', 'updateRoutesDisplay', 'getCurrentWaypoints'],\n dependsOn: ['setRoute'],\n },\n getCurrentWaypoints: {\n description: getCurrentWaypointsDescription,\n classificationPrompt:\n 'Return the staged waypoint slots (origin, stops, destination) not yet committed to a route.',\n inputSchema: getCurrentWaypointsSchema,\n outputSchema: getCurrentWaypointsOutputSchema,\n execute: executeGetCurrentWaypoints,\n tags: ['waypoint', 'route'],\n examples: [\n 'getCurrentWaypoints()',\n 'getCurrentWaypoints({ slotIndex: 1 })',\n 'getCurrentWaypoints({ includeEmptySlots: true })',\n 'getCurrentWaypoints({ offset: 2, limit: 4 })',\n ],\n examplePrompts: ['What are my current route waypoints?', \"What's my destination?\", 'Show my staged stops'],\n relatedTools: ['locatePlace', 'setRoute', 'showWaypoints', 'recallRoutes'],\n },\n getStandardMapStyles: {\n description: getStandardMapStylesDescription,\n classificationPrompt: 'List available standard map style IDs for use with setMapStandardStyle.',\n inputSchema: getStandardMapStylesSchema,\n outputSchema: getStandardMapStylesOutputSchema,\n execute: executeGetStandardMapStyles,\n tags: ['map style'],\n examples: ['getStandardMapStyles()'],\n examplePrompts: ['What map styles are available?', 'List the map themes'],\n relatedTools: ['setMapStandardStyle'],\n },\n setMapStandardStyle: {\n description: setMapStandardStyleDescription,\n classificationPrompt: 'Switch the map to a standard style preset (light, dark, satellite, driving, etc.).',\n inputSchema: setMapStandardStyleSchema,\n outputSchema: setMapStandardStyleOutputSchema,\n execute: executeSetMapStandardStyle,\n tags: ['map style'],\n examples: ['setMapStandardStyle(\"standardDark\")', 'setMapStandardStyle(\"satellite\")'],\n examplePrompts: ['Switch to dark mode', 'Use the satellite view', 'Change to the light theme'],\n relatedTools: ['getStandardMapStyles', 'setLanguage', 'toggleBaseMapLayerGroups', 'getMapStyleLayers'],\n },\n setRouteTheme: {\n description: setRouteThemeDescription,\n classificationPrompt:\n 'Color the route line and waypoint icons; prefer over setPaintProperties for route styling.',\n inputSchema: setRouteThemeSchema,\n outputSchema: setRouteThemeOutputSchema,\n execute: executeSetRouteTheme,\n tags: ['route', 'map style'],\n examples: [\n 'setRouteTheme({ mainColor: \"#FF0000\" }) // red route',\n 'setRouteTheme({ mainColor: \"coral\" })',\n 'setRouteTheme({ mainColor: \"steelblue\" })',\n ],\n examplePrompts: ['Make the route line red', 'Change the route color to blue', 'Style the route in coral'],\n relatedTools: ['updateRoutesDisplay', 'setRoute', 'setPaintProperties'],\n dependsOn: ['updateRoutesDisplay'],\n },\n setLanguage: {\n description: setLanguageDescription,\n classificationPrompt: 'Change the language for map labels and all subsequent service API responses.',\n inputSchema: setLanguageSchema,\n outputSchema: setLanguageOutputSchema,\n execute: executeSetLanguage,\n tags: ['map style'],\n examples: ['setLanguage(\"fr-FR\")', 'setLanguage(\"de-DE\")'],\n examplePrompts: ['Show map labels in French', 'Switch the map to German', 'Use Spanish for place names'],\n relatedTools: ['setMapStandardStyle'],\n },\n toggleBaseMapLayerGroups: {\n description: toggleBaseMapLayerGroupsDescription,\n classificationPrompt:\n 'Show or hide named base map layer groups (buildings3D, roadLabels, water, placeLabels, etc.).',\n inputSchema: toggleBaseMapLayerGroupsSchema,\n outputSchema: toggleBaseMapLayerGroupsOutputSchema,\n execute: executeToggleBaseMapLayerGroups,\n tags: ['map style'],\n examples: [\n 'toggleBaseMapLayerGroups(false, [\"buildings3D\"])',\n 'toggleBaseMapLayerGroups(true, [\"roadLabels\"])',\n ],\n examplePrompts: ['Hide 3D buildings', 'Show road labels', 'Toggle the building layer'],\n relatedTools: ['togglePOIs', 'getMapStyleLayers', 'setMapStandardStyle'],\n },\n togglePOIs: {\n description: togglePOIsDescription,\n classificationPrompt: 'Show or hide built-in map POI icons with optional category filtering.',\n inputSchema: togglePOIsSchema,\n outputSchema: togglePOIsOutputSchema,\n execute: executeTogglePOIs,\n tags: ['place', 'map style'],\n examples: [\n 'togglePOIs({ allMapPOIsVisible: false })',\n 'togglePOIs({ allMapPOIsVisible: true, filterCategories: { show: \"only\", values: [\"RESTAURANT\", \"CAFE\"] } })',\n 'togglePOIs({ filterCategories: { show: \"all_except\", values: [\"PARKING_GARAGE\"] } })',\n 'togglePOIs({ reset: true })',\n ],\n examplePrompts: ['Hide all POI icons', 'Show only restaurant POIs', 'Turn off parking icons'],\n relatedTools: ['toggleBaseMapLayerGroups', 'discoverPlaces', 'setMapStandardStyle'],\n },\n toggleTrafficFlow: {\n description: toggleTrafficFlowDescription,\n classificationPrompt:\n 'Toggle REAL-TIME traffic flow (colored road segments for live speeds). Not historical analytics.',\n inputSchema: toggleTrafficFlowSchema,\n outputSchema: toggleTrafficFlowOutputSchema,\n execute: executeToggleTrafficFlow,\n tags: ['traffic', 'map style'],\n examples: ['toggleTrafficFlow(true)', 'toggleTrafficFlow(false)'],\n examplePrompts: ['Show traffic flow', 'Turn off traffic colors', 'Enable the congestion overlay'],\n relatedTools: ['toggleTrafficIncidents', 'setRoute', 'getTrafficIncidents'],\n },\n toggleTrafficIncidents: {\n description: toggleTrafficIncidentsDescription,\n classificationPrompt:\n 'Toggle REAL-TIME traffic incident markers and lines (accidents, jams, closures). Not historical analytics.',\n inputSchema: toggleTrafficIncidentsSchema,\n outputSchema: toggleTrafficIncidentsOutputSchema,\n execute: executeToggleTrafficIncidents,\n tags: ['traffic', 'map style'],\n examples: ['toggleTrafficIncidents(true)', 'toggleTrafficIncidents(false)'],\n examplePrompts: ['Show traffic incidents', 'Hide accident icons', 'Enable incident markers'],\n relatedTools: ['toggleTrafficFlow', 'getTrafficIncidents', 'getShownIncidents', 'setRoute'],\n },\n getMapStyleLayers: {\n description: getMapStyleLayersDescription,\n classificationPrompt: 'List MapLibre layer IDs with their paint/layout properties for custom styling.',\n inputSchema: getMapStyleLayersSchema,\n outputSchema: getMapStyleLayersOutputSchema,\n execute: executeGetMapStyleLayers,\n tags: ['map style'],\n examples: ['getMapStyleLayers()', 'getMapStyleLayers(\"road\")', 'getMapStyleLayers(\"water\")'],\n examplePrompts: ['What are the paint properties of the water layer?', 'Show me the road layer layout'],\n relatedTools: ['setLayoutProperties', 'setPaintProperties'],\n },\n setLayoutProperties: {\n description: setLayoutPropertiesDescription,\n classificationPrompt: 'Set MapLibre layout properties (visibility, text-field, icon-size) on named layers.',\n inputSchema: setLayoutPropertiesSchema,\n outputSchema: setLayoutPropertiesOutputSchema,\n execute: executeSetLayoutProperties,\n tags: ['map style'],\n examples: [\n 'setLayoutProperties([{ layerId: \"road-label\", propertyName: \"visibility\", value: \"none\" }])',\n 'setLayoutProperties([{ layerId: \"poi-label\", propertyName: \"text-field\", value: \"{name}\" }, { layerId: \"poi-label\", propertyName: \"icon-size\", value: 1.2 }])',\n ],\n examplePrompts: [\n 'Hide the road-label layer',\n 'Change the text size on building labels',\n 'Make the city labels bigger',\n ],\n relatedTools: ['getMapStyleLayers', 'setPaintProperties'],\n dependsOn: ['getMapStyleLayers'],\n },\n setPaintProperties: {\n description: setPaintPropertiesDescription,\n classificationPrompt: 'Set MapLibre paint properties (colors, widths, opacity) on named layers.',\n inputSchema: setPaintPropertiesSchema,\n outputSchema: setPaintPropertiesOutputSchema,\n execute: executeSetPaintProperties,\n tags: ['map style'],\n examples: [\n 'setPaintProperties([{ layerId: \"water\", propertyName: \"fill-color\", value: \"#0000ff\" }])',\n 'setPaintProperties([{ layerId: \"road\", propertyName: \"line-color\", value: \"#ff0000\" }, { layerId: \"road\", propertyName: \"line-width\", value: 3 }])',\n ],\n examplePrompts: [\n 'Make the water bright blue',\n 'Change the road color to red',\n 'Make buildings semi-transparent',\n 'Make the country borders pink',\n ],\n relatedTools: ['getMapStyleLayers', 'setLayoutProperties', 'setRouteTheme'],\n dependsOn: ['getMapStyleLayers'],\n },\n setPitchBearing: {\n description: setPitchBearingDescription,\n classificationPrompt: 'Tilt (pitch) and/or rotate (bearing) the map camera for a 3D perspective.',\n inputSchema: setPitchBearingSchema,\n outputSchema: setPitchBearingOutputSchema,\n execute: executeSetPitchBearing,\n tags: ['map style'],\n examples: [\n 'setPitchBearing({ pitch: 45 })',\n 'setPitchBearing({ bearing: 90 })',\n 'setPitchBearing({ pitch: 60, bearing: -45 })',\n ],\n examplePrompts: ['Tilt the map to 45 degrees', 'Rotate the map to face north', 'Set a 3D perspective view'],\n relatedTools: ['getViewport', 'locatePlace', 'flyTo'],\n },\n zoomInOrOut: {\n description: zoomInOrOutDescription,\n classificationPrompt: 'Adjust the map zoom level by a delta; positive zooms in, negative zooms out.',\n inputSchema: zoomInOrOutSchema,\n outputSchema: zoomInOrOutOutputSchema,\n execute: executeZoomInOrOut,\n tags: ['map style'],\n examples: ['zoomInOrOut({ delta: 2 })', 'zoomInOrOut({ delta: -1 })'],\n examplePrompts: ['Zoom in', 'Zoom out 2 levels', 'Get a wider view'],\n relatedTools: ['getViewport', 'flyTo'],\n },\n executeMaplibreCode: {\n description: executeMaplibreCodeDescription,\n classificationPrompt:\n 'Execute any MapLibre JS code against the live map for maximum flexibility — use when no other tool covers the required operation.',\n inputSchema: executeMaplibreCodeSchema,\n outputSchema: executeMaplibreCodeOutputSchema,\n execute: executeExecuteMaplibreCode,\n tags: ['map style', 'maplibre', 'layers', 'sources', 'animation'],\n examples: [\n \"executeMaplibreCode({ code: \\\"map.addSource('pts', { type: 'geojson', data: fc }); map.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-color': '#f00' } })\\\" })\",\n 'executeMaplibreCode({ code: \"map.easeTo({ center: [4.9, 52.37], zoom: 14, duration: 2000 })\" })',\n \"executeMaplibreCode({ code: \\\"for (let i = 0; i <= 20; i++) { map.setPaintProperty('fill', 'fill-opacity', i / 20); await new Promise(r => setTimeout(r, 50)); }\\\" })\",\n \"executeMaplibreCode({ code: \\\"map.setFog({ color: 'white', 'horizon-blend': 0.1 })\\\" })\",\n ],\n examplePrompts: [\n 'Add a GeoJSON layer to the map',\n 'Animate the map camera',\n 'Fade in a layer gradually',\n 'Set map fog or atmosphere',\n 'Add a raster tile overlay',\n ],\n relatedTools: ['getMapStyleLayers', 'setLayoutProperties', 'setPaintProperties', 'flyTo'],\n dependsOn: ['getMapStyleLayers'],\n },\n calculateBBox: {\n description: calculateBBoxDescription,\n classificationPrompt:\n 'Compute a [minLng, minLat, maxLng, maxLat] bounding box from GeoJSON features or tool results.',\n inputSchema: calculateBBoxSchema,\n execute: executeCalculateBBox,\n tags: ['utilities', 'location'],\n examples: ['calculateBBox(place)', 'calculateBBox(routes)', 'calculateBBox([place1, place2])'],\n examplePrompts: [\"What's the bounding box of these places?\", 'Calculate the extent of this route'],\n relatedTools: ['flyTo', 'getViewport', 'updatePlacesDisplay', 'updateRoutesDisplay'],\n },\n formatDistance: {\n description: formatDistanceDescription,\n classificationPrompt:\n 'Format meters into a human-readable string (e.g. \"2.5 km\"); use after tools that return distances.',\n inputSchema: formatDistanceSchema,\n execute: executeFormatDistance,\n tags: ['utilities', 'route'],\n examples: ['formatDistance(2500)', 'formatDistance(5000, \"imperial_us\")'],\n examplePrompts: ['Format 5000 meters as a distance', 'Convert meters to a readable string'],\n relatedTools: ['formatDuration', 'setRoute'],\n },\n formatDuration: {\n description: formatDurationDescription,\n classificationPrompt:\n 'Format seconds into a human-readable string (e.g. \"1 h 30 min\"); use after tools that return durations.',\n inputSchema: formatDurationSchema,\n execute: executeFormatDuration,\n tags: ['utilities', 'route'],\n examples: ['formatDuration(3600)', 'formatDuration(9000)'],\n examplePrompts: ['Format 3600 seconds as a duration', 'Convert seconds to hours and minutes'],\n relatedTools: ['formatDistance', 'setRoute'],\n },\n getCurrentLocation: {\n description: getCurrentLocationDescription,\n classificationPrompt:\n 'Get the user\\'s physical GPS location from the browser; use for \"near me\" queries, not map center.',\n inputSchema: getCurrentLocationSchema,\n outputSchema: getCurrentLocationOutputSchema,\n execute: executeGetCurrentLocation,\n tags: ['utilities', 'location'],\n examples: ['getCurrentLocation()'],\n examplePrompts: ['Where am I?', 'Get my GPS location', 'Use my current position'],\n relatedTools: ['discoverPlaces', 'reverseGeocode', 'locatePlace'],\n },\n help: {\n description: helpDescription,\n classificationPrompt:\n 'Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.',\n inputSchema: helpSchema,\n outputSchema: helpOutputSchema,\n execute: async () => {\n throw new Error('help tool requires setupTools() — it needs resolved metadata from all tools.');\n },\n tags: ['utilities'],\n examples: [\n 'help({ mode: \"summary\" })',\n 'help({ mode: \"detail\", query: \"traffic\" })',\n 'help({ mode: \"detail\", tag: \"route\" })',\n 'help({ mode: \"detail\", query: \"route\", tag: \"utilities\" })',\n ],\n examplePrompts: ['What can you do?', 'Show me available tools', 'How do I search for places?'],\n },\n findReachableAreas: {\n description: findReachableAreasDescription,\n classificationPrompt:\n 'Calculate isochrone/isodistance polygons showing how far you can travel from one or more origins.',\n inputSchema: findReachableAreasSchema,\n outputSchema: findReachableAreasOutputSchema,\n execute: executeFindReachableAreas,\n tags: ['reachable-range', 'isochrone', 'range', 'EV', 'coverage', 'multi-origin'],\n examples: [\n 'findReachableAreas({ origins: [{ query: \"Amsterdam\", queryAs: \"place\" }], budgets: [{ type: \"timeMinutes\", value: 30 }], showOnMap: true })',\n 'findReachableAreas({ origins: [{ query: \"Amsterdam\", queryAs: \"place\" }, { query: \"Rotterdam\", queryAs: \"place\" }], budgets: [{ type: \"timeMinutes\", value: 30 }], showOnMap: true })',\n 'findReachableAreas({ origins: [{ position: { lng: 4.9, lat: 52.3 } }], budgets: [{ type: \"timeMinutes\", value: 10 }, { type: \"timeMinutes\", value: 30 }], showOnMap: true, theme: \"inverted\" })',\n 'findReachableAreas({ origins: [{ query: \"my location\", queryAs: \"place\" }], budgets: [{ type: \"distanceKM\", value: 50 }], showOnMap: false })',\n ],\n examplePrompts: [\n 'Show me where I can reach in 30 minutes from Amsterdam',\n 'Compare 30-minute reach from Amsterdam, Rotterdam and Utrecht',\n 'What area is within 50 km of Berlin?',\n 'Show 10, 20, 30 minute drive zones from here',\n ],\n relatedTools: ['recallRanges', 'locatePlace'],\n },\n recallRanges: {\n description: recallRangesDescription,\n classificationPrompt: 'Retrieve stored reachable range results from session history.',\n inputSchema: recallRangesSchema,\n outputSchema: recallRangesOutputSchema,\n execute: executeRecallRanges,\n tags: ['recall', 'reachable-range', 'state'],\n examples: ['recallRanges()', 'recallRanges({ id: \"ranges-0\" })'],\n examplePrompts: ['What range did I calculate earlier?', 'Recall the isochrone from Amsterdam'],\n relatedTools: ['findReachableAreas'],\n },\n recallState: {\n description: recallStateDescription,\n classificationPrompt:\n \"Inventory the agent's session: list every places / routes / ranges entry by id+label, plus current map style and POIs/traffic visualization configs.\",\n inputSchema: recallStateSchema,\n outputSchema: recallStateOutputSchema,\n execute: executeRecallState,\n tags: ['recall', 'state', 'overview'],\n examples: ['recallState()'],\n examplePrompts: [\n 'What do you have so far?',\n 'List everything you remember from this session',\n 'Show me the current map / data overview',\n ],\n relatedTools: ['recallPlaces', 'recallRoutes', 'recallRanges', 'resetState'],\n },\n resetState: {\n description: resetStateDescription,\n classificationPrompt:\n 'Wipe the session: clear every places/routes/ranges entry, reset POIs/traffic configs, revert to standard style. Use only when the user asks for a clean slate.',\n inputSchema: resetStateSchema,\n outputSchema: resetStateOutputSchema,\n execute: executeResetState,\n tags: ['state', 'reset', 'destructive'],\n examples: ['resetState()'],\n examplePrompts: ['Start over', 'Reset everything', 'Clear all my data and the map'],\n relatedTools: ['recallState', 'clearMap'],\n },\n setEntryMode: {\n description: setEntryModeDescription,\n classificationPrompt:\n 'Switch a slice (places/routes/ranges) between `multiple` (overlay several entries) and `single` (latest only). `single` drops older entries.',\n inputSchema: setEntryModeSchema,\n outputSchema: setEntryModeOutputSchema,\n execute: executeSetEntryMode,\n tags: ['state', 'mode', 'display'],\n examples: [\n 'setEntryMode({ slice: \"places\", mode: \"single\" })',\n 'setEntryMode({ slice: \"routes\", mode: \"multiple\" })',\n ],\n examplePrompts: [\n 'Only show one route at a time',\n 'Let me overlay multiple places searches',\n 'Switch ranges back to single mode',\n ],\n relatedTools: ['recallState', 'recallPlaces', 'recallRoutes', 'recallRanges'],\n },\n} satisfies Record<string, ToolDefinition>;\n\n// Narrow the value type so the emitted d.ts captures only the keys, not the\n// inlined `z.infer<...>` signatures from each `execute` (which the d.ts bundler\n// flattens to invalid `infer<typeof X>` syntax). Must go through an intermediate\n// const so TypeScript resolves the mapped type before re-exporting.\nconst DEFAULT_TOOLS_TYPED: { [K in keyof typeof defaultTools]: ToolDefinition } = defaultTools;\n\n/**\n * Default tool set for the agent toolkit. Each entry is a {@link ToolDefinition}\n * — either a static `ToolEntry` or a `ToolEntryBuilder` that tailors the tool to\n * {@link ToolBuildOptions} (e.g. feature flags) at agent-creation time.\n *\n * Included automatically by createMapAgent unless includeDefaultTools is false.\n * Use resolveTools() for manual composition outside createMapAgent.\n *\n * @group Agent Toolkit\n */\nexport const DEFAULT_TOOLS = DEFAULT_TOOLS_TYPED;\n\n/**\n * Union of all default tool names.\n *\n * @group Agent Toolkit\n */\nexport type ToolName = keyof typeof DEFAULT_TOOLS;\n\n/**\n * All valid default tool names, derived from {@link DEFAULT_TOOLS}.\n *\n * @group Agent Toolkit\n */\nexport const TOOL_NAMES = Object.keys(DEFAULT_TOOLS) as ToolName[];\n","/**\n * @module agent-toolkit\n */\n\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { PrepareStepFunction, PrepareStepResult } from 'ai';\nimport { stepCountIs, ToolLoopAgent } from 'ai';\nimport { resolveTools } from './resolve-tools';\nimport { createToolState } from './state';\nimport { buildSystemPrompt } from './system-prompt';\nimport { setupTools } from './tool-setup';\nimport { DEFAULT_TOOLS } from './tools';\nimport type { Classifier, MapAgentInstance, MapAgentOptions, StateSlice, ToolDefinition, ToolState } from './types';\nimport type { ClassificationResult } from './utils';\nimport { createDefaultClassifier } from './utils';\n\n/** Intersects two activeTools lists. Returns undefined if neither is set. */\nconst intersectActiveTools = (\n a: string[] | undefined,\n b: readonly (string | number | symbol)[] | undefined,\n): string[] | undefined => {\n if (!a) return b as string[] | undefined;\n if (!b) return a;\n const setB = new Set(b);\n return a.filter((name) => setB.has(name));\n};\n\n/** Type guard for StateSlice — checks for reset() method. */\nconst isStateSlice = (value: unknown): value is StateSlice =>\n typeof value === 'object' && value !== null && 'reset' in value && typeof (value as any).reset === 'function';\n\n/** Resets all state slices that implement StateSlice. */\nconst destroyState = (state: ToolState): void => {\n for (const slice of Object.values(state)) {\n if (isStateSlice(slice)) {\n slice.reset();\n }\n }\n};\n\n/**\n * Creates a conversational agent toolkit that gives an LLM control over a TomTom map.\n *\n * Returns a ToolLoopAgent instance (compatible with DirectChatTransport) with\n * `state` and `destroy` attached. Classification runs transparently inside\n * `prepareStep` — no wrapper type needed.\n *\n * @param map - The TomTomMap instance to control\n * @param options - Agent configuration options\n * @returns A ToolLoopAgent with state access and cleanup\n *\n * @example Basic usage\n * ```typescript\n * import { createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n * import { openai } from '@ai-sdk/openai';\n * import { DirectChatTransport } from 'ai/react';\n *\n * const agent = createMapAgent(map, { model: openai('gpt-4o') });\n *\n * // Works directly with DirectChatTransport — no .agent property needed\n * const { messages, sendMessage } = useChat({\n * transport: new DirectChatTransport({ agent }),\n * });\n * ```\n *\n * @example Custom tools + removal\n * ```typescript\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * tools: {\n * getCustomLocation: myLocationTool, // add custom\n * setLanguage: false, // remove default\n * },\n * });\n * ```\n * @group Agent Toolkit\n */\nexport const createMapAgent = <CS extends ToolState = ToolState>(\n map: TomTomMap,\n options: MapAgentOptions<CS>,\n): MapAgentInstance<CS> => {\n if (!options.model) {\n throw new Error('MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.');\n }\n\n const state = createToolState(map, options.state) as CS;\n\n // Safe: default tools only access base ToolState properties; CS extends ToolState.\n const defaults = options.includeDefaultTools === false ? {} : (DEFAULT_TOOLS as Record<string, ToolDefinition<CS>>);\n const toolEntries = resolveTools(defaults, options.tools);\n\n const { tools, toolsMetadata } = setupTools(toolEntries, state, {\n outputSchemas: options.outputSchemas,\n featureFlags: options.featureFlags,\n });\n\n // Resolve classifier\n const classifier: Classifier | null =\n options.classifier === false ? null : (options.classifier ?? createDefaultClassifier({ model: options.model }));\n\n const systemPrompt = buildSystemPrompt(options.systemPrompt, options.systemPromptSuffix);\n\n // Classification state — cached per turn (reset on step 0)\n let lastClassification: ClassificationResult | null = null;\n\n const prepareStep: PrepareStepFunction = async (stepInfo) => {\n let activeTools: string[] | undefined;\n\n if (stepInfo.stepNumber === 0) {\n lastClassification = null;\n if (classifier) {\n try {\n lastClassification = await classifier({\n messages: stepInfo.messages,\n toolsMetadata,\n });\n } catch {\n lastClassification = null;\n }\n }\n options.onClassify?.(lastClassification);\n }\n\n if (lastClassification) {\n activeTools = lastClassification.activeToolNames;\n }\n\n // Compose with developer's prepareStep\n const userResult: PrepareStepResult | undefined = await options.prepareStep?.(stepInfo);\n\n // Per-step providerOptions override — the AI SDK merges this onto the\n // agent-level providerOptions, so we only need to return the diff.\n const stepProviderOptions = options.stepProviderOptions?.({\n activeTools,\n stepNumber: stepInfo.stepNumber,\n });\n\n if (userResult) {\n // Shallow-merge per-provider keys so a user-supplied prepareStep doesn't\n // silently drop the per-step provider override (and vice versa). User\n // values win on collision.\n const mergedProviderOptions =\n stepProviderOptions || userResult.providerOptions\n ? { ...stepProviderOptions, ...userResult.providerOptions }\n : undefined;\n return {\n ...userResult,\n activeTools: intersectActiveTools(activeTools, userResult.activeTools),\n providerOptions: mergedProviderOptions,\n };\n }\n\n return {\n ...(activeTools ? { activeTools } : {}),\n ...(stepProviderOptions ? { providerOptions: stepProviderOptions } : {}),\n };\n };\n\n const agent = new ToolLoopAgent({\n model: options.model,\n tools,\n instructions: systemPrompt,\n stopWhen: stepCountIs(options.maxSteps ?? 10),\n prepareStep,\n providerOptions: options.providerOptions,\n });\n\n return Object.assign(agent, {\n state,\n destroy: () => destroyState(state),\n });\n};\n"],"mappings":"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"}