@tomtom-org/maps-sdk-plugin-agent-toolkit 0.2.11 → 0.2.12

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/prepare-step-helpers.ts","../src/resolve-tools.ts","../src/state/base-map/state.ts","../src/state/entry-helpers.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/byod/layer-defaults.ts","../src/state/byod/state.ts","../src/state/traffic-area-analytics/state.ts","../src/tools/shared/geometries-id.ts","../src/tools/shared/geometries-schema-doc.ts","../src/tools/shared/places-schema-doc.ts","../src/tools/shared/routes-schema-doc.ts","../src/tools/shared/schema.ts","../src/tools/shared/entry-kinds.ts","../src/tools/shared/fit-on-map.ts","../src/tools/shared/hide-previous-entries.ts","../src/tools/shared/location-input.ts","../src/tools/shared/state-inputs.ts","../src/tools/shared/multi-input.ts","../src/tools/shared/resolve-poi-categories.ts","../src/tools/shared/run-analysis.ts","../src/tools/shared/sandbox-code.ts","../src/tools/shared/show-entry-geometries.ts","../src/tools/shared/show-places-on-map.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/add-byod-layer.ts","../src/tools/state/analyse-data.ts","../src/tools/state/focus-incidents.ts","../src/tools/state/get-current-waypoints.ts","../src/tools/state/process-data.ts","../src/tools/state/query-traffic-analytics.ts","../src/tools/state/recall-byod.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-tile-incidents.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/toggle-tiles-base-map-layer-groups.ts","../src/tools/tomtom-map/toggle-tiles-pois.ts","../src/tools/tomtom-map/toggle-tiles-traffic.ts","../src/tools/tomtom-map/update-byod-display.ts","../src/tools/tomtom-map/update-places-display.ts","../src/tools/tomtom-map/update-routes-display.ts","../src/tools/tomtom-map/update-traffic-area-analytics-display.ts","../src/tools/tomtom-map/update-waypoints-display.ts","../src/tools/tool-registry.ts","../src/create-map-agent.ts"],"sourcesContent":["/**\n * @module agent-toolkit\n * @ignore\n *\n * Pure helpers used by {@link createMapAgent}'s `prepareStep` orchestrator. Extracted from the\n * monolithic module so they can be exercised in isolation by the unit-test suite — none of these\n * touch the AI SDK, the TomTom map, or any singleton state, so they're testable without mocks.\n */\n\nimport type { PrepareStepResult, Tool } from 'ai';\nimport type { ScopableToolInfo } from './tool-setup';\nimport type { StateSlice, ToolState } from './types';\n\ntype ProviderOptions = NonNullable<PrepareStepResult>['providerOptions'];\n\n// Tool objects with mutable description/inputSchema. The AI SDK reads these fresh on every step,\n// so per-turn mutation is the supported way to scope a tool's surface — see the concurrency note\n// on `createMapAgent`.\ntype MutableTool = Tool & { description: string; inputSchema: unknown };\n\n/**\n * Build a per-agent serialising mutex. `prepareStep` chains onto this so a concurrent step's\n * classification + scope-mutation cannot interleave with the previous one's request-build window.\n * Each agent instance gets its own chain via a fresh call to this factory.\n *\n * @ignore\n */\nexport const createPrepareStepMutex = () => {\n let tail: Promise<void> = Promise.resolve();\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n const previous = tail;\n let release: () => void = () => undefined;\n tail = new Promise<void>((resolve) => {\n release = resolve;\n });\n try {\n await previous;\n return await fn();\n } finally {\n release();\n }\n };\n};\n\n/**\n * Restore each scopable tool's description + inputSchema to its full-surface defaults. Called at\n * the start of every turn (step 0) before classification so the new turn inherits a clean slate.\n *\n * @ignore\n */\nexport const restoreScopableTools = (\n tools: Record<string, Tool>,\n scopableTools: Record<string, ScopableToolInfo>,\n): void => {\n for (const [name, info] of Object.entries(scopableTools)) {\n const tool = tools[name] as MutableTool | undefined;\n if (!tool) continue;\n tool.description = info.defaultDescription;\n tool.inputSchema = info.defaultInputSchema;\n }\n};\n\n/**\n * Apply classifier-emitted per-tool scopes by mutating the live tool object. Validation and\n * rebuilding are encapsulated in {@link ScopableToolInfo.applyScope}; a `null` return covers both\n * mis-shaped payloads and rebuild throws, in which case the default surface stays in place —\n * fail-open, never crash the step.\n *\n * @ignore\n */\nexport const applyToolScopes = (\n tools: Record<string, Tool>,\n scopableTools: Record<string, ScopableToolInfo>,\n toolScopes: Record<string, unknown> | undefined,\n): void => {\n if (!toolScopes) return;\n for (const [name, rawScope] of Object.entries(toolScopes)) {\n const scopableTool = scopableTools[name];\n if (!scopableTool || rawScope == null) continue;\n const tool = tools[name] as MutableTool | undefined;\n if (!tool) continue;\n const rebuiltTool = scopableTool.applyScope(rawScope);\n if (!rebuiltTool) continue;\n tool.description = rebuiltTool.description;\n tool.inputSchema = rebuiltTool.inputSchema;\n }\n};\n\n/**\n * Intersect two activeTools lists. Returns `undefined` only when neither side was set (so the\n * caller can omit the key from `PrepareStepResult` entirely). When only one side is set the\n * intersection is that side; when both are set, the result preserves `a`'s order and drops any\n * name absent from `b`. Used to combine classifier-picked tools with a user-supplied override.\n *\n * @ignore\n */\nexport const 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/**\n * Type guard for {@link StateSlice}. Detects the canonical shape (an object with a callable\n * `reset` method) without committing to a particular slice's full surface — used by\n * {@link destroyState} to walk the heterogeneous ToolState bag without per-slice knowledge.\n *\n * @ignore\n */\nexport const isStateSlice = (value: unknown): value is StateSlice =>\n typeof value === 'object' &&\n value !== null &&\n 'reset' in value &&\n typeof (value as { reset: unknown }).reset === 'function';\n\n/**\n * Reset every slice on `state` that implements {@link StateSlice}. Slices without a `reset`\n * method (e.g. the bare-bones `baseMap` proxy) are skipped silently.\n *\n * @ignore\n */\nexport const 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 * Merge per-provider keys from the developer's `prepareStep` override and the agent's per-step\n * providerOptions factory. Either side may be `undefined`; the developer's values win on\n * collision so caller intent is preserved. Returns `undefined` when both sides are absent so\n * callers can omit the key from the final result.\n *\n * @ignore\n */\nexport const mergeProviderOptions = (\n fromStepFactory: ProviderOptions | undefined,\n fromUser: ProviderOptions | undefined,\n): ProviderOptions | undefined => {\n if (!fromStepFactory && !fromUser) return undefined;\n return { ...fromStepFactory, ...fromUser };\n};\n\n/**\n * Compose the PrepareStepResult from three independent contributions: the active-tools list\n * (from the classifier), the per-step providerOptions factory, and the developer's optional\n * user-level `prepareStep` override. When the user overrides, their `activeTools` are\n * intersected with the classifier picks (so the user can narrow further but not widen past the\n * classifier's choice).\n *\n * @ignore\n */\nexport const composeStepResult = (\n activeTools: string[] | undefined,\n stepProviderOptions: ProviderOptions | undefined,\n userResult: PrepareStepResult | undefined,\n): PrepareStepResult => {\n if (userResult) {\n return {\n ...userResult,\n activeTools: intersectActiveTools(activeTools, userResult.activeTools),\n providerOptions: mergeProviderOptions(stepProviderOptions, userResult.providerOptions),\n };\n }\n return {\n ...(activeTools ? { activeTools } : {}),\n ...(stepProviderOptions ? { providerOptions: stepProviderOptions } : {}),\n };\n};\n","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 * @ignore\n *\n * Cross-slice helpers for entry-owning state slices. The agent-toolkit has seven such slices\n * (places, routing, ranges, customGeometries, trafficAreaAnalytics, trafficIncidents, byod) that\n * shared the same id-collision + single-mode-collapse code; consolidating it here keeps the\n * slices in lockstep on edge-case behaviour and makes the rules unit-testable in isolation.\n */\n\n/**\n * Return `requested` if no entry in `takenIds` already uses it; otherwise append `-2`, `-3`, …\n * until an unused id appears. Pure — only reads `takenIds`.\n *\n * Behaviour notes:\n * - `requested` is never modified, only suffixed (`\"foo\"` → `\"foo-2\"`, not `\"foo-1\"`). The first\n * suffix is `-2` so the chain reads naturally: \"foo, foo-2, foo-3, …\".\n * - Acceps any `Iterable<string>` (Set / Array / generator) — the slice's internal entry store\n * shape is irrelevant.\n *\n * @ignore\n */\nexport const pickUniqueEntryId = (requested: string, takenIds: Iterable<string>): string => {\n const taken = new Set(takenIds);\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 * Hide every entry in `entries` and resolve once they're all settled. Sequential by default\n * (avoids MapLibre source-mutation races — see the project-wide `show/hide sequential` note);\n * pass `parallel: true` only when the slice's `hideOne` is provably race-safe (e.g. each entry\n * owns a separate module).\n *\n * Used in two places:\n * - `setEntryMode('single')` collapses history to the latest entry (via\n * {@link collapseHistoryToLatest}, which delegates to this primitive).\n * - `addEntry` under `single` mode replaces the entire history — same hide-all-then-drop\n * shape, but no survivor.\n *\n * Centralising this means addEntry implementations can `await` the hides instead of the\n * fire-and-forget `void this.hideEntry(…)` pattern that previously let a new entry render\n * while the old map modules were still being torn down.\n *\n * @ignore\n */\nexport const hideAllEntries = async <T>(\n entries: readonly T[],\n hideOne: (entry: T) => Promise<void>,\n options: { parallel?: boolean } = {},\n): Promise<void> => {\n if (entries.length === 0) return;\n if (options.parallel) {\n await Promise.all(entries.map(hideOne));\n } else {\n for (const entry of entries) await hideOne(entry);\n }\n};\n\n/**\n * Drop every entry except the last, hiding the dropped ones first. Returns the surviving entries\n * (an array with the latest entry, or empty when the input was empty).\n *\n * Used by `setEntryMode('single')` across every entry-owning slice — `single` mode means only the\n * latest entry stays on the map, so flipping the mode has to retroactively hide and forget every\n * older entry. The helper centralises the \"hide-then-drop\" order so a future change to hide\n * semantics lands in one place.\n *\n * @ignore\n */\nexport const collapseHistoryToLatest = async <T>(\n entries: readonly T[],\n hideOne: (entry: T) => Promise<void>,\n options: { parallel?: boolean } = {},\n): Promise<T[]> => {\n if (entries.length <= 1) return [...entries];\n const latest = entries[entries.length - 1];\n await hideAllEntries(entries.slice(0, -1), hideOne, options);\n return [latest];\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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\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 `processData` 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\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 async addEntry(\n features: PolygonFeature<CommonPlaceProps>[],\n provenance: CustomGeometriesEntry['provenance'],\n label: string,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n const fallback = `geometries-${this._entries.length}`;\n const entryId = explicitId\n ? pickUniqueEntryId(\n explicitId,\n this._entries.map((entry) => entry.id),\n )\n : 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?._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 /**\n * Hide every custom-geometries entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\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// `Array.prototype.sort` without a comparator falls back to UTF-16 code-unit order — fine for\n// ASCII-only ids but unreliable once a localised label leaks in. Centralise the locale-aware\n// comparator so every digest field gets the same predictable order.\nconst compareIds = (a: string, b: string): number => a.localeCompare(b);\nconst sortIds = (ids: Iterable<string>): string[] => [...ids].sort(compareIds);\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 /** Traffic-area-analytics entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total traffic-area-analytics entries in history (shown + hidden). */\n entryCount: number;\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 customGeometries: {\n /** Custom-geometries entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total custom-geometries entries in history (shown + hidden). */\n entryCount: number;\n };\n byod: {\n /** BYOD entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total BYOD entries in history (shown + hidden). */\n entryCount: number;\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: sortIds(state.places.shownAsPinIds),\n shownAsBaseMap: sortIds(state.places.shownAsBaseMapIds),\n entryMode: state.places.entryMode,\n },\n routes: { shown: sortIds(state.routing.shownEntryIds), entryMode: state.routing.entryMode },\n ranges: { shown: sortIds(state.ranges.shownEntryIds), entryMode: state.ranges.entryMode },\n traffic: {\n shown: sortIds(state.trafficAreaAnalytics.shownEntryIds),\n entryMode: state.trafficAreaAnalytics.entryMode,\n entryCount: state.trafficAreaAnalytics.entries.length,\n },\n incidents: {\n shown: sortIds(state.trafficIncidents.shownEntryIds),\n monitored: sortIds(\n state.trafficIncidents.entries\n .filter((entry) => entry._monitor?.status === 'running')\n .map((entry) => entry.id),\n ),\n entryMode: state.trafficIncidents.entryMode,\n },\n customGeometries: {\n shown: sortIds(state.customGeometries.shownEntryIds),\n entryMode: state.customGeometries.entryMode,\n entryCount: state.customGeometries.entries.length,\n },\n byod: {\n shown: sortIds(state.byod.shownEntryIds),\n entryMode: state.byod.entryMode,\n entryCount: state.byod.entries.length,\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 const trafficAnalyticsFragment = formatShownSetDiff('traffic analytics', prev.traffic.shown, next.traffic.shown);\n if (trafficAnalyticsFragment) fragments.push(trafficAnalyticsFragment);\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 const customGeometriesFragment = formatShownSetDiff(\n 'custom geometries',\n prev.customGeometries.shown,\n next.customGeometries.shown,\n );\n if (customGeometriesFragment) fragments.push(customGeometriesFragment);\n\n const byodFragment = formatShownSetDiff('byod', prev.byod.shown, next.byod.shown);\n if (byodFragment) fragments.push(byodFragment);\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 if (prev.customGeometries.entryMode !== next.customGeometries.entryMode) {\n fragments.push(`custom geometries entryMode → ${next.customGeometries.entryMode}`);\n }\n if (prev.byod.entryMode !== next.byod.entryMode) {\n fragments.push(`byod entryMode → ${next.byod.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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\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// 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 a single PlacesModule (rethemed in-place via `applyTheme` when the marker\n * type changes — an entry only renders under one theme at a time) and a dedicated\n * GeometriesModule, lazy-initialised on first show. Display state lives on the entry, so two\n * entries can be rendered side by 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this._hideEntry(entry));\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, rethemed to `markerType`. An entry only\n * ever renders under one theme at a time, so the same module is reused — flipping themes\n * calls `applyTheme` in place. 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 if (!entry._modules.places) {\n entry._modules.places = await PlacesModule.get(this._ttMap, { theme: markerType });\n } else if (entry._shownAs !== markerType) {\n entry._modules.places.applyTheme(markerType);\n }\n return entry._modules.places;\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 async addPlaceResult(\n place: Place | Places,\n label: string,\n explicitId?: string,\n connections?: PlaceConnectionDisplay[],\n geometries?: PolygonFeature<CommonPlaceProps>[],\n ): Promise<string> {\n // Under `single` mode the new entry replaces everything older. Hide and await each\n // older entry's modules before pushing the new one — otherwise the caller's render\n // can race with the in-flight teardown and the previous markers flicker.\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this._hideEntry(entry));\n this._entries = [];\n }\n const fallback = `places-${this._entries.length}`;\n const entryId = explicitId\n ? pickUniqueEntryId(\n explicitId,\n this._entries.map((entry) => entry.id),\n )\n : 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 /**\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 /**\n * Hide every PlacesState rendering — pins AND attached boundary polygons. Map-only:\n * history (`entries`) is untouched. Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n await this.clearShownEntries();\n await this.clearShownGeometries();\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.places?.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`: lazy-inits or rethemes the entry's single\n // PlacesModule, then pushes its places onto it. Idempotent — calling twice with the same\n // theme just re-pushes the same data; switching themes rethemes the existing module in place.\n private async _showEntryAs(entry: PlacesEntry, markerType: PlacesMarkerType): Promise<void> {\n const module = await this.getEntryPlacesModule(entry.id, markerType);\n await module.show({ type: 'FeatureCollection', features: entry.places } as Places);\n if (entry.connections?.length) {\n await module.showConnections(entry.connections);\n }\n entry._shownAs = markerType;\n }\n\n // Hides the entry by clearing its PlacesModule. No-op if already hidden.\n private async _hideEntry(entry: PlacesEntry): Promise<void> {\n if (!entry._shownAs) return;\n if (entry._modules?.places) await entry._modules.places.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.places?.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 { collapseHistoryToLatest, hideAllEntries } from '../entry-helpers';\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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\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?._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 async addEntry(entry: Omit<RangesEntry, 'id' | 'timestamp'>): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (existing) => 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 /**\n * Hide every reachable-range entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(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 { collapseHistoryToLatest, hideAllEntries } from '../entry-helpers';\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 * Slice-level \"main color\" applied to every RoutingModule on first show / re-show. Sticky\n * across entry swaps so the rendered route always uses the most recently requested color.\n * Set via {@link setMainColor}; persists for the lifetime of the slice (cleared on `reset`).\n */\n private _mainColor?: string;\n /**\n * The most recently `.showEntry`-ed id. Tracks \"who was last brought up\" so tools that\n * operate on the focused module have a single pointer to read; the authoritative shown\n * set is `shownEntryIds` (derived from each 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\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 // Apply the sticky main color BEFORE showing so the first paint already has the theme,\n // avoiding a flash of the default style on newly-shown entries.\n if (this._mainColor !== undefined) {\n module.applyConfig({ theme: { mainColor: this._mainColor } });\n }\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 /** Slice-level main color persisted across entry swaps. Undefined when no theme has been set. */\n get mainColor(): string | undefined {\n return this._mainColor;\n }\n\n /**\n * Set the slice-level main color. The colour is applied immediately to every currently-shown\n * entry's RoutingModule and remembered so that subsequent {@link showEntry} calls apply it\n * automatically. Pass `undefined` to clear the sticky theme — currently-shown modules keep\n * their last paint until they're re-shown (MapLibre style restore handles full revert).\n */\n setMainColor(color: string | undefined): void {\n this._mainColor = color;\n if (color === undefined) return;\n for (const entry of this._entries) {\n if (entry._shown && entry._module) {\n entry._module.applyConfig({ theme: { mainColor: color } });\n }\n }\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?._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. Returns undefined when no\n * 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 async addRoutes(routes: Routes, waypoints: WaypointLike[], label: string): Promise<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 await hideAllEntries(this._entries, (entry) => 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 /**\n * Hide every route + waypoint set currently on the map. Map-only — history (`entries`)\n * stays intact. Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\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._mainColor = 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 * Default MapLibre layer specs the BYOD slice picks when the caller does not\n * supply layers explicitly. The slice inspects the entry's geometry types and\n * picks one layer per kind present (circle / line / fill). Mixed-kind entries\n * get all three stacked. The defaults are intentionally subtle (low opacity,\n * stable theme-neutral colour) so they read as \"the agent's data\" rather than\n * competing with TomTom's own layers — the integrator can always override.\n */\n\nimport type { CustomGeoJSONLayerSpec } from '@tomtom-org/maps-sdk/map';\nimport type { FeatureCollection } from 'geojson';\n\nconst POINT_LAYER: CustomGeoJSONLayerSpec = {\n type: 'circle',\n paint: {\n 'circle-radius': 4,\n 'circle-color': '#0a3653',\n 'circle-stroke-color': '#ffffff',\n 'circle-stroke-width': 1,\n },\n};\n\nconst LINE_LAYER: CustomGeoJSONLayerSpec = {\n type: 'line',\n paint: {\n 'line-color': '#0a3653',\n 'line-width': 2,\n },\n};\n\nconst FILL_LAYER: CustomGeoJSONLayerSpec = {\n type: 'fill',\n paint: {\n 'fill-color': '#0a3653',\n 'fill-opacity': 0.25,\n 'fill-outline-color': '#0a3653',\n },\n};\n\ntype GeomKind = 'point' | 'line' | 'polygon';\n\n// Map every GeoJSON geometry-type string to its display kind. Cheaper than an\n// if/else-if cascade and trivially extended; `GeometryCollection` is handled\n// separately at the call site because it nests further geometries.\nconst KIND_BY_TYPE: Record<string, GeomKind> = {\n Point: 'point',\n MultiPoint: 'point',\n LineString: 'line',\n MultiLineString: 'line',\n Polygon: 'polygon',\n MultiPolygon: 'polygon',\n};\n\nconst classify = (data: FeatureCollection): Set<GeomKind> => {\n const kinds = new Set<GeomKind>();\n for (const feature of data.features) {\n const geometry = feature.geometry;\n if (!geometry) continue;\n if (geometry.type === 'GeometryCollection') {\n for (const inner of geometry.geometries) {\n const kind = KIND_BY_TYPE[inner.type];\n if (kind) kinds.add(kind);\n }\n continue;\n }\n const kind = KIND_BY_TYPE[geometry.type];\n if (kind) kinds.add(kind);\n }\n return kinds;\n};\n\n/**\n * Pick sensible default MapLibre layer specs for the given FeatureCollection.\n * For polygon-first overlays the fill comes before the line so the outline\n * sits on top; otherwise the order is fill → line → circle so points stack\n * above areas.\n *\n * @ignore\n */\nexport const defaultLayersFor = (data: FeatureCollection): CustomGeoJSONLayerSpec[] => {\n const kinds = classify(data);\n const layers: CustomGeoJSONLayerSpec[] = [];\n if (kinds.has('polygon')) layers.push(FILL_LAYER);\n if (kinds.has('line')) layers.push(LINE_LAYER);\n if (kinds.has('point')) layers.push(POINT_LAYER);\n // Empty / unknown geometry → fall back to a circle layer; the source will\n // be empty so nothing renders, but the module still needs at least one layer.\n if (layers.length === 0) layers.push(POINT_LAYER);\n return layers;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type CustomGeoJSONLayerSpec, CustomGeoJSONModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { FeatureCollection } from 'geojson';\nimport type { StateSlice } from '../../types';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { BYODEntry, BYODSource } from './entry';\nimport { defaultLayersFor } from './layer-defaults';\n\n/**\n * Events fired by {@link BYODState}. Subscribe via `state.byod.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type BYODStateEvents = {\n /** History changed — entry added, removed, or cleared via `reset()`. */\n 'entries-change': readonly BYODEntry[];\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * Options accepted by {@link BYODState.addEntry}.\n *\n * @group Agent Toolkit\n */\nexport type AddBYODEntryOptions = {\n /**\n * Explicit MapLibre layer specs. When omitted, the slice inspects the\n * FeatureCollection and picks defaults by geometry type\n * (Point → circle, Line → line, Polygon → fill). Mixed-kind entries get\n * one layer per kind present.\n */\n layers?: CustomGeoJSONLayerSpec[];\n /** Provenance for the data. Defaults to `{ kind: 'integrator' }`. */\n source?: BYODSource;\n /**\n * Semantic entry id. Auto-suffixed (`-2`, `-3`, …) on collision. Defaults\n * to `byod-N`.\n */\n explicitId?: string;\n};\n\n/**\n * State slice for BYOD (bring-your-own-data) GeoJSON layers — customer-owned\n * data that doesn't fit places / routes / ranges / custom-geometries. Each\n * entry wraps a {@link CustomGeoJSONModule} so the agent can render, query,\n * and pipe it through analyseData / processData.\n *\n * Per-entry display model mirrors {@link CustomGeometriesState}: every entry\n * owns its own module, lazy-initialised on first show.\n *\n * @group Agent Toolkit\n */\nexport class BYODState implements ShownEntriesSlice, StateSlice {\n private _entries: BYODEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link BYODStateEvents}. */\n readonly events = new StateEvents<BYODStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly BYODEntry[] {\n return this._entries;\n }\n\n get latestEntry(): BYODEntry | undefined {\n return this._entries.at(-1);\n }\n\n /** Set of BYOD 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 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): BYODEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new BYOD entry. Under `single` mode the new entry replaces every older one (hides\n * + drops them before the new entry is pushed, so the caller never sees the new entry\n * rendered on top of a still-visible old one). Returns the assigned id.\n */\n async addEntry(data: FeatureCollection, label: string, options: AddBYODEntryOptions = {}): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n // Always run through `pickUniqueEntryId` — the bare `byod-${length}` fallback collides\n // when an earlier entry has been removed (e.g. add a, add b, remove a → length=1 and\n // the new fallback `byod-1` clashes with the surviving entry's id). The dedupe path\n // appends `-2`, `-3`, … on collision, matching the explicit-id behaviour.\n const entryId = pickUniqueEntryId(\n options.explicitId ?? `byod-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data,\n source: options.source ?? { kind: 'integrator' },\n layers: options.layers ?? defaultLayersFor(data),\n });\n this.events.emit('entries-change', this._entries);\n return entryId;\n }\n\n /**\n * Lazy-init the entry's CustomGeoJSONModule. The module is created with a\n * single source named `data`, configured with this entry's `layers`. Layer\n * changes after creation are not supported — drop the entry and add it back\n * to swap layers.\n */\n async getEntryModule(entryId: string): Promise<CustomGeoJSONModule> {\n const entry = this._requireEntry(entryId);\n entry._module ??= await CustomGeoJSONModule.get(this._ttMap, {\n sources: { data: { layers: entry.layers } },\n });\n return entry._module;\n }\n\n /** Render this entry on the map. */\n async showEntry(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n await this._hideOthersUnderSingleMode(entryId);\n const module = await this.getEntryModule(entryId);\n await module.show(entry.data, 'data');\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?._shown) return;\n 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): BYODEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`BYODState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every BYOD layer currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n for (const entry of this._entries) entry._module?.clear().catch(() => undefined);\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 { 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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { TrafficAreaAnalyticsAnalysis } from './analysis';\nimport type { TrafficAreaAnalyticsEntry, TrafficAreaAnalyticsParams } from './entry';\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 /** History changed — entry added, removed, or cleared via `reset()`. */\n 'entries-change': readonly TrafficAreaAnalyticsEntry[];\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Per-entry viz config changed (mode / metric / scaleMode / …). */\n 'config-change': { entryId: string; config: TrafficAreaAnalyticsConfig | undefined };\n /** A new analysis was attached to (or replaced on) an entry. */\n 'analysis-added': { entryId: string; analysis: TrafficAreaAnalyticsAnalysis };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * Append-only history of traffic-area-analytics aggregations, with per-entry\n * rendering. Each entry owns a dedicated {@link TrafficAreaAnalyticsModule},\n * lazy-initialised on first show. Mirrors the per-entry pattern used by\n * `RoutingState` / `CustomGeometriesState`.\n *\n * Live tile overlays (flow + incidents) live in {@link TrafficTilesState}.\n *\n * @group Agent Toolkit\n */\nexport class TrafficAreaAnalyticsState implements ShownEntriesSlice, StateSlice {\n private _entries: TrafficAreaAnalyticsEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link TrafficAreaAnalyticsStateEvents}. */\n readonly events = new StateEvents<TrafficAreaAnalyticsStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly TrafficAreaAnalyticsEntry[] {\n return this._entries;\n }\n\n get latestEntry(): TrafficAreaAnalyticsEntry | undefined {\n return this._entries.at(-1);\n }\n\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 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): TrafficAreaAnalyticsEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new entry. Auto-suffixes the id on collision. Under `single` mode the new entry\n * replaces every older one (hidden + dropped from history).\n */\n async addEntry(\n data: TrafficAreaAnalytics,\n label: string,\n params: TrafficAreaAnalyticsParams,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n const fallback = `tta-${this._entries.length}`;\n const entryId = explicitId\n ? pickUniqueEntryId(\n explicitId,\n this._entries.map((entry) => entry.id),\n )\n : fallback;\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data,\n params,\n });\n this.events.emit('entries-change', this._entries);\n return entryId;\n }\n\n /**\n * Attach an analysis to an existing entry. Replaces the prior result keyed by `analysis.name`\n * on the same entry (mirrors PlacesState / CustomGeometriesState semantics). Returns `false`\n * when the entry id is unknown.\n */\n addAnalysisToEntry(entryId: string, analysis: TrafficAreaAnalyticsAnalysis): 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 /**\n * Lazy-init the entry's TrafficAreaAnalyticsModule, wiring per-entry config-change events.\n * Throws on unknown id.\n */\n async getEntryModule(entryId: string): Promise<TrafficAreaAnalyticsModule> {\n const entry = this._requireEntry(entryId);\n if (!entry._module) {\n entry._module = await TrafficAreaAnalyticsModule.get(this._ttMap);\n entry._configChangeUnsub = entry._module.events.on('config-change', (config) => {\n entry._config = config;\n this.events.emit('config-change', { entryId, config });\n });\n }\n return entry._module;\n }\n\n /** Render this entry's analytics on its own module. Under `single` mode, hides every other shown entry first. */\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.getEntryModule(entryId);\n await module.clear();\n await module.show(entry.data);\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Hide the entry's analytics on its module. No-op when unknown or already hidden. */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n 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. Hides it first, then removes; emits `entries-change`. No-op on unknown id. */\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 const entry = this._entries[idx];\n entry._configChangeUnsub?.();\n entry._configChangeUnsub = undefined;\n entry._module = undefined;\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n }\n\n private _requireEntry(entryId: string): TrafficAreaAnalyticsEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`TrafficAreaAnalyticsState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every traffic-area-analytics entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n for (const entry of this._entries) {\n entry._configChangeUnsub?.();\n entry._module?.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-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' | 'customGeometries';\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('customGeometries').describe('Use the exact literal \"customGeometries\".'),\n id: z\n .string()\n .min(1)\n .describe('Id of a custom-geometries entry produced by a previous `processData` call.'),\n })\n .describe('A derived entry produced by a previous `processData` call.'),\n ])\n .describe(\n 'Tagged id `{ kind, id }`. `kind` must be exactly one of: \"place\" | \"places\" | \"ranges\" | \"customGeometries\" ' +\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\"` | `\"customGeometries\"` (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 schema doc for the `geometries` array consumed/produced by sandboxed\n * code in `processData` / `analyseData`. Each item is a\n * `PolygonFeature<CommonPlaceProps>`; only the non-GeoJSON-standard bits are\n * spelled out here.\n */\n\n/** @ignore */\nexport const GEOMETRIES_SCHEMA_DOC =\n '`geometries` is an array of Polygon/MultiPolygon Features. Each has `id` (the geometry-data-source id, ' +\n 'matches `place.properties.dataSources.geometry.id`) and `bbox`. `properties` varies by source — see ' +\n '`GEOMETRIES_PROPS_DOC`. Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, ' +\n '`turf.buffer`, … directly on these features. For h3, convert via `h3.polygonToCells(feature.geometry.coordinates, res)`.';\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `Place` properties shape, reused by\n * every tool whose LLM-authored code reads or returns a Places\n * FeatureCollection (`processData`, `analyseData`). Only the non-obvious\n * (non-GeoJSON-standard) bits are spelled out — the wrapping `Feature` /\n * `FeatureCollection` shape is assumed known.\n */\n\nimport type { FeatureFlags } from '../../types';\n\nconst EXPERIMENTAL_AREA_FIELDS_DOC =\n ' // Experimental — populated only when `experimentalSearch` is on and the place came from\\n' +\n ' // the exploration-search backend (DE / NL / FR area-tag pipeline).\\n' +\n ' areaId?: string; // id of the surrounding small-area polygon (few km², not a whole municipality)\\n' +\n ' areaCountry?: string; // ISO2 country of the area polygon\\n' +\n ' areaTags?: string[]; // e.g. \"coastal\", \"walkable\", \"transit_connected\"\\n';\n\n/**\n * Build the Place properties schema doc embedded in tool descriptions for\n * sandboxed code. `Places` is a FeatureCollection of Point Features; only the\n * `properties` shape (the TomTom-specific bit) is documented here.\n *\n * @ignore\n */\nexport const buildPlacesSchemaDoc = (flags: FeatureFlags): string =>\n '`places` is a FeatureCollection of Point Features. `Place.properties` shape:\\n' +\n '```ts\\n' +\n 'type PlaceProperties = {\\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?: { // when type === \"POI\"\\n' +\n ' name: string; phone?: string; url?: string;\\n' +\n ' brands?: string[];\\n' +\n ' categories: string[]; // POICategory enum, e.g. \"RESTAURANT\", \"CAFE_PUB\", \"GAS_STATION\", \"HOTEL_MOTEL\", \"ELECTRIC_VEHICLE_STATION\"\\n' +\n ' localizedCategories: string[]; // 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 hits also carry:\\n' +\n ' score?: number; // relevance (higher = better)\\n' +\n ' distance?: number; // meters from geoBias, when provided\\n' +\n ' info?: string;\\n' +\n ' // Geography hits also carry:\\n' +\n ' geographyType?: Array<\"Country\"|\"CountrySubdivision\"|\"CountrySecondarySubdivision\"|\"CountryTertiarySubdivision\"|\"Municipality\"|\"MunicipalitySubdivision\"|\"Neighbourhood\"|\"PostalCodeArea\">;\\n' +\n (flags.experimentalSearch ? EXPERIMENTAL_AREA_FIELDS_DOC : '') +\n ' // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\\n' +\n '};\\n' +\n '```\\n' +\n 'Areas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`.';\n\n/**\n * Default-flag (`experimentalSearch: false`) Places schema doc — preserved as a\n * named constant so non-flag-aware tools keep working.\n *\n * @ignore\n */\nexport const PLACES_SCHEMA_DOC = buildPlacesSchemaDoc({});\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `Route` properties shape, reused by\n * every tool whose LLM-authored code reads a Routes FeatureCollection\n * (`analyseData`, `processData`). Only the non-GeoJSON-standard bits are\n * spelled out — `Route` is a LineString Feature.\n */\n\n/** @ignore */\nexport const ROUTES_SCHEMA_DOC =\n '`routes` is a FeatureCollection of LineString Features (each route has `feature.bbox`). `Route.properties` shape:\\n' +\n '```ts\\n' +\n 'type RouteProperties = {\\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;\\n' +\n ' trafficDelayInSeconds: number;\\n' +\n ' trafficLengthInMeters: number;\\n' +\n ' departureTime: Date; arrivalTime: Date;\\n' +\n ' // EV-only:\\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 ' // Segments grouped by characteristic. Every section has { id, startPointIndex, endPointIndex }\\n' +\n ' // unless noted; *Index values reference `geometry.coordinates`.\\n' +\n ' sections: {\\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\\n' +\n ' tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>;\\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;\\n' +\n ' delayInSeconds?: number;\\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\\n' +\n ' guidance?: { instructions: Array<{ pointIndex: number; maneuver: string; street?: string; message?: string }> };\\n' +\n '};\\n' +\n '```\\n' +\n '`pointIndex` values reference positions in `geometry.coordinates`. Guard `?` fields with `?.` / `??`. ' +\n 'For \"how long / how far\" questions, use `summary.lengthInMeters` / `summary.travelTimeInSeconds`.';\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 * Shared scaffolding for tools that consume multi-kind entry inputs (`analyseData`,\n * `processData`). Centralises the per-{@link EntryDataKind} metadata each tool needs\n * to build its `*EntryIDs` schema fields, sandbox-doc fragments, schema-doc fragments,\n * scope schema and \"at least one of …\" refine — so the tool files themselves stay\n * focused on their own contract (return-shape validator, per-tool extras, execute body).\n */\n\nimport { z } from 'zod';\nimport type { EntryDataKind } from '../../types';\nimport { geometriesEntryIDsSchema } from './geometries-id';\nimport { GEOMETRIES_SCHEMA_DOC } from './geometries-schema-doc';\nimport { PLACES_SCHEMA_DOC } from './places-schema-doc';\nimport { ROUTES_SCHEMA_DOC } from './routes-schema-doc';\nimport { placesEntryIDsSchema, routesEntryIDsSchema } from './schema';\n\n/**\n * Canonical ordering for {@link EntryDataKind}. Used wherever per-kind doc fragments or\n * field lists are concatenated — keeps the layout stable regardless of the order kinds\n * appear in a scope or enabled-kinds list.\n *\n * @ignore\n */\nexport const ALL_ENTRY_DATA_KINDS = [\n 'places',\n 'routes',\n 'incidents',\n 'customGeometries',\n 'trafficAreaAnalytics',\n 'byod',\n] as const satisfies readonly EntryDataKind[];\n\n/**\n * Per-kind scope envelope. The classifier emits one of these in `toolScopes.<toolName>`\n * to narrow which kinds the tool's per-turn input schema and code-doc mention.\n *\n * @ignore\n */\nexport type EntryKindScope = { kinds: readonly EntryDataKind[] };\n\n/**\n * Build a zod scope schema that only accepts kinds within the supplied enabled set.\n * Mirrors the per-tool scope guard so a classifier picking a disabled kind fails-open\n * (the malformed scope is dropped, full surface stays in place).\n *\n * @ignore\n */\nexport const buildEntryKindScopeSchema = (enabled: readonly EntryDataKind[]) =>\n z.object({\n kinds: z.array(z.enum(enabled as readonly [EntryDataKind, ...EntryDataKind[]])).min(1),\n });\n\n/**\n * `extra` fragment supplied by the tool — appended to each generated input-field\n * description so callers see tool-specific hints alongside the shared metadata.\n *\n * @ignore\n */\ntype FieldBuildOptions = {\n verb: 'analyse' | 'process';\n requiredHint: string;\n};\n\n/**\n * Per-kind metadata. One row per {@link EntryDataKind}. Owns:\n * - field name + label used in schemas and \"at least one of\" messages;\n * - the factory that produces the `*EntryIDs` zod field for a given tool verb;\n * - the one-line sandbox-doc fragment (\"• `places` — merged FeatureCollection …\");\n * - the deep schema-doc fragment (only included when scope commits to this kind);\n * - which recall tool the LLM should call to list available ids.\n *\n * @ignore\n */\nexport type EntryKindMeta = {\n fieldName: string;\n fieldLabel: string;\n buildField: (options: FieldBuildOptions) => z.ZodTypeAny;\n sandboxDoc: string;\n schemaDoc: string;\n recallTool: string;\n};\n\nconst placesField = (options: FieldBuildOptions) =>\n placesEntryIDsSchema({\n verb: options.verb,\n extra: 'Injected as `places` (merged) and `placesByEntry[id]`. Undefined when omitted. ' + options.requiredHint,\n });\n\nconst routesField = (options: FieldBuildOptions) =>\n routesEntryIDsSchema({\n verb: options.verb,\n extra: 'Injected as `routes` (merged) and `routesByEntry[id]`. Undefined when omitted. ' + options.requiredHint,\n });\n\nconst incidentsField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing incidents entries to ${options.verb} (e.g. [\"incidents-0\"]). ` +\n 'Injected as `incidents` (merged) and `incidentsByEntry[id]`. Undefined when omitted. ' +\n 'Use `recallState` to list incident entries. ' +\n options.requiredHint,\n );\n\nconst customGeometriesField = (options: FieldBuildOptions) =>\n geometriesEntryIDsSchema\n .optional()\n .describe(\n 'Tagged polygon sources `{ kind, id }` (see geometriesEntryIDsSchema). ' +\n 'Injected as `geometries`. Undefined when omitted. ' +\n options.requiredHint,\n );\n\nconst trafficAreaAnalyticsField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing traffic-area-analytics entries to ${options.verb} (e.g. [\"tta-0\"]). ` +\n 'Injected as `trafficAreaAnalytics` (merged FeatureCollection of tile/hex regions with metric ' +\n 'properties) and `trafficAreaAnalyticsByEntry[id]`. Undefined when omitted. ' +\n 'Use `recallState` to list available ids. ' +\n options.requiredHint,\n );\n\nconst byodField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing BYOD entries to ${options.verb} (e.g. [\"byod-0\"]) — customer-authored GeoJSON layers. ` +\n 'Injected as `byod` (merged FeatureCollection) and `byodByEntry[id]`. Undefined when omitted. ' +\n 'Use `recallByod` to list available ids. ' +\n options.requiredHint,\n );\n\n/**\n * Single source of truth for the per-kind input vocabulary. Both `analyseData` and\n * `processData` read this map to assemble their input schemas and doc fragments.\n *\n * @ignore\n */\nexport const ENTRY_KIND_META: Record<EntryDataKind, EntryKindMeta> = {\n places: {\n fieldName: 'placesEntryIDs',\n fieldLabel: '`placesEntryIDs`',\n buildField: placesField,\n sandboxDoc:\n '• `places` — merged FeatureCollection over `placesEntryIDs`; `placesByEntry[id]` keeps them separate.',\n schemaDoc: PLACES_SCHEMA_DOC,\n recallTool: 'recallPlaces',\n },\n routes: {\n fieldName: 'routesEntryIDs',\n fieldLabel: '`routesEntryIDs`',\n buildField: routesField,\n sandboxDoc:\n '• `routes` — merged FeatureCollection over `routesEntryIDs`; `routesByEntry[id]` keeps them separate.',\n schemaDoc: ROUTES_SCHEMA_DOC,\n recallTool: 'recallRoutes',\n },\n incidents: {\n fieldName: 'incidentsEntryIDs',\n fieldLabel: '`incidentsEntryIDs`',\n buildField: incidentsField,\n sandboxDoc:\n '• `incidents` — flat `TrafficIncident[]` over `incidentsEntryIDs`; `incidentsByEntry[id]` keeps them separate.',\n schemaDoc: '',\n recallTool: 'recallState',\n },\n customGeometries: {\n fieldName: 'geometriesEntryIDs',\n fieldLabel: '`geometriesEntryIDs`',\n buildField: customGeometriesField,\n sandboxDoc:\n '• `geometries` — mixed Polygon/MultiPolygon array over `geometriesEntryIDs`; every feature carries ' +\n '`properties._source: { kind, id }` so you can filter by role (see `GEOMETRIES_PROPS_DOC`).',\n schemaDoc: GEOMETRIES_SCHEMA_DOC,\n recallTool: 'recallGeometries',\n },\n trafficAreaAnalytics: {\n fieldName: 'trafficAreaAnalyticsEntryIDs',\n fieldLabel: '`trafficAreaAnalyticsEntryIDs`',\n buildField: trafficAreaAnalyticsField,\n sandboxDoc:\n '• `trafficAreaAnalytics` — merged TrafficAreaAnalytics FeatureCollection over `trafficAreaAnalyticsEntryIDs`. ' +\n 'Each feature is a tile / hex region with metric properties on `feature.properties` (e.g. `congestionLevel`, ' +\n '`speed`, `freeFlowSpeed`, `travelTime`, `networkLength`). `trafficAreaAnalyticsByEntry[id]` keeps each ' +\n \"request's collection separate. The collection's top-level `properties` (metrics list, date range) come from \" +\n 'the first entry when ids span multiple requests.',\n schemaDoc: '',\n recallTool: 'recallState',\n },\n byod: {\n fieldName: 'byodEntryIDs',\n fieldLabel: '`byodEntryIDs`',\n buildField: byodField,\n sandboxDoc:\n '• `byod` — merged customer-authored GeoJSON FeatureCollection over `byodEntryIDs`; mixed Point / LineString / ' +\n 'Polygon features are normal (BYOD layers carry whatever the integrator uploaded). `byodByEntry[id]` keeps ' +\n 'each entry separate. Treat `feature.properties` as opaque application-specific data.',\n schemaDoc: '',\n recallTool: 'recallByod',\n },\n};\n\n/**\n * Resolve the set of kinds active for a single per-turn build: the intersection of\n * the agent's enabled kinds and the classifier's per-turn scope. `scope === undefined`\n * means \"no per-turn narrowing — every enabled kind stays in scope\".\n *\n * @ignore\n */\nexport const resolveActiveKinds = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n): readonly EntryDataKind[] =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => enabled.includes(kind) && (!scope || scope.kinds.includes(kind)));\n\n/**\n * Predicate form of {@link resolveActiveKinds} for a single kind. Used when generating\n * tool-specific fields (e.g. analyseData's `monitor`) that should only appear when\n * `incidents` is in the active set.\n *\n * @ignore\n */\nexport const isKindActive = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n kind: EntryDataKind,\n): boolean => enabled.includes(kind) && (!scope || scope.kinds.includes(kind));\n\n/**\n * Slash-joined list of scoped `*EntryIDs` field labels — used in \"At least one of `A` / `B`\n * must be set.\" messages and in per-field hints. Order follows {@link ALL_ENTRY_DATA_KINDS}.\n *\n * @ignore\n */\nexport const buildEntryFieldList = (active: readonly EntryDataKind[]): string =>\n active.map((kind) => ENTRY_KIND_META[kind].fieldLabel).join(' / ');\n\n/**\n * Join the per-kind sandbox-doc fragments in canonical order for the supplied active set.\n * Returns the multi-line \"Each input is …\" block.\n *\n * @ignore\n */\nexport const buildEntryKindSandboxDocs = (active: readonly EntryDataKind[]): string =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => active.includes(kind))\n .map((kind) => ENTRY_KIND_META[kind].sandboxDoc)\n .join('\\n');\n\n/**\n * Join the per-kind deep schema-doc fragments in canonical order for the supplied active\n * set. Empty when no active kind has a schema doc (only places / routes / customGeometries\n * carry one today).\n *\n * @ignore\n */\nexport const buildEntryKindSchemaDocs = (active: readonly EntryDataKind[]): string =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => active.includes(kind))\n .map((kind) => ENTRY_KIND_META[kind].schemaDoc)\n .filter((doc) => doc.length > 0)\n .join('\\n\\n');\n\n/**\n * Build the input-shape Record passed into `z.object({...})`. Both tools share this\n * shape — each kind in the active set contributes its `*EntryIDs` field via\n * {@link EntryKindMeta.buildField}. Tools layer their own per-tool fields onto the\n * resulting record before calling `z.object`.\n *\n * @ignore\n */\nexport const buildEntryIdFields = (\n active: readonly EntryDataKind[],\n options: FieldBuildOptions,\n): Record<string, z.ZodTypeAny> => {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n if (!active.includes(kind)) continue;\n const meta = ENTRY_KIND_META[kind];\n shape[meta.fieldName] = meta.buildField(options);\n }\n return shape;\n};\n\n/**\n * Set of LLM-input objects that contain any `*EntryIDs` field. Used by the shared\n * \"at least one of …\" refine so both tools agree on what counts as \"input present\".\n *\n * `geometriesEntryIDs` carries `GeometriesId[]`; the rest carry `string[]`. We treat\n * either as \"present\" when the array has length, which is what the existing inline\n * refines did.\n *\n * @ignore\n */\nexport const hasAnyEntryIds = (value: Record<string, unknown>): boolean => {\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n const arr = value[ENTRY_KIND_META[kind].fieldName] as unknown[] | undefined;\n if (arr?.length) return true;\n }\n return false;\n};\n\n/**\n * Message used by the shared \"at least one of …\" refine. Lists the active kinds'\n * field labels and the slash-joined recall-tool hints for the same set.\n *\n * @ignore\n */\nexport const buildAtLeastOneEntryIdMessage = (active: readonly EntryDataKind[]): string => {\n const fields = buildEntryFieldList(active);\n const recalls = Array.from(new Set(active.map((kind) => ENTRY_KIND_META[kind].recallTool)))\n .map((tool) => `\\`${tool}\\``)\n .join(' / ');\n return `At least one of ${fields} must be set. Call ${recalls} first to list available ids.`;\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 * 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\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' | 'customGeometries';\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 customGeometries: GeometriesId[];\n};\n\nconst groupByKind = (sources: readonly GeometriesId[]): ParsedByKind => {\n const out: ParsedByKind = { place: [], places: [], ranges: [], customGeometries: [] };\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: 'customGeometries', 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.customGeometries, 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: 'customGeometries' })),\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 'customGeometries':\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 * Cross-slice input prep for the merged `analyseData` / `processData` tools.\n *\n * Per-type tools (analyse-places, process-routes, …) default to \"use the last\n * entry of this slice\" when their id array is omitted. The merged tools don't\n * do that: each input kind is purely opt-in. If `placesEntryIDs` is missing,\n * the sandbox sees `places === undefined`; if it's `[]`, same. The LLM\n * explicitly lists every kind it wants to consider.\n *\n * The helper resolves every requested kind, builds the sandbox-facing\n * `merged + byEntry` views, and returns the resolved entries so the analyse\n * path can attach an analysis to each contributing entry afterwards.\n */\n\nimport type { Place, PolygonFeature, Route, TrafficAreaAnalytics, TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection as GeoJSONFeatureCollection } from 'geojson';\nimport type {\n BYODEntry,\n PlacesEntry,\n RoutesEntry,\n TrafficAreaAnalyticsEntry,\n TrafficIncidentsEntry,\n} from '../../state';\nimport type { ToolState } from '../../types';\nimport type { GeometriesId } from './geometries-id';\nimport { type AffectedEntry, collectInputGeometries, type SkippedSource } from './state-inputs';\n\ntype FeatureCollection<F> = { type: 'FeatureCollection'; features: F[] };\n\n/** Inputs the LLM passes through the tool schema. All optional, but at least one must be set. */\nexport type MultiInputIds = {\n placesEntryIDs?: readonly string[];\n routesEntryIDs?: readonly string[];\n incidentsEntryIDs?: readonly string[];\n geometriesEntryIDs?: readonly GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: readonly string[];\n byodEntryIDs?: readonly string[];\n};\n\n/** Resolved entries, kept for the attach step in analyseData. Empty arrays for kinds the caller didn't request. */\nexport type ResolvedEntries = {\n places: PlacesEntry[];\n routes: RoutesEntry[];\n incidents: TrafficIncidentsEntry[];\n trafficAreaAnalytics: TrafficAreaAnalyticsEntry[];\n byod: BYODEntry[];\n};\n\n/** Sandbox-facing inputs. Each field is `undefined` when its kind wasn't requested. */\nexport type SandboxInputs = {\n places: FeatureCollection<Place> | undefined;\n placesByEntry: Record<string, FeatureCollection<Place>> | undefined;\n routes: FeatureCollection<Route> | undefined;\n routesByEntry: Record<string, FeatureCollection<Route>> | undefined;\n incidents: TrafficIncident[] | undefined;\n incidentsByEntry: Record<string, TrafficIncident[]> | undefined;\n geometries: PolygonFeature[] | undefined;\n /**\n * Merged TrafficAreaAnalytics FeatureCollection — every feature is a tile/hex region with\n * the metric properties from the SDK (`congestionLevel`, `speed`, `travelTime`, …). When\n * multiple entries are requested, their features are concatenated; `trafficAreaAnalyticsByEntry`\n * keeps them separate.\n */\n trafficAreaAnalytics: TrafficAreaAnalytics | undefined;\n trafficAreaAnalyticsByEntry: Record<string, TrafficAreaAnalytics> | undefined;\n /**\n * Merged BYOD FeatureCollection. Each requested entry contributes its features; mixed\n * geometry types are normal (BYOD layers can be Point / LineString / Polygon). When multiple\n * entries are requested they're concatenated; `byodByEntry` keeps them separate.\n */\n byod: GeoJSONFeatureCollection | undefined;\n byodByEntry: Record<string, GeoJSONFeatureCollection> | undefined;\n};\n\n/** Geometries metadata — only populated when `geometriesEntryIDs` was passed. */\nexport type GeometriesMeta = {\n affectedEntries: AffectedEntry[];\n sourceIds: GeometriesId[];\n skipped: SkippedSource[];\n};\n\nexport type PreparedInputs = {\n resolved: ResolvedEntries;\n sandbox: SandboxInputs;\n geometries: GeometriesMeta;\n};\n\n/**\n * Resolve every requested input kind, build sandbox views, and surface the\n * resolved entries to the caller. The caller (analyseData / processData) is\n * responsible for enforcing \"at least one input must be set\" via the zod\n * schema; this helper enforces it as a safety net.\n *\n * @ignore\n */\n// Per-id-array hint used by the \"at least one input must be set\" guard. Kept inline so the\n// guard's error message stays accurate when new kinds get added.\nconst ID_FIELDS = [\n 'placesEntryIDs',\n 'routesEntryIDs',\n 'incidentsEntryIDs',\n 'geometriesEntryIDs',\n 'trafficAreaAnalyticsEntryIDs',\n 'byodEntryIDs',\n] as const;\n\nconst hasAnyInput = (ids: MultiInputIds): boolean => ID_FIELDS.some((field) => (ids[field]?.length ?? 0) > 0);\n\n// Pull the slice-resolution stack out of `prepareMultiInputs` so each function's cognitive\n// complexity stays under the SonarQube cap. Returns the resolved entries grouped by kind, or\n// the first per-slice resolution error encountered.\nconst resolveAllSlices = (ids: MultiInputIds, state: ToolState): { value: ResolvedEntries } | { error: string } => {\n const placesResult = resolveSliceEntries(ids.placesEntryIDs, state.places, {\n missingId: (id) => `No places entry with id \"${id}\". Use recallPlaces to list available IDs.`,\n });\n if ('error' in placesResult) return placesResult;\n\n const routesResult = resolveSliceEntries(ids.routesEntryIDs, state.routing, {\n missingId: (id) => `No routes entry with id \"${id}\". Use recallRoutes to list available IDs.`,\n });\n if ('error' in routesResult) return routesResult;\n\n const incidentsResult = resolveSliceEntries(ids.incidentsEntryIDs, state.trafficIncidents, {\n missingId: (id) => `No incidents entry with id \"${id}\". Call getTrafficIncidents first.`,\n });\n if ('error' in incidentsResult) return incidentsResult;\n\n const trafficAreaAnalyticsResult = resolveSliceEntries(\n ids.trafficAreaAnalyticsEntryIDs,\n state.trafficAreaAnalytics,\n {\n missingId: (id) => `No traffic-area-analytics entry with id \"${id}\". Call getTrafficAreaAnalytics first.`,\n },\n );\n if ('error' in trafficAreaAnalyticsResult) return trafficAreaAnalyticsResult;\n\n const byodResult = resolveSliceEntries(ids.byodEntryIDs, state.byod, {\n missingId: (id) => `No BYOD entry with id \"${id}\". Use recallByod to list available IDs.`,\n });\n if ('error' in byodResult) return byodResult;\n\n return {\n value: {\n places: placesResult.value,\n routes: routesResult.value,\n incidents: incidentsResult.value,\n trafficAreaAnalytics: trafficAreaAnalyticsResult.value,\n byod: byodResult.value,\n },\n };\n};\n\nexport const prepareMultiInputs = async (\n ids: MultiInputIds,\n state: ToolState,\n): Promise<{ value: PreparedInputs } | { error: string }> => {\n if (!hasAnyInput(ids)) {\n return {\n error:\n 'At least one of `placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, ' +\n '`geometriesEntryIDs`, `trafficAreaAnalyticsEntryIDs`, `byodEntryIDs` must be set. ' +\n 'Use `recallPlaces` / `recallRoutes` / `recallGeometries` / `recallByod` / `recallState` to list available ids.',\n };\n }\n\n const resolved = resolveAllSlices(ids, state);\n if ('error' in resolved) return resolved;\n const {\n places: placesEntries,\n routes: routesEntries,\n incidents: incidentsEntries,\n trafficAreaAnalytics: trafficAreaAnalyticsEntries,\n byod: byodEntries,\n } = resolved.value;\n\n let geometries: PolygonFeature[] | undefined;\n let geometriesMeta: GeometriesMeta = { affectedEntries: [], sourceIds: [], skipped: [] };\n if (ids.geometriesEntryIDs?.length) {\n const collected = await collectInputGeometries(ids.geometriesEntryIDs, state);\n if ('error' in collected) return collected;\n geometries = collected.value.geometries;\n geometriesMeta = {\n affectedEntries: collected.value.affectedEntries,\n sourceIds: collected.value.contributingSourceIds,\n skipped: collected.value.skipped,\n };\n }\n\n return {\n value: {\n resolved: {\n places: placesEntries,\n routes: routesEntries,\n incidents: incidentsEntries,\n trafficAreaAnalytics: trafficAreaAnalyticsEntries,\n byod: byodEntries,\n },\n sandbox: {\n places: placesEntries.length\n ? { type: 'FeatureCollection', features: placesEntries.flatMap((e) => e.places) }\n : undefined,\n placesByEntry: placesEntries.length\n ? Object.fromEntries(\n placesEntries.map((e) => [\n e.id,\n { type: 'FeatureCollection', features: [...e.places] } as FeatureCollection<Place>,\n ]),\n )\n : undefined,\n routes: routesEntries.length\n ? { type: 'FeatureCollection', features: routesEntries.flatMap((e) => e.data.features) }\n : undefined,\n routesByEntry: routesEntries.length\n ? Object.fromEntries(\n routesEntries.map((e) => [\n e.id,\n {\n type: 'FeatureCollection',\n features: [...e.data.features],\n } as FeatureCollection<Route>,\n ]),\n )\n : undefined,\n incidents: incidentsEntries.length ? incidentsEntries.flatMap((e) => e.data) : undefined,\n incidentsByEntry: incidentsEntries.length\n ? Object.fromEntries(incidentsEntries.map((e) => [e.id, [...e.data]]))\n : undefined,\n geometries,\n trafficAreaAnalytics: trafficAreaAnalyticsEntries.length\n ? mergeTrafficAreaAnalytics(trafficAreaAnalyticsEntries.map((e) => e.data))\n : undefined,\n trafficAreaAnalyticsByEntry: trafficAreaAnalyticsEntries.length\n ? Object.fromEntries(trafficAreaAnalyticsEntries.map((e) => [e.id, e.data]))\n : undefined,\n byod: byodEntries.length\n ? {\n type: 'FeatureCollection',\n features: byodEntries.flatMap((e) => e.data.features),\n }\n : undefined,\n byodByEntry: byodEntries.length\n ? Object.fromEntries(byodEntries.map((e) => [e.id, e.data]))\n : undefined,\n },\n geometries: geometriesMeta,\n },\n };\n};\n\n// Concatenate features from N TrafficAreaAnalytics entries into a single FeatureCollection.\n// Collection-level `properties` (metrics list, date range) are taken from the first entry —\n// the LLM should treat the merged collection as feature-level when ids span different requests.\nconst mergeTrafficAreaAnalytics = (entries: readonly TrafficAreaAnalytics[]): TrafficAreaAnalytics => ({\n type: 'FeatureCollection',\n properties: entries[0].properties,\n features: entries.flatMap((e) => e.features),\n});\n\nconst resolveSliceEntries = <E extends { id: string }>(\n ids: readonly string[] | undefined,\n slice: { entries: readonly E[] },\n hints: { missingId: (id: string) => string },\n): { value: E[] } | { error: string } => {\n if (!ids?.length) return { value: [] };\n const out: E[] = [];\n for (const id of ids) {\n const found = slice.entries.find((entry) => entry.id === id);\n if (!found) return { error: hints.missingId(id) };\n out.push(found);\n }\n return { value: out };\n};\n\n/**\n * LLM-facing doc string that spells out which turf/h3 operations bridge across\n * input kinds. The merged tools both expose `places` (Points) / `routes`\n * (LineStrings) / `incidents` (Points + LineStrings) / `geometries` (Polygons\n * / MultiPolygons) / `trafficAreaAnalytics` (Polygon tiles with metrics) to\n * the same sandbox — but the model frequently assumes those slots are\n * isolated and gives up. Including this in the `code` field description\n * unblocks the common cross-kind work.\n *\n * @ignore\n */\nexport const CROSS_KIND_OPS_DOC =\n 'CROSS-KIND OPS — `turf` and `h3` operate on EVERY feature regardless of which input slot it came from. ' +\n 'You can freely combine `places` (Points), `routes` (LineStrings), `incidents` (Point or LineString), ' +\n '`geometries` (Polygon / MultiPolygon), and `trafficAreaAnalytics` (Polygon tiles with `properties` like ' +\n '`congestionLevel` / `speed` / `travelTime` / `freeFlowSpeed`) in the same call. Common bridges:\\n' +\n '• Point ↔ LineString — `turf.pointToLineDistance(point, lineFeature, { units: \"meters\" })`, ' +\n '`turf.nearestPointOnLine(line, point)`.\\n' +\n '• Point ↔ Polygon — `turf.booleanPointInPolygon(point, poly)`, ' +\n '`turf.pointsWithinPolygon(turf.featureCollection(points), poly)`.\\n' +\n '• LineString ↔ Polygon — `turf.lineIntersect(line, poly)`, `turf.booleanCrosses(line, poly)`, ' +\n '`turf.lineSplit(line, poly)`.\\n' +\n '• Polygon ↔ Polygon — `turf.union/intersect/difference(turf.featureCollection([a, b]))` (Turf v7 takes a single collection).\\n' +\n '• Point/LineString ↔ trafficAreaAnalytics tile — `turf.booleanPointInPolygon(p, tile)` for points; ' +\n '`turf.lineIntersect(routeLine, tile)` for routes; iterate `trafficAreaAnalytics.features` to find which ' +\n 'tile a place/route segment falls into and read its metric (`tile.properties.congestionLevel`, etc.).\\n' +\n '• Buffer to bridge kinds — `turf.buffer(anyFeature, meters/1000, { units: \"kilometers\" })` turns a point or ' +\n 'line into a polygon you can then filter against.\\n' +\n '• Bbox of anything — `turf.bbox(featureOrCollection)`.\\n' +\n '• Centroid for Point↔shape distance — `turf.centroid(polygonOrLine).geometry.coordinates`.\\n' +\n '• h3 — `h3.latLngToCell(lat, lng, res)` for any Point; `h3.polygonToCells(poly.geometry.coordinates, res)` ' +\n 'for any Polygon; for a LineString iterate `geometry.coordinates` and bin each `[lng, lat]`.\\n' +\n 'Note: arguments to turf functions are GeoJSON Features — pass the whole feature (`places.features[i]`, ' +\n '`routes.features[0]`, `geometries[i]`, `incidents[i]`, `trafficAreaAnalytics.features[i]`), not the bare ' +\n '`geometry.coordinates` (a few helpers accept coords but most expect Features). Always check the function ' +\n 'signature at https://turfjs.org/docs/.';\n\n/** Names injected into the sandbox by analyseData / processData, in fixed order. */\nexport const MULTI_INPUT_SANDBOX_PARAMS = [\n 'places',\n 'placesByEntry',\n 'routes',\n 'routesByEntry',\n 'incidents',\n 'incidentsByEntry',\n 'geometries',\n 'trafficAreaAnalytics',\n 'trafficAreaAnalyticsByEntry',\n 'byod',\n 'byodByEntry',\n 'h3',\n 'turf',\n] as const;\n\n/**\n * Pack `prepared.sandbox` into the positional argument array expected by\n * `runSandboxedFn`, in the same order as {@link MULTI_INPUT_SANDBOX_PARAMS}.\n * Trailing `h3` and `turf` are supplied by the caller.\n *\n * @ignore\n */\nexport const packSandboxArgs = (sandbox: SandboxInputs, libs: { h3: unknown; turf: unknown }): readonly unknown[] => [\n sandbox.places,\n sandbox.placesByEntry,\n sandbox.routes,\n sandbox.routesByEntry,\n sandbox.incidents,\n sandbox.incidentsByEntry,\n sandbox.geometries,\n sandbox.trafficAreaAnalytics,\n sandbox.trafficAreaAnalyticsByEntry,\n sandbox.byod,\n sandbox.byodByEntry,\n libs.h3,\n libs.turf,\n];\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 * Shared executor primitives for `analyseData` and `processData`.\n *\n * Both tools follow the same skeleton:\n *\n * 1. resolve entries by id (with a \"last entry\" default)\n * 2. assemble a merged FeatureCollection + per-entry view\n * 3. run sandboxed code\n * 4. validate the result\n * 5. attach the analysis (`analyseData` only) to each contributing entry\n *\n * These helpers cover steps 1–2 and step 5 generically. The sandbox call\n * itself stays in {@link runSandboxedFn} / {@link validateAnalysisResult}\n * because the per-tool param names and arg lists are too specific to wrap\n * usefully.\n */\n\nimport { type AnalysisOutputFormat } from './sandbox-code';\n\n/** Minimal shape required by {@link resolveEntriesByIds}. */\ntype SliceWithEntries<E> = { entries: readonly E[] };\n\n/**\n * Resolve entries by id with the standard \"no ids → last entry\" fallback and\n * uniform missing-entry errors. Pulls the inline `ids?.length ? map(find) :\n * [last]` pattern out of every analyse/process executor.\n *\n * @ignore\n */\nexport const resolveEntriesByIds = <E extends { id: string }>(\n ids: readonly string[] | undefined,\n slice: SliceWithEntries<E>,\n hints: { missingId: (id: string) => string; emptySlice: string },\n): { value: E[] } | { error: string } => {\n if (ids?.length) {\n const resolved: E[] = [];\n for (const id of ids) {\n const found = slice.entries.find((entry) => entry.id === id);\n if (!found) return { error: hints.missingId(id) };\n resolved.push(found);\n }\n return { value: resolved };\n }\n const last = slice.entries.at(-1);\n if (!last) return { error: hints.emptySlice };\n return { value: [last] };\n};\n\n/**\n * Build a merged FeatureCollection plus a per-entry view from a list of\n * resolved entries. `extractFeatures` adapts each entry's feature-bearing\n * field name (`places`, `data.features`, …) to a uniform shape so the sandbox\n * receives the same inputs regardless of which slice the entries came from.\n *\n * @ignore\n */\nexport const buildFeatureViews = <E extends { id: string }, F>(\n entries: readonly E[],\n extractFeatures: (entry: E) => readonly F[],\n): {\n merged: { type: 'FeatureCollection'; features: F[] };\n byEntry: Record<string, { type: 'FeatureCollection'; features: F[] }>;\n} => ({\n merged: { type: 'FeatureCollection', features: entries.flatMap((entry) => [...extractFeatures(entry)]) },\n byEntry: Object.fromEntries(\n entries.map((entry) => [\n entry.id,\n { type: 'FeatureCollection' as const, features: [...extractFeatures(entry)] },\n ]),\n ),\n});\n\n/** Minimal shape required by {@link attachAnalysisToEntries}. Every state */\n/** slice's `addAnalysisToEntry` matches this — the per-slice `Analysis` */\n/** type is structurally identical across places/routes/incidents/custom. */\ntype AnalysisSink<E extends { id: string }> = {\n addAnalysisToEntry: (entryId: E['id'], analysis: AnalysisRecord) => boolean;\n};\n\ntype AnalysisRecord = {\n name: string;\n timestamp: number;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n data: unknown;\n};\n\n/**\n * Attach an analysis to every entry in `entries` on `slice`. Shares the\n * timestamp across the batch so the agent UI can correlate per-slice updates\n * that originated from the same call.\n *\n * @ignore\n */\nexport const attachAnalysisToEntries = <E extends { id: string }>(\n slice: AnalysisSink<E>,\n entries: readonly E[],\n payload: {\n name: string;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n analysis: unknown;\n timestamp?: number;\n },\n): void => {\n const timestamp = payload.timestamp ?? Date.now();\n for (const entry of entries) {\n slice.addAnalysisToEntry(entry.id, {\n name: payload.name,\n timestamp,\n ...(payload.description && { description: payload.description }),\n outputFormat: payload.outputFormat,\n data: payload.analysis,\n });\n }\n};\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 const injectedList = injected.map((name) => `\\`${name}\\``).join(', ');\n return (\n `In scope: ${injectedList} — injected as parameters; use directly. ` +\n 'No `require` / `import` / `await import` (body is an AsyncFunction, not a module — such lines are stripped silently). ' +\n '`h3` = h3-js (hex math). `turf` = @turf/turf v7 (verify names at https://turfjs.org/docs/). ' +\n 'TURF INPUTS: pass a Feature or FeatureCollection — never a bare Array (throws \"Unknown Geometry Type\"). ' +\n 'FC-shaped inputs (`places`, `routes`, `trafficAreaAnalytics`, `byod`) → pass directly: `turf.bbox(places)`. ' +\n 'Array inputs (`incidents`, `geometries`, `places.features`, …) → iterate per-feature ' +\n '(`for (const f of places.features) turf.X(f)`) or wrap with `turf.featureCollection([...])`. ' +\n 'Never `.map(f => f.geometry.coordinates)` / `.flatMap(f => f.geometry.coordinates)` to feed turf — ' +\n 'that strips the feature wrapper turf reads and throws \"coordinates must be an Array\" (or worse, mixes ' +\n 'Point/LineString shapes silently). Pass the whole feature and let turf read the geometry: ' +\n '`turf.pointToLineDistance(point, lineFeature, { units: \"meters\" })`. `incidents` mixes Point + LineString ' +\n 'features — guard with `if (inc.geometry.type === \"LineString\")` before line-only ops. ' +\n 'TURF v7 SET OPS: `union`/`intersect`/`difference` take ONE FeatureCollection — ' +\n '`turf.difference(turf.featureCollection([outer, inner]))`. The v6 two-arg form throws. ' +\n 'PERF: for large-N nearest/within queries, pre-bucket points with ' +\n '`h3.latLngToCell(lat, lng, res)` + `h3.gridDisk(cell, k)` and only run turf on same-/neighbour-cell ' +\n 'pairs — turns O(N·M) into ~O(N+M). Pick `res` so each cell ≈ your query radius. ' +\n 'No spatial search or HTTP — call `discoverPlaces` BEFORE this tool for new places. ' +\n 'Top-level `await`/locals/consts fine. End with `return …;`.'\n );\n};\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 // RHS forms we recognise as redeclarations of injected identifiers:\n // require('…') / import('…') / await import('…') — module-system reaches\n // arguments[N].turf / arguments[N]['turf'] — reaching for the same param off `arguments`\n // Anything else (e.g. `const turf = computeTurf()`) stays untouched so we never corrupt\n // genuine user code that happens to shadow an injected name.\n const rhs =\n String.raw`(?:(?:await\\s+)?(?:require|import)\\s*\\(` +\n String.raw`|arguments\\s*\\[\\s*\\d+\\s*\\]\\s*(?:\\.\\w+|\\[\\s*['\"]\\w+['\"]\\s*\\]))`;\n const pattern = new RegExp(\n String.raw`^[ \\t]*(?:const|let|var)[ \\t]+(?:${names})[ \\t]*=[ \\t]*${rhs}[^\\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: /coordinates must be an Array|Unknown Geometry Type/i,\n hint: 'turf was handed a raw coordinate (or a bare Array of features) instead of a Feature / FeatureCollection. Do NOT extract `.geometry.coordinates` with `.map` / `.flatMap` and feed the result to turf — pass the WHOLE feature: `turf.pointToLineDistance(pointFeature, lineFeature, { units: \"meters\" })`. `incidents` is an Array of mixed Point + LineString features; guard with `if (inc.geometry.type === \"LineString\")` before line-only ops. For FC-shaped inputs (`places`, `routes`, `trafficAreaAnalytics`, `byod`), pass the value directly (`turf.bbox(places)`); never `turf.bbox(places.features)`.',\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 turf = await import(...)` / `const turf = arguments[0].turf` / `const places = ...`. Those names are already in scope as function parameters; drop the redeclaration line and use them directly (`turf.bbox(places)`).',\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 the tool inputs (`placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`): each is exposed via the injected sandbox args (`places`, `routes`, `incidents`, `geometries`, plus their `*ByEntry` per-entry views). 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\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 * 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 `analyseData` (one-shot or `monitor`-rerun); `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 `analyseData` with `monitor: { entryId }`;\n * replayed by 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 * `analyseData`'s `monitor` path 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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this._teardownEntry(entry), {\n parallel: true,\n });\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 async addIncidentsEntry(\n data: TrafficIncident[],\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n label: string,\n sampledAt: number,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n // `_teardownEntry` tears down a per-entry monitor + module — each instance is\n // independent, so `parallel: true` is race-safe and faster than serial.\n await hideAllEntries(this._entries, (entry) => this._teardownEntry(entry), { parallel: true });\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 = pickUniqueEntryId(\n explicitId ?? `incidents-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\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?._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 // 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 /**\n * Hide every traffic-incidents entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\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 { BYODState } from './byod';\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 { type BYODEntry, type BYODSource, BYODState, type BYODStateEvents } from './byod';\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 history of entries and can be driven into `single` or `multiple`\n * mode. Every {@link ToolState} slice with `setEntryMode` implements this shape, which lets\n * callers (the `setEntryMode` tool, `createMapAgent`'s `dataEntries` option, …) handle the\n * whole family uniformly without listing slice names.\n *\n * @group Agent Toolkit\n */\nexport interface EntryModeSlice {\n readonly entryMode: EntryMode;\n readonly entries: readonly { readonly id: string }[];\n setEntryMode(mode: EntryMode): Promise<void>;\n}\n\n/**\n * Data-entry kind names — the vocabulary used by `dataEntries` on `createMapAgent`. Superset\n * of the analyse/process input kinds (`EntryDataKind`) — adds `ranges`, which has its own\n * entry-mode-supporting slice but is not a top-level input kind for those tools.\n *\n * The kind names are LLM-facing (`routes`, `incidents`) rather than slice names (`routing`,\n * `trafficIncidents`); the slice mapping is internal — see {@link DATA_ENTRY_KIND_TO_SLICE}.\n *\n * @group Agent Toolkit\n */\nexport type DataEntryKind =\n | 'places'\n | 'routes'\n | 'incidents'\n | 'customGeometries'\n | 'trafficAreaAnalytics'\n | 'byod'\n | 'ranges';\n\n/**\n * Per-kind configuration accepted by `createMapAgent`'s `dataEntries` option.\n *\n * @group Agent Toolkit\n */\nexport type DataEntryConfig = {\n /**\n * When `false`, the kind is removed from the agent's tool surface entirely:\n * - the kind disappears from `analyseData` / `processData` scope and input fields;\n * - related recall / display / fetch tools are dropped from the registry\n * (see {@link TOOLS_BY_DATA_ENTRY_KIND}).\n *\n * Default: `true`.\n */\n enabled?: boolean;\n /**\n * Initial entry-display policy for the underlying slice. `single` keeps only the latest\n * entry on the map (others auto-hide); `multiple` (default) lets several coexist.\n */\n entryMode?: EntryMode;\n};\n\n/**\n * Mapping from {@link DataEntryKind} (LLM-facing name) to the {@link ToolState} slice key\n * that owns the entries for that kind. Used by `createMapAgent` to apply `entryMode` from\n * `dataEntries[kind].entryMode` to the right slice.\n *\n * @group Agent Toolkit\n */\nexport const DATA_ENTRY_KIND_TO_SLICE = {\n places: 'places',\n routes: 'routing',\n incidents: 'trafficIncidents',\n customGeometries: 'customGeometries',\n trafficAreaAnalytics: 'trafficAreaAnalytics',\n byod: 'byod',\n ranges: 'ranges',\n} as const satisfies Record<DataEntryKind, EntryModeSliceName>;\n\n/**\n * Default tool names that should be dropped from the registry when a {@link DataEntryKind}\n * is disabled. Only default-tool names are listed here — user-supplied custom tools are\n * never filtered automatically; integrators that want to gate their own tools on enabled\n * kinds can read `dataEntries` themselves.\n *\n * Tools that span multiple kinds (`clearMap`, `recallState`, `setEntryMode`, …) are\n * intentionally absent — they keep working regardless of which kinds are disabled.\n *\n * @group Agent Toolkit\n */\nexport const TOOLS_BY_DATA_ENTRY_KIND: Record<DataEntryKind, readonly string[]> = {\n places: ['recallPlaces', 'updatePlacesDisplay', 'locatePlace', 'discoverPlaces'],\n routes: [\n 'recallRoutes',\n 'updateRoutesDisplay',\n 'setRoute',\n 'addWaypointsToRoute',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n 'getCurrentWaypoints',\n 'updateWaypointsDisplay',\n ],\n incidents: [\n 'getTrafficIncidents',\n 'focusIncidents',\n 'startTrafficIncidentsMonitor',\n 'stopTrafficIncidentsMonitor',\n 'getShownTileIncidents',\n ],\n customGeometries: ['recallGeometries'],\n trafficAreaAnalytics: ['getTrafficAreaAnalytics', 'queryTrafficAnalytics', 'updateTrafficAreaAnalyticsDisplay'],\n byod: ['recallByod', 'addByodLayer', 'updateByodDisplay'],\n ranges: ['findReachableAreas', 'recallRanges'],\n};\n\n/**\n * Compile-time union of {@link ToolState} keys whose slice supports {@link EntryMode}.\n * Names match the {@link ToolState} keys exactly — `routing` (not `routes`),\n * `customGeometries` (not `geometries`) — so the slice name in the tool API never drifts\n * from the property on `state`.\n *\n * Auto-grows when a new slice implements `setEntryMode`: the union picks up the new key,\n * and the runtime list {@link ENTRY_MODE_SLICE_NAMES} fails to type-check until the key\n * is added there too.\n *\n * @group Agent Toolkit\n */\nexport type EntryModeSliceName = {\n [K in keyof ToolState]: ToolState[K] extends EntryModeSlice ? K : never;\n}[keyof ToolState];\n\n/**\n * Runtime enumeration of slice names that support entry mode. The `satisfies` clause\n * makes drift a compile error: when a new {@link ToolState} slice gains `setEntryMode`,\n * this constant has to be updated before the package will build.\n *\n * @group Agent Toolkit\n */\nexport const ENTRY_MODE_SLICE_NAMES = Object.keys({\n places: true,\n routing: true,\n ranges: true,\n customGeometries: true,\n byod: true,\n trafficAreaAnalytics: true,\n trafficIncidents: true,\n} satisfies Record<EntryModeSliceName, true>) as readonly EntryModeSliceName[];\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 * State slice that renders something on the map and can hide ALL of it in one call. The\n * {@link clearMap} tool walks every slice that implements this interface so adding a new\n * slice (e.g. a future overlay) auto-grows the tool's surface — no hand-edits required.\n *\n * `clearShown` is map-only: it hides what's currently rendered but leaves the slice's\n * history (`entries`) intact. Use the slice's `reset()` for a full wipe.\n *\n * @group Agent Toolkit\n */\nexport interface ClearableMapSlice {\n clearShown(): Promise<void>;\n}\n\n/**\n * Compile-time union of {@link ToolState} keys whose slice implements {@link ClearableMapSlice}.\n * Same pattern as {@link EntryModeSliceName}: the union auto-grows when a slice gains\n * `clearShown`, and the runtime list {@link CLEARABLE_SLICE_NAMES} fails to type-check until\n * the key is added there too.\n *\n * @group Agent Toolkit\n */\nexport type ClearableSliceName = {\n [K in keyof ToolState]: ToolState[K] extends ClearableMapSlice ? K : never;\n}[keyof ToolState];\n\n/**\n * Runtime enumeration of slice names that support `clearShown`. The `satisfies` clause\n * makes drift a compile error: when a new {@link ToolState} slice implements\n * {@link ClearableMapSlice}, this constant has to be updated before the package will build.\n *\n * @group Agent Toolkit\n */\nexport const CLEARABLE_SLICE_NAMES = Object.keys({\n places: true,\n routing: true,\n ranges: true,\n customGeometries: true,\n byod: true,\n trafficAreaAnalytics: true,\n trafficIncidents: true,\n} satisfies Record<ClearableSliceName, true>) as readonly ClearableSliceName[];\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 byod: new BYODState(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 / updateWaypointsDisplay only when displaying results already in context from a previous step.\n\nSESSION STATE & ENTRIES:\n- Seven append-only histories live in the session: \\`places\\`, \\`routes\\`, \\`ranges\\`, \\`customGeometries\\`, \\`trafficAreaAnalytics\\`, \\`trafficIncidents\\`, \\`byod\\`. Each tool call that produces results stores a new ENTRY (id like \\`places-3\\`, \\`routes-1\\`, \\`byod-0\\`) — these ids stay stable and are accepted by every show / recall / \\`processData\\` / \\`analyseData\\` tool via the matching \\`*EntryIDs\\` field.\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\\` / \\`recallGeometries\\` / \\`recallByod\\` (traffic slices surface via \\`recallState\\`). 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';\nimport type { FeatureFlags } from '../types';\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\nconst basePlaceShape = {\n id: z.string().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\n// Experimental-only fields propagated from the exploration-search backend's area-tag\n// pipeline. Area data currently populated only in DE / NL / FR. Gated by `experimentalSearch`\n// so the LLM doesn't see them on tool runs that can't produce them.\nconst experimentalPlaceShape = {\n areaId: z\n .string()\n .optional()\n .describe(\n 'Id of the small area polygon this place sits in (few km², not a whole municipality). ' +\n 'Round-trip into `discoverPlaces.where.areaId` for \"what else is in this same area?\". ' +\n 'Only set on explorationSearch results in DE / NL / FR.',\n ),\n areaCountry: z.string().optional().describe('ISO 3166-1 alpha-2 country code of the surrounding area polygon.'),\n areaTags: z\n .array(z.string())\n .optional()\n .describe(\n 'Tokens describing the surrounding small area (few km², not a whole municipality) ' +\n '(e.g. \"coastal\", \"walkable\", \"transit_connected\"). Useful for downstream `areaTags` filters.',\n ),\n};\n\n/**\n * Build a flag-aware compact place summary schema. When `experimentalSearch`\n * is true, the schema also documents the exploration-search-only `areaId` /\n * `areaCountry` / `areaTags` fields propagated from the surrounding small-area polygon.\n */\nexport const buildPlaceOutputSchema = (flags: FeatureFlags) =>\n z\n .object(flags.experimentalSearch ? { ...basePlaceShape, ...experimentalPlaceShape } : basePlaceShape)\n .describe('Compact place summary');\n\n/**\n * Build a flag-aware compact places-collection summary. Mirrors\n * {@link buildPlaceOutputSchema} for `features[]`.\n */\nexport const buildPlacesOutputSchema = (flags: FeatureFlags) =>\n z\n .object({\n count: z.number(),\n features: z.array(buildPlaceOutputSchema(flags)),\n })\n .describe('Compact places summary');\n\n/**\n * Token-efficient summary of a single place — default flags\n * (`experimentalSearch: false`).\n */\nexport const placeOutputSchema = buildPlaceOutputSchema({});\n\n/**\n * Token-efficient summary of a places collection — default flags\n * (`experimentalSearch: false`).\n */\nexport const placesOutputSchema = buildPlacesOutputSchema({});\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\n .string()\n .describe('Section ID — obtain from `recallRoutes({ id })` detail mode or from `analyseData` over routes.'),\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 type { z } from 'zod';\nimport { createHelpTool } from './tools/utilities';\nimport type { EntryDataKind, 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 scopePrompt: entry.scopePrompt,\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 * Registry entry for a scopable tool — captured at setup time so {@link prepareStep}\n * can rebuild the tool's description + inputSchema per turn from the classifier's\n * `toolScopes` output.\n *\n * @ignore\n */\nexport type ScopableToolInfo = {\n /**\n * Validate `rawScope` against the tool's scopeSchema and re-invoke its builder.\n * Returns `null` when either the parse or the rebuild fails (fail-open — callers\n * leave the default surface in place rather than crashing the step). The original\n * builder, scope schema, and any other build options are captured in the closure\n * so callers stay decoupled from the underlying validation/build mechanics.\n */\n applyScope: (rawScope: unknown) => { description: string; inputSchema: z.ZodType } | null;\n /** Default (full-surface) values, used to restore between turns. */\n defaultDescription: string;\n defaultInputSchema: z.ZodType;\n};\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 * When a builder produces a {@link ToolEntry} carrying a `scopeSchema`, the\n * builder reference is retained in `scopableTools` so {@link prepareStep} can\n * re-invoke it with a per-turn scope and mutate the live tool object.\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?: {\n outputSchemas?: boolean;\n featureFlags?: ToolBuildOptions['featureFlags'];\n enabledDataKinds?: readonly EntryDataKind[];\n },\n): {\n tools: Record<string, Tool>;\n toolsMetadata: Record<string, ToolMetadata>;\n scopableTools: Record<string, ScopableToolInfo>;\n} => {\n const includeOutputSchema = options?.outputSchemas !== false;\n const buildOptions: ToolBuildOptions = {\n featureFlags: options?.featureFlags,\n enabledDataKinds: options?.enabledDataKinds,\n };\n const tools: Record<string, Tool> = {};\n const toolsMetadata: Record<string, ToolMetadata> = {};\n const scopableTools: Record<string, ScopableToolInfo> = {};\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 // Builder + scopeSchema is the precondition for per-turn rebuilding. A static\n // ToolEntry with a scopeSchema isn't rebuildable because there's no factory to\n // re-invoke; we silently treat it as a non-scopable tool to keep the contract simple.\n if (typeof entryOrBuilder === 'function' && entry.scopeSchema) {\n const builder = entryOrBuilder;\n const scopeSchema = entry.scopeSchema;\n scopableTools[name] = {\n applyScope: (rawScope) => {\n const parsed = scopeSchema.safeParse(rawScope);\n if (!parsed.success) return null;\n try {\n const rebuilt = builder({ ...buildOptions, scope: parsed.data });\n return { description: rebuilt.description, inputSchema: rebuilt.inputSchema };\n } catch {\n return null;\n }\n },\n defaultDescription: entry.description,\n defaultInputSchema: entry.inputSchema,\n };\n }\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, scopableTools };\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 `updateRoutesDisplay({ mainColor })`.';\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 const scope = entry.scopePrompt ? ` SCOPE: ${entry.scopePrompt}` : '';\n return `${name}${classificationPrompt}${depends}${scope}`;\n};\n\n// Names of tools whose metadata declares a `scopePrompt` — used to surface the per-turn\n// scope contract in the classifier system prompt.\nconst scopableToolNames = (toolsMetadata: Record<string, ToolMetadata>): string[] =>\n Object.entries(toolsMetadata)\n .filter(([, meta]) => meta.scopePrompt)\n .map(([name]) => name);\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 const scopable = scopableToolNames(toolsMetadata);\n const scopeContract = scopable.length\n ? [\n '',\n 'TOOL SCOPES — MANDATORY when selecting a scopable tool:',\n `The following tools REQUIRE a scope entry in \\`toolScopes\\` whenever they appear in \\`tools\\`: ${scopable.join(', ')}.`,\n \"For each picked scopable tool, emit `toolScopes[<name>]` with the per-tool object shape described in its SCOPE hint above. Don't omit. Don't leave it implicit.\",\n 'If the user query truly needs all input kinds the tool supports, still emit the scope explicitly — list every kind. Never skip the scope entry when picking the tool.',\n ]\n : [];\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 ...scopeContract,\n ].join('\\n');\n};\n\n// Per-call output schema. Scopable tools (those whose metadata carries a `scopePrompt`) must\n// appear in `toolScopes` when selected — the LLM cannot opt out.\n//\n// IMPORTANT: We can't use `z.record(z.string(), z.unknown())` for `toolScopes`. Azure / OpenAI\n// strict structured-output mode requires every object property to be declared explicitly with\n// `additionalProperties: false`, and `z.record` doesn't translate to that. Instead we build an\n// explicit `z.object({ [scopableToolName]: scopeShape.optional(), … })` with one optional field\n// per known scopable tool. The value shape is loose (`{ kinds: string[] }`) at the classifier\n// level — final validation against the tool's real `scopeSchema` happens in `prepareStep`.\n//\n// The `superRefine` then enforces \"if you picked tool X and X is scopable, you must emit\n// toolScopes[X]\". The AI SDK's structured-output retry loop sees that as a typed validation error\n// and re-runs with the message pointing at the missing entry.\nconst looseScopeShape = z.object({\n kinds: z\n .array(z.string())\n .min(1)\n .describe(\"Entry-data kinds the turn touches (one of the kinds listed in the tool's SCOPE hint).\"),\n});\n\n// Human-readable hint of the expected response shape, embedded in the system prompt. The model\n// produces any JSON (we use `output: 'no-schema'` to avoid attaching a strict schema to the API\n// request); this hint plus the SCOPE blocks already in the system prompt give it enough structure\n// to consistently emit something our zod schema can validate.\nconst buildExpectedShapeHint = (scopableNames: ReadonlySet<string>): string => {\n if (scopableNames.size === 0) {\n return '{ \"tools\": [\"toolName\", ...] }';\n }\n const scopeKeys = [...scopableNames].map((n) => ` \"${n}\": { \"kinds\": [...] } | null`).join(',\\n');\n return `{\n \"tools\": [\"toolName\", ...],\n \"toolScopes\": {\n${scopeKeys}\n }\n}\nFor every scopable tool you LIST in \"tools\", the matching \"toolScopes\" entry MUST be a non-null object \\`{ \"kinds\": [...] }\\`. For scopable tools you DON'T pick, set the corresponding \"toolScopes\" entry to \\`null\\`.`;\n};\n\nconst buildClassifySchema = (scopableNames: ReadonlySet<string>) => {\n const base = 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 if (scopableNames.size === 0) {\n return base;\n }\n\n // Each scopable tool gets a `.nullable()` field (not `.optional()`). Reason: Azure / OpenAI\n // strict structured-output mode requires every property to be in the JSON schema's `required`\n // list — `optional` maps to \"may be absent\" which strict mode rejects. `nullable` lets the\n // LLM emit `null` for unused entries while keeping the field listed in `required`.\n // We use `generateObject` with `mode: 'json'` below which doesn't require strict-mode\n // semantics, but keeping the schema strict-mode-clean future-proofs against runtime mode\n // switches.\n const toolScopesShape: Record<string, z.ZodNullable<typeof looseScopeShape>> = {};\n for (const name of scopableNames) {\n toolScopesShape[name] = looseScopeShape.nullable();\n }\n const toolScopesObject = z\n .object(toolScopesShape)\n .nullable()\n .describe(\n 'Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in ' +\n \"`tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.\",\n );\n\n return base.extend({ toolScopes: toolScopesObject }).superRefine((data, ctx) => {\n const scopes: Record<string, unknown> = (data.toolScopes ?? {}) as Record<string, unknown>;\n for (const name of data.tools) {\n if (!scopableNames.has(name)) continue;\n const value = scopes[name];\n if (value === null || value === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['toolScopes', name],\n message: `Tool \"${name}\" requires \\`toolScopes.${name}\\` — emit it per the SCOPE hint (don't leave null when picking).`,\n });\n }\n }\n });\n};\n\n/**\n * Calls the LLM with structured output to select tools for the conversation. Tries up to\n * `MAX_CLASSIFY_ATTEMPTS` times — the structured-output mode rejects classifications missing\n * mandatory scope, and on rejection the LLM gets a retry with the validation error pointing at\n * the missing entry. If every attempt still fails, returns the latest tool list without scopes\n * and lets `prepareStep` fall back to the (terse) unscoped surface.\n */\nconst MAX_CLASSIFY_ATTEMPTS = 2;\n\ntype Usage = ClassificationResult['usage'];\ntype Partial = { tools?: unknown; toolScopes?: unknown } | null | undefined;\ntype Soft = { tools: string[]; scopes?: Record<string, unknown> } | undefined;\n\nconst addUsage = (\n target: Usage,\n source: { inputTokens?: number; outputTokens?: number; totalTokens?: number } | undefined,\n): void => {\n if (!source) return;\n target.inputTokens += source.inputTokens ?? 0;\n target.outputTokens += source.outputTokens ?? 0;\n target.totalTokens += source.totalTokens ?? 0;\n};\n\n// Pull a (best-effort) `{ tools, scopes }` pair out of whatever the LLM returned. Used by both\n// the \"valid-shape but failed local validation\" and \"generateObject threw\" paths to populate the\n// soft-fallback when every attempt fails. Returns `undefined` when nothing looks usable.\nconst extractPartialTools = (partial: Partial): Soft => {\n if (!partial || !Array.isArray(partial.tools) || !partial.tools.every((tool) => typeof tool === 'string')) {\n return undefined;\n }\n const scopes =\n partial.toolScopes && typeof partial.toolScopes === 'object'\n ? (partial.toolScopes as Record<string, unknown>)\n : undefined;\n return { tools: partial.tools, ...(scopes && { scopes }) };\n};\n\n// Compact a successful classification: drop the `null` placeholders in `toolScopes` (the \"not\n// picked\" sentinel) so prepareStep's `applyToolScopes` doesn't churn through no-ops.\nconst compactSuccess = (\n parsed: { tools: string[]; toolScopes?: Record<string, unknown> | null },\n usage: Usage,\n): { tools: string[]; toolScopes?: Record<string, unknown>; usage: Usage } => {\n const cleanedScopes = parsed.toolScopes\n ? Object.fromEntries(Object.entries(parsed.toolScopes).filter(([, value]) => value != null))\n : undefined;\n return {\n tools: parsed.tools,\n ...(cleanedScopes && Object.keys(cleanedScopes).length > 0 && { toolScopes: cleanedScopes }),\n usage,\n };\n};\n\nconst classifyQueryToTools = async (\n conversation: ConversationMessage[],\n model: LanguageModel,\n toolsMetadata: Record<string, ToolMetadata>,\n): Promise<{ tools: string[]; toolScopes?: Record<string, unknown>; usage: Usage }> => {\n const scopableNames = new Set(scopableToolNames(toolsMetadata));\n const schema = buildClassifySchema(scopableNames);\n const aggregateUsage: Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n\n // System prompt embeds the expected JSON shape so the model produces something we can validate\n // locally. We use `output: 'no-schema'` below (json_object mode on Azure / OpenAI), which means\n // the API doesn't see our zod schema at all — strict-mode-friendly by construction.\n const expectedShape = buildExpectedShapeHint(scopableNames);\n const system = `${buildClassifySystemPrompt(toolsMetadata)}\\n\\nReturn JSON matching this shape exactly:\\n${expectedShape}`;\n\n let soft: Soft;\n for (let attempt = 0; attempt < MAX_CLASSIFY_ATTEMPTS; attempt++) {\n try {\n // `Output.json()` puts the model into json-output mode without attaching a JSON Schema\n // to the request — strict-mode-friendly on Azure/OpenAI (their strict mode rejects\n // several Zod constructs we use). We validate locally against our zod schema after.\n const generated = await generateText({\n model,\n output: Output.json(),\n system,\n messages: conversation,\n maxOutputTokens: 400,\n });\n addUsage(aggregateUsage, generated.usage);\n\n const parsed = schema.safeParse(generated.output);\n if (parsed.success) {\n return compactSuccess(\n parsed.data as { tools: string[]; toolScopes?: Record<string, unknown> | null },\n aggregateUsage,\n );\n }\n // Validation failed locally — capture the partial for soft-fallback and retry.\n soft = extractPartialTools(generated.output as Partial) ?? soft;\n } catch (error) {\n // Best-effort: `generateText` may reject with `{ value, usage }` on output-validation\n // failures; capture both so the soft-fallback below still has something useful and\n // usage stays accurate across the retry.\n const errRecord = error as { value?: unknown; usage?: typeof aggregateUsage };\n addUsage(aggregateUsage, errRecord.usage);\n soft = extractPartialTools(errRecord.value as Partial) ?? soft;\n }\n }\n\n if (!soft) {\n throw new Error(`Classifier failed after ${MAX_CLASSIFY_ATTEMPTS} attempts to produce a valid response.`);\n }\n return {\n tools: soft.tools,\n ...(soft.scopes && { toolScopes: soft.scopes }),\n usage: aggregateUsage,\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, toolScopes, usage } = await classifyQueryToTools(conversation, chatModel, toolsMetadata);\n\n const activeToolNames = tools;\n\n const timeMs = performance.now() - startTime;\n\n return {\n activeToolNames,\n ...(toolScopes && { toolScopes }),\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';\nimport type { FeatureFlags } from '../types';\n\n/** @ignore */\nexport type PlaceSummary = {\n id: string;\n name?: string;\n address?: string;\n position: [number, number];\n // Populated only when `experimentalSearch` is on and the underlying place came from the\n // exploration-search backend (DE / NL / FR area-tag pipeline; areas are small, few km²).\n areaId?: string;\n areaCountry?: string;\n areaTags?: string[];\n};\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 * When `flags.experimentalSearch` is `true`, the exploration-search-only\n * `areaId` / `areaCountry` / `areaTags` fields (when present on the source\n * place) are also copied into the summary. When the flag is off they are\n * always omitted, regardless of what the source place carries.\n *\n * @ignore\n */\nexport const summarizePlace = (place: Place, flags: FeatureFlags = {}): PlaceSummary => {\n const summary: 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 if (flags.experimentalSearch) {\n const props = place.properties as\n | {\n areaId?: string;\n areaCountry?: string;\n areaTags?: string[];\n }\n | undefined;\n if (props?.areaId) summary.areaId = props.areaId;\n if (props?.areaCountry) summary.areaCountry = props.areaCountry;\n if (props?.areaTags?.length) summary.areaTags = props.areaTags;\n }\n return summary;\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[], flags: FeatureFlags = {}): 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, flags)),\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 = await 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 = await 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 {\n alongRouteSearch,\n explorationSearch,\n geometryData,\n POPULATED_AREA_TAGS,\n type PopulatedAreaTag,\n search,\n} 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 { buildPlacesOutputSchema, 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/** Build the flag-aware output schema for the discover-places tool. */\nexport const buildDiscoverPlacesOutputSchema = (flags: FeatureFlags) =>\n z.union([\n buildPlacesOutputSchema(flags).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/** Default-flag (`experimentalSearch: false`) output schema. */\nexport const discoverPlacesOutputSchema = buildDiscoverPlacesOutputSchema({});\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 areaId: z\n .string()\n .optional()\n .describe(\n 'Id of a small area polygon (few km², not a whole municipality) — restricts results to that single area. ' +\n 'Typically chained from a previous hit\\'s `areaId` (\"what else is in this same area?\"). ' +\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 experimentalData = data as {\n municipalities?: string[];\n boundingBoxes?: unknown[];\n areaId?: string;\n };\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 !!experimentalData.municipalities?.length ||\n !!experimentalData.boundingBoxes?.length ||\n !!experimentalData.areaId;\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 / areaId' : '') +\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/areaId/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 on POI name/address. ' +\n 'NEVER a city/region/area name (e.g. \"Paris\", \"Amsterdam\") — those go in `where`. ' +\n 'For \"restaurants in Paris\": `query` is empty (or a name filter like \"pizza\"), `where.queries: [{query: \"Paris\"}]`. ' +\n 'Omit when filtering 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\n .optional()\n .describe(\n 'Display places/geometries on the map. ' +\n 'Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry.',\n ),\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 areaTags: z\n .array(z.enum(POPULATED_AREA_TAGS))\n .optional()\n .describe(\n 'Tokens describing a small surrounding area (few km², not a whole municipality). ' +\n 'Enum drawn from the SDK canonical vocabulary — e.g. \"coastal\", \"walkable\", \"alpine\", ' +\n '\"transit_connected\", \"village\", \"tourism_economy\". ' +\n 'OR semantics — matches places in areas tagged with ANY token. ' +\n 'Filter works on any `where` scope (including global), but area data is currently ' +\n 'populated only for DE / NL / FR — zero hits where data is absent. ' +\n 'Filter, not a geo-bias — pair with a `where` mode that supplies one.',\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/areaId/geometries/range/route'\n : 'queries/placeIds/geometries/range/route';\n const preferLine = flags.experimentalSearch\n ? 'Prefer `municipalities` / `areaId` / `queries` / `placeIds` (precise) over `boundingBoxes` (coarse). '\n : 'Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ';\n const areaTagsLine = flags.experimentalSearch\n ? 'Top-level `areaTags` narrows hits by small-area character (\"coastal\", \"walkable\", …) — area data currently populated only in DE/NL/FR. '\n : '';\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 areaTagsLine +\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 // `areaId` is a single-municipality geo-bias on the underlying explorationSearch — like\n // `municipalities` but matches by polygon id instead of name (faster + unambiguous).\n areaId?: string;\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 // Filter (not a geo-bias). Composes with any `where` scope.\n areaTags?: PopulatedAreaTag[];\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 = await 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`, `areaId`,\n// `areaTags`) only exist on the experimental exploration-search backend. Strip them when\n// 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 = await 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 baseFilters: MultiFilters,\n): Promise<WithinResolution | { error: string }> => {\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`, `municipalities`, and `areaId` only exist on the experimental schema;\n // treat them as absent on the default schema.\n const experimentalWhere = where as DiscoverWithinWhere & {\n boundingBoxes?: HasBBox[];\n municipalities?: string[];\n areaId?: 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 ...(experimentalWhere.areaId && { areaId: experimentalWhere.areaId }),\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 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, baseMultiFilters);\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 = await 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 flags: FeatureFlags,\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, flags),\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` and `areaTags` only exist on the experimental schema.\n const experimentalParams = params as {\n placeTypes?: ('POI' | 'PointAddress' | 'Street')[];\n areaTags?: PopulatedAreaTag[];\n };\n const placeTypes = experimentalParams.placeTypes;\n const areaTags = experimentalParams.areaTags;\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 // Top-level filters that compose with any `where` scope (not geo-biases on their own).\n const baseMultiFilters: MultiFilters = {\n ...(placeTypes?.length && { placeTypes }),\n ...(areaTags?.length && { areaTags }),\n };\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(\n state,\n detourResult.result,\n detourResult.placesEntryId,\n show,\n geometries,\n flags,\n );\n }\n\n const resolved = await resolveDiscoverBias(effectiveWhere, state, 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(\n state,\n rangeResult.result,\n rangeResult.placesEntryId,\n show,\n geometries,\n flags,\n );\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, flags);\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: buildDiscoverPlacesOutputSchema(flags),\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 '`query` filters POI names ONLY — area names go in `where`. ' +\n '`where.mode`: within (area) / nearby (point bias) / maxDetour (route-relative ranked) / global. ' +\n 'For \"X in [named area]\", put X in top-level `query`/`poiCategories` and the area in `where.queries: [{query: \"...\"}]` — geocoded in one step, no precursor locatePlace. ' +\n 'Within-mode: route → corridor. Prefer geometries/municipalities/placeIds over bbox. ' +\n 'Skip for already-stored places — use recallPlaces/processData.',\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\", queries: [{ query: \"De Jordaan, Amsterdam\", queryAs: \"place\" }] } }) // area name goes in `where.queries`, NOT top-level `query`',\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({ poiCategories: [\"RESTAURANT\"], where: { mode: \"within\", queries: [{ query: \"Paris\" }] } }) // \"restaurants in Paris\" — subject in `poiCategories`, area in `where.queries`',\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\", placeIds: [\"G55fc4abe-...\"] } }) // search inside stored place polygons (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 'processData',\n 'analyseData',\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 = await 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 toggleTilesPOIs 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 entryId: z\n .string()\n .describe(\n 'Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData.',\n ),\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(\n 'Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize.',\n ),\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 the given entry on its per-entry module with default settings, fitting the viewport.\nconst showAnalyticsOnMap = async (state: ToolState, entryId: string): Promise<void> => {\n await state.trafficAreaAnalytics.showEntry(entryId);\n const entry = state.trafficAreaAnalytics.findById(entryId);\n if (!entry) return;\n const analyticsModule = await state.trafficAreaAnalytics.getEntryModule(entryId);\n const defaultMetric = entry.data.properties?.metrics?.[0] ?? 'congestionLevel';\n analyticsModule.setMetric(defaultMetric);\n const bbox = bboxFromGeoJSON(entry.data.features);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\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 entryId: string,\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 entryId,\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 entryId,\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 const regionName = result.features[0]?.properties?.name;\n const label = regionName\n ? `${regionName} (${dateParams.startDate ?? '…'} → ${dateParams.endDate ?? '…'})`\n : `analytics (${dateParams.startDate ?? '…'} → ${dateParams.endDate ?? '…'})`;\n const entryId = await state.trafficAreaAnalytics.addEntry(result, label, {\n ...dateParams,\n metrics,\n functionalRoadClasses: functionalRoadClasses ?? 'all',\n hours: hours ?? 'all',\n });\n\n if (showOnMap) {\n await showAnalyticsOnMap(state, entryId);\n }\n\n return summarize(entryId, 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, bboxFromGeoJSON, trafficIncidentRequestCategories } from '@tomtom-org/maps-sdk/core';\nimport { type TrafficIncidentDetailsByBBoxParams, trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\n\nimport * as turf from '@turf/turf';\nimport type { MultiPolygon, Polygon } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { geoJsonBBoxSchema, geometryInputSchema, getViewportBias, queryAsSchema, withinSharedFields } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { locatePlaces, type QueryAs } from './locate-place';\n\n// Number of candidate places to consider when resolving `where.queries` — a precise area often\n// lives a few results down the list (e.g. a street POI may rank above the neighbourhood it sits in).\nconst QUERY_CANDIDATE_LIMIT = 5;\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 `analyseData`\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 * analyseData/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// `within` mode for getTrafficIncidents — single-area scope. Either `viewport` alone or any\n// combination of the multi-region fields (boundingBox / queries / placeIds / geometries / range /\n// route). Each multi-region input is resolved to a bbox and the union of bboxes is sent to the\n// SDK (which only accepts a single bbox of at most 10,000 km²).\nconst withinTrafficIncidentsWhereSchema = z\n .object({\n ...withinSharedFields,\n boundingBox: geoJsonBBoxSchema\n .optional()\n .describe('Single bbox [W,S,E,N]. EXCLUSIVE with viewport; composes with other multi-region fields.'),\n queries: z\n .array(\n z.object({\n query: z\n .string()\n .describe(\n 'Name of a CONTAINING area (e.g. \"Paris\", \"De Jordaan, Amsterdam\", \"M25\"). ' +\n 'Resolved to its bbox (or polygon bbox) and merged into the search area.',\n ),\n queryAs: queryAsSchema,\n }),\n )\n .optional()\n .describe(\n 'CONTAINING areas to fetch incidents within. Each entry geocodes to a bbox; the union of all ' +\n 'bboxes is used. Pair per-item `queryAs` to disambiguate 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) contributes its bbox to the 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 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n })\n .refine((data) => hasViewportFlag(data) || hasAnyMeaningfulMultiRegion(data), {\n message:\n '`within` requires `viewport: true` OR one or more multi-region fields ' +\n '(boundingBox / queries / placeIds / geometries / range / route) with a meaningful value.',\n });\n\n// --- \"Meaningful value\" predicates ---------------------------------------------------------------\n// Strict-mode LLM providers (Azure / OpenAI strict tool params) fill every optional field of an\n// object with empty defaults — `boundingBox: [0,0,0,0]`, `range: \"\"`, `queries: []`,\n// `route: { routeId: \"\", widthMeters: 0 }` — even when the agent's real intent is a single field.\n// These predicates treat such defaulted-but-empty values as omitted, so both the schema refinement\n// and the executor agree on which fields actually carry intent.\nconst bboxIsMeaningful = (b: number[] | undefined): boolean => !!b && b.length === 4 && b.some((v) => v !== 0);\nconst routeIsMeaningful = (r: { routeId?: string; widthMeters: number } | undefined): boolean =>\n !!r && r.widthMeters > 0 && (r.routeId === undefined || r.routeId.length > 0);\nconst rangeIsMeaningful = (s: string | undefined): boolean => !!s && s.length > 0;\nconst hasViewportFlag = (data: { viewport?: true }): boolean => data.viewport === true;\nconst hasAnyMeaningfulMultiRegion = (data: {\n boundingBox?: number[];\n queries?: unknown[];\n placeIds?: unknown[];\n geometries?: unknown[];\n range?: string;\n route?: { routeId?: string; widthMeters: number };\n}): boolean =>\n bboxIsMeaningful(data.boundingBox) ||\n !!data.queries?.length ||\n !!data.placeIds?.length ||\n !!data.geometries?.length ||\n rangeIsMeaningful(data.range) ||\n routeIsMeaningful(data.route);\n\nconst trafficIncidentsWhereSchema = z\n .union([withinTrafficIncidentsWhereSchema])\n .describe(\n 'Geographic scope. Only `within` mode (single area, defaults to `{ mode: \"within\", viewport: true }`). ' +\n 'Use `viewport` alone for \"the network / right now / this area\", or any combination of ' +\n 'boundingBox / queries / placeIds / geometries / range / route — each resolves to a bbox and ' +\n 'the union is sent to the SDK (≤10,000 km²).',\n );\n\n/**\n * Tool schema for fetching traffic incident details.\n */\nexport const getTrafficIncidentsSchema = z.object({\n where: trafficIncidentsWhereSchema\n .optional()\n .describe('Geographic scope. Default: `{ mode: \"within\", viewport: true }`.'),\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 analyseData / ' +\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 analyseData/focusIncidents. ' +\n '\\n\\n' +\n 'AREA via `where` (within mode only). Omit `where` to use the current map viewport ' +\n '(\"the network / right now / this area / here\"). For named areas use `where.queries`; ' +\n 'for stored places use `where.placeIds`; for an isochrone use `where.range`; for a corridor ' +\n 'along a route use `where.route`; for raw bounds use `where.boundingBox`; for custom shapes ' +\n 'use `where.geometries`. Multi-region fields compose into one union bbox (≤10,000 km²). ' +\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 'analyseData (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\ntype WithinWhere = z.infer<typeof withinTrafficIncidentsWhereSchema>;\n\n// Union of BBox tuples → single BBox tuple. Returns undefined when given no inputs.\nconst unionBBoxes = (parts: BBox[]): BBox | undefined => {\n if (parts.length === 0) return undefined;\n let minLng = Number.POSITIVE_INFINITY;\n let minLat = Number.POSITIVE_INFINITY;\n let maxLng = Number.NEGATIVE_INFINITY;\n let maxLat = Number.NEGATIVE_INFINITY;\n for (const [a, b, c, d] of parts) {\n if (a < minLng) minLng = a;\n if (b < minLat) minLat = b;\n if (c > maxLng) maxLng = c;\n if (d > maxLat) maxLat = d;\n }\n return [minLng, minLat, maxLng, maxLat];\n};\n\n// Resolves `where.queries` to a list of bboxes. Each query geocodes (POI or place per `queryAs`)\n// with the current viewport as bias. A query failing to resolve aborts the whole call — better to\n// surface a precise error than to silently search a smaller area.\nconst resolveQueriesToBBoxes = async (\n queries: { query: string; queryAs?: QueryAs }[],\n state: ToolState,\n): Promise<{ bboxes: BBox[] } | { error: string }> => {\n const biasPosition = getViewportBias(state.baseMap);\n const bboxes: BBox[] = [];\n for (const q of queries) {\n const candidates = await locatePlaces(q.query, q.queryAs ?? 'place', {\n limit: QUERY_CANDIDATE_LIMIT,\n bias: biasPosition ? { position: biasPosition } : undefined,\n });\n if (candidates.length === 0) {\n return { error: `Could not resolve \"${q.query}\". Try a more specific query, a placeId, or a boundingBox.` };\n }\n const bbox = bboxFromGeoJSON(candidates[0]) as BBox | undefined;\n if (!bbox) {\n return {\n error:\n `\"${q.query}\" resolved to a place with no bbox — cannot use as a \"within\" area. ` +\n 'Provide a boundingBox or a different query.',\n };\n }\n bboxes.push(bbox);\n }\n return { bboxes };\n};\n\n// Resolves `where.placeIds` to a list of bboxes. Prefers a cached `place.bbox`; falls back to\n// fetching the boundary polygon via Geometry Data when the place has no bbox of its own.\nconst resolvePlaceIdsToBBoxes = async (\n placeIds: string[],\n state: ToolState,\n): Promise<{ bboxes: BBox[] } | { error: string }> => {\n const bboxes: BBox[] = [];\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 const directBBox = bboxFromGeoJSON(lookup.place) as BBox | undefined;\n if (directBBox) {\n bboxes.push(directBBox);\n continue;\n }\n const feature = await state.places.fetchPlaceGeometry(placeId);\n const fetched = feature ? (bboxFromGeoJSON(feature) as BBox | undefined) : undefined;\n if (!fetched) {\n return {\n error:\n `Place \"${placeId}\" has no bbox or fetchable boundary polygon. ` +\n 'Provide a boundingBox or a different placeId.',\n };\n }\n bboxes.push(fetched);\n }\n return { bboxes };\n};\n\n// Resolves `where.range` to a list of bboxes (one per origin polygon). Multi-origin entries\n// combine their reachable polygons into the union area.\nconst resolveRangeToBBoxes = (state: ToolState, rangeId: string): { bboxes: BBox[] } | { error: string } => {\n const rangesEntry = state.ranges.entries.find((e) => e.id === rangeId);\n if (!rangesEntry || rangesEntry.ranges.length === 0) {\n return { error: `Range \"${rangeId}\" not found. Use recallRanges to list available ranges.` };\n }\n const bboxes: BBox[] = [];\n for (const r of rangesEntry.ranges) {\n const polygon = r.polygon?.features[0];\n const bbox = polygon ? (bboxFromGeoJSON(polygon) as BBox | undefined) : undefined;\n if (bbox) bboxes.push(bbox);\n }\n if (bboxes.length === 0) {\n return { error: `Range \"${rangeId}\" has no polygons. Recompute via findReachableAreas.` };\n }\n return { bboxes };\n};\n\n// Resolves `where.route` to a list of bboxes — one per route alternative — for the buffered\n// corridor at `widthMeters/2` either side of each route line. `widthMeters` is the *total*\n// corridor width, mirroring discover-places' `within.route` semantics.\nconst resolveRouteToBBoxes = (\n state: ToolState,\n route: { routeId?: string; widthMeters: number },\n): { bboxes: BBox[]; 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 = route.routeId ? entries.find((e) => e.id === route.routeId) : entries.at(-1);\n if (!entry) {\n return { error: `No route found with id \"${route.routeId}\". Use recallRoutes to list available routes.` };\n }\n if (entry.data.features.length === 0) {\n return { error: `Route entry \"${entry.id}\" has no geometry.` };\n }\n const radiusMeters = route.widthMeters / 2;\n const bboxes: BBox[] = [];\n for (const routeFeature of entry.data.features) {\n const buffered = turf.buffer(routeFeature, radiusMeters, { units: 'meters' });\n const bbox = buffered ? (bboxFromGeoJSON(buffered) as BBox | undefined) : undefined;\n if (bbox) bboxes.push(bbox);\n }\n if (bboxes.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 { bboxes, routeLabel: entry.label };\n};\n\ntype ResolvedScope = { bbox: BBox; routeLabel?: string };\n\n// Viewport branch — isolated so the try/catch noise doesn't inflate the main resolver.\nconst resolveViewportToBBox = (state: ToolState): ResolvedScope | { error: string } => {\n try {\n return { bbox: state.baseMap.ttMap.getBBox() };\n } catch (error) {\n return { error: `Viewport fallback failed: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n// Cheap inline pass — converts `where.geometries` polygons into bboxes.\nconst bboxesFromGeometries = (geometries: WithinWhere['geometries']): BBox[] => {\n if (!geometries?.length) return [];\n const out: BBox[] = [];\n for (const g of geometries as (Polygon | MultiPolygon)[]) {\n const bbox = bboxFromGeoJSON(g) as BBox | undefined;\n if (bbox) out.push(bbox);\n }\n return out;\n};\n\n// Drains the multi-region branches in sequence, skipping fields with non-meaningful (strict-mode\n// LLM-defaulted) values. Any sub-resolver short-circuit (error) aborts the whole pass — better to\n// surface a precise error than to silently search a smaller area.\nconst collectMultiRegionBBoxes = async (\n where: WithinWhere,\n state: ToolState,\n): Promise<{ bboxes: BBox[]; routeLabel?: string } | { error: string }> => {\n const collected: BBox[] = [];\n let routeLabel: string | undefined;\n\n if (bboxIsMeaningful(where.boundingBox)) collected.push(where.boundingBox as BBox);\n\n if (where.queries?.length) {\n const resolved = await resolveQueriesToBBoxes(where.queries, state);\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n }\n\n if (where.placeIds?.length) {\n const resolved = await resolvePlaceIdsToBBoxes(where.placeIds, state);\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n }\n\n collected.push(...bboxesFromGeometries(where.geometries));\n\n if (rangeIsMeaningful(where.range)) {\n const resolved = resolveRangeToBBoxes(state, where.range as string);\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n }\n\n if (routeIsMeaningful(where.route)) {\n const resolved = resolveRouteToBBoxes(state, where.route as { routeId?: string; widthMeters: number });\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n routeLabel = resolved.routeLabel;\n }\n\n return { bboxes: collected, ...(routeLabel && { routeLabel }) };\n};\n\n// Resolve a `within` where to a single union bbox. Returns the union along with any incidental\n// labels (e.g. the route entry's label) that feed into the auto-generated entry label.\n//\n// Priority: when any multi-region field carries a meaningful value (e.g. `queries`, `boundingBox`,\n// `route`), take the multi-region path even if `viewport: true` is also set. Strict-mode LLM\n// providers fill every optional field with empty defaults, so `viewport: true` is frequently\n// present alongside the agent's real intent — preferring the more specific signal avoids silently\n// answering with the wrong area (e.g. London viewport when the agent asked about Paris).\nconst resolveWithinToBBox = async (\n where: WithinWhere,\n state: ToolState,\n): Promise<ResolvedScope | { error: string }> => {\n if (hasAnyMeaningfulMultiRegion(where)) {\n const collected = await collectMultiRegionBBoxes(where, state);\n if ('error' in collected) return collected;\n const merged = unionBBoxes(collected.bboxes);\n if (!merged) {\n return { error: 'No area could be resolved from `where` — provide at least one within field.' };\n }\n return { bbox: merged, ...(collected.routeLabel && { routeLabel: collected.routeLabel }) };\n }\n if (hasViewportFlag(where)) return resolveViewportToBBox(state);\n return { error: 'No area could be resolved from `where` — provide at least one within field.' };\n};\n\n// Build a human-readable label from `where` when the caller didn't pass one. Falls back to a\n// bbox-coordinate string for silent intermediate fetches.\nconst defaultLabel = (\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n where: WithinWhere | undefined,\n routeLabel: string | undefined,\n): string => {\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\n let head: string | undefined;\n if (where && !where.viewport) {\n if (where.queries?.length) head = where.queries.map((q) => q.query).join(', ');\n else if (routeLabel) head = `along ${routeLabel}`;\n else if (where.range) head = where.range;\n }\n\n if (!head) {\n const [minLng, minLat, maxLng, maxLat] = params.bbox;\n head = `bbox ${minLng.toFixed(2)},${minLat.toFixed(2)} → ${maxLng.toFixed(2)},${maxLat.toFixed(2)}`;\n }\n\n return filterParts.length ? `${head} — ${filterParts.join('; ')}` : head;\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 { where, categoryFilter, timeValidityFilter, label, silent = false, fitBounds = true } = params;\n\n // Default to viewport when `where` is omitted — keeps the \"no scope = current viewport\"\n // contract the system prompt promises.\n const effectiveWhere: WithinWhere = where ?? { mode: 'within', viewport: true };\n\n const resolved = await resolveWithinToBBox(effectiveWhere, state);\n if ('error' in resolved) return resolved;\n const { bbox: effectiveBbox, routeLabel } = resolved;\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 = await state.trafficIncidents.addIncidentsEntry(\n result.features,\n entryParams,\n label ?? defaultLabel(entryParams, effectiveWhere, routeLabel),\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 await 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 type { FeatureCollection } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { placesEntryIdHintSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const addByodLayerOutputSchema = z.union([\n z.object({\n byodEntryId: z\n .string()\n .describe('Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData).'),\n label: z.string(),\n featureCount: z.number(),\n geometryTypes: z.array(z.string()).describe('Geometry types present (Point / LineString / Polygon / …).'),\n source: z\n .union([z.object({ kind: z.literal('url'), url: z.string() }), z.object({ kind: z.literal('inline') })])\n .describe('Where the data came from.'),\n shown: z.boolean().describe('Whether the entry was rendered immediately (mirrors the `show` input).'),\n }),\n toolErrorSchema,\n]);\n\nexport const addByodLayerSchema = z\n .object({\n label: z\n .string()\n .describe('Human-readable label for the new entry (e.g. \"Sales territories\", \"Customer pins 2026-Q1\").'),\n url: z\n .url()\n .optional()\n .describe(\n 'URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. ' +\n 'EXCLUSIVE with `data`.',\n ),\n data: z\n .unknown()\n .optional()\n .describe(\n 'Inline GeoJSON FeatureCollection (`{ type: \"FeatureCollection\", features: [...] }`). ' +\n 'EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.',\n ),\n entryId: placesEntryIdHintSchema,\n show: z\n .boolean()\n .optional()\n .describe(\n 'Render the entry immediately after adding. Default: false (entry is stored but hidden — use `updateByodDisplay` to render later).',\n ),\n })\n .refine((v) => (v.url ? !v.data : !!v.data), {\n message: 'Pass EXACTLY ONE of `url` or `data`.',\n });\n\nexport const addByodLayerDescription =\n 'Register a customer-owned GeoJSON layer as a new BYOD entry. Accepts either a URL (fetched once) or ' +\n 'inline GeoJSON. The slice renders the entry with sensible defaults based on geometry type ' +\n '(Point → circle, Line → line, Polygon → fill). The new entry id is usable with `byodEntryIDs` on ' +\n '`analyseData` / `processData` and with `updateByodDisplay` to toggle visibility. ' +\n 'For very large datasets, prefer programmatic seeding from the embedding app via `state.byod.addEntry(...)`.';\n\nconst isFeatureCollection = (value: unknown): value is FeatureCollection => {\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\n// Bounded-fetch policy for BYOD URL inputs. The tool runs with whatever network reach the host\n// gives it (server-side: full reach; browser: subject to CORS). We don't try to enforce SSRF\n// guarantees that the host can't itself — instead we make accidental abuse expensive: only\n// http(s), short timeout, hard size cap.\nconst FETCH_TIMEOUT_MS = 15_000;\nconst MAX_RESPONSE_BYTES = 25 * 1024 * 1024; // 25 MB — comfortably above typical GeoJSON layers, refuses anything pathological.\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);\n\nconst validateUrl = (raw: string): { url: URL } | { error: string } => {\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n return { error: `Invalid URL: \"${raw}\".` };\n }\n if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {\n return {\n error: `URL protocol \"${parsed.protocol}\" is not allowed. Only http: and https: are supported.`,\n };\n }\n return { url: parsed };\n};\n\n// Check the advertised Content-Length and bail before we read a single byte if the server\n// honestly admits the payload is too large. Servers that lie or omit the header fall through.\nconst checkAdvertisedSize = (response: Response, url: URL): { error: string } | undefined => {\n const contentLength = response.headers.get('content-length');\n if (!contentLength) return undefined;\n const advertised = Number.parseInt(contentLength, 10);\n if (Number.isFinite(advertised) && advertised > MAX_RESPONSE_BYTES) {\n return {\n error: `Response too large for \"${url}\": advertised ${advertised} bytes (max ${MAX_RESPONSE_BYTES}).`,\n };\n }\n return undefined;\n};\n\n// Streaming-cap body reader. Pulls chunks from the response and aborts as soon as the running\n// total crosses MAX_RESPONSE_BYTES, so we never page in a multi-GB payload. Falls back to a\n// single `text()` read with post-hoc size check when the runtime has no streaming reader.\nconst readBodyBounded = async (response: Response, url: URL): Promise<{ value: string } | { error: string }> => {\n const reader = response.body?.getReader();\n if (!reader) {\n const text = await response.text();\n if (text.length > MAX_RESPONSE_BYTES) {\n return { error: `Response too large for \"${url}\": ${text.length} bytes (max ${MAX_RESPONSE_BYTES}).` };\n }\n return { value: text };\n }\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_RESPONSE_BYTES) {\n await reader.cancel();\n return {\n error: `Response exceeded ${MAX_RESPONSE_BYTES} bytes while streaming \"${url}\". Refusing to load.`,\n };\n }\n chunks.push(value);\n }\n const merged = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { value: new TextDecoder().decode(merged) };\n};\n\n// Translate AbortController-triggered aborts into a friendlier timeout message; pass everything\n// else through verbatim. Pulled out so the main fetch flow doesn't carry the catch-block branch.\nconst wrapFetchError = (error: unknown, url: URL): { error: string } => {\n if (error instanceof Error && error.name === 'AbortError') {\n return { error: `Fetch timed out after ${FETCH_TIMEOUT_MS}ms for \"${url}\".` };\n }\n return { error: `Failed to fetch \"${url}\": ${error instanceof Error ? error.message : String(error)}` };\n};\n\n// Fetches a JSON resource with timeout + size cap. Returns either the parsed body or a structured\n// error. Body-streaming and size enforcement live in `readBodyBounded`.\nconst fetchJsonBounded = async (url: URL): Promise<{ value: unknown } | { error: string }> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const response = await fetch(url, { signal: controller.signal });\n if (!response.ok) {\n return { error: `Failed to fetch \"${url}\": HTTP ${response.status} ${response.statusText}` };\n }\n const sizeProblem = checkAdvertisedSize(response, url);\n if (sizeProblem) return sizeProblem;\n const body = await readBodyBounded(response, url);\n if ('error' in body) return body;\n return { value: JSON.parse(body.value) };\n } catch (error) {\n return wrapFetchError(error, url);\n } finally {\n clearTimeout(timer);\n }\n};\n\nconst collectGeometryTypes = (data: FeatureCollection): string[] => {\n const types = new Set<string>();\n for (const feature of data.features) {\n const t = feature.geometry?.type;\n if (t) types.add(t);\n }\n return [...types];\n};\n\nexport const executeAddByodLayer = async (\n params: z.infer<typeof addByodLayerSchema>,\n state: ToolState,\n): Promise<z.infer<typeof addByodLayerOutputSchema>> => {\n let data: unknown;\n let sourceRecord: { kind: 'url'; url: string } | { kind: 'inline' };\n\n if (params.url) {\n const urlCheck = validateUrl(params.url);\n if ('error' in urlCheck) return urlCheck;\n const fetched = await fetchJsonBounded(urlCheck.url);\n if ('error' in fetched) return fetched;\n data = fetched.value;\n sourceRecord = { kind: 'url', url: params.url };\n } else {\n data = params.data;\n sourceRecord = { kind: 'inline' };\n }\n\n if (!isFeatureCollection(data)) {\n return {\n error: 'Payload is not a GeoJSON FeatureCollection (`{ type: \"FeatureCollection\", features: [...] }`).',\n };\n }\n\n const byodEntryId = await state.byod.addEntry(data, params.label, {\n source: sourceRecord,\n ...(params.entryId && { explicitId: params.entryId }),\n });\n\n let shown = false;\n if (params.show) {\n try {\n await state.byod.showEntry(byodEntryId);\n shown = true;\n } catch (error) {\n // The entry is already in state, so we keep it — `updateByodDisplay` can retry the\n // render later. Surface the render error through the tool-result schema rather than\n // letting it reject the tool call.\n return {\n error: `addByodLayer added entry \"${byodEntryId}\" but rendering failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n };\n }\n }\n\n return {\n byodEntryId,\n label: params.label,\n featureCount: data.features.length,\n geometryTypes: collectGeometryTypes(data),\n source: sourceRecord,\n shown,\n };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Sandbox-running analysis tool. Accepts any combination of places / routes /\n * incidents / customGeometries / trafficAreaAnalytics / byod entries and exposes\n * them to user-authored JS as `places`, `routes`, `incidents`, `geometries`,\n * `trafficAreaAnalytics`, `byod` (and per-entry views), with `h3` and `turf`\n * available. The result is attached as `_analysis[name]` on every contributing\n * entry.\n *\n * Opt-in `monitor: { entryId }` registers the call as a recurring spec on the\n * targeted incidents entry — the code re-runs on every monitor-tick with\n * `previous` / `now` / `log` in scope. Constraint: only incidents-only inputs\n * are allowed when `monitor` is set (no cross-kind reruns in the lean merge).\n *\n * Opt-in `applyFocus` (default true when `monitor` is set) honours `focusIds`\n * on the result by calling `setFocus` on the source incidents entry.\n *\n * **Scope + enablement.** Shared scaffolding in `tools/shared/entry-kinds.ts` builds\n * the per-kind input fields, the \"at least one of …\" message, the per-kind sandbox /\n * schema docs, and the scope schema. This file only adds the tool-specific surface:\n * `name` / `description` / `outputFormat` / `monitor` / `applyFocus`, the code-doc\n * body (including the incidents-monitor extras), the description preamble, the\n * monitor refine, and the executor.\n */\n\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport { runIncidentSpec } from '../../state';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport {\n ALL_ENTRY_DATA_KINDS,\n ANALYSE_OUTPUT_FORMAT_DESCRIPTION,\n attachAnalysisToEntries,\n buildAnalyseReturnPrompt,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxCodePrompt,\n CROSS_KIND_OPS_DOC,\n type EntryKindScope,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n hasAnyEntryIds,\n isKindActive,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n prepareMultiInputs,\n resolveActiveKinds,\n runSandboxedFn,\n skippedSourceSchema,\n validateAnalysisResult,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Per-turn scope kinds that `analyseData` understands. Identical to {@link EntryDataKind}\n * — every kind that's an analyseData input also accepts a scoped narrow.\n *\n * @group Agent Toolkit\n */\nexport type AnalyseDataKind = EntryDataKind;\n\n/**\n * Per-turn scope for `analyseData`. Emitted by the classifier in\n * `toolScopes.analyseData` when only a subset of input kinds is relevant.\n *\n * Object-shaped so future per-tool scope additions (e.g. flags) don't break\n * the wire format.\n *\n * @group Agent Toolkit\n */\nexport type AnalyseDataScope = EntryKindScope;\n\n/**\n * Default scope schema for analyseData — accepts every {@link EntryDataKind}. When\n * the agent narrows the enabled kinds via `createMapAgent`'s `dataEntries`, the\n * builder produces a narrower schema in {@link buildAnalyseDataEntry}.\n *\n * Exported for backward compat with consumers that imported the static schema.\n */\nexport const analyseDataScopeSchema = buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS);\n\n/** Output schema for analyse-data. */\nexport const analyseDataOutputSchema = z.union([\n z.object({\n affectedEntries: z\n .array(\n z.union([\n z.object({ kind: z.literal('places'), id: z.string() }),\n z.object({ kind: z.literal('routes'), id: z.string() }),\n z.object({ kind: z.literal('incidents'), id: z.string() }),\n z.object({ kind: z.literal('customGeometries'), id: z.string() }),\n z.object({ kind: z.literal('trafficAreaAnalytics'), id: z.string() }),\n ]),\n )\n .describe(\n 'Entries the analysis was attached to: every contributing places / routes / incidents / ' +\n 'customGeometries / trafficAreaAnalytics entry. Ranges entries feed the aggregation but do not carry analyses.',\n ),\n sourceIds: z\n .array(z.unknown())\n .optional()\n .describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only present when \\`geometriesEntryIDs\\` was set).`),\n name: z.string().describe('Unique name of the analysis within each affected entry.'),\n description: z.string().optional(),\n outputFormat: z.enum(['json', 'chart']),\n analysis: z.unknown(),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\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 ran with `monitor` AND returned `focusIds: string[]` (+ optional ' +\n '`focusReason`). The tool applied that focus as a side-effect on the source incidents entry.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/** Build the scope-aware `code` field description for analyse-data. */\nexport const buildAnalyseDataCodeDoc = (\n enabled: readonly EntryDataKind[],\n scope: AnalyseDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const sandboxList = buildEntryKindSandboxDocs(active);\n // Deep per-kind schema docs are gated on `scope !== undefined` — they only appear once\n // the classifier commits to specific kinds. The unscoped fallback shows only the terse\n // one-liners, which is enough to write reasonable code without 3-4 KB of schema\n // reference per kind.\n const schemaBlocks = scope ? buildEntryKindSchemaDocs(active) : '';\n // Cross-kind ops cheat-sheet only matters when more than one kind is in scope. It's a\n // sizeable doc (~2 KB) so we keep it strictly gated on scoped + multi-kind.\n const showCrossKind = !!scope && active.length > 1;\n // Geometries provenance doc only matters when customGeometries is actually in scope.\n const showGeometriesProps = !!scope && active.includes('customGeometries');\n // Incidents monitor + focus side-effect blocks only matter when incidents is in scope.\n // ~400 tokens and irrelevant to non-incidents analyses, so gate them too.\n const showIncidentsExtras = !scope || active.includes('incidents');\n const incidentsExtras = showIncidentsExtras\n ? 'When `monitor: { entryId }` is set, the call ALSO registers as a recurring spec on that incidents entry. ' +\n 'On every monitor tick the code re-runs against the fresh `incidents` snapshot and these EXTRA sandbox args ' +\n 'become available:\\n' +\n '• `previous` — your last result for this `name` on the source entry, or `undefined` on the first run. Use it ' +\n 'for id stability (e.g. reuse a prior cluster id when memberIds overlap by ≥50%) and short trend reads ' +\n \"('growing' / 'fading' / 'steady'). Always guard `previous === undefined` — true on every first run.\\n\" +\n '• `now: Date` — call-start time; compare against `startTime` / `endTime` on incidents.\\n' +\n '• `log: (...args) => void` — writes into the response logs.\\n' +\n 'Constraint: cross-kind reruns are NOT supported — when `monitor` is set, `incidentsEntryIDs: [monitor.entryId]` ' +\n 'is the only allowed input. For one-shot cross-kind incidents analysis (no rerun), omit `monitor`.\\n' +\n 'FOCUS SIDE-EFFECT: when `applyFocus !== false` AND the result has `focusIds: string[]` (+ optional ' +\n '`focusReason`), `setFocus` is called once on the source entry to dim/highlight the listed incidents. ' +\n 'One-shot — the focus side-effect does NOT re-apply on monitor-tick replays.\\n\\n'\n : '';\n return (\n 'Async JS that aggregates the injected inputs and returns the result.\\n\\n' +\n `${buildSandboxCodePrompt(MULTI_INPUT_SANDBOX_PARAMS)}\\n\\n` +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading ' +\n '(`if (places) ...`, `routes?.features.length`, etc.). When defined:\\n' +\n `${sandboxList}\\n\\n` +\n incidentsExtras +\n (showCrossKind ? `${CROSS_KIND_OPS_DOC}\\n\\n` : '') +\n `${buildAnalyseReturnPrompt('counts, per-kind breakdowns, hex bins, intersections, distance bins')}\\n\\n` +\n (showGeometriesProps ? `${GEOMETRIES_PROPS_DOC}\\n\\n` : '') +\n schemaBlocks\n );\n};\n\n/** Build the scope-aware analyseData tool input schema. */\nexport const buildAnalyseDataSchema = (enabled: readonly EntryDataKind[], scope: AnalyseDataScope | undefined) => {\n const active = resolveActiveKinds(enabled, scope);\n const requiredHint = `At least one of ${buildEntryFieldList(active)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(active, { verb: 'analyse', requiredHint }),\n };\n\n shape.name = z\n .string()\n .describe(\n 'Unique name within each affected entry (e.g. \"incidents-per-cluster\", \"ev-near-route\"). ' +\n 'Reusing a name replaces the previous analysis on those entries.',\n );\n shape.description = z.string().optional().describe('Optional short description of what the analysis computes.');\n shape.outputFormat = z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION);\n shape.code = z.string().describe(buildAnalyseDataCodeDoc(enabled, scope));\n\n // Monitor + applyFocus are only meaningful when `incidents` is in the active set. Out of\n // scope or disabled at the agent level → drop both fields entirely so the LLM doesn't see\n // surface that can't be exercised.\n const incidentsActive = isKindActive(enabled, scope, 'incidents');\n if (incidentsActive) {\n shape.monitor = z\n .object({\n entryId: z\n .string()\n .describe(\n 'Incidents entry id to register the spec on. Must match `incidentsEntryIDs[0]`. The spec re-runs on every monitor-tick of that entry.',\n ),\n })\n .optional()\n .describe(\n 'Opt-in: register this analysis as a recurring spec on the targeted incidents entry. The code re-runs ' +\n 'on every monitor-tick with extra sandbox args (`previous`, `now`, `log`). When set, only ' +\n '`incidentsEntryIDs: [monitor.entryId]` is allowed alongside — cross-kind reruns are not supported.',\n );\n shape.applyFocus = z\n .boolean()\n .optional()\n .describe(\n 'When `monitor` is set AND the result has `focusIds: string[]` (+ optional `focusReason`), call ' +\n '`setFocus` on the source entry to dim/highlight those incidents. Default: true. One-shot — the ' +\n 'focus side-effect does NOT re-apply on monitor-tick replays.',\n );\n }\n\n const baseObject = z.object(shape);\n\n return baseObject.refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(active) }).refine(\n (v: Record<string, unknown>) => {\n const monitor = v.monitor as { entryId: string } | undefined;\n if (!monitor) return true;\n // Monitor requires incidentsEntryIDs to match the targeted entry, and no other input kinds.\n const incidents = v.incidentsEntryIDs as string[] | undefined;\n const incidentsOk = Array.isArray(incidents) && incidents.length === 1 && incidents[0] === monitor.entryId;\n if (!incidentsOk) return false;\n // No other *EntryIDs may be present alongside incidents.\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n if (kind === 'incidents') continue;\n const fieldName = `${kind === 'customGeometries' ? 'geometries' : kind}EntryIDs`;\n const value = v[fieldName] as unknown[] | undefined;\n if (value?.length) return false;\n }\n return true;\n },\n {\n message:\n 'When `monitor` is set, only `incidentsEntryIDs: [monitor.entryId]` is allowed (cross-kind reruns are not supported in the lean merge). ' +\n 'Omit `monitor` for one-shot cross-kind incidents analysis.',\n },\n );\n};\n\n/** Default full-surface input schema for analyse-data — every kind enabled, no per-turn scope. */\nexport const analyseDataSchema = buildAnalyseDataSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\n/** Build the scope-aware analyseData tool description. */\nexport const buildAnalyseDataDescription = (\n enabled: readonly EntryDataKind[],\n scope: AnalyseDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const kindList = active.join(' / ');\n const incidentsTail =\n scope && active.includes('incidents')\n ? 'Opt-in `monitor: { entryId }` registers the call as a recurring spec on the targeted incidents entry; ' +\n 'opt-in `applyFocus` honours `focusIds: string[]` by calling `setFocus`.'\n : '';\n const contrast =\n 'CONTRAST `processData`, which writes NEW renderable map entries (places / custom-geometries / BYOD); ' +\n '`analyseData` never renders or creates entries — only attaches metadata.';\n if (scope) {\n return (\n `Aggregate ${kindList} entries via dynamic JS — counts, groupings, charts, hex bins, ` +\n `cross-kind correlations. ${contrast} Each scoped input is exposed as a merged collection and a ` +\n 'per-entry record; fields are `undefined` when their `*EntryIDs` argument is omitted (guard with `?.`). ' +\n 'Result is attached as `_analysis[name]` on every contributing entry. ' +\n '`outputFormat: \"json\"` (default) or `\"chart\"` (Chart.js config). ' +\n incidentsTail\n );\n }\n const incidentsUnscopedTail = active.includes('incidents')\n ? 'Opt-in `monitor: { entryId }` registers the call as a recurring spec on the targeted incidents entry — the ' +\n 'code re-runs on every monitor-tick with `previous` / `now` / `log` available in the sandbox. When `monitor` ' +\n 'is set, only `incidentsEntryIDs: [monitor.entryId]` is allowed alongside (no cross-kind reruns). ' +\n 'Opt-in `applyFocus` (default true under `monitor`) honours `focusIds: string[]` on the result by calling ' +\n '`setFocus` on the source entry to dim/highlight a subset.'\n : '';\n return (\n `Aggregate ${kindList} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ` +\n `${contrast} ` +\n 'Each input is exposed as a merged collection and a per-entry record; fields are `undefined` when their ' +\n '`*EntryIDs` argument is omitted (guard with `?.` / `if (places) …`). ' +\n 'Result is attached as `_analysis[name]` on every contributing entry (ranges feed in but do not carry ' +\n 'analyses; BYOD entries are read-only). `outputFormat: \"json\"` (default) or `\"chart\"` (Chart.js config). ' +\n incidentsUnscopedTail\n );\n};\n\n/** Default full-surface description — every kind enabled. */\nexport const analyseDataDescription = buildAnalyseDataDescription(ALL_ENTRY_DATA_KINDS, undefined);\n\n// Build the classifier hint dynamically so disabled kinds don't appear as scope choices.\nconst buildAnalyseDataScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((k) => `\"${k}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` ` +\n 'listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all).'\n );\n};\n\ntype AnalyseDataInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n name: string;\n description?: string;\n code: string;\n outputFormat?: 'json' | 'chart';\n monitor?: { entryId: string };\n applyFocus?: boolean;\n};\n\nexport const executeAnalyseData = async (\n params: AnalyseDataInput,\n state: ToolState,\n): Promise<z.infer<typeof analyseDataOutputSchema>> => {\n const {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n name,\n description,\n code,\n monitor,\n applyFocus,\n } = params;\n const outputFormat = params.outputFormat ?? 'json';\n\n // Monitor path: register a recurring spec on the source incidents entry, run with the\n // incidents-only sandbox surface (incidents / h3 / turf / now / log / previous), then\n // optionally apply the focus side-effect. Mirrors the old analyse-incidents semantics\n // but lives behind the analyseData surface.\n if (monitor) {\n return runMonitorPath(state, monitor.entryId, name, description, code, outputFormat, applyFocus !== false);\n }\n\n const prepared = await prepareMultiInputs(\n {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n state,\n );\n if ('error' in prepared) return { error: prepared.error };\n const { resolved, sandbox, geometries: geometriesMeta } = prepared.value;\n\n const sandboxResult = await runSandboxedFn(\n code,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs(sandbox, { 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 // Attach to every contributing entry across all slices, with a shared\n // timestamp so the UI can correlate the per-slice updates back to one call.\n const timestamp = Date.now();\n const payload = { name, description, outputFormat, analysis, timestamp };\n attachAnalysisToEntries(state.places, resolved.places, payload);\n attachAnalysisToEntries(state.routing, resolved.routes, payload);\n attachAnalysisToEntries(state.trafficIncidents, resolved.incidents, payload);\n attachAnalysisToEntries(state.trafficAreaAnalytics, resolved.trafficAreaAnalytics, payload);\n // Geometries pull in places + custom entries indirectly via `collectInputGeometries`.\n attachAnalysisToEntries(\n state.places,\n geometriesMeta.affectedEntries.filter((e) => e.kind === 'places').map((e) => ({ id: e.id })),\n payload,\n );\n attachAnalysisToEntries(\n state.customGeometries,\n geometriesMeta.affectedEntries.filter((e) => e.kind === 'customGeometries').map((e) => ({ id: e.id })),\n payload,\n );\n\n const affectedEntries: Array<{\n kind: 'places' | 'routes' | 'incidents' | 'customGeometries' | 'trafficAreaAnalytics';\n id: string;\n }> = [\n ...resolved.places.map((e) => ({ kind: 'places' as const, id: e.id })),\n ...resolved.routes.map((e) => ({ kind: 'routes' as const, id: e.id })),\n ...resolved.incidents.map((e) => ({ kind: 'incidents' as const, id: e.id })),\n ...resolved.trafficAreaAnalytics.map((e) => ({ kind: 'trafficAreaAnalytics' as const, id: e.id })),\n ...geometriesMeta.affectedEntries\n .filter((e) => e.kind === 'places' && !resolved.places.some((p) => p.id === e.id))\n .map((e) => ({ kind: 'places' as const, id: e.id })),\n ...geometriesMeta.affectedEntries\n .filter((e) => e.kind === 'customGeometries')\n .map((e) => ({ kind: 'customGeometries' as const, id: e.id })),\n ];\n\n const sourceIds: GeometriesId[] | undefined = geometriesEntryIDs ? geometriesMeta.sourceIds : undefined;\n\n return {\n affectedEntries,\n ...(sourceIds && { sourceIds }),\n name,\n ...(description && { description }),\n outputFormat,\n analysis,\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n };\n};\n\n/**\n * Monitor path — incidents-only spec rerun. Mirrors the old `executeAnalyseIncidents` but\n * returns analyseData's output shape (affectedEntries + optional focused).\n *\n * Registers the spec BEFORE the first run so monitor-tick replays survive an initial-run\n * error. The focus side-effect is one-shot — replays read the registered spec but skip the\n * setFocus call.\n */\nconst runMonitorPath = async (\n state: ToolState,\n entryId: string,\n name: string,\n description: string | undefined,\n code: string,\n outputFormat: 'json' | 'chart',\n applyFocus: boolean,\n): Promise<z.infer<typeof analyseDataOutputSchema>> => {\n const sourceEntry = state.trafficIncidents.entries.find((e) => e.id === entryId);\n if (!sourceEntry) {\n return {\n error: `No incidents entry with id \"${entryId}\". 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 a failing initial run still leaves the spec\n // available 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 if ('error' in result) return { error: result.error };\n\n const { value: analysis } = result;\n attachAnalysisToEntries(state.trafficIncidents, [sourceEntry], {\n name,\n description,\n outputFormat,\n analysis,\n timestamp: sampledAt,\n });\n\n const focused = applyFocus ? applyFocusSideEffect(analysis, sourceEntry.id, state) : undefined;\n\n return {\n affectedEntries: [{ kind: 'incidents' as const, id: 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// Honour `focusIds` / `focusReason` on the analysis result by calling `setFocus` against the\n// source incidents entry. No-op when the result does not carry valid focus ids.\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/**\n * Scope-aware builder for the analyseData tool. Reads classifier-resolved `scope` AND the\n * agent's `enabledDataKinds` from {@link ToolBuildOptions} so disabled kinds disappear from\n * the description, schema, code-doc, classifier scope, and the monitor refine. Without\n * either, emits the full surface unchanged. Caller supplies static metadata (tags,\n * examples, related/depends, classificationPrompt) via the second positional arg so the\n * registry stays the single source of truth for those.\n */\nexport const buildAnalyseDataEntry = (\n options: ToolBuildOptions<AnalyseDataScope>,\n metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n },\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n return {\n description: buildAnalyseDataDescription(enabled, options.scope),\n inputSchema: buildAnalyseDataSchema(enabled, options.scope),\n outputSchema: analyseDataOutputSchema,\n execute: executeAnalyseData as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildAnalyseDataScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/**\n * Default builder for the `analyseData` tool. Reads classifier-resolved scope and\n * agent-level enabled kinds from {@link ToolBuildOptions} and produces a narrowed entry;\n * without either, emits the full surface unchanged. The registry supplies the rest of\n * the metadata.\n */\nexport const analyseDataBuilder =\n (metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n }): ToolEntryBuilder<ToolState, AnalyseDataScope> =>\n (options) =>\n buildAnalyseDataEntry(options, metadata);\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 `analyseData` 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 queued for the NEXT setRoute call (origin / stops / destination not yet committed to a route). ' +\n 'Distinct from `recallRoutes({ id })`, which returns the waypoints of an existing calculated route entry. ' +\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 * Sandbox-running processing tool. Counterpart to `analyseData`: accepts any\n * combination of places / routes / incidents / customGeometries /\n * trafficAreaAnalytics / byod entries as inputs and produces a NEW places entry,\n * a NEW custom-geometries entry, a NEW BYOD entry, a camera fit, or any\n * combination of those, from a single sandbox run.\n *\n * Output disambiguation:\n * • `places` returned → writes a NEW places entry; `placeConnections` and\n * inline `geometries` (when also present) are attached to that entry.\n * • `geometries` returned without `places` → writes a NEW custom-geometries entry.\n * • `byod` returned alone → writes a NEW BYOD entry.\n * • `fitOnMap` alone → camera move only (no entry written).\n *\n * **Scope + enablement.** Shared scaffolding in `tools/shared/entry-kinds.ts` builds\n * the per-kind input fields, the per-kind sandbox / schema docs, the scope schema and\n * the \"at least one of …\" message. This file only adds the tool-specific surface:\n * the return-shape preamble, `routeUtils`, fit-on-map / show / connectMarkerType /\n * label / entryId / operation, the description, the result validator and the executor.\n */\n\nimport {\n bboxFromGeoJSON,\n type CommonPlaceProps,\n calculateProgressAtRoutePoint,\n getCoordinateAtRouteProgress,\n getProgressAtNearestRoutePoint,\n getRouteProgressBetween,\n getRouteProgressForSection,\n getSectionBBox,\n type Places,\n type PolygonFeature,\n} from '@tomtom-org/maps-sdk/core';\nimport type { PlaceConnectionDisplay } from '@tomtom-org/maps-sdk/map';\nimport * as turf from '@turf/turf';\nimport type { FeatureCollection as GeoJSONFeatureCollection, GeoJsonProperties } from 'geojson';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport {\n ALL_ENTRY_DATA_KINDS,\n applyFitOnMap,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxCodePrompt,\n CROSS_KIND_OPS_DOC,\n type EntryKindScope,\n FIT_ON_MAP_DOC,\n type FitOnMapInput,\n fittedReportSchema,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n type GeometriesMeta,\n geometriesIdSchema,\n hasAnyEntryIds,\n hidePreviousEntriesSchema,\n hidePreviousShownEntries,\n isFitOnMapInput,\n isPolygonFeature,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n placesEntryIdHintSchema,\n prepareMultiInputs,\n resolveActiveKinds,\n runSandboxedFn,\n showModeSchema,\n showPlacesSchema,\n showResultsOnMap,\n skippedSourceSchema,\n} from '../shared';\nimport { buildPlacesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Per-turn scope kinds that `processData` understands. Identical to {@link EntryDataKind}.\n *\n * @group Agent Toolkit\n */\nexport type ProcessDataKind = EntryDataKind;\n\n/**\n * Per-turn scope for `processData`. Emitted by the classifier in `toolScopes.processData`\n * when only a subset of input kinds is relevant.\n *\n * @group Agent Toolkit\n */\nexport type ProcessDataScope = EntryKindScope;\n\n/**\n * Default scope schema for processData — accepts every {@link EntryDataKind}. When the\n * agent narrows the enabled kinds via `createMapAgent`'s `dataEntries`, the builder\n * produces a narrower schema in {@link buildProcessDataEntry}.\n */\nexport const processDataScopeSchema = buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS);\n\n// `routeUtils` namespace — same set processRoutes injects, reused here so any\n// routes input can be sliced by section/progress without re-implementing it.\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, only useful when `routesEntryIDs` is set):\\n' +\n '• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>`.\\n' +\n '• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startIdx, endIdx)`.\\n' +\n '• `calculateProgressAtRoutePoint(route, pathIndex)`.\\n' +\n '• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }`.\\n' +\n '• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point to the line.\\n' +\n '• `bboxFromGeoJSON(featureOrCollection)`.';\n\nconst PROCESS_DATA_SANDBOX_PARAMS = [...MULTI_INPUT_SANDBOX_PARAMS, 'routeUtils'] as const;\n\nconst showProducedGeometriesSchema = z.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').optional(),\n});\n\nconst sharedProcessDataReport = {\n sourceIds: z\n .array(geometriesIdSchema)\n .optional()\n .describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only when \\`geometriesEntryIDs\\` was set).`),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\n fitted: fittedReportSchema.optional(),\n} as const;\n\n/** Output schema for process-data. */\nexport const processDataOutputSchema = z.union([\n // Places branch: code returned `places`. Attached connections + polygons go on the new entry.\n buildPlacesOutputSchema({}).extend({\n placesEntryId: z.string(),\n label: z.string().optional(),\n connectionsRendered: z.number().optional(),\n placesGeometryCount: z\n .number()\n .optional()\n .describe('Polygons attached to the new places entry (when `geometries` was returned alongside).'),\n shownPlaces: z.boolean().optional(),\n ...sharedProcessDataReport,\n }),\n // Custom-geometries branch: code returned `geometries` WITHOUT `places`.\n z.object({\n customGeometriesEntryId: geometriesIdSchema,\n customGeometriesCount: z.number(),\n label: z.string(),\n operation: z.string().optional(),\n shownCustomGeometries: z.boolean().optional(),\n ...sharedProcessDataReport,\n }),\n // BYOD branch: code returned `byod` WITHOUT `places`/`geometries`. Writes a new BYOD entry.\n z.object({\n byodEntryId: z.string(),\n byodFeatureCount: z.number(),\n label: z.string(),\n fitted: fittedReportSchema.optional(),\n }),\n // Fit-only branch: code returned `fitOnMap` alone.\n z.object({ fitted: fittedReportSchema }),\n toolErrorSchema,\n]);\n\n/** Build the scope-aware `code` field description for process-data. */\nexport const buildProcessDataCodeDoc = (\n enabled: readonly EntryDataKind[],\n scope: ProcessDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n // Deep per-kind schema docs only appear once the classifier has committed to specific\n // kinds. The unscoped path stays terse (terse one-line field hints already live in the\n // input-field describes; the code-doc just needs the operational reference).\n const schemaBlocks = scope ? buildEntryKindSchemaDocs(active) : '';\n const showCrossKind = !!scope && active.length > 1;\n const showTrafficNote = !!scope && active.includes('trafficAreaAnalytics');\n const trafficNote = showTrafficNote\n ? '`trafficAreaAnalytics` is a FeatureCollection of tile/hex regions whose `feature.properties` carry metric ' +\n 'fields (`congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`); use ' +\n '`turf.booleanPointInPolygon` / `turf.lineIntersect` to bridge with places/routes.\\n\\n'\n : '';\n const routeUtilsDoc = !!scope && active.includes('routes') ? `${ROUTE_UTILS_DOC}\\n\\n` : '';\n const geometriesProps = !!scope && active.includes('customGeometries') ? `${GEOMETRIES_PROPS_DOC}\\n\\n` : '';\n const byodOutputLine = active.includes('byod')\n ? '• `byod: FeatureCollection` — writes a NEW BYOD entry (any GeoJSON: Point/Line/Polygon/mixed). Use for ' +\n 'derived BYOD layers (e.g. cleaned/filtered customer data).\\n'\n : '';\n // Per-kind sandbox-doc one-liners — only when scope commits to specific kinds (matches\n // analyseData's gating; otherwise the input-field describes already carry enough info).\n const sandboxList = scope ? `${buildEntryKindSandboxDocs(active)}\\n` : '';\n return (\n 'Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`. At least one of ' +\n '`places` / `geometries` / `fitOnMap` / `byod` must be present. ' +\n '`places` (with optional attached `geometries`), standalone `geometries`, and `byod` are MUTUALLY ' +\n 'EXCLUSIVE — only one is written per call, others are dropped. `fitOnMap` composes with any of them.\\n\\n' +\n '• `places: Places` — writes a NEW places entry. Filters/merges/subsets of `places.features`, or computed point ' +\n 'features (must conform to the Place schema).\\n' +\n '• `placeConnections: { from, to, id? }[]` — lines between output places (endpoints: Place or place id). ' +\n \"Requires `places`. Drawn on the new entry's PlacesModule.\\n\" +\n '• `geometries: PolygonFeature[]` — polygons (Polygon/MultiPolygon, stable `id` recommended). ' +\n 'When `places` is also present, attached to the new places entry; when `places` is absent, written as a NEW ' +\n 'custom-geometries entry.\\n' +\n '• `fitOnMap` — bbox or `{ bbox, padding?, animate? }`. Camera move; when alone, no entry written.\\n' +\n byodOutputLine +\n '\\n' +\n `${buildSandboxCodePrompt(PROCESS_DATA_SANDBOX_PARAMS)}\\n\\n` +\n sandboxList +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. ' +\n trafficNote +\n (showCrossKind ? `${CROSS_KIND_OPS_DOC}\\n\\n` : '') +\n routeUtilsDoc +\n `${FIT_ON_MAP_DOC}\\n\\n` +\n geometriesProps +\n schemaBlocks\n );\n};\n\n/** Build the scope-aware processData input schema. */\nexport const buildProcessDataSchema = (enabled: readonly EntryDataKind[], scope: ProcessDataScope | undefined) => {\n const active = resolveActiveKinds(enabled, scope);\n const requiredHint = `At least one of ${buildEntryFieldList(active)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(active, { verb: 'process', requiredHint }),\n };\n\n shape.code = z.string().describe(buildProcessDataCodeDoc(enabled, scope));\n shape.label = z\n .string()\n .optional()\n .describe('Short label for the new entry (used by both places and custom-geometries paths).');\n shape.entryId = placesEntryIdHintSchema;\n shape.operation = z\n .string()\n .optional()\n .describe(\n 'Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance ' +\n 'on a new custom-geometries entry. Ignored on the places path.',\n );\n // `show` sub-fields are conditional on the corresponding kind being writable on the active\n // set: `places` write is possible whenever the code returns places (always allowed —\n // returning `places` is a sandbox concern, not a *EntryIDs concern); same for\n // `customGeometries`. We keep all three sub-fields exposed so the LLM can return any\n // supported kind without a scope twist. The executor only consults the matching field.\n shape.show = z\n .object({\n places: showPlacesSchema.optional().describe('Render the new places entry (when `places` was returned).'),\n placesGeometries: z\n .object({\n theme: z.enum(['filled', 'outline', 'inverted']).optional(),\n mode: showModeSchema,\n })\n .optional()\n .describe(\n 'Render the polygons attached to the new places entry (when both `places` and `geometries` were returned).',\n ),\n customGeometries: showProducedGeometriesSchema\n .optional()\n .describe('Render the new custom-geometries entry (when `geometries` was returned WITHOUT `places`).'),\n })\n .optional()\n .describe(\n 'Optional per-output render knobs. The executor only consults the field matching what the code returned ' +\n '(so leaving e.g. `customGeometries` set when `places` was returned is a silent no-op).',\n );\n shape.connectMarkerType = z\n .enum(['pin', 'base-map'])\n .optional()\n .describe(\n 'Which PlacesModule renders `placeConnections`. Default: \"pin\". Match the module holding the endpoints.',\n );\n\n return z.object(shape).refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(active) });\n};\n\n/** Default full-surface input schema for process-data — every kind enabled, no per-turn scope. */\nexport const processDataSchema = buildProcessDataSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\n/** Build the scope-aware processData tool description. */\nexport const buildProcessDataDescription = (\n enabled: readonly EntryDataKind[],\n scope: ProcessDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const kindList = active.join(' / ');\n const contrast =\n 'CONTRAST `analyseData`, which attaches JSON/chart metadata to existing entries and never renders.';\n if (scope) {\n return (\n `Run dynamic JS over ${kindList} entries to produce NEW renderable map data — a places, ` +\n 'custom-geometries, or BYOD entry (with optional `placeConnections` / attached polygon footprints), ' +\n `and/or a \\`fitOnMap\\` camera move. ${contrast} Returns ` +\n '`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`: when `places` is returned, inline ' +\n '`geometries` attach to it; otherwise they become a standalone custom-geometries entry. Inputs are ' +\n '`undefined` when their `*EntryIDs` argument is omitted — guard before reading.'\n );\n }\n return (\n `Run dynamic JS over any combination of ${kindList} entries to produce NEW renderable map data — a places, ` +\n 'custom-geometries, or BYOD entry (with optional `placeConnections` / attached polygon footprints), ' +\n `and/or a \\`fitOnMap\\` camera move. ${contrast} Returns ` +\n '`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`: when `places` is returned, inline ' +\n '`geometries` attach to it as place footprints; otherwise they become a standalone custom-geometries entry. ' +\n '`routeUtils` is exposed alongside `h3` / `turf` for route section / progress work. Inputs are `undefined` ' +\n 'when their `*EntryIDs` argument is omitted — guard before reading.'\n );\n};\n\n/** Default full-surface description — every kind enabled. */\nexport const processDataDescription = buildProcessDataDescription(ALL_ENTRY_DATA_KINDS, undefined);\n\nconst buildProcessDataScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((k) => `\"${k}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` ` +\n 'listing only the entry kinds the user query touches. Omit when truly cross-kind.'\n );\n};\n\ntype ProcessResult = {\n places?: Places;\n placeConnections?: PlaceConnectionDisplay[];\n geometries?: PolygonFeature[];\n fitOnMap?: FitOnMapInput;\n byod?: GeoJSONFeatureCollection;\n};\n\n// Both BYOD and Places outputs are FeatureCollections with no further per-feature checks at this\n// layer — keep one shape check and let the call site choose the narrow it wants.\nconst isFeatureCollectionLike = <T>(value: unknown): value is T => {\n if (!value || typeof value !== 'object') return false;\n const candidate = value as { type?: unknown; features?: unknown };\n return candidate.type === 'FeatureCollection' && Array.isArray(candidate.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\n// Walk the raw `geometries` value, narrow it to `PolygonFeature[]` with a pinpointed\n// error message on the first bad element. Splitting validation out keeps the type\n// predicate flowing into the returned `ProcessResult` without any `as` casts.\nconst validateGeometries = (raw: unknown): { value: PolygonFeature[] } | { error: string } => {\n if (!Array.isArray(raw)) {\n return { error: '`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features.' };\n }\n const validated: PolygonFeature[] = [];\n for (let i = 0; i < raw.length; i++) {\n const item = raw[i];\n // Generic-explicit so the narrowed `item` matches `PolygonFeature`'s default `GeoJsonProperties`\n // (otherwise the predicate's `<P = unknown>` default leaks `PolygonFeature<unknown>` here).\n if (!isPolygonFeature<GeoJsonProperties>(item)) {\n return { error: `\\`geometries[${i}]\\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.` };\n }\n validated.push(item);\n }\n return { value: validated };\n};\n\n// `validateField` collapses the \"either the optional field is missing OR it passes the shape check\"\n// pattern into one helper, so `validateProcessOutput` stays under the per-function cognitive\n// complexity cap (each per-field block was a nested `if` previously). Returning `value: undefined`\n// for an absent field lets callers assign unconditionally.\nconst validateField = <T>(\n raw: unknown,\n check: (value: unknown) => value is T,\n errorMessage: string,\n): { value: T | undefined } | { error: string } => {\n if (raw === undefined) return { value: undefined };\n if (!check(raw)) return { error: errorMessage };\n return { value: raw };\n};\n\n// Treat an empty FeatureCollection as \"no content produced\". Write sites downstream use truthy\n// checks (`if (processed)` / `if (producedByod)`), and the joint-constraint check below counts\n// fields as \"set\" by `!== undefined`. Without this normalisation, returning `{ features: [] }`\n// would slip past both — writing an empty places/byod entry while a non-empty geometries / byod\n// return on the same call gets silently dropped (single-write-per-call semantics).\nconst nonEmptyFeatureCollection = <T extends { features: readonly unknown[] }>(fc: T | undefined): T | undefined =>\n fc && fc.features.length > 0 ? fc : undefined;\n\n// Joint-shape rule: at least one writable output must be present, and placeConnections may only\n// accompany places. Split out so `validateProcessOutput`'s per-field validations stay flat.\nconst checkProcessJointConstraints = (result: ProcessResult): string | undefined => {\n const noOutput =\n result.places === undefined &&\n result.geometries === undefined &&\n result.fitOnMap === undefined &&\n result.byod === undefined;\n if (noOutput) {\n return 'At least one of `places`, `geometries`, `fitOnMap`, `byod` must produce content (empty arrays / FeatureCollections do not count).';\n }\n if (result.places === undefined && result.placeConnections !== undefined) {\n return '`placeConnections` requires `places` to be returned alongside.';\n }\n return undefined;\n};\n\nconst validateProcessOutput = (raw: unknown): ProcessResult | string => {\n if (!raw || typeof raw !== 'object') {\n return 'Process code must return `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`.';\n }\n const candidate = raw as {\n places?: unknown;\n placeConnections?: unknown;\n geometries?: unknown;\n fitOnMap?: unknown;\n byod?: unknown;\n };\n\n const result: ProcessResult = {};\n\n const places = validateField<Places>(\n candidate.places,\n isFeatureCollectionLike<Places>,\n '`places` must be a `Places` FeatureCollection of Point features.',\n );\n if ('error' in places) return places.error;\n result.places = nonEmptyFeatureCollection(places.value);\n\n const placeConnections = validateField(\n candidate.placeConnections,\n isConnectionArray,\n '`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).',\n );\n if ('error' in placeConnections) return placeConnections.error;\n result.placeConnections = placeConnections.value;\n\n if (candidate.geometries !== undefined) {\n const validated = validateGeometries(candidate.geometries);\n if ('error' in validated) return validated.error;\n // Empty array drops to undefined for the same reason as `nonEmptyFeatureCollection` above —\n // joint-constraint + downstream truthy checks treat absent and empty consistently.\n result.geometries = validated.value.length > 0 ? validated.value : undefined;\n }\n\n const fitOnMap = validateField(\n candidate.fitOnMap,\n isFitOnMapInput,\n '`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.',\n );\n if ('error' in fitOnMap) return fitOnMap.error;\n result.fitOnMap = fitOnMap.value;\n\n const byod = validateField<GeoJSONFeatureCollection>(\n candidate.byod,\n isFeatureCollectionLike<GeoJSONFeatureCollection>,\n '`byod` must be a GeoJSON FeatureCollection (any geometry types).',\n );\n if ('error' in byod) return byod.error;\n result.byod = nonEmptyFeatureCollection(byod.value);\n\n const jointError = checkProcessJointConstraints(result);\n return jointError ?? result;\n};\n\ntype ProcessDataInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n code: string;\n label?: string;\n entryId?: string;\n operation?: string;\n show?: {\n places?: z.infer<typeof showPlacesSchema>;\n placesGeometries?: { theme?: 'filled' | 'outline' | 'inverted'; mode?: 'add' | 'replace' };\n customGeometries?: z.infer<typeof showProducedGeometriesSchema>;\n };\n connectMarkerType?: 'pin' | 'base-map';\n};\n\ntype ProcessOutput = z.infer<typeof processDataOutputSchema>;\n\n// Write the BYOD branch's output. No places/geometries override — BYOD layers manage their own\n// visibility via state.byod, so we just write the entry and optionally fit the camera.\nconst writeByodResult = async (\n state: ToolState,\n params: ProcessDataInput,\n producedByod: GeoJSONFeatureCollection,\n fitOnMap: FitOnMapInput | undefined,\n): Promise<ProcessOutput> => {\n const { label, entryId } = params;\n const byodLabel = label ? `processed: ${label}` : `processed byod (${producedByod.features.length})`;\n const newByodEntryId = await state.byod.addEntry(producedByod, byodLabel, {\n ...(entryId && { explicitId: entryId }),\n });\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n return {\n byodEntryId: newByodEntryId,\n byodFeatureCount: producedByod.features.length,\n label: byodLabel,\n ...(fitted && { fitted }),\n };\n};\n\n// Optional connection rendering — pulled out so its try/catch + module-access doesn't bloat the\n// places-path branch counter.\nconst renderConnections = async (\n state: ToolState,\n newPlacesEntryId: string,\n placeConnections: PlaceConnectionDisplay[],\n connectMarkerType: 'pin' | 'base-map' | undefined,\n): Promise<{ count: number } | { error: string }> => {\n try {\n const placesModule = await state.places.getEntryPlacesModule(newPlacesEntryId, connectMarkerType ?? 'pin');\n await placesModule.showConnections(placeConnections);\n return { count: placeConnections.length };\n } catch (error) {\n return { error: `Failed to render connections: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n// Places branch — writes the entry, optionally renders it, attaches polygons / connections, and\n// fits the camera. Returns the same shape as the executor's places branch did inline.\nconst writePlacesResult = async (\n state: ToolState,\n params: ProcessDataInput,\n validated: ProcessResult & { places: Places },\n geometriesMeta: GeometriesMeta,\n): Promise<ProcessOutput> => {\n const { label, entryId, show, connectMarkerType, geometriesEntryIDs } = params;\n const { places: processed, placeConnections, geometries: producedGeometries, fitOnMap } = validated;\n\n const count = processed.features.length;\n const labelPrefix = label ? `processed: ${label}` : 'processed';\n const placeWord = count === 1 ? 'place' : 'places';\n const entryLabel = `${labelPrefix} (${count} ${placeWord})`;\n const newPlacesEntryId = await state.places.addPlaceResult(\n processed,\n entryLabel,\n entryId,\n placeConnections,\n producedGeometries as PolygonFeature<CommonPlaceProps>[] | undefined,\n );\n\n const shownPlaces = show?.places ? await showResultsOnMap(state, [newPlacesEntryId], show.places) : undefined;\n\n let placesGeometryCount: number | undefined;\n if (producedGeometries?.length) {\n placesGeometryCount = producedGeometries.length;\n if (show?.placesGeometries) {\n await state.places.showPlaceGeometries(\n producedGeometries as PolygonFeature<CommonPlaceProps>[],\n show.placesGeometries.theme ?? 'outline',\n show.placesGeometries.mode ?? 'replace',\n newPlacesEntryId,\n );\n }\n }\n\n let connectionsRendered: number | undefined;\n if (placeConnections) {\n const rendered = await renderConnections(state, newPlacesEntryId, placeConnections, connectMarkerType);\n if ('error' in rendered) return rendered;\n connectionsRendered = rendered.count;\n }\n\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n const storedLabel = state.places.entries.find((entry) => entry.id === newPlacesEntryId)?.label;\n\n return {\n ...summarizePlaces(processed),\n placesEntryId: newPlacesEntryId,\n ...(storedLabel && { label: storedLabel }),\n ...(shownPlaces && { shownPlaces: true }),\n ...(connectionsRendered !== undefined && { connectionsRendered }),\n ...(placesGeometryCount !== undefined && { placesGeometryCount }),\n ...(geometriesEntryIDs && { sourceIds: geometriesMeta.sourceIds }),\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n ...(fitted && { fitted }),\n };\n};\n\n// Custom-geometries branch — writes a new entry from the produced polygons and optionally renders.\nconst writeCustomGeometriesResult = async (\n state: ToolState,\n params: ProcessDataInput,\n producedGeometries: PolygonFeature[],\n fitOnMap: FitOnMapInput | undefined,\n geometriesMeta: GeometriesMeta,\n): Promise<ProcessOutput> => {\n const { label, entryId, operation, show, geometriesEntryIDs } = params;\n const customLabel = label ? `processed: ${label}` : `processed geometries (${producedGeometries.length})`;\n const newCustomEntryId = await state.customGeometries.addEntry(\n producedGeometries as PolygonFeature<CommonPlaceProps>[],\n { sourceIds: geometriesMeta.sourceIds, ...(operation && { operation }) },\n customLabel,\n entryId,\n );\n\n let shownCustomGeometries = false;\n if (show?.customGeometries) {\n await hidePreviousShownEntries(\n state.customGeometries,\n [newCustomEntryId],\n show.customGeometries.hidePreviousEntries,\n );\n await state.customGeometries.showEntry(\n newCustomEntryId,\n show.customGeometries.theme ?? 'outline',\n show.customGeometries.mode ?? 'replace',\n );\n shownCustomGeometries = true;\n }\n\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n const customGeometriesEntryId: GeometriesId = { kind: 'customGeometries', id: newCustomEntryId };\n\n return {\n customGeometriesEntryId,\n customGeometriesCount: producedGeometries.length,\n label: customLabel,\n ...(operation && { operation }),\n ...(geometriesEntryIDs && { sourceIds: geometriesMeta.sourceIds }),\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n ...(shownCustomGeometries && { shownCustomGeometries: true }),\n ...(fitted && { fitted }),\n };\n};\n\nexport const executeProcessData = async (params: ProcessDataInput, state: ToolState): Promise<ProcessOutput> => {\n const {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n code,\n } = params;\n\n const prepared = await prepareMultiInputs(\n {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n state,\n );\n if ('error' in prepared) return { error: prepared.error };\n const { sandbox, geometries: geometriesMeta } = prepared.value;\n\n const sandboxResult = await runSandboxedFn(\n code,\n PROCESS_DATA_SANDBOX_PARAMS,\n [...packSandboxArgs(sandbox, { h3, turf }), routeUtils],\n 'Process',\n );\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 const { places: processed, geometries: producedGeometries, fitOnMap, byod: producedByod } = validated;\n\n // Places + BYOD in the same return aren't supported — places path takes precedence when both\n // are present (BYOD is silently dropped to keep single-write-per-call semantics).\n if (processed) {\n return writePlacesResult(state, params, { ...validated, places: processed }, geometriesMeta);\n }\n if (producedGeometries?.length) {\n return writeCustomGeometriesResult(state, params, producedGeometries, fitOnMap, geometriesMeta);\n }\n if (producedByod) {\n return writeByodResult(state, params, producedByod, fitOnMap);\n }\n // Fit-only: no entry produced, just move the camera.\n if (fitOnMap) {\n return { fitted: applyFitOnMap(state, fitOnMap) };\n }\n return { error: 'Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`.' };\n};\n\n/**\n * Scope-aware builder for the processData tool. Reads classifier-resolved `scope` AND the\n * agent's `enabledDataKinds` from {@link ToolBuildOptions} so disabled kinds disappear from\n * the description, schema, code-doc and classifier scope. The registry composes this with\n * static classifier metadata (tags / examples / related / depends / classificationPrompt)\n * so the registry remains the single source of truth for those fields.\n */\nexport const buildProcessDataEntry = (\n options: ToolBuildOptions<ProcessDataScope>,\n metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n },\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n return {\n description: buildProcessDataDescription(enabled, options.scope),\n inputSchema: buildProcessDataSchema(enabled, options.scope),\n outputSchema: processDataOutputSchema,\n execute: executeProcessData as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildProcessDataScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/**\n * Builder for the `processData` default tool entry. Reads classifier-resolved scope and\n * the agent's enabled kinds from {@link ToolBuildOptions} and produces a narrowed entry;\n * without either, emits the full surface unchanged.\n */\nexport const processDataBuilder =\n (metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n }): ToolEntryBuilder<ToolState, ProcessDataScope> =>\n (options) =>\n buildProcessDataEntry(options, metadata);\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 entryId: z\n .string()\n .optional()\n .describe(\n 'Traffic-area-analytics entry id to query (e.g. \"tta-0\"). Omit to query the most recently added entry. ' +\n 'Use `recallState` to list available ids.',\n ),\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 { entryId, granularity = 'daily', metric, startDate, endDate, hourStart, hourEnd, dayOfWeek } = params;\n\n const entry = entryId ? state.trafficAreaAnalytics.findById(entryId) : state.trafficAreaAnalytics.latestEntry;\n if (!entry) {\n return {\n error: entryId\n ? `No traffic-area-analytics entry with id \"${entryId}\". Use recallState to list available ids.`\n : 'No analytics entries available. Call getTrafficAreaAnalytics first.',\n };\n }\n const analytics = entry.data;\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 the source entry's visualization state so the LLM knows what's displayed (if anything).\n const vizConfig = entry._config;\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 { toolErrorSchema } from '../shared-output-schemas';\n\nconst sourceSchema = z.union([\n z.object({ kind: z.literal('integrator'), description: z.string().optional() }),\n z.object({ kind: z.literal('url'), url: z.string() }),\n z.object({ kind: z.literal('inline'), description: z.string().optional() }),\n]);\n\nconst refSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number(),\n geometryTypes: z.array(z.string()),\n source: sourceSchema,\n shown: z.boolean(),\n});\n\nconst detailSchema = refSchema.extend({\n features: z.unknown().describe(\"GeoJSON FeatureCollection of the entry's features.\"),\n});\n\nexport const recallByodOutputSchema = z.union([\n z.object({ entries: z.array(refSchema), entryMode: z.enum(['single', 'multiple']) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallByodSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.'),\n});\n\nexport const recallByodDescription =\n 'List or inspect BYOD (bring-your-own-data) GeoJSON layers in session state. These come from either ' +\n 'programmatic seeding by the embedding app OR from `addByodLayer` calls. Call this BEFORE passing ids ' +\n 'to `byodEntryIDs` on `analyseData` / `processData` / `updateByodDisplay` — never guess. ' +\n 'No args → index (id, label, featureCount, geometryTypes, source, shown); pass `id` → full FeatureCollection.';\n\nconst collectGeometryTypes = (data: { features: Array<{ geometry?: { type?: string } | null }> }): string[] => {\n const types = new Set<string>();\n for (const feature of data.features) {\n const t = feature.geometry?.type;\n if (t) types.add(t);\n }\n return [...types];\n};\n\nexport const executeRecallByod = async (\n params: z.infer<typeof recallByodSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallByodOutputSchema>> => {\n if (!params.id) {\n const entries = state.byod.entries.map((entry) => ({\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.data.features.length,\n geometryTypes: collectGeometryTypes(entry.data),\n source: entry.source,\n shown: !!entry._shown,\n }));\n return { entries, entryMode: state.byod.entryMode };\n }\n const entry = state.byod.findById(params.id);\n if (!entry) {\n return { error: `No BYOD entry with id \"${params.id}\". Call recallByod with no args to list available IDs.` };\n }\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.data.features.length,\n geometryTypes: collectGeometryTypes(entry.data),\n source: entry.source,\n shown: !!entry._shown,\n features: entry.data,\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: \"customGeometries\", 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 `processData`. ALWAYS call this before passing ids to `processData` or `analyseData` ' +\n '(`geometriesEntryIDs`); 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: 'customGeometries', 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\ntype Detail = z.infer<typeof detailSchema>;\n\nconst detailForPlaces = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\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 const analyses = summariseAnalyses(entry._analysis);\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 ...(analyses && { analyses }),\n };\n};\n\nconst detailForRanges = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\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((range) => range.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\nconst detailForCustomGeometries = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n const entry = state.customGeometries.findById(id.id);\n if (!entry) return { error: `No custom-geometries entry with id \"${id.id}\"` };\n const analyses = summariseAnalyses(entry._analysis);\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 ...(analyses && { analyses }),\n };\n};\n\nconst detailFor = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n switch (id.kind) {\n case 'places':\n return detailForPlaces(id, state);\n case 'ranges':\n return detailForRanges(id, state);\n case 'customGeometries':\n return detailForCustomGeometries(id, state);\n default:\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};\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 { FeatureFlags, ToolEntry, ToolEntryBuilder, ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport { buildPlacesOutputSchema, 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 shown: z.boolean().describe('Whether this entry is currently rendered on the map.'),\n markerType: z\n .enum(['pin', 'base-map', 'pin-clustered'])\n .optional()\n .describe('Which marker theme is rendering this entry (undefined when `shown` is false).'),\n analyses: z\n .array(analysisIndexSchema)\n .optional()\n .describe('Analyses already attached to this entry via analyseData (name + output format).'),\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\n/** Build the flag-aware output schema for recall-places. */\nexport const buildRecallPlacesOutputSchema = (flags: FeatureFlags) => {\n const detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n places: buildPlacesOutputSchema(flags),\n });\n return z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n ]);\n};\n\n/** Default-flag (`experimentalSearch: false`) output schema. */\nexport const recallPlacesOutputSchema = buildRecallPlacesOutputSchema({});\n\nexport const recallPlacesDescription =\n 'List / inspect places entries already in session state (from discoverPlaces / locatePlace / processData) ' +\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\n/**\n * Build the recall-places executor for a given {@link FeatureFlags} bag.\n * Controls whether the place summaries include the experimental `areaId` /\n * `areaCountry` / `areaTags` fields.\n */\nexport const buildExecuteRecallPlaces =\n (flags: FeatureFlags) =>\n async (\n params: z.infer<typeof recallPlacesSchema>,\n state: ToolState,\n ): Promise<z.infer<ReturnType<typeof buildRecallPlacesOutputSchema>>> => {\n const { id } = params;\n const allEntries = state.places.entries;\n const entryMode = state.places.entryMode;\n const shownEntryIds = state.places.shownEntryIds;\n\n if (!id) {\n const entries = [...allEntries]\n .sort((a, b) => b.timestamp - a.timestamp)\n .map(({ id, label, timestamp, places, _analysis }) => {\n const shown = shownEntryIds.has(id);\n const markerType = shown ? state.places.getShownMarkerType(id) : undefined;\n return {\n id,\n label,\n timestamp,\n featureCount: places.length,\n shown,\n ...(markerType && { markerType }),\n ...(_analysis?.length && {\n analyses: _analysis.map(({ name, outputFormat, timestamp, description }) => ({\n name,\n outputFormat,\n timestamp,\n ...(description && { description }),\n })),\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, flags);\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n places,\n entryMode,\n };\n };\n\n/** Default-flag (`experimentalSearch: false`) executor. */\nexport const executeRecallPlaces = buildExecuteRecallPlaces({});\n\n/**\n * Build a complete {@link ToolEntry} for `recallPlaces` for the given\n * {@link FeatureFlags}.\n */\nexport const buildRecallPlacesEntry = <S extends ToolState = ToolState>(\n flags: FeatureFlags,\n metadata: Omit<ToolEntry<S>, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>,\n): ToolEntry<S> => ({\n ...metadata,\n description: recallPlacesDescription,\n inputSchema: recallPlacesSchema,\n outputSchema: buildRecallPlacesOutputSchema(flags),\n execute: buildExecuteRecallPlaces(flags) as ToolEntry<S>['execute'],\n});\n\nconst recallPlacesMetadata = {\n classificationPrompt:\n 'List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processData, analyseData, updatePlacesDisplay).',\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 processData 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} satisfies Omit<ToolEntry, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>;\n\n/**\n * Builder for the recallPlaces default tool entry.\n */\nexport const recallPlacesBuilder: ToolEntryBuilder = (options) =>\n buildRecallPlacesEntry(options.featureFlags ?? {}, recallPlacesMetadata);\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';\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 shown: z.boolean().describe('Whether this entry is currently rendered on the map.'),\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 const shownEntryIds = state.routing.shownEntryIds;\n\n if (!id) {\n const entries = [...state.routing.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp, shown: shownEntryIds.has(id) }));\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// Slices wiped by `executeResetState`. Kept as a single source of truth so the\n// output schema's enum and the returned `cleared` array can't drift apart.\nconst CLEARED_SLICES = [\n 'places',\n 'routes',\n 'ranges',\n 'customGeometries',\n 'trafficAreaAnalytics',\n 'trafficIncidents',\n 'byod',\n 'mapPOIs',\n 'trafficFlow',\n 'mapStyle',\n] as const;\n\n/** Output schema for reset-state. */\nexport const resetStateOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z.array(z.enum(CLEARED_SLICES)).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 for (const entry of state.trafficAreaAnalytics.entries) {\n await entry._module?.clear();\n }\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. Every entry-owning slice with a `reset()`\n // needs to be wiped — missing one leaves stale entries that show up\n // in subsequent `recallState` / `recall*` calls even after a reset.\n state.places.reset();\n state.routing.reset();\n state.ranges.reset();\n state.customGeometries.reset();\n state.trafficAreaAnalytics.reset();\n state.trafficIncidents.reset();\n state.byod.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: [...CLEARED_SLICES],\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 { ENTRY_MODE_SLICE_NAMES, type EntryModeSlice, type EntryModeSliceName } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// `z.enum` needs a non-empty tuple. ENTRY_MODE_SLICE_NAMES is derived from a\n// `Record<EntryModeSliceName, true>` check at the type level, so the runtime\n// list is always non-empty whenever the union has at least one member.\nconst sliceSchema = z.enum(ENTRY_MODE_SLICE_NAMES as unknown as [EntryModeSliceName, ...EntryModeSliceName[]]);\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(\n 'Which entry-owning slice to reconfigure. Names match the keys on the agent state exactly — ' +\n '`routing` (not `routes`), `customGeometries` (not `geometries`), and so on.',\n ),\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 entry-owning slice between `multiple` and `single`. ' +\n `Supported slices: ${ENTRY_MODE_SLICE_NAMES.join(', ')}. ` +\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 // Index into state with the slice name — names match keys exactly, so\n // there is no `case 'routes': state.routing.…` style mismatch to maintain.\n const target = state[slice] as EntryModeSlice;\n\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 = target.entries.map((e) => e.id);\n\n await target.setEntryMode(mode);\n\n const afterIds = new Set(target.entries.map((e) => e.id));\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 { CLEARABLE_SLICE_NAMES, type ClearableMapSlice, type ClearableSliceName } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// Zod's z.enum needs a non-empty literal tuple; CLEARABLE_SLICE_NAMES is typed as readonly so\n// the cast just narrows it to the tuple-of-literals shape Zod expects. Order is irrelevant.\nconst layerEnum = z.enum(CLEARABLE_SLICE_NAMES as unknown as readonly [ClearableSliceName, ...ClearableSliceName[]]);\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(layerEnum),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for clear-map. */\nexport const clearMapSchema = z.object({\n layers: z\n .array(layerEnum)\n .optional()\n .describe(\n 'Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: ' +\n CLEARABLE_SLICE_NAMES.map((n) => `\\`${n}\\``).join(', ') +\n '. Map-only — slice histories (`entries`) are kept.',\n ),\n});\n\nexport const clearMapDescription =\n 'Hide everything a chosen set of state slices is rendering on the map (places, routing, ranges, ' +\n 'customGeometries, byod, trafficAreaAnalytics, trafficIncidents). Map-only — slice `entries` history ' +\n 'is untouched. Use to clean up before showing new results.';\n\n/**\n * Execute clear map.\n */\nexport const executeClearMap = async (params: z.infer<typeof clearMapSchema>, state: ToolState) => {\n const { layers } = params;\n const targets = layers && layers.length > 0 ? layers : CLEARABLE_SLICE_NAMES;\n try {\n for (const name of targets) {\n // Cast widens the slice instance to the ClearableMapSlice interface — the\n // CLEARABLE_SLICE_NAMES list is `satisfies`-guarded so every name reaches a\n // slice that actually implements `clearShown`.\n await (state[name] as unknown as ClearableMapSlice).clearShown();\n }\n return {\n success: true as const,\n cleared: [...targets] as ClearableSliceName[],\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 (obtain ids from `recallRoutes({ id })` detail mode or via `analyseData` over routes). 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-tile-incidents tool. */\nexport const getShownTileIncidentsOutputSchema = 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/** Tool schema for get-shown-tile-incidents. */\nexport const getShownTileIncidentsSchema = z.object({});\n\nexport const getShownTileIncidentsDescription =\n 'List the traffic incidents currently rendered by the TomTom traffic-tile overlay (the rasterised live tiles, ' +\n 'not the GeoJSON `getTrafficIncidents` service). Reads `state.trafficTiles.getTrafficIncidentsModule().getShown()` — ' +\n 'so it only reflects what the tile layer is showing in the current viewport. ' +\n 'For full TrafficIncident objects (with start/end times, road numbers, lanes, …) call `getTrafficIncidents` instead; ' +\n 'for \"incidents along this route\" read `route.properties.sections.traffic[]` via `analyseData`.';\n\n/** Execute get-shown-tile-incidents. */\nexport const executeGetShownTileIncidents = async (\n _params: z.infer<typeof getShownTileIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownTileIncidentsOutputSchema>> => {\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 rendered by the traffic-tile overlay.' };\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 read traffic-tile overlay incidents: ${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 { 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-tiles-base-map-layer-groups tool. */\nexport const toggleTilesBaseMapLayerGroupsOutputSchema = 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/** Tool schema for toggle-tiles-base-map-layer-groups. */\nexport const toggleTilesBaseMapLayerGroupsSchema = z.object({\n visible: z.boolean(),\n layerGroups: z.array(z.enum(baseMapLayerGroupNames)).describe('Vector-tile base-map layer groups to show or hide.'),\n});\n\nexport const toggleTilesBaseMapLayerGroupsDescription =\n 'Show / hide groups of vector-tile base-map style layers (water, buildings3D, roadLines, placeLabels, capitalLabels, …). ' +\n 'Operates on the underlying MapLibre style — orthogonal to anything rendered through PlacesModule / RoutingModule / ' +\n 'CustomGeoJSONModule (those layers are unaffected).';\n\n/** Execute toggle-tiles-base-map-layer-groups. */\nexport const executeToggleTilesBaseMapLayerGroups = async (\n params: z.infer<typeof toggleTilesBaseMapLayerGroupsSchema>,\n state: ToolState,\n) => {\n const { visible, layerGroups } = params;\n try {\n const baseMapModule = await state.baseMap.getBaseMapModule();\n baseMapModule.setVisible(visible, {\n layerGroups: {\n mode: 'include',\n names: layerGroups,\n },\n });\n return { success: true, visible, layerGroups };\n } catch (error) {\n return {\n error: `Failed to toggle tile base-map 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-tiles-pois tool. */\nexport const toggleTilesPOIsOutputSchema = 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/** Tool schema for toggle-tiles-pois. */\nexport const toggleTilesPOIsSchema = z.object({\n reset: z\n .boolean()\n .optional()\n .describe('Reset all tile-POI configuration to defaults. Other params ignored when true.'),\n allMapPOIsVisible: z\n .boolean()\n .optional()\n .describe('Overall visibility of the vector-tile POI icons baked into the base map style.'),\n filterCategories: z\n .object({\n show: z\n .enum(['only', 'all_except'])\n .describe('\"only\": keep only these categories. \"all_except\": hide these categories.'),\n values: z\n .array(z.string())\n .describe(\n 'MapStylePOICategory / POICategoryGroup values (e.g. \"RESTAURANT\", \"FOOD_DRINKS_GROUP\"). ' +\n 'Fine-grained codes like \"ITALIAN_RESTAURANT\" not supported here.',\n ),\n })\n .optional()\n .describe('Restrict which built-in vector-tile POI categories are shown.'),\n});\n\nexport const toggleTilesPOIsDescription =\n 'Show/hide the built-in vector-tile POI icons baked into the base map style (restaurants, hotels, gas stations, …). ' +\n 'NOT the same as places shown via `discoverPlaces` / `updatePlacesDisplay` — those are session-state pins on the ' +\n 'PlacesModule, untouched by this tool. `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts ' +\n 'which categories show; `reset: true` restores defaults.';\n\n/** Execute toggle-tiles-pois. */\nexport const executeToggleTilesPOIs = async (\n params: z.infer<typeof toggleTilesPOIsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof toggleTilesPOIsOutputSchema>> => {\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 tile 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/** Tool schema for toggle-tiles-traffic-flow. */\nexport const toggleTilesTrafficFlowSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-tiles-traffic-flow tool. */\nexport const toggleTilesTrafficFlowOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficFlowVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for toggle-tiles-traffic-incidents. */\nexport const toggleTilesTrafficIncidentsSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-tiles-traffic-incidents tool. */\nexport const toggleTilesTrafficIncidentsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficIncidentsVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const toggleTilesTrafficFlowDescription =\n 'Toggle the vector-tile traffic-flow overlay (live coloured road segments). ' +\n 'NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical analytics) or `route.properties.sections.traffic[]` ' +\n '(per-route incidents). Just a base-map style layer on/off.';\n\nexport const toggleTilesTrafficIncidentsDescription =\n 'Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). ' +\n 'NOT the same as `getTrafficIncidents` (GeoJSON service → structured entries) or `analyseData` ' +\n '(sandbox over fetched entries). Just a base-map style layer on/off. ' +\n 'Use `getShownTileIncidents` to read what this overlay is currently rendering.';\n\n/** Execute toggle-tiles-traffic-flow. */\nexport const executeToggleTilesTrafficFlow = async (\n params: z.infer<typeof toggleTilesTrafficFlowSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n const trafficFlowModule = await state.trafficTiles.getTrafficFlowModule();\n trafficFlowModule.setVisible(visible);\n return { success: true, trafficFlowVisible: visible };\n } catch (error) {\n return {\n error: `Failed to toggle tile traffic flow: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n/** Execute toggle-tiles-traffic-incidents. */\nexport const executeToggleTilesTrafficIncidents = async (\n params: z.infer<typeof toggleTilesTrafficIncidentsSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n const trafficIncidentsModule = await state.trafficTiles.getTrafficIncidentsModule();\n trafficIncidentsModule.setVisible(visible);\n return { success: true, trafficIncidentsVisible: visible };\n } catch (error) {\n return {\n error: `Failed to toggle tile 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';\n\nconst actionSchema = z\n .enum(['show', 'hide', 'remove'])\n .describe(\n '`show` renders the listed entries (lazy-init their CustomGeoJSONModule); ' +\n '`hide` removes them from the map but keeps them in session state; ' +\n '`remove` hides AND drops them from history.',\n );\n\nexport const updateByodDisplaySchema = z\n .object({\n entryIds: z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n 'BYOD entry ids to act on (e.g. [\"byod-0\"]). Use `recallByod` to list. ' +\n 'Omit when `action: \"hide\"` and `clearAll: true` to hide every shown BYOD entry in one call.',\n ),\n action: actionSchema,\n clearAll: z\n .boolean()\n .optional()\n .describe(\n 'Only valid with `action: \"hide\"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.',\n ),\n hideOthers: z\n .boolean()\n .optional()\n .describe(\n 'Only valid with `action: \"show\"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).',\n ),\n })\n .refine(\n (v) => {\n if (v.action === 'hide' && v.clearAll) return true;\n return !!v.entryIds?.length;\n },\n { message: '`entryIds` is required unless `action: \"hide\"` with `clearAll: true`.' },\n )\n .refine((v) => !v.clearAll || v.action === 'hide', {\n message: '`clearAll: true` is only valid with `action: \"hide\"`.',\n })\n .refine((v) => !v.hideOthers || v.action === 'show', {\n message: '`hideOthers: true` is only valid with `action: \"show\"`.',\n });\n\nexport const updateByodDisplayOutputSchema = z.union([\n z.object({\n action: actionSchema,\n affectedIds: z.array(z.string()).describe('Entry ids that were actually shown / hidden / removed.'),\n shown: z.array(z.string()).describe('BYOD entry ids currently rendered on the map after this call.'),\n }),\n toolErrorSchema,\n]);\n\nexport const updateByodDisplayDescription =\n 'Toggle visibility (or drop) for one or more BYOD entries. Use after `addByodLayer` or after seeing entries ' +\n 'via `recallByod`. Three actions: `show` renders listed entries, `hide` removes them from the map but keeps ' +\n 'them in state, `remove` hides AND drops them from history. Pair `action: \"hide\"` with `clearAll: true` to ' +\n 'hide every shown BYOD entry in one call. Pair `action: \"show\"` with `hideOthers: true` to swap to a single ' +\n \"set of entries (replaces what's already on the map).\";\n\ntype BYODSlice = ToolState['byod'];\n\n// Reject the call up-front if any requested id is unknown — leaves the slice in its original\n// state rather than partially applying. Returns `undefined` when every id is valid.\nconst validateRequestedIds = (slice: BYODSlice, requested: readonly string[]): { error: string } | undefined => {\n for (const id of requested) {\n if (!slice.findById(id)) {\n return { error: `No BYOD entry with id \"${id}\". Call recallByod to list available IDs.` };\n }\n }\n return undefined;\n};\n\nconst showEntries = async (slice: BYODSlice, requested: readonly string[], hideOthers: boolean): Promise<string[]> => {\n const affected: string[] = [];\n if (hideOthers) {\n const requestedSet = new Set(requested);\n for (const id of slice.shownEntryIds) {\n if (requestedSet.has(id)) continue;\n await slice.hideEntry(id);\n affected.push(id);\n }\n }\n for (const id of requested) {\n await slice.showEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nconst hideEntries = async (slice: BYODSlice, requested: readonly string[], clearAll: boolean): Promise<string[]> => {\n const affected: string[] = [];\n const ids = clearAll ? slice.shownEntryIds : requested;\n for (const id of ids) {\n await slice.hideEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nconst removeEntries = async (slice: BYODSlice, requested: readonly string[]): Promise<string[]> => {\n const affected: string[] = [];\n for (const id of requested) {\n await slice.removeEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nexport const executeUpdateByodDisplay = async (\n params: z.infer<typeof updateByodDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateByodDisplayOutputSchema>> => {\n const slice = state.byod;\n const { action, clearAll, hideOthers } = params;\n const requested = params.entryIds ?? [];\n\n const validationProblem = validateRequestedIds(slice, requested);\n if (validationProblem) return validationProblem;\n\n let affected: string[];\n if (action === 'show') {\n affected = await showEntries(slice, requested, !!hideOthers);\n } else if (action === 'hide') {\n affected = await hideEntries(slice, requested, !!clearAll);\n } else {\n affected = await removeEntries(slice, requested);\n }\n\n return {\n action,\n affectedIds: affected,\n shown: [...slice.shownEntryIds],\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 mainColor: z\n .string()\n .optional()\n .describe('Sticky main colour now applied to every shown route, when set by this or a previous call.'),\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 mainColor: z\n .string()\n .optional()\n .describe(\n 'Recolor the route line + waypoint icons. CSS named colour (\"coral\", \"steelblue\") or hex (\"#FF0000\"). ' +\n 'Sticky — applied to every currently-shown entry AND remembered so subsequent shows pick it up automatically. ' +\n 'Can be set by itself (no add/remove/clear) to recolor without changing visibility.',\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 data.mainColor !== undefined,\n {\n message:\n 'updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, mainColor.',\n },\n );\n\nexport const updateRoutesDisplayDescription =\n 'Show, hide, swap, and recolor 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; with no ' +\n '`add`/`addMatching` it implicitly targets the most recently stored entry. ' +\n '`mainColor` recolors the route line + waypoint icons (sticky across entry swaps — applied to current shows AND remembered); ' +\n 'it does NOT auto-show a hidden entry, so set `add`/`addMatching` too if nothing is on the map yet. ' +\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 // `selectedIndex` implicitly targets \"the most recently stored entry\" when no\n // `add` / `addMatching` is supplied. `mainColor` is handled separately by the executor\n // (sticky, applies to whatever is shown — no implicit-latest add needed).\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\n// Apply the requested add / remove / clear / selectedIndex sequence and return the\n// before/after shown sets so the response layer can compute deltas. Sequential awaits — concurrent\n// show/hide on the routing slice races MapLibre's style mutation.\nconst applyAddRemove = async (\n state: ToolState,\n plan: { add: readonly string[]; remove: readonly string[]; clear: boolean; selectedIndex: number | undefined },\n): Promise<{ beforeShown: Set<string>; afterShown: ReadonlySet<string> }> => {\n const beforeShown = new Set(state.routing.shownEntryIds);\n if (plan.clear) {\n for (const id of state.routing.shownEntryIds) {\n await state.routing.hideEntry(id);\n }\n }\n for (const id of plan.remove) {\n await state.routing.hideEntry(id);\n }\n for (const id of plan.add) {\n await state.routing.showEntry(id);\n }\n if (plan.selectedIndex !== undefined) {\n await applySelectedIndex(state, plan.add, plan.selectedIndex);\n }\n return { beforeShown, afterShown: state.routing.shownEntryIds };\n};\n\n// Fit the camera around every currently-shown route. No-op when nothing is shown or the joined\n// feature collection has no computable bbox.\nconst fitCameraToShown = (state: ToolState, shownIds: ReadonlySet<string>): void => {\n const features: Routes['features'] = [];\n for (const entry of state.routing.entries) {\n if (shownIds.has(entry.id)) features.push(...entry.data.features);\n }\n if (features.length === 0) return;\n const bbox = bboxFromGeoJSON({ type: 'FeatureCollection', features });\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50 });\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, mainColor } = 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 // Persist mainColor BEFORE add/remove so newly-shown entries pick it up on first paint,\n // and apply to whatever is already on the map. Sticky for the lifetime of the slice.\n if (mainColor !== undefined) {\n state.routing.setMainColor(mainColor);\n }\n\n const { beforeShown, afterShown } = await applyAddRemove(state, {\n add: resolvedAdd,\n remove: resolvedRemove,\n clear: !!clear,\n selectedIndex,\n });\n\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 if (fitBounds ?? resolvedAdd.length > 0) {\n fitCameraToShown(state, afterShown);\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 const currentMainColor = state.routing.mainColor;\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n ...(currentMainColor !== undefined && { mainColor: currentMainColor }),\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 { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { TrafficAreaAnalyticsEntry } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst renderKnobs = {\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 on the targeted entries.'),\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' (default), 'predefinedRange' (normalize to SDK range), 'currentRange' (normalize to live data range).\",\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} as const;\n\nconst summarySchema = z.object({\n id: z.string(),\n label: z.string(),\n activeMetric: z.string().optional(),\n displayMode: z.string().optional(),\n});\n\n/** Tool schema for update-traffic-area-analytics-display. */\nexport const updateTrafficAreaAnalyticsDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe('Entry ids to render (e.g. [\"tta-0\"]). Use `recallState` to list available ids.'),\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 fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added.',\n ),\n ...renderKnobs,\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.mode !== undefined ||\n data.metric !== undefined ||\n data.colorTheme !== undefined ||\n data.heightScale !== undefined ||\n data.scaleMode !== undefined ||\n data.filter !== undefined,\n {\n message:\n 'updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, mode, metric, colorTheme, heightScale, scaleMode, filter.',\n },\n );\n\n/** Output schema for the update-traffic-area-analytics-display tool. */\nexport const updateTrafficAreaAnalyticsDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z.array(summarySchema).describe('Traffic-area-analytics entries currently on the map.'),\n actuallyAdded: z.array(z.string()),\n actuallyRemoved: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\nexport const updateTrafficAreaAnalyticsDisplayDescription =\n 'Show, hide, swap, and restyle HISTORICAL traffic-area-analytics entries 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. ' +\n 'Render knobs (`mode`, `metric`, `colorTheme`, `heightScale`, `scaleMode`, `filter`) apply to every entry being added AND every entry that stays shown after this call. ' +\n 'When only render knobs are set with no add/remove/clear, implicitly targets the most recently added entry. ' +\n 'Not real-time — use `toggleTilesTrafficFlow` for the live vector-tile flow overlay.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\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.trafficAreaAnalytics.entries) {\n if (needles.some((needle) => entry.label.toLowerCase().includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\nconst resolveSelectors = (\n state: ToolState,\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, clear } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n // Implicit-latest: render knobs but no entry selectors → target the most recent entry.\n const onlyRenderKnobs =\n add === undefined &&\n addMatching === undefined &&\n remove === undefined &&\n removeMatching === undefined &&\n clear !== true;\n const hasRenderKnobs =\n params.mode !== undefined ||\n params.metric !== undefined ||\n params.colorTheme !== undefined ||\n params.heightScale !== undefined ||\n params.scaleMode !== undefined ||\n params.filter !== undefined;\n if (onlyRenderKnobs && hasRenderKnobs) {\n const latestId = state.trafficAreaAnalytics.entries.at(-1)?.id;\n if (!latestId) {\n return { error: 'No traffic-area-analytics entries available. Call getTrafficAreaAnalytics first.' };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.trafficAreaAnalytics.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown traffic-area-analytics entry ids: ${unknownAdd.join(', ')}. Use recallState to list available ids.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\nconst applyRenderKnobs = async (\n state: ToolState,\n entryId: string,\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n): Promise<void> => {\n const entry = state.trafficAreaAnalytics.findById(entryId);\n if (!entry) return;\n const defaultMetric = entry.data.properties?.metrics?.[0] ?? 'congestionLevel';\n const mode = params.mode ?? 'hexgrid-3d';\n const metric = params.metric ?? defaultMetric;\n const colorTheme = params.colorTheme ?? 'trafficLight';\n\n const module = await state.trafficAreaAnalytics.getEntryModule(entryId);\n module.setMode(mode);\n module.setMetric(metric);\n module.setColor(colorTheme);\n if (params.heightScale !== undefined || params.scaleMode !== undefined) {\n module.setHeight({\n ...(params.heightScale !== undefined && { maxHeightMeters: params.heightScale }),\n ...(params.scaleMode !== undefined && { scaleMode: params.scaleMode }),\n });\n }\n if (params.filter) {\n module.filter({ min: params.filter.min, max: params.filter.max });\n } else {\n module.clearFilter();\n }\n};\n\nconst summarizeEntry = (entry: TrafficAreaAnalyticsEntry): z.infer<typeof summarySchema> => ({\n id: entry.id,\n label: entry.label,\n ...(entry._config?.activeMetric && { activeMetric: entry._config.activeMetric }),\n ...(entry._config?.displayMode && { displayMode: entry._config.displayMode }),\n});\n\n// ---------------------------------------------------------------------------\n// Executor\n// ---------------------------------------------------------------------------\n\nexport const executeUpdateTrafficAreaAnalyticsDisplay = async (\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateTrafficAreaAnalyticsDisplayOutputSchema>> => {\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.trafficAreaAnalytics.shownEntryIds);\n\n if (params.clear) {\n for (const id of state.trafficAreaAnalytics.shownEntryIds) {\n await state.trafficAreaAnalytics.hideEntry(id);\n }\n }\n for (const id of resolvedRemove) {\n await state.trafficAreaAnalytics.hideEntry(id);\n }\n for (const id of resolvedAdd) {\n await state.trafficAreaAnalytics.showEntry(id);\n }\n\n const afterShown = state.trafficAreaAnalytics.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 // Render knobs apply to: every entry just added AND every entry currently shown after the call\n // (so existing entries pick up new mode/metric/colorTheme/filter).\n const targets = new Set<string>([...actuallyAdded, ...afterShown]);\n for (const id of targets) await applyRenderKnobs(state, id, params);\n\n const shouldFit = params.fitBounds ?? actuallyAdded.length > 0;\n if (shouldFit && afterShown.size > 0) {\n const features = state.trafficAreaAnalytics.entries\n .filter((e) => afterShown.has(e.id))\n .flatMap((e) => e.data.features);\n const bbox = bboxFromGeoJSON({ type: 'FeatureCollection', features });\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\n }\n\n const shown = state.trafficAreaAnalytics.entries.filter((e) => afterShown.has(e.id)).map(summarizeEntry);\n\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown,\n actuallyAdded,\n actuallyRemoved,\n };\n } catch (error) {\n return {\n error: `Failed to update traffic-area-analytics display: ${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 update-waypoints-display tool. */\nexport const updateWaypointsDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n visible: z.boolean(),\n count: z.number(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for update-waypoints-display. */\nexport const updateWaypointsDisplaySchema = z.object({\n visible: z\n .boolean()\n .optional()\n .describe(\n 'Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.',\n ),\n});\n\nexport const updateWaypointsDisplayDescription =\n 'Show or hide the staged origin/stop/destination markers held in `state.routing.planningSlots` (the slots being ' +\n 'built up for the next `setRoute` — NOT the waypoints of a calculated route entry). ' +\n 'No route line is drawn; for that use `updateRoutesDisplay`. Requires at least one routes entry to host the module.';\n\n/** Execute update-waypoints-display. */\nexport const executeUpdateWaypointsDisplay = async (\n params: z.infer<typeof updateWaypointsDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateWaypointsDisplayOutputSchema>> => {\n const visible = params.visible ?? true;\n try {\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\n if (!visible) {\n await routingModule.clearWaypoints();\n return { success: true, visible: false, count: 0 };\n }\n\n const current = state.routing.planningSlots;\n await routingModule.showWaypoints(current);\n return { success: true, visible: true, count: current.length };\n } catch (error) {\n return {\n error: `Failed to update waypoints display: ${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 addByodLayerDescription,\n addByodLayerOutputSchema,\n addByodLayerSchema,\n analyseDataBuilder,\n executeAddByodLayer,\n executeFocusIncidents,\n executeGetCurrentWaypoints,\n executeQueryTrafficAnalytics,\n executeRecallByod,\n executeRecallGeometries,\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 processDataBuilder,\n queryTrafficAnalyticsDescription,\n queryTrafficAnalyticsOutputSchema,\n queryTrafficAnalyticsSchema,\n recallByodDescription,\n recallByodOutputSchema,\n recallByodSchema,\n recallGeometriesDescription,\n recallGeometriesOutputSchema,\n recallGeometriesSchema,\n recallPlacesBuilder,\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 executeGetShownTileIncidents,\n executeGetStandardMapStyles,\n executeSetLanguage,\n executeSetMapStandardStyle,\n executeToggleTilesBaseMapLayerGroups,\n executeToggleTilesPOIs,\n executeToggleTilesTrafficFlow,\n executeToggleTilesTrafficIncidents,\n executeUpdateByodDisplay,\n executeUpdatePlacesDisplay,\n executeUpdateRoutesDisplay,\n executeUpdateTrafficAreaAnalyticsDisplay,\n executeUpdateWaypointsDisplay,\n getShownTileIncidentsDescription,\n getShownTileIncidentsOutputSchema,\n getShownTileIncidentsSchema,\n getStandardMapStylesDescription,\n getStandardMapStylesOutputSchema,\n getStandardMapStylesSchema,\n setLanguageDescription,\n setLanguageOutputSchema,\n setLanguageSchema,\n setMapStandardStyleDescription,\n setMapStandardStyleOutputSchema,\n setMapStandardStyleSchema,\n toggleTilesBaseMapLayerGroupsDescription,\n toggleTilesBaseMapLayerGroupsOutputSchema,\n toggleTilesBaseMapLayerGroupsSchema,\n toggleTilesPOIsDescription,\n toggleTilesPOIsOutputSchema,\n toggleTilesPOIsSchema,\n toggleTilesTrafficFlowDescription,\n toggleTilesTrafficFlowOutputSchema,\n toggleTilesTrafficFlowSchema,\n toggleTilesTrafficIncidentsDescription,\n toggleTilesTrafficIncidentsOutputSchema,\n toggleTilesTrafficIncidentsSchema,\n updateByodDisplayDescription,\n updateByodDisplayOutputSchema,\n updateByodDisplaySchema,\n updatePlacesDisplayDescription,\n updatePlacesDisplayOutputSchema,\n updatePlacesDisplaySchema,\n updateRoutesDisplayDescription,\n updateRoutesDisplayOutputSchema,\n updateRoutesDisplaySchema,\n updateTrafficAreaAnalyticsDisplayDescription,\n updateTrafficAreaAnalyticsDisplayOutputSchema,\n updateTrafficAreaAnalyticsDisplaySchema,\n updateWaypointsDisplayDescription,\n updateWaypointsDisplayOutputSchema,\n updateWaypointsDisplaySchema,\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\n// Shared classifier-prompt framing for the two dynamic-JS data tools (`analyseData`,\n// `processData`). Both run user-authored code against the same set of injected inputs\n// (`places` / `routes` / `incidents` / `geometries` / `trafficAreaAnalytics`); only the *contract*\n// of what the code returns differs. Lifting the common framing here keeps the two\n// `classificationPrompt`s short — the per-tool prompt then only carries the verb (transforms vs\n// aggregates) plus disambiguating triggers.\nconst DATA_TOOL_INPUTS = 'places / routes / incidents / geometries / trafficAreaAnalytics / byod';\nconst DATA_TOOL_SHARED_FRAMING =\n `Dynamic-JS sandbox over any combination of ${DATA_TOOL_INPUTS} entries. ` +\n 'Each input arg is `undefined` when its `*EntryIDs` was omitted.';\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 processData: processDataBuilder({\n classificationPrompt:\n `${DATA_TOOL_SHARED_FRAMING} ` +\n 'WRITES NEW renderable map entries — returns `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }` ' +\n 'to write a places, custom-geometries, or BYOD entry, move the camera, or any combination. ' +\n 'NOT for digests/charts/counts — those are `analyseData` (metadata only, never renders). ' +\n 'Use for: filter/merge places (set ops), connect places (hub-spoke), compute polygons ' +\n '(union/buffer/h3-coverage/difference), focus camera on route sections via `routeUtils.getSectionBBox`, ' +\n 'cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile).',\n tags: ['turf', 'h3', 'geometry', 'place', 'route', 'aggregation', 'connections'],\n examples: [\n // places-only: filter\n 'processData({ placesEntryIDs: [\"places-2\"], code: \"return { places: { type: \\'FeatureCollection\\', features: places.features.filter(p => (p.properties.poi?.name ?? \\'\\').toLowerCase().includes(\\'vegan\\')) } };\", label: \"vegan only\", show: { places: { markerType: \"pin\", zoomMode: \"auto\", hidePreviousEntries: \"all\" } } })',\n // places-only: hub-spoke connections\n 'processData({ placesEntryIDs: [\"places-1\"], code: \"const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };\" })',\n // places + geometries (attached polygons): h3 hex coverage of result places\n 'processData({ placesEntryIDs: [\"places-1\"], code: \"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 })) };\", show: { places: { markerType: \"pin\", zoomMode: \"auto\" }, placesGeometries: { theme: \"outline\" } } })',\n // routes-only: fit to worst traffic section\n 'processData({ routesEntryIDs: [\"routes-0\"], 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 // routes-only: fit to first toll section\n 'processData({ routesEntryIDs: [\"routes-0\"], 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 // routes + places: corridor filter into new places entry\n 'processData({ placesEntryIDs: [\"ev-stations\"], routesEntryIDs: [\"routes-0\"], code: \"const r = routes.features[0]; const buf = turf.buffer(r, 0.5, { units: \\'kilometers\\' }); const near = places.features.filter(p => turf.booleanPointInPolygon(p, buf)); return { places: { type: \\'FeatureCollection\\', features: near }, fitOnMap: turf.bbox(turf.featureCollection([...near, r])) };\", label: \"ev near route\", entryId: \"ev-near-routes-0\", show: { places: { markerType: \"pin\", zoomMode: \"none\", hidePreviousEntries: \"all\" } } })',\n // geometries-only: union → new custom-geometries entry\n 'processData({ geometriesEntryIDs: [{ kind: \"place\", id: \"amsterdam-id\" }, { kind: \"place\", id: \"rotterdam-id\" }], code: \"return { geometries: [turf.union(turf.featureCollection(geometries))].filter(Boolean) };\", label: \"ams ∪ rdam\", operation: \"union\" })',\n // geometries-only: buffer each\n 'processData({ geometriesEntryIDs: [{ kind: \"place\", id: \"paris-id\" }], code: \"return { geometries: geometries.map(f => ({ ...turf.buffer(f, 5, { units: \\'kilometers\\' }), id: `${f.id}-buf` })) };\", label: \"paris +5km\", operation: \"buffer\" })',\n // geometries + places: inverse (areas in X NOT reachable from Y)\n 'processData({ 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 { geometries: inv ? [{ ...inv, id: \\'unreachable\\' }] : [] };\", label: \"amsterdam minus reachable\", operation: \"difference\", show: { customGeometries: { theme: \"inverted\", hidePreviousEntries: \"all\" } } })',\n // geometries (ranges) + places cross-ref: keep places inside the reachable area\n 'processData({ geometriesEntryIDs: [{ kind: \"ranges\", id: \"ranges-0\" }, { kind: \"places\", id: \"ev-stations\" }], code: \"const ranges = geometries.filter(g => g.properties._source.kind === \\'ranges\\'); const env = ranges.length ? turf.union(turf.featureCollection(ranges)) : null; const ev = (placesByEntry?.[\\'ev-stations\\']?.features ?? []); const kept = env ? ev.filter(p => turf.booleanPointInPolygon(p, env)) : ev; return { places: { type: \\'FeatureCollection\\', features: kept } };\", label: \"ev within reachable\" })',\n // trafficAreaAnalytics + places cross-ref: keep places inside high-congestion tiles\n 'processData({ placesEntryIDs: [\"ev-stations\"], trafficAreaAnalyticsEntryIDs: [\"tta-0\"], code: \"const hot = trafficAreaAnalytics.features.filter(t => (t.properties.congestionLevel ?? 0) >= 75); const kept = places.features.filter(p => hot.some(t => turf.booleanPointInPolygon(p, t))); return { places: { type: \\'FeatureCollection\\', features: kept } };\", label: \"ev in high-congestion tiles\" })',\n // trafficAreaAnalytics → derived geometry entry: hot-tiles polygon\n 'processData({ trafficAreaAnalyticsEntryIDs: [\"tta-0\"], code: \"const hot = trafficAreaAnalytics.features.filter(t => (t.properties.congestionLevel ?? 0) >= 75).map((t, i) => ({ ...t, id: `hot-${i}` })); const u = hot.length ? turf.union(turf.featureCollection(hot)) : null; return { geometries: u ? [{ ...u, id: \\'congestion-hot-zone\\' }] : [] };\", label: \"congestion hot zone\", operation: \"union\", show: { customGeometries: { theme: \"filled\" } } })',\n ],\n examplePrompts: [\n 'Keep only the vegan restaurants',\n 'Draw lines from this hub to every nearby cafe',\n 'Show the h3 hex coverage of these places',\n 'Zoom to the worst traffic on the route',\n 'Focus on the first toll section',\n 'Show EV stations within 500 m of this route',\n 'Union the boundaries of these cities',\n 'Buffer this neighbourhood by 500 m',\n 'Areas of Amsterdam not reachable from the hospitals',\n 'Places inside the reachable area',\n 'Filter EV stations to those inside high-congestion tiles',\n 'Outline the congestion hot zone as a polygon',\n ],\n relatedTools: ['analyseData', 'recallPlaces', 'recallRoutes', 'recallGeometries', 'recallState'],\n dependsOn: [\n 'discoverPlaces',\n 'locatePlace',\n 'setRoute',\n 'getTrafficIncidents',\n 'getTrafficAreaAnalytics',\n 'findReachableAreas',\n 'recallPlaces',\n 'recallRoutes',\n 'recallGeometries',\n ],\n }),\n analyseData: analyseDataBuilder({\n classificationPrompt:\n `${DATA_TOOL_SHARED_FRAMING} ` +\n 'WRITES metadata only — returns JSON or a Chart.js config (`outputFormat: \"chart\"`), attached as ' +\n '`_analysis[name]` on every contributing entry. Never renders or creates new map entries (that is ' +\n '`processData`). ' +\n 'Use for: counts / groupings / charts (by-category, hex density); route summaries (per-leg / per-country / ' +\n 'alternative compare); route TRAFFIC analysis from `route.properties.sections.traffic[]` — pick THIS for ' +\n '\"distribution / count / chart of <something> along the route\", NOT `getTrafficIncidents`; geometry stats ' +\n '(areas, h3 coverage); historical traffic-area-analytics breakdowns (congestion / speed / travel-time per ' +\n 'tile); cross-kind correlations (incidents-per-cluster, places-near-route, distance-to-route bins, ' +\n 'route-segments-by-tile-metric). ' +\n 'For recurring monitor-tick reruns with `previous` / `now` / `log` and optional `focusIds` side-effect, ' +\n 'set `monitor: { entryId }` (only `incidentsEntryIDs: [monitor.entryId]` allowed alongside).',\n tags: ['analysis', 'turf', 'h3', 'place', 'route', 'geometry'],\n examples: [\n // places-only: counts\n 'analyseData({ 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 // places-only: h3 density\n 'analyseData({ placesEntryIDs: [\"places-1\"], name: \"hex-density-8\", 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 // places-only: chart\n 'analyseData({ placesEntryIDs: [\"places-2\"], 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]) }] } };\" })',\n // routes-only: per-route summary\n 'analyseData({ routesEntryIDs: [\"routes-0\"], 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 // routes-only: alternative-comparison chart\n 'analyseData({ routesEntryIDs: [\"routes-0\"], 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 // routes-only: distribution of traffic incidents ALONG the route (reads route.properties.sections.traffic[])\n 'analyseData({ routesEntryIDs: [\"routes-0\"], name: \"on-route-incidents-by-category\", outputFormat: \"chart\", code: \"const sections = (routes.features[0].properties.sections.traffic ?? []); const counts = {}; for (const s of sections) for (const c of (s.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]); return { type: \\'bar\\', data: { labels: entries.map(e => e[0]), datasets: [{ label: \\'Incidents on route\\', data: entries.map(e => e[1]) }] }, options: { plugins: { title: { display: true, text: \\'Traffic incidents along the route\\' } } } };\" })',\n // routes-only: total delay per country section\n 'analyseData({ routesEntryIDs: [\"routes-0\"], name: \"delay-by-country\", code: \"const r = routes.features[0]; const byCountry = {}; 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 // routes + places cross-ref (count within distance)\n 'analyseData({ routesEntryIDs: [\"routes-0\"], placesEntryIDs: [\"ev-stations\"], name: \"ev-near-route-500m\", code: \"const route = routes.features[0]; const out = {}; for (const [id, fc] of Object.entries(placesByEntry)) out[id] = fc.features.filter(p => turf.pointToLineDistance(p, route, { units: \\'meters\\' }) <= 500).length; return { withinMeters: 500, byEntry: out };\" })',\n // routes + places cross-ref (distance-to-route histogram, binned in km)\n 'analyseData({ routesEntryIDs: [\"routes-0\"], placesEntryIDs: [\"cafes\"], name: \"cafe-distance-bins-km\", outputFormat: \"chart\", code: \"const route = routes.features[0]; const distances = places.features.map(p => turf.pointToLineDistance(p, route, { units: \\'kilometers\\' })); const max = Math.max(0, ...distances); const binWidth = 1; const binCount = Math.max(1, Math.ceil(max / binWidth)); const bins = new Array(binCount).fill(0); for (const d of distances) bins[Math.min(binCount - 1, Math.floor(d / binWidth))]++; const labels = bins.map((_, i) => `${i}-${i + binWidth} km`); return { type: \\'bar\\', data: { labels, datasets: [{ label: \\'cafes\\', data: bins }] }, options: { plugins: { title: { display: true, text: \\'Cafe distance to route\\' } } } };\" })',\n // geometries-only: areas\n 'analyseData({ geometriesEntryIDs: [{ kind: \"places\", id: \"cities\" }], name: \"areas\", code: \"return geometries.map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));\" })',\n // geometries-only: chart\n 'analyseData({ 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 // cross-kind: incidents per place cluster\n 'analyseData({ placesEntryIDs: [\"clusters\"], incidentsEntryIDs: [\"incidents-0\"], name: \"incidents-per-cluster\", code: \"const out = {}; for (const c of places.features) { const inCluster = incidents.filter(i => { const coord = i.geometry.type === \\'Point\\' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.distance(coord, c.geometry.coordinates, { units: \\'kilometers\\' }) <= 0.5; }); out[c.id] = inCluster.length; } return out;\" })',\n // cross-kind: incidents inside custom corridors\n 'analyseData({ geometriesEntryIDs: [{ kind: \"customGeometries\", id: \"buffered-routes\" }], incidentsEntryIDs: [\"incidents-0\"], name: \"incidents-in-corridors\", code: \"const out = []; for (const g of geometries) { const hits = incidents.filter(i => { const coord = i.geometry.type === \\'Point\\' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.booleanPointInPolygon(coord, g); }); out.push({ id: g.id, count: hits.length, totalDelaySec: hits.reduce((s, i) => s + (i.properties.delayInSeconds ?? 0), 0) }); } return out;\" })',\n // trafficAreaAnalytics-only: congestion distribution chart\n \"analyseData({ trafficAreaAnalyticsEntryIDs: [\\\"tta-0\\\"], name: \\\"congestion-distribution\\\", outputFormat: \\\"chart\\\", code: \\\"const bins = [0, 25, 50, 75, 100]; const counts = new Array(bins.length).fill(0); for (const t of trafficAreaAnalytics.features) { const c = t.properties.congestionLevel ?? 0; const idx = Math.min(bins.length - 1, Math.floor(c / 25)); counts[idx]++; } return { type: 'bar', data: { labels: ['0–24', '25–49', '50–74', '75+'], datasets: [{ label: 'Tiles', data: counts.slice(0, 4) }] } };\\\" })\",\n // cross-kind: places by congestion tile\n 'analyseData({ placesEntryIDs: [\"ev-stations\"], trafficAreaAnalyticsEntryIDs: [\"tta-0\"], name: \"ev-stations-by-congestion\", code: \"const buckets = { low: 0, mid: 0, high: 0 }; for (const p of places.features) { const tile = trafficAreaAnalytics.features.find(t => turf.booleanPointInPolygon(p, t)); if (!tile) continue; const c = tile.properties.congestionLevel ?? 0; if (c < 25) buckets.low++; else if (c < 75) buckets.mid++; else buckets.high++; } return buckets;\" })',\n // cross-kind: route segments by tile metric\n 'analyseData({ routesEntryIDs: [\"routes-0\"], trafficAreaAnalyticsEntryIDs: [\"tta-0\"], name: \"route-tile-coverage\", code: \"const r = routes.features[0]; const hits = trafficAreaAnalytics.features.filter(t => turf.lineIntersect(r, t).features.length > 0); return { tilesCrossed: hits.length, avgCongestion: hits.length ? +(hits.reduce((s, t) => s + (t.properties.congestionLevel ?? 0), 0) / hits.length).toFixed(1) : null };\" })',\n // incidents-only: counts by category (one-shot)\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], 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 // incidents-only: top-N by delay (one-shot)\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], 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 // incidents-only with monitor: recurring counts that survive ticks\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], monitor: { entryId: \"incidents-0\" }, name: \"rolling-count\", code: \"const trend = previous ? (incidents.length > previous.count ? \\'growing\\' : incidents.length < previous.count ? \\'fading\\' : \\'steady\\') : \\'new\\'; return { count: incidents.length, trend, at: now.toISOString() };\" })',\n // incidents-only with monitor + focus: surface top-3 worst delays as the focus subset\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], monitor: { entryId: \"incidents-0\" }, name: \"top-3-focus\", code: \"const top = [...incidents].sort((a,b) => (b.properties.delayInSeconds ?? 0) - (a.properties.delayInSeconds ?? 0)).slice(0,3); return { count: top.length, focusIds: top.map(i => i.properties.id), focusReason: \\'top 3 by delay\\' };\" })',\n ],\n examplePrompts: [\n 'How many of each POI category are in these results?',\n 'h3 hex density of these places',\n 'Bar chart of the top POI categories',\n 'How long is each route alternative?',\n 'Compare travel times across alternatives',\n 'How many EV stations are within 500 m of the route?',\n 'Chart the distance from each cafe to the route, binned in km',\n 'Histogram of place-to-route distances',\n 'Distribution of traffic incidents along the drive from Paris to Brussels',\n 'Chart the incidents on the route by category',\n 'Total delay per country on this route',\n 'How big are these areas?',\n 'Bar chart of city areas',\n 'Incidents per place cluster',\n 'Total delay per traffic corridor',\n 'Congestion distribution across the analytics tiles',\n 'Average historical speed per tile, as a chart',\n 'Group these EV stations by the congestion level of the tile they fall in',\n 'For the route, which tiles does it cross and what is their average congestion?',\n 'How many incidents of each category?',\n 'Top 5 incidents by delay',\n 'Total delay grouped by road',\n 'Keep counting the incidents and tell me when it grows or fades',\n 'Highlight the 3 worst incidents by delay on the map',\n 'Bar chart of incidents per category',\n 'Which jams are growing vs fading on this monitored area?',\n ],\n relatedTools: [\n 'processData',\n 'focusIncidents',\n 'recallPlaces',\n 'recallRoutes',\n 'recallGeometries',\n 'recallState',\n ],\n dependsOn: [\n 'discoverPlaces',\n 'setRoute',\n 'getTrafficIncidents',\n 'getTrafficAreaAnalytics',\n 'findReachableAreas',\n 'recallPlaces',\n 'recallRoutes',\n 'recallGeometries',\n ],\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', 'toggleTilesPOIs'],\n },\n getTrafficIncidents: {\n description: getTrafficIncidentsDescription,\n classificationPrompt:\n 'AREA-BOUND incident loader — bbox / viewport / \"incidents around here\" / \"accidents in <city>\". ' +\n 'DO NOT pick for route-bound queries (\"incidents ALONG / ON the route\", \"delays on the drive from X to Y\"): ' +\n 'a calculated route already carries its incidents in `route.properties.sections.traffic[]` (category, ' +\n 'magnitudeOfDelay, delayInSeconds, effectiveSpeedInKmh) — call `analyseData` with `routesEntryIDs` for those. ' +\n 'When this tool IS the right pick: MANDATORY co-pick startTrafficIncidentsMonitor (they are a pair — data goes stale without it). ' +\n 'Also co-pick analyseData (one-shot aggregations / charts / clusters; pass `monitor: { entryId }` for the recurring spec + focus side-effect) 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({}) // omit `where` → current viewport',\n 'getTrafficIncidents({ where: { mode: \"within\", boundingBox: [4.728, 52.278, 5.080, 52.479] } })',\n 'getTrafficIncidents({ where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }] } }) // named area',\n 'getTrafficIncidents({ where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }] }, categoryFilter: [\"accident\", \"jam\"] })',\n 'getTrafficIncidents({ where: { mode: \"within\", route: { widthMeters: 1000 } }, categoryFilter: [\"roadworks\"], timeValidityFilter: [\"future\"] }) // corridor around latest route',\n 'getTrafficIncidents({ where: { mode: \"within\", range: \"ranges-0\" } }) // inside a reachable area',\n ],\n examplePrompts: [\n 'Fetch traffic incidents in Amsterdam',\n 'Any accidents in this area?',\n 'Show roadworks across the city',\n // Counter-examples: NOT this tool — route-bound queries use analyseData over routes.\n ],\n relatedTools: ['calculateBBox', 'getViewport', 'analyseData', 'focusIncidents', 'startTrafficIncidentsMonitor'],\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 analyseData 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: ['analyseData', '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', 'analyseData'],\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', 'updateTrafficAreaAnalyticsDisplay', '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', 'updateTrafficAreaAnalyticsDisplay'],\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', 'processData'],\n },\n updateRoutesDisplay: {\n description: updateRoutesDisplayDescription,\n classificationPrompt:\n 'Show, hide, swap, and recolor routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, or set `mainColor` (CSS color / hex; sticky across entry swaps).',\n inputSchema: updateRoutesDisplaySchema,\n outputSchema: updateRoutesDisplayOutputSchema,\n execute: executeUpdateRoutesDisplay,\n tags: ['route', 'waypoint', 'map style'],\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 'updateRoutesDisplay({ mainColor: \"coral\" }) // recolor every currently-shown route (and future shows)',\n 'updateRoutesDisplay({ add: [\"routes-1\"], mainColor: \"#FF0000\" }) // show in red',\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 'Make the route line red',\n 'Change the route color to coral',\n 'Style the route in steelblue',\n ],\n relatedTools: ['setRoute', 'processData', 'recallRoutes', 'clearMap'],\n dependsOn: ['setRoute'],\n },\n updateTrafficAreaAnalyticsDisplay: {\n description: updateTrafficAreaAnalyticsDisplayDescription,\n classificationPrompt:\n 'Show / hide / swap / restyle historical traffic-area-analytics entries on the map. `add` / `remove` / `addMatching` / `removeMatching` / `clear` for visibility; `mode` / `metric` / `colorTheme` / `heightScale` / `scaleMode` / `filter` for the rendering. Render knobs alone implicitly target the most recent entry.',\n inputSchema: updateTrafficAreaAnalyticsDisplaySchema,\n outputSchema: updateTrafficAreaAnalyticsDisplayOutputSchema,\n execute: executeUpdateTrafficAreaAnalyticsDisplay,\n tags: ['traffic', 'map style'],\n examples: [\n 'updateTrafficAreaAnalyticsDisplay({ add: [\"tta-0\"] })',\n \"updateTrafficAreaAnalyticsDisplay({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' }) // re-style the latest (implicit)\",\n 'updateTrafficAreaAnalyticsDisplay({ filter: { min: 50 } })',\n \"updateTrafficAreaAnalyticsDisplay({ scaleMode: 'currentRange', heightScale: 200 })\",\n 'updateTrafficAreaAnalyticsDisplay({ clear: true })',\n 'updateTrafficAreaAnalyticsDisplay({ clear: true, add: [\"tta-1\"] }) // atomic swap',\n 'updateTrafficAreaAnalyticsDisplay({ remove: [\"tta-0\"] })',\n 'updateTrafficAreaAnalyticsDisplay({ addMatching: [\"Amsterdam\"] })',\n ],\n examplePrompts: [\n 'Show the analytics 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 'Also show the Amsterdam analytics',\n 'Hide the Rotterdam analytics',\n 'Replace what is on the map with this analytics',\n ],\n relatedTools: [\n 'getTrafficAreaAnalytics',\n 'queryTrafficAnalytics',\n 'toggleTilesTrafficFlow',\n 'clearMap',\n 'analyseData',\n 'processData',\n ],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n updateWaypointsDisplay: {\n description: updateWaypointsDisplayDescription,\n classificationPrompt:\n 'Show or hide the STAGED origin/stop/destination markers (`state.routing.planningSlots` — what is queued for the NEXT `setRoute`). No route line is drawn; for that use `updateRoutesDisplay`. Single-collection toggle: `visible: true` (default) renders the staged set, `visible: false` clears it.',\n inputSchema: updateWaypointsDisplaySchema,\n outputSchema: updateWaypointsDisplayOutputSchema,\n execute: executeUpdateWaypointsDisplay,\n tags: ['waypoint', 'route'],\n examples: [\n 'updateWaypointsDisplay()',\n 'updateWaypointsDisplay({ visible: true })',\n 'updateWaypointsDisplay({ visible: false })',\n ],\n examplePrompts: [\n 'Show the staged waypoints on the map',\n 'Mark my pending route stops',\n 'Hide the staged waypoints',\n ],\n relatedTools: ['setRoute', 'updateRoutesDisplay', 'getCurrentWaypoints', 'clearMap'],\n dependsOn: ['setRoute', 'locatePlace'],\n },\n getShownTileIncidents: {\n description: getShownTileIncidentsDescription,\n classificationPrompt:\n 'Read traffic incidents currently rendered by the TomTom TRAFFIC-TILE OVERLAY (the rasterised live tiles ' +\n \"toggled via `toggleTilesTrafficIncidents`). DO NOT confuse with `getTrafficIncidents` — that's the GeoJSON \" +\n 'service that returns full structured incidents with start/end times, road numbers, lanes, etc. ' +\n 'Pick this ONLY for \"what\\'s on the tile overlay right now\"; for richer per-incident data use ' +\n '`getTrafficIncidents`, and for \"incidents along the route\" use `analyseData` over routes ' +\n '(`route.properties.sections.traffic[]`).',\n inputSchema: getShownTileIncidentsSchema,\n outputSchema: getShownTileIncidentsOutputSchema,\n execute: executeGetShownTileIncidents,\n tags: ['traffic', 'location', 'map style'],\n examples: ['getShownTileIncidents()'],\n examplePrompts: [\n 'What incidents does the traffic tile overlay show right now?',\n \"What's rendered on the traffic incidents layer?\",\n ],\n relatedTools: ['toggleTilesTrafficIncidents', 'getTrafficIncidents', 'flyTo', 'getViewport'],\n dependsOn: ['toggleTilesTrafficIncidents'],\n },\n clearMap: {\n description: clearMapDescription,\n classificationPrompt:\n 'Hide what the chosen state slices are rendering on the map. `layers` are SLICE KEYS: `places`, `routing`, `ranges`, `customGeometries`, `byod`, `trafficAreaAnalytics`, `trafficIncidents`. Omit to clear every slice. Map-only — slice history is kept.',\n inputSchema: clearMapSchema,\n outputSchema: clearMapOutputSchema,\n execute: executeClearMap,\n tags: ['place', 'route', 'map style'],\n examples: [\n 'clearMap()',\n 'clearMap({ layers: [\"places\"] })',\n 'clearMap({ layers: [\"places\", \"routing\"] })',\n 'clearMap({ layers: [\"trafficAreaAnalytics\", \"trafficIncidents\"] })',\n 'clearMap({ layers: [\"byod\"] })',\n ],\n examplePrompts: ['Clear the map', 'Remove all markers', 'Start fresh', 'Hide the analytics overlay'],\n relatedTools: ['updatePlacesDisplay', 'updateRoutesDisplay', 'updateTrafficAreaAnalyticsDisplay'],\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: \"customGeometries\", id: \"bakery-candidates-gap-oost\" } })',\n ],\n examplePrompts: [\n 'What polygons are in state right now?',\n 'Which IDs can I pass to processData (geometriesEntryIDs)?',\n 'Recall the custom zone we just produced',\n 'What does the bakery-candidates entry contain?',\n ],\n relatedTools: ['processData', 'analyseData', 'recallPlaces', 'recallRanges'],\n },\n recallPlaces: recallPlacesBuilder,\n recallByod: {\n description: recallByodDescription,\n classificationPrompt:\n 'List/inspect BYOD (bring-your-own-data) layers in session state. Returns ids the model can pass to `byodEntryIDs` (analyseData / processData) or to `updateByodDisplay` for visibility control.',\n inputSchema: recallByodSchema,\n outputSchema: recallByodOutputSchema,\n execute: executeRecallByod,\n tags: ['recall', 'byod', 'state'],\n examples: ['recallByod()', 'recallByod({ id: \"byod-0\" })'],\n examplePrompts: [\n 'What customer-uploaded layers do we have?',\n 'List BYOD entries',\n 'Show details of the sales-territories BYOD layer',\n ],\n relatedTools: ['addByodLayer', 'updateByodDisplay', 'analyseData', 'processData'],\n },\n addByodLayer: {\n description: addByodLayerDescription,\n classificationPrompt:\n 'Add a customer-owned GeoJSON layer (URL fetch or inline) as a new BYOD entry. Use when the user wants to import / load / overlay a customer-supplied dataset. Returns the new entry id (use with `byodEntryIDs` on analyseData / processData).',\n inputSchema: addByodLayerSchema,\n outputSchema: addByodLayerOutputSchema,\n execute: executeAddByodLayer,\n tags: ['byod', 'state', 'import'],\n examples: [\n 'addByodLayer({ label: \"Sales territories\", url: \"https://example.com/territories.geojson\", show: true })',\n 'addByodLayer({ label: \"Inline pins\", data: { type: \"FeatureCollection\", features: [/* … */] } })',\n ],\n examplePrompts: [\n 'Load my territories layer from https://...',\n 'Import this GeoJSON as a BYOD layer',\n 'Add a customer-pins layer from this URL',\n ],\n relatedTools: ['recallByod', 'updateByodDisplay', 'analyseData', 'processData'],\n },\n updateByodDisplay: {\n description: updateByodDisplayDescription,\n classificationPrompt:\n 'Toggle visibility (or drop) of BYOD entries already in session state. Use after `addByodLayer` or after `recallByod` exposes the ids. Three actions: show / hide / remove. Pair with `clearAll` or `hideOthers` for bulk swaps.',\n inputSchema: updateByodDisplaySchema,\n outputSchema: updateByodDisplayOutputSchema,\n execute: executeUpdateByodDisplay,\n tags: ['byod', 'display', 'visibility'],\n examples: [\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"show\" })',\n 'updateByodDisplay({ entryIds: [\"byod-0\", \"byod-1\"], action: \"show\", hideOthers: true })',\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"hide\" })',\n 'updateByodDisplay({ action: \"hide\", clearAll: true })',\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"remove\" })',\n ],\n examplePrompts: [\n 'Hide the customer pins layer',\n 'Show only the territories BYOD layer',\n 'Hide all BYOD layers',\n 'Drop the imported file from the session',\n ],\n relatedTools: ['recallByod', 'addByodLayer'],\n dependsOn: ['addByodLayer'],\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 'STAGED waypoint slots only — the origin/stops/destination being built up for the NEXT `setRoute` call ' +\n '(read off `state.routing.planningSlots`, NOT off any calculated route entry). ' +\n \"For waypoints of an EXISTING calculated route, use `recallRoutes({ id })` — its detail mode returns the entry's own waypoints.\",\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: [\n \"What's currently staged for the next route?\",\n \"What's my pending destination before I hit go?\",\n 'Show the stops I have queued up',\n ],\n relatedTools: ['locatePlace', 'setRoute', 'updateWaypointsDisplay', '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', 'toggleTilesBaseMapLayerGroups', 'getMapStyleLayers'],\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 toggleTilesBaseMapLayerGroups: {\n description: toggleTilesBaseMapLayerGroupsDescription,\n classificationPrompt:\n 'Show / hide vector-tile base-map layer groups (buildings3D, roadLabels, water, placeLabels, …). Style-layer only — does NOT touch places / routes / custom-geometries rendered by the agent.',\n inputSchema: toggleTilesBaseMapLayerGroupsSchema,\n outputSchema: toggleTilesBaseMapLayerGroupsOutputSchema,\n execute: executeToggleTilesBaseMapLayerGroups,\n tags: ['map style'],\n examples: [\n 'toggleTilesBaseMapLayerGroups({ visible: false, layerGroups: [\"buildings3D\"] })',\n 'toggleTilesBaseMapLayerGroups({ visible: true, layerGroups: [\"roadLabels\"] })',\n ],\n examplePrompts: ['Hide 3D buildings', 'Show road labels', 'Toggle the building layer'],\n relatedTools: ['toggleTilesPOIs', 'getMapStyleLayers', 'setMapStandardStyle'],\n },\n toggleTilesPOIs: {\n description: toggleTilesPOIsDescription,\n classificationPrompt:\n 'Show / hide vector-tile POI icons baked into the base-map style (restaurants/hotels/gas stations etc. shipped with the map). NOT places shown via `discoverPlaces` / `updatePlacesDisplay` (those are PlacesModule pins, untouched).',\n inputSchema: toggleTilesPOIsSchema,\n outputSchema: toggleTilesPOIsOutputSchema,\n execute: executeToggleTilesPOIs,\n tags: ['place', 'map style'],\n examples: [\n 'toggleTilesPOIs({ allMapPOIsVisible: false })',\n 'toggleTilesPOIs({ allMapPOIsVisible: true, filterCategories: { show: \"only\", values: [\"RESTAURANT\", \"CAFE\"] } })',\n 'toggleTilesPOIs({ filterCategories: { show: \"all_except\", values: [\"PARKING_GARAGE\"] } })',\n 'toggleTilesPOIs({ reset: true })',\n ],\n examplePrompts: [\n 'Hide all map POI icons',\n 'Show only restaurant icons on the map style',\n 'Turn off parking icons in the base map',\n ],\n relatedTools: ['toggleTilesBaseMapLayerGroups', 'discoverPlaces', 'setMapStandardStyle'],\n },\n toggleTilesTrafficFlow: {\n description: toggleTilesTrafficFlowDescription,\n classificationPrompt:\n 'Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical) or route-section traffic (read via `analyseData`).',\n inputSchema: toggleTilesTrafficFlowSchema,\n outputSchema: toggleTilesTrafficFlowOutputSchema,\n execute: executeToggleTilesTrafficFlow,\n tags: ['traffic', 'map style'],\n examples: ['toggleTilesTrafficFlow({ visible: true })', 'toggleTilesTrafficFlow({ visible: false })'],\n examplePrompts: ['Show traffic flow', 'Turn off traffic colors', 'Enable the congestion overlay'],\n relatedTools: ['toggleTilesTrafficIncidents', 'setRoute', 'getTrafficIncidents'],\n },\n toggleTilesTrafficIncidents: {\n description: toggleTilesTrafficIncidentsDescription,\n classificationPrompt:\n 'Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the GeoJSON `getTrafficIncidents` service and NOT `analyseData` (which aggregates fetched entries). Use `getShownTileIncidents` to read what this overlay currently renders.',\n inputSchema: toggleTilesTrafficIncidentsSchema,\n outputSchema: toggleTilesTrafficIncidentsOutputSchema,\n execute: executeToggleTilesTrafficIncidents,\n tags: ['traffic', 'map style'],\n examples: ['toggleTilesTrafficIncidents({ visible: true })', 'toggleTilesTrafficIncidents({ visible: false })'],\n examplePrompts: ['Show traffic incidents overlay', 'Hide accident icons', 'Enable incident markers on the map'],\n relatedTools: ['toggleTilesTrafficFlow', 'getTrafficIncidents', 'getShownTileIncidents', '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', 'updateRoutesDisplay'],\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 any entry-owning slice (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents) 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: \"routing\", mode: \"multiple\" })',\n 'setEntryMode({ slice: \"trafficIncidents\", mode: \"single\" })',\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 'Only keep the latest traffic incidents snapshot',\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/**\n * Returns each default tool's `examplePrompts` keyed by tool name. Builders are\n * materialised with empty {@link ToolBuildOptions} so the prompts reflect the\n * tool's default (full-surface) shape. Tools without prompts map to `[]`.\n *\n * Surface these in chat UIs to keep starter suggestions in sync with what the\n * tools can actually do — the registry stays the single source of truth.\n *\n * @group Agent Toolkit\n */\nexport const getDefaultToolPrompts = (): Record<ToolName, readonly string[]> => {\n const result = {} as Record<ToolName, readonly string[]>;\n for (const name of TOOL_NAMES) {\n const entry = DEFAULT_TOOLS[name];\n const materialized = typeof entry === 'function' ? entry({}) : entry;\n result[name] = materialized.examplePrompts ?? [];\n }\n return result;\n};\n","/**\n * @module agent-toolkit\n */\n\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { PrepareStepFunction, Tool } from 'ai';\nimport { stepCountIs, ToolLoopAgent } from 'ai';\nimport {\n applyToolScopes,\n composeStepResult,\n createPrepareStepMutex,\n destroyState,\n restoreScopableTools,\n} from './prepare-step-helpers';\nimport { resolveTools } from './resolve-tools';\nimport {\n createToolState,\n DATA_ENTRY_KIND_TO_SLICE,\n type DataEntryKind,\n type EntryModeSlice,\n TOOLS_BY_DATA_ENTRY_KIND,\n} from './state';\nimport { buildSystemPrompt } from './system-prompt';\nimport { type ScopableToolInfo, setupTools } from './tool-setup';\nimport { DEFAULT_TOOLS } from './tools';\nimport { ALL_ENTRY_DATA_KINDS } from './tools/shared';\nimport type {\n Classifier,\n EntryDataKind,\n MapAgentInstance,\n MapAgentOptions,\n ToolDefinition,\n ToolMetadata,\n ToolState,\n} from './types';\nimport type { ClassificationResult } from './utils';\nimport { createDefaultClassifier } from './utils';\n\n// CONCURRENCY: the AI SDK does not support per-step tool overrides (`PrepareStepResult` has no\n// `tools` field — only `activeTools` for name-level filtering). Mutating the shared `tools` map is\n// therefore the only path to a per-turn scoped schema, and it imposes a single-concurrent-run\n// invariant on any agent instance that has scopable tools. Two concurrent `respond()` calls on the\n// same agent would race on the mutation; we serialise `prepareStep` invocations across the agent\n// with `createPrepareStepMutex` to keep the mutation/serialize-request window atomic per call.\n// Callers who genuinely need parallel sessions should create one agent per session (the typical\n// embedding-app pattern) rather than share an agent across concurrent users.\n\n/**\n * Inputs the step-0 classification needs. Bundled into a single object so the orchestrator's\n * inner closure can pass the agent-creation-time state through to `runStep0Classification` without\n * a wide parameter list. None of the fields change across steps — the agent's whole lifetime sees\n * the same `tools` / `scopableTools` / `toolsMetadata` / `classifier`.\n */\ntype ClassificationDeps = {\n tools: Record<string, Tool>;\n scopableTools: Record<string, ScopableToolInfo>;\n toolsMetadata: Record<string, ToolMetadata>;\n classifier: Classifier | null;\n onClassify?: (result: ClassificationResult | null) => void;\n};\n\n/**\n * Step-0 pipeline: restore each scopable tool to its full surface so the classifier has a clean\n * baseline, run the classifier (swallowing any throw — networks are flaky and `null` triggers\n * fail-open), surface the result via `onClassify`, and finally mutate the live tool objects'\n * `description` + `inputSchema` using the classifier's per-tool scopes. The AI SDK reads tool\n * definitions on its next request build, so this mutation is what makes per-turn scoping work.\n *\n * Returns the classification (or null) for the caller to cache across subsequent steps.\n */\nconst runStep0Classification = async (\n deps: ClassificationDeps,\n messages: Parameters<PrepareStepFunction>[0]['messages'],\n): Promise<ClassificationResult | null> => {\n restoreScopableTools(deps.tools, deps.scopableTools);\n let result: ClassificationResult | null = null;\n if (deps.classifier) {\n try {\n result = await deps.classifier({\n messages,\n toolsMetadata: deps.toolsMetadata,\n });\n } catch {\n result = null;\n }\n }\n deps.onClassify?.(result);\n applyToolScopes(deps.tools, deps.scopableTools, result?.toolScopes);\n return result;\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 // Apply per-kind `entryMode` from `dataEntries`. Each kind maps to a single slice via\n // {@link DATA_ENTRY_KIND_TO_SLICE}. The slice histories are empty at this point so\n // `setEntryMode` resolves synchronously (no entries to hide); the promise is\n // intentionally not awaited because createMapAgent itself is sync.\n if (options.dataEntries) {\n for (const [kindKey, config] of Object.entries(options.dataEntries)) {\n const kind = kindKey as DataEntryKind;\n if (!config?.entryMode) continue;\n const sliceName = DATA_ENTRY_KIND_TO_SLICE[kind];\n void (state[sliceName] as EntryModeSlice).setEntryMode(config.entryMode);\n }\n }\n\n // Compute the set of enabled data-entry kinds. Used both to filter the tool registry\n // (drop slice-specific tools for disabled kinds) and to narrow analyseData / processData\n // input surface via {@link ToolBuildOptions.enabledDataKinds}. Default is everything\n // enabled; only kinds with an explicit `enabled: false` drop out.\n const isKindEnabled = (kind: DataEntryKind): boolean => options.dataEntries?.[kind]?.enabled !== false;\n const disabledKinds: DataEntryKind[] = (Object.keys(DATA_ENTRY_KIND_TO_SLICE) as DataEntryKind[]).filter(\n (kind) => !isKindEnabled(kind),\n );\n const enabledDataKinds: readonly EntryDataKind[] = ALL_ENTRY_DATA_KINDS.filter((kind) => isKindEnabled(kind));\n // Names of default tools to drop because their slice is disabled. Custom-tool overrides\n // (passed via `options.tools`) are NOT filtered — integrators that want to gate their\n // own tools on a disabled kind can inspect `dataEntries` themselves.\n const droppedToolNames = new Set<string>(disabledKinds.flatMap((kind) => TOOLS_BY_DATA_ENTRY_KIND[kind]));\n\n // Safe: default tools only access base ToolState properties; CS extends ToolState.\n const defaultsAll =\n options.includeDefaultTools === false ? {} : (DEFAULT_TOOLS as Record<string, ToolDefinition<CS>>);\n // Filter slice-specific default tools out before user overrides — user overrides win,\n // so a custom tool registered under the same name resurrects it on purpose.\n const defaults: Record<string, ToolDefinition<CS>> = Object.fromEntries(\n Object.entries(defaultsAll).filter(([name]) => !droppedToolNames.has(name)),\n );\n const toolEntries = resolveTools(defaults, options.tools);\n\n const { tools, toolsMetadata, scopableTools } = setupTools(toolEntries, state, {\n outputSchemas: options.outputSchemas,\n featureFlags: options.featureFlags,\n enabledDataKinds,\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 // Per-agent serialising mutex around prepareStep. Guards the tool-object mutation window so\n // concurrent `respond()` calls on the same agent don't race on `tools[name].description /\n // inputSchema`. The AI SDK reads tool definitions immediately after prepareStep returns, so\n // serialising prepareStep keeps each call's request-build atomic with respect to mutations.\n const withPrepareStepLock = createPrepareStepMutex();\n\n // Bundle classifier + tools so the step-0 helper has a single argument. None of these change\n // across steps, so the bag is constructed once and closed over by prepareStep.\n const classificationDeps: ClassificationDeps = {\n tools,\n scopableTools,\n toolsMetadata,\n classifier,\n onClassify: options.onClassify,\n };\n\n const prepareStep: PrepareStepFunction = (stepInfo) =>\n withPrepareStepLock(async () => {\n if (stepInfo.stepNumber === 0) {\n lastClassification = await runStep0Classification(classificationDeps, stepInfo.messages);\n }\n const activeTools = lastClassification?.activeToolNames;\n const userResult = await options.prepareStep?.(stepInfo);\n const stepProviderOptions = options.stepProviderOptions?.({\n activeTools,\n stepNumber: stepInfo.stepNumber,\n });\n return composeStepResult(activeTools, stepProviderOptions, userResult);\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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"}
1
+ {"version":3,"file":"index.es.js","names":[],"sources":["../src/prepare-step-helpers.ts","../src/resolve-tools.ts","../src/state/base-map/state.ts","../src/state/entry-helpers.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/byod/layer-defaults.ts","../src/state/byod/state.ts","../src/state/traffic-area-analytics/state.ts","../src/tools/shared/geometries-id.ts","../src/tools/shared/geometries-schema-doc.ts","../src/tools/shared/places-schema-doc.ts","../src/tools/shared/routes-schema-doc.ts","../src/tools/shared/schema.ts","../src/tools/shared/entry-kinds.ts","../src/tools/shared/fit-on-map.ts","../src/tools/shared/hide-previous-entries.ts","../src/tools/shared/location-input.ts","../src/tools/shared/state-inputs.ts","../src/tools/shared/multi-input.ts","../src/tools/shared/resolve-poi-categories.ts","../src/tools/shared/sandbox-code.ts","../src/tools/shared/show-entry-geometries.ts","../src/tools/shared/show-places-on-map.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/add-byod-layer.ts","../src/tools/state/analyse-data.ts","../src/tools/state/focus-incidents.ts","../src/tools/state/get-current-waypoints.ts","../src/tools/state/process-data.ts","../src/tools/state/query-traffic-analytics.ts","../src/tools/state/recall-byod.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-tile-incidents.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/toggle-tiles-base-map-layer-groups.ts","../src/tools/tomtom-map/toggle-tiles-pois.ts","../src/tools/tomtom-map/toggle-tiles-traffic.ts","../src/tools/tomtom-map/update-byod-display.ts","../src/tools/tomtom-map/update-places-display.ts","../src/tools/tomtom-map/update-routes-display.ts","../src/tools/tomtom-map/update-traffic-area-analytics-display.ts","../src/tools/tomtom-map/update-waypoints-display.ts","../src/tools/tool-registry.ts","../src/create-map-agent.ts"],"sourcesContent":["/**\n * @module agent-toolkit\n * @ignore\n *\n * Pure helpers used by {@link createMapAgent}'s `prepareStep` orchestrator. Extracted from the\n * monolithic module so they can be exercised in isolation by the unit-test suite — none of these\n * touch the AI SDK, the TomTom map, or any singleton state, so they're testable without mocks.\n */\n\nimport type { PrepareStepResult, Tool } from 'ai';\nimport type { ScopableToolInfo } from './tool-setup';\nimport type { StateSlice, ToolState } from './types';\n\ntype ProviderOptions = NonNullable<PrepareStepResult>['providerOptions'];\n\n// Tool objects with mutable description/inputSchema. The AI SDK reads these fresh on every step,\n// so per-turn mutation is the supported way to scope a tool's surface — see the concurrency note\n// on `createMapAgent`.\ntype MutableTool = Tool & { description: string; inputSchema: unknown };\n\n/**\n * Build a per-agent serialising mutex. `prepareStep` chains onto this so a concurrent step's\n * classification + scope-mutation cannot interleave with the previous one's request-build window.\n * Each agent instance gets its own chain via a fresh call to this factory.\n *\n * @ignore\n */\nexport const createPrepareStepMutex = () => {\n let tail: Promise<void> = Promise.resolve();\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n const previous = tail;\n let release: () => void = () => undefined;\n tail = new Promise<void>((resolve) => {\n release = resolve;\n });\n try {\n await previous;\n return await fn();\n } finally {\n release();\n }\n };\n};\n\n/**\n * Restore each scopable tool's description + inputSchema to its full-surface defaults. Called at\n * the start of every turn (step 0) before classification so the new turn inherits a clean slate.\n *\n * @ignore\n */\nexport const restoreScopableTools = (\n tools: Record<string, Tool>,\n scopableTools: Record<string, ScopableToolInfo>,\n): void => {\n for (const [name, info] of Object.entries(scopableTools)) {\n const tool = tools[name] as MutableTool | undefined;\n if (!tool) continue;\n tool.description = info.defaultDescription;\n tool.inputSchema = info.defaultInputSchema;\n }\n};\n\n/**\n * Apply classifier-emitted per-tool scopes by mutating the live tool object. Validation and\n * rebuilding are encapsulated in {@link ScopableToolInfo.applyScope}; a `null` return covers both\n * mis-shaped payloads and rebuild throws, in which case the default surface stays in place —\n * fail-open, never crash the step.\n *\n * @ignore\n */\nexport const applyToolScopes = (\n tools: Record<string, Tool>,\n scopableTools: Record<string, ScopableToolInfo>,\n toolScopes: Record<string, unknown> | undefined,\n): void => {\n if (!toolScopes) return;\n for (const [name, rawScope] of Object.entries(toolScopes)) {\n const scopableTool = scopableTools[name];\n if (!scopableTool || rawScope == null) continue;\n const tool = tools[name] as MutableTool | undefined;\n if (!tool) continue;\n const rebuiltTool = scopableTool.applyScope(rawScope);\n if (!rebuiltTool) continue;\n tool.description = rebuiltTool.description;\n tool.inputSchema = rebuiltTool.inputSchema;\n }\n};\n\n/**\n * Intersect two activeTools lists. Returns `undefined` only when neither side was set (so the\n * caller can omit the key from `PrepareStepResult` entirely). When only one side is set the\n * intersection is that side; when both are set, the result preserves `a`'s order and drops any\n * name absent from `b`. Used to combine classifier-picked tools with a user-supplied override.\n *\n * @ignore\n */\nexport const 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/**\n * Type guard for {@link StateSlice}. Detects the canonical shape (an object with a callable\n * `reset` method) without committing to a particular slice's full surface — used by\n * {@link destroyState} to walk the heterogeneous ToolState bag without per-slice knowledge.\n *\n * @ignore\n */\nexport const isStateSlice = (value: unknown): value is StateSlice =>\n typeof value === 'object' &&\n value !== null &&\n 'reset' in value &&\n typeof (value as { reset: unknown }).reset === 'function';\n\n/**\n * Reset every slice on `state` that implements {@link StateSlice}. Slices without a `reset`\n * method (e.g. the bare-bones `baseMap` proxy) are skipped silently.\n *\n * @ignore\n */\nexport const 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 * Merge per-provider keys from the developer's `prepareStep` override and the agent's per-step\n * providerOptions factory. Either side may be `undefined`; the developer's values win on\n * collision so caller intent is preserved. Returns `undefined` when both sides are absent so\n * callers can omit the key from the final result.\n *\n * @ignore\n */\nexport const mergeProviderOptions = (\n fromStepFactory: ProviderOptions | undefined,\n fromUser: ProviderOptions | undefined,\n): ProviderOptions | undefined => {\n if (!fromStepFactory && !fromUser) return undefined;\n return { ...fromStepFactory, ...fromUser };\n};\n\n/**\n * Compose the PrepareStepResult from three independent contributions: the active-tools list\n * (from the classifier), the per-step providerOptions factory, and the developer's optional\n * user-level `prepareStep` override. When the user overrides, their `activeTools` are\n * intersected with the classifier picks (so the user can narrow further but not widen past the\n * classifier's choice).\n *\n * @ignore\n */\nexport const composeStepResult = (\n activeTools: string[] | undefined,\n stepProviderOptions: ProviderOptions | undefined,\n userResult: PrepareStepResult | undefined,\n): PrepareStepResult => {\n if (userResult) {\n return {\n ...userResult,\n activeTools: intersectActiveTools(activeTools, userResult.activeTools),\n providerOptions: mergeProviderOptions(stepProviderOptions, userResult.providerOptions),\n };\n }\n return {\n ...(activeTools ? { activeTools } : {}),\n ...(stepProviderOptions ? { providerOptions: stepProviderOptions } : {}),\n };\n};\n","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 * @ignore\n *\n * Cross-slice helpers for entry-owning state slices. The agent-toolkit has seven such slices\n * (places, routing, ranges, customGeometries, trafficAreaAnalytics, trafficIncidents, byod) that\n * shared the same id-collision + single-mode-collapse code; consolidating it here keeps the\n * slices in lockstep on edge-case behaviour and makes the rules unit-testable in isolation.\n */\n\n/**\n * Return `requested` if no entry in `takenIds` already uses it; otherwise append `-2`, `-3`, …\n * until an unused id appears. Pure — only reads `takenIds`.\n *\n * Behaviour notes:\n * - `requested` is never modified, only suffixed (`\"foo\"` → `\"foo-2\"`, not `\"foo-1\"`). The first\n * suffix is `-2` so the chain reads naturally: \"foo, foo-2, foo-3, …\".\n * - Acceps any `Iterable<string>` (Set / Array / generator) — the slice's internal entry store\n * shape is irrelevant.\n *\n * @ignore\n */\nexport const pickUniqueEntryId = (requested: string, takenIds: Iterable<string>): string => {\n const taken = new Set(takenIds);\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 * Hide every entry in `entries` and resolve once they're all settled. Sequential by default\n * (avoids MapLibre source-mutation races — see the project-wide `show/hide sequential` note);\n * pass `parallel: true` only when the slice's `hideOne` is provably race-safe (e.g. each entry\n * owns a separate module).\n *\n * Used in two places:\n * - `setEntryMode('single')` collapses history to the latest entry (via\n * {@link collapseHistoryToLatest}, which delegates to this primitive).\n * - `addEntry` under `single` mode replaces the entire history — same hide-all-then-drop\n * shape, but no survivor.\n *\n * Centralising this means addEntry implementations can `await` the hides instead of the\n * fire-and-forget `void this.hideEntry(…)` pattern that previously let a new entry render\n * while the old map modules were still being torn down.\n *\n * @ignore\n */\nexport const hideAllEntries = async <T>(\n entries: readonly T[],\n hideOne: (entry: T) => Promise<void>,\n options: { parallel?: boolean } = {},\n): Promise<void> => {\n if (entries.length === 0) return;\n if (options.parallel) {\n await Promise.all(entries.map(hideOne));\n } else {\n for (const entry of entries) await hideOne(entry);\n }\n};\n\n/**\n * Drop every entry except the last, hiding the dropped ones first. Returns the surviving entries\n * (an array with the latest entry, or empty when the input was empty).\n *\n * Used by `setEntryMode('single')` across every entry-owning slice — `single` mode means only the\n * latest entry stays on the map, so flipping the mode has to retroactively hide and forget every\n * older entry. The helper centralises the \"hide-then-drop\" order so a future change to hide\n * semantics lands in one place.\n *\n * @ignore\n */\nexport const collapseHistoryToLatest = async <T>(\n entries: readonly T[],\n hideOne: (entry: T) => Promise<void>,\n options: { parallel?: boolean } = {},\n): Promise<T[]> => {\n if (entries.length <= 1) return [...entries];\n const latest = entries[entries.length - 1];\n await hideAllEntries(entries.slice(0, -1), hideOne, options);\n return [latest];\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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\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 `processData` 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\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 async addEntry(\n features: PolygonFeature<CommonPlaceProps>[],\n provenance: CustomGeometriesEntry['provenance'],\n label: string,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n const fallback = `geometries-${this._entries.length}`;\n const entryId = explicitId\n ? pickUniqueEntryId(\n explicitId,\n this._entries.map((entry) => entry.id),\n )\n : 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?._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 /**\n * Hide every custom-geometries entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\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// `Array.prototype.sort` without a comparator falls back to UTF-16 code-unit order — fine for\n// ASCII-only ids but unreliable once a localised label leaks in. Centralise the locale-aware\n// comparator so every digest field gets the same predictable order.\nconst compareIds = (a: string, b: string): number => a.localeCompare(b);\nconst sortIds = (ids: Iterable<string>): string[] => [...ids].sort(compareIds);\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 /** Traffic-area-analytics entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total traffic-area-analytics entries in history (shown + hidden). */\n entryCount: number;\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 customGeometries: {\n /** Custom-geometries entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total custom-geometries entries in history (shown + hidden). */\n entryCount: number;\n };\n byod: {\n /** BYOD entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total BYOD entries in history (shown + hidden). */\n entryCount: number;\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: sortIds(state.places.shownAsPinIds),\n shownAsBaseMap: sortIds(state.places.shownAsBaseMapIds),\n entryMode: state.places.entryMode,\n },\n routes: { shown: sortIds(state.routing.shownEntryIds), entryMode: state.routing.entryMode },\n ranges: { shown: sortIds(state.ranges.shownEntryIds), entryMode: state.ranges.entryMode },\n traffic: {\n shown: sortIds(state.trafficAreaAnalytics.shownEntryIds),\n entryMode: state.trafficAreaAnalytics.entryMode,\n entryCount: state.trafficAreaAnalytics.entries.length,\n },\n incidents: {\n shown: sortIds(state.trafficIncidents.shownEntryIds),\n monitored: sortIds(\n state.trafficIncidents.entries\n .filter((entry) => entry._monitor?.status === 'running')\n .map((entry) => entry.id),\n ),\n entryMode: state.trafficIncidents.entryMode,\n },\n customGeometries: {\n shown: sortIds(state.customGeometries.shownEntryIds),\n entryMode: state.customGeometries.entryMode,\n entryCount: state.customGeometries.entries.length,\n },\n byod: {\n shown: sortIds(state.byod.shownEntryIds),\n entryMode: state.byod.entryMode,\n entryCount: state.byod.entries.length,\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 const trafficAnalyticsFragment = formatShownSetDiff('traffic analytics', prev.traffic.shown, next.traffic.shown);\n if (trafficAnalyticsFragment) fragments.push(trafficAnalyticsFragment);\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 const customGeometriesFragment = formatShownSetDiff(\n 'custom geometries',\n prev.customGeometries.shown,\n next.customGeometries.shown,\n );\n if (customGeometriesFragment) fragments.push(customGeometriesFragment);\n\n const byodFragment = formatShownSetDiff('byod', prev.byod.shown, next.byod.shown);\n if (byodFragment) fragments.push(byodFragment);\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 if (prev.customGeometries.entryMode !== next.customGeometries.entryMode) {\n fragments.push(`custom geometries entryMode → ${next.customGeometries.entryMode}`);\n }\n if (prev.byod.entryMode !== next.byod.entryMode) {\n fragments.push(`byod entryMode → ${next.byod.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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\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// 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 a single PlacesModule (rethemed in-place via `applyTheme` when the marker\n * type changes — an entry only renders under one theme at a time) and a dedicated\n * GeometriesModule, lazy-initialised on first show. Display state lives on the entry, so two\n * entries can be rendered side by 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this._hideEntry(entry));\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, rethemed to `markerType`. An entry only\n * ever renders under one theme at a time, so the same module is reused — flipping themes\n * calls `applyTheme` in place. 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 if (!entry._modules.places) {\n entry._modules.places = await PlacesModule.get(this._ttMap, { theme: markerType });\n } else if (entry._shownAs !== markerType) {\n entry._modules.places.applyTheme(markerType);\n }\n return entry._modules.places;\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 async addPlaceResult(\n place: Place | Places,\n label: string,\n explicitId?: string,\n connections?: PlaceConnectionDisplay[],\n geometries?: PolygonFeature<CommonPlaceProps>[],\n ): Promise<string> {\n // Under `single` mode the new entry replaces everything older. Hide and await each\n // older entry's modules before pushing the new one — otherwise the caller's render\n // can race with the in-flight teardown and the previous markers flicker.\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this._hideEntry(entry));\n this._entries = [];\n }\n const fallback = `places-${this._entries.length}`;\n const entryId = explicitId\n ? pickUniqueEntryId(\n explicitId,\n this._entries.map((entry) => entry.id),\n )\n : 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 /**\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 /**\n * Hide every PlacesState rendering — pins AND attached boundary polygons. Map-only:\n * history (`entries`) is untouched. Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n await this.clearShownEntries();\n await this.clearShownGeometries();\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.places?.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`: lazy-inits or rethemes the entry's single\n // PlacesModule, then pushes its places onto it. Idempotent — calling twice with the same\n // theme just re-pushes the same data; switching themes rethemes the existing module in place.\n private async _showEntryAs(entry: PlacesEntry, markerType: PlacesMarkerType): Promise<void> {\n const module = await this.getEntryPlacesModule(entry.id, markerType);\n await module.show({ type: 'FeatureCollection', features: entry.places } as Places);\n if (entry.connections?.length) {\n await module.showConnections(entry.connections);\n }\n entry._shownAs = markerType;\n }\n\n // Hides the entry by clearing its PlacesModule. No-op if already hidden.\n private async _hideEntry(entry: PlacesEntry): Promise<void> {\n if (!entry._shownAs) return;\n if (entry._modules?.places) await entry._modules.places.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.places?.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 { collapseHistoryToLatest, hideAllEntries } from '../entry-helpers';\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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\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?._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 async addEntry(entry: Omit<RangesEntry, 'id' | 'timestamp'>): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (existing) => 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 /**\n * Hide every reachable-range entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(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 { collapseHistoryToLatest, hideAllEntries } from '../entry-helpers';\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 * Slice-level \"main color\" applied to every RoutingModule on first show / re-show. Sticky\n * across entry swaps so the rendered route always uses the most recently requested color.\n * Set via {@link setMainColor}; persists for the lifetime of the slice (cleared on `reset`).\n */\n private _mainColor?: string;\n /**\n * The most recently `.showEntry`-ed id. Tracks \"who was last brought up\" so tools that\n * operate on the focused module have a single pointer to read; the authoritative shown\n * set is `shownEntryIds` (derived from each 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\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 // Apply the sticky main color BEFORE showing so the first paint already has the theme,\n // avoiding a flash of the default style on newly-shown entries.\n if (this._mainColor !== undefined) {\n module.applyConfig({ theme: { mainColor: this._mainColor } });\n }\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 /** Slice-level main color persisted across entry swaps. Undefined when no theme has been set. */\n get mainColor(): string | undefined {\n return this._mainColor;\n }\n\n /**\n * Set the slice-level main color. The colour is applied immediately to every currently-shown\n * entry's RoutingModule and remembered so that subsequent {@link showEntry} calls apply it\n * automatically. Pass `undefined` to clear the sticky theme — currently-shown modules keep\n * their last paint until they're re-shown (MapLibre style restore handles full revert).\n */\n setMainColor(color: string | undefined): void {\n this._mainColor = color;\n if (color === undefined) return;\n for (const entry of this._entries) {\n if (entry._shown && entry._module) {\n entry._module.applyConfig({ theme: { mainColor: color } });\n }\n }\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?._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. Returns undefined when no\n * 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 async addRoutes(routes: Routes, waypoints: WaypointLike[], label: string): Promise<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 await hideAllEntries(this._entries, (entry) => 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 /**\n * Hide every route + waypoint set currently on the map. Map-only — history (`entries`)\n * stays intact. Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\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._mainColor = 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 * Default MapLibre layer specs the BYOD slice picks when the caller does not\n * supply layers explicitly. The slice inspects the entry's geometry types and\n * picks one layer per kind present (circle / line / fill). Mixed-kind entries\n * get all three stacked. The defaults are intentionally subtle (low opacity,\n * stable theme-neutral colour) so they read as \"the agent's data\" rather than\n * competing with TomTom's own layers — the integrator can always override.\n */\n\nimport type { CustomGeoJSONLayerSpec } from '@tomtom-org/maps-sdk/map';\nimport type { FeatureCollection } from 'geojson';\n\nconst POINT_LAYER: CustomGeoJSONLayerSpec = {\n type: 'circle',\n paint: {\n 'circle-radius': 4,\n 'circle-color': '#0a3653',\n 'circle-stroke-color': '#ffffff',\n 'circle-stroke-width': 1,\n },\n};\n\nconst LINE_LAYER: CustomGeoJSONLayerSpec = {\n type: 'line',\n paint: {\n 'line-color': '#0a3653',\n 'line-width': 2,\n },\n};\n\nconst FILL_LAYER: CustomGeoJSONLayerSpec = {\n type: 'fill',\n paint: {\n 'fill-color': '#0a3653',\n 'fill-opacity': 0.25,\n 'fill-outline-color': '#0a3653',\n },\n};\n\ntype GeomKind = 'point' | 'line' | 'polygon';\n\n// Map every GeoJSON geometry-type string to its display kind. Cheaper than an\n// if/else-if cascade and trivially extended; `GeometryCollection` is handled\n// separately at the call site because it nests further geometries.\nconst KIND_BY_TYPE: Record<string, GeomKind> = {\n Point: 'point',\n MultiPoint: 'point',\n LineString: 'line',\n MultiLineString: 'line',\n Polygon: 'polygon',\n MultiPolygon: 'polygon',\n};\n\nconst classify = (data: FeatureCollection): Set<GeomKind> => {\n const kinds = new Set<GeomKind>();\n for (const feature of data.features) {\n const geometry = feature.geometry;\n if (!geometry) continue;\n if (geometry.type === 'GeometryCollection') {\n for (const inner of geometry.geometries) {\n const kind = KIND_BY_TYPE[inner.type];\n if (kind) kinds.add(kind);\n }\n continue;\n }\n const kind = KIND_BY_TYPE[geometry.type];\n if (kind) kinds.add(kind);\n }\n return kinds;\n};\n\n/**\n * Pick sensible default MapLibre layer specs for the given FeatureCollection.\n * For polygon-first overlays the fill comes before the line so the outline\n * sits on top; otherwise the order is fill → line → circle so points stack\n * above areas.\n *\n * @ignore\n */\nexport const defaultLayersFor = (data: FeatureCollection): CustomGeoJSONLayerSpec[] => {\n const kinds = classify(data);\n const layers: CustomGeoJSONLayerSpec[] = [];\n if (kinds.has('polygon')) layers.push(FILL_LAYER);\n if (kinds.has('line')) layers.push(LINE_LAYER);\n if (kinds.has('point')) layers.push(POINT_LAYER);\n // Empty / unknown geometry → fall back to a circle layer; the source will\n // be empty so nothing renders, but the module still needs at least one layer.\n if (layers.length === 0) layers.push(POINT_LAYER);\n return layers;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type CustomGeoJSONLayerSpec, CustomGeoJSONModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { FeatureCollection } from 'geojson';\nimport type { StateSlice } from '../../types';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { BYODEntry, BYODSource } from './entry';\nimport { defaultLayersFor } from './layer-defaults';\n\n/**\n * Events fired by {@link BYODState}. Subscribe via `state.byod.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type BYODStateEvents = {\n /** History changed — entry added, removed, or cleared via `reset()`. */\n 'entries-change': readonly BYODEntry[];\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * Options accepted by {@link BYODState.addEntry}.\n *\n * @group Agent Toolkit\n */\nexport type AddBYODEntryOptions = {\n /**\n * Explicit MapLibre layer specs. When omitted, the slice inspects the\n * FeatureCollection and picks defaults by geometry type\n * (Point → circle, Line → line, Polygon → fill). Mixed-kind entries get\n * one layer per kind present.\n */\n layers?: CustomGeoJSONLayerSpec[];\n /** Provenance for the data. Defaults to `{ kind: 'integrator' }`. */\n source?: BYODSource;\n /**\n * Semantic entry id. Auto-suffixed (`-2`, `-3`, …) on collision. Defaults\n * to `byod-N`.\n */\n explicitId?: string;\n};\n\n/**\n * State slice for BYOD (bring-your-own-data) GeoJSON layers — customer-owned\n * data that doesn't fit places / routes / ranges / custom-geometries. Each\n * entry wraps a {@link CustomGeoJSONModule} so the agent can render, query,\n * and pipe it through analyseData / processData.\n *\n * Per-entry display model mirrors {@link CustomGeometriesState}: every entry\n * owns its own module, lazy-initialised on first show.\n *\n * @group Agent Toolkit\n */\nexport class BYODState implements ShownEntriesSlice, StateSlice {\n private _entries: BYODEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link BYODStateEvents}. */\n readonly events = new StateEvents<BYODStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly BYODEntry[] {\n return this._entries;\n }\n\n get latestEntry(): BYODEntry | undefined {\n return this._entries.at(-1);\n }\n\n /** Set of BYOD 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 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): BYODEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new BYOD entry. Under `single` mode the new entry replaces every older one (hides\n * + drops them before the new entry is pushed, so the caller never sees the new entry\n * rendered on top of a still-visible old one). Returns the assigned id.\n */\n async addEntry(data: FeatureCollection, label: string, options: AddBYODEntryOptions = {}): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n // Always run through `pickUniqueEntryId` — the bare `byod-${length}` fallback collides\n // when an earlier entry has been removed (e.g. add a, add b, remove a → length=1 and\n // the new fallback `byod-1` clashes with the surviving entry's id). The dedupe path\n // appends `-2`, `-3`, … on collision, matching the explicit-id behaviour.\n const entryId = pickUniqueEntryId(\n options.explicitId ?? `byod-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data,\n source: options.source ?? { kind: 'integrator' },\n layers: options.layers ?? defaultLayersFor(data),\n });\n this.events.emit('entries-change', this._entries);\n return entryId;\n }\n\n /**\n * Lazy-init the entry's CustomGeoJSONModule. The module is created with a\n * single source named `data`, configured with this entry's `layers`. Layer\n * changes after creation are not supported — drop the entry and add it back\n * to swap layers.\n */\n async getEntryModule(entryId: string): Promise<CustomGeoJSONModule> {\n const entry = this._requireEntry(entryId);\n entry._module ??= await CustomGeoJSONModule.get(this._ttMap, {\n sources: { data: { layers: entry.layers } },\n });\n return entry._module;\n }\n\n /** Render this entry on the map. */\n async showEntry(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n await this._hideOthersUnderSingleMode(entryId);\n const module = await this.getEntryModule(entryId);\n await module.show(entry.data, 'data');\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?._shown) return;\n 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): BYODEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`BYODState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every BYOD layer currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n for (const entry of this._entries) entry._module?.clear().catch(() => undefined);\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 { 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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { TrafficAreaAnalyticsAnalysis } from './analysis';\nimport type { TrafficAreaAnalyticsEntry, TrafficAreaAnalyticsParams } from './entry';\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 /** History changed — entry added, removed, or cleared via `reset()`. */\n 'entries-change': readonly TrafficAreaAnalyticsEntry[];\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Per-entry viz config changed (mode / metric / scaleMode / …). */\n 'config-change': { entryId: string; config: TrafficAreaAnalyticsConfig | undefined };\n /** A new analysis was attached to (or replaced on) an entry. */\n 'analysis-added': { entryId: string; analysis: TrafficAreaAnalyticsAnalysis };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * Append-only history of traffic-area-analytics aggregations, with per-entry\n * rendering. Each entry owns a dedicated {@link TrafficAreaAnalyticsModule},\n * lazy-initialised on first show. Mirrors the per-entry pattern used by\n * `RoutingState` / `CustomGeometriesState`.\n *\n * Live tile overlays (flow + incidents) live in {@link TrafficTilesState}.\n *\n * @group Agent Toolkit\n */\nexport class TrafficAreaAnalyticsState implements ShownEntriesSlice, StateSlice {\n private _entries: TrafficAreaAnalyticsEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link TrafficAreaAnalyticsStateEvents}. */\n readonly events = new StateEvents<TrafficAreaAnalyticsStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly TrafficAreaAnalyticsEntry[] {\n return this._entries;\n }\n\n get latestEntry(): TrafficAreaAnalyticsEntry | undefined {\n return this._entries.at(-1);\n }\n\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 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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', this._entries);\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): TrafficAreaAnalyticsEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new entry. Auto-suffixes the id on collision. Under `single` mode the new entry\n * replaces every older one (hidden + dropped from history).\n */\n async addEntry(\n data: TrafficAreaAnalytics,\n label: string,\n params: TrafficAreaAnalyticsParams,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n const fallback = `tta-${this._entries.length}`;\n const entryId = explicitId\n ? pickUniqueEntryId(\n explicitId,\n this._entries.map((entry) => entry.id),\n )\n : fallback;\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data,\n params,\n });\n this.events.emit('entries-change', this._entries);\n return entryId;\n }\n\n /**\n * Attach an analysis to an existing entry. Replaces the prior result keyed by `analysis.name`\n * on the same entry (mirrors PlacesState / CustomGeometriesState semantics). Returns `false`\n * when the entry id is unknown.\n */\n addAnalysisToEntry(entryId: string, analysis: TrafficAreaAnalyticsAnalysis): 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 /**\n * Lazy-init the entry's TrafficAreaAnalyticsModule, wiring per-entry config-change events.\n * Throws on unknown id.\n */\n async getEntryModule(entryId: string): Promise<TrafficAreaAnalyticsModule> {\n const entry = this._requireEntry(entryId);\n if (!entry._module) {\n entry._module = await TrafficAreaAnalyticsModule.get(this._ttMap);\n entry._configChangeUnsub = entry._module.events.on('config-change', (config) => {\n entry._config = config;\n this.events.emit('config-change', { entryId, config });\n });\n }\n return entry._module;\n }\n\n /** Render this entry's analytics on its own module. Under `single` mode, hides every other shown entry first. */\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.getEntryModule(entryId);\n await module.clear();\n await module.show(entry.data);\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Hide the entry's analytics on its module. No-op when unknown or already hidden. */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n 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. Hides it first, then removes; emits `entries-change`. No-op on unknown id. */\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 const entry = this._entries[idx];\n entry._configChangeUnsub?.();\n entry._configChangeUnsub = undefined;\n entry._module = undefined;\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', this._entries);\n }\n\n private _requireEntry(entryId: string): TrafficAreaAnalyticsEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`TrafficAreaAnalyticsState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every traffic-area-analytics entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n for (const entry of this._entries) {\n entry._configChangeUnsub?.();\n entry._module?.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-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' | 'customGeometries';\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('customGeometries').describe('Use the exact literal \"customGeometries\".'),\n id: z\n .string()\n .min(1)\n .describe('Id of a custom-geometries entry produced by a previous `processData` call.'),\n })\n .describe('A derived entry produced by a previous `processData` call.'),\n ])\n .describe(\n 'Tagged id `{ kind, id }`. `kind` must be exactly one of: \"place\" | \"places\" | \"ranges\" | \"customGeometries\" ' +\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\"` | `\"customGeometries\"` (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 schema doc for the `geometries` array consumed/produced by sandboxed\n * code in `processData` / `analyseData`. Each item is a\n * `PolygonFeature<CommonPlaceProps>`; only the non-GeoJSON-standard bits are\n * spelled out here.\n */\n\n/** @ignore */\nexport const GEOMETRIES_SCHEMA_DOC =\n '`geometries` is an array of Polygon/MultiPolygon Features. Each has `id` (the geometry-data-source id, ' +\n 'matches `place.properties.dataSources.geometry.id`) and `bbox`. `properties` varies by source — see ' +\n '`GEOMETRIES_PROPS_DOC`. Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, ' +\n '`turf.buffer`, … directly on these features. For h3, convert via `h3.polygonToCells(feature.geometry.coordinates, res)`.';\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `Place` properties shape, reused by\n * every tool whose LLM-authored code reads or returns a Places\n * FeatureCollection (`processData`, `analyseData`). Only the non-obvious\n * (non-GeoJSON-standard) bits are spelled out — the wrapping `Feature` /\n * `FeatureCollection` shape is assumed known.\n */\n\nimport type { FeatureFlags } from '../../types';\n\nconst EXPERIMENTAL_AREA_FIELDS_DOC =\n ' // Experimental — populated only when `experimentalSearch` is on and the place came from\\n' +\n ' // the exploration-search backend (DE / NL / FR area-tag pipeline).\\n' +\n ' areaId?: string; // id of the surrounding small-area polygon (few km², not a whole municipality)\\n' +\n ' areaCountry?: string; // ISO2 country of the area polygon\\n' +\n ' areaTags?: string[]; // e.g. \"coastal\", \"walkable\", \"transit_connected\"\\n';\n\n/**\n * Build the Place properties schema doc embedded in tool descriptions for\n * sandboxed code. `Places` is a FeatureCollection of Point Features; only the\n * `properties` shape (the TomTom-specific bit) is documented here.\n *\n * @ignore\n */\nexport const buildPlacesSchemaDoc = (flags: FeatureFlags): string =>\n '`places` is a FeatureCollection of Point Features. `Place.properties` shape:\\n' +\n '```ts\\n' +\n 'type PlaceProperties = {\\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?: { // when type === \"POI\"\\n' +\n ' name: string; phone?: string; url?: string;\\n' +\n ' brands?: string[];\\n' +\n ' categories: string[]; // POICategory enum, e.g. \"RESTAURANT\", \"CAFE_PUB\", \"GAS_STATION\", \"HOTEL_MOTEL\", \"ELECTRIC_VEHICLE_STATION\"\\n' +\n ' localizedCategories: string[]; // 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 hits also carry:\\n' +\n ' score?: number; // relevance (higher = better)\\n' +\n ' distance?: number; // meters from geoBias, when provided\\n' +\n ' info?: string;\\n' +\n ' // Geography hits also carry:\\n' +\n ' geographyType?: Array<\"Country\"|\"CountrySubdivision\"|\"CountrySecondarySubdivision\"|\"CountryTertiarySubdivision\"|\"Municipality\"|\"MunicipalitySubdivision\"|\"Neighbourhood\"|\"PostalCodeArea\">;\\n' +\n (flags.experimentalSearch ? EXPERIMENTAL_AREA_FIELDS_DOC : '') +\n ' // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\\n' +\n '};\\n' +\n '```\\n' +\n 'Areas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`.';\n\n/**\n * Default-flag (`experimentalSearch: false`) Places schema doc — preserved as a\n * named constant so non-flag-aware tools keep working.\n *\n * @ignore\n */\nexport const PLACES_SCHEMA_DOC = buildPlacesSchemaDoc({});\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `Route` properties shape, reused by\n * every tool whose LLM-authored code reads a Routes FeatureCollection\n * (`analyseData`, `processData`). Only the non-GeoJSON-standard bits are\n * spelled out — `Route` is a LineString Feature.\n */\n\n/** @ignore */\nexport const ROUTES_SCHEMA_DOC =\n '`routes` is a FeatureCollection of LineString Features (each route has `feature.bbox`). `Route.properties` shape:\\n' +\n '```ts\\n' +\n 'type RouteProperties = {\\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;\\n' +\n ' trafficDelayInSeconds: number;\\n' +\n ' trafficLengthInMeters: number;\\n' +\n ' departureTime: Date; arrivalTime: Date;\\n' +\n ' // EV-only:\\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 ' // Segments grouped by characteristic. Every section has { id, startPointIndex, endPointIndex }\\n' +\n ' // unless noted; *Index values reference `geometry.coordinates`.\\n' +\n ' sections: {\\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\\n' +\n ' tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>;\\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;\\n' +\n ' delayInSeconds?: number;\\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\\n' +\n ' guidance?: { instructions: Array<{ pointIndex: number; maneuver: string; street?: string; message?: string }> };\\n' +\n '};\\n' +\n '```\\n' +\n '`pointIndex` values reference positions in `geometry.coordinates`. Guard `?` fields with `?.` / `??`. ' +\n 'For \"how long / how far\" questions, use `summary.lengthInMeters` / `summary.travelTimeInSeconds`.';\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 * Shared scaffolding for tools that consume multi-kind entry inputs (`analyseData`,\n * `processData`). Centralises the per-{@link EntryDataKind} metadata each tool needs\n * to build its `*EntryIDs` schema fields, sandbox-doc fragments, schema-doc fragments,\n * scope schema and \"at least one of …\" refine — so the tool files themselves stay\n * focused on their own contract (return-shape validator, per-tool extras, execute body).\n */\n\nimport { z } from 'zod';\nimport type { EntryDataKind } from '../../types';\nimport { geometriesEntryIDsSchema } from './geometries-id';\nimport { GEOMETRIES_SCHEMA_DOC } from './geometries-schema-doc';\nimport { PLACES_SCHEMA_DOC } from './places-schema-doc';\nimport { ROUTES_SCHEMA_DOC } from './routes-schema-doc';\nimport { placesEntryIDsSchema, routesEntryIDsSchema } from './schema';\n\n/**\n * Canonical ordering for {@link EntryDataKind}. Used wherever per-kind doc fragments or\n * field lists are concatenated — keeps the layout stable regardless of the order kinds\n * appear in a scope or enabled-kinds list.\n *\n * @ignore\n */\nexport const ALL_ENTRY_DATA_KINDS = [\n 'places',\n 'routes',\n 'incidents',\n 'customGeometries',\n 'trafficAreaAnalytics',\n 'byod',\n] as const satisfies readonly EntryDataKind[];\n\n/**\n * Per-kind scope envelope. The classifier emits one of these in `toolScopes.<toolName>`\n * to narrow which kinds the tool's per-turn input schema and code-doc mention.\n *\n * @ignore\n */\nexport type EntryKindScope = { kinds: readonly EntryDataKind[] };\n\n/**\n * Build a zod scope schema that only accepts kinds within the supplied enabled set.\n * Mirrors the per-tool scope guard so a classifier picking a disabled kind fails-open\n * (the malformed scope is dropped, full surface stays in place).\n *\n * @ignore\n */\nexport const buildEntryKindScopeSchema = (enabled: readonly EntryDataKind[]) =>\n z.object({\n kinds: z.array(z.enum(enabled as readonly [EntryDataKind, ...EntryDataKind[]])).min(1),\n });\n\n/**\n * `extra` fragment supplied by the tool — appended to each generated input-field\n * description so callers see tool-specific hints alongside the shared metadata.\n *\n * @ignore\n */\ntype FieldBuildOptions = {\n verb: 'analyse' | 'process';\n requiredHint: string;\n};\n\n/**\n * Per-kind metadata. One row per {@link EntryDataKind}. Owns:\n * - field name + label used in schemas and \"at least one of\" messages;\n * - the factory that produces the `*EntryIDs` zod field for a given tool verb;\n * - the one-line sandbox-doc fragment (\"• `places` — merged FeatureCollection …\");\n * - the deep schema-doc fragment (only included when scope commits to this kind);\n * - which recall tool the LLM should call to list available ids.\n *\n * @ignore\n */\nexport type EntryKindMeta = {\n fieldName: string;\n fieldLabel: string;\n buildField: (options: FieldBuildOptions) => z.ZodTypeAny;\n sandboxDoc: string;\n schemaDoc: string;\n recallTool: string;\n};\n\nconst placesField = (options: FieldBuildOptions) =>\n placesEntryIDsSchema({\n verb: options.verb,\n extra: 'Injected as `places` (merged) and `placesByEntry[id]`. Undefined when omitted. ' + options.requiredHint,\n });\n\nconst routesField = (options: FieldBuildOptions) =>\n routesEntryIDsSchema({\n verb: options.verb,\n extra: 'Injected as `routes` (merged) and `routesByEntry[id]`. Undefined when omitted. ' + options.requiredHint,\n });\n\nconst incidentsField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing incidents entries to ${options.verb} (e.g. [\"incidents-0\"]). ` +\n 'Injected as `incidents` (merged) and `incidentsByEntry[id]`. Undefined when omitted. ' +\n 'Use `recallState` to list incident entries. ' +\n options.requiredHint,\n );\n\nconst customGeometriesField = (options: FieldBuildOptions) =>\n geometriesEntryIDsSchema\n .optional()\n .describe(\n 'Tagged polygon sources `{ kind, id }` (see geometriesEntryIDsSchema). ' +\n 'Injected as `geometries`. Undefined when omitted. ' +\n options.requiredHint,\n );\n\nconst trafficAreaAnalyticsField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing traffic-area-analytics entries to ${options.verb} (e.g. [\"tta-0\"]). ` +\n 'Injected as `trafficAreaAnalytics` (merged FeatureCollection of tile/hex regions with metric ' +\n 'properties) and `trafficAreaAnalyticsByEntry[id]`. Undefined when omitted. ' +\n 'Use `recallState` to list available ids. ' +\n options.requiredHint,\n );\n\nconst byodField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing BYOD entries to ${options.verb} (e.g. [\"byod-0\"]) — customer-authored GeoJSON layers. ` +\n 'Injected as `byod` (merged FeatureCollection) and `byodByEntry[id]`. Undefined when omitted. ' +\n 'Use `recallByod` to list available ids. ' +\n options.requiredHint,\n );\n\n/**\n * Single source of truth for the per-kind input vocabulary. Both `analyseData` and\n * `processData` read this map to assemble their input schemas and doc fragments.\n *\n * @ignore\n */\nexport const ENTRY_KIND_META: Record<EntryDataKind, EntryKindMeta> = {\n places: {\n fieldName: 'placesEntryIDs',\n fieldLabel: '`placesEntryIDs`',\n buildField: placesField,\n sandboxDoc:\n '• `places` — merged FeatureCollection over `placesEntryIDs`; `placesByEntry[id]` keeps them separate.',\n schemaDoc: PLACES_SCHEMA_DOC,\n recallTool: 'recallPlaces',\n },\n routes: {\n fieldName: 'routesEntryIDs',\n fieldLabel: '`routesEntryIDs`',\n buildField: routesField,\n sandboxDoc:\n '• `routes` — merged FeatureCollection over `routesEntryIDs`; `routesByEntry[id]` keeps them separate.',\n schemaDoc: ROUTES_SCHEMA_DOC,\n recallTool: 'recallRoutes',\n },\n incidents: {\n fieldName: 'incidentsEntryIDs',\n fieldLabel: '`incidentsEntryIDs`',\n buildField: incidentsField,\n sandboxDoc:\n '• `incidents` — flat `TrafficIncident[]` over `incidentsEntryIDs`; `incidentsByEntry[id]` keeps them separate.',\n schemaDoc: '',\n recallTool: 'recallState',\n },\n customGeometries: {\n fieldName: 'geometriesEntryIDs',\n fieldLabel: '`geometriesEntryIDs`',\n buildField: customGeometriesField,\n sandboxDoc:\n '• `geometries` — mixed Polygon/MultiPolygon array over `geometriesEntryIDs`; every feature carries ' +\n '`properties._source: { kind, id }` so you can filter by role (see `GEOMETRIES_PROPS_DOC`).',\n schemaDoc: GEOMETRIES_SCHEMA_DOC,\n recallTool: 'recallGeometries',\n },\n trafficAreaAnalytics: {\n fieldName: 'trafficAreaAnalyticsEntryIDs',\n fieldLabel: '`trafficAreaAnalyticsEntryIDs`',\n buildField: trafficAreaAnalyticsField,\n sandboxDoc:\n '• `trafficAreaAnalytics` — merged TrafficAreaAnalytics FeatureCollection over `trafficAreaAnalyticsEntryIDs`. ' +\n 'Each feature is a tile / hex region with metric properties on `feature.properties` (e.g. `congestionLevel`, ' +\n '`speed`, `freeFlowSpeed`, `travelTime`, `networkLength`). `trafficAreaAnalyticsByEntry[id]` keeps each ' +\n \"request's collection separate. The collection's top-level `properties` (metrics list, date range) come from \" +\n 'the first entry when ids span multiple requests.',\n schemaDoc: '',\n recallTool: 'recallState',\n },\n byod: {\n fieldName: 'byodEntryIDs',\n fieldLabel: '`byodEntryIDs`',\n buildField: byodField,\n sandboxDoc:\n '• `byod` — merged customer-authored GeoJSON FeatureCollection over `byodEntryIDs`; mixed Point / LineString / ' +\n 'Polygon features are normal (BYOD layers carry whatever the integrator uploaded). `byodByEntry[id]` keeps ' +\n 'each entry separate. Treat `feature.properties` as opaque application-specific data.',\n schemaDoc: '',\n recallTool: 'recallByod',\n },\n};\n\n/**\n * Resolve the set of kinds active for a single per-turn build: the intersection of\n * the agent's enabled kinds and the classifier's per-turn scope. `scope === undefined`\n * means \"no per-turn narrowing — every enabled kind stays in scope\".\n *\n * @ignore\n */\nexport const resolveActiveKinds = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n): readonly EntryDataKind[] =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => enabled.includes(kind) && (!scope || scope.kinds.includes(kind)));\n\n/**\n * Predicate form of {@link resolveActiveKinds} for a single kind. Used when generating\n * tool-specific fields (e.g. analyseData's `monitor`) that should only appear when\n * `incidents` is in the active set.\n *\n * @ignore\n */\nexport const isKindActive = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n kind: EntryDataKind,\n): boolean => enabled.includes(kind) && (!scope || scope.kinds.includes(kind));\n\n/**\n * Slash-joined list of scoped `*EntryIDs` field labels — used in \"At least one of `A` / `B`\n * must be set.\" messages and in per-field hints. Order follows {@link ALL_ENTRY_DATA_KINDS}.\n *\n * @ignore\n */\nexport const buildEntryFieldList = (active: readonly EntryDataKind[]): string =>\n active.map((kind) => ENTRY_KIND_META[kind].fieldLabel).join(' / ');\n\n/**\n * Join the per-kind sandbox-doc fragments in canonical order for the supplied active set.\n * Returns the multi-line \"Each input is …\" block.\n *\n * @ignore\n */\nexport const buildEntryKindSandboxDocs = (active: readonly EntryDataKind[]): string =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => active.includes(kind))\n .map((kind) => ENTRY_KIND_META[kind].sandboxDoc)\n .join('\\n');\n\n/**\n * Join the per-kind deep schema-doc fragments in canonical order for the supplied active\n * set. Empty when no active kind has a schema doc (only places / routes / customGeometries\n * carry one today).\n *\n * @ignore\n */\nexport const buildEntryKindSchemaDocs = (active: readonly EntryDataKind[]): string =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => active.includes(kind))\n .map((kind) => ENTRY_KIND_META[kind].schemaDoc)\n .filter((doc) => doc.length > 0)\n .join('\\n\\n');\n\n/**\n * Build the input-shape Record passed into `z.object({...})`. Both tools share this\n * shape — each kind in the active set contributes its `*EntryIDs` field via\n * {@link EntryKindMeta.buildField}. Tools layer their own per-tool fields onto the\n * resulting record before calling `z.object`.\n *\n * @ignore\n */\nexport const buildEntryIdFields = (\n active: readonly EntryDataKind[],\n options: FieldBuildOptions,\n): Record<string, z.ZodTypeAny> => {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n if (!active.includes(kind)) continue;\n const meta = ENTRY_KIND_META[kind];\n shape[meta.fieldName] = meta.buildField(options);\n }\n return shape;\n};\n\n/**\n * Set of LLM-input objects that contain any `*EntryIDs` field. Used by the shared\n * \"at least one of …\" refine so both tools agree on what counts as \"input present\".\n *\n * `geometriesEntryIDs` carries `GeometriesId[]`; the rest carry `string[]`. We treat\n * either as \"present\" when the array has length, which is what the existing inline\n * refines did.\n *\n * @ignore\n */\nexport const hasAnyEntryIds = (value: Record<string, unknown>): boolean => {\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n const arr = value[ENTRY_KIND_META[kind].fieldName] as unknown[] | undefined;\n if (arr?.length) return true;\n }\n return false;\n};\n\n/**\n * Message used by the shared \"at least one of …\" refine. Lists the active kinds'\n * field labels and the slash-joined recall-tool hints for the same set.\n *\n * @ignore\n */\nexport const buildAtLeastOneEntryIdMessage = (active: readonly EntryDataKind[]): string => {\n const fields = buildEntryFieldList(active);\n const recalls = Array.from(new Set(active.map((kind) => ENTRY_KIND_META[kind].recallTool)))\n .map((tool) => `\\`${tool}\\``)\n .join(' / ');\n return `At least one of ${fields} must be set. Call ${recalls} first to list available ids.`;\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 * 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\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' | 'customGeometries';\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 customGeometries: GeometriesId[];\n};\n\nconst groupByKind = (sources: readonly GeometriesId[]): ParsedByKind => {\n const out: ParsedByKind = { place: [], places: [], ranges: [], customGeometries: [] };\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: 'customGeometries', 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.customGeometries, 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: 'customGeometries' })),\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 'customGeometries':\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 * Cross-slice input prep for the merged `analyseData` / `processData` tools.\n *\n * Per-type tools (analyse-places, process-routes, …) default to \"use the last\n * entry of this slice\" when their id array is omitted. The merged tools don't\n * do that: each input kind is purely opt-in. If `placesEntryIDs` is missing,\n * the sandbox sees `places === undefined`; if it's `[]`, same. The LLM\n * explicitly lists every kind it wants to consider.\n *\n * The helper resolves every requested kind, builds the sandbox-facing\n * `merged + byEntry` views, and returns the resolved entries so the analyse\n * path can attach an analysis to each contributing entry afterwards.\n */\n\nimport type { Place, PolygonFeature, Route, TrafficAreaAnalytics, TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection as GeoJSONFeatureCollection } from 'geojson';\nimport type {\n BYODEntry,\n PlacesEntry,\n RoutesEntry,\n TrafficAreaAnalyticsEntry,\n TrafficIncidentsEntry,\n} from '../../state';\nimport type { ToolState } from '../../types';\nimport type { GeometriesId } from './geometries-id';\nimport { type AffectedEntry, collectInputGeometries, type SkippedSource } from './state-inputs';\n\ntype FeatureCollection<F> = { type: 'FeatureCollection'; features: F[] };\n\n/** Inputs the LLM passes through the tool schema. All optional, but at least one must be set. */\nexport type MultiInputIds = {\n placesEntryIDs?: readonly string[];\n routesEntryIDs?: readonly string[];\n incidentsEntryIDs?: readonly string[];\n geometriesEntryIDs?: readonly GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: readonly string[];\n byodEntryIDs?: readonly string[];\n};\n\n/** Resolved entries, kept for the attach step in analyseData. Empty arrays for kinds the caller didn't request. */\nexport type ResolvedEntries = {\n places: PlacesEntry[];\n routes: RoutesEntry[];\n incidents: TrafficIncidentsEntry[];\n trafficAreaAnalytics: TrafficAreaAnalyticsEntry[];\n byod: BYODEntry[];\n};\n\n/** Sandbox-facing inputs. Each field is `undefined` when its kind wasn't requested. */\nexport type SandboxInputs = {\n places: FeatureCollection<Place> | undefined;\n placesByEntry: Record<string, FeatureCollection<Place>> | undefined;\n routes: FeatureCollection<Route> | undefined;\n routesByEntry: Record<string, FeatureCollection<Route>> | undefined;\n incidents: TrafficIncident[] | undefined;\n incidentsByEntry: Record<string, TrafficIncident[]> | undefined;\n geometries: PolygonFeature[] | undefined;\n /**\n * Merged TrafficAreaAnalytics FeatureCollection — every feature is a tile/hex region with\n * the metric properties from the SDK (`congestionLevel`, `speed`, `travelTime`, …). When\n * multiple entries are requested, their features are concatenated; `trafficAreaAnalyticsByEntry`\n * keeps them separate.\n */\n trafficAreaAnalytics: TrafficAreaAnalytics | undefined;\n trafficAreaAnalyticsByEntry: Record<string, TrafficAreaAnalytics> | undefined;\n /**\n * Merged BYOD FeatureCollection. Each requested entry contributes its features; mixed\n * geometry types are normal (BYOD layers can be Point / LineString / Polygon). When multiple\n * entries are requested they're concatenated; `byodByEntry` keeps them separate.\n */\n byod: GeoJSONFeatureCollection | undefined;\n byodByEntry: Record<string, GeoJSONFeatureCollection> | undefined;\n};\n\n/** Geometries metadata — only populated when `geometriesEntryIDs` was passed. */\nexport type GeometriesMeta = {\n affectedEntries: AffectedEntry[];\n sourceIds: GeometriesId[];\n skipped: SkippedSource[];\n};\n\nexport type PreparedInputs = {\n resolved: ResolvedEntries;\n sandbox: SandboxInputs;\n geometries: GeometriesMeta;\n};\n\n/**\n * Resolve every requested input kind, build sandbox views, and surface the\n * resolved entries to the caller. The caller (analyseData / processData) is\n * responsible for enforcing \"at least one input must be set\" via the zod\n * schema; this helper enforces it as a safety net.\n *\n * @ignore\n */\n// Per-id-array hint used by the \"at least one input must be set\" guard. Kept inline so the\n// guard's error message stays accurate when new kinds get added.\nconst ID_FIELDS = [\n 'placesEntryIDs',\n 'routesEntryIDs',\n 'incidentsEntryIDs',\n 'geometriesEntryIDs',\n 'trafficAreaAnalyticsEntryIDs',\n 'byodEntryIDs',\n] as const;\n\nconst hasAnyInput = (ids: MultiInputIds): boolean => ID_FIELDS.some((field) => (ids[field]?.length ?? 0) > 0);\n\n// Pull the slice-resolution stack out of `prepareMultiInputs` so each function's cognitive\n// complexity stays under the SonarQube cap. Returns the resolved entries grouped by kind, or\n// the first per-slice resolution error encountered.\nconst resolveAllSlices = (ids: MultiInputIds, state: ToolState): { value: ResolvedEntries } | { error: string } => {\n const placesResult = resolveSliceEntries(ids.placesEntryIDs, state.places, {\n missingId: (id) => `No places entry with id \"${id}\". Use recallPlaces to list available IDs.`,\n });\n if ('error' in placesResult) return placesResult;\n\n const routesResult = resolveSliceEntries(ids.routesEntryIDs, state.routing, {\n missingId: (id) => `No routes entry with id \"${id}\". Use recallRoutes to list available IDs.`,\n });\n if ('error' in routesResult) return routesResult;\n\n const incidentsResult = resolveSliceEntries(ids.incidentsEntryIDs, state.trafficIncidents, {\n missingId: (id) => `No incidents entry with id \"${id}\". Call getTrafficIncidents first.`,\n });\n if ('error' in incidentsResult) return incidentsResult;\n\n const trafficAreaAnalyticsResult = resolveSliceEntries(\n ids.trafficAreaAnalyticsEntryIDs,\n state.trafficAreaAnalytics,\n {\n missingId: (id) => `No traffic-area-analytics entry with id \"${id}\". Call getTrafficAreaAnalytics first.`,\n },\n );\n if ('error' in trafficAreaAnalyticsResult) return trafficAreaAnalyticsResult;\n\n const byodResult = resolveSliceEntries(ids.byodEntryIDs, state.byod, {\n missingId: (id) => `No BYOD entry with id \"${id}\". Use recallByod to list available IDs.`,\n });\n if ('error' in byodResult) return byodResult;\n\n return {\n value: {\n places: placesResult.value,\n routes: routesResult.value,\n incidents: incidentsResult.value,\n trafficAreaAnalytics: trafficAreaAnalyticsResult.value,\n byod: byodResult.value,\n },\n };\n};\n\nexport const prepareMultiInputs = async (\n ids: MultiInputIds,\n state: ToolState,\n): Promise<{ value: PreparedInputs } | { error: string }> => {\n if (!hasAnyInput(ids)) {\n return {\n error:\n 'At least one of `placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, ' +\n '`geometriesEntryIDs`, `trafficAreaAnalyticsEntryIDs`, `byodEntryIDs` must be set. ' +\n 'Use `recallPlaces` / `recallRoutes` / `recallGeometries` / `recallByod` / `recallState` to list available ids.',\n };\n }\n\n const resolved = resolveAllSlices(ids, state);\n if ('error' in resolved) return resolved;\n const {\n places: placesEntries,\n routes: routesEntries,\n incidents: incidentsEntries,\n trafficAreaAnalytics: trafficAreaAnalyticsEntries,\n byod: byodEntries,\n } = resolved.value;\n\n let geometries: PolygonFeature[] | undefined;\n let geometriesMeta: GeometriesMeta = { affectedEntries: [], sourceIds: [], skipped: [] };\n if (ids.geometriesEntryIDs?.length) {\n const collected = await collectInputGeometries(ids.geometriesEntryIDs, state);\n if ('error' in collected) return collected;\n geometries = collected.value.geometries;\n geometriesMeta = {\n affectedEntries: collected.value.affectedEntries,\n sourceIds: collected.value.contributingSourceIds,\n skipped: collected.value.skipped,\n };\n }\n\n return {\n value: {\n resolved: {\n places: placesEntries,\n routes: routesEntries,\n incidents: incidentsEntries,\n trafficAreaAnalytics: trafficAreaAnalyticsEntries,\n byod: byodEntries,\n },\n sandbox: {\n places: placesEntries.length\n ? { type: 'FeatureCollection', features: placesEntries.flatMap((e) => e.places) }\n : undefined,\n placesByEntry: placesEntries.length\n ? Object.fromEntries(\n placesEntries.map((e) => [\n e.id,\n { type: 'FeatureCollection', features: [...e.places] } as FeatureCollection<Place>,\n ]),\n )\n : undefined,\n routes: routesEntries.length\n ? { type: 'FeatureCollection', features: routesEntries.flatMap((e) => e.data.features) }\n : undefined,\n routesByEntry: routesEntries.length\n ? Object.fromEntries(\n routesEntries.map((e) => [\n e.id,\n {\n type: 'FeatureCollection',\n features: [...e.data.features],\n } as FeatureCollection<Route>,\n ]),\n )\n : undefined,\n incidents: incidentsEntries.length ? incidentsEntries.flatMap((e) => e.data) : undefined,\n incidentsByEntry: incidentsEntries.length\n ? Object.fromEntries(incidentsEntries.map((e) => [e.id, [...e.data]]))\n : undefined,\n geometries,\n trafficAreaAnalytics: trafficAreaAnalyticsEntries.length\n ? mergeTrafficAreaAnalytics(trafficAreaAnalyticsEntries.map((e) => e.data))\n : undefined,\n trafficAreaAnalyticsByEntry: trafficAreaAnalyticsEntries.length\n ? Object.fromEntries(trafficAreaAnalyticsEntries.map((e) => [e.id, e.data]))\n : undefined,\n byod: byodEntries.length\n ? {\n type: 'FeatureCollection',\n features: byodEntries.flatMap((e) => e.data.features),\n }\n : undefined,\n byodByEntry: byodEntries.length\n ? Object.fromEntries(byodEntries.map((e) => [e.id, e.data]))\n : undefined,\n },\n geometries: geometriesMeta,\n },\n };\n};\n\n// Concatenate features from N TrafficAreaAnalytics entries into a single FeatureCollection.\n// Collection-level `properties` (metrics list, date range) are taken from the first entry —\n// the LLM should treat the merged collection as feature-level when ids span different requests.\nconst mergeTrafficAreaAnalytics = (entries: readonly TrafficAreaAnalytics[]): TrafficAreaAnalytics => ({\n type: 'FeatureCollection',\n properties: entries[0].properties,\n features: entries.flatMap((e) => e.features),\n});\n\nconst resolveSliceEntries = <E extends { id: string }>(\n ids: readonly string[] | undefined,\n slice: { entries: readonly E[] },\n hints: { missingId: (id: string) => string },\n): { value: E[] } | { error: string } => {\n if (!ids?.length) return { value: [] };\n const out: E[] = [];\n for (const id of ids) {\n const found = slice.entries.find((entry) => entry.id === id);\n if (!found) return { error: hints.missingId(id) };\n out.push(found);\n }\n return { value: out };\n};\n\n/**\n * LLM-facing doc string that spells out which turf/h3 operations bridge across\n * input kinds. The merged tools both expose `places` (Points) / `routes`\n * (LineStrings) / `incidents` (Points + LineStrings) / `geometries` (Polygons\n * / MultiPolygons) / `trafficAreaAnalytics` (Polygon tiles with metrics) to\n * the same sandbox — but the model frequently assumes those slots are\n * isolated and gives up. Including this in the `code` field description\n * unblocks the common cross-kind work.\n *\n * @ignore\n */\nexport const CROSS_KIND_OPS_DOC =\n 'CROSS-KIND OPS — `turf` and `h3` operate on EVERY feature regardless of which input slot it came from. ' +\n 'You can freely combine `places` (Points), `routes` (LineStrings), `incidents` (Point or LineString), ' +\n '`geometries` (Polygon / MultiPolygon), and `trafficAreaAnalytics` (Polygon tiles with `properties` like ' +\n '`congestionLevel` / `speed` / `travelTime` / `freeFlowSpeed`) in the same call. Common bridges:\\n' +\n '• Point ↔ LineString — `turf.pointToLineDistance(point, lineFeature, { units: \"meters\" })`, ' +\n '`turf.nearestPointOnLine(line, point)`.\\n' +\n '• Point ↔ Polygon — `turf.booleanPointInPolygon(point, poly)`, ' +\n '`turf.pointsWithinPolygon(turf.featureCollection(points), poly)`.\\n' +\n '• LineString ↔ Polygon — `turf.lineIntersect(line, poly)`, `turf.booleanCrosses(line, poly)`, ' +\n '`turf.lineSplit(line, poly)`.\\n' +\n '• Polygon ↔ Polygon — `turf.union/intersect/difference(turf.featureCollection([a, b]))` (Turf v7 takes a single collection).\\n' +\n '• Point/LineString ↔ trafficAreaAnalytics tile — `turf.booleanPointInPolygon(p, tile)` for points; ' +\n '`turf.lineIntersect(routeLine, tile)` for routes; iterate `trafficAreaAnalytics.features` to find which ' +\n 'tile a place/route segment falls into and read its metric (`tile.properties.congestionLevel`, etc.).\\n' +\n '• Buffer to bridge kinds — `turf.buffer(anyFeature, meters/1000, { units: \"kilometers\" })` turns a point or ' +\n 'line into a polygon you can then filter against.\\n' +\n '• Bbox of anything — `turf.bbox(featureOrCollection)`.\\n' +\n '• Centroid for Point↔shape distance — `turf.centroid(polygonOrLine).geometry.coordinates`.\\n' +\n '• h3 — `h3.latLngToCell(lat, lng, res)` for any Point; `h3.polygonToCells(poly.geometry.coordinates, res)` ' +\n 'for any Polygon; for a LineString iterate `geometry.coordinates` and bin each `[lng, lat]`.\\n' +\n 'Note: arguments to turf functions are GeoJSON Features — pass the whole feature (`places.features[i]`, ' +\n '`routes.features[0]`, `geometries[i]`, `incidents[i]`, `trafficAreaAnalytics.features[i]`), not the bare ' +\n '`geometry.coordinates` (a few helpers accept coords but most expect Features). Always check the function ' +\n 'signature at https://turfjs.org/docs/.';\n\n/** Names injected into the sandbox by analyseData / processData, in fixed order. */\nexport const MULTI_INPUT_SANDBOX_PARAMS = [\n 'places',\n 'placesByEntry',\n 'routes',\n 'routesByEntry',\n 'incidents',\n 'incidentsByEntry',\n 'geometries',\n 'trafficAreaAnalytics',\n 'trafficAreaAnalyticsByEntry',\n 'byod',\n 'byodByEntry',\n 'h3',\n 'turf',\n] as const;\n\n/**\n * Pack `prepared.sandbox` into the positional argument array expected by\n * `runSandboxedFn`, in the same order as {@link MULTI_INPUT_SANDBOX_PARAMS}.\n * Trailing `h3` and `turf` are supplied by the caller.\n *\n * @ignore\n */\nexport const packSandboxArgs = (sandbox: SandboxInputs, libs: { h3: unknown; turf: unknown }): readonly unknown[] => [\n sandbox.places,\n sandbox.placesByEntry,\n sandbox.routes,\n sandbox.routesByEntry,\n sandbox.incidents,\n sandbox.incidentsByEntry,\n sandbox.geometries,\n sandbox.trafficAreaAnalytics,\n sandbox.trafficAreaAnalyticsByEntry,\n sandbox.byod,\n sandbox.byodByEntry,\n libs.h3,\n libs.turf,\n];\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\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 const injectedList = injected.map((name) => `\\`${name}\\``).join(', ');\n return (\n `In scope: ${injectedList} — injected as parameters; use directly. ` +\n 'No `require` / `import` / `await import` (body is an AsyncFunction, not a module — such lines are stripped silently). ' +\n '`h3` = h3-js (hex math). `turf` = @turf/turf v7 (verify names at https://turfjs.org/docs/). ' +\n 'TURF INPUTS: pass a Feature or FeatureCollection — never a bare Array (throws \"Unknown Geometry Type\"). ' +\n 'FC-shaped inputs (`places`, `routes`, `trafficAreaAnalytics`, `byod`) → pass directly: `turf.bbox(places)`. ' +\n 'Array inputs (`incidents`, `geometries`, `places.features`, …) → iterate per-feature ' +\n '(`for (const f of places.features) turf.X(f)`) or wrap with `turf.featureCollection([...])`. ' +\n 'Never `.map(f => f.geometry.coordinates)` / `.flatMap(f => f.geometry.coordinates)` to feed turf — ' +\n 'that strips the feature wrapper turf reads and throws \"coordinates must be an Array\" (or worse, mixes ' +\n 'Point/LineString shapes silently). Pass the whole feature and let turf read the geometry: ' +\n '`turf.pointToLineDistance(point, lineFeature, { units: \"meters\" })`. `incidents` mixes Point + LineString ' +\n 'features — guard with `if (inc.geometry.type === \"LineString\")` before line-only ops. ' +\n 'TURF v7 SET OPS: `union`/`intersect`/`difference` take ONE FeatureCollection — ' +\n '`turf.difference(turf.featureCollection([outer, inner]))`. The v6 two-arg form throws. ' +\n 'PERF: for large-N nearest/within queries, pre-bucket points with ' +\n '`h3.latLngToCell(lat, lng, res)` + `h3.gridDisk(cell, k)` and only run turf on same-/neighbour-cell ' +\n 'pairs — turns O(N·M) into ~O(N+M). Pick `res` so each cell ≈ your query radius. ' +\n 'No spatial search or HTTP — call `discoverPlaces` BEFORE this tool for new places. ' +\n 'Top-level `await`/locals/consts fine. End with `return …;`.'\n );\n};\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 // RHS forms we recognise as redeclarations of injected identifiers:\n // require('…') / import('…') / await import('…') — module-system reaches\n // arguments[N].turf / arguments[N]['turf'] — reaching for the same param off `arguments`\n // Anything else (e.g. `const turf = computeTurf()`) stays untouched so we never corrupt\n // genuine user code that happens to shadow an injected name.\n const rhs =\n String.raw`(?:(?:await\\s+)?(?:require|import)\\s*\\(` +\n String.raw`|arguments\\s*\\[\\s*\\d+\\s*\\]\\s*(?:\\.\\w+|\\[\\s*['\"]\\w+['\"]\\s*\\]))`;\n const pattern = new RegExp(\n String.raw`^[ \\t]*(?:const|let|var)[ \\t]+(?:${names})[ \\t]*=[ \\t]*${rhs}[^\\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: /coordinates must be an Array|Unknown Geometry Type/i,\n hint: 'turf was handed a raw coordinate (or a bare Array of features) instead of a Feature / FeatureCollection. Do NOT extract `.geometry.coordinates` with `.map` / `.flatMap` and feed the result to turf — pass the WHOLE feature: `turf.pointToLineDistance(pointFeature, lineFeature, { units: \"meters\" })`. `incidents` is an Array of mixed Point + LineString features; guard with `if (inc.geometry.type === \"LineString\")` before line-only ops. For FC-shaped inputs (`places`, `routes`, `trafficAreaAnalytics`, `byod`), pass the value directly (`turf.bbox(places)`); never `turf.bbox(places.features)`.',\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 turf = await import(...)` / `const turf = arguments[0].turf` / `const places = ...`. Those names are already in scope as function parameters; drop the redeclaration line and use them directly (`turf.bbox(places)`).',\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 the tool inputs (`placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`): each is exposed via the injected sandbox args (`places`, `routes`, `incidents`, `geometries`, plus their `*ByEntry` per-entry views). 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\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 * 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 `analyseData` (one-shot or `monitor`-rerun); `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 `analyseData` with `monitor: { entryId }`;\n * replayed by 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 * `analyseData`'s `monitor` path 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 { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\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 this._entries = await collapseHistoryToLatest(this._entries, (entry) => this._teardownEntry(entry), {\n parallel: true,\n });\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 async addIncidentsEntry(\n data: TrafficIncident[],\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n label: string,\n sampledAt: number,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n // `_teardownEntry` tears down a per-entry monitor + module — each instance is\n // independent, so `parallel: true` is race-safe and faster than serial.\n await hideAllEntries(this._entries, (entry) => this._teardownEntry(entry), { parallel: true });\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 = pickUniqueEntryId(\n explicitId ?? `incidents-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\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?._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 // 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 /**\n * Hide every traffic-incidents entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\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 { BYODState } from './byod';\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 { type BYODEntry, type BYODSource, BYODState, type BYODStateEvents } from './byod';\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 history of entries and can be driven into `single` or `multiple`\n * mode. Every {@link ToolState} slice with `setEntryMode` implements this shape, which lets\n * callers (the `setEntryMode` tool, `createMapAgent`'s `dataEntries` option, …) handle the\n * whole family uniformly without listing slice names.\n *\n * @group Agent Toolkit\n */\nexport interface EntryModeSlice {\n readonly entryMode: EntryMode;\n readonly entries: readonly { readonly id: string }[];\n setEntryMode(mode: EntryMode): Promise<void>;\n}\n\n/**\n * Data-entry kind names — the vocabulary used by `dataEntries` on `createMapAgent`. Superset\n * of the analyse/process input kinds (`EntryDataKind`) — adds `ranges`, which has its own\n * entry-mode-supporting slice but is not a top-level input kind for those tools.\n *\n * The kind names are LLM-facing (`routes`, `incidents`) rather than slice names (`routing`,\n * `trafficIncidents`); the slice mapping is internal — see {@link DATA_ENTRY_KIND_TO_SLICE}.\n *\n * @group Agent Toolkit\n */\nexport type DataEntryKind =\n | 'places'\n | 'routes'\n | 'incidents'\n | 'customGeometries'\n | 'trafficAreaAnalytics'\n | 'byod'\n | 'ranges';\n\n/**\n * Per-kind configuration accepted by `createMapAgent`'s `dataEntries` option.\n *\n * @group Agent Toolkit\n */\nexport type DataEntryConfig = {\n /**\n * When `false`, the kind is removed from the agent's tool surface entirely:\n * - the kind disappears from `analyseData` / `processData` scope and input fields;\n * - related recall / display / fetch tools are dropped from the registry\n * (see {@link TOOLS_BY_DATA_ENTRY_KIND}).\n *\n * Default: `true`.\n */\n enabled?: boolean;\n /**\n * Initial entry-display policy for the underlying slice. `single` keeps only the latest\n * entry on the map (others auto-hide); `multiple` (default) lets several coexist.\n */\n entryMode?: EntryMode;\n};\n\n/**\n * Mapping from {@link DataEntryKind} (LLM-facing name) to the {@link ToolState} slice key\n * that owns the entries for that kind. Used by `createMapAgent` to apply `entryMode` from\n * `dataEntries[kind].entryMode` to the right slice.\n *\n * @group Agent Toolkit\n */\nexport const DATA_ENTRY_KIND_TO_SLICE = {\n places: 'places',\n routes: 'routing',\n incidents: 'trafficIncidents',\n customGeometries: 'customGeometries',\n trafficAreaAnalytics: 'trafficAreaAnalytics',\n byod: 'byod',\n ranges: 'ranges',\n} as const satisfies Record<DataEntryKind, EntryModeSliceName>;\n\n/**\n * Default tool names that should be dropped from the registry when a {@link DataEntryKind}\n * is disabled. Only default-tool names are listed here — user-supplied custom tools are\n * never filtered automatically; integrators that want to gate their own tools on enabled\n * kinds can read `dataEntries` themselves.\n *\n * Tools that span multiple kinds (`clearMap`, `recallState`, `setEntryMode`, …) are\n * intentionally absent — they keep working regardless of which kinds are disabled.\n *\n * @group Agent Toolkit\n */\nexport const TOOLS_BY_DATA_ENTRY_KIND: Record<DataEntryKind, readonly string[]> = {\n places: ['recallPlaces', 'updatePlacesDisplay', 'locatePlace', 'discoverPlaces'],\n routes: [\n 'recallRoutes',\n 'updateRoutesDisplay',\n 'setRoute',\n 'addWaypointsToRoute',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n 'getCurrentWaypoints',\n 'updateWaypointsDisplay',\n ],\n incidents: [\n 'getTrafficIncidents',\n 'focusIncidents',\n 'startTrafficIncidentsMonitor',\n 'stopTrafficIncidentsMonitor',\n 'getShownTileIncidents',\n ],\n customGeometries: ['recallGeometries'],\n trafficAreaAnalytics: ['getTrafficAreaAnalytics', 'queryTrafficAnalytics', 'updateTrafficAreaAnalyticsDisplay'],\n byod: ['recallByod', 'addByodLayer', 'updateByodDisplay'],\n ranges: ['findReachableAreas', 'recallRanges'],\n};\n\n/**\n * Compile-time union of {@link ToolState} keys whose slice supports {@link EntryMode}.\n * Names match the {@link ToolState} keys exactly — `routing` (not `routes`),\n * `customGeometries` (not `geometries`) — so the slice name in the tool API never drifts\n * from the property on `state`.\n *\n * Auto-grows when a new slice implements `setEntryMode`: the union picks up the new key,\n * and the runtime list {@link ENTRY_MODE_SLICE_NAMES} fails to type-check until the key\n * is added there too.\n *\n * @group Agent Toolkit\n */\nexport type EntryModeSliceName = {\n [K in keyof ToolState]: ToolState[K] extends EntryModeSlice ? K : never;\n}[keyof ToolState];\n\n/**\n * Runtime enumeration of slice names that support entry mode. The `satisfies` clause\n * makes drift a compile error: when a new {@link ToolState} slice gains `setEntryMode`,\n * this constant has to be updated before the package will build.\n *\n * @group Agent Toolkit\n */\nexport const ENTRY_MODE_SLICE_NAMES = Object.keys({\n places: true,\n routing: true,\n ranges: true,\n customGeometries: true,\n byod: true,\n trafficAreaAnalytics: true,\n trafficIncidents: true,\n} satisfies Record<EntryModeSliceName, true>) as readonly EntryModeSliceName[];\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 * State slice that renders something on the map and can hide ALL of it in one call. The\n * {@link clearMap} tool walks every slice that implements this interface so adding a new\n * slice (e.g. a future overlay) auto-grows the tool's surface — no hand-edits required.\n *\n * `clearShown` is map-only: it hides what's currently rendered but leaves the slice's\n * history (`entries`) intact. Use the slice's `reset()` for a full wipe.\n *\n * @group Agent Toolkit\n */\nexport interface ClearableMapSlice {\n clearShown(): Promise<void>;\n}\n\n/**\n * Compile-time union of {@link ToolState} keys whose slice implements {@link ClearableMapSlice}.\n * Same pattern as {@link EntryModeSliceName}: the union auto-grows when a slice gains\n * `clearShown`, and the runtime list {@link CLEARABLE_SLICE_NAMES} fails to type-check until\n * the key is added there too.\n *\n * @group Agent Toolkit\n */\nexport type ClearableSliceName = {\n [K in keyof ToolState]: ToolState[K] extends ClearableMapSlice ? K : never;\n}[keyof ToolState];\n\n/**\n * Runtime enumeration of slice names that support `clearShown`. The `satisfies` clause\n * makes drift a compile error: when a new {@link ToolState} slice implements\n * {@link ClearableMapSlice}, this constant has to be updated before the package will build.\n *\n * @group Agent Toolkit\n */\nexport const CLEARABLE_SLICE_NAMES = Object.keys({\n places: true,\n routing: true,\n ranges: true,\n customGeometries: true,\n byod: true,\n trafficAreaAnalytics: true,\n trafficIncidents: true,\n} satisfies Record<ClearableSliceName, true>) as readonly ClearableSliceName[];\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 byod: new BYODState(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 / updateWaypointsDisplay only when displaying results already in context from a previous step.\n\nSESSION STATE & ENTRIES:\n- Seven append-only histories live in the session: \\`places\\`, \\`routes\\`, \\`ranges\\`, \\`customGeometries\\`, \\`trafficAreaAnalytics\\`, \\`trafficIncidents\\`, \\`byod\\`. Each tool call that produces results stores a new ENTRY (id like \\`places-3\\`, \\`routes-1\\`, \\`byod-0\\`) — these ids stay stable and are accepted by every show / recall / \\`processData\\` / \\`analyseData\\` tool via the matching \\`*EntryIDs\\` field.\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\\` / \\`recallGeometries\\` / \\`recallByod\\` (traffic slices surface via \\`recallState\\`). 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';\nimport type { FeatureFlags } from '../types';\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\nconst basePlaceShape = {\n id: z.string().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\n// Experimental-only fields propagated from the exploration-search backend's area-tag\n// pipeline. Area data currently populated only in DE / NL / FR. Gated by `experimentalSearch`\n// so the LLM doesn't see them on tool runs that can't produce them.\nconst experimentalPlaceShape = {\n areaId: z\n .string()\n .optional()\n .describe(\n 'Id of the small area polygon this place sits in (few km², not a whole municipality). ' +\n 'Round-trip into `discoverPlaces.where.areaId` for \"what else is in this same area?\". ' +\n 'Only set on explorationSearch results in DE / NL / FR.',\n ),\n areaCountry: z.string().optional().describe('ISO 3166-1 alpha-2 country code of the surrounding area polygon.'),\n areaTags: z\n .array(z.string())\n .optional()\n .describe(\n 'Tokens describing the surrounding small area (few km², not a whole municipality) ' +\n '(e.g. \"coastal\", \"walkable\", \"transit_connected\"). Useful for downstream `areaTags` filters.',\n ),\n};\n\n/**\n * Build a flag-aware compact place summary schema. When `experimentalSearch`\n * is true, the schema also documents the exploration-search-only `areaId` /\n * `areaCountry` / `areaTags` fields propagated from the surrounding small-area polygon.\n */\nexport const buildPlaceOutputSchema = (flags: FeatureFlags) =>\n z\n .object(flags.experimentalSearch ? { ...basePlaceShape, ...experimentalPlaceShape } : basePlaceShape)\n .describe('Compact place summary');\n\n/**\n * Build a flag-aware compact places-collection summary. Mirrors\n * {@link buildPlaceOutputSchema} for `features[]`.\n */\nexport const buildPlacesOutputSchema = (flags: FeatureFlags) =>\n z\n .object({\n count: z.number(),\n features: z.array(buildPlaceOutputSchema(flags)),\n })\n .describe('Compact places summary');\n\n/**\n * Token-efficient summary of a single place — default flags\n * (`experimentalSearch: false`).\n */\nexport const placeOutputSchema = buildPlaceOutputSchema({});\n\n/**\n * Token-efficient summary of a places collection — default flags\n * (`experimentalSearch: false`).\n */\nexport const placesOutputSchema = buildPlacesOutputSchema({});\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\n .string()\n .describe('Section ID — obtain from `recallRoutes({ id })` detail mode or from `analyseData` over routes.'),\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 type { z } from 'zod';\nimport { createHelpTool } from './tools/utilities';\nimport type { EntryDataKind, 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 scopePrompt: entry.scopePrompt,\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 * Registry entry for a scopable tool — captured at setup time so {@link prepareStep}\n * can rebuild the tool's description + inputSchema per turn from the classifier's\n * `toolScopes` output.\n *\n * @ignore\n */\nexport type ScopableToolInfo = {\n /**\n * Validate `rawScope` against the tool's scopeSchema and re-invoke its builder.\n * Returns `null` when either the parse or the rebuild fails (fail-open — callers\n * leave the default surface in place rather than crashing the step). The original\n * builder, scope schema, and any other build options are captured in the closure\n * so callers stay decoupled from the underlying validation/build mechanics.\n */\n applyScope: (rawScope: unknown) => { description: string; inputSchema: z.ZodType } | null;\n /** Default (full-surface) values, used to restore between turns. */\n defaultDescription: string;\n defaultInputSchema: z.ZodType;\n};\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 * When a builder produces a {@link ToolEntry} carrying a `scopeSchema`, the\n * builder reference is retained in `scopableTools` so {@link prepareStep} can\n * re-invoke it with a per-turn scope and mutate the live tool object.\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?: {\n outputSchemas?: boolean;\n featureFlags?: ToolBuildOptions['featureFlags'];\n enabledDataKinds?: readonly EntryDataKind[];\n },\n): {\n tools: Record<string, Tool>;\n toolsMetadata: Record<string, ToolMetadata>;\n scopableTools: Record<string, ScopableToolInfo>;\n} => {\n const includeOutputSchema = options?.outputSchemas !== false;\n const buildOptions: ToolBuildOptions = {\n featureFlags: options?.featureFlags,\n enabledDataKinds: options?.enabledDataKinds,\n };\n const tools: Record<string, Tool> = {};\n const toolsMetadata: Record<string, ToolMetadata> = {};\n const scopableTools: Record<string, ScopableToolInfo> = {};\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 // Builder + scopeSchema is the precondition for per-turn rebuilding. A static\n // ToolEntry with a scopeSchema isn't rebuildable because there's no factory to\n // re-invoke; we silently treat it as a non-scopable tool to keep the contract simple.\n if (typeof entryOrBuilder === 'function' && entry.scopeSchema) {\n const builder = entryOrBuilder;\n const scopeSchema = entry.scopeSchema;\n scopableTools[name] = {\n applyScope: (rawScope) => {\n const parsed = scopeSchema.safeParse(rawScope);\n if (!parsed.success) return null;\n try {\n const rebuilt = builder({ ...buildOptions, scope: parsed.data });\n return { description: rebuilt.description, inputSchema: rebuilt.inputSchema };\n } catch {\n return null;\n }\n },\n defaultDescription: entry.description,\n defaultInputSchema: entry.inputSchema,\n };\n }\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, scopableTools };\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 `updateRoutesDisplay({ mainColor })`.';\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 const scope = entry.scopePrompt ? ` SCOPE: ${entry.scopePrompt}` : '';\n return `${name}${classificationPrompt}${depends}${scope}`;\n};\n\n// Names of tools whose metadata declares a `scopePrompt` — used to surface the per-turn\n// scope contract in the classifier system prompt.\nconst scopableToolNames = (toolsMetadata: Record<string, ToolMetadata>): string[] =>\n Object.entries(toolsMetadata)\n .filter(([, meta]) => meta.scopePrompt)\n .map(([name]) => name);\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 const scopable = scopableToolNames(toolsMetadata);\n const scopeContract = scopable.length\n ? [\n '',\n 'TOOL SCOPES — MANDATORY when selecting a scopable tool:',\n `The following tools REQUIRE a scope entry in \\`toolScopes\\` whenever they appear in \\`tools\\`: ${scopable.join(', ')}.`,\n \"For each picked scopable tool, emit `toolScopes[<name>]` with the per-tool object shape described in its SCOPE hint above. Don't omit. Don't leave it implicit.\",\n 'If the user query truly needs all input kinds the tool supports, still emit the scope explicitly — list every kind. Never skip the scope entry when picking the tool.',\n ]\n : [];\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 ...scopeContract,\n ].join('\\n');\n};\n\n// Per-call output schema. Scopable tools (those whose metadata carries a `scopePrompt`) must\n// appear in `toolScopes` when selected — the LLM cannot opt out.\n//\n// IMPORTANT: We can't use `z.record(z.string(), z.unknown())` for `toolScopes`. Azure / OpenAI\n// strict structured-output mode requires every object property to be declared explicitly with\n// `additionalProperties: false`, and `z.record` doesn't translate to that. Instead we build an\n// explicit `z.object({ [scopableToolName]: scopeShape.optional(), … })` with one optional field\n// per known scopable tool. The value shape is loose (`{ kinds: string[] }`) at the classifier\n// level — final validation against the tool's real `scopeSchema` happens in `prepareStep`.\n//\n// The `superRefine` then enforces \"if you picked tool X and X is scopable, you must emit\n// toolScopes[X]\". The AI SDK's structured-output retry loop sees that as a typed validation error\n// and re-runs with the message pointing at the missing entry.\nconst looseScopeShape = z.object({\n kinds: z\n .array(z.string())\n .min(1)\n .describe(\"Entry-data kinds the turn touches (one of the kinds listed in the tool's SCOPE hint).\"),\n});\n\n// Human-readable hint of the expected response shape, embedded in the system prompt. The model\n// produces any JSON (we use `output: 'no-schema'` to avoid attaching a strict schema to the API\n// request); this hint plus the SCOPE blocks already in the system prompt give it enough structure\n// to consistently emit something our zod schema can validate.\nconst buildExpectedShapeHint = (scopableNames: ReadonlySet<string>): string => {\n if (scopableNames.size === 0) {\n return '{ \"tools\": [\"toolName\", ...] }';\n }\n const scopeKeys = [...scopableNames].map((n) => ` \"${n}\": { \"kinds\": [...] } | null`).join(',\\n');\n return `{\n \"tools\": [\"toolName\", ...],\n \"toolScopes\": {\n${scopeKeys}\n }\n}\nFor every scopable tool you LIST in \"tools\", the matching \"toolScopes\" entry MUST be a non-null object \\`{ \"kinds\": [...] }\\`. For scopable tools you DON'T pick, set the corresponding \"toolScopes\" entry to \\`null\\`.`;\n};\n\nconst buildClassifySchema = (scopableNames: ReadonlySet<string>) => {\n const base = 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 if (scopableNames.size === 0) {\n return base;\n }\n\n // Each scopable tool gets a `.nullable()` field (not `.optional()`). Reason: Azure / OpenAI\n // strict structured-output mode requires every property to be in the JSON schema's `required`\n // list — `optional` maps to \"may be absent\" which strict mode rejects. `nullable` lets the\n // LLM emit `null` for unused entries while keeping the field listed in `required`.\n // We use `generateObject` with `mode: 'json'` below which doesn't require strict-mode\n // semantics, but keeping the schema strict-mode-clean future-proofs against runtime mode\n // switches.\n const toolScopesShape: Record<string, z.ZodNullable<typeof looseScopeShape>> = {};\n for (const name of scopableNames) {\n toolScopesShape[name] = looseScopeShape.nullable();\n }\n const toolScopesObject = z\n .object(toolScopesShape)\n .nullable()\n .describe(\n 'Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in ' +\n \"`tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.\",\n );\n\n return base.extend({ toolScopes: toolScopesObject }).superRefine((data, ctx) => {\n const scopes: Record<string, unknown> = (data.toolScopes ?? {}) as Record<string, unknown>;\n for (const name of data.tools) {\n if (!scopableNames.has(name)) continue;\n const value = scopes[name];\n if (value === null || value === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['toolScopes', name],\n message: `Tool \"${name}\" requires \\`toolScopes.${name}\\` — emit it per the SCOPE hint (don't leave null when picking).`,\n });\n }\n }\n });\n};\n\n/**\n * Calls the LLM with structured output to select tools for the conversation. Tries up to\n * `MAX_CLASSIFY_ATTEMPTS` times — the structured-output mode rejects classifications missing\n * mandatory scope, and on rejection the LLM gets a retry with the validation error pointing at\n * the missing entry. If every attempt still fails, returns the latest tool list without scopes\n * and lets `prepareStep` fall back to the (terse) unscoped surface.\n */\nconst MAX_CLASSIFY_ATTEMPTS = 2;\n\ntype Usage = ClassificationResult['usage'];\ntype Partial = { tools?: unknown; toolScopes?: unknown } | null | undefined;\ntype Soft = { tools: string[]; scopes?: Record<string, unknown> } | undefined;\n\nconst addUsage = (\n target: Usage,\n source: { inputTokens?: number; outputTokens?: number; totalTokens?: number } | undefined,\n): void => {\n if (!source) return;\n target.inputTokens += source.inputTokens ?? 0;\n target.outputTokens += source.outputTokens ?? 0;\n target.totalTokens += source.totalTokens ?? 0;\n};\n\n// Pull a (best-effort) `{ tools, scopes }` pair out of whatever the LLM returned. Used by both\n// the \"valid-shape but failed local validation\" and \"generateObject threw\" paths to populate the\n// soft-fallback when every attempt fails. Returns `undefined` when nothing looks usable.\nconst extractPartialTools = (partial: Partial): Soft => {\n if (!partial || !Array.isArray(partial.tools) || !partial.tools.every((tool) => typeof tool === 'string')) {\n return undefined;\n }\n const scopes =\n partial.toolScopes && typeof partial.toolScopes === 'object'\n ? (partial.toolScopes as Record<string, unknown>)\n : undefined;\n return { tools: partial.tools, ...(scopes && { scopes }) };\n};\n\n// Compact a successful classification: drop the `null` placeholders in `toolScopes` (the \"not\n// picked\" sentinel) so prepareStep's `applyToolScopes` doesn't churn through no-ops.\nconst compactSuccess = (\n parsed: { tools: string[]; toolScopes?: Record<string, unknown> | null },\n usage: Usage,\n): { tools: string[]; toolScopes?: Record<string, unknown>; usage: Usage } => {\n const cleanedScopes = parsed.toolScopes\n ? Object.fromEntries(Object.entries(parsed.toolScopes).filter(([, value]) => value != null))\n : undefined;\n return {\n tools: parsed.tools,\n ...(cleanedScopes && Object.keys(cleanedScopes).length > 0 && { toolScopes: cleanedScopes }),\n usage,\n };\n};\n\nconst classifyQueryToTools = async (\n conversation: ConversationMessage[],\n model: LanguageModel,\n toolsMetadata: Record<string, ToolMetadata>,\n): Promise<{ tools: string[]; toolScopes?: Record<string, unknown>; usage: Usage }> => {\n const scopableNames = new Set(scopableToolNames(toolsMetadata));\n const schema = buildClassifySchema(scopableNames);\n const aggregateUsage: Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n\n // System prompt embeds the expected JSON shape so the model produces something we can validate\n // locally. We use `output: 'no-schema'` below (json_object mode on Azure / OpenAI), which means\n // the API doesn't see our zod schema at all — strict-mode-friendly by construction.\n const expectedShape = buildExpectedShapeHint(scopableNames);\n const system = `${buildClassifySystemPrompt(toolsMetadata)}\\n\\nReturn JSON matching this shape exactly:\\n${expectedShape}`;\n\n let soft: Soft;\n for (let attempt = 0; attempt < MAX_CLASSIFY_ATTEMPTS; attempt++) {\n try {\n // `Output.json()` puts the model into json-output mode without attaching a JSON Schema\n // to the request — strict-mode-friendly on Azure/OpenAI (their strict mode rejects\n // several Zod constructs we use). We validate locally against our zod schema after.\n const generated = await generateText({\n model,\n output: Output.json(),\n system,\n messages: conversation,\n maxOutputTokens: 400,\n });\n addUsage(aggregateUsage, generated.usage);\n\n const parsed = schema.safeParse(generated.output);\n if (parsed.success) {\n return compactSuccess(\n parsed.data as { tools: string[]; toolScopes?: Record<string, unknown> | null },\n aggregateUsage,\n );\n }\n // Validation failed locally — capture the partial for soft-fallback and retry.\n soft = extractPartialTools(generated.output as Partial) ?? soft;\n } catch (error) {\n // Best-effort: `generateText` may reject with `{ value, usage }` on output-validation\n // failures; capture both so the soft-fallback below still has something useful and\n // usage stays accurate across the retry.\n const errRecord = error as { value?: unknown; usage?: typeof aggregateUsage };\n addUsage(aggregateUsage, errRecord.usage);\n soft = extractPartialTools(errRecord.value as Partial) ?? soft;\n }\n }\n\n if (!soft) {\n throw new Error(`Classifier failed after ${MAX_CLASSIFY_ATTEMPTS} attempts to produce a valid response.`);\n }\n return {\n tools: soft.tools,\n ...(soft.scopes && { toolScopes: soft.scopes }),\n usage: aggregateUsage,\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, toolScopes, usage } = await classifyQueryToTools(conversation, chatModel, toolsMetadata);\n\n const activeToolNames = tools;\n\n const timeMs = performance.now() - startTime;\n\n return {\n activeToolNames,\n ...(toolScopes && { toolScopes }),\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';\nimport type { FeatureFlags } from '../types';\n\n/** @ignore */\nexport type PlaceSummary = {\n id: string;\n name?: string;\n address?: string;\n position: [number, number];\n // Populated only when `experimentalSearch` is on and the underlying place came from the\n // exploration-search backend (DE / NL / FR area-tag pipeline; areas are small, few km²).\n areaId?: string;\n areaCountry?: string;\n areaTags?: string[];\n};\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 * When `flags.experimentalSearch` is `true`, the exploration-search-only\n * `areaId` / `areaCountry` / `areaTags` fields (when present on the source\n * place) are also copied into the summary. When the flag is off they are\n * always omitted, regardless of what the source place carries.\n *\n * @ignore\n */\nexport const summarizePlace = (place: Place, flags: FeatureFlags = {}): PlaceSummary => {\n const summary: 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 if (flags.experimentalSearch) {\n const props = place.properties as\n | {\n areaId?: string;\n areaCountry?: string;\n areaTags?: string[];\n }\n | undefined;\n if (props?.areaId) summary.areaId = props.areaId;\n if (props?.areaCountry) summary.areaCountry = props.areaCountry;\n if (props?.areaTags?.length) summary.areaTags = props.areaTags;\n }\n return summary;\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[], flags: FeatureFlags = {}): 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, flags)),\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 = await 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 = await 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 {\n alongRouteSearch,\n explorationSearch,\n geometryData,\n POPULATED_AREA_TAGS,\n type PopulatedAreaTag,\n search,\n} 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 { buildPlacesOutputSchema, 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/** Build the flag-aware output schema for the discover-places tool. */\nexport const buildDiscoverPlacesOutputSchema = (flags: FeatureFlags) =>\n z.union([\n buildPlacesOutputSchema(flags).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/** Default-flag (`experimentalSearch: false`) output schema. */\nexport const discoverPlacesOutputSchema = buildDiscoverPlacesOutputSchema({});\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 areaId: z\n .string()\n .optional()\n .describe(\n 'Id of a small area polygon (few km², not a whole municipality) — restricts results to that single area. ' +\n 'Typically chained from a previous hit\\'s `areaId` (\"what else is in this same area?\"). ' +\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 experimentalData = data as {\n municipalities?: string[];\n boundingBoxes?: unknown[];\n areaId?: string;\n };\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 !!experimentalData.municipalities?.length ||\n !!experimentalData.boundingBoxes?.length ||\n !!experimentalData.areaId;\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 / areaId' : '') +\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/areaId/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 on POI name/address. ' +\n 'NEVER a city/region/area name (e.g. \"Paris\", \"Amsterdam\") — those go in `where`. ' +\n 'For \"restaurants in Paris\": `query` is empty (or a name filter like \"pizza\"), `where.queries: [{query: \"Paris\"}]`. ' +\n 'Omit when filtering 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\n .optional()\n .describe(\n 'Display places/geometries on the map. ' +\n 'Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry.',\n ),\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 areaTags: z\n .array(z.enum(POPULATED_AREA_TAGS))\n .optional()\n .describe(\n 'Tokens describing a small surrounding area (few km², not a whole municipality). ' +\n 'Enum drawn from the SDK canonical vocabulary — e.g. \"coastal\", \"walkable\", \"alpine\", ' +\n '\"transit_connected\", \"village\", \"tourism_economy\". ' +\n 'OR semantics — matches places in areas tagged with ANY token. ' +\n 'Filter works on any `where` scope (including global), but area data is currently ' +\n 'populated only for DE / NL / FR — zero hits where data is absent. ' +\n 'Filter, not a geo-bias — pair with a `where` mode that supplies one.',\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/areaId/geometries/range/route'\n : 'queries/placeIds/geometries/range/route';\n const preferLine = flags.experimentalSearch\n ? 'Prefer `municipalities` / `areaId` / `queries` / `placeIds` (precise) over `boundingBoxes` (coarse). '\n : 'Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ';\n const areaTagsLine = flags.experimentalSearch\n ? 'Top-level `areaTags` narrows hits by small-area character (\"coastal\", \"walkable\", …) — area data currently populated only in DE/NL/FR. '\n : '';\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 areaTagsLine +\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 // `areaId` is a single-municipality geo-bias on the underlying explorationSearch — like\n // `municipalities` but matches by polygon id instead of name (faster + unambiguous).\n areaId?: string;\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 // Filter (not a geo-bias). Composes with any `where` scope.\n areaTags?: PopulatedAreaTag[];\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 = await 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`, `areaId`,\n// `areaTags`) only exist on the experimental exploration-search backend. Strip them when\n// 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 = await 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 baseFilters: MultiFilters,\n): Promise<WithinResolution | { error: string }> => {\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`, `municipalities`, and `areaId` only exist on the experimental schema;\n // treat them as absent on the default schema.\n const experimentalWhere = where as DiscoverWithinWhere & {\n boundingBoxes?: HasBBox[];\n municipalities?: string[];\n areaId?: 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 ...(experimentalWhere.areaId && { areaId: experimentalWhere.areaId }),\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 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, baseMultiFilters);\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 = await 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 flags: FeatureFlags,\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, flags),\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` and `areaTags` only exist on the experimental schema.\n const experimentalParams = params as {\n placeTypes?: ('POI' | 'PointAddress' | 'Street')[];\n areaTags?: PopulatedAreaTag[];\n };\n const placeTypes = experimentalParams.placeTypes;\n const areaTags = experimentalParams.areaTags;\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 // Top-level filters that compose with any `where` scope (not geo-biases on their own).\n const baseMultiFilters: MultiFilters = {\n ...(placeTypes?.length && { placeTypes }),\n ...(areaTags?.length && { areaTags }),\n };\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(\n state,\n detourResult.result,\n detourResult.placesEntryId,\n show,\n geometries,\n flags,\n );\n }\n\n const resolved = await resolveDiscoverBias(effectiveWhere, state, 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(\n state,\n rangeResult.result,\n rangeResult.placesEntryId,\n show,\n geometries,\n flags,\n );\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, flags);\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: buildDiscoverPlacesOutputSchema(flags),\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 '`query` filters POI names ONLY — area names go in `where`. ' +\n '`where.mode`: within (area) / nearby (point bias) / maxDetour (route-relative ranked) / global. ' +\n 'For \"X in [named area]\", put X in top-level `query`/`poiCategories` and the area in `where.queries: [{query: \"...\"}]` — geocoded in one step, no precursor locatePlace. ' +\n 'Within-mode: route → corridor. Prefer geometries/municipalities/placeIds over bbox. ' +\n 'Skip for already-stored places — use recallPlaces/processData.',\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\", queries: [{ query: \"De Jordaan, Amsterdam\", queryAs: \"place\" }] } }) // area name goes in `where.queries`, NOT top-level `query`',\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({ poiCategories: [\"RESTAURANT\"], where: { mode: \"within\", queries: [{ query: \"Paris\" }] } }) // \"restaurants in Paris\" — subject in `poiCategories`, area in `where.queries`',\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\", placeIds: [\"G55fc4abe-...\"] } }) // search inside stored place polygons (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 'Search for new EV stations along 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 'processData',\n 'analyseData',\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 = await 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 toggleTilesPOIs 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 entryId: z\n .string()\n .describe(\n 'Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData.',\n ),\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(\n 'Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize.',\n ),\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 the given entry on its per-entry module with default settings, fitting the viewport.\nconst showAnalyticsOnMap = async (state: ToolState, entryId: string): Promise<void> => {\n await state.trafficAreaAnalytics.showEntry(entryId);\n const entry = state.trafficAreaAnalytics.findById(entryId);\n if (!entry) return;\n const analyticsModule = await state.trafficAreaAnalytics.getEntryModule(entryId);\n const defaultMetric = entry.data.properties?.metrics?.[0] ?? 'congestionLevel';\n analyticsModule.setMetric(defaultMetric);\n const bbox = bboxFromGeoJSON(entry.data.features);\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\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 entryId: string,\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 entryId,\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 entryId,\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 const regionName = result.features[0]?.properties?.name;\n const label = regionName\n ? `${regionName} (${dateParams.startDate ?? '…'} → ${dateParams.endDate ?? '…'})`\n : `analytics (${dateParams.startDate ?? '…'} → ${dateParams.endDate ?? '…'})`;\n const entryId = await state.trafficAreaAnalytics.addEntry(result, label, {\n ...dateParams,\n metrics,\n functionalRoadClasses: functionalRoadClasses ?? 'all',\n hours: hours ?? 'all',\n });\n\n if (showOnMap) {\n await showAnalyticsOnMap(state, entryId);\n }\n\n return summarize(entryId, 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, bboxFromGeoJSON, trafficIncidentRequestCategories } from '@tomtom-org/maps-sdk/core';\nimport { type TrafficIncidentDetailsByBBoxParams, trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\n\nimport * as turf from '@turf/turf';\nimport type { MultiPolygon, Polygon } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { geoJsonBBoxSchema, geometryInputSchema, getViewportBias, queryAsSchema, withinSharedFields } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { locatePlaces, type QueryAs } from './locate-place';\n\n// Number of candidate places to consider when resolving `where.queries` — a precise area often\n// lives a few results down the list (e.g. a street POI may rank above the neighbourhood it sits in).\nconst QUERY_CANDIDATE_LIMIT = 5;\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 `analyseData`\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 * analyseData/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// `within` mode for getTrafficIncidents — single-area scope. Either `viewport` alone or any\n// combination of the multi-region fields (boundingBox / queries / placeIds / geometries / range /\n// route). Each multi-region input is resolved to a bbox and the union of bboxes is sent to the\n// SDK (which only accepts a single bbox of at most 10,000 km²).\nconst withinTrafficIncidentsWhereSchema = z\n .object({\n ...withinSharedFields,\n boundingBox: geoJsonBBoxSchema\n .optional()\n .describe('Single bbox [W,S,E,N]. EXCLUSIVE with viewport; composes with other multi-region fields.'),\n queries: z\n .array(\n z.object({\n query: z\n .string()\n .describe(\n 'Name of a CONTAINING area (e.g. \"Paris\", \"De Jordaan, Amsterdam\", \"M25\"). ' +\n 'Resolved to its bbox (or polygon bbox) and merged into the search area.',\n ),\n queryAs: queryAsSchema,\n }),\n )\n .optional()\n .describe(\n 'CONTAINING areas to fetch incidents within. Each entry geocodes to a bbox; the union of all ' +\n 'bboxes is used. Pair per-item `queryAs` to disambiguate 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) contributes its bbox to the 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 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n })\n .refine((data) => hasViewportFlag(data) || hasAnyMeaningfulMultiRegion(data), {\n message:\n '`within` requires `viewport: true` OR one or more multi-region fields ' +\n '(boundingBox / queries / placeIds / geometries / range / route) with a meaningful value.',\n });\n\n// --- \"Meaningful value\" predicates ---------------------------------------------------------------\n// Strict-mode LLM providers (Azure / OpenAI strict tool params) fill every optional field of an\n// object with empty defaults — `boundingBox: [0,0,0,0]`, `range: \"\"`, `queries: []`,\n// `route: { routeId: \"\", widthMeters: 0 }` — even when the agent's real intent is a single field.\n// These predicates treat such defaulted-but-empty values as omitted, so both the schema refinement\n// and the executor agree on which fields actually carry intent.\nconst bboxIsMeaningful = (b: number[] | undefined): boolean => !!b && b.length === 4 && b.some((v) => v !== 0);\nconst routeIsMeaningful = (r: { routeId?: string; widthMeters: number } | undefined): boolean =>\n !!r && r.widthMeters > 0 && (r.routeId === undefined || r.routeId.length > 0);\nconst rangeIsMeaningful = (s: string | undefined): boolean => !!s && s.length > 0;\nconst hasViewportFlag = (data: { viewport?: true }): boolean => data.viewport === true;\nconst hasAnyMeaningfulMultiRegion = (data: {\n boundingBox?: number[];\n queries?: unknown[];\n placeIds?: unknown[];\n geometries?: unknown[];\n range?: string;\n route?: { routeId?: string; widthMeters: number };\n}): boolean =>\n bboxIsMeaningful(data.boundingBox) ||\n !!data.queries?.length ||\n !!data.placeIds?.length ||\n !!data.geometries?.length ||\n rangeIsMeaningful(data.range) ||\n routeIsMeaningful(data.route);\n\nconst trafficIncidentsWhereSchema = z\n .union([withinTrafficIncidentsWhereSchema])\n .describe(\n 'Geographic scope. Only `within` mode (single area, defaults to `{ mode: \"within\", viewport: true }`). ' +\n 'Use `viewport` alone for \"the network / right now / this area\", or any combination of ' +\n 'boundingBox / queries / placeIds / geometries / range / route — each resolves to a bbox and ' +\n 'the union is sent to the SDK (≤10,000 km²).',\n );\n\n/**\n * Tool schema for fetching traffic incident details.\n */\nexport const getTrafficIncidentsSchema = z.object({\n where: trafficIncidentsWhereSchema\n .optional()\n .describe('Geographic scope. Default: `{ mode: \"within\", viewport: true }`.'),\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 analyseData / ' +\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 analyseData/focusIncidents. ' +\n '\\n\\n' +\n 'AREA via `where` (within mode only). Omit `where` to use the current map viewport ' +\n '(\"the network / right now / this area / here\"). For named areas use `where.queries`; ' +\n 'for stored places use `where.placeIds`; for an isochrone use `where.range`; for a corridor ' +\n 'along a route use `where.route`; for raw bounds use `where.boundingBox`; for custom shapes ' +\n 'use `where.geometries`. Multi-region fields compose into one union bbox (≤10,000 km²). ' +\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 'analyseData (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\ntype WithinWhere = z.infer<typeof withinTrafficIncidentsWhereSchema>;\n\n// Union of BBox tuples → single BBox tuple. Returns undefined when given no inputs.\nconst unionBBoxes = (parts: BBox[]): BBox | undefined => {\n if (parts.length === 0) return undefined;\n let minLng = Number.POSITIVE_INFINITY;\n let minLat = Number.POSITIVE_INFINITY;\n let maxLng = Number.NEGATIVE_INFINITY;\n let maxLat = Number.NEGATIVE_INFINITY;\n for (const [a, b, c, d] of parts) {\n if (a < minLng) minLng = a;\n if (b < minLat) minLat = b;\n if (c > maxLng) maxLng = c;\n if (d > maxLat) maxLat = d;\n }\n return [minLng, minLat, maxLng, maxLat];\n};\n\n// Resolves `where.queries` to a list of bboxes. Each query geocodes (POI or place per `queryAs`)\n// with the current viewport as bias. A query failing to resolve aborts the whole call — better to\n// surface a precise error than to silently search a smaller area.\nconst resolveQueriesToBBoxes = async (\n queries: { query: string; queryAs?: QueryAs }[],\n state: ToolState,\n): Promise<{ bboxes: BBox[] } | { error: string }> => {\n const biasPosition = getViewportBias(state.baseMap);\n const bboxes: BBox[] = [];\n for (const q of queries) {\n const candidates = await locatePlaces(q.query, q.queryAs ?? 'place', {\n limit: QUERY_CANDIDATE_LIMIT,\n bias: biasPosition ? { position: biasPosition } : undefined,\n });\n if (candidates.length === 0) {\n return { error: `Could not resolve \"${q.query}\". Try a more specific query, a placeId, or a boundingBox.` };\n }\n const bbox = bboxFromGeoJSON(candidates[0]) as BBox | undefined;\n if (!bbox) {\n return {\n error:\n `\"${q.query}\" resolved to a place with no bbox — cannot use as a \"within\" area. ` +\n 'Provide a boundingBox or a different query.',\n };\n }\n bboxes.push(bbox);\n }\n return { bboxes };\n};\n\n// Resolves `where.placeIds` to a list of bboxes. Prefers a cached `place.bbox`; falls back to\n// fetching the boundary polygon via Geometry Data when the place has no bbox of its own.\nconst resolvePlaceIdsToBBoxes = async (\n placeIds: string[],\n state: ToolState,\n): Promise<{ bboxes: BBox[] } | { error: string }> => {\n const bboxes: BBox[] = [];\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 const directBBox = bboxFromGeoJSON(lookup.place) as BBox | undefined;\n if (directBBox) {\n bboxes.push(directBBox);\n continue;\n }\n const feature = await state.places.fetchPlaceGeometry(placeId);\n const fetched = feature ? (bboxFromGeoJSON(feature) as BBox | undefined) : undefined;\n if (!fetched) {\n return {\n error:\n `Place \"${placeId}\" has no bbox or fetchable boundary polygon. ` +\n 'Provide a boundingBox or a different placeId.',\n };\n }\n bboxes.push(fetched);\n }\n return { bboxes };\n};\n\n// Resolves `where.range` to a list of bboxes (one per origin polygon). Multi-origin entries\n// combine their reachable polygons into the union area.\nconst resolveRangeToBBoxes = (state: ToolState, rangeId: string): { bboxes: BBox[] } | { error: string } => {\n const rangesEntry = state.ranges.entries.find((e) => e.id === rangeId);\n if (!rangesEntry || rangesEntry.ranges.length === 0) {\n return { error: `Range \"${rangeId}\" not found. Use recallRanges to list available ranges.` };\n }\n const bboxes: BBox[] = [];\n for (const r of rangesEntry.ranges) {\n const polygon = r.polygon?.features[0];\n const bbox = polygon ? (bboxFromGeoJSON(polygon) as BBox | undefined) : undefined;\n if (bbox) bboxes.push(bbox);\n }\n if (bboxes.length === 0) {\n return { error: `Range \"${rangeId}\" has no polygons. Recompute via findReachableAreas.` };\n }\n return { bboxes };\n};\n\n// Resolves `where.route` to a list of bboxes — one per route alternative — for the buffered\n// corridor at `widthMeters/2` either side of each route line. `widthMeters` is the *total*\n// corridor width, mirroring discover-places' `within.route` semantics.\nconst resolveRouteToBBoxes = (\n state: ToolState,\n route: { routeId?: string; widthMeters: number },\n): { bboxes: BBox[]; 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 = route.routeId ? entries.find((e) => e.id === route.routeId) : entries.at(-1);\n if (!entry) {\n return { error: `No route found with id \"${route.routeId}\". Use recallRoutes to list available routes.` };\n }\n if (entry.data.features.length === 0) {\n return { error: `Route entry \"${entry.id}\" has no geometry.` };\n }\n const radiusMeters = route.widthMeters / 2;\n const bboxes: BBox[] = [];\n for (const routeFeature of entry.data.features) {\n const buffered = turf.buffer(routeFeature, radiusMeters, { units: 'meters' });\n const bbox = buffered ? (bboxFromGeoJSON(buffered) as BBox | undefined) : undefined;\n if (bbox) bboxes.push(bbox);\n }\n if (bboxes.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 { bboxes, routeLabel: entry.label };\n};\n\ntype ResolvedScope = { bbox: BBox; routeLabel?: string };\n\n// Viewport branch — isolated so the try/catch noise doesn't inflate the main resolver.\nconst resolveViewportToBBox = (state: ToolState): ResolvedScope | { error: string } => {\n try {\n return { bbox: state.baseMap.ttMap.getBBox() };\n } catch (error) {\n return { error: `Viewport fallback failed: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n// Cheap inline pass — converts `where.geometries` polygons into bboxes.\nconst bboxesFromGeometries = (geometries: WithinWhere['geometries']): BBox[] => {\n if (!geometries?.length) return [];\n const out: BBox[] = [];\n for (const g of geometries as (Polygon | MultiPolygon)[]) {\n const bbox = bboxFromGeoJSON(g) as BBox | undefined;\n if (bbox) out.push(bbox);\n }\n return out;\n};\n\n// Drains the multi-region branches in sequence, skipping fields with non-meaningful (strict-mode\n// LLM-defaulted) values. Any sub-resolver short-circuit (error) aborts the whole pass — better to\n// surface a precise error than to silently search a smaller area.\nconst collectMultiRegionBBoxes = async (\n where: WithinWhere,\n state: ToolState,\n): Promise<{ bboxes: BBox[]; routeLabel?: string } | { error: string }> => {\n const collected: BBox[] = [];\n let routeLabel: string | undefined;\n\n if (bboxIsMeaningful(where.boundingBox)) collected.push(where.boundingBox as BBox);\n\n if (where.queries?.length) {\n const resolved = await resolveQueriesToBBoxes(where.queries, state);\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n }\n\n if (where.placeIds?.length) {\n const resolved = await resolvePlaceIdsToBBoxes(where.placeIds, state);\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n }\n\n collected.push(...bboxesFromGeometries(where.geometries));\n\n if (rangeIsMeaningful(where.range)) {\n const resolved = resolveRangeToBBoxes(state, where.range as string);\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n }\n\n if (routeIsMeaningful(where.route)) {\n const resolved = resolveRouteToBBoxes(state, where.route as { routeId?: string; widthMeters: number });\n if ('error' in resolved) return resolved;\n collected.push(...resolved.bboxes);\n routeLabel = resolved.routeLabel;\n }\n\n return { bboxes: collected, ...(routeLabel && { routeLabel }) };\n};\n\n// Resolve a `within` where to a single union bbox. Returns the union along with any incidental\n// labels (e.g. the route entry's label) that feed into the auto-generated entry label.\n//\n// Priority: when any multi-region field carries a meaningful value (e.g. `queries`, `boundingBox`,\n// `route`), take the multi-region path even if `viewport: true` is also set. Strict-mode LLM\n// providers fill every optional field with empty defaults, so `viewport: true` is frequently\n// present alongside the agent's real intent — preferring the more specific signal avoids silently\n// answering with the wrong area (e.g. London viewport when the agent asked about Paris).\nconst resolveWithinToBBox = async (\n where: WithinWhere,\n state: ToolState,\n): Promise<ResolvedScope | { error: string }> => {\n if (hasAnyMeaningfulMultiRegion(where)) {\n const collected = await collectMultiRegionBBoxes(where, state);\n if ('error' in collected) return collected;\n const merged = unionBBoxes(collected.bboxes);\n if (!merged) {\n return { error: 'No area could be resolved from `where` — provide at least one within field.' };\n }\n return { bbox: merged, ...(collected.routeLabel && { routeLabel: collected.routeLabel }) };\n }\n if (hasViewportFlag(where)) return resolveViewportToBBox(state);\n return { error: 'No area could be resolved from `where` — provide at least one within field.' };\n};\n\n// Build a human-readable label from `where` when the caller didn't pass one. Falls back to a\n// bbox-coordinate string for silent intermediate fetches.\nconst defaultLabel = (\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n where: WithinWhere | undefined,\n routeLabel: string | undefined,\n): string => {\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\n let head: string | undefined;\n if (where && !where.viewport) {\n if (where.queries?.length) head = where.queries.map((q) => q.query).join(', ');\n else if (routeLabel) head = `along ${routeLabel}`;\n else if (where.range) head = where.range;\n }\n\n if (!head) {\n const [minLng, minLat, maxLng, maxLat] = params.bbox;\n head = `bbox ${minLng.toFixed(2)},${minLat.toFixed(2)} → ${maxLng.toFixed(2)},${maxLat.toFixed(2)}`;\n }\n\n return filterParts.length ? `${head} — ${filterParts.join('; ')}` : head;\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 { where, categoryFilter, timeValidityFilter, label, silent = false, fitBounds = true } = params;\n\n // Default to viewport when `where` is omitted — keeps the \"no scope = current viewport\"\n // contract the system prompt promises.\n const effectiveWhere: WithinWhere = where ?? { mode: 'within', viewport: true };\n\n const resolved = await resolveWithinToBBox(effectiveWhere, state);\n if ('error' in resolved) return resolved;\n const { bbox: effectiveBbox, routeLabel } = resolved;\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 = await state.trafficIncidents.addIncidentsEntry(\n result.features,\n entryParams,\n label ?? defaultLabel(entryParams, effectiveWhere, routeLabel),\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 await 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 type { FeatureCollection } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { placesEntryIdHintSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const addByodLayerOutputSchema = z.union([\n z.object({\n byodEntryId: z\n .string()\n .describe('Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData).'),\n label: z.string(),\n featureCount: z.number(),\n geometryTypes: z.array(z.string()).describe('Geometry types present (Point / LineString / Polygon / …).'),\n source: z\n .union([z.object({ kind: z.literal('url'), url: z.string() }), z.object({ kind: z.literal('inline') })])\n .describe('Where the data came from.'),\n shown: z.boolean().describe('Whether the entry was rendered immediately (mirrors the `show` input).'),\n }),\n toolErrorSchema,\n]);\n\nexport const addByodLayerSchema = z\n .object({\n label: z\n .string()\n .describe('Human-readable label for the new entry (e.g. \"Sales territories\", \"Customer pins 2026-Q1\").'),\n url: z\n .url()\n .optional()\n .describe(\n 'URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. ' +\n 'EXCLUSIVE with `data`.',\n ),\n data: z\n .unknown()\n .optional()\n .describe(\n 'Inline GeoJSON FeatureCollection (`{ type: \"FeatureCollection\", features: [...] }`). ' +\n 'EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.',\n ),\n entryId: placesEntryIdHintSchema,\n show: z\n .boolean()\n .optional()\n .describe(\n 'Render the entry immediately after adding. Default: false (entry is stored but hidden — use `updateByodDisplay` to render later).',\n ),\n })\n .refine((v) => (v.url ? !v.data : !!v.data), {\n message: 'Pass EXACTLY ONE of `url` or `data`.',\n });\n\nexport const addByodLayerDescription =\n 'Register a customer-owned GeoJSON layer as a new BYOD entry. Accepts either a URL (fetched once) or ' +\n 'inline GeoJSON. The slice renders the entry with sensible defaults based on geometry type ' +\n '(Point → circle, Line → line, Polygon → fill). The new entry id is usable with `byodEntryIDs` on ' +\n '`analyseData` / `processData` and with `updateByodDisplay` to toggle visibility. ' +\n 'For very large datasets, prefer programmatic seeding from the embedding app via `state.byod.addEntry(...)`.';\n\nconst isFeatureCollection = (value: unknown): value is FeatureCollection => {\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\n// Bounded-fetch policy for BYOD URL inputs. The tool runs with whatever network reach the host\n// gives it (server-side: full reach; browser: subject to CORS). We don't try to enforce SSRF\n// guarantees that the host can't itself — instead we make accidental abuse expensive: only\n// http(s), short timeout, hard size cap.\nconst FETCH_TIMEOUT_MS = 15_000;\nconst MAX_RESPONSE_BYTES = 25 * 1024 * 1024; // 25 MB — comfortably above typical GeoJSON layers, refuses anything pathological.\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);\n\nconst validateUrl = (raw: string): { url: URL } | { error: string } => {\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n return { error: `Invalid URL: \"${raw}\".` };\n }\n if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {\n return {\n error: `URL protocol \"${parsed.protocol}\" is not allowed. Only http: and https: are supported.`,\n };\n }\n return { url: parsed };\n};\n\n// Check the advertised Content-Length and bail before we read a single byte if the server\n// honestly admits the payload is too large. Servers that lie or omit the header fall through.\nconst checkAdvertisedSize = (response: Response, url: URL): { error: string } | undefined => {\n const contentLength = response.headers.get('content-length');\n if (!contentLength) return undefined;\n const advertised = Number.parseInt(contentLength, 10);\n if (Number.isFinite(advertised) && advertised > MAX_RESPONSE_BYTES) {\n return {\n error: `Response too large for \"${url}\": advertised ${advertised} bytes (max ${MAX_RESPONSE_BYTES}).`,\n };\n }\n return undefined;\n};\n\n// Streaming-cap body reader. Pulls chunks from the response and aborts as soon as the running\n// total crosses MAX_RESPONSE_BYTES, so we never page in a multi-GB payload. Falls back to a\n// single `text()` read with post-hoc size check when the runtime has no streaming reader.\nconst readBodyBounded = async (response: Response, url: URL): Promise<{ value: string } | { error: string }> => {\n const reader = response.body?.getReader();\n if (!reader) {\n const text = await response.text();\n if (text.length > MAX_RESPONSE_BYTES) {\n return { error: `Response too large for \"${url}\": ${text.length} bytes (max ${MAX_RESPONSE_BYTES}).` };\n }\n return { value: text };\n }\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_RESPONSE_BYTES) {\n await reader.cancel();\n return {\n error: `Response exceeded ${MAX_RESPONSE_BYTES} bytes while streaming \"${url}\". Refusing to load.`,\n };\n }\n chunks.push(value);\n }\n const merged = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { value: new TextDecoder().decode(merged) };\n};\n\n// Translate AbortController-triggered aborts into a friendlier timeout message; pass everything\n// else through verbatim. Pulled out so the main fetch flow doesn't carry the catch-block branch.\nconst wrapFetchError = (error: unknown, url: URL): { error: string } => {\n if (error instanceof Error && error.name === 'AbortError') {\n return { error: `Fetch timed out after ${FETCH_TIMEOUT_MS}ms for \"${url}\".` };\n }\n return { error: `Failed to fetch \"${url}\": ${error instanceof Error ? error.message : String(error)}` };\n};\n\n// Fetches a JSON resource with timeout + size cap. Returns either the parsed body or a structured\n// error. Body-streaming and size enforcement live in `readBodyBounded`.\nconst fetchJsonBounded = async (url: URL): Promise<{ value: unknown } | { error: string }> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const response = await fetch(url, { signal: controller.signal });\n if (!response.ok) {\n return { error: `Failed to fetch \"${url}\": HTTP ${response.status} ${response.statusText}` };\n }\n const sizeProblem = checkAdvertisedSize(response, url);\n if (sizeProblem) return sizeProblem;\n const body = await readBodyBounded(response, url);\n if ('error' in body) return body;\n return { value: JSON.parse(body.value) };\n } catch (error) {\n return wrapFetchError(error, url);\n } finally {\n clearTimeout(timer);\n }\n};\n\nconst collectGeometryTypes = (data: FeatureCollection): string[] => {\n const types = new Set<string>();\n for (const feature of data.features) {\n const t = feature.geometry?.type;\n if (t) types.add(t);\n }\n return [...types];\n};\n\nexport const executeAddByodLayer = async (\n params: z.infer<typeof addByodLayerSchema>,\n state: ToolState,\n): Promise<z.infer<typeof addByodLayerOutputSchema>> => {\n let data: unknown;\n let sourceRecord: { kind: 'url'; url: string } | { kind: 'inline' };\n\n if (params.url) {\n const urlCheck = validateUrl(params.url);\n if ('error' in urlCheck) return urlCheck;\n const fetched = await fetchJsonBounded(urlCheck.url);\n if ('error' in fetched) return fetched;\n data = fetched.value;\n sourceRecord = { kind: 'url', url: params.url };\n } else {\n data = params.data;\n sourceRecord = { kind: 'inline' };\n }\n\n if (!isFeatureCollection(data)) {\n return {\n error: 'Payload is not a GeoJSON FeatureCollection (`{ type: \"FeatureCollection\", features: [...] }`).',\n };\n }\n\n const byodEntryId = await state.byod.addEntry(data, params.label, {\n source: sourceRecord,\n ...(params.entryId && { explicitId: params.entryId }),\n });\n\n let shown = false;\n if (params.show) {\n try {\n await state.byod.showEntry(byodEntryId);\n shown = true;\n } catch (error) {\n // The entry is already in state, so we keep it — `updateByodDisplay` can retry the\n // render later. Surface the render error through the tool-result schema rather than\n // letting it reject the tool call.\n return {\n error: `addByodLayer added entry \"${byodEntryId}\" but rendering failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n };\n }\n }\n\n return {\n byodEntryId,\n label: params.label,\n featureCount: data.features.length,\n geometryTypes: collectGeometryTypes(data),\n source: sourceRecord,\n shown,\n };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Sandbox-running analysis tool. Accepts any combination of places / routes /\n * incidents / customGeometries / trafficAreaAnalytics / byod entries and exposes\n * them to user-authored JS as `places`, `routes`, `incidents`, `geometries`,\n * `trafficAreaAnalytics`, `byod` (and per-entry views), with `h3` and `turf`\n * available. The result is attached as `_analysis[name]` on every contributing\n * entry.\n *\n * Opt-in `monitor: { entryId }` registers the call as a recurring spec on the\n * targeted incidents entry — the code re-runs on every monitor-tick with\n * `previous` / `now` / `log` in scope. Constraint: only incidents-only inputs\n * are allowed when `monitor` is set (no cross-kind reruns in the lean merge).\n *\n * Opt-in `applyFocus` (default true when `monitor` is set) honours `focusIds`\n * on the result by calling `setFocus` on the source incidents entry.\n *\n * **Scope + enablement.** Shared scaffolding in `tools/shared/entry-kinds.ts` builds\n * the per-kind input fields, the \"at least one of …\" message, the per-kind sandbox /\n * schema docs, and the scope schema. This file only adds the tool-specific surface:\n * `name` / `description` / `outputFormat` / `monitor` / `applyFocus`, the code-doc\n * body (including the incidents-monitor extras), the description preamble, the\n * monitor refine, and the executor.\n */\n\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport { runIncidentSpec } from '../../state';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport {\n ALL_ENTRY_DATA_KINDS,\n ANALYSE_OUTPUT_FORMAT_DESCRIPTION,\n type AnalysisOutputFormat,\n buildAnalyseReturnPrompt,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxCodePrompt,\n CROSS_KIND_OPS_DOC,\n type EntryKindScope,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n hasAnyEntryIds,\n isKindActive,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n prepareMultiInputs,\n resolveActiveKinds,\n runSandboxedFn,\n skippedSourceSchema,\n validateAnalysisResult,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Per-turn scope kinds that `analyseData` understands. Identical to {@link EntryDataKind}\n * — every kind that's an analyseData input also accepts a scoped narrow.\n *\n * @group Agent Toolkit\n */\nexport type AnalyseDataKind = EntryDataKind;\n\n/**\n * Per-turn scope for `analyseData`. Emitted by the classifier in\n * `toolScopes.analyseData` when only a subset of input kinds is relevant.\n *\n * Object-shaped so future per-tool scope additions (e.g. flags) don't break\n * the wire format.\n *\n * @group Agent Toolkit\n */\nexport type AnalyseDataScope = EntryKindScope;\n\n/**\n * Default scope schema for analyseData — accepts every {@link EntryDataKind}. When\n * the agent narrows the enabled kinds via `createMapAgent`'s `dataEntries`, the\n * builder produces a narrower schema in {@link buildAnalyseDataEntry}.\n *\n * Exported for backward compat with consumers that imported the static schema.\n */\nexport const analyseDataScopeSchema = buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS);\n\n/** Output schema for analyse-data. */\nexport const analyseDataOutputSchema = z.union([\n z.object({\n affectedEntries: z\n .array(\n z.union([\n z.object({ kind: z.literal('places'), id: z.string() }),\n z.object({ kind: z.literal('routes'), id: z.string() }),\n z.object({ kind: z.literal('incidents'), id: z.string() }),\n z.object({ kind: z.literal('customGeometries'), id: z.string() }),\n z.object({ kind: z.literal('trafficAreaAnalytics'), id: z.string() }),\n ]),\n )\n .describe(\n 'Entries the analysis was attached to: every contributing places / routes / incidents / ' +\n 'customGeometries / trafficAreaAnalytics entry. Ranges entries feed the aggregation but do not carry analyses.',\n ),\n sourceIds: z\n .array(z.unknown())\n .optional()\n .describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only present when \\`geometriesEntryIDs\\` was set).`),\n name: z.string().describe('Unique name of the analysis within each affected entry.'),\n description: z.string().optional(),\n outputFormat: z.enum(['json', 'chart']),\n analysis: z.unknown(),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\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 ran with `monitor` AND returned `focusIds: string[]` (+ optional ' +\n '`focusReason`). The tool applied that focus as a side-effect on the source incidents entry.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/** Build the scope-aware `code` field description for analyse-data. */\nexport const buildAnalyseDataCodeDoc = (\n enabled: readonly EntryDataKind[],\n scope: AnalyseDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const sandboxList = buildEntryKindSandboxDocs(active);\n // Deep per-kind schema docs are gated on `scope !== undefined` — they only appear once\n // the classifier commits to specific kinds. The unscoped fallback shows only the terse\n // one-liners, which is enough to write reasonable code without 3-4 KB of schema\n // reference per kind.\n const schemaBlocks = scope ? buildEntryKindSchemaDocs(active) : '';\n // Cross-kind ops cheat-sheet only matters when more than one kind is in scope. It's a\n // sizeable doc (~2 KB) so we keep it strictly gated on scoped + multi-kind.\n const showCrossKind = !!scope && active.length > 1;\n // Geometries provenance doc only matters when customGeometries is actually in scope.\n const showGeometriesProps = !!scope && active.includes('customGeometries');\n // Incidents monitor + focus side-effect blocks only matter when incidents is in scope.\n // ~400 tokens and irrelevant to non-incidents analyses, so gate them too.\n const showIncidentsExtras = !scope || active.includes('incidents');\n const incidentsExtras = showIncidentsExtras\n ? 'When `monitor: { entryId }` is set, the call ALSO registers as a recurring spec on that incidents entry. ' +\n 'On every monitor tick the code re-runs against the fresh `incidents` snapshot and these EXTRA sandbox args ' +\n 'become available:\\n' +\n '• `previous` — your last result for this `name` on the source entry, or `undefined` on the first run. Use it ' +\n 'for id stability (e.g. reuse a prior cluster id when memberIds overlap by ≥50%) and short trend reads ' +\n \"('growing' / 'fading' / 'steady'). Always guard `previous === undefined` — true on every first run.\\n\" +\n '• `now: Date` — call-start time; compare against `startTime` / `endTime` on incidents.\\n' +\n '• `log: (...args) => void` — writes into the response logs.\\n' +\n 'Constraint: cross-kind reruns are NOT supported — when `monitor` is set, `incidentsEntryIDs: [monitor.entryId]` ' +\n 'is the only allowed input. For one-shot cross-kind incidents analysis (no rerun), omit `monitor`.\\n' +\n 'FOCUS SIDE-EFFECT: when `applyFocus !== false` AND the result has `focusIds: string[]` (+ optional ' +\n '`focusReason`), `setFocus` is called once on the source entry to dim/highlight the listed incidents. ' +\n 'One-shot — the focus side-effect does NOT re-apply on monitor-tick replays.\\n\\n'\n : '';\n return (\n 'Async JS that aggregates the injected inputs and returns the result.\\n\\n' +\n `${buildSandboxCodePrompt(MULTI_INPUT_SANDBOX_PARAMS)}\\n\\n` +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading ' +\n '(`if (places) ...`, `routes?.features.length`, etc.). When defined:\\n' +\n `${sandboxList}\\n\\n` +\n incidentsExtras +\n (showCrossKind ? `${CROSS_KIND_OPS_DOC}\\n\\n` : '') +\n `${buildAnalyseReturnPrompt('counts, per-kind breakdowns, hex bins, intersections, distance bins')}\\n\\n` +\n (showGeometriesProps ? `${GEOMETRIES_PROPS_DOC}\\n\\n` : '') +\n schemaBlocks\n );\n};\n\n/** Build the scope-aware analyseData tool input schema. */\nexport const buildAnalyseDataSchema = (enabled: readonly EntryDataKind[], scope: AnalyseDataScope | undefined) => {\n const active = resolveActiveKinds(enabled, scope);\n const requiredHint = `At least one of ${buildEntryFieldList(active)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(active, { verb: 'analyse', requiredHint }),\n };\n\n shape.name = z\n .string()\n .describe(\n 'Unique name within each affected entry (e.g. \"incidents-per-cluster\", \"ev-near-route\"). ' +\n 'Reusing a name replaces the previous analysis on those entries.',\n );\n shape.description = z.string().optional().describe('Optional short description of what the analysis computes.');\n shape.outputFormat = z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION);\n shape.code = z.string().describe(buildAnalyseDataCodeDoc(enabled, scope));\n\n // Monitor + applyFocus are only meaningful when `incidents` is in the active set. Out of\n // scope or disabled at the agent level → drop both fields entirely so the LLM doesn't see\n // surface that can't be exercised.\n const incidentsActive = isKindActive(enabled, scope, 'incidents');\n if (incidentsActive) {\n shape.monitor = z\n .object({\n entryId: z\n .string()\n .describe(\n 'Incidents entry id to register the spec on. Must match `incidentsEntryIDs[0]`. The spec re-runs on every monitor-tick of that entry.',\n ),\n })\n .optional()\n .describe(\n 'Opt-in: register this analysis as a recurring spec on the targeted incidents entry. The code re-runs ' +\n 'on every monitor-tick with extra sandbox args (`previous`, `now`, `log`). When set, only ' +\n '`incidentsEntryIDs: [monitor.entryId]` is allowed alongside — cross-kind reruns are not supported.',\n );\n shape.applyFocus = z\n .boolean()\n .optional()\n .describe(\n 'When `monitor` is set AND the result has `focusIds: string[]` (+ optional `focusReason`), call ' +\n '`setFocus` on the source entry to dim/highlight those incidents. Default: true. One-shot — the ' +\n 'focus side-effect does NOT re-apply on monitor-tick replays.',\n );\n }\n\n const baseObject = z.object(shape);\n\n return baseObject.refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(active) }).refine(\n (v: Record<string, unknown>) => {\n const monitor = v.monitor as { entryId: string } | undefined;\n if (!monitor) return true;\n // Monitor requires incidentsEntryIDs to match the targeted entry, and no other input kinds.\n const incidents = v.incidentsEntryIDs as string[] | undefined;\n const incidentsOk = Array.isArray(incidents) && incidents.length === 1 && incidents[0] === monitor.entryId;\n if (!incidentsOk) return false;\n // No other *EntryIDs may be present alongside incidents.\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n if (kind === 'incidents') continue;\n const fieldName = `${kind === 'customGeometries' ? 'geometries' : kind}EntryIDs`;\n const value = v[fieldName] as unknown[] | undefined;\n if (value?.length) return false;\n }\n return true;\n },\n {\n message:\n 'When `monitor` is set, only `incidentsEntryIDs: [monitor.entryId]` is allowed (cross-kind reruns are not supported in the lean merge). ' +\n 'Omit `monitor` for one-shot cross-kind incidents analysis.',\n },\n );\n};\n\n/** Default full-surface input schema for analyse-data — every kind enabled, no per-turn scope. */\nexport const analyseDataSchema = buildAnalyseDataSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\n/** Build the scope-aware analyseData tool description. */\nexport const buildAnalyseDataDescription = (\n enabled: readonly EntryDataKind[],\n scope: AnalyseDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const kindList = active.join(' / ');\n const incidentsTail =\n scope && active.includes('incidents')\n ? 'Opt-in `monitor: { entryId }` registers the call as a recurring spec on the targeted incidents entry; ' +\n 'opt-in `applyFocus` honours `focusIds: string[]` by calling `setFocus`.'\n : '';\n const contrast =\n 'CONTRAST `processData`, which writes NEW renderable map entries (places / custom-geometries / BYOD); ' +\n '`analyseData` never renders or creates entries — only attaches metadata.';\n if (scope) {\n return (\n `Aggregate ${kindList} entries via dynamic JS — counts, groupings, charts, hex bins, ` +\n `cross-kind correlations. ${contrast} Each scoped input is exposed as a merged collection and a ` +\n 'per-entry record; fields are `undefined` when their `*EntryIDs` argument is omitted (guard with `?.`). ' +\n 'Result is attached as `_analysis[name]` on every contributing entry. ' +\n '`outputFormat: \"json\"` (default) or `\"chart\"` (Chart.js config). ' +\n incidentsTail\n );\n }\n const incidentsUnscopedTail = active.includes('incidents')\n ? 'Opt-in `monitor: { entryId }` registers the call as a recurring spec on the targeted incidents entry — the ' +\n 'code re-runs on every monitor-tick with `previous` / `now` / `log` available in the sandbox. When `monitor` ' +\n 'is set, only `incidentsEntryIDs: [monitor.entryId]` is allowed alongside (no cross-kind reruns). ' +\n 'Opt-in `applyFocus` (default true under `monitor`) honours `focusIds: string[]` on the result by calling ' +\n '`setFocus` on the source entry to dim/highlight a subset.'\n : '';\n return (\n `Aggregate ${kindList} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ` +\n `${contrast} ` +\n 'Each input is exposed as a merged collection and a per-entry record; fields are `undefined` when their ' +\n '`*EntryIDs` argument is omitted (guard with `?.` / `if (places) …`). ' +\n 'Result is attached as `_analysis[name]` on every contributing entry (ranges feed in but do not carry ' +\n 'analyses; BYOD entries are read-only). `outputFormat: \"json\"` (default) or `\"chart\"` (Chart.js config). ' +\n incidentsUnscopedTail\n );\n};\n\n/** Default full-surface description — every kind enabled. */\nexport const analyseDataDescription = buildAnalyseDataDescription(ALL_ENTRY_DATA_KINDS, undefined);\n\n// Build the classifier hint dynamically so disabled kinds don't appear as scope choices.\nconst buildAnalyseDataScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((k) => `\"${k}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` ` +\n 'listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all).'\n );\n};\n\n// Every state slice's `addAnalysisToEntry` matches this shape — the per-slice `Analysis`\n// type is structurally identical across places/routes/incidents/trafficAreaAnalytics/custom.\ntype AnalysisSink<E extends { id: string }> = {\n addAnalysisToEntry: (entryId: E['id'], analysis: AnalysisRecord) => boolean;\n};\n\ntype AnalysisRecord = {\n name: string;\n timestamp: number;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n data: unknown;\n};\n\n// Attach an analysis to every entry in `entries` on `slice`. Shares the timestamp across the\n// batch so the agent UI can correlate per-slice updates that originated from the same call.\nconst attachAnalysisToEntries = <E extends { id: string }>(\n slice: AnalysisSink<E>,\n entries: readonly E[],\n payload: {\n name: string;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n analysis: unknown;\n timestamp?: number;\n },\n): void => {\n const timestamp = payload.timestamp ?? Date.now();\n for (const entry of entries) {\n slice.addAnalysisToEntry(entry.id, {\n name: payload.name,\n timestamp,\n ...(payload.description && { description: payload.description }),\n outputFormat: payload.outputFormat,\n data: payload.analysis,\n });\n }\n};\n\ntype AnalyseDataInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n name: string;\n description?: string;\n code: string;\n outputFormat?: 'json' | 'chart';\n monitor?: { entryId: string };\n applyFocus?: boolean;\n};\n\nexport const executeAnalyseData = async (\n params: AnalyseDataInput,\n state: ToolState,\n): Promise<z.infer<typeof analyseDataOutputSchema>> => {\n const {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n name,\n description,\n code,\n monitor,\n applyFocus,\n } = params;\n const outputFormat = params.outputFormat ?? 'json';\n\n // Monitor path: register a recurring spec on the source incidents entry, run with the\n // incidents-only sandbox surface (incidents / h3 / turf / now / log / previous), then\n // optionally apply the focus side-effect. Mirrors the old analyse-incidents semantics\n // but lives behind the analyseData surface.\n if (monitor) {\n return runMonitorPath(state, monitor.entryId, name, description, code, outputFormat, applyFocus !== false);\n }\n\n const prepared = await prepareMultiInputs(\n {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n state,\n );\n if ('error' in prepared) return { error: prepared.error };\n const { resolved, sandbox, geometries: geometriesMeta } = prepared.value;\n\n const sandboxResult = await runSandboxedFn(\n code,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs(sandbox, { 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 // Attach to every contributing entry across all slices, with a shared\n // timestamp so the UI can correlate the per-slice updates back to one call.\n const timestamp = Date.now();\n const payload = { name, description, outputFormat, analysis, timestamp };\n attachAnalysisToEntries(state.places, resolved.places, payload);\n attachAnalysisToEntries(state.routing, resolved.routes, payload);\n attachAnalysisToEntries(state.trafficIncidents, resolved.incidents, payload);\n attachAnalysisToEntries(state.trafficAreaAnalytics, resolved.trafficAreaAnalytics, payload);\n // Geometries pull in places + custom entries indirectly via `collectInputGeometries`.\n attachAnalysisToEntries(\n state.places,\n geometriesMeta.affectedEntries.filter((e) => e.kind === 'places').map((e) => ({ id: e.id })),\n payload,\n );\n attachAnalysisToEntries(\n state.customGeometries,\n geometriesMeta.affectedEntries.filter((e) => e.kind === 'customGeometries').map((e) => ({ id: e.id })),\n payload,\n );\n\n const affectedEntries: Array<{\n kind: 'places' | 'routes' | 'incidents' | 'customGeometries' | 'trafficAreaAnalytics';\n id: string;\n }> = [\n ...resolved.places.map((e) => ({ kind: 'places' as const, id: e.id })),\n ...resolved.routes.map((e) => ({ kind: 'routes' as const, id: e.id })),\n ...resolved.incidents.map((e) => ({ kind: 'incidents' as const, id: e.id })),\n ...resolved.trafficAreaAnalytics.map((e) => ({ kind: 'trafficAreaAnalytics' as const, id: e.id })),\n ...geometriesMeta.affectedEntries\n .filter((e) => e.kind === 'places' && !resolved.places.some((p) => p.id === e.id))\n .map((e) => ({ kind: 'places' as const, id: e.id })),\n ...geometriesMeta.affectedEntries\n .filter((e) => e.kind === 'customGeometries')\n .map((e) => ({ kind: 'customGeometries' as const, id: e.id })),\n ];\n\n const sourceIds: GeometriesId[] | undefined = geometriesEntryIDs ? geometriesMeta.sourceIds : undefined;\n\n return {\n affectedEntries,\n ...(sourceIds && { sourceIds }),\n name,\n ...(description && { description }),\n outputFormat,\n analysis,\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n };\n};\n\n/**\n * Monitor path — incidents-only spec rerun. Mirrors the old `executeAnalyseIncidents` but\n * returns analyseData's output shape (affectedEntries + optional focused).\n *\n * Registers the spec BEFORE the first run so monitor-tick replays survive an initial-run\n * error. The focus side-effect is one-shot — replays read the registered spec but skip the\n * setFocus call.\n */\nconst runMonitorPath = async (\n state: ToolState,\n entryId: string,\n name: string,\n description: string | undefined,\n code: string,\n outputFormat: 'json' | 'chart',\n applyFocus: boolean,\n): Promise<z.infer<typeof analyseDataOutputSchema>> => {\n const sourceEntry = state.trafficIncidents.entries.find((e) => e.id === entryId);\n if (!sourceEntry) {\n return {\n error: `No incidents entry with id \"${entryId}\". 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 a failing initial run still leaves the spec\n // available 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 if ('error' in result) return { error: result.error };\n\n const { value: analysis } = result;\n attachAnalysisToEntries(state.trafficIncidents, [sourceEntry], {\n name,\n description,\n outputFormat,\n analysis,\n timestamp: sampledAt,\n });\n\n const focused = applyFocus ? applyFocusSideEffect(analysis, sourceEntry.id, state) : undefined;\n\n return {\n affectedEntries: [{ kind: 'incidents' as const, id: 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// Honour `focusIds` / `focusReason` on the analysis result by calling `setFocus` against the\n// source incidents entry. No-op when the result does not carry valid focus ids.\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/**\n * Scope-aware builder for the analyseData tool. Reads classifier-resolved `scope` AND the\n * agent's `enabledDataKinds` from {@link ToolBuildOptions} so disabled kinds disappear from\n * the description, schema, code-doc, classifier scope, and the monitor refine. Without\n * either, emits the full surface unchanged. Caller supplies static metadata (tags,\n * examples, related/depends, classificationPrompt) via the second positional arg so the\n * registry stays the single source of truth for those.\n */\nexport const buildAnalyseDataEntry = (\n options: ToolBuildOptions<AnalyseDataScope>,\n metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n },\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n return {\n description: buildAnalyseDataDescription(enabled, options.scope),\n inputSchema: buildAnalyseDataSchema(enabled, options.scope),\n outputSchema: analyseDataOutputSchema,\n execute: executeAnalyseData as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildAnalyseDataScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/**\n * Default builder for the `analyseData` tool. Reads classifier-resolved scope and\n * agent-level enabled kinds from {@link ToolBuildOptions} and produces a narrowed entry;\n * without either, emits the full surface unchanged. The registry supplies the rest of\n * the metadata.\n */\nexport const analyseDataBuilder =\n (metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n }): ToolEntryBuilder<ToolState, AnalyseDataScope> =>\n (options) =>\n buildAnalyseDataEntry(options, metadata);\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 `analyseData` 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 queued for the NEXT setRoute call (origin / stops / destination not yet committed to a route). ' +\n 'Distinct from `recallRoutes({ id })`, which returns the waypoints of an existing calculated route entry. ' +\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 * Sandbox-running processing tool. Counterpart to `analyseData`: accepts any\n * combination of places / routes / incidents / customGeometries /\n * trafficAreaAnalytics / byod entries as inputs and produces a NEW places entry,\n * a NEW custom-geometries entry, a NEW BYOD entry, a camera fit, or any\n * combination of those, from a single sandbox run.\n *\n * Output disambiguation:\n * • `places` returned → writes a NEW places entry; `placeConnections` and\n * inline `geometries` (when also present) are attached to that entry.\n * • `geometries` returned without `places` → writes a NEW custom-geometries entry.\n * • `byod` returned alone → writes a NEW BYOD entry.\n * • `fitOnMap` alone → camera move only (no entry written).\n *\n * **Scope + enablement.** Shared scaffolding in `tools/shared/entry-kinds.ts` builds\n * the per-kind input fields, the per-kind sandbox / schema docs, the scope schema and\n * the \"at least one of …\" message. This file only adds the tool-specific surface:\n * the return-shape preamble, `routeUtils`, fit-on-map / show / connectMarkerType /\n * label / entryId / operation, the description, the result validator and the executor.\n */\n\nimport {\n bboxFromGeoJSON,\n type CommonPlaceProps,\n calculateProgressAtRoutePoint,\n getCoordinateAtRouteProgress,\n getProgressAtNearestRoutePoint,\n getRouteProgressBetween,\n getRouteProgressForSection,\n getSectionBBox,\n type Places,\n type PolygonFeature,\n} from '@tomtom-org/maps-sdk/core';\nimport type { PlaceConnectionDisplay } from '@tomtom-org/maps-sdk/map';\nimport * as turf from '@turf/turf';\nimport type { FeatureCollection as GeoJSONFeatureCollection, GeoJsonProperties } from 'geojson';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport {\n ALL_ENTRY_DATA_KINDS,\n applyFitOnMap,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxCodePrompt,\n CROSS_KIND_OPS_DOC,\n type EntryKindScope,\n FIT_ON_MAP_DOC,\n type FitOnMapInput,\n fittedReportSchema,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n type GeometriesMeta,\n geometriesIdSchema,\n hasAnyEntryIds,\n hidePreviousEntriesSchema,\n hidePreviousShownEntries,\n isFitOnMapInput,\n isPolygonFeature,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n placesEntryIdHintSchema,\n prepareMultiInputs,\n resolveActiveKinds,\n runSandboxedFn,\n showModeSchema,\n showPlacesSchema,\n showResultsOnMap,\n skippedSourceSchema,\n} from '../shared';\nimport { buildPlacesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Per-turn scope kinds that `processData` understands. Identical to {@link EntryDataKind}.\n *\n * @group Agent Toolkit\n */\nexport type ProcessDataKind = EntryDataKind;\n\n/**\n * Per-turn scope for `processData`. Emitted by the classifier in `toolScopes.processData`\n * when only a subset of input kinds is relevant.\n *\n * @group Agent Toolkit\n */\nexport type ProcessDataScope = EntryKindScope;\n\n/**\n * Default scope schema for processData — accepts every {@link EntryDataKind}. When the\n * agent narrows the enabled kinds via `createMapAgent`'s `dataEntries`, the builder\n * produces a narrower schema in {@link buildProcessDataEntry}.\n */\nexport const processDataScopeSchema = buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS);\n\n// `routeUtils` namespace — same set processRoutes injects, reused here so any\n// routes input can be sliced by section/progress without re-implementing it.\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, only useful when `routesEntryIDs` is set):\\n' +\n '• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>`.\\n' +\n '• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startIdx, endIdx)`.\\n' +\n '• `calculateProgressAtRoutePoint(route, pathIndex)`.\\n' +\n '• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }`.\\n' +\n '• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point to the line.\\n' +\n '• `bboxFromGeoJSON(featureOrCollection)`.';\n\nconst PROCESS_DATA_SANDBOX_PARAMS = [...MULTI_INPUT_SANDBOX_PARAMS, 'routeUtils'] as const;\n\nconst showProducedGeometriesSchema = z.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').optional(),\n});\n\nconst sharedProcessDataReport = {\n sourceIds: z\n .array(geometriesIdSchema)\n .optional()\n .describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only when \\`geometriesEntryIDs\\` was set).`),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\n fitted: fittedReportSchema.optional(),\n} as const;\n\n/** Output schema for process-data. */\nexport const processDataOutputSchema = z.union([\n // Places branch: code returned `places`. Attached connections + polygons go on the new entry.\n buildPlacesOutputSchema({}).extend({\n placesEntryId: z.string(),\n label: z.string().optional(),\n connectionsRendered: z.number().optional(),\n placesGeometryCount: z\n .number()\n .optional()\n .describe('Polygons attached to the new places entry (when `geometries` was returned alongside).'),\n shownPlaces: z.boolean().optional(),\n ...sharedProcessDataReport,\n }),\n // Custom-geometries branch: code returned `geometries` WITHOUT `places`.\n z.object({\n customGeometriesEntryId: geometriesIdSchema,\n customGeometriesCount: z.number(),\n label: z.string(),\n operation: z.string().optional(),\n shownCustomGeometries: z.boolean().optional(),\n ...sharedProcessDataReport,\n }),\n // BYOD branch: code returned `byod` WITHOUT `places`/`geometries`. Writes a new BYOD entry.\n z.object({\n byodEntryId: z.string(),\n byodFeatureCount: z.number(),\n label: z.string(),\n fitted: fittedReportSchema.optional(),\n }),\n // Fit-only branch: code returned `fitOnMap` alone.\n z.object({ fitted: fittedReportSchema }),\n toolErrorSchema,\n]);\n\n/** Build the scope-aware `code` field description for process-data. */\nexport const buildProcessDataCodeDoc = (\n enabled: readonly EntryDataKind[],\n scope: ProcessDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n // Deep per-kind schema docs only appear once the classifier has committed to specific\n // kinds. The unscoped path stays terse (terse one-line field hints already live in the\n // input-field describes; the code-doc just needs the operational reference).\n const schemaBlocks = scope ? buildEntryKindSchemaDocs(active) : '';\n const showCrossKind = !!scope && active.length > 1;\n const showTrafficNote = !!scope && active.includes('trafficAreaAnalytics');\n const trafficNote = showTrafficNote\n ? '`trafficAreaAnalytics` is a FeatureCollection of tile/hex regions whose `feature.properties` carry metric ' +\n 'fields (`congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`); use ' +\n '`turf.booleanPointInPolygon` / `turf.lineIntersect` to bridge with places/routes.\\n\\n'\n : '';\n const routeUtilsDoc = !!scope && active.includes('routes') ? `${ROUTE_UTILS_DOC}\\n\\n` : '';\n const geometriesProps = !!scope && active.includes('customGeometries') ? `${GEOMETRIES_PROPS_DOC}\\n\\n` : '';\n const byodOutputLine = active.includes('byod')\n ? '• `byod: FeatureCollection` — writes a NEW BYOD entry (any GeoJSON: Point/Line/Polygon/mixed). Use for ' +\n 'derived BYOD layers (e.g. cleaned/filtered customer data).\\n'\n : '';\n // Per-kind sandbox-doc one-liners — only when scope commits to specific kinds (matches\n // analyseData's gating; otherwise the input-field describes already carry enough info).\n const sandboxList = scope ? `${buildEntryKindSandboxDocs(active)}\\n` : '';\n return (\n 'Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`. At least one of ' +\n '`places` / `geometries` / `fitOnMap` / `byod` must be present. ' +\n '`places` (with optional attached `geometries`), standalone `geometries`, and `byod` are MUTUALLY ' +\n 'EXCLUSIVE — only one is written per call, others are dropped. `fitOnMap` composes with any of them.\\n\\n' +\n '• `places: Places` — writes a NEW places entry. Filters/merges/subsets of `places.features`, or computed point ' +\n 'features (must conform to the Place schema).\\n' +\n '• `placeConnections: { from, to, id? }[]` — lines between output places (endpoints: Place or place id). ' +\n \"Requires `places`. Drawn on the new entry's PlacesModule.\\n\" +\n '• `geometries: PolygonFeature[]` — polygons (Polygon/MultiPolygon, stable `id` recommended). ' +\n 'When `places` is also present, attached to the new places entry; when `places` is absent, written as a NEW ' +\n 'custom-geometries entry.\\n' +\n '• `fitOnMap` — bbox or `{ bbox, padding?, animate? }`. Camera move; when alone, no entry written.\\n' +\n byodOutputLine +\n '\\n' +\n `${buildSandboxCodePrompt(PROCESS_DATA_SANDBOX_PARAMS)}\\n\\n` +\n sandboxList +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. ' +\n trafficNote +\n (showCrossKind ? `${CROSS_KIND_OPS_DOC}\\n\\n` : '') +\n routeUtilsDoc +\n `${FIT_ON_MAP_DOC}\\n\\n` +\n geometriesProps +\n schemaBlocks\n );\n};\n\n/** Build the scope-aware processData input schema. */\nexport const buildProcessDataSchema = (enabled: readonly EntryDataKind[], scope: ProcessDataScope | undefined) => {\n const active = resolveActiveKinds(enabled, scope);\n const requiredHint = `At least one of ${buildEntryFieldList(active)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(active, { verb: 'process', requiredHint }),\n };\n\n shape.code = z.string().describe(buildProcessDataCodeDoc(enabled, scope));\n shape.label = z\n .string()\n .optional()\n .describe('Short label for the new entry (used by both places and custom-geometries paths).');\n shape.entryId = placesEntryIdHintSchema;\n shape.operation = z\n .string()\n .optional()\n .describe(\n 'Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance ' +\n 'on a new custom-geometries entry. Ignored on the places path.',\n );\n // `show` sub-fields are conditional on the corresponding kind being writable on the active\n // set: `places` write is possible whenever the code returns places (always allowed —\n // returning `places` is a sandbox concern, not a *EntryIDs concern); same for\n // `customGeometries`. We keep all three sub-fields exposed so the LLM can return any\n // supported kind without a scope twist. The executor only consults the matching field.\n shape.show = z\n .object({\n places: showPlacesSchema.optional().describe('Render the new places entry (when `places` was returned).'),\n placesGeometries: z\n .object({\n theme: z.enum(['filled', 'outline', 'inverted']).optional(),\n mode: showModeSchema,\n })\n .optional()\n .describe(\n 'Render the polygons attached to the new places entry (when both `places` and `geometries` were returned).',\n ),\n customGeometries: showProducedGeometriesSchema\n .optional()\n .describe('Render the new custom-geometries entry (when `geometries` was returned WITHOUT `places`).'),\n })\n .optional()\n .describe(\n 'Optional per-output render knobs. The executor only consults the field matching what the code returned ' +\n '(so leaving e.g. `customGeometries` set when `places` was returned is a silent no-op).',\n );\n shape.connectMarkerType = z\n .enum(['pin', 'base-map'])\n .optional()\n .describe(\n 'Which PlacesModule renders `placeConnections`. Default: \"pin\". Match the module holding the endpoints.',\n );\n\n return z.object(shape).refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(active) });\n};\n\n/** Default full-surface input schema for process-data — every kind enabled, no per-turn scope. */\nexport const processDataSchema = buildProcessDataSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\n/** Build the scope-aware processData tool description. */\nexport const buildProcessDataDescription = (\n enabled: readonly EntryDataKind[],\n scope: ProcessDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const kindList = active.join(' / ');\n const contrast =\n 'CONTRAST `analyseData`, which attaches JSON/chart metadata to existing entries and never renders.';\n if (scope) {\n return (\n `Run dynamic JS over ${kindList} entries to produce NEW renderable map data — a places, ` +\n 'custom-geometries, or BYOD entry (with optional `placeConnections` / attached polygon footprints), ' +\n `and/or a \\`fitOnMap\\` camera move. ${contrast} Returns ` +\n '`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`: when `places` is returned, inline ' +\n '`geometries` attach to it; otherwise they become a standalone custom-geometries entry. Inputs are ' +\n '`undefined` when their `*EntryIDs` argument is omitted — guard before reading.'\n );\n }\n return (\n `Run dynamic JS over any combination of ${kindList} entries to produce NEW renderable map data — a places, ` +\n 'custom-geometries, or BYOD entry (with optional `placeConnections` / attached polygon footprints), ' +\n `and/or a \\`fitOnMap\\` camera move. ${contrast} Returns ` +\n '`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`: when `places` is returned, inline ' +\n '`geometries` attach to it as place footprints; otherwise they become a standalone custom-geometries entry. ' +\n '`routeUtils` is exposed alongside `h3` / `turf` for route section / progress work. Inputs are `undefined` ' +\n 'when their `*EntryIDs` argument is omitted — guard before reading.'\n );\n};\n\n/** Default full-surface description — every kind enabled. */\nexport const processDataDescription = buildProcessDataDescription(ALL_ENTRY_DATA_KINDS, undefined);\n\nconst buildProcessDataScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((k) => `\"${k}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` ` +\n 'listing only the entry kinds the user query touches. Omit when truly cross-kind.'\n );\n};\n\ntype ProcessResult = {\n places?: Places;\n placeConnections?: PlaceConnectionDisplay[];\n geometries?: PolygonFeature[];\n fitOnMap?: FitOnMapInput;\n byod?: GeoJSONFeatureCollection;\n};\n\n// Both BYOD and Places outputs are FeatureCollections with no further per-feature checks at this\n// layer — keep one shape check and let the call site choose the narrow it wants.\nconst isFeatureCollectionLike = <T>(value: unknown): value is T => {\n if (!value || typeof value !== 'object') return false;\n const candidate = value as { type?: unknown; features?: unknown };\n return candidate.type === 'FeatureCollection' && Array.isArray(candidate.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\n// Walk the raw `geometries` value, narrow it to `PolygonFeature[]` with a pinpointed\n// error message on the first bad element. Splitting validation out keeps the type\n// predicate flowing into the returned `ProcessResult` without any `as` casts.\nconst validateGeometries = (raw: unknown): { value: PolygonFeature[] } | { error: string } => {\n if (!Array.isArray(raw)) {\n return { error: '`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features.' };\n }\n const validated: PolygonFeature[] = [];\n for (let i = 0; i < raw.length; i++) {\n const item = raw[i];\n // Generic-explicit so the narrowed `item` matches `PolygonFeature`'s default `GeoJsonProperties`\n // (otherwise the predicate's `<P = unknown>` default leaks `PolygonFeature<unknown>` here).\n if (!isPolygonFeature<GeoJsonProperties>(item)) {\n return { error: `\\`geometries[${i}]\\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.` };\n }\n validated.push(item);\n }\n return { value: validated };\n};\n\n// `validateField` collapses the \"either the optional field is missing OR it passes the shape check\"\n// pattern into one helper, so `validateProcessOutput` stays under the per-function cognitive\n// complexity cap (each per-field block was a nested `if` previously). Returning `value: undefined`\n// for an absent field lets callers assign unconditionally.\nconst validateField = <T>(\n raw: unknown,\n check: (value: unknown) => value is T,\n errorMessage: string,\n): { value: T | undefined } | { error: string } => {\n if (raw === undefined) return { value: undefined };\n if (!check(raw)) return { error: errorMessage };\n return { value: raw };\n};\n\n// Treat an empty FeatureCollection as \"no content produced\". Write sites downstream use truthy\n// checks (`if (processed)` / `if (producedByod)`), and the joint-constraint check below counts\n// fields as \"set\" by `!== undefined`. Without this normalisation, returning `{ features: [] }`\n// would slip past both — writing an empty places/byod entry while a non-empty geometries / byod\n// return on the same call gets silently dropped (single-write-per-call semantics).\nconst nonEmptyFeatureCollection = <T extends { features: readonly unknown[] }>(fc: T | undefined): T | undefined =>\n fc && fc.features.length > 0 ? fc : undefined;\n\n// Joint-shape rule: at least one writable output must be present, and placeConnections may only\n// accompany places. Split out so `validateProcessOutput`'s per-field validations stay flat.\nconst checkProcessJointConstraints = (result: ProcessResult): string | undefined => {\n const noOutput =\n result.places === undefined &&\n result.geometries === undefined &&\n result.fitOnMap === undefined &&\n result.byod === undefined;\n if (noOutput) {\n return 'At least one of `places`, `geometries`, `fitOnMap`, `byod` must produce content (empty arrays / FeatureCollections do not count).';\n }\n if (result.places === undefined && result.placeConnections !== undefined) {\n return '`placeConnections` requires `places` to be returned alongside.';\n }\n return undefined;\n};\n\nconst validateProcessOutput = (raw: unknown): ProcessResult | string => {\n if (!raw || typeof raw !== 'object') {\n return 'Process code must return `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`.';\n }\n const candidate = raw as {\n places?: unknown;\n placeConnections?: unknown;\n geometries?: unknown;\n fitOnMap?: unknown;\n byod?: unknown;\n };\n\n const result: ProcessResult = {};\n\n const places = validateField<Places>(\n candidate.places,\n isFeatureCollectionLike<Places>,\n '`places` must be a `Places` FeatureCollection of Point features.',\n );\n if ('error' in places) return places.error;\n result.places = nonEmptyFeatureCollection(places.value);\n\n const placeConnections = validateField(\n candidate.placeConnections,\n isConnectionArray,\n '`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).',\n );\n if ('error' in placeConnections) return placeConnections.error;\n result.placeConnections = placeConnections.value;\n\n if (candidate.geometries !== undefined) {\n const validated = validateGeometries(candidate.geometries);\n if ('error' in validated) return validated.error;\n // Empty array drops to undefined for the same reason as `nonEmptyFeatureCollection` above —\n // joint-constraint + downstream truthy checks treat absent and empty consistently.\n result.geometries = validated.value.length > 0 ? validated.value : undefined;\n }\n\n const fitOnMap = validateField(\n candidate.fitOnMap,\n isFitOnMapInput,\n '`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.',\n );\n if ('error' in fitOnMap) return fitOnMap.error;\n result.fitOnMap = fitOnMap.value;\n\n const byod = validateField<GeoJSONFeatureCollection>(\n candidate.byod,\n isFeatureCollectionLike<GeoJSONFeatureCollection>,\n '`byod` must be a GeoJSON FeatureCollection (any geometry types).',\n );\n if ('error' in byod) return byod.error;\n result.byod = nonEmptyFeatureCollection(byod.value);\n\n const jointError = checkProcessJointConstraints(result);\n return jointError ?? result;\n};\n\ntype ProcessDataInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n code: string;\n label?: string;\n entryId?: string;\n operation?: string;\n show?: {\n places?: z.infer<typeof showPlacesSchema>;\n placesGeometries?: { theme?: 'filled' | 'outline' | 'inverted'; mode?: 'add' | 'replace' };\n customGeometries?: z.infer<typeof showProducedGeometriesSchema>;\n };\n connectMarkerType?: 'pin' | 'base-map';\n};\n\ntype ProcessOutput = z.infer<typeof processDataOutputSchema>;\n\n// Write the BYOD branch's output. No places/geometries override — BYOD layers manage their own\n// visibility via state.byod, so we just write the entry and optionally fit the camera.\nconst writeByodResult = async (\n state: ToolState,\n params: ProcessDataInput,\n producedByod: GeoJSONFeatureCollection,\n fitOnMap: FitOnMapInput | undefined,\n): Promise<ProcessOutput> => {\n const { label, entryId } = params;\n const byodLabel = label ? `processed: ${label}` : `processed byod (${producedByod.features.length})`;\n const newByodEntryId = await state.byod.addEntry(producedByod, byodLabel, {\n ...(entryId && { explicitId: entryId }),\n });\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n return {\n byodEntryId: newByodEntryId,\n byodFeatureCount: producedByod.features.length,\n label: byodLabel,\n ...(fitted && { fitted }),\n };\n};\n\n// Optional connection rendering — pulled out so its try/catch + module-access doesn't bloat the\n// places-path branch counter.\nconst renderConnections = async (\n state: ToolState,\n newPlacesEntryId: string,\n placeConnections: PlaceConnectionDisplay[],\n connectMarkerType: 'pin' | 'base-map' | undefined,\n): Promise<{ count: number } | { error: string }> => {\n try {\n const placesModule = await state.places.getEntryPlacesModule(newPlacesEntryId, connectMarkerType ?? 'pin');\n await placesModule.showConnections(placeConnections);\n return { count: placeConnections.length };\n } catch (error) {\n return { error: `Failed to render connections: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n// Places branch — writes the entry, optionally renders it, attaches polygons / connections, and\n// fits the camera. Returns the same shape as the executor's places branch did inline.\nconst writePlacesResult = async (\n state: ToolState,\n params: ProcessDataInput,\n validated: ProcessResult & { places: Places },\n geometriesMeta: GeometriesMeta,\n): Promise<ProcessOutput> => {\n const { label, entryId, show, connectMarkerType, geometriesEntryIDs } = params;\n const { places: processed, placeConnections, geometries: producedGeometries, fitOnMap } = validated;\n\n const count = processed.features.length;\n const labelPrefix = label ? `processed: ${label}` : 'processed';\n const placeWord = count === 1 ? 'place' : 'places';\n const entryLabel = `${labelPrefix} (${count} ${placeWord})`;\n const newPlacesEntryId = await state.places.addPlaceResult(\n processed,\n entryLabel,\n entryId,\n placeConnections,\n producedGeometries as PolygonFeature<CommonPlaceProps>[] | undefined,\n );\n\n const shownPlaces = show?.places ? await showResultsOnMap(state, [newPlacesEntryId], show.places) : undefined;\n\n let placesGeometryCount: number | undefined;\n if (producedGeometries?.length) {\n placesGeometryCount = producedGeometries.length;\n if (show?.placesGeometries) {\n await state.places.showPlaceGeometries(\n producedGeometries as PolygonFeature<CommonPlaceProps>[],\n show.placesGeometries.theme ?? 'outline',\n show.placesGeometries.mode ?? 'replace',\n newPlacesEntryId,\n );\n }\n }\n\n let connectionsRendered: number | undefined;\n if (placeConnections) {\n const rendered = await renderConnections(state, newPlacesEntryId, placeConnections, connectMarkerType);\n if ('error' in rendered) return rendered;\n connectionsRendered = rendered.count;\n }\n\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n const storedLabel = state.places.entries.find((entry) => entry.id === newPlacesEntryId)?.label;\n\n return {\n ...summarizePlaces(processed),\n placesEntryId: newPlacesEntryId,\n ...(storedLabel && { label: storedLabel }),\n ...(shownPlaces && { shownPlaces: true }),\n ...(connectionsRendered !== undefined && { connectionsRendered }),\n ...(placesGeometryCount !== undefined && { placesGeometryCount }),\n ...(geometriesEntryIDs && { sourceIds: geometriesMeta.sourceIds }),\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n ...(fitted && { fitted }),\n };\n};\n\n// Custom-geometries branch — writes a new entry from the produced polygons and optionally renders.\nconst writeCustomGeometriesResult = async (\n state: ToolState,\n params: ProcessDataInput,\n producedGeometries: PolygonFeature[],\n fitOnMap: FitOnMapInput | undefined,\n geometriesMeta: GeometriesMeta,\n): Promise<ProcessOutput> => {\n const { label, entryId, operation, show, geometriesEntryIDs } = params;\n const customLabel = label ? `processed: ${label}` : `processed geometries (${producedGeometries.length})`;\n const newCustomEntryId = await state.customGeometries.addEntry(\n producedGeometries as PolygonFeature<CommonPlaceProps>[],\n { sourceIds: geometriesMeta.sourceIds, ...(operation && { operation }) },\n customLabel,\n entryId,\n );\n\n let shownCustomGeometries = false;\n if (show?.customGeometries) {\n await hidePreviousShownEntries(\n state.customGeometries,\n [newCustomEntryId],\n show.customGeometries.hidePreviousEntries,\n );\n await state.customGeometries.showEntry(\n newCustomEntryId,\n show.customGeometries.theme ?? 'outline',\n show.customGeometries.mode ?? 'replace',\n );\n shownCustomGeometries = true;\n }\n\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n const customGeometriesEntryId: GeometriesId = { kind: 'customGeometries', id: newCustomEntryId };\n\n return {\n customGeometriesEntryId,\n customGeometriesCount: producedGeometries.length,\n label: customLabel,\n ...(operation && { operation }),\n ...(geometriesEntryIDs && { sourceIds: geometriesMeta.sourceIds }),\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n ...(shownCustomGeometries && { shownCustomGeometries: true }),\n ...(fitted && { fitted }),\n };\n};\n\nexport const executeProcessData = async (params: ProcessDataInput, state: ToolState): Promise<ProcessOutput> => {\n const {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n code,\n } = params;\n\n const prepared = await prepareMultiInputs(\n {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n state,\n );\n if ('error' in prepared) return { error: prepared.error };\n const { sandbox, geometries: geometriesMeta } = prepared.value;\n\n const sandboxResult = await runSandboxedFn(\n code,\n PROCESS_DATA_SANDBOX_PARAMS,\n [...packSandboxArgs(sandbox, { h3, turf }), routeUtils],\n 'Process',\n );\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 const { places: processed, geometries: producedGeometries, fitOnMap, byod: producedByod } = validated;\n\n // Places + BYOD in the same return aren't supported — places path takes precedence when both\n // are present (BYOD is silently dropped to keep single-write-per-call semantics).\n if (processed) {\n return writePlacesResult(state, params, { ...validated, places: processed }, geometriesMeta);\n }\n if (producedGeometries?.length) {\n return writeCustomGeometriesResult(state, params, producedGeometries, fitOnMap, geometriesMeta);\n }\n if (producedByod) {\n return writeByodResult(state, params, producedByod, fitOnMap);\n }\n // Fit-only: no entry produced, just move the camera.\n if (fitOnMap) {\n return { fitted: applyFitOnMap(state, fitOnMap) };\n }\n return { error: 'Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`.' };\n};\n\n/**\n * Scope-aware builder for the processData tool. Reads classifier-resolved `scope` AND the\n * agent's `enabledDataKinds` from {@link ToolBuildOptions} so disabled kinds disappear from\n * the description, schema, code-doc and classifier scope. The registry composes this with\n * static classifier metadata (tags / examples / related / depends / classificationPrompt)\n * so the registry remains the single source of truth for those fields.\n */\nexport const buildProcessDataEntry = (\n options: ToolBuildOptions<ProcessDataScope>,\n metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n },\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n return {\n description: buildProcessDataDescription(enabled, options.scope),\n inputSchema: buildProcessDataSchema(enabled, options.scope),\n outputSchema: processDataOutputSchema,\n execute: executeProcessData as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildProcessDataScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/**\n * Builder for the `processData` default tool entry. Reads classifier-resolved scope and\n * the agent's enabled kinds from {@link ToolBuildOptions} and produces a narrowed entry;\n * without either, emits the full surface unchanged.\n */\nexport const processDataBuilder =\n (metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n }): ToolEntryBuilder<ToolState, ProcessDataScope> =>\n (options) =>\n buildProcessDataEntry(options, metadata);\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 entryId: z\n .string()\n .optional()\n .describe(\n 'Traffic-area-analytics entry id to query (e.g. \"tta-0\"). Omit to query the most recently added entry. ' +\n 'Use `recallState` to list available ids.',\n ),\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 { entryId, granularity = 'daily', metric, startDate, endDate, hourStart, hourEnd, dayOfWeek } = params;\n\n const entry = entryId ? state.trafficAreaAnalytics.findById(entryId) : state.trafficAreaAnalytics.latestEntry;\n if (!entry) {\n return {\n error: entryId\n ? `No traffic-area-analytics entry with id \"${entryId}\". Use recallState to list available ids.`\n : 'No analytics entries available. Call getTrafficAreaAnalytics first.',\n };\n }\n const analytics = entry.data;\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 the source entry's visualization state so the LLM knows what's displayed (if anything).\n const vizConfig = entry._config;\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 { toolErrorSchema } from '../shared-output-schemas';\n\nconst sourceSchema = z.union([\n z.object({ kind: z.literal('integrator'), description: z.string().optional() }),\n z.object({ kind: z.literal('url'), url: z.string() }),\n z.object({ kind: z.literal('inline'), description: z.string().optional() }),\n]);\n\nconst refSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number(),\n geometryTypes: z.array(z.string()),\n source: sourceSchema,\n shown: z.boolean(),\n});\n\nconst detailSchema = refSchema.extend({\n features: z.unknown().describe(\"GeoJSON FeatureCollection of the entry's features.\"),\n});\n\nexport const recallByodOutputSchema = z.union([\n z.object({ entries: z.array(refSchema), entryMode: z.enum(['single', 'multiple']) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallByodSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.'),\n});\n\nexport const recallByodDescription =\n 'List or inspect BYOD (bring-your-own-data) GeoJSON layers in session state. These come from either ' +\n 'programmatic seeding by the embedding app OR from `addByodLayer` calls. Call this BEFORE passing ids ' +\n 'to `byodEntryIDs` on `analyseData` / `processData` / `updateByodDisplay` — never guess. ' +\n 'No args → index (id, label, featureCount, geometryTypes, source, shown); pass `id` → full FeatureCollection.';\n\nconst collectGeometryTypes = (data: { features: Array<{ geometry?: { type?: string } | null }> }): string[] => {\n const types = new Set<string>();\n for (const feature of data.features) {\n const t = feature.geometry?.type;\n if (t) types.add(t);\n }\n return [...types];\n};\n\nexport const executeRecallByod = async (\n params: z.infer<typeof recallByodSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallByodOutputSchema>> => {\n if (!params.id) {\n const entries = state.byod.entries.map((entry) => ({\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.data.features.length,\n geometryTypes: collectGeometryTypes(entry.data),\n source: entry.source,\n shown: !!entry._shown,\n }));\n return { entries, entryMode: state.byod.entryMode };\n }\n const entry = state.byod.findById(params.id);\n if (!entry) {\n return { error: `No BYOD entry with id \"${params.id}\". Call recallByod with no args to list available IDs.` };\n }\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.data.features.length,\n geometryTypes: collectGeometryTypes(entry.data),\n source: entry.source,\n shown: !!entry._shown,\n features: entry.data,\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: \"customGeometries\", 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 `processData`. ALWAYS call this before passing ids to `processData` or `analyseData` ' +\n '(`geometriesEntryIDs`); 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: 'customGeometries', 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\ntype Detail = z.infer<typeof detailSchema>;\n\nconst detailForPlaces = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\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 const analyses = summariseAnalyses(entry._analysis);\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 ...(analyses && { analyses }),\n };\n};\n\nconst detailForRanges = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\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((range) => range.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\nconst detailForCustomGeometries = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n const entry = state.customGeometries.findById(id.id);\n if (!entry) return { error: `No custom-geometries entry with id \"${id.id}\"` };\n const analyses = summariseAnalyses(entry._analysis);\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 ...(analyses && { analyses }),\n };\n};\n\nconst detailFor = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n switch (id.kind) {\n case 'places':\n return detailForPlaces(id, state);\n case 'ranges':\n return detailForRanges(id, state);\n case 'customGeometries':\n return detailForCustomGeometries(id, state);\n default:\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};\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 { FeatureFlags, ToolEntry, ToolEntryBuilder, ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport { buildPlacesOutputSchema, 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 shown: z.boolean().describe('Whether this entry is currently rendered on the map.'),\n markerType: z\n .enum(['pin', 'base-map', 'pin-clustered'])\n .optional()\n .describe('Which marker theme is rendering this entry (undefined when `shown` is false).'),\n analyses: z\n .array(analysisIndexSchema)\n .optional()\n .describe('Analyses already attached to this entry via analyseData (name + output format).'),\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\n/** Build the flag-aware output schema for recall-places. */\nexport const buildRecallPlacesOutputSchema = (flags: FeatureFlags) => {\n const detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n places: buildPlacesOutputSchema(flags),\n });\n return z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n ]);\n};\n\n/** Default-flag (`experimentalSearch: false`) output schema. */\nexport const recallPlacesOutputSchema = buildRecallPlacesOutputSchema({});\n\nexport const recallPlacesDescription =\n 'List / inspect places entries already in session state (from discoverPlaces / locatePlace / processData) ' +\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\n/**\n * Build the recall-places executor for a given {@link FeatureFlags} bag.\n * Controls whether the place summaries include the experimental `areaId` /\n * `areaCountry` / `areaTags` fields.\n */\nexport const buildExecuteRecallPlaces =\n (flags: FeatureFlags) =>\n async (\n params: z.infer<typeof recallPlacesSchema>,\n state: ToolState,\n ): Promise<z.infer<ReturnType<typeof buildRecallPlacesOutputSchema>>> => {\n const { id } = params;\n const allEntries = state.places.entries;\n const entryMode = state.places.entryMode;\n const shownEntryIds = state.places.shownEntryIds;\n\n if (!id) {\n const entries = [...allEntries]\n .sort((a, b) => b.timestamp - a.timestamp)\n .map(({ id, label, timestamp, places, _analysis }) => {\n const shown = shownEntryIds.has(id);\n const markerType = shown ? state.places.getShownMarkerType(id) : undefined;\n return {\n id,\n label,\n timestamp,\n featureCount: places.length,\n shown,\n ...(markerType && { markerType }),\n ...(_analysis?.length && {\n analyses: _analysis.map(({ name, outputFormat, timestamp, description }) => ({\n name,\n outputFormat,\n timestamp,\n ...(description && { description }),\n })),\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, flags);\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n places,\n entryMode,\n };\n };\n\n/** Default-flag (`experimentalSearch: false`) executor. */\nexport const executeRecallPlaces = buildExecuteRecallPlaces({});\n\n/**\n * Build a complete {@link ToolEntry} for `recallPlaces` for the given\n * {@link FeatureFlags}.\n */\nexport const buildRecallPlacesEntry = <S extends ToolState = ToolState>(\n flags: FeatureFlags,\n metadata: Omit<ToolEntry<S>, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>,\n): ToolEntry<S> => ({\n ...metadata,\n description: recallPlacesDescription,\n inputSchema: recallPlacesSchema,\n outputSchema: buildRecallPlacesOutputSchema(flags),\n execute: buildExecuteRecallPlaces(flags) as ToolEntry<S>['execute'],\n});\n\nconst recallPlacesMetadata = {\n classificationPrompt:\n 'List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processData, analyseData, updatePlacesDisplay).',\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 processData 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} satisfies Omit<ToolEntry, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>;\n\n/**\n * Builder for the recallPlaces default tool entry.\n */\nexport const recallPlacesBuilder: ToolEntryBuilder = (options) =>\n buildRecallPlacesEntry(options.featureFlags ?? {}, recallPlacesMetadata);\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';\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 shown: z.boolean().describe('Whether this entry is currently rendered on the map.'),\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 const shownEntryIds = state.routing.shownEntryIds;\n\n if (!id) {\n const entries = [...state.routing.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp, shown: shownEntryIds.has(id) }));\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// Slices wiped by `executeResetState`. Kept as a single source of truth so the\n// output schema's enum and the returned `cleared` array can't drift apart.\nconst CLEARED_SLICES = [\n 'places',\n 'routes',\n 'ranges',\n 'customGeometries',\n 'trafficAreaAnalytics',\n 'trafficIncidents',\n 'byod',\n 'mapPOIs',\n 'trafficFlow',\n 'mapStyle',\n] as const;\n\n/** Output schema for reset-state. */\nexport const resetStateOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z.array(z.enum(CLEARED_SLICES)).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 for (const entry of state.trafficAreaAnalytics.entries) {\n await entry._module?.clear();\n }\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. Every entry-owning slice with a `reset()`\n // needs to be wiped — missing one leaves stale entries that show up\n // in subsequent `recallState` / `recall*` calls even after a reset.\n state.places.reset();\n state.routing.reset();\n state.ranges.reset();\n state.customGeometries.reset();\n state.trafficAreaAnalytics.reset();\n state.trafficIncidents.reset();\n state.byod.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: [...CLEARED_SLICES],\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 { ENTRY_MODE_SLICE_NAMES, type EntryModeSlice, type EntryModeSliceName } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// `z.enum` needs a non-empty tuple. ENTRY_MODE_SLICE_NAMES is derived from a\n// `Record<EntryModeSliceName, true>` check at the type level, so the runtime\n// list is always non-empty whenever the union has at least one member.\nconst sliceSchema = z.enum(ENTRY_MODE_SLICE_NAMES as unknown as [EntryModeSliceName, ...EntryModeSliceName[]]);\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(\n 'Which entry-owning slice to reconfigure. Names match the keys on the agent state exactly — ' +\n '`routing` (not `routes`), `customGeometries` (not `geometries`), and so on.',\n ),\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 entry-owning slice between `multiple` and `single`. ' +\n `Supported slices: ${ENTRY_MODE_SLICE_NAMES.join(', ')}. ` +\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 // Index into state with the slice name — names match keys exactly, so\n // there is no `case 'routes': state.routing.…` style mismatch to maintain.\n const target = state[slice] as EntryModeSlice;\n\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 = target.entries.map((e) => e.id);\n\n await target.setEntryMode(mode);\n\n const afterIds = new Set(target.entries.map((e) => e.id));\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 { CLEARABLE_SLICE_NAMES, type ClearableMapSlice, type ClearableSliceName } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// Zod's z.enum needs a non-empty literal tuple; CLEARABLE_SLICE_NAMES is typed as readonly so\n// the cast just narrows it to the tuple-of-literals shape Zod expects. Order is irrelevant.\nconst layerEnum = z.enum(CLEARABLE_SLICE_NAMES as unknown as readonly [ClearableSliceName, ...ClearableSliceName[]]);\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(layerEnum),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for clear-map. */\nexport const clearMapSchema = z.object({\n layers: z\n .array(layerEnum)\n .optional()\n .describe(\n 'Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: ' +\n CLEARABLE_SLICE_NAMES.map((n) => `\\`${n}\\``).join(', ') +\n '. Map-only — slice histories (`entries`) are kept.',\n ),\n});\n\nexport const clearMapDescription =\n 'Hide everything a chosen set of state slices is rendering on the map (places, routing, ranges, ' +\n 'customGeometries, byod, trafficAreaAnalytics, trafficIncidents). Map-only — slice `entries` history ' +\n 'is untouched. Use to clean up before showing new results.';\n\n/**\n * Execute clear map.\n */\nexport const executeClearMap = async (params: z.infer<typeof clearMapSchema>, state: ToolState) => {\n const { layers } = params;\n const targets = layers && layers.length > 0 ? layers : CLEARABLE_SLICE_NAMES;\n try {\n for (const name of targets) {\n // Cast widens the slice instance to the ClearableMapSlice interface — the\n // CLEARABLE_SLICE_NAMES list is `satisfies`-guarded so every name reaches a\n // slice that actually implements `clearShown`.\n await (state[name] as unknown as ClearableMapSlice).clearShown();\n }\n return {\n success: true as const,\n cleared: [...targets] as ClearableSliceName[],\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 (obtain ids from `recallRoutes({ id })` detail mode or via `analyseData` over routes). 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-tile-incidents tool. */\nexport const getShownTileIncidentsOutputSchema = 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/** Tool schema for get-shown-tile-incidents. */\nexport const getShownTileIncidentsSchema = z.object({});\n\nexport const getShownTileIncidentsDescription =\n 'List the traffic incidents currently rendered by the TomTom traffic-tile overlay (the rasterised live tiles, ' +\n 'not the GeoJSON `getTrafficIncidents` service). Reads `state.trafficTiles.getTrafficIncidentsModule().getShown()` — ' +\n 'so it only reflects what the tile layer is showing in the current viewport. ' +\n 'For full TrafficIncident objects (with start/end times, road numbers, lanes, …) call `getTrafficIncidents` instead; ' +\n 'for \"incidents along this route\" read `route.properties.sections.traffic[]` via `analyseData`.';\n\n/** Execute get-shown-tile-incidents. */\nexport const executeGetShownTileIncidents = async (\n _params: z.infer<typeof getShownTileIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getShownTileIncidentsOutputSchema>> => {\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 rendered by the traffic-tile overlay.' };\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 read traffic-tile overlay incidents: ${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 { 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-tiles-base-map-layer-groups tool. */\nexport const toggleTilesBaseMapLayerGroupsOutputSchema = 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/** Tool schema for toggle-tiles-base-map-layer-groups. */\nexport const toggleTilesBaseMapLayerGroupsSchema = z.object({\n visible: z.boolean(),\n layerGroups: z.array(z.enum(baseMapLayerGroupNames)).describe('Vector-tile base-map layer groups to show or hide.'),\n});\n\nexport const toggleTilesBaseMapLayerGroupsDescription =\n 'Show / hide groups of vector-tile base-map style layers (water, buildings3D, roadLines, placeLabels, capitalLabels, …). ' +\n 'Operates on the underlying MapLibre style — orthogonal to anything rendered through PlacesModule / RoutingModule / ' +\n 'CustomGeoJSONModule (those layers are unaffected).';\n\n/** Execute toggle-tiles-base-map-layer-groups. */\nexport const executeToggleTilesBaseMapLayerGroups = async (\n params: z.infer<typeof toggleTilesBaseMapLayerGroupsSchema>,\n state: ToolState,\n) => {\n const { visible, layerGroups } = params;\n try {\n const baseMapModule = await state.baseMap.getBaseMapModule();\n baseMapModule.setVisible(visible, {\n layerGroups: {\n mode: 'include',\n names: layerGroups,\n },\n });\n return { success: true, visible, layerGroups };\n } catch (error) {\n return {\n error: `Failed to toggle tile base-map 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-tiles-pois tool. */\nexport const toggleTilesPOIsOutputSchema = 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/** Tool schema for toggle-tiles-pois. */\nexport const toggleTilesPOIsSchema = z.object({\n reset: z\n .boolean()\n .optional()\n .describe('Reset all tile-POI configuration to defaults. Other params ignored when true.'),\n allMapPOIsVisible: z\n .boolean()\n .optional()\n .describe('Overall visibility of the vector-tile POI icons baked into the base map style.'),\n filterCategories: z\n .object({\n show: z\n .enum(['only', 'all_except'])\n .describe('\"only\": keep only these categories. \"all_except\": hide these categories.'),\n values: z\n .array(z.string())\n .describe(\n 'MapStylePOICategory / POICategoryGroup values (e.g. \"RESTAURANT\", \"FOOD_DRINKS_GROUP\"). ' +\n 'Fine-grained codes like \"ITALIAN_RESTAURANT\" not supported here.',\n ),\n })\n .optional()\n .describe('Restrict which built-in vector-tile POI categories are shown.'),\n});\n\nexport const toggleTilesPOIsDescription =\n 'Show/hide the built-in vector-tile POI icons baked into the base map style (restaurants, hotels, gas stations, …). ' +\n 'NOT the same as places shown via `discoverPlaces` / `updatePlacesDisplay` — those are session-state pins on the ' +\n 'PlacesModule, untouched by this tool. `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts ' +\n 'which categories show; `reset: true` restores defaults.';\n\n/** Execute toggle-tiles-pois. */\nexport const executeToggleTilesPOIs = async (\n params: z.infer<typeof toggleTilesPOIsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof toggleTilesPOIsOutputSchema>> => {\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 tile 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/** Tool schema for toggle-tiles-traffic-flow. */\nexport const toggleTilesTrafficFlowSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-tiles-traffic-flow tool. */\nexport const toggleTilesTrafficFlowOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficFlowVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for toggle-tiles-traffic-incidents. */\nexport const toggleTilesTrafficIncidentsSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-tiles-traffic-incidents tool. */\nexport const toggleTilesTrafficIncidentsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficIncidentsVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const toggleTilesTrafficFlowDescription =\n 'Toggle the vector-tile traffic-flow overlay (live coloured road segments). ' +\n 'NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical analytics) or `route.properties.sections.traffic[]` ' +\n '(per-route incidents). Just a base-map style layer on/off.';\n\nexport const toggleTilesTrafficIncidentsDescription =\n 'Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). ' +\n 'NOT the same as `getTrafficIncidents` (GeoJSON service → structured entries) or `analyseData` ' +\n '(sandbox over fetched entries). Just a base-map style layer on/off. ' +\n 'Use `getShownTileIncidents` to read what this overlay is currently rendering.';\n\n/** Execute toggle-tiles-traffic-flow. */\nexport const executeToggleTilesTrafficFlow = async (\n params: z.infer<typeof toggleTilesTrafficFlowSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n const trafficFlowModule = await state.trafficTiles.getTrafficFlowModule();\n trafficFlowModule.setVisible(visible);\n return { success: true, trafficFlowVisible: visible };\n } catch (error) {\n return {\n error: `Failed to toggle tile traffic flow: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n/** Execute toggle-tiles-traffic-incidents. */\nexport const executeToggleTilesTrafficIncidents = async (\n params: z.infer<typeof toggleTilesTrafficIncidentsSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n const trafficIncidentsModule = await state.trafficTiles.getTrafficIncidentsModule();\n trafficIncidentsModule.setVisible(visible);\n return { success: true, trafficIncidentsVisible: visible };\n } catch (error) {\n return {\n error: `Failed to toggle tile 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';\n\nconst actionSchema = z\n .enum(['show', 'hide', 'remove'])\n .describe(\n '`show` renders the listed entries (lazy-init their CustomGeoJSONModule); ' +\n '`hide` removes them from the map but keeps them in session state; ' +\n '`remove` hides AND drops them from history.',\n );\n\nexport const updateByodDisplaySchema = z\n .object({\n entryIds: z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n 'BYOD entry ids to act on (e.g. [\"byod-0\"]). Use `recallByod` to list. ' +\n 'Omit when `action: \"hide\"` and `clearAll: true` to hide every shown BYOD entry in one call.',\n ),\n action: actionSchema,\n clearAll: z\n .boolean()\n .optional()\n .describe(\n 'Only valid with `action: \"hide\"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.',\n ),\n hideOthers: z\n .boolean()\n .optional()\n .describe(\n 'Only valid with `action: \"show\"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).',\n ),\n })\n .refine(\n (v) => {\n if (v.action === 'hide' && v.clearAll) return true;\n return !!v.entryIds?.length;\n },\n { message: '`entryIds` is required unless `action: \"hide\"` with `clearAll: true`.' },\n )\n .refine((v) => !v.clearAll || v.action === 'hide', {\n message: '`clearAll: true` is only valid with `action: \"hide\"`.',\n })\n .refine((v) => !v.hideOthers || v.action === 'show', {\n message: '`hideOthers: true` is only valid with `action: \"show\"`.',\n });\n\nexport const updateByodDisplayOutputSchema = z.union([\n z.object({\n action: actionSchema,\n affectedIds: z.array(z.string()).describe('Entry ids that were actually shown / hidden / removed.'),\n shown: z.array(z.string()).describe('BYOD entry ids currently rendered on the map after this call.'),\n }),\n toolErrorSchema,\n]);\n\nexport const updateByodDisplayDescription =\n 'Toggle visibility (or drop) for one or more BYOD entries. Use after `addByodLayer` or after seeing entries ' +\n 'via `recallByod`. Three actions: `show` renders listed entries, `hide` removes them from the map but keeps ' +\n 'them in state, `remove` hides AND drops them from history. Pair `action: \"hide\"` with `clearAll: true` to ' +\n 'hide every shown BYOD entry in one call. Pair `action: \"show\"` with `hideOthers: true` to swap to a single ' +\n \"set of entries (replaces what's already on the map).\";\n\ntype BYODSlice = ToolState['byod'];\n\n// Reject the call up-front if any requested id is unknown — leaves the slice in its original\n// state rather than partially applying. Returns `undefined` when every id is valid.\nconst validateRequestedIds = (slice: BYODSlice, requested: readonly string[]): { error: string } | undefined => {\n for (const id of requested) {\n if (!slice.findById(id)) {\n return { error: `No BYOD entry with id \"${id}\". Call recallByod to list available IDs.` };\n }\n }\n return undefined;\n};\n\nconst showEntries = async (slice: BYODSlice, requested: readonly string[], hideOthers: boolean): Promise<string[]> => {\n const affected: string[] = [];\n if (hideOthers) {\n const requestedSet = new Set(requested);\n for (const id of slice.shownEntryIds) {\n if (requestedSet.has(id)) continue;\n await slice.hideEntry(id);\n affected.push(id);\n }\n }\n for (const id of requested) {\n await slice.showEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nconst hideEntries = async (slice: BYODSlice, requested: readonly string[], clearAll: boolean): Promise<string[]> => {\n const affected: string[] = [];\n const ids = clearAll ? slice.shownEntryIds : requested;\n for (const id of ids) {\n await slice.hideEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nconst removeEntries = async (slice: BYODSlice, requested: readonly string[]): Promise<string[]> => {\n const affected: string[] = [];\n for (const id of requested) {\n await slice.removeEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nexport const executeUpdateByodDisplay = async (\n params: z.infer<typeof updateByodDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateByodDisplayOutputSchema>> => {\n const slice = state.byod;\n const { action, clearAll, hideOthers } = params;\n const requested = params.entryIds ?? [];\n\n const validationProblem = validateRequestedIds(slice, requested);\n if (validationProblem) return validationProblem;\n\n let affected: string[];\n if (action === 'show') {\n affected = await showEntries(slice, requested, !!hideOthers);\n } else if (action === 'hide') {\n affected = await hideEntries(slice, requested, !!clearAll);\n } else {\n affected = await removeEntries(slice, requested);\n }\n\n return {\n action,\n affectedIds: affected,\n shown: [...slice.shownEntryIds],\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 mainColor: z\n .string()\n .optional()\n .describe('Sticky main colour now applied to every shown route, when set by this or a previous call.'),\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 mainColor: z\n .string()\n .optional()\n .describe(\n 'Recolor the route line + waypoint icons. CSS named colour (\"coral\", \"steelblue\") or hex (\"#FF0000\"). ' +\n 'Sticky — applied to every currently-shown entry AND remembered so subsequent shows pick it up automatically. ' +\n 'Can be set by itself (no add/remove/clear) to recolor without changing visibility.',\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 data.mainColor !== undefined,\n {\n message:\n 'updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, mainColor.',\n },\n );\n\nexport const updateRoutesDisplayDescription =\n 'Show, hide, swap, and recolor 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; with no ' +\n '`add`/`addMatching` it implicitly targets the most recently stored entry. ' +\n '`mainColor` recolors the route line + waypoint icons (sticky across entry swaps — applied to current shows AND remembered); ' +\n 'it does NOT auto-show a hidden entry, so set `add`/`addMatching` too if nothing is on the map yet. ' +\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 // `selectedIndex` implicitly targets \"the most recently stored entry\" when no\n // `add` / `addMatching` is supplied. `mainColor` is handled separately by the executor\n // (sticky, applies to whatever is shown — no implicit-latest add needed).\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\n// Apply the requested add / remove / clear / selectedIndex sequence and return the\n// before/after shown sets so the response layer can compute deltas. Sequential awaits — concurrent\n// show/hide on the routing slice races MapLibre's style mutation.\nconst applyAddRemove = async (\n state: ToolState,\n plan: { add: readonly string[]; remove: readonly string[]; clear: boolean; selectedIndex: number | undefined },\n): Promise<{ beforeShown: Set<string>; afterShown: ReadonlySet<string> }> => {\n const beforeShown = new Set(state.routing.shownEntryIds);\n if (plan.clear) {\n for (const id of state.routing.shownEntryIds) {\n await state.routing.hideEntry(id);\n }\n }\n for (const id of plan.remove) {\n await state.routing.hideEntry(id);\n }\n for (const id of plan.add) {\n await state.routing.showEntry(id);\n }\n if (plan.selectedIndex !== undefined) {\n await applySelectedIndex(state, plan.add, plan.selectedIndex);\n }\n return { beforeShown, afterShown: state.routing.shownEntryIds };\n};\n\n// Fit the camera around every currently-shown route. No-op when nothing is shown or the joined\n// feature collection has no computable bbox.\nconst fitCameraToShown = (state: ToolState, shownIds: ReadonlySet<string>): void => {\n const features: Routes['features'] = [];\n for (const entry of state.routing.entries) {\n if (shownIds.has(entry.id)) features.push(...entry.data.features);\n }\n if (features.length === 0) return;\n const bbox = bboxFromGeoJSON({ type: 'FeatureCollection', features });\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50 });\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, mainColor } = 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 // Persist mainColor BEFORE add/remove so newly-shown entries pick it up on first paint,\n // and apply to whatever is already on the map. Sticky for the lifetime of the slice.\n if (mainColor !== undefined) {\n state.routing.setMainColor(mainColor);\n }\n\n const { beforeShown, afterShown } = await applyAddRemove(state, {\n add: resolvedAdd,\n remove: resolvedRemove,\n clear: !!clear,\n selectedIndex,\n });\n\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 if (fitBounds ?? resolvedAdd.length > 0) {\n fitCameraToShown(state, afterShown);\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 const currentMainColor = state.routing.mainColor;\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n ...(currentMainColor !== undefined && { mainColor: currentMainColor }),\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 { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport type { LngLatBoundsLike } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { TrafficAreaAnalyticsEntry } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst renderKnobs = {\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 on the targeted entries.'),\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' (default), 'predefinedRange' (normalize to SDK range), 'currentRange' (normalize to live data range).\",\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} as const;\n\nconst summarySchema = z.object({\n id: z.string(),\n label: z.string(),\n activeMetric: z.string().optional(),\n displayMode: z.string().optional(),\n});\n\n/** Tool schema for update-traffic-area-analytics-display. */\nexport const updateTrafficAreaAnalyticsDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe('Entry ids to render (e.g. [\"tta-0\"]). Use `recallState` to list available ids.'),\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 fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added.',\n ),\n ...renderKnobs,\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.mode !== undefined ||\n data.metric !== undefined ||\n data.colorTheme !== undefined ||\n data.heightScale !== undefined ||\n data.scaleMode !== undefined ||\n data.filter !== undefined,\n {\n message:\n 'updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, mode, metric, colorTheme, heightScale, scaleMode, filter.',\n },\n );\n\n/** Output schema for the update-traffic-area-analytics-display tool. */\nexport const updateTrafficAreaAnalyticsDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z.array(summarySchema).describe('Traffic-area-analytics entries currently on the map.'),\n actuallyAdded: z.array(z.string()),\n actuallyRemoved: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\nexport const updateTrafficAreaAnalyticsDisplayDescription =\n 'Show, hide, swap, and restyle HISTORICAL traffic-area-analytics entries 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. ' +\n 'Render knobs (`mode`, `metric`, `colorTheme`, `heightScale`, `scaleMode`, `filter`) apply to every entry being added AND every entry that stays shown after this call. ' +\n 'When only render knobs are set with no add/remove/clear, implicitly targets the most recently added entry. ' +\n 'Not real-time — use `toggleTilesTrafficFlow` for the live vector-tile flow overlay.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\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.trafficAreaAnalytics.entries) {\n if (needles.some((needle) => entry.label.toLowerCase().includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\nconst resolveSelectors = (\n state: ToolState,\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, clear } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n // Implicit-latest: render knobs but no entry selectors → target the most recent entry.\n const onlyRenderKnobs =\n add === undefined &&\n addMatching === undefined &&\n remove === undefined &&\n removeMatching === undefined &&\n clear !== true;\n const hasRenderKnobs =\n params.mode !== undefined ||\n params.metric !== undefined ||\n params.colorTheme !== undefined ||\n params.heightScale !== undefined ||\n params.scaleMode !== undefined ||\n params.filter !== undefined;\n if (onlyRenderKnobs && hasRenderKnobs) {\n const latestId = state.trafficAreaAnalytics.entries.at(-1)?.id;\n if (!latestId) {\n return { error: 'No traffic-area-analytics entries available. Call getTrafficAreaAnalytics first.' };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.trafficAreaAnalytics.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown traffic-area-analytics entry ids: ${unknownAdd.join(', ')}. Use recallState to list available ids.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\nconst applyRenderKnobs = async (\n state: ToolState,\n entryId: string,\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n): Promise<void> => {\n const entry = state.trafficAreaAnalytics.findById(entryId);\n if (!entry) return;\n const defaultMetric = entry.data.properties?.metrics?.[0] ?? 'congestionLevel';\n const mode = params.mode ?? 'hexgrid-3d';\n const metric = params.metric ?? defaultMetric;\n const colorTheme = params.colorTheme ?? 'trafficLight';\n\n const module = await state.trafficAreaAnalytics.getEntryModule(entryId);\n module.setMode(mode);\n module.setMetric(metric);\n module.setColor(colorTheme);\n if (params.heightScale !== undefined || params.scaleMode !== undefined) {\n module.setHeight({\n ...(params.heightScale !== undefined && { maxHeightMeters: params.heightScale }),\n ...(params.scaleMode !== undefined && { scaleMode: params.scaleMode }),\n });\n }\n if (params.filter) {\n module.filter({ min: params.filter.min, max: params.filter.max });\n } else {\n module.clearFilter();\n }\n};\n\nconst summarizeEntry = (entry: TrafficAreaAnalyticsEntry): z.infer<typeof summarySchema> => ({\n id: entry.id,\n label: entry.label,\n ...(entry._config?.activeMetric && { activeMetric: entry._config.activeMetric }),\n ...(entry._config?.displayMode && { displayMode: entry._config.displayMode }),\n});\n\n// ---------------------------------------------------------------------------\n// Executor\n// ---------------------------------------------------------------------------\n\nexport const executeUpdateTrafficAreaAnalyticsDisplay = async (\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateTrafficAreaAnalyticsDisplayOutputSchema>> => {\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.trafficAreaAnalytics.shownEntryIds);\n\n if (params.clear) {\n for (const id of state.trafficAreaAnalytics.shownEntryIds) {\n await state.trafficAreaAnalytics.hideEntry(id);\n }\n }\n for (const id of resolvedRemove) {\n await state.trafficAreaAnalytics.hideEntry(id);\n }\n for (const id of resolvedAdd) {\n await state.trafficAreaAnalytics.showEntry(id);\n }\n\n const afterShown = state.trafficAreaAnalytics.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 // Render knobs apply to: every entry just added AND every entry currently shown after the call\n // (so existing entries pick up new mode/metric/colorTheme/filter).\n const targets = new Set<string>([...actuallyAdded, ...afterShown]);\n for (const id of targets) await applyRenderKnobs(state, id, params);\n\n const shouldFit = params.fitBounds ?? actuallyAdded.length > 0;\n if (shouldFit && afterShown.size > 0) {\n const features = state.trafficAreaAnalytics.entries\n .filter((e) => afterShown.has(e.id))\n .flatMap((e) => e.data.features);\n const bbox = bboxFromGeoJSON({ type: 'FeatureCollection', features });\n if (bbox) {\n state.baseMap.mapLibreMap.fitBounds(bbox as LngLatBoundsLike, { padding: 50, pitch: 45 });\n }\n }\n\n const shown = state.trafficAreaAnalytics.entries.filter((e) => afterShown.has(e.id)).map(summarizeEntry);\n\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown,\n actuallyAdded,\n actuallyRemoved,\n };\n } catch (error) {\n return {\n error: `Failed to update traffic-area-analytics display: ${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 update-waypoints-display tool. */\nexport const updateWaypointsDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n visible: z.boolean(),\n count: z.number(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for update-waypoints-display. */\nexport const updateWaypointsDisplaySchema = z.object({\n visible: z\n .boolean()\n .optional()\n .describe(\n 'Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.',\n ),\n});\n\nexport const updateWaypointsDisplayDescription =\n 'Show or hide the staged origin/stop/destination markers held in `state.routing.planningSlots` (the slots being ' +\n 'built up for the next `setRoute` — NOT the waypoints of a calculated route entry). ' +\n 'No route line is drawn; for that use `updateRoutesDisplay`. Requires at least one routes entry to host the module.';\n\n/** Execute update-waypoints-display. */\nexport const executeUpdateWaypointsDisplay = async (\n params: z.infer<typeof updateWaypointsDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateWaypointsDisplayOutputSchema>> => {\n const visible = params.visible ?? true;\n try {\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\n if (!visible) {\n await routingModule.clearWaypoints();\n return { success: true, visible: false, count: 0 };\n }\n\n const current = state.routing.planningSlots;\n await routingModule.showWaypoints(current);\n return { success: true, visible: true, count: current.length };\n } catch (error) {\n return {\n error: `Failed to update waypoints display: ${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 addByodLayerDescription,\n addByodLayerOutputSchema,\n addByodLayerSchema,\n analyseDataBuilder,\n executeAddByodLayer,\n executeFocusIncidents,\n executeGetCurrentWaypoints,\n executeQueryTrafficAnalytics,\n executeRecallByod,\n executeRecallGeometries,\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 processDataBuilder,\n queryTrafficAnalyticsDescription,\n queryTrafficAnalyticsOutputSchema,\n queryTrafficAnalyticsSchema,\n recallByodDescription,\n recallByodOutputSchema,\n recallByodSchema,\n recallGeometriesDescription,\n recallGeometriesOutputSchema,\n recallGeometriesSchema,\n recallPlacesBuilder,\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 executeGetShownTileIncidents,\n executeGetStandardMapStyles,\n executeSetLanguage,\n executeSetMapStandardStyle,\n executeToggleTilesBaseMapLayerGroups,\n executeToggleTilesPOIs,\n executeToggleTilesTrafficFlow,\n executeToggleTilesTrafficIncidents,\n executeUpdateByodDisplay,\n executeUpdatePlacesDisplay,\n executeUpdateRoutesDisplay,\n executeUpdateTrafficAreaAnalyticsDisplay,\n executeUpdateWaypointsDisplay,\n getShownTileIncidentsDescription,\n getShownTileIncidentsOutputSchema,\n getShownTileIncidentsSchema,\n getStandardMapStylesDescription,\n getStandardMapStylesOutputSchema,\n getStandardMapStylesSchema,\n setLanguageDescription,\n setLanguageOutputSchema,\n setLanguageSchema,\n setMapStandardStyleDescription,\n setMapStandardStyleOutputSchema,\n setMapStandardStyleSchema,\n toggleTilesBaseMapLayerGroupsDescription,\n toggleTilesBaseMapLayerGroupsOutputSchema,\n toggleTilesBaseMapLayerGroupsSchema,\n toggleTilesPOIsDescription,\n toggleTilesPOIsOutputSchema,\n toggleTilesPOIsSchema,\n toggleTilesTrafficFlowDescription,\n toggleTilesTrafficFlowOutputSchema,\n toggleTilesTrafficFlowSchema,\n toggleTilesTrafficIncidentsDescription,\n toggleTilesTrafficIncidentsOutputSchema,\n toggleTilesTrafficIncidentsSchema,\n updateByodDisplayDescription,\n updateByodDisplayOutputSchema,\n updateByodDisplaySchema,\n updatePlacesDisplayDescription,\n updatePlacesDisplayOutputSchema,\n updatePlacesDisplaySchema,\n updateRoutesDisplayDescription,\n updateRoutesDisplayOutputSchema,\n updateRoutesDisplaySchema,\n updateTrafficAreaAnalyticsDisplayDescription,\n updateTrafficAreaAnalyticsDisplayOutputSchema,\n updateTrafficAreaAnalyticsDisplaySchema,\n updateWaypointsDisplayDescription,\n updateWaypointsDisplayOutputSchema,\n updateWaypointsDisplaySchema,\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\n// Shared classifier-prompt framing for the two dynamic-JS data tools (`analyseData`,\n// `processData`). Both run user-authored code against the same set of injected inputs\n// (`places` / `routes` / `incidents` / `geometries` / `trafficAreaAnalytics`); only the *contract*\n// of what the code returns differs. Lifting the common framing here keeps the two\n// `classificationPrompt`s short — the per-tool prompt then only carries the verb (transforms vs\n// aggregates) plus disambiguating triggers.\nconst DATA_TOOL_INPUTS = 'places / routes / incidents / geometries / trafficAreaAnalytics / byod';\nconst DATA_TOOL_SHARED_FRAMING =\n `Dynamic-JS sandbox over any combination of ${DATA_TOOL_INPUTS} entries. ` +\n 'Each input arg is `undefined` when its `*EntryIDs` was omitted.';\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 'Show Times Square on the map',\n 'Pin Schiphol Airport on the map',\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 processData: processDataBuilder({\n classificationPrompt:\n `${DATA_TOOL_SHARED_FRAMING} ` +\n 'WRITES NEW renderable map entries — returns `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }` ' +\n 'to write a places, custom-geometries, or BYOD entry, move the camera, or any combination. ' +\n 'NOT for digests/charts/counts — those are `analyseData` (metadata only, never renders). ' +\n 'Use for: filter/merge places (set ops), connect places (hub-spoke), compute polygons ' +\n '(union/buffer/h3-coverage/difference), focus camera on route sections via `routeUtils.getSectionBBox`, ' +\n 'cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile).',\n tags: ['turf', 'h3', 'geometry', 'place', 'route', 'aggregation', 'connections'],\n examples: [\n // places-only: filter\n 'processData({ placesEntryIDs: [\"places-2\"], code: \"return { places: { type: \\'FeatureCollection\\', features: places.features.filter(p => (p.properties.poi?.name ?? \\'\\').toLowerCase().includes(\\'vegan\\')) } };\", label: \"vegan only\", show: { places: { markerType: \"pin\", zoomMode: \"auto\", hidePreviousEntries: \"all\" } } })',\n // places-only: hub-spoke connections\n 'processData({ placesEntryIDs: [\"places-1\"], code: \"const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };\" })',\n // places + geometries (attached polygons): h3 hex coverage of result places\n 'processData({ placesEntryIDs: [\"places-1\"], code: \"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 })) };\", show: { places: { markerType: \"pin\", zoomMode: \"auto\" }, placesGeometries: { theme: \"outline\" } } })',\n // routes-only: fit to worst traffic section\n 'processData({ routesEntryIDs: [\"routes-0\"], 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 // routes-only: fit to first toll section\n 'processData({ routesEntryIDs: [\"routes-0\"], 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 // routes + places: corridor filter into new places entry\n 'processData({ placesEntryIDs: [\"ev-stations\"], routesEntryIDs: [\"routes-0\"], code: \"const r = routes.features[0]; const buf = turf.buffer(r, 0.5, { units: \\'kilometers\\' }); const near = places.features.filter(p => turf.booleanPointInPolygon(p, buf)); return { places: { type: \\'FeatureCollection\\', features: near }, fitOnMap: turf.bbox(turf.featureCollection([...near, r])) };\", label: \"ev near route\", entryId: \"ev-near-routes-0\", show: { places: { markerType: \"pin\", zoomMode: \"none\", hidePreviousEntries: \"all\" } } })',\n // geometries-only: union → new custom-geometries entry\n 'processData({ geometriesEntryIDs: [{ kind: \"place\", id: \"amsterdam-id\" }, { kind: \"place\", id: \"rotterdam-id\" }], code: \"return { geometries: [turf.union(turf.featureCollection(geometries))].filter(Boolean) };\", label: \"ams ∪ rdam\", operation: \"union\" })',\n // geometries-only: buffer each\n 'processData({ geometriesEntryIDs: [{ kind: \"place\", id: \"paris-id\" }], code: \"return { geometries: geometries.map(f => ({ ...turf.buffer(f, 5, { units: \\'kilometers\\' }), id: `${f.id}-buf` })) };\", label: \"paris +5km\", operation: \"buffer\" })',\n // geometries + places: inverse (areas in X NOT reachable from Y)\n 'processData({ 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 { geometries: inv ? [{ ...inv, id: \\'unreachable\\' }] : [] };\", label: \"amsterdam minus reachable\", operation: \"difference\", show: { customGeometries: { theme: \"inverted\", hidePreviousEntries: \"all\" } } })',\n // geometries (ranges) + places cross-ref: keep places inside the reachable area\n 'processData({ geometriesEntryIDs: [{ kind: \"ranges\", id: \"ranges-0\" }, { kind: \"places\", id: \"ev-stations\" }], code: \"const ranges = geometries.filter(g => g.properties._source.kind === \\'ranges\\'); const env = ranges.length ? turf.union(turf.featureCollection(ranges)) : null; const ev = (placesByEntry?.[\\'ev-stations\\']?.features ?? []); const kept = env ? ev.filter(p => turf.booleanPointInPolygon(p, env)) : ev; return { places: { type: \\'FeatureCollection\\', features: kept } };\", label: \"ev within reachable\" })',\n // trafficAreaAnalytics + places cross-ref: keep places inside high-congestion tiles\n 'processData({ placesEntryIDs: [\"ev-stations\"], trafficAreaAnalyticsEntryIDs: [\"tta-0\"], code: \"const hot = trafficAreaAnalytics.features.filter(t => (t.properties.congestionLevel ?? 0) >= 75); const kept = places.features.filter(p => hot.some(t => turf.booleanPointInPolygon(p, t))); return { places: { type: \\'FeatureCollection\\', features: kept } };\", label: \"ev in high-congestion tiles\" })',\n // trafficAreaAnalytics → derived geometry entry: hot-tiles polygon\n 'processData({ trafficAreaAnalyticsEntryIDs: [\"tta-0\"], code: \"const hot = trafficAreaAnalytics.features.filter(t => (t.properties.congestionLevel ?? 0) >= 75).map((t, i) => ({ ...t, id: `hot-${i}` })); const u = hot.length ? turf.union(turf.featureCollection(hot)) : null; return { geometries: u ? [{ ...u, id: \\'congestion-hot-zone\\' }] : [] };\", label: \"congestion hot zone\", operation: \"union\", show: { customGeometries: { theme: \"filled\" } } })',\n ],\n examplePrompts: [\n 'Keep only the vegan restaurants',\n 'Draw lines from this hub to every nearby cafe',\n 'Show the h3 hex coverage of these places',\n 'Fit the map to the worst traffic section of the route',\n 'Fit the map to the first toll section of the route',\n 'Filter my loaded EV stations to those within 500 m of the route',\n 'Create a custom polygon by unioning these city boundaries',\n 'Compute a 500 m buffer around this neighbourhood',\n 'Areas of Amsterdam not reachable from the hospitals',\n 'Filter loaded places to those inside the reachable area',\n 'Keep only the EV stations that fall inside high-congestion tiles',\n 'Create a custom polygon layer outlining the congestion hot zone',\n ],\n relatedTools: ['analyseData', 'recallPlaces', 'recallRoutes', 'recallGeometries', 'recallState'],\n dependsOn: [\n 'discoverPlaces',\n 'locatePlace',\n 'setRoute',\n 'getTrafficIncidents',\n 'getTrafficAreaAnalytics',\n 'findReachableAreas',\n 'recallPlaces',\n 'recallRoutes',\n 'recallGeometries',\n ],\n }),\n analyseData: analyseDataBuilder({\n classificationPrompt:\n `${DATA_TOOL_SHARED_FRAMING} ` +\n 'WRITES metadata only — returns JSON or a Chart.js config (`outputFormat: \"chart\"`), attached as ' +\n '`_analysis[name]` on every contributing entry. Never renders or creates new map entries (that is ' +\n '`processData`). ' +\n 'Use for: counts / groupings / charts (by-category, hex density); route summaries (per-leg / per-country / ' +\n 'alternative compare); route TRAFFIC analysis from `route.properties.sections.traffic[]` — pick THIS for ' +\n '\"distribution / count / chart of <something> along the route\", NOT `getTrafficIncidents`; geometry stats ' +\n '(areas, h3 coverage); historical traffic-area-analytics breakdowns (congestion / speed / travel-time per ' +\n 'tile); cross-kind correlations (incidents-per-cluster, places-near-route, distance-to-route bins, ' +\n 'route-segments-by-tile-metric). ' +\n 'For recurring monitor-tick reruns with `previous` / `now` / `log` and optional `focusIds` side-effect, ' +\n 'set `monitor: { entryId }` (only `incidentsEntryIDs: [monitor.entryId]` allowed alongside).',\n tags: ['analysis', 'turf', 'h3', 'place', 'route', 'geometry'],\n examples: [\n // places-only: counts\n 'analyseData({ 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 // places-only: h3 density\n 'analyseData({ placesEntryIDs: [\"places-1\"], name: \"hex-density-8\", 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 // places-only: chart\n 'analyseData({ placesEntryIDs: [\"places-2\"], 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]) }] } };\" })',\n // routes-only: per-route summary\n 'analyseData({ routesEntryIDs: [\"routes-0\"], 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 // routes-only: alternative-comparison chart\n 'analyseData({ routesEntryIDs: [\"routes-0\"], 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 // routes-only: distribution of traffic incidents ALONG the route (reads route.properties.sections.traffic[])\n 'analyseData({ routesEntryIDs: [\"routes-0\"], name: \"on-route-incidents-by-category\", outputFormat: \"chart\", code: \"const sections = (routes.features[0].properties.sections.traffic ?? []); const counts = {}; for (const s of sections) for (const c of (s.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]); return { type: \\'bar\\', data: { labels: entries.map(e => e[0]), datasets: [{ label: \\'Incidents on route\\', data: entries.map(e => e[1]) }] }, options: { plugins: { title: { display: true, text: \\'Traffic incidents along the route\\' } } } };\" })',\n // routes-only: total delay per country section\n 'analyseData({ routesEntryIDs: [\"routes-0\"], name: \"delay-by-country\", code: \"const r = routes.features[0]; const byCountry = {}; 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 // routes + places cross-ref (count within distance)\n 'analyseData({ routesEntryIDs: [\"routes-0\"], placesEntryIDs: [\"ev-stations\"], name: \"ev-near-route-500m\", code: \"const route = routes.features[0]; const out = {}; for (const [id, fc] of Object.entries(placesByEntry)) out[id] = fc.features.filter(p => turf.pointToLineDistance(p, route, { units: \\'meters\\' }) <= 500).length; return { withinMeters: 500, byEntry: out };\" })',\n // routes + places cross-ref (distance-to-route histogram, binned in km)\n 'analyseData({ routesEntryIDs: [\"routes-0\"], placesEntryIDs: [\"cafes\"], name: \"cafe-distance-bins-km\", outputFormat: \"chart\", code: \"const route = routes.features[0]; const distances = places.features.map(p => turf.pointToLineDistance(p, route, { units: \\'kilometers\\' })); const max = Math.max(0, ...distances); const binWidth = 1; const binCount = Math.max(1, Math.ceil(max / binWidth)); const bins = new Array(binCount).fill(0); for (const d of distances) bins[Math.min(binCount - 1, Math.floor(d / binWidth))]++; const labels = bins.map((_, i) => `${i}-${i + binWidth} km`); return { type: \\'bar\\', data: { labels, datasets: [{ label: \\'cafes\\', data: bins }] }, options: { plugins: { title: { display: true, text: \\'Cafe distance to route\\' } } } };\" })',\n // geometries-only: areas\n 'analyseData({ geometriesEntryIDs: [{ kind: \"places\", id: \"cities\" }], name: \"areas\", code: \"return geometries.map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));\" })',\n // geometries-only: chart\n 'analyseData({ 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 // cross-kind: incidents per place cluster\n 'analyseData({ placesEntryIDs: [\"clusters\"], incidentsEntryIDs: [\"incidents-0\"], name: \"incidents-per-cluster\", code: \"const out = {}; for (const c of places.features) { const inCluster = incidents.filter(i => { const coord = i.geometry.type === \\'Point\\' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.distance(coord, c.geometry.coordinates, { units: \\'kilometers\\' }) <= 0.5; }); out[c.id] = inCluster.length; } return out;\" })',\n // cross-kind: incidents inside custom corridors\n 'analyseData({ geometriesEntryIDs: [{ kind: \"customGeometries\", id: \"buffered-routes\" }], incidentsEntryIDs: [\"incidents-0\"], name: \"incidents-in-corridors\", code: \"const out = []; for (const g of geometries) { const hits = incidents.filter(i => { const coord = i.geometry.type === \\'Point\\' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.booleanPointInPolygon(coord, g); }); out.push({ id: g.id, count: hits.length, totalDelaySec: hits.reduce((s, i) => s + (i.properties.delayInSeconds ?? 0), 0) }); } return out;\" })',\n // trafficAreaAnalytics-only: congestion distribution chart\n \"analyseData({ trafficAreaAnalyticsEntryIDs: [\\\"tta-0\\\"], name: \\\"congestion-distribution\\\", outputFormat: \\\"chart\\\", code: \\\"const bins = [0, 25, 50, 75, 100]; const counts = new Array(bins.length).fill(0); for (const t of trafficAreaAnalytics.features) { const c = t.properties.congestionLevel ?? 0; const idx = Math.min(bins.length - 1, Math.floor(c / 25)); counts[idx]++; } return { type: 'bar', data: { labels: ['0–24', '25–49', '50–74', '75+'], datasets: [{ label: 'Tiles', data: counts.slice(0, 4) }] } };\\\" })\",\n // cross-kind: places by congestion tile\n 'analyseData({ placesEntryIDs: [\"ev-stations\"], trafficAreaAnalyticsEntryIDs: [\"tta-0\"], name: \"ev-stations-by-congestion\", code: \"const buckets = { low: 0, mid: 0, high: 0 }; for (const p of places.features) { const tile = trafficAreaAnalytics.features.find(t => turf.booleanPointInPolygon(p, t)); if (!tile) continue; const c = tile.properties.congestionLevel ?? 0; if (c < 25) buckets.low++; else if (c < 75) buckets.mid++; else buckets.high++; } return buckets;\" })',\n // cross-kind: route segments by tile metric\n 'analyseData({ routesEntryIDs: [\"routes-0\"], trafficAreaAnalyticsEntryIDs: [\"tta-0\"], name: \"route-tile-coverage\", code: \"const r = routes.features[0]; const hits = trafficAreaAnalytics.features.filter(t => turf.lineIntersect(r, t).features.length > 0); return { tilesCrossed: hits.length, avgCongestion: hits.length ? +(hits.reduce((s, t) => s + (t.properties.congestionLevel ?? 0), 0) / hits.length).toFixed(1) : null };\" })',\n // incidents-only: counts by category (one-shot)\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], 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 // incidents-only: top-N by delay (one-shot)\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], 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 // incidents-only with monitor: recurring counts that survive ticks\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], monitor: { entryId: \"incidents-0\" }, name: \"rolling-count\", code: \"const trend = previous ? (incidents.length > previous.count ? \\'growing\\' : incidents.length < previous.count ? \\'fading\\' : \\'steady\\') : \\'new\\'; return { count: incidents.length, trend, at: now.toISOString() };\" })',\n // incidents-only with monitor + focus: surface top-3 worst delays as the focus subset\n 'analyseData({ incidentsEntryIDs: [\"incidents-0\"], monitor: { entryId: \"incidents-0\" }, name: \"top-3-focus\", code: \"const top = [...incidents].sort((a,b) => (b.properties.delayInSeconds ?? 0) - (a.properties.delayInSeconds ?? 0)).slice(0,3); return { count: top.length, focusIds: top.map(i => i.properties.id), focusReason: \\'top 3 by delay\\' };\" })',\n ],\n examplePrompts: [\n 'How many of each POI category are in these results?',\n 'h3 hex density of these places',\n 'Bar chart of the top POI categories',\n 'How long is each route alternative?',\n 'Compare travel times across alternatives',\n 'How many EV stations are within 500 m of the route?',\n 'Chart the distance from each cafe to the route, binned in km',\n 'Histogram of place-to-route distances',\n 'Distribution of traffic incidents along the drive from Paris to Brussels',\n 'Chart the incidents on the route by category',\n 'Total delay per country on this route',\n 'How big are these areas?',\n 'Bar chart of city areas',\n 'Incidents per place cluster',\n 'Total delay per traffic corridor',\n 'Congestion distribution across the analytics tiles',\n 'Average historical speed per tile, as a chart',\n 'Group these EV stations by the congestion level of the tile they fall in',\n 'For the route, which tiles does it cross and what is their average congestion?',\n 'How many incidents of each category?',\n 'Top 5 incidents by delay',\n 'Total delay grouped by road',\n 'Keep counting the incidents and tell me when it grows or fades',\n 'Highlight the 3 worst incidents by delay on the map',\n 'Bar chart of incidents per category',\n 'Which jams are growing vs fading on this monitored area?',\n ],\n relatedTools: [\n 'processData',\n 'focusIncidents',\n 'recallPlaces',\n 'recallRoutes',\n 'recallGeometries',\n 'recallState',\n ],\n dependsOn: [\n 'discoverPlaces',\n 'setRoute',\n 'getTrafficIncidents',\n 'getTrafficAreaAnalytics',\n 'findReachableAreas',\n 'recallPlaces',\n 'recallRoutes',\n 'recallGeometries',\n ],\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 from Amsterdam to Schiphol airport',\n 'Recalculate my route to Brussels from Schiphol airport',\n 'Recalculate the route from Utrecht to Brussels',\n 'Avoid toll roads on my current route',\n 'Find the fastest route from Berlin to Munich',\n 'Show 2 alternative routes for my current trip',\n 'Depart at 9am on my current route',\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', 'toggleTilesPOIs'],\n },\n getTrafficIncidents: {\n description: getTrafficIncidentsDescription,\n classificationPrompt:\n 'AREA-BOUND incident loader — bbox / viewport / \"incidents around here\" / \"accidents in <city>\". ' +\n 'DO NOT pick for route-bound queries (\"incidents ALONG / ON the route\", \"delays on the drive from X to Y\"): ' +\n 'a calculated route already carries its incidents in `route.properties.sections.traffic[]` (category, ' +\n 'magnitudeOfDelay, delayInSeconds, effectiveSpeedInKmh) — call `analyseData` with `routesEntryIDs` for those. ' +\n 'When this tool IS the right pick: MANDATORY co-pick startTrafficIncidentsMonitor (they are a pair — data goes stale without it). ' +\n 'Also co-pick analyseData (one-shot aggregations / charts / clusters; pass `monitor: { entryId }` for the recurring spec + focus side-effect) 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({}) // omit `where` → current viewport',\n 'getTrafficIncidents({ where: { mode: \"within\", boundingBox: [4.728, 52.278, 5.080, 52.479] } })',\n 'getTrafficIncidents({ where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }] } }) // named area',\n 'getTrafficIncidents({ where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }] }, categoryFilter: [\"accident\", \"jam\"] })',\n 'getTrafficIncidents({ where: { mode: \"within\", route: { widthMeters: 1000 } }, categoryFilter: [\"roadworks\"], timeValidityFilter: [\"future\"] }) // corridor around latest route',\n 'getTrafficIncidents({ where: { mode: \"within\", range: \"ranges-0\" } }) // inside a reachable area',\n ],\n examplePrompts: [\n 'Fetch traffic incidents in Amsterdam',\n 'Any accidents in this area?',\n 'Show roadworks across the city',\n // Counter-examples: NOT this tool — route-bound queries use analyseData over routes.\n ],\n relatedTools: ['calculateBBox', 'getViewport', 'analyseData', 'focusIncidents', 'startTrafficIncidentsMonitor'],\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 analyseData 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: ['analyseData', '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', 'analyseData'],\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', 'updateTrafficAreaAnalyticsDisplay', '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', 'updateTrafficAreaAnalyticsDisplay'],\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', 'processData'],\n },\n updateRoutesDisplay: {\n description: updateRoutesDisplayDescription,\n classificationPrompt:\n 'Show, hide, swap, and recolor routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, or set `mainColor` (CSS color / hex; sticky across entry swaps).',\n inputSchema: updateRoutesDisplaySchema,\n outputSchema: updateRoutesDisplayOutputSchema,\n execute: executeUpdateRoutesDisplay,\n tags: ['route', 'waypoint', 'map style'],\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 'updateRoutesDisplay({ mainColor: \"coral\" }) // recolor every currently-shown route (and future shows)',\n 'updateRoutesDisplay({ add: [\"routes-1\"], mainColor: \"#FF0000\" }) // show in red',\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 'Make the route line red',\n 'Change the route color to coral',\n 'Style the route in steelblue',\n ],\n relatedTools: ['setRoute', 'processData', 'recallRoutes', 'clearMap'],\n dependsOn: ['setRoute'],\n },\n updateTrafficAreaAnalyticsDisplay: {\n description: updateTrafficAreaAnalyticsDisplayDescription,\n classificationPrompt:\n 'Show / hide / swap / restyle historical traffic-area-analytics entries on the map. `add` / `remove` / `addMatching` / `removeMatching` / `clear` for visibility; `mode` / `metric` / `colorTheme` / `heightScale` / `scaleMode` / `filter` for the rendering. Render knobs alone implicitly target the most recent entry.',\n inputSchema: updateTrafficAreaAnalyticsDisplaySchema,\n outputSchema: updateTrafficAreaAnalyticsDisplayOutputSchema,\n execute: executeUpdateTrafficAreaAnalyticsDisplay,\n tags: ['traffic', 'map style'],\n examples: [\n 'updateTrafficAreaAnalyticsDisplay({ add: [\"tta-0\"] })',\n \"updateTrafficAreaAnalyticsDisplay({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' }) // re-style the latest (implicit)\",\n 'updateTrafficAreaAnalyticsDisplay({ filter: { min: 50 } })',\n \"updateTrafficAreaAnalyticsDisplay({ scaleMode: 'currentRange', heightScale: 200 })\",\n 'updateTrafficAreaAnalyticsDisplay({ clear: true })',\n 'updateTrafficAreaAnalyticsDisplay({ clear: true, add: [\"tta-1\"] }) // atomic swap',\n 'updateTrafficAreaAnalyticsDisplay({ remove: [\"tta-0\"] })',\n 'updateTrafficAreaAnalyticsDisplay({ addMatching: [\"Amsterdam\"] })',\n ],\n examplePrompts: [\n 'Show the analytics 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 'Also show the Amsterdam analytics',\n 'Hide the Rotterdam analytics',\n 'Replace what is on the map with this analytics',\n ],\n relatedTools: [\n 'getTrafficAreaAnalytics',\n 'queryTrafficAnalytics',\n 'toggleTilesTrafficFlow',\n 'clearMap',\n 'analyseData',\n 'processData',\n ],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n updateWaypointsDisplay: {\n description: updateWaypointsDisplayDescription,\n classificationPrompt:\n 'Show or hide the STAGED origin/stop/destination markers (`state.routing.planningSlots` — what is queued for the NEXT `setRoute`). No route line is drawn; for that use `updateRoutesDisplay`. Single-collection toggle: `visible: true` (default) renders the staged set, `visible: false` clears it.',\n inputSchema: updateWaypointsDisplaySchema,\n outputSchema: updateWaypointsDisplayOutputSchema,\n execute: executeUpdateWaypointsDisplay,\n tags: ['waypoint', 'route'],\n examples: [\n 'updateWaypointsDisplay()',\n 'updateWaypointsDisplay({ visible: true })',\n 'updateWaypointsDisplay({ visible: false })',\n ],\n examplePrompts: [\n 'Show the staged waypoints on the map',\n 'Mark my pending route stops',\n 'Hide the staged waypoints',\n ],\n relatedTools: ['setRoute', 'updateRoutesDisplay', 'getCurrentWaypoints', 'clearMap'],\n dependsOn: ['setRoute', 'locatePlace'],\n },\n getShownTileIncidents: {\n description: getShownTileIncidentsDescription,\n classificationPrompt:\n 'Read traffic incidents currently rendered by the TomTom TRAFFIC-TILE OVERLAY (the rasterised live tiles ' +\n \"toggled via `toggleTilesTrafficIncidents`). DO NOT confuse with `getTrafficIncidents` — that's the GeoJSON \" +\n 'service that returns full structured incidents with start/end times, road numbers, lanes, etc. ' +\n 'Pick this ONLY for \"what\\'s on the tile overlay right now\"; for richer per-incident data use ' +\n '`getTrafficIncidents`, and for \"incidents along the route\" use `analyseData` over routes ' +\n '(`route.properties.sections.traffic[]`).',\n inputSchema: getShownTileIncidentsSchema,\n outputSchema: getShownTileIncidentsOutputSchema,\n execute: executeGetShownTileIncidents,\n tags: ['traffic', 'location', 'map style'],\n examples: ['getShownTileIncidents()'],\n examplePrompts: [\n 'What incidents does the traffic tile overlay show right now?',\n \"What's rendered on the traffic incidents layer?\",\n ],\n relatedTools: ['toggleTilesTrafficIncidents', 'getTrafficIncidents', 'flyTo', 'getViewport'],\n dependsOn: ['toggleTilesTrafficIncidents'],\n },\n clearMap: {\n description: clearMapDescription,\n classificationPrompt:\n 'Hide what the chosen state slices are rendering on the map. `layers` are SLICE KEYS: `places`, `routing`, `ranges`, `customGeometries`, `byod`, `trafficAreaAnalytics`, `trafficIncidents`. Omit to clear every slice. Map-only — slice history is kept.',\n inputSchema: clearMapSchema,\n outputSchema: clearMapOutputSchema,\n execute: executeClearMap,\n tags: ['place', 'route', 'map style'],\n examples: [\n 'clearMap()',\n 'clearMap({ layers: [\"places\"] })',\n 'clearMap({ layers: [\"places\", \"routing\"] })',\n 'clearMap({ layers: [\"trafficAreaAnalytics\", \"trafficIncidents\"] })',\n 'clearMap({ layers: [\"byod\"] })',\n ],\n examplePrompts: ['Clear the map', 'Remove all markers', 'Start fresh', 'Hide the analytics overlay'],\n relatedTools: ['updatePlacesDisplay', 'updateRoutesDisplay', 'updateTrafficAreaAnalyticsDisplay'],\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: \"customGeometries\", id: \"bakery-candidates-gap-oost\" } })',\n ],\n examplePrompts: [\n 'What polygons are in state right now?',\n 'Which IDs can I pass to processData (geometriesEntryIDs)?',\n 'Recall the custom zone we just produced',\n 'What does the bakery-candidates entry contain?',\n ],\n relatedTools: ['processData', 'analyseData', 'recallPlaces', 'recallRanges'],\n },\n recallPlaces: recallPlacesBuilder,\n recallByod: {\n description: recallByodDescription,\n classificationPrompt:\n 'List/inspect BYOD (bring-your-own-data) layers in session state. Returns ids the model can pass to `byodEntryIDs` (analyseData / processData) or to `updateByodDisplay` for visibility control.',\n inputSchema: recallByodSchema,\n outputSchema: recallByodOutputSchema,\n execute: executeRecallByod,\n tags: ['recall', 'byod', 'state'],\n examples: ['recallByod()', 'recallByod({ id: \"byod-0\" })'],\n examplePrompts: [\n 'What customer-uploaded layers do we have?',\n 'List BYOD entries',\n 'Show details of the sales-territories BYOD layer',\n ],\n relatedTools: ['addByodLayer', 'updateByodDisplay', 'analyseData', 'processData'],\n },\n addByodLayer: {\n description: addByodLayerDescription,\n classificationPrompt:\n 'Add a customer-owned GeoJSON layer (URL fetch or inline) as a new BYOD entry. Use when the user wants to import / load / overlay a customer-supplied dataset. Returns the new entry id (use with `byodEntryIDs` on analyseData / processData).',\n inputSchema: addByodLayerSchema,\n outputSchema: addByodLayerOutputSchema,\n execute: executeAddByodLayer,\n tags: ['byod', 'state', 'import'],\n examples: [\n 'addByodLayer({ label: \"Sales territories\", url: \"https://example.com/territories.geojson\", show: true })',\n 'addByodLayer({ label: \"Inline pins\", data: { type: \"FeatureCollection\", features: [/* … */] } })',\n ],\n examplePrompts: [\n 'Load my territories layer from https://...',\n 'Import this GeoJSON as a BYOD layer',\n 'Add a customer-pins layer from this URL',\n ],\n relatedTools: ['recallByod', 'updateByodDisplay', 'analyseData', 'processData'],\n },\n updateByodDisplay: {\n description: updateByodDisplayDescription,\n classificationPrompt:\n 'Toggle visibility (or drop) of BYOD entries already in session state. Use after `addByodLayer` or after `recallByod` exposes the ids. Three actions: show / hide / remove. Pair with `clearAll` or `hideOthers` for bulk swaps.',\n inputSchema: updateByodDisplaySchema,\n outputSchema: updateByodDisplayOutputSchema,\n execute: executeUpdateByodDisplay,\n tags: ['byod', 'display', 'visibility'],\n examples: [\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"show\" })',\n 'updateByodDisplay({ entryIds: [\"byod-0\", \"byod-1\"], action: \"show\", hideOthers: true })',\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"hide\" })',\n 'updateByodDisplay({ action: \"hide\", clearAll: true })',\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"remove\" })',\n ],\n examplePrompts: [\n 'Hide the customer pins layer',\n 'Show only the territories BYOD layer',\n 'Hide all BYOD layers',\n 'Drop the imported file from the session',\n ],\n relatedTools: ['recallByod', 'addByodLayer'],\n dependsOn: ['addByodLayer'],\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 'STAGED waypoint slots only — the origin/stops/destination being built up for the NEXT `setRoute` call ' +\n '(read off `state.routing.planningSlots`, NOT off any calculated route entry). ' +\n \"For waypoints of an EXISTING calculated route, use `recallRoutes({ id })` — its detail mode returns the entry's own waypoints.\",\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: [\n \"What's currently staged for the next route?\",\n \"What's my pending destination before I hit go?\",\n 'Show the stops I have queued up',\n ],\n relatedTools: ['locatePlace', 'setRoute', 'updateWaypointsDisplay', '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', 'toggleTilesBaseMapLayerGroups', 'getMapStyleLayers'],\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 toggleTilesBaseMapLayerGroups: {\n description: toggleTilesBaseMapLayerGroupsDescription,\n classificationPrompt:\n 'Show / hide vector-tile base-map layer groups (buildings3D, roadLabels, water, placeLabels, …). Style-layer only — does NOT touch places / routes / custom-geometries rendered by the agent.',\n inputSchema: toggleTilesBaseMapLayerGroupsSchema,\n outputSchema: toggleTilesBaseMapLayerGroupsOutputSchema,\n execute: executeToggleTilesBaseMapLayerGroups,\n tags: ['map style'],\n examples: [\n 'toggleTilesBaseMapLayerGroups({ visible: false, layerGroups: [\"buildings3D\"] })',\n 'toggleTilesBaseMapLayerGroups({ visible: true, layerGroups: [\"roadLabels\"] })',\n ],\n examplePrompts: ['Hide 3D buildings', 'Show road labels', 'Toggle the building layer'],\n relatedTools: ['toggleTilesPOIs', 'getMapStyleLayers', 'setMapStandardStyle'],\n },\n toggleTilesPOIs: {\n description: toggleTilesPOIsDescription,\n classificationPrompt:\n 'Show / hide vector-tile POI icons baked into the base-map style (restaurants/hotels/gas stations etc. shipped with the map). NOT places shown via `discoverPlaces` / `updatePlacesDisplay` (those are PlacesModule pins, untouched).',\n inputSchema: toggleTilesPOIsSchema,\n outputSchema: toggleTilesPOIsOutputSchema,\n execute: executeToggleTilesPOIs,\n tags: ['place', 'map style'],\n examples: [\n 'toggleTilesPOIs({ allMapPOIsVisible: false })',\n 'toggleTilesPOIs({ allMapPOIsVisible: true, filterCategories: { show: \"only\", values: [\"RESTAURANT\", \"CAFE\"] } })',\n 'toggleTilesPOIs({ filterCategories: { show: \"all_except\", values: [\"PARKING_GARAGE\"] } })',\n 'toggleTilesPOIs({ reset: true })',\n ],\n examplePrompts: [\n 'Hide all map POI icons',\n 'Show only restaurant icons on the map style',\n 'Turn off parking icons in the base map',\n ],\n relatedTools: ['toggleTilesBaseMapLayerGroups', 'discoverPlaces', 'setMapStandardStyle'],\n },\n toggleTilesTrafficFlow: {\n description: toggleTilesTrafficFlowDescription,\n classificationPrompt:\n 'Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical) or route-section traffic (read via `analyseData`).',\n inputSchema: toggleTilesTrafficFlowSchema,\n outputSchema: toggleTilesTrafficFlowOutputSchema,\n execute: executeToggleTilesTrafficFlow,\n tags: ['traffic', 'map style'],\n examples: ['toggleTilesTrafficFlow({ visible: true })', 'toggleTilesTrafficFlow({ visible: false })'],\n examplePrompts: ['Show traffic flow', 'Turn off traffic colors', 'Enable the congestion overlay'],\n relatedTools: ['toggleTilesTrafficIncidents', 'setRoute', 'getTrafficIncidents'],\n },\n toggleTilesTrafficIncidents: {\n description: toggleTilesTrafficIncidentsDescription,\n classificationPrompt:\n 'Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the GeoJSON `getTrafficIncidents` service and NOT `analyseData` (which aggregates fetched entries). Use `getShownTileIncidents` to read what this overlay currently renders.',\n inputSchema: toggleTilesTrafficIncidentsSchema,\n outputSchema: toggleTilesTrafficIncidentsOutputSchema,\n execute: executeToggleTilesTrafficIncidents,\n tags: ['traffic', 'map style'],\n examples: ['toggleTilesTrafficIncidents({ visible: true })', 'toggleTilesTrafficIncidents({ visible: false })'],\n examplePrompts: ['Show traffic incidents overlay', 'Hide accident icons', 'Enable incident markers on the map'],\n relatedTools: ['toggleTilesTrafficFlow', 'getTrafficIncidents', 'getShownTileIncidents', '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', 'updateRoutesDisplay'],\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 any entry-owning slice (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents) 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: \"routing\", mode: \"multiple\" })',\n 'setEntryMode({ slice: \"trafficIncidents\", mode: \"single\" })',\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 'Only keep the latest traffic incidents snapshot',\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/**\n * Returns each default tool's `examplePrompts` keyed by tool name. Builders are\n * materialised with empty {@link ToolBuildOptions} so the prompts reflect the\n * tool's default (full-surface) shape. Tools without prompts map to `[]`.\n *\n * Surface these in chat UIs to keep starter suggestions in sync with what the\n * tools can actually do — the registry stays the single source of truth.\n *\n * @group Agent Toolkit\n */\nexport const getDefaultToolPrompts = (): Record<ToolName, readonly string[]> => {\n const result = {} as Record<ToolName, readonly string[]>;\n for (const name of TOOL_NAMES) {\n const entry = DEFAULT_TOOLS[name];\n const materialized = typeof entry === 'function' ? entry({}) : entry;\n result[name] = materialized.examplePrompts ?? [];\n }\n return result;\n};\n","/**\n * @module agent-toolkit\n */\n\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { PrepareStepFunction, Tool } from 'ai';\nimport { stepCountIs, ToolLoopAgent } from 'ai';\nimport {\n applyToolScopes,\n composeStepResult,\n createPrepareStepMutex,\n destroyState,\n restoreScopableTools,\n} from './prepare-step-helpers';\nimport { resolveTools } from './resolve-tools';\nimport {\n createToolState,\n DATA_ENTRY_KIND_TO_SLICE,\n type DataEntryKind,\n type EntryModeSlice,\n TOOLS_BY_DATA_ENTRY_KIND,\n} from './state';\nimport { buildSystemPrompt } from './system-prompt';\nimport { type ScopableToolInfo, setupTools } from './tool-setup';\nimport { DEFAULT_TOOLS } from './tools';\nimport { ALL_ENTRY_DATA_KINDS } from './tools/shared';\nimport type {\n Classifier,\n EntryDataKind,\n MapAgentInstance,\n MapAgentOptions,\n ToolDefinition,\n ToolMetadata,\n ToolState,\n} from './types';\nimport type { ClassificationResult } from './utils';\nimport { createDefaultClassifier } from './utils';\n\n// CONCURRENCY: the AI SDK does not support per-step tool overrides (`PrepareStepResult` has no\n// `tools` field — only `activeTools` for name-level filtering). Mutating the shared `tools` map is\n// therefore the only path to a per-turn scoped schema, and it imposes a single-concurrent-run\n// invariant on any agent instance that has scopable tools. Two concurrent `respond()` calls on the\n// same agent would race on the mutation; we serialise `prepareStep` invocations across the agent\n// with `createPrepareStepMutex` to keep the mutation/serialize-request window atomic per call.\n// Callers who genuinely need parallel sessions should create one agent per session (the typical\n// embedding-app pattern) rather than share an agent across concurrent users.\n\n/**\n * Inputs the step-0 classification needs. Bundled into a single object so the orchestrator's\n * inner closure can pass the agent-creation-time state through to `runStep0Classification` without\n * a wide parameter list. None of the fields change across steps — the agent's whole lifetime sees\n * the same `tools` / `scopableTools` / `toolsMetadata` / `classifier`.\n */\ntype ClassificationDeps = {\n tools: Record<string, Tool>;\n scopableTools: Record<string, ScopableToolInfo>;\n toolsMetadata: Record<string, ToolMetadata>;\n classifier: Classifier | null;\n onClassify?: (result: ClassificationResult | null) => void;\n};\n\n/**\n * Step-0 pipeline: restore each scopable tool to its full surface so the classifier has a clean\n * baseline, run the classifier (swallowing any throw — networks are flaky and `null` triggers\n * fail-open), surface the result via `onClassify`, and finally mutate the live tool objects'\n * `description` + `inputSchema` using the classifier's per-tool scopes. The AI SDK reads tool\n * definitions on its next request build, so this mutation is what makes per-turn scoping work.\n *\n * Returns the classification (or null) for the caller to cache across subsequent steps.\n */\nconst runStep0Classification = async (\n deps: ClassificationDeps,\n messages: Parameters<PrepareStepFunction>[0]['messages'],\n): Promise<ClassificationResult | null> => {\n restoreScopableTools(deps.tools, deps.scopableTools);\n let result: ClassificationResult | null = null;\n if (deps.classifier) {\n try {\n result = await deps.classifier({\n messages,\n toolsMetadata: deps.toolsMetadata,\n });\n } catch {\n result = null;\n }\n }\n deps.onClassify?.(result);\n applyToolScopes(deps.tools, deps.scopableTools, result?.toolScopes);\n return result;\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 // Apply per-kind `entryMode` from `dataEntries`. Each kind maps to a single slice via\n // {@link DATA_ENTRY_KIND_TO_SLICE}. The slice histories are empty at this point so\n // `setEntryMode` resolves synchronously (no entries to hide); the promise is\n // intentionally not awaited because createMapAgent itself is sync.\n if (options.dataEntries) {\n for (const [kindKey, config] of Object.entries(options.dataEntries)) {\n const kind = kindKey as DataEntryKind;\n if (!config?.entryMode) continue;\n const sliceName = DATA_ENTRY_KIND_TO_SLICE[kind];\n void (state[sliceName] as EntryModeSlice).setEntryMode(config.entryMode);\n }\n }\n\n // Compute the set of enabled data-entry kinds. Used both to filter the tool registry\n // (drop slice-specific tools for disabled kinds) and to narrow analyseData / processData\n // input surface via {@link ToolBuildOptions.enabledDataKinds}. Default is everything\n // enabled; only kinds with an explicit `enabled: false` drop out.\n const isKindEnabled = (kind: DataEntryKind): boolean => options.dataEntries?.[kind]?.enabled !== false;\n const disabledKinds: DataEntryKind[] = (Object.keys(DATA_ENTRY_KIND_TO_SLICE) as DataEntryKind[]).filter(\n (kind) => !isKindEnabled(kind),\n );\n const enabledDataKinds: readonly EntryDataKind[] = ALL_ENTRY_DATA_KINDS.filter((kind) => isKindEnabled(kind));\n // Names of default tools to drop because their slice is disabled. Custom-tool overrides\n // (passed via `options.tools`) are NOT filtered — integrators that want to gate their\n // own tools on a disabled kind can inspect `dataEntries` themselves.\n const droppedToolNames = new Set<string>(disabledKinds.flatMap((kind) => TOOLS_BY_DATA_ENTRY_KIND[kind]));\n\n // Safe: default tools only access base ToolState properties; CS extends ToolState.\n const defaultsAll =\n options.includeDefaultTools === false ? {} : (DEFAULT_TOOLS as Record<string, ToolDefinition<CS>>);\n // Filter slice-specific default tools out before user overrides — user overrides win,\n // so a custom tool registered under the same name resurrects it on purpose.\n const defaults: Record<string, ToolDefinition<CS>> = Object.fromEntries(\n Object.entries(defaultsAll).filter(([name]) => !droppedToolNames.has(name)),\n );\n const toolEntries = resolveTools(defaults, options.tools);\n\n const { tools, toolsMetadata, scopableTools } = setupTools(toolEntries, state, {\n outputSchemas: options.outputSchemas,\n featureFlags: options.featureFlags,\n enabledDataKinds,\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 // Per-agent serialising mutex around prepareStep. Guards the tool-object mutation window so\n // concurrent `respond()` calls on the same agent don't race on `tools[name].description /\n // inputSchema`. The AI SDK reads tool definitions immediately after prepareStep returns, so\n // serialising prepareStep keeps each call's request-build atomic with respect to mutations.\n const withPrepareStepLock = createPrepareStepMutex();\n\n // Bundle classifier + tools so the step-0 helper has a single argument. None of these change\n // across steps, so the bag is constructed once and closed over by prepareStep.\n const classificationDeps: ClassificationDeps = {\n tools,\n scopableTools,\n toolsMetadata,\n classifier,\n onClassify: options.onClassify,\n };\n\n const prepareStep: PrepareStepFunction = (stepInfo) =>\n withPrepareStepLock(async () => {\n if (stepInfo.stepNumber === 0) {\n lastClassification = await runStep0Classification(classificationDeps, stepInfo.messages);\n }\n const activeTools = lastClassification?.activeToolNames;\n const userResult = await options.prepareStep?.(stepInfo);\n const stepProviderOptions = options.stepProviderOptions?.({\n activeTools,\n stepNumber: stepInfo.stepNumber,\n });\n return composeStepResult(activeTools, stepProviderOptions, userResult);\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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"}