@tomtom-org/maps-sdk-plugin-agent-toolkit 0.2.5 → 0.2.7

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/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{_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{_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{_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{_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{_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{_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{_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{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{_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{_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,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 k,avoidableTypes as D,bboxFromGeoJSON as O,calculateProgressAtRoutePoint as j,formatDistance as F,formatDuration as L,getCoordinateAtRouteProgress as N,getPosition as q,getProgressAtNearestRoutePoint as $,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=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})},le=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);return r._modules??={},r._modules.places?r._shownAs!==t&&r._modules.places.applyTheme(t):r._modules.places=await l.get(this._ttMap,{theme:t}),r._modules.places}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 ie(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.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)}},ce=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)}},de=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)}},ue=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}},pe=(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.")})),he="`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)`.",me=e=>Array.isArray(e)&&4===e.length&&e.every(e=>"number"==typeof e&&Number.isFinite(e)),ye=e=>{if(me(e))return!0;if(!e||"object"!=typeof e)return!1;const t=e;return!!me(t.bbox)&&(!!(void 0===t.padding||"number"==typeof t.padding&&Number.isFinite(t.padding))&&!(void 0!==t.animate&&"boolean"!=typeof t.animate))},ge=(e,t)=>{const r=me(t)?t:t.bbox,s=(me(t)?void 0:t.padding)??50,o=me(t)?void 0:t.animate;return e.baseMap.mapLibreMap.fitBounds(r,{padding:s,...void 0!==o&&{animate:o}}),{bbox:r,padding:s}},fe=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.'),be=K.array(fe).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.'),we="`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.",ve="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",Ie="Tagged input ids that contributed at least one polygon to the run, in input order.",Se='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`.',xe=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)},Pe=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.")})]),Me=e=>'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'+(e.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 municipality polygon\n areaCountry?: string; // ISO2 country of the municipality polygon\n areaTags?: string[]; // e.g. "coastal", "walkable", "transit_connected"\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.",Ee=Me({}),_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)},ke=e=>Array.isArray(e)&&e.every(Re),De='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:$e(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,"")},qe=[{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."}],$e=(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=qe.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:fe,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}};k.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={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]")},Rt={areaId:K.string().optional().describe('Id of the municipality polygon this place sits in. Round-trip into `discoverPlaces.where.areaId` for "what else is in this same municipality?". Only set on explorationSearch results in DE / NL / FR.'),areaCountry:K.string().optional().describe("ISO 3166-1 alpha-2 country code of the surrounding municipality polygon."),areaTags:K.array(K.string()).optional().describe('Area-character tokens describing the surrounding municipality (e.g. "coastal", "walkable", "transit_connected"). Useful for downstream `areaTags` filters.')},kt=e=>K.object(e.experimentalSearch?{...Ct,...Rt}:Ct).describe("Compact place summary"),Dt=e=>K.object({count:K.number(),features:K.array(kt(e))}).describe("Compact places summary"),Ot=kt({}),jt=(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()})),Ft=K.object({index:K.number().describe("0=best, 1+=alternative"),summary:jt,legSummaries:K.array(jt).optional().describe("Present when route has multiple legs")}),Lt=K.object({count:K.number(),routes:K.array(Ft)}),Nt=K.object({}),qt=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]),$t=K.number().int().min(0).optional().describe("default: 0"),Bt=(K.object({routeIndex:$t,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"]),Gt=K.object({name:K.string(),description:K.string(),examplePrompts:K.array(K.string()),relatedTools:K.array(K.string()).optional()}),Ut=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(Bt).optional(),matchedTools:K.array(Gt),totalMatched:K.number()}),At]),Wt=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(Bt).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),zt='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.',Vt=(e,t,r)=>({description:e.description,inputSchema:e.inputSchema,outputSchema:r?e.outputSchema:void 0,execute:r=>e.execute(r,t)}),Ht=(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}),Jt=(e,t)=>"function"==typeof e?e(t):e,Yt=(e,t,r)=>{const s=!1!==r?.outputSchemas,o={featureFlags:r?.featureFlags},n={},a={};for(const[l,c]of Object.entries(e)){const e=Jt(c,o);n[l]=Vt(e,t,s),a[l]=Ht(l,e)}var i;if(n.help&&(n.help=(i=()=>a,{description:zt,inputSchema:Wt,outputSchema:Ut,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}},Kt=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.")}),Xt=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.")}),Zt=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.')}),Qt=K.union([K.object({success:K.literal(!0),result:K.unknown().optional().describe("Return value of the executed code, if any."),sources:Kt,layers:Xt}),At]),er=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},tr=e=>{const t=e.getStyle()?.layers??[],r=/* @__PURE__ */new Map;for(const s of t)r.set(s.id,JSON.stringify(s));return r},rr=(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}},sr=K.union([K.object({success:K.literal(!0)}),At]),or=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.")])}),nr=K.union([K.object({layers:K.array(K.object({id:K.string(),layout:K.unknown().optional(),paint:K.unknown().optional()}))}),At]),ar=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.')}),ir=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]),lr=K.object({}),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. visibility|text-field|icon-size"),value:K.any()}))}).describe("List of layout property changes to apply in one go."),pr=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()})]),hr=K.union([K.object({results:K.array(pr)}),At]),mr=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.")}),yr=K.union([K.object({success:K.literal(!0)}),At]),gr=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")}),fr=K.union([K.object({zoom:K.number().describe("Resulting zoom level after the change")}),At]),br=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.")}),wr=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"),vr=K.object({tools:K.array(K.string()).min(1).describe("Tool names needed to fulfill the query.")}),Ir=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:vr}),system:wr(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}},Sr=e=>"text"===e.type,xr=e=>{if("string"==typeof e)return e;const t=e.filter(Sr);return t.length>0?t.map(e=>e.text).join(""):null},Pr=e=>{for(let t=e.length-1;t>=0;t--)if("user"===e[t].role)return xr(e[t].content);return null},Mr=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=xr(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=xr(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 Ir(d,{chatModel:t,toolsMetadata:n})}catch{return null}}},Er=e=>{if(!Array.isArray(e))return e.properties?.address?.freeformAddress},_r=(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"))},Tr={timeMinutes:"min",distanceKM:"km",remainingChargeCPT:"% remaining",spentChargePCT:"% spent",spentFuelLiters:"L"},Ar=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${Tr[e.type]??e.type}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`,Cr=(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},Rr=(e,t={})=>{const r=Array.isArray(e)?e:e.features;return{count:r.length,features:r.slice(0,5).map(e=>Cr(e,t))}},kr=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))}}},Dr=e=>({count:e.features.length,routes:e.features.map(kr)}),Or=K.object({slotIndex:K.number(),isFilled:K.boolean(),address:K.string().optional(),position:K.array(K.number()).length(2).optional().describe("[longitude, latitude]")}),jr=e=>{if(null==e)return null;const t=q(e);let r;return Array.isArray(e)||(r=e.properties?.address?.freeformAddress),{slotIndex:-1,isFilled:!0,...r&&{address:r},...t&&{position:t}}},Fr=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},Lr=async(e,t,r)=>{const[s]=await Fr(e,t,{limit:1,bias:r});return s??null},Nr=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}),qr=K.union([Ot.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]),$r=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 Lr(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=q(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 Lr(e.query,e.queryAs??"place",r?{position:r}:void 0);if(!s)return{bias:void 0};const o=q(s);return{bias:o?{position:o}:void 0}}return{bias:void 0}})(e,t),Br=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=q(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 Lr(e.query,e.queryAs);if(!r)return null;const s=q(r);return s?{place:r,position:s,name:r.properties?.address?.freeformAddress??e.query,query:e.query}:null},Gr=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(D)).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.")}),Ur=K.object({option:K.enum(["departAt","arriveBy"]),date:K.string().describe("ISO 8601")}),Wr=K.object({maxAlternatives:K.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:Gr.optional(),when:Ur.optional().describe("Omit to depart now.")}),zr=K.union([Lt,K.object({success:K.literal(!0)}),At]),Vr=K.object({locations:K.array(Pe).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:Wr.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`."}),Hr=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},Jr=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)}}}},Yr=async(e,t,r,s)=>{const o=await v({locations:t,...Jr(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(Er).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 xe(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),Dr(o)},Kr=K.union([Lt,At]),Xr=K.object({origin:Pe.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:Pe.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(Pe).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`."}),Zr=e=>"query"in e?`"${e.query}"`:"placeId"in e?`placeId "${e.placeId}"`:JSON.stringify(e.position),Qr=async(e,t,r)=>{const s=await Fr(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]}},es=e=>K.union([Dt(e).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]),ts=(es({}),{...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.")}),rs={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.'),areaId:K.string().optional().describe('Id of a municipality polygon — restricts results to that single municipality. Typically chained from a previous hit\'s `areaId` ("what else is in this same area?"). EXCLUSIVE with viewport; composes with other multi-region fields.')},ss=e=>K.object(e.experimentalSearch?{...ts,...rs}:ts).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."}),os=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.")}),ns=e=>{const t=e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/areaId/geometries/range/route":"queries/placeIds/geometries/range/route";return K.union([ss(e),at,os,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 }\`.`)},as=e=>{const t={query:K.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:ns(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 places/geometries on the map. Skip for intermediate results — let the follow-up (processPlaces / analysePlaces / processGeometries) render the final entry."),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."),areaTags:K.array(K.string()).optional().describe('Area-character tokens describing the surrounding municipality (e.g. "coastal", "walkable", "alpine", "transit_connected"). OR semantics — matches places in municipalities tagged with ANY of the tokens. Populated only for DE / NL / FR; supplying these in other countries returns zero hits. Filter, not a geo-bias — pair with a `where` mode that supplies one.')}: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`)."})},is=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` (DE/NL/FR only) narrows hits by municipality character ("coastal", "walkable", …). ':"")+'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.',ls={},cs=(as(ls),is(ls),e=>{const{geometries:t}=e;return t?.length?{geometries:t}:{}}),ds=(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,...cs(a)})},us=async(e,t,r)=>{if(e.viewport)return{bias:{boundingBox:bt(t.baseMap)},multiFilters:r};const s=[],o=[];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 Qr(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,a=[...n.boundingBoxes??[],...o],i=[...e.geometries??[],...s],l={...r,...n.municipalities?.length&&{municipalities:n.municipalities},...n.areaId&&{areaId:n.areaId},...a.length&&{boundingBoxes:a},...i.length&&{geometries:i}};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 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;l.geometries=[...l.geometries??[],...r.polygons],c=r.routeLabel}return{bias:{},multiFilters:l,range:e.range,routeLabel:c}},ps=async(e,t,r)=>{if("nearby"===e.mode){const s=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 Lr(e.query,e.queryAs??"place",r?{position:r}:void 0);if(s){const t=q(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 us(e,t,r);return"error"in s?s:{bias:s.bias,multiFilters:s.multiFilters,range:s.range,routeLabel:s.routeLabel}}return{bias:{},multiFilters:r}},hs=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},ms=async(e,t,r,s,o,n)=>{const a=s?await dt(e,[r],s):void 0;let i,l;if(o){const t=await ct(e,r,o);i=t.fetched,t.shown&&(l=!0)}const c=e.places.entries.find(e=>e.id===r)?.label;return{...Rr(t,n),placesEntryId:r,...c&&{label:c},...a&&{shown:a},...void 0!==i&&{geometriesFetched:i},...void 0!==l&&{geometriesShown:l}}},ys=e=>{const t=!0===e.experimentalSearch;return async(r,s)=>{const{query:o,where:n,poiCategories:a,show:i,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=>_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}})(a);if(a?.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},g={...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 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,_r(l,{query:r,poiCategories:s,routeLabel:a.label}),o)}})(s,y,o,h,l);return"error"in t?t:ms(s,t.result,t.placesEntryId,i,c,e)}const r=await ps(y,s,g);if("error"in r)return r;const{bias:n,multiFilters:a,radiusMeters:d,range:u,routeLabel:p}=r;if(u){const r=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 ds(i,{query:r,poiCategories:s,multiFilters:{...o,geometries:d}});return{result:u,placesEntryId:e.places.addPlaceResult(u,_r(u,{query:r,poiCategories:s,...a&&{routeLabel:a}}),n)}})(s,u,o,h,a,l,p,t);return"error"in r?r:ms(s,r.result,r.placesEntryId,i,c,e)}const m=await(async(e,t,r,s,o,n,a,i,l,c)=>{const d=await ds(c,{query:t,poiCategories:s,bias:r,radiusMeters:o,multiFilters:n});return{result:d,placesEntryId:e.places.addPlaceResult(d,_r(d,{query:t,poiCategories:s,where:hs(i),...l&&{routeLabel:l}}),a)}})(s,o,n,h,d,a,l,y,p,t);return ms(s,m.result,m.placesEntryId,i,c,e)}catch(f){return{error:`Search failed: ${f instanceof Error?f.message:String(f)}`}}}},gs=(ys(ls),{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/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", 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","processPlaces","analysePlaces"],dependsOn:["getPOICategoryCodes","setRoute"]}),fs=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.")}),bs=K.object({origins:K.array(Pe).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(fs).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")}),ws=K.object({rangesId:K.string(),ranges:K.array(K.object({originName:K.string(),budgets:K.array(fs)})),skipped:K.array(K.object({origin:K.string(),reason:K.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry).")}),vs=K.union([ws,K.object({status:K.enum(["no_results"])}),At]),Is=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},Ss=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,xs=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]),Ps=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".')}),Ms=K.object({speed:K.number().optional(),freeFlowSpeed:K.number().optional(),congestionLevel:K.number().optional(),travelTime:K.number().optional(),networkLength:K.number().optional()}),Es=K.union([K.object({name:K.string().optional(),timezone:K.string().optional(),dateRange:K.object({start:K.string(),end:K.string()}),baseData:Ms,metrics:K.array(K.string()),tileCount:K.number(),availableGranularities:K.array(K.string())}),At]),_s=K.object({location:Pe.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.")}),Ts=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`,As=e=>e instanceof Date?Number.isNaN(e.getTime())?void 0:Ts(e):"string"==typeof e?e.slice(0,10):void 0,Cs=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),Rs=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]),ks=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 Ds=K.union([Lt,At]),Os=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")}),js=K.union([Lt,At]),Fs=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)."),Ls=K.object({waypointIndex:Fs,location:Pe.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")}),Ns=e=>"query"in e?`"${e.query}"`:"placeId"in e?`placeId "${e.placeId}"`:JSON.stringify(e.position),qs=K.union([Ot,At]),$s=K.object({position:K.array(K.number()).length(2).describe("[longitude, latitude] GeoJSON position to reverse geocode")}),Bs=K.union([K.object({affectedEntries:K.array(K.object({id:fe.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(fe).describe(Ie),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(ve)}),At]),Gs=`Async JS that aggregates the injected \`geometries\` array and returns the result. ${we}\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${Se}`,Us=K.object({geometriesEntryIDs:be,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(De),code:K.string().describe(Gs)}),Ws=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]),zs=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(De),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\`).`)}),Vs=(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}}},Hs=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]),Js=e=>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(De),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${Me(e)}\n\n`)}),Ys=(Js({}),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}}),Ks={classificationPrompt:"Aggregate / summarise / chart existing places via dynamic JS (h3, turf available). `outputFormat`: `json` (text) or `chart` (Chart.js).",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"]},Xs=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]),Zs=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(De),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`)}),Qs=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]),eo=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").')}),to=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.")}),ro=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(Or)}),so=K.union([K.object({geometriesEntryId:fe.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(fe).describe(Ie),operation:K.string().optional().describe("Operation label stored on the new entry."),skipped:K.array(ut).optional().describe(ve),fitted:pe.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:pe}),At]),oo=`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${we}\n\n${Ae(["geometries","h3","turf"])}\n\n${he}\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${Se}`,no=K.object({geometriesEntryIDs:be,code:K.string().describe(oo),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.')}),ao=K.object({mode:Je,theme:K.enum(["filled","outline","inverted"]).optional().describe('Visual theme for the polygons. Default: "outline".')}),io=K.object({places:et.optional(),geometries:K.object({count:K.number(),theme:K.enum(["filled","outline","inverted"]),mode:K.enum(["add","replace"])}).optional()}),lo=e=>K.union([Dt(e).extend({shown:io.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:pe.optional().describe("Bounds the camera was fit to (when the code returned `fitOnMap`).")}),K.object({fitted:pe}),At]),co=(lo({}),e=>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? }\`. Need at least one of \`places\` / \`fitOnMap\`; \`placeConnections\` and \`geometries\` attach to the new entry and require \`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\` merges all \`placesEntryIDs\`; \`placesByEntry[id]\` keeps them separate for set ops (diff/intersection/group-by-source). List every needed id in \`placesEntryIDs\` — the sandbox cannot call other tools. \`geometries\` arg is empty unless \`withGeometries: true\`.\n\n${Ae(["places","placesByEntry","geometries","h3","turf"])}\n\n${he}\n\n${Me(e)}\n\n${Se}\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:ao.optional().describe("Render the polygons returned in `geometries`. No-op when none were produced.")}).optional().describe('`show.places` draws the processed places; `show.geometries` draws produced polygons; combine freely. For derived results that supersede their inputs (filter/refine/subset), set `show.places.hidePreviousEntries: "all"` (or the source `placesEntryIDs`) so only the final entry is visible. Omit to store without rendering.'),connectMarkerType:K.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Match the module holding the endpoints — ids only resolve against currently-shown places.'),withGeometries:K.boolean().optional().describe("Pre-fetch each input place's boundary polygon and pass it as the `geometries` arg. Required when the code reads `geometries` (aggregate footprints, h3 coverage, buffer outlines). Default: false.")})),uo=(co({}),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&&!ye(t.fitOnMap))&&((void 0!==t.places||void 0!==t.fitOnMap)&&!(void 0===t.places&&(void 0!==t.placeConnections||void 0!==t.geometries))))))}),po=e=>async(t,r)=>{const{placesEntryIDs:s,code:o,label:n,show:a,connectMarkerType:i,entryId:l,withGeometries:c}=t,d=s?.length?s.map(e=>r.places.entries.find(t=>t.id===e)):[r.places.entries.at(-1)];for(let e=0;e<d.length;e++)if(!d[e]){const t=s?.[e];return{error:t?`No entry found with id "${t}". Use recallPlaces to list available IDs.`:"No places entries available to process. Run discoverPlaces first."}}const u=d,p={type:"FeatureCollection",features:u.flatMap(e=>e.places)},h=Object.fromEntries(u.map(e=>[e.id,{type:"FeatureCollection",features:e.places}]));let m=[];if(c)try{m=(await Promise.all(u.map(e=>r.places.fetchGeometriesForEntry(e.id)))).flat()}catch(A){return{error:`Failed to fetch input geometries: ${A instanceof Error?A.message:String(A)}`}}const y=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}:uo(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? }`."}})(o,p,h,m);if("error"in y)return y;const{places:g,placeConnections:f,geometries:b,fitOnMap:w}=y;if(!g)return{fitted:ge(r,w)};const v=g.features.length,I=`${n?`processed: ${n}`:"processed"} (${v} ${1===v?"place":"places"})`;let S;if(l)S=l;else{const e=`${u.map(e=>e.id).join("+")}-process-`;S=`${e}${r.places.entries.filter(t=>t.id.startsWith(e)).length}`}const x=r.places.addPlaceResult(g,I,S,f,b),P={};if(a?.places&&(P.places=await dt(r,[x],a.places)),a?.geometries&&b?.length){const e=a.geometries.theme??"outline",t=a.geometries.mode??"replace";await r.places.showPlaceGeometries(b,e,t,x),P.geometries={count:b.length,theme:e,mode:t}}const M=P.places||P.geometries?P:void 0,E=r.places.entries.find(e=>e.id===x)?.label;let _;if(f){const e=await(async(e,t,r,s)=>{try{return await(await r.places.getEntryPlacesModule(e,s)).showConnections(t),t.length}catch(A){return{error:`Failed to render connections: ${A instanceof Error?A.message:String(A)}`}}})(x,f,r,i??"pin");if("object"==typeof e)return e;_=e}const T=w?ge(r,w):void 0;return{...Rr(g,e),placesEntryId:x,...E&&{label:E},...M&&{shown:M},...void 0!==_&&{connectionsRendered:_},...b?.length&&{geometryCount:b.length},...T&&{fitted:T}}},ho=(po({}),{classificationPrompt:"Run dynamic JS over existing places to filter/merge/subset/re-order and/or compute connections between them. Code returns `{ places, placeConnections? }`.",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"]}),mo={bboxFromGeoJSON:O,calculateProgressAtRoutePoint:j,getCoordinateAtRouteProgress:N,getProgressAtNearestRoutePoint:$,getRouteProgressBetween:B,getRouteProgressForSection:G,getSectionBBox:U},yo=K.union([K.object({fitted:pe.describe("Bounds the camera was fit to.")}),At]),go=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${he}\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}`)}),fo=Ms.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()}),bo=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).")}),wo=K.union([K.object({granularity:K.string(),count:K.number(),entries:K.array(fo),currentVisualization:K.object({activeMetric:K.string().optional(),displayMode:K.string().optional()}).optional()}),At]),vo=K.object({id:fe.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.')}),Io=K.object({name:K.string(),outputFormat:K.enum(["json","chart"]),timestamp:K.number(),description:K.string().optional()}),So=K.object({id:fe.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(fe).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:K.array(Io).optional().describe("Analyses already attached to this entry (places and custom only).")}),xo=So.extend({features:K.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),Po=K.union([K.object({entries:K.array(So)}),xo,At]),Mo=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:s})=>({name:e,outputFormat:t,timestamp:r,...s&&{description:s}})):void 0,Eo=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const e=Mo(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=Mo(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)},_o=K.object({id:K.string().optional().describe('Entry ID to retrieve (e.g. "places-3"). Omit to list all entries.')}),To=K.object({name:K.string(),outputFormat:K.enum(["json","chart"]),timestamp:K.number(),description:K.string().optional()}),Ao=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(To).optional().describe("Analyses already attached to this entry via analysePlaces (name + output format).")}),Co=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.'),Ro=e=>{const t=K.object({id:K.string(),label:K.string(),timestamp:K.number(),places:Dt(e)});return K.union([K.object({entries:K.array(Ao),entryMode:Co}),t.extend({entryMode:Co}),At])},ko=(Ro({}),e=>async(t,r)=>{const{id:s}=t,o=r.places.entries,n=r.places.entryMode;if(!s)return{entries:[...o].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:n};const a=o.find(e=>e.id===s);if(!a)return{error:`No entry found with id "${s}"`};const i=Rr(a.places,e);return{id:a.id,label:a.label,timestamp:a.timestamp,places:i,entryMode:n}}),Do=(ko({}),{classificationPrompt:"List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processPlaces, analysePlaces, 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 processPlaces or updatePlacesDisplay?","Recall the bakeries I discovered earlier","What is in entry places-2?"],relatedTools:["discoverPlaces","locatePlace","reverseGeocode","updatePlacesDisplay"],dependsOn:["locatePlace","discoverPlaces"]}),Oo=K.object({id:K.string().optional().describe('Entry ID to retrieve (e.g. "ranges-0"). Omit to list all entries.')}),jo=K.object({id:K.string(),label:K.string(),timestamp:K.number(),originCount:K.number()}),Fo=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()}))}),Lo=K.object({id:K.string(),label:K.string(),timestamp:K.number(),ranges:K.array(Fo)}),No=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.'),qo=K.union([K.object({entries:K.array(jo),entryMode:No}),Lo.extend({entryMode:No}),At]),$o=K.object({id:K.string().optional().describe('Entry ID to retrieve (e.g. "routes-1"). Omit to list all entries.')}),Bo=K.object({id:K.string(),label:K.string(),timestamp:K.number()}),Go=K.object({maxAlternatives:K.number().optional(),costModel:Gr.optional(),when:Ur.optional()}),Uo=K.object({id:K.string(),label:K.string(),timestamp:K.number(),routes:Lt,waypoints:K.array(K.object({position:K.array(K.number()).length(2).describe("[longitude, latitude]")})),params:Go}),Wo=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.'),zo=K.union([K.object({entries:K.array(Bo),entryMode:Wo}),Uo.extend({entryMode:Wo}),At]),Vo=K.object({id:K.string(),label:K.string()}),Ho=K.enum(["single","multiple"]),Jo=K.union([K.object({places:K.array(Vo).describe("Places history entries — newest first."),routes:K.array(Vo).describe("Routes history entries — newest first."),ranges:K.array(Vo).describe("Reachable-range history entries — newest first."),entryModes:K.object({places:Ho,routes:Ho,ranges:Ho}).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]),Yo=K.object({}),Ko=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]),Xo=K.object({}),Zo=K.enum(["places","routes","ranges"]),Qo=K.enum(["single","multiple"]),en=K.union([K.object({success:K.literal(!0),slice:Zo,mode:Qo,droppedEntryIds:K.array(K.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),At]),tn=K.object({slice:Zo.describe("Which history to reconfigure: places / routes / ranges."),mode:Qo.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),rn=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]),sn=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).")}),on=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]),nn=K.object({incidentsEntryID:K.string().describe("Id of the incidents entry whose monitor should stop.")}),an=K.union([K.object({success:K.literal(!0),cleared:K.array(K.string())}),At]),ln=["places","routes","geometries","analytics","incidents"],cn=K.object({layers:K.array(K.enum(ln)).optional().describe("Layers to clear (default: all). `analytics` covers traffic area analytics; `incidents` covers fetched traffic incidents.")}),dn=(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])),un=K.object({}),pn=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]),hn=K.object({}),mn=(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])),yn=K.object({}),gn=K.union([Lt,At]),fn=K.object({}),bn=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]),wn=K.object({}),vn=K.union([K.object({styles:K.array(K.string()).readonly()}),At]),In=K.object({}),Sn=K.union([K.object({success:K.literal(!0),language:K.string()}),At]),xn=K.object({language:K.string().describe('e.g. "en-US", "fr-FR"')}),Pn=K.union([K.object({success:K.literal(!0),style:K.string()}),At]),Mn=K.object({style:K.enum(g).describe("Map visual theme")}),En=K.union([K.object({success:K.literal(!0),theme:K.object({mainColor:K.string().optional()})}),At]),_n=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").')}),Tn={},An=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.")}),Cn=K.union([K.object({success:K.literal(!0),mode:K.string(),metric:K.string(),colorTheme:K.string(),visible:K.boolean(),filtered:K.boolean()}),At]),Rn=K.union([K.object({success:K.literal(!0),count:K.number()}),At]),kn=K.object({}),Dn=K.union([K.object({success:K.literal(!0),visible:K.boolean(),layerGroups:K.array(K.string())}),At]),On=K.object({visible:K.boolean(),layerGroups:K.array(K.enum(m)).describe("The base map layer groups to show or hide.")}),jn=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]),Fn=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.")}),Ln=K.object({visible:K.boolean()}),Nn=K.union([K.object({success:K.literal(!0),trafficFlowVisible:K.boolean()}),At]),qn=K.object({visible:K.boolean()}),$n=K.union([K.object({success:K.literal(!0),trafficIncidentsVisible:K.boolean()}),At]),Bn=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.")}),Gn=K.union([K.object({success:K.literal(!0),totalShown:K.number(),shown:K.array(Bn).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]),Un=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."}),Wn=(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]},zn=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).")}),Vn=K.union([K.object({success:K.literal(!0),totalShown:K.number(),shown:K.array(zn).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]),Hn=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."}),Jn=(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]},Yn={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:Nr,outputSchema:qr,execute:async(e,t)=>{const{query:r,queryAs:s,waypointIndex:o,where:n,show:a,entryId:i,geometry:l}=e;try{const e=await $r(n,t);if("error"in e)return e;const c=await Lr(r,s,e.bias);if(!c)return{error:`No result found for "${r}"`};const d=t.places.addPlaceResult(c,_r(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=q(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{...Cr(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:$s,outputSchema:qs,execute:async(e,t)=>{const{position:r}=e,s=r;try{const e=await E({position:s});return e?(t.places.addPlaceResult(e,_r(e)),Cr(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=gs,{...r,description:is(t),inputSchema:as(t),outputSchema:es(t),execute:ys(t)};var t,r},processPlaces:e=>{return t=e.featureFlags??{},r=ho,{...r,description:'Dynamic JS over place entries: filter/merge/subset/re-order, draw connections (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 (rendered via `show.places` / `show.geometries` / `connectMarkerType`); `fitOnMap`-only skips state. For derived results that supersede inputs, set `show.places.hidePreviousEntries: "all"` so only the new entry is visible. Set `withGeometries: true` to pre-fetch boundary polygons.',inputSchema:co(t),outputSchema:lo(t),execute:po(t)};var t,r},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:no,outputSchema:so,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(!ke(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&&!ke(t.geometries)?"`geometries` must be a `PolygonFeature[]` (each a GeoJSON Feature with a Polygon or MultiPolygon geometry).":s&&!ye(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:ge(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?ge(t,h.fitOnMap):void 0;let b=!1;return i&&(await xe(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:go,outputSchema:yo,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,mo,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&&ye(t.fitOnMap)})(d)?{fitted:ge(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:Us,outputSchema:Bs,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:e=>{return t=e.featureFlags??{},r=Ks,{...r,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).',inputSchema:Js(t),outputSchema:Hs,execute:Ys};var t,r},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:Zs,outputSchema:Xs,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:Vr,outputSchema:zr,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=>Br(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=Hr(s.map(e=>e?.place??null)),!e)return{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}else if(e=Hr(t.routing.planningSlots),!e)return{success:!0};return Yr(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:Xr,outputSchema:Kr,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})=>Br(e,t))),i=[];for(let c=0;c<n.length;c++)null===a[c]&&i.push(Zr(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=Hr(c.stops.length>0?H(i.features[0],d,c.stops):d);return u?Yr(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:Os,outputSchema:Ds,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=Hr(e);return n?Yr(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:Ls,outputSchema:js,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 Br(s,t);if(!c)return{error:`Could not resolve location: ${Ns(s)}`};const d=[...e];d[l]=c.place;const u=Hr(d);return u?Yr(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:Ps,outputSchema:xs,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:ks,outputSchema:Rs,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:Cs(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:Cs(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:zs,outputSchema:Ws,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=Vs(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:eo,outputSchema:Qs,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:sn,outputSchema:rn,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:nn,outputSchema:on,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:_s,outputSchema:Es,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 Br(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??As(e.properties?.startDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-3),Ts(e)})(),n=r??As(e.properties?.endDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-2),Ts(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:bo,outputSchema:wo,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=As(c.properties?.startDate);let m=p.map((e,t)=>{const o={};if("daily"===r||"hourly"===r){const s=((e,t,r,s)=>{const o=As(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),Ts(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:Un,outputSchema:Gn,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??[],...Wn(e,o??[])];const c=[...s??[],...Wn(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:Hn,outputSchema:Vn,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??[],...Jn(e,o??[])];const l=[...s??[],...Jn(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:An,outputSchema:Cn,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:kn,outputSchema:Rn,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:hn,outputSchema:pn,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:fn,outputSchema:gn,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?Dr(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:wn,outputSchema:bn,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:yn,outputSchema:mn,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:un,outputSchema:dn,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:cn,outputSchema:an,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||[...ln]}}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:or,outputSchema:sr,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:lr,outputSchema:ir,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:vo,outputSchema:Po,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},...Mo(r._analysis)&&{analyses:Mo(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]},...Mo(r._analysis)&&{analyses:Mo(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:Eo(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:e=>{return t=e.featureFlags??{},r=Do,{...r,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.",inputSchema:_o,outputSchema:Ro(t),execute:ko(t)};var t,r},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:$o,outputSchema:zo,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=Dr(o.data),a=o.waypoints.map(e=>jr(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:to,outputSchema:ro,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=jr(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:In,outputSchema:vn,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:Mn,outputSchema:Pn,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:_n,outputSchema:En,execute:async(e,t)=>{try{void 0!==e.mainColor&&(Tn.mainColor=e.mainColor);const r=t.routing.currentEntryModule;return r?(r.applyConfig({theme:{mainColor:Tn.mainColor}}),{success:!0,theme:{...Tn}}):{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:xn,outputSchema:Sn,execute:async(e,t)=>{const{language:r}=e;try{return k.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:On,outputSchema:Dn,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:Fn,outputSchema:jn,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:Ln,outputSchema:Nn,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:qn,outputSchema:$n,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:ar,outputSchema:nr,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: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.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:mr,outputSchema:hr,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:gr,outputSchema:yr,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:br,outputSchema:fr,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:Zt,outputSchema:Qt,execute:async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,s=er(e),o=tr(e),n=Object.getPrototypeOf(async()=>{}).constructor,a=await new n("map",r)(e),i=rr(s,er(e));return{success:!0,result:a??void 0,sources:i,layers:rr(o,tr(e))}}catch(s){return{error:$e("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:Nt,outputSchema:qt,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:zt,classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:Wt,outputSchema:Ut,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:bs,outputSchema:vs,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 Br(s,t);if(!o){c.push({origin:r,reason:"origin not found"});continue}const n=await Is(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(Ss),p=t.ranges.addEntry({label:Ar(e,u),ranges:l});return(n||a)&&await xe(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:Ss(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:Ss(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:Oo,outputSchema:qo,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:Yo,outputSchema:Jo,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:Xo,outputSchema:Ko,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:tn,outputSchema:en,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"]}},Kn=Object.keys(Yn),Xn=(e,t)=>{if(!e)return t;if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},Zn=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,Qn=(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 le(e),mapPOIs:new ae(e),routing:new de(e),baseMap:new Z(e),trafficTiles:new xt(e),trafficAreaAnalytics:new ue(e),trafficIncidents:new St(e),ranges:new ce(e),customGeometries:new te(e)};return t&&Object.assign(r,t),r})(e,r.state),{tools:n,toolsMetadata:a}=Yt(X(!1===r.includeDefaultTools?{}:Yn,r.tools),o,{outputSchemas:r.outputSchemas,featureFlags:r.featureFlags}),i=!1===r.classifier?null:r.classifier??Mr({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:Xn(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))Zn(t)&&t.reset()})(o)})};export{Pt as BASE_SYSTEM_PROMPT,Yn as DEFAULT_TOOLS,Kn as TOOL_NAMES,Mt as buildSystemPrompt,Ir as classifyUserIntent,Mr as createDefaultClassifier,Qn as createMapAgent,Pr as extractLastUserText,ne as formatStateDigestDiff,fe as geometriesIdSchema,re as getStateDigest,X as resolveTools};
2
2
  //# sourceMappingURL=index.es.js.map