@tomtom-org/maps-sdk-plugin-agent-toolkit 0.2.6 → 0.2.11
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.
- package/README.md +153 -119
- package/dist/index.d.ts +592 -102
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/package.json +9 -9
package/dist/index.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Output as e,ToolLoopAgent as t,generateText as r,stepCountIs as s}from"ai";import{BaseMapModule as o,GeometriesModule as n,HillshadeModule as a,POIsModule as i,PlacesModule as l,RoutingModule as c,TrafficAreaAnalyticsModule as d,TrafficFlowModule as u,TrafficIncidentOverlayModule as p,TrafficIncidentsModule as h,baseMapLayerGroupNames as m,reachableRangeGeometryConfig as y,standardStyleIDs as g,themedGeometryConfig as f}from"@tomtom-org/maps-sdk/map";import{alongRouteSearch as b,calculateReachableRanges as w,calculateRoute as v,explorationSearch as I,geocode as S,geocodeOne as x,geometryData as P,getPOICategoryCodes as M,reverseGeocode as E,routeTypes as _,search as T,searchOne as A,trafficAreaAnalytics as C,trafficIncidentDetails as R}from"@tomtom-org/maps-sdk/services";import{TomTomConfig as D,avoidableTypes as k,bboxFromGeoJSON as O,calculateProgressAtRoutePoint as j,formatDistance as F,formatDuration as L,getCoordinateAtRouteProgress as N,getPosition as $,getProgressAtNearestRoutePoint as q,getRouteProgressBetween as B,getRouteProgressForSection as G,getSectionBBox as U,poiCategories as W,sectionTypes as z,trafficIncidentRequestCategories as V,withInsertedWaypoints as H}from"@tomtom-org/maps-sdk/core";import*as J from"@turf/turf";import*as Y from"h3-js";import{z as K}from"zod";var X=(e,t)=>{const r={...e};if(!t)return r;for(const[s,o]of Object.entries(t))o?r[s]=o:delete r[s];return r},Z=class{ttMap;_baseMapModule;_hillshadeModule;constructor(e){this.ttMap=e}get mapLibreMap(){return this.ttMap.mapLibreMap}async getBaseMapModule(){return this._baseMapModule??=await o.get(this.ttMap),this._baseMapModule}async getHillshadeModule(){return this._hillshadeModule??=await a.get(this.ttMap),this._hillshadeModule}reset(){this._baseMapModule=void 0,this._hillshadeModule=void 0}},Q=class{handlers={};on(e,t){return(this.handlers[e]??(this.handlers[e]=[])).push(t),()=>{const r=this.handlers[e];if(!r)return;const s=r.indexOf(t);-1!==s&&r.splice(s,1)}}off(e){this.handlers[e]&&(this.handlers[e].length=0)}emit(e,t){const r=this.handlers[e];if(r?.length)for(const s of[...r])s(t)}clear(){for(const e of Object.keys(this.handlers))this.handlers[e]=[]}},ee=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const s of e){const e="string"==typeof s.id?s.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(s)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},te=class{_ttMap;_entries=[];_entryMode="multiple";events=new Q;constructor(e){this._ttMap=e}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){if(this._entryMode!==e){if(this._entryMode=e,"single"===e&&this._entries.length>1){const e=this._entries.at(-1),t=this._entries.slice(0,-1);for(const r of t)await this.hideEntry(r.id);this._entries=e?[e]:[],this.events.emit("entries-change",this._entries)}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}addEntry(e,t,r,s){if("single"===this._entryMode&&this._entries.length>0){for(const e of this._entries)this.hideEntry(e.id);this._entries=[]}const o=`geometries-${this._entries.length}`,n=s?this._uniqueEntryId(s):o;return this._entries.push({id:n,timestamp:Date.now(),label:r,features:e,provenance:t}),this.events.emit("entries-change",this._entries),n}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e),s={...f(),theme:t};return r._module?r._shownTheme!==t&&(r._module.applyConfig(s),r._shownTheme=t):(r._module=await n.get(this._ttMap,s),r._shownTheme=t),r._module}async showEntry(e,t="outline",r="replace"){const s=this._requireEntry(e);await this._hideOthersUnderSingleMode(e);const o=await this.getEntryGeometriesModule(e,t),n=s.features;"add"===r?await ee(o,n):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(o,n);const a=s._shown;s._shown=!0,a||this.events.emit("shown-change",this.shownEntryIds)}async _hideOthersUnderSingleMode(e){if("single"!==this._entryMode)return;const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const r of t)await this.hideEntry(r.id)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&t._shown&&(t._module&&await t._module.clear(),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`CustomGeometriesState: unknown entry "${e}"`);return t}_uniqueEntryId(e){const t=new Set(this._entries.map(e=>e.id));if(!t.has(e))return e;for(let r=2;;r++){const s=`${e}-${r}`;if(!t.has(s))return s}}reset(){for(const e of this._entries)e._module?.clear();this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},re=e=>({places:{shownAsPin:[...e.places.shownAsPinIds].sort(),shownAsBaseMap:[...e.places.shownAsBaseMapIds].sort(),entryMode:e.places.entryMode},routes:{shown:[...e.routing.shownEntryIds].sort(),entryMode:e.routing.entryMode},ranges:{shown:[...e.ranges.shownEntryIds].sort(),entryMode:e.ranges.entryMode},traffic:{analyticsActive:void 0!==e.trafficAreaAnalytics.lastAreaAnalytics},incidents:{shown:[...e.trafficIncidents.shownEntryIds].sort(),monitored:e.trafficIncidents.entries.filter(e=>"running"===e._monitor?.status).map(e=>e.id).sort(),entryMode:e.trafficIncidents.entryMode}}),se=(e,t)=>{if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(e[r]!==t[r])return!1;return!0},oe=(e,t,r)=>{if(!se(t,r))return 0===r.length?`${e} cleared`:`${e} shown: ${r.map(e=>`"${e}"`).join(", ")}`},ne=(e,t)=>{const r=[],s=((e,t)=>{const r=[];return se(e.shownAsPin,t.shownAsPin)||r.push(0===t.shownAsPin.length?"no places shown as pins":`places shown as pins: ${t.shownAsPin.map(e=>`"${e}"`).join(", ")}`),se(e.shownAsBaseMap,t.shownAsBaseMap)||r.push(0===t.shownAsBaseMap.length?"no places shown as base-map POIs":`places shown as base-map POIs: ${t.shownAsBaseMap.map(e=>`"${e}"`).join(", ")}`),r.length?r.join("; "):void 0})(e.places,t.places);s&&r.push(s);const o=oe("routes",e.routes.shown,t.routes.shown);o&&r.push(o);const n=oe("ranges",e.ranges.shown,t.ranges.shown);n&&r.push(n),e.traffic.analyticsActive!==t.traffic.analyticsActive&&r.push(t.traffic.analyticsActive?"traffic analytics now active":"traffic analytics cleared");const a=oe("incidents",e.incidents.shown,t.incidents.shown);a&&r.push(a);const i=oe("incidents monitor",e.incidents.monitored,t.incidents.monitored);return i&&r.push(i),e.places.entryMode!==t.places.entryMode&&r.push(`places entryMode → ${t.places.entryMode}`),e.routes.entryMode!==t.routes.entryMode&&r.push(`routes entryMode → ${t.routes.entryMode}`),e.ranges.entryMode!==t.ranges.entryMode&&r.push(`ranges entryMode → ${t.ranges.entryMode}`),e.incidents.entryMode!==t.incidents.entryMode&&r.push(`incidents entryMode → ${t.incidents.entryMode}`),0===r.length?null:`[map state changed since last response: ${r.join("; ")}]`},ae=class{_ttMap;_poisModule;constructor(e){this._ttMap=e}async getPOIsModule(){return this._poisModule??=await i.get(this._ttMap),this._poisModule}get poisModule(){return this._poisModule}reset(){this._poisModule=void 0}},ie=e=>{switch(e){case"pin":return"pin";case"base-map":return"baseMap";case"pin-clustered":return"pinClustered"}},le=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const s of e){const e="string"==typeof s.id?s.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(s)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},ce=class{_ttMap;_entries=[];_entryMode="multiple";events=new Q;constructor(e){this._ttMap=e}get entryMode(){return this._entryMode}async setEntryMode(e){if(this._entryMode!==e){if(this._entryMode=e,"single"===e&&this._entries.length>1){const e=this._entries.at(-1),t=this._entries.slice(0,-1);for(const r of t)await this._hideEntry(r);this._entries=e?[e]:[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}this.events.emit("mode-change",e)}}async getEntryPlacesModule(e,t){const r=this._requireEntry(e);r._modules??={};const s=ie(t);let o=r._modules[s];return o||(o=await l.get(this._ttMap,{theme:t}),r._modules[s]=o),o}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);r._modules??={};const s={...f(),theme:t};return r._modules.geometries?r._modules.geometries.applyConfig(s):r._modules.geometries=await n.get(this._ttMap,s),r._modules.geometries}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`PlacesState: unknown entry "${e}"`);return t}get entries(){return this._entries}get latestPlace(){return this._entries.at(-1)?.places}addPlaceResult(e,t,r,s,o){if("single"===this._entryMode&&this._entries.length>0){for(const e of this._entries)this._hideEntry(e);this._entries=[]}const n=`places-${this._entries.length}`,a=r?this._uniqueEntryId(r):n;return this._entries.push({id:a,timestamp:Date.now(),label:t,places:"features"in e?e.features:[e],...s?.length&&{connections:s},...o?.length&&{geometries:o}}),this.events.emit("entries-change",this._entries),o?.length&&this.events.emit("geometries-change",{entryId:a,count:o.length}),a}_uniqueEntryId(e){const t=new Set(this._entries.map(e=>e.id));if(!t.has(e))return e;for(let r=2;;r++){const s=`${e}-${r}`;if(!t.has(s))return s}}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}findPlaceById(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(!r._byId){r._byId={};for(const e of r.places)r._byId[e.id]=e}const s=r._byId[e];if(s)return{place:s,entryId:r.id}}}getGeometryForPlace(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(!r.geometries?.length)continue;r._geometryByPlaceId||(r._geometryByPlaceId=this._buildGeometryIndex(r));const s=r._geometryByPlaceId[e];if(s)return s}}async fetchPlaceGeometry(e){const t=this.getGeometryForPlace(e);if(t)return t;const r=this.findPlaceById(e);if(!r)return;if(!r.place.properties.dataSources?.geometry?.id)return;const s=(await P({geometries:[r.place]})).features[0];if(!s)return;const o=this._entries.find(e=>e.id===r.entryId);return o&&(o.geometries??=[],o.geometries.push(s),o._geometryByPlaceId??={},o._geometryByPlaceId[e]=s,this.events.emit("geometries-change",{entryId:o.id,count:o.geometries.length})),s}async fetchGeometriesForEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)return[];const r=/* @__PURE__ */new Set;for(const l of t.geometries??[])"string"==typeof l.id&&r.add(l.id);const s=[],o=/* @__PURE__ */new Set;for(const l of t.places){const e=l.properties.dataSources?.geometry?.id;e&&(r.has(e)||o.has(e)||(o.add(e),s.push(l)))}if(0===s.length)return t.geometries??[];const n=[];for(let l=0;l<s.length;l+=5)n.push(s.slice(l,l+5));const a=await Promise.all(n.map(e=>P({geometries:e}))),i=[...t.geometries??[]];for(const l of a)i.push(...l.features);return t.geometries=i,t._geometryByPlaceId=void 0,this.events.emit("geometries-change",{entryId:t.id,count:i.length}),i}async showPlaceGeometries(e,t="outline",r="replace",s){const o=s??this._latestGeometriesEntryId();if(!o)return;const n=await this.getEntryGeometriesModule(o,t);"add"===r?await le(n,e):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(n,e)}_latestGeometriesEntryId(){for(let e=this._entries.length-1;e>=0;e--)if(this._entries[e].geometries?.length)return this._entries[e].id;return this._entries.at(-1)?.id}_buildGeometryIndex(e){const t=/* @__PURE__ */new Map;for(const s of e.geometries??[])"string"==typeof s.id&&t.set(s.id,s);const r={};for(const s of e.places){const e=s.properties.dataSources?.geometry?.id;if(!e)continue;const o=t.get(e);o&&(r[s.id]=o)}return r}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shownAs&&e.add(t.id);return e}get shownAsPinIds(){return this._idsShownAs("pin")}get shownAsBaseMapIds(){return this._idsShownAs("base-map")}get shownAsPinClusteredIds(){return this._idsShownAs("pin-clustered")}_idsShownAs(e){const t=/* @__PURE__ */new Set;for(const r of this._entries)r._shownAs===e&&t.add(r.id);return t}getShownMarkerType(e){return this._entries.find(t=>t.id===e)?._shownAs}getShownFeatures(){const e=[];for(const t of this._entries)t._shownAs&&e.push(...t.places);return e}async setShownEntries(e,t){const r=new Set(e),s=[],o=[];for(const n of this._entries)r.has(n.id)?s.push(n):n._shownAs===t&&o.push(n);for(const n of o)await this._hideEntry(n);for(const n of s)await this._showEntryAs(n,t);return this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async addShownEntries(e,t){if("single"===this._entryMode){const r=e.at(-1);return r?this.setShownEntries([r],t):this.getShownFeatures()}const r=e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?[t]:[]}).filter(e=>e._shownAs!==t);for(const s of r)await this._showEntryAs(s,t);return r.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async removeShownEntries(e){const t=e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._shownAs?[t]:[]});for(const r of t)await this._hideEntry(r);return t.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async clearShownEntries(){const e=this._entries.filter(e=>e._shownAs);if(0!==e.length){for(const t of e)await this._hideEntry(t);this.events.emit("shown-change",this.shownEntryIds)}}async clearShownGeometries(e){const t=e?e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._modules?.geometries?[t]:[]}):this._entries.filter(e=>e._modules?.geometries);if(0!==t.length)for(const r of t)await r._modules.geometries.clear(),this.events.emit("geometries-change",{entryId:r.id,count:0})}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],s=!!r._shownAs;r._modules&&(await(r._modules.pin?.clear()),await(r._modules.baseMap?.clear()),await(r._modules.pinClustered?.clear()),await(r._modules.geometries?.clear())),this._entries.splice(t,1),this.events.emit("entries-change",this._entries),s&&this.events.emit("shown-change",this.shownEntryIds)}async _showEntryAs(e,t){if(e._shownAs&&e._shownAs!==t){const t=e._modules?.[ie(e._shownAs)];t&&await t.clear()}const r=await this.getEntryPlacesModule(e.id,t);await r.show({type:"FeatureCollection",features:e.places}),e.connections?.length&&await r.showConnections(e.connections),e._shownAs=t}async _hideEntry(e){if(!e._shownAs)return;const t=e._modules?.[ie(e._shownAs)];t&&await t.clear(),e._shownAs=void 0}reset(){for(const e of this._entries)e._modules&&(e._modules.pin?.clear(),e._modules.baseMap?.clear(),e._modules.pinClustered?.clear(),e._modules.geometries?.clear());this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},de=class{_ttMap;_entries=[];_entryMode="multiple";events=new Q;constructor(e){this._ttMap=e}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){if(this._entryMode!==e){if(this._entryMode=e,"single"===e&&this._entries.length>1){const e=this._entries.at(-1),t=this._entries.slice(0,-1);for(const r of t)await this.hideEntry(r.id);this._entries=e?[e]:[],this.events.emit("entries-change",this._entries)}this.events.emit("mode-change",e)}}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);return r._modules??={},r._modules.geometries?r._shownTheme!==t&&(r._modules.geometries.applyConfig(y(void 0,t)),r._shownTheme=t):(r._modules.geometries=await n.get(this._ttMap,y(void 0,t)),r._shownTheme=t),r._modules.geometries}async getEntryPlacesModule(e){const t=this._requireEntry(e);return t._modules??={},t._modules.places||(t._modules.places=await l.get(this._ttMap)),t._modules.places}async markEntryShown(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&t._shown&&(t._modules?.geometries&&await t._modules.geometries.clear(),t._modules?.places&&await t._modules.places.clear(),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RangeState: unknown entry "${e}"`);return t}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}addEntry(e){if("single"===this._entryMode&&this._entries.length>0){for(const e of this._entries)this.hideEntry(e.id);this._entries=[]}const t=`ranges-${this._entries.length}`;return this._entries.push({id:t,timestamp:Date.now(),...e}),this.events.emit("entries-change",this._entries),t}reset(){for(const e of this._entries)e._modules?.geometries?.clear(),e._modules?.places?.clear();this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},ue=class{_ttMap;_entries=[];_planningSlots=[];_params={};_entryMode="multiple";_lastShownEntryId;events=new Q;constructor(e){this._ttMap=e}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){if(this._entryMode!==e){if(this._entryMode=e,"single"===e&&this._entries.length>1){const e=this._entries.at(-1),t=this._entries.slice(0,-1);for(const r of t)await this.hideEntry(r.id);this._entries=e?[e]:[],this.events.emit("entries-change",this._entries)}this.events.emit("mode-change",e)}}async getEntryRoutingModule(e){const t=this._requireEntry(e);return t._module||(t._module=await c.get(this._ttMap)),t._module}async showEntry(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=await this.getEntryRoutingModule(e);await r.showRoutes(t.data),t.waypoints.length&&await r.showWaypoints(t.waypoints);const s=t._shown;t._shown=!0,this._lastShownEntryId=e,s||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&t._shown&&(t._module&&(await t._module.clearRoutes(),await t._module.clearWaypoints()),t._shown=!1,this._lastShownEntryId===e&&(this._lastShownEntryId=this._entries.findLast(e=>e._shown)?.id),this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RoutingState: unknown entry "${e}"`);return t}get currentEntryModule(){if(!this._lastShownEntryId)return;const e=this._entries.find(e=>e.id===this._lastShownEntryId);return e?._shown?e._module:void 0}get entries(){return this._entries}get currentRoutes(){return this._entries.at(-1)?.data}get currentWaypoints(){return this._entries.at(-1)?.waypoints}get planningSlots(){return this._planningSlots}get params(){return this._params}addRoutes(e,t,r){if("single"===this._entryMode&&this._entries.length>0){for(const e of this._entries)this.hideEntry(e.id);this._entries=[]}const s=`routes-${this._entries.length}`;return this._entries.push({id:s,timestamp:Date.now(),label:r,data:e,waypoints:t,params:{...this._params}}),this._planningSlots=[...t],this.events.emit("entries-change",this._entries),this.events.emit("planning-change",this._planningSlots),s}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}setWaypointAt(e,t){const r=Math.max(2,e+1);for(;this._planningSlots.length<r;)this._planningSlots.push(null);this._planningSlots[e]=t,this.events.emit("planning-change",this._planningSlots)}setParams(e){this._params={...this._params,...e},this.events.emit("params-change",this._params)}reset(){for(const e of this._entries)e._module?.clearRoutes(),e._module?.clearWaypoints();this._entries=[],this._planningSlots=[],this._params={},this._lastShownEntryId=void 0,this.events.emit("entries-change",this._entries),this.events.emit("planning-change",this._planningSlots),this.events.emit("params-change",this._params),this.events.emit("shown-change",this.shownEntryIds)}},pe=class{_ttMap;_trafficAreaAnalyticsModule;_lastAreaAnalytics;_currentAnalyticsConfig;_configChangeUnsub;events=new Q;constructor(e){this._ttMap=e}async getTrafficAreaAnalyticsModule(){return this._trafficAreaAnalyticsModule||(this._trafficAreaAnalyticsModule=await d.get(this._ttMap),this._configChangeUnsub=this._trafficAreaAnalyticsModule.events.on("config-change",e=>{this._currentAnalyticsConfig=e,this.events.emit("config-change",e)})),this._trafficAreaAnalyticsModule}get trafficAreaAnalyticsModule(){return this._trafficAreaAnalyticsModule}get lastAreaAnalytics(){return this._lastAreaAnalytics}setLastAreaAnalytics(e){this._lastAreaAnalytics=e}get currentAnalyticsConfig(){return this._currentAnalyticsConfig}notifyAnalyticsShown(e,t){this.events.emit("analytics-shown",{analytics:e,module:t})}notifyAnalyticsCleared(){this.events.emit("analytics-cleared",void 0)}reset(){this._configChangeUnsub?.(),this.notifyAnalyticsCleared(),this._trafficAreaAnalyticsModule=void 0,this._lastAreaAnalytics=void 0,this._currentAnalyticsConfig=void 0,this._configChangeUnsub=void 0}},he=(K.union([K.tuple([K.number(),K.number(),K.number(),K.number()]),K.object({bbox:K.tuple([K.number(),K.number(),K.number(),K.number()]),padding:K.number().optional(),animate:K.boolean().optional()})]).describe("Bounds to fit the camera to: a `[W, S, E, N]` BBox tuple, or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`. Default padding: 50px."),K.object({bbox:K.tuple([K.number(),K.number(),K.number(),K.number()]).describe("Bounds the camera was fit to."),padding:K.number().describe("Padding (px) applied around the bbox.")})),me="`fitOnMap` (opt): `[W,S,E,N]` BBox or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }` (default padding 50). Camera fits these bounds after the tool runs. Returning ONLY `fitOnMap` skips state writes (pure camera focus); combine with `places` / `geometries` to also write+focus. Derive bboxes via `turf.bbox(feature | FeatureCollection)`.",ye=e=>Array.isArray(e)&&4===e.length&&e.every(e=>"number"==typeof e&&Number.isFinite(e)),ge=e=>{if(ye(e))return!0;if(!e||"object"!=typeof e)return!1;const t=e;return!!ye(t.bbox)&&(!!(void 0===t.padding||"number"==typeof t.padding&&Number.isFinite(t.padding))&&!(void 0!==t.animate&&"boolean"!=typeof t.animate))},fe=(e,t)=>{const r=ye(t)?t:t.bbox,s=(ye(t)?void 0:t.padding)??50,o=ye(t)?void 0:t.animate;return e.baseMap.mapLibreMap.fitBounds(r,{padding:s,...void 0!==o&&{animate:o}}),{bbox:r,padding:s}},be=K.discriminatedUnion("kind",[K.object({kind:K.literal("place").describe('Use the exact literal "place".'),id:K.string().min(1).describe("Id of a single place; resolves to that place’s footprint.")}).describe("A single place footprint."),K.object({kind:K.literal("places").describe('Use the exact literal "places".'),id:K.string().min(1).describe("Id of a places-entry; resolves to every footprint in the entry.")}).describe("Every footprint in a places entry."),K.object({kind:K.literal("ranges").describe('Use the exact literal "ranges".'),id:K.string().min(1).describe("Id of a ranges-entry; resolves to every isochrone polygon in the entry.")}).describe("Every isochrone polygon in a ranges entry."),K.object({kind:K.literal("custom").describe('Use the exact literal "custom".'),id:K.string().min(1).describe("Id of a custom-geometries entry produced by a previous processGeometries.")}).describe("A derived entry produced by a previous processGeometries.")]).describe('Tagged id `{ kind, id }`. `kind` must be exactly one of: "place" | "places" | "ranges" | "custom" (do NOT use variants like "ranges-entry", "place-footprints", etc.). Use `recallGeometries` to list every available `{kind, id}` pair.'),we=K.array(be).min(1).describe('Tagged ids `{ kind, id }` of polygon sources to feed the run. `kind` must be exactly one of `"place"` | `"places"` | `"ranges"` | `"custom"` (no variants). Mix kinds freely (e.g. union place footprints with isochrones). Use `recallGeometries` to list available ids.'),ve="`geometry.properties` shape varies by source — place footprints: `CommonPlaceProps`; ranges: service response (`range.type`, `range.value`); custom: whatever the producing op set. Treat as opaque unless you know the source. Every feature also carries `properties._source: { kind, id }` (the tagged id it was passed in under) — use it to filter / partition mixed inputs by role inside the sandbox.",Ie="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",Se="Tagged input ids that contributed at least one polygon to the run, in input order.",xe='Schema (the `geometries` array shape):\n```ts\ntype Geometry = { // a polygon footprint for one place\n type: "Feature";\n id: string; // geometry-data-source id (matches place.properties.dataSources.geometry.id)\n geometry: { type: "Polygon" | "MultiPolygon"; coordinates: number[][][] | number[][][][] };\n bbox: [minLng, minLat, maxLng, maxLat];\n properties?: { /* place props if available — usually empty here */ };\n};\ntype Geometries = Geometry[]; // ordered list, same length as the input place ids when fully populated\n```\nNotes: every polygon is a closed GeoJSON ring (last point == first). Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, `turf.buffer`, etc. directly on these features. For h3 work, convert with `turf.bbox(feature)` → cells, or iterate `feature.geometry.coordinates`.',Pe=async(e,t,r)=>{if("multiple"!==e.entryMode||void 0===r)return;const s=new Set(t),o="all"===r?[...e.shownEntryIds].filter(e=>!s.has(e)):r.filter(e=>!s.has(e));for(const n of o)await e.hideEntry(n)},Me=K.union([K.object({query:K.string().describe("Location string to resolve."),queryAs:K.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.')}),K.object({position:K.object({lng:K.number(),lat:K.number()}).describe("Explicit coordinates — use when you already have a position.")}),K.object({placeId:K.string().describe("ID of a place stored in session state (any previous places entry). Resolves to the full Place with its POI name, address, and coordinates — prefer this over re-searching by name when routing to a place the user has already seen.")})]),Ee='Schema (the full `Place` / `Places` shape you can rely on):\n```ts\ntype Places = { type: "FeatureCollection"; features: Place[]; bbox?: [minLng, minLat, maxLng, maxLat] };\ntype Place = {\n type: "Feature";\n id: string;\n geometry: { type: "Point"; coordinates: [lng, lat] };\n bbox?: [minLng, minLat, maxLng, maxLat]; // present for areas (Street, Geography, ...)\n properties: {\n type: "POI" | "Street" | "Geography" | "Point Address" | "Address Range" | "Cross Street";\n address: {\n freeformAddress: string;\n streetNumber?: string; streetName?: string;\n municipality?: string; municipalitySubdivision?: string;\n countrySubdivision?: string; countrySubdivisionName?: string;\n countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\n postalCode?: string; extendedPostalCode?: string;\n countryCode?: string; countryCodeISO3?: string; country?: string;\n localName?: string;\n };\n poi?: { // present when type === "POI"\n name: string; phone?: string; url?: string;\n brands?: string[];\n categories: string[]; // POICategory enum values, e.g. "RESTAURANT", "CAFE_PUB", "GAS_STATION", "HOTEL_MOTEL", "ELECTRIC_VEHICLE_STATION"\n localizedCategories: string[]; // human-readable, language-sensitive\n openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\n timeZone?: { name: string };\n };\n // Search results also carry:\n score?: number; // relevance score (higher = better)\n distance?: number; // meters from geoBias, when one was provided\n info?: string;\n // Geography results also carry:\n geographyType?: Array<"Country"|"CountrySubdivision"|"CountrySecondarySubdivision"|"CountryTertiarySubdivision"|"Municipality"|"MunicipalitySubdivision"|"Neighbourhood"|"PostalCodeArea">;\n // Misc optional fields: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\n };\n};\n```\nNotes: coordinates are always [lng, lat] (GeoJSON order); fields marked `?` may be undefined — guard with optional chaining before use.',_e=new Set(W),Te='Schema (the full `Route` / `Routes` shape you can rely on):\n```ts\ntype Routes = { type: "FeatureCollection"; features: Route[]; bbox?: [minLng, minLat, maxLng, maxLat]; properties?: { requestId?: string; calculatedAt?: Date } };\ntype Route = {\n type: "Feature";\n id: string;\n geometry: { type: "LineString"; coordinates: [lng, lat][] };\n bbox: [minLng, minLat, maxLng, maxLat];\n properties: {\n index: number; // 0 = main route, 1+ = alternatives\n summary: {\n lengthInMeters: number;\n travelTimeInSeconds: number; // includes traffic delay\n noTrafficTravelTimeInSeconds?: number; // free-flow estimate\n trafficDelayInSeconds: number;\n trafficLengthInMeters: number;\n departureTime: Date; arrivalTime: Date;\n // EV-only (when applicable):\n batteryConsumptionInkWh?: number; batteryConsumptionInPCT?: number;\n remainingChargeAtArrivalInkWh?: number; remainingChargeAtArrivalInPCT?: number;\n totalChargingTimeInSeconds?: number;\n // Combustion-only:\n fuelConsumptionInLiters?: number;\n };\n sections: { // segments of the route grouped by characteristic. Every entry has { id, startPointIndex, endPointIndex } unless noted; *Index values reference `geometry.coordinates`.\n leg: Array<{ id; startPointIndex?; endPointIndex?; summary: RouteSummary }>; // ALWAYS present; one per non-circle waypoint pair\n country?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // ISO 3166-1 alpha-3 — "USA","NLD","DEU"\n tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // same shape as country\n traffic?: Array<{\n id; startPointIndex; endPointIndex;\n categories: TrafficIncidentCategory[]; // e.g. "Jam","Accident","RoadWorks","Closure"\n magnitudeOfDelay: "Unknown"|"Minor"|"Moderate"|"Major"|"Undefined";\n tec: { effectCode?: number; causes?: Array<{ mainCauseCode: number; subCauseCode?: number }> };\n effectiveSpeedInKmh?: number; // actual avg speed through the incident\n delayInSeconds?: number; // extra time vs free-flow\n }>;\n importantRoadStretch?: Array<{ id; startPointIndex; endPointIndex; index: number; streetName?: string; roadNumbers?: string[] }>;\n lanes?: Array<{\n id; startPointIndex; endPointIndex;\n lanes: Array<{ directions: PossibleLaneDirection[]; follow?: PossibleLaneDirection }>;\n laneSeparators: PossibleLaneSeparator[];\n properties?: string[]; // e.g. "IS_MANEUVER"\n }>;\n speedLimit?: Array<{ id; startPointIndex; endPointIndex; maxSpeedLimitInKmh: number }>;\n roadShields?: Array<{ id; startPointIndex; endPointIndex; roadShieldReferences: RoadShieldReference[] }>;\n // Plain section ranges (id + startPointIndex + endPointIndex only):\n ferry?: SectionProps[]; motorway?: SectionProps[]; toll?: SectionProps[]; tunnel?: SectionProps[];\n unpaved?: SectionProps[]; urban?: SectionProps[]; pedestrian?: SectionProps[];\n carpool?: SectionProps[]; carTrain?: SectionProps[];\n lowEmissionZone?: SectionProps[]; vehicleRestricted?: SectionProps[];\n };\n progress?: Array<{ pointIndex: number; travelTimeInSeconds?: number; distanceInMeters?: number }>; // cumulative milestones along the geometry\n guidance?: { instructions: Array<{ pointIndex: number; maneuver: string; street?: string; message?: string }> }; // turn-by-turn (when requested)\n };\n};\n```\nNotes: coordinates are [lng, lat] (GeoJSON order). `pointIndices` reference positions in `geometry.coordinates`. Fields marked `?` may be undefined — guard with optional chaining. Use `summary.lengthInMeters` / `summary.travelTimeInSeconds` for "how long / how far" questions.',Ae=e=>`In scope: ${e.map(e=>`\`${e}\``).join(", ")}. Already injected as function parameters — use directly. Do NOT \`require()\` / \`import\` / redeclare them; the body is a sandboxed async function (no Node module system). \`h3\` is h3-js (hex-grid math: \`latLngToCell\`, \`polygonToCells\`, \`gridDisk\`, \`cellToBoundary\`, …). \`turf\` is @turf/turf (GeoJSON: \`area\`, \`bbox\`, \`centroid\`, \`union\`, \`intersect\`, \`buffer\`, \`distance\`, \`booleanPointInPolygon\`, \`pointsWithinPolygon\`, \`clustersDbscan\`, \`convex\`, …; verify names at https://turfjs.org/docs/). Neither library searches places or fetches data — call \`discoverPlaces\` BEFORE this tool to bring in new places. Top-level \`await\`, locals, and helper consts are fine. End with \`return …;\`.`,Ce=new Set(["bar","line","pie","doughnut","radar","polarArea","scatter","bubble"]),Re=e=>{if(!e||"object"!=typeof e)return!1;const t=e;if("Feature"!==t.type)return!1;const r=t.geometry;return!!r&&("Polygon"===r.type||"MultiPolygon"===r.type)&&Array.isArray(r.coordinates)},De=e=>Array.isArray(e)&&e.every(Re),ke='Shape of the returned value (default: "json"). `json` — compact JSON-serializable object for text display. `chart` — Chart.js `ChartConfiguration` rendered via chart.js in the chat UI. Pick `chart` whenever the user asks for a "chart", "graph", "plot", "histogram", "bar/pie/line chart", etc.',Oe=(e,t="")=>`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• \`json\` (default) — compact object (${e}, …).\n• \`chart\` — Chart.js config: \`{ type: "bar"|"line"|"pie"|"doughnut"|"radar"|"polarArea"|"scatter"|"bubble", data: { labels: (string | string[])[], datasets }, options? }\`. 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${t?` (e.g. ${t})`:""}.`,je=(e,t)=>{if(void 0===e)return{error:"Analysis code must return a value (the aggregation result). Hint: `code` is the function BODY, not a function expression. If you wrote `(args) => { ...; return result; }` (or `async (args) => { ... }`) as the whole code, the arrow is created and discarded — drop the wrapper and have the body itself end with `return result;`."};const r=Fe(e);return"error"in r?{error:r.error}:"chart"!==t||(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"string"==typeof t.type&&Ce.has(t.type)&&"object"==typeof t.data&&null!==t.data})(r.value)?{value:r.value}:{error:'Analysis code with `outputFormat: "chart"` must return a Chart.js ChartConfiguration ({ type: "bar"|"line"|"pie"|"doughnut"|"radar"|"polarArea"|"scatter"|"bubble", data: { labels, datasets }, options? }).'}},Fe=e=>{try{return{value:JSON.parse(JSON.stringify(e))}}catch(t){return{error:`Returned value is not JSON-serializable: ${t instanceof Error?t.message:String(t)}. Return only plain objects, arrays, strings, numbers, booleans, and null — no functions, classes, circular references, or DOM/Map references.`}}},Le=async(e,t,r,s)=>{try{const s=Object.getPrototypeOf(async()=>{}).constructor,o=Ne(e,t);return{value:await new s(...t,o)(...r)}}catch(o){return{error:qe(s,o)}}},Ne=(e,t)=>{const r=t.join("|"),s=new RegExp(String.raw`^[ \t]*(?:const|let|var)[ \t]+(?:${r})[ \t]*=[ \t]*(?:await\s+)?(?:require|import)\s*\([^\n]*\n?`,"gm");return e.replace(s,"")},$e=[{pattern:/Cannot read propert(?:y|ies) of undefined/i,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."},{pattern:/Cannot read propert(?:y|ies) of null/i,hint:"A property was read on `null`. Guard property access with `?.` and provide defaults with `??`."},{pattern:/\bh3\.\w+ is not a function/i,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."},{pattern:/\bturf\.\w+ is not a function/i,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."},{pattern:/is not a function/i,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(...) : []`."},{pattern:/Illegal return statement/i,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."},{pattern:/Identifier ['"]?\w+['"]? has already been declared/i,hint:"A variable was declared twice — typically because the code prepends `const turf = require(...)` / `const h3 = require(...)` / `const places = ...`. Those names are already injected as parameters; remove the redeclaration line and use them directly."},{pattern:/Unexpected (?:token|identifier|end of input)/i,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 ...;`."},{pattern:/\brequire is not defined\b|\bimport (?:is not defined|outside a module)\b/i,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."},{pattern:/\b(?:functions|tools|agent|recallPlaces|recallRoutes|recallRanges|discoverPlaces|locatePlace) is not defined\b/i,hint:"The sandbox cannot call other tools — `functions.recallPlaces(...)`, `tools.discoverPlaces(...)`, etc. are NOT available. To use additional entries, list every needed id in `placesEntryIDs` (or `routesEntryIDs`/`rangesIds` for the analyse-* tools): each is exposed via the injected inputs. To bring in NEW places call `discoverPlaces` BEFORE this tool and pass the resulting entry id back in."},{pattern:/is not iterable/i,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 ?? []) { ... }`."},{pattern:/Cannot access '.+?' before initialization/i,hint:"A `let`/`const` was used before its declaration (temporal dead zone). Move the declaration above its first use."},{pattern:/Assignment to constant variable/i,hint:"A `const`-declared variable was reassigned. Either use `let`, or mutate the value in place."},{pattern:/structuredClone|could not be cloned|DataCloneError/i,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."}],qe=(e,t)=>{const r=t instanceof Error?t.message:String(t),s=`${e} code execution failed: ${r}`;if(t instanceof Error&&"SyntaxError"===t.name)return`${s} Hint: the provided \`code\` is not valid JavaScript — ensure it parses as an async function body and \`return\`s a value.`;const o=$e.find(({pattern:e})=>e.test(r));return o?`${s} Hint: ${o.hint}`:s},Be=K.union([K.array(K.number()).refine(e=>4===e.length,{message:"BBox must have 4 elements"})]).describe("[minLng, minLat, maxLng, maxLat] or [W,S,E,N]"),Ge=K.object({type:K.literal("Polygon"),coordinates:K.array(K.array(K.array(K.number())))}),Ue=K.object({type:K.literal("MultiPolygon"),coordinates:K.array(K.array(K.array(K.array(K.number()))))}),We=K.union([Ge,Ue]),ze=({verb:e,extra:t})=>K.array(K.string()).min(1).optional().describe(`IDs of existing places entries to ${e} (e.g. ["places-2"], ["places-1", "places-2"]). Use recallPlaces to list IDs. Omit to ${e} the latest entry.`+(t?` ${t}`:"")),Ve=({verb:e,extra:t})=>K.array(K.string()).min(1).optional().describe(`IDs of existing routes entries to ${e} (e.g. ["routes-0"], ["routes-0", "routes-1"]). Use recallRoutes to list IDs. Omit to ${e} the latest route.`+(t?` ${t}`:"")),He=K.string().regex(/^[a-z0-9][a-z0-9-]{1,48}$/,"Use lowercase letters, digits, and hyphens (2–49 chars).").optional().describe('Compact lowercase kebab-case id derived from the user prompt (e.g. "vegan-cafes-amsterdam", "ev-stations-along-route"). Avoid generic names like "places" / "results". Auto-suffixed on collision — do not track uniqueness yourself. Default: `places-N`.'),Je=K.enum(["add","replace"]).optional().describe('WITHIN THE TARGETED ENTRY ONLY — controls how the new polygons combine with what is already rendered on this entry\'s GeometriesModule layer. Does NOT affect other entries (use `hidePreviousEntries` for that). "replace" (default) clears this entry\'s layer first; "add" stacks (deduped by feature id).'),Ye=K.object({show:K.boolean().optional().describe("Also render the polygons on the map. Default: false."),mode:Je,theme:K.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline".')}).optional().describe('Batch-fetch boundary polygons for the resulting places and cache them on the entry. Use when the user wants the *area / shape / boundary* (e.g. "outline this neighbourhood", "show the city footprint"). Set `show: true` to also render. Skips places without a geometry source (most addresses/streets).'),Ke=K.object({theme:K.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline".'),placeIds:K.array(K.string()).optional().describe("Subset of place ids within the targeted entries — render boundaries only for these. Ids outside the entries are ignored. Default: all places with a geometry source."),mode:Je}).describe('Render boundary polygons for the targeted entries. Use when the user wants the *area / shape / outline / footprint* (e.g. "outline this neighbourhood", "show the city footprint"). Independent of `markerType` — set both to render pins AND boundaries.'),Xe=K.enum(["base-map","pin","pin-clustered","none"]).describe('"pin" for few results; "base-map" for 10+; "pin-clustered" aggregates nearby pins under a count badge (best for dense city-scale POI sets — clusters expose aggregated `placeTypes` / `poiCategories`); "none" for towns/cities/areas.'),Ze=e=>K.union([K.literal("all"),K.array(K.string())]).optional().describe(`Which previously-shown ${e} entries to hide before showing the new ones. \`"all"\` clears every previous entry (use for the typical "show X" — replace existing); an array hides only the listed entry IDs. Omit to stack on top of existing entries (use for "also show", "additionally", "and X"). The entries being shown by this call are never hidden. Only applied when \`entryMode === "multiple"\` (in \`single\` mode every other entry is auto-hidden, so this prop is ignored).`),Qe=K.object({markerType:Xe,zoomMode:K.enum(["auto","none"]).describe('Camera behaviour. "none" keeps the map still.'),hidePreviousEntries:Ze("places")}),et=K.object({markerType:K.boolean(),zoomMode:K.boolean()}),tt={mode:K.literal("within"),viewport:K.literal(!0).optional().describe("When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.")},rt=K.enum(["poi","place"]).optional().describe('Drives query resolution. `"poi"` for venues / landmarks / businesses (e.g. "Eiffel Tower"). `"place"` for addresses, cities, neighborhoods, parks, postal codes (e.g. "De Jordaan, Amsterdam", "1600 Pennsylvania Ave"). Default: "place".'),st=["boundingBox","viewport","query","placeId"],ot=K.object({...tt,boundingBox:Be.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId."),query:K.string().optional().describe('Resolve a name/address/POI → its boundary polygon (or bbox) as search area (e.g. "Paris", "De Jordaan, Amsterdam"). Pair with `queryAs` to disambiguate. EXCLUSIVE with boundingBox / viewport / placeId.'),queryAs:rt,placeId:K.string().optional().describe("ID of a session place — its boundary polygon (fetched on demand) is the search area. EXCLUSIVE with boundingBox / viewport / query.")}).refine(e=>1===st.filter(t=>void 0!==e[t]).length,{message:"`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId."}),nt=["position","viewport","query"],at=K.object({mode:K.literal("nearby"),position:K.array(K.number()).length(2).optional().describe("Bias point [lng, lat]. EXCLUSIVE with viewport / query."),viewport:K.literal(!0).optional().describe("When true, bias toward the current map viewport centre. EXCLUSIVE with position / query."),query:K.string().optional().describe("Resolve a name/address/POI → its centroid as bias point. Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport."),queryAs:rt,radiusMeters:K.number().positive().optional().describe("Hard radius cap (metres) around the bias point. Independent of bias source.")}).refine(e=>1===nt.filter(t=>void 0!==e[t]).length,{message:"`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent)."}),it=K.object({mode:K.literal("global")}),lt=K.union([ot,at,it]),ct=async(e,t,r)=>{const s=await e.places.fetchGeometriesForEntry(t);return r.show&&s.length>0?(await e.places.showPlaceGeometries(s,r.theme??"outline",r.mode??"replace"),{fetched:s.length,shown:!0}):{fetched:s.length,shown:!1}},dt=async(e,t,r)=>{const s={markerType:!1,zoomMode:!1};if("none"!==r.markerType){if("multiple"===e.places.entryMode&&"all"!==r.hidePreviousEntries){if(Array.isArray(r.hidePreviousEntries)){const s=new Set(t),o=r.hidePreviousEntries.filter(e=>!s.has(e));o.length>0&&await e.places.removeShownEntries(o)}await e.places.addShownEntries(t,r.markerType)}else await e.places.setShownEntries(t,r.markerType);s.markerType=!0}if("auto"===r.zoomMode){const t=e.places.getShownFeatures();if(t.length>0){const r=O({type:"FeatureCollection",features:t});r&&e.baseMap.mapLibreMap.fitBounds(r,{padding:50})}s.zoomMode=!0}return s},ut=K.object({source:be,reason:K.string()}),pt=e=>`${e.source.kind}:${e.source.id}: ${e.reason}`,ht=(e,t)=>({...e,properties:{...e.properties??{},_source:t}}),mt=async(e,t)=>{const r=t.places.findPlaceById(e);if(!r)return{kind:"skip",reason:"No place with this id in session state. Use `recallGeometries` to list known places ids and entries."};if(!r.place.properties.dataSources?.geometry?.id)return{kind:"skip",reason:"Place has no geometry data source (e.g. addresses and streets often lack one)."};try{const s=await t.places.fetchPlaceGeometry(e);return s?{kind:"success",place:r.place,feature:s,ownerEntryId:r.entryId}:{kind:"skip",reason:"Geometry Data service returned no feature for this place."}}catch(s){return{kind:"skip",reason:`Geometry fetch failed: ${s instanceof Error?s.message:String(s)}`}}},yt=async(e,t)=>{if(0===e.length)return{error:"`geometriesEntryIDs` must contain one or more tagged ids (e.g. `{ kind: 'places', id: <entryId> }`, `{ kind: 'place', id: <placeId> }`, `{ kind: 'ranges', id: <entryId> }`, `{ kind: 'custom', id: <entryId> }`). Use `recallGeometries` to list available ids."};const r=(e=>{const t={place:[],places:[],ranges:[],custom:[]};for(const r of e)t[r.kind].push(r);return t})(e),s=((e,t)=>{const r=[],s=/* @__PURE__ */new Map,o=[],n=/* @__PURE__ */new Set;for(const a of e){const e=t.places.entries.find(e=>e.id===a.id);if(!e){o.push({source:a,reason:"No places entry with this id in session state. Use `recallGeometries` to list available ids."});continue}let i=0;for(const t of e.places)t.properties.dataSources?.geometry?.id&&"string"==typeof t.id&&(i++,s.has(t.id)||s.set(t.id,a.id),n.has(t.id)||(n.add(t.id),r.push(t.id)));0===i&&o.push({source:a,reason:"Places entry has no places with a geometry data source (e.g. addresses and streets often lack one)."})}return{placeIds:r,placeIdToEntry:s,skipped:o}})(r.places,t),o=[...s.skipped],n=/* @__PURE__ */new Set,a=[],i=/* @__PURE__ */new Map;for(const g of r.place)n.has(g.id)||(n.add(g.id),a.push(g.id),i.set(g.id,g));for(const g of s.placeIds)n.has(g)||(n.add(g),a.push(g));const l=[],c=[],d=/* @__PURE__ */new Set,u=/* @__PURE__ */new Set,p=/* @__PURE__ */new Set;for(const g of a){const e=await mt(g,t),r=i.get(g),n=s.placeIdToEntry.get(g);if("skip"===e.kind){const t=r??{kind:"place",id:g};o.push({source:t,reason:e.reason});continue}const a=r??(n?{kind:"places",id:n}:{kind:"place",id:g});l.push(ht(e.feature,a)),c.push(e.place),d.add(e.ownerEntryId),r&&u.add(r.id),n&&p.add(n)}const h=((e,t)=>{const r=[],s=/* @__PURE__ */new Set,o=[];for(const n of e){const e=t.ranges.entries.find(e=>e.id===n.id);if(!e){o.push({source:n,reason:"No ranges entry with this id in session state."});continue}const a=e.ranges.flatMap(e=>e.polygon?.features??[]);if(0!==a.length){for(const e of a)r.push(ht(e,n));s.add(n.id)}else o.push({source:n,reason:"Ranges entry has no polygons — recompute via findReachableAreas first."})}return{features:r,contributingIds:s,skipped:o}})(r.ranges,t);l.push(...h.features),o.push(...h.skipped);const m=((e,t)=>{const r=[],s=/* @__PURE__ */new Set,o=[];for(const n of e){const e=t.customGeometries.findById(n.id);if(e)if(0!==e.features.length){for(const t of e.features)r.push(ht(t,n));s.add(n.id)}else o.push({source:n,reason:"Custom-geometries entry is empty."});else o.push({source:n,reason:"No custom-geometries entry with this id in session state. Use `recallGeometries` to list available ids."})}return{features:r,contributingIds:s,skipped:o}})(r.custom,t);l.push(...m.features),o.push(...m.skipped);const y=m.contributingIds;return 0===l.length?{error:`No input geometries available. ${o.map(pt).join(" | ")}`}:{value:{geometries:l,sourcePlaces:c,affectedEntries:[...[...d].map(e=>({id:e,kind:"places"})),...[...y].map(e=>({id:e,kind:"custom"}))],skipped:o,contributingSourceIds:e.filter(e=>{switch(e.kind){case"place":return u.has(e.id);case"places":return p.has(e.id);case"ranges":return h.contributingIds.has(e.id);case"custom":return y.has(e.id)}})}}},gt=(e,t)=>{if(!e?.length)return{value:void 0};const r={};for(const s of e){const e=t.places.entries.find(e=>e.id===s);if(!e)return{error:`No places entry found with id "${s}". Use recallPlaces to list available IDs.`};r[s]={type:"FeatureCollection",features:e.places}}return{value:r}},ft=(e,t=6)=>{if(e.mapLibreMap.getZoom()<t)return;const r=e.mapLibreMap.getCenter();return[r.lng,r.lat]},bt=e=>{const t=e.mapLibreMap.getBounds();return[t.getWest(),t.getSouth(),t.getEast(),t.getNorth()]},wt=async(e,t,r,s)=>{const o=await Le(e.code,["incidents","h3","turf","now","log","previous"],[t,Y,J,new Date(s),()=>{},r],"Analysis");return"error"in o?{error:o.error}:je(o.value,e.outputFormat)},vt=class{_specs=[];_historyByName=/* @__PURE__ */new Map;get specs(){return this._specs}get results(){const e=[];for(const t of this._specs){const r=this.getResult(t.name);r&&e.push(r)}return e}history(e){return this._historyByName.get(e)??[]}getResult(e){return this._historyByName.get(e)?.at(-1)}register(e){const t=this._specs.findIndex(t=>t.name===e.name);if(t>=0){const r=this._specs[t],s=r.code!==e.code||r.outputFormat!==e.outputFormat||r.description!==e.description;this._specs[t]=e,s&&this._historyByName.delete(e.name)}else this._specs.push(e)}unregister(e){this._specs=this._specs.filter(t=>t.name!==e)}attach(e){let t=this._historyByName.get(e.name);t||(t=[],this._historyByName.set(e.name,t)),t.push(e),t.length>240&&t.shift()}async replay(e,t){const r=[];for(const s of this._specs){const o=await wt(s,e,this.getResult(s.name)?.data,t);if("error"in o)continue;const n={name:s.name,timestamp:t,...s.description&&{description:s.description},outputFormat:s.outputFormat,data:o.value};this.attach(n),r.push(n)}return r}},It=class{_bbox;events=new Q;_status="idle";_area;_error;_intervalHandle;_clearInterval;_fetchIncidents;constructor(e){this._bbox=e}get status(){return this._status}get monitoredArea(){return this._area}get error(){return this._error}get isRunning(){return"running"===this._status}get isActive(){return"idle"!==this._status}start(e,t,r=6e4){if("running"===this._status)return;this._status="running",this._error=void 0,this._area=e,this._fetchIncidents=t.fetchIncidents,this._clearInterval=t.clearInterval??(e=>clearInterval(e));const s=t.setInterval??setInterval;this._tick(),this._intervalHandle=s(()=>{this._tick()},r)}stop(){"idle"!==this._status&&(this._releaseTimer(),this._status="idle",this._error=void 0,this._area=void 0,this._fetchIncidents=void 0)}async _tick(){const e=this._fetchIncidents;if(e)try{const t=await e(this._bbox),r={takenAt:Date.now(),incidents:t};this.events.emit("tick",r)}catch(t){this._failed(t instanceof Error?t.message:String(t))}}_failed(e){this._releaseTimer(),this._error=e,this._status="stopped-error",this._fetchIncidents=void 0,this.events.emit("error",e)}_releaseTimer(){null!=this._intervalHandle&&(this._clearInterval?.(this._intervalHandle),this._intervalHandle=void 0),this._clearInterval=void 0}},St=class{_ttMap;_entries=[];_entryMode="multiple";events=new Q;constructor(e){this._ttMap=e}get entries(){return this._entries}get entryMode(){return this._entryMode}async setEntryMode(e){if(this._entryMode!==e){if(this._entryMode=e,"single"===e&&this._entries.length>1){const e=this._entries.at(-1),t=this._entries.slice(0,-1);await Promise.all(t.map(e=>this._teardownEntry(e))),this._entries=e?[e]:[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}this.events.emit("mode-change",e)}}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}addIncidentsEntry(e,t,r,s,o){if("single"===this._entryMode&&this._entries.length>0){for(const e of this._entries)this._teardownEntry(e);this._entries=[]}const n=this._uniqueEntryId(o??`incidents-${this._entries.length}`);return this._entries.push({id:n,timestamp:s,label:r,data:e,params:t}),this.events.emit("entries-change",this._entries),n}async replaceEntryData(e,t,r){const s=this._entries.find(t=>t.id===e);if(!s)return!1;const o=(s._tick??0)+1;if(s._tick=o,s.data=t,s.timestamp=r,this.events.emit("entries-change",this._entries),s._module){if(await s._module.show({type:"FeatureCollection",features:t}),s._tick!==o)return!0;if(s._focus){const e=new Set(t.map(e=>e.properties.id)),r=[...s._focus.ids].filter(t=>e.has(t));s._module.setFocus(r.length>0?r:null),0===r.length&&(s._focus=void 0,this.events.emit("focus-change",{entryId:s.id,focus:null}))}}return s._tick!==o||await this._rerunRegisteredSpecs(e,r),!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await p.get(this._ttMap),t._module}async showEntry(e,t=!0){const r=this._requireEntry(e),s=await this.getEntryModule(e);await s.show({type:"FeatureCollection",features:r.data}),s.setVisible(!0),t&&this._ttMap.mapLibreMap.fitBounds(r.params.bbox,{padding:50,maxZoom:14});const o=!!r._shown;r._shown=!0,o||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&t._shown&&(t._focus&&(t._focus=void 0,this.events.emit("focus-change",{entryId:t.id,focus:null})),t._module?.setVisible(!1),await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],s=!!r._shown;await this._teardownEntry(r),this._entries.splice(t,1),this.events.emit("entries-change",this._entries),s&&this.events.emit("shown-change",this.shownEntryIds)}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);return!!r&&((r._analyses??=new vt).attach(t),this.events.emit("analysis-change",{entryId:r.id,analysis:t}),this.events.emit("entries-change",this._entries),!0)}setAnalysisSpec(e){const t=this._entries.find(t=>t.id===e.source);t&&(t._analyses??=new vt).register(e)}removeAnalysisSpec(e){for(const t of this._entries)t._analyses?.unregister(e)}getFocus(e){const t=this._entries.find(t=>t.id===e);return t?._focus?{ids:new Set(t._focus.ids),reason:t._focus.reason}:null}setFocus(e,t,r){const s=this._entries.find(t=>t.id===e);if(!s)return{focusedCount:0,droppedIds:[...t]};if(0===t.length)return this.clearFocus(e),{focusedCount:0,droppedIds:[]};const o=new Set(s.data.map(e=>e.properties.id)),n=[],a=[];for(const i of t)o.has(i)?n.push(i):a.push(i);return 0===n.length?{focusedCount:0,droppedIds:a}:(s._focus={ids:new Set(n),reason:r},s._module?.setFocus(n),this.events.emit("focus-change",{entryId:s.id,focus:this.getFocus(e)}),{focusedCount:n.length,droppedIds:a})}clearFocus(e){const t=this._entries.find(t=>t.id===e);t?._focus&&(t._focus=void 0,t._module?.setFocus(null),this.events.emit("focus-change",{entryId:t.id,focus:null}))}focusedFeatures(e){const t=this._entries.find(t=>t.id===e);if(!t?._focus)return[];const r={};for(const o of t.data){const e=o.properties.id;"string"==typeof e&&(r[e]=o)}const s=[];for(const o of t._focus.ids){const e=r[o];e&&s.push(e)}return s}navigateFocus(e,t){const r=this.focusedFeatures(e);if(0===r.length)return;const s=r[(t%r.length+r.length)%r.length],o=this._ttMap.mapLibreMap;if("Point"===s.geometry.type)return void o.flyTo({center:s.geometry.coordinates,zoom:15,duration:600});const n=O(s);n&&o.fitBounds(n,{padding:120,maxZoom:16,duration:600})}findEntryWithIncident(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(r.data.some(t=>t.properties.id===e))return r}}startMonitoring(e,t,r,s){const o=this._requireEntry(e),n=o._monitor??=this._createMonitorForEntry(o),a=n.isRunning;n.start(t,r,s?.intervalMs),a||this.events.emit("monitor-start",{entryId:e})}stopMonitoring(e){const t=this._entries.find(t=>t.id===e)?._monitor;t?.isActive&&(t.stop(),this.events.emit("monitor-stop",{entryId:e,reason:"manual"}))}isMonitored(e){return this._entries.find(t=>t.id===e)?._monitor?.isRunning??!1}_createMonitorForEntry(e){const t=new It(e.params.bbox);return t.events.on("tick",t=>{this.replaceEntryData(e.id,t.incidents,t.takenAt),this.events.emit("monitor-tick",{entryId:e.id,snap:t})}),t.events.on("error",t=>{this.events.emit("monitor-error",{entryId:e.id,error:t}),this.events.emit("monitor-stop",{entryId:e.id,reason:"error"})}),t}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficIncidentsState: unknown entry "${e}"`);return t}_uniqueEntryId(e){const t=new Set(this._entries.map(e=>e.id));if(!t.has(e))return e;for(let r=2;;r++){const s=`${e}-${r}`;if(!t.has(s))return s}}async _rerunRegisteredSpecs(e,t){const r=this._entries.find(t=>t.id===e);if(!r?._analyses?.specs.length)return;const s=await r._analyses.replay(r.data,t);for(const o of s)this.events.emit("analysis-change",{entryId:r.id,analysis:o});s.length>0&&this.events.emit("entries-change",this._entries)}async _teardownEntry(e){this.stopMonitoring(e.id),e._focus&&(e._focus=void 0,this.events.emit("focus-change",{entryId:e.id,focus:null})),e._shown&&(e._shown=!1),e._module&&(e._module.setVisible(!1),await e._module.clear()),e._module=void 0}reset(){for(const t of this._entries)this._teardownEntry(t);const e=this._entries.length>0;this._entries=[],e&&(this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)),this.events.clear()}},xt=class{_ttMap;_trafficFlowModule;_trafficIncidentsModule;constructor(e){this._ttMap=e}async getTrafficFlowModule(){return this._trafficFlowModule??=await u.get(this._ttMap),this._trafficFlowModule}async getTrafficIncidentsModule(){return this._trafficIncidentsModule??=await h.get(this._ttMap),this._trafficIncidentsModule}get trafficFlowModule(){return this._trafficFlowModule}get trafficIncidentsModule(){return this._trafficIncidentsModule}reset(){this._trafficFlowModule=void 0,this._trafficIncidentsModule=void 0}};D.instance.put({language:"en-GB"});var Pt='You are a helpful map assistant with access to a TomTom interactive map and location services.\nIf a request is completely unrelated to geospatial topics, gracefully decline and steer the user back to map-related tasks you can help with.\n\nCOORDINATE ORDER:\n- All coordinates are [longitude, latitude] (GeoJSON standard)\n- All bounding boxes are [minLng, minLat, maxLng, maxLat]\n\nTOOL EXECUTION:\n- Call context-independent tools in the same step (parallel execution).\n- Many service tools have a show or showOnMap parameter — use it to display results on the map in one step instead of calling a separate show tool afterward.\n- Use standalone updatePlacesDisplay / updateRoutesDisplay / showWaypoints only when displaying results already in context from a previous step.\n\nSESSION STATE & ENTRIES:\n- Three append-only histories live in the session: `places`, `routes`, `ranges`. Each tool call that produces results stores a new ENTRY (id like `places-3`, `routes-1`, `ranges-0`) — these ids stay stable and are accepted by every show / recall / processX tool.\n- Each slice has an `entryMode`: `multiple` (default — overlay several entries on the map) or `single` (only the latest entry stays; switching to it auto-drops every older entry from history). Inspect via `recallState` / `recallPlaces` / `recallRoutes` / `recallRanges`. Flip with `setEntryMode({ slice, mode })` when the user asks to "only show one at a time" or "let me overlay multiple".\n- Mode is sticky across turns — under `single`, calling another `discoverPlaces` automatically replaces the previous places entry.\n- Always check `recallX` (or `recallState`) before guessing entry ids; never invent them.\n\nLOCATION REFERENCE:\n- "near me" / "my location" / "where I am" → getCurrentLocation (user\'s physical GPS position, may prompt permission)\n- "in this area" / "here on the map" / "near the map center" → getViewport (map\'s current view)\n- If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point\n\nRESPONSE FORMATTING:\n- Use markdown in all responses\n- Show key information in bold\n- Use bullet lists when presenting multiple items or results\n- Keep responses concise — lead with the most relevant information\n\n',Mt=(e,t)=>e||(t?`${Pt}\n\nADDITIONAL INSTRUCTIONS:\n${t}`:Pt),Et=K.object({geoJson:K.union([K.record(K.string(),K.unknown()),K.array(K.record(K.string(),K.unknown()))]).describe("GeoJSON object or array of GeoJSON objects")}),_t=K.object({meters:K.number().describe("Distance in meters to format"),unitType:K.enum(["metric","imperial_us","imperial_uk"]).optional().describe("default: metric")}),Tt=K.object({seconds:K.number().describe("Duration in seconds to format")}),At=K.object({error:K.string()}),Ct=K.object({id:K.string().describe("Stable place feature ID; pass as `{ placeId }` to setRoute / addWaypointsToRoute / etc."),name:K.string().optional().describe("POI name if available"),address:K.string().optional().describe("Human-readable address"),position:K.array(K.number()).length(2).describe("[longitude, latitude]")}).describe("Compact place summary"),Rt=K.object({count:K.number(),features:K.array(Ct)}).describe("Compact places summary"),Dt=K.object({travelTimeInSeconds:K.number(),lengthInMeters:K.number(),trafficDelayInSeconds:K.number().optional(),trafficLengthInMeters:K.number().optional(),departureTime:K.string().optional(),arrivalTime:K.string().optional(),noTrafficTravelTimeInSeconds:K.number().optional(),historicTrafficTravelTimeInSeconds:K.number().optional(),liveTrafficIncidentsTravelTimeInSeconds:K.number().optional()}),kt=K.object({index:K.number().describe("0=best, 1+=alternative"),summary:Dt,legSummaries:K.array(Dt).optional().describe("Present when route has multiple legs")}),Ot=K.object({count:K.number(),routes:K.array(kt)}),jt=K.object({}),Ft=K.union([K.object({position:K.array(K.number()).min(2).max(2).describe("[longitude, latitude]"),accuracy:K.number().describe("Accuracy of the location in meters"),timestamp:K.number().describe("Timestamp of the location data")}),At]),Lt=K.number().int().min(0).optional().describe("default: 0"),Nt=(K.object({routeIndex:Lt,traveledTimeInSeconds:K.number().min(0).optional().describe("seconds from start"),traveledDistanceInMeters:K.number().min(0).optional().describe("meters from start"),clockTime:K.string().optional().describe("ISO 8601; elapsed time derived from departure time")}).refine(({traveledTimeInSeconds:e,traveledDistanceInMeters:t,clockTime:r})=>1===[e,t,r].filter(e=>void 0!==e).length,{message:"Provide exactly one of: traveledTimeInSeconds, traveledDistanceInMeters, or clockTime."}),K.union([K.object({bbox:K.array(K.number()).describe("GeoJSON bbox [minLng, minLat, maxLng, maxLat]")}),At]),K.object({sectionType:K.enum(z).describe("e.g. country|traffic|motorway|toll"),id:K.string().describe("Section ID from getShownRouteSections or getSectionProgress")}),K.object({sectionType:K.enum(z).describe("e.g. country|traffic|motorway|toll"),ids:K.array(K.string()).optional().describe("Section IDs to query. If omitted, all sections of the given sectionType are returned.")}),["location","route","waypoint","place","locate","discover","traffic","map style","utilities","search","detour","along-route","reachable-range","isochrone","range","EV","coverage","recall","state","carpool","carTrain","country","ferry","importantRoadStretch","lanes","leg","lowEmissionZone","motorway","pedestrian","roadShields","speedLimit","toll","tollVignette","tunnel","unpaved","urban","vehicleRestricted"]),$t=K.object({name:K.string(),description:K.string(),examplePrompts:K.array(K.string()),relatedTools:K.array(K.string()).optional()}),qt=K.union([K.object({mode:K.literal("summary"),examplePrompts:K.array(K.string())}),K.object({mode:K.literal("detail"),query:K.string().optional(),tag:K.enum(Nt).optional(),matchedTools:K.array($t),totalMatched:K.number()}),At]),Bt=K.object({mode:K.enum(["summary","detail"]).describe('"summary": overview of all capabilities with example prompts mixed from all tools. "detail": searchable full tool info — use when you need to find a specific tool.'),query:K.string().optional().describe("Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags."),tag:K.enum(Nt).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),Gt='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.',Ut=(e,t,r)=>({description:e.description,inputSchema:e.inputSchema,outputSchema:r?e.outputSchema:void 0,execute:r=>e.execute(r,t)}),Wt=(e,t)=>({name:e,description:t.description,classificationPrompt:t.classificationPrompt,tags:t.tags,examples:t.examples,examplePrompts:t.examplePrompts,relatedTools:t.relatedTools,dependsOn:t.dependsOn}),zt=(e,t)=>"function"==typeof e?e(t):e,Vt=(e,t,r)=>{const s=!1!==r?.outputSchemas,o={featureFlags:r?.featureFlags},n={},a={};for(const[l,c]of Object.entries(e)){const e=zt(c,o);n[l]=Ut(e,t,s),a[l]=Wt(l,e)}var i;if(n.help&&(n.help=(i=()=>a,{description:Gt,inputSchema:Bt,outputSchema:qt,execute:async e=>{const{mode:t,query:r,tag:s}=e;try{const e=Object.values(i());if("summary"===t)return{mode:"summary",examplePrompts:e.flatMap(e=>e.examplePrompts??[])};let o=e;if(s&&(o=o.filter(e=>e.tags?.includes(s)??!1)),r){const e=r.toLowerCase();o=o.filter(t=>t.name.toLowerCase().includes(e)||t.description.toLowerCase().includes(e)||(t.tags??[]).some(t=>t.toLowerCase().includes(e))||(t.examplePrompts??[]).some(t=>t.toLowerCase().includes(e)))}return{mode:"detail",query:r,tag:s,matchedTools:o.map(e=>({name:e.name,description:e.description,examplePrompts:e.examplePrompts??[],relatedTools:e.relatedTools??[]})),totalMatched:o.length}}catch(o){return{error:`Failed to load help: ${o instanceof Error?o.message:String(o)}`}}}})),0===Object.keys(n).length)throw new Error("MapAgent requires at least one tool.");return{tools:n,toolsMetadata:a}},Ht=K.object({added:K.array(K.string()).describe("Source IDs added by the executed code."),removed:K.array(K.string()).describe("Source IDs removed by the executed code."),updated:K.array(K.string()).describe("Source IDs whose definition changed.")}),Jt=K.object({added:K.array(K.string()).describe("Layer IDs added by the executed code."),removed:K.array(K.string()).describe("Layer IDs removed by the executed code."),updated:K.array(K.string()).describe("Layer IDs whose properties changed.")}),Yt=K.object({code:K.string().describe('Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. Example: await a map event with `await new Promise(r => map.once("idle", r))`. Return a value with `return` to include it in the result.')}),Kt=K.union([K.object({success:K.literal(!0),result:K.unknown().optional().describe("Return value of the executed code, if any."),sources:Ht,layers:Jt}),At]),Xt=e=>{const t=e.getStyle()?.sources??{},r=/* @__PURE__ */new Map;for(const[s,o]of Object.entries(t))r.set(s,JSON.stringify(o));return r},Zt=e=>{const t=e.getStyle()?.layers??[],r=/* @__PURE__ */new Map;for(const s of t)r.set(s.id,JSON.stringify(s));return r},Qt=(e,t)=>{const r=[],s=[],o=[];for(const[n,a]of t){const t=e.get(n);void 0===t?r.push(n):t!==a&&o.push(n)}for(const n of e.keys())t.has(n)||s.push(n);return{added:r,removed:s,updated:o}},er=K.union([K.object({success:K.literal(!0)}),At]),tr=K.object({where:K.union([K.object({boundingBox:Be.describe("BBox [W,S,E,N]."),padding:K.number().optional().describe("Padding in pixels. Default: 50.")}).describe("Fit the camera to a bounding box."),K.object({position:K.array(K.number()).length(2).describe("[lng, lat] to fly to."),zoom:K.number().optional().describe("Camera zoom level. Default: 14.")}).describe("Fly the camera to a specific position.")])}),rr=K.union([K.object({layers:K.array(K.object({id:K.string(),layout:K.unknown().optional(),paint:K.unknown().optional()}))}),At]),sr=K.object({layerIdQuery:K.string().optional().describe("Case-insensitive partial match on layer IDs. If omitted, all layers are returned."),include:K.array(K.enum(["paint","layout"])).describe('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.')}),or=K.union([K.object({center:K.array(K.number()).describe("[lng, lat]"),zoom:K.number().describe("0–22"),bbox:K.array(K.number()).describe("[W, S, E, N]"),pitch:K.number().describe("degrees, 0=top-down, 85=near-horizontal"),bearing:K.number().describe("degrees, 0=north, 90=east")}),At]),nr=K.object({}),ar=K.union([K.object({layerId:K.string(),propertyName:K.string(),value:K.unknown(),success:K.literal(!0)}),K.object({layerId:K.string(),propertyName:K.string(),error:K.string()})]),ir=K.union([K.object({results:K.array(ar)}),At]),lr=K.object({changes:K.array(K.object({layerId:K.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:K.string().describe("e.g. visibility|text-field|icon-size"),value:K.any()}))}).describe("List of layout property changes to apply in one go."),cr=K.union([K.object({layerId:K.string(),propertyName:K.string(),value:K.unknown(),success:K.literal(!0)}),K.object({layerId:K.string(),propertyName:K.string(),error:K.string()})]),dr=K.union([K.object({results:K.array(cr)}),At]),ur=K.object({changes:K.array(K.object({layerId:K.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:K.string().describe("e.g. fill-color|line-width|text-color"),value:K.any()})).describe("List of paint property changes to apply in one go.")}),pr=K.union([K.object({success:K.literal(!0)}),At]),hr=K.object({pitch:K.number().min(0).max(85).optional().describe("0=top-down, 85=near-horizontal"),bearing:K.number().min(-180).max(180).optional().describe("0=north, 90=east")}),mr=K.union([K.object({zoom:K.number().describe("Resulting zoom level after the change")}),At]),yr=K.object({delta:K.number().describe("Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.")}),gr=e=>["You are a tool selector for a map assistant.","You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.",'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',"","Available tools:",...Object.entries(e).map(([e,t])=>((e,t)=>`${e}${t.classificationPrompt?` ${t.classificationPrompt}.`:""}${t.dependsOn?.length?` Depends on: ${t.dependsOn.join(", ")}.`:""}`)(e,t))].join("\n"),fr=K.object({tools:K.array(K.string()).min(1).describe("Tool names needed to fulfill the query.")}),br=async(t,s)=>{const{chatModel:o,toolsMetadata:n}=s,a=performance.now(),{tools:i,usage:l}=await(async(t,s,o)=>{const n=await r({model:s,output:e.object({schema:fr}),system:gr(o),messages:t,maxOutputTokens:200});return{tools:n.output.tools,usage:{inputTokens:n.usage?.inputTokens??0,outputTokens:n.usage?.outputTokens??0,totalTokens:n.usage?.totalTokens??0}}})(t,o,n);return{activeToolNames:i,timeMs:performance.now()-a,usage:l}},wr=e=>"text"===e.type,vr=e=>{if("string"==typeof e)return e;const t=e.filter(wr);return t.length>0?t.map(e=>e.text).join(""):null},Ir=e=>{for(let t=e.length-1;t>=0;t--)if("user"===e[t].role)return vr(e[t].content);return null},Sr=e=>{const{model:t,maxHistoryMessages:r,maxHistoryMessageLength:s}=e;return async e=>{const{messages:o,toolsMetadata:n}=e;if(!o?.length)return null;const a=r??8,i=s??300;let l=-1,c=null;for(let t=o.length-1;t>=0;t--)if("user"===o[t].role){l=t,c=vr(o[t].content);break}if(!c)return null;const d=[];for(let t=Math.max(0,l-a);t<l;t++){const e=o[t];if("user"!==e.role&&"assistant"!==e.role)continue;const r=vr(e.content);if(!r)continue;const s=r.length>i?r.slice(0,i)+"…":r;d.push({role:e.role,content:s})}d.push({role:"user",content:c});try{return await br(d,{chatModel:t,toolsMetadata:n})}catch{return null}}},xr=e=>{if(!Array.isArray(e))return e.properties?.address?.freeformAddress},Pr=(e,t)=>{if("features"in e)return((e,t)=>{const r=[];t?.query&&r.push(`"${t.query}"`),t?.poiCategories?.length&&r.push(t.poiCategories.join(", ")),t?.routeLabel?r.push(`along ${t.routeLabel}`):t?.where&&r.push(`in ${t.where}`);const s=`${e} ${1===e?"place":"places"}`;return r.length?`${r.join(" ")} (${s})`:s})(e.features.length,t);const r=(s=e,s.properties?.poi?.name);var s;const o=(e=>e.properties?.address?.freeformAddress)(e);return r&&o?`${r}, ${o}`:r||(o||(t?.query?t.query:"Place"))},Mr={timeMinutes:"min",distanceKM:"km",remainingChargeCPT:"% remaining",spentChargePCT:"% spent",spentFuelLiters:"L"},Er=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${Mr[e.type]??e.type}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`,_r=e=>({id:e.id,name:e.properties?.poi?.name,address:e.properties?.address?.freeformAddress,position:e.geometry?.coordinates??[0,0]}),Tr=e=>{const t=Array.isArray(e)?e:e.features;return{count:t.length,features:t.slice(0,5).map(e=>_r(e))}},Ar=e=>{const{summary:t,sections:r,index:s}=e.properties,o=r.leg,n=e=>({...e,arrivalTime:e.arrivalTime.toISOString(),departureTime:e.departureTime.toISOString()});return{index:s,summary:n(t),...o.length>1&&{legSummaries:o.map(e=>n(e.summary))}}},Cr=e=>({count:e.features.length,routes:e.features.map(Ar)}),Rr=K.object({slotIndex:K.number(),isFilled:K.boolean(),address:K.string().optional(),position:K.array(K.number()).length(2).optional().describe("[longitude, latitude]")}),Dr=e=>{if(null==e)return null;const t=$(e);let r;return Array.isArray(e)||(r=e.properties?.address?.freeformAddress),{slotIndex:-1,isFilled:!0,...r&&{address:r},...t&&{position:t}}},kr=async(e,t,r)=>{const[s,o]="poi"===t?[T,A]:[S,x],{limit:n,bias:a}=r;if(a&&"position"in a)return(await s({query:e,position:a.position,limit:n})).features;if(a&&"boundingBox"in a)return(await s({query:e,boundingBox:a.boundingBox,limit:n})).features;if(1===n){const t=await o(e);return t?[t]:[]}return(await s({query:e,limit:n})).features},Or=async(e,t,r)=>{const[s]=await kr(e,t,{limit:1,bias:r});return s??null},jr=K.object({query:K.string().describe("Location string to resolve for downstream routes or searches."),waypointIndex:K.number().int().min(0).optional().describe("Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination."),queryAs:K.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, geographies.'),where:lt.describe("Geographic scope."),show:Qe.optional().describe("Map display actions after resolving. Skip for intermediate calculations."),entryId:He,geometry:Ye}),Fr=K.union([Ct.extend({waypointIndex:K.number().optional(),shown:et.optional(),geometryFetched:K.boolean().optional().describe("Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set)."),geometryShown:K.boolean().optional().describe("Whether the fetched polygon was rendered on the map.")}),At]),Lr=async(e,t)=>"global"===e.mode?{bias:void 0}:"within"===e.mode?(async(e,t)=>{if(e.viewport)return{bias:{boundingBox:bt(t.baseMap)}};if(e.boundingBox)return{bias:{boundingBox:e.boundingBox}};if(e.query){const r=ft(t.baseMap),s=await Or(e.query,e.queryAs??"place",r?{position:r}:void 0);if(!s)return{bias:void 0};if(s.bbox)return{bias:{boundingBox:s.bbox}};const o=$(s);return{bias:o?{position:o}:void 0}}if(e.placeId){const r=t.places.findPlaceById(e.placeId);if(!r)return{error:`Unknown placeId "${e.placeId}". Use recallPlaces to list available IDs.`};if(!r.place.properties.dataSources?.geometry?.id)return{error:`Place "${e.placeId}" has no geometry data source (most addresses and streets lack one).`};const s=await t.places.fetchPlaceGeometry(e.placeId);return s?{bias:{boundingBox:s.bbox??J.bbox(s)}}:{error:`Geometry Data service returned no feature for place "${e.placeId}".`}}return{bias:void 0}})(e,t):(async(e,t)=>{if(e.viewport){const e=ft(t.baseMap);return{bias:e?{position:e}:void 0}}if(e.position)return{bias:{position:e.position}};if(e.query){const r=ft(t.baseMap),s=await Or(e.query,e.queryAs??"place",r?{position:r}:void 0);if(!s)return{bias:void 0};const o=$(s);return{bias:o?{position:o}:void 0}}return{bias:void 0}})(e,t),Nr=async(e,t)=>{if("position"in e){const{lng:t,lat:r}=e.position;return{place:[t,r],position:[t,r],name:`[${t}, ${r}]`}}if("placeId"in e){const r=t.places.findPlaceById(e.placeId);if(!r)return null;const s=$(r.place);return s?{place:r.place,position:s,name:r.place.properties?.poi?.name??r.place.properties?.address?.freeformAddress??e.placeId}:null}const r=await Or(e.query,e.queryAs);if(!r)return null;const s=$(r);return s?{place:r,position:s,name:r.properties?.address?.freeformAddress??e.query,query:e.query}:null},$r=K.object({routeType:K.enum(_).optional().describe("fast|short|efficient|thrilling"),traffic:K.enum(["live","historical"]).optional().describe("live|historical"),avoid:K.array(K.enum(k)).optional().describe("tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones"),avoidAreas:K.array(K.array(K.number()).describe("GeoJSON bbox [minLng, minLat, maxLng, maxLat]")).max(10).optional().describe("Up to 10 bounding boxes to bypass.")}),qr=K.object({option:K.enum(["departAt","arriveBy"]),date:K.string().describe("ISO 8601")}),Br=K.object({maxAlternatives:K.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:$r.optional(),when:qr.optional().describe("Omit to depart now.")}),Gr=K.union([Ot,K.object({success:K.literal(!0)}),At]),Ur=K.object({locations:K.array(Me).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:Br.optional().describe("Routing options. Omitted fields keep their current values."),showOnMap:K.boolean().describe("Show the route and waypoints on the map after calculation."),hidePreviousEntries:Ze("routes")}).refine(e=>void 0!==e.locations||void 0!==e.parameters,{message:"Provide at least one of `locations` or `parameters`."}),Wr=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},zr=e=>{const{maxAlternatives:t=0,costModel:r,when:s}=e;return{...t>0&&{maxAlternatives:t},...r&&{costModel:{...r,...r.avoidAreas&&{avoidAreas:r.avoidAreas}}},...s&&{when:{option:s.option,date:new Date(s.date)}}}},Vr=async(e,t,r,s)=>{const o=await v({locations:t,...zr(e.routing.params)}),n=e.routing.addRoutes(o,t,((e,t)=>{const r=e.features[0]?.properties?.summary,s=[r?L(r.travelTimeInSeconds):void 0,r?F(r.lengthInMeters):void 0].filter(Boolean),o=s.length?` (${s.join(", ")})`:"",n=t.map(xr).filter(Boolean);if(n.length>=2){const e=n[0],t=n.at(-1),r=n.slice(1,-1);return`${r.length>0?`${e} via ${r.join(", ")} to ${t}`:`${e} to ${t}`}${o}`}return`Route${o}`})(o,t));return r&&await(async(e,t,r,s,o)=>{await Pe(e.routing,[t],o),await e.routing.showEntry(t);const n=O(r);n&&e.baseMap.mapLibreMap.fitBounds(n,{padding:50})})(e,n,o,0,s),Cr(o)},Hr=K.union([Ot,At]),Jr=K.object({origin:Me.optional().describe("Prepend a new origin in front of the current origin. The previous origin is kept as the first intermediate stop (it is not replaced)."),destination:Me.optional().describe("Append a new destination after the current destination. The previous destination is kept as an intermediate stop (it is not replaced)."),stops:K.array(Me).min(1).optional().describe("Intermediate stops to insert at optimal mid-positions along the existing route. Multiple stops settle into natural along-route order regardless of input order."),showOnMap:K.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Ze("routes")}).refine(e=>void 0!==e.origin||void 0!==e.destination||e.stops&&e.stops.length>0,{message:"Provide at least one of `origin`, `destination`, or `stops`."}),Yr=e=>"query"in e?`"${e.query}"`:"placeId"in e?`placeId "${e.placeId}"`:JSON.stringify(e.position),Kr=async(e,t,r)=>{const s=await kr(e,t,{limit:5,bias:r?{position:r}:void 0});if(0===s.length)return null;for(const o of s){if(!o.properties.dataSources?.geometry?.id)continue;const e=(await P({geometries:[o]})).features[0];if(e)return{place:o,geometry:e}}return{place:s[0]}},Xr=K.union([Rt.extend({shown:et.optional(),placesEntryId:K.string().optional(),label:K.string().optional().describe('Human-readable label stored with the entry (e.g. "cafe", "CINEMA (50 places)").'),geometriesFetched:K.number().optional().describe("Count of boundary polygons fetched and cached on the entry (when `geometries` was set)."),geometriesShown:K.boolean().optional().describe("Whether the fetched polygons were rendered on the map.")}),At]),Zr={...tt,queries:K.array(K.object({query:K.string().describe('Name of a CONTAINING area to search within (e.g. "Paris", "De Jordaan, Amsterdam"). NEVER the user\'s search subject — that goes in the top-level `query`.'),queryAs:rt})).optional().describe('CONTAINING areas to search WITHIN — answers "search where?", NOT "search for what?". Never put the user\'s search subject here. Example: for "cafes in Amsterdam" → top-level `query`/`poiCategories` carry "cafes"; `where.queries: [{query: "Amsterdam"}]` carries the region. Each entry resolves to a boundary polygon (or bbox); the union is searched. Per-item `queryAs` disambiguates POI vs place. EXCLUSIVE with viewport; composes with other multi-region fields.'),placeIds:K.array(K.string()).optional().describe("IDs of session places — each polygon (fetched on demand) joins the search area. Use recallPlaces to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields."),geometries:K.array(We).optional().describe("Direct GeoJSON Polygons / MultiPolygons (custom drawing, external data). For named or stored places use `queries` / `placeIds`. EXCLUSIVE with viewport; composes with other multi-region fields."),range:K.string().optional().describe('Range ID from findReachableAreas (e.g. "ranges-0") — restricts to that entry; multi-origin entries combine their polygons. Use recallRanges to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),route:K.object({routeId:K.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallRoutes. Default: latest route.'),widthMeters:K.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. BULK discovery (≤10000); for a SHORT ranked detour list use mode `maxDetour`. EXCLUSIVE with viewport; composes with other multi-region fields.")},Qr={boundingBoxes:K.array(Be).optional().describe("Bboxes [W,S,E,N] — results in the union. Last-resort: prefer `municipalities` / `queries` / `placeIds` / `geometries` when polygons are resolvable. EXCLUSIVE with viewport; composes with other multi-region fields."),municipalities:K.array(K.string()).optional().describe('Exact, case-sensitive municipality names (e.g. ["Amsterdam", "Utrecht"]). EXCLUSIVE with viewport; composes with other multi-region fields.')},es=e=>K.object(e.experimentalSearch?{...Zr,...Qr}:Zr).refine(e=>{const t=!!e.viewport,r=!!(e.queries?.length||e.placeIds?.length||e.geometries?.length||e.range||e.route||e.municipalities?.length||e.boundingBoxes?.length);return(!t||!r)&&(t||r)},{message:"`within` requires EITHER `viewport: true` OR one or more multi-region fields (queries / placeIds / geometries / range / route"+(e.experimentalSearch?" / boundingBoxes / municipalities":"")+"), not both."}),ts=K.object({mode:K.literal("maxDetour"),routeId:K.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallRoutes. Default: latest route.'),maxDetourTimeSeconds:K.number().positive().describe("Max extra travel time (seconds) the user accepts to reach a POI."),sortBy:K.enum(["detourTime","detourOffset"]).optional().describe('"detourTime" (default) ranks by extra travel time; "detourOffset" by distance from the route line.'),limit:K.number().max(100).optional().describe("Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.")}),rs=e=>{const t=e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/geometries/range/route":"queries/placeIds/geometries/range/route";return K.union([es(e),at,ts,it]).describe(`Geographic scope, by \`mode\`: \`within\` (area — \`viewport\` alone, OR any combo of ${t}) | \`nearby\` (point bias — position/viewport/query, optional radiusMeters) | \`maxDetour\` (ranked detour list off a route) | \`global\` (no bias). Default: \`{ mode: "within", viewport: true }\`.`)},ss=e=>{const t={query:K.string().optional().describe("Free-text filter (name/address). Omit when searching by category — use `poiCategories` instead."),where:rs(e).optional(),poiCategories:K.array(K.string()).optional().describe('CONSTANT_CASE category codes. ALWAYS batch all requested categories in ONE array — never one call per category. Common: "RESTAURANT", "CAFE", "BAR", "HOTEL", "PARK", "PARK_RECREATION_AREA", "ELECTRIC_VEHICLE_STATION", "GAS_STATION", "PARKING_GARAGE", "SUPERMARKETS_HYPERMARKETS", "PHARMACY", "ATM". For anything else, call getPoiCategoryCodes first to resolve natural-language terms (e.g. "italian food", "gym").'),show:Qe.optional().describe("Display the places and/or geometries on the map."),entryId:He,geometries:Ye},r=e.experimentalSearch?{...t,placeTypes:K.array(K.enum(["POI","PointAddress","Street"])).optional().describe("Restrict to record types (default: all). POI = businesses/landmarks/amenities; PointAddress = numbered addresses; Street = unnumbered streets.")}:t;return K.object(r).refine(e=>!!e.query||!!e.poiCategories?.length,{message:"discoverPlaces needs a search subject: set `query` (free-text) or `poiCategories`. For a single named place (no filter), use locatePlace — including for its boundary polygon (`geometry`)."})},os=e=>`Search for MULTIPLE places in a region — REQUIRES a search subject (\`query\` and/or \`poiCategories\`). NOT for a single named place — use locatePlace for that (including its polygon/boundary via \`geometry\`). Stores an entry and returns \`placesEntryId\`. CALL ONCE PER REQUEST — batch all \`poiCategories\` + areas in one call; split only for explicitly comparable result sets. \`where.mode\`: \`within\` (\`viewport\` OR any combo of ${e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/geometries/range/route":"queries/placeIds/geometries/range/route"}), \`nearby\` (position/viewport/query + optional radiusMeters), \`maxDetour\` (≤100 ranked detours off a route), \`global\`. `+(e.experimentalSearch?"Prefer `municipalities` / `queries` / `placeIds` (precise) over `boundingBoxes` (coarse). ":"Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ")+'Route: `within.route` = bulk corridor (≤10000); `maxDetour` = short ranked list (≤100). Default: `{ mode: "within", viewport: true }`. Never call this with empty `query` AND empty `poiCategories` — that is locatePlace territory.',ns={},as=(ss(ns),os(ns),e=>{const{geometries:t}=e;return t?.length?{geometries:t}:{}}),is=(e,t)=>{const{query:r,poiCategories:s,bias:o={},radiusMeters:n,multiFilters:a}=t,i={query:r,poiCategories:s,...o,...void 0!==n&&{radiusMeters:n}};return e?I({...i,limit:1e4,...a}):T({...i,limit:100,...as(a)})},ls=async(e,t,r)=>{const s=r?.length?{placeTypes:r}:{};if(e.viewport)return{bias:{boundingBox:bt(t.baseMap)},multiFilters:s};const o=[],n=[];if(e.queries?.length){const r=await(async(e,t)=>{const r=ft(t.baseMap),s=[],o=[];for(const n of e){const e=await Kr(n.query,n.queryAs??"place",r);if(!e)return{error:`Could not resolve "${n.query}". Try a more specific query, a placeId, or a boundingBox.`};if(e.geometry?.geometry)s.push(e.geometry.geometry);else{if(!e.place.bbox)return{error:`"${n.query}" resolved to a place with no geometry or bbox — cannot use as a "within" area. Use mode "nearby" with this query for a point bias instead.`};o.push(e.place.bbox)}}return{geometries:s,boundingBoxes:o}})(e.queries,t);if("error"in r)return r;o.push(...r.geometries),n.push(...r.boundingBoxes)}if(e.placeIds?.length){const r=await(async(e,t)=>{const r=[];for(const s of e){const e=t.places.findPlaceById(s);if(!e)return{error:`Unknown placeId "${s}". Use recallPlaces to list available IDs.`};if(!e.place.properties.dataSources?.geometry?.id)return{error:`Place "${s}" has no geometry data source (most addresses and streets lack one).`};const o=await t.places.fetchPlaceGeometry(s);if(!o)return{error:`Geometry Data service returned no feature for place "${s}".`};r.push(o.geometry)}return{geometries:r}})(e.placeIds,t);if("error"in r)return r;o.push(...r.geometries)}const a=e,i=[...a.boundingBoxes??[],...n],l=[...e.geometries??[],...o],c={...s,...a.municipalities?.length&&{municipalities:a.municipalities},...i.length&&{boundingBoxes:i},...l.length&&{geometries:l}};let d;if(e.route){const r=((e,t)=>{const r=e.routing.entries;if(0===r.length)return{error:"No routes in state. Calculate a route first via setRoute."};const s=t.routeId?r.find(e=>e.id===t.routeId):r.at(-1);if(!s)return{error:`No route found with id "${t.routeId}". Use recallRoutes to list available routes.`};if(0===s.data.features.length)return{error:`Route entry "${s.id}" has no geometry.`};const o=t.widthMeters/2,n=[];for(const a of s.data.features){const e=J.buffer(a,o,{units:"meters"})?.geometry;e&&n.push(e)}return 0===n.length?{error:`Failed to compute route corridor for "${s.id}" (turf.buffer returned no geometry for any of the ${s.data.features.length} route alternative(s)).`}:{polygons:n,routeLabel:s.label}})(t,e.route);if("error"in r)return r;c.geometries=[...c.geometries??[],...r.polygons],d=r.routeLabel}return{bias:{},multiFilters:c,range:e.range,routeLabel:d}},cs=async(e,t,r,s)=>{if("nearby"===e.mode){const r=await(async(e,t)=>{if(e.viewport){const r=ft(t.baseMap);return{bias:r?{position:r}:{},radiusMeters:e.radiusMeters}}if(e.position)return{bias:{position:e.position},radiusMeters:e.radiusMeters};if(e.query){const r=ft(t.baseMap),s=await Or(e.query,e.queryAs??"place",r?{position:r}:void 0);if(s){const t=$(s);if(t)return{bias:{position:t},radiusMeters:e.radiusMeters}}return{bias:{},radiusMeters:e.radiusMeters}}return{bias:{},radiusMeters:e.radiusMeters}})(e,t);return{bias:r.bias,multiFilters:s,radiusMeters:r.radiusMeters}}if("within"===e.mode){const s=await ls(e,t,r);return"error"in s?s:{bias:s.bias,multiFilters:s.multiFilters,range:s.range,routeLabel:s.routeLabel}}return{bias:{},multiFilters:s}},ds=e=>{if(e)return"within"===e.mode&&e.queries?.length?e.queries.map(e=>e.query).join(", "):"nearby"===e.mode&&e.query?e.query:void 0},us=async(e,t,r,s,o)=>{const n=s?await dt(e,[r],s):void 0;let a,i;if(o){const t=await ct(e,r,o);a=t.fetched,t.shown&&(i=!0)}const l=e.places.entries.find(e=>e.id===r)?.label;return{...Tr(t),placesEntryId:r,...l&&{label:l},...n&&{shown:n},...void 0!==a&&{geometriesFetched:a},...void 0!==i&&{geometriesShown:i}}},ps=e=>{const t=!0===e.experimentalSearch;return async(e,r)=>{const{query:s,where:o,poiCategories:n,show:a,entryId:i,geometries:l}=e,c=e.placeTypes,{resolved:d,unresolved:u}=await(async e=>{if(!e)return{resolved:void 0,unresolved:[]};const t=e.filter(e=>_e.has(e)),r=e.filter(e=>!_e.has(e));if(0===r.length)return{resolved:t.length?t:void 0,unresolved:[]};await M();const s=await Promise.all(r.map(async e=>({term:e,codes:await M({filters:[e]})}))),o=[],n=[];for(const{term:i,codes:l}of s)0===l.length?o.push(i):n.push(...l);const a=[...new Set([...t,...n])];return{resolved:a.length?a:void 0,unresolved:o}})(n);if(n?.length&&!d)return{error:`No POI categories matched: ${u.map(e=>`"${e}"`).join(", ")}. POI category codes must be exact CONSTANT_CASE values, or natural-language terms resolvable by the SDK catalog. Call getPoiCategoryCodes to find valid codes for your search subject (e.g. "italian food", "gym").`};const p=o??{mode:"within",viewport:!0},h=c?.length?{placeTypes:c}:{};try{if("maxDetour"===p.mode){const e=await(async(e,t,r,s,o)=>{const n=e.routing.entries;if(0===n.length)return{error:"No routes in state. Calculate a route first via setRoute."};const a=t.routeId?n.find(e=>e.id===t.routeId):n.at(-1);if(!a)return{error:`No route found with id "${t.routeId}". Use recallRoutes to list available routes.`};const i=a.data.features[0];if(!i)return{error:`Route entry "${a.id}" has no geometry.`};const l=await b({route:i,maxDetourTimeSeconds:t.maxDetourTimeSeconds,...r&&{query:r},...s&&{poiCategories:s},...t.sortBy&&{sortBy:t.sortBy},...void 0!==t.limit&&{limit:t.limit}});return{result:l,placesEntryId:e.places.addPlaceResult(l,Pr(l,{query:r,poiCategories:s,routeLabel:a.label}),o)}})(r,p,s,d,i);return"error"in e?e:us(r,e.result,e.placesEntryId,a,l)}const e=await cs(p,r,c,h);if("error"in e)return e;const{bias:o,multiFilters:n,radiusMeters:u,range:m,routeLabel:y}=e;if(m){const e=await(async(e,t,r,s,o,n,a,i)=>{const l=e.ranges.entries.find(e=>e.id===t);if(!l||0===l.ranges.length)return{error:`Range "${t}" not found. Use recallRanges to list available ranges.`};const c=l.ranges.map(e=>e.polygon?.features[0]?.geometry).filter(e=>!!e);if(0===c.length)return{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`};const d=[...c,...o.geometries??[]],u=await is(i,{query:r,poiCategories:s,multiFilters:{...o,geometries:d}});return{result:u,placesEntryId:e.places.addPlaceResult(u,Pr(u,{query:r,poiCategories:s,...a&&{routeLabel:a}}),n)}})(r,m,s,d,n,i,y,t);return"error"in e?e:us(r,e.result,e.placesEntryId,a,l)}const g=await(async(e,t,r,s,o,n,a,i,l,c)=>{const d=await is(c,{query:t,poiCategories:s,bias:r,radiusMeters:o,multiFilters:n});return{result:d,placesEntryId:e.places.addPlaceResult(d,Pr(d,{query:t,poiCategories:s,where:ds(i),...l&&{routeLabel:l}}),a)}})(r,s,o,d,u,n,i,p,y,t);return us(r,g.result,g.placesEntryId,a,l)}catch(m){return{error:`Search failed: ${m instanceof Error?m.message:String(m)}`}}}},hs=(ps(ns),{classificationPrompt:'Search multiple places by query/category in a region. BATCH categories+areas in one call. `where.mode`: within (area) / nearby (point bias) / maxDetour (route-relative ranked) / global. For "X in [named area]", use `where.query` directly — it geocodes and prefers polygon geometry over bbox in one step, no precursor locatePlace needed. Within-mode: route → corridor. Prefer geometries/municipalities/placeId over bbox. Skip for already-stored places — use recallPlaces/processPlaces.',tags:["discover","place","location"],examples:['discoverPlaces({ poiCategories: ["RESTAURANT", "CAFE", "BAR"], entryId: "amsterdam-food-spots" }) // batch many categories + semantic id','discoverPlaces({ query: "coffee", where: { mode: "nearby", position: [4.9, 52.4], radiusMeters: 500 } })','discoverPlaces({ query: "parking", where: { mode: "within", query: "De Jordaan, Amsterdam", queryAs: "place" } }) // resolves to neighbourhood polygon','discoverPlaces({ query: "coffee", where: { mode: "nearby", query: "Schiphol Airport", queryAs: "poi", radiusMeters: 2000 } }) // POI-resolved bias point','discoverPlaces({ poiCategories: ["HOTEL"], where: { mode: "within", municipalities: ["Amsterdam", "Utrecht"] }, entryId: "ams-utrecht-hotels" })','discoverPlaces({ query: "bakery", where: { mode: "within", range: "ranges-0" } })','discoverPlaces({ poiCategories: ["CAFE"], show: { markerType: "pin", zoomMode: "auto" } }) // omit `where` → defaults to viewport','discoverPlaces({ query: "Amsterdam neighbourhood", placeTypes: ["Geography"], geometries: { show: true, theme: "outline" } }) // outline boundaries','discoverPlaces({ poiCategories: ["ELECTRIC_VEHICLE_STATION"], where: { mode: "within", route: { routeId: "routes-0", widthMeters: 1000 } } }) // bulk corridor scan along the route','discoverPlaces({ query: "coffee", where: { mode: "maxDetour", maxDetourTimeSeconds: 300, limit: 5 } }) // top 5 coffee detours within 5 min off the latest route','discoverPlaces({ poiCategories: ["ELECTRIC_VEHICLE_STATION"], where: { mode: "maxDetour", routeId: "routes-1", maxDetourTimeSeconds: 600, sortBy: "detourOffset" } }) // ranked EV detours on a specific route','discoverPlaces({ query: "bakery", where: { mode: "within", placeId: "G55fc4abe-..." } }) // search inside a state place\'s polygon (auto-fetched if needed)','discoverPlaces({ query: "vegan", where: { mode: "global" } })'],examplePrompts:["Find coffee shops near me","Italian restaurants in Amsterdam","EV chargers in the current map area","Hotels in Amsterdam and Utrecht","Bakeries within the reachable range","Outline the Amsterdam neighbourhoods","Show the boundaries of these municipalities","Find all EV stations within 1 km of my route","Cafes in a 500 m corridor around the planned route","Best 5 coffee stops without losing more than 5 min off my route","EV chargers I can reach with under 10 min detour"],relatedTools:["getPOICategoryCodes","updatePlacesDisplay","updatePlacesDisplay","locatePlace","getViewport","findReachableAreas","processPlaces","analysePlaces"],dependsOn:["getPOICategoryCodes","setRoute"]}),ms=K.object({type:K.enum(["timeMinutes","distanceKM"]).describe("Budget type including its unit."),value:K.number().describe("Budget value. e.g. 30 for 30 minutes, 50 for 50 km.")}),ys=K.object({origins:K.array(Me).min(1).describe("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."),budgets:K.array(ms).min(1).describe("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."),theme:K.enum(["filled","inverted","outline"]).optional().describe("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."),showOnMap:K.boolean().describe("Whether to render the range polygon(s) on the map."),showOriginPin:K.boolean().optional().describe("Whether to drop a pin at each origin. Default: true."),hidePreviousEntries:Ze("ranges")}),gs=K.object({rangesId:K.string(),ranges:K.array(K.object({originName:K.string(),budgets:K.array(ms)})),skipped:K.array(K.object({origin:K.string(),reason:K.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry).")}),fs=K.union([gs,K.object({status:K.enum(["no_results"])}),At]),bs=async(e,t)=>{const r=await w(e.map(e=>({origin:t,budget:{type:e.type,value:e.value}})));return 0===r.features.length?null:r},ws=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,vs=K.union([K.object({count:K.number().describe("Number of codes returned"),codes:K.array(K.string()).describe('POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. "RESTAURANT") also accepted by togglePOIs categories.')}),At]),Is=K.object({filters:K.array(K.string()).optional().describe("Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes."),language:K.string().describe('Locale e.g. "en-GB", "fr-FR", "es-ES", "de-DE".')}),Ss=K.object({speed:K.number().optional(),freeFlowSpeed:K.number().optional(),congestionLevel:K.number().optional(),travelTime:K.number().optional(),networkLength:K.number().optional()}),xs=K.union([K.object({name:K.string().optional(),timezone:K.string().optional(),dateRange:K.object({start:K.string(),end:K.string()}),baseData:Ss,metrics:K.array(K.string()),tileCount:K.number(),availableGranularities:K.array(K.string())}),At]),Ps=K.object({location:Me.optional().describe("Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Mutually exclusive with bbox and geometry."),bbox:K.array(K.number()).length(4).optional().describe("[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Mutually exclusive with location."),showOnMap:K.boolean().optional().describe("Visualize the result immediately with default settings. Use showTrafficAreaAnalytics to customize."),startDate:K.string().optional().describe("Start date 'YYYY-MM-DD'. Use with endDate for a continuous range (max 31 days)."),endDate:K.string().optional().describe("End date 'YYYY-MM-DD' (inclusive). Defaults to today if omitted."),days:K.array(K.string()).optional().describe("Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate."),metrics:K.union([K.literal("all"),K.array(K.enum(["speed","congestionLevel","freeFlowSpeed","travelTime","networkLength"])).min(1)]).describe("Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength']."),functionalRoadClasses:K.union([K.literal("all"),K.array(K.enum(["MOTORWAY","MAJOR_ROAD","OTHER_MAJOR_ROAD","SECONDARY_ROAD","LOCAL_CONNECTING_ROAD","LOCAL_ROAD_HIGH_IMPORTANCE","LOCAL_ROAD","LOCAL_ROAD_MINOR_IMPORTANCE","OTHER_ROAD"]))]).optional().describe("Road classes to include. 'all' (default) or array of specific classes."),hours:K.union([K.literal("all"),K.array(K.number().int().min(0).max(23))]).optional().describe("Hours of the day (0-23) to include. 'all' (default) or array like [7,8,9,17,18] for rush hours.")}),Ms=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`,Es=e=>e instanceof Date?Number.isNaN(e.getTime())?void 0:Ms(e):"string"==typeof e?e.slice(0,10):void 0,_s=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),Ts=K.union([K.object({count:K.number(),entryId:K.string().optional(),entries:K.array(K.object({id:K.string(),label:K.string(),count:K.number(),timestamp:K.number()}))}),At]),As=K.object({bbox:K.optional(K.array(K.number())).describe("[minLng, minLat, maxLng, maxLat]. If omitted, the current map viewport is used."),categoryFilter:K.array(K.enum([...V])).optional().describe("Filter by incident category. If omitted, all categories are returned."),timeValidityFilter:K.array(K.enum(["present","future"])).optional().describe("default: ['present']"),label:K.string().optional().describe('Short human-readable label for this fetch — name the place ("Central London", "Amsterdam ring", "M25 corridor"), not coordinates. Surfaced verbatim in the monitor chip and on the entry chip in the UI; also used by analyseIncidents / focusIncidents to disambiguate which entry to operate on. Always pass one when silent is false; the bbox-coordinate fallback ("bbox a,b → c,d") is for silent intermediate fetches only.'),silent:K.boolean().optional().describe("Default: false. When true, suppress map rendering (use for intermediate calculations only). Normally, fetched incidents auto-render on the map so the user can visually validate the agent is looking at the right area."),fitBounds:K.boolean().optional().describe("Default: true. Pass false to skip camera move after rendering.")});var Cs=K.union([Ot,At]),Rs=K.object({waypointIndices:K.array(K.number().int()).min(1).describe("Indices of waypoints to remove. 0=origin, last=destination. Min 2 waypoints must remain. Duplicates are not allowed."),showOnMap:K.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Ze("routes")}),Ds=K.union([Ot,At]),ks=K.union([K.literal("origin"),K.literal("destination"),K.number().int()]).describe("Which waypoint to overwrite. Use 'origin' or 'destination' for the endpoints, or a 0-based absolute index (0 = origin, last = destination, intermediate stops in between)."),Os=K.object({waypointIndex:ks,location:Me.describe("The new waypoint that replaces the targeted one."),showOnMap:K.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Ze("routes")}),js=e=>"query"in e?`"${e.query}"`:"placeId"in e?`placeId "${e.placeId}"`:JSON.stringify(e.position),Fs=K.union([Ct,At]),Ls=K.object({position:K.array(K.number()).length(2).describe("[longitude, latitude] GeoJSON position to reverse geocode")}),Ns=K.union([K.object({affectedEntries:K.array(K.object({id:be.describe("Tagged id of the affected entry.")})).describe("Entries the analysis was attached to: every contributing places entry plus every contributing custom-geometries entry. Ranges entries feed the aggregation but do not carry analyses."),sourceIds:K.array(be).describe(Se),name:K.string().describe("Analysis name (unique within each affected entry)."),description:K.string().optional(),outputFormat:K.enum(["json","chart"]).describe("`json` = plain object for text rendering; `chart` = Chart.js `ChartConfiguration`."),analysis:K.unknown().describe("Aggregation result. JSON-serializable when `json`; Chart.js config when `chart`."),skipped:K.array(ut).optional().describe(Ie)}),At]),$s=`Async JS that aggregates the injected \`geometries\` array and returns the result. ${ve}\n\n${Ae(["geometries","h3","turf"])}\n\n${Oe("totals, per-feature breakdowns, hex bin counts, bboxes")}\n\nExamples:\n- total + average area: \`const areas = geometries.map(turf.area); return { totalKm2: turf.round(areas.reduce((a,b)=>a+b,0)/1e6, 2), avgKm2: turf.round((areas.reduce((a,b)=>a+b,0)/areas.length)/1e6, 2), perFeature: areas.map((a,i) => ({ id: geometries[i].id, km2: turf.round(a/1e6, 2) })) };\`\n- combined bbox: \`return { bbox: turf.bbox({ type: "FeatureCollection", features: geometries }) };\`\n- h3 coverage at res 8: \`const cells = new Set(); for (const f of geometries) for (const c of h3.polygonToCells(f.geometry.coordinates, 8)) cells.add(c); return { resolution: 8, cellCount: cells.size };\`\n- areas as bar chart: \`return { type: "bar", data: { labels: geometries.map(f => f.id), datasets: [{ label: "km²", data: geometries.map(f => +(turf.area(f)/1e6).toFixed(2)) }] } };\`\n\n${xe}`,qs=K.object({geometriesEntryIDs:we,name:K.string().describe('Unique name within each affected entry (e.g. "total-area", "bbox-extent", "h3-coverage"). Reusing a name replaces the previous analysis on each entry.'),description:K.string().optional().describe("Optional short description of what the analysis computes."),outputFormat:K.enum(["json","chart"]).optional().describe(ke),code:K.string().describe($s)}),Bs=K.union([K.object({incidentsEntryID:K.string().describe("ID of the source incidents entry the analysis was attached to."),name:K.string().describe("Unique name of the analysis within the entry."),description:K.string().optional(),outputFormat:K.enum(["json","chart"]).describe("How the consumer should render `analysis`: `json` = plain object to display as text; `chart` = Chart.js `ChartConfiguration` to feed directly into `new Chart(ctx, analysis)` in the chat message UI."),analysis:K.unknown().describe("The aggregation result. Shape depends on `outputFormat`: when `json`, any JSON-serializable value; when `chart`, a Chart.js `ChartConfiguration` ({ type, data, options? })."),focused:K.object({incidentsEntryID:K.string(),focusedCount:K.number(),droppedIds:K.array(K.string()),reason:K.string().optional()}).optional().describe("Present when the analysis result included a `focusIds` field — the tool applied that focus as a side-effect on the source entry. The agent can read `focusedCount` to confirm.")}),At]),Gs=K.object({incidentsEntryID:K.string().optional().describe("ID of an existing incidents entry to analyse. Omit to analyse the most recently fetched entry. The analysis is attached to that entry under `_analyses` with the given name; the latest result is read via `_analyses.getResult(name)` and the timeline via `_analyses.history(name)`."),name:K.string().describe('Unique name within the entry (e.g. "by-category", "delay-by-road", "hex-density-8"). Reusing a name replaces the previous analysis on the same entry.'),description:K.string().optional().describe("Optional short description of what the analysis computes."),outputFormat:K.enum(["json","chart"]).optional().describe(ke),code:K.string().describe(`Async JS that aggregates the injected \`incidents\` and returns the result. \`now: Date\` is the call-start time (compare against \`startTime\`/\`endTime\`). \`log(...)\` writes into the response logs.\n\n${Ae(["incidents","h3","turf","now","log","previous"])}\n\nPREVIOUS: the 6th argument is \`previous\` — your last result for this spec on the source entry, or \`undefined\` on the first run. Use it for id stability (e.g. reuse a previous group/cluster id when memberIds overlap by ≥50%) and to surface short trend reads ("growing" / "fading" / "steady") in your output. Always handle \`previous === undefined\` gracefully — it is \`undefined\` on every first run.\n\n${Oe("counts, top-N, groupings, hex bins, corridor totals",'`labels: incidents.map(i => [i.properties.from ?? "?", i.properties.events[0]?.description ?? "", \\`${(i.properties.delayInSeconds ?? 0)}s delay\\`])`')}\n\nTrafficIncident schema (input shape):\n\`\`\`\ntype TrafficIncident = {\n type: "Feature",\n id: string,\n geometry: { type: "Point", coordinates: [lng, lat] } | { type: "LineString", coordinates: [lng,lat][] },\n properties: {\n id: string,\n category: "accident" | "animals-on-road" | "broken-down-vehicle" | "danger" | "flooding" | "fog" | "frost"\n | "jam" | "lane-closed" | "narrow-lanes" | "other" | "rain" | "road-closed" | "roadworks" | "wind",\n magnitudeOfDelay: "unknown" | "minor" | "moderate" | "major" | "indefinite",\n timeValidity: "present" | "future",\n delayInSeconds?: number,\n lengthInMeters?: number,\n roadNumbers?: string[],\n from?: string,\n to?: string,\n startTime?: Date,\n endTime?: Date,\n events: { description: string }[],\n },\n};\n\`\`\`\n\nExamples (one canonical shape per kind — adapt rather than copy verbatim):\n- counts: \`const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; return { total: incidents.length, byCategory: c };\`\n- top-N: \`return [...incidents].sort((a,b) => (b.properties.delayInSeconds ?? 0) - (a.properties.delayInSeconds ?? 0)).slice(0,5).map(i => ({ id: i.properties.id, delay: i.properties.delayInSeconds, road: i.properties.roadNumbers?.[0] }));\`\n- bar chart: \`const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; const e = Object.entries(c).sort((a,b) => b[1]-a[1]); return { type: 'bar', data: { labels: e.map(x => x[0]), datasets: [{ label: 'Incidents', data: e.map(x => x[1]) }] } };\`\n- h3 density: \`const bins = {}; for (const i of incidents) { const c = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; bins[h3.latLngToCell(c[1], c[0], 8)] = (bins[h3.latLngToCell(c[1], c[0], 8)] ?? 0) + 1; } return { resolution: 8, bins };\`\n- spatial clusters (slowdown pockets, ranked corridors): produce \`{ groups: [{ id, headline, body, memberIds, centroid, size, totalDelaySeconds, peakDelaySeconds, diameterKm, primaryRoads }] }\` plus \`focusIds\` for the worst group. CRITICAL: \`id\` must be \`"c1"\`, \`"c2"\`, … (rank, NOT centroid coords or TTI ids — the agent quotes these verbatim in chat). \`headline\` must be a road label like \`\` \`\${primaryRoads[0]} corridor\` \`\` (the user reads this directly). \`body\` is the metric line \`\` \`Trend \${trend} · total \${(totalDelay/60).toFixed(1)} min · \${size} incidents · \${diameterKm.toFixed(2)} km wide\` \`\`. Reference implementation (adapt eps / minPoints / cap as needed):\n\`\`\`\nconst pts = incidents.filter(i => (i.properties.delayInSeconds ?? 0) > 60).map(i => {\n const c = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates;\n return turf.point(c, { id: i.properties.id, delay: i.properties.delayInSeconds, road: i.properties.roadNumbers?.[0] ?? null, from: i.properties.from ?? null });\n});\nif (pts.length === 0) return { groups: [], focusIds: [] };\nconst clustered = turf.clustersDbscan(turf.featureCollection(pts), 0.4, { units: 'kilometers', minPoints: 4 });\nconst buckets = {};\nfor (const f of clustered.features) {\n const cid = f.properties.cluster;\n if (cid == null) continue;\n (buckets[cid] ??= []).push(f);\n}\nconst ranked = Object.values(buckets).map(feats => {\n const memberIds = feats.map(f => f.properties.id);\n const totalDelaySeconds = feats.reduce((s, f) => s + f.properties.delay, 0);\n const peakDelaySeconds = feats.reduce((m, f) => Math.max(m, f.properties.delay), 0);\n const centroid = turf.centroid(turf.featureCollection(feats)).geometry.coordinates;\n const bb = turf.bbox(turf.featureCollection(feats));\n const diameterKm = turf.distance([bb[0], bb[1]], [bb[2], bb[3]], { units: 'kilometers' });\n const roadCount = {};\n for (const f of feats) if (f.properties.road) roadCount[f.properties.road] = (roadCount[f.properties.road] ?? 0) + 1;\n const primaryRoads = Object.entries(roadCount).sort((a,b) => b[1]-a[1]).slice(0,3).map(([r]) => r);\n return { memberIds, centroid, size: feats.length, totalDelaySeconds, peakDelaySeconds, diameterKm, primaryRoads };\n}).filter(g => g.diameterKm <= 2)\n .sort((a,b) => b.totalDelaySeconds - a.totalDelaySeconds)\n .slice(0, 6)\n .map((g, i) => {\n const trend = 'new'; // optionally compare against \`previous\` here\n const road = g.primaryRoads[0] ?? 'cluster';\n const headline = \`\${road} corridor\`;\n const body = \`Trend \${trend} · total \${(g.totalDelaySeconds/60).toFixed(1)} min · \${g.size} incidents · \${g.diameterKm.toFixed(2)} km wide\`;\n return { id: \`c\${i+1}\`, headline, body, ...g };\n });\nreturn { groups: ranked, focusIds: ranked[0]?.memberIds ?? [], focusReason: ranked[0] ? \`\${ranked[0].headline}: worst total delay\` : undefined };\n\`\`\`\nKnobs: eps 0.3–0.5 km (eps ≥ 1 km chains every corridor into one mega-cluster); pre-filter delay > 60s to drop zero-delay roadworks/closures; cap diameter ≤ 2 km for "compact pocket".\n- trend across ticks: when \`previous\` is non-undefined, reuse a prior group id when \`memberIds\` overlap ≥50%, then derive \`growing\`/\`fading\`/\`steady\` from \`totalDelaySeconds\` ratio against the matched prior (else \`new\`).`)}),Us=(e,t,r)=>{if(!e||"object"!=typeof e)return;const s=e.focusIds;if(!Array.isArray(s)||0===s.length)return;const o=s.filter(e=>"string"==typeof e);if(0===o.length)return;const n=e.focusReason,a="string"==typeof n?n:void 0,i=r.trafficIncidents.setFocus(t,o,a);return{incidentsEntryID:t,focusedCount:i.focusedCount,droppedIds:i.droppedIds,...a&&{reason:a}}},Ws=K.union([K.object({placesEntryIDs:K.array(K.string()).describe("IDs of the source places entries the analysis was attached to."),name:K.string().describe("Unique name of the analysis within the entry."),description:K.string().optional(),outputFormat:K.enum(["json","chart"]).describe("How the consumer should render `analysis`: `json` = plain object to display as text; `chart` = Chart.js `ChartConfiguration` to feed directly into `new Chart(ctx, analysis)` in the chat message UI."),analysis:K.unknown().describe("The aggregation result. Shape depends on `outputFormat`: when `json`, any JSON-serializable value; when `chart`, a Chart.js `ChartConfiguration` ({ type, data, options? }).")}),At]),zs=K.object({placesEntryIDs:ze({verb:"analyse",extra:"Multiple IDs are merged into a single `places` input. The analysis is attached to EACH source entry under `_analysis` with the given name."}),name:K.string().describe('Unique name within the entry (e.g. "by-category", "hex-density-8"). Reusing a name replaces the previous analysis.'),description:K.string().optional().describe("Optional short description of what the analysis computes."),outputFormat:K.enum(["json","chart"]).optional().describe(ke),code:K.string().describe(`Async JS that aggregates the injected \`places\` FeatureCollection and returns the result. \`places\` is the merged set of every entry in \`placesEntryIDs\`; \`placesByEntry[entryId]\` exposes each one separately for per-entry totals or set ops — list every needed id in \`placesEntryIDs\` rather than trying to call \`recallPlaces\` (sandbox cannot call other tools).\n\n${Ae(["places","placesByEntry","h3","turf"])}\n\n${Oe("counts, groupings, hex bins, centroids, bboxes","`labels: hexIds.map(h => [\\`Hex ${h}\\`, \\`${counts[h]} items\\`, ...names[h].slice(0, 5)])`")}\n\n${Ee}\n\n`)}),Vs=K.union([K.object({routesEntryIDs:K.array(K.string()).describe("IDs of the source routes entries the analysis was attached to."),name:K.string().describe("Unique name of the analysis within the entry."),description:K.string().optional(),outputFormat:K.enum(["json","chart"]).describe("How the consumer should render `analysis`: `json` = plain object to display as text; `chart` = Chart.js `ChartConfiguration` to feed directly into `new Chart(ctx, analysis)` in the chat message UI."),analysis:K.unknown().describe("The aggregation result. Shape depends on `outputFormat`: when `json`, any JSON-serializable value; when `chart`, a Chart.js `ChartConfiguration`.")}),At]),Hs=K.object({routesEntryIDs:Ve({verb:"analyse",extra:"Multiple IDs are merged into a single `routes` input (features concatenated). The analysis is attached to EACH source entry under `_analysis` with the given name."}),placesEntryIDs:ze({verb:"cross-reference",extra:"Optional. When set, those entries are injected as `placesByEntry: Record<entryId, Places>` so the code can correlate route geometry/sections with places (e.g. POIs near the route, places per country, hex co-occurrence)."}),name:K.string().describe('Unique name within the entry (e.g. "summary", "traffic-delay-by-country", "ev-stops-near-route"). Reusing a name replaces the previous analysis.'),description:K.string().optional().describe("Optional short description of what the analysis computes."),outputFormat:K.enum(["json","chart"]).optional().describe(ke),code:K.string().describe(`Async JS that aggregates the injected \`routes\` FeatureCollection (and \`placesByEntry\` when \`placesEntryIDs\` is set, else \`undefined\`) and returns the result.\n\n${Ae(["routes","placesByEntry","h3","turf"])}\n\n${Oe("totals, per-section breakdowns, hex bins, place counts within X m of the route","`labels: sections.map(s => [s.name, \\`${s.km} km\\`, \\`+${s.delaySec}s delay\\`])`")}\n\nFor places-vs-route work: buffer the line with \`turf.buffer(route, meters/1000, { units: "kilometers" })\` and test with \`turf.booleanPointInPolygon\`, or compute \`turf.pointToLineDistance(place, route, { units: "meters" })\`. Iterate \`Object.entries(placesByEntry)\` to keep results grouped by entry.\n\n${Te}\n\n${Ee}\n\n`)}),Js=K.union([K.object({incidentsEntryID:K.string().describe("The entry the focus was applied to."),focusedCount:K.number().describe("How many of the requested ids ended up in the focus subset."),droppedIds:K.array(K.string()).describe("Ids that were not present on the entry and were dropped from the focus."),reason:K.string().optional().describe("Echo of the focus reason for downstream display.")}),At]),Ys=K.object({incidentsEntryID:K.string().describe("Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source)."),incidentIds:K.array(K.string()).min(1).describe("Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored."),reason:K.string().optional().describe('Short label shown in the UI FocusChip — what this subset represents (e.g. "top 3 by delay").')}),Ks=K.object({slotIndex:K.number().int().min(0).optional().describe("Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot."),includeEmptySlots:K.boolean().optional().describe("Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses."),offset:K.number().int().min(0).optional().describe("Pagination offset within the filtered slot list. Ignored when slotIndex is provided."),limit:K.number().int().min(1).max(25).optional().describe("Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.")}),Xs=K.object({totalSlots:K.number().describe("Total waypoint slots currently staged"),filledSlots:K.number().describe("Number of staged slots that already contain coordinates"),matchedCount:K.number().describe("Total slots matching the request before pagination"),returnedCount:K.number().describe("Number of slots returned in this page"),offset:K.number().describe("Offset applied to the filtered slot list"),hasMore:K.boolean().describe("True when more slots can be fetched with a higher offset"),nextOffset:K.number().optional().describe("Offset to request the next page when hasMore=true"),waypoints:K.array(Rr)}),Zs=K.union([K.object({geometriesEntryId:be.describe('Tagged id of the new entry (always `kind: "custom"`).'),label:K.string().describe("Human-readable label for the new entry."),geometryCount:K.number().describe("Number of geometries returned by the user code."),sourceIds:K.array(be).describe(Se),operation:K.string().optional().describe("Operation label stored on the new entry."),skipped:K.array(ut).optional().describe(Ie),fitted:he.optional().describe("Bounds the camera was fit to (when `fitOnMap` was set)."),shown:K.boolean().optional().describe("Whether the new entry was rendered on the map (when `show` was set).")}),K.object({fitted:he}),At]),Qs=`Async JS over \`geometries\`. Return \`PolygonFeature[]\` (Polygon/MultiPolygon, stable \`id\` recommended; empty = empty result) OR \`{ geometries?, fitOnMap? }\` for code-and-zoom; \`{ fitOnMap }\` alone skips state.\n\n${ve}\n\n${Ae(["geometries","h3","turf"])}\n\n${me}\n\nTurf v7: \`union\`/\`intersect\`/\`difference\` take a single \`FeatureCollection\` (NOT two features). Wrap inputs with \`turf.featureCollection([...])\`.\n\nEach input feature is tagged with \`properties._source: { kind, id }\` reflecting the tagged id it was passed in under. Filter by that to pick a specific input role inside the sandbox — e.g. for "areas in Amsterdam not reachable from the hospitals", first call \`locatePlace\` to fetch Amsterdam's polygon and \`findReachableAreas\` for the hospitals, then pass BOTH ids to \`geometriesEntryIDs\` and select them in code via \`_source\`.\n\nExamples:\n- union: \`return geometries.length ? [turf.union(turf.featureCollection(geometries))].filter(Boolean) : [];\`\n- buffer each by 200m: \`return geometries.map((f) => ({ ...turf.buffer(f, 200, { units: "meters" }), id: \`\${f.id}-buf\` }));\`\n- pairwise intersections: \`const out = []; for (let i=0;i<geometries.length;i++) for (let j=i+1;j<geometries.length;j++) { const x = turf.intersect(turf.featureCollection([geometries[i], geometries[j]])); if (x) out.push({ ...x, id: \`x-\${i}-\${j}\` }); } return out;\`\n- inverse within a named region (pass region polygon AND the inner polygons; pick by \`_source\`): \`const outer = geometries.filter(g => g.properties._source.kind === "place"); const inner = geometries.filter(g => g.properties._source.kind === "ranges"); const env = turf.union(turf.featureCollection(outer)); const u = turf.union(turf.featureCollection(inner)); const inv = (env && u) ? turf.difference(turf.featureCollection([env, u])) : env; return inv ? [{ ...inv, id: "inverse" }] : [];\`\n- inverse with bbox-envelope fallback (no containing region available): \`const fc = turf.featureCollection(geometries); const [w,s,e,n] = turf.bbox(fc); const env = turf.bboxPolygon([w-0.05,s-0.05,e+0.05,n+0.05]); const u = turf.union(fc); const inv = u ? turf.difference(turf.featureCollection([env, u])) : env; return inv ? [{ ...inv, id: "inverse" }] : [];\`\n- h3 hex coverage: \`const cells = new Set(); for (const f of geometries) for (const c of h3.polygonToCells(f.geometry.coordinates, 8)) cells.add(c); return [...cells].map((c) => turf.polygon([h3.cellToBoundary(c, true)], { cell: c, type: "h3-cell" }));\`\n- focus camera (no entry): \`const u = geometries.length ? turf.union(turf.featureCollection(geometries)) : null; return { fitOnMap: u ? turf.bbox(u) : turf.bbox({type:"FeatureCollection",features:geometries}) };\`\n- write entry AND zoom: \`const out = geometries.map((f) => ({ ...turf.buffer(f, 200, { units: "meters" }), id: \`\${f.id}-buf\` })); return { geometries: out, fitOnMap: { bbox: turf.bbox({type:"FeatureCollection",features:out}), padding: 80 } };\`\n\n${xe}`,eo=K.object({geometriesEntryIDs:we,code:K.string().describe(Qs),label:K.string().optional().describe('Short label for the new entry (default: "processed geometries (<N>)").'),operation:K.string().optional().describe("Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance. Defaults to `custom`."),entryId:He,show:K.object({theme:K.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline". Use "inverted" for difference/inverse results.'),mode:Je,hidePreviousEntries:Ze("custom-geometries")}).optional().describe('Render the new custom-geometries entry on the map. For derived results that supersede their inputs (inverse, difference, union of isochrones, …), set `hidePreviousEntries: "all"` so the prior reachable / source polygons are cleared and only the new entry is visible. Omit `show` to write the entry without rendering.')}),to=K.object({mode:Je,theme:K.enum(["filled","outline","inverted"]).optional().describe('Visual theme for the polygons. Default: "outline".')}),ro=K.object({places:et.optional(),geometries:K.object({count:K.number(),theme:K.enum(["filled","outline","inverted"]),mode:K.enum(["add","replace"])}).optional()}),so=K.union([Rt.extend({shown:ro.optional(),placesEntryId:K.string().optional(),label:K.string().optional(),connectionsRendered:K.number().optional().describe("Number of connection lines drawn between the output places (when `placeConnections` was returned)."),geometryCount:K.number().optional().describe("Number of polygons stored on the new entry under `geometries` (when the code returned a `geometries` array)."),fitted:he.optional().describe("Bounds the camera was fit to (when the code returned `fitOnMap`).")}),K.object({fitted:he}),At]),oo=K.object({placesEntryIDs:ze({verb:"process",extra:"Multiple IDs are merged into a single `places` input. Sources are untouched; results go to a NEW entry."}),code:K.string().describe(`Async JS returning \`{ places?, placeConnections?, geometries?, fitOnMap? }\`. At least one of \`places\` / \`fitOnMap\` required; \`placeConnections\` and \`geometries\` require \`places\` (they attach to the new entry). \`places\`: Places FeatureCollection (filter/merge/subset of \`places.features\`) — written as a NEW entry. \`placeConnections\`: \`{ from, to, id?, ...}[]\` between output places (endpoints: Place or place id). \`geometries\`: \`PolygonFeature[]\` (Polygon/MultiPolygon, stable \`id\` recommended). \`fitOnMap\`: see below. Inputs: \`places\` is the merged set across \`placesEntryIDs\`; \`placesByEntry[id]\` keeps them separate for set ops (diff/intersection/group-by-source) — list every needed id in \`placesEntryIDs\` (sandbox can't call other tools). \`geometries\` arg is empty unless \`withGeometries: true\`.\n\n${Ae(["places","placesByEntry","geometries","h3","turf"])}\n\n${me}\n\n${Ee}\n\n${xe}\n\nExamples:\n- filter: \`return { places: { type: "FeatureCollection", features: places.features.filter(p => p.properties.poi?.name?.toLowerCase().includes("vegan")) } };\`\n- diff between two entries (in \`placesEntryIDs: ["A","B"]\`): \`const aIds = new Set(placesByEntry["A"].features.map(p => p.id)); return { places: { type: "FeatureCollection", features: placesByEntry["B"].features.filter(p => !aIds.has(p.id)) } };\`\n- filter + connect: \`const f = places.features.filter(p => p.properties.poi?.categories?.includes("CAFE")); return { places: { type: "FeatureCollection", features: f }, placeConnections: f.slice(1).map(p => ({ from: f[0], to: p })) };\`\n- passthrough + star: \`const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };\`\n- dbscan clusters → convex hulls: \`const c = turf.clustersDbscan({type:"FeatureCollection",features:places.features}, 0.5, {units:"kilometers"}); const groups = {}; for (const f of c.features) { const k = f.properties.cluster; if (k != null) (groups[k] ||= []).push(f); } const hulls = Object.entries(groups).map(([k, fs]) => { const h = turf.convex({type:"FeatureCollection",features:fs}); if (h) { h.id = \`cluster-\${k}\`; h.properties = { cluster: k, count: fs.length }; } return h; }).filter(Boolean); return { places, geometries: hulls };\`\n- buffer footprints (needs \`withGeometries: true\`): \`return { places, geometries: geometries.map((f, i) => ({ ...turf.buffer(f, 250, { units: "meters" }), id: \`\${f.id}-buf\` })) };\`\n- union footprints (needs \`withGeometries: true\`): \`const u = geometries.reduce((a, f) => a ? turf.union(a, f) : f, null); return { places, geometries: u ? [u] : [] };\`\n- h3 hex coverage of result places: \`const cells = new Set(); for (const p of places.features) cells.add(h3.latLngToCell(p.geometry.coordinates[1], p.geometry.coordinates[0], 8)); return { places, geometries: [...cells].map((c) => turf.polygon([h3.cellToBoundary(c, true)], { cell: c, type: "h3-cell" })) };\`\n- focus camera on filtered places (no new entry): \`const f = places.features.filter(p => p.properties.poi?.categories?.includes("EV_STATION")); return { fitOnMap: turf.bbox({ type: "FeatureCollection", features: f }) };\`\n- write entry AND zoom: \`const f = places.features.filter(p => p.properties.poi?.name?.toLowerCase().includes("vegan")); return { places: { type: "FeatureCollection", features: f }, fitOnMap: { bbox: turf.bbox({ type: "FeatureCollection", features: f }), padding: 80 } };\``),label:K.string().optional().describe('Short label for the new entry (default: "processed (<N> places)").'),entryId:He,show:K.object({places:Qe.optional().describe("Render the processed places (markerType / zoomMode / hidePreviousEntries)."),geometries:to.optional().describe("Render the polygons returned in `geometries` via the place GeometriesModule. No-op when the code did not return any geometries.")}).optional().describe("What to render on the map after processing. Set `show.places` to draw the processed places, set `show.geometries` to draw the produced polygons; both can be set together. Omit to store the new entry without touching the map."),connectMarkerType:K.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Pick the module whose shown places the connection endpoints reference (ids only resolve against currently-shown places).'),withGeometries:K.boolean().optional().describe("When true, fetch each input place's boundary polygon (where available) before running the code and inject them as the `geometries` array. Required when the code reads `geometries` (e.g. to aggregate footprints, compute h3 coverage, or buffer outlines). Defaults to false to keep the tool light when only `places` are needed.")}),no=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return!(void 0!==t.places&&!(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)})(t.places))&&(!(void 0!==t.placeConnections&&!(e=>{if(!Array.isArray(e))return!1;const t=e=>"string"==typeof e||"object"==typeof e&&null!==e&&"geometry"in e;return e.every(e=>{if(!e||"object"!=typeof e)return!1;const r=e;return t(r.from)&&t(r.to)})})(t.placeConnections))&&(!(void 0!==t.geometries&&!(e=>Array.isArray(e)&&e.every(e=>Re(e)))(t.geometries))&&(!(void 0!==t.fitOnMap&&!ge(t.fitOnMap))&&((void 0!==t.places||void 0!==t.fitOnMap)&&(void 0!==t.places||void 0===t.placeConnections&&void 0===t.geometries)))))},ao={bboxFromGeoJSON:O,calculateProgressAtRoutePoint:j,getCoordinateAtRouteProgress:N,getProgressAtNearestRoutePoint:q,getRouteProgressBetween:B,getRouteProgressForSection:G,getSectionBBox:U},io=K.union([K.object({fitted:he.describe("Bounds the camera was fit to.")}),At]),lo=K.object({routesEntryIDs:Ve({verb:"process",extra:"Multiple IDs are merged into a single `routes` input (features concatenated)."}),placesEntryIDs:ze({verb:"cross-reference",extra:'Optional. Injects `placesByEntry: Record<entryId, Places>` for correlating route sections/geometry with places (e.g. "fit the route AND every EV station within 500m").'}),code:K.string().describe(`Async JS returning \`{ fitOnMap }\`. Read-only — no new entries. Derive section/progress bboxes via \`routeUtils.*\`; fall back to \`turf.bbox(routes.features[0])\` for the whole route.\n\n${Ae(["routes","placesByEntry","routeUtils","h3","turf"])}\n\n\`routeUtils\` (from @tomtom-org/maps-sdk/core):\n• \`getSectionBBox(route, section)\` → \`[W,S,E,N]\`. Sections at \`route.properties.sections.<type>\` (\`traffic\`, \`motorway\`, \`country\`, \`toll\`, …).\n• \`getRouteProgressForSection(route, section)\` / \`getRouteProgressBetween(route, startPathIndex, endPathIndex)\` → \`{ start, end, delta: { distanceInMeters, travelTimeInSeconds } }\`.\n• \`calculateProgressAtRoutePoint(route, pathIndex)\` → \`{ distanceInMeters, travelTimeInSeconds }\`.\n• \`getCoordinateAtRouteProgress(route, query)\` — \`query\`: \`{ traveledDistanceInMeters }\` | \`{ traveledTimeInSeconds }\` | \`{ clockTime: Date }\` → \`{ position, ... }\`.\n• \`getProgressAtNearestRoutePoint(route, { lng, lat })\` — snap a point → projected position + cumulative distance/time.\n• \`bboxFromGeoJSON(featureOrCollection)\` — bbox of any GeoJSON.\n\n${me}\n\nExamples:\n- focus on the worst traffic section: \`const r = routes.features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };\`\n- focus on the first toll section: \`const r = routes.features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };\`\n- focus on a 10–20 km segment: \`const r = routes.features[0]; const a = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 10000 }); const b = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 20000 }); if (!a || !b) return { fitOnMap: turf.bbox(r) }; const fc = { type: "FeatureCollection", features: [turf.point(a.position), turf.point(b.position)] }; return { fitOnMap: { bbox: turf.bbox(fc), padding: 100 } };\`\n- focus on the first leg only: \`const r = routes.features[0]; const leg = r.properties.sections.leg?.[0]; if (!leg) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, leg) };\`\n- focus on a country-section + nearby EV stations (cross-ref): \`const r = routes.features[0]; const sec = r.properties.sections.country?.find(c => c.countryCodeISO3 === "FRA"); if (!sec) return { fitOnMap: turf.bbox(r) }; const ev = (placesByEntry?.["ev-stations"]?.features ?? []).filter(p => turf.pointToLineDistance(p, r, { units: "meters" }) <= 1000); const fc = { type: "FeatureCollection", features: [...ev, turf.bboxPolygon(routeUtils.getSectionBBox(r, sec) ?? turf.bbox(r))] }; return { fitOnMap: { bbox: turf.bbox(fc), padding: 80 } };\`\n\n${Te}\n\n${Ee}`)}),co=Ss.extend({date:K.string().optional(),hour:K.number().optional(),year:K.number().optional(),month:K.number().optional(),week:K.number().optional(),day:K.number().optional()}),uo=K.object({granularity:K.enum(["daily","hourly","weekly","monthly","yearly","average"]).optional().describe("Time granularity. Default: 'daily'. Use 'average' for day-of-week × hour patterns."),metric:K.enum(["speed","congestionLevel","freeFlowSpeed","travelTime","networkLength"]).optional().describe("Filter to a single metric. Default: all requested metrics."),startDate:K.string().optional().describe("Filter entries from this date (YYYY-MM-DD). Works with 'daily' and 'hourly'."),endDate:K.string().optional().describe("Filter entries up to this date (YYYY-MM-DD). Works with 'daily' and 'hourly'."),hourStart:K.number().int().min(0).max(23).optional().describe("Filter hourly/average entries from this hour (0-23)."),hourEnd:K.number().int().min(0).max(23).optional().describe("Filter hourly/average entries up to this hour (0-23)."),dayOfWeek:K.array(K.number().int().min(1).max(7)).optional().describe("Filter 'average' entries by day of week (1=Mon…7=Sun).")}),po=K.union([K.object({granularity:K.string(),count:K.number(),entries:K.array(co),currentVisualization:K.object({activeMetric:K.string().optional(),displayMode:K.string().optional()}).optional()}),At]),ho=K.object({id:be.optional().describe('Tagged id to inspect in detail (e.g. `{ kind: "places", id: "ev-stations-oost" }`, `{ kind: "ranges", id: "ranges-0" }`, `{ kind: "custom", id: "bakery-candidates-gap-oost" }`). Omit to list every available id across all slices.')}),mo=K.object({name:K.string(),outputFormat:K.enum(["json","chart"]),timestamp:K.number(),description:K.string().optional()}),yo=K.object({id:be.describe("Tagged id of this entry."),label:K.string(),timestamp:K.number(),featureCount:K.number(),operation:K.string().optional().describe("Operation label (custom-geometries entries only)."),sourceIds:K.array(be).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:K.array(mo).optional().describe("Analyses already attached to this entry (places and custom only).")}),go=yo.extend({features:K.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),fo=K.union([K.object({entries:K.array(yo)}),go,At]),bo=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:s})=>({name:e,outputFormat:t,timestamp:r,...s&&{description:s}})):void 0,wo=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const e=bo(r._analysis);t.push({id:{kind:"places",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,...e&&{analyses:e}})}for(const r of e.ranges.entries){const e=r.ranges.reduce((e,t)=>e+(t.polygon?.features.length??0),0);0!==e&&t.push({id:{kind:"ranges",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:e})}for(const r of e.customGeometries.entries){const e=bo(r._analysis);t.push({id:{kind:"custom",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.features.length,...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...e&&{analyses:e}})}return t.sort((e,t)=>t.timestamp-e.timestamp)},vo=K.object({id:K.string().optional().describe('Entry ID to retrieve (e.g. "places-3"). Omit to list all entries.')}),Io=K.object({name:K.string(),outputFormat:K.enum(["json","chart"]),timestamp:K.number(),description:K.string().optional()}),So=K.object({id:K.string(),label:K.string(),timestamp:K.number(),featureCount:K.number().describe("Number of Place features stored in this entry."),analyses:K.array(Io).optional().describe("Analyses already attached to this entry via analysePlaces (name + output format).")}),xo=K.object({id:K.string(),label:K.string(),timestamp:K.number(),places:Rt}),Po=K.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several entries render simultaneously; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),Mo=K.union([K.object({entries:K.array(So),entryMode:Po}),xo.extend({entryMode:Po}),At]),Eo=K.object({id:K.string().optional().describe('Entry ID to retrieve (e.g. "ranges-0"). Omit to list all entries.')}),_o=K.object({id:K.string(),label:K.string(),timestamp:K.number(),originCount:K.number()}),To=K.object({origin:K.object({query:K.string().optional(),position:K.tuple([K.number(),K.number()])}),budgets:K.array(K.object({type:K.string(),value:K.number()}))}),Ao=K.object({id:K.string(),label:K.string(),timestamp:K.number(),ranges:K.array(To)}),Co=K.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several reachable-range entries render at once; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),Ro=K.union([K.object({entries:K.array(_o),entryMode:Co}),Ao.extend({entryMode:Co}),At]),Do=K.object({id:K.string().optional().describe('Entry ID to retrieve (e.g. "routes-1"). Omit to list all entries.')}),ko=K.object({id:K.string(),label:K.string(),timestamp:K.number()}),Oo=K.object({maxAlternatives:K.number().optional(),costModel:$r.optional(),when:qr.optional()}),jo=K.object({id:K.string(),label:K.string(),timestamp:K.number(),routes:Ot,waypoints:K.array(K.object({position:K.array(K.number()).length(2).describe("[longitude, latitude]")})),params:Oo}),Fo=K.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several routes render at once; `single` enforces "at most one route on the map" — switching to it auto-clears non-latest entries.'),Lo=K.union([K.object({entries:K.array(ko),entryMode:Fo}),jo.extend({entryMode:Fo}),At]),No=K.object({id:K.string(),label:K.string()}),$o=K.enum(["single","multiple"]),qo=K.union([K.object({places:K.array(No).describe("Places history entries — newest first."),routes:K.array(No).describe("Routes history entries — newest first."),ranges:K.array(No).describe("Reachable-range history entries — newest first."),entryModes:K.object({places:$o,routes:$o,ranges:$o}).describe("Per-slice display policy. `multiple` (default) lets several entries render at once; `single` keeps only the latest entry — set via setEntryMode tools."),mapStyle:K.object({id:K.string().optional().describe('Standard style id (e.g. "standardLight").')}).nullable().describe("Current map style. `null` when the map has no style assigned yet."),mapPOIs:K.object({visible:K.boolean().optional(),filterMode:K.enum(["only","all_except"]).optional(),categories:K.array(K.string()).optional()}).nullable().describe("Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session."),traffic:K.object({flow:K.object({visible:K.boolean().optional()}).nullable().describe("Traffic-flow layer config. `null` when the module is uninitialised."),incidents:K.object({visible:K.boolean().optional()}).nullable().describe("Traffic-incidents layer config. `null` when the module is uninitialised.")}).describe("Real-time traffic visualization configs.")}),At]),Bo=K.object({}),Go=K.union([K.object({success:K.literal(!0),cleared:K.array(K.enum(["places","routes","ranges","mapPOIs","traffic","mapStyle"])).describe("Slices that were wiped / reset to defaults.")}),At]),Uo=K.object({}),Wo=K.enum(["places","routes","ranges"]),zo=K.enum(["single","multiple"]),Vo=K.union([K.object({success:K.literal(!0),slice:Wo,mode:zo,droppedEntryIds:K.array(K.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),At]),Ho=K.object({slice:Wo.describe("Which history to reconfigure: places / routes / ranges."),mode:zo.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),Jo=K.union([K.object({incidentsEntryID:K.string(),alreadyRunning:K.boolean().describe("True when the entry already had a running monitor — start is idempotent. When true, any new intervalMs / filter context in this call was IGNORED; the monitor keeps running with whatever it was started with. Call stopTrafficIncidentsMonitor first if you need different settings.")}),At]),Yo=K.object({incidentsEntryID:K.string().describe("Id of the incidents entry to monitor (returned by `getTrafficIncidents`)."),intervalMs:K.number().int().positive().optional().describe("Polling interval in milliseconds. Defaults to 60000 (60s).")}),Ko=K.union([K.object({incidentsEntryID:K.string(),wasRunning:K.boolean().describe("True when a monitor was actually stopped; false when it was already idle.")}),At]),Xo=K.object({incidentsEntryID:K.string().describe("Id of the incidents entry whose monitor should stop.")}),Zo=K.union([K.object({success:K.literal(!0),cleared:K.array(K.string())}),At]),Qo=["places","routes","geometries","analytics","incidents"],en=K.object({layers:K.array(K.enum(Qo)).optional().describe("Layers to clear (default: all). `analytics` covers traffic area analytics; `incidents` covers fetched traffic incidents.")}),tn=(K.union([K.object({success:K.literal(!0)}),At]),K.object({sectionType:K.enum(z).describe("e.g. country|traffic|motorway|toll"),id:K.string().describe("From a shown route section"),padding:K.number().optional().describe("pixels, default: 50")}),K.union([K.object({count:K.number(),trafficIncidents:K.array(K.object({id:K.string(),category:K.string(),magnitudeOfDelay:K.string().describe("unknown|minor|moderate|major|indefinite"),description:K.string().optional(),delayInSeconds:K.number().optional(),roadCategory:K.string().optional(),averageSpeedKmph:K.number().optional()}))}),At])),rn=K.object({}),sn=K.union([K.object({count:K.number().describe("Total feature count across all shown entries."),entries:K.array(K.object({id:K.string(),label:K.string(),featureCount:K.number(),markerType:K.enum(["pin","base-map"]).describe("How the entry is rendered on the map."),features:K.array(K.object({name:K.string().optional(),address:K.string().optional(),position:K.array(K.number()).describe("[lng, lat]")}))}))}),At]),on=K.object({}),nn=(K.union([K.object({sectionType:K.string(),routes:K.array(K.object({routeIndex:K.number().describe("0=primary, 1+=alternative"),sections:K.array(K.unknown()).describe("startPointIndex/endPointIndex omitted")}))}),At]),K.object({sectionType:K.enum(z).describe("carpool|carTrain|country|ferry|importantRoadStretch|lanes|leg|lowEmissionZone|motorway|pedestrian|roadShields|speedLimit|toll|tollVignette|traffic|tunnel|unpaved|urban|vehicleRestricted")}),K.union([K.object({count:K.number(),trafficIncidents:K.array(K.object({id:K.string(),magnitudeOfDelay:K.string().describe("unknown|minor|moderate|major|indefinite"),title:K.string().optional(),delayInSeconds:K.number().optional(),effectiveSpeedInKmh:K.number().optional(),categories:K.array(K.string()).optional()}))}),At])),an=K.object({}),ln=K.union([Ot,At]),cn=K.object({}),dn=K.union([K.object({count:K.number(),waypoints:K.array(K.object({name:K.unknown().optional(),position:K.array(K.number()).describe("[lng, lat]")}))}),At]),un=K.object({}),pn=K.union([K.object({styles:K.array(K.string()).readonly()}),At]),hn=K.object({}),mn=K.union([K.object({success:K.literal(!0),language:K.string()}),At]),yn=K.object({language:K.string().describe('e.g. "en-US", "fr-FR"')}),gn=K.union([K.object({success:K.literal(!0),style:K.string()}),At]),fn=K.object({style:K.enum(g).describe("Map visual theme")}),bn=K.union([K.object({success:K.literal(!0),theme:K.object({mainColor:K.string().optional()})}),At]),wn=K.object({mainColor:K.string().optional().describe('CSS color for the route line and waypoint icons. Prefer CSS named colors when they match (e.g. "peachpuff", "coral", "steelblue"), otherwise use hex (e.g. "#FF0000").')}),vn={},In=K.object({mode:K.enum(["hexgrid-3d","hexgrid-2d","square-3d","square-2d","heatmap"]).optional().describe("Visualization mode. 'hexgrid-3d' (default): 3D hexagons. 'hexgrid-2d': flat hexagons. 'square-3d': 3D squares. 'square-2d': flat squares. 'heatmap': density layer."),metric:K.enum(["congestionLevel","speed","travelTime"]).optional().describe("Metric to visualize. Defaults to first fetched metric."),colorTheme:K.enum(["trafficLight","heat","monochrome","viridis","plasma"]).optional().describe("Preset color theme. 'trafficLight' (green→amber→red, default), 'heat' (blue→orange→red), 'monochrome' (grey), 'viridis' (purple→green→yellow), 'plasma' (purple→magenta→yellow)."),heightScale:K.number().optional().describe("Extrusion height multiplier / visual maximum in meters. Default: metric-dependent."),scaleMode:K.enum(["raw","predefinedRange","currentRange"]).optional().describe("Height scaling mode. 'raw': metric value × scale (default). 'predefinedRange': normalize to SDK predefined range so max = scale. 'currentRange': normalize to live data range so max = scale."),filter:K.object({min:K.number().optional(),max:K.number().optional()}).refine(e=>void 0!==e.min||void 0!==e.max,{message:"Filter must have at least one of min or max"}).optional().describe("Filter visible tiles for the active metric by value range."),visible:K.boolean().optional().describe("Set to false to clear the visualization. Default: true."),fitBounds:K.boolean().optional().describe("Fit map viewport to the analytics area. Default: true.")}),Sn=K.union([K.object({success:K.literal(!0),mode:K.string(),metric:K.string(),colorTheme:K.string(),visible:K.boolean(),filtered:K.boolean()}),At]),xn=K.union([K.object({success:K.literal(!0),count:K.number()}),At]),Pn=K.object({}),Mn=K.union([K.object({success:K.literal(!0),visible:K.boolean(),layerGroups:K.array(K.string())}),At]),En=K.object({visible:K.boolean(),layerGroups:K.array(K.enum(m)).describe("The base map layer groups to show or hide.")}),_n=K.union([K.object({success:K.literal(!0),allMapPOIsVisible:K.boolean().optional(),filterCategories:K.object({show:K.enum(["only","all_except"]),values:K.array(K.string())}).optional(),reset:K.boolean().optional()}),At]),Tn=K.object({reset:K.boolean().optional().describe("Set to true to reset all POI configuration to defaults (visibility and category filters). When true, all other parameters are ignored."),allMapPOIsVisible:K.boolean().optional().describe("Set the overall visibility of all built-in map POI icons. True to show all POIs, false to hide all POIs."),filterCategories:K.object({show:K.enum(["only","all_except"]).describe('"only": show POIs only from the given categories. "all_except": show all POIs except those in the given categories.'),values:K.array(K.string()).describe('MapStylePOICategory or POICategoryGroup values (e.g. "RESTAURANT", "FOOD_DRINKS_GROUP"). Overly specific codes like "ITALIAN_RESTAURANT" are not supported.')}).optional().describe("Filter which built-in map POI categories are shown.")}),An=K.object({visible:K.boolean()}),Cn=K.union([K.object({success:K.literal(!0),trafficFlowVisible:K.boolean()}),At]),Rn=K.object({visible:K.boolean()}),Dn=K.union([K.object({success:K.literal(!0),trafficIncidentsVisible:K.boolean()}),At]),kn=K.object({id:K.string(),label:K.string(),featureCount:K.number(),markerType:K.enum(["pin","base-map","pin-clustered"]).describe("How the entry is rendered on the map.")}),On=K.union([K.object({success:K.literal(!0),totalShown:K.number(),shown:K.array(kn).describe("Entries currently on the map, with their labels."),actuallyAdded:K.array(K.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:K.array(K.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),geometriesRendered:K.number().optional().describe("Number of boundary polygons rendered when `geometry` was set."),geometriesSkipped:K.array(K.object({placeId:K.string(),reason:K.string()})).optional().describe("Places whose boundary could not be rendered (no geometry data source, fetch failure).")}),At]),jn=K.object({add:K.array(K.string()).optional().describe('Entry ids to show (e.g. ["places-1"]). Omit while still setting `markerType` or `geometry` to target the most recently stored entry.'),remove:K.array(K.string()).optional().describe("Entry ids to hide."),addMatching:K.array(K.string()).optional().describe("Category labels to show."),removeMatching:K.array(K.string()).optional().describe("Category labels to hide."),clear:K.boolean().optional().describe("Hide every displayed entry."),markerType:K.enum(["pin","base-map","pin-clustered"]).optional().describe('How newly-added entries are rendered as markers: "pin", "base-map", or "pin-clustered" (pins with a count badge where nearby ones aggregate). Defaults to "pin" unless `geometry` is set without an explicit `markerType`, in which case markers are skipped. Entries previously shown with another marker type are moved to this one.'),geometry:Ke.optional(),fitBounds:K.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.markerType||void 0!==e.geometry,{message:"updatePlacesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, markerType, geometry."}),Fn=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),s=/* @__PURE__ */new Set;for(const o of e.places.entries){const e=o.label.toLowerCase();r.some(t=>e.includes(t))&&s.add(o.id)}return[...s]},Ln=K.object({id:K.string(),label:K.string(),routeCount:K.number().describe("Number of route alternatives in the entry."),selectedIndex:K.number().describe("Highlighted alternative index (0 unless overridden by this call).")}),Nn=K.union([K.object({success:K.literal(!0),totalShown:K.number(),shown:K.array(Ln).describe("Routes entries currently on the map, with their labels."),actuallyAdded:K.array(K.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:K.array(K.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op.")}),At]),$n=K.object({add:K.array(K.string()).optional().describe('Entry ids to show (e.g. ["routes-1"]). Omit while still setting `selectedIndex` to target the most recently stored entry.'),remove:K.array(K.string()).optional().describe("Entry ids to hide."),addMatching:K.array(K.string()).optional().describe("Label substrings to show."),removeMatching:K.array(K.string()).optional().describe("Label substrings to hide."),clear:K.boolean().optional().describe("Hide every displayed entry."),selectedIndex:K.number().int().min(0).optional().describe("Which alternative variant to highlight on newly-added entries (default: 0). Use to switch the selected alternative on a multi-alternative result. Applied after the entry is shown — entries previously rendered with another index are re-rendered."),fitBounds:K.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.selectedIndex,{message:"updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex."}),qn=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),s=/* @__PURE__ */new Set;for(const o of e.routing.entries){const e=o.label.toLowerCase();r.some(t=>e.includes(t))&&s.add(o.id)}return[...s]},Bn={locatePlace:{description:"Resolve ONE named location (landmark, venue, city, neighborhood, address, POI) to a normalized place summary. Use this for any SINGLE named place — including requests for its boundary / polygon / outline / footprint (via `geometry`), pin or zoom display (via `show`), or staging as a routing waypoint. For multi-place or category searches, use discoverPlaces.",classificationPrompt:"Resolve a single location string (landmark, city, address) to a place without showing it; optionally stages a waypoint slot. Do NOT call as a precursor to discoverPlaces — discoverPlaces resolves named areas itself via `where.query` (or `where.municipalities` / `where.placeId`).",inputSchema:jr,outputSchema:Fr,execute:async(e,t)=>{const{query:r,queryAs:s,waypointIndex:o,where:n,show:a,entryId:i,geometry:l}=e;try{const e=await Lr(n,t);if("error"in e)return e;const c=await Or(r,s,e.bias);if(!c)return{error:`No result found for "${r}"`};const d=t.places.addPlaceResult(c,Pr(c,{query:r}),i);void 0!==o&&t.routing.setWaypointAt(o,c);const u=a?await(async(e,t,r,s)=>{const o=await dt(e,[r],{...s,zoomMode:"none"});if("auto"===s.zoomMode){if(t.bbox)e.baseMap.mapLibreMap.fitBounds(t.bbox);else{const r=$(t);r&&e.baseMap.mapLibreMap.flyTo({center:r,zoom:13})}o.zoomMode=!0}return o})(t,c,d,a):void 0;let p,h;if(l){const e=await ct(t,d,l);p=e.fetched>0,e.shown&&(h=!0)}return{..._r(c),...void 0!==o&&{waypointIndex:o},...u&&{shown:u},...void 0!==p&&{geometryFetched:p},...void 0!==h&&{geometryShown:h}}}catch(c){return{error:`Location resolution failed: ${c instanceof Error?c.message:String(c)}`}}},tags:["locate","place","location","waypoint"],examples:['locatePlace("Buckingham Palace", queryAs: "poi", where: { mode: "within", viewport: true }, waypointIndex: 0)','locatePlace("Tower Bridge", queryAs: "poi", where: { mode: "nearby", viewport: true }, waypointIndex: 1)','locatePlace("Westminster Abbey", queryAs: "poi", where: { mode: "global" })','locatePlace("10 Downing Street", queryAs: "place", where: { mode: "nearby", position: [4.9, 52.37] })','locatePlace("Amsterdam", queryAs: "place", where: { mode: "global" }, entryId: "amsterdam-city", geometry: { show: true }) // outline the city','locatePlace("Utrecht", queryAs: "place", where: { mode: "global" }, entryId: "utrecht-city", geometry: { show: true, mode: "add" }) // also outline a second city'],examplePrompts:["Where is the Louvre?","Pin Central Station on the map","What are the coordinates of Times Square?","Set my origin to Schiphol Airport","Outline Amsterdam on the map","Show the boundary of Berlin"],relatedTools:["setRoute","addWaypointsToRoute","getCurrentWaypoints","recallPlaces"]},reverseGeocode:{description:"Convert [lng, lat] to an address and place summary. Use after a click / getViewport / getCurrentLocation. Coordinates only — for text queries use locatePlace or discoverPlaces.",classificationPrompt:"Convert [lng, lat] coordinates to an address; use after map clicks or any tool that returns a position.",inputSchema:Ls,outputSchema:Fs,execute:async(e,t)=>{const{position:r}=e,s=r;try{const e=await E({position:s});return e?(t.places.addPlaceResult(e,Pr(e)),_r(e)):{error:"No result found for the given coordinates"}}catch(o){return{error:`Reverse geocoding failed: ${o instanceof Error?o.message:String(o)}`}}},tags:["location","place"],examples:["reverseGeocode({ position: [4.9, 52.4] })"],examplePrompts:["What's the address at this point?","Which city is [4.9, 52.4] in?","Name the place where I clicked"],relatedTools:["getViewport","getCurrentLocation","updatePlacesDisplay","locatePlace"]},discoverPlaces:e=>{return t=e.featureFlags??{},r=hs,{...r,description:os(t),inputSchema:ss(t),outputSchema:Xr,execute:ps(t)};var t,r},processPlaces:{description:"Dynamic JS over place entries: filter/merge/subset/re-order, draw connections between output places (pairings, stars, nearest-neighbour graphs), compute polygons (union, buffer, h3 coverage), and/or fit the camera. Code `(places, placesByEntry, geometries, h3, turf) => { places?, placeConnections?, geometries?, fitOnMap? }`. Sources untouched; `places` writes a NEW entry (optionally rendered via `show.places` / `show.geometries` / `connectMarkerType`); ONLY `fitOnMap` skips state. Set `withGeometries: true` to pre-fetch boundary polygons.",classificationPrompt:"Run dynamic JS over existing places to filter/merge/subset/re-order and/or compute connections between them. Code returns `{ places, placeConnections? }`.",inputSchema:oo,outputSchema:so,execute:async(e,t)=>{const{placesEntryIDs:r,code:s,label:o,show:n,connectMarkerType:a,entryId:i,withGeometries:l}=e,c=r?.length?r.map(e=>t.places.entries.find(t=>t.id===e)):[t.places.entries.at(-1)];for(let A=0;A<c.length;A++)if(!c[A]){const e=r?.[A];return{error:e?`No entry found with id "${e}". Use recallPlaces to list available IDs.`:"No places entries available to process. Run discoverPlaces first."}}const d=c,u={type:"FeatureCollection",features:d.flatMap(e=>e.places)},p=Object.fromEntries(d.map(e=>[e.id,{type:"FeatureCollection",features:e.places}]));let h=[];if(l)try{h=(await Promise.all(d.map(e=>t.places.fetchGeometriesForEntry(e.id)))).flat()}catch(T){return{error:`Failed to fetch input geometries: ${T instanceof Error?T.message:String(T)}`}}const m=await(async(e,t,r,s)=>{const o=await Le(e,["places","placesByEntry","geometries","h3","turf"],[t,r,s,Y,J],"Process");return"error"in o?{error:o.error}:no(o.value)?o.value:{error:"Process code must return `{ places?, placeConnections?, geometries?, fitOnMap? }`. At least one of `places`/`fitOnMap` required; `placeConnections` and `geometries` require `places`. Geometries must be Polygon or MultiPolygon GeoJSON Features. `fitOnMap`: `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`."}})(s,u,p,h);if("error"in m)return m;const{places:y,placeConnections:g,geometries:f,fitOnMap:b}=m;if(!y)return{fitted:fe(t,b)};const w=y.features.length,v=`${o?`processed: ${o}`:"processed"} (${w} ${1===w?"place":"places"})`;let I;if(i)I=i;else{const e=`${d.map(e=>e.id).join("+")}-process-`;I=`${e}${t.places.entries.filter(t=>t.id.startsWith(e)).length}`}const S=t.places.addPlaceResult(y,v,I,g,f),x={};if(n?.places&&(x.places=await dt(t,[S],n.places)),n?.geometries&&f?.length){const e=n.geometries.theme??"outline",r=n.geometries.mode??"replace";await t.places.showPlaceGeometries(f,e,r,S),x.geometries={count:f.length,theme:e,mode:r}}const P=x.places||x.geometries?x:void 0,M=t.places.entries.find(e=>e.id===S)?.label;let E;if(g){const e=await(async(e,t,r,s)=>{try{return await(await r.places.getEntryPlacesModule(e,s)).showConnections(t),t.length}catch(T){return{error:`Failed to render connections: ${T instanceof Error?T.message:String(T)}`}}})(S,g,t,a??"pin");if("object"==typeof e)return e;E=e}const _=b?fe(t,b):void 0;return{...Tr(y),placesEntryId:S,...M&&{label:M},...P&&{shown:P},...void 0!==E&&{connectionsRendered:E},...f?.length&&{geometryCount:f.length},..._&&{fitted:_}}},tags:["place","location","discover","connections"],examples:["processPlaces({ placesEntryIDs: [\"places-2\"], code: \"return { places: { type: 'FeatureCollection', features: places.features.filter(p => (p.properties.poi?.name ?? '').toLowerCase().includes('vegan')) } };\", label: \"vegan only\" })",'processPlaces({ placesEntryIDs: ["places-1"], code: "const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };" })','processPlaces({ placesEntryIDs: ["places-1"], code: "const f = places.features.filter(p => p.properties.poi?.categories?.includes(\'CAFE\')); return { places: { type: \'FeatureCollection\', features: f }, placeConnections: f.slice(1).map(p => ({ from: f[0], to: p })) };", show: { markerType: "pin", zoomMode: "auto" } })'],examplePrompts:["Keep only the vegan restaurants from the last search","Merge these two entries and drop non-POIs","Show these places in relation to each other","Draw lines from the charging station to every nearby cafe","Connect each hotel to its closest restaurant","Link every place in this set to the map center"],relatedTools:["discoverPlaces","recallPlaces","analysePlaces","updatePlacesDisplay"],dependsOn:["discoverPlaces","recallPlaces"]},processGeometries:{description:'Aggregate polygons (place footprints, isochrones, custom-entries) via JS — union, intersect, buffer, difference, h3 coverage, etc. — and/or fit the camera. Code `(geometries, h3, turf) => PolygonFeature[] | { geometries?, fitOnMap? }`. Returning `geometries` writes a NEW custom-geometries entry; `{ fitOnMap }` alone skips state. Each input feature is tagged with `properties._source: { kind, id }` so code can pick specific roles by source — e.g. for inverse/difference within a NAMED region, pass the region polygon (via locatePlace/discoverPlaces) AND the inner polygons in `geometriesEntryIDs`, then filter by `_source` in code. Set `show: { hidePreviousEntries: "all" }` for derived results that should replace their inputs (otherwise the new entry is stored but not rendered). Sources untouched.',classificationPrompt:"Aggregate polygons (place footprints, isochrones, custom-entries) via dynamic JS (h3, turf) — union/intersect/buffer/h3 coverage. Output writes a NEW custom-geometries entry.",inputSchema:eo,outputSchema:Zs,execute:async(e,t)=>{const{geometriesEntryIDs:r,code:s,label:o,operation:n,entryId:a,show:i}=e,l=await yt(r,t);if("error"in l)return{error:l.error};const{geometries:c,skipped:d,contributingSourceIds:u}=l.value,p=await Le(s,["geometries","h3","turf"],[c,Y,J],"Process");if("error"in p)return{error:p.error};const h=(e=>{if(Array.isArray(e)){if(!De(e))for(let t=0;t<e.length;t++)if(!Re(e[t]))return`Process code returned an invalid feature at index ${t}: must be a GeoJSON Feature with a Polygon or MultiPolygon geometry.`;return{geometries:e}}if(e&&"object"==typeof e){const t=e,r=void 0!==t.geometries,s=void 0!==t.fitOnMap;return r||s?r&&!De(t.geometries)?"`geometries` must be a `PolygonFeature[]` (each a GeoJSON Feature with a Polygon or MultiPolygon geometry).":s&&!ge(t.fitOnMap)?"`fitOnMap` must be a `[W,S,E,N]` BBox tuple or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`.":{geometries:r?t.geometries:[],...s&&{fitOnMap:t.fitOnMap}}:"Process code returned an empty object — set at least one of `geometries` or `fitOnMap`."}return"Process code must return either a `PolygonFeature[]` or an object `{ geometries?: PolygonFeature[], fitOnMap?: BBox | { bbox, padding?, animate? } }`."})(p.value);if("string"==typeof h)return{error:h};if(0===h.geometries.length&&h.fitOnMap)return{fitted:fe(t,h.fitOnMap)};const m=`processed geometries (${h.geometries.length})`,y=o?`processed: ${o}`:m,g=t.customGeometries.addEntry(h.geometries,{sourceIds:u,...n&&{operation:n}},y,a),f=h.fitOnMap?fe(t,h.fitOnMap):void 0;let b=!1;return i&&(await Pe(t.customGeometries,[g],i.hidePreviousEntries),await t.customGeometries.showEntry(g,i.theme??"outline",i.mode??"replace"),b=!0),{geometriesEntryId:{kind:"custom",id:g},label:y,geometryCount:h.geometries.length,sourceIds:u,...n&&{operation:n},...d.length&&{skipped:d},...f&&{fitted:f},...b&&{shown:b}}},tags:["geometry","boundary","aggregation","turf","h3"],examples:['processGeometries({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam-id" }, { kind: "place", id: "rotterdam-id" }], code: "return [turf.union(turf.featureCollection(geometries))].filter(Boolean);", label: "ams ∪ rdam", operation: "union" })','processGeometries({ geometriesEntryIDs: [{ kind: "place", id: "paris-id" }], code: "return [turf.buffer(geometries[0], 5, { units: \'kilometers\' })];", label: "paris +5km", operation: "buffer" })','processGeometries({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], code: "const out = []; for (let i=0;i<geometries.length;i++) for (let j=i+1;j<geometries.length;j++) { const x = turf.intersect(turf.featureCollection([geometries[i], geometries[j]])); if (x) out.push({ ...x, id: `x-${i}-${j}` }); } return out;", label: "pairwise overlaps" })','processGeometries({ geometriesEntryIDs: [{ kind: "ranges", id: "ranges-0" }, { kind: "places", id: "ev-stations" }], code: "const u = geometries.length ? turf.union(turf.featureCollection(geometries)) : null; return u ? [u] : [];", label: "isochrone ∪ ev coverage" })','processGeometries({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam-id" }, { kind: "ranges", id: "ranges-0" }], code: "const outer = geometries.filter(g => g.properties._source.kind === \'place\'); const inner = geometries.filter(g => g.properties._source.kind === \'ranges\'); const env = turf.union(turf.featureCollection(outer)); const u = turf.union(turf.featureCollection(inner)); const inv = (env && u) ? turf.difference(turf.featureCollection([env, u])) : env; return inv ? [{ ...inv, id: \'unreachable\' }] : [];", label: "amsterdam minus reachable", operation: "difference", show: { theme: "inverted", hidePreviousEntries: "all" } }) // areas in Amsterdam NOT reachable from the hospitals — fetch Amsterdam via locatePlace first, then filter by `_source` in code'],examplePrompts:["Union the boundaries of these cities","Buffer each neighbourhood by 500 m","Find the overlapping areas of these districts","Compute the h3 hex coverage of these places combined","Combine the reachable area with the EV stations into one zone","Show the areas of Amsterdam not reachable from the hospitals"],relatedTools:["recallGeometries","discoverPlaces","recallPlaces","analyseGeometries"],dependsOn:["discoverPlaces","locatePlace","findReachableAreas","recallGeometries"]},processRoutes:{description:"Dynamic JS over routes entries: derive a bbox via `routeUtils` (sections, progress, snap-to-line) and fit the camera. Read-only. Code `(routes, placesByEntry, routeUtils, h3, turf) => { fitOnMap }`. Pass `placesEntryIDs` to cross-reference with places.",classificationPrompt:"Dynamic JS over routes → bbox via `routeUtils` → fit camera. Read-only, returns `{ fitOnMap }`. Use when the user wants to focus on a specific PART of a route (worst traffic, toll section, km-10-to-20 segment).",inputSchema:lo,outputSchema:io,execute:async(e,t)=>{const{routesEntryIDs:r,placesEntryIDs:s,code:o}=e,n=r?.length?r.map(e=>t.routing.entries.find(t=>t.id===e)):[t.routing.entries.at(-1)];for(let u=0;u<n.length;u++)if(!n[u]){const e=r?.[u];return{error:e?`No entry found with id "${e}". Use recallRoutes to list available IDs.`:"No routes entries available to process. Run setRoute first."}}const a=n,i=gt(s,t);if("error"in i)return{error:i.error};const l=i.value,c=await Le(o,["routes","placesByEntry","routeUtils","h3","turf"],[{type:"FeatureCollection",features:a.flatMap(e=>e.data.features)},l,ao,Y,J],"Process");if("error"in c)return{error:c.error};const d=c.value;return(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return void 0!==t.fitOnMap&&ge(t.fitOnMap)})(d)?{fitted:fe(t,d.fitOnMap)}:{error:"Process code must return `{ fitOnMap: [W,S,E,N] | { bbox: [W,S,E,N], padding?, animate? } }`. Derive via `routeUtils.getSectionBBox(route, section)` or `turf.bbox(...)`."}},tags:["route","utilities"],examples:['processRoutes({ code: "const r = routes.features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };" })','processRoutes({ code: "const r = routes.features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };" })',"processRoutes({ code: \"const r = routes.features[0]; const a = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 10000 }); const b = routeUtils.getCoordinateAtRouteProgress(r, { traveledDistanceInMeters: 20000 }); if (!a || !b) return { fitOnMap: turf.bbox(r) }; const fc = { type: 'FeatureCollection', features: [turf.point(a.position), turf.point(b.position)] }; return { fitOnMap: { bbox: turf.bbox(fc), padding: 100 } };\" })","processRoutes({ placesEntryIDs: [\"ev-stations\"], code: \"const r = routes.features[0]; const ev = (placesByEntry?.['ev-stations']?.features ?? []).filter(p => turf.pointToLineDistance(p, r, { units: 'meters' }) <= 1000); if (ev.length === 0) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: { bbox: turf.bbox({ type: 'FeatureCollection', features: [...ev, r] }), padding: 80 } };\" })"],examplePrompts:["Zoom into the worst traffic on the route","Show me the toll section","Focus on the segment between km 10 and 20","Frame the part of the route inside France","Zoom to the first leg only","Focus on the route plus every EV station within 1 km of it"],relatedTools:["recallRoutes","analyseRoutes"],dependsOn:["setRoute","recallRoutes","recallPlaces"]},analyseGeometries:{description:"Aggregate polygons (place footprints, isochrones, custom-entries) via JS — areas, perimeters, hex coverage, bboxes, charts. Code `(geometries, h3, turf) => result`. Result attached as `_analysis[name]` on every contributing places/custom entry (ranges entries do not carry analyses). Read-only.",classificationPrompt:"Compute aggregate stats / charts over polygons (place footprints, isochrones, custom-entries) via dynamic JS (h3, turf). Result attaches to each contributing places-entry / custom-entry as `_analysis[name]`. `outputFormat`: `json` or `chart`.",inputSchema:qs,outputSchema:Ns,execute:async(e,t)=>{const{geometriesEntryIDs:r,name:s,description:o,code:n,outputFormat:a="json"}=e,i=await yt(r,t);if("error"in i)return{error:i.error};const{geometries:l,affectedEntries:c,skipped:d,contributingSourceIds:u}=i.value,p=await Le(n,["geometries","h3","turf"],[l,Y,J],"Analysis");if("error"in p)return{error:p.error};const h=je(p.value,a);if("error"in h)return{error:h.error};const m=h.value,y=Date.now();for(const g of c)("places"===g.kind?t.places:t.customGeometries).addAnalysisToEntry(g.id,{name:s,timestamp:y,...o&&{description:o},outputFormat:a,data:m});return{affectedEntries:c.map(e=>({id:{kind:e.kind,id:e.id}})),sourceIds:u,name:s,...o&&{description:o},outputFormat:a,analysis:m,...d.length&&{skipped:d}}},tags:["geometry","boundary","analysis","turf","h3"],examples:['analyseGeometries({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "areas", code: "return geometries.map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));" })','analyseGeometries({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam" }], name: "h3-coverage-8", code: "const cells = new Set(); for (const f of geometries) for (const c of h3.polygonToCells(f.geometry.coordinates, 8)) cells.add(c); return { resolution: 8, cellCount: cells.size };" })','analyseGeometries({ geometriesEntryIDs: [{ kind: "custom", id: "bakery-candidates-gap-oost" }], name: "candidate-area", code: "return { totalKm2: +(geometries.reduce((a,f)=>a+turf.area(f),0)/1e6).toFixed(2) };" })','analyseGeometries({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "area-bar", outputFormat: "chart", code: "return { type: \'bar\', data: { labels: geometries.map(f => f.id), datasets: [{ label: \'km²\', data: geometries.map(f => +(turf.area(f)/1e6).toFixed(2)) }] } };" })'],examplePrompts:["How big are these areas?","h3 hex coverage of this neighbourhood","Bar chart of city areas","Combined bbox of these places","How big is the candidate zone we just produced?"],relatedTools:["recallGeometries","discoverPlaces","recallPlaces","processGeometries"],dependsOn:["discoverPlaces","locatePlace","findReachableAreas","recallGeometries"]},analysePlaces:{description:'Aggregate existing places entries via dynamic JS: counts, groupings, densities, summaries, charts. Code: `(places, h3, turf) => result`; `outputFormat: "json"` (default, text) or `"chart"` (Chart.js config). Result is returned and attached as `_analysis[name]` on each source entry. Does NOT create a new entry or touch the map (use processPlaces for that).',classificationPrompt:"Aggregate / summarise / chart existing places via dynamic JS (h3, turf available). `outputFormat`: `json` (text) or `chart` (Chart.js).",inputSchema:zs,outputSchema:Ws,execute:async(e,t)=>{const{placesEntryIDs:r,name:s,description:o,code:n,outputFormat:a="json"}=e,i=r?.length?r.map(e=>t.places.entries.find(t=>t.id===e)):[t.places.entries.at(-1)];for(let h=0;h<i.length;h++)if(!i[h]){const e=r?.[h];return{error:e?`No entry found with id "${e}". Use recallPlaces to list available IDs.`:"No places entries available to analyse. Run discoverPlaces or processPlaces first."}}const l=i,c=await Le(n,["places","placesByEntry","h3","turf"],[{type:"FeatureCollection",features:l.flatMap(e=>e.places)},Object.fromEntries(l.map(e=>[e.id,{type:"FeatureCollection",features:e.places}])),Y,J],"Analysis");if("error"in c)return{error:c.error};const d=je(c.value,a);if("error"in d)return{error:d.error};const u=d.value,p=Date.now();for(const h of l)t.places.addAnalysisToEntry(h.id,{name:s,timestamp:p,...o&&{description:o},outputFormat:a,data:u});return{placesEntryIDs:l.map(e=>e.id),name:s,...o&&{description:o},outputFormat:a,analysis:u}},tags:["place","location","utilities"],examples:['analysePlaces({ placesEntryIDs: ["places-2"], name: "by-category", code: "const counts = {}; for (const p of places.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; return { total: places.features.length, byCategory: counts };" })','analysePlaces({ placesEntryIDs: ["places-1"], name: "hex-density-8", description: "h3 resolution 8 density", code: "const bins = {}; for (const p of places.features) { const [lng,lat] = p.geometry.coordinates; const cell = h3.latLngToCell(lat, lng, 8); bins[cell] = (bins[cell] ?? 0) + 1; } return { resolution: 8, bins };" })','analysePlaces({ placesEntryIDs: ["places-0", "places-1"], name: "extent", code: "return { bbox: turf.bbox(places), centroid: turf.centroid(places).geometry.coordinates };" })',"analysePlaces({ name: \"top-categories-bar\", outputFormat: \"chart\", code: \"const counts = {}; for (const p of places.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]).slice(0,10); return { type: 'bar', data: { labels: entries.map(e => e[0]), datasets: [{ label: 'Places', data: entries.map(e => e[1]) }] }, options: { plugins: { title: { display: true, text: 'Top categories' } } } };\" })",'analysePlaces({ name: "brand-share", outputFormat: "chart", code: "const counts = {}; for (const p of places.features) { const n = p.properties.poi?.brands?.[0]?.name ?? \'other\'; counts[n] = (counts[n] ?? 0) + 1; } return { type: \'doughnut\', data: { labels: Object.keys(counts), datasets: [{ data: Object.values(counts) }] } };" })'],examplePrompts:["How many of each POI category are in these results?","h3 hex density of these places at resolution 8","Bounding box and centroid of these places","Bar chart of the top POI categories","Pie chart of the brand distribution","Which municipality has the most of these places?"],relatedTools:["discoverPlaces","recallPlaces","processPlaces"],dependsOn:["discoverPlaces","recallPlaces"]},analyseRoutes:{description:'Aggregate routes entries via dynamic JS — totals, per-section breakdowns, traffic delays, charts; cross-references places entries when `placesEntryIDs` is set (POIs near route, hex co-occurrence). Code: `(routes, h3, turf, placesByEntry?) => result`; `outputFormat: "json"` (default) or `"chart"`. Result attached as `_analysis[name]` on each source routes entry. Read-only.',classificationPrompt:"Aggregate / summarise / chart existing routes via dynamic JS (h3, turf). Optionally pass `placesEntryIDs` to cross-reference routes with places (POIs near route, per leg/country). `outputFormat`: `json` or `chart`.",inputSchema:Hs,outputSchema:Vs,execute:async(e,t)=>{const{routesEntryIDs:r,placesEntryIDs:s,name:o,description:n,code:a,outputFormat:i="json"}=e,l=r?.length?r.map(e=>t.routing.entries.find(t=>t.id===e)):[t.routing.entries.at(-1)];for(let g=0;g<l.length;g++)if(!l[g]){const e=r?.[g];return{error:e?`No entry found with id "${e}". Use recallRoutes to list available IDs.`:"No routes entries available to analyse. Run setRoute first."}}const c=l,d=gt(s,t);if("error"in d)return{error:d.error};const u=d.value,p=await Le(a,["routes","h3","turf","placesByEntry"],[{type:"FeatureCollection",features:c.flatMap(e=>e.data.features)},Y,J,u],"Analysis");if("error"in p)return{error:p.error};const h=je(p.value,i);if("error"in h)return{error:h.error};const m=h.value,y=Date.now();for(const g of c)t.routing.addAnalysisToEntry(g.id,{name:o,timestamp:y,...n&&{description:n},outputFormat:i,data:m});return{routesEntryIDs:c.map(e=>e.id),name:o,...n&&{description:n},outputFormat:i,analysis:m}},tags:["route","utilities"],examples:['analyseRoutes({ name: "summary", code: "return routes.features.map(r => ({ index: r.properties.index, distanceKm: r.properties.summary.lengthInMeters / 1000, durationMin: r.properties.summary.travelTimeInSeconds / 60, trafficDelaySec: r.properties.summary.trafficDelayInSeconds }));" })','analyseRoutes({ name: "delay-by-country", code: "const byCountry = {}; for (const r of routes.features) for (const c of (r.properties.sections.country ?? [])) byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (r.properties.sections.traffic ?? []).filter(t => t.startPointIndex >= c.startPointIndex && t.endPointIndex <= c.endPointIndex).reduce((s, t) => s + (t.delayInSeconds ?? 0), 0); return byCountry;" })',"analyseRoutes({ name: \"alternative-comparison\", outputFormat: \"chart\", code: \"const r = routes.features; return { type: 'bar', data: { labels: r.map(x => `Route ${x.properties.index}`), datasets: [{ label: 'Travel time (min)', data: r.map(x => Math.round(x.properties.summary.travelTimeInSeconds / 60)) }, { label: 'Distance (km)', data: r.map(x => Math.round(x.properties.summary.lengthInMeters / 1000)) }] } };\" })",'analyseRoutes({ routesEntryIDs: ["routes-0"], name: "hex-coverage-7", code: "const cells = new Set(); for (const r of routes.features) for (const [lng, lat] of r.geometry.coordinates) cells.add(h3.latLngToCell(lat, lng, 7)); return { resolution: 7, cellCount: cells.size, cells: [...cells] };" })','analyseRoutes({ placesEntryIDs: ["ev-stations"], name: "ev-near-route-500m", code: "const route = routes.features[0]; const out = {}; for (const [id, places] of Object.entries(placesByEntry)) out[id] = places.features.filter(p => turf.pointToLineDistance(p, route, { units: \'meters\' }) <= 500).length; return { withinMeters: 500, byEntry: out };" })','analyseRoutes({ placesEntryIDs: ["cafes", "hotels"], name: "stops-per-leg", code: "const route = routes.features[0]; const corridor = turf.buffer(route, 0.3, { units: \'kilometers\' }); const legs = route.properties.sections.leg; return Object.fromEntries(Object.entries(placesByEntry).map(([id, fc]) => [id, legs.map((leg, i) => ({ leg: i, count: fc.features.filter(p => leg.startPointIndex != null && turf.booleanPointInPolygon(p, corridor)).length }))])); " })'],examplePrompts:["How long is each route alternative?","Compare the travel times of these route options","Bar chart of distance and time per alternative","Total traffic delay grouped by country","h3 hex coverage of the route at resolution 7","How many EV stations are within 500 m of the route?","Count cafes and hotels per leg of the route","Which POIs from the search are close to the route?"],relatedTools:["recallRoutes","recallPlaces","setRoute","processRoutes","discoverPlaces"],dependsOn:["setRoute","recallRoutes","recallPlaces"]},setRoute:{description:"Calculate or recalculate a route. Provide `locations` to set/replace origin/stops/destination, `parameters` to update cost model / alternatives / when, or both in a single call. At least one of `locations` or `parameters` MUST be provided. If only `parameters` is given and waypoints already exist, recalculates with the new options; if no waypoints exist yet, parameters are stored and no recalculation runs. Car/driving only. Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallRoutes / addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / getRouteProgress instead.",classificationPrompt:"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.",inputSchema:Ur,outputSchema:Gr,execute:async(e,t)=>{const{locations:r,parameters:s,showOnMap:o,hidePreviousEntries:n}=e;try{let e;if(s&&t.routing.setParams(s),r){const s=await Promise.all(r.map(e=>Nr(e,t))),o=[];for(let e=0;e<r.length;e++){if(null!==s[e])continue;const t=r[e];"query"in t?o.push(`"${t.query}"`):"placeId"in t&&o.push(`placeId "${t.placeId}"`)}if(o.length>0)return{error:`Could not resolve: ${o.join(", ")}`};if(e=Wr(s.map(e=>e?.place??null)),!e)return{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}else if(e=Wr(t.routing.planningSlots),!e)return{success:!0};return Vr(t,e,o,n)}catch(a){return{error:`Route calculation failed: ${a instanceof Error?a.message:String(a)}`}}},tags:["route","waypoint","location"],examples:['setRoute({ locations: [{ query: "Amsterdam" }, { query: "Brussels" }] })','setRoute({ locations: [{ query: "Eiffel Tower" }, { query: "Louvre Museum" }, { query: "Notre Dame" }] })',"setRoute({ parameters: { maxAlternatives: 2 } })",'setRoute({ parameters: { costModel: { routeType: "fast", avoid: ["tollRoads"] } } })','setRoute({ parameters: { when: { option: "departAt", date: "2025-06-01T09:00:00Z" } } })','setRoute({ locations: [{ query: "Paris" }, { query: "Lyon" }], parameters: { costModel: { routeType: "short" } } })'],examplePrompts:["Get directions from Paris to Lyon","Route me from Amsterdam to Brussels","Plan a new trip to the airport","Change my origin to Schiphol","Re-route from a different starting point","Avoid toll roads","Find the fastest route","Show 2 alternative routes","Depart at 9am"],relatedTools:["updateRoutesDisplay","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","recallRoutes"]},addWaypointsToRoute:{description:"Extend the current route — prepend a new origin in front of the existing one, append a new destination after the existing one, and/or insert intermediate stops at optimal positions along the existing route geometry. Existing origin/destination are preserved as intermediate stops, never replaced. Always recalculates. At least one of `origin`, `destination`, or `stops` MUST be provided. Requires an existing route. To outright replace waypoints, use setRoute instead.",classificationPrompt:"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.",inputSchema:Jr,outputSchema:Hr,execute:async(e,t)=>{const{origin:r,destination:s,stops:o,showOnMap:n,hidePreviousEntries:a}=e;try{const e=await(async e=>{const t=e.routing.currentEntryModule;if(!t)return"No existing route on the map. Use calculate-route or updateRoutesDisplay first.";const r=t.getShown(),s=r?.mainLines,o=r?.waypoints;return s&&s.features.length?!o||o.features.length<2?"No existing waypoints available. Use calculate-route first to create a route with waypoints.":{shownRouteLines:s,shownWaypoints:o}:"No existing route available. Use calculate-route first."})(t);if("string"==typeof e)return{error:e};const{shownRouteLines:i,shownWaypoints:l}=e,c=await(async(e,t)=>{const{origin:r,destination:s,stops:o=[]}=e,n=[...r?[{kind:"origin",location:r}]:[],...s?[{kind:"destination",location:s}]:[],...o.map(e=>({kind:"stop",location:e}))],a=await Promise.all(n.map(({location:e})=>Nr(e,t))),i=[];for(let c=0;c<n.length;c++)null===a[c]&&i.push(Yr(n[c].location));if(i.length>0)return{error:`Could not resolve: ${i.join(", ")}`};const l={stops:[]};for(let c=0;c<n.length;c++){const e=a[c]?.place;"origin"===n[c].kind?l.origin=e:"destination"===n[c].kind?l.destination=e:l.stops.push(e)}return l})({origin:r,destination:s,stops:o},t);if("error"in c)return c;const d=[...c.origin?[c.origin]:[],...l.features,...c.destination?[c.destination]:[]],u=Wr(c.stops.length>0?H(i.features[0],d,c.stops):d);return u?Vr(t,u,n,a):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(i){return{error:`Failed to add waypoints to route: ${i instanceof Error?i.message:String(i)}`}}},tags:["route","waypoint"],examples:['addWaypointsToRoute({ stops: [{ query: "Westminster Abbey" }] }) // single intermediate stop','addWaypointsToRoute({ stops: [{ query: "Reims" }, { query: "Metz" }] }) // multiple stops, settle into along-route order','addWaypointsToRoute({ origin: { query: "my house" } }) // prepend new origin; previous origin becomes a stop','addWaypointsToRoute({ destination: { query: "Heathrow Airport" } }) // append new destination; previous destination becomes a stop','addWaypointsToRoute({ origin: { query: "Schiphol" }, destination: { query: "Brussels" } }) // extend on both ends','addWaypointsToRoute({ origin: { query: "X" }, stops: [{ query: "Y" }] }) // combine endpoint extension and stops'],examplePrompts:["Add a stop at Westminster Abbey","Add stops at Reims and Metz on the way","Pick me up from my house first, then continue the trip","Continue past the destination to Heathrow"],relatedTools:["locatePlace","setRoute","updateRoutesDisplay","removeWaypointsFromRoute","replaceWaypointInRoute"],dependsOn:["setRoute"]},removeWaypointsFromRoute:{description:"Remove one or more waypoints from the current route by index; always recalculates. Index 0 = origin, last = destination (both protected when removing them would leave fewer than 2). Requires an existing route.",classificationPrompt:"Remove one or more waypoints by index from the current route and recalculate.",inputSchema:Rs,outputSchema:Cs,execute:async(e,t)=>{const{waypointIndices:r,showOnMap:s,hidePreviousEntries:o}=e;try{const e=((e,t)=>{if(!e||e.length<2)return"No existing waypoints available. Use calculate-route first to create a route with waypoints.";const r=new Set(t);if(r.size!==t.length)return"Duplicate waypoint indices are not allowed.";for(const s of t)if(s<0||s>=e.length)return`Invalid waypoint index ${s}. Valid range is 0 to ${e.length-1}.`;return e.length-t.length<2?"Cannot remove waypoints: at least 2 waypoints (origin and destination) must remain.":e.filter((e,t)=>!r.has(t))})(t.routing.currentWaypoints,r);if("string"==typeof e)return{error:e};const n=Wr(e);return n?Vr(t,n,s,o):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(n){return{error:`Failed to remove waypoints from route: ${n instanceof Error?n.message:String(n)}`}}},tags:["route","waypoint"],examples:["removeWaypointsFromRoute({ waypointIndices: [1] }) // remove a single stop","removeWaypointsFromRoute({ waypointIndices: [1, 2] }) // remove multiple stops in one call"],examplePrompts:["Remove the second stop from my route","Delete the Utrecht stop","Drop the last two intermediate stops"],relatedTools:["addWaypointsToRoute","replaceWaypointInRoute","setRoute","updateRoutesDisplay"],dependsOn:["setRoute"]},replaceWaypointInRoute:{description:"Replace a single waypoint in the current route in place — origin, destination, or any intermediate stop by 0-based index. Always recalculates. Other waypoints are preserved. Requires an existing route. Use addWaypointsToRoute to extend the route, removeWaypointsFromRoute to drop a waypoint, or setRoute to fully replace all waypoints.",classificationPrompt:"Overwrite a single waypoint of the current route in place — by 'origin', 'destination', or numeric index — and recalculate. Other waypoints are kept.",inputSchema:Os,outputSchema:Ds,execute:async(e,t)=>{const{waypointIndex:r,location:s,showOnMap:o,hidePreviousEntries:n}=e;try{const e=t.routing.currentWaypoints;if(!e||e.length<2)return{error:"No existing waypoints available. Use calculate-route first to create a route with waypoints."};const l=(a=r,i=e.length,"origin"===a?0:"destination"===a?i-1:a<0||a>=i?`Invalid waypoint index ${a}. Valid range is 0 to ${i-1}.`:a);if("string"==typeof l)return{error:l};const c=await Nr(s,t);if(!c)return{error:`Could not resolve location: ${js(s)}`};const d=[...e];d[l]=c.place;const u=Wr(d);return u?Vr(t,u,o,n):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(l){return{error:`Failed to replace waypoint in route: ${l instanceof Error?l.message:String(l)}`}}var a,i},tags:["route","waypoint"],examples:['replaceWaypointInRoute({ waypointIndex: "origin", location: { query: "Schiphol" } })','replaceWaypointInRoute({ waypointIndex: "destination", location: { query: "Heathrow" } })','replaceWaypointInRoute({ waypointIndex: 1, location: { query: "Utrecht" } }) // overwrite the first intermediate stop'],examplePrompts:["Change the origin to Schiphol","Make Heathrow my destination","Replace the second stop with Utrecht"],relatedTools:["addWaypointsToRoute","removeWaypointsFromRoute","setRoute","locatePlace"],dependsOn:["setRoute"]},getPOICategoryCodes:{description:'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.',classificationPrompt:'Resolve natural-language category names (e.g. "gym", "italian food") to POICategory codes for discoverPlaces.',inputSchema:Is,outputSchema:vs,execute:async(e,t)=>{const{filters:r,language:s}=e;try{const e=await M({filters:r,language:s});return{count:e.length,codes:e}}catch(o){return{error:`Failed to fetch POI category codes: ${o instanceof Error?o.message:String(o)}`}}},tags:["discover","place"],examples:["getPOICategoryCodes()",'getPOICategoryCodes(filters: ["gym"])','getPOICategoryCodes(filters: ["italian restaurant"]) // multi-word phrase, spaces removed before matching','getPOICategoryCodes(filters: ["restaurant"], language: "fr-FR")'],examplePrompts:["What POI categories are available?","What's the code for gym?"],relatedTools:["discoverPlaces","togglePOIs"]},getTrafficIncidents:{description:'LOADER. Fetches traffic incidents (present or future) into a cached entry, renders them on the map, returns `{ count, entryId, entries }` (`entries` lists every cached entry so the agent can pick a follow-up target). No per-incident details, no aggregation — those belong to analyseIncidents/focusIncidents. \n\nAREA: omit `bbox` to use the current map viewport ("the network / right now / this area / here"). Pass explicit `bbox` only when the user names an area not already on screen. \n\nCALL ONCE PER AREA. Each call writes a new entry; re-calling burns steps. Pick the right follow-up instead: analyseIncidents (counts, top-N, charts, clusters, trend via `previous`); focusIncidents (highlight a subset); startTrafficIncidentsMonitor (keep the entry fresh in the background). \n\n`silent: true` suppresses rendering (intermediate calcs only); `label` names the snapshot in the UI.',classificationPrompt:"Loader for traffic incidents — caches features in state, renders on the map, returns `{count, entryId}` only. MANDATORY: always also pick startTrafficIncidentsMonitor (no exceptions — they are a pair, data goes stale without it). Also co-pick analyseIncidents (counts / charts / top-N / clusters) and focusIncidents (highlight a subset) when the query implies them.",inputSchema:As,outputSchema:Ts,execute:async(e,t)=>{const{bbox:r,categoryFilter:s,timeValidityFilter:o,label:n,silent:a=!1,fitBounds:i=!0}=e;let l=r;if(!l)try{l=t.baseMap.ttMap.getBBox()}catch(d){return{error:`No bbox provided and viewport fallback failed: ${d instanceof Error?d.message:String(d)}`}}const c={...s&&{categoryFilter:s},...o&&{timeValidityFilter:o}};try{const e=await R({...c,bbox:l});if(!e.features.length)return{count:0,entries:_s(t)};const r={bbox:l,...c},s=t.trafficIncidents.addIncidentsEntry(e.features,r,n??function(e){const[t,r,s,o]=e.bbox,n=`bbox ${t.toFixed(2)},${r.toFixed(2)} → ${s.toFixed(2)},${o.toFixed(2)}`,a=[];return e.categoryFilter?.length&&a.push(`filtered: ${e.categoryFilter.join(",")}`),e.timeValidityFilter?.length&&a.push(`when: ${e.timeValidityFilter.join(",")}`),a.length?`${n} — ${a.join("; ")}`:n}(r),Date.now());return a||await t.trafficIncidents.showEntry(s,i),{count:e.features.length,entryId:s,entries:_s(t)}}catch(d){return{error:`Failed to get traffic incidents: ${d instanceof Error?d.message:String(d)}`}}},tags:["traffic","location"],examples:["getTrafficIncidents({ bbox: [4.728, 52.278, 5.080, 52.479] })",'getTrafficIncidents({ bbox: [...], categoryFilter: ["accident", "jam"] })','getTrafficIncidents({ bbox: [...], categoryFilter: ["roadworks"], timeValidityFilter: ["future"] })'],examplePrompts:["Fetch traffic incidents in Amsterdam","Any accidents in this area?"],relatedTools:["calculateBBox","getViewport","analyseIncidents","focusIncidents","startTrafficIncidentsMonitor"]},analyseIncidents:{description:'Aggregate an incidents entry via dynamic JS: counts, top-N, group-bys, hex density, charts, spatial clusters. Registered against ONE entry — re-runs on every monitor-tick of that entry, with `previous` carrying the prior result for trend reads. Reusing a name replaces the spec. Does NOT create new entries. `code` is an async function BODY (not a function expression) that uses `incidents`, `h3`, `turf`, `now`, `log`, `previous` and ends with `return result;`. `outputFormat: "json"` (default) or `"chart"`. FOCUS SIDE-EFFECT: when the result has `focusIds: string[]` (+ optional `focusReason`), setFocus is called once on the source entry — monitor-tick replays do NOT re-apply it. For pure focus, use focusIncidents.',classificationPrompt:"Aggregate / chart / cluster an incidents entry via dynamic JS (h3, turf). Re-runs on every monitor-tick; `previous` carries the prior result for trend reads. Pick for counts, group-bys, top-N, charts, density, clusters.",inputSchema:Gs,outputSchema:Bs,execute:async(e,t)=>{const{incidentsEntryID:r,name:s,description:o,code:n,outputFormat:a="json"}=e,i=t.trafficIncidents.entries,l=r?i.find(e=>e.id===r):i[i.length-1];if(!l)return{error:r?`No incidents entry with id "${r}". Call getTrafficIncidents first.`:"No incidents entries available to analyse. Call getTrafficIncidents first."};const c={name:s,...o&&{description:o},outputFormat:a,code:n,source:l.id},d=l._analyses?.getResult(s)?.data;t.trafficIncidents.setAnalysisSpec(c);const u=l.timestamp,p=await wt(c,l.data,d,u);if("error"in p)return{error:p.error};const{value:h}=p;t.trafficIncidents.addAnalysisToEntry(l.id,{name:s,timestamp:u,...o&&{description:o},outputFormat:a,data:h});const m=Us(h,l.id,t);return{incidentsEntryID:l.id,name:s,...o&&{description:o},outputFormat:a,analysis:h,...m&&{focused:m}}},tags:["traffic","incident","analytics","state"],examples:["analyseIncidents({ name: 'by-category', code: \"const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; return { total: incidents.length, byCategory: c };\" })","analyseIncidents({ name: 'top-delays', code: \"return [...incidents].sort((a,b) => (b.properties.delayInSeconds ?? 0) - (a.properties.delayInSeconds ?? 0)).slice(0,5).map(i => ({ id: i.properties.id, delay: i.properties.delayInSeconds, road: i.properties.roadNumbers?.[0] }));\" })","analyseIncidents({ name: 'category-bar', outputFormat: 'chart', code: \"const c = {}; for (const i of incidents) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; const e = Object.entries(c).sort((a,b) => b[1]-a[1]); return { type: 'bar', data: { labels: e.map(x => x[0]), datasets: [{ label: 'Incidents', data: e.map(x => x[1]) }] } };\" })"],examplePrompts:["How many incidents of each category?","Top 5 incidents by delay","Total delay grouped by road","Which jams are growing vs fading on this monitored area?","Bar chart of incidents per category","Hex density of incidents at resolution 8"],relatedTools:["getTrafficIncidents","focusIncidents"],dependsOn:["getTrafficIncidents"]},focusIncidents:{description:"Highlight a subset of incidents on an existing entry. Drives the dim/highlight on the map and the FocusChip in the UI. Compose with `analyseIncidents` to compute the ids first (e.g. top-N by delay, members of a cluster). Unknown ids are dropped silently and returned in `droppedIds`. Errors only when the entry is unknown or NONE of the ids resolve.",classificationPrompt:'Highlight a subset of incidents on an existing entry (drives map dim/highlight + FocusChip). Pick for "focus on…", "show only…", "highlight the…". Compose with analyseIncidents to compute the ids.',inputSchema:Ys,outputSchema:Js,execute:async(e,t)=>{const{incidentsEntryID:r,incidentIds:s,reason:o}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const n=t.trafficIncidents.setFocus(r,s,o);return 0===n.focusedCount?{error:`None of the ${s.length} requested ids exist on entry "${r}". The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.`}:{incidentsEntryID:r,focusedCount:n.focusedCount,droppedIds:n.droppedIds,...o&&{reason:o}}},tags:["traffic","incident","state","ui"],examples:["focusIncidents({ incidentsEntryID: 'incidents-0', incidentIds: ['TTI-...', 'TTI-...'], reason: 'top 3 by delay' })"],examplePrompts:["Focus on the 3 worst incidents by delay","Highlight the closures","Show only the incidents on the A4"],relatedTools:["analyseIncidents","getTrafficIncidents"],dependsOn:["getTrafficIncidents"]},startTrafficIncidentsMonitor:{description:"Start a background monitor for an existing incidents entry. Re-fetches the entry's bbox + filters every interval (default 60s) and replaces its data; any registered analyses re-run on each tick. The first tick fires immediately, so this duplicates the most recent getTrafficIncidents fetch — only call it when the user wants the entry to stay current. \n\nIdempotent on a running monitor: a second call is a no-op and returns alreadyRunning: true. A new intervalMs is NOT applied to a running monitor — stop first, then start again to change settings.",classificationPrompt:'Polls an existing incidents entry on a fixed interval (default 60s); registered analyses re-run on each tick. ALWAYS pick this whenever getTrafficIncidents is picked — they are a mandatory pair, no exceptions. Without it the loaded data is a frozen snapshot. Also pick on its own when the user says "live"/"watch"/"keep updated"/"refresh every N seconds" against an already-loaded entry.',inputSchema:Yo,outputSchema:Jo,execute:async(e,t)=>{const{incidentsEntryID:r,intervalMs:s}=e,o=t.trafficIncidents.entries.find(e=>e.id===r);if(!o)return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const n=t.trafficIncidents.isMonitored(r),{bbox:a,categoryFilter:i,timeValidityFilter:l}=o.params,c={...i&&{categoryFilter:i},...l&&{timeValidityFilter:l}};return t.trafficIncidents.startMonitoring(r,{bbox:a,capturedAt:o.timestamp,label:o.label},{fetchIncidents:async e=>(await R({...c,bbox:e})).features},s?{intervalMs:s}:void 0),{incidentsEntryID:r,alreadyRunning:n}},tags:["traffic","incident","state"],examples:["startTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0' })","startTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', intervalMs: 30000 })"],examplePrompts:["Keep this updated","Refresh the incidents every 30 seconds","Watch this area for new accidents"],relatedTools:["getTrafficIncidents","stopTrafficIncidentsMonitor","analyseIncidents"],dependsOn:["getTrafficIncidents"]},stopTrafficIncidentsMonitor:{description:"Stop the background monitor for an incidents entry. The entry itself stays in state with its last data — use removeEntry if you also want to drop the entry. No-op when no monitor is running for the entry.",classificationPrompt:'Stop polling an incidents entry; the entry stays in state with its last data. Pick for "stop refreshing"/"pause updates" or when ending a watch.',inputSchema:Xo,outputSchema:Ko,execute:async(e,t)=>{const{incidentsEntryID:r}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}".`};const s=t.trafficIncidents.isMonitored(r);return t.trafficIncidents.stopMonitoring(r),{incidentsEntryID:r,wasRunning:s}},tags:["traffic","incident","state"],examples:["stopTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0' })"],examplePrompts:["Stop refreshing","Pause updates for this area"],relatedTools:["startTrafficIncidentsMonitor","getTrafficIncidents"],dependsOn:["startTrafficIncidentsMonitor"]},getTrafficAreaAnalytics:{description:"Fetch historical traffic analytics for an area (≤31 days) via location (named city/region) or bbox. Fetch ALL metrics in ONE call (visualization needs them together); use city-scale areas (~10 km+) for meaningful data. Returns period averages — `showOnMap` visualizes immediately; queryTrafficAnalytics for breakdowns.",classificationPrompt:"Fetch historical traffic analytics (speed, congestion, travel time) for an area. queryTrafficAnalytics drills down without re-fetching.",inputSchema:Ps,outputSchema:xs,execute:async(e,t)=>{const{location:r,bbox:s,showOnMap:o,startDate:n,endDate:a,days:i,metrics:l,functionalRoadClasses:c,hours:d}=e;if(!r&&!s)return{error:"Provide a location or bbox to define the analytics area."};const u=process.env.MOVE_PORTAL_KEY;if(!u)return{error:"MOVE_PORTAL_KEY environment variable is not set."};try{const e=await(async(e,t,r)=>{if(e){const t=await Nr(e,r);if(!t)return{error:"Could not resolve the provided location."};if(Array.isArray(t.place))return{error:"A bare position cannot define an analytics area. Provide a named location query instead."};const s=(await P({geometries:[t.place]}))?.features?.[0]?.geometry;return s||{error:`No boundary polygon found for "${t.name}". Try a bbox instead.`}}return t?(e=>{const[t,r,s,o]=e;return{type:"Polygon",coordinates:[[[t,r],[s,r],[s,o],[t,o],[t,r]]]}})(t):{error:"No area provided."}})(r,s,t);if("error"in e)return e;const p=((e,t,r)=>e&&e.length>0?{days:e}:t?r?{startDate:t,endDate:r}:{startDate:t}:{})(i,n,a),h=await C({apiKey:u,...p,metrics:l,functionalRoadClasses:c??"all",hours:d??"all",geometry:e});return t.trafficAreaAnalytics.setLastAreaAnalytics(h),o&&await(async(e,t)=>{const r=await e.trafficAreaAnalytics.getTrafficAreaAnalyticsModule();await r.clear(),await r.show(t);const s=t.properties?.metrics?.[0]??"congestionLevel";r.setMetric(s);const o=O(t.features);o&&e.baseMap.mapLibreMap.fitBounds(o,{padding:50,pitch:45}),e.trafficAreaAnalytics.notifyAnalyticsShown(t,r)})(t,h),((e,t,r)=>{const s=e.features[0]?.properties;if(!s)return{dateRange:{start:"",end:""},baseData:{},metrics:[],tileCount:0,availableGranularities:[]};const o=t??Es(e.properties?.startDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-3),Ms(e)})(),n=r??Es(e.properties?.endDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-2),Ms(e)})(),a=[];return s.timedData?.yearly?.length&&a.push("yearly"),s.timedData?.monthly?.length&&a.push("monthly"),s.timedData?.weekly?.length&&a.push("weekly"),s.timedData?.daily?.length&&a.push("daily"),s.timedData?.hourly?.length&&a.push("hourly"),s.timedData?.average?.length&&a.push("average"),{...s.name&&{name:s.name},...s.timezone&&{timezone:s.timezone},dateRange:{start:o,end:n},baseData:s.baseData,metrics:e.properties?.metrics??[],tileCount:s.tiledData?.tiles?.length??0,availableGranularities:a}})(h,p.startDate,p.endDate)}catch(p){return{error:`Failed to get traffic area analytics: ${p instanceof Error?p.message:String(p)}`}}},tags:["traffic","location"],examples:["getTrafficAreaAnalytics({ bbox: [4.728, 52.278, 5.080, 52.479], metrics: ['speed', 'congestionLevel'], startDate: '2025-03-01', endDate: '2025-03-07' })","getTrafficAreaAnalytics({ bbox: [...], metrics: ['congestionLevel'], hours: [7,8,9,17,18] })"],examplePrompts:["What was the average congestion in Amsterdam last week?","Analyze traffic patterns during rush hour in this area","Show me historical speed data for the city center"],relatedTools:["queryTrafficAnalytics","showTrafficAreaAnalytics","calculateBBox","getViewport"],dependsOn:["locatePlace"]},queryTrafficAnalytics:{description:'Query cached traffic analytics (from getTrafficAreaAnalytics) without re-fetching — filter by granularity, date/hour range, day, metric. Returns filtered entries plus the current map visualization state (metric, mode). Use for breakdowns and "what is shown" follow-ups.',classificationPrompt:"Query cached traffic analytics data or check what metric/mode is currently displayed on the map.",inputSchema:uo,outputSchema:po,execute:async(e,t)=>{const{granularity:r="daily",metric:s,startDate:o,endDate:n,hourStart:a,hourEnd:i,dayOfWeek:l}=e,c=t.trafficAreaAnalytics.lastAreaAnalytics;if(!c)return{error:"No analytics data cached. Call getTrafficAreaAnalytics first."};const d=c.features[0]?.properties;if(!d)return{error:"Analytics result has no region data."};const u=d.timedData,p=u?.[r];if(!p||!Array.isArray(p)||0===p.length)return{error:`No '${r}' data available. Available: ${Object.keys(u??{}).join(", ")}`};const h=Es(c.properties?.startDate);let m=p.map((e,t)=>{const o={};if("daily"===r||"hourly"===r){const s=((e,t,r,s)=>{const o=Es(e);if(o)return o;if(s&&t>=0){const e=new Date(s);if(!Number.isNaN(e.getTime())){const s="hourly"===r?Math.floor(t/24):t;return e.setDate(e.getDate()+s),Ms(e)}}})(e.date,t,r,h);s&&(o.date=s)}if(void 0!==e.hour&&(o.hour=e.hour),void 0!==e.year&&(o.year=e.year),void 0!==e.month&&(o.month=e.month),void 0!==e.week&&(o.week=e.week),void 0!==e.day&&(o.day=e.day),s){const t=e[s];void 0!==t&&(o[s]=t)}else Object.assign(o,(e=>({...void 0!==e.speed&&{speed:e.speed},...void 0!==e.freeFlowSpeed&&{freeFlowSpeed:e.freeFlowSpeed},...void 0!==e.congestionLevel&&{congestionLevel:e.congestionLevel},...void 0!==e.travelTime&&{travelTime:e.travelTime},...void 0!==e.networkLength&&{networkLength:e.networkLength}}))(e));return o});o&&(m=m.filter(e=>!e.date||e.date>=o)),n&&(m=m.filter(e=>!e.date||e.date<=n)),void 0!==a&&(m=m.filter(e=>void 0===e.hour||e.hour>=a)),void 0!==i&&(m=m.filter(e=>void 0===e.hour||e.hour<=i)),l&&l.length>0&&(m=m.filter(e=>void 0===e.day||l.includes(e.day)));const y=t.trafficAreaAnalytics.currentAnalyticsConfig,g=y?{activeMetric:y.activeMetric,displayMode:y.displayMode}:void 0;return{granularity:r,count:m.length,entries:m,...g&&{currentVisualization:g}}},tags:["traffic","location"],examples:["queryTrafficAnalytics({ granularity: 'daily' })","queryTrafficAnalytics({ granularity: 'hourly', hourStart: 7, hourEnd: 9, metric: 'congestionLevel' })","queryTrafficAnalytics({ granularity: 'average', dayOfWeek: [6, 7] })"],examplePrompts:["Show me the daily breakdown","What was rush hour congestion?","Which day had the worst traffic?","What metric am I looking at?","What is currently shown on the map?"],relatedTools:["getTrafficAreaAnalytics","showTrafficAreaAnalytics"],dependsOn:["getTrafficAreaAnalytics"]},updatePlacesDisplay:{description:'Show, hide, and swap which places entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by category label (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show these"). Markers (`markerType`) and boundary polygons (`geometry`) are independent display channels — set either or both for the targeted entries. Omit `add`/`addMatching` while still setting `markerType` or `geometry` to target the most recently stored entry. Does not delete from history.',classificationPrompt:"Show, hide, and swap places entries on the map — add some (as pins and/or boundary polygons), remove others, swap atomically, or render boundaries.",inputSchema:jn,outputSchema:On,execute:async(e,t)=>{const{clear:r,markerType:s,geometry:o,fitBounds:n}=e;try{const a=((e,t)=>{const{add:r,remove:s,addMatching:o,removeMatching:n,markerType:a,geometry:i}=t;let l=[...r??[],...Fn(e,o??[])];const c=[...s??[],...Fn(e,n??[])];if(void 0===r&&void 0===o&&(void 0!==a||void 0!==i)){const t=e.places.entries.at(-1)?.id;if(!t)return{error:"No places available to display. Resolve a place or run a search first (e.g. discoverPlaces)."};l=[t]}const d=new Set(e.places.entries.map(e=>e.id)),u=l.filter(e=>!d.has(e));return u.length>0?{error:`Unknown places entry ids: ${u.join(", ")}. Use recallPlaces to list available entries.`}:{add:l,remove:c}})(t,e);if("error"in a)return a;const{add:i,remove:l}=a,c=new Set(t.places.shownEntryIds);r&&(await t.places.clearShownEntries(),await t.places.clearShownGeometries()),l.length>0&&(await t.places.removeShownEntries(l),await t.places.clearShownGeometries(l));const d=void 0!==s||!o;let u;i.length>0&&d&&await(async(e,t,r,s)=>{const o=void 0!==r?t:t.filter(e=>!s.has(e));o.length>0&&await e.places.addShownEntries(o,r??"pin")})(t,i,s,c),o&&i.length>0&&(u=await(async(e,t,r)=>{const s=r.theme??"outline",o=r.mode??"replace",n=r.placeIds?new Set(r.placeIds):void 0,a=[];let i=0;for(const c of t){const t=e.places.entries.find(e=>e.id===c);if(!t)continue;const r=n?t.places.filter(e=>n.has(e.id)):t.places,d=[];for(const s of r)if(s.properties.dataSources?.geometry?.id)try{const t=await e.places.fetchPlaceGeometry(s.id);if(!t){a.push({placeId:s.id,reason:"Geometry Data service returned no feature for this place."});continue}d.push(t)}catch(l){a.push({placeId:s.id,reason:`Geometry fetch failed: ${l instanceof Error?l.message:String(l)}`})}else a.push({placeId:s.id,reason:"Place has no geometry data source (e.g. addresses and streets often lack one)."});d.length>0&&(await e.places.showPlaceGeometries(d,s,o,c),i+=d.length)}return{rendered:i,skipped:a}})(t,i,o));const p=t.places.shownEntryIds,h=i.filter(e=>!c.has(e)&&p.has(e)),m=l.filter(e=>c.has(e)&&!p.has(e)),y=n??i.length>0,g=t.places.getShownFeatures();if(y&&g.length>0){const e=O(g);e&&t.baseMap.mapLibreMap.fitBounds(e,{padding:50})}const f=t.places.entries.filter(e=>p.has(e.id)).map(e=>({id:e.id,label:e.label,featureCount:e.places.length,markerType:t.places.getShownMarkerType(e.id)??"pin"}));return{success:!0,totalShown:g.length,shown:f,actuallyAdded:h,actuallyRemoved:m,...u&&{geometriesRendered:u.rendered},...u?.skipped.length&&{geometriesSkipped:u.skipped}}}catch(a){return{error:`updatePlacesDisplay failed: ${a instanceof Error?a.message:String(a)}`}}},tags:["place","location","map style","boundary","outline"],examples:['updatePlacesDisplay({ add: ["places-2"] })','updatePlacesDisplay({ add: ["places-1", "places-3"], markerType: "base-map" })','updatePlacesDisplay({ remove: ["places-1"] })','updatePlacesDisplay({ removeMatching: ["cinema"] }) // hide every entry labelled "cinema"','updatePlacesDisplay({ addMatching: ["cafe"] })',"updatePlacesDisplay({ clear: true })",'updatePlacesDisplay({ clear: true, add: ["places-2"] }) // atomic swap: hide everything, show this','updatePlacesDisplay({ markerType: "pin" }) // re-pin the most recently stored entry',"updatePlacesDisplay({ geometry: {} }) // outline the most recently stored entry",'updatePlacesDisplay({ add: ["places-2"], geometry: { mode: "add" } }) // also outline on top of existing boundaries','updatePlacesDisplay({ add: ["places-2"], markerType: "pin", geometry: { theme: "outline" } }) // pin AND outline','updatePlacesDisplay({ add: ["places-2"], geometry: { placeIds: ["G55fc4abe-..."], theme: "outline" } }) // outline a subset'],examplePrompts:["Show the results on the map","Pin the search results","Hide the banks","Also show the cafes on the map","Additionally pin the pubs","Remove the banks from the map",'Drop every entry labelled "parking" from the map',"Toggle off the hotels without clearing the rest","Replace the pins with the new set","Show entry places-3 as base-map POIs","Show the boundary of Amsterdam on the map","Outline these neighbourhoods","Highlight the shape of this place","Pin the cities and outline them too","Also outline Utrecht while keeping Amsterdam drawn","Add the neighbourhood boundaries on top of the pins"],relatedTools:["discoverPlaces","locatePlace","reverseGeocode","recallPlaces","clearMap","flyTo"],dependsOn:["discoverPlaces","locatePlace","processPlaces"]},updateRoutesDisplay:{description:'Show, hide, and swap which routes entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show this"). `selectedIndex` re-highlights an alternative variant on the newly-added entries. Omit `add`/`addMatching` while still setting `selectedIndex` to target the most recently stored entry. Does not delete from history. Does not call any service.',classificationPrompt:"Show, hide, and swap routes entries on the map — add some, remove others, swap atomically, or switch the highlighted alternative.",inputSchema:$n,outputSchema:Nn,execute:async(e,t)=>{const{clear:r,selectedIndex:s,fitBounds:o}=e;try{const n=((e,t)=>{const{add:r,remove:s,addMatching:o,removeMatching:n,selectedIndex:a}=t;let i=[...r??[],...qn(e,o??[])];const l=[...s??[],...qn(e,n??[])];if(void 0===r&&void 0===o&&void 0!==a){const t=e.routing.entries.at(-1)?.id;if(!t)return{error:"No routes available to display. Calculate a route first via setRoute."};i=[t]}const c=new Set(e.routing.entries.map(e=>e.id)),d=i.filter(e=>!c.has(e));return d.length>0?{error:`Unknown routes entry ids: ${d.join(", ")}. Use recallRoutes to list available entries.`}:{add:i,remove:l}})(t,e);if("error"in n)return n;const{add:a,remove:i}=n,l=new Set(t.routing.shownEntryIds);if(r)for(const e of[...t.routing.shownEntryIds])await t.routing.hideEntry(e);for(const e of i)await t.routing.hideEntry(e);for(const e of a)await t.routing.showEntry(e);void 0!==s&&await(async(e,t,r)=>{if(0!==r)for(const s of t){const t=e.routing.entries.find(e=>e.id===s);if(!t)continue;const o=await e.routing.getEntryRoutingModule(s);await o.showRoutes(t.data,{selectedIndex:r}),t.waypoints.length&&await o.showWaypoints(t.waypoints)}})(t,a,s);const c=t.routing.shownEntryIds,d=a.filter(e=>!l.has(e)&&c.has(e)),u=i.filter(e=>l.has(e)&&!c.has(e));if(o??a.length>0){const e=[];for(const r of t.routing.entries)c.has(r.id)&&e.push(...r.data.features);if(e.length>0){const r=O({type:"FeatureCollection",features:e});r&&t.baseMap.mapLibreMap.fitBounds(r,{padding:50})}}const p=t.routing.entries.filter(e=>c.has(e.id)).map(e=>({id:e.id,label:e.label,routeCount:e.data.features.length,selectedIndex:d.includes(e.id)?s??0:0}));return{success:!0,totalShown:c.size,shown:p,actuallyAdded:d,actuallyRemoved:u}}catch(n){return{error:`updateRoutesDisplay failed: ${n instanceof Error?n.message:String(n)}`}}},tags:["route","waypoint"],examples:['updateRoutesDisplay({ add: ["routes-1"] })','updateRoutesDisplay({ add: ["routes-0", "routes-2"] })','updateRoutesDisplay({ remove: ["routes-1"] })','updateRoutesDisplay({ removeMatching: ["Brussels"] }) // hide every entry whose label mentions "Brussels"','updateRoutesDisplay({ addMatching: ["Edinburgh"] })',"updateRoutesDisplay({ clear: true })",'updateRoutesDisplay({ clear: true, add: ["routes-2"] }) // atomic swap: hide everything, show this',"updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route",'updateRoutesDisplay({ add: ["routes-0"], selectedIndex: 2, fitBounds: false })'],examplePrompts:["Draw the route on the map","Show the directions","Display the calculated route","Show the Edinburgh route from earlier","Also show the Brussels route","Hide the Lyon route","Switch to the second alternative","Highlight alternative 2","Replace what is shown with this route"],relatedTools:["setRoute","processRoutes","recallRoutes","clearMap"],dependsOn:["setRoute"]},showTrafficAreaAnalytics:{description:"Render HISTORICAL traffic analytics (hexgrid/heatmap) on the map after getTrafficAreaAnalytics. Configure metric, color theme, height, and tile filtering; defaults to the first fetched metric. Not real-time — use toggleTrafficFlow for live traffic.",classificationPrompt:"Render traffic analytics as hexgrid, heatmap, or tiles with custom colors, filtering, height, and tooltip.",inputSchema:In,outputSchema:Sn,execute:async(e,t)=>{try{const r=t.trafficAreaAnalytics.lastAreaAnalytics;if(!r)return{error:"No analytics data available. Call getTrafficAreaAnalytics first to fetch data."};const s=r.properties?.metrics?.[0]??"congestionLevel",{mode:o="hexgrid-3d",metric:n=s,colorTheme:a="trafficLight",heightScale:i,scaleMode:l,filter:c,visible:d=!0,fitBounds:u=!0}=e,p=await t.trafficAreaAnalytics.getTrafficAreaAnalyticsModule();if(!d)return await p.clear(),t.trafficAreaAnalytics.notifyAnalyticsCleared(),{success:!0,mode:o,metric:n,colorTheme:a,visible:!1,filtered:!1};await p.clear(),await p.show(r),p.setMode(o),p.setMetric(n),p.setColor(a),void 0===i&&void 0===l||p.setHeight({...void 0!==i&&{maxHeightMeters:i},...void 0!==l&&{scaleMode:l}});const h=!!c;if(c?p.filter({min:c.min,max:c.max}):p.clearFilter(),u){const e=O(r.features);e&&t.baseMap.mapLibreMap.fitBounds(e,{padding:50,pitch:45})}return t.trafficAreaAnalytics.notifyAnalyticsShown(r,p),{success:!0,mode:o,metric:n,colorTheme:a,visible:!0,filtered:h}}catch(r){return{error:`Failed to show traffic area analytics: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","map style"],examples:["showTrafficAreaAnalytics()","showTrafficAreaAnalytics({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' })","showTrafficAreaAnalytics({ filter: { min: 50 } })","showTrafficAreaAnalytics({ scaleMode: 'currentRange', heightScale: 200 })","showTrafficAreaAnalytics({ visible: false })"],examplePrompts:["Show it on the map","Visualize the congestion","Only show areas with congestion above 50%","Normalize height to the current data range","Switch to heatmap view"],relatedTools:["getTrafficAreaAnalytics","queryTrafficAnalytics","toggleTrafficFlow","clearMap"],dependsOn:["getTrafficAreaAnalytics"]},showWaypoints:{description:"Display origin/stop/destination markers (no route line; no recalculation). Requires staged waypoints from setRoute or locatePlace. Use updateRoutesDisplay when you also need the route line drawn.",classificationPrompt:"Display staged origin/stop/destination markers without drawing the route line.",inputSchema:Pn,outputSchema:xn,execute:async(e,t)=>{try{const e=t.routing.planningSlots,r=[...t.routing.shownEntryIds][0],s=t.routing.entries.at(-1),o=r??s?.id;return o?(await(await t.routing.getEntryRoutingModule(o)).showWaypoints(e),{success:!0,count:e.length}):{error:"No route history yet — calculate a route via setRoute first."}}catch(r){return{error:`Failed to show waypoints: ${r instanceof Error?r.message:String(r)}`}}},tags:["waypoint","route"],examples:["showWaypoints()"],examplePrompts:["Show the waypoints on the map","Mark my route stops"],relatedTools:["setRoute","updateRoutesDisplay","getShownWaypoints","clearMap"],dependsOn:["setRoute","locatePlace"]},getShownPlaces:{description:'List the place entries currently shown on the map, grouped by `placesEntryId` with labels and a feature preview. Use to check which categories are visible before calling `updatePlacesDisplay`, or when the user asks "what is on the map". Positions are [longitude, latitude].',classificationPrompt:"Return places currently displayed on the map (available after updatePlacesDisplay or locatePlace).",inputSchema:on,outputSchema:sn,execute:async(e,t)=>{try{const e=t.places.shownEntryIds;if(0===e.size)return{count:0,entries:[]};const r=t.places.entries.filter(t=>e.has(t.id)).map(e=>({id:e.id,label:e.label,featureCount:e.places.length,markerType:t.places.getShownMarkerType(e.id)??"pin",features:e.places.slice(0,5).map(e=>({name:e.properties.poi?.name,address:e.properties.address?.freeformAddress,position:e.geometry.coordinates}))}));return{count:r.reduce((e,t)=>e+t.featureCount,0),entries:r}}catch(r){return{error:`Failed to get shown places: ${r instanceof Error?r.message:String(r)}`}}},tags:["place","location"],examples:["getShownPlaces()"],examplePrompts:["What places are on the map?","Which of my search results are currently displayed?","How many places are visible right now?"],relatedTools:["updatePlacesDisplay","clearMap","locatePlace","reverseGeocode"],dependsOn:["updatePlacesDisplay"]},getShownRoutes:{description:"Return routes currently rendered on the map (reads map state, not history). Use after updateRoutesDisplay.",classificationPrompt:"Return routes currently rendered on the map (available after updateRoutesDisplay).",inputSchema:cn,outputSchema:ln,execute:async(e,t)=>{try{if(!t.routing.currentEntryModule)return{count:0,routes:[]};const e=t.routing.currentEntryModule.getShown();return e.mainLines&&0!==e.mainLines.features.length?Cr(e.mainLines):{count:0,routes:[]}}catch(r){return{error:`Failed to get shown routes: ${r instanceof Error?r.message:String(r)}`}}},tags:["route"],examples:["getShownRoutes()"],examplePrompts:["What routes are displayed?","Is there a route on the map?"],relatedTools:["updateRoutesDisplay","clearMap"],dependsOn:["updateRoutesDisplay"]},getShownWaypoints:{description:"Get origin/stop/destination waypoints shown on the map. Context-dependent: requires waypoints shown on the map. Returns positions as [longitude, latitude].",classificationPrompt:"Return waypoint markers currently shown on the map (available after updateRoutesDisplay or showWaypoints).",inputSchema:un,outputSchema:dn,execute:async(e,t)=>{try{const e=t.routing.currentEntryModule;if(!e)return{error:"No waypoints currently shown on the map"};const r=e.getShown();return r.waypoints&&0!==r.waypoints.features.length?{count:r.waypoints.features.length,waypoints:r.waypoints.features.map(e=>({name:e.properties.name,position:e.geometry.coordinates}))}:{error:"No waypoints currently shown on the map"}}catch(r){return{error:`Failed to get shown waypoints: ${r instanceof Error?r.message:String(r)}`}}},tags:["waypoint","route"],examples:["getShownWaypoints()"],examplePrompts:["What waypoints are shown?","List the stops on the map"],relatedTools:["updateRoutesDisplay","clearMap"],dependsOn:["updateRoutesDisplay","showWaypoints"]},getShownRouteTrafficIncidents:{description:"Get the route traffic incidents currently displayed on the map (not from service, but what is actually shown). Each section has an id.",classificationPrompt:"Return traffic incidents overlaid on the shown route.",inputSchema:an,outputSchema:nn,execute:async(e,t)=>{try{if(!t.routing.currentEntryModule)return{error:"No routing module initialized - no route traffic incidents shown on map"};const e=t.routing.currentEntryModule.getShown();return e.incidents&&0!==e.incidents.features.length?{count:e.incidents.features.length,trafficIncidents:e.incidents.features.map(e=>({id:e.properties.id,magnitudeOfDelay:e.properties.magnitudeOfDelay,title:e.properties.title,delayInSeconds:e.properties.delayInSeconds,effectiveSpeedInKmh:e.properties.effectiveSpeedInKmh,categories:e.properties.categories}))}:{error:"No route traffic incidents currently shown on the map"}}catch(r){return{error:`Failed to get shown route traffic incidents: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","route"],examples:["getShownRouteTrafficIncidents()"],examplePrompts:["What traffic incidents are on my route?","Any delays along the route?"],relatedTools:["updateRoutesDisplay","clearMap","setRoute"],dependsOn:["updateRoutesDisplay"]},getShownIncidents:{description:"Get the real-time traffic incidents currently visible in the map viewport (not from service, but what is actually rendered on the map).",classificationPrompt:"Return real-time incidents currently visible in the map viewport (rendered on map, not fetched from service).",inputSchema:rn,outputSchema:tn,execute:async(e,t)=>{try{const e=(await t.trafficTiles.getTrafficIncidentsModule()).getShown();return 0===e.trafficIncidents.length?{error:"No traffic incidents currently shown on the map"}:{count:e.trafficIncidents.length,trafficIncidents:e.trafficIncidents.map(e=>({id:e.properties.id,category:e.properties.category,magnitudeOfDelay:e.properties.magnitudeOfDelay,description:e.properties.description,delayInSeconds:e.properties.delayInSeconds,roadCategory:e.properties.roadCategory,averageSpeedKmph:e.properties.averageSpeedKmph}))}}catch(r){return{error:`Failed to get shown incidents: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","location"],examples:["getShownIncidents()"],examplePrompts:["What traffic incidents are visible?","Any incidents in the current view?"],relatedTools:["toggleTrafficIncidents","flyTo","getViewport"],dependsOn:["toggleTrafficIncidents"]},clearMap:{description:"Remove displayed features from the map (places, routes, geometries, analytics, incidents). Use to clean up before showing new results or to reset the map.",classificationPrompt:"Remove displayed places, routes, or all features from the map.",inputSchema:en,outputSchema:Zo,execute:async(e,t)=>{const{layers:r}=e;try{const e=e=>!r||0===r.length||r.includes(e);return e("places")&&await(e=>e.places.clearShownEntries())(t),e("routes")&&await(async e=>{for(const t of e.routing.entries)t._module&&(await t._module.clearRoutes(),await t._module.clearWaypoints())})(t),e("geometries")&&await(async e=>{await e.places.clearShownGeometries();for(const t of e.ranges.shownEntryIds)await e.ranges.hideEntry(t);for(const t of e.customGeometries.shownEntryIds)await e.customGeometries.hideEntry(t)})(t),e("analytics")&&await(async e=>{const t=e.trafficAreaAnalytics.trafficAreaAnalyticsModule;t&&await t.clear()})(t),e("incidents")&&await(async e=>{for(const t of e.trafficIncidents.shownEntryIds)await e.trafficIncidents.hideEntry(t)})(t),{success:!0,cleared:r||[...Qo]}}catch(s){return{error:`Failed to clear map: ${s instanceof Error?s.message:String(s)}`}}},tags:["place","route","map style"],examples:["clearMap()",'clearMap(["places"])','clearMap(["places", "routes"])'],examplePrompts:["Clear the map","Remove all markers","Start fresh"],relatedTools:["updatePlacesDisplay","updateRoutesDisplay"]},flyTo:{description:"Move the map camera to a bounding box or a specific position. Use with boundingBox to frame a region or set of results. Use with position to center on a point.",classificationPrompt:"Move the camera to a bounding box or specific position; use after locating places or computing bounds.",inputSchema:tr,outputSchema:er,execute:async(e,t)=>{const{where:r}=e;try{return"boundingBox"in r?t.baseMap.mapLibreMap.fitBounds(r.boundingBox,{padding:r.padding??50}):t.baseMap.mapLibreMap.flyTo({center:r.position,zoom:r.zoom??14}),{success:!0}}catch(s){return{error:`flyTo failed: ${s instanceof Error?s.message:String(s)}`}}},tags:["location","map style"],examples:["flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5] } })","flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5], padding: 100 } })","flyTo({ where: { position: [4.9, 52.4], zoom: 14 } })"],examplePrompts:["Go to Berlin","Center the map on Rome","Fly to these coordinates"],relatedTools:["getViewport","locatePlace","updatePlacesDisplay","updateRoutesDisplay","calculateBBox"]},getViewport:{description:'Get the current map viewport: center coordinates [longitude, latitude], zoom level, and bounding box [minLng, minLat, maxLng, maxLat]. Use for queries referencing the map view: "in this area", "what I can see", "near the map center". Does NOT return the user\'s physical location — use getCurrentLocation for "near me" queries. Reads map state. Does not call any service.',classificationPrompt:'Return the map center, zoom, and bounding box; use for area-relative queries like "in this area" or "near the map center".',inputSchema:nr,outputSchema:or,execute:async(e,t)=>{try{const e=t.baseMap.mapLibreMap;return{center:e.getCenter().toArray(),zoom:e.getZoom(),bbox:t.baseMap.ttMap.getBBox(),pitch:e.getPitch(),bearing:e.getBearing()}}catch(r){return{error:`Failed to get viewport: ${r instanceof Error?r.message:String(r)}`}}},tags:["location","map style"],examples:["getViewport()"],examplePrompts:["Where is the map centered?","What's the current map area?","What are the current map bounds?"],relatedTools:["locatePlace","flyTo","discoverPlaces","reverseGeocode","getTrafficIncidents"]},recallGeometries:{description:"List or inspect every polygon source in session state — place footprints (cached on places entries via `withGeometries: true`), reachable-area isochrones, and custom-geometries entries produced by `processGeometries`. ALWAYS call this before passing ids to `processGeometries` or `analyseGeometries`; never guess. No args = index across all three slices; pass `id` to retrieve features and provenance for one entry. No service call.",classificationPrompt:"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.",inputSchema:ho,outputSchema:fo,execute:async(e,t)=>{const{id:r}=e;return r?((e,t)=>{if("places"===e.kind){const r=t.places.entries.find(t=>t.id===e.id);return r?r.geometries?.length?{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,features:{type:"FeatureCollection",features:r.geometries},...bo(r._analysis)&&{analyses:bo(r._analysis)}}:{error:`Places entry "${e.id}" has no cached footprints. Call discoverPlaces / locatePlace with \`withGeometries: true\` first.`}:{error:`No places entry with id "${e.id}"`}}if("ranges"===e.kind){const r=t.ranges.entries.find(t=>t.id===e.id);if(!r)return{error:`No ranges entry with id "${e.id}"`};const s=r.ranges.flatMap(e=>e.polygon?.features??[]);return 0===s.length?{error:`Ranges entry "${e.id}" has no polygons.`}:{id:e,label:r.label,timestamp:r.timestamp,featureCount:s.length,features:{type:"FeatureCollection",features:s}}}if("custom"===e.kind){const r=t.customGeometries.findById(e.id);return r?{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.features.length,features:{type:"FeatureCollection",features:r.features},...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...bo(r._analysis)&&{analyses:bo(r._analysis)}}:{error:`No custom-geometries entry with id "${e.id}"`}}return{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.'}})(r,t):{entries:wo(t)}},tags:["geometry","boundary"],examples:["recallGeometries()",'recallGeometries({ id: { kind: "places", id: "ev-stations-oost" } })','recallGeometries({ id: { kind: "custom", id: "bakery-candidates-gap-oost" } })'],examplePrompts:["What polygons are in state right now?","Which IDs can I pass to processGeometries?","Recall the custom zone we just produced","What does the bakery-candidates entry contain?"],relatedTools:["processGeometries","analyseGeometries","recallPlaces","recallRanges"]},recallPlaces:{description:"List / inspect places entries already in session state (from discoverPlaces / locatePlace / processPlaces) — the same entries every `placesEntryIDs` parameter accepts. ALWAYS call this before referencing past places; never guess IDs. No args = index (id, label, timestamp, featureCount, analyses) + `entryMode`; pass `id` for full Place coordinates. No service call.",classificationPrompt:"List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processPlaces, analysePlaces, updatePlacesDisplay).",inputSchema:vo,outputSchema:Mo,execute:async(e,t)=>{const{id:r}=e,s=t.places.entries,o=t.places.entryMode;if(!r)return{entries:[...s].sort((e,t)=>t.timestamp-e.timestamp).map(({id:e,label:t,timestamp:r,places:s,_analysis:o})=>({id:e,label:t,timestamp:r,featureCount:s.length,...o?.length&&{analyses:o.map(({name:e,outputFormat:t,timestamp:r,description:s})=>({name:e,outputFormat:t,timestamp:r,...s&&{description:s}}))}})),entryMode:o};const n=s.find(e=>e.id===r);if(!n)return{error:`No entry found with id "${r}"`};const a=Tr(n.places);return{id:n.id,label:n.label,timestamp:n.timestamp,places:a,entryMode:o}},tags:["place","location"],examples:["recallPlaces()",'recallPlaces({ id: "places-0" })'],examplePrompts:["What places did I search for?","List every places entry with its ID","Which IDs can I pass to processPlaces or updatePlacesDisplay?","Recall the bakeries I discovered earlier","What is in entry places-2?"],relatedTools:["discoverPlaces","locatePlace","reverseGeocode","updatePlacesDisplay"],dependsOn:["locatePlace","discoverPlaces"]},recallRoutes:{description:"List calculated routes from session history, or retrieve one by `id` (route details + params). IDs are stable and accepted by updateRoutesDisplay. Never guess IDs — list first. No service call.",classificationPrompt:"Return previously calculated routes from this session.",inputSchema:Do,outputSchema:Lo,execute:async(e,t)=>{const{id:r}=e,s=t.routing.entryMode;if(!r)return{entries:[...t.routing.entries].reverse().map(({id:e,label:t,timestamp:r})=>({id:e,label:t,timestamp:r})),entryMode:s};const o=t.routing.entries.find(e=>e.id===r);if(!o)return{error:`No entry found with id "${r}"`};const n=Cr(o.data),a=o.waypoints.map(e=>Dr(e)).filter(e=>null!==e).map(e=>({position:e.position??[0,0]}));return{id:o.id,label:o.label,timestamp:o.timestamp,routes:n,waypoints:a,params:o.params,entryMode:s}},tags:["route"],examples:["recallRoutes()",'recallRoutes({ id: "routes-0" })'],examplePrompts:["What routes have I calculated?","Show the first route we planned","What was the route to Schiphol?"],relatedTools:["setRoute","updateRoutesDisplay","getCurrentWaypoints"],dependsOn:["setRoute"]},getCurrentWaypoints:{description:"Read staged waypoint slots for the next route calc, without recalculating. `slotIndex` for one slot; otherwise paged (default 5), empty slots omitted unless `includeEmptySlots: true`. No service call.",classificationPrompt:"Return the staged waypoint slots (origin, stops, destination) not yet committed to a route.",inputSchema:Ks,outputSchema:Xs,execute:async(e,t)=>{const{slotIndex:r,includeEmptySlots:s=!1,offset:o=0,limit:n=5}=e,a=t.routing.planningSlots.map((e,t)=>{const r=Dr(e);return r?{...r,slotIndex:t}:{slotIndex:t,isFilled:!1}}),i=s?a:a.filter(e=>e.isFilled),l=void 0!==r,c=l?i.filter(e=>e.slotIndex===r):i.slice(o,o+n),d=o+c.length;return{totalSlots:a.length,filledSlots:a.filter(e=>e.isFilled).length,matchedCount:l?c.length:i.length,returnedCount:c.length,offset:l?0:o,hasMore:!l&&d<i.length,...!l&&d<i.length&&{nextOffset:d},waypoints:c}},tags:["waypoint","route"],examples:["getCurrentWaypoints()","getCurrentWaypoints({ slotIndex: 1 })","getCurrentWaypoints({ includeEmptySlots: true })","getCurrentWaypoints({ offset: 2, limit: 4 })"],examplePrompts:["What are my current route waypoints?","What's my destination?","Show my staged stops"],relatedTools:["locatePlace","setRoute","showWaypoints","recallRoutes"]},getStandardMapStyles:{description:"Get the list of available standard map style IDs. If you need more fine styling control, refer to getMapStyleLayers.",classificationPrompt:"List available standard map style IDs for use with setMapStandardStyle.",inputSchema:hn,outputSchema:pn,execute:async(e,t)=>{try{return{styles:g}}catch(r){return{error:`Failed to get standard map styles: ${r instanceof Error?r.message:String(r)}`}}},tags:["map style"],examples:["getStandardMapStyles()"],examplePrompts:["What map styles are available?","List the map themes"],relatedTools:["setMapStandardStyle"]},setMapStandardStyle:{description:"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.",classificationPrompt:"Switch the map to a standard style preset (light, dark, satellite, driving, etc.).",inputSchema:fn,outputSchema:gn,execute:async(e,t)=>{const{style:r}=e;try{return t.baseMap.ttMap.setStyle(r),{success:!0,style:r}}catch(s){return{error:`Failed to set map style: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setMapStandardStyle("standardDark")','setMapStandardStyle("satellite")'],examplePrompts:["Switch to dark mode","Use the satellite view","Change to the light theme"],relatedTools:["getStandardMapStyles","setLanguage","toggleBaseMapLayerGroups","getMapStyleLayers"]},setRouteTheme:{description:"Color the route line + waypoint icons consistently across all route layers (preferred over setPaintProperties for route styling). Requires a route on the map.",classificationPrompt:"Color the route line and waypoint icons; prefer over setPaintProperties for route styling.",inputSchema:wn,outputSchema:bn,execute:async(e,t)=>{try{void 0!==e.mainColor&&(vn.mainColor=e.mainColor);const r=t.routing.currentEntryModule;return r?(r.applyConfig({theme:{mainColor:vn.mainColor}}),{success:!0,theme:{...vn}}):{error:"No route currently shown — call updateRoutesDisplay or setRoute first."}}catch(r){return{error:`Failed to set route theme: ${r instanceof Error?r.message:String(r)}`}}},tags:["route","map style"],examples:['setRouteTheme({ mainColor: "#FF0000" }) // red route','setRouteTheme({ mainColor: "coral" })','setRouteTheme({ mainColor: "steelblue" })'],examplePrompts:["Make the route line red","Change the route color to blue","Style the route in coral"],relatedTools:["updateRoutesDisplay","setRoute","setPaintProperties"],dependsOn:["updateRoutesDisplay"]},setLanguage:{description:"Set the language for map labels and all subsequent service API responses (search, geocoding, routing instructions).",classificationPrompt:"Change the language for map labels and all subsequent service API responses.",inputSchema:yn,outputSchema:mn,execute:async(e,t)=>{const{language:r}=e;try{return D.instance.put({language:r}),t.baseMap.ttMap.setLanguage(r),{success:!0,language:r}}catch(s){return{error:`Failed to set language: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setLanguage("fr-FR")','setLanguage("de-DE")'],examplePrompts:["Show map labels in French","Switch the map to German","Use Spanish for place names"],relatedTools:["setMapStandardStyle"]},toggleBaseMapLayerGroups:{description:"Show or hide specific base map layer groups (water, buildings3D, roadLines, placeLabels, capitalLabels, etc.)",classificationPrompt:"Show or hide named base map layer groups (buildings3D, roadLabels, water, placeLabels, etc.).",inputSchema:En,outputSchema:Mn,execute:async(e,t)=>{const{visible:r,layerGroups:s}=e;try{return(await t.baseMap.getBaseMapModule()).setVisible(r,{layerGroups:{mode:"include",names:s}}),{success:!0,visible:r,layerGroups:s}}catch(o){return{error:`Failed to toggle layer groups: ${o instanceof Error?o.message:String(o)}`}}},tags:["map style"],examples:['toggleBaseMapLayerGroups(false, ["buildings3D"])','toggleBaseMapLayerGroups(true, ["roadLabels"])'],examplePrompts:["Hide 3D buildings","Show road labels","Toggle the building layer"],relatedTools:["togglePOIs","getMapStyleLayers","setMapStandardStyle"]},togglePOIs:{description:"Show/hide built-in map POI icons (restaurants, hotels, gas stations, etc.). `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts which categories show; `reset: true` restores defaults.",classificationPrompt:"Show or hide built-in map POI icons with optional category filtering.",inputSchema:Tn,outputSchema:_n,execute:async(e,t)=>{try{const r=await t.mapPOIs.getPOIsModule();if(e.reset)return r.resetConfig(),{success:!0,reset:!0};const{allMapPOIsVisible:s,filterCategories:o}=e;return void 0!==s&&r.setVisible(s),o&&r.filterCategories({show:o.show,values:o.values}),{success:!0,allMapPOIsVisible:s,filterCategories:o}}catch(r){return{error:`Failed to toggle POIs: ${r instanceof Error?r.message:String(r)}`}}},tags:["place","map style"],examples:["togglePOIs({ allMapPOIsVisible: false })",'togglePOIs({ allMapPOIsVisible: true, filterCategories: { show: "only", values: ["RESTAURANT", "CAFE"] } })','togglePOIs({ filterCategories: { show: "all_except", values: ["PARKING_GARAGE"] } })',"togglePOIs({ reset: true })"],examplePrompts:["Hide all POI icons","Show only restaurant POIs","Turn off parking icons"],relatedTools:["toggleBaseMapLayerGroups","discoverPlaces","setMapStandardStyle"]},toggleTrafficFlow:{description:"Toggle the REAL-TIME traffic flow layer — colored road segments showing live speed conditions. This is NOT historical analytics; use showTrafficAreaAnalytics for historical data.",classificationPrompt:"Toggle REAL-TIME traffic flow (colored road segments for live speeds). Not historical analytics.",inputSchema:An,outputSchema:Cn,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficFlowModule()).setVisible(r),{success:!0,trafficFlowVisible:r}}catch(s){return{error:`Failed to toggle traffic flow: ${s instanceof Error?s.message:String(s)}`}}},tags:["traffic","map style"],examples:["toggleTrafficFlow(true)","toggleTrafficFlow(false)"],examplePrompts:["Show traffic flow","Turn off traffic colors","Enable the congestion overlay"],relatedTools:["toggleTrafficIncidents","setRoute","getTrafficIncidents"]},toggleTrafficIncidents:{description:"Toggle REAL-TIME traffic incident markers on the map (jams, accidents, closures, roadworks). This is NOT historical analytics. Use getTrafficIncidents for incident details.",classificationPrompt:"Toggle REAL-TIME traffic incident markers and lines (accidents, jams, closures). Not historical analytics.",inputSchema:Rn,outputSchema:Dn,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficIncidentsModule()).setVisible(r),{success:!0,trafficIncidentsVisible:r}}catch(s){return{error:`Failed to toggle traffic incidents: ${s instanceof Error?s.message:String(s)}`}}},tags:["traffic","map style"],examples:["toggleTrafficIncidents(true)","toggleTrafficIncidents(false)"],examplePrompts:["Show traffic incidents","Hide accident icons","Enable incident markers"],relatedTools:["toggleTrafficFlow","getTrafficIncidents","getShownIncidents","setRoute"]},getMapStyleLayers:{description:"Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayoutProperty/setPaintProperty for custom styling.",classificationPrompt:"List MapLibre layer IDs with their paint/layout properties for custom styling.",inputSchema:sr,outputSchema:rr,execute:async(e,t)=>{const{layerIdQuery:r,include:s}=e;try{let e=t.baseMap.mapLibreMap.getStyle().layers??[];return r&&(e=e.filter(e=>e.id.toLowerCase().includes(r.toLowerCase())),0===e.length)?{error:`No layers found matching query: "${r}"`}:{layers:e.map(e=>({id:e.id,...s.includes("paint")&&{paint:e.paint},...s.includes("layout")&&{layout:e.layout}}))}}catch(o){return{error:`Failed to get style: ${o instanceof Error?o.message:String(o)}`}}},tags:["map style"],examples:["getMapStyleLayers()",'getMapStyleLayers("road")','getMapStyleLayers("water")'],examplePrompts:["What are the paint properties of the water layer?","Show me the road layer layout"],relatedTools:["setLayoutProperties","setPaintProperties"]},setLayoutProperties:{description:"Set MapLibre layout properties (visibility, text-field, icon-size) on named layers — use getMapStyleLayers for valid IDs. For colors/widths use setPaintProperties.",classificationPrompt:"Set MapLibre layout properties (visibility, text-field, icon-size) on named layers.",inputSchema:lr,outputSchema:ir,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:s})=>{try{return t.baseMap.mapLibreMap.setLayoutProperty(e,r,s),{layerId:e,propertyName:r,value:s,success:!0}}catch(o){return{layerId:e,propertyName:r,error:`Failed to set layout property: ${o instanceof Error?o.message:String(o)}`}}})}}catch(s){return{error:`Failed to set layout properties: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setLayoutProperties([{ layerId: "road-label", propertyName: "visibility", value: "none" }])','setLayoutProperties([{ layerId: "poi-label", propertyName: "text-field", value: "{name}" }, { layerId: "poi-label", propertyName: "icon-size", value: 1.2 }])'],examplePrompts:["Hide the road-label layer","Change the text size on building labels","Make the city labels bigger"],relatedTools:["getMapStyleLayers","setPaintProperties"],dependsOn:["getMapStyleLayers"]},setPaintProperties:{description:"Set MapLibre paint properties (colors, widths, opacity) on named layers — use getMapStyleLayers for valid IDs. For visibility/structure use setLayoutProperties; for route styling prefer setRouteTheme.",classificationPrompt:"Set MapLibre paint properties (colors, widths, opacity) on named layers.",inputSchema:ur,outputSchema:dr,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:s})=>{try{return t.baseMap.mapLibreMap.setPaintProperty(e,r,s),{layerId:e,propertyName:r,value:s,success:!0}}catch(o){return{layerId:e,propertyName:r,error:`Failed to set paint property: ${o instanceof Error?o.message:String(o)}`}}})}}catch(s){return{error:`Failed to set paint properties: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setPaintProperties([{ layerId: "water", propertyName: "fill-color", value: "#0000ff" }])','setPaintProperties([{ layerId: "road", propertyName: "line-color", value: "#ff0000" }, { layerId: "road", propertyName: "line-width", value: 3 }])'],examplePrompts:["Make the water bright blue","Change the road color to red","Make buildings semi-transparent","Make the country borders pink"],relatedTools:["getMapStyleLayers","setLayoutProperties","setRouteTheme"],dependsOn:["getMapStyleLayers"]},setPitchBearing:{description:"Tilt and/or rotate the map camera. Pitch 0 = top-down view, 85 = near-horizontal (3D effect). Bearing 0 = north up, 90 = east up. Provide at least one of pitch or bearing.",classificationPrompt:"Tilt (pitch) and/or rotate (bearing) the map camera for a 3D perspective.",inputSchema:hr,outputSchema:pr,execute:async(e,t)=>{const{pitch:r,bearing:s}=e;if(void 0===r&&void 0===s)return{error:"Provide at least one of pitch or bearing"};try{return t.baseMap.mapLibreMap.easeTo({...void 0!==r&&{pitch:r},...void 0!==s&&{bearing:s}}),{success:!0}}catch(o){return{error:`Failed to set pitch/bearing: ${o instanceof Error?o.message:String(o)}`}}},tags:["map style"],examples:["setPitchBearing({ pitch: 45 })","setPitchBearing({ bearing: 90 })","setPitchBearing({ pitch: 60, bearing: -45 })"],examplePrompts:["Tilt the map to 45 degrees","Rotate the map to face north","Set a 3D perspective view"],relatedTools:["getViewport","locatePlace","flyTo"]},zoomInOrOut:{description:"Zoom the map in or out by a given amount relative to the current zoom level. Positive delta zooms in (closer), negative delta zooms out (further away). Returns the resulting zoom level.",classificationPrompt:"Adjust the map zoom level by a delta; positive zooms in, negative zooms out.",inputSchema:yr,outputSchema:mr,execute:async(e,t)=>{const{delta:r}=e;try{const e=t.baseMap.mapLibreMap,s=e.getZoom()+r;return e.zoomTo(s),{zoom:s}}catch(s){return{error:`Zoom failed: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:["zoomInOrOut({ delta: 2 })","zoomInOrOut({ delta: -1 })"],examplePrompts:["Zoom in","Zoom out 2 levels","Get a wider view"],relatedTools:["getViewport","flyTo"]},executeMaplibreCode:{description:"Run arbitrary MapLibre JS against the live `map` for anything no other tool covers — custom sources/layers, camera, style mutations, animations, event sequences. Full JS (loops, helpers, top-level `await`). Returns added/removed/updated sources & layers.",classificationPrompt:"Execute any MapLibre JS code against the live map for maximum flexibility — use when no other tool covers the required operation.",inputSchema:Yt,outputSchema:Kt,execute:async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,s=Xt(e),o=Zt(e),n=Object.getPrototypeOf(async()=>{}).constructor,a=await new n("map",r)(e),i=Qt(s,Xt(e));return{success:!0,result:a??void 0,sources:i,layers:Qt(o,Zt(e))}}catch(s){return{error:qe("Code",s)}}},tags:["map style","maplibre","layers","sources","animation"],examples:["executeMaplibreCode({ code: \"map.addSource('pts', { type: 'geojson', data: fc }); map.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-color': '#f00' } })\" })",'executeMaplibreCode({ code: "map.easeTo({ center: [4.9, 52.37], zoom: 14, duration: 2000 })" })',"executeMaplibreCode({ code: \"for (let i = 0; i <= 20; i++) { map.setPaintProperty('fill', 'fill-opacity', i / 20); await new Promise(r => setTimeout(r, 50)); }\" })","executeMaplibreCode({ code: \"map.setFog({ color: 'white', 'horizon-blend': 0.1 })\" })"],examplePrompts:["Add a GeoJSON layer to the map","Animate the map camera","Fade in a layer gradually","Set map fog or atmosphere","Add a raster tile overlay"],relatedTools:["getMapStyleLayers","setLayoutProperties","setPaintProperties","flyTo"],dependsOn:["getMapStyleLayers"]},calculateBBox:{description:"Compute a `[minLng, minLat, maxLng, maxLat]` bbox from GeoJSON (longitude before latitude).",classificationPrompt:"Compute a [minLng, minLat, maxLng, maxLat] bounding box from GeoJSON features or tool results.",inputSchema:Et,execute:async(e,t)=>{const{geoJson:r}=e;try{const e=O(r);return e?{bbox:e,minLng:e[0],minLat:e[1],maxLng:e[2],maxLat:e[3]}:{error:"Could not calculate bounding box - input may be empty or invalid GeoJSON"}}catch(s){return{error:`Failed to calculate bbox: ${s instanceof Error?s.message:String(s)}`}}},tags:["utilities","location"],examples:["calculateBBox(place)","calculateBBox(routes)","calculateBBox([place1, place2])"],examplePrompts:["What's the bounding box of these places?","Calculate the extent of this route"],relatedTools:["flyTo","getViewport","updatePlacesDisplay","updateRoutesDisplay"]},formatDistance:{description:'Format a distance in meters into a human-readable string (e.g. "2.5 km", "1.5 mi"). Use after getSectionProgress, recallRoutes, or any tool that returns distances in meters.',classificationPrompt:'Format meters into a human-readable string (e.g. "2.5 km"); use after tools that return distances.',inputSchema:_t,execute:async(e,t)=>{const{meters:r,unitType:s}=e;try{return{formatted:F(r,s?{type:s}:void 0),input:{meters:r,unitType:s??"metric"}}}catch(o){return{error:`Failed to format distance: ${o instanceof Error?o.message:String(o)}`}}},tags:["utilities","route"],examples:["formatDistance(2500)",'formatDistance(5000, "imperial_us")'],examplePrompts:["Format 5000 meters as a distance","Convert meters to a readable string"],relatedTools:["formatDuration","setRoute"]},formatDuration:{description:'Format a duration in seconds into a human-readable time string (e.g. "1 h 30 min"). Use after getSectionProgress, recallRoutes, or any tool that returns durations in seconds.',classificationPrompt:'Format seconds into a human-readable string (e.g. "1 h 30 min"); use after tools that return durations.',inputSchema:Tt,execute:async(e,t)=>{const{seconds:r}=e;try{const e=L(r);return void 0===e?{formatted:void 0,message:"< 1 min",input:{seconds:r}}:{formatted:e,input:{seconds:r}}}catch(s){return{error:`Failed to format duration: ${s instanceof Error?s.message:String(s)}`}}},tags:["utilities","route"],examples:["formatDuration(3600)","formatDuration(9000)"],examplePrompts:["Format 3600 seconds as a duration","Convert seconds to hours and minutes"],relatedTools:["formatDistance","setRoute"]},getCurrentLocation:{description:'Browser geolocation — the user\'s physical position [lng, lat] for "near me" / "where I am" queries (may prompt for permission). For map-based reference use getViewport.',classificationPrompt:'Get the user\'s physical GPS location from the browser; use for "near me" queries, not map center.',inputSchema:jt,outputSchema:Ft,execute:async(e,t)=>{if(void 0===globalThis.window||void 0===globalThis.navigator)return{error:"Current location is only available in a browser environment"};if(!("geolocation"in globalThis.navigator))return{error:"Geolocation is not supported by this browser"};try{const e=await new Promise((e,t)=>{globalThis.navigator.geolocation.getCurrentPosition(e,t,{enableHighAccuracy:!0,timeout:15e3,maximumAge:0})});return{position:[e.coords.longitude,e.coords.latitude],accuracy:e.coords.accuracy,timestamp:e.timestamp}}catch(r){const e=(e=>{if("object"!=typeof e||null===e||!("code"in e))return;const t=e.code;return 1===t?"Location permission denied by the user":2===t?"Current location is unavailable":3===t?"Timed out while trying to get current location":void 0})(r);return e?{error:e}:{error:`Failed to get current location: ${r instanceof Error?r.message:String(r)}`}}},tags:["utilities","location"],examples:["getCurrentLocation()"],examplePrompts:["Where am I?","Get my GPS location","Use my current position"],relatedTools:["discoverPlaces","reverseGeocode","locatePlace"]},help:{description:Gt,classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:Bt,outputSchema:qt,execute:async()=>{throw new Error("help tool requires setupTools() — it needs resolved metadata from all tools.")},tags:["utilities"],examples:['help({ mode: "summary" })','help({ mode: "detail", query: "traffic" })','help({ mode: "detail", tag: "route" })','help({ mode: "detail", query: "route", tag: "utilities" })'],examplePrompts:["What can you do?","Show me available tools","How do I search for places?"]},findReachableAreas:{description:"Compute reachable-area polygons (isochrone/isodistance) from one or more origins under shared time/distance budgets. Multiple origins → compared in one call (sequential to respect QPS); multiple budgets → nested ranges. Stored in state (recallRanges); `showOnMap: true` renders. Per-origin failures land under `skipped` instead of aborting the whole call.",classificationPrompt:"Calculate isochrone/isodistance polygons showing how far you can travel from one or more origins.",inputSchema:ys,outputSchema:fs,execute:async(e,t)=>{const{origins:r,budgets:s,theme:o,showOnMap:n,showOriginPin:a=!0,hidePreviousEntries:i}=e;try{const e=[...s].sort((e,t)=>t.value-e.value),l=[],c=[];for(const s of r){const r="query"in s?s.query:"position"in s?`${s.position.lat}, ${s.position.lng}`:"placeId"in s?s.placeId:"unnamed origin",o=await Nr(s,t);if(!o){c.push({origin:r,reason:"origin not found"});continue}const n=await bs(e,o.position);n?l.push({origin:o.query?{query:o.query,position:o.position}:{position:o.position},budgets:e,polygon:n}):c.push({origin:o.name??r,reason:"no reachable area returned"})}if(0===l.length)return{status:"no_results"};const d=o??"outline",u=l.map(ws),p=t.ranges.addEntry({label:Er(e,u),ranges:l});return(n||a)&&await Pe(t.ranges,[p],i),await(async(e,t,r,s,o,n)=>{if(o){const o=(e=>{const t=e.flatMap(e=>e.polygon?.features??[]);return 0===t.length?null:{type:"FeatureCollection",features:t}})(r);o&&(await(await e.ranges.getEntryGeometriesModule(t,s)).show(o),o.bbox&&e.baseMap.mapLibreMap.fitBounds(o.bbox,{padding:50}))}if(n){const s=await e.ranges.getEntryPlacesModule(t),o=r.map((e,r)=>({type:"Feature",id:`range-origin-${t}-${r}`,geometry:{type:"Point",coordinates:e.origin.position},properties:{type:"Point Address",address:{freeformAddress:ws(e)}}}));await s.show(o)}})(t,p,l,d,n,a),(n||a)&&await t.ranges.markEntryShown(p),{rangesId:p,ranges:l.map(t=>({originName:ws(t),budgets:e})),...c.length&&{skipped:c}}}catch(l){return{error:`Reachable area calculation failed: ${l instanceof Error?l.message:String(l)}`}}},tags:["reachable-range","isochrone","range","EV","coverage","multi-origin"],examples:['findReachableAreas({ origins: [{ query: "Amsterdam", queryAs: "place" }], budgets: [{ type: "timeMinutes", value: 30 }], showOnMap: true })','findReachableAreas({ origins: [{ query: "Amsterdam", queryAs: "place" }, { query: "Rotterdam", queryAs: "place" }], budgets: [{ type: "timeMinutes", value: 30 }], showOnMap: true })','findReachableAreas({ origins: [{ position: { lng: 4.9, lat: 52.3 } }], budgets: [{ type: "timeMinutes", value: 10 }, { type: "timeMinutes", value: 30 }], showOnMap: true, theme: "inverted" })','findReachableAreas({ origins: [{ query: "my location", queryAs: "place" }], budgets: [{ type: "distanceKM", value: 50 }], showOnMap: false })'],examplePrompts:["Show me where I can reach in 30 minutes from Amsterdam","Compare 30-minute reach from Amsterdam, Rotterdam and Utrecht","What area is within 50 km of Berlin?","Show 10, 20, 30 minute drive zones from here"],relatedTools:["recallRanges","locatePlace"]},recallRanges:{description:"List stored reachable-range results, or retrieve one by `id` (returns each origin and its budgets). Multi-origin entries surface every origin under `ranges`. Never guess IDs — list first. No service call.",classificationPrompt:"Retrieve stored reachable range results from session history.",inputSchema:Eo,outputSchema:Ro,execute:async(e,t)=>{const{id:r}=e,s=t.ranges.entryMode;if(!r)return{entries:[...t.ranges.entries].reverse().map(({id:e,label:t,timestamp:r,ranges:s})=>({id:e,label:t,timestamp:r,originCount:s.length})),entryMode:s};const o=t.ranges.entries.find(e=>e.id===r);return o?{id:o.id,label:o.label,timestamp:o.timestamp,ranges:o.ranges.map(e=>({origin:{...e.origin,position:e.origin.position},budgets:e.budgets})),entryMode:s}:{error:`No entry found with id "${r}"`}},tags:["recall","reachable-range","state"],examples:["recallRanges()",'recallRanges({ id: "ranges-0" })'],examplePrompts:["What range did I calculate earlier?","Recall the isochrone from Amsterdam"],relatedTools:["findReachableAreas"]},recallState:{description:"Single-shot session snapshot: places / routes / ranges entries (id+label) + per-slice `entryMode` + active map style + POIs / traffic visualization configs. Read-only. Use as orientation before making decisions that depend on what is already loaded.",classificationPrompt:"Inventory the agent's session: list every places / routes / ranges entry by id+label, plus current map style and POIs/traffic visualization configs.",inputSchema:Bo,outputSchema:qo,execute:async(e,t)=>{const r=e=>[...e].sort((e,t)=>t.timestamp-e.timestamp),s=r(t.places.entries).map(({id:e,label:t})=>({id:e,label:t})),o=r(t.routing.entries).map(({id:e,label:t})=>({id:e,label:t})),n=r(t.ranges.entries).map(({id:e,label:t})=>({id:e,label:t})),a="string"==typeof(i=t.baseMap.ttMap.getStyle())?i:i&&"object"==typeof i&&"id"in i?i.id:void 0;var i;const l=void 0===a?null:{id:a},c=t.mapPOIs.poisModule?.getConfig(),d=c?{..."boolean"==typeof c.visible&&{visible:c.visible},...c.filters?.categories?.show&&{filterMode:c.filters.categories.show},...c.filters?.categories?.values&&{categories:[...c.filters.categories.values]}}:null,u=t.trafficTiles.trafficFlowModule?.getConfig(),p=t.trafficTiles.trafficIncidentsModule?.getConfig(),h={flow:u?{..."boolean"==typeof u.visible&&{visible:u.visible}}:null,incidents:p?{..."boolean"==typeof p.visible&&{visible:p.visible}}:null};return{places:s,routes:o,ranges:n,entryModes:{places:t.places.entryMode,routes:t.routing.entryMode,ranges:t.ranges.entryMode},mapStyle:l,mapPOIs:d,traffic:h}},tags:["recall","state","overview"],examples:["recallState()"],examplePrompts:["What do you have so far?","List everything you remember from this session","Show me the current map / data overview"],relatedTools:["recallPlaces","recallRoutes","recallRanges","resetState"]},resetState:{description:'Wipe the session: drops every places/routes/ranges entry + their map renders, resets POIs + traffic configs to defaults, reverts the map style to "standardLight". Destructive — use only when the user explicitly asks for a clean slate ("start over", "reset", "clear everything").',classificationPrompt:"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.",inputSchema:Uo,outputSchema:Go,execute:async(e,t)=>{try{await t.places.clearShownEntries();for(const e of t.places.entries)await(e._modules?.geometries?.clear());for(const e of t.routing.entries)e._module&&(await e._module.clearRoutes(),await e._module.clearWaypoints());for(const e of t.ranges.entries)await(e._modules?.geometries?.clear()),await(e._modules?.places?.clear());return await(t.trafficAreaAnalytics.trafficAreaAnalyticsModule?.clear()),t.mapPOIs.poisModule?.resetConfig(),t.trafficTiles.trafficFlowModule?.resetConfig(),t.trafficTiles.trafficIncidentsModule?.resetConfig(),t.places.reset(),t.routing.reset(),t.ranges.reset(),t.baseMap.ttMap.setStyle("standardLight",{keepState:!1}),{success:!0,cleared:["places","routes","ranges","mapPOIs","traffic","mapStyle"]}}catch(r){return{error:`Failed to reset state: ${r instanceof Error?r.message:String(r)}`}}},tags:["state","reset","destructive"],examples:["resetState()"],examplePrompts:["Start over","Reset everything","Clear all my data and the map"],relatedTools:["recallState","clearMap"]},setEntryMode:{description:'Switch the display policy of one history slice (places, routes, or ranges) between `multiple` and `single`. `single` ⇒ at most one entry on the map, and any older entries get dropped from history immediately. Use when the user asks for a "clean view" / "only show one at a time" or wants to compare overlays again.',classificationPrompt:"Switch a slice (places/routes/ranges) between `multiple` (overlay several entries) and `single` (latest only). `single` drops older entries.",inputSchema:Ho,outputSchema:Vo,execute:async(e,t)=>{const{slice:r,mode:s}=e;try{const e=(()=>{switch(r){case"places":return t.places.entries.map(e=>e.id);case"routes":return t.routing.entries.map(e=>e.id);case"ranges":return t.ranges.entries.map(e=>e.id)}})();switch(r){case"places":await t.places.setEntryMode(s);break;case"routes":await t.routing.setEntryMode(s);break;case"ranges":await t.ranges.setEntryMode(s)}const o=(()=>{switch(r){case"places":return new Set(t.places.entries.map(e=>e.id));case"routes":return new Set(t.routing.entries.map(e=>e.id));case"ranges":return new Set(t.ranges.entries.map(e=>e.id))}})();return{success:!0,slice:r,mode:s,droppedEntryIds:e.filter(e=>!o.has(e))}}catch(o){return{error:`Failed to set entry mode: ${o instanceof Error?o.message:String(o)}`}}},tags:["state","mode","display"],examples:['setEntryMode({ slice: "places", mode: "single" })','setEntryMode({ slice: "routes", mode: "multiple" })'],examplePrompts:["Only show one route at a time","Let me overlay multiple places searches","Switch ranges back to single mode"],relatedTools:["recallState","recallPlaces","recallRoutes","recallRanges"]}},Gn=Object.keys(Bn),Un=(e,t)=>{if(!e)return t;if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},Wn=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,zn=(e,r)=>{if(!r.model)throw new Error("MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.");const o=((e,t)=>{const r={places:new ce(e),mapPOIs:new ae(e),routing:new ue(e),baseMap:new Z(e),trafficTiles:new xt(e),trafficAreaAnalytics:new pe(e),trafficIncidents:new St(e),ranges:new de(e),customGeometries:new te(e)};return t&&Object.assign(r,t),r})(e,r.state),{tools:n,toolsMetadata:a}=Vt(X(!1===r.includeDefaultTools?{}:Bn,r.tools),o,{outputSchemas:r.outputSchemas,featureFlags:r.featureFlags}),i=!1===r.classifier?null:r.classifier??Sr({model:r.model}),l=Mt(r.systemPrompt,r.systemPromptSuffix);let c=null;const d=new t({model:r.model,tools:n,instructions:l,stopWhen:s(r.maxSteps??10),prepareStep:async e=>{let t;if(0===e.stepNumber){if(c=null,i)try{c=await i({messages:e.messages,toolsMetadata:a})}catch{c=null}r.onClassify?.(c)}c&&(t=c.activeToolNames);const s=await(r.prepareStep?.(e)),o=r.stepProviderOptions?.({activeTools:t,stepNumber:e.stepNumber});if(s){const e=o||s.providerOptions?{...o,...s.providerOptions}:void 0;return{...s,activeTools:Un(t,s.activeTools),providerOptions:e}}return{...t?{activeTools:t}:{},...o?{providerOptions:o}:{}}},providerOptions:r.providerOptions});return Object.assign(d,{state:o,destroy:()=>(e=>{for(const t of Object.values(e))Wn(t)&&t.reset()})(o)})};export{Pt as BASE_SYSTEM_PROMPT,Bn as DEFAULT_TOOLS,Gn as TOOL_NAMES,Mt as buildSystemPrompt,br as classifyUserIntent,Sr as createDefaultClassifier,zn as createMapAgent,Ir as extractLastUserText,ne as formatStateDigestDiff,be as geometriesIdSchema,re as getStateDigest,X as resolveTools};
|
|
1
|
+
import{Output as e,ToolLoopAgent as t,generateText as r,stepCountIs as s}from"ai";import{BaseMapModule as o,CustomGeoJSONModule as n,GeometriesModule as i,HillshadeModule as a,POIsModule as l,PlacesModule as c,RoutingModule as d,TrafficAreaAnalyticsModule as u,TrafficFlowModule as p,TrafficIncidentOverlayModule as h,TrafficIncidentsModule as m,baseMapLayerGroupNames as y,reachableRangeGeometryConfig as f,standardStyleIDs as g,themedGeometryConfig as b}from"@tomtom-org/maps-sdk/map";import{POPULATED_AREA_TAGS as w,alongRouteSearch as v,calculateReachableRanges as I,calculateRoute as S,explorationSearch as E,geocode as M,geocodeOne as x,geometryData as P,getPOICategoryCodes as D,reverseGeocode as _,routeTypes as T,search as A,searchOne as C,trafficAreaAnalytics as k,trafficIncidentDetails as O}from"@tomtom-org/maps-sdk/services";import{TomTomConfig as R,avoidableTypes as L,bboxFromGeoJSON as F,calculateProgressAtRoutePoint as N,formatDistance as j,formatDuration as q,getCoordinateAtRouteProgress as B,getPosition as $,getProgressAtNearestRoutePoint as G,getRouteProgressBetween as U,getRouteProgressForSection as W,getSectionBBox as V,poiCategories as z,sectionTypes as H,trafficIncidentRequestCategories as Y,withInsertedWaypoints as J}from"@tomtom-org/maps-sdk/core";import*as X from"@turf/turf";import*as K from"h3-js";import{z as Z}from"zod";var Q=(e,t)=>{if(!e)return t;if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},ee=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,te=(e,t)=>{if(e||t)return{...e,...t}},re=(e,t)=>{const r={...e};if(!t)return r;for(const[s,o]of Object.entries(t))o?r[s]=o:delete r[s];return r},se=class{ttMap;_baseMapModule;_hillshadeModule;constructor(e){this.ttMap=e}get mapLibreMap(){return this.ttMap.mapLibreMap}async getBaseMapModule(){return this._baseMapModule??=await o.get(this.ttMap),this._baseMapModule}async getHillshadeModule(){return this._hillshadeModule??=await a.get(this.ttMap),this._hillshadeModule}reset(){this._baseMapModule=void 0,this._hillshadeModule=void 0}},oe=(e,t)=>{const r=new Set(t);if(!r.has(e))return e;for(let s=2;;s++){const t=`${e}-${s}`;if(!r.has(t))return t}},ne=async(e,t,r={})=>{if(0!==e.length)if(r.parallel)await Promise.all(e.map(t));else for(const s of e)await t(s)},ie=async(e,t,r={})=>{if(e.length<=1)return[...e];const s=e[e.length-1];return await ne(e.slice(0,-1),t,r),[s]},ae=class{handlers={};on(e,t){return(this.handlers[e]??(this.handlers[e]=[])).push(t),()=>{const r=this.handlers[e];if(!r)return;const s=r.indexOf(t);-1!==s&&r.splice(s,1)}}off(e){this.handlers[e]&&(this.handlers[e].length=0)}emit(e,t){const r=this.handlers[e];if(r?.length)for(const s of[...r])s(t)}clear(){for(const e of Object.keys(this.handlers))this.handlers[e]=[]}},le=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const s of e){const e="string"==typeof s.id?s.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(s)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},ce=class{_ttMap;_entries=[];_entryMode="multiple";events=new ae;constructor(e){this._ttMap=e}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",this._entries)),this.events.emit("mode-change",e))}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r,s){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const o=`geometries-${this._entries.length}`,n=s?oe(s,this._entries.map(e=>e.id)):o;return this._entries.push({id:n,timestamp:Date.now(),label:r,features:e,provenance:t}),this.events.emit("entries-change",this._entries),n}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e),s={...b(),theme:t};return r._module?r._shownTheme!==t&&(r._module.applyConfig(s),r._shownTheme=t):(r._module=await i.get(this._ttMap,s),r._shownTheme=t),r._module}async showEntry(e,t="outline",r="replace"){const s=this._requireEntry(e);await this._hideOthersUnderSingleMode(e);const o=await this.getEntryGeometriesModule(e,t),n=s.features;"add"===r?await le(o,n):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(o,n);const i=s._shown;s._shown=!0,i||this.events.emit("shown-change",this.shownEntryIds)}async _hideOthersUnderSingleMode(e){if("single"!==this._entryMode)return;const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const r of t)await this.hideEntry(r.id)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._module&&await t._module.clear(),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`CustomGeometriesState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){for(const e of this._entries)e._module?.clear();this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},de=(e,t)=>e.localeCompare(t),ue=e=>[...e].sort(de),pe=e=>({places:{shownAsPin:ue(e.places.shownAsPinIds),shownAsBaseMap:ue(e.places.shownAsBaseMapIds),entryMode:e.places.entryMode},routes:{shown:ue(e.routing.shownEntryIds),entryMode:e.routing.entryMode},ranges:{shown:ue(e.ranges.shownEntryIds),entryMode:e.ranges.entryMode},traffic:{shown:ue(e.trafficAreaAnalytics.shownEntryIds),entryMode:e.trafficAreaAnalytics.entryMode,entryCount:e.trafficAreaAnalytics.entries.length},incidents:{shown:ue(e.trafficIncidents.shownEntryIds),monitored:ue(e.trafficIncidents.entries.filter(e=>"running"===e._monitor?.status).map(e=>e.id)),entryMode:e.trafficIncidents.entryMode},customGeometries:{shown:ue(e.customGeometries.shownEntryIds),entryMode:e.customGeometries.entryMode,entryCount:e.customGeometries.entries.length},byod:{shown:ue(e.byod.shownEntryIds),entryMode:e.byod.entryMode,entryCount:e.byod.entries.length}}),he=(e,t)=>{if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(e[r]!==t[r])return!1;return!0},me=(e,t,r)=>{if(!he(t,r))return 0===r.length?`${e} cleared`:`${e} shown: ${r.map(e=>`"${e}"`).join(", ")}`},ye=(e,t)=>{const r=[],s=((e,t)=>{const r=[];return he(e.shownAsPin,t.shownAsPin)||r.push(0===t.shownAsPin.length?"no places shown as pins":`places shown as pins: ${t.shownAsPin.map(e=>`"${e}"`).join(", ")}`),he(e.shownAsBaseMap,t.shownAsBaseMap)||r.push(0===t.shownAsBaseMap.length?"no places shown as base-map POIs":`places shown as base-map POIs: ${t.shownAsBaseMap.map(e=>`"${e}"`).join(", ")}`),r.length?r.join("; "):void 0})(e.places,t.places);s&&r.push(s);const o=me("routes",e.routes.shown,t.routes.shown);o&&r.push(o);const n=me("ranges",e.ranges.shown,t.ranges.shown);n&&r.push(n);const i=me("traffic analytics",e.traffic.shown,t.traffic.shown);i&&r.push(i);const a=me("incidents",e.incidents.shown,t.incidents.shown);a&&r.push(a);const l=me("incidents monitor",e.incidents.monitored,t.incidents.monitored);l&&r.push(l);const c=me("custom geometries",e.customGeometries.shown,t.customGeometries.shown);c&&r.push(c);const d=me("byod",e.byod.shown,t.byod.shown);return d&&r.push(d),e.places.entryMode!==t.places.entryMode&&r.push(`places entryMode → ${t.places.entryMode}`),e.routes.entryMode!==t.routes.entryMode&&r.push(`routes entryMode → ${t.routes.entryMode}`),e.ranges.entryMode!==t.ranges.entryMode&&r.push(`ranges entryMode → ${t.ranges.entryMode}`),e.incidents.entryMode!==t.incidents.entryMode&&r.push(`incidents entryMode → ${t.incidents.entryMode}`),e.customGeometries.entryMode!==t.customGeometries.entryMode&&r.push(`custom geometries entryMode → ${t.customGeometries.entryMode}`),e.byod.entryMode!==t.byod.entryMode&&r.push(`byod entryMode → ${t.byod.entryMode}`),0===r.length?null:`[map state changed since last response: ${r.join("; ")}]`},fe=class{_ttMap;_poisModule;constructor(e){this._ttMap=e}async getPOIsModule(){return this._poisModule??=await l.get(this._ttMap),this._poisModule}get poisModule(){return this._poisModule}reset(){this._poisModule=void 0}},ge=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const s of e){const e="string"==typeof s.id?s.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(s)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},be=class{_ttMap;_entries=[];_entryMode="multiple";events=new ae;constructor(e){this._ttMap=e}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this._hideEntry(e)),this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)),this.events.emit("mode-change",e))}async getEntryPlacesModule(e,t){const r=this._requireEntry(e);return r._modules??={},r._modules.places?r._shownAs!==t&&r._modules.places.applyTheme(t):r._modules.places=await c.get(this._ttMap,{theme:t}),r._modules.places}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);r._modules??={};const s={...b(),theme:t};return r._modules.geometries?r._modules.geometries.applyConfig(s):r._modules.geometries=await i.get(this._ttMap,s),r._modules.geometries}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`PlacesState: unknown entry "${e}"`);return t}get entries(){return this._entries}get latestPlace(){return this._entries.at(-1)?.places}async addPlaceResult(e,t,r,s,o){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this._hideEntry(e)),this._entries=[]);const n=`places-${this._entries.length}`,i=r?oe(r,this._entries.map(e=>e.id)):n;return this._entries.push({id:i,timestamp:Date.now(),label:t,places:"features"in e?e.features:[e],...s?.length&&{connections:s},...o?.length&&{geometries:o}}),this.events.emit("entries-change",this._entries),o?.length&&this.events.emit("geometries-change",{entryId:i,count:o.length}),i}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}findPlaceById(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(!r._byId){r._byId={};for(const e of r.places)r._byId[e.id]=e}const s=r._byId[e];if(s)return{place:s,entryId:r.id}}}getGeometryForPlace(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(!r.geometries?.length)continue;r._geometryByPlaceId||(r._geometryByPlaceId=this._buildGeometryIndex(r));const s=r._geometryByPlaceId[e];if(s)return s}}async fetchPlaceGeometry(e){const t=this.getGeometryForPlace(e);if(t)return t;const r=this.findPlaceById(e);if(!r)return;if(!r.place.properties.dataSources?.geometry?.id)return;const s=(await P({geometries:[r.place]})).features[0];if(!s)return;const o=this._entries.find(e=>e.id===r.entryId);return o&&(o.geometries??=[],o.geometries.push(s),o._geometryByPlaceId??={},o._geometryByPlaceId[e]=s,this.events.emit("geometries-change",{entryId:o.id,count:o.geometries.length})),s}async fetchGeometriesForEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)return[];const r=/* @__PURE__ */new Set;for(const l of t.geometries??[])"string"==typeof l.id&&r.add(l.id);const s=[],o=/* @__PURE__ */new Set;for(const l of t.places){const e=l.properties.dataSources?.geometry?.id;e&&(r.has(e)||o.has(e)||(o.add(e),s.push(l)))}if(0===s.length)return t.geometries??[];const n=[];for(let l=0;l<s.length;l+=5)n.push(s.slice(l,l+5));const i=await Promise.all(n.map(e=>P({geometries:e}))),a=[...t.geometries??[]];for(const l of i)a.push(...l.features);return t.geometries=a,t._geometryByPlaceId=void 0,this.events.emit("geometries-change",{entryId:t.id,count:a.length}),a}async showPlaceGeometries(e,t="outline",r="replace",s){const o=s??this._latestGeometriesEntryId();if(!o)return;const n=await this.getEntryGeometriesModule(o,t);"add"===r?await ge(n,e):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(n,e)}_latestGeometriesEntryId(){for(let e=this._entries.length-1;e>=0;e--)if(this._entries[e].geometries?.length)return this._entries[e].id;return this._entries.at(-1)?.id}_buildGeometryIndex(e){const t=/* @__PURE__ */new Map;for(const s of e.geometries??[])"string"==typeof s.id&&t.set(s.id,s);const r={};for(const s of e.places){const e=s.properties.dataSources?.geometry?.id;if(!e)continue;const o=t.get(e);o&&(r[s.id]=o)}return r}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shownAs&&e.add(t.id);return e}get shownAsPinIds(){return this._idsShownAs("pin")}get shownAsBaseMapIds(){return this._idsShownAs("base-map")}get shownAsPinClusteredIds(){return this._idsShownAs("pin-clustered")}_idsShownAs(e){const t=/* @__PURE__ */new Set;for(const r of this._entries)r._shownAs===e&&t.add(r.id);return t}getShownMarkerType(e){return this._entries.find(t=>t.id===e)?._shownAs}getShownFeatures(){const e=[];for(const t of this._entries)t._shownAs&&e.push(...t.places);return e}async setShownEntries(e,t){const r=new Set(e),s=[],o=[];for(const n of this._entries)r.has(n.id)?s.push(n):n._shownAs===t&&o.push(n);for(const n of o)await this._hideEntry(n);for(const n of s)await this._showEntryAs(n,t);return this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async addShownEntries(e,t){if("single"===this._entryMode){const r=e.at(-1);return r?this.setShownEntries([r],t):this.getShownFeatures()}const r=e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?[t]:[]}).filter(e=>e._shownAs!==t);for(const s of r)await this._showEntryAs(s,t);return r.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async removeShownEntries(e){const t=e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._shownAs?[t]:[]});for(const r of t)await this._hideEntry(r);return t.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async clearShownEntries(){const e=this._entries.filter(e=>e._shownAs);if(0!==e.length){for(const t of e)await this._hideEntry(t);this.events.emit("shown-change",this.shownEntryIds)}}async clearShown(){await this.clearShownEntries(),await this.clearShownGeometries()}async clearShownGeometries(e){const t=e?e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._modules?.geometries?[t]:[]}):this._entries.filter(e=>e._modules?.geometries);if(0!==t.length)for(const r of t)await r._modules.geometries.clear(),this.events.emit("geometries-change",{entryId:r.id,count:0})}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],s=!!r._shownAs;r._modules&&(await(r._modules.places?.clear()),await(r._modules.geometries?.clear())),this._entries.splice(t,1),this.events.emit("entries-change",this._entries),s&&this.events.emit("shown-change",this.shownEntryIds)}async _showEntryAs(e,t){const r=await this.getEntryPlacesModule(e.id,t);await r.show({type:"FeatureCollection",features:e.places}),e.connections?.length&&await r.showConnections(e.connections),e._shownAs=t}async _hideEntry(e){e._shownAs&&(e._modules?.places&&await e._modules.places.clear(),e._shownAs=void 0)}reset(){for(const e of this._entries)e._modules&&(e._modules.places?.clear(),e._modules.geometries?.clear());this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},we=class{_ttMap;_entries=[];_entryMode="multiple";events=new ae;constructor(e){this._ttMap=e}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",this._entries)),this.events.emit("mode-change",e))}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);return r._modules??={},r._modules.geometries?r._shownTheme!==t&&(r._modules.geometries.applyConfig(f(void 0,t)),r._shownTheme=t):(r._modules.geometries=await i.get(this._ttMap,f(void 0,t)),r._shownTheme=t),r._modules.geometries}async getEntryPlacesModule(e){const t=this._requireEntry(e);return t._modules??={},t._modules.places||(t._modules.places=await c.get(this._ttMap)),t._modules.places}async markEntryShown(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._modules?.geometries&&await t._modules.geometries.clear(),t._modules?.places&&await t._modules.places.clear(),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RangeState: unknown entry "${e}"`);return t}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}async addEntry(e){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const t=`ranges-${this._entries.length}`;return this._entries.push({id:t,timestamp:Date.now(),...e}),this.events.emit("entries-change",this._entries),t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){for(const e of this._entries)e._modules?.geometries?.clear(),e._modules?.places?.clear();this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},ve=class{_ttMap;_entries=[];_planningSlots=[];_params={};_entryMode="multiple";_mainColor;_lastShownEntryId;events=new ae;constructor(e){this._ttMap=e}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",this._entries)),this.events.emit("mode-change",e))}async getEntryRoutingModule(e){const t=this._requireEntry(e);return t._module||(t._module=await d.get(this._ttMap)),t._module}async showEntry(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=await this.getEntryRoutingModule(e);void 0!==this._mainColor&&r.applyConfig({theme:{mainColor:this._mainColor}}),await r.showRoutes(t.data),t.waypoints.length&&await r.showWaypoints(t.waypoints);const s=t._shown;t._shown=!0,this._lastShownEntryId=e,s||this.events.emit("shown-change",this.shownEntryIds)}get mainColor(){return this._mainColor}setMainColor(e){if(this._mainColor=e,void 0!==e)for(const t of this._entries)t._shown&&t._module&&t._module.applyConfig({theme:{mainColor:e}})}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._module&&(await t._module.clearRoutes(),await t._module.clearWaypoints()),t._shown=!1,this._lastShownEntryId===e&&(this._lastShownEntryId=this._entries.findLast(e=>e._shown)?.id),this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RoutingState: unknown entry "${e}"`);return t}get currentEntryModule(){if(!this._lastShownEntryId)return;const e=this._entries.find(e=>e.id===this._lastShownEntryId);return e?._shown?e._module:void 0}get entries(){return this._entries}get currentRoutes(){return this._entries.at(-1)?.data}get currentWaypoints(){return this._entries.at(-1)?.waypoints}get planningSlots(){return this._planningSlots}get params(){return this._params}async addRoutes(e,t,r){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=`routes-${this._entries.length}`;return this._entries.push({id:s,timestamp:Date.now(),label:r,data:e,waypoints:t,params:{...this._params}}),this._planningSlots=[...t],this.events.emit("entries-change",this._entries),this.events.emit("planning-change",this._planningSlots),s}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}setWaypointAt(e,t){const r=Math.max(2,e+1);for(;this._planningSlots.length<r;)this._planningSlots.push(null);this._planningSlots[e]=t,this.events.emit("planning-change",this._planningSlots)}setParams(e){this._params={...this._params,...e},this.events.emit("params-change",this._params)}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){for(const e of this._entries)e._module?.clearRoutes(),e._module?.clearWaypoints();this._entries=[],this._planningSlots=[],this._params={},this._lastShownEntryId=void 0,this._mainColor=void 0,this.events.emit("entries-change",this._entries),this.events.emit("planning-change",this._planningSlots),this.events.emit("params-change",this._params),this.events.emit("shown-change",this.shownEntryIds)}},Ie={type:"circle",paint:{"circle-radius":4,"circle-color":"#0a3653","circle-stroke-color":"#ffffff","circle-stroke-width":1}},Se={type:"line",paint:{"line-color":"#0a3653","line-width":2}},Ee={type:"fill",paint:{"fill-color":"#0a3653","fill-opacity":.25,"fill-outline-color":"#0a3653"}},Me={Point:"point",MultiPoint:"point",LineString:"line",MultiLineString:"line",Polygon:"polygon",MultiPolygon:"polygon"},xe=e=>{const t=(e=>{const t=/* @__PURE__ */new Set;for(const r of e.features){const e=r.geometry;if(!e)continue;if("GeometryCollection"===e.type){for(const r of e.geometries){const e=Me[r.type];e&&t.add(e)}continue}const s=Me[e.type];s&&t.add(s)}return t})(e),r=[];return t.has("polygon")&&r.push(Ee),t.has("line")&&r.push(Se),t.has("point")&&r.push(Ie),0===r.length&&r.push(Ie),r},Pe=class{_ttMap;_entries=[];_entryMode="multiple";events=new ae;constructor(e){this._ttMap=e}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",this._entries)),this.events.emit("mode-change",e))}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r={}){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=oe(r.explicitId??`byod-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:s,timestamp:Date.now(),label:t,data:e,source:r.source??{kind:"integrator"},layers:r.layers??xe(e)}),this.events.emit("entries-change",this._entries),s}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await n.get(this._ttMap,{sources:{data:{layers:t.layers}}}),t._module}async showEntry(e){const t=this._requireEntry(e);await this._hideOthersUnderSingleMode(e),await(await this.getEntryModule(e)).show(t.data,"data");const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async _hideOthersUnderSingleMode(e){if("single"!==this._entryMode)return;const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const r of t)await this.hideEntry(r.id)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",this._entries))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`BYODState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){for(const e of this._entries)e._module?.clear().catch(()=>{});this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},De=class{_ttMap;_entries=[];_entryMode="multiple";events=new ae;constructor(e){this._ttMap=e}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",this._entries)),this.events.emit("mode-change",e))}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r,s){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const o=`tta-${this._entries.length}`,n=s?oe(s,this._entries.map(e=>e.id)):o;return this._entries.push({id:n,timestamp:Date.now(),label:t,data:e,params:r}),this.events.emit("entries-change",this._entries),n}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return!1;r._analysis??=[];const s=r._analysis.findIndex(e=>e.name===t.name);return s>=0?r._analysis[s]=t:r._analysis.push(t),this.events.emit("analysis-added",{entryId:e,analysis:t}),this.events.emit("entries-change",this._entries),!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module||(t._module=await u.get(this._ttMap),t._configChangeUnsub=t._module.events.on("config-change",r=>{t._config=r,this.events.emit("config-change",{entryId:e,config:r})})),t._module}async showEntry(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=await this.getEntryModule(e);await r.clear(),await r.show(t.data);const s=t._shown;t._shown=!0,s||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;await this.hideEntry(e);const r=this._entries[t];r._configChangeUnsub?.(),r._configChangeUnsub=void 0,r._module=void 0,this._entries.splice(t,1),this.events.emit("entries-change",this._entries)}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficAreaAnalyticsState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){for(const e of this._entries)e._configChangeUnsub?.(),e._module?.clear();this._entries=[],this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)}},_e=Z.discriminatedUnion("kind",[Z.object({kind:Z.literal("place").describe('Use the exact literal "place".'),id:Z.string().min(1).describe("Id of a single place; resolves to that place’s footprint.")}).describe("A single place footprint."),Z.object({kind:Z.literal("places").describe('Use the exact literal "places".'),id:Z.string().min(1).describe("Id of a places-entry; resolves to every footprint in the entry.")}).describe("Every footprint in a places entry."),Z.object({kind:Z.literal("ranges").describe('Use the exact literal "ranges".'),id:Z.string().min(1).describe("Id of a ranges-entry; resolves to every isochrone polygon in the entry.")}).describe("Every isochrone polygon in a ranges entry."),Z.object({kind:Z.literal("customGeometries").describe('Use the exact literal "customGeometries".'),id:Z.string().min(1).describe("Id of a custom-geometries entry produced by a previous `processData` call.")}).describe("A derived entry produced by a previous `processData` call.")]).describe('Tagged id `{ kind, id }`. `kind` must be exactly one of: "place" | "places" | "ranges" | "customGeometries" (do NOT use variants like "ranges-entry", "place-footprints", etc.). Use `recallGeometries` to list every available `{kind, id}` pair.'),Te=Z.array(_e).min(1).describe('Tagged ids `{ kind, id }` of polygon sources to feed the run. `kind` must be exactly one of `"place"` | `"places"` | `"ranges"` | `"customGeometries"` (no variants). Mix kinds freely (e.g. union place footprints with isochrones). Use `recallGeometries` to list available ids.'),Ae="`geometry.properties` shape varies by source — place footprints: `CommonPlaceProps`; ranges: service response (`range.type`, `range.value`); custom: whatever the producing op set. Treat as opaque unless you know the source. Every feature also carries `properties._source: { kind, id }` (the tagged id it was passed in under) — use it to filter / partition mixed inputs by role inside the sandbox.",Ce="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",ke="Tagged input ids that contributed at least one polygon to the run, in input order.",Oe='`places` is a FeatureCollection of Point Features. `Place.properties` shape:\n```ts\ntype PlaceProperties = {\n type: "POI" | "Street" | "Geography" | "Point Address" | "Address Range" | "Cross Street";\n address: {\n freeformAddress: string;\n streetNumber?: string; streetName?: string;\n municipality?: string; municipalitySubdivision?: string;\n countrySubdivision?: string; countrySubdivisionName?: string;\n countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\n postalCode?: string; extendedPostalCode?: string;\n countryCode?: string; countryCodeISO3?: string; country?: string;\n localName?: string;\n };\n poi?: { // when type === "POI"\n name: string; phone?: string; url?: string;\n brands?: string[];\n categories: string[]; // POICategory enum, e.g. "RESTAURANT", "CAFE_PUB", "GAS_STATION", "HOTEL_MOTEL", "ELECTRIC_VEHICLE_STATION"\n localizedCategories: string[]; // language-sensitive\n openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\n timeZone?: { name: string };\n };\n // Search hits also carry:\n score?: number; // relevance (higher = better)\n distance?: number; // meters from geoBias, when provided\n info?: string;\n // Geography hits also carry:\n geographyType?: Array<"Country"|"CountrySubdivision"|"CountrySecondarySubdivision"|"CountryTertiarySubdivision"|"Municipality"|"MunicipalitySubdivision"|"Neighbourhood"|"PostalCodeArea">;\n'+({}.experimentalSearch?' // Experimental — populated only when `experimentalSearch` is on and the place came from\n // the exploration-search backend (DE / NL / FR area-tag pipeline).\n areaId?: string; // id of the surrounding small-area polygon (few km², not a whole municipality)\n areaCountry?: string; // ISO2 country of the area polygon\n areaTags?: string[]; // e.g. "coastal", "walkable", "transit_connected"\n':"")+" // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\n};\n```\nAreas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`.",Re=Z.union([Z.array(Z.number()).refine(e=>4===e.length,{message:"BBox must have 4 elements"})]).describe("[minLng, minLat, maxLng, maxLat] or [W,S,E,N]"),Le=Z.object({type:Z.literal("Polygon"),coordinates:Z.array(Z.array(Z.array(Z.number())))}),Fe=Z.object({type:Z.literal("MultiPolygon"),coordinates:Z.array(Z.array(Z.array(Z.array(Z.number()))))}),Ne=Z.union([Le,Fe]),je=Z.string().regex(/^[a-z0-9][a-z0-9-]{1,48}$/,"Use lowercase letters, digits, and hyphens (2–49 chars).").optional().describe('Compact lowercase kebab-case id derived from the user prompt (e.g. "vegan-cafes-amsterdam", "ev-stations-along-route"). Avoid generic names like "places" / "results". Auto-suffixed on collision — do not track uniqueness yourself. Default: `places-N`.'),qe=Z.enum(["add","replace"]).optional().describe('WITHIN THE TARGETED ENTRY ONLY — controls how the new polygons combine with what is already rendered on this entry\'s GeometriesModule layer. Does NOT affect other entries (use `hidePreviousEntries` for that). "replace" (default) clears this entry\'s layer first; "add" stacks (deduped by feature id).'),Be=Z.object({show:Z.boolean().optional().describe("Also render the polygons on the map. Default: false."),mode:qe,theme:Z.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline".')}).optional().describe('Batch-fetch boundary polygons for the resulting places and cache them on the entry. Use when the user wants the *area / shape / boundary* (e.g. "outline this neighbourhood", "show the city footprint"). Set `show: true` to also render. Skips places without a geometry source (most addresses/streets).'),$e=Z.object({theme:Z.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline".'),placeIds:Z.array(Z.string()).optional().describe("Subset of place ids within the targeted entries — render boundaries only for these. Ids outside the entries are ignored. Default: all places with a geometry source."),mode:qe}).describe('Render boundary polygons for the targeted entries. Use when the user wants the *area / shape / outline / footprint* (e.g. "outline this neighbourhood", "show the city footprint"). Independent of `markerType` — set both to render pins AND boundaries.'),Ge=Z.enum(["base-map","pin","pin-clustered","none"]).describe('"pin" for few results; "base-map" for 10+; "pin-clustered" aggregates nearby pins under a count badge (best for dense city-scale POI sets — clusters expose aggregated `placeTypes` / `poiCategories`); "none" for towns/cities/areas.'),Ue=e=>Z.union([Z.literal("all"),Z.array(Z.string())]).optional().describe(`Which previously-shown ${e} entries to hide before showing the new ones. \`"all"\` clears every previous entry (use for the typical "show X" — replace existing); an array hides only the listed entry IDs. Omit to stack on top of existing entries (use for "also show", "additionally", "and X"). The entries being shown by this call are never hidden. Only applied when \`entryMode === "multiple"\` (in \`single\` mode every other entry is auto-hidden, so this prop is ignored).`),We=Z.object({markerType:Ge,zoomMode:Z.enum(["auto","none"]).describe('Camera behaviour. "none" keeps the map still.'),hidePreviousEntries:Ue("places")}),Ve=Z.object({markerType:Z.boolean(),zoomMode:Z.boolean()}),ze={mode:Z.literal("within"),viewport:Z.literal(!0).optional().describe("When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.")},He=Z.enum(["poi","place"]).optional().describe('Drives query resolution. `"poi"` for venues / landmarks / businesses (e.g. "Eiffel Tower"). `"place"` for addresses, cities, neighborhoods, parks, postal codes (e.g. "De Jordaan, Amsterdam", "1600 Pennsylvania Ave"). Default: "place".'),Ye=["boundingBox","viewport","query","placeId"],Je=Z.object({...ze,boundingBox:Re.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId."),query:Z.string().optional().describe('Resolve a name/address/POI → its boundary polygon (or bbox) as search area (e.g. "Paris", "De Jordaan, Amsterdam"). Pair with `queryAs` to disambiguate. EXCLUSIVE with boundingBox / viewport / placeId.'),queryAs:He,placeId:Z.string().optional().describe("ID of a session place — its boundary polygon (fetched on demand) is the search area. EXCLUSIVE with boundingBox / viewport / query.")}).refine(e=>1===Ye.filter(t=>void 0!==e[t]).length,{message:"`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId."}),Xe=["position","viewport","query"],Ke=Z.object({mode:Z.literal("nearby"),position:Z.array(Z.number()).length(2).optional().describe("Bias point [lng, lat]. EXCLUSIVE with viewport / query."),viewport:Z.literal(!0).optional().describe("When true, bias toward the current map viewport centre. EXCLUSIVE with position / query."),query:Z.string().optional().describe("Resolve a name/address/POI → its centroid as bias point. Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport."),queryAs:He,radiusMeters:Z.number().positive().optional().describe("Hard radius cap (metres) around the bias point. Independent of bias source.")}).refine(e=>1===Xe.filter(t=>void 0!==e[t]).length,{message:"`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent)."}),Ze=Z.object({mode:Z.literal("global")}),Qe=Z.union([Je,Ke,Ze]),et=["places","routes","incidents","customGeometries","trafficAreaAnalytics","byod"],tt=e=>Z.object({kinds:Z.array(Z.enum(e)).min(1)}),rt={places:{fieldName:"placesEntryIDs",fieldLabel:"`placesEntryIDs`",buildField:e=>(({verb:e,extra:t})=>Z.array(Z.string()).min(1).optional().describe(`IDs of existing places entries to ${e} (e.g. ["places-2"], ["places-1", "places-2"]). Use recallPlaces to list IDs. Omit to ${e} the latest entry.`+(t?` ${t}`:"")))({verb:e.verb,extra:"Injected as `places` (merged) and `placesByEntry[id]`. Undefined when omitted. "+e.requiredHint}),sandboxDoc:"• `places` — merged FeatureCollection over `placesEntryIDs`; `placesByEntry[id]` keeps them separate.",schemaDoc:Oe,recallTool:"recallPlaces"},routes:{fieldName:"routesEntryIDs",fieldLabel:"`routesEntryIDs`",buildField:e=>(({verb:e,extra:t})=>Z.array(Z.string()).min(1).optional().describe(`IDs of existing routes entries to ${e} (e.g. ["routes-0"], ["routes-0", "routes-1"]). Use recallRoutes to list IDs. Omit to ${e} the latest route.`+(t?` ${t}`:"")))({verb:e.verb,extra:"Injected as `routes` (merged) and `routesByEntry[id]`. Undefined when omitted. "+e.requiredHint}),sandboxDoc:"• `routes` — merged FeatureCollection over `routesEntryIDs`; `routesByEntry[id]` keeps them separate.",schemaDoc:'`routes` is a FeatureCollection of LineString Features (each route has `feature.bbox`). `Route.properties` shape:\n```ts\ntype RouteProperties = {\n index: number; // 0 = main route, 1+ = alternatives\n summary: {\n lengthInMeters: number;\n travelTimeInSeconds: number; // includes traffic delay\n noTrafficTravelTimeInSeconds?: number;\n trafficDelayInSeconds: number;\n trafficLengthInMeters: number;\n departureTime: Date; arrivalTime: Date;\n // EV-only:\n batteryConsumptionInkWh?: number; batteryConsumptionInPCT?: number;\n remainingChargeAtArrivalInkWh?: number; remainingChargeAtArrivalInPCT?: number;\n totalChargingTimeInSeconds?: number;\n // Combustion-only:\n fuelConsumptionInLiters?: number;\n };\n // Segments grouped by characteristic. Every section has { id, startPointIndex, endPointIndex }\n // unless noted; *Index values reference `geometry.coordinates`.\n sections: {\n leg: Array<{ id; startPointIndex?; endPointIndex?; summary: RouteSummary }>; // ALWAYS present; one per non-circle waypoint pair\n country?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // ISO 3166-1 alpha-3\n tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>;\n traffic?: Array<{\n id; startPointIndex; endPointIndex;\n categories: TrafficIncidentCategory[]; // e.g. "Jam","Accident","RoadWorks","Closure"\n magnitudeOfDelay: "Unknown"|"Minor"|"Moderate"|"Major"|"Undefined";\n tec: { effectCode?: number; causes?: Array<{ mainCauseCode: number; subCauseCode?: number }> };\n effectiveSpeedInKmh?: number;\n delayInSeconds?: number;\n }>;\n importantRoadStretch?: Array<{ id; startPointIndex; endPointIndex; index: number; streetName?: string; roadNumbers?: string[] }>;\n lanes?: Array<{\n id; startPointIndex; endPointIndex;\n lanes: Array<{ directions: PossibleLaneDirection[]; follow?: PossibleLaneDirection }>;\n laneSeparators: PossibleLaneSeparator[];\n properties?: string[]; // e.g. "IS_MANEUVER"\n }>;\n speedLimit?: Array<{ id; startPointIndex; endPointIndex; maxSpeedLimitInKmh: number }>;\n roadShields?: Array<{ id; startPointIndex; endPointIndex; roadShieldReferences: RoadShieldReference[] }>;\n // Plain section ranges (id + startPointIndex + endPointIndex only):\n ferry?: SectionProps[]; motorway?: SectionProps[]; toll?: SectionProps[]; tunnel?: SectionProps[];\n unpaved?: SectionProps[]; urban?: SectionProps[]; pedestrian?: SectionProps[];\n carpool?: SectionProps[]; carTrain?: SectionProps[];\n lowEmissionZone?: SectionProps[]; vehicleRestricted?: SectionProps[];\n };\n progress?: Array<{ pointIndex: number; travelTimeInSeconds?: number; distanceInMeters?: number }>; // cumulative milestones\n guidance?: { instructions: Array<{ pointIndex: number; maneuver: string; street?: string; message?: string }> };\n};\n```\n`pointIndex` values reference positions in `geometry.coordinates`. Guard `?` fields with `?.` / `??`. For "how long / how far" questions, use `summary.lengthInMeters` / `summary.travelTimeInSeconds`.',recallTool:"recallRoutes"},incidents:{fieldName:"incidentsEntryIDs",fieldLabel:"`incidentsEntryIDs`",buildField:e=>Z.array(Z.string()).min(1).optional().describe(`IDs of existing incidents entries to ${e.verb} (e.g. ["incidents-0"]). Injected as \`incidents\` (merged) and \`incidentsByEntry[id]\`. Undefined when omitted. Use \`recallState\` to list incident entries. `+e.requiredHint),sandboxDoc:"• `incidents` — flat `TrafficIncident[]` over `incidentsEntryIDs`; `incidentsByEntry[id]` keeps them separate.",schemaDoc:"",recallTool:"recallState"},customGeometries:{fieldName:"geometriesEntryIDs",fieldLabel:"`geometriesEntryIDs`",buildField:e=>Te.optional().describe("Tagged polygon sources `{ kind, id }` (see geometriesEntryIDsSchema). Injected as `geometries`. Undefined when omitted. "+e.requiredHint),sandboxDoc:"• `geometries` — mixed Polygon/MultiPolygon array over `geometriesEntryIDs`; every feature carries `properties._source: { kind, id }` so you can filter by role (see `GEOMETRIES_PROPS_DOC`).",schemaDoc:"`geometries` is an array of Polygon/MultiPolygon Features. Each has `id` (the geometry-data-source id, matches `place.properties.dataSources.geometry.id`) and `bbox`. `properties` varies by source — see `GEOMETRIES_PROPS_DOC`. Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, `turf.buffer`, … directly on these features. For h3, convert via `h3.polygonToCells(feature.geometry.coordinates, res)`.",recallTool:"recallGeometries"},trafficAreaAnalytics:{fieldName:"trafficAreaAnalyticsEntryIDs",fieldLabel:"`trafficAreaAnalyticsEntryIDs`",buildField:e=>Z.array(Z.string()).min(1).optional().describe(`IDs of existing traffic-area-analytics entries to ${e.verb} (e.g. ["tta-0"]). Injected as \`trafficAreaAnalytics\` (merged FeatureCollection of tile/hex regions with metric properties) and \`trafficAreaAnalyticsByEntry[id]\`. Undefined when omitted. Use \`recallState\` to list available ids. `+e.requiredHint),sandboxDoc:"• `trafficAreaAnalytics` — merged TrafficAreaAnalytics FeatureCollection over `trafficAreaAnalyticsEntryIDs`. Each feature is a tile / hex region with metric properties on `feature.properties` (e.g. `congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`). `trafficAreaAnalyticsByEntry[id]` keeps each request's collection separate. The collection's top-level `properties` (metrics list, date range) come from the first entry when ids span multiple requests.",schemaDoc:"",recallTool:"recallState"},byod:{fieldName:"byodEntryIDs",fieldLabel:"`byodEntryIDs`",buildField:e=>Z.array(Z.string()).min(1).optional().describe(`IDs of existing BYOD entries to ${e.verb} (e.g. ["byod-0"]) — customer-authored GeoJSON layers. Injected as \`byod\` (merged FeatureCollection) and \`byodByEntry[id]\`. Undefined when omitted. Use \`recallByod\` to list available ids. `+e.requiredHint),sandboxDoc:"• `byod` — merged customer-authored GeoJSON FeatureCollection over `byodEntryIDs`; mixed Point / LineString / Polygon features are normal (BYOD layers carry whatever the integrator uploaded). `byodByEntry[id]` keeps each entry separate. Treat `feature.properties` as opaque application-specific data.",schemaDoc:"",recallTool:"recallByod"}},st=(e,t)=>et.filter(r=>e.includes(r)&&(!t||t.kinds.includes(r))),ot=e=>e.map(e=>rt[e].fieldLabel).join(" / "),nt=e=>et.filter(t=>e.includes(t)).map(e=>rt[e].sandboxDoc).join("\n"),it=e=>et.filter(t=>e.includes(t)).map(e=>rt[e].schemaDoc).filter(e=>e.length>0).join("\n\n"),at=(e,t)=>{const r={};for(const s of et){if(!e.includes(s))continue;const o=rt[s];r[o.fieldName]=o.buildField(t)}return r},lt=e=>{for(const t of et)if(e[rt[t].fieldName]?.length)return!0;return!1},ct=e=>`At least one of ${ot(e)} must be set. Call ${Array.from(new Set(e.map(e=>rt[e].recallTool))).map(e=>`\`${e}\``).join(" / ")} first to list available ids.`,dt=(Z.union([Z.tuple([Z.number(),Z.number(),Z.number(),Z.number()]),Z.object({bbox:Z.tuple([Z.number(),Z.number(),Z.number(),Z.number()]),padding:Z.number().optional(),animate:Z.boolean().optional()})]).describe("Bounds to fit the camera to: a `[W, S, E, N]` BBox tuple, or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`. Default padding: 50px."),Z.object({bbox:Z.tuple([Z.number(),Z.number(),Z.number(),Z.number()]).describe("Bounds the camera was fit to."),padding:Z.number().describe("Padding (px) applied around the bbox.")})),ut=e=>Array.isArray(e)&&4===e.length&&e.every(e=>"number"==typeof e&&Number.isFinite(e)),pt=e=>{if(ut(e))return!0;if(!e||"object"!=typeof e)return!1;const t=e;return!!ut(t.bbox)&&(!!(void 0===t.padding||"number"==typeof t.padding&&Number.isFinite(t.padding))&&!(void 0!==t.animate&&"boolean"!=typeof t.animate))},ht=(e,t)=>{const r=ut(t)?t:t.bbox,s=(ut(t)?void 0:t.padding)??50,o=ut(t)?void 0:t.animate;return e.baseMap.mapLibreMap.fitBounds(r,{padding:s,...void 0!==o&&{animate:o}}),{bbox:r,padding:s}},mt=async(e,t,r)=>{if("multiple"!==e.entryMode||void 0===r)return;const s=new Set(t),o="all"===r?[...e.shownEntryIds].filter(e=>!s.has(e)):r.filter(e=>!s.has(e));for(const n of o)await e.hideEntry(n)},yt=Z.union([Z.object({query:Z.string().describe("Location string to resolve."),queryAs:Z.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.')}),Z.object({position:Z.object({lng:Z.number(),lat:Z.number()}).describe("Explicit coordinates — use when you already have a position.")}),Z.object({placeId:Z.string().describe("ID of a place stored in session state (any previous places entry). Resolves to the full Place with its POI name, address, and coordinates — prefer this over re-searching by name when routing to a place the user has already seen.")})]),ft=Z.object({source:_e,reason:Z.string()}),gt=e=>`${e.source.kind}:${e.source.id}: ${e.reason}`,bt=(e,t)=>({...e,properties:{...e.properties??{},_source:t}}),wt=async(e,t)=>{const r=t.places.findPlaceById(e);if(!r)return{kind:"skip",reason:"No place with this id in session state. Use `recallGeometries` to list known places ids and entries."};if(!r.place.properties.dataSources?.geometry?.id)return{kind:"skip",reason:"Place has no geometry data source (e.g. addresses and streets often lack one)."};try{const s=await t.places.fetchPlaceGeometry(e);return s?{kind:"success",place:r.place,feature:s,ownerEntryId:r.entryId}:{kind:"skip",reason:"Geometry Data service returned no feature for this place."}}catch(s){return{kind:"skip",reason:`Geometry fetch failed: ${s instanceof Error?s.message:String(s)}`}}},vt=async(e,t)=>{if(0===e.length)return{error:"`geometriesEntryIDs` must contain one or more tagged ids (e.g. `{ kind: 'places', id: <entryId> }`, `{ kind: 'place', id: <placeId> }`, `{ kind: 'ranges', id: <entryId> }`, `{ kind: 'customGeometries', id: <entryId> }`). Use `recallGeometries` to list available ids."};const r=(e=>{const t={place:[],places:[],ranges:[],customGeometries:[]};for(const r of e)t[r.kind].push(r);return t})(e),s=((e,t)=>{const r=[],s=/* @__PURE__ */new Map,o=[],n=/* @__PURE__ */new Set;for(const i of e){const e=t.places.entries.find(e=>e.id===i.id);if(!e){o.push({source:i,reason:"No places entry with this id in session state. Use `recallGeometries` to list available ids."});continue}let a=0;for(const t of e.places)t.properties.dataSources?.geometry?.id&&"string"==typeof t.id&&(a++,s.has(t.id)||s.set(t.id,i.id),n.has(t.id)||(n.add(t.id),r.push(t.id)));0===a&&o.push({source:i,reason:"Places entry has no places with a geometry data source (e.g. addresses and streets often lack one)."})}return{placeIds:r,placeIdToEntry:s,skipped:o}})(r.places,t),o=[...s.skipped],n=/* @__PURE__ */new Set,i=[],a=/* @__PURE__ */new Map;for(const f of r.place)n.has(f.id)||(n.add(f.id),i.push(f.id),a.set(f.id,f));for(const f of s.placeIds)n.has(f)||(n.add(f),i.push(f));const l=[],c=[],d=/* @__PURE__ */new Set,u=/* @__PURE__ */new Set,p=/* @__PURE__ */new Set;for(const f of i){const e=await wt(f,t),r=a.get(f),n=s.placeIdToEntry.get(f);if("skip"===e.kind){const t=r??{kind:"place",id:f};o.push({source:t,reason:e.reason});continue}const i=r??(n?{kind:"places",id:n}:{kind:"place",id:f});l.push(bt(e.feature,i)),c.push(e.place),d.add(e.ownerEntryId),r&&u.add(r.id),n&&p.add(n)}const h=((e,t)=>{const r=[],s=/* @__PURE__ */new Set,o=[];for(const n of e){const e=t.ranges.entries.find(e=>e.id===n.id);if(!e){o.push({source:n,reason:"No ranges entry with this id in session state."});continue}const i=e.ranges.flatMap(e=>e.polygon?.features??[]);if(0!==i.length){for(const e of i)r.push(bt(e,n));s.add(n.id)}else o.push({source:n,reason:"Ranges entry has no polygons — recompute via findReachableAreas first."})}return{features:r,contributingIds:s,skipped:o}})(r.ranges,t);l.push(...h.features),o.push(...h.skipped);const m=((e,t)=>{const r=[],s=/* @__PURE__ */new Set,o=[];for(const n of e){const e=t.customGeometries.findById(n.id);if(e)if(0!==e.features.length){for(const t of e.features)r.push(bt(t,n));s.add(n.id)}else o.push({source:n,reason:"Custom-geometries entry is empty."});else o.push({source:n,reason:"No custom-geometries entry with this id in session state. Use `recallGeometries` to list available ids."})}return{features:r,contributingIds:s,skipped:o}})(r.customGeometries,t);l.push(...m.features),o.push(...m.skipped);const y=m.contributingIds;return 0===l.length?{error:`No input geometries available. ${o.map(gt).join(" | ")}`}:{value:{geometries:l,sourcePlaces:c,affectedEntries:[...[...d].map(e=>({id:e,kind:"places"})),...[...y].map(e=>({id:e,kind:"customGeometries"}))],skipped:o,contributingSourceIds:e.filter(e=>{switch(e.kind){case"place":return u.has(e.id);case"places":return p.has(e.id);case"ranges":return h.contributingIds.has(e.id);case"customGeometries":return y.has(e.id)}})}}},It=["placesEntryIDs","routesEntryIDs","incidentsEntryIDs","geometriesEntryIDs","trafficAreaAnalyticsEntryIDs","byodEntryIDs"],St=async(e,t)=>{if(!(e=>It.some(t=>(e[t]?.length??0)>0))(e))return{error:"At least one of `placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`, `trafficAreaAnalyticsEntryIDs`, `byodEntryIDs` must be set. Use `recallPlaces` / `recallRoutes` / `recallGeometries` / `recallByod` / `recallState` to list available ids."};const r=((e,t)=>{const r=Mt(e.placesEntryIDs,t.places,{missingId:e=>`No places entry with id "${e}". Use recallPlaces to list available IDs.`});if("error"in r)return r;const s=Mt(e.routesEntryIDs,t.routing,{missingId:e=>`No routes entry with id "${e}". Use recallRoutes to list available IDs.`});if("error"in s)return s;const o=Mt(e.incidentsEntryIDs,t.trafficIncidents,{missingId:e=>`No incidents entry with id "${e}". Call getTrafficIncidents first.`});if("error"in o)return o;const n=Mt(e.trafficAreaAnalyticsEntryIDs,t.trafficAreaAnalytics,{missingId:e=>`No traffic-area-analytics entry with id "${e}". Call getTrafficAreaAnalytics first.`});if("error"in n)return n;const i=Mt(e.byodEntryIDs,t.byod,{missingId:e=>`No BYOD entry with id "${e}". Use recallByod to list available IDs.`});return"error"in i?i:{value:{places:r.value,routes:s.value,incidents:o.value,trafficAreaAnalytics:n.value,byod:i.value}}})(e,t);if("error"in r)return r;const{places:s,routes:o,incidents:n,trafficAreaAnalytics:i,byod:a}=r.value;let l,c={affectedEntries:[],sourceIds:[],skipped:[]};if(e.geometriesEntryIDs?.length){const r=await vt(e.geometriesEntryIDs,t);if("error"in r)return r;l=r.value.geometries,c={affectedEntries:r.value.affectedEntries,sourceIds:r.value.contributingSourceIds,skipped:r.value.skipped}}return{value:{resolved:{places:s,routes:o,incidents:n,trafficAreaAnalytics:i,byod:a},sandbox:{places:s.length?{type:"FeatureCollection",features:s.flatMap(e=>e.places)}:void 0,placesByEntry:s.length?Object.fromEntries(s.map(e=>[e.id,{type:"FeatureCollection",features:[...e.places]}])):void 0,routes:o.length?{type:"FeatureCollection",features:o.flatMap(e=>e.data.features)}:void 0,routesByEntry:o.length?Object.fromEntries(o.map(e=>[e.id,{type:"FeatureCollection",features:[...e.data.features]}])):void 0,incidents:n.length?n.flatMap(e=>e.data):void 0,incidentsByEntry:n.length?Object.fromEntries(n.map(e=>[e.id,[...e.data]])):void 0,geometries:l,trafficAreaAnalytics:i.length?Et(i.map(e=>e.data)):void 0,trafficAreaAnalyticsByEntry:i.length?Object.fromEntries(i.map(e=>[e.id,e.data])):void 0,byod:a.length?{type:"FeatureCollection",features:a.flatMap(e=>e.data.features)}:void 0,byodByEntry:a.length?Object.fromEntries(a.map(e=>[e.id,e.data])):void 0},geometries:c}}},Et=e=>({type:"FeatureCollection",properties:e[0].properties,features:e.flatMap(e=>e.features)}),Mt=(e,t,r)=>{if(!e?.length)return{value:[]};const s=[];for(const o of e){const e=t.entries.find(e=>e.id===o);if(!e)return{error:r.missingId(o)};s.push(e)}return{value:s}},xt='CROSS-KIND OPS — `turf` and `h3` operate on EVERY feature regardless of which input slot it came from. You can freely combine `places` (Points), `routes` (LineStrings), `incidents` (Point or LineString), `geometries` (Polygon / MultiPolygon), and `trafficAreaAnalytics` (Polygon tiles with `properties` like `congestionLevel` / `speed` / `travelTime` / `freeFlowSpeed`) in the same call. Common bridges:\n• Point ↔ LineString — `turf.pointToLineDistance(point, lineFeature, { units: "meters" })`, `turf.nearestPointOnLine(line, point)`.\n• Point ↔ Polygon — `turf.booleanPointInPolygon(point, poly)`, `turf.pointsWithinPolygon(turf.featureCollection(points), poly)`.\n• LineString ↔ Polygon — `turf.lineIntersect(line, poly)`, `turf.booleanCrosses(line, poly)`, `turf.lineSplit(line, poly)`.\n• Polygon ↔ Polygon — `turf.union/intersect/difference(turf.featureCollection([a, b]))` (Turf v7 takes a single collection).\n• Point/LineString ↔ trafficAreaAnalytics tile — `turf.booleanPointInPolygon(p, tile)` for points; `turf.lineIntersect(routeLine, tile)` for routes; iterate `trafficAreaAnalytics.features` to find which tile a place/route segment falls into and read its metric (`tile.properties.congestionLevel`, etc.).\n• Buffer to bridge kinds — `turf.buffer(anyFeature, meters/1000, { units: "kilometers" })` turns a point or line into a polygon you can then filter against.\n• Bbox of anything — `turf.bbox(featureOrCollection)`.\n• Centroid for Point↔shape distance — `turf.centroid(polygonOrLine).geometry.coordinates`.\n• h3 — `h3.latLngToCell(lat, lng, res)` for any Point; `h3.polygonToCells(poly.geometry.coordinates, res)` for any Polygon; for a LineString iterate `geometry.coordinates` and bin each `[lng, lat]`.\nNote: arguments to turf functions are GeoJSON Features — pass the whole feature (`places.features[i]`, `routes.features[0]`, `geometries[i]`, `incidents[i]`, `trafficAreaAnalytics.features[i]`), not the bare `geometry.coordinates` (a few helpers accept coords but most expect Features). Always check the function signature at https://turfjs.org/docs/.',Pt=["places","placesByEntry","routes","routesByEntry","incidents","incidentsByEntry","geometries","trafficAreaAnalytics","trafficAreaAnalyticsByEntry","byod","byodByEntry","h3","turf"],Dt=(e,t)=>[e.places,e.placesByEntry,e.routes,e.routesByEntry,e.incidents,e.incidentsByEntry,e.geometries,e.trafficAreaAnalytics,e.trafficAreaAnalyticsByEntry,e.byod,e.byodByEntry,t.h3,t.turf],_t=new Set(z),Tt=(e,t,r)=>{const s=r.timestamp??Date.now();for(const o of t)e.addAnalysisToEntry(o.id,{name:r.name,timestamp:s,...r.description&&{description:r.description},outputFormat:r.outputFormat,data:r.analysis})},At=e=>`In scope: ${e.map(e=>`\`${e}\``).join(", ")} — injected as parameters; use directly. No \`require\` / \`import\` / \`await import\` (body is an AsyncFunction, not a module — such lines are stripped silently). \`h3\` = h3-js (hex math). \`turf\` = @turf/turf v7 (verify names at https://turfjs.org/docs/). TURF INPUTS: pass a Feature or FeatureCollection — never a bare Array (throws "Unknown Geometry Type"). FC-shaped inputs (\`places\`, \`routes\`, \`trafficAreaAnalytics\`, \`byod\`) → pass directly: \`turf.bbox(places)\`. Array inputs (\`incidents\`, \`geometries\`, \`places.features\`, …) → iterate per-feature (\`for (const f of places.features) turf.X(f)\`) or wrap with \`turf.featureCollection([...])\`. Never \`.map(f => f.geometry.coordinates)\` / \`.flatMap(f => f.geometry.coordinates)\` to feed turf — that strips the feature wrapper turf reads and throws "coordinates must be an Array" (or worse, mixes Point/LineString shapes silently). Pass the whole feature and let turf read the geometry: \`turf.pointToLineDistance(point, lineFeature, { units: "meters" })\`. \`incidents\` mixes Point + LineString features — guard with \`if (inc.geometry.type === "LineString")\` before line-only ops. TURF v7 SET OPS: \`union\`/\`intersect\`/\`difference\` take ONE FeatureCollection — \`turf.difference(turf.featureCollection([outer, inner]))\`. The v6 two-arg form throws. PERF: for large-N nearest/within queries, pre-bucket points with \`h3.latLngToCell(lat, lng, res)\` + \`h3.gridDisk(cell, k)\` and only run turf on same-/neighbour-cell pairs — turns O(N·M) into ~O(N+M). Pick \`res\` so each cell ≈ your query radius. No spatial search or HTTP — call \`discoverPlaces\` BEFORE this tool for new places. Top-level \`await\`/locals/consts fine. End with \`return …;\`.`,Ct=new Set(["bar","line","pie","doughnut","radar","polarArea","scatter","bubble"]),kt=e=>{if(!e||"object"!=typeof e)return!1;const t=e;if("Feature"!==t.type)return!1;const r=t.geometry;return!!r&&("Polygon"===r.type||"MultiPolygon"===r.type)&&Array.isArray(r.coordinates)},Ot=(e,t)=>{if(void 0===e)return{error:"Analysis code must return a value (the aggregation result). Hint: `code` is the function BODY, not a function expression. If you wrote `(args) => { ...; return result; }` (or `async (args) => { ... }`) as the whole code, the arrow is created and discarded — drop the wrapper and have the body itself end with `return result;`."};const r=Rt(e);return"error"in r?{error:r.error}:"chart"!==t||(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"string"==typeof t.type&&Ct.has(t.type)&&"object"==typeof t.data&&null!==t.data})(r.value)?{value:r.value}:{error:'Analysis code with `outputFormat: "chart"` must return a Chart.js ChartConfiguration ({ type: "bar"|"line"|"pie"|"doughnut"|"radar"|"polarArea"|"scatter"|"bubble", data: { labels, datasets }, options? }).'}},Rt=e=>{try{return{value:JSON.parse(JSON.stringify(e))}}catch(t){return{error:`Returned value is not JSON-serializable: ${t instanceof Error?t.message:String(t)}. Return only plain objects, arrays, strings, numbers, booleans, and null — no functions, classes, circular references, or DOM/Map references.`}}},Lt=async(e,t,r,s)=>{try{const s=Object.getPrototypeOf(async()=>{}).constructor,o=Ft(e,t);return{value:await new s(...t,o)(...r)}}catch(o){return{error:jt(s,o)}}},Ft=(e,t)=>{const r=t.join("|"),s=String.raw`(?:(?:await\s+)?(?:require|import)\s*\(`+String.raw`|arguments\s*\[\s*\d+\s*\]\s*(?:\.\w+|\[\s*['"]\w+['"]\s*\]))`,o=new RegExp(String.raw`^[ \t]*(?:const|let|var)[ \t]+(?:${r})[ \t]*=[ \t]*${s}[^\n]*\n?`,"gm");return e.replace(o,"")},Nt=[{pattern:/Cannot read propert(?:y|ies) of undefined/i,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."},{pattern:/Cannot read propert(?:y|ies) of null/i,hint:"A property was read on `null`. Guard property access with `?.` and provide defaults with `??`."},{pattern:/\bh3\.\w+ is not a function/i,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."},{pattern:/\bturf\.\w+ is not a function/i,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."},{pattern:/coordinates must be an Array|Unknown Geometry Type/i,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)`.'},{pattern:/is not a function/i,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(...) : []`."},{pattern:/Illegal return statement/i,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."},{pattern:/Identifier ['"]?\w+['"]? has already been declared/i,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)`)."},{pattern:/Unexpected (?:token|identifier|end of input)/i,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 ...;`."},{pattern:/\brequire is not defined\b|\bimport (?:is not defined|outside a module)\b/i,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."},{pattern:/\b(?:functions|tools|agent|recallPlaces|recallRoutes|recallRanges|discoverPlaces|locatePlace) is not defined\b/i,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."},{pattern:/is not iterable/i,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 ?? []) { ... }`."},{pattern:/Cannot access '.+?' before initialization/i,hint:"A `let`/`const` was used before its declaration (temporal dead zone). Move the declaration above its first use."},{pattern:/Assignment to constant variable/i,hint:"A `const`-declared variable was reassigned. Either use `let`, or mutate the value in place."},{pattern:/structuredClone|could not be cloned|DataCloneError/i,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."}],jt=(e,t)=>{const r=t instanceof Error?t.message:String(t),s=`${e} code execution failed: ${r}`;if(t instanceof Error&&"SyntaxError"===t.name)return`${s} Hint: the provided \`code\` is not valid JavaScript — ensure it parses as an async function body and \`return\`s a value.`;const o=Nt.find(({pattern:e})=>e.test(r));return o?`${s} Hint: ${o.hint}`:s},qt=async(e,t,r)=>{const s=await e.places.fetchGeometriesForEntry(t);return r.show&&s.length>0?(await e.places.showPlaceGeometries(s,r.theme??"outline",r.mode??"replace"),{fetched:s.length,shown:!0}):{fetched:s.length,shown:!1}},Bt=async(e,t,r)=>{const s={markerType:!1,zoomMode:!1};if("none"!==r.markerType){if("multiple"===e.places.entryMode&&"all"!==r.hidePreviousEntries){if(Array.isArray(r.hidePreviousEntries)){const s=new Set(t),o=r.hidePreviousEntries.filter(e=>!s.has(e));o.length>0&&await e.places.removeShownEntries(o)}await e.places.addShownEntries(t,r.markerType)}else await e.places.setShownEntries(t,r.markerType);s.markerType=!0}if("auto"===r.zoomMode){const t=e.places.getShownFeatures();if(t.length>0){const r=F({type:"FeatureCollection",features:t});r&&e.baseMap.mapLibreMap.fitBounds(r,{padding:50})}s.zoomMode=!0}return s},$t=(e,t=6)=>{if(e.mapLibreMap.getZoom()<t)return;const r=e.mapLibreMap.getCenter();return[r.lng,r.lat]},Gt=e=>{const t=e.mapLibreMap.getBounds();return[t.getWest(),t.getSouth(),t.getEast(),t.getNorth()]},Ut=async(e,t,r,s)=>{const o=await Lt(e.code,["incidents","h3","turf","now","log","previous"],[t,K,X,new Date(s),()=>{},r],"Analysis");return"error"in o?{error:o.error}:Ot(o.value,e.outputFormat)},Wt=class{_specs=[];_historyByName=/* @__PURE__ */new Map;get specs(){return this._specs}get results(){const e=[];for(const t of this._specs){const r=this.getResult(t.name);r&&e.push(r)}return e}history(e){return this._historyByName.get(e)??[]}getResult(e){return this._historyByName.get(e)?.at(-1)}register(e){const t=this._specs.findIndex(t=>t.name===e.name);if(t>=0){const r=this._specs[t],s=r.code!==e.code||r.outputFormat!==e.outputFormat||r.description!==e.description;this._specs[t]=e,s&&this._historyByName.delete(e.name)}else this._specs.push(e)}unregister(e){this._specs=this._specs.filter(t=>t.name!==e)}attach(e){let t=this._historyByName.get(e.name);t||(t=[],this._historyByName.set(e.name,t)),t.push(e),t.length>240&&t.shift()}async replay(e,t){const r=[];for(const s of this._specs){const o=await Ut(s,e,this.getResult(s.name)?.data,t);if("error"in o)continue;const n={name:s.name,timestamp:t,...s.description&&{description:s.description},outputFormat:s.outputFormat,data:o.value};this.attach(n),r.push(n)}return r}},Vt=class{_bbox;events=new ae;_status="idle";_area;_error;_intervalHandle;_clearInterval;_fetchIncidents;constructor(e){this._bbox=e}get status(){return this._status}get monitoredArea(){return this._area}get error(){return this._error}get isRunning(){return"running"===this._status}get isActive(){return"idle"!==this._status}start(e,t,r=6e4){if("running"===this._status)return;this._status="running",this._error=void 0,this._area=e,this._fetchIncidents=t.fetchIncidents,this._clearInterval=t.clearInterval??(e=>clearInterval(e));const s=t.setInterval??setInterval;this._tick(),this._intervalHandle=s(()=>{this._tick()},r)}stop(){"idle"!==this._status&&(this._releaseTimer(),this._status="idle",this._error=void 0,this._area=void 0,this._fetchIncidents=void 0)}async _tick(){const e=this._fetchIncidents;if(e)try{const t=await e(this._bbox),r={takenAt:Date.now(),incidents:t};this.events.emit("tick",r)}catch(t){this._failed(t instanceof Error?t.message:String(t))}}_failed(e){this._releaseTimer(),this._error=e,this._status="stopped-error",this._fetchIncidents=void 0,this.events.emit("error",e)}_releaseTimer(){null!=this._intervalHandle&&(this._clearInterval?.(this._intervalHandle),this._intervalHandle=void 0),this._clearInterval=void 0}},zt=class{_ttMap;_entries=[];_entryMode="multiple";events=new ae;constructor(e){this._ttMap=e}get entries(){return this._entries}get entryMode(){return this._entryMode}async setEntryMode(e){this._entryMode!==e&&(this._entryMode=e,"single"===e&&this._entries.length>1&&(this._entries=await ie(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)),this.events.emit("mode-change",e))}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}async addIncidentsEntry(e,t,r,s,o){"single"===this._entryMode&&this._entries.length>0&&(await ne(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this._entries=[]);const n=oe(o??`incidents-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:s,label:r,data:e,params:t}),this.events.emit("entries-change",this._entries),n}async replaceEntryData(e,t,r){const s=this._entries.find(t=>t.id===e);if(!s)return!1;const o=(s._tick??0)+1;if(s._tick=o,s.data=t,s.timestamp=r,this.events.emit("entries-change",this._entries),s._module){if(await s._module.show({type:"FeatureCollection",features:t}),s._tick!==o)return!0;if(s._focus){const e=new Set(t.map(e=>e.properties.id)),r=[...s._focus.ids].filter(t=>e.has(t));s._module.setFocus(r.length>0?r:null),0===r.length&&(s._focus=void 0,this.events.emit("focus-change",{entryId:s.id,focus:null}))}}return s._tick!==o||await this._rerunRegisteredSpecs(e,r),!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await h.get(this._ttMap),t._module}async showEntry(e,t=!0){const r=this._requireEntry(e),s=await this.getEntryModule(e);await s.show({type:"FeatureCollection",features:r.data}),s.setVisible(!0),t&&this._ttMap.mapLibreMap.fitBounds(r.params.bbox,{padding:50,maxZoom:14});const o=!!r._shown;r._shown=!0,o||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._focus&&(t._focus=void 0,this.events.emit("focus-change",{entryId:t.id,focus:null})),t._module?.setVisible(!1),await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],s=!!r._shown;await this._teardownEntry(r),this._entries.splice(t,1),this.events.emit("entries-change",this._entries),s&&this.events.emit("shown-change",this.shownEntryIds)}addAnalysisToEntry(e,t){const r=this._entries.find(t=>t.id===e);return!!r&&((r._analyses??=new Wt).attach(t),this.events.emit("analysis-change",{entryId:r.id,analysis:t}),this.events.emit("entries-change",this._entries),!0)}setAnalysisSpec(e){const t=this._entries.find(t=>t.id===e.source);t&&(t._analyses??=new Wt).register(e)}removeAnalysisSpec(e){for(const t of this._entries)t._analyses?.unregister(e)}getFocus(e){const t=this._entries.find(t=>t.id===e);return t?._focus?{ids:new Set(t._focus.ids),reason:t._focus.reason}:null}setFocus(e,t,r){const s=this._entries.find(t=>t.id===e);if(!s)return{focusedCount:0,droppedIds:[...t]};if(0===t.length)return this.clearFocus(e),{focusedCount:0,droppedIds:[]};const o=new Set(s.data.map(e=>e.properties.id)),n=[],i=[];for(const a of t)o.has(a)?n.push(a):i.push(a);return 0===n.length?{focusedCount:0,droppedIds:i}:(s._focus={ids:new Set(n),reason:r},s._module?.setFocus(n),this.events.emit("focus-change",{entryId:s.id,focus:this.getFocus(e)}),{focusedCount:n.length,droppedIds:i})}clearFocus(e){const t=this._entries.find(t=>t.id===e);t?._focus&&(t._focus=void 0,t._module?.setFocus(null),this.events.emit("focus-change",{entryId:t.id,focus:null}))}focusedFeatures(e){const t=this._entries.find(t=>t.id===e);if(!t?._focus)return[];const r={};for(const o of t.data){const e=o.properties.id;"string"==typeof e&&(r[e]=o)}const s=[];for(const o of t._focus.ids){const e=r[o];e&&s.push(e)}return s}navigateFocus(e,t){const r=this.focusedFeatures(e);if(0===r.length)return;const s=r[(t%r.length+r.length)%r.length],o=this._ttMap.mapLibreMap;if("Point"===s.geometry.type)return void o.flyTo({center:s.geometry.coordinates,zoom:15,duration:600});const n=F(s);n&&o.fitBounds(n,{padding:120,maxZoom:16,duration:600})}findEntryWithIncident(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(r.data.some(t=>t.properties.id===e))return r}}startMonitoring(e,t,r,s){const o=this._requireEntry(e),n=o._monitor??=this._createMonitorForEntry(o),i=n.isRunning;n.start(t,r,s?.intervalMs),i||this.events.emit("monitor-start",{entryId:e})}stopMonitoring(e){const t=this._entries.find(t=>t.id===e)?._monitor;t?.isActive&&(t.stop(),this.events.emit("monitor-stop",{entryId:e,reason:"manual"}))}isMonitored(e){return this._entries.find(t=>t.id===e)?._monitor?.isRunning??!1}_createMonitorForEntry(e){const t=new Vt(e.params.bbox);return t.events.on("tick",t=>{this.replaceEntryData(e.id,t.incidents,t.takenAt),this.events.emit("monitor-tick",{entryId:e.id,snap:t})}),t.events.on("error",t=>{this.events.emit("monitor-error",{entryId:e.id,error:t}),this.events.emit("monitor-stop",{entryId:e.id,reason:"error"})}),t}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficIncidentsState: unknown entry "${e}"`);return t}async _rerunRegisteredSpecs(e,t){const r=this._entries.find(t=>t.id===e);if(!r?._analyses?.specs.length)return;const s=await r._analyses.replay(r.data,t);for(const o of s)this.events.emit("analysis-change",{entryId:r.id,analysis:o});s.length>0&&this.events.emit("entries-change",this._entries)}async _teardownEntry(e){this.stopMonitoring(e.id),e._focus&&(e._focus=void 0,this.events.emit("focus-change",{entryId:e.id,focus:null})),e._shown&&(e._shown=!1),e._module&&(e._module.setVisible(!1),await e._module.clear()),e._module=void 0}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){for(const t of this._entries)this._teardownEntry(t);const e=this._entries.length>0;this._entries=[],e&&(this.events.emit("entries-change",this._entries),this.events.emit("shown-change",this.shownEntryIds)),this.events.clear()}},Ht=class{_ttMap;_trafficFlowModule;_trafficIncidentsModule;constructor(e){this._ttMap=e}async getTrafficFlowModule(){return this._trafficFlowModule??=await p.get(this._ttMap),this._trafficFlowModule}async getTrafficIncidentsModule(){return this._trafficIncidentsModule??=await m.get(this._ttMap),this._trafficIncidentsModule}get trafficFlowModule(){return this._trafficFlowModule}get trafficIncidentsModule(){return this._trafficIncidentsModule}reset(){this._trafficFlowModule=void 0,this._trafficIncidentsModule=void 0}};R.instance.put({language:"en-GB"});var Yt,Jt={places:"places",routes:"routing",incidents:"trafficIncidents",customGeometries:"customGeometries",trafficAreaAnalytics:"trafficAreaAnalytics",byod:"byod",ranges:"ranges"},Xt={places:["recallPlaces","updatePlacesDisplay","locatePlace","discoverPlaces"],routes:["recallRoutes","updateRoutesDisplay","setRoute","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","getCurrentWaypoints","updateWaypointsDisplay"],incidents:["getTrafficIncidents","focusIncidents","startTrafficIncidentsMonitor","stopTrafficIncidentsMonitor","getShownTileIncidents"],customGeometries:["recallGeometries"],trafficAreaAnalytics:["getTrafficAreaAnalytics","queryTrafficAnalytics","updateTrafficAreaAnalyticsDisplay"],byod:["recallByod","addByodLayer","updateByodDisplay"],ranges:["findReachableAreas","recallRanges"]},Kt=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),Zt=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),Qt='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',er=(e,t)=>e||(t?`${Qt}\n\nADDITIONAL INSTRUCTIONS:\n${t}`:Qt),tr=Z.object({geoJson:Z.union([Z.record(Z.string(),Z.unknown()),Z.array(Z.record(Z.string(),Z.unknown()))]).describe("GeoJSON object or array of GeoJSON objects")}),rr=Z.object({meters:Z.number().describe("Distance in meters to format"),unitType:Z.enum(["metric","imperial_us","imperial_uk"]).optional().describe("default: metric")}),sr=Z.object({seconds:Z.number().describe("Duration in seconds to format")}),or=Z.object({error:Z.string()}),nr={id:Z.string().describe("Stable place feature ID; pass as `{ placeId }` to setRoute / addWaypointsToRoute / etc."),name:Z.string().optional().describe("POI name if available"),address:Z.string().optional().describe("Human-readable address"),position:Z.array(Z.number()).length(2).describe("[longitude, latitude]")},ir={areaId:Z.string().optional().describe('Id of the small area polygon this place sits in (few km², not a whole municipality). Round-trip into `discoverPlaces.where.areaId` for "what else is in this same area?". Only set on explorationSearch results in DE / NL / FR.'),areaCountry:Z.string().optional().describe("ISO 3166-1 alpha-2 country code of the surrounding area polygon."),areaTags:Z.array(Z.string()).optional().describe('Tokens describing the surrounding small area (few km², not a whole municipality) (e.g. "coastal", "walkable", "transit_connected"). Useful for downstream `areaTags` filters.')},ar=e=>Z.object(e.experimentalSearch?{...nr,...ir}:nr).describe("Compact place summary"),lr=e=>Z.object({count:Z.number(),features:Z.array(ar(e))}).describe("Compact places summary"),cr=ar({}),dr=(lr({}),Z.object({travelTimeInSeconds:Z.number(),lengthInMeters:Z.number(),trafficDelayInSeconds:Z.number().optional(),trafficLengthInMeters:Z.number().optional(),departureTime:Z.string().optional(),arrivalTime:Z.string().optional(),noTrafficTravelTimeInSeconds:Z.number().optional(),historicTrafficTravelTimeInSeconds:Z.number().optional(),liveTrafficIncidentsTravelTimeInSeconds:Z.number().optional()})),ur=Z.object({index:Z.number().describe("0=best, 1+=alternative"),summary:dr,legSummaries:Z.array(dr).optional().describe("Present when route has multiple legs")}),pr=Z.object({count:Z.number(),routes:Z.array(ur)}),hr=Z.object({}),mr=Z.union([Z.object({position:Z.array(Z.number()).min(2).max(2).describe("[longitude, latitude]"),accuracy:Z.number().describe("Accuracy of the location in meters"),timestamp:Z.number().describe("Timestamp of the location data")}),or]),yr=Z.number().int().min(0).optional().describe("default: 0"),fr=(Z.object({routeIndex:yr,traveledTimeInSeconds:Z.number().min(0).optional().describe("seconds from start"),traveledDistanceInMeters:Z.number().min(0).optional().describe("meters from start"),clockTime:Z.string().optional().describe("ISO 8601; elapsed time derived from departure time")}).refine(({traveledTimeInSeconds:e,traveledDistanceInMeters:t,clockTime:r})=>1===[e,t,r].filter(e=>void 0!==e).length,{message:"Provide exactly one of: traveledTimeInSeconds, traveledDistanceInMeters, or clockTime."}),Z.union([Z.object({bbox:Z.array(Z.number()).describe("GeoJSON bbox [minLng, minLat, maxLng, maxLat]")}),or]),Z.object({sectionType:Z.enum(H).describe("e.g. country|traffic|motorway|toll"),id:Z.string().describe("Section ID — obtain from `recallRoutes({ id })` detail mode or from `analyseData` over routes.")}),Z.object({sectionType:Z.enum(H).describe("e.g. country|traffic|motorway|toll"),ids:Z.array(Z.string()).optional().describe("Section IDs to query. If omitted, all sections of the given sectionType are returned.")}),["location","route","waypoint","place","locate","discover","traffic","map style","utilities","search","detour","along-route","reachable-range","isochrone","range","EV","coverage","recall","state","carpool","carTrain","country","ferry","importantRoadStretch","lanes","leg","lowEmissionZone","motorway","pedestrian","roadShields","speedLimit","toll","tollVignette","tunnel","unpaved","urban","vehicleRestricted"]),gr=Z.object({name:Z.string(),description:Z.string(),examplePrompts:Z.array(Z.string()),relatedTools:Z.array(Z.string()).optional()}),br=Z.union([Z.object({mode:Z.literal("summary"),examplePrompts:Z.array(Z.string())}),Z.object({mode:Z.literal("detail"),query:Z.string().optional(),tag:Z.enum(fr).optional(),matchedTools:Z.array(gr),totalMatched:Z.number()}),or]),wr=Z.object({mode:Z.enum(["summary","detail"]).describe('"summary": overview of all capabilities with example prompts mixed from all tools. "detail": searchable full tool info — use when you need to find a specific tool.'),query:Z.string().optional().describe("Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags."),tag:Z.enum(fr).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),vr='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.',Ir=(e,t,r)=>({description:e.description,inputSchema:e.inputSchema,outputSchema:r?e.outputSchema:void 0,execute:r=>e.execute(r,t)}),Sr=(e,t)=>({name:e,description:t.description,classificationPrompt:t.classificationPrompt,tags:t.tags,examples:t.examples,examplePrompts:t.examplePrompts,relatedTools:t.relatedTools,dependsOn:t.dependsOn,scopePrompt:t.scopePrompt}),Er=(e,t)=>"function"==typeof e?e(t):e,Mr=(e,t,r)=>{const s=!1!==r?.outputSchemas,o={featureFlags:r?.featureFlags,enabledDataKinds:r?.enabledDataKinds},n={},i={},a={};for(const[c,d]of Object.entries(e)){const e=Er(d,o);if(n[c]=Ir(e,t,s),i[c]=Sr(c,e),"function"==typeof d&&e.scopeSchema){const t=d,r=e.scopeSchema;a[c]={applyScope:e=>{const s=r.safeParse(e);if(!s.success)return null;try{const e=t({...o,scope:s.data});return{description:e.description,inputSchema:e.inputSchema}}catch{return null}},defaultDescription:e.description,defaultInputSchema:e.inputSchema}}}var l;if(n.help&&(n.help=(l=()=>i,{description:vr,inputSchema:wr,outputSchema:br,execute:async e=>{const{mode:t,query:r,tag:s}=e;try{const e=Object.values(l());if("summary"===t)return{mode:"summary",examplePrompts:e.flatMap(e=>e.examplePrompts??[])};let o=e;if(s&&(o=o.filter(e=>e.tags?.includes(s)??!1)),r){const e=r.toLowerCase();o=o.filter(t=>t.name.toLowerCase().includes(e)||t.description.toLowerCase().includes(e)||(t.tags??[]).some(t=>t.toLowerCase().includes(e))||(t.examplePrompts??[]).some(t=>t.toLowerCase().includes(e)))}return{mode:"detail",query:r,tag:s,matchedTools:o.map(e=>({name:e.name,description:e.description,examplePrompts:e.examplePrompts??[],relatedTools:e.relatedTools??[]})),totalMatched:o.length}}catch(o){return{error:`Failed to load help: ${o instanceof Error?o.message:String(o)}`}}}})),0===Object.keys(n).length)throw new Error("MapAgent requires at least one tool.");return{tools:n,toolsMetadata:i,scopableTools:a}},xr=Z.object({added:Z.array(Z.string()).describe("Source IDs added by the executed code."),removed:Z.array(Z.string()).describe("Source IDs removed by the executed code."),updated:Z.array(Z.string()).describe("Source IDs whose definition changed.")}),Pr=Z.object({added:Z.array(Z.string()).describe("Layer IDs added by the executed code."),removed:Z.array(Z.string()).describe("Layer IDs removed by the executed code."),updated:Z.array(Z.string()).describe("Layer IDs whose properties changed.")}),Dr=Z.object({code:Z.string().describe('Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. Example: await a map event with `await new Promise(r => map.once("idle", r))`. Return a value with `return` to include it in the result.')}),_r=Z.union([Z.object({success:Z.literal(!0),result:Z.unknown().optional().describe("Return value of the executed code, if any."),sources:xr,layers:Pr}),or]),Tr=e=>{const t=e.getStyle()?.sources??{},r=/* @__PURE__ */new Map;for(const[s,o]of Object.entries(t))r.set(s,JSON.stringify(o));return r},Ar=e=>{const t=e.getStyle()?.layers??[],r=/* @__PURE__ */new Map;for(const s of t)r.set(s.id,JSON.stringify(s));return r},Cr=(e,t)=>{const r=[],s=[],o=[];for(const[n,i]of t){const t=e.get(n);void 0===t?r.push(n):t!==i&&o.push(n)}for(const n of e.keys())t.has(n)||s.push(n);return{added:r,removed:s,updated:o}},kr=Z.union([Z.object({success:Z.literal(!0)}),or]),Or=Z.object({where:Z.union([Z.object({boundingBox:Re.describe("BBox [W,S,E,N]."),padding:Z.number().optional().describe("Padding in pixels. Default: 50.")}).describe("Fit the camera to a bounding box."),Z.object({position:Z.array(Z.number()).length(2).describe("[lng, lat] to fly to."),zoom:Z.number().optional().describe("Camera zoom level. Default: 14.")}).describe("Fly the camera to a specific position.")])}),Rr=Z.union([Z.object({layers:Z.array(Z.object({id:Z.string(),layout:Z.unknown().optional(),paint:Z.unknown().optional()}))}),or]),Lr=Z.object({layerIdQuery:Z.string().optional().describe("Case-insensitive partial match on layer IDs. If omitted, all layers are returned."),include:Z.array(Z.enum(["paint","layout"])).describe('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.')}),Fr=Z.union([Z.object({center:Z.array(Z.number()).describe("[lng, lat]"),zoom:Z.number().describe("0–22"),bbox:Z.array(Z.number()).describe("[W, S, E, N]"),pitch:Z.number().describe("degrees, 0=top-down, 85=near-horizontal"),bearing:Z.number().describe("degrees, 0=north, 90=east")}),or]),Nr=Z.object({}),jr=Z.union([Z.object({layerId:Z.string(),propertyName:Z.string(),value:Z.unknown(),success:Z.literal(!0)}),Z.object({layerId:Z.string(),propertyName:Z.string(),error:Z.string()})]),qr=Z.union([Z.object({results:Z.array(jr)}),or]),Br=Z.object({changes:Z.array(Z.object({layerId:Z.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:Z.string().describe("e.g. visibility|text-field|icon-size"),value:Z.any()}))}).describe("List of layout property changes to apply in one go."),$r=Z.union([Z.object({layerId:Z.string(),propertyName:Z.string(),value:Z.unknown(),success:Z.literal(!0)}),Z.object({layerId:Z.string(),propertyName:Z.string(),error:Z.string()})]),Gr=Z.union([Z.object({results:Z.array($r)}),or]),Ur=Z.object({changes:Z.array(Z.object({layerId:Z.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:Z.string().describe("e.g. fill-color|line-width|text-color"),value:Z.any()})).describe("List of paint property changes to apply in one go.")}),Wr=Z.union([Z.object({success:Z.literal(!0)}),or]),Vr=Z.object({pitch:Z.number().min(0).max(85).optional().describe("0=top-down, 85=near-horizontal"),bearing:Z.number().min(-180).max(180).optional().describe("0=north, 90=east")}),zr=Z.union([Z.object({zoom:Z.number().describe("Resulting zoom level after the change")}),or]),Hr=Z.object({delta:Z.number().describe("Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.")}),Yr=e=>Object.entries(e).filter(([,e])=>e.scopePrompt).map(([e])=>e),Jr=Z.object({kinds:Z.array(Z.string()).min(1).describe("Entry-data kinds the turn touches (one of the kinds listed in the tool's SCOPE hint).")}),Xr=(e,t)=>{t&&(e.inputTokens+=t.inputTokens??0,e.outputTokens+=t.outputTokens??0,e.totalTokens+=t.totalTokens??0)},Kr=e=>{if(!e||!Array.isArray(e.tools)||!e.tools.every(e=>"string"==typeof e))return;const t=e.toolScopes&&"object"==typeof e.toolScopes?e.toolScopes:void 0;return{tools:e.tools,...t&&{scopes:t}}},Zr=(e,t)=>{const r=e.toolScopes?Object.fromEntries(Object.entries(e.toolScopes).filter(([,e])=>null!=e)):void 0;return{tools:e.tools,...r&&Object.keys(r).length>0&&{toolScopes:r},usage:t}},Qr=async(t,s,o)=>{const n=new Set(Yr(o)),i=(e=>{const t=Z.object({tools:Z.array(Z.string()).min(1).describe("Tool names needed to fulfill the query.")});if(0===e.size)return t;const r={};for(const o of e)r[o]=Jr.nullable();const s=Z.object(r).nullable().describe("Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in `tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.");return t.extend({toolScopes:s}).superRefine((t,r)=>{const s=t.toolScopes??{};for(const o of t.tools){if(!e.has(o))continue;null==s[o]&&r.addIssue({code:"custom",path:["toolScopes",o],message:`Tool "${o}" requires \`toolScopes.${o}\` — emit it per the SCOPE hint (don't leave null when picking).`})}})})(n),a={inputTokens:0,outputTokens:0,totalTokens:0},l=(e=>0===e.size?'{ "tools": ["toolName", ...] }':`{\n "tools": ["toolName", ...],\n "toolScopes": {\n${[...e].map(e=>` "${e}": { "kinds": [...] } | null`).join(",\n")}\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),c=`${(e=>{const t=Object.entries(e).map(([e,t])=>((e,t)=>`${e}${t.classificationPrompt?` ${t.classificationPrompt}.`:""}${t.dependsOn?.length?` Depends on: ${t.dependsOn.join(", ")}.`:""}${t.scopePrompt?` SCOPE: ${t.scopePrompt}`:""}`)(e,t)),r=Yr(e);return["You are a tool selector for a map assistant.","You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.",'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',"","Available tools:",...t,...r.length?["","TOOL SCOPES — MANDATORY when selecting a scopable tool:",`The following tools REQUIRE a scope entry in \`toolScopes\` whenever they appear in \`tools\`: ${r.join(", ")}.`,"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.","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."]:[]].join("\n")})(o)}\n\nReturn JSON matching this shape exactly:\n${l}`;let d;for(let p=0;p<2;p++)try{const o=await r({model:s,output:e.json(),system:c,messages:t,maxOutputTokens:400});Xr(a,o.usage);const n=i.safeParse(o.output);if(n.success)return Zr(n.data,a);d=Kr(o.output)??d}catch(u){const e=u;Xr(a,e.usage),d=Kr(e.value)??d}if(!d)throw new Error("Classifier failed after 2 attempts to produce a valid response.");return{tools:d.tools,...d.scopes&&{toolScopes:d.scopes},usage:a}},es=async(e,t)=>{const{chatModel:r,toolsMetadata:s}=t,o=performance.now(),{tools:n,toolScopes:i,usage:a}=await Qr(e,r,s);return{activeToolNames:n,...i&&{toolScopes:i},timeMs:performance.now()-o,usage:a}},ts=e=>"text"===e.type,rs=e=>{if("string"==typeof e)return e;const t=e.filter(ts);return t.length>0?t.map(e=>e.text).join(""):null},ss=e=>{for(let t=e.length-1;t>=0;t--)if("user"===e[t].role)return rs(e[t].content);return null},os=e=>{const{model:t,maxHistoryMessages:r,maxHistoryMessageLength:s}=e;return async e=>{const{messages:o,toolsMetadata:n}=e;if(!o?.length)return null;const i=r??8,a=s??300;let l=-1,c=null;for(let t=o.length-1;t>=0;t--)if("user"===o[t].role){l=t,c=rs(o[t].content);break}if(!c)return null;const d=[];for(let t=Math.max(0,l-i);t<l;t++){const e=o[t];if("user"!==e.role&&"assistant"!==e.role)continue;const r=rs(e.content);if(!r)continue;const s=r.length>a?r.slice(0,a)+"…":r;d.push({role:e.role,content:s})}d.push({role:"user",content:c});try{return await es(d,{chatModel:t,toolsMetadata:n})}catch{return null}}},ns=e=>{if(!Array.isArray(e))return e.properties?.address?.freeformAddress},is=(e,t)=>{if("features"in e)return((e,t)=>{const r=[];t?.query&&r.push(`"${t.query}"`),t?.poiCategories?.length&&r.push(t.poiCategories.join(", ")),t?.routeLabel?r.push(`along ${t.routeLabel}`):t?.where&&r.push(`in ${t.where}`);const s=`${e} ${1===e?"place":"places"}`;return r.length?`${r.join(" ")} (${s})`:s})(e.features.length,t);const r=(s=e,s.properties?.poi?.name);var s;const o=(e=>e.properties?.address?.freeformAddress)(e);return r&&o?`${r}, ${o}`:r||(o||(t?.query?t.query:"Place"))},as={timeMinutes:"min",distanceKM:"km",remainingChargeCPT:"% remaining",spentChargePCT:"% spent",spentFuelLiters:"L"},ls=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${as[e.type]??e.type}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`,cs=(e,t={})=>{const r={id:e.id,name:e.properties?.poi?.name,address:e.properties?.address?.freeformAddress,position:e.geometry?.coordinates??[0,0]};if(t.experimentalSearch){const t=e.properties;t?.areaId&&(r.areaId=t.areaId),t?.areaCountry&&(r.areaCountry=t.areaCountry),t?.areaTags?.length&&(r.areaTags=t.areaTags)}return r},ds=(e,t={})=>{const r=Array.isArray(e)?e:e.features;return{count:r.length,features:r.slice(0,5).map(e=>cs(e,t))}},us=e=>{const{summary:t,sections:r,index:s}=e.properties,o=r.leg,n=e=>({...e,arrivalTime:e.arrivalTime.toISOString(),departureTime:e.departureTime.toISOString()});return{index:s,summary:n(t),...o.length>1&&{legSummaries:o.map(e=>n(e.summary))}}},ps=e=>({count:e.features.length,routes:e.features.map(us)}),hs=Z.object({slotIndex:Z.number(),isFilled:Z.boolean(),address:Z.string().optional(),position:Z.array(Z.number()).length(2).optional().describe("[longitude, latitude]")}),ms=e=>{if(null==e)return null;const t=$(e);let r;return Array.isArray(e)||(r=e.properties?.address?.freeformAddress),{slotIndex:-1,isFilled:!0,...r&&{address:r},...t&&{position:t}}},ys=async(e,t,r)=>{const[s,o]="poi"===t?[A,C]:[M,x],{limit:n,bias:i}=r;if(i&&"position"in i)return(await s({query:e,position:i.position,limit:n})).features;if(i&&"boundingBox"in i)return(await s({query:e,boundingBox:i.boundingBox,limit:n})).features;if(1===n){const t=await o(e);return t?[t]:[]}return(await s({query:e,limit:n})).features},fs=async(e,t,r)=>{const[s]=await ys(e,t,{limit:1,bias:r});return s??null},gs=Z.object({query:Z.string().describe("Location string to resolve for downstream routes or searches."),waypointIndex:Z.number().int().min(0).optional().describe("Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination."),queryAs:Z.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, geographies.'),where:Qe.describe("Geographic scope."),show:We.optional().describe("Map display actions after resolving. Skip for intermediate calculations."),entryId:je,geometry:Be}),bs=Z.union([cr.extend({waypointIndex:Z.number().optional(),shown:Ve.optional(),geometryFetched:Z.boolean().optional().describe("Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set)."),geometryShown:Z.boolean().optional().describe("Whether the fetched polygon was rendered on the map.")}),or]),ws=async(e,t)=>"global"===e.mode?{bias:void 0}:"within"===e.mode?(async(e,t)=>{if(e.viewport)return{bias:{boundingBox:Gt(t.baseMap)}};if(e.boundingBox)return{bias:{boundingBox:e.boundingBox}};if(e.query){const r=$t(t.baseMap),s=await fs(e.query,e.queryAs??"place",r?{position:r}:void 0);if(!s)return{bias:void 0};if(s.bbox)return{bias:{boundingBox:s.bbox}};const o=$(s);return{bias:o?{position:o}:void 0}}if(e.placeId){const r=t.places.findPlaceById(e.placeId);if(!r)return{error:`Unknown placeId "${e.placeId}". Use recallPlaces to list available IDs.`};if(!r.place.properties.dataSources?.geometry?.id)return{error:`Place "${e.placeId}" has no geometry data source (most addresses and streets lack one).`};const s=await t.places.fetchPlaceGeometry(e.placeId);return s?{bias:{boundingBox:s.bbox??X.bbox(s)}}:{error:`Geometry Data service returned no feature for place "${e.placeId}".`}}return{bias:void 0}})(e,t):(async(e,t)=>{if(e.viewport){const e=$t(t.baseMap);return{bias:e?{position:e}:void 0}}if(e.position)return{bias:{position:e.position}};if(e.query){const r=$t(t.baseMap),s=await fs(e.query,e.queryAs??"place",r?{position:r}:void 0);if(!s)return{bias:void 0};const o=$(s);return{bias:o?{position:o}:void 0}}return{bias:void 0}})(e,t),vs=async(e,t)=>{if("position"in e){const{lng:t,lat:r}=e.position;return{place:[t,r],position:[t,r],name:`[${t}, ${r}]`}}if("placeId"in e){const r=t.places.findPlaceById(e.placeId);if(!r)return null;const s=$(r.place);return s?{place:r.place,position:s,name:r.place.properties?.poi?.name??r.place.properties?.address?.freeformAddress??e.placeId}:null}const r=await fs(e.query,e.queryAs);if(!r)return null;const s=$(r);return s?{place:r,position:s,name:r.properties?.address?.freeformAddress??e.query,query:e.query}:null},Is=Z.object({routeType:Z.enum(T).optional().describe("fast|short|efficient|thrilling"),traffic:Z.enum(["live","historical"]).optional().describe("live|historical"),avoid:Z.array(Z.enum(L)).optional().describe("tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones"),avoidAreas:Z.array(Z.array(Z.number()).describe("GeoJSON bbox [minLng, minLat, maxLng, maxLat]")).max(10).optional().describe("Up to 10 bounding boxes to bypass.")}),Ss=Z.object({option:Z.enum(["departAt","arriveBy"]),date:Z.string().describe("ISO 8601")}),Es=Z.object({maxAlternatives:Z.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:Is.optional(),when:Ss.optional().describe("Omit to depart now.")}),Ms=Z.union([pr,Z.object({success:Z.literal(!0)}),or]),xs=Z.object({locations:Z.array(yt).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:Es.optional().describe("Routing options. Omitted fields keep their current values."),showOnMap:Z.boolean().describe("Show the route and waypoints on the map after calculation."),hidePreviousEntries:Ue("routes")}).refine(e=>void 0!==e.locations||void 0!==e.parameters,{message:"Provide at least one of `locations` or `parameters`."}),Ps=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},Ds=e=>{const{maxAlternatives:t=0,costModel:r,when:s}=e;return{...t>0&&{maxAlternatives:t},...r&&{costModel:{...r,...r.avoidAreas&&{avoidAreas:r.avoidAreas}}},...s&&{when:{option:s.option,date:new Date(s.date)}}}},_s=async(e,t,r,s)=>{const o=await S({locations:t,...Ds(e.routing.params)}),n=await e.routing.addRoutes(o,t,((e,t)=>{const r=e.features[0]?.properties?.summary,s=[r?q(r.travelTimeInSeconds):void 0,r?j(r.lengthInMeters):void 0].filter(Boolean),o=s.length?` (${s.join(", ")})`:"",n=t.map(ns).filter(Boolean);if(n.length>=2){const e=n[0],t=n.at(-1),r=n.slice(1,-1);return`${r.length>0?`${e} via ${r.join(", ")} to ${t}`:`${e} to ${t}`}${o}`}return`Route${o}`})(o,t));return r&&await(async(e,t,r,s,o)=>{await mt(e.routing,[t],o),await e.routing.showEntry(t);const n=F(r);n&&e.baseMap.mapLibreMap.fitBounds(n,{padding:50})})(e,n,o,0,s),ps(o)},Ts=Z.union([pr,or]),As=Z.object({origin:yt.optional().describe("Prepend a new origin in front of the current origin. The previous origin is kept as the first intermediate stop (it is not replaced)."),destination:yt.optional().describe("Append a new destination after the current destination. The previous destination is kept as an intermediate stop (it is not replaced)."),stops:Z.array(yt).min(1).optional().describe("Intermediate stops to insert at optimal mid-positions along the existing route. Multiple stops settle into natural along-route order regardless of input order."),showOnMap:Z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Ue("routes")}).refine(e=>void 0!==e.origin||void 0!==e.destination||e.stops&&e.stops.length>0,{message:"Provide at least one of `origin`, `destination`, or `stops`."}),Cs=e=>"query"in e?`"${e.query}"`:"placeId"in e?`placeId "${e.placeId}"`:JSON.stringify(e.position),ks=async(e,t,r)=>{const s=await ys(e,t,{limit:5,bias:r?{position:r}:void 0});if(0===s.length)return null;for(const o of s){if(!o.properties.dataSources?.geometry?.id)continue;const e=(await P({geometries:[o]})).features[0];if(e)return{place:o,geometry:e}}return{place:s[0]}},Os=e=>Z.union([lr(e).extend({shown:Ve.optional(),placesEntryId:Z.string().optional(),label:Z.string().optional().describe('Human-readable label stored with the entry (e.g. "cafe", "CINEMA (50 places)").'),geometriesFetched:Z.number().optional().describe("Count of boundary polygons fetched and cached on the entry (when `geometries` was set)."),geometriesShown:Z.boolean().optional().describe("Whether the fetched polygons were rendered on the map.")}),or]),Rs=(Os({}),{...ze,queries:Z.array(Z.object({query:Z.string().describe('Name of a CONTAINING area to search within (e.g. "Paris", "De Jordaan, Amsterdam"). NEVER the user\'s search subject — that goes in the top-level `query`.'),queryAs:He})).optional().describe('CONTAINING areas to search WITHIN — answers "search where?", NOT "search for what?". Never put the user\'s search subject here. Example: for "cafes in Amsterdam" → top-level `query`/`poiCategories` carry "cafes"; `where.queries: [{query: "Amsterdam"}]` carries the region. Each entry resolves to a boundary polygon (or bbox); the union is searched. Per-item `queryAs` disambiguates POI vs place. EXCLUSIVE with viewport; composes with other multi-region fields.'),placeIds:Z.array(Z.string()).optional().describe("IDs of session places — each polygon (fetched on demand) joins the search area. Use recallPlaces to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields."),geometries:Z.array(Ne).optional().describe("Direct GeoJSON Polygons / MultiPolygons (custom drawing, external data). For named or stored places use `queries` / `placeIds`. EXCLUSIVE with viewport; composes with other multi-region fields."),range:Z.string().optional().describe('Range ID from findReachableAreas (e.g. "ranges-0") — restricts to that entry; multi-origin entries combine their polygons. Use recallRanges to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),route:Z.object({routeId:Z.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallRoutes. Default: latest route.'),widthMeters:Z.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. BULK discovery (≤10000); for a SHORT ranked detour list use mode `maxDetour`. EXCLUSIVE with viewport; composes with other multi-region fields.")}),Ls={boundingBoxes:Z.array(Re).optional().describe("Bboxes [W,S,E,N] — results in the union. Last-resort: prefer `municipalities` / `queries` / `placeIds` / `geometries` when polygons are resolvable. EXCLUSIVE with viewport; composes with other multi-region fields."),municipalities:Z.array(Z.string()).optional().describe('Exact, case-sensitive municipality names (e.g. ["Amsterdam", "Utrecht"]). EXCLUSIVE with viewport; composes with other multi-region fields.'),areaId:Z.string().optional().describe('Id of a small area polygon (few km², not a whole municipality) — restricts results to that single area. Typically chained from a previous hit\'s `areaId` ("what else is in this same area?"). EXCLUSIVE with viewport; composes with other multi-region fields.')},Fs=e=>Z.object(e.experimentalSearch?{...Rs,...Ls}:Rs).refine(e=>{const t=!!e.viewport,r=e,s=!!(e.queries?.length||e.placeIds?.length||e.geometries?.length||e.range||e.route||r.municipalities?.length||r.boundingBoxes?.length||r.areaId);return(!t||!s)&&(t||s)},{message:"`within` requires EITHER `viewport: true` OR one or more multi-region fields (queries / placeIds / geometries / range / route"+(e.experimentalSearch?" / boundingBoxes / municipalities / areaId":"")+"), not both."}),Ns=Z.object({mode:Z.literal("maxDetour"),routeId:Z.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallRoutes. Default: latest route.'),maxDetourTimeSeconds:Z.number().positive().describe("Max extra travel time (seconds) the user accepts to reach a POI."),sortBy:Z.enum(["detourTime","detourOffset"]).optional().describe('"detourTime" (default) ranks by extra travel time; "detourOffset" by distance from the route line.'),limit:Z.number().max(100).optional().describe("Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.")}),js=e=>{const t=e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/areaId/geometries/range/route":"queries/placeIds/geometries/range/route";return Z.union([Fs(e),Ke,Ns,Ze]).describe(`Geographic scope, by \`mode\`: \`within\` (area — \`viewport\` alone, OR any combo of ${t}) | \`nearby\` (point bias — position/viewport/query, optional radiusMeters) | \`maxDetour\` (ranked detour list off a route) | \`global\` (no bias). Default: \`{ mode: "within", viewport: true }\`.`)},qs=e=>{const t={query:Z.string().optional().describe('Free-text filter on POI name/address. NEVER a city/region/area name (e.g. "Paris", "Amsterdam") — those go in `where`. For "restaurants in Paris": `query` is empty (or a name filter like "pizza"), `where.queries: [{query: "Paris"}]`. Omit when filtering by category — use `poiCategories` instead.'),where:js(e).optional(),poiCategories:Z.array(Z.string()).optional().describe('CONSTANT_CASE category codes. ALWAYS batch all requested categories in ONE array — never one call per category. Common: "RESTAURANT", "CAFE", "BAR", "HOTEL", "PARK", "PARK_RECREATION_AREA", "ELECTRIC_VEHICLE_STATION", "GAS_STATION", "PARKING_GARAGE", "SUPERMARKETS_HYPERMARKETS", "PHARMACY", "ATM". For anything else, call getPoiCategoryCodes first to resolve natural-language terms (e.g. "italian food", "gym").'),show:We.optional().describe("Display places/geometries on the map. Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry."),entryId:je,geometries:Be},r=e.experimentalSearch?{...t,placeTypes:Z.array(Z.enum(["POI","PointAddress","Street"])).optional().describe("Restrict to record types (default: all). POI = businesses/landmarks/amenities; PointAddress = numbered addresses; Street = unnumbered streets."),areaTags:Z.array(Z.enum(w)).optional().describe('Tokens describing a small surrounding area (few km², not a whole municipality). Enum drawn from the SDK canonical vocabulary — e.g. "coastal", "walkable", "alpine", "transit_connected", "village", "tourism_economy". OR semantics — matches places in areas tagged with ANY token. Filter works on any `where` scope (including global), but area data is currently populated only for DE / NL / FR — zero hits where data is absent. Filter, not a geo-bias — pair with a `where` mode that supplies one.')}:t;return Z.object(r).refine(e=>!!e.query||!!e.poiCategories?.length,{message:"discoverPlaces needs a search subject: set `query` (free-text) or `poiCategories`. For a single named place (no filter), use locatePlace — including for its boundary polygon (`geometry`)."})},Bs=e=>`Search for MULTIPLE places in a region — REQUIRES a search subject (\`query\` and/or \`poiCategories\`). NOT for a single named place — use locatePlace for that (including its polygon/boundary via \`geometry\`). Stores an entry and returns \`placesEntryId\`. CALL ONCE PER REQUEST — batch all \`poiCategories\` + areas in one call; split only for explicitly comparable result sets. \`where.mode\`: \`within\` (\`viewport\` OR any combo of ${e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/areaId/geometries/range/route":"queries/placeIds/geometries/range/route"}), \`nearby\` (position/viewport/query + optional radiusMeters), \`maxDetour\` (≤100 ranked detours off a route), \`global\`. `+(e.experimentalSearch?"Prefer `municipalities` / `areaId` / `queries` / `placeIds` (precise) over `boundingBoxes` (coarse). ":"Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ")+(e.experimentalSearch?'Top-level `areaTags` narrows hits by small-area character ("coastal", "walkable", …) — area data currently populated only in DE/NL/FR. ':"")+'Route: `within.route` = bulk corridor (≤10000); `maxDetour` = short ranked list (≤100). Default: `{ mode: "within", viewport: true }`. Never call this with empty `query` AND empty `poiCategories` — that is locatePlace territory.',$s={},Gs=(qs($s),Bs($s),e=>{const{geometries:t}=e;return t?.length?{geometries:t}:{}}),Us=(e,t)=>{const{query:r,poiCategories:s,bias:o={},radiusMeters:n,multiFilters:i}=t,a={query:r,poiCategories:s,...o,...void 0!==n&&{radiusMeters:n}};return e?E({...a,limit:1e4,...i}):A({...a,limit:100,...Gs(i)})},Ws=async(e,t,r)=>{if(e.viewport)return{bias:{boundingBox:Gt(t.baseMap)},multiFilters:r};const s=[],o=[];if(e.queries?.length){const r=await(async(e,t)=>{const r=$t(t.baseMap),s=[],o=[];for(const n of e){const e=await ks(n.query,n.queryAs??"place",r);if(!e)return{error:`Could not resolve "${n.query}". Try a more specific query, a placeId, or a boundingBox.`};if(e.geometry?.geometry)s.push(e.geometry.geometry);else{if(!e.place.bbox)return{error:`"${n.query}" resolved to a place with no geometry or bbox — cannot use as a "within" area. Use mode "nearby" with this query for a point bias instead.`};o.push(e.place.bbox)}}return{geometries:s,boundingBoxes:o}})(e.queries,t);if("error"in r)return r;s.push(...r.geometries),o.push(...r.boundingBoxes)}if(e.placeIds?.length){const r=await(async(e,t)=>{const r=[];for(const s of e){const e=t.places.findPlaceById(s);if(!e)return{error:`Unknown placeId "${s}". Use recallPlaces to list available IDs.`};if(!e.place.properties.dataSources?.geometry?.id)return{error:`Place "${s}" has no geometry data source (most addresses and streets lack one).`};const o=await t.places.fetchPlaceGeometry(s);if(!o)return{error:`Geometry Data service returned no feature for place "${s}".`};r.push(o.geometry)}return{geometries:r}})(e.placeIds,t);if("error"in r)return r;s.push(...r.geometries)}const n=e,i=[...n.boundingBoxes??[],...o],a=[...e.geometries??[],...s],l={...r,...n.municipalities?.length&&{municipalities:n.municipalities},...n.areaId&&{areaId:n.areaId},...i.length&&{boundingBoxes:i},...a.length&&{geometries:a}};let c;if(e.route){const r=((e,t)=>{const r=e.routing.entries;if(0===r.length)return{error:"No routes in state. Calculate a route first via setRoute."};const s=t.routeId?r.find(e=>e.id===t.routeId):r.at(-1);if(!s)return{error:`No route found with id "${t.routeId}". Use recallRoutes to list available routes.`};if(0===s.data.features.length)return{error:`Route entry "${s.id}" has no geometry.`};const o=t.widthMeters/2,n=[];for(const i of s.data.features){const e=X.buffer(i,o,{units:"meters"})?.geometry;e&&n.push(e)}return 0===n.length?{error:`Failed to compute route corridor for "${s.id}" (turf.buffer returned no geometry for any of the ${s.data.features.length} route alternative(s)).`}:{polygons:n,routeLabel:s.label}})(t,e.route);if("error"in r)return r;l.geometries=[...l.geometries??[],...r.polygons],c=r.routeLabel}return{bias:{},multiFilters:l,range:e.range,routeLabel:c}},Vs=async(e,t,r)=>{if("nearby"===e.mode){const s=await(async(e,t)=>{if(e.viewport){const r=$t(t.baseMap);return{bias:r?{position:r}:{},radiusMeters:e.radiusMeters}}if(e.position)return{bias:{position:e.position},radiusMeters:e.radiusMeters};if(e.query){const r=$t(t.baseMap),s=await fs(e.query,e.queryAs??"place",r?{position:r}:void 0);if(s){const t=$(s);if(t)return{bias:{position:t},radiusMeters:e.radiusMeters}}return{bias:{},radiusMeters:e.radiusMeters}}return{bias:{},radiusMeters:e.radiusMeters}})(e,t);return{bias:s.bias,multiFilters:r,radiusMeters:s.radiusMeters}}if("within"===e.mode){const s=await Ws(e,t,r);return"error"in s?s:{bias:s.bias,multiFilters:s.multiFilters,range:s.range,routeLabel:s.routeLabel}}return{bias:{},multiFilters:r}},zs=e=>{if(e)return"within"===e.mode&&e.queries?.length?e.queries.map(e=>e.query).join(", "):"nearby"===e.mode&&e.query?e.query:void 0},Hs=async(e,t,r,s,o,n)=>{const i=s?await Bt(e,[r],s):void 0;let a,l;if(o){const t=await qt(e,r,o);a=t.fetched,t.shown&&(l=!0)}const c=e.places.entries.find(e=>e.id===r)?.label;return{...ds(t,n),placesEntryId:r,...c&&{label:c},...i&&{shown:i},...void 0!==a&&{geometriesFetched:a},...void 0!==l&&{geometriesShown:l}}},Ys=e=>{const t=!0===e.experimentalSearch;return async(r,s)=>{const{query:o,where:n,poiCategories:i,show:a,entryId:l,geometries:c}=r,d=r,u=d.placeTypes,p=d.areaTags,{resolved:h,unresolved:m}=await(async e=>{if(!e)return{resolved:void 0,unresolved:[]};const t=e.filter(e=>_t.has(e)),r=e.filter(e=>!_t.has(e));if(0===r.length)return{resolved:t.length?t:void 0,unresolved:[]};await D();const s=await Promise.all(r.map(async e=>({term:e,codes:await D({filters:[e]})}))),o=[],n=[];for(const{term:a,codes:l}of s)0===l.length?o.push(a):n.push(...l);const i=[...new Set([...t,...n])];return{resolved:i.length?i:void 0,unresolved:o}})(i);if(i?.length&&!h)return{error:`No POI categories matched: ${m.map(e=>`"${e}"`).join(", ")}. POI category codes must be exact CONSTANT_CASE values, or natural-language terms resolvable by the SDK catalog. Call getPoiCategoryCodes to find valid codes for your search subject (e.g. "italian food", "gym").`};const y=n??{mode:"within",viewport:!0},f={...u?.length&&{placeTypes:u},...p?.length&&{areaTags:p}};try{if("maxDetour"===y.mode){const t=await(async(e,t,r,s,o)=>{const n=e.routing.entries;if(0===n.length)return{error:"No routes in state. Calculate a route first via setRoute."};const i=t.routeId?n.find(e=>e.id===t.routeId):n.at(-1);if(!i)return{error:`No route found with id "${t.routeId}". Use recallRoutes to list available routes.`};const a=i.data.features[0];if(!a)return{error:`Route entry "${i.id}" has no geometry.`};const l=await v({route:a,maxDetourTimeSeconds:t.maxDetourTimeSeconds,...r&&{query:r},...s&&{poiCategories:s},...t.sortBy&&{sortBy:t.sortBy},...void 0!==t.limit&&{limit:t.limit}});return{result:l,placesEntryId:await e.places.addPlaceResult(l,is(l,{query:r,poiCategories:s,routeLabel:i.label}),o)}})(s,y,o,h,l);return"error"in t?t:Hs(s,t.result,t.placesEntryId,a,c,e)}const r=await Vs(y,s,f);if("error"in r)return r;const{bias:n,multiFilters:i,radiusMeters:d,range:u,routeLabel:p}=r;if(u){const r=await(async(e,t,r,s,o,n,i,a)=>{const l=e.ranges.entries.find(e=>e.id===t);if(!l||0===l.ranges.length)return{error:`Range "${t}" not found. Use recallRanges to list available ranges.`};const c=l.ranges.map(e=>e.polygon?.features[0]?.geometry).filter(e=>!!e);if(0===c.length)return{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`};const d=[...c,...o.geometries??[]],u=await Us(a,{query:r,poiCategories:s,multiFilters:{...o,geometries:d}});return{result:u,placesEntryId:await e.places.addPlaceResult(u,is(u,{query:r,poiCategories:s,...i&&{routeLabel:i}}),n)}})(s,u,o,h,i,l,p,t);return"error"in r?r:Hs(s,r.result,r.placesEntryId,a,c,e)}const m=await(async(e,t,r,s,o,n,i,a,l,c)=>{const d=await Us(c,{query:t,poiCategories:s,bias:r,radiusMeters:o,multiFilters:n});return{result:d,placesEntryId:await e.places.addPlaceResult(d,is(d,{query:t,poiCategories:s,where:zs(a),...l&&{routeLabel:l}}),i)}})(s,o,n,h,d,i,l,y,p,t);return Hs(s,m.result,m.placesEntryId,a,c,e)}catch(g){return{error:`Search failed: ${g instanceof Error?g.message:String(g)}`}}}},Js=(Ys($s),{classificationPrompt:'Search multiple places by query/category in a region. BATCH categories+areas in one call. `query` filters POI names ONLY — area names go in `where`. `where.mode`: within (area) / nearby (point bias) / maxDetour (route-relative ranked) / global. 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. Within-mode: route → corridor. Prefer geometries/municipalities/placeIds over bbox. Skip for already-stored places — use recallPlaces/processData.',tags:["discover","place","location"],examples:['discoverPlaces({ poiCategories: ["RESTAURANT", "CAFE", "BAR"], entryId: "amsterdam-food-spots" }) // batch many categories + semantic id','discoverPlaces({ query: "coffee", where: { mode: "nearby", position: [4.9, 52.4], radiusMeters: 500 } })','discoverPlaces({ query: "parking", where: { mode: "within", queries: [{ query: "De Jordaan, Amsterdam", queryAs: "place" }] } }) // area name goes in `where.queries`, NOT top-level `query`','discoverPlaces({ query: "coffee", where: { mode: "nearby", query: "Schiphol Airport", queryAs: "poi", radiusMeters: 2000 } }) // POI-resolved bias point','discoverPlaces({ poiCategories: ["HOTEL"], where: { mode: "within", municipalities: ["Amsterdam", "Utrecht"] }, entryId: "ams-utrecht-hotels" })','discoverPlaces({ query: "bakery", where: { mode: "within", range: "ranges-0" } })','discoverPlaces({ poiCategories: ["CAFE"], show: { markerType: "pin", zoomMode: "auto" } }) // omit `where` → defaults to viewport','discoverPlaces({ poiCategories: ["RESTAURANT"], where: { mode: "within", queries: [{ query: "Paris" }] } }) // "restaurants in Paris" — subject in `poiCategories`, area in `where.queries`','discoverPlaces({ poiCategories: ["ELECTRIC_VEHICLE_STATION"], where: { mode: "within", route: { routeId: "routes-0", widthMeters: 1000 } } }) // bulk corridor scan along the route','discoverPlaces({ query: "coffee", where: { mode: "maxDetour", maxDetourTimeSeconds: 300, limit: 5 } }) // top 5 coffee detours within 5 min off the latest route','discoverPlaces({ poiCategories: ["ELECTRIC_VEHICLE_STATION"], where: { mode: "maxDetour", routeId: "routes-1", maxDetourTimeSeconds: 600, sortBy: "detourOffset" } }) // ranked EV detours on a specific route','discoverPlaces({ query: "bakery", where: { mode: "within", placeIds: ["G55fc4abe-..."] } }) // search inside stored place polygons (auto-fetched if needed)','discoverPlaces({ query: "vegan", where: { mode: "global" } })'],examplePrompts:["Find coffee shops near me","Italian restaurants in Amsterdam","EV chargers in the current map area","Hotels in Amsterdam and Utrecht","Bakeries within the reachable range","Outline the Amsterdam neighbourhoods","Show the boundaries of these municipalities","Find all EV stations within 1 km of my route","Cafes in a 500 m corridor around the planned route","Best 5 coffee stops without losing more than 5 min off my route","EV chargers I can reach with under 10 min detour"],relatedTools:["getPOICategoryCodes","updatePlacesDisplay","updatePlacesDisplay","locatePlace","getViewport","findReachableAreas","processData","analyseData"],dependsOn:["getPOICategoryCodes","setRoute"]}),Xs=Z.object({type:Z.enum(["timeMinutes","distanceKM"]).describe("Budget type including its unit."),value:Z.number().describe("Budget value. e.g. 30 for 30 minutes, 50 for 50 km.")}),Ks=Z.object({origins:Z.array(yt).min(1).describe("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."),budgets:Z.array(Xs).min(1).describe("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."),theme:Z.enum(["filled","inverted","outline"]).optional().describe("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."),showOnMap:Z.boolean().describe("Whether to render the range polygon(s) on the map."),showOriginPin:Z.boolean().optional().describe("Whether to drop a pin at each origin. Default: true."),hidePreviousEntries:Ue("ranges")}),Zs=Z.object({rangesId:Z.string(),ranges:Z.array(Z.object({originName:Z.string(),budgets:Z.array(Xs)})),skipped:Z.array(Z.object({origin:Z.string(),reason:Z.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry).")}),Qs=Z.union([Zs,Z.object({status:Z.enum(["no_results"])}),or]),eo=async(e,t)=>{const r=await I(e.map(e=>({origin:t,budget:{type:e.type,value:e.value}})));return 0===r.features.length?null:r},to=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,ro=Z.union([Z.object({count:Z.number().describe("Number of codes returned"),codes:Z.array(Z.string()).describe('POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. "RESTAURANT") also accepted by toggleTilesPOIs categories.')}),or]),so=Z.object({filters:Z.array(Z.string()).optional().describe("Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes."),language:Z.string().describe('Locale e.g. "en-GB", "fr-FR", "es-ES", "de-DE".')}),oo=Z.object({speed:Z.number().optional(),freeFlowSpeed:Z.number().optional(),congestionLevel:Z.number().optional(),travelTime:Z.number().optional(),networkLength:Z.number().optional()}),no=Z.union([Z.object({entryId:Z.string().describe("Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData."),name:Z.string().optional(),timezone:Z.string().optional(),dateRange:Z.object({start:Z.string(),end:Z.string()}),baseData:oo,metrics:Z.array(Z.string()),tileCount:Z.number(),availableGranularities:Z.array(Z.string())}),or]),io=Z.object({location:yt.optional().describe("Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Mutually exclusive with bbox and geometry."),bbox:Z.array(Z.number()).length(4).optional().describe("[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Mutually exclusive with location."),showOnMap:Z.boolean().optional().describe("Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize."),startDate:Z.string().optional().describe("Start date 'YYYY-MM-DD'. Use with endDate for a continuous range (max 31 days)."),endDate:Z.string().optional().describe("End date 'YYYY-MM-DD' (inclusive). Defaults to today if omitted."),days:Z.array(Z.string()).optional().describe("Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate."),metrics:Z.union([Z.literal("all"),Z.array(Z.enum(["speed","congestionLevel","freeFlowSpeed","travelTime","networkLength"])).min(1)]).describe("Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength']."),functionalRoadClasses:Z.union([Z.literal("all"),Z.array(Z.enum(["MOTORWAY","MAJOR_ROAD","OTHER_MAJOR_ROAD","SECONDARY_ROAD","LOCAL_CONNECTING_ROAD","LOCAL_ROAD_HIGH_IMPORTANCE","LOCAL_ROAD","LOCAL_ROAD_MINOR_IMPORTANCE","OTHER_ROAD"]))]).optional().describe("Road classes to include. 'all' (default) or array of specific classes."),hours:Z.union([Z.literal("all"),Z.array(Z.number().int().min(0).max(23))]).optional().describe("Hours of the day (0-23) to include. 'all' (default) or array like [7,8,9,17,18] for rush hours.")}),ao=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`,lo=e=>e instanceof Date?Number.isNaN(e.getTime())?void 0:ao(e):"string"==typeof e?e.slice(0,10):void 0,co=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),uo=Z.union([Z.object({count:Z.number(),entryId:Z.string().optional(),entries:Z.array(Z.object({id:Z.string(),label:Z.string(),count:Z.number(),timestamp:Z.number()}))}),or]),po=Z.object({...ze,boundingBox:Re.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport; composes with other multi-region fields."),queries:Z.array(Z.object({query:Z.string().describe('Name of a CONTAINING area (e.g. "Paris", "De Jordaan, Amsterdam", "M25"). Resolved to its bbox (or polygon bbox) and merged into the search area.'),queryAs:He})).optional().describe("CONTAINING areas to fetch incidents within. Each entry geocodes to a bbox; the union of all bboxes is used. Pair per-item `queryAs` to disambiguate POI vs place. EXCLUSIVE with viewport; composes with other multi-region fields."),placeIds:Z.array(Z.string()).optional().describe("IDs of session places — each polygon (fetched on demand) contributes its bbox to the area. Use recallPlaces to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields."),geometries:Z.array(Ne).optional().describe("Direct GeoJSON Polygons / MultiPolygons (custom drawing, external data). For named or stored places use `queries` / `placeIds`. EXCLUSIVE with viewport; composes with other multi-region fields."),range:Z.string().optional().describe('Range ID from findReachableAreas (e.g. "ranges-0") — restricts to that entry; multi-origin entries combine their polygons. Use recallRanges to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),route:Z.object({routeId:Z.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallRoutes. Default: latest route.'),widthMeters:Z.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. EXCLUSIVE with viewport; composes with other multi-region fields.")}).refine(e=>fo(e)||go(e),{message:"`within` requires `viewport: true` OR one or more multi-region fields (boundingBox / queries / placeIds / geometries / range / route) with a meaningful value."}),ho=e=>!!e&&4===e.length&&e.some(e=>0!==e),mo=e=>!!e&&e.widthMeters>0&&(void 0===e.routeId||e.routeId.length>0),yo=e=>!!e&&e.length>0,fo=e=>!0===e.viewport,go=e=>ho(e.boundingBox)||!!e.queries?.length||!!e.placeIds?.length||!!e.geometries?.length||yo(e.range)||mo(e.route),bo=Z.union([po]).describe('Geographic scope. Only `within` mode (single area, defaults to `{ mode: "within", viewport: true }`). Use `viewport` alone for "the network / right now / this area", or any combination of boundingBox / queries / placeIds / geometries / range / route — each resolves to a bbox and the union is sent to the SDK (≤10,000 km²).'),wo=Z.object({where:bo.optional().describe('Geographic scope. Default: `{ mode: "within", viewport: true }`.'),categoryFilter:Z.array(Z.enum([...Y])).optional().describe("Filter by incident category. If omitted, all categories are returned."),timeValidityFilter:Z.array(Z.enum(["present","future"])).optional().describe("default: ['present']"),label:Z.string().optional().describe('Short human-readable label for this fetch — name the place ("Central London", "Amsterdam ring", "M25 corridor"), not coordinates. Surfaced verbatim in the monitor chip and on the entry chip in the UI; also used by analyseData / focusIncidents to disambiguate which entry to operate on. Always pass one when silent is false; the bbox-coordinate fallback ("bbox a,b → c,d") is for silent intermediate fetches only.'),silent:Z.boolean().optional().describe("Default: false. When true, suppress map rendering (use for intermediate calculations only). Normally, fetched incidents auto-render on the map so the user can visually validate the agent is looking at the right area."),fitBounds:Z.boolean().optional().describe("Default: true. Pass false to skip camera move after rendering.")}),vo=async(e,t)=>{const r=[];let s;if(ho(e.boundingBox)&&r.push(e.boundingBox),e.queries?.length){const s=await(async(e,t)=>{const r=$t(t.baseMap),s=[];for(const o of e){const e=await ys(o.query,o.queryAs??"place",{limit:5,bias:r?{position:r}:void 0});if(0===e.length)return{error:`Could not resolve "${o.query}". Try a more specific query, a placeId, or a boundingBox.`};const t=F(e[0]);if(!t)return{error:`"${o.query}" resolved to a place with no bbox — cannot use as a "within" area. Provide a boundingBox or a different query.`};s.push(t)}return{bboxes:s}})(e.queries,t);if("error"in s)return s;r.push(...s.bboxes)}if(e.placeIds?.length){const s=await(async(e,t)=>{const r=[];for(const s of e){const e=t.places.findPlaceById(s);if(!e)return{error:`Unknown placeId "${s}". Use recallPlaces to list available IDs.`};const o=F(e.place);if(o){r.push(o);continue}const n=await t.places.fetchPlaceGeometry(s),i=n?F(n):void 0;if(!i)return{error:`Place "${s}" has no bbox or fetchable boundary polygon. Provide a boundingBox or a different placeId.`};r.push(i)}return{bboxes:r}})(e.placeIds,t);if("error"in s)return s;r.push(...s.bboxes)}if(r.push(...(e=>{if(!e?.length)return[];const t=[];for(const r of e){const e=F(r);e&&t.push(e)}return t})(e.geometries)),yo(e.range)){const s=((e,t)=>{const r=e.ranges.entries.find(e=>e.id===t);if(!r||0===r.ranges.length)return{error:`Range "${t}" not found. Use recallRanges to list available ranges.`};const s=[];for(const o of r.ranges){const e=o.polygon?.features[0],t=e?F(e):void 0;t&&s.push(t)}return 0===s.length?{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`}:{bboxes:s}})(t,e.range);if("error"in s)return s;r.push(...s.bboxes)}if(mo(e.route)){const o=((e,t)=>{const r=e.routing.entries;if(0===r.length)return{error:"No routes in state. Calculate a route first via setRoute."};const s=t.routeId?r.find(e=>e.id===t.routeId):r.at(-1);if(!s)return{error:`No route found with id "${t.routeId}". Use recallRoutes to list available routes.`};if(0===s.data.features.length)return{error:`Route entry "${s.id}" has no geometry.`};const o=t.widthMeters/2,n=[];for(const i of s.data.features){const e=X.buffer(i,o,{units:"meters"}),t=e?F(e):void 0;t&&n.push(t)}return 0===n.length?{error:`Failed to compute route corridor for "${s.id}" (turf.buffer returned no geometry for any of the ${s.data.features.length} route alternative(s)).`}:{bboxes:n,routeLabel:s.label}})(t,e.route);if("error"in o)return o;r.push(...o.bboxes),s=o.routeLabel}return{bboxes:r,...s&&{routeLabel:s}}},Io=async(e,t)=>{if(go(e)){const r=await vo(e,t);if("error"in r)return r;const s=(e=>{if(0===e.length)return;let t=Number.POSITIVE_INFINITY,r=Number.POSITIVE_INFINITY,s=Number.NEGATIVE_INFINITY,o=Number.NEGATIVE_INFINITY;for(const[n,i,a,l]of e)n<t&&(t=n),i<r&&(r=i),a>s&&(s=a),l>o&&(o=l);return[t,r,s,o]})(r.bboxes);return s?{bbox:s,...r.routeLabel&&{routeLabel:r.routeLabel}}:{error:"No area could be resolved from `where` — provide at least one within field."}}return fo(e)?(e=>{try{return{bbox:e.baseMap.ttMap.getBBox()}}catch(t){return{error:`Viewport fallback failed: ${t instanceof Error?t.message:String(t)}`}}})(t):{error:"No area could be resolved from `where` — provide at least one within field."}},So=Z.union([pr,or]),Eo=Z.object({waypointIndices:Z.array(Z.number().int()).min(1).describe("Indices of waypoints to remove. 0=origin, last=destination. Min 2 waypoints must remain. Duplicates are not allowed."),showOnMap:Z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Ue("routes")}),Mo=Z.union([pr,or]),xo=Z.union([Z.literal("origin"),Z.literal("destination"),Z.number().int()]).describe("Which waypoint to overwrite. Use 'origin' or 'destination' for the endpoints, or a 0-based absolute index (0 = origin, last = destination, intermediate stops in between)."),Po=Z.object({waypointIndex:xo,location:yt.describe("The new waypoint that replaces the targeted one."),showOnMap:Z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Ue("routes")}),Do=e=>"query"in e?`"${e.query}"`:"placeId"in e?`placeId "${e.placeId}"`:JSON.stringify(e.position),_o=Z.union([cr,or]),To=Z.object({position:Z.array(Z.number()).length(2).describe("[longitude, latitude] GeoJSON position to reverse geocode")}),Ao=Z.union([Z.object({byodEntryId:Z.string().describe("Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData)."),label:Z.string(),featureCount:Z.number(),geometryTypes:Z.array(Z.string()).describe("Geometry types present (Point / LineString / Polygon / …)."),source:Z.union([Z.object({kind:Z.literal("url"),url:Z.string()}),Z.object({kind:Z.literal("inline")})]).describe("Where the data came from."),shown:Z.boolean().describe("Whether the entry was rendered immediately (mirrors the `show` input).")}),or]),Co=Z.object({label:Z.string().describe('Human-readable label for the new entry (e.g. "Sales territories", "Customer pins 2026-Q1").'),url:Z.url().optional().describe("URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. EXCLUSIVE with `data`."),data:Z.unknown().optional().describe('Inline GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`). EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.'),entryId:je,show:Z.boolean().optional().describe("Render the entry immediately after adding. Default: false (entry is stored but hidden — use `updateByodDisplay` to render later).")}).refine(e=>e.url?!e.data:!!e.data,{message:"Pass EXACTLY ONE of `url` or `data`."}),ko=26214400,Oo=new Set(["http:","https:"]),Ro=async e=>{const t=new AbortController,r=setTimeout(()=>t.abort(),15e3);try{const r=await fetch(e,{signal:t.signal});if(!r.ok)return{error:`Failed to fetch "${e}": HTTP ${r.status} ${r.statusText}`};const s=((e,t)=>{const r=e.headers.get("content-length");if(!r)return;const s=Number.parseInt(r,10);return Number.isFinite(s)&&s>ko?{error:`Response too large for "${t}": advertised ${s} bytes (max 26214400).`}:void 0})(r,e);if(s)return s;const o=await(async(e,t)=>{const r=e.body?.getReader();if(!r){const r=await e.text();return r.length>ko?{error:`Response too large for "${t}": ${r.length} bytes (max 26214400).`}:{value:r}}const s=[];let o=0;for(;;){const{done:e,value:n}=await r.read();if(e)break;if(o+=n.byteLength,o>ko)return await r.cancel(),{error:`Response exceeded 26214400 bytes while streaming "${t}". Refusing to load.`};s.push(n)}const n=new Uint8Array(o);let i=0;for(const a of s)n.set(a,i),i+=a.byteLength;return{value:(new TextDecoder).decode(n)}})(r,e);return"error"in o?o:{value:JSON.parse(o.value)}}catch(s){return((e,t)=>e instanceof Error&&"AbortError"===e.name?{error:`Fetch timed out after 15000ms for "${t}".`}:{error:`Failed to fetch "${t}": ${e instanceof Error?e.message:String(e)}`})(s,e)}finally{clearTimeout(r)}},Lo=e=>{const t=/* @__PURE__ */new Set;for(const r of e.features){const e=r.geometry?.type;e&&t.add(e)}return[...t]},Fo=(tt(et),Z.union([Z.object({affectedEntries:Z.array(Z.union([Z.object({kind:Z.literal("places"),id:Z.string()}),Z.object({kind:Z.literal("routes"),id:Z.string()}),Z.object({kind:Z.literal("incidents"),id:Z.string()}),Z.object({kind:Z.literal("customGeometries"),id:Z.string()}),Z.object({kind:Z.literal("trafficAreaAnalytics"),id:Z.string()})])).describe("Entries the analysis was attached to: every contributing places / routes / incidents / customGeometries / trafficAreaAnalytics entry. Ranges entries feed the aggregation but do not carry analyses."),sourceIds:Z.array(Z.unknown()).optional().describe(`${ke} (only present when \`geometriesEntryIDs\` was set).`),name:Z.string().describe("Unique name of the analysis within each affected entry."),description:Z.string().optional(),outputFormat:Z.enum(["json","chart"]),analysis:Z.unknown(),skipped:Z.array(ft).optional().describe(Ce),focused:Z.object({incidentsEntryID:Z.string(),focusedCount:Z.number(),droppedIds:Z.array(Z.string()),reason:Z.string().optional()}).optional().describe("Present when the analysis ran with `monitor` AND returned `focusIds: string[]` (+ optional `focusReason`). The tool applied that focus as a side-effect on the source incidents entry.")}),or])),No=(e,t)=>{const r=st(e,t),s={...at(r,{verb:"analyse",requiredHint:`At least one of ${ot(r)} is REQUIRED.`})};return s.name=Z.string().describe('Unique name within each affected entry (e.g. "incidents-per-cluster", "ev-near-route"). Reusing a name replaces the previous analysis on those entries.'),s.description=Z.string().optional().describe("Optional short description of what the analysis computes."),s.outputFormat=Z.enum(["json","chart"]).optional().describe('Shape of the returned value (default: "json"). `json` — compact JSON-serializable object for text display. `chart` — Chart.js `ChartConfiguration` rendered via chart.js in the chat UI. Pick `chart` whenever the user asks for a "chart", "graph", "plot", "histogram", "bar/pie/line chart", etc.'),s.code=Z.string().describe(((e,t)=>{const r=st(e,t),s=nt(r),o=t?it(r):"",n=!!t&&r.length>1,i=!!t&&r.includes("customGeometries"),a=!t||r.includes("incidents")?"When `monitor: { entryId }` is set, the call ALSO registers as a recurring spec on that incidents entry. On every monitor tick the code re-runs against the fresh `incidents` snapshot and these EXTRA sandbox args become available:\n• `previous` — your last result for this `name` on the source entry, or `undefined` on the first run. Use it for id stability (e.g. reuse a prior cluster id when memberIds overlap by ≥50%) and short trend reads ('growing' / 'fading' / 'steady'). Always guard `previous === undefined` — true on every first run.\n• `now: Date` — call-start time; compare against `startTime` / `endTime` on incidents.\n• `log: (...args) => void` — writes into the response logs.\nConstraint: cross-kind reruns are NOT supported — when `monitor` is set, `incidentsEntryIDs: [monitor.entryId]` is the only allowed input. For one-shot cross-kind incidents analysis (no rerun), omit `monitor`.\nFOCUS SIDE-EFFECT: when `applyFocus !== false` AND the result has `focusIds: string[]` (+ optional `focusReason`), `setFocus` is called once on the source entry to dim/highlight the listed incidents. One-shot — the focus side-effect does NOT re-apply on monitor-tick replays.\n\n":"";return`Async JS that aggregates the injected inputs and returns the result.\n\n${At(Pt)}\n\nEach input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading (\`if (places) ...\`, \`routes?.features.length\`, etc.). When defined:\n${s}\n\n`+a+(n?`${xt}\n\n`:"")+`${((e,t="")=>`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• \`json\` (default) — compact object (${"counts, per-kind breakdowns, hex bins, intersections, distance bins"}, …).\n• \`chart\` — Chart.js config: \`{ type: "bar"|"line"|"pie"|"doughnut"|"radar"|"polarArea"|"scatter"|"bubble", data: { labels: (string | string[])[], datasets }, options? }\`. 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${t?` (e.g. ${t})`:""}.`)()}\n\n`+(i?`${Ae}\n\n`:"")+o})(e,t)),((e,t,r)=>e.includes(r)&&(!t||t.kinds.includes(r)))(e,t,"incidents")&&(s.monitor=Z.object({entryId:Z.string().describe("Incidents entry id to register the spec on. Must match `incidentsEntryIDs[0]`. The spec re-runs on every monitor-tick of that entry.")}).optional().describe("Opt-in: register this analysis as a recurring spec on the targeted incidents entry. The code re-runs on every monitor-tick with extra sandbox args (`previous`, `now`, `log`). When set, only `incidentsEntryIDs: [monitor.entryId]` is allowed alongside — cross-kind reruns are not supported."),s.applyFocus=Z.boolean().optional().describe("When `monitor` is set AND the result has `focusIds: string[]` (+ optional `focusReason`), call `setFocus` on the source entry to dim/highlight those incidents. Default: true. One-shot — the focus side-effect does NOT re-apply on monitor-tick replays.")),Z.object(s).refine(lt,{message:ct(r)}).refine(e=>{const t=e.monitor;if(!t)return!0;const r=e.incidentsEntryIDs;if(!Array.isArray(r)||1!==r.length||r[0]!==t.entryId)return!1;for(const s of et)if("incidents"!==s&&e[`${"customGeometries"===s?"geometries":s}EntryIDs`]?.length)return!1;return!0},{message:"When `monitor` is set, only `incidentsEntryIDs: [monitor.entryId]` is allowed (cross-kind reruns are not supported in the lean merge). Omit `monitor` for one-shot cross-kind incidents analysis."})},jo=(No(et,void 0),(e,t)=>{const r=st(e,t),s=r.join(" / "),o=t&&r.includes("incidents")?"Opt-in `monitor: { entryId }` registers the call as a recurring spec on the targeted incidents entry; opt-in `applyFocus` honours `focusIds: string[]` by calling `setFocus`.":"",n="CONTRAST `processData`, which writes NEW renderable map entries (places / custom-geometries / BYOD); `analyseData` never renders or creates entries — only attaches metadata.";if(t)return`Aggregate ${s} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ${n} Each scoped input is exposed as a merged collection and a per-entry record; fields are \`undefined\` when their \`*EntryIDs\` argument is omitted (guard with \`?.\`). Result is attached as \`_analysis[name]\` on every contributing entry. \`outputFormat: "json"\` (default) or \`"chart"\` (Chart.js config). `+o;return`Aggregate ${s} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ${n} Each input is exposed as a merged collection and a per-entry record; fields are \`undefined\` when their \`*EntryIDs\` argument is omitted (guard with \`?.\` / \`if (places) …\`). Result is attached as \`_analysis[name]\` on every contributing entry (ranges feed in but do not carry analyses; BYOD entries are read-only). \`outputFormat: "json"\` (default) or \`"chart"\` (Chart.js config). `+(r.includes("incidents")?"Opt-in `monitor: { entryId }` registers the call as a recurring spec on the targeted incidents entry — the code re-runs on every monitor-tick with `previous` / `now` / `log` available in the sandbox. When `monitor` is set, only `incidentsEntryIDs: [monitor.entryId]` is allowed alongside (no cross-kind reruns). Opt-in `applyFocus` (default true under `monitor`) honours `focusIds: string[]` on the result by calling `setFocus` on the source entry to dim/highlight a subset.":"")}),qo=(jo(et,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all).`),Bo=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:s,incidentsEntryIDs:o,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:a,name:l,description:c,code:d,monitor:u,applyFocus:p}=e,h=e.outputFormat??"json";if(u)return $o(t,u.entryId,l,c,d,h,!1!==p);const m=await St({placesEntryIDs:r,routesEntryIDs:s,incidentsEntryIDs:o,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:a},t);if("error"in m)return{error:m.error};const{resolved:y,sandbox:f,geometries:g}=m.value,b=await Lt(d,Pt,Dt(f,{h3:K,turf:X}),"Analysis");if("error"in b)return{error:b.error};const w=Ot(b.value,h);if("error"in w)return{error:w.error};const v=w.value,I={name:l,description:c,outputFormat:h,analysis:v,timestamp:Date.now()};Tt(t.places,y.places,I),Tt(t.routing,y.routes,I),Tt(t.trafficIncidents,y.incidents,I),Tt(t.trafficAreaAnalytics,y.trafficAreaAnalytics,I),Tt(t.places,g.affectedEntries.filter(e=>"places"===e.kind).map(e=>({id:e.id})),I),Tt(t.customGeometries,g.affectedEntries.filter(e=>"customGeometries"===e.kind).map(e=>({id:e.id})),I);const S=[...y.places.map(e=>({kind:"places",id:e.id})),...y.routes.map(e=>({kind:"routes",id:e.id})),...y.incidents.map(e=>({kind:"incidents",id:e.id})),...y.trafficAreaAnalytics.map(e=>({kind:"trafficAreaAnalytics",id:e.id})),...g.affectedEntries.filter(e=>"places"===e.kind&&!y.places.some(t=>t.id===e.id)).map(e=>({kind:"places",id:e.id})),...g.affectedEntries.filter(e=>"customGeometries"===e.kind).map(e=>({kind:"customGeometries",id:e.id}))],E=n?g.sourceIds:void 0;return{affectedEntries:S,...E&&{sourceIds:E},name:l,...c&&{description:c},outputFormat:h,analysis:v,...g.skipped.length&&{skipped:g.skipped}}},$o=async(e,t,r,s,o,n,i)=>{const a=e.trafficIncidents.entries.find(e=>e.id===t);if(!a)return{error:`No incidents entry with id "${t}". Call getTrafficIncidents first.`};const l={name:r,...s&&{description:s},outputFormat:n,code:o,source:a.id},c=a._analyses?.getResult(r)?.data;e.trafficIncidents.setAnalysisSpec(l);const d=a.timestamp,u=await Ut(l,a.data,c,d);if("error"in u)return{error:u.error};const{value:p}=u;Tt(e.trafficIncidents,[a],{name:r,description:s,outputFormat:n,analysis:p,timestamp:d});const h=i?Go(p,a.id,e):void 0;return{affectedEntries:[{kind:"incidents",id:a.id}],name:r,...s&&{description:s},outputFormat:n,analysis:p,...h&&{focused:h}}},Go=(e,t,r)=>{if(!e||"object"!=typeof e)return;const s=e.focusIds;if(!Array.isArray(s)||0===s.length)return;const o=s.filter(e=>"string"==typeof e);if(0===o.length)return;const n=e.focusReason,i="string"==typeof n?n:void 0,a=r.trafficIncidents.setFocus(t,o,i);return{incidentsEntryID:t,focusedCount:a.focusedCount,droppedIds:a.droppedIds,...i&&{reason:i}}},Uo=Z.union([Z.object({incidentsEntryID:Z.string().describe("The entry the focus was applied to."),focusedCount:Z.number().describe("How many of the requested ids ended up in the focus subset."),droppedIds:Z.array(Z.string()).describe("Ids that were not present on the entry and were dropped from the focus."),reason:Z.string().optional().describe("Echo of the focus reason for downstream display.")}),or]),Wo=Z.object({incidentsEntryID:Z.string().describe("Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source)."),incidentIds:Z.array(Z.string()).min(1).describe("Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored."),reason:Z.string().optional().describe('Short label shown in the UI FocusChip — what this subset represents (e.g. "top 3 by delay").')}),Vo=Z.object({slotIndex:Z.number().int().min(0).optional().describe("Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot."),includeEmptySlots:Z.boolean().optional().describe("Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses."),offset:Z.number().int().min(0).optional().describe("Pagination offset within the filtered slot list. Ignored when slotIndex is provided."),limit:Z.number().int().min(1).max(25).optional().describe("Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.")}),zo=Z.object({totalSlots:Z.number().describe("Total waypoint slots currently staged"),filledSlots:Z.number().describe("Number of staged slots that already contain coordinates"),matchedCount:Z.number().describe("Total slots matching the request before pagination"),returnedCount:Z.number().describe("Number of slots returned in this page"),offset:Z.number().describe("Offset applied to the filtered slot list"),hasMore:Z.boolean().describe("True when more slots can be fetched with a higher offset"),nextOffset:Z.number().optional().describe("Offset to request the next page when hasMore=true"),waypoints:Z.array(hs)}),Ho=(tt(et),{bboxFromGeoJSON:F,calculateProgressAtRoutePoint:N,getCoordinateAtRouteProgress:B,getProgressAtNearestRoutePoint:G,getRouteProgressBetween:U,getRouteProgressForSection:W,getSectionBBox:V}),Yo=[...Pt,"routeUtils"],Jo=Z.object({theme:Z.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline". Use "inverted" for difference/inverse results.'),mode:qe,hidePreviousEntries:Ue("custom-geometries").optional()}),Xo={sourceIds:Z.array(_e).optional().describe(`${ke} (only when \`geometriesEntryIDs\` was set).`),skipped:Z.array(ft).optional().describe(Ce),fitted:dt.optional()},Ko=Z.union([lr({}).extend({placesEntryId:Z.string(),label:Z.string().optional(),connectionsRendered:Z.number().optional(),placesGeometryCount:Z.number().optional().describe("Polygons attached to the new places entry (when `geometries` was returned alongside)."),shownPlaces:Z.boolean().optional(),...Xo}),Z.object({customGeometriesEntryId:_e,customGeometriesCount:Z.number(),label:Z.string(),operation:Z.string().optional(),shownCustomGeometries:Z.boolean().optional(),...Xo}),Z.object({byodEntryId:Z.string(),byodFeatureCount:Z.number(),label:Z.string(),fitted:dt.optional()}),Z.object({fitted:dt}),or]),Zo=(e,t)=>{const r=st(e,t),s={...at(r,{verb:"process",requiredHint:`At least one of ${ot(r)} is REQUIRED.`})};return s.code=Z.string().describe(((e,t)=>{const r=st(e,t),s=t?it(r):"",o=!!t&&r.length>1,n=t&&r.includes("trafficAreaAnalytics")?"`trafficAreaAnalytics` is a FeatureCollection of tile/hex regions whose `feature.properties` carry metric fields (`congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`); use `turf.booleanPointInPolygon` / `turf.lineIntersect` to bridge with places/routes.\n\n":"",i=t&&r.includes("routes")?"`routeUtils` (from @tomtom-org/maps-sdk/core, only useful when `routesEntryIDs` is set):\n• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>`.\n• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startIdx, endIdx)`.\n• `calculateProgressAtRoutePoint(route, pathIndex)`.\n• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }`.\n• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point to the line.\n• `bboxFromGeoJSON(featureOrCollection)`.\n\n":"",a=t&&r.includes("customGeometries")?`${Ae}\n\n`:"",l=r.includes("byod")?"• `byod: FeatureCollection` — writes a NEW BYOD entry (any GeoJSON: Point/Line/Polygon/mixed). Use for derived BYOD layers (e.g. cleaned/filtered customer data).\n":"",c=t?`${nt(r)}\n`:"";return"Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`. At least one of `places` / `geometries` / `fitOnMap` / `byod` must be present. `places` (with optional attached `geometries`), standalone `geometries`, and `byod` are MUTUALLY EXCLUSIVE — only one is written per call, others are dropped. `fitOnMap` composes with any of them.\n\n• `places: Places` — writes a NEW places entry. Filters/merges/subsets of `places.features`, or computed point features (must conform to the Place schema).\n• `placeConnections: { from, to, id? }[]` — lines between output places (endpoints: Place or place id). Requires `places`. Drawn on the new entry's PlacesModule.\n• `geometries: PolygonFeature[]` — polygons (Polygon/MultiPolygon, stable `id` recommended). When `places` is also present, attached to the new places entry; when `places` is absent, written as a NEW custom-geometries entry.\n• `fitOnMap` — bbox or `{ bbox, padding?, animate? }`. Camera move; when alone, no entry written.\n"+l+`\n${At(Yo)}\n\n`+c+"Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. "+n+(o?`${xt}\n\n`:"")+i+"`fitOnMap` (opt): `[W,S,E,N]` BBox or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }` (default padding 50). Camera fits these bounds after the tool runs. Returning ONLY `fitOnMap` skips state writes (pure camera focus); combine with `places` / `geometries` to also write+focus. Derive bboxes via `turf.bbox(feature | FeatureCollection)`.\n\n"+a+s})(e,t)),s.label=Z.string().optional().describe("Short label for the new entry (used by both places and custom-geometries paths)."),s.entryId=je,s.operation=Z.string().optional().describe("Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance on a new custom-geometries entry. Ignored on the places path."),s.show=Z.object({places:We.optional().describe("Render the new places entry (when `places` was returned)."),placesGeometries:Z.object({theme:Z.enum(["filled","outline","inverted"]).optional(),mode:qe}).optional().describe("Render the polygons attached to the new places entry (when both `places` and `geometries` were returned)."),customGeometries:Jo.optional().describe("Render the new custom-geometries entry (when `geometries` was returned WITHOUT `places`).")}).optional().describe("Optional per-output render knobs. The executor only consults the field matching what the code returned (so leaving e.g. `customGeometries` set when `places` was returned is a silent no-op)."),s.connectMarkerType=Z.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Match the module holding the endpoints.'),Z.object(s).refine(lt,{message:ct(r)})},Qo=(Zo(et,void 0),(e,t)=>{const r=st(e,t).join(" / "),s="CONTRAST `analyseData`, which attaches JSON/chart metadata to existing entries and never renders.";return t?`Run dynamic JS over ${r} entries to produce NEW renderable map data — a places, custom-geometries, or BYOD entry (with optional \`placeConnections\` / attached polygon footprints), and/or a \`fitOnMap\` camera move. ${s} Returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\`: when \`places\` is returned, inline \`geometries\` attach to it; otherwise they become a standalone custom-geometries entry. Inputs are \`undefined\` when their \`*EntryIDs\` argument is omitted — guard before reading.`:`Run dynamic JS over any combination of ${r} entries to produce NEW renderable map data — a places, custom-geometries, or BYOD entry (with optional \`placeConnections\` / attached polygon footprints), and/or a \`fitOnMap\` camera move. ${s} Returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\`: when \`places\` is returned, inline \`geometries\` attach to it as place footprints; otherwise they become a standalone custom-geometries entry. \`routeUtils\` is exposed alongside \`h3\` / \`turf\` for route section / progress work. Inputs are \`undefined\` when their \`*EntryIDs\` argument is omitted — guard before reading.`}),en=(Qo(et,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind.`),tn=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)},rn=e=>{if(!Array.isArray(e))return!1;const t=e=>"string"==typeof e||"object"==typeof e&&null!==e&&"geometry"in e;return e.every(e=>{if(!e||"object"!=typeof e)return!1;const r=e;return t(r.from)&&t(r.to)})},sn=(e,t,r)=>void 0===e?{value:void 0}:t(e)?{value:e}:{error:r},on=e=>e&&e.features.length>0?e:void 0,nn=e=>{if(!e||"object"!=typeof e)return"Process code must return `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`.";const t=e,r={},s=sn(t.places,tn,"`places` must be a `Places` FeatureCollection of Point features.");if("error"in s)return s.error;r.places=on(s.value);const o=sn(t.placeConnections,rn,"`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).");if("error"in o)return o.error;if(r.placeConnections=o.value,void 0!==t.geometries){const e=(e=>{if(!Array.isArray(e))return{error:"`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features."};const t=[];for(let r=0;r<e.length;r++){const s=e[r];if(!kt(s))return{error:`\`geometries[${r}]\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.`};t.push(s)}return{value:t}})(t.geometries);if("error"in e)return e.error;r.geometries=e.value.length>0?e.value:void 0}const n=sn(t.fitOnMap,pt,"`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.");if("error"in n)return n.error;r.fitOnMap=n.value;const i=sn(t.byod,tn,"`byod` must be a GeoJSON FeatureCollection (any geometry types).");return"error"in i?i.error:(r.byod=on(i.value),(e=>void 0===e.places&&void 0===e.geometries&&void 0===e.fitOnMap&&void 0===e.byod?"At least one of `places`, `geometries`, `fitOnMap`, `byod` must produce content (empty arrays / FeatureCollections do not count).":void 0===e.places&&void 0!==e.placeConnections?"`placeConnections` requires `places` to be returned alongside.":void 0)(r)??r)},an=async(e,t,r,s)=>{const{label:o,entryId:n,show:i,connectMarkerType:a,geometriesEntryIDs:l}=t,{places:c,placeConnections:d,geometries:u,fitOnMap:p}=r,h=c.features.length,m=`${o?`processed: ${o}`:"processed"} (${h} ${1===h?"place":"places"})`,y=await e.places.addPlaceResult(c,m,n,d,u),f=i?.places?await Bt(e,[y],i.places):void 0;let g,b;if(u?.length&&(g=u.length,i?.placesGeometries&&await e.places.showPlaceGeometries(u,i.placesGeometries.theme??"outline",i.placesGeometries.mode??"replace",y)),d){const t=await(async(e,t,r,s)=>{try{return await(await e.places.getEntryPlacesModule(t,s??"pin")).showConnections(r),{count:r.length}}catch(o){return{error:`Failed to render connections: ${o instanceof Error?o.message:String(o)}`}}})(e,y,d,a);if("error"in t)return t;b=t.count}const w=p?ht(e,p):void 0,v=e.places.entries.find(e=>e.id===y)?.label;return{...ds(c),placesEntryId:y,...v&&{label:v},...f&&{shownPlaces:!0},...void 0!==b&&{connectionsRendered:b},...void 0!==g&&{placesGeometryCount:g},...l&&{sourceIds:s.sourceIds},...s.skipped.length&&{skipped:s.skipped},...w&&{fitted:w}}},ln=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:s,incidentsEntryIDs:o,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:a,code:l}=e,c=await St({placesEntryIDs:r,routesEntryIDs:s,incidentsEntryIDs:o,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:a},t);if("error"in c)return{error:c.error};const{sandbox:d,geometries:u}=c.value,p=await Lt(l,Yo,[...Dt(d,{h3:K,turf:X}),Ho],"Process");if("error"in p)return{error:p.error};const h=nn(p.value);if("string"==typeof h)return{error:h};const{places:m,geometries:y,fitOnMap:f,byod:g}=h;return m?an(t,e,{...h,places:m},u):y?.length?(async(e,t,r,s,o)=>{const{label:n,entryId:i,operation:a,show:l,geometriesEntryIDs:c}=t,d=n?`processed: ${n}`:`processed geometries (${r.length})`,u=await e.customGeometries.addEntry(r,{sourceIds:o.sourceIds,...a&&{operation:a}},d,i);let p=!1;l?.customGeometries&&(await mt(e.customGeometries,[u],l.customGeometries.hidePreviousEntries),await e.customGeometries.showEntry(u,l.customGeometries.theme??"outline",l.customGeometries.mode??"replace"),p=!0);const h=s?ht(e,s):void 0;return{customGeometriesEntryId:{kind:"customGeometries",id:u},customGeometriesCount:r.length,label:d,...a&&{operation:a},...c&&{sourceIds:o.sourceIds},...o.skipped.length&&{skipped:o.skipped},...p&&{shownCustomGeometries:!0},...h&&{fitted:h}}})(t,e,y,f,u):g?(async(e,t,r,s)=>{const{label:o,entryId:n}=t,i=o?`processed: ${o}`:`processed byod (${r.features.length})`,a=await e.byod.addEntry(r,i,{...n&&{explicitId:n}}),l=s?ht(e,s):void 0;return{byodEntryId:a,byodFeatureCount:r.features.length,label:i,...l&&{fitted:l}}})(t,e,g,f):f?{fitted:ht(t,f)}:{error:"Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`."}},cn=oo.extend({date:Z.string().optional(),hour:Z.number().optional(),year:Z.number().optional(),month:Z.number().optional(),week:Z.number().optional(),day:Z.number().optional()}),dn=Z.object({entryId:Z.string().optional().describe('Traffic-area-analytics entry id to query (e.g. "tta-0"). Omit to query the most recently added entry. Use `recallState` to list available ids.'),granularity:Z.enum(["daily","hourly","weekly","monthly","yearly","average"]).optional().describe("Time granularity. Default: 'daily'. Use 'average' for day-of-week × hour patterns."),metric:Z.enum(["speed","congestionLevel","freeFlowSpeed","travelTime","networkLength"]).optional().describe("Filter to a single metric. Default: all requested metrics."),startDate:Z.string().optional().describe("Filter entries from this date (YYYY-MM-DD). Works with 'daily' and 'hourly'."),endDate:Z.string().optional().describe("Filter entries up to this date (YYYY-MM-DD). Works with 'daily' and 'hourly'."),hourStart:Z.number().int().min(0).max(23).optional().describe("Filter hourly/average entries from this hour (0-23)."),hourEnd:Z.number().int().min(0).max(23).optional().describe("Filter hourly/average entries up to this hour (0-23)."),dayOfWeek:Z.array(Z.number().int().min(1).max(7)).optional().describe("Filter 'average' entries by day of week (1=Mon…7=Sun).")}),un=Z.union([Z.object({granularity:Z.string(),count:Z.number(),entries:Z.array(cn),currentVisualization:Z.object({activeMetric:Z.string().optional(),displayMode:Z.string().optional()}).optional()}),or]),pn=Z.union([Z.object({kind:Z.literal("integrator"),description:Z.string().optional()}),Z.object({kind:Z.literal("url"),url:Z.string()}),Z.object({kind:Z.literal("inline"),description:Z.string().optional()})]),hn=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),featureCount:Z.number(),geometryTypes:Z.array(Z.string()),source:pn,shown:Z.boolean()}),mn=hn.extend({features:Z.unknown().describe("GeoJSON FeatureCollection of the entry's features.")}),yn=Z.union([Z.object({entries:Z.array(hn),entryMode:Z.enum(["single","multiple"])}),mn,or]),fn=Z.object({id:Z.string().optional().describe("BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.")}),gn=e=>{const t=/* @__PURE__ */new Set;for(const r of e.features){const e=r.geometry?.type;e&&t.add(e)}return[...t]},bn=Z.object({id:_e.optional().describe('Tagged id to inspect in detail (e.g. `{ kind: "places", id: "ev-stations-oost" }`, `{ kind: "ranges", id: "ranges-0" }`, `{ kind: "customGeometries", id: "bakery-candidates-gap-oost" }`). Omit to list every available id across all slices.')}),wn=Z.object({name:Z.string(),outputFormat:Z.enum(["json","chart"]),timestamp:Z.number(),description:Z.string().optional()}),vn=Z.object({id:_e.describe("Tagged id of this entry."),label:Z.string(),timestamp:Z.number(),featureCount:Z.number(),operation:Z.string().optional().describe("Operation label (custom-geometries entries only)."),sourceIds:Z.array(_e).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:Z.array(wn).optional().describe("Analyses already attached to this entry (places and custom only).")}),In=vn.extend({features:Z.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),Sn=Z.union([Z.object({entries:Z.array(vn)}),In,or]),En=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:s})=>({name:e,outputFormat:t,timestamp:r,...s&&{description:s}})):void 0,Mn=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const e=En(r._analysis);t.push({id:{kind:"places",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,...e&&{analyses:e}})}for(const r of e.ranges.entries){const e=r.ranges.reduce((e,t)=>e+(t.polygon?.features.length??0),0);0!==e&&t.push({id:{kind:"ranges",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:e})}for(const r of e.customGeometries.entries){const e=En(r._analysis);t.push({id:{kind:"customGeometries",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.features.length,...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...e&&{analyses:e}})}return t.sort((e,t)=>t.timestamp-e.timestamp)},xn=(e,t)=>{switch(e.kind){case"places":return((e,t)=>{const r=t.places.entries.find(t=>t.id===e.id);if(!r)return{error:`No places entry with id "${e.id}"`};if(!r.geometries?.length)return{error:`Places entry "${e.id}" has no cached footprints. Call discoverPlaces / locatePlace with \`withGeometries: true\` first.`};const s=En(r._analysis);return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,features:{type:"FeatureCollection",features:r.geometries},...s&&{analyses:s}}})(e,t);case"ranges":return((e,t)=>{const r=t.ranges.entries.find(t=>t.id===e.id);if(!r)return{error:`No ranges entry with id "${e.id}"`};const s=r.ranges.flatMap(e=>e.polygon?.features??[]);return 0===s.length?{error:`Ranges entry "${e.id}" has no polygons.`}:{id:e,label:r.label,timestamp:r.timestamp,featureCount:s.length,features:{type:"FeatureCollection",features:s}}})(e,t);case"customGeometries":return((e,t)=>{const r=t.customGeometries.findById(e.id);if(!r)return{error:`No custom-geometries entry with id "${e.id}"`};const s=En(r._analysis);return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.features.length,features:{type:"FeatureCollection",features:r.features},...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...s&&{analyses:s}}})(e,t);default:return{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.'}}},Pn=Z.object({id:Z.string().optional().describe('Entry ID to retrieve (e.g. "places-3"). Omit to list all entries.')}),Dn=Z.object({name:Z.string(),outputFormat:Z.enum(["json","chart"]),timestamp:Z.number(),description:Z.string().optional()}),_n=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),featureCount:Z.number().describe("Number of Place features stored in this entry."),shown:Z.boolean().describe("Whether this entry is currently rendered on the map."),markerType:Z.enum(["pin","base-map","pin-clustered"]).optional().describe("Which marker theme is rendering this entry (undefined when `shown` is false)."),analyses:Z.array(Dn).optional().describe("Analyses already attached to this entry via analyseData (name + output format).")}),Tn=Z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several entries render simultaneously; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),An=e=>{const t=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),places:lr(e)});return Z.union([Z.object({entries:Z.array(_n),entryMode:Tn}),t.extend({entryMode:Tn}),or])},Cn=(An({}),e=>async(t,r)=>{const{id:s}=t,o=r.places.entries,n=r.places.entryMode,i=r.places.shownEntryIds;if(!s)return{entries:[...o].sort((e,t)=>t.timestamp-e.timestamp).map(({id:e,label:t,timestamp:s,places:o,_analysis:n})=>{const a=i.has(e),l=a?r.places.getShownMarkerType(e):void 0;return{id:e,label:t,timestamp:s,featureCount:o.length,shown:a,...l&&{markerType:l},...n?.length&&{analyses:n.map(({name:e,outputFormat:t,timestamp:r,description:s})=>({name:e,outputFormat:t,timestamp:r,...s&&{description:s}}))}}}),entryMode:n};const a=o.find(e=>e.id===s);if(!a)return{error:`No entry found with id "${s}"`};const l=ds(a.places,e);return{id:a.id,label:a.label,timestamp:a.timestamp,places:l,entryMode:n}}),kn=(Cn({}),{classificationPrompt:"List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processData, analyseData, updatePlacesDisplay).",tags:["place","location"],examples:["recallPlaces()",'recallPlaces({ id: "places-0" })'],examplePrompts:["What places did I search for?","List every places entry with its ID","Which IDs can I pass to processData or updatePlacesDisplay?","Recall the bakeries I discovered earlier","What is in entry places-2?"],relatedTools:["discoverPlaces","locatePlace","reverseGeocode","updatePlacesDisplay"],dependsOn:["locatePlace","discoverPlaces"]}),On=Z.object({id:Z.string().optional().describe('Entry ID to retrieve (e.g. "ranges-0"). Omit to list all entries.')}),Rn=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),originCount:Z.number()}),Ln=Z.object({origin:Z.object({query:Z.string().optional(),position:Z.tuple([Z.number(),Z.number()])}),budgets:Z.array(Z.object({type:Z.string(),value:Z.number()}))}),Fn=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),ranges:Z.array(Ln)}),Nn=Z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several reachable-range entries render at once; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),jn=Z.union([Z.object({entries:Z.array(Rn),entryMode:Nn}),Fn.extend({entryMode:Nn}),or]),qn=Z.object({id:Z.string().optional().describe('Entry ID to retrieve (e.g. "routes-1"). Omit to list all entries.')}),Bn=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),shown:Z.boolean().describe("Whether this entry is currently rendered on the map.")}),$n=Z.object({maxAlternatives:Z.number().optional(),costModel:Is.optional(),when:Ss.optional()}),Gn=Z.object({id:Z.string(),label:Z.string(),timestamp:Z.number(),routes:pr,waypoints:Z.array(Z.object({position:Z.array(Z.number()).length(2).describe("[longitude, latitude]")})),params:$n}),Un=Z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several routes render at once; `single` enforces "at most one route on the map" — switching to it auto-clears non-latest entries.'),Wn=Z.union([Z.object({entries:Z.array(Bn),entryMode:Un}),Gn.extend({entryMode:Un}),or]),Vn=Z.object({id:Z.string(),label:Z.string()}),zn=Z.enum(["single","multiple"]),Hn=Z.union([Z.object({places:Z.array(Vn).describe("Places history entries — newest first."),routes:Z.array(Vn).describe("Routes history entries — newest first."),ranges:Z.array(Vn).describe("Reachable-range history entries — newest first."),entryModes:Z.object({places:zn,routes:zn,ranges:zn}).describe("Per-slice display policy. `multiple` (default) lets several entries render at once; `single` keeps only the latest entry — set via setEntryMode tools."),mapStyle:Z.object({id:Z.string().optional().describe('Standard style id (e.g. "standardLight").')}).nullable().describe("Current map style. `null` when the map has no style assigned yet."),mapPOIs:Z.object({visible:Z.boolean().optional(),filterMode:Z.enum(["only","all_except"]).optional(),categories:Z.array(Z.string()).optional()}).nullable().describe("Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session."),traffic:Z.object({flow:Z.object({visible:Z.boolean().optional()}).nullable().describe("Traffic-flow layer config. `null` when the module is uninitialised."),incidents:Z.object({visible:Z.boolean().optional()}).nullable().describe("Traffic-incidents layer config. `null` when the module is uninitialised.")}).describe("Real-time traffic visualization configs.")}),or]),Yn=Z.object({}),Jn=["places","routes","ranges","customGeometries","trafficAreaAnalytics","trafficIncidents","byod","mapPOIs","trafficFlow","mapStyle"],Xn=Z.union([Z.object({success:Z.literal(!0),cleared:Z.array(Z.enum(Jn)).describe("Slices that were wiped / reset to defaults.")}),or]),Kn=Z.object({}),Zn=Z.enum(Kt),Qn=Z.enum(["single","multiple"]),ei=Z.union([Z.object({success:Z.literal(!0),slice:Zn,mode:Qn,droppedEntryIds:Z.array(Z.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),or]),ti=Z.object({slice:Zn.describe("Which entry-owning slice to reconfigure. Names match the keys on the agent state exactly — `routing` (not `routes`), `customGeometries` (not `geometries`), and so on."),mode:Qn.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),ri=`Switch the display policy of one entry-owning slice between \`multiple\` and \`single\`. Supported slices: ${Kt.join(", ")}. \`single\` ⇒ at most one entry on the map, and any older entries get dropped from history immediately. Use when the user asks for a "clean view" / "only show one at a time" or wants to compare overlays again.`,si=Z.union([Z.object({incidentsEntryID:Z.string(),alreadyRunning:Z.boolean().describe("True when the entry already had a running monitor — start is idempotent. When true, any new intervalMs / filter context in this call was IGNORED; the monitor keeps running with whatever it was started with. Call stopTrafficIncidentsMonitor first if you need different settings.")}),or]),oi=Z.object({incidentsEntryID:Z.string().describe("Id of the incidents entry to monitor (returned by `getTrafficIncidents`)."),intervalMs:Z.number().int().positive().optional().describe("Polling interval in milliseconds. Defaults to 60000 (60s).")}),ni=Z.union([Z.object({incidentsEntryID:Z.string(),wasRunning:Z.boolean().describe("True when a monitor was actually stopped; false when it was already idle.")}),or]),ii=Z.object({incidentsEntryID:Z.string().describe("Id of the incidents entry whose monitor should stop.")}),ai=Z.enum(Zt),li=Z.union([Z.object({success:Z.literal(!0),cleared:Z.array(ai)}),or]),ci=Z.object({layers:Z.array(ai).optional().describe("Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: "+Zt.map(e=>`\`${e}\``).join(", ")+". Map-only — slice histories (`entries`) are kept.")}),di=(Z.union([Z.object({success:Z.literal(!0)}),or]),Z.object({sectionType:Z.enum(H).describe("e.g. country|traffic|motorway|toll"),id:Z.string().describe("From a shown route section"),padding:Z.number().optional().describe("pixels, default: 50")}),Z.union([Z.object({count:Z.number(),trafficIncidents:Z.array(Z.object({id:Z.string(),category:Z.string(),magnitudeOfDelay:Z.string().describe("unknown|minor|moderate|major|indefinite"),description:Z.string().optional(),delayInSeconds:Z.number().optional(),roadCategory:Z.string().optional(),averageSpeedKmph:Z.number().optional()}))}),or])),ui=Z.object({}),pi=Z.union([Z.object({styles:Z.array(Z.string()).readonly()}),or]),hi=Z.object({}),mi=Z.union([Z.object({success:Z.literal(!0),language:Z.string()}),or]),yi=Z.object({language:Z.string().describe('e.g. "en-US", "fr-FR"')}),fi=Z.union([Z.object({success:Z.literal(!0),style:Z.string()}),or]),gi=Z.object({style:Z.enum(g).describe("Map visual theme")}),bi=Z.union([Z.object({success:Z.literal(!0),visible:Z.boolean(),layerGroups:Z.array(Z.string())}),or]),wi=Z.object({visible:Z.boolean(),layerGroups:Z.array(Z.enum(y)).describe("Vector-tile base-map layer groups to show or hide.")}),vi=Z.union([Z.object({success:Z.literal(!0),allMapPOIsVisible:Z.boolean().optional(),filterCategories:Z.object({show:Z.enum(["only","all_except"]),values:Z.array(Z.string())}).optional(),reset:Z.boolean().optional()}),or]),Ii=Z.object({reset:Z.boolean().optional().describe("Reset all tile-POI configuration to defaults. Other params ignored when true."),allMapPOIsVisible:Z.boolean().optional().describe("Overall visibility of the vector-tile POI icons baked into the base map style."),filterCategories:Z.object({show:Z.enum(["only","all_except"]).describe('"only": keep only these categories. "all_except": hide these categories.'),values:Z.array(Z.string()).describe('MapStylePOICategory / POICategoryGroup values (e.g. "RESTAURANT", "FOOD_DRINKS_GROUP"). Fine-grained codes like "ITALIAN_RESTAURANT" not supported here.')}).optional().describe("Restrict which built-in vector-tile POI categories are shown.")}),Si=Z.object({visible:Z.boolean()}),Ei=Z.union([Z.object({success:Z.literal(!0),trafficFlowVisible:Z.boolean()}),or]),Mi=Z.object({visible:Z.boolean()}),xi=Z.union([Z.object({success:Z.literal(!0),trafficIncidentsVisible:Z.boolean()}),or]),Pi=Z.enum(["show","hide","remove"]).describe("`show` renders the listed entries (lazy-init their CustomGeoJSONModule); `hide` removes them from the map but keeps them in session state; `remove` hides AND drops them from history."),Di=Z.object({entryIds:Z.array(Z.string()).min(1).optional().describe('BYOD entry ids to act on (e.g. ["byod-0"]). Use `recallByod` to list. Omit when `action: "hide"` and `clearAll: true` to hide every shown BYOD entry in one call.'),action:Pi,clearAll:Z.boolean().optional().describe('Only valid with `action: "hide"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.'),hideOthers:Z.boolean().optional().describe('Only valid with `action: "show"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).')}).refine(e=>!("hide"!==e.action||!e.clearAll)||!!e.entryIds?.length,{message:'`entryIds` is required unless `action: "hide"` with `clearAll: true`.'}).refine(e=>!e.clearAll||"hide"===e.action,{message:'`clearAll: true` is only valid with `action: "hide"`.'}).refine(e=>!e.hideOthers||"show"===e.action,{message:'`hideOthers: true` is only valid with `action: "show"`.'}),_i=Z.union([Z.object({action:Pi,affectedIds:Z.array(Z.string()).describe("Entry ids that were actually shown / hidden / removed."),shown:Z.array(Z.string()).describe("BYOD entry ids currently rendered on the map after this call.")}),or]),Ti=Z.object({id:Z.string(),label:Z.string(),featureCount:Z.number(),markerType:Z.enum(["pin","base-map","pin-clustered"]).describe("How the entry is rendered on the map.")}),Ai=Z.union([Z.object({success:Z.literal(!0),totalShown:Z.number(),shown:Z.array(Ti).describe("Entries currently on the map, with their labels."),actuallyAdded:Z.array(Z.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:Z.array(Z.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),geometriesRendered:Z.number().optional().describe("Number of boundary polygons rendered when `geometry` was set."),geometriesSkipped:Z.array(Z.object({placeId:Z.string(),reason:Z.string()})).optional().describe("Places whose boundary could not be rendered (no geometry data source, fetch failure).")}),or]),Ci=Z.object({add:Z.array(Z.string()).optional().describe('Entry ids to show (e.g. ["places-1"]). Omit while still setting `markerType` or `geometry` to target the most recently stored entry.'),remove:Z.array(Z.string()).optional().describe("Entry ids to hide."),addMatching:Z.array(Z.string()).optional().describe("Category labels to show."),removeMatching:Z.array(Z.string()).optional().describe("Category labels to hide."),clear:Z.boolean().optional().describe("Hide every displayed entry."),markerType:Z.enum(["pin","base-map","pin-clustered"]).optional().describe('How newly-added entries are rendered as markers: "pin", "base-map", or "pin-clustered" (pins with a count badge where nearby ones aggregate). Defaults to "pin" unless `geometry` is set without an explicit `markerType`, in which case markers are skipped. Entries previously shown with another marker type are moved to this one.'),geometry:$e.optional(),fitBounds:Z.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.markerType||void 0!==e.geometry,{message:"updatePlacesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, markerType, geometry."}),ki=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),s=/* @__PURE__ */new Set;for(const o of e.places.entries){const e=o.label.toLowerCase();r.some(t=>e.includes(t))&&s.add(o.id)}return[...s]},Oi=Z.object({id:Z.string(),label:Z.string(),routeCount:Z.number().describe("Number of route alternatives in the entry."),selectedIndex:Z.number().describe("Highlighted alternative index (0 unless overridden by this call).")}),Ri=Z.union([Z.object({success:Z.literal(!0),totalShown:Z.number(),shown:Z.array(Oi).describe("Routes entries currently on the map, with their labels."),actuallyAdded:Z.array(Z.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:Z.array(Z.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),mainColor:Z.string().optional().describe("Sticky main colour now applied to every shown route, when set by this or a previous call.")}),or]),Li=Z.object({add:Z.array(Z.string()).optional().describe('Entry ids to show (e.g. ["routes-1"]). Omit while still setting `selectedIndex` to target the most recently stored entry.'),remove:Z.array(Z.string()).optional().describe("Entry ids to hide."),addMatching:Z.array(Z.string()).optional().describe("Label substrings to show."),removeMatching:Z.array(Z.string()).optional().describe("Label substrings to hide."),clear:Z.boolean().optional().describe("Hide every displayed entry."),selectedIndex:Z.number().int().min(0).optional().describe("Which alternative variant to highlight on newly-added entries (default: 0). Use to switch the selected alternative on a multi-alternative result. Applied after the entry is shown — entries previously rendered with another index are re-rendered."),fitBounds:Z.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise."),mainColor:Z.string().optional().describe('Recolor the route line + waypoint icons. CSS named colour ("coral", "steelblue") or hex ("#FF0000"). Sticky — applied to every currently-shown entry AND remembered so subsequent shows pick it up automatically. Can be set by itself (no add/remove/clear) to recolor without changing visibility.')}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.selectedIndex||void 0!==e.mainColor,{message:"updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, mainColor."}),Fi=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),s=/* @__PURE__ */new Set;for(const o of e.routing.entries){const e=o.label.toLowerCase();r.some(t=>e.includes(t))&&s.add(o.id)}return[...s]},Ni={mode:Z.enum(["hexgrid-3d","hexgrid-2d","square-3d","square-2d","heatmap"]).optional().describe("Visualization mode. 'hexgrid-3d' (default): 3D hexagons. 'hexgrid-2d': flat hexagons. 'square-3d': 3D squares. 'square-2d': flat squares. 'heatmap': density layer."),metric:Z.enum(["congestionLevel","speed","travelTime"]).optional().describe("Metric to visualize. Defaults to first fetched metric on the targeted entries."),colorTheme:Z.enum(["trafficLight","heat","monochrome","viridis","plasma"]).optional().describe("Preset color theme. 'trafficLight' (green→amber→red, default), 'heat' (blue→orange→red), 'monochrome' (grey), 'viridis' (purple→green→yellow), 'plasma' (purple→magenta→yellow)."),heightScale:Z.number().optional().describe("Extrusion height multiplier / visual maximum in meters. Default: metric-dependent."),scaleMode:Z.enum(["raw","predefinedRange","currentRange"]).optional().describe("Height scaling mode. 'raw' (default), 'predefinedRange' (normalize to SDK range), 'currentRange' (normalize to live data range)."),filter:Z.object({min:Z.number().optional(),max:Z.number().optional()}).refine(e=>void 0!==e.min||void 0!==e.max,{message:"Filter must have at least one of min or max"}).optional().describe("Filter visible tiles for the active metric by value range.")},ji=Z.object({id:Z.string(),label:Z.string(),activeMetric:Z.string().optional(),displayMode:Z.string().optional()}),qi=Z.object({add:Z.array(Z.string()).optional().describe('Entry ids to render (e.g. ["tta-0"]). Use `recallState` to list available ids.'),remove:Z.array(Z.string()).optional().describe("Entry ids to hide."),addMatching:Z.array(Z.string()).optional().describe("Label substrings to show."),removeMatching:Z.array(Z.string()).optional().describe("Label substrings to hide."),clear:Z.boolean().optional().describe("Hide every displayed entry."),fitBounds:Z.boolean().optional().describe("Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added."),...Ni}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.mode||void 0!==e.metric||void 0!==e.colorTheme||void 0!==e.heightScale||void 0!==e.scaleMode||void 0!==e.filter,{message:"updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, mode, metric, colorTheme, heightScale, scaleMode, filter."}),Bi=Z.union([Z.object({success:Z.literal(!0),totalShown:Z.number(),shown:Z.array(ji).describe("Traffic-area-analytics entries currently on the map."),actuallyAdded:Z.array(Z.string()),actuallyRemoved:Z.array(Z.string())}),or]),$i=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),s=/* @__PURE__ */new Set;for(const o of e.trafficAreaAnalytics.entries)r.some(e=>o.label.toLowerCase().includes(e))&&s.add(o.id);return[...s]},Gi=async(e,t,r)=>{const s=e.trafficAreaAnalytics.findById(t);if(!s)return;const o=s.data.properties?.metrics?.[0]??"congestionLevel",n=r.mode??"hexgrid-3d",i=r.metric??o,a=r.colorTheme??"trafficLight",l=await e.trafficAreaAnalytics.getEntryModule(t);l.setMode(n),l.setMetric(i),l.setColor(a),void 0===r.heightScale&&void 0===r.scaleMode||l.setHeight({...void 0!==r.heightScale&&{maxHeightMeters:r.heightScale},...void 0!==r.scaleMode&&{scaleMode:r.scaleMode}}),r.filter?l.filter({min:r.filter.min,max:r.filter.max}):l.clearFilter()},Ui=e=>({id:e.id,label:e.label,...e._config?.activeMetric&&{activeMetric:e._config.activeMetric},...e._config?.displayMode&&{displayMode:e._config.displayMode}}),Wi=Z.union([Z.object({success:Z.literal(!0),visible:Z.boolean(),count:Z.number()}),or]),Vi=Z.object({visible:Z.boolean().optional().describe("Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.")}),zi="Dynamic-JS sandbox over any combination of places / routes / incidents / geometries / trafficAreaAnalytics / byod entries. Each input arg is `undefined` when its `*EntryIDs` was omitted.",Hi={locatePlace:{description:"Resolve ONE named location (landmark, venue, city, neighborhood, address, POI) to a normalized place summary. Use this for any SINGLE named place — including requests for its boundary / polygon / outline / footprint (via `geometry`), pin or zoom display (via `show`), or staging as a routing waypoint. For multi-place or category searches, use discoverPlaces.",classificationPrompt:"Resolve a single location string (landmark, city, address) to a place without showing it; optionally stages a waypoint slot. Do NOT call as a precursor to discoverPlaces — discoverPlaces resolves named areas itself via `where.query` (or `where.municipalities` / `where.placeId`).",inputSchema:gs,outputSchema:bs,execute:async(e,t)=>{const{query:r,queryAs:s,waypointIndex:o,where:n,show:i,entryId:a,geometry:l}=e;try{const e=await ws(n,t);if("error"in e)return e;const c=await fs(r,s,e.bias);if(!c)return{error:`No result found for "${r}"`};const d=await t.places.addPlaceResult(c,is(c,{query:r}),a);void 0!==o&&t.routing.setWaypointAt(o,c);const u=i?await(async(e,t,r,s)=>{const o=await Bt(e,[r],{...s,zoomMode:"none"});if("auto"===s.zoomMode){if(t.bbox)e.baseMap.mapLibreMap.fitBounds(t.bbox);else{const r=$(t);r&&e.baseMap.mapLibreMap.flyTo({center:r,zoom:13})}o.zoomMode=!0}return o})(t,c,d,i):void 0;let p,h;if(l){const e=await qt(t,d,l);p=e.fetched>0,e.shown&&(h=!0)}return{...cs(c),...void 0!==o&&{waypointIndex:o},...u&&{shown:u},...void 0!==p&&{geometryFetched:p},...void 0!==h&&{geometryShown:h}}}catch(c){return{error:`Location resolution failed: ${c instanceof Error?c.message:String(c)}`}}},tags:["locate","place","location","waypoint"],examples:['locatePlace("Buckingham Palace", queryAs: "poi", where: { mode: "within", viewport: true }, waypointIndex: 0)','locatePlace("Tower Bridge", queryAs: "poi", where: { mode: "nearby", viewport: true }, waypointIndex: 1)','locatePlace("Westminster Abbey", queryAs: "poi", where: { mode: "global" })','locatePlace("10 Downing Street", queryAs: "place", where: { mode: "nearby", position: [4.9, 52.37] })','locatePlace("Amsterdam", queryAs: "place", where: { mode: "global" }, entryId: "amsterdam-city", geometry: { show: true }) // outline the city','locatePlace("Utrecht", queryAs: "place", where: { mode: "global" }, entryId: "utrecht-city", geometry: { show: true, mode: "add" }) // also outline a second city'],examplePrompts:["Where is the Louvre?","Pin Central Station on the map","What are the coordinates of Times Square?","Set my origin to Schiphol Airport","Outline Amsterdam on the map","Show the boundary of Berlin"],relatedTools:["setRoute","addWaypointsToRoute","getCurrentWaypoints","recallPlaces"]},reverseGeocode:{description:"Convert [lng, lat] to an address and place summary. Use after a click / getViewport / getCurrentLocation. Coordinates only — for text queries use locatePlace or discoverPlaces.",classificationPrompt:"Convert [lng, lat] coordinates to an address; use after map clicks or any tool that returns a position.",inputSchema:To,outputSchema:_o,execute:async(e,t)=>{const{position:r}=e,s=r;try{const e=await _({position:s});return e?(await t.places.addPlaceResult(e,is(e)),cs(e)):{error:"No result found for the given coordinates"}}catch(o){return{error:`Reverse geocoding failed: ${o instanceof Error?o.message:String(o)}`}}},tags:["location","place"],examples:["reverseGeocode({ position: [4.9, 52.4] })"],examplePrompts:["What's the address at this point?","Which city is [4.9, 52.4] in?","Name the place where I clicked"],relatedTools:["getViewport","getCurrentLocation","updatePlacesDisplay","locatePlace"]},discoverPlaces:e=>((e,t)=>({...t,description:Bs(e),inputSchema:qs(e),outputSchema:Os(e),execute:Ys(e)}))(e.featureFlags??{},Js),processData:(Yt={classificationPrompt:`${zi} WRITES NEW renderable map entries — returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\` to write a places, custom-geometries, or BYOD entry, move the camera, or any combination. NOT for digests/charts/counts — those are \`analyseData\` (metadata only, never renders). Use for: filter/merge places (set ops), connect places (hub-spoke), compute polygons (union/buffer/h3-coverage/difference), focus camera on route sections via \`routeUtils.getSectionBBox\`, cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile).`,tags:["turf","h3","geometry","place","route","aggregation","connections"],examples:['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" } } })','processData({ placesEntryIDs: ["places-1"], code: "const [hub, ...rest] = places.features; return { places, placeConnections: rest.map(p => ({ from: hub, to: p })) };" })','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" } } })','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 } };" })','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) };" })','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" } } })','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" })','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" })','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" } } })','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" })','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" })','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" } } })'],examplePrompts:["Keep only the vegan restaurants","Draw lines from this hub to every nearby cafe","Show the h3 hex coverage of these places","Zoom to the worst traffic on the route","Focus on the first toll section","Show EV stations within 500 m of this route","Union the boundaries of these cities","Buffer this neighbourhood by 500 m","Areas of Amsterdam not reachable from the hospitals","Places inside the reachable area","Filter EV stations to those inside high-congestion tiles","Outline the congestion hot zone as a polygon"],relatedTools:["analyseData","recallPlaces","recallRoutes","recallGeometries","recallState"],dependsOn:["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","recallPlaces","recallRoutes","recallGeometries"]},e=>((e,t)=>{const r=e.enabledDataKinds??et;return{description:Qo(r,e.scope),inputSchema:Zo(r,e.scope),outputSchema:Ko,execute:ln,scopeSchema:tt(r),scopePrompt:en(r),...t}})(e,Yt)),analyseData:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??et;return{description:jo(r,e.scope),inputSchema:No(r,e.scope),outputSchema:Fo,execute:Bo,scopeSchema:tt(r),scopePrompt:qo(r),...t}})(t,e))({classificationPrompt:`${zi} WRITES metadata only — returns JSON or a Chart.js config (\`outputFormat: "chart"\`), attached as \`_analysis[name]\` on every contributing entry. Never renders or creates new map entries (that is \`processData\`). Use for: counts / groupings / charts (by-category, hex density); route summaries (per-leg / per-country / alternative compare); route TRAFFIC analysis from \`route.properties.sections.traffic[]\` — pick THIS for "distribution / count / chart of <something> along the route", NOT \`getTrafficIncidents\`; geometry stats (areas, h3 coverage); historical traffic-area-analytics breakdowns (congestion / speed / travel-time per tile); cross-kind correlations (incidents-per-cluster, places-near-route, distance-to-route bins, route-segments-by-tile-metric). For recurring monitor-tick reruns with \`previous\` / \`now\` / \`log\` and optional \`focusIds\` side-effect, set \`monitor: { entryId }\` (only \`incidentsEntryIDs: [monitor.entryId]\` allowed alongside).`,tags:["analysis","turf","h3","place","route","geometry"],examples:['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 };" })','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 };" })','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]) }] } };" })','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 }));" })','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)) }] } };" })','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\' } } } };" })','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;" })','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 };" })','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\' } } } };" })','analyseData({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "areas", code: "return geometries.map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));" })','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)) }] } };" })','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;" })','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;" })',"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) }] } };\" })",'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;" })','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 };" })','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 };" })','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] }));" })',"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() };\" })",'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\' };" })'],examplePrompts:["How many of each POI category are in these results?","h3 hex density of these places","Bar chart of the top POI categories","How long is each route alternative?","Compare travel times across alternatives","How many EV stations are within 500 m of the route?","Chart the distance from each cafe to the route, binned in km","Histogram of place-to-route distances","Distribution of traffic incidents along the drive from Paris to Brussels","Chart the incidents on the route by category","Total delay per country on this route","How big are these areas?","Bar chart of city areas","Incidents per place cluster","Total delay per traffic corridor","Congestion distribution across the analytics tiles","Average historical speed per tile, as a chart","Group these EV stations by the congestion level of the tile they fall in","For the route, which tiles does it cross and what is their average congestion?","How many incidents of each category?","Top 5 incidents by delay","Total delay grouped by road","Keep counting the incidents and tell me when it grows or fades","Highlight the 3 worst incidents by delay on the map","Bar chart of incidents per category","Which jams are growing vs fading on this monitored area?"],relatedTools:["processData","focusIncidents","recallPlaces","recallRoutes","recallGeometries","recallState"],dependsOn:["discoverPlaces","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","recallPlaces","recallRoutes","recallGeometries"]}),setRoute:{description:"Calculate or recalculate a route. Provide `locations` to set/replace origin/stops/destination, `parameters` to update cost model / alternatives / when, or both in a single call. At least one of `locations` or `parameters` MUST be provided. If only `parameters` is given and waypoints already exist, recalculates with the new options; if no waypoints exist yet, parameters are stored and no recalculation runs. Car/driving only. Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallRoutes / addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / getRouteProgress instead.",classificationPrompt:"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.",inputSchema:xs,outputSchema:Ms,execute:async(e,t)=>{const{locations:r,parameters:s,showOnMap:o,hidePreviousEntries:n}=e;try{let e;if(s&&t.routing.setParams(s),r){const s=await Promise.all(r.map(e=>vs(e,t))),o=[];for(let e=0;e<r.length;e++){if(null!==s[e])continue;const t=r[e];"query"in t?o.push(`"${t.query}"`):"placeId"in t&&o.push(`placeId "${t.placeId}"`)}if(o.length>0)return{error:`Could not resolve: ${o.join(", ")}`};if(e=Ps(s.map(e=>e?.place??null)),!e)return{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}else if(e=Ps(t.routing.planningSlots),!e)return{success:!0};return _s(t,e,o,n)}catch(i){return{error:`Route calculation failed: ${i instanceof Error?i.message:String(i)}`}}},tags:["route","waypoint","location"],examples:['setRoute({ locations: [{ query: "Amsterdam" }, { query: "Brussels" }] })','setRoute({ locations: [{ query: "Eiffel Tower" }, { query: "Louvre Museum" }, { query: "Notre Dame" }] })',"setRoute({ parameters: { maxAlternatives: 2 } })",'setRoute({ parameters: { costModel: { routeType: "fast", avoid: ["tollRoads"] } } })','setRoute({ parameters: { when: { option: "departAt", date: "2025-06-01T09:00:00Z" } } })','setRoute({ locations: [{ query: "Paris" }, { query: "Lyon" }], parameters: { costModel: { routeType: "short" } } })'],examplePrompts:["Get directions from Paris to Lyon","Route me from Amsterdam to Brussels","Plan a new trip to the airport","Change my origin to Schiphol","Re-route from a different starting point","Avoid toll roads","Find the fastest route","Show 2 alternative routes","Depart at 9am"],relatedTools:["updateRoutesDisplay","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","recallRoutes"]},addWaypointsToRoute:{description:"Extend the current route — prepend a new origin in front of the existing one, append a new destination after the existing one, and/or insert intermediate stops at optimal positions along the existing route geometry. Existing origin/destination are preserved as intermediate stops, never replaced. Always recalculates. At least one of `origin`, `destination`, or `stops` MUST be provided. Requires an existing route. To outright replace waypoints, use setRoute instead.",classificationPrompt:"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.",inputSchema:As,outputSchema:Ts,execute:async(e,t)=>{const{origin:r,destination:s,stops:o,showOnMap:n,hidePreviousEntries:i}=e;try{const e=await(async e=>{const t=e.routing.currentEntryModule;if(!t)return"No existing route on the map. Use calculate-route or updateRoutesDisplay first.";const r=t.getShown(),s=r?.mainLines,o=r?.waypoints;return s&&s.features.length?!o||o.features.length<2?"No existing waypoints available. Use calculate-route first to create a route with waypoints.":{shownRouteLines:s,shownWaypoints:o}:"No existing route available. Use calculate-route first."})(t);if("string"==typeof e)return{error:e};const{shownRouteLines:a,shownWaypoints:l}=e,c=await(async(e,t)=>{const{origin:r,destination:s,stops:o=[]}=e,n=[...r?[{kind:"origin",location:r}]:[],...s?[{kind:"destination",location:s}]:[],...o.map(e=>({kind:"stop",location:e}))],i=await Promise.all(n.map(({location:e})=>vs(e,t))),a=[];for(let c=0;c<n.length;c++)null===i[c]&&a.push(Cs(n[c].location));if(a.length>0)return{error:`Could not resolve: ${a.join(", ")}`};const l={stops:[]};for(let c=0;c<n.length;c++){const e=i[c]?.place;"origin"===n[c].kind?l.origin=e:"destination"===n[c].kind?l.destination=e:l.stops.push(e)}return l})({origin:r,destination:s,stops:o},t);if("error"in c)return c;const d=[...c.origin?[c.origin]:[],...l.features,...c.destination?[c.destination]:[]],u=Ps(c.stops.length>0?J(a.features[0],d,c.stops):d);return u?_s(t,u,n,i):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(a){return{error:`Failed to add waypoints to route: ${a instanceof Error?a.message:String(a)}`}}},tags:["route","waypoint"],examples:['addWaypointsToRoute({ stops: [{ query: "Westminster Abbey" }] }) // single intermediate stop','addWaypointsToRoute({ stops: [{ query: "Reims" }, { query: "Metz" }] }) // multiple stops, settle into along-route order','addWaypointsToRoute({ origin: { query: "my house" } }) // prepend new origin; previous origin becomes a stop','addWaypointsToRoute({ destination: { query: "Heathrow Airport" } }) // append new destination; previous destination becomes a stop','addWaypointsToRoute({ origin: { query: "Schiphol" }, destination: { query: "Brussels" } }) // extend on both ends','addWaypointsToRoute({ origin: { query: "X" }, stops: [{ query: "Y" }] }) // combine endpoint extension and stops'],examplePrompts:["Add a stop at Westminster Abbey","Add stops at Reims and Metz on the way","Pick me up from my house first, then continue the trip","Continue past the destination to Heathrow"],relatedTools:["locatePlace","setRoute","updateRoutesDisplay","removeWaypointsFromRoute","replaceWaypointInRoute"],dependsOn:["setRoute"]},removeWaypointsFromRoute:{description:"Remove one or more waypoints from the current route by index; always recalculates. Index 0 = origin, last = destination (both protected when removing them would leave fewer than 2). Requires an existing route.",classificationPrompt:"Remove one or more waypoints by index from the current route and recalculate.",inputSchema:Eo,outputSchema:So,execute:async(e,t)=>{const{waypointIndices:r,showOnMap:s,hidePreviousEntries:o}=e;try{const e=((e,t)=>{if(!e||e.length<2)return"No existing waypoints available. Use calculate-route first to create a route with waypoints.";const r=new Set(t);if(r.size!==t.length)return"Duplicate waypoint indices are not allowed.";for(const s of t)if(s<0||s>=e.length)return`Invalid waypoint index ${s}. Valid range is 0 to ${e.length-1}.`;return e.length-t.length<2?"Cannot remove waypoints: at least 2 waypoints (origin and destination) must remain.":e.filter((e,t)=>!r.has(t))})(t.routing.currentWaypoints,r);if("string"==typeof e)return{error:e};const n=Ps(e);return n?_s(t,n,s,o):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(n){return{error:`Failed to remove waypoints from route: ${n instanceof Error?n.message:String(n)}`}}},tags:["route","waypoint"],examples:["removeWaypointsFromRoute({ waypointIndices: [1] }) // remove a single stop","removeWaypointsFromRoute({ waypointIndices: [1, 2] }) // remove multiple stops in one call"],examplePrompts:["Remove the second stop from my route","Delete the Utrecht stop","Drop the last two intermediate stops"],relatedTools:["addWaypointsToRoute","replaceWaypointInRoute","setRoute","updateRoutesDisplay"],dependsOn:["setRoute"]},replaceWaypointInRoute:{description:"Replace a single waypoint in the current route in place — origin, destination, or any intermediate stop by 0-based index. Always recalculates. Other waypoints are preserved. Requires an existing route. Use addWaypointsToRoute to extend the route, removeWaypointsFromRoute to drop a waypoint, or setRoute to fully replace all waypoints.",classificationPrompt:"Overwrite a single waypoint of the current route in place — by 'origin', 'destination', or numeric index — and recalculate. Other waypoints are kept.",inputSchema:Po,outputSchema:Mo,execute:async(e,t)=>{const{waypointIndex:r,location:s,showOnMap:o,hidePreviousEntries:n}=e;try{const e=t.routing.currentWaypoints;if(!e||e.length<2)return{error:"No existing waypoints available. Use calculate-route first to create a route with waypoints."};const l=(i=r,a=e.length,"origin"===i?0:"destination"===i?a-1:i<0||i>=a?`Invalid waypoint index ${i}. Valid range is 0 to ${a-1}.`:i);if("string"==typeof l)return{error:l};const c=await vs(s,t);if(!c)return{error:`Could not resolve location: ${Do(s)}`};const d=[...e];d[l]=c.place;const u=Ps(d);return u?_s(t,u,o,n):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(l){return{error:`Failed to replace waypoint in route: ${l instanceof Error?l.message:String(l)}`}}var i,a},tags:["route","waypoint"],examples:['replaceWaypointInRoute({ waypointIndex: "origin", location: { query: "Schiphol" } })','replaceWaypointInRoute({ waypointIndex: "destination", location: { query: "Heathrow" } })','replaceWaypointInRoute({ waypointIndex: 1, location: { query: "Utrecht" } }) // overwrite the first intermediate stop'],examplePrompts:["Change the origin to Schiphol","Make Heathrow my destination","Replace the second stop with Utrecht"],relatedTools:["addWaypointsToRoute","removeWaypointsFromRoute","setRoute","locatePlace"],dependsOn:["setRoute"]},getPOICategoryCodes:{description:'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.',classificationPrompt:'Resolve natural-language category names (e.g. "gym", "italian food") to POICategory codes for discoverPlaces.',inputSchema:so,outputSchema:ro,execute:async(e,t)=>{const{filters:r,language:s}=e;try{const e=await D({filters:r,language:s});return{count:e.length,codes:e}}catch(o){return{error:`Failed to fetch POI category codes: ${o instanceof Error?o.message:String(o)}`}}},tags:["discover","place"],examples:["getPOICategoryCodes()",'getPOICategoryCodes(filters: ["gym"])','getPOICategoryCodes(filters: ["italian restaurant"]) // multi-word phrase, spaces removed before matching','getPOICategoryCodes(filters: ["restaurant"], language: "fr-FR")'],examplePrompts:["What POI categories are available?","What's the code for gym?"],relatedTools:["discoverPlaces","toggleTilesPOIs"]},getTrafficIncidents:{description:'LOADER. Fetches traffic incidents (present or future) into a cached entry, renders them on the map, returns `{ count, entryId, entries }` (`entries` lists every cached entry so the agent can pick a follow-up target). No per-incident details, no aggregation — those belong to analyseData/focusIncidents. \n\nAREA via `where` (within mode only). Omit `where` to use the current map viewport ("the network / right now / this area / here"). For named areas use `where.queries`; for stored places use `where.placeIds`; for an isochrone use `where.range`; for a corridor along a route use `where.route`; for raw bounds use `where.boundingBox`; for custom shapes use `where.geometries`. Multi-region fields compose into one union bbox (≤10,000 km²). \n\nCALL ONCE PER AREA. Each call writes a new entry; re-calling burns steps. Pick the right follow-up instead: analyseData (counts, top-N, charts, clusters, trend via `previous`); focusIncidents (highlight a subset); startTrafficIncidentsMonitor (keep the entry fresh in the background). \n\n`silent: true` suppresses rendering (intermediate calcs only); `label` names the snapshot in the UI.',classificationPrompt:'AREA-BOUND incident loader — bbox / viewport / "incidents around here" / "accidents in <city>". DO NOT pick for route-bound queries ("incidents ALONG / ON the route", "delays on the drive from X to Y"): a calculated route already carries its incidents in `route.properties.sections.traffic[]` (category, magnitudeOfDelay, delayInSeconds, effectiveSpeedInKmh) — call `analyseData` with `routesEntryIDs` for those. When this tool IS the right pick: MANDATORY co-pick startTrafficIncidentsMonitor (they are a pair — data goes stale without it). 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.',inputSchema:wo,outputSchema:uo,execute:async(e,t)=>{const{where:r,categoryFilter:s,timeValidityFilter:o,label:n,silent:i=!1,fitBounds:a=!0}=e,l=r??{mode:"within",viewport:!0},c=await Io(l,t);if("error"in c)return c;const{bbox:d,routeLabel:u}=c,p={...s&&{categoryFilter:s},...o&&{timeValidityFilter:o}};try{const e=await O({...p,bbox:d});if(!e.features.length)return{count:0,entries:co(t)};const r={bbox:d,...p},s=await t.trafficIncidents.addIncidentsEntry(e.features,r,n??((e,t,r)=>{const s=[];let o;if(e.categoryFilter?.length&&s.push(`filtered: ${e.categoryFilter.join(",")}`),e.timeValidityFilter?.length&&s.push(`when: ${e.timeValidityFilter.join(",")}`),t&&!t.viewport&&(t.queries?.length?o=t.queries.map(e=>e.query).join(", "):r?o=`along ${r}`:t.range&&(o=t.range)),!o){const[t,r,s,n]=e.bbox;o=`bbox ${t.toFixed(2)},${r.toFixed(2)} → ${s.toFixed(2)},${n.toFixed(2)}`}return s.length?`${o} — ${s.join("; ")}`:o})(r,l,u),Date.now());return i||await t.trafficIncidents.showEntry(s,a),{count:e.features.length,entryId:s,entries:co(t)}}catch(h){return{error:`Failed to get traffic incidents: ${h instanceof Error?h.message:String(h)}`}}},tags:["traffic","location"],examples:["getTrafficIncidents({}) // omit `where` → current viewport",'getTrafficIncidents({ where: { mode: "within", boundingBox: [4.728, 52.278, 5.080, 52.479] } })','getTrafficIncidents({ where: { mode: "within", queries: [{ query: "Amsterdam" }] } }) // named area','getTrafficIncidents({ where: { mode: "within", queries: [{ query: "Amsterdam" }] }, categoryFilter: ["accident", "jam"] })','getTrafficIncidents({ where: { mode: "within", route: { widthMeters: 1000 } }, categoryFilter: ["roadworks"], timeValidityFilter: ["future"] }) // corridor around latest route','getTrafficIncidents({ where: { mode: "within", range: "ranges-0" } }) // inside a reachable area'],examplePrompts:["Fetch traffic incidents in Amsterdam","Any accidents in this area?","Show roadworks across the city"],relatedTools:["calculateBBox","getViewport","analyseData","focusIncidents","startTrafficIncidentsMonitor"]},focusIncidents:{description:"Highlight a subset of incidents on an existing entry. Drives the dim/highlight on the map and the FocusChip in the UI. Compose with `analyseData` to compute the ids first (e.g. top-N by delay, members of a cluster). Unknown ids are dropped silently and returned in `droppedIds`. Errors only when the entry is unknown or NONE of the ids resolve.",classificationPrompt:'Highlight a subset of incidents on an existing entry (drives map dim/highlight + FocusChip). Pick for "focus on…", "show only…", "highlight the…". Compose with analyseData to compute the ids.',inputSchema:Wo,outputSchema:Uo,execute:async(e,t)=>{const{incidentsEntryID:r,incidentIds:s,reason:o}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const n=t.trafficIncidents.setFocus(r,s,o);return 0===n.focusedCount?{error:`None of the ${s.length} requested ids exist on entry "${r}". The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.`}:{incidentsEntryID:r,focusedCount:n.focusedCount,droppedIds:n.droppedIds,...o&&{reason:o}}},tags:["traffic","incident","state","ui"],examples:["focusIncidents({ incidentsEntryID: 'incidents-0', incidentIds: ['TTI-...', 'TTI-...'], reason: 'top 3 by delay' })"],examplePrompts:["Focus on the 3 worst incidents by delay","Highlight the closures","Show only the incidents on the A4"],relatedTools:["analyseData","getTrafficIncidents"],dependsOn:["getTrafficIncidents"]},startTrafficIncidentsMonitor:{description:"Start a background monitor for an existing incidents entry. Re-fetches the entry's bbox + filters every interval (default 60s) and replaces its data; any registered analyses re-run on each tick. The first tick fires immediately, so this duplicates the most recent getTrafficIncidents fetch — only call it when the user wants the entry to stay current. \n\nIdempotent on a running monitor: a second call is a no-op and returns alreadyRunning: true. A new intervalMs is NOT applied to a running monitor — stop first, then start again to change settings.",classificationPrompt:'Polls an existing incidents entry on a fixed interval (default 60s); registered analyses re-run on each tick. ALWAYS pick this whenever getTrafficIncidents is picked — they are a mandatory pair, no exceptions. Without it the loaded data is a frozen snapshot. Also pick on its own when the user says "live"/"watch"/"keep updated"/"refresh every N seconds" against an already-loaded entry.',inputSchema:oi,outputSchema:si,execute:async(e,t)=>{const{incidentsEntryID:r,intervalMs:s}=e,o=t.trafficIncidents.entries.find(e=>e.id===r);if(!o)return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const n=t.trafficIncidents.isMonitored(r),{bbox:i,categoryFilter:a,timeValidityFilter:l}=o.params,c={...a&&{categoryFilter:a},...l&&{timeValidityFilter:l}};return t.trafficIncidents.startMonitoring(r,{bbox:i,capturedAt:o.timestamp,label:o.label},{fetchIncidents:async e=>(await O({...c,bbox:e})).features},s?{intervalMs:s}:void 0),{incidentsEntryID:r,alreadyRunning:n}},tags:["traffic","incident","state"],examples:["startTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0' })","startTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', intervalMs: 30000 })"],examplePrompts:["Keep this updated","Refresh the incidents every 30 seconds","Watch this area for new accidents"],relatedTools:["getTrafficIncidents","stopTrafficIncidentsMonitor","analyseData"],dependsOn:["getTrafficIncidents"]},stopTrafficIncidentsMonitor:{description:"Stop the background monitor for an incidents entry. The entry itself stays in state with its last data — use removeEntry if you also want to drop the entry. No-op when no monitor is running for the entry.",classificationPrompt:'Stop polling an incidents entry; the entry stays in state with its last data. Pick for "stop refreshing"/"pause updates" or when ending a watch.',inputSchema:ii,outputSchema:ni,execute:async(e,t)=>{const{incidentsEntryID:r}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}".`};const s=t.trafficIncidents.isMonitored(r);return t.trafficIncidents.stopMonitoring(r),{incidentsEntryID:r,wasRunning:s}},tags:["traffic","incident","state"],examples:["stopTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0' })"],examplePrompts:["Stop refreshing","Pause updates for this area"],relatedTools:["startTrafficIncidentsMonitor","getTrafficIncidents"],dependsOn:["startTrafficIncidentsMonitor"]},getTrafficAreaAnalytics:{description:"Fetch historical traffic analytics for an area (≤31 days) via location (named city/region) or bbox. Fetch ALL metrics in ONE call (visualization needs them together); use city-scale areas (~10 km+) for meaningful data. Returns period averages — `showOnMap` visualizes immediately; queryTrafficAnalytics for breakdowns.",classificationPrompt:"Fetch historical traffic analytics (speed, congestion, travel time) for an area. queryTrafficAnalytics drills down without re-fetching.",inputSchema:io,outputSchema:no,execute:async(e,t)=>{const{location:r,bbox:s,showOnMap:o,startDate:n,endDate:i,days:a,metrics:l,functionalRoadClasses:c,hours:d}=e;if(!r&&!s)return{error:"Provide a location or bbox to define the analytics area."};const u=process.env.MOVE_PORTAL_KEY;if(!u)return{error:"MOVE_PORTAL_KEY environment variable is not set."};try{const e=await(async(e,t,r)=>{if(e){const t=await vs(e,r);if(!t)return{error:"Could not resolve the provided location."};if(Array.isArray(t.place))return{error:"A bare position cannot define an analytics area. Provide a named location query instead."};const s=(await P({geometries:[t.place]}))?.features?.[0]?.geometry;return s||{error:`No boundary polygon found for "${t.name}". Try a bbox instead.`}}return t?(e=>{const[t,r,s,o]=e;return{type:"Polygon",coordinates:[[[t,r],[s,r],[s,o],[t,o],[t,r]]]}})(t):{error:"No area provided."}})(r,s,t);if("error"in e)return e;const p=((e,t,r)=>e&&e.length>0?{days:e}:t?r?{startDate:t,endDate:r}:{startDate:t}:{})(a,n,i),h=await k({apiKey:u,...p,metrics:l,functionalRoadClasses:c??"all",hours:d??"all",geometry:e}),m=h.features[0]?.properties?.name,y=m?`${m} (${p.startDate??"…"} → ${p.endDate??"…"})`:`analytics (${p.startDate??"…"} → ${p.endDate??"…"})`,f=await t.trafficAreaAnalytics.addEntry(h,y,{...p,metrics:l,functionalRoadClasses:c??"all",hours:d??"all"});return o&&await(async(e,t)=>{await e.trafficAreaAnalytics.showEntry(t);const r=e.trafficAreaAnalytics.findById(t);if(!r)return;const s=await e.trafficAreaAnalytics.getEntryModule(t),o=r.data.properties?.metrics?.[0]??"congestionLevel";s.setMetric(o);const n=F(r.data.features);n&&e.baseMap.mapLibreMap.fitBounds(n,{padding:50,pitch:45})})(t,f),((e,t,r,s)=>{const o=t.features[0]?.properties;if(!o)return{entryId:e,dateRange:{start:"",end:""},baseData:{},metrics:[],tileCount:0,availableGranularities:[]};const n=r??lo(t.properties?.startDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-3),ao(e)})(),i=s??lo(t.properties?.endDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-2),ao(e)})(),a=[];return o.timedData?.yearly?.length&&a.push("yearly"),o.timedData?.monthly?.length&&a.push("monthly"),o.timedData?.weekly?.length&&a.push("weekly"),o.timedData?.daily?.length&&a.push("daily"),o.timedData?.hourly?.length&&a.push("hourly"),o.timedData?.average?.length&&a.push("average"),{entryId:e,...o.name&&{name:o.name},...o.timezone&&{timezone:o.timezone},dateRange:{start:n,end:i},baseData:o.baseData,metrics:t.properties?.metrics??[],tileCount:o.tiledData?.tiles?.length??0,availableGranularities:a}})(f,h,p.startDate,p.endDate)}catch(p){return{error:`Failed to get traffic area analytics: ${p instanceof Error?p.message:String(p)}`}}},tags:["traffic","location"],examples:["getTrafficAreaAnalytics({ bbox: [4.728, 52.278, 5.080, 52.479], metrics: ['speed', 'congestionLevel'], startDate: '2025-03-01', endDate: '2025-03-07' })","getTrafficAreaAnalytics({ bbox: [...], metrics: ['congestionLevel'], hours: [7,8,9,17,18] })"],examplePrompts:["What was the average congestion in Amsterdam last week?","Analyze traffic patterns during rush hour in this area","Show me historical speed data for the city center"],relatedTools:["queryTrafficAnalytics","updateTrafficAreaAnalyticsDisplay","calculateBBox","getViewport"],dependsOn:["locatePlace"]},queryTrafficAnalytics:{description:'Query cached traffic analytics (from getTrafficAreaAnalytics) without re-fetching — filter by granularity, date/hour range, day, metric. Returns filtered entries plus the current map visualization state (metric, mode). Use for breakdowns and "what is shown" follow-ups.',classificationPrompt:"Query cached traffic analytics data or check what metric/mode is currently displayed on the map.",inputSchema:dn,outputSchema:un,execute:async(e,t)=>{const{entryId:r,granularity:s="daily",metric:o,startDate:n,endDate:i,hourStart:a,hourEnd:l,dayOfWeek:c}=e,d=r?t.trafficAreaAnalytics.findById(r):t.trafficAreaAnalytics.latestEntry;if(!d)return{error:r?`No traffic-area-analytics entry with id "${r}". Use recallState to list available ids.`:"No analytics entries available. Call getTrafficAreaAnalytics first."};const u=d.data,p=u.features[0]?.properties;if(!p)return{error:"Analytics result has no region data."};const h=p.timedData,m=h?.[s];if(!m||!Array.isArray(m)||0===m.length)return{error:`No '${s}' data available. Available: ${Object.keys(h??{}).join(", ")}`};const y=lo(u.properties?.startDate);let f=m.map((e,t)=>{const r={};if("daily"===s||"hourly"===s){const o=((e,t,r,s)=>{const o=lo(e);if(o)return o;if(s&&t>=0){const e=new Date(s);if(!Number.isNaN(e.getTime())){const s="hourly"===r?Math.floor(t/24):t;return e.setDate(e.getDate()+s),ao(e)}}})(e.date,t,s,y);o&&(r.date=o)}if(void 0!==e.hour&&(r.hour=e.hour),void 0!==e.year&&(r.year=e.year),void 0!==e.month&&(r.month=e.month),void 0!==e.week&&(r.week=e.week),void 0!==e.day&&(r.day=e.day),o){const t=e[o];void 0!==t&&(r[o]=t)}else Object.assign(r,(e=>({...void 0!==e.speed&&{speed:e.speed},...void 0!==e.freeFlowSpeed&&{freeFlowSpeed:e.freeFlowSpeed},...void 0!==e.congestionLevel&&{congestionLevel:e.congestionLevel},...void 0!==e.travelTime&&{travelTime:e.travelTime},...void 0!==e.networkLength&&{networkLength:e.networkLength}}))(e));return r});n&&(f=f.filter(e=>!e.date||e.date>=n)),i&&(f=f.filter(e=>!e.date||e.date<=i)),void 0!==a&&(f=f.filter(e=>void 0===e.hour||e.hour>=a)),void 0!==l&&(f=f.filter(e=>void 0===e.hour||e.hour<=l)),c&&c.length>0&&(f=f.filter(e=>void 0===e.day||c.includes(e.day)));const g=d._config,b=g?{activeMetric:g.activeMetric,displayMode:g.displayMode}:void 0;return{granularity:s,count:f.length,entries:f,...b&&{currentVisualization:b}}},tags:["traffic","location"],examples:["queryTrafficAnalytics({ granularity: 'daily' })","queryTrafficAnalytics({ granularity: 'hourly', hourStart: 7, hourEnd: 9, metric: 'congestionLevel' })","queryTrafficAnalytics({ granularity: 'average', dayOfWeek: [6, 7] })"],examplePrompts:["Show me the daily breakdown","What was rush hour congestion?","Which day had the worst traffic?","What metric am I looking at?","What is currently shown on the map?"],relatedTools:["getTrafficAreaAnalytics","updateTrafficAreaAnalyticsDisplay"],dependsOn:["getTrafficAreaAnalytics"]},updatePlacesDisplay:{description:'Show, hide, and swap which places entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by category label (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show these"). Markers (`markerType`) and boundary polygons (`geometry`) are independent display channels — set either or both for the targeted entries. Omit `add`/`addMatching` while still setting `markerType` or `geometry` to target the most recently stored entry. Does not delete from history.',classificationPrompt:"Show, hide, and swap places entries on the map — add some (as pins and/or boundary polygons), remove others, swap atomically, or render boundaries.",inputSchema:Ci,outputSchema:Ai,execute:async(e,t)=>{const{clear:r,markerType:s,geometry:o,fitBounds:n}=e;try{const i=((e,t)=>{const{add:r,remove:s,addMatching:o,removeMatching:n,markerType:i,geometry:a}=t;let l=[...r??[],...ki(e,o??[])];const c=[...s??[],...ki(e,n??[])];if(void 0===r&&void 0===o&&(void 0!==i||void 0!==a)){const t=e.places.entries.at(-1)?.id;if(!t)return{error:"No places available to display. Resolve a place or run a search first (e.g. discoverPlaces)."};l=[t]}const d=new Set(e.places.entries.map(e=>e.id)),u=l.filter(e=>!d.has(e));return u.length>0?{error:`Unknown places entry ids: ${u.join(", ")}. Use recallPlaces to list available entries.`}:{add:l,remove:c}})(t,e);if("error"in i)return i;const{add:a,remove:l}=i,c=new Set(t.places.shownEntryIds);r&&(await t.places.clearShownEntries(),await t.places.clearShownGeometries()),l.length>0&&(await t.places.removeShownEntries(l),await t.places.clearShownGeometries(l));const d=void 0!==s||!o;let u;a.length>0&&d&&await(async(e,t,r,s)=>{const o=void 0!==r?t:t.filter(e=>!s.has(e));o.length>0&&await e.places.addShownEntries(o,r??"pin")})(t,a,s,c),o&&a.length>0&&(u=await(async(e,t,r)=>{const s=r.theme??"outline",o=r.mode??"replace",n=r.placeIds?new Set(r.placeIds):void 0,i=[];let a=0;for(const c of t){const t=e.places.entries.find(e=>e.id===c);if(!t)continue;const r=n?t.places.filter(e=>n.has(e.id)):t.places,d=[];for(const s of r)if(s.properties.dataSources?.geometry?.id)try{const t=await e.places.fetchPlaceGeometry(s.id);if(!t){i.push({placeId:s.id,reason:"Geometry Data service returned no feature for this place."});continue}d.push(t)}catch(l){i.push({placeId:s.id,reason:`Geometry fetch failed: ${l instanceof Error?l.message:String(l)}`})}else i.push({placeId:s.id,reason:"Place has no geometry data source (e.g. addresses and streets often lack one)."});d.length>0&&(await e.places.showPlaceGeometries(d,s,o,c),a+=d.length)}return{rendered:a,skipped:i}})(t,a,o));const p=t.places.shownEntryIds,h=a.filter(e=>!c.has(e)&&p.has(e)),m=l.filter(e=>c.has(e)&&!p.has(e)),y=n??a.length>0,f=t.places.getShownFeatures();if(y&&f.length>0){const e=F(f);e&&t.baseMap.mapLibreMap.fitBounds(e,{padding:50})}const g=t.places.entries.filter(e=>p.has(e.id)).map(e=>({id:e.id,label:e.label,featureCount:e.places.length,markerType:t.places.getShownMarkerType(e.id)??"pin"}));return{success:!0,totalShown:f.length,shown:g,actuallyAdded:h,actuallyRemoved:m,...u&&{geometriesRendered:u.rendered},...u?.skipped.length&&{geometriesSkipped:u.skipped}}}catch(i){return{error:`updatePlacesDisplay failed: ${i instanceof Error?i.message:String(i)}`}}},tags:["place","location","map style","boundary","outline"],examples:['updatePlacesDisplay({ add: ["places-2"] })','updatePlacesDisplay({ add: ["places-1", "places-3"], markerType: "base-map" })','updatePlacesDisplay({ remove: ["places-1"] })','updatePlacesDisplay({ removeMatching: ["cinema"] }) // hide every entry labelled "cinema"','updatePlacesDisplay({ addMatching: ["cafe"] })',"updatePlacesDisplay({ clear: true })",'updatePlacesDisplay({ clear: true, add: ["places-2"] }) // atomic swap: hide everything, show this','updatePlacesDisplay({ markerType: "pin" }) // re-pin the most recently stored entry',"updatePlacesDisplay({ geometry: {} }) // outline the most recently stored entry",'updatePlacesDisplay({ add: ["places-2"], geometry: { mode: "add" } }) // also outline on top of existing boundaries','updatePlacesDisplay({ add: ["places-2"], markerType: "pin", geometry: { theme: "outline" } }) // pin AND outline','updatePlacesDisplay({ add: ["places-2"], geometry: { placeIds: ["G55fc4abe-..."], theme: "outline" } }) // outline a subset'],examplePrompts:["Show the results on the map","Pin the search results","Hide the banks","Also show the cafes on the map","Additionally pin the pubs","Remove the banks from the map",'Drop every entry labelled "parking" from the map',"Toggle off the hotels without clearing the rest","Replace the pins with the new set","Show entry places-3 as base-map POIs","Show the boundary of Amsterdam on the map","Outline these neighbourhoods","Highlight the shape of this place","Pin the cities and outline them too","Also outline Utrecht while keeping Amsterdam drawn","Add the neighbourhood boundaries on top of the pins"],relatedTools:["discoverPlaces","locatePlace","reverseGeocode","recallPlaces","clearMap","flyTo"],dependsOn:["discoverPlaces","locatePlace","processData"]},updateRoutesDisplay:{description:'Show, hide, swap, and recolor which routes entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show this"). `selectedIndex` re-highlights an alternative variant on the newly-added entries; with no `add`/`addMatching` it implicitly targets the most recently stored entry. `mainColor` recolors the route line + waypoint icons (sticky across entry swaps — applied to current shows AND remembered); it does NOT auto-show a hidden entry, so set `add`/`addMatching` too if nothing is on the map yet. Does not delete from history. Does not call any service.',classificationPrompt:"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).",inputSchema:Li,outputSchema:Ri,execute:async(e,t)=>{const{clear:r,selectedIndex:s,fitBounds:o,mainColor:n}=e;try{const i=((e,t)=>{const{add:r,remove:s,addMatching:o,removeMatching:n,selectedIndex:i}=t;let a=[...r??[],...Fi(e,o??[])];const l=[...s??[],...Fi(e,n??[])];if(void 0===r&&void 0===o&&void 0!==i){const t=e.routing.entries.at(-1)?.id;if(!t)return{error:"No routes available to display. Calculate a route first via setRoute."};a=[t]}const c=new Set(e.routing.entries.map(e=>e.id)),d=a.filter(e=>!c.has(e));return d.length>0?{error:`Unknown routes entry ids: ${d.join(", ")}. Use recallRoutes to list available entries.`}:{add:a,remove:l}})(t,e);if("error"in i)return i;const{add:a,remove:l}=i;void 0!==n&&t.routing.setMainColor(n);const{beforeShown:c,afterShown:d}=await(async(e,t)=>{const r=new Set(e.routing.shownEntryIds);if(t.clear)for(const s of e.routing.shownEntryIds)await e.routing.hideEntry(s);for(const s of t.remove)await e.routing.hideEntry(s);for(const s of t.add)await e.routing.showEntry(s);return void 0!==t.selectedIndex&&await(async(e,t,r)=>{if(0!==r)for(const s of t){const t=e.routing.entries.find(e=>e.id===s);if(!t)continue;const o=await e.routing.getEntryRoutingModule(s);await o.showRoutes(t.data,{selectedIndex:r}),t.waypoints.length&&await o.showWaypoints(t.waypoints)}})(e,t.add,t.selectedIndex),{beforeShown:r,afterShown:e.routing.shownEntryIds}})(t,{add:a,remove:l,clear:!!r,selectedIndex:s}),u=a.filter(e=>!c.has(e)&&d.has(e)),p=l.filter(e=>c.has(e)&&!d.has(e));(o??a.length>0)&&((e,t)=>{const r=[];for(const o of e.routing.entries)t.has(o.id)&&r.push(...o.data.features);if(0===r.length)return;const s=F({type:"FeatureCollection",features:r});s&&e.baseMap.mapLibreMap.fitBounds(s,{padding:50})})(t,d);const h=t.routing.entries.filter(e=>d.has(e.id)).map(e=>({id:e.id,label:e.label,routeCount:e.data.features.length,selectedIndex:u.includes(e.id)?s??0:0})),m=t.routing.mainColor;return{success:!0,totalShown:d.size,shown:h,actuallyAdded:u,actuallyRemoved:p,...void 0!==m&&{mainColor:m}}}catch(i){return{error:`updateRoutesDisplay failed: ${i instanceof Error?i.message:String(i)}`}}},tags:["route","waypoint","map style"],examples:['updateRoutesDisplay({ add: ["routes-1"] })','updateRoutesDisplay({ add: ["routes-0", "routes-2"] })','updateRoutesDisplay({ remove: ["routes-1"] })','updateRoutesDisplay({ removeMatching: ["Brussels"] }) // hide every entry whose label mentions "Brussels"','updateRoutesDisplay({ addMatching: ["Edinburgh"] })',"updateRoutesDisplay({ clear: true })",'updateRoutesDisplay({ clear: true, add: ["routes-2"] }) // atomic swap: hide everything, show this',"updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route",'updateRoutesDisplay({ add: ["routes-0"], selectedIndex: 2, fitBounds: false })','updateRoutesDisplay({ mainColor: "coral" }) // recolor every currently-shown route (and future shows)','updateRoutesDisplay({ add: ["routes-1"], mainColor: "#FF0000" }) // show in red'],examplePrompts:["Draw the route on the map","Show the directions","Display the calculated route","Show the Edinburgh route from earlier","Also show the Brussels route","Hide the Lyon route","Switch to the second alternative","Highlight alternative 2","Replace what is shown with this route","Make the route line red","Change the route color to coral","Style the route in steelblue"],relatedTools:["setRoute","processData","recallRoutes","clearMap"],dependsOn:["setRoute"]},updateTrafficAreaAnalyticsDisplay:{description:"Show, hide, swap, and restyle HISTORICAL traffic-area-analytics entries on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap. Render knobs (`mode`, `metric`, `colorTheme`, `heightScale`, `scaleMode`, `filter`) apply to every entry being added AND every entry that stays shown after this call. When only render knobs are set with no add/remove/clear, implicitly targets the most recently added entry. Not real-time — use `toggleTilesTrafficFlow` for the live vector-tile flow overlay.",classificationPrompt:"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.",inputSchema:qi,outputSchema:Bi,execute:async(e,t)=>{try{const r=((e,t)=>{const{add:r,remove:s,addMatching:o,removeMatching:n,clear:i}=t;let a=[...r??[],...$i(e,o??[])];const l=[...s??[],...$i(e,n??[])],c=void 0===r&&void 0===o&&void 0===s&&void 0===n&&!0!==i,d=void 0!==t.mode||void 0!==t.metric||void 0!==t.colorTheme||void 0!==t.heightScale||void 0!==t.scaleMode||void 0!==t.filter;if(c&&d){const t=e.trafficAreaAnalytics.entries.at(-1)?.id;if(!t)return{error:"No traffic-area-analytics entries available. Call getTrafficAreaAnalytics first."};a=[t]}const u=new Set(e.trafficAreaAnalytics.entries.map(e=>e.id)),p=a.filter(e=>!u.has(e));return p.length>0?{error:`Unknown traffic-area-analytics entry ids: ${p.join(", ")}. Use recallState to list available ids.`}:{add:a,remove:l}})(t,e);if("error"in r)return r;const{add:s,remove:o}=r,n=new Set(t.trafficAreaAnalytics.shownEntryIds);if(e.clear)for(const e of t.trafficAreaAnalytics.shownEntryIds)await t.trafficAreaAnalytics.hideEntry(e);for(const e of o)await t.trafficAreaAnalytics.hideEntry(e);for(const e of s)await t.trafficAreaAnalytics.showEntry(e);const i=t.trafficAreaAnalytics.shownEntryIds,a=s.filter(e=>!n.has(e)&&i.has(e)),l=o.filter(e=>n.has(e)&&!i.has(e)),c=new Set([...a,...i]);for(const u of c)await Gi(t,u,e);if((e.fitBounds??a.length>0)&&i.size>0){const e=F({type:"FeatureCollection",features:t.trafficAreaAnalytics.entries.filter(e=>i.has(e.id)).flatMap(e=>e.data.features)});e&&t.baseMap.mapLibreMap.fitBounds(e,{padding:50,pitch:45})}const d=t.trafficAreaAnalytics.entries.filter(e=>i.has(e.id)).map(Ui);return{success:!0,totalShown:i.size,shown:d,actuallyAdded:a,actuallyRemoved:l}}catch(r){return{error:`Failed to update traffic-area-analytics display: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","map style"],examples:['updateTrafficAreaAnalyticsDisplay({ add: ["tta-0"] })',"updateTrafficAreaAnalyticsDisplay({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' }) // re-style the latest (implicit)","updateTrafficAreaAnalyticsDisplay({ filter: { min: 50 } })","updateTrafficAreaAnalyticsDisplay({ scaleMode: 'currentRange', heightScale: 200 })","updateTrafficAreaAnalyticsDisplay({ clear: true })",'updateTrafficAreaAnalyticsDisplay({ clear: true, add: ["tta-1"] }) // atomic swap','updateTrafficAreaAnalyticsDisplay({ remove: ["tta-0"] })','updateTrafficAreaAnalyticsDisplay({ addMatching: ["Amsterdam"] })'],examplePrompts:["Show the analytics on the map","Visualize the congestion","Only show areas with congestion above 50%","Normalize height to the current data range","Switch to heatmap view","Also show the Amsterdam analytics","Hide the Rotterdam analytics","Replace what is on the map with this analytics"],relatedTools:["getTrafficAreaAnalytics","queryTrafficAnalytics","toggleTilesTrafficFlow","clearMap","analyseData","processData"],dependsOn:["getTrafficAreaAnalytics"]},updateWaypointsDisplay:{description:"Show or hide the staged origin/stop/destination markers held in `state.routing.planningSlots` (the slots being built up for the next `setRoute` — NOT the waypoints of a calculated route entry). No route line is drawn; for that use `updateRoutesDisplay`. Requires at least one routes entry to host the module.",classificationPrompt:"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.",inputSchema:Vi,outputSchema:Wi,execute:async(e,t)=>{const r=e.visible??!0;try{const e=[...t.routing.shownEntryIds][0],s=t.routing.entries.at(-1),o=e??s?.id;if(!o)return{error:"No route history yet — calculate a route via setRoute first."};const n=await t.routing.getEntryRoutingModule(o);if(!r)return await n.clearWaypoints(),{success:!0,visible:!1,count:0};const i=t.routing.planningSlots;return await n.showWaypoints(i),{success:!0,visible:!0,count:i.length}}catch(s){return{error:`Failed to update waypoints display: ${s instanceof Error?s.message:String(s)}`}}},tags:["waypoint","route"],examples:["updateWaypointsDisplay()","updateWaypointsDisplay({ visible: true })","updateWaypointsDisplay({ visible: false })"],examplePrompts:["Show the staged waypoints on the map","Mark my pending route stops","Hide the staged waypoints"],relatedTools:["setRoute","updateRoutesDisplay","getCurrentWaypoints","clearMap"],dependsOn:["setRoute","locatePlace"]},getShownTileIncidents:{description:'List the traffic incidents currently rendered by the TomTom traffic-tile overlay (the rasterised live tiles, not the GeoJSON `getTrafficIncidents` service). Reads `state.trafficTiles.getTrafficIncidentsModule().getShown()` — so it only reflects what the tile layer is showing in the current viewport. For full TrafficIncident objects (with start/end times, road numbers, lanes, …) call `getTrafficIncidents` instead; for "incidents along this route" read `route.properties.sections.traffic[]` via `analyseData`.',classificationPrompt:'Read traffic incidents currently rendered by the TomTom TRAFFIC-TILE OVERLAY (the rasterised live tiles toggled via `toggleTilesTrafficIncidents`). DO NOT confuse with `getTrafficIncidents` — that\'s the GeoJSON service that returns full structured incidents with start/end times, road numbers, lanes, etc. Pick this ONLY for "what\'s on the tile overlay right now"; for richer per-incident data use `getTrafficIncidents`, and for "incidents along the route" use `analyseData` over routes (`route.properties.sections.traffic[]`).',inputSchema:ui,outputSchema:di,execute:async(e,t)=>{try{const e=(await t.trafficTiles.getTrafficIncidentsModule()).getShown();return 0===e.trafficIncidents.length?{error:"No traffic incidents currently rendered by the traffic-tile overlay."}:{count:e.trafficIncidents.length,trafficIncidents:e.trafficIncidents.map(e=>({id:e.properties.id,category:e.properties.category,magnitudeOfDelay:e.properties.magnitudeOfDelay,description:e.properties.description,delayInSeconds:e.properties.delayInSeconds,roadCategory:e.properties.roadCategory,averageSpeedKmph:e.properties.averageSpeedKmph}))}}catch(r){return{error:`Failed to read traffic-tile overlay incidents: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","location","map style"],examples:["getShownTileIncidents()"],examplePrompts:["What incidents does the traffic tile overlay show right now?","What's rendered on the traffic incidents layer?"],relatedTools:["toggleTilesTrafficIncidents","getTrafficIncidents","flyTo","getViewport"],dependsOn:["toggleTilesTrafficIncidents"]},clearMap:{description:"Hide everything a chosen set of state slices is rendering on the map (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents). Map-only — slice `entries` history is untouched. Use to clean up before showing new results.",classificationPrompt:"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.",inputSchema:ci,outputSchema:li,execute:async(e,t)=>{const{layers:r}=e,s=r&&r.length>0?r:Zt;try{for(const e of s)await t[e].clearShown();return{success:!0,cleared:[...s]}}catch(o){return{error:`Failed to clear map: ${o instanceof Error?o.message:String(o)}`}}},tags:["place","route","map style"],examples:["clearMap()",'clearMap({ layers: ["places"] })','clearMap({ layers: ["places", "routing"] })','clearMap({ layers: ["trafficAreaAnalytics", "trafficIncidents"] })','clearMap({ layers: ["byod"] })'],examplePrompts:["Clear the map","Remove all markers","Start fresh","Hide the analytics overlay"],relatedTools:["updatePlacesDisplay","updateRoutesDisplay","updateTrafficAreaAnalyticsDisplay"]},flyTo:{description:"Move the map camera to a bounding box or a specific position. Use with boundingBox to frame a region or set of results. Use with position to center on a point.",classificationPrompt:"Move the camera to a bounding box or specific position; use after locating places or computing bounds.",inputSchema:Or,outputSchema:kr,execute:async(e,t)=>{const{where:r}=e;try{return"boundingBox"in r?t.baseMap.mapLibreMap.fitBounds(r.boundingBox,{padding:r.padding??50}):t.baseMap.mapLibreMap.flyTo({center:r.position,zoom:r.zoom??14}),{success:!0}}catch(s){return{error:`flyTo failed: ${s instanceof Error?s.message:String(s)}`}}},tags:["location","map style"],examples:["flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5] } })","flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5], padding: 100 } })","flyTo({ where: { position: [4.9, 52.4], zoom: 14 } })"],examplePrompts:["Go to Berlin","Center the map on Rome","Fly to these coordinates"],relatedTools:["getViewport","locatePlace","updatePlacesDisplay","updateRoutesDisplay","calculateBBox"]},getViewport:{description:'Get the current map viewport: center coordinates [longitude, latitude], zoom level, and bounding box [minLng, minLat, maxLng, maxLat]. Use for queries referencing the map view: "in this area", "what I can see", "near the map center". Does NOT return the user\'s physical location — use getCurrentLocation for "near me" queries. Reads map state. Does not call any service.',classificationPrompt:'Return the map center, zoom, and bounding box; use for area-relative queries like "in this area" or "near the map center".',inputSchema:Nr,outputSchema:Fr,execute:async(e,t)=>{try{const e=t.baseMap.mapLibreMap;return{center:e.getCenter().toArray(),zoom:e.getZoom(),bbox:t.baseMap.ttMap.getBBox(),pitch:e.getPitch(),bearing:e.getBearing()}}catch(r){return{error:`Failed to get viewport: ${r instanceof Error?r.message:String(r)}`}}},tags:["location","map style"],examples:["getViewport()"],examplePrompts:["Where is the map centered?","What's the current map area?","What are the current map bounds?"],relatedTools:["locatePlace","flyTo","discoverPlaces","reverseGeocode","getTrafficIncidents"]},recallGeometries:{description:"List or inspect every polygon source in session state — place footprints (cached on places entries via `withGeometries: true`), reachable-area isochrones, and custom-geometries entries produced by `processData`. ALWAYS call this before passing ids to `processData` or `analyseData` (`geometriesEntryIDs`); never guess. No args = index across all three slices; pass `id` to retrieve features and provenance for one entry. No service call.",classificationPrompt:"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.",inputSchema:bn,outputSchema:Sn,execute:async(e,t)=>{const{id:r}=e;return r?xn(r,t):{entries:Mn(t)}},tags:["geometry","boundary"],examples:["recallGeometries()",'recallGeometries({ id: { kind: "places", id: "ev-stations-oost" } })','recallGeometries({ id: { kind: "customGeometries", id: "bakery-candidates-gap-oost" } })'],examplePrompts:["What polygons are in state right now?","Which IDs can I pass to processData (geometriesEntryIDs)?","Recall the custom zone we just produced","What does the bakery-candidates entry contain?"],relatedTools:["processData","analyseData","recallPlaces","recallRanges"]},recallPlaces:e=>((e,t)=>({...t,description:"List / inspect places entries already in session state (from discoverPlaces / locatePlace / processData) — the same entries every `placesEntryIDs` parameter accepts. ALWAYS call this before referencing past places; never guess IDs. No args = index (id, label, timestamp, featureCount, analyses) + `entryMode`; pass `id` for full Place coordinates. No service call.",inputSchema:Pn,outputSchema:An(e),execute:Cn(e)}))(e.featureFlags??{},kn),recallByod:{description:"List or inspect BYOD (bring-your-own-data) GeoJSON layers in session state. These come from either programmatic seeding by the embedding app OR from `addByodLayer` calls. Call this BEFORE passing ids to `byodEntryIDs` on `analyseData` / `processData` / `updateByodDisplay` — never guess. No args → index (id, label, featureCount, geometryTypes, source, shown); pass `id` → full FeatureCollection.",classificationPrompt:"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.",inputSchema:fn,outputSchema:yn,execute:async(e,t)=>{if(!e.id)return{entries:t.byod.entries.map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,featureCount:e.data.features.length,geometryTypes:gn(e.data),source:e.source,shown:!!e._shown})),entryMode:t.byod.entryMode};const r=t.byod.findById(e.id);return r?{id:r.id,label:r.label,timestamp:r.timestamp,featureCount:r.data.features.length,geometryTypes:gn(r.data),source:r.source,shown:!!r._shown,features:r.data}:{error:`No BYOD entry with id "${e.id}". Call recallByod with no args to list available IDs.`}},tags:["recall","byod","state"],examples:["recallByod()",'recallByod({ id: "byod-0" })'],examplePrompts:["What customer-uploaded layers do we have?","List BYOD entries","Show details of the sales-territories BYOD layer"],relatedTools:["addByodLayer","updateByodDisplay","analyseData","processData"]},addByodLayer:{description:"Register a customer-owned GeoJSON layer as a new BYOD entry. Accepts either a URL (fetched once) or inline GeoJSON. The slice renders the entry with sensible defaults based on geometry type (Point → circle, Line → line, Polygon → fill). The new entry id is usable with `byodEntryIDs` on `analyseData` / `processData` and with `updateByodDisplay` to toggle visibility. For very large datasets, prefer programmatic seeding from the embedding app via `state.byod.addEntry(...)`.",classificationPrompt:"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).",inputSchema:Co,outputSchema:Ao,execute:async(e,t)=>{let r,s;if(e.url){const t=(e=>{let t;try{t=new URL(e)}catch{return{error:`Invalid URL: "${e}".`}}return Oo.has(t.protocol)?{url:t}:{error:`URL protocol "${t.protocol}" is not allowed. Only http: and https: are supported.`}})(e.url);if("error"in t)return t;const o=await Ro(t.url);if("error"in o)return o;r=o.value,s={kind:"url",url:e.url}}else r=e.data,s={kind:"inline"};if(!(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)})(r))return{error:'Payload is not a GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`).'};const o=await t.byod.addEntry(r,e.label,{source:s,...e.entryId&&{explicitId:e.entryId}});let n=!1;if(e.show)try{await t.byod.showEntry(o),n=!0}catch(i){return{error:`addByodLayer added entry "${o}" but rendering failed: ${i instanceof Error?i.message:String(i)}`}}return{byodEntryId:o,label:e.label,featureCount:r.features.length,geometryTypes:Lo(r),source:s,shown:n}},tags:["byod","state","import"],examples:['addByodLayer({ label: "Sales territories", url: "https://example.com/territories.geojson", show: true })','addByodLayer({ label: "Inline pins", data: { type: "FeatureCollection", features: [/* … */] } })'],examplePrompts:["Load my territories layer from https://...","Import this GeoJSON as a BYOD layer","Add a customer-pins layer from this URL"],relatedTools:["recallByod","updateByodDisplay","analyseData","processData"]},updateByodDisplay:{description:'Toggle visibility (or drop) for one or more BYOD entries. Use after `addByodLayer` or after seeing entries via `recallByod`. Three actions: `show` renders listed entries, `hide` removes them from the map but keeps them in state, `remove` hides AND drops them from history. Pair `action: "hide"` with `clearAll: true` to hide every shown BYOD entry in one call. Pair `action: "show"` with `hideOthers: true` to swap to a single set of entries (replaces what\'s already on the map).',classificationPrompt:"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.",inputSchema:Di,outputSchema:_i,execute:async(e,t)=>{const r=t.byod,{action:s,clearAll:o,hideOthers:n}=e,i=e.entryIds??[],a=((e,t)=>{for(const r of t)if(!e.findById(r))return{error:`No BYOD entry with id "${r}". Call recallByod to list available IDs.`}})(r,i);if(a)return a;let l;return l="show"===s?await(async(e,t,r)=>{const s=[];if(r){const r=new Set(t);for(const t of e.shownEntryIds)r.has(t)||(await e.hideEntry(t),s.push(t))}for(const o of t)await e.showEntry(o),s.push(o);return s})(r,i,!!n):"hide"===s?await(async(e,t,r)=>{const s=[],o=r?e.shownEntryIds:t;for(const n of o)await e.hideEntry(n),s.push(n);return s})(r,i,!!o):await(async(e,t)=>{const r=[];for(const s of t)await e.removeEntry(s),r.push(s);return r})(r,i),{action:s,affectedIds:l,shown:[...r.shownEntryIds]}},tags:["byod","display","visibility"],examples:['updateByodDisplay({ entryIds: ["byod-0"], action: "show" })','updateByodDisplay({ entryIds: ["byod-0", "byod-1"], action: "show", hideOthers: true })','updateByodDisplay({ entryIds: ["byod-0"], action: "hide" })','updateByodDisplay({ action: "hide", clearAll: true })','updateByodDisplay({ entryIds: ["byod-0"], action: "remove" })'],examplePrompts:["Hide the customer pins layer","Show only the territories BYOD layer","Hide all BYOD layers","Drop the imported file from the session"],relatedTools:["recallByod","addByodLayer"],dependsOn:["addByodLayer"]},recallRoutes:{description:"List calculated routes from session history, or retrieve one by `id` (route details + params). IDs are stable and accepted by updateRoutesDisplay. Never guess IDs — list first. No service call.",classificationPrompt:"Return previously calculated routes from this session.",inputSchema:qn,outputSchema:Wn,execute:async(e,t)=>{const{id:r}=e,s=t.routing.entryMode,o=t.routing.shownEntryIds;if(!r)return{entries:[...t.routing.entries].reverse().map(({id:e,label:t,timestamp:r})=>({id:e,label:t,timestamp:r,shown:o.has(e)})),entryMode:s};const n=t.routing.entries.find(e=>e.id===r);if(!n)return{error:`No entry found with id "${r}"`};const i=ps(n.data),a=n.waypoints.map(e=>ms(e)).filter(e=>null!==e).map(e=>({position:e.position??[0,0]}));return{id:n.id,label:n.label,timestamp:n.timestamp,routes:i,waypoints:a,params:n.params,entryMode:s}},tags:["route"],examples:["recallRoutes()",'recallRoutes({ id: "routes-0" })'],examplePrompts:["What routes have I calculated?","Show the first route we planned","What was the route to Schiphol?"],relatedTools:["setRoute","updateRoutesDisplay","getCurrentWaypoints"],dependsOn:["setRoute"]},getCurrentWaypoints:{description:"Read STAGED waypoint slots queued for the NEXT setRoute call (origin / stops / destination not yet committed to a route). Distinct from `recallRoutes({ id })`, which returns the waypoints of an existing calculated route entry. `slotIndex` for one slot; otherwise paged (default 5), empty slots omitted unless `includeEmptySlots: true`. No service call.",classificationPrompt:"STAGED waypoint slots only — the origin/stops/destination being built up for the NEXT `setRoute` call (read off `state.routing.planningSlots`, NOT off any calculated route entry). For waypoints of an EXISTING calculated route, use `recallRoutes({ id })` — its detail mode returns the entry's own waypoints.",inputSchema:Vo,outputSchema:zo,execute:async(e,t)=>{const{slotIndex:r,includeEmptySlots:s=!1,offset:o=0,limit:n=5}=e,i=t.routing.planningSlots.map((e,t)=>{const r=ms(e);return r?{...r,slotIndex:t}:{slotIndex:t,isFilled:!1}}),a=s?i:i.filter(e=>e.isFilled),l=void 0!==r,c=l?a.filter(e=>e.slotIndex===r):a.slice(o,o+n),d=o+c.length;return{totalSlots:i.length,filledSlots:i.filter(e=>e.isFilled).length,matchedCount:l?c.length:a.length,returnedCount:c.length,offset:l?0:o,hasMore:!l&&d<a.length,...!l&&d<a.length&&{nextOffset:d},waypoints:c}},tags:["waypoint","route"],examples:["getCurrentWaypoints()","getCurrentWaypoints({ slotIndex: 1 })","getCurrentWaypoints({ includeEmptySlots: true })","getCurrentWaypoints({ offset: 2, limit: 4 })"],examplePrompts:["What's currently staged for the next route?","What's my pending destination before I hit go?","Show the stops I have queued up"],relatedTools:["locatePlace","setRoute","updateWaypointsDisplay","recallRoutes"]},getStandardMapStyles:{description:"Get the list of available standard map style IDs. If you need more fine styling control, refer to getMapStyleLayers.",classificationPrompt:"List available standard map style IDs for use with setMapStandardStyle.",inputSchema:hi,outputSchema:pi,execute:async(e,t)=>{try{return{styles:g}}catch(r){return{error:`Failed to get standard map styles: ${r instanceof Error?r.message:String(r)}`}}},tags:["map style"],examples:["getStandardMapStyles()"],examplePrompts:["What map styles are available?","List the map themes"],relatedTools:["setMapStandardStyle"]},setMapStandardStyle:{description:"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.",classificationPrompt:"Switch the map to a standard style preset (light, dark, satellite, driving, etc.).",inputSchema:gi,outputSchema:fi,execute:async(e,t)=>{const{style:r}=e;try{return t.baseMap.ttMap.setStyle(r),{success:!0,style:r}}catch(s){return{error:`Failed to set map style: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setMapStandardStyle("standardDark")','setMapStandardStyle("satellite")'],examplePrompts:["Switch to dark mode","Use the satellite view","Change to the light theme"],relatedTools:["getStandardMapStyles","setLanguage","toggleTilesBaseMapLayerGroups","getMapStyleLayers"]},setLanguage:{description:"Set the language for map labels and all subsequent service API responses (search, geocoding, routing instructions).",classificationPrompt:"Change the language for map labels and all subsequent service API responses.",inputSchema:yi,outputSchema:mi,execute:async(e,t)=>{const{language:r}=e;try{return R.instance.put({language:r}),t.baseMap.ttMap.setLanguage(r),{success:!0,language:r}}catch(s){return{error:`Failed to set language: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setLanguage("fr-FR")','setLanguage("de-DE")'],examplePrompts:["Show map labels in French","Switch the map to German","Use Spanish for place names"],relatedTools:["setMapStandardStyle"]},toggleTilesBaseMapLayerGroups:{description:"Show / hide groups of vector-tile base-map style layers (water, buildings3D, roadLines, placeLabels, capitalLabels, …). Operates on the underlying MapLibre style — orthogonal to anything rendered through PlacesModule / RoutingModule / CustomGeoJSONModule (those layers are unaffected).",classificationPrompt:"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.",inputSchema:wi,outputSchema:bi,execute:async(e,t)=>{const{visible:r,layerGroups:s}=e;try{return(await t.baseMap.getBaseMapModule()).setVisible(r,{layerGroups:{mode:"include",names:s}}),{success:!0,visible:r,layerGroups:s}}catch(o){return{error:`Failed to toggle tile base-map layer groups: ${o instanceof Error?o.message:String(o)}`}}},tags:["map style"],examples:['toggleTilesBaseMapLayerGroups({ visible: false, layerGroups: ["buildings3D"] })','toggleTilesBaseMapLayerGroups({ visible: true, layerGroups: ["roadLabels"] })'],examplePrompts:["Hide 3D buildings","Show road labels","Toggle the building layer"],relatedTools:["toggleTilesPOIs","getMapStyleLayers","setMapStandardStyle"]},toggleTilesPOIs:{description:"Show/hide the built-in vector-tile POI icons baked into the base map style (restaurants, hotels, gas stations, …). NOT the same as places shown via `discoverPlaces` / `updatePlacesDisplay` — those are session-state pins on the PlacesModule, untouched by this tool. `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts which categories show; `reset: true` restores defaults.",classificationPrompt:"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).",inputSchema:Ii,outputSchema:vi,execute:async(e,t)=>{try{const r=await t.mapPOIs.getPOIsModule();if(e.reset)return r.resetConfig(),{success:!0,reset:!0};const{allMapPOIsVisible:s,filterCategories:o}=e;return void 0!==s&&r.setVisible(s),o&&r.filterCategories({show:o.show,values:o.values}),{success:!0,allMapPOIsVisible:s,filterCategories:o}}catch(r){return{error:`Failed to toggle tile POIs: ${r instanceof Error?r.message:String(r)}`}}},tags:["place","map style"],examples:["toggleTilesPOIs({ allMapPOIsVisible: false })",'toggleTilesPOIs({ allMapPOIsVisible: true, filterCategories: { show: "only", values: ["RESTAURANT", "CAFE"] } })','toggleTilesPOIs({ filterCategories: { show: "all_except", values: ["PARKING_GARAGE"] } })',"toggleTilesPOIs({ reset: true })"],examplePrompts:["Hide all map POI icons","Show only restaurant icons on the map style","Turn off parking icons in the base map"],relatedTools:["toggleTilesBaseMapLayerGroups","discoverPlaces","setMapStandardStyle"]},toggleTilesTrafficFlow:{description:"Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical analytics) or `route.properties.sections.traffic[]` (per-route incidents). Just a base-map style layer on/off.",classificationPrompt:"Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical) or route-section traffic (read via `analyseData`).",inputSchema:Si,outputSchema:Ei,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficFlowModule()).setVisible(r),{success:!0,trafficFlowVisible:r}}catch(s){return{error:`Failed to toggle tile traffic flow: ${s instanceof Error?s.message:String(s)}`}}},tags:["traffic","map style"],examples:["toggleTilesTrafficFlow({ visible: true })","toggleTilesTrafficFlow({ visible: false })"],examplePrompts:["Show traffic flow","Turn off traffic colors","Enable the congestion overlay"],relatedTools:["toggleTilesTrafficIncidents","setRoute","getTrafficIncidents"]},toggleTilesTrafficIncidents:{description:"Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the same as `getTrafficIncidents` (GeoJSON service → structured entries) or `analyseData` (sandbox over fetched entries). Just a base-map style layer on/off. Use `getShownTileIncidents` to read what this overlay is currently rendering.",classificationPrompt:"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.",inputSchema:Mi,outputSchema:xi,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficIncidentsModule()).setVisible(r),{success:!0,trafficIncidentsVisible:r}}catch(s){return{error:`Failed to toggle tile traffic incidents: ${s instanceof Error?s.message:String(s)}`}}},tags:["traffic","map style"],examples:["toggleTilesTrafficIncidents({ visible: true })","toggleTilesTrafficIncidents({ visible: false })"],examplePrompts:["Show traffic incidents overlay","Hide accident icons","Enable incident markers on the map"],relatedTools:["toggleTilesTrafficFlow","getTrafficIncidents","getShownTileIncidents","setRoute"]},getMapStyleLayers:{description:"Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayoutProperty/setPaintProperty for custom styling.",classificationPrompt:"List MapLibre layer IDs with their paint/layout properties for custom styling.",inputSchema:Lr,outputSchema:Rr,execute:async(e,t)=>{const{layerIdQuery:r,include:s}=e;try{let e=t.baseMap.mapLibreMap.getStyle().layers??[];return r&&(e=e.filter(e=>e.id.toLowerCase().includes(r.toLowerCase())),0===e.length)?{error:`No layers found matching query: "${r}"`}:{layers:e.map(e=>({id:e.id,...s.includes("paint")&&{paint:e.paint},...s.includes("layout")&&{layout:e.layout}}))}}catch(o){return{error:`Failed to get style: ${o instanceof Error?o.message:String(o)}`}}},tags:["map style"],examples:["getMapStyleLayers()",'getMapStyleLayers("road")','getMapStyleLayers("water")'],examplePrompts:["What are the paint properties of the water layer?","Show me the road layer layout"],relatedTools:["setLayoutProperties","setPaintProperties"]},setLayoutProperties:{description:"Set MapLibre layout properties (visibility, text-field, icon-size) on named layers — use getMapStyleLayers for valid IDs. For colors/widths use setPaintProperties.",classificationPrompt:"Set MapLibre layout properties (visibility, text-field, icon-size) on named layers.",inputSchema:Br,outputSchema:qr,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:s})=>{try{return t.baseMap.mapLibreMap.setLayoutProperty(e,r,s),{layerId:e,propertyName:r,value:s,success:!0}}catch(o){return{layerId:e,propertyName:r,error:`Failed to set layout property: ${o instanceof Error?o.message:String(o)}`}}})}}catch(s){return{error:`Failed to set layout properties: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setLayoutProperties([{ layerId: "road-label", propertyName: "visibility", value: "none" }])','setLayoutProperties([{ layerId: "poi-label", propertyName: "text-field", value: "{name}" }, { layerId: "poi-label", propertyName: "icon-size", value: 1.2 }])'],examplePrompts:["Hide the road-label layer","Change the text size on building labels","Make the city labels bigger"],relatedTools:["getMapStyleLayers","setPaintProperties"],dependsOn:["getMapStyleLayers"]},setPaintProperties:{description:"Set MapLibre paint properties (colors, widths, opacity) on named layers — use getMapStyleLayers for valid IDs. For visibility/structure use setLayoutProperties; for route styling prefer `updateRoutesDisplay({ mainColor })`.",classificationPrompt:"Set MapLibre paint properties (colors, widths, opacity) on named layers.",inputSchema:Ur,outputSchema:Gr,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:s})=>{try{return t.baseMap.mapLibreMap.setPaintProperty(e,r,s),{layerId:e,propertyName:r,value:s,success:!0}}catch(o){return{layerId:e,propertyName:r,error:`Failed to set paint property: ${o instanceof Error?o.message:String(o)}`}}})}}catch(s){return{error:`Failed to set paint properties: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['setPaintProperties([{ layerId: "water", propertyName: "fill-color", value: "#0000ff" }])','setPaintProperties([{ layerId: "road", propertyName: "line-color", value: "#ff0000" }, { layerId: "road", propertyName: "line-width", value: 3 }])'],examplePrompts:["Make the water bright blue","Change the road color to red","Make buildings semi-transparent","Make the country borders pink"],relatedTools:["getMapStyleLayers","setLayoutProperties","updateRoutesDisplay"],dependsOn:["getMapStyleLayers"]},setPitchBearing:{description:"Tilt and/or rotate the map camera. Pitch 0 = top-down view, 85 = near-horizontal (3D effect). Bearing 0 = north up, 90 = east up. Provide at least one of pitch or bearing.",classificationPrompt:"Tilt (pitch) and/or rotate (bearing) the map camera for a 3D perspective.",inputSchema:Vr,outputSchema:Wr,execute:async(e,t)=>{const{pitch:r,bearing:s}=e;if(void 0===r&&void 0===s)return{error:"Provide at least one of pitch or bearing"};try{return t.baseMap.mapLibreMap.easeTo({...void 0!==r&&{pitch:r},...void 0!==s&&{bearing:s}}),{success:!0}}catch(o){return{error:`Failed to set pitch/bearing: ${o instanceof Error?o.message:String(o)}`}}},tags:["map style"],examples:["setPitchBearing({ pitch: 45 })","setPitchBearing({ bearing: 90 })","setPitchBearing({ pitch: 60, bearing: -45 })"],examplePrompts:["Tilt the map to 45 degrees","Rotate the map to face north","Set a 3D perspective view"],relatedTools:["getViewport","locatePlace","flyTo"]},zoomInOrOut:{description:"Zoom the map in or out by a given amount relative to the current zoom level. Positive delta zooms in (closer), negative delta zooms out (further away). Returns the resulting zoom level.",classificationPrompt:"Adjust the map zoom level by a delta; positive zooms in, negative zooms out.",inputSchema:Hr,outputSchema:zr,execute:async(e,t)=>{const{delta:r}=e;try{const e=t.baseMap.mapLibreMap,s=e.getZoom()+r;return e.zoomTo(s),{zoom:s}}catch(s){return{error:`Zoom failed: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:["zoomInOrOut({ delta: 2 })","zoomInOrOut({ delta: -1 })"],examplePrompts:["Zoom in","Zoom out 2 levels","Get a wider view"],relatedTools:["getViewport","flyTo"]},executeMaplibreCode:{description:"Run arbitrary MapLibre JS against the live `map` for anything no other tool covers — custom sources/layers, camera, style mutations, animations, event sequences. Full JS (loops, helpers, top-level `await`). Returns added/removed/updated sources & layers.",classificationPrompt:"Execute any MapLibre JS code against the live map for maximum flexibility — use when no other tool covers the required operation.",inputSchema:Dr,outputSchema:_r,execute:async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,s=Tr(e),o=Ar(e),n=Object.getPrototypeOf(async()=>{}).constructor,i=await new n("map",r)(e),a=Cr(s,Tr(e));return{success:!0,result:i??void 0,sources:a,layers:Cr(o,Ar(e))}}catch(s){return{error:jt("Code",s)}}},tags:["map style","maplibre","layers","sources","animation"],examples:["executeMaplibreCode({ code: \"map.addSource('pts', { type: 'geojson', data: fc }); map.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-color': '#f00' } })\" })",'executeMaplibreCode({ code: "map.easeTo({ center: [4.9, 52.37], zoom: 14, duration: 2000 })" })',"executeMaplibreCode({ code: \"for (let i = 0; i <= 20; i++) { map.setPaintProperty('fill', 'fill-opacity', i / 20); await new Promise(r => setTimeout(r, 50)); }\" })","executeMaplibreCode({ code: \"map.setFog({ color: 'white', 'horizon-blend': 0.1 })\" })"],examplePrompts:["Add a GeoJSON layer to the map","Animate the map camera","Fade in a layer gradually","Set map fog or atmosphere","Add a raster tile overlay"],relatedTools:["getMapStyleLayers","setLayoutProperties","setPaintProperties","flyTo"],dependsOn:["getMapStyleLayers"]},calculateBBox:{description:"Compute a `[minLng, minLat, maxLng, maxLat]` bbox from GeoJSON (longitude before latitude).",classificationPrompt:"Compute a [minLng, minLat, maxLng, maxLat] bounding box from GeoJSON features or tool results.",inputSchema:tr,execute:async(e,t)=>{const{geoJson:r}=e;try{const e=F(r);return e?{bbox:e,minLng:e[0],minLat:e[1],maxLng:e[2],maxLat:e[3]}:{error:"Could not calculate bounding box - input may be empty or invalid GeoJSON"}}catch(s){return{error:`Failed to calculate bbox: ${s instanceof Error?s.message:String(s)}`}}},tags:["utilities","location"],examples:["calculateBBox(place)","calculateBBox(routes)","calculateBBox([place1, place2])"],examplePrompts:["What's the bounding box of these places?","Calculate the extent of this route"],relatedTools:["flyTo","getViewport","updatePlacesDisplay","updateRoutesDisplay"]},formatDistance:{description:'Format a distance in meters into a human-readable string (e.g. "2.5 km", "1.5 mi"). Use after getSectionProgress, recallRoutes, or any tool that returns distances in meters.',classificationPrompt:'Format meters into a human-readable string (e.g. "2.5 km"); use after tools that return distances.',inputSchema:rr,execute:async(e,t)=>{const{meters:r,unitType:s}=e;try{return{formatted:j(r,s?{type:s}:void 0),input:{meters:r,unitType:s??"metric"}}}catch(o){return{error:`Failed to format distance: ${o instanceof Error?o.message:String(o)}`}}},tags:["utilities","route"],examples:["formatDistance(2500)",'formatDistance(5000, "imperial_us")'],examplePrompts:["Format 5000 meters as a distance","Convert meters to a readable string"],relatedTools:["formatDuration","setRoute"]},formatDuration:{description:'Format a duration in seconds into a human-readable time string (e.g. "1 h 30 min"). Use after getSectionProgress, recallRoutes, or any tool that returns durations in seconds.',classificationPrompt:'Format seconds into a human-readable string (e.g. "1 h 30 min"); use after tools that return durations.',inputSchema:sr,execute:async(e,t)=>{const{seconds:r}=e;try{const e=q(r);return void 0===e?{formatted:void 0,message:"< 1 min",input:{seconds:r}}:{formatted:e,input:{seconds:r}}}catch(s){return{error:`Failed to format duration: ${s instanceof Error?s.message:String(s)}`}}},tags:["utilities","route"],examples:["formatDuration(3600)","formatDuration(9000)"],examplePrompts:["Format 3600 seconds as a duration","Convert seconds to hours and minutes"],relatedTools:["formatDistance","setRoute"]},getCurrentLocation:{description:'Browser geolocation — the user\'s physical position [lng, lat] for "near me" / "where I am" queries (may prompt for permission). For map-based reference use getViewport.',classificationPrompt:'Get the user\'s physical GPS location from the browser; use for "near me" queries, not map center.',inputSchema:hr,outputSchema:mr,execute:async(e,t)=>{if(void 0===globalThis.window||void 0===globalThis.navigator)return{error:"Current location is only available in a browser environment"};if(!("geolocation"in globalThis.navigator))return{error:"Geolocation is not supported by this browser"};try{const e=await new Promise((e,t)=>{globalThis.navigator.geolocation.getCurrentPosition(e,t,{enableHighAccuracy:!0,timeout:15e3,maximumAge:0})});return{position:[e.coords.longitude,e.coords.latitude],accuracy:e.coords.accuracy,timestamp:e.timestamp}}catch(r){const e=(e=>{if("object"!=typeof e||null===e||!("code"in e))return;const t=e.code;return 1===t?"Location permission denied by the user":2===t?"Current location is unavailable":3===t?"Timed out while trying to get current location":void 0})(r);return e?{error:e}:{error:`Failed to get current location: ${r instanceof Error?r.message:String(r)}`}}},tags:["utilities","location"],examples:["getCurrentLocation()"],examplePrompts:["Where am I?","Get my GPS location","Use my current position"],relatedTools:["discoverPlaces","reverseGeocode","locatePlace"]},help:{description:vr,classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:wr,outputSchema:br,execute:async()=>{throw new Error("help tool requires setupTools() — it needs resolved metadata from all tools.")},tags:["utilities"],examples:['help({ mode: "summary" })','help({ mode: "detail", query: "traffic" })','help({ mode: "detail", tag: "route" })','help({ mode: "detail", query: "route", tag: "utilities" })'],examplePrompts:["What can you do?","Show me available tools","How do I search for places?"]},findReachableAreas:{description:"Compute reachable-area polygons (isochrone/isodistance) from one or more origins under shared time/distance budgets. Multiple origins → compared in one call (sequential to respect QPS); multiple budgets → nested ranges. Stored in state (recallRanges); `showOnMap: true` renders. Per-origin failures land under `skipped` instead of aborting the whole call.",classificationPrompt:"Calculate isochrone/isodistance polygons showing how far you can travel from one or more origins.",inputSchema:Ks,outputSchema:Qs,execute:async(e,t)=>{const{origins:r,budgets:s,theme:o,showOnMap:n,showOriginPin:i=!0,hidePreviousEntries:a}=e;try{const e=[...s].sort((e,t)=>t.value-e.value),l=[],c=[];for(const s of r){const r="query"in s?s.query:"position"in s?`${s.position.lat}, ${s.position.lng}`:"placeId"in s?s.placeId:"unnamed origin",o=await vs(s,t);if(!o){c.push({origin:r,reason:"origin not found"});continue}const n=await eo(e,o.position);n?l.push({origin:o.query?{query:o.query,position:o.position}:{position:o.position},budgets:e,polygon:n}):c.push({origin:o.name??r,reason:"no reachable area returned"})}if(0===l.length)return{status:"no_results"};const d=o??"outline",u=l.map(to),p=await t.ranges.addEntry({label:ls(e,u),ranges:l});return(n||i)&&await mt(t.ranges,[p],a),await(async(e,t,r,s,o,n)=>{if(o){const o=(e=>{const t=e.flatMap(e=>e.polygon?.features??[]);return 0===t.length?null:{type:"FeatureCollection",features:t}})(r);o&&(await(await e.ranges.getEntryGeometriesModule(t,s)).show(o),o.bbox&&e.baseMap.mapLibreMap.fitBounds(o.bbox,{padding:50}))}if(n){const s=await e.ranges.getEntryPlacesModule(t),o=r.map((e,r)=>({type:"Feature",id:`range-origin-${t}-${r}`,geometry:{type:"Point",coordinates:e.origin.position},properties:{type:"Point Address",address:{freeformAddress:to(e)}}}));await s.show(o)}})(t,p,l,d,n,i),(n||i)&&await t.ranges.markEntryShown(p),{rangesId:p,ranges:l.map(t=>({originName:to(t),budgets:e})),...c.length&&{skipped:c}}}catch(l){return{error:`Reachable area calculation failed: ${l instanceof Error?l.message:String(l)}`}}},tags:["reachable-range","isochrone","range","EV","coverage","multi-origin"],examples:['findReachableAreas({ origins: [{ query: "Amsterdam", queryAs: "place" }], budgets: [{ type: "timeMinutes", value: 30 }], showOnMap: true })','findReachableAreas({ origins: [{ query: "Amsterdam", queryAs: "place" }, { query: "Rotterdam", queryAs: "place" }], budgets: [{ type: "timeMinutes", value: 30 }], showOnMap: true })','findReachableAreas({ origins: [{ position: { lng: 4.9, lat: 52.3 } }], budgets: [{ type: "timeMinutes", value: 10 }, { type: "timeMinutes", value: 30 }], showOnMap: true, theme: "inverted" })','findReachableAreas({ origins: [{ query: "my location", queryAs: "place" }], budgets: [{ type: "distanceKM", value: 50 }], showOnMap: false })'],examplePrompts:["Show me where I can reach in 30 minutes from Amsterdam","Compare 30-minute reach from Amsterdam, Rotterdam and Utrecht","What area is within 50 km of Berlin?","Show 10, 20, 30 minute drive zones from here"],relatedTools:["recallRanges","locatePlace"]},recallRanges:{description:"List stored reachable-range results, or retrieve one by `id` (returns each origin and its budgets). Multi-origin entries surface every origin under `ranges`. Never guess IDs — list first. No service call.",classificationPrompt:"Retrieve stored reachable range results from session history.",inputSchema:On,outputSchema:jn,execute:async(e,t)=>{const{id:r}=e,s=t.ranges.entryMode;if(!r)return{entries:[...t.ranges.entries].reverse().map(({id:e,label:t,timestamp:r,ranges:s})=>({id:e,label:t,timestamp:r,originCount:s.length})),entryMode:s};const o=t.ranges.entries.find(e=>e.id===r);return o?{id:o.id,label:o.label,timestamp:o.timestamp,ranges:o.ranges.map(e=>({origin:{...e.origin,position:e.origin.position},budgets:e.budgets})),entryMode:s}:{error:`No entry found with id "${r}"`}},tags:["recall","reachable-range","state"],examples:["recallRanges()",'recallRanges({ id: "ranges-0" })'],examplePrompts:["What range did I calculate earlier?","Recall the isochrone from Amsterdam"],relatedTools:["findReachableAreas"]},recallState:{description:"Single-shot session snapshot: places / routes / ranges entries (id+label) + per-slice `entryMode` + active map style + POIs / traffic visualization configs. Read-only. Use as orientation before making decisions that depend on what is already loaded.",classificationPrompt:"Inventory the agent's session: list every places / routes / ranges entry by id+label, plus current map style and POIs/traffic visualization configs.",inputSchema:Yn,outputSchema:Hn,execute:async(e,t)=>{const r=e=>[...e].sort((e,t)=>t.timestamp-e.timestamp),s=r(t.places.entries).map(({id:e,label:t})=>({id:e,label:t})),o=r(t.routing.entries).map(({id:e,label:t})=>({id:e,label:t})),n=r(t.ranges.entries).map(({id:e,label:t})=>({id:e,label:t})),i="string"==typeof(a=t.baseMap.ttMap.getStyle())?a:a&&"object"==typeof a&&"id"in a?a.id:void 0;var a;const l=void 0===i?null:{id:i},c=t.mapPOIs.poisModule?.getConfig(),d=c?{..."boolean"==typeof c.visible&&{visible:c.visible},...c.filters?.categories?.show&&{filterMode:c.filters.categories.show},...c.filters?.categories?.values&&{categories:[...c.filters.categories.values]}}:null,u=t.trafficTiles.trafficFlowModule?.getConfig(),p=t.trafficTiles.trafficIncidentsModule?.getConfig(),h={flow:u?{..."boolean"==typeof u.visible&&{visible:u.visible}}:null,incidents:p?{..."boolean"==typeof p.visible&&{visible:p.visible}}:null};return{places:s,routes:o,ranges:n,entryModes:{places:t.places.entryMode,routes:t.routing.entryMode,ranges:t.ranges.entryMode},mapStyle:l,mapPOIs:d,traffic:h}},tags:["recall","state","overview"],examples:["recallState()"],examplePrompts:["What do you have so far?","List everything you remember from this session","Show me the current map / data overview"],relatedTools:["recallPlaces","recallRoutes","recallRanges","resetState"]},resetState:{description:'Wipe the session: drops every places/routes/ranges entry + their map renders, resets POIs + traffic configs to defaults, reverts the map style to "standardLight". Destructive — use only when the user explicitly asks for a clean slate ("start over", "reset", "clear everything").',classificationPrompt:"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.",inputSchema:Kn,outputSchema:Xn,execute:async(e,t)=>{try{await t.places.clearShownEntries();for(const e of t.places.entries)await(e._modules?.geometries?.clear());for(const e of t.routing.entries)e._module&&(await e._module.clearRoutes(),await e._module.clearWaypoints());for(const e of t.ranges.entries)await(e._modules?.geometries?.clear()),await(e._modules?.places?.clear());for(const e of t.trafficAreaAnalytics.entries)await(e._module?.clear());return t.mapPOIs.poisModule?.resetConfig(),t.trafficTiles.trafficFlowModule?.resetConfig(),t.trafficTiles.trafficIncidentsModule?.resetConfig(),t.places.reset(),t.routing.reset(),t.ranges.reset(),t.customGeometries.reset(),t.trafficAreaAnalytics.reset(),t.trafficIncidents.reset(),t.byod.reset(),t.baseMap.ttMap.setStyle("standardLight",{keepState:!1}),{success:!0,cleared:[...Jn]}}catch(r){return{error:`Failed to reset state: ${r instanceof Error?r.message:String(r)}`}}},tags:["state","reset","destructive"],examples:["resetState()"],examplePrompts:["Start over","Reset everything","Clear all my data and the map"],relatedTools:["recallState","clearMap"]},setEntryMode:{description:ri,classificationPrompt:"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.",inputSchema:ti,outputSchema:ei,execute:async(e,t)=>{const{slice:r,mode:s}=e;try{const e=t[r],o=e.entries.map(e=>e.id);await e.setEntryMode(s);const n=new Set(e.entries.map(e=>e.id));return{success:!0,slice:r,mode:s,droppedEntryIds:o.filter(e=>!n.has(e))}}catch(o){return{error:`Failed to set entry mode: ${o instanceof Error?o.message:String(o)}`}}},tags:["state","mode","display"],examples:['setEntryMode({ slice: "places", mode: "single" })','setEntryMode({ slice: "routing", mode: "multiple" })','setEntryMode({ slice: "trafficIncidents", mode: "single" })'],examplePrompts:["Only show one route at a time","Let me overlay multiple places searches","Switch ranges back to single mode","Only keep the latest traffic incidents snapshot"],relatedTools:["recallState","recallPlaces","recallRoutes","recallRanges"]}},Yi=Hi,Ji=Object.keys(Yi),Xi=()=>{const e={};for(const t of Ji){const r=Yi[t];e[t]=("function"==typeof r?r({}):r).examplePrompts??[]}return e},Ki=async(e,t)=>{((e,t)=>{for(const[r,s]of Object.entries(t)){const t=e[r];t&&(t.description=s.defaultDescription,t.inputSchema=s.defaultInputSchema)}})(e.tools,e.scopableTools);let r=null;if(e.classifier)try{r=await e.classifier({messages:t,toolsMetadata:e.toolsMetadata})}catch{r=null}return e.onClassify?.(r),((e,t,r)=>{if(r)for(const[s,o]of Object.entries(r)){const r=t[s];if(!r||null==o)continue;const n=e[s];if(!n)continue;const i=r.applyScope(o);i&&(n.description=i.description,n.inputSchema=i.inputSchema)}})(e.tools,e.scopableTools,r?.toolScopes),r},Zi=(e,r)=>{if(!r.model)throw new Error("MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.");const o=((e,t)=>{const r={places:new be(e),mapPOIs:new fe(e),routing:new ve(e),baseMap:new se(e),trafficTiles:new Ht(e),trafficAreaAnalytics:new De(e),trafficIncidents:new zt(e),ranges:new we(e),customGeometries:new ce(e),byod:new Pe(e)};return t&&Object.assign(r,t),r})(e,r.state);if(r.dataEntries)for(const[t,s]of Object.entries(r.dataEntries)){const e=t;s?.entryMode&&o[Jt[e]].setEntryMode(s.entryMode)}const n=e=>!1!==r.dataEntries?.[e]?.enabled,i=Object.keys(Jt).filter(e=>!n(e)),a=et.filter(e=>n(e)),l=new Set(i.flatMap(e=>Xt[e])),c=!1===r.includeDefaultTools?{}:Yi,{tools:d,toolsMetadata:u,scopableTools:p}=Mr(re(Object.fromEntries(Object.entries(c).filter(([e])=>!l.has(e))),r.tools),o,{outputSchemas:r.outputSchemas,featureFlags:r.featureFlags,enabledDataKinds:a}),h=!1===r.classifier?null:r.classifier??os({model:r.model}),m=er(r.systemPrompt,r.systemPromptSuffix);let y=null;const f=(()=>{let e=Promise.resolve();return async t=>{const r=e;let s=()=>{};e=new Promise(e=>{s=e});try{return await r,await t()}finally{s()}}})(),g={tools:d,scopableTools:p,toolsMetadata:u,classifier:h,onClassify:r.onClassify},b=new t({model:r.model,tools:d,instructions:m,stopWhen:s(r.maxSteps??10),prepareStep:e=>f(async()=>{0===e.stepNumber&&(y=await Ki(g,e.messages));const t=y?.activeToolNames,s=await(r.prepareStep?.(e)),o=r.stepProviderOptions?.({activeTools:t,stepNumber:e.stepNumber});return((e,t,r)=>r?{...r,activeTools:Q(e,r.activeTools),providerOptions:te(t,r.providerOptions)}:{...e?{activeTools:e}:{},...t?{providerOptions:t}:{}})(t,o,s)}),providerOptions:r.providerOptions});return Object.assign(b,{state:o,destroy:()=>(e=>{for(const t of Object.values(e))ee(t)&&t.reset()})(o)})};export{Qt as BASE_SYSTEM_PROMPT,Jt as DATA_ENTRY_KIND_TO_SLICE,Yi as DEFAULT_TOOLS,Kt as ENTRY_MODE_SLICE_NAMES,Xt as TOOLS_BY_DATA_ENTRY_KIND,Ji as TOOL_NAMES,er as buildSystemPrompt,es as classifyUserIntent,os as createDefaultClassifier,Zi as createMapAgent,ss as extractLastUserText,ye as formatStateDigestDiff,_e as geometriesIdSchema,Xi as getDefaultToolPrompts,pe as getStateDigest,re as resolveTools};
|
|
2
2
|
//# sourceMappingURL=index.es.js.map
|