@tomtom-org/maps-sdk-plugin-agent-toolkit 0.6.0 → 0.7.0

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,hasToolCall as n,stepCountIs as s}from"ai";import{BaseMapModule as o,CustomGeoJSONModule as a,GeometriesModule as i,HillshadeModule as l,POIsModule as c,PlacesModule as d,RoutingModule as u,StylingModule as p,TrafficAreaAnalyticsModule as h,TrafficFlowModule as y,TrafficIncidentOverlayModule as m,TrafficIncidentsModule as g,baseMapLayerGroupNames as f,mapStyleLayerIDs as b,reachableRangeGeometryConfig as w,standardStyleIDs as v,stylingKnobAppliesTo as I,stylingKnobIds as E,stylingKnobKinds as S,stylingPresetIds as M,themedGeometryConfig as k}from"@tomtom-org/maps-sdk/map";import{POPULATED_AREA_TAGS as x,alongRouteSearch as A,calculateReachableRanges as D,calculateRoute as T,explorationSearch as P,geocode as _,geometryData as O,getPOICategoryCodes as C,reverseGeocode as R,routeTypes as j,search as B,trafficAreaAnalytics as N,trafficIncidentDetails as L}from"@tomtom-org/maps-sdk/services";import{TOMTOM_USER_AGENT_SDK_NAME as F,TomTomConfig as q,avoidableTypes as $,bboxCenter as G,bboxFromBBoxes as U,bboxFromGeoJSON as W,calculateProgressAtRoutePoint as z,formatDistance as Y,formatDuration as H,getCoordinateAtRouteProgress as V,getPosition as J,getProgressAtNearestRoutePoint as K,getRouteProgressBetween as X,getRouteProgressForSection as Z,getSectionBBox as Q,placeTypes as ee,poiCategories as te,trafficIncidentRequestCategories as re,withInsertedWaypoints as ne}from"@tomtom-org/maps-sdk/core";import*as se from"@turf/turf";import{z as oe}from"zod";import*as ae from"h3-js";var ie=(e,t)=>{if(!e)return t;if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},le=(e,t)=>{if(!e)return;if(0===t.length)return e;const r=new Set(e);return[...e,...t.filter(e=>!r.has(e))]},ce=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,de=(e,t)=>{if(e||t)return{...e,...t}},ue=(e,t)=>{const r={...e};if(!t)return r;for(const[n,s]of Object.entries(t))s?r[n]=s:delete r[n];return r},pe=class{handlers={};on(e,t){return(this.handlers[e]??(this.handlers[e]=[])).push(t),()=>{const r=this.handlers[e];if(!r)return;const n=r.indexOf(t);-1!==n&&r.splice(n,1)}}off(e){this.handlers[e]&&(this.handlers[e].length=0)}emit(e,t){const r=this.handlers[e];if(r?.length)for(const n of[...r])n(t)}clear(){for(const e of Object.keys(this.handlers))this.handlers[e]=[]}},he=class{_records=/* @__PURE__ */new Map;_jobs=/* @__PURE__ */new Map;events=new pe;register(e){const t=this._records.get(e.analysisId);this._records.set(e.analysisId,{analysisId:e.analysisId,name:e.name,description:e.description,outputFormat:e.outputFormat,affectedEntryIds:e.affectedEntryIds,history:t?.history??[]})}setJob(e,t){this._jobs.set(e,t)}job(e){return this._jobs.get(e)}resetHistory(e){const t=this._records.get(e);t&&(t.history=[])}attachResult(e,t){const r=this._records.get(e);r&&(r.history.at(-1)?.timestamp===t.timestamp?r.history[r.history.length-1]=t:(r.history.push(t),r.history.length>240&&r.history.shift()),this.events.emit("analysis-change",{analysisId:e,affectedEntryIds:r.affectedEntryIds}))}getAnalysesForEntry(e){const t=[];for(const r of this._records.values()){if(!r.affectedEntryIds.includes(e))continue;const n=r.history.at(-1);n&&t.push(n)}return t}all(){return[...this._records.values()]}history(e){return this._records.get(e)?.history??[]}lastResult(e){return this._records.get(e)?.history.at(-1)?.data}get(e){return this._records.get(e)}isMonitored(e){return this._jobs.get(e)?.active??!1}remove(e){this._jobs.get(e)?.unregister(),this._jobs.delete(e),this._records.delete(e)}removeByName(e){for(const[t,r]of this._records)r.name===e&&this.remove(t)}reset(){for(const e of this._jobs.values())e.unregister();this._jobs.clear(),this._records.clear()}},ye=class{ttMap;_baseMapModule;_hillshadeModule;_stylingModule;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 l.get(this.ttMap),this._hillshadeModule}async getStylingModule(){return this._stylingModule=await p.get(this.ttMap),this._stylingModule}reset(){this._baseMapModule=void 0,this._hillshadeModule=void 0,this._stylingModule=void 0}},me=(e,t)=>{const r=new Set(t);if(!r.has(e))return e;for(let n=2;;n++){const t=`${e}-${n}`;if(!r.has(t))return t}},ge=async(e,t,r={})=>{if(0!==e.length)if(r.parallel)await Promise.all(e.map(t));else for(const n of e)await t(n)},fe=async(e,t,r={})=>{if(e.length<=1)return[...e];const n=e[e.length-1];return await ge(e.slice(0,-1),t,r),[n]},be=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const n of e){const e="string"==typeof n.id?n.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(n)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},we=class{_ttMap;_entries=[];_entryMode="multiple";events=new pe;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r,n){const s="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const o=`geometries-${this._entries.length}`,a=me(n??o,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:r,data:e,provenance:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...s,a]}),a}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e),n={...k(),theme:t};return r._module?r._shownTheme!==t&&(r._module.applyConfig(n),r._shownTheme=t):(r._module=await i.create(this._ttMap,n),r._shownTheme=t),r._module}async showEntry(e,t="outline",r="replace"){const n=this._requireEntry(e);await this._hideOthersUnderSingleMode(e);const s=await this.getEntryGeometriesModule(e,t),o=n.data;"add"===r?await be(s,o):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(s,o);const a=n._shown;n._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?._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",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`CustomGeometriesState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._module?.clear();this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},ve=(e,t)=>e.localeCompare(t),Ie=e=>[...e].sort(ve),Ee=e=>({places:{shownAsPin:Ie(e.places.shownAsPinIds),shownAsBaseMap:Ie(e.places.shownAsBaseMapIds),entryMode:e.places.entryMode},routes:{shown:Ie(e.routing.shownEntryIds),entryMode:e.routing.entryMode},ranges:{shown:Ie(e.ranges.shownEntryIds),entryMode:e.ranges.entryMode},traffic:{shown:Ie(e.trafficAreaAnalytics.shownEntryIds),entryMode:e.trafficAreaAnalytics.entryMode,entryCount:e.trafficAreaAnalytics.entries.length},incidents:{shown:Ie(e.trafficIncidents.shownEntryIds),monitored:Ie(e.trafficIncidents.entries.filter(e=>"running"===e._monitor?.status).map(e=>e.id)),entryMode:e.trafficIncidents.entryMode},customGeometries:{shown:Ie(e.customGeometries.shownEntryIds),entryMode:e.customGeometries.entryMode,entryCount:e.customGeometries.entries.length},byod:{shown:Ie(e.byod.shownEntryIds),entryMode:e.byod.entryMode,entryCount:e.byod.entries.length}}),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},Me=(e,t,r)=>{if(!Se(t,r))return 0===r.length?`${e} cleared`:`${e} shown: ${r.map(e=>`"${e}"`).join(", ")}`},ke=(e,t)=>{const r=[],n=((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);n&&r.push(n);const s=Me("routes",e.routes.shown,t.routes.shown);s&&r.push(s);const o=Me("ranges",e.ranges.shown,t.ranges.shown);o&&r.push(o);const a=Me("traffic analytics",e.traffic.shown,t.traffic.shown);a&&r.push(a);const i=Me("incidents",e.incidents.shown,t.incidents.shown);i&&r.push(i);const l=Me("incidents monitor",e.incidents.monitored,t.incidents.monitored);l&&r.push(l);const c=Me("custom geometries",e.customGeometries.shown,t.customGeometries.shown);c&&r.push(c);const d=Me("byod",e.byod.shown,t.byod.shown);return d&&r.push(d),e.places.entryMode!==t.places.entryMode&&r.push(`places entryMode → ${t.places.entryMode}`),e.routes.entryMode!==t.routes.entryMode&&r.push(`routes entryMode → ${t.routes.entryMode}`),e.ranges.entryMode!==t.ranges.entryMode&&r.push(`ranges entryMode → ${t.ranges.entryMode}`),e.incidents.entryMode!==t.incidents.entryMode&&r.push(`incidents entryMode → ${t.incidents.entryMode}`),e.customGeometries.entryMode!==t.customGeometries.entryMode&&r.push(`custom geometries entryMode → ${t.customGeometries.entryMode}`),e.byod.entryMode!==t.byod.entryMode&&r.push(`byod entryMode → ${t.byod.entryMode}`),0===r.length?null:`[map state changed since last response: ${r.join("; ")}]`},xe=class{_ttMap;_poisModule;constructor(e){this._ttMap=e}async getPOIsModule(){return this._poisModule=await c.get(this._ttMap),this._poisModule}get poisModule(){return this._poisModule}reset(){this._poisModule=void 0}},Ae="AgentToolkit",De=`${F}-${Ae}/0.6.0`,Te=e=>({...e,"tomtom-user-agent":De}),Pe=["pin","base-map","pin-clustered"],_e=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const n of e){const e="string"==typeof n.id?n.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(n)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},Oe=class{_ttMap;_entries=[];_entryMode="multiple";events=new pe;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this._hideEntry(e)),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),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 d.create(this._ttMap,{theme:t}),r._modules.places}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);r._modules??={};const n={...k(),theme:t};return r._modules.geometries?r._modules.geometries.applyConfig(n):r._modules.geometries=await i.create(this._ttMap,n),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)?.data}async addPlaceResult(e,t,r,n,s){"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this._hideEntry(e)),this._entries=[]);const o=`places-${this._entries.length}`,a=me(r??o,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:t,data:"features"in e?e.features:[e],...n?.length&&{connections:n},...s?.length&&{geometries:s}}),this.events.emit("entries-change",{entries:this._entries,changedIds:[a]}),s?.length&&this.events.emit("geometries-change",{entryId:a,count:s.length}),a}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.data)r._byId[e.id]=e}const n=r._byId[e];if(n)return{place:n,entryId:r.id}}}geometryPlaceIdsForEntry(e){const t=this._entries.find(t=>t.id===e);if(t)return t.data.filter(e=>e.properties.dataSources?.geometry?.id&&"string"==typeof e.id).map(e=>e.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 n=r._geometryByPlaceId[e];if(n)return n}}async fetchPlaceGeometry(e,t){const r=this.getGeometryForPlace(e);if(r)return r;const n=this.findPlaceById(e);if(!n)return;if(!n.place.properties.dataSources?.geometry?.id)return;const s=Te({geometries:[n.place],signal:t?.signal}),o=(await O(s)).features[0];if(!o)return;const a=this._entries.find(e=>e.id===n.entryId);return a&&(a.geometries??=[],a.geometries.push(o),a._geometryByPlaceId??={},a._geometryByPlaceId[e]=o,this.events.emit("geometries-change",{entryId:a.id,count:a.geometries.length})),o}async fetchGeometriesForEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return[];const n=/* @__PURE__ */new Set;for(const c of r.geometries??[])"string"==typeof c.id&&n.add(c.id);const s=[],o=/* @__PURE__ */new Set;for(const c of r.data){const e=c.properties.dataSources?.geometry?.id;e&&(n.has(e)||o.has(e)||(o.add(e),s.push(c)))}if(0===s.length)return r.geometries??[];const a=[];for(let c=0;c<s.length;c+=5)a.push(s.slice(c,c+5));const i=await Promise.all(a.map(e=>O(Te({geometries:e,signal:t?.signal})))),l=[...r.geometries??[]];for(const c of i)l.push(...c.features);return r.geometries=l,r._geometryByPlaceId=void 0,this.events.emit("geometries-change",{entryId:r.id,count:l.length}),l}async showPlaceGeometries(e,t="outline",r="replace",n){const s=n??this._latestGeometriesEntryId();if(!s)return;const o=await this.getEntryGeometriesModule(s,t);"add"===r?await _e(o,e):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(o,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 n of e.geometries??[])"string"==typeof n.id&&t.set(n.id,n);const r={};for(const n of e.data){const e=n.properties.dataSources?.geometry?.id;if(!e)continue;const s=t.get(e);s&&(r[n.id]=s)}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.data);return e}async setShownEntries(e,t){const r=new Set(e),n=[],s=[];for(const o of this._entries)r.has(o.id)?n.push(o):o._shownAs===t&&s.push(o);for(const o of s)await this._hideEntry(o);for(const o of n)await this._showEntryAs(o,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 n of r)await this._showEntryAs(n,t);return r.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async removeShownEntries(e){const t=e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._shownAs?[t]:[]});for(const r of t)await this._hideEntry(r);return t.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async clearShownEntries(){const e=this._entries.filter(e=>e._shownAs);if(0!==e.length){for(const t of e)await this._hideEntry(t);this.events.emit("shown-change",this.shownEntryIds)}}async clearShown(){await this.clearShownEntries(),await this.clearShownGeometries()}async clearShownGeometries(e){const t=e?e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._modules?.geometries?[t]:[]}):this._entries.filter(e=>e._modules?.geometries);if(0!==t.length)for(const r of t)await r._modules.geometries.clear(),this.events.emit("geometries-change",{entryId:r.id,count:0})}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],n=!!r._shownAs;r._modules&&(await(r._modules.places?.clear()),await(r._modules.geometries?.clear())),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n&&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.data}),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 t of this._entries)t._modules&&(t._modules.places?.clear(),t._modules.geometries?.clear());const e=this._entries.map(e=>e.id);this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},Ce=class{_ttMap;_entries=[];_entryMode="multiple";events=new pe;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}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(w(void 0,t)),r._shownTheme=t):(r._modules.geometries=await i.create(this._ttMap,w(void 0,t)),r._shownTheme=t),r._modules.geometries}async getEntryPlacesModule(e){const t=this._requireEntry(e);return t._modules??={},t._modules.places??=await d.create(this._ttMap),t._modules.places}async markEntryShown(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._modules?.geometries&&await t._modules.geometries.clear(),t._modules?.places&&await t._modules.places.clear(),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RangeState: unknown entry "${e}"`);return t}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}async addEntry(e){const t="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const r=me(`ranges-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:r,timestamp:Date.now(),...e}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...t,r]}),r}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._modules?.geometries?.clear(),t._modules?.places?.clear();this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},Re=class{events=new pe;_status="idle";_error;_intervalHandle;_clearInterval;_recalculate;get status(){return this._status}get error(){return this._error}get isRunning(){return"running"===this._status}get isActive(){return"idle"!==this._status}start(e,t=6e4,r=!1){if("running"===this._status)return;this._status="running",this._error=void 0,this._recalculate=e.recalculate,this._clearInterval=e.clearInterval??(e=>clearInterval(e));const n=e.setInterval??setInterval;r||this._tick(),this._intervalHandle=n(()=>{this._tick()},t)}stop(){"idle"!==this._status&&(this._releaseTimer(),this._status="idle",this._error=void 0,this._recalculate=void 0)}async _tick(){const e=this._recalculate;if(e)try{const t=await e(),r={takenAt:Date.now(),routes: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._recalculate=void 0,this.events.emit("error",e)}_releaseTimer(){null!=this._intervalHandle&&(this._clearInterval?.(this._intervalHandle),this._intervalHandle=void 0),this._clearInterval=void 0}},je=(e,t)=>e.applyConfig({...e.getConfig(),...t}),Be=class{_ttMap;_entries=[];_planningSlots=[];_params={};_entryMode="multiple";_mainColor;_lastShownEntryId;events=new pe;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}async getEntryRoutingModule(e){const t=this._requireEntry(e);return t._module??=await u.create(this._ttMap),t._module}async showEntry(e,t){const r=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 n=await this.getEntryRoutingModule(e),s={};void 0!==this._mainColor&&(s.theme={mainColor:this._mainColor}),!1===t?.showSummaryBubbles&&(s.summaryBubbles={visible:!1}),(s.theme||s.summaryBubbles)&&je(n,s),await n.showRoutes(r.data),!1===t?.showWaypoints?await n.clearWaypoints():r.waypoints.length&&await n.showWaypoints(r.waypoints);const o=r._shown;r._shown=!0,this._lastShownEntryId=e,o||this.events.emit("shown-change",this.shownEntryIds)}get mainColor(){return this._mainColor}setMainColor(e){if(this._mainColor=e,void 0!==e)for(const t of this._entries)t._shown&&t._module&&je(t._module,{theme:{mainColor:e}})}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._module&&(await t._module.clearRoutes(),await t._module.clearWaypoints()),t._shown=!1,this._lastShownEntryId===e&&(this._lastShownEntryId=this._entries.findLast(e=>e._shown)?.id),this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(this.stopMonitoring(e),await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RoutingState: unknown entry "${e}"`);return t}get currentEntryModule(){if(!this._lastShownEntryId)return;const e=this._entries.find(e=>e.id===this._lastShownEntryId);return e?._shown?e._module:void 0}get entries(){return this._entries}get currentRoutes(){return this._entries.at(-1)?.data}get currentWaypoints(){return this._entries.at(-1)?.waypoints}get planningSlots(){return this._planningSlots}get params(){return this._params}async addRoutes(e,t,r){const n="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=me(`routes-${this._entries.length}`,this._entries.map(e=>e.id));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",{entries:this._entries,changedIds:[...n,s]}),this.events.emit("planning-change",this._planningSlots),s}setWaypointAt(e,t){const r=Math.max(2,e+1);for(;this._planningSlots.length<r;)this._planningSlots.push(null);this._planningSlots[e]=t,this.events.emit("planning-change",this._planningSlots)}setParams(e){this._params={...this._params,...e},this.events.emit("params-change",this._params)}async replaceRouteData(e,t,r){const n=this._entries.find(t=>t.id===e);return!!n&&(n._tick=(n._tick??0)+1,n.data=t,n.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n._shown&&n._module&&await n._module.showRoutes(t),!0)}_createMonitorForEntry(e){const t=new Re;return t.events.on("tick",t=>{this.replaceRouteData(e.id,t.routes,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}startMonitoring(e,t,r){const n=this._requireEntry(e),s=n._monitor??=this._createMonitorForEntry(n),o=s.isRunning;s.start(t,r?.intervalMs,r?.skipInitialTick),o||this.events.emit("monitor-start",{entryId:e})}stopMonitoring(e){const t=this._entries.find(t=>t.id===e);if(!t?._monitor)return;const r=t._monitor.isRunning;t._monitor.stop(),r&&this.events.emit("monitor-stop",{entryId:e,reason:"manual"})}isMonitored(e){return this._entries.find(t=>t.id===e)?._monitor?.isRunning??!1}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._monitor?.stop(),t._module?.clearRoutes(),t._module?.clearWaypoints();this._entries=[],this._planningSlots=[],this._params={},this._lastShownEntryId=void 0,this._mainColor=void 0,this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("planning-change",this._planningSlots),this.events.emit("params-change",this._params),this.events.emit("shown-change",this.shownEntryIds)}},Ne=class{jobs=/* @__PURE__ */new Set;wired=!1;scanning=!1;scheduled=!1;pending=/* @__PURE__ */new Set;register(e){const t=new Le(e,this);return this.jobs.add(t),t}drop(e){this.jobs.delete(e)}ensureWired(e){if(this.wired)return;this.wired=!0;const t=t=>this.onChange(e,t.changedIds);for(const r of this.sourceSlices(e))r.events.on("entries-change",t)}sourceSlices(e){return[e.places,e.routing,e.trafficIncidents,e.trafficAreaAnalytics,e.byod,e.customGeometries]}onChange(e,t){this.collectOrphans(e);for(const r of t)this.pending.add(r);this.scheduleScan()}collectOrphans(e){const t=/* @__PURE__ */new Set;for(const n of this.sourceSlices(e))for(const e of n.entries)t.add(e.id);const r=[...this.jobs].filter(e=>!e.affectedEntryIds.some(e=>t.has(e)));for(const n of r)this.jobs.delete(n),n.onOrphaned?.()}hasActiveJob(){for(const e of this.jobs)if(e.active)return!0;return!1}scheduleScan(){this.scanning||this.scheduled||(this.hasActiveJob()?(this.scheduled=!0,queueMicrotask(()=>{this.scheduled=!1,this.runScan()})):this.pending.clear())}async runScan(){const e=this.pending;this.pending=/* @__PURE__ */new Set,this.scanning=!0;try{for(const t of this.jobs)t.active&&t.affectedEntryIds.some(t=>e.has(t))&&await t.run()}finally{this.scanning=!1}}reset(){this.jobs.clear(),this.pending.clear(),this.scanning=!1,this.scheduled=!1}},Le=class{engine;affectedEntryIds;code;active;run;onOrphaned;constructor(e,t){this.engine=t,this.affectedEntryIds=e.affectedEntryIds,this.code=e.code,this.active=e.active,this.run=e.run,this.onOrphaned=e.onOrphaned}setActive(e){this.active=e}unregister(){this.engine.drop(this)}},Fe=e=>`In scope: ${e.map(e=>`\`${e}\``).join(", ")} — injected as named parameters; use them directly. Do NOT redeclare them or read them off \`arguments\`: lines like \`const turf = arguments.turf\` / \`const turf = arguments[0].turf\` / \`const placesByEntry = arguments.placesByEntry\` throw "Identifier '…' has already been declared". The body has no \`arguments\`-based access — reference each name as-is. No \`require\` / \`import\` / \`await import\` (body is an AsyncFunction, not a module — such lines are stripped silently). \`h3\` = h3-js (hex math). \`turf\` = @turf/turf v7 (verify names at https://turfjs.org/docs/). INPUTS are \`<kind>ByEntry\` records keyed by entry id — get a value out first: one entry \`placesByEntry[id]\` (a FeatureCollection) / \`incidentsByEntry[id]\` (an array), or all entries \`Object.values(placesByEntry).flatMap(fc => fc.features)\` / \`Object.values(incidentsByEntry).flat()\`. TURF INPUTS: pass a Feature or FeatureCollection — never a bare Array (throws "Unknown Geometry Type"). FeatureCollection values (\`placesByEntry[id]\`, \`routesByEntry[id]\`, \`trafficAreaAnalyticsByEntry[id]\`, \`byodByEntry[id]\`) → pass directly: \`turf.bbox(placesByEntry[id])\`. Array values (\`incidentsByEntry[id]\`, \`geometriesByEntry[key]\`, \`…features\`, merged \`Object.values(...)\`) → iterate per-feature (\`for (const f of incidentsByEntry[id]) turf.X(f)\`) or wrap with \`turf.featureCollection([...])\`. Never \`.map(f => f.geometry.coordinates)\` / \`.flatMap(f => f.geometry.coordinates)\` to feed turf — that strips the feature wrapper turf reads and throws "coordinates must be an Array" (or worse, mixes Point/LineString shapes silently). Pass the whole feature and let turf read the geometry: \`turf.pointToLineDistance(point, lineFeature, { units: "meters" })\`. \`incidentsByEntry[id]\` mixes Point + LineString features — guard with \`if (inc.geometry.type === "LineString")\` before line-only ops. TURF v7 SET OPS: \`union\`/\`intersect\`/\`difference\` take ONE FeatureCollection — \`turf.difference(turf.featureCollection([outer, inner]))\`. The v6 two-arg form throws. PERF: for large-N nearest/within queries, pre-bucket points with \`h3.latLngToCell(lat, lng, res)\` + \`h3.gridDisk(cell, k)\` and only run turf on same-/neighbour-cell pairs — turns O(N·M) into ~O(N+M). Pick \`res\` so each cell ≈ your query radius. No spatial search or HTTP — call \`discoverPlaces\` BEFORE this tool for new places. Top-level \`await\`/locals/consts fine. End with \`return …;\`.`,qe=/* @__PURE__ */new Set(["bar","line","pie","doughnut","radar","polarArea","scatter","bubble"]),$e=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"string"==typeof t.type&&qe.has(t.type)&&"object"==typeof t.data&&null!==t.data},Ge=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)},Ue=e=>Array.isArray(e)&&e.every(Ge),We='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.',ze=(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})`:""}.`,Ye=(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=He(e);return"error"in r?{error:r.error}:"chart"!==t||$e(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? }).'}},He=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.`}}},Ve=["fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","document","window","globalThis","self","navigator","importScripts","process"],Je=Ve,Ke=["turf","h3","routeUtils","log","cluster"],Xe={run:async(e,t,r,n)=>{try{const n=Object.getPrototypeOf(async()=>{}).constructor,s=Ve.filter(e=>!t.includes(e)),o=r.map((e,r)=>((e,t)=>void 0===e||Ke.includes(t)?e:structuredClone(e))(e,t[r]));return{value:await new n(...t,...s,e)(...o,...s.map(()=>{}))}}catch(s){return{error:tt(n,s)}}}},Ze=(e,t,r,n,s)=>s.run(Qe(e,t),t,r,n),Qe=(e,t)=>{const r=t.join("|"),n=String.raw`(?:(?:await\s+)?(?:require|import)\s*\(`+String.raw`|arguments\s*(?:\[\s*\d+\s*\])?\s*(?:\.\w+|\[\s*['"]\w+['"]\s*\]))`,s=new RegExp(String.raw`^[ \t]*(?:const|let|var)[ \t]+(?:${r})[ \t]*=[ \t]*${n}[^\n]*\n?`,"gm");return e.replace(s,"")},et=[{pattern:/Cannot read propert(?:y|ies) of undefined/i,hint:"A property was read on `undefined`. Likely a `reduce` callback that does not `return` the accumulator, or a `reduce` called without an initial value — use `arr.reduce((acc, x) => { ...; return acc; }, {})`. Also check unguarded chains like `feature.properties.poi.categories` — use optional chaining (`?.`) and `??` defaults."},{pattern:/Cannot read propert(?:y|ies) of null/i,hint:"A property was read on `null`. Guard property access with `?.` and provide defaults with `??`."},{pattern:/\bh3\.\w+ is not a function/i,hint:"`h3` is [h3-js](https://github.com/uber/h3-js) — hex-grid math only. NO spatial search / place lookup / HTTP. Common: `latLngToCell`, `cellToLatLng`, `cellToBoundary`, `polygonToCells`, `gridDisk`, `cellArea`. For nearby places call `discoverPlaces` BEFORE this tool — sandbox cannot call other tools."},{pattern:/\bturf\.\w+ is not a function/i,hint:"`turf` is [@turf/turf](https://turfjs.org/) — GeoJSON geometry only. NO spatial search / place lookup / HTTP. Common: `area`, `length`, `bbox`, `centroid`, `union`, `intersect`, `buffer`, `distance`, `booleanPointInPolygon`, `pointsWithinPolygon`, `clustersDbscan`, `convex`. Verify names at https://turfjs.org/docs/. For nearby places call `discoverPlaces` BEFORE this tool."},{pattern:/coordinates must be an Array|Unknown Geometry Type/i,hint:'turf was handed a raw coordinate (or a bare Array of features) instead of a Feature / FeatureCollection. Do NOT extract `.geometry.coordinates` with `.map` / `.flatMap` and feed the result to turf — pass the WHOLE feature: `turf.pointToLineDistance(pointFeature, lineFeature, { units: "meters" })`. `incidentsByEntry[id]` is an Array of mixed Point + LineString features; guard with `if (inc.geometry.type === "LineString")` before line-only ops. A single entry of an FC kind (`placesByEntry[id]`, `routesByEntry[id]`, `trafficAreaAnalyticsByEntry[id]`, `byodByEntry[id]`) is a FeatureCollection — pass it directly (`turf.bbox(placesByEntry[id])`), never `turf.bbox(placesByEntry[id].features)`. To span entries, merge then wrap: `turf.bbox(turf.featureCollection(Object.values(placesByEntry).flatMap(fc => fc.features)))`.'},{pattern:/coord must be (?:a )?(?:GeoJSON )?Point|must be a Point/i,hint:'A point-only turf op (`booleanPointInPolygon`, `pointsWithinPolygon`, `getCoord`, `distance`, …) was handed a non-Point feature. `incidentsByEntry[id]` and `byodByEntry[id]` mix Point + LineString + Polygon geometries, so `feature` may be a LineString. To test whether ANY feature falls inside a polygon regardless of its geometry type, use `turf.booleanIntersects(feature, polygon)` — it accepts Point / LineString / Polygon. Only reach for `turf.booleanPointInPolygon(pt, polygon)` after guarding `if (feature.geometry.type === "Point")`, or reduce a line/area to one representative point first with `turf.pointOnFeature(feature)` (guaranteed on the feature) or `turf.centroid(feature)`.'},{pattern:/is not a function/i,hint:"A method was called on a value that does not have it (typo, or the value is not the expected type). Verify the type before calling, e.g. `Array.isArray(x) ? x.map(...) : []`."},{pattern:/Illegal return statement/i,hint:"A `return` was placed outside the top-level function body — usually a missing brace or a callback that the parser thinks closed too early. Common cause: a multi-line `const X = arr.map((item) => { ... })` whose closing `})` was lost. Re-emit the code with balanced braces, keep `return` only at the top level of the body, and try again."},{pattern:/Identifier ['"]?\w+['"]? has already been declared/i,hint:"A variable was declared twice — typically because the code prepends `const turf = require(...)` / `const turf = await import(...)` / `const turf = arguments.turf` / `const turf = arguments[0].turf` / `const placesByEntry = ...`. Those names are already in scope as function parameters; drop the redeclaration line and use them directly (`turf.bbox(placesByEntry[id])`)."},{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 — `placesByEntry`/`geometriesByEntry`/`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 (`placesByEntry`, `geometriesByEntry`, …) are already injected as parameters; drop the import lines and use them directly."},{pattern:/\b(?:functions|tools|agent|recallState|discoverPlaces|locatePlace) is not defined\b/i,hint:"The sandbox cannot call other tools — `functions.recallState(...)`, `tools.discoverPlaces(...)`, etc. are NOT available. To use additional entries, list every needed id in the tool inputs (`placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`): each is exposed via the injected per-entry sandbox args (`placesByEntry`, `routesByEntry`, `incidentsByEntry`, `geometriesByEntry`), keyed by the ids you passed. 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."}],tt=(e,t)=>{const r=t instanceof Error?t.message:String(t),n=`${e} code execution failed: ${r}`,s=et.find(({pattern:e})=>e.test(r));return s?`${n} Hint: ${s.hint}`:t instanceof Error&&"SyntaxError"===t.name?`${n} Hint: the provided \`code\` is not valid JavaScript — ensure it parses as an async function body and \`return\`s a value.`:n},rt=`\n(function () {\n var SHADOWS = ${JSON.stringify([...Je])};\n var AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;\n var provided = {\n turf: function () { return self.turf; },\n h3: function () { return self.h3; },\n routeUtils: function () { return self.routeUtils; },\n cluster: function () { return self.cluster; },\n log: function () { return function () {}; },\n };\n self.onmessage = function (event) {\n var msg = event.data;\n if (!msg || msg.type !== 'run') return;\n var id = msg.id;\n try {\n var paramNames = msg.paramNames || [];\n var dataByIndex = msg.dataByIndex || {};\n var shadows = SHADOWS.filter(function (n) { return paramNames.indexOf(n) === -1; });\n var argValues = paramNames.map(function (name, index) {\n return Object.prototype.hasOwnProperty.call(provided, name) ? provided[name]() : dataByIndex[index];\n });\n var fn = AsyncFunction.apply(null, paramNames.concat(shadows).concat([msg.code]));\n Promise.resolve(fn.apply(null, argValues.concat(shadows.map(function () { return undefined; }))))\n .then(function (value) {\n try {\n self.postMessage({ type: 'result', id: id, ok: true, value: value });\n } catch (postError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorMessage: String((postError && postError.message) || postError) });\n }\n })\n .catch(function (runError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorMessage: String((runError && runError.message) || runError) });\n });\n } catch (compileError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorMessage: String((compileError && compileError.message) || compileError) });\n }\n };\n self.postMessage({ type: 'ready' });\n})();\n`,nt=()=>"undefined"!=typeof document&&"undefined"!=typeof Worker&&"undefined"!=typeof URL&&"undefined"!=typeof Blob,st=()=>import("./worker-libs-C794xj9c.js").then(e=>e.sandboxLibrarySource),ot=e=>{let t=null,r=null,n=!1,s=!1,o=1;const a=/* @__PURE__ */new Map,i=e=>{s||(s=!0,console.warn(`[agent-toolkit] iframe-worker sandbox unavailable (${e}); falling back to main-thread execution (NOT isolated).`))},l=e=>{t?.contentWindow?.postMessage(e,"*")},c=e=>{if(e.source!==t?.contentWindow)return;const r=e.data;if("worker-error"===r?.type){const e=r.errorMessage??"worker error";for(const[t,r]of a)clearTimeout(r.timer),r.resolve({error:tt(r.verb,e)}),a.delete(t);return}if("result"!==r?.type)return;const n=r,s=a.get(n.id);s&&(a.delete(n.id),clearTimeout(s.timer),s.resolve(n.ok?{value:n.value}:{error:tt(s.verb,n.errorMessage)}))},d=e=>new Promise((r,n)=>{const s=setTimeout(()=>{globalThis.removeEventListener("message",o),n(/* @__PURE__ */new Error(`timed out waiting for "${e}"`))},2e3),o=n=>{n.source===t?.contentWindow&&n.data?.type===e&&(clearTimeout(s),globalThis.removeEventListener("message",o),r())};globalThis.addEventListener("message",o)}),u=async()=>{if(!nt())return i("browser APIs missing"),!1;if(!e.loadLibrarySource)return i("no worker library source configured (codeExecution.loadWorkerLibrarySource)"),!1;try{const r=document.createElement("iframe");r.setAttribute("sandbox","allow-scripts"),r.setAttribute("aria-hidden","true"),r.style.display="none",r.srcdoc="<!doctype html><html><head><meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob:; worker-src blob:\"></head><body><script>\n(function () {\n var worker = null, workerSource = null;\n function spawn() {\n var blob = new Blob([workerSource], { type: 'application/javascript' });\n worker = new Worker(URL.createObjectURL(blob));\n worker.onmessage = function (e) { parent.postMessage(e.data, '*'); };\n // A worker-level error (e.g. the library source or AsyncFunction eval throwing on\n // load) isn't tied to one run id — relay it as a distinct signal so the parent can\n // fail the in-flight calls with a real message instead of letting them time out.\n worker.onerror = function (e) {\n var detail = e && e.message ? (e.message + (e.filename ? ' @ ' + e.filename + ':' + e.lineno : '')) : String(e);\n parent.postMessage({ type: 'worker-error', errorMessage: detail }, '*');\n };\n }\n window.addEventListener('message', function (ev) {\n var msg = ev.data || {};\n if (msg.type === 'init') { workerSource = msg.workerSource; spawn(); parent.postMessage({ type: 'iframe-ready' }, '*'); return; }\n if (msg.type === 'run' && worker) { worker.postMessage(msg); return; }\n if (msg.type === 'terminate' && worker) { try { worker.terminate(); } catch (_e) {} spawn(); return; }\n });\n parent.postMessage({ type: 'bootstrap-ready' }, '*');\n})();\n<\/script></body></html>",t=r;const n=d("bootstrap-ready");document.body.appendChild(r),await n;const s=(e=>`${e}\n;${rt}`)(await e.loadLibrarySource()),o=d("iframe-ready");return l({type:"init",workerSource:s}),await o,globalThis.addEventListener("message",c),!0}catch(r){return i(r instanceof Error?r.message:String(r)),t?.remove(),t=null,!1}};return{run:async(s,i,c,d)=>{if(!(await(n?Promise.resolve(!1):(r??=u().then(e=>(e||(n=!0),e)),r)))||!t)return Xe.run(s,i,c,d);const p=o++,{dataByIndex:h}=((e,t)=>{const r=new Set(Ke),n={};for(let s=0;s<e.length;s++)r.has(e[s])||(n[s]=t[s]);return{dataByIndex:n}})(i,c);return new Promise(t=>{const r=setTimeout(()=>{a.delete(p),l({type:"terminate"}),t({error:tt(d,/* @__PURE__ */new Error(`code execution exceeded the ${e.timeoutMs}ms sandbox timeout and was terminated`))})},e.timeoutMs);a.set(p,{resolve:t,verb:d,timer:r});try{const e={type:"run",id:p,code:s,paramNames:[...i],dataByIndex:h};l(e)}catch(n){clearTimeout(r),a.delete(p),t({error:tt(d,n)})}})},destroy:()=>{void 0!==globalThis.window&&globalThis.removeEventListener("message",c);for(const e of a.values())clearTimeout(e.timer);a.clear(),t?.remove(),t=null}}},at=e=>nt()?ot({timeoutMs:e?.timeoutMs??1e4,loadLibrarySource:e?.loadWorkerLibrarySource??st}):Xe,it=e=>null===e?"null":Array.isArray(e)?"array":typeof e,lt=(e,t)=>{if(!(e.examples.length>=3)){if("string"==typeof t){const r=t.length>60?`${t.slice(0,60)}…`:t;return void(e.examples.includes(r)||e.examples.push(r))}"number"!=typeof t&&"boolean"!=typeof t||e.examples.includes(t)||e.examples.push(t)}},ct=e=>{const t=/* @__PURE__ */new Set;for(const r of e.features){const e=r.geometry?.type;e&&t.add(e)}return[...t]},dt=e=>{const t=/* @__PURE__ */new Map;for(const r of e.features){const e=r.properties;if(e&&"object"==typeof e)for(const[r,n]of Object.entries(e)){let e=t.get(r);e||(e={count:0,types:/* @__PURE__ */new Set,examples:[]},t.set(r,e)),e.count+=1,e.types.add(it(n)),lt(e,n)}}return t},ut=e=>{const t=e.features.length,r=[...dt(e).entries()].map(([e,r])=>({name:e,types:[...r.types],coverage:t>0?Math.round(r.count/t*100)/100:0,examples:r.examples})).sort((e,t)=>t.coverage-e.coverage||e.name.localeCompare(t.name)),n=r.slice(0,40),s=r.length-n.length;return{featureCount:t,geometryTypes:ct(e),properties:n,...s>0&&{propertiesOmitted:s}}},pt=e=>({...e,properties:e.properties.map(e=>({...e,examples:e.examples.filter(e=>"string"!=typeof e)}))}),ht=class{_ttMap;_entries=[];_entryMode="multiple";events=new pe;sourceUrlValidator;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r={}){const n="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=me(r.explicitId??`byod-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:s,timestamp:Date.now(),label:t,data:e,source:r.source??{kind:"integrator"},layers:r.layers??[],profile:ut(e)}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...n,s]}),s}setEntryLayers(e,t){const r=this._requireEntry(e);r.layers=t,r._module?.applyConfig({sources:{data:{layers:t}}}),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await a.create(this._ttMap,{sources:{data:{layers:t.layers}}}),t._module}async showEntry(e){const t=this._requireEntry(e);if(0===t.layers.length)return;await this._hideOthersUnderSingleMode(e),await(await this.getEntryModule(e)).show(t.data,"data");const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async _hideOthersUnderSingleMode(e){if("single"!==this._entryMode)return;const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const r of t)await this.hideEntry(r.id)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`BYODState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._module?.clear().catch(()=>{});this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},yt=class{_trackers=/* @__PURE__ */new Map;_log=[];_jobs=/* @__PURE__ */new Map;_seq=0;events=new pe;register(e){this._trackers.set(e.id,e),this.emitTrackers()}setJob(e,t){this._jobs.set(e,t)}unregister(e){this._trackers.has(e)&&(this._jobs.get(e)?.unregister(),this._jobs.delete(e),this._trackers.delete(e),this.emitTrackers())}setEnabled(e,t){const r=this._trackers.get(e);return!!r&&(r.enabled=t,this._jobs.get(e)?.setActive(t),this.emitTrackers(),!0)}get trackers(){return[...this._trackers.values()].map(e=>({...e}))}get(e){const t=this._trackers.get(e);return t&&{...t}}trackersForEntry(e){return this.trackers.filter(t=>t.affectedEntryIds.includes(e))}log(e={}){return this._log.filter(t=>(void 0===e.trackerId||t.trackerId===e.trackerId)&&(void 0===e.sinceMs||t.at>=e.sinceMs)).map(e=>structuredClone(e))}reset(){for(const e of this._jobs.values())e.unregister();this._jobs.clear(),this._trackers.clear(),this._log.length=0,this._seq=0}ingestVerdict(e,t,r){const n=this._trackers.get(e);if(n?.enabled&&mt(t))if(t.active&&!n.wasActive)n.wasActive=!0,n.openedAt=r,this.append(n,{type:"alert",kind:"opened",verdict:t,at:r});else if(!t.active&&n.wasActive){const e=n.openedAt;n.wasActive=!1,n.openedAt=void 0,this.append(n,{type:"event",kind:"resolved",verdict:t,at:r,openedAt:e})}}append(e,t){const r={id:`${e.id}:${t.kind}:${this._seq++}`,at:t.at,trackerId:e.id,trackerName:e.name,type:t.type,kind:t.kind,summary:t.verdict.summary,members:t.verdict.members.map(e=>({entryId:e.entryId,featureIds:[...e.featureIds]})),...void 0!==t.openedAt&&{openedAt:t.openedAt}};this._log.push(r);const n=this._log.filter(t=>t.trackerId===e.id);n.length>200&&this._log.splice(this._log.indexOf(n[0]),1),this.events.emit("tracker-event",r),this.emitTrackers()}emitTrackers(){this.events.emit("trackers-change",this.trackers)}};function mt(e){if("object"!=typeof e||null===e)return!1;const t=e;return"boolean"==typeof t.active&&"string"==typeof t.summary&&Array.isArray(t.members)}var gt=class{_ttMap;_entries=[];_entryMode="multiple";events=new pe;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r,n){const s="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const o=`tta-${this._entries.length}`,a=me(n??o,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:t,data:e,params:r}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...s,a]}),a}async getEntryModule(e){const t=this._requireEntry(e);return t._module||(t._module=await h.create(this._ttMap),t._configChangeUnsub=t._module.events.on("config-change",r=>{t._config=r,this.events.emit("config-change",{entryId:e,config:r})})),t._module}async showEntry(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=await this.getEntryModule(e);await r.clear(),await r.show(t.data);const n=t._shown;t._shown=!0,n||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;await this.hideEntry(e);const r=this._entries[t];r._configChangeUnsub?.(),r._configChangeUnsub=void 0,r._module=void 0,this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficAreaAnalyticsState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._configChangeUnsub?.(),t._module?.clear();this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},ft=["roadworks","road-closed"];function bt(e){const t=e.properties.id;if("Point"===e.geometry.type)return se.point(e.geometry.coordinates,{_incidentId:t});const r=se.centroid(e);return r.properties={_incidentId:t},r}function wt(e){const t=/* @__PURE__ */new Map;for(const r of e)for(const e of r.properties.roadNumbers??[])t.set(e,(t.get(e)??0)+1);return[...t.entries()].sort((e,t)=>t[1]-e[1]).map(([e])=>e)}function vt(e){const t=/* @__PURE__ */new Map;for(const r of e){const e=r.properties.category;e&&t.set(e,(t.get(e)??0)+1)}return[...t.entries()].sort((e,t)=>t[1]-e[1])[0]?.[0]??"incident"}function It(e,t,r,n,s){if(0===e.length)return{groups:[]};const o=function(e,t,r){if(0===r)return e;const n=new Set(t);return e.filter(e=>{const t=e.properties.category,s=e.properties.delayInSeconds??0;return!(n.has(t)&&s<r)})}(e,t.preFilter?.excludeCategories??ft,t.preFilter?.minDelaySeconds??1);if(0===o.length)return{groups:[]};const a=/* @__PURE__ */new Map;for(const w of o)a.set(w.properties.id,w);const i=o.map(bt),l=se.featureCollection(i),c=t.eps??.5,d=t.minMembers??3,u=se.clustersDbscan(l,c,{minPoints:d}),p=/* @__PURE__ */new Map;for(const w of u.features){const e=w.properties.cluster;if(void 0===e||e<0)continue;let t=p.get(e);t||(t=[],p.set(e,t)),t.push(w)}const h=t.maxClusters??6,y=[];for(const[,w]of p){const e=w.map(e=>e.properties._incidentId),t=e.map(e=>a.get(e)).filter(e=>void 0!==e),r=t.reduce((e,t)=>e+(t.properties.delayInSeconds??0),0),n=t.reduce((e,t)=>Math.max(e,t.properties.delayInSeconds??0),0),s=se.featureCollection(w),o=se.centroid(s).geometry.coordinates;let i=0;if(w.length>=2){const e=se.bbox(s);i=se.distance([e[0],e[1]],[e[2],e[3]])}const l=wt(t);y.push({centroid:o,memberIds:e,size:t.length,totalDelaySeconds:r,peakDelaySeconds:n,diameterKm:i,primaryRoads:l,primaryCategory:vt(t)})}y.sort((e,t)=>t.totalDelaySeconds-e.totalDelaySeconds);const m=y.slice(0,h),g=r?function(e,t){const r=t.map(e=>new Set(e.memberIds)),n=[];for(let i=0;i<e.length;i++){const s=new Set(e[i].memberIds);for(let e=0;e<t.length;e++){const t=r[e];let o=0;for(const e of s)t.has(e)&&o++;const a=s.size+t.size-o,l=0===a?0:o/a;l>=.5&&n.push({freshIdx:i,prevIdx:e,overlap:l})}}n.sort((e,t)=>t.overlap-e.overlap);const s=/* @__PURE__ */new Map,o=/* @__PURE__ */new Set,a=/* @__PURE__ */new Set;for(const{freshIdx:i,prevIdx:l,overlap:c}of n)o.has(i)||a.has(l)||(s.set(i,t[l].id),o.add(i),a.add(l));return s}(m,r):/* @__PURE__ */new Map,f=/* @__PURE__ */new Map;if(r)for(const w of r)f.set(w.id,w);const b=void 0===n||void 0===s||n>s;return{groups:m.map((e,t)=>{const r=g.get(t),n=r??`cluster-${e.centroid[0].toFixed(3)}-${e.centroid[1].toFixed(3)}-${[...e.memberIds].sort()[0]??t}`,s=r?f.get(r):void 0,o=s?.delaySamples??[],a=s?(b?[...o,e.totalDelaySeconds]:[...o.slice(0,-1),e.totalDelaySeconds]).slice(-3):[e.totalDelaySeconds],i=1===a.length?"new":function(e){if(e.length<3)return"steady";const[t,r,n]=e.slice(-3),s=(n-t)/Math.max(t,1);return r>t&&n>r&&s>.1?"growing":r<t&&n<r&&s<-.1?"fading":"steady"}(a);return{id:n,centroid:e.centroid,memberIds:e.memberIds,size:e.size,totalDelaySeconds:e.totalDelaySeconds,peakDelaySeconds:e.peakDelaySeconds,diameterKm:e.diameterKm,primaryRoads:e.primaryRoads,primaryCategory:e.primaryCategory,trend:i,delaySamples:a}})}}var Et=(e,t={},r,n)=>{const s=(e=>{if(null!=e)return e instanceof Date?e.getTime():e})(n),{groups:o}=It(e,t,r?.groups,s,r?.sampledAt);return void 0===s?{groups:o}:{groups:o,sampledAt:s}},St=class{_bbox;events=new pe;_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,n=!1){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;n||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}},Mt=class{_ttMap;_entries=[];_entryMode="multiple";_clusters=/* @__PURE__ */new Map;events=new pe;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.slice(0,-1).map(e=>e.id);this._entries=await fe(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}this.events.emit("mode-change",e)}}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}async addIncidentsEntry(e,t,r,n,s){"single"===this._entryMode&&this._entries.length>0&&(await ge(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this._entries=[]);const o=me(s??`incidents-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:o,timestamp:n,label:r,data:e,params:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[o]}),o}async replaceEntryData(e,t,r){const n=this._entries.find(t=>t.id===e);if(!n)return!1;const s=(n._tick??0)+1;if(n._tick=s,n.data=t,n.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n._module){if(await n._module.show({type:"FeatureCollection",features:t}),n._tick!==s)return!0;if(n._focus){const e=new Set(t.map(e=>e.properties.id)),r=[...n._focus.ids].filter(t=>e.has(t));n._module.setFocus(r.length>0?r:null),0===r.length&&(n._focus=void 0,this.events.emit("focus-change",{entryId:n.id,focus:null}))}}return!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await m.create(this._ttMap),t._module}async showEntry(e,t=!0){const r=this._requireEntry(e),n=await this.getEntryModule(e);await n.show({type:"FeatureCollection",features:r.data}),n.setVisible(!0),t&&this._ttMap.mapLibreMap.fitBounds(r.params.bbox,{padding:50,maxZoom:14});const s=!!r._shown;r._shown=!0,s||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._focus&&(t._focus=void 0,this.events.emit("focus-change",{entryId:t.id,focus:null})),t._module?.setVisible(!1),await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],n=!!r._shown;await this._teardownEntry(r),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n&&this.events.emit("shown-change",this.shownEntryIds)}setClusters(e,t){this._entries.some(t=>t.id===e)&&(this._clusters.set(e,t),this.events.emit("clusters-change",{entryId:e,clusters:t.output}))}getClusters(e){return this._clusters.get(e)?.output}getClustersRecord(e){return this._clusters.get(e)}clearClusters(e){this._clusters.delete(e)&&this.events.emit("clusters-change",{entryId:e,clusters:null})}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 n=this._entries.find(t=>t.id===e);if(!n)return{focusedCount:0,droppedIds:[...t]};if(0===t.length)return this.clearFocus(e),{focusedCount:0,droppedIds:[]};const s=new Set(n.data.map(e=>e.properties.id)),o=[],a=[];for(const i of t)s.has(i)?o.push(i):a.push(i);return 0===o.length?{focusedCount:0,droppedIds:a}:(n._focus={ids:new Set(o),reason:r},n._module?.setFocus(o),this.events.emit("focus-change",{entryId:n.id,focus:this.getFocus(e)}),{focusedCount:o.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 s of t.data){const e=s.properties.id;"string"==typeof e&&(r[e]=s)}const n=[];for(const s of t._focus.ids){const e=r[s];e&&n.push(e)}return n}navigateFocus(e,t){const r=this.focusedFeatures(e);if(0===r.length)return;const n=r[(t%r.length+r.length)%r.length],s=this._ttMap.mapLibreMap;if("Point"===n.geometry.type)return void s.flyTo({center:n.geometry.coordinates,zoom:15,duration:600});const o=W(n);o&&s.fitBounds(o,{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,n){const s=this._requireEntry(e),o=s._monitor??=this._createMonitorForEntry(s),a=o.isRunning;o.start(t,r,n?.intervalMs,n?.skipInitialTick),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 St(e.params.bbox);return t.events.on("tick",t=>{this.replaceEntryData(e.id,t.incidents,t.takenAt),this.events.emit("monitor-tick",{entryId:e.id,snap:t})}),t.events.on("error",t=>{this.events.emit("monitor-error",{entryId:e.id,error:t}),this.events.emit("monitor-stop",{entryId:e.id,reason:"error"})}),t}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficIncidentsState: unknown entry "${e}"`);return t}async _teardownEntry(e){this.clearClusters(e.id),this.stopMonitoring(e.id),e._focus&&(e._focus=void 0,this.events.emit("focus-change",{entryId:e.id,focus:null})),e._shown&&(e._shown=!1),e._module&&(e._module.setVisible(!1),await e._module.clear()),e._module=void 0}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const r of this._entries)this._teardownEntry(r);const t=this._entries.length>0;this._entries=[],t&&(this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)),this.events.clear()}},kt=class{_ttMap;_trafficFlowModule;_trafficIncidentsModule;constructor(e){this._ttMap=e}async getTrafficFlowModule(){return this._trafficFlowModule=await y.get(this._ttMap),this._trafficFlowModule}async getTrafficIncidentsModule(){return this._trafficIncidentsModule=await g.get(this._ttMap),this._trafficIncidentsModule}get trafficFlowModule(){return this._trafficFlowModule}get trafficIncidentsModule(){return this._trafficIncidentsModule}reset(){this._trafficFlowModule=void 0,this._trafficIncidentsModule=void 0}};q.instance.put({language:"en-GB"});var xt={places:"places",routes:"routing",incidents:"trafficIncidents",customGeometries:"customGeometries",trafficAreaAnalytics:"trafficAreaAnalytics",byod:"byod",ranges:"ranges"},At=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),Dt=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),Tt={identity:null,capabilities:null,rejectionRules:"SCOPE & REJECTIONS",responseFormatting:"RESPONSE FORMATTING",dataConfidence:"DATA CONFIDENCE",toolExecution:"TOOL EXECUTION",sessionState:"SESSION STATE"},Pt={identity:"You are a helpful map assistant.",capabilities:"You operate a TomTom interactive map with location services. You can search places, POIs, and addresses (and reverse-geocode); plan and edit multi-stop routes (alternatives, cost models); compute reachable-area (isochrone) ranges; load live traffic incidents and historical traffic analytics, then cluster, monitor, or focus them; run spatial analysis, aggregations, and charts over loaded data and derive geometries; add your own GeoJSON layers; and control the map — pan, zoom, pitch/bearing, styles, base-map tiles, and show/hide results.",rejectionRules:"- Handle the in-scope part fully, then note what you can't help with.\n- Fully out of scope: decline and describe what you cover — don't guess the user's need.\n- Flag scope creep as it arises, not at the end.\n- Reject illegal requests outright with a brief reason and no detail — no partial help, rephrasing, or redirecting.\n- Every rejection states why and offers an in-scope next step — never a dead end.",responseFormatting:"- Use markdown; bold key info; bullet lists for multiple items.\n- Be concise — lead with the most relevant information.",dataConfidence:"- Return incomplete, outdated, or low-coverage data with a clear note on what's limited and why — never present it as complete.\n- If only partial, name what's missing and offer to proceed — let the user decide.\n- If signals conflict, acknowledge it, state what you used, and why.",toolExecution:'- Run independent tools in the same step (parallel); render results with the tool\'s own show / showOnMap in that step, not a follow-up call.\n- Ground every fact and effect in a tool, not your training data: anything a tool can supply or do (an address, a count, a category, a list, any map change) is real ONLY when you call that tool — even when the request reads like a general or explanatory question ("what categories represent X?"). Without the call, never reply as if a result exists, the map changed, or an action is underway ("here are the results", "Profiling X…", "the panel will show…"); the call IS the action, so make it.\n- The ONE exception is ANSWERING from what you already have: you may reply in prose, with no tool call, from what is ALREADY in this conversation (earlier tool results, session state, what the user told you) or about your own capabilities ("what can you do?"). Using what you have is fine; inventing what a tool would return is not.\n- Needing MORE input is not that exception: for a clear, complete request, act with sensible defaults (do not ask); when input is missing, implied, or ambiguous, your ONLY way to ask the user is to CALL clarifyIntent. A plain-text request for the details — "I need…", "please provide…", "could you tell me…", or a bare list of questions — without that call does NOT count; call clarifyIntent (it then states whether to also write the questions as text).',sessionState:"- Results are stored as stable, append-only entries (e.g. `places-3`, `routes-1`) that every show / recall / process / analyse tool accepts. Call recallState to see what's loaded; always check it before referencing an entry id — never invent ids."},_t=["identity","capabilities","rejectionRules","responseFormatting","dataConfidence","toolExecution","sessionState"],Ot=e=>_t.map(t=>({key:t,body:e?.[t]??Pt[t]})).filter(({body:e})=>e.trim().length>0).map(({key:e,body:t})=>((e,t)=>{const r=Tt[e];return r?`${r}:\n${t}`:t})(e,t)).join("\n\n"),Ct=Ot(),Rt=({customPrompt:e,prefix:t,suffix:r}={})=>{if("string"==typeof e)return e;const n=e?Ot(e):Ct,s=r?`${n}\n\nADDITIONAL INSTRUCTIONS:\n${r}`:n;return t?`${t}\n\n${s}`:s},jt=oe.object({error:oe.string()}),Bt=oe.object({featureCount:oe.number(),geometryTypes:oe.array(oe.string()).describe("Geometry types present (Point / LineString / Polygon / …)."),properties:oe.array(oe.object({name:oe.string(),types:oe.array(oe.string()).describe('Distinct JSON value types seen for this key (e.g. ["string"], ["number","null"]).'),coverage:oe.number().describe("Fraction of features carrying this key (1 = every feature)."),examples:oe.array(oe.union([oe.string(),oe.number(),oe.boolean()])).describe("A few example NUMERIC / BOOLEAN values for inferring semantics. String values are withheld from results (customer-supplied free text — a prompt-injection vector); use `types` to know a property is a string.")})).describe("Per-property profile, highest-coverage first."),propertiesOmitted:oe.number().optional().describe("Count of property keys omitted when the data has more distinct keys than the cap.")}),Nt=oe.array(oe.object({query:oe.string().optional().describe("Your input query text, echoed."),matched:oe.string().describe("The GROUNDED place the data was actually loaded for.")})).describe('Where each named `where` query ACTUALLY resolved. `matched` is the grounded geocoded place the data was loaded for — NOT your query text. ALWAYS check it is the place the operator meant: a same-name place elsewhere ("east London" → "London, CA") silently loads the wrong area. If `matched` is wrong, re-issue with a more specific query. Omitted for raw bounding boxes / geometries (nothing was geocoded).'),Lt={id:oe.string().describe("Stable place feature ID; pass as `{ placeIdOrEntryId }` to setRoute / addWaypointsToRoute / etc."),type:oe.enum(ee).optional().describe("Place classification (POI, Street, Geography, Cross Street, …), when known."),name:oe.string().optional().describe("POI name if available"),address:oe.string().optional().describe("Human-readable address"),position:oe.array(oe.number()).length(2).describe("[longitude, latitude]")},Ft={areaId:oe.string().optional().describe('Id of the small area polygon this place sits in (few km², not a whole municipality). Round-trip into `discoverPlaces.where.areaId` for "what else is in this same area?". Only set on explorationSearch results in DE / NL / FR.'),areaCountry:oe.string().optional().describe("ISO 3166-1 alpha-2 country code of the surrounding area polygon."),areaTags:oe.array(oe.string()).optional().describe('Tokens describing the surrounding small area (few km², not a whole municipality) (e.g. "coastal", "walkable", "transit_connected"). Useful for downstream `areaTags` filters.')},qt=e=>oe.object(e.experimentalSearch?{...Lt,...Ft}:Lt).describe("Compact place summary"),$t=e=>oe.object({count:oe.number(),features:oe.array(qt(e))}).describe("Compact places summary"),Gt=qt({}),Ut=($t({}),oe.object({travelTimeInSeconds:oe.number(),lengthInMeters:oe.number(),trafficDelayInSeconds:oe.number().optional(),trafficLengthInMeters:oe.number().optional(),departureTime:oe.string().optional(),arrivalTime:oe.string().optional(),noTrafficTravelTimeInSeconds:oe.number().optional(),historicTrafficTravelTimeInSeconds:oe.number().optional(),liveTrafficIncidentsTravelTimeInSeconds:oe.number().optional()})),Wt=oe.object({index:oe.number().describe("0=best, 1+=alternative"),summary:Ut,legSummaries:oe.array(Ut).optional().describe("Present when route has multiple legs")}),zt=oe.object({count:oe.number(),routes:oe.array(Wt)}),Yt=zt.extend({entryId:oe.string().describe("Routing entry id the route was written to — pass as `routesEntryIDs` to analyseData / processData.")}),Ht=oe.object({geoJson:oe.union([oe.record(oe.string(),oe.unknown()),oe.array(oe.record(oe.string(),oe.unknown()))]).describe("GeoJSON object or array of GeoJSON objects")}),Vt=oe.union([oe.object({bbox:oe.array(oe.number()).length(4).describe("[minLng, minLat, maxLng, maxLat]"),minLng:oe.number(),minLat:oe.number(),maxLng:oe.number(),maxLat:oe.number()}),jt]),Jt=oe.object({id:oe.string().describe('Short stable key for the answer, e.g. "travelMode" or "radius".'),question:oe.string().describe("The question to put to the user."),options:oe.array(oe.string()).min(2).max(8).describe('Concrete suggestions, shown as numbered rows the user taps to answer. A freeform "Something else" input is always shown too, so do NOT add your own "other"/"something else" option — just offer your best concrete suggestions.'),multiSelect:oe.boolean().default(!1).describe('Allow more than one answer: tapping toggles options (checked) and the user confirms with the send button — use for lists like "which place types matter". Default false = single-select, where tapping an option answers and advances immediately. Each question carries its own flag, so a single call may freely mix single- and multi-select questions.')}),Kt=oe.object({intro:oe.string().optional().describe("One short line of context for why you need to ask."),questions:oe.array(Jt).min(1).max(4).describe("1-4 questions that resolve the ambiguity.")}),Xt=oe.object({status:oe.literal("awaiting_user_response"),intro:oe.string().optional(),questions:oe.array(Jt)}),Zt='- When a request is missing inputs you need to act on, do NOT call the action tool with guessed or empty inputs, and do NOT ask for the missing details in prose — call clarifyIntent FIRST to collect them, then STOP and wait. Asking in a sentence when you could offer a form is the most common mistake; default to the form.\n- Any question whose answer is a choice among a few concrete options IS a clarifyIntent form, even just two or three. If you catch yourself about to ask "A, B, or C?" in text, call clarifyIntent with those as options instead.\n- A clarifyIntent call must be the ENTIRE turn: one tool call with EMPTY text content — no preamble, and NEVER restate the questions or options in the message (the form already shows them). Put any one-line context in the form\'s `intro` field, and give each question a few concrete options.\n- One call can carry several related questions (the form shows them one at a time). Fall back to a prose question ONLY when the answer is genuinely free-form with no suggestable options.',Qt='- When a request is missing inputs you need to act on, do NOT call the action tool with guessed or empty inputs — call clarifyIntent FIRST to collect them, then STOP and wait for the answer.\n- Any question whose answer is a choice among a few concrete options belongs in clarifyIntent, even just two or three; offer your best concrete suggestions as its options.\n- After calling clarifyIntent, present the questions as text in your reply: one short line of context, then a numbered markdown list of each question with its options (e.g. "1. How are people getting there? — Walking / Driving / Public transport"), then STOP and wait. This lets the user read and answer even when the host does not render a form. (Echoing the tool\'s own returned questions is presenting a result, not narrating — do it.)\n- One call can carry several related questions. Fall back to a single prose question ONLY when the answer is genuinely free-form with no suggestable options.',er=['clarifyIntent({ intro: "A couple of details first.", questions: [{ id: "travelMode", question: "How are people getting there?", options: ["Walking", "Driving", "Public transport"] }] })','clarifyIntent({ questions: [{ id: "priorities", question: "What matters most?", options: ["Speed", "Cost", "Comfort"], multiSelect: true }] })'],tr=["Plan a route for me","Help me decide between these options"],rr=async(e,t)=>({status:"awaiting_user_response",intro:e.intro,questions:e.questions}),nr=(e={})=>({description:`${e.description??"Ask the user to resolve a genuinely ambiguous or missing input before acting — collects a few discrete choices as a short set of questions (numbered suggestions plus a freeform answer). Use it ONLY when you cannot proceed sensibly: a required input is missing with no reasonable default, or the request is genuinely ambiguous (e.g. an unclear concept, or which option matters most). Do NOT use it for a clear, actionable request — prefer acting with sensible defaults and stating them. Offer your best concrete suggestions. Batching a few related questions in a single call is fine; put any context in the `intro` field. After calling it, STOP and wait for the user — do not call other tools or answer until they reply."}\n\n${e.rendersForm?Zt:Qt}`,classificationPrompt:e.classificationPrompt??"The request is under-specified or ambiguous and needs the user to choose among discrete options before the task can proceed; renders the questions as an in-chat survey form.",inputSchema:Kt,outputSchema:Xt,execute:rr,tags:["utilities"],examples:e.examples??er,examplePrompts:e.examplePrompts??tr,alwaysActive:e.alwaysActive??!0,endsTurnOnCall:e.rendersForm??!1}),sr=oe.object({}),or=oe.union([oe.object({position:oe.array(oe.number()).min(2).max(2).describe("[longitude, latitude]"),accuracy:oe.number().describe("Accuracy of the location in meters"),timestamp:oe.number().describe("Timestamp of the location data")}),jt]),ar=["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"],ir=oe.object({name:oe.string(),description:oe.string(),examplePrompts:oe.array(oe.string()),relatedTools:oe.array(oe.string()).optional()}),lr=oe.union([oe.object({mode:oe.literal("summary"),examplePrompts:oe.array(oe.string())}),oe.object({mode:oe.literal("detail"),query:oe.string().optional(),tag:oe.enum(ar).optional(),matchedTools:oe.array(ir),totalMatched:oe.number()}),jt]),cr=oe.object({mode:oe.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:oe.string().optional().describe("Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags."),tag:oe.enum(ar).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),dr='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.',ur=(e,t,r,n,s)=>({description:t.description,inputSchema:t.inputSchema,outputSchema:n?t.outputSchema:void 0,execute:s?async(n,o)=>{const a=Date.now();try{const i=await t.execute(n,r,{signal:o?.abortSignal}),l=(e=>{const t=e?.error;return"string"==typeof t?t:void 0})(i);return s({toolName:e,durationMs:Date.now()-a,isError:void 0!==l,errorMessage:l}),i}catch(i){throw s({toolName:e,durationMs:Date.now()-a,isError:!0,errorMessage:i instanceof Error?i.message:String(i)}),i}}:(e,n)=>t.execute(e,r,{signal:n?.abortSignal})}),pr=(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,alwaysActive:t.alwaysActive,endsTurnOnCall:t.endsTurnOnCall,scopePrompt:t.scopePrompt}),hr=(e,t)=>"function"==typeof e?e(t):e,yr=(e,t,r)=>{const n=!1!==r?.outputSchemas,s={featureFlags:r?.featureFlags,enabledDataKinds:r?.enabledDataKinds},o={},a={},i={};for(const[c,d]of Object.entries(e)){const e=hr(d,s);if(o[c]=ur(c,e,t,n,r?.onToolExecute),a[c]=pr(c,e),"function"==typeof d&&e.scopeSchema){const t=d,r=e.scopeSchema;i[c]={applyScope:e=>{const n=r.safeParse(e);if(!n.success)return null;try{const e=t({...s,scope:n.data});return{description:e.description,inputSchema:e.inputSchema}}catch{return null}},defaultDescription:e.description,defaultInputSchema:e.inputSchema}}}var l;if(o.help&&(o.help=(l=()=>a,{description:dr,inputSchema:cr,outputSchema:lr,execute:async e=>{const{mode:t,query:r,tag:n}=e;try{const e=Object.values(l());if("summary"===t)return{mode:"summary",examplePrompts:e.flatMap(e=>e.examplePrompts??[])};let s=e;if(n&&(s=s.filter(e=>e.tags?.includes(n)??!1)),r){const e=r.toLowerCase();s=s.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:n,matchedTools:s.map(e=>({name:e.name,description:e.description,examplePrompts:e.examplePrompts??[],relatedTools:e.relatedTools??[]})),totalMatched:s.length}}catch(s){return{error:`Failed to load help: ${s instanceof Error?s.message:String(s)}`}}}})),0===Object.keys(o).length)throw new Error("MapAgent requires at least one tool.");return{tools:o,toolsMetadata:a,scopableTools:i}},mr=oe.discriminatedUnion("kind",[oe.object({kind:oe.literal("place").describe('Use the exact literal "place".'),id:oe.string().min(1).describe("Id of a single place; resolves to that place’s footprint.")}).describe("A single place footprint."),oe.object({kind:oe.literal("places").describe('Use the exact literal "places".'),id:oe.string().min(1).describe("Id of a places-entry; resolves to every footprint in the entry.")}).describe("Every footprint in a places entry."),oe.object({kind:oe.literal("ranges").describe('Use the exact literal "ranges".'),id:oe.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."),oe.object({kind:oe.literal("customGeometries").describe('Use the exact literal "customGeometries".'),id:oe.string().min(1).describe("Id of a custom-geometries entry produced by a previous `processData` call.")}).describe("A derived entry produced by a previous `processData` call.")]).describe('Tagged id `{ kind, id }`. `kind` must be exactly one of: "place" | "places" | "ranges" | "customGeometries" (do NOT use variants like "ranges-entry", "place-footprints", etc.). Use `recallState` to list every available `{kind, id}` pair.'),gr=oe.array(mr).min(1).describe('Tagged ids `{ kind, id }` of polygon sources to feed the run. `kind` must be exactly one of `"place"` | `"places"` | `"ranges"` | `"customGeometries"` (no variants). Mix kinds freely (e.g. union place footprints with isochrones). Use `recallState` to list available ids.'),fr="`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.",br="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",wr="Tagged input ids that contributed at least one polygon to the run, in input order.",vr="Each `geometriesByEntry[key]` (key = `${kind}:${id}`) is an array of Polygon/MultiPolygon Features. Each has `id` (the geometry-data-source id, matches `place.properties.dataSources.geometry.id`) and `bbox`. `properties` varies by source — see `GEOMETRIES_PROPS_DOC`. Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, `turf.buffer`, … directly on these features. For h3, convert via `h3.polygonToCells(feature.geometry.coordinates, res)`.",Ir=e=>'Each `placesByEntry[id]` is a FeatureCollection of Point Features. `Place.properties` shape:\n```ts\ntype PlaceProperties = {\n type: "POI" | "Street" | "Geography" | "Point Address" | "Address Range" | "Cross Street";\n address: {\n freeformAddress: string;\n streetNumber?: string; streetName?: string;\n municipality?: string; municipalitySubdivision?: string;\n countrySubdivision?: string; countrySubdivisionName?: string;\n countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\n postalCode?: string; extendedPostalCode?: string;\n countryCode?: string; countryCodeISO3?: string; country?: string;\n localName?: string;\n };\n poi?: { // when type === "POI"\n name: string; phone?: string; url?: string;\n brands?: string[];\n categories: string[]; // POICategory enum, e.g. "RESTAURANT", "CAFE_PUB", "GAS_STATION", "HOTEL_MOTEL", "ELECTRIC_VEHICLE_STATION"\n localizedCategories: string[]; // language-sensitive\n openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\n timeZone?: { name: string };\n };\n // Search hits also carry:\n score?: number; // relevance (higher = better)\n distance?: number; // meters from geoBias, when provided\n info?: string;\n // Geography hits also carry:\n geographyType?: Array<"Country"|"CountrySubdivision"|"CountrySecondarySubdivision"|"CountryTertiarySubdivision"|"Municipality"|"MunicipalitySubdivision"|"Neighbourhood"|"PostalCodeArea">;\n'+(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 small-area polygon (few km², not a whole municipality)\n areaCountry?: string; // ISO2 country of the area polygon\n areaTags?: string[]; // e.g. "coastal", "walkable", "transit_connected"\n':"")+" // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\n};\n```\nAreas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`. The fields above describe places you READ from input. When your output is a filter / subset / merge of the input places, return the source features as-is (spread them: `{ ...place }`, or `{ ...place, properties: { ...place.properties } }` when editing) so their `id` and all their fields ride through — carrying real fields forward is NOT the same as inventing them, keep them. Only when you RETURN a freshly computed point (one with no source place behind it) are just `type`, a display name at the schema path for that type (`poi.name` for POIs, `address.freeformAddress` for streets / intersections (`Cross Street`) / addresses), and Point coordinates required. For those computed points do NOT invent a flat `properties.name` (it is dropped, so the name would be lost), and do NOT fabricate the search / POI extras (`categories`, `localizedCategories`, `score`, `distance`, `info`, …) — a computed point has no such data behind it.",Er=Ir({}),Sr='Each `routesByEntry[id]` is a FeatureCollection of LineString Features (each route has `feature.bbox`). `Route.properties` shape:\n```ts\ntype RouteProperties = {\n index: number; // 0 = main route, 1+ = alternatives\n summary: {\n lengthInMeters: number;\n travelTimeInSeconds: number; // includes traffic delay\n noTrafficTravelTimeInSeconds?: number;\n trafficDelayInSeconds: number;\n trafficLengthInMeters: number;\n departureTime: Date; arrivalTime: Date;\n // EV-only:\n batteryConsumptionInkWh?: number; batteryConsumptionInPCT?: number;\n remainingChargeAtArrivalInkWh?: number; remainingChargeAtArrivalInPCT?: number;\n totalChargingTimeInSeconds?: number;\n // Combustion-only:\n fuelConsumptionInLiters?: number;\n };\n // Segments grouped by characteristic. Every section has { id, startPointIndex, endPointIndex }\n // unless noted; *Index values reference `geometry.coordinates`.\n sections: {\n leg: Array<{ id; startPointIndex?; endPointIndex?; summary: RouteSummary; originalWaypointIndex?: number }>; // ALWAYS present\n // originalWaypointIndex = which requested stop the leg arrives at; absent on the last leg and on service-inserted EV charging stops\n country?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // ISO 3166-1 alpha-3\n tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>;\n traffic?: Array<{\n id; startPointIndex; endPointIndex;\n categories: TrafficIncidentCategory[]; // e.g. "Jam","Accident","RoadWorks","Closure"\n magnitudeOfDelay: "Unknown"|"Minor"|"Moderate"|"Major"|"Undefined";\n tec: { effectCode?: number; causes?: Array<{ mainCauseCode: number; subCauseCode?: number }> };\n effectiveSpeedInKmh?: number;\n delayInSeconds?: number;\n eventId?: string; // join key to the traffic incident details service\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 tollRoad?: SectionProps[]; // costs money by ANY scheme: every `toll` stretch, plus vignette motorways and city charge zones\n unpaved?: SectionProps[]; urban?: SectionProps[]; pedestrian?: SectionProps[];\n carpool?: SectionProps[]; carTrain?: SectionProps[];\n lowEmissionZone?: SectionProps[]; vehicleRestricted?: SectionProps[];\n };\n progress?: Array<{ pointIndex: number; travelTimeInSeconds?: number; distanceInMeters?: number }>; // cumulative milestones\n guidance?: { instructions: Array<{\n pathPointIndex: number; maneuver: string;\n message?: string; // ready-made localised text ("Turn right onto Damrak") — prefer it over mapping `maneuver` yourself\n nextRoadInfo: { streetName?: { text: string; phonetic?: string }; countryCode?: string };\n roundaboutType?: "REGULAR"|"SMALL";\n maneuverView?: { onRouteAngle?: ManeuverAngle; offRouteAngles: ManeuverAngle[] }; // relative directions ("RIGHT","SLIGHT_LEFT","BACK"), not degrees\n sideRoads?: Array<{ side: "LEFT"|"RIGHT"|"LEFT_AND_RIGHT"; offsetFromManeuverInMeters: number; isDrivable?: boolean }>;\n }> };\n};\n```\n`pathPointIndex` and `pointIndex` values reference positions in `geometry.coordinates`. Guard `?` fields with `?.` / `??`. For "how long / how far" questions, use `summary.lengthInMeters` / `summary.travelTimeInSeconds`.',Mr=e=>Number.isFinite(e)&&e>=-180&&e<=180,kr=e=>Number.isFinite(e)&&e>=-90&&e<=90,xr="Position must be [lng, lat] with lng in [-180, 180] and lat in [-90, 90].",Ar=oe.array(oe.number()).length(2).refine(([e,t])=>Mr(e)&&kr(t),{message:xr}),Dr=oe.array(oe.number()).min(2).max(3).refine(([e,t])=>Mr(e)&&kr(t),{message:xr}),Tr=oe.array(oe.number()).length(4).refine(([e,t,r,n])=>Mr(e)&&kr(t)&&Mr(r)&&kr(n),{message:"BBox values out of range — lng must be in [-180, 180] and lat in [-90, 90]."}).refine(([,e,,t])=>e<=t,{message:"BBox minLat must be <= maxLat."}).describe("[minLng, minLat, maxLng, maxLat] or [W,S,E,N]. lng in [-180, 180], lat in [-90, 90]."),Pr=oe.object({type:oe.literal("Polygon"),coordinates:oe.array(oe.array(Dr))}),_r=oe.object({type:oe.literal("MultiPolygon"),coordinates:oe.array(oe.array(oe.array(Dr)))}),Or=oe.union([Pr,_r]),Cr=({verb:e,extra:t})=>oe.array(oe.string()).min(1).optional().describe(`IDs of existing places entries to ${e} (e.g. ["places-2"], ["places-1", "places-2"]). Use recallState to list IDs. Omit to ${e} the latest entry.`+(t?` ${t}`:"")),Rr=({verb:e,extra:t})=>oe.array(oe.string()).min(1).optional().describe(`IDs of existing routes entries to ${e} (e.g. ["routes-0"], ["routes-0", "routes-1"]). Use recallState to list IDs. Omit to ${e} the latest route.`+(t?` ${t}`:"")),jr=oe.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`.'),Br=oe.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).'),Nr=oe.object({show:oe.boolean().optional().describe("Also render the polygons on the map. Default: false."),mode:Br,theme:oe.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).'),Lr=oe.object({theme:oe.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline".'),placeIds:oe.array(oe.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:Br}).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.'),Fr=oe.enum([...Pe,"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.'),qr=e=>oe.union([oe.literal("all"),oe.array(oe.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).`),$r=oe.object({markerType:Fr,zoomMode:oe.enum(["auto","none"]).describe('Camera behaviour. "none" keeps the map still.'),hidePreviousEntries:qr("places")}),Gr=oe.object({markerType:oe.boolean(),zoomMode:oe.boolean()}),Ur=oe.object({zoomMode:oe.enum(["auto","none"]).describe('Camera behaviour. "auto" fits the map to the shown BYOD entries; "none" keeps the map still.'),hidePreviousEntries:qr("BYOD")}),Wr=oe.object({zoomMode:oe.boolean().describe("Whether the camera was moved to fit the shown entries.")}),zr={mode:oe.literal("within"),viewport:oe.literal(!0).optional().describe("When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.")},Yr=oe.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".'),Hr=["boundingBox","viewport","query","placeId"],Vr=oe.object({...zr,boundingBox:Tr.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId."),query:oe.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:Yr,placeId:oe.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===Hr.filter(t=>void 0!==e[t]).length,{message:"`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId."}),Jr=["position","viewport","query"],Kr=oe.object({mode:oe.literal("nearby"),position:Ar.optional().describe("Bias point [lng, lat]. EXCLUSIVE with viewport / query."),viewport:oe.literal(!0).optional().describe("When true, bias toward the current map viewport centre. EXCLUSIVE with position / query."),query:oe.string().optional().describe("Resolve a name/address/POI → its centroid as bias point. Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport."),queryAs:Yr,radiusMeters:oe.number().positive().optional().describe("Hard radius cap (metres) around the bias point. Independent of bias source.")}).refine(e=>1===Jr.filter(t=>void 0!==e[t]).length,{message:"`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent)."}),Xr=oe.object({mode:oe.literal("global").describe("No spatial constraint — resolve the name anywhere. The default for a uniquely-named place.")}),Zr=oe.union([Vr,Kr,Xr]),Qr={...zr,queries:oe.array(oe.object({query:oe.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:Yr})).min(1).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:oe.array(oe.string()).min(1).optional().describe('IDs of session places — a places ENTRY id (e.g. "places-0", or the `placesEntryId` returned by locatePlace) expands to every place it holds, or a single place id. Each resolved polygon (fetched on demand) joins the search area. Use recallState to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),geometries:oe.array(Or).min(1).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:oe.string().min(1).optional().describe('Range ID from findReachableAreas (e.g. "ranges-0") — restricts to that entry; multi-origin entries combine their polygons. Use recallState to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),route:oe.object({routeId:oe.string().min(1).optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),widthMeters:oe.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. EXCLUSIVE with viewport; composes with other multi-region fields.")},en=oe.object({viewport:oe.boolean().optional(),boundingBox:Tr.optional(),queries:Qr.queries,placeIds:Qr.placeIds,geometries:Qr.geometries,route:Qr.route}),tn=["places","routes","incidents","customGeometries","trafficAreaAnalytics","byod"],rn=e=>oe.object({kinds:oe.array(oe.enum(e)).min(1)}),nn={places:{fieldName:"placesEntryIDs",fieldLabel:"`placesEntryIDs`",buildField:e=>Cr({verb:e.verb,extra:"Injected as `placesByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. "+e.requiredHint}),sandboxDoc:"• `placesByEntry` — object keyed by `placesEntryIDs` id; each value a FeatureCollection of that entry's places. Merge all entries with `Object.values(placesByEntry).flatMap(fc => fc.features)`.",schemaDoc:Er,recallTool:"recallState({ kind: 'places' })"},routes:{fieldName:"routesEntryIDs",fieldLabel:"`routesEntryIDs`",buildField:e=>Rr({verb:e.verb,extra:"Injected as `routesByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. "+e.requiredHint}),sandboxDoc:"• `routesByEntry` — object keyed by `routesEntryIDs` id; each value a FeatureCollection of that entry's Route LineStrings. Merge all entries with `Object.values(routesByEntry).flatMap(fc => fc.features)`. On-route incidents live in `f.properties.sections.traffic[]` (each `{ categories: string[], magnitudeOfDelay, delayInSeconds }`) — there is NO `f.properties.incidents`. Route totals are on `f.properties.summary` (`travelTimeInSeconds`, `trafficDelayInSeconds`).",schemaDoc:Sr,recallTool:"recallState({ kind: 'routes' })"},incidents:{fieldName:"incidentsEntryIDs",fieldLabel:"`incidentsEntryIDs`",buildField:e=>oe.array(oe.string()).min(1).optional().describe(`IDs of existing incidents entries to ${e.verb} (e.g. ["incidents-0"]). Injected as \`incidentsByEntry[id]\` (a TrafficIncident[] per entry id). Undefined when omitted. Use \`recallState({ kind: 'incidents' })\` to list incident entries. `+e.requiredHint),sandboxDoc:"• `incidentsByEntry` — object keyed by `incidentsEntryIDs` id; each value a flat `TrafficIncident[]` for that entry. Merge all entries with `Object.values(incidentsByEntry).flat()`.",schemaDoc:'Each `incidentsByEntry[id]` is a flat array of TrafficIncident Features (geometry is `Point` OR `LineString` — guard with `feature.geometry.type` before line-only ops). All fields below live on `feature.properties`:\n```ts\ntype TrafficIncidentProperties = {\n id: string;\n category: TrafficIncidentCategory; // "jam" | "accident" | "roadworks" | "road-closed" | "lane-closed" | "broken-down-vehicle" | "danger" | "flooding" | "fog" | "frost" | "rain" | "wind" | "other"\n magnitudeOfDelay: "unknown" | "minor" | "moderate" | "major" | "undefined"; // severity\n delayInSeconds?: number; // delay caused by the incident (NOT every incident has one — guard with ?? 0)\n lengthInMeters?: number; // affected road length\n roadNumbers?: string[]; // e.g. ["M25", "A1"]\n from?: string; to?: string; // human-readable start / end of the affected stretch\n timeValidity: "present" | "future";\n events: Array<{ description: string; code?: number; iconCategory?: number }>;\n startTime?: Date; endTime?: Date; lastReportTime?: Date;\n probabilityOfOccurrence?: string; numberOfReports?: number;\n};\n```\nRead everything off `properties` — there is NO `reportedDelayInSeconds` / top-level delay field (e.g. `properties.delayInSeconds`, guard `?? 0`).',recallTool:"recallState({ kind: 'incidents' })"},customGeometries:{fieldName:"geometriesEntryIDs",fieldLabel:"`geometriesEntryIDs`",buildField:e=>gr.optional().describe("Tagged polygon sources `{ kind, id }` (see geometriesEntryIDsSchema). Injected as `geometriesByEntry` (keyed by the tagged source `${kind}:${id}`). Undefined when omitted. "+e.requiredHint),sandboxDoc:"• `geometriesByEntry` — object keyed by the tagged source `${kind}:${id}` (geometries mixes sources); each value a mixed Polygon/MultiPolygon array. Merge all with `Object.values(geometriesByEntry).flat()`. Every feature also carries `properties._source: { kind, id }` so you can filter by role (see `GEOMETRIES_PROPS_DOC`).",schemaDoc:vr,recallTool:"recallState({ kind: 'geometries' })"},trafficAreaAnalytics:{fieldName:"trafficAreaAnalyticsEntryIDs",fieldLabel:"`trafficAreaAnalyticsEntryIDs`",buildField:e=>oe.array(oe.string()).min(1).optional().describe(`IDs of existing traffic-area-analytics entries to ${e.verb} (e.g. ["tta-0"]). Injected as \`trafficAreaAnalyticsByEntry[id]\` (a FeatureCollection of tile/hex regions with metric properties, per entry id). Undefined when omitted. Use \`recallState({ kind: 'trafficAreaAnalytics' })\` to list available ids. `+e.requiredHint),sandboxDoc:"• `trafficAreaAnalyticsByEntry` — object keyed by `trafficAreaAnalyticsEntryIDs` id; each value a TrafficAreaAnalytics FeatureCollection for that entry. Each feature is a region whose metrics live under `feature.properties.baseData` (e.g. `congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`). Each entry's collection keeps its own top-level `properties` (metrics list, date range, ranges) intact — no cross-entry merge.",schemaDoc:'Each `trafficAreaAnalyticsByEntry[id]` is a FeatureCollection of Polygon Features (one per analysed region). The aggregated metrics live under `feature.properties.baseData` (NOT directly on `properties`):\n```ts\ntype AreaAnalyticsFeatureProperties = {\n name: string; timezone: string; level: number;\n baseData: { // aggregated over the whole period — the usual read\n speed?: number; // avg km/h\n freeFlowSpeed?: number; // avg uncongested km/h\n congestionLevel?: number; // % travel-time increase over free-flow\n travelTime?: number; // minutes per 10 km\n networkLength?: number; // meters of road network with data\n };\n timedData: { … }; // same metrics broken down by time granularity\n tiledData?: { tiles: Array<{ … }> }; // per-tile metrics, only when heatmap was requested\n};\n// the FeatureCollection\'s own `.properties` (collection-level):\ntype AreaAnalyticsCollectionProperties = {\n startDate: Date; endDate: Date;\n metrics: ("speed"|"freeFlowSpeed"|"congestionLevel"|"travelTime"|"networkLength")[];\n heatmap: boolean; frcs: number[];\n ranges: Partial<Record<metric, { min: number; max: number }>>; // data-driven min/max across tiles\n};\n```\nRead metrics from `feature.properties.baseData.<metric>` (guard `?? 0`).',recallTool:"recallState({ kind: 'trafficAreaAnalytics' })"},byod:{fieldName:"byodEntryIDs",fieldLabel:"`byodEntryIDs`",buildField:e=>oe.array(oe.string()).min(1).optional().describe(`IDs of existing BYOD entries to ${e.verb} (e.g. ["byod-0"]) — customer-authored GeoJSON layers. Injected as \`byodByEntry[id]\` (a FeatureCollection per entry id). Undefined when omitted. Use \`recallState\` to list available ids. `+e.requiredHint),sandboxDoc:"• `byodByEntry` — object keyed by `byodEntryIDs` id; each value a customer-authored GeoJSON FeatureCollection. Merge all with `Object.values(byodByEntry).flatMap(fc => fc.features)`. Mixed Point / LineString / Polygon features are normal (BYOD layers carry whatever the integrator uploaded). Treat `feature.properties` as opaque application-specific data.",schemaDoc:"",recallTool:"recallState({ kind: 'byod' })"}},sn=(e,t)=>tn.filter(r=>e.includes(r)&&(!t||t.kinds.includes(r))),on=(e,t,r)=>e.includes(r)&&(!t||t.kinds.includes(r)),an=e=>e.map(e=>nn[e].fieldLabel).join(" / "),ln=e=>tn.filter(t=>e.includes(t)).map(e=>nn[e].sandboxDoc).join("\n"),cn=e=>tn.filter(t=>e.includes(t)).map(e=>nn[e].schemaDoc).filter(e=>e.length>0).join("\n\n"),dn=(e,t)=>{const r={};for(const n of tn){if(!e.includes(n))continue;const s=nn[n];r[s.fieldName]=s.buildField(t)}return r},un=e=>{for(const t of tn)if(e[nn[t].fieldName]?.length)return!0;return!1},pn=e=>`At least one of ${an(e)} must be set. Call ${Array.from(new Set(e.map(e=>nn[e].recallTool))).map(e=>`\`${e}\``).join(" / ")} first to list available ids.`,hn=oe.union([Tr,oe.object({bbox:Tr,padding:oe.number().optional(),animate:oe.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 }`. lng in [-180, 180], lat in [-90, 90]. Default padding: 50px."),yn=oe.object({bbox:oe.tuple([oe.number(),oe.number(),oe.number(),oe.number()]).describe("Bounds the camera was fit to."),padding:oe.number().describe("Padding (px) applied around the bbox.")}),mn="`fitOnMap` (opt): `[W,S,E,N]` BBox or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }` (default padding 50, lng in [-180, 180], lat in [-90, 90]). 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)`.",gn=e=>Tr.safeParse(e).success,fn=e=>{if(gn(e))return!0;if(!e||"object"!=typeof e)return!1;const t=e;return!!gn(t.bbox)&&(!!(void 0===t.padding||"number"==typeof t.padding&&Number.isFinite(t.padding))&&!(void 0!==t.animate&&"boolean"!=typeof t.animate))},bn=(e,t)=>{const r=gn(t)?t:t.bbox,n=(gn(t)?void 0:t.padding)??50,s=gn(t)?void 0:t.animate;return e.baseMap.mapLibreMap.fitBounds(r,{padding:n,...void 0!==s&&{animate:s}}),{bbox:r,padding:n}},wn=["CountrySubdivision","CountrySecondarySubdivision","Municipality","MunicipalitySubdivision","Neighbourhood"],vn=async(e,t={},r)=>{const n=Te({query:e,limit:t.limit??6,geographyTypes:wn,...t.bias?{geoBias:{position:t.bias}}:{},signal:r?.signal});return(await _(n)).features},In=async(e,t,r)=>{if("multiple"!==e.entryMode||void 0===r)return;const n=new Set(t),s="all"===r?[...e.shownEntryIds].filter(e=>!n.has(e)):r.filter(e=>!n.has(e));for(const o of s)await e.hideEntry(o)},En=async(e,t,r,n)=>{const s="poi"===t?B:_,{limit:o,bias:a}=r;return(await s(Te({query:e,limit:o,...a&&{geoBias:a},signal:n?.signal}))).features},Sn=async(e,t,r,n)=>{const[s]=await En(e,t,{limit:1,bias:r},n);return s??null},Mn=oe.union([oe.object({query:oe.string().describe("Location string to resolve."),queryAs:oe.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.')}),oe.object({position:Ar.describe("Explicit [lng, lat] — GeoJSON order, longitude first. Use when you already have a position, e.g. copied straight from a getViewport / getCurrentLocation / reverseGeocode result. lng in [-180, 180], lat in [-90, 90].")}),oe.object({placeIdOrEntryId:oe.string().describe("Place already in session state, by its feature `id` or its `placesEntryId`. Prefer over re-searching by name. Prefer a feature `id`: it targets one place, whereas an entry id resolves to its FIRST place (fine for a single-place locatePlace entry, ambiguous for a multi-place discoverPlaces one).")})]),kn=e=>"query"in e?`"${e.query}"`:"placeIdOrEntryId"in e?`placeIdOrEntryId "${e.placeIdOrEntryId}"`:JSON.stringify(e.position),xn=oe.object({source:mr,reason:oe.string()}),An=e=>`${e.source.kind}:${e.source.id}: ${e.reason}`,Dn=(e,t,r)=>{let n=0;for(const s of e)s.properties.dataSources?.geometry?.id&&"string"==typeof s.id&&(n++,r.placeIdToEntry.has(s.id)||r.placeIdToEntry.set(s.id,t),r.seen.has(s.id)||(r.seen.add(s.id),r.placeIds.push(s.id)));return n},Tn=(e,t)=>({...e,properties:{...e.properties??{},_source:t}}),Pn=async(e,t)=>{const r=t.places.findPlaceById(e);if(!r)return{kind:"skip",reason:"No place with this id in session state. Use `recallState` 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 n=await t.places.fetchPlaceGeometry(e);return n?{kind:"success",place:r.place,feature:n,ownerEntryId:r.entryId}:{kind:"skip",reason:"Geometry Data service returned no feature for this place."}}catch(n){return{kind:"skip",reason:`Geometry fetch failed: ${n instanceof Error?n.message:String(n)}`}}},_n=async(e,t)=>{if(0===e.length)return{error:"`geometriesEntryIDs` must contain one or more tagged ids (e.g. `{ kind: 'places', id: <entryId> }`, `{ kind: 'place', id: <placeId> }`, `{ kind: 'ranges', id: <entryId> }`, `{ kind: 'customGeometries', id: <entryId> }`). Use `recallState` to list available ids."};const r=(e=>{const t={place:[],places:[],ranges:[],customGeometries:[]};for(const r of e)t[r.kind].push(r);return t})(e),n=((e,t)=>{const r=[],n=/* @__PURE__ */new Map,s=[],o=/* @__PURE__ */new Set;for(const a of e){const e=t.places.entries.find(e=>e.id===a.id);e?0===Dn(e.data,a.id,{placeIds:r,placeIdToEntry:n,seen:o})&&s.push({source:a,reason:"Places entry has no places with a geometry data source (e.g. addresses and streets often lack one)."}):s.push({source:a,reason:"No places entry with this id in session state. Use `recallState` to list available ids."})}return{placeIds:r,placeIdToEntry:n,skipped:s}})(r.places,t),s=[...n.skipped],o=/* @__PURE__ */new Set,a=[],i=/* @__PURE__ */new Map;for(const g of r.place)o.has(g.id)||(o.add(g.id),a.push(g.id),i.set(g.id,g));for(const g of n.placeIds)o.has(g)||(o.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 Pn(g,t),r=i.get(g),o=n.placeIdToEntry.get(g);if("skip"===e.kind){const t=r??{kind:"place",id:g};s.push({source:t,reason:e.reason});continue}const a=r??(o?{kind:"places",id:o}:{kind:"place",id:g});l.push(Tn(e.feature,a)),c.push(e.place),d.add(e.ownerEntryId),r&&u.add(r.id),o&&p.add(o)}const h=((e,t)=>{const r=[],n=/* @__PURE__ */new Set,s=[];for(const o of e){const e=t.ranges.entries.find(e=>e.id===o.id);if(!e){s.push({source:o,reason:"No ranges entry with this id in session state."});continue}const a=e.data.flatMap(e=>e.polygon?.features??[]);if(0!==a.length){for(const e of a)r.push(Tn(e,o));n.add(o.id)}else s.push({source:o,reason:"Ranges entry has no polygons — recompute via findReachableAreas first."})}return{features:r,contributingIds:n,skipped:s}})(r.ranges,t);l.push(...h.features),s.push(...h.skipped);const y=((e,t)=>{const r=[],n=/* @__PURE__ */new Set,s=[];for(const o of e){const e=t.customGeometries.findById(o.id);if(e)if(0!==e.data.length){for(const t of e.data)r.push(Tn(t,o));n.add(o.id)}else s.push({source:o,reason:"Custom-geometries entry is empty."});else s.push({source:o,reason:"No custom-geometries entry with this id in session state. Use `recallState` to list available ids."})}return{features:r,contributingIds:n,skipped:s}})(r.customGeometries,t);l.push(...y.features),s.push(...y.skipped);const m=y.contributingIds;return 0===l.length?{error:`No input geometries available. ${s.map(An).join(" | ")}`}:{value:{geometries:l,sourcePlaces:c,affectedEntries:[...[...d].map(e=>({id:e,kind:"places"})),...[...m].map(e=>({id:e,kind:"customGeometries"}))],skipped:s,contributingSourceIds:e.filter(e=>{switch(e.kind){case"place":return u.has(e.id);case"places":return p.has(e.id);case"ranges":return h.contributingIds.has(e.id);case"customGeometries":return m.has(e.id)}})}}},On=(e,t)=>{if(!e?.length)return{value:void 0};const r={};for(const n of e){const e=t.places.entries.find(e=>e.id===n);if(!e)return{error:`No places entry found with id "${n}". Use recallState to list available IDs.`};r[n]={type:"FeatureCollection",features:e.data}}return{value:r}},Cn=e=>{let t;for(const r of Object.values(e))for(const e of r){const r=e.timestamp;"number"==typeof r&&(void 0===t||r>t)&&(t=r)}return t},Rn=["placesEntryIDs","routesEntryIDs","incidentsEntryIDs","geometriesEntryIDs","trafficAreaAnalyticsEntryIDs","byodEntryIDs"],jn=async(e,t)=>{if(!(e=>Rn.some(t=>(e[t]?.length??0)>0))(e))return{error:"At least one of `placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`, `trafficAreaAnalyticsEntryIDs`, `byodEntryIDs` must be set. Use `recallState` (optionally with a `kind`) to list available ids."};const r=((e,t)=>{const r=Ln(e.placesEntryIDs,t.places,{missingId:e=>`No places entry with id "${e}". Use recallState to list available IDs.`});if("error"in r)return r;const n=Ln(e.routesEntryIDs,t.routing,{missingId:e=>`No routes entry with id "${e}". Use recallState to list available IDs.`});if("error"in n)return n;const s=Ln(e.incidentsEntryIDs,t.trafficIncidents,{missingId:e=>`No incidents entry with id "${e}". Call getTrafficIncidents first.`});if("error"in s)return s;const o=Ln(e.trafficAreaAnalyticsEntryIDs,t.trafficAreaAnalytics,{missingId:e=>`No traffic-area-analytics entry with id "${e}". Call getTrafficAreaAnalytics first.`});if("error"in o)return o;const a=Ln(e.byodEntryIDs,t.byod,{missingId:e=>`No BYOD entry with id "${e}". Use recallState to list available IDs.`});return"error"in a?a:{value:{places:r.value,routes:n.value,incidents:s.value,trafficAreaAnalytics:o.value,byod:a.value}}})(e,t);if("error"in r)return r;let n,s={affectedEntries:[],sourceIds:[],skipped:[]};if(e.geometriesEntryIDs?.length){const r=await _n(e.geometriesEntryIDs,t);if("error"in r)return r;n=r.value.geometries,s={affectedEntries:r.value.affectedEntries,sourceIds:r.value.contributingSourceIds,skipped:r.value.skipped}}return{value:{resolved:r.value,sandbox:Bn(r.value,n),geometries:s}}},Bn=(e,t)=>({placesByEntry:e.places.length?Object.fromEntries(e.places.map(e=>[e.id,{type:"FeatureCollection",features:[...e.data]}])):void 0,routesByEntry:e.routes.length?Object.fromEntries(e.routes.map(e=>[e.id,{type:"FeatureCollection",features:[...e.data.features]}])):void 0,incidentsByEntry:e.incidents.length?Object.fromEntries(e.incidents.map(e=>[e.id,[...e.data]])):void 0,geometriesByEntry:Nn(t),trafficAreaAnalyticsByEntry:e.trafficAreaAnalytics.length?Object.fromEntries(e.trafficAreaAnalytics.map(e=>[e.id,e.data])):void 0,byodByEntry:e.byod.length?Object.fromEntries(e.byod.map(e=>[e.id,e.data])):void 0}),Nn=e=>{if(!e?.length)return;const t={};for(const r of e){const e=r.properties?._source,n=e?`${e.kind}:${e.id}`:"unknown",s=t[n]??[];s.push(r),t[n]=s}return t},Ln=(e,t,r)=>{if(!e?.length)return{value:[]};const n=[];for(const s of e){const e=t.entries.find(e=>e.id===s);if(!e)return{error:r.missingId(s)};n.push(e)}return{value:n}},Fn='INPUTS (per-entry) — each requested kind is an object keyed by the id you passed in `<kind>EntryIDs`: `placesByEntry`, `routesByEntry`, `incidentsByEntry`, `trafficAreaAnalyticsByEntry`, `byodByEntry`. There is NO flat `places`/`routes`/… — only these per-entry records. Each value is that one entry\'s data: a FeatureCollection (`placesByEntry`, `routesByEntry`, `byodByEntry`), a TrafficAreaAnalytics FeatureCollection (`trafficAreaAnalyticsByEntry`), or a plain array (`incidentsByEntry`).\n• One entry — index by id: `placesByEntry["places-2"]` (a FeatureCollection → `turf.bbox(placesByEntry["places-2"])`).\n• All requested entries of a kind — merge with `Object.values(...)`: `Object.values(placesByEntry).flatMap(fc => fc.features)` for FeatureCollection kinds (wrap with `turf.featureCollection(...)` before passing to turf), `Object.values(incidentsByEntry).flat()` for arrays.\n• Per-entry / comparative work ("count per entry", "which route set is faster") — iterate the record\'s entries (`Object.entries(routesByEntry)`) so results stay labelled by source id.\n`geometriesByEntry` is the odd one out: keyed by the tagged source `${kind}:${id}` (e.g. `"customGeometries:abc"`) rather than a bare id, since geometries mixes sources — each feature also carries `properties._source` (`{ kind, id }`).',qn={calculateProgressAtRoutePoint:z,getCoordinateAtRouteProgress:V,getProgressAtNearestRoutePoint:K,getRouteProgressBetween:X,getRouteProgressForSection:Z,getSectionBBox:Q},$n=["placesByEntry","routesByEntry","incidentsByEntry","geometriesByEntry","trafficAreaAnalyticsByEntry","byodByEntry","h3","turf","routeUtils","cluster"],Gn=(e,t)=>{const r=t&&e.length>1?'\n\nCROSS-KIND OPS — `turf` and `h3` operate on EVERY feature regardless of which entry/kind it came from. You can freely combine features from `placesByEntry` (Points), `routesByEntry` (LineStrings), `incidentsByEntry` (Point or LineString), `geometriesByEntry` (Polygon / MultiPolygon), `byodByEntry` (customer GeoJSON — Point / LineString / Polygon, mixed), and `trafficAreaAnalyticsByEntry` (Polygon tiles with `properties` like `congestionLevel` / `speed` / `travelTime` / `freeFlowSpeed`) in the same call. Pull features out of a record first — one entry `placesByEntry[id].features`, or all entries `Object.values(placesByEntry).flatMap(fc => fc.features)` (`Object.values(incidentsByEntry).flat()` for the array kinds). Common bridges:\n• Point ↔ LineString — `turf.pointToLineDistance(point, lineFeature, { units: "meters" })`, `turf.nearestPointOnLine(line, point)`.\n• Point ↔ Polygon — `turf.booleanPointInPolygon(point, poly)`, `turf.pointsWithinPolygon(turf.featureCollection(points), poly)` — POINT-ONLY (throw "coord must be GeoJSON Point" on a line/polygon).\n• ANY feature ↔ Polygon (containment / overlap) — `incidentsByEntry` and `byodByEntry` mix geometry types, so when you group features into polygons (e.g. incidents per BYOD district) use `turf.booleanIntersects(feature, poly)`, which accepts Point / LineString / Polygon alike. Only use `booleanPointInPolygon` after a `feature.geometry.type === "Point"` guard, or first collapse a line/area to a point with `turf.pointOnFeature(feature)`.\n• LineString ↔ Polygon — `turf.lineIntersect(line, poly)`, `turf.booleanCrosses(line, poly)`, `turf.lineSplit(line, poly)`.\n• Polygon ↔ Polygon — `turf.union/intersect/difference(turf.featureCollection([a, b]))` (Turf v7 takes a single collection).\n• Point/LineString ↔ trafficAreaAnalytics tile — `turf.booleanPointInPolygon(p, tile)` for points; `turf.lineIntersect(routeLine, tile)` for routes; iterate a tile collection\'s `.features` (e.g. `trafficAreaAnalyticsByEntry[id].features`) to find which tile a place/route segment falls into and read its metric (`tile.properties.congestionLevel`, etc.).\n• Buffer to bridge kinds — `turf.buffer(anyFeature, meters/1000, { units: "kilometers" })` turns a point or line into a polygon you can then filter against.\n• Bbox of anything — `turf.bbox(featureOrCollection)`.\n• Centroid for Point↔shape distance — `turf.centroid(polygonOrLine).geometry.coordinates`.\n• h3 — `h3.latLngToCell(lat, lng, res)` for any Point; `h3.polygonToCells(poly.geometry.coordinates, res)` for any Polygon; for a LineString iterate `geometry.coordinates` and bin each `[lng, lat]`.\nNote: arguments to turf functions are GeoJSON Features — pass the whole feature (`placesByEntry[id].features[i]`, `routesByEntry[id].features[0]`, `geometriesByEntry[key][i]`, `incidentsByEntry[id][i]`, `trafficAreaAnalyticsByEntry[id].features[i]`), not the bare `geometry.coordinates` (a few helpers accept coords but most expect Features). Always check the function signature at https://turfjs.org/docs/.':"",n=t&&e.includes("routes")?"\n\n`routeUtils` (route section / progress helpers, only useful when `routesEntryIDs` is set; each takes a single route feature, e.g. `routesByEntry[id].features[0]`; for a plain bounding box use `turf.bbox(route)`):\n• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>`.\n• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startIdx, endIdx)`.\n• `calculateProgressAtRoutePoint(route, pathIndex)`.\n• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }`.\n• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point to the line.":"",s=t&&e.includes("incidents")?"\n\n`cluster(incidents, params?, previous?, now?)` — DBSCAN clustering with stable IDs + trends (only useful with `incidentsEntryIDs`; `incidents` is an array — get it via `Object.values(incidentsByEntry).flat()` or one entry `incidentsByEntry[id]`; filter first, then cluster).\n• `params`: `{ eps?, minMembers?, maxClusters?, preFilter? }` — `eps` km radius (default 0.5; 0.3–0.5 urban, 1+ highways), `minMembers` (default 3), `maxClusters` top-N by delay (default 6).\n• Returns `{ groups: [{ id, centroid, memberIds, size, totalDelaySeconds, peakDelaySeconds, diameterKm, primaryRoads, primaryCategory, trend }], sampledAt }`.\n• Live trends: pass the injected `previous` + `now` — `return cluster(incidents, { eps: 0.4 }, previous, now);`. Omit both for one-shot.":"";return`${Fe($n)}\n\n${Fn}${r}${n}${s}\n\n`},Un=(e,t)=>[e.placesByEntry,e.routesByEntry,e.incidentsByEntry,e.geometriesByEntry,e.trafficAreaAnalyticsByEntry,e.byodByEntry,t.h3,t.turf,qn,Et],Wn=new Set(te),zn=async(e,t)=>{if(!e)return{resolved:void 0,unresolved:[]};const r=e.filter(e=>Wn.has(e)),n=e.filter(e=>!Wn.has(e));if(0===n.length)return{resolved:r.length?r:void 0,unresolved:[]};const s=Te({signal:t?.signal});await C(s);const o=await Promise.all(n.map(async e=>{const r=Te({filters:[e],signal:t?.signal});return{term:e,codes:await C(r)}})),a=[],i=[];for(const{term:c,codes:d}of o)0===d.length?a.push(c):i.push(...d);const l=[.../* @__PURE__ */new Set([...r,...i])];return{resolved:l.length?l:void 0,unresolved:a}},Yn=(e,t)=>{const r=e.ranges.entries.find(e=>e.id===t);if(!r||0===r.data.length)return{error:`Range "${t}" not found. Use recallState to list available ranges.`};const n=r.data.map(e=>e.polygon?.features[0]?.geometry).filter(e=>!!e);return 0===n.length?{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`}:{polygons:n}},Hn=e=>Object.entries(e).filter(([,e])=>e.scopePrompt).map(([e])=>e),Vn=oe.object({kinds:oe.array(oe.string()).min(1).describe("Entry-data kinds the turn touches (one of the kinds listed in the tool's SCOPE hint).")}),Jn=(e,t)=>{t&&(e.inputTokens+=t.inputTokens??0,e.outputTokens+=t.outputTokens??0,e.totalTokens+=t.totalTokens??0)},Kn=e=>{if(!e||!Array.isArray(e.tools)||!e.tools.every(e=>"string"==typeof e))return;const t=e.toolScopes&&"object"==typeof e.toolScopes?e.toolScopes:void 0;return{tools:e.tools,...t&&{scopes:t}}},Xn=(e,t)=>{const r=e.toolScopes?Object.fromEntries(Object.entries(e.toolScopes).filter(([,e])=>null!=e)):void 0;return{tools:e.tools,...r&&Object.keys(r).length>0&&{toolScopes:r},usage:t}},Zn=async(t,n,s)=>{const o=new Set(Hn(s)),a=(e=>{const t=oe.object({tools:oe.array(oe.string()).min(1).describe("Tool names needed to fulfill the query.")});if(0===e.size)return t;const r={};for(const s of e)r[s]=Vn.nullable();const n=oe.object(r).nullable().describe("Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in `tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.");return t.extend({toolScopes:n}).superRefine((t,r)=>{const n=t.toolScopes??{};for(const s of t.tools){if(!e.has(s))continue;null==n[s]&&r.addIssue({code:"custom",path:["toolScopes",s],message:`Tool "${s}" requires \`toolScopes.${s}\` — emit it per the SCOPE hint (don't leave null when picking).`})}})})(o),i={inputTokens:0,outputTokens:0,totalTokens:0},l=(e=>0===e.size?'{ "tools": ["toolName", ...] }':`{\n "tools": ["toolName", ...],\n "toolScopes": {\n${[...e].map(e=>` "${e}": { "kinds": [...] } | null`).join(",\n")}\n }\n}\nFor every scopable tool you LIST in "tools", the matching "toolScopes" entry MUST be a non-null object \`{ "kinds": [...] }\`. For scopable tools you DON'T pick, set the corresponding "toolScopes" entry to \`null\`.`)(o),c=`${(e=>{const t=Object.entries(e).map(([e,t])=>((e,t)=>`${e}${t.classificationPrompt?` ${t.classificationPrompt}.`:""}${t.dependsOn?.length?` Depends on: ${t.dependsOn.join(", ")}.`:""}${t.scopePrompt?` SCOPE: ${t.scopePrompt}`:""}`)(e,t)),r=Hn(e);return["You are a tool selector for a map assistant.","You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.","PREREQUISITES: each tool below may list `Depends on: <tools>` — the tools that produce the data it reads. When you pick a tool, ALSO pick every tool in its `Depends on:` list, UNLESS the conversation shows that prerequisite is already satisfied (the data was loaded, or the place located, on an earlier turn). E.g. arming a tracker/alert on a fresh area needs getTrafficIncidents (to load the incidents) and locatePlace (for a named area) picked alongside it.",'LOCATION REFERENCE:\n - "near me" / "my location" / "where I am" → getCurrentLocation (user\'s physical GPS position, may prompt permission)\n - "in this area" / "here on the map" / "near the map center" → getViewport (map\'s current view)\n - If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point',"","Available tools:",...t,...r.length?["","TOOL SCOPES — MANDATORY when selecting a scopable tool:",`The following tools REQUIRE a scope entry in \`toolScopes\` whenever they appear in \`tools\`: ${r.join(", ")}.`,"For each picked scopable tool, emit `toolScopes[<name>]` with the per-tool object shape described in its SCOPE hint above. Don't omit. Don't leave it implicit.","If the user query truly needs all input kinds the tool supports, still emit the scope explicitly — list every kind. Never skip the scope entry when picking the tool."]:[]].join("\n")})(s)}\n\nReturn JSON matching this shape exactly:\n${l}`;let d;for(let p=0;p<2;p++)try{const s=await r({model:n,output:e.json(),system:c,messages:t,maxOutputTokens:400});Jn(i,s.usage);const o=a.safeParse(s.output);if(o.success)return Xn(o.data,i);d=Kn(s.output)??d}catch(u){const e=u;Jn(i,e.usage),d=Kn(e.value)??d}if(!d)throw new Error("Classifier failed after 2 attempts to produce a valid response.");return{tools:d.tools,...d.scopes&&{toolScopes:d.scopes},usage:i}},Qn=async(e,t)=>{const{chatModel:r,toolsMetadata:n}=t,s=performance.now(),{tools:o,toolScopes:a,usage:i}=await Zn(e,r,n);return{activeToolNames:o,...a&&{toolScopes:a},timeMs:performance.now()-s,usage:i}},es=e=>"text"===e.type,ts=e=>{if("string"==typeof e)return e;const t=e.filter(es);return t.length>0?t.map(e=>e.text).join(""):null},rs=e=>{for(let t=e.length-1;t>=0;t--)if("user"===e[t].role)return ts(e[t].content);return null},ns=e=>{const{model:t,maxHistoryMessages:r,maxHistoryMessageLength:n}=e;return async e=>{const{messages:s,toolsMetadata:o}=e;if(!s?.length)return null;const a=r??8,i=n??300;let l=-1,c=null;for(let t=s.length-1;t>=0;t--)if("user"===s[t].role){l=t,c=ts(s[t].content);break}if(!c)return null;const d=[];for(let t=Math.max(0,l-a);t<l;t++){const e=s[t];if("user"!==e.role&&"assistant"!==e.role)continue;const r=ts(e.content);if(!r)continue;const n=r.length>i?r.slice(0,i)+"…":r;d.push({role:e.role,content:n})}d.push({role:"user",content:c});try{return await Qn(d,{chatModel:t,toolsMetadata:o})}catch{return null}}},ss=e=>{if(!Array.isArray(e))return e.properties?.address?.freeformAddress},os=(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 n=`${e} ${1===e?"place":"places"}`;return r.length?`${r.join(" ")} (${n})`:n})(e.features.length,t);const r=e.properties?.poi?.name,n=e.properties?.address?.freeformAddress;return r&&n?`${r}, ${n}`:r||(n||(t?.query?t.query:"Place"))},as=e=>{const t=e.properties?.address?.freeformAddress;if(!t)return;const r=e.properties?.address?.countryCode;if(!r)return t;const n=`, ${r}`;return t.toLowerCase().endsWith(n.toLowerCase())?t:`${t}${n}`},is={timeMinutes:"min",distanceKM:"km",remainingChargeCPT:"% remaining",spentChargePCT:"% spent",spentFuelLiters:"L"},ls=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${is[e.type]??e.type}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`,cs=(e,t={})=>{const r={id:e.id,type:e.properties?.type,name:e.properties?.poi?.name,address:e.properties?.address?.freeformAddress,position:e.geometry?.coordinates??[0,0]};if(t.experimentalSearch){const t=e.properties;t?.areaId&&(r.areaId=t.areaId),t?.areaCountry&&(r.areaCountry=t.areaCountry),t?.areaTags?.length&&(r.areaTags=t.areaTags)}return r},ds=(e,t={})=>{const r=Array.isArray(e)?e:e.features;return{count:r.length,features:r.slice(0,5).map(e=>cs(e,t))}},us=e=>{const{summary:t,sections:r,index:n}=e.properties,s=r.leg,o=e=>({...e,arrivalTime:e.arrivalTime.toISOString(),departureTime:e.departureTime.toISOString()});return{index:n,summary:o(t),...s.length>1&&{legSummaries:s.map(e=>o(e.summary))}}},ps=e=>({count:e.features.length,routes:e.features.map(us)}),hs=oe.object({slotIndex:oe.number(),isFilled:oe.boolean(),address:oe.string().optional(),position:oe.array(oe.number()).length(2).optional().describe("[longitude, latitude]")}),ys=e=>{if(null==e)return null;const t=J(e);let r;return Array.isArray(e)||(r=e.properties?.address?.freeformAddress),{slotIndex:-1,isFilled:!0,...r&&{address:r},...t&&{position:t}}};function ms(e,t,r){switch(e.kind){case"within":return fs(t,r);case"nearby":return bs(t,r);case"global":return ws()}}var gs,fs=(e,t)=>Es(Is(e,t),t),bs=async(e,t)=>{const r=Is(e,t),n=r.mapViewport.center;if(!r.queries?.length)return r.useViewport&&n?{position:n}:{};const s=await Ds(r,t,n);if(0===s.length)return{};const o=J(s[0]);if(!o)return{};const a=as(s[0]);return{position:o,query:r.queries[0].query,...a&&{label:a}}},ws=()=>({}),vs=e=>"object"==typeof e&&null!==e&&"error"in e,Is=(e,t)=>{const{viewport:r,boundingBox:n,queries:s,placeIds:o,geometries:a,route:i}=e;return{useViewport:r,boundingBox:n,queries:s,placeIds:o,geometries:a,route:i,mapViewport:t.viewport()}},Es=async(e,t)=>{const{useViewport:r,boundingBox:n,queries:s,placeIds:o,geometries:a,route:i,mapViewport:l}=e,c=[];n&&c.push({bbox:n,source:"boundingBox"});const d=await Ss(s,t,l);if(vs(d))return d;c.push(...d);const u=await Ms(o,t);if(vs(u))return u;if(c.push(...u,...ks(a)),i){const e=await xs(i,t);if(vs(e))return e;c.push(...e)}return c.length>0?c:r?l.bbox?[{bbox:l.bbox,source:"viewport"}]:{error:"No map viewport available to resolve as an area."}:c},Ss=async(e,t,r)=>{const n=[];for(const{query:s}of e??[]){const e=(await t.geocodeAreas(s,void 0,r.center)).filter(e=>e.bbox);if(0===e.length)return{error:`Could not resolve "${s}" to an area. Provide a boundingBox or a more specific place.`};const o=(r.center?As(e,r.center):e)[0],a=as(o),i=o.properties.dataSources?.geometry?.id?await t.fetchGeometry(o):void 0;n.push(i?{bbox:i.bbox??W(i),polygon:i.geometry,label:a,source:"query",query:s}:{bbox:o.bbox,label:a,source:"query",query:s})}return n},Ms=async(e,t)=>{const r=[];for(const n of e??[]){const e=t.expandEntry(n);if(0===e?.length)return{error:`Places entry "${n}" has no place with a geometry data source. Provide a boundingBox or a different entry.`};for(const s of e??[n]){const e=await t.fetchGeometry(s);if(!e)return{error:`Unknown placeId "${s}" or it has no fetchable geometry. Use recallState to list available IDs.`};r.push({bbox:W(e),polygon:e.geometry,source:"placeId"})}}return r},ks=e=>{const t=[];for(const r of e??[]){const e=W(r);e&&t.push({bbox:e,polygon:r,source:"geometries"})}return t},xs=async(e,t)=>{const r=t.getRoute(e.routeId);if("error"in r)return r;const n=[];for(const s of r.features){const t=se.buffer(s,e.widthMeters/2,{units:"meters"});t&&n.push({bbox:W(t),polygon:t.geometry,label:r.label,source:"route"})}return 0===n.length?{error:"Route produced no corridor geometry."}:n},As=(e,t)=>{const r=[...e].sort((e,r)=>{if(void 0===e.bbox||void 0===r.bbox)return 0;const n=se.distance(G(e.bbox),t)-se.distance(G(r.bbox),t);return Math.abs(n)>1e3?n:0});return e.map((e,t)=>({place:e,rank:(t+r.indexOf(e))/2})).toSorted((e,t)=>e.rank-t.rank).map(e=>e.place)},Ds=async(e,t,r)=>{const n=[];for(const{query:s,queryAs:o}of e.queries??[]){const e=await t.geocodeAreas(s,o,r);n.push(...e)}return n},Ts=async(e,t,r)=>{await In(e.byod,[t],r.hidePreviousEntries),await e.byod.showEntry(t);let n=!1;if("auto"===r.zoomMode){const t=(e=>{const t=[];for(const r of e.byod.shownEntryIds){const n=e.byod.findById(r);n&&t.push(...n.data.features)}return t})(e);if(t.length>0){const r=W({type:"FeatureCollection",features:t});r&&(e.baseMap.mapLibreMap.fitBounds(r,{padding:50}),n=!0)}}return{zoomMode:n}},Ps=async(e,t,r,n)=>{const s=await e.places.fetchGeometriesForEntry(t,n);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}},_s=async(e,t,r)=>{const n={markerType:!1,zoomMode:!1};if("none"!==r.markerType){if("multiple"===e.places.entryMode&&"all"!==r.hidePreviousEntries){if(Array.isArray(r.hidePreviousEntries)){const n=new Set(t),s=r.hidePreviousEntries.filter(e=>!n.has(e));s.length>0&&await e.places.removeShownEntries(s)}await e.places.addShownEntries(t,r.markerType)}else await e.places.setShownEntries(t,r.markerType);n.markerType=!0}if("auto"===r.zoomMode){const t=e.places.getShownFeatures();if(t.length>0){const r=W({type:"FeatureCollection",features:t});r&&e.baseMap.mapLibreMap.fitBounds(r,{padding:50})}n.zoomMode=!0}return n},Os=6,Cs=(e,t=6)=>{if(e.mapLibreMap.getZoom()<t)return;const r=e.mapLibreMap.getCenter();return[r.lng,r.lat]},Rs=e=>{const t=e.mapLibreMap.getBounds();return[t.getWest(),t.getSouth(),t.getEast(),t.getNorth()]},js=(e,t)=>({viewport:()=>{let t,r;try{t=Rs(e.baseMap)}catch{t=void 0}try{r=Cs(e.baseMap)}catch{r=void 0}return{bbox:t,center:r}},expandEntry:t=>e.places.geometryPlaceIdsForEntry(t),geocodeAreas:(e,r,n)=>r?En(e,r,{limit:5,bias:n?{position:n}:void 0},t):vn(e,{bias:n},t),fetchGeometry:async r=>{if("string"==typeof r)return e.places.fetchPlaceGeometry(r,t);if(!r.properties.dataSources?.geometry?.id)return;const n=Te({geometries:[r],signal:t?.signal});return(await O(n)).features[0]},getRoute:t=>{const r=e.routing.entries;if(0===r.length)return{error:"No routes available yet. Calculate or select a route first (e.g. calculateRoute), then retry."};const n=t?r.find(e=>e.id===t):r.at(-1);if(!n)return{error:`Route "${t}" not found. Use recallState to list available route IDs.`};const s=n.data.features;return 0===s.length?{error:`Route "${n.id}" has no geometry. Recompute it via calculateRoute.`}:{features:s,label:n.label}}}),Bs=oe.object({added:oe.array(oe.string()).describe("Source IDs added by the executed code."),removed:oe.array(oe.string()).describe("Source IDs removed by the executed code."),updated:oe.array(oe.string()).describe("Source IDs whose definition changed.")}),Ns=oe.object({added:oe.array(oe.string()).describe("Layer IDs added by the executed code."),removed:oe.array(oe.string()).describe("Layer IDs removed by the executed code."),updated:oe.array(oe.string()).describe("Layer IDs whose properties changed.")}),Ls=oe.object({code:oe.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.')}),Fs=oe.union([oe.object({success:oe.literal(!0),result:oe.unknown().optional().describe("Return value of the executed code, if any."),sources:Bs,layers:Ns}),jt]),qs=e=>{const t=e.getStyle()?.sources??{},r=/* @__PURE__ */new Map;for(const[n,s]of Object.entries(t))r.set(n,JSON.stringify(s));return r},$s=e=>{const t=e.getStyle()?.layers??[],r=/* @__PURE__ */new Map;for(const n of t)r.set(n.id,JSON.stringify(n));return r},Gs=(e,t)=>{const r=[],n=[],s=[];for(const[o,a]of t){const t=e.get(o);void 0===t?r.push(o):t!==a&&s.push(o)}for(const o of e.keys())t.has(o)||n.push(o);return{added:r,removed:n,updated:s}},Us=oe.union([oe.object({success:oe.literal(!0)}),jt]),Ws=oe.object({where:oe.union([oe.object({boundingBox:Tr.describe("BBox [W,S,E,N]."),padding:oe.number().optional().describe("Padding in pixels. Default: 50.")}).describe("Fit the camera to a bounding box."),oe.object({position:Ar.describe("[lng, lat] to fly to."),zoom:oe.number().optional().describe("Camera zoom level. Default: 14.")}).describe("Fly the camera to a specific position.")])}),zs=oe.union([oe.object({layers:oe.array(oe.object({id:oe.string(),layout:oe.unknown().optional(),paint:oe.unknown().optional()}))}),jt]),Ys=oe.object({layerIdQuery:oe.string().optional().describe("Case-insensitive partial match on layer IDs. If omitted, all layers are returned."),include:oe.array(oe.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.')}),Hs=oe.union([oe.object({center:oe.array(oe.number()).describe("[lng, lat]"),zoom:oe.number().describe("0–22"),bbox:oe.array(oe.number()).describe("[W, S, E, N]"),pitch:oe.number().describe("degrees, 0=top-down, 85=near-horizontal"),bearing:oe.number().describe("degrees, 0=north, 90=east")}),jt]),Vs=oe.object({}),Js=oe.union([oe.object({layerId:oe.string(),propertyName:oe.string(),value:oe.unknown(),success:oe.literal(!0)}),oe.object({layerId:oe.string(),propertyName:oe.string(),error:oe.string()})]),Ks=oe.union([oe.object({results:oe.array(Js)}),jt]),Xs=oe.object({changes:oe.array(oe.object({layerId:oe.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:oe.string().describe("e.g. visibility|text-field|icon-size"),value:oe.any()}))}).describe("List of layout property changes to apply in one go."),Zs=oe.union([oe.object({layerId:oe.string(),propertyName:oe.string(),value:oe.unknown(),success:oe.literal(!0)}),oe.object({layerId:oe.string(),propertyName:oe.string(),error:oe.string()})]),Qs=oe.union([oe.object({results:oe.array(Zs)}),jt]),eo=oe.object({changes:oe.array(oe.object({layerId:oe.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:oe.string().describe("e.g. fill-color|line-width|text-color"),value:oe.any()})).describe("List of paint property changes to apply in one go.")}),to=oe.union([oe.object({success:oe.literal(!0)}),jt]),ro=oe.object({pitch:oe.number().min(0).max(85).optional().describe("0=top-down, 85=near-horizontal"),bearing:oe.number().min(-180).max(180).optional().describe("0=north, 90=east")}),no=oe.union([oe.object({zoom:oe.number().describe("Resulting zoom level after the change")}),jt]),so=oe.object({delta:oe.number().describe("Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.")}),oo=async(e,t,r)=>{if("position"in e){const[t,r]=e.position;return{place:[t,r],position:[t,r],name:`[${t}, ${r}]`}}if("placeIdOrEntryId"in e){const{placeIdOrEntryId:r}=e;let n=t.places.findPlaceById(r);if(!n){const e=t.places.entries.find(e=>e.id===r),s=e?.data[0];s&&(n={place:s,entryId:e.id})}if(!n)return null;const s=J(n.place);return s?{place:n.place,position:s,name:n.place.properties?.poi?.name??n.place.properties?.address?.freeformAddress??r}:null}const n=await Sn(e.query,e.queryAs,void 0,r);if(!n)return null;const s=J(n);return s?{place:n,position:s,name:n.properties?.address?.freeformAddress??e.query,query:e.query}:null},ao=oe.object({routeType:oe.enum(j).optional().describe("fast|short|efficient|thrilling"),traffic:oe.enum(["live","historical"]).optional().describe("live|historical. `historical` picks the TYPICAL route path (ignores current incidents when routing) while still reporting live delays — use it with startRouteMonitor so each tick refreshes delays on the SAME paths instead of re-routing around current traffic."),avoid:oe.array(oe.enum($)).optional().describe("tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones"),avoidAreas:oe.array(Tr.describe("GeoJSON bbox [minLng, minLat, maxLng, maxLat]")).max(10).optional().describe("Up to 10 bounding boxes to bypass.")}),io=oe.object({option:oe.enum(["departAt","arriveBy"]),date:oe.string().describe("ISO 8601")}),lo=oe.object({maxAlternatives:oe.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:ao.optional(),when:io.optional().describe("Omit to depart now.")}),co=oe.union([Yt,oe.object({success:oe.literal(!0)}),jt]),uo=oe.object({locations:oe.array(Mn).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:lo.optional().describe("Routing options. Omitted fields keep their current values."),showOnMap:oe.boolean().describe("Show the route on the map after calculation."),showWaypoints:oe.boolean().optional().describe("Show the origin/stop/destination pins (default true). Pass false to draw the route line without waypoint markers — e.g. a monitored traffic corridor where the pins add noise. Only applies when showOnMap is true."),showSummaryBubbles:oe.boolean().optional().describe("Show the per-route ETA/summary bubbles (default true). Pass false to drop them — e.g. when monitoring traffic on a corridor, where the bubbles add noise. Only applies when showOnMap is true."),monitor:oe.boolean().optional().describe('Arm a live route monitor on the new route immediately (default false) — recalculates the same route every 60s so its live-traffic delays stay current, no separate startRouteMonitor call needed. Pair with `costModel.traffic: "historical"` + `maxAlternatives: 1-2` so the typical paths stay stable while delays refresh. Ignored when no route is calculated.'),hidePreviousEntries:qr("routes")}).refine(e=>void 0!==e.locations||void 0!==e.parameters,{message:"Provide at least one of `locations` or `parameters`."}),po=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},ho=e=>{const{maxAlternatives:t=0,costModel:r,when:n}=e;return{...t>0&&{maxAlternatives:t},...r&&{costModel:{...r,...r.avoidAreas&&{avoidAreas:r.avoidAreas}}},...n&&{when:{option:n.option,date:new Date(n.date)}}}},yo=(e,t)=>()=>{const r=Te({locations:e,...ho(t)});return T(r)},mo=async(e,t,r,n,s,o,a,i)=>{const l=Te({locations:t,...ho(e.routing.params),signal:i?.signal}),c=await T(l),d=await e.routing.addRoutes(c,t,((e,t)=>{const r=e.features[0]?.properties?.summary,n=[r?H(r.travelTimeInSeconds):void 0,r?Y(r.lengthInMeters):void 0].filter(Boolean),s=n.length?` (${n.join(", ")})`:"",o=t.map(ss).filter(Boolean);if(o.length>=2){const e=o[0],t=o.at(-1),r=o.slice(1,-1);return`${r.length>0?`${e} via ${r.join(", ")} to ${t}`:`${e} to ${t}`}${s}`}return`Route${s}`})(c,t));if(r&&await(async(e,t,r,n,s,o,a)=>{await In(e.routing,[t],s),await e.routing.showEntry(t,{showWaypoints:!1!==o,showSummaryBubbles:!1!==a});const i=W(r);i&&e.baseMap.mapLibreMap.fitBounds(i,{padding:50})})(e,d,c,0,n,o,a),s){const t=e.routing.entries.find(e=>e.id===d);t&&e.routing.startMonitoring(d,{recalculate:yo(t.waypoints,t.params)})}return{...ps(c),entryId:d}},go=oe.union([Yt,jt]),fo=oe.object({origin:Mn.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:Mn.optional().describe("Append a new destination after the current destination. The previous destination is kept as an intermediate stop (it is not replaced)."),stops:oe.array(Mn).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:oe.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:qr("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`."}),bo=e=>oe.union([$t(e).extend({shown:Gr.optional(),placesEntryId:oe.string().optional(),label:oe.string().optional().describe('Human-readable label stored with the entry (e.g. "cafe", "CINEMA (50 places)").'),geometriesFetched:oe.number().optional().describe("Count of boundary polygons fetched and cached on the entry (when `geometries` was set)."),geometriesShown:oe.boolean().optional().describe("Whether the fetched polygons were rendered on the map."),resolvedAreas:Nt.optional()}),jt]),wo=(bo({}),{boundingBoxes:oe.array(Tr).min(1).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:oe.array(oe.string()).min(1).optional().describe('Exact, case-sensitive municipality names (e.g. ["Amsterdam", "Utrecht"]). EXCLUSIVE with viewport; composes with other multi-region fields.'),areaId:oe.string().min(1).optional().describe('Id of a small area polygon (few km², not a whole municipality) — restricts results to that single area. Typically chained from a previous hit\'s `areaId` ("what else is in this same area?"). EXCLUSIVE with viewport; composes with other multi-region fields.')}),vo=e=>oe.object(e.experimentalSearch?{...Qr,...wo}:Qr).refine(e=>{const t=!0===e.viewport,r=e,n=void 0!==e.queries||void 0!==e.placeIds||void 0!==e.geometries||void 0!==e.range||void 0!==e.route||void 0!==r.boundingBoxes||void 0!==r.municipalities||void 0!==r.areaId;return(!t||!n)&&(t||n)},{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."}),Io=oe.object({mode:oe.literal("maxDetour"),routeId:oe.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),maxDetourTimeSeconds:oe.number().positive().describe("Max extra travel time (seconds) the user accepts to reach a POI."),sortBy:oe.enum(["detourTime","detourOffset"]).optional().describe('"detourTime" (default) ranks by extra travel time; "detourOffset" by distance from the route line.'),limit:oe.number().max(100).optional().describe("Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.")}),Eo=e=>{const t=e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/areaId/geometries/range/route":"queries/placeIds/geometries/range/route";return oe.union([vo(e),Kr,Io,Xr]).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 }\`.`)},So=e=>{const t={query:oe.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:Eo(e).optional(),poiCategories:oe.array(oe.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:$r.optional().describe("Display places/geometries on the map. Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry."),entryId:jr,geometries:Nr},r=e.experimentalSearch?{...t,placeTypes:oe.array(oe.enum(["POI","PointAddress","Street"])).optional().describe("Restrict to record types (default: all). POI = businesses/landmarks/amenities; PointAddress = numbered addresses; Street = unnumbered streets."),areaTags:oe.array(oe.enum(x)).optional().describe('Tokens describing a small surrounding area (few km², not a whole municipality). Enum drawn from the SDK canonical vocabulary — e.g. "coastal", "walkable", "alpine", "transit_connected", "village", "tourism_economy". OR semantics — matches places in areas tagged with ANY token. Filter works on any `where` scope (including global), but area data is currently populated only for DE / NL / FR — zero hits where data is absent. Filter, not a geo-bias — pair with a `where` mode that supplies one.')}:t;return oe.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`)."})},Mo=e=>`Search for MULTIPLE places in a region — REQUIRES a search subject (\`query\` and/or \`poiCategories\`). NOT for a single named place — use locatePlace for that (including its polygon/boundary via \`geometry\`). Stores an entry and returns \`placesEntryId\`. CALL ONCE PER REQUEST — batch all \`poiCategories\` + areas in one call; split only for explicitly comparable result sets. \`where.mode\`: \`within\` (\`viewport\` OR any combo of ${e.experimentalSearch?"boundingBoxes/queries/placeIds/municipalities/areaId/geometries/range/route":"queries/placeIds/geometries/range/route"}), \`nearby\` (position/viewport/query + optional radiusMeters), \`maxDetour\` (≤100 ranked detours off a route), \`global\`. `+(e.experimentalSearch?"Prefer `municipalities` / `areaId` / `queries` / `placeIds` (precise) over `boundingBoxes` (coarse). ":"Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ")+(e.experimentalSearch?'Top-level `areaTags` narrows hits by small-area character ("coastal", "walkable", …) — area data currently populated only in DE/NL/FR. ':"")+'Route: `within.route` = bulk corridor (≤10000); `maxDetour` = short ranked list (≤100). Default: `{ mode: "within", viewport: true }`. Never call this with empty `query` AND empty `poiCategories` — that is locatePlace territory.',ko={},xo=(So(ko),Mo(ko),e=>{const{geometries:t}=e;return t?.length?{geometries:t}:{}}),Ao=(e,t,r)=>{const{query:n,poiCategories:s,bias:o,radiusMeters:a,multiFilters:i}=t,l={query:n,poiCategories:s,signal:r?.signal};if(e){const e=[...i.boundingBoxes??[],...o?.boundingBox?[o.boundingBox]:[]],t=Te({...l,limit:1e4,...i,...e.length&&{boundingBoxes:e},...o?.position&&{geoBias:{position:o.position,radiusMeters:a}}});return P(t)}const c=((e,t)=>e?.position?{position:e.position,radiusMeters:t}:e?.boundingBox?{boundingBox:e.boundingBox}:void 0)(o,a),d=Te({...l,limit:100,...xo(i),...c&&{geoBias:c}});return B(d)},Do=async(e,t,r,n)=>{if("nearby"===e.mode){const s=await(async(e,t,r)=>{if(e.position)return{bias:{position:e.position},radiusMeters:e.radiusMeters};const n=await bs({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},js(t,r)),s=n.label?[{matched:n.label,...void 0!==n.query&&{query:n.query}}]:[];return{bias:n.position?{position:n.position}:{},radiusMeters:e.radiusMeters,...s.length>0&&{resolvedAreas:s}}})(e,t,n);return{bias:s.bias,multiFilters:r,radiusMeters:s.radiusMeters,resolvedAreas:s.resolvedAreas}}if("within"===e.mode){const s=await(async(e,t,r,n)=>{if(e.viewport)return{bias:{boundingBox:Rs(t.baseMap)},multiFilters:r};const s=await fs({boundingBox:void 0,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},js(t,n));if(vs(s))return s;const o=s.filter(e=>e.polygon).map(e=>e.polygon),a=s.filter(e=>!e.polygon).map(e=>e.bbox),i=s.find(e=>"route"===e.source)?.label,l=s.filter(e=>"query"===e.source&&e.label).map(e=>({matched:e.label,...void 0!==e.query&&{query:e.query}})),c=e,d=[...c.boundingBoxes??[],...a],u=[...o];return{bias:{},multiFilters:{...r,...c.municipalities?.length&&{municipalities:c.municipalities},...c.areaId&&{areaId:c.areaId},...d.length&&{boundingBoxes:d},...u.length&&{geometries:u}},range:e.range,routeLabel:i,...l.length>0&&{resolvedAreas:l}}})(e,t,r,n);return"error"in s?s:{bias:s.bias,multiFilters:s.multiFilters,range:s.range,routeLabel:s.routeLabel,resolvedAreas:s.resolvedAreas}}return{bias:{},multiFilters:r}},To=async(e,t,r,n,s,o,a,i)=>{const l=n?await _s(e,[r],n):void 0;let c,d;if(s){const t=await Ps(e,r,s,i);c=t.fetched,t.shown&&(d=!0)}const u=e.places.entries.find(e=>e.id===r)?.label;return{...ds(t,o),placesEntryId:r,...u&&{label:u},...l&&{shown:l},...void 0!==c&&{geometriesFetched:c},...void 0!==d&&{geometriesShown:d},...a&&a.length>0&&{resolvedAreas:a}}},Po=e=>{const t=!0===e.experimentalSearch;return async(r,n,s)=>{const{query:o,where:a,poiCategories:i,show:l,entryId:c,geometries:d}=r,u=r,p=u.placeTypes,h=u.areaTags,{resolved:y,unresolved:m}=await zn(i,s);if(i?.length&&!y)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 g=a??{mode:"within",viewport:!0},f={...p?.length&&{placeTypes:p},...h?.length&&{areaTags:h}};try{if("maxDetour"===g.mode){const t=await(async(e,t,r,n,s,o)=>{const a=e.routing.entries;if(0===a.length)return{error:"No routes in state. Calculate a route first via setRoute."};const i=t.routeId?a.find(e=>e.id===t.routeId):a.at(-1);if(!i)return{error:`No route found with id "${t.routeId}". Use recallState to list available routes.`};const l=i.data.features[0];if(!l)return{error:`Route entry "${i.id}" has no geometry.`};const c=Te({route:l,maxDetourTimeSeconds:t.maxDetourTimeSeconds,...r&&{query:r},...n&&{poiCategories:n},...t.sortBy&&{sortBy:t.sortBy},...void 0!==t.limit&&{limit:t.limit},signal:o?.signal}),d=await A(c);return{result:d,placesEntryId:await e.places.addPlaceResult(d,os(d,{query:r,poiCategories:n,routeLabel:i.label}),s)}})(n,g,o,y,c,s);return"error"in t?t:To(n,t.result,t.placesEntryId,l,d,e,void 0,s)}const r=await Do(g,n,f,s);if("error"in r)return r;const{bias:a,multiFilters:i,radiusMeters:u,range:p,routeLabel:h,resolvedAreas:m}=r;if(p){const r=await(async(e,t,r,n,s,o,a,i,l)=>{const c=Yn(e,t);if("error"in c)return c;const d=[...c.polygons,...s.geometries??[]],u=await Ao(i,{query:r,poiCategories:n,multiFilters:{...s,geometries:d}},l);return{result:u,placesEntryId:await e.places.addPlaceResult(u,os(u,{query:r,poiCategories:n,...a&&{routeLabel:a}}),o)}})(n,p,o,y,i,c,h,t,s);return"error"in r?r:To(n,r.result,r.placesEntryId,l,d,e,m,s)}const b=(m?.length?m.map(e=>e.matched).join(", "):void 0)??(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})(g),w=await(async(e,t,r,n,s,o,a,i,l,c,d)=>{const u=await Ao(c,{query:t,poiCategories:n,bias:r,radiusMeters:s,multiFilters:o},d);return{result:u,placesEntryId:await e.places.addPlaceResult(u,os(u,{query:t,poiCategories:n,where:i,...l&&{routeLabel:l}}),a)}})(n,o,a,y,u,i,c,b,h,t,s);return To(n,w.result,w.placesEntryId,l,d,e,m,s)}catch(b){return{error:`Search failed: ${b instanceof Error?b.message:String(b)}`}}}},_o=(Po(ko),{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. Also OWNS outlining the SUB-AREAS of a place — the neighbourhoods/districts of a city ("outline the Amsterdam neighbourhoods"); a SINGLE named place\'s own boundary is locatePlace, and incrementally adding/removing outlines among already-shown places is updatePlacesDisplay. Skip for already-stored places — use recallState/processData.',tags:["discover","place","location"],examples:['discoverPlaces({ poiCategories: ["RESTAURANT", "CAFE", "BAR"], entryId: "amsterdam-food-spots" }) // batch many categories + semantic id','discoverPlaces({ query: "coffee", where: { mode: "nearby", position: [4.9, 52.4], radiusMeters: 500 } })','discoverPlaces({ query: "parking", where: { mode: "within", queries: [{ query: "De Jordaan, Amsterdam", queryAs: "place" }] } }) // area name goes in `where.queries`, NOT top-level `query`','discoverPlaces({ query: "coffee", where: { mode: "nearby", query: "Schiphol Airport", queryAs: "poi", radiusMeters: 2000 } }) // POI-resolved bias point','discoverPlaces({ poiCategories: ["HOTEL"], where: { mode: "within", municipalities: ["Amsterdam", "Utrecht"] }, entryId: "ams-utrecht-hotels" })','discoverPlaces({ query: "bakery", where: { mode: "within", range: "ranges-0" } })','discoverPlaces({ poiCategories: ["CAFE"], show: { markerType: "pin", zoomMode: "auto" } }) // omit `where` → defaults to viewport','discoverPlaces({ poiCategories: ["RESTAURANT"], where: { mode: "within", queries: [{ query: "Paris" }] } }) // "restaurants in Paris" — subject in `poiCategories`, area in `where.queries`','discoverPlaces({ poiCategories: ["ELECTRIC_VEHICLE_STATION"], where: { mode: "within", route: { routeId: "routes-0", widthMeters: 1000 } } }) // bulk corridor scan along the route','discoverPlaces({ query: "coffee", where: { mode: "maxDetour", maxDetourTimeSeconds: 300, limit: 5 } }) // top 5 coffee detours within 5 min off the latest route','discoverPlaces({ poiCategories: ["ELECTRIC_VEHICLE_STATION"], where: { mode: "maxDetour", routeId: "routes-1", maxDetourTimeSeconds: 600, sortBy: "detourOffset" } }) // ranked EV detours on a specific route','discoverPlaces({ query: "bakery", where: { mode: "within", placeIds: ["G55fc4abe-..."] } }) // search inside stored place polygons (auto-fetched if needed)','discoverPlaces({ query: "vegan", where: { mode: "global" } })'],examplePrompts:["Find coffee shops near me","Italian restaurants in Amsterdam","EV chargers in the current map area","Hotels in Amsterdam and Utrecht","Bakeries within the reachable range","Outline the Amsterdam neighbourhoods","Search for EV chargers in a corridor along my planned 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","Find playgrounds in this neighbourhood","Show pharmacies near Berlin Hauptbahnhof"],relatedTools:["getPOICategoryCodes","updatePlacesDisplay","locatePlace","getViewport","findReachableAreas","processData","analyseData"],dependsOn:["getPOICategoryCodes","setRoute"]}),Oo=oe.object({type:oe.enum(["timeMinutes","distanceKM"]).describe("Budget type including its unit."),value:oe.number().describe("Budget value. e.g. 30 for 30 minutes, 50 for 50 km.")}),Co=oe.object({origins:oe.array(Mn).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:oe.array(Oo).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:oe.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:oe.boolean().describe("Whether to render the range polygon(s) on the map."),showOriginPin:oe.boolean().optional().describe("Whether to drop a pin at each origin. Default: true."),hidePreviousEntries:qr("ranges")}),Ro=oe.object({rangesId:oe.string(),ranges:oe.array(oe.object({originName:oe.string(),budgets:oe.array(Oo)})),skipped:oe.array(oe.object({origin:oe.string(),reason:oe.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry).")}),jo=oe.union([Ro,oe.object({status:oe.enum(["no_results"])}),jt]),Bo=async(e,t,r)=>{const n=e.map(e=>Te({origin:t,budget:{type:e.type,value:e.value}})),s=await D(n,{signal:r?.signal});return 0===s.features.length?null:s},No=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,Lo=e=>"query"in e?e.query:"position"in e?`${e.position[1]}, ${e.position[0]}`:"placeIdOrEntryId"in e?e.placeIdOrEntryId:"unnamed origin",Fo=oe.union([oe.object({count:oe.number().describe("Number of codes returned"),codes:oe.array(oe.string()).describe('POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. "RESTAURANT") also accepted by toggleTilesPOIs categories.')}),jt]),qo=oe.object({filters:oe.array(oe.string()).optional().describe("Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes."),language:oe.string().describe('Locale e.g. "en-GB", "fr-FR", "es-ES", "de-DE".')}),$o=oe.object({speed:oe.number().optional(),freeFlowSpeed:oe.number().optional(),congestionLevel:oe.number().optional(),travelTime:oe.number().optional(),networkLength:oe.number().optional()}),Go=oe.union([oe.object({entryId:oe.string().describe("Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData."),name:oe.string().optional(),timezone:oe.string().optional(),dateRange:oe.object({start:oe.string(),end:oe.string()}),baseData:$o,metrics:oe.array(oe.string()),tileCount:oe.number(),availableGranularities:oe.array(oe.string())}),jt]),Uo=oe.object({location:Mn.optional().describe("Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Provide this or bbox, never both."),bbox:oe.array(oe.number()).length(4).optional().describe("[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Provide this or location, never both."),showOnMap:oe.boolean().optional().describe("Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize."),startDate:oe.string().optional().describe("Start date 'YYYY-MM-DD'. Use with endDate for a continuous range (max 31 days)."),endDate:oe.string().optional().describe("End date 'YYYY-MM-DD' (inclusive). Defaults to today if omitted."),days:oe.array(oe.string()).optional().describe("Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate."),metrics:oe.union([oe.literal("all"),oe.array(oe.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:oe.union([oe.literal("all"),oe.array(oe.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:oe.union([oe.literal("all"),oe.array(oe.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.")}).refine(e=>void 0===e.location!=(void 0===e.bbox),{message:"Provide exactly one area: either `location` or `bbox`."}).refine(e=>!(e.days?.length&&(e.startDate||e.endDate)),{message:"Provide either `days` or a `startDate`/`endDate` range, not both."}),Wo=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`,zo=e=>e instanceof Date?Number.isNaN(e.getTime())?void 0:Wo(e):"string"==typeof e?e.slice(0,10):void 0,Yo=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),Ho=oe.union([oe.object({count:oe.number(),entryId:oe.string().optional(),resolvedAreas:Nt.optional(),entries:oe.array(oe.object({id:oe.string(),label:oe.string(),count:oe.number(),timestamp:oe.number()})),monitoring:oe.boolean().optional().describe("True when this fetch also armed a background monitor on the new entry.")}),jt]),Vo=oe.object({...Qr,boundingBox:Tr.optional().describe("Single bbox [W,S,E,N]. Composes with other multi-region fields (union); may also be combined with viewport, in which case these explicit bounds take precedence over it.")}).refine(e=>!0===e.viewport||void 0!==e.boundingBox||void 0!==e.queries||void 0!==e.placeIds||void 0!==e.geometries||void 0!==e.range||void 0!==e.route,{message:"`within` requires `viewport: true` OR one or more multi-region fields (boundingBox / queries / placeIds / geometries / range / route)."}),Jo=oe.union([Vo]).describe('Geographic scope. Only `within` mode (single area, defaults to `{ mode: "within", viewport: true }`). Use `viewport` alone for "the network / right now / this area", or any combination of boundingBox / queries / placeIds / geometries / range / route — each resolves to a bbox and the union is sent to the SDK (≤10,000 km²).'),Ko=`Filter by incident category (server-side). Canonical values: ${re.join(", ")}. Map the user's wording to the closest value. If omitted, all categories are returned.`,Xo=oe.object({where:Jo.optional().describe('Geographic scope. Default: `{ mode: "within", viewport: true }`.'),categoryFilter:oe.array(oe.enum([...re])).optional().describe(Ko),timeValidityFilter:oe.array(oe.enum(["present","future"])).optional().describe("Which incidents to fetch by time-validity. Default ['present'] — incidents active RIGHT NOW. Only pass 'future' when the user EXPLICITLY asks about upcoming, planned, or future incidents."),label:oe.string().optional().describe('Short human-readable label for this fetch — name the place ("Central London", "Amsterdam ring", "M25 corridor"), not coordinates. Surfaced verbatim in the monitor chip and on the entry chip in the UI; also used by analyseData / focusIncidents to disambiguate which entry to operate on. Always pass one when show is true (the default); the bbox-coordinate fallback ("bbox a,b → c,d") is for hidden (show: false) intermediate fetches only.'),show:oe.boolean().optional().describe("Default: true — incidents auto-render so the user can confirm the agent is looking at the right area. Set false ONLY for an extra throwaway load the user should not see — e.g. a second area fetched purely to compare counts. It is NOT needed to run clusterIncidents or analyseData: those read the entry you already loaded, so never re-fetch an area (shown or not) just to analyse it."),fitBounds:oe.boolean().optional().describe("Default: true. Pass false to skip camera move after rendering."),monitor:oe.boolean().optional().describe('Keep this entry live: poll its area in the background and re-run any registered analyses on each tick. Defaults to true for a shown fetch — live incidents go stale otherwise. Pass false for a one-shot snapshot (e.g. "how many incidents right now?"). Ignored when `show` is false (a hidden throwaway load is never monitored).'),monitorIntervalMs:oe.number().int().positive().optional().describe("Polling interval in ms when monitoring. Default 60000 (60s).")}),Zo=async(e,t,r)=>{const n=[];if(void 0!==e.range){const r=((e,t)=>{const r=Yn(e,t);return"error"in r?r:{bboxes:r.polygons.map(e=>W(e)).filter(e=>!!e)}})(t,e.range);if("error"in r)return r;n.push(...r.bboxes)}const s={viewport:e.viewport,boundingBox:e.boundingBox,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},o=await fs(s,js(t));if(vs(o))return o;n.push(...o.map(e=>e.bbox));const a=o.filter(e=>"query"===e.source&&e.label).map(e=>({matched:e.label,...void 0!==e.query&&{query:e.query}})),i=o.find(e=>"route"===e.source)?.label;return 0===n.length?{error:"No area resolved from `where`."}:{bbox:U(n),routeLabel:i,...a.length>0&&{resolvedAreas:a}}},Qo=oe.object({query:oe.string().describe("Location string to resolve for downstream routes or searches."),waypointIndex:oe.number().int().min(0).optional().describe("Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination."),queryAs:oe.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, geographies.'),where:Zr.optional().describe('Optional geographic scope. OMIT to resolve a uniquely-named place anywhere (mode "global", the default) — correct for landmarks, cities, and areas like "Schiphol" or "Westminster", on-screen or not. Use "nearby" (a SOFT bias toward a point / viewport centre / query) only to disambiguate same-named places. Use "within" only to HARD-restrict the result to a known area (boundingBox / query / placeId).'),show:$r.optional().describe("Map display actions after resolving. Skip for intermediate calculations."),entryId:jr,geometry:Nr}),ea=oe.union([Gt.extend({placesEntryId:oe.string().describe("Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData, or as `placeIdOrEntryId` to setRoute / addWaypointsToRoute to route to this place."),waypointIndex:oe.number().optional(),shown:Gr.optional(),geometryFetched:oe.boolean().optional().describe("Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set)."),geometryShown:oe.boolean().optional().describe("Whether the fetched polygon was rendered on the map.")}),jt]),ta=async(e,t,r)=>e&&"global"!==e.mode?"within"===e.mode?(async(e,t,r)=>{const n=await fs({viewport:e.viewport,boundingBox:e.boundingBox,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0,placeIds:e.placeId?[e.placeId]:void 0},js(t,r));return vs(n)?n:0===n.length?{bias:void 0}:{bias:{boundingBox:U(n.map(e=>e.bbox))}}})(e,t,r):(async(e,t,r)=>{if(e.position)return{bias:{position:e.position}};const{position:n}=await bs({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},js(t,r));return{bias:n?{position:n}:void 0}})(e,t,r):{bias:void 0},ra=oe.union([Yt,jt]),na=oe.object({waypointIndices:oe.array(oe.number().int()).min(1).describe("Indices of waypoints to remove. 0=origin, last=destination. Min 2 waypoints must remain. Duplicates are not allowed."),showOnMap:oe.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:qr("routes")}),sa=oe.union([Yt,jt]),oa=oe.union([oe.literal("origin"),oe.literal("destination"),oe.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)."),aa=oe.object({waypointIndex:oa,location:Mn.describe("The new waypoint that replaces the targeted one."),showOnMap:oe.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:qr("routes")}),ia=oe.union([Gt.extend({placesEntryId:oe.string().describe("Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData.")}),jt]),la=oe.object({position:Ar.describe("[longitude, latitude] GeoJSON position to reverse geocode")}),ca=oe.union([oe.object({byodEntryId:oe.string().describe("Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData)."),label:oe.string(),source:oe.union([oe.object({kind:oe.literal("url"),url:oe.string()}),oe.object({kind:oe.literal("inline")})]).describe("Where the data came from."),profile:Bt.describe("Runtime-inferred shape — geometry types plus a per-property profile (types, coverage, numeric/boolean examples; string example values are withheld). Use it to pick fields for analyseData / processData without re-fetching the raw GeoJSON, and to choose the `setByodLayers` layers that render this entry.")}),jt]),da=oe.object({label:oe.string().describe('Human-readable label for the new entry (e.g. "Sales territories", "Customer pins 2026-Q1").'),url:oe.url().optional().describe("URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. EXCLUSIVE with `data`."),data:oe.unknown().optional().describe('Inline GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`). EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.'),entryId:jr}).refine(e=>e.url?!e.data:!!e.data,{message:"Pass EXACTLY ONE of `url` or `data`."}),ua=26214400,pa=/* @__PURE__ */new Set(["http:","https:"]),ha=async e=>{const t=new AbortController,r=setTimeout(()=>t.abort(),15e3);try{const r=await fetch(e,{signal:t.signal});if(!r.ok)return{error:`Failed to fetch "${e}": HTTP ${r.status} ${r.statusText}`};const n=((e,t)=>{const r=e.headers.get("content-length");if(!r)return;const n=Number.parseInt(r,10);return Number.isFinite(n)&&n>ua?{error:`Response too large for "${t}": advertised ${n} bytes (max 26214400).`}:void 0})(r,e);if(n)return n;const s=await(async(e,t)=>{const r=e.body?.getReader();if(!r){const r=await e.text();return r.length>ua?{error:`Response too large for "${t}": ${r.length} bytes (max 26214400).`}:{value:r}}const n=[];let s=0;for(;;){const{done:e,value:o}=await r.read();if(e)break;if(s+=o.byteLength,s>ua)return await r.cancel(),{error:`Response exceeded 26214400 bytes while streaming "${t}". Refusing to load.`};n.push(o)}const o=new Uint8Array(s);let a=0;for(const i of n)o.set(i,a),a+=i.byteLength;return{value:(new TextDecoder).decode(o)}})(r,e);return"error"in s?s:{value:JSON.parse(s.value)}}catch(n){return((e,t)=>e instanceof Error&&"AbortError"===e.name?{error:`Fetch timed out after 15000ms for "${t}".`}:{error:`Failed to fetch "${t}": ${e instanceof Error?e.message:String(e)}`})(n,e)}finally{clearTimeout(r)}},ya=[...$n,"previous","now","log"],ma=async(e,t,r)=>{const n=await Ze(r.code,ya,[...Un(t.sandbox,{h3:ae,turf:se}),r.previous,new Date(r.sampledAt),()=>{}],"Analysis",e.codeExecution);return"error"in n?n:Ye(n.value,r.outputFormat)},ga=(rn(tn),oe.union([oe.object({affectedEntries:oe.array(oe.union([oe.object({kind:oe.literal("places"),id:oe.string()}),oe.object({kind:oe.literal("routes"),id:oe.string()}),oe.object({kind:oe.literal("incidents"),id:oe.string()}),oe.object({kind:oe.literal("customGeometries"),id:oe.string()}),oe.object({kind:oe.literal("trafficAreaAnalytics"),id:oe.string()}),oe.object({kind:oe.literal("byod"),id:oe.string()})])).describe("Entries the analysis was attached to: every contributing places / routes / incidents / customGeometries / trafficAreaAnalytics / byod entry. Ranges entries feed the aggregation but do not carry analyses."),sourceIds:oe.array(oe.unknown()).optional().describe(`${wr} (only present when \`geometriesEntryIDs\` was set).`),analysisId:oe.string().describe("Stable handle for this analysis. Pass to `monitorAnalysis` to keep it recomputing whenever its source entries change."),name:oe.string().describe("Unique name of the analysis within each affected entry."),description:oe.string().optional(),outputFormat:oe.enum(["json","chart"]),monitoring:oe.boolean().optional().describe("True when `monitor: true` was set — the analysis now recomputes on every source change."),analysis:oe.unknown(),skipped:oe.array(xn).optional().describe(br)}),jt])),fa=(e,t)=>{const r=sn(e,t),n=`At least one of ${an(r)} is REQUIRED.`,s={...dn(r,{verb:"analyse",requiredHint:n})};return s.name=oe.string().describe('Unique name within each affected entry (e.g. "incidents-per-cluster", "ev-near-route"). Reusing a name replaces the previous analysis on those entries.'),s.description=oe.string().optional().describe("Optional short description of what the analysis computes."),s.outputFormat=oe.enum(["json","chart"]).optional().describe(We),s.code=oe.string().describe(((e,t)=>{const r=sn(e,t),n=ln(r),s=cn(r),o=r.includes("customGeometries");return"Async JS that aggregates the injected inputs and returns the result.\n\n"+Gn(r,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading (\`if (places) ...\`, \`routes?.features.length\`, etc.). When defined:\n${n}\n\n${ze("counts, per-kind breakdowns, hex bins, intersections, distance bins")}\n\n`+(o?`${fr}\n\n`:"")+s})(e,t)),s.monitor=oe.boolean().optional().describe("When true, keep this analysis live — it recomputes on every change to its source entries (a monitored incidents poll, a re-search, …), no separate monitorAnalysis call needed. Omit (or false) for a one-shot run; you can still toggle it later via monitorAnalysis using the returned `analysisId`."),oe.object(s).refine(un,{message:pn(r)})},ba=(fa(tn,void 0),(e,t)=>{const r=sn(e,t).join(" / "),n="CONTRAST `processData`, which writes NEW renderable map entries (places / custom-geometries / BYOD); `analyseData` never renders or creates entries — only attaches metadata. Returns an `analysisId`; pass it to `monitorAnalysis` to keep the analysis recomputing whenever its source entries change.";return t?`Aggregate ${r} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ${n} Each scoped input is exposed as a merged collection and a per-entry record; fields are \`undefined\` when their \`*EntryIDs\` argument is omitted (guard with \`?.\`). Result is attached as \`_analysis[name]\` on every contributing entry. \`outputFormat: "json"\` (default) or \`"chart"\` (Chart.js config).`:`Aggregate ${r} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ${n} Each input is exposed as a merged collection and a per-entry record; fields are \`undefined\` when their \`*EntryIDs\` argument is omitted (guard with \`?.\` / \`if (places) …\`). Result is attached as \`_analysis[name]\` on every contributing entry (ranges feed in but do not carry analyses). \`outputFormat: "json"\` (default) or \`"chart"\` (Chart.js config).`}),wa=(ba(tn,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all).`),va=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i,name:l,description:c,code:d}=e,u=e.outputFormat??"json",p=await jn({placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i},t);if("error"in p)return{error:p.error};const{resolved:h,sandbox:y,geometries:m}=p.value,g=await Ze(d,$n,Un(y,{h3:ae,turf:se}),"Analysis",t.codeExecution);if("error"in g)return{error:g.error};const f=Ye(g.value,u);if("error"in f)return{error:f.error};const b=f.value,w=Date.now(),v=[...h.places.map(e=>({kind:"places",id:e.id})),...h.routes.map(e=>({kind:"routes",id:e.id})),...h.incidents.map(e=>({kind:"incidents",id:e.id})),...h.trafficAreaAnalytics.map(e=>({kind:"trafficAreaAnalytics",id:e.id})),...h.byod.map(e=>({kind:"byod",id:e.id})),...m.affectedEntries.filter(e=>"places"===e.kind&&!h.places.some(t=>t.id===e.id)).map(e=>({kind:"places",id:e.id})),...m.affectedEntries.filter(e=>"customGeometries"===e.kind).map(e=>({kind:"customGeometries",id:e.id}))],I=o?m.sourceIds:void 0,E=v.map(e=>e.id),S=((e,t)=>`${e}::${[...t].sort((e,t)=>e.localeCompare(t)).join(",")}`)(l,E),M=!0===e.monitor;return((e,t)=>{const r=()=>e.analyses.register({analysisId:t.analysisId,name:t.name,description:t.description,outputFormat:t.outputFormat,affectedEntryIds:t.affectedEntryIds}),n=e.analyses.job(t.analysisId);if(n&&n.code===t.code)return r(),void(t.monitor&&n.setActive(!0));n&&e.analyses.resetHistory(t.analysisId);const s=t.monitor||(n?.active??!1);let o;n?.unregister(),r(),o=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:s,run:async()=>{if(!e.analyses.get(t.analysisId))return;const r=await jn(t.inputs,e);if("error"in r)return;const n=Cn(r.value.resolved)??Date.now(),s=await ma(e,r.value,{code:t.code,previous:e.analyses.lastResult(t.analysisId),sampledAt:n,outputFormat:t.outputFormat});"error"in s||e.analyses.job(t.analysisId)!==o||e.analyses.attachResult(t.analysisId,{name:t.name,...t.description&&{description:t.description},outputFormat:t.outputFormat,data:s.value,timestamp:n})},onOrphaned:()=>e.analyses.remove(t.analysisId)}),e.analyses.setJob(t.analysisId,o)})(t,{analysisId:S,name:l,description:c,outputFormat:u,code:d,inputs:{placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i},affectedEntryIds:E,monitor:M}),t.analyses.attachResult(S,{name:l,description:c,outputFormat:u,data:b,timestamp:w}),{affectedEntries:v,...I&&{sourceIds:I},analysisId:S,name:l,...c&&{description:c},outputFormat:u,monitoring:M,analysis:b,...m.skipped.length&&{skipped:m.skipped}}},Ia=oe.object({trackerId:oe.string().optional().describe("Which tracker to clear. Omit when only one tracker exists.")}),Ea=oe.union([oe.object({cleared:oe.literal(!0),trackerId:oe.string(),name:oe.string()}),jt]),Sa=async(e,t,r,n)=>{const s=e.trafficIncidents.entries.find(e=>e.id===t);if(!s)return{error:`unknown incidents entry "${t}"`};if("params"===r.kind){const{groups:e}=It(s.data,r.params,n?.groups,s.timestamp,n?.sampledAt);return{value:{groups:e,sampledAt:s.timestamp}}}const o=await jn(r.inputs,e);if("error"in o)return o;const a=await ma(e,o.value,{code:r.code,previous:n,sampledAt:s.timestamp,outputFormat:"json"});return"error"in a?a:(i=a.value)&&"object"==typeof i&&Array.isArray(i.groups)?{value:a.value}:{error:"clustering `code` must return a cluster(...) result (`{ groups }`)"};var i},Ma=async(e,t,r)=>{Da(e);const n=e.trafficIncidents.getClustersRecord(t),s=n&&(o=n.recipe,a=r,JSON.stringify(o)===JSON.stringify(a))?n.output:void 0;var o,a;const i=await Sa(e,t,r,s);return"error"in i||e.trafficIncidents.setClusters(t,{recipe:r,output:i.value}),i},ka=/* @__PURE__ */new WeakMap,xa=e=>{let t=ka.get(e);return t||(t={running:!1,scheduled:!1,pending:/* @__PURE__ */new Set},ka.set(e,t)),t},Aa=/* @__PURE__ */new WeakSet,Da=e=>{Aa.has(e)||(Aa.add(e),e.trafficIncidents.events.on("entries-change",({changedIds:t})=>Ta(e,t)))},Ta=(e,t)=>{const r=xa(e);for(const n of t)r.pending.add(n);r.running||r.scheduled||(r.scheduled=!0,queueMicrotask(()=>{r.scheduled=!1,Pa(e)}))},Pa=async e=>{const t=xa(e),r=t.pending;t.pending=/* @__PURE__ */new Set,t.running=!0;try{for(const t of r){const r=e.trafficIncidents.getClustersRecord(t);if(!r)continue;const n=r.recipe,s=await Sa(e,t,n,r.output);"value"in s&&e.trafficIncidents.setClusters(t,{recipe:n,output:s.value})}}finally{t.running=!1}},_a=(e,t)=>{const r=t?sn(e,t).filter(e=>"incidents"!==e):[];return{context:r,active:["incidents",...r]}},Oa=e=>"OPTIONAL — omit for the default DBSCAN over all incidents (tune eps / minMembers / maxClusters). Async JS body: filter/combine the inputs, then RETURN `cluster(...)` (the only way to build the required `{ groups }` result) — e.g. `return cluster(filtered, { eps: 0.4 }, previous, now);`.\n\n"+Gn(e,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${ln(e)}\n\n`+cn(e),Ca=(e,t)=>{const{context:r,active:n}=_a(e,t);return oe.object({incidentsEntryID:oe.string().describe("Entry id from getTrafficIncidents — the incidents to cluster."),eps:oe.number().positive().optional().describe("DBSCAN radius in km. Default 0.5. Use 0.3–0.5 for dense urban, 1+ for highways. Ignored when `code` is set."),minMembers:oe.number().int().min(1).optional().describe("Minimum incidents per cluster. Default 3. Ignored when `code` is set."),maxClusters:oe.number().int().min(1).optional().describe("Top N clusters by total delay. Default 6. Ignored when `code` is set."),...dn(r,{verb:"analyse",requiredHint:"Only used by custom `code`."}),code:oe.string().optional().describe(Oa(n))})},Ra=(Ca(tn,void 0),oe.union([oe.object({incidentsEntryID:oe.string(),clusterCount:oe.number(),clusters:oe.array(oe.object({id:oe.string(),primaryRoads:oe.array(oe.string()),primaryCategory:oe.string(),size:oe.number(),totalDelaySeconds:oe.number(),peakDelaySeconds:oe.number(),trend:oe.enum(["growing","fading","steady","new"])}))}),jt])),ja="DBSCAN clustering of traffic incidents into compact pockets — stable IDs (survive monitor ticks), road/category labels, delay aggregates, multi-poll trends. Stored as dedicated clustering state on the entry and re-run each monitor tick (stays live without re-calling); the UI pins each group. Returns structured fields (roads, category, size, delay, trend) for the caller to phrase. One clustering per entry (re-calling replaces it). Defaults to DBSCAN over all incidents (tune eps / minMembers / maxClusters); pass `code` to filter/combine incidents dynamically first. Pair with getTrafficIncidents + setTrafficIncidentsMonitor.",Ba=e=>{const t=e.filter(e=>"incidents"!==e);return t.length?`${ja} Custom \`code\` can also filter incidents against ${t.join(" / ")}.`:ja},Na=(Ba(["incidents"]),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` — always include "incidents" (the clustering source). Add another kind ONLY when the query filters incidents against it (e.g. "near depots" → places, "along the route" → routes, "within this zone" → customGeometries). Default: \`{ kinds: ["incidents"] }\`.`),La=async(e,t)=>{const{incidentsEntryID:r,eps:n,minMembers:s,maxClusters:o,code:a}=e,i=t.trafficIncidents.entries.find(e=>e.id===r);if(!i)return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const l={...null!=n&&{eps:n},...null!=s&&{minMembers:s},...null!=o&&{maxClusters:o}},c=a?{kind:"code",code:a,inputs:{incidentsEntryIDs:[r],...e.placesEntryIDs?.length&&{placesEntryIDs:e.placesEntryIDs},...e.routesEntryIDs?.length&&{routesEntryIDs:e.routesEntryIDs},...e.geometriesEntryIDs?.length&&{geometriesEntryIDs:e.geometriesEntryIDs},...e.trafficAreaAnalyticsEntryIDs?.length&&{trafficAreaAnalyticsEntryIDs:e.trafficAreaAnalyticsEntryIDs},...e.byodEntryIDs?.length&&{byodEntryIDs:e.byodEntryIDs}}}:{kind:"params",params:l},d=await Ma(t,i.id,c);if("error"in d)return{error:`Clustering failed for entry "${i.id}": ${d.error}`};const u=d.value;return{incidentsEntryID:i.id,clusterCount:u.groups.length,clusters:u.groups.map(e=>({id:e.id,primaryRoads:e.primaryRoads,primaryCategory:e.primaryCategory,size:e.size,totalDelaySeconds:e.totalDelaySeconds,peakDelaySeconds:e.peakDelaySeconds,trend:e.trend}))}},Fa=(()=>{const e=`At least one of ${an(tn)} is REQUIRED.`,t={...dn(tn,{verb:"analyse",requiredHint:e})};return t.name=oe.string().describe('Short tracker label, e.g. "Incidents near hospitals".'),t.rule=oe.string().describe('The watched condition in plain language, e.g. "an incident within 100m of a hospital". For display.'),t.code=oe.string().describe("JS that inspects the injected inputs and returns a VERDICT describing whether the watched condition holds RIGHT NOW. Pure + synchronous — no fetch/network/await.\n\n"+Gn(tn,!1)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${ln(tn)}\n\nReturn exactly:\n\`{ active: boolean, members: { entryId: string, featureIds: string[] }[], summary: string }\`\n- \`active\`: is the condition met now? (false→true logs an alert; true→false resolves it)\n- \`members\`: the features that matched, GROUPED BY their source entry id — include EVERY entity the alert is about (e.g. the hospital AND the nearby incidents), each under its own entry id.\n- \`summary\`: a short grounded headline built from the data (e.g. "Incident 80m from St Mary's Hospital").\n\n`+cn(tn)),oe.object(t).refine(un,{message:pn(tn)})})(),qa=oe.union([oe.object({trackerId:oe.string(),name:oe.string(),rule:oe.string(),watching:oe.array(oe.string()).describe("Source entry ids the rule reads."),firingNow:oe.boolean().describe("Whether the condition holds at arm time. If true, one `opened` alert fires for it."),summary:oe.string().describe("The arm-time verdict summary.")}),jt]),$a=(e,t)=>{const r=e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"tracker",n=new Set(t.trackers.trackers.map(e=>e.id));if(!n.has(r))return r;for(let s=2;;s++)if(!n.has(`${r}-${s}`))return`${r}-${s}`},Ga=oe.union([oe.object({incidentsEntryID:oe.string().describe("The entry the focus was applied to."),focusedCount:oe.number().describe("How many of the requested ids ended up in the focus subset."),droppedIds:oe.array(oe.string()).describe("Ids that were not present on the entry and were dropped from the focus."),reason:oe.string().optional().describe("Echo of the focus reason for downstream display.")}),jt]),Ua=oe.object({incidentsEntryID:oe.string().describe("Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source)."),incidentIds:oe.array(oe.string()).min(1).describe("Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored."),reason:oe.string().optional().describe('Short label shown in the UI FocusChip — what this subset represents (e.g. "top 3 by delay").')}),Wa=oe.object({slotIndex:oe.number().int().min(0).optional().describe("Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot."),includeEmptySlots:oe.boolean().optional().describe("Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses."),offset:oe.number().int().min(0).optional().describe("Pagination offset within the filtered slot list. Ignored when slotIndex is provided."),limit:oe.number().int().min(1).max(25).optional().describe("Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.")}),za=oe.object({totalSlots:oe.number().describe("Total waypoint slots currently staged"),filledSlots:oe.number().describe("Number of staged slots that already contain coordinates"),matchedCount:oe.number().describe("Total slots matching the request before pagination"),returnedCount:oe.number().describe("Number of slots returned in this page"),offset:oe.number().describe("Offset applied to the filtered slot list"),hasMore:oe.boolean().describe("True when more slots can be fetched with a higher offset"),nextOffset:oe.number().optional().describe("Offset to request the next page when hasMore=true"),waypoints:oe.array(hs)}),Ya=oe.object({trackerId:oe.string().optional().describe("Scope to one tracker. Omit for every tracker."),sinceMs:oe.number().optional().describe("Only events at/after this epoch-ms sample time.")}),Ha=oe.object({at:oe.number(),trackerId:oe.string(),trackerName:oe.string(),type:oe.enum(["alert","event"]),kind:oe.enum(["opened","resolved"]),summary:oe.string(),openedAt:oe.number().optional()}),Va=oe.object({events:oe.array(Ha).describe("Newest first."),count:oe.number(),coverage:oe.object({oldestRetainedAt:oe.number(),newestAt:oe.number()}).optional().describe("Sample-time span the bounded log currently retains — older events were evicted.")}),Ja=oe.object({}),Ka=oe.object({trackers:oe.array(oe.object({trackerId:oe.string(),name:oe.string(),rule:oe.string(),enabled:oe.boolean(),firing:oe.boolean().describe("Whether the rule is currently active (an open episode)."),openedAt:oe.number().optional(),watching:oe.array(oe.string()).describe("Source entry ids the rule reads.")})),count:oe.number()}),Xa=oe.union([oe.object({analysisId:oe.string(),enabled:oe.boolean().describe("The analysis is now recomputed on every source-entry change (true) or not (false).")}),jt]),Za=oe.object({analysisId:oe.string().describe("The `analysisId` returned by `analyseData` for the analysis to start or stop monitoring."),enabled:oe.boolean().describe("true → recompute this analysis on every change to its source entries; false → stop monitoring it. The latest result stays attached either way.")}),Qa=(rn(tn),oe.object({theme:oe.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline". Use "inverted" for difference/inverse results.'),mode:Br,hidePreviousEntries:qr("custom-geometries").optional()})),ei={sourceIds:oe.array(mr).optional().describe(`${wr} (only when \`geometriesEntryIDs\` was set).`),skipped:oe.array(xn).optional().describe(br),fitted:yn.optional()},ti=oe.union([$t({}).extend({placesEntryId:oe.string(),label:oe.string().optional(),connectionsRendered:oe.number().optional(),placesGeometryCount:oe.number().optional().describe("Polygons attached to the new places entry (when `geometries` was returned alongside)."),shownPlaces:oe.boolean().optional(),...ei}),oe.object({customGeometriesEntryId:mr,customGeometriesCount:oe.number(),label:oe.string(),operation:oe.string().optional(),shownCustomGeometries:oe.boolean().optional(),...ei}),oe.object({byodEntryId:oe.string(),byodFeatureCount:oe.number(),label:oe.string(),fitted:yn.optional()}),oe.object({fitted:yn}),jt]),ri=(e,t)=>{const r=sn(e,t),n=`At least one of ${an(r)} is REQUIRED.`,s={...dn(r,{verb:"process",requiredHint:n})};return s.code=oe.string().describe(((e,t)=>{const r=sn(e,t),n=cn(r),s=r.includes("trafficAreaAnalytics")?"`trafficAreaAnalytics` is a FeatureCollection of tile/hex regions whose `feature.properties` carry metric fields (`congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`); use `turf.booleanPointInPolygon` / `turf.lineIntersect` to bridge with places/routes.\n\n":"",o=r.includes("customGeometries")?`${fr}\n\n`:"",a=r.includes("byod")?"• `byod: FeatureCollection` — writes a NEW BYOD entry (any GeoJSON: Point/Line/Polygon/mixed). Use for derived BYOD layers (e.g. cleaned/filtered customer data).\n":"",i=`${ln(r)}\n`;return"Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`. At least one of `places` / `geometries` / `fitOnMap` / `byod` must be present. `places` (with optional attached `geometries`), standalone `geometries`, and `byod` are MUTUALLY EXCLUSIVE — only one is written per call, others are dropped. `fitOnMap` composes with any of them.\n\n• `places: Places` — writes a NEW places entry. Filters/merges/subsets of the input `placesByEntry[id].features`, or computed point features that conform to the Place schema. Output is validated (validatePlaces): each place must be a Point Feature with a `properties.type` and a display name at its schema path, else it is rejected with a fixable error. When transforming an EXISTING place, spread it so its `id` is kept: `{ ...place, properties: { ...place.properties, poi: { ...place.properties.poi, name } } }`. A deterministic id is minted only for a feature that carries none (a freshly computed point).\n• `placeConnections: { from, to, id? }[]` — lines between output places (endpoints: Place or place id). Requires `places`. Drawn on the new entry's PlacesModule.\n• `geometries: PolygonFeature[]` — polygons (Polygon/MultiPolygon, stable `id` recommended). When `places` is also present, attached to the new places entry; when `places` is absent, written as a NEW custom-geometries entry.\n• `fitOnMap` — bbox or `{ bbox, padding?, animate? }`. Camera move; when alone, no entry written.\n"+a+"\n"+Gn(r,!0)+i+"Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. "+s+`${mn}\n\n`+o+n})(e,t)),s.label=oe.string().optional().describe("Short label for the new entry (used by both places and custom-geometries paths)."),s.entryId=jr,s.operation=oe.string().optional().describe("Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance on a new custom-geometries entry. Ignored on the places path."),s.show=oe.object({places:$r.optional().describe("Render the new places entry (when `places` was returned)."),placesGeometries:oe.object({theme:oe.enum(["filled","outline","inverted"]).optional(),mode:Br}).optional().describe("Render the polygons attached to the new places entry (when both `places` and `geometries` were returned)."),customGeometries:Qa.optional().describe("Render the new custom-geometries entry (when `geometries` was returned WITHOUT `places`).")}).optional().describe("Optional per-output render knobs. The executor only consults the field matching what the code returned (so leaving e.g. `customGeometries` set when `places` was returned is a silent no-op)."),s.connectMarkerType=oe.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Match the module holding the endpoints.'),oe.object(s).refine(un,{message:pn(r)})},ni=(ri(tn,void 0),(e,t)=>{const r=sn(e,t).join(" / "),n="CONTRAST `analyseData`, which attaches JSON/chart metadata to existing entries and never renders.";return t?`Run dynamic JS over ${r} entries to produce NEW renderable map data — a places, custom-geometries, or BYOD entry (with optional \`placeConnections\` / attached polygon footprints), and/or a \`fitOnMap\` camera move. ${n} Returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\`: when \`places\` is returned, inline \`geometries\` attach to it; otherwise they become a standalone custom-geometries entry. Inputs are \`undefined\` when their \`*EntryIDs\` argument is omitted — guard before reading.`:`Run dynamic JS over any combination of ${r} entries to produce NEW renderable map data — a places, custom-geometries, or BYOD entry (with optional \`placeConnections\` / attached polygon footprints), and/or a \`fitOnMap\` camera move. ${n} Returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\`: when \`places\` is returned, inline \`geometries\` attach to it as place footprints; otherwise they become a standalone custom-geometries entry. \`routeUtils\` is exposed alongside \`h3\` / \`turf\` for route section / progress work. Inputs are \`undefined\` when their \`*EntryIDs\` argument is omitted — guard before reading.`}),si=(ni(tn,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind.`),oi=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)},ai=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)})},ii=e=>"string"==typeof e&&e.trim().length>0,li=e=>{if(!Array.isArray(e)||e.length<2)return!1;const[t,r]=e;return Number.isFinite(t)&&Number.isFinite(r)&&Math.abs(t)<=180&&Math.abs(r)<=90},ci=(e,t,r)=>`derived-${e.toFixed(6)}_${t.toFixed(6)}-${r}`,di=(e,t,r)=>void 0===e?{value:void 0}:t(e)?{value:e}:{error:r},ui=e=>e&&e.features.length>0?e:void 0,pi=e=>{if(!e||"object"!=typeof e)return"Process code must return `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`.";const t=e,r={};if(void 0!==t.places){const e=(e=>{if(!oi(e))return{error:"`places` must be a `Places` FeatureCollection of Point features."};const t=/* @__PURE__ */new Map;for(let r=0;r<e.features.length;r++){const n=e.features[r],s=n?.geometry;if(!s||"Point"!==s.type||!li(s.coordinates))return{error:`\`places[${r}]\` must be a Point Feature with valid [lng, lat] coordinates (lng ∈ [-180,180], lat ∈ [-90,90]).`};const o=n.properties;if(![o?.poi?.name,o?.address?.freeformAddress].find(ii))return{error:`\`places[${r}]\` needs a display name in \`properties.poi.name\` (POIs) or \`properties.address.freeformAddress\` (streets, intersections, addresses).`};if(!ii(o?.type)||!ee.includes(o.type))return{error:`\`places[${r}].properties.type\` is required and must be one of: ${ee.join(", ")}.`};if(!ii(n.id)){const[e,t]=s.coordinates;n.id=ci(e,t,r)}const a=t.get(n.id);if(void 0!==a)return{error:`\`places[${r}]\` duplicates id "${n.id}" of \`places[${a}]\`; give each place a distinct id, or omit it so a unique one is derived.`};t.set(n.id,r)}return{value:e}})(t.places);if("error"in e)return e.error;r.places=ui(e.value)}const n=di(t.placeConnections,ai,"`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).");if("error"in n)return n.error;if(r.placeConnections=n.value,void 0!==t.geometries){const e=(e=>{if(!Array.isArray(e))return{error:"`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features."};const t=[];for(let r=0;r<e.length;r++){const n=e[r];if(!Ge(n))return{error:`\`geometries[${r}]\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.`};t.push(n)}return{value:t}})(t.geometries);if("error"in e)return e.error;r.geometries=e.value.length>0?e.value:void 0}const s=di(t.fitOnMap,fn,"`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.");if("error"in s)return s.error;r.fitOnMap=s.value;const o=di(t.byod,oi,"`byod` must be a GeoJSON FeatureCollection (any geometry types).");return"error"in o?o.error:(r.byod=ui(o.value),(e=>void 0===e.places&&void 0===e.geometries&&void 0===e.fitOnMap&&void 0===e.byod?"At least one of `places`, `geometries`, `fitOnMap`, `byod` must produce content (empty arrays / FeatureCollections do not count).":void 0===e.places&&void 0!==e.placeConnections?"`placeConnections` requires `places` to be returned alongside.":void 0)(r)??r)},hi=async(e,t,r,n)=>{const{label:s,entryId:o,show:a,connectMarkerType:i,geometriesEntryIDs:l}=t,{places:c,placeConnections:d,geometries:u,fitOnMap:p}=r,h=c.features.length,y=`${s?`processed: ${s}`:"processed"} (${h} ${1===h?"place":"places"})`,m=await e.places.addPlaceResult(c,y,o,d,u),g=a?.places?await _s(e,[m],a.places):void 0;let f,b;if(u?.length&&(f=u.length,a?.placesGeometries&&await e.places.showPlaceGeometries(u,a.placesGeometries.theme??"outline",a.placesGeometries.mode??"replace",m)),d){const t=await(async(e,t,r,n)=>{try{return await(await e.places.getEntryPlacesModule(t,n??"pin")).showConnections(r),{count:r.length}}catch(s){return{error:`Failed to render connections: ${s instanceof Error?s.message:String(s)}`}}})(e,m,d,i);if("error"in t)return t;b=t.count}const w=p?bn(e,p):void 0,v=e.places.entries.find(e=>e.id===m)?.label;return{...ds(c),placesEntryId:m,...v&&{label:v},...g&&{shownPlaces:!0},...void 0!==b&&{connectionsRendered:b},...void 0!==f&&{placesGeometryCount:f},...l&&{sourceIds:n.sourceIds},...n.skipped.length&&{skipped:n.skipped},...w&&{fitted:w}}},yi=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i,code:l}=e,c=await jn({placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i},t);if("error"in c)return{error:c.error};const{sandbox:d,geometries:u}=c.value,p=await Ze(l,$n,Un(d,{h3:ae,turf:se}),"Process",t.codeExecution);if("error"in p)return{error:p.error};const h=pi(p.value);if("string"==typeof h)return{error:h};const{places:y,geometries:m,fitOnMap:g,byod:f}=h;return y?hi(t,e,{...h,places:y},u):m?.length?(async(e,t,r,n,s)=>{const{label:o,entryId:a,operation:i,show:l,geometriesEntryIDs:c}=t,d=o?`processed: ${o}`:`processed geometries (${r.length})`,u=await e.customGeometries.addEntry(r,{sourceIds:s.sourceIds,...i&&{operation:i}},d,a);let p=!1;l?.customGeometries&&(await In(e.customGeometries,[u],l.customGeometries.hidePreviousEntries),await e.customGeometries.showEntry(u,l.customGeometries.theme??"outline",l.customGeometries.mode??"replace"),p=!0);const h=n?bn(e,n):void 0;return{customGeometriesEntryId:{kind:"customGeometries",id:u},customGeometriesCount:r.length,label:d,...i&&{operation:i},...c&&{sourceIds:s.sourceIds},...s.skipped.length&&{skipped:s.skipped},...p&&{shownCustomGeometries:!0},...h&&{fitted:h}}})(t,e,m,g,u):f?(async(e,t,r,n)=>{const{label:s,entryId:o}=t,a=s?`processed: ${s}`:`processed byod (${r.features.length})`,i=await e.byod.addEntry(r,a,{...o&&{explicitId:o}}),l=n?bn(e,n):void 0;return{byodEntryId:i,byodFeatureCount:r.features.length,label:a,...l&&{fitted:l}}})(t,e,f,g):g?{fitted:bn(t,g)}:{error:"Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`."}},mi=oe.union([oe.object({kind:oe.literal("integrator"),description:oe.string().optional()}),oe.object({kind:oe.literal("url"),url:oe.string()}),oe.object({kind:oe.literal("inline"),description:oe.string().optional()})]),gi=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),featureCount:oe.number(),geometryTypes:oe.array(oe.string()),propertyNames:oe.array(oe.string()).describe("Feature-property keys seen in the data, highest-coverage first and capped — wide datasets may omit low-coverage keys. Pass `id` for the full per-property profile (types/coverage/numeric examples) plus any `propertiesOmitted` count."),source:mi,shown:oe.boolean()}),fi=gi.extend({profile:Bt.describe("Runtime-inferred shape of the data (per-property types/coverage; numeric/boolean examples only — string example values are withheld). To compute over the actual feature values, pass this entry to `analyseData` / `processData`; the raw GeoJSON is never returned to the model.")}),bi=oe.union([oe.object({entries:oe.array(gi),entryMode:oe.enum(["single","multiple"])}),fi,jt]),wi=(oe.object({id:oe.string().optional().describe("BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.")}),oe.object({id:mr.optional().describe('Tagged id to inspect in detail (e.g. `{ kind: "places", id: "ev-stations-oost" }`, `{ kind: "ranges", id: "ranges-0" }`, `{ kind: "customGeometries", id: "bakery-candidates-gap-oost" }`). Omit to list every available id across all slices.')}),oe.object({name:oe.string(),outputFormat:oe.enum(["json","chart"]),timestamp:oe.number(),description:oe.string().optional()})),vi=oe.object({id:mr.describe("Tagged id of this entry."),label:oe.string(),timestamp:oe.number(),featureCount:oe.number(),operation:oe.string().optional().describe("Operation label (custom-geometries entries only)."),sourceIds:oe.array(mr).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:oe.array(wi).optional().describe("Analyses already attached to this entry (places and custom only).")}),Ii=vi.extend({features:oe.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),Ei=oe.union([oe.object({entries:oe.array(vi)}),Ii,jt]),Si=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:n})=>({name:e,outputFormat:t,timestamp:r,...n&&{description:n}})):void 0,Mi=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const n=Si(e.analyses.getAnalysesForEntry(r.id));t.push({id:{kind:"places",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,...n&&{analyses:n}})}for(const r of e.ranges.entries){const e=r.data.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 n=Si(e.analyses.getAnalysesForEntry(r.id));t.push({id:{kind:"customGeometries",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.data.length,...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...n&&{analyses:n}})}return t.sort((e,t)=>t.timestamp-e.timestamp)},ki=(e,t)=>{switch(e.kind){case"places":return((e,t)=>{const r=t.places.entries.find(t=>t.id===e.id);if(!r)return{error:`No places entry with id "${e.id}"`};if(!r.geometries?.length)return{error:`Places entry "${e.id}" has no cached footprints. Call discoverPlaces / locatePlace with \`withGeometries: true\` first.`};const n=Si(t.analyses.getAnalysesForEntry(r.id));return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,features:{type:"FeatureCollection",features:r.geometries},...n&&{analyses:n}}})(e,t);case"ranges":return((e,t)=>{const r=t.ranges.entries.find(t=>t.id===e.id);if(!r)return{error:`No ranges entry with id "${e.id}"`};const n=r.data.flatMap(e=>e.polygon?.features??[]);return 0===n.length?{error:`Ranges entry "${e.id}" has no polygons.`}:{id:e,label:r.label,timestamp:r.timestamp,featureCount:n.length,features:{type:"FeatureCollection",features:n}}})(e,t);case"customGeometries":return((e,t)=>{const r=t.customGeometries.findById(e.id);if(!r)return{error:`No custom-geometries entry with id "${e.id}"`};const n=Si(t.analyses.getAnalysesForEntry(r.id));return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.data.length,features:{type:"FeatureCollection",features:r.data},...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...n&&{analyses:n}}})(e,t);default:return{error:'`{ kind: "place" }` is not a recallable detail target. Pass a places-entry id (`recallState({ kind: "places", id })`) to inspect a single place.'}}},xi=(oe.object({id:oe.string().optional().describe('Incidents entry id to inspect (e.g. "incidents-0"). Omit to list every incidents entry.')}),oe.enum(["single","multiple"]).describe("Display policy: `multiple` (default) lets several incidents entries render at once; `single` keeps only the latest entry — switching to it auto-clears the others.")),Ai=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),count:oe.number().describe("Number of incidents captured in this entry."),shown:oe.boolean().describe("True while this entry is rendered on the map."),monitored:oe.boolean().describe("True while a background monitor is actively polling this entry."),focusedCount:oe.number().describe("Incidents in the entry's focus subset (0 when nothing is focused).")}),Di=Ai.extend({byMagnitude:oe.record(oe.string(),oe.number()).describe("Incident count grouped by `magnitudeOfDelay` (e.g. `{ major: 3, moderate: 5, minor: 10 }`)."),totalDelaySeconds:oe.number().describe("Sum of `delayInSeconds` across the entry (0 when no delay data)."),clusterCount:oe.number().optional().describe("Number of incident clusters computed for this entry — present only once clustering has run.")}),Ti=oe.union([oe.object({entries:oe.array(Ai),entryMode:xi}),Di.extend({entryMode:xi}),jt]),Pi=(oe.object({id:oe.string().optional().describe('Entry ID to retrieve (e.g. "places-3"). Omit to list all entries.')}),oe.object({name:oe.string(),outputFormat:oe.enum(["json","chart"]),timestamp:oe.number(),description:oe.string().optional()})),_i=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),featureCount:oe.number().describe("Number of Place features stored in this entry."),shown:oe.boolean().describe("Whether this entry is currently rendered on the map."),markerType:oe.enum(Pe).optional().describe("Which marker theme is rendering this entry (undefined when `shown` is false)."),analyses:oe.array(Pi).optional().describe("Analyses already attached to this entry via analyseData (name + output format).")}),Oi=oe.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.'),Ci=e=>{const t=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),places:$t(e)});return oe.union([oe.object({entries:oe.array(_i),entryMode:Oi}),t.extend({entryMode:Oi}),jt])},Ri=(Ci({}),e=>async(t,r)=>{const{id:n}=t,s=r.places.entries,o=r.places.entryMode,a=r.places.shownEntryIds;if(!n)return{entries:[...s].sort((e,t)=>t.timestamp-e.timestamp).map(({id:e,label:t,timestamp:n,data:s})=>{const o=a.has(e),i=o?r.places.getShownMarkerType(e):void 0,l=r.analyses.getAnalysesForEntry(e);return{id:e,label:t,timestamp:n,featureCount:s.length,shown:o,...i&&{markerType:i},...l.length&&{analyses:l.map(({name:e,outputFormat:t,timestamp:r,description:n})=>({name:e,outputFormat:t,timestamp:r,...n&&{description:n}}))}}}),entryMode:o};const i=s.find(e=>e.id===n);if(!i)return{error:`No entry found with id "${n}"`};const l=ds(i.data,e);return{id:i.id,label:i.label,timestamp:i.timestamp,places:l,entryMode:o}}),ji=(Ri({}),oe.object({id:oe.string().optional().describe('Entry ID to retrieve (e.g. "ranges-0"). Omit to list all entries.')}),oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),originCount:oe.number()})),Bi=oe.object({origin:oe.object({query:oe.string().optional(),position:oe.tuple([oe.number(),oe.number()])}),budgets:oe.array(oe.object({type:oe.string(),value:oe.number()}))}),Ni=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),ranges:oe.array(Bi)}),Li=oe.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.'),Fi=oe.union([oe.object({entries:oe.array(ji),entryMode:Li}),Ni.extend({entryMode:Li}),jt]),qi=(oe.object({id:oe.string().optional().describe('Entry ID to retrieve (e.g. "routes-1"). Omit to list all entries.')}),oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),shown:oe.boolean().describe("Whether this entry is currently rendered on the map.")})),$i=oe.object({maxAlternatives:oe.number().optional(),costModel:ao.optional(),when:io.optional()}),Gi=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),routes:zt,waypoints:oe.array(oe.object({position:oe.array(oe.number()).length(2).describe("[longitude, latitude]")})),params:$i}),Ui=oe.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.'),Wi=oe.union([oe.object({entries:oe.array(qi),entryMode:Ui}),Gi.extend({entryMode:Ui}),jt]),zi=(oe.object({id:oe.string().optional().describe('Traffic-area-analytics entry id to inspect (e.g. "tta-0"). Omit to list every analytics entry.')}),oe.enum(["single","multiple"]).describe("Display policy: `multiple` (default) lets several analytics entries render at once; `single` keeps only the latest entry — switching to it auto-clears the others.")),Yi=oe.object({id:oe.string(),label:oe.string(),timestamp:oe.number(),regionCount:oe.number().describe("Number of analysed regions (features) in this entry."),metrics:oe.array(oe.string()).describe('Traffic metrics included in the report (e.g. ["congestionLevel", "speed"]).'),shown:oe.boolean().describe("True while this entry is rendered on the map.")}),Hi=Yi.extend({dateRange:oe.object({start:oe.string(),end:oe.string()}).describe("Analysis period (ISO dates) — empty strings when the entry carries no date metadata."),regions:oe.array(oe.string()).describe("Names of the analysed regions, in feature order."),heatmap:oe.boolean().describe("Whether tile-level heatmap data is included.")}),Vi=oe.union([oe.object({entries:oe.array(Yi),entryMode:zi}),Hi.extend({entryMode:zi}),jt]),Ji=e=>!e||Number.isNaN(e.getTime())?"":e.toISOString().slice(0,10),Ki=oe.object({id:oe.string(),label:oe.string()}),Xi=oe.enum(["single","multiple"]),Zi=oe.object({places:oe.array(Ki).describe("Places history entries — newest first."),routes:oe.array(Ki).describe("Routes history entries — newest first."),ranges:oe.array(Ki).describe("Reachable-range history entries — newest first."),entryModes:oe.object({places:Xi,routes:Xi,ranges:Xi}).describe("Per-slice display policy. `multiple` (default) lets several entries render at once; `single` keeps only the latest entry — set via setEntryMode tools."),mapStyle:oe.object({id:oe.string().optional().describe('Standard style id (e.g. "standardLight").')}).nullable().describe("Current map style. `null` when the map has no style assigned yet."),mapPOIs:oe.object({visible:oe.boolean().optional(),filterMode:oe.enum(["only","all_except"]).optional(),categories:oe.array(oe.string()).optional()}).nullable().describe("Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session."),traffic:oe.object({flow:oe.object({visible:oe.boolean().optional()}).nullable().describe("Traffic-flow layer config. `null` when the module is uninitialised."),incidents:oe.object({visible:oe.boolean().optional()}).nullable().describe("Traffic-incidents layer config. `null` when the module is uninitialised.")}).describe("Real-time traffic visualization configs.")}),Qi=e=>oe.union([Zi,Ci(e),Wi,Fi,bi,Ei,Ti,Vi,jt]),el=(Qi({}),oe.object({kind:oe.enum(["places","routes","ranges","geometries","byod","incidents","trafficAreaAnalytics"]).optional().describe("Narrow to ONE entry kind: list that kind's entries (omit `id`) or inspect one (`id`). Omit `kind` for a full session snapshot (places/routes/ranges index + map style / POIs / traffic)."),id:oe.union([oe.string(),mr]).optional().describe('Entry id to inspect in detail — requires `kind`. A plain id for places/routes/ranges/byod/incidents/trafficAreaAnalytics (e.g. "places-3"); a tagged `{ kind, id }` for geometries. Omit to list the kind\'s entries.')})),tl=e=>async(t,r)=>{const{kind:n,id:s}=t;if(!n)return(e=>{const t=e=>[...e].sort((e,t)=>t.timestamp-e.timestamp),r=t(e.places.entries).map(({id:e,label:t})=>({id:e,label:t})),n=t(e.routing.entries).map(({id:e,label:t})=>({id:e,label:t})),s=t(e.ranges.entries).map(({id:e,label:t})=>({id:e,label:t})),o="string"==typeof(a=e.baseMap.ttMap.getStyle())?a:a&&"object"==typeof a&&"id"in a?a.id:void 0;var a;const i=void 0===o?null:{id:o},l=e.mapPOIs.poisModule?.getConfig(),c=l?{..."boolean"==typeof l.visible&&{visible:l.visible},...l.filters?.categories?.show&&{filterMode:l.filters.categories.show},...l.filters?.categories?.values&&{categories:[...l.filters.categories.values]}}:null,d=e.trafficTiles.trafficFlowModule?.getConfig(),u=e.trafficTiles.trafficIncidentsModule?.getConfig(),p={flow:d?{..."boolean"==typeof d.visible&&{visible:d.visible}}:null,incidents:u?{..."boolean"==typeof u.visible&&{visible:u.visible}}:null};return{places:r,routes:n,ranges:s,entryModes:{places:e.places.entryMode,routes:e.routing.entryMode,ranges:e.ranges.entryMode},mapStyle:i,mapPOIs:c,traffic:p}})(r);switch(n){case"places":return Ri(e)({id:s},r);case"routes":return(async(e,t)=>{const{id:r}=e,n=t.routing.entryMode,s=t.routing.shownEntryIds;if(!r)return{entries:[...t.routing.entries].reverse().map(({id:e,label:t,timestamp:r})=>({id:e,label:t,timestamp:r,shown:s.has(e)})),entryMode:n};const o=t.routing.entries.find(e=>e.id===r);if(!o)return{error:`No entry found with id "${r}"`};const a=ps(o.data),i=o.waypoints.map(e=>ys(e)).filter(e=>null!==e).map(e=>({position:e.position??[0,0]}));return{id:o.id,label:o.label,timestamp:o.timestamp,routes:a,waypoints:i,params:o.params,entryMode:n}})({id:s},r);case"ranges":return(async(e,t)=>{const{id:r}=e,n=t.ranges.entryMode;if(!r)return{entries:[...t.ranges.entries].reverse().map(({id:e,label:t,timestamp:r,data:n})=>({id:e,label:t,timestamp:r,originCount:n.length})),entryMode:n};const s=t.ranges.entries.find(e=>e.id===r);return s?{id:s.id,label:s.label,timestamp:s.timestamp,ranges:s.data.map(e=>({origin:{...e.origin,position:e.origin.position},budgets:e.budgets})),entryMode:n}:{error:`No entry found with id "${r}"`}})({id:s},r);case"byod":return(async(e,t)=>{if(!e.id)return{entries:t.byod.entries.map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,featureCount:e.profile.featureCount,geometryTypes:e.profile.geometryTypes,propertyNames:e.profile.properties.map(e=>e.name),source:e.source,shown:!!e._shown})),entryMode:t.byod.entryMode};const r=t.byod.findById(e.id);return r?{id:r.id,label:r.label,timestamp:r.timestamp,featureCount:r.profile.featureCount,geometryTypes:r.profile.geometryTypes,propertyNames:r.profile.properties.map(e=>e.name),profile:pt(r.profile),source:r.source,shown:!!r._shown}:{error:`No BYOD entry with id "${e.id}". Call recallState({ kind: "byod" }) to list available IDs.`}})({id:s},r);case"incidents":return(async(e,t)=>{const r=t.trafficIncidents,n=r.entryMode;if(!e.id)return{entries:[...r.entries].reverse().map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,count:e.data.length,shown:r.shownEntryIds.has(e.id),monitored:r.isMonitored(e.id),focusedCount:r.getFocus(e.id)?.ids.size??0})),entryMode:n};const s=r.entries.find(t=>t.id===e.id);if(!s)return{error:`No incidents entry with id "${e.id}". Call recallState({ kind: "incidents" }) to list available IDs.`};const o={};let a=0;for(const l of s.data){const e=l.properties.magnitudeOfDelay;o[e]=(o[e]??0)+1,a+=l.properties.delayInSeconds??0}const i=r.getClusters(s.id);return{id:s.id,label:s.label,timestamp:s.timestamp,count:s.data.length,shown:r.shownEntryIds.has(s.id),monitored:r.isMonitored(s.id),focusedCount:r.getFocus(s.id)?.ids.size??0,byMagnitude:o,totalDelaySeconds:a,...i&&{clusterCount:i.groups.length},entryMode:n}})({id:s},r);case"trafficAreaAnalytics":return(async(e,t)=>{const r=t.trafficAreaAnalytics,n=r.entryMode;if(!e.id)return{entries:[...r.entries].reverse().map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,regionCount:e.data.features.length,metrics:e.data.properties.metrics,shown:r.shownEntryIds.has(e.id)})),entryMode:n};const s=r.findById(e.id);if(!s)return{error:`No traffic-area-analytics entry with id "${e.id}". Call recallState({ kind: "trafficAreaAnalytics" }) to list available IDs.`};const o=s.data.properties;return{id:s.id,label:s.label,timestamp:s.timestamp,regionCount:s.data.features.length,metrics:o.metrics,shown:r.shownEntryIds.has(s.id),dateRange:{start:Ji(o.startDate),end:Ji(o.endDate)},regions:s.data.features.map(e=>e.properties.name),heatmap:o.heatmap,entryMode:n}})({id:s},r);case"geometries":return(async(e,t)=>{const{id:r}=e;return r?ki(r,t):{entries:Mi(t)}})({id:s},r)}},rl=(tl({}),{classificationPrompt:"Read session state: list / inspect loaded entries (places, routes, ranges, geometries, byod, incidents, trafficAreaAnalytics) and pick valid ids before any `*EntryIDs` follow-up (analyseData, processData, updatePlacesDisplay, …), or get a full snapshot of what is loaded. No args = snapshot; `kind` = that kind's index; `kind`+`id` = detail.",tags:["state","recall","orientation"],examples:["recallState()",'recallState({ kind: "places" })','recallState({ kind: "places", id: "places-0" })','recallState({ kind: "routes" })','recallState({ kind: "geometries", id: { kind: "customGeometries", id: "buffered-routes" } })'],examplePrompts:["What is currently loaded in my session?","What places did I search for?","List my routes with their IDs","What is in entry places-2?","Which IDs can I pass to analyseData or processData?","Show the BYOD layers I added"],relatedTools:["analyseData","processData","updatePlacesDisplay","updateRoutesDisplay","updateByodDisplay"],dependsOn:["discoverPlaces","locatePlace","reverseGeocode","setRoute","findReachableAreas","addByodSource","processData","getTrafficAreaAnalytics"]}),nl=["places","routes","ranges","customGeometries","trafficAreaAnalytics","trafficIncidents","byod","analyses","trackers","mapPOIs","trafficFlow","mapStyle"],sl=oe.union([oe.object({success:oe.literal(!0),cleared:oe.array(oe.enum(nl)).describe("Slices that were wiped / reset to defaults.")}),jt]),ol=oe.object({}),al=oe.enum(["circle","line","fill","symbol","heatmap","fill-extrusion"]).describe("MapLibre layer type. Must suit the geometry: Point → circle/symbol/heatmap, Line → line, Polygon → fill/fill-extrusion."),il=oe.object({type:al,paint:oe.record(oe.string(),oe.unknown()).optional().describe('MapLibre PAINT properties — how the feature LOOKS: colour, opacity, halo, blur (any `*-color` / `*-opacity` / `*-halo-*`), plus `circle-radius` and `line-width`. Values may be data-driven expressions referencing feature properties seen in the entry `profile` (e.g. `["interpolate", ["linear"], ["get", "revenue"], 0, 4, 1000, 20]`).'),layout:oe.record(oe.string(),oe.unknown()).optional().describe("MapLibre LAYOUT properties — WHAT/WHERE the feature is: `visibility`, placement/anchor/offset, and a symbol's text/icon content + glyph sizing (`text-field`, `text-font`, `text-size`, `icon-image`, `icon-size`). Everything else — all colour, opacity and halo — is `paint`. A `symbol` splits its text styling this way (`text-field`/`text-size` here, `text-color`/`text-halo-*` in `paint`); mixing them up makes MapLibre reject the layer with `unknown property`."),filter:oe.unknown().optional().describe("Optional MapLibre filter expression restricting which features this layer renders."),id:oe.string().optional().describe("Stable layer id. Auto-generated when omitted."),beforeID:oe.string().optional().describe(`Style layer id to draw this layer BELOW; omit to draw on top of everything. Any layer already on the map is allowed (e.g. another entry's layer), but prefer these base-map anchors: '${b.lowestLabel}' for fill/line/heatmap, which keeps every map label readable above the data, or '${b.lowestRoadLine}' when the road network should stay drawn over a fill; omit for point layers. An id that is not in the current style silently drops the layer.`)}).describe("A single MapLibre layer rendering the entry. Pick `type` by geometry; drive `paint` from the profile."),ll=oe.object({byodEntryId:oe.string().describe("Id of the BYOD entry to restyle (from `addByodSource` or `recallState`)."),layers:oe.array(il).min(1).describe("Replacement MapLibre layer specs, applied in array order (first drawn underneath). Replaces the entry's current layers wholesale. Use the entry `profile` (geometry types + per-property types/coverage/examples) to choose layer `type`s and data-driven `paint`."),show:Ur.optional().describe("Render the entry after restyling. Omit when the entry is already shown — the new layers apply live in place. Pass it to render a hidden entry (or to re-fit the camera via `zoomMode`).")}),cl=oe.union([oe.object({byodEntryId:oe.string(),label:oe.string(),layerCount:oe.number().describe("Number of layers now configured on the entry."),layerTypes:oe.array(oe.string()).describe("The `type` of each configured layer, in draw order."),shown:Wr.optional().describe("Render report — present only when `show` was provided (or the entry re-rendered live).")}),jt]),dl=oe.enum(At),ul=oe.enum(["single","multiple"]),pl=oe.union([oe.object({success:oe.literal(!0),slice:dl,mode:ul,droppedEntryIds:oe.array(oe.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),jt]),hl=oe.object({slice:dl.describe("Which entry-owning slice to reconfigure. Names match the keys on the agent state exactly — `routing` (not `routes`), `customGeometries` (not `geometries`), and so on."),mode:ul.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),yl=`Switch the display policy of one entry-owning slice between \`multiple\` and \`single\`. Supported slices: ${At.join(", ")}. \`single\` ⇒ at most one entry on the map, and any older entries get dropped from history immediately. Use when the user asks for a "clean view" / "only show one at a time" or wants to compare overlays again.`,ml=oe.union([oe.object({incidentsEntryID:oe.string(),enabled:oe.boolean().describe("The monitor is now polling (true) or stopped (false)."),alreadyInState:oe.boolean().describe("True when the monitor was already in the requested state — this call was a no-op.")}),jt]),gl=oe.object({incidentsEntryID:oe.string().describe("Id of the incidents entry to monitor (returned by `getTrafficIncidents`)."),enabled:oe.boolean().describe("true → start background polling (re-fetch + re-run registered analyses each tick); false → stop it. The entry keeps its last data either way."),intervalMs:oe.number().int().positive().optional().describe("Polling interval in milliseconds when enabling (default 60000). Ignored when disabling, and not applied to an already-running monitor — disable then re-enable to change it.")}),fl=oe.union([oe.object({routesEntryID:oe.string(),alreadyRunning:oe.boolean().describe("True when the entry already had a running monitor — start is idempotent. When true any new intervalMs in this call was IGNORED; stop first, then start again to change the interval.")}),jt]),bl=oe.object({routesEntryID:oe.string().describe("Id of the routes entry to monitor (the `entryId` returned by `setRoute`)."),intervalMs:oe.number().int().positive().optional().describe("Recalculation interval in milliseconds. Defaults to 60000 (60s).")}),wl=oe.union([oe.object({routesEntryID:oe.string(),wasRunning:oe.boolean().describe("True when a monitor was actually stopped; false when it was already idle.")}),jt]),vl=oe.object({routesEntryID:oe.string().describe("Id of the routes entry whose monitor should stop.")}),Il=oe.enum(Dt),El=oe.union([oe.object({success:oe.literal(!0),cleared:oe.array(Il)}),jt]),Sl=oe.object({layers:oe.array(Il).optional().describe("Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: "+Dt.map(e=>`\`${e}\``).join(", ")+". Map-only — slice histories (`entries`) are kept.")}),Ml=oe.union([oe.number(),oe.boolean(),oe.string()]),kl=oe.enum(S),xl=oe.object({id:oe.string(),kind:kl,description:oe.string(),default:Ml.optional(),current:Ml.optional(),overridden:oe.boolean(),range:oe.object({min:oe.number(),max:oe.number(),step:oe.number()}).optional(),options:oe.array(oe.string()).optional(),available:oe.boolean(),appliesTo:oe.enum(I)}),Al=oe.object({id:oe.string(),name:oe.string(),description:oe.string(),settings:oe.record(oe.string(),Ml)}),Dl=oe.union([oe.object({knobs:oe.array(xl),presets:oe.array(Al)}),jt]),Tl=oe.object({kind:kl.optional().describe("Only list knobs of this kind. Omit for the whole catalogue.")}),Pl=oe.union([oe.object({styles:oe.array(oe.string()).readonly()}),jt]),_l=oe.object({}),Ol=oe.union([oe.object({success:oe.literal(!0),language:oe.string()}),jt]),Cl=oe.object({language:oe.string().describe('e.g. "en-US", "fr-FR"')}),Rl=oe.union([oe.object({success:oe.literal(!0),style:oe.string()}),jt]),jl=oe.object({style:oe.enum(v).describe("Map visual theme")}),Bl=E.join(", "),Nl=oe.union([oe.number(),oe.boolean(),oe.string()]),Ll=oe.union([oe.object({success:oe.literal(!0),settings:oe.record(oe.string(),Nl),rejected:oe.array(oe.object({id:oe.string(),reason:oe.string()}))}),jt]),Fl=oe.object({set:oe.record(oe.string(),Nl).optional().describe(`Knob id → value. Factors are multipliers (1 = unchanged, e.g. 1.3), toggles are booleans, colours are CSS colour strings. Ranges come from describeMapStyling. Ids: ${Bl}.`),reset:oe.union([oe.literal(!0),oe.array(oe.string())]).optional().describe("true resets every knob to the style default; a list resets just those knobs. Applied first."),preset:oe.enum(M).optional().describe("Apply a named preset (replaces the current knobs; `set` is applied on top). data-viz: quiet base for data overlays · night-driving: bigger labels, wider roads · minimal: bare map · globe: globe projection with atmosphere · terrain: 3D terrain with sky.")}),ql=oe.union([oe.object({success:oe.literal(!0),visible:oe.boolean(),layerGroups:oe.array(oe.string())}),jt]),$l=oe.object({visible:oe.boolean(),layerGroups:oe.array(oe.enum(f)).describe("Vector-tile base-map layer groups to show or hide.")}),Gl=oe.union([oe.object({success:oe.literal(!0),allMapPOIsVisible:oe.boolean().optional(),filterCategories:oe.object({show:oe.enum(["only","all_except"]),values:oe.array(oe.string())}).optional(),reset:oe.boolean().optional()}),jt]),Ul=oe.object({reset:oe.boolean().optional().describe("Reset all tile-POI configuration to defaults. Other params ignored when true."),allMapPOIsVisible:oe.boolean().optional().describe("Overall visibility of the vector-tile POI icons baked into the base map style."),filterCategories:oe.object({show:oe.enum(["only","all_except"]).describe('"only": keep only these categories. "all_except": hide these categories.'),values:oe.array(oe.string()).describe('MapStylePOICategory / POICategoryGroup values (e.g. "RESTAURANT", "FOOD_DRINKS_GROUP"). Fine-grained codes like "ITALIAN_RESTAURANT" not supported here.')}).optional().describe("Restrict which built-in vector-tile POI categories are shown.")}),Wl=oe.object({visible:oe.boolean()}),zl=oe.union([oe.object({success:oe.literal(!0),trafficFlowVisible:oe.boolean()}),jt]),Yl=oe.object({visible:oe.boolean()}),Hl=oe.union([oe.object({success:oe.literal(!0),trafficIncidentsVisible:oe.boolean()}),jt]),Vl=oe.enum(["show","hide","remove"]).describe("`show` renders the listed entries (lazy-init their CustomGeoJSONModule); `hide` removes them from the map but keeps them in session state; `remove` hides AND drops them from history."),Jl=oe.object({entryIds:oe.array(oe.string()).min(1).optional().describe('BYOD entry ids to act on (e.g. ["byod-0"]). Use `recallState` to list. Omit when `action: "hide"` and `clearAll: true` to hide every shown BYOD entry in one call.'),action:Vl,clearAll:oe.boolean().optional().describe('Only valid with `action: "hide"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.'),hideOthers:oe.boolean().optional().describe('Only valid with `action: "show"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).')}).refine(e=>!("hide"!==e.action||!e.clearAll)||!!e.entryIds?.length,{message:'`entryIds` is required unless `action: "hide"` with `clearAll: true`.'}).refine(e=>!e.clearAll||"hide"===e.action,{message:'`clearAll: true` is only valid with `action: "hide"`.'}).refine(e=>!e.hideOthers||"show"===e.action,{message:'`hideOthers: true` is only valid with `action: "show"`.'}),Kl=oe.union([oe.object({action:Vl,affectedIds:oe.array(oe.string()).describe("Entry ids that were actually shown / hidden / removed."),shown:oe.array(oe.string()).describe("BYOD entry ids currently rendered on the map after this call.")}),jt]),Xl=async(e,t)=>{if(!t.properties.dataSources?.geometry?.id)return{skip:"Place has no geometry data source (e.g. addresses and streets often lack one)."};try{const r=await e.places.fetchPlaceGeometry(t.id);return r?{feature:r}:{skip:"Geometry Data service returned no feature for this place."}}catch(r){return{skip:`Geometry fetch failed: ${r instanceof Error?r.message:String(r)}`}}},Zl=async(e,t,r)=>{const n=e.places.entries.find(e=>e.id===t);if(!n)return{rendered:0,skipped:[]};const s=r.placeIds?new Set(r.placeIds):void 0,o=s?n.data.filter(e=>s.has(e.id)):n.data,a=[],i=[];for(const l of o){const t=await Xl(e,l);"skip"in t?a.push({placeId:l.id,reason:t.skip}):i.push(t.feature)}return i.length>0&&await e.places.showPlaceGeometries(i,r.theme??"outline",r.mode??"replace",t),{rendered:i.length,skipped:a}},Ql=oe.object({id:oe.string(),label:oe.string(),featureCount:oe.number(),markerType:oe.enum(Pe).describe("How the entry is rendered on the map.")}),ec=oe.union([oe.object({success:oe.literal(!0),totalShown:oe.number(),shown:oe.array(Ql).describe("Entries currently on the map, with their labels."),actuallyAdded:oe.array(oe.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:oe.array(oe.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),geometriesRendered:oe.number().optional().describe("Number of boundary polygons rendered when `geometry` was set."),geometriesSkipped:oe.array(oe.object({placeId:oe.string(),reason:oe.string()})).optional().describe("Places whose boundary could not be rendered (no geometry data source, fetch failure).")}),jt]),tc=oe.object({add:oe.array(oe.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:oe.array(oe.string()).optional().describe("Entry ids to hide."),addMatching:oe.array(oe.string()).optional().describe("Category labels to show."),removeMatching:oe.array(oe.string()).optional().describe("Category labels to hide."),clear:oe.boolean().optional().describe("Hide every displayed entry."),markerType:oe.enum(Pe).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:Lr.optional(),fitBounds:oe.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."}),rc=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),n=/* @__PURE__ */new Set;for(const s of e.places.entries){const e=s.label.toLowerCase();r.some(t=>e.includes(t))&&n.add(s.id)}return[...n]},nc=oe.object({id:oe.string(),label:oe.string(),routeCount:oe.number().describe("Number of route alternatives in the entry."),selectedIndex:oe.number().describe("Highlighted alternative index (0 unless overridden by this call).")}),sc=oe.union([oe.object({success:oe.literal(!0),totalShown:oe.number(),shown:oe.array(nc).describe("Routes entries currently on the map, with their labels."),actuallyAdded:oe.array(oe.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:oe.array(oe.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),mainColor:oe.string().optional().describe("Sticky main colour now applied to every shown route, when set by this or a previous call.")}),jt]),oc=oe.object({add:oe.array(oe.string()).optional().describe('Entry ids to show (e.g. ["routes-1"]). Omit while still setting `selectedIndex` to target the most recently stored entry.'),remove:oe.array(oe.string()).optional().describe("Entry ids to hide."),addMatching:oe.array(oe.string()).optional().describe("Label substrings to show."),removeMatching:oe.array(oe.string()).optional().describe("Label substrings to hide."),clear:oe.boolean().optional().describe("Hide every displayed entry."),selectedIndex:oe.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:oe.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise."),mainColor:oe.string().optional().describe('Recolor the route line + waypoint icons. CSS named colour ("coral", "steelblue") or hex ("#FF0000"). Sticky — applied to every currently-shown entry AND remembered so subsequent shows pick it up automatically. Can be set by itself (no add/remove/clear) to recolor without changing visibility.')}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.selectedIndex||void 0!==e.mainColor,{message:"updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, mainColor."}),ac=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),n=/* @__PURE__ */new Set;for(const s of e.routing.entries){const e=s.label.toLowerCase();r.some(t=>e.includes(t))&&n.add(s.id)}return[...n]},ic={mode:oe.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:oe.enum(["congestionLevel","speed","travelTime"]).optional().describe("Metric to visualize. Defaults to first fetched metric on the targeted entries."),colorTheme:oe.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:oe.number().optional().describe("Extrusion height multiplier / visual maximum in meters. Default: metric-dependent."),scaleMode:oe.enum(["raw","predefinedRange","currentRange"]).optional().describe("Height scaling mode. 'raw' (default), 'predefinedRange' (normalize to SDK range), 'currentRange' (normalize to live data range)."),filter:oe.object({min:oe.number().optional(),max:oe.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.")},lc=oe.object({id:oe.string(),label:oe.string(),activeMetric:oe.string().optional(),displayMode:oe.string().optional()}),cc=oe.object({add:oe.array(oe.string()).optional().describe('Entry ids to render (e.g. ["tta-0"]). Use `recallState` to list available ids.'),remove:oe.array(oe.string()).optional().describe("Entry ids to hide."),addMatching:oe.array(oe.string()).optional().describe("Label substrings to show."),removeMatching:oe.array(oe.string()).optional().describe("Label substrings to hide."),clear:oe.boolean().optional().describe("Hide every displayed entry."),fitBounds:oe.boolean().optional().describe("Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added."),...ic}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.mode||void 0!==e.metric||void 0!==e.colorTheme||void 0!==e.heightScale||void 0!==e.scaleMode||void 0!==e.filter,{message:"updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, mode, metric, colorTheme, heightScale, scaleMode, filter."}),dc=oe.union([oe.object({success:oe.literal(!0),totalShown:oe.number(),shown:oe.array(lc).describe("Traffic-area-analytics entries currently on the map."),actuallyAdded:oe.array(oe.string()),actuallyRemoved:oe.array(oe.string())}),jt]),uc=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),n=/* @__PURE__ */new Set;for(const s of e.trafficAreaAnalytics.entries)r.some(e=>s.label.toLowerCase().includes(e))&&n.add(s.id);return[...n]},pc=async(e,t,r)=>{const n=e.trafficAreaAnalytics.findById(t);if(!n)return;const s=n.data.properties?.metrics?.[0]??"congestionLevel",o=r.mode??"hexgrid-3d",a=r.metric??s,i=await e.trafficAreaAnalytics.getEntryModule(t);i.setMode(o),i.setMetric(a),void 0!==r.colorTheme&&i.setColor(r.colorTheme),void 0===r.heightScale&&void 0===r.scaleMode||i.setHeight({...void 0!==r.heightScale&&{maxHeightMeters:r.heightScale},...void 0!==r.scaleMode&&{scaleMode:r.scaleMode}}),r.filter?i.filter({min:r.filter.min,max:r.filter.max}):i.clearFilter()},hc=e=>({id:e.id,label:e.label,...e._config?.activeMetric&&{activeMetric:e._config.activeMetric},...e._config?.displayMode&&{displayMode:e._config.displayMode}}),yc=oe.union([oe.object({success:oe.literal(!0),visible:oe.boolean(),count:oe.number()}),jt]),mc=oe.object({visible:oe.boolean().optional().describe("Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.")}),gc="Dynamic-JS sandbox over any combination of places / routes / incidents / geometries / trafficAreaAnalytics / byod entries. Each input arg is `undefined` when its `*EntryIDs` was omitted.",fc={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:Qo,outputSchema:ea,execute:async(e,t,r)=>{const{query:n,queryAs:s,waypointIndex:o,where:a,show:i,entryId:l,geometry:c}=e;try{const e=await ta(a,t,r);if("error"in e)return e;const d=await Sn(n,s,e.bias,r);if(!d)return{error:`No result found for "${n}"`};const u=await t.places.addPlaceResult(d,os(d,{query:n}),l);void 0!==o&&t.routing.setWaypointAt(o,d);const p=i?await(async(e,t,r,n)=>{const s=await _s(e,[r],{...n,zoomMode:"none"});if("auto"===n.zoomMode){if(t.bbox)e.baseMap.mapLibreMap.fitBounds(t.bbox);else{const r=J(t);r&&e.baseMap.mapLibreMap.flyTo({center:r,zoom:13})}s.zoomMode=!0}return s})(t,d,u,i):void 0;let h,y;if(c){const e=await Ps(t,u,c,r);h=e.fetched>0,e.shown&&(y=!0)}return{...cs(d),placesEntryId:u,...void 0!==o&&{waypointIndex:o},...p&&{shown:p},...void 0!==h&&{geometryFetched:h},...void 0!==y&&{geometryShown:y}}}catch(d){return{error:`Location resolution failed: ${d instanceof Error?d.message:String(d)}`}}},tags:["locate","place","location","waypoint"],examples:['locatePlace("Buckingham Palace", queryAs: "poi", waypointIndex: 0) // omit `where` to resolve the name anywhere (global)','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","Show Times Square on the map","Pin Schiphol Airport on the map","Outline Amsterdam on the map","Show the boundary of Berlin","Show the boundaries of Amsterdam and Utrecht","Pin the Eiffel Tower on the map","Where is the Sydney Opera House?"],relatedTools:["setRoute","addWaypointsToRoute","getCurrentWaypoints","recallState"]},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:la,outputSchema:ia,execute:async(e,t,r)=>{const{position:n}=e,s=n;try{const e=Te({position:s,signal:r?.signal}),n=await R(e);if(n.properties){const e=await t.places.addPlaceResult(n,os(n));return{...cs(n),placesEntryId:e}}return{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:["Which city is [4.9, 52.4] in?","What's the address at this point?","Name the place where I clicked"],relatedTools:["getViewport","getCurrentLocation","updatePlacesDisplay","locatePlace"]},discoverPlaces:e=>{return t=e.featureFlags??{},r=_o,{...r,description:Mo(t),inputSchema:So(t),outputSchema:bo(t),execute:Po(t)};var t,r},processData:(gs={classificationPrompt:`${gc} WRITES NEW renderable map entries — returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\` to write a places, custom-geometries, or BYOD entry, move the camera, or any combination. NOT for digests/charts/counts — those are \`analyseData\` (metadata only, never renders). Use for: filter/merge places (set ops), connect places (hub-spoke), compute polygons (union/buffer/h3-coverage/difference), focus camera on route sections via \`routeUtils.getSectionBBox\`, cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile).`,tags:["turf","h3","geometry","place","route","aggregation","connections"],examples:['processData({ placesEntryIDs: ["places-2"], code: "return { places: { type: \'FeatureCollection\', features: placesByEntry[\'places-2\'].features.filter(p => (p.properties.poi?.name ?? \'\').toLowerCase().includes(\'vegan\')) } };", label: "vegan only", show: { places: { markerType: "pin", zoomMode: "auto", hidePreviousEntries: "all" } } })','processData({ placesEntryIDs: ["places-1"], code: "const fc = placesByEntry[\'places-1\']; const [hub, ...rest] = fc.features; return { places: fc, placeConnections: rest.map(p => ({ from: hub, to: p })) };" })','processData({ placesEntryIDs: ["places-1"], code: "const fc = placesByEntry[\'places-1\']; const cells = new Set(); for (const p of fc.features) cells.add(h3.latLngToCell(p.geometry.coordinates[1], p.geometry.coordinates[0], 8)); return { places: fc, geometries: [...cells].map(c => turf.polygon([h3.cellToBoundary(c, true)], { cell: c })) };", show: { places: { markerType: "pin", zoomMode: "auto" }, placesGeometries: { theme: "outline" } } })','processData({ routesEntryIDs: ["routes-0"], code: "const r = routesByEntry[\'routes-0\'].features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };" })','processData({ routesEntryIDs: ["routes-0"], code: "const r = routesByEntry[\'routes-0\'].features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };" })','processData({ placesEntryIDs: ["ev-stations"], routesEntryIDs: ["routes-0"], code: "const r = routesByEntry[\'routes-0\'].features[0]; const buf = turf.buffer(r, 0.5, { units: \'kilometers\' }); const near = placesByEntry[\'ev-stations\'].features.filter(p => turf.booleanPointInPolygon(p, buf)); return { places: { type: \'FeatureCollection\', features: near }, fitOnMap: turf.bbox(turf.featureCollection([...near, r])) };", label: "ev near route", entryId: "ev-near-routes-0", show: { places: { markerType: "pin", zoomMode: "none", hidePreviousEntries: "all" } } })','processData({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam-id" }, { kind: "place", id: "rotterdam-id" }], code: "const polys = Object.values(geometriesByEntry).flat(); return { geometries: [turf.union(turf.featureCollection(polys))].filter(Boolean) };", label: "ams ∪ rdam", operation: "union" })','processData({ geometriesEntryIDs: [{ kind: "place", id: "paris-id" }], code: "return { geometries: Object.values(geometriesByEntry).flat().map(f => ({ ...turf.buffer(f, 5, { units: \'kilometers\' }), id: `${f.id}-buf` })) };", label: "paris +5km", operation: "buffer" })','processData({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam-id" }, { kind: "ranges", id: "ranges-0" }], code: "const all = Object.values(geometriesByEntry).flat(); const outer = all.filter(g => g.properties._source.kind === \'place\'); const inner = all.filter(g => g.properties._source.kind === \'ranges\'); const env = turf.union(turf.featureCollection(outer)); const u = turf.union(turf.featureCollection(inner)); const inv = (env && u) ? turf.difference(turf.featureCollection([env, u])) : env; return { geometries: inv ? [{ ...inv, id: \'unreachable\' }] : [] };", label: "amsterdam minus reachable", operation: "difference", show: { customGeometries: { theme: "inverted", hidePreviousEntries: "all" } } })','processData({ geometriesEntryIDs: [{ kind: "ranges", id: "ranges-0" }, { kind: "places", id: "ev-stations" }], code: "const ranges = Object.values(geometriesByEntry).flat().filter(g => g.properties._source.kind === \'ranges\'); const env = ranges.length ? turf.union(turf.featureCollection(ranges)) : null; const ev = (placesByEntry?.[\'ev-stations\']?.features ?? []); const kept = env ? ev.filter(p => turf.booleanPointInPolygon(p, env)) : ev; return { places: { type: \'FeatureCollection\', features: kept } };", label: "ev within reachable" })','processData({ placesEntryIDs: ["ev-stations"], trafficAreaAnalyticsEntryIDs: ["tta-0"], code: "const hot = trafficAreaAnalyticsByEntry[\'tta-0\'].features.filter(t => (t.properties.congestionLevel ?? 0) >= 75); const kept = placesByEntry[\'ev-stations\'].features.filter(p => hot.some(t => turf.booleanPointInPolygon(p, t))); return { places: { type: \'FeatureCollection\', features: kept } };", label: "ev in high-congestion tiles" })','processData({ trafficAreaAnalyticsEntryIDs: ["tta-0"], code: "const hot = trafficAreaAnalyticsByEntry[\'tta-0\'].features.filter(t => (t.properties.congestionLevel ?? 0) >= 75).map((t, i) => ({ ...t, id: `hot-${i}` })); const u = hot.length ? turf.union(turf.featureCollection(hot)) : null; return { geometries: u ? [{ ...u, id: \'congestion-hot-zone\' }] : [] };", label: "congestion hot zone", operation: "union", show: { customGeometries: { theme: "filled" } } })'],examplePrompts:["Keep only the vegan restaurants","Draw lines from this hub to every nearby cafe","Show the h3 hex coverage of these places","Fit the map to the worst traffic section of the route","Fit the map to the first toll section of the route","Filter my loaded EV stations to those within 500 m of the route","Union the city boundaries I loaded into one custom polygon","Compute a 500 m buffer around the neighbourhood I loaded","Areas of Amsterdam not reachable from the hospitals","Filter loaded places to those inside the reachable area","Keep only the EV stations that fall inside high-congestion tiles","Create a custom polygon outlining the congestion hot zone in my loaded analytics"],relatedTools:["analyseData","recallState"],dependsOn:["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","addByodSource","recallState"]},e=>((e,t)=>{const r=e.enabledDataKinds??tn;return{description:ni(r,e.scope),inputSchema:ri(r,e.scope),outputSchema:ti,execute:yi,scopeSchema:rn(r),scopePrompt:si(r),...t}})(e,gs)),analyseData:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??tn;return{description:ba(r,e.scope),inputSchema:fa(r,e.scope),outputSchema:ga,execute:va,scopeSchema:rn(r),scopePrompt:wa(r),...t}})(t,e))({classificationPrompt:`${gc} WRITES metadata only — returns JSON or a Chart.js config (\`outputFormat: "chart"\`), attached as \`_analysis[name]\` on every contributing entry. Never renders or creates new map entries (that is \`processData\`). Use for: counts / groupings / charts (by-category, hex density); route summaries (per-leg / per-country / alternative compare); route TRAFFIC analysis from \`route.properties.sections.traffic[]\` — pick THIS for "distribution / count / chart of <something> along the route", NOT \`getTrafficIncidents\`; geometry stats (areas, h3 coverage); historical traffic-area-analytics breakdowns (congestion / speed / travel-time per tile); cross-kind correlations (incidents-per-cluster, places-near-route, distance-to-route bins, route-segments-by-tile-metric). One-shot by default — it returns an \`analysisId\`; pass that to \`monitorAnalysis\` to keep the analysis recomputing whenever its source entries change (the recurring sandbox then also sees \`previous\` / \`now\`).`,tags:["analysis","turf","h3","place","route","geometry"],examples:['analyseData({ placesEntryIDs: ["places-2"], name: "by-category", code: "const fc = placesByEntry[\'places-2\']; const counts = {}; for (const p of fc.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; return { total: fc.features.length, byCategory: counts };" })','analyseData({ placesEntryIDs: ["places-1"], name: "hex-density-8", code: "const fc = placesByEntry[\'places-1\']; const bins = {}; for (const p of fc.features) { const [lng,lat] = p.geometry.coordinates; const cell = h3.latLngToCell(lat, lng, 8); bins[cell] = (bins[cell] ?? 0) + 1; } return { resolution: 8, bins };" })','analyseData({ placesEntryIDs: ["places-2"], name: "top-categories-bar", outputFormat: "chart", code: "const fc = placesByEntry[\'places-2\']; const counts = {}; for (const p of fc.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]).slice(0,10); return { type: \'bar\', data: { labels: entries.map(e => e[0]), datasets: [{ label: \'Places\', data: entries.map(e => e[1]) }] } };" })','analyseData({ routesEntryIDs: ["routes-0"], name: "summary", code: "return routesByEntry[\'routes-0\'].features.map(r => ({ index: r.properties.index, distanceKm: r.properties.summary.lengthInMeters / 1000, durationMin: r.properties.summary.travelTimeInSeconds / 60, trafficDelaySec: r.properties.summary.trafficDelayInSeconds }));" })',"analyseData({ routesEntryIDs: [\"routes-0\"], name: \"alternative-comparison\", outputFormat: \"chart\", code: \"const r = routesByEntry['routes-0'].features; return { type: 'bar', data: { labels: r.map(x => `Route ${x.properties.index}`), datasets: [{ label: 'Travel time (min)', data: r.map(x => Math.round(x.properties.summary.travelTimeInSeconds / 60)) }, { label: 'Distance (km)', data: r.map(x => Math.round(x.properties.summary.lengthInMeters / 1000)) }] } };\" })","analyseData({ routesEntryIDs: [\"routes-0\"], name: \"on-route-incidents-by-category\", outputFormat: \"chart\", code: \"const sections = (routesByEntry['routes-0'].features[0].properties.sections.traffic ?? []); const counts = {}; for (const s of sections) for (const c of (s.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]); return { type: 'bar', data: { labels: entries.map(e => e[0]), datasets: [{ label: 'Incidents on route', data: entries.map(e => e[1]) }] }, options: { plugins: { title: { display: true, text: 'Traffic incidents along the route' } } } };\" })",'analyseData({ routesEntryIDs: ["routes-0"], name: "delay-by-country", code: "const r = routesByEntry[\'routes-0\'].features[0]; const byCountry = {}; for (const c of (r.properties.sections.country ?? [])) byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (r.properties.sections.traffic ?? []).filter(t => t.startPointIndex >= c.startPointIndex && t.endPointIndex <= c.endPointIndex).reduce((s, t) => s + (t.delayInSeconds ?? 0), 0); return byCountry;" })','analyseData({ routesEntryIDs: ["routes-0"], placesEntryIDs: ["ev-stations"], name: "ev-near-route-500m", code: "const route = routesByEntry[\'routes-0\'].features[0]; const out = {}; for (const [id, fc] of Object.entries(placesByEntry)) out[id] = fc.features.filter(p => turf.pointToLineDistance(p, route, { units: \'meters\' }) <= 500).length; return { withinMeters: 500, byEntry: out };" })',"analyseData({ routesEntryIDs: [\"routes-0\"], placesEntryIDs: [\"cafes\"], name: \"cafe-distance-bins-km\", outputFormat: \"chart\", code: \"const route = routesByEntry['routes-0'].features[0]; const distances = placesByEntry['cafes'].features.map(p => turf.pointToLineDistance(p, route, { units: 'kilometers' })); const max = Math.max(0, ...distances); const binWidth = 1; const binCount = Math.max(1, Math.ceil(max / binWidth)); const bins = new Array(binCount).fill(0); for (const d of distances) bins[Math.min(binCount - 1, Math.floor(d / binWidth))]++; const labels = bins.map((_, i) => `${i}-${i + binWidth} km`); return { type: 'bar', data: { labels, datasets: [{ label: 'cafes', data: bins }] }, options: { plugins: { title: { display: true, text: 'Cafe distance to route' } } } };\" })",'analyseData({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "areas", code: "return Object.values(geometriesByEntry).flat().map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));" })','analyseData({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "area-bar", outputFormat: "chart", code: "const polys = Object.values(geometriesByEntry).flat(); return { type: \'bar\', data: { labels: polys.map(f => f.id), datasets: [{ label: \'km²\', data: polys.map(f => +(turf.area(f)/1e6).toFixed(2)) }] } };" })',"analyseData({ placesEntryIDs: [\"clusters\"], incidentsEntryIDs: [\"incidents-0\"], name: \"incidents-per-cluster\", code: \"const fc = placesByEntry['clusters']; const inc = incidentsByEntry['incidents-0']; const out = {}; for (const c of fc.features) { const inCluster = inc.filter(i => { const coord = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.distance(coord, c.geometry.coordinates, { units: 'kilometers' }) <= 0.5; }); out[c.id] = inCluster.length; } return out;\" })",'analyseData({ geometriesEntryIDs: [{ kind: "customGeometries", id: "buffered-routes" }], incidentsEntryIDs: ["incidents-0"], name: "incidents-in-corridors", code: "const polys = Object.values(geometriesByEntry).flat(); const inc = incidentsByEntry[\'incidents-0\']; const out = []; for (const g of polys) { const hits = inc.filter(i => { const coord = i.geometry.type === \'Point\' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.booleanPointInPolygon(coord, g); }); out.push({ id: g.id, count: hits.length, totalDelaySec: hits.reduce((s, i) => s + (i.properties.delayInSeconds ?? 0), 0) }); } return out;" })',"analyseData({ trafficAreaAnalyticsEntryIDs: [\"tta-0\"], name: \"congestion-distribution\", outputFormat: \"chart\", code: \"const bins = [0, 25, 50, 75, 100]; const counts = new Array(bins.length).fill(0); for (const t of trafficAreaAnalyticsByEntry['tta-0'].features) { const c = t.properties.congestionLevel ?? 0; const idx = Math.min(bins.length - 1, Math.floor(c / 25)); counts[idx]++; } return { type: 'bar', data: { labels: ['0–24', '25–49', '50–74', '75+'], datasets: [{ label: 'Tiles', data: counts.slice(0, 4) }] } };\" })",'analyseData({ placesEntryIDs: ["ev-stations"], trafficAreaAnalyticsEntryIDs: ["tta-0"], name: "ev-stations-by-congestion", code: "const fc = placesByEntry[\'ev-stations\']; const tiles = trafficAreaAnalyticsByEntry[\'tta-0\'].features; const buckets = { low: 0, mid: 0, high: 0 }; for (const p of fc.features) { const tile = tiles.find(t => turf.booleanPointInPolygon(p, t)); if (!tile) continue; const c = tile.properties.congestionLevel ?? 0; if (c < 25) buckets.low++; else if (c < 75) buckets.mid++; else buckets.high++; } return buckets;" })','analyseData({ routesEntryIDs: ["routes-0"], trafficAreaAnalyticsEntryIDs: ["tta-0"], name: "route-tile-coverage", code: "const r = routesByEntry[\'routes-0\'].features[0]; const hits = trafficAreaAnalyticsByEntry[\'tta-0\'].features.filter(t => turf.lineIntersect(r, t).features.length > 0); return { tilesCrossed: hits.length, avgCongestion: hits.length ? +(hits.reduce((s, t) => s + (t.properties.congestionLevel ?? 0), 0) / hits.length).toFixed(1) : null };" })','analyseData({ incidentsEntryIDs: ["incidents-0"], name: "by-category", code: "const inc = incidentsByEntry[\'incidents-0\']; const c = {}; for (const i of inc) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; return { total: inc.length, byCategory: c };" })','analyseData({ incidentsEntryIDs: ["incidents-0"], name: "top-delays", code: "return [...incidentsByEntry[\'incidents-0\']].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] }));" })'],examplePrompts:["How many of each POI category are in these results?","h3 hex density of these places","Bar chart of the top POI categories","Bar chart of the lengths of my route alternatives","Bar chart of travel times across my route alternatives","Of my loaded EV stations, how many are within 500 m of my route?","Chart the distance from each cafe to the route, binned in km","Histogram of place-to-route distances","Distribution by category of the traffic incidents I loaded along my route","Chart the incidents on the route by category","Total delay by country for the incidents on my route","How big are the city areas I loaded?","Bar chart of the sizes of the city areas I loaded","Count the loaded incidents per cluster of my loaded places","Total delay per corridor for my loaded route incidents","Congestion distribution across the analytics tiles","Chart the average historical speed per tile in my loaded analytics","Group these EV stations by the congestion level of the tile they fall in","For the route, which tiles does it cross and what is their average congestion?","How many incidents of each category?","Top 5 incidents by delay","Total delay per road across my loaded incidents","Bar chart of incidents per category"],relatedTools:["processData","focusIncidents","recallState"],dependsOn:["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","addByodSource","processData","recallState"]}),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. To MONITOR traffic between places over time, calculate with `parameters.costModel.traffic: "historical"` + `maxAlternatives: 1-2` (stable typical paths) and set `monitor: true` — that arms the live monitor in the same call (each tick refreshes live-traffic delays on the same routes), so no separate startRouteMonitor call is needed. Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallState / addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / analyseData 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. To MONITOR / track / keep fresh the live traffic BETWEEN two places, START here (calculate with traffic "historical" + maxAlternatives 1-2 for stable paths), then pair with startRouteMonitor — startRouteMonitor needs a route to exist first.',inputSchema:uo,outputSchema:co,execute:async(e,t,r)=>{const{locations:n,parameters:s,showOnMap:o,showWaypoints:a,showSummaryBubbles:i,monitor:l,hidePreviousEntries:c}=e;try{let e;if(s&&t.routing.setParams(s),n){const s=await Promise.all(n.map(e=>oo(e,t,r))),o=[];for(let e=0;e<n.length;e++){if(null!==s[e])continue;const t=n[e];"query"in t?o.push(`"${t.query}"`):"placeIdOrEntryId"in t&&o.push(`placeIdOrEntryId "${t.placeIdOrEntryId}"`)}if(o.length>0)return{error:`Could not resolve: ${o.join(", ")}`};if(e=po(s.map(e=>e?.place??null)),!e)return{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}else if(e=po(t.routing.planningSlots),!e)return{success:!0};return mo(t,e,o,c,l,a,i,r)}catch(d){return{error:`Route calculation failed: ${d instanceof Error?d.message:String(d)}`}}},tags:["route","waypoint","location","traffic"],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" } } })','setRoute({ locations: [{ query: "Paris" }, { query: "Amsterdam" }], parameters: { maxAlternatives: 2, costModel: { traffic: "historical" } }, monitor: true })'],examplePrompts:["Get directions from Paris to Lyon","Route me from Amsterdam to Brussels","Plan a new trip from Amsterdam to Schiphol airport","Recalculate my route to Brussels from Schiphol airport","Recalculate the route from Utrecht to Brussels","Avoid toll roads on my current route","Find the fastest route from Berlin to Munich","Show 2 alternative routes for my current trip","Depart at 9am on my current route","Monitor traffic between Paris and Amsterdam","Monitor traffic between Santa Monica and Downtown LA","Keep an eye on live traffic from the office to the airport","Show me fresh traffic between A and B","Plan a road trip from Lisbon to Porto","Take me from the Eiffel Tower to the Arc de Triomphe"],relatedTools:["startRouteMonitor","updateRoutesDisplay","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","recallState"]},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:fo,outputSchema:go,execute:async(e,t,r)=>{const{origin:n,destination:s,stops:o,showOnMap:a,hidePreviousEntries:i}=e;try{const e=await(async e=>{const t=e.routing.currentEntryModule;if(!t)return"No existing route on the map. Use calculate-route or updateRoutesDisplay first.";const r=t.getShown(),n=r?.mainLines,s=r?.waypoints;return n&&n.features.length?!s||s.features.length<2?"No existing waypoints available. Use calculate-route first to create a route with waypoints.":{shownRouteLines:n,shownWaypoints:s}:"No existing route available. Use calculate-route first."})(t);if("string"==typeof e)return{error:e};const{shownRouteLines:l,shownWaypoints:c}=e,d=await(async(e,t,r)=>{const{origin:n,destination:s,stops:o=[]}=e,a=[...n?[{kind:"origin",location:n}]:[],...s?[{kind:"destination",location:s}]:[],...o.map(e=>({kind:"stop",location:e}))],i=await Promise.all(a.map(({location:e})=>oo(e,t,r))),l=[];for(let d=0;d<a.length;d++)null===i[d]&&l.push(kn(a[d].location));if(l.length>0)return{error:`Could not resolve: ${l.join(", ")}`};const c={stops:[]};for(let d=0;d<a.length;d++){const e=i[d]?.place;"origin"===a[d].kind?c.origin=e:"destination"===a[d].kind?c.destination=e:c.stops.push(e)}return c})({origin:n,destination:s,stops:o},t,r);if("error"in d)return d;const u=[...d.origin?[d.origin]:[],...c.features,...d.destination?[d.destination]:[]],p=d.stops.length>0?ne(l.features[0],u,d.stops):u,h=po(p);return h?mo(t,h,a,i,void 0,void 0,void 0,r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(l){return{error:`Failed to add waypoints to route: ${l instanceof Error?l.message:String(l)}`}}},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:na,outputSchema:ra,execute:async(e,t,r)=>{const{waypointIndices:n,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 n of t)if(n<0||n>=e.length)return`Invalid waypoint index ${n}. 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,n);if("string"==typeof e)return{error:e};const a=po(e);return a?mo(t,a,s,o,void 0,void 0,void 0,r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(a){return{error:`Failed to remove waypoints from route: ${a instanceof Error?a.message:String(a)}`}}},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:aa,outputSchema:sa,execute:async(e,t,r)=>{const{waypointIndex:n,location:s,showOnMap:o,hidePreviousEntries:a}=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 c=(i=n,l=e.length,"origin"===i?0:"destination"===i?l-1:i<0||i>=l?`Invalid waypoint index ${i}. Valid range is 0 to ${l-1}.`:i);if("string"==typeof c)return{error:c};const d=await oo(s,t,r);if(!d)return{error:`Could not resolve location: ${kn(s)}`};const u=[...e];u[c]=d.place;const p=po(u);return p?mo(t,p,o,a,void 0,void 0,void 0,r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(c){return{error:`Failed to replace waypoint in route: ${c instanceof Error?c.message:String(c)}`}}var i,l},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:qo,outputSchema:Fo,execute:async(e,t,r)=>{const{filters:n,language:s}=e;try{const e=Te({filters:n,language:s,signal:r?.signal}),t=await C(e);return{count:t.length,codes:t}}catch(o){return{error:`Failed to fetch POI category codes: ${o instanceof Error?o.message:String(o)}`}}},tags:["discover","place"],examples:["getPOICategoryCodes()",'getPOICategoryCodes(filters: ["gym"])','getPOICategoryCodes(filters: ["italian restaurant"]) // multi-word phrase, spaces removed before matching','getPOICategoryCodes(filters: ["restaurant"], language: "fr-FR")'],examplePrompts:["What POI categories are available?","What's the code for gym?"],relatedTools:["discoverPlaces","toggleTilesPOIs"]},getTrafficIncidents:{description:'LOADER. Fetches present/future traffic incidents into a cached entry, renders them, and returns `{ count, entryId, entries }` (`entries` lists every cached entry as a follow-up target). No per-incident detail or aggregation — those belong to analyseData/focusIncidents. \n\nAREA via `where` (within mode only): omit `where` → current viewport; named area → `where.queries`; stored places → `where.placeIds` (a places ENTRY id like "places-0" or locatePlace\'s `placesEntryId`, or a single place id); isochrone → `where.range`; route corridor → `where.route`; raw bounds → `where.boundingBox`; custom shapes → `where.geometries`. Fields union into one bbox (≤10,000 km²). \n\nCALL ONCE PER AREA — each call writes a new entry, so re-calling burns steps; pick a follow-up instead: analyseData (counts, top-N, charts, clusters, trend via `previous`) or focusIncidents (highlight a subset). \n\nSTAYS LIVE BY DEFAULT: a shown fetch arms a background monitor (`monitor: true`) that keeps the entry current and re-runs registered analyses each tick — no separate setTrafficIncidentsMonitor call needed (that is only for an entry loaded on an EARLIER turn). Pass `monitor: false` for a one-shot snapshot. `show: false` suppresses rendering (throwaway loads only; never monitored); `label` names the snapshot in the UI. \n\nCHECK WHERE IT RESOLVED: the result `resolvedAreas` reports the GROUNDED place each named query matched (e.g. "east London" → "London, CA"). If it resolved wrong, re-issue with a more specific query (append a region/country).',classificationPrompt:`AREA-BOUND incident loader — bbox / viewport / "incidents around here" / "accidents in <city>". Covers every incident category: ${re.join(", ")}. DO NOT pick for route-bound queries ("incidents ALONG / ON the route", "delays on the drive from X to Y"): a calculated route already carries its incidents in \`route.properties.sections.traffic[]\` — call \`analyseData\` with \`routesEntryIDs\` for those. Monitoring is built in: a shown fetch keeps the entry live (\`monitor: true\` by default; \`monitor: false\` for a one-shot count) — no separate setTrafficIncidentsMonitor call needed. Co-pick analyseData (aggregations / charts / clusters; pass \`monitor: { entryId }\` for the recurring spec) and focusIncidents (highlight a subset) when the query implies them.`,inputSchema:Xo,outputSchema:Ho,execute:async(e,t,r)=>{const{where:n,categoryFilter:s,timeValidityFilter:o,label:a,show:i=!0,fitBounds:l=!0,monitor:c,monitorIntervalMs:d}=e,u=n??{mode:"within",viewport:!0},p={...s&&{categoryFilter:s},...o&&{timeValidityFilter:o}};try{const e=await Zo(u,t);if("error"in e)return e;const{bbox:n,routeLabel:s,resolvedAreas:o}=e,h=Te({...p,bbox:n,signal:r?.signal}),y=await L(h),m={bbox:n,...p},g=Date.now(),f=a??((e,t,r,n)=>{const s=[];e.categoryFilter?.length&&s.push(`filtered: ${e.categoryFilter.join(",")}`),e.timeValidityFilter?.length&&s.push(`when: ${e.timeValidityFilter.join(",")}`);let o=n?.length?n.map(e=>e.matched).join(", "):void 0;if(void 0===o&&t&&!t.viewport&&(t.queries?.length?o=t.queries.map(e=>e.query).join(", "):r?o=`along ${r}`:t.range&&(o=t.range)),!o){const[t,r,n,s]=e.bbox;o=`bbox ${t.toFixed(2)},${r.toFixed(2)} → ${n.toFixed(2)},${s.toFixed(2)}`}return s.length?`${o} — ${s.join("; ")}`:o})(m,u,s,o);if(!y.features.length)return{count:0,entryId:await t.trafficIncidents.addIncidentsEntry([],m,f,g),entries:Yo(t),...o&&o.length>0&&{resolvedAreas:o}};const b=await t.trafficIncidents.addIncidentsEntry(y.features,m,f,g);i&&await t.trafficIncidents.showEntry(b,l);const w=i&&(c??!0);return w&&t.trafficIncidents.startMonitoring(b,{bbox:n,capturedAt:g,label:f},{fetchIncidents:async e=>{const t=Te({...p,bbox:e});return(await L(t)).features}},{intervalMs:d,skipInitialTick:!0}),{count:y.features.length,entryId:b,entries:Yo(t),monitoring:w,...o&&o.length>0&&{resolvedAreas:o}}}catch(h){return{error:`Failed to get traffic incidents: ${h instanceof Error?h.message:String(h)}`}}},tags:["traffic","location"],examples:["getTrafficIncidents({}) // omit `where` → current viewport",'getTrafficIncidents({ where: { mode: "within", boundingBox: [4.728, 52.278, 5.080, 52.479] } })','getTrafficIncidents({ where: { mode: "within", queries: [{ query: "Amsterdam" }] } }) // named area','getTrafficIncidents({ where: { mode: "within", queries: [{ query: "Amsterdam" }] }, categoryFilter: ["accident", "jam"] })','getTrafficIncidents({ where: { mode: "within", route: { widthMeters: 1000 } }, categoryFilter: ["roadworks"], timeValidityFilter: ["future"] }) // corridor around latest route','getTrafficIncidents({ where: { mode: "within", range: "ranges-0" } }) // inside a reachable area'],examplePrompts:["Fetch traffic incidents in Amsterdam","Any accidents in this area?","Show roadworks across the city","Watch this area for new accidents","Are there any road closures downtown right now?","Show me the jams around the Amsterdam ring road"],relatedTools:["calculateBBox","getViewport","analyseData","focusIncidents","setTrafficIncidentsMonitor"]},focusIncidents:{description:"Highlight a subset of incidents on an existing entry. Drives the dim/highlight on the map and the FocusChip in the UI. Compose with `analyseData` to compute the ids first (e.g. top-N by delay, members of a cluster). Unknown ids are dropped silently and returned in `droppedIds`. Errors only when the entry is unknown or NONE of the ids resolve.",classificationPrompt:'Highlight a subset of incidents on an existing entry (drives map dim/highlight + FocusChip). Pick for "focus on…", "show only…", "highlight the…". Compose with analyseData to compute the ids.',inputSchema:Ua,outputSchema:Ga,execute:async(e,t)=>{const{incidentsEntryID:r,incidentIds:n,reason:s}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const o=t.trafficIncidents.setFocus(r,n,s);return 0===o.focusedCount?{error:`None of the ${n.length} requested ids exist on entry "${r}". The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.`}:{incidentsEntryID:r,focusedCount:o.focusedCount,droppedIds:o.droppedIds,...s&&{reason:s}}},tags:["traffic","incident","state","ui"],examples:["focusIncidents({ incidentsEntryID: 'incidents-0', incidentIds: ['TTI-...', 'TTI-...'], reason: 'top 3 by delay' })"],examplePrompts:["Focus on the 3 worst incidents by delay","Highlight the closures","Show only the incidents on the A4"],relatedTools:["analyseData","getTrafficIncidents"],dependsOn:["getTrafficIncidents"]},clusterIncidents:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??tn,{active:n}=_a(r,e.scope);return{description:Ba(n),inputSchema:Ca(r,e.scope),outputSchema:Ra,execute:La,scopeSchema:rn(r),scopePrompt:Na(r),...t}})(t,e))({classificationPrompt:'DBSCAN clustering of loaded traffic incidents — "clusters / hotspots / dense pockets / worst areas". Produces stable IDs, road/category labels, delay aggregates, trend detection. Auto-reruns on monitor ticks. Also handles clustering of a FILTERED subset ("only jams / accidents", "delayed over N minutes", "along this route / within this area") via generated `code`. Co-pick with getTrafficIncidents (which keeps the entry monitored by default).',tags:["traffic","incident","analysis","clustering"],examples:["clusterIncidents({ incidentsEntryID: 'incidents-0' })","clusterIncidents({ incidentsEntryID: 'incidents-0', eps: 0.3, minMembers: 4, maxClusters: 8 })","clusterIncidents({ incidentsEntryID: 'incidents-0', code: \"const major = incidentsByEntry['incidents-0'].filter(i => (i.properties.delayInSeconds ?? 0) >= 300); return cluster(major, { eps: 0.4 }, previous, now);\" })","clusterIncidents({ incidentsEntryID: 'incidents-0', code: \"const jams = incidentsByEntry['incidents-0'].filter(i => i.properties.category === 'jam'); return cluster(jams, {}, previous, now);\" })"],examplePrompts:["Cluster the incidents","Show me the hotspots","Where are the worst congestion pockets?","Group the incidents into clusters","Cluster only the major jams","Where are the hotspots of incidents delayed more than 5 minutes?","Cluster just the accidents and road closures"],relatedTools:["getTrafficIncidents","setTrafficIncidentsMonitor","analyseData","focusIncidents"],dependsOn:["getTrafficIncidents"]}),monitorAnalysis:{description:'Start or stop MONITORING an existing analysis (one created by `analyseData`, referenced by its `analysisId`). When enabled, the analysis recomputes on every change to its source entries — incidents monitor ticks, re-searched places, recalculated routes, refreshed BYOD/area-analytics — keeping its attached result live (use `previous` / `now` in the code for trend reads). Pick for "keep this updated / tell me when it changes / stop tracking it". This is a pure on/off switch: define the computation with `analyseData` first. For a one-off highlight of incidents use `focusIncidents`.',classificationPrompt:'Start or stop MONITORING an existing analysis by its `analysisId` (from analyseData) — a pure on/off switch that recomputes the analysis on every change to its source entries (incidents monitor ticks, re-searched places, recalculated routes, refreshed data). When the user points at an analysis ALREADY in state ("that count", "the breakdown you computed") and wants it kept live — "keep that count updated and tell me when it grows or fades", "stop tracking that analysis" — pick THIS ALONE: the analysis already exists, so do NOT re-fetch the data or arm a tracker. "Tell me when it grows or fades" about an existing computed metric is monitorAnalysis, not createTracker. Define the computation with analyseData first (a ONE-SHOT analysis needs analyseData alone); for a one-off highlight use focusIncidents.',inputSchema:Za,outputSchema:Xa,execute:async(e,t)=>((e,t,r)=>{const n=e.analyses.job(t);return!!n&&(n.setActive(r),!0)})(t,e.analysisId,e.enabled)?{analysisId:e.analysisId,enabled:e.enabled}:{error:`No analysis with id "${e.analysisId}". Run analyseData first and pass the analysisId it returns.`},tags:["analysis","monitor"],examples:['monitorAnalysis({ analysisId: "rolling-count::incidents-0", enabled: true })','monitorAnalysis({ analysisId: "ev-near-route::places-0,route-1", enabled: false })'],examplePrompts:["Keep that incident count updated and tell me when it grows or fades","Stop tracking that analysis"],relatedTools:["analyseData","getTrafficIncidents","setTrafficIncidentsMonitor","focusIncidents"],dependsOn:["analyseData"]},createTracker:{description:'Arm a tracker: watch loaded entries and alert when a condition crosses from false to true (and log when it clears). You write `code` returning `{ active, members, summary }`; it recomputes automatically whenever the watched entries change (a monitored incidents poll, a re-search, …). Reads the same multi-kind inputs as `analyseData`, so one rule can span kinds — e.g. an incident near a hospital. The `*EntryIDs` you pass MUST be ids of entries already loaded this session — load them FIRST (e.g. call getTrafficIncidents and pass its returned entry id) and never invent ids like "incidents-0". CONTRAST `analyseData` (one-shot metadata, no alerting). Use `clearTracker` to stop, `getTrackerHistory` to read what fired.',classificationPrompt:'Arm a TRACKER (a watch that ALERTS) on already-loaded entries: you write `code` returning `{ active, members, summary }`, and it recomputes whenever the watched entries change, logging an alert when the condition turns true and an event when it clears. Pick for "alert me when…", "let me know if…", "watch for…", "notify when an incident appears near…". Reads multiple entry kinds at once (e.g. incidents near hospitals/places). The watched entries must already be loaded — for a fresh area co-pick getTrafficIncidents (load the incidents) and locatePlace (resolve a named area) so the tracker has real entry ids to read. CONTRAST analyseData (one-shot, no alerts) and monitorAnalysis: keeping an EXISTING analysis fresh — "keep that count updated, tell me when it grows or fades" — is monitorAnalysis, NOT a tracker. createTracker defines a NEW condition in `code` from scratch.',inputSchema:Fa,outputSchema:qa,execute:async(e,t)=>{const r={placesEntryIDs:e.placesEntryIDs,routesEntryIDs:e.routesEntryIDs,incidentsEntryIDs:e.incidentsEntryIDs,geometriesEntryIDs:e.geometriesEntryIDs,trafficAreaAnalyticsEntryIDs:e.trafficAreaAnalyticsEntryIDs,byodEntryIDs:e.byodEntryIDs},n=await jn(r,t);if("error"in n)return{error:n.error};const{resolved:s,sandbox:o,geometries:a}=n.value,i=await Ze(e.code,$n,Un(o,{h3:ae,turf:se}),"Tracker rule",t.codeExecution);if("error"in i)return{error:i.error};if(!mt(i.value))return{error:"Tracker `code` must return { active: boolean, members: {entryId, featureIds}[], summary: string }."};const l=i.value,c=[...s.places.map(e=>e.id),...s.routes.map(e=>e.id),...s.incidents.map(e=>e.id),...s.trafficAreaAnalytics.map(e=>e.id),...s.byod.map(e=>e.id),...a.affectedEntries.map(e=>e.id)],d=$a(e.name,t),u=Date.now(),p={id:d,name:e.name,rule:e.rule,affectedEntryIds:c,enabled:!0,wasActive:!1};return t.trackers.register(p),((e,t)=>{const r=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:!0,run:async()=>{const r=await jn(t.inputs,e);if("error"in r)return;const n=Cn(r.value.resolved)??Date.now(),s=await ma(e,r.value,{code:t.code,previous:void 0,sampledAt:n,outputFormat:"json"});!("error"in s)&&mt(s.value)&&e.trackers.ingestVerdict(t.trackerId,s.value,n)},onOrphaned:()=>e.trackers.unregister(t.trackerId)});e.trackers.setJob(t.trackerId,r)})(t,{trackerId:d,code:e.code,inputs:r,affectedEntryIds:c}),t.trackers.ingestVerdict(d,l,u),{trackerId:d,name:e.name,rule:e.rule,watching:c,firingNow:l.active,summary:l.summary}},tags:["tracker","alert","monitor","turf"],examples:["createTracker({ incidentsEntryIDs: [\"incidents-0\"], name: \"Major incidents\", rule: \"any major incident in the area\", code: \"const hits = incidentsByEntry['incidents-0'].filter(i => i.properties.magnitudeOfDelay === 'major'); return { active: hits.length > 0, members: [{ entryId: 'incidents-0', featureIds: hits.map(i => i.properties.id) }], summary: hits.length ? `${hits.length} major incident(s)` : 'no major incidents' };\" })","createTracker({ placesEntryIDs: [\"hospitals\"], incidentsEntryIDs: [\"incidents-0\"], name: \"Incidents near hospitals\", rule: \"an incident within 100m of a hospital\", code: \"const near = []; const hosp = new Set(); for (const h of placesByEntry['hospitals'].features) for (const i of incidentsByEntry['incidents-0']) { const c = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; if (turf.distance(c, h.geometry.coordinates, { units: 'meters' }) <= 100) { near.push(i.properties.id); hosp.add(h.properties.id); } } return { active: near.length > 0, members: [{ entryId: 'hospitals', featureIds: [...hosp] }, { entryId: 'incidents-0', featureIds: near }], summary: near.length ? `${near.length} incident(s) near ${hosp.size} hospital(s)` : 'clear' };\" })"],examplePrompts:["Alert me when there is a major incident in this area","Let me know if an incident appears within 100m of a hospital","Watch these incidents and tell me when a jam forms near my route"],relatedTools:["getTrafficIncidents","analyseData","clearTracker","getTrackerHistory","getTrackers"],dependsOn:["getTrafficIncidents","locatePlace","analyseData"]},clearTracker:{description:"Stop a tracker and remove its rule. Does not touch the watched entries (they were never the tracker's). Omit `trackerId` when there is only one tracker.",classificationPrompt:'Stop a tracker armed by createTracker — "stop watching / stop alerting / clear that tracker". Omit trackerId when only one exists. Does not touch the watched entries.',inputSchema:Ia,outputSchema:Ea,execute:async(e,t)=>{const r=t.trackers.trackers;if(0===r.length)return{error:"No trackers to clear."};const n=e.trackerId??(1===r.length?r[0].id:void 0);if(void 0===n)return{error:`Which tracker? Pass trackerId — one of: ${r.map(e=>e.id).join(", ")}.`};const s=t.trackers.get(n);return s?(t.trackers.unregister(n),{cleared:!0,trackerId:n,name:s.name}):{error:`No tracker "${n}". Active: ${r.map(e=>e.id).join(", ")||"none"}.`}},tags:["tracker","alert"],examples:["clearTracker({})",'clearTracker({ trackerId: "incidents-near-hospitals" })'],examplePrompts:["Stop that tracker","Stop alerting me about incidents near hospitals"],relatedTools:["createTracker","getTrackers"]},getTrackerHistory:{description:"Read the tracker event log — `opened` alerts and `resolved` events, newest first. Optionally scope to one `trackerId` or a `sinceMs` cutoff. The log is bounded; `coverage` reports how far back it reaches.",classificationPrompt:'Read what trackers have FIRED — the alert/event log ("what alerts fired", "what happened with my trackers", "any incidents near hospitals today"). Scope to one trackerId or a sinceMs cutoff.',inputSchema:Ya,outputSchema:Va,execute:async(e,t)=>{const r=t.trackers.log({trackerId:e.trackerId}),n=[...t.trackers.log({trackerId:e.trackerId,sinceMs:e.sinceMs})].reverse().map(e=>({at:e.at,trackerId:e.trackerId,trackerName:e.trackerName,type:e.type,kind:e.kind,summary:e.summary,...void 0!==e.openedAt&&{openedAt:e.openedAt}}));return{events:n,count:n.length,...r.length>0&&{coverage:{oldestRetainedAt:r[0].at,newestAt:r[r.length-1].at}}}},tags:["tracker","alert","history"],examples:["getTrackerHistory({})",'getTrackerHistory({ trackerId: "incidents-near-hospitals" })'],examplePrompts:["What alerts have fired?","Show me the history for that tracker"],relatedTools:["createTracker","getTrackers"]},getTrackers:{description:"List active trackers: name, rule, enabled, whether firing now, and which entries each watches.",classificationPrompt:'List the active trackers and their live state — "what am I tracking / what alerts are set / is anything firing". Read-only.',inputSchema:Ja,outputSchema:Ka,execute:async(e,t)=>{const r=t.trackers.trackers.map(e=>({trackerId:e.id,name:e.name,rule:e.rule,enabled:e.enabled,firing:e.wasActive,...void 0!==e.openedAt&&{openedAt:e.openedAt},watching:[...e.affectedEntryIds]}));return{trackers:r,count:r.length}},tags:["tracker","alert"],examples:["getTrackers({})"],examplePrompts:["What am I tracking?","Which alerts are set up?"],relatedTools:["createTracker","getTrackerHistory"]},setTrafficIncidentsMonitor:{description:'Start or stop the background monitor for an EXISTING incidents entry (`enabled: true` / `false`). When enabled, re-fetches the entry\'s bbox + filters every interval (default 60s) and replaces its data; the first tick fires immediately, and any registered analyses re-run each tick. When disabled, polling stops and the entry stays in state with its last data (use removeEntry to drop it). \n\ngetTrafficIncidents already monitors a fresh shown fetch by default, so only enable here for an entry loaded on an EARLIER turn ("keep that area updated") or one loaded with monitor: false. Enabling is idempotent on a running monitor (no-op, alreadyInState: true); a new intervalMs is NOT applied to a running monitor — disable then enable to change it. Only a displayed entry can be monitored: a show:false load is a one-shot snapshot.',classificationPrompt:'Arm or pause background polling on an ALREADY-loaded incidents entry — one toggle (`enabled: true/false`). Enable for "keep updated"/"watch"/"live"/"refresh every N seconds" referring to incidents loaded on an EARLIER turn; disable for "stop refreshing"/"pause updates". A fresh getTrafficIncidents fetch already monitors by default, so do not co-pick this to start monitoring right after it.',inputSchema:gl,outputSchema:ml,execute:async(e,t)=>{const{incidentsEntryID:r,enabled:n,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.`};if(!n){const e=t.trafficIncidents.isMonitored(r);return t.trafficIncidents.stopMonitoring(r),{incidentsEntryID:r,enabled:!1,alreadyInState:!e}}if(!t.trafficIncidents.shownEntryIds.has(r))return{error:`Entry "${r}" isn't displayed on the map, so it can't be monitored. Monitoring keeps the live shown area current; a show:false load is a one-shot snapshot. Re-fetch the area with getTrafficIncidents (show: true, the default) and monitor that entry.`};const a=t.trafficIncidents.isMonitored(r),{bbox:i,categoryFilter:l,timeValidityFilter:c}=o.params,d={...l&&{categoryFilter:l},...c&&{timeValidityFilter:c}};return t.trafficIncidents.startMonitoring(r,{bbox:i,capturedAt:o.timestamp,label:o.label},{fetchIncidents:async e=>{const t=Te({...d,bbox:e});return(await L(t)).features}},s?{intervalMs:s}:void 0),{incidentsEntryID:r,enabled:!0,alreadyInState:a}},tags:["traffic","incident","state"],examples:["setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: true })","setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: true, intervalMs: 30000 })","setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: false })"],examplePrompts:["Keep the incidents I loaded updated","Refresh the incidents every 30 seconds","Stop refreshing the incidents"],relatedTools:["getTrafficIncidents","analyseData","focusIncidents"],dependsOn:["getTrafficIncidents"]},startRouteMonitor:{description:'Start a background monitor on an EXISTING route entry: recalculates the SAME route (same waypoints + parameters) every interval (default 60s) and replaces its data in place, so the route stays current with live traffic. The first recalculation fires immediately. \n\nPairs with a route calculated as `traffic: "historical"` + `maxAlternatives: 1-2`: historical input keeps the typical route PATHS stable across ticks (live input would keep re-routing to dodge current traffic), while each recalculation refreshes the live-traffic delays carried on those paths. \n\nIdempotent on a running monitor: a second call is a no-op returning alreadyRunning: true. A new intervalMs is NOT applied to a running monitor — stop first, then start again. Listen to the routing `entries-change` / `monitor-tick` events to react to each refresh.',classificationPrompt:'Arm background recalculation on an ALREADY-calculated route (default 60s): re-runs the SAME route each tick so its live-traffic delays stay current. Pick for "keep this route updated" / "watch the traffic on this drive" / "recalculate every N seconds". Best paired with a route calculated as traffic "historical" + maxAlternatives 1-2 (stable typical paths, fresh delays). Not needed just to calculate a route — that is setRoute.',inputSchema:bl,outputSchema:fl,execute:async(e,t)=>{const{routesEntryID:r,intervalMs:n}=e,s=t.routing.entries.find(e=>e.id===r);if(!s)return{error:`No routes entry with id "${r}". Calculate a route first via setRoute.`};const o=t.routing.isMonitored(r),{waypoints:a,params:i}=s;return t.routing.startMonitoring(r,{recalculate:yo(a,i)},n?{intervalMs:n}:void 0),{routesEntryID:r,alreadyRunning:o}},tags:["route","traffic","state"],examples:["startRouteMonitor({ routesEntryID: 'routes-0' })","startRouteMonitor({ routesEntryID: 'routes-0', intervalMs: 30000 })"],examplePrompts:["Keep this route updated with live traffic","Watch the traffic on the drive from Paris to Amsterdam","Recalculate this route every 30 seconds","Monitor traffic between these places"],relatedTools:["setRoute","stopRouteMonitor","updateRoutesDisplay","analyseData"],dependsOn:["setRoute"]},stopRouteMonitor:{description:"Stop the background monitor for a route entry. The entry itself stays in state with its last calculated data — use removeEntry if you also want to drop it. No-op when no monitor is running for the entry.",classificationPrompt:'Stop recalculating a route entry; the entry stays in state with its last data. Pick for "stop refreshing the route" / "pause traffic updates on this drive".',inputSchema:vl,outputSchema:wl,execute:async(e,t)=>{const{routesEntryID:r}=e;if(!t.routing.entries.find(e=>e.id===r))return{error:`No routes entry with id "${r}".`};const n=t.routing.isMonitored(r);return t.routing.stopMonitoring(r),{routesEntryID:r,wasRunning:n}},tags:["route","traffic","state"],examples:["stopRouteMonitor({ routesEntryID: 'routes-0' })"],examplePrompts:["Stop refreshing the route","Pause traffic updates on this drive"],relatedTools:["startRouteMonitor","setRoute"],dependsOn:["startRouteMonitor"]},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; use analyseData over the loaded entry for breakdowns.",classificationPrompt:"Fetch historical traffic analytics (speed, congestion, travel time) for an area. Use analyseData over the loaded entry for breakdowns (per-day / per-hour / worst-day) without re-fetching.",inputSchema:Uo,outputSchema:Go,execute:async(e,t,r)=>{const{location:n,bbox:s,showOnMap:o,startDate:a,endDate:i,days:l,metrics:c,functionalRoadClasses:d,hours:u}=e,p=process.env.MOVE_PORTAL_KEY;if(!p)return{error:"MOVE_PORTAL_KEY environment variable is not set."};try{const e=await(async(e,t,r,n)=>{if(e){const t=await oo(e,r,n);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=Te({geometries:[t.place],signal:n?.signal}),o=(await O(s))?.features?.[0]?.geometry;return o||{error:`No boundary polygon found for "${t.name}". Try a bbox instead.`}}return t?(e=>{const[t,r,n,s]=e;return{type:"Polygon",coordinates:[[[t,r],[n,r],[n,s],[t,s],[t,r]]]}})(t):{error:"Provide a location or bbox to define the analytics area."}})(n,s,t,r);if("error"in e)return e;const h=((e,t,r)=>e&&e.length>0?{days:e}:t?r?{startDate:t,endDate:r}:{startDate:t}:{})(l,a,i),y=Te({apiKey:p,...h,metrics:c,functionalRoadClasses:d??"all",hours:u??"all",geometry:e,signal:r?.signal}),m=await N(y),g=m.features[0]?.properties?.name,f=g?`${g} (${h.startDate??"…"} → ${h.endDate??"…"})`:`analytics (${h.startDate??"…"} → ${h.endDate??"…"})`,b=await t.trafficAreaAnalytics.addEntry(m,f,{...h,metrics:c,functionalRoadClasses:d??"all",hours:u??"all"});return o&&await(async(e,t)=>{await e.trafficAreaAnalytics.showEntry(t);const r=e.trafficAreaAnalytics.findById(t);if(!r)return;const n=await e.trafficAreaAnalytics.getEntryModule(t),s=r.data.properties?.metrics?.[0]??"congestionLevel";n.setMetric(s);const o=W(r.data.features);o&&e.baseMap.mapLibreMap.fitBounds(o,{padding:50,pitch:45})})(t,b),((e,t,r,n)=>{const s=t.features[0]?.properties;if(!s)return{entryId:e,dateRange:{start:"",end:""},baseData:{},metrics:[],tileCount:0,availableGranularities:[]};const o=r??zo(t.properties?.startDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-3),Wo(e)})(),a=n??zo(t.properties?.endDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-2),Wo(e)})(),i=[];return s.timedData?.yearly?.length&&i.push("yearly"),s.timedData?.monthly?.length&&i.push("monthly"),s.timedData?.weekly?.length&&i.push("weekly"),s.timedData?.daily?.length&&i.push("daily"),s.timedData?.hourly?.length&&i.push("hourly"),s.timedData?.average?.length&&i.push("average"),{entryId:e,...s.name&&{name:s.name},...s.timezone&&{timezone:s.timezone},dateRange:{start:o,end:a},baseData:s.baseData,metrics:t.properties?.metrics??[],tileCount:s.tiledData?.tiles?.length??0,availableGranularities:i}})(b,m,h.startDate,h.endDate)}catch(h){return{error:`Failed to get traffic area analytics: ${h instanceof Error?h.message:String(h)}`}}},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:["analyseData","updateTrafficAreaAnalyticsDisplay","calculateBBox","getViewport"],dependsOn:["locatePlace"]},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 already in state on the map — add some (as pins, base-map-style markers via `markerType`, and/or boundary polygons), remove others, swap atomically, or render boundaries. Owns rendering a stored entry with base-map-style markers; NOT the base-map tile POI icons (those are toggleTilesPOIs).",inputSchema:tc,outputSchema:ec,execute:async(e,t)=>{const{clear:r,markerType:n,geometry:s,fitBounds:o}=e;try{const a=((e,t)=>{const{add:r,remove:n,addMatching:s,removeMatching:o,markerType:a,geometry:i}=t;let l=[...r??[],...rc(e,s??[])];const c=[...n??[],...rc(e,o??[])];if(void 0===r&&void 0===s&&(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 recallState 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!==n||!s;let u;i.length>0&&d&&await(async(e,t,r,n)=>{const s=void 0!==r?t:t.filter(e=>!n.has(e));s.length>0&&await e.places.addShownEntries(s,r??"pin")})(t,i,n,c),s&&i.length>0&&(u=await(async(e,t,r)=>{const n=[];let s=0;for(const o of t){const t=await Zl(e,o,r);s+=t.rendered,n.push(...t.skipped)}return{rendered:s,skipped:n}})(t,i,s));const p=t.places.shownEntryIds,h=i.filter(e=>!c.has(e)&&p.has(e)),y=l.filter(e=>c.has(e)&&!p.has(e)),m=o??i.length>0,g=t.places.getShownFeatures();if(m&&g.length>0){const e=W(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.data.length,markerType:t.places.getShownMarkerType(e.id)??"pin"}));return{success:!0,totalShown:g.length,shown:f,actuallyAdded:h,actuallyRemoved:y,...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","Render entry places-3 with the base-map marker style","Show the boundaries of the neighbourhoods I loaded","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","recallState","clearMap","flyTo"],dependsOn:["discoverPlaces","locatePlace","processData"]},updateRoutesDisplay:{description:'Show, hide, swap, and recolor which routes entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show this"). `selectedIndex` re-highlights an alternative variant on the newly-added entries; with no `add`/`addMatching` it implicitly targets the most recently stored entry. `mainColor` recolors the route line + waypoint icons (sticky across entry swaps — applied to current shows AND remembered); it does NOT auto-show a hidden entry, so set `add`/`addMatching` too if nothing is on the map yet. Does not delete from history. Does not call any service.',classificationPrompt:"Show, hide, swap, and recolor routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, or set `mainColor` (CSS color / hex; sticky across entry swaps).",inputSchema:oc,outputSchema:sc,execute:async(e,t)=>{const{clear:r,selectedIndex:n,fitBounds:s,mainColor:o}=e;try{const a=((e,t)=>{const{add:r,remove:n,addMatching:s,removeMatching:o,selectedIndex:a}=t;let i=[...r??[],...ac(e,s??[])];const l=[...n??[],...ac(e,o??[])];if(void 0===r&&void 0===s&&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 recallState to list available entries.`}:{add:i,remove:l}})(t,e);if("error"in a)return a;const{add:i,remove:l}=a;void 0!==o&&t.routing.setMainColor(o);const{beforeShown:c,afterShown:d}=await(async(e,t)=>{const r=new Set(e.routing.shownEntryIds);if(t.clear)for(const n of e.routing.shownEntryIds)await e.routing.hideEntry(n);for(const n of t.remove)await e.routing.hideEntry(n);for(const n of t.add)await e.routing.showEntry(n);return void 0!==t.selectedIndex&&await(async(e,t,r)=>{if(0!==r)for(const n of t){const t=e.routing.entries.find(e=>e.id===n);if(!t)continue;const s=await e.routing.getEntryRoutingModule(n);await s.showRoutes(t.data,{selectedIndex:r}),t.waypoints.length&&await s.showWaypoints(t.waypoints)}})(e,t.add,t.selectedIndex),{beforeShown:r,afterShown:e.routing.shownEntryIds}})(t,{add:i,remove:l,clear:!!r,selectedIndex:n}),u=i.filter(e=>!c.has(e)&&d.has(e)),p=l.filter(e=>c.has(e)&&!d.has(e));(s??i.length>0)&&((e,t)=>{const r=[];for(const s of e.routing.entries)t.has(s.id)&&r.push(...s.data.features);if(0===r.length)return;const n=W({type:"FeatureCollection",features:r});n&&e.baseMap.mapLibreMap.fitBounds(n,{padding:50})})(t,d);const h=t.routing.entries.filter(e=>d.has(e.id)).map(e=>({id:e.id,label:e.label,routeCount:e.data.features.length,selectedIndex:u.includes(e.id)?n??0:0})),y=t.routing.mainColor;return{success:!0,totalShown:d.size,shown:h,actuallyAdded:u,actuallyRemoved:p,...void 0!==y&&{mainColor:y}}}catch(a){return{error:`updateRoutesDisplay failed: ${a instanceof Error?a.message:String(a)}`}}},tags:["route","waypoint","map style"],examples:['updateRoutesDisplay({ add: ["routes-1"] })','updateRoutesDisplay({ add: ["routes-0", "routes-2"] })','updateRoutesDisplay({ remove: ["routes-1"] })','updateRoutesDisplay({ removeMatching: ["Brussels"] }) // hide every entry whose label mentions "Brussels"','updateRoutesDisplay({ addMatching: ["Edinburgh"] })',"updateRoutesDisplay({ clear: true })",'updateRoutesDisplay({ clear: true, add: ["routes-2"] }) // atomic swap: hide everything, show this',"updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route",'updateRoutesDisplay({ add: ["routes-0"], selectedIndex: 2, fitBounds: false })','updateRoutesDisplay({ mainColor: "coral" }) // recolor every currently-shown route (and future shows)','updateRoutesDisplay({ add: ["routes-1"], mainColor: "#FF0000" }) // show in red'],examplePrompts:["Draw the route on the map","Show the directions","Display the calculated route","Show the Edinburgh route from earlier","Also show the Brussels route","Hide the Lyon route","Switch to the second alternative","Highlight alternative 2","Replace what is shown with this route","Make the route line red","Change the route color to coral","Style the route in steelblue"],relatedTools:["setRoute","processData","recallState","clearMap"],dependsOn:["setRoute"]},updateTrafficAreaAnalyticsDisplay:{description:"Show, hide, swap, and restyle HISTORICAL traffic-area-analytics entries on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap. Render knobs (`mode`, `metric`, `colorTheme`, `heightScale`, `scaleMode`, `filter`) apply to every entry being added AND every entry that stays shown after this call. When only render knobs are set with no add/remove/clear, implicitly targets the most recently added entry. Not real-time — use `toggleTilesTrafficFlow` for the live vector-tile flow overlay.",classificationPrompt:"Show / hide / swap / restyle historical traffic-area-analytics entries on the map. `add` / `remove` / `addMatching` / `removeMatching` / `clear` for visibility; `mode` / `metric` / `colorTheme` / `heightScale` / `scaleMode` / `filter` for the rendering. Render knobs alone implicitly target the most recent entry.",inputSchema:cc,outputSchema:dc,execute:async(e,t)=>{try{const r=((e,t)=>{const{add:r,remove:n,addMatching:s,removeMatching:o,clear:a}=t;let i=[...r??[],...uc(e,s??[])];const l=[...n??[],...uc(e,o??[])],c=void 0===r&&void 0===s&&void 0===n&&void 0===o&&!0!==a,d=void 0!==t.mode||void 0!==t.metric||void 0!==t.colorTheme||void 0!==t.heightScale||void 0!==t.scaleMode||void 0!==t.filter;if(c&&d){const t=e.trafficAreaAnalytics.entries.at(-1)?.id;if(!t)return{error:"No traffic-area-analytics entries available. Call getTrafficAreaAnalytics first."};i=[t]}const u=new Set(e.trafficAreaAnalytics.entries.map(e=>e.id)),p=i.filter(e=>!u.has(e));return p.length>0?{error:`Unknown traffic-area-analytics entry ids: ${p.join(", ")}. Use recallState to list available ids.`}:{add:i,remove:l}})(t,e);if("error"in r)return r;const{add:n,remove:s}=r,o=new Set(t.trafficAreaAnalytics.shownEntryIds);if(e.clear)for(const e of t.trafficAreaAnalytics.shownEntryIds)await t.trafficAreaAnalytics.hideEntry(e);for(const e of s)await t.trafficAreaAnalytics.hideEntry(e);for(const e of n)await t.trafficAreaAnalytics.showEntry(e);const a=t.trafficAreaAnalytics.shownEntryIds,i=n.filter(e=>!o.has(e)&&a.has(e)),l=s.filter(e=>o.has(e)&&!a.has(e)),c=/* @__PURE__ */new Set([...i,...a]);for(const u of c)await pc(t,u,e);if((e.fitBounds??i.length>0)&&a.size>0){const e=t.trafficAreaAnalytics.entries.filter(e=>a.has(e.id)).flatMap(e=>e.data.features),r=W({type:"FeatureCollection",features:e});r&&t.baseMap.mapLibreMap.fitBounds(r,{padding:50,pitch:45})}const d=t.trafficAreaAnalytics.entries.filter(e=>a.has(e.id)).map(hc);return{success:!0,totalShown:a.size,shown:d,actuallyAdded:i,actuallyRemoved:l}}catch(r){return{error:`Failed to update traffic-area-analytics display: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","map style"],examples:['updateTrafficAreaAnalyticsDisplay({ add: ["tta-0"] })',"updateTrafficAreaAnalyticsDisplay({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' }) // re-style the latest (implicit)","updateTrafficAreaAnalyticsDisplay({ filter: { min: 50 } })","updateTrafficAreaAnalyticsDisplay({ scaleMode: 'currentRange', heightScale: 200 })","updateTrafficAreaAnalyticsDisplay({ clear: true })",'updateTrafficAreaAnalyticsDisplay({ clear: true, add: ["tta-1"] }) // atomic swap','updateTrafficAreaAnalyticsDisplay({ remove: ["tta-0"] })','updateTrafficAreaAnalyticsDisplay({ addMatching: ["Amsterdam"] })'],examplePrompts:["Show the analytics on the map","Visualize the congestion","Only show areas with congestion above 50%","Normalize height to the current data range","Switch to heatmap view","Also show the Amsterdam analytics","Hide the Amsterdam analytics","Replace what is on the map with this analytics"],relatedTools:["getTrafficAreaAnalytics","toggleTilesTrafficFlow","clearMap","analyseData","processData"],dependsOn:["getTrafficAreaAnalytics"]},updateWaypointsDisplay:{description:"Show or hide the staged origin/stop/destination markers held in `state.routing.planningSlots` (the slots being built up for the next `setRoute` — NOT the waypoints of a calculated route entry). No route line is drawn; for that use `updateRoutesDisplay`. Requires at least one routes entry to host the module.",classificationPrompt:"Show or hide the STAGED origin/stop/destination markers (`state.routing.planningSlots` — what is queued for the NEXT `setRoute`). No route line is drawn; for that use `updateRoutesDisplay`. Single-collection toggle: `visible: true` (default) renders the staged set, `visible: false` clears it.",inputSchema:mc,outputSchema:yc,execute:async(e,t)=>{const r=e.visible??!0;try{const e=[...t.routing.shownEntryIds][0],n=t.routing.entries.at(-1),s=e??n?.id;if(!s)return{error:"No route history yet — calculate a route via setRoute first."};const o=await t.routing.getEntryRoutingModule(s);if(!r)return await o.clearWaypoints(),{success:!0,visible:!1,count:0};const a=t.routing.planningSlots;return await o.showWaypoints(a),{success:!0,visible:!0,count:a.length}}catch(n){return{error:`Failed to update waypoints display: ${n instanceof Error?n.message:String(n)}`}}},tags:["waypoint","route"],examples:["updateWaypointsDisplay()","updateWaypointsDisplay({ visible: true })","updateWaypointsDisplay({ visible: false })"],examplePrompts:["Show the staged waypoints on the map","Mark my pending route stops","Hide the staged waypoints"],relatedTools:["setRoute","updateRoutesDisplay","getCurrentWaypoints","clearMap"],dependsOn:["setRoute","locatePlace"]},clearMap:{description:"Hide everything a chosen set of state slices is rendering on the map (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents). Map-only — slice `entries` history is untouched. Use to clean up before showing new results.",classificationPrompt:"Hide what the chosen state slices are rendering on the map. `layers` are SLICE KEYS: `places`, `routing`, `ranges`, `customGeometries`, `byod`, `trafficAreaAnalytics`, `trafficIncidents`. Omit to clear every slice. Map-only — slice history is kept.",inputSchema:Sl,outputSchema:El,execute:async(e,t)=>{const{layers:r}=e,n=r&&r.length>0?r:Dt;try{for(const e of n)await t[e].clearShown();return{success:!0,cleared:[...n]}}catch(s){return{error:`Failed to clear map: ${s instanceof Error?s.message:String(s)}`}}},tags:["place","route","map style"],examples:["clearMap()",'clearMap({ layers: ["places"] })','clearMap({ layers: ["places", "routing"] })','clearMap({ layers: ["trafficAreaAnalytics", "trafficIncidents"] })','clearMap({ layers: ["byod"] })'],examplePrompts:["Clear the map","Remove all markers","Hide everything on the map"],relatedTools:["updatePlacesDisplay","updateRoutesDisplay","updateTrafficAreaAnalyticsDisplay"]},flyTo:{description:"Move the map camera to a bounding box or a specific position. Use with boundingBox to frame a region or set of results. Use with position to center on a point.",classificationPrompt:"Move the camera to a bounding box or specific position; use after locating places or computing bounds.",inputSchema:Ws,outputSchema:Us,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(n){return{error:`flyTo failed: ${n instanceof Error?n.message:String(n)}`}}},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:["Fly to [4.9, 52.37]","Go to Berlin","Center the map on Rome"],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:Vs,outputSchema:Hs,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"]},addByodSource:{description:'Register a customer-owned GeoJSON FeatureCollection as a new BYOD source from a URL (fetched once) or inline GeoJSON. Returns the new entry id (usable with `byodEntryIDs` on `analyseData` / `processData` and with `updateByodDisplay`) plus a `profile` of the data shape (geometry types + per-property types/coverage/examples) so you can reason about it without re-fetching. For very large datasets, prefer programmatic seeding via `state.byod.addEntry(...)`. This tool ONLY ingests — the entry starts with NO layers and renders nothing (no automatic defaults). ALWAYS follow it with `setByodLayers`, which picks layers + data-driven paint from the returned `profile` AND draws the entry; until then nothing is shown. Choosing the layers is always your job, in every case. The entry does not exist until this call RETURNS — even when you chose `entryId` yourself. So every tool that reads the entry (`setByodLayers`, `analyseData` / `processData`, `updateByodDisplay`, …) belongs in a LATER step: issuing one alongside this call fails with "No BYOD entry with id …".',classificationPrompt:"Add a customer-owned GeoJSON FeatureCollection (URL fetch or inline) as a new BYOD source. Use when the user wants to import / load / overlay a customer-supplied dataset. Returns the new entry id (use with `byodEntryIDs` on analyseData / processData) plus a data `profile`. The entry starts with NO layers and renders nothing, so co-pick setByodLayers on every ingest turn to decide the layers from that profile.",inputSchema:da,outputSchema:ca,execute:async(e,t)=>{let r,n;if(e.url){const s=(e=>{let t;try{t=new URL(e)}catch{return{error:`Invalid URL: "${e}".`}}return pa.has(t.protocol)?{url:t}:{error:`URL protocol "${t.protocol}" is not allowed. Only http: and https: are supported.`}})(e.url);if("error"in s)return s;const o=await(t.byod.sourceUrlValidator?.(s.url));if(o&&!o.valid)return{error:o.reason};const a=await ha(s.url);if("error"in a)return a;r=a.value,n={kind:"url",url:e.url}}else r=e.data,n={kind:"inline"};if(!(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)})(r))return{error:'Payload is not a GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`).'};const s=await t.byod.addEntry(r,e.label,{source:n,...e.entryId&&{explicitId:e.entryId}}),o=t.byod.findById(s);return o?{byodEntryId:s,label:e.label,source:n,profile:pt(o.profile)}:{error:`addByodSource stored entry "${s}" but could not read it back.`}},tags:["byod","state","import"],examples:['addByodSource({ label: "Sales territories", url: "https://example.com/territories.geojson" }) // then setByodLayers to render','addByodSource({ label: "Inline pins", data: { type: "FeatureCollection", features: [/* … */] } })','addByodSource({ label: "Q2 pins", data: { type: "FeatureCollection", features: [/* … */] } })'],examplePrompts:["Load my territories layer from https://example.com/territories.geojson","Import the GeoJSON at https://example.com/data.geojson as a BYOD layer","Add a customer-pins layer from https://example.com/pins.geojson"],relatedTools:["recallState","setByodLayers","updateByodDisplay","analyseData","processData"]},setByodLayers:{description:"Replace the MapLibre layers a BYOD entry renders under, styling customer data from its `profile` (geometry types + per-property types/coverage/examples). Operates on an entry already in state (`addByodSource` / `recallState`); the supplied `layers` replace the current set wholesale, applying live in place if the entry is shown. Pass `show` to render a hidden entry or re-fit the camera. Invalid specs leave the entry unchanged and return an error to retry, so the entry's styling is never lost. This is the SECOND STEP OF EVERY BYOD INGEST and the ONLY way a BYOD entry becomes visible — `addByodSource` creates the entry with NO layers — so always call it to pick layers that fit the detected schema: graduate `circle-radius`/colour by a numeric field (`interpolate`), colour a `fill`/`line` by a categorical field (`match`), or switch dense points to `symbol`; even with no dominant property, choose legible layers for the geometry. STYLE FOR LEGIBILITY ON THE TOMTOM BASEMAP (no need to inspect the style): translucent `fill` (`fill-opacity` ~0.3–0.5) with a contrasting `fill-outline-color`; `symbol` text with a white halo (`text-halo-color: '#ffffff'` + `text-halo-width` ~1.5 in `paint`, while `text-field`/`text-size` ~12 go in `layout`); `circle` with a thin white stroke (`circle-stroke-color`/`-width`); a small, high-contrast palette that avoids fully opaque black or white fills.",classificationPrompt:"Restyle a BYOD entry by replacing the MapLibre layers it renders under — pick layer types and data-driven paint from the data `profile` (graduate point size by a numeric field, colour a fill by category, switch points to symbols). OWNS every restyle of a loaded / imported / customer (BYOD) layer — recolour it, thicken its outlines, resize its points — even when phrased as generic styling; do NOT route those to setPaintProperties/setLayoutProperties (which are for base-map style layers). ALWAYS co-pick alongside addByodSource on EVERY BYOD ingest turn (whether or not styling was mentioned) — addByodSource creates the entry with no layers, so this is the only way it becomes visible. NOT for visibility (show/hide/remove → updateByodDisplay) nor for importing new data (→ addByodSource).",inputSchema:ll,outputSchema:cl,execute:async(e,t)=>{const r=t.byod.findById(e.byodEntryId);if(!r)return{error:`No BYOD entry with id "${e.byodEntryId}". Call recallState to list available IDs.`};const n=r.layers,s=e.layers;try{t.byod.setEntryLayers(e.byodEntryId,s)}catch(a){try{t.byod.setEntryLayers(e.byodEntryId,n)}catch{r.layers=n}return{error:`Failed to apply layers to "${e.byodEntryId}": ${a instanceof Error?a.message:String(a)}. The entry keeps its previous layers — fix the layer specs and retry.`}}let o;if(e.show)try{o=await Ts(t,e.byodEntryId,e.show)}catch(a){return{error:`Layers applied to "${e.byodEntryId}" but rendering failed: ${a instanceof Error?a.message:String(a)}`}}return{byodEntryId:e.byodEntryId,label:r.label,layerCount:s.length,layerTypes:s.map(e=>e.type),...o&&{shown:o}}},tags:["byod","map style","display"],examples:['setByodLayers({ byodEntryId: "byod-0", layers: [{ type: "fill", paint: { "fill-color": ["match", ["get", "region"], "north", "#1f77b4", "south", "#ff7f0e", "#cccccc"], "fill-opacity": 0.5 } }] }) // colour polygons by category','setByodLayers({ byodEntryId: "byod-1", layers: [{ type: "circle", paint: { "circle-radius": ["interpolate", ["linear"], ["get", "revenue"], 0, 3, 100000, 20], "circle-color": "#d62728" } }] }) // size points by a numeric field','setByodLayers({ byodEntryId: "byod-2", layers: [{ type: "line", paint: { "line-color": "#2ca02c", "line-width": 3 } }], show: { zoomMode: "none" } }) // restyle a hidden entry and render it'],examplePrompts:["Colour my loaded territories layer by region","Size my BYOD layer points by revenue","Style my loaded BYOD layer with thicker green outlines","Shade my BYOD polygons by their value"],relatedTools:["recallState","addByodSource","updateByodDisplay"],dependsOn:["addByodSource"]},updateByodDisplay:{description:'Toggle visibility (or drop) for one or more BYOD entries. Use after `addByodSource` or after seeing entries via `recallState`. Three actions: `show` renders listed entries, `hide` removes them from the map but keeps them in state, `remove` hides AND drops them from history. Pair `action: "hide"` with `clearAll: true` to hide every shown BYOD entry in one call. Pair `action: "show"` with `hideOthers: true` to swap to a single set of entries (replaces what\'s already on the map).',classificationPrompt:'Toggle visibility (or drop) of BYOD entries already in session state. Use after `addByodSource` or after `recallState({ kind: "byod" })` exposes the ids. Three actions: show / hide / remove. Pair with `clearAll` or `hideOthers` for bulk swaps.',inputSchema:Jl,outputSchema:Kl,execute:async(e,t)=>{const r=t.byod,{action:n,clearAll:s,hideOthers:o}=e,a=e.entryIds??[],i=((e,t)=>{for(const r of t)if(!e.findById(r))return{error:`No BYOD entry with id "${r}". Call recallState to list available IDs.`}})(r,a);if(i)return i;let l;return l="show"===n?await(async(e,t,r)=>{const n=[];if(r){const r=new Set(t);for(const t of e.shownEntryIds)r.has(t)||(await e.hideEntry(t),n.push(t))}for(const s of t)await e.showEntry(s),n.push(s);return n})(r,a,!!o):"hide"===n?await(async(e,t,r)=>{const n=[],s=r?e.shownEntryIds:t;for(const o of s)await e.hideEntry(o),n.push(o);return n})(r,a,!!s):await(async(e,t)=>{const r=[];for(const n of t)await e.removeEntry(n),r.push(n);return r})(r,a),{action:n,affectedIds:l,shown:[...r.shownEntryIds]}},tags:["byod","display","visibility"],examples:['updateByodDisplay({ entryIds: ["byod-0"], action: "show" })','updateByodDisplay({ entryIds: ["byod-0", "byod-1"], action: "show", hideOthers: true })','updateByodDisplay({ entryIds: ["byod-0"], action: "hide" })','updateByodDisplay({ action: "hide", clearAll: true })','updateByodDisplay({ entryIds: ["byod-0"], action: "remove" })'],examplePrompts:["Hide the customer pins layer","Show only the territories BYOD layer","Hide all BYOD layers","Drop the imported file from the session"],relatedTools:["recallState","addByodSource"],dependsOn:["addByodSource"]},getCurrentWaypoints:{description:"Read STAGED waypoint slots queued for the NEXT setRoute call (origin / stops / destination not yet committed to a route). Distinct from `recallState({ kind: 'routes', id })`, which returns the waypoints of an existing calculated route entry. `slotIndex` for one slot; otherwise paged (default 5), empty slots omitted unless `includeEmptySlots: true`. No service call.",classificationPrompt:'STAGED waypoint slots only — the origin/stops/destination being built up for the NEXT `setRoute` call (read off `state.routing.planningSlots`, NOT off any calculated route entry). For waypoints of an EXISTING calculated route, use `recallState({ kind: "routes", id })` — its detail mode returns the entry\'s own waypoints.',inputSchema:Wa,outputSchema:za,execute:async(e,t)=>{const{slotIndex:r,includeEmptySlots:n=!1,offset:s=0,limit:o=5}=e,a=t.routing.planningSlots.map((e,t)=>{const r=ys(e);return r?{...r,slotIndex:t}:{slotIndex:t,isFilled:!1}}),i=n?a:a.filter(e=>e.isFilled),l=void 0!==r,c=l?i.filter(e=>e.slotIndex===r):i.slice(s,s+o),d=s+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:s,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's currently staged for the next route?","What's my pending destination before I hit go?","List the stops I have queued up"],relatedTools:["locatePlace","setRoute","updateWaypointsDisplay","recallState"]},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:_l,outputSchema:Pl,execute:async(e,t)=>{try{return{styles:v}}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:"Switch the map to a standard base-map style preset (IDs are the `style` argument's values). ONLY when the user explicitly asks to change the map style/theme — never on your own initiative. For finer styling use getMapStyleLayers, setLayoutProperties, setPaintProperties.",classificationPrompt:"Switch the map to a standard base-map style/theme preset (light, dark, mono, satellite, etc.) — ONLY when the user explicitly asks to change it, never as a side effect of another task.",inputSchema:jl,outputSchema:Rl,execute:async(e,t)=>{const{style:r}=e;try{return await t.baseMap.ttMap.setStyle(r),{success:!0,style:r}}catch(n){return{error:`Failed to set map style: ${n instanceof Error?n.message:String(n)}`}}},tags:["map style"],examples:['setMapStandardStyle("standardDark")','setMapStandardStyle("satellite")'],examplePrompts:["Switch to dark mode","Use the satellite view","Change to the light theme","Use the driving map style"],relatedTools:["getStandardMapStyles","setLanguage","toggleTilesBaseMapLayerGroups","getMapStyleLayers"]},setLanguage:{description:"Set the language for map labels and all subsequent service API responses (search, geocoding, routing instructions).",classificationPrompt:"Change the language for map labels and all subsequent service API responses.",inputSchema:Cl,outputSchema:Ol,execute:async(e,t)=>{const{language:r}=e;try{return q.instance.put({language:r}),t.baseMap.ttMap.setLanguage(r),{success:!0,language:r}}catch(n){return{error:`Failed to set language: ${n instanceof Error?n.message:String(n)}`}}},tags:["map style"],examples:['setLanguage("fr-FR")','setLanguage("de-DE")'],examplePrompts:["Show map labels in French","Switch the map to German","Use Spanish for place names"],relatedTools:["setMapStandardStyle"]},toggleTilesBaseMapLayerGroups:{description:"Show / hide groups of vector-tile base-map style layers (water, buildings3D, roads, allPlaceLabels, capitalLabels, …). Operates on the underlying MapLibre style — orthogonal to anything rendered through PlacesModule / RoutingModule / CustomGeoJSONModule (those layers are unaffected).",classificationPrompt:"Show / hide vector-tile base-map layer groups (buildings3D, roadLabels, water, allPlaceLabels, …). Style-layer only — does NOT touch places / routes / custom-geometries rendered by the agent.",inputSchema:$l,outputSchema:ql,execute:async(e,t)=>{const{visible:r,layerGroups:n}=e;try{return(await t.baseMap.getBaseMapModule()).setVisible(r,{layerGroups:{mode:"include",names:n}}),{success:!0,visible:r,layerGroups:n}}catch(s){return{error:`Failed to toggle tile base-map layer groups: ${s instanceof Error?s.message:String(s)}`}}},tags:["map style"],examples:['toggleTilesBaseMapLayerGroups({ visible: false, layerGroups: ["buildings3D"] })','toggleTilesBaseMapLayerGroups({ visible: true, layerGroups: ["roadLabels"] })'],examplePrompts:["Hide 3D buildings","Show road labels","Toggle the building layer"],relatedTools:["toggleTilesPOIs","getMapStyleLayers","setMapStandardStyle"]},toggleTilesPOIs:{description:"Show/hide the built-in vector-tile POI icons baked into the base map style (restaurants, hotels, gas stations, …). NOT the same as places shown via `discoverPlaces` / `updatePlacesDisplay` — those are session-state pins on the PlacesModule, untouched by this tool. `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts which categories show; `reset: true` restores defaults.",classificationPrompt:"Show / hide vector-tile POI icons baked into the base-map style (restaurants/hotels/gas stations etc. shipped with the map). NOT places shown via `discoverPlaces` / `updatePlacesDisplay` (those are PlacesModule pins, untouched).",inputSchema:Ul,outputSchema:Gl,execute:async(e,t)=>{try{const r=await t.mapPOIs.getPOIsModule();if(e.reset)return r.resetConfig(),{success:!0,reset:!0};const{allMapPOIsVisible:n,filterCategories:s}=e;return void 0!==n&&r.setVisible(n),s&&r.filterCategories({show:s.show,values:s.values}),{success:!0,allMapPOIsVisible:n,filterCategories:s}}catch(r){return{error:`Failed to toggle tile POIs: ${r instanceof Error?r.message:String(r)}`}}},tags:["place","map style"],examples:["toggleTilesPOIs({ allMapPOIsVisible: false })",'toggleTilesPOIs({ allMapPOIsVisible: true, filterCategories: { show: "only", values: ["RESTAURANT", "CAFE"] } })','toggleTilesPOIs({ filterCategories: { show: "all_except", values: ["PARKING_GARAGE"] } })',"toggleTilesPOIs({ reset: true })"],examplePrompts:["Hide all map POI icons","Show only restaurant icons on the map style","Turn off parking icons in the base map"],relatedTools:["toggleTilesBaseMapLayerGroups","discoverPlaces","setMapStandardStyle"]},describeMapStyling:{description:"List the semantic base-map styling knobs of the loaded style (label/icon/road sizes, feature toggles such as exit numbers or 3D buildings, POI zoom and colours, traffic congestion colours, globe projection, sky and 3D terrain) with kind, range, default and current value, plus the available presets. Call before setMapStyling to know valid ids and ranges.",classificationPrompt:"List what can be restyled on the base map (sizes, feature toggles, POI and traffic colours) with ranges, defaults and current values. Read-only.",inputSchema:Tl,outputSchema:Dl,execute:async(e,t)=>{try{const{knobs:r,presets:n}=(await t.baseMap.getStylingModule()).describe();return{knobs:e.kind?r.filter(t=>t.kind===e.kind):r,presets:n}}catch(r){return{error:`Failed to describe map styling: ${r instanceof Error?r.message:String(r)}`}}},tags:["map style"],examples:["describeMapStyling()",'describeMapStyling({ kind: "toggle" })'],examplePrompts:["What can I change about the map styling?","Which map styling knobs are currently overridden?"],relatedTools:["setMapStyling","setMapStandardStyle"]},setMapStyling:{description:"Restyle the base map semantically: label/icon/road size factors, feature toggles (exit numbers, shields, road arrows, 3D buildings, POI micro markers…), POI zoom and label colours, traffic congestion and incident colours, globe projection, sky and 3D terrain, or a whole preset (data-viz, night-driving, minimal, globe, terrain). Durable across style switches. Not for layers the agent drew (places, routes, BYOD) — use their update*Display tools.",classificationPrompt:"Adjust the base-map look semantically — bigger/smaller labels or icons, thicker roads, hide/show exit numbers, route shields, road arrows, 3D buildings or POI micro markers, POI label colour or zoom, traffic congestion/incident colours — or reset those to the style defaults. NOT for switching the whole style (setMapStandardStyle), NOT for layers the agent drew.",inputSchema:Fl,outputSchema:Ll,execute:async(e,t)=>{try{const r=await t.baseMap.getStylingModule(),n=[],s=(e,t)=>{try{t()}catch(r){n.push({id:e,reason:r instanceof Error?r.message:String(r)})}};if(!0===e.reset)r.reset();else if(Array.isArray(e.reset))for(const t of e.reset)s(t,()=>r.reset(t));e.preset&&r.applyPreset(e.preset);for(const[t,o]of Object.entries(e.set??{}))s(t,()=>r.set(t,o));return{success:!0,settings:r.getConfig()??{},rejected:n}}catch(r){return{error:`Failed to set map styling: ${r instanceof Error?r.message:String(r)}`}}},tags:["map style"],examples:['setMapStyling({ set: { "labels.sizeFactor": 1.3 } })','setMapStyling({ set: { "roads.exitNumbers": false, "roads.shields": false } })','setMapStyling({ set: { "buildings.3d": true } })','setMapStyling({ set: { "traffic.flow.slowColor": "#f59e0b", "traffic.flow.stationaryColor": "#7f1d1d" } })','setMapStyling({ set: { "view.projection": "globe", "view.sky": true } })','setMapStyling({ preset: "data-viz" })',"setMapStyling({ reset: true })"],examplePrompts:["Make the map labels bigger","Hide the exit numbers and route shields","Show 3D buildings","Use orange for slow traffic and dark red for stationary traffic","Show the map as a globe","Turn on 3D terrain","Make the base map quieter so my data stands out","Reset the map styling"],relatedTools:["describeMapStyling","setMapStandardStyle","toggleTilesBaseMapLayerGroups","toggleTilesPOIs"]},toggleTilesTrafficFlow:{description:"Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical analytics) or `route.properties.sections.traffic[]` (per-route incidents). Just a base-map style layer on/off.",classificationPrompt:"Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical) or route-section traffic (read via `analyseData`).",inputSchema:Wl,outputSchema:zl,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficFlowModule()).setVisible(r),{success:!0,trafficFlowVisible:r}}catch(n){return{error:`Failed to toggle tile traffic flow: ${n instanceof Error?n.message:String(n)}`}}},tags:["traffic","map style"],examples:["toggleTilesTrafficFlow({ visible: true })","toggleTilesTrafficFlow({ visible: false })"],examplePrompts:["Show traffic flow","Turn off traffic colors","Enable the congestion overlay"],relatedTools:["toggleTilesTrafficIncidents","setRoute","getTrafficIncidents"]},toggleTilesTrafficIncidents:{description:"Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the same as `getTrafficIncidents` (GeoJSON service → structured entries) or `analyseData` (sandbox over fetched entries). Just a base-map style layer on/off.",classificationPrompt:"Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the GeoJSON `getTrafficIncidents` service and NOT `analyseData` (which aggregates fetched entries).",inputSchema:Yl,outputSchema:Hl,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficIncidentsModule()).setVisible(r),{success:!0,trafficIncidentsVisible:r}}catch(n){return{error:`Failed to toggle tile traffic incidents: ${n instanceof Error?n.message:String(n)}`}}},tags:["traffic","map style"],examples:["toggleTilesTrafficIncidents({ visible: true })","toggleTilesTrafficIncidents({ visible: false })"],examplePrompts:["Show traffic incidents overlay","Hide accident icons","Enable incident markers on the map"],relatedTools:["toggleTilesTrafficFlow","getTrafficIncidents","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:Ys,outputSchema:zs,execute:async(e,t)=>{const{layerIdQuery:r,include:n}=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,...n.includes("paint")&&{paint:e.paint},...n.includes("layout")&&{layout:e.layout}}))}}catch(s){return{error:`Failed to get style: ${s instanceof Error?s.message:String(s)}`}}},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 base-map style layers. NOT for loaded BYOD / imported / customer layers — restyle those with setByodLayers.",inputSchema:Xs,outputSchema:Ks,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:n})=>{try{const s=r;return t.baseMap.mapLibreMap.setLayoutProperty(e,s,n),{layerId:e,propertyName:r,value:n,success:!0}}catch(s){return{layerId:e,propertyName:r,error:`Failed to set layout property: ${s instanceof Error?s.message:String(s)}`}}})}}catch(n){return{error:`Failed to set layout properties: ${n instanceof Error?n.message:String(n)}`}}},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:["Set the road-label layer's visibility property to none","Change the text size on building labels","Make the city labels bigger"],relatedTools:["getMapStyleLayers","setPaintProperties"],dependsOn:["getMapStyleLayers"]},setPaintProperties:{description:"Set MapLibre paint properties (colors, widths, opacity) on named layers — use getMapStyleLayers for valid IDs. For visibility/structure use setLayoutProperties; for route styling prefer `updateRoutesDisplay({ mainColor })`.",classificationPrompt:"Set MapLibre paint properties (colors, widths, opacity) on named base-map style layers. NOT for loaded BYOD / imported / customer layers — restyle those with setByodLayers.",inputSchema:eo,outputSchema:Qs,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:n})=>{try{const s=r;return t.baseMap.mapLibreMap.setPaintProperty(e,s,n),{layerId:e,propertyName:r,value:n,success:!0}}catch(s){return{layerId:e,propertyName:r,error:`Failed to set paint property: ${s instanceof Error?s.message:String(s)}`}}})}}catch(n){return{error:`Failed to set paint properties: ${n instanceof Error?n.message:String(n)}`}}},tags:["map style"],examples:['setPaintProperties([{ layerId: "water", propertyName: "fill-color", value: "#0000ff" }])','setPaintProperties([{ layerId: "road", propertyName: "line-color", value: "#ff0000" }, { layerId: "road", propertyName: "line-width", value: 3 }])'],examplePrompts:["Make the water bright blue","Change the road color to red","Make buildings semi-transparent","Make the country borders pink"],relatedTools:["getMapStyleLayers","setLayoutProperties","updateRoutesDisplay"],dependsOn:["getMapStyleLayers"]},setPitchBearing:{description:"Tilt (pitch) and/or rotate (bearing) the map camera — the dedicated tool for a tilt, 3D/perspective, or rotate-to-face view. Pitch 0 = top-down, 85 = near-horizontal (3D); 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:ro,outputSchema:to,execute:async(e,t)=>{const{pitch:r,bearing:n}=e;if(void 0===r&&void 0===n)return{error:"Provide at least one of pitch or bearing"};try{return t.baseMap.mapLibreMap.easeTo({...void 0!==r&&{pitch:r},...void 0!==n&&{bearing:n}}),{success:!0}}catch(s){return{error:`Failed to set pitch/bearing: ${s instanceof Error?s.message:String(s)}`}}},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:so,outputSchema:no,execute:async(e,t)=>{const{delta:r}=e;try{const e=t.baseMap.mapLibreMap,n=e.getZoom()+r;return e.zoomTo(n),{zoom:n}}catch(n){return{error:`Zoom failed: ${n instanceof Error?n.message:String(n)}`}}},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` — the escape hatch for operations no dedicated tool covers (custom sources/layers, style mutations, animations, event sequences). If another tool already performs the operation, prefer that tool. Full JS (loops, helpers, top-level `await`). Returns added/removed/updated sources & layers.",classificationPrompt:"Execute arbitrary MapLibre JS against the live map — the escape hatch ONLY for operations no dedicated tool covers: fog / atmosphere / sky, terrain, globe vs mercator projection, raster / tile overlays, and time-based animations / multi-step loops that one-shot tools cannot express. If any other tool already performs the operation, use that tool instead.",inputSchema:Ls,outputSchema:Fs,execute:async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,n=qs(e),s=$s(e),o=Object.getPrototypeOf(async()=>{}).constructor,a=await new o("map",r)(e),i=Gs(n,qs(e));return{success:!0,result:a??void 0,sources:i,layers:Gs(s,$s(e))}}catch(n){return{error:tt("Code",n)}}},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:["Set map fog or atmosphere","Add a raster tile overlay","Fade in a layer gradually","Switch the map to a globe projection"],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:Ht,outputSchema:Vt,execute:async(e,t)=>{const{geoJson:r}=e;try{const e=W(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(n){return{error:`Failed to calculate bbox: ${n instanceof Error?n.message:String(n)}`}}},tags:["utilities","location"],examples:["calculateBBox(place)","calculateBBox(routes)","calculateBBox([place1, place2])"],examplePrompts:["Calculate the extent of this route","What's the bounding box of these places?"],relatedTools:["flyTo","getViewport","updatePlacesDisplay","updateRoutesDisplay"]},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; if permission is denied, fall back to getViewport as the reference point.',classificationPrompt:'Get the user\'s physical GPS location from the browser; use for "near me" queries, not map center.',inputSchema:sr,outputSchema:or,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"]},clarifyIntent:nr(),help:{description:dr,classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:cr,outputSchema:lr,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 (recallState); `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:Co,outputSchema:jo,execute:async(e,t,r)=>{const{origins:n,budgets:s,theme:o,showOnMap:a,showOriginPin:i=!0,hidePreviousEntries:l}=e;try{const e=[...s].sort((e,t)=>t.value-e.value),{ranges:c,skipped:d}=await(async(e,t,r,n)=>{const s=[],o=[];for(const a of e){const e=Lo(a),i=await oo(a,r,n);if(!i){o.push({origin:e,reason:"origin not found"});continue}const l=await Bo(t,i.position,n);l?s.push({origin:i.query?{query:i.query,position:i.position}:{position:i.position},budgets:t,polygon:l}):o.push({origin:i.name??e,reason:"no reachable area returned"})}return{ranges:s,skipped:o}})(n,e,t,r);if(0===c.length)return{status:"no_results"};const u=await t.ranges.addEntry({label:ls(e,c.map(No)),data:c}),p=a||i;return p&&await In(t.ranges,[u],l),await(async(e,t,r,n,s,o)=>{if(s){const s=(e=>{const t=e.flatMap(e=>e.polygon?.features??[]);return 0===t.length?null:{type:"FeatureCollection",features:t}})(r);s&&(await(await e.ranges.getEntryGeometriesModule(t,n)).show(s),s.bbox&&e.baseMap.mapLibreMap.fitBounds(s.bbox,{padding:50}))}if(o){const n=await e.ranges.getEntryPlacesModule(t),s=r.map((e,r)=>({type:"Feature",id:`range-origin-${t}-${r}`,geometry:{type:"Point",coordinates:e.origin.position},properties:{type:"Point Address",address:{freeformAddress:No(e)}}}));await n.show(s)}})(t,u,c,o??"outline",a,i),p&&await t.ranges.markEntryShown(u),{rangesId:u,ranges:c.map(t=>({originName:No(t),budgets:e})),...d.length&&{skipped:d}}}catch(c){return{error:`Reachable area calculation failed: ${c instanceof Error?c.message:String(c)}`}}},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: [4.9, 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","Map the 20-minute drive zone around our Rotterdam depot","Show the area reachable within 25 km of the Eindhoven warehouse"],relatedTools:["recallState","locatePlace"]},recallState:e=>{const t=e.featureFlags??{};return{...rl,description:'Read-only window onto session state — the ONE tool for "what is already loaded?" and "which id can I pass to a `*EntryIDs` follow-up?". No args → session snapshot (places/routes/ranges entries + per-slice `entryMode` + map style / POIs / traffic configs). `kind` → that kind\'s entry index (places | routes | ranges | geometries | byod | incidents | trafficAreaAnalytics). `kind` + `id` → that entry\'s full detail (places coordinates, route legs, range polygons, BYOD profile, geometry features, incident delay breakdown, analytics metrics). ALWAYS call before referencing past entries; never guess ids. No service call.',inputSchema:el,outputSchema:Qi(t),execute:tl(t)}},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:ol,outputSchema:sl,execute:async(e,t)=>{try{await t.places.clearShownEntries();for(const e of t.places.entries)await(e._modules?.geometries?.clear());for(const e of t.routing.entries)e._module&&(await e._module.clearRoutes(),await e._module.clearWaypoints());for(const e of t.ranges.entries)await(e._modules?.geometries?.clear()),await(e._modules?.places?.clear());for(const e of t.trafficAreaAnalytics.entries)await(e._module?.clear());return t.mapPOIs.poisModule?.resetConfig(),t.trafficTiles.trafficFlowModule?.resetConfig(),t.trafficTiles.trafficIncidentsModule?.resetConfig(),t.places.reset(),t.routing.reset(),t.ranges.reset(),t.customGeometries.reset(),t.trafficAreaAnalytics.reset(),t.trafficIncidents.reset(),t.byod.reset(),t.analyses.reset(),t.trackers.reset(),t.engine.reset(),await t.baseMap.ttMap.setStyle("standardLight",{resetState:!0}),{success:!0,cleared:[...nl]}}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:yl,classificationPrompt:"Switch any entry-owning slice (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents) between `multiple` (overlay several entries) and `single` (latest only). `single` drops older entries.",inputSchema:hl,outputSchema:pl,execute:async(e,t)=>{const{slice:r,mode:n}=e;try{const e=t[r],s=e.entries.map(e=>e.id);await e.setEntryMode(n);const o=new Set(e.entries.map(e=>e.id));return{success:!0,slice:r,mode:n,droppedEntryIds:s.filter(e=>!o.has(e))}}catch(s){return{error:`Failed to set entry mode: ${s instanceof Error?s.message:String(s)}`}}},tags:["state","mode","display"],examples:['setEntryMode({ slice: "places", mode: "single" })','setEntryMode({ slice: "routing", mode: "multiple" })','setEntryMode({ slice: "trafficIncidents", mode: "single" })'],examplePrompts:["Only show one route at a time","Let me overlay multiple places searches","Switch ranges back to single mode","Only keep the latest traffic incidents snapshot"],relatedTools:["recallState"]}},bc=fc,wc=Object.keys(bc),vc={places:["updatePlacesDisplay","locatePlace","discoverPlaces"],routes:["updateRoutesDisplay","setRoute","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","getCurrentWaypoints","updateWaypointsDisplay","startRouteMonitor","stopRouteMonitor"],incidents:["getTrafficIncidents","focusIncidents","setTrafficIncidentsMonitor","clusterIncidents"],customGeometries:[],trafficAreaAnalytics:["getTrafficAreaAnalytics","updateTrafficAreaAnalyticsDisplay"],byod:["addByodSource","setByodLayers","updateByodDisplay"],ranges:["findReachableAreas"]},Ic=()=>{const e={};for(const t of wc){const r=bc[t];e[t]=("function"==typeof r?r({}):r).examplePrompts??[]}return e},Ec=async(e,t)=>{((e,t)=>{for(const[r,n]of Object.entries(t)){const t=e[r];t&&(t.description=n.defaultDescription,t.inputSchema=n.defaultInputSchema)}})(e.tools,e.scopableTools);let r=null;if(e.classifier)try{r=await e.classifier({messages:t,toolsMetadata:e.toolsMetadata})}catch{r=null}return e.onClassify?.(r),((e,t,r)=>{if(r)for(const[n,s]of Object.entries(r)){const r=t[n];if(!r||null==s)continue;const o=e[n];if(!o)continue;const a=r.applyScope(s);a&&(o.description=a.description,o.inputSchema=a.inputSchema)}})(e.tools,e.scopableTools,r?.toolScopes),r},Sc=(e,r)=>{if(!r.model)throw new Error("MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.");e._setTomTomUserAgent(De);const o=((e,t)=>{const r={places:new Oe(e),mapPOIs:new xe(e),routing:new Be(e),baseMap:new ye(e),trafficTiles:new kt(e),trafficAreaAnalytics:new gt(e),trafficIncidents:new Mt(e),ranges:new Ce(e),customGeometries:new we(e),byod:new ht(e),codeExecution:at(),engine:new Ne,analyses:new he,trackers:new yt};return t&&Object.assign(r,t),r.engine.ensureWired(r),r})(e,r.state);if(r.byod?.validateSourceUrl&&(o.byod.sourceUrlValidator=r.byod.validateSourceUrl),o.codeExecution=at(r.codeExecution),r.dataEntries)for(const[t,n]of Object.entries(r.dataEntries)){const e=t;n?.entryMode&&o[xt[e]].setEntryMode(n.entryMode)}const a=e=>!1!==r.dataEntries?.[e]?.enabled,i=Object.keys(xt).filter(e=>!a(e)),l=tn.filter(e=>a(e)),c=new Set(i.flatMap(e=>vc[e])),d=!1===r.includeDefaultTools?{}:bc,u=Object.fromEntries(Object.entries(d).filter(([e])=>!c.has(e))),p=ue(u,r.tools),{tools:h,toolsMetadata:y,scopableTools:m}=yr(p,o,{outputSchemas:r.outputSchemas,featureFlags:r.featureFlags,enabledDataKinds:l,onToolExecute:r.onToolExecute}),g=!1===r.classifier?null:r.classifier??ns({model:r.model}),f=Rt({customPrompt:r.systemPrompt,prefix:r.systemPromptPrefix,suffix:r.systemPromptSuffix}),b=Object.entries(y).filter(([,e])=>e.alwaysActive).map(([e])=>e),w=Object.entries(y).filter(([,e])=>e.endsTurnOnCall).map(([e])=>n(e));let v=null;const I=(()=>{let e=Promise.resolve();return async t=>{const r=e;let n=()=>{};e=new Promise(e=>{n=e});try{return await r,await t()}finally{n()}}})(),E={tools:h,scopableTools:m,toolsMetadata:y,classifier:g,onClassify:r.onClassify},S=new t({model:r.model,tools:h,instructions:f,stopWhen:[s(r.maxSteps??10),...w],prepareStep:e=>I(async()=>{0===e.stepNumber&&(v=await Ec(E,e.messages));const t=le(v?.activeToolNames,b),n=await(r.prepareStep?.(e)),s=r.stepProviderOptions?.({activeTools:t,stepNumber:e.stepNumber}),o=((e,t,r)=>r?{...r,activeTools:ie(e,r.activeTools),providerOptions:de(t,r.providerOptions)}:{...e?{activeTools:e}:{},...t?{providerOptions:t}:{}})(t,s,n);return o?.activeTools?{...o,activeTools:le(o.activeTools,b)}:o}),providerOptions:r.providerOptions});return Object.assign(S,{state:o,destroy:()=>(e=>{for(const t of Object.values(e))ce(t)&&t.reset()})(o)})};export{Ae as AGENT_TOOLKIT_ID,De as AGENT_TOOLKIT_USER_AGENT,tn as ALL_ENTRY_DATA_KINDS,We as ANALYSE_OUTPUT_FORMAT_DESCRIPTION,wn as AREA_GEOGRAPHY_TYPES,Ct as BASE_SYSTEM_PROMPT,Fn as BY_ENTRY_VIEWS_DOC,qe as CHART_TYPES,Zt as CLARIFY_FORM_PROMPT,Qt as CLARIFY_PROSE_PROMPT,xt as DATA_ENTRY_KIND_TO_SLICE,bc as DEFAULT_TOOLS,nn as ENTRY_KIND_META,At as ENTRY_MODE_SLICE_NAMES,mn as FIT_ON_MAP_DOC,fr as GEOMETRIES_PROPS_DOC,vr as GEOMETRIES_SCHEMA_DOC,br as GEOMETRIES_SKIPPED_DESC,wr as GEOMETRIES_SOURCE_IDS_DESC,Os as MIN_VIEWPORT_BIAS_ZOOM,$n as MULTI_INPUT_SANDBOX_PARAMS,Er as PLACES_SCHEMA_DOC,Sr as ROUTES_SCHEMA_DOC,Pt as SYSTEM_PROMPT_SECTIONS,vc as TOOLS_BY_DATA_ENTRY_KIND,wc as TOOL_NAMES,Ke as WORKER_PROVIDED_PARAMS,bn as applyFitOnMap,en as areaWhereSchema,ze as buildAnalyseReturnPrompt,pn as buildAtLeastOneEntryIdMessage,an as buildEntryFieldList,dn as buildEntryIdFields,ln as buildEntryKindSandboxDocs,cn as buildEntryKindSchemaDocs,rn as buildEntryKindScopeSchema,Ir as buildPlacesSchemaDoc,Fe as buildSandboxCodePrompt,Gn as buildSandboxToolsDoc,Rt as buildSystemPrompt,Xt as clarifyIntentOutputSchema,Kt as clarifyIntentSchema,Qn as classifyUserIntent,_n as collectInputGeometries,Ot as composeSystemPrompt,nr as createClarifyIntentTool,ns as createDefaultClassifier,Sc as createMapAgent,rs as extractLastUserText,On as findPlacesByEntry,hn as fitOnMapInputSchema,yn as fittedReportSchema,tt as formatSandboxExecutionError,ke as formatStateDigestDiff,Tr as geoJsonBBoxSchema,vn as geocodeAreas,gr as geometriesEntryIDsSchema,mr as geometriesIdSchema,Lr as geometryDisplayConfigSchema,Or as geometryInputSchema,Ic as getDefaultToolPrompts,Yn as getRangePolygons,Ee as getStateDigest,Cs as getViewportBias,Rs as getViewportBoundingBox,Xr as globalWhereSchema,un as hasAnyEntryIds,qr as hidePreviousEntriesSchema,In as hidePreviousShownEntries,$e as isChartConfiguration,fn as isFitOnMapInput,on as isKindActive,Ge as isPolygonFeature,Ue as isPolygonFeatureArray,vs as isResolveError,kn as labelForLocationInput,Cn as latestTimestamp,Sn as locatePlace,En as locatePlaces,Mn as locationInputSchema,Xe as mainThreadExecutor,Fr as markerTypeSchema,_r as multiPolygonInputSchema,Kr as nearbyWhereSchema,Un as packSandboxArgs,Cr as placesEntryIDsSchema,jr as placesEntryIdHintSchema,Pr as polygonInputSchema,Ar as positionSchema,jn as prepareMultiInputs,Yr as queryAsSchema,Jt as questionSchema,sn as resolveActiveKinds,ws as resolveGlobal,bs as resolveNearby,zn as resolvePoiCategories,ue as resolveTools,ms as resolveWhere,fs as resolveWithin,qn as routeUtils,Rr as routesEntryIDsSchema,Ze as runSandboxedFn,Je as sandboxedGlobalShadows,Qr as sharedWithinFields,Ts as showByodOnMap,Ur as showByodSchema,Ps as showEntryGeometries,Br as showModeSchema,Nr as showPlaceGeometriesSchema,$r as showPlacesSchema,_s as showResultsOnMap,Wr as shownByodSchema,Gr as shownSchema,xn as skippedSourceSchema,Qe as stripInjectedRedeclarations,js as toolStateToWhereContext,Ye as validateAnalysisResult,Zr as whereSchema,Te as withAgentToolkitHeaders,zr as withinSharedFields,Vr as withinWhereSchema};
1
+ import{Output as e,ToolLoopAgent as t,generateText as r,hasToolCall as n,stepCountIs as s}from"ai";import{BaseMapModule as o,CustomGeoJSONModule as a,GeometriesModule as i,POIsModule as l,PlacesModule as c,RoutingModule as d,StylingModule as u,TrafficAreaAnalyticsModule as p,TrafficFlowModule as h,TrafficIncidentOverlayModule as y,TrafficIncidentsModule as m,mapStyleLayerIDs as f,reachableRangeGeometryConfig as g,standardStyleIDs as b,stylingKnobAppliesTo as w,stylingKnobIds as v,stylingKnobKinds as I,stylingPresetIds as E,themedGeometryConfig as S}from"@tomtom-org/maps-sdk/map";import{alongRouteSearch as k,calculateReachableRanges as M,calculateRoute as A,geocode as x,geometryData as D,getPOICategoryCodes as T,reverseGeocode as P,routeTypes as _,search as O,trafficAreaAnalytics as C,trafficIncidentDetails as R}from"@tomtom-org/maps-sdk/services";import{TOMTOM_USER_AGENT_SDK_NAME as j,TomTomConfig as N,avoidableTypes as B,bboxCenter as L,bboxFromBBoxes as F,bboxFromGeoJSON as q,calculateProgressAtRoutePoint as $,formatDistance as G,formatDuration as W,getCoordinateAtRouteProgress as U,getPosition as z,getProgressAtNearestRoutePoint as H,getRouteProgressBetween as Y,getRouteProgressForSection as V,getSectionBBox as J,placeTypes as K,poiCategories as X,trafficIncidentRequestCategories as Z,withInsertedWaypoints as Q}from"@tomtom-org/maps-sdk/core";import*as ee from"@turf/turf";import{z as te}from"zod";import*as re from"h3-js";var ne=(e,t)=>{if(!e)return t;if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},se=(e,t)=>{if(!e)return;if(0===t.length)return e;const r=new Set(e);return[...e,...t.filter(e=>!r.has(e))]},oe=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,ae=(e,t)=>{if(e||t)return{...e,...t}},ie=(e,t)=>{const r={...e};if(!t)return r;for(const[n,s]of Object.entries(t))s?r[n]=s:delete r[n];return r},le=class{handlers={};on(e,t){return(this.handlers[e]??(this.handlers[e]=[])).push(t),()=>{const r=this.handlers[e];if(!r)return;const n=r.indexOf(t);-1!==n&&r.splice(n,1)}}off(e){this.handlers[e]&&(this.handlers[e].length=0)}emit(e,t){const r=this.handlers[e];if(r?.length)for(const n of[...r])n(t)}clear(){for(const e of Object.keys(this.handlers))this.handlers[e]=[]}},ce=class{_records=/* @__PURE__ */new Map;_jobs=/* @__PURE__ */new Map;events=new le;register(e){const t=this._records.get(e.analysisId);this._records.set(e.analysisId,{analysisId:e.analysisId,name:e.name,description:e.description,outputFormat:e.outputFormat,affectedEntryIds:e.affectedEntryIds,history:t?.history??[]})}setJob(e,t){this._jobs.set(e,t)}job(e){return this._jobs.get(e)}resetHistory(e){const t=this._records.get(e);t&&(t.history=[])}attachResult(e,t){const r=this._records.get(e);r&&(r.history.at(-1)?.timestamp===t.timestamp?r.history[r.history.length-1]=t:(r.history.push(t),r.history.length>240&&r.history.shift()),this.events.emit("analysis-change",{analysisId:e,affectedEntryIds:r.affectedEntryIds}))}getAnalysesForEntry(e){const t=[];for(const r of this._records.values()){if(!r.affectedEntryIds.includes(e))continue;const n=r.history.at(-1);n&&t.push(n)}return t}all(){return[...this._records.values()]}history(e){return this._records.get(e)?.history??[]}lastResult(e){return this._records.get(e)?.history.at(-1)?.data}get(e){return this._records.get(e)}isMonitored(e){return this._jobs.get(e)?.active??!1}remove(e){this._jobs.get(e)?.unregister(),this._jobs.delete(e),this._records.delete(e)}removeByName(e){for(const[t,r]of this._records)r.name===e&&this.remove(t)}reset(){for(const e of this._jobs.values())e.unregister();this._jobs.clear(),this._records.clear()}},de=class{ttMap;_baseMapModule;_stylingModule;constructor(e){this.ttMap=e}get mapLibreMap(){return this.ttMap.mapLibreMap}async getBaseMapModule(){return this._baseMapModule=await o.get(this.ttMap),this._baseMapModule}async getStylingModule(){return this._stylingModule=await u.get(this.ttMap),this._stylingModule}reset(){this._baseMapModule=void 0,this._stylingModule=void 0}},ue=(e,t)=>{const r=new Set(t);if(!r.has(e))return e;for(let n=2;;n++){const t=`${e}-${n}`;if(!r.has(t))return t}},pe=async(e,t,r={})=>{if(0!==e.length)if(r.parallel)await Promise.all(e.map(t));else for(const n of e)await t(n)},he=async(e,t,r={})=>{if(e.length<=1)return[...e];const n=e[e.length-1];return await pe(e.slice(0,-1),t,r),[n]},ye=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const n of e){const e="string"==typeof n.id?n.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(n)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},me=class{_ttMap;_entries=[];_entryMode="multiple";events=new le;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r,n){const s="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const o=`geometries-${this._entries.length}`,a=ue(n??o,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:r,data:e,provenance:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...s,a]}),a}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e),n={...S(),theme:t};return r._module?r._shownTheme!==t&&(r._module.applyConfig(n),r._shownTheme=t):(r._module=await i.create(this._ttMap,n),r._shownTheme=t),r._module}async showEntry(e,t="outline",r="replace"){const n=this._requireEntry(e);await this._hideOthersUnderSingleMode(e);const s=await this.getEntryGeometriesModule(e,t),o=n.data;"add"===r?await ye(s,o):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(s,o);const a=n._shown;n._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?._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",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`CustomGeometriesState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._module?.clear();this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},fe=(e,t)=>e.localeCompare(t),ge=e=>[...e].sort(fe),be=e=>({places:{shownAsPin:ge(e.places.shownAsPinIds),shownAsBaseMap:ge(e.places.shownAsBaseMapIds),entryMode:e.places.entryMode},routes:{shown:ge(e.routing.shownEntryIds),entryMode:e.routing.entryMode},ranges:{shown:ge(e.ranges.shownEntryIds),entryMode:e.ranges.entryMode},traffic:{shown:ge(e.trafficAreaAnalytics.shownEntryIds),entryMode:e.trafficAreaAnalytics.entryMode,entryCount:e.trafficAreaAnalytics.entries.length},incidents:{shown:ge(e.trafficIncidents.shownEntryIds),monitored:ge(e.trafficIncidents.entries.filter(e=>"running"===e._monitor?.status).map(e=>e.id)),entryMode:e.trafficIncidents.entryMode},customGeometries:{shown:ge(e.customGeometries.shownEntryIds),entryMode:e.customGeometries.entryMode,entryCount:e.customGeometries.entries.length},byod:{shown:ge(e.byod.shownEntryIds),entryMode:e.byod.entryMode,entryCount:e.byod.entries.length}}),we=(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},ve=(e,t,r)=>{if(!we(t,r))return 0===r.length?`${e} cleared`:`${e} shown: ${r.map(e=>`"${e}"`).join(", ")}`},Ie=(e,t)=>{const r=[],n=((e,t)=>{const r=[];return we(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(", ")}`),we(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);n&&r.push(n);const s=ve("routes",e.routes.shown,t.routes.shown);s&&r.push(s);const o=ve("ranges",e.ranges.shown,t.ranges.shown);o&&r.push(o);const a=ve("traffic analytics",e.traffic.shown,t.traffic.shown);a&&r.push(a);const i=ve("incidents",e.incidents.shown,t.incidents.shown);i&&r.push(i);const l=ve("incidents monitor",e.incidents.monitored,t.incidents.monitored);l&&r.push(l);const c=ve("custom geometries",e.customGeometries.shown,t.customGeometries.shown);c&&r.push(c);const d=ve("byod",e.byod.shown,t.byod.shown);return d&&r.push(d),e.places.entryMode!==t.places.entryMode&&r.push(`places entryMode → ${t.places.entryMode}`),e.routes.entryMode!==t.routes.entryMode&&r.push(`routes entryMode → ${t.routes.entryMode}`),e.ranges.entryMode!==t.ranges.entryMode&&r.push(`ranges entryMode → ${t.ranges.entryMode}`),e.incidents.entryMode!==t.incidents.entryMode&&r.push(`incidents entryMode → ${t.incidents.entryMode}`),e.customGeometries.entryMode!==t.customGeometries.entryMode&&r.push(`custom geometries entryMode → ${t.customGeometries.entryMode}`),e.byod.entryMode!==t.byod.entryMode&&r.push(`byod entryMode → ${t.byod.entryMode}`),0===r.length?null:`[map state changed since last response: ${r.join("; ")}]`},Ee=class{_ttMap;_poisModule;constructor(e){this._ttMap=e}async getPOIsModule(){return this._poisModule=await l.get(this._ttMap),this._poisModule}get poisModule(){return this._poisModule}reset(){this._poisModule=void 0}},Se="AgentToolkit",ke=`${j}-${Se}/0.7.0`,Me=e=>({...e,"tomtom-user-agent":ke}),Ae=["pin","base-map","pin-clustered"],xe=async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const n of e){const e="string"==typeof n.id?n.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(n)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})},De=class{_ttMap;_entries=[];_entryMode="multiple";events=new le;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this._hideEntry(e)),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}this.events.emit("mode-change",e)}}async getEntryPlacesModule(e,t){const r=this._requireEntry(e);return r._modules??={},r._modules.places?r._shownAs!==t&&r._modules.places.applyTheme(t):r._modules.places=await c.create(this._ttMap,{theme:t}),r._modules.places}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);r._modules??={};const n={...S(),theme:t};return r._modules.geometries?r._modules.geometries.applyConfig(n):r._modules.geometries=await i.create(this._ttMap,n),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)?.data}async addPlaceResult(e,t,r,n,s){"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this._hideEntry(e)),this._entries=[]);const o=`places-${this._entries.length}`,a=ue(r??o,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:t,data:"features"in e?e.features:[e],...n?.length&&{connections:n},...s?.length&&{geometries:s}}),this.events.emit("entries-change",{entries:this._entries,changedIds:[a]}),s?.length&&this.events.emit("geometries-change",{entryId:a,count:s.length}),a}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.data)r._byId[e.id]=e}const n=r._byId[e];if(n)return{place:n,entryId:r.id}}}geometryPlaceIdsForEntry(e){const t=this._entries.find(t=>t.id===e);if(t)return t.data.filter(e=>e.properties.dataSources?.geometry?.id&&"string"==typeof e.id).map(e=>e.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 n=r._geometryByPlaceId[e];if(n)return n}}async fetchPlaceGeometry(e,t){const r=this.getGeometryForPlace(e);if(r)return r;const n=this.findPlaceById(e);if(!n)return;if(!n.place.properties.dataSources?.geometry?.id)return;const s=Me({geometries:[n.place],signal:t?.signal}),o=(await D(s)).features[0];if(!o)return;const a=this._entries.find(e=>e.id===n.entryId);return a&&(a.geometries??=[],a.geometries.push(o),a._geometryByPlaceId??={},a._geometryByPlaceId[e]=o,this.events.emit("geometries-change",{entryId:a.id,count:a.geometries.length})),o}async fetchGeometriesForEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return[];const n=/* @__PURE__ */new Set;for(const c of r.geometries??[])"string"==typeof c.id&&n.add(c.id);const s=[],o=/* @__PURE__ */new Set;for(const c of r.data){const e=c.properties.dataSources?.geometry?.id;e&&(n.has(e)||o.has(e)||(o.add(e),s.push(c)))}if(0===s.length)return r.geometries??[];const a=[];for(let c=0;c<s.length;c+=5)a.push(s.slice(c,c+5));const i=await Promise.all(a.map(e=>D(Me({geometries:e,signal:t?.signal})))),l=[...r.geometries??[]];for(const c of i)l.push(...c.features);return r.geometries=l,r._geometryByPlaceId=void 0,this.events.emit("geometries-change",{entryId:r.id,count:l.length}),l}async showPlaceGeometries(e,t="outline",r="replace",n){const s=n??this._latestGeometriesEntryId();if(!s)return;const o=await this.getEntryGeometriesModule(s,t);"add"===r?await xe(o,e):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(o,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 n of e.geometries??[])"string"==typeof n.id&&t.set(n.id,n);const r={};for(const n of e.data){const e=n.properties.dataSources?.geometry?.id;if(!e)continue;const s=t.get(e);s&&(r[n.id]=s)}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.data);return e}async setShownEntries(e,t){const r=new Set(e),n=[],s=[];for(const o of this._entries)r.has(o.id)?n.push(o):o._shownAs===t&&s.push(o);for(const o of s)await this._hideEntry(o);for(const o of n)await this._showEntryAs(o,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 n of r)await this._showEntryAs(n,t);return r.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async removeShownEntries(e){const t=e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._shownAs?[t]:[]});for(const r of t)await this._hideEntry(r);return t.length>0&&this.events.emit("shown-change",this.shownEntryIds),this.getShownFeatures()}async clearShownEntries(){const e=this._entries.filter(e=>e._shownAs);if(0!==e.length){for(const t of e)await this._hideEntry(t);this.events.emit("shown-change",this.shownEntryIds)}}async clearShown(){await this.clearShownEntries(),await this.clearShownGeometries()}async clearShownGeometries(e){const t=e?e.flatMap(e=>{const t=this._entries.find(t=>t.id===e);return t?._modules?.geometries?[t]:[]}):this._entries.filter(e=>e._modules?.geometries);if(0!==t.length)for(const r of t)await r._modules.geometries.clear(),this.events.emit("geometries-change",{entryId:r.id,count:0})}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],n=!!r._shownAs;r._modules&&(await(r._modules.places?.clear()),await(r._modules.geometries?.clear())),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n&&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.data}),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 t of this._entries)t._modules&&(t._modules.places?.clear(),t._modules.geometries?.clear());const e=this._entries.map(e=>e.id);this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},Te=class{_ttMap;_entries=[];_entryMode="multiple";events=new le;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}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(g(void 0,t)),r._shownTheme=t):(r._modules.geometries=await i.create(this._ttMap,g(void 0,t)),r._shownTheme=t),r._modules.geometries}async getEntryPlacesModule(e){const t=this._requireEntry(e);return t._modules??={},t._modules.places??=await c.create(this._ttMap),t._modules.places}async markEntryShown(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._modules?.geometries&&await t._modules.geometries.clear(),t._modules?.places&&await t._modules.places.clear(),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RangeState: unknown entry "${e}"`);return t}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}async addEntry(e){const t="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const r=ue(`ranges-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:r,timestamp:Date.now(),...e}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...t,r]}),r}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._modules?.geometries?.clear(),t._modules?.places?.clear();this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},Pe=class{events=new le;_status="idle";_error;_intervalHandle;_clearInterval;_recalculate;get status(){return this._status}get error(){return this._error}get isRunning(){return"running"===this._status}get isActive(){return"idle"!==this._status}start(e,t=6e4,r=!1){if("running"===this._status)return;this._status="running",this._error=void 0,this._recalculate=e.recalculate,this._clearInterval=e.clearInterval??(e=>clearInterval(e));const n=e.setInterval??setInterval;r||this._tick(),this._intervalHandle=n(()=>{this._tick()},t)}stop(){"idle"!==this._status&&(this._releaseTimer(),this._status="idle",this._error=void 0,this._recalculate=void 0)}async _tick(){const e=this._recalculate;if(e)try{const t=await e(),r={takenAt:Date.now(),routes: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._recalculate=void 0,this.events.emit("error",e)}_releaseTimer(){null!=this._intervalHandle&&(this._clearInterval?.(this._intervalHandle),this._intervalHandle=void 0),this._clearInterval=void 0}},_e=class{_ttMap;_entries=[];_planningSlots=[];_params={};_entryMode="multiple";_mainColor;_lastShownEntryId;events=new le;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}async getEntryRoutingModule(e){const t=this._requireEntry(e);return t._module??=await d.create(this._ttMap),t._module}async showEntry(e,t){const r=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 n=await this.getEntryRoutingModule(e),s={};void 0!==this._mainColor&&(s.theme={mainColor:this._mainColor}),!1===t?.showSummaryBubbles&&(s.summaryBubbles={visible:!1}),(s.theme||s.summaryBubbles)&&n.updateConfig(s),await n.showRoutes(r.data),!1===t?.showWaypoints?await n.clearWaypoints():r.waypoints.length&&await n.showWaypoints(r.waypoints);const o=r._shown;r._shown=!0,this._lastShownEntryId=e,o||this.events.emit("shown-change",this.shownEntryIds)}get mainColor(){return this._mainColor}setMainColor(e){if(this._mainColor=e,void 0!==e)for(const t of this._entries)t._shown&&t._module&&t._module.updateConfig({theme:{mainColor:e}})}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._module&&(await t._module.clearRoutes(),await t._module.clearWaypoints()),t._shown=!1,this._lastShownEntryId===e&&(this._lastShownEntryId=this._entries.findLast(e=>e._shown)?.id),this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(this.stopMonitoring(e),await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`RoutingState: unknown entry "${e}"`);return t}get currentEntryModule(){if(!this._lastShownEntryId)return;const e=this._entries.find(e=>e.id===this._lastShownEntryId);return e?._shown?e._module:void 0}get entries(){return this._entries}get currentRoutes(){return this._entries.at(-1)?.data}get currentWaypoints(){return this._entries.at(-1)?.waypoints}get planningSlots(){return this._planningSlots}get params(){return this._params}async addRoutes(e,t,r){const n="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=ue(`routes-${this._entries.length}`,this._entries.map(e=>e.id));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",{entries:this._entries,changedIds:[...n,s]}),this.events.emit("planning-change",this._planningSlots),s}setWaypointAt(e,t){const r=Math.max(2,e+1);for(;this._planningSlots.length<r;)this._planningSlots.push(null);this._planningSlots[e]=t,this.events.emit("planning-change",this._planningSlots)}setParams(e){this._params={...this._params,...e},this.events.emit("params-change",this._params)}async replaceRouteData(e,t,r){const n=this._entries.find(t=>t.id===e);return!!n&&(n._tick=(n._tick??0)+1,n.data=t,n.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n._shown&&n._module&&await n._module.showRoutes(t),!0)}_createMonitorForEntry(e){const t=new Pe;return t.events.on("tick",t=>{this.replaceRouteData(e.id,t.routes,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}startMonitoring(e,t,r){const n=this._requireEntry(e),s=n._monitor??=this._createMonitorForEntry(n),o=s.isRunning;s.start(t,r?.intervalMs,r?.skipInitialTick),o||this.events.emit("monitor-start",{entryId:e})}stopMonitoring(e){const t=this._entries.find(t=>t.id===e);if(!t?._monitor)return;const r=t._monitor.isRunning;t._monitor.stop(),r&&this.events.emit("monitor-stop",{entryId:e,reason:"manual"})}isMonitored(e){return this._entries.find(t=>t.id===e)?._monitor?.isRunning??!1}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._monitor?.stop(),t._module?.clearRoutes(),t._module?.clearWaypoints();this._entries=[],this._planningSlots=[],this._params={},this._lastShownEntryId=void 0,this._mainColor=void 0,this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("planning-change",this._planningSlots),this.events.emit("params-change",this._params),this.events.emit("shown-change",this.shownEntryIds)}},Oe=class{jobs=/* @__PURE__ */new Set;wired=!1;scanning=!1;scheduled=!1;pending=/* @__PURE__ */new Set;register(e){const t=new Ce(e,this);return this.jobs.add(t),t}drop(e){this.jobs.delete(e)}ensureWired(e){if(this.wired)return;this.wired=!0;const t=t=>this.onChange(e,t.changedIds);for(const r of this.sourceSlices(e))r.events.on("entries-change",t)}sourceSlices(e){return[e.places,e.routing,e.trafficIncidents,e.trafficAreaAnalytics,e.byod,e.customGeometries]}onChange(e,t){this.collectOrphans(e);for(const r of t)this.pending.add(r);this.scheduleScan()}collectOrphans(e){const t=/* @__PURE__ */new Set;for(const n of this.sourceSlices(e))for(const e of n.entries)t.add(e.id);const r=[...this.jobs].filter(e=>!e.affectedEntryIds.some(e=>t.has(e)));for(const n of r)this.jobs.delete(n),n.onOrphaned?.()}hasActiveJob(){for(const e of this.jobs)if(e.active)return!0;return!1}scheduleScan(){this.scanning||this.scheduled||(this.hasActiveJob()?(this.scheduled=!0,queueMicrotask(()=>{this.scheduled=!1,this.runScan()})):this.pending.clear())}async runScan(){const e=this.pending;this.pending=/* @__PURE__ */new Set,this.scanning=!0;try{for(const t of this.jobs)t.active&&t.affectedEntryIds.some(t=>e.has(t))&&await t.run()}finally{this.scanning=!1}}reset(){this.jobs.clear(),this.pending.clear(),this.scanning=!1,this.scheduled=!1}},Ce=class{engine;affectedEntryIds;code;active;run;onOrphaned;constructor(e,t){this.engine=t,this.affectedEntryIds=e.affectedEntryIds,this.code=e.code,this.active=e.active,this.run=e.run,this.onOrphaned=e.onOrphaned}setActive(e){this.active=e}unregister(){this.engine.drop(this)}},Re=e=>`In scope: ${e.map(e=>`\`${e}\``).join(", ")} — injected as named parameters; use them directly. Do NOT redeclare them or read them off \`arguments\`: lines like \`const turf = arguments.turf\` / \`const turf = arguments[0].turf\` / \`const placesByEntry = arguments.placesByEntry\` throw "Identifier '…' has already been declared". The body has no \`arguments\`-based access — reference each name as-is. No \`require\` / \`import\` / \`await import\` (body is an AsyncFunction, not a module — such lines are stripped silently). \`h3\` = h3-js (hex math). \`turf\` = @turf/turf v7 (verify names at https://turfjs.org/docs/). INPUTS are \`<kind>ByEntry\` records keyed by entry id — get a value out first: one entry \`placesByEntry[id]\` (a FeatureCollection) / \`incidentsByEntry[id]\` (an array), or all entries \`Object.values(placesByEntry).flatMap(fc => fc.features)\` / \`Object.values(incidentsByEntry).flat()\`. TURF INPUTS: pass a Feature or FeatureCollection — never a bare Array (throws "Unknown Geometry Type"). FeatureCollection values (\`placesByEntry[id]\`, \`routesByEntry[id]\`, \`trafficAreaAnalyticsByEntry[id]\`, \`byodByEntry[id]\`) → pass directly: \`turf.bbox(placesByEntry[id])\`. Array values (\`incidentsByEntry[id]\`, \`geometriesByEntry[key]\`, \`…features\`, merged \`Object.values(...)\`) → iterate per-feature (\`for (const f of incidentsByEntry[id]) turf.X(f)\`) or wrap with \`turf.featureCollection([...])\`. Never \`.map(f => f.geometry.coordinates)\` / \`.flatMap(f => f.geometry.coordinates)\` to feed turf — that strips the feature wrapper turf reads and throws "coordinates must be an Array" (or worse, mixes Point/LineString shapes silently). Pass the whole feature and let turf read the geometry: \`turf.pointToLineDistance(point, lineFeature, { units: "meters" })\`. \`incidentsByEntry[id]\` mixes Point + LineString features — guard with \`if (inc.geometry.type === "LineString")\` before line-only ops. TURF v7 SET OPS: \`union\`/\`intersect\`/\`difference\` take ONE FeatureCollection — \`turf.difference(turf.featureCollection([outer, inner]))\`. The v6 two-arg form throws. PERF: for large-N nearest/within queries, pre-bucket points with \`h3.latLngToCell(lat, lng, res)\` + \`h3.gridDisk(cell, k)\` and only run turf on same-/neighbour-cell pairs — turns O(N·M) into ~O(N+M). Pick \`res\` so each cell ≈ your query radius. No spatial search or HTTP — call \`discoverPlaces\` BEFORE this tool for new places. Top-level \`await\`/locals/consts fine. End with \`return …;\`.`,je=/* @__PURE__ */new Set(["bar","line","pie","doughnut","radar","polarArea","scatter","bubble"]),Ne=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"string"==typeof t.type&&je.has(t.type)&&"object"==typeof t.data&&null!==t.data},Be=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)},Le=e=>Array.isArray(e)&&e.every(Be),Fe='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.',qe=(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})`:""}.`,$e=(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=Ge(e);return"error"in r?{error:r.error}:"chart"!==t||Ne(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? }).'}},Ge=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.`}}},We=["fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","document","window","globalThis","self","navigator","importScripts","process"],Ue=We,ze=["turf","h3","routeUtils","log","cluster"],He={run:async(e,t,r,n)=>{try{const n=Object.getPrototypeOf(async()=>{}).constructor,s=We.filter(e=>!t.includes(e)),o=r.map((e,r)=>((e,t)=>void 0===e||ze.includes(t)?e:structuredClone(e))(e,t[r]));return{value:await new n(...t,...s,e)(...o,...s.map(()=>{}))}}catch(s){return{error:Ke(n,s)}}}},Ye=(e,t,r,n,s)=>s.run(Ve(e,t),t,r,n),Ve=(e,t)=>{const r=t.join("|"),n=String.raw`(?:(?:await\s+)?(?:require|import)\s*\(`+String.raw`|arguments\s*(?:\[\s*\d+\s*\])?\s*(?:\.\w+|\[\s*['"]\w+['"]\s*\]))`,s=new RegExp(String.raw`^[ \t]*(?:const|let|var)[ \t]+(?:${r})[ \t]*=[ \t]*${n}[^\n]*\n?`,"gm");return e.replace(s,"")},Je=[{pattern:/Cannot read propert(?:y|ies) of undefined/i,hint:"A property was read on `undefined`. Likely a `reduce` callback that does not `return` the accumulator, or a `reduce` called without an initial value — use `arr.reduce((acc, x) => { ...; return acc; }, {})`. Also check unguarded chains like `feature.properties.poi.categories` — use optional chaining (`?.`) and `??` defaults."},{pattern:/Cannot read propert(?:y|ies) of null/i,hint:"A property was read on `null`. Guard property access with `?.` and provide defaults with `??`."},{pattern:/\bh3\.\w+ is not a function/i,hint:"`h3` is [h3-js](https://github.com/uber/h3-js) — hex-grid math only. NO spatial search / place lookup / HTTP. Common: `latLngToCell`, `cellToLatLng`, `cellToBoundary`, `polygonToCells`, `gridDisk`, `cellArea`. For nearby places call `discoverPlaces` BEFORE this tool — sandbox cannot call other tools."},{pattern:/\bturf\.\w+ is not a function/i,hint:"`turf` is [@turf/turf](https://turfjs.org/) — GeoJSON geometry only. NO spatial search / place lookup / HTTP. Common: `area`, `length`, `bbox`, `centroid`, `union`, `intersect`, `buffer`, `distance`, `booleanPointInPolygon`, `pointsWithinPolygon`, `clustersDbscan`, `convex`. Verify names at https://turfjs.org/docs/. For nearby places call `discoverPlaces` BEFORE this tool."},{pattern:/coordinates must be an Array|Unknown Geometry Type/i,hint:'turf was handed a raw coordinate (or a bare Array of features) instead of a Feature / FeatureCollection. Do NOT extract `.geometry.coordinates` with `.map` / `.flatMap` and feed the result to turf — pass the WHOLE feature: `turf.pointToLineDistance(pointFeature, lineFeature, { units: "meters" })`. `incidentsByEntry[id]` is an Array of mixed Point + LineString features; guard with `if (inc.geometry.type === "LineString")` before line-only ops. A single entry of an FC kind (`placesByEntry[id]`, `routesByEntry[id]`, `trafficAreaAnalyticsByEntry[id]`, `byodByEntry[id]`) is a FeatureCollection — pass it directly (`turf.bbox(placesByEntry[id])`), never `turf.bbox(placesByEntry[id].features)`. To span entries, merge then wrap: `turf.bbox(turf.featureCollection(Object.values(placesByEntry).flatMap(fc => fc.features)))`.'},{pattern:/coord must be (?:a )?(?:GeoJSON )?Point|must be a Point/i,hint:'A point-only turf op (`booleanPointInPolygon`, `pointsWithinPolygon`, `getCoord`, `distance`, …) was handed a non-Point feature. `incidentsByEntry[id]` and `byodByEntry[id]` mix Point + LineString + Polygon geometries, so `feature` may be a LineString. To test whether ANY feature falls inside a polygon regardless of its geometry type, use `turf.booleanIntersects(feature, polygon)` — it accepts Point / LineString / Polygon. Only reach for `turf.booleanPointInPolygon(pt, polygon)` after guarding `if (feature.geometry.type === "Point")`, or reduce a line/area to one representative point first with `turf.pointOnFeature(feature)` (guaranteed on the feature) or `turf.centroid(feature)`.'},{pattern:/is not a function/i,hint:"A method was called on a value that does not have it (typo, or the value is not the expected type). Verify the type before calling, e.g. `Array.isArray(x) ? x.map(...) : []`."},{pattern:/Illegal return statement/i,hint:"A `return` was placed outside the top-level function body — usually a missing brace or a callback that the parser thinks closed too early. Common cause: a multi-line `const X = arr.map((item) => { ... })` whose closing `})` was lost. Re-emit the code with balanced braces, keep `return` only at the top level of the body, and try again."},{pattern:/Identifier ['"]?\w+['"]? has already been declared/i,hint:"A variable was declared twice — typically because the code prepends `const turf = require(...)` / `const turf = await import(...)` / `const turf = arguments.turf` / `const turf = arguments[0].turf` / `const placesByEntry = ...`. Those names are already in scope as function parameters; drop the redeclaration line and use them directly (`turf.bbox(placesByEntry[id])`)."},{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 — `placesByEntry`/`geometriesByEntry`/`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 (`placesByEntry`, `geometriesByEntry`, …) are already injected as parameters; drop the import lines and use them directly."},{pattern:/\b(?:functions|tools|agent|recallState|discoverPlaces|locatePlace) is not defined\b/i,hint:"The sandbox cannot call other tools — `functions.recallState(...)`, `tools.discoverPlaces(...)`, etc. are NOT available. To use additional entries, list every needed id in the tool inputs (`placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`): each is exposed via the injected per-entry sandbox args (`placesByEntry`, `routesByEntry`, `incidentsByEntry`, `geometriesByEntry`), keyed by the ids you passed. 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."}],Ke=(e,t)=>{const r=t instanceof Error?t.message:String(t),n=`${e} code execution failed: ${r}`,s=Je.find(({pattern:e})=>e.test(r));return s?`${n} Hint: ${s.hint}`:t instanceof Error&&"SyntaxError"===t.name?`${n} Hint: the provided \`code\` is not valid JavaScript — ensure it parses as an async function body and \`return\`s a value.`:n},Xe=`\n(function () {\n var SHADOWS = ${JSON.stringify([...Ue])};\n var AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;\n var provided = {\n turf: function () { return self.turf; },\n h3: function () { return self.h3; },\n routeUtils: function () { return self.routeUtils; },\n cluster: function () { return self.cluster; },\n log: function () { return function () {}; },\n };\n self.onmessage = function (event) {\n var msg = event.data;\n if (!msg || msg.type !== 'run') return;\n var id = msg.id;\n try {\n var paramNames = msg.paramNames || [];\n var dataByIndex = msg.dataByIndex || {};\n var shadows = SHADOWS.filter(function (n) { return paramNames.indexOf(n) === -1; });\n var argValues = paramNames.map(function (name, index) {\n return Object.prototype.hasOwnProperty.call(provided, name) ? provided[name]() : dataByIndex[index];\n });\n var fn = AsyncFunction.apply(null, paramNames.concat(shadows).concat([msg.code]));\n Promise.resolve(fn.apply(null, argValues.concat(shadows.map(function () { return undefined; }))))\n .then(function (value) {\n try {\n self.postMessage({ type: 'result', id: id, ok: true, value: value });\n } catch (postError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorMessage: String((postError && postError.message) || postError) });\n }\n })\n .catch(function (runError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorMessage: String((runError && runError.message) || runError) });\n });\n } catch (compileError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorMessage: String((compileError && compileError.message) || compileError) });\n }\n };\n self.postMessage({ type: 'ready' });\n})();\n`,Ze=()=>"undefined"!=typeof document&&"undefined"!=typeof Worker&&"undefined"!=typeof URL&&"undefined"!=typeof Blob,Qe=()=>import("./worker-libs-C794xj9c.js").then(e=>e.sandboxLibrarySource),et=e=>{let t=null,r=null,n=!1,s=!1,o=1;const a=/* @__PURE__ */new Map,i=e=>{s||(s=!0,console.warn(`[agent-toolkit] iframe-worker sandbox unavailable (${e}); falling back to main-thread execution (NOT isolated).`))},l=e=>{t?.contentWindow?.postMessage(e,"*")},c=e=>{if(e.source!==t?.contentWindow)return;const r=e.data;if("worker-error"===r?.type){const e=r.errorMessage??"worker error";for(const[t,r]of a)clearTimeout(r.timer),r.resolve({error:Ke(r.verb,e)}),a.delete(t);return}if("result"!==r?.type)return;const n=r,s=a.get(n.id);s&&(a.delete(n.id),clearTimeout(s.timer),s.resolve(n.ok?{value:n.value}:{error:Ke(s.verb,n.errorMessage)}))},d=e=>new Promise((r,n)=>{const s=setTimeout(()=>{globalThis.removeEventListener("message",o),n(/* @__PURE__ */new Error(`timed out waiting for "${e}"`))},2e3),o=n=>{n.source===t?.contentWindow&&n.data?.type===e&&(clearTimeout(s),globalThis.removeEventListener("message",o),r())};globalThis.addEventListener("message",o)}),u=async()=>{if(!Ze())return i("browser APIs missing"),!1;if(!e.loadLibrarySource)return i("no worker library source configured (codeExecution.loadWorkerLibrarySource)"),!1;try{const r=document.createElement("iframe");r.setAttribute("sandbox","allow-scripts"),r.setAttribute("aria-hidden","true"),r.style.display="none",r.srcdoc="<!doctype html><html><head><meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob:; worker-src blob:\"></head><body><script>\n(function () {\n var worker = null, workerSource = null;\n function spawn() {\n var blob = new Blob([workerSource], { type: 'application/javascript' });\n worker = new Worker(URL.createObjectURL(blob));\n worker.onmessage = function (e) { parent.postMessage(e.data, '*'); };\n // A worker-level error (e.g. the library source or AsyncFunction eval throwing on\n // load) isn't tied to one run id — relay it as a distinct signal so the parent can\n // fail the in-flight calls with a real message instead of letting them time out.\n worker.onerror = function (e) {\n var detail = e && e.message ? (e.message + (e.filename ? ' @ ' + e.filename + ':' + e.lineno : '')) : String(e);\n parent.postMessage({ type: 'worker-error', errorMessage: detail }, '*');\n };\n }\n window.addEventListener('message', function (ev) {\n var msg = ev.data || {};\n if (msg.type === 'init') { workerSource = msg.workerSource; spawn(); parent.postMessage({ type: 'iframe-ready' }, '*'); return; }\n if (msg.type === 'run' && worker) { worker.postMessage(msg); return; }\n if (msg.type === 'terminate' && worker) { try { worker.terminate(); } catch (_e) {} spawn(); return; }\n });\n parent.postMessage({ type: 'bootstrap-ready' }, '*');\n})();\n<\/script></body></html>",t=r;const n=d("bootstrap-ready");document.body.appendChild(r),await n;const s=(e=>`${e}\n;${Xe}`)(await e.loadLibrarySource()),o=d("iframe-ready");return l({type:"init",workerSource:s}),await o,globalThis.addEventListener("message",c),!0}catch(r){return i(r instanceof Error?r.message:String(r)),t?.remove(),t=null,!1}};return{run:async(s,i,c,d)=>{if(!(await(n?Promise.resolve(!1):(r??=u().then(e=>(e||(n=!0),e)),r)))||!t)return He.run(s,i,c,d);const p=o++,{dataByIndex:h}=((e,t)=>{const r=new Set(ze),n={};for(let s=0;s<e.length;s++)r.has(e[s])||(n[s]=t[s]);return{dataByIndex:n}})(i,c);return new Promise(t=>{const r=setTimeout(()=>{a.delete(p),l({type:"terminate"}),t({error:Ke(d,/* @__PURE__ */new Error(`code execution exceeded the ${e.timeoutMs}ms sandbox timeout and was terminated`))})},e.timeoutMs);a.set(p,{resolve:t,verb:d,timer:r});try{const e={type:"run",id:p,code:s,paramNames:[...i],dataByIndex:h};l(e)}catch(n){clearTimeout(r),a.delete(p),t({error:Ke(d,n)})}})},destroy:()=>{void 0!==globalThis.window&&globalThis.removeEventListener("message",c);for(const e of a.values())clearTimeout(e.timer);a.clear(),t?.remove(),t=null}}},tt=e=>Ze()?et({timeoutMs:e?.timeoutMs??1e4,loadLibrarySource:e?.loadWorkerLibrarySource??Qe}):He,rt=e=>null===e?"null":Array.isArray(e)?"array":typeof e,nt=(e,t)=>{if(!(e.examples.length>=3)){if("string"==typeof t){const r=t.length>60?`${t.slice(0,60)}…`:t;return void(e.examples.includes(r)||e.examples.push(r))}"number"!=typeof t&&"boolean"!=typeof t||e.examples.includes(t)||e.examples.push(t)}},st=e=>{const t=/* @__PURE__ */new Set;for(const r of e.features){const e=r.geometry?.type;e&&t.add(e)}return[...t]},ot=e=>{const t=/* @__PURE__ */new Map;for(const r of e.features){const e=r.properties;if(e&&"object"==typeof e)for(const[r,n]of Object.entries(e)){let e=t.get(r);e||(e={count:0,types:/* @__PURE__ */new Set,examples:[]},t.set(r,e)),e.count+=1,e.types.add(rt(n)),nt(e,n)}}return t},at=e=>{const t=e.features.length,r=[...ot(e).entries()].map(([e,r])=>({name:e,types:[...r.types],coverage:t>0?Math.round(r.count/t*100)/100:0,examples:r.examples})).sort((e,t)=>t.coverage-e.coverage||e.name.localeCompare(t.name)),n=r.slice(0,40),s=r.length-n.length;return{featureCount:t,geometryTypes:st(e),properties:n,...s>0&&{propertiesOmitted:s}}},it=e=>({...e,properties:e.properties.map(e=>({...e,examples:e.examples.filter(e=>"string"!=typeof e)}))}),lt=class{_ttMap;_entries=[];_entryMode="multiple";events=new le;sourceUrlValidator;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r={}){const n="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=ue(r.explicitId??`byod-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:s,timestamp:Date.now(),label:t,data:e,source:r.source??{kind:"integrator"},layers:r.layers??[],profile:at(e)}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...n,s]}),s}setEntryLayers(e,t){const r=this._requireEntry(e);r.layers=t,r._module?.applyConfig({sources:{data:{layers:t}}}),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await a.create(this._ttMap,{sources:{data:{layers:t.layers}}}),t._module}async showEntry(e){const t=this._requireEntry(e);if(0===t.layers.length)return;await this._hideOthersUnderSingleMode(e),await(await this.getEntryModule(e)).show(t.data,"data");const r=t._shown;t._shown=!0,r||this.events.emit("shown-change",this.shownEntryIds)}async _hideOthersUnderSingleMode(e){if("single"!==this._entryMode)return;const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const r of t)await this.hideEntry(r.id)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);-1!==t&&(await this.hideEntry(e),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}))}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`BYODState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._module?.clear().catch(()=>{});this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},ct=class{_trackers=/* @__PURE__ */new Map;_log=[];_jobs=/* @__PURE__ */new Map;_seq=0;events=new le;register(e){this._trackers.set(e.id,e),this.emitTrackers()}setJob(e,t){this._jobs.set(e,t)}unregister(e){this._trackers.has(e)&&(this._jobs.get(e)?.unregister(),this._jobs.delete(e),this._trackers.delete(e),this.emitTrackers())}setEnabled(e,t){const r=this._trackers.get(e);return!!r&&(r.enabled=t,this._jobs.get(e)?.setActive(t),this.emitTrackers(),!0)}get trackers(){return[...this._trackers.values()].map(e=>({...e}))}get(e){const t=this._trackers.get(e);return t&&{...t}}trackersForEntry(e){return this.trackers.filter(t=>t.affectedEntryIds.includes(e))}log(e={}){return this._log.filter(t=>(void 0===e.trackerId||t.trackerId===e.trackerId)&&(void 0===e.sinceMs||t.at>=e.sinceMs)).map(e=>structuredClone(e))}reset(){for(const e of this._jobs.values())e.unregister();this._jobs.clear(),this._trackers.clear(),this._log.length=0,this._seq=0}ingestVerdict(e,t,r){const n=this._trackers.get(e);if(n?.enabled&&dt(t))if(t.active&&!n.wasActive)n.wasActive=!0,n.openedAt=r,this.append(n,{type:"alert",kind:"opened",verdict:t,at:r});else if(!t.active&&n.wasActive){const e=n.openedAt;n.wasActive=!1,n.openedAt=void 0,this.append(n,{type:"event",kind:"resolved",verdict:t,at:r,openedAt:e})}}append(e,t){const r={id:`${e.id}:${t.kind}:${this._seq++}`,at:t.at,trackerId:e.id,trackerName:e.name,type:t.type,kind:t.kind,summary:t.verdict.summary,members:t.verdict.members.map(e=>({entryId:e.entryId,featureIds:[...e.featureIds]})),...void 0!==t.openedAt&&{openedAt:t.openedAt}};this._log.push(r);const n=this._log.filter(t=>t.trackerId===e.id);n.length>200&&this._log.splice(this._log.indexOf(n[0]),1),this.events.emit("tracker-event",r),this.emitTrackers()}emitTrackers(){this.events.emit("trackers-change",this.trackers)}};function dt(e){if("object"!=typeof e||null===e)return!1;const t=e;return"boolean"==typeof t.active&&"string"==typeof t.summary&&Array.isArray(t.members)}var ut=class{_ttMap;_entries=[];_entryMode="multiple";events=new le;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}findById(e){return this._entries.find(t=>t.id===e)}async addEntry(e,t,r,n){const s="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const o=`tta-${this._entries.length}`,a=ue(n??o,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:t,data:e,params:r}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...s,a]}),a}async getEntryModule(e){const t=this._requireEntry(e);return t._module||(t._module=await p.create(this._ttMap),t._configChangeUnsub=t._module.events.on("config-change",r=>{t._config=r,this.events.emit("config-change",{entryId:e,config:r})})),t._module}async showEntry(e){const t=this._requireEntry(e);if("single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this.hideEntry(e.id)}const r=await this.getEntryModule(e);await r.clear(),await r.show(t.data);const n=t._shown;t._shown=!0,n||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;await this.hideEntry(e);const r=this._entries[t];r._configChangeUnsub?.(),r._configChangeUnsub=void 0,r._module=void 0,this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficAreaAnalyticsState: unknown entry "${e}"`);return t}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const t of this._entries)t._configChangeUnsub?.(),t._module?.clear();this._entries=[],this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},pt=["roadworks","road-closed"];function ht(e){const t=e.properties.id;if("Point"===e.geometry.type)return ee.point(e.geometry.coordinates,{_incidentId:t});const r=ee.centroid(e);return r.properties={_incidentId:t},r}function yt(e){const t=/* @__PURE__ */new Map;for(const r of e)for(const e of r.properties.roadNumbers??[])t.set(e,(t.get(e)??0)+1);return[...t.entries()].sort((e,t)=>t[1]-e[1]).map(([e])=>e)}function mt(e){const t=/* @__PURE__ */new Map;for(const r of e){const e=r.properties.category;e&&t.set(e,(t.get(e)??0)+1)}return[...t.entries()].sort((e,t)=>t[1]-e[1])[0]?.[0]??"incident"}function ft(e,t,r,n,s){if(0===e.length)return{groups:[]};const o=function(e,t,r){if(0===r)return e;const n=new Set(t);return e.filter(e=>{const t=e.properties.category,s=e.properties.delayInSeconds??0;return!(n.has(t)&&s<r)})}(e,t.preFilter?.excludeCategories??pt,t.preFilter?.minDelaySeconds??1);if(0===o.length)return{groups:[]};const a=/* @__PURE__ */new Map;for(const w of o)a.set(w.properties.id,w);const i=o.map(ht),l=ee.featureCollection(i),c=t.eps??.5,d=t.minMembers??3,u=ee.clustersDbscan(l,c,{minPoints:d}),p=/* @__PURE__ */new Map;for(const w of u.features){const e=w.properties.cluster;if(void 0===e||e<0)continue;let t=p.get(e);t||(t=[],p.set(e,t)),t.push(w)}const h=t.maxClusters??6,y=[];for(const[,w]of p){const e=w.map(e=>e.properties._incidentId),t=e.map(e=>a.get(e)).filter(e=>void 0!==e),r=t.reduce((e,t)=>e+(t.properties.delayInSeconds??0),0),n=t.reduce((e,t)=>Math.max(e,t.properties.delayInSeconds??0),0),s=ee.featureCollection(w),o=ee.centroid(s).geometry.coordinates;let i=0;if(w.length>=2){const e=ee.bbox(s);i=ee.distance([e[0],e[1]],[e[2],e[3]])}const l=yt(t);y.push({centroid:o,memberIds:e,size:t.length,totalDelaySeconds:r,peakDelaySeconds:n,diameterKm:i,primaryRoads:l,primaryCategory:mt(t)})}y.sort((e,t)=>t.totalDelaySeconds-e.totalDelaySeconds);const m=y.slice(0,h),f=r?function(e,t){const r=t.map(e=>new Set(e.memberIds)),n=[];for(let i=0;i<e.length;i++){const s=new Set(e[i].memberIds);for(let e=0;e<t.length;e++){const t=r[e];let o=0;for(const e of s)t.has(e)&&o++;const a=s.size+t.size-o,l=0===a?0:o/a;l>=.5&&n.push({freshIdx:i,prevIdx:e,overlap:l})}}n.sort((e,t)=>t.overlap-e.overlap);const s=/* @__PURE__ */new Map,o=/* @__PURE__ */new Set,a=/* @__PURE__ */new Set;for(const{freshIdx:i,prevIdx:l,overlap:c}of n)o.has(i)||a.has(l)||(s.set(i,t[l].id),o.add(i),a.add(l));return s}(m,r):/* @__PURE__ */new Map,g=/* @__PURE__ */new Map;if(r)for(const w of r)g.set(w.id,w);const b=void 0===n||void 0===s||n>s;return{groups:m.map((e,t)=>{const r=f.get(t),n=r??`cluster-${e.centroid[0].toFixed(3)}-${e.centroid[1].toFixed(3)}-${[...e.memberIds].sort()[0]??t}`,s=r?g.get(r):void 0,o=s?.delaySamples??[],a=s?(b?[...o,e.totalDelaySeconds]:[...o.slice(0,-1),e.totalDelaySeconds]).slice(-3):[e.totalDelaySeconds],i=1===a.length?"new":function(e){if(e.length<3)return"steady";const[t,r,n]=e.slice(-3),s=(n-t)/Math.max(t,1);return r>t&&n>r&&s>.1?"growing":r<t&&n<r&&s<-.1?"fading":"steady"}(a);return{id:n,centroid:e.centroid,memberIds:e.memberIds,size:e.size,totalDelaySeconds:e.totalDelaySeconds,peakDelaySeconds:e.peakDelaySeconds,diameterKm:e.diameterKm,primaryRoads:e.primaryRoads,primaryCategory:e.primaryCategory,trend:i,delaySamples:a}})}}var gt=(e,t={},r,n)=>{const s=(e=>{if(null!=e)return e instanceof Date?e.getTime():e})(n),{groups:o}=ft(e,t,r?.groups,s,r?.sampledAt);return void 0===s?{groups:o}:{groups:o,sampledAt:s}},bt=class{_bbox;events=new le;_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,n=!1){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;n||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}},wt=class{_ttMap;_entries=[];_entryMode="multiple";_clusters=/* @__PURE__ */new Map;events=new le;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.slice(0,-1).map(e=>e.id);this._entries=await he(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}this.events.emit("mode-change",e)}}get shownEntryIds(){const e=/* @__PURE__ */new Set;for(const t of this._entries)t._shown&&e.add(t.id);return e}async addIncidentsEntry(e,t,r,n,s){"single"===this._entryMode&&this._entries.length>0&&(await pe(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this._entries=[]);const o=ue(s??`incidents-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:o,timestamp:n,label:r,data:e,params:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[o]}),o}async replaceEntryData(e,t,r){const n=this._entries.find(t=>t.id===e);if(!n)return!1;const s=(n._tick??0)+1;if(n._tick=s,n.data=t,n.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n._module){if(await n._module.show({type:"FeatureCollection",features:t}),n._tick!==s)return!0;if(n._focus){const e=new Set(t.map(e=>e.properties.id)),r=[...n._focus.ids].filter(t=>e.has(t));n._module.setFocus(r.length>0?r:null),0===r.length&&(n._focus=void 0,this.events.emit("focus-change",{entryId:n.id,focus:null}))}}return!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await y.create(this._ttMap),t._module}async showEntry(e,t=!0){const r=this._requireEntry(e),n=await this.getEntryModule(e);await n.show({type:"FeatureCollection",features:r.data}),n.setVisible(!0),t&&this._ttMap.mapLibreMap.fitBounds(r.params.bbox,{padding:50,maxZoom:14});const s=!!r._shown;r._shown=!0,s||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(t._focus&&(t._focus=void 0,this.events.emit("focus-change",{entryId:t.id,focus:null})),t._module?.setVisible(!1),await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;const r=this._entries[t],n=!!r._shown;await this._teardownEntry(r),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),n&&this.events.emit("shown-change",this.shownEntryIds)}setClusters(e,t){this._entries.some(t=>t.id===e)&&(this._clusters.set(e,t),this.events.emit("clusters-change",{entryId:e,clusters:t.output}))}getClusters(e){return this._clusters.get(e)?.output}getClustersRecord(e){return this._clusters.get(e)}clearClusters(e){this._clusters.delete(e)&&this.events.emit("clusters-change",{entryId:e,clusters:null})}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 n=this._entries.find(t=>t.id===e);if(!n)return{focusedCount:0,droppedIds:[...t]};if(0===t.length)return this.clearFocus(e),{focusedCount:0,droppedIds:[]};const s=new Set(n.data.map(e=>e.properties.id)),o=[],a=[];for(const i of t)s.has(i)?o.push(i):a.push(i);return 0===o.length?{focusedCount:0,droppedIds:a}:(n._focus={ids:new Set(o),reason:r},n._module?.setFocus(o),this.events.emit("focus-change",{entryId:n.id,focus:this.getFocus(e)}),{focusedCount:o.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 s of t.data){const e=s.properties.id;"string"==typeof e&&(r[e]=s)}const n=[];for(const s of t._focus.ids){const e=r[s];e&&n.push(e)}return n}navigateFocus(e,t){const r=this.focusedFeatures(e);if(0===r.length)return;const n=r[(t%r.length+r.length)%r.length],s=this._ttMap.mapLibreMap;if("Point"===n.geometry.type)return void s.flyTo({center:n.geometry.coordinates,zoom:15,duration:600});const o=q(n);o&&s.fitBounds(o,{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,n){const s=this._requireEntry(e),o=s._monitor??=this._createMonitorForEntry(s),a=o.isRunning;o.start(t,r,n?.intervalMs,n?.skipInitialTick),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 bt(e.params.bbox);return t.events.on("tick",t=>{this.replaceEntryData(e.id,t.incidents,t.takenAt),this.events.emit("monitor-tick",{entryId:e.id,snap:t})}),t.events.on("error",t=>{this.events.emit("monitor-error",{entryId:e.id,error:t}),this.events.emit("monitor-stop",{entryId:e.id,reason:"error"})}),t}_requireEntry(e){const t=this._entries.find(t=>t.id===e);if(!t)throw new Error(`TrafficIncidentsState: unknown entry "${e}"`);return t}async _teardownEntry(e){this.clearClusters(e.id),this.stopMonitoring(e.id),e._focus&&(e._focus=void 0,this.events.emit("focus-change",{entryId:e.id,focus:null})),e._shown&&(e._shown=!1),e._module&&(e._module.setVisible(!1),await e._module.clear()),e._module=void 0}async clearShown(){for(const e of this.shownEntryIds)await this.hideEntry(e)}reset(){const e=this._entries.map(e=>e.id);for(const r of this._entries)this._teardownEntry(r);const t=this._entries.length>0;this._entries=[],t&&(this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)),this.events.clear()}},vt=class{_ttMap;_trafficFlowModule;_trafficIncidentsModule;constructor(e){this._ttMap=e}async getTrafficFlowModule(){return this._trafficFlowModule=await h.get(this._ttMap),this._trafficFlowModule}async getTrafficIncidentsModule(){return this._trafficIncidentsModule=await m.get(this._ttMap),this._trafficIncidentsModule}get trafficFlowModule(){return this._trafficFlowModule}get trafficIncidentsModule(){return this._trafficIncidentsModule}reset(){this._trafficFlowModule=void 0,this._trafficIncidentsModule=void 0}};N.instance.put({language:"en-GB"});var It={places:"places",routes:"routing",incidents:"trafficIncidents",customGeometries:"customGeometries",trafficAreaAnalytics:"trafficAreaAnalytics",byod:"byod",ranges:"ranges"},Et=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),St=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),kt={identity:null,capabilities:null,rejectionRules:"SCOPE & REJECTIONS",responseFormatting:"RESPONSE FORMATTING",dataConfidence:"DATA CONFIDENCE",toolExecution:"TOOL EXECUTION",sessionState:"SESSION STATE"},Mt={identity:"You are a helpful map assistant.",capabilities:"You operate a TomTom interactive map with location services. You can search places, POIs, and addresses (and reverse-geocode); plan and edit multi-stop routes (alternatives, cost models); compute reachable-area (isochrone) ranges; load live traffic incidents and historical traffic analytics, then cluster, monitor, or focus them; run spatial analysis, aggregations, and charts over loaded data and derive geometries; add your own GeoJSON layers; and control the map — pan, zoom, pitch/bearing, styles, base-map tiles, and show/hide results.",rejectionRules:"- Handle the in-scope part fully, then note what you can't help with.\n- Fully out of scope: decline and describe what you cover — don't guess the user's need.\n- Flag scope creep as it arises, not at the end.\n- Reject illegal requests outright with a brief reason and no detail — no partial help, rephrasing, or redirecting.\n- Every rejection states why and offers an in-scope next step — never a dead end.",responseFormatting:"- Use markdown; bold key info; bullet lists for multiple items.\n- Be concise — lead with the most relevant information.",dataConfidence:"- Return incomplete, outdated, or low-coverage data with a clear note on what's limited and why — never present it as complete.\n- If only partial, name what's missing and offer to proceed — let the user decide.\n- If signals conflict, acknowledge it, state what you used, and why.",toolExecution:'- Run independent tools in the same step (parallel); render results with the tool\'s own show / showOnMap in that step, not a follow-up call.\n- Ground every fact and effect in a tool, not your training data: anything a tool can supply or do (an address, a count, a category, a list, any map change) is real ONLY when you call that tool — even when the request reads like a general or explanatory question ("what categories represent X?"). Without the call, never reply as if a result exists, the map changed, or an action is underway ("here are the results", "Profiling X…", "the panel will show…"); the call IS the action, so make it.\n- The ONE exception is ANSWERING from what you already have: you may reply in prose, with no tool call, from what is ALREADY in this conversation (earlier tool results, session state, what the user told you) or about your own capabilities ("what can you do?"). Using what you have is fine; inventing what a tool would return is not.\n- Needing MORE input is not that exception: for a clear, complete request, act with sensible defaults (do not ask); when input is missing, implied, or ambiguous, your ONLY way to ask the user is to CALL clarifyIntent. A plain-text request for the details — "I need…", "please provide…", "could you tell me…", or a bare list of questions — without that call does NOT count; call clarifyIntent (it then states whether to also write the questions as text).',sessionState:"- Results are stored as stable, append-only entries (e.g. `places-3`, `routes-1`) that every show / recall / process / analyse tool accepts. Call recallState to see what's loaded; always check it before referencing an entry id — never invent ids."},At=["identity","capabilities","rejectionRules","responseFormatting","dataConfidence","toolExecution","sessionState"],xt=e=>At.map(t=>({key:t,body:e?.[t]??Mt[t]})).filter(({body:e})=>e.trim().length>0).map(({key:e,body:t})=>((e,t)=>{const r=kt[e];return r?`${r}:\n${t}`:t})(e,t)).join("\n\n"),Dt=xt(),Tt=({customPrompt:e,prefix:t,suffix:r}={})=>{if("string"==typeof e)return e;const n=e?xt(e):Dt,s=r?`${n}\n\nADDITIONAL INSTRUCTIONS:\n${r}`:n;return t?`${t}\n\n${s}`:s},Pt=te.object({error:te.string()}),_t=te.object({featureCount:te.number(),geometryTypes:te.array(te.string()).describe("Geometry types present (Point / LineString / Polygon / …)."),properties:te.array(te.object({name:te.string(),types:te.array(te.string()).describe('Distinct JSON value types seen for this key (e.g. ["string"], ["number","null"]).'),coverage:te.number().describe("Fraction of features carrying this key (1 = every feature)."),examples:te.array(te.union([te.string(),te.number(),te.boolean()])).describe("A few example NUMERIC / BOOLEAN values for inferring semantics. String values are withheld from results (customer-supplied free text — a prompt-injection vector); use `types` to know a property is a string.")})).describe("Per-property profile, highest-coverage first."),propertiesOmitted:te.number().optional().describe("Count of property keys omitted when the data has more distinct keys than the cap.")}),Ot=te.array(te.object({query:te.string().optional().describe("Your input query text, echoed."),matched:te.string().describe("The GROUNDED place the data was actually loaded for.")})).describe('Where each named `where` query ACTUALLY resolved. `matched` is the grounded geocoded place the data was loaded for — NOT your query text. ALWAYS check it is the place the operator meant: a same-name place elsewhere ("east London" → "London, CA") silently loads the wrong area. If `matched` is wrong, re-issue with a more specific query. Omitted for raw bounding boxes / geometries (nothing was geocoded).'),Ct={id:te.string().describe("Stable place feature ID; pass as `{ placeIdOrEntryId }` to setRoute / addWaypointsToRoute / etc."),type:te.enum(K).optional().describe("Place classification (POI, Street, Geography, Cross Street, …), when known."),name:te.string().optional().describe("POI name if available"),address:te.string().optional().describe("Human-readable address"),position:te.array(te.number()).length(2).describe("[longitude, latitude]")},Rt=e=>te.object(Ct).describe("Compact place summary"),jt=e=>te.object({count:te.number(),features:te.array(Rt())}).describe("Compact places summary"),Nt=Rt(),Bt=(jt(),te.object({travelTimeInSeconds:te.number(),lengthInMeters:te.number(),trafficDelayInSeconds:te.number().optional(),trafficLengthInMeters:te.number().optional(),departureTime:te.string().optional(),arrivalTime:te.string().optional(),noTrafficTravelTimeInSeconds:te.number().optional(),historicTrafficTravelTimeInSeconds:te.number().optional(),liveTrafficIncidentsTravelTimeInSeconds:te.number().optional()})),Lt=te.object({index:te.number().describe("0=best, 1+=alternative"),summary:Bt,legSummaries:te.array(Bt).optional().describe("Present when route has multiple legs")}),Ft=te.object({count:te.number(),routes:te.array(Lt)}),qt=Ft.extend({entryId:te.string().describe("Routing entry id the route was written to — pass as `routesEntryIDs` to analyseData / processData.")}),$t=te.object({geoJson:te.union([te.record(te.string(),te.unknown()),te.array(te.record(te.string(),te.unknown()))]).describe("GeoJSON object or array of GeoJSON objects")}),Gt=te.union([te.object({bbox:te.array(te.number()).length(4).describe("[minLng, minLat, maxLng, maxLat]"),minLng:te.number(),minLat:te.number(),maxLng:te.number(),maxLat:te.number()}),Pt]),Wt=te.object({id:te.string().describe('Short stable key for the answer, e.g. "travelMode" or "radius".'),question:te.string().describe("The question to put to the user."),options:te.array(te.string()).min(2).max(8).describe('Concrete suggestions, shown as numbered rows the user taps to answer. A freeform "Something else" input is always shown too, so do NOT add your own "other"/"something else" option — just offer your best concrete suggestions.'),multiSelect:te.boolean().default(!1).describe('Allow more than one answer: tapping toggles options (checked) and the user confirms with the send button — use for lists like "which place types matter". Default false = single-select, where tapping an option answers and advances immediately. Each question carries its own flag, so a single call may freely mix single- and multi-select questions.')}),Ut=te.object({intro:te.string().optional().describe("One short line of context for why you need to ask."),questions:te.array(Wt).min(1).max(4).describe("1-4 questions that resolve the ambiguity.")}),zt=te.object({status:te.literal("awaiting_user_response"),intro:te.string().optional(),questions:te.array(Wt)}),Ht='- When a request is missing inputs you need to act on, do NOT call the action tool with guessed or empty inputs, and do NOT ask for the missing details in prose — call clarifyIntent FIRST to collect them, then STOP and wait. Asking in a sentence when you could offer a form is the most common mistake; default to the form.\n- Any question whose answer is a choice among a few concrete options IS a clarifyIntent form, even just two or three. If you catch yourself about to ask "A, B, or C?" in text, call clarifyIntent with those as options instead.\n- A clarifyIntent call must be the ENTIRE turn: one tool call with EMPTY text content — no preamble, and NEVER restate the questions or options in the message (the form already shows them). Put any one-line context in the form\'s `intro` field, and give each question a few concrete options.\n- One call can carry several related questions (the form shows them one at a time). Fall back to a prose question ONLY when the answer is genuinely free-form with no suggestable options.',Yt='- When a request is missing inputs you need to act on, do NOT call the action tool with guessed or empty inputs — call clarifyIntent FIRST to collect them, then STOP and wait for the answer.\n- Any question whose answer is a choice among a few concrete options belongs in clarifyIntent, even just two or three; offer your best concrete suggestions as its options.\n- After calling clarifyIntent, present the questions as text in your reply: one short line of context, then a numbered markdown list of each question with its options (e.g. "1. How are people getting there? — Walking / Driving / Public transport"), then STOP and wait. This lets the user read and answer even when the host does not render a form. (Echoing the tool\'s own returned questions is presenting a result, not narrating — do it.)\n- One call can carry several related questions. Fall back to a single prose question ONLY when the answer is genuinely free-form with no suggestable options.',Vt=['clarifyIntent({ intro: "A couple of details first.", questions: [{ id: "travelMode", question: "How are people getting there?", options: ["Walking", "Driving", "Public transport"] }] })','clarifyIntent({ questions: [{ id: "priorities", question: "What matters most?", options: ["Speed", "Cost", "Comfort"], multiSelect: true }] })'],Jt=["Plan a route for me","Help me decide between these options"],Kt=async(e,t)=>({status:"awaiting_user_response",intro:e.intro,questions:e.questions}),Xt=(e={})=>({description:`${e.description??"Ask the user to resolve a genuinely ambiguous or missing input before acting — collects a few discrete choices as a short set of questions (numbered suggestions plus a freeform answer). Use it ONLY when you cannot proceed sensibly: a required input is missing with no reasonable default, or the request is genuinely ambiguous (e.g. an unclear concept, or which option matters most). Do NOT use it for a clear, actionable request — prefer acting with sensible defaults and stating them. Offer your best concrete suggestions. Batching a few related questions in a single call is fine; put any context in the `intro` field. After calling it, STOP and wait for the user — do not call other tools or answer until they reply."}\n\n${e.rendersForm?Ht:Yt}`,classificationPrompt:e.classificationPrompt??"The request is under-specified or ambiguous and needs the user to choose among discrete options before the task can proceed; renders the questions as an in-chat survey form.",inputSchema:Ut,outputSchema:zt,execute:Kt,tags:["utilities"],examples:e.examples??Vt,examplePrompts:e.examplePrompts??Jt,alwaysActive:e.alwaysActive??!0,endsTurnOnCall:e.rendersForm??!1}),Zt=te.object({}),Qt=te.union([te.object({position:te.array(te.number()).min(2).max(2).describe("[longitude, latitude]"),accuracy:te.number().describe("Accuracy of the location in meters"),timestamp:te.number().describe("Timestamp of the location data")}),Pt]),er=["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"],tr=te.object({name:te.string(),description:te.string(),examplePrompts:te.array(te.string()),relatedTools:te.array(te.string()).optional()}),rr=te.union([te.object({mode:te.literal("summary"),examplePrompts:te.array(te.string())}),te.object({mode:te.literal("detail"),query:te.string().optional(),tag:te.enum(er).optional(),matchedTools:te.array(tr),totalMatched:te.number()}),Pt]),nr=te.object({mode:te.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:te.string().optional().describe("Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags."),tag:te.enum(er).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),sr='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.',or=(e,t,r,n,s)=>({description:t.description,inputSchema:t.inputSchema,outputSchema:n?t.outputSchema:void 0,execute:s?async(n,o)=>{const a=Date.now();try{const i=await t.execute(n,r,{signal:o?.abortSignal}),l=(e=>{const t=e?.error;return"string"==typeof t?t:void 0})(i);return s({toolName:e,durationMs:Date.now()-a,isError:void 0!==l,errorMessage:l}),i}catch(i){throw s({toolName:e,durationMs:Date.now()-a,isError:!0,errorMessage:i instanceof Error?i.message:String(i)}),i}}:(e,n)=>t.execute(e,r,{signal:n?.abortSignal})}),ar=(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,alwaysActive:t.alwaysActive,endsTurnOnCall:t.endsTurnOnCall,scopePrompt:t.scopePrompt}),ir=(e,t)=>"function"==typeof e?e(t):e,lr=(e,t,r)=>{const n=!1!==r?.outputSchemas,s={featureFlags:r?.featureFlags,enabledDataKinds:r?.enabledDataKinds},o={},a={},i={};for(const[c,d]of Object.entries(e)){const e=ir(d,s);if(o[c]=or(c,e,t,n,r?.onToolExecute),a[c]=ar(c,e),"function"==typeof d&&e.scopeSchema){const t=d,r=e.scopeSchema;i[c]={applyScope:e=>{const n=r.safeParse(e);if(!n.success)return null;try{const e=t({...s,scope:n.data});return{description:e.description,inputSchema:e.inputSchema}}catch{return null}},defaultDescription:e.description,defaultInputSchema:e.inputSchema}}}var l;if(o.help&&(o.help=(l=()=>a,{description:sr,inputSchema:nr,outputSchema:rr,execute:async e=>{const{mode:t,query:r,tag:n}=e;try{const e=Object.values(l());if("summary"===t)return{mode:"summary",examplePrompts:e.flatMap(e=>e.examplePrompts??[])};let s=e;if(n&&(s=s.filter(e=>e.tags?.includes(n)??!1)),r){const e=r.toLowerCase();s=s.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:n,matchedTools:s.map(e=>({name:e.name,description:e.description,examplePrompts:e.examplePrompts??[],relatedTools:e.relatedTools??[]})),totalMatched:s.length}}catch(s){return{error:`Failed to load help: ${s instanceof Error?s.message:String(s)}`}}}})),0===Object.keys(o).length)throw new Error("MapAgent requires at least one tool.");return{tools:o,toolsMetadata:a,scopableTools:i}},cr=te.discriminatedUnion("kind",[te.object({kind:te.literal("place").describe('Use the exact literal "place".'),id:te.string().min(1).describe("Id of a single place; resolves to that place’s footprint.")}).describe("A single place footprint."),te.object({kind:te.literal("places").describe('Use the exact literal "places".'),id:te.string().min(1).describe("Id of a places-entry; resolves to every footprint in the entry.")}).describe("Every footprint in a places entry."),te.object({kind:te.literal("ranges").describe('Use the exact literal "ranges".'),id:te.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."),te.object({kind:te.literal("customGeometries").describe('Use the exact literal "customGeometries".'),id:te.string().min(1).describe("Id of a custom-geometries entry produced by a previous `processData` call.")}).describe("A derived entry produced by a previous `processData` call.")]).describe('Tagged id `{ kind, id }`. `kind` must be exactly one of: "place" | "places" | "ranges" | "customGeometries" (do NOT use variants like "ranges-entry", "place-footprints", etc.). Use `recallState` to list every available `{kind, id}` pair.'),dr=te.array(cr).min(1).describe('Tagged ids `{ kind, id }` of polygon sources to feed the run. `kind` must be exactly one of `"place"` | `"places"` | `"ranges"` | `"customGeometries"` (no variants). Mix kinds freely (e.g. union place footprints with isochrones). Use `recallState` to list available ids.'),ur="`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.",pr="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",hr="Tagged input ids that contributed at least one polygon to the run, in input order.",yr="Each `geometriesByEntry[key]` (key = `${kind}:${id}`) is an array of Polygon/MultiPolygon Features. Each has `id` (the geometry-data-source id, matches `place.properties.dataSources.geometry.id`) and `bbox`. `properties` varies by source — see `GEOMETRIES_PROPS_DOC`. Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, `turf.buffer`, … directly on these features. For h3, convert via `h3.polygonToCells(feature.geometry.coordinates, res)`.",mr=e=>'Each `placesByEntry[id]` is a FeatureCollection of Point Features. `Place.properties` shape:\n```ts\ntype PlaceProperties = {\n type: "POI" | "Street" | "Geography" | "Point Address" | "Address Range" | "Cross Street";\n address: {\n freeformAddress: string;\n streetNumber?: string; streetName?: string;\n municipality?: string; municipalitySubdivision?: string;\n countrySubdivision?: string; countrySubdivisionName?: string;\n countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\n postalCode?: string; extendedPostalCode?: string;\n countryCode?: string; countryCodeISO3?: string; country?: string;\n localName?: string;\n };\n poi?: { // when type === "POI"\n name: string; phone?: string; url?: string;\n brands?: string[];\n categories: string[]; // POICategory enum, e.g. "RESTAURANT", "CAFE_PUB", "GAS_STATION", "HOTEL_MOTEL", "ELECTRIC_VEHICLE_STATION"\n localizedCategories: string[]; // language-sensitive\n openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\n timeZone?: { name: string };\n };\n // Search hits also carry:\n score?: number; // relevance (higher = better)\n distance?: number; // meters from geoBias, when provided\n info?: string;\n // Geography hits also carry:\n geographyType?: Array<"Country"|"CountrySubdivision"|"CountrySecondarySubdivision"|"CountryTertiarySubdivision"|"Municipality"|"MunicipalitySubdivision"|"Neighbourhood"|"PostalCodeArea">;\n // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\n};\n```\nAreas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`. The fields above describe places you READ from input. When your output is a filter / subset / merge of the input places, return the source features as-is (spread them: `{ ...place }`, or `{ ...place, properties: { ...place.properties } }` when editing) so their `id` and all their fields ride through — carrying real fields forward is NOT the same as inventing them, keep them. Only when you RETURN a freshly computed point (one with no source place behind it) are just `type`, a display name at the schema path for that type (`poi.name` for POIs, `address.freeformAddress` for streets / intersections (`Cross Street`) / addresses), and Point coordinates required. For those computed points do NOT invent a flat `properties.name` (it is dropped, so the name would be lost), and do NOT fabricate the search / POI extras (`categories`, `localizedCategories`, `score`, `distance`, `info`, …) — a computed point has no such data behind it.',fr='Each `placesByEntry[id]` is a FeatureCollection of Point Features. `Place.properties` shape:\n```ts\ntype PlaceProperties = {\n type: "POI" | "Street" | "Geography" | "Point Address" | "Address Range" | "Cross Street";\n address: {\n freeformAddress: string;\n streetNumber?: string; streetName?: string;\n municipality?: string; municipalitySubdivision?: string;\n countrySubdivision?: string; countrySubdivisionName?: string;\n countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\n postalCode?: string; extendedPostalCode?: string;\n countryCode?: string; countryCodeISO3?: string; country?: string;\n localName?: string;\n };\n poi?: { // when type === "POI"\n name: string; phone?: string; url?: string;\n brands?: string[];\n categories: string[]; // POICategory enum, e.g. "RESTAURANT", "CAFE_PUB", "GAS_STATION", "HOTEL_MOTEL", "ELECTRIC_VEHICLE_STATION"\n localizedCategories: string[]; // language-sensitive\n openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\n timeZone?: { name: string };\n };\n // Search hits also carry:\n score?: number; // relevance (higher = better)\n distance?: number; // meters from geoBias, when provided\n info?: string;\n // Geography hits also carry:\n geographyType?: Array<"Country"|"CountrySubdivision"|"CountrySecondarySubdivision"|"CountryTertiarySubdivision"|"Municipality"|"MunicipalitySubdivision"|"Neighbourhood"|"PostalCodeArea">;\n // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\n};\n```\nAreas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`. The fields above describe places you READ from input. When your output is a filter / subset / merge of the input places, return the source features as-is (spread them: `{ ...place }`, or `{ ...place, properties: { ...place.properties } }` when editing) so their `id` and all their fields ride through — carrying real fields forward is NOT the same as inventing them, keep them. Only when you RETURN a freshly computed point (one with no source place behind it) are just `type`, a display name at the schema path for that type (`poi.name` for POIs, `address.freeformAddress` for streets / intersections (`Cross Street`) / addresses), and Point coordinates required. For those computed points do NOT invent a flat `properties.name` (it is dropped, so the name would be lost), and do NOT fabricate the search / POI extras (`categories`, `localizedCategories`, `score`, `distance`, `info`, …) — a computed point has no such data behind it.',gr='Each `routesByEntry[id]` is a FeatureCollection of LineString Features (each route has `feature.bbox`). `Route.properties` shape:\n```ts\ntype RouteProperties = {\n index: number; // 0 = main route, 1+ = alternatives\n summary: {\n lengthInMeters: number;\n travelTimeInSeconds: number; // includes traffic delay\n noTrafficTravelTimeInSeconds?: number;\n trafficDelayInSeconds: number;\n trafficLengthInMeters: number;\n departureTime: Date; arrivalTime: Date;\n // EV-only:\n batteryConsumptionInkWh?: number; batteryConsumptionInPCT?: number;\n remainingChargeAtArrivalInkWh?: number; remainingChargeAtArrivalInPCT?: number;\n totalChargingTimeInSeconds?: number;\n // Combustion-only:\n fuelConsumptionInLiters?: number;\n };\n // Segments grouped by characteristic. Every section has { id, startPointIndex, endPointIndex }\n // unless noted; *Index values reference `geometry.coordinates`.\n sections: {\n leg: Array<{ id; startPointIndex?; endPointIndex?; summary: RouteSummary; originalWaypointIndex?: number }>; // ALWAYS present\n // originalWaypointIndex = which requested stop the leg arrives at; absent on the last leg and on service-inserted EV charging stops\n country?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // ISO 3166-1 alpha-3\n tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>;\n traffic?: Array<{\n id; startPointIndex; endPointIndex;\n categories: TrafficIncidentCategory[]; // e.g. "Jam","Accident","RoadWorks","Closure"\n magnitudeOfDelay: "Unknown"|"Minor"|"Moderate"|"Major"|"Undefined";\n tec: { effectCode?: number; causes?: Array<{ mainCauseCode: number; subCauseCode?: number }> };\n effectiveSpeedInKmh?: number;\n delayInSeconds?: number;\n eventId?: string; // join key to the traffic incident details service\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 tollRoad?: SectionProps[]; // costs money by ANY scheme: every `toll` stretch, plus vignette motorways and city charge zones\n unpaved?: SectionProps[]; urban?: SectionProps[]; pedestrian?: SectionProps[];\n carpool?: SectionProps[]; carTrain?: SectionProps[];\n lowEmissionZone?: SectionProps[]; vehicleRestricted?: SectionProps[];\n };\n progress?: Array<{ pointIndex: number; travelTimeInSeconds?: number; distanceInMeters?: number }>; // cumulative milestones\n guidance?: { instructions: Array<{\n pathPointIndex: number; maneuver: string;\n message?: string; // ready-made localised text ("Turn right onto Damrak") — prefer it over mapping `maneuver` yourself\n nextRoadInfo: { streetName?: { text: string; phonetic?: string }; countryCode?: string };\n roundaboutType?: "REGULAR"|"SMALL";\n maneuverView?: { onRouteAngle?: ManeuverAngle; offRouteAngles: ManeuverAngle[] }; // relative directions ("RIGHT","SLIGHT_LEFT","BACK"), not degrees\n sideRoads?: Array<{ side: "LEFT"|"RIGHT"|"LEFT_AND_RIGHT"; offsetFromManeuverInMeters: number; isDrivable?: boolean }>;\n }> };\n};\n```\n`pathPointIndex` and `pointIndex` values reference positions in `geometry.coordinates`. Guard `?` fields with `?.` / `??`. For "how long / how far" questions, use `summary.lengthInMeters` / `summary.travelTimeInSeconds`.',br=e=>Number.isFinite(e)&&e>=-180&&e<=180,wr=e=>Number.isFinite(e)&&e>=-90&&e<=90,vr="Position must be [lng, lat] with lng in [-180, 180] and lat in [-90, 90].",Ir=te.array(te.number()).length(2).refine(([e,t])=>br(e)&&wr(t),{message:vr}),Er=te.array(te.number()).min(2).max(3).refine(([e,t])=>br(e)&&wr(t),{message:vr}),Sr=te.array(te.number()).length(4).refine(([e,t,r,n])=>br(e)&&wr(t)&&br(r)&&wr(n),{message:"BBox values out of range — lng must be in [-180, 180] and lat in [-90, 90]."}).refine(([,e,,t])=>e<=t,{message:"BBox minLat must be <= maxLat."}).describe("[minLng, minLat, maxLng, maxLat] or [W,S,E,N]. lng in [-180, 180], lat in [-90, 90]."),kr=te.object({type:te.literal("Polygon"),coordinates:te.array(te.array(Er))}),Mr=te.object({type:te.literal("MultiPolygon"),coordinates:te.array(te.array(te.array(Er)))}),Ar=te.union([kr,Mr]),xr=({verb:e,extra:t})=>te.array(te.string()).min(1).optional().describe(`IDs of existing places entries to ${e} (e.g. ["places-2"], ["places-1", "places-2"]). Use recallState to list IDs. Omit to ${e} the latest entry.`+(t?` ${t}`:"")),Dr=({verb:e,extra:t})=>te.array(te.string()).min(1).optional().describe(`IDs of existing routes entries to ${e} (e.g. ["routes-0"], ["routes-0", "routes-1"]). Use recallState to list IDs. Omit to ${e} the latest route.`+(t?` ${t}`:"")),Tr=te.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`.'),Pr=te.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).'),_r=te.object({show:te.boolean().optional().describe("Also render the polygons on the map. Default: false."),mode:Pr,theme:te.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).'),Or=te.object({theme:te.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline".'),placeIds:te.array(te.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:Pr}).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.'),Cr=te.enum([...Ae,"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.'),Rr=e=>te.union([te.literal("all"),te.array(te.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).`),jr=te.object({markerType:Cr,zoomMode:te.enum(["auto","none"]).describe('Camera behaviour. "none" keeps the map still.'),hidePreviousEntries:Rr("places")}),Nr=te.object({markerType:te.boolean(),zoomMode:te.boolean()}),Br=te.object({zoomMode:te.enum(["auto","none"]).describe('Camera behaviour. "auto" fits the map to the shown BYOD entries; "none" keeps the map still.'),hidePreviousEntries:Rr("BYOD")}),Lr=te.object({zoomMode:te.boolean().describe("Whether the camera was moved to fit the shown entries.")}),Fr={mode:te.literal("within"),viewport:te.literal(!0).optional().describe("When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.")},qr=te.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".'),$r=["boundingBox","viewport","query","placeId"],Gr=te.object({...Fr,boundingBox:Sr.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId."),query:te.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:qr,placeId:te.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===$r.filter(t=>void 0!==e[t]).length,{message:"`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId."}),Wr=["position","viewport","query"],Ur=te.object({mode:te.literal("nearby"),position:Ir.optional().describe("Bias point [lng, lat]. EXCLUSIVE with viewport / query."),viewport:te.literal(!0).optional().describe("When true, bias toward the current map viewport centre. EXCLUSIVE with position / query."),query:te.string().optional().describe("Resolve a name/address/POI → its centroid as bias point. Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport."),queryAs:qr,radiusMeters:te.number().positive().optional().describe("Hard radius cap (metres) around the bias point. Independent of bias source.")}).refine(e=>1===Wr.filter(t=>void 0!==e[t]).length,{message:"`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent)."}),zr=te.object({mode:te.literal("global").describe("No spatial constraint — resolve the name anywhere. The default for a uniquely-named place.")}),Hr=te.union([Gr,Ur,zr]),Yr={...Fr,queries:te.array(te.object({query:te.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:qr})).min(1).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:te.array(te.string()).min(1).optional().describe('IDs of session places — a places ENTRY id (e.g. "places-0", or the `placesEntryId` returned by locatePlace) expands to every place it holds, or a single place id. Each resolved polygon (fetched on demand) joins the search area. Use recallState to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),geometries:te.array(Ar).min(1).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:te.string().min(1).optional().describe('Range ID from findReachableAreas (e.g. "ranges-0") — restricts to that entry; multi-origin entries combine their polygons. Use recallState to list IDs. EXCLUSIVE with viewport; composes with other multi-region fields.'),route:te.object({routeId:te.string().min(1).optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),widthMeters:te.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. EXCLUSIVE with viewport; composes with other multi-region fields.")},Vr=te.object({viewport:te.boolean().optional(),boundingBox:Sr.optional(),queries:Yr.queries,placeIds:Yr.placeIds,geometries:Yr.geometries,route:Yr.route}),Jr=["places","routes","incidents","customGeometries","trafficAreaAnalytics","byod"],Kr=e=>te.object({kinds:te.array(te.enum(e)).min(1)}),Xr={places:{fieldName:"placesEntryIDs",fieldLabel:"`placesEntryIDs`",buildField:e=>xr({verb:e.verb,extra:"Injected as `placesByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. "+e.requiredHint}),sandboxDoc:"• `placesByEntry` — object keyed by `placesEntryIDs` id; each value a FeatureCollection of that entry's places. Merge all entries with `Object.values(placesByEntry).flatMap(fc => fc.features)`.",schemaDoc:fr,recallTool:"recallState({ kind: 'places' })"},routes:{fieldName:"routesEntryIDs",fieldLabel:"`routesEntryIDs`",buildField:e=>Dr({verb:e.verb,extra:"Injected as `routesByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. "+e.requiredHint}),sandboxDoc:"• `routesByEntry` — object keyed by `routesEntryIDs` id; each value a FeatureCollection of that entry's Route LineStrings. Merge all entries with `Object.values(routesByEntry).flatMap(fc => fc.features)`. On-route incidents live in `f.properties.sections.traffic[]` (each `{ categories: string[], magnitudeOfDelay, delayInSeconds }`) — there is NO `f.properties.incidents`. Route totals are on `f.properties.summary` (`travelTimeInSeconds`, `trafficDelayInSeconds`).",schemaDoc:gr,recallTool:"recallState({ kind: 'routes' })"},incidents:{fieldName:"incidentsEntryIDs",fieldLabel:"`incidentsEntryIDs`",buildField:e=>te.array(te.string()).min(1).optional().describe(`IDs of existing incidents entries to ${e.verb} (e.g. ["incidents-0"]). Injected as \`incidentsByEntry[id]\` (a TrafficIncident[] per entry id). Undefined when omitted. Use \`recallState({ kind: 'incidents' })\` to list incident entries. `+e.requiredHint),sandboxDoc:"• `incidentsByEntry` — object keyed by `incidentsEntryIDs` id; each value a flat `TrafficIncident[]` for that entry. Merge all entries with `Object.values(incidentsByEntry).flat()`.",schemaDoc:'Each `incidentsByEntry[id]` is a flat array of TrafficIncident Features (geometry is `Point` OR `LineString` — guard with `feature.geometry.type` before line-only ops). All fields below live on `feature.properties`:\n```ts\ntype TrafficIncidentProperties = {\n id: string;\n category: TrafficIncidentCategory; // "jam" | "accident" | "roadworks" | "road-closed" | "lane-closed" | "broken-down-vehicle" | "danger" | "flooding" | "fog" | "frost" | "rain" | "wind" | "other"\n magnitudeOfDelay: "unknown" | "minor" | "moderate" | "major" | "undefined"; // severity\n delayInSeconds?: number; // delay caused by the incident (NOT every incident has one — guard with ?? 0)\n lengthInMeters?: number; // affected road length\n roadNumbers?: string[]; // e.g. ["M25", "A1"]\n from?: string; to?: string; // human-readable start / end of the affected stretch\n timeValidity: "present" | "future";\n events: Array<{ description: string; code?: number; iconCategory?: number }>;\n startTime?: Date; endTime?: Date; lastReportTime?: Date;\n probabilityOfOccurrence?: string; numberOfReports?: number;\n};\n```\nRead everything off `properties` — there is NO `reportedDelayInSeconds` / top-level delay field (e.g. `properties.delayInSeconds`, guard `?? 0`).',recallTool:"recallState({ kind: 'incidents' })"},customGeometries:{fieldName:"geometriesEntryIDs",fieldLabel:"`geometriesEntryIDs`",buildField:e=>dr.optional().describe("Tagged polygon sources `{ kind, id }` (see geometriesEntryIDsSchema). Injected as `geometriesByEntry` (keyed by the tagged source `${kind}:${id}`). Undefined when omitted. "+e.requiredHint),sandboxDoc:"• `geometriesByEntry` — object keyed by the tagged source `${kind}:${id}` (geometries mixes sources); each value a mixed Polygon/MultiPolygon array. Merge all with `Object.values(geometriesByEntry).flat()`. Every feature also carries `properties._source: { kind, id }` so you can filter by role (see `GEOMETRIES_PROPS_DOC`).",schemaDoc:yr,recallTool:"recallState({ kind: 'geometries' })"},trafficAreaAnalytics:{fieldName:"trafficAreaAnalyticsEntryIDs",fieldLabel:"`trafficAreaAnalyticsEntryIDs`",buildField:e=>te.array(te.string()).min(1).optional().describe(`IDs of existing traffic-area-analytics entries to ${e.verb} (e.g. ["tta-0"]). Injected as \`trafficAreaAnalyticsByEntry[id]\` (a FeatureCollection of tile/hex regions with metric properties, per entry id). Undefined when omitted. Use \`recallState({ kind: 'trafficAreaAnalytics' })\` to list available ids. `+e.requiredHint),sandboxDoc:"• `trafficAreaAnalyticsByEntry` — object keyed by `trafficAreaAnalyticsEntryIDs` id; each value a TrafficAreaAnalytics FeatureCollection for that entry. Each feature is a region whose metrics live under `feature.properties.baseData` (e.g. `congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`). Each entry's collection keeps its own top-level `properties` (metrics list, date range, ranges) intact — no cross-entry merge.",schemaDoc:'Each `trafficAreaAnalyticsByEntry[id]` is a FeatureCollection of Polygon Features (one per analysed region). The aggregated metrics live under `feature.properties.baseData` (NOT directly on `properties`):\n```ts\ntype AreaAnalyticsFeatureProperties = {\n name: string; timezone: string; level: number;\n baseData: { // aggregated over the whole period — the usual read\n speed?: number; // avg km/h\n freeFlowSpeed?: number; // avg uncongested km/h\n congestionLevel?: number; // % travel-time increase over free-flow\n travelTime?: number; // minutes per 10 km\n networkLength?: number; // meters of road network with data\n };\n timedData: { … }; // same metrics broken down by time granularity\n tiledData?: { tiles: Array<{ … }> }; // per-tile metrics, only when heatmap was requested\n};\n// the FeatureCollection\'s own `.properties` (collection-level):\ntype AreaAnalyticsCollectionProperties = {\n startDate: Date; endDate: Date;\n metrics: ("speed"|"freeFlowSpeed"|"congestionLevel"|"travelTime"|"networkLength")[];\n heatmap: boolean; frcs: number[];\n ranges: Partial<Record<metric, { min: number; max: number }>>; // data-driven min/max across tiles\n};\n```\nRead metrics from `feature.properties.baseData.<metric>` (guard `?? 0`).',recallTool:"recallState({ kind: 'trafficAreaAnalytics' })"},byod:{fieldName:"byodEntryIDs",fieldLabel:"`byodEntryIDs`",buildField:e=>te.array(te.string()).min(1).optional().describe(`IDs of existing BYOD entries to ${e.verb} (e.g. ["byod-0"]) — customer-authored GeoJSON layers. Injected as \`byodByEntry[id]\` (a FeatureCollection per entry id). Undefined when omitted. Use \`recallState\` to list available ids. `+e.requiredHint),sandboxDoc:"• `byodByEntry` — object keyed by `byodEntryIDs` id; each value a customer-authored GeoJSON FeatureCollection. Merge all with `Object.values(byodByEntry).flatMap(fc => fc.features)`. Mixed Point / LineString / Polygon features are normal (BYOD layers carry whatever the integrator uploaded). Treat `feature.properties` as opaque application-specific data.",schemaDoc:"",recallTool:"recallState({ kind: 'byod' })"}},Zr=(e,t)=>Jr.filter(r=>e.includes(r)&&(!t||t.kinds.includes(r))),Qr=(e,t,r)=>e.includes(r)&&(!t||t.kinds.includes(r)),en=e=>e.map(e=>Xr[e].fieldLabel).join(" / "),tn=e=>Jr.filter(t=>e.includes(t)).map(e=>Xr[e].sandboxDoc).join("\n"),rn=e=>Jr.filter(t=>e.includes(t)).map(e=>Xr[e].schemaDoc).filter(e=>e.length>0).join("\n\n"),nn=(e,t)=>{const r={};for(const n of Jr){if(!e.includes(n))continue;const s=Xr[n];r[s.fieldName]=s.buildField(t)}return r},sn=e=>{for(const t of Jr)if(e[Xr[t].fieldName]?.length)return!0;return!1},on=e=>`At least one of ${en(e)} must be set. Call ${Array.from(new Set(e.map(e=>Xr[e].recallTool))).map(e=>`\`${e}\``).join(" / ")} first to list available ids.`,an=te.union([Sr,te.object({bbox:Sr,padding:te.number().optional(),animate:te.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 }`. lng in [-180, 180], lat in [-90, 90]. Default padding: 50px."),ln=te.object({bbox:te.tuple([te.number(),te.number(),te.number(),te.number()]).describe("Bounds the camera was fit to."),padding:te.number().describe("Padding (px) applied around the bbox.")}),cn="`fitOnMap` (opt): `[W,S,E,N]` BBox or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }` (default padding 50, lng in [-180, 180], lat in [-90, 90]). 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)`.",dn=e=>Sr.safeParse(e).success,un=e=>{if(dn(e))return!0;if(!e||"object"!=typeof e)return!1;const t=e;return!!dn(t.bbox)&&(!!(void 0===t.padding||"number"==typeof t.padding&&Number.isFinite(t.padding))&&!(void 0!==t.animate&&"boolean"!=typeof t.animate))},pn=(e,t)=>{const r=dn(t)?t:t.bbox,n=(dn(t)?void 0:t.padding)??50,s=dn(t)?void 0:t.animate;return e.baseMap.mapLibreMap.fitBounds(r,{padding:n,...void 0!==s&&{animate:s}}),{bbox:r,padding:n}},hn=["CountrySubdivision","CountrySecondarySubdivision","Municipality","MunicipalitySubdivision","Neighbourhood"],yn=async(e,t={},r)=>{const n=Me({query:e,limit:t.limit??6,geographyTypes:hn,...t.bias?{geoBias:{position:t.bias}}:{},signal:r?.signal});return(await x(n)).features},mn=async(e,t,r)=>{if("multiple"!==e.entryMode||void 0===r)return;const n=new Set(t),s="all"===r?[...e.shownEntryIds].filter(e=>!n.has(e)):r.filter(e=>!n.has(e));for(const o of s)await e.hideEntry(o)},fn=async(e,t,r,n)=>{const s="poi"===t?O:x,{limit:o,bias:a}=r;return(await s(Me({query:e,limit:o,...a&&{geoBias:a},signal:n?.signal}))).features},gn=async(e,t,r,n)=>{const[s]=await fn(e,t,{limit:1,bias:r},n);return s??null},bn=te.union([te.object({query:te.string().describe("Location string to resolve."),queryAs:te.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.')}),te.object({position:Ir.describe("Explicit [lng, lat] — GeoJSON order, longitude first. Use when you already have a position, e.g. copied straight from a getViewport / getCurrentLocation / reverseGeocode result. lng in [-180, 180], lat in [-90, 90].")}),te.object({placeIdOrEntryId:te.string().describe("Place already in session state, by its feature `id` or its `placesEntryId`. Prefer over re-searching by name. Prefer a feature `id`: it targets one place, whereas an entry id resolves to its FIRST place (fine for a single-place locatePlace entry, ambiguous for a multi-place discoverPlaces one).")})]),wn=e=>"query"in e?`"${e.query}"`:"placeIdOrEntryId"in e?`placeIdOrEntryId "${e.placeIdOrEntryId}"`:JSON.stringify(e.position),vn=te.object({source:cr,reason:te.string()}),In=e=>`${e.source.kind}:${e.source.id}: ${e.reason}`,En=(e,t,r)=>{let n=0;for(const s of e)s.properties.dataSources?.geometry?.id&&"string"==typeof s.id&&(n++,r.placeIdToEntry.has(s.id)||r.placeIdToEntry.set(s.id,t),r.seen.has(s.id)||(r.seen.add(s.id),r.placeIds.push(s.id)));return n},Sn=(e,t)=>({...e,properties:{...e.properties??{},_source:t}}),kn=async(e,t)=>{const r=t.places.findPlaceById(e);if(!r)return{kind:"skip",reason:"No place with this id in session state. Use `recallState` 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 n=await t.places.fetchPlaceGeometry(e);return n?{kind:"success",place:r.place,feature:n,ownerEntryId:r.entryId}:{kind:"skip",reason:"Geometry Data service returned no feature for this place."}}catch(n){return{kind:"skip",reason:`Geometry fetch failed: ${n instanceof Error?n.message:String(n)}`}}},Mn=async(e,t)=>{if(0===e.length)return{error:"`geometriesEntryIDs` must contain one or more tagged ids (e.g. `{ kind: 'places', id: <entryId> }`, `{ kind: 'place', id: <placeId> }`, `{ kind: 'ranges', id: <entryId> }`, `{ kind: 'customGeometries', id: <entryId> }`). Use `recallState` to list available ids."};const r=(e=>{const t={place:[],places:[],ranges:[],customGeometries:[]};for(const r of e)t[r.kind].push(r);return t})(e),n=((e,t)=>{const r=[],n=/* @__PURE__ */new Map,s=[],o=/* @__PURE__ */new Set;for(const a of e){const e=t.places.entries.find(e=>e.id===a.id);e?0===En(e.data,a.id,{placeIds:r,placeIdToEntry:n,seen:o})&&s.push({source:a,reason:"Places entry has no places with a geometry data source (e.g. addresses and streets often lack one)."}):s.push({source:a,reason:"No places entry with this id in session state. Use `recallState` to list available ids."})}return{placeIds:r,placeIdToEntry:n,skipped:s}})(r.places,t),s=[...n.skipped],o=/* @__PURE__ */new Set,a=[],i=/* @__PURE__ */new Map;for(const f of r.place)o.has(f.id)||(o.add(f.id),a.push(f.id),i.set(f.id,f));for(const f of n.placeIds)o.has(f)||(o.add(f),a.push(f));const l=[],c=[],d=/* @__PURE__ */new Set,u=/* @__PURE__ */new Set,p=/* @__PURE__ */new Set;for(const f of a){const e=await kn(f,t),r=i.get(f),o=n.placeIdToEntry.get(f);if("skip"===e.kind){const t=r??{kind:"place",id:f};s.push({source:t,reason:e.reason});continue}const a=r??(o?{kind:"places",id:o}:{kind:"place",id:f});l.push(Sn(e.feature,a)),c.push(e.place),d.add(e.ownerEntryId),r&&u.add(r.id),o&&p.add(o)}const h=((e,t)=>{const r=[],n=/* @__PURE__ */new Set,s=[];for(const o of e){const e=t.ranges.entries.find(e=>e.id===o.id);if(!e){s.push({source:o,reason:"No ranges entry with this id in session state."});continue}const a=e.data.flatMap(e=>e.polygon?.features??[]);if(0!==a.length){for(const e of a)r.push(Sn(e,o));n.add(o.id)}else s.push({source:o,reason:"Ranges entry has no polygons — recompute via findReachableAreas first."})}return{features:r,contributingIds:n,skipped:s}})(r.ranges,t);l.push(...h.features),s.push(...h.skipped);const y=((e,t)=>{const r=[],n=/* @__PURE__ */new Set,s=[];for(const o of e){const e=t.customGeometries.findById(o.id);if(e)if(0!==e.data.length){for(const t of e.data)r.push(Sn(t,o));n.add(o.id)}else s.push({source:o,reason:"Custom-geometries entry is empty."});else s.push({source:o,reason:"No custom-geometries entry with this id in session state. Use `recallState` to list available ids."})}return{features:r,contributingIds:n,skipped:s}})(r.customGeometries,t);l.push(...y.features),s.push(...y.skipped);const m=y.contributingIds;return 0===l.length?{error:`No input geometries available. ${s.map(In).join(" | ")}`}:{value:{geometries:l,sourcePlaces:c,affectedEntries:[...[...d].map(e=>({id:e,kind:"places"})),...[...m].map(e=>({id:e,kind:"customGeometries"}))],skipped:s,contributingSourceIds:e.filter(e=>{switch(e.kind){case"place":return u.has(e.id);case"places":return p.has(e.id);case"ranges":return h.contributingIds.has(e.id);case"customGeometries":return m.has(e.id)}})}}},An=(e,t)=>{if(!e?.length)return{value:void 0};const r={};for(const n of e){const e=t.places.entries.find(e=>e.id===n);if(!e)return{error:`No places entry found with id "${n}". Use recallState to list available IDs.`};r[n]={type:"FeatureCollection",features:e.data}}return{value:r}},xn=e=>{let t;for(const r of Object.values(e))for(const e of r){const r=e.timestamp;"number"==typeof r&&(void 0===t||r>t)&&(t=r)}return t},Dn=["placesEntryIDs","routesEntryIDs","incidentsEntryIDs","geometriesEntryIDs","trafficAreaAnalyticsEntryIDs","byodEntryIDs"],Tn=async(e,t)=>{if(!(e=>Dn.some(t=>(e[t]?.length??0)>0))(e))return{error:"At least one of `placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, `geometriesEntryIDs`, `trafficAreaAnalyticsEntryIDs`, `byodEntryIDs` must be set. Use `recallState` (optionally with a `kind`) to list available ids."};const r=((e,t)=>{const r=On(e.placesEntryIDs,t.places,{missingId:e=>`No places entry with id "${e}". Use recallState to list available IDs.`});if("error"in r)return r;const n=On(e.routesEntryIDs,t.routing,{missingId:e=>`No routes entry with id "${e}". Use recallState to list available IDs.`});if("error"in n)return n;const s=On(e.incidentsEntryIDs,t.trafficIncidents,{missingId:e=>`No incidents entry with id "${e}". Call getTrafficIncidents first.`});if("error"in s)return s;const o=On(e.trafficAreaAnalyticsEntryIDs,t.trafficAreaAnalytics,{missingId:e=>`No traffic-area-analytics entry with id "${e}". Call getTrafficAreaAnalytics first.`});if("error"in o)return o;const a=On(e.byodEntryIDs,t.byod,{missingId:e=>`No BYOD entry with id "${e}". Use recallState to list available IDs.`});return"error"in a?a:{value:{places:r.value,routes:n.value,incidents:s.value,trafficAreaAnalytics:o.value,byod:a.value}}})(e,t);if("error"in r)return r;let n,s={affectedEntries:[],sourceIds:[],skipped:[]};if(e.geometriesEntryIDs?.length){const r=await Mn(e.geometriesEntryIDs,t);if("error"in r)return r;n=r.value.geometries,s={affectedEntries:r.value.affectedEntries,sourceIds:r.value.contributingSourceIds,skipped:r.value.skipped}}return{value:{resolved:r.value,sandbox:Pn(r.value,n),geometries:s}}},Pn=(e,t)=>({placesByEntry:e.places.length?Object.fromEntries(e.places.map(e=>[e.id,{type:"FeatureCollection",features:[...e.data]}])):void 0,routesByEntry:e.routes.length?Object.fromEntries(e.routes.map(e=>[e.id,{type:"FeatureCollection",features:[...e.data.features]}])):void 0,incidentsByEntry:e.incidents.length?Object.fromEntries(e.incidents.map(e=>[e.id,[...e.data]])):void 0,geometriesByEntry:_n(t),trafficAreaAnalyticsByEntry:e.trafficAreaAnalytics.length?Object.fromEntries(e.trafficAreaAnalytics.map(e=>[e.id,e.data])):void 0,byodByEntry:e.byod.length?Object.fromEntries(e.byod.map(e=>[e.id,e.data])):void 0}),_n=e=>{if(!e?.length)return;const t={};for(const r of e){const e=r.properties?._source,n=e?`${e.kind}:${e.id}`:"unknown",s=t[n]??[];s.push(r),t[n]=s}return t},On=(e,t,r)=>{if(!e?.length)return{value:[]};const n=[];for(const s of e){const e=t.entries.find(e=>e.id===s);if(!e)return{error:r.missingId(s)};n.push(e)}return{value:n}},Cn='INPUTS (per-entry) — each requested kind is an object keyed by the id you passed in `<kind>EntryIDs`: `placesByEntry`, `routesByEntry`, `incidentsByEntry`, `trafficAreaAnalyticsByEntry`, `byodByEntry`. There is NO flat `places`/`routes`/… — only these per-entry records. Each value is that one entry\'s data: a FeatureCollection (`placesByEntry`, `routesByEntry`, `byodByEntry`), a TrafficAreaAnalytics FeatureCollection (`trafficAreaAnalyticsByEntry`), or a plain array (`incidentsByEntry`).\n• One entry — index by id: `placesByEntry["places-2"]` (a FeatureCollection → `turf.bbox(placesByEntry["places-2"])`).\n• All requested entries of a kind — merge with `Object.values(...)`: `Object.values(placesByEntry).flatMap(fc => fc.features)` for FeatureCollection kinds (wrap with `turf.featureCollection(...)` before passing to turf), `Object.values(incidentsByEntry).flat()` for arrays.\n• Per-entry / comparative work ("count per entry", "which route set is faster") — iterate the record\'s entries (`Object.entries(routesByEntry)`) so results stay labelled by source id.\n`geometriesByEntry` is the odd one out: keyed by the tagged source `${kind}:${id}` (e.g. `"customGeometries:abc"`) rather than a bare id, since geometries mixes sources — each feature also carries `properties._source` (`{ kind, id }`).',Rn={calculateProgressAtRoutePoint:$,getCoordinateAtRouteProgress:U,getProgressAtNearestRoutePoint:H,getRouteProgressBetween:Y,getRouteProgressForSection:V,getSectionBBox:J},jn=["placesByEntry","routesByEntry","incidentsByEntry","geometriesByEntry","trafficAreaAnalyticsByEntry","byodByEntry","h3","turf","routeUtils","cluster"],Nn=(e,t)=>{const r=t&&e.length>1?'\n\nCROSS-KIND OPS — `turf` and `h3` operate on EVERY feature regardless of which entry/kind it came from. You can freely combine features from `placesByEntry` (Points), `routesByEntry` (LineStrings), `incidentsByEntry` (Point or LineString), `geometriesByEntry` (Polygon / MultiPolygon), `byodByEntry` (customer GeoJSON — Point / LineString / Polygon, mixed), and `trafficAreaAnalyticsByEntry` (Polygon tiles with `properties` like `congestionLevel` / `speed` / `travelTime` / `freeFlowSpeed`) in the same call. Pull features out of a record first — one entry `placesByEntry[id].features`, or all entries `Object.values(placesByEntry).flatMap(fc => fc.features)` (`Object.values(incidentsByEntry).flat()` for the array kinds). Common bridges:\n• Point ↔ LineString — `turf.pointToLineDistance(point, lineFeature, { units: "meters" })`, `turf.nearestPointOnLine(line, point)`.\n• Point ↔ Polygon — `turf.booleanPointInPolygon(point, poly)`, `turf.pointsWithinPolygon(turf.featureCollection(points), poly)` — POINT-ONLY (throw "coord must be GeoJSON Point" on a line/polygon).\n• ANY feature ↔ Polygon (containment / overlap) — `incidentsByEntry` and `byodByEntry` mix geometry types, so when you group features into polygons (e.g. incidents per BYOD district) use `turf.booleanIntersects(feature, poly)`, which accepts Point / LineString / Polygon alike. Only use `booleanPointInPolygon` after a `feature.geometry.type === "Point"` guard, or first collapse a line/area to a point with `turf.pointOnFeature(feature)`.\n• LineString ↔ Polygon — `turf.lineIntersect(line, poly)`, `turf.booleanCrosses(line, poly)`, `turf.lineSplit(line, poly)`.\n• Polygon ↔ Polygon — `turf.union/intersect/difference(turf.featureCollection([a, b]))` (Turf v7 takes a single collection).\n• Point/LineString ↔ trafficAreaAnalytics tile — `turf.booleanPointInPolygon(p, tile)` for points; `turf.lineIntersect(routeLine, tile)` for routes; iterate a tile collection\'s `.features` (e.g. `trafficAreaAnalyticsByEntry[id].features`) to find which tile a place/route segment falls into and read its metric (`tile.properties.congestionLevel`, etc.).\n• Buffer to bridge kinds — `turf.buffer(anyFeature, meters/1000, { units: "kilometers" })` turns a point or line into a polygon you can then filter against.\n• Bbox of anything — `turf.bbox(featureOrCollection)`.\n• Centroid for Point↔shape distance — `turf.centroid(polygonOrLine).geometry.coordinates`.\n• h3 — `h3.latLngToCell(lat, lng, res)` for any Point; `h3.polygonToCells(poly.geometry.coordinates, res)` for any Polygon; for a LineString iterate `geometry.coordinates` and bin each `[lng, lat]`.\nNote: arguments to turf functions are GeoJSON Features — pass the whole feature (`placesByEntry[id].features[i]`, `routesByEntry[id].features[0]`, `geometriesByEntry[key][i]`, `incidentsByEntry[id][i]`, `trafficAreaAnalyticsByEntry[id].features[i]`), not the bare `geometry.coordinates` (a few helpers accept coords but most expect Features). Always check the function signature at https://turfjs.org/docs/.':"",n=t&&e.includes("routes")?"\n\n`routeUtils` (route section / progress helpers, only useful when `routesEntryIDs` is set; each takes a single route feature, e.g. `routesByEntry[id].features[0]`; for a plain bounding box use `turf.bbox(route)`):\n• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>`.\n• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startIdx, endIdx)`.\n• `calculateProgressAtRoutePoint(route, pathIndex)`.\n• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }`.\n• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point to the line.":"",s=t&&e.includes("incidents")?"\n\n`cluster(incidents, params?, previous?, now?)` — DBSCAN clustering with stable IDs + trends (only useful with `incidentsEntryIDs`; `incidents` is an array — get it via `Object.values(incidentsByEntry).flat()` or one entry `incidentsByEntry[id]`; filter first, then cluster).\n• `params`: `{ eps?, minMembers?, maxClusters?, preFilter? }` — `eps` km radius (default 0.5; 0.3–0.5 urban, 1+ highways), `minMembers` (default 3), `maxClusters` top-N by delay (default 6).\n• Returns `{ groups: [{ id, centroid, memberIds, size, totalDelaySeconds, peakDelaySeconds, diameterKm, primaryRoads, primaryCategory, trend }], sampledAt }`.\n• Live trends: pass the injected `previous` + `now` — `return cluster(incidents, { eps: 0.4 }, previous, now);`. Omit both for one-shot.":"";return`${Re(jn)}\n\n${Cn}${r}${n}${s}\n\n`},Bn=(e,t)=>[e.placesByEntry,e.routesByEntry,e.incidentsByEntry,e.geometriesByEntry,e.trafficAreaAnalyticsByEntry,e.byodByEntry,t.h3,t.turf,Rn,gt],Ln=new Set(X),Fn=async(e,t)=>{if(!e)return{resolved:void 0,unresolved:[]};const r=e.filter(e=>Ln.has(e)),n=e.filter(e=>!Ln.has(e));if(0===n.length)return{resolved:r.length?r:void 0,unresolved:[]};const s=Me({signal:t?.signal});await T(s);const o=await Promise.all(n.map(async e=>{const r=Me({filters:[e],signal:t?.signal});return{term:e,codes:await T(r)}})),a=[],i=[];for(const{term:c,codes:d}of o)0===d.length?a.push(c):i.push(...d);const l=[.../* @__PURE__ */new Set([...r,...i])];return{resolved:l.length?l:void 0,unresolved:a}},qn=(e,t)=>{const r=e.ranges.entries.find(e=>e.id===t);if(!r||0===r.data.length)return{error:`Range "${t}" not found. Use recallState to list available ranges.`};const n=r.data.map(e=>e.polygon?.features[0]?.geometry).filter(e=>!!e);return 0===n.length?{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`}:{polygons:n}},$n=e=>Object.entries(e).filter(([,e])=>e.scopePrompt).map(([e])=>e),Gn=te.object({kinds:te.array(te.string()).min(1).describe("Entry-data kinds the turn touches (one of the kinds listed in the tool's SCOPE hint).")}),Wn=(e,t)=>{t&&(e.inputTokens+=t.inputTokens??0,e.outputTokens+=t.outputTokens??0,e.totalTokens+=t.totalTokens??0)},Un=e=>{if(!e||!Array.isArray(e.tools)||!e.tools.every(e=>"string"==typeof e))return;const t=e.toolScopes&&"object"==typeof e.toolScopes?e.toolScopes:void 0;return{tools:e.tools,...t&&{scopes:t}}},zn=(e,t)=>{const r=e.toolScopes?Object.fromEntries(Object.entries(e.toolScopes).filter(([,e])=>null!=e)):void 0;return{tools:e.tools,...r&&Object.keys(r).length>0&&{toolScopes:r},usage:t}},Hn=async(t,n,s)=>{const o=new Set($n(s)),a=(e=>{const t=te.object({tools:te.array(te.string()).min(1).describe("Tool names needed to fulfill the query.")});if(0===e.size)return t;const r={};for(const s of e)r[s]=Gn.nullable();const n=te.object(r).nullable().describe("Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in `tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.");return t.extend({toolScopes:n}).superRefine((t,r)=>{const n=t.toolScopes??{};for(const s of t.tools){if(!e.has(s))continue;null==n[s]&&r.addIssue({code:"custom",path:["toolScopes",s],message:`Tool "${s}" requires \`toolScopes.${s}\` — emit it per the SCOPE hint (don't leave null when picking).`})}})})(o),i={inputTokens:0,outputTokens:0,totalTokens:0},l=(e=>0===e.size?'{ "tools": ["toolName", ...] }':`{\n "tools": ["toolName", ...],\n "toolScopes": {\n${[...e].map(e=>` "${e}": { "kinds": [...] } | null`).join(",\n")}\n }\n}\nFor every scopable tool you LIST in "tools", the matching "toolScopes" entry MUST be a non-null object \`{ "kinds": [...] }\`. For scopable tools you DON'T pick, set the corresponding "toolScopes" entry to \`null\`.`)(o),c=`${(e=>{const t=Object.entries(e).map(([e,t])=>((e,t)=>`${e}${t.classificationPrompt?` ${t.classificationPrompt}.`:""}${t.dependsOn?.length?` Depends on: ${t.dependsOn.join(", ")}.`:""}${t.scopePrompt?` SCOPE: ${t.scopePrompt}`:""}`)(e,t)),r=$n(e);return["You are a tool selector for a map assistant.","You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.","PREREQUISITES: each tool below may list `Depends on: <tools>` — the tools that produce the data it reads. When you pick a tool, ALSO pick every tool in its `Depends on:` list, UNLESS the conversation shows that prerequisite is already satisfied (the data was loaded, or the place located, on an earlier turn). E.g. arming a tracker/alert on a fresh area needs getTrafficIncidents (to load the incidents) and locatePlace (for a named area) picked alongside it.",'LOCATION REFERENCE:\n - "near me" / "my location" / "where I am" → getCurrentLocation (user\'s physical GPS position, may prompt permission)\n - "in this area" / "here on the map" / "near the map center" → getViewport (map\'s current view)\n - If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point',"","Available tools:",...t,...r.length?["","TOOL SCOPES — MANDATORY when selecting a scopable tool:",`The following tools REQUIRE a scope entry in \`toolScopes\` whenever they appear in \`tools\`: ${r.join(", ")}.`,"For each picked scopable tool, emit `toolScopes[<name>]` with the per-tool object shape described in its SCOPE hint above. Don't omit. Don't leave it implicit.","If the user query truly needs all input kinds the tool supports, still emit the scope explicitly — list every kind. Never skip the scope entry when picking the tool."]:[]].join("\n")})(s)}\n\nReturn JSON matching this shape exactly:\n${l}`;let d;for(let p=0;p<2;p++)try{const s=await r({model:n,output:e.json(),system:c,messages:t,maxOutputTokens:400});Wn(i,s.usage);const o=a.safeParse(s.output);if(o.success)return zn(o.data,i);d=Un(s.output)??d}catch(u){const e=u;Wn(i,e.usage),d=Un(e.value)??d}if(!d)throw new Error("Classifier failed after 2 attempts to produce a valid response.");return{tools:d.tools,...d.scopes&&{toolScopes:d.scopes},usage:i}},Yn=async(e,t)=>{const{chatModel:r,toolsMetadata:n}=t,s=performance.now(),{tools:o,toolScopes:a,usage:i}=await Hn(e,r,n);return{activeToolNames:o,...a&&{toolScopes:a},timeMs:performance.now()-s,usage:i}},Vn=e=>"text"===e.type,Jn=e=>{if("string"==typeof e)return e;const t=e.filter(Vn);return t.length>0?t.map(e=>e.text).join(""):null},Kn=e=>{for(let t=e.length-1;t>=0;t--)if("user"===e[t].role)return Jn(e[t].content);return null},Xn=e=>{const{model:t,maxHistoryMessages:r,maxHistoryMessageLength:n}=e;return async e=>{const{messages:s,toolsMetadata:o}=e;if(!s?.length)return null;const a=r??8,i=n??300;let l=-1,c=null;for(let t=s.length-1;t>=0;t--)if("user"===s[t].role){l=t,c=Jn(s[t].content);break}if(!c)return null;const d=[];for(let t=Math.max(0,l-a);t<l;t++){const e=s[t];if("user"!==e.role&&"assistant"!==e.role)continue;const r=Jn(e.content);if(!r)continue;const n=r.length>i?r.slice(0,i)+"…":r;d.push({role:e.role,content:n})}d.push({role:"user",content:c});try{return await Yn(d,{chatModel:t,toolsMetadata:o})}catch{return null}}},Zn=e=>{if(!Array.isArray(e))return e.properties?.address?.freeformAddress},Qn=(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 n=`${e} ${1===e?"place":"places"}`;return r.length?`${r.join(" ")} (${n})`:n})(e.features.length,t);const r=e.properties?.poi?.name,n=e.properties?.address?.freeformAddress;return r&&n?`${r}, ${n}`:r||(n||(t?.query?t.query:"Place"))},es=e=>{const t=e.properties?.address?.freeformAddress;if(!t)return;const r=e.properties?.address?.countryCode;if(!r)return t;const n=`, ${r}`;return t.toLowerCase().endsWith(n.toLowerCase())?t:`${t}${n}`},ts={timeMinutes:"min",distanceKM:"km",remainingChargeCPT:"% remaining",spentChargePCT:"% spent",spentFuelLiters:"L"},rs=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${ts[e.type]??e.type}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`,ns=e=>({id:e.id,type:e.properties?.type,name:e.properties?.poi?.name,address:e.properties?.address?.freeformAddress,position:e.geometry?.coordinates??[0,0]}),ss=e=>{const t=Array.isArray(e)?e:e.features;return{count:t.length,features:t.slice(0,5).map(ns)}},os=e=>{const{summary:t,sections:r,index:n}=e.properties,s=r.leg,o=e=>({...e,arrivalTime:e.arrivalTime.toISOString(),departureTime:e.departureTime.toISOString()});return{index:n,summary:o(t),...s.length>1&&{legSummaries:s.map(e=>o(e.summary))}}},as=e=>({count:e.features.length,routes:e.features.map(os)}),is=te.object({slotIndex:te.number(),isFilled:te.boolean(),address:te.string().optional(),position:te.array(te.number()).length(2).optional().describe("[longitude, latitude]")}),ls=e=>{if(null==e)return null;const t=z(e);let r;return Array.isArray(e)||(r=e.properties?.address?.freeformAddress),{slotIndex:-1,isFilled:!0,...r&&{address:r},...t&&{position:t}}};function cs(e,t,r){switch(e.kind){case"within":return us(t,r);case"nearby":return ps(t,r);case"global":return hs()}}var ds,us=(e,t)=>fs(ms(e,t),t),ps=async(e,t)=>{const r=ms(e,t),n=r.mapViewport.center;if(!r.queries?.length)return r.useViewport&&n?{position:n}:{};const s=await Es(r,t,n);if(0===s.length)return{};const o=z(s[0]);if(!o)return{};const a=es(s[0]);return{position:o,query:r.queries[0].query,...a&&{label:a}}},hs=()=>({}),ys=e=>"object"==typeof e&&null!==e&&"error"in e,ms=(e,t)=>{const{viewport:r,boundingBox:n,queries:s,placeIds:o,geometries:a,route:i}=e;return{useViewport:r,boundingBox:n,queries:s,placeIds:o,geometries:a,route:i,mapViewport:t.viewport()}},fs=async(e,t)=>{const{useViewport:r,boundingBox:n,queries:s,placeIds:o,geometries:a,route:i,mapViewport:l}=e,c=[];n&&c.push({bbox:n,source:"boundingBox"});const d=await gs(s,t,l);if(ys(d))return d;c.push(...d);const u=await bs(o,t);if(ys(u))return u;if(c.push(...u,...ws(a)),i){const e=await vs(i,t);if(ys(e))return e;c.push(...e)}return c.length>0?c:r?l.bbox?[{bbox:l.bbox,source:"viewport"}]:{error:"No map viewport available to resolve as an area."}:c},gs=async(e,t,r)=>{const n=[];for(const{query:s}of e??[]){const e=(await t.geocodeAreas(s,void 0,r.center)).filter(e=>e.bbox);if(0===e.length)return{error:`Could not resolve "${s}" to an area. Provide a boundingBox or a more specific place.`};const o=(r.center?Is(e,r.center):e)[0],a=es(o),i=o.properties.dataSources?.geometry?.id?await t.fetchGeometry(o):void 0;n.push(i?{bbox:i.bbox??q(i),polygon:i.geometry,label:a,source:"query",query:s}:{bbox:o.bbox,label:a,source:"query",query:s})}return n},bs=async(e,t)=>{const r=[];for(const n of e??[]){const e=t.expandEntry(n);if(0===e?.length)return{error:`Places entry "${n}" has no place with a geometry data source. Provide a boundingBox or a different entry.`};for(const s of e??[n]){const e=await t.fetchGeometry(s);if(!e)return{error:`Unknown placeId "${s}" or it has no fetchable geometry. Use recallState to list available IDs.`};r.push({bbox:q(e),polygon:e.geometry,source:"placeId"})}}return r},ws=e=>{const t=[];for(const r of e??[]){const e=q(r);e&&t.push({bbox:e,polygon:r,source:"geometries"})}return t},vs=async(e,t)=>{const r=t.getRoute(e.routeId);if("error"in r)return r;const n=[];for(const s of r.features){const t=ee.buffer(s,e.widthMeters/2,{units:"meters"});t&&n.push({bbox:q(t),polygon:t.geometry,label:r.label,source:"route"})}return 0===n.length?{error:"Route produced no corridor geometry."}:n},Is=(e,t)=>{const r=[...e].sort((e,r)=>{if(void 0===e.bbox||void 0===r.bbox)return 0;const n=ee.distance(L(e.bbox),t)-ee.distance(L(r.bbox),t);return Math.abs(n)>1e3?n:0});return e.map((e,t)=>({place:e,rank:(t+r.indexOf(e))/2})).toSorted((e,t)=>e.rank-t.rank).map(e=>e.place)},Es=async(e,t,r)=>{const n=[];for(const{query:s,queryAs:o}of e.queries??[]){const e=await t.geocodeAreas(s,o,r);n.push(...e)}return n},Ss=async(e,t,r)=>{await mn(e.byod,[t],r.hidePreviousEntries),await e.byod.showEntry(t);let n=!1;if("auto"===r.zoomMode){const t=(e=>{const t=[];for(const r of e.byod.shownEntryIds){const n=e.byod.findById(r);n&&t.push(...n.data.features)}return t})(e);if(t.length>0){const r=q({type:"FeatureCollection",features:t});r&&(e.baseMap.mapLibreMap.fitBounds(r,{padding:50}),n=!0)}}return{zoomMode:n}},ks=async(e,t,r,n)=>{const s=await e.places.fetchGeometriesForEntry(t,n);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}},Ms=async(e,t,r)=>{const n={markerType:!1,zoomMode:!1};if("none"!==r.markerType){if("multiple"===e.places.entryMode&&"all"!==r.hidePreviousEntries){if(Array.isArray(r.hidePreviousEntries)){const n=new Set(t),s=r.hidePreviousEntries.filter(e=>!n.has(e));s.length>0&&await e.places.removeShownEntries(s)}await e.places.addShownEntries(t,r.markerType)}else await e.places.setShownEntries(t,r.markerType);n.markerType=!0}if("auto"===r.zoomMode){const t=e.places.getShownFeatures();if(t.length>0){const r=q({type:"FeatureCollection",features:t});r&&e.baseMap.mapLibreMap.fitBounds(r,{padding:50})}n.zoomMode=!0}return n},As=6,xs=(e,t=6)=>{if(e.mapLibreMap.getZoom()<t)return;const r=e.mapLibreMap.getCenter();return[r.lng,r.lat]},Ds=e=>{const t=e.mapLibreMap.getBounds();return[t.getWest(),t.getSouth(),t.getEast(),t.getNorth()]},Ts=(e,t)=>({viewport:()=>{let t,r;try{t=Ds(e.baseMap)}catch{t=void 0}try{r=xs(e.baseMap)}catch{r=void 0}return{bbox:t,center:r}},expandEntry:t=>e.places.geometryPlaceIdsForEntry(t),geocodeAreas:(e,r,n)=>r?fn(e,r,{limit:5,bias:n?{position:n}:void 0},t):yn(e,{bias:n},t),fetchGeometry:async r=>{if("string"==typeof r)return e.places.fetchPlaceGeometry(r,t);if(!r.properties.dataSources?.geometry?.id)return;const n=Me({geometries:[r],signal:t?.signal});return(await D(n)).features[0]},getRoute:t=>{const r=e.routing.entries;if(0===r.length)return{error:"No routes available yet. Calculate or select a route first (e.g. calculateRoute), then retry."};const n=t?r.find(e=>e.id===t):r.at(-1);if(!n)return{error:`Route "${t}" not found. Use recallState to list available route IDs.`};const s=n.data.features;return 0===s.length?{error:`Route "${n.id}" has no geometry. Recompute it via calculateRoute.`}:{features:s,label:n.label}}}),Ps=te.object({added:te.array(te.string()).describe("Source IDs added by the executed code."),removed:te.array(te.string()).describe("Source IDs removed by the executed code."),updated:te.array(te.string()).describe("Source IDs whose definition changed.")}),_s=te.object({added:te.array(te.string()).describe("Layer IDs added by the executed code."),removed:te.array(te.string()).describe("Layer IDs removed by the executed code."),updated:te.array(te.string()).describe("Layer IDs whose properties changed.")}),Os=te.object({code:te.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.')}),Cs=te.union([te.object({success:te.literal(!0),result:te.unknown().optional().describe("Return value of the executed code, if any."),sources:Ps,layers:_s}),Pt]),Rs=e=>{const t=e.getStyle()?.sources??{},r=/* @__PURE__ */new Map;for(const[n,s]of Object.entries(t))r.set(n,JSON.stringify(s));return r},js=e=>{const t=e.getStyle()?.layers??[],r=/* @__PURE__ */new Map;for(const n of t)r.set(n.id,JSON.stringify(n));return r},Ns=(e,t)=>{const r=[],n=[],s=[];for(const[o,a]of t){const t=e.get(o);void 0===t?r.push(o):t!==a&&s.push(o)}for(const o of e.keys())t.has(o)||n.push(o);return{added:r,removed:n,updated:s}},Bs=te.union([te.object({success:te.literal(!0)}),Pt]),Ls=te.object({where:te.union([te.object({boundingBox:Sr.describe("BBox [W,S,E,N]."),padding:te.number().optional().describe("Padding in pixels. Default: 50.")}).describe("Fit the camera to a bounding box."),te.object({position:Ir.describe("[lng, lat] to fly to."),zoom:te.number().optional().describe("Camera zoom level. Default: 14.")}).describe("Fly the camera to a specific position.")])}),Fs=te.union([te.object({layers:te.array(te.object({id:te.string(),layout:te.unknown().optional(),paint:te.unknown().optional()}))}),Pt]),qs=te.object({layerIdQuery:te.string().optional().describe("Case-insensitive partial match on layer IDs. If omitted, all layers are returned."),include:te.array(te.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.')}),$s=te.union([te.object({center:te.array(te.number()).describe("[lng, lat]"),zoom:te.number().describe("0–22"),bbox:te.array(te.number()).describe("[W, S, E, N]"),pitch:te.number().describe("degrees, 0=top-down, 85=near-horizontal"),bearing:te.number().describe("degrees, 0=north, 90=east")}),Pt]),Gs=te.object({}),Ws=te.union([te.object({layerId:te.string(),propertyName:te.string(),value:te.unknown(),success:te.literal(!0)}),te.object({layerId:te.string(),propertyName:te.string(),error:te.string()})]),Us=te.union([te.object({results:te.array(Ws)}),Pt]),zs=te.object({changes:te.array(te.object({layerId:te.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:te.string().describe("e.g. visibility|text-field|icon-size"),value:te.any()}))}).describe("List of layout property changes to apply in one go."),Hs=te.union([te.object({layerId:te.string(),propertyName:te.string(),value:te.unknown(),success:te.literal(!0)}),te.object({layerId:te.string(),propertyName:te.string(),error:te.string()})]),Ys=te.union([te.object({results:te.array(Hs)}),Pt]),Vs=te.object({changes:te.array(te.object({layerId:te.string().describe("MapLibre layer ID from getMapStyleLayers"),propertyName:te.string().describe("e.g. fill-color|line-width|text-color"),value:te.any()})).describe("List of paint property changes to apply in one go.")}),Js=te.union([te.object({success:te.literal(!0)}),Pt]),Ks=te.object({pitch:te.number().min(0).max(85).optional().describe("0=top-down, 85=near-horizontal"),bearing:te.number().min(-180).max(180).optional().describe("0=north, 90=east")}),Xs=te.union([te.object({zoom:te.number().describe("Resulting zoom level after the change")}),Pt]),Zs=te.object({delta:te.number().describe("Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.")}),Qs=async(e,t,r)=>{if("position"in e){const[t,r]=e.position;return{place:[t,r],position:[t,r],name:`[${t}, ${r}]`}}if("placeIdOrEntryId"in e){const{placeIdOrEntryId:r}=e;let n=t.places.findPlaceById(r);if(!n){const e=t.places.entries.find(e=>e.id===r),s=e?.data[0];s&&(n={place:s,entryId:e.id})}if(!n)return null;const s=z(n.place);return s?{place:n.place,position:s,name:n.place.properties?.poi?.name??n.place.properties?.address?.freeformAddress??r}:null}const n=await gn(e.query,e.queryAs,void 0,r);if(!n)return null;const s=z(n);return s?{place:n,position:s,name:n.properties?.address?.freeformAddress??e.query,query:e.query}:null},eo=te.object({routeType:te.enum(_).optional().describe("fast|short|efficient|thrilling"),traffic:te.enum(["live","historical"]).optional().describe("live|historical. `historical` picks the TYPICAL route path (ignores current incidents when routing) while still reporting live delays — use it with startRouteMonitor so each tick refreshes delays on the SAME paths instead of re-routing around current traffic."),avoid:te.array(te.enum(B)).optional().describe("tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones"),avoidAreas:te.array(Sr.describe("GeoJSON bbox [minLng, minLat, maxLng, maxLat]")).max(10).optional().describe("Up to 10 bounding boxes to bypass.")}),to=te.object({option:te.enum(["departAt","arriveBy"]),date:te.string().describe("ISO 8601")}),ro=te.object({maxAlternatives:te.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:eo.optional(),when:to.optional().describe("Omit to depart now.")}),no=te.union([qt,te.object({success:te.literal(!0)}),Pt]),so=te.object({locations:te.array(bn).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:ro.optional().describe("Routing options. Omitted fields keep their current values."),showOnMap:te.boolean().describe("Show the route on the map after calculation."),showWaypoints:te.boolean().optional().describe("Show the origin/stop/destination pins (default true). Pass false to draw the route line without waypoint markers — e.g. a monitored traffic corridor where the pins add noise. Only applies when showOnMap is true."),showSummaryBubbles:te.boolean().optional().describe("Show the per-route ETA/summary bubbles (default true). Pass false to drop them — e.g. when monitoring traffic on a corridor, where the bubbles add noise. Only applies when showOnMap is true."),monitor:te.boolean().optional().describe('Arm a live route monitor on the new route immediately (default false) — recalculates the same route every 60s so its live-traffic delays stay current, no separate startRouteMonitor call needed. Pair with `costModel.traffic: "historical"` + `maxAlternatives: 1-2` so the typical paths stay stable while delays refresh. Ignored when no route is calculated.'),hidePreviousEntries:Rr("routes")}).refine(e=>void 0!==e.locations||void 0!==e.parameters,{message:"Provide at least one of `locations` or `parameters`."}),oo=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},ao=e=>{const{maxAlternatives:t=0,costModel:r,when:n}=e;return{...t>0&&{maxAlternatives:t},...r&&{costModel:{...r,...r.avoidAreas&&{avoidAreas:r.avoidAreas}}},...n&&{when:{option:n.option,date:new Date(n.date)}}}},io=(e,t)=>()=>{const r=Me({locations:e,...ao(t)});return A(r)},lo=async(e,t,r,n,s,o,a,i)=>{const l=Me({locations:t,...ao(e.routing.params),signal:i?.signal}),c=await A(l),d=await e.routing.addRoutes(c,t,((e,t)=>{const r=e.features[0]?.properties?.summary,n=[r?W(r.travelTimeInSeconds):void 0,r?G(r.lengthInMeters):void 0].filter(Boolean),s=n.length?` (${n.join(", ")})`:"",o=t.map(Zn).filter(Boolean);if(o.length>=2){const e=o[0],t=o.at(-1),r=o.slice(1,-1);return`${r.length>0?`${e} via ${r.join(", ")} to ${t}`:`${e} to ${t}`}${s}`}return`Route${s}`})(c,t));if(r&&await(async(e,t,r,n,s,o,a)=>{await mn(e.routing,[t],s),await e.routing.showEntry(t,{showWaypoints:!1!==o,showSummaryBubbles:!1!==a});const i=q(r);i&&e.baseMap.mapLibreMap.fitBounds(i,{padding:50})})(e,d,c,0,n,o,a),s){const t=e.routing.entries.find(e=>e.id===d);t&&e.routing.startMonitoring(d,{recalculate:io(t.waypoints,t.params)})}return{...as(c),entryId:d}},co=te.union([qt,Pt]),uo=te.object({origin:bn.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:bn.optional().describe("Append a new destination after the current destination. The previous destination is kept as an intermediate stop (it is not replaced)."),stops:te.array(bn).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:te.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Rr("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`."}),po=e=>te.union([jt().extend({shown:Nr.optional(),placesEntryId:te.string().optional(),label:te.string().optional().describe('Human-readable label stored with the entry (e.g. "cafe", "CINEMA (50 places)").'),geometriesFetched:te.number().optional().describe("Count of boundary polygons fetched and cached on the entry (when `geometries` was set)."),geometriesShown:te.boolean().optional().describe("Whether the fetched polygons were rendered on the map."),resolvedAreas:Ot.optional()}),Pt]),ho=(po(),te.object({mode:te.literal("maxDetour"),routeId:te.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),maxDetourTimeSeconds:te.number().positive().describe("Max extra travel time (seconds) the user accepts to reach a POI."),sortBy:te.enum(["detourTime","detourOffset"]).optional().describe('"detourTime" (default) ranks by extra travel time; "detourOffset" by distance from the route line.'),limit:te.number().max(100).optional().describe("Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.")})),yo=e=>{const t={query:te.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:te.union([te.object(Yr).refine(e=>{const t=!0===e.viewport,r=void 0!==e.queries||void 0!==e.placeIds||void 0!==e.geometries||void 0!==e.range||void 0!==e.route;return(!t||!r)&&(t||r)},{message:"`within` requires EITHER `viewport: true` OR one or more multi-region fields (queries / placeIds / geometries / range / route), not both."}),Ur,ho,zr]).describe('Geographic scope, by `mode`: `within` (area — `viewport` alone, OR any combo of queries/placeIds/geometries/range/route) | `nearby` (point bias — position/viewport/query, optional radiusMeters) | `maxDetour` (ranked detour list off a route) | `global` (no bias). Default: `{ mode: "within", viewport: true }`.').optional(),poiCategories:te.array(te.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:jr.optional().describe("Display places/geometries on the map. Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry."),entryId:Tr,geometries:_r};return te.object(t).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`)."})},mo=(yo(),e=>{const{geometries:t}=e;return t?.length?{geometries:t}:{}}),fo=(e,t)=>{const{query:r,poiCategories:n,bias:s,radiusMeters:o,multiFilters:a}=e,i=((e,t)=>e?.position?{position:e.position,radiusMeters:t}:e?.boundingBox?{boundingBox:e.boundingBox}:void 0)(s,o),l=Me({query:r,poiCategories:n,signal:t?.signal,limit:100,...mo(a),...i&&{geoBias:i}});return O(l)},go=async(e,t,r,n)=>{if("nearby"===e.mode){const s=await(async(e,t,r)=>{if(e.position)return{bias:{position:e.position},radiusMeters:e.radiusMeters};const n=await ps({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},Ts(t,r)),s=n.label?[{matched:n.label,...void 0!==n.query&&{query:n.query}}]:[];return{bias:n.position?{position:n.position}:{},radiusMeters:e.radiusMeters,...s.length>0&&{resolvedAreas:s}}})(e,t,n);return{bias:s.bias,multiFilters:r,radiusMeters:s.radiusMeters,resolvedAreas:s.resolvedAreas}}if("within"===e.mode){const s=await(async(e,t,r,n)=>{if(e.viewport)return{bias:{boundingBox:Ds(t.baseMap)},multiFilters:r};const s=await us({boundingBox:void 0,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},Ts(t,n));if(ys(s))return s;const o=s.filter(e=>e.polygon).map(e=>e.polygon),a=s.filter(e=>!e.polygon).map(e=>e.bbox),i=s.find(e=>"route"===e.source)?.label,l=s.filter(e=>"query"===e.source&&e.label).map(e=>({matched:e.label,...void 0!==e.query&&{query:e.query}})),c=[...a],d=[...o],u={...r,...c.length&&{boundingBoxes:c},...d.length&&{geometries:d}},p=d.length?void 0:F(a);return{bias:p?{boundingBox:p}:{},multiFilters:u,range:e.range,routeLabel:i,...l.length>0&&{resolvedAreas:l}}})(e,t,r,n);return"error"in s?s:{bias:s.bias,multiFilters:s.multiFilters,range:s.range,routeLabel:s.routeLabel,resolvedAreas:s.resolvedAreas}}return{bias:{},multiFilters:r}},bo=async(e,t,r,n,s,o,a,i)=>{const l=n?await Ms(e,[r],n):void 0;let c,d;if(s){const t=await ks(e,r,s,i);c=t.fetched,t.shown&&(d=!0)}const u=e.places.entries.find(e=>e.id===r)?.label;return{...ss(t),placesEntryId:r,...u&&{label:u},...l&&{shown:l},...void 0!==c&&{geometriesFetched:c},...void 0!==d&&{geometriesShown:d},...a&&a.length>0&&{resolvedAreas:a}}},wo=e=>async(e,t,r)=>{const{query:n,where:s,poiCategories:o,show:a,entryId:i,geometries:l}=e,{resolved:c,unresolved:d}=await Fn(o,r);if(o?.length&&!c)return{error:`No POI categories matched: ${d.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 u=s??{mode:"within",viewport:!0},p={};try{if("maxDetour"===u.mode){const e=await(async(e,t,r,n,s,o)=>{const a=e.routing.entries;if(0===a.length)return{error:"No routes in state. Calculate a route first via setRoute."};const i=t.routeId?a.find(e=>e.id===t.routeId):a.at(-1);if(!i)return{error:`No route found with id "${t.routeId}". Use recallState to list available routes.`};const l=i.data.features[0];if(!l)return{error:`Route entry "${i.id}" has no geometry.`};const c=Me({route:l,maxDetourTimeSeconds:t.maxDetourTimeSeconds,...r&&{query:r},...n&&{poiCategories:n},...t.sortBy&&{sortBy:t.sortBy},...void 0!==t.limit&&{limit:t.limit},signal:o?.signal}),d=await k(c);return{result:d,placesEntryId:await e.places.addPlaceResult(d,Qn(d,{query:r,poiCategories:n,routeLabel:i.label}),s)}})(t,u,n,c,i,r);return"error"in e?e:bo(t,e.result,e.placesEntryId,a,l,0,void 0,r)}const e=await go(u,t,p,r);if("error"in e)return e;const{bias:s,multiFilters:o,radiusMeters:d,range:h,routeLabel:y,resolvedAreas:m}=e;if(h){const e=await(async(e,t)=>{const{state:r,range:n,query:s,resolvedPoiCategories:o,multiFilters:a,entryId:i,routeLabel:l}=e,c=qn(r,n);if("error"in c)return c;const d=[...c.polygons,...a.geometries??[]],u=await fo({query:s,poiCategories:o,multiFilters:{...a,geometries:d}},t);return{result:u,placesEntryId:await r.places.addPlaceResult(u,Qn(u,{query:s,poiCategories:o,...l&&{routeLabel:l}}),i)}})({state:t,range:h,query:n,resolvedPoiCategories:c,multiFilters:o,entryId:i,routeLabel:y},r);return"error"in e?e:bo(t,e.result,e.placesEntryId,a,l,0,m,r)}const f=(m?.length?m.map(e=>e.matched).join(", "):void 0)??(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})(u),g=await(async(e,t)=>{const{state:r,query:n,bias:s,resolvedPoiCategories:o,radiusMeters:a,multiFilters:i,entryId:l,whereLabel:c,routeLabel:d}=e,u=await fo({query:n,poiCategories:o,bias:s,radiusMeters:a,multiFilters:i},t);return{result:u,placesEntryId:await r.places.addPlaceResult(u,Qn(u,{query:n,poiCategories:o,where:c,...d&&{routeLabel:d}}),l)}})({state:t,query:n,bias:s,resolvedPoiCategories:c,radiusMeters:d,multiFilters:o,entryId:i,whereLabel:f,routeLabel:y},r);return bo(t,g.result,g.placesEntryId,a,l,0,m,r)}catch(h){return{error:`Search failed: ${h instanceof Error?h.message:String(h)}`}}},vo=(wo(),{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/placeIds over bbox. Also OWNS outlining the SUB-AREAS of a place — the neighbourhoods/districts of a city ("outline the Amsterdam neighbourhoods"); a SINGLE named place\'s own boundary is locatePlace, and incrementally adding/removing outlines among already-shown places is updatePlacesDisplay. Skip for already-stored places — use recallState/processData.',tags:["discover","place","location"],examples:['discoverPlaces({ poiCategories: ["RESTAURANT", "CAFE", "BAR"], entryId: "amsterdam-food-spots" }) // batch many categories + semantic id','discoverPlaces({ query: "coffee", where: { mode: "nearby", position: [4.9, 52.4], radiusMeters: 500 } })','discoverPlaces({ query: "parking", where: { mode: "within", queries: [{ query: "De Jordaan, Amsterdam", queryAs: "place" }] } }) // area name goes in `where.queries`, NOT top-level `query`','discoverPlaces({ query: "coffee", where: { mode: "nearby", query: "Schiphol Airport", queryAs: "poi", radiusMeters: 2000 } }) // POI-resolved bias point','discoverPlaces({ poiCategories: ["HOTEL"], where: { mode: "within", queries: [{ query: "Amsterdam" }, { query: "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 } } }) // 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","Search for EV chargers in a corridor along my planned 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","Find playgrounds in this neighbourhood","Show pharmacies near Berlin Hauptbahnhof"],relatedTools:["getPOICategoryCodes","updatePlacesDisplay","locatePlace","getViewport","findReachableAreas","processData","analyseData"],dependsOn:["getPOICategoryCodes","setRoute"]}),Io=te.object({type:te.enum(["timeMinutes","distanceKM"]).describe("Budget type including its unit."),value:te.number().describe("Budget value. e.g. 30 for 30 minutes, 50 for 50 km.")}),Eo=te.object({origins:te.array(bn).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:te.array(Io).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:te.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:te.boolean().describe("Whether to render the range polygon(s) on the map."),showOriginPin:te.boolean().optional().describe("Whether to drop a pin at each origin. Default: true."),hidePreviousEntries:Rr("ranges")}),So=te.object({rangesId:te.string(),ranges:te.array(te.object({originName:te.string(),budgets:te.array(Io)})),skipped:te.array(te.object({origin:te.string(),reason:te.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry).")}),ko=te.union([So,te.object({status:te.enum(["no_results"])}),Pt]),Mo=async(e,t,r)=>{const n=e.map(e=>Me({origin:t,budget:{type:e.type,value:e.value}})),s=await M(n,{signal:r?.signal});return 0===s.features.length?null:s},Ao=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,xo=e=>"query"in e?e.query:"position"in e?`${e.position[1]}, ${e.position[0]}`:"placeIdOrEntryId"in e?e.placeIdOrEntryId:"unnamed origin",Do=te.union([te.object({count:te.number().describe("Number of codes returned"),codes:te.array(te.string()).describe('POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. "RESTAURANT") also accepted by toggleTilesPOIs categories.')}),Pt]),To=te.object({filters:te.array(te.string()).optional().describe("Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes."),language:te.string().describe('Locale e.g. "en-GB", "fr-FR", "es-ES", "de-DE".')}),Po=te.object({speed:te.number().optional(),freeFlowSpeed:te.number().optional(),congestionLevel:te.number().optional(),travelTime:te.number().optional(),networkLength:te.number().optional()}),_o=te.union([te.object({entryId:te.string().describe("Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData."),name:te.string().optional(),timezone:te.string().optional(),dateRange:te.object({start:te.string(),end:te.string()}),baseData:Po,metrics:te.array(te.string()),tileCount:te.number(),availableGranularities:te.array(te.string())}),Pt]),Oo=te.object({location:bn.optional().describe("Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Provide this or bbox, never both."),bbox:te.array(te.number()).length(4).optional().describe("[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Provide this or location, never both."),showOnMap:te.boolean().optional().describe("Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize."),startDate:te.string().optional().describe("Start date 'YYYY-MM-DD'. Use with endDate for a continuous range (max 31 days)."),endDate:te.string().optional().describe("End date 'YYYY-MM-DD' (inclusive). Defaults to today if omitted."),days:te.array(te.string()).optional().describe("Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate."),metrics:te.union([te.literal("all"),te.array(te.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:te.union([te.literal("all"),te.array(te.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:te.union([te.literal("all"),te.array(te.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.")}).refine(e=>void 0===e.location!=(void 0===e.bbox),{message:"Provide exactly one area: either `location` or `bbox`."}).refine(e=>!(e.days?.length&&(e.startDate||e.endDate)),{message:"Provide either `days` or a `startDate`/`endDate` range, not both."}),Co=e=>`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`,Ro=e=>e instanceof Date?Number.isNaN(e.getTime())?void 0:Co(e):"string"==typeof e?e.slice(0,10):void 0,jo=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),No=te.union([te.object({count:te.number(),entryId:te.string().optional(),resolvedAreas:Ot.optional(),entries:te.array(te.object({id:te.string(),label:te.string(),count:te.number(),timestamp:te.number()})),monitoring:te.boolean().optional().describe("True when this fetch also armed a background monitor on the new entry.")}),Pt]),Bo=te.object({...Yr,boundingBox:Sr.optional().describe("Single bbox [W,S,E,N]. Composes with other multi-region fields (union); may also be combined with viewport, in which case these explicit bounds take precedence over it.")}).refine(e=>!0===e.viewport||void 0!==e.boundingBox||void 0!==e.queries||void 0!==e.placeIds||void 0!==e.geometries||void 0!==e.range||void 0!==e.route,{message:"`within` requires `viewport: true` OR one or more multi-region fields (boundingBox / queries / placeIds / geometries / range / route)."}),Lo=te.union([Bo]).describe('Geographic scope. Only `within` mode (single area, defaults to `{ mode: "within", viewport: true }`). Use `viewport` alone for "the network / right now / this area", or any combination of boundingBox / queries / placeIds / geometries / range / route — each resolves to a bbox and the union is sent to the SDK (≤10,000 km²).'),Fo=`Filter by incident category (server-side). Canonical values: ${Z.join(", ")}. Map the user's wording to the closest value. If omitted, all categories are returned.`,qo=te.object({where:Lo.optional().describe('Geographic scope. Default: `{ mode: "within", viewport: true }`.'),categoryFilter:te.array(te.enum([...Z])).optional().describe(Fo),timeValidityFilter:te.array(te.enum(["present","future"])).optional().describe("Which incidents to fetch by time-validity. Default ['present'] — incidents active RIGHT NOW. Only pass 'future' when the user EXPLICITLY asks about upcoming, planned, or future incidents."),label:te.string().optional().describe('Short human-readable label for this fetch — name the place ("Central London", "Amsterdam ring", "M25 corridor"), not coordinates. Surfaced verbatim in the monitor chip and on the entry chip in the UI; also used by analyseData / focusIncidents to disambiguate which entry to operate on. Always pass one when show is true (the default); the bbox-coordinate fallback ("bbox a,b → c,d") is for hidden (show: false) intermediate fetches only.'),show:te.boolean().optional().describe("Default: true — incidents auto-render so the user can confirm the agent is looking at the right area. Set false ONLY for an extra throwaway load the user should not see — e.g. a second area fetched purely to compare counts. It is NOT needed to run clusterIncidents or analyseData: those read the entry you already loaded, so never re-fetch an area (shown or not) just to analyse it."),fitBounds:te.boolean().optional().describe("Default: true. Pass false to skip camera move after rendering."),monitor:te.boolean().optional().describe('Keep this entry live: poll its area in the background and re-run any registered analyses on each tick. Defaults to true for a shown fetch — live incidents go stale otherwise. Pass false for a one-shot snapshot (e.g. "how many incidents right now?"). Ignored when `show` is false (a hidden throwaway load is never monitored).'),monitorIntervalMs:te.number().int().positive().optional().describe("Polling interval in ms when monitoring. Default 60000 (60s).")}),$o=async(e,t,r)=>{const n=[];if(void 0!==e.range){const r=((e,t)=>{const r=qn(e,t);return"error"in r?r:{bboxes:r.polygons.map(e=>q(e)).filter(e=>!!e)}})(t,e.range);if("error"in r)return r;n.push(...r.bboxes)}const s={viewport:e.viewport,boundingBox:e.boundingBox,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},o=await us(s,Ts(t));if(ys(o))return o;n.push(...o.map(e=>e.bbox));const a=o.filter(e=>"query"===e.source&&e.label).map(e=>({matched:e.label,...void 0!==e.query&&{query:e.query}})),i=o.find(e=>"route"===e.source)?.label;return 0===n.length?{error:"No area resolved from `where`."}:{bbox:F(n),routeLabel:i,...a.length>0&&{resolvedAreas:a}}},Go=te.object({query:te.string().describe("Location string to resolve for downstream routes or searches."),waypointIndex:te.number().int().min(0).optional().describe("Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination."),queryAs:te.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, geographies.'),where:Hr.optional().describe('Optional geographic scope. OMIT to resolve a uniquely-named place anywhere (mode "global", the default) — correct for landmarks, cities, and areas like "Schiphol" or "Westminster", on-screen or not. Use "nearby" (a SOFT bias toward a point / viewport centre / query) only to disambiguate same-named places. Use "within" only to HARD-restrict the result to a known area (boundingBox / query / placeId).'),show:jr.optional().describe("Map display actions after resolving. Skip for intermediate calculations."),entryId:Tr,geometry:_r}),Wo=te.union([Nt.extend({placesEntryId:te.string().describe("Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData, or as `placeIdOrEntryId` to setRoute / addWaypointsToRoute to route to this place."),waypointIndex:te.number().optional(),shown:Nr.optional(),geometryFetched:te.boolean().optional().describe("Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set)."),geometryShown:te.boolean().optional().describe("Whether the fetched polygon was rendered on the map.")}),Pt]),Uo=async(e,t,r)=>e&&"global"!==e.mode?"within"===e.mode?(async(e,t,r)=>{const n=await us({viewport:e.viewport,boundingBox:e.boundingBox,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0,placeIds:e.placeId?[e.placeId]:void 0},Ts(t,r));return ys(n)?n:0===n.length?{bias:void 0}:{bias:{boundingBox:F(n.map(e=>e.bbox))}}})(e,t,r):(async(e,t,r)=>{if(e.position)return{bias:{position:e.position}};const{position:n}=await ps({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},Ts(t,r));return{bias:n?{position:n}:void 0}})(e,t,r):{bias:void 0},zo=te.union([qt,Pt]),Ho=te.object({waypointIndices:te.array(te.number().int()).min(1).describe("Indices of waypoints to remove. 0=origin, last=destination. Min 2 waypoints must remain. Duplicates are not allowed."),showOnMap:te.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Rr("routes")}),Yo=te.union([qt,Pt]),Vo=te.union([te.literal("origin"),te.literal("destination"),te.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)."),Jo=te.object({waypointIndex:Vo,location:bn.describe("The new waypoint that replaces the targeted one."),showOnMap:te.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:Rr("routes")}),Ko=te.union([Nt.extend({placesEntryId:te.string().describe("Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData.")}),Pt]),Xo=te.object({position:Ir.describe("[longitude, latitude] GeoJSON position to reverse geocode")}),Zo=te.union([te.object({byodEntryId:te.string().describe("Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData)."),label:te.string(),source:te.union([te.object({kind:te.literal("url"),url:te.string()}),te.object({kind:te.literal("inline")})]).describe("Where the data came from."),profile:_t.describe("Runtime-inferred shape — geometry types plus a per-property profile (types, coverage, numeric/boolean examples; string example values are withheld). Use it to pick fields for analyseData / processData without re-fetching the raw GeoJSON, and to choose the `setByodLayers` layers that render this entry.")}),Pt]),Qo=te.object({label:te.string().describe('Human-readable label for the new entry (e.g. "Sales territories", "Customer pins 2026-Q1").'),url:te.url().optional().describe("URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. EXCLUSIVE with `data`."),data:te.unknown().optional().describe('Inline GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`). EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.'),entryId:Tr}).refine(e=>e.url?!e.data:!!e.data,{message:"Pass EXACTLY ONE of `url` or `data`."}),ea=26214400,ta=/* @__PURE__ */new Set(["http:","https:"]),ra=async e=>{const t=new AbortController,r=setTimeout(()=>t.abort(),15e3);try{const r=await fetch(e,{signal:t.signal});if(!r.ok)return{error:`Failed to fetch "${e}": HTTP ${r.status} ${r.statusText}`};const n=((e,t)=>{const r=e.headers.get("content-length");if(!r)return;const n=Number.parseInt(r,10);return Number.isFinite(n)&&n>ea?{error:`Response too large for "${t}": advertised ${n} bytes (max 26214400).`}:void 0})(r,e);if(n)return n;const s=await(async(e,t)=>{const r=e.body?.getReader();if(!r){const r=await e.text();return r.length>ea?{error:`Response too large for "${t}": ${r.length} bytes (max 26214400).`}:{value:r}}const n=[];let s=0;for(;;){const{done:e,value:o}=await r.read();if(e)break;if(s+=o.byteLength,s>ea)return await r.cancel(),{error:`Response exceeded 26214400 bytes while streaming "${t}". Refusing to load.`};n.push(o)}const o=new Uint8Array(s);let a=0;for(const i of n)o.set(i,a),a+=i.byteLength;return{value:(new TextDecoder).decode(o)}})(r,e);return"error"in s?s:{value:JSON.parse(s.value)}}catch(n){return((e,t)=>e instanceof Error&&"AbortError"===e.name?{error:`Fetch timed out after 15000ms for "${t}".`}:{error:`Failed to fetch "${t}": ${e instanceof Error?e.message:String(e)}`})(n,e)}finally{clearTimeout(r)}},na=[...jn,"previous","now","log"],sa=async(e,t,r)=>{const n=await Ye(r.code,na,[...Bn(t.sandbox,{h3:re,turf:ee}),r.previous,new Date(r.sampledAt),()=>{}],"Analysis",e.codeExecution);return"error"in n?n:$e(n.value,r.outputFormat)},oa=(Kr(Jr),te.union([te.object({affectedEntries:te.array(te.union([te.object({kind:te.literal("places"),id:te.string()}),te.object({kind:te.literal("routes"),id:te.string()}),te.object({kind:te.literal("incidents"),id:te.string()}),te.object({kind:te.literal("customGeometries"),id:te.string()}),te.object({kind:te.literal("trafficAreaAnalytics"),id:te.string()}),te.object({kind:te.literal("byod"),id:te.string()})])).describe("Entries the analysis was attached to: every contributing places / routes / incidents / customGeometries / trafficAreaAnalytics / byod entry. Ranges entries feed the aggregation but do not carry analyses."),sourceIds:te.array(te.unknown()).optional().describe(`${hr} (only present when \`geometriesEntryIDs\` was set).`),analysisId:te.string().describe("Stable handle for this analysis. Pass to `monitorAnalysis` to keep it recomputing whenever its source entries change."),name:te.string().describe("Unique name of the analysis within each affected entry."),description:te.string().optional(),outputFormat:te.enum(["json","chart"]),monitoring:te.boolean().optional().describe("True when `monitor: true` was set — the analysis now recomputes on every source change."),analysis:te.unknown(),skipped:te.array(vn).optional().describe(pr)}),Pt])),aa=(e,t)=>{const r=Zr(e,t),n=`At least one of ${en(r)} is REQUIRED.`,s={...nn(r,{verb:"analyse",requiredHint:n})};return s.name=te.string().describe('Unique name within each affected entry (e.g. "incidents-per-cluster", "ev-near-route"). Reusing a name replaces the previous analysis on those entries.'),s.description=te.string().optional().describe("Optional short description of what the analysis computes."),s.outputFormat=te.enum(["json","chart"]).optional().describe(Fe),s.code=te.string().describe(((e,t)=>{const r=Zr(e,t),n=tn(r),s=rn(r),o=r.includes("customGeometries");return"Async JS that aggregates the injected inputs and returns the result.\n\n"+Nn(r,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading (\`if (places) ...\`, \`routes?.features.length\`, etc.). When defined:\n${n}\n\n${qe("counts, per-kind breakdowns, hex bins, intersections, distance bins")}\n\n`+(o?`${ur}\n\n`:"")+s})(e,t)),s.monitor=te.boolean().optional().describe("When true, keep this analysis live — it recomputes on every change to its source entries (a monitored incidents poll, a re-search, …), no separate monitorAnalysis call needed. Omit (or false) for a one-shot run; you can still toggle it later via monitorAnalysis using the returned `analysisId`."),te.object(s).refine(sn,{message:on(r)})},ia=(aa(Jr,void 0),(e,t)=>{const r=Zr(e,t).join(" / "),n="CONTRAST `processData`, which writes NEW renderable map entries (places / custom-geometries / BYOD); `analyseData` never renders or creates entries — only attaches metadata. Returns an `analysisId`; pass it to `monitorAnalysis` to keep the analysis recomputing whenever its source entries change.";return t?`Aggregate ${r} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ${n} Each scoped input is exposed as a merged collection and a per-entry record; fields are \`undefined\` when their \`*EntryIDs\` argument is omitted (guard with \`?.\`). Result is attached as \`_analysis[name]\` on every contributing entry. \`outputFormat: "json"\` (default) or \`"chart"\` (Chart.js config).`:`Aggregate ${r} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ${n} Each input is exposed as a merged collection and a per-entry record; fields are \`undefined\` when their \`*EntryIDs\` argument is omitted (guard with \`?.\` / \`if (places) …\`). Result is attached as \`_analysis[name]\` on every contributing entry (ranges feed in but do not carry analyses). \`outputFormat: "json"\` (default) or \`"chart"\` (Chart.js config).`}),la=(ia(Jr,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all).`),ca=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i,name:l,description:c,code:d}=e,u=e.outputFormat??"json",p=await Tn({placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i},t);if("error"in p)return{error:p.error};const{resolved:h,sandbox:y,geometries:m}=p.value,f=await Ye(d,jn,Bn(y,{h3:re,turf:ee}),"Analysis",t.codeExecution);if("error"in f)return{error:f.error};const g=$e(f.value,u);if("error"in g)return{error:g.error};const b=g.value,w=Date.now(),v=[...h.places.map(e=>({kind:"places",id:e.id})),...h.routes.map(e=>({kind:"routes",id:e.id})),...h.incidents.map(e=>({kind:"incidents",id:e.id})),...h.trafficAreaAnalytics.map(e=>({kind:"trafficAreaAnalytics",id:e.id})),...h.byod.map(e=>({kind:"byod",id:e.id})),...m.affectedEntries.filter(e=>"places"===e.kind&&!h.places.some(t=>t.id===e.id)).map(e=>({kind:"places",id:e.id})),...m.affectedEntries.filter(e=>"customGeometries"===e.kind).map(e=>({kind:"customGeometries",id:e.id}))],I=o?m.sourceIds:void 0,E=v.map(e=>e.id),S=((e,t)=>`${e}::${[...t].sort((e,t)=>e.localeCompare(t)).join(",")}`)(l,E),k=!0===e.monitor;return((e,t)=>{const r=()=>e.analyses.register({analysisId:t.analysisId,name:t.name,description:t.description,outputFormat:t.outputFormat,affectedEntryIds:t.affectedEntryIds}),n=e.analyses.job(t.analysisId);if(n&&n.code===t.code)return r(),void(t.monitor&&n.setActive(!0));n&&e.analyses.resetHistory(t.analysisId);const s=t.monitor||(n?.active??!1);let o;n?.unregister(),r(),o=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:s,run:async()=>{if(!e.analyses.get(t.analysisId))return;const r=await Tn(t.inputs,e);if("error"in r)return;const n=xn(r.value.resolved)??Date.now(),s=await sa(e,r.value,{code:t.code,previous:e.analyses.lastResult(t.analysisId),sampledAt:n,outputFormat:t.outputFormat});"error"in s||e.analyses.job(t.analysisId)!==o||e.analyses.attachResult(t.analysisId,{name:t.name,...t.description&&{description:t.description},outputFormat:t.outputFormat,data:s.value,timestamp:n})},onOrphaned:()=>e.analyses.remove(t.analysisId)}),e.analyses.setJob(t.analysisId,o)})(t,{analysisId:S,name:l,description:c,outputFormat:u,code:d,inputs:{placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i},affectedEntryIds:E,monitor:k}),t.analyses.attachResult(S,{name:l,description:c,outputFormat:u,data:b,timestamp:w}),{affectedEntries:v,...I&&{sourceIds:I},analysisId:S,name:l,...c&&{description:c},outputFormat:u,monitoring:k,analysis:b,...m.skipped.length&&{skipped:m.skipped}}},da=te.object({trackerId:te.string().optional().describe("Which tracker to clear. Omit when only one tracker exists.")}),ua=te.union([te.object({cleared:te.literal(!0),trackerId:te.string(),name:te.string()}),Pt]),pa=async(e,t,r,n)=>{const s=e.trafficIncidents.entries.find(e=>e.id===t);if(!s)return{error:`unknown incidents entry "${t}"`};if("params"===r.kind){const{groups:e}=ft(s.data,r.params,n?.groups,s.timestamp,n?.sampledAt);return{value:{groups:e,sampledAt:s.timestamp}}}const o=await Tn(r.inputs,e);if("error"in o)return o;const a=await sa(e,o.value,{code:r.code,previous:n,sampledAt:s.timestamp,outputFormat:"json"});return"error"in a?a:(i=a.value)&&"object"==typeof i&&Array.isArray(i.groups)?{value:a.value}:{error:"clustering `code` must return a cluster(...) result (`{ groups }`)"};var i},ha=async(e,t,r)=>{ga(e);const n=e.trafficIncidents.getClustersRecord(t),s=n&&(o=n.recipe,a=r,JSON.stringify(o)===JSON.stringify(a))?n.output:void 0;var o,a;const i=await pa(e,t,r,s);return"error"in i||e.trafficIncidents.setClusters(t,{recipe:r,output:i.value}),i},ya=/* @__PURE__ */new WeakMap,ma=e=>{let t=ya.get(e);return t||(t={running:!1,scheduled:!1,pending:/* @__PURE__ */new Set},ya.set(e,t)),t},fa=/* @__PURE__ */new WeakSet,ga=e=>{fa.has(e)||(fa.add(e),e.trafficIncidents.events.on("entries-change",({changedIds:t})=>ba(e,t)))},ba=(e,t)=>{const r=ma(e);for(const n of t)r.pending.add(n);r.running||r.scheduled||(r.scheduled=!0,queueMicrotask(()=>{r.scheduled=!1,wa(e)}))},wa=async e=>{const t=ma(e),r=t.pending;t.pending=/* @__PURE__ */new Set,t.running=!0;try{for(const t of r){const r=e.trafficIncidents.getClustersRecord(t);if(!r)continue;const n=r.recipe,s=await pa(e,t,n,r.output);"value"in s&&e.trafficIncidents.setClusters(t,{recipe:n,output:s.value})}}finally{t.running=!1}},va=(e,t)=>{const r=t?Zr(e,t).filter(e=>"incidents"!==e):[];return{context:r,active:["incidents",...r]}},Ia=e=>"OPTIONAL — omit for the default DBSCAN over all incidents (tune eps / minMembers / maxClusters). Async JS body: filter/combine the inputs, then RETURN `cluster(...)` (the only way to build the required `{ groups }` result) — e.g. `return cluster(filtered, { eps: 0.4 }, previous, now);`.\n\n"+Nn(e,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${tn(e)}\n\n`+rn(e),Ea=(e,t)=>{const{context:r,active:n}=va(e,t);return te.object({incidentsEntryID:te.string().describe("Entry id from getTrafficIncidents — the incidents to cluster."),eps:te.number().positive().optional().describe("DBSCAN radius in km. Default 0.5. Use 0.3–0.5 for dense urban, 1+ for highways. Ignored when `code` is set."),minMembers:te.number().int().min(1).optional().describe("Minimum incidents per cluster. Default 3. Ignored when `code` is set."),maxClusters:te.number().int().min(1).optional().describe("Top N clusters by total delay. Default 6. Ignored when `code` is set."),...nn(r,{verb:"analyse",requiredHint:"Only used by custom `code`."}),code:te.string().optional().describe(Ia(n))})},Sa=(Ea(Jr,void 0),te.union([te.object({incidentsEntryID:te.string(),clusterCount:te.number(),clusters:te.array(te.object({id:te.string(),primaryRoads:te.array(te.string()),primaryCategory:te.string(),size:te.number(),totalDelaySeconds:te.number(),peakDelaySeconds:te.number(),trend:te.enum(["growing","fading","steady","new"])}))}),Pt])),ka="DBSCAN clustering of traffic incidents into compact pockets — stable IDs (survive monitor ticks), road/category labels, delay aggregates, multi-poll trends. Stored as dedicated clustering state on the entry and re-run each monitor tick (stays live without re-calling); the UI pins each group. Returns structured fields (roads, category, size, delay, trend) for the caller to phrase. One clustering per entry (re-calling replaces it). Defaults to DBSCAN over all incidents (tune eps / minMembers / maxClusters); pass `code` to filter/combine incidents dynamically first. Pair with getTrafficIncidents + setTrafficIncidentsMonitor.",Ma=e=>{const t=e.filter(e=>"incidents"!==e);return t.length?`${ka} Custom \`code\` can also filter incidents against ${t.join(" / ")}.`:ka},Aa=(Ma(["incidents"]),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` — always include "incidents" (the clustering source). Add another kind ONLY when the query filters incidents against it (e.g. "near depots" → places, "along the route" → routes, "within this zone" → customGeometries). Default: \`{ kinds: ["incidents"] }\`.`),xa=async(e,t)=>{const{incidentsEntryID:r,eps:n,minMembers:s,maxClusters:o,code:a}=e,i=t.trafficIncidents.entries.find(e=>e.id===r);if(!i)return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const l={...null!=n&&{eps:n},...null!=s&&{minMembers:s},...null!=o&&{maxClusters:o}},c=a?{kind:"code",code:a,inputs:{incidentsEntryIDs:[r],...e.placesEntryIDs?.length&&{placesEntryIDs:e.placesEntryIDs},...e.routesEntryIDs?.length&&{routesEntryIDs:e.routesEntryIDs},...e.geometriesEntryIDs?.length&&{geometriesEntryIDs:e.geometriesEntryIDs},...e.trafficAreaAnalyticsEntryIDs?.length&&{trafficAreaAnalyticsEntryIDs:e.trafficAreaAnalyticsEntryIDs},...e.byodEntryIDs?.length&&{byodEntryIDs:e.byodEntryIDs}}}:{kind:"params",params:l},d=await ha(t,i.id,c);if("error"in d)return{error:`Clustering failed for entry "${i.id}": ${d.error}`};const u=d.value;return{incidentsEntryID:i.id,clusterCount:u.groups.length,clusters:u.groups.map(e=>({id:e.id,primaryRoads:e.primaryRoads,primaryCategory:e.primaryCategory,size:e.size,totalDelaySeconds:e.totalDelaySeconds,peakDelaySeconds:e.peakDelaySeconds,trend:e.trend}))}},Da=(()=>{const e=`At least one of ${en(Jr)} is REQUIRED.`,t={...nn(Jr,{verb:"analyse",requiredHint:e})};return t.name=te.string().describe('Short tracker label, e.g. "Incidents near hospitals".'),t.rule=te.string().describe('The watched condition in plain language, e.g. "an incident within 100m of a hospital". For display.'),t.code=te.string().describe("JS that inspects the injected inputs and returns a VERDICT describing whether the watched condition holds RIGHT NOW. Pure + synchronous — no fetch/network/await.\n\n"+Nn(Jr,!1)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${tn(Jr)}\n\nReturn exactly:\n\`{ active: boolean, members: { entryId: string, featureIds: string[] }[], summary: string }\`\n- \`active\`: is the condition met now? (false→true logs an alert; true→false resolves it)\n- \`members\`: the features that matched, GROUPED BY their source entry id — include EVERY entity the alert is about (e.g. the hospital AND the nearby incidents), each under its own entry id.\n- \`summary\`: a short grounded headline built from the data (e.g. "Incident 80m from St Mary's Hospital").\n\n`+rn(Jr)),te.object(t).refine(sn,{message:on(Jr)})})(),Ta=te.union([te.object({trackerId:te.string(),name:te.string(),rule:te.string(),watching:te.array(te.string()).describe("Source entry ids the rule reads."),firingNow:te.boolean().describe("Whether the condition holds at arm time. If true, one `opened` alert fires for it."),summary:te.string().describe("The arm-time verdict summary.")}),Pt]),Pa=(e,t)=>{const r=e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"tracker",n=new Set(t.trackers.trackers.map(e=>e.id));if(!n.has(r))return r;for(let s=2;;s++)if(!n.has(`${r}-${s}`))return`${r}-${s}`},_a=te.union([te.object({incidentsEntryID:te.string().describe("The entry the focus was applied to."),focusedCount:te.number().describe("How many of the requested ids ended up in the focus subset."),droppedIds:te.array(te.string()).describe("Ids that were not present on the entry and were dropped from the focus."),reason:te.string().optional().describe("Echo of the focus reason for downstream display.")}),Pt]),Oa=te.object({incidentsEntryID:te.string().describe("Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source)."),incidentIds:te.array(te.string()).min(1).describe("Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored."),reason:te.string().optional().describe('Short label shown in the UI FocusChip — what this subset represents (e.g. "top 3 by delay").')}),Ca=te.object({slotIndex:te.number().int().min(0).optional().describe("Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot."),includeEmptySlots:te.boolean().optional().describe("Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses."),offset:te.number().int().min(0).optional().describe("Pagination offset within the filtered slot list. Ignored when slotIndex is provided."),limit:te.number().int().min(1).max(25).optional().describe("Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.")}),Ra=te.object({totalSlots:te.number().describe("Total waypoint slots currently staged"),filledSlots:te.number().describe("Number of staged slots that already contain coordinates"),matchedCount:te.number().describe("Total slots matching the request before pagination"),returnedCount:te.number().describe("Number of slots returned in this page"),offset:te.number().describe("Offset applied to the filtered slot list"),hasMore:te.boolean().describe("True when more slots can be fetched with a higher offset"),nextOffset:te.number().optional().describe("Offset to request the next page when hasMore=true"),waypoints:te.array(is)}),ja=te.object({trackerId:te.string().optional().describe("Scope to one tracker. Omit for every tracker."),sinceMs:te.number().optional().describe("Only events at/after this epoch-ms sample time.")}),Na=te.object({at:te.number(),trackerId:te.string(),trackerName:te.string(),type:te.enum(["alert","event"]),kind:te.enum(["opened","resolved"]),summary:te.string(),openedAt:te.number().optional()}),Ba=te.object({events:te.array(Na).describe("Newest first."),count:te.number(),coverage:te.object({oldestRetainedAt:te.number(),newestAt:te.number()}).optional().describe("Sample-time span the bounded log currently retains — older events were evicted.")}),La=te.object({}),Fa=te.object({trackers:te.array(te.object({trackerId:te.string(),name:te.string(),rule:te.string(),enabled:te.boolean(),firing:te.boolean().describe("Whether the rule is currently active (an open episode)."),openedAt:te.number().optional(),watching:te.array(te.string()).describe("Source entry ids the rule reads.")})),count:te.number()}),qa=te.union([te.object({analysisId:te.string(),enabled:te.boolean().describe("The analysis is now recomputed on every source-entry change (true) or not (false).")}),Pt]),$a=te.object({analysisId:te.string().describe("The `analysisId` returned by `analyseData` for the analysis to start or stop monitoring."),enabled:te.boolean().describe("true → recompute this analysis on every change to its source entries; false → stop monitoring it. The latest result stays attached either way.")}),Ga=(Kr(Jr),te.object({theme:te.enum(["filled","outline","inverted"]).optional().describe('Visual theme. Default: "outline". Use "inverted" for difference/inverse results.'),mode:Pr,hidePreviousEntries:Rr("custom-geometries").optional()})),Wa={sourceIds:te.array(cr).optional().describe(`${hr} (only when \`geometriesEntryIDs\` was set).`),skipped:te.array(vn).optional().describe(pr),fitted:ln.optional()},Ua=te.union([jt().extend({placesEntryId:te.string(),label:te.string().optional(),connectionsRendered:te.number().optional(),placesGeometryCount:te.number().optional().describe("Polygons attached to the new places entry (when `geometries` was returned alongside)."),shownPlaces:te.boolean().optional(),...Wa}),te.object({customGeometriesEntryId:cr,customGeometriesCount:te.number(),label:te.string(),operation:te.string().optional(),shownCustomGeometries:te.boolean().optional(),...Wa}),te.object({byodEntryId:te.string(),byodFeatureCount:te.number(),label:te.string(),fitted:ln.optional()}),te.object({fitted:ln}),Pt]),za=(e,t)=>{const r=Zr(e,t),n=`At least one of ${en(r)} is REQUIRED.`,s={...nn(r,{verb:"process",requiredHint:n})};return s.code=te.string().describe(((e,t)=>{const r=Zr(e,t),n=rn(r),s=r.includes("trafficAreaAnalytics")?"`trafficAreaAnalytics` is a FeatureCollection of tile/hex regions whose `feature.properties` carry metric fields (`congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`); use `turf.booleanPointInPolygon` / `turf.lineIntersect` to bridge with places/routes.\n\n":"",o=r.includes("customGeometries")?`${ur}\n\n`:"",a=r.includes("byod")?"• `byod: FeatureCollection` — writes a NEW BYOD entry (any GeoJSON: Point/Line/Polygon/mixed). Use for derived BYOD layers (e.g. cleaned/filtered customer data).\n":"",i=`${tn(r)}\n`;return"Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`. At least one of `places` / `geometries` / `fitOnMap` / `byod` must be present. `places` (with optional attached `geometries`), standalone `geometries`, and `byod` are MUTUALLY EXCLUSIVE — only one is written per call, others are dropped. `fitOnMap` composes with any of them.\n\n• `places: Places` — writes a NEW places entry. Filters/merges/subsets of the input `placesByEntry[id].features`, or computed point features that conform to the Place schema. Output is validated (validatePlaces): each place must be a Point Feature with a `properties.type` and a display name at its schema path, else it is rejected with a fixable error. When transforming an EXISTING place, spread it so its `id` is kept: `{ ...place, properties: { ...place.properties, poi: { ...place.properties.poi, name } } }`. A deterministic id is minted only for a feature that carries none (a freshly computed point).\n• `placeConnections: { from, to, id? }[]` — lines between output places (endpoints: Place or place id). Requires `places`. Drawn on the new entry's PlacesModule.\n• `geometries: PolygonFeature[]` — polygons (Polygon/MultiPolygon, stable `id` recommended). When `places` is also present, attached to the new places entry; when `places` is absent, written as a NEW custom-geometries entry.\n• `fitOnMap` — bbox or `{ bbox, padding?, animate? }`. Camera move; when alone, no entry written.\n"+a+"\n"+Nn(r,!0)+i+"Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. "+s+`${cn}\n\n`+o+n})(e,t)),s.label=te.string().optional().describe("Short label for the new entry (used by both places and custom-geometries paths)."),s.entryId=Tr,s.operation=te.string().optional().describe("Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance on a new custom-geometries entry. Ignored on the places path."),s.show=te.object({places:jr.optional().describe("Render the new places entry (when `places` was returned)."),placesGeometries:te.object({theme:te.enum(["filled","outline","inverted"]).optional(),mode:Pr}).optional().describe("Render the polygons attached to the new places entry (when both `places` and `geometries` were returned)."),customGeometries:Ga.optional().describe("Render the new custom-geometries entry (when `geometries` was returned WITHOUT `places`).")}).optional().describe("Optional per-output render knobs. The executor only consults the field matching what the code returned (so leaving e.g. `customGeometries` set when `places` was returned is a silent no-op)."),s.connectMarkerType=te.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Match the module holding the endpoints.'),te.object(s).refine(sn,{message:on(r)})},Ha=(za(Jr,void 0),(e,t)=>{const r=Zr(e,t).join(" / "),n="CONTRAST `analyseData`, which attaches JSON/chart metadata to existing entries and never renders.";return t?`Run dynamic JS over ${r} entries to produce NEW renderable map data — a places, custom-geometries, or BYOD entry (with optional \`placeConnections\` / attached polygon footprints), and/or a \`fitOnMap\` camera move. ${n} Returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\`: when \`places\` is returned, inline \`geometries\` attach to it; otherwise they become a standalone custom-geometries entry. Inputs are \`undefined\` when their \`*EntryIDs\` argument is omitted — guard before reading.`:`Run dynamic JS over any combination of ${r} entries to produce NEW renderable map data — a places, custom-geometries, or BYOD entry (with optional \`placeConnections\` / attached polygon footprints), and/or a \`fitOnMap\` camera move. ${n} Returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\`: when \`places\` is returned, inline \`geometries\` attach to it as place footprints; otherwise they become a standalone custom-geometries entry. \`routeUtils\` is exposed alongside \`h3\` / \`turf\` for route section / progress work. Inputs are \`undefined\` when their \`*EntryIDs\` argument is omitted — guard before reading.`}),Ya=(Ha(Jr,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind.`),Va=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)},Ja=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)})},Ka=e=>"string"==typeof e&&e.trim().length>0,Xa=e=>{if(!Array.isArray(e)||e.length<2)return!1;const[t,r]=e;return Number.isFinite(t)&&Number.isFinite(r)&&Math.abs(t)<=180&&Math.abs(r)<=90},Za=(e,t,r)=>`derived-${e.toFixed(6)}_${t.toFixed(6)}-${r}`,Qa=(e,t,r)=>void 0===e?{value:void 0}:t(e)?{value:e}:{error:r},ei=e=>e&&e.features.length>0?e:void 0,ti=e=>{if(!e||"object"!=typeof e)return"Process code must return `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`.";const t=e,r={};if(void 0!==t.places){const e=(e=>{if(!Va(e))return{error:"`places` must be a `Places` FeatureCollection of Point features."};const t=/* @__PURE__ */new Map;for(let r=0;r<e.features.length;r++){const n=e.features[r],s=n?.geometry;if(!s||"Point"!==s.type||!Xa(s.coordinates))return{error:`\`places[${r}]\` must be a Point Feature with valid [lng, lat] coordinates (lng ∈ [-180,180], lat ∈ [-90,90]).`};const o=n.properties;if(![o?.poi?.name,o?.address?.freeformAddress].find(Ka))return{error:`\`places[${r}]\` needs a display name in \`properties.poi.name\` (POIs) or \`properties.address.freeformAddress\` (streets, intersections, addresses).`};if(!Ka(o?.type)||!K.includes(o.type))return{error:`\`places[${r}].properties.type\` is required and must be one of: ${K.join(", ")}.`};if(!Ka(n.id)){const[e,t]=s.coordinates;n.id=Za(e,t,r)}const a=t.get(n.id);if(void 0!==a)return{error:`\`places[${r}]\` duplicates id "${n.id}" of \`places[${a}]\`; give each place a distinct id, or omit it so a unique one is derived.`};t.set(n.id,r)}return{value:e}})(t.places);if("error"in e)return e.error;r.places=ei(e.value)}const n=Qa(t.placeConnections,Ja,"`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).");if("error"in n)return n.error;if(r.placeConnections=n.value,void 0!==t.geometries){const e=(e=>{if(!Array.isArray(e))return{error:"`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features."};const t=[];for(let r=0;r<e.length;r++){const n=e[r];if(!Be(n))return{error:`\`geometries[${r}]\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.`};t.push(n)}return{value:t}})(t.geometries);if("error"in e)return e.error;r.geometries=e.value.length>0?e.value:void 0}const s=Qa(t.fitOnMap,un,"`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.");if("error"in s)return s.error;r.fitOnMap=s.value;const o=Qa(t.byod,Va,"`byod` must be a GeoJSON FeatureCollection (any geometry types).");return"error"in o?o.error:(r.byod=ei(o.value),(e=>void 0===e.places&&void 0===e.geometries&&void 0===e.fitOnMap&&void 0===e.byod?"At least one of `places`, `geometries`, `fitOnMap`, `byod` must produce content (empty arrays / FeatureCollections do not count).":void 0===e.places&&void 0!==e.placeConnections?"`placeConnections` requires `places` to be returned alongside.":void 0)(r)??r)},ri=async(e,t,r,n)=>{const{label:s,entryId:o,show:a,connectMarkerType:i,geometriesEntryIDs:l}=t,{places:c,placeConnections:d,geometries:u,fitOnMap:p}=r,h=c.features.length,y=`${s?`processed: ${s}`:"processed"} (${h} ${1===h?"place":"places"})`,m=await e.places.addPlaceResult(c,y,o,d,u),f=a?.places?await Ms(e,[m],a.places):void 0;let g,b;if(u?.length&&(g=u.length,a?.placesGeometries&&await e.places.showPlaceGeometries(u,a.placesGeometries.theme??"outline",a.placesGeometries.mode??"replace",m)),d){const t=await(async(e,t,r,n)=>{try{return await(await e.places.getEntryPlacesModule(t,n??"pin")).showConnections(r),{count:r.length}}catch(s){return{error:`Failed to render connections: ${s instanceof Error?s.message:String(s)}`}}})(e,m,d,i);if("error"in t)return t;b=t.count}const w=p?pn(e,p):void 0,v=e.places.entries.find(e=>e.id===m)?.label;return{...ss(c),placesEntryId:m,...v&&{label:v},...f&&{shownPlaces:!0},...void 0!==b&&{connectionsRendered:b},...void 0!==g&&{placesGeometryCount:g},...l&&{sourceIds:n.sourceIds},...n.skipped.length&&{skipped:n.skipped},...w&&{fitted:w}}},ni=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i,code:l}=e,c=await Tn({placesEntryIDs:r,routesEntryIDs:n,incidentsEntryIDs:s,geometriesEntryIDs:o,trafficAreaAnalyticsEntryIDs:a,byodEntryIDs:i},t);if("error"in c)return{error:c.error};const{sandbox:d,geometries:u}=c.value,p=await Ye(l,jn,Bn(d,{h3:re,turf:ee}),"Process",t.codeExecution);if("error"in p)return{error:p.error};const h=ti(p.value);if("string"==typeof h)return{error:h};const{places:y,geometries:m,fitOnMap:f,byod:g}=h;return y?ri(t,e,{...h,places:y},u):m?.length?(async(e,t,r,n,s)=>{const{label:o,entryId:a,operation:i,show:l,geometriesEntryIDs:c}=t,d=o?`processed: ${o}`:`processed geometries (${r.length})`,u=await e.customGeometries.addEntry(r,{sourceIds:s.sourceIds,...i&&{operation:i}},d,a);let p=!1;l?.customGeometries&&(await mn(e.customGeometries,[u],l.customGeometries.hidePreviousEntries),await e.customGeometries.showEntry(u,l.customGeometries.theme??"outline",l.customGeometries.mode??"replace"),p=!0);const h=n?pn(e,n):void 0;return{customGeometriesEntryId:{kind:"customGeometries",id:u},customGeometriesCount:r.length,label:d,...i&&{operation:i},...c&&{sourceIds:s.sourceIds},...s.skipped.length&&{skipped:s.skipped},...p&&{shownCustomGeometries:!0},...h&&{fitted:h}}})(t,e,m,f,u):g?(async(e,t,r,n)=>{const{label:s,entryId:o}=t,a=s?`processed: ${s}`:`processed byod (${r.features.length})`,i=await e.byod.addEntry(r,a,{...o&&{explicitId:o}}),l=n?pn(e,n):void 0;return{byodEntryId:i,byodFeatureCount:r.features.length,label:a,...l&&{fitted:l}}})(t,e,g,f):f?{fitted:pn(t,f)}:{error:"Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`."}},si=te.union([te.object({kind:te.literal("integrator"),description:te.string().optional()}),te.object({kind:te.literal("url"),url:te.string()}),te.object({kind:te.literal("inline"),description:te.string().optional()})]),oi=te.object({id:te.string(),label:te.string(),timestamp:te.number(),featureCount:te.number(),geometryTypes:te.array(te.string()),propertyNames:te.array(te.string()).describe("Feature-property keys seen in the data, highest-coverage first and capped — wide datasets may omit low-coverage keys. Pass `id` for the full per-property profile (types/coverage/numeric examples) plus any `propertiesOmitted` count."),source:si,shown:te.boolean()}),ai=oi.extend({profile:_t.describe("Runtime-inferred shape of the data (per-property types/coverage; numeric/boolean examples only — string example values are withheld). To compute over the actual feature values, pass this entry to `analyseData` / `processData`; the raw GeoJSON is never returned to the model.")}),ii=te.union([te.object({entries:te.array(oi),entryMode:te.enum(["single","multiple"])}),ai,Pt]),li=(te.object({id:te.string().optional().describe("BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.")}),te.object({id:cr.optional().describe('Tagged id to inspect in detail (e.g. `{ kind: "places", id: "ev-stations-oost" }`, `{ kind: "ranges", id: "ranges-0" }`, `{ kind: "customGeometries", id: "bakery-candidates-gap-oost" }`). Omit to list every available id across all slices.')}),te.object({name:te.string(),outputFormat:te.enum(["json","chart"]),timestamp:te.number(),description:te.string().optional()})),ci=te.object({id:cr.describe("Tagged id of this entry."),label:te.string(),timestamp:te.number(),featureCount:te.number(),operation:te.string().optional().describe("Operation label (custom-geometries entries only)."),sourceIds:te.array(cr).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:te.array(li).optional().describe("Analyses already attached to this entry (places and custom only).")}),di=ci.extend({features:te.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),ui=te.union([te.object({entries:te.array(ci)}),di,Pt]),pi=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:n})=>({name:e,outputFormat:t,timestamp:r,...n&&{description:n}})):void 0,hi=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const n=pi(e.analyses.getAnalysesForEntry(r.id));t.push({id:{kind:"places",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,...n&&{analyses:n}})}for(const r of e.ranges.entries){const e=r.data.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 n=pi(e.analyses.getAnalysesForEntry(r.id));t.push({id:{kind:"customGeometries",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.data.length,...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...n&&{analyses:n}})}return t.sort((e,t)=>t.timestamp-e.timestamp)},yi=(e,t)=>{switch(e.kind){case"places":return((e,t)=>{const r=t.places.entries.find(t=>t.id===e.id);if(!r)return{error:`No places entry with id "${e.id}"`};if(!r.geometries?.length)return{error:`Places entry "${e.id}" has no cached footprints. Call discoverPlaces / locatePlace with \`withGeometries: true\` first.`};const n=pi(t.analyses.getAnalysesForEntry(r.id));return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,features:{type:"FeatureCollection",features:r.geometries},...n&&{analyses:n}}})(e,t);case"ranges":return((e,t)=>{const r=t.ranges.entries.find(t=>t.id===e.id);if(!r)return{error:`No ranges entry with id "${e.id}"`};const n=r.data.flatMap(e=>e.polygon?.features??[]);return 0===n.length?{error:`Ranges entry "${e.id}" has no polygons.`}:{id:e,label:r.label,timestamp:r.timestamp,featureCount:n.length,features:{type:"FeatureCollection",features:n}}})(e,t);case"customGeometries":return((e,t)=>{const r=t.customGeometries.findById(e.id);if(!r)return{error:`No custom-geometries entry with id "${e.id}"`};const n=pi(t.analyses.getAnalysesForEntry(r.id));return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.data.length,features:{type:"FeatureCollection",features:r.data},...r.provenance.operation&&{operation:r.provenance.operation},...r.provenance.sourceIds.length&&{sourceIds:[...r.provenance.sourceIds]},...n&&{analyses:n}}})(e,t);default:return{error:'`{ kind: "place" }` is not a recallable detail target. Pass a places-entry id (`recallState({ kind: "places", id })`) to inspect a single place.'}}},mi=(te.object({id:te.string().optional().describe('Incidents entry id to inspect (e.g. "incidents-0"). Omit to list every incidents entry.')}),te.enum(["single","multiple"]).describe("Display policy: `multiple` (default) lets several incidents entries render at once; `single` keeps only the latest entry — switching to it auto-clears the others.")),fi=te.object({id:te.string(),label:te.string(),timestamp:te.number(),count:te.number().describe("Number of incidents captured in this entry."),shown:te.boolean().describe("True while this entry is rendered on the map."),monitored:te.boolean().describe("True while a background monitor is actively polling this entry."),focusedCount:te.number().describe("Incidents in the entry's focus subset (0 when nothing is focused).")}),gi=fi.extend({byMagnitude:te.record(te.string(),te.number()).describe("Incident count grouped by `magnitudeOfDelay` (e.g. `{ major: 3, moderate: 5, minor: 10 }`)."),totalDelaySeconds:te.number().describe("Sum of `delayInSeconds` across the entry (0 when no delay data)."),clusterCount:te.number().optional().describe("Number of incident clusters computed for this entry — present only once clustering has run.")}),bi=te.union([te.object({entries:te.array(fi),entryMode:mi}),gi.extend({entryMode:mi}),Pt]),wi=(te.object({id:te.string().optional().describe('Entry ID to retrieve (e.g. "places-3"). Omit to list all entries.')}),te.object({name:te.string(),outputFormat:te.enum(["json","chart"]),timestamp:te.number(),description:te.string().optional()})),vi=te.object({id:te.string(),label:te.string(),timestamp:te.number(),featureCount:te.number().describe("Number of Place features stored in this entry."),shown:te.boolean().describe("Whether this entry is currently rendered on the map."),markerType:te.enum(Ae).optional().describe("Which marker theme is rendering this entry (undefined when `shown` is false)."),analyses:te.array(wi).optional().describe("Analyses already attached to this entry via analyseData (name + output format).")}),Ii=te.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.'),Ei=e=>{const t=te.object({id:te.string(),label:te.string(),timestamp:te.number(),places:jt()});return te.union([te.object({entries:te.array(vi),entryMode:Ii}),t.extend({entryMode:Ii}),Pt])},Si=(Ei(),e=>async(e,t)=>{const{id:r}=e,n=t.places.entries,s=t.places.entryMode,o=t.places.shownEntryIds;if(!r)return{entries:[...n].sort((e,t)=>t.timestamp-e.timestamp).map(({id:e,label:r,timestamp:n,data:s})=>{const a=o.has(e),i=a?t.places.getShownMarkerType(e):void 0,l=t.analyses.getAnalysesForEntry(e);return{id:e,label:r,timestamp:n,featureCount:s.length,shown:a,...i&&{markerType:i},...l.length&&{analyses:l.map(({name:e,outputFormat:t,timestamp:r,description:n})=>({name:e,outputFormat:t,timestamp:r,...n&&{description:n}}))}}}),entryMode:s};const a=n.find(e=>e.id===r);if(!a)return{error:`No entry found with id "${r}"`};const i=ss(a.data);return{id:a.id,label:a.label,timestamp:a.timestamp,places:i,entryMode:s}}),ki=(Si(),te.object({id:te.string().optional().describe('Entry ID to retrieve (e.g. "ranges-0"). Omit to list all entries.')}),te.object({id:te.string(),label:te.string(),timestamp:te.number(),originCount:te.number()})),Mi=te.object({origin:te.object({query:te.string().optional(),position:te.tuple([te.number(),te.number()])}),budgets:te.array(te.object({type:te.string(),value:te.number()}))}),Ai=te.object({id:te.string(),label:te.string(),timestamp:te.number(),ranges:te.array(Mi)}),xi=te.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.'),Di=te.union([te.object({entries:te.array(ki),entryMode:xi}),Ai.extend({entryMode:xi}),Pt]),Ti=(te.object({id:te.string().optional().describe('Entry ID to retrieve (e.g. "routes-1"). Omit to list all entries.')}),te.object({id:te.string(),label:te.string(),timestamp:te.number(),shown:te.boolean().describe("Whether this entry is currently rendered on the map.")})),Pi=te.object({maxAlternatives:te.number().optional(),costModel:eo.optional(),when:to.optional()}),_i=te.object({id:te.string(),label:te.string(),timestamp:te.number(),routes:Ft,waypoints:te.array(te.object({position:te.array(te.number()).length(2).describe("[longitude, latitude]")})),params:Pi}),Oi=te.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.'),Ci=te.union([te.object({entries:te.array(Ti),entryMode:Oi}),_i.extend({entryMode:Oi}),Pt]),Ri=(te.object({id:te.string().optional().describe('Traffic-area-analytics entry id to inspect (e.g. "tta-0"). Omit to list every analytics entry.')}),te.enum(["single","multiple"]).describe("Display policy: `multiple` (default) lets several analytics entries render at once; `single` keeps only the latest entry — switching to it auto-clears the others.")),ji=te.object({id:te.string(),label:te.string(),timestamp:te.number(),regionCount:te.number().describe("Number of analysed regions (features) in this entry."),metrics:te.array(te.string()).describe('Traffic metrics included in the report (e.g. ["congestionLevel", "speed"]).'),shown:te.boolean().describe("True while this entry is rendered on the map.")}),Ni=ji.extend({dateRange:te.object({start:te.string(),end:te.string()}).describe("Analysis period (ISO dates) — empty strings when the entry carries no date metadata."),regions:te.array(te.string()).describe("Names of the analysed regions, in feature order."),heatmap:te.boolean().describe("Whether tile-level heatmap data is included.")}),Bi=te.union([te.object({entries:te.array(ji),entryMode:Ri}),Ni.extend({entryMode:Ri}),Pt]),Li=e=>!e||Number.isNaN(e.getTime())?"":e.toISOString().slice(0,10),Fi=te.object({id:te.string(),label:te.string()}),qi=te.enum(["single","multiple"]),$i=te.object({places:te.array(Fi).describe("Places history entries — newest first."),routes:te.array(Fi).describe("Routes history entries — newest first."),ranges:te.array(Fi).describe("Reachable-range history entries — newest first."),entryModes:te.object({places:qi,routes:qi,ranges:qi}).describe("Per-slice display policy. `multiple` (default) lets several entries render at once; `single` keeps only the latest entry — set via setEntryMode tools."),mapStyle:te.object({id:te.string().optional().describe('Standard style id (e.g. "standardLight").')}).nullable().describe("Current map style. `null` when the map has no style assigned yet."),mapPOIs:te.object({visible:te.boolean().optional(),filterMode:te.enum(["only","all_except"]).optional(),categories:te.array(te.string()).optional()}).nullable().describe("Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session."),traffic:te.object({flow:te.object({visible:te.boolean().optional()}).nullable().describe("Traffic-flow layer config. `null` when the module is uninitialised."),incidents:te.object({visible:te.boolean().optional()}).nullable().describe("Traffic-incidents layer config. `null` when the module is uninitialised.")}).describe("Real-time traffic visualization configs.")}),Gi=e=>te.union([$i,Ei(),Ci,Di,ii,ui,bi,Bi,Pt]),Wi=(Gi(),te.object({kind:te.enum(["places","routes","ranges","geometries","byod","incidents","trafficAreaAnalytics"]).optional().describe("Narrow to ONE entry kind: list that kind's entries (omit `id`) or inspect one (`id`). Omit `kind` for a full session snapshot (places/routes/ranges index + map style / POIs / traffic)."),id:te.union([te.string(),cr]).optional().describe('Entry id to inspect in detail — requires `kind`. A plain id for places/routes/ranges/byod/incidents/trafficAreaAnalytics (e.g. "places-3"); a tagged `{ kind, id }` for geometries. Omit to list the kind\'s entries.')})),Ui=e=>async(e,t)=>{const{kind:r,id:n}=e;if(!r)return(e=>{const t=e=>[...e].sort((e,t)=>t.timestamp-e.timestamp),r=t(e.places.entries).map(({id:e,label:t})=>({id:e,label:t})),n=t(e.routing.entries).map(({id:e,label:t})=>({id:e,label:t})),s=t(e.ranges.entries).map(({id:e,label:t})=>({id:e,label:t})),o="string"==typeof(a=e.baseMap.ttMap.getStyle())?a:a&&"object"==typeof a&&"id"in a?a.id:void 0;var a;const i=void 0===o?null:{id:o},l=e.mapPOIs.poisModule?.getConfig(),c=l?{..."boolean"==typeof l.visible&&{visible:l.visible},...l.filters?.categories?.show&&{filterMode:l.filters.categories.show},...l.filters?.categories?.values&&{categories:[...l.filters.categories.values]}}:null,d=e.trafficTiles.trafficFlowModule?.getConfig(),u=e.trafficTiles.trafficIncidentsModule?.getConfig(),p={flow:d?{..."boolean"==typeof d.visible&&{visible:d.visible}}:null,incidents:u?{..."boolean"==typeof u.visible&&{visible:u.visible}}:null};return{places:r,routes:n,ranges:s,entryModes:{places:e.places.entryMode,routes:e.routing.entryMode,ranges:e.ranges.entryMode},mapStyle:i,mapPOIs:c,traffic:p}})(t);switch(r){case"places":return Si()({id:n},t);case"routes":return(async(e,t)=>{const{id:r}=e,n=t.routing.entryMode,s=t.routing.shownEntryIds;if(!r)return{entries:[...t.routing.entries].reverse().map(({id:e,label:t,timestamp:r})=>({id:e,label:t,timestamp:r,shown:s.has(e)})),entryMode:n};const o=t.routing.entries.find(e=>e.id===r);if(!o)return{error:`No entry found with id "${r}"`};const a=as(o.data),i=o.waypoints.map(e=>ls(e)).filter(e=>null!==e).map(e=>({position:e.position??[0,0]}));return{id:o.id,label:o.label,timestamp:o.timestamp,routes:a,waypoints:i,params:o.params,entryMode:n}})({id:n},t);case"ranges":return(async(e,t)=>{const{id:r}=e,n=t.ranges.entryMode;if(!r)return{entries:[...t.ranges.entries].reverse().map(({id:e,label:t,timestamp:r,data:n})=>({id:e,label:t,timestamp:r,originCount:n.length})),entryMode:n};const s=t.ranges.entries.find(e=>e.id===r);return s?{id:s.id,label:s.label,timestamp:s.timestamp,ranges:s.data.map(e=>({origin:{...e.origin,position:e.origin.position},budgets:e.budgets})),entryMode:n}:{error:`No entry found with id "${r}"`}})({id:n},t);case"byod":return(async(e,t)=>{if(!e.id)return{entries:t.byod.entries.map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,featureCount:e.profile.featureCount,geometryTypes:e.profile.geometryTypes,propertyNames:e.profile.properties.map(e=>e.name),source:e.source,shown:!!e._shown})),entryMode:t.byod.entryMode};const r=t.byod.findById(e.id);return r?{id:r.id,label:r.label,timestamp:r.timestamp,featureCount:r.profile.featureCount,geometryTypes:r.profile.geometryTypes,propertyNames:r.profile.properties.map(e=>e.name),profile:it(r.profile),source:r.source,shown:!!r._shown}:{error:`No BYOD entry with id "${e.id}". Call recallState({ kind: "byod" }) to list available IDs.`}})({id:n},t);case"incidents":return(async(e,t)=>{const r=t.trafficIncidents,n=r.entryMode;if(!e.id)return{entries:[...r.entries].reverse().map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,count:e.data.length,shown:r.shownEntryIds.has(e.id),monitored:r.isMonitored(e.id),focusedCount:r.getFocus(e.id)?.ids.size??0})),entryMode:n};const s=r.entries.find(t=>t.id===e.id);if(!s)return{error:`No incidents entry with id "${e.id}". Call recallState({ kind: "incidents" }) to list available IDs.`};const o={};let a=0;for(const l of s.data){const e=l.properties.magnitudeOfDelay;o[e]=(o[e]??0)+1,a+=l.properties.delayInSeconds??0}const i=r.getClusters(s.id);return{id:s.id,label:s.label,timestamp:s.timestamp,count:s.data.length,shown:r.shownEntryIds.has(s.id),monitored:r.isMonitored(s.id),focusedCount:r.getFocus(s.id)?.ids.size??0,byMagnitude:o,totalDelaySeconds:a,...i&&{clusterCount:i.groups.length},entryMode:n}})({id:n},t);case"trafficAreaAnalytics":return(async(e,t)=>{const r=t.trafficAreaAnalytics,n=r.entryMode;if(!e.id)return{entries:[...r.entries].reverse().map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,regionCount:e.data.features.length,metrics:e.data.properties.metrics,shown:r.shownEntryIds.has(e.id)})),entryMode:n};const s=r.findById(e.id);if(!s)return{error:`No traffic-area-analytics entry with id "${e.id}". Call recallState({ kind: "trafficAreaAnalytics" }) to list available IDs.`};const o=s.data.properties;return{id:s.id,label:s.label,timestamp:s.timestamp,regionCount:s.data.features.length,metrics:o.metrics,shown:r.shownEntryIds.has(s.id),dateRange:{start:Li(o.startDate),end:Li(o.endDate)},regions:s.data.features.map(e=>e.properties.name),heatmap:o.heatmap,entryMode:n}})({id:n},t);case"geometries":return(async(e,t)=>{const{id:r}=e;return r?yi(r,t):{entries:hi(t)}})({id:n},t)}},zi=(Ui(),{classificationPrompt:"Read session state: list / inspect loaded entries (places, routes, ranges, geometries, byod, incidents, trafficAreaAnalytics) and pick valid ids before any `*EntryIDs` follow-up (analyseData, processData, updatePlacesDisplay, …), or get a full snapshot of what is loaded. No args = snapshot; `kind` = that kind's index; `kind`+`id` = detail.",tags:["state","recall","orientation"],examples:["recallState()",'recallState({ kind: "places" })','recallState({ kind: "places", id: "places-0" })','recallState({ kind: "routes" })','recallState({ kind: "geometries", id: { kind: "customGeometries", id: "buffered-routes" } })'],examplePrompts:["What is currently loaded in my session?","What places did I search for?","List my routes with their IDs","What is in entry places-2?","Which IDs can I pass to analyseData or processData?","Show the BYOD layers I added"],relatedTools:["analyseData","processData","updatePlacesDisplay","updateRoutesDisplay","updateByodDisplay"],dependsOn:["discoverPlaces","locatePlace","reverseGeocode","setRoute","findReachableAreas","addByodSource","processData","getTrafficAreaAnalytics"]}),Hi=["places","routes","ranges","customGeometries","trafficAreaAnalytics","trafficIncidents","byod","analyses","trackers","mapPOIs","trafficFlow","mapStyle"],Yi=te.union([te.object({success:te.literal(!0),cleared:te.array(te.enum(Hi)).describe("Slices that were wiped / reset to defaults.")}),Pt]),Vi=te.object({}),Ji=te.enum(["circle","line","fill","symbol","heatmap","fill-extrusion"]).describe("MapLibre layer type. Must suit the geometry: Point → circle/symbol/heatmap, Line → line, Polygon → fill/fill-extrusion."),Ki=te.object({type:Ji,paint:te.record(te.string(),te.unknown()).optional().describe('MapLibre PAINT properties — how the feature LOOKS: colour, opacity, halo, blur (any `*-color` / `*-opacity` / `*-halo-*`), plus `circle-radius` and `line-width`. Values may be data-driven expressions referencing feature properties seen in the entry `profile` (e.g. `["interpolate", ["linear"], ["get", "revenue"], 0, 4, 1000, 20]`).'),layout:te.record(te.string(),te.unknown()).optional().describe("MapLibre LAYOUT properties — WHAT/WHERE the feature is: `visibility`, placement/anchor/offset, and a symbol's text/icon content + glyph sizing (`text-field`, `text-font`, `text-size`, `icon-image`, `icon-size`). Everything else — all colour, opacity and halo — is `paint`. A `symbol` splits its text styling this way (`text-field`/`text-size` here, `text-color`/`text-halo-*` in `paint`); mixing them up makes MapLibre reject the layer with `unknown property`."),filter:te.unknown().optional().describe("Optional MapLibre filter expression restricting which features this layer renders."),id:te.string().optional().describe("Stable layer id. Auto-generated when omitted."),beforeID:te.string().optional().describe(`Style layer id to draw this layer BELOW; omit to draw on top of everything. Any layer already on the map is allowed (e.g. another entry's layer), but prefer these base-map anchors: '${f.lowestLabel}' for fill/line/heatmap, which keeps every map label readable above the data, or '${f.lowestRoadLine}' when the road network should stay drawn over a fill; omit for point layers. An id that is not in the current style silently drops the layer.`)}).describe("A single MapLibre layer rendering the entry. Pick `type` by geometry; drive `paint` from the profile."),Xi=te.object({byodEntryId:te.string().describe("Id of the BYOD entry to restyle (from `addByodSource` or `recallState`)."),layers:te.array(Ki).min(1).describe("Replacement MapLibre layer specs, applied in array order (first drawn underneath). Replaces the entry's current layers wholesale. Use the entry `profile` (geometry types + per-property types/coverage/examples) to choose layer `type`s and data-driven `paint`."),show:Br.optional().describe("Render the entry after restyling. Omit when the entry is already shown — the new layers apply live in place. Pass it to render a hidden entry (or to re-fit the camera via `zoomMode`).")}),Zi=te.union([te.object({byodEntryId:te.string(),label:te.string(),layerCount:te.number().describe("Number of layers now configured on the entry."),layerTypes:te.array(te.string()).describe("The `type` of each configured layer, in draw order."),shown:Lr.optional().describe("Render report — present only when `show` was provided (or the entry re-rendered live).")}),Pt]),Qi=te.enum(Et),el=te.enum(["single","multiple"]),tl=te.union([te.object({success:te.literal(!0),slice:Qi,mode:el,droppedEntryIds:te.array(te.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),Pt]),rl=te.object({slice:Qi.describe("Which entry-owning slice to reconfigure. Names match the keys on the agent state exactly — `routing` (not `routes`), `customGeometries` (not `geometries`), and so on."),mode:el.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),nl=`Switch the display policy of one entry-owning slice between \`multiple\` and \`single\`. Supported slices: ${Et.join(", ")}. \`single\` ⇒ at most one entry on the map, and any older entries get dropped from history immediately. Use when the user asks for a "clean view" / "only show one at a time" or wants to compare overlays again.`,sl=te.union([te.object({incidentsEntryID:te.string(),enabled:te.boolean().describe("The monitor is now polling (true) or stopped (false)."),alreadyInState:te.boolean().describe("True when the monitor was already in the requested state — this call was a no-op.")}),Pt]),ol=te.object({incidentsEntryID:te.string().describe("Id of the incidents entry to monitor (returned by `getTrafficIncidents`)."),enabled:te.boolean().describe("true → start background polling (re-fetch + re-run registered analyses each tick); false → stop it. The entry keeps its last data either way."),intervalMs:te.number().int().positive().optional().describe("Polling interval in milliseconds when enabling (default 60000). Ignored when disabling, and not applied to an already-running monitor — disable then re-enable to change it.")}),al=te.union([te.object({routesEntryID:te.string(),alreadyRunning:te.boolean().describe("True when the entry already had a running monitor — start is idempotent. When true any new intervalMs in this call was IGNORED; stop first, then start again to change the interval.")}),Pt]),il=te.object({routesEntryID:te.string().describe("Id of the routes entry to monitor (the `entryId` returned by `setRoute`)."),intervalMs:te.number().int().positive().optional().describe("Recalculation interval in milliseconds. Defaults to 60000 (60s).")}),ll=te.union([te.object({routesEntryID:te.string(),wasRunning:te.boolean().describe("True when a monitor was actually stopped; false when it was already idle.")}),Pt]),cl=te.object({routesEntryID:te.string().describe("Id of the routes entry whose monitor should stop.")}),dl=te.enum(St),ul=te.union([te.object({success:te.literal(!0),cleared:te.array(dl)}),Pt]),pl=te.object({layers:te.array(dl).optional().describe("Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: "+St.map(e=>`\`${e}\``).join(", ")+". Map-only — slice histories (`entries`) are kept.")}),hl=te.union([te.number(),te.boolean(),te.string()]),yl=te.enum(I),ml=te.object({id:te.string(),kind:yl,description:te.string(),default:hl.optional(),current:hl.optional(),overridden:te.boolean(),range:te.object({min:te.number(),max:te.number(),step:te.number()}).optional(),options:te.array(te.string()).optional(),available:te.boolean(),appliesTo:te.enum(w)}),fl=te.object({id:te.string(),name:te.string(),description:te.string(),settings:te.record(te.string(),hl)}),gl=te.union([te.object({knobs:te.array(ml),presets:te.array(fl)}),Pt]),bl=te.object({kind:yl.optional().describe("Only list knobs of this kind. Omit for the whole catalogue.")}),wl=te.union([te.object({styles:te.array(te.string()).readonly()}),Pt]),vl=te.object({}),Il=te.union([te.object({success:te.literal(!0),language:te.string()}),Pt]),El=te.object({language:te.string().describe('e.g. "en-US", "fr-FR"')}),Sl=te.union([te.object({success:te.literal(!0),style:te.string()}),Pt]),kl=te.object({style:te.enum(b).describe("Map visual theme")}),Ml=v.join(", "),Al=te.union([te.number(),te.boolean(),te.string()]),xl=te.union([te.object({success:te.literal(!0),settings:te.record(te.string(),Al),rejected:te.array(te.object({id:te.string(),reason:te.string()}))}),Pt]),Dl=te.object({set:te.record(te.string(),Al).optional().describe(`Knob id → value. Factors are multipliers (1 = unchanged, e.g. 1.3), toggles are booleans, colours are CSS colour strings. Ranges come from describeMapStyling. Ids: ${Ml}.`),reset:te.union([te.literal(!0),te.array(te.string())]).optional().describe("true resets every knob to the style default; a list resets just those knobs. Applied first."),preset:te.enum(E).optional().describe("Apply a named preset (replaces the current knobs; `set` is applied on top). data-viz: quiet base for data overlays · night-driving: bigger labels, wider roads · minimal: bare map · globe: globe projection with atmosphere.")}),Tl=te.union([te.object({success:te.literal(!0),allMapPOIsVisible:te.boolean().optional(),filterCategories:te.object({show:te.enum(["only","all_except"]),values:te.array(te.string())}).optional(),reset:te.boolean().optional()}),Pt]),Pl=te.object({reset:te.boolean().optional().describe("Reset all tile-POI configuration to defaults. Other params ignored when true."),allMapPOIsVisible:te.boolean().optional().describe("Overall visibility of the vector-tile POI icons baked into the base map style."),filterCategories:te.object({show:te.enum(["only","all_except"]).describe('"only": keep only these categories. "all_except": hide these categories.'),values:te.array(te.string()).describe('MapStylePOICategory / POICategoryGroup values (e.g. "RESTAURANT", "FOOD_DRINKS_GROUP"). Fine-grained codes like "ITALIAN_RESTAURANT" not supported here.')}).optional().describe("Restrict which built-in vector-tile POI categories are shown.")}),_l=te.object({visible:te.boolean()}),Ol=te.union([te.object({success:te.literal(!0),trafficFlowVisible:te.boolean()}),Pt]),Cl=te.object({visible:te.boolean()}),Rl=te.union([te.object({success:te.literal(!0),trafficIncidentsVisible:te.boolean()}),Pt]),jl=te.enum(["show","hide","remove"]).describe("`show` renders the listed entries (lazy-init their CustomGeoJSONModule); `hide` removes them from the map but keeps them in session state; `remove` hides AND drops them from history."),Nl=te.object({entryIds:te.array(te.string()).min(1).optional().describe('BYOD entry ids to act on (e.g. ["byod-0"]). Use `recallState` to list. Omit when `action: "hide"` and `clearAll: true` to hide every shown BYOD entry in one call.'),action:jl,clearAll:te.boolean().optional().describe('Only valid with `action: "hide"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.'),hideOthers:te.boolean().optional().describe('Only valid with `action: "show"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).')}).refine(e=>!("hide"!==e.action||!e.clearAll)||!!e.entryIds?.length,{message:'`entryIds` is required unless `action: "hide"` with `clearAll: true`.'}).refine(e=>!e.clearAll||"hide"===e.action,{message:'`clearAll: true` is only valid with `action: "hide"`.'}).refine(e=>!e.hideOthers||"show"===e.action,{message:'`hideOthers: true` is only valid with `action: "show"`.'}),Bl=te.union([te.object({action:jl,affectedIds:te.array(te.string()).describe("Entry ids that were actually shown / hidden / removed."),shown:te.array(te.string()).describe("BYOD entry ids currently rendered on the map after this call.")}),Pt]),Ll=async(e,t)=>{if(!t.properties.dataSources?.geometry?.id)return{skip:"Place has no geometry data source (e.g. addresses and streets often lack one)."};try{const r=await e.places.fetchPlaceGeometry(t.id);return r?{feature:r}:{skip:"Geometry Data service returned no feature for this place."}}catch(r){return{skip:`Geometry fetch failed: ${r instanceof Error?r.message:String(r)}`}}},Fl=async(e,t,r)=>{const n=e.places.entries.find(e=>e.id===t);if(!n)return{rendered:0,skipped:[]};const s=r.placeIds?new Set(r.placeIds):void 0,o=s?n.data.filter(e=>s.has(e.id)):n.data,a=[],i=[];for(const l of o){const t=await Ll(e,l);"skip"in t?a.push({placeId:l.id,reason:t.skip}):i.push(t.feature)}return i.length>0&&await e.places.showPlaceGeometries(i,r.theme??"outline",r.mode??"replace",t),{rendered:i.length,skipped:a}},ql=te.object({id:te.string(),label:te.string(),featureCount:te.number(),markerType:te.enum(Ae).describe("How the entry is rendered on the map.")}),$l=te.union([te.object({success:te.literal(!0),totalShown:te.number(),shown:te.array(ql).describe("Entries currently on the map, with their labels."),actuallyAdded:te.array(te.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:te.array(te.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),geometriesRendered:te.number().optional().describe("Number of boundary polygons rendered when `geometry` was set."),geometriesSkipped:te.array(te.object({placeId:te.string(),reason:te.string()})).optional().describe("Places whose boundary could not be rendered (no geometry data source, fetch failure).")}),Pt]),Gl=te.object({add:te.array(te.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:te.array(te.string()).optional().describe("Entry ids to hide."),addMatching:te.array(te.string()).optional().describe("Category labels to show."),removeMatching:te.array(te.string()).optional().describe("Category labels to hide."),clear:te.boolean().optional().describe("Hide every displayed entry."),markerType:te.enum(Ae).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:Or.optional(),fitBounds:te.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."}),Wl=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),n=/* @__PURE__ */new Set;for(const s of e.places.entries){const e=s.label.toLowerCase();r.some(t=>e.includes(t))&&n.add(s.id)}return[...n]},Ul=te.object({id:te.string(),label:te.string(),routeCount:te.number().describe("Number of route alternatives in the entry."),selectedIndex:te.number().describe("Highlighted alternative index (0 unless overridden by this call).")}),zl=te.union([te.object({success:te.literal(!0),totalShown:te.number(),shown:te.array(Ul).describe("Routes entries currently on the map, with their labels."),actuallyAdded:te.array(te.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:te.array(te.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),mainColor:te.string().optional().describe("Sticky main colour now applied to every shown route, when set by this or a previous call.")}),Pt]),Hl=te.object({add:te.array(te.string()).optional().describe('Entry ids to show (e.g. ["routes-1"]). Omit while still setting `selectedIndex` to target the most recently stored entry.'),remove:te.array(te.string()).optional().describe("Entry ids to hide."),addMatching:te.array(te.string()).optional().describe("Label substrings to show."),removeMatching:te.array(te.string()).optional().describe("Label substrings to hide."),clear:te.boolean().optional().describe("Hide every displayed entry."),selectedIndex:te.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:te.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise."),mainColor:te.string().optional().describe('Recolor the route line + waypoint icons. CSS named colour ("coral", "steelblue") or hex ("#FF0000"). Sticky — applied to every currently-shown entry AND remembered so subsequent shows pick it up automatically. Can be set by itself (no add/remove/clear) to recolor without changing visibility.')}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.selectedIndex||void 0!==e.mainColor,{message:"updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, mainColor."}),Yl=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),n=/* @__PURE__ */new Set;for(const s of e.routing.entries){const e=s.label.toLowerCase();r.some(t=>e.includes(t))&&n.add(s.id)}return[...n]},Vl={mode:te.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:te.enum(["congestionLevel","speed","travelTime"]).optional().describe("Metric to visualize. Defaults to first fetched metric on the targeted entries."),colorTheme:te.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:te.number().optional().describe("Extrusion height multiplier / visual maximum in meters. Default: metric-dependent."),scaleMode:te.enum(["raw","predefinedRange","currentRange"]).optional().describe("Height scaling mode. 'raw' (default), 'predefinedRange' (normalize to SDK range), 'currentRange' (normalize to live data range)."),filter:te.object({min:te.number().optional(),max:te.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.")},Jl=te.object({id:te.string(),label:te.string(),activeMetric:te.string().optional(),displayMode:te.string().optional()}),Kl=te.object({add:te.array(te.string()).optional().describe('Entry ids to render (e.g. ["tta-0"]). Use `recallState` to list available ids.'),remove:te.array(te.string()).optional().describe("Entry ids to hide."),addMatching:te.array(te.string()).optional().describe("Label substrings to show."),removeMatching:te.array(te.string()).optional().describe("Label substrings to hide."),clear:te.boolean().optional().describe("Hide every displayed entry."),fitBounds:te.boolean().optional().describe("Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added."),...Vl}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.mode||void 0!==e.metric||void 0!==e.colorTheme||void 0!==e.heightScale||void 0!==e.scaleMode||void 0!==e.filter,{message:"updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, mode, metric, colorTheme, heightScale, scaleMode, filter."}),Xl=te.union([te.object({success:te.literal(!0),totalShown:te.number(),shown:te.array(Jl).describe("Traffic-area-analytics entries currently on the map."),actuallyAdded:te.array(te.string()),actuallyRemoved:te.array(te.string())}),Pt]),Zl=(e,t)=>{if(0===t.length)return[];const r=t.map(e=>e.toLowerCase()),n=/* @__PURE__ */new Set;for(const s of e.trafficAreaAnalytics.entries)r.some(e=>s.label.toLowerCase().includes(e))&&n.add(s.id);return[...n]},Ql=async(e,t,r)=>{const n=e.trafficAreaAnalytics.findById(t);if(!n)return;const s=n.data.properties?.metrics?.[0]??"congestionLevel",o=r.mode??"hexgrid-3d",a=r.metric??s,i=await e.trafficAreaAnalytics.getEntryModule(t);i.setMode(o),i.setMetric(a),void 0!==r.colorTheme&&i.setColor(r.colorTheme),void 0===r.heightScale&&void 0===r.scaleMode||i.setHeight({...void 0!==r.heightScale&&{maxHeightMeters:r.heightScale},...void 0!==r.scaleMode&&{scaleMode:r.scaleMode}}),r.filter?i.filter({min:r.filter.min,max:r.filter.max}):i.clearFilter()},ec=e=>({id:e.id,label:e.label,...e._config?.activeMetric&&{activeMetric:e._config.activeMetric},...e._config?.displayMode&&{displayMode:e._config.displayMode}}),tc=te.union([te.object({success:te.literal(!0),visible:te.boolean(),count:te.number()}),Pt]),rc=te.object({visible:te.boolean().optional().describe("Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.")}),nc="Dynamic-JS sandbox over any combination of places / routes / incidents / geometries / trafficAreaAnalytics / byod entries. Each input arg is `undefined` when its `*EntryIDs` was omitted.",sc={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:Go,outputSchema:Wo,execute:async(e,t,r)=>{const{query:n,queryAs:s,waypointIndex:o,where:a,show:i,entryId:l,geometry:c}=e;try{const e=await Uo(a,t,r);if("error"in e)return e;const d=await gn(n,s,e.bias,r);if(!d)return{error:`No result found for "${n}"`};const u=await t.places.addPlaceResult(d,Qn(d,{query:n}),l);void 0!==o&&t.routing.setWaypointAt(o,d);const p=i?await(async(e,t,r,n)=>{const s=await Ms(e,[r],{...n,zoomMode:"none"});if("auto"===n.zoomMode){if(t.bbox)e.baseMap.mapLibreMap.fitBounds(t.bbox);else{const r=z(t);r&&e.baseMap.mapLibreMap.flyTo({center:r,zoom:13})}s.zoomMode=!0}return s})(t,d,u,i):void 0;let h,y;if(c){const e=await ks(t,u,c,r);h=e.fetched>0,e.shown&&(y=!0)}return{...ns(d),placesEntryId:u,...void 0!==o&&{waypointIndex:o},...p&&{shown:p},...void 0!==h&&{geometryFetched:h},...void 0!==y&&{geometryShown:y}}}catch(d){return{error:`Location resolution failed: ${d instanceof Error?d.message:String(d)}`}}},tags:["locate","place","location","waypoint"],examples:['locatePlace("Buckingham Palace", queryAs: "poi", waypointIndex: 0) // omit `where` to resolve the name anywhere (global)','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","Show Times Square on the map","Pin Schiphol Airport on the map","Outline Amsterdam on the map","Show the boundary of Berlin","Show the boundaries of Amsterdam and Utrecht","Pin the Eiffel Tower on the map","Where is the Sydney Opera House?"],relatedTools:["setRoute","addWaypointsToRoute","getCurrentWaypoints","recallState"]},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:Xo,outputSchema:Ko,execute:async(e,t,r)=>{const{position:n}=e,s=n;try{const e=Me({position:s,signal:r?.signal}),n=await P(e);if(n.properties){const e=await t.places.addPlaceResult(n,Qn(n));return{...ns(n),placesEntryId:e}}return{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:["Which city is [4.9, 52.4] in?","What's the address at this point?","Name the place where I clicked"],relatedTools:["getViewport","getCurrentLocation","updatePlacesDisplay","locatePlace"]},discoverPlaces:e=>{return e.featureFlags,t=vo,{...t,description:'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 queries/placeIds/geometries/range/route), `nearby` (position/viewport/query + optional radiusMeters), `maxDetour` (≤100 ranked detours off a route), `global`. Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. Route: `within.route` = corridor scan; `maxDetour` = ranked detour list. Both cap at 100 results. Default: `{ mode: "within", viewport: true }`. Never call this with empty `query` AND empty `poiCategories` — that is locatePlace territory.',inputSchema:yo(),outputSchema:po(),execute:wo()};var t},processData:(ds={classificationPrompt:`${nc} WRITES NEW renderable map entries — returns \`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }\` to write a places, custom-geometries, or BYOD entry, move the camera, or any combination. NOT for digests/charts/counts — those are \`analyseData\` (metadata only, never renders). Use for: filter/merge places (set ops), connect places (hub-spoke), compute polygons (union/buffer/h3-coverage/difference), focus camera on route sections via \`routeUtils.getSectionBBox\`, cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile).`,tags:["turf","h3","geometry","place","route","aggregation","connections"],examples:['processData({ placesEntryIDs: ["places-2"], code: "return { places: { type: \'FeatureCollection\', features: placesByEntry[\'places-2\'].features.filter(p => (p.properties.poi?.name ?? \'\').toLowerCase().includes(\'vegan\')) } };", label: "vegan only", show: { places: { markerType: "pin", zoomMode: "auto", hidePreviousEntries: "all" } } })','processData({ placesEntryIDs: ["places-1"], code: "const fc = placesByEntry[\'places-1\']; const [hub, ...rest] = fc.features; return { places: fc, placeConnections: rest.map(p => ({ from: hub, to: p })) };" })','processData({ placesEntryIDs: ["places-1"], code: "const fc = placesByEntry[\'places-1\']; const cells = new Set(); for (const p of fc.features) cells.add(h3.latLngToCell(p.geometry.coordinates[1], p.geometry.coordinates[0], 8)); return { places: fc, geometries: [...cells].map(c => turf.polygon([h3.cellToBoundary(c, true)], { cell: c })) };", show: { places: { markerType: "pin", zoomMode: "auto" }, placesGeometries: { theme: "outline" } } })','processData({ routesEntryIDs: ["routes-0"], code: "const r = routesByEntry[\'routes-0\'].features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };" })','processData({ routesEntryIDs: ["routes-0"], code: "const r = routesByEntry[\'routes-0\'].features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };" })','processData({ placesEntryIDs: ["ev-stations"], routesEntryIDs: ["routes-0"], code: "const r = routesByEntry[\'routes-0\'].features[0]; const buf = turf.buffer(r, 0.5, { units: \'kilometers\' }); const near = placesByEntry[\'ev-stations\'].features.filter(p => turf.booleanPointInPolygon(p, buf)); return { places: { type: \'FeatureCollection\', features: near }, fitOnMap: turf.bbox(turf.featureCollection([...near, r])) };", label: "ev near route", entryId: "ev-near-routes-0", show: { places: { markerType: "pin", zoomMode: "none", hidePreviousEntries: "all" } } })','processData({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam-id" }, { kind: "place", id: "rotterdam-id" }], code: "const polys = Object.values(geometriesByEntry).flat(); return { geometries: [turf.union(turf.featureCollection(polys))].filter(Boolean) };", label: "ams ∪ rdam", operation: "union" })','processData({ geometriesEntryIDs: [{ kind: "place", id: "paris-id" }], code: "return { geometries: Object.values(geometriesByEntry).flat().map(f => ({ ...turf.buffer(f, 5, { units: \'kilometers\' }), id: `${f.id}-buf` })) };", label: "paris +5km", operation: "buffer" })','processData({ geometriesEntryIDs: [{ kind: "place", id: "amsterdam-id" }, { kind: "ranges", id: "ranges-0" }], code: "const all = Object.values(geometriesByEntry).flat(); const outer = all.filter(g => g.properties._source.kind === \'place\'); const inner = all.filter(g => g.properties._source.kind === \'ranges\'); const env = turf.union(turf.featureCollection(outer)); const u = turf.union(turf.featureCollection(inner)); const inv = (env && u) ? turf.difference(turf.featureCollection([env, u])) : env; return { geometries: inv ? [{ ...inv, id: \'unreachable\' }] : [] };", label: "amsterdam minus reachable", operation: "difference", show: { customGeometries: { theme: "inverted", hidePreviousEntries: "all" } } })','processData({ geometriesEntryIDs: [{ kind: "ranges", id: "ranges-0" }, { kind: "places", id: "ev-stations" }], code: "const ranges = Object.values(geometriesByEntry).flat().filter(g => g.properties._source.kind === \'ranges\'); const env = ranges.length ? turf.union(turf.featureCollection(ranges)) : null; const ev = (placesByEntry?.[\'ev-stations\']?.features ?? []); const kept = env ? ev.filter(p => turf.booleanPointInPolygon(p, env)) : ev; return { places: { type: \'FeatureCollection\', features: kept } };", label: "ev within reachable" })','processData({ placesEntryIDs: ["ev-stations"], trafficAreaAnalyticsEntryIDs: ["tta-0"], code: "const hot = trafficAreaAnalyticsByEntry[\'tta-0\'].features.filter(t => (t.properties.congestionLevel ?? 0) >= 75); const kept = placesByEntry[\'ev-stations\'].features.filter(p => hot.some(t => turf.booleanPointInPolygon(p, t))); return { places: { type: \'FeatureCollection\', features: kept } };", label: "ev in high-congestion tiles" })','processData({ trafficAreaAnalyticsEntryIDs: ["tta-0"], code: "const hot = trafficAreaAnalyticsByEntry[\'tta-0\'].features.filter(t => (t.properties.congestionLevel ?? 0) >= 75).map((t, i) => ({ ...t, id: `hot-${i}` })); const u = hot.length ? turf.union(turf.featureCollection(hot)) : null; return { geometries: u ? [{ ...u, id: \'congestion-hot-zone\' }] : [] };", label: "congestion hot zone", operation: "union", show: { customGeometries: { theme: "filled" } } })'],examplePrompts:["Keep only the vegan restaurants","Draw lines from this hub to every nearby cafe","Show the h3 hex coverage of these places","Fit the map to the worst traffic section of the route","Fit the map to the first toll section of the route","Filter my loaded EV stations to those within 500 m of the route","Union the city boundaries I loaded into one custom polygon","Compute a 500 m buffer around the neighbourhood I loaded","Areas of Amsterdam not reachable from the hospitals","Filter loaded places to those inside the reachable area","Keep only the EV stations that fall inside high-congestion tiles","Create a custom polygon outlining the congestion hot zone in my loaded analytics"],relatedTools:["analyseData","recallState"],dependsOn:["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","addByodSource","recallState"]},e=>((e,t)=>{const r=e.enabledDataKinds??Jr;return{description:Ha(r,e.scope),inputSchema:za(r,e.scope),outputSchema:Ua,execute:ni,scopeSchema:Kr(r),scopePrompt:Ya(r),...t}})(e,ds)),analyseData:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??Jr;return{description:ia(r,e.scope),inputSchema:aa(r,e.scope),outputSchema:oa,execute:ca,scopeSchema:Kr(r),scopePrompt:la(r),...t}})(t,e))({classificationPrompt:`${nc} WRITES metadata only — returns JSON or a Chart.js config (\`outputFormat: "chart"\`), attached as \`_analysis[name]\` on every contributing entry. Never renders or creates new map entries (that is \`processData\`). Use for: counts / groupings / charts (by-category, hex density); route summaries (per-leg / per-country / alternative compare); route TRAFFIC analysis from \`route.properties.sections.traffic[]\` — pick THIS for "distribution / count / chart of <something> along the route", NOT \`getTrafficIncidents\`; geometry stats (areas, h3 coverage); historical traffic-area-analytics breakdowns (congestion / speed / travel-time per tile); cross-kind correlations (incidents-per-cluster, places-near-route, distance-to-route bins, route-segments-by-tile-metric). One-shot by default — it returns an \`analysisId\`; pass that to \`monitorAnalysis\` to keep the analysis recomputing whenever its source entries change (the recurring sandbox then also sees \`previous\` / \`now\`).`,tags:["analysis","turf","h3","place","route","geometry"],examples:['analyseData({ placesEntryIDs: ["places-2"], name: "by-category", code: "const fc = placesByEntry[\'places-2\']; const counts = {}; for (const p of fc.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; return { total: fc.features.length, byCategory: counts };" })','analyseData({ placesEntryIDs: ["places-1"], name: "hex-density-8", code: "const fc = placesByEntry[\'places-1\']; const bins = {}; for (const p of fc.features) { const [lng,lat] = p.geometry.coordinates; const cell = h3.latLngToCell(lat, lng, 8); bins[cell] = (bins[cell] ?? 0) + 1; } return { resolution: 8, bins };" })','analyseData({ placesEntryIDs: ["places-2"], name: "top-categories-bar", outputFormat: "chart", code: "const fc = placesByEntry[\'places-2\']; const counts = {}; for (const p of fc.features) for (const c of (p.properties.poi?.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]).slice(0,10); return { type: \'bar\', data: { labels: entries.map(e => e[0]), datasets: [{ label: \'Places\', data: entries.map(e => e[1]) }] } };" })','analyseData({ routesEntryIDs: ["routes-0"], name: "summary", code: "return routesByEntry[\'routes-0\'].features.map(r => ({ index: r.properties.index, distanceKm: r.properties.summary.lengthInMeters / 1000, durationMin: r.properties.summary.travelTimeInSeconds / 60, trafficDelaySec: r.properties.summary.trafficDelayInSeconds }));" })',"analyseData({ routesEntryIDs: [\"routes-0\"], name: \"alternative-comparison\", outputFormat: \"chart\", code: \"const r = routesByEntry['routes-0'].features; return { type: 'bar', data: { labels: r.map(x => `Route ${x.properties.index}`), datasets: [{ label: 'Travel time (min)', data: r.map(x => Math.round(x.properties.summary.travelTimeInSeconds / 60)) }, { label: 'Distance (km)', data: r.map(x => Math.round(x.properties.summary.lengthInMeters / 1000)) }] } };\" })","analyseData({ routesEntryIDs: [\"routes-0\"], name: \"on-route-incidents-by-category\", outputFormat: \"chart\", code: \"const sections = (routesByEntry['routes-0'].features[0].properties.sections.traffic ?? []); const counts = {}; for (const s of sections) for (const c of (s.categories ?? [])) counts[c] = (counts[c] ?? 0) + 1; const entries = Object.entries(counts).sort((a,b) => b[1]-a[1]); return { type: 'bar', data: { labels: entries.map(e => e[0]), datasets: [{ label: 'Incidents on route', data: entries.map(e => e[1]) }] }, options: { plugins: { title: { display: true, text: 'Traffic incidents along the route' } } } };\" })",'analyseData({ routesEntryIDs: ["routes-0"], name: "delay-by-country", code: "const r = routesByEntry[\'routes-0\'].features[0]; const byCountry = {}; for (const c of (r.properties.sections.country ?? [])) byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (r.properties.sections.traffic ?? []).filter(t => t.startPointIndex >= c.startPointIndex && t.endPointIndex <= c.endPointIndex).reduce((s, t) => s + (t.delayInSeconds ?? 0), 0); return byCountry;" })','analyseData({ routesEntryIDs: ["routes-0"], placesEntryIDs: ["ev-stations"], name: "ev-near-route-500m", code: "const route = routesByEntry[\'routes-0\'].features[0]; const out = {}; for (const [id, fc] of Object.entries(placesByEntry)) out[id] = fc.features.filter(p => turf.pointToLineDistance(p, route, { units: \'meters\' }) <= 500).length; return { withinMeters: 500, byEntry: out };" })',"analyseData({ routesEntryIDs: [\"routes-0\"], placesEntryIDs: [\"cafes\"], name: \"cafe-distance-bins-km\", outputFormat: \"chart\", code: \"const route = routesByEntry['routes-0'].features[0]; const distances = placesByEntry['cafes'].features.map(p => turf.pointToLineDistance(p, route, { units: 'kilometers' })); const max = Math.max(0, ...distances); const binWidth = 1; const binCount = Math.max(1, Math.ceil(max / binWidth)); const bins = new Array(binCount).fill(0); for (const d of distances) bins[Math.min(binCount - 1, Math.floor(d / binWidth))]++; const labels = bins.map((_, i) => `${i}-${i + binWidth} km`); return { type: 'bar', data: { labels, datasets: [{ label: 'cafes', data: bins }] }, options: { plugins: { title: { display: true, text: 'Cafe distance to route' } } } };\" })",'analyseData({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "areas", code: "return Object.values(geometriesByEntry).flat().map(f => ({ id: f.id, km2: +(turf.area(f)/1e6).toFixed(2) }));" })','analyseData({ geometriesEntryIDs: [{ kind: "places", id: "cities" }], name: "area-bar", outputFormat: "chart", code: "const polys = Object.values(geometriesByEntry).flat(); return { type: \'bar\', data: { labels: polys.map(f => f.id), datasets: [{ label: \'km²\', data: polys.map(f => +(turf.area(f)/1e6).toFixed(2)) }] } };" })',"analyseData({ placesEntryIDs: [\"clusters\"], incidentsEntryIDs: [\"incidents-0\"], name: \"incidents-per-cluster\", code: \"const fc = placesByEntry['clusters']; const inc = incidentsByEntry['incidents-0']; const out = {}; for (const c of fc.features) { const inCluster = inc.filter(i => { const coord = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.distance(coord, c.geometry.coordinates, { units: 'kilometers' }) <= 0.5; }); out[c.id] = inCluster.length; } return out;\" })",'analyseData({ geometriesEntryIDs: [{ kind: "customGeometries", id: "buffered-routes" }], incidentsEntryIDs: ["incidents-0"], name: "incidents-in-corridors", code: "const polys = Object.values(geometriesByEntry).flat(); const inc = incidentsByEntry[\'incidents-0\']; const out = []; for (const g of polys) { const hits = inc.filter(i => { const coord = i.geometry.type === \'Point\' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; return turf.booleanPointInPolygon(coord, g); }); out.push({ id: g.id, count: hits.length, totalDelaySec: hits.reduce((s, i) => s + (i.properties.delayInSeconds ?? 0), 0) }); } return out;" })',"analyseData({ trafficAreaAnalyticsEntryIDs: [\"tta-0\"], name: \"congestion-distribution\", outputFormat: \"chart\", code: \"const bins = [0, 25, 50, 75, 100]; const counts = new Array(bins.length).fill(0); for (const t of trafficAreaAnalyticsByEntry['tta-0'].features) { const c = t.properties.congestionLevel ?? 0; const idx = Math.min(bins.length - 1, Math.floor(c / 25)); counts[idx]++; } return { type: 'bar', data: { labels: ['0–24', '25–49', '50–74', '75+'], datasets: [{ label: 'Tiles', data: counts.slice(0, 4) }] } };\" })",'analyseData({ placesEntryIDs: ["ev-stations"], trafficAreaAnalyticsEntryIDs: ["tta-0"], name: "ev-stations-by-congestion", code: "const fc = placesByEntry[\'ev-stations\']; const tiles = trafficAreaAnalyticsByEntry[\'tta-0\'].features; const buckets = { low: 0, mid: 0, high: 0 }; for (const p of fc.features) { const tile = tiles.find(t => turf.booleanPointInPolygon(p, t)); if (!tile) continue; const c = tile.properties.congestionLevel ?? 0; if (c < 25) buckets.low++; else if (c < 75) buckets.mid++; else buckets.high++; } return buckets;" })','analyseData({ routesEntryIDs: ["routes-0"], trafficAreaAnalyticsEntryIDs: ["tta-0"], name: "route-tile-coverage", code: "const r = routesByEntry[\'routes-0\'].features[0]; const hits = trafficAreaAnalyticsByEntry[\'tta-0\'].features.filter(t => turf.lineIntersect(r, t).features.length > 0); return { tilesCrossed: hits.length, avgCongestion: hits.length ? +(hits.reduce((s, t) => s + (t.properties.congestionLevel ?? 0), 0) / hits.length).toFixed(1) : null };" })','analyseData({ incidentsEntryIDs: ["incidents-0"], name: "by-category", code: "const inc = incidentsByEntry[\'incidents-0\']; const c = {}; for (const i of inc) c[i.properties.category] = (c[i.properties.category] ?? 0) + 1; return { total: inc.length, byCategory: c };" })','analyseData({ incidentsEntryIDs: ["incidents-0"], name: "top-delays", code: "return [...incidentsByEntry[\'incidents-0\']].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] }));" })'],examplePrompts:["How many of each POI category are in these results?","h3 hex density of these places","Bar chart of the top POI categories","Bar chart of the lengths of my route alternatives","Bar chart of travel times across my route alternatives","Of my loaded EV stations, how many are within 500 m of my route?","Chart the distance from each cafe to the route, binned in km","Histogram of place-to-route distances","Distribution by category of the traffic incidents I loaded along my route","Chart the incidents on the route by category","Total delay by country for the incidents on my route","How big are the city areas I loaded?","Bar chart of the sizes of the city areas I loaded","Count the loaded incidents per cluster of my loaded places","Total delay per corridor for my loaded route incidents","Congestion distribution across the analytics tiles","Chart the average historical speed per tile in my loaded analytics","Group these EV stations by the congestion level of the tile they fall in","For the route, which tiles does it cross and what is their average congestion?","How many incidents of each category?","Top 5 incidents by delay","Total delay per road across my loaded incidents","Bar chart of incidents per category"],relatedTools:["processData","focusIncidents","recallState"],dependsOn:["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","addByodSource","processData","recallState"]}),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. To MONITOR traffic between places over time, calculate with `parameters.costModel.traffic: "historical"` + `maxAlternatives: 1-2` (stable typical paths) and set `monitor: true` — that arms the live monitor in the same call (each tick refreshes live-traffic delays on the same routes), so no separate startRouteMonitor call is needed. Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallState / addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / analyseData 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. To MONITOR / track / keep fresh the live traffic BETWEEN two places, START here (calculate with traffic "historical" + maxAlternatives 1-2 for stable paths), then pair with startRouteMonitor — startRouteMonitor needs a route to exist first.',inputSchema:so,outputSchema:no,execute:async(e,t,r)=>{const{locations:n,parameters:s,showOnMap:o,showWaypoints:a,showSummaryBubbles:i,monitor:l,hidePreviousEntries:c}=e;try{let e;if(s&&t.routing.setParams(s),n){const s=await Promise.all(n.map(e=>Qs(e,t,r))),o=[];for(let e=0;e<n.length;e++){if(null!==s[e])continue;const t=n[e];"query"in t?o.push(`"${t.query}"`):"placeIdOrEntryId"in t&&o.push(`placeIdOrEntryId "${t.placeIdOrEntryId}"`)}if(o.length>0)return{error:`Could not resolve: ${o.join(", ")}`};if(e=oo(s.map(e=>e?.place??null)),!e)return{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}else if(e=oo(t.routing.planningSlots),!e)return{success:!0};return lo(t,e,o,c,l,a,i,r)}catch(d){return{error:`Route calculation failed: ${d instanceof Error?d.message:String(d)}`}}},tags:["route","waypoint","location","traffic"],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" } } })','setRoute({ locations: [{ query: "Paris" }, { query: "Amsterdam" }], parameters: { maxAlternatives: 2, costModel: { traffic: "historical" } }, monitor: true })'],examplePrompts:["Get directions from Paris to Lyon","Route me from Amsterdam to Brussels","Plan a new trip from Amsterdam to Schiphol airport","Recalculate my route to Brussels from Schiphol airport","Recalculate the route from Utrecht to Brussels","Avoid toll roads on my current route","Find the fastest route from Berlin to Munich","Show 2 alternative routes for my current trip","Depart at 9am on my current route","Monitor traffic between Paris and Amsterdam","Monitor traffic between Santa Monica and Downtown LA","Keep an eye on live traffic from the office to the airport","Show me fresh traffic between A and B","Plan a road trip from Lisbon to Porto","Take me from the Eiffel Tower to the Arc de Triomphe"],relatedTools:["startRouteMonitor","updateRoutesDisplay","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","recallState"]},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:uo,outputSchema:co,execute:async(e,t,r)=>{const{origin:n,destination:s,stops:o,showOnMap:a,hidePreviousEntries:i}=e;try{const e=await(async e=>{const t=e.routing.currentEntryModule;if(!t)return"No existing route on the map. Use calculate-route or updateRoutesDisplay first.";const r=t.getShown(),n=r?.mainLines,s=r?.waypoints;return n&&n.features.length?!s||s.features.length<2?"No existing waypoints available. Use calculate-route first to create a route with waypoints.":{shownRouteLines:n,shownWaypoints:s}:"No existing route available. Use calculate-route first."})(t);if("string"==typeof e)return{error:e};const{shownRouteLines:l,shownWaypoints:c}=e,d=await(async(e,t,r)=>{const{origin:n,destination:s,stops:o=[]}=e,a=[...n?[{kind:"origin",location:n}]:[],...s?[{kind:"destination",location:s}]:[],...o.map(e=>({kind:"stop",location:e}))],i=await Promise.all(a.map(({location:e})=>Qs(e,t,r))),l=[];for(let d=0;d<a.length;d++)null===i[d]&&l.push(wn(a[d].location));if(l.length>0)return{error:`Could not resolve: ${l.join(", ")}`};const c={stops:[]};for(let d=0;d<a.length;d++){const e=i[d]?.place;"origin"===a[d].kind?c.origin=e:"destination"===a[d].kind?c.destination=e:c.stops.push(e)}return c})({origin:n,destination:s,stops:o},t,r);if("error"in d)return d;const u=[...d.origin?[d.origin]:[],...c.features,...d.destination?[d.destination]:[]],p=d.stops.length>0?Q(l.features[0],u,d.stops):u,h=oo(p);return h?lo(t,h,a,i,void 0,void 0,void 0,r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(l){return{error:`Failed to add waypoints to route: ${l instanceof Error?l.message:String(l)}`}}},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:Ho,outputSchema:zo,execute:async(e,t,r)=>{const{waypointIndices:n,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 n of t)if(n<0||n>=e.length)return`Invalid waypoint index ${n}. 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,n);if("string"==typeof e)return{error:e};const a=oo(e);return a?lo(t,a,s,o,void 0,void 0,void 0,r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(a){return{error:`Failed to remove waypoints from route: ${a instanceof Error?a.message:String(a)}`}}},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:Jo,outputSchema:Yo,execute:async(e,t,r)=>{const{waypointIndex:n,location:s,showOnMap:o,hidePreviousEntries:a}=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 c=(i=n,l=e.length,"origin"===i?0:"destination"===i?l-1:i<0||i>=l?`Invalid waypoint index ${i}. Valid range is 0 to ${l-1}.`:i);if("string"==typeof c)return{error:c};const d=await Qs(s,t,r);if(!d)return{error:`Could not resolve location: ${wn(s)}`};const u=[...e];u[c]=d.place;const p=oo(u);return p?lo(t,p,o,a,void 0,void 0,void 0,r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(c){return{error:`Failed to replace waypoint in route: ${c instanceof Error?c.message:String(c)}`}}var i,l},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:To,outputSchema:Do,execute:async(e,t,r)=>{const{filters:n,language:s}=e;try{const e=Me({filters:n,language:s,signal:r?.signal}),t=await T(e);return{count:t.length,codes:t}}catch(o){return{error:`Failed to fetch POI category codes: ${o instanceof Error?o.message:String(o)}`}}},tags:["discover","place"],examples:["getPOICategoryCodes()",'getPOICategoryCodes(filters: ["gym"])','getPOICategoryCodes(filters: ["italian restaurant"]) // multi-word phrase, spaces removed before matching','getPOICategoryCodes(filters: ["restaurant"], language: "fr-FR")'],examplePrompts:["What POI categories are available?","What's the code for gym?"],relatedTools:["discoverPlaces","toggleTilesPOIs"]},getTrafficIncidents:{description:'LOADER. Fetches present/future traffic incidents into a cached entry, renders them, and returns `{ count, entryId, entries }` (`entries` lists every cached entry as a follow-up target). No per-incident detail or aggregation — those belong to analyseData/focusIncidents. \n\nAREA via `where` (within mode only): omit `where` → current viewport; named area → `where.queries`; stored places → `where.placeIds` (a places ENTRY id like "places-0" or locatePlace\'s `placesEntryId`, or a single place id); isochrone → `where.range`; route corridor → `where.route`; raw bounds → `where.boundingBox`; custom shapes → `where.geometries`. Fields union into one bbox (≤10,000 km²). \n\nCALL ONCE PER AREA — each call writes a new entry, so re-calling burns steps; pick a follow-up instead: analyseData (counts, top-N, charts, clusters, trend via `previous`) or focusIncidents (highlight a subset). \n\nSTAYS LIVE BY DEFAULT: a shown fetch arms a background monitor (`monitor: true`) that keeps the entry current and re-runs registered analyses each tick — no separate setTrafficIncidentsMonitor call needed (that is only for an entry loaded on an EARLIER turn). Pass `monitor: false` for a one-shot snapshot. `show: false` suppresses rendering (throwaway loads only; never monitored); `label` names the snapshot in the UI. \n\nCHECK WHERE IT RESOLVED: the result `resolvedAreas` reports the GROUNDED place each named query matched (e.g. "east London" → "London, CA"). If it resolved wrong, re-issue with a more specific query (append a region/country).',classificationPrompt:`AREA-BOUND incident loader — bbox / viewport / "incidents around here" / "accidents in <city>". Covers every incident category: ${Z.join(", ")}. DO NOT pick for route-bound queries ("incidents ALONG / ON the route", "delays on the drive from X to Y"): a calculated route already carries its incidents in \`route.properties.sections.traffic[]\` — call \`analyseData\` with \`routesEntryIDs\` for those. Monitoring is built in: a shown fetch keeps the entry live (\`monitor: true\` by default; \`monitor: false\` for a one-shot count) — no separate setTrafficIncidentsMonitor call needed. Co-pick analyseData (aggregations / charts / clusters; pass \`monitor: { entryId }\` for the recurring spec) and focusIncidents (highlight a subset) when the query implies them.`,inputSchema:qo,outputSchema:No,execute:async(e,t,r)=>{const{where:n,categoryFilter:s,timeValidityFilter:o,label:a,show:i=!0,fitBounds:l=!0,monitor:c,monitorIntervalMs:d}=e,u=n??{mode:"within",viewport:!0},p={...s&&{categoryFilter:s},...o&&{timeValidityFilter:o}};try{const e=await $o(u,t);if("error"in e)return e;const{bbox:n,routeLabel:s,resolvedAreas:o}=e,h=Me({...p,bbox:n,signal:r?.signal}),y=await R(h),m={bbox:n,...p},f=Date.now(),g=a??((e,t,r,n)=>{const s=[];e.categoryFilter?.length&&s.push(`filtered: ${e.categoryFilter.join(",")}`),e.timeValidityFilter?.length&&s.push(`when: ${e.timeValidityFilter.join(",")}`);let o=n?.length?n.map(e=>e.matched).join(", "):void 0;if(void 0===o&&t&&!t.viewport&&(t.queries?.length?o=t.queries.map(e=>e.query).join(", "):r?o=`along ${r}`:t.range&&(o=t.range)),!o){const[t,r,n,s]=e.bbox;o=`bbox ${t.toFixed(2)},${r.toFixed(2)} → ${n.toFixed(2)},${s.toFixed(2)}`}return s.length?`${o} — ${s.join("; ")}`:o})(m,u,s,o);if(!y.features.length)return{count:0,entryId:await t.trafficIncidents.addIncidentsEntry([],m,g,f),entries:jo(t),...o&&o.length>0&&{resolvedAreas:o}};const b=await t.trafficIncidents.addIncidentsEntry(y.features,m,g,f);i&&await t.trafficIncidents.showEntry(b,l);const w=i&&(c??!0);return w&&t.trafficIncidents.startMonitoring(b,{bbox:n,capturedAt:f,label:g},{fetchIncidents:async e=>{const t=Me({...p,bbox:e});return(await R(t)).features}},{intervalMs:d,skipInitialTick:!0}),{count:y.features.length,entryId:b,entries:jo(t),monitoring:w,...o&&o.length>0&&{resolvedAreas:o}}}catch(h){return{error:`Failed to get traffic incidents: ${h instanceof Error?h.message:String(h)}`}}},tags:["traffic","location"],examples:["getTrafficIncidents({}) // omit `where` → current viewport",'getTrafficIncidents({ where: { mode: "within", boundingBox: [4.728, 52.278, 5.080, 52.479] } })','getTrafficIncidents({ where: { mode: "within", queries: [{ query: "Amsterdam" }] } }) // named area','getTrafficIncidents({ where: { mode: "within", queries: [{ query: "Amsterdam" }] }, categoryFilter: ["accident", "jam"] })','getTrafficIncidents({ where: { mode: "within", route: { widthMeters: 1000 } }, categoryFilter: ["roadworks"], timeValidityFilter: ["future"] }) // corridor around latest route','getTrafficIncidents({ where: { mode: "within", range: "ranges-0" } }) // inside a reachable area'],examplePrompts:["Fetch traffic incidents in Amsterdam","Any accidents in this area?","Show roadworks across the city","Watch this area for new accidents","Are there any road closures downtown right now?","Show me the jams around the Amsterdam ring road"],relatedTools:["calculateBBox","getViewport","analyseData","focusIncidents","setTrafficIncidentsMonitor"]},focusIncidents:{description:"Highlight a subset of incidents on an existing entry. Drives the dim/highlight on the map and the FocusChip in the UI. Compose with `analyseData` to compute the ids first (e.g. top-N by delay, members of a cluster). Unknown ids are dropped silently and returned in `droppedIds`. Errors only when the entry is unknown or NONE of the ids resolve.",classificationPrompt:'Highlight a subset of incidents on an existing entry (drives map dim/highlight + FocusChip). Pick for "focus on…", "show only…", "highlight the…". Compose with analyseData to compute the ids.',inputSchema:Oa,outputSchema:_a,execute:async(e,t)=>{const{incidentsEntryID:r,incidentIds:n,reason:s}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const o=t.trafficIncidents.setFocus(r,n,s);return 0===o.focusedCount?{error:`None of the ${n.length} requested ids exist on entry "${r}". The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.`}:{incidentsEntryID:r,focusedCount:o.focusedCount,droppedIds:o.droppedIds,...s&&{reason:s}}},tags:["traffic","incident","state","ui"],examples:["focusIncidents({ incidentsEntryID: 'incidents-0', incidentIds: ['TTI-...', 'TTI-...'], reason: 'top 3 by delay' })"],examplePrompts:["Focus on the 3 worst incidents by delay","Highlight the closures","Show only the incidents on the A4"],relatedTools:["analyseData","getTrafficIncidents"],dependsOn:["getTrafficIncidents"]},clusterIncidents:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??Jr,{active:n}=va(r,e.scope);return{description:Ma(n),inputSchema:Ea(r,e.scope),outputSchema:Sa,execute:xa,scopeSchema:Kr(r),scopePrompt:Aa(r),...t}})(t,e))({classificationPrompt:'DBSCAN clustering of loaded traffic incidents — "clusters / hotspots / dense pockets / worst areas". Produces stable IDs, road/category labels, delay aggregates, trend detection. Auto-reruns on monitor ticks. Also handles clustering of a FILTERED subset ("only jams / accidents", "delayed over N minutes", "along this route / within this area") via generated `code`. Co-pick with getTrafficIncidents (which keeps the entry monitored by default).',tags:["traffic","incident","analysis","clustering"],examples:["clusterIncidents({ incidentsEntryID: 'incidents-0' })","clusterIncidents({ incidentsEntryID: 'incidents-0', eps: 0.3, minMembers: 4, maxClusters: 8 })","clusterIncidents({ incidentsEntryID: 'incidents-0', code: \"const major = incidentsByEntry['incidents-0'].filter(i => (i.properties.delayInSeconds ?? 0) >= 300); return cluster(major, { eps: 0.4 }, previous, now);\" })","clusterIncidents({ incidentsEntryID: 'incidents-0', code: \"const jams = incidentsByEntry['incidents-0'].filter(i => i.properties.category === 'jam'); return cluster(jams, {}, previous, now);\" })"],examplePrompts:["Cluster the incidents","Show me the hotspots","Where are the worst congestion pockets?","Group the incidents into clusters","Cluster only the major jams","Where are the hotspots of incidents delayed more than 5 minutes?","Cluster just the accidents and road closures"],relatedTools:["getTrafficIncidents","setTrafficIncidentsMonitor","analyseData","focusIncidents"],dependsOn:["getTrafficIncidents"]}),monitorAnalysis:{description:'Start or stop MONITORING an existing analysis (one created by `analyseData`, referenced by its `analysisId`). When enabled, the analysis recomputes on every change to its source entries — incidents monitor ticks, re-searched places, recalculated routes, refreshed BYOD/area-analytics — keeping its attached result live (use `previous` / `now` in the code for trend reads). Pick for "keep this updated / tell me when it changes / stop tracking it". This is a pure on/off switch: define the computation with `analyseData` first. For a one-off highlight of incidents use `focusIncidents`.',classificationPrompt:'Start or stop MONITORING an existing analysis by its `analysisId` (from analyseData) — a pure on/off switch that recomputes the analysis on every change to its source entries (incidents monitor ticks, re-searched places, recalculated routes, refreshed data). When the user points at an analysis ALREADY in state ("that count", "the breakdown you computed") and wants it kept live — "keep that count updated and tell me when it grows or fades", "stop tracking that analysis" — pick THIS ALONE: the analysis already exists, so do NOT re-fetch the data or arm a tracker. "Tell me when it grows or fades" about an existing computed metric is monitorAnalysis, not createTracker. Define the computation with analyseData first (a ONE-SHOT analysis needs analyseData alone); for a one-off highlight use focusIncidents.',inputSchema:$a,outputSchema:qa,execute:async(e,t)=>((e,t,r)=>{const n=e.analyses.job(t);return!!n&&(n.setActive(r),!0)})(t,e.analysisId,e.enabled)?{analysisId:e.analysisId,enabled:e.enabled}:{error:`No analysis with id "${e.analysisId}". Run analyseData first and pass the analysisId it returns.`},tags:["analysis","monitor"],examples:['monitorAnalysis({ analysisId: "rolling-count::incidents-0", enabled: true })','monitorAnalysis({ analysisId: "ev-near-route::places-0,route-1", enabled: false })'],examplePrompts:["Keep that incident count updated and tell me when it grows or fades","Stop tracking that analysis"],relatedTools:["analyseData","getTrafficIncidents","setTrafficIncidentsMonitor","focusIncidents"],dependsOn:["analyseData"]},createTracker:{description:'Arm a tracker: watch loaded entries and alert when a condition crosses from false to true (and log when it clears). You write `code` returning `{ active, members, summary }`; it recomputes automatically whenever the watched entries change (a monitored incidents poll, a re-search, …). Reads the same multi-kind inputs as `analyseData`, so one rule can span kinds — e.g. an incident near a hospital. The `*EntryIDs` you pass MUST be ids of entries already loaded this session — load them FIRST (e.g. call getTrafficIncidents and pass its returned entry id) and never invent ids like "incidents-0". CONTRAST `analyseData` (one-shot metadata, no alerting). Use `clearTracker` to stop, `getTrackerHistory` to read what fired.',classificationPrompt:'Arm a TRACKER (a watch that ALERTS) on already-loaded entries: you write `code` returning `{ active, members, summary }`, and it recomputes whenever the watched entries change, logging an alert when the condition turns true and an event when it clears. Pick for "alert me when…", "let me know if…", "watch for…", "notify when an incident appears near…". Reads multiple entry kinds at once (e.g. incidents near hospitals/places). The watched entries must already be loaded — for a fresh area co-pick getTrafficIncidents (load the incidents) and locatePlace (resolve a named area) so the tracker has real entry ids to read. CONTRAST analyseData (one-shot, no alerts) and monitorAnalysis: keeping an EXISTING analysis fresh — "keep that count updated, tell me when it grows or fades" — is monitorAnalysis, NOT a tracker. createTracker defines a NEW condition in `code` from scratch.',inputSchema:Da,outputSchema:Ta,execute:async(e,t)=>{const r={placesEntryIDs:e.placesEntryIDs,routesEntryIDs:e.routesEntryIDs,incidentsEntryIDs:e.incidentsEntryIDs,geometriesEntryIDs:e.geometriesEntryIDs,trafficAreaAnalyticsEntryIDs:e.trafficAreaAnalyticsEntryIDs,byodEntryIDs:e.byodEntryIDs},n=await Tn(r,t);if("error"in n)return{error:n.error};const{resolved:s,sandbox:o,geometries:a}=n.value,i=await Ye(e.code,jn,Bn(o,{h3:re,turf:ee}),"Tracker rule",t.codeExecution);if("error"in i)return{error:i.error};if(!dt(i.value))return{error:"Tracker `code` must return { active: boolean, members: {entryId, featureIds}[], summary: string }."};const l=i.value,c=[...s.places.map(e=>e.id),...s.routes.map(e=>e.id),...s.incidents.map(e=>e.id),...s.trafficAreaAnalytics.map(e=>e.id),...s.byod.map(e=>e.id),...a.affectedEntries.map(e=>e.id)],d=Pa(e.name,t),u=Date.now(),p={id:d,name:e.name,rule:e.rule,affectedEntryIds:c,enabled:!0,wasActive:!1};return t.trackers.register(p),((e,t)=>{const r=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:!0,run:async()=>{const r=await Tn(t.inputs,e);if("error"in r)return;const n=xn(r.value.resolved)??Date.now(),s=await sa(e,r.value,{code:t.code,previous:void 0,sampledAt:n,outputFormat:"json"});!("error"in s)&&dt(s.value)&&e.trackers.ingestVerdict(t.trackerId,s.value,n)},onOrphaned:()=>e.trackers.unregister(t.trackerId)});e.trackers.setJob(t.trackerId,r)})(t,{trackerId:d,code:e.code,inputs:r,affectedEntryIds:c}),t.trackers.ingestVerdict(d,l,u),{trackerId:d,name:e.name,rule:e.rule,watching:c,firingNow:l.active,summary:l.summary}},tags:["tracker","alert","monitor","turf"],examples:["createTracker({ incidentsEntryIDs: [\"incidents-0\"], name: \"Major incidents\", rule: \"any major incident in the area\", code: \"const hits = incidentsByEntry['incidents-0'].filter(i => i.properties.magnitudeOfDelay === 'major'); return { active: hits.length > 0, members: [{ entryId: 'incidents-0', featureIds: hits.map(i => i.properties.id) }], summary: hits.length ? `${hits.length} major incident(s)` : 'no major incidents' };\" })","createTracker({ placesEntryIDs: [\"hospitals\"], incidentsEntryIDs: [\"incidents-0\"], name: \"Incidents near hospitals\", rule: \"an incident within 100m of a hospital\", code: \"const near = []; const hosp = new Set(); for (const h of placesByEntry['hospitals'].features) for (const i of incidentsByEntry['incidents-0']) { const c = i.geometry.type === 'Point' ? i.geometry.coordinates : turf.centroid(i).geometry.coordinates; if (turf.distance(c, h.geometry.coordinates, { units: 'meters' }) <= 100) { near.push(i.properties.id); hosp.add(h.properties.id); } } return { active: near.length > 0, members: [{ entryId: 'hospitals', featureIds: [...hosp] }, { entryId: 'incidents-0', featureIds: near }], summary: near.length ? `${near.length} incident(s) near ${hosp.size} hospital(s)` : 'clear' };\" })"],examplePrompts:["Alert me when there is a major incident in this area","Let me know if an incident appears within 100m of a hospital","Watch these incidents and tell me when a jam forms near my route"],relatedTools:["getTrafficIncidents","analyseData","clearTracker","getTrackerHistory","getTrackers"],dependsOn:["getTrafficIncidents","locatePlace","analyseData"]},clearTracker:{description:"Stop a tracker and remove its rule. Does not touch the watched entries (they were never the tracker's). Omit `trackerId` when there is only one tracker.",classificationPrompt:'Stop a tracker armed by createTracker — "stop watching / stop alerting / clear that tracker". Omit trackerId when only one exists. Does not touch the watched entries.',inputSchema:da,outputSchema:ua,execute:async(e,t)=>{const r=t.trackers.trackers;if(0===r.length)return{error:"No trackers to clear."};const n=e.trackerId??(1===r.length?r[0].id:void 0);if(void 0===n)return{error:`Which tracker? Pass trackerId — one of: ${r.map(e=>e.id).join(", ")}.`};const s=t.trackers.get(n);return s?(t.trackers.unregister(n),{cleared:!0,trackerId:n,name:s.name}):{error:`No tracker "${n}". Active: ${r.map(e=>e.id).join(", ")||"none"}.`}},tags:["tracker","alert"],examples:["clearTracker({})",'clearTracker({ trackerId: "incidents-near-hospitals" })'],examplePrompts:["Stop that tracker","Stop alerting me about incidents near hospitals"],relatedTools:["createTracker","getTrackers"]},getTrackerHistory:{description:"Read the tracker event log — `opened` alerts and `resolved` events, newest first. Optionally scope to one `trackerId` or a `sinceMs` cutoff. The log is bounded; `coverage` reports how far back it reaches.",classificationPrompt:'Read what trackers have FIRED — the alert/event log ("what alerts fired", "what happened with my trackers", "any incidents near hospitals today"). Scope to one trackerId or a sinceMs cutoff.',inputSchema:ja,outputSchema:Ba,execute:async(e,t)=>{const r=t.trackers.log({trackerId:e.trackerId}),n=[...t.trackers.log({trackerId:e.trackerId,sinceMs:e.sinceMs})].reverse().map(e=>({at:e.at,trackerId:e.trackerId,trackerName:e.trackerName,type:e.type,kind:e.kind,summary:e.summary,...void 0!==e.openedAt&&{openedAt:e.openedAt}}));return{events:n,count:n.length,...r.length>0&&{coverage:{oldestRetainedAt:r[0].at,newestAt:r[r.length-1].at}}}},tags:["tracker","alert","history"],examples:["getTrackerHistory({})",'getTrackerHistory({ trackerId: "incidents-near-hospitals" })'],examplePrompts:["What alerts have fired?","Show me the history for that tracker"],relatedTools:["createTracker","getTrackers"]},getTrackers:{description:"List active trackers: name, rule, enabled, whether firing now, and which entries each watches.",classificationPrompt:'List the active trackers and their live state — "what am I tracking / what alerts are set / is anything firing". Read-only.',inputSchema:La,outputSchema:Fa,execute:async(e,t)=>{const r=t.trackers.trackers.map(e=>({trackerId:e.id,name:e.name,rule:e.rule,enabled:e.enabled,firing:e.wasActive,...void 0!==e.openedAt&&{openedAt:e.openedAt},watching:[...e.affectedEntryIds]}));return{trackers:r,count:r.length}},tags:["tracker","alert"],examples:["getTrackers({})"],examplePrompts:["What am I tracking?","Which alerts are set up?"],relatedTools:["createTracker","getTrackerHistory"]},setTrafficIncidentsMonitor:{description:'Start or stop the background monitor for an EXISTING incidents entry (`enabled: true` / `false`). When enabled, re-fetches the entry\'s bbox + filters every interval (default 60s) and replaces its data; the first tick fires immediately, and any registered analyses re-run each tick. When disabled, polling stops and the entry stays in state with its last data (use removeEntry to drop it). \n\ngetTrafficIncidents already monitors a fresh shown fetch by default, so only enable here for an entry loaded on an EARLIER turn ("keep that area updated") or one loaded with monitor: false. Enabling is idempotent on a running monitor (no-op, alreadyInState: true); a new intervalMs is NOT applied to a running monitor — disable then enable to change it. Only a displayed entry can be monitored: a show:false load is a one-shot snapshot.',classificationPrompt:'Arm or pause background polling on an ALREADY-loaded incidents entry — one toggle (`enabled: true/false`). Enable for "keep updated"/"watch"/"live"/"refresh every N seconds" referring to incidents loaded on an EARLIER turn; disable for "stop refreshing"/"pause updates". A fresh getTrafficIncidents fetch already monitors by default, so do not co-pick this to start monitoring right after it.',inputSchema:ol,outputSchema:sl,execute:async(e,t)=>{const{incidentsEntryID:r,enabled:n,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.`};if(!n){const e=t.trafficIncidents.isMonitored(r);return t.trafficIncidents.stopMonitoring(r),{incidentsEntryID:r,enabled:!1,alreadyInState:!e}}if(!t.trafficIncidents.shownEntryIds.has(r))return{error:`Entry "${r}" isn't displayed on the map, so it can't be monitored. Monitoring keeps the live shown area current; a show:false load is a one-shot snapshot. Re-fetch the area with getTrafficIncidents (show: true, the default) and monitor that entry.`};const a=t.trafficIncidents.isMonitored(r),{bbox:i,categoryFilter:l,timeValidityFilter:c}=o.params,d={...l&&{categoryFilter:l},...c&&{timeValidityFilter:c}};return t.trafficIncidents.startMonitoring(r,{bbox:i,capturedAt:o.timestamp,label:o.label},{fetchIncidents:async e=>{const t=Me({...d,bbox:e});return(await R(t)).features}},s?{intervalMs:s}:void 0),{incidentsEntryID:r,enabled:!0,alreadyInState:a}},tags:["traffic","incident","state"],examples:["setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: true })","setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: true, intervalMs: 30000 })","setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: false })"],examplePrompts:["Keep the incidents I loaded updated","Refresh the incidents every 30 seconds","Stop refreshing the incidents"],relatedTools:["getTrafficIncidents","analyseData","focusIncidents"],dependsOn:["getTrafficIncidents"]},startRouteMonitor:{description:'Start a background monitor on an EXISTING route entry: recalculates the SAME route (same waypoints + parameters) every interval (default 60s) and replaces its data in place, so the route stays current with live traffic. The first recalculation fires immediately. \n\nPairs with a route calculated as `traffic: "historical"` + `maxAlternatives: 1-2`: historical input keeps the typical route PATHS stable across ticks (live input would keep re-routing to dodge current traffic), while each recalculation refreshes the live-traffic delays carried on those paths. \n\nIdempotent on a running monitor: a second call is a no-op returning alreadyRunning: true. A new intervalMs is NOT applied to a running monitor — stop first, then start again. Listen to the routing `entries-change` / `monitor-tick` events to react to each refresh.',classificationPrompt:'Arm background recalculation on an ALREADY-calculated route (default 60s): re-runs the SAME route each tick so its live-traffic delays stay current. Pick for "keep this route updated" / "watch the traffic on this drive" / "recalculate every N seconds". Best paired with a route calculated as traffic "historical" + maxAlternatives 1-2 (stable typical paths, fresh delays). Not needed just to calculate a route — that is setRoute.',inputSchema:il,outputSchema:al,execute:async(e,t)=>{const{routesEntryID:r,intervalMs:n}=e,s=t.routing.entries.find(e=>e.id===r);if(!s)return{error:`No routes entry with id "${r}". Calculate a route first via setRoute.`};const o=t.routing.isMonitored(r),{waypoints:a,params:i}=s;return t.routing.startMonitoring(r,{recalculate:io(a,i)},n?{intervalMs:n}:void 0),{routesEntryID:r,alreadyRunning:o}},tags:["route","traffic","state"],examples:["startRouteMonitor({ routesEntryID: 'routes-0' })","startRouteMonitor({ routesEntryID: 'routes-0', intervalMs: 30000 })"],examplePrompts:["Keep this route updated with live traffic","Watch the traffic on the drive from Paris to Amsterdam","Recalculate this route every 30 seconds","Monitor traffic between these places"],relatedTools:["setRoute","stopRouteMonitor","updateRoutesDisplay","analyseData"],dependsOn:["setRoute"]},stopRouteMonitor:{description:"Stop the background monitor for a route entry. The entry itself stays in state with its last calculated data — use removeEntry if you also want to drop it. No-op when no monitor is running for the entry.",classificationPrompt:'Stop recalculating a route entry; the entry stays in state with its last data. Pick for "stop refreshing the route" / "pause traffic updates on this drive".',inputSchema:cl,outputSchema:ll,execute:async(e,t)=>{const{routesEntryID:r}=e;if(!t.routing.entries.find(e=>e.id===r))return{error:`No routes entry with id "${r}".`};const n=t.routing.isMonitored(r);return t.routing.stopMonitoring(r),{routesEntryID:r,wasRunning:n}},tags:["route","traffic","state"],examples:["stopRouteMonitor({ routesEntryID: 'routes-0' })"],examplePrompts:["Stop refreshing the route","Pause traffic updates on this drive"],relatedTools:["startRouteMonitor","setRoute"],dependsOn:["startRouteMonitor"]},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; use analyseData over the loaded entry for breakdowns.",classificationPrompt:"Fetch historical traffic analytics (speed, congestion, travel time) for an area. Use analyseData over the loaded entry for breakdowns (per-day / per-hour / worst-day) without re-fetching.",inputSchema:Oo,outputSchema:_o,execute:async(e,t,r)=>{const{location:n,bbox:s,showOnMap:o,startDate:a,endDate:i,days:l,metrics:c,functionalRoadClasses:d,hours:u}=e,p=process.env.MOVE_PORTAL_KEY;if(!p)return{error:"MOVE_PORTAL_KEY environment variable is not set."};try{const e=await(async(e,t,r,n)=>{if(e){const t=await Qs(e,r,n);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=Me({geometries:[t.place],signal:n?.signal}),o=(await D(s))?.features?.[0]?.geometry;return o||{error:`No boundary polygon found for "${t.name}". Try a bbox instead.`}}return t?(e=>{const[t,r,n,s]=e;return{type:"Polygon",coordinates:[[[t,r],[n,r],[n,s],[t,s],[t,r]]]}})(t):{error:"Provide a location or bbox to define the analytics area."}})(n,s,t,r);if("error"in e)return e;const h=((e,t,r)=>e&&e.length>0?{days:e}:t?r?{startDate:t,endDate:r}:{startDate:t}:{})(l,a,i),y=Me({apiKey:p,...h,metrics:c,functionalRoadClasses:d??"all",hours:u??"all",geometry:e,signal:r?.signal}),m=await C(y),f=m.features[0]?.properties?.name,g=f?`${f} (${h.startDate??"…"} → ${h.endDate??"…"})`:`analytics (${h.startDate??"…"} → ${h.endDate??"…"})`,b=await t.trafficAreaAnalytics.addEntry(m,g,{...h,metrics:c,functionalRoadClasses:d??"all",hours:u??"all"});return o&&await(async(e,t)=>{await e.trafficAreaAnalytics.showEntry(t);const r=e.trafficAreaAnalytics.findById(t);if(!r)return;const n=await e.trafficAreaAnalytics.getEntryModule(t),s=r.data.properties?.metrics?.[0]??"congestionLevel";n.setMetric(s);const o=q(r.data.features);o&&e.baseMap.mapLibreMap.fitBounds(o,{padding:50,pitch:45})})(t,b),((e,t,r,n)=>{const s=t.features[0]?.properties;if(!s)return{entryId:e,dateRange:{start:"",end:""},baseData:{},metrics:[],tileCount:0,availableGranularities:[]};const o=r??Ro(t.properties?.startDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-3),Co(e)})(),a=n??Ro(t.properties?.endDate)??(()=>{const e=/* @__PURE__ */new Date;return e.setDate(e.getDate()-2),Co(e)})(),i=[];return s.timedData?.yearly?.length&&i.push("yearly"),s.timedData?.monthly?.length&&i.push("monthly"),s.timedData?.weekly?.length&&i.push("weekly"),s.timedData?.daily?.length&&i.push("daily"),s.timedData?.hourly?.length&&i.push("hourly"),s.timedData?.average?.length&&i.push("average"),{entryId:e,...s.name&&{name:s.name},...s.timezone&&{timezone:s.timezone},dateRange:{start:o,end:a},baseData:s.baseData,metrics:t.properties?.metrics??[],tileCount:s.tiledData?.tiles?.length??0,availableGranularities:i}})(b,m,h.startDate,h.endDate)}catch(h){return{error:`Failed to get traffic area analytics: ${h instanceof Error?h.message:String(h)}`}}},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:["analyseData","updateTrafficAreaAnalyticsDisplay","calculateBBox","getViewport"],dependsOn:["locatePlace"]},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 already in state on the map — add some (as pins, base-map-style markers via `markerType`, and/or boundary polygons), remove others, swap atomically, or render boundaries. Owns rendering a stored entry with base-map-style markers; NOT the base-map tile POI icons (those are toggleTilesPOIs).",inputSchema:Gl,outputSchema:$l,execute:async(e,t)=>{const{clear:r,markerType:n,geometry:s,fitBounds:o}=e;try{const a=((e,t)=>{const{add:r,remove:n,addMatching:s,removeMatching:o,markerType:a,geometry:i}=t;let l=[...r??[],...Wl(e,s??[])];const c=[...n??[],...Wl(e,o??[])];if(void 0===r&&void 0===s&&(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 recallState 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!==n||!s;let u;i.length>0&&d&&await(async(e,t,r,n)=>{const s=void 0!==r?t:t.filter(e=>!n.has(e));s.length>0&&await e.places.addShownEntries(s,r??"pin")})(t,i,n,c),s&&i.length>0&&(u=await(async(e,t,r)=>{const n=[];let s=0;for(const o of t){const t=await Fl(e,o,r);s+=t.rendered,n.push(...t.skipped)}return{rendered:s,skipped:n}})(t,i,s));const p=t.places.shownEntryIds,h=i.filter(e=>!c.has(e)&&p.has(e)),y=l.filter(e=>c.has(e)&&!p.has(e)),m=o??i.length>0,f=t.places.getShownFeatures();if(m&&f.length>0){const e=q(f);e&&t.baseMap.mapLibreMap.fitBounds(e,{padding:50})}const g=t.places.entries.filter(e=>p.has(e.id)).map(e=>({id:e.id,label:e.label,featureCount:e.data.length,markerType:t.places.getShownMarkerType(e.id)??"pin"}));return{success:!0,totalShown:f.length,shown:g,actuallyAdded:h,actuallyRemoved:y,...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","Render entry places-3 with the base-map marker style","Show the boundaries of the neighbourhoods I loaded","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","recallState","clearMap","flyTo"],dependsOn:["discoverPlaces","locatePlace","processData"]},updateRoutesDisplay:{description:'Show, hide, swap, and recolor which routes entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show this"). `selectedIndex` re-highlights an alternative variant on the newly-added entries; with no `add`/`addMatching` it implicitly targets the most recently stored entry. `mainColor` recolors the route line + waypoint icons (sticky across entry swaps — applied to current shows AND remembered); it does NOT auto-show a hidden entry, so set `add`/`addMatching` too if nothing is on the map yet. Does not delete from history. Does not call any service.',classificationPrompt:"Show, hide, swap, and recolor routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, or set `mainColor` (CSS color / hex; sticky across entry swaps).",inputSchema:Hl,outputSchema:zl,execute:async(e,t)=>{const{clear:r,selectedIndex:n,fitBounds:s,mainColor:o}=e;try{const a=((e,t)=>{const{add:r,remove:n,addMatching:s,removeMatching:o,selectedIndex:a}=t;let i=[...r??[],...Yl(e,s??[])];const l=[...n??[],...Yl(e,o??[])];if(void 0===r&&void 0===s&&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 recallState to list available entries.`}:{add:i,remove:l}})(t,e);if("error"in a)return a;const{add:i,remove:l}=a;void 0!==o&&t.routing.setMainColor(o);const{beforeShown:c,afterShown:d}=await(async(e,t)=>{const r=new Set(e.routing.shownEntryIds);if(t.clear)for(const n of e.routing.shownEntryIds)await e.routing.hideEntry(n);for(const n of t.remove)await e.routing.hideEntry(n);for(const n of t.add)await e.routing.showEntry(n);return void 0!==t.selectedIndex&&await(async(e,t,r)=>{if(0!==r)for(const n of t){const t=e.routing.entries.find(e=>e.id===n);if(!t)continue;const s=await e.routing.getEntryRoutingModule(n);await s.showRoutes(t.data,{selectedIndex:r}),t.waypoints.length&&await s.showWaypoints(t.waypoints)}})(e,t.add,t.selectedIndex),{beforeShown:r,afterShown:e.routing.shownEntryIds}})(t,{add:i,remove:l,clear:!!r,selectedIndex:n}),u=i.filter(e=>!c.has(e)&&d.has(e)),p=l.filter(e=>c.has(e)&&!d.has(e));(s??i.length>0)&&((e,t)=>{const r=[];for(const s of e.routing.entries)t.has(s.id)&&r.push(...s.data.features);if(0===r.length)return;const n=q({type:"FeatureCollection",features:r});n&&e.baseMap.mapLibreMap.fitBounds(n,{padding:50})})(t,d);const h=t.routing.entries.filter(e=>d.has(e.id)).map(e=>({id:e.id,label:e.label,routeCount:e.data.features.length,selectedIndex:u.includes(e.id)?n??0:0})),y=t.routing.mainColor;return{success:!0,totalShown:d.size,shown:h,actuallyAdded:u,actuallyRemoved:p,...void 0!==y&&{mainColor:y}}}catch(a){return{error:`updateRoutesDisplay failed: ${a instanceof Error?a.message:String(a)}`}}},tags:["route","waypoint","map style"],examples:['updateRoutesDisplay({ add: ["routes-1"] })','updateRoutesDisplay({ add: ["routes-0", "routes-2"] })','updateRoutesDisplay({ remove: ["routes-1"] })','updateRoutesDisplay({ removeMatching: ["Brussels"] }) // hide every entry whose label mentions "Brussels"','updateRoutesDisplay({ addMatching: ["Edinburgh"] })',"updateRoutesDisplay({ clear: true })",'updateRoutesDisplay({ clear: true, add: ["routes-2"] }) // atomic swap: hide everything, show this',"updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route",'updateRoutesDisplay({ add: ["routes-0"], selectedIndex: 2, fitBounds: false })','updateRoutesDisplay({ mainColor: "coral" }) // recolor every currently-shown route (and future shows)','updateRoutesDisplay({ add: ["routes-1"], mainColor: "#FF0000" }) // show in red'],examplePrompts:["Draw the route on the map","Show the directions","Display the calculated route","Show the Edinburgh route from earlier","Also show the Brussels route","Hide the Lyon route","Switch to the second alternative","Highlight alternative 2","Replace what is shown with this route","Make the route line red","Change the route color to coral","Style the route in steelblue"],relatedTools:["setRoute","processData","recallState","clearMap"],dependsOn:["setRoute"]},updateTrafficAreaAnalyticsDisplay:{description:"Show, hide, swap, and restyle HISTORICAL traffic-area-analytics entries on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap. Render knobs (`mode`, `metric`, `colorTheme`, `heightScale`, `scaleMode`, `filter`) apply to every entry being added AND every entry that stays shown after this call. When only render knobs are set with no add/remove/clear, implicitly targets the most recently added entry. Not real-time — use `toggleTilesTrafficFlow` for the live vector-tile flow overlay.",classificationPrompt:"Show / hide / swap / restyle historical traffic-area-analytics entries on the map. `add` / `remove` / `addMatching` / `removeMatching` / `clear` for visibility; `mode` / `metric` / `colorTheme` / `heightScale` / `scaleMode` / `filter` for the rendering. Render knobs alone implicitly target the most recent entry.",inputSchema:Kl,outputSchema:Xl,execute:async(e,t)=>{try{const r=((e,t)=>{const{add:r,remove:n,addMatching:s,removeMatching:o,clear:a}=t;let i=[...r??[],...Zl(e,s??[])];const l=[...n??[],...Zl(e,o??[])],c=void 0===r&&void 0===s&&void 0===n&&void 0===o&&!0!==a,d=void 0!==t.mode||void 0!==t.metric||void 0!==t.colorTheme||void 0!==t.heightScale||void 0!==t.scaleMode||void 0!==t.filter;if(c&&d){const t=e.trafficAreaAnalytics.entries.at(-1)?.id;if(!t)return{error:"No traffic-area-analytics entries available. Call getTrafficAreaAnalytics first."};i=[t]}const u=new Set(e.trafficAreaAnalytics.entries.map(e=>e.id)),p=i.filter(e=>!u.has(e));return p.length>0?{error:`Unknown traffic-area-analytics entry ids: ${p.join(", ")}. Use recallState to list available ids.`}:{add:i,remove:l}})(t,e);if("error"in r)return r;const{add:n,remove:s}=r,o=new Set(t.trafficAreaAnalytics.shownEntryIds);if(e.clear)for(const e of t.trafficAreaAnalytics.shownEntryIds)await t.trafficAreaAnalytics.hideEntry(e);for(const e of s)await t.trafficAreaAnalytics.hideEntry(e);for(const e of n)await t.trafficAreaAnalytics.showEntry(e);const a=t.trafficAreaAnalytics.shownEntryIds,i=n.filter(e=>!o.has(e)&&a.has(e)),l=s.filter(e=>o.has(e)&&!a.has(e)),c=/* @__PURE__ */new Set([...i,...a]);for(const u of c)await Ql(t,u,e);if((e.fitBounds??i.length>0)&&a.size>0){const e=t.trafficAreaAnalytics.entries.filter(e=>a.has(e.id)).flatMap(e=>e.data.features),r=q({type:"FeatureCollection",features:e});r&&t.baseMap.mapLibreMap.fitBounds(r,{padding:50,pitch:45})}const d=t.trafficAreaAnalytics.entries.filter(e=>a.has(e.id)).map(ec);return{success:!0,totalShown:a.size,shown:d,actuallyAdded:i,actuallyRemoved:l}}catch(r){return{error:`Failed to update traffic-area-analytics display: ${r instanceof Error?r.message:String(r)}`}}},tags:["traffic","map style"],examples:['updateTrafficAreaAnalyticsDisplay({ add: ["tta-0"] })',"updateTrafficAreaAnalyticsDisplay({ mode: 'hexgrid-2d', metric: 'speed', colorTheme: 'heat' }) // re-style the latest (implicit)","updateTrafficAreaAnalyticsDisplay({ filter: { min: 50 } })","updateTrafficAreaAnalyticsDisplay({ scaleMode: 'currentRange', heightScale: 200 })","updateTrafficAreaAnalyticsDisplay({ clear: true })",'updateTrafficAreaAnalyticsDisplay({ clear: true, add: ["tta-1"] }) // atomic swap','updateTrafficAreaAnalyticsDisplay({ remove: ["tta-0"] })','updateTrafficAreaAnalyticsDisplay({ addMatching: ["Amsterdam"] })'],examplePrompts:["Show the analytics on the map","Visualize the congestion","Only show areas with congestion above 50%","Normalize height to the current data range","Switch to heatmap view","Also show the Amsterdam analytics","Hide the Amsterdam analytics","Replace what is on the map with this analytics"],relatedTools:["getTrafficAreaAnalytics","toggleTilesTrafficFlow","clearMap","analyseData","processData"],dependsOn:["getTrafficAreaAnalytics"]},updateWaypointsDisplay:{description:"Show or hide the staged origin/stop/destination markers held in `state.routing.planningSlots` (the slots being built up for the next `setRoute` — NOT the waypoints of a calculated route entry). No route line is drawn; for that use `updateRoutesDisplay`. Requires at least one routes entry to host the module.",classificationPrompt:"Show or hide the STAGED origin/stop/destination markers (`state.routing.planningSlots` — what is queued for the NEXT `setRoute`). No route line is drawn; for that use `updateRoutesDisplay`. Single-collection toggle: `visible: true` (default) renders the staged set, `visible: false` clears it.",inputSchema:rc,outputSchema:tc,execute:async(e,t)=>{const r=e.visible??!0;try{const e=[...t.routing.shownEntryIds][0],n=t.routing.entries.at(-1),s=e??n?.id;if(!s)return{error:"No route history yet — calculate a route via setRoute first."};const o=await t.routing.getEntryRoutingModule(s);if(!r)return await o.clearWaypoints(),{success:!0,visible:!1,count:0};const a=t.routing.planningSlots;return await o.showWaypoints(a),{success:!0,visible:!0,count:a.length}}catch(n){return{error:`Failed to update waypoints display: ${n instanceof Error?n.message:String(n)}`}}},tags:["waypoint","route"],examples:["updateWaypointsDisplay()","updateWaypointsDisplay({ visible: true })","updateWaypointsDisplay({ visible: false })"],examplePrompts:["Show the staged waypoints on the map","Mark my pending route stops","Hide the staged waypoints"],relatedTools:["setRoute","updateRoutesDisplay","getCurrentWaypoints","clearMap"],dependsOn:["setRoute","locatePlace"]},clearMap:{description:"Hide everything a chosen set of state slices is rendering on the map (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents). Map-only — slice `entries` history is untouched. Use to clean up before showing new results.",classificationPrompt:"Hide what the chosen state slices are rendering on the map. `layers` are SLICE KEYS: `places`, `routing`, `ranges`, `customGeometries`, `byod`, `trafficAreaAnalytics`, `trafficIncidents`. Omit to clear every slice. Map-only — slice history is kept.",inputSchema:pl,outputSchema:ul,execute:async(e,t)=>{const{layers:r}=e,n=r&&r.length>0?r:St;try{for(const e of n)await t[e].clearShown();return{success:!0,cleared:[...n]}}catch(s){return{error:`Failed to clear map: ${s instanceof Error?s.message:String(s)}`}}},tags:["place","route","map style"],examples:["clearMap()",'clearMap({ layers: ["places"] })','clearMap({ layers: ["places", "routing"] })','clearMap({ layers: ["trafficAreaAnalytics", "trafficIncidents"] })','clearMap({ layers: ["byod"] })'],examplePrompts:["Clear the map","Remove all markers","Hide everything on the map"],relatedTools:["updatePlacesDisplay","updateRoutesDisplay","updateTrafficAreaAnalyticsDisplay"]},flyTo:{description:"Move the map camera to a bounding box or a specific position. Use with boundingBox to frame a region or set of results. Use with position to center on a point.",classificationPrompt:"Move the camera to a bounding box or specific position; use after locating places or computing bounds.",inputSchema:Ls,outputSchema:Bs,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(n){return{error:`flyTo failed: ${n instanceof Error?n.message:String(n)}`}}},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:["Fly to [4.9, 52.37]","Go to Berlin","Center the map on Rome"],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:Gs,outputSchema:$s,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"]},addByodSource:{description:'Register a customer-owned GeoJSON FeatureCollection as a new BYOD source from a URL (fetched once) or inline GeoJSON. Returns the new entry id (usable with `byodEntryIDs` on `analyseData` / `processData` and with `updateByodDisplay`) plus a `profile` of the data shape (geometry types + per-property types/coverage/examples) so you can reason about it without re-fetching. For very large datasets, prefer programmatic seeding via `state.byod.addEntry(...)`. This tool ONLY ingests — the entry starts with NO layers and renders nothing (no automatic defaults). ALWAYS follow it with `setByodLayers`, which picks layers + data-driven paint from the returned `profile` AND draws the entry; until then nothing is shown. Choosing the layers is always your job, in every case. The entry does not exist until this call RETURNS — even when you chose `entryId` yourself. So every tool that reads the entry (`setByodLayers`, `analyseData` / `processData`, `updateByodDisplay`, …) belongs in a LATER step: issuing one alongside this call fails with "No BYOD entry with id …".',classificationPrompt:"Add a customer-owned GeoJSON FeatureCollection (URL fetch or inline) as a new BYOD source. Use when the user wants to import / load / overlay a customer-supplied dataset. Returns the new entry id (use with `byodEntryIDs` on analyseData / processData) plus a data `profile`. The entry starts with NO layers and renders nothing, so co-pick setByodLayers on every ingest turn to decide the layers from that profile.",inputSchema:Qo,outputSchema:Zo,execute:async(e,t)=>{let r,n;if(e.url){const s=(e=>{let t;try{t=new URL(e)}catch{return{error:`Invalid URL: "${e}".`}}return ta.has(t.protocol)?{url:t}:{error:`URL protocol "${t.protocol}" is not allowed. Only http: and https: are supported.`}})(e.url);if("error"in s)return s;const o=await(t.byod.sourceUrlValidator?.(s.url));if(o&&!o.valid)return{error:o.reason};const a=await ra(s.url);if("error"in a)return a;r=a.value,n={kind:"url",url:e.url}}else r=e.data,n={kind:"inline"};if(!(e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)})(r))return{error:'Payload is not a GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`).'};const s=await t.byod.addEntry(r,e.label,{source:n,...e.entryId&&{explicitId:e.entryId}}),o=t.byod.findById(s);return o?{byodEntryId:s,label:e.label,source:n,profile:it(o.profile)}:{error:`addByodSource stored entry "${s}" but could not read it back.`}},tags:["byod","state","import"],examples:['addByodSource({ label: "Sales territories", url: "https://example.com/territories.geojson" }) // then setByodLayers to render','addByodSource({ label: "Inline pins", data: { type: "FeatureCollection", features: [/* … */] } })','addByodSource({ label: "Q2 pins", data: { type: "FeatureCollection", features: [/* … */] } })'],examplePrompts:["Load my territories layer from https://example.com/territories.geojson","Import the GeoJSON at https://example.com/data.geojson as a BYOD layer","Add a customer-pins layer from https://example.com/pins.geojson"],relatedTools:["recallState","setByodLayers","updateByodDisplay","analyseData","processData"]},setByodLayers:{description:"Replace the MapLibre layers a BYOD entry renders under, styling customer data from its `profile` (geometry types + per-property types/coverage/examples). Operates on an entry already in state (`addByodSource` / `recallState`); the supplied `layers` replace the current set wholesale, applying live in place if the entry is shown. Pass `show` to render a hidden entry or re-fit the camera. Invalid specs leave the entry unchanged and return an error to retry, so the entry's styling is never lost. This is the SECOND STEP OF EVERY BYOD INGEST and the ONLY way a BYOD entry becomes visible — `addByodSource` creates the entry with NO layers — so always call it to pick layers that fit the detected schema: graduate `circle-radius`/colour by a numeric field (`interpolate`), colour a `fill`/`line` by a categorical field (`match`), or switch dense points to `symbol`; even with no dominant property, choose legible layers for the geometry. STYLE FOR LEGIBILITY ON THE TOMTOM BASEMAP (no need to inspect the style): translucent `fill` (`fill-opacity` ~0.3–0.5) with a contrasting `fill-outline-color`; `symbol` text with a white halo (`text-halo-color: '#ffffff'` + `text-halo-width` ~1.5 in `paint`, while `text-field`/`text-size` ~12 go in `layout`); `circle` with a thin white stroke (`circle-stroke-color`/`-width`); a small, high-contrast palette that avoids fully opaque black or white fills.",classificationPrompt:"Restyle a BYOD entry by replacing the MapLibre layers it renders under — pick layer types and data-driven paint from the data `profile` (graduate point size by a numeric field, colour a fill by category, switch points to symbols). OWNS every restyle of a loaded / imported / customer (BYOD) layer — recolour it, thicken its outlines, resize its points — even when phrased as generic styling; do NOT route those to setPaintProperties/setLayoutProperties (which are for base-map style layers). ALWAYS co-pick alongside addByodSource on EVERY BYOD ingest turn (whether or not styling was mentioned) — addByodSource creates the entry with no layers, so this is the only way it becomes visible. NOT for visibility (show/hide/remove → updateByodDisplay) nor for importing new data (→ addByodSource).",inputSchema:Xi,outputSchema:Zi,execute:async(e,t)=>{const r=t.byod.findById(e.byodEntryId);if(!r)return{error:`No BYOD entry with id "${e.byodEntryId}". Call recallState to list available IDs.`};const n=r.layers,s=e.layers;try{t.byod.setEntryLayers(e.byodEntryId,s)}catch(a){try{t.byod.setEntryLayers(e.byodEntryId,n)}catch{r.layers=n}return{error:`Failed to apply layers to "${e.byodEntryId}": ${a instanceof Error?a.message:String(a)}. The entry keeps its previous layers — fix the layer specs and retry.`}}let o;if(e.show)try{o=await Ss(t,e.byodEntryId,e.show)}catch(a){return{error:`Layers applied to "${e.byodEntryId}" but rendering failed: ${a instanceof Error?a.message:String(a)}`}}return{byodEntryId:e.byodEntryId,label:r.label,layerCount:s.length,layerTypes:s.map(e=>e.type),...o&&{shown:o}}},tags:["byod","map style","display"],examples:['setByodLayers({ byodEntryId: "byod-0", layers: [{ type: "fill", paint: { "fill-color": ["match", ["get", "region"], "north", "#1f77b4", "south", "#ff7f0e", "#cccccc"], "fill-opacity": 0.5 } }] }) // colour polygons by category','setByodLayers({ byodEntryId: "byod-1", layers: [{ type: "circle", paint: { "circle-radius": ["interpolate", ["linear"], ["get", "revenue"], 0, 3, 100000, 20], "circle-color": "#d62728" } }] }) // size points by a numeric field','setByodLayers({ byodEntryId: "byod-2", layers: [{ type: "line", paint: { "line-color": "#2ca02c", "line-width": 3 } }], show: { zoomMode: "none" } }) // restyle a hidden entry and render it'],examplePrompts:["Colour my loaded territories layer by region","Size my BYOD layer points by revenue","Style my loaded BYOD layer with thicker green outlines","Shade my BYOD polygons by their value"],relatedTools:["recallState","addByodSource","updateByodDisplay"],dependsOn:["addByodSource"]},updateByodDisplay:{description:'Toggle visibility (or drop) for one or more BYOD entries. Use after `addByodSource` or after seeing entries via `recallState`. Three actions: `show` renders listed entries, `hide` removes them from the map but keeps them in state, `remove` hides AND drops them from history. Pair `action: "hide"` with `clearAll: true` to hide every shown BYOD entry in one call. Pair `action: "show"` with `hideOthers: true` to swap to a single set of entries (replaces what\'s already on the map).',classificationPrompt:'Toggle visibility (or drop) of BYOD entries already in session state. Use after `addByodSource` or after `recallState({ kind: "byod" })` exposes the ids. Three actions: show / hide / remove. Pair with `clearAll` or `hideOthers` for bulk swaps.',inputSchema:Nl,outputSchema:Bl,execute:async(e,t)=>{const r=t.byod,{action:n,clearAll:s,hideOthers:o}=e,a=e.entryIds??[],i=((e,t)=>{for(const r of t)if(!e.findById(r))return{error:`No BYOD entry with id "${r}". Call recallState to list available IDs.`}})(r,a);if(i)return i;let l;return l="show"===n?await(async(e,t,r)=>{const n=[];if(r){const r=new Set(t);for(const t of e.shownEntryIds)r.has(t)||(await e.hideEntry(t),n.push(t))}for(const s of t)await e.showEntry(s),n.push(s);return n})(r,a,!!o):"hide"===n?await(async(e,t,r)=>{const n=[],s=r?e.shownEntryIds:t;for(const o of s)await e.hideEntry(o),n.push(o);return n})(r,a,!!s):await(async(e,t)=>{const r=[];for(const n of t)await e.removeEntry(n),r.push(n);return r})(r,a),{action:n,affectedIds:l,shown:[...r.shownEntryIds]}},tags:["byod","display","visibility"],examples:['updateByodDisplay({ entryIds: ["byod-0"], action: "show" })','updateByodDisplay({ entryIds: ["byod-0", "byod-1"], action: "show", hideOthers: true })','updateByodDisplay({ entryIds: ["byod-0"], action: "hide" })','updateByodDisplay({ action: "hide", clearAll: true })','updateByodDisplay({ entryIds: ["byod-0"], action: "remove" })'],examplePrompts:["Hide the customer pins layer","Show only the territories BYOD layer","Hide all BYOD layers","Drop the imported file from the session"],relatedTools:["recallState","addByodSource"],dependsOn:["addByodSource"]},getCurrentWaypoints:{description:"Read STAGED waypoint slots queued for the NEXT setRoute call (origin / stops / destination not yet committed to a route). Distinct from `recallState({ kind: 'routes', id })`, which returns the waypoints of an existing calculated route entry. `slotIndex` for one slot; otherwise paged (default 5), empty slots omitted unless `includeEmptySlots: true`. No service call.",classificationPrompt:'STAGED waypoint slots only — the origin/stops/destination being built up for the NEXT `setRoute` call (read off `state.routing.planningSlots`, NOT off any calculated route entry). For waypoints of an EXISTING calculated route, use `recallState({ kind: "routes", id })` — its detail mode returns the entry\'s own waypoints.',inputSchema:Ca,outputSchema:Ra,execute:async(e,t)=>{const{slotIndex:r,includeEmptySlots:n=!1,offset:s=0,limit:o=5}=e,a=t.routing.planningSlots.map((e,t)=>{const r=ls(e);return r?{...r,slotIndex:t}:{slotIndex:t,isFilled:!1}}),i=n?a:a.filter(e=>e.isFilled),l=void 0!==r,c=l?i.filter(e=>e.slotIndex===r):i.slice(s,s+o),d=s+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:s,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's currently staged for the next route?","What's my pending destination before I hit go?","List the stops I have queued up"],relatedTools:["locatePlace","setRoute","updateWaypointsDisplay","recallState"]},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:vl,outputSchema:wl,execute:async(e,t)=>{try{return{styles:b}}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:"Switch the map to a standard base-map style preset (IDs are the `style` argument's values). ONLY when the user explicitly asks to change the map style/theme — never on your own initiative. For finer styling use getMapStyleLayers, setLayoutProperties, setPaintProperties.",classificationPrompt:"Switch the map to a standard base-map style/theme preset (light, dark, mono, satellite, etc.) — ONLY when the user explicitly asks to change it, never as a side effect of another task.",inputSchema:kl,outputSchema:Sl,execute:async(e,t)=>{const{style:r}=e;try{return await t.baseMap.ttMap.setStyle(r),{success:!0,style:r}}catch(n){return{error:`Failed to set map style: ${n instanceof Error?n.message:String(n)}`}}},tags:["map style"],examples:['setMapStandardStyle("standardDark")','setMapStandardStyle("satellite")'],examplePrompts:["Switch to dark mode","Use the satellite view","Change to the light theme","Use the driving map style"],relatedTools:["getStandardMapStyles","setLanguage","getMapStyleLayers"]},setLanguage:{description:"Set the language for map labels and all subsequent service API responses (search, geocoding, routing instructions).",classificationPrompt:"Change the language for map labels and all subsequent service API responses.",inputSchema:El,outputSchema:Il,execute:async(e,t)=>{const{language:r}=e;try{return N.instance.put({language:r}),t.baseMap.ttMap.setLanguage(r),{success:!0,language:r}}catch(n){return{error:`Failed to set language: ${n instanceof Error?n.message:String(n)}`}}},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"]},toggleTilesPOIs:{description:"Show/hide the built-in vector-tile POI icons baked into the base map style (restaurants, hotels, gas stations, …). NOT the same as places shown via `discoverPlaces` / `updatePlacesDisplay` — those are session-state pins on the PlacesModule, untouched by this tool. `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts which categories show; `reset: true` restores defaults.",classificationPrompt:"Show / hide vector-tile POI icons baked into the base-map style (restaurants/hotels/gas stations etc. shipped with the map). NOT places shown via `discoverPlaces` / `updatePlacesDisplay` (those are PlacesModule pins, untouched).",inputSchema:Pl,outputSchema:Tl,execute:async(e,t)=>{try{const r=await t.mapPOIs.getPOIsModule();if(e.reset)return r.resetConfig(),{success:!0,reset:!0};const{allMapPOIsVisible:n,filterCategories:s}=e;return void 0!==n&&r.setVisible(n),s&&r.filterCategories({show:s.show,values:s.values}),{success:!0,allMapPOIsVisible:n,filterCategories:s}}catch(r){return{error:`Failed to toggle tile POIs: ${r instanceof Error?r.message:String(r)}`}}},tags:["place","map style"],examples:["toggleTilesPOIs({ allMapPOIsVisible: false })",'toggleTilesPOIs({ allMapPOIsVisible: true, filterCategories: { show: "only", values: ["RESTAURANT", "CAFE"] } })','toggleTilesPOIs({ filterCategories: { show: "all_except", values: ["PARKING_GARAGE"] } })',"toggleTilesPOIs({ reset: true })"],examplePrompts:["Hide all map POI icons","Show only restaurant icons on the map style","Turn off parking icons in the base map"],relatedTools:["discoverPlaces","setMapStandardStyle"]},describeMapStyling:{description:"List the semantic base-map styling knobs of the loaded style (label/icon/road sizes, feature toggles such as exit numbers or 3D buildings, POI zoom and colours, traffic congestion colours, globe projection and sky) with kind, range, default and current value, plus the available presets. Call before setMapStyling to know valid ids and ranges.",classificationPrompt:"List what can be restyled on the base map (sizes, feature toggles, POI and traffic colours) with ranges, defaults and current values. Read-only.",inputSchema:bl,outputSchema:gl,execute:async(e,t)=>{try{const{knobs:r,presets:n}=(await t.baseMap.getStylingModule()).describe();return{knobs:e.kind?r.filter(t=>t.kind===e.kind):r,presets:n}}catch(r){return{error:`Failed to describe map styling: ${r instanceof Error?r.message:String(r)}`}}},tags:["map style"],examples:["describeMapStyling()",'describeMapStyling({ kind: "toggle" })'],examplePrompts:["What can I change about the map styling?","Which map styling knobs are currently overridden?"],relatedTools:["setMapStyling","setMapStandardStyle"]},setMapStyling:{description:"Restyle the base map semantically: label/icon/road size factors, feature toggles (exit numbers, shields, road arrows, 3D buildings, POI micro markers…), the base-map layer groups (basemap.water, basemap.roadLabels…; buildings are buildings.footprints/buildings.3d), hillshade shading, POI zoom and label colours, traffic colours, the ten map colours (colors.land/water/vegetation/park/artificial/roadMajor/road/roadOutline/label/labelOutline — one name recolours every derived shade, so for a dark palette switch to a dark standard style first and recolour from there), globe projection and sky, or a whole preset (data-viz, night-driving, minimal, globe). Durable across style switches. Not for layers the agent drew (places, routes, BYOD) — use their update*Display tools.",classificationPrompt:"Adjust the base-map look semantically — bigger/smaller labels or icons, thicker roads, hide/show exit numbers, route shields, road arrows, 3D buildings or POI micro markers, POI label colour or zoom, traffic congestion/incident colours — or reset those to the style defaults. NOT for switching the whole style (setMapStandardStyle), NOT for layers the agent drew.",inputSchema:Dl,outputSchema:xl,execute:async(e,t)=>{try{const r=await t.baseMap.getStylingModule(),n=[],s=(e,t)=>{try{t()}catch(r){n.push({id:e,reason:r instanceof Error?r.message:String(r)})}};if(!0===e.reset)r.reset();else if(Array.isArray(e.reset))for(const t of e.reset)s(t,()=>r.reset(t));e.preset&&r.applyPreset(e.preset);for(const[t,o]of Object.entries(e.set??{}))s(t,()=>r.set(t,o));return{success:!0,settings:r.getConfig()??{},rejected:n}}catch(r){return{error:`Failed to set map styling: ${r instanceof Error?r.message:String(r)}`}}},tags:["map style"],examples:['setMapStyling({ set: { "labels.sizeFactor": 1.3 } })','setMapStyling({ set: { "roads.exitNumbers": false, "roads.shields": false } })','setMapStyling({ set: { "buildings.3d": true } })','setMapStyling({ set: { "traffic.flow.slowColor": "#f59e0b", "traffic.flow.stationaryColor": "#7f1d1d" } })','setMapStyling({ set: { "view.projection": "globe", "view.sky": true } })','setMapStyling({ set: { "colors.water": "#0fa8c8", "colors.roadMajor": "#f59e0b" } })','setMapStyling({ preset: "data-viz" })',"setMapStyling({ reset: true })"],examplePrompts:["Make the map labels bigger","Hide the exit numbers and route shields","Show 3D buildings","Use orange for slow traffic and dark red for stationary traffic","Show the map as a globe","Hide the road labels","Turn off the building layer","Make the water teal and the motorways orange","Use our brand colours on the map: dark land, light labels","Make the base map quieter so my data stands out","Reset the map styling"],relatedTools:["describeMapStyling","setMapStandardStyle","toggleTilesPOIs"]},toggleTilesTrafficFlow:{description:"Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical analytics) or `route.properties.sections.traffic[]` (per-route incidents). Just a base-map style layer on/off.",classificationPrompt:"Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical) or route-section traffic (read via `analyseData`).",inputSchema:_l,outputSchema:Ol,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficFlowModule()).setVisible(r),{success:!0,trafficFlowVisible:r}}catch(n){return{error:`Failed to toggle tile traffic flow: ${n instanceof Error?n.message:String(n)}`}}},tags:["traffic","map style"],examples:["toggleTilesTrafficFlow({ visible: true })","toggleTilesTrafficFlow({ visible: false })"],examplePrompts:["Show traffic flow","Turn off traffic colors","Enable the congestion overlay"],relatedTools:["toggleTilesTrafficIncidents","setRoute","getTrafficIncidents"]},toggleTilesTrafficIncidents:{description:"Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the same as `getTrafficIncidents` (GeoJSON service → structured entries) or `analyseData` (sandbox over fetched entries). Just a base-map style layer on/off.",classificationPrompt:"Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the GeoJSON `getTrafficIncidents` service and NOT `analyseData` (which aggregates fetched entries).",inputSchema:Cl,outputSchema:Rl,execute:async(e,t)=>{const{visible:r}=e;try{return(await t.trafficTiles.getTrafficIncidentsModule()).setVisible(r),{success:!0,trafficIncidentsVisible:r}}catch(n){return{error:`Failed to toggle tile traffic incidents: ${n instanceof Error?n.message:String(n)}`}}},tags:["traffic","map style"],examples:["toggleTilesTrafficIncidents({ visible: true })","toggleTilesTrafficIncidents({ visible: false })"],examplePrompts:["Show traffic incidents overlay","Hide accident icons","Enable incident markers on the map"],relatedTools:["toggleTilesTrafficFlow","getTrafficIncidents","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:qs,outputSchema:Fs,execute:async(e,t)=>{const{layerIdQuery:r,include:n}=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,...n.includes("paint")&&{paint:e.paint},...n.includes("layout")&&{layout:e.layout}}))}}catch(s){return{error:`Failed to get style: ${s instanceof Error?s.message:String(s)}`}}},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 base-map style layers. NOT for loaded BYOD / imported / customer layers — restyle those with setByodLayers.",inputSchema:zs,outputSchema:Us,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:n})=>{try{const s=r;return t.baseMap.mapLibreMap.setLayoutProperty(e,s,n),{layerId:e,propertyName:r,value:n,success:!0}}catch(s){return{layerId:e,propertyName:r,error:`Failed to set layout property: ${s instanceof Error?s.message:String(s)}`}}})}}catch(n){return{error:`Failed to set layout properties: ${n instanceof Error?n.message:String(n)}`}}},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:["Set the road-label layer's visibility property to none","Change the text size on building labels","Make the city labels bigger"],relatedTools:["getMapStyleLayers","setPaintProperties"],dependsOn:["getMapStyleLayers"]},setPaintProperties:{description:"Set MapLibre paint properties (colors, widths, opacity) on named layers — use getMapStyleLayers for valid IDs. For visibility/structure use setLayoutProperties; for route styling prefer `updateRoutesDisplay({ mainColor })`.",classificationPrompt:"Set MapLibre paint properties (colors, widths, opacity) on named base-map style layers. NOT for loaded BYOD / imported / customer layers — restyle those with setByodLayers.",inputSchema:Vs,outputSchema:Ys,execute:async(e,t)=>{const{changes:r}=e;try{return{results:r.map(({layerId:e,propertyName:r,value:n})=>{try{const s=r;return t.baseMap.mapLibreMap.setPaintProperty(e,s,n),{layerId:e,propertyName:r,value:n,success:!0}}catch(s){return{layerId:e,propertyName:r,error:`Failed to set paint property: ${s instanceof Error?s.message:String(s)}`}}})}}catch(n){return{error:`Failed to set paint properties: ${n instanceof Error?n.message:String(n)}`}}},tags:["map style"],examples:['setPaintProperties([{ layerId: "water", propertyName: "fill-color", value: "#0000ff" }])','setPaintProperties([{ layerId: "road", propertyName: "line-color", value: "#ff0000" }, { layerId: "road", propertyName: "line-width", value: 3 }])'],examplePrompts:["Make the water bright blue","Change the road color to red","Make buildings semi-transparent","Make the country borders pink"],relatedTools:["getMapStyleLayers","setLayoutProperties","updateRoutesDisplay"],dependsOn:["getMapStyleLayers"]},setPitchBearing:{description:"Tilt (pitch) and/or rotate (bearing) the map camera — the dedicated tool for a tilt, 3D/perspective, or rotate-to-face view. Pitch 0 = top-down, 85 = near-horizontal (3D); 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:Ks,outputSchema:Js,execute:async(e,t)=>{const{pitch:r,bearing:n}=e;if(void 0===r&&void 0===n)return{error:"Provide at least one of pitch or bearing"};try{return t.baseMap.mapLibreMap.easeTo({...void 0!==r&&{pitch:r},...void 0!==n&&{bearing:n}}),{success:!0}}catch(s){return{error:`Failed to set pitch/bearing: ${s instanceof Error?s.message:String(s)}`}}},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:Zs,outputSchema:Xs,execute:async(e,t)=>{const{delta:r}=e;try{const e=t.baseMap.mapLibreMap,n=e.getZoom()+r;return e.zoomTo(n),{zoom:n}}catch(n){return{error:`Zoom failed: ${n instanceof Error?n.message:String(n)}`}}},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` — the escape hatch for operations no dedicated tool covers (custom sources/layers, style mutations, animations, event sequences). If another tool already performs the operation, prefer that tool. Full JS (loops, helpers, top-level `await`). Returns added/removed/updated sources & layers.",classificationPrompt:"Execute arbitrary MapLibre JS against the live map — the escape hatch ONLY for operations no dedicated tool covers: fog / atmosphere / sky, terrain, globe vs mercator projection, raster / tile overlays, and time-based animations / multi-step loops that one-shot tools cannot express. If any other tool already performs the operation, use that tool instead.",inputSchema:Os,outputSchema:Cs,execute:async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,n=Rs(e),s=js(e),o=Object.getPrototypeOf(async()=>{}).constructor,a=await new o("map",r)(e),i=Ns(n,Rs(e));return{success:!0,result:a??void 0,sources:i,layers:Ns(s,js(e))}}catch(n){return{error:Ke("Code",n)}}},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:["Set map fog or atmosphere","Add a raster tile overlay","Fade in a layer gradually","Switch the map to a globe projection"],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:$t,outputSchema:Gt,execute:async(e,t)=>{const{geoJson:r}=e;try{const e=q(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(n){return{error:`Failed to calculate bbox: ${n instanceof Error?n.message:String(n)}`}}},tags:["utilities","location"],examples:["calculateBBox(place)","calculateBBox(routes)","calculateBBox([place1, place2])"],examplePrompts:["Calculate the extent of this route","What's the bounding box of these places?"],relatedTools:["flyTo","getViewport","updatePlacesDisplay","updateRoutesDisplay"]},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; if permission is denied, fall back to getViewport as the reference point.',classificationPrompt:'Get the user\'s physical GPS location from the browser; use for "near me" queries, not map center.',inputSchema:Zt,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"]},clarifyIntent:Xt(),help:{description:sr,classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:nr,outputSchema:rr,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 (recallState); `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:Eo,outputSchema:ko,execute:async(e,t,r)=>{const{origins:n,budgets:s,theme:o,showOnMap:a,showOriginPin:i=!0,hidePreviousEntries:l}=e;try{const e=[...s].sort((e,t)=>t.value-e.value),{ranges:c,skipped:d}=await(async(e,t,r,n)=>{const s=[],o=[];for(const a of e){const e=xo(a),i=await Qs(a,r,n);if(!i){o.push({origin:e,reason:"origin not found"});continue}const l=await Mo(t,i.position,n);l?s.push({origin:i.query?{query:i.query,position:i.position}:{position:i.position},budgets:t,polygon:l}):o.push({origin:i.name??e,reason:"no reachable area returned"})}return{ranges:s,skipped:o}})(n,e,t,r);if(0===c.length)return{status:"no_results"};const u=await t.ranges.addEntry({label:rs(e,c.map(Ao)),data:c}),p=a||i;return p&&await mn(t.ranges,[u],l),await(async(e,t,r,n,s,o)=>{if(s){const s=(e=>{const t=e.flatMap(e=>e.polygon?.features??[]);return 0===t.length?null:{type:"FeatureCollection",features:t}})(r);s&&(await(await e.ranges.getEntryGeometriesModule(t,n)).show(s),s.bbox&&e.baseMap.mapLibreMap.fitBounds(s.bbox,{padding:50}))}if(o){const n=await e.ranges.getEntryPlacesModule(t),s=r.map((e,r)=>({type:"Feature",id:`range-origin-${t}-${r}`,geometry:{type:"Point",coordinates:e.origin.position},properties:{type:"Point Address",address:{freeformAddress:Ao(e)}}}));await n.show(s)}})(t,u,c,o??"outline",a,i),p&&await t.ranges.markEntryShown(u),{rangesId:u,ranges:c.map(t=>({originName:Ao(t),budgets:e})),...d.length&&{skipped:d}}}catch(c){return{error:`Reachable area calculation failed: ${c instanceof Error?c.message:String(c)}`}}},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: [4.9, 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","Map the 20-minute drive zone around our Rotterdam depot","Show the area reachable within 25 km of the Eindhoven warehouse"],relatedTools:["recallState","locatePlace"]},recallState:e=>{e.featureFlags;return{...zi,description:'Read-only window onto session state — the ONE tool for "what is already loaded?" and "which id can I pass to a `*EntryIDs` follow-up?". No args → session snapshot (places/routes/ranges entries + per-slice `entryMode` + map style / POIs / traffic configs). `kind` → that kind\'s entry index (places | routes | ranges | geometries | byod | incidents | trafficAreaAnalytics). `kind` + `id` → that entry\'s full detail (places coordinates, route legs, range polygons, BYOD profile, geometry features, incident delay breakdown, analytics metrics). ALWAYS call before referencing past entries; never guess ids. No service call.',inputSchema:Wi,outputSchema:Gi(),execute:Ui()}},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:Vi,outputSchema:Yi,execute:async(e,t)=>{try{await t.places.clearShownEntries();for(const e of t.places.entries)await(e._modules?.geometries?.clear());for(const e of t.routing.entries)e._module&&(await e._module.clearRoutes(),await e._module.clearWaypoints());for(const e of t.ranges.entries)await(e._modules?.geometries?.clear()),await(e._modules?.places?.clear());for(const e of t.trafficAreaAnalytics.entries)await(e._module?.clear());return t.mapPOIs.poisModule?.resetConfig(),t.trafficTiles.trafficFlowModule?.resetConfig(),t.trafficTiles.trafficIncidentsModule?.resetConfig(),t.places.reset(),t.routing.reset(),t.ranges.reset(),t.customGeometries.reset(),t.trafficAreaAnalytics.reset(),t.trafficIncidents.reset(),t.byod.reset(),t.analyses.reset(),t.trackers.reset(),t.engine.reset(),await t.baseMap.ttMap.setStyle("standardLight",{resetState:!0}),{success:!0,cleared:[...Hi]}}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:nl,classificationPrompt:"Switch any entry-owning slice (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents) between `multiple` (overlay several entries) and `single` (latest only). `single` drops older entries.",inputSchema:rl,outputSchema:tl,execute:async(e,t)=>{const{slice:r,mode:n}=e;try{const e=t[r],s=e.entries.map(e=>e.id);await e.setEntryMode(n);const o=new Set(e.entries.map(e=>e.id));return{success:!0,slice:r,mode:n,droppedEntryIds:s.filter(e=>!o.has(e))}}catch(s){return{error:`Failed to set entry mode: ${s instanceof Error?s.message:String(s)}`}}},tags:["state","mode","display"],examples:['setEntryMode({ slice: "places", mode: "single" })','setEntryMode({ slice: "routing", mode: "multiple" })','setEntryMode({ slice: "trafficIncidents", mode: "single" })'],examplePrompts:["Only show one route at a time","Let me overlay multiple places searches","Switch ranges back to single mode","Only keep the latest traffic incidents snapshot"],relatedTools:["recallState"]}},oc=sc,ac=Object.keys(oc),ic={places:["updatePlacesDisplay","locatePlace","discoverPlaces"],routes:["updateRoutesDisplay","setRoute","addWaypointsToRoute","removeWaypointsFromRoute","replaceWaypointInRoute","getCurrentWaypoints","updateWaypointsDisplay","startRouteMonitor","stopRouteMonitor"],incidents:["getTrafficIncidents","focusIncidents","setTrafficIncidentsMonitor","clusterIncidents"],customGeometries:[],trafficAreaAnalytics:["getTrafficAreaAnalytics","updateTrafficAreaAnalyticsDisplay"],byod:["addByodSource","setByodLayers","updateByodDisplay"],ranges:["findReachableAreas"]},lc=()=>{const e={};for(const t of ac){const r=oc[t];e[t]=("function"==typeof r?r({}):r).examplePrompts??[]}return e},cc=async(e,t)=>{((e,t)=>{for(const[r,n]of Object.entries(t)){const t=e[r];t&&(t.description=n.defaultDescription,t.inputSchema=n.defaultInputSchema)}})(e.tools,e.scopableTools);let r=null;if(e.classifier)try{r=await e.classifier({messages:t,toolsMetadata:e.toolsMetadata})}catch{r=null}return e.onClassify?.(r),((e,t,r)=>{if(r)for(const[n,s]of Object.entries(r)){const r=t[n];if(!r||null==s)continue;const o=e[n];if(!o)continue;const a=r.applyScope(s);a&&(o.description=a.description,o.inputSchema=a.inputSchema)}})(e.tools,e.scopableTools,r?.toolScopes),r},dc=(e,r)=>{if(!r.model)throw new Error("MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.");e._setTomTomUserAgent(ke);const o=((e,t)=>{const r={places:new De(e),mapPOIs:new Ee(e),routing:new _e(e),baseMap:new de(e),trafficTiles:new vt(e),trafficAreaAnalytics:new ut(e),trafficIncidents:new wt(e),ranges:new Te(e),customGeometries:new me(e),byod:new lt(e),codeExecution:tt(),engine:new Oe,analyses:new ce,trackers:new ct};return t&&Object.assign(r,t),r.engine.ensureWired(r),r})(e,r.state);if(r.byod?.validateSourceUrl&&(o.byod.sourceUrlValidator=r.byod.validateSourceUrl),o.codeExecution=tt(r.codeExecution),r.dataEntries)for(const[t,n]of Object.entries(r.dataEntries)){const e=t;n?.entryMode&&o[It[e]].setEntryMode(n.entryMode)}const a=e=>!1!==r.dataEntries?.[e]?.enabled,i=Object.keys(It).filter(e=>!a(e)),l=Jr.filter(e=>a(e)),c=new Set(i.flatMap(e=>ic[e])),d=!1===r.includeDefaultTools?{}:oc,u=Object.fromEntries(Object.entries(d).filter(([e])=>!c.has(e))),p=ie(u,r.tools),{tools:h,toolsMetadata:y,scopableTools:m}=lr(p,o,{outputSchemas:r.outputSchemas,featureFlags:r.featureFlags,enabledDataKinds:l,onToolExecute:r.onToolExecute}),f=!1===r.classifier?null:r.classifier??Xn({model:r.model}),g=Tt({customPrompt:r.systemPrompt,prefix:r.systemPromptPrefix,suffix:r.systemPromptSuffix}),b=Object.entries(y).filter(([,e])=>e.alwaysActive).map(([e])=>e),w=Object.entries(y).filter(([,e])=>e.endsTurnOnCall).map(([e])=>n(e));let v=null;const I=(()=>{let e=Promise.resolve();return async t=>{const r=e;let n=()=>{};e=new Promise(e=>{n=e});try{return await r,await t()}finally{n()}}})(),E={tools:h,scopableTools:m,toolsMetadata:y,classifier:f,onClassify:r.onClassify},S=new t({model:r.model,tools:h,instructions:g,stopWhen:[s(r.maxSteps??10),...w],prepareStep:e=>I(async()=>{0===e.stepNumber&&(v=await cc(E,e.messages));const t=se(v?.activeToolNames,b),n=await(r.prepareStep?.(e)),s=r.stepProviderOptions?.({activeTools:t,stepNumber:e.stepNumber}),o=((e,t,r)=>r?{...r,activeTools:ne(e,r.activeTools),providerOptions:ae(t,r.providerOptions)}:{...e?{activeTools:e}:{},...t?{providerOptions:t}:{}})(t,s,n);return o?.activeTools?{...o,activeTools:se(o.activeTools,b)}:o}),providerOptions:r.providerOptions});return Object.assign(S,{state:o,destroy:()=>(e=>{for(const t of Object.values(e))oe(t)&&t.reset()})(o)})};export{Se as AGENT_TOOLKIT_ID,ke as AGENT_TOOLKIT_USER_AGENT,Jr as ALL_ENTRY_DATA_KINDS,Fe as ANALYSE_OUTPUT_FORMAT_DESCRIPTION,hn as AREA_GEOGRAPHY_TYPES,Dt as BASE_SYSTEM_PROMPT,Cn as BY_ENTRY_VIEWS_DOC,je as CHART_TYPES,Ht as CLARIFY_FORM_PROMPT,Yt as CLARIFY_PROSE_PROMPT,It as DATA_ENTRY_KIND_TO_SLICE,oc as DEFAULT_TOOLS,Xr as ENTRY_KIND_META,Et as ENTRY_MODE_SLICE_NAMES,cn as FIT_ON_MAP_DOC,ur as GEOMETRIES_PROPS_DOC,yr as GEOMETRIES_SCHEMA_DOC,pr as GEOMETRIES_SKIPPED_DESC,hr as GEOMETRIES_SOURCE_IDS_DESC,As as MIN_VIEWPORT_BIAS_ZOOM,jn as MULTI_INPUT_SANDBOX_PARAMS,fr as PLACES_SCHEMA_DOC,gr as ROUTES_SCHEMA_DOC,Mt as SYSTEM_PROMPT_SECTIONS,ic as TOOLS_BY_DATA_ENTRY_KIND,ac as TOOL_NAMES,ze as WORKER_PROVIDED_PARAMS,pn as applyFitOnMap,Vr as areaWhereSchema,qe as buildAnalyseReturnPrompt,on as buildAtLeastOneEntryIdMessage,en as buildEntryFieldList,nn as buildEntryIdFields,tn as buildEntryKindSandboxDocs,rn as buildEntryKindSchemaDocs,Kr as buildEntryKindScopeSchema,mr as buildPlacesSchemaDoc,Re as buildSandboxCodePrompt,Nn as buildSandboxToolsDoc,Tt as buildSystemPrompt,zt as clarifyIntentOutputSchema,Ut as clarifyIntentSchema,Yn as classifyUserIntent,Mn as collectInputGeometries,xt as composeSystemPrompt,Xt as createClarifyIntentTool,Xn as createDefaultClassifier,dc as createMapAgent,Kn as extractLastUserText,An as findPlacesByEntry,an as fitOnMapInputSchema,ln as fittedReportSchema,Ke as formatSandboxExecutionError,Ie as formatStateDigestDiff,Sr as geoJsonBBoxSchema,yn as geocodeAreas,dr as geometriesEntryIDsSchema,cr as geometriesIdSchema,Or as geometryDisplayConfigSchema,Ar as geometryInputSchema,lc as getDefaultToolPrompts,qn as getRangePolygons,be as getStateDigest,xs as getViewportBias,Ds as getViewportBoundingBox,zr as globalWhereSchema,sn as hasAnyEntryIds,Rr as hidePreviousEntriesSchema,mn as hidePreviousShownEntries,Ne as isChartConfiguration,un as isFitOnMapInput,Qr as isKindActive,Be as isPolygonFeature,Le as isPolygonFeatureArray,ys as isResolveError,wn as labelForLocationInput,xn as latestTimestamp,gn as locatePlace,fn as locatePlaces,bn as locationInputSchema,He as mainThreadExecutor,Cr as markerTypeSchema,Mr as multiPolygonInputSchema,Ur as nearbyWhereSchema,Bn as packSandboxArgs,xr as placesEntryIDsSchema,Tr as placesEntryIdHintSchema,kr as polygonInputSchema,Ir as positionSchema,Tn as prepareMultiInputs,qr as queryAsSchema,Wt as questionSchema,Zr as resolveActiveKinds,hs as resolveGlobal,ps as resolveNearby,Fn as resolvePoiCategories,ie as resolveTools,cs as resolveWhere,us as resolveWithin,Rn as routeUtils,Dr as routesEntryIDsSchema,Ye as runSandboxedFn,Ue as sandboxedGlobalShadows,Yr as sharedWithinFields,Ss as showByodOnMap,Br as showByodSchema,ks as showEntryGeometries,Pr as showModeSchema,_r as showPlaceGeometriesSchema,jr as showPlacesSchema,Ms as showResultsOnMap,Lr as shownByodSchema,Nr as shownSchema,vn as skippedSourceSchema,Ve as stripInjectedRedeclarations,Ts as toolStateToWhereContext,$e as validateAnalysisResult,Hr as whereSchema,Me as withAgentToolkitHeaders,Fr as withinSharedFields,Gr as withinWhereSchema};
2
2
  //# sourceMappingURL=index.es.js.map