@tomtom-org/maps-sdk-plugin-agent-toolkit 0.17.1 → 0.17.2
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/CHANGELOG.md +768 -0
- package/README.md +17 -13
- package/dist/index.d.ts +975 -841
- package/dist/index.es.js +2 -2
- package/dist/{worker-libs-B6xqxvae.js → worker-libs-CGRly7A4.js} +1 -1
- package/package.json +4 -3
package/dist/index.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Output,ToolLoopAgent,generateText,hasToolCall,isStepCount}from"ai";import{BaseMapModule,CustomGeoJSONModule,GeometriesModule,POIsModule,PlacesModule,ReachableRangesModule,RoutingModule,StylingFoundationsModule,TerrainModule,TomTomMapError,TrafficAreaAnalyticsModule,TrafficFlowModule,TrafficIncidentDetailsModule,TrafficIncidentsModule,applyMapPreset,baseMapKnobCatalogue,fillStyledGeometryConfig,filterShowModes,geometriesKnobCatalogue,geometryFillStyles,knobValueIn,mapColorNames,mapPresetIds,mapStyleLayerIDs,placesKnobCatalogue,poisKnobCatalogue,reachableRangesKnobCatalogue,resetKnob,routingKnobCatalogue,setKnob,standardStyleIDs,stylingFoundationsKnobCatalogue,terrainKnobCatalogue,trafficAreaAnalyticsKnobCatalogue,trafficFlowKnobCatalogue,trafficIncidentDetailsKnobCatalogue,trafficIncidentsKnobCatalogue,validateKnobValue}from"@tomtom-org/maps-sdk/map";import{SDKAbortError,SDKError,calculateReachableRanges,calculateRoute,discoverPlaces,functionalRoadClasses,geocode,geometryData,getPOICategoryCodes,resolvePOICategories,reverseGeocode,routeTypes,trafficAreaAnalytics,trafficIncidentDetails}from"@tomtom-org/maps-sdk/services";import{TOMTOM_USER_AGENT_SDK_NAME,TomTomConfig,areaAnalyticsMetricKeys,avoidableTypes,bboxCenter,bboxFromBBoxes,bboxFromGeoJSON,budgetTypes,budgetUnits,calculateProgressAtRoutePoint,compareRoutes,defaultSectionTypes,formatDistance,formatDuration,geopoliticalViews,getCoordinateAtRouteProgress,getImportantRoads,getPosition,getProgressAtNearestRoutePoint,getRouteProgressBetween,getRouteProgressForSection,getSectionBBox,getSectionsWithin,isHairpin,placeTypes,polygonFromBBox,sdkErrorCodes,trafficIncidentRequestCategories,withInsertedWaypoints}from"@tomtom-org/maps-sdk/core";import*as turf from"@turf/turf";import{z}from"zod";import*as h3 from"h3-js";import{deriveMapColors,deriveMapColorsFromImage,mapColorsLightDark,mapThemeModes}from"@tomtom-org/maps-sdk-plugin-map-theme";var intersectActiveTools=(e,t)=>{if(!e)return t;if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},unionActiveTools=(e,t)=>{if(!e)return;if(0===t.length)return[...e];const r=new Set(e);return[...e,...t.filter(e=>!r.has(e))]},isStateSlice=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,mergeProviderOptions=(e,t)=>{if(e||t)return{...e,...t}},resolveTools=(e,t)=>{const r={...e};if(!t)return r;for(const[o,a]of Object.entries(t))a?r[o]=a:delete r[o];return r},StateEvents=class{handlers={};on(e,t){return(this.handlers[e]??(this.handlers[e]=[])).push(t),()=>{const r=this.handlers[e];if(!r)return;const o=r.indexOf(t);-1!==o&&r.splice(o,1)}}off(e){this.handlers[e]&&(this.handlers[e].length=0)}emit(e,t){const r=this.handlers[e];if(r?.length)for(const o of[...r])o(t)}clear(){for(const e of Object.keys(this.handlers))this.handlers[e]=[]}},Analyses=class{_records=/* @__PURE__ */new Map;_jobs=/* @__PURE__ */new Map;events=new StateEvents;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 o=r.history.at(-1);o&&t.push(o)}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()}},BaseMapState=class{ttMap;_baseMapModule;_stylingFoundationsModule;_terrainModule;constructor(e){this.ttMap=e}get mapLibreMap(){return this.ttMap.mapLibreMap}async getBaseMapModule(){return this._baseMapModule=await BaseMapModule.get(this.ttMap),this._baseMapModule}async getStylingFoundationsModule(){return this._stylingFoundationsModule=await StylingFoundationsModule.get(this.ttMap),this._stylingFoundationsModule}async getTerrainModule(){return this._terrainModule=await TerrainModule.get(this.ttMap),this._terrainModule}get terrainModule(){return this._terrainModule}reset(){this._baseMapModule=void 0,this._stylingFoundationsModule=void 0,this._terrainModule=void 0}},modulesOf=(e,t)=>e.flatMap(e=>{const r=t(e);return r?[r]:[]}),withFillStyle=async(e,t,r)=>{if(!e)return r();const o=e.getConfig()?.fill;return o?.style!==t&&e.updateConfig({fill:{...o,style:t}}),e},fillStyledGeometriesConfig=e=>{const t=fillStyledGeometryConfig();return{...t,fill:{...t.fill,style:e}}},pickUniqueEntryId=(e,t)=>{const r=new Set(t);if(!r.has(e))return e;for(let o=2;;o++){const t=`${e}-${o}`;if(!r.has(t))return t}},hideAllEntries=async(e,t,r={})=>{if(0!==e.length)if(r.parallel)await Promise.all(e.map(t));else for(const o of e)await t(o)},collapseHistoryToLatest=async(e,t,r={})=>{if(e.length<=1)return[...e];const o=e[e.length-1];return await hideAllEntries(e.slice(0,-1),t,r),[o]},SliceKnobs=class{catalogue;modules;_values=/* @__PURE__ */new Map;constructor(e,t){this.catalogue=e,this.modules=t}get values(){return Object.fromEntries(this._values)}set(e,t){validateKnobValue(this._knob(e),t);for(const r of this.modules())setKnob(r,this.catalogue,e,t);this._values.set(e,t)}reset(e){this._knob(e);for(const t of this.modules())resetKnob(t,this.catalogue,e);this._values.delete(e)}resetAll(){for(const e of[...this._values.keys()])this.reset(e)}clear(){this._values.clear()}applyTo(e){for(const[t,r]of this._values)setKnob(e,this.catalogue,t,r);return e}_knob(e){const t=this.catalogue.find(t=>t.id===e);if(!t)throw new Error(`Unknown knob '${e}'`);return t}},CustomGeometriesState=class{_ttMap;_entries=[];_entryMode="multiple";events=new StateEvents;knobs=new SliceKnobs(geometriesKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));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 collapseHistoryToLatest(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,o){const a="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await hideAllEntries(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=`geometries-${this._entries.length}`,n=pickUniqueEntryId(o??s,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:Date.now(),label:r,data:e,provenance:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...a,n]}),n}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e);return r._module=await withFillStyle(r._module,t,async()=>this.knobs.applyTo(await GeometriesModule.create(this._ttMap,fillStyledGeometriesConfig(t)))),r._module}async showEntry(e,t="outline",r="replace"){const o=this._requireEntry(e);await this._hideOthersUnderSingleMode(e);const a=await this.getEntryGeometriesModule(e,t),s=o.data;"add"===r?await(async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const o of e){const e="string"==typeof o.id?o.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(o)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})})(a,s):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(a,s);const n=o._shown;o._shown=!0,n||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.knobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},compareIds=(e,t)=>e.localeCompare(t),sortIds=e=>[...e].sort(compareIds),getStateDigest=e=>({places:{shownAsPin:sortIds(e.places.shownAsPinIds),shownAsBaseMap:sortIds(e.places.shownAsBaseMapIds),entryMode:e.places.entryMode},routes:{shown:sortIds(e.routing.shownEntryIds),entryMode:e.routing.entryMode},ranges:{shown:sortIds(e.ranges.shownEntryIds),entryMode:e.ranges.entryMode},traffic:{shown:sortIds(e.trafficAreaAnalytics.shownEntryIds),entryMode:e.trafficAreaAnalytics.entryMode,entryCount:e.trafficAreaAnalytics.entries.length},incidents:{shown:sortIds(e.trafficIncidents.shownEntryIds),monitored:sortIds(e.trafficIncidents.entries.filter(e=>"running"===e._monitor?.status).map(e=>e.id)),entryMode:e.trafficIncidents.entryMode},customGeometries:{shown:sortIds(e.customGeometries.shownEntryIds),entryMode:e.customGeometries.entryMode,entryCount:e.customGeometries.entries.length},byod:{shown:sortIds(e.byod.shownEntryIds),entryMode:e.byod.entryMode,entryCount:e.byod.entries.length}}),arraysEqual=(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},formatShownSetDiff=(e,t,r)=>{if(!arraysEqual(t,r))return 0===r.length?`${e} cleared`:`${e} shown: ${r.map(e=>`"${e}"`).join(", ")}`},formatStateDigestDiff=(e,t)=>{const r=[],o=((e,t)=>{const r=[];return arraysEqual(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(", ")}`),arraysEqual(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);o&&r.push(o);const a=formatShownSetDiff("routes",e.routes.shown,t.routes.shown);a&&r.push(a);const s=formatShownSetDiff("ranges",e.ranges.shown,t.ranges.shown);s&&r.push(s);const n=formatShownSetDiff("traffic analytics",e.traffic.shown,t.traffic.shown);n&&r.push(n);const i=formatShownSetDiff("incidents",e.incidents.shown,t.incidents.shown);i&&r.push(i);const l=formatShownSetDiff("incidents monitor",e.incidents.monitored,t.incidents.monitored);l&&r.push(l);const c=formatShownSetDiff("custom geometries",e.customGeometries.shown,t.customGeometries.shown);c&&r.push(c);const d=formatShownSetDiff("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("; ")}]`},MapPOIsState=class{_ttMap;_poisModule;constructor(e){this._ttMap=e}async getPOIsModule(){return this._poisModule=await POIsModule.get(this._ttMap),this._poisModule}get poisModule(){return this._poisModule}reset(){this._poisModule=void 0}},AGENT_TOOLKIT_ID="AgentToolkit",AGENT_TOOLKIT_USER_AGENT=`${TOMTOM_USER_AGENT_SDK_NAME}-AgentToolkit/0.17.1`,withAgentToolkitHeaders=e=>({...e,"tomtom-user-agent":AGENT_TOOLKIT_USER_AGENT}),placesMarkerTypes=["pin","base-map","pin-clustered"],PlacesState=class{_ttMap;_entries=[];_entryMode="multiple";events=new StateEvents;knobs=new SliceKnobs(placesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.places));geometriesKnobs=new SliceKnobs(geometriesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.geometries));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 collapseHistoryToLatest(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.updateConfig({markerType:t}):r._modules.places=this.knobs.applyTo(await PlacesModule.create(this._ttMap,{markerType:t})),r._modules.places}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e)._modules??={};return r.geometries=await withFillStyle(r.geometries,t,async()=>this.geometriesKnobs.applyTo(await GeometriesModule.create(this._ttMap,fillStyledGeometriesConfig(t)))),r.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,o,a){"single"===this._entryMode&&this._entries.length>0&&(await hideAllEntries(this._entries,e=>this._hideEntry(e)),this._entries=[]);const s=`places-${this._entries.length}`,n=pickUniqueEntryId(r??s,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:Date.now(),label:t,data:"features"in e?e.features:[e],...o?.length&&{connections:o},...a?.length&&{geometries:a}}),this.events.emit("entries-change",{entries:this._entries,changedIds:[n]}),a?.length&&this.events.emit("geometries-change",{entryId:n,count:a.length}),n}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 o=r._byId[e];if(o)return{place:o,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 o=r._geometryByPlaceId[e];if(o)return o}}async fetchPlaceGeometry(e,t){const r=this.getGeometryForPlace(e);if(r)return r;const o=this.findPlaceById(e);if(!o)return;if(!o.place.properties.dataSources?.geometry?.id)return;const a=withAgentToolkitHeaders({geometries:[o.place],signal:t?.signal}),s=(await geometryData(a)).features[0];if(!s)return;const n=this._entries.find(e=>e.id===o.entryId);return n&&(n.geometries??=[],n.geometries.push(s),n._geometryByPlaceId??={},n._geometryByPlaceId[e]=s,this.events.emit("geometries-change",{entryId:n.id,count:n.geometries.length})),s}async fetchGeometriesForEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return[];const o=/* @__PURE__ */new Set;for(const c of r.geometries??[])"string"==typeof c.id&&o.add(c.id);const a=[],s=/* @__PURE__ */new Set;for(const c of r.data){const e=c.properties.dataSources?.geometry?.id;e&&(o.has(e)||s.has(e)||(s.add(e),a.push(c)))}if(0===a.length)return r.geometries??[];const n=[];for(let c=0;c<a.length;c+=5)n.push(a.slice(c,c+5));const i=await Promise.all(n.map(e=>geometryData(withAgentToolkitHeaders({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",o){const a=o??this._latestGeometriesEntryId();if(!a)return;const s=await this.getEntryGeometriesModule(a,t);"add"===r?await(async(e,t)=>{if(0===t.length)return;const r=(e=>{const t=/* @__PURE__ */new Set,r=[];for(const o of e){const e="string"==typeof o.id?o.id:void 0;if(e){if(t.has(e))continue;t.add(e)}r.push(o)}return r})([...e.getShown().geometry.features??[],...t]);await e.show({type:"FeatureCollection",features:r})})(s,e):await(async(e,t)=>{0!==t.length?await e.show({type:"FeatureCollection",features:t}):await e.clear()})(s,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 o of e.geometries??[])"string"==typeof o.id&&t.set(o.id,o);const r={};for(const o of e.data){const e=o.properties.dataSources?.geometry?.id;if(!e)continue;const a=t.get(e);a&&(r[o.id]=a)}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),o=[],a=[];for(const s of this._entries)r.has(s.id)?o.push(s):s._shownAs===t&&a.push(s);for(const s of a)await this._hideEntry(s);for(const s of o)await this._showEntryAs(s,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 o of r)await this._showEntryAs(o,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],o=!!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]}),o&&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.knobs.clear(),this.geometriesKnobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},RangeState=class{_ttMap;_entries=[];_entryMode="multiple";events=new StateEvents;knobs=new SliceKnobs(reachableRangesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.ranges));geometriesKnobs=new SliceKnobs(geometriesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.geometries));placesKnobs=new SliceKnobs(placesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.places));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 collapseHistoryToLatest(this._entries,e=>this.hideEntry(e.id)),this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}async getEntryRangesModule(e,t="outline"){const r=this._requireEntry(e)._modules??={};return r.ranges=await withFillStyle(r.ranges,t,async()=>this.knobs.applyTo(await ReachableRangesModule.create(this._ttMap,{fill:{style:t}}))),r.ranges}async getEntryGeometriesModule(e,t="outline"){const r=this._requireEntry(e)._modules??={};return r.geometries=await withFillStyle(r.geometries,t,async()=>this.geometriesKnobs.applyTo(await GeometriesModule.create(this._ttMap,fillStyledGeometriesConfig(t)))),r.geometries}async getEntryPlacesModule(e){const t=this._requireEntry(e);return t._modules??={},t._modules.places??=this.placesKnobs.applyTo(await PlacesModule.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?.ranges&&await t._modules.ranges.clear(),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 hideAllEntries(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const r=pickUniqueEntryId(`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?.ranges?.clear(),t._modules?.geometries?.clear(),t._modules?.places?.clear();this._entries=[],this.knobs.clear(),this.geometriesKnobs.clear(),this.placesKnobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},RouteMonitor=class{events=new StateEvents;_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 o=e.setInterval??setInterval;r||this._tick(),this._intervalHandle=o(()=>{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}},selectedRouteIndex=e=>Math.max(0,e?.getShown().mainLines.features.findIndex(e=>"selected"===e.properties.routeState)??0),RoutingState=class{_ttMap;_entries=[];_planningSlots=[];_params={};_entryMode="multiple";knobs=new SliceKnobs(routingKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));_lastShownEntryId;events=new StateEvents;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 collapseHistoryToLatest(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??=this.knobs.applyTo(await RoutingModule.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 o=await this.getEntryRoutingModule(e);!1===t?.showSummaryBubbles&&o.updateConfig({summaryBubbles:{...o.getConfig()?.summaryBubbles,visible:!1}}),await o.showRoutes(r.data);const a=t?.showWaypoints??!1!==r._waypointsShown;await this._renderWaypoints(o,a?r.waypoints:[]);const s=r._shown;r._shown=!0,this._lastShownEntryId=e,s||this.events.emit("shown-change",this.shownEntryIds)}async setWaypointsShown(e,t){const r=this._requireEntry(e);r._waypointsShown=t,r._shown&&r._module&&await this._renderWaypoints(r._module,t?r.waypoints:[])}async _renderWaypoints(e,t){t.length?await e.showWaypoints(t):await e.clearWaypoints()}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,t._sectionFocus=void 0,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 o="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await hideAllEntries(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const a=pickUniqueEntryId(`routes-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:r,data:e,waypoints:t,params:{...this._params}}),this._planningSlots=[...t],this.events.emit("entries-change",{entries:this._entries,changedIds:[...o,a]}),this.events.emit("planning-change",this._planningSlots),a}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 o=this._entries.find(t=>t.id===e);if(!o)return!1;const a=(o._tick??0)+1;if(o._tick=a,o.data=t,o.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),o._shown&&o._module){const e=selectedRouteIndex(o._module),r=e<t.features.length?e:0;await o._module.showRoutes(t,{selectedIndex:r});const s=t.features[r],n=s&&o._sectionFocus?.(s);n&&o._tick===a&&await o._module.setSectionFocus(n)}return!0}_createMonitorForEntry(e){const t=new RouteMonitor;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 o=this._requireEntry(e),a=o._monitor??=this._createMonitorForEntry(o),s=a.isRunning;a.start(t,r?.intervalMs,r?.skipInitialTick),s||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.knobs.clear(),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)}},JobEngine=class{jobs=/* @__PURE__ */new Set;wired=!1;scanning=!1;scheduled=!1;pending=/* @__PURE__ */new Set;register(e){const t=new JobHandle(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 o of this.sourceSlices(e))for(const e of o.entries)t.add(e.id);const r=[...this.jobs].filter(e=>!e.affectedEntryIds.some(e=>t.has(e)));for(const o of r)this.jobs.delete(o),o.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}},JobHandle=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)}},buildSandboxCodePrompt=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 …;\`.`,CHART_TYPES=/* @__PURE__ */new Set(["bar","line","pie","doughnut","radar","polarArea","scatter","bubble"]),isChartConfiguration=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"string"==typeof t.type&&CHART_TYPES.has(t.type)&&"object"==typeof t.data&&null!==t.data},isPolygonFeature=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)},isPolygonFeatureArray=e=>Array.isArray(e)&&e.every(isPolygonFeature),ANALYSE_OUTPUT_FORMAT_DESCRIPTION='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.',buildAnalyseReturnPrompt=(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})`:""}.`,validateAnalysisResult=(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=toJsonSafe(e);return"error"in r?{error:r.error}:"chart"!==t||isChartConfiguration(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? }).'}},toJsonSafe=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.`}}},SHADOWED_GLOBALS=["fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","document","window","globalThis","self","navigator","importScripts","process"],sandboxedGlobalShadows=SHADOWED_GLOBALS,WORKER_PROVIDED_PARAMS=["turf","h3","routeUtils","log","cluster"],mainThreadExecutor={run:async(e,t,r,o)=>{try{const o=Object.getPrototypeOf(async()=>{}).constructor,a=SHADOWED_GLOBALS.filter(e=>!t.includes(e)),s=r.map((e,r)=>((e,t)=>void 0===e||WORKER_PROVIDED_PARAMS.includes(t)?e:structuredClone(e))(e,t[r]));return{value:await new o(...t,...a,e)(...s,...a.map(()=>{}))}}catch(a){return{error:formatSandboxExecutionError(o,a)}}}},runSandboxedFn=(e,t,r,o,a)=>a.run(stripInjectedRedeclarations(e,t),t,r,o),stripInjectedRedeclarations=(e,t)=>{const r=t.join("|"),o=String.raw`(?:(?:await\s+)?(?:require|import)\s*\(`+String.raw`|arguments\s*(?:\[\s*\d+\s*\])?\s*(?:\.\w+|\[\s*['"]\w+['"]\s*\]))`,a=new RegExp(String.raw`^[ \t]*(?:const|let|var)[ \t]+(?:${r})[ \t]*=[ \t]*${o}[^\n]*\n?`,"gm");return e.replace(a,"")},SANDBOX_ERROR_HINTS=[{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."}],formatSandboxExecutionError=(e,t)=>{const r=t instanceof Error?t.message:String(t),o=`${e} code execution failed: ${r}`,a=SANDBOX_ERROR_HINTS.find(({pattern:e})=>e.test(r));return a?`${o} Hint: ${a.hint}`:t instanceof Error&&"SyntaxError"===t.name?`${o} Hint: the provided \`code\` is not valid JavaScript — ensure it parses as an async function body and \`return\`s a value.`:o},WORKER_RUNTIME_BODY=`\n(function () {\n var SHADOWS = ${JSON.stringify([...sandboxedGlobalShadows])};\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 // A page CSP without 'unsafe-eval' reaches this worker too; fail the start rather than every run.\n try {\n new AsyncFunction('');\n self.postMessage({ type: 'worker-ready' });\n } catch (evalError) {\n self.postMessage({ type: 'worker-error', errorMessage: String((evalError && evalError.message) || evalError) });\n }\n})();\n`,hostRealmExecutor={...mainThreadExecutor,getMode:()=>"main-thread"},formatStartFailure=(e,t)=>`${e} code was not run: the isolated code sandbox could not start (${t}). Tell the user that code execution is unavailable right now; a later request tries the sandbox again.`,defaultLoadLibrarySource=()=>import("./worker-libs-B6xqxvae.js").then(e=>e.sandboxLibrarySource),createIframeWorkerExecutor=e=>{let t,r=null,o=null,a="not-started",s=0,n=!1,i=1;const l=/* @__PURE__ */new Map,c=e=>{r?.contentWindow?.postMessage(e,"*")},d=e=>{if(e.source!==r?.contentWindow)return;const t=e.data;if("worker-error"===t?.type){const e=t.errorMessage??"worker error";for(const[t,r]of l)clearTimeout(r.timer),r.resolve({error:formatSandboxExecutionError(r.verb,e)}),l.delete(t);return}if("result"!==t?.type)return;const o=t,a=l.get(o.id);a&&(l.delete(o.id),clearTimeout(a.timer),a.resolve(o.ok?{value:o.value}:{error:formatSandboxExecutionError(a.verb,o.errorMessage)}))},u=e=>new Promise((t,o)=>{const a=e=>{clearTimeout(s),globalThis.removeEventListener("message",n),e?o(e):t()},s=setTimeout(()=>a(/* @__PURE__ */new Error(`timed out waiting for "${e}"`)),2e3),n=t=>{if(t.source!==r?.contentWindow)return;const o=t.data;o?.type===e?a():"worker-error"===o?.type&&a(new Error(o.errorMessage??"worker error"))};globalThis.addEventListener("message",n)}),h=()=>"iframe-worker"===a?Promise.resolve(void 0):Date.now()<s?Promise.resolve(t):(o??=(async()=>{if("undefined"==typeof document||"undefined"==typeof Worker||"undefined"==typeof URL||"undefined"==typeof Blob)return"browser APIs missing";if(!e.loadLibrarySource)return"no worker library source configured (codeExecution.loadWorkerLibrarySource)";try{const t=document.createElement("iframe");t.setAttribute("sandbox","allow-scripts"),t.setAttribute("aria-hidden","true"),t.style.display="none",t.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 // An error event without a message is the worker failing to load, e.g. blocked by the page's CSP.\n worker.onerror = function (e) {\n var detail = e && e.message ? (e.message + (e.filename ? ' @ ' + e.filename + ':' + e.lineno : '')) : 'the worker failed to load';\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(); 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>",r=t;const o=u("bootstrap-ready");document.body.appendChild(t),await o;const a=`${await e.loadLibrarySource()}\n;${WORKER_RUNTIME_BODY}`,s=u("worker-ready");return c({type:"init",workerSource:a}),await s,void globalThis.addEventListener("message",d)}catch(t){return r?.remove(),r=null,t instanceof Error?t.message:String(t)}})().then(r=>(o=null,void 0===r?(a="iframe-worker",t=void 0,s=0):(a=e.allowMainThreadFallback?"main-thread":"unavailable",t=r,s=e.allowMainThreadFallback?Number.POSITIVE_INFINITY:Date.now()+3e4,(t=>{if(n)return;n=!0;const r=e.allowMainThreadFallback?"falling back to main-thread execution (NOT isolated)":"model-written code does not run until it starts";console.warn(`[agent-toolkit] iframe-worker sandbox unavailable (${t}); ${r}.`)})(r)),r)),o);return{run:async(t,r,o,a)=>{const s=await h();if(void 0!==s)return e.allowMainThreadFallback?mainThreadExecutor.run(t,r,o,a):{error:formatStartFailure(a,s)};const n=i++,{dataByIndex:d}=((e,t)=>{const r=new Set(WORKER_PROVIDED_PARAMS),o={};for(let a=0;a<e.length;a++)r.has(e[a])||(o[a]=t[a]);return{dataByIndex:o}})(r,o);return new Promise(o=>{const s=setTimeout(()=>{l.delete(n),c({type:"terminate"}),o({error:formatSandboxExecutionError(a,/* @__PURE__ */new Error(`code execution exceeded the ${e.timeoutMs}ms sandbox timeout and was terminated`))})},e.timeoutMs);l.set(n,{resolve:o,verb:a,timer:s});try{const e={type:"run",id:n,code:t,paramNames:[...r],dataByIndex:d};c(e)}catch(i){clearTimeout(s),l.delete(n),o({error:formatSandboxExecutionError(a,i)})}})},destroy:()=>{void 0!==globalThis.window&&globalThis.removeEventListener("message",d);for(const e of l.values())clearTimeout(e.timer);l.clear(),r?.remove(),r=null,a="not-started",t=void 0,s=0,n=!1},getMode:()=>a}},resolveSandboxExecutor=e=>"undefined"==typeof document?hostRealmExecutor:createIframeWorkerExecutor({timeoutMs:e?.timeoutMs??1e4,loadLibrarySource:e?.loadWorkerLibrarySource??defaultLoadLibrarySource,allowMainThreadFallback:e?.allowMainThreadFallback??!1}),jsonTypeOf=e=>null===e?"null":Array.isArray(e)?"array":typeof e,recordExample=(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)}},collectGeometryTypes=e=>{const t=/* @__PURE__ */new Set;for(const r of e.features){const e=r.geometry?.type;e&&t.add(e)}return[...t]},accumulateProperties=e=>{const t=/* @__PURE__ */new Map;for(const r of e.features){const e=r.properties;if(e&&"object"==typeof e)for(const[r,o]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(jsonTypeOf(o)),recordExample(e,o)}}return t},profileFeatureCollection=e=>{const t=e.features.length,r=[...accumulateProperties(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)),o=r.slice(0,40),a=r.length-o.length;return{featureCount:t,geometryTypes:collectGeometryTypes(e),properties:o,...a>0&&{propertiesOmitted:a}}},toByodSafeProfile=e=>({...e,properties:e.properties.map(e=>({...e,examples:e.examples.filter(e=>"string"!=typeof e)}))}),BYODState=class{_ttMap;_entries=[];_entryMode="multiple";events=new StateEvents;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 collapseHistoryToLatest(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 o="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await hideAllEntries(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const a=pickUniqueEntryId(r.explicitId??`byod-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:a,timestamp:Date.now(),label:t,data:e,source:r.source??{kind:"integrator"},layers:r.layers??[],profile:profileFeatureCollection(e)}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...o,a]}),a}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 CustomGeoJSONModule.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)}},EventsState=class{_trackers=/* @__PURE__ */new Map;_log=[];_jobs=/* @__PURE__ */new Map;_seq=0;events=new StateEvents;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 o=this._trackers.get(e);if(o?.enabled&&isVerdict(t))if(t.active&&!o.wasActive)o.wasActive=!0,o.openedAt=r,this.append(o,{type:"alert",kind:"opened",verdict:t,at:r});else if(!t.active&&o.wasActive){const e=o.openedAt;o.wasActive=!1,o.openedAt=void 0,this.append(o,{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 o=this._log.filter(t=>t.trackerId===e.id);o.length>200&&this._log.splice(this._log.indexOf(o[0]),1),this.events.emit("tracker-event",r),this.emitTrackers()}emitTrackers(){this.events.emit("trackers-change",this.trackers)}};function isVerdict(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 TrafficAreaAnalyticsState=class{_ttMap;_entries=[];_entryMode="multiple";events=new StateEvents;knobs=new SliceKnobs(trafficAreaAnalyticsKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));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 collapseHistoryToLatest(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,o){const a="single"===this._entryMode?this._entries.map(e=>e.id):[];"single"===this._entryMode&&this._entries.length>0&&(await hideAllEntries(this._entries,e=>this.hideEntry(e.id)),this._entries=[]);const s=`tta-${this._entries.length}`,n=pickUniqueEntryId(o??s,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:Date.now(),label:t,data:e,params:r}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...a,n]}),n}async getEntryModule(e){const t=this._requireEntry(e);return t._module||(t._module=await TrafficAreaAnalyticsModule.create(this._ttMap),t._configChangeUnsub=t._module.events.on("config-change",r=>{t._config=r,this.events.emit("config-change",{entryId:e,config:r})}),this.knobs.applyTo(t._module)),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),o=t.data.properties?.metrics??[],a=o.find(e=>e===this.knobs.values.activeMetric)??o[0];a&&r.updateConfig({activeMetric:a}),await r.clear(),await r.show(t.data);const s=t._shown;t._shown=!0,s||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t?._shown&&(await(t._module?.clear()),t._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){const t=this._entries.findIndex(t=>t.id===e);if(-1===t)return;await this.hideEntry(e);const r=this._entries[t];r._configChangeUnsub?.(),r._configChangeUnsub=void 0,r._module=void 0,this._entries.splice(t,1),this.events.emit("entries-change",{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.knobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},DEFAULT_EXCLUDE_CATEGORIES=["roadworks","road-closed"];function toPoint(e){const t=e.properties.id;if("Point"===e.geometry.type)return turf.point(e.geometry.coordinates,{_incidentId:t});const r=turf.centroid(e);return r.properties={_incidentId:t},r}function collectPrimaryRoads(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 collectPrimaryCategory(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 runClustering(e,t,r,o,a){if(0===e.length)return{groups:[]};const s=function(e,t,r){if(0===r)return e;const o=new Set(t);return e.filter(e=>{const t=e.properties.category,a=e.properties.delayInSeconds??0;return!(o.has(t)&&a<r)})}(e,t.preFilter?.excludeCategories??DEFAULT_EXCLUDE_CATEGORIES,t.preFilter?.minDelaySeconds??1);if(0===s.length)return{groups:[]};const n=/* @__PURE__ */new Map;for(const w of s)n.set(w.properties.id,w);const i=s.map(toPoint),l=turf.featureCollection(i),c=t.eps??.5,d=t.minMembers??3,u=turf.clustersDbscan(l,c,{minPoints:d}),h=/* @__PURE__ */new Map;for(const w of u.features){const e=w.properties.cluster;if(void 0===e||e<0)continue;let t=h.get(e);t||(t=[],h.set(e,t)),t.push(w)}const p=t.maxClusters??6,y=[];for(const[,w]of h){const e=w.map(e=>e.properties._incidentId),t=e.map(e=>n.get(e)).filter(e=>void 0!==e),r=t.reduce((e,t)=>e+(t.properties.delayInSeconds??0),0),o=t.reduce((e,t)=>Math.max(e,t.properties.delayInSeconds??0),0),a=turf.featureCollection(w),s=turf.centroid(a).geometry.coordinates;let i=0;if(w.length>=2){const e=turf.bbox(a);i=turf.distance([e[0],e[1]],[e[2],e[3]])}const l=collectPrimaryRoads(t);y.push({centroid:s,memberIds:e,size:t.length,totalDelaySeconds:r,peakDelaySeconds:o,diameterKm:i,primaryRoads:l,primaryCategory:collectPrimaryCategory(t)})}y.sort((e,t)=>t.totalDelaySeconds-e.totalDelaySeconds);const m=y.slice(0,p),f=r?function(e,t){const r=t.map(e=>new Set(e.memberIds)),o=[];for(let i=0;i<e.length;i++){const a=new Set(e[i].memberIds);for(let e=0;e<t.length;e++){const t=r[e];let s=0;for(const e of a)t.has(e)&&s++;const n=a.size+t.size-s,l=0===n?0:s/n;l>=.5&&o.push({freshIdx:i,prevIdx:e,overlap:l})}}o.sort((e,t)=>t.overlap-e.overlap);const a=/* @__PURE__ */new Map,s=/* @__PURE__ */new Set,n=/* @__PURE__ */new Set;for(const{freshIdx:i,prevIdx:l,overlap:c}of o)s.has(i)||n.has(l)||(a.set(i,t[l].id),s.add(i),n.add(l));return a}(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===o||void 0===a||o>a;return{groups:m.map((e,t)=>{const r=f.get(t),o=r??`cluster-${e.centroid[0].toFixed(3)}-${e.centroid[1].toFixed(3)}-${[...e.memberIds].sort()[0]??t}`,a=r?g.get(r):void 0,s=a?.delaySamples??[],n=a?(b?[...s,e.totalDelaySeconds]:[...s.slice(0,-1),e.totalDelaySeconds]).slice(-3):[e.totalDelaySeconds],i=1===n.length?"new":function(e){if(e.length<3)return"steady";const[t,r,o]=e.slice(-3),a=(o-t)/Math.max(t,1);return r>t&&o>r&&a>.1?"growing":r<t&&o<r&&a<-.1?"fading":"steady"}(n);return{id:o,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:n}})}}var clusterIncidents=(e,t={},r,o)=>{const a=(e=>{if(null!=e)return e instanceof Date?e.getTime():e})(o),{groups:s}=runClustering(e,t,r?.groups,a,r?.sampledAt);return void 0===a?{groups:s}:{groups:s,sampledAt:a}},IncidentMonitor=class{_bbox;events=new StateEvents;_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,o=!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 a=t.setInterval??setInterval;o||this._tick(),this._intervalHandle=a(()=>{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}},fitMapToBBox=(e,t,r)=>{e.fitBounds(t,{padding:50,...r})},fitMapToGeoJSON=(e,t,r)=>{const o=bboxFromGeoJSON(t);return!!o&&(fitMapToBBox(e,o,r),!0)},TrafficIncidentsState=class{_ttMap;_entries=[];_entryMode="multiple";_clusters=/* @__PURE__ */new Map;events=new StateEvents;knobs=new SliceKnobs(trafficIncidentDetailsKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));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 collapseHistoryToLatest(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,o,a){"single"===this._entryMode&&this._entries.length>0&&(await hideAllEntries(this._entries,e=>this._teardownEntry(e),{parallel:!0}),this._entries=[]);const s=pickUniqueEntryId(a??`incidents-${this._entries.length}`,this._entries.map(e=>e.id));return this._entries.push({id:s,timestamp:o,label:r,data:e,params:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[s]}),s}async replaceEntryData(e,t,r){const o=this._entries.find(t=>t.id===e);if(!o)return!1;const a=(o._tick??0)+1;if(o._tick=a,o.data=t,o.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),o._module){if(await o._module.show({type:"FeatureCollection",features:t}),o._tick!==a)return!0;if(o._focus){const e=new Set(t.map(e=>e.properties.id)),r=[...o._focus.ids].filter(t=>e.has(t));o._module.setFocus(r.length>0?r:null),0===r.length&&(o._focus=void 0,this.events.emit("focus-change",{entryId:o.id,focus:null}))}}return!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=this.knobs.applyTo(await TrafficIncidentDetailsModule.create(this._ttMap)),t._module}async showEntry(e,t=!0){const r=this._requireEntry(e),o=await this.getEntryModule(e);await o.show({type:"FeatureCollection",features:r.data}),o.setVisible(!0),t&&fitMapToBBox(this._ttMap.mapLibreMap,r.params.bbox,{maxZoom:14});const a=!!r._shown;r._shown=!0,a||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],o=!!r._shown;await this._teardownEntry(r),this._entries.splice(t,1),this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),o&&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 o=this._entries.find(t=>t.id===e);if(!o)return{focusedCount:0,droppedIds:[...t]};if(0===t.length)return this.clearFocus(e),{focusedCount:0,droppedIds:[]};const a=new Set(o.data.map(e=>e.properties.id)),s=[],n=[];for(const i of t)a.has(i)?s.push(i):n.push(i);return 0===s.length?{focusedCount:0,droppedIds:n}:(o._focus={ids:new Set(s),reason:r},o._module?.setFocus(s),this.events.emit("focus-change",{entryId:o.id,focus:this.getFocus(e)}),{focusedCount:s.length,droppedIds:n})}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 a of t.data){const e=a.properties.id;"string"==typeof e&&(r[e]=a)}const o=[];for(const a of t._focus.ids){const e=r[a];e&&o.push(e)}return o}navigateFocus(e,t){const r=this.focusedFeatures(e);if(0===r.length)return;const o=r[(t%r.length+r.length)%r.length],a=this._ttMap.mapLibreMap;if("Point"===o.geometry.type)return void a.flyTo({center:o.geometry.coordinates,zoom:15,duration:600});const s=bboxFromGeoJSON(o);s&&a.fitBounds(s,{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,o){const a=this._requireEntry(e),s=a._monitor??=this._createMonitorForEntry(a),n=s.isRunning;s.start(t,r,o?.intervalMs,o?.skipInitialTick),n||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 IncidentMonitor(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=[],this.knobs.clear(),t&&(this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)),this.events.clear()}},TrafficTilesState=class{_ttMap;_trafficFlowModule;_trafficIncidentsModule;constructor(e){this._ttMap=e}async getTrafficFlowModule(){return this._trafficFlowModule=await TrafficFlowModule.get(this._ttMap),this._trafficFlowModule}async getTrafficIncidentsModule(){return this._trafficIncidentsModule=await TrafficIncidentsModule.get(this._ttMap),this._trafficIncidentsModule}get trafficFlowModule(){return this._trafficFlowModule}get trafficIncidentsModule(){return this._trafficIncidentsModule}reset(){this._trafficFlowModule=void 0,this._trafficIncidentsModule=void 0}};TomTomConfig.instance.put({language:"en-GB"});var DATA_ENTRY_KIND_TO_SLICE={places:"places",routes:"routing",incidents:"trafficIncidents",customGeometries:"customGeometries",trafficAreaAnalytics:"trafficAreaAnalytics",byod:"byod",ranges:"ranges"},ENTRY_MODE_SLICE_NAMES=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),CLEARABLE_SLICE_NAMES=Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}),SECTION_HEADINGS={identity:null,capabilities:null,rejectionRules:"SCOPE & REJECTIONS",responseFormatting:"RESPONSE FORMATTING",dataConfidence:"DATA CONFIDENCE",toolExecution:"TOOL EXECUTION",sessionState:"SESSION STATE"},SYSTEM_PROMPT_SECTIONS={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."},SECTION_ORDER=["identity","capabilities","rejectionRules","responseFormatting","dataConfidence","toolExecution","sessionState"],composeSystemPrompt=e=>SECTION_ORDER.map(t=>({key:t,body:e?.[t]??SYSTEM_PROMPT_SECTIONS[t]})).filter(({body:e})=>e.trim().length>0).map(({key:e,body:t})=>((e,t)=>{const r=SECTION_HEADINGS[e];return r?`${r}:\n${t}`:t})(e,t)).join("\n\n"),BASE_SYSTEM_PROMPT=composeSystemPrompt(),buildSystemPrompt=({customPrompt:e,prefix:t,suffix:r}={})=>{if("string"==typeof e)return e;const o=e?composeSystemPrompt(e):BASE_SYSTEM_PROMPT,a=r?`${o}\n\nADDITIONAL INSTRUCTIONS:\n${r}`:o;return t?`${t}\n\n${a}`:a},RECOVERY_BY_CODE={INVALID_REQUEST:"The service rejected the arguments — change them; the same call fails the same way.",INVALID_CREDENTIALS:"The TomTom API key is missing, invalid or expired — do not retry; tell the user.",INSUFFICIENT_PERMISSIONS:"The TomTom API key is not allowed this request — do not retry; tell the user.",NOT_FOUND:"The service found nothing for this input — do not retry the same call.",RATE_LIMITED:"The TomTom service is rate-limiting requests — retry once at most, then tell the user.",LIVE_SERVICE_NOT_AVAILABLE:"The TomTom service failed — retry once at most, then tell the user.",NETWORK_NOT_AVAILABLE:"No response arrived from the TomTom service — retry once at most, then tell the user.",ABORTED:"The call was cancelled — do not retry unless the user asks again."},errorMessageOf=e=>e instanceof Error?e.message:String(e),toToolError=(e,t)=>{const r=errorMessageOf(t),o=t instanceof SDKError||t instanceof TomTomMapError?t.code:void 0,a=o&&RECOVERY_BY_CODE[o];return{error:`${e}: ${r}${a?` ${a}`:""}`,...o&&{code:o}}},countedRouteSectionTypes=["tollRoad","toll","ferry","carTrain","traffic","tunnel","unpaved","lowEmissionZone","vehicleRestricted"],summarizePlace=e=>({id:e.id,type:e.properties?.type,name:e.properties?.poi?.name,address:e.properties?.address?.freeformAddress,position:e.geometry?.coordinates??[0,0]}),summarizePlaces=e=>{const t=Array.isArray(e)?e:e.features;return{count:t.length,features:t.slice(0,5).map(summarizePlace)}},countSections=e=>{const t=Object.fromEntries(countedRouteSectionTypes.flatMap(t=>{const r=e(t);return r>0?[[t,r]]:[]}));return Object.keys(t).length>0?t:void 0},summarizeRoutes=e=>{const[t,...r]=e.features,o=r.length>0?t:void 0;return{count:e.features.length,routes:e.features.map(e=>((e,t)=>{const{summary:r,sections:o,index:a}=e.properties,s=o.leg,n=e=>({...e,arrivalTime:e.arrivalTime.toISOString(),departureTime:e.departureTime.toISOString()}),i=countSections(e=>o[e]?.length??0),l=[...new Set(o.country?.map(e=>e.countryCodeISO3))],c=((e,t)=>{const r=getImportantRoads(e,{within:t,minLengthInMeters:2e3}).filter(e=>e.name),o=new Set([...r].sort((e,t)=>t.lengthInMeters-e.lengthInMeters).slice(0,3));return r.filter(e=>o.has(e)).map(e=>e.name)})(e,t&&o.distinct),d=(e=>{const{sections:t,summary:r}=e.properties,o=(t.curve??[]).reduce((t,r)=>t+(getRouteProgressForSection(e,r)?.delta.travelTimeInSeconds??0),0);return r.travelTimeInSeconds>0?Math.round(100*o/r.travelTimeInSeconds):0})(e),u=t&&t!==e?((e,t)=>{const{adds:r,avoids:o}=compareRoutes(e,t,{types:countedRouteSectionTypes}),a=countSections(e=>r[e]?.sections.length??0),s=countSections(e=>o[e]?.sections.length??0);if(a||s)return{...a&&{adds:a},...s&&{avoids:s}}})(e,t):void 0;return{index:a,summary:n(r),...s.length>1&&{legSummaries:s.map(e=>n(e.summary))},...i&&{sectionCounts:i},...l.length>0&&{countries:l},...c.length>0&&{via:c},...d>0&&{timeInCurvesPercent:d},...u&&{comparedToBest:u}}})(e,o))}},waypointSummarySchema=z.object({slotIndex:z.number(),isFilled:z.boolean(),address:z.string().optional(),position:z.array(z.number()).length(2).optional().describe("[longitude, latitude]")}),summarizeWaypoint=e=>{if(null==e)return null;const t=getPosition(e);let r;return Array.isArray(e)||(r=e.properties?.address?.freeformAddress),{slotIndex:-1,isFilled:!0,...r&&{address:r},...t&&{position:t}}},isoDate=e=>!e||Number.isNaN(e.getTime())?"":e.toISOString().slice(0,10),toolErrorSchema=z.object({error:z.string(),code:z.enum(sdkErrorCodes).optional().describe("The SDK error code, when a TomTom service or the map failed.")}),analysisIndexSchema=z.object({name:z.string(),outputFormat:z.enum(["json","chart"]),timestamp:z.number(),description:z.string().optional()}),byodDataProfileSchema=z.object({featureCount:z.number(),geometryTypes:z.array(z.string()).describe("Geometry types present (Point / LineString / Polygon / …)."),properties:z.array(z.object({name:z.string(),types:z.array(z.string()).describe('Distinct JSON value types seen for this key (e.g. ["string"], ["number","null"]).'),coverage:z.number().describe("Fraction of features carrying this key (1 = every feature)."),examples:z.array(z.union([z.string(),z.number(),z.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:z.number().optional().describe("Count of property keys omitted when the data has more distinct keys than the cap.")}),resolvedAreasOutputSchema=z.array(z.object({query:z.string().optional().describe("Your input query text, echoed."),matched:z.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).'),basePlaceShape={id:z.string().describe("Stable place feature ID; pass as `{ placeIdOrEntryId }` to setRoute / addWaypointsToRoute / etc."),type:z.enum(placeTypes).optional().describe("Place classification (POI, Street, Geography, Cross Street, …), when known."),name:z.string().optional().describe("POI name if available"),address:z.string().optional().describe("Human-readable address"),position:z.array(z.number()).length(2).describe("[longitude, latitude]")},buildPlaceOutputSchema=e=>z.object(basePlaceShape).describe("Compact place summary"),buildPlacesOutputSchema=e=>z.object({count:z.number(),features:z.array(buildPlaceOutputSchema())}).describe("Compact places summary"),placeOutputSchema=buildPlaceOutputSchema(),summarySchema$1=(buildPlacesOutputSchema(),z.object({travelTimeInSeconds:z.number(),lengthInMeters:z.number(),trafficDelayInSeconds:z.number().optional(),trafficLengthInMeters:z.number().optional(),departureTime:z.string().optional(),arrivalTime:z.string().optional(),noTrafficTravelTimeInSeconds:z.number().optional(),historicTrafficTravelTimeInSeconds:z.number().optional(),liveTrafficIncidentsTravelTimeInSeconds:z.number().optional()})),sectionCountsSchema=z.partialRecord(z.enum(countedRouteSectionTypes),z.number()),summarizedRouteSchema=z.object({index:z.number().describe("0=best, 1+=alternative"),summary:summarySchema$1,legSummaries:z.array(summarySchema$1).optional().describe("Present when route has multiple legs"),sectionCounts:sectionCountsSchema.optional().describe("Sections per type, only the types present: tollRoad (costs money by any scheme), toll (per-use toll), ferry, carTrain, traffic (incidents on the route), tunnel, unpaved, lowEmissionZone, vehicleRestricted. Absent = none of them."),countries:z.array(z.string()).optional().describe("ISO 3166-1 alpha-3 codes of the countries crossed, in order"),via:z.array(z.string()).optional().describe("Main roads taken, in order; with alternatives, only those on the stretches this route alone drives"),timeInCurvesPercent:z.number().optional().describe("% of the travel time in curves, junction turns included: how winding. Absent = under 0.5%"),comparedToBest:z.object({adds:sectionCountsSchema.optional(),avoids:sectionCountsSchema.optional()}).optional().describe("Alternatives only: the sections this route takes on (`adds`) and stays clear of (`avoids`) against route 0. Absent = same")}),routesOutputSchema=z.object({count:z.number(),routes:z.array(summarizedRouteSchema)}),routesWriteOutputSchema=routesOutputSchema.extend({entryId:z.string().describe("Routing entry id the route was written to — pass as `routesEntryIDs` to analyseData / processData.")}),calculateBBoxSchema=z.object({geoJson:z.union([z.record(z.string(),z.unknown()),z.array(z.record(z.string(),z.unknown()))]).describe("GeoJSON object or array of GeoJSON objects")}),calculateBBoxOutputSchema=z.union([z.object({bbox:z.array(z.number()).length(4).describe("[west, south, east, north]; west > east crosses the antimeridian"),minLng:z.number(),minLat:z.number(),maxLng:z.number(),maxLat:z.number()}),toolErrorSchema]),questionSchema=z.object({id:z.string().describe('Short stable key for the answer, e.g. "travelMode" or "radius".'),question:z.string().describe("The question to put to the user."),options:z.array(z.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:z.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.')}),clarifyIntentSchema=z.object({intro:z.string().optional().describe("One short line of context for why you need to ask."),questions:z.array(questionSchema).min(1).max(4).describe("1-4 questions that resolve the ambiguity.")}),clarifyIntentOutputSchema=z.object({status:z.literal("awaiting_user_response"),intro:z.string().optional(),questions:z.array(questionSchema)}),CLARIFY_FORM_PROMPT='- 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.',CLARIFY_PROSE_PROMPT='- 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.',clarifyIntentExamples=['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 }] })'],clarifyIntentExamplePrompts=["Plan a route for me","Help me decide between these options"],executeClarifyIntent=async(e,t)=>({status:"awaiting_user_response",intro:e.intro,questions:e.questions}),createClarifyIntentTool=(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?CLARIFY_FORM_PROMPT:CLARIFY_PROSE_PROMPT}`,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:clarifyIntentSchema,outputSchema:clarifyIntentOutputSchema,execute:executeClarifyIntent,tags:["utilities"],examples:e.examples??clarifyIntentExamples,examplePrompts:e.examplePrompts??clarifyIntentExamplePrompts,alwaysActive:e.alwaysActive??!0,endsTurnOnCall:e.rendersForm??!1}),getCurrentLocationSchema=z.object({}),getCurrentLocationOutputSchema=z.union([z.object({position:z.array(z.number()).min(2).max(2).describe("[longitude, latitude]"),accuracy:z.number().describe("Accuracy of the location in meters"),timestamp:z.number().optional().describe("Timestamp of the location data")}),toolErrorSchema]),roundCoordinate=e=>Math.round(1e3*e)/1e3,createGetCurrentLocationTool=(e={})=>({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:getCurrentLocationSchema,outputSchema:getCurrentLocationOutputSchema,execute:()=>(async e=>{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{return(({coords:e,timestamp:t},r)=>r?{position:[e.longitude,e.latitude],accuracy:e.accuracy,timestamp:t}:{position:[roundCoordinate(e.longitude),roundCoordinate(e.latitude)],accuracy:Math.max(Math.round(e.accuracy),111)})(await(e=>new Promise((t,r)=>{globalThis.navigator.geolocation.getCurrentPosition(t,r,{enableHighAccuracy:e,timeout:15e3,maximumAge:e?0:6e4})}))(e),e)}catch(t){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})(t);return e?{error:e}:toToolError("Failed to get current location",t)}})(e.fullPrecision??!1),tags:["utilities","location"],examples:["getCurrentLocation()"],examplePrompts:["Where am I?","Get my GPS location","Use my current position"],relatedTools:["discoverPlaces","reverseGeocode","locatePlace"]}),TOOL_TAGS=["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"],detailToolSchema=z.object({name:z.string(),description:z.string(),examplePrompts:z.array(z.string()),relatedTools:z.array(z.string()).optional()}),helpOutputSchema=z.union([z.object({mode:z.literal("summary"),examplePrompts:z.array(z.string())}),z.object({mode:z.literal("detail"),query:z.string().optional(),tag:z.enum(TOOL_TAGS).optional(),matchedTools:z.array(detailToolSchema),totalMatched:z.number()}),toolErrorSchema]),helpSchema=z.object({mode:z.enum(["summary","detail"]).describe('"summary": overview of all capabilities with example prompts mixed from all tools. "detail": searchable full tool info — use when you need to find a specific tool.'),query:z.string().optional().describe("Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags."),tag:z.enum(TOOL_TAGS).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),helpDescription='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.',rejectingUnknownKeys=e=>{if(!(e instanceof z.ZodObject))return e;const t=z.globalRegistry.get(e),r=e.strict();return t?r.meta(t):r},toAiTool=(e,t,r,o,a)=>({description:t.description,inputSchema:rejectingUnknownKeys(t.inputSchema),outputSchema:o?t.outputSchema:void 0,execute:a?async(o,s)=>{const n=Date.now();try{const i=await t.execute(o,r,{signal:s?.abortSignal}),l=(e=>{const t=e?.error;return"string"==typeof t?t:void 0})(i);return a({toolName:e,durationMs:Date.now()-n,isError:void 0!==l,errorMessage:l}),i}catch(i){throw a({toolName:e,durationMs:Date.now()-n,isError:!0,errorMessage:i instanceof Error?i.message:String(i)}),i}}:(e,o)=>t.execute(e,r,{signal:o?.abortSignal})}),toMetadata=(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}),materializeEntry=(e,t)=>"function"==typeof e?e(t):e,geometriesIdSchema=z.discriminatedUnion("kind",[z.object({kind:z.literal("place").describe('Use the exact literal "place".'),id:z.string().min(1).describe("Id of a single place; resolves to that place’s footprint.")}).describe("A single place footprint."),z.object({kind:z.literal("places").describe('Use the exact literal "places".'),id:z.string().min(1).describe("Id of a places-entry; resolves to every footprint in the entry.")}).describe("Every footprint in a places entry."),z.object({kind:z.literal("ranges").describe('Use the exact literal "ranges".'),id:z.string().min(1).describe("Id of a ranges-entry; resolves to every isochrone polygon in the entry.")}).describe("Every isochrone polygon in a ranges entry."),z.object({kind:z.literal("customGeometries").describe('Use the exact literal "customGeometries".'),id:z.string().min(1).describe("Id of a custom-geometries entry produced by a previous `processData` call.")}).describe("A derived entry produced by a previous `processData` call.")]).describe('Tagged id `{ kind, id }`. `kind` must be exactly one of: "place" | "places" | "ranges" | "customGeometries" (do NOT use variants like "ranges-entry", "place-footprints", etc.). Use `recallState` to list every available `{kind, id}` pair.'),geometriesEntryIDsSchema=z.array(geometriesIdSchema).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.'),GEOMETRIES_PROPS_DOC="`geometry.properties` shape varies by source — place footprints: the geometry data service's own, with no place fields (match `feature.id` to `place.properties.dataSources.geometry.id`); 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.",GEOMETRIES_SKIPPED_DESC="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",GEOMETRIES_SOURCE_IDS_DESC="Tagged input ids that contributed at least one polygon to the run, in input order.",GEOMETRIES_SCHEMA_DOC="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)`.",buildPlacesSchemaDoc=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", "FUEL_STATION", "HOTEL_OR_MOTEL", "CHARGING_LOCATION"\n localizedCategories: string[]; // English, whatever the request language\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.',PLACES_SCHEMA_DOC='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", "FUEL_STATION", "HOTEL_OR_MOTEL", "CHARGING_LOCATION"\n localizedCategories: string[]; // English, whatever the request language\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.',ROUTES_SCHEMA_DOC='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 liveTrafficIncidentsTravelTimeInSeconds?: number;\n trafficDelayInSeconds?: number; // delay of the traffic events along trafficLengthInMeters, NOT the gap to free-flow\n trafficLengthInMeters?: number;\n // gap to free-flow: Math.max(0, liveTrafficIncidentsTravelTimeInSeconds - noTrafficTravelTimeInSeconds)\n departureTime: Date; arrivalTime: Date;\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\n distinct?: Array<{ id; startPointIndex; endPointIndex; lengthInMeters: number }>; // stretches no other route of the response drives: what sets an alternative apart; on every route when there are 2+, [] if none\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; // the tail of a traffic incident id, not that id: match id.endsWith(eventId)\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 curve?: Array<{ id; startPointIndex; endPointIndex; headingChangeInDegrees /* + = right */; radiusInMeters; lengthInMeters; curvinessScore; severity /* 0-1 */ }>; // junction turns too\n curvyStretch?: Array<{ id; startPointIndex; endPointIndex; curveCount; hairpinCount; lengthInMeters; minRadiusInMeters; curvinessScore }>;\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 }; 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`.',isLongitude=e=>Number.isFinite(e)&&e>=-180&&e<=180,isLatitude=e=>Number.isFinite(e)&&e>=-90&&e<=90,POSITION_RANGE_MESSAGE="Position must be [lng, lat] with lng in [-180, 180] and lat in [-90, 90].",positionSchema=z.array(z.number()).length(2).refine(([e,t])=>isLongitude(e)&&isLatitude(t),{message:POSITION_RANGE_MESSAGE}),geoJsonPositionSchema=z.array(z.number()).min(2).max(3).refine(([e,t])=>isLongitude(e)&&isLatitude(t),{message:POSITION_RANGE_MESSAGE}),geoJsonBBoxSchema=z.array(z.number()).length(4).refine(([e,t,r,o])=>isLongitude(e)&&isLatitude(t)&&isLongitude(r)&&isLatitude(o),{message:"BBox values out of range — lng must be in [-180, 180] and lat in [-90, 90]."}).refine(([,e,,t])=>e<=t,{message:"BBox south must be <= north."}).describe("[west, south, east, north]; west > east crosses the antimeridian. lng in [-180, 180], lat in [-90, 90]."),polygonInputSchema=z.object({type:z.literal("Polygon"),coordinates:z.array(z.array(geoJsonPositionSchema))}),multiPolygonInputSchema=z.object({type:z.literal("MultiPolygon"),coordinates:z.array(z.array(z.array(geoJsonPositionSchema)))}),geometryInputSchema=z.union([polygonInputSchema,multiPolygonInputSchema]),placesEntryIDsSchema=({verb:e,extra:t})=>z.array(z.string()).min(1).optional().describe(`IDs of existing places entries to ${e} (e.g. ["places-2"], ["places-1", "places-2"]). Use recallState to list IDs. Omit to ${e} the latest entry.`+(t?` ${t}`:"")),routesEntryIDsSchema=({verb:e,extra:t})=>z.array(z.string()).min(1).optional().describe(`IDs of existing routes entries to ${e} (e.g. ["routes-0"], ["routes-0", "routes-1"]). Use recallState to list IDs. Omit to ${e} the latest route.`+(t?` ${t}`:"")),placesEntryIdHintSchema=z.string().regex(/^[a-z0-9][a-z0-9-]{1,48}$/,"Use lowercase letters, digits, and hyphens (2–49 chars).").optional().describe('Compact lowercase kebab-case id derived from the user prompt (e.g. "vegan-cafes-amsterdam", "ev-stations-along-route"). Avoid generic names like "places" / "results". Auto-suffixed on collision — do not track uniqueness yourself. Default: `places-N`.'),showModeSchema=z.enum(["add","replace"]).optional().describe('WITHIN THE TARGETED ENTRY ONLY — controls how the new polygons combine with what is already rendered on this entry\'s GeometriesModule layer. Does NOT affect other entries (use `hidePreviousEntries` for that). "replace" (default) clears this entry\'s layer first; "add" stacks (deduped by feature id).'),geometryFillStyleSchema=z.enum(geometryFillStyles),showPlaceGeometriesSchema=z.object({show:z.boolean().optional().describe("Also render the polygons on the map. Default: false."),mode:showModeSchema,fillStyle:geometryFillStyleSchema.optional().describe('How the polygons are filled. 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).'),geometryDisplayConfigSchema=z.object({fillStyle:geometryFillStyleSchema.optional().describe('How the polygons are filled. Default: "outline".'),placeIds:z.array(z.string()).optional().describe("Subset of place ids within the targeted entries — render boundaries only for these. Ids outside the entries are ignored. Default: all places with a geometry source."),mode:showModeSchema}).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.'),markerTypeSchema=z.enum([...placesMarkerTypes,"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.'),hidePreviousEntriesSchema=e=>z.union([z.literal("all"),z.array(z.string())]).optional().describe(`Previously-shown ${e} entries to hide first: \`"all"\` replaces them (the usual "show X"), an id array hides only those. Omit to stack ("also show X", "and X"). Ignored in \`single\` entryMode, which hides the others anyway.`),showPlacesSchema=z.object({markerType:markerTypeSchema,zoomMode:z.enum(["auto","none"]).describe('Camera behaviour. "none" keeps the map still.'),hidePreviousEntries:hidePreviousEntriesSchema("places")}),shownSchema=z.object({markerType:z.boolean(),zoomMode:z.boolean()}),showByodSchema=z.object({zoomMode:z.enum(["auto","none"]).describe('Camera behaviour. "auto" fits the map to the shown BYOD entries; "none" keeps the map still.'),hidePreviousEntries:hidePreviousEntriesSchema("BYOD")}),shownByodSchema=z.object({zoomMode:z.boolean().describe("Whether the camera was moved to fit the shown entries.")}),withinSharedFields={mode:z.literal("within"),viewport:z.literal(!0).optional().describe("When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.")},queryAsSchema=z.enum(["poi","place"]).optional().describe('Drives query resolution. `"poi"` for venues / landmarks / businesses (e.g. "Eiffel Tower"). `"place"` for addresses, cities, neighborhoods, parks, postal codes (e.g. "De Jordaan, Amsterdam", "1600 Pennsylvania Ave"). Default: "place".'),locateWithinSingularKeys=["boundingBox","viewport","query","placeId"],withinWhereSchema=z.object({...withinSharedFields,boundingBox:geoJsonBBoxSchema.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId."),query:z.string().optional().describe('Resolve a name/address/POI → its boundary polygon (or bbox) as search area (e.g. "Paris", "De Jordaan, Amsterdam"). Pair with `queryAs` to disambiguate. EXCLUSIVE with boundingBox / viewport / placeId.'),queryAs:queryAsSchema,placeId:z.string().optional().describe("ID of a session place — its boundary polygon (fetched on demand) is the search area. EXCLUSIVE with boundingBox / viewport / query.")}).refine(e=>1===locateWithinSingularKeys.filter(t=>void 0!==e[t]).length,{message:"`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId."}),nearbySingularKeys=["position","viewport","query"],nearbyWhereSchema=z.object({mode:z.literal("nearby"),position:positionSchema.optional().describe("Bias point [lng, lat]. EXCLUSIVE with viewport / query."),viewport:z.literal(!0).optional().describe("When true, bias toward the current map viewport centre. EXCLUSIVE with position / query."),query:z.string().optional().describe("Resolve a name/address/POI → its centroid as bias point. Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport."),queryAs:queryAsSchema,radiusMeters:z.number().positive().optional().describe("Hard radius cap (metres) around the bias point. Independent of bias source.")}).refine(e=>1===nearbySingularKeys.filter(t=>void 0!==e[t]).length,{message:"`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent)."}),globalWhereSchema=z.object({mode:z.literal("global").describe("No spatial constraint — resolve the name anywhere. The default for a uniquely-named place.")}),whereSchema=z.union([withinWhereSchema,nearbyWhereSchema,globalWhereSchema]),sharedWithinFields={...withinSharedFields,queries:z.array(z.object({query:z.string().describe('Name of a CONTAINING area to search within (e.g. "Paris", "De Jordaan, Amsterdam"). NEVER the user\'s search subject — that goes in the top-level `query`.'),queryAs:queryAsSchema})).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:z.array(z.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:z.array(geometryInputSchema).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:z.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:z.object({routeId:z.string().min(1).optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),widthMeters:z.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. EXCLUSIVE with viewport; composes with other multi-region fields.")},areaWhereSchema=z.object({viewport:z.boolean().optional(),boundingBox:geoJsonBBoxSchema.optional(),queries:sharedWithinFields.queries,placeIds:sharedWithinFields.placeIds,geometries:sharedWithinFields.geometries,route:sharedWithinFields.route}),ALL_ENTRY_DATA_KINDS=["places","routes","incidents","customGeometries","trafficAreaAnalytics","byod"],buildEntryKindScopeSchema=e=>z.object({kinds:z.array(z.enum(e)).min(1)}),ENTRY_KIND_META={places:{fieldName:"placesEntryIDs",fieldLabel:"`placesEntryIDs`",buildField:e=>placesEntryIDsSchema({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:PLACES_SCHEMA_DOC,recallTool:"recallState({ kind: 'places' })"},routes:{fieldName:"routesEntryIDs",fieldLabel:"`routesEntryIDs`",buildField:e=>routesEntryIDsSchema({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:ROUTES_SCHEMA_DOC,recallTool:"recallState({ kind: 'routes' })"},incidents:{fieldName:"incidentsEntryIDs",fieldLabel:"`incidentsEntryIDs`",buildField:e=>z.array(z.string()).min(1).optional().describe(`IDs of existing incidents entries to ${e.verb} (e.g. ["incidents-0"]). Injected as \`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=>geometriesEntryIDsSchema.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:GEOMETRIES_SCHEMA_DOC,recallTool:"recallState({ kind: 'geometries' })"},trafficAreaAnalytics:{fieldName:"trafficAreaAnalyticsEntryIDs",fieldLabel:"`trafficAreaAnalyticsEntryIDs`",buildField:e=>z.array(z.string()).min(1).optional().describe(`IDs of existing traffic-area-analytics entries to ${e.verb} (e.g. ["tta-0"]). Injected as \`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; level: number; // name only when the request names the region\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 per period, only periods with data; every label is UTC\n daily?: Array<{ date: Date; …metrics }>; // midnight UTC: read with toISOString()\n hourly?: Array<{ date: Date; hour: number; …metrics }>; // hour 0-23\n average?: Array<{ day: 1|2|3|4|5|6|7; hour: number; …metrics }>; // average week, 1 = Monday\n yearly?, monthly?, weekly?: …;\n };\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`). Shift a UTC `day`/`hour` by the area\'s UTC offset to report local time (Amsterdam summer, UTC+2: `{ day: 1, hour: 5 }` is Monday 07:00).',recallTool:"recallState({ kind: 'trafficAreaAnalytics' })"},byod:{fieldName:"byodEntryIDs",fieldLabel:"`byodEntryIDs`",buildField:e=>z.array(z.string()).min(1).optional().describe(`IDs of existing BYOD entries to ${e.verb} (e.g. ["byod-0"]) — customer-authored GeoJSON layers. Injected as \`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' })"}},resolveActiveKinds=(e,t)=>ALL_ENTRY_DATA_KINDS.filter(r=>e.includes(r)&&(!t||t.kinds.includes(r))),isKindActive=(e,t,r)=>e.includes(r)&&(!t||t.kinds.includes(r)),buildEntryFieldList=e=>e.map(e=>ENTRY_KIND_META[e].fieldLabel).join(" / "),buildEntryKindSandboxDocs=e=>ALL_ENTRY_DATA_KINDS.filter(t=>e.includes(t)).map(e=>ENTRY_KIND_META[e].sandboxDoc).join("\n"),buildEntryKindSchemaDocs=e=>ALL_ENTRY_DATA_KINDS.filter(t=>e.includes(t)).map(e=>ENTRY_KIND_META[e].schemaDoc).filter(e=>e.length>0).join("\n\n"),buildEntryIdFields=(e,t)=>{const r={};for(const o of ALL_ENTRY_DATA_KINDS){if(!e.includes(o))continue;const a=ENTRY_KIND_META[o];r[a.fieldName]=a.buildField(t)}return r},hasAnyEntryIds=e=>{for(const t of ALL_ENTRY_DATA_KINDS)if(e[ENTRY_KIND_META[t].fieldName]?.length)return!0;return!1},buildAtLeastOneEntryIdMessage=e=>`At least one of ${buildEntryFieldList(e)} must be set. Call ${Array.from(new Set(e.map(e=>ENTRY_KIND_META[e].recallTool))).map(e=>`\`${e}\``).join(" / ")} first to list available ids.`,fitOnMapInputSchema=z.union([geoJsonBBoxSchema,z.object({bbox:geoJsonBBoxSchema,padding:z.number().optional(),animate:z.boolean().optional()})]).describe("Bounds to fit the camera to: a `[W, S, E, N]` BBox tuple, or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`. lng in [-180, 180], lat in [-90, 90]. Default padding: 50px."),fittedReportSchema=z.object({bbox:z.tuple([z.number(),z.number(),z.number(),z.number()]).describe("Bounds the camera was fit to."),padding:z.number().describe("Padding (px) applied around the bbox.")}),FIT_ON_MAP_DOC="`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)`.",isBBoxTuple=e=>geoJsonBBoxSchema.safeParse(e).success,isFitOnMapInput=e=>{if(isBBoxTuple(e))return!0;if(!e||"object"!=typeof e)return!1;const t=e;return!(!isBBoxTuple(t.bbox)||!(void 0===t.padding||"number"==typeof t.padding&&Number.isFinite(t.padding))||void 0!==t.animate&&"boolean"!=typeof t.animate)},applyFitOnMap=(e,t)=>{const r=isBBoxTuple(t)?t:t.bbox,o=(isBBoxTuple(t)?void 0:t.padding)??50,a=isBBoxTuple(t)?void 0:t.animate;return fitMapToBBox(e.baseMap.mapLibreMap,r,{padding:o,...void 0!==a&&{animate:a}}),{bbox:r,padding:o}},AREA_GEOGRAPHY_TYPES=["CountrySubdivision","CountrySecondarySubdivision","Municipality","MunicipalitySubdivision","Neighbourhood"],geocodeAreas=async(e,t={},r)=>{const o=withAgentToolkitHeaders({query:e,limit:t.limit??6,filters:{geographyTypes:AREA_GEOGRAPHY_TYPES},...t.bias?{geoBias:{position:t.bias}}:{},signal:r?.signal});return(await geocode(o)).features},hidePreviousShownEntries=async(e,t,r)=>{if("multiple"!==e.entryMode||void 0===r)return;const o=new Set(t),a="all"===r?[...e.shownEntryIds].filter(e=>!o.has(e)):r.filter(e=>!o.has(e));for(const s of a)await e.hideEntry(s)},locatePlaces=async(e,t,r,o)=>{const a="poi"===t?discoverPlaces:geocode,{limit:s,bias:n}=r;return(await a(withAgentToolkitHeaders({query:e,limit:s,...n&&{geoBias:n},signal:o?.signal}))).features},locatePlace=async(e,t,r,o)=>{const[a]=await locatePlaces(e,t,{limit:1,bias:r},o);return a??null},locationInputSchema=z.union([z.object({query:z.string().describe("Location string to resolve."),queryAs:z.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.')}),z.object({position:positionSchema.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].")}),z.object({placeIdOrEntryId:z.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).")})]),labelForLocationInput=e=>"query"in e?`"${e.query}"`:"placeIdOrEntryId"in e?`placeIdOrEntryId "${e.placeIdOrEntryId}"`:JSON.stringify(e.position),routeUtils={calculateProgressAtRoutePoint:calculateProgressAtRoutePoint,compareRoutes:compareRoutes,getCoordinateAtRouteProgress:getCoordinateAtRouteProgress,getImportantRoads:getImportantRoads,getProgressAtNearestRoutePoint:getProgressAtNearestRoutePoint,getRouteProgressBetween:getRouteProgressBetween,getRouteProgressForSection:getRouteProgressForSection,getSectionBBox:getSectionBBox,getSectionsWithin:getSectionsWithin,isHairpin:isHairpin},fetchPlaceBoundary=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: ${errorMessageOf(r)}`}}},skippedSourceSchema=z.object({source:geometriesIdSchema,reason:z.string()}),formatSkipReason=e=>`${e.source.kind}:${e.source.id}: ${e.reason}`,collectGeometryBearingPlaceIds=(e,t,r)=>{let o=0;for(const a of e)a.properties.dataSources?.geometry?.id&&"string"==typeof a.id&&(o++,r.placeIdToEntry.has(a.id)||r.placeIdToEntry.set(a.id,t),r.seen.has(a.id)||(r.seen.add(a.id),r.placeIds.push(a.id)));return o},tagFeatureSource=(e,t)=>({...e,properties:{...e.properties??{},_source:t}}),resolvePlaceGeometry=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."};const o=await fetchPlaceBoundary(t,r.place);return"skip"in o?{kind:"skip",reason:o.skip}:{kind:"success",place:r.place,feature:o.feature,ownerEntryId:r.entryId}},collectInputGeometries=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),o=((e,t)=>{const r=[],o=/* @__PURE__ */new Map,a=[],s=/* @__PURE__ */new Set;for(const n of e){const e=t.places.entries.find(e=>e.id===n.id);e?0===collectGeometryBearingPlaceIds(e.data,n.id,{placeIds:r,placeIdToEntry:o,seen:s})&&a.push({source:n,reason:"Places entry has no places with a geometry data source (e.g. addresses and streets often lack one)."}):a.push({source:n,reason:"No places entry with this id in session state. Use `recallState` to list available ids."})}return{placeIds:r,placeIdToEntry:o,skipped:a}})(r.places,t),a=[...o.skipped],s=/* @__PURE__ */new Set,n=[],i=/* @__PURE__ */new Map;for(const f of r.place)s.has(f.id)||(s.add(f.id),n.push(f.id),i.set(f.id,f));for(const f of o.placeIds)s.has(f)||(s.add(f),n.push(f));const l=[],c=[],d=/* @__PURE__ */new Set,u=/* @__PURE__ */new Set,h=/* @__PURE__ */new Set;for(const f of n){const e=await resolvePlaceGeometry(f,t),r=i.get(f),s=o.placeIdToEntry.get(f);if("skip"===e.kind){const t=r??{kind:"place",id:f};a.push({source:t,reason:e.reason});continue}const n=r??(s?{kind:"places",id:s}:{kind:"place",id:f});l.push(tagFeatureSource(e.feature,n)),c.push(e.place),d.add(e.ownerEntryId),r&&u.add(r.id),s&&h.add(s)}const p=((e,t)=>{const r=[],o=/* @__PURE__ */new Set,a=[];for(const s of e){const e=t.ranges.entries.find(e=>e.id===s.id);if(!e){a.push({source:s,reason:"No ranges entry with this id in session state."});continue}const n=e.data.flatMap(e=>e.polygon?.features??[]);if(0!==n.length){for(const e of n)r.push(tagFeatureSource(e,s));o.add(s.id)}else a.push({source:s,reason:"Ranges entry has no polygons — recompute via findReachableAreas first."})}return{features:r,contributingIds:o,skipped:a}})(r.ranges,t);l.push(...p.features),a.push(...p.skipped);const y=((e,t)=>{const r=[],o=/* @__PURE__ */new Set,a=[];for(const s of e){const e=t.customGeometries.findById(s.id);if(e)if(0!==e.data.length){for(const t of e.data)r.push(tagFeatureSource(t,s));o.add(s.id)}else a.push({source:s,reason:"Custom-geometries entry is empty."});else a.push({source:s,reason:"No custom-geometries entry with this id in session state. Use `recallState` to list available ids."})}return{features:r,contributingIds:o,skipped:a}})(r.customGeometries,t);l.push(...y.features),a.push(...y.skipped);const m=y.contributingIds;return 0===l.length?{error:`No input geometries available. ${a.map(formatSkipReason).join(" | ")}`}:{value:{geometries:l,sourcePlaces:c,affectedEntries:[...[...d].map(e=>({id:e,kind:"places"})),...[...m].map(e=>({id:e,kind:"customGeometries"}))],skipped:a,contributingSourceIds:e.filter(e=>{switch(e.kind){case"place":return u.has(e.id);case"places":return h.has(e.id);case"ranges":return p.contributingIds.has(e.id);case"customGeometries":return m.has(e.id)}})}}},findPlacesByEntry=(e,t)=>{if(!e?.length)return{value:void 0};const r={};for(const o of e){const e=t.places.entries.find(e=>e.id===o);if(!e)return{error:`No places entry found with id "${o}". Use recallState to list available IDs.`};r[o]={type:"FeatureCollection",features:e.data}}return{value:r}},latestTimestamp=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},ID_FIELDS=["placesEntryIDs","routesEntryIDs","incidentsEntryIDs","geometriesEntryIDs","trafficAreaAnalyticsEntryIDs","byodEntryIDs"],prepareMultiInputs=async(e,t)=>{if(!(e=>ID_FIELDS.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=resolveSliceEntries(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 o=resolveSliceEntries(e.routesEntryIDs,t.routing,{missingId:e=>`No routes entry with id "${e}". Use recallState to list available IDs.`});if("error"in o)return o;const a=resolveSliceEntries(e.incidentsEntryIDs,t.trafficIncidents,{missingId:e=>`No incidents entry with id "${e}". Call getTrafficIncidents first.`});if("error"in a)return a;const s=resolveSliceEntries(e.trafficAreaAnalyticsEntryIDs,t.trafficAreaAnalytics,{missingId:e=>`No traffic-area-analytics entry with id "${e}". Call getTrafficAreaAnalytics first.`});if("error"in s)return s;const n=resolveSliceEntries(e.byodEntryIDs,t.byod,{missingId:e=>`No BYOD entry with id "${e}". Use recallState to list available IDs.`});return"error"in n?n:{value:{places:r.value,routes:o.value,incidents:a.value,trafficAreaAnalytics:s.value,byod:n.value}}})(e,t);if("error"in r)return r;let o,a={affectedEntries:[],sourceIds:[],skipped:[]};if(e.geometriesEntryIDs?.length){const r=await collectInputGeometries(e.geometriesEntryIDs,t);if("error"in r)return r;o=r.value.geometries,a={affectedEntries:r.value.affectedEntries,sourceIds:r.value.contributingSourceIds,skipped:r.value.skipped}}return{value:{resolved:r.value,sandbox:buildSandboxInputs(r.value,o),geometries:a}}},buildSandboxInputs=(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:partitionGeometriesBySource(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}),partitionGeometriesBySource=e=>{if(!e?.length)return;const t={};for(const r of e){const e=r.properties?._source,o=e?`${e.kind}:${e.id}`:"unknown",a=t[o]??[];a.push(r),t[o]=a}return t},resolveSliceEntries=(e,t,r)=>{if(!e?.length)return{value:[]};const o=[];for(const a of e){const e=t.entries.find(e=>e.id===a);if(!e)return{error:r.missingId(a)};o.push(e)}return{value:o}},BY_ENTRY_VIEWS_DOC='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 }`).',MULTI_INPUT_SANDBOX_PARAMS=["placesByEntry","routesByEntry","incidentsByEntry","geometriesByEntry","trafficAreaAnalyticsByEntry","byodByEntry","h3","turf","routeUtils","cluster"],buildSandboxToolsDoc=(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/.':"",o=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.\n• `getSectionsWithin(route, { within?: spans, types? })` → `{ [type]: { sections, shares, lengthInMeters } }` — sections overlapping the spans (e.g. one `country` section); `shares[i]` = the part of `sections[i]` inside, to prorate `delayInSeconds`.\n• `getImportantRoads(route, { within?, minLengthInMeters? })` → `[{ name, roadNumbers, lengthInMeters, sections }]` in driving order.\n• `compareRoutes(route, reference, { types? })` → `{ distinct, referenceDistinct, adds, avoids }` (`adds` / `avoids` shaped like getSectionsWithin).\n• `isHairpin(curve)` — 140°+ on a radius of 40 m or less.":"",a=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`${buildSandboxCodePrompt(MULTI_INPUT_SANDBOX_PARAMS)}\n\n${BY_ENTRY_VIEWS_DOC}${r}${o}${a}\n\n`},packSandboxArgs=(e,t)=>[e.placesByEntry,e.routesByEntry,e.incidentsByEntry,e.geometriesByEntry,e.trafficAreaAnalyticsByEntry,e.byodByEntry,t.h3,t.turf,routeUtils,clusterIncidents],getRangePolygons=(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 o=r.data.map(e=>e.polygon?.features[0]?.geometry).filter(e=>!!e);return 0===o.length?{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`}:{polygons:o}},scopableToolNames=e=>Object.entries(e).filter(([,e])=>e.scopePrompt).map(([e])=>e),looseScopeShape=z.object({kinds:z.array(z.string()).min(1).describe("The kinds the turn touches, from those the tool's SCOPE hint lists.")}),addUsage=(e,t)=>{t&&(e.inputTokens+=t.inputTokens??0,e.outputTokens+=t.outputTokens??0,e.totalTokens+=t.totalTokens??0)},extractPartialTools=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}}},compactSuccess=(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}},classifyUserIntent=async(e,t)=>{const{chatModel:r,toolsMetadata:o}=t,a=performance.now(),{tools:s,toolScopes:n,usage:i}=await(async(e,t,r)=>{const o=new Set(scopableToolNames(r)),a=(e=>{const t=z.object({tools:z.array(z.string()).min(1).describe("Tool names needed to fulfill the query.")});if(0===e.size)return t;const r={};for(const a of e)r[a]=looseScopeShape.nullable();const o=z.object(r).nullable().describe("Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in `tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.");return t.extend({toolScopes:o}).superRefine((t,r)=>{const o=t.toolScopes??{};for(const a of t.tools)e.has(a)&&null==o[a]&&r.addIssue({code:"custom",path:["toolScopes",a],message:`Tool "${a}" requires \`toolScopes.${a}\` — emit it per the SCOPE hint (don't leave null when picking).`})})})(o),s={inputTokens:0,outputTokens:0,totalTokens:0},n=(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),i=`${(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=scopableToolNames(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")})(r)}\n\nReturn JSON matching this shape exactly:\n${n}`;let l;for(let d=0;d<2;d++)try{const r=await generateText({model:t,output:Output.json(),instructions:i,messages:e,maxOutputTokens:400});addUsage(s,r.usage);const o=a.safeParse(r.output);if(o.success)return compactSuccess(o.data,s);l=extractPartialTools(r.output)??l}catch(c){const e=c;addUsage(s,e.usage),l=extractPartialTools(e.value)??l}if(!l)throw new Error("Classifier failed after 2 attempts to produce a valid response.");return{tools:l.tools,...l.scopes&&{toolScopes:l.scopes},usage:s}})(e,r,o);return{activeToolNames:s,...n&&{toolScopes:n},timeMs:performance.now()-a,usage:i}},isTextPart=e=>"text"===e.type,extractMessageText=e=>{if("string"==typeof e)return e;const t=e.filter(isTextPart);return t.length>0?t.map(e=>e.text).join(""):null},extractLastUserText=e=>{for(let t=e.length-1;t>=0;t--)if("user"===e[t].role)return extractMessageText(e[t].content);return null},createDefaultClassifier=e=>{const{model:t,maxHistoryMessages:r,maxHistoryMessageLength:o}=e;return async e=>{const{messages:a,toolsMetadata:s}=e;if(!a?.length)return null;const n=r??8,i=o??300;let l=-1,c=null;for(let t=a.length-1;t>=0;t--)if("user"===a[t].role){l=t,c=extractMessageText(a[t].content);break}if(!c)return null;const d=[];for(let t=Math.max(0,l-n);t<l;t++){const e=a[t];if("user"!==e.role&&"assistant"!==e.role)continue;const r=extractMessageText(e.content);if(!r)continue;const o=r.length>i?r.slice(0,i)+"…":r;d.push({role:e.role,content:o})}d.push({role:"user",content:c});try{return await classifyUserIntent(d,{chatModel:t,toolsMetadata:s})}catch{return null}}},waypointAddress=e=>{if(!Array.isArray(e))return e.properties?.address?.freeformAddress},makePlacesLabel=(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 o=`${e} ${1===e?"place":"places"}`;return r.length?`${r.join(" ")} (${o})`:o})(e.features.length,t);const r=e.properties?.poi?.name,o=e.properties?.address?.freeformAddress;return r&&o?`${r}, ${o}`:r||o||(t?.query?t.query:"Place")},placeAreaLabel=e=>{const t=e.properties?.address?.freeformAddress;if(!t)return;const r=e.properties?.address?.countryCode;if(!r)return t;const o=`, ${r}`;return t.toLowerCase().endsWith(o.toLowerCase())?t:`${t}${o}`},makeRangesLabel=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${budgetUnits[e.type]}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`;function resolveWhere(e,t,r){switch(e.kind){case"within":return resolveWithin(t,r);case"nearby":return resolveNearby(t,r);case"global":return resolveGlobal()}}var metadata,resolveWithin=(e,t)=>gatherCandidates(gatherInputs(e,t),t),resolveNearby=async(e,t)=>{const r=gatherInputs(e,t),o=r.mapViewport.center;if(!r.queries?.length)return r.useViewport&&o?{position:o}:{};const a=await geocodeQueryCandidates(r,t,o);if(0===a.length)return{};const s=getPosition(a[0]);if(!s)return{};const n=placeAreaLabel(a[0]);return{position:s,query:r.queries[0].query,...n&&{label:n}}},resolveGlobal=()=>({}),isResolveError=e=>"object"==typeof e&&null!==e&&"error"in e,gatherInputs=(e,t)=>{const{viewport:r,boundingBox:o,queries:a,placeIds:s,geometries:n,route:i}=e;return{useViewport:r,boundingBox:o,queries:a,placeIds:s,geometries:n,route:i,mapViewport:t.viewport()}},gatherCandidates=async(e,t)=>{const{useViewport:r,boundingBox:o,queries:a,placeIds:s,geometries:n,route:i,mapViewport:l}=e,c=[];o&&c.push({bbox:o,source:"boundingBox"});const d=await resolveQueryCandidates(a,t,l);if(isResolveError(d))return d;c.push(...d);const u=await resolvePlaceIdCandidates(s,t);if(isResolveError(u))return u;if(c.push(...u,...resolveGeometryCandidates(n)),i){const e=await resolveRouteCandidates(i,t);if(isResolveError(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},resolveQueryCandidates=async(e,t,r)=>{const o=[];for(const{query:a}of e??[]){const e=(await t.geocodeAreas(a,void 0,r.center)).filter(e=>e.bbox);if(0===e.length)return{error:`Could not resolve "${a}" to an area. Provide a boundingBox or a more specific place.`};const s=(r.center?rerankWithNearestCandidate(e,r.center):e)[0],n=placeAreaLabel(s),i=s.properties.dataSources?.geometry?.id?await t.fetchGeometry(s):void 0;o.push(i?{bbox:i.bbox??bboxFromGeoJSON(i),polygon:i.geometry,label:n,source:"query",query:a}:{bbox:s.bbox,label:n,source:"query",query:a})}return o},resolvePlaceIdCandidates=async(e,t)=>{const r=[];for(const o of e??[]){const e=t.expandEntry(o);if(0===e?.length)return{error:`Places entry "${o}" has no place with a geometry data source. Provide a boundingBox or a different entry.`};for(const a of e??[o]){const e=await t.fetchGeometry(a);if(!e)return{error:`Unknown placeId "${a}" or it has no fetchable geometry. Use recallState to list available IDs.`};r.push({bbox:bboxFromGeoJSON(e),polygon:e.geometry,source:"placeId"})}}return r},resolveGeometryCandidates=e=>{const t=[];for(const r of e??[]){const e=bboxFromGeoJSON(r);e&&t.push({bbox:e,polygon:r,source:"geometries"})}return t},resolveRouteCandidates=async(e,t)=>{const r=t.getRoute(e.routeId);if("error"in r)return r;const o=[];for(const a of r.features){const t=turf.buffer(a,e.widthMeters/2,{units:"meters"});t&&o.push({bbox:bboxFromGeoJSON(t),polygon:t.geometry,label:r.label,source:"route"})}return 0===o.length?{error:"Route produced no corridor geometry."}:o},rerankWithNearestCandidate=(e,t)=>{const r=[...e].sort((e,r)=>{if(void 0===e.bbox||void 0===r.bbox)return 0;const o=turf.distance(bboxCenter(e.bbox),t)-turf.distance(bboxCenter(r.bbox),t);return Math.abs(o)>1e3?o: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)},geocodeQueryCandidates=async(e,t,r)=>{const o=[];for(const{query:a,queryAs:s}of e.queries??[]){const e=await t.geocodeAreas(a,s,r);o.push(...e)}return o},collectShownByodFeatures=e=>{const t=[];for(const r of e.byod.shownEntryIds){const o=e.byod.findById(r);o&&t.push(...o.data.features)}return t},showByodOnMap=async(e,t,r)=>(await hidePreviousShownEntries(e.byod,[t],r.hidePreviousEntries),await e.byod.showEntry(t),{zoomMode:"auto"===r.zoomMode&&fitMapToGeoJSON(e.baseMap.mapLibreMap,collectShownByodFeatures(e))}),showEntryGeometries=async(e,t,r,o)=>{const a=await e.places.fetchGeometriesForEntry(t,o);return r.show&&a.length>0?(await e.places.showPlaceGeometries(a,r.fillStyle??"outline",r.mode??"replace"),{fetched:a.length,shown:!0}):{fetched:a.length,shown:!1}},showResultsOnMap=async(e,t,r)=>{const o={markerType:!1,zoomMode:!1};if("none"!==r.markerType){if("multiple"===e.places.entryMode&&"all"!==r.hidePreviousEntries){if(Array.isArray(r.hidePreviousEntries)){const o=new Set(t),a=r.hidePreviousEntries.filter(e=>!o.has(e));a.length>0&&await e.places.removeShownEntries(a)}await e.places.addShownEntries(t,r.markerType)}else await e.places.setShownEntries(t,r.markerType);o.markerType=!0}return"auto"===r.zoomMode&&(fitMapToGeoJSON(e.baseMap.mapLibreMap,e.places.getShownFeatures()),o.zoomMode=!0),o},MIN_VIEWPORT_BIAS_ZOOM=6,getViewportBias=(e,t=6)=>{if(e.mapLibreMap.getZoom()<t)return;const r=e.mapLibreMap.getCenter();return[r.lng,r.lat]},getViewportBoundingBox=e=>{const t=e.mapLibreMap.getBounds();return[t.getWest(),t.getSouth(),t.getEast(),t.getNorth()]},toolStateToWhereContext=(e,t)=>({viewport:()=>{let t,r;try{t=getViewportBoundingBox(e.baseMap)}catch{t=void 0}try{r=getViewportBias(e.baseMap)}catch{r=void 0}return{bbox:t,center:r}},expandEntry:t=>e.places.geometryPlaceIdsForEntry(t),geocodeAreas:(e,r,o)=>r?locatePlaces(e,r,{limit:5,bias:o?{position:o}:void 0},t):geocodeAreas(e,{bias:o},t),fetchGeometry:async r=>{if("string"==typeof r)return e.places.fetchPlaceGeometry(r,t);if(!r.properties.dataSources?.geometry?.id)return;const o=withAgentToolkitHeaders({geometries:[r],signal:t?.signal});return(await geometryData(o)).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. setRoute), then retry."};const o=t?r.find(e=>e.id===t):r.at(-1);if(!o)return{error:`Route "${t}" not found. Use recallState to list available route IDs.`};const a=o.data.features;return 0===a.length?{error:`Route "${o.id}" has no geometry. Recompute it via setRoute.`}:{features:a,label:o.label}}}),sourceDiffSchema=z.object({added:z.array(z.string()).describe("Source IDs added by the executed code."),removed:z.array(z.string()).describe("Source IDs removed by the executed code."),updated:z.array(z.string()).describe("Source IDs whose definition changed.")}),layerDiffSchema=z.object({added:z.array(z.string()).describe("Layer IDs added by the executed code."),removed:z.array(z.string()).describe("Layer IDs removed by the executed code."),updated:z.array(z.string()).describe("Layer IDs whose properties changed.")}),executeMaplibreCodeSchema=z.object({code:z.string().describe('Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. Example: await a map event with `await new Promise(r => map.once("idle", r))`. Return a value with `return` to include it in the result.')}),executeMaplibreCodeOutputSchema=z.union([z.object({success:z.literal(!0),result:z.unknown().optional().describe("Return value of the executed code, if any."),sources:sourceDiffSchema,layers:layerDiffSchema}),toolErrorSchema]),snapshotSources=e=>{const t=e.getStyle()?.sources??{},r=/* @__PURE__ */new Map;for(const[o,a]of Object.entries(t))r.set(o,JSON.stringify(a));return r},snapshotLayers=e=>{const t=e.getStyle()?.layers??[],r=/* @__PURE__ */new Map;for(const o of t)r.set(o.id,JSON.stringify(o));return r},diffSnapshots=(e,t)=>{const r=[],o=[],a=[];for(const[s,n]of t){const t=e.get(s);void 0===t?r.push(s):t!==n&&a.push(s)}for(const s of e.keys())t.has(s)||o.push(s);return{added:r,removed:o,updated:a}},executeExecuteMaplibreCode=async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,o=snapshotSources(e),a=snapshotLayers(e),s=Object.getPrototypeOf(async()=>{}).constructor,n=await new s("map",r)(e);return{success:!0,result:n??void 0,sources:diffSnapshots(o,snapshotSources(e)),layers:diffSnapshots(a,snapshotLayers(e))}}catch(o){return{error:formatSandboxExecutionError("Code",o)}}},createExecuteMaplibreCodeTool=()=>({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: raster / tile overlays, and time-based animations / multi-step loops that one-shot tools cannot express. NOT for a globe projection, a sky or its colours, the hillshade or 3D terrain (setMapStyling). If any other tool already performs the operation, use that tool instead.",inputSchema:executeMaplibreCodeSchema,outputSchema:executeMaplibreCodeOutputSchema,execute:executeExecuteMaplibreCode,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)); }\" })"],examplePrompts:["Add the OpenStreetMap raster tiles as a semi-transparent overlay","Fade in a layer gradually"],relatedTools:["getMapStyleLayers","setLayerProperties","flyTo"],dependsOn:["getMapStyleLayers"]}),flyToOutputSchema=z.union([z.object({success:z.literal(!0)}),toolErrorSchema]),flyToSchema=z.object({where:z.union([z.object({boundingBox:geoJsonBBoxSchema.describe("BBox [W,S,E,N]."),padding:z.number().optional().describe("Padding in pixels. Default: 50.")}).describe("Fit the camera to a bounding box."),z.object({position:positionSchema.describe("[lng, lat] to fly to."),zoom:z.number().optional().describe("Camera zoom level. Default: 14.")}).describe("Fly the camera to a specific position.")])}),getMapStyleLayersOutputSchema=z.union([z.object({layers:z.array(z.object({id:z.string(),layout:z.unknown().optional(),paint:z.unknown().optional()}))}),toolErrorSchema]),getMapStyleLayersSchema=z.object({layerIdQuery:z.string().optional().describe("Case-insensitive partial match on layer IDs. If omitted, all layers are returned."),include:z.array(z.enum(["paint","layout"])).describe('Which properties to include in the response. Pass only what you need: "paint" for visual properties like colors and opacity, "layout" for structural properties like sizes, positions, and visibility.')}),getViewportOutputSchema=z.union([z.object({center:z.array(z.number()).describe("[lng, lat]"),zoom:z.number().describe("0–22"),bbox:z.array(z.number()).describe("[W, S, E, N]"),pitch:z.number().describe("degrees, 0=top-down, 85=near-horizontal"),bearing:z.number().describe("degrees, 0=north, 90=east")}),toolErrorSchema]),getViewportSchema=z.object({}),propertyGroups=["paint","layout"],changedPropertySchema=z.object({layerId:z.string(),group:z.enum(propertyGroups),propertyName:z.string()}),changeResultSchema=z.union([changedPropertySchema.extend({value:z.unknown(),success:z.literal(!0)}),changedPropertySchema.extend({error:z.string()})]),setLayerPropertiesOutputSchema=z.union([z.object({results:z.array(changeResultSchema)}),toolErrorSchema]),setLayerPropertiesSchema=z.object({changes:z.array(z.object({layerId:z.string().describe("MapLibre layer ID from getMapStyleLayers"),paint:z.record(z.string(),z.any()).optional().describe('Paint properties by name, e.g. { "fill-color": "#00f", "line-width": 3 }'),layout:z.record(z.string(),z.any()).optional().describe('Layout properties by name, e.g. { "visibility": "none", "text-size": 14 }')})).describe("Changes to apply in one go. A property goes under paint or layout as getMapStyleLayers lists it.")}),setPitchBearingOutputSchema=z.union([z.object({success:z.literal(!0)}),toolErrorSchema]),setPitchBearingSchema=z.object({pitch:z.number().min(0).max(85).optional().describe("0=top-down, 85=near-horizontal"),bearing:z.number().min(-180).max(180).optional().describe("0=north, 90=east")}),zoomInOrOutOutputSchema=z.union([z.object({zoom:z.number().describe("Resulting zoom level after the change")}),toolErrorSchema]),zoomInOrOutSchema=z.object({delta:z.number().describe("Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.")}),resolveLocationInput=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 o=t.places.findPlaceById(r);if(!o){const e=t.places.entries.find(e=>e.id===r),a=e?.data[0];a&&(o={place:a,entryId:e.id})}if(!o)return null;const a=getPosition(o.place);return a?{place:o.place,position:a,name:o.place.properties?.poi?.name??o.place.properties?.address?.freeformAddress??r}:null}const o=await locatePlace(e.query,e.queryAs,void 0,r);if(!o)return null;const a=getPosition(o);return a?{place:o,position:a,name:o.properties?.address?.freeformAddress??e.query,query:e.query}:null},costModelSchema=z.object({routeType:z.enum(routeTypes).optional(),traffic:z.enum(["live","historical"]).optional().describe("`historical` routes on the TYPICAL path, ignoring current incidents, yet reports live delays — for monitoring, so each tick refreshes delays on the SAME paths."),avoid:z.array(z.enum(avoidableTypes)).optional(),avoidAreas:z.array(geoJsonBBoxSchema.describe("GeoJSON bbox [west, south, east, north]")).max(10).optional().describe("Up to 10 bounding boxes to bypass.")}),whenSchema=z.object({option:z.enum(["departAt","arriveBy"]),date:z.string().describe("ISO 8601")}),routeParametersSchema=z.object({maxAlternatives:z.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:costModelSchema.optional(),when:whenSchema.optional().describe("Omit to depart now.")}),setRouteOutputSchema=z.union([routesWriteOutputSchema,z.object({success:z.literal(!0)}),toolErrorSchema]),setRouteSchema=z.object({locations:z.array(locationInputSchema).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:routeParametersSchema.optional().describe("Routing options. Omitted fields keep their current values."),showOnMap:z.boolean().describe("Show the route on the map after calculation."),monitor:z.boolean().optional().describe('Arm a live monitor on the new route: recalculates it every 60s so its live-traffic delays stay current. Pair with `costModel.traffic: "historical"` + `maxAlternatives: 1-2` so the typical paths stay stable while delays refresh.'),hidePreviousEntries:hidePreviousEntriesSchema("routes")}).refine(e=>void 0!==e.locations||void 0!==e.parameters,{message:"Provide at least one of `locations` or `parameters`."}),resolveRouteWaypoints=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},sectionTypesWithCurves=[...defaultSectionTypes,"curve","curvyStretch"],buildCalculateRouteParams=e=>{const{maxAlternatives:t=0,costModel:r,when:o}=e;return{computeTravelTimeFor:"all",sectionTypes:sectionTypesWithCurves,...t>0&&{maxAlternatives:t},...r&&{costModel:{...r,...r.avoidAreas&&{avoidAreas:r.avoidAreas}}},...o&&{when:{option:o.option,date:new Date(o.date)}}}},buildRouteRecalc=(e,t)=>()=>{const r=withAgentToolkitHeaders({locations:e,...buildCalculateRouteParams(t)});return calculateRoute(r)},calculateAndAddRoute=async(e,t,{showOnMap:r,hidePreviousEntries:o,monitor:a},s)=>{const n=withAgentToolkitHeaders({locations:t,...buildCalculateRouteParams(e.routing.params),signal:s?.signal}),i=await calculateRoute(n),l=await e.routing.addRoutes(i,t,((e,t)=>{const r=e.features[0]?.properties?.summary,o=[r?formatDuration(r.travelTimeInSeconds):void 0,r?formatDistance(r.lengthInMeters):void 0].filter(Boolean),a=o.length?` (${o.join(", ")})`:"",s=t.map(waypointAddress).filter(Boolean);if(s.length>=2){const e=s[0],t=s.at(-1),r=s.slice(1,-1);return`${r.length>0?`${e} via ${r.join(", ")} to ${t}`:`${e} to ${t}`}${a}`}return`Route${a}`})(i,t));if(r&&await(async(e,t,r,o)=>{await hidePreviousShownEntries(e.routing,[t],o),await e.routing.showEntry(t),fitMapToGeoJSON(e.baseMap.mapLibreMap,r)})(e,l,i,o),a){const t=e.routing.entries.find(e=>e.id===l);t&&e.routing.startMonitoring(l,{recalculate:buildRouteRecalc(t.waypoints,t.params)})}return{...summarizeRoutes(i),entryId:l}},addWaypointsToRouteOutputSchema=z.union([routesWriteOutputSchema,toolErrorSchema]),addWaypointsToRouteSchema=z.object({origin:locationInputSchema.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:locationInputSchema.optional().describe("Append a new destination after the current destination. The previous destination is kept as an intermediate stop (it is not replaced)."),stops:z.array(locationInputSchema).min(1).optional().describe("Intermediate stops to insert at optimal mid-positions along the existing route. Multiple stops settle into natural along-route order regardless of input order."),showOnMap:z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:hidePreviousEntriesSchema("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`."}),buildDiscoverPlacesOutputSchema=e=>z.union([buildPlacesOutputSchema().extend({shown:shownSchema.optional(),placesEntryId:z.string().optional(),label:z.string().optional().describe('Human-readable label stored with the entry (e.g. "cafe", "CINEMA (50 places)").'),geometriesFetched:z.number().optional().describe("Count of boundary polygons fetched and cached on the entry (when `geometries` was set)."),geometriesShown:z.boolean().optional().describe("Whether the fetched polygons were rendered on the map."),resolvedAreas:resolvedAreasOutputSchema.optional(),unmatchedPoiCategories:z.array(z.string()).optional().describe("`poiCategories` inputs that matched no category, left out of the search.")}),toolErrorSchema]),maxDetourWhereSchema=(buildDiscoverPlacesOutputSchema(),z.object({mode:z.literal("maxDetour"),routeId:z.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),maxDetourTimeSeconds:z.number().int().positive().max(3600).describe("Max extra travel time (seconds, at most 3600) the user accepts to reach a POI."),sortBy:z.enum(["detourTime","detourOffset"]).optional().describe('"detourTime" (default) ranks by the travel time a stop adds, negative (so first) when the route through the POI is faster; "detourOffset" by distance from the route line.'),limit:z.number().max(100).optional().describe("Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.")})),buildDiscoverPlacesSchema=e=>{const t={query:z.string().optional().describe('Free-text filter on POI name/address. NEVER a city/region/area name (e.g. "Paris", "Amsterdam") — those go in `where`. For "restaurants in Paris": `query` is empty (or a name filter like "pizza"), `where.queries: [{query: "Paris"}]`. Omit when filtering by category — use `poiCategories` instead.'),where:z.union([z.object(sharedWithinFields).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."}),nearbyWhereSchema,maxDetourWhereSchema,globalWhereSchema]).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:z.array(z.string()).optional().describe('CONSTANT_CASE category codes. ALWAYS batch all requested categories in ONE array — never one call per category. Common: "RESTAURANT", "CAFE", "BAR", "HOTEL", "PARK", "PARK_AND_RECREATION_AREA", "CHARGING_LOCATION", "FUEL_STATION", "PARKING_GARAGE", "SUPERMARKET", "PHARMACY", "ATM". For anything else, pass the plain words (e.g. "italian food", "gym"): this call resolves them to their best codes.'),show:showPlacesSchema.optional().describe("Display places/geometries on the map. Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry."),entryId:placesEntryIdHintSchema,geometries:showPlaceGeometriesSchema};return z.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`)."})},dispatchPlacesSearch=(buildDiscoverPlacesSchema(),(e,t)=>{const{query:r,poiCategories:o,bias:a,radiusMeters:s,multiFilters:n}=e,i=((e,t)=>e?.position?{position:e.position,radiusMeters:t}:e?.boundingBox?{boundingBox:e.boundingBox}:void 0)(a,s),l=(c=n,c.geometries?.length?c.geometries:void 0);var c;const d={query:r,...o&&{filters:{poiCategories:o}},signal:t?.signal,limit:100},u=l?{...d,geometries:l}:{...d,...i&&{geoBias:i}};return discoverPlaces(withAgentToolkitHeaders(u))}),finalizeDiscoverResult=async(e,t,r,o,a,s,n,i)=>{const l=o?await showResultsOnMap(e,[r],o):void 0;let c,d;if(a){const t=await showEntryGeometries(e,r,a,i);c=t.fetched,t.shown&&(d=!0)}const u=e.places.entries.find(e=>e.id===r)?.label;return{...summarizePlaces(t),placesEntryId:r,...u&&{label:u},...l&&{shown:l},...void 0!==c&&{geometriesFetched:c},...void 0!==d&&{geometriesShown:d},...n&&n.length>0&&{resolvedAreas:n}}},buildExecuteDiscoverPlaces=e=>async(e,t,r)=>{const{query:o,where:a,poiCategories:s,show:n,entryId:i,geometries:l}=e,c=a??{mode:"within",viewport:!0},d={};try{const e=s?.length?await resolvePOICategories(withAgentToolkitHeaders({filters:s,signal:r?.signal})):void 0;if(e&&!e.poiCategories.length)return{error:`No POI categories matched: ${e.unmatched.map(e=>`"${e}"`).join(", ")}. Retry with a broader word (e.g. "restaurant" rather than "trattoria") or an exact CONSTANT_CASE code, or search it as free text in \`query\`.`};const a=e?.poiCategories,u=e?.unmatched.length?{unmatchedPoiCategories:e.unmatched}:void 0;if("maxDetour"===c.mode){const e=await(async(e,t,r,o,a,s)=>{const n=e.routing.entries;if(0===n.length)return{error:"No routes in state. Calculate a route first via setRoute."};const i=t.routeId?n.find(e=>e.id===t.routeId):n.at(-1);if(!i)return{error:`No route found with id "${t.routeId}". Use recallState to list available routes.`};const l=i.data.features[0];if(!l)return{error:`Route entry "${i.id}" has no geometry.`};const c=withAgentToolkitHeaders({route:l,maxDetourTimeSeconds:t.maxDetourTimeSeconds,...r&&{query:r},...o&&{filters:{poiCategories:o}},...t.sortBy&&{sortBy:t.sortBy},...void 0!==t.limit&&{limit:t.limit},signal:s?.signal}),d=await discoverPlaces(c);return{result:d,placesEntryId:await e.places.addPlaceResult(d,makePlacesLabel(d,{query:r,poiCategories:o,routeLabel:i.label}),a)}})(t,c,o,a,i,r);return"error"in e?e:{...await finalizeDiscoverResult(t,e.result,e.placesEntryId,n,l,0,void 0,r),...u}}const h=await(async(e,t,r,o)=>{if("nearby"===e.mode){const a=await(async(e,t,r)=>{if(e.position)return{bias:{position:e.position},radiusMeters:e.radiusMeters};const o=await resolveNearby({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},toolStateToWhereContext(t,r)),a=o.label?[{matched:o.label,...void 0!==o.query&&{query:o.query}}]:[];return{bias:o.position?{position:o.position}:{},radiusMeters:e.radiusMeters,...a.length>0&&{resolvedAreas:a}}})(e,t,o);return{bias:a.bias,multiFilters:r,radiusMeters:a.radiusMeters,resolvedAreas:a.resolvedAreas}}if("within"===e.mode){const a=await(async(e,t,r,o)=>{if(e.viewport)return{bias:{boundingBox:getViewportBoundingBox(t.baseMap)},multiFilters:r};const a=await resolveWithin({boundingBox:void 0,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},toolStateToWhereContext(t,o));if(isResolveError(a))return a;const s=a.filter(e=>e.polygon).map(e=>e.polygon),n=a.filter(e=>!e.polygon).map(e=>e.bbox),i=a.find(e=>"route"===e.source)?.label,l=a.filter(e=>"query"===e.source&&e.label).map(e=>({matched:e.label,...void 0!==e.query&&{query:e.query}})),c=[...n],d=[...s],u={...r,...c.length&&{boundingBoxes:c},...d.length&&{geometries:d}},h=d.length?void 0:bboxFromBBoxes(n);return{bias:h?{boundingBox:h}:{},multiFilters:u,range:e.range,routeLabel:i,...l.length>0&&{resolvedAreas:l}}})(e,t,r,o);return"error"in a?a:{bias:a.bias,multiFilters:a.multiFilters,range:a.range,routeLabel:a.routeLabel,resolvedAreas:a.resolvedAreas}}return{bias:{},multiFilters:r}})(c,t,d,r);if("error"in h)return h;const{bias:p,multiFilters:y,radiusMeters:m,range:f,routeLabel:g,resolvedAreas:b}=h;if(f){const e=await(async(e,t)=>{const{state:r,range:o,query:a,resolvedPoiCategories:s,multiFilters:n,entryId:i,routeLabel:l}=e,c=getRangePolygons(r,o);if("error"in c)return c;const d=[...c.polygons,...n.geometries??[]],u=await dispatchPlacesSearch({query:a,poiCategories:s,multiFilters:{...n,geometries:d}},t);return{result:u,placesEntryId:await r.places.addPlaceResult(u,makePlacesLabel(u,{query:a,poiCategories:s,...l&&{routeLabel:l}}),i)}})({state:t,range:f,query:o,resolvedPoiCategories:a,multiFilters:y,entryId:i,routeLabel:g},r);return"error"in e?e:{...await finalizeDiscoverResult(t,e.result,e.placesEntryId,n,l,0,b,r),...u}}const w=(b?.length?b.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})(c),S=await(async(e,t)=>{const{state:r,query:o,bias:a,resolvedPoiCategories:s,radiusMeters:n,multiFilters:i,entryId:l,whereLabel:c,routeLabel:d}=e,u=await dispatchPlacesSearch({query:o,poiCategories:s,bias:a,radiusMeters:n,multiFilters:i},t);return{result:u,placesEntryId:await r.places.addPlaceResult(u,makePlacesLabel(u,{query:o,poiCategories:s,where:c,...d&&{routeLabel:d}}),l)}})({state:t,query:o,bias:p,resolvedPoiCategories:a,radiusMeters:m,multiFilters:y,entryId:i,whereLabel:w,routeLabel:g},r);return{...await finalizeDiscoverResult(t,S.result,S.placesEntryId,n,l,0,b,r),...u}}catch(u){return toToolError("Search failed",u)}},discoverPlacesMetadata=(buildExecuteDiscoverPlaces(),{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: ["CHARGING_LOCATION"], 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: ["CHARGING_LOCATION"], 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:["setRoute"]}),budgetSchema=z.object({type:z.enum(budgetTypes).describe("Budget type including its unit."),value:z.number().describe("Budget value. e.g. 30 for 30 minutes, 50 for 50 km.")}),findReachableAreasSchema=z.object({origins:z.array(locationInputSchema).min(1).describe("One or more starting points; one reachable area is computed per origin under the same budgets and bundled into a single ranges entry. Pass a single-element array for the classic one-origin call."),budgets:z.array(budgetSchema).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."),fillStyle:geometryFillStyleSchema.optional().describe("How the polygons are filled. Default: outline (transparent fill with thick colored border). Use inverted for a donut (reachable highlighted, outside dimmed) or filled for solid polygons."),showOnMap:z.boolean().describe("Whether to render the range polygon(s) on the map."),showOriginPin:z.boolean().optional().describe("Whether to drop a pin at each origin. Default: true."),hidePreviousEntries:hidePreviousEntriesSchema("ranges")}),rangeOutputSchema=z.object({rangesId:z.string(),ranges:z.array(z.object({originName:z.string(),budgets:z.array(budgetSchema)})),skipped:z.array(z.object({origin:z.string(),reason:z.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry, failed service call).")}),findReachableAreasOutputSchema=z.union([rangeOutputSchema,z.object({status:z.enum(["no_results"])}),toolErrorSchema]),formatOriginName=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,rangeForOrigin=async(e,t,r,o)=>{let a=(e=>"query"in e?e.query:"position"in e?`${e.position[1]}, ${e.position[0]}`:"placeIdOrEntryId"in e?e.placeIdOrEntryId:"unnamed origin")(e);try{const s=await resolveLocationInput(e,r,o);if(!s)return{skipped:{origin:a,reason:"origin not found"}};a=s.name??a;const n=await(async(e,t,r)=>{const o=e.map(e=>withAgentToolkitHeaders({origin:t,budget:{type:e.type,value:e.value}})),a=await calculateReachableRanges(o,{signal:r?.signal});return 0===a.features.length?null:a})(t,s.position,o);return n?{range:{origin:s.query?{query:s.query,position:s.position}:{position:s.position},budgets:t,polygon:n}}:{skipped:{origin:a,reason:"no reachable area returned"}}}catch(s){if(s instanceof SDKAbortError)throw s;return{skipped:{origin:a,reason:errorMessageOf(s)},error:s}}},getPoiCategoryCodesOutputSchema=z.union([z.object({count:z.number().describe("Number of codes returned"),codes:z.array(z.string()).describe('POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. "RESTAURANT") also accepted by the setMapStyling pois.filters.categories.values knob.')}),toolErrorSchema]),getPoiCategoryCodesSchema=z.object({filters:z.array(z.string()).optional().describe("Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes."),language:z.string().describe('Locale e.g. "en-GB", "fr-FR", "es-ES", "de-DE".')}),metricsSchema=z.object({speed:z.number().optional(),freeFlowSpeed:z.number().optional(),congestionLevel:z.number().optional(),travelTime:z.number().optional(),networkLength:z.number().optional()}),getTrafficAreaAnalyticsOutputSchema=z.union([z.object({entryId:z.string().describe("Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData."),name:z.string().optional(),dateRange:z.object({start:z.string(),end:z.string()}),baseData:metricsSchema,metrics:z.array(z.string()),tileCount:z.number(),availableGranularities:z.array(z.string())}),toolErrorSchema]),getTrafficAreaAnalyticsSchema=z.object({location:locationInputSchema.optional().describe("Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Provide this or bbox, never both."),bbox:z.array(z.number()).length(4).optional().describe("[west, south, east, north], west > east across the antimeridian — use a wide area (at least 5-10km across, city-level) for meaningful results. Provide this or location, never both."),showOnMap:z.boolean().optional().describe("Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize."),startDate:z.string().optional().describe("Start date 'YYYY-MM-DD'. Required unless `days` is given. Use with endDate for a continuous range (max 31 days)."),endDate:z.string().optional().describe("End date 'YYYY-MM-DD' (inclusive). Defaults to three days ago, the latest day with published data."),days:z.array(z.string()).optional().describe("Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate."),metrics:z.union([z.literal("all"),z.array(z.enum(areaAnalyticsMetricKeys)).min(1)]).describe("Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength']."),functionalRoadClasses:z.union([z.literal("all"),z.array(z.enum(functionalRoadClasses))]).optional().describe("Road classes to include. 'all' (default) or array of specific classes."),hours:z.union([z.literal("all"),z.array(z.number().int().min(0).max(23))]).optional().describe("Hours of the day in UTC (0-23) to include. 'all' (default) or an array of UTC hours: convert the area's local hours first (Amsterdam summer rush hours [7, 8, 9] local are [5, 6, 7] UTC).")}).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."}),entriesSummary=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),getTrafficIncidentsOutputSchema=z.union([z.object({count:z.number(),entryId:z.string().optional(),resolvedAreas:resolvedAreasOutputSchema.optional(),entries:z.array(z.object({id:z.string(),label:z.string(),count:z.number(),timestamp:z.number()})),monitoring:z.boolean().optional().describe("True when this fetch also armed a background monitor on the new entry.")}),toolErrorSchema]),withinTrafficIncidentsWhereSchema=z.object({...sharedWithinFields,boundingBox:geoJsonBBoxSchema.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)."}),trafficIncidentsWhereSchema=z.union([withinTrafficIncidentsWhereSchema]).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²).'),categoryFilterDescription=`Filter by incident category (server-side). Canonical values: ${trafficIncidentRequestCategories.join(", ")}. Map the user's wording to the closest value. If omitted, all categories are returned.`,getTrafficIncidentsSchema=z.object({where:trafficIncidentsWhereSchema.optional().describe('Geographic scope. Default: `{ mode: "within", viewport: true }`.'),categoryFilter:z.array(z.enum([...trafficIncidentRequestCategories])).optional().describe(categoryFilterDescription),timeValidityFilter:z.array(z.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:z.string().optional().describe('Short human-readable label for this fetch — name the place ("Central London", "Amsterdam ring", "M25 corridor"), not coordinates. Surfaced verbatim in the monitor chip and on the entry chip in the UI; also used by analyseData / focusIncidents to disambiguate which entry to operate on. Always pass one when show is true (the default); the bbox-coordinate fallback ("bbox a,b → c,d") is for hidden (show: false) intermediate fetches only.'),show:z.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:z.boolean().optional().describe("Default: true. Pass false to skip camera move after rendering."),monitor:z.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:z.number().int().positive().optional().describe("Polling interval in ms when monitoring. Default 60000 (60s).")}),locatePlaceSchema=z.object({query:z.string().describe("Location string to resolve for downstream routes or searches."),waypointIndex:z.number().int().min(0).optional().describe("Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination."),queryAs:z.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, geographies.'),where:whereSchema.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:showPlacesSchema.optional().describe("Map display actions after resolving. Skip for intermediate calculations."),entryId:placesEntryIdHintSchema,geometry:showPlaceGeometriesSchema}),locatePlaceOutputSchema=z.union([placeOutputSchema.extend({placesEntryId:z.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:z.number().optional(),shown:shownSchema.optional(),geometryFetched:z.boolean().optional().describe("Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set)."),geometryShown:z.boolean().optional().describe("Whether the fetched polygon was rendered on the map.")}),toolErrorSchema]),removeWaypointsFromRouteOutputSchema=z.union([routesWriteOutputSchema,toolErrorSchema]),removeWaypointsFromRouteSchema=z.object({waypointIndices:z.array(z.number().int()).min(1).describe("Indices of waypoints to remove. 0=origin, last=destination. Min 2 waypoints must remain. Duplicates are not allowed."),showOnMap:z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:hidePreviousEntriesSchema("routes")}),replaceWaypointInRouteOutputSchema=z.union([routesWriteOutputSchema,toolErrorSchema]),waypointTargetSchema=z.union([z.literal("origin"),z.literal("destination"),z.number().int()]).describe("Which waypoint to overwrite. Use 'origin' or 'destination' for the endpoints, or a 0-based absolute index (0 = origin, last = destination, intermediate stops in between)."),replaceWaypointInRouteSchema=z.object({waypointIndex:waypointTargetSchema,location:locationInputSchema.describe("The new waypoint that replaces the targeted one."),showOnMap:z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),hidePreviousEntries:hidePreviousEntriesSchema("routes")}),reverseGeocodeOutputSchema=z.union([placeOutputSchema.extend({placesEntryId:z.string().describe("Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData.")}),toolErrorSchema]),reverseGeocodeSchema=z.object({position:positionSchema.describe("[longitude, latitude] GeoJSON position to reverse geocode")}),isFeatureCollectionLike=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)},addByodSourceOutputSchema=z.union([z.object({byodEntryId:z.string().describe("Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData)."),label:z.string(),source:z.union([z.object({kind:z.literal("url"),url:z.string()}),z.object({kind:z.literal("inline")})]).describe("Where the data came from."),profile:byodDataProfileSchema.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.")}),toolErrorSchema]),addByodSourceSchema=z.object({label:z.string().describe('Human-readable label for the new entry (e.g. "Sales territories", "Customer pins 2026-Q1").'),url:z.url().optional().describe("URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. EXCLUSIVE with `data`."),data:z.unknown().optional().describe('Inline GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`). EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.'),entryId:placesEntryIdHintSchema}).refine(e=>e.url?!e.data:!!e.data,{message:"Pass EXACTLY ONE of `url` or `data`."}),ALLOWED_PROTOCOLS=/* @__PURE__ */new Set(["http:","https:"]),NON_PUBLIC_IPV4_BLOCKS=[[[0,0,0,0],8],[[10,0,0,0],8],[[100,64,0,0],10],[[127,0,0,0],8],[[169,254,0,0],16],[[172,16,0,0],12],[[192,0,0,0],24],[[192,0,2,0],24],[[192,168,0,0],16],[[198,18,0,0],15],[[198,51,100,0],24],[[203,0,113,0],24],[[224,0,0,0],4],[[240,0,0,0],4]],IPV4_LITERAL=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,LOCAL_HOSTNAME_SUFFIXES=[".localhost",".local",".localdomain",".internal",".home.arpa"],octetsToNumber=e=>e.reduce((e,t)=>256*e+t,0),isPublicHost=e=>{const t=e.replace(/\.$/,"");return t.startsWith("[")?(e=>{const[t,r]=(e=>{const t=e=>e?e.split(":").map(e=>Number.parseInt(e,16)):[],[r,o]=e.split("::"),a=t(r),s=t(o);return[...a,...new Array(8-a.length-s.length).fill(0),...s]})(e);return 8192==(57344&t)&&!(8194===t||8193===t&&(0===r||3512===r))})(t.slice(1,-1)):IPV4_LITERAL.test(t)?(e=>{const t=octetsToNumber(e.split(".").map(Number));return!NON_PUBLIC_IPV4_BLOCKS.some(([e,r])=>{const o=2**(32-r);return Math.floor(t/o)===Math.floor(octetsToNumber(e)/o)})})(t):!(e=>!e.includes(".")||LOCAL_HOSTNAME_SUFFIXES.some(t=>e.endsWith(t)))(t)},builtInSourceUrlPolicy=e=>"https:"===e.protocol&&e.origin!==globalThis.location?.origin&&isPublicHost(e.hostname)?{valid:!0}:{valid:!1,reason:"URL refused: only public https:// URLs can be loaded, not local, private or same-origin addresses. Ask the user for a public https:// URL, or for the GeoJSON itself to pass as `data`."},STANDING_SANDBOX_PARAMS=[...MULTI_INPUT_SANDBOX_PARAMS,"previous","now","log"],runPreparedSandbox=async(e,t,r)=>{const o=await runSandboxedFn(r.code,STANDING_SANDBOX_PARAMS,[...packSandboxArgs(t.sandbox,{h3:h3,turf:turf}),r.previous,new Date(r.sampledAt),()=>{}],"Analysis",e.codeExecution);return"error"in o?o:validateAnalysisResult(o.value,r.outputFormat)},analyseDataOutputSchema=(buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS),z.union([z.object({affectedEntries:z.array(z.union([z.object({kind:z.literal("places"),id:z.string()}),z.object({kind:z.literal("routes"),id:z.string()}),z.object({kind:z.literal("incidents"),id:z.string()}),z.object({kind:z.literal("customGeometries"),id:z.string()}),z.object({kind:z.literal("trafficAreaAnalytics"),id:z.string()}),z.object({kind:z.literal("byod"),id:z.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:z.array(z.unknown()).optional().describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only present when \`geometriesEntryIDs\` was set).`),analysisId:z.string().describe("Stable handle for this analysis. Pass to `monitorAnalysis` to keep it recomputing whenever its source entries change."),name:z.string().describe("Unique name of the analysis within each affected entry."),description:z.string().optional(),outputFormat:z.enum(["json","chart"]),monitoring:z.boolean().optional().describe("True when `monitor: true` was set — the analysis now recomputes on every source change."),analysis:z.unknown(),skipped:z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC)}),toolErrorSchema])),buildAnalyseDataSchema=(e,t)=>{const r=resolveActiveKinds(e,t),o=`At least one of ${buildEntryFieldList(r)} is REQUIRED.`,a={...buildEntryIdFields(r,{verb:"analyse",requiredHint:o})};return a.name=z.string().describe('Unique name within each affected entry (e.g. "incidents-per-cluster", "ev-near-route"). Reusing a name replaces the previous analysis on those entries.'),a.description=z.string().optional().describe("Optional short description of what the analysis computes."),a.outputFormat=z.enum(["json","chart"]).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION),a.code=z.string().describe(((e,t)=>{const r=resolveActiveKinds(e,t),o=buildEntryKindSandboxDocs(r),a=buildEntryKindSchemaDocs(r),s=r.includes("customGeometries");return"Async JS that aggregates the injected inputs and returns the result.\n\n"+buildSandboxToolsDoc(r,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading (\`if (places) ...\`, \`routes?.features.length\`, etc.). When defined:\n${o}\n\n${buildAnalyseReturnPrompt("counts, per-kind breakdowns, hex bins, intersections, distance bins")}\n\n`+(s?`${GEOMETRIES_PROPS_DOC}\n\n`:"")+a})(e,t)),a.monitor=z.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`."),z.object(a).refine(hasAnyEntryIds,{message:buildAtLeastOneEntryIdMessage(r)})},buildAnalyseDataDescription=(buildAnalyseDataSchema(ALL_ENTRY_DATA_KINDS,void 0),(e,t)=>{const r=resolveActiveKinds(e,t).join(" / "),o="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. ${o} 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. ${o} 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).`}),buildAnalyseDataScopePrompt=(buildAnalyseDataDescription(ALL_ENTRY_DATA_KINDS,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).`),executeAnalyseData=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:o,incidentsEntryIDs:a,geometriesEntryIDs:s,trafficAreaAnalyticsEntryIDs:n,byodEntryIDs:i,name:l,description:c,code:d}=e,u=e.outputFormat??"json",h=await prepareMultiInputs({placesEntryIDs:r,routesEntryIDs:o,incidentsEntryIDs:a,geometriesEntryIDs:s,trafficAreaAnalyticsEntryIDs:n,byodEntryIDs:i},t);if("error"in h)return{error:h.error};const{resolved:p,sandbox:y,geometries:m}=h.value,f=await runSandboxedFn(d,MULTI_INPUT_SANDBOX_PARAMS,packSandboxArgs(y,{h3:h3,turf:turf}),"Analysis",t.codeExecution);if("error"in f)return{error:f.error};const g=validateAnalysisResult(f.value,u);if("error"in g)return{error:g.error};const b=g.value,w=Date.now(),S=[...p.places.map(e=>({kind:"places",id:e.id})),...p.routes.map(e=>({kind:"routes",id:e.id})),...p.incidents.map(e=>({kind:"incidents",id:e.id})),...p.trafficAreaAnalytics.map(e=>({kind:"trafficAreaAnalytics",id:e.id})),...p.byod.map(e=>({kind:"byod",id:e.id})),...m.affectedEntries.filter(e=>"places"===e.kind&&!p.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}))],v=s?m.sourceIds:void 0,I=S.map(e=>e.id),E=((e,t)=>`${e}::${[...t].sort((e,t)=>e.localeCompare(t)).join(",")}`)(l,I),T=!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}),o=e.analyses.job(t.analysisId);if(o&&o.code===t.code)return r(),void(t.monitor&&o.setActive(!0));o&&e.analyses.resetHistory(t.analysisId);const a=t.monitor||(o?.active??!1);let s;o?.unregister(),r(),s=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:a,run:async()=>{if(!e.analyses.get(t.analysisId))return;const r=await prepareMultiInputs(t.inputs,e);if("error"in r)return;const o=latestTimestamp(r.value.resolved)??Date.now(),a=await runPreparedSandbox(e,r.value,{code:t.code,previous:e.analyses.lastResult(t.analysisId),sampledAt:o,outputFormat:t.outputFormat});"error"in a||e.analyses.job(t.analysisId)!==s||e.analyses.attachResult(t.analysisId,{name:t.name,...t.description&&{description:t.description},outputFormat:t.outputFormat,data:a.value,timestamp:o})},onOrphaned:()=>e.analyses.remove(t.analysisId)}),e.analyses.setJob(t.analysisId,s)})(t,{analysisId:E,name:l,description:c,outputFormat:u,code:d,inputs:{placesEntryIDs:r,routesEntryIDs:o,incidentsEntryIDs:a,geometriesEntryIDs:s,trafficAreaAnalyticsEntryIDs:n,byodEntryIDs:i},affectedEntryIds:I,monitor:T}),t.analyses.attachResult(E,{name:l,description:c,outputFormat:u,data:b,timestamp:w}),{affectedEntries:S,...v&&{sourceIds:v},analysisId:E,name:l,...c&&{description:c},outputFormat:u,monitoring:T,analysis:b,...m.skipped.length&&{skipped:m.skipped}}},clearTrackerSchema=z.object({trackerId:z.string().optional().describe("Which tracker to clear. Omit when only one tracker exists.")}),clearTrackerOutputSchema=z.union([z.object({cleared:z.literal(!0),trackerId:z.string(),name:z.string()}),toolErrorSchema]),execClustering=async(e,t,r,o)=>{const a=e.trafficIncidents.entries.find(e=>e.id===t);if(!a)return{error:`unknown incidents entry "${t}"`};if("params"===r.kind){const{groups:e}=runClustering(a.data,r.params,o?.groups,a.timestamp,o?.sampledAt);return{value:{groups:e,sampledAt:a.timestamp}}}const s=await prepareMultiInputs(r.inputs,e);if("error"in s)return s;const n=await runPreparedSandbox(e,s.value,{code:r.code,previous:o,sampledAt:a.timestamp,outputFormat:"json"});return"error"in n?n:(i=n.value)&&"object"==typeof i&&Array.isArray(i.groups)?{value:n.value}:{error:"clustering `code` must return a cluster(...) result (`{ groups }`)"};var i},rerunFlags=/* @__PURE__ */new WeakMap,flagsFor=e=>{let t=rerunFlags.get(e);return t||(t={running:!1,scheduled:!1,pending:/* @__PURE__ */new Set},rerunFlags.set(e,t)),t},wired=/* @__PURE__ */new WeakSet,ensureClusterRerunWired=e=>{wired.has(e)||(wired.add(e),e.trafficIncidents.events.on("entries-change",({changedIds:t})=>scheduleRerun(e,t)))},scheduleRerun=(e,t)=>{const r=flagsFor(e);for(const o of t)r.pending.add(o);r.running||r.scheduled||(r.scheduled=!0,queueMicrotask(()=>{r.scheduled=!1,rerunClusters(e)}))},rerunClusters=async e=>{const t=flagsFor(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 o=r.recipe,a=await execClustering(e,t,o,r.output);"value"in a&&e.trafficIncidents.setClusters(t,{recipe:o,output:a.value})}}finally{t.running=!1}},resolveClusterKinds=(e,t)=>{const r=t?resolveActiveKinds(e,t).filter(e=>"incidents"!==e):[];return{context:r,active:["incidents",...r]}},buildClusterIncidentsCodeDoc=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"+buildSandboxToolsDoc(e,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${buildEntryKindSandboxDocs(e)}\n\n`+buildEntryKindSchemaDocs(e),buildClusterIncidentsSchema=(e,t)=>{const{context:r,active:o}=resolveClusterKinds(e,t);return z.object({incidentsEntryID:z.string().describe("Entry id from getTrafficIncidents — the incidents to cluster."),eps:z.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:z.number().int().min(1).optional().describe("Minimum incidents per cluster. Default 3. Ignored when `code` is set."),maxClusters:z.number().int().min(1).optional().describe("Top N clusters by total delay. Default 6. Ignored when `code` is set."),...buildEntryIdFields(r,{verb:"analyse",requiredHint:"Only used by custom `code`."}),code:z.string().optional().describe(buildClusterIncidentsCodeDoc(o))})},clusterIncidentsOutputSchema=(buildClusterIncidentsSchema(ALL_ENTRY_DATA_KINDS,void 0),z.union([z.object({incidentsEntryID:z.string(),clusterCount:z.number(),clusters:z.array(z.object({id:z.string(),primaryRoads:z.array(z.string()),primaryCategory:z.string(),size:z.number(),totalDelaySeconds:z.number(),peakDelaySeconds:z.number(),trend:z.enum(["growing","fading","steady","new"])}))}),toolErrorSchema])),CLUSTER_BASE_DESCRIPTION="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.",buildClusterIncidentsDescription=e=>{const t=e.filter(e=>"incidents"!==e);return t.length?`${CLUSTER_BASE_DESCRIPTION} Custom \`code\` can also filter incidents against ${t.join(" / ")}.`:CLUSTER_BASE_DESCRIPTION},buildClusterIncidentsScopePrompt=(buildClusterIncidentsDescription(["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"] }\`.`),executeClusterIncidents=async(e,t)=>{const{incidentsEntryID:r,eps:o,minMembers:a,maxClusters:s,code:n}=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!=o&&{eps:o},...null!=a&&{minMembers:a},...null!=s&&{maxClusters:s}},c=n?{kind:"code",code:n,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(async(e,t,r)=>{ensureClusterRerunWired(e);const o=e.trafficIncidents.getClustersRecord(t),a=o&&(s=o.recipe,n=r,JSON.stringify(s)===JSON.stringify(n))?o.output:void 0;var s,n;const i=await execClustering(e,t,r,a);return"error"in i||e.trafficIncidents.setClusters(t,{recipe:r,output:i.value}),i})(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}))}},buildCreateTrackerSchema=(e,t)=>{const r=resolveActiveKinds(e,t),o=`At least one of ${buildEntryFieldList(r)} is REQUIRED.`,a={...buildEntryIdFields(r,{verb:"analyse",requiredHint:o})};return a.name=z.string().describe('Short tracker label, e.g. "Incidents near hospitals".'),a.rule=z.string().describe('The watched condition in plain language, e.g. "an incident within 100m of a hospital". For display.'),a.code=z.string().describe((e=>"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"+buildSandboxToolsDoc(e,!1)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${buildEntryKindSandboxDocs(e)}\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`+buildEntryKindSchemaDocs(e))(r)),z.object(a).refine(hasAnyEntryIds,{message:buildAtLeastOneEntryIdMessage(r)})},createTrackerOutputSchema=(buildCreateTrackerSchema(ALL_ENTRY_DATA_KINDS,void 0),z.union([z.object({trackerId:z.string(),name:z.string(),rule:z.string(),watching:z.array(z.string()).describe("Source entry ids the rule reads."),firingNow:z.boolean().describe("Whether the condition holds at arm time. If true, one `opened` alert fires for it."),summary:z.string().describe("The arm-time verdict summary.")}),toolErrorSchema])),executeCreateTracker=async(e,t)=>{const r={placesEntryIDs:e.placesEntryIDs,routesEntryIDs:e.routesEntryIDs,incidentsEntryIDs:e.incidentsEntryIDs,geometriesEntryIDs:e.geometriesEntryIDs,trafficAreaAnalyticsEntryIDs:e.trafficAreaAnalyticsEntryIDs,byodEntryIDs:e.byodEntryIDs},o=await prepareMultiInputs(r,t);if("error"in o)return{error:o.error};const{resolved:a,sandbox:s,geometries:n}=o.value,i=await runSandboxedFn(e.code,MULTI_INPUT_SANDBOX_PARAMS,packSandboxArgs(s,{h3:h3,turf:turf}),"Tracker rule",t.codeExecution);if("error"in i)return{error:i.error};if(!isVerdict(i.value))return{error:"Tracker `code` must return { active: boolean, members: {entryId, featureIds}[], summary: string }."};const l=i.value,c=[...a.places.map(e=>e.id),...a.routes.map(e=>e.id),...a.incidents.map(e=>e.id),...a.trafficAreaAnalytics.map(e=>e.id),...a.byod.map(e=>e.id),...n.affectedEntries.map(e=>e.id)],d=uniqueTrackerId(e.name,t),u=Date.now(),h={id:d,name:e.name,rule:e.rule,affectedEntryIds:c,enabled:!0,wasActive:!1};return t.trackers.register(h),((e,t)=>{const r=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:!0,run:async()=>{const r=await prepareMultiInputs(t.inputs,e);if("error"in r)return;const o=latestTimestamp(r.value.resolved)??Date.now(),a=await runPreparedSandbox(e,r.value,{code:t.code,previous:void 0,sampledAt:o,outputFormat:"json"});!("error"in a)&&isVerdict(a.value)&&e.trackers.ingestVerdict(t.trackerId,a.value,o)},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}},uniqueTrackerId=(e,t)=>{const r=e.toLowerCase().replaceAll(/[^a-z0-9]+/g,"-").replaceAll(/(?:^-+|-+$)/g,"")||"tracker",o=new Set(t.trackers.trackers.map(e=>e.id));if(!o.has(r))return r;for(let a=2;;a++)if(!o.has(`${r}-${a}`))return`${r}-${a}`},buildCreateTrackerScopePrompt=e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the watched condition reads (e.g. ["incidents", "places"] for incidents near hospitals).`,focusIncidentsOutputSchema=z.union([z.object({incidentsEntryID:z.string().describe("The entry the focus was applied to."),focusedCount:z.number().describe("How many of the requested ids ended up in the focus subset."),droppedIds:z.array(z.string()).describe("Ids that were not present on the entry and were dropped from the focus."),reason:z.string().optional().describe("Echo of the focus reason for downstream display.")}),toolErrorSchema]),focusIncidentsSchema=z.object({incidentsEntryID:z.string().describe("Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source)."),incidentIds:z.array(z.string()).min(1).describe("Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored."),reason:z.string().optional().describe('Short label shown in the UI FocusChip — what this subset represents (e.g. "top 3 by delay").')}),getCurrentWaypointsSchema=z.object({slotIndex:z.number().int().min(0).optional().describe("Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot."),includeEmptySlots:z.boolean().optional().describe("Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses."),offset:z.number().int().min(0).optional().describe("Pagination offset within the filtered slot list. Ignored when slotIndex is provided."),limit:z.number().int().min(1).max(25).optional().describe("Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.")}),getCurrentWaypointsOutputSchema=z.object({totalSlots:z.number().describe("Total waypoint slots currently staged"),filledSlots:z.number().describe("Number of staged slots that already contain coordinates"),matchedCount:z.number().describe("Total slots matching the request before pagination"),returnedCount:z.number().describe("Number of slots returned in this page"),offset:z.number().describe("Offset applied to the filtered slot list"),hasMore:z.boolean().describe("True when more slots can be fetched with a higher offset"),nextOffset:z.number().optional().describe("Offset to request the next page when hasMore=true"),waypoints:z.array(waypointSummarySchema)}),getTrackerHistorySchema=z.object({trackerId:z.string().optional().describe("Scope to one tracker. Omit for every tracker."),sinceMs:z.number().optional().describe("Only events at/after this epoch-ms sample time.")}),eventSchema=z.object({at:z.number(),trackerId:z.string(),trackerName:z.string(),type:z.enum(["alert","event"]),kind:z.enum(["opened","resolved"]),summary:z.string(),openedAt:z.number().optional()}),getTrackerHistoryOutputSchema=z.object({events:z.array(eventSchema).describe("Newest first."),count:z.number(),coverage:z.object({oldestRetainedAt:z.number(),newestAt:z.number()}).optional().describe("Sample-time span the bounded log currently retains — older events were evicted.")}),getTrackersSchema=z.object({}),getTrackersOutputSchema=z.object({trackers:z.array(z.object({trackerId:z.string(),name:z.string(),rule:z.string(),enabled:z.boolean(),firing:z.boolean().describe("Whether the rule is currently active (an open episode)."),openedAt:z.number().optional(),watching:z.array(z.string()).describe("Source entry ids the rule reads.")})),count:z.number()}),monitorAnalysisOutputSchema=z.union([z.object({analysisId:z.string(),enabled:z.boolean().describe("The analysis is now recomputed on every source-entry change (true) or not (false).")}),toolErrorSchema]),monitorAnalysisSchema=z.object({analysisId:z.string().describe("The `analysisId` returned by `analyseData` for the analysis to start or stop monitoring."),enabled:z.boolean().describe("true → recompute this analysis on every change to its source entries; false → stop monitoring it. The latest result stays attached either way.")}),showProducedGeometriesSchema=(buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS),z.object({fillStyle:geometryFillStyleSchema.optional().describe('How the polygons are filled. Default: "outline". Use "inverted" for difference/inverse results.'),mode:showModeSchema,hidePreviousEntries:hidePreviousEntriesSchema("custom-geometries").optional()})),sharedProcessDataReport={sourceIds:z.array(geometriesIdSchema).optional().describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only when \`geometriesEntryIDs\` was set).`),skipped:z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),fitted:fittedReportSchema.optional()},processDataOutputSchema=z.union([buildPlacesOutputSchema().extend({placesEntryId:z.string(),label:z.string().optional(),connectionsRendered:z.number().optional(),placesGeometryCount:z.number().optional().describe("Polygons attached to the new places entry (when `geometries` was returned alongside)."),shownPlaces:z.boolean().optional(),...sharedProcessDataReport}),z.object({customGeometriesEntryId:geometriesIdSchema,customGeometriesCount:z.number(),label:z.string(),operation:z.string().optional(),shownCustomGeometries:z.boolean().optional(),...sharedProcessDataReport}),z.object({byodEntryId:z.string(),byodFeatureCount:z.number(),label:z.string(),fitted:fittedReportSchema.optional()}),z.object({fitted:fittedReportSchema}),toolErrorSchema]),buildProcessDataSchema=(e,t)=>{const r=resolveActiveKinds(e,t),o=`At least one of ${buildEntryFieldList(r)} is REQUIRED.`,a={...buildEntryIdFields(r,{verb:"process",requiredHint:o})};return a.code=z.string().describe(((e,t)=>{const r=resolveActiveKinds(e,t),o=buildEntryKindSchemaDocs(r),a=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":"",s=r.includes("customGeometries")?`${GEOMETRIES_PROPS_DOC}\n\n`:"",n=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=`${buildEntryKindSandboxDocs(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"+n+"\n"+buildSandboxToolsDoc(r,!0)+i+"Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. "+a+`${FIT_ON_MAP_DOC}\n\n`+s+o})(e,t)),a.label=z.string().optional().describe("Short label for the new entry (used by both places and custom-geometries paths)."),a.entryId=placesEntryIdHintSchema,a.operation=z.string().optional().describe("Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance on a new custom-geometries entry. Ignored on the places path."),a.show=z.object({places:showPlacesSchema.optional().describe("Render the new places entry (when `places` was returned)."),placesGeometries:z.object({fillStyle:geometryFillStyleSchema.optional(),mode:showModeSchema}).optional().describe("Render the polygons attached to the new places entry (when both `places` and `geometries` were returned)."),customGeometries:showProducedGeometriesSchema.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)."),a.connectMarkerType=z.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Match the module holding the endpoints.'),z.object(a).refine(hasAnyEntryIds,{message:buildAtLeastOneEntryIdMessage(r)})},buildProcessDataDescription=(buildProcessDataSchema(ALL_ENTRY_DATA_KINDS,void 0),(e,t)=>{const r=resolveActiveKinds(e,t).join(" / "),o="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. ${o} 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. ${o} 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.`}),buildProcessDataScopePrompt=(buildProcessDataDescription(ALL_ENTRY_DATA_KINDS,void 0),e=>`Emit \`{ kinds: [${e.map(e=>`"${e}"`).join(" | ")}] }\` listing only the entry kinds the user query touches. Omit when truly cross-kind.`),isConnectionArray=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)})},isNonEmptyString=e=>"string"==typeof e&&e.trim().length>0,isValidLngLat=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},derivedPlaceId=(e,t,r)=>`derived-${e.toFixed(6)}_${t.toFixed(6)}-${r}`,validateField=(e,t,r)=>void 0===e?{value:void 0}:t(e)?{value:e}:{error:r},nonEmptyFeatureCollection=e=>e&&e.features.length>0?e:void 0,executeProcessData=async(e,t)=>{const{placesEntryIDs:r,routesEntryIDs:o,incidentsEntryIDs:a,geometriesEntryIDs:s,trafficAreaAnalyticsEntryIDs:n,byodEntryIDs:i,code:l}=e,c=await prepareMultiInputs({placesEntryIDs:r,routesEntryIDs:o,incidentsEntryIDs:a,geometriesEntryIDs:s,trafficAreaAnalyticsEntryIDs:n,byodEntryIDs:i},t);if("error"in c)return{error:c.error};const{sandbox:d,geometries:u}=c.value,h=await runSandboxedFn(l,MULTI_INPUT_SANDBOX_PARAMS,packSandboxArgs(d,{h3:h3,turf:turf}),"Process",t.codeExecution);if("error"in h)return{error:h.error};const p=(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(!isFeatureCollectionLike(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 o=e.features[r],a=o?.geometry;if(!a||"Point"!==a.type||!isValidLngLat(a.coordinates))return{error:`\`places[${r}]\` must be a Point Feature with valid [lng, lat] coordinates (lng ∈ [-180,180], lat ∈ [-90,90]).`};const s=o.properties;if(![s?.poi?.name,s?.address?.freeformAddress].find(isNonEmptyString))return{error:`\`places[${r}]\` needs a display name in \`properties.poi.name\` (POIs) or \`properties.address.freeformAddress\` (streets, intersections, addresses).`};if(!isNonEmptyString(s?.type)||!placeTypes.includes(s.type))return{error:`\`places[${r}].properties.type\` is required and must be one of: ${placeTypes.join(", ")}.`};if(!isNonEmptyString(o.id)){const[e,t]=a.coordinates;o.id=derivedPlaceId(e,t,r)}const n=t.get(o.id);if(void 0!==n)return{error:`\`places[${r}]\` duplicates id "${o.id}" of \`places[${n}]\`; give each place a distinct id, or omit it so a unique one is derived.`};t.set(o.id,r)}return{value:e}})(t.places);if("error"in e)return e.error;r.places=nonEmptyFeatureCollection(e.value)}const o=validateField(t.placeConnections,isConnectionArray,"`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).");if("error"in o)return o.error;if(r.placeConnections=o.value,void 0!==t.geometries){const e=(e=>{if(!Array.isArray(e))return{error:"`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features."};const t=[];for(let r=0;r<e.length;r++){const o=e[r];if(!isPolygonFeature(o))return{error:`\`geometries[${r}]\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.`};t.push(o)}return{value:t}})(t.geometries);if("error"in e)return e.error;r.geometries=e.value.length>0?e.value:void 0}const a=validateField(t.fitOnMap,isFitOnMapInput,"`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.");if("error"in a)return a.error;r.fitOnMap=a.value;const s=validateField(t.byod,isFeatureCollectionLike,"`byod` must be a GeoJSON FeatureCollection (any geometry types).");return"error"in s?s.error:(r.byod=nonEmptyFeatureCollection(s.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)})(h.value);if("string"==typeof p)return{error:p};const{places:y,geometries:m,fitOnMap:f,byod:g}=p;return y?(async(e,t,r,o)=>{const{label:a,entryId:s,show:n,connectMarkerType:i,geometriesEntryIDs:l}=t,{places:c,placeConnections:d,geometries:u,fitOnMap:h}=r,p=c.features.length,y=`${a?`processed: ${a}`:"processed"} (${p} ${1===p?"place":"places"})`,m=await e.places.addPlaceResult(c,y,s,d,u),f=n?.places?await showResultsOnMap(e,[m],n.places):void 0;let g,b;if(u?.length&&(g=u.length,n?.placesGeometries&&await e.places.showPlaceGeometries(u,n.placesGeometries.fillStyle??"outline",n.placesGeometries.mode??"replace",m)),d){const t=await(async(e,t,r,o)=>{try{return await(await e.places.getEntryPlacesModule(t,o??"pin")).showConnections(r),{count:r.length}}catch(a){return toToolError("Failed to render connections",a)}})(e,m,d,i);if("error"in t)return t;b=t.count}const w=h?applyFitOnMap(e,h):void 0,S=e.places.entries.find(e=>e.id===m)?.label;return{...summarizePlaces(c),placesEntryId:m,...S&&{label:S},...f&&{shownPlaces:!0},...void 0!==b&&{connectionsRendered:b},...void 0!==g&&{placesGeometryCount:g},...l&&{sourceIds:o.sourceIds},...o.skipped.length&&{skipped:o.skipped},...w&&{fitted:w}}})(t,e,{...p,places:y},u):m?.length?(async(e,t,r,o,a)=>{const{label:s,entryId:n,operation:i,show:l,geometriesEntryIDs:c}=t,d=s?`processed: ${s}`:`processed geometries (${r.length})`,u=await e.customGeometries.addEntry(r,{sourceIds:a.sourceIds,...i&&{operation:i}},d,n);let h=!1;l?.customGeometries&&(await hidePreviousShownEntries(e.customGeometries,[u],l.customGeometries.hidePreviousEntries),await e.customGeometries.showEntry(u,l.customGeometries.fillStyle??"outline",l.customGeometries.mode??"replace"),h=!0);const p=o?applyFitOnMap(e,o):void 0;return{customGeometriesEntryId:{kind:"customGeometries",id:u},customGeometriesCount:r.length,label:d,...i&&{operation:i},...c&&{sourceIds:a.sourceIds},...a.skipped.length&&{skipped:a.skipped},...h&&{shownCustomGeometries:!0},...p&&{fitted:p}}})(t,e,m,f,u):g?(async(e,t,r,o)=>{const{label:a,entryId:s}=t,n=a?`processed: ${a}`:`processed byod (${r.features.length})`,i=await e.byod.addEntry(r,n,{...s&&{explicitId:s}}),l=o?applyFitOnMap(e,o):void 0;return{byodEntryId:i,byodFeatureCount:r.features.length,label:n,...l&&{fitted:l}}})(t,e,g,f):f?{fitted:applyFitOnMap(t,f)}:{error:"Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`."}},sourceSchema=z.union([z.object({kind:z.literal("integrator"),description:z.string().optional()}),z.object({kind:z.literal("url"),url:z.string()}),z.object({kind:z.literal("inline"),description:z.string().optional()})]),refSchema$1=z.object({id:z.string(),label:z.string(),timestamp:z.number(),featureCount:z.number(),geometryTypes:z.array(z.string()),propertyNames:z.array(z.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:sourceSchema,shown:z.boolean()}),detailSchema$5=refSchema$1.extend({profile:byodDataProfileSchema.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.")}),recallByodOutputSchema=z.union([z.object({entries:z.array(refSchema$1),entryMode:z.enum(["single","multiple"])}),detailSchema$5,toolErrorSchema]),refSchema=(z.object({id:z.string().optional().describe("BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.")}),z.object({id:geometriesIdSchema.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.')}),z.object({id:geometriesIdSchema.describe("Tagged id of this entry."),label:z.string(),timestamp:z.number(),featureCount:z.number(),operation:z.string().optional().describe("Operation label (custom-geometries entries only)."),sourceIds:z.array(geometriesIdSchema).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:z.array(analysisIndexSchema).optional().describe("Analyses already attached to this entry (places and custom only).")})),detailSchema$4=refSchema.extend({features:z.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),recallGeometriesOutputSchema=z.union([z.object({entries:z.array(refSchema)}),detailSchema$4,toolErrorSchema]),summariseAnalyses=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:o})=>({name:e,outputFormat:t,timestamp:r,...o&&{description:o}})):void 0,indexEntries=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const o=summariseAnalyses(e.analyses.getAnalysesForEntry(r.id));t.push({id:{kind:"places",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,...o&&{analyses:o}})}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 o=summariseAnalyses(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]},...o&&{analyses:o}})}return t.sort((e,t)=>t.timestamp-e.timestamp)},entryModeSchema$5=(z.object({id:z.string().optional().describe('Incidents entry id to inspect (e.g. "incidents-0"). Omit to list every incidents entry.')}),z.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.")),indexEntrySchema$4=z.object({id:z.string(),label:z.string(),timestamp:z.number(),count:z.number().describe("Number of incidents captured in this entry."),shown:z.boolean().describe("True while this entry is rendered on the map."),monitored:z.boolean().describe("True while a background monitor is actively polling this entry."),focusedCount:z.number().describe("Incidents in the entry's focus subset (0 when nothing is focused).")}),detailSchema$3=indexEntrySchema$4.extend({byMagnitude:z.record(z.string(),z.number()).describe("Incident count grouped by `magnitudeOfDelay` (e.g. `{ major: 3, moderate: 5, minor: 10 }`)."),totalDelaySeconds:z.number().describe("Sum of `delayInSeconds` across the entry (0 when no delay data)."),clusterCount:z.number().optional().describe("Number of incident clusters computed for this entry — present only once clustering has run.")}),recallIncidentsOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema$4),entryMode:entryModeSchema$5}),detailSchema$3.extend({entryMode:entryModeSchema$5}),toolErrorSchema]),indexEntrySchema$3=(z.object({id:z.string().optional().describe('Entry ID to retrieve (e.g. "places-3"). Omit to list all entries.')}),z.object({id:z.string(),label:z.string(),timestamp:z.number(),featureCount:z.number().describe("Number of Place features stored in this entry."),shown:z.boolean().describe("Whether this entry is currently rendered on the map."),markerType:z.enum(placesMarkerTypes).optional().describe("Which marker type is rendering this entry (undefined when `shown` is false)."),analyses:z.array(analysisIndexSchema).optional().describe("Analyses already attached to this entry via analyseData (name + output format).")})),entryModeSchema$4=z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several entries render simultaneously; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),buildRecallPlacesOutputSchema=e=>{const t=z.object({id:z.string(),label:z.string(),timestamp:z.number(),places:buildPlacesOutputSchema()});return z.union([z.object({entries:z.array(indexEntrySchema$3),entryMode:entryModeSchema$4}),t.extend({entryMode:entryModeSchema$4}),toolErrorSchema])},buildExecuteRecallPlaces=(buildRecallPlacesOutputSchema(),e=>async(e,t)=>{const{id:r}=e,o=t.places.entries,a=t.places.entryMode,s=t.places.shownEntryIds;if(!r)return{entries:[...o].sort((e,t)=>t.timestamp-e.timestamp).map(({id:e,label:r,timestamp:o,data:a})=>{const n=s.has(e),i=n?t.places.getShownMarkerType(e):void 0,l=t.analyses.getAnalysesForEntry(e);return{id:e,label:r,timestamp:o,featureCount:a.length,shown:n,...i&&{markerType:i},...l.length&&{analyses:l.map(({name:e,outputFormat:t,timestamp:r,description:o})=>({name:e,outputFormat:t,timestamp:r,...o&&{description:o}}))}}}),entryMode:a};const n=o.find(e=>e.id===r);if(!n)return{error:`No entry found with id "${r}"`};const i=summarizePlaces(n.data);return{id:n.id,label:n.label,timestamp:n.timestamp,places:i,entryMode:a}}),indexEntrySchema$2=(buildExecuteRecallPlaces(),z.object({id:z.string().optional().describe('Entry ID to retrieve (e.g. "ranges-0"). Omit to list all entries.')}),z.object({id:z.string(),label:z.string(),timestamp:z.number(),originCount:z.number()})),rangeSummarySchema=z.object({origin:z.object({query:z.string().optional(),position:z.tuple([z.number(),z.number()])}),budgets:z.array(z.object({type:z.string(),value:z.number()}))}),detailSchema$2=z.object({id:z.string(),label:z.string(),timestamp:z.number(),ranges:z.array(rangeSummarySchema)}),entryModeSchema$3=z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several reachable-range entries render at once; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),recallRangesOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema$2),entryMode:entryModeSchema$3}),detailSchema$2.extend({entryMode:entryModeSchema$3}),toolErrorSchema]),indexEntrySchema$1=(z.object({id:z.string().optional().describe('Entry ID to retrieve (e.g. "routes-1"). Omit to list all entries.')}),z.object({id:z.string(),label:z.string(),timestamp:z.number(),shown:z.boolean().describe("Whether this entry is currently rendered on the map.")})),routeParamsSchema=z.object({maxAlternatives:z.number().optional(),costModel:costModelSchema.optional(),when:whenSchema.optional()}),detailSchema$1=z.object({id:z.string(),label:z.string(),timestamp:z.number(),routes:routesOutputSchema,waypoints:z.array(z.object({position:z.array(z.number()).length(2).describe("[longitude, latitude]")})),params:routeParamsSchema}),entryModeSchema$2=z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several routes render at once; `single` enforces "at most one route on the map" — switching to it auto-clears non-latest entries.'),recallRoutesOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema$1),entryMode:entryModeSchema$2}),detailSchema$1.extend({entryMode:entryModeSchema$2}),toolErrorSchema]),standardStyleIdOf=e=>("string"==typeof e?e:e?.id)??"streetLight",entryModeSchema$1=(z.object({id:z.string().optional().describe('Traffic-area-analytics entry id to inspect (e.g. "tta-0"). Omit to list every analytics entry.')}),z.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.")),indexEntrySchema=z.object({id:z.string(),label:z.string(),timestamp:z.number(),regionCount:z.number().describe("Number of analysed regions (features) in this entry."),metrics:z.array(z.string()).describe('Traffic metrics included in the report (e.g. ["congestionLevel", "speed"]).'),shown:z.boolean().describe("True while this entry is rendered on the map.")}),detailSchema=indexEntrySchema.extend({dateRange:z.object({start:z.string(),end:z.string()}).describe("Analysis period (ISO dates) — empty strings when the entry carries no date metadata."),regions:z.array(z.string()).describe("Names of the analysed regions that have one, in feature order."),heatmap:z.boolean().describe("Whether tile-level heatmap data is included.")}),recallTrafficAreaAnalyticsOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema),entryMode:entryModeSchema$1}),detailSchema.extend({entryMode:entryModeSchema$1}),toolErrorSchema]),idLabelSchema=z.object({id:z.string(),label:z.string()}),entryModeSchema=z.enum(["single","multiple"]),snapshotSchema=z.object({places:z.array(idLabelSchema).describe("Places history entries — newest first."),routes:z.array(idLabelSchema).describe("Routes history entries — newest first."),ranges:z.array(idLabelSchema).describe("Reachable-range history entries — newest first."),entryModes:z.object({places:entryModeSchema,routes:entryModeSchema,ranges:entryModeSchema}).describe("Per-slice display policy. `multiple` (default) lets several entries render at once; `single` keeps only the latest entry — set via setEntryMode tools."),mapStyle:z.object({id:z.enum(standardStyleIDs)}).describe("Current standard map style."),mapPOIs:z.object({visible:z.boolean().optional(),filterMode:z.enum(filterShowModes).optional(),categories:z.array(z.string()).optional()}).nullable().describe("Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session."),traffic:z.object({flow:z.object({visible:z.boolean().optional()}).nullable().describe("Traffic-flow layer config. `null` when the module is uninitialised."),incidents:z.object({visible:z.boolean().optional()}).nullable().describe("Traffic-incidents layer config. `null` when the module is uninitialised.")}).describe("Real-time traffic visualization configs.")}),buildRecallStateOutputSchema=e=>z.union([snapshotSchema,buildRecallPlacesOutputSchema(),recallRoutesOutputSchema,recallRangesOutputSchema,recallByodOutputSchema,recallGeometriesOutputSchema,recallIncidentsOutputSchema,recallTrafficAreaAnalyticsOutputSchema,toolErrorSchema]),recallStateSchema=(buildRecallStateOutputSchema(),z.object({kind:z.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:z.union([z.string(),geometriesIdSchema]).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.')})),buildExecuteRecallState=e=>async(e,t)=>{const{kind:r,id:o}=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})),o=t(e.routing.entries).map(({id:e,label:t})=>({id:e,label:t})),a=t(e.ranges.entries).map(({id:e,label:t})=>({id:e,label:t})),s={id:standardStyleIdOf(e.baseMap.ttMap.getStyleInput())},n=e.mapPOIs.poisModule?.getConfig(),i=n?{..."boolean"==typeof n.visible&&{visible:n.visible},...n.filters?.categories?.show&&{filterMode:n.filters.categories.show},...n.filters?.categories?.values&&{categories:[...n.filters.categories.values]}}:null,l=e.trafficTiles.trafficFlowModule?.getConfig(),c=e.trafficTiles.trafficIncidentsModule?.getConfig(),d={flow:l?{..."boolean"==typeof l.visible&&{visible:l.visible}}:null,incidents:c?{..."boolean"==typeof c.visible&&{visible:c.visible}}:null};return{places:r,routes:o,ranges:a,entryModes:{places:e.places.entryMode,routes:e.routing.entryMode,ranges:e.ranges.entryMode},mapStyle:s,mapPOIs:i,traffic:d}})(t);switch(r){case"places":return buildExecuteRecallPlaces()({id:o},t);case"routes":return(async(e,t)=>{const{id:r}=e,o=t.routing.entryMode,a=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:a.has(e)})),entryMode:o};const s=t.routing.entries.find(e=>e.id===r);if(!s)return{error:`No entry found with id "${r}"`};const n=summarizeRoutes(s.data),i=s.waypoints.map(e=>summarizeWaypoint(e)).filter(e=>null!==e).map(e=>({position:e.position??[0,0]}));return{id:s.id,label:s.label,timestamp:s.timestamp,routes:n,waypoints:i,params:s.params,entryMode:o}})({id:o},t);case"ranges":return(async(e,t)=>{const{id:r}=e,o=t.ranges.entryMode;if(!r)return{entries:[...t.ranges.entries].reverse().map(({id:e,label:t,timestamp:r,data:o})=>({id:e,label:t,timestamp:r,originCount:o.length})),entryMode:o};const a=t.ranges.entries.find(e=>e.id===r);return a?{id:a.id,label:a.label,timestamp:a.timestamp,ranges:a.data.map(e=>({origin:{...e.origin,position:e.origin.position},budgets:e.budgets})),entryMode:o}:{error:`No entry found with id "${r}"`}})({id:o},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:toByodSafeProfile(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:o},t);case"incidents":return(async(e,t)=>{const r=t.trafficIncidents,o=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:o};const a=r.entries.find(t=>t.id===e.id);if(!a)return{error:`No incidents entry with id "${e.id}". Call recallState({ kind: "incidents" }) to list available IDs.`};const s={};let n=0;for(const l of a.data){const e=l.properties.magnitudeOfDelay;s[e]=(s[e]??0)+1,n+=l.properties.delayInSeconds??0}const i=r.getClusters(a.id);return{id:a.id,label:a.label,timestamp:a.timestamp,count:a.data.length,shown:r.shownEntryIds.has(a.id),monitored:r.isMonitored(a.id),focusedCount:r.getFocus(a.id)?.ids.size??0,byMagnitude:s,totalDelaySeconds:n,...i&&{clusterCount:i.groups.length},entryMode:o}})({id:o},t);case"trafficAreaAnalytics":return(async(e,t)=>{const r=t.trafficAreaAnalytics,o=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:o};const a=r.findById(e.id);if(!a)return{error:`No traffic-area-analytics entry with id "${e.id}". Call recallState({ kind: "trafficAreaAnalytics" }) to list available IDs.`};const s=a.data.properties;return{id:a.id,label:a.label,timestamp:a.timestamp,regionCount:a.data.features.length,metrics:s.metrics,shown:r.shownEntryIds.has(a.id),dateRange:{start:isoDate(s.startDate),end:isoDate(s.endDate)},regions:a.data.features.flatMap(e=>e.properties.name??[]),heatmap:s.heatmap,entryMode:o}})({id:o},t);case"geometries":return(async(e,t)=>{const{id:r}=e;return r?((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 o=summariseAnalyses(t.analyses.getAnalysesForEntry(r.id));return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,features:{type:"FeatureCollection",features:r.geometries},...o&&{analyses:o}}})(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 o=r.data.flatMap(e=>e.polygon?.features??[]);return 0===o.length?{error:`Ranges entry "${e.id}" has no polygons.`}:{id:e,label:r.label,timestamp:r.timestamp,featureCount:o.length,features:{type:"FeatureCollection",features:o}}})(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 o=summariseAnalyses(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]},...o&&{analyses:o}}})(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.'}}})(r,t):{entries:indexEntries(t)}})({id:o},t)}},recallStateMetadata=(buildExecuteRecallState(),{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"]}),CLEARED_SLICES=["places","routes","ranges","customGeometries","trafficAreaAnalytics","trafficIncidents","byod","analyses","trackers","mapPOIs","trafficFlow","mapStyle"],resetStateOutputSchema=z.union([z.object({success:z.literal(!0),cleared:z.array(z.enum(CLEARED_SLICES)).describe("Slices that were wiped / reset to defaults.")}),toolErrorSchema]),resetStateSchema=z.object({}),byodLayerTypeSchema=z.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."),byodLayerSpecSchema=z.object({type:byodLayerTypeSchema,paint:z.record(z.string(),z.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:z.record(z.string(),z.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:z.unknown().optional().describe("Optional MapLibre filter expression restricting which features this layer renders."),id:z.string().optional().describe("Stable layer id. Auto-generated when omitted."),beforeID:z.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: '${mapStyleLayerIDs.lowestLabel}' for fill/line/heatmap, which keeps every map label readable above the data, or '${mapStyleLayerIDs.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."),setByodLayersSchema=z.object({byodEntryId:z.string().describe("Id of the BYOD entry to restyle (from `addByodSource` or `recallState`)."),layers:z.array(byodLayerSpecSchema).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:showByodSchema.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`).")}),setByodLayersOutputSchema=z.union([z.object({byodEntryId:z.string(),label:z.string(),layerCount:z.number().describe("Number of layers now configured on the entry."),layerTypes:z.array(z.string()).describe("The `type` of each configured layer, in draw order."),shown:shownByodSchema.optional().describe("Render report — present only when `show` was provided (or the entry re-rendered live).")}),toolErrorSchema]),sliceSchema=z.enum(ENTRY_MODE_SLICE_NAMES),modeSchema=z.enum(["single","multiple"]),setEntryModeOutputSchema=z.union([z.object({success:z.literal(!0),slice:sliceSchema,mode:modeSchema,droppedEntryIds:z.array(z.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),toolErrorSchema]),setEntryModeSchema=z.object({slice:sliceSchema.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:modeSchema.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),setEntryModeDescription=`Switch the display policy of one entry-owning slice between \`multiple\` and \`single\`. Supported slices: ${ENTRY_MODE_SLICE_NAMES.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.`,setTrafficIncidentsMonitorOutputSchema=z.union([z.object({incidentsEntryID:z.string(),enabled:z.boolean().describe("The monitor is now polling (true) or stopped (false)."),alreadyInState:z.boolean().describe("True when the monitor was already in the requested state — this call was a no-op.")}),toolErrorSchema]),setTrafficIncidentsMonitorSchema=z.object({incidentsEntryID:z.string().describe("Id of the incidents entry to monitor (returned by `getTrafficIncidents`)."),enabled:z.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:z.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.")}),startRouteMonitorOutputSchema=z.union([z.object({routesEntryID:z.string(),alreadyRunning:z.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.")}),toolErrorSchema]),startRouteMonitorSchema=z.object({routesEntryID:z.string().describe("Id of the routes entry to monitor (the `entryId` returned by `setRoute`)."),intervalMs:z.number().int().positive().optional().describe("Recalculation interval in milliseconds. Defaults to 60000 (60s).")}),stopRouteMonitorOutputSchema=z.union([z.object({routesEntryID:z.string(),wasRunning:z.boolean().describe("True when a monitor was actually stopped; false when it was already idle.")}),toolErrorSchema]),stopRouteMonitorSchema=z.object({routesEntryID:z.string().describe("Id of the routes entry whose monitor should stop.")}),layerEnum=z.enum(CLEARABLE_SLICE_NAMES),clearMapOutputSchema=z.union([z.object({success:z.literal(!0),cleared:z.array(layerEnum)}),toolErrorSchema]),clearMapSchema=z.object({layers:z.array(layerEnum).optional().describe("Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: "+CLEARABLE_SLICE_NAMES.map(e=>`\`${e}\``).join(", ")+". Map-only — slice histories (`entries`) are kept.")}),getStandardMapStylesOutputSchema=z.union([z.object({styles:z.array(z.string()).readonly()}),toolErrorSchema]),getStandardMapStylesSchema=z.object({}),setGeopoliticalViewOutputSchema=z.union([z.object({success:z.literal(!0),geopoliticalView:z.enum(geopoliticalViews).optional()}),toolErrorSchema]),setGeopoliticalViewSchema=z.object({geopoliticalView:z.enum(geopoliticalViews).optional().describe('"Unified" = the international view; a country code = that country\'s view. Omit to reset to the default view of the country the map is viewed from.')}),setLanguageOutputSchema=z.union([z.object({success:z.literal(!0),language:z.string()}),toolErrorSchema]),setLanguageSchema=z.object({language:z.string().describe('e.g. "en-US", "fr-FR"')}),setMapStandardStyleOutputSchema=z.union([z.object({success:z.literal(!0),style:z.string()}),toolErrorSchema]),setMapStandardStyleSchema=z.object({style:z.enum(standardStyleIDs).describe("Map visual theme")}),annotationOf=({kind:e,range:t,options:r})=>{if(t)return`${t.min}–${t.max}`;if(!r)return"";const o=(r.length>16?[...r.slice(0,3),"…"]:r).join("|");return"enums"===e?`[${o}]`:o},entryText=({path:e,annotation:t})=>t?`${e} (${t})`:e,entriesOf=e=>{const t=/* @__PURE__ */new Map;for(const[r,o]of e.children){const e=[...void 0===o.annotation?[]:[{path:"",annotation:o.annotation}],...entriesOf(o)],a=1===e.length||e.every(({path:e})=>e)?JSON.stringify(e):r,s=t.get(a);s?s.keys.push(r):t.set(a,{keys:[r],entries:e})}return[...t.values()].flatMap(({keys:e,entries:t})=>{const r=e.length>1?`{${e.join("|")}}`:e[0];if(e.length>1&&t.length>1){const{shared:e,rest:o}=sharedHead(t);return[{path:`${r}.${e}{${o.map(entryText).join(", ")}}`,annotation:""}]}return t.map(({path:e,annotation:t})=>({path:e?`${r}.${e}`:r,annotation:t}))})},sharedHead=e=>{const t=/^[^.{]+\./.exec(e[0].path)?.[0];if(!t||!e.every(({path:e})=>e.startsWith(t)&&e.length>t.length))return{shared:"",rest:e};const{shared:r,rest:o}=sharedHead(e.map(e=>({...e,path:e.path.slice(t.length)})));return{shared:`${t}${r}`,rest:o}},withPrefixedIds=e=>e.flatMap(({prefix:e,knobs:t})=>t.map(t=>({id:`${e}${t.id}`,knob:t}))),normalized=e=>e.toLowerCase().replace(/[^a-z0-9]/g,""),knobParts=["look","baseMap","pois","trafficFlow","trafficIncidents","terrain","places","routes","geometries","ranges","trafficAreaAnalytics","trafficIncidentDetails"],knobPartScopeSchema=z.object({kinds:z.array(z.enum(knobParts)).min(1)}),moduleHolder=(e,t)=>({set:(r,o)=>setKnob(e,t,r,o),reset:r=>resetKnob(e,t,r),resetAll:()=>{for(const{id:r}of t)resetKnob(e,t,r)},get values(){const r=e.getConfig();return Object.fromEntries(t.map(({id:e})=>[e,knobValueIn(r,e)]).filter(([,e])=>void 0!==e))}}),styleSource=(e,t,r,o,a)=>({part:e,drawn:!1,prefix:t,knobs:r,holder:async e=>moduleHolder(await o(e),r),...a&&{existing:e=>{const t=a(e);return t&&moduleHolder(t,r)}}}),displayKnobIds=/* @__PURE__ */new Set(["visible","markerType","fill.style"]),dataSource=(e,t,r,o)=>({part:e,drawn:!0,prefix:t,knobs:r.filter(({id:e})=>!displayKnobIds.has(e)),holder:async e=>{const t=o(e);return{set:(e,r)=>{for(const o of t)o.set(e,r)},reset:e=>{for(const r of t)r.reset(e)},resetAll:()=>{for(const e of t)e.resetAll()},get values(){return Object.assign({},...t.map(({values:e})=>e))}}}}),knobSources=[styleSource("look","",stylingFoundationsKnobCatalogue,e=>e.baseMap.getStylingFoundationsModule()),styleSource("baseMap","",baseMapKnobCatalogue,e=>e.baseMap.getBaseMapModule()),styleSource("pois","pois.",poisKnobCatalogue,e=>e.mapPOIs.getPOIsModule(),e=>e.mapPOIs.poisModule),styleSource("trafficFlow","traffic.flow.",trafficFlowKnobCatalogue,e=>e.trafficTiles.getTrafficFlowModule(),e=>e.trafficTiles.trafficFlowModule),styleSource("trafficIncidents","traffic.incidents.",trafficIncidentsKnobCatalogue,e=>e.trafficTiles.getTrafficIncidentsModule(),e=>e.trafficTiles.trafficIncidentsModule),styleSource("terrain","terrain.",terrainKnobCatalogue,e=>e.baseMap.getTerrainModule(),e=>e.baseMap.terrainModule),dataSource("places","places.",placesKnobCatalogue,e=>[e.places.knobs,e.ranges.placesKnobs]),dataSource("routes","routes.",routingKnobCatalogue,e=>[e.routing.knobs]),dataSource("geometries","geometries.",geometriesKnobCatalogue,e=>[e.customGeometries.knobs,e.places.geometriesKnobs,e.ranges.geometriesKnobs]),dataSource("ranges","ranges.",reachableRangesKnobCatalogue,e=>[e.ranges.knobs]),dataSource("trafficAreaAnalytics","traffic.areaAnalytics.",trafficAreaAnalyticsKnobCatalogue,e=>[e.trafficAreaAnalytics.knobs]),dataSource("trafficIncidentDetails","traffic.incidentDetails.",trafficIncidentDetailsKnobCatalogue,e=>[e.trafficIncidents.knobs])],allKnobIds=withPrefixedIds(knobSources).map(({id:e})=>e),prefixHint=`Ids start with ${knobSources.flatMap(({prefix:e})=>e||[]).join(", ")}, or with none for the look and the base map.`,knobValueSchema=z.union([z.number(),z.boolean(),z.string(),z.array(z.string())]),themeModeSchema=z.enum(mapThemeModes).optional().describe("Light or dark map; auto (default) decides from the colours."),setMapStylingOutputSchema=z.union([z.object({success:z.literal(!0),settings:z.record(z.string(),knobValueSchema),rejected:z.array(z.object({id:z.string(),reason:z.string()})),themeColors:z.record(z.enum(mapColorNames),z.string()).optional(),switchedStyle:z.enum(standardStyleIDs).optional()}),toolErrorSchema]),stylingFieldsShape={reset:z.union([z.literal(!0),z.array(z.string())]).optional().describe("true resets every knob, what shows included, to the style default; a list resets just those knobs. Applied first."),preset:z.enum(mapPresetIds).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."),theme:z.union([z.object({colors:z.array(z.string()).min(1).max(12).describe("The CSS colours the theme is built from, three to six works best — a brand palette, a website, an artist's or design movement's typical palette, or a described mood turned into hex values. The SDK assigns them to land, water, roads, labels… coherently (readable labels guaranteed)."),mode:themeModeSchema}),z.object({imageUrl:z.url({protocol:/^https?$/}).describe("A CORS-readable image URL (logo, photo, poster) whose dominant colours become the theme."),mode:themeModeSchema})]).optional().describe("Theme the whole map from a few colours or an image instead of naming the ten colours yourself. Applied after preset and before `set`, so single colours in `set` can still override a role. A dark theme on a light standard style (or the reverse) switches to its dark (light) variant first. It also sets `colors.accent`, which routes and place pins follow unless a colour was set for them — so one call themes the whole map, not the base map alone.")},setMapStylingSchemaFor=(e=knobParts)=>{return z.object({set:z.record(z.string(),knobValueSchema).optional().describe(`Knob id → value. Factors are multipliers (1 = unchanged, e.g. 1.3), toggles are booleans, colours are CSS colour strings, a [bracketed] knob takes a list of its values (getPOICategoryCodes finds POI category codes). A filter applies once its \`.show\` and \`.values\` are both set. \`a.{b|c}.d\` stands for a.b.d and a.c.d, and \`a.{b, c (0–1)}\` for a.b and a.c. Ids: ${t=knobSources.filter(({part:t})=>e.includes(t)),entriesOf((e=>{const t={children:/* @__PURE__ */new Map};for(const{id:r,knob:o}of e)r.split(".").reduce((e,t)=>{let r=e.children.get(t);return r||e.children.set(t,r={children:/* @__PURE__ */new Map}),r},t).annotation=annotationOf(o);return t})(withPrefixedIds(t))).map(entryText).join(", ")}.`),...stylingFieldsShape});var t},existingHolder=(setMapStylingSchemaFor(),async(e,t)=>e.existing?e.existing(t):e.holder(t)),settingsOf=async e=>{const t={};for(const r of knobSources){const o=(await existingHolder(r,e))?.values??{};for(const{id:e}of r.knobs)void 0!==o[e]&&(t[`${r.prefix}${e}`]=o[e])}return t},standardVariants={light:"streetLight",dark:"streetDark"},drivingVariants={light:"streetLightDriving",dark:"streetDarkDriving"},monoVariants={light:"monoLight",dark:"monoDark"},LIGHT_DARK_VARIANTS={streetLight:standardVariants,streetDark:standardVariants,streetLightDriving:drivingVariants,streetDarkDriving:drivingVariants,monoLight:monoVariants,monoDark:monoVariants},executeSetMapStyling=async(e,t)=>{try{const o=await t.baseMap.getStylingFoundationsModule(),a=[],s=(e,t)=>{a.push({id:e,reason:errorMessageOf(t)})},n=async(e,r)=>{try{const o=(e=>{for(const t of knobSources){const r=e.startsWith(t.prefix)?e.slice(t.prefix.length):void 0;if(void 0!==r&&t.knobs.some(e=>e.id===r))return{source:t,local:r}}})(e);if(!o)throw(e=>{if(knobSources.some(({drawn:t,prefix:r})=>t&&e.startsWith(r)&&displayKnobIds.has(e.slice(r.length))))/* @__PURE__ */return new Error(`'${e}' is set per entry by the update*Display tools (show, hide, markerType, fillStyle).`);const t=((e,t)=>{const r=normalized(e),o=Math.max(2,Math.floor(r.length/3));return t.map(e=>{const t=normalized(e),o=((e,t)=>{let r=Array.from({length:t.length+1},(e,t)=>t);for(let o=1;o<=e.length;o++){const a=[o];for(let s=1;s<=t.length;s++){const n=r[s-1]+(e[o-1]===t[s-1]?0:1);a.push(Math.min(r[s]+1,a[s-1]+1,n))}r=a}return r[t.length]})(r,t);return{candidate:e,distance:t.includes(r)||r.includes(t)?Math.min(1,o):o}}).filter(({distance:e})=>e<=o).sort((e,t)=>e.distance-t.distance).slice(0,3).map(({candidate:e})=>e)})(e,allKnobIds),r=t.length?`; did you mean ${t.join(", ")}?`:".";/* @__PURE__ */
|
|
2
|
-
return new Error(`Unknown knob '${e}'${r} ${prefixHint}`)})(e);r(await o.source.holder(t),o.local)}catch(o){s(e,o)}};if(!0===e.reset)for(const e of knobSources)(await existingHolder(e,t))?.resetAll();else if(Array.isArray(e.reset))for(const t of e.reset)await n(t,(e,t)=>e.reset(t));let i,l;if(e.preset&&await applyMapPreset(t.baseMap.ttMap,e.preset),e.theme)try{i=await(async e=>"colors"in e?deriveMapColors(e.colors,{mode:e.mode}):deriveMapColorsFromImage(e.imageUrl,{mode:e.mode}))(e.theme),l=await(async(e,t)=>{const r=mapColorsLightDark(t);if(e.styleLightDarkTheme===r)return;const o=LIGHT_DARK_VARIANTS[standardStyleIdOf(e.getStyleInput())]?.[r];return o&&await e.setStyle(o),o})(t.baseMap.ttMap,i),o.setMapColors(i)}catch(r){s("theme",r)}for(const[t,r]of Object.entries(e.set??{}))await n(t,(e,t)=>e.set(t,r));return{success:!0,settings:await settingsOf(t),rejected:a,...i&&{themeColors:i},...l&&{switchedStyle:l}}}catch(r){return toToolError("Failed to set map styling",r)}},actionSchema=z.enum(["show","hide","remove"]).describe("`show` renders the listed entries (lazy-init their CustomGeoJSONModule); `hide` removes them from the map but keeps them in session state; `remove` hides AND drops them from history."),updateByodDisplaySchema=z.object({entryIds:z.array(z.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:actionSchema,clearAll:z.boolean().optional().describe('Only valid with `action: "hide"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.'),hideOthers:z.boolean().optional().describe('Only valid with `action: "show"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).')}).refine(e=>!("hide"!==e.action||!e.clearAll)||!!e.entryIds?.length,{message:'`entryIds` is required unless `action: "hide"` with `clearAll: true`.'}).refine(e=>!e.clearAll||"hide"===e.action,{message:'`clearAll: true` is only valid with `action: "hide"`.'}).refine(e=>!e.hideOthers||"show"===e.action,{message:'`hideOthers: true` is only valid with `action: "show"`.'}),updateByodDisplayOutputSchema=z.union([z.object({action:actionSchema,affectedIds:z.array(z.string()).describe("Entry ids that were actually shown / hidden / removed."),shown:z.array(z.string()).describe("BYOD entry ids currently rendered on the map after this call.")}),toolErrorSchema]),entryIdsMatching=(e,t=[])=>{const r=t.map(e=>e.toLowerCase());return e.filter(e=>r.some(t=>e.label.toLowerCase().includes(t))).map(e=>e.id)},renderEntryGeometries=async(e,t,r)=>{const o=e.places.entries.find(e=>e.id===t);if(!o)return{rendered:0,skipped:[]};const a=r.placeIds?new Set(r.placeIds):void 0,s=a?o.data.filter(e=>a.has(e.id)):o.data,n=[],i=[];for(const l of s){const t=await fetchPlaceBoundary(e,l);"skip"in t?n.push({placeId:l.id,reason:t.skip}):i.push(t.feature)}return i.length>0&&await e.places.showPlaceGeometries(i,r.fillStyle??"outline",r.mode??"replace",t),{rendered:i.length,skipped:n}},shownEntrySummarySchema=z.object({id:z.string(),label:z.string(),featureCount:z.number(),markerType:z.enum(placesMarkerTypes).describe("How the entry is rendered on the map.")}),updatePlacesDisplayOutputSchema=z.union([z.object({success:z.literal(!0),totalShown:z.number(),shown:z.array(shownEntrySummarySchema).describe("Entries currently on the map, with their labels."),actuallyAdded:z.array(z.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:z.array(z.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),geometriesRendered:z.number().optional().describe("Number of boundary polygons rendered when `geometry` was set."),geometriesSkipped:z.array(z.object({placeId:z.string(),reason:z.string()})).optional().describe("Places whose boundary could not be rendered (no geometry data source, fetch failure).")}),toolErrorSchema]),updatePlacesDisplaySchema=z.object({add:z.array(z.string()).optional().describe('Entry ids to show (e.g. ["places-1"]). Omit while still setting `markerType` or `geometry` to target the most recently stored entry.'),remove:z.array(z.string()).optional().describe("Entry ids to hide."),addMatching:z.array(z.string()).optional().describe("Category labels to show."),removeMatching:z.array(z.string()).optional().describe("Category labels to hide."),clear:z.boolean().optional().describe("Hide every displayed entry."),markerType:z.enum(placesMarkerTypes).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:geometryDisplayConfigSchema.optional(),fitBounds:z.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.markerType||void 0!==e.geometry,{message:"updatePlacesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, markerType, geometry."}),focusableSectionTypes=[...countedRouteSectionTypes,"motorway","distinct","leg","country","curve","curvyStretch"],shownRouteEntrySummarySchema=z.object({id:z.string(),label:z.string(),routeCount:z.number().describe("Number of route alternatives in the entry."),selectedIndex:z.number().describe("Highlighted alternative index.")}),updateRoutesDisplayOutputSchema=z.union([z.object({success:z.literal(!0),totalShown:z.number(),shown:z.array(shownRouteEntrySummarySchema).describe("Routes entries currently on the map, with their labels."),actuallyAdded:z.array(z.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:z.array(z.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),focused:z.array(z.object({entryId:z.string(),sectionCount:z.number().describe("0 = the selected route has none of them; do not retry."),lengthInMeters:z.number()})).optional().describe("Present when `focus` was set.")}),toolErrorSchema]),focusSchema=z.object({sectionTypes:z.array(z.enum(focusableSectionTypes)).min(1).optional().describe("Section types to ring. `distinct` = the stretches only this alternative drives; `curvyStretch` = runs of curves."),roads:z.array(z.string()).min(1).optional().describe('Road numbers or street names to ring, e.g. ["A2"].'),pick:z.union([z.enum(["all","longest","mostDelay","curviest"]),z.number().int().min(0)]).optional().describe("Which matching sections: `all` (default), the `longest`, the traffic one with `mostDelay`, the `curviest` curve or curvy stretch, or a 0-based index in driving order.")}).refine(e=>void 0!==e.sectionTypes||void 0!==e.roads,{message:"focus needs `sectionTypes` and/or `roads`."}),updateRoutesDisplaySchema=z.object({add:z.array(z.string()).optional().describe('Entry ids to show (e.g. ["routes-1"]). Omit while setting `selectedIndex`, `focus` or `waypoints` to target the most recently stored entry.'),remove:z.array(z.string()).optional().describe("Entry ids to hide."),addMatching:z.array(z.string()).optional().describe("Label substrings to show."),removeMatching:z.array(z.string()).optional().describe("Label substrings to hide."),clear:z.boolean().optional().describe("Hide every displayed entry."),selectedIndex:z.number().int().min(0).optional().describe("Which alternative to highlight on the targeted entries."),focus:focusSchema.nullable().optional().describe("Ring sections of the selected route of the targeted entries and fit the camera to them; `null` clears the ring."),waypoints:z.boolean().optional().describe("Show (true) or hide (false) the origin/stop/destination pins of the targeted entries."),fitBounds:z.boolean().optional().describe("Pan/zoom to fit the resulting display, or the focused sections. Defaults to true when `add` / `addMatching` or a `focus` with sections is set.")}).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.focus||void 0!==e.waypoints,{message:"updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, focus, waypoints."}),lengthOf=(e,t)=>getRouteProgressForSection(e,t)?.delta.distanceInMeters??0,normalizeRoadName=e=>e.replace(/\s+/g,"").toLowerCase(),hasSpan=e=>void 0!==e.startPointIndex&&void 0!==e.endPointIndex,roadSectionsNamed=(e,t)=>{const r=new Set(t.map(normalizeRoadName));return getImportantRoads(e).filter(e=>[e.name,e.streetName,...e.roadNumbers].some(e=>void 0!==e&&r.has(normalizeRoadName(e)))).flatMap(e=>e.sections)},sectionsToFocus=(e,t)=>{const r=[...(t.sectionTypes??[]).flatMap(t=>e.properties.sections[t]??[]).filter(hasSpan),...roadSectionsNamed(e,t.roads??[])].sort((e,t)=>e.startPointIndex-t.startPointIndex),{pick:o="all"}=t;if("all"===o)return r;if("number"==typeof o)return r.slice(o,o+1);const a=t=>SCORE_FOR_PICK[o](e,t);return r.length?[r.reduce((e,t)=>a(t)>a(e)?t:e,r[0])]:[]},SCORE_FOR_PICK={longest:lengthOf,mostDelay:(e,t)=>t.delayInSeconds??0,curviest:(e,t)=>t.curvinessScore??0},summarySchema=z.object({id:z.string(),label:z.string(),activeMetric:z.string().optional(),displayMode:z.string().optional()}),updateTrafficAreaAnalyticsDisplaySchema=z.object({add:z.array(z.string()).optional().describe('Entry ids to render (e.g. ["tta-0"]). Use `recallState` to list available ids.'),remove:z.array(z.string()).optional().describe("Entry ids to hide."),addMatching:z.array(z.string()).optional().describe("Label substrings to show."),removeMatching:z.array(z.string()).optional().describe("Label substrings to hide."),clear:z.boolean().optional().describe("Hide every displayed entry."),fitBounds:z.boolean().optional().describe("Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear,{message:"updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear."}),updateTrafficAreaAnalyticsDisplayOutputSchema=z.union([z.object({success:z.literal(!0),totalShown:z.number(),shown:z.array(summarySchema).describe("Traffic-area-analytics entries currently on the map."),actuallyAdded:z.array(z.string()),actuallyRemoved:z.array(z.string())}),toolErrorSchema]),summarizeEntry=e=>({id:e.id,label:e.label,...e._config?.activeMetric&&{activeMetric:e._config.activeMetric},...e._config?.displayMode&&{displayMode:e._config.displayMode}}),updateWaypointsDisplayOutputSchema=z.union([z.object({success:z.literal(!0),visible:z.boolean(),count:z.number()}),toolErrorSchema]),updateWaypointsDisplaySchema=z.object({visible:z.boolean().optional().describe("Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.")}),DATA_TOOL_SHARED_FRAMING="Dynamic-JS sandbox over any combination of places / routes / incidents / geometries / trafficAreaAnalytics / byod entries. Each input arg is `undefined` when its `*EntryIDs` was omitted.",DEFAULT_TOOLS_TYPED={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:locatePlaceSchema,outputSchema:locatePlaceOutputSchema,execute:async(e,t,r)=>{const{query:o,queryAs:a,waypointIndex:s,where:n,show:i,entryId:l,geometry:c}=e;try{const e=await(async(e,t,r)=>e&&"global"!==e.mode?"within"===e.mode?(async(e,t,r)=>{const o=await resolveWithin({viewport:e.viewport,boundingBox:e.boundingBox,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0,placeIds:e.placeId?[e.placeId]:void 0},toolStateToWhereContext(t,r));return isResolveError(o)?o:0===o.length?{bias:void 0}:{bias:{boundingBox:bboxFromBBoxes(o.map(e=>e.bbox))}}})(e,t,r):(async(e,t,r)=>{if(e.position)return{bias:{position:e.position}};const{position:o}=await resolveNearby({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},toolStateToWhereContext(t,r));return{bias:o?{position:o}:void 0}})(e,t,r):{bias:void 0})(n,t,r);if("error"in e)return e;const d=await locatePlace(o,a,e.bias,r);if(!d)return{error:`No result found for "${o}"`};const u=await t.places.addPlaceResult(d,makePlacesLabel(d,{query:o}),l);void 0!==s&&t.routing.setWaypointAt(s,d);const h=i?await(async(e,t,r,o)=>{const a=await showResultsOnMap(e,[r],{...o,zoomMode:"none"});if("auto"===o.zoomMode){if(t.bbox)e.baseMap.mapLibreMap.fitBounds(t.bbox);else{const r=getPosition(t);r&&e.baseMap.mapLibreMap.flyTo({center:r,zoom:13})}a.zoomMode=!0}return a})(t,d,u,i):void 0;let p,y;if(c){const e=await showEntryGeometries(t,u,c,r);p=e.fetched>0,e.shown&&(y=!0)}return{...summarizePlace(d),placesEntryId:u,...void 0!==s&&{waypointIndex:s},...h&&{shown:h},...void 0!==p&&{geometryFetched:p},...void 0!==y&&{geometryShown:y}}}catch(d){return toToolError("Location resolution failed",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:reverseGeocodeSchema,outputSchema:reverseGeocodeOutputSchema,execute:async(e,t,r)=>{const{position:o}=e,a=o;try{const e=withAgentToolkitHeaders({position:a,signal:r?.signal}),o=await reverseGeocode(e);if(o.properties){const e=await t.places.addPlaceResult(o,makePlacesLabel(o));return{...summarizePlace(o),placesEntryId:e}}return{error:"No result found for the given coordinates"}}catch(s){return toToolError("Reverse geocoding failed",s)}},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=discoverPlacesMetadata,{...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:buildDiscoverPlacesSchema(),outputSchema:buildDiscoverPlacesOutputSchema(),execute:buildExecuteDiscoverPlaces()};var t},processData:(metadata={classificationPrompt:`${DATA_TOOL_SHARED_FRAMING} 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), cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile). NOT for ringing / zooming to route sections (tolls, jams, a road) — that is updateRoutesDisplay \`focus\`.`,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: { fillStyle: "outline" } } })','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: { fillStyle: "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: { fillStyle: "filled" } } })'],examplePrompts:["Keep only the vegan restaurants","Draw lines from this hub to every nearby cafe","Show the h3 hex coverage of these places","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??ALL_ENTRY_DATA_KINDS;return{description:buildProcessDataDescription(r,e.scope),inputSchema:buildProcessDataSchema(r,e.scope),outputSchema:processDataOutputSchema,execute:executeProcessData,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildProcessDataScopePrompt(r),...t}})(e,metadata)),analyseData:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??ALL_ENTRY_DATA_KINDS;return{description:buildAnalyseDataDescription(r,e.scope),inputSchema:buildAnalyseDataSchema(r,e.scope),outputSchema:analyseDataOutputSchema,execute:executeAnalyseData,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildAnalyseDataScopePrompt(r),...t}})(t,e))({classificationPrompt:`${DATA_TOOL_SHARED_FRAMING} 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 breakdowns (per-leg / per-country, CHARTS of alternatives — NOT how they differ or which roads they take, which recallState says); 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 ?? [])) { const t = routeUtils.getSectionsWithin(r, { within: [c], types: [\'traffic\'] }).traffic; byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (t ? t.sections.reduce((s, x, i) => s + (x.delayInSeconds ?? 0) * t.shares[i], 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","How many hairpins does my route have?"],relatedTools:["processData","focusIncidents","recallState"],dependsOn:["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","addByodSource","processData","recallState"]}),setRoute:{description:'Calculate or recalculate a car route. `locations` sets origin/stops/destination, `parameters` the cost model / alternatives / when; pass at least one. With `parameters` alone it recalculates the current waypoints, or only stores the options when there are none. Each route summary carries `sectionCounts` (tollRoad, ferry, traffic…, absent = none), `countries` crossed and `via` (main roads) and `timeInCurvesPercent` (how winding); an alternative also carries `comparedToBest` (sections it adds / avoids against route 0) — answer "are there tolls?", "which roads?", "which is least winding?" and "how do the routes differ?" from them, no analyseData needed. To MONITOR traffic between places, set `monitor: true` with `costModel.traffic: "historical"` + `maxAlternatives: 1-2` — no separate startRouteMonitor call. Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallState / addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / analyseData instead.',classificationPrompt:'Calculate a NEW route from its full list of places, or change the current route\'s options (cost model / alternatives / when). Changing, adding or removing ONE waypoint of an existing route is replaceWaypointInRoute / addWaypointsToRoute / removeWaypointsFromRoute. To MONITOR / track the live traffic BETWEEN two places, START here (traffic "historical" + maxAlternatives 1-2); startRouteMonitor needs a route first.',inputSchema:setRouteSchema,outputSchema:setRouteOutputSchema,execute:async(e,t,r)=>{const{locations:o,parameters:a,showOnMap:s,monitor:n,hidePreviousEntries:i}=e;try{let e;if(a&&t.routing.setParams(a),o){const a=await Promise.all(o.map(e=>resolveLocationInput(e,t,r))),s=[];for(let e=0;e<o.length;e++){if(null!==a[e])continue;const t=o[e];"query"in t?s.push(`"${t.query}"`):"placeIdOrEntryId"in t&&s.push(`placeIdOrEntryId "${t.placeIdOrEntryId}"`)}if(s.length>0)return{error:`Could not resolve: ${s.join(", ")}`};if(e=resolveRouteWaypoints(a.map(e=>e?.place??null)),!e)return{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}else if(e=resolveRouteWaypoints(t.routing.planningSlots),!e)return{success:!0};return await calculateAndAddRoute(t,e,{showOnMap:s,hidePreviousEntries:i,monitor:n},r)}catch(l){return toToolError("Route calculation failed",l)}},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","Plan a curvy route from Innsbruck to Bolzano for my motorbike","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:addWaypointsToRouteSchema,outputSchema:addWaypointsToRouteOutputSchema,execute:async(e,t,r)=>{const{origin:o,destination:a,stops:s,showOnMap:n,hidePreviousEntries:i}=e;try{const e=await(async e=>{const t=e.routing.currentEntryModule;if(!t)return"No existing route on the map. Call setRoute or updateRoutesDisplay first.";const r=t.getShown()?.mainLines;if(!r||!r.features.length)return"No existing route available. Call setRoute first.";const o=e.routing.entries.find(e=>e._module===t)?.waypoints;return!o||o.length<2?"No existing waypoints available. Call setRoute first to create a route with waypoints.":{shownRouteLines:r,entryWaypoints:o}})(t);if("string"==typeof e)return{error:e};const{shownRouteLines:l,entryWaypoints:c}=e,d=await(async(e,t,r)=>{const{origin:o,destination:a,stops:s=[]}=e,n=[...o?[{kind:"origin",location:o}]:[],...a?[{kind:"destination",location:a}]:[],...s.map(e=>({kind:"stop",location:e}))],i=await Promise.all(n.map(({location:e})=>resolveLocationInput(e,t,r))),l=[];for(let d=0;d<n.length;d++)null===i[d]&&l.push(labelForLocationInput(n[d].location));if(l.length>0)return{error:`Could not resolve: ${l.join(", ")}`};const c={stops:[]};for(let d=0;d<n.length;d++){const e=i[d]?.place;"origin"===n[d].kind?c.origin=e:"destination"===n[d].kind?c.destination=e:c.stops.push(e)}return c})({origin:o,destination:a,stops:s},t,r);if("error"in d)return d;const u=[...d.origin?[d.origin]:[],...c,...d.destination?[d.destination]:[]],h=d.stops.length>0?withInsertedWaypoints(l.features[0],u,d.stops):u,p=resolveRouteWaypoints(h);return p?await calculateAndAddRoute(t,p,{showOnMap:n,hidePreviousEntries:i},r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(l){return toToolError("Failed to add waypoints to route",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:removeWaypointsFromRouteSchema,outputSchema:removeWaypointsFromRouteOutputSchema,execute:async(e,t,r)=>{const{waypointIndices:o,showOnMap:a,hidePreviousEntries:s}=e;try{const e=((e,t)=>{if(!e||e.length<2)return"No existing waypoints available. Call setRoute 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 o of t)if(o<0||o>=e.length)return`Invalid waypoint index ${o}. 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,o);if("string"==typeof e)return{error:e};const n=resolveRouteWaypoints(e);return n?await calculateAndAddRoute(t,n,{showOnMap:a,hidePreviousEntries:s},r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(n){return toToolError("Failed to remove waypoints from route",n)}},tags:["route","waypoint"],examples:["removeWaypointsFromRoute({ waypointIndices: [1] }) // remove a single stop","removeWaypointsFromRoute({ waypointIndices: [1, 2] }) // remove multiple stops in one call"],examplePrompts:["Remove the second stop from my route","Delete the Utrecht stop","Drop the last two intermediate stops"],relatedTools:["addWaypointsToRoute","replaceWaypointInRoute","setRoute","updateRoutesDisplay"],dependsOn:["setRoute"]},replaceWaypointInRoute:{description:"Replace a single waypoint in the current route in place — origin, destination, or any intermediate stop by 0-based index. Always recalculates. Other waypoints are preserved. Requires an existing route. Use addWaypointsToRoute to extend the route, removeWaypointsFromRoute to drop a waypoint, or setRoute to fully replace all waypoints.",classificationPrompt:"Overwrite a single waypoint of the current route in place — by 'origin', 'destination', or numeric index — and recalculate. Other waypoints are kept.",inputSchema:replaceWaypointInRouteSchema,outputSchema:replaceWaypointInRouteOutputSchema,execute:async(e,t,r)=>{const{waypointIndex:o,location:a,showOnMap:s,hidePreviousEntries:n}=e;try{const e=t.routing.currentWaypoints;if(!e||e.length<2)return{error:"No existing waypoints available. Call setRoute first to create a route with waypoints."};const c=(i=o,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 resolveLocationInput(a,t,r);if(!d)return{error:`Could not resolve location: ${labelForLocationInput(a)}`};const u=[...e];u[c]=d.place;const h=resolveRouteWaypoints(u);return h?await calculateAndAddRoute(t,h,{showOnMap:s,hidePreviousEntries:n},r):{error:"Not enough valid waypoints to calculate a route (minimum 2)."}}catch(c){return toToolError("Failed to replace waypoint in route",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:'Look up POICategory codes by natural-language name (e.g. "gym", "italian food", "bookstore"), or list all codes when no filter is given. A search does not need it: discoverPlaces poiCategories takes the plain words.',classificationPrompt:'Look up or list POICategory codes (e.g. "what is the code for gym?"). Not a step before discoverPlaces, which takes category words itself.',inputSchema:getPoiCategoryCodesSchema,outputSchema:getPoiCategoryCodesOutputSchema,execute:async(e,t,r)=>{const{filters:o,language:a}=e;try{const e=withAgentToolkitHeaders({filters:o,language:a,signal:r?.signal}),t=await getPOICategoryCodes(e);return{count:t.length,codes:t}}catch(s){return toToolError("Failed to fetch POI category codes",s)}},tags:["discover","place"],examples:["getPOICategoryCodes()",'getPOICategoryCodes(filters: ["gym"])','getPOICategoryCodes(filters: ["italian restaurant"]) // multi-word phrase, best match first','getPOICategoryCodes(filters: ["restaurant"], language: "fr-FR")'],examplePrompts:["What POI categories are available?","What's the code for gym?"],relatedTools:["discoverPlaces","setMapStyling"]},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: ${trafficIncidentRequestCategories.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. DO NOT pick to show / hide the map's own vector-tile incidents overlay ("show the traffic incidents overlay", "hide accident icons") — that is setMapStyling. Co-pick analyseData (aggregations / charts / clusters; pass \`monitor: { entryId }\` for the recurring spec) and focusIncidents (highlight a subset) when the query implies them.`,inputSchema:getTrafficIncidentsSchema,outputSchema:getTrafficIncidentsOutputSchema,execute:async(e,t,r)=>{const{where:o,categoryFilter:a,timeValidityFilter:s,label:n,show:i=!0,fitBounds:l=!0,monitor:c,monitorIntervalMs:d}=e,u=o??{mode:"within",viewport:!0},h={...a&&{categoryFilter:a},...s&&{timeValidityFilter:s}};try{const e=await(async(e,t)=>{const r=[];if(void 0!==e.range){const o=((e,t)=>{const r=getRangePolygons(e,t);return"error"in r?r:{bboxes:r.polygons.map(e=>bboxFromGeoJSON(e)).filter(e=>!!e)}})(t,e.range);if("error"in o)return o;r.push(...o.bboxes)}const o={viewport:e.viewport,boundingBox:e.boundingBox,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},a=await resolveWithin(o,toolStateToWhereContext(t));if(isResolveError(a))return a;r.push(...a.map(e=>e.bbox));const s=a.filter(e=>"query"===e.source&&e.label).map(e=>({matched:e.label,...void 0!==e.query&&{query:e.query}})),n=a.find(e=>"route"===e.source)?.label;return 0===r.length?{error:"No area resolved from `where`."}:{bbox:bboxFromBBoxes(r),routeLabel:n,...s.length>0&&{resolvedAreas:s}}})(u,t);if("error"in e)return e;const{bbox:o,routeLabel:a,resolvedAreas:s}=e,p=withAgentToolkitHeaders({...h,bbox:o,signal:r?.signal}),y=await trafficIncidentDetails(p),m={bbox:o,...h},f=Date.now(),g=n??((e,t,r,o)=>{const a=[];e.categoryFilter?.length&&a.push(`filtered: ${e.categoryFilter.join(",")}`),e.timeValidityFilter?.length&&a.push(`when: ${e.timeValidityFilter.join(",")}`);let s=o?.length?o.map(e=>e.matched).join(", "):void 0;if(void 0===s&&t&&!t.viewport&&(t.queries?.length?s=t.queries.map(e=>e.query).join(", "):r?s=`along ${r}`:t.range&&(s=t.range)),!s){const[t,r,o,a]=e.bbox;s=`bbox ${t.toFixed(2)},${r.toFixed(2)} → ${o.toFixed(2)},${a.toFixed(2)}`}return a.length?`${s} — ${a.join("; ")}`:s})(m,u,a,s);if(!y.features.length)return{count:0,entryId:await t.trafficIncidents.addIncidentsEntry([],m,g,f),entries:entriesSummary(t),...s&&s.length>0&&{resolvedAreas:s}};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:o,capturedAt:f,label:g},{fetchIncidents:async e=>{const t=withAgentToolkitHeaders({...h,bbox:e});return(await trafficIncidentDetails(t)).features}},{intervalMs:d,skipInitialTick:!0}),{count:y.features.length,entryId:b,entries:entriesSummary(t),monitoring:w,...s&&s.length>0&&{resolvedAreas:s}}}catch(p){return toToolError("Failed to get traffic incidents",p)}},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","What roadworks are planned in Rotterdam?"],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:focusIncidentsSchema,outputSchema:focusIncidentsOutputSchema,execute:async(e,t)=>{const{incidentsEntryID:r,incidentIds:o,reason:a}=e;if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};const s=t.trafficIncidents.setFocus(r,o,a);return 0===s.focusedCount?{error:`None of the ${o.length} requested ids exist on entry "${r}". The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.`}:{incidentsEntryID:r,focusedCount:s.focusedCount,droppedIds:s.droppedIds,...a&&{reason:a}}},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??ALL_ENTRY_DATA_KINDS,{active:o}=resolveClusterKinds(r,e.scope);return{description:buildClusterIncidentsDescription(o),inputSchema:buildClusterIncidentsSchema(r,e.scope),outputSchema:clusterIncidentsOutputSchema,execute:executeClusterIncidents,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildClusterIncidentsScopePrompt(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:monitorAnalysisSchema,outputSchema:monitorAnalysisOutputSchema,execute:async(e,t)=>((e,t,r)=>{const o=e.analyses.job(t);return!!o&&(o.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:(e=>t=>{const r=t.enabledDataKinds??ALL_ENTRY_DATA_KINDS;return{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.',inputSchema:buildCreateTrackerSchema(r,t.scope),outputSchema:createTrackerOutputSchema,execute:executeCreateTracker,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildCreateTrackerScopePrompt(r),...e}})({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.',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:clearTrackerSchema,outputSchema:clearTrackerOutputSchema,execute:async(e,t)=>{const r=t.trackers.trackers;if(0===r.length)return{error:"No trackers to clear."};const o=e.trackerId??(1===r.length?r[0].id:void 0);if(void 0===o)return{error:`Which tracker? Pass trackerId — one of: ${r.map(e=>e.id).join(", ")}.`};const a=t.trackers.get(o);return a?(t.trackers.unregister(o),{cleared:!0,trackerId:o,name:a.name}):{error:`No tracker "${o}". 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:getTrackerHistorySchema,outputSchema:getTrackerHistoryOutputSchema,execute:async(e,t)=>{const r=t.trackers.log({trackerId:e.trackerId}),o=[...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}})),a=r.at(-1);return{events:o,count:o.length,...a&&{coverage:{oldestRetainedAt:r[0].at,newestAt:a.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:getTrackersSchema,outputSchema:getTrackersOutputSchema,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:setTrafficIncidentsMonitorSchema,outputSchema:setTrafficIncidentsMonitorOutputSchema,execute:async(e,t)=>{const{incidentsEntryID:r,enabled:o,intervalMs:a}=e,s=t.trafficIncidents.entries.find(e=>e.id===r);if(!s)return{error:`No incidents entry with id "${r}". Call getTrafficIncidents first.`};if(!o){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 n=t.trafficIncidents.isMonitored(r),{bbox:i,categoryFilter:l,timeValidityFilter:c}=s.params,d={...l&&{categoryFilter:l},...c&&{timeValidityFilter:c}};return t.trafficIncidents.startMonitoring(r,{bbox:i,capturedAt:s.timestamp,label:s.label},{fetchIncidents:async e=>{const t=withAgentToolkitHeaders({...d,bbox:e});return(await trafficIncidentDetails(t)).features}},a?{intervalMs:a}:void 0),{incidentsEntryID:r,enabled:!0,alreadyInState:n}},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:startRouteMonitorSchema,outputSchema:startRouteMonitorOutputSchema,execute:async(e,t)=>{const{routesEntryID:r,intervalMs:o}=e,a=t.routing.entries.find(e=>e.id===r);if(!a)return{error:`No routes entry with id "${r}". Calculate a route first via setRoute.`};const s=t.routing.isMonitored(r),{waypoints:n,params:i}=a;return t.routing.startMonitoring(r,{recalculate:buildRouteRecalc(n,i)},o?{intervalMs:o}:void 0),{routesEntryID:r,alreadyRunning:s}},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:stopRouteMonitorSchema,outputSchema:stopRouteMonitorOutputSchema,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 o=t.routing.isMonitored(r);return t.routing.stopMonitoring(r),{routesEntryID:r,wasRunning:o}},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. The `hours` filter and every returned day and hour label are UTC.",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:getTrafficAreaAnalyticsSchema,outputSchema:getTrafficAreaAnalyticsOutputSchema,execute:async(e,t,r)=>{const{location:o,bbox:a,showOnMap:s,startDate:n,endDate:i,days:l,metrics:c,functionalRoadClasses:d,hours:u}=e,h=process.env.MOVE_PORTAL_KEY;if(!h)return{error:"MOVE_PORTAL_KEY environment variable is not set."};try{const e=await(async(e,t,r,o)=>{if(e){const t=await resolveLocationInput(e,r,o);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 a=withAgentToolkitHeaders({geometries:[t.place],signal:o?.signal}),s=(await geometryData(a))?.features?.[0]?.geometry;return s||{error:`No boundary polygon found for "${t.name}". Try a bbox instead.`}}if(!t)return{error:"Provide a location or bbox to define the analytics area."};const[a,s,n,i]=t;return polygonFromBBox([a,s,n,i]).geometry})(o,a,t,r);if("error"in e)return e;const p=((e,t,r)=>e&&e.length>0?{days:e}:t?r?{startDate:t,endDate:r}:{startDate:t}:{})(l,n,i),y=withAgentToolkitHeaders({apiKey:h,...p,metrics:c,functionalRoadClasses:d??"all",hours:u??"all",geometry:e,signal:r?.signal}),m=await trafficAreaAnalytics(y),f={start:isoDate(m.properties.startDate),end:isoDate(m.properties.endDate)},g=`${m.features[0]?.properties?.name??"analytics"} (${f.start} → ${f.end})`,b=await t.trafficAreaAnalytics.addEntry(m,g,{...p,metrics:c,functionalRoadClasses:d??"all",hours:u??"all"});return s&&await(async(e,t)=>{await e.trafficAreaAnalytics.showEntry(t);const r=e.trafficAreaAnalytics.findById(t);if(!r)return;const o=await e.trafficAreaAnalytics.getEntryModule(t),a=r.data.properties?.metrics?.[0]??"congestionLevel";o.updateConfig({activeMetric:a}),fitMapToGeoJSON(e.baseMap.mapLibreMap,r.data.features,{pitch:45})})(t,b),((e,t,r)=>{const o=t.features[0]?.properties;if(!o)return{entryId:e,dateRange:r,baseData:{},metrics:[],tileCount:0,availableGranularities:[]};const a=[];return o.timedData?.yearly?.length&&a.push("yearly"),o.timedData?.monthly?.length&&a.push("monthly"),o.timedData?.weekly?.length&&a.push("weekly"),o.timedData?.daily?.length&&a.push("daily"),o.timedData?.hourly?.length&&a.push("hourly"),o.timedData?.average?.length&&a.push("average"),{entryId:e,...o.name&&{name:o.name},dateRange:r,baseData:o.baseData,metrics:t.properties?.metrics??[],tileCount:o.tiledData?.tiles?.length??0,availableGranularities:a}})(b,m,f)}catch(p){return toToolError("Failed to get traffic area analytics",p)}},tags:["traffic","location"],examples:["getTrafficAreaAnalytics({ bbox: [4.728, 52.278, 5.080, 52.479], metrics: ['speed', 'congestionLevel'], startDate: '2025-03-01', endDate: '2025-03-07' })","getTrafficAreaAnalytics({ bbox: [...], metrics: ['congestionLevel'], startDate: '2025-06-02', hours: [5, 6, 7, 15, 16] }) // Amsterdam summer rush hours [7, 8, 9, 17, 18] local (UTC+2), in UTC"],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 setMapStyling).",inputSchema:updatePlacesDisplaySchema,outputSchema:updatePlacesDisplayOutputSchema,execute:async(e,t)=>{const{clear:r,markerType:o,geometry:a,fitBounds:s}=e;try{const n=((e,t)=>{const{add:r,remove:o,addMatching:a,removeMatching:s,markerType:n,geometry:i}=t;let l=[...r??[],...entryIdsMatching(e.places.entries,a)];const c=[...o??[],...entryIdsMatching(e.places.entries,s)];if(void 0===r&&void 0===a&&(void 0!==n||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 n)return n;const{add:i,remove:l}=n,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!==o||!a;let u;i.length>0&&d&&await(async(e,t,r,o)=>{const a=void 0!==r?t:t.filter(e=>!o.has(e));a.length>0&&await e.places.addShownEntries(a,r??"pin")})(t,i,o,c),a&&i.length>0&&(u=await(async(e,t,r)=>{const o=[];let a=0;for(const s of t){const t=await renderEntryGeometries(e,s,r);a+=t.rendered,o.push(...t.skipped)}return{rendered:a,skipped:o}})(t,i,a));const h=t.places.shownEntryIds,p=i.filter(e=>!c.has(e)&&h.has(e)),y=l.filter(e=>c.has(e)&&!h.has(e)),m=s??i.length>0,f=t.places.getShownFeatures();m&&fitMapToGeoJSON(t.baseMap.mapLibreMap,f);const g=t.places.entries.filter(e=>h.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:p,actuallyRemoved:y,...u&&{geometriesRendered:u.rendered},...u?.skipped.length&&{geometriesSkipped:u.skipped}}}catch(n){return toToolError("updatePlacesDisplay failed",n)}},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: { fillStyle: "outline" } }) // pin AND outline','updatePlacesDisplay({ add: ["places-2"], geometry: { placeIds: ["G55fc4abe-..."], fillStyle: "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 and swap which routes entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show this"). `selectedIndex` switches the highlighted alternative; `focus` rings sections of the selected route — its tolls, ferries, traffic, curves, a leg, a named road — and zooms to them ("where are the tolls?", "zoom to the worst jam"); `waypoints: false` hides the pins. With no `add`/`addMatching`, these target the most recently stored entry. Does not delete from history. Does not call any service. The routes\' look (colour, line width, section styling, summary bubbles) is setMapStyling\'s `routes.*` knobs.',classificationPrompt:"Show, hide and swap routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, show or hide a calculated route's waypoint pins — and ring / zoom to sections of a route via `focus`: its tolls, ferries, traffic jams (the worst one), curves (the curviest stretch), a leg, a named road, the stretch only an alternative drives. NOT the routes' colour, width or summary bubbles (setMapStyling).",inputSchema:updateRoutesDisplaySchema,outputSchema:updateRoutesDisplayOutputSchema,execute:async(e,t)=>{const{clear:r,selectedIndex:o,focus:a,waypoints:s,fitBounds:n}=e;try{const i=((e,t)=>{const{add:r,remove:o,addMatching:a,removeMatching:s,selectedIndex:n,focus:i,waypoints:l}=t;let c=[...r??[],...entryIdsMatching(e.routing.entries,a)];const d=[...o??[],...entryIdsMatching(e.routing.entries,s)];if(void 0===r&&void 0===a&&(void 0!==n||void 0!==i||void 0!==l)){const t=e.routing.entries.at(-1)?.id;if(!t)return{error:"No routes available to display. Calculate a route first via setRoute."};c=[t]}const u=new Set(e.routing.entries.map(e=>e.id)),h=c.filter(e=>!u.has(e));return h.length>0?{error:`Unknown routes entry ids: ${h.join(", ")}. Use recallState to list available entries.`}:{add:c,remove:d}})(t,e);if("error"in i)return i;const{add:l,remove:c}=i,d=void 0!==o&&t.routing.entries.find(e=>l.includes(e.id)&&o>=e.data.features.length);if(d)return{error:`Entry ${d.id} has ${d.data.features.length} route(s); selectedIndex ${o} is out of range.`};const{beforeShown:u,afterShown:h}=await(async(e,t)=>{const r=new Set(e.routing.shownEntryIds);if(t.clear)for(const o of e.routing.shownEntryIds)await e.routing.hideEntry(o);for(const o of t.remove)await e.routing.hideEntry(o);for(const o of t.add)void 0!==t.waypoints&&await e.routing.setWaypointsShown(o,t.waypoints),e.routing.shownEntryIds.has(o)||await e.routing.showEntry(o);if(void 0!==t.selectedIndex)for(const o of t.add)await(await e.routing.getEntryRoutingModule(o)).selectRoute(t.selectedIndex);return{beforeShown:r,afterShown:e.routing.shownEntryIds}})(t,{add:l,remove:c,clear:!!r,selectedIndex:o,waypoints:s}),p=void 0===a?void 0:await(async(e,t,r)=>{const o=[];for(const a of t){const t=await e.routing.getEntryRoutingModule(a.id),s=a.data.features[selectedRouteIndex(t)],n=r&&s?sectionsToFocus(s,r):[];a._sectionFocus=r?e=>sectionsToFocus(e,r).map(e=>e.id):void 0,await t.setSectionFocus(r?n.map(e=>e.id):null),o.push({entryId:a.id,sectionCount:n.length,lengthInMeters:Math.round(n.reduce((e,t)=>e+lengthOf(s,t),0)),bbox:bboxFromBBoxes(n.map(e=>getSectionBBox(s,e)))})}return o})(t,t.routing.entries.filter(e=>l.includes(e.id)),a),y=l.filter(e=>!u.has(e)&&h.has(e)),m=c.filter(e=>u.has(e)&&!h.has(e)),f=bboxFromBBoxes(p?.map(e=>e.bbox)??[]),g=void 0!==e.add||void 0!==e.addMatching;(n??(g&&l.length>0||void 0!==f))&&(f?fitMapToBBox(t.baseMap.mapLibreMap,f):((e,t)=>{const r=[];for(const o of e.routing.entries)t.has(o.id)&&r.push(...o.data.features);fitMapToGeoJSON(e.baseMap.mapLibreMap,r)})(t,h));const b=t.routing.entries.filter(e=>h.has(e.id)).map(e=>({id:e.id,label:e.label,routeCount:e.data.features.length,selectedIndex:selectedRouteIndex(e._module)}));return{success:!0,totalShown:h.size,shown:b,actuallyAdded:y,actuallyRemoved:m,...p&&{focused:p.map(({bbox:e,...t})=>t)}}}catch(i){return toToolError("updateRoutesDisplay failed",i)}},tags:["route","waypoint","map style"],examples:['updateRoutesDisplay({ add: ["routes-1"] })','updateRoutesDisplay({ add: ["routes-0", "routes-2"] })','updateRoutesDisplay({ remove: ["routes-1"] })','updateRoutesDisplay({ removeMatching: ["Brussels"] }) // hide every entry whose label mentions "Brussels"','updateRoutesDisplay({ addMatching: ["Edinburgh"] })',"updateRoutesDisplay({ clear: true })",'updateRoutesDisplay({ clear: true, add: ["routes-2"] }) // atomic swap: hide everything, show this',"updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route",'updateRoutesDisplay({ add: ["routes-0"], selectedIndex: 2, fitBounds: false })','updateRoutesDisplay({ focus: { sectionTypes: ["tollRoad"] } }) // ring and zoom to the tolls of the latest route','updateRoutesDisplay({ focus: { sectionTypes: ["traffic"], pick: "mostDelay" } }) // the worst jam','updateRoutesDisplay({ focus: { sectionTypes: ["tollRoad"], pick: 0 } }) // the first toll stretch','updateRoutesDisplay({ focus: { roads: ["A2"] } })','updateRoutesDisplay({ focus: { sectionTypes: ["curvyStretch"], pick: "curviest" } })','updateRoutesDisplay({ selectedIndex: 1, focus: { sectionTypes: ["distinct"] } }) // what sets alternative 1 apart',"updateRoutesDisplay({ focus: null }) // clear the ring","updateRoutesDisplay({ waypoints: false }) // the latest route without its pins"],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","Fit the map to the worst traffic section of the route","Fit the map to the first toll section of the route","Show me where the tolls are on my route","Highlight the A2 stretch of the route","Zoom to the curviest stretch of the route","Hide the start and destination pins of the route"],relatedTools:["setRoute","processData","recallState","clearMap"],dependsOn:["setRoute"]},updateTrafficAreaAnalyticsDisplay:{description:"Show, hide and swap 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. The cells' look (display mode, metric shown, palette, height, value filters) is setMapStyling's `traffic.areaAnalytics.*` knobs. Not real-time — the live vector-tile flow overlay is the setMapStyling `traffic.flow.visible` knob.",classificationPrompt:"Show / hide / swap historical traffic-area-analytics entries on the map: `add` / `remove` / `addMatching` / `removeMatching` / `clear`. NOT the cells' look — display mode, metric shown, palette, height, value filters (setMapStyling).",inputSchema:updateTrafficAreaAnalyticsDisplaySchema,outputSchema:updateTrafficAreaAnalyticsDisplayOutputSchema,execute:async(e,t)=>{try{const r=((e,t)=>{const{add:r,remove:o,addMatching:a,removeMatching:s}=t,n=[...r??[],...entryIdsMatching(e.trafficAreaAnalytics.entries,a)],i=[...o??[],...entryIdsMatching(e.trafficAreaAnalytics.entries,s)],l=new Set(e.trafficAreaAnalytics.entries.map(e=>e.id)),c=n.filter(e=>!l.has(e));return c.length>0?{error:`Unknown traffic-area-analytics entry ids: ${c.join(", ")}. Use recallState to list available ids.`}:{add:n,remove:i}})(t,e);if("error"in r)return r;const{add:o,remove:a}=r,s=new Set(t.trafficAreaAnalytics.shownEntryIds);if(e.clear)for(const e of t.trafficAreaAnalytics.shownEntryIds)await t.trafficAreaAnalytics.hideEntry(e);for(const e of a)await t.trafficAreaAnalytics.hideEntry(e);for(const e of o)await t.trafficAreaAnalytics.showEntry(e);const n=t.trafficAreaAnalytics.shownEntryIds,i=o.filter(e=>!s.has(e)&&n.has(e)),l=a.filter(e=>s.has(e)&&!n.has(e));if((e.fitBounds??i.length>0)&&n.size>0){const e=t.trafficAreaAnalytics.entries.filter(e=>n.has(e.id)).flatMap(e=>e.data.features);fitMapToGeoJSON(t.baseMap.mapLibreMap,e,{pitch:45})}const c=t.trafficAreaAnalytics.entries.filter(e=>n.has(e.id)).map(summarizeEntry);return{success:!0,totalShown:n.size,shown:c,actuallyAdded:i,actuallyRemoved:l}}catch(r){return toToolError("Failed to update traffic-area-analytics display",r)}},tags:["traffic","map style"],examples:['updateTrafficAreaAnalyticsDisplay({ add: ["tta-0"] })',"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","Also show the Amsterdam analytics","Hide the Amsterdam analytics","Replace what is on the map with this analytics"],relatedTools:["getTrafficAreaAnalytics","setMapStyling","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:updateWaypointsDisplaySchema,outputSchema:updateWaypointsDisplayOutputSchema,execute:async(e,t)=>{const r=e.visible??!0;try{const e=[...t.routing.shownEntryIds][0],o=t.routing.entries.at(-1),a=e??o?.id;if(!a)return{error:"No route history yet — calculate a route via setRoute first."};const s=await t.routing.getEntryRoutingModule(a);if(!r)return await s.clearWaypoints(),{success:!0,visible:!1,count:0};const n=t.routing.planningSlots;return await s.showWaypoints(n),{success:!0,visible:!0,count:n.length}}catch(o){return toToolError("Failed to update waypoints display",o)}},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:clearMapSchema,outputSchema:clearMapOutputSchema,execute:async(e,t)=>{const{layers:r}=e,o=r&&r.length>0?r:CLEARABLE_SLICE_NAMES;try{for(const e of o)await t[e].clearShown();return{success:!0,cleared:[...o]}}catch(a){return toToolError("Failed to clear map",a)}},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:flyToSchema,outputSchema:flyToOutputSchema,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(o){return toToolError("flyTo failed",o)}},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 [west, south, east, north], west > east across the antimeridian. 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:getViewportSchema,outputSchema:getViewportOutputSchema,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 toToolError("Failed to get viewport",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:addByodSourceSchema,outputSchema:addByodSourceOutputSchema,execute:async(e,t)=>{let r,o;if(e.url){const a=(e=>{let t;try{t=new URL(e)}catch{return{error:`Invalid URL: "${e}".`}}return ALLOWED_PROTOCOLS.has(t.protocol)?{url:t}:{error:`URL protocol "${t.protocol}" is not allowed. Only http: and https: are supported.`}})(e.url);if("error"in a)return a;const s=await(t.byod.sourceUrlValidator??builtInSourceUrlPolicy)(a.url);if(!s.valid)return{error:s.reason};const n=await(async e=>{const t=new AbortController,r=setTimeout(()=>t.abort(),15e3);try{const r=await fetch(e,{signal:t.signal,credentials:"omit",redirect:"error"});if(!r.ok||(e=>{const t=Number.parseInt(e.headers.get("content-length")??"",10);return Number.isFinite(t)&&t>26214400})(r))return;const o=await(async e=>{const t=e.body?.getReader();if(!t){const t=await e.text();return t.length>26214400?void 0:t}const r=[];let o=0;for(;;){const{done:e,value:a}=await t.read();if(e)break;if(o+=a.byteLength,o>26214400)return void(await t.cancel());r.push(a)}const a=new Uint8Array(o);let s=0;for(const n of r)a.set(n,s),s+=n.byteLength;return(new TextDecoder).decode(a)})(r);return void 0===o?void 0:{value:JSON.parse(o)}}catch{return}finally{clearTimeout(r)}})(a.url);if(!n||!isFeatureCollectionLike(n.value))return{error:"Could not load a GeoJSON FeatureCollection from that URL. Ask the user to check the URL, or for the GeoJSON itself to pass as `data`."};r=n.value,o={kind:"url",url:e.url}}else{if(!isFeatureCollectionLike(e.data))return{error:'Payload is not a GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`).'};r=e.data,o={kind:"inline"}}const a=await t.byod.addEntry(r,e.label,{source:o,...e.entryId&&{explicitId:e.entryId}}),s=t.byod.findById(a);return s?{byodEntryId:a,label:e.label,source:o,profile:toByodSafeProfile(s.profile)}:{error:`addByodSource stored entry "${a}" 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 setLayerProperties (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:setByodLayersSchema,outputSchema:setByodLayersOutputSchema,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 o=r.layers,a=e.layers.map(({beforeID:e,...t})=>e?{...t,beforeLayerConfig:{layerID:e}}:t);try{t.byod.setEntryLayers(e.byodEntryId,a)}catch(n){try{t.byod.setEntryLayers(e.byodEntryId,o)}catch{r.layers=o}return{error:`Failed to apply layers to "${e.byodEntryId}": ${errorMessageOf(n)}. The entry keeps its previous layers — fix the layer specs and retry.`}}let s;if(e.show)try{s=await showByodOnMap(t,e.byodEntryId,e.show)}catch(n){return{error:`Layers applied to "${e.byodEntryId}" but rendering failed: ${errorMessageOf(n)}`}}return{byodEntryId:e.byodEntryId,label:r.label,layerCount:a.length,layerTypes:a.map(e=>e.type),...s&&{shown:s}}},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:updateByodDisplaySchema,outputSchema:updateByodDisplayOutputSchema,execute:async(e,t)=>{const r=t.byod,{action:o,clearAll:a,hideOthers:s}=e,n=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,n);if(i)return i;let l;return l="show"===o?await(async(e,t,r)=>{const o=[];if(r){const r=new Set(t);for(const t of e.shownEntryIds)r.has(t)||(await e.hideEntry(t),o.push(t))}for(const a of t)await e.showEntry(a),o.push(a);return o})(r,n,!!s):"hide"===o?await(async(e,t,r)=>{const o=[],a=r?e.shownEntryIds:t;for(const s of a)await e.hideEntry(s),o.push(s);return o})(r,n,!!a):await(async(e,t)=>{const r=[];for(const o of t)await e.removeEntry(o),r.push(o);return r})(r,n),{action:o,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:getCurrentWaypointsSchema,outputSchema:getCurrentWaypointsOutputSchema,execute:async(e,t)=>{const{slotIndex:r,includeEmptySlots:o=!1,offset:a=0,limit:s=5}=e,n=t.routing.planningSlots.map((e,t)=>{const r=summarizeWaypoint(e);return r?{...r,slotIndex:t}:{slotIndex:t,isFilled:!1}}),i=o?n:n.filter(e=>e.isFilled),l=void 0!==r,c=l?i.filter(e=>e.slotIndex===r):i.slice(a,a+s),d=a+c.length;return{totalSlots:n.length,filledSlots:n.filter(e=>e.isFilled).length,matchedCount:l?c.length:i.length,returnedCount:c.length,offset:l?0:a,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:"List the standard map styles (the base-map themes: light, dark, satellite…) that setMapStandardStyle switches to.",classificationPrompt:"List available standard map style IDs for use with setMapStandardStyle.",inputSchema:getStandardMapStylesSchema,outputSchema:getStandardMapStylesOutputSchema,execute:async(e,t)=>{try{return{styles:standardStyleIDs}}catch(r){return toToolError("Failed to get standard map styles",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 (map colours, sizes, a theme, a preset, terrain) use setMapStyling.",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:setMapStandardStyleSchema,outputSchema:setMapStandardStyleOutputSchema,execute:async(e,t)=>{const{style:r}=e;try{return await t.baseMap.ttMap.setStyle(r),{success:!0,style:r}}catch(o){return toToolError("Failed to set map style",o)}},tags:["map style"],examples:['setMapStandardStyle("streetDark")','setMapStandardStyle("streetSatellite")'],examplePrompts:["Switch to dark mode","Use the satellite view","Change to the light theme","Use the driving map style","Use the monochrome dark style","Switch to the dark driving map"],relatedTools:["getStandardMapStyles","setLanguage","setMapStyling"]},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:setLanguageSchema,outputSchema:setLanguageOutputSchema,execute:async(e,t)=>{const{language:r}=e;try{return TomTomConfig.instance.put({language:r}),t.baseMap.ttMap.setLanguage(r),{success:!0,language:r}}catch(o){return toToolError("Failed to set language",o)}},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","Show the traffic incident descriptions in Dutch"],relatedTools:["setMapStandardStyle","setGeopoliticalView"]},setGeopoliticalView:{description:"Set whose perspective disputed territories are shown from: the borders and the territory and sea names the map draws, and the country a disputed place belongs to in subsequent search and reverse-geocoding results. Not the label language (setLanguage).",classificationPrompt:"Show disputed borders and territory names from a country's perspective (or the international / default view). NOT the label language (setLanguage).",inputSchema:setGeopoliticalViewSchema,outputSchema:setGeopoliticalViewOutputSchema,execute:async(e,t)=>{const{geopoliticalView:r}=e;try{return TomTomConfig.instance.put({geopoliticalView:r}),t.baseMap.ttMap.setGeopoliticalView(r),{success:!0,geopoliticalView:r}}catch(o){return toToolError("Failed to set geopolitical view",o)}},tags:["map style"],examples:['setGeopoliticalView({ geopoliticalView: "IN" })','setGeopoliticalView({ geopoliticalView: "Unified" })',"setGeopoliticalView({})"],examplePrompts:["Show the borders as India sees them","Use the international view for disputed territories","Reset the disputed borders to the default view","Show Kashmir the way Pakistan maps it"],relatedTools:["setLanguage"]},setMapStyling:(e=>({scope:t})=>({...e,description:"Set what the map shows and how it looks, by knob id: whole base-map layer groups (groups.<group>.visible: water, roads, 2D/3D buildings, road shields, road/city/country labels…; `visible` for the whole base map), road markings (exit numbers, arrows), the built-in POI icons (pois.visible, which categories show, their zoom and label colours — not the places the agent found), the live traffic flow and incident overlays (traffic.flow.visible, traffic.incidents.visible, planned incidents with traffic.incidents.timeValidity, their road and delay filters and colours), the hillshade and 3D terrain elevation (tilt the camera with setPitchBearing to see the relief), label/icon/road size factors, the ten map colours (colors.land/water/vegetation/park/artificial/roadMajor/road/roadOutline/label/labelHalo — one name recolours every derived shade, so for a dark palette switch to a dark standard style first and recolour from there), a whole theme from a few colours or an image (`theme`: a brand, a website's colours, an artist's palette, a described mood — turn it into three to six hex colours first), globe projection and sky, or a whole preset (data-viz, night-driving, minimal, globe). It also styles what the agent drew, every layer of a kind, shown now or later: found places (places.*: pin colour, labels, clusters), routes (routes.*: colour, width, waypoints, section styling), polygons and place boundaries (geometries.*), reachable areas (ranges.*), area analytics cells (traffic.areaAnalytics.*: display mode, the metric shown, its palette, height and value filters) and fetched incidents (traffic.incidentDetails.*). Showing or hiding those, a places entry's marker type and a polygon's fill style are the update*Display tools' job. Durable across style switches; returns every knob set afterwards. `colors.accent` is the colour routes and place pins default to, so a theme reaches them too; set `routes.color` or `places.color` only for a specific colour.",inputSchema:setMapStylingSchemaFor(t?.kinds),outputSchema:setMapStylingOutputSchema,execute:executeSetMapStyling,scopeSchema:knobPartScopeSchema,scopePrompt:"Emit { kinds: [...] }, the knob families the turn touches: 'look' (label/icon/road sizes, the map colours, globe and sky, a theme or preset), 'baseMap' (show/hide whole base-map layer groups, road markings), 'pois' (the built-in POI icons and their categories), 'trafficFlow' (the live flow overlay), 'trafficIncidents' (the live incident overlay), 'terrain' (hillshade, 3D elevation); and the look of what the agent drew: 'places' (found places' pins, labels, clusters), 'routes' (route colour, width, waypoints, sections), 'geometries' (polygons, place boundaries), 'ranges' (reachable areas), 'trafficAreaAnalytics' (the analytics cells: display mode, metric shown, palette, height, filters), 'trafficIncidentDetails' (fetched incidents' highlight)."}))({classificationPrompt:"Set what the base map shows and how it looks — show/hide whole layer groups (3D buildings, road shields, road / city / country labels, house numbers, water, railways… or the whole base map), the built-in POI icons and which categories show, the vector-tile traffic overlays — live flow / congestion, and the incident markers (jams, accidents, closures, planned ones too) — and their filters (NOT the getTrafficIncidents fetch, nor the historical area analytics), exit numbers, road arrows, the hillshade, 3D terrain elevation; bigger/smaller labels or icons, thicker roads, POI label colour or zoom, traffic line colours, map colours, a globe projection with a sky and its sky/horizon/space colours, a map preset, or a whole theme from a few colours or an image (a brand, a website's look, an artist's palette, a mood) — or reset those to the style defaults. Also the look of every layer the agent drew: the found places' pin colour and labels, the route colour, width, section styling (tolls, curves… drawn or not) and summary bubbles (ETA labels, shown or not), polygons, reachable areas, the area analytics cells (display mode, metric shown, palette, height, value filters — “only show the cells above X” hides the rest in place, no new entry via processData) and fetched incidents. NOT showing or hiding those (update*Display), NOT for switching the whole style (setMapStandardStyle).",tags:["map style","traffic","place"],examples:['setMapStyling({ set: { "labels.sizeFactor": 1.3 } })','setMapStyling({ set: { "groups.buildings3D.visible": true } })','setMapStyling({ set: { "groups.roadShields.visible": false, "groups.houseNumbers.visible": false } })','setMapStyling({ set: { "traffic.flow.visible": true } })','setMapStyling({ set: { "traffic.incidents.visible": true, "traffic.incidents.filters.magnitudes.show": "only", "traffic.incidents.filters.magnitudes.values": ["major"] } })','setMapStyling({ set: { "traffic.incidents.visible": true, "traffic.incidents.timeValidity": ["present", "future"] } })','setMapStyling({ set: { "pois.filters.categories.show": "only", "pois.filters.categories.values": ["RESTAURANT", "CAFE_PUB"] } })','setMapStyling({ set: { "pois.visible": false } })','setMapStyling({ set: { "roads.exitNumbers.visible": false, "roads.arrows.visible": false } })','setMapStyling({ set: { "traffic.flow.colors.slow": "#f59e0b", "traffic.flow.colors.stationary": "#7f1d1d" } })','setMapStyling({ set: { "view.projection": "globe", "view.sky.visible": true } })','setMapStyling({ set: { "terrain.elevation.visible": true, "terrain.elevation.exaggeration": 1.5, "terrain.hillshade.visible": true } })','setMapStyling({ set: { "colors.water": "#0fa8c8", "colors.roadMajor": "#f59e0b" } })','setMapStyling({ theme: { colors: ["#0b2a3a", "#0fa8c8", "#f6efe3", "#d9822b"] } })','setMapStyling({ theme: { colors: ["#1d3f8a", "#e63312", "#f5d000", "#f4f1ea", "#111111"], mode: "light" } })','setMapStyling({ theme: { imageUrl: "https://example.com/brand/logo.png" } })','setMapStyling({ preset: "data-viz" })','setMapStyling({ set: { "routes.color": "coral", "routes.widthFactor": 1.5 } })','setMapStyling({ set: { "routes.sections.tollRoad.color": "#d97706", "routes.sections.ferry.pattern": "dashed" } })','setMapStyling({ set: { "routes.summaryBubbles.visible": false } })','setMapStyling({ set: { "routes.sections.curve.visible": true, "routes.sections.curve.color": "purple" } })','setMapStyling({ set: { "places.color": "#7c3aed", "places.label.size": 14 } })','setMapStyling({ set: { "geometries.color": "teal", "geometries.fill.opacity": 0.4 } })','setMapStyling({ set: { "traffic.areaAnalytics.displayMode": "heatmap", "traffic.areaAnalytics.activeMetric": "speed", "traffic.areaAnalytics.metrics.speed.palette": "heat" } })','setMapStyling({ set: { "traffic.areaAnalytics.metrics.congestionLevel.filters.min": 50 } })','setMapStyling({ set: { "traffic.areaAnalytics.metrics.congestionLevel.height.scaleMode": "currentRange", "traffic.areaAnalytics.metrics.congestionLevel.height.maxMeters": 200 } })',"setMapStyling({ reset: true })"],examplePrompts:["Make the map labels bigger","Hide the exit numbers and road arrows","Use orange for slow traffic and dark red for stationary traffic","Show the map as a globe","Make the water teal and the motorways orange","Use our brand colours on the map: dark land, light labels","Theme the map like a Miró painting","Style the map in warm earthy tones","Make the map match our website: navy, coral and off-white","Make the base map quieter so my data stands out","Reset the map styling","Show the mountains in 3D","Turn off the hillshade","Give the horizon a warm sunset colour","Show 3D buildings","Hide the road labels","Hide the route shields and house numbers","Bring back all the base map layers","Show traffic flow","Turn off traffic colors","Show traffic incidents overlay","Hide accident icons","Include the planned closures in the traffic incidents overlay","Hide all map POI icons","Show only restaurant icons on the map style","Turn off parking icons in the base map","Make the route line red","Change the route color to coral","Make the route thicker","Show toll roads on the route in orange","Hide the route summary bubbles","Draw the route's curves in purple","Make the place pins purple","Make the area outlines teal","Only show the analytics cells with congestion above 50%","Normalize height to the current data range","Switch to heatmap view","Colour the cells by free-flow speed instead"],relatedTools:["setMapStandardStyle","getTrafficIncidents","updatePlacesDisplay","updateRoutesDisplay","updateTrafficAreaAnalyticsDisplay"]}),getMapStyleLayers:{description:"Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayerProperties, for styling no setMapStyling knob covers.",classificationPrompt:"List MapLibre layer IDs with their paint/layout properties for custom styling.",inputSchema:getMapStyleLayersSchema,outputSchema:getMapStyleLayersOutputSchema,execute:async(e,t)=>{const{layerIdQuery:r,include:o}=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,...o.includes("paint")&&{paint:e.paint},...o.includes("layout")&&{layout:e.layout}}))}}catch(a){return toToolError("Failed to get style",a)}},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:["setLayerProperties"]},setLayerProperties:{description:"Set MapLibre paint (colours, widths, opacity) and layout (visibility, text-field, sizes, fonts) properties on named layers — use getMapStyleLayers for valid IDs. For the routes, places and other layers the agent drew prefer setMapStyling's `routes.*`, `places.*`… knobs. The map colours (land, water, vegetation, parks, roads, labels), label and icon sizes, traffic colours and whole layer groups (road labels, shields, 3D buildings…) are setMapStyling knobs, which survive style switches — use this only for what those knobs do not cover.",classificationPrompt:"Set MapLibre paint (colours, widths, opacity, dashes) or layout (visibility, text-field, fonts, text size) properties on named base-map style layers no setMapStyling knob covers (borders, buildings, railways, one label layer…). NOT for the map colours of land, water, vegetation, parks, roads or labels, nor label/icon sizes or whole layer groups (setMapStyling). NOT for loaded BYOD / imported / customer layers — restyle those with setByodLayers.",inputSchema:setLayerPropertiesSchema,outputSchema:setLayerPropertiesOutputSchema,execute:async(e,t)=>{try{return{results:e.changes.flatMap(({layerId:e,...r})=>propertyGroups.flatMap(o=>Object.entries(r[o]??{}).map(([r,a])=>{try{return((e,t,{group:r,propertyName:o,value:a})=>{const s=e.baseMap.mapLibreMap;if("paint"===r){const e=o;s.setPaintProperty(t,e,a)}else{const e=o;s.setLayoutProperty(t,e,a)}})(t,e,{group:o,propertyName:r,value:a}),{layerId:e,group:o,propertyName:r,value:a,success:!0}}catch(s){return{layerId:e,group:o,propertyName:r,error:`Failed to set ${o} property: ${errorMessageOf(s)}`}}})))}}catch(r){return toToolError("Failed to set layer properties",r)}},tags:["map style"],examples:['setLayerProperties({ changes: [{ layerId: "admin-boundary", paint: { "line-color": "#ff69b4" } }] })','setLayerProperties({ changes: [{ layerId: "road-label", layout: { visibility: "none" } }] })','setLayerProperties({ changes: [{ layerId: "poi-label", paint: { "text-color": "#333" }, layout: { "text-size": 14 } }] })'],examplePrompts:["Make the country borders pink","Set the road-label layer's visibility property to none","Make buildings semi-transparent","Make the country borders dashed","Change the text size on building labels","Use a bold font for the road labels"],relatedTools:["getMapStyleLayers","setMapStyling","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:setPitchBearingSchema,outputSchema:setPitchBearingOutputSchema,execute:async(e,t)=>{const{pitch:r,bearing:o}=e;if(void 0===r&&void 0===o)return{error:"Provide at least one of pitch or bearing"};try{return t.baseMap.mapLibreMap.easeTo({...void 0!==r&&{pitch:r},...void 0!==o&&{bearing:o}}),{success:!0}}catch(a){return toToolError("Failed to set pitch/bearing",a)}},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:zoomInOrOutSchema,outputSchema:zoomInOrOutOutputSchema,execute:async(e,t)=>{const{delta:r}=e;try{const e=t.baseMap.mapLibreMap,o=e.getZoom()+r;return e.zoomTo(o),{zoom:o}}catch(o){return toToolError("Zoom failed",o)}},tags:["map style"],examples:["zoomInOrOut({ delta: 2 })","zoomInOrOut({ delta: -1 })"],examplePrompts:["Zoom in","Zoom out 2 levels","Get a wider view"],relatedTools:["getViewport","flyTo"]},calculateBBox:{description:"Compute a `[west, south, east, north]` bbox from GeoJSON (longitude before latitude). Across the antimeridian west > east, and `minLng`/`maxLng` carry west and east.",classificationPrompt:"Compute a [west, south, east, north] bounding box from GeoJSON features or tool results.",inputSchema:calculateBBoxSchema,outputSchema:calculateBBoxOutputSchema,execute:async(e,t)=>{const{geoJson:r}=e;try{const e=bboxFromGeoJSON(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(o){return toToolError("Failed to calculate bbox",o)}},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:createGetCurrentLocationTool(),clarifyIntent:createClarifyIntentTool(),help:{description:helpDescription,classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:helpSchema,outputSchema:helpOutputSchema,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:findReachableAreasSchema,outputSchema:findReachableAreasOutputSchema,execute:async(e,t,r)=>{const{origins:o,budgets:a,fillStyle:s,showOnMap:n,showOriginPin:i=!0,hidePreviousEntries:l}=e;try{const e=[...a].sort((e,t)=>t.value-e.value),{ranges:c,skipped:d,errors:u}=await(async(e,t,r,o)=>{const a=[],s=[],n=[];for(const i of e){const e=await rangeForOrigin(i,t,r,o);"range"in e?a.push(e.range):(s.push(e.skipped),"error"in e&&n.push(e.error))}return{ranges:a,skipped:s,errors:n}})(o,e,t,r);if(0===c.length)return u.length?toToolError("Reachable area calculation failed",u[0]):{status:"no_results"};const h=await t.ranges.addEntry({label:makeRangesLabel(e,c.map(formatOriginName)),data:c}),p=n||i;return p&&await hidePreviousShownEntries(t.ranges,[h],l),await(async(e,t,r,o,a,s)=>{if(a){const a=(e=>{const t=e.flatMap(e=>e.polygon?.features??[]);return 0===t.length?null:{type:"FeatureCollection",features:t}})(r);a&&(await(await e.ranges.getEntryRangesModule(t,o)).show(a),a.bbox&&fitMapToBBox(e.baseMap.mapLibreMap,a.bbox))}if(s){const o=await e.ranges.getEntryPlacesModule(t),a=r.map((e,r)=>({type:"Feature",id:`range-origin-${t}-${r}`,geometry:{type:"Point",coordinates:e.origin.position},properties:{type:"Point Address",address:{freeformAddress:formatOriginName(e)}}}));await o.show(a)}})(t,h,c,s??"outline",n,i),p&&await t.ranges.markEntryShown(h),{rangesId:h,ranges:c.map(t=>({originName:formatOriginName(t),budgets:e})),...d.length&&{skipped:d}}}catch(c){return toToolError("Reachable area calculation failed",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, fillStyle: "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,{...recallStateMetadata,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:recallStateSchema,outputSchema:buildRecallStateOutputSchema(),execute:buildExecuteRecallState()}),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 "streetLight". 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:resetStateSchema,outputSchema:resetStateOutputSchema,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?.ranges?.clear()),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("streetLight",{resetState:!0}),{success:!0,cleared:[...CLEARED_SLICES]}}catch(r){return toToolError("Failed to reset state",r)}},tags:["state","reset","destructive"],examples:["resetState()"],examplePrompts:["Start over","Reset everything","Clear all my data and the map"],relatedTools:["recallState","clearMap"]},setEntryMode:{description:setEntryModeDescription,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. OWNS "let me overlay / keep several searches (routes, ranges…) on the map" and "only show one at a time", even when no new search is asked for — pick it, not discoverPlaces, unless the turn also names what to search.',inputSchema:setEntryModeSchema,outputSchema:setEntryModeOutputSchema,execute:async(e,t)=>{const{slice:r,mode:o}=e;try{const e=t[r],a=e.entries.map(e=>e.id);await e.setEntryMode(o);const s=new Set(e.entries.map(e=>e.id));return{success:!0,slice:r,mode:o,droppedEntryIds:a.filter(e=>!s.has(e))}}catch(a){return toToolError("Failed to set entry mode",a)}},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"]}},DEFAULT_TOOLS=DEFAULT_TOOLS_TYPED,TOOL_NAMES=Object.keys(DEFAULT_TOOLS),TOOLS_BY_DATA_ENTRY_KIND={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"]},getDefaultToolPrompts=()=>{const e={};for(const t of TOOL_NAMES){const r=DEFAULT_TOOLS[t];e[t]=("function"==typeof r?r({}):r).examplePrompts??[]}return e},createMapAgent=(e,t)=>{if(!t.model)throw new Error("MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.");e._setTomTomUserAgent(AGENT_TOOLKIT_USER_AGENT);const r=((e,t)=>{const r={places:new PlacesState(e),mapPOIs:new MapPOIsState(e),routing:new RoutingState(e),baseMap:new BaseMapState(e),trafficTiles:new TrafficTilesState(e),trafficAreaAnalytics:new TrafficAreaAnalyticsState(e),trafficIncidents:new TrafficIncidentsState(e),ranges:new RangeState(e),customGeometries:new CustomGeometriesState(e),byod:new BYODState(e),codeExecution:resolveSandboxExecutor(),engine:new JobEngine,analyses:new Analyses,trackers:new EventsState};return t&&Object.assign(r,t),r.engine.ensureWired(r),r})(e,t.state);if(t.byod?.validateSourceUrl&&(r.byod.sourceUrlValidator=t.byod.validateSourceUrl),r.codeExecution=resolveSandboxExecutor(t.codeExecution),t.dataEntries)for(const[v,I]of Object.entries(t.dataEntries)){const e=v;I?.entryMode&&r[DATA_ENTRY_KIND_TO_SLICE[e]].setEntryMode(I.entryMode)}const o=e=>!1!==t.dataEntries?.[e]?.enabled,a=Object.keys(DATA_ENTRY_KIND_TO_SLICE).filter(e=>!o(e)),s=ALL_ENTRY_DATA_KINDS.filter(e=>o(e)),n=new Set(a.flatMap(e=>TOOLS_BY_DATA_ENTRY_KIND[e])),i=!1===t.includeDefaultTools?{}:DEFAULT_TOOLS,l=Object.fromEntries(Object.entries(i).filter(([e])=>!n.has(e))),c=resolveTools(l,t.tools),{tools:d,toolsMetadata:u,scopableTools:h}=((e,t,r)=>{const o=!1!==r?.outputSchemas,a={featureFlags:r?.featureFlags,enabledDataKinds:r?.enabledDataKinds},s={},n={},i={};for(const[c,d]of Object.entries(e)){const e=materializeEntry(d,a);if(s[c]=toAiTool(c,e,t,o,r?.onToolExecute),n[c]=toMetadata(c,e),"function"==typeof d&&e.scopeSchema){const t=d,r=e.scopeSchema;i[c]={applyScope:e=>{const o=r.safeParse(e);if(!o.success)return null;try{const e=t({...a,scope:o.data});return{description:e.description,inputSchema:rejectingUnknownKeys(e.inputSchema)}}catch{return null}},defaultDescription:e.description,defaultInputSchema:rejectingUnknownKeys(e.inputSchema)}}}var l;if(s.help&&(s.help={...(l=()=>n,{description:helpDescription,inputSchema:helpSchema,outputSchema:helpOutputSchema,execute:async e=>{const{mode:t,query:r,tag:o}=e;try{const e=Object.values(l());if("summary"===t)return{mode:"summary",examplePrompts:e.flatMap(e=>e.examplePrompts??[])};let a=e;if(o&&(a=a.filter(e=>e.tags?.includes(o)??!1)),r){const e=r.toLowerCase();a=a.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:o,matchedTools:a.map(e=>({name:e.name,description:e.description,examplePrompts:e.examplePrompts??[],relatedTools:e.relatedTools??[]})),totalMatched:a.length}}catch(a){return toToolError("Failed to load help",a)}}}),inputSchema:rejectingUnknownKeys(helpSchema)}),0===Object.keys(s).length)throw new Error("MapAgent requires at least one tool.");return{tools:s,toolsMetadata:n,scopableTools:i}})(c,r,{outputSchemas:t.outputSchemas,featureFlags:t.featureFlags,enabledDataKinds:s,onToolExecute:t.onToolExecute}),p=!1===t.classifier?null:t.classifier??createDefaultClassifier({model:t.model}),y=buildSystemPrompt({customPrompt:t.systemPrompt,prefix:t.systemPromptPrefix,suffix:t.systemPromptSuffix}),m=Object.entries(u).filter(([,e])=>e.alwaysActive).map(([e])=>e),f=Object.entries(u).filter(([,e])=>e.endsTurnOnCall).map(([e])=>hasToolCall(e));let g=null;const b=(()=>{let e=Promise.resolve();return async t=>{const r=e;let o=()=>{};e=new Promise(e=>{o=e});try{return await r,await t()}finally{o()}}})(),w={tools:d,scopableTools:h,toolsMetadata:u,classifier:p,onClassify:t.onClassify},S=new ToolLoopAgent({model:t.model,tools:d,instructions:y,stopWhen:[isStepCount(t.maxSteps??10),...f],prepareStep:e=>b(async()=>{0===e.stepNumber&&(g=await(async(e,t)=>{((e,t)=>{for(const[r,o]of Object.entries(t)){const t=e[r];t&&(t.description=o.defaultDescription,t.inputSchema=o.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[o,a]of Object.entries(r)){const r=t[o];if(!r||null==a)continue;const s=e[o];if(!s)continue;const n=r.applyScope(a);n&&(s.description=n.description,s.inputSchema=n.inputSchema)}})(e.tools,e.scopableTools,r?.toolScopes),r})(w,e.messages));const r=unionActiveTools(g?.activeToolNames,m),o=await(t.prepareStep?.(e)),a=t.stepProviderOptions?.({activeTools:r,stepNumber:e.stepNumber}),s=((e,t,r)=>r?{...r,activeTools:intersectActiveTools(e,r.activeTools),providerOptions:mergeProviderOptions(t,r.providerOptions)}:{...e?{activeTools:e}:{},...t?{providerOptions:t}:{}})(r,a,o);return s?.activeTools?{...s,activeTools:unionActiveTools(s.activeTools,m)}:s}),providerOptions:t.providerOptions});return Object.assign(S,{state:r,getCodeExecutionMode:()=>r.codeExecution.getMode(),destroy:()=>(e=>{for(const t of Object.values(e))isStateSlice(t)&&t.reset()})(r)})};export{AGENT_TOOLKIT_ID,AGENT_TOOLKIT_USER_AGENT,ALL_ENTRY_DATA_KINDS,ANALYSE_OUTPUT_FORMAT_DESCRIPTION,AREA_GEOGRAPHY_TYPES,BASE_SYSTEM_PROMPT,BY_ENTRY_VIEWS_DOC,CHART_TYPES,CLARIFY_FORM_PROMPT,CLARIFY_PROSE_PROMPT,DATA_ENTRY_KIND_TO_SLICE,DEFAULT_TOOLS,ENTRY_KIND_META,ENTRY_MODE_SLICE_NAMES,FIT_ON_MAP_DOC,GEOMETRIES_PROPS_DOC,GEOMETRIES_SCHEMA_DOC,GEOMETRIES_SKIPPED_DESC,GEOMETRIES_SOURCE_IDS_DESC,MIN_VIEWPORT_BIAS_ZOOM,MULTI_INPUT_SANDBOX_PARAMS,PLACES_SCHEMA_DOC,ROUTES_SCHEMA_DOC,SYSTEM_PROMPT_SECTIONS,TOOLS_BY_DATA_ENTRY_KIND,TOOL_NAMES,WORKER_PROVIDED_PARAMS,applyFitOnMap,areaWhereSchema,buildAnalyseReturnPrompt,buildAtLeastOneEntryIdMessage,buildEntryFieldList,buildEntryIdFields,buildEntryKindSandboxDocs,buildEntryKindSchemaDocs,buildEntryKindScopeSchema,buildPlacesSchemaDoc,buildSandboxCodePrompt,buildSandboxToolsDoc,buildSystemPrompt,clarifyIntentOutputSchema,clarifyIntentSchema,classifyUserIntent,collectInputGeometries,composeSystemPrompt,createClarifyIntentTool,createDefaultClassifier,createExecuteMaplibreCodeTool,createGetCurrentLocationTool,createMapAgent,extractLastUserText,findPlacesByEntry,fitOnMapInputSchema,fittedReportSchema,formatSandboxExecutionError,formatStateDigestDiff,geoJsonBBoxSchema,geocodeAreas,geometriesEntryIDsSchema,geometriesIdSchema,geometryDisplayConfigSchema,geometryFillStyleSchema,geometryInputSchema,getDefaultToolPrompts,getRangePolygons,getStateDigest,getViewportBias,getViewportBoundingBox,globalWhereSchema,hasAnyEntryIds,hidePreviousEntriesSchema,hidePreviousShownEntries,isChartConfiguration,isFitOnMapInput,isKindActive,isPolygonFeature,isPolygonFeatureArray,isResolveError,labelForLocationInput,latestTimestamp,locatePlace,locatePlaces,locationInputSchema,mainThreadExecutor,markerTypeSchema,multiPolygonInputSchema,nearbyWhereSchema,packSandboxArgs,placesEntryIDsSchema,placesEntryIdHintSchema,polygonInputSchema,positionSchema,prepareMultiInputs,queryAsSchema,questionSchema,resolveActiveKinds,resolveGlobal,resolveNearby,resolveTools,resolveWhere,resolveWithin,routeUtils,routesEntryIDsSchema,runSandboxedFn,sandboxedGlobalShadows,sharedWithinFields,showByodOnMap,showByodSchema,showEntryGeometries,showModeSchema,showPlaceGeometriesSchema,showPlacesSchema,showResultsOnMap,shownByodSchema,shownSchema,skippedSourceSchema,stripInjectedRedeclarations,toolStateToWhereContext,validateAnalysisResult,whereSchema,withAgentToolkitHeaders,withinSharedFields,withinWhereSchema};
|
|
1
|
+
import{NoObjectGeneratedError,Output,ToolLoopAgent,generateText,hasToolCall,isStepCount,parsePartialJson}from"ai";import{BaseMapModule,CustomGeoJSONModule,GeometriesModule,POIsModule,PlacesModule,ReachableRangesModule,RoutingModule,StylingFoundationsModule,TerrainModule,TomTomMapError,TrafficAreaAnalyticsModule,TrafficFlowModule,TrafficIncidentDetailsModule,TrafficIncidentsModule,applyMapPreset,baseMapKnobCatalogue,fillStyledGeometryConfig,filterShowModes,geometriesKnobCatalogue,geometryFillStyles,knobEntryOf,knobValueIn,mapColorNames,mapPresetIds,mapStyleLayerIDs,placesKnobCatalogue,poisKnobCatalogue,reachableRangesKnobCatalogue,resetKnob,routingKnobCatalogue,setKnob,setKnobs,standardStyleIDs,stylingFoundationsKnobCatalogue,terrainKnobCatalogue,trafficAreaAnalyticsKnobCatalogue,trafficFlowKnobCatalogue,trafficIncidentDetailsKnobCatalogue,trafficIncidentsKnobCatalogue,validateKnobValue}from"@tomtom-org/maps-sdk/map";import{SDKAbortError,SDKError,calculateReachableRanges,calculateRoute,discoverPlaces,functionalRoadClasses,geocode,geometryData,getPOICategoryCodes,resolvePOICategories,reverseGeocode,routeTypes,trafficAreaAnalytics,trafficIncidentDetails}from"@tomtom-org/maps-sdk/services";import{TOMTOM_USER_AGENT_SDK_NAME,TomTomConfig,areaAnalyticsMetricKeys,avoidableTypes,bboxCenter,bboxFromBBoxes,bboxFromGeoJSON,budgetTypes,budgetUnits,calculateProgressAtRoutePoint,compareRoutes,defaultSectionTypes,formatDistance,formatDuration,geopoliticalViews,getCoordinateAtRouteProgress,getImportantRoads,getPosition,getProgressAtNearestRoutePoint,getRouteProgressBetween,getRouteProgressForSection,getSectionBBox,getSectionsWithin,isHairpin,placeTypes,polygonFromBBox,sdkErrorCodes,trafficIncidentRequestCategories,withInsertedWaypoints}from"@tomtom-org/maps-sdk/core";import*as turf from"@turf/turf";import{z}from"zod";import*as h3 from"h3-js";import{deriveMapColors,deriveMapColorsFromImage,mapColorsLightDark,mapThemeModes}from"@tomtom-org/maps-sdk-plugin-map-theme";var intersectActiveTools=(e,t)=>{if(!e)return t?.filter(e=>"string"==typeof e);if(!t)return e;const r=new Set(t);return e.filter(e=>r.has(e))},unionActiveTools=(e,t)=>{if(!e)return;if(0===t.length)return[...e];const r=new Set(e);return[...e,...t.filter(e=>!r.has(e))]},isStateSlice=e=>"object"==typeof e&&null!==e&&"reset"in e&&"function"==typeof e.reset,resetSlices=(e,t)=>{for(const o of Object.values(e))if(!t?.has(o)&&isStateSlice(o))try{o.reset()}catch(r){console.warn("[agent-toolkit] a state slice failed to reset:",r)}},mergeProviderOptions=(e,t)=>{if(e||t)return{...e,...t}},resolveTools=(e,t)=>{const r={...e};if(!t)return r;for(const[o,a]of Object.entries(t))a?r[o]=a:delete r[o];return r},StateEvents=class{handlers={};on(e,t){return(this.handlers[e]??(this.handlers[e]=[])).push(t),()=>{const r=this.handlers[e];if(!r)return;const o=r.indexOf(t);-1!==o&&r.splice(o,1)}}off(e){this.handlers[e]&&(this.handlers[e].length=0)}emit(e,t){const r=this.handlers[e];if(r?.length)for(const o of[...r])o(t)}clear(){for(const e of Object.keys(this.handlers))this.handlers[e]=[]}},Analyses=class{_records=/* @__PURE__ */new Map;_jobs=/* @__PURE__ */new Map;events=new StateEvents;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 o=r.history.at(-1);o&&t.push(o)}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);const t=this._records.get(e);t&&(this._records.delete(e),this.events.emit("analysis-change",{analysisId:e,affectedEntryIds:t.affectedEntryIds}))}removeByName(e){for(const[t,r]of this._records)r.name===e&&this.remove(t)}reset(){for(const t of this._jobs.values())t.unregister();this._jobs.clear();const e=[...this._records.values()];this._records.clear();for(const{analysisId:t,affectedEntryIds:r}of e)this.events.emit("analysis-change",{analysisId:t,affectedEntryIds:r})}},BaseMapState=class{ttMap;_terrainModule;constructor(e){this.ttMap=e}get mapLibreMap(){return this.ttMap.mapLibreMap}getBaseMapModule(){return BaseMapModule.get(this.ttMap)}getStylingFoundationsModule(){return StylingFoundationsModule.get(this.ttMap)}async getTerrainModule(){return this._terrainModule=await TerrainModule.get(this.ttMap),this._terrainModule}get terrainModule(){return this._terrainModule}reset(){this._terrainModule=void 0}},GEOMETRY_TYPES=/* @__PURE__ */new Set(["Point","MultiPoint","LineString","MultiLineString","Polygon","MultiPolygon","GeometryCollection"]),jsonTypeOf=e=>null===e?"null":Array.isArray(e)?"array":typeof e,recordExample=(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)}},coverageOf=(e,t)=>e===t?1:Math.min(.99,Math.max(.01,Math.round(e/t*100)/100)),toByodSafeProfile=e=>({...e,properties:e.properties.map(e=>({...e,examples:e.examples.filter(e=>"string"!=typeof e)}))}),modulesOf=(e,t)=>e.flatMap(e=>{const r=t(e);return r?[r]:[]}),createOnce=(e,t,r)=>{let o=e.get(t);return o||(o=r().finally(()=>e.delete(t)),e.set(t,o)),o},withFillStyle=async(e,t,r)=>{const o=e??await r(t??"outline"),a=o.getConfig()?.fill;return t&&a?.style!==t&&o.updateConfig({fill:{...a,style:t}}),o},fillStyledGeometriesConfig=e=>{const t=fillStyledGeometryConfig();return{...t,fill:{...t.fill,style:e}}},dedupeById=e=>{const t=/* @__PURE__ */new Map;return e.forEach((e,r)=>{void 0!==e.id&&t.set(e.id,r)}),e.filter((e,r)=>void 0===e.id||t.get(e.id)===r)},showPolygons=async(e,t,r)=>{if("add"===r){if(0===t.length)return;const r=e.getShown().geometry.features??[];await e.show({type:"FeatureCollection",features:dedupeById([...r,...t])})}else 0===t.length?await e.clear():await e.show({type:"FeatureCollection",features:t})},pickUniqueEntryId=(e,t)=>{const r=new Set(t);if(!r.has(e))return e;for(let o=2;;o++){const t=`${e}-${o}`;if(!r.has(t))return t}},dropEntries=async(e,t,r)=>{e.set(e.get().filter(e=>!t.includes(e))),await(async(e,t)=>{for(const r of e)await t(r)})(t,r)},removeEntryById=async(e,t,r)=>{const o=e.get().find(e=>e.id===t);return o&&await dropEntries(e,[o],r),o},collapseHistoryToLatest=async(e,t)=>{const r=e.get().slice(0,-1);return await dropEntries(e,r,t),r.map(e=>e.id)},dropAllEntries=async(e,t)=>{const r=[];for(;e.get().length>0;){const o=[...e.get()];await dropEntries(e,o,t),r.push(...o.map(e=>e.id))}return r},BYODState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_entryMode="multiple";_addedCount=0;_creatingModules=/* @__PURE__ */new WeakMap;events=new StateEvents;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=await collapseHistoryToLatest(this._history,e=>this._hideEntry(e));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 o=(e=>{const t=e.features.length,{geometryTypes:r,byKey:o}=(e=>{const t=/* @__PURE__ */new Set,r=/* @__PURE__ */new Map;for(const o of e.features){const e=o.geometry?.type;e&&GEOMETRY_TYPES.has(e)&&t.add(e);const a=o.properties;if(a&&"object"==typeof a)for(const[t,o]of Object.entries(a)){let e=r.get(t);e||(e={count:0,types:/* @__PURE__ */new Set,examples:[]},r.set(t,e)),e.count+=1,e.types.add(jsonTypeOf(o)),recordExample(e,o)}}return{geometryTypes:t,byKey:r}})(e),a=[...o.entries()].map(([e,r])=>({name:e,types:[...r.types],coverage:coverageOf(r.count,t),examples:r.examples})).sort((e,t)=>t.coverage-e.coverage||e.name.localeCompare(t.name)),s=a.slice(0,40),n=a.length-s.length;return{featureCount:t,geometryTypes:[...r],properties:s,...n>0&&{propertiesOmitted:n}}})(e),a="single"===this._entryMode?await dropAllEntries(this._history,e=>this._hideEntry(e)):[],s="byod-"+this._addedCount++,n=pickUniqueEntryId(r.explicitId??s,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:Date.now(),label:t,data:e,source:r.source??{kind:"integrator"},layers:r.layers??[],profile:o}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...a,n]}),n}setEntryLayers(e,t){const r=this._requireEntry(e);r._module?.applyConfig({sources:{data:{layers:t}}}),r.layers=t,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}async getEntryModule(e){return this._entryModule(this._requireEntry(e))}async _entryModule(e){return e._module??=await createOnce(this._creatingModules,e,async()=>{const t=e.layers,r=await CustomGeoJSONModule.create(this._ttMap,{sources:{data:{layers:t}}});return e.layers!==t&&r.applyConfig({sources:{data:{layers:e.layers}}}),r}),e._module}async showEntry(e){const t=this._requireEntry(e);if(0===t.layers.length)return;await this._hideOthersUnderSingleMode(e);const r=await this._entryModule(t);if(await r.show(t.data,"data"),!this._entries.includes(t))return void(await r.clear());const o=t._shown;t._shown=!0,o||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)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&await this._hideEntry(t)}async _hideEntry(e){e._shown&&(await(e._module?.clear()),e._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){await removeEntryById(this._history,e,e=>this._hideEntry(e))&&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(){this._addedCount=0;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)}},SliceKnobs=class{catalogue;modules;_values=/* @__PURE__ */new Map;constructor(e,t){this.catalogue=e,this.modules=t}get values(){return Object.fromEntries(this._values)}set(e,t){validateKnobValue(knobEntryOf(this.catalogue,e),t);for(const r of this.modules())setKnob(r,this.catalogue,e,t);this._values.set(e,t)}reset(e){knobEntryOf(this.catalogue,e);for(const t of this.modules())resetKnob(t,this.catalogue,e);this._values.delete(e)}resetAll(){for(const e of[...this._values.keys()])this.reset(e)}clear(){this._values.clear()}applyTo(e){return setKnobs(e,this.catalogue,this.values),e}},CustomGeometriesState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_entryMode="multiple";_addedCount=0;_creatingModules=/* @__PURE__ */new WeakMap;events=new StateEvents;knobs=new SliceKnobs(geometriesKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));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=await collapseHistoryToLatest(this._history,e=>this._hideEntry(e));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,o){const a="single"===this._entryMode?await dropAllEntries(this._history,e=>this._hideEntry(e)):[],s="geometries-"+this._addedCount++,n=pickUniqueEntryId(o??s,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:Date.now(),label:r,data:e,provenance:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...a,n]}),n}async getEntryGeometriesModule(e,t){return this._geometriesModule(this._requireEntry(e),t)}async _geometriesModule(e,t){return e._module=await withFillStyle(e._module,t,t=>createOnce(this._creatingModules,e,async()=>this.knobs.applyTo(await GeometriesModule.create(this._ttMap,fillStyledGeometriesConfig(t))))),e._module}async showEntry(e,t,r="replace"){const o=this._requireEntry(e);await this._hideOthersUnderSingleMode(e);const a=await this._geometriesModule(o,t);if(await showPolygons(a,o.data,r),!this._entries.includes(o))return void(await a.clear());const s=o._shown;o._shown=!0,s||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)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&await this._hideEntry(t)}async _hideEntry(e){e._shown&&(e._module&&await e._module.clear(),e._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){await removeEntryById(this._history,e,e=>this._hideEntry(e))&&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(){this._addedCount=0;const e=this._entries.map(e=>e.id);for(const t of this._entries)t._module?.clear();this._entries=[],this.knobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},compareIds=(e,t)=>e.localeCompare(t),sortIds=e=>[...e].sort(compareIds),getStateDigest=e=>({places:{shownAsPin:sortIds(e.places.shownAsPinIds),shownAsBaseMap:sortIds(e.places.shownAsBaseMapIds),entryMode:e.places.entryMode},routes:{shown:sortIds(e.routing.shownEntryIds),entryMode:e.routing.entryMode},ranges:{shown:sortIds(e.ranges.shownEntryIds),entryMode:e.ranges.entryMode},traffic:{shown:sortIds(e.trafficAreaAnalytics.shownEntryIds),entryMode:e.trafficAreaAnalytics.entryMode,entryCount:e.trafficAreaAnalytics.entries.length},incidents:{shown:sortIds(e.trafficIncidents.shownEntryIds),monitored:sortIds(e.trafficIncidents.entries.filter(e=>e._monitor?.isRunning).map(e=>e.id)),entryMode:e.trafficIncidents.entryMode},customGeometries:{shown:sortIds(e.customGeometries.shownEntryIds),entryMode:e.customGeometries.entryMode,entryCount:e.customGeometries.entries.length},byod:{shown:sortIds(e.byod.shownEntryIds),entryMode:e.byod.entryMode,entryCount:e.byod.entries.length}}),arraysEqual=(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},formatShownSetDiff=(e,t,r)=>{if(!arraysEqual(t,r))return 0===r.length?`${e} cleared`:`${e} shown: ${r.map(e=>`"${e}"`).join(", ")}`},SHOWN_SET_LABELS=[["routes","routes"],["ranges","ranges"],["traffic","traffic analytics"],["incidents","incidents"],["customGeometries","custom geometries"],["byod","byod"]],formatStateDigestDiff=(e,t)=>{const r=[],o=((e,t)=>{const r=[];return arraysEqual(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(", ")}`),arraysEqual(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);o&&r.push(o);for(const[a,s]of SHOWN_SET_LABELS){const o=formatShownSetDiff(s,e[a].shown,t[a].shown);if(o&&r.push(o),"incidents"===a){const o=formatShownSetDiff("incidents monitor",e.incidents.monitored,t.incidents.monitored);o&&r.push(o)}}for(const[a,s]of[["places","places"],...SHOWN_SET_LABELS])e[a].entryMode!==t[a].entryMode&&r.push(`${s} entryMode → ${t[a].entryMode}`);return 0===r.length?null:`[map state changed since last response: ${r.join("; ")}]`},MapPOIsState=class{_ttMap;_poisModule;constructor(e){this._ttMap=e}async getPOIsModule(){return this._poisModule=await POIsModule.get(this._ttMap),this._poisModule}get poisModule(){return this._poisModule}reset(){this._poisModule=void 0}},AGENT_TOOLKIT_ID="AgentToolkit",AGENT_TOOLKIT_USER_AGENT=`${TOMTOM_USER_AGENT_SDK_NAME}-AgentToolkit/0.17.2`,withAgentToolkitHeaders=e=>({...e,"tomtom-user-agent":AGENT_TOOLKIT_USER_AGENT}),placesMarkerTypes=["pin","base-map","pin-clustered"],PlacesState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_entryMode="multiple";_addedCount=0;_creatingPlacesModules=/* @__PURE__ */new WeakMap;_creatingGeometriesModules=/* @__PURE__ */new WeakMap;events=new StateEvents;knobs=new SliceKnobs(placesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.places));geometriesKnobs=new SliceKnobs(geometriesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.geometries));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=await collapseHistoryToLatest(this._history,e=>this._dropEntry(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);r._modules??={};const o=r._modules.places??=await createOnce(this._creatingPlacesModules,r,async()=>this.knobs.applyTo(await PlacesModule.create(this._ttMap,{markerType:t})));return o.getConfig()?.markerType!==t&&o.updateConfig({markerType:t}),o}async getEntryGeometriesModule(e,t){const r=this._requireEntry(e),o=r._modules??={};return o.geometries=await withFillStyle(o.geometries,t,e=>createOnce(this._creatingGeometriesModules,r,async()=>this.geometriesKnobs.applyTo(await GeometriesModule.create(this._ttMap,fillStyledGeometriesConfig(e))))),o.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,o,a){let s=[];if("single"===this._entryMode&&this._entries.length>0){const e=this.shownEntryIds.size>0;s=await dropAllEntries(this._history,e=>this._dropEntry(e)),e&&this.events.emit("shown-change",this.shownEntryIds)}const n="places-"+this._addedCount++,i=pickUniqueEntryId(r??n,this._entries.map(e=>e.id));return this._entries.push({id:i,timestamp:Date.now(),label:t,data:"features"in e?e.features:[e],...o?.length&&{connections:o},...a?.length&&{geometries:a}}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...s,i]}),a?.length&&this.events.emit("geometries-change",{entryId:i,count:a.length}),i}findPlaceById(e){for(let t=this._entries.length-1;t>=0;t--){const r=this._entries[t];if(!r._byId){r._byId=Object.create(null);for(const e of r.data)r._byId[e.id]=e}const o=r._byId[e];if(o)return{place:o,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).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 o=r._geometryByPlaceId[e];if(o)return o}}async fetchPlaceGeometry(e,t){const r=this.getGeometryForPlace(e);if(r)return r;const o=this.findPlaceById(e);if(!o)return;if(!o.place.properties.dataSources?.geometry?.id)return;const a=this._requireEntry(o.entryId),s=withAgentToolkitHeaders({geometries:[o.place],signal:t?.signal}),n=(await geometryData(s)).features[0];if(!n)return;return this.getGeometryForPlace(e)||(this._storeGeometries(a,[n]),n)}_storeGeometries(e,t){const r=[...e.geometries??[]],o=new Set(r.map(e=>e.id));for(const a of t)"string"==typeof a.id&&o.has(a.id)||(o.add(a.id),r.push(a));return this._entries.includes(e)?(e.geometries=r,e._geometryByPlaceId=void 0,this.events.emit("geometries-change",{entryId:e.id,count:r.length}),r):r}_placesNeedingGeometry(e){const t=/* @__PURE__ */new Set;for(const a of e.geometries??[])"string"==typeof a.id&&t.add(a.id);const r=[],o=/* @__PURE__ */new Set;for(const a of e.data){const e=a.properties.dataSources?.geometry?.id;e&&(t.has(e)||o.has(e)||(o.add(e),r.push(a)))}return r}async fetchGeometriesForEntry(e,t){const r=this._entries.find(t=>t.id===e);if(!r)return[];const o=this._placesNeedingGeometry(r);if(0===o.length)return r.geometries??[];const a=[];for(let c=0;c<o.length;c+=5)a.push(o.slice(c,c+5));const s=await Promise.allSettled(a.map(e=>geometryData(withAgentToolkitHeaders({geometries:e,signal:t?.signal})))),n=s.flatMap(e=>"fulfilled"===e.status?e.value.features:[]),i=s.find(e=>"rejected"===e.status),l=i&&0===n.length?r.geometries??[]:this._storeGeometries(r,n);if(i)throw i.reason;return l}async showPlaceGeometries(e,t,r="replace",o){const a=o??this._latestGeometriesEntryId();if(!a)return;const s=this._requireEntry(a),n=await this.getEntryGeometriesModule(a,t);await showPolygons(n,e,r),this._entries.includes(s)||await n.clear()}_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 o of e.geometries??[])"string"==typeof o.id&&t.set(o.id,o);const r=Object.create(null);for(const o of e.data){const e=o.properties.dataSources?.geometry?.id;if(!e)continue;const a=t.get(e);a&&(r[o.id]=a)}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),o=[],a=[];for(const s of this._entries)r.has(s.id)?o.push(s):s._shownAs===t&&a.push(s);for(const s of a)await this._hideEntry(s);for(const s of o)await this._showEntryAs(s,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 o of r)await this._showEntryAs(o,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.shownEntryIds.has(e);await removeEntryById(this._history,e,e=>this._dropEntry(e))&&(this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),t&&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),this._entries.includes(e)?e._shownAs=t:await r.clear()}async _hideEntry(e){e._shownAs&&(e._modules?.places&&await e._modules.places.clear(),e._shownAs=void 0)}async _dropEntry(e){await this._hideEntry(e),await(e._modules?.geometries?.clear())}reset(){this._addedCount=0;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.knobs.clear(),this.geometriesKnobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},RangeState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_entryMode="multiple";_addedCount=0;_creatingRangesModules=/* @__PURE__ */new WeakMap;_creatingGeometriesModules=/* @__PURE__ */new WeakMap;_creatingPlacesModules=/* @__PURE__ */new WeakMap;_drawing=/* @__PURE__ */new WeakSet;_droppedWhileDrawn=/* @__PURE__ */new Map;events=new StateEvents;knobs=new SliceKnobs(reachableRangesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.ranges));geometriesKnobs=new SliceKnobs(geometriesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.geometries));placesKnobs=new SliceKnobs(placesKnobCatalogue,()=>modulesOf(this._entries,e=>e._modules?.places));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=await collapseHistoryToLatest(this._history,e=>this._dropEntry(e));this.events.emit("entries-change",{entries:this._entries,changedIds:e})}this.events.emit("mode-change",e)}}async getEntryRangesModule(e,t){const r=this._requireDrawnEntry(e),o=r._modules??={};return o.ranges=await withFillStyle(o.ranges,t,e=>createOnce(this._creatingRangesModules,r,async()=>this.knobs.applyTo(await ReachableRangesModule.create(this._ttMap,{fill:{style:e}})))),o.ranges}async getEntryGeometriesModule(e,t){const r=this._requireDrawnEntry(e),o=r._modules??={};return o.geometries=await withFillStyle(o.geometries,t,e=>createOnce(this._creatingGeometriesModules,r,async()=>this.geometriesKnobs.applyTo(await GeometriesModule.create(this._ttMap,fillStyledGeometriesConfig(e))))),o.geometries}async getEntryPlacesModule(e){const t=this._requireDrawnEntry(e);return t._modules??={},t._modules.places??=await createOnce(this._creatingPlacesModules,t,async()=>this.placesKnobs.applyTo(await PlacesModule.create(this._ttMap))),t._modules.places}async markEntryShown(e){const t=this._droppedWhileDrawn.get(e);if(t)return this._droppedWhileDrawn.delete(e),void(await this._clearModules(t));const r=this._requireEntry(e);if(this._drawing.delete(r),"single"===this._entryMode){const t=this._entries.filter(t=>t._shown&&t.id!==e);for(const e of t)await this._hideEntry(e)}const o=r._shown;r._shown=!0,o||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&await this._hideEntry(t)}async _hideEntry(e){e._shown&&(await this._clearModules(e),e._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async _clearModules(e){e._modules?.ranges&&await e._modules.ranges.clear(),e._modules?.geometries&&await e._modules.geometries.clear(),e._modules?.places&&await e._modules.places.clear()}async _dropEntry(e){this._drawing.has(e)&&this._droppedWhileDrawn.set(e.id,e),await this._hideEntry(e)}async removeEntry(e){await removeEntryById(this._history,e,e=>this._dropEntry(e))&&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}_requireDrawnEntry(e){const t=this._droppedWhileDrawn.get(e);if(t)return t;const r=this._requireEntry(e);return r._shown||this._drawing.add(r),r}get entries(){return this._entries}get latestEntry(){return this._entries.at(-1)}async addEntry(e){const t="single"===this._entryMode?await dropAllEntries(this._history,e=>this._dropEntry(e)):[],r="ranges-"+this._addedCount++;return this._entries.push({id:r,timestamp:Date.now(),label:e.label,data:e.data}),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(){this._addedCount=0;const e=this._entries.map(e=>e.id);for(const t of this._entries)t._modules?.ranges?.clear(),t._modules?.geometries?.clear(),t._modules?.places?.clear();this._entries=[],this._droppedWhileDrawn.clear(),this.knobs.clear(),this.geometriesKnobs.clear(),this.placesKnobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},errorMessageOf=e=>e instanceof Error?e.message:String(e),PollingMonitor=class{_subject;events=new StateEvents;_status="idle";_error;_intervalHandle;_clearInterval;_fetch;_run=0;_tickInFlightRun;constructor(e){this._subject=e}get status(){return this._status}get error(){return this._error}get isRunning(){return"running"===this._status}get isActive(){return"idle"!==this._status}stop(){"idle"!==this._status&&(this._run++,this._releaseTimer(),this._status="idle",this._error=void 0,this._fetch=void 0)}_startPolling(e,t,r,o){if("running"===this._status)return;this._run++,this._status="running",this._error=void 0,this._fetch=e,this._clearInterval=t.clearInterval??(e=>clearInterval(e));const a=t.setInterval??setInterval;this._intervalHandle=a(()=>{this._tick()},r),o||this._tick()}async _tick(){const e=this._fetch,t=this._run;if(!e||this._tickInFlightRun===t)return;let r;this._tickInFlightRun=t;try{r=await e()}catch(o){return void(t===this._run&&this._failed(errorMessageOf(o)))}finally{this._tickInFlightRun===t&&(this._tickInFlightRun=void 0)}if(t===this._run)try{this.events.emit("tick",this._snapshot(r,Date.now()))}catch(o){console.warn(`[agent-toolkit] ${this._subject} tick listener threw:`,o)}}_failed(e){this._releaseTimer(),this._error=e,this._status="stopped-error",this._fetch=void 0;try{this.events.emit("error",e)}catch(t){console.warn(`[agent-toolkit] ${this._subject} error listener threw:`,t)}}_releaseTimer(){null!=this._intervalHandle&&(this._clearInterval?.(this._intervalHandle),this._intervalHandle=void 0),this._clearInterval=void 0}},RouteMonitor=class extends PollingMonitor{constructor(){super("a route monitor")}start(e,t=6e4,r=!1){this._startPolling(e.recalculate,e,t,r)}_snapshot(e,t){return{takenAt:t,routes:e}}},selectedRouteIndex=e=>Math.max(0,e?.getShown().mainLines.features.findIndex(e=>"selected"===e.properties.routeState)??0),RoutingState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_planningSlots=[];_params={};_entryMode="multiple";_addedCount=0;_creatingModules=/* @__PURE__ */new WeakMap;knobs=new SliceKnobs(routingKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));_lastShownEntryId;events=new StateEvents;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=await collapseHistoryToLatest(this._history,e=>this._dropEntry(e));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 createOnce(this._creatingModules,t,async()=>this.knobs.applyTo(await RoutingModule.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)}const o=await this.getEntryRoutingModule(e);!1===t?.showSummaryBubbles&&o.updateConfig({summaryBubbles:{...o.getConfig()?.summaryBubbles,visible:!1}});let a=r.data;r._shown?await this._showRoutesKeepingSelection(r,o):await o.showRoutes(a);const s=t?.showWaypoints??!1!==r._waypointsShown;for(await this._renderWaypoints(o,s?r.waypoints:[]);!r._shown&&r.data!==a;)a=r.data,await o.showRoutes(a);if(!this._entries.includes(r))return await o.clearRoutes(),void(await o.clearWaypoints());const n=r._shown;r._shown=!0,this._lastShownEntryId=e,n||this.events.emit("shown-change",this.shownEntryIds)}async setWaypointsShown(e,t){const r=this._requireEntry(e);r._waypointsShown=t,r._shown&&r._module&&await this._renderWaypoints(r._module,t?r.waypoints:[])}async _renderWaypoints(e,t){t.length?await e.showWaypoints(t):await e.clearWaypoints()}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&await this._hideEntry(t)}async _hideEntry(e){e._shown&&(e._module&&(await e._module.clearRoutes(),await e._module.clearWaypoints()),e._shown=!1,e._sectionFocus=void 0,this._lastShownEntryId===e.id&&(this._lastShownEntryId=this._entries.findLast(e=>e._shown)?.id),this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){await removeEntryById(this._history,e,e=>this._dropEntry(e))&&this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}async _dropEntry(e){this._stopMonitor(e),await this._hideEntry(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 o="single"===this._entryMode?await dropAllEntries(this._history,e=>this._dropEntry(e)):[],a="routes-"+this._addedCount++;return this._entries.push({id:a,timestamp:Date.now(),label:r,data:e,waypoints:t,params:{...this._params}}),this._planningSlots=[...t],this.events.emit("entries-change",{entries:this._entries,changedIds:[...o,a]}),this.events.emit("planning-change",this._planningSlots),a}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 o=this._entries.find(t=>t.id===e);return!!o&&(o._tick=(o._tick??0)+1,o.data=t,o.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),o._shown&&o._module&&await this._showRoutesKeepingSelection(o,o._module),!0)}async _showRoutesKeepingSelection(e,t){const{data:r,_tick:o}=e,a=selectedRouteIndex(t),s=a<r.features.length?a:0;await t.showRoutes(r,{selectedIndex:s});const n=r.features[s],i=n&&e._sectionFocus?.(n);i&&e._tick===o&&await t.setSectionFocus(i)}_createMonitorForEntry(e){const t=new RouteMonitor;return t.events.on("tick",t=>{this.replaceRouteData(e.id,t.routes,t.takenAt).catch(e=>console.warn("[agent-toolkit] a monitored route failed to repaint:",e)),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 o=this._requireEntry(e),a=o._monitor??=this._createMonitorForEntry(o);a.isRunning||this.events.emit("monitor-start",{entryId:e}),a.start(t,r?.intervalMs,r?.skipInitialTick)}stopMonitoring(e){const t=this._entries.find(t=>t.id===e);t&&this._stopMonitor(t)}_stopMonitor(e){if(!e._monitor)return;const t=e._monitor.isRunning;e._monitor.stop(),t&&this.events.emit("monitor-stop",{entryId:e.id,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(){this._addedCount=0;const e=this._entries.map(e=>e.id);for(const t of this._entries)this.stopMonitoring(t.id),t._module?.clearRoutes(),t._module?.clearWaypoints();this._entries=[],this._planningSlots=[],this._params={},this._lastShownEntryId=void 0,this.knobs.clear(),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)}},JobEngine=class{jobs=/* @__PURE__ */new Set;wired=!1;scanning=!1;scheduled=!1;pending=/* @__PURE__ */new Set;generation=0;register(e){const t=new JobHandle(e,this);return this.jobs.add(t),t}drop(e){this.jobs.delete(e),e.active=!1}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){if(0!==this.jobs.size){this.collectOrphans(e);for(const e of t)this.pending.add(e);this.scheduleScan()}}collectOrphans(e){const t=/* @__PURE__ */new Set;for(const o of this.sourceSlices(e))for(const e of o.entries)t.add(e.id);const r=[...this.jobs].filter(e=>!e.affectedEntryIds.some(e=>t.has(e)));for(const o of r)this.drop(o),o.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,t=this.generation;this.pending=/* @__PURE__ */new Set,this.scanning=!0;try{for(const o of[...this.jobs]){if(t!==this.generation)return;if(o.active&&this.jobs.has(o)&&o.affectedEntryIds.some(t=>e.has(t)))try{await o.run()}catch(r){console.warn("[agent-toolkit] a standing job failed to re-run:",r)}}}finally{t===this.generation&&(this.scanning=!1)}t===this.generation&&this.pending.size>0&&this.scheduleScan()}reset(){this.jobs.clear(),this.pending.clear(),this.generation++,this.scanning=!1,this.scheduled=!1}},JobHandle=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)}},toJsonSafe=e=>{if(void 0===e)return{value:e};try{return{value:JSON.parse(JSON.stringify(e))}}catch(t){return{error:`Returned value is not JSON-serializable: ${errorMessageOf(t)}. Return only plain objects, arrays, strings, numbers, booleans, and null — no functions, classes, circular references, or DOM/Map references.`}}},buildSandboxCodePrompt=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 …;\`.`,CHART_TYPES=/* @__PURE__ */new Set(["bar","line","pie","doughnut","radar","polarArea","scatter","bubble"]),isChartConfiguration=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"string"==typeof t.type&&CHART_TYPES.has(t.type)&&"object"==typeof t.data&&null!==t.data},isPolygonFeature=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)},isPolygonFeatureArray=e=>Array.isArray(e)&&e.every(isPolygonFeature),ANALYSE_OUTPUT_FORMAT_DESCRIPTION='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.',buildAnalyseReturnPrompt=(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})`:""}.`,validateAnalysisResult=(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=toJsonSafe(e);return"error"in r?{error:r.error}:"chart"!==t||isChartConfiguration(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? }).'}},SHADOWED_GLOBALS=["fetch","XMLHttpRequest","WebSocket","EventSource","localStorage","sessionStorage","indexedDB","document","window","globalThis","self","navigator","importScripts","process"],sandboxedGlobalShadows=SHADOWED_GLOBALS,WORKER_PROVIDED_PARAMS=["turf","h3","routeUtils","log","cluster"],mainThreadExecutor={run:async(e,t,r,o)=>{try{const o=Object.getPrototypeOf(async()=>{}).constructor,a=SHADOWED_GLOBALS.filter(e=>!t.includes(e)),s=r.map((e,r)=>((e,t)=>void 0===e||WORKER_PROVIDED_PARAMS.includes(t)?e:structuredClone(e))(e,t[r]));return{value:await new o(...t,...a,e).apply(Object.create(null),[...s,...a.map(()=>{})])}}catch(a){return{error:formatSandboxExecutionError(o,a)}}}},runSandboxedFn=(e,t,r,o,a)=>a.run(stripInjectedRedeclarations(e,t),t,r,o),stripInjectedRedeclarations=(e,t)=>{const r=t.join("|"),o=String.raw`(?:(?:await\s+)?(?:require|import)\s*\(`+String.raw`|arguments\s*(?:\[\s*\d+\s*\])?\s*(?:\.\w+|\[\s*['"]\w+['"]\s*\]))`,a=new RegExp(String.raw`^[ \t]*(?:const|let|var)[ \t]+(?:${r})[ \t]*=[ \t]*${o}[^;\n]*;?[ \t]*\n?`,"gm");return e.replace(a,"")},SANDBOX_ERROR_HINTS=[{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."}],formatSandboxExecutionError=(e,t)=>{const r=errorMessageOf(t),o=`${e} code execution failed: ${r}`,a=SANDBOX_ERROR_HINTS.find(({pattern:e})=>e.test(r));return a?`${o} Hint: ${a.hint}`:t instanceof Error&&"SyntaxError"===t.name?`${o} Hint: the provided \`code\` is not valid JavaScript — ensure it parses as an async function body and \`return\`s a value.`:o},WORKER_RUNTIME_BODY=`\n(function () {\n var SHADOWS = ${JSON.stringify([...sandboxedGlobalShadows,"postMessage","onmessage","addEventListener","removeEventListener","close"])};\n var PROVIDED = ${JSON.stringify(WORKER_PROVIDED_PARAMS)};\n var AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;\n var providedValue = function (name) { return name === 'log' ? function () {} : self[name]; };\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 PROVIDED.indexOf(name) === -1 ? dataByIndex[index] : providedValue(name);\n });\n var fn = AsyncFunction.apply(null, paramNames.concat(shadows).concat([msg.code]));\n Promise.resolve(fn.apply(Object.create(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, errorName: runError && runError.name, errorMessage: String((runError && runError.message) || runError) });\n });\n } catch (compileError) {\n self.postMessage({ type: 'result', id: id, ok: false, errorName: compileError && compileError.name, errorMessage: String((compileError && compileError.message) || compileError) });\n }\n };\n try {\n new AsyncFunction('');\n self.postMessage({ type: 'worker-ready' });\n } catch (evalError) {\n self.postMessage({ type: 'worker-error', errorMessage: String((evalError && evalError.message) || evalError) });\n }\n})();\n`,hostRealmExecutor={...mainThreadExecutor,getMode:()=>"main-thread"},formatStartFailure=(e,t)=>`${e} code was not run: the isolated code sandbox could not start (${t}). Tell the user that code execution is unavailable right now; a later request tries the sandbox again.`,defaultLoadLibrarySource=()=>import("./worker-libs-CGRly7A4.js").then(e=>e.sandboxLibrarySource),createIframeWorkerExecutor=e=>{let t,r=null,o=null,a=0,s="not-started",n=0,i=!1,l=1;const c=/* @__PURE__ */new Map,d=e=>{r?.contentWindow?.postMessage(e,"*")},u=e=>{for(const t of c.values())clearTimeout(t.timer),t.resolve({error:formatSandboxExecutionError(t.verb,e)});c.clear()},h=e=>{if(e.source!==r?.contentWindow)return;const t=e.data;if("worker-error"===t?.type)return void u(t.errorMessage??"worker error");if("result"!==t?.type)return;const o=t,a=c.get(o.id);if(!a)return;if(c.delete(o.id),clearTimeout(a.timer),o.ok)return void a.resolve({value:o.value});const s=new Error(o.errorMessage);o.errorName&&(s.name=o.errorName),a.resolve({error:formatSandboxExecutionError(a.verb,s)})},p=e=>new Promise((t,o)=>{const a=e=>{clearTimeout(s),globalThis.removeEventListener("message",n),e?o(e):t()},s=setTimeout(()=>a(/* @__PURE__ */new Error(`timed out waiting for "${e}"`)),2e3),n=t=>{if(t.source!==r?.contentWindow)return;const o=t.data;o?.type===e?a():"worker-error"===o?.type&&a(new Error(o.errorMessage??"worker error"))};globalThis.addEventListener("message",n)}),y=()=>{if("iframe-worker"===s)return Promise.resolve(void 0);if(Date.now()<n)return Promise.resolve(t);if(o)return o;const l=a;return o=(async()=>{if("undefined"==typeof document||"undefined"==typeof Worker||"undefined"==typeof URL||"undefined"==typeof Blob)return"browser APIs missing";const t=a;let o;try{o=document.createElement("iframe"),o.setAttribute("sandbox","allow-scripts"),o.setAttribute("aria-hidden","true"),o.style.display="none",o.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, workerUrl = null;\n function spawn() {\n worker = new Worker(workerUrl);\n worker.onmessage = function (e) { parent.postMessage(e.data, '*'); };\n worker.onerror = function (e) {\n var detail = e && e.message ? (e.message + (e.filename ? ' @ ' + e.filename + ':' + e.lineno : '')) : 'the worker failed to load';\n parent.postMessage({ type: 'worker-error', errorMessage: detail }, '*');\n };\n }\n window.addEventListener('message', function (ev) {\n if (ev.source !== parent) return;\n var msg = ev.data || {};\n if (msg.type === 'init') {\n workerUrl = URL.createObjectURL(new Blob([msg.workerSource], { type: 'application/javascript' }));\n spawn();\n return;\n }\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>",r=o;const s=p("bootstrap-ready");document.body.appendChild(o),await s;const n=await e.loadLibrarySource();if(a!==t)throw new Error("the code sandbox was destroyed");const i=(e=>`${e}\n;${WORKER_RUNTIME_BODY}`)(n),l=p("worker-ready");if(d({type:"init",workerSource:i}),await l,a!==t)throw new Error("the code sandbox was destroyed");return void globalThis.addEventListener("message",h)}catch(s){return o?.remove(),r===o&&(r=null),errorMessageOf(s)}})().then(r=>(a!==l||(o=null,void 0===r?(s="iframe-worker",t=void 0,n=0):(s=e.allowMainThreadFallback?"main-thread":"unavailable",t=r,n=e.allowMainThreadFallback?Number.POSITIVE_INFINITY:Date.now()+3e4,(t=>{if(i)return;i=!0;const r=e.allowMainThreadFallback?"falling back to main-thread execution (NOT isolated)":"model-written code does not run until it starts";console.warn(`[agent-toolkit] iframe-worker sandbox unavailable (${t}); ${r}.`)})(r))),r)),o};return{run:async(t,o,a,n)=>{"iframe-worker"!==s||r?.isConnected||(globalThis.removeEventListener("message",h),u(/* @__PURE__ */new Error("the code sandbox was removed from the page")),r=null,s="not-started");const i=await y();if(void 0!==i)return e.allowMainThreadFallback?mainThreadExecutor.run(t,o,a,n):{error:formatStartFailure(n,i)};const p=l++,{dataByIndex:m}=((e,t)=>{const r=new Set(WORKER_PROVIDED_PARAMS),o={};for(let a=0;a<e.length;a++)r.has(e[a])||(o[a]=t[a]);return{dataByIndex:o}})(o,a);return new Promise(r=>{const a=setTimeout(()=>{c.delete(p),d({type:"terminate"}),u(/* @__PURE__ */new Error("code execution was terminated because a concurrent run timed out")),r({error:formatSandboxExecutionError(n,/* @__PURE__ */new Error(`code execution exceeded the ${e.timeoutMs}ms sandbox timeout and was terminated`))})},e.timeoutMs);c.set(p,{resolve:r,verb:n,timer:a});try{const e={type:"run",id:p,code:t,paramNames:[...o],dataByIndex:m};d(e)}catch(s){clearTimeout(a),c.delete(p),r({error:formatSandboxExecutionError(n,s)})}})},destroy:()=>{void 0!==globalThis.window&&globalThis.removeEventListener("message",h),u(/* @__PURE__ */new Error("the code sandbox was destroyed")),a++,o=null,r?.remove(),r=null,s="not-started",t=void 0,n=0,i=!1},getMode:()=>s}},resolveSandboxExecutor=e=>"undefined"==typeof document?hostRealmExecutor:createIframeWorkerExecutor({timeoutMs:e?.timeoutMs??1e4,loadLibrarySource:e?.loadWorkerLibrarySource??defaultLoadLibrarySource,allowMainThreadFallback:e?.allowMainThreadFallback??!1}),EventsState=class{_trackers=/* @__PURE__ */new Map;_log=[];_jobs=/* @__PURE__ */new Map;_seq=0;events=new StateEvents;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,this.emitTrackers()}ingestVerdict(e,t,r){const o=this._trackers.get(e);if(o?.enabled&&isVerdict(t))if(t.active&&!o.wasActive)o.wasActive=!0,o.openedAt=r,this.append(o,{type:"alert",kind:"opened",verdict:t,at:r});else if(!t.active&&o.wasActive){const e=o.openedAt;o.wasActive=!1,o.openedAt=void 0,this.append(o,{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 o=this._log.filter(t=>t.trackerId===e.id);o.length>200&&this._log.splice(this._log.indexOf(o[0]),1),this.events.emit("tracker-event",r),this.emitTrackers()}emitTrackers(){this.events.emit("trackers-change",this.trackers)}},isMemberRef=e=>{if("object"!=typeof e||null===e)return!1;const{entryId:t,featureIds:r}=e;return"string"==typeof t&&Array.isArray(r)&&r.every(e=>"string"==typeof e)},isVerdict=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)&&t.members.every(isMemberRef)},TrafficAreaAnalyticsState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_entryMode="multiple";_addedCount=0;_creatingModules=/* @__PURE__ */new WeakMap;events=new StateEvents;knobs=new SliceKnobs(trafficAreaAnalyticsKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));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=await collapseHistoryToLatest(this._history,e=>this._dropEntry(e));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,o){const a="single"===this._entryMode?await dropAllEntries(this._history,e=>this._dropEntry(e)):[],s="tta-"+this._addedCount++,n=pickUniqueEntryId(o??s,this._entries.map(e=>e.id));return this._entries.push({id:n,timestamp:Date.now(),label:t,data:e,params:r}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...a,n]}),n}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await createOnce(this._creatingModules,t,async()=>{const r=await TrafficAreaAnalyticsModule.create(this._ttMap);return t._configChangeUnsub=r.events.on("config-change",o=>{const a=t.data.properties?.metrics??[];o?.activeMetric&&a.length>0&&!a.includes(o.activeMetric)?r.updateConfig({activeMetric:a[0]}):(t._config=o,this.events.emit("config-change",{entryId:e,config:o}))}),this.knobs.applyTo(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)}const r=await this.getEntryModule(e),o=t.data.properties?.metrics??[],a=r.getConfig()?.activeMetric;if(o.length>0&&(!a||!o.includes(a))){const{activeMetric:e}=this.knobs.values;r.updateConfig({activeMetric:o.find(t=>t===e)??o[0]})}if(await r.show(t.data),!this._entries.includes(t))return t._configChangeUnsub?.(),t._configChangeUnsub=void 0,void(await r.clear());const s=t._shown;t._shown=!0,s||this.events.emit("shown-change",this.shownEntryIds)}async hideEntry(e){const t=this._entries.find(t=>t.id===e);t&&await this._hideEntry(t)}async _hideEntry(e){e._shown&&(await(e._module?.clear()),e._shown=!1,this.events.emit("shown-change",this.shownEntryIds))}async removeEntry(e){await removeEntryById(this._history,e,e=>this._dropEntry(e))&&this.events.emit("entries-change",{entries:this._entries,changedIds:[e]})}async _dropEntry(e){await this._hideEntry(e),e._configChangeUnsub?.(),e._configChangeUnsub=void 0,e._module=void 0}_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(){this._addedCount=0;const e=this._entries.map(e=>e.id);for(const t of this._entries)t._configChangeUnsub?.(),t._module?.clear();this._entries=[],this.knobs.clear(),this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds)}},DEFAULT_EXCLUDE_CATEGORIES=["roadworks","road-closed"],positiveIntegerOr=(e,t)=>void 0!==e&&Number.isInteger(e)&&e>0?e:t,compareCodeUnits=(e,t)=>Number(e>t)-Number(e<t),toPoint=e=>{const t=e.properties.id;return"Point"===e.geometry.type?turf.point(e.geometry.coordinates,{_incidentId:t}):turf.centroid(e,{properties:{_incidentId:t}})},collectPrimaryRoads=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)},collectPrimaryCategory=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"},jaccardIndex=(e,t)=>{let r=0;for(const a of e)t.has(a)&&r++;const o=e.size+t.size-r;return 0===o?0:r/o},clusterIncidents=(e,t={},r,o)=>{const a=(e=>{if(null!=e)return e instanceof Date?e.getTime():e})(o),{groups:s}=((e,t,r,o,a)=>{if(0===e.length)return{groups:[]};const s=(n=t.preFilter?.excludeCategories,i=DEFAULT_EXCLUDE_CATEGORIES,Array.isArray(n)&&n.every(e=>"string"==typeof e)?n:i);var n,i;const l=t.preFilter?.minDelaySeconds,c=((e,t,r)=>{if(0===r)return e;const o=new Set(t);return e.filter(e=>{const t=e.properties.category,a=e.properties.delayInSeconds??0;return!(o.has(t)&&a<r)})})(e,s,"number"==typeof l&&Number.isFinite(l)?l:1);if(0===c.length)return{groups:[]};const d=/* @__PURE__ */new Map;for(const I of c)d.set(I.properties.id,I);const u=[...d.values()].map(toPoint),h=turf.featureCollection(u),p=(e=>void 0!==e&&Number.isFinite(e)&&e>0?e:.5)(t.eps),y=positiveIntegerOr(t.minMembers,3),m=turf.clustersDbscan(h,p,{minPoints:y,mutate:!0}),f=/* @__PURE__ */new Map;for(const I of m.features){const e=I.properties.cluster;if(void 0===e||e<0)continue;let t=f.get(e);t||(t=[],f.set(e,t)),t.push(I)}const g=positiveIntegerOr(t.maxClusters,6),b=[...f.values()].map(e=>{const t=e.map(e=>e.properties._incidentId),r=t.map(e=>d.get(e)).filter(e=>void 0!==e);return{groupPoints:e,memberIds:t,members:r,totalDelaySeconds:r.reduce((e,t)=>e+(t.properties.delayInSeconds??0),0)}});b.sort((e,t)=>t.totalDelaySeconds-e.totalDelaySeconds);const w=b.slice(0,g).map(({groupPoints:e,memberIds:t,members:r,totalDelaySeconds:o})=>{const a=r.reduce((e,t)=>Math.max(e,t.properties.delayInSeconds??0),0),s=turf.featureCollection(e),n=turf.centroid(s).geometry.coordinates;let i=0;if(e.length>=2){const e=turf.bbox(s);i=turf.distance([e[0],e[1]],[e[2],e[3]])}return{centroid:n,memberIds:t,size:r.length,totalDelaySeconds:o,peakDelaySeconds:a,diameterKm:i,primaryRoads:collectPrimaryRoads(r),primaryCategory:collectPrimaryCategory(r)}}),S=r?((e,t)=>{const r=t.map(e=>new Set(e.memberIds)),o=[];for(let i=0;i<e.length;i++){const a=new Set(e[i].memberIds);for(let e=0;e<t.length;e++){const t=jaccardIndex(a,r[e]);t>=.5&&o.push({freshIndex:i,previousIndex:e,overlap:t})}}o.sort((e,t)=>t.overlap-e.overlap);const a=/* @__PURE__ */new Map,s=/* @__PURE__ */new Set,n=/* @__PURE__ */new Set;for(const{freshIndex:i,previousIndex:l,overlap:c}of o)s.has(i)||n.has(l)||(a.set(i,t[l].id),s.add(i),n.add(l));return a})(w,r):/* @__PURE__ */new Map,v=/* @__PURE__ */new Map;if(r)for(const I of r)v.set(I.id,I);const E=void 0===o||void 0===a||o>a;return{groups:w.map((e,t)=>{const r=S.get(t),o=r??`cluster-${e.centroid[0].toFixed(3)}-${e.centroid[1].toFixed(3)}-${[...e.memberIds].sort(compareCodeUnits)[0]??t}`,a=r?v.get(r):void 0,s=a?.delaySamples??[],n=a?(E?[...s,e.totalDelaySeconds]:[...s.slice(0,-1),e.totalDelaySeconds]).slice(-3):[e.totalDelaySeconds],i=1===n.length?"new":(e=>{if(e.length<3)return"steady";const[t,r,o]=e.slice(-3),a=(o-t)/Math.max(t,1);return r>t&&o>r&&a>.1?"growing":r<t&&o<r&&a<-.1?"fading":"steady"})(n);return{id:o,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:n}})}})(e,t??{},r?.groups,a,r?.sampledAt);return void 0===a?{groups:s}:{groups:s,sampledAt:a}},isClusterLike=e=>{if(!e||"object"!=typeof e)return!1;const{id:t,memberIds:r,centroid:o}=e;return"string"==typeof t&&Array.isArray(r)&&Array.isArray(o)&&2===o.length&&o.every(e=>"number"==typeof e)},IncidentMonitor=class extends PollingMonitor{_bbox;_area;constructor(e){super("an incident monitor"),this._bbox=e}get monitoredArea(){return this._area}start(e,t,r=6e4,o=!1){this.isRunning||(this._area=e,this._startPolling(()=>t.fetchIncidents(this._bbox),t,r,o))}stop(){super.stop(),this._area=void 0}_snapshot(e,t){return{takenAt:t,incidents:e}}},fitMapToBBox=(e,t,r)=>{const[o,a,s,n]=t,i=o>s?[o,a,s+360,n]:t;e.fitBounds(i,{padding:50,...r})},fitMapToGeoJSON=(e,t,r)=>{const o=bboxFromGeoJSON(t);return!!o&&(fitMapToBBox(e,o,r),!0)},TrafficIncidentsState=class{_ttMap;_entries=[];_history={get:()=>this._entries,set:e=>{this._entries=e}};_entryMode="multiple";_addedCount=0;_creatingModules=/* @__PURE__ */new WeakMap;_clusters=/* @__PURE__ */new Map;events=new StateEvents;knobs=new SliceKnobs(trafficIncidentDetailsKnobCatalogue,()=>modulesOf(this._entries,e=>e._module));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=await collapseHistoryToLatest(this._history,e=>this._teardownEntry(e));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,o,a){let s=!1;const n="single"===this._entryMode?await dropAllEntries(this._history,e=>(s||=!!e._shown,this._teardownEntry(e))):[];s&&this.events.emit("shown-change",this.shownEntryIds);const i="incidents-"+this._addedCount++,l=pickUniqueEntryId(a??i,this._entries.map(e=>e.id));return this._entries.push({id:l,timestamp:o,label:r,data:e,params:t}),this.events.emit("entries-change",{entries:this._entries,changedIds:[...n,l]}),l}async replaceEntryData(e,t,r){const o=this._entries.find(t=>t.id===e);if(!o)return!1;const a=(o._tick??0)+1;o._tick=a,o.data=t,o.timestamp=r,this.events.emit("entries-change",{entries:this._entries,changedIds:[e]});const s=o._shown?o._module:void 0;if(s&&(await s.show({type:"FeatureCollection",features:t}),o._tick!==a))return!0;if(o._focus){const e=new Set(t.map(e=>e.properties.id)),r=[...o._focus.ids].filter(t=>e.has(t));s?.setFocus(r.length>0?r:null),r.length<o._focus.ids.size&&(o._focus=r.length>0?{...o._focus,ids:new Set(r)}:void 0,this.events.emit("focus-change",{entryId:o.id,focus:this.getFocus(o.id)}))}return!0}async getEntryModule(e){const t=this._requireEntry(e);return t._module??=await createOnce(this._creatingModules,t,async()=>this.knobs.applyTo(await TrafficIncidentDetailsModule.create(this._ttMap))),t._module}async showEntry(e,t=!0){const r=this._requireEntry(e),o=async e=>!this._entries.includes(r)&&(e.setVisible(!1),await e.clear(),r._module=void 0,!0),a=await this.getEntryModule(e);if(await o(a))return;let s;do{if(s=r.data,await a.show({type:"FeatureCollection",features:s}),await o(a))return}while(r.data!==s);r._focus&&a.setFocus([...r._focus.ids]),a.setVisible(!0),t&&fitMapToBBox(this._ttMap.mapLibreMap,r.params.bbox,{maxZoom:14});const n=!!r._shown;r._shown=!0,n||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.shownEntryIds.has(e);await removeEntryById(this._history,e,e=>this._teardownEntry(e))&&(this.events.emit("entries-change",{entries:this._entries,changedIds:[e]}),t&&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 o=this._entries.find(t=>t.id===e);if(!o)return{focusedCount:0,droppedIds:[...t]};if(0===t.length)return this.clearFocus(e),{focusedCount:0,droppedIds:[]};const a=new Set(o.data.map(e=>e.properties.id)),s=[],n=[];for(const i of new Set(t))a.has(i)?s.push(i):n.push(i);return 0===s.length?{focusedCount:0,droppedIds:n}:(o._focus={ids:new Set(s),reason:r},o._module?.setFocus(s),this.events.emit("focus-change",{entryId:o.id,focus:this.getFocus(e)}),{focusedCount:s.length,droppedIds:n})}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 a of t.data){const e=a.properties.id;"string"==typeof e&&(r[e]=a)}const o=[];for(const a of t._focus.ids){const e=r[a];e&&o.push(e)}return o}navigateFocus(e,t){const r=this.focusedFeatures(e);if(0===r.length)return;const o=r[(t%r.length+r.length)%r.length],a=this._ttMap.mapLibreMap;"Point"!==o.geometry.type?fitMapToGeoJSON(a,o,{padding:120,maxZoom:16,duration:600}):a.flyTo({center:o.geometry.coordinates,zoom:15,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,o){const a=this._requireEntry(e),s=a._monitor??=this._createMonitorForEntry(a);s.isRunning||this.events.emit("monitor-start",{entryId:e}),s.start(t,r,o?.intervalMs,o?.skipInitialTick)}stopMonitoring(e){const t=this._entries.find(t=>t.id===e);t&&this._stopMonitor(t)}_stopMonitor(e){const t=e._monitor;if(!t?.isActive)return;const r=t.isRunning;t.stop(),r&&this.events.emit("monitor-stop",{entryId:e.id,reason:"manual"})}isMonitored(e){return this._entries.find(t=>t.id===e)?._monitor?.isRunning??!1}_createMonitorForEntry(e){const t=new IncidentMonitor(e.params.bbox);return t.events.on("tick",t=>{this.replaceEntryData(e.id,t.incidents,t.takenAt).catch(e=>console.warn("[agent-toolkit] monitored incidents failed to repaint:",e)),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._stopMonitor(e),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(){this._addedCount=0;const e=this._entries.map(e=>e.id);for(const r of this._entries)this._teardownEntry(r).catch(e=>console.warn("[agent-toolkit] a dropped incidents entry failed to clear:",e));const t=this._entries.length>0;this._entries=[],this.knobs.clear(),t&&(this.events.emit("entries-change",{entries:this._entries,changedIds:e}),this.events.emit("shown-change",this.shownEntryIds))}},TrafficTilesState=class{_ttMap;_trafficFlowModule;_trafficIncidentsModule;constructor(e){this._ttMap=e}async getTrafficFlowModule(){return this._trafficFlowModule=await TrafficFlowModule.get(this._ttMap),this._trafficFlowModule}async getTrafficIncidentsModule(){return this._trafficIncidentsModule=await TrafficIncidentsModule.get(this._ttMap),this._trafficIncidentsModule}get trafficFlowModule(){return this._trafficFlowModule}get trafficIncidentsModule(){return this._trafficIncidentsModule}reset(){this._trafficFlowModule=void 0,this._trafficIncidentsModule=void 0}};TomTomConfig.instance.put({language:"en-GB"});var DATA_ENTRY_KIND_TO_SLICE={places:"places",routes:"routing",incidents:"trafficIncidents",customGeometries:"customGeometries",trafficAreaAnalytics:"trafficAreaAnalytics",byod:"byod",ranges:"ranges"},ENTRY_MODE_SLICE_NAMES=/* @__PURE__ */(()=>Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}))(),CLEARABLE_SLICE_NAMES=/* @__PURE__ */(()=>Object.keys({places:!0,routing:!0,ranges:!0,customGeometries:!0,byod:!0,trafficAreaAnalytics:!0,trafficIncidents:!0}))(),SECTION_ORDER=["identity","capabilities","rejectionRules","responseFormatting","dataConfidence","toolExecution","sessionState"],SECTION_HEADINGS={identity:null,capabilities:null,rejectionRules:"SCOPE & REJECTIONS",responseFormatting:"RESPONSE FORMATTING",dataConfidence:"DATA CONFIDENCE",toolExecution:"TOOL EXECUTION",sessionState:"SESSION STATE"},SYSTEM_PROMPT_SECTIONS={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."},composeSystemPrompt=e=>SECTION_ORDER.map(t=>({key:t,body:e?.[t]??SYSTEM_PROMPT_SECTIONS[t]})).filter(({body:e})=>e.trim().length>0).map(({key:e,body:t})=>((e,t)=>{const r=SECTION_HEADINGS[e];return r?`${r}:\n${t}`:t})(e,t)).join("\n\n"),BASE_SYSTEM_PROMPT=/* @__PURE__ */composeSystemPrompt(),buildSystemPrompt=({customPrompt:e,prefix:t,suffix:r}={})=>{if("string"==typeof e)return e;const o=e?composeSystemPrompt(e):BASE_SYSTEM_PROMPT,a=r?`${o}\n\nADDITIONAL INSTRUCTIONS:\n${r}`:o;return t?`${t}\n\n${a}`:a},geometriesIdSchema=z.discriminatedUnion("kind",[z.object({kind:z.literal("place").describe('Use the exact literal "place".'),id:z.string().min(1).describe("Id of a single place; resolves to that place’s footprint.")}).describe("A single place footprint."),z.object({kind:z.literal("places").describe('Use the exact literal "places".'),id:z.string().min(1).describe("Id of a places-entry; resolves to every footprint in the entry.")}).describe("Every footprint in a places entry."),z.object({kind:z.literal("ranges").describe('Use the exact literal "ranges".'),id:z.string().min(1).describe("Id of a ranges-entry; resolves to every isochrone polygon in the entry.")}).describe("Every isochrone polygon in a ranges entry."),z.object({kind:z.literal("customGeometries").describe('Use the exact literal "customGeometries".'),id:z.string().min(1).describe("Id of a custom-geometries entry produced by a previous `processData` call.")}).describe("A derived entry produced by a previous `processData` call.")]).describe('Tagged id `{ kind, id }`. `kind` must be exactly one of: "place" | "places" | "ranges" | "customGeometries" (do NOT use variants like "ranges-entry", "place-footprints", etc.). Use `recallState` to list every available `{kind, id}` pair.'),geometriesEntryIDsSchema=z.array(geometriesIdSchema).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.'),GEOMETRIES_PROPS_DOC="`geometry.properties` shape varies by source — place footprints: the geometry data service's own, with no place fields (match `feature.id` to `place.properties.dataSources.geometry.id`); 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.",GEOMETRIES_SKIPPED_DESC="Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).",GEOMETRIES_SOURCE_IDS_DESC="Tagged input ids that contributed at least one polygon to the run, in input order.",GEOMETRIES_SCHEMA_DOC="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)`.",PLACES_SCHEMA_DOC='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", "FUEL_STATION", "HOTEL_OR_MOTEL", "CHARGING_LOCATION"\n localizedCategories: string[]; // English, whatever the request language\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.',buildPlacesSchemaDoc=e=>PLACES_SCHEMA_DOC,ROUTES_SCHEMA_DOC='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 liveTrafficIncidentsTravelTimeInSeconds?: number;\n trafficDelayInSeconds?: number; // delay of the traffic events along trafficLengthInMeters, NOT the gap to free-flow\n trafficLengthInMeters?: number;\n // gap to free-flow: Math.max(0, liveTrafficIncidentsTravelTimeInSeconds - noTrafficTravelTimeInSeconds)\n departureTime: Date; arrivalTime: Date;\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\n distinct?: Array<{ id; startPointIndex; endPointIndex; lengthInMeters: number }>; // stretches no other route of the response drives: what sets an alternative apart; on every route when there are 2+, [] if none\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; // the tail of a traffic incident id, not that id: match id.endsWith(eventId)\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 curve?: Array<{ id; startPointIndex; endPointIndex; headingChangeInDegrees /* + = right */; radiusInMeters; lengthInMeters; curvinessScore; severity /* 0-1 */ }>; // junction turns too\n curvyStretch?: Array<{ id; startPointIndex; endPointIndex; curveCount; hairpinCount; lengthInMeters; minRadiusInMeters; curvinessScore }>;\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 }; 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`.',isLongitude=e=>Number.isFinite(e)&&e>=-180&&e<=180,isLatitude=e=>Number.isFinite(e)&&e>=-90&&e<=90,POSITION_RANGE_MESSAGE="Position must be [lng, lat] with lng in [-180, 180] and lat in [-90, 90].",positionSchema=z.array(z.number()).length(2).refine(([e,t])=>isLongitude(e)&&isLatitude(t),{message:POSITION_RANGE_MESSAGE}),geoJsonPositionSchema=z.array(z.number()).min(2).max(3).refine(([e,t])=>isLongitude(e)&&isLatitude(t),{message:POSITION_RANGE_MESSAGE}),geoJsonBBoxSchema=z.array(z.number()).length(4).refine(([e,t,r,o])=>isLongitude(e)&&isLatitude(t)&&isLongitude(r)&&isLatitude(o),{message:"BBox values out of range — lng must be in [-180, 180] and lat in [-90, 90]."}).refine(([,e,,t])=>e<=t,{message:"BBox south must be <= north."}).describe("[west, south, east, north]; west > east crosses the antimeridian. lng in [-180, 180], lat in [-90, 90]."),polygonInputSchema=z.object({type:z.literal("Polygon"),coordinates:z.array(z.array(geoJsonPositionSchema))}),multiPolygonInputSchema=z.object({type:z.literal("MultiPolygon"),coordinates:z.array(z.array(z.array(geoJsonPositionSchema)))}),geometryInputSchema=z.union([polygonInputSchema,multiPolygonInputSchema]),placesEntryIDsSchema=({verb:e,extra:t})=>z.array(z.string()).min(1).optional().describe(`IDs of existing places entries to ${e} (e.g. ["places-2"], ["places-1", "places-2"]). Use recallState to list IDs. Omit to ${e} the latest entry.`+(t?` ${t}`:"")),routesEntryIDsSchema=({verb:e,extra:t})=>z.array(z.string()).min(1).optional().describe(`IDs of existing routes entries to ${e} (e.g. ["routes-0"], ["routes-0", "routes-1"]). Use recallState to list IDs. Omit to ${e} the latest route.`+(t?` ${t}`:"")),placesEntryIdHintSchema=z.string().regex(/^[a-z0-9][a-z0-9-]{1,48}$/,"Use lowercase letters, digits, and hyphens (2–49 chars).").optional().describe('Compact lowercase kebab-case id derived from the user prompt (e.g. "vegan-cafes-amsterdam", "ev-stations-along-route"). Avoid generic names like "places" / "results". Auto-suffixed on collision — do not track uniqueness yourself. Default: `places-N`.'),showModeSchema=z.enum(["add","replace"]).optional().describe('WITHIN THE TARGETED ENTRY ONLY — controls how the new polygons combine with what is already rendered on this entry\'s GeometriesModule layer. Does NOT affect other entries (use `hidePreviousEntries` for that). "replace" (default) clears this entry\'s layer first; "add" stacks (deduped by feature id).'),geometryFillStyleSchema=z.enum(geometryFillStyles),showPlaceGeometriesSchema=z.object({show:z.boolean().optional().describe("Also render the polygons on the map. Default: false."),mode:showModeSchema,fillStyle:geometryFillStyleSchema.optional().describe('How the polygons are filled. 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).'),geometryDisplayConfigSchema=z.object({fillStyle:geometryFillStyleSchema.optional().describe('How the polygons are filled. Default: "outline".'),placeIds:z.array(z.string()).optional().describe("Subset of place ids within the targeted entries — render boundaries only for these. Ids outside the entries are ignored. Default: all places with a geometry source."),mode:showModeSchema}).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.'),markerTypeSchema=z.enum([...placesMarkerTypes,"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.'),hidePreviousEntriesSchema=e=>z.union([z.literal("all"),z.array(z.string())]).optional().describe(`Previously-shown ${e} entries to hide first: \`"all"\` replaces them (the usual "show X"), an id array hides only those. Omit to stack ("also show X", "and X"). Ignored in \`single\` entryMode, which hides the others anyway.`),showPlacesSchema=z.object({markerType:markerTypeSchema,zoomMode:z.enum(["auto","none"]).describe('Camera behaviour. "none" keeps the map still.'),hidePreviousEntries:hidePreviousEntriesSchema("places")}),shownSchema=z.object({markerType:z.boolean(),zoomMode:z.boolean()}),showByodSchema=z.object({zoomMode:z.enum(["auto","none"]).describe('Camera behaviour. "auto" fits the map to the shown BYOD entries; "none" keeps the map still.'),hidePreviousEntries:hidePreviousEntriesSchema("BYOD")}),shownByodSchema=z.object({zoomMode:z.boolean().describe("Whether the camera was moved to fit the shown entries.")}),withinSharedFields={mode:z.literal("within"),viewport:z.literal(!0).optional().describe("When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.")},queryAsSchema=z.enum(["poi","place"]).optional().describe('Drives query resolution. `"poi"` for venues / landmarks / businesses (e.g. "Eiffel Tower"). `"place"` for addresses, cities, neighborhoods, parks, postal codes (e.g. "De Jordaan, Amsterdam", "1600 Pennsylvania Ave"). Default: "place".'),locateWithinSingularKeys=["boundingBox","viewport","query","placeId"],withinWhereSchema=z.object({...withinSharedFields,boundingBox:geoJsonBBoxSchema.optional().describe("Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId."),query:z.string().optional().describe('Resolve a name/address/POI → its boundary polygon (or bbox) as search area (e.g. "Paris", "De Jordaan, Amsterdam"). Pair with `queryAs` to disambiguate. EXCLUSIVE with boundingBox / viewport / placeId.'),queryAs:queryAsSchema,placeId:z.string().optional().describe("ID of a session place — its boundary polygon (fetched on demand) is the search area. EXCLUSIVE with boundingBox / viewport / query.")}).refine(e=>1===locateWithinSingularKeys.filter(t=>void 0!==e[t]).length,{message:"`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId."}),nearbySingularKeys=["position","viewport","query"],nearbyWhereSchema=z.object({mode:z.literal("nearby"),position:positionSchema.optional().describe("Bias point [lng, lat]. EXCLUSIVE with viewport / query."),viewport:z.literal(!0).optional().describe("When true, bias toward the current map viewport centre. EXCLUSIVE with position / query."),query:z.string().optional().describe("Resolve a name/address/POI → its centroid as bias point. Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport."),queryAs:queryAsSchema,radiusMeters:z.number().positive().optional().describe("Hard radius cap (metres) around the bias point. Independent of bias source.")}).refine(e=>1===nearbySingularKeys.filter(t=>void 0!==e[t]).length,{message:"`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent)."}),globalWhereSchema=z.object({mode:z.literal("global").describe("No spatial constraint — resolve the name anywhere. The default for a uniquely-named place.")}),whereSchema=z.union([withinWhereSchema,nearbyWhereSchema,globalWhereSchema]),sharedWithinFields={...withinSharedFields,queries:z.array(z.object({query:z.string().describe('Name of a CONTAINING area to search within (e.g. "Paris", "De Jordaan, Amsterdam"). NEVER the user\'s search subject — that goes in the top-level `query`.'),queryAs:queryAsSchema})).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:z.array(z.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:z.array(geometryInputSchema).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:z.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:z.object({routeId:z.string().min(1).optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),widthMeters:z.number().positive().describe('Total corridor width (metres) — widthMeters/2 each side. Typical: 200–500 m ("near the road"), 2–5 km ("broad area along the route").')}).optional().describe("Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. EXCLUSIVE with viewport; composes with other multi-region fields.")},areaWhereSchema=z.object({viewport:z.boolean().optional(),boundingBox:geoJsonBBoxSchema.optional(),queries:sharedWithinFields.queries,placeIds:sharedWithinFields.placeIds,geometries:sharedWithinFields.geometries,route:sharedWithinFields.route}),ALL_ENTRY_DATA_KINDS=["places","routes","incidents","customGeometries","trafficAreaAnalytics","byod"],buildEntryKindScopeSchema=e=>z.object({kinds:z.array(z.enum(e)).min(1)}),ENTRY_KIND_META={places:{fieldName:"placesEntryIDs",fieldLabel:"`placesEntryIDs`",buildField:e=>placesEntryIDsSchema({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:PLACES_SCHEMA_DOC,recallTool:"recallState({ kind: 'places' })",entryLabel:"places"},routes:{fieldName:"routesEntryIDs",fieldLabel:"`routesEntryIDs`",buildField:e=>routesEntryIDsSchema({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:ROUTES_SCHEMA_DOC,recallTool:"recallState({ kind: 'routes' })",entryLabel:"routes"},incidents:{fieldName:"incidentsEntryIDs",fieldLabel:"`incidentsEntryIDs`",buildField:e=>z.array(z.string()).min(1).optional().describe(`IDs of existing incidents entries to ${e.verb} (e.g. ["incidents-0"]). Injected as \`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' })",entryLabel:"incidents"},customGeometries:{fieldName:"geometriesEntryIDs",fieldLabel:"`geometriesEntryIDs`",buildField:e=>geometriesEntryIDsSchema.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:GEOMETRIES_SCHEMA_DOC,recallTool:"recallState({ kind: 'geometries' })",entryLabel:"custom-geometries"},trafficAreaAnalytics:{fieldName:"trafficAreaAnalyticsEntryIDs",fieldLabel:"`trafficAreaAnalyticsEntryIDs`",buildField:e=>z.array(z.string()).min(1).optional().describe(`IDs of existing traffic-area-analytics entries to ${e.verb} (e.g. ["tta-0"]). Injected as \`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; level: number; // name only when the request names the region\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 per period, only periods with data; every label is UTC\n daily?: Array<{ date: Date; …metrics }>; // midnight UTC: read with toISOString()\n hourly?: Array<{ date: Date; hour: number; …metrics }>; // hour 0-23\n average?: Array<{ day: 1|2|3|4|5|6|7; hour: number; …metrics }>; // average week, 1 = Monday\n yearly?, monthly?, weekly?: …;\n };\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`). Shift a UTC `day`/`hour` by the area\'s UTC offset to report local time (Amsterdam summer, UTC+2: `{ day: 1, hour: 5 }` is Monday 07:00).',recallTool:"recallState({ kind: 'trafficAreaAnalytics' })",entryLabel:"traffic-area-analytics"},byod:{fieldName:"byodEntryIDs",fieldLabel:"`byodEntryIDs`",buildField:e=>z.array(z.string()).min(1).optional().describe(`IDs of existing BYOD entries to ${e.verb} (e.g. ["byod-0"]) — customer-authored GeoJSON layers. Injected as \`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' })",entryLabel:"BYOD"}},resolveActiveKinds=(e,t)=>ALL_ENTRY_DATA_KINDS.filter(r=>e.includes(r)&&(!t||t.kinds.includes(r))),isKindActive=(e,t,r)=>e.includes(r)&&(!t||t.kinds.includes(r)),buildEntryFieldList=e=>e.map(e=>ENTRY_KIND_META[e].fieldLabel).join(" / "),buildEntryKindSandboxDocs=e=>ALL_ENTRY_DATA_KINDS.filter(t=>e.includes(t)).map(e=>ENTRY_KIND_META[e].sandboxDoc).join("\n"),buildEntryKindSchemaDocs=e=>ALL_ENTRY_DATA_KINDS.filter(t=>e.includes(t)).map(e=>ENTRY_KIND_META[e].schemaDoc).filter(e=>e.length>0).join("\n\n"),buildEntryIdFields=(e,t)=>{const r={};for(const o of ALL_ENTRY_DATA_KINDS){if(!e.includes(o))continue;const a=ENTRY_KIND_META[o];r[a.fieldName]=a.buildField(t)}return r},hasAnyEntryIds=e=>{for(const t of ALL_ENTRY_DATA_KINDS)if(e[ENTRY_KIND_META[t].fieldName]?.length)return!0;return!1},buildAtLeastOneEntryIdMessage=e=>`At least one of ${buildEntryFieldList(e)} must be set. Call ${Array.from(new Set(e.map(e=>ENTRY_KIND_META[e].recallTool))).map(e=>`\`${e}\``).join(" / ")} first to list available ids.`,fitOnMapInputSchema=z.union([geoJsonBBoxSchema,z.object({bbox:geoJsonBBoxSchema,padding:z.number().optional(),animate:z.boolean().optional()})]).describe("Bounds to fit the camera to: a `[W, S, E, N]` BBox tuple, or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`. lng in [-180, 180], lat in [-90, 90]. Default padding: 50px."),fittedReportSchema=z.object({bbox:z.tuple([z.number(),z.number(),z.number(),z.number()]).describe("Bounds the camera was fit to."),padding:z.number().describe("Padding (px) applied around the bbox.")}),FIT_ON_MAP_DOC="`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)`.",isFitOnMapInput=e=>fitOnMapInputSchema.safeParse(e).success,applyFitOnMap=(e,t)=>{const{bbox:r,padding:o=50,animate:a}=Array.isArray(t)?{bbox:t}:t;return fitMapToBBox(e.baseMap.mapLibreMap,r,{padding:o,...void 0!==a&&{animate:a}}),{bbox:r,padding:o}},AREA_GEOGRAPHY_TYPES=["CountrySubdivision","CountrySecondarySubdivision","Municipality","MunicipalitySubdivision","Neighbourhood"],geocodeAreas=async(e,t={},r)=>{const o=withAgentToolkitHeaders({query:e,limit:t.limit??6,filters:{geographyTypes:AREA_GEOGRAPHY_TYPES},...t.bias?{geoBias:{position:t.bias}}:{},signal:r?.signal});return(await geocode(o)).features},entryIdsMatching=(e,t=[])=>{const r=t.map(e=>e.toLowerCase());return e.filter(e=>r.some(t=>e.label.toLowerCase().includes(t))).map(e=>e.id)},entryIdsToHide=(e,t,r)=>{if(void 0===r)return[];const o=new Set(t);return("all"===r?[...e]:r).filter(e=>!o.has(e))},hidePreviousShownEntries=async(e,t,r)=>{if("multiple"===e.entryMode)for(const o of entryIdsToHide(e.shownEntryIds,t,r))await e.hideEntry(o)},locatePlaces=async(e,t,r,o)=>{const a="poi"===t?discoverPlaces:geocode,{limit:s,bias:n}=r;return(await a(withAgentToolkitHeaders({query:e,limit:s,...n&&{geoBias:n},signal:o?.signal}))).features},locatePlace=async(e,t,r,o)=>{const[a]=await locatePlaces(e,t,{limit:1,bias:r},o);return a??null},locationInputSchema=z.union([z.object({query:z.string().describe("Location string to resolve."),queryAs:z.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.')}),z.object({position:positionSchema.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].")}),z.object({placeIdOrEntryId:z.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).")})]),labelForLocationInput=e=>"query"in e?`"${e.query}"`:"placeIdOrEntryId"in e?`placeIdOrEntryId "${e.placeIdOrEntryId}"`:JSON.stringify(e.position),geometriesIdKey=e=>`${e.kind}:${e.id}`,missingEntry=(e,t,r=ENTRY_KIND_META[e].entryLabel)=>`No ${r} entry with id "${t}". Call ${ENTRY_KIND_META[e].recallTool} to list available IDs.`,routeUtils={calculateProgressAtRoutePoint:calculateProgressAtRoutePoint,compareRoutes:compareRoutes,getCoordinateAtRouteProgress:getCoordinateAtRouteProgress,getImportantRoads:getImportantRoads,getProgressAtNearestRoutePoint:getProgressAtNearestRoutePoint,getRouteProgressBetween:getRouteProgressBetween,getRouteProgressForSection:getRouteProgressForSection,getSectionBBox:getSectionBBox,getSectionsWithin:getSectionsWithin,isHairpin:isHairpin},fetchPlaceBoundary=async(e,t,r)=>{if(!t.properties.dataSources?.geometry?.id)return{skip:"Place has no geometry data source (e.g. addresses and streets often lack one)."};try{const o=await e.places.fetchPlaceGeometry(t.id,r);return o?{feature:o}:{skip:"Geometry Data service returned no feature for this place."}}catch(o){return{skip:`Geometry fetch failed: ${errorMessageOf(o)}`}}},skippedSourceSchema=z.object({source:geometriesIdSchema,reason:z.string()}),formatSkipReason=e=>`${geometriesIdKey(e.source)}: ${e.reason}`,collectGeometryBearingPlaceIds=(e,t,r)=>{let o=0;for(const a of e)a.properties.dataSources?.geometry?.id&&"string"==typeof a.id&&(o++,r.placeIdToEntry.has(a.id)||r.placeIdToEntry.set(a.id,t),r.seen.has(a.id)||(r.seen.add(a.id),r.placeIds.push(a.id)));return o},tagFeatureSource=(e,t)=>({...e,properties:{...e.properties??{},_source:t}}),resolvePlaceGeometry=async(e,t,r,o)=>{const a=t.places.findPlaceById(e);if(!a)return{kind:"skip",reason:"No place with this id in session state. Use `recallState` to list known places ids and entries."};if(r&&a.place.properties.dataSources?.geometry?.id&&!t.places.getGeometryForPlace(e))return{kind:"skip",reason:r};const s=await fetchPlaceBoundary(t,a.place,o);return"skip"in s?{kind:"skip",reason:s.skip}:{kind:"success",place:a.place,feature:s.feature,ownerEntryId:a.entryId}},batchSkipReasonOf=(e,t,r)=>{if(!e&&t)return r.get(t)??"Geometry Data service returned no feature for this place."},featureSourceOf=(e,t,r)=>e||(t?{kind:"places",id:t}:{kind:"place",id:r}),collectInputGeometries=async(e,t,r)=>{const o=(e=>{const t=/* @__PURE__ */new Set;return e.filter(e=>{const r=geometriesIdKey(e);return!t.has(r)&&(t.add(r),!0)})})(e);if(0===o.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 a=(e=>{const t={place:[],places:[],ranges:[],customGeometries:[]};for(const r of e)t[r.kind].push(r);return t})(o),s=((e,t)=>{const r=[],o=/* @__PURE__ */new Map,a=[],s=/* @__PURE__ */new Set;for(const n of e){const e=t.places.entries.find(e=>e.id===n.id);e?0===collectGeometryBearingPlaceIds(e.data,n.id,{placeIds:r,placeIdToEntry:o,seen:s})&&a.push({source:n,reason:"Places entry has no places with a geometry data source (e.g. addresses and streets often lack one)."}):a.push({source:n,reason:"No places entry with this id in session state. Use `recallState` to list available ids."})}return{placeIds:r,placeIdToEntry:o,skipped:a}})(a.places,t),n=[...s.skipped],i=/* @__PURE__ */new Map;await Promise.all(a.places.map(e=>t.places.fetchGeometriesForEntry(e.id,r).catch(t=>{i.set(e.id,`Geometry fetch failed: ${errorMessageOf(t)}`)})));const{mergedPlaceIds:l,placeIdSourceQualified:c}=((e,t)=>{const r=/* @__PURE__ */new Set,o=[],a=/* @__PURE__ */new Map;for(const s of e)r.has(s.id)||(r.add(s.id),o.push(s.id),a.set(s.id,s));for(const s of t)r.has(s)||(r.add(s),o.push(s));return{mergedPlaceIds:o,placeIdSourceQualified:a}})(a.place,s.placeIds),d=[],u=[],h=/* @__PURE__ */new Set,p=/* @__PURE__ */new Set,y=/* @__PURE__ */new Set,m=/* @__PURE__ */new Set;for(const w of l){const e=c.get(w),o=s.placeIdToEntry.get(w),a=batchSkipReasonOf(e,o,i),l=await resolvePlaceGeometry(w,t,a,r);if("skip"===l.kind){const t=e??{kind:"place",id:w};n.push({source:t,reason:l.reason});continue}const f=featureSourceOf(e,o,w),g=`${geometriesIdKey(f)}:${String(l.feature.id)}`;void 0!==l.feature.id&&m.has(g)||(m.add(g),d.push(tagFeatureSource(l.feature,f))),u.push(l.place),h.add(o??l.ownerEntryId),e&&p.add(e.id),o&&y.add(o)}const f=((e,t)=>{const r=[],o=/* @__PURE__ */new Set,a=[];for(const s of e){const e=t.ranges.entries.find(e=>e.id===s.id);if(!e){a.push({source:s,reason:"No ranges entry with this id in session state. Use `recallState` to list available ids."});continue}const n=e.data.flatMap(e=>e.polygon?.features??[]);if(0!==n.length){for(const e of n)r.push(tagFeatureSource(e,s));o.add(s.id)}else a.push({source:s,reason:"Ranges entry has no polygons — recompute via findReachableAreas first."})}return{features:r,contributingIds:o,skipped:a}})(a.ranges,t);d.push(...f.features),n.push(...f.skipped);const g=((e,t)=>{const r=[],o=/* @__PURE__ */new Set,a=[];for(const s of e){const e=t.customGeometries.findById(s.id);if(e)if(0!==e.data.length){for(const t of e.data)r.push(tagFeatureSource(t,s));o.add(s.id)}else a.push({source:s,reason:"Custom-geometries entry is empty."});else a.push({source:s,reason:"No custom-geometries entry with this id in session state. Use `recallState` to list available ids."})}return{features:r,contributingIds:o,skipped:a}})(a.customGeometries,t);d.push(...g.features),n.push(...g.skipped);const b=g.contributingIds;return 0===d.length?{error:`No input geometries available. ${n.map(formatSkipReason).join(" | ")}`}:{value:{geometries:d,sourcePlaces:u,affectedEntries:[...[...h].map(e=>({id:e,kind:"places"})),...[...b].map(e=>({id:e,kind:"customGeometries"}))],skipped:n,contributingSourceIds:o.filter(e=>{switch(e.kind){case"place":return p.has(e.id);case"places":return y.has(e.id);case"ranges":return f.contributingIds.has(e.id);case"customGeometries":return b.has(e.id)}})}}},findPlacesByEntry=(e,t)=>{if(!e?.length)return{value:void 0};const r={};for(const o of e){const e=t.places.entries.find(e=>e.id===o);if(!e)return{error:`No places entry found with id "${o}". Use recallState to list available IDs.`};r[o]={type:"FeatureCollection",features:e.data}}return{value:r}},latestTimestamp=e=>{let t;for(const r of Object.values(e))for(const{timestamp:e}of r)(void 0===t||e>t)&&(t=e);return t},prepareMultiInputs=async(e,t,r)=>{if(!hasAnyEntryIds(e))return{error:buildAtLeastOneEntryIdMessage(ALL_ENTRY_DATA_KINDS)};const o=((e,t)=>{const r=resolveSliceEntries(e.placesEntryIDs,t.places,{missingId:e=>missingEntry("places",e)});if("error"in r)return r;const o=resolveSliceEntries(e.routesEntryIDs,t.routing,{missingId:e=>missingEntry("routes",e)});if("error"in o)return o;const a=resolveSliceEntries(e.incidentsEntryIDs,t.trafficIncidents,{missingId:e=>missingEntry("incidents",e)});if("error"in a)return a;const s=resolveSliceEntries(e.trafficAreaAnalyticsEntryIDs,t.trafficAreaAnalytics,{missingId:e=>missingEntry("trafficAreaAnalytics",e)});if("error"in s)return s;const n=resolveSliceEntries(e.byodEntryIDs,t.byod,{missingId:e=>missingEntry("byod",e)});return"error"in n?n:{value:{places:r.value,routes:o.value,incidents:a.value,trafficAreaAnalytics:s.value,byod:n.value}}})(e,t);if("error"in o)return o;let a,s={affectedEntries:[],sourceIds:[],skipped:[]};if(e.geometriesEntryIDs?.length){const o=await collectInputGeometries(e.geometriesEntryIDs,t,r);if("error"in o)return o;a=o.value.geometries,s={affectedEntries:o.value.affectedEntries,sourceIds:o.value.contributingSourceIds,skipped:o.value.skipped}}return{value:{resolved:o.value,sandbox:buildSandboxInputs(o.value,a),geometries:s}}},buildSandboxInputs=(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:partitionGeometriesBySource(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}),partitionGeometriesBySource=e=>{if(!e?.length)return;const t={};for(const r of e){const e=r.properties?._source,o=e?geometriesIdKey(e):"unknown",a=t[o]??[];a.push(r),t[o]=a}return t},resolveSliceEntries=(e,t,r)=>{if(!e?.length)return{value:[]};const o=[];for(const a of e){const e=t.entries.find(e=>e.id===a);if(!e)return{error:r.missingId(a)};o.includes(e)||o.push(e)}return{value:o}},BY_ENTRY_VIEWS_DOC='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 }`).',MULTI_INPUT_SANDBOX_PARAMS=["placesByEntry","routesByEntry","incidentsByEntry","geometriesByEntry","trafficAreaAnalyticsByEntry","byodByEntry","h3","turf","routeUtils","cluster"],buildSandboxToolsDoc=(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/.':"",o=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.\n• `getSectionsWithin(route, { within?: spans, types? })` → `{ [type]: { sections, shares, lengthInMeters } }` — sections overlapping the spans (e.g. one `country` section); `shares[i]` = the part of `sections[i]` inside, to prorate `delayInSeconds`.\n• `getImportantRoads(route, { within?, minLengthInMeters? })` → `[{ name, roadNumbers, lengthInMeters, sections }]` in driving order.\n• `compareRoutes(route, reference, { types? })` → `{ distinct, referenceDistinct, adds, avoids }` (`adds` / `avoids` shaped like getSectionsWithin).\n• `isHairpin(curve)` — 140°+ on a radius of 40 m or less.":"",a=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`${buildSandboxCodePrompt(MULTI_INPUT_SANDBOX_PARAMS)}\n\n${BY_ENTRY_VIEWS_DOC}${r}${o}${a}\n\n`},packSandboxArgs=(e,t)=>[e.placesByEntry,e.routesByEntry,e.incidentsByEntry,e.geometriesByEntry,e.trafficAreaAnalyticsByEntry,e.byodByEntry,t.h3,t.turf,routeUtils,clusterIncidents],getRangePolygons=(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 o=r.data.flatMap(e=>{const t=/* @__PURE__ */new Set;return(e.polygon?.features??[]).flatMap((r,o)=>{const a=e.budgets?.[o]?.type;return void 0!==a&&t.has(a)?[]:(void 0!==a&&t.add(a),[r.geometry])})}).filter(e=>!!e);return 0===o.length?{error:`Range "${t}" has no polygons. Recompute via findReachableAreas.`}:{polygons:o}},scopableToolNames=e=>Object.entries(e).filter(([,e])=>e.scopePrompt).map(([e])=>e),looseScopeShape=z.object({kinds:z.array(z.string()).min(1).describe("The kinds the turn touches, from those the tool's SCOPE hint lists.")}),addUsage=(e,t)=>{t&&(e.inputTokens+=t.inputTokens??0,e.outputTokens+=t.outputTokens??0,e.totalTokens+=t.totalTokens??0)},extractPartialTools=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}}},compactSuccess=(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}},classifyUserIntent=async(e,t)=>{const{chatModel:r,toolsMetadata:o}=t,a=performance.now(),{tools:s,toolScopes:n,usage:i}=await(async(e,t,r)=>{const o=new Set(scopableToolNames(r)),a=(e=>{const t=z.object({tools:z.array(z.string()).min(1).describe("Tool names needed to fulfill the query.")});if(0===e.size)return t;const r={};for(const a of e)r[a]=looseScopeShape.nullable();const o=z.object(r).nullable().describe("Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in `tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.");return t.extend({toolScopes:o}).superRefine((t,r)=>{const o=t.toolScopes??{};for(const a of t.tools)e.has(a)&&null==o[a]&&r.addIssue({code:"custom",path:["toolScopes",a],message:`Tool "${a}" requires \`toolScopes.${a}\` — emit it per the SCOPE hint (don't leave null when picking).`})})})(o),s={inputTokens:0,outputTokens:0,totalTokens:0},n=(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),i=`${(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=scopableToolNames(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")})(r)}\n\nReturn JSON matching this shape exactly:\n${n}`;let l;for(let d=0;d<2;d++)try{const r=await generateText({model:t,output:Output.json(),instructions:i,messages:e,maxOutputTokens:400});addUsage(s,r.usage);const o=a.safeParse(r.output);if(o.success)return compactSuccess(o.data,s);l=extractPartialTools(r.output)??l}catch(c){if(!NoObjectGeneratedError.isInstance(c)){if(l)break;throw c}addUsage(s,c.usage);const e=await parsePartialJson(c.text);l=extractPartialTools(e.value)??l}if(!l)throw new Error("Classifier failed after 2 attempts to produce a valid response.");return{tools:l.tools,...l.scopes&&{toolScopes:l.scopes},usage:s}})(e,r,o),l=s.filter(e=>Object.hasOwn(o,e));if(0===l.length)throw new Error(`Classifier picked no known tool: [${s.join(", ")}].`);return{activeToolNames:l,...n&&{toolScopes:n},timeMs:performance.now()-a,usage:i}},isTextPart=e=>"text"===e.type,extractMessageText=e=>{if("string"==typeof e)return e;const t=e.filter(isTextPart);return t.length>0?t.map(e=>e.text).join(""):null},findLastUserIndex=e=>e.findLastIndex(e=>"user"===e.role),extractLastUserText=e=>{const t=findLastUserIndex(e);return-1===t?null:extractMessageText(e[t].content)},compactHistory=(e,t,r)=>{const o=[];for(let a=e.length-1;a>=0&&o.length<t;a--){const t=e[a];if("user"!==t.role&&"assistant"!==t.role)continue;const s=extractMessageText(t.content);s&&o.push({role:t.role,content:s.length>r?s.slice(0,r)+"…":s})}return o.reverse()},createDefaultClassifier=e=>{const{model:t,maxHistoryMessages:r,maxHistoryMessageLength:o}=e;return async e=>{const{messages:a,toolsMetadata:s}=e;if(!a?.length)return null;const n=r??8,i=o??300,l=findLastUserIndex(a),c=-1===l?null:extractMessageText(a[l].content);if(!c)return null;const d=[...compactHistory(a.slice(0,l),n,i),{role:"user",content:c}];try{return await classifyUserIntent(d,{chatModel:t,toolsMetadata:s})}catch{return null}}},waypointAddress=e=>{if(e&&!Array.isArray(e))return e.properties?.address?.freeformAddress},makePlacesLabel=(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 o=`${e} ${1===e?"place":"places"}`;return r.length?`${r.join(" ")} (${o})`:o})(e.features.length,t);const r=e.properties?.poi?.name,o=e.properties?.address?.freeformAddress;return r&&o?`${r}, ${o}`:r||o||(t?.query?t.query:"Place")},placeAreaLabel=e=>{const t=e.properties?.address?.freeformAddress;if(!t)return;const r=e.properties?.address?.countryCode;if(!r)return t;const o=`, ${r}`;return t.toLowerCase().endsWith(o.toLowerCase())?t:`${t}${o}`},makeRangesLabel=(e,t)=>`${[...e].sort((e,t)=>e.value-t.value).map(e=>`${e.value} ${budgetUnits[e.type]}`).join("/")} from ${1===t.length?t[0]:2===t.length?t.join(", "):`${t.length} origins`}`,countedRouteSectionTypes=["tollRoad","toll","ferry","carTrain","traffic","tunnel","unpaved","lowEmissionZone","vehicleRestricted"],summarizePlace=e=>{const[t=0,r=0]=e.geometry?.coordinates??[];return{id:e.id,type:e.properties?.type,name:e.properties?.poi?.name,address:e.properties?.address?.freeformAddress,position:[t,r]}},summarizePlaces=e=>{const t=Array.isArray(e)?e:e.features;return{count:t.length,features:t.slice(0,5).map(summarizePlace)}},countSections=e=>{const t=Object.fromEntries(countedRouteSectionTypes.flatMap(t=>{const r=e(t);return r>0?[[t,r]]:[]}));return Object.keys(t).length>0?t:void 0},summarizeRoutes=e=>{const[t,...r]=e.features,o=r.length>0?t:void 0;return{count:e.features.length,routes:e.features.map(e=>((e,t)=>{const{summary:r,sections:o,index:a}=e.properties,s=o.leg,n=e=>({...e,arrivalTime:e.arrivalTime.toISOString(),departureTime:e.departureTime.toISOString()}),i=countSections(e=>o[e]?.length??0),l=[...new Set(o.country?.map(e=>e.countryCodeISO3))],c=((e,t)=>{const r=getImportantRoads(e,{within:t,minLengthInMeters:2e3}).filter(e=>e.name),o=new Set([...r].sort((e,t)=>t.lengthInMeters-e.lengthInMeters).slice(0,3));return r.filter(e=>o.has(e)).map(e=>e.name)})(e,t&&o.distinct),d=(e=>{const{sections:t,summary:r}=e.properties,o=(t.curve??[]).reduce((t,r)=>t+(getRouteProgressForSection(e,r)?.delta.travelTimeInSeconds??0),0);return r.travelTimeInSeconds>0?Math.round(100*o/r.travelTimeInSeconds):0})(e),u=t&&t!==e?((e,t)=>{const{adds:r,avoids:o}=compareRoutes(e,t,{types:countedRouteSectionTypes}),a=countSections(e=>r[e]?.sections.length??0),s=countSections(e=>o[e]?.sections.length??0);if(a||s)return{...a&&{adds:a},...s&&{avoids:s}}})(e,t):void 0;return{index:a,summary:n(r),...s.length>1&&{legSummaries:s.map(e=>n(e.summary))},...i&&{sectionCounts:i},...l.length>0&&{countries:l},...c.length>0&&{via:c},...d>0&&{timeInCurvesPercent:d},...u&&{comparedToBest:u}}})(e,o))}},waypointSummarySchema=z.object({slotIndex:z.number(),isFilled:z.boolean(),address:z.string().optional(),position:z.array(z.number()).length(2).optional().describe("[longitude, latitude]")}),summarizeWaypoint=e=>{if(null==e)return null;const t=getPosition(e),r=waypointAddress(e);return{...r&&{address:r},...t&&{position:[t[0],t[1]]}}},isoDate=e=>!e||Number.isNaN(e.getTime())?"":e.toISOString().slice(0,10);function resolveWhere(e,t,r){switch(e.kind){case"within":return resolveWithin(t,r);case"nearby":return resolveNearby(t,r);case"global":return resolveGlobal()}}var metadata,resolveWithin=(e,t)=>gatherCandidates(gatherInputs(e,t),t),resolveNearby=async(e,t)=>{const r=gatherInputs(e,t),o=r.mapViewport.center;if(!r.queries?.length)return r.useViewport&&o?{position:o}:{};for(const{query:a,queryAs:s}of r.queries){const[e]=await t.geocodeAreas(a,s??"place",o);if(!e)continue;const r=getPosition(e);if(!r)return{};const n=placeAreaLabel(e);return{position:r,query:a,...n&&{label:n}}}return{}},resolveGlobal=()=>({}),isResolveError=e=>"object"==typeof e&&null!==e&&"error"in e,gatherInputs=(e,t)=>{const{viewport:r,boundingBox:o,queries:a,placeIds:s,geometries:n,route:i}=e;return{useViewport:r,boundingBox:o,queries:a,placeIds:s,geometries:n,route:i,mapViewport:t.viewport()}},gatherCandidates=async(e,t)=>{const{useViewport:r,boundingBox:o,queries:a,placeIds:s,geometries:n,route:i,mapViewport:l}=e,c=[];o&&c.push({bbox:o,source:"boundingBox"});const d=await resolveQueryCandidates(a,t,l);if(isResolveError(d))return d;c.push(...d);const u=await resolvePlaceIdCandidates(s,t);if(isResolveError(u))return u;if(c.push(...u,...resolveGeometryCandidates(n)),i){const e=await resolveRouteCandidates(i,t);if(isResolveError(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},resolveQueryCandidates=async(e,t,r)=>{const o=await Promise.all((e??[]).map(({query:e})=>(async(e,t,r)=>{const o=(await t.geocodeAreas(e,void 0,r.center)).filter(e=>e.bbox);if(0===o.length)return{error:`Could not resolve "${e}" to an area. Provide a boundingBox or a more specific place.`};const a=(r.center?rerankWithNearestCandidate(o,r.center):o)[0],s=placeAreaLabel(a),n=a.properties.dataSources?.geometry?.id?await t.fetchGeometry(a):void 0;return n?{bbox:n.bbox??bboxFromGeoJSON(n),polygon:n.geometry,label:s,source:"query",query:e}:{bbox:a.bbox,label:s,source:"query",query:e}})(e,t,r)));return o.find(isResolveError)??o},resolvePlaceIdCandidates=async(e,t)=>{const r=[];for(const o of e??[]){const e=t.expandEntry(o);if(0===e?.length)return{error:`Places entry "${o}" has no place with a geometry data source. Provide a boundingBox or a different entry.`};for(const a of e??[o]){const e=await t.fetchGeometry(a);if(!e)return{error:`Unknown placeId "${a}" or it has no fetchable geometry. Use recallState to list available IDs.`};r.push({bbox:bboxFromGeoJSON(e),polygon:e.geometry,source:"placeId"})}}return r},resolveGeometryCandidates=e=>{const t=[];for(const r of e??[]){const e=bboxFromGeoJSON(r);e&&t.push({bbox:e,polygon:r,source:"geometries"})}return t},resolveRouteCandidates=async(e,t)=>{const r=t.getRoute(e.routeId);if("error"in r)return r;const o=[];for(const a of r.features){const t=turf.buffer(a,e.widthMeters/2,{units:"meters"});t&&o.push({bbox:bboxFromGeoJSON(t),polygon:t.geometry,label:r.label,source:"route"})}return 0===o.length?{error:"Route produced no corridor geometry."}:o},rerankWithNearestCandidate=(e,t)=>{const r=e.map(e=>e.bbox&&turf.distance(bboxCenter(e.bbox),t));return e.map((e,t)=>{const o=r[t],a=void 0===o?0:r.filter(e=>void 0!==e&&o-e>1e3).length;return{place:e,rank:(t+a)/2,distanceRank:a}}).toSorted((e,t)=>e.rank-t.rank||e.distanceRank-t.distanceRank).map(e=>e.place)},collectShownByodCollections=e=>{const t=[];for(const r of e.byod.shownEntryIds){const o=e.byod.findById(r);o&&t.push(o.data)}return t},showByodOnMap=async(e,t,r)=>(await hidePreviousShownEntries(e.byod,[t],r.hidePreviousEntries),await e.byod.showEntry(t),{zoomMode:"auto"===r.zoomMode&&fitMapToGeoJSON(e.baseMap.mapLibreMap,collectShownByodCollections(e))}),RECOVERY_BY_CODE={INVALID_REQUEST:"The service rejected the arguments — change them; the same call fails the same way.",INVALID_CREDENTIALS:"The TomTom API key is missing, invalid or expired — do not retry; tell the user.",INSUFFICIENT_PERMISSIONS:"The TomTom API key is not allowed this request — do not retry; tell the user.",NOT_FOUND:"The service found nothing for this input — do not retry the same call.",RATE_LIMITED:"The TomTom service is rate-limiting requests — retry once at most, then tell the user.",LIVE_SERVICE_NOT_AVAILABLE:"The TomTom service failed — retry once at most, then tell the user.",NETWORK_NOT_AVAILABLE:"No response arrived from the TomTom service — retry once at most, then tell the user.",ABORTED:"The call was cancelled — do not retry unless the user asks again."},toToolError=(e,t)=>{const r=errorMessageOf(t),o=t instanceof SDKError||t instanceof TomTomMapError?t.code:void 0,a=o&&RECOVERY_BY_CODE[o];return{error:`${e}: ${r}${a?` ${a}`:""}`,...o&&{code:o}}},showEntryGeometries=async(e,t,r,o)=>{let a,s;try{a=await e.places.fetchGeometriesForEntry(t,o)}catch(i){if(i instanceof SDKAbortError)throw i;a=e.places.entries.find(e=>e.id===t)?.geometries??[],s=toToolError("Boundary fetch failed",i).error}const n=!0===r.show&&a.length>0;return n&&await e.places.showPlaceGeometries(a,r.fillStyle??"outline",r.mode??"replace",t),{fetched:a.length,shown:n,...s&&{error:s}}},showResultsOnMap=async(e,t,r)=>{await(async(e,t,r)=>{const o="multiple"===e.places.entryMode;if("none"===r.markerType){const a=o?entryIdsToHide(e.places.shownEntryIds,t,r.hidePreviousEntries):[];return void(a.length>0&&await e.places.removeShownEntries(a))}if(o&&"all"!==r.hidePreviousEntries){const o=entryIdsToHide(e.places.shownEntryIds,t,r.hidePreviousEntries);return o.length>0&&await e.places.removeShownEntries(o),void(await e.places.addShownEntries(t,r.markerType))}const a=[...e.places.shownEntryIds].filter(e=>!t.includes(e));a.length>0&&await e.places.removeShownEntries(a),await e.places.setShownEntries(t,r.markerType)})(e,t,r);const o={markerType:"none"!==r.markerType,zoomMode:!1};if("auto"===r.zoomMode){const a="none"===r.markerType?e.places.entries.filter(e=>t.includes(e.id)).flatMap(e=>e.data):e.places.getShownFeatures();o.zoomMode=fitMapToGeoJSON(e.baseMap.mapLibreMap,a)}return o},MIN_VIEWPORT_BIAS_ZOOM=6,mod360=e=>(e%360+360)%360,getViewportBias=(e,t=6)=>{if(e.mapLibreMap.getZoom()<t)return;const{lng:r,lat:o}=e.mapLibreMap.getCenter();return[r>=-180&&r<=180?r:mod360(r+180)-180,o]},wrapWest=e=>e>=-180&&e<180?e:mod360(e+180)-180,wrapEast=e=>e>-180&&e<=180?e:180-mod360(180-e),getViewportBoundingBox=e=>{const t=e.mapLibreMap.getBounds(),r=t.getWest(),o=t.getEast();return o-r>=360?[-180,t.getSouth(),180,t.getNorth()]:[wrapWest(r),t.getSouth(),wrapEast(o),t.getNorth()]},toolStateToWhereContext=(e,t)=>({viewport:()=>{let t,r;try{t=getViewportBoundingBox(e.baseMap)}catch{t=void 0}try{r=getViewportBias(e.baseMap)}catch{r=void 0}return{bbox:t,center:r}},expandEntry:t=>e.places.geometryPlaceIdsForEntry(t),geocodeAreas:(e,r,o)=>r?locatePlaces(e,r,{limit:5,bias:o?{position:o}:void 0},t):geocodeAreas(e,{bias:o},t),fetchGeometry:async r=>{if("string"==typeof r)return e.places.fetchPlaceGeometry(r,t);if(!r.properties.dataSources?.geometry?.id)return;const o=withAgentToolkitHeaders({geometries:[r],signal:t?.signal});return(await geometryData(o)).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. setRoute), then retry."};const o=t?r.find(e=>e.id===t):r.at(-1);if(!o)return{error:`Route "${t}" not found. Use recallState to list available route IDs.`};const a=o.data.features;return 0===a.length?{error:`Route "${o.id}" has no geometry. Recompute it via setRoute.`}:{features:a,label:o.label}}}),toolErrorSchema=z.object({error:z.string(),code:z.enum(sdkErrorCodes).optional().describe("The SDK error code, when a TomTom service or the map failed.")}),analysisIndexSchema=z.object({name:z.string(),outputFormat:z.enum(["json","chart"]),timestamp:z.number(),description:z.string().optional()}),byodDataProfileSchema=z.object({featureCount:z.number(),geometryTypes:z.array(z.string()).describe("Geometry types present (Point / LineString / Polygon / …)."),properties:z.array(z.object({name:z.string(),types:z.array(z.string()).describe('Distinct JSON value types seen for this key (e.g. ["string"], ["number","null"]).'),coverage:z.number().describe("Fraction of features carrying this key (1 = every feature)."),examples:z.array(z.union([z.string(),z.number(),z.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:z.number().optional().describe("Count of property keys omitted when the data has more distinct keys than the cap.")}),resolvedAreasOutputSchema=z.array(z.object({query:z.string().optional().describe("Your input query text, echoed."),matched:z.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).'),basePlaceShape={id:z.string().describe("Stable place feature ID; pass as `{ placeIdOrEntryId }` to setRoute / addWaypointsToRoute / etc."),type:z.enum(placeTypes).optional().describe("Place classification (POI, Street, Geography, Cross Street, …), when known."),name:z.string().optional().describe("POI name if available"),address:z.string().optional().describe("Human-readable address"),position:z.array(z.number()).length(2).describe("[longitude, latitude]")},buildPlaceOutputSchema=e=>z.object(basePlaceShape).describe("Compact place summary"),buildPlacesOutputSchema=e=>z.object({count:z.number(),features:z.array(buildPlaceOutputSchema())}).describe("Compact places summary"),placeOutputSchema=buildPlaceOutputSchema(),summarySchema$1=z.object({travelTimeInSeconds:z.number(),lengthInMeters:z.number(),trafficDelayInSeconds:z.number().optional(),trafficLengthInMeters:z.number().optional(),departureTime:z.string().optional(),arrivalTime:z.string().optional(),noTrafficTravelTimeInSeconds:z.number().optional(),historicTrafficTravelTimeInSeconds:z.number().optional(),liveTrafficIncidentsTravelTimeInSeconds:z.number().optional()}),sectionCountsSchema=z.partialRecord(z.enum(countedRouteSectionTypes),z.number()),summarizedRouteSchema=z.object({index:z.number().describe("0=best, 1+=alternative"),summary:summarySchema$1,legSummaries:z.array(summarySchema$1).optional().describe("Present when route has multiple legs"),sectionCounts:sectionCountsSchema.optional().describe("Sections per type, only the types present: tollRoad (costs money by any scheme), toll (per-use toll), ferry, carTrain, traffic (incidents on the route), tunnel, unpaved, lowEmissionZone, vehicleRestricted. Absent = none of them."),countries:z.array(z.string()).optional().describe("ISO 3166-1 alpha-3 codes of the countries crossed, in order"),via:z.array(z.string()).optional().describe("Main roads taken, in order; with alternatives, only those on the stretches this route alone drives"),timeInCurvesPercent:z.number().optional().describe("% of the travel time in curves, junction turns included: how winding. Absent = under 0.5%"),comparedToBest:z.object({adds:sectionCountsSchema.optional(),avoids:sectionCountsSchema.optional()}).optional().describe("Alternatives only: the sections this route takes on (`adds`) and stays clear of (`avoids`) against route 0. Absent = same")}),routesOutputSchema=z.object({count:z.number(),routes:z.array(summarizedRouteSchema)}),routesWriteOutputSchema=routesOutputSchema.extend({entryId:z.string().describe("Routing entry id the route was written to — pass as `routesEntryIDs` to analyseData / processData.")}),sourceDiffSchema=/* @__PURE__ */(()=>z.object({added:z.array(z.string()).describe("Source IDs added by the executed code."),removed:z.array(z.string()).describe("Source IDs removed by the executed code."),updated:z.array(z.string()).describe("Source IDs whose definition changed.")}))(),layerDiffSchema=/* @__PURE__ */(()=>z.object({added:z.array(z.string()).describe("Layer IDs added by the executed code."),removed:z.array(z.string()).describe("Layer IDs removed by the executed code."),updated:z.array(z.string()).describe("Layer IDs whose properties changed.")}))(),executeMaplibreCodeSchema=/* @__PURE__ */(()=>z.object({code:z.string().describe('Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. Example: await a map event with `await new Promise(r => map.once("idle", r))`. Return a value with `return` to include it in the result.')}))(),executeMaplibreCodeOutputSchema=/* @__PURE__ */(()=>z.union([z.object({success:z.literal(!0),result:z.unknown().optional().describe("Return value of the executed code, if any."),resultError:z.string().optional().describe("Why `result` was dropped. The code ran, and its map changes are in `sources` / `layers`."),sources:sourceDiffSchema,layers:layerDiffSchema}),toolErrorSchema]))(),snapshotStyle=e=>{const t=e.getStyle(),r=/* @__PURE__ */new Map;for(const[a,s]of Object.entries(t?.sources??{}))r.set(a,JSON.stringify(s));const o=/* @__PURE__ */new Map;for(const a of t?.layers??[])o.set(a.id,JSON.stringify(a));return{sources:r,layers:o}},diffSnapshots=(e,t)=>{const r=[],o=[],a=[];for(const[s,n]of t){const t=e.get(s);void 0===t?r.push(s):t!==n&&a.push(s)}for(const s of e.keys())t.has(s)||o.push(s);return{added:r,removed:o,updated:a}},executeExecuteMaplibreCode=async(e,t)=>{const{code:r}=e;try{const e=t.baseMap.mapLibreMap,o=snapshotStyle(e),a=Object.getPrototypeOf(async()=>{}).constructor,s=await new a("map",r)(e),n=snapshotStyle(e),i=diffSnapshots(o.sources,n.sources),l=diffSnapshots(o.layers,n.layers),c=toJsonSafe(s);return"error"in c?{success:!0,resultError:c.error,sources:i,layers:l}:{success:!0,result:c.value,sources:i,layers:l}}catch(o){return{error:formatSandboxExecutionError("MapLibre",o)}}},createExecuteMaplibreCodeTool=()=>({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: raster / tile overlays, and time-based animations / multi-step loops that one-shot tools cannot express. NOT for a globe projection, a sky or its colours, the hillshade or 3D terrain (setMapStyling). If any other tool already performs the operation, use that tool instead.",inputSchema:executeMaplibreCodeSchema,outputSchema:executeMaplibreCodeOutputSchema,execute:executeExecuteMaplibreCode,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)); }\" })"],examplePrompts:["Add the OpenStreetMap raster tiles as a semi-transparent overlay","Fade in a layer gradually"],relatedTools:["getMapStyleLayers","setLayerProperties","flyTo"],dependsOn:["getMapStyleLayers"]}),flyToOutputSchema=z.union([z.object({success:z.literal(!0)}),toolErrorSchema]),flyToSchema=z.object({where:z.union([z.object({boundingBox:geoJsonBBoxSchema.describe("BBox [W,S,E,N]."),padding:z.number().optional().describe("Padding in pixels. Default: 50.")}).describe("Fit the camera to a bounding box."),z.object({position:positionSchema.describe("[lng, lat] to fly to."),zoom:z.number().optional().describe("Camera zoom level. Default: 14.")}).describe("Fly the camera to a specific position.")])}),getMapStyleLayersOutputSchema=z.union([z.object({layers:z.array(z.object({id:z.string(),layout:z.unknown().optional(),paint:z.unknown().optional()}))}),toolErrorSchema]),getMapStyleLayersSchema=z.object({layerIdQuery:z.string().optional().describe("Case-insensitive partial match on layer IDs. If omitted, all layers are returned."),include:z.array(z.enum(["paint","layout"])).describe('Which properties to include in the response. Pass only what you need: "paint" for visual properties like colors and opacity, "layout" for structural properties like sizes, positions, and visibility.')}),getViewportOutputSchema=z.union([z.object({center:z.array(z.number()).describe("[lng, lat]"),zoom:z.number().describe("0–22"),bbox:z.array(z.number()).describe("[W, S, E, N]"),pitch:z.number().describe("degrees, 0=top-down, 85=near-horizontal"),bearing:z.number().describe("degrees, 0=north, 90=east")}),toolErrorSchema]),getViewportSchema=z.object({}),propertyGroups=["paint","layout"],changedPropertySchema=z.object({layerId:z.string(),group:z.enum(propertyGroups),propertyName:z.string()}),changeResultSchema=z.union([changedPropertySchema.extend({value:z.unknown(),success:z.literal(!0)}),changedPropertySchema.extend({error:z.string()})]),setLayerPropertiesOutputSchema=z.union([z.object({results:z.array(changeResultSchema)}),toolErrorSchema]),setLayerPropertiesSchema=z.object({changes:z.array(z.object({layerId:z.string().describe("MapLibre layer ID from getMapStyleLayers"),paint:z.record(z.string(),z.any()).optional().describe('Paint properties by name, e.g. { "fill-color": "#00f", "line-width": 3 }'),layout:z.record(z.string(),z.any()).optional().describe('Layout properties by name, e.g. { "visibility": "none", "text-size": 14 }')})).describe("Changes to apply in one go. A property goes under paint or layout as getMapStyleLayers lists it.")}),setPitchBearingOutputSchema=z.union([z.object({success:z.literal(!0)}),toolErrorSchema]),setPitchBearingSchema=z.object({pitch:z.number().min(0).max(85).optional().describe("0=top-down, 85=near-horizontal"),bearing:z.number().min(-180).max(180).optional().describe("0=north, 90=east")}),zoomInOrOutOutputSchema=z.union([z.object({zoom:z.number().describe("Resulting zoom level after the change")}),toolErrorSchema]),zoomInOrOutSchema=z.object({delta:z.number().describe("Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.")}),placeName=(e,t)=>e.properties?.poi?.name??e.properties?.address?.freeformAddress??t,resolveLocationInput=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 o=t.places.findPlaceById(r);if(!o){const e=t.places.entries.find(e=>e.id===r),a=e?.data[0];a&&(o={place:a,entryId:e.id})}if(!o)return null;const a=getPosition(o.place);return a?{place:o.place,position:a,name:placeName(o.place,r)}:null}const o=await locatePlace(e.query,e.queryAs,void 0,r);if(!o)return null;const a=getPosition(o);return a?{place:o,position:a,name:placeName(o,e.query),query:e.query}:null},costModelSchema=z.object({routeType:z.enum(routeTypes).optional(),traffic:z.enum(["live","historical"]).optional().describe("`historical` routes on the TYPICAL path, ignoring current incidents, yet reports live delays — for monitoring, so each tick refreshes delays on the SAME paths."),avoid:z.array(z.enum(avoidableTypes)).optional(),avoidAreas:z.array(geoJsonBBoxSchema.describe("GeoJSON bbox [west, south, east, north]")).max(10).optional().describe("Up to 10 bounding boxes to bypass.")}),whenSchema=z.object({option:z.enum(["departAt","arriveBy"]),date:z.string().describe("ISO 8601")}),routeParametersSchema=z.object({maxAlternatives:z.number().int().min(0).max(5).optional().describe("0–5, default 0"),costModel:costModelSchema.optional(),when:whenSchema.optional().describe("Omit to depart now.")}),setRouteOutputSchema=z.union([routesWriteOutputSchema,z.object({success:z.literal(!0)}),toolErrorSchema]),setRouteSchema=z.object({locations:z.array(locationInputSchema).min(2).optional().describe("Ordered [origin, ...stops, destination]. Minimum 2. Provide to set/replace waypoints; omit to keep the current waypoints."),parameters:routeParametersSchema.optional().describe("Routing options. Omitted fields keep their current values."),showOnMap:z.boolean().describe("Show the route on the map after calculation."),monitor:z.boolean().optional().describe('Arm a live monitor on the new route: recalculates it every 60s so its live-traffic delays stay current. Pair with `costModel.traffic: "historical"` + `maxAlternatives: 1-2` so the typical paths stay stable while delays refresh.'),hidePreviousEntries:hidePreviousEntriesSchema("routes")}).refine(e=>void 0!==e.locations||void 0!==e.parameters,{message:"Provide at least one of `locations` or `parameters`."}),routeEditOutputSchema=z.union([routesWriteOutputSchema,toolErrorSchema]),routeEditShowOnMapSchema=z.boolean().describe("Show the updated route and waypoints on the map after recalculation."),NOT_ENOUGH_WAYPOINTS_ERROR="Not enough valid waypoints to calculate a route (minimum 2).",NO_ROUTE_WAYPOINTS_ERROR="No existing waypoints available. Call setRoute first to create a route with waypoints.",resolveRouteWaypoints=e=>{const t=e.filter(e=>null!==e);return t.length>=2?t:null},sectionTypesWithCurves=[...defaultSectionTypes,"curve","curvyStretch"],OFFSETLESS_DATE_TIME=/^(\d{4}-\d{2}-\d{2})(?:[T ](\d{2}:\d{2})(:\d{2})?(\.\d+)?)?$/,toDepartArrive=({option:e,date:t})=>{const r=OFFSETLESS_DATE_TIME.exec(t);if(!r)return{option:e,date:new Date(t)};const[,o,a="00:00",s=":00"]=r;return{option:e,localDateTime:`${o}T${a}${s}`}},buildCalculateRouteParams=e=>{const{maxAlternatives:t=0,costModel:r,when:o}=e;return{computeTravelTimeFor:"all",sectionTypes:sectionTypesWithCurves,...t>0&&{maxAlternatives:t},...r&&{costModel:{...r,...r.avoidAreas&&{avoidAreas:r.avoidAreas}}},...o&&{when:toDepartArrive(o)}}},buildRouteRecalc=(e,t)=>()=>{const r=withAgentToolkitHeaders({locations:e,...buildCalculateRouteParams(t)});return calculateRoute(r)},calculateAndAddRoute=async(e,t,{showOnMap:r,hidePreviousEntries:o,monitor:a,params:s},n)=>{const i={...e.routing.params,...s},l=withAgentToolkitHeaders({locations:t,...buildCalculateRouteParams(i),signal:n?.signal}),c=await calculateRoute(l);s&&e.routing.setParams(s);const d=await e.routing.addRoutes(c,t,((e,t)=>{const r=e.features[0]?.properties?.summary,o=[r?formatDuration(r.travelTimeInSeconds):void 0,r?formatDistance(r.lengthInMeters):void 0].filter(Boolean),a=o.length?` (${o.join(", ")})`:"",s=t.length>=2?waypointAddress(t[0]):void 0,n=waypointAddress(t.at(-1));if(s&&n){const e=t.slice(1,-1).map(waypointAddress).filter(e=>Boolean(e));return`${e.length>0?`${s} via ${e.join(", ")} to ${n}`:`${s} to ${n}`}${a}`}return`Route${a}`})(c,t)),u=e.routing.entries.find(e=>e.id===d);return u&&(u.params=i),r&&await(async(e,t,r,o)=>{await hidePreviousShownEntries(e.routing,[t],o),await e.routing.showEntry(t),fitMapToGeoJSON(e.baseMap.mapLibreMap,r)})(e,d,c,o),a&&u&&e.routing.entries.includes(u)&&e.routing.startMonitoring(d,{recalculate:buildRouteRecalc(u.waypoints,u.params)},{skipInitialTick:!0}),{...summarizeRoutes(c),entryId:d}},addWaypointsToRouteOutputSchema=routeEditOutputSchema,addWaypointsToRouteSchema=z.object({origin:locationInputSchema.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:locationInputSchema.optional().describe("Append a new destination after the current destination. The previous destination is kept as an intermediate stop (it is not replaced)."),stops:z.array(locationInputSchema).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:routeEditShowOnMapSchema,hidePreviousEntries:hidePreviousEntriesSchema("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`."}),resolvedAreasOf=e=>{const t=e.flatMap(e=>"query"===e.source&&e.label?[{matched:e.label,...void 0!==e.query&&{query:e.query}}]:[]);return{routeLabel:e.find(e=>"route"===e.source)?.label,...t.length>0&&{resolvedAreas:t}}},maxDetourWhereSchema=z.object({mode:z.literal("maxDetour"),routeId:z.string().optional().describe('Route entry ID (e.g. "routes-0"). Use recallState. Default: latest route.'),maxDetourTimeSeconds:z.number().int().positive().max(3600).describe("Max extra travel time (seconds, at most 3600) the user accepts to reach a POI."),sortBy:z.enum(["detourTime","detourOffset"]).optional().describe('"detourTime" (default) ranks by the travel time a stop adds, negative (so first) when the route through the POI is faster; "detourOffset" by distance from the route line.'),limit:z.number().max(100).optional().describe("Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.")}),buildDiscoverPlacesSchema=e=>{const t={query:z.string().optional().describe('Free-text filter on POI name/address. NEVER a city/region/area name (e.g. "Paris", "Amsterdam") — those go in `where`. For "restaurants in Paris": `query` is empty (or a name filter like "pizza"), `where.queries: [{query: "Paris"}]`. Omit when filtering by category — use `poiCategories` instead.'),where:z.union([z.object(sharedWithinFields).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."}),nearbyWhereSchema,maxDetourWhereSchema,globalWhereSchema]).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:z.array(z.string()).optional().describe('CONSTANT_CASE category codes. ALWAYS batch all requested categories in ONE array — never one call per category. Common: "RESTAURANT", "CAFE", "BAR", "HOTEL", "PARK", "PARK_AND_RECREATION_AREA", "CHARGING_LOCATION", "FUEL_STATION", "PARKING_GARAGE", "SUPERMARKET", "PHARMACY", "ATM". For anything else, pass the plain words (e.g. "italian food", "gym"): this call resolves them to their best codes.'),show:showPlacesSchema.optional().describe("Display places/geometries on the map. Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry."),entryId:placesEntryIdHintSchema,geometries:showPlaceGeometriesSchema};return z.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`)."})},dispatchPlacesSearch=(e,t)=>{const{query:r,poiCategories:o,bias:a,radiusMeters:s,multiFilters:n}=e,i=((e,t)=>e?.position?{position:e.position,radiusMeters:t}:e?.boundingBox?{boundingBox:e.boundingBox}:void 0)(a,s),l=(c=n,c.geometries?.length?c.geometries:void 0);var c;const d={query:r,...o&&{filters:{poiCategories:o}},signal:t?.signal,limit:100},u=l?{...d,geometries:l}:{...d,...i&&{geoBias:i}};return discoverPlaces(withAgentToolkitHeaders(u))},finalizeDiscoverResult=async(e,t,r,o,a,s,n,i)=>{const l=o?await showResultsOnMap(e,[r],o):void 0;let c,d,u;if(a){const t=await showEntryGeometries(e,r,a,i);c=t.fetched,t.shown&&(d=!0),u=t.error}const h=e.places.entries.find(e=>e.id===r)?.label;return{...summarizePlaces(t),placesEntryId:r,...h&&{label:h},...l&&{shown:l},...void 0!==c&&{geometriesFetched:c},...void 0!==d&&{geometriesShown:d},...u&&{geometriesError:u},...n&&{resolvedAreas:n}}},buildExecuteDiscoverPlaces=e=>async(e,t,r)=>{const{query:o,where:a,poiCategories:s,show:n,entryId:i,geometries:l}=e,c=a??{mode:"within",viewport:!0},d={};try{const e=s?.length?await resolvePOICategories(withAgentToolkitHeaders({filters:s,signal:r?.signal})):void 0;if(e&&!e.poiCategories.length)return{error:`No POI categories matched: ${e.unmatched.map(e=>`"${e}"`).join(", ")}. Retry with a broader word (e.g. "restaurant" rather than "trattoria") or an exact CONSTANT_CASE code, or search it as free text in \`query\`.`};const a=e?.poiCategories,u=e?.unmatched.length?{unmatchedPoiCategories:e.unmatched}:void 0;if("maxDetour"===c.mode){const e=await(async(e,t,r,o,a,s)=>{const n=e.routing.entries;if(0===n.length)return{error:"No routes in state. Calculate a route first via setRoute."};const i=t.routeId?n.find(e=>e.id===t.routeId):n.at(-1);if(!i)return{error:`No route found with id "${t.routeId}". Use recallState to list available routes.`};const l=i.data.features[0];if(!l)return{error:`Route entry "${i.id}" has no geometry.`};const c=withAgentToolkitHeaders({route:l,maxDetourTimeSeconds:t.maxDetourTimeSeconds,...r&&{query:r},...o&&{filters:{poiCategories:o}},...t.sortBy&&{sortBy:t.sortBy},...void 0!==t.limit&&{limit:t.limit},signal:s?.signal}),d=await discoverPlaces(c);return{result:d,placesEntryId:await e.places.addPlaceResult(d,makePlacesLabel(d,{query:r,poiCategories:o,routeLabel:i.label}),a)}})(t,c,o,a,i,r);return"error"in e?e:{...await finalizeDiscoverResult(t,e.result,e.placesEntryId,n,l,0,void 0,r),...u}}const h=await(async(e,t,r,o)=>{if("nearby"===e.mode){const a=await(async(e,t,r)=>{if(e.position)return{bias:{position:e.position},radiusMeters:e.radiusMeters};const o=await resolveNearby({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},toolStateToWhereContext(t,r)),a=o.label?[{matched:o.label,...void 0!==o.query&&{query:o.query}}]:[];return{bias:o.position?{position:o.position}:{},radiusMeters:e.radiusMeters,...a.length>0&&{resolvedAreas:a}}})(e,t,o);return{bias:a.bias,multiFilters:r,radiusMeters:a.radiusMeters,resolvedAreas:a.resolvedAreas}}return"within"===e.mode?(async(e,t,r,o)=>{if(e.viewport)return{bias:{boundingBox:getViewportBoundingBox(t.baseMap)},multiFilters:r};const a=await resolveWithin({boundingBox:void 0,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},toolStateToWhereContext(t,o));if(isResolveError(a))return a;const s=a.flatMap(e=>e.polygon?[e.polygon]:[]),n=a.flatMap(e=>e.polygon?[]:[e.bbox]),i={...r,...n.length&&{boundingBoxes:n},...s.length&&{geometries:s}},l=s.length?void 0:bboxFromBBoxes(n);return{bias:l?{boundingBox:l}:{},multiFilters:i,range:e.range,...resolvedAreasOf(a)}})(e,t,r,o):{bias:{},multiFilters:r}})(c,t,d,r);if("error"in h)return h;const{bias:p,multiFilters:y,radiusMeters:m,range:f,routeLabel:g,resolvedAreas:b}=h;if(f){const e=await(async(e,t)=>{const{state:r,range:o,query:a,resolvedPoiCategories:s,multiFilters:n,entryId:i,routeLabel:l}=e,c=getRangePolygons(r,o);if("error"in c)return c;const d=[...c.polygons,...n.geometries??[]],u=await dispatchPlacesSearch({query:a,poiCategories:s,multiFilters:{...n,geometries:d}},t);return{result:u,placesEntryId:await r.places.addPlaceResult(u,makePlacesLabel(u,{query:a,poiCategories:s,...l&&{routeLabel:l}}),i)}})({state:t,range:f,query:o,resolvedPoiCategories:a,multiFilters:y,entryId:i,routeLabel:g},r);return"error"in e?e:{...await finalizeDiscoverResult(t,e.result,e.placesEntryId,n,l,0,b,r),...u}}const w=b?.length?b.map(e=>e.matched).join(", "):void 0,S="nearby"===c?.mode&&!p.position,v=w??(S?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})(c)),E=await(async(e,t)=>{const{state:r,query:o,bias:a,resolvedPoiCategories:s,radiusMeters:n,multiFilters:i,entryId:l,whereLabel:c,routeLabel:d}=e,u=await dispatchPlacesSearch({query:o,poiCategories:s,bias:a,radiusMeters:n,multiFilters:i},t);return{result:u,placesEntryId:await r.places.addPlaceResult(u,makePlacesLabel(u,{query:o,poiCategories:s,where:c,...d&&{routeLabel:d}}),l)}})({state:t,query:o,bias:p,resolvedPoiCategories:a,radiusMeters:m,multiFilters:y,entryId:i,whereLabel:v,routeLabel:g},r);return{...await finalizeDiscoverResult(t,E.result,E.placesEntryId,n,l,0,b,r),...u}}catch(u){return toToolError("Search failed",u)}},discoverPlacesMetadata={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: ["CHARGING_LOCATION"], 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: ["CHARGING_LOCATION"], 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:["setRoute"]},budgetSchema=z.object({type:z.enum(budgetTypes).describe("Budget type including its unit."),value:z.number().describe("Budget value. e.g. 30 for 30 minutes, 50 for 50 km.")}),findReachableAreasSchema=z.object({origins:z.array(locationInputSchema).min(1).describe("One or more starting points; one reachable area is computed per origin under the same budgets and bundled into a single ranges entry. Pass a single-element array for the classic one-origin call."),budgets:z.array(budgetSchema).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."),fillStyle:geometryFillStyleSchema.optional().describe("How the polygons are filled. Default: outline (transparent fill with thick colored border). Use inverted for a donut (reachable highlighted, outside dimmed) or filled for solid polygons."),showOnMap:z.boolean().describe("Whether to render the range polygon(s) on the map."),showOriginPin:z.boolean().optional().describe("Whether to drop a pin at each origin. Default: true."),hidePreviousEntries:hidePreviousEntriesSchema("ranges")}),rangeOutputSchema=z.object({rangesId:z.string(),ranges:z.array(z.object({originName:z.string(),budgets:z.array(budgetSchema)})),skipped:z.array(z.object({origin:z.string(),reason:z.string()})).optional().describe("Origins that could not contribute (unresolved query, empty geometry, failed service call).")}),findReachableAreasOutputSchema=z.union([rangeOutputSchema,z.object({status:z.enum(["no_results"])}),toolErrorSchema]),formatOriginName=e=>e.origin.query??`${e.origin.position[1]}, ${e.origin.position[0]}`,rangeForOrigin=async(e,t,r,o)=>{let a=(e=>"query"in e?e.query:"position"in e?`${e.position[1]}, ${e.position[0]}`:"placeIdOrEntryId"in e?e.placeIdOrEntryId:"unnamed origin")(e);try{const s=await resolveLocationInput(e,r,o);if(!s)return{skipped:{origin:a,reason:"origin not found"}};a=s.name??a;const n=await(async(e,t,r)=>{const o=e.map(e=>withAgentToolkitHeaders({origin:t,budget:{type:e.type,value:e.value}})),a=await calculateReachableRanges(o,{signal:r?.signal});return 0===a.features.length?null:a})(t,s.position,o);if(!n)return{skipped:{origin:a,reason:"no reachable area returned"}};const i="placeIdOrEntryId"in e?s.name:s.query;return{range:{origin:i?{query:i,position:s.position}:{position:s.position},budgets:n.features.map(e=>e.properties.budget),polygon:n}}}catch(s){if(s instanceof SDKAbortError)throw s;return{skipped:{origin:a,reason:errorMessageOf(s)},error:s}}},getPoiCategoryCodesOutputSchema=z.union([z.object({count:z.number().describe("Number of codes returned"),codes:z.array(z.string()).describe('POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. "RESTAURANT") also accepted by the setMapStyling pois.filters.categories.values knob.')}),toolErrorSchema]),getPoiCategoryCodesSchema=z.object({filters:z.array(z.string()).optional().describe("Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes."),language:z.string().describe('Locale e.g. "en-GB", "fr-FR", "es-ES", "de-DE".')}),GRANULARITIES=["yearly","monthly","weekly","daily","hourly","average"],metricsSchema=z.object({speed:z.number().optional(),freeFlowSpeed:z.number().optional(),congestionLevel:z.number().optional(),travelTime:z.number().optional(),networkLength:z.number().optional()}),getTrafficAreaAnalyticsOutputSchema=z.union([z.object({entryId:z.string().describe("Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData."),name:z.string().optional(),dateRange:z.object({start:z.string(),end:z.string()}),baseData:metricsSchema,metrics:z.array(z.string()),tileCount:z.number(),availableGranularities:z.array(z.string())}),toolErrorSchema]),getTrafficAreaAnalyticsSchema=z.object({location:locationInputSchema.optional().describe("Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Provide this or bbox, never both."),bbox:z.array(z.number()).length(4).optional().describe("[west, south, east, north], west > east across the antimeridian — use a wide area (at least 5-10km across, city-level) for meaningful results. Provide this or location, never both."),showOnMap:z.boolean().optional().describe("Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize."),startDate:z.string().optional().describe("Start date 'YYYY-MM-DD'. Required unless `days` is given. Use with endDate for a continuous range (max 31 days)."),endDate:z.string().optional().describe("End date 'YYYY-MM-DD' (inclusive). Defaults to three days ago, the latest day with published data."),days:z.array(z.string()).optional().describe("Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate."),metrics:z.union([z.literal("all"),z.array(z.enum(areaAnalyticsMetricKeys)).min(1)]).describe("Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength']."),functionalRoadClasses:z.union([z.literal("all"),z.array(z.enum(functionalRoadClasses))]).optional().describe("Road classes to include. 'all' (default) or array of specific classes."),hours:z.union([z.literal("all"),z.array(z.number().int().min(0).max(23))]).optional().describe("Hours of the day in UTC (0-23) to include. 'all' (default) or an array of UTC hours: convert the area's local hours first (Amsterdam summer rush hours [7, 8, 9] local are [5, 6, 7] UTC).")}).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."}),resolveDateParams=(e,t,r)=>e&&e.length>0?{days:e}:t?r?{startDate:t,endDate:r}:{startDate:t}:{},entriesSummary=e=>e.trafficIncidents.entries.map(e=>({id:e.id,label:e.label,count:e.data.length,timestamp:e.timestamp})),getTrafficIncidentsOutputSchema=z.union([z.object({count:z.number(),entryId:z.string().optional(),resolvedAreas:resolvedAreasOutputSchema.optional(),entries:z.array(z.object({id:z.string(),label:z.string(),count:z.number(),timestamp:z.number()})),monitoring:z.boolean().optional().describe("True when this fetch also armed a background monitor on the new entry.")}),toolErrorSchema]),withinTrafficIncidentsWhereSchema=z.object({...sharedWithinFields,boundingBox:geoJsonBBoxSchema.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)."}),trafficIncidentsWhereSchema=z.union([withinTrafficIncidentsWhereSchema]).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²).'),categoryFilterDescription=`Filter by incident category (server-side). Canonical values: ${trafficIncidentRequestCategories.join(", ")}. Map the user's wording to the closest value. If omitted, all categories are returned.`,getTrafficIncidentsSchema=z.object({where:trafficIncidentsWhereSchema.optional().describe('Geographic scope. Default: `{ mode: "within", viewport: true }`.'),categoryFilter:z.array(z.enum([...trafficIncidentRequestCategories])).optional().describe(categoryFilterDescription),timeValidityFilter:z.array(z.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:z.string().optional().describe('Short human-readable label for this fetch — name the place ("Central London", "Amsterdam ring", "M25 corridor"), not coordinates. Surfaced verbatim in the monitor chip and on the entry chip in the UI; also used by analyseData / focusIncidents to disambiguate which entry to operate on. Always pass one when show is true (the default); the bbox-coordinate fallback ("bbox a,b → c,d") is for hidden (show: false) intermediate fetches only.'),show:z.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:z.boolean().optional().describe("Default: true. Pass false to skip camera move after rendering."),monitor:z.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:z.number().int().positive().optional().describe("Polling interval in ms when monitoring. Default 60000 (60s).")}),buildIncidentsFetcher=e=>async t=>(await trafficIncidentDetails(withAgentToolkitHeaders({...e,bbox:t}))).features,locatePlaceSchema=z.object({query:z.string().describe("Location string to resolve for downstream routes or searches."),waypointIndex:z.number().int().min(0).optional().describe("Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination."),queryAs:z.enum(["poi","place"]).describe('"poi" for venues / landmarks / businesses; "place" for addresses, cities, neighborhoods, parks, postal codes, geographies.'),where:whereSchema.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:showPlacesSchema.optional().describe("Map display actions after resolving. Skip for intermediate calculations."),entryId:placesEntryIdHintSchema,geometry:showPlaceGeometriesSchema}),locatePlaceOutputSchema=z.union([placeOutputSchema.extend({placesEntryId:z.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:z.number().optional(),shown:shownSchema.optional(),geometryFetched:z.boolean().optional().describe("Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set)."),geometryShown:z.boolean().optional().describe("Whether the fetched polygon was rendered on the map."),geometryError:z.string().optional().describe("Why the boundary fetch failed. Absent when the place simply has no boundary.")}),toolErrorSchema]),removeWaypointsFromRouteOutputSchema=routeEditOutputSchema,removeWaypointsFromRouteSchema=z.object({waypointIndices:z.array(z.number().int()).min(1).describe("Indices of waypoints to remove. 0=origin, last=destination. Min 2 waypoints must remain. Duplicates are not allowed."),showOnMap:routeEditShowOnMapSchema,hidePreviousEntries:hidePreviousEntriesSchema("routes")}),replaceWaypointInRouteOutputSchema=routeEditOutputSchema,waypointTargetSchema=z.union([z.literal("origin"),z.literal("destination"),z.number().int()]).describe("Which waypoint to overwrite. Use 'origin' or 'destination' for the endpoints, or a 0-based absolute index (0 = origin, last = destination, intermediate stops in between)."),replaceWaypointInRouteSchema=z.object({waypointIndex:waypointTargetSchema,location:locationInputSchema.describe("The new waypoint that replaces the targeted one."),showOnMap:routeEditShowOnMapSchema,hidePreviousEntries:hidePreviousEntriesSchema("routes")}),reverseGeocodeOutputSchema=z.union([placeOutputSchema.extend({placesEntryId:z.string().describe("Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData.")}),toolErrorSchema]),reverseGeocodeSchema=z.object({position:positionSchema.describe("[longitude, latitude] GeoJSON position to reverse geocode")}),isFeatureCollectionLike=e=>{if(!e||"object"!=typeof e)return!1;const t=e;return"FeatureCollection"===t.type&&Array.isArray(t.features)},addByodSourceOutputSchema=z.union([z.object({byodEntryId:z.string().describe("Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData)."),label:z.string(),source:z.union([z.object({kind:z.literal("url"),url:z.string()}),z.object({kind:z.literal("inline")})]).describe("Where the data came from."),profile:byodDataProfileSchema.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.")}),toolErrorSchema]),addByodSourceSchema=z.object({label:z.string().describe('Human-readable label for the new entry (e.g. "Sales territories", "Customer pins 2026-Q1").'),url:z.url().optional().describe("URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. EXCLUSIVE with `data`."),data:z.unknown().optional().describe('Inline GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`). EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.'),entryId:placesEntryIdHintSchema}).refine(e=>e.url?!e.data:!!e.data,{message:"Pass EXACTLY ONE of `url` or `data`."}),ALLOWED_PROTOCOLS=/* @__PURE__ */new Set(["http:","https:"]),CANCELLED_ERROR="The turn was cancelled; the BYOD source was not added.",NON_PUBLIC_IPV4_BLOCKS=[[[0,0,0,0],8],[[10,0,0,0],8],[[100,64,0,0],10],[[127,0,0,0],8],[[169,254,0,0],16],[[172,16,0,0],12],[[192,0,0,0],24],[[192,0,2,0],24],[[192,168,0,0],16],[[198,18,0,0],15],[[198,51,100,0],24],[[203,0,113,0],24],[[224,0,0,0],4],[[240,0,0,0],4]],IPV4_LITERAL=/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/,LOCAL_HOSTNAME_SUFFIXES=[".localhost",".local",".localdomain",".internal",".intranet",".private",".corp",".home",".lan",".home.arpa"],octetsToNumber=e=>e.reduce((e,t)=>256*e+t,0),isPublicHost=e=>{const t=e.replace(/\.$/,"");return t.startsWith("[")?(e=>{const[t,r]=(e=>{const t=e=>e?e.split(":").map(e=>Number.parseInt(e,16)):[],[r,o]=e.split("::"),a=t(r),s=t(o);return[...a,...new Array(8-a.length-s.length).fill(0),...s]})(e);return 8192==(57344&t)&&!(8194===t||8193===t&&(0===r||3512===r))})(t.slice(1,-1)):IPV4_LITERAL.test(t)?(e=>{const t=octetsToNumber(e.split(".").map(Number));return!NON_PUBLIC_IPV4_BLOCKS.some(([e,r])=>{const o=2**(32-r);return Math.floor(t/o)===Math.floor(octetsToNumber(e)/o)})})(t):!(e=>!e.includes(".")||LOCAL_HOSTNAME_SUFFIXES.some(t=>e.endsWith(t)))(t)},builtInSourceUrlPolicy=e=>"https:"===e.protocol&&e.origin!==globalThis.location?.origin&&isPublicHost(e.hostname)?{valid:!0}:{valid:!1,reason:"URL refused: only public https:// URLs can be loaded, not local, private or same-origin addresses. Ask the user for a public https:// URL, or for the GeoJSON itself to pass as `data`."},loadUrlSource=async(e,t,r)=>{const o=(e=>{let t;try{t=new URL(e)}catch{return{error:`Invalid URL: "${e}".`}}return ALLOWED_PROTOCOLS.has(t.protocol)?{url:t}:{error:`URL protocol "${t.protocol}" is not allowed. Only http: and https: are supported.`}})(e);if("error"in o)return o;const a=t.byod.sourceUrlValidator??builtInSourceUrlPolicy,s=await Promise.resolve().then(()=>a(o.url)).catch(e=>toToolError("URL validation failed",e));if("error"in s)return s;if(!s.valid)return{error:s.reason};const n=await(async(e,t)=>{const r=new AbortController,o=setTimeout(()=>r.abort(),15e3),a=()=>r.abort();t?.aborted&&a(),t?.addEventListener("abort",a,{once:!0});try{const t=await fetch(e,{signal:r.signal,credentials:"omit",redirect:"error"});if(!t.ok||(e=>{const t=Number.parseInt(e.headers.get("content-length")??"",10);return Number.isFinite(t)&&t>26214400})(t))return;const o=await(async e=>{const t=e.body?.getReader();if(!t){const t=await e.text();return t.length>26214400?void 0:t}const r=new TextDecoder,o=[];let a=0;for(;;){const{done:e,value:s}=await t.read();if(e)break;if(a+=s.byteLength,a>26214400)return void(await t.cancel());o.push(r.decode(s,{stream:!0}))}return o.push(r.decode()),o.join("")})(t);return void 0===o?void 0:{value:JSON.parse(o)}}catch{return}finally{clearTimeout(o),t?.removeEventListener("abort",a),r.abort()}})(o.url,r);return r?.aborted?{error:CANCELLED_ERROR}:n&&isFeatureCollectionLike(n.value)?{data:n.value,sourceRecord:{kind:"url",url:e}}:{error:"Could not load a GeoJSON FeatureCollection from that URL. Ask the user to check the URL, or for the GeoJSON itself to pass as `data`."}},buildEntryKindScopePrompt=(e,t)=>`Emit \`{ kinds: [${e.map(e=>JSON.stringify(e)).join(" | ")}] }\` ${t}`,STANDING_SANDBOX_PARAMS=[...MULTI_INPUT_SANDBOX_PARAMS,"previous","now","log"],runPreparedSandbox=async(e,t,r)=>{const o=await runSandboxedFn(r.code,STANDING_SANDBOX_PARAMS,[...packSandboxArgs(t.sandbox,{h3:h3,turf:turf}),r.previous,new Date(r.sampledAt),()=>{}],r.verb,e.codeExecution);return"error"in o?o:validateAnalysisResult(o.value,r.outputFormat)},recipeKeys=/* @__PURE__ */new WeakMap,affectedEntriesOf=({resolved:e,geometries:t})=>[...e.places.map(e=>({kind:"places",id:e.id})),...e.routes.map(e=>({kind:"routes",id:e.id})),...e.incidents.map(e=>({kind:"incidents",id:e.id})),...e.trafficAreaAnalytics.map(e=>({kind:"trafficAreaAnalytics",id:e.id})),...e.byod.map(e=>({kind:"byod",id:e.id})),...t.affectedEntries.filter(t=>"places"===t.kind&&!e.places.some(e=>e.id===t.id)).map(e=>({kind:"places",id:e.id})),...t.affectedEntries.filter(e=>"customGeometries"===e.kind).map(e=>({kind:"customGeometries",id:e.id}))],analyseDataOutputSchema=z.union([z.object({affectedEntries:z.array(z.union([z.object({kind:z.literal("places"),id:z.string()}),z.object({kind:z.literal("routes"),id:z.string()}),z.object({kind:z.literal("incidents"),id:z.string()}),z.object({kind:z.literal("customGeometries"),id:z.string()}),z.object({kind:z.literal("trafficAreaAnalytics"),id:z.string()}),z.object({kind:z.literal("byod"),id:z.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:z.array(z.unknown()).optional().describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only present when \`geometriesEntryIDs\` was set).`),analysisId:z.string().describe("Stable handle for this analysis. Pass to `monitorAnalysis` to keep it recomputing whenever its source entries change."),name:z.string().describe("Unique name of the analysis within each affected entry."),description:z.string().optional(),outputFormat:z.enum(["json","chart"]),monitoring:z.boolean().optional().describe("Whether the analysis now recomputes on every change to its source entries."),analysis:z.unknown(),skipped:z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC)}),toolErrorSchema]),buildAnalyseDataSchema=(e,t)=>{const r=resolveActiveKinds(e,t),o=`At least one of ${buildEntryFieldList(r)} is REQUIRED.`,a={...buildEntryIdFields(r,{verb:"analyse",requiredHint:o})};return a.name=z.string().describe('Unique name within each affected entry (e.g. "incidents-per-cluster", "ev-near-route"). Reusing a name replaces the previous analysis on those entries.'),a.description=z.string().optional().describe("Optional short description of what the analysis computes."),a.outputFormat=z.enum(["json","chart"]).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION),a.code=z.string().describe(((e,t)=>{const r=resolveActiveKinds(e,t),o=buildEntryKindSandboxDocs(r),a=buildEntryKindSchemaDocs(r),s=r.includes("customGeometries");return"Async JS that aggregates the injected inputs and returns the result.\n\n"+buildSandboxToolsDoc(r,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading (\`if (places) ...\`, \`routes?.features.length\`, etc.). When defined:\n${o}\n\n${buildAnalyseReturnPrompt("counts, per-kind breakdowns, hex bins, intersections, distance bins")}\n\n`+(s?`${GEOMETRIES_PROPS_DOC}\n\n`:"")+a})(e,t)),a.monitor=z.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`."),z.object(a).refine(hasAnyEntryIds,{message:buildAtLeastOneEntryIdMessage(r)})},buildAnalyseDataDescription=(e,t)=>{const r=resolveActiveKinds(e,t).join(" / "),o="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. ${o} 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. ${o} 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).`},executeAnalyseData=async(e,t,r)=>{const{placesEntryIDs:o,routesEntryIDs:a,incidentsEntryIDs:s,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:l,name:c,description:d,code:u}=e,h=e.outputFormat??"json",p={placesEntryIDs:o,routesEntryIDs:a,incidentsEntryIDs:s,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:l},y=await prepareMultiInputs(p,t,r);if("error"in y)return{error:y.error};const{resolved:m,sandbox:f,geometries:g}=y.value,b=latestTimestamp(m)??Date.now(),w=await runSandboxedFn(u,MULTI_INPUT_SANDBOX_PARAMS,packSandboxArgs(f,{h3:h3,turf:turf}),"Analysis",t.codeExecution);if("error"in w)return{error:w.error};const S=validateAnalysisResult(w.value,h);if("error"in S)return{error:S.error};const v=S.value,E=affectedEntriesOf(y.value),I=n?g.sourceIds:void 0,T=E.map(e=>e.id),M=((e,t)=>`${e}::${[...t].sort((e,t)=>e.localeCompare(t)).join(",")}`)(c,T);return((e,t)=>{const r=()=>e.analyses.register({analysisId:t.analysisId,name:t.name,description:t.description,outputFormat:t.outputFormat,affectedEntryIds:t.affectedEntryIds});if(0===t.affectedEntryIds.length)return void r();const o=JSON.stringify([t.code,t.inputs,t.outputFormat,t.name,t.description]),a=e.analyses.job(t.analysisId);if(a&&recipeKeys.get(a)===o)return r(),void(t.monitor&&a.setActive(!0));a&&e.analyses.resetHistory(t.analysisId);const s=t.monitor||(a?.active??!1);let n;a?.unregister(),r(),n=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:s,run:async()=>{if(!e.analyses.get(t.analysisId))return;const r=await prepareMultiInputs(t.inputs,e);if("error"in r)return;const o=latestTimestamp(r.value.resolved)??Date.now(),a=await runPreparedSandbox(e,r.value,{code:t.code,previous:e.analyses.lastResult(t.analysisId),sampledAt:o,outputFormat:t.outputFormat,verb:"Analysis"});"error"in a||e.analyses.job(t.analysisId)!==n||e.analyses.attachResult(t.analysisId,{name:t.name,...t.description&&{description:t.description},outputFormat:t.outputFormat,data:a.value,timestamp:o})},onOrphaned:()=>e.analyses.remove(t.analysisId)}),recipeKeys.set(n,o),e.analyses.setJob(t.analysisId,n)})(t,{analysisId:M,name:c,description:d,outputFormat:h,code:u,inputs:p,affectedEntryIds:T,monitor:!0===e.monitor}),t.analyses.attachResult(M,{name:c,description:d,outputFormat:h,data:v,timestamp:b}),{affectedEntries:E,...I&&{sourceIds:I},analysisId:M,name:c,...d&&{description:d},outputFormat:h,monitoring:t.analyses.isMonitored(M),analysis:v,...g.skipped.length&&{skipped:g.skipped}}},clearTrackerSchema=z.object({trackerId:z.string().optional().describe("Which tracker to clear. Omit when only one tracker exists.")}),clearTrackerOutputSchema=z.union([z.object({cleared:z.literal(!0),trackerId:z.string(),name:z.string()}),toolErrorSchema]),execClustering=async(e,t,r,o)=>{const a=e.trafficIncidents.entries.find(e=>e.id===t);if(!a)return{error:`unknown incidents entry "${t}"`};if("params"===r.kind)return{value:clusterIncidents(a.data,r.params,o,a.timestamp)};const s=await prepareMultiInputs(r.inputs,e);if("error"in s)return s;const n=await runPreparedSandbox(e,s.value,{code:r.code,previous:o,sampledAt:a.timestamp,outputFormat:"json",verb:"Clustering"});return"error"in n?n:(i=n.value)&&"object"==typeof i&&Array.isArray(i.groups)&&i.groups.every(isClusterLike)?{value:n.value}:{error:"clustering `code` must return a cluster(...) result (`{ groups }`)"};var i},rerunFlags=/* @__PURE__ */new WeakMap,flagsFor=e=>{let t=rerunFlags.get(e);return t||(t={running:!1,scheduled:!1,pending:/* @__PURE__ */new Set},rerunFlags.set(e,t)),t},wired=/* @__PURE__ */new WeakSet,ensureClusterRerunWired=e=>{wired.has(e)||(wired.add(e),e.trafficIncidents.events.on("entries-change",({changedIds:t})=>scheduleRerun(e,t)))},scheduleRerun=(e,t)=>{const r=flagsFor(e);for(const o of t)r.pending.add(o);r.running||r.scheduled||(r.scheduled=!0,queueMicrotask(()=>{r.scheduled=!1,rerunClusters(e)}))},rerunClusters=async e=>{const t=flagsFor(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 a=r.recipe;try{const o=await execClustering(e,t,a,r.output);"value"in o&&e.trafficIncidents.getClustersRecord(t)===r&&e.trafficIncidents.setClusters(t,{recipe:a,output:o.value})}catch(o){console.warn(`[agent-toolkit] re-clustering "${t}" failed:`,o)}}}finally{t.running=!1}t.pending.size>0&&scheduleRerun(e,[])},resolveClusterKinds=(e,t)=>{const r=t?resolveActiveKinds(e,t).filter(e=>"incidents"!==e):[];return{context:r,active:["incidents",...r]}},buildClusterIncidentsCodeDoc=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"+buildSandboxToolsDoc(e,!0)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${buildEntryKindSandboxDocs(e)}\n\n`+buildEntryKindSchemaDocs(e),buildClusterIncidentsSchema=(e,t)=>{const{context:r,active:o}=resolveClusterKinds(e,t);return z.object({incidentsEntryID:z.string().describe("Entry id from getTrafficIncidents — the incidents to cluster."),eps:z.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:z.number().int().min(1).optional().describe("Minimum incidents per cluster. Default 3. Ignored when `code` is set."),maxClusters:z.number().int().min(1).optional().describe("Top N clusters by total delay. Default 6. Ignored when `code` is set."),...buildEntryIdFields(r,{verb:"analyse",requiredHint:"Only used by custom `code`."}),code:z.string().optional().describe(buildClusterIncidentsCodeDoc(o))})},clusterIncidentsOutputSchema=z.union([z.object({incidentsEntryID:z.string(),clusterCount:z.number(),clusters:z.array(z.object({id:z.string(),primaryRoads:z.array(z.string()),primaryCategory:z.string(),size:z.number(),totalDelaySeconds:z.number(),peakDelaySeconds:z.number(),trend:z.enum(["growing","fading","steady","new"])}))}),toolErrorSchema]),CLUSTER_BASE_DESCRIPTION="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.",buildClusterIncidentsDescription=e=>{const t=e.filter(e=>"incidents"!==e);return t.length?`${CLUSTER_BASE_DESCRIPTION} Custom \`code\` can also filter incidents against ${t.join(" / ")}.`:CLUSTER_BASE_DESCRIPTION},executeClusterIncidents=async(e,t)=>{const{incidentsEntryID:r,eps:o,minMembers:a,maxClusters:s,code:n}=e,i=t.trafficIncidents.entries.find(e=>e.id===r);if(!i)return{error:missingEntry("incidents",r)};const l={...null!=o&&{eps:o},...null!=a&&{minMembers:a},...null!=s&&{maxClusters:s}},c=n?{kind:"code",code:n,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(async(e,t,r)=>{ensureClusterRerunWired(e);const o=e.trafficIncidents.getClustersRecord(t),a=o&&(s=o.recipe,n=r,JSON.stringify(s)===JSON.stringify(n))?o.output:void 0;var s,n;const i=await execClustering(e,t,r,a);return"error"in i||e.trafficIncidents.setClusters(t,{recipe:r,output:i.value}),i})(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}))}},buildCreateTrackerSchema=(e,t)=>{const r=resolveActiveKinds(e,t),o=`At least one of ${buildEntryFieldList(r)} is REQUIRED.`,a={...buildEntryIdFields(r,{verb:"analyse",requiredHint:o})};return a.name=z.string().describe('Short tracker label, e.g. "Incidents near hospitals".'),a.rule=z.string().describe('The watched condition in plain language, e.g. "an incident within 100m of a hospital". For display.'),a.code=z.string().describe((e=>"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"+buildSandboxToolsDoc(e,!1)+`Each input is \`undefined\` when its \`*EntryIDs\` argument was omitted — guard before reading. When defined:\n${buildEntryKindSandboxDocs(e)}\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`+buildEntryKindSchemaDocs(e))(r)),z.object(a).refine(hasAnyEntryIds,{message:buildAtLeastOneEntryIdMessage(r)})},createTrackerOutputSchema=z.union([z.object({trackerId:z.string(),name:z.string(),rule:z.string(),watching:z.array(z.string()).describe("Source entry ids the rule reads."),firingNow:z.boolean().describe("Whether the condition holds at arm time. If true, one `opened` alert fires for it."),summary:z.string().describe("The arm-time verdict summary.")}),toolErrorSchema]),executeCreateTracker=async(e,t,r)=>{const o={placesEntryIDs:e.placesEntryIDs,routesEntryIDs:e.routesEntryIDs,incidentsEntryIDs:e.incidentsEntryIDs,geometriesEntryIDs:e.geometriesEntryIDs,trafficAreaAnalyticsEntryIDs:e.trafficAreaAnalyticsEntryIDs,byodEntryIDs:e.byodEntryIDs},a=await prepareMultiInputs(o,t,r);if("error"in a)return{error:a.error};const{resolved:s,sandbox:n}=a.value,i=latestTimestamp(s)??Date.now(),l=affectedEntriesOf(a.value).map(e=>e.id);if(0===l.length)return{error:"A tracker must watch at least one entry other than a reachable range."};const c=await runSandboxedFn(e.code,MULTI_INPUT_SANDBOX_PARAMS,packSandboxArgs(n,{h3:h3,turf:turf}),"Tracker rule",t.codeExecution);if("error"in c)return{error:c.error};if(!isVerdict(c.value))return{error:"Tracker `code` must return { active: boolean, members: {entryId, featureIds}[], summary: string }."};const d=c.value,u=uniqueTrackerId(e.name,t),h={id:u,name:e.name,rule:e.rule,affectedEntryIds:l,enabled:!0,wasActive:!1};return t.trackers.register(h),((e,t)=>{let r;r=e.engine.register({affectedEntryIds:t.affectedEntryIds,code:t.code,active:!0,run:async()=>{const o=await prepareMultiInputs(t.inputs,e);if("error"in o)return;const a=latestTimestamp(o.value.resolved)??Date.now(),s=await runPreparedSandbox(e,o.value,{code:t.code,previous:void 0,sampledAt:a,outputFormat:"json",verb:"Tracker rule"});!("error"in s)&&r.active&&isVerdict(s.value)&&e.trackers.ingestVerdict(t.trackerId,s.value,a)},onOrphaned:()=>e.trackers.unregister(t.trackerId)}),e.trackers.setJob(t.trackerId,r)})(t,{trackerId:u,code:e.code,inputs:o,affectedEntryIds:l}),t.trackers.ingestVerdict(u,d,i),{trackerId:u,name:e.name,rule:e.rule,watching:l,firingNow:d.active,summary:d.summary}},uniqueTrackerId=(e,t)=>{const r=e.toLowerCase().replaceAll(/[^a-z0-9]+/g,"-").replaceAll(/(?:^-+|-+$)/g,"")||"tracker";return pickUniqueEntryId(r,[...t.trackers.trackers.map(e=>e.id),...t.trackers.log().map(e=>e.trackerId)])},focusIncidentsOutputSchema=z.union([z.object({incidentsEntryID:z.string().describe("The entry the focus was applied to."),focusedCount:z.number().describe("How many of the requested ids ended up in the focus subset."),droppedIds:z.array(z.string()).describe("Ids that were not present on the entry and were dropped from the focus."),reason:z.string().optional().describe("Echo of the focus reason for downstream display.")}),toolErrorSchema]),focusIncidentsSchema=z.object({incidentsEntryID:z.string().describe("Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source)."),incidentIds:z.array(z.string()).min(1).describe("Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored."),reason:z.string().optional().describe('Short label shown in the UI FocusChip — what this subset represents (e.g. "top 3 by delay").')}),getCurrentWaypointsSchema=z.object({slotIndex:z.number().int().min(0).optional().describe("Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot."),includeEmptySlots:z.boolean().optional().describe("Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses."),offset:z.number().int().min(0).optional().describe("Pagination offset within the filtered slot list. Ignored when slotIndex is provided."),limit:z.number().int().min(1).max(25).optional().describe("Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.")}),getCurrentWaypointsOutputSchema=z.object({totalSlots:z.number().describe("Total waypoint slots currently staged"),filledSlots:z.number().describe("Number of staged slots that already contain coordinates"),matchedCount:z.number().describe("Total slots matching the request before pagination"),returnedCount:z.number().describe("Number of slots returned in this page"),offset:z.number().describe("Offset applied to the filtered slot list"),hasMore:z.boolean().describe("True when more slots can be fetched with a higher offset"),nextOffset:z.number().optional().describe("Offset to request the next page when hasMore=true"),waypoints:z.array(waypointSummarySchema)}),getTrackerHistorySchema=z.object({trackerId:z.string().optional().describe("Scope to one tracker. Omit for every tracker."),sinceMs:z.number().optional().describe("Only events at/after this epoch-ms sample time.")}),eventSchema=z.object({at:z.number(),trackerId:z.string(),trackerName:z.string(),type:z.enum(["alert","event"]),kind:z.enum(["opened","resolved"]),summary:z.string(),openedAt:z.number().optional()}),getTrackerHistoryOutputSchema=z.object({events:z.array(eventSchema).describe("Newest first."),count:z.number(),coverage:z.object({oldestRetainedAt:z.number(),newestAt:z.number()}).optional().describe("Sample-time span the bounded log currently retains — older events were evicted.")}),oldestRetainedAt=e=>{const t=/* @__PURE__ */new Map;for(const o of e){const e=t.get(o.trackerId);e?e.count++:t.set(o.trackerId,{count:1,oldestAt:o.at})}let r=e[0].at;for(const{count:o,oldestAt:a}of t.values())o>=200&&(r=Math.max(r,a));return r},getTrackersSchema=z.object({}),getTrackersOutputSchema=z.object({trackers:z.array(z.object({trackerId:z.string(),name:z.string(),rule:z.string(),enabled:z.boolean(),firing:z.boolean().describe("Whether the rule is currently active (an open episode)."),openedAt:z.number().optional(),watching:z.array(z.string()).describe("Source entry ids the rule reads.")})),count:z.number()}),monitorAnalysisOutputSchema=z.union([z.object({analysisId:z.string(),enabled:z.boolean().describe("The analysis is now recomputed on every source-entry change (true) or not (false).")}),toolErrorSchema]),monitorAnalysisSchema=z.object({analysisId:z.string().describe("The `analysisId` returned by `analyseData` for the analysis to start or stop monitoring."),enabled:z.boolean().describe("true → recompute this analysis on every change to its source entries; false → stop monitoring it. The latest result stays attached either way.")}),showProducedGeometriesSchema=z.object({fillStyle:geometryFillStyleSchema.optional().describe('How the polygons are filled. Default: "outline". Use "inverted" for difference/inverse results.'),mode:showModeSchema,hidePreviousEntries:hidePreviousEntriesSchema("custom-geometries").optional()}),sharedProcessDataReport={sourceIds:z.array(geometriesIdSchema).optional().describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only when \`geometriesEntryIDs\` was set).`),skipped:z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),fitted:fittedReportSchema.optional()},processDataOutputSchema=z.union([buildPlacesOutputSchema().extend({placesEntryId:z.string(),label:z.string().optional(),connectionsRendered:z.number().optional(),placesGeometryCount:z.number().optional().describe("Polygons attached to the new places entry (when `geometries` was returned alongside)."),shownPlaces:z.boolean().optional(),...sharedProcessDataReport}),z.object({customGeometriesEntryId:geometriesIdSchema,customGeometriesCount:z.number(),label:z.string(),operation:z.string().optional(),shownCustomGeometries:z.boolean().optional(),...sharedProcessDataReport}),z.object({byodEntryId:z.string(),byodFeatureCount:z.number(),label:z.string(),...sharedProcessDataReport}),z.object({fitted:fittedReportSchema}),toolErrorSchema]),buildProcessDataSchema=(e,t)=>{const r=resolveActiveKinds(e,t),o=`At least one of ${buildEntryFieldList(r)} is REQUIRED.`,a={...buildEntryIdFields(r,{verb:"process",requiredHint:o})};return a.code=z.string().describe(((e,t)=>{const r=resolveActiveKinds(e,t),o=buildEntryKindSchemaDocs(r),a=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":"",s=r.includes("customGeometries")?`${GEOMETRIES_PROPS_DOC}\n\n`:"",n=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=`${buildEntryKindSandboxDocs(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"+n+"\n"+buildSandboxToolsDoc(r,!0)+i+"Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. "+a+`${FIT_ON_MAP_DOC}\n\n`+s+o})(e,t)),a.label=z.string().optional().describe("Short label for the new entry (used by both places and custom-geometries paths)."),a.entryId=placesEntryIdHintSchema,a.operation=z.string().optional().describe("Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance on a new custom-geometries entry. Ignored on the places path."),a.show=z.object({places:showPlacesSchema.optional().describe("Render the new places entry (when `places` was returned)."),placesGeometries:z.object({fillStyle:geometryFillStyleSchema.optional(),mode:showModeSchema}).optional().describe("Render the polygons attached to the new places entry (when both `places` and `geometries` were returned)."),customGeometries:showProducedGeometriesSchema.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)."),a.connectMarkerType=z.enum(["pin","base-map"]).optional().describe('Which PlacesModule renders `placeConnections`. Default: "pin". Match the module holding the endpoints.'),z.object(a).refine(hasAnyEntryIds,{message:buildAtLeastOneEntryIdMessage(r)})},buildProcessDataDescription=(e,t)=>{const r=resolveActiveKinds(e,t).join(" / "),o="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. ${o} 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. ${o} 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.`},isConnectionArray=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)})},isNonEmptyString=e=>"string"==typeof e&&e.trim().length>0,isValidLngLat=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},derivedPlaceId=(e,t,r)=>`derived-${e.toFixed(6)}_${t.toFixed(6)}-${r}`,validateField=(e,t,r)=>void 0===e?{value:void 0}:t(e)?{value:e}:{error:r},nonEmptyFeatureCollection=e=>e&&e.features.length>0?e:void 0,executeProcessData=async(e,t,r)=>{const{placesEntryIDs:o,routesEntryIDs:a,incidentsEntryIDs:s,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:l,code:c}=e,d=await prepareMultiInputs({placesEntryIDs:o,routesEntryIDs:a,incidentsEntryIDs:s,geometriesEntryIDs:n,trafficAreaAnalyticsEntryIDs:i,byodEntryIDs:l},t,r);if("error"in d)return{error:d.error};const{sandbox:u,geometries:h}=d.value,p=await runSandboxedFn(c,MULTI_INPUT_SANDBOX_PARAMS,packSandboxArgs(u,{h3:h3,turf:turf}),"Process",t.codeExecution);if("error"in p)return{error:p.error};const y=toJsonSafe(p.value);if("error"in y)return{error:y.error};const m=(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(!isFeatureCollectionLike(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 o=e.features[r],a=o?.geometry;if(!a||"Point"!==a.type||!isValidLngLat(a.coordinates))return{error:`\`places[${r}]\` must be a Point Feature with valid [lng, lat] coordinates (lng ∈ [-180,180], lat ∈ [-90,90]).`};const s=o.properties;if(![s?.poi?.name,s?.address?.freeformAddress].find(isNonEmptyString))return{error:`\`places[${r}]\` needs a display name in \`properties.poi.name\` (POIs) or \`properties.address.freeformAddress\` (streets, intersections, addresses).`};if(!isNonEmptyString(s?.type)||!placeTypes.includes(s.type))return{error:`\`places[${r}].properties.type\` is required and must be one of: ${placeTypes.join(", ")}.`};if(!isNonEmptyString(o.id)){const[e,t]=a.coordinates;o.id=derivedPlaceId(e,t,r)}const n=t.get(o.id);if(void 0!==n)return{error:`\`places[${r}]\` duplicates id "${o.id}" of \`places[${n}]\`; give each place a distinct id, or omit it so a unique one is derived.`};t.set(o.id,r)}return{value:e}})(t.places);if("error"in e)return e.error;r.places=nonEmptyFeatureCollection(e.value)}const o=validateField(t.placeConnections,isConnectionArray,"`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).");if("error"in o)return o.error;if(r.placeConnections=o.value,void 0!==t.geometries){const e=(e=>{if(!Array.isArray(e))return{error:"`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features."};const t=[];for(let r=0;r<e.length;r++){const o=e[r];if(!isPolygonFeature(o))return{error:`\`geometries[${r}]\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.`};t.push(o)}return{value:t}})(t.geometries);if("error"in e)return e.error;r.geometries=e.value.length>0?e.value:void 0}const a=validateField(t.fitOnMap,isFitOnMapInput,"`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.");if("error"in a)return a.error;r.fitOnMap=a.value;const s=validateField(t.byod,isFeatureCollectionLike,"`byod` must be a GeoJSON FeatureCollection (any geometry types).");return"error"in s?s.error:(r.byod=nonEmptyFeatureCollection(s.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)})(y.value);if("string"==typeof m)return{error:m};const{places:f,geometries:g,fitOnMap:b,byod:w}=m;try{if(f)return await(async(e,t,r,o)=>{const{label:a,entryId:s,show:n,connectMarkerType:i,geometriesEntryIDs:l}=t,{places:c,placeConnections:d,geometries:u,fitOnMap:h}=r,p=c.features.length,y=`${a?`processed: ${a}`:"processed"} (${p} ${1===p?"place":"places"})`,m=await e.places.addPlaceResult(c,y,s,d,u),f=n?.places?await showResultsOnMap(e,[m],n.places):void 0;let g,b;if(u?.length&&(g=u.length,n?.placesGeometries&&await e.places.showPlaceGeometries(u,n.placesGeometries.fillStyle??"outline",n.placesGeometries.mode??"replace",m)),d){const t=await(async(e,t,r,o)=>{try{return await(await e.places.getEntryPlacesModule(t,o??"pin")).showConnections(r),{count:r.length}}catch(a){return toToolError("Failed to render connections",a)}})(e,m,d,i);if("error"in t)return t;b=t.count}const w=h?applyFitOnMap(e,h):void 0,S=e.places.entries.find(e=>e.id===m)?.label;return{...summarizePlaces(c),placesEntryId:m,...S&&{label:S},...f&&{shownPlaces:!0},...void 0!==b&&{connectionsRendered:b},...void 0!==g&&{placesGeometryCount:g},...l&&{sourceIds:o.sourceIds},...o.skipped.length&&{skipped:o.skipped},...w&&{fitted:w}}})(t,e,{...m,places:f},h);if(g?.length)return await(async(e,t,r,o,a)=>{const{label:s,entryId:n,operation:i,show:l,geometriesEntryIDs:c}=t,d=s?`processed: ${s}`:`processed geometries (${r.length})`,u=await e.customGeometries.addEntry(r,{sourceIds:a.sourceIds,...i&&{operation:i}},d,n);let h=!1;l?.customGeometries&&(await hidePreviousShownEntries(e.customGeometries,[u],l.customGeometries.hidePreviousEntries),await e.customGeometries.showEntry(u,l.customGeometries.fillStyle??"outline",l.customGeometries.mode??"replace"),h=!0);const p=o?applyFitOnMap(e,o):void 0;return{customGeometriesEntryId:{kind:"customGeometries",id:u},customGeometriesCount:r.length,label:d,...i&&{operation:i},...c&&{sourceIds:a.sourceIds},...a.skipped.length&&{skipped:a.skipped},...h&&{shownCustomGeometries:!0},...p&&{fitted:p}}})(t,e,g,b,h);if(w)return await(async(e,t,r,o,a)=>{const{label:s,entryId:n,geometriesEntryIDs:i}=t,l=s?`processed: ${s}`:`processed byod (${r.features.length})`,c=await e.byod.addEntry(r,l,{...n&&{explicitId:n}}),d=o?applyFitOnMap(e,o):void 0;return{byodEntryId:c,byodFeatureCount:r.features.length,label:l,...i&&{sourceIds:a.sourceIds},...a.skipped.length&&{skipped:a.skipped},...d&&{fitted:d}}})(t,e,w,b,h);if(b)return{fitted:applyFitOnMap(t,b)}}catch(S){return toToolError("Failed to write the process result",S)}return{error:"Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`."}},standardStyleIdOf=e=>("string"==typeof e?e:e?.id)??"streetLight",sourceSchema=z.union([z.object({kind:z.literal("integrator"),description:z.string().optional()}),z.object({kind:z.literal("url"),url:z.string()}),z.object({kind:z.literal("inline"),description:z.string().optional()})]),refSchema$1=z.object({id:z.string(),label:z.string(),timestamp:z.number(),featureCount:z.number(),geometryTypes:z.array(z.string()),propertyNames:z.array(z.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:sourceSchema,shown:z.boolean()}),detailSchema$5=refSchema$1.extend({profile:byodDataProfileSchema.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.")}),recallByodOutputSchema=z.union([z.object({entries:z.array(refSchema$1),entryMode:z.enum(["single","multiple"])}),detailSchema$5,toolErrorSchema]),refSchema=z.object({id:geometriesIdSchema.describe("Tagged id of this entry."),label:z.string(),timestamp:z.number(),featureCount:z.number(),operation:z.string().optional().describe("Operation label (custom-geometries entries only)."),sourceIds:z.array(geometriesIdSchema).optional().describe("Provenance: tagged ids of inputs that fed the producing op (custom only)."),analyses:z.array(analysisIndexSchema).optional().describe("Analyses already attached to this entry (places and custom only).")}),detailSchema$4=refSchema.extend({features:z.unknown().describe("GeoJSON FeatureCollection of the entry's polygon features.")}),recallGeometriesOutputSchema=z.union([z.object({entries:z.array(refSchema)}),detailSchema$4,toolErrorSchema]),summariseAnalyses=e=>e?.length?e.map(({name:e,outputFormat:t,timestamp:r,description:o})=>({name:e,outputFormat:t,timestamp:r,...o&&{description:o}})):void 0,indexEntries=e=>{const t=[];for(const r of e.places.entries){if(!r.geometries?.length)continue;const o=summariseAnalyses(e.analyses.getAnalysesForEntry(r.id));t.push({id:{kind:"places",id:r.id},label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,...o&&{analyses:o}})}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 o=summariseAnalyses(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]},...o&&{analyses:o}})}return t.sort((e,t)=>t.timestamp-e.timestamp)},entryModeSchema$5=z.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."),indexEntrySchema$4=z.object({id:z.string(),label:z.string(),timestamp:z.number(),count:z.number().describe("Number of incidents captured in this entry."),shown:z.boolean().describe("True while this entry is rendered on the map."),monitored:z.boolean().describe("True while a background monitor is actively polling this entry."),focusedCount:z.number().describe("Incidents in the entry's focus subset (0 when nothing is focused).")}),detailSchema$3=indexEntrySchema$4.extend({byMagnitude:z.record(z.string(),z.number()).describe("Incident count grouped by `magnitudeOfDelay` (e.g. `{ major: 3, moderate: 5, minor: 10 }`)."),totalDelaySeconds:z.number().describe("Sum of `delayInSeconds` across the entry (0 when no delay data)."),clusterCount:z.number().optional().describe("Number of incident clusters computed for this entry — present only once clustering has run.")}),recallIncidentsOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema$4),entryMode:entryModeSchema$5}),detailSchema$3.extend({entryMode:entryModeSchema$5}),toolErrorSchema]),indexEntrySchema$3=z.object({id:z.string(),label:z.string(),timestamp:z.number(),featureCount:z.number().describe("Number of Place features stored in this entry."),shown:z.boolean().describe("Whether this entry is currently rendered on the map."),markerType:z.enum(placesMarkerTypes).optional().describe("Which marker type is rendering this entry (undefined when `shown` is false)."),analyses:z.array(analysisIndexSchema).optional().describe("Analyses already attached to this entry via analyseData (name + output format).")}),entryModeSchema$4=z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several entries render simultaneously; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),buildRecallPlacesOutputSchema=e=>{const t=z.object({id:z.string(),label:z.string(),timestamp:z.number(),places:buildPlacesOutputSchema()});return z.union([z.object({entries:z.array(indexEntrySchema$3),entryMode:entryModeSchema$4}),t.extend({entryMode:entryModeSchema$4}),toolErrorSchema])},indexEntrySchema$2=z.object({id:z.string(),label:z.string(),timestamp:z.number(),originCount:z.number()}),rangeSummarySchema=z.object({origin:z.object({query:z.string().optional(),position:z.tuple([z.number(),z.number()])}),budgets:z.array(z.object({type:z.string(),value:z.number()}))}),detailSchema$2=z.object({id:z.string(),label:z.string(),timestamp:z.number(),ranges:z.array(rangeSummarySchema)}),entryModeSchema$3=z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several reachable-range entries render at once; `single` enforces "at most one entry on the map" — switching to it auto-clears non-latest entries.'),recallRangesOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema$2),entryMode:entryModeSchema$3}),detailSchema$2.extend({entryMode:entryModeSchema$3}),toolErrorSchema]),indexEntrySchema$1=z.object({id:z.string(),label:z.string(),timestamp:z.number(),shown:z.boolean().describe("Whether this entry is currently rendered on the map.")}),detailSchema$1=z.object({id:z.string(),label:z.string(),timestamp:z.number(),routes:routesOutputSchema,waypoints:z.array(z.object({position:z.array(z.number()).length(2).describe("[longitude, latitude]")})),params:routeParametersSchema}),entryModeSchema$2=z.enum(["single","multiple"]).describe('Display policy: `multiple` (default) lets several routes render at once; `single` enforces "at most one route on the map" — switching to it auto-clears non-latest entries.'),recallRoutesOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema$1),entryMode:entryModeSchema$2}),detailSchema$1.extend({entryMode:entryModeSchema$2}),toolErrorSchema]),entryModeSchema$1=z.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."),indexEntrySchema=z.object({id:z.string(),label:z.string(),timestamp:z.number(),regionCount:z.number().describe("Number of analysed regions (features) in this entry."),metrics:z.array(z.string()).describe('Traffic metrics included in the report (e.g. ["congestionLevel", "speed"]).'),shown:z.boolean().describe("True while this entry is rendered on the map.")}),detailSchema=indexEntrySchema.extend({dateRange:z.object({start:z.string(),end:z.string()}).describe("Analysis period (ISO dates) — empty strings when the entry carries no date metadata."),regions:z.array(z.string()).describe("Names of the analysed regions that have one, in feature order."),heatmap:z.boolean().describe("Whether tile-level heatmap data is included.")}),recallTrafficAreaAnalyticsOutputSchema=z.union([z.object({entries:z.array(indexEntrySchema),entryMode:entryModeSchema$1}),detailSchema.extend({entryMode:entryModeSchema$1}),toolErrorSchema]),idLabelSchema=z.object({id:z.string(),label:z.string()}),entryModeSchema=z.enum(["single","multiple"]),snapshotSchema=z.object({places:z.array(idLabelSchema).describe("Places history entries — newest first."),routes:z.array(idLabelSchema).describe("Routes history entries — newest first."),ranges:z.array(idLabelSchema).describe("Reachable-range history entries — newest first."),entryModes:z.object({places:entryModeSchema,routes:entryModeSchema,ranges:entryModeSchema}).describe("Per-slice display policy. `multiple` (default) lets several entries render at once; `single` keeps only the latest entry — set via setEntryMode tools."),mapStyle:z.object({id:z.enum(standardStyleIDs)}).describe("Current standard map style."),mapPOIs:z.object({visible:z.boolean().optional(),filterMode:z.enum(filterShowModes).optional(),categories:z.array(z.string()).optional()}).nullable().describe("Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session."),traffic:z.object({flow:z.object({visible:z.boolean().optional()}).nullable().describe("Traffic-flow layer config. `null` when the module is uninitialised."),incidents:z.object({visible:z.boolean().optional()}).nullable().describe("Traffic-incidents layer config. `null` when the module is uninitialised.")}).describe("Real-time traffic visualization configs.")}),recallStateSchema=z.object({kind:z.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:z.union([z.string(),geometriesIdSchema]).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.')}),buildExecuteRecallState=e=>async(e,t)=>{const{kind:r,id:o}=e;if(!r)return void 0!==o?{error:'`id` requires `kind`, the kind of entry it names ("geometries" for a tagged id).'}:(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})),o=t(e.routing.entries).map(({id:e,label:t})=>({id:e,label:t})),a=t(e.ranges.entries).map(({id:e,label:t})=>({id:e,label:t})),s={id:standardStyleIdOf(e.baseMap.ttMap.getStyleInput())},n=e.mapPOIs.poisModule?.getConfig(),i=n?{..."boolean"==typeof n.visible&&{visible:n.visible},...n.filters?.categories?.show&&{filterMode:n.filters.categories.show},...n.filters?.categories?.values&&{categories:[...n.filters.categories.values]}}:null,l=e.trafficTiles.trafficFlowModule?.getConfig(),c=e.trafficTiles.trafficIncidentsModule?.getConfig(),d={flow:l?{..."boolean"==typeof l.visible&&{visible:l.visible}}:null,incidents:c?{..."boolean"==typeof c.visible&&{visible:c.visible}}:null};return{places:r,routes:o,ranges:a,entryModes:{places:e.places.entryMode,routes:e.routing.entryMode,ranges:e.ranges.entryMode},mapStyle:s,mapPOIs:i,traffic:d}})(t);if("geometries"===r)return"string"==typeof o?{error:`kind "geometries" takes a tagged id, { kind, id: "${o}" } with kind "places", "ranges" or "customGeometries".`}:(async(e,t)=>{const{id:r}=e;return r?((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:missingEntry("customGeometries",e.id,"places")};if(!r.geometries?.length)return{error:`Places entry "${e.id}" has no cached footprints. Call updatePlacesDisplay with \`add: ["${e.id}"]\` and \`geometry\` to fetch them first.`};const o=summariseAnalyses(t.analyses.getAnalysesForEntry(r.id));return{id:e,label:r.label,timestamp:r.timestamp,featureCount:r.geometries.length,features:{type:"FeatureCollection",features:r.geometries},...o&&{analyses:o}}})(e,t);case"ranges":return((e,t)=>{const r=t.ranges.entries.find(t=>t.id===e.id);if(!r)return{error:missingEntry("customGeometries",e.id,"ranges")};const o=r.data.flatMap(e=>e.polygon?.features??[]);return 0===o.length?{error:`Ranges entry "${e.id}" has no polygons.`}:{id:e,label:r.label,timestamp:r.timestamp,featureCount:o.length,features:{type:"FeatureCollection",features:o}}})(e,t);case"customGeometries":return((e,t)=>{const r=t.customGeometries.findById(e.id);if(!r)return{error:missingEntry("customGeometries",e.id)};const o=summariseAnalyses(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]},...o&&{analyses:o}}})(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.'}}})(r,t):{entries:indexEntries(t)}})({id:o},t);if(void 0!==o&&"string"!=typeof o)return{error:`kind "${r}" takes a plain string id; a tagged { kind, id } is for kind "geometries".`};switch(r){case"places":return(async(e,t)=>{const{id:r}=e,o=t.places.entries,a=t.places.entryMode,s=t.places.shownEntryIds;if(!r)return{entries:[...o].sort((e,t)=>t.timestamp-e.timestamp).map(({id:e,label:r,timestamp:o,data:a})=>{const n=s.has(e),i=n?t.places.getShownMarkerType(e):void 0,l=t.analyses.getAnalysesForEntry(e);return{id:e,label:r,timestamp:o,featureCount:a.length,shown:n,...i&&{markerType:i},...l.length&&{analyses:l.map(({name:e,outputFormat:t,timestamp:r,description:o})=>({name:e,outputFormat:t,timestamp:r,...o&&{description:o}}))}}}),entryMode:a};const n=o.find(e=>e.id===r);if(!n)return{error:missingEntry("places",r)};const i=summarizePlaces(n.data);return{id:n.id,label:n.label,timestamp:n.timestamp,places:i,entryMode:a}})({id:o},t);case"routes":return(async(e,t)=>{const{id:r}=e,o=t.routing.entryMode,a=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:a.has(e)})),entryMode:o};const s=t.routing.entries.find(e=>e.id===r);if(!s)return{error:missingEntry("routes",r)};try{const e=summarizeRoutes(s.data),t=s.waypoints.map(e=>summarizeWaypoint(e)?.position).filter(e=>void 0!==e).map(e=>({position:e}));return{id:s.id,label:s.label,timestamp:s.timestamp,routes:e,waypoints:t,params:s.params,entryMode:o}}catch(n){return toToolError(`Failed to summarize route entry "${r}"`,n)}})({id:o},t);case"ranges":return(async(e,t)=>{const{id:r}=e,o=t.ranges.entryMode;if(!r)return{entries:[...t.ranges.entries].reverse().map(({id:e,label:t,timestamp:r,data:o})=>({id:e,label:t,timestamp:r,originCount:o.length})),entryMode:o};const a=t.ranges.entries.find(e=>e.id===r);return a?{id:a.id,label:a.label,timestamp:a.timestamp,ranges:a.data.map(e=>({origin:{...e.origin,position:e.origin.position},budgets:e.budgets})),entryMode:o}:{error:`No ranges entry with id "${r}". Call recallState({ kind: "ranges" }) to list available IDs.`}})({id:o},t);case"byod":return(async(e,t)=>{if(!e.id){const{shownEntryIds:e}=t.byod;return{entries:[...t.byod.entries].reverse().map(t=>({id:t.id,label:t.label,timestamp:t.timestamp,featureCount:t.profile.featureCount,geometryTypes:t.profile.geometryTypes,propertyNames:t.profile.properties.map(e=>e.name),source:t.source,shown:e.has(t.id)})),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:toByodSafeProfile(r.profile),source:r.source,shown:t.byod.shownEntryIds.has(r.id)}:{error:missingEntry("byod",e.id)}})({id:o},t);case"incidents":return(async(e,t)=>{const r=t.trafficIncidents,o=r.entryMode,a=r.shownEntryIds;if(!e.id)return{entries:[...r.entries].reverse().map(e=>({id:e.id,label:e.label,timestamp:e.timestamp,count:e.data.length,shown:a.has(e.id),monitored:r.isMonitored(e.id),focusedCount:r.getFocus(e.id)?.ids.size??0})),entryMode:o};const s=r.entries.find(t=>t.id===e.id);if(!s)return{error:missingEntry("incidents",e.id)};const n={};let i=0;for(const c of s.data){const e=c.properties.magnitudeOfDelay;n[e]=(n[e]??0)+1,i+=c.properties.delayInSeconds??0}const l=r.getClusters(s.id);return{id:s.id,label:s.label,timestamp:s.timestamp,count:s.data.length,shown:a.has(s.id),monitored:r.isMonitored(s.id),focusedCount:r.getFocus(s.id)?.ids.size??0,byMagnitude:n,totalDelaySeconds:i,...l&&{clusterCount:l.groups.length},entryMode:o}})({id:o},t);case"trafficAreaAnalytics":return(async(e,t)=>{const r=t.trafficAreaAnalytics,o=r.entryMode,a=r.shownEntryIds;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:a.has(e.id)})),entryMode:o};const s=r.findById(e.id);if(!s)return{error:missingEntry("trafficAreaAnalytics",e.id)};const n=s.data.properties;return{id:s.id,label:s.label,timestamp:s.timestamp,regionCount:s.data.features.length,metrics:n.metrics,shown:a.has(s.id),dateRange:{start:isoDate(n.startDate),end:isoDate(n.endDate)},regions:s.data.features.flatMap(e=>e.properties.name??[]),heatmap:n.heatmap,entryMode:o}})({id:o},t)}},recallStateMetadata={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"]},CLEARED_SLICES=[...Object.values({places:"places",routing:"routes",ranges:"ranges",customGeometries:"customGeometries",trafficAreaAnalytics:"trafficAreaAnalytics",trafficIncidents:"trafficIncidents",byod:"byod"}),"analyses","trackers","mapPOIs","trafficFlow","mapStyle"],resetStateOutputSchema=z.union([z.object({success:z.literal(!0),cleared:z.array(z.enum(CLEARED_SLICES)).describe("Slices that were wiped / reset to defaults.")}),toolErrorSchema]),resetStateSchema=z.object({}),byodLayerTypeSchema=z.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."),byodLayerSpecSchema=z.object({type:byodLayerTypeSchema,paint:z.record(z.string(),z.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:z.record(z.string(),z.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:z.unknown().optional().describe("Optional MapLibre filter expression restricting which features this layer renders."),id:z.string().optional().describe("Stable layer id. Auto-generated when omitted."),beforeID:z.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: '${mapStyleLayerIDs.lowestLabel}' for fill/line/heatmap, which keeps every map label readable above the data, or '${mapStyleLayerIDs.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."),setByodLayersSchema=z.object({byodEntryId:z.string().describe("Id of the BYOD entry to restyle (from `addByodSource` or `recallState`)."),layers:z.array(byodLayerSpecSchema).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:showByodSchema.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`).")}),setByodLayersOutputSchema=z.union([z.object({byodEntryId:z.string(),label:z.string(),layerCount:z.number().describe("Number of layers now configured on the entry."),layerTypes:z.array(z.string()).describe("The `type` of each configured layer, in draw order."),shown:shownByodSchema.optional().describe("Render report — present only when `show` was provided (or the entry re-rendered live).")}),toolErrorSchema]),sliceSchema=z.enum(ENTRY_MODE_SLICE_NAMES),modeSchema=z.enum(["single","multiple"]),setEntryModeOutputSchema=z.union([z.object({success:z.literal(!0),slice:sliceSchema,mode:modeSchema,droppedEntryIds:z.array(z.string()).describe("Entry IDs removed from history because the new mode is `single` and they were not the latest.")}),toolErrorSchema]),setEntryModeSchema=z.object({slice:sliceSchema.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:modeSchema.describe("`multiple` (default) lets several entries render at once; `single` keeps only the latest entry — switching to it auto-clears every older entry.")}),setEntryModeDescription=`Switch the display policy of one entry-owning slice between \`multiple\` and \`single\`. Supported slices: ${ENTRY_MODE_SLICE_NAMES.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.`,setTrafficIncidentsMonitorOutputSchema=z.union([z.object({incidentsEntryID:z.string(),enabled:z.boolean().describe("The monitor is now polling (true) or stopped (false)."),alreadyInState:z.boolean().describe("True when the monitor was already in the requested state — this call was a no-op.")}),toolErrorSchema]),setTrafficIncidentsMonitorSchema=z.object({incidentsEntryID:z.string().describe("Id of the incidents entry to monitor (returned by `getTrafficIncidents`)."),enabled:z.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:z.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.")}),startRouteMonitorOutputSchema=z.union([z.object({routesEntryID:z.string(),alreadyRunning:z.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.")}),toolErrorSchema]),startRouteMonitorSchema=z.object({routesEntryID:z.string().describe("Id of the routes entry to monitor (the `entryId` returned by `setRoute`)."),intervalMs:z.number().int().positive().optional().describe("Recalculation interval in milliseconds. Defaults to 60000 (60s).")}),stopRouteMonitorOutputSchema=z.union([z.object({routesEntryID:z.string(),wasRunning:z.boolean().describe("True when a monitor was actually stopped; false when it was already idle.")}),toolErrorSchema]),stopRouteMonitorSchema=z.object({routesEntryID:z.string().describe("Id of the routes entry whose monitor should stop.")}),layerEnum=z.enum(CLEARABLE_SLICE_NAMES),clearMapOutputSchema=z.union([z.object({success:z.literal(!0),cleared:z.array(layerEnum)}),toolErrorSchema]),clearMapSchema=z.object({layers:z.array(layerEnum).optional().describe("Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: "+CLEARABLE_SLICE_NAMES.map(e=>`\`${e}\``).join(", ")+". Map-only — slice histories (`entries`) are kept.")}),getStandardMapStylesOutputSchema=z.union([z.object({styles:z.array(z.string()).readonly()}),toolErrorSchema]),getStandardMapStylesSchema=z.object({}),setGeopoliticalViewOutputSchema=z.union([z.object({success:z.literal(!0),geopoliticalView:z.enum(geopoliticalViews).optional()}),toolErrorSchema]),setGeopoliticalViewSchema=z.object({geopoliticalView:z.enum(geopoliticalViews).optional().describe('"Unified" = the international view; a country code = that country\'s view. Omit to reset to the default view of the country the map is viewed from.')}),setLanguageOutputSchema=z.union([z.object({success:z.literal(!0),language:z.string()}),toolErrorSchema]),setLanguageSchema=z.object({language:z.string().describe('e.g. "en-US", "fr-FR"')}),setMapStandardStyleOutputSchema=z.union([z.object({success:z.literal(!0),style:z.string()}),toolErrorSchema]),setMapStandardStyleSchema=z.object({style:z.enum(standardStyleIDs).describe("Map visual theme")}),annotationOf=({kind:e,range:t,options:r})=>{if(t)return`${t.min}–${t.max}`;if(!r)return"";const o=(r.length>16?[...r.slice(0,3),"…"]:r).join("|");return"enums"===e?`[${o}]`:o},entryText=({path:e,annotation:t})=>t?`${e} (${t})`:e,entriesOf=e=>{const t=/* @__PURE__ */new Map;for(const[r,o]of e.children){const e=[...void 0===o.annotation?[]:[{path:"",annotation:o.annotation}],...entriesOf(o)],a=1===e.length||e.every(({path:e})=>e)?JSON.stringify(e):r,s=t.get(a);s?s.keys.push(r):t.set(a,{keys:[r],entries:e})}return[...t.values()].flatMap(({keys:e,entries:t})=>{const r=e.length>1?`{${e.join("|")}}`:e[0];if(e.length>1&&t.length>1){const{shared:e,rest:o}=sharedHead(t);return[{path:`${r}.${e}{${o.map(entryText).join(", ")}}`,annotation:""}]}return t.map(({path:e,annotation:t})=>({path:e?`${r}.${e}`:r,annotation:t}))})},sharedHead=e=>{const t=/^[^.{]+\./.exec(e[0].path)?.[0];if(!t||!e.every(({path:e})=>e.startsWith(t)&&e.length>t.length))return{shared:"",rest:e};const{shared:r,rest:o}=sharedHead(e.map(e=>({...e,path:e.path.slice(t.length)})));return{shared:`${t}${r}`,rest:o}},withPrefixedIds=e=>e.flatMap(({prefix:e,knobs:t})=>t.map(t=>({id:`${e}${t.id}`,knob:t}))),normalized=e=>e.toLowerCase().replace(/[^a-z0-9]/g,""),knobParts=["look","baseMap","pois","trafficFlow","trafficIncidents","terrain","places","routes","geometries","ranges","trafficAreaAnalytics","trafficIncidentDetails"],knobPartScopeSchema=z.object({kinds:z.array(z.enum(knobParts)).min(1)}),moduleHolder=(e,t)=>{const r=()=>{const r=e.getConfig();return Object.fromEntries(t.map(({id:e})=>[e,knobValueIn(r,e)]).filter(([,e])=>void 0!==e))};return{set:(r,o)=>setKnob(e,t,r,o),reset:r=>resetKnob(e,t,r),resetAll:()=>{for(const o of Object.keys(r()))resetKnob(e,t,o)},get values(){return r()}}},styleSource=(e,t,r,o,a)=>({part:e,drawn:!1,prefix:t,knobs:r,holder:async e=>moduleHolder(await o(e),r),...a&&{existing:e=>{const t=a(e);return t&&moduleHolder(t,r)}}}),displayKnobIds=/* @__PURE__ */new Set(["visible","markerType","fill.style"]),dataSource=(e,t,r,o)=>({part:e,drawn:!0,prefix:t,knobs:r.filter(({id:e})=>!displayKnobIds.has(e)),holder:async e=>{const t=o(e);return{set:(e,r)=>{for(const o of t)o.set(e,r)},reset:e=>{for(const r of t)r.reset(e)},resetAll:()=>{for(const e of t)e.resetAll()},get values(){return Object.assign({},...t.map(({values:e})=>e))}}}}),knobSources=[styleSource("look","",stylingFoundationsKnobCatalogue,e=>e.baseMap.getStylingFoundationsModule()),styleSource("baseMap","",baseMapKnobCatalogue.filter(({id:e})=>!/^buildings3D\.(hover|click)\./.test(e)),e=>e.baseMap.getBaseMapModule()),styleSource("pois","pois.",poisKnobCatalogue,e=>e.mapPOIs.getPOIsModule(),e=>e.mapPOIs.poisModule),styleSource("trafficFlow","traffic.flow.",trafficFlowKnobCatalogue,e=>e.trafficTiles.getTrafficFlowModule(),e=>e.trafficTiles.trafficFlowModule),styleSource("trafficIncidents","traffic.incidents.",trafficIncidentsKnobCatalogue,e=>e.trafficTiles.getTrafficIncidentsModule(),e=>e.trafficTiles.trafficIncidentsModule),styleSource("terrain","terrain.",terrainKnobCatalogue,e=>e.baseMap.getTerrainModule(),e=>e.baseMap.terrainModule),dataSource("places","places.",placesKnobCatalogue,e=>[e.places.knobs,e.ranges.placesKnobs]),dataSource("routes","routes.",routingKnobCatalogue,e=>[e.routing.knobs]),dataSource("geometries","geometries.",geometriesKnobCatalogue,e=>[e.customGeometries.knobs,e.places.geometriesKnobs,e.ranges.geometriesKnobs]),dataSource("ranges","ranges.",reachableRangesKnobCatalogue,e=>[e.ranges.knobs]),dataSource("trafficAreaAnalytics","traffic.areaAnalytics.",trafficAreaAnalyticsKnobCatalogue,e=>[e.trafficAreaAnalytics.knobs]),dataSource("trafficIncidentDetails","traffic.incidentDetails.",trafficIncidentDetailsKnobCatalogue,e=>[e.trafficIncidents.knobs])],knobSourceOf=e=>{for(const t of knobSources){const r=e.startsWith(t.prefix)?e.slice(t.prefix.length):void 0;if(void 0!==r&&t.knobs.some(e=>e.id===r))return{source:t,local:r}}},allKnobIds=withPrefixedIds(knobSources).map(({id:e})=>e),prefixHint=`Ids start with ${knobSources.flatMap(({prefix:e})=>e||[]).join(", ")}, or with none for the look and the base map.`,unknownKnob=e=>{if(knobSources.some(({drawn:t,prefix:r})=>t&&e.startsWith(r)&&displayKnobIds.has(e.slice(r.length))))/* @__PURE__ */return new Error(`'${e}' is set per entry by the update*Display tools (show, hide, markerType, fillStyle).`);const t=((e,t)=>{const r=normalized(e),o=Math.max(2,Math.floor(r.length/3));return t.map(e=>{const t=normalized(e),o=((e,t)=>{let r=Array.from({length:t.length+1},(e,t)=>t);for(let o=1;o<=e.length;o++){const a=[o];for(let s=1;s<=t.length;s++){const n=r[s-1]+(e[o-1]===t[s-1]?0:1);a.push(Math.min(r[s]+1,a[s-1]+1,n))}r=a}return r[t.length]})(r,t);return{candidate:e,distance:t.includes(r)||r.includes(t)?Math.min(1,o):o}}).filter(({distance:e})=>e<=o).sort((e,t)=>e.distance-t.distance).slice(0,3).map(({candidate:e})=>e)})(e,allKnobIds),r=t.length?`; did you mean ${t.join(", ")}?`:".";/* @__PURE__ */
|
|
2
|
+
return new Error(`Unknown knob '${e}'${r} ${prefixHint}`)},knobValueSchema=z.union([z.number(),z.boolean(),z.string(),z.array(z.string())]),themeModeSchema=z.enum(mapThemeModes).optional().describe("Light or dark map; auto (default) decides from the colours."),setMapStylingOutputSchema=z.union([z.object({success:z.literal(!0),settings:z.record(z.string(),knobValueSchema),rejected:z.array(z.object({id:z.string(),reason:z.string()})),themeColors:z.record(z.enum(mapColorNames),z.string()).optional(),switchedStyle:z.enum(standardStyleIDs).optional()}),toolErrorSchema]),stylingFieldsShape={reset:z.union([z.literal(!0),z.array(z.string())]).optional().describe("true resets every knob, what shows included, to the style default; a list resets just those knobs. Applied first."),preset:z.enum(mapPresetIds).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."),theme:z.union([z.object({colors:z.array(z.string()).min(1).max(12).describe("The CSS colours the theme is built from, three to six works best — a brand palette, a website, an artist's or design movement's typical palette, or a described mood turned into hex values. The SDK assigns them to land, water, roads, labels… coherently (readable labels guaranteed)."),mode:themeModeSchema}),z.object({imageUrl:z.url({protocol:/^https?$/}).describe("A CORS-readable image URL (logo, photo, poster) whose dominant colours become the theme."),mode:themeModeSchema})]).optional().describe("Theme the whole map from a few colours or an image instead of naming the ten colours yourself. Applied after preset and before `set`, so single colours in `set` can still override a role. A dark theme on a light standard style (or the reverse) switches to its dark (light) variant first. It also sets `colors.accent`, which routes and place pins follow unless a colour was set for them — so one call themes the whole map, not the base map alone.")},setMapStylingSchemaFor=(e=knobParts)=>{return z.object({set:z.record(z.string(),knobValueSchema).optional().describe(`Knob id → value. Factors are multipliers (1 = unchanged, e.g. 1.3), toggles are booleans, colours are CSS colour strings, a [bracketed] knob takes a list of its values (getPOICategoryCodes finds POI category codes). A filter applies once its \`.show\` and \`.values\` are both set. \`a.{b|c}.d\` stands for a.b.d and a.c.d, and \`a.{b, c (0–1)}\` for a.b and a.c. Ids: ${t=knobSources.filter(({part:t})=>e.includes(t)),entriesOf((e=>{const t={children:/* @__PURE__ */new Map};for(const{id:r,knob:o}of e)r.split(".").reduce((e,t)=>{let r=e.children.get(t);return r||e.children.set(t,r={children:/* @__PURE__ */new Map}),r},t).annotation=annotationOf(o);return t})(withPrefixedIds(t))).map(entryText).join(", ")}.`),...stylingFieldsShape});var t},existingHolder=async(e,t)=>e.existing?e.existing(t):e.holder(t),settingsOf=async e=>{const t={};for(const r of knobSources){const o=(await existingHolder(r,e))?.values??{};for(const{id:e}of r.knobs)void 0!==o[e]&&(t[`${r.prefix}${e}`]=o[e])}return t},standardVariants={light:"streetLight",dark:"streetDark"},drivingVariants={light:"streetLightDriving",dark:"streetDarkDriving"},monoVariants={light:"monoLight",dark:"monoDark"},LIGHT_DARK_VARIANTS={streetLight:standardVariants,streetDark:standardVariants,streetLightDriving:drivingVariants,streetDarkDriving:drivingVariants,monoLight:monoVariants,monoDark:monoVariants},executeSetMapStyling=async(e,t)=>{try{const r=await t.baseMap.getStylingFoundationsModule(),o=[],a=(e,t)=>{o.push({id:e,reason:errorMessageOf(t)})},s=async(e,r)=>{try{const o=knobSourceOf(e);if(!o)throw unknownKnob(e);r(await o.source.holder(t),o.local)}catch(o){a(e,o)}};void 0!==e.reset&&await(async(e,t,r)=>{if(!0!==t)for(const a of t)try{const t=knobSourceOf(a);if(!t)throw unknownKnob(a);(await existingHolder(t.source,e))?.reset(t.local)}catch(o){r(a,o)}else for(const a of knobSources)(await existingHolder(a,e))?.resetAll()})(t,e.reset,a),e.preset&&(await applyMapPreset(t.baseMap.ttMap,e.preset),await t.mapPOIs.getPOIsModule());const{themeColors:n,switchedStyle:i}=e.theme?await(async(e,t,r,o)=>{let a;try{const o=await(async e=>"colors"in e?deriveMapColors(e.colors,{mode:e.mode}):deriveMapColorsFromImage(e.imageUrl,{mode:e.mode}))(r);return a=await(async(e,t)=>{const r=mapColorsLightDark(t);if(e.styleLightDarkTheme===r)return;const o=LIGHT_DARK_VARIANTS[standardStyleIdOf(e.getStyleInput())]?.[r];return o&&await e.setStyle(o),o})(e.baseMap.ttMap,o),t.setMapColors(o),{themeColors:o,switchedStyle:a}}catch(s){return o("theme",s),{switchedStyle:a}}})(t,r,e.theme,a):{};for(const[t,l]of Object.entries(e.set??{}))await s(t,(e,t)=>e.set(t,l));return{success:!0,settings:await settingsOf(t),rejected:o,...n&&{themeColors:n},...i&&{switchedStyle:i}}}catch(r){return toToolError("Failed to set map styling",r)}},actionSchema=z.enum(["show","hide","remove"]).describe("`show` renders the listed entries (lazy-init their CustomGeoJSONModule); `hide` removes them from the map but keeps them in session state; `remove` hides AND drops them from history."),updateByodDisplaySchema=z.object({entryIds:z.array(z.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:actionSchema,clearAll:z.boolean().optional().describe('Only valid with `action: "hide"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.'),hideOthers:z.boolean().optional().describe('Only valid with `action: "show"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).')}).refine(e=>!("hide"!==e.action||!e.clearAll)||!!e.entryIds?.length,{message:'`entryIds` is required unless `action: "hide"` with `clearAll: true`.'}).refine(e=>!e.clearAll||"hide"===e.action,{message:'`clearAll: true` is only valid with `action: "hide"`.'}).refine(e=>!e.hideOthers||"show"===e.action,{message:'`hideOthers: true` is only valid with `action: "show"`.'}),updateByodDisplayOutputSchema=z.union([z.object({action:actionSchema,affectedIds:z.array(z.string()).describe("Entry ids that were actually shown / hidden / removed."),shown:z.array(z.string()).describe("BYOD entry ids currently rendered on the map after this call.")}),toolErrorSchema]),renderEntryGeometries=async(e,t,r,o)=>{const a=e.places.entries.find(e=>e.id===t);if(!a)return{rendered:0,skipped:[]};const s=r.placeIds?new Set(r.placeIds):void 0,n=s?a.data.filter(e=>s.has(e.id)):a.data,i=[],l=[];for(const d of n){const t=await fetchPlaceBoundary(e,d,o);"skip"in t?i.push({placeId:d.id,reason:t.skip}):l.push(t.feature)}const c=r.mode??"replace";return(l.length>0||"replace"===c&&a._modules?.geometries)&&await e.places.showPlaceGeometries(l,r.fillStyle??"outline",c,t),{rendered:l.length,skipped:i}},shownEntrySummarySchema=z.object({id:z.string(),label:z.string(),featureCount:z.number(),markerType:z.enum(placesMarkerTypes).describe("How the entry is rendered on the map.")}),updatePlacesDisplayOutputSchema=z.union([z.object({success:z.literal(!0),totalShown:z.number(),shown:z.array(shownEntrySummarySchema).describe("Entries currently on the map, with their labels."),actuallyAdded:z.array(z.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:z.array(z.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),geometriesRendered:z.number().optional().describe("Number of boundary polygons rendered when `geometry` was set."),geometriesSkipped:z.array(z.object({placeId:z.string(),reason:z.string()})).optional().describe("Places whose boundary could not be rendered (no geometry data source, fetch failure).")}),toolErrorSchema]),updatePlacesDisplaySchema=z.object({add:z.array(z.string()).optional().describe('Entry ids to show (e.g. ["places-1"]). Omit while still setting `markerType` or `geometry` to target the most recently stored entry.'),remove:z.array(z.string()).optional().describe("Entry ids to hide."),addMatching:z.array(z.string()).optional().describe("Category labels to show."),removeMatching:z.array(z.string()).optional().describe("Category labels to hide."),clear:z.boolean().optional().describe("Hide every displayed entry."),markerType:z.enum(placesMarkerTypes).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:geometryDisplayConfigSchema.optional(),fitBounds:z.boolean().optional().describe("Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear||void 0!==e.markerType||void 0!==e.geometry,{message:"updatePlacesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, markerType, geometry."}),focusableSectionTypes=[...countedRouteSectionTypes,"motorway","distinct","leg","country","curve","curvyStretch"],shownRouteEntrySummarySchema=z.object({id:z.string(),label:z.string(),routeCount:z.number().describe("Number of route alternatives in the entry."),selectedIndex:z.number().describe("Highlighted alternative index.")}),updateRoutesDisplayOutputSchema=z.union([z.object({success:z.literal(!0),totalShown:z.number(),shown:z.array(shownRouteEntrySummarySchema).describe("Routes entries currently on the map, with their labels."),actuallyAdded:z.array(z.string()).describe("Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op."),actuallyRemoved:z.array(z.string()).describe("Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op."),focused:z.array(z.object({entryId:z.string(),sectionCount:z.number().describe("0 = the selected route has none of them; do not retry."),lengthInMeters:z.number()})).optional().describe("Present when `focus` was set.")}),toolErrorSchema]),focusSchema=z.object({sectionTypes:z.array(z.enum(focusableSectionTypes)).min(1).optional().describe("Section types to ring. `distinct` = the stretches only this alternative drives; `curvyStretch` = runs of curves."),roads:z.array(z.string()).min(1).optional().describe('Road numbers or street names to ring, e.g. ["A2"].'),pick:z.union([z.enum(["all","longest","mostDelay","curviest"]),z.number().int().min(0)]).optional().describe("Which matching sections: `all` (default), the `longest`, the traffic one with `mostDelay`, the `curviest` curve or curvy stretch, or a 0-based index in driving order.")}).refine(e=>void 0!==e.sectionTypes||void 0!==e.roads,{message:"focus needs `sectionTypes` and/or `roads`."}),updateRoutesDisplaySchema=z.object({add:z.array(z.string()).optional().describe('Entry ids to show (e.g. ["routes-1"]). Omit while setting `selectedIndex`, `focus` or `waypoints` to target the most recently stored entry.'),remove:z.array(z.string()).optional().describe("Entry ids to hide."),addMatching:z.array(z.string()).optional().describe("Label substrings to show."),removeMatching:z.array(z.string()).optional().describe("Label substrings to hide."),clear:z.boolean().optional().describe("Hide every displayed entry."),selectedIndex:z.number().int().min(0).optional().describe("Which alternative to highlight on the targeted entries."),focus:focusSchema.nullable().optional().describe("Ring sections of the selected route of the targeted entries and fit the camera to them; `null` clears the ring."),waypoints:z.boolean().optional().describe("Show (true) or hide (false) the origin/stop/destination pins of the targeted entries."),fitBounds:z.boolean().optional().describe("Pan/zoom to fit the resulting display, or the focused sections. Defaults to true when `add` / `addMatching` or a `focus` with sections is set.")}).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.focus||void 0!==e.waypoints,{message:"updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, focus, waypoints."}),lengthOf=(e,t)=>getRouteProgressForSection(e,t)?.delta.distanceInMeters??0,normalizeRoadName=e=>e.replace(/\s+/g,"").toLowerCase(),hasSpan=e=>void 0!==e.startPointIndex&&void 0!==e.endPointIndex,sectionsToFocus=(e,t)=>{const r=(t.sectionTypes??[]).flatMap(t=>e.properties.sections[t]??[]),o=t.roads?.length?((e,t)=>{const r=new Set(t.map(normalizeRoadName));return getImportantRoads(e).filter(e=>[e.name,e.streetName,...e.roadNumbers].some(e=>void 0!==e&&r.has(normalizeRoadName(e)))).flatMap(e=>e.sections)})(e,t.roads):[],a=[...r.filter(hasSpan),...o].sort((e,t)=>e.startPointIndex-t.startPointIndex),{pick:s="all"}=t;if("all"===s)return a;if("number"==typeof s)return a.slice(s,s+1);const n=t=>SCORE_FOR_PICK[s](e,t);return a.length?[a.reduce((e,t)=>n(t)>n(e)?t:e,a[0])]:[]},SCORE_FOR_PICK={longest:lengthOf,mostDelay:(e,t)=>t.delayInSeconds??0,curviest:(e,t)=>t.curvinessScore??0},displayAdded=async(e,t,r)=>{void 0!==r.waypoints&&await e.routing.setWaypointsShown(t,r.waypoints),r.show&&!e.routing.shownEntryIds.has(t)&&await e.routing.showEntry(t)},summarySchema=z.object({id:z.string(),label:z.string(),activeMetric:z.string().optional(),displayMode:z.string().optional()}),updateTrafficAreaAnalyticsDisplaySchema=z.object({add:z.array(z.string()).optional().describe('Entry ids to render (e.g. ["tta-0"]). Use `recallState` to list available ids.'),remove:z.array(z.string()).optional().describe("Entry ids to hide."),addMatching:z.array(z.string()).optional().describe("Label substrings to show."),removeMatching:z.array(z.string()).optional().describe("Label substrings to hide."),clear:z.boolean().optional().describe("Hide every displayed entry."),fitBounds:z.boolean().optional().describe("Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added.")}).refine(e=>void 0!==e.add||void 0!==e.remove||void 0!==e.addMatching||void 0!==e.removeMatching||!0===e.clear,{message:"updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear."}),updateTrafficAreaAnalyticsDisplayOutputSchema=z.union([z.object({success:z.literal(!0),totalShown:z.number(),shown:z.array(summarySchema).describe("Traffic-area-analytics entries currently on the map."),actuallyAdded:z.array(z.string()),actuallyRemoved:z.array(z.string())}),toolErrorSchema]),summarizeEntry=e=>({id:e.id,label:e.label,...e._config?.activeMetric&&{activeMetric:e._config.activeMetric},...e._config?.displayMode&&{displayMode:e._config.displayMode}}),updateWaypointsDisplayOutputSchema=z.union([z.object({success:z.literal(!0),visible:z.boolean(),count:z.number()}),toolErrorSchema]),updateWaypointsDisplaySchema=z.object({visible:z.boolean().optional().describe("Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.")}),calculateBBoxSchema=z.object({geoJson:z.union([z.record(z.string(),z.unknown()),z.array(z.record(z.string(),z.unknown()))]).describe("GeoJSON object or array of GeoJSON objects")}),calculateBBoxOutputSchema=z.union([z.object({bbox:z.array(z.number()).length(4).describe("[west, south, east, north]; west > east crosses the antimeridian"),minLng:z.number(),minLat:z.number(),maxLng:z.number(),maxLat:z.number()}),toolErrorSchema]),questionSchema=z.object({id:z.string().describe('Short stable key for the answer, e.g. "travelMode" or "radius".'),question:z.string().describe("The question to put to the user."),options:z.array(z.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:z.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.')}),clarifyIntentSchema=z.object({intro:z.string().optional().describe("One short line of context for why you need to ask."),questions:z.array(questionSchema).min(1).max(4).describe("1-4 questions that resolve the ambiguity.")}),clarifyIntentOutputSchema=z.object({status:z.literal("awaiting_user_response"),intro:z.string().optional(),questions:z.array(questionSchema)}),CLARIFY_FORM_PROMPT='- 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.',CLARIFY_PROSE_PROMPT='- 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.',clarifyIntentExamples=['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 }] })'],clarifyIntentExamplePrompts=["Plan a route for me","Help me decide between these options"],executeClarifyIntent=async(e,t)=>({status:"awaiting_user_response",intro:e.intro,questions:e.questions}),createClarifyIntentTool=(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?CLARIFY_FORM_PROMPT:CLARIFY_PROSE_PROMPT}`,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:clarifyIntentSchema,outputSchema:clarifyIntentOutputSchema,execute:executeClarifyIntent,tags:["utilities"],examples:e.examples??clarifyIntentExamples,examplePrompts:e.examplePrompts??clarifyIntentExamplePrompts,alwaysActive:e.alwaysActive??!0,endsTurnOnCall:e.rendersForm??!1}),getCurrentLocationSchema=z.object({}),getCurrentLocationOutputSchema=z.union([z.object({position:z.array(z.number()).min(2).max(2).describe("[longitude, latitude]"),accuracy:z.number().describe("Accuracy of the location in meters"),timestamp:z.number().optional().describe("Timestamp of the location data")}),toolErrorSchema]),roundCoordinate=e=>Math.round(1e3*e)/1e3,createGetCurrentLocationTool=(e={})=>({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:getCurrentLocationSchema,outputSchema:getCurrentLocationOutputSchema,execute:(t,r,o)=>(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{return(({coords:e,timestamp:t},r)=>r?{position:[e.longitude,e.latitude],accuracy:e.accuracy,timestamp:t}:{position:[roundCoordinate(e.longitude),roundCoordinate(e.latitude)],accuracy:Math.max(Math.round(e.accuracy),111)})(await((e,t)=>new Promise((r,o)=>{t?.throwIfAborted();const a=()=>o(t?.reason);t?.addEventListener("abort",a,{once:!0});const s=e=>r=>{t?.removeEventListener("abort",a),e(r)};globalThis.navigator.geolocation.getCurrentPosition(s(r),s(o),{enableHighAccuracy:e,timeout:15e3,maximumAge:e?0:6e4})}))(e,t),e)}catch(r){const e=(e=>{if("object"!=typeof e||null===e||!("code"in e))return;const{code:t}=e;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}:toToolError("Failed to get current location",r)}})(e.fullPrecision??!1,o?.signal),tags:["utilities","location"],examples:["getCurrentLocation()"],examplePrompts:["Where am I?","Get my GPS location","Use my current position"],relatedTools:["discoverPlaces","reverseGeocode","locatePlace"]}),TOOL_TAGS=["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"],detailToolSchema=z.object({name:z.string(),description:z.string(),examplePrompts:z.array(z.string()),relatedTools:z.array(z.string()).optional()}),helpOutputSchema=z.union([z.object({mode:z.literal("summary"),examplePrompts:z.array(z.string())}),z.object({mode:z.literal("detail"),query:z.string().optional(),tag:z.enum(TOOL_TAGS).optional(),matchedTools:z.array(detailToolSchema),totalMatched:z.number()}),toolErrorSchema]),helpSchema=z.object({mode:z.enum(["summary","detail"]).describe('"summary": overview of all capabilities with example prompts mixed from all tools. "detail": searchable full tool info — use when you need to find a specific tool.'),query:z.string().optional().describe("Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags."),tag:z.enum(TOOL_TAGS).optional().describe("Detail mode only. Filter results to tools with a specific tag.")}),DATA_TOOL_SHARED_FRAMING="Dynamic-JS sandbox over any combination of places / routes / incidents / geometries / trafficAreaAnalytics / byod entries. Each input arg is `undefined` when its `*EntryIDs` was omitted.",DATA_TOOL_PRODUCERS=["discoverPlaces","locatePlace","setRoute","getTrafficIncidents","getTrafficAreaAnalytics","findReachableAreas","addByodSource"],DEFAULT_TOOLS_TYPED={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:locatePlaceSchema,outputSchema:locatePlaceOutputSchema,execute:async(e,t,r)=>{const{query:o,queryAs:a,waypointIndex:s,where:n,show:i,entryId:l,geometry:c}=e;try{const e=await(async(e,t,r)=>e&&"global"!==e.mode?"within"===e.mode?(async(e,t,r)=>{const o=await resolveWithin({viewport:e.viewport,boundingBox:e.boundingBox,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0,placeIds:e.placeId?[e.placeId]:void 0},toolStateToWhereContext(t,r));if(isResolveError(o))return o;const a=bboxFromBBoxes(o.map(e=>e.bbox));return{bias:a?{boundingBox:a}:void 0}})(e,t,r):(async(e,t,r)=>{if(e.position)return{bias:{position:e.position}};const{position:o}=await resolveNearby({viewport:e.viewport,queries:e.query?[{query:e.query,queryAs:e.queryAs}]:void 0},toolStateToWhereContext(t,r));return{bias:o?{position:o}:void 0}})(e,t,r):{bias:void 0})(n,t,r);if("error"in e)return e;const d=await locatePlace(o,a,e.bias,r);if(!d)return{error:`No result found for "${o}"`};const u=await t.places.addPlaceResult(d,makePlacesLabel(d,{query:o}),l);void 0!==s&&t.routing.setWaypointAt(s,d);const h=i?await(async(e,t,r,o)=>{const a=await showResultsOnMap(e,[r],{...o,zoomMode:"none"});if("auto"===o.zoomMode){if(t.bbox)fitMapToBBox(e.baseMap.mapLibreMap,t.bbox);else{const r=getPosition(t);if(r){const[t,o]=r;e.baseMap.mapLibreMap.flyTo({center:[t,o],zoom:13})}}a.zoomMode=!0}return a})(t,d,u,i):void 0;let p,y,m;if(c){const e=await showEntryGeometries(t,u,c,r);p=e.fetched>0,e.shown&&(y=!0),m=e.error}return{...summarizePlace(d),placesEntryId:u,...void 0!==s&&{waypointIndex:s},...h&&{shown:h},...void 0!==p&&{geometryFetched:p},...void 0!==y&&{geometryShown:y},...m&&{geometryError:m}}}catch(d){return toToolError("Location resolution failed",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:reverseGeocodeSchema,outputSchema:reverseGeocodeOutputSchema,execute:async(e,t,r)=>{const{position:o}=e;try{const e=withAgentToolkitHeaders({position:o,signal:r?.signal}),a=await reverseGeocode(e);if(a.properties){const e=await t.places.addPlaceResult(a,makePlacesLabel(a));return{...summarizePlace(a),placesEntryId:e}}return{error:"No result found for the given coordinates"}}catch(a){return toToolError("Reverse geocoding failed",a)}},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=discoverPlacesMetadata,{...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:buildDiscoverPlacesSchema(),outputSchema:z.union([buildPlacesOutputSchema().extend({shown:shownSchema.optional(),placesEntryId:z.string().optional(),label:z.string().optional().describe('Human-readable label stored with the entry (e.g. "cafe", "CINEMA (50 places)").'),geometriesFetched:z.number().optional().describe("Count of boundary polygons fetched and cached on the entry (when `geometries` was set)."),geometriesShown:z.boolean().optional().describe("Whether the fetched polygons were rendered on the map."),geometriesError:z.string().optional().describe("Why a boundary fetch failed. Absent when the places simply have no boundaries."),resolvedAreas:resolvedAreasOutputSchema.optional(),unmatchedPoiCategories:z.array(z.string()).optional().describe("`poiCategories` inputs that matched no category, left out of the search.")}),toolErrorSchema]),execute:buildExecuteDiscoverPlaces()};var t},processData:(metadata={classificationPrompt:`${DATA_TOOL_SHARED_FRAMING} 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), cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile). NOT for ringing / zooming to route sections (tolls, jams, a road) — that is updateRoutesDisplay \`focus\`.`,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: { fillStyle: "outline" } } })','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: { fillStyle: "inverted", hidePreviousEntries: "all" } } })','processData({ placesEntryIDs: ["ev-stations"], geometriesEntryIDs: [{ kind: "ranges", id: "ranges-0" }], code: "const ranges = Object.values(geometriesByEntry).flat(); 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.baseData.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.baseData.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: { fillStyle: "filled" } } })'],examplePrompts:["Keep only the vegan restaurants","Draw lines from this hub to every nearby cafe","Show the h3 hex coverage of these places","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:[...DATA_TOOL_PRODUCERS,"recallState"]},e=>((e,t)=>{const r=e.enabledDataKinds??ALL_ENTRY_DATA_KINDS;return{description:buildProcessDataDescription(r,e.scope),inputSchema:buildProcessDataSchema(r,e.scope),outputSchema:processDataOutputSchema,execute:executeProcessData,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildEntryKindScopePrompt(r,"listing only the entry kinds the user query touches. Omit when truly cross-kind."),...t}})(e,metadata)),analyseData:(e=>t=>((e,t)=>{const r=e.enabledDataKinds??ALL_ENTRY_DATA_KINDS;return{description:buildAnalyseDataDescription(r,e.scope),inputSchema:buildAnalyseDataSchema(r,e.scope),outputSchema:analyseDataOutputSchema,execute:executeAnalyseData,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildEntryKindScopePrompt(r,"listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all)."),...t}})(t,e))({classificationPrompt:`${DATA_TOOL_SHARED_FRAMING} 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 breakdowns (per-leg / per-country, CHARTS of alternatives — NOT how they differ or which roads they take, which recallState says); 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 ?? [])) { const t = routeUtils.getSectionsWithin(r, { within: [c], types: [\'traffic\'] }).traffic; byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (t ? t.sections.reduce((s, x, i) => s + (x.delayInSeconds ?? 0) * t.shares[i], 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.baseData.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.baseData.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.baseData.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","How many hairpins does my route have?"],relatedTools:["processData","focusIncidents","recallState"],dependsOn:[...DATA_TOOL_PRODUCERS,"processData","recallState"]}),setRoute:{description:'Calculate or recalculate a car route. `locations` sets origin/stops/destination, `parameters` the cost model / alternatives / when; pass at least one. With `parameters` alone it recalculates the current waypoints, or only stores the options when there are none. Each route summary carries `sectionCounts` (tollRoad, ferry, traffic…, absent = none), `countries` crossed and `via` (main roads) and `timeInCurvesPercent` (how winding); an alternative also carries `comparedToBest` (sections it adds / avoids against route 0) — answer "are there tolls?", "which roads?", "which is least winding?" and "how do the routes differ?" from them, no analyseData needed. To MONITOR traffic between places, set `monitor: true` with `costModel.traffic: "historical"` + `maxAlternatives: 1-2` — no separate startRouteMonitor call. Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallState / addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / analyseData instead.',classificationPrompt:'Calculate a NEW route from its full list of places, or change the current route\'s options (cost model / alternatives / when). Changing, adding or removing ONE waypoint of an existing route is replaceWaypointInRoute / addWaypointsToRoute / removeWaypointsFromRoute. To MONITOR / track the live traffic BETWEEN two places, START here (traffic "historical" + maxAlternatives 1-2); startRouteMonitor needs a route first.',inputSchema:setRouteSchema,outputSchema:setRouteOutputSchema,execute:async(e,t,r)=>{const{locations:o,parameters:a,showOnMap:s,monitor:n,hidePreviousEntries:i}=e;try{const{costModel:e}=t.routing.params,l=a&&{...a,...a.costModel&&{costModel:{...e,...a.costModel}}};let c;if(o){const e=await Promise.all(o.map(e=>resolveLocationInput(e,t,r))),a=o.filter((t,r)=>null===e[r]).map(e=>labelForLocationInput(e));if(a.length>0)return{error:`Could not resolve: ${a.join(", ")}`};if(c=resolveRouteWaypoints(e.map(e=>e?.place??null)),!c)return{error:NOT_ENOUGH_WAYPOINTS_ERROR}}else if(c=resolveRouteWaypoints(t.routing.planningSlots),!c)return l&&t.routing.setParams(l),{success:!0};return await calculateAndAddRoute(t,c,{showOnMap:s,hidePreviousEntries:i,monitor:n,params:l},r)}catch(l){return toToolError("Route calculation failed",l)}},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","Plan a curvy route from Innsbruck to Bolzano for my motorbike","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:addWaypointsToRouteSchema,outputSchema:addWaypointsToRouteOutputSchema,execute:async(e,t,r)=>{const{origin:o,destination:a,stops:s,showOnMap:n,hidePreviousEntries:i}=e;try{const e=await(async e=>{const t=e.routing.currentEntryModule;if(!t)return"No existing route on the map. Call setRoute or updateRoutesDisplay first.";const r=t.getShown()?.mainLines;if(!r||!r.features.length)return"No existing route available. Call setRoute first.";const o=e.routing.entries.find(e=>e._module===t)?.waypoints;return!o||o.length<2?NO_ROUTE_WAYPOINTS_ERROR:{shownRouteLines:r,entryWaypoints:o}})(t);if("string"==typeof e)return{error:e};const{shownRouteLines:l,entryWaypoints:c}=e,d=await(async(e,t,r)=>{const{origin:o,destination:a,stops:s=[]}=e,n=[...o?[{kind:"origin",location:o}]:[],...a?[{kind:"destination",location:a}]:[],...s.map(e=>({kind:"stop",location:e}))],i=await Promise.all(n.map(({location:e})=>resolveLocationInput(e,t,r))),l=[],c={stops:[]};for(let d=0;d<n.length;d++){const e=i[d];e?"origin"===n[d].kind?c.origin=e.place:"destination"===n[d].kind?c.destination=e.place:c.stops.push(e.place):l.push(labelForLocationInput(n[d].location))}return l.length>0?{error:`Could not resolve: ${l.join(", ")}`}:c})({origin:o,destination:a,stops:s},t,r);if("error"in d)return d;const u=[...d.origin?[d.origin]:[],...c,...d.destination?[d.destination]:[]],h=d.stops.length>0?withInsertedWaypoints(l.features[0],u,d.stops):u,p=resolveRouteWaypoints(h);return p?await calculateAndAddRoute(t,p,{showOnMap:n,hidePreviousEntries:i},r):{error:NOT_ENOUGH_WAYPOINTS_ERROR}}catch(l){return toToolError("Failed to add waypoints to route",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: { position: [4.89, 52.373] } }) // prepend the getCurrentLocation position as the 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:removeWaypointsFromRouteSchema,outputSchema:removeWaypointsFromRouteOutputSchema,execute:async(e,t,r)=>{const{waypointIndices:o,showOnMap:a,hidePreviousEntries:s}=e;try{const e=((e,t)=>{if(!e||e.length<2)return NO_ROUTE_WAYPOINTS_ERROR;const r=new Set(t);if(r.size!==t.length)return"Duplicate waypoint indices are not allowed.";for(const o of t)if(o<0||o>=e.length)return`Invalid waypoint index ${o}. 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,o);if("string"==typeof e)return{error:e};const n=resolveRouteWaypoints(e);return n?await calculateAndAddRoute(t,n,{showOnMap:a,hidePreviousEntries:s},r):{error:NOT_ENOUGH_WAYPOINTS_ERROR}}catch(n){return toToolError("Failed to remove waypoints from route",n)}},tags:["route","waypoint"],examples:["removeWaypointsFromRoute({ waypointIndices: [1] }) // remove a single stop","removeWaypointsFromRoute({ waypointIndices: [1, 2] }) // remove multiple stops in one call"],examplePrompts:["Remove the second stop from my route","Delete the Utrecht stop","Drop the last two intermediate stops"],relatedTools:["addWaypointsToRoute","replaceWaypointInRoute","setRoute","updateRoutesDisplay"],dependsOn:["setRoute"]},replaceWaypointInRoute:{description:"Replace a single waypoint in the current route in place — origin, destination, or any intermediate stop by 0-based index. Always recalculates. Other waypoints are preserved. Requires an existing route. Use addWaypointsToRoute to extend the route, removeWaypointsFromRoute to drop a waypoint, or setRoute to fully replace all waypoints.",classificationPrompt:"Overwrite a single waypoint of the current route in place — by 'origin', 'destination', or numeric index — and recalculate. Other waypoints are kept.",inputSchema:replaceWaypointInRouteSchema,outputSchema:replaceWaypointInRouteOutputSchema,execute:async(e,t,r)=>{const{waypointIndex:o,location:a,showOnMap:s,hidePreviousEntries:n}=e;try{const e=t.routing.currentWaypoints;if(!e||e.length<2)return{error:NO_ROUTE_WAYPOINTS_ERROR};const c=(i=o,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 resolveLocationInput(a,t,r);if(!d)return{error:`Could not resolve location: ${labelForLocationInput(a)}`};const u=[...e];u[c]=d.place;const h=resolveRouteWaypoints(u);return h?await calculateAndAddRoute(t,h,{showOnMap:s,hidePreviousEntries:n},r):{error:NOT_ENOUGH_WAYPOINTS_ERROR}}catch(c){return toToolError("Failed to replace waypoint in route",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:'Look up POICategory codes by natural-language name (e.g. "gym", "italian food", "bookstore"), or list all codes when no filter is given. A search does not need it: discoverPlaces poiCategories takes the plain words.',classificationPrompt:'Look up or list POICategory codes (e.g. "what is the code for gym?"). Not a step before discoverPlaces, which takes category words itself.',inputSchema:getPoiCategoryCodesSchema,outputSchema:getPoiCategoryCodesOutputSchema,execute:async(e,t,r)=>{const{filters:o,language:a}=e;try{const e=withAgentToolkitHeaders({filters:o,language:a,signal:r?.signal}),t=await getPOICategoryCodes(e);return{count:t.length,codes:t}}catch(s){return toToolError("Failed to fetch POI category codes",s)}},tags:["discover","place"],examples:['getPOICategoryCodes(language: "en-GB")','getPOICategoryCodes(filters: ["gym"], language: "en-GB")','getPOICategoryCodes(filters: ["italian restaurant"], language: "en-GB") // multi-word phrase, best match first','getPOICategoryCodes(filters: ["restaurant"], language: "fr-FR")'],examplePrompts:["What POI categories are available?","What's the code for gym?"],relatedTools:["discoverPlaces","setMapStyling"]},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: ${trafficIncidentRequestCategories.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. DO NOT pick to show / hide the map's own vector-tile incidents overlay ("show the traffic incidents overlay", "hide accident icons") — that is setMapStyling. Co-pick analyseData (aggregations / charts / clusters; pass \`monitor: { entryId }\` for the recurring spec) and focusIncidents (highlight a subset) when the query implies them.`,inputSchema:getTrafficIncidentsSchema,outputSchema:getTrafficIncidentsOutputSchema,execute:async(e,t,r)=>{const{where:o,categoryFilter:a,timeValidityFilter:s,label:n,show:i=!0,fitBounds:l=!0,monitor:c,monitorIntervalMs:d}=e,u=o??{mode:"within",viewport:!0},h={...a&&{categoryFilter:a},...s&&{timeValidityFilter:s}};try{const e=await(async(e,t,r)=>{const o=[];if(void 0!==e.range){const r=((e,t)=>{const r=getRangePolygons(e,t);return"error"in r?r:{bboxes:r.polygons.map(e=>bboxFromGeoJSON(e)).filter(e=>!!e)}})(t,e.range);if("error"in r)return r;o.push(...r.bboxes)}const a={viewport:void 0===e.range&&e.viewport,boundingBox:e.boundingBox,queries:e.queries,placeIds:e.placeIds,geometries:e.geometries,route:e.route},s=await resolveWithin(a,toolStateToWhereContext(t,r));if(isResolveError(s))return s;o.push(...s.map(e=>e.bbox));const n=bboxFromBBoxes(o);return n?{bbox:n,...resolvedAreasOf(s)}:{error:"No area resolved from `where`."}})(u,t,r);if("error"in e)return e;const{bbox:o,routeLabel:a,resolvedAreas:s}=e,p=withAgentToolkitHeaders({...h,bbox:o,signal:r?.signal}),y=await trafficIncidentDetails(p),m={bbox:o,...h},f=Date.now(),g=n??((e,t,r,o)=>{const a=[];e.categoryFilter?.length&&a.push(`filtered: ${e.categoryFilter.join(",")}`),e.timeValidityFilter?.length&&a.push(`when: ${e.timeValidityFilter.join(",")}`);let s=o?.length?o.map(e=>e.matched).join(", "):void 0;if(void 0===s&&t&&(t.queries?.length?s=t.queries.map(e=>e.query).join(", "):r?s=`along ${r}`:t.range&&(s=t.range)),!s){const[t,r,o,a]=e.bbox;s=`bbox ${t.toFixed(2)},${r.toFixed(2)} → ${o.toFixed(2)},${a.toFixed(2)}`}return a.length?`${s} — ${a.join("; ")}`:s})(m,u,a,s);if(!y.features.length)return{count:0,entryId:await t.trafficIncidents.addIncidentsEntry([],m,g,f),entries:entriesSummary(t),...s&&{resolvedAreas:s}};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:o,capturedAt:f,label:g},{fetchIncidents:buildIncidentsFetcher(h)},{intervalMs:d,skipInitialTick:!0}),{count:y.features.length,entryId:b,entries:entriesSummary(t),monitoring:w,...s&&{resolvedAreas:s}}}catch(p){return toToolError("Failed to get traffic incidents",p)}},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","What roadworks are planned in Rotterdam?"],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:focusIncidentsSchema,outputSchema:focusIncidentsOutputSchema,execute:async(e,t)=>{const{incidentsEntryID:r,incidentIds:o,reason:a}=e;try{if(!t.trafficIncidents.entries.find(e=>e.id===r))return{error:missingEntry("incidents",r)};const e=t.trafficIncidents.setFocus(r,o,a);return 0===e.focusedCount?{error:`None of the ${o.length} requested ids exist on entry "${r}". The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.`}:{incidentsEntryID:r,focusedCount:e.focusedCount,droppedIds:e.droppedIds,...a&&{reason:a}}}catch(s){return toToolError("Failed to focus incidents",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??ALL_ENTRY_DATA_KINDS,{active:o}=resolveClusterKinds(r,e.scope);return{description:buildClusterIncidentsDescription(o),inputSchema:buildClusterIncidentsSchema(r,e.scope),outputSchema:clusterIncidentsOutputSchema,execute:executeClusterIncidents,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildEntryKindScopePrompt(r,'— 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"] }`.'),...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:monitorAnalysisSchema,outputSchema:monitorAnalysisOutputSchema,execute:async(e,t)=>{const r=((e,t,r)=>{const o=e.analyses.job(t);return!!o&&(o.setActive(r),!0)})(t,e.analysisId,e.enabled);return!r&&t.analyses.get(e.analysisId)?e.enabled?{error:`Analysis "${e.analysisId}" reads only reachable ranges, which cannot anchor monitoring.`}:{analysisId:e.analysisId,enabled:!1}:r?{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:(e=>t=>{const r=t.enabledDataKinds??ALL_ENTRY_DATA_KINDS;return{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.',inputSchema:buildCreateTrackerSchema(r,t.scope),outputSchema:createTrackerOutputSchema,execute:executeCreateTracker,scopeSchema:buildEntryKindScopeSchema(r),scopePrompt:buildEntryKindScopePrompt(r,'listing only the entry kinds the watched condition reads (e.g. ["incidents", "places"] for incidents near hospitals).'),...e}})({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.',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.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:clearTrackerSchema,outputSchema:clearTrackerOutputSchema,execute:async(e,t)=>{const r=t.trackers.trackers;if(0===r.length)return{error:"No trackers to clear."};const o=e.trackerId??(1===r.length?r[0].id:void 0);if(void 0===o)return{error:`Which tracker? Pass trackerId — one of: ${r.map(e=>e.id).join(", ")}.`};const a=t.trackers.get(o);return a?(t.trackers.unregister(o),{cleared:!0,trackerId:o,name:a.name}):{error:`No tracker "${o}". Trackers: ${r.map(e=>e.id).join(", ")}.`}},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:getTrackerHistorySchema,outputSchema:getTrackerHistoryOutputSchema,execute:async(e,t)=>{const{sinceMs:r}=e,o=[...t.trackers.log({trackerId:e.trackerId})].sort((e,t)=>e.at-t.at),a=[...void 0===r?o:o.filter(e=>e.at>=r)].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}})),s=o.at(-1);return{events:a,count:a.length,...s&&{coverage:{oldestRetainedAt:oldestRetainedAt(o),newestAt:s.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:getTrackersSchema,outputSchema:getTrackersOutputSchema,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:setTrafficIncidentsMonitorSchema,outputSchema:setTrafficIncidentsMonitorOutputSchema,execute:async(e,t)=>{const{incidentsEntryID:r,enabled:o,intervalMs:a}=e;try{const e=t.trafficIncidents.entries.find(e=>e.id===r);if(!e)return{error:missingEntry("incidents",r)};if(!o){const e=t.trafficIncidents.isMonitored(r);return t.trafficIncidents.stopMonitoring(r),{incidentsEntryID:r,enabled:!1,alreadyInState:!e}}const s=t.trafficIncidents.isMonitored(r);if(!s&&!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{bbox:n}=e.params;return t.trafficIncidents.startMonitoring(r,{bbox:n,capturedAt:e.timestamp,label:e.label},{fetchIncidents:buildIncidentsFetcher(e.params)},a?{intervalMs:a}:void 0),{incidentsEntryID:r,enabled:!0,alreadyInState:s}}catch(s){return toToolError("Failed to update the incidents monitor",s)}},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:startRouteMonitorSchema,outputSchema:startRouteMonitorOutputSchema,execute:async(e,t)=>{const{routesEntryID:r,intervalMs:o}=e;try{const e=t.routing.entries.find(e=>e.id===r);if(!e)return{error:missingEntry("routes",r)};const a=t.routing.isMonitored(r),{waypoints:s,params:n}=e;return t.routing.startMonitoring(r,{recalculate:buildRouteRecalc(s,n)},o?{intervalMs:o}:void 0),{routesEntryID:r,alreadyRunning:a}}catch(a){return toToolError("Failed to start the route monitor",a)}},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:stopRouteMonitorSchema,outputSchema:stopRouteMonitorOutputSchema,execute:async(e,t)=>{const{routesEntryID:r}=e;try{if(!t.routing.entries.find(e=>e.id===r))return{error:missingEntry("routes",r)};const e=t.routing.isMonitored(r);return t.routing.stopMonitoring(r),{routesEntryID:r,wasRunning:e}}catch(o){return toToolError("Failed to stop the route monitor",o)}},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. The `hours` filter and every returned day and hour label are UTC.",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:getTrafficAreaAnalyticsSchema,outputSchema:getTrafficAreaAnalyticsOutputSchema,execute:async(e,t,r)=>{const{location:o,bbox:a,showOnMap:s,startDate:n,endDate:i,days:l,metrics:c,functionalRoadClasses:d,hours:u}=e;try{const e="undefined"==typeof process?void 0:process.env.MOVE_PORTAL_KEY;if(!e)return{error:"MOVE_PORTAL_KEY environment variable is not set."};const h=await(async(e,t,r,o)=>{if(e){const t=await resolveLocationInput(e,r,o);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{place:a}=t,s=(await toolStateToWhereContext(r,o).fetchGeometry("placeIdOrEntryId"in e?a.id:a))?.geometry;return s||{error:`No boundary polygon found for "${t.name}". Try a bbox instead.`}}if(!t)return{error:"Provide a location or bbox to define the analytics area."};const[a,s,n,i]=t;return polygonFromBBox([a,s,n,i]).geometry})(o,a,t,r);if("error"in h)return h;const p={...resolveDateParams(l,n,i),metrics:c,functionalRoadClasses:d??"all",hours:u??"all"},y=withAgentToolkitHeaders({apiKey:e,...p,geometry:h,signal:r?.signal}),m=await trafficAreaAnalytics(y),f={start:isoDate(m.properties.startDate),end:isoDate(m.properties.endDate)},g=`${m.features[0]?.properties?.name??"analytics"} (${f.start} → ${f.end})`,b=await t.trafficAreaAnalytics.addEntry(m,g,p);return s&&await(async(e,t)=>{await e.trafficAreaAnalytics.showEntry(t);const r=e.trafficAreaAnalytics.findById(t);r&&fitMapToGeoJSON(e.baseMap.mapLibreMap,r.data.features,{pitch:45})})(t,b),((e,t,r)=>{const o=t.features[0]?.properties;if(!o)return{entryId:e,dateRange:r,baseData:{},metrics:[],tileCount:0,availableGranularities:[]};const a=GRANULARITIES.filter(e=>o.timedData?.[e]?.length);return{entryId:e,...o.name&&{name:o.name},dateRange:r,baseData:o.baseData,metrics:t.properties?.metrics??[],tileCount:o.tiledData?.tiles?.length??0,availableGranularities:a}})(b,m,f)}catch(h){return toToolError("Failed to get traffic area analytics",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'], startDate: '2025-06-02', hours: [5, 6, 7, 15, 16] }) // Amsterdam summer rush hours [7, 8, 9, 17, 18] local (UTC+2), in UTC"],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 setMapStyling).",inputSchema:updatePlacesDisplaySchema,outputSchema:updatePlacesDisplayOutputSchema,execute:async(e,t,r)=>{const{clear:o,markerType:a,geometry:s,fitBounds:n}=e;try{const i=((e,t)=>{const{add:r,remove:o,addMatching:a,removeMatching:s,markerType:n,geometry:i}=t;let l=[...r??[],...entryIdsMatching(e.places.entries,a)];const c=[...o??[],...entryIdsMatching(e.places.entries,s)];if(void 0===r&&void 0===a&&(void 0!==n||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=c.includes(t)?[]:[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 i)return i;const{add:l,remove:c}=i,d=new Set(t.places.shownEntryIds);o&&(await t.places.clearShownEntries(),await t.places.clearShownGeometries()),c.length>0&&(await t.places.removeShownEntries(c),await t.places.clearShownGeometries(c));const u=void 0!==a||!s;let h;l.length>0&&u&&await(async(e,t,r)=>{const o=e.places.shownEntryIds,a=void 0!==r?t:t.filter(e=>!o.has(e));a.length>0&&await e.places.addShownEntries(a,r??"pin")})(t,l,a),s&&l.length>0&&(h=await(async(e,t,r,o)=>{const a=[];let s=0;for(const n of t){const t=await renderEntryGeometries(e,n,r,o);s+=t.rendered,a.push(...t.skipped)}return{rendered:s,skipped:a}})(t,l,s,r));const p=t.places.shownEntryIds,y=l.filter(e=>!d.has(e)&&p.has(e)),m=c.filter(e=>d.has(e)&&!p.has(e)),f=n??l.length>0,g=t.places.getShownFeatures();f&&fitMapToGeoJSON(t.baseMap.mapLibreMap,g);const b=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:b,actuallyAdded:y,actuallyRemoved:m,...h&&{geometriesRendered:h.rendered},...h?.skipped.length&&{geometriesSkipped:h.skipped}}}catch(i){return toToolError("updatePlacesDisplay failed",i)}},tags:["place","location","map style","boundary","outline"],examples:['updatePlacesDisplay({ add: ["places-2"] })','updatePlacesDisplay({ add: ["places-1", "places-3"], markerType: "base-map" })','updatePlacesDisplay({ remove: ["places-1"] })','updatePlacesDisplay({ removeMatching: ["cinema"] }) // hide every entry labelled "cinema"','updatePlacesDisplay({ addMatching: ["cafe"] })',"updatePlacesDisplay({ clear: true })",'updatePlacesDisplay({ clear: true, add: ["places-2"] }) // atomic swap: hide everything, show this','updatePlacesDisplay({ markerType: "pin" }) // re-pin the most recently stored entry',"updatePlacesDisplay({ geometry: {} }) // outline the most recently stored entry",'updatePlacesDisplay({ add: ["places-2"], geometry: { mode: "add" } }) // also outline on top of existing boundaries','updatePlacesDisplay({ add: ["places-2"], markerType: "pin", geometry: { fillStyle: "outline" } }) // pin AND outline','updatePlacesDisplay({ add: ["places-2"], geometry: { placeIds: ["G55fc4abe-..."], fillStyle: "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 and swap which routes entries are displayed on the map in one atomic call. Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). `clear` hides every currently-shown entry; combine with `add` to swap the display ("hide everything, show this"). `selectedIndex` switches the highlighted alternative; `focus` rings sections of the selected route — its tolls, ferries, traffic, curves, a leg, a named road — and zooms to them ("where are the tolls?", "zoom to the worst jam"); `waypoints: false` hides the pins. With no `add`/`addMatching`, these target the most recently stored entry. Does not delete from history. Does not call any service. The routes\' look (colour, line width, section styling, summary bubbles) is setMapStyling\'s `routes.*` knobs.',classificationPrompt:"Show, hide and swap routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, show or hide a calculated route's waypoint pins — and ring / zoom to sections of a route via `focus`: its tolls, ferries, traffic jams (the worst one), curves (the curviest stretch), a leg, a named road, the stretch only an alternative drives. NOT the routes' colour, width or summary bubbles (setMapStyling).",inputSchema:updateRoutesDisplaySchema,outputSchema:updateRoutesDisplayOutputSchema,execute:async(e,t)=>{const{clear:r,selectedIndex:o,focus:a,waypoints:s,fitBounds:n}=e;try{const i=((e,t)=>{const{add:r,remove:o,addMatching:a,removeMatching:s,selectedIndex:n,focus:i,waypoints:l}=t;let c=[...r??[],...entryIdsMatching(e.routing.entries,a)];const d=[...o??[],...entryIdsMatching(e.routing.entries,s)];if(void 0===r&&void 0===a&&(void 0!==n||void 0!==i||void 0!==l)){const t=e.routing.entries.at(-1)?.id;if(!t)return{error:"No routes available to display. Calculate a route first via setRoute."};c=d.includes(t)?[]:[t]}const u=new Set(e.routing.entries.map(e=>e.id)),h=c.filter(e=>!u.has(e));return h.length>0?{error:`Unknown routes entry ids: ${h.join(", ")}. Use recallState to list available entries.`}:{add:c,remove:d}})(t,e);if("error"in i)return i;const{add:l,remove:c}=i,d=void 0!==o&&t.routing.entries.find(e=>l.includes(e.id)&&o>=e.data.features.length);if(d)return{error:`Entry ${d.id} has ${d.data.features.length} route(s); selectedIndex ${o} is out of range.`};const u=void 0!==e.add||void 0!==e.addMatching,{beforeShown:h,afterShown:p}=await(async(e,t)=>{const r=new Set(e.routing.shownEntryIds);if(t.clear)for(const o of e.routing.shownEntryIds)t.add.includes(o)||await e.routing.hideEntry(o);for(const o of t.remove)await e.routing.hideEntry(o);for(const o of t.add)await displayAdded(e,o,t);if(void 0!==t.selectedIndex)for(const o of t.add)await(await e.routing.getEntryRoutingModule(o)).selectRoute(t.selectedIndex);return{beforeShown:r,afterShown:e.routing.shownEntryIds}})(t,{add:l,remove:c,clear:!!r,selectedIndex:o,waypoints:s,show:u||!(void 0===o&&!a&&!0!==s)}),y=void 0===a?void 0:await(async(e,t,r)=>{const o=[];for(const a of t){const t=await e.routing.getEntryRoutingModule(a.id),s=a.data.features[selectedRouteIndex(t)],n=r&&s?sectionsToFocus(s,r):[];a._sectionFocus=r?e=>sectionsToFocus(e,r).map(e=>e.id):void 0,await t.setSectionFocus(r?n.map(e=>e.id):null),o.push({entryId:a.id,sectionCount:n.length,lengthInMeters:Math.round(n.reduce((e,t)=>e+lengthOf(s,t),0)),bbox:bboxFromBBoxes(n.map(e=>bboxFromGeoJSON({type:"LineString",coordinates:s.geometry.coordinates.slice(e.startPointIndex,e.endPointIndex+1)})))})}return o})(t,t.routing.entries.filter(e=>l.includes(e.id)&&p.has(e.id)),a),m=l.filter(e=>!h.has(e)&&p.has(e)),f=c.filter(e=>h.has(e)&&!p.has(e)),g=bboxFromBBoxes(y?.map(e=>e.bbox)??[]);(n??(u&&l.length>0||void 0!==g))&&(g?fitMapToBBox(t.baseMap.mapLibreMap,g):((e,t)=>{const r=[];for(const o of e.routing.entries)t.has(o.id)&&r.push(...o.data.features);fitMapToGeoJSON(e.baseMap.mapLibreMap,r)})(t,p));const b=t.routing.entries.filter(e=>p.has(e.id)).map(e=>({id:e.id,label:e.label,routeCount:e.data.features.length,selectedIndex:selectedRouteIndex(e._module)}));return{success:!0,totalShown:p.size,shown:b,actuallyAdded:m,actuallyRemoved:f,...y&&{focused:y.map(({bbox:e,...t})=>t)}}}catch(i){return toToolError("updateRoutesDisplay failed",i)}},tags:["route","waypoint","map style"],examples:['updateRoutesDisplay({ add: ["routes-1"] })','updateRoutesDisplay({ add: ["routes-0", "routes-2"] })','updateRoutesDisplay({ remove: ["routes-1"] })','updateRoutesDisplay({ removeMatching: ["Brussels"] }) // hide every entry whose label mentions "Brussels"','updateRoutesDisplay({ addMatching: ["Edinburgh"] })',"updateRoutesDisplay({ clear: true })",'updateRoutesDisplay({ clear: true, add: ["routes-2"] }) // atomic swap: hide everything, show this',"updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route",'updateRoutesDisplay({ add: ["routes-0"], selectedIndex: 2, fitBounds: false })','updateRoutesDisplay({ focus: { sectionTypes: ["tollRoad"] } }) // ring and zoom to the tolls of the latest route','updateRoutesDisplay({ focus: { sectionTypes: ["traffic"], pick: "mostDelay" } }) // the worst jam','updateRoutesDisplay({ focus: { sectionTypes: ["tollRoad"], pick: 0 } }) // the first toll stretch','updateRoutesDisplay({ focus: { roads: ["A2"] } })','updateRoutesDisplay({ focus: { sectionTypes: ["curvyStretch"], pick: "curviest" } })','updateRoutesDisplay({ selectedIndex: 1, focus: { sectionTypes: ["distinct"] } }) // what sets alternative 1 apart',"updateRoutesDisplay({ focus: null }) // clear the ring","updateRoutesDisplay({ waypoints: false }) // the latest route without its pins"],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","Fit the map to the worst traffic section of the route","Fit the map to the first toll section of the route","Show me where the tolls are on my route","Highlight the A2 stretch of the route","Zoom to the curviest stretch of the route","Hide the start and destination pins of the route"],relatedTools:["setRoute","processData","recallState","clearMap"],dependsOn:["setRoute"]},updateTrafficAreaAnalyticsDisplay:{description:"Show, hide and swap 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. The cells' look (display mode, metric shown, palette, height, value filters) is setMapStyling's `traffic.areaAnalytics.*` knobs. Not real-time — the live vector-tile flow overlay is the setMapStyling `traffic.flow.visible` knob.",classificationPrompt:"Show / hide / swap historical traffic-area-analytics entries on the map: `add` / `remove` / `addMatching` / `removeMatching` / `clear`. NOT the cells' look — display mode, metric shown, palette, height, value filters (setMapStyling).",inputSchema:updateTrafficAreaAnalyticsDisplaySchema,outputSchema:updateTrafficAreaAnalyticsDisplayOutputSchema,execute:async(e,t)=>{try{const r=((e,t)=>{const{add:r,remove:o,addMatching:a,removeMatching:s}=t,n=[...r??[],...entryIdsMatching(e.trafficAreaAnalytics.entries,a)],i=[...o??[],...entryIdsMatching(e.trafficAreaAnalytics.entries,s)],l=new Set(e.trafficAreaAnalytics.entries.map(e=>e.id)),c=n.filter(e=>!l.has(e));return c.length>0?{error:`Unknown traffic-area-analytics entry ids: ${c.join(", ")}. Use recallState to list available ids.`}:{add:n,remove:i}})(t,e);if("error"in r)return r;const{add:o,remove:a}=r,s=new Set(t.trafficAreaAnalytics.shownEntryIds);if(e.clear)for(const e of t.trafficAreaAnalytics.shownEntryIds)o.includes(e)||await t.trafficAreaAnalytics.hideEntry(e);for(const e of a)await t.trafficAreaAnalytics.hideEntry(e);for(const e of o)await t.trafficAreaAnalytics.showEntry(e);const n=t.trafficAreaAnalytics.shownEntryIds,i=o.filter(e=>!s.has(e)&&n.has(e)),l=a.filter(e=>s.has(e)&&!n.has(e));if((e.fitBounds??o.length>0)&&n.size>0){const e=t.trafficAreaAnalytics.entries.filter(e=>n.has(e.id)).flatMap(e=>e.data.features);fitMapToGeoJSON(t.baseMap.mapLibreMap,e,{pitch:45})}const c=t.trafficAreaAnalytics.entries.filter(e=>n.has(e.id)).map(summarizeEntry);return{success:!0,totalShown:n.size,shown:c,actuallyAdded:i,actuallyRemoved:l}}catch(r){return toToolError("Failed to update traffic-area-analytics display",r)}},tags:["traffic","map style"],examples:['updateTrafficAreaAnalyticsDisplay({ add: ["tta-0"] })',"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","Also show the Amsterdam analytics","Hide the Amsterdam analytics","Replace what is on the map with this analytics"],relatedTools:["getTrafficAreaAnalytics","setMapStyling","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:updateWaypointsDisplaySchema,outputSchema:updateWaypointsDisplayOutputSchema,execute:async(e,t)=>{const r=e.visible??!0;try{const e=[...t.routing.shownEntryIds][0],o=t.routing.entries.at(-1),a=e??o?.id;if(!a)return{error:"No route history yet — calculate a route via setRoute first."};const s=await t.routing.getEntryRoutingModule(a);if(!r)return await s.clearWaypoints(),{success:!0,visible:!1,count:0};const n=t.routing.planningSlots;return await s.showWaypoints(n),{success:!0,visible:!0,count:n.filter(e=>null!==e).length}}catch(o){return toToolError("Failed to update waypoints display",o)}},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:clearMapSchema,outputSchema:clearMapOutputSchema,execute:async(e,t)=>{const{layers:r}=e,o=r&&r.length>0?r:CLEARABLE_SLICE_NAMES;try{for(const e of o)await t[e].clearShown();return{success:!0,cleared:[...o]}}catch(a){return toToolError("Failed to clear map",a)}},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:flyToSchema,outputSchema:flyToOutputSchema,execute:async(e,t)=>{const{where:r}=e;try{return"boundingBox"in r?fitMapToBBox(t.baseMap.mapLibreMap,r.boundingBox,void 0===r.padding?void 0:{padding:r.padding}):t.baseMap.mapLibreMap.flyTo({center:r.position,zoom:r.zoom??14}),{success:!0}}catch(o){return toToolError("flyTo failed",o)}},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 [west, south, east, north], west > east across the antimeridian. 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:getViewportSchema,outputSchema:getViewportOutputSchema,execute:async(e,t)=>{try{const e=t.baseMap.mapLibreMap;return{center:e.getCenter().wrap().toArray(),zoom:e.getZoom(),bbox:getViewportBoundingBox(t.baseMap),pitch:e.getPitch(),bearing:e.getBearing()}}catch(r){return toToolError("Failed to get viewport",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:addByodSourceSchema,outputSchema:addByodSourceOutputSchema,execute:async(e,t,r)=>{const o=e.url?await loadUrlSource(e.url,t,r?.signal):(e=>isFeatureCollectionLike(e)?{data:e,sourceRecord:{kind:"inline"}}:{error:'Payload is not a GeoJSON FeatureCollection (`{ type: "FeatureCollection", features: [...] }`).'})(e.data);if("error"in o)return o;const{data:a,sourceRecord:s}=o;if(r?.signal?.aborted)return{error:CANCELLED_ERROR};let n;try{n=await t.byod.addEntry(a,e.label,{source:s,...e.entryId&&{explicitId:e.entryId}})}catch(l){return toToolError("Failed to add the BYOD source",l)}const i=t.byod.findById(n);return i?{byodEntryId:n,label:e.label,source:s,profile:toByodSafeProfile(i.profile)}:{error:`addByodSource stored entry "${n}" 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 setLayerProperties (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:setByodLayersSchema,outputSchema:setByodLayersOutputSchema,execute:async(e,t)=>{const r=t.byod.findById(e.byodEntryId);if(!r)return{error:missingEntry("byod",e.byodEntryId)};const o=((e,t,r)=>{const o=new Set(e.byod.findById(t)?.layers.map(e=>e.id)),a=new Set(e.byod.entries.filter(e=>e.id!==t).flatMap(e=>e.layers.map(e=>e.id))),s=/* @__PURE__ */new Set,n=t=>s.has(t)?"another layer in this call":a.has(t)?"another BYOD entry":!o.has(t)&&e.baseMap.mapLibreMap.getLayer(t)?"a layer already on the map":void 0;for(const{id:i}of r){if(void 0===i)continue;const e=n(i);if(e)return`Layer id "${i}" is already used by ${e}. Pick another id, or omit it.`;s.add(i)}})(t,e.byodEntryId,e.layers);if(o)return{error:o};const a=r.layers,s=e.layers.map(({beforeID:e,...t})=>e?{...t,beforeLayerConfig:{layerID:e}}:t);try{t.byod.setEntryLayers(e.byodEntryId,s)}catch(i){try{t.byod.setEntryLayers(e.byodEntryId,a)}catch{}return{error:`Failed to apply layers to "${e.byodEntryId}": ${errorMessageOf(i)}. The entry keeps its previous layers — fix the layer specs and retry.`}}let n;if(e.show)try{n=await showByodOnMap(t,e.byodEntryId,e.show)}catch(i){return{error:`Layers applied to "${e.byodEntryId}" but rendering failed: ${errorMessageOf(i)}`}}return{byodEntryId:e.byodEntryId,label:r.label,layerCount:s.length,layerTypes:s.map(e=>e.type),...n&&{shown:n}}},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:updateByodDisplaySchema,outputSchema:updateByodDisplayOutputSchema,execute:async(e,t)=>{const r=t.byod,{action:o,clearAll:a,hideOthers:s}=e,n=e.entryIds??[],i="hide"===o&&a?void 0:((e,t)=>{for(const r of t)if(!e.findById(r))return{error:missingEntry("byod",r)}})(r,n);if(i)return i;try{let e;return e="show"===o?await(async(e,t,r)=>{const o=[];if(r){const r=new Set(t);for(const t of e.shownEntryIds)r.has(t)||(await e.hideEntry(t),o.push(t))}for(const a of t)await e.showEntry(a),o.push(a);return o})(r,n,!!s):"hide"===o?await(async(e,t,r)=>{const o=[],a=r?e.shownEntryIds:t;for(const s of a)await e.hideEntry(s),o.push(s);return o})(r,n,!!a):await(async(e,t)=>{const r=[];for(const o of t)await e.removeEntry(o),r.push(o);return r})(r,n),{action:o,affectedIds:e,shown:[...r.shownEntryIds]}}catch(l){return toToolError("Failed to update BYOD display",l)}},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:getCurrentWaypointsSchema,outputSchema:getCurrentWaypointsOutputSchema,execute:async(e,t)=>{const{slotIndex:r,includeEmptySlots:o=!1,offset:a=0,limit:s=5}=e,n=t.routing.planningSlots.map((e,t)=>{const r=summarizeWaypoint(e);return r?{slotIndex:t,isFilled:!0,...r}:{slotIndex:t,isFilled:!1}}),i=o?n:n.filter(e=>e.isFilled),l=void 0!==r,c=l?n.filter(e=>e.slotIndex===r):i.slice(a,a+s),d=a+c.length;return{totalSlots:n.length,filledSlots:n.filter(e=>e.isFilled).length,matchedCount:l?c.length:i.length,returnedCount:c.length,offset:l?0:a,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:"List the standard map styles (the base-map themes: light, dark, satellite…) that setMapStandardStyle switches to.",classificationPrompt:"List available standard map style IDs for use with setMapStandardStyle.",inputSchema:getStandardMapStylesSchema,outputSchema:getStandardMapStylesOutputSchema,execute:async(e,t)=>({styles:standardStyleIDs}),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 (map colours, sizes, a theme, a preset, terrain) use setMapStyling.",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:setMapStandardStyleSchema,outputSchema:setMapStandardStyleOutputSchema,execute:async(e,t)=>{const{style:r}=e;try{return await t.baseMap.ttMap.setStyle(r),{success:!0,style:r}}catch(o){return toToolError("Failed to set map style",o)}},tags:["map style"],examples:['setMapStandardStyle("streetDark")','setMapStandardStyle("streetSatellite")'],examplePrompts:["Switch to dark mode","Use the satellite view","Change to the light theme","Use the driving map style","Use the monochrome dark style","Switch to the dark driving map"],relatedTools:["getStandardMapStyles","setLanguage","setMapStyling"]},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:setLanguageSchema,outputSchema:setLanguageOutputSchema,execute:async(e,t)=>{const{language:r}=e;try{return TomTomConfig.instance.put({language:r}),t.baseMap.ttMap.setLanguage(r),{success:!0,language:r}}catch(o){return toToolError("Failed to set language",o)}},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","Show the traffic incident descriptions in Dutch"],relatedTools:["setMapStandardStyle","setGeopoliticalView"]},setGeopoliticalView:{description:"Set whose perspective disputed territories are shown from: the borders and the territory and sea names the map draws, and the country a disputed place belongs to in subsequent search and reverse-geocoding results. Not the label language (setLanguage).",classificationPrompt:"Show disputed borders and territory names from a country's perspective (or the international / default view). NOT the label language (setLanguage).",inputSchema:setGeopoliticalViewSchema,outputSchema:setGeopoliticalViewOutputSchema,execute:async(e,t)=>{const{geopoliticalView:r}=e;try{return TomTomConfig.instance.put({geopoliticalView:r}),t.baseMap.ttMap.setGeopoliticalView(r),{success:!0,geopoliticalView:r}}catch(o){return toToolError("Failed to set geopolitical view",o)}},tags:["map style"],examples:['setGeopoliticalView({ geopoliticalView: "IN" })','setGeopoliticalView({ geopoliticalView: "Unified" })',"setGeopoliticalView({})"],examplePrompts:["Show the borders as India sees them","Use the international view for disputed territories","Reset the disputed borders to the default view","Show Kashmir the way Pakistan maps it"],relatedTools:["setLanguage"]},setMapStyling:(e=>({scope:t})=>({...e,description:"Set what the map shows and how it looks, by knob id: whole base-map layer groups (groups.<group>.visible: water, roads, 2D/3D buildings, road shields, road/city/country labels…; `visible` for the whole base map), road markings (exit numbers, arrows), the built-in POI icons (pois.visible, which categories show, their zoom and label colours — not the places the agent found), the live traffic flow and incident overlays (traffic.flow.visible, traffic.incidents.visible, planned incidents with traffic.incidents.timeValidity, their road and delay filters and colours), the hillshade and 3D terrain elevation (tilt the camera with setPitchBearing to see the relief), label/icon/road size factors, the ten map colours (colors.land/water/vegetation/park/artificial/roadMajor/road/roadOutline/label/labelHalo — one name recolours every derived shade, so for a dark palette switch to a dark standard style first and recolour from there), a whole theme from a few colours or an image (`theme`: a brand, a website's colours, an artist's palette, a described mood — turn it into three to six hex colours first), globe projection and sky, or a whole preset (data-viz, night-driving, minimal, globe). It also styles what the agent drew, every layer of a kind, shown now or later: found places (places.*: pin colour, labels, clusters), routes (routes.*: colour, width, waypoints, section styling), polygons and place boundaries (geometries.*), reachable areas (ranges.*), area analytics cells (traffic.areaAnalytics.*: display mode, the metric shown, its palette, height and value filters) and fetched incidents (traffic.incidentDetails.*). Showing or hiding those, a places entry's marker type and a polygon's fill style are the update*Display tools' job. Durable across style switches; returns every knob set afterwards. `colors.accent` is the colour routes and place pins default to, so a theme reaches them too; set `routes.color` or `places.color` only for a specific colour.",inputSchema:setMapStylingSchemaFor(t?.kinds),outputSchema:setMapStylingOutputSchema,execute:executeSetMapStyling,scopeSchema:knobPartScopeSchema,scopePrompt:"Emit { kinds: [...] }, the knob families the turn touches: 'look' (label/icon/road sizes, the map colours, globe and sky, a theme or preset), 'baseMap' (show/hide whole base-map layer groups, road markings), 'pois' (the built-in POI icons and their categories), 'trafficFlow' (the live flow overlay), 'trafficIncidents' (the live incident overlay), 'terrain' (hillshade, 3D elevation); and the look of what the agent drew: 'places' (found places' pins, labels, clusters), 'routes' (route colour, width, waypoints, sections), 'geometries' (polygons, place boundaries), 'ranges' (reachable areas), 'trafficAreaAnalytics' (the analytics cells: display mode, metric shown, palette, height, filters), 'trafficIncidentDetails' (fetched incidents' highlight)."}))({classificationPrompt:"Set what the base map shows and how it looks — show/hide whole layer groups (3D buildings, road shields, road / city / country labels, house numbers, water, railways… or the whole base map), the built-in POI icons and which categories show, the vector-tile traffic overlays — live flow / congestion, and the incident markers (jams, accidents, closures, planned ones too) — and their filters (NOT the getTrafficIncidents fetch, nor the historical area analytics), exit numbers, road arrows, the hillshade, 3D terrain elevation; bigger/smaller labels or icons, thicker roads, POI label colour or zoom, traffic line colours, map colours, a globe projection with a sky and its sky/horizon/space colours, a map preset, or a whole theme from a few colours or an image (a brand, a website's look, an artist's palette, a mood) — or reset those to the style defaults. Also the look of every layer the agent drew: the found places' pin colour and labels, the route colour, width, section styling (tolls, curves… drawn or not) and summary bubbles (ETA labels, shown or not), polygons, reachable areas, the area analytics cells (display mode, metric shown, palette, height, value filters — “only show the cells above X” hides the rest in place, no new entry via processData) and fetched incidents. NOT showing or hiding those (update*Display), NOT for switching the whole style (setMapStandardStyle).",tags:["map style","traffic","place"],examples:['setMapStyling({ set: { "labels.sizeFactor": 1.3 } })','setMapStyling({ set: { "groups.buildings3D.visible": true } })','setMapStyling({ set: { "groups.roadShields.visible": false, "groups.houseNumbers.visible": false } })','setMapStyling({ set: { "traffic.flow.visible": true } })','setMapStyling({ set: { "traffic.incidents.visible": true, "traffic.incidents.filters.magnitudes.show": "only", "traffic.incidents.filters.magnitudes.values": ["major"] } })','setMapStyling({ set: { "traffic.incidents.visible": true, "traffic.incidents.timeValidity": ["present", "future"] } })','setMapStyling({ set: { "pois.filters.categories.show": "only", "pois.filters.categories.values": ["RESTAURANT", "CAFE_PUB"] } })','setMapStyling({ set: { "pois.visible": false } })','setMapStyling({ set: { "roads.exitNumbers.visible": false, "roads.arrows.visible": false } })','setMapStyling({ set: { "traffic.flow.colors.slow": "#f59e0b", "traffic.flow.colors.stationary": "#7f1d1d" } })','setMapStyling({ set: { "view.projection": "globe", "view.sky.visible": true } })','setMapStyling({ set: { "terrain.elevation.visible": true, "terrain.elevation.exaggeration": 1.5, "terrain.hillshade.visible": true } })','setMapStyling({ set: { "colors.water": "#0fa8c8", "colors.roadMajor": "#f59e0b" } })','setMapStyling({ theme: { colors: ["#0b2a3a", "#0fa8c8", "#f6efe3", "#d9822b"] } })','setMapStyling({ theme: { colors: ["#1d3f8a", "#e63312", "#f5d000", "#f4f1ea", "#111111"], mode: "light" } })','setMapStyling({ theme: { imageUrl: "https://example.com/brand/logo.png" } })','setMapStyling({ preset: "data-viz" })','setMapStyling({ set: { "routes.color": "coral", "routes.widthFactor": 1.5 } })','setMapStyling({ set: { "routes.sections.tollRoad.color": "#d97706", "routes.sections.ferry.pattern": "dashed" } })','setMapStyling({ set: { "routes.summaryBubbles.visible": false } })','setMapStyling({ set: { "routes.sections.curve.visible": true, "routes.sections.curve.color": "purple" } })','setMapStyling({ set: { "places.color": "#7c3aed", "places.label.size": 14 } })','setMapStyling({ set: { "geometries.color": "teal", "geometries.fill.opacity": 0.4 } })','setMapStyling({ set: { "traffic.areaAnalytics.displayMode": "heatmap", "traffic.areaAnalytics.activeMetric": "speed", "traffic.areaAnalytics.metrics.speed.palette": "heat" } })','setMapStyling({ set: { "traffic.areaAnalytics.metrics.congestionLevel.filters.min": 50 } })','setMapStyling({ set: { "traffic.areaAnalytics.metrics.congestionLevel.height.scaleMode": "currentRange", "traffic.areaAnalytics.metrics.congestionLevel.height.maxMeters": 200 } })',"setMapStyling({ reset: true })"],examplePrompts:["Make the map labels bigger","Hide the exit numbers and road arrows","Use orange for slow traffic and dark red for stationary traffic","Show the map as a globe","Make the water teal and the motorways orange","Use our brand colours on the map: dark land, light labels","Theme the map like a Miró painting","Style the map in warm earthy tones","Make the map match our website: navy, coral and off-white","Make the base map quieter so my data stands out","Reset the map styling","Show the mountains in 3D","Turn off the hillshade","Give the horizon a warm sunset colour","Show 3D buildings","Hide the road labels","Hide the route shields and house numbers","Bring back all the base map layers","Show traffic flow","Turn off traffic colors","Show traffic incidents overlay","Hide accident icons","Include the planned closures in the traffic incidents overlay","Hide all map POI icons","Show only restaurant icons on the map style","Turn off parking icons in the base map","Make the route line red","Change the route color to coral","Make the route thicker","Show toll roads on the route in orange","Hide the route summary bubbles","Draw the route's curves in purple","Make the place pins purple","Make the area outlines teal","Only show the analytics cells with congestion above 50%","Normalize height to the current data range","Switch to heatmap view","Colour the cells by free-flow speed instead"],relatedTools:["setMapStandardStyle","getTrafficIncidents","updatePlacesDisplay","updateRoutesDisplay","updateTrafficAreaAnalyticsDisplay"]}),getMapStyleLayers:{description:"Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayerProperties, for styling no setMapStyling knob covers.",classificationPrompt:"List MapLibre layer IDs with their paint/layout properties for custom styling.",inputSchema:getMapStyleLayersSchema,outputSchema:getMapStyleLayersOutputSchema,execute:async(e,t)=>{const{layerIdQuery:r,include:o}=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,...o.includes("paint")&&{paint:e.paint},...o.includes("layout")&&{layout:e.layout}}))}}catch(a){return toToolError("Failed to get style",a)}},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:["setLayerProperties"]},setLayerProperties:{description:"Set MapLibre paint (colours, widths, opacity) and layout (visibility, text-field, sizes, fonts) properties on named layers — use getMapStyleLayers for valid IDs. For the routes, places and other layers the agent drew prefer setMapStyling's `routes.*`, `places.*`… knobs. The map colours (land, water, vegetation, parks, roads, labels), label and icon sizes, traffic colours and whole layer groups (road labels, shields, 3D buildings…) are setMapStyling knobs, which survive style switches — use this only for what those knobs do not cover.",classificationPrompt:"Set MapLibre paint (colours, widths, opacity, dashes) or layout (visibility, text-field, fonts, text size) properties on named base-map style layers no setMapStyling knob covers (borders, buildings, railways, one label layer…). NOT for the map colours of land, water, vegetation, parks, roads or labels, nor label/icon sizes or whole layer groups (setMapStyling). NOT for loaded BYOD / imported / customer layers — restyle those with setByodLayers.",inputSchema:setLayerPropertiesSchema,outputSchema:setLayerPropertiesOutputSchema,execute:async(e,t)=>{try{return{results:e.changes.flatMap(({layerId:e,...r})=>propertyGroups.flatMap(o=>Object.entries(r[o]??{}).map(([r,a])=>{try{return((e,t,{group:r,propertyName:o,value:a})=>{const s=e.baseMap.mapLibreMap;if(!s.getLayer(t))throw new Error(`no layer "${t}" in the current style`);if("paint"===r){const e=o;s.setPaintProperty(t,e,a)}else{const e=o;s.setLayoutProperty(t,e,a)}})(t,e,{group:o,propertyName:r,value:a}),{layerId:e,group:o,propertyName:r,value:a,success:!0}}catch(s){return{layerId:e,group:o,propertyName:r,error:`Failed to set ${o} property: ${errorMessageOf(s)}`}}})))}}catch(r){return toToolError("Failed to set layer properties",r)}},tags:["map style"],examples:['setLayerProperties({ changes: [{ layerId: "admin-boundary", paint: { "line-color": "#ff69b4" } }] })','setLayerProperties({ changes: [{ layerId: "road-label", layout: { visibility: "none" } }] })','setLayerProperties({ changes: [{ layerId: "poi-label", paint: { "text-color": "#333" }, layout: { "text-size": 14 } }] })'],examplePrompts:["Make the country borders pink","Set the road-label layer's visibility property to none","Make buildings semi-transparent","Make the country borders dashed","Change the text size on building labels","Use a bold font for the road labels"],relatedTools:["getMapStyleLayers","setMapStyling","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:setPitchBearingSchema,outputSchema:setPitchBearingOutputSchema,execute:async(e,t)=>{const{pitch:r,bearing:o}=e;if(void 0===r&&void 0===o)return{error:"Provide at least one of pitch or bearing"};try{return t.baseMap.mapLibreMap.easeTo({...void 0!==r&&{pitch:r},...void 0!==o&&{bearing:o}}),{success:!0}}catch(a){return toToolError("Failed to set pitch/bearing",a)}},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:zoomInOrOutSchema,outputSchema:zoomInOrOutOutputSchema,execute:async(e,t)=>{const{delta:r}=e;try{const e=t.baseMap.mapLibreMap,o=Math.min(Math.max(e.getZoom()+r,e.getMinZoom()),e.getMaxZoom());return e.zoomTo(o),{zoom:o}}catch(o){return toToolError("Zoom failed",o)}},tags:["map style"],examples:["zoomInOrOut({ delta: 2 })","zoomInOrOut({ delta: -1 })"],examplePrompts:["Zoom in","Zoom out 2 levels","Get a wider view"],relatedTools:["getViewport","flyTo"]},calculateBBox:{description:"Compute a `[west, south, east, north]` bbox from GeoJSON (longitude before latitude). Across the antimeridian west > east, and `minLng`/`maxLng` carry west and east.",classificationPrompt:"Compute a [west, south, east, north] bounding box from GeoJSON features or tool results.",inputSchema:calculateBBoxSchema,outputSchema:calculateBBoxOutputSchema,execute:async(e,t)=>{const{geoJson:r}=e;try{const e=bboxFromGeoJSON(r);return 4!==e?.length||!e.every(Number.isFinite)||Math.abs(e[1])>90||Math.abs(e[3])>90?{error:"Could not calculate bounding box - input may be empty or invalid GeoJSON"}:{bbox:e,minLng:e[0],minLat:e[1],maxLng:e[2],maxLat:e[3]}}catch(o){return toToolError("Failed to calculate bbox",o)}},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:createGetCurrentLocationTool(),clarifyIntent:createClarifyIntentTool(),help:{description:'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.',classificationPrompt:"Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.",inputSchema:helpSchema,outputSchema:helpOutputSchema,execute:async()=>({error:"help needs the resolved metadata of every tool, which createMapAgent binds; it cannot run on its own."}),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:findReachableAreasSchema,outputSchema:findReachableAreasOutputSchema,execute:async(e,t,r)=>{const{origins:o,budgets:a,fillStyle:s,showOnMap:n,showOriginPin:i=!0,hidePreviousEntries:l}=e;try{const e=[...a].sort((e,t)=>t.value-e.value),{ranges:c,skipped:d,errors:u}=await(async(e,t,r,o)=>{const a=[],s=[],n=[];for(const i of e){const e=await rangeForOrigin(i,t,r,o);"range"in e?a.push(e.range):(s.push(e.skipped),"error"in e&&n.push(e.error))}return{ranges:a,skipped:s,errors:n}})(o,e,t,r);if(0===c.length)return u.length?toToolError("Reachable area calculation failed",u[0]):{status:"no_results"};const h=e.filter(e=>c.some(t=>t.budgets.some(t=>t.type===e.type&&t.value===e.value))),p=await t.ranges.addEntry({label:makeRangesLabel(h,c.map(formatOriginName)),data:c}),y=n||i;return y&&await hidePreviousShownEntries(t.ranges,[p],l),await(async(e,t,r,o,a,s)=>{if(a){const a=(e=>{const t=e.flatMap(e=>e.polygon?.features??[]);return 0===t.length?null:{type:"FeatureCollection",features:t}})(r);a&&(await(await e.ranges.getEntryRangesModule(t,o)).show(a),fitMapToGeoJSON(e.baseMap.mapLibreMap,a))}if(s){const o=await e.ranges.getEntryPlacesModule(t),a=r.map((e,r)=>({type:"Feature",id:`range-origin-${t}-${r}`,geometry:{type:"Point",coordinates:e.origin.position},properties:{type:"Point Address",address:{freeformAddress:formatOriginName(e)}}}));await o.show(a)}})(t,p,c,s??"outline",n,i),y&&await t.ranges.markEntryShown(p),{rangesId:p,ranges:c.map(e=>({originName:formatOriginName(e),budgets:e.budgets})),...d.length&&{skipped:d}}}catch(c){return toToolError("Reachable area calculation failed",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, fillStyle: "inverted" })','findReachableAreas({ origins: [{ position: [4.89, 52.373] }], budgets: [{ type: "distanceKM", value: 50 }], showOnMap: false }) // from the getCurrentLocation position'],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,{...recallStateMetadata,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:recallStateSchema,outputSchema:z.union([snapshotSchema,buildRecallPlacesOutputSchema(),recallRoutesOutputSchema,recallRangesOutputSchema,recallByodOutputSchema,recallGeometriesOutputSchema,recallIncidentsOutputSchema,recallTrafficAreaAnalyticsOutputSchema,toolErrorSchema]),execute:buildExecuteRecallState()}),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 "streetLight". 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:resetStateSchema,outputSchema:resetStateOutputSchema,execute:async(e,t)=>{try{for(const r of CLEARABLE_SLICE_NAMES)await t[r].clearShown();t.mapPOIs.poisModule?.resetConfig(),t.trafficTiles.trafficFlowModule?.resetConfig(),t.trafficTiles.trafficIncidentsModule?.resetConfig();for(const r of ENTRY_MODE_SLICE_NAMES)t[r].reset();t.analyses.reset(),t.trackers.reset(),t.engine.reset();const e=/* @__PURE__ */new Set([...ENTRY_MODE_SLICE_NAMES.map(e=>t[e]),t.analyses,t.trackers,t.engine,t.mapPOIs,t.trafficTiles,t.baseMap]);return resetSlices(t,e),await t.baseMap.ttMap.setStyle("streetLight",{resetState:!0}),{success:!0,cleared:[...CLEARED_SLICES]}}catch(r){return toToolError("Failed to reset state",r)}},tags:["state","reset","destructive"],examples:["resetState()"],examplePrompts:["Start over","Reset everything","Clear all my data and the map"],relatedTools:["recallState","clearMap"]},setEntryMode:{description:setEntryModeDescription,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. OWNS "let me overlay / keep several searches (routes, ranges…) on the map" and "only show one at a time", even when no new search is asked for — pick it, not discoverPlaces, unless the turn also names what to search.',inputSchema:setEntryModeSchema,outputSchema:setEntryModeOutputSchema,execute:async(e,t)=>{const{slice:r,mode:o}=e;try{const e=t[r],a=e.entries.map(e=>e.id);await e.setEntryMode(o);const s=new Set(e.entries.map(e=>e.id));return{success:!0,slice:r,mode:o,droppedEntryIds:a.filter(e=>!s.has(e))}}catch(a){return toToolError("Failed to set entry mode",a)}},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"]}},DEFAULT_TOOLS=DEFAULT_TOOLS_TYPED,TOOL_NAMES=Object.keys(DEFAULT_TOOLS),TOOLS_BY_DATA_ENTRY_KIND={places:["updatePlacesDisplay","locatePlace","discoverPlaces","reverseGeocode"],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"]},getDefaultToolPrompts=()=>{const e={};for(const t of TOOL_NAMES){const r=DEFAULT_TOOLS[t];e[t]=("function"==typeof r?r({}):r).examplePrompts??[]}return e},rejectingUnknownKeys=e=>{if(!(e instanceof z.ZodObject))return e;const t=z.globalRegistry.get(e),r=e.strict();return t?r.meta(t):r},toAiTool=(e,t,r,o,a,s)=>({description:t.description,inputSchema:r,outputSchema:a?t.outputSchema:void 0,execute:s?async(r,a)=>{const n=Date.now();let i;try{i=await t.execute(r,o,{signal:a?.abortSignal})}catch(c){throw s({toolName:e,durationMs:Date.now()-n,isError:!0,errorMessage:errorMessageOf(c)}),c}const l=(e=>{const t=e?.error;return"string"==typeof t?t:void 0})(i);return s({toolName:e,durationMs:Date.now()-n,isError:void 0!==l,errorMessage:l}),i}:(e,r)=>t.execute(e,o,{signal:r?.abortSignal})}),toMetadata=(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}),materializeEntry=(e,t)=>"function"==typeof e?e(t):e,createMapAgent=(e,t)=>{if(!t.model)throw new Error("MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.");e._setTomTomUserAgent(AGENT_TOOLKIT_USER_AGENT);const r=((e,t)=>{const r={places:new PlacesState(e),mapPOIs:new MapPOIsState(e),routing:new RoutingState(e),baseMap:new BaseMapState(e),trafficTiles:new TrafficTilesState(e),trafficAreaAnalytics:new TrafficAreaAnalyticsState(e),trafficIncidents:new TrafficIncidentsState(e),ranges:new RangeState(e),customGeometries:new CustomGeometriesState(e),byod:new BYODState(e),codeExecution:resolveSandboxExecutor(),engine:new JobEngine,analyses:new Analyses,trackers:new EventsState};return t&&Object.assign(r,t),r.engine.ensureWired(r),r})(e,t.state);if(t.byod?.validateSourceUrl&&(r.byod.sourceUrlValidator=t.byod.validateSourceUrl),r.codeExecution=resolveSandboxExecutor(t.codeExecution),t.dataEntries)for(const[v,E]of Object.entries(t.dataEntries)){const e=v;E?.entryMode&&r[DATA_ENTRY_KIND_TO_SLICE[e]].setEntryMode(E.entryMode)}const o=e=>!1!==t.dataEntries?.[e]?.enabled,a=Object.keys(DATA_ENTRY_KIND_TO_SLICE).filter(e=>!o(e)),s=ALL_ENTRY_DATA_KINDS.filter(e=>o(e)),n=new Set(a.flatMap(e=>TOOLS_BY_DATA_ENTRY_KIND[e])),i=!1===t.includeDefaultTools?{}:DEFAULT_TOOLS,l=Object.fromEntries(Object.entries(i).filter(([e])=>!n.has(e))),c=resolveTools(l,t.tools),{tools:d,toolsMetadata:u,scopableTools:h}=((e,t,r)=>{const o=!1!==r?.outputSchemas,a={featureFlags:r?.featureFlags,enabledDataKinds:r?.enabledDataKinds},s={},n={},i={};for(const[l,c]of Object.entries(e)){const e=materializeEntry(c,a),d=rejectingUnknownKeys(e.inputSchema);if(s[l]=toAiTool(l,e,d,t,o,r?.onToolExecute),n[l]=toMetadata(l,e),"function"==typeof c&&e.scopeSchema){const t=c,r=e.scopeSchema;i[l]={applyScope:e=>{const o=r.safeParse(e);if(!o.success)return null;try{const e=t({...a,scope:o.data});return{description:e.description,inputSchema:rejectingUnknownKeys(e.inputSchema)}}catch{return null}},defaultDescription:e.description,defaultInputSchema:d}}}if(e.help&&e.help===DEFAULT_TOOLS.help){const i={...materializeEntry(e.help,a),execute:e=>(async(e,t)=>{const{mode:r,query:o,tag:a}=e;try{const e=Object.values(t);if("summary"===r)return{mode:"summary",examplePrompts:e.flatMap(e=>e.examplePrompts??[])};let s=e;if(a&&(s=s.filter(e=>e.tags?.includes(a)??!1)),o){const e=o.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:o,tag:a,matchedTools:s.map(e=>({name:e.name,description:e.description,examplePrompts:e.examplePrompts??[],relatedTools:e.relatedTools??[]})),totalMatched:s.length}}catch(s){return toToolError("Failed to load help",s)}})(e,n)};s.help=toAiTool("help",i,rejectingUnknownKeys(i.inputSchema),t,o,r?.onToolExecute)}if(0===Object.keys(s).length)throw new Error("MapAgent requires at least one tool.");return{tools:s,toolsMetadata:n,scopableTools:i}})(c,r,{outputSchemas:t.outputSchemas,featureFlags:t.featureFlags,enabledDataKinds:s,onToolExecute:t.onToolExecute}),p=!1===t.classifier?null:t.classifier??createDefaultClassifier({model:t.model}),y=buildSystemPrompt({customPrompt:t.systemPrompt,prefix:t.systemPromptPrefix,suffix:t.systemPromptSuffix}),m=Object.entries(u).filter(([,e])=>e.alwaysActive).map(([e])=>e),f=Object.entries(u).filter(([,e])=>e.endsTurnOnCall).map(([e])=>hasToolCall(e));let g=null;const b=(()=>{let e=Promise.resolve();return async t=>{const r=e;let o=()=>{};e=new Promise(e=>{o=e});try{return await r,await t()}finally{o()}}})(),w={tools:d,scopableTools:h,toolsMetadata:u,classifier:p,onClassify:t.onClassify},S=new ToolLoopAgent({model:t.model,tools:d,instructions:y,stopWhen:[isStepCount(t.maxSteps??10),...f],prepareStep:e=>b(async()=>{0===e.stepNumber&&(g=await(async(e,t)=>{((e,t)=>{for(const[r,o]of Object.entries(t)){const t=e[r];t&&(t.description=o.defaultDescription,t.inputSchema=o.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[o,a]of Object.entries(r)){const r=t[o];if(!r||null==a)continue;const s=e[o];if(!s)continue;const n=r.applyScope(a);n&&(s.description=n.description,s.inputSchema=n.inputSchema)}})(e.tools,e.scopableTools,r?.toolScopes),r})(w,e.messages));const r=unionActiveTools(g?.activeToolNames,m),o=await(t.prepareStep?.(e)),a=t.stepProviderOptions?.({activeTools:r,stepNumber:e.stepNumber}),s=((e,t,r)=>r?{...r,activeTools:intersectActiveTools(e,r.activeTools),providerOptions:mergeProviderOptions(t,r.providerOptions)}:{...e?{activeTools:e}:{},...t?{providerOptions:t}:{}})(r,a,o);return s?.activeTools?{...s,activeTools:unionActiveTools(s.activeTools,m)}:s}),providerOptions:t.providerOptions});return Object.assign(S,{state:r,getCodeExecutionMode:()=>r.codeExecution.getMode(),destroy:()=>(e=>{resetSlices(e),e.codeExecution?.destroy?.()})(r)})};export{AGENT_TOOLKIT_ID,AGENT_TOOLKIT_USER_AGENT,ALL_ENTRY_DATA_KINDS,ANALYSE_OUTPUT_FORMAT_DESCRIPTION,AREA_GEOGRAPHY_TYPES,BASE_SYSTEM_PROMPT,BY_ENTRY_VIEWS_DOC,CHART_TYPES,CLARIFY_FORM_PROMPT,CLARIFY_PROSE_PROMPT,DATA_ENTRY_KIND_TO_SLICE,DEFAULT_TOOLS,ENTRY_KIND_META,ENTRY_MODE_SLICE_NAMES,FIT_ON_MAP_DOC,GEOMETRIES_PROPS_DOC,GEOMETRIES_SCHEMA_DOC,GEOMETRIES_SKIPPED_DESC,GEOMETRIES_SOURCE_IDS_DESC,MIN_VIEWPORT_BIAS_ZOOM,MULTI_INPUT_SANDBOX_PARAMS,PLACES_SCHEMA_DOC,ROUTES_SCHEMA_DOC,SYSTEM_PROMPT_SECTIONS,TOOLS_BY_DATA_ENTRY_KIND,TOOL_NAMES,WORKER_PROVIDED_PARAMS,applyFitOnMap,areaWhereSchema,buildAnalyseReturnPrompt,buildAtLeastOneEntryIdMessage,buildEntryFieldList,buildEntryIdFields,buildEntryKindSandboxDocs,buildEntryKindSchemaDocs,buildEntryKindScopeSchema,buildPlacesSchemaDoc,buildSandboxCodePrompt,buildSandboxToolsDoc,buildSystemPrompt,clarifyIntentOutputSchema,clarifyIntentSchema,classifyUserIntent,collectInputGeometries,composeSystemPrompt,createClarifyIntentTool,createDefaultClassifier,createExecuteMaplibreCodeTool,createGetCurrentLocationTool,createMapAgent,extractLastUserText,findPlacesByEntry,fitOnMapInputSchema,fittedReportSchema,formatSandboxExecutionError,formatStateDigestDiff,geoJsonBBoxSchema,geocodeAreas,geometriesEntryIDsSchema,geometriesIdSchema,geometryDisplayConfigSchema,geometryFillStyleSchema,geometryInputSchema,getDefaultToolPrompts,getRangePolygons,getStateDigest,getViewportBias,getViewportBoundingBox,globalWhereSchema,hasAnyEntryIds,hidePreviousEntriesSchema,hidePreviousShownEntries,isChartConfiguration,isFitOnMapInput,isKindActive,isPolygonFeature,isPolygonFeatureArray,isResolveError,labelForLocationInput,latestTimestamp,locatePlace,locatePlaces,locationInputSchema,mainThreadExecutor,markerTypeSchema,multiPolygonInputSchema,nearbyWhereSchema,packSandboxArgs,placesEntryIDsSchema,placesEntryIdHintSchema,polygonInputSchema,positionSchema,prepareMultiInputs,queryAsSchema,questionSchema,resolveActiveKinds,resolveGlobal,resolveNearby,resolveTools,resolveWhere,resolveWithin,routeUtils,routesEntryIDsSchema,runSandboxedFn,sandboxedGlobalShadows,sharedWithinFields,showByodOnMap,showByodSchema,showEntryGeometries,showModeSchema,showPlaceGeometriesSchema,showPlacesSchema,showResultsOnMap,shownByodSchema,shownSchema,skippedSourceSchema,stripInjectedRedeclarations,toolStateToWhereContext,validateAnalysisResult,whereSchema,withAgentToolkitHeaders,withinSharedFields,withinWhereSchema};
|