@tomtom-org/maps-sdk-plugin-agent-toolkit 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +4 -4
- package/README.md +38 -23
- package/THIRD_PARTY.txt +1 -1
- package/dist/THIRD_PARTY.txt +1 -1
- package/dist/index.d.ts +787 -751
- package/dist/index.es.js +2 -2
- package/dist/third-party.json +1 -1
- package/dist/worker-libs-CUm6Y1-t.js +1 -2
- package/package.json +7 -6
- package/dist/index.es.js.map +0 -1
- package/dist/worker-libs-CUm6Y1-t.js.map +0 -1
package/dist/index.es.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","names":["dedupeById","renderReplace","renderMerged","summarySchema","getPosition","geocodeContainingAreas","changeResultSchema","resolveNearbyBias","refSchema","detailSchema","detailSchema","entryModeSchema","indexEntrySchema","detailSchema","indexEntrySchema","entryModeSchema","indexEntrySchema","detailSchema","entryModeSchema","indexEntrySchema","detailSchema","entryModeSchema","entryModeSchema","resolveMatchingIds","resolveSelectors","resolveMatchingIds","resolveSelectors","Output","ToolLoopAgent","generateText","hasToolCall","stepCountIs","BaseMapModule","CustomGeoJSONModule","GeometriesModule","POIsModule","PlacesModule","ReachableRangesModule","RoutingModule","StylingFoundationsModule","TerrainModule","TrafficAreaAnalyticsModule","TrafficFlowModule","TrafficIncidentDetailsModule","TrafficIncidentsModule","applyMapPreset","areaAnalyticsPalettes","baseMapKnobCatalogue","baseMapLayerGroupNames","fillStyledGeometryConfig","filterShowModes","geometryFillStyles","mapColorNames","mapPresetIds","mapStyleLayerIDs","poisKnobCatalogue","resetKnob","setKnob","standardStyleIDs","stylingFoundationsKnobCatalogue","terrainKnobCatalogue","trafficFlowKnobCatalogue","trafficIncidentsKnobCatalogue","validateKnobValue","calculateReachableRanges","calculateRoute","discoverPlaces","geocode","geometryData","getPOICategoryCodes","resolvePOICategories","reverseGeocode","routeTypes","trafficAreaAnalytics","trafficIncidentDetails","TOMTOM_USER_AGENT_SDK_NAME","TomTomConfig","avoidableTypes","bboxCenter","bboxFromBBoxes","bboxFromGeoJSON","budgetUnits","calculateProgressAtRoutePoint","formatDistance","formatDuration","getCoordinateAtRouteProgress","getPosition","getProgressAtNearestRoutePoint","getRouteProgressBetween","getRouteProgressForSection","getSectionBBox","placeTypes","trafficIncidentRequestCategories","withInsertedWaypoints","turf","z","h3","deriveMapColors","deriveMapColorsFromImage","mapColorsLightDark","mapThemeModes","intersectActiveTools","a","b","setB","Set","filter","name","has","unionActiveTools","activeTools","alwaysActive","length","present","isStateSlice","value","reset","mergeProviderOptions","fromStepFactory","fromUser","resolveTools","defaults","overrides","result","entry","Object","entries","StateEvents","handlers","on","type","handler","this","push","current","idx","indexOf","splice","off","emit","payload","list","clear","key","keys","Analyses","_records","Map","_jobs","events","register","input","existing","get","analysisId","set","description","outputFormat","affectedEntryIds","history","setJob","job","resetHistory","record","attachResult","at","timestamp","shift","getAnalysesForEntry","entryId","out","values","includes","latest","all","lastResult","data","isMonitored","active","remove","unregister","delete","removeByName","id","BaseMapState","ttMap","_baseMapModule","_stylingFoundationsModule","_terrainModule","constructor","mapLibreMap","getBaseMapModule","getStylingFoundationsModule","getTerrainModule","terrainModule","pickUniqueEntryId","requested","takenIds","taken","i","candidate","hideAllEntries","async","hideOne","options","parallel","Promise","map","collapseHistoryToLatest","slice","renderMerged$1","module","features","merged","seen","feature","featureId","add","dedupeById$1","getShown","geometry","show","CustomGeometriesState","_ttMap","_entries","_entryMode","latestEntry","shownEntryIds","_shown","entryMode","setEntryMode","mode","droppedIds","hideEntry","changedIds","findById","find","e","addEntry","provenance","label","explicitId","fallback","Date","now","getEntryGeometriesModule","fillStyle","_requireEntry","config","_module","_shownFillStyle","applyConfig","create","showEntry","_hideOthersUnderSingleMode","renderReplace$1","wasShown","others","other","removeEntry","findIndex","Error","clearShown","clearedIds","compareIds","localeCompare","sortIds","ids","sort","getStateDigest","state","places","shownAsPin","shownAsPinIds","shownAsBaseMap","shownAsBaseMapIds","routes","shown","routing","ranges","traffic","entryCount","incidents","trafficIncidents","monitored","_monitor","status","customGeometries","byod","arraysEqual","formatShownSetDiff","prev","next","join","formatStateDigestDiff","fragments","placesFragment","formatPlacesDiff","routeFragment","rangeFragment","trafficAnalyticsFragment","incidentsShownFragment","monitoredFragment","customGeometriesFragment","byodFragment","MapPOIsState","_poisModule","getPOIsModule","poisModule","AGENT_TOOLKIT_ID","AGENT_TOOLKIT_USER_AGENT","withAgentToolkitHeaders","params","placesMarkerTypes","renderMerged","dedupeById","PlacesState","_hideEntry","getEntryPlacesModule","markerType","_modules","_shownAs","updateConfig","geometries","latestPlace","addPlaceResult","place","connections","count","findPlaceById","placeId","_byId","geometryPlaceIdsForEntry","properties","dataSources","getGeometryForPlace","_geometryByPlaceId","_buildGeometryIndex","fetchPlaceGeometry","cached","lookup","requestParams","signal","fetchGeometriesForEntry","alreadyFetched","placesNeedingFetch","queuedGeometryIds","geometryDataSourceId","batches","results","batch","showPlaceGeometries","targetId","_latestGeometriesEntryId","renderReplace","byGeometryId","index","geometryId","_idsShownAs","shownAsPinClusteredIds","getShownMarkerType","getShownFeatures","setShownEntries","idSet","targets","toHide","_showEntryAs","addShownEntries","lastId","changed","flatMap","removeShownEntries","clearShownEntries","clearShownGeometries","showConnections","withFillStyle","getConfig","RangeState","getEntryRangesModule","modules","markEntryShown","RouteMonitor","_status","_error","_intervalHandle","_clearInterval","_recalculate","error","isRunning","isActive","start","deps","intervalMs","skipInitialTick","recalculate","clearInterval","handle","setIntervalFn","setInterval","_tick","stop","_releaseTimer","snap","takenAt","err","_failed","message","String","RoutingState","_planningSlots","_params","_color","_lastShownEntryId","getEntryRoutingModule","opts","partial","color","showSummaryBubbles","summaryBubbles","visible","showRoutes","showWaypoints","clearWaypoints","waypoints","setColor","clearRoutes","findLast","stopMonitoring","currentEntryModule","currentRoutes","currentWaypoints","planningSlots","addRoutes","setWaypointAt","waypoint","minSize","Math","max","setParams","replaceRouteData","sampledAt","_createMonitorForEntry","monitor","reason","startMonitoring","wasRunning","JobEngine","jobs","wired","scanning","scheduled","pending","spec","JobHandle","drop","ensureWired","onChange","sourceSlices","collectOrphans","scheduleScan","live","orphans","some","onOrphaned","hasActiveJob","queueMicrotask","runScan","run","engine","code","setActive","buildSandboxCodePrompt","injected","CHART_TYPES","isChartConfiguration","v","isPolygonFeature","g","Array","isArray","coordinates","isPolygonFeatureArray","every","ANALYSE_OUTPUT_FORMAT_DESCRIPTION","buildAnalyseReturnPrompt","jsonExamples","chartLabelExample","validateAnalysisResult","analysis","normalized","toJsonSafe","JSON","parse","stringify","SHADOWED_GLOBALS","sandboxedGlobalShadows","WORKER_PROVIDED_PARAMS","mainThreadExecutor","paramNames","args","verb","AsyncFunction","getPrototypeOf","shadows","clonedArgs","structuredClone","cloneDataArg","formatSandboxExecutionError","runSandboxedFn","executor","stripInjectedRedeclarations","identifiers","names","rhs","raw","pattern","RegExp","replace","SANDBOX_ERROR_HINTS","hint","base","match","test","WORKER_RUNTIME_BODY","hasBrowserSandboxApis","document","Worker","URL","Blob","defaultLoadLibrarySource","import","then","sandboxLibrarySource","createIframeWorkerExecutor","iframe","ready","unavailable","warned","nextId","warnFallback","console","warn","postToFrame","contentWindow","postMessage","onMessage","event","source","errorMessage","clearTimeout","timer","resolve","response","ok","waitForSignal","reject","setTimeout","globalThis","removeEventListener","addEventListener","init","loadLibrarySource","frame","createElement","setAttribute","style","display","srcdoc","bootstrapReady","body","appendChild","workerSource","librarySource","assembleWorkerSource","iframeReady","dataByIndex","provided","partitionSandboxArgs","timeoutMs","request","destroy","window","resolveSandboxExecutor","loadWorkerLibrarySource","jsonTypeOf","recordExample","accumulator","examples","trimmed","collectGeometryTypes","types","accumulateProperties","byKey","profileFeatureCollection","featureCount","allProperties","coverage","round","first","second","propertiesOmitted","geometryTypes","toByodSafeProfile","profile","property","BYODState","sourceUrlValidator","kind","layers","setEntryLayers","sources","getEntryModule","catch","EventsState","_trackers","_log","_seq","tracker","emitTrackers","trackerId","setEnabled","enabled","trackers","t","trackersForEntry","log","sinceMs","ingestVerdict","verdict","isVerdict","wasActive","openedAt","append","trackerName","summary","members","m","featureIds","own","ev","verdictCandidate","TrafficAreaAnalyticsState","_configChangeUnsub","_config","DEFAULT_EXCLUDE_CATEGORIES","toPoint","incident","point","_incidentId","center","centroid","collectPrimaryRoads","freq","inc","road","roadNumbers","collectPrimaryCategory","cat","category","runClustering","previous","previousSampledAt","groups","filtered","excludeCategories","minDelaySeconds","excluded","delay","delayInSeconds","preFilter","incidentById","points","pointsFC","featureCollection","eps","minMembers","clustered","clustersDbscan","minPoints","clusterMap","feat","clusterNum","cluster","group","maxClusters","rawClusters","groupPoints","memberIds","p","totalDelaySeconds","reduce","sum","peakDelaySeconds","groupFC","diameterKm","bbox","distance","primaryRoads","size","primaryCategory","topClusters","idMapping","fresh","prevSets","pairs","fi","freshSet","pi","prevSet","intersection","union","jaccard","freshIdx","prevIdx","overlap","usedFresh","usedPrev","_","matchPreviousClusters","prevById","advancing","matchedId","toFixed","priorSamples","delaySamples","trend","samples","c","net","classifyTrend","clusterIncidents","getTime","toSampledAt","IncidentMonitor","_bbox","_area","_fetchIncidents","monitoredArea","area","fetchIncidents","fitMapToBBox","fitBounds","padding","fitMapToGeoJSON","geoJson","TrafficIncidentsState","_clusters","_teardownEntry","addIncidentsEntry","replaceEntryData","tick","_focus","f","stillThere","setFocus","focus","setVisible","maxZoom","setClusters","clusters","output","getClusters","getClustersRecord","clearClusters","getFocus","focusedCount","clearFocus","keep","dropped","focusedFeatures","byId","ordered","navigateFocus","flyTo","zoom","duration","findEntryWithIncident","incidentId","hadEntries","TrafficTilesState","_trafficFlowModule","_trafficIncidentsModule","getTrafficFlowModule","getTrafficIncidentsModule","trafficFlowModule","trafficIncidentsModule","instance","put","language","DATA_ENTRY_KIND_TO_SLICE","ENTRY_MODE_SLICE_NAMES","CLEARABLE_SLICE_NAMES","SECTION_HEADINGS","identity","capabilities","rejectionRules","responseFormatting","dataConfidence","toolExecution","sessionState","SYSTEM_PROMPT_SECTIONS","SECTION_ORDER","composeSystemPrompt","trim","heading","renderSection","BASE_SYSTEM_PROMPT","buildSystemPrompt","customPrompt","prefix","suffix","withSuffix","toolErrorSchema","object","string","analysisIndexSchema","enum","number","optional","byodDataProfileSchema","array","describe","boolean","resolvedAreasOutputSchema","query","matched","basePlaceShape","address","position","buildPlaceOutputSchema","_flags","buildPlacesOutputSchema","flags","placeOutputSchema","summarySchema$1","travelTimeInSeconds","lengthInMeters","trafficDelayInSeconds","trafficLengthInMeters","departureTime","arrivalTime","noTrafficTravelTimeInSeconds","historicTrafficTravelTimeInSeconds","liveTrafficIncidentsTravelTimeInSeconds","summarizedRouteSchema","legSummaries","routesOutputSchema","routesWriteOutputSchema","extend","calculateBBoxSchema","unknown","calculateBBoxOutputSchema","minLng","minLat","maxLng","maxLat","questionSchema","question","min","multiSelect","default","clarifyIntentSchema","intro","questions","clarifyIntentOutputSchema","literal","CLARIFY_FORM_PROMPT","CLARIFY_PROSE_PROMPT","clarifyIntentExamples","clarifyIntentExamplePrompts","executeClarifyIntent","_state","createClarifyIntentTool","rendersForm","classificationPrompt","inputSchema","outputSchema","execute","tags","examplePrompts","endsTurnOnCall","getCurrentLocationSchema","getCurrentLocationOutputSchema","accuracy","roundCoordinate","locate","fullPrecision","navigator","coords","longitude","latitude","toResult","geolocation","getCurrentPosition","enableHighAccuracy","timeout","maximumAge","getPosition$1","locationErrorMessage","getLocationErrorMessage","createGetCurrentLocationTool","relatedTools","TOOL_TAGS","detailToolSchema","helpOutputSchema","tag","matchedTools","totalMatched","helpSchema","helpDescription","toAiTool","includeOutputSchema","onToolExecute","abortSignal","errorFromOutput","toolName","durationMs","isError","toMetadata","dependsOn","scopePrompt","materializeEntry","buildOptions","setupTools","toolEntries","outputSchemas","featureFlags","enabledDataKinds","tools","toolsMetadata","scopableTools","entryOrBuilder","scopeSchema","builder","applyScope","rawScope","parsed","safeParse","success","rebuilt","scope","defaultDescription","defaultInputSchema","getToolsMetadata","help","allTools","toolMetadata","lowerQuery","toLowerCase","toolTag","prompt","geometriesIdSchema","discriminatedUnion","geometriesEntryIDsSchema","GEOMETRIES_PROPS_DOC","GEOMETRIES_SKIPPED_DESC","GEOMETRIES_SOURCE_IDS_DESC","GEOMETRIES_SCHEMA_DOC","buildPlacesSchemaDoc","PLACES_SCHEMA_DOC","ROUTES_SCHEMA_DOC","isLongitude","Number","isFinite","isLatitude","POSITION_RANGE_MESSAGE","positionSchema","refine","lng","lat","geoJsonPositionSchema","geoJsonBBoxSchema","west","south","east","north","polygonInputSchema","multiPolygonInputSchema","geometryInputSchema","placesEntryIDsSchema","extra","routesEntryIDsSchema","placesEntryIdHintSchema","regex","showModeSchema","geometryFillStyleSchema","showPlaceGeometriesSchema","geometryDisplayConfigSchema","placeIds","markerTypeSchema","hidePreviousEntriesSchema","entryNoun","showPlacesSchema","zoomMode","hidePreviousEntries","shownSchema","showByodSchema","shownByodSchema","withinSharedFields","viewport","queryAsSchema","locateWithinSingularKeys","withinWhereSchema","boundingBox","queryAs","k","nearbySingularKeys","nearbyWhereSchema","radiusMeters","positive","globalWhereSchema","whereSchema","sharedWithinFields","queries","range","route","routeId","widthMeters","areaWhereSchema","ALL_ENTRY_DATA_KINDS","buildEntryKindScopeSchema","kinds","ENTRY_KIND_META","fieldName","fieldLabel","buildField","requiredHint","sandboxDoc","schemaDoc","recallTool","resolveActiveKinds","isKindActive","buildEntryFieldList","buildEntryKindSandboxDocs","buildEntryKindSchemaDocs","doc","buildEntryIdFields","shape","meta","hasAnyEntryIds","buildAtLeastOneEntryIdMessage","from","tool","fitOnMapInputSchema","animate","fittedReportSchema","tuple","FIT_ON_MAP_DOC","isBBoxTuple","isFitOnMapInput","applyFitOnMap","fit","baseMap","AREA_GEOGRAPHY_TYPES","geocodeAreas","execOptions","limit","filters","geographyTypes","bias","geoBias","hidePreviousShownEntries","keepEntryIds","selector","locatePlaces","searchFn","locatePlace","locationInputSchema","placeIdOrEntryId","labelForLocationInput","location","routeUtils","skippedSourceSchema","formatSkipReason","skip","collectGeometryBearingPlaceIds","sourceId","acc","added","placeIdToEntry","tagFeatureSource","_source","resolvePlaceGeometry","ownerEntryId","collectInputGeometries","grouped","groupByKind","expanded","skipped","expandPlacesEntries","seenPlaceIds","mergedPlaceIds","placeIdSourceQualified","sourcePlaces","affectedPlacesEntries","contributingPlaceIds","contributingPlacesEntries","explicitPlaceSource","fromPlacesEntry","skipSource","featureSource","contributingIds","polygons","r","polygon","collectRangePolygons","custom","collectCustomGeometries","affectedCustomEntries","affectedEntries","contributingSourceIds","findPlacesByEntry","placesEntryIDs","placesByEntry","placesEntry","latestTimestamp","resolved","ID_FIELDS","prepareMultiInputs","field","hasAnyInput","placesResult","resolveSliceEntries","missingId","routesResult","routesEntryIDs","incidentsResult","incidentsEntryIDs","trafficAreaAnalyticsResult","trafficAreaAnalyticsEntryIDs","byodResult","byodEntryIDs","resolveAllSlices","geometriesMeta","sourceIds","geometriesEntryIDs","collected","sandbox","buildSandboxInputs","fromEntries","routesByEntry","incidentsByEntry","geometriesByEntry","partitionGeometriesBySource","trafficAreaAnalyticsByEntry","byodByEntry","byEntry","hints","found","BY_ENTRY_VIEWS_DOC","MULTI_INPUT_SANDBOX_PARAMS","buildSandboxToolsDoc","scoped","crossKindDoc","routeUtilsDoc","clusterDoc","packSandboxArgs","libs","getRangePolygons","rangeId","rangesEntry","scopableToolNames","looseScopeShape","addUsage","target","inputTokens","outputTokens","totalTokens","extractPartialTools","scopes","toolScopes","compactSuccess","usage","cleanedScopes","classifyQueryToTools","conversation","model","scopableNames","schema","toolScopesShape","nullable","toolScopesObject","superRefine","ctx","addIssue","path","buildClassifySchema","aggregateUsage","expectedShape","n","buildExpectedShapeHint","system","formatToolEntry","scopable","buildClassifySystemPrompt","soft","attempt","generated","json","messages","maxOutputTokens","errRecord","classifyUserIntent","chatModel","startTime","performance","activeToolNames","timeMs","isTextPart","part","extractMessageText","content","textParts","text","extractLastUserText","role","createDefaultClassifier","maxHistoryMessages","maxHistoryMessageLength","context","historyLimit","messageCharLimit","lastUserIndex","lastUserText","compacted","waypointAddress","wp","freeformAddress","makePlacesLabel","parts","poiCategories","routeLabel","where","countStr","makeCollectionLabel","poi","placeAreaLabel","countryCode","endsWith","makeRangesLabel","budgets","originNames","summarizePlace","summarizePlaces","summarizeRoute","sections","legs","leg","stringifyDates","toISOString","summarizeRoutes","waypointSummarySchema","slotIndex","isFilled","summarizeWaypoint","isoDate","isNaN","resolveWhere","resolveWithin","resolveNearby","resolveGlobal","metadata","gatherCandidates","gatherInputs","mapViewport","useViewport","candidates","geocodeQueryCandidates","isResolveError","inputs","queryCandidates","resolveQueryCandidates","placeIdCandidates","resolvePlaceIdCandidates","resolveGeometryCandidates","routeCandidates","resolveRouteCandidates","matches","top","rerankWithNearestCandidate","fetchGeometry","entryIds","expandEntry","getRoute","routeAreas","buffered","buffer","units","sortedFromDistance","diff","abs","geocoderRank","rank","toSorted","collectShownByodFeatures","showByodOnMap","byodEntryId","showEntryGeometries","placesEntryId","fetched","showResultsOnMap","placesEntryIds","exclude","MIN_VIEWPORT_BIAS_ZOOM","getViewportBias","minZoom","getZoom","getCenter","getViewportBoundingBox","bounds","getBounds","getWest","getSouth","getEast","getNorth","toolStateToWhereContext","sourceDiffSchema","removed","updated","layerDiffSchema","executeMaplibreCodeSchema","executeMaplibreCodeOutputSchema","snapshotSources","getStyle","snapshot","snapshotLayers","layer","diffSnapshots","before","after","identifier","afterJson","beforeJson","flyToOutputSchema","flyToSchema","getMapStyleLayersOutputSchema","layout","paint","getMapStyleLayersSchema","layerIdQuery","include","getViewportOutputSchema","pitch","bearing","getViewportSchema","changeResultSchema$1","layerId","propertyName","setLayoutPropertiesOutputSchema","setLayoutPropertiesSchema","changes","any","changeResultSchema","setPaintPropertiesOutputSchema","setPaintPropertiesSchema","setPitchBearingOutputSchema","setPitchBearingSchema","zoomInOrOutOutputSchema","zoomInOrOutSchema","delta","resolveLocationInput","hit","pos","costModelSchema","routeType","avoid","avoidAreas","whenSchema","option","date","routeParametersSchema","maxAlternatives","int","costModel","when","setRouteOutputSchema","setRouteSchema","locations","parameters","showOnMap","resolveRouteWaypoints","valid","w","buildCalculateRouteParams","routeParams","buildRouteRecalc","calculateAndAddRoute","timeDistParts","Boolean","timeDist","addresses","origin","destination","vias","makeRoutesLabel","_waypoints","showRouteOnMap","addWaypointsToRouteOutputSchema","addWaypointsToRouteSchema","stops","buildDiscoverPlacesOutputSchema","geometriesFetched","geometriesShown","resolvedAreas","unmatchedPoiCategories","maxDetourWhereSchema","maxDetourTimeSeconds","sortBy","buildDiscoverPlacesSchema","baseShape","hasViewport","hasMulti","dispatchPlacesSearch","multiFilters","toGeoBias","commonParams","resolveDiscoverBias","effectiveWhere","baseMultiFilters","nearby","resolveNearbyBias$1","baseFilters","within","resolvedGeometries","resolvedBoundingBoxes","mergedBoundingBoxes","mergedGeometries","boundingBoxes","scopeBoundingBox","resolveDiscoverWithin","finalizeDiscoverResult","geometryResult","buildExecuteDiscoverPlaces","categories","unmatched","resolvedPoiCategories","unmatchedNote","detourResult","detour","routeEntry","routeFeature","searchByDetour","rangeResult","ranged","combinedGeometries","searchInRange","whereLabel","q","resolveWhereLabel","biasResult","searchWithBias","discoverPlacesMetadata","budgetSchema","findReachableAreasSchema","origins","showOriginPin","rangeOutputSchema","rangesId","originName","findReachableAreasOutputSchema","computeReachableRangesForOrigin","budget","formatOriginName","labelForUnresolvedOrigin","getPoiCategoryCodesOutputSchema","codes","getPoiCategoryCodesSchema","metricsSchema","speed","freeFlowSpeed","congestionLevel","travelTime","networkLength","getTrafficAreaAnalyticsOutputSchema","dateRange","end","baseData","metrics","tileCount","availableGranularities","getTrafficAreaAnalyticsSchema","startDate","endDate","days","functionalRoadClasses","hours","entriesSummary","getTrafficIncidentsOutputSchema","monitoring","withinTrafficIncidentsWhereSchema","trafficIncidentsWhereSchema","categoryFilterDescription","getTrafficIncidentsSchema","categoryFilter","timeValidityFilter","monitorIntervalMs","resolveWithinToBBox","bboxes","resolveRangeToBBoxes","areaWhere","locatePlaceSchema","waypointIndex","locatePlaceOutputSchema","geometryFetched","geometryShown","resolveLocateBias","resolveWithinBias","resolveNearbyBias","removeWaypointsFromRouteOutputSchema","removeWaypointsFromRouteSchema","waypointIndices","replaceWaypointInRouteOutputSchema","waypointTargetSchema","replaceWaypointInRouteSchema","reverseGeocodeOutputSchema","reverseGeocodeSchema","isFeatureCollectionLike","addByodSourceOutputSchema","url","addByodSourceSchema","MAX_RESPONSE_BYTES","ALLOWED_PROTOCOLS","fetchJsonBounded","controller","AbortController","abort","fetch","statusText","sizeProblem","contentLength","headers","advertised","parseInt","checkAdvertisedSize","reader","getReader","chunks","total","done","read","byteLength","cancel","Uint8Array","offset","chunk","TextDecoder","decode","readBodyBounded","wrapFetchError","STANDING_SANDBOX_PARAMS","runPreparedSandbox","prepared","codeExecution","analyseDataOutputSchema","buildAnalyseDataSchema","sandboxList","schemaBlocks","showGeometriesProps","buildAnalyseDataCodeDoc","buildAnalyseDataDescription","kindList","contrast","buildAnalyseDataScopePrompt","executeAnalyseData","sandboxResult","validated","affectedIds","makeAnalysisId","registerRecord","analyses","registerAnalysisJob","clearTrackerSchema","clearTrackerOutputSchema","cleared","execClustering","recipe","runAndStoreClusters","ensureClusterRerunWired","rerunFlags","WeakMap","flagsFor","running","WeakSet","scheduleRerun","rerunClusters","resolveClusterKinds","buildClusterIncidentsCodeDoc","buildClusterIncidentsSchema","incidentsEntryID","clusterIncidentsOutputSchema","clusterCount","CLUSTER_BASE_DESCRIPTION","buildClusterIncidentsDescription","buildClusterIncidentsScopePrompt","executeClusterIncidents","clusterParams","createTrackerSchema","rule","createTrackerOutputSchema","watching","firingNow","uniqueTrackerId","replaceAll","focusIncidentsOutputSchema","focusIncidentsSchema","incidentIds","getCurrentWaypointsSchema","includeEmptySlots","getCurrentWaypointsOutputSchema","totalSlots","filledSlots","matchedCount","returnedCount","hasMore","nextOffset","getTrackerHistorySchema","eventSchema","getTrackerHistoryOutputSchema","oldestRetainedAt","newestAt","getTrackersSchema","getTrackersOutputSchema","firing","monitorAnalysisOutputSchema","monitorAnalysisSchema","showProducedGeometriesSchema","sharedProcessDataReport","fitted","processDataOutputSchema","connectionsRendered","placesGeometryCount","shownPlaces","customGeometriesEntryId","customGeometriesCount","operation","shownCustomGeometries","byodFeatureCount","buildProcessDataSchema","trafficNote","geometriesProps","byodOutputLine","buildProcessDataCodeDoc","placesGeometries","connectMarkerType","buildProcessDataDescription","buildProcessDataScopePrompt","isConnectionArray","isEndpoint","endpoint","item","connection","to","isNonEmptyString","isValidLngLat","derivedPlaceId","validateField","check","nonEmptyFeatureCollection","fc","validateProcessOutput","seenIds","priorIndex","validatePlaces","placeConnections","validateGeometries","fitOnMap","checkProcessJointConstraints","writePlacesResult","processed","producedGeometries","entryLabel","newPlacesEntryId","rendered","renderConnections","storedLabel","executeProcessData","producedByod","customLabel","newCustomEntryId","writeCustomGeometriesResult","byodLabel","newByodEntryId","writeByodResult","sourceSchema","refSchema$1","propertyNames","detailSchema$5","recallByodOutputSchema","refSchema","detailSchema$4","recallGeometriesOutputSchema","summariseAnalyses","indexEntries","refs","detailFor","detailForPlaces","detailForRanges","detailForCustomGeometries","entryModeSchema$5","indexEntrySchema$4","detailSchema$3","byMagnitude","recallIncidentsOutputSchema","indexEntrySchema$3","entryModeSchema$4","buildRecallPlacesOutputSchema","detailSchema","buildExecuteRecallPlaces","allEntries","indexEntrySchema$2","originCount","rangeSummarySchema","detailSchema$2","entryModeSchema$3","recallRangesOutputSchema","indexEntrySchema$1","routeParamsSchema","detailSchema$1","entryModeSchema$2","recallRoutesOutputSchema","standardStyleIdOf","entryModeSchema$1","indexEntrySchema","regionCount","regions","heatmap","recallTrafficAreaAnalyticsOutputSchema","idLabelSchema","entryModeSchema","snapshotSchema","entryModes","mapStyle","mapPOIs","filterMode","flow","buildRecallStateOutputSchema","recallStateSchema","buildExecuteRecallState","byNewest","arr","styleId","getStyleInput","poisConfig","flowConfig","trafficTiles","incidentsConfig","reverse","summarized","executeRecallRoutes","executeRecallRanges","executeRecallByod","magnitude","magnitudeOfDelay","executeRecallIncidents","executeRecallTrafficAreaAnalytics","executeRecallGeometries","recallStateMetadata","CLEARED_SLICES","resetStateOutputSchema","resetStateSchema","byodLayerTypeSchema","byodLayerSpecSchema","beforeID","lowestLabel","lowestRoadLine","setByodLayersSchema","setByodLayersOutputSchema","layerCount","layerTypes","sliceSchema","modeSchema","setEntryModeOutputSchema","droppedEntryIds","setEntryModeSchema","setEntryModeDescription","setTrafficIncidentsMonitorOutputSchema","alreadyInState","setTrafficIncidentsMonitorSchema","startRouteMonitorOutputSchema","routesEntryID","alreadyRunning","startRouteMonitorSchema","stopRouteMonitorOutputSchema","stopRouteMonitorSchema","layerEnum","clearMapOutputSchema","clearMapSchema","getStandardMapStylesOutputSchema","styles","readonly","getStandardMapStylesSchema","setLanguageOutputSchema","setLanguageSchema","setMapStandardStyleOutputSchema","setMapStandardStyleSchema","lookOnly","knobs","isLook","isTrafficLook","startsWith","lookKnobs","knownKnobs","knobValueSchema","themeModeSchema","setMapStylingOutputSchema","settings","rejected","themeColors","switchedStyle","setMapStylingSchema","preset","theme","colors","imageUrl","protocol","valueAt","split","existingModule","lookSettingsOf","standardVariants","light","dark","drivingVariants","monoVariants","LIGHT_DARK_VARIANTS","standardLight","standardDark","drivingLight","drivingDark","monoLight","monoDark","toggleTilesBaseMapLayerGroupsOutputSchema","hiddenGroups","toggleTilesBaseMapLayerGroupsSchema","layerGroups","hiddenGroupsOf","isVisible","toggleTilesPOIsOutputSchema","allMapPOIsVisible","filterCategories","toggleTilesPOIsSchema","categoriesKnob","filterableCategories","isFilterableCategory","toggleTilesTrafficFlowSchema","toggleTilesTrafficFlowOutputSchema","trafficFlowVisible","toggleTilesTrafficIncidentsSchema","toggleTilesTrafficIncidentsOutputSchema","trafficIncidentsVisible","actionSchema","updateByodDisplaySchema","action","clearAll","hideOthers","updateByodDisplayOutputSchema","fetchPlaceGeometryFeature","renderEntryGeometries","placeIdFilter","targetPlaces","shownEntrySummarySchema","updatePlacesDisplayOutputSchema","totalShown","actuallyAdded","actuallyRemoved","geometriesRendered","geometriesSkipped","updatePlacesDisplaySchema","addMatching","removeMatching","resolveMatchingIds$2","terms","needles","term","needle","shownRouteEntrySummarySchema","routeCount","selectedIndex","updateRoutesDisplayOutputSchema","updateRoutesDisplaySchema","resolveMatchingIds$1","renderKnobs","metric","palette","scaleMode","maxHeightMeters","metersPerUnit","summarySchema","activeMetric","displayMode","updateTrafficAreaAnalyticsDisplaySchema","updateTrafficAreaAnalyticsDisplayOutputSchema","resolveMatchingIds","applyRenderKnobs","defaultMetric","setPalette","heightConfig","heightConfigOf","setHeight","setFilters","summarizeEntry","updateWaypointsDisplayOutputSchema","updateWaypointsDisplaySchema","DATA_TOOL_SHARED_FRAMING","DEFAULT_TOOLS_TYPED","showLocateResult","buildDiscoverPlacesEntry","processData","buildProcessDataEntry","analyseData","buildAnalyseDataEntry","analyseDataBuilder","setRoute","loc","unresolved","addWaypointsToRoute","routeResult","currentModule","shownRoutes","shownRouteLines","mainLines","shownWaypoints","validateExistingRoute","resolveAllInputs","baseWaypoints","updatedWaypoints","removeWaypointsFromRoute","unique","validateRemoveWaypoints","replaceWaypointInRoute","targetIndex","waypointsLen","getTrafficIncidents","effectiveBbox","entryParams","capturedAt","filterParts","head","defaultLabel","tickParams","focusIncidents","buildClusterIncidentsEntry","clusterIncidentsBuilder","monitorAnalysis","setAnalysisMonitored","createTracker","registerTrackerJob","clearTracker","getTrackerHistory","retained","newest","getTrackers","setTrafficIncidentsMonitor","startRouteMonitor","stopRouteMonitor","getTrafficAreaAnalytics","apiKey","process","env","MOVE_PORTAL_KEY","resolvedGeometry","boundaryGeometry","bboxToPolygon","resolveGeometry","dateParams","resolveDateParams","analyticsModule","showAnalyticsOnMap","region","timedData","yearly","monthly","weekly","daily","hourly","average","tiledData","tiles","summarize","updatePlacesDisplay","selectors","resolvedAdd","resolvedRemove","latestId","knownIds","unknownAdd","resolveSelectors$2","beforeShown","renderMarkers","addIds","markerIds","applyMarkerAdds","applyGeometryDisplay","afterShown","shouldFit","shownFeatures","shownWithLabels","updateRoutesDisplay","resolveSelectors$1","plan","applySelectedIndex","applyAddRemove","shownIds","fitCameraToShown","currentColor","updateTrafficAreaAnalyticsDisplay","onlyRenderKnobs","hasRenderKnobs","resolveSelectors","updateWaypointsDisplay","firstShownId","targetEntryId","routingModule","clearMap","getViewport","toArray","getBBox","getPitch","getBearing","addByodSource","sourceRecord","urlCheck","validateUrl","setByodLayers","previousLayers","nextLayers","beforeLayerConfig","layerID","updateByodDisplay","validationProblem","validateRequestedIds","affected","requestedSet","showEntries","hideEntries","removeEntries","getCurrentWaypoints","slots","filteredSlots","slot","hasExactSlot","selected","getStandardMapStyles","setMapStandardStyle","setStyle","setLanguage","toggleTilesPOIs","resetConfig","problem","categoriesProblem","toggleTilesBaseMapLayerGroups","setMapStyling","styling","onKnob","owner","local","knob","lookKnobOf","unknownKnob","deriveTheme","lightDark","styleLightDarkTheme","matchThemeStyle","setMapColors","toggleTilesTrafficFlow","toggleTilesTrafficIncidents","getMapStyleLayers","setLayoutProperties","layoutProperty","setLayoutProperty","setPaintProperties","paintProperty","setPaintProperty","setPitchBearing","easeTo","zoomInOrOut","targetZoom","zoomTo","executeMaplibreCode","beforeSources","beforeLayers","calculateBBox","getCurrentLocation","clarifyIntent","findReachableAreas","sortedBudgets","fallbackLabel","resolveOriginsToRanges","willRender","mergeRangePolygons","placesModule","originPlaces","showOriginsAndPolygons","recallState","resetState","beforeIds","afterIds","DEFAULT_TOOLS","TOOL_NAMES","TOOLS_BY_DATA_ENTRY_KIND","getDefaultToolPrompts","runStep0Classification","info","restoreScopableTools","classifier","onClassify","scopableTool","rebuiltTool","applyToolScopes","createMapAgent","_setTomTomUserAgent","customSlices","assign","createToolState","validateSourceUrl","dataEntries","kindKey","isKindEnabled","disabledKinds","droppedToolNames","defaultsAll","includeDefaultTools","systemPrompt","systemPromptPrefix","systemPromptSuffix","alwaysActiveToolNames","endsTurnStops","lastClassification","withPrepareStepLock","tail","fn","release","createPrepareStepMutex","classificationDeps","agent","instructions","stopWhen","maxSteps","prepareStep","stepInfo","stepNumber","userResult","stepProviderOptions","composed","providerOptions","composeStepResult","destroyState"],"sources":["../src/prepare-step-helpers.ts","../src/resolve-tools.ts","../src/state/events.ts","../src/state/analyses.ts","../src/state/base-map/state.ts","../src/state/entry-helpers.ts","../src/state/custom-geometries/state.ts","../src/state/digest.ts","../src/state/map-pois/state.ts","../package.json","../src/tools/shared/agent-headers.ts","../src/state/places/state.ts","../src/state/range/state.ts","../src/state/routing/monitor/monitor.ts","../src/state/routing/state.ts","../src/engine/engine.ts","../src/tools/shared/sandbox-code.ts","../src/tools/shared/sandbox/worker-runtime.ts","../src/tools/shared/sandbox/iframe-worker-executor.ts","../src/state/byod/profile.ts","../src/state/byod/state.ts","../src/state/trackers.ts","../src/state/traffic-area-analytics/state.ts","../src/state/traffic-incidents/clustering.ts","../src/state/traffic-incidents/monitor/monitor.ts","../src/tools/shared/map-fit.ts","../src/state/traffic-incidents/state.ts","../src/state/traffic-tiles/state.ts","../src/state/state.ts","../src/system-prompt.ts","../src/tools/shared-output-schemas.ts","../src/tools/utilities/calculate-bbox.ts","../src/tools/utilities/clarify-user-intent.ts","../src/tools/utilities/get-current-location.ts","../src/tools/tool-tags.ts","../src/tools/utilities/help.ts","../src/tool-setup.ts","../src/tools/shared/geometries-id.ts","../src/tools/shared/geometries-schema-doc.ts","../src/tools/shared/incidents-schema-doc.ts","../src/tools/shared/places-schema-doc.ts","../src/tools/shared/routes-schema-doc.ts","../src/tools/shared/schema.ts","../src/tools/shared/traffic-area-analytics-schema-doc.ts","../src/tools/shared/entry-kinds.ts","../src/tools/shared/fit-on-map.ts","../src/tools/shared/geocode-areas.ts","../src/tools/shared/hide-previous-entries.ts","../src/tools/shared/locate-places.ts","../src/tools/shared/location-input.ts","../src/tools/shared/route-utils.ts","../src/tools/shared/state-inputs.ts","../src/tools/shared/multi-input.ts","../src/tools/shared/resolve-range.ts","../src/utils/intent-classifier.ts","../src/utils/state-labels.ts","../src/utils/summarize.ts","../src/tools/shared/resolve-where.ts","../src/tools/shared/show-byod-on-map.ts","../src/tools/shared/show-entry-geometries.ts","../src/tools/shared/show-places-on-map.ts","../src/tools/shared/viewport-bias.ts","../src/tools/shared/tool-state-where-context.ts","../src/tools/maplibre/execute-maplibre-code.ts","../src/tools/maplibre/fly-to.ts","../src/tools/maplibre/get-map-style-layers.ts","../src/tools/maplibre/get-viewport.ts","../src/tools/maplibre/set-layout-properties.ts","../src/tools/maplibre/set-paint-properties.ts","../src/tools/maplibre/set-pitch-bearing.ts","../src/tools/maplibre/zoom-in-or-out.ts","../src/tools/services/resolve-location-input.ts","../src/tools/services/set-route.ts","../src/tools/services/add-waypoints-to-route.ts","../src/tools/services/discover-places.ts","../src/tools/services/find-reachable-areas.ts","../src/tools/services/get-poi-category-codes.ts","../src/tools/services/get-traffic-area-analytics.ts","../src/tools/services/get-traffic-incidents.ts","../src/tools/services/locate-place.ts","../src/tools/services/remove-waypoints-from-route.ts","../src/tools/services/replace-waypoint-in-route.ts","../src/tools/services/reverse-geocode.ts","../src/tools/shared/geojson-guards.ts","../src/tools/state/add-byod-source.ts","../src/tools/shared/sandbox-replay.ts","../src/tools/state/monitoring.ts","../src/tools/state/analyse-data.ts","../src/tools/state/clear-tracker.ts","../src/tools/state/clusters-runtime.ts","../src/tools/state/cluster-incidents.ts","../src/tools/state/create-tracker.ts","../src/tools/state/focus-incidents.ts","../src/tools/state/get-current-waypoints.ts","../src/tools/state/get-tracker-history.ts","../src/tools/state/get-trackers.ts","../src/tools/state/monitor-analysis.ts","../src/tools/state/process-data.ts","../src/tools/state/recall-byod.ts","../src/tools/state/recall-geometries.ts","../src/tools/state/recall-incidents.ts","../src/tools/state/recall-places.ts","../src/tools/state/recall-ranges.ts","../src/tools/state/recall-routes.ts","../src/tools/shared/standard-style-id.ts","../src/tools/state/recall-traffic-area-analytics.ts","../src/tools/state/recall-state.ts","../src/tools/state/reset-state.ts","../src/tools/state/set-byod-layers.ts","../src/tools/state/set-entry-mode.ts","../src/tools/state/set-traffic-incidents-monitor.ts","../src/tools/state/start-route-monitor.ts","../src/tools/state/stop-route-monitor.ts","../src/tools/tomtom-map/clear-map.ts","../src/tools/tomtom-map/get-standard-map-styles.ts","../src/tools/tomtom-map/set-language.ts","../src/tools/tomtom-map/set-map-standard-style.ts","../src/tools/tomtom-map/set-map-styling.ts","../src/tools/tomtom-map/toggle-tiles-base-map-layer-groups.ts","../src/tools/tomtom-map/toggle-tiles-pois.ts","../src/tools/tomtom-map/toggle-tiles-traffic.ts","../src/tools/tomtom-map/update-byod-display.ts","../src/tools/tomtom-map/update-places-display.ts","../src/tools/tomtom-map/update-routes-display.ts","../src/tools/tomtom-map/update-traffic-area-analytics-display.ts","../src/tools/tomtom-map/update-waypoints-display.ts","../src/tools/tool-registry.ts","../src/create-map-agent.ts"],"sourcesContent":["/**\n * @module agent-toolkit\n * @ignore\n *\n * Pure helpers used by {@link createMapAgent}'s `prepareStep` orchestrator. Extracted from the\n * monolithic module so they can be exercised in isolation by the unit-test suite — none of these\n * touch the AI SDK, the TomTom map, or any singleton state, so they're testable without mocks.\n */\n\nimport type { PrepareStepResult, Tool } from 'ai';\nimport type { ScopableToolInfo } from './tool-setup';\nimport type { StateSlice, ToolState } from './types';\n\ntype ProviderOptions = NonNullable<PrepareStepResult>['providerOptions'];\n\n// Tool objects with mutable description/inputSchema. The AI SDK reads these fresh on every step,\n// so per-turn mutation is the supported way to scope a tool's surface — see the concurrency note\n// on `createMapAgent`.\ntype MutableTool = Tool & { description: string; inputSchema: unknown };\n\n/**\n * Build a per-agent serialising mutex. `prepareStep` chains onto this so a concurrent step's\n * classification + scope-mutation cannot interleave with the previous one's request-build window.\n * Each agent instance gets its own chain via a fresh call to this factory.\n *\n * @ignore\n */\nexport const createPrepareStepMutex = () => {\n let tail: Promise<void> = Promise.resolve();\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n const previous = tail;\n let release: () => void = () => undefined;\n tail = new Promise<void>((resolve) => {\n release = resolve;\n });\n try {\n await previous;\n return await fn();\n } finally {\n release();\n }\n };\n};\n\n/**\n * Restore each scopable tool's description + inputSchema to its full-surface defaults. Called at\n * the start of every turn (step 0) before classification so the new turn inherits a clean slate.\n *\n * @ignore\n */\nexport const restoreScopableTools = (\n tools: Record<string, Tool>,\n scopableTools: Record<string, ScopableToolInfo>,\n): void => {\n for (const [name, info] of Object.entries(scopableTools)) {\n const tool = tools[name] as MutableTool | undefined;\n if (!tool) continue;\n tool.description = info.defaultDescription;\n tool.inputSchema = info.defaultInputSchema;\n }\n};\n\n/**\n * Apply classifier-emitted per-tool scopes by mutating the live tool object. Validation and\n * rebuilding are encapsulated in {@link ScopableToolInfo.applyScope}; a `null` return covers both\n * mis-shaped payloads and rebuild throws, in which case the default surface stays in place —\n * fail-open, never crash the step.\n *\n * @ignore\n */\nexport const applyToolScopes = (\n tools: Record<string, Tool>,\n scopableTools: Record<string, ScopableToolInfo>,\n toolScopes: Record<string, unknown> | undefined,\n): void => {\n if (!toolScopes) return;\n for (const [name, rawScope] of Object.entries(toolScopes)) {\n const scopableTool = scopableTools[name];\n if (!scopableTool || rawScope == null) continue;\n const tool = tools[name] as MutableTool | undefined;\n if (!tool) continue;\n const rebuiltTool = scopableTool.applyScope(rawScope);\n if (!rebuiltTool) continue;\n tool.description = rebuiltTool.description;\n tool.inputSchema = rebuiltTool.inputSchema;\n }\n};\n\n/**\n * Intersect two activeTools lists. Returns `undefined` only when neither side was set (so the\n * caller can omit the key from `PrepareStepResult` entirely). When only one side is set the\n * intersection is that side; when both are set, the result preserves `a`'s order and drops any\n * name absent from `b`. Used to combine classifier-picked tools with a user-supplied override.\n *\n * @ignore\n */\nexport const intersectActiveTools = (\n a: string[] | undefined,\n b: readonly (string | number | symbol)[] | undefined,\n): string[] | undefined => {\n if (!a) return b as string[] | undefined;\n if (!b) return a;\n const setB = new Set(b);\n return a.filter((name) => setB.has(name));\n};\n\n/**\n * Union `alwaysActive` tool names into a classifier-picked activeTools list. Returns `undefined`\n * when `activeTools` is unset (the fail-open path — every tool is already active, so the always-on\n * tools are included implicitly). Otherwise appends any always-active name not already picked,\n * preserving the classifier's order first. Used so tools flagged `alwaysActive` are offered to the\n * model every turn regardless of what the classifier selected.\n *\n * @ignore\n */\nexport const unionActiveTools = (\n activeTools: string[] | undefined,\n alwaysActive: readonly string[],\n): string[] | undefined => {\n if (!activeTools) return undefined;\n if (alwaysActive.length === 0) return activeTools;\n const present = new Set(activeTools);\n return [...activeTools, ...alwaysActive.filter((name) => !present.has(name))];\n};\n\n/**\n * Type guard for {@link StateSlice}. Detects the canonical shape (an object with a callable\n * `reset` method) without committing to a particular slice's full surface — used by\n * {@link destroyState} to walk the heterogeneous ToolState bag without per-slice knowledge.\n *\n * @ignore\n */\nexport const isStateSlice = (value: unknown): value is StateSlice =>\n typeof value === 'object' &&\n value !== null &&\n 'reset' in value &&\n typeof (value as { reset: unknown }).reset === 'function';\n\n/**\n * Reset every slice on `state` that implements {@link StateSlice}. Slices without a `reset`\n * method (e.g. the bare-bones `baseMap` proxy) are skipped silently.\n *\n * @ignore\n */\nexport const destroyState = (state: ToolState): void => {\n for (const slice of Object.values(state)) {\n if (isStateSlice(slice)) {\n slice.reset();\n }\n }\n};\n\n/**\n * Merge per-provider keys from the developer's `prepareStep` override and the agent's per-step\n * providerOptions factory. Either side may be `undefined`; the developer's values win on\n * collision so caller intent is preserved. Returns `undefined` when both sides are absent so\n * callers can omit the key from the final result.\n *\n * @ignore\n */\nexport const mergeProviderOptions = (\n fromStepFactory: ProviderOptions | undefined,\n fromUser: ProviderOptions | undefined,\n): ProviderOptions | undefined => {\n if (!fromStepFactory && !fromUser) return undefined;\n return { ...fromStepFactory, ...fromUser };\n};\n\n/**\n * Compose the PrepareStepResult from three independent contributions: the active-tools list\n * (from the classifier), the per-step providerOptions factory, and the developer's optional\n * user-level `prepareStep` override. When the user overrides, their `activeTools` are\n * intersected with the classifier picks (so the user can narrow further but not widen past the\n * classifier's choice).\n *\n * @ignore\n */\nexport const composeStepResult = (\n activeTools: string[] | undefined,\n stepProviderOptions: ProviderOptions | undefined,\n userResult: PrepareStepResult | undefined,\n): PrepareStepResult => {\n if (userResult) {\n return {\n ...userResult,\n activeTools: intersectActiveTools(activeTools, userResult.activeTools),\n providerOptions: mergeProviderOptions(stepProviderOptions, userResult.providerOptions),\n };\n }\n return {\n ...(activeTools ? { activeTools } : {}),\n ...(stepProviderOptions ? { providerOptions: stepProviderOptions } : {}),\n };\n};\n","import type { MapAgentOptions, ToolDefinition, ToolState } from './types';\n\n/**\n * Merges default tools with user overrides.\n *\n * - `ToolEntry` (or builder) values add or replace tools in `defaults`.\n * - `false` values remove tools from `defaults`.\n * - Returns a new object — `defaults` is never mutated.\n *\n * Default entries may be {@link ToolEntryBuilder builders} that produce\n * their {@link ToolEntry} from build-time options. Builders pass through\n * untouched and are materialized later by `setupTools`.\n *\n * @param defaults - Base tool record (typically DEFAULT_TOOLS or empty).\n * @param overrides - User-provided additions, replacements, and exclusions.\n * @returns Resolved tool record ready for setupTools.\n *\n * @group Agent Toolkit\n */\nexport const resolveTools = <S extends ToolState>(\n defaults: Record<string, ToolDefinition<S>>,\n overrides?: MapAgentOptions<S>['tools'],\n): Record<string, ToolDefinition<S>> => {\n const result: Record<string, ToolDefinition<S>> = { ...defaults };\n\n if (!overrides) return result;\n\n for (const [name, entry] of Object.entries(overrides)) {\n if (entry) {\n result[name] = entry;\n } else {\n delete result[name];\n }\n }\n\n return result;\n};\n","/**\n * @module agent-toolkit-state\n *\n * Tiny typed event emitter used by every state slice (PlacesState, RoutingState, ...) to notify\n * subscribers when their internals change. The shape mirrors the SDK's `ModuleEvents` API\n * (`on(type, handler) => unsubscribe`, `off(type)`) so consumers see one consistent pattern across\n * the SDK and the agent toolkit. Designed to plug straight into React's `useSyncExternalStore`.\n *\n * @group Agent Toolkit\n */\n\n/**\n * Generic typed event bus. The `EventMap` parameter declares the set of event names this emitter\n * supports along with each event's payload type — `on` and `emit` are then strongly typed against it.\n *\n * Usage from outside the slice (subscribe-only):\n * ```ts\n * const unsub = state.places.events.on('entries-change', ({ entries }) => render(entries));\n * // ...\n * unsub();\n * ```\n *\n * @group Agent Toolkit\n */\nexport class StateEvents<EventMap extends Record<string, unknown>> {\n private readonly handlers: { [K in keyof EventMap]?: ((payload: EventMap[K]) => void)[] } = {};\n\n /**\n * Subscribe to an event. Returns an unsubscribe function that removes only this handler.\n */\n on<K extends keyof EventMap>(type: K, handler: (payload: EventMap[K]) => void): () => void {\n const list = this.handlers[type] ?? (this.handlers[type] = []);\n list.push(handler);\n return () => {\n const current = this.handlers[type];\n if (!current) return;\n const idx = current.indexOf(handler);\n if (idx !== -1) current.splice(idx, 1);\n };\n }\n\n /** Remove all handlers for a given event type. Use the unsubscribe from `on` for single-handler removal. */\n off<K extends keyof EventMap>(type: K): void {\n if (this.handlers[type]) this.handlers[type]!.length = 0;\n }\n\n /**\n * Fire an event. Internal — only the owning slice should call this. We don't make it `protected`\n * because slices hold an instance via composition (not inheritance), and the alternative\n * (subclassing per slice) bloats the public API surface.\n *\n * @internal\n */\n emit<K extends keyof EventMap>(type: K, payload: EventMap[K]): void {\n const list = this.handlers[type];\n if (!list?.length) return;\n // Iterate over a snapshot so a handler that unsubscribes mid-dispatch can't cause\n // others on the same tick to be skipped.\n for (const handler of [...list]) handler(payload);\n }\n\n /** Drop every handler for every type. Used by slice `reset()` to detach lingering listeners. */\n clear(): void {\n for (const key of Object.keys(this.handlers) as (keyof EventMap)[]) {\n this.handlers[key] = [];\n }\n }\n}\n","/**\n * @module agent-toolkit-state\n *\n * The session-level {@link Analyses} registry — the single home for every `analyseData` analysis\n * (one-shot + monitored) across every entry kind. Entries carry no analysis state; per-entry views read\n * results on demand via {@link Analyses.getAnalysesForEntry}. Writers `register` an analysis then\n * `attachResult`; the registry emits `analysis-change` whenever a result lands.\n *\n * It is a pure sink: it holds each analysis's metadata + bounded result `history`, but NOT its recipe\n * or recurrence. A monitored analysis's recipe lives on a {@link Job} the tools layer registers with the\n * {@link JobEngine}; the registry keeps the returned handle in `_jobs` (keyed by `analysisId`) so it can\n * pause/resume it or compare its `code` on re-arm. The job's `run` writes results back here via\n * {@link attachResult}. A one-shot analysis keeps a paused job (its recipe), so `monitorAnalysis` can\n * promote it later without re-declaring the code.\n *\n * Incident clustering is NOT here — it is typed, dedicated state owned by `TrafficIncidentsState`\n * (`clusters`), not a generic analysis.\n *\n * @group Agent Toolkit\n */\n\nimport type { Job } from '../engine';\nimport type { AnalysisOutputFormat } from '../tools/shared/sandbox-code';\nimport { StateEvents } from './events';\n\n/**\n * A single analysis result — the result of an `analyseData` run (one-shot or a `monitorAnalysis`\n * standing replay). `data` is whatever the dynamic code returned. Identical across every entry kind.\n *\n * @group Agent Toolkit\n *\n * @ignore\n */\nexport type EntryAnalysis = {\n /** Unique name within the parent entry — used as the key (re-attaching the same name replaces). */\n name: string;\n timestamp: number;\n /** Optional human-readable description of what the analysis computed. */\n description?: string;\n /** How `data` should be interpreted — plain JSON or a Chart.js configuration. */\n outputFormat: AnalysisOutputFormat;\n /** Arbitrary aggregation result returned by the dynamic code. */\n data: unknown;\n};\n\n/**\n * Maximum number of historical results retained per analysis name. Older entries are evicted as new\n * ones land. At a 60s monitor tick this is ~4 hours of trend data — enough for a chart, bounded for\n * memory.\n *\n * @group Agent Toolkit\n */\nexport const MAX_HISTORY_PER_ANALYSIS = 240;\n\n/**\n * Stable analysis handle: an analysis's `name` plus the sorted ids of every entry it is attached to.\n * An identical re-run (same name + same sources) yields the same id, so monitoring and result history\n * survive the re-run. Lives here (state layer) so both the slices and the tools-layer runtime can mint\n * ids without the state layer importing `tools/`.\n *\n * @group Agent Toolkit\n */\nexport const makeAnalysisId = (name: string, affectedIds: readonly string[]): string =>\n `${name}::${[...affectedIds].sort((a, b) => a.localeCompare(b)).join(',')}`;\n\n/**\n * One analysis tracked by the session-level {@link Analyses} registry — the single home for every\n * `analyseData` analysis, one-shot or monitored, across every entry kind. Pure sink state: metadata plus\n * the bounded result `history`. The recipe (`code` / `inputs`) and recurrence are NOT here — they live on\n * the analysis's {@link Job} (see `_jobs`). `affectedEntryIds` is the per-entry lookup key\n * ({@link Analyses.getAnalysesForEntry}); `history` is the result timeline (oldest first), whose latest\n * is the current result and is threaded back into the sandbox code as `previous`.\n *\n * @group Agent Toolkit\n */\nexport type AnalysisRecord = {\n /** Stable handle minted by {@link makeAnalysisId} (name + sorted source ids). */\n analysisId: string;\n name: string;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n /** Ids of every entry this analysis is attached to. The per-entry lookup key. */\n affectedEntryIds: readonly string[];\n /** Bounded result timeline, oldest first (see {@link MAX_HISTORY_PER_ANALYSIS}). */\n history: EntryAnalysis[];\n};\n\n/**\n * {@link Analyses.register} input — the record's metadata. No `history` (the registry builds that as\n * results land) and no recipe (that lives on the {@link Job}).\n *\n * @group Agent Toolkit\n */\nexport type AnalysisRegistration = Omit<AnalysisRecord, 'history'>;\n\n/**\n * Events emitted by {@link Analyses}. One event for \"an analysis result was attached or recomputed\".\n *\n * @ignore\n */\nexport type AnalysesEvents = {\n /**\n * An analysis result landed (one-shot attach or a recompute). Carries the `analysisId` and the ids\n * of every entry it touches — a per-entry consumer filters with `affectedEntryIds.includes(id)`.\n */\n 'analysis-change': { analysisId: string; affectedEntryIds: readonly string[] };\n};\n\n/**\n * Session-level registry of EVERY analysis — the single source of truth that replaces the per-entry\n * `_analyses` stores. Holds one {@link AnalysisRecord} per analysis (keyed by `analysisId`), each with\n * its own bounded result history. Entries carry no analysis state; per-entry views call\n * {@link getAnalysesForEntry}. Held on `ToolState` as `state.analyses`.\n *\n * Pure sink: metadata + result history only. The recurrence lives on the {@link JobEngine}; this\n * registry keeps each monitored analysis's {@link Job} handle in `_jobs` (keyed by `analysisId`) so it\n * can pause/resume it ({@link job}) or compare its `code` on re-arm. A one-shot analysis keeps a paused\n * job for later promotion.\n *\n * @group Agent Toolkit\n */\nexport class Analyses {\n private readonly _records = new Map<string, AnalysisRecord>();\n /** analysisId → the {@link Job} backing it (one-shot = paused). The reference linking record to engine. */\n private readonly _jobs = new Map<string, Job>();\n\n /** Subscribe to {@link AnalysesEvents} — fired whenever a result is attached or recomputed. */\n readonly events = new StateEvents<AnalysesEvents>();\n\n /**\n * Insert or replace a record's metadata (NOT its result — use {@link attachResult}; nor its recipe —\n * that lives on the {@link Job}). Re-registering an existing `analysisId` preserves its result\n * history; callers drop stale history explicitly via {@link resetHistory} when the recipe changes.\n */\n register(input: AnalysisRegistration): void {\n const existing = this._records.get(input.analysisId);\n this._records.set(input.analysisId, {\n analysisId: input.analysisId,\n name: input.name,\n description: input.description,\n outputFormat: input.outputFormat,\n affectedEntryIds: input.affectedEntryIds,\n history: existing?.history ?? [],\n });\n }\n\n /** Store the {@link Job} backing an analysis — the reference linking this record to the engine. */\n setJob(analysisId: string, job: Job): void {\n this._jobs.set(analysisId, job);\n }\n\n /** The {@link Job} backing an analysis, if monitored or a retained one-shot. */\n job(analysisId: string): Job | undefined {\n return this._jobs.get(analysisId);\n }\n\n /** Drop an analysis's result history — called on re-arm when the recipe changed (stale `previous`). */\n resetHistory(analysisId: string): void {\n const record = this._records.get(analysisId);\n if (record) record.history = [];\n }\n\n /**\n * Append a result to a record's timeline, evicting the oldest when the per-analysis cap is hit. A\n * same-timestamp re-run (an id-stability re-read of an unchanged snapshot) overwrites the last entry\n * rather than duplicating it. No-op (no emit) when the `analysisId` is unknown. Emits `analysis-change`.\n */\n attachResult(analysisId: string, result: EntryAnalysis): void {\n const record = this._records.get(analysisId);\n if (!record) return;\n const last = record.history.at(-1);\n if (last?.timestamp === result.timestamp) {\n record.history[record.history.length - 1] = result;\n } else {\n record.history.push(result);\n if (record.history.length > MAX_HISTORY_PER_ANALYSIS) record.history.shift();\n }\n this.events.emit('analysis-change', { analysisId, affectedEntryIds: record.affectedEntryIds });\n }\n\n /**\n * Latest result of every analysis attached to `entryId`, in registration order. The on-demand\n * replacement for the old per-entry `_analyses.results`. Linear scan over records — fine at session\n * scale (one record per distinct analyseData name+sources, plus one per clustered incidents entry).\n */\n getAnalysesForEntry(entryId: string): readonly EntryAnalysis[] {\n const out: EntryAnalysis[] = [];\n for (const record of this._records.values()) {\n if (!record.affectedEntryIds.includes(entryId)) continue;\n const latest = record.history.at(-1);\n if (latest) out.push(latest);\n }\n return out;\n }\n\n /**\n * Every analysis record, in registration order — the combined, cross-entry view. Read\n * `record.history.at(-1)` for each one's latest result. (Tracker verdicts are no longer analyses, so\n * every record here is operator-facing.)\n */\n all(): readonly AnalysisRecord[] {\n return [...this._records.values()];\n }\n\n /** Full result timeline for one analysis, oldest first. Empty when unknown. */\n history(analysisId: string): readonly EntryAnalysis[] {\n return this._records.get(analysisId)?.history ?? [];\n }\n\n /** Latest result data threaded into the next replay as `previous`, or undefined. */\n lastResult(analysisId: string): unknown {\n return this._records.get(analysisId)?.history.at(-1)?.data;\n }\n\n get(analysisId: string): AnalysisRecord | undefined {\n return this._records.get(analysisId);\n }\n\n /** Whether the analysis is currently recomputed on source change (its job exists and is active). */\n isMonitored(analysisId: string): boolean {\n return this._jobs.get(analysisId)?.active ?? false;\n }\n\n /** Remove an analysis: tear down its job and drop the record + handle. */\n remove(analysisId: string): void {\n this._jobs.get(analysisId)?.unregister();\n this._jobs.delete(analysisId);\n this._records.delete(analysisId);\n }\n\n /**\n * Remove every record sharing a `name`, across all source entries. Mirrors the old per-entry\n * `unregister(name)` which dropped a named analysis wherever it was attached.\n */\n removeByName(name: string): void {\n for (const [id, record] of this._records) if (record.name === name) this.remove(id);\n }\n\n /** Drop every record + job handle. Walked automatically by `destroyState` (duck-typed `reset`). */\n reset(): void {\n for (const job of this._jobs.values()) job.unregister();\n this._jobs.clear();\n this._records.clear();\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport { BaseMapModule, StylingFoundationsModule, TerrainModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport type { StateSlice } from '../../types';\n\n/**\n * State for base map display: style, language, viewport and layers.\n *\n * Provides direct access to the TomTomMap and MapLibre instances alongside\n * lazy-initialized BaseMapModule, StylingFoundationsModule and TerrainModule.\n *\n * @group Agent Toolkit\n */\nexport class BaseMapState implements StateSlice {\n private _baseMapModule?: BaseMapModule;\n private _stylingFoundationsModule?: StylingFoundationsModule;\n private _terrainModule?: TerrainModule;\n\n constructor(public readonly ttMap: TomTomMap) {}\n\n get mapLibreMap(): MapLibreMap {\n return this.ttMap.mapLibreMap;\n }\n\n // Module getters. The SDK shares style-owned modules per map, so these are lookups —\n // the field only records that we have fetched it, for the synchronous accessors below.\n\n async getBaseMapModule(): Promise<BaseMapModule> {\n this._baseMapModule = await BaseMapModule.get(this.ttMap);\n return this._baseMapModule;\n }\n\n async getStylingFoundationsModule(): Promise<StylingFoundationsModule> {\n this._stylingFoundationsModule = await StylingFoundationsModule.get(this.ttMap);\n return this._stylingFoundationsModule;\n }\n\n async getTerrainModule(): Promise<TerrainModule> {\n this._terrainModule = await TerrainModule.get(this.ttMap);\n return this._terrainModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get terrainModule(): TerrainModule | undefined {\n return this._terrainModule;\n }\n\n reset(): void {\n this._baseMapModule = undefined;\n this._stylingFoundationsModule = undefined;\n this._terrainModule = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n * @ignore\n *\n * Cross-slice helpers for entry-owning state slices. The agent-toolkit has seven such slices\n * (places, routing, ranges, customGeometries, trafficAreaAnalytics, trafficIncidents, byod) that\n * shared the same id-collision + single-mode-collapse code; consolidating it here keeps the\n * slices in lockstep on edge-case behaviour and makes the rules unit-testable in isolation.\n */\n\n/**\n * Return `requested` if no entry in `takenIds` already uses it; otherwise append `-2`, `-3`, …\n * until an unused id appears. Pure — only reads `takenIds`.\n *\n * Behaviour notes:\n * - `requested` is never modified, only suffixed (`\"foo\"` → `\"foo-2\"`, not `\"foo-1\"`). The first\n * suffix is `-2` so the chain reads naturally: \"foo, foo-2, foo-3, …\".\n * - Acceps any `Iterable<string>` (Set / Array / generator) — the slice's internal entry store\n * shape is irrelevant.\n *\n * @ignore\n */\nexport const pickUniqueEntryId = (requested: string, takenIds: Iterable<string>): string => {\n const taken = new Set(takenIds);\n if (!taken.has(requested)) return requested;\n for (let i = 2; ; i++) {\n const candidate = `${requested}-${i}`;\n if (!taken.has(candidate)) return candidate;\n }\n};\n\n/**\n * Hide every entry in `entries` and resolve once they're all settled. Sequential by default\n * (avoids MapLibre source-mutation races — see the project-wide `show/hide sequential` note);\n * pass `parallel: true` only when the slice's `hideOne` is provably race-safe (e.g. each entry\n * owns a separate module).\n *\n * Used in two places:\n * - `setEntryMode('single')` collapses history to the latest entry (via\n * {@link collapseHistoryToLatest}, which delegates to this primitive).\n * - `addEntry` under `single` mode replaces the entire history — same hide-all-then-drop\n * shape, but no survivor.\n *\n * Centralising this means addEntry implementations can `await` the hides instead of the\n * fire-and-forget `void this.hideEntry(…)` pattern that previously let a new entry render\n * while the old map modules were still being torn down.\n *\n * @ignore\n */\nexport const hideAllEntries = async <T>(\n entries: readonly T[],\n hideOne: (entry: T) => Promise<void>,\n options: { parallel?: boolean } = {},\n): Promise<void> => {\n if (entries.length === 0) return;\n if (options.parallel) {\n await Promise.all(entries.map(hideOne));\n } else {\n for (const entry of entries) await hideOne(entry);\n }\n};\n\n/**\n * Drop every entry except the last, hiding the dropped ones first. Returns the surviving entries\n * (an array with the latest entry, or empty when the input was empty).\n *\n * Used by `setEntryMode('single')` across every entry-owning slice — `single` mode means only the\n * latest entry stays on the map, so flipping the mode has to retroactively hide and forget every\n * older entry. The helper centralises the \"hide-then-drop\" order so a future change to hide\n * semantics lands in one place.\n *\n * @ignore\n */\nexport const collapseHistoryToLatest = async <T>(\n entries: readonly T[],\n hideOne: (entry: T) => Promise<void>,\n options: { parallel?: boolean } = {},\n): Promise<T[]> => {\n if (entries.length <= 1) return [...entries];\n const latest = entries[entries.length - 1];\n await hideAllEntries(entries.slice(0, -1), hideOne, options);\n return [latest];\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { CommonPlaceProps, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport {\n fillStyledGeometryConfig,\n GeometriesModule,\n type GeometryFillStyle,\n type TomTomMap,\n} from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { CustomGeometriesEntry } from './entry';\n\n/**\n * Events fired by {@link CustomGeometriesState}. Subscribe via\n * `state.customGeometries.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type CustomGeometriesStateEvents = {\n /**\n * History changed — entry added or cleared via `reset()`. `entries` is the full snapshot after\n * the change; `changedIds` lists the ids of the entries this specific change added, replaced in\n * place, or removed.\n */\n 'entries-change': { entries: readonly CustomGeometriesEntry[]; changedIds: readonly string[] };\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n// Dedupe features by their string `id`. Features without a string id are kept as-is (cannot\n// be deduped against anything).\nconst dedupeById = (features: PolygonFeature<CommonPlaceProps>[]): PolygonFeature<CommonPlaceProps>[] => {\n const seen = new Set<string>();\n const out: PolygonFeature<CommonPlaceProps>[] = [];\n for (const feature of features) {\n const featureId = typeof feature.id === 'string' ? feature.id : undefined;\n if (featureId) {\n if (seen.has(featureId)) continue;\n seen.add(featureId);\n }\n out.push(feature);\n }\n return out;\n};\n\n// Replace the module's currently-rendered FeatureCollection with `features`. An empty list\n// clears the layer.\nconst renderReplace = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) {\n await module.clear();\n return;\n }\n await module.show({ type: 'FeatureCollection', features });\n};\n\n// Merge `features` with whatever the module is currently rendering, deduped by feature id.\n// Empty input is a no-op (leaves whatever is on the layer in place).\nconst renderMerged = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) return;\n const current = (module.getShown().geometry.features ?? []) as PolygonFeature<CommonPlaceProps>[];\n const merged = dedupeById([...current, ...features]);\n await module.show({ type: 'FeatureCollection', features: merged });\n};\n\n/**\n * State for derived / custom geometries — output of `processData` and any future\n * polygon-producing tool whose result doesn't belong to a place or a reachable-range entry.\n *\n * Each entry owns a dedicated GeometriesModule, lazy-initialised on first show, mirroring\n * the per-entry display model used by {@link RangeState}. Provenance metadata captures the\n * input sources (tagged ids) and the operation that produced the polygons.\n *\n * @group Agent Toolkit\n */\nexport class CustomGeometriesState implements ShownEntriesSlice, StateSlice {\n private _entries: CustomGeometriesEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link CustomGeometriesStateEvents}. */\n readonly events = new StateEvents<CustomGeometriesStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly CustomGeometriesEntry[] {\n return this._entries;\n }\n\n get latestEntry(): CustomGeometriesEntry | undefined {\n return this._entries.at(-1);\n }\n\n /** Set of custom-geometries entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Display policy: `multiple` (default) lets several derived-geometry entries render side\n * by side; `single` enforces \"at most one entry on the map\" — switching to it auto-hides\n * older entries.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): CustomGeometriesEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new entry. If `explicitId` collides, the slice auto-suffixes (`-2`, `-3`, ...).\n * Returns the assigned id. Under `single` mode the new entry replaces every older one.\n */\n async addEntry(\n features: PolygonFeature<CommonPlaceProps>[],\n provenance: CustomGeometriesEntry['provenance'],\n label: string,\n explicitId?: string,\n ): Promise<string> {\n const droppedIds = this._entryMode === 'single' ? this._entries.map((entry) => entry.id) : [];\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n // The length-based fallback must be deduped too: after removals, `entries.length` can equal\n // the index of a surviving entry (add 3, remove the first → next fallback collides with the\n // last). A duplicate id makes every id-based lookup resolve the wrong entry.\n const fallback = `geometries-${this._entries.length}`;\n const entryId = pickUniqueEntryId(\n explicitId ?? fallback,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data: features,\n provenance,\n });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [...droppedIds, entryId] });\n return entryId;\n }\n\n /** Lazy-init the entry's GeometriesModule, applying / re-applying the requested fill style. */\n async getEntryGeometriesModule(\n entryId: string,\n fillStyle: GeometryFillStyle = 'outline',\n ): Promise<GeometriesModule> {\n const entry = this._requireEntry(entryId);\n // Module-level `fillStyle` flows into `prepareGeometryForDisplay` and is used as the\n // effective fill style for every feature that doesn't carry its own `properties.fillStyle`.\n const config = { ...fillStyledGeometryConfig(), fillStyle };\n if (!entry._module) {\n entry._module = await GeometriesModule.create(this._ttMap, config);\n entry._shownFillStyle = fillStyle;\n } else if (entry._shownFillStyle !== fillStyle) {\n entry._module.applyConfig(config);\n entry._shownFillStyle = fillStyle;\n }\n return entry._module;\n }\n\n /**\n * Render this entry's polygons. `mode` is scoped to this entry's own layer:\n * - `'replace'` (default) — show exactly `entry.data`, clearing whatever the\n * entry's module had rendered before.\n * - `'add'` — merge `entry.data` with the polygons currently shown on this entry's\n * layer (deduped by feature id). Mostly only meaningful when re-showing the same entry.\n *\n * Cross-entry visibility is handled separately: under `entryMode === 'single'` other\n * shown entries are hidden first; otherwise the caller uses `hideEntry` / the\n * tools' `hidePreviousEntries` option.\n */\n async showEntry(\n entryId: string,\n fillStyle: GeometryFillStyle = 'outline',\n mode: 'add' | 'replace' = 'replace',\n ): Promise<void> {\n const entry = this._requireEntry(entryId);\n await this._hideOthersUnderSingleMode(entryId);\n const module = await this.getEntryGeometriesModule(entryId, fillStyle);\n const features = entry.data as PolygonFeature<CommonPlaceProps>[];\n if (mode === 'add') {\n await renderMerged(module, features);\n } else {\n await renderReplace(module, features);\n }\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n private async _hideOthersUnderSingleMode(entryId: string): Promise<void> {\n if (this._entryMode !== 'single') return;\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n if (entry._module) await entry._module.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Drop a single entry from history (clears its module first). No-op when unknown. */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n }\n\n private _requireEntry(entryId: string): CustomGeometriesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`CustomGeometriesState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every custom-geometries entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n const clearedIds = this._entries.map((entry) => entry.id);\n for (const entry of this._entries) entry._module?.clear();\n this._entries = [];\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n *\n * Compact snapshots of user-visible state, used to inform the LLM about changes that happened\n * outside the conversation (e.g. the user clicked a recents shortcut in the demo's UI).\n *\n * Pattern of use:\n * 1. After every assistant turn, call `getStateDigest(state)` and stash the result.\n * 2. Before sending the next user prompt, call `getStateDigest` again and pass both into\n * `formatStateDigestDiff`. If a non-empty string comes back, prepend it to the prompt so\n * the LLM sees what changed since its last response.\n *\n * Attribution is implicit: anything the LLM's tool calls did happens during the assistant turn\n * (so it's already reflected in the post-turn snapshot). Anything that changes between turns is,\n * by construction, UI-driven.\n */\n\nimport type { ToolState } from '../types';\nimport type { EntryMode } from './state';\n\n// `Array.prototype.sort` without a comparator falls back to UTF-16 code-unit order — fine for\n// ASCII-only ids but unreliable once a localised label leaks in. Centralise the locale-aware\n// comparator so every digest field gets the same predictable order.\nconst compareIds = (a: string, b: string): number => a.localeCompare(b);\nconst sortIds = (ids: Iterable<string>): string[] => [...ids].sort(compareIds);\n\n/**\n * Compact snapshot of the user-visible parts of `ToolState`. Designed to be diffable: every field\n * is serialisable to JSON and stable for `===` comparison when the underlying state hasn't changed.\n *\n * @group Agent Toolkit\n */\nexport type StateDigest = {\n places: {\n /** Entry ids currently rendered as individual pins, sorted ascending. */\n shownAsPin: string[];\n /** Entry ids currently rendered as base-map POIs, sorted ascending. */\n shownAsBaseMap: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n routes: {\n /** Routes-entry ids currently drawn on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n ranges: {\n /** Range-entry ids currently drawn on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n traffic: {\n /** Traffic-area-analytics entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total traffic-area-analytics entries in history (shown + hidden). */\n entryCount: number;\n };\n incidents: {\n /** Traffic-incidents entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Entry ids currently being polled by the per-entry network monitor, sorted ascending. */\n monitored: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n };\n customGeometries: {\n /** Custom-geometries entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total custom-geometries entries in history (shown + hidden). */\n entryCount: number;\n };\n byod: {\n /** BYOD entry ids currently rendered on the map, sorted ascending. */\n shown: string[];\n /** Display policy in force on the slice — `multiple` (default) or `single`. */\n entryMode: EntryMode;\n /** Total BYOD entries in history (shown + hidden). */\n entryCount: number;\n };\n};\n\n/**\n * Snapshot the user-visible state surface. Call after every assistant turn (to record the\n * \"agent-known\" baseline) and again before each user prompt (to detect what changed since).\n *\n * @group Agent Toolkit\n */\nexport const getStateDigest = (state: ToolState): StateDigest => ({\n places: {\n shownAsPin: sortIds(state.places.shownAsPinIds),\n shownAsBaseMap: sortIds(state.places.shownAsBaseMapIds),\n entryMode: state.places.entryMode,\n },\n routes: { shown: sortIds(state.routing.shownEntryIds), entryMode: state.routing.entryMode },\n ranges: { shown: sortIds(state.ranges.shownEntryIds), entryMode: state.ranges.entryMode },\n traffic: {\n shown: sortIds(state.trafficAreaAnalytics.shownEntryIds),\n entryMode: state.trafficAreaAnalytics.entryMode,\n entryCount: state.trafficAreaAnalytics.entries.length,\n },\n incidents: {\n shown: sortIds(state.trafficIncidents.shownEntryIds),\n monitored: sortIds(\n state.trafficIncidents.entries\n .filter((entry) => entry._monitor?.status === 'running')\n .map((entry) => entry.id),\n ),\n entryMode: state.trafficIncidents.entryMode,\n },\n customGeometries: {\n shown: sortIds(state.customGeometries.shownEntryIds),\n entryMode: state.customGeometries.entryMode,\n entryCount: state.customGeometries.entries.length,\n },\n byod: {\n shown: sortIds(state.byod.shownEntryIds),\n entryMode: state.byod.entryMode,\n entryCount: state.byod.entries.length,\n },\n});\n\nconst arraysEqual = (a: readonly string[], b: readonly string[]): boolean => {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n};\n\nconst formatPlacesDiff = (prev: StateDigest['places'], next: StateDigest['places']): string | undefined => {\n const fragments: string[] = [];\n if (!arraysEqual(prev.shownAsPin, next.shownAsPin)) {\n fragments.push(\n next.shownAsPin.length === 0\n ? 'no places shown as pins'\n : `places shown as pins: ${next.shownAsPin.map((id) => `\"${id}\"`).join(', ')}`,\n );\n }\n if (!arraysEqual(prev.shownAsBaseMap, next.shownAsBaseMap)) {\n fragments.push(\n next.shownAsBaseMap.length === 0\n ? 'no places shown as base-map POIs'\n : `places shown as base-map POIs: ${next.shownAsBaseMap.map((id) => `\"${id}\"`).join(', ')}`,\n );\n }\n return fragments.length ? fragments.join('; ') : undefined;\n};\n\n// Diffs a sorted-array snapshot of \"currently shown\" entry ids and produces a one-line\n// summary. Used for routes / ranges where multiple entries can be on the map under\n// `multiple` mode — the digest can't be a scalar anymore.\nconst formatShownSetDiff = (label: string, prev: readonly string[], next: readonly string[]): string | undefined => {\n if (arraysEqual(prev, next)) return undefined;\n if (next.length === 0) return `${label} cleared`;\n return `${label} shown: ${next.map((id) => `\"${id}\"`).join(', ')}`;\n};\n\n/**\n * Produce a compact, LLM-readable summary of the differences between two digests, or `null` when\n * nothing changed. The output is short enough to prepend to a user prompt without bloating the\n * conversation; longer per-field diffs are deliberately omitted.\n *\n * @example\n * ```ts\n * const prev = getStateDigest(state);\n * // ... user clicks around in the UI ...\n * const diff = formatStateDigestDiff(prev, getStateDigest(state));\n * // → '[map state changed since last response: places shown as pins: \"amsterdam-cinemas\"; route now \"routes-2\"]'\n * ```\n *\n * @group Agent Toolkit\n */\nexport const formatStateDigestDiff = (prev: StateDigest, next: StateDigest): string | null => {\n const fragments: string[] = [];\n\n const placesFragment = formatPlacesDiff(prev.places, next.places);\n if (placesFragment) fragments.push(placesFragment);\n\n const routeFragment = formatShownSetDiff('routes', prev.routes.shown, next.routes.shown);\n if (routeFragment) fragments.push(routeFragment);\n\n const rangeFragment = formatShownSetDiff('ranges', prev.ranges.shown, next.ranges.shown);\n if (rangeFragment) fragments.push(rangeFragment);\n\n const trafficAnalyticsFragment = formatShownSetDiff('traffic analytics', prev.traffic.shown, next.traffic.shown);\n if (trafficAnalyticsFragment) fragments.push(trafficAnalyticsFragment);\n\n const incidentsShownFragment = formatShownSetDiff('incidents', prev.incidents.shown, next.incidents.shown);\n if (incidentsShownFragment) fragments.push(incidentsShownFragment);\n\n const monitoredFragment = formatShownSetDiff(\n 'incidents monitor',\n prev.incidents.monitored,\n next.incidents.monitored,\n );\n if (monitoredFragment) fragments.push(monitoredFragment);\n\n const customGeometriesFragment = formatShownSetDiff(\n 'custom geometries',\n prev.customGeometries.shown,\n next.customGeometries.shown,\n );\n if (customGeometriesFragment) fragments.push(customGeometriesFragment);\n\n const byodFragment = formatShownSetDiff('byod', prev.byod.shown, next.byod.shown);\n if (byodFragment) fragments.push(byodFragment);\n\n // Entry-mode flips are surfaced last so the LLM sees them as policy\n // changes, separate from \"what's on the map right now\".\n if (prev.places.entryMode !== next.places.entryMode) {\n fragments.push(`places entryMode → ${next.places.entryMode}`);\n }\n if (prev.routes.entryMode !== next.routes.entryMode) {\n fragments.push(`routes entryMode → ${next.routes.entryMode}`);\n }\n if (prev.ranges.entryMode !== next.ranges.entryMode) {\n fragments.push(`ranges entryMode → ${next.ranges.entryMode}`);\n }\n if (prev.incidents.entryMode !== next.incidents.entryMode) {\n fragments.push(`incidents entryMode → ${next.incidents.entryMode}`);\n }\n if (prev.customGeometries.entryMode !== next.customGeometries.entryMode) {\n fragments.push(`custom geometries entryMode → ${next.customGeometries.entryMode}`);\n }\n if (prev.byod.entryMode !== next.byod.entryMode) {\n fragments.push(`byod entryMode → ${next.byod.entryMode}`);\n }\n\n if (fragments.length === 0) return null;\n return `[map state changed since last response: ${fragments.join('; ')}]`;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport { POIsModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\n\n/**\n * State for the map POI layer (points of interest overlay).\n *\n * Holds the lazy-initialized POIsModule for showing, hiding, and filtering\n * the built-in POI categories rendered on the base map.\n *\n * @group Agent Toolkit\n */\nexport class MapPOIsState implements StateSlice {\n private _poisModule?: POIsModule;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getter. The SDK shares style-owned modules per map, so this is a lookup —\n // the field only records that we have fetched it, for the synchronous accessor below.\n\n async getPOIsModule(): Promise<POIsModule> {\n this._poisModule = await POIsModule.get(this._ttMap);\n return this._poisModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get poisModule(): POIsModule | undefined {\n return this._poisModule;\n }\n\n reset(): void {\n this._poisModule = undefined;\n }\n}\n","{\n \"name\": \"@tomtom-org/maps-sdk-plugin-agent-toolkit\",\n \"author\": \"TomTom International B.V.\",\n \"license\": \"See in LICENSE.txt\",\n \"version\": \"0.13.0\",\n \"type\": \"module\",\n \"description\": \"Agent toolkit plugin for TomTom Maps SDK JS using Vercel AI SDK\",\n \"types\": \"dist/index.d.ts\",\n \"scripts\": {\n \"build\": \"vite build --config vite.lib.config.ts\",\n \"build:watch\": \"vite build --watch --config vite.lib.config.ts\",\n \"type-check\": \"tsc --noEmit\",\n \"test\": \"vitest run --exclude '**/tests/scenarios/**'\",\n \"test:coverage\": \"vitest run --coverage --exclude '**/tests/scenarios/**'\",\n \"test:watch\": \"vitest --exclude '**/tests/scenarios/**'\",\n \"test:agent-tool-calling\": \"vitest run src/tests/scenarios\",\n \"test:agent-tool-calling:full\": \"cross-env SCENARIOS_FULL=1 vitest run src/tests/scenarios\",\n \"test:agent-tool-calling:watch\": \"vitest src/tests/scenarios\",\n \"test:e2e\": \"playwright test --config e2e-tests/playwright.config.ts\",\n \"test:e2e:ui\": \"playwright test --config e2e-tests/playwright.config.ts --ui\",\n \"test:e2e:server\": \"vite --config e2e-tests/vite.config.ts\",\n \"test:e2e:install\": \"playwright install chromium\"\n },\n \"peerDependencies\": {\n \"@tomtom-org/maps-sdk\": \">=0.64.0 <1.0.0\",\n \"@tomtom-org/maps-sdk-plugin-map-theme\": \">=0.3.0\",\n \"@turf/turf\": \"catalog:\",\n \"ai\": \"catalog:\",\n \"chart.js\": \"catalog:\",\n \"h3-js\": \"catalog:\",\n \"maplibre-gl\": \"catalog:\",\n \"zod\": \"catalog:\"\n },\n \"devDependencies\": {\n \"@ai-sdk/azure\": \"catalog:\",\n \"@langwatch/scenario\": \"^0.4.15\",\n \"@playwright/test\": \"catalog:\",\n \"@testing/agent-tool-calling\": \"workspace:*\",\n \"@tomtom-org/maps-sdk\": \"workspace:*\",\n \"@tomtom-org/maps-sdk-plugin-map-theme\": \"workspace:*\",\n \"@tsconfig/recommended\": \"catalog:\",\n \"@types/geojson\": \"catalog:\",\n \"@types/node\": \"catalog:\",\n \"@vitest/coverage-v8\": \"catalog:\",\n \"ai\": \"catalog:\",\n \"cross-env\": \"^10.1.0\",\n \"rolldown\": \"catalog:\",\n \"typescript\": \"catalog:\",\n \"vite\": \"catalog:\",\n \"vitest\": \"catalog:\"\n },\n \"files\": [\n \"dist\",\n \"package.json\",\n \"README.md\",\n \"LICENSE.txt\",\n \"THIRD_PARTY.txt\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.es.js\"\n }\n }\n}\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { TOMTOM_USER_AGENT_SDK_NAME } from '@tomtom-org/maps-sdk/core';\nimport { version } from '../../../package.json';\n\nexport const AGENT_TOOLKIT_ID = 'AgentToolkit';\n\n/**\n * The `tomtom-user-agent` value identifying agent-toolkit traffic. Shared by both paths that carry\n * it — {@link withAgentToolkitHeaders} on service calls, `createMapAgent` on the map — so the two\n * cannot drift into separate analytics rows.\n *\n * @ignore\n */\nexport const AGENT_TOOLKIT_USER_AGENT = `${TOMTOM_USER_AGENT_SDK_NAME}-${AGENT_TOOLKIT_ID}/${version}`;\n\n/**\n * Tags an SDK service call as coming from agent-toolkit, via the SDK's own supported per-call\n * `tomtom-user-agent` override (see `generateTomTomHeaders`, `core/src/util/headers.ts`) — reuses\n * an existing, already-allow-listed header rather than adding a new one.\n *\n * @ignore\n */\nexport const withAgentToolkitHeaders = <P extends object>(params: P): P => ({\n ...params,\n 'tomtom-user-agent': AGENT_TOOLKIT_USER_AGENT,\n});\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { CommonPlaceProps, Place, Places, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport {\n fillStyledGeometryConfig,\n GeometriesModule,\n type GeometryFillStyle,\n type PlaceConnectionDisplay,\n PlacesModule,\n type PlacesMarkerType as SDKPlacesMarkerType,\n type TomTomMap,\n} from '@tomtom-org/maps-sdk/map';\nimport { geometryData } from '@tomtom-org/maps-sdk/services';\nimport { withAgentToolkitHeaders } from '../../tools/shared/agent-headers';\nimport type { StateSlice, ToolExecuteOptions } from '../../types';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode } from '../state';\nimport type { PlacesEntry } from './entry';\n\n// Geometry Data service accepts up to 20 ids per request; we intentionally cap our batches\n// at 5 to keep individual requests small (lower per-call latency, easier to fan out in\n// parallel without hitting rate limits). Any larger input set is split into multiple chunks.\nconst GEOMETRY_DATA_BATCH_SIZE = 5;\n\n/**\n * The marker looks an entry can be rendered with: individual pins, base-map POIs, or clustered\n * pins. A type-checked subset of the SDK's `PlacesMarkerType` — `circle-icon` is not offered to\n * the agent. Every tool enum for marker types derives from this array rather than repeating it.\n */\nexport const placesMarkerTypes = ['pin', 'base-map', 'pin-clustered'] as const satisfies readonly SDKPlacesMarkerType[];\n\n/** How an entry is rendered on the map — one of {@link placesMarkerTypes}. */\nexport type PlacesMarkerType = (typeof placesMarkerTypes)[number];\n\n/**\n * Events fired by {@link PlacesState}. Subscribe via `state.places.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type PlacesStateEvents = {\n /**\n * History changed — entry added, replaced, or cleared via `reset()`. `entries` is the full\n * snapshot after the change; `changedIds` lists the ids of the entries this specific change\n * added, replaced in place, or removed (the {@link JobEngine} matches these against each job's\n * `affectedEntryIds`).\n */\n 'entries-change': { entries: readonly PlacesEntry[]; changedIds: readonly string[] };\n /** Set of entries currently rendered on the map changed (or marker type swapped). */\n 'shown-change': ReadonlySet<string>;\n /** Boundary polygons were fetched and cached on an entry. */\n 'geometries-change': { entryId: string; count: number };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n// Dedupe features by their string `id`. Features without a string id are kept as-is.\nconst dedupeById = (features: PolygonFeature<CommonPlaceProps>[]): PolygonFeature<CommonPlaceProps>[] => {\n const seen = new Set<string>();\n const out: PolygonFeature<CommonPlaceProps>[] = [];\n for (const feature of features) {\n const featureId = typeof feature.id === 'string' ? feature.id : undefined;\n if (featureId) {\n if (seen.has(featureId)) continue;\n seen.add(featureId);\n }\n out.push(feature);\n }\n return out;\n};\n\n// Replace the module's currently-rendered FeatureCollection with `features`. An empty list\n// clears the layer.\nconst renderReplace = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) {\n await module.clear();\n return;\n }\n await module.show({ type: 'FeatureCollection', features });\n};\n\n// Merge `features` with whatever the module is currently rendering, deduped by feature id.\n// Empty input is a no-op (leaves whatever is on the layer in place).\nconst renderMerged = async (module: GeometriesModule, features: PolygonFeature<CommonPlaceProps>[]): Promise<void> => {\n if (features.length === 0) return;\n const current = (module.getShown().geometry.features ?? []) as PolygonFeature<CommonPlaceProps>[];\n const merged = dedupeById([...current, ...features]);\n await module.show({ type: 'FeatureCollection', features: merged });\n};\n\n/**\n * State for the places layer: an append-only history of place results plus per-entry display\n * modules.\n *\n * Each entry owns a single PlacesModule (switched in place via `updateConfig` when the marker\n * type changes — an entry only renders under one marker type at a time) and a dedicated\n * GeometriesModule, lazy-initialised on first show. Display state lives on the entry, so two\n * entries can be rendered side by side under different marker types without sharing a single source.\n *\n * @group Agent Toolkit\n */\nexport class PlacesState implements StateSlice {\n private _entries: PlacesEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link PlacesStateEvents}. */\n readonly events = new StateEvents<PlacesStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n /**\n * Display policy: `multiple` (default) keeps every history entry available to render\n * simultaneously; `single` enforces \"at most one entry on the map at a time\" and trims\n * the history down to the latest entry whenever it's set.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every entry\n * older than the latest, drop them from history, and emit `entries-change` so listeners\n * (the recents rail, recall tools, …) re-render. No-op when the mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this._hideEntry(entry));\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n this.events.emit('mode-change', mode);\n }\n\n /**\n * Lazy-init and return the entry's PlacesModule, switched to `markerType`. An entry only\n * ever renders under one marker type at a time, so the same module is reused — flipping marker\n * types calls `updateConfig` in place. Throws when the entry id is unknown.\n */\n async getEntryPlacesModule(entryId: string, markerType: PlacesMarkerType): Promise<PlacesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n if (!entry._modules.places) {\n entry._modules.places = await PlacesModule.create(this._ttMap, { markerType });\n } else if (entry._shownAs !== markerType) {\n entry._modules.places.updateConfig({ markerType });\n }\n return entry._modules.places;\n }\n\n /**\n * Lazy-init and return the entry's place-geometries module (for POI footprints, municipality\n * outlines). Re-applies the config when the fill style changes. Throws on unknown entry id.\n */\n async getEntryGeometriesModule(\n entryId: string,\n fillStyle: GeometryFillStyle = 'outline',\n ): Promise<GeometriesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n // Module-level `fillStyle` flows into `prepareGeometryForDisplay` and is used as the\n // effective fill style for every feature that doesn't carry its own `properties.fillStyle`.\n const config = { ...fillStyledGeometryConfig(), fillStyle };\n if (!entry._modules.geometries) {\n entry._modules.geometries = await GeometriesModule.create(this._ttMap, config);\n } else {\n entry._modules.geometries.applyConfig(config);\n }\n return entry._modules.geometries;\n }\n\n private _requireEntry(entryId: string): PlacesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`PlacesState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n get entries(): readonly PlacesEntry[] {\n return this._entries;\n }\n\n /**\n * This is used purely to pass data between tools in a context-dependent way,\n * so we assume the most recent entry is the relevant one to show on the map.\n */\n get latestPlace(): Place[] | undefined {\n return this._entries.at(-1)?.data;\n }\n\n async addPlaceResult(\n place: Place | Places,\n label: string,\n explicitId?: string,\n connections?: PlaceConnectionDisplay[],\n geometries?: PolygonFeature<CommonPlaceProps>[],\n ): Promise<string> {\n // Under `single` mode the new entry replaces everything older. Hide and await each\n // older entry's modules before pushing the new one — otherwise the caller's render\n // can race with the in-flight teardown and the previous markers flicker.\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this._hideEntry(entry));\n this._entries = [];\n }\n // Dedupe the length-based fallback too — after removals it can collide with a surviving entry.\n const fallback = `places-${this._entries.length}`;\n const entryId = pickUniqueEntryId(\n explicitId ?? fallback,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data: 'features' in place ? place.features : [place],\n ...(connections?.length && { connections }),\n ...(geometries?.length && { geometries }),\n });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n // Surface geometries via the same `geometries-change` event consumers\n // already use for `fetchGeometriesForEntry`, so a place panel that\n // listens once picks them up regardless of which writer added them.\n if (geometries?.length) {\n this.events.emit('geometries-change', { entryId, count: geometries.length });\n }\n return entryId;\n }\n\n // Scans entries (newest first) for a Place by its feature id. Each entry\n // lazily caches an id → Place record on first miss so repeated lookups stay\n // O(1) per entry. Safe because entry.data is effectively immutable after\n // addPlaceResult — no mutation path invalidates the index.\n findPlaceById(placeId: string): { place: Place; entryId: string } | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n const entry = this._entries[i];\n if (!entry._byId) {\n entry._byId = {};\n for (const place of entry.data) entry._byId[place.id] = place;\n }\n const place = entry._byId[placeId];\n if (place) return { place, entryId: entry.id };\n }\n return undefined;\n }\n\n // Expands an ENTRY id to the feature ids of its places that carry a geometry data source — the\n // ids `resolveWithinAreas` can fetch a boundary polygon for. Returns `undefined` when no entry\n // with this id exists (so the caller can fall back to treating the id as a place feature id),\n // or `[]` when the entry exists but holds no geometry-bearing place (addresses/streets often\n // lack one). Keeps the place-id keying in one place alongside findPlaceById.\n geometryPlaceIdsForEntry(entryId: string): string[] | undefined {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return undefined;\n return entry.data\n .filter((place) => place.properties.dataSources?.geometry?.id && typeof place.id === 'string')\n .map((place) => place.id as string);\n }\n\n /**\n * Returns the cached boundary polygon for a place id, or undefined if no geometry has been\n * fetched for it yet. Builds a lazy place-id → feature index per entry on first lookup.\n */\n getGeometryForPlace(placeId: string): PolygonFeature<CommonPlaceProps> | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n const entry = this._entries[i];\n if (!entry.geometries?.length) continue;\n if (!entry._geometryByPlaceId) entry._geometryByPlaceId = this._buildGeometryIndex(entry);\n const geometry = entry._geometryByPlaceId[placeId];\n if (geometry) return geometry;\n }\n return undefined;\n }\n\n /**\n * Fetches the boundary polygon for a place from the Geometry Data service and stores it on\n * the owning entry. Returns the cached feature if already fetched, or undefined if the place\n * is unknown or has no geometry data source.\n */\n async fetchPlaceGeometry(\n placeId: string,\n options?: ToolExecuteOptions,\n ): Promise<PolygonFeature<CommonPlaceProps> | undefined> {\n const cached = this.getGeometryForPlace(placeId);\n if (cached) return cached;\n\n const lookup = this.findPlaceById(placeId);\n if (!lookup) return undefined;\n const geometryDataSourceId = lookup.place.properties.dataSources?.geometry?.id;\n if (!geometryDataSourceId) return undefined;\n\n const requestParams = withAgentToolkitHeaders({\n geometries: [lookup.place],\n signal: options?.signal,\n });\n const result = await geometryData(requestParams);\n const feature = result.features[0];\n if (!feature) return undefined;\n\n const entry = this._entries.find((e) => e.id === lookup.entryId);\n if (entry) {\n entry.geometries ??= [];\n entry.geometries.push(feature);\n entry._geometryByPlaceId ??= {};\n entry._geometryByPlaceId[placeId] = feature;\n this.events.emit('geometries-change', { entryId: entry.id, count: entry.geometries.length });\n }\n return feature;\n }\n\n /**\n * Batch-fetches boundary polygons for every place in an entry that has a geometry data source\n * but is not yet cached. Issues one Geometry Data request per chunk of {@link GEOMETRY_DATA_BATCH_SIZE}\n * place ids (the API caps each request) and stores the merged result on `entry.geometries`.\n * Returns the full geometries list for the entry, or an empty array if the entry is unknown.\n */\n async fetchGeometriesForEntry(\n entryId: string,\n options?: ToolExecuteOptions,\n ): Promise<PolygonFeature<CommonPlaceProps>[]> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return [];\n\n const alreadyFetched = new Set<string>();\n for (const feature of entry.geometries ?? []) {\n if (typeof feature.id === 'string') alreadyFetched.add(feature.id);\n }\n\n // Dedupe by geometry-data-source id so a place repeated in the entry (or sharing a footprint)\n // contributes one request, not several.\n const placesNeedingFetch: Place[] = [];\n const queuedGeometryIds = new Set<string>();\n for (const place of entry.data) {\n const geometryDataSourceId = place.properties.dataSources?.geometry?.id;\n if (!geometryDataSourceId) continue;\n if (alreadyFetched.has(geometryDataSourceId)) continue;\n if (queuedGeometryIds.has(geometryDataSourceId)) continue;\n queuedGeometryIds.add(geometryDataSourceId);\n placesNeedingFetch.push(place);\n }\n\n if (placesNeedingFetch.length === 0) return entry.geometries ?? [];\n\n const batches: Place[][] = [];\n for (let i = 0; i < placesNeedingFetch.length; i += GEOMETRY_DATA_BATCH_SIZE) {\n batches.push(placesNeedingFetch.slice(i, i + GEOMETRY_DATA_BATCH_SIZE));\n }\n const results = await Promise.all(\n batches.map((batch) =>\n geometryData(\n withAgentToolkitHeaders({\n geometries: batch,\n signal: options?.signal,\n }),\n ),\n ),\n );\n\n const merged: PolygonFeature<CommonPlaceProps>[] = [...(entry.geometries ?? [])];\n for (const result of results) merged.push(...result.features);\n\n entry.geometries = merged;\n // Invalidate the lazy place-id index so the next lookup rebuilds it from the fuller list.\n entry._geometryByPlaceId = undefined;\n this.events.emit('geometries-change', { entryId: entry.id, count: merged.length });\n return merged;\n }\n\n /**\n * Render the given polygons via the entry's GeometriesModule. The `mode` flag is\n * scoped to **this entry's layer only** — it does not touch other entries (use\n * the slice's per-entry hide/show methods for cross-entry visibility).\n *\n * - `mode: 'replace'` (default) — calls `module.show` with exactly these features, replacing\n * whatever was previously on this entry's layer. An empty array clears the layer.\n * - `mode: 'add'` — merges the new features with the polygons currently shown on this\n * entry's layer (deduped by `feature.id`) and re-renders the union. An empty array is a\n * no-op.\n *\n * `entryId` defaults to the latest entry that owns geometries — handy when callers only ever\n * render the most-recently produced footprints. Pass an explicit id to target an older one.\n */\n async showPlaceGeometries(\n features: PolygonFeature<CommonPlaceProps>[],\n fillStyle: GeometryFillStyle = 'outline',\n mode: 'add' | 'replace' = 'replace',\n entryId?: string,\n ): Promise<void> {\n const targetId = entryId ?? this._latestGeometriesEntryId();\n if (!targetId) return;\n const module = await this.getEntryGeometriesModule(targetId, fillStyle);\n if (mode === 'add') {\n await renderMerged(module, features);\n } else {\n await renderReplace(module, features);\n }\n }\n\n // Walks entries newest-first looking for the most recent one that has any\n // boundary polygons attached — that's the natural default for callers that\n // didn't pin a specific entry id (e.g. the legacy showPlaceGeometries surface).\n private _latestGeometriesEntryId(): string | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n if (this._entries[i].geometries?.length) return this._entries[i].id;\n }\n return this._entries.at(-1)?.id;\n }\n\n // Each entry's geometries are stored as an array (mirroring the geometryData response shape);\n // build an O(1) place-id → feature lookup on first read.\n private _buildGeometryIndex(entry: PlacesEntry): Record<string, PolygonFeature<CommonPlaceProps>> {\n const byGeometryId = new Map<string, PolygonFeature<CommonPlaceProps>>();\n for (const feature of entry.geometries ?? []) {\n if (typeof feature.id === 'string') byGeometryId.set(feature.id, feature);\n }\n const index: Record<string, PolygonFeature<CommonPlaceProps>> = {};\n for (const place of entry.data) {\n const geometryId = place.properties.dataSources?.geometry?.id;\n if (!geometryId) continue;\n const feature = byGeometryId.get(geometryId);\n if (feature) index[place.id] = feature;\n }\n return index;\n }\n\n /** IDs of all entries currently shown on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) {\n if (entry._shownAs) out.add(entry.id);\n }\n return out;\n }\n\n /** IDs of entries currently shown as individual pins. */\n get shownAsPinIds(): ReadonlySet<string> {\n return this._idsShownAs('pin');\n }\n\n /** IDs of entries currently shown as base-map POIs. */\n get shownAsBaseMapIds(): ReadonlySet<string> {\n return this._idsShownAs('base-map');\n }\n\n /** IDs of entries currently shown as clustered pins. */\n get shownAsPinClusteredIds(): ReadonlySet<string> {\n return this._idsShownAs('pin-clustered');\n }\n\n private _idsShownAs(markerType: PlacesMarkerType): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) {\n if (entry._shownAs === markerType) out.add(entry.id);\n }\n return out;\n }\n\n /** Marker type for a specific entry, or undefined if it is not shown. */\n getShownMarkerType(id: string): PlacesMarkerType | undefined {\n return this._entries.find((entry) => entry.id === id)?._shownAs;\n }\n\n /** Return the union of all features belonging to currently-shown entries. */\n getShownFeatures(): Place[] {\n const out: Place[] = [];\n for (const entry of this._entries) {\n if (entry._shownAs) out.push(...entry.data);\n }\n return out;\n }\n\n /**\n * Replace the shown set for `markerType` with exactly these ids and re-render.\n * Entries currently shown as `markerType` but absent from `ids` are hidden;\n * entries in `ids` are switched to `markerType` (lazy-creating their module).\n */\n async setShownEntries(ids: readonly string[], markerType: PlacesMarkerType): Promise<Place[]> {\n const idSet = new Set(ids);\n const targets: PlacesEntry[] = [];\n const toHide: PlacesEntry[] = [];\n for (const entry of this._entries) {\n if (idSet.has(entry.id)) {\n targets.push(entry);\n } else if (entry._shownAs === markerType) {\n toHide.push(entry);\n }\n }\n for (const entry of toHide) await this._hideEntry(entry);\n for (const entry of targets) await this._showEntryAs(entry, markerType);\n this.events.emit('shown-change', this.shownEntryIds);\n return this.getShownFeatures();\n }\n\n /**\n * Add ids to the shown set under `markerType`. Any id previously shown as\n * a different marker type is moved (its module switches to the new\n * marker type).\n *\n * Under `entryMode === 'single'` everything else is hidden first, so the\n * effective behaviour collapses to \"show exactly the last id in the list\".\n */\n async addShownEntries(ids: readonly string[], markerType: PlacesMarkerType): Promise<Place[]> {\n if (this._entryMode === 'single') {\n const lastId = ids.at(-1);\n return lastId ? this.setShownEntries([lastId], markerType) : this.getShownFeatures();\n }\n const targets = ids.flatMap((id) => {\n const entry = this._entries.find((e) => e.id === id);\n return entry ? [entry] : [];\n });\n const changed = targets.filter((entry) => entry._shownAs !== markerType);\n for (const entry of changed) await this._showEntryAs(entry, markerType);\n if (changed.length > 0) this.events.emit('shown-change', this.shownEntryIds);\n return this.getShownFeatures();\n }\n\n /** Remove ids from the shown set and clear their modules. */\n async removeShownEntries(ids: readonly string[]): Promise<Place[]> {\n const targets = ids.flatMap((id) => {\n const entry = this._entries.find((e) => e.id === id);\n return entry?._shownAs ? [entry] : [];\n });\n for (const entry of targets) await this._hideEntry(entry);\n if (targets.length > 0) this.events.emit('shown-change', this.shownEntryIds);\n return this.getShownFeatures();\n }\n\n /** Clear everything from the map (does not touch entries history). */\n async clearShownEntries(): Promise<void> {\n const shown = this._entries.filter((entry) => entry._shownAs);\n if (shown.length === 0) return;\n for (const entry of shown) await this._hideEntry(entry);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Hide every PlacesState rendering — pins AND attached boundary polygons. Map-only:\n * history (`entries`) is untouched. Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n await this.clearShownEntries();\n await this.clearShownGeometries();\n }\n\n // Clears the rendered boundary polygons for the given entries (or every entry when `ids`\n // is omitted). Cached `entry.geometries` features are kept so re-showing is instant.\n async clearShownGeometries(ids?: readonly string[]): Promise<void> {\n const targets = ids\n ? ids.flatMap((id) => {\n const entry = this._entries.find((e) => e.id === id);\n return entry?._modules?.geometries ? [entry] : [];\n })\n : this._entries.filter((entry) => entry._modules?.geometries);\n if (targets.length === 0) return;\n for (const entry of targets) {\n // biome-ignore lint/style/noNonNullAssertion: filter guarantees the module exists\n await entry._modules!.geometries!.clear();\n this.events.emit('geometries-change', { entryId: entry.id, count: 0 });\n }\n }\n\n /**\n * Drop a single entry from history. Hides it from the map first (clears every module\n * it owns) then removes it from `_entries`. Emits `entries-change` and, when the entry was\n * shown, `shown-change`. No-op when the id is unknown.\n */\n async removeEntry(id: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === id);\n if (idx === -1) return;\n const entry = this._entries[idx];\n const wasShown = !!entry._shownAs;\n if (entry._modules) {\n await entry._modules.places?.clear();\n await entry._modules.geometries?.clear();\n }\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [id] });\n if (wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n // Brings an entry up under `markerType`: lazy-inits or switches the entry's single\n // PlacesModule, then pushes its places onto it. Idempotent — calling twice with the same\n // marker type just re-pushes the same data; switching marker types reuses the module in place.\n private async _showEntryAs(entry: PlacesEntry, markerType: PlacesMarkerType): Promise<void> {\n const module = await this.getEntryPlacesModule(entry.id, markerType);\n await module.show({ type: 'FeatureCollection', features: entry.data } as Places);\n if (entry.connections?.length) {\n await module.showConnections(entry.connections);\n }\n entry._shownAs = markerType;\n }\n\n // Hides the entry by clearing its PlacesModule. No-op if already hidden.\n private async _hideEntry(entry: PlacesEntry): Promise<void> {\n if (!entry._shownAs) return;\n if (entry._modules?.places) await entry._modules.places.clear();\n entry._shownAs = undefined;\n }\n\n reset(): void {\n // Modules can't be unmounted from the map without their `destroy()`\n // hook (not part of the SDK surface today), so we clear them so the\n // map looks empty before dropping the references. Layers stay parked\n // on the style — same trade-off the previous shared-module reset had.\n for (const entry of this._entries) {\n if (entry._modules) {\n entry._modules.places?.clear();\n entry._modules.geometries?.clear();\n }\n }\n const clearedIds = this._entries.map((entry) => entry.id);\n this._entries = [];\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport {\n fillStyledGeometryConfig,\n GeometriesModule,\n type GeometryFillStyle,\n PlacesModule,\n ReachableRangesModule,\n type TomTomMap,\n} from '@tomtom-org/maps-sdk/map';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { RangesEntry } from './entry';\n\ntype FillStyledModule = {\n getConfig(): { fillStyle?: GeometryFillStyle } | undefined;\n updateConfig(config: { fillStyle: GeometryFillStyle }): void;\n};\n\n// The module created on first use; after that only its fill style changes, keeping any styling set since.\nconst withFillStyle = async <MODULE extends FillStyledModule>(\n module: MODULE | undefined,\n fillStyle: GeometryFillStyle,\n create: () => Promise<MODULE>,\n): Promise<MODULE> => {\n if (!module) return create();\n if (module.getConfig()?.fillStyle !== fillStyle) module.updateConfig({ fillStyle });\n return module;\n};\n\n/**\n * Events fired by {@link RangeState}. Subscribe via `state.ranges.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type RangeStateEvents = {\n /**\n * Range history changed — entry added or cleared via `reset()`. `entries` is the full snapshot\n * after the change; `changedIds` lists the ids of the entries this specific change added,\n * replaced in place, or removed.\n */\n 'entries-change': { entries: readonly RangesEntry[]; changedIds: readonly string[] };\n /** Set of ranges entries currently rendered on the map changed (multi-show under `multiple`). */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * State for reachable range results.\n *\n * Stores semantic summaries of calculated ranges (origin, budgets).\n * Also retains raw polygon geometry internally for use in geometry searches\n * (e.g. discoverPlaces withinRange). Displayed on the map via ReachableRangesModule.\n *\n * @group Agent Toolkit\n */\nexport class RangeState implements ShownEntriesSlice {\n private _entries: RangesEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link RangeStateEvents}. */\n readonly events = new StateEvents<RangeStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n /** Set of ranges-entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Display policy: `multiple` (default) lets several reachable-area entries render side\n * by side; `single` forces \"at most one ranges entry on the map\" — `markEntryShown`\n * then hides any previously-shown entry first.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every\n * non-latest entry, drop them from history, and emit `entries-change`. No-op when the\n * mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n }\n this.events.emit('mode-change', mode);\n }\n\n /**\n * Lazy-init the entry's ReachableRangesModule (for the isochrone/isodistance polygons),\n * applying the requested fill style. Throws on unknown id.\n */\n async getEntryRangesModule(\n entryId: string,\n fillStyle: GeometryFillStyle = 'outline',\n ): Promise<ReachableRangesModule> {\n const modules = (this._requireEntry(entryId)._modules ??= {});\n modules.ranges = await withFillStyle(modules.ranges, fillStyle, () =>\n ReachableRangesModule.create(this._ttMap, { fillStyle }),\n );\n return modules.ranges;\n }\n\n /**\n * Lazy-init the entry's plain GeometriesModule, for drawing the entry's polygons in a style of\n * your own (per-feature colours, border widths) rather than as reachable-range bands.\n * Updates only the fill style when it changes, keeping the styling applied since. Throws on\n * unknown id.\n */\n async getEntryGeometriesModule(\n entryId: string,\n fillStyle: GeometryFillStyle = 'outline',\n ): Promise<GeometriesModule> {\n const modules = (this._requireEntry(entryId)._modules ??= {});\n modules.geometries = await withFillStyle(modules.geometries, fillStyle, () =>\n GeometriesModule.create(this._ttMap, { ...fillStyledGeometryConfig(), fillStyle }),\n );\n return modules.geometries;\n }\n\n /**\n * Lazy-init the entry's origin-pin PlacesModule. Each entry maintains its own pin module\n * so multiple ranges entries can be on the map simultaneously without the pins of one\n * overwriting another.\n */\n async getEntryPlacesModule(entryId: string): Promise<PlacesModule> {\n const entry = this._requireEntry(entryId);\n entry._modules ??= {};\n entry._modules.places ??= await PlacesModule.create(this._ttMap);\n\n return entry._modules.places;\n }\n\n /**\n * Mark this entry as shown — records `_shown = true` and emits `shown-change`. The\n * caller is expected to push features into the lazy modules itself; this method only\n * keeps the `_shown` flag in sync with what the user sees.\n *\n * Under `entryMode === 'single'` any other shown entry is hidden first so two range\n * layers don't overlap.\n */\n async markEntryShown(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n if (this._entryMode === 'single') {\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Hide every module on the entry (clear). No-op when the entry is unknown. */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n if (entry._modules?.ranges) await entry._modules.ranges.clear();\n if (entry._modules?.geometries) await entry._modules.geometries.clear();\n if (entry._modules?.places) await entry._modules.places.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Drop a single entry from history. Hides it from the map first (clears its ranges +\n * origin-pin modules) then removes it from `_entries`. Emits `entries-change`. No-op when\n * the id is unknown.\n */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n }\n\n private _requireEntry(entryId: string): RangesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`RangeState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n get entries(): readonly RangesEntry[] {\n return this._entries;\n }\n\n get latestEntry(): RangesEntry | undefined {\n return this._entries.at(-1);\n }\n\n async addEntry(entry: Omit<RangesEntry, 'id' | 'timestamp'>): Promise<string> {\n const droppedIds = this._entryMode === 'single' ? this._entries.map((existing) => existing.id) : [];\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (existing) => this.hideEntry(existing.id));\n this._entries = [];\n }\n // Dedupe the length-based id — after removals it can collide with a surviving entry.\n const id = pickUniqueEntryId(\n `ranges-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id,\n timestamp: Date.now(),\n ...entry,\n });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [...droppedIds, id] });\n return id;\n }\n\n /**\n * Hide every reachable-range entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n const clearedIds = this._entries.map((entry) => entry.id);\n for (const entry of this._entries) {\n entry._modules?.ranges?.clear();\n entry._modules?.geometries?.clear();\n entry._modules?.places?.clear();\n }\n this._entries = [];\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","import type { Routes } from '@tomtom-org/maps-sdk/core';\nimport { StateEvents } from '../../events';\nimport type { RoutePollingStatus, RouteSnapshot } from './types';\n\n/** Default interval between route recalculations (ms). Customizable via {@link RouteMonitor.start}. */\nexport const ROUTE_MONITOR_INTERVAL_MS = 60_000;\n\n/**\n * Dependency injection for a {@link RouteMonitor}. Production code wires `recalculate` to a closure\n * over `calculateRoute` + the entry's waypoints / params (so each tick re-runs the SAME route);\n * tests pass a deterministic fake and override the timer.\n *\n * The recalculator returns just the routes — the monitor stamps `takenAt` itself so every consumer of\n * one tick (entry data, derived analyses) agrees on the same canonical moment.\n *\n * @group Agent Toolkit\n */\nexport type RouteMonitorDeps = {\n recalculate: () => Promise<Routes>;\n /** Override for tests. Defaults to the global `setInterval`. */\n setInterval?: (fn: () => void, ms: number) => unknown;\n clearInterval?: (handle: unknown) => void;\n};\n\n/**\n * Events fired by {@link RouteMonitor}. The slice subscribes once and re-emits translated copies\n * (`monitor-tick` / `monitor-error`) keyed by entry id.\n */\nexport type RouteMonitorEvents = {\n /** A recalculation succeeded. Payload: the fresh snapshot. */\n tick: RouteSnapshot;\n /** A recalculation failed — the monitor has cleared its timer and moved to `stopped-error`. */\n error: string;\n};\n\n/**\n * One-shot polling loop that recalculates a single route on an interval. Owns its timer, status, and\n * error. Re-starting after `stop()` is allowed and resets all state.\n *\n * The monitor never knows about the entry that owns it — the `recalculate` closure captures the\n * waypoints + params, and the caller decides what to do with each fresh snapshot. Mirrors\n * `IncidentMonitor`; routes carry live traffic, so periodic recalculation keeps the entry fresh.\n *\n * @group Agent Toolkit\n */\nexport class RouteMonitor {\n readonly events = new StateEvents<RouteMonitorEvents>();\n\n private _status: RoutePollingStatus = 'idle';\n private _error?: string;\n private _intervalHandle?: unknown;\n private _clearInterval?: (handle: unknown) => void;\n private _recalculate?: RouteMonitorDeps['recalculate'];\n\n get status(): RoutePollingStatus {\n return this._status;\n }\n\n get error(): string | undefined {\n return this._error;\n }\n\n /** True when the monitor is *currently* recalculating on its interval. */\n get isRunning(): boolean {\n return this._status === 'running';\n }\n\n /** True when the monitor has any activity (running or stopped-error). */\n get isActive(): boolean {\n return this._status !== 'idle';\n }\n\n /**\n * Begin polling. Captures `deps` for the lifetime of the run, fires the first recalculation\n * immediately (unless `skipInitialTick`), then schedules an interval. No-op when already running.\n *\n * `skipInitialTick` is for a caller that JUST calculated the route and only wants the recurring\n * refresh — an eager first tick would be a redundant duplicate recalculation.\n */\n start(deps: RouteMonitorDeps, intervalMs: number = ROUTE_MONITOR_INTERVAL_MS, skipInitialTick = false): void {\n if (this._status === 'running') return;\n this._status = 'running';\n this._error = undefined;\n // Deps are NOT sticky across stop/start cycles — every restart (incl. recovery from\n // `stopped-error`) reads fresh `recalculate` / timer overrides from the caller.\n this._recalculate = deps.recalculate;\n this._clearInterval =\n deps.clearInterval ?? ((handle: unknown) => clearInterval(handle as ReturnType<typeof setInterval>));\n const setIntervalFn = deps.setInterval ?? setInterval;\n if (!skipInitialTick) void this._tick();\n this._intervalHandle = setIntervalFn(() => void this._tick(), intervalMs);\n }\n\n /** Stop polling and reset state. No-op when idle. */\n stop(): void {\n if (this._status === 'idle') return;\n this._releaseTimer();\n this._status = 'idle';\n this._error = undefined;\n this._recalculate = undefined;\n }\n\n // _tick stamps `takenAt` after the recalculation resolves so every downstream consumer\n // (entry data, derived analyses) shares one byte-equal moment.\n private async _tick(): Promise<void> {\n const recalculate = this._recalculate;\n if (!recalculate) return;\n try {\n const routes = await recalculate();\n const snap: RouteSnapshot = { takenAt: Date.now(), routes };\n this.events.emit('tick', snap);\n } catch (err) {\n this._failed(err instanceof Error ? err.message : String(err));\n }\n }\n\n private _failed(message: string): void {\n this._releaseTimer();\n this._error = message;\n this._status = 'stopped-error';\n this._recalculate = undefined;\n this.events.emit('error', message);\n }\n\n private _releaseTimer(): void {\n if (this._intervalHandle != null) {\n this._clearInterval?.(this._intervalHandle);\n this._intervalHandle = undefined;\n }\n this._clearInterval = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { Routes, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { PlanningWaypoint, RoutingModule, type RoutingModuleConfig, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { RouteParams, RoutesEntry } from './entry';\nimport { RouteMonitor, type RouteMonitorDeps } from './monitor/monitor';\nimport type { RouteSnapshot } from './monitor/types';\n\n/**\n * Events fired by {@link RoutingState}. Subscribe via `state.routing.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type RoutingStateEvents = {\n /**\n * Route history changed — new calculation stored, analysis attached, or cleared via `reset()`.\n * `entries` is the full snapshot after the change; `changedIds` lists the ids of the entries\n * this specific change added, replaced in place, or removed.\n */\n 'entries-change': { entries: readonly RoutesEntry[]; changedIds: readonly string[] };\n /** Sparse waypoint planning slots changed (origin/stop/destination assignment). */\n 'planning-change': readonly PlanningWaypoint[];\n /** Route parameters (alternatives, cost model, depart/arrive time) changed. */\n 'params-change': RouteParams;\n /** Set of routes entries currently rendered on the map changed (multi-show under `multiple`). */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n /** Monitoring began on an entry (idle → running). Skipped when already running. */\n 'monitor-start': { entryId: string };\n /**\n * Monitoring halted on an entry. `reason: 'manual'` covers explicit `stopMonitoring` and entry\n * teardown (remove / reset); `reason: 'error'` fires alongside `monitor-error`.\n */\n 'monitor-stop': { entryId: string; reason: 'manual' | 'error' };\n /** A monitored route was recalculated (per-tick). The entry's data has been updated in place. */\n 'monitor-tick': { entryId: string; snap: RouteSnapshot };\n /** A monitored route's recalculation failed — the monitor cleared its timer and stopped. */\n 'monitor-error': { entryId: string; error: string };\n};\n\n/**\n * State for route calculation, waypoint management, and route planning parameters.\n *\n * Each entry carries its own lazy-initialised RoutingModule (`entry._module`), so display\n * state lives on the entry instead of a shared slice-level module. Two routes can be rendered\n * concurrently by showing both entries, and switching the active route hides the previous\n * entry's module without disturbing its calculated data.\n *\n * @group Agent Toolkit\n */\nexport class RoutingState implements ShownEntriesSlice {\n private _entries: RoutesEntry[] = [];\n private _planningSlots: PlanningWaypoint[] = [];\n private _params: RouteParams = {};\n private _entryMode: EntryMode = 'multiple';\n /**\n * Slice-level route colour applied to every RoutingModule on first show / re-show. Sticky\n * across entry swaps so the rendered route always uses the most recently requested color.\n * Set via {@link setColor}; persists for the lifetime of the slice (cleared on `reset`).\n */\n private _color?: string;\n /**\n * The most recently `.showEntry`-ed id. Tracks \"who was last brought up\" so tools that\n * operate on the focused module have a single pointer to read; the authoritative shown\n * set is `shownEntryIds` (derived from each entry's `_shown` flag).\n */\n private _lastShownEntryId?: string;\n\n /** Subscribe to state changes — see {@link RoutingStateEvents}. */\n readonly events = new StateEvents<RoutingStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n /** Set of routes-entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Display policy: `multiple` (default) lets several entries render side-by-side;\n * `single` forces \"at most one route on the map\" — `showEntry` then hides any\n * previously-shown entry first.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every\n * non-latest entry, drop them from history, and emit `entries-change`. No-op when the\n * mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n }\n this.events.emit('mode-change', mode);\n }\n\n /**\n * Lazy-init and return an entry's RoutingModule. Throws on unknown id. Callers that need\n * to show/hide should prefer the higher-level {@link showEntry} / {@link hideEntry}.\n */\n async getEntryRoutingModule(entryId: string): Promise<RoutingModule> {\n const entry = this._requireEntry(entryId);\n entry._module ??= await RoutingModule.create(this._ttMap);\n\n return entry._module;\n }\n\n /**\n * Render the given entry's routes + waypoints on its own RoutingModule.\n *\n * Under `entryMode === 'single'` any other shown entry is hidden first so two route\n * layers don't pile up. Under `entryMode === 'multiple'` previously-shown entries stay\n * rendered, letting the caller overlay several routes at once.\n *\n * Emits `shown-change` once the new entry is up. Pass `showWaypoints: false` to render the\n * route lines without origin/stop/destination pins, and `showSummaryBubbles: false` to drop the\n * per-route ETA bubbles (e.g. a monitored corridor backdrop).\n */\n async showEntry(entryId: string, opts?: { showWaypoints?: boolean; showSummaryBubbles?: boolean }): Promise<void> {\n const entry = this._requireEntry(entryId);\n if (this._entryMode === 'single') {\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n const module = await this.getEntryRoutingModule(entryId);\n // Apply the sticky colour + bubble visibility in ONE update BEFORE showing, so the first\n // paint already has it (no flash); `updateConfig` keeps whatever else the module holds.\n const partial: RoutingModuleConfig = {};\n if (this._color !== undefined) partial.color = this._color;\n // showRoutes gates the ETA bubbles on the top-level `summaryBubbles.visible` flag (NOT a\n // layer visibility), so that's what we set to drop them.\n if (opts?.showSummaryBubbles === false) partial.summaryBubbles = { visible: false };\n if (partial.color !== undefined || partial.summaryBubbles) module.updateConfig(partial);\n await module.showRoutes(entry.data);\n if (opts?.showWaypoints === false) {\n await module.clearWaypoints();\n } else if (entry.waypoints.length) {\n await module.showWaypoints(entry.waypoints);\n }\n const wasShown = entry._shown;\n entry._shown = true;\n this._lastShownEntryId = entryId;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Slice-level route colour persisted across entry swaps. Undefined when none has been set. */\n get color(): string | undefined {\n return this._color;\n }\n\n /**\n * Set the slice-level route colour. The colour is applied immediately to every currently-shown\n * entry's RoutingModule and remembered so that subsequent {@link showEntry} calls apply it\n * automatically. Pass `undefined` to clear the sticky colour — currently-shown modules keep\n * their last paint until they're re-shown (MapLibre style restore handles full revert).\n */\n setColor(color: string | undefined): void {\n this._color = color;\n if (color === undefined) return;\n for (const entry of this._entries) {\n if (entry._shown && entry._module) {\n entry._module.updateConfig({ color });\n }\n }\n }\n\n /**\n * Hide the entry's routes + waypoints on its module (clear; the module stays cached).\n * No-op when the entry is unknown or already hidden.\n */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n if (entry._module) {\n await entry._module.clearRoutes();\n await entry._module.clearWaypoints();\n }\n entry._shown = false;\n if (this._lastShownEntryId === entryId) {\n // Repoint to whichever entry is still rendering, or clear the\n // pointer if none is.\n this._lastShownEntryId = this._entries.findLast((e) => e._shown)?.id;\n }\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Drop a single entry from history. Hides it from the map first (clears its routing module)\n * then removes it from `_entries`. Emits `entries-change`. No-op when the id is unknown.\n */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n this.stopMonitoring(entryId);\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n }\n\n private _requireEntry(entryId: string): RoutesEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`RoutingState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * RoutingModule of the most-recently-shown entry, if any. Returns undefined when no\n * entry is currently shown.\n */\n get currentEntryModule(): RoutingModule | undefined {\n if (!this._lastShownEntryId) return undefined;\n const entry = this._entries.find((e) => e.id === this._lastShownEntryId);\n return entry?._shown ? entry._module : undefined;\n }\n\n // History\n\n get entries(): readonly RoutesEntry[] {\n return this._entries;\n }\n\n get currentRoutes(): Routes | undefined {\n return this._entries.at(-1)?.data;\n }\n\n get currentWaypoints(): WaypointLike[] | undefined {\n return this._entries.at(-1)?.waypoints;\n }\n\n /** Sparse nullable slots being assembled before triggering a route calculation. */\n get planningSlots(): PlanningWaypoint[] {\n return this._planningSlots;\n }\n\n get params(): RouteParams {\n return this._params;\n }\n\n async addRoutes(routes: Routes, waypoints: WaypointLike[], label: string): Promise<string> {\n const droppedIds = this._entryMode === 'single' ? this._entries.map((entry) => entry.id) : [];\n if (this._entryMode === 'single' && this._entries.length > 0) {\n // Hide each existing entry's module so the map doesn't keep\n // showing the previous routes; we then drop them from history.\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n // Dedupe the length-based id — after removals it can collide with a surviving entry.\n const id = pickUniqueEntryId(\n `routes-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id,\n timestamp: Date.now(),\n label,\n data: routes,\n waypoints,\n params: { ...this._params },\n });\n this._planningSlots = [...waypoints];\n this.events.emit('entries-change', { entries: this._entries, changedIds: [...droppedIds, id] });\n this.events.emit('planning-change', this._planningSlots);\n return id;\n }\n\n setWaypointAt(index: number, waypoint: WaypointLike): void {\n const minSize = Math.max(2, index + 1);\n while (this._planningSlots.length < minSize) {\n this._planningSlots.push(null);\n }\n this._planningSlots[index] = waypoint;\n this.events.emit('planning-change', this._planningSlots);\n }\n\n setParams(params: Partial<RouteParams>): void {\n this._params = { ...this._params, ...params };\n this.events.emit('params-change', this._params);\n }\n\n // ---- Monitoring (live-traffic recalculation) ----\n\n /**\n * Replace a route entry's data in place (a monitor recalculated it). Bumps `_tick`, updates\n * `data`/`timestamp`, emits `entries-change`, and re-renders the route on its module when shown.\n * The `_tick` guard bails if a newer tick overwrote `data` while `showRoutes` awaited. No-op\n * (returns false) when the entry is unknown. Mirrors `TrafficIncidentsState.replaceEntryData`.\n */\n async replaceRouteData(entryId: string, routes: Routes, sampledAt: number): Promise<boolean> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n const tick = (entry._tick ?? 0) + 1;\n entry._tick = tick;\n entry.data = routes;\n entry.timestamp = sampledAt;\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n // Re-render only when this entry is currently on the map; the route geometry / traffic\n // delays change each tick, while the waypoints are unchanged (same route).\n if (entry._shown && entry._module) {\n await entry._module.showRoutes(routes);\n }\n return true;\n }\n\n // Wire a fresh monitor to this entry: each tick recalculates the route and replaces the entry's\n // data; errors surface as monitor-error + monitor-stop. The monitor itself is recalculation-\n // agnostic — the `recalculate` closure (built in the tools layer) captures waypoints + params.\n private _createMonitorForEntry(entry: RoutesEntry): RouteMonitor {\n const monitor = new RouteMonitor();\n monitor.events.on('tick', (snap) => {\n void this.replaceRouteData(entry.id, snap.routes, snap.takenAt);\n this.events.emit('monitor-tick', { entryId: entry.id, snap });\n });\n monitor.events.on('error', (error) => {\n this.events.emit('monitor-error', { entryId: entry.id, error });\n this.events.emit('monitor-stop', { entryId: entry.id, reason: 'error' });\n });\n return monitor;\n }\n\n /**\n * Arm background recalculation on an entry. Lazy-creates its monitor, then starts polling with\n * the caller-supplied `recalculate` closure. Emits `monitor-start` on the idle → running\n * transition. Throws on unknown id.\n */\n startMonitoring(\n entryId: string,\n deps: RouteMonitorDeps,\n opts?: { intervalMs?: number; skipInitialTick?: boolean },\n ): void {\n const entry = this._requireEntry(entryId);\n const monitor = (entry._monitor ??= this._createMonitorForEntry(entry));\n const wasRunning = monitor.isRunning;\n monitor.start(deps, opts?.intervalMs, opts?.skipInitialTick);\n if (!wasRunning) this.events.emit('monitor-start', { entryId });\n }\n\n /** Stop recalculating an entry; its last data stays. Emits `monitor-stop`. No-op when not running. */\n stopMonitoring(entryId: string): void {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._monitor) return;\n const wasRunning = entry._monitor.isRunning;\n entry._monitor.stop();\n if (wasRunning) this.events.emit('monitor-stop', { entryId, reason: 'manual' });\n }\n\n /** True when the entry is currently being recalculated on its interval. */\n isMonitored(entryId: string): boolean {\n return this._entries.find((e) => e.id === entryId)?._monitor?.isRunning ?? false;\n }\n\n /**\n * Hide every route + waypoint set currently on the map. Map-only — history (`entries`)\n * stays intact. Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n // Clear every entry's module so the map looks empty before the\n // references go away. Layers persist on the style — same trade-off the\n // previous shared-module reset had. Stop any monitors first so no tick\n // fires against a torn-down entry.\n const clearedIds = this._entries.map((entry) => entry.id);\n for (const entry of this._entries) {\n entry._monitor?.stop();\n entry._module?.clearRoutes();\n entry._module?.clearWaypoints();\n }\n this._entries = [];\n this._planningSlots = [];\n this._params = {};\n this._lastShownEntryId = undefined;\n this._color = undefined;\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('planning-change', this._planningSlots);\n this.events.emit('params-change', this._params);\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-engine\n *\n * The {@link JobEngine} — the single recurrence engine behind every standing computation in the toolkit.\n * A job is one entry-anchored unit of recurring work: an opaque `run` thunk plus the `affectedEntryIds`\n * it depends on. The engine subscribes once per input slice to `entries-change`, and on each change\n * re-runs exactly the active jobs whose `affectedEntryIds` intersect the changed ids — an unrelated\n * entry's change matches nothing and runs nothing.\n *\n * The engine is deliberately generic: it never interprets `run` (the tools layer builds that closure,\n * which owns input prep, the sandbox crossing, reading `previous`, and writing the result to its sink).\n * So the engine imports the INPUT slices only (read side, via {@link ensureWired}) and depends on no\n * output sink — results leave a job only through its own `run`. The engine holds no results.\n *\n * Linkage to a sink is by reference, not convention: {@link register} returns a {@link Job} handle and\n * the owner stores it (e.g. `Analyses._jobs`, `EventsState._jobs`). Use the handle to `setActive`\n * (pause/resume — a paused job is retained but never scanned, so a one-shot `analyseData` keeps its recipe\n * for later promotion) or `unregister`.\n *\n * @group Agent Toolkit\n */\n\nimport type { ToolState } from '../types';\n\n/** What {@link JobEngine.register} needs to mint a {@link Job}. `run` and `onOrphaned` are owner closures. */\nexport type JobSpec = {\n /** Ids of every entry this job reads. The engine re-runs the job when any of them changes. */\n affectedEntryIds: readonly string[];\n /** The recipe, opaque to the engine — stored as the single home of the code + for re-arm change-detect. */\n code: string;\n /** Whether the engine scans this job. A paused job (`false`) is retained but never run. */\n active: boolean;\n /** Owner closure: prepare inputs → run sandbox → write result to its sink. The engine never inspects it. */\n run: () => Promise<void>;\n /** Owner closure invoked just before the engine drops the job because all its source entries are gone. */\n onOrphaned?: () => void;\n};\n\n/** A registered job, handed back to the owner as the reference linking a sink record to its recurrence. */\nexport interface Job {\n /** Ids of every entry this job reads (immutable; a re-arm with different sources is a different job). */\n readonly affectedEntryIds: readonly string[];\n /** The recipe behind this job — compared on re-arm to decide whether to drop stale history. */\n readonly code: string;\n /** Whether the engine currently scans this job. */\n readonly active: boolean;\n /** Pause (`false`) or resume (`true`) scanning. A paused job keeps its recipe for later promotion. */\n setActive(active: boolean): void;\n /** Remove the job from the engine. The owner drops its stored reference. */\n unregister(): void;\n}\n\n/** The shared surface of every input slice the engine watches: an `entries-change` emitter + `entries`\n * with ids. The slices' full event maps differ, so {@link JobEngine.sourceSlices} narrows to this. */\ntype SourceSlice = {\n readonly events: {\n on(event: 'entries-change', handler: (payload: { changedIds: readonly string[] }) => void): unknown;\n };\n readonly entries: readonly { id: string }[];\n};\n\n/**\n * Session-level recurrence engine. Held on `ToolState` as `state.engine`; constructed and wired by the\n * state factory. Owners ({@link Analyses}, {@link EventsState}) register jobs and keep the returned\n * handles.\n *\n * @group Agent Toolkit\n */\nexport class JobEngine {\n private readonly jobs = new Set<JobHandle>();\n private wired = false;\n // Off-job runtime flags. `scanning` ignores a change landing mid-scan (rare, self-correcting — the\n // next change re-runs it); `scheduled` coalesces a synchronous burst of `entries-change` into one\n // scan; `pending` accumulates the changed entry ids across that burst so the scan runs exactly the\n // jobs whose sources moved.\n private scanning = false;\n private scheduled = false;\n private pending = new Set<string>();\n\n /** Register a job and return its handle. Stored by the owner as its reference to the recurrence. */\n register(spec: JobSpec): Job {\n const job = new JobHandle(spec, this);\n this.jobs.add(job);\n return job;\n }\n\n /** @internal — called by {@link JobHandle.unregister}. */\n drop(job: JobHandle): void {\n this.jobs.delete(job);\n }\n\n /**\n * Subscribe the scan to every input slice's `entries-change`, exactly once per session. The state\n * factory calls this at construction (it holds the slices), so every `ToolState` gets a live engine\n * without the job builders having to wire lazily.\n */\n ensureWired(state: ToolState): void {\n if (this.wired) return;\n this.wired = true;\n const onChange = (payload: { changedIds: readonly string[] }) => this.onChange(state, payload.changedIds);\n for (const slice of this.sourceSlices(state)) slice.events.on('entries-change', onChange);\n }\n\n // The input slices whose entries can anchor a job: places / routes / incidents / area-analytics /\n // byod / custom-geometries. Single source of truth for both wiring (subscribe) and orphan GC\n // (liveness), so the set can't drift between them. Ranges feed in but never anchor a job.\n private sourceSlices(state: ToolState): readonly SourceSlice[] {\n return [\n state.places,\n state.routing,\n state.trafficIncidents,\n state.trafficAreaAnalytics,\n state.byod,\n state.customGeometries,\n ] as unknown as readonly SourceSlice[];\n }\n\n private onChange(state: ToolState, changedIds: readonly string[]): void {\n this.collectOrphans(state);\n for (const id of changedIds) this.pending.add(id);\n this.scheduleScan();\n }\n\n // Drop jobs whose source entries have ALL been removed, invoking each one's `onOrphaned` so its owner\n // drops the matching sink record. Entries no longer carry their jobs, so a removal can't GC implicitly;\n // we collect on every `entries-change` against the live source-entry ids. Snapshot the orphans before\n // mutating `jobs`, and let the engine own removal (delete first; a sink's `onOrphaned`→`unregister`\n // then no-ops) so there's no mutation-during-iteration and no double-fire.\n private collectOrphans(state: ToolState): void {\n const live = new Set<string>();\n for (const slice of this.sourceSlices(state)) for (const entry of slice.entries) live.add(entry.id);\n\n const orphans = [...this.jobs].filter((job) => !job.affectedEntryIds.some((id) => live.has(id)));\n for (const job of orphans) {\n this.jobs.delete(job);\n job.onOrphaned?.();\n }\n }\n\n private hasActiveJob(): boolean {\n for (const job of this.jobs) if (job.active) return true;\n return false;\n }\n\n private scheduleScan(): void {\n // Collapse a synchronous burst (scheduled) and ignore changes landing mid-scan (scanning).\n if (this.scanning || this.scheduled) return;\n // Nothing active — drop the accumulated ids so `pending` can't grow unbounded.\n if (!this.hasActiveJob()) {\n this.pending.clear();\n return;\n }\n this.scheduled = true;\n queueMicrotask(() => {\n this.scheduled = false;\n void this.runScan();\n });\n }\n\n private async runScan(): Promise<void> {\n const changed = this.pending;\n this.pending = new Set();\n this.scanning = true;\n try {\n // Run only the active jobs whose sources actually moved — an unrelated entry's change leaves\n // every `affectedEntryIds` disjoint from `changed` and runs nothing.\n for (const job of this.jobs) {\n if (!job.active) continue;\n if (!job.affectedEntryIds.some((id) => changed.has(id))) continue;\n await job.run();\n }\n } finally {\n this.scanning = false;\n }\n }\n\n /** Drop every job. Walked automatically by `destroyState` (duck-typed `reset`). Subscriptions persist\n * (harmless without jobs), mirroring the one-shot `wired` guard. */\n reset(): void {\n this.jobs.clear();\n this.pending.clear();\n this.scanning = false;\n this.scheduled = false;\n }\n}\n\n// The concrete job handed back to owners — a reference linking a sink record to its recurrence.\nclass JobHandle implements Job {\n readonly affectedEntryIds: readonly string[];\n readonly code: string;\n active: boolean;\n readonly run: () => Promise<void>;\n readonly onOrphaned?: () => void;\n\n constructor(\n spec: JobSpec,\n private readonly engine: JobEngine,\n ) {\n this.affectedEntryIds = spec.affectedEntryIds;\n this.code = spec.code;\n this.active = spec.active;\n this.run = spec.run;\n this.onOrphaned = spec.onOrphaned;\n }\n\n setActive(active: boolean): void {\n this.active = active;\n }\n\n unregister(): void {\n this.engine.drop(this);\n }\n}\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport type { ChartConfiguration } from 'chart.js';\n\nexport type AnalysisOutputFormat = 'json' | 'chart';\n\n// Sandboxed `new AsyncFunction` bodies take `places`, `h3`, `turf` as injected\n// parameters, but LLMs regularly prepend `const turf = require('@turf/turf')`\n// or `const h3 = require('h3-js')` out of habit — producing a parse-time\n// \"Identifier 'X' has already been declared\" error before the body even runs.\n// We defensively strip those redeclarations.\n//\n// Critical: only strip when the RHS is a `require(...)`/`import(...)` call.\n// A naive name-only filter would also nuke legitimate user variables that\n// happen to share a name with an injected one (e.g. `const geometriesByEntry =\n// Object.values(placesByEntry).map(...)`), corrupting the body and yielding a far\n// less actionable \"Illegal return statement\" error downstream.\n/**\n * Compact sandbox-code blurb shared by `process-places`, `process-geometries`,\n * `analyse-places`, `analyse-geometries`, and `analyse-routes`. Keeps the\n * \"what's in scope / what's forbidden\" rules short and consistent across all\n * code-generation tools so the LLM is prompted the same way each time. Pass\n * the names of the injected identifiers (the params the AsyncFunction body\n * receives) so the rule reads naturally for each tool.\n *\n * @ignore\n */\nexport const buildSandboxCodePrompt = (injected: readonly string[]): string => {\n const injectedList = injected.map((name) => `\\`${name}\\``).join(', ');\n return (\n `In scope: ${injectedList} — injected as named parameters; use them directly. ` +\n 'Do NOT redeclare them or read them off `arguments`: lines like `const turf = arguments.turf` / ' +\n '`const turf = arguments[0].turf` / `const placesByEntry = arguments.placesByEntry` throw ' +\n '\"Identifier \\'…\\' has already been declared\". The body has no `arguments`-based access — reference each name as-is. ' +\n 'No `require` / `import` / `await import` (body is an AsyncFunction, not a module — such lines are stripped silently). ' +\n '`h3` = h3-js (hex math). `turf` = @turf/turf v7 (verify names at https://turfjs.org/docs/). ' +\n 'INPUTS are `<kind>ByEntry` records keyed by entry id — get a value out first: one entry ' +\n '`placesByEntry[id]` (a FeatureCollection) / `incidentsByEntry[id]` (an array), or all entries ' +\n '`Object.values(placesByEntry).flatMap(fc => fc.features)` / `Object.values(incidentsByEntry).flat()`. ' +\n 'TURF INPUTS: pass a Feature or FeatureCollection — never a bare Array (throws \"Unknown Geometry Type\"). ' +\n 'FeatureCollection values (`placesByEntry[id]`, `routesByEntry[id]`, `trafficAreaAnalyticsByEntry[id]`, ' +\n '`byodByEntry[id]`) → pass directly: `turf.bbox(placesByEntry[id])`. ' +\n 'Array values (`incidentsByEntry[id]`, `geometriesByEntry[key]`, `…features`, merged `Object.values(...)`) → ' +\n 'iterate per-feature (`for (const f of incidentsByEntry[id]) turf.X(f)`) or wrap with `turf.featureCollection([...])`. ' +\n 'Never `.map(f => f.geometry.coordinates)` / `.flatMap(f => f.geometry.coordinates)` to feed turf — ' +\n 'that strips the feature wrapper turf reads and throws \"coordinates must be an Array\" (or worse, mixes ' +\n 'Point/LineString shapes silently). Pass the whole feature and let turf read the geometry: ' +\n '`turf.pointToLineDistance(point, lineFeature, { units: \"meters\" })`. `incidentsByEntry[id]` mixes Point + ' +\n 'LineString features — guard with `if (inc.geometry.type === \"LineString\")` before line-only ops. ' +\n 'TURF v7 SET OPS: `union`/`intersect`/`difference` take ONE FeatureCollection — ' +\n '`turf.difference(turf.featureCollection([outer, inner]))`. The v6 two-arg form throws. ' +\n 'PERF: for large-N nearest/within queries, pre-bucket points with ' +\n '`h3.latLngToCell(lat, lng, res)` + `h3.gridDisk(cell, k)` and only run turf on same-/neighbour-cell ' +\n 'pairs — turns O(N·M) into ~O(N+M). Pick `res` so each cell ≈ your query radius. ' +\n 'No spatial search or HTTP — call `discoverPlaces` BEFORE this tool for new places. ' +\n 'Top-level `await`/locals/consts fine. End with `return …;`.'\n );\n};\n\n/** Chart.js chart types accepted by the analyse-* tools when `outputFormat: \"chart\"`. */\nexport const CHART_TYPES = new Set(['bar', 'line', 'pie', 'doughnut', 'radar', 'polarArea', 'scatter', 'bubble']);\n\n/** Lightweight structural check that a sandbox return value is a Chart.js config. */\nexport const isChartConfiguration = (value: unknown): value is ChartConfiguration => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { type?: unknown; data?: unknown };\n return typeof v.type === 'string' && CHART_TYPES.has(v.type) && typeof v.data === 'object' && v.data !== null;\n};\n\n/**\n * GeoJSON Feature with a Polygon or MultiPolygon geometry. Generic over\n * `properties` so callers that need a narrower property shape (e.g. place\n * footprints with `CommonPlaceProps`) get the typed result without a cast.\n *\n * @ignore\n */\nexport const isPolygonFeature = <P = unknown>(value: unknown): value is PolygonFeature<P> => {\n if (!value || typeof value !== 'object') return false;\n const v = value as { type?: unknown; geometry?: unknown };\n if (v.type !== 'Feature') return false;\n const g = v.geometry as { type?: unknown; coordinates?: unknown } | undefined;\n return !!g && (g.type === 'Polygon' || g.type === 'MultiPolygon') && Array.isArray(g.coordinates);\n};\n\n/** @ignore */\nexport const isPolygonFeatureArray = <P = unknown>(value: unknown): value is PolygonFeature<P>[] =>\n Array.isArray(value) && value.every(isPolygonFeature);\n\n/** Shared `describe()` text for the analyse-* `outputFormat` enum field. */\nexport const ANALYSE_OUTPUT_FORMAT_DESCRIPTION =\n 'Shape of the returned value (default: \"json\"). ' +\n '`json` — compact JSON-serializable object for text display. ' +\n '`chart` — Chart.js `ChartConfiguration` rendered via chart.js in the chat UI. ' +\n 'Pick `chart` whenever the user asks for a \"chart\", \"graph\", \"plot\", \"histogram\", \"bar/pie/line chart\", etc.';\n\n/**\n * Builds the shared \"return value\" blurb for the analyse-* tools' `code` field.\n * Encodes the JSON-only contract, the Chart.js config shape, and the\n * `string[]`-label workaround for tooltip-callback use cases.\n *\n * @param jsonExamples Comma-separated examples of typical `json`-shape outputs\n * for the specific tool (e.g. \"counts, groupings, hex bins, centroids\").\n * @param chartLabelExample Optional inline code snippet illustrating a\n * multi-line `string[]` label using the tool's domain. Omitted when empty.\n */\nexport const buildAnalyseReturnPrompt = (jsonExamples: string, chartLabelExample = ''): string => {\n const example = chartLabelExample ? ` (e.g. ${chartLabelExample})` : '';\n return (\n 'Return value MUST be pure JSON — plain objects, arrays, strings, numbers, booleans, null. No functions, no classes, no DOM/Map refs (it crosses a JSON boundary). Keep it small for chat display.\\n' +\n `• \\`json\\` (default) — compact object (${jsonExamples}, …).\\n` +\n '• `chart` — Chart.js config: `{ type: \"bar\"|\"line\"|\"pie\"|\"doughnut\"|\"radar\"|\"polarArea\"|\"scatter\"|\"bubble\", data: { labels: (string | string[])[], datasets }, options? }`. ' +\n `For per-point detail you would normally put in a \\`tooltip.callbacks\\` function, use a \\`string[]\\` label — Chart.js renders it multi-line on the axis tick AND in the tooltip title${example}.`\n );\n};\n\n// Sandbox return values cross the AI SDK's `jsonValueSchema` boundary as part\n// of the tool-result message. That schema accepts `null` but NOT `undefined`,\n// so user code that produces sparse arrays (`arr[i] = v` skipping indices),\n// `NaN`, `Infinity`, or `undefined` object values silently looks JSON-fine\n// (those serialize to `null`/get dropped) yet poisons every subsequent turn —\n// the next prompt fails ModelMessage validation with no obvious culprit.\n// Round-tripping through `JSON.stringify`/`JSON.parse` normalizes every one of\n// those cases before the value leaves the tool. Circular refs (the only thing\n// `stringify` throws on) are surfaced as a clear LLM-facing error instead.\n/**\n * Validates the value returned by analyse-* sandboxed code: rejects\n * `undefined`, JSON-normalizes via `toJsonSafe`, and — when\n * `outputFormat: \"chart\"` — checks the result is a Chart.js\n * `ChartConfiguration`. Centralises the contract shared by analyse-places,\n * analyse-routes, and analyse-geometries.\n *\n * @ignore\n */\nexport const validateAnalysisResult = (\n analysis: unknown,\n outputFormat: AnalysisOutputFormat,\n): { value: unknown } | { error: string } => {\n if (analysis === undefined) {\n return {\n error:\n 'Analysis code must return a value (the aggregation result). ' +\n 'Hint: `code` is the function BODY, not a function expression. If you wrote ' +\n '`(args) => { ...; return result; }` (or `async (args) => { ... }`) as the whole code, ' +\n 'the arrow is created and discarded — drop the wrapper and have the body itself end with `return result;`.',\n };\n }\n\n const normalized = toJsonSafe(analysis);\n if ('error' in normalized) return { error: normalized.error };\n\n if (outputFormat === 'chart' && !isChartConfiguration(normalized.value)) {\n return {\n error:\n 'Analysis code with `outputFormat: \"chart\"` must return a Chart.js ChartConfiguration ' +\n '({ type: \"bar\"|\"line\"|\"pie\"|\"doughnut\"|\"radar\"|\"polarArea\"|\"scatter\"|\"bubble\", data: { labels, datasets }, options? }).',\n };\n }\n\n return { value: normalized.value };\n};\n\nconst toJsonSafe = (value: unknown): { value: unknown } | { error: string } => {\n try {\n // JSON round-trip, NOT structuredClone: this must COERCE to a JSON-safe value\n // (drop `undefined`, `NaN`/`Infinity` → `null`, Date → ISO string, throw on\n // circular) before it crosses the AI SDK's `jsonValueSchema` boundary.\n // `structuredClone` faithfully preserves those non-JSON values, which then\n // poison later ModelMessage validation — see the note above this function.\n return { value: JSON.parse(JSON.stringify(value)) };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n error:\n `Returned value is not JSON-serializable: ${message}. ` +\n 'Return only plain objects, arrays, strings, numbers, booleans, and null — ' +\n 'no functions, classes, circular references, or DOM/Map references.',\n };\n }\n};\n\n// Globals shadowed to `undefined` inside every sandbox body. LLM-authored code\n// has no legitimate need for network / storage / DOM / global reaches: the data\n// it works on is injected, and the only libraries it needs (`h3`, `turf`,\n// `routeUtils`) are injected too. Binding these names as `undefined` parameters\n// turns a stray `fetch(...)` / `localStorage.getItem(...)` into a loud\n// \"fetch is not a function\" instead of a silent exfiltration or browser-state\n// mutation.\n//\n// `process` matters specifically for the Node main-thread path (the `'auto'`\n// default on the server): it is a direct global there and the first hop toward\n// `child_process` → RCE. Harmless to shadow in the browser (no such global).\n//\n// This is a TRIPWIRE, not a security boundary. The same capabilities stay\n// reachable via constructor walks (`({}).constructor.constructor`) and other\n// realm escapes, so it raises the bar and surfaces accidents — it does not\n// contain a determined attacker. True isolation is a separate concern (running\n// these tools off the main thread in a locked-down realm).\n//\n// Only valid, non-reserved identifiers are listed: `eval` / `arguments` are\n// illegal as strict-mode parameter names, and `Function` is omitted because it\n// is trivially reachable through any constructor — shadowing it would be theatre.\nconst SHADOWED_GLOBALS = [\n 'fetch',\n 'XMLHttpRequest',\n 'WebSocket',\n 'EventSource',\n 'localStorage',\n 'sessionStorage',\n 'indexedDB',\n 'document',\n 'window',\n 'globalThis',\n 'self',\n 'navigator',\n 'importScripts',\n 'process',\n] as const;\n\n// Exported so the iframe-worker executor (and any future executor) can re-apply the\n// exact same shadowing inside its own realm, keeping behaviour identical across executors.\n/** @ignore */\nexport const sandboxedGlobalShadows = SHADOWED_GLOBALS;\n\n// Injected parameter names the worker supplies itself rather than receiving over\n// `postMessage`: `turf` / `h3` are function namespaces (not cloneable), `routeUtils`\n// is the route-helper namespace (bundled into the worker as `self.routeUtils`; shared\n// by analyseData + processData), and `log` (monitor path) is a no-op in the worker.\n// Two consumers share this one list:\n// • the iframe-worker executor partitions these OUT of the data it posts and the\n// worker re-provides them from its own globals;\n// • the main-thread executor skips them when deep-copying inputs (`structuredClone`\n// throws on the function namespaces).\n// Defined here (the core module) so both share one source of truth without an import\n// cycle. `now` (monitor clock, a Date) is intentionally absent: it is cloneable, so\n// the main-thread copy harmlessly clones it, and the monitor path never routes\n// through the worker — keeping this list exactly the set the worker re-provides.\n/** @ignore */\nexport const WORKER_PROVIDED_PARAMS = ['turf', 'h3', 'routeUtils', 'log', 'cluster'] as const;\n\n/**\n * Pluggable strategy for executing sandbox code. The default\n * {@link mainThreadExecutor} compiles + runs in the host realm; an alternative\n * executor (e.g. the iframe-worker one) can run the same code in a locked-down\n * realm off the main thread while honouring this exact contract.\n *\n * `run` MUST resolve to `{ value }` or `{ error }` and never reject — execution\n * failures are reported as `{ error }` via {@link formatSandboxExecutionError}.\n *\n * @group Agent Toolkit\n */\nexport type SandboxExecutor = {\n run<Result = unknown>(\n code: string,\n paramNames: readonly string[],\n args: readonly unknown[],\n verb: string,\n ): Promise<{ value: Result } | { error: string }>;\n /** Release any held resources (iframe / worker). No-op for the main-thread executor. */\n destroy?(): void;\n};\n\n// Deep-copy a single sandbox arg before it crosses into LLM-authored code, UNLESS it\n// is a library namespace (which `structuredClone` would throw on) or `undefined`. The\n// merged / per-entry data views are shallow — their feature objects are the SAME\n// references held in the entry slices — so without this copy a sandbox body could\n// mutate live app state in place (`placesByEntry[id].features[0].properties = …`). We copy rather\n// than freeze so legitimate in-place work (`placesByEntry[id].features.sort(...)`) still succeeds\n// on the throwaway copy.\n//\n// This clone lives in the MAIN-THREAD executor only: the iframe-worker executor gets a\n// copy for free because `postMessage` structured-clones every data arg across the\n// worker boundary. Cloning here too would be redundant double-work — so callers\n// (`packSandboxArgs`, the monitor path) now pass live references and let each executor\n// copy exactly once, only where needed.\nconst cloneDataArg = (value: unknown, name: string): unknown =>\n value === undefined || (WORKER_PROVIDED_PARAMS as readonly string[]).includes(name)\n ? value\n : structuredClone(value);\n\n// Compile `code` as the body of an async function taking `paramNames` (+ the shadowed\n// globals as trailing `undefined` params) and run it with deep-copied `args`.\nconst compileAndRun = async <Result>(\n code: string,\n paramNames: readonly string[],\n args: readonly unknown[],\n verb: string,\n): Promise<{ value: Result } | { error: string }> => {\n try {\n const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new (\n ...names: string[]\n ) => (...callArgs: unknown[]) => Promise<Result> | Result;\n // NOTE: `code` arrives already sanitised — `runSandboxedFn` strips injected\n // redeclarations once, before dispatching to any executor, so the iframe-worker\n // realm gets the same treatment. (The worker compiles `code` verbatim too.)\n // Never shadow a name the caller legitimately injects — that would add a\n // duplicate formal parameter and clobber the real value with `undefined`.\n const shadows = SHADOWED_GLOBALS.filter((name) => !paramNames.includes(name));\n // Deep-copy the data inputs so the body can mutate them in place without\n // corrupting live state; library namespaces and undefined pass through.\n const clonedArgs = args.map((value, index) => cloneDataArg(value, paramNames[index]));\n // Caller args stay first so they line up positionally with `paramNames`;\n // the shadow `undefined`s fill the appended slots.\n const fn = new AsyncFunction(...paramNames, ...shadows, code);\n return { value: await fn(...clonedArgs, ...shadows.map(() => undefined)) };\n } catch (error) {\n return { error: formatSandboxExecutionError(verb, error) };\n }\n};\n\n/**\n * Default executor for Node/SSR (and the in-browser fallback): compiles + runs the\n * code synchronously in the host realm. Deep-copies data inputs (see\n * {@link cloneDataArg}) so the body can't mutate live state, but provides no isolation\n * beyond the global shadowing — see {@link SHADOWED_GLOBALS}.\n *\n * @ignore\n */\nexport const mainThreadExecutor: SandboxExecutor = {\n run: compileAndRun,\n};\n\n/**\n * Compiles `code` as the body of an `async` function that takes the named\n * `paramNames`, sanitises injected redeclarations, runs it with `args`, and\n * returns either `{ value }` or `{ error }`. Centralises the AsyncFunction\n * dance shared by every sandbox-running tool (analyse-* and process-*) so\n * each callsite only owns its inputs and result handling.\n *\n * In addition to the caller's `paramNames`, a fixed set of network / storage /\n * DOM globals ({@link SHADOWED_GLOBALS}) is appended as `undefined` parameters\n * so the body cannot reach them by name. This is defense-in-depth, not a\n * sandbox: it surfaces accidental/undesired global access as an error rather\n * than containing a hostile escape (see the note on `SHADOWED_GLOBALS`).\n *\n * Injected-redeclaration sanitising happens HERE, once, before dispatch — so every\n * executor (main-thread and iframe-worker alike) runs identically de-fanged code\n * and an LLM `const turf = require(...)` can't compile in one realm but throw in\n * another. `executor` is REQUIRED (no implicit main-thread default): callers must\n * choose the realm explicitly — `state.codeExecution` for the env-resolved sandbox,\n * or `mainThreadExecutor` to opt into host-realm execution on purpose. `verb` is the\n * human-readable action used in the error prefix (e.g. `\"Analysis\"`).\n *\n * @ignore\n */\nexport const runSandboxedFn = <Result = unknown>(\n code: string,\n paramNames: readonly string[],\n args: readonly unknown[],\n verb: string,\n executor: SandboxExecutor,\n): Promise<{ value: Result } | { error: string }> =>\n executor.run<Result>(stripInjectedRedeclarations(code, paramNames), paramNames, args, verb);\n\n/** @ignore */\nexport const stripInjectedRedeclarations = (code: string, identifiers: readonly string[]): string => {\n const names = identifiers.join('|');\n // RHS forms we recognise as redeclarations of injected identifiers:\n // require('…') / import('…') / await import('…') — module-system reaches\n // arguments.turf / arguments['turf'] — reaching for the same param off `arguments`\n // arguments[N].turf / arguments[N]['turf'] — same, with a (pointless) index first\n // The `[N]` index is optional so both the bare and indexed forms are stripped.\n // Anything else (e.g. `const turf = computeTurf()`) stays untouched so we never corrupt\n // genuine user code that happens to shadow an injected name.\n const rhs =\n String.raw`(?:(?:await\\s+)?(?:require|import)\\s*\\(` +\n String.raw`|arguments\\s*(?:\\[\\s*\\d+\\s*\\])?\\s*(?:\\.\\w+|\\[\\s*['\"]\\w+['\"]\\s*\\]))`;\n const pattern = new RegExp(\n String.raw`^[ \\t]*(?:const|let|var)[ \\t]+(?:${names})[ \\t]*=[ \\t]*${rhs}[^\\n]*\\n?`,\n 'gm',\n );\n return code.replace(pattern, '');\n};\n\n// Hints appended to LLM-facing error strings when the runtime message matches\n// a known pitfall in model-generated sandbox code. Lets the model self-correct\n// on the next call instead of guessing what went wrong.\n//\n// Order matters — earlier patterns win. The library-specific\n// `h3.X is not a function` / `turf.X is not a function` rules are listed\n// before the generic `is not a function` so callers get the targeted redirect\n// (the libraries are pure-geometry — no spatial-search / no I/O — and the\n// generic hint doesn't say that).\nconst SANDBOX_ERROR_HINTS: ReadonlyArray<{ pattern: RegExp; hint: string }> = [\n {\n pattern: /Cannot read propert(?:y|ies) of undefined/i,\n hint: 'A property was read on `undefined`. Likely a `reduce` callback that does not `return` the accumulator, or a `reduce` called without an initial value — use `arr.reduce((acc, x) => { ...; return acc; }, {})`. Also check unguarded chains like `feature.properties.poi.categories` — use optional chaining (`?.`) and `??` defaults.',\n },\n {\n pattern: /Cannot read propert(?:y|ies) of null/i,\n hint: 'A property was read on `null`. Guard property access with `?.` and provide defaults with `??`.',\n },\n {\n pattern: /\\bh3\\.\\w+ is not a function/i,\n hint: '`h3` is [h3-js](https://github.com/uber/h3-js) — hex-grid math only. NO spatial search / place lookup / HTTP. Common: `latLngToCell`, `cellToLatLng`, `cellToBoundary`, `polygonToCells`, `gridDisk`, `cellArea`. For nearby places call `discoverPlaces` BEFORE this tool — sandbox cannot call other tools.',\n },\n {\n pattern: /\\bturf\\.\\w+ is not a function/i,\n hint: '`turf` is [@turf/turf](https://turfjs.org/) — GeoJSON geometry only. NO spatial search / place lookup / HTTP. Common: `area`, `length`, `bbox`, `centroid`, `union`, `intersect`, `buffer`, `distance`, `booleanPointInPolygon`, `pointsWithinPolygon`, `clustersDbscan`, `convex`. Verify names at https://turfjs.org/docs/. For nearby places call `discoverPlaces` BEFORE this tool.',\n },\n {\n pattern: /coordinates must be an Array|Unknown Geometry Type/i,\n hint: 'turf was handed a raw coordinate (or a bare Array of features) instead of a Feature / FeatureCollection. Do NOT extract `.geometry.coordinates` with `.map` / `.flatMap` and feed the result to turf — pass the WHOLE feature: `turf.pointToLineDistance(pointFeature, lineFeature, { units: \"meters\" })`. `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)))`.',\n },\n {\n pattern: /coord must be (?:a )?(?:GeoJSON )?Point|must be a Point/i,\n 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)`.',\n },\n {\n pattern: /is not a function/i,\n hint: 'A method was called on a value that does not have it (typo, or the value is not the expected type). Verify the type before calling, e.g. `Array.isArray(x) ? x.map(...) : []`.',\n },\n {\n pattern: /Illegal return statement/i,\n hint: 'A `return` was placed outside the top-level function body — usually a missing brace or a callback that the parser thinks closed too early. Common cause: a multi-line `const X = arr.map((item) => { ... })` whose closing `})` was lost. Re-emit the code with balanced braces, keep `return` only at the top level of the body, and try again.',\n },\n {\n pattern: /Identifier ['\"]?\\w+['\"]? has already been declared/i,\n hint: 'A variable was declared twice — typically because the code prepends `const turf = require(...)` / `const turf = await import(...)` / `const turf = arguments.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])`).',\n },\n {\n pattern: /Unexpected (?:token|identifier|end of input)/i,\n hint: 'The code did not parse as a JavaScript async-function body. Common causes: unbalanced braces/parens/brackets, a stray template literal, or `import`/`export` statements (not allowed in sandbox bodies — `placesByEntry`/`geometriesByEntry`/`h3`/`turf` are already in scope). Re-emit a self-contained body that ends with `return ...;`.',\n },\n {\n pattern: /\\brequire is not defined\\b|\\bimport (?:is not defined|outside a module)\\b/i,\n hint: 'The sandbox is not a Node module — `require` and top-level `import` are not available. The libraries you need (`h3`, `turf`) and inputs (`placesByEntry`, `geometriesByEntry`, …) are already injected as parameters; drop the import lines and use them directly.',\n },\n {\n pattern: /\\b(?:functions|tools|agent|recallState|discoverPlaces|locatePlace) is not defined\\b/i,\n 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.',\n },\n {\n pattern: /is not iterable/i,\n hint: 'A value used in `for..of`, spread, or destructuring was not iterable. Default arrays with `?? []`, e.g. `for (const c of feature.properties.poi?.categories ?? []) { ... }`.',\n },\n {\n pattern: /Cannot access '.+?' before initialization/i,\n hint: 'A `let`/`const` was used before its declaration (temporal dead zone). Move the declaration above its first use.',\n },\n {\n pattern: /Assignment to constant variable/i,\n hint: 'A `const`-declared variable was reassigned. Either use `let`, or mutate the value in place.',\n },\n {\n pattern: /structuredClone|could not be cloned|DataCloneError/i,\n hint: 'Return value must be pure JSON — no functions or classes. The result crosses a JSON boundary before rendering. For per-point detail you would put in a Chart.js `tooltip.callbacks` function, use a `string[]` label instead — it renders multi-line in the axis tick and the tooltip title.',\n },\n];\n\n/**\n * Builds an LLM-facing error string for sandboxed code execution failures.\n * Appends a `Hint:` suffix when the error matches a common pattern so the\n * model can self-correct on retry. `verb` is the human-readable action used\n * in the prefix (e.g. `\"Analysis\"`, `\"Process\"`).\n */\n/** @ignore */\nexport const formatSandboxExecutionError = (verb: string, error: unknown): string => {\n const message = error instanceof Error ? error.message : String(error);\n const base = `${verb} code execution failed: ${message}`;\n // Specific hints win over the generic SyntaxError catch-all: several syntax\n // errors (e.g. \"Identifier '…' has already been declared\", \"Illegal return\n // statement\", \"Unexpected token\") have targeted, actionable hints below, and\n // the generic \"not valid JavaScript\" line would otherwise preempt them.\n const match = SANDBOX_ERROR_HINTS.find(({ pattern }) => pattern.test(message));\n if (match) return `${base} Hint: ${match.hint}`;\n if (error instanceof Error && error.name === 'SyntaxError') {\n return `${base} Hint: the provided \\`code\\` is not valid JavaScript — ensure it parses as an async function body and \\`return\\`s a value.`;\n }\n return base;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Pure (DOM-free) building blocks for the iframe-worker sandbox executor: the\n * worker IIFE source assembly and the arg-partitioning that decides which\n * injected parameters are cloneable DATA (posted into the worker) versus\n * worker-provided LIB namespaces (`turf` / `h3` / `routeUtils` / `log`). Kept\n * separate from the DOM harness so it can be unit-tested without a browser.\n */\n\nimport { sandboxedGlobalShadows, WORKER_PROVIDED_PARAMS } from '../sandbox-code';\n\n/** RPC request posted into the iframe → worker. @ignore */\nexport type SandboxRunRequest = {\n type: 'run';\n id: number;\n code: string;\n paramNames: readonly string[];\n /** Cloneable values for the non-lib params, keyed by their index in `paramNames`. */\n dataByIndex: Record<number, unknown>;\n};\n\n/** RPC response posted back from the worker → iframe → parent. @ignore */\nexport type SandboxRunResponse =\n | { type: 'result'; id: number; ok: true; value: unknown }\n | { type: 'result'; id: number; ok: false; errorMessage: string };\n\n/**\n * Split sandbox call args into the cloneable DATA values (to be posted into the\n * worker) and drop the worker-provided lib args. Returns `dataByIndex` keyed by\n * position so the worker can reconstruct the full positional argument list,\n * filling lib positions from its own globals.\n *\n * @ignore\n */\nexport const partitionSandboxArgs = (\n paramNames: readonly string[],\n args: readonly unknown[],\n): { dataByIndex: Record<number, unknown> } => {\n const provided = new Set<string>(WORKER_PROVIDED_PARAMS);\n const dataByIndex: Record<number, unknown> = {};\n for (let index = 0; index < paramNames.length; index++) {\n if (!provided.has(paramNames[index])) dataByIndex[index] = args[index];\n }\n return { dataByIndex };\n};\n\n// The worker's message loop, as a string injected into the blob worker. It is\n// concatenated AFTER the library UMD source (which defines `self.turf` / `self.h3`).\n// Each `run` message compiles a fresh AsyncFunction — no user state leaks between\n// calls (the \"reset between calls\" guarantee; the worker itself stays warm).\nconst 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 self.postMessage({ type: 'ready' });\n})();\n`;\n\n/**\n * Assemble the full self-contained worker source: the consumer-provided library\n * UMD source (which must define `self.turf` and `self.h3`) followed by the\n * message loop. The result is a single string a blob worker can run with no\n * network access — the Option B \"portable, self-contained string\" shape.\n *\n * @ignore\n */\nexport const assembleWorkerSource = (librarySource: string): string => `${librarySource}\\n;${WORKER_RUNTIME_BODY}`;\n","/**\n * @module agent-toolkit-tools\n *\n * iframe-worker sandbox executor (Phase 3, first implementation).\n *\n * Runs analyse/process code in a **Web Worker hosted inside a sandboxed,\n * opaque-origin iframe**, so the code cannot reach the parent's DOM / storage\n * (opaque origin), cannot make network requests (CSP `default-src 'none'`), and\n * can be terminated on timeout (worker). The iframe is long-lived; the worker\n * stays warm and each call runs in a fresh function scope (reset between calls);\n * a timed-out call terminates and respawns the worker.\n *\n * Component layout (data crosses each hop via postMessage + structuredClone):\n *\n * main thread opaque-origin iframe (CSP default-src 'none')\n * ─────────── ─────────────────────────────────────────────\n * runSandboxedFn\n * → IframeWorkerExecutor ──post──▶ bootstrap (owns worker, relays messages)\n * └──post──▶ Web Worker (Blob URL)\n * worker-libs: self.turf/h3/routeUtils\n * worker-runtime: new AsyncFunction(…)\n * ◀── {ok,value} | {error} ◀──post──────────── ← structuredClone(result)\n * (timeout → terminate + respawn worker)\n *\n * The user-facing version of this diagram (with the threat→boundary mapping) lives\n * in the code-generation guide's \"How the pieces fit\" section.\n *\n * SECURITY STATUS: this is a FIRST IMPLEMENTATION. The isolation depends on\n * real-browser behaviour (CSP enforcement, opaque-origin iframes, Worker\n * termination) that the unit-test environment cannot verify — the `e2e-tests/`\n * Playwright suite exercises exactly those properties in real Chromium. When the\n * environment lacks the needed APIs, or no worker library source is supplied, it\n * falls back (loudly) to the main-thread executor.\n */\n\nimport type { CodeExecutionConfig } from '../../../types';\nimport { formatSandboxExecutionError, mainThreadExecutor, type SandboxExecutor } from '../sandbox-code';\nimport {\n assembleWorkerSource,\n partitionSandboxArgs,\n type SandboxRunRequest,\n type SandboxRunResponse,\n} from './worker-runtime';\n\n/** Default wall-clock budget for one isolated sandbox run. @ignore */\nexport const DEFAULT_SANDBOX_TIMEOUT_MS = 10_000;\n\n// How long to wait for the iframe + worker to come up before declaring the\n// isolated path unavailable and falling back. Keeps tests / SSR from hanging.\nconst INIT_TIMEOUT_MS = 2_000;\n\n// CSP for the sandbox document: no network/DOM egress (`default-src 'none'`),\n// inline bootstrap script allowed, `'unsafe-eval'` so the worker's AsyncFunction\n// compiles, and `blob:` so the worker can be spawned from a Blob URL. The worker\n// inherits this policy — so it, too, cannot reach the network.\nconst SANDBOX_CSP = \"default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob:; worker-src blob:\";\n\n// Bootstrap that runs inside the opaque-origin iframe: it owns the worker and\n// relays messages between the parent and the worker. Kept tiny — all sandbox\n// logic lives in the worker source assembled on the parent.\nconst IFRAME_BOOTSTRAP = `\n(function () {\n var worker = null, workerSource = null;\n function spawn() {\n var blob = new Blob([workerSource], { type: 'application/javascript' });\n worker = new Worker(URL.createObjectURL(blob));\n worker.onmessage = function (e) { parent.postMessage(e.data, '*'); };\n // A worker-level error (e.g. the library source or AsyncFunction eval throwing on\n // load) isn't tied to one run id — relay it as a distinct signal so the parent can\n // fail the in-flight calls with a real message instead of letting them time out.\n worker.onerror = function (e) {\n var detail = e && e.message ? (e.message + (e.filename ? ' @ ' + e.filename + ':' + e.lineno : '')) : String(e);\n parent.postMessage({ type: 'worker-error', errorMessage: detail }, '*');\n };\n }\n window.addEventListener('message', function (ev) {\n var msg = ev.data || {};\n if (msg.type === 'init') { workerSource = msg.workerSource; spawn(); parent.postMessage({ type: 'iframe-ready' }, '*'); return; }\n if (msg.type === 'run' && worker) { worker.postMessage(msg); return; }\n if (msg.type === 'terminate' && worker) { try { worker.terminate(); } catch (_e) {} spawn(); return; }\n });\n parent.postMessage({ type: 'bootstrap-ready' }, '*');\n})();\n`;\n\nconst buildSrcdoc = (): string =>\n `<!doctype html><html><head><meta http-equiv=\"Content-Security-Policy\" content=\"${SANDBOX_CSP}\"></head>` +\n `<body><script>${IFRAME_BOOTSTRAP}</script></body></html>`;\n\ntype Pending = {\n resolve: (r: { value: unknown } | { error: string }) => void;\n verb: string;\n timer: ReturnType<typeof setTimeout>;\n};\n\n// Minimal browser-API surface the harness needs — feature-detected so the module\n// stays importable in Node / SSR (where it simply falls back to main-thread).\nconst hasBrowserSandboxApis = (): boolean =>\n typeof document !== 'undefined' &&\n typeof Worker !== 'undefined' &&\n typeof URL !== 'undefined' &&\n typeof Blob !== 'undefined';\n\ntype IframeWorkerOptions = {\n timeoutMs: number;\n /**\n * Returns the UMD source defining `self.turf` / `self.h3`. Defaults to the SDK's bundled\n * libs via {@link defaultLoadLibrarySource}; `resolveSandboxExecutor` always supplies one.\n */\n loadLibrarySource?: () => string | Promise<string>;\n};\n\n// Zero-config default: lazily load the SDK's own inlined turf + h3 UMD (a separate\n// chunk, so it never bloats the main bundle). Consumers can override via\n// `codeExecution.loadWorkerLibrarySource` to supply their own.\nconst defaultLoadLibrarySource = (): Promise<string> =>\n import('./worker-libs').then((module) => module.sandboxLibrarySource);\n\n// Create an iframe-worker SandboxExecutor. Falls back to the main-thread executor\n// (with a one-time warning) when the browser APIs or a library source are\n// unavailable, so callers can use it unconditionally. Internal — reached via\n// `resolveSandboxExecutor`.\nconst createIframeWorkerExecutor = (options: IframeWorkerOptions): SandboxExecutor => {\n let iframe: HTMLIFrameElement | null = null;\n let ready: Promise<boolean> | null = null;\n let unavailable = false;\n let warned = false;\n let nextId = 1;\n const pending = new Map<number, Pending>();\n\n const warnFallback = (reason: string): void => {\n if (warned) return;\n warned = true;\n // Surfacing a security-relevant fallback is intentional (not silent).\n console.warn(\n `[agent-toolkit] iframe-worker sandbox unavailable (${reason}); falling back to main-thread execution (NOT isolated).`,\n );\n };\n\n // Post a message into the sandbox iframe. The frame is opaque-origin (sandbox\n // without allow-same-origin), so it has NO addressable origin — `'*'` is the only\n // valid `targetOrigin` and a specific one is impossible by design. Safety comes\n // from the receiving side instead, which validates `event.source`. Centralised so\n // the unavoidable wildcard lives (and is justified) in exactly one place.\n const postToFrame = (message: unknown): void => {\n iframe?.contentWindow?.postMessage(message, '*'); // NOSONAR(typescript:S2819) — opaque-origin frame, '*' required\n };\n\n const onMessage = (event: MessageEvent): void => {\n if (event.source !== iframe?.contentWindow) return;\n const data = event.data as { type?: string; errorMessage?: string } | undefined;\n // A worker-level error isn't tied to one run id (it usually means the worker\n // crashed on load) — fail every in-flight call with it rather than time out.\n if (data?.type === 'worker-error') {\n const message = data.errorMessage ?? 'worker error';\n for (const [id, entry] of pending) {\n clearTimeout(entry.timer);\n entry.resolve({ error: formatSandboxExecutionError(entry.verb, message) });\n pending.delete(id);\n }\n return;\n }\n if (data?.type !== 'result') return;\n const response = data as SandboxRunResponse;\n const entry = pending.get(response.id);\n if (!entry) return;\n pending.delete(response.id);\n clearTimeout(entry.timer);\n entry.resolve(\n response.ok\n ? { value: response.value }\n : { error: formatSandboxExecutionError(entry.verb, response.errorMessage) },\n );\n };\n\n // Wait for a one-off signal type from the iframe, with an init timeout.\n const waitForSignal = (signal: string): Promise<void> =>\n new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n globalThis.removeEventListener('message', handler);\n reject(new Error(`timed out waiting for \"${signal}\"`));\n }, INIT_TIMEOUT_MS);\n const handler = (event: MessageEvent): void => {\n if (event.source === iframe?.contentWindow && (event.data as { type?: string })?.type === signal) {\n clearTimeout(timer);\n globalThis.removeEventListener('message', handler);\n resolve();\n }\n };\n globalThis.addEventListener('message', handler);\n });\n\n const init = async (): Promise<boolean> => {\n if (!hasBrowserSandboxApis()) {\n warnFallback('browser APIs missing');\n return false;\n }\n if (!options.loadLibrarySource) {\n warnFallback('no worker library source configured (codeExecution.loadWorkerLibrarySource)');\n return false;\n }\n try {\n const frame = document.createElement('iframe');\n frame.setAttribute('sandbox', 'allow-scripts'); // NO allow-same-origin → opaque origin\n frame.setAttribute('aria-hidden', 'true');\n frame.style.display = 'none';\n frame.srcdoc = buildSrcdoc();\n iframe = frame;\n const bootstrapReady = waitForSignal('bootstrap-ready');\n document.body.appendChild(frame);\n await bootstrapReady;\n\n const librarySource = await options.loadLibrarySource();\n const workerSource = assembleWorkerSource(librarySource);\n const iframeReady = waitForSignal('iframe-ready');\n postToFrame({ type: 'init', workerSource });\n await iframeReady;\n\n globalThis.addEventListener('message', onMessage);\n return true;\n } catch (error) {\n warnFallback(error instanceof Error ? error.message : String(error));\n iframe?.remove();\n iframe = null;\n return false;\n }\n };\n\n const ensureReady = (): Promise<boolean> => {\n if (unavailable) return Promise.resolve(false);\n ready ??= init().then((ok) => {\n if (!ok) unavailable = true;\n return ok;\n });\n return ready;\n };\n\n const run: SandboxExecutor['run'] = async (code, paramNames, args, verb) => {\n const ok = await ensureReady();\n if (!ok || !iframe) return mainThreadExecutor.run(code, paramNames, args, verb);\n\n const id = nextId++;\n const { dataByIndex } = partitionSandboxArgs(paramNames, args);\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n pending.delete(id);\n // Kill the runaway worker and respawn it so later calls still work.\n postToFrame({ type: 'terminate' });\n resolve({\n error: formatSandboxExecutionError(\n verb,\n new Error(\n `code execution exceeded the ${options.timeoutMs}ms sandbox timeout and was terminated`,\n ),\n ),\n });\n }, options.timeoutMs);\n pending.set(id, { resolve: resolve as Pending['resolve'], verb, timer });\n try {\n const request: SandboxRunRequest = { type: 'run', id, code, paramNames: [...paramNames], dataByIndex };\n postToFrame(request);\n } catch (error) {\n clearTimeout(timer);\n pending.delete(id);\n // A non-cloneable data arg (shouldn't happen — inputs are JSON) — report cleanly.\n resolve({ error: formatSandboxExecutionError(verb, error) });\n }\n });\n };\n\n const destroy = (): void => {\n if (globalThis.window !== undefined) globalThis.removeEventListener('message', onMessage);\n for (const entry of pending.values()) clearTimeout(entry.timer);\n pending.clear();\n iframe?.remove();\n iframe = null;\n };\n\n return { run, destroy };\n};\n\n/**\n * Resolve a {@link CodeExecutionConfig} to a concrete {@link SandboxExecutor}.\n *\n * Selection is **env-based, not configurable**: the browser always isolates in an\n * iframe-worker; Node / SSR always runs on the main thread. The browser boundary is\n * the iframe's opaque origin + CSP egress-block (plus worker termination); none of\n * that exists in Node, where a `worker_thread` would add only termination while\n * exposing `fs` / `net` / `child_process` — isolated in name only — so we don't spin\n * one up there. (If the iframe fails to initialise at runtime in the browser, the\n * executor itself falls back to the main thread with a warning.)\n *\n * @ignore\n */\nexport const resolveSandboxExecutor = (config?: CodeExecutionConfig): SandboxExecutor => {\n if (!hasBrowserSandboxApis()) return mainThreadExecutor;\n return createIframeWorkerExecutor({\n timeoutMs: config?.timeoutMs ?? DEFAULT_SANDBOX_TIMEOUT_MS,\n // Zero-config: fall back to the SDK's bundled turf/h3 when the consumer\n // doesn't supply its own worker library source.\n loadLibrarySource: config?.loadWorkerLibrarySource ?? defaultLoadLibrarySource,\n });\n};\n","/**\n * @module agent-toolkit-state\n *\n * Runtime \"schema\" inference for a BYOD FeatureCollection. A BYOD entry's only\n * guaranteed shape is \"GeoJSON FeatureCollection\" — the geometry types and the\n * feature `properties` are whatever the customer's data happens to carry. This\n * module derives a compact, token-frugal profile of that shape so the agent can\n * reason about the data (which properties to filter on, what geometry to expect)\n * without ever shipping the full collection to the model.\n *\n * Dependency-free on purpose: a generic JSON-Schema inferrer (`to-json-schema`,\n * `generate-schema`, …) would emit verbose, deeply-nested schemas that cost more\n * tokens than they save and still need GeoJSON-specific post-processing. A single\n * linear pass over the features yields exactly the fields the LLM needs.\n */\n\nimport type { FeatureCollection, GeoJsonProperties } from 'geojson';\n\n/**\n * Compact profile of one feature-`properties` key across the whole collection.\n *\n * @ignore\n */\nexport type BYODPropertyProfile = {\n /** Property key as it appears in `feature.properties`. */\n name: string;\n /** Distinct JSON value types seen for this key (e.g. `[\"string\"]`, `[\"number\", \"null\"]`). */\n types: string[];\n /** Fraction of features carrying this key, 0–1 (1 = present on every feature). */\n coverage: number;\n /** Up to a few short, distinct example values — lets the LLM infer semantics. */\n examples: Array<string | number | boolean>;\n};\n\n/**\n * Token-frugal description of a BYOD FeatureCollection's runtime shape: feature\n * count, the geometry types present, and a per-property profile. Stored on every\n * {@link BYODEntry} and surfaced through `addByodSource` / `recallState`\n * so the model never has to receive raw GeoJSON just to learn the schema.\n *\n * @group Agent Toolkit\n *\n * @ignore\n */\nexport type BYODDataProfile = {\n featureCount: number;\n /** GeoJSON geometry types present (Point / LineString / Polygon / …). */\n geometryTypes: string[];\n /** Per-property profile, highest-coverage first. Capped — see `propertiesOmitted`. */\n properties: BYODPropertyProfile[];\n /** Count of property keys omitted when the collection has more distinct keys than the cap. */\n propertiesOmitted?: number;\n};\n\n// Caps that bound the profile's size regardless of how wide or heterogeneous the\n// customer data is — the profile is meant to be cheap to ship to the model.\nconst MAX_PROPERTIES = 40;\nconst MAX_EXAMPLES_PER_PROPERTY = 3;\nconst MAX_EXAMPLE_STRING_LENGTH = 60;\n\nconst jsonTypeOf = (value: unknown): string => {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n};\n\n// Accumulator built per property key during the single pass, then collapsed into\n// a BYODPropertyProfile at the end.\ntype PropertyAccumulator = {\n count: number;\n types: Set<string>;\n examples: Array<string | number | boolean>;\n};\n\nconst recordExample = (accumulator: PropertyAccumulator, value: unknown): void => {\n if (accumulator.examples.length >= MAX_EXAMPLES_PER_PROPERTY) return;\n // Only primitives make compact, useful examples; objects/arrays are captured by `types`.\n if (typeof value === 'string') {\n const trimmed =\n value.length > MAX_EXAMPLE_STRING_LENGTH ? `${value.slice(0, MAX_EXAMPLE_STRING_LENGTH)}…` : value;\n if (!accumulator.examples.includes(trimmed)) accumulator.examples.push(trimmed);\n return;\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n if (!accumulator.examples.includes(value)) accumulator.examples.push(value);\n }\n};\n\nconst collectGeometryTypes = (data: FeatureCollection): string[] => {\n const types = new Set<string>();\n for (const feature of data.features) {\n const type = feature.geometry?.type;\n if (type) types.add(type);\n }\n return [...types];\n};\n\nconst accumulateProperties = (data: FeatureCollection): Map<string, PropertyAccumulator> => {\n const byKey = new Map<string, PropertyAccumulator>();\n for (const feature of data.features) {\n const properties: GeoJsonProperties = feature.properties;\n if (!properties || typeof properties !== 'object') continue;\n for (const [key, value] of Object.entries(properties)) {\n let accumulator = byKey.get(key);\n if (!accumulator) {\n accumulator = { count: 0, types: new Set(), examples: [] };\n byKey.set(key, accumulator);\n }\n accumulator.count += 1;\n accumulator.types.add(jsonTypeOf(value));\n recordExample(accumulator, value);\n }\n }\n return byKey;\n};\n\n/**\n * Derive a {@link BYODDataProfile} from a GeoJSON FeatureCollection in a single\n * linear pass. Bounded output: at most {@link MAX_PROPERTIES} property profiles,\n * each with at most a few short examples.\n *\n * @ignore\n */\nexport const profileFeatureCollection = (data: FeatureCollection): BYODDataProfile => {\n const featureCount = data.features.length;\n const byKey = accumulateProperties(data);\n\n const allProperties: BYODPropertyProfile[] = [...byKey.entries()]\n .map(([name, accumulator]) => ({\n name,\n types: [...accumulator.types],\n coverage: featureCount > 0 ? Math.round((accumulator.count / featureCount) * 100) / 100 : 0,\n examples: accumulator.examples,\n }))\n // Highest-coverage first, then alphabetical — the most-shared keys read first.\n .sort((first, second) => second.coverage - first.coverage || first.name.localeCompare(second.name));\n\n const properties = allProperties.slice(0, MAX_PROPERTIES);\n const propertiesOmitted = allProperties.length - properties.length;\n\n return {\n featureCount,\n geometryTypes: collectGeometryTypes(data),\n properties,\n ...(propertiesOmitted > 0 && { propertiesOmitted }),\n };\n};\n\n/**\n * Strip attacker-controlled free-text from a {@link BYODDataProfile} before it\n * crosses into the language model's context. A BYOD entry's data is supplied by\n * the customer (URL fetch / inline GeoJSON), so its property string VALUES are\n * untrusted — a malicious feature could carry prompt-injection text in a\n * property value, and those values are exactly what {@link profileFeatureCollection}\n * samples into each property's `examples`.\n *\n * This keeps every *structural* part of the profile (feature count, geometry\n * types, property key names, JSON types, coverage, `propertiesOmitted`) and the\n * numeric / boolean examples (which cannot carry instructions), and drops only\n * the **string** examples. The full profile — examples included — stays on the\n * {@link BYODEntry} for the embedding app to render directly to the user; it is\n * just never echoed back to the model as if it were trusted.\n *\n * @group Agent Toolkit\n */\nexport const toByodSafeProfile = (profile: BYODDataProfile): BYODDataProfile => ({\n ...profile,\n properties: profile.properties.map((property) => ({\n ...property,\n examples: property.examples.filter((value) => typeof value !== 'string'),\n })),\n});\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type CustomGeoJSONLayerSpec, CustomGeoJSONModule, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { FeatureCollection } from 'geojson';\nimport type { StateSlice } from '../../types';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { BYODEntry, BYODSource } from './entry';\nimport { profileFeatureCollection } from './profile';\n\n/**\n * Events fired by {@link BYODState}. Subscribe via `state.byod.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type BYODStateEvents = {\n /**\n * History changed — entry added, removed, or cleared via `reset()`. `entries` is the full\n * snapshot after the change; `changedIds` lists the ids of the entries this specific change\n * added, replaced in place, or removed.\n */\n 'entries-change': { entries: readonly BYODEntry[]; changedIds: readonly string[] };\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n /** An analysis was attached to (or replaced on) an entry by `analyseData`. */\n};\n\n/**\n * Options accepted by {@link BYODState.addEntry}.\n *\n * @group Agent Toolkit\n */\nexport type AddBYODEntryOptions = {\n /**\n * Explicit MapLibre layer specs. Layers are never derived automatically:\n * when omitted the entry is created with no layers and renders nothing until\n * they are set — by the agent via `setByodLayers`, or by passing them here.\n */\n layers?: CustomGeoJSONLayerSpec[];\n /** Provenance for the data. Defaults to `{ kind: 'integrator' }`. */\n source?: BYODSource;\n /**\n * Semantic entry id. Auto-suffixed (`-2`, `-3`, …) on collision. Defaults\n * to `byod-N`.\n */\n explicitId?: string;\n};\n\n/**\n * Verdict returned by a {@link ByodSourceUrlValidator}: explicitly allow the fetch with\n * `{ valid: true }`, or block it with `{ valid: false, reason }` — the `reason` is surfaced\n * to the model as the tool error.\n *\n * @group Agent Toolkit\n */\nexport type ByodSourceUrlValidation = { valid: true } | { valid: false; reason: string };\n\n/**\n * App-supplied authorization hook for BYOD source URLs. Runs inside\n * `addByodSource` AFTER the built-in scheme / size / timeout policy and BEFORE\n * the network fetch. Return `{ valid: true }` to allow the fetch or\n * `{ valid: false, reason }` to block it (see {@link ByodSourceUrlValidation}).\n * May be sync or async — e.g. an allowlist lookup.\n *\n * The toolkit can't enforce SSRF guarantees the host itself can't, so it leaves\n * the URL surface open by default; this hook lets a deployment add its own\n * policy — refuse cloud-metadata endpoints (`169.254.169.254`), restrict to\n * known hosts, etc. — without the SDK prescribing one for everyone. Configure\n * via the `byod.validateSourceUrl` option on `createMapAgent`, or assign\n * `state.byod.sourceUrlValidator` directly.\n *\n * @group Agent Toolkit\n */\nexport type ByodSourceUrlValidator = (url: URL) => ByodSourceUrlValidation | Promise<ByodSourceUrlValidation>;\n\n/**\n * State slice for BYOD (bring-your-own-data) GeoJSON layers — customer-owned\n * data that doesn't fit places / routes / ranges / custom-geometries. Each\n * entry wraps a {@link CustomGeoJSONModule} so the agent can render, query,\n * and pipe it through analyseData / processData.\n *\n * Per-entry display model mirrors {@link CustomGeometriesState}: every entry\n * owns its own module, lazy-initialised on first show.\n *\n * @group Agent Toolkit\n */\nexport class BYODState implements ShownEntriesSlice, StateSlice {\n private _entries: BYODEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link BYODStateEvents}. */\n readonly events = new StateEvents<BYODStateEvents>();\n\n /**\n * Optional authorization hook for `addByodSource` URL fetches — see\n * {@link ByodSourceUrlValidator}. Unset = no restriction beyond the built-in\n * scheme / size / timeout policy.\n */\n sourceUrlValidator?: ByodSourceUrlValidator;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly BYODEntry[] {\n return this._entries;\n }\n\n get latestEntry(): BYODEntry | undefined {\n return this._entries.at(-1);\n }\n\n /** Set of BYOD entry ids currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): BYODEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new BYOD entry. Under `single` mode the new entry replaces every older one (hides\n * + drops them before the new entry is pushed, so the caller never sees the new entry\n * rendered on top of a still-visible old one). Returns the assigned id.\n */\n async addEntry(data: FeatureCollection, label: string, options: AddBYODEntryOptions = {}): Promise<string> {\n const droppedIds = this._entryMode === 'single' ? this._entries.map((entry) => entry.id) : [];\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n // Always run through `pickUniqueEntryId` — the bare `byod-${length}` fallback collides\n // when an earlier entry has been removed (e.g. add a, add b, remove a → length=1 and\n // the new fallback `byod-1` clashes with the surviving entry's id). The dedupe path\n // appends `-2`, `-3`, … on collision, matching the explicit-id behaviour.\n const entryId = pickUniqueEntryId(\n options.explicitId ?? `byod-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data,\n source: options.source ?? { kind: 'integrator' },\n layers: options.layers ?? [],\n profile: profileFeatureCollection(data),\n });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [...droppedIds, entryId] });\n return entryId;\n }\n\n /**\n * Replace the MapLibre layer specs an entry renders under. When the entry already owns a\n * module (it has been shown at least once) the new specs are pushed live via the module's\n * `applyConfig` runtime layer diff — no tear-down, no flicker. When no module exists yet the\n * specs are stored and the next {@link showEntry} picks them up. Throws on an unknown id.\n */\n setEntryLayers(entryId: string, layers: CustomGeoJSONLayerSpec[]): void {\n const entry = this._requireEntry(entryId);\n entry.layers = layers;\n entry._module?.applyConfig({ sources: { data: { layers } } });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n }\n\n /**\n * Lazy-init the entry's CustomGeoJSONModule. The module is created with a\n * single source named `data`, configured with this entry's `layers`. To swap\n * layers after creation, call {@link setEntryLayers} — it pushes the new specs\n * into this module via `applyConfig` rather than recreating it.\n */\n async getEntryModule(entryId: string): Promise<CustomGeoJSONModule> {\n const entry = this._requireEntry(entryId);\n entry._module ??= await CustomGeoJSONModule.create(this._ttMap, {\n sources: { data: { layers: entry.layers } },\n });\n return entry._module;\n }\n\n /** Render this entry on the map. */\n async showEntry(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n // An entry has no layers until they are set explicitly (via setByodLayers or addEntry).\n // CustomGeoJSONModule requires at least one layer, so a layer-less entry simply isn't\n // rendered yet — no-op rather than throw. It becomes visible once setByodLayers adds layers.\n if (entry.layers.length === 0) return;\n await this._hideOthersUnderSingleMode(entryId);\n const module = await this.getEntryModule(entryId);\n await module.show(entry.data, 'data');\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n private async _hideOthersUnderSingleMode(entryId: string): Promise<void> {\n if (this._entryMode !== 'single') return;\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n await entry._module?.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Drop a single entry from history (clears its module first). No-op when unknown. */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n }\n\n private _requireEntry(entryId: string): BYODEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`BYODState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every BYOD layer currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n const clearedIds = this._entries.map((entry) => entry.id);\n for (const entry of this._entries) entry._module?.clear().catch(() => undefined);\n this._entries = [];\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * @module agent-toolkit-state\n *\n * The {@link EventsState} slice — generic trackers over toolkit state. A tracker is one rule: sandbox\n * code returning a {@link Verdict}, recomputed by a {@link JobEngine} job whenever its source entries\n * change. The rule's INPUTS are the job's (and the tracker's) `affectedEntryIds` — a rule reads one or\n * more entry kinds, e.g. hospitals + incidents. This slice owns the tracker metadata, one bit of\n * rising-edge memory (`wasActive`), the {@link Job} handle backing it, and the durable\n * {@link TrackerEvent} log the UI renders.\n *\n * It computes nothing itself. The job's `run` hands each fresh verdict to {@link ingestVerdict}, which\n * runs a pure rising-edge reducer: a verdict flipping `false→true` logs an `opened` alert, `true→false`\n * logs a `resolved` event. There is no episode state machine, escalation, or resolve-hysteresis: a\n * jittery feed can therefore produce open/resolve churn.\n *\n * @group Agent Toolkit\n */\n\nimport type { Job } from '../engine';\nimport type { StateSlice } from '../types/state';\nimport { StateEvents } from './events';\n\n/**\n * The features within ONE source entry that matched the rule — the per-entry unit of a {@link Verdict}.\n * `featureIds` is the matched SUBSET, not the whole entry: a rule selects features by arbitrary sandbox\n * logic (proximity, threshold, filter), and that selection isn't recoverable from `entryId` alone (the\n * entry holds every feature) without re-running the rule — so the verdict records exactly what matched.\n * These are the features the UI highlights and the `summary` cites.\n */\nexport type MemberRef = { entryId: string; featureIds: string[] };\n\n/**\n * What a tracker's sandboxed `evaluateCode` returns — stored verbatim as the rule analysis's `data`\n * and read back by the reducer. `members` groups matches by source entry so a verdict can span\n * multiple entry kinds (e.g. \"an incident within 100m of a hospital\" returns a hospitals group and an\n * incidents group). `summary` is a grounded headline the code produces deterministically.\n */\nexport type Verdict = {\n active: boolean;\n members: MemberRef[];\n summary: string;\n};\n\n/** Toast-worthy (`alert`) vs quietly-logged (`event`). One store; the type discriminates. */\nexport type TrackerEventType = 'alert' | 'event';\n/** Rising edge (`opened`) vs falling edge (`resolved`). The only two statuses in the rising-edge model. */\nexport type TrackerEventStatus = 'opened' | 'resolved';\n\n/** One thing that happened — a tracker crossing an edge. The durable record the feed + toasts render. */\nexport type TrackerEvent = {\n id: string;\n /** Source-data sample time of the recompute that fired it (NOT wall-clock). */\n at: number;\n trackerId: string;\n trackerName: string;\n type: TrackerEventType;\n kind: TrackerEventStatus;\n summary: string;\n members: MemberRef[];\n /** Rising-edge time of the episode this closes — present on `resolved`. */\n openedAt?: number;\n};\n\n/**\n * One tracker = one rule: an NL condition plus its rising-edge state. The compiled code lives on the\n * tracker's {@link Job}, not here. `affectedEntryIds` are the entries the rule watches; a verdict's\n * `members` are the ones that matched.\n */\nexport type Tracker = {\n id: string;\n name: string;\n /** Natural-language condition (LLM-readable); the compiled code lives on the tracker's job. */\n rule: string;\n /** Ids of every entry the rule reads — the per-entry lookup key ({@link EventsState.trackersForEntry}). */\n affectedEntryIds: readonly string[];\n enabled: boolean;\n /** The one bit of memory: was the verdict active at the last recompute (rising-edge detection). */\n wasActive: boolean;\n /** Rising-edge time of the current open episode, if active. */\n openedAt?: number;\n};\n\n/** Events emitted by {@link EventsState}. */\nexport type TrackerStateEvents = {\n /** The tracker set or a tracker's live state changed (create / clear / enable / edge crossed). */\n 'trackers-change': readonly Tracker[];\n /** A tracker crossed an edge — the freshly-logged event (drives toasts + feed append). */\n 'tracker-event': TrackerEvent;\n};\n\n/** Max retained events PER tracker — its oldest is evicted past this. One noisy tracker can't starve\n * another's history out of the shared log. */\nexport const MAX_TRACKER_EVENTS = 200;\n\n/**\n * Generic tracker store + rising-edge reducer. Held on `ToolState` as `state.trackers`. Pure\n * storage + reaction: tools register/unregister trackers and register their rule jobs on the\n * {@link JobEngine}; each job's `run` feeds verdicts here via {@link ingestVerdict}. Keeps the\n * {@link Job} handle backing each tracker (`_jobs`) so pausing a tracker pauses its job.\n *\n * @group Agent Toolkit\n */\nexport class EventsState implements StateSlice {\n private readonly _trackers = new Map<string, Tracker>();\n private readonly _log: TrackerEvent[] = [];\n /** trackerId → the {@link Job} backing it. The reference linking tracker to engine. */\n private readonly _jobs = new Map<string, Job>();\n private _seq = 0;\n\n readonly events = new StateEvents<TrackerStateEvents>();\n\n /**\n * Register a tracker. The caller (createTracker) registers it with the rising-edge bit clear\n * (`wasActive: false`) and feeds the arm-time verdict via {@link ingestVerdict} AFTER this call, so a\n * condition already live at creation crosses false→true and fires one `opened` alert. Emits\n * `trackers-change`.\n */\n register(tracker: Tracker): void {\n this._trackers.set(tracker.id, tracker);\n this.emitTrackers();\n }\n\n /** Store the {@link Job} backing a tracker — the reference linking it to the engine. */\n setJob(trackerId: string, job: Job): void {\n this._jobs.set(trackerId, job);\n }\n\n /** Remove a tracker and tear down its job. */\n unregister(trackerId: string): void {\n if (!this._trackers.has(trackerId)) return;\n this._jobs.get(trackerId)?.unregister();\n this._jobs.delete(trackerId);\n this._trackers.delete(trackerId);\n this.emitTrackers();\n }\n\n /** Pause/resume a tracker AND its job (a paused job never recomputes, so never crosses edges). */\n setEnabled(trackerId: string, enabled: boolean): boolean {\n const tracker = this._trackers.get(trackerId);\n if (!tracker) return false;\n tracker.enabled = enabled;\n this._jobs.get(trackerId)?.setActive(enabled);\n this.emitTrackers();\n return true;\n }\n\n /** Live trackers, as copies so external mutation can't corrupt the store. */\n get trackers(): readonly Tracker[] {\n return [...this._trackers.values()].map((t) => ({ ...t }));\n }\n\n get(trackerId: string): Tracker | undefined {\n const t = this._trackers.get(trackerId);\n return t && { ...t };\n }\n\n /**\n * Trackers whose rule reads `entryId` — e.g. every tracker watching a given incidents area. Drives\n * \"show all trackers for this area\" in the UI.\n */\n trackersForEntry(entryId: string): readonly Tracker[] {\n return this.trackers.filter((t) => t.affectedEntryIds.includes(entryId));\n }\n\n /**\n * The event log, newest last. Pass `sinceMs` to filter and `trackerId` to scope. Returns deep copies\n * so a caller can't mutate a logged event (or its `members`) and corrupt the store.\n */\n log(opts: { trackerId?: string; sinceMs?: number } = {}): readonly TrackerEvent[] {\n return this._log\n .filter(\n (e) =>\n (opts.trackerId === undefined || e.trackerId === opts.trackerId) &&\n (opts.sinceMs === undefined || e.at >= opts.sinceMs),\n )\n .map((e) => structuredClone(e));\n }\n\n /** Clear all trackers + log + job handles. Walked automatically by `destroyState`. */\n reset(): void {\n for (const job of this._jobs.values()) job.unregister();\n this._jobs.clear();\n this._trackers.clear();\n this._log.length = 0;\n this._seq = 0;\n }\n\n /**\n * Feed a freshly-computed verdict through the rising-edge reducer — called by the tracker's job `run`\n * on each recompute (and once at arm time). Bails fast unless the tracker is live + enabled. A verdict\n * flipping false→true logs `opened` (alert); true→false logs `resolved` (event). No change on\n * active→active / inactive→inactive. `at` is the source-data sample time of the recompute.\n */\n ingestVerdict(trackerId: string, verdict: Verdict, at: number): void {\n const tracker = this._trackers.get(trackerId);\n if (!tracker?.enabled) return;\n // Public method — never let a malformed verdict (a misbehaving rule that slipped the gate) crash\n // the reducer on `verdict.members.map`.\n if (!isVerdict(verdict)) return;\n\n if (verdict.active && !tracker.wasActive) {\n tracker.wasActive = true;\n tracker.openedAt = at;\n this.append(tracker, { type: 'alert', kind: 'opened', verdict, at });\n } else if (!verdict.active && tracker.wasActive) {\n const openedAt = tracker.openedAt;\n tracker.wasActive = false;\n tracker.openedAt = undefined;\n this.append(tracker, { type: 'event', kind: 'resolved', verdict, at, openedAt });\n }\n }\n\n private append(\n tracker: Tracker,\n e: { type: TrackerEventType; kind: TrackerEventStatus; verdict: Verdict; at: number; openedAt?: number },\n ): void {\n const event: TrackerEvent = {\n id: `${tracker.id}:${e.kind}:${this._seq++}`,\n at: e.at,\n trackerId: tracker.id,\n trackerName: tracker.name,\n type: e.type,\n kind: e.kind,\n summary: e.verdict.summary,\n members: e.verdict.members.map((m) => ({ entryId: m.entryId, featureIds: [...m.featureIds] })),\n ...(e.openedAt !== undefined && { openedAt: e.openedAt }),\n };\n this._log.push(event);\n // Cap per tracker, not globally: evict THIS tracker's oldest event once it exceeds the cap. The\n // log is append-ordered, so the first matching entry is the oldest. At most one over-cap per push.\n const own = this._log.filter((ev) => ev.trackerId === tracker.id);\n if (own.length > MAX_TRACKER_EVENTS) this._log.splice(this._log.indexOf(own[0]), 1);\n this.events.emit('tracker-event', event);\n this.emitTrackers();\n }\n\n private emitTrackers(): void {\n this.events.emit('trackers-change', this.trackers);\n }\n}\n\n/**\n * Structural guard for a {@link Verdict} — the shape a tracker rule's sandbox `code` must return. The\n * arm-time gate and the job runner trust it before {@link EventsState.ingestVerdict}; ingest re-checks\n * defensively since it's public.\n */\nexport function isVerdict(data: unknown): data is Verdict {\n if (typeof data !== 'object' || data === null) return false;\n const verdictCandidate = data as Record<string, unknown>;\n return (\n typeof verdictCandidate.active === 'boolean' &&\n typeof verdictCandidate.summary === 'string' &&\n Array.isArray(verdictCandidate.members)\n );\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport type { TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport {\n type TomTomMap,\n TrafficAreaAnalyticsModule,\n type TrafficAreaAnalyticsModuleConfig,\n} from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode, ShownEntriesSlice } from '../state';\nimport type { TrafficAreaAnalyticsEntry, TrafficAreaAnalyticsParams } from './entry';\n\n/**\n * Events fired by {@link TrafficAreaAnalyticsState}. Subscribe via\n * `state.trafficAreaAnalytics.events.on(type, handler)`.\n *\n * @group Agent Toolkit\n */\nexport type TrafficAreaAnalyticsStateEvents = {\n /**\n * History changed — entry added, removed, or cleared via `reset()`. `entries` is the full\n * snapshot after the change; `changedIds` lists the ids of the entries this specific change\n * added, replaced in place, or removed.\n */\n 'entries-change': { entries: readonly TrafficAreaAnalyticsEntry[]; changedIds: readonly string[] };\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<string>;\n /** Per-entry viz config changed (mode / metric / scaleMode / …). */\n 'config-change': { entryId: string; config: TrafficAreaAnalyticsModuleConfig | undefined };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n};\n\n/**\n * Append-only history of traffic-area-analytics aggregations, with per-entry\n * rendering. Each entry owns a dedicated {@link TrafficAreaAnalyticsModule},\n * lazy-initialised on first show. Mirrors the per-entry pattern used by\n * `RoutingState` / `CustomGeometriesState`.\n *\n * Live tile overlays (flow + incidents) live in {@link TrafficTilesState}.\n *\n * @group Agent Toolkit\n */\nexport class TrafficAreaAnalyticsState implements ShownEntriesSlice, StateSlice {\n private _entries: TrafficAreaAnalyticsEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n\n /** Subscribe to state changes — see {@link TrafficAreaAnalyticsStateEvents}. */\n readonly events = new StateEvents<TrafficAreaAnalyticsStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly TrafficAreaAnalyticsEntry[] {\n return this._entries;\n }\n\n get latestEntry(): TrafficAreaAnalyticsEntry | undefined {\n return this._entries.at(-1);\n }\n\n get shownEntryIds(): ReadonlySet<string> {\n const out = new Set<string>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this.hideEntry(entry.id));\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n }\n this.events.emit('mode-change', mode);\n }\n\n findById(entryId: string): TrafficAreaAnalyticsEntry | undefined {\n return this._entries.find((e) => e.id === entryId);\n }\n\n /**\n * Append a new entry. Auto-suffixes the id on collision. Under `single` mode the new entry\n * replaces every older one (hidden + dropped from history).\n */\n async addEntry(\n data: TrafficAreaAnalytics,\n label: string,\n params: TrafficAreaAnalyticsParams,\n explicitId?: string,\n ): Promise<string> {\n const droppedIds = this._entryMode === 'single' ? this._entries.map((entry) => entry.id) : [];\n if (this._entryMode === 'single' && this._entries.length > 0) {\n await hideAllEntries(this._entries, (entry) => this.hideEntry(entry.id));\n this._entries = [];\n }\n // Dedupe the length-based fallback too — after removals it can collide with a surviving entry.\n const fallback = `tta-${this._entries.length}`;\n const entryId = pickUniqueEntryId(\n explicitId ?? fallback,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({\n id: entryId,\n timestamp: Date.now(),\n label,\n data,\n params,\n });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [...droppedIds, entryId] });\n return entryId;\n }\n\n /**\n * Lazy-init the entry's TrafficAreaAnalyticsModule, wiring per-entry config-change events.\n * Throws on unknown id.\n */\n async getEntryModule(entryId: string): Promise<TrafficAreaAnalyticsModule> {\n const entry = this._requireEntry(entryId);\n if (!entry._module) {\n entry._module = await TrafficAreaAnalyticsModule.create(this._ttMap);\n entry._configChangeUnsub = entry._module.events.on('config-change', (config) => {\n entry._config = config;\n this.events.emit('config-change', { entryId, config });\n });\n }\n return entry._module;\n }\n\n /** Render this entry's analytics on its own module. Under `single` mode, hides every other shown entry first. */\n async showEntry(entryId: string): Promise<void> {\n const entry = this._requireEntry(entryId);\n if (this._entryMode === 'single') {\n const others = this._entries.filter((e) => e._shown && e.id !== entryId);\n for (const other of others) await this.hideEntry(other.id);\n }\n const module = await this.getEntryModule(entryId);\n await module.clear();\n await module.show(entry.data);\n const wasShown = entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Hide the entry's analytics on its module. No-op when unknown or already hidden. */\n async hideEntry(entryId: string): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n await entry._module?.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /** Drop a single entry from history. Hides it first, then removes; emits `entries-change`. No-op on unknown id. */\n async removeEntry(entryId: string): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n await this.hideEntry(entryId);\n const entry = this._entries[idx];\n entry._configChangeUnsub?.();\n entry._configChangeUnsub = undefined;\n entry._module = undefined;\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n }\n\n private _requireEntry(entryId: string): TrafficAreaAnalyticsEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`TrafficAreaAnalyticsState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n /**\n * Hide every traffic-area-analytics entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n const clearedIds = this._entries.map((entry) => entry.id);\n for (const entry of this._entries) {\n entry._configChangeUnsub?.();\n entry._module?.clear();\n }\n this._entries = [];\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n}\n","/**\n * Pure DBSCAN clustering logic for traffic incidents.\n *\n * Takes a set of {@link TrafficIncident} features, clusters them spatially\n * using `turf.clustersDbscan`, and returns ranked {@link ClusterResult}\n * groups with stable IDs and multi-poll trend detection. Structured-only —\n * consumers compose any display prose from these fields.\n *\n * No tool-layer or state-layer concerns — just the algorithm.\n *\n * @module agent-toolkit-state\n */\n\nimport type { TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport type { Feature, Point } from 'geojson';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Direction a cluster's total delay is trending across recent snapshots. */\nexport type ClusterTrend = 'growing' | 'fading' | 'steady' | 'new';\n\n/** Tuneable parameters for {@link runClustering}. All optional with sensible defaults. */\nexport type ClusteringParams = {\n /** DBSCAN neighbourhood radius in km. Default: 0.5 */\n eps?: number;\n /** Minimum members to form a cluster. Default: 3 */\n minMembers?: number;\n /** Maximum clusters to return (top-N by total delay). Default: 6 */\n maxClusters?: number;\n /** Pre-filter rules applied before clustering. */\n preFilter?: {\n /** Categories to exclude entirely. Default: `['roadworks', 'road-closed']` */\n excludeCategories?: string[];\n /** Incidents with `delayInSeconds` below this are excluded (within excludeCategories). Default: 1 (i.e. exclude 0-delay) */\n minDelaySeconds?: number;\n };\n};\n\n/** A single cluster produced by {@link runClustering}. */\nexport type ClusterResult = {\n /** Stable identifier — reused across runs when member overlap >= 50%. */\n id: string;\n /** Geographic centre of the cluster `[lng, lat]`. */\n centroid: [number, number];\n /** Incident IDs belonging to this cluster. */\n memberIds: string[];\n /** Number of member incidents. */\n size: number;\n /** Sum of `delayInSeconds` across all members. */\n totalDelaySeconds: number;\n /** Maximum `delayInSeconds` among all members. */\n peakDelaySeconds: number;\n /** Diagonal of the cluster's bounding box in km. */\n diameterKm: number;\n /** Most-frequently referenced road numbers across members. */\n primaryRoads: string[];\n /** Most-frequent incident category across members (e.g. \"jam\"). */\n primaryCategory: string;\n /** Trend across recent snapshots — see {@link classifyTrend}. */\n trend: ClusterTrend;\n /**\n * Rolling window of `totalDelaySeconds` across recent matched snapshots\n * (bounded to {@link TREND_WINDOW}). Internal trend state threaded forward\n * via the previous run; not display data.\n */\n delaySamples: number[];\n};\n\n/** Output of {@link runClustering}. */\nexport type ClusteringOutput = { groups: ClusterResult[] };\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_EPS = 0.5;\nconst DEFAULT_MIN_MEMBERS = 3;\nconst DEFAULT_MAX_CLUSTERS = 6;\nconst DEFAULT_EXCLUDE_CATEGORIES = ['roadworks', 'road-closed'];\nconst DEFAULT_MIN_DELAY_SECONDS = 1;\nconst OVERLAP_THRESHOLD = 0.5;\n/** Snapshots retained in a cluster's trend window — what {@link classifyTrend} reads. */\nconst TREND_WINDOW = 3;\n/** Net change over the window required to call a trend, not call it noise. */\nconst TREND_NET_THRESHOLD = 0.1;\n\n/**\n * Pre-filter incidents: exclude those matching `excludeCategories` whose\n * delay is below `minDelaySeconds`. When `minDelaySeconds` is 0, delay\n * filtering is skipped entirely.\n */\nfunction preFilter(\n incidents: TrafficIncident[],\n excludeCategories: string[],\n minDelaySeconds: number,\n): TrafficIncident[] {\n if (minDelaySeconds === 0) return incidents;\n const excluded = new Set(excludeCategories);\n return incidents.filter((inc) => {\n const cat = inc.properties.category;\n const delay = inc.properties.delayInSeconds ?? 0;\n // Only exclude incidents whose category is in the exclude set AND delay is below threshold\n if (excluded.has(cat) && delay < minDelaySeconds) return false;\n return true;\n });\n}\n\n/**\n * Convert any incident (Point or LineString) to a Point feature carrying the\n * original incident's `properties.id` in a `_incidentId` property.\n */\nfunction toPoint(incident: TrafficIncident): Feature<Point, { _incidentId: string }> {\n const id = incident.properties.id;\n if (incident.geometry.type === 'Point') {\n return turf.point(incident.geometry.coordinates, { _incidentId: id });\n }\n // LineString → centroid\n const center = turf.centroid(incident);\n center.properties = { _incidentId: id };\n return center as Feature<Point, { _incidentId: string }>;\n}\n\n/**\n * Collect road numbers from member incidents, sorted by frequency (desc),\n * deduplicated.\n */\nfunction collectPrimaryRoads(members: TrafficIncident[]): string[] {\n const freq = new Map<string, number>();\n for (const inc of members) {\n for (const road of inc.properties.roadNumbers ?? []) {\n freq.set(road, (freq.get(road) ?? 0) + 1);\n }\n }\n return [...freq.entries()].sort((a, b) => b[1] - a[1]).map(([road]) => road);\n}\n\n/**\n * Collect the most-frequent incident category across member incidents;\n * \"incident\" when none present.\n */\nfunction collectPrimaryCategory(members: TrafficIncident[]): string {\n const freq = new Map<string, number>();\n for (const inc of members) {\n const cat = inc.properties.category;\n if (cat) freq.set(cat, (freq.get(cat) ?? 0) + 1);\n }\n return [...freq.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 'incident';\n}\n\n// ---------------------------------------------------------------------------\n// ID stability — Jaccard-index matching\n// ---------------------------------------------------------------------------\n\n/**\n * Match fresh clusters to previous clusters using Jaccard similarity of\n * member IDs. Returns a map from fresh-cluster index to the previous\n * cluster's `id`. Uses a greedy best-overlap-first, 1:1 assignment.\n */\nexport function matchPreviousClusters(\n fresh: Pick<ClusterResult, 'memberIds'>[],\n previous: Pick<ClusterResult, 'id' | 'memberIds'>[],\n): Map<number, string> {\n // Build all (freshIdx, prevIdx, overlap) pairs. Hoist the previous-side Sets\n // so each is built once, not once per fresh cluster (O(F+P) builds, not O(F*P)).\n const prevSets = previous.map((p) => new Set(p.memberIds));\n const pairs: { freshIdx: number; prevIdx: number; overlap: number }[] = [];\n for (let fi = 0; fi < fresh.length; fi++) {\n const freshSet = new Set(fresh[fi].memberIds);\n for (let pi = 0; pi < previous.length; pi++) {\n const prevSet = prevSets[pi];\n let intersection = 0;\n for (const id of freshSet) {\n if (prevSet.has(id)) intersection++;\n }\n const union = freshSet.size + prevSet.size - intersection;\n const jaccard = union === 0 ? 0 : intersection / union;\n if (jaccard >= OVERLAP_THRESHOLD) {\n pairs.push({ freshIdx: fi, prevIdx: pi, overlap: jaccard });\n }\n }\n }\n\n // Sort by overlap descending for greedy assignment\n pairs.sort((a, b) => b.overlap - a.overlap);\n\n const result = new Map<number, string>();\n const usedFresh = new Set<number>();\n const usedPrev = new Set<number>();\n for (const { freshIdx, prevIdx, overlap: _ } of pairs) {\n if (usedFresh.has(freshIdx) || usedPrev.has(prevIdx)) continue;\n result.set(freshIdx, previous[prevIdx].id);\n usedFresh.add(freshIdx);\n usedPrev.add(prevIdx);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Trend detection\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a cluster's trend from its rolling delay window with hysteresis:\n * two consecutive moves in the same direction AND a net change past\n * {@link TREND_NET_THRESHOLD}. This rides out per-tick jitter that single-step\n * comparison would flicker on. Needs ≥3 samples; below that, \"steady\".\n */\nexport function classifyTrend(samples: number[]): ClusterTrend {\n if (samples.length < 3) return 'steady';\n const [a, b, c] = samples.slice(-3);\n const net = (c - a) / Math.max(a, 1);\n if (b > a && c > b && net > TREND_NET_THRESHOLD) return 'growing';\n if (b < a && c < b && net < -TREND_NET_THRESHOLD) return 'fading';\n return 'steady';\n}\n\n// ---------------------------------------------------------------------------\n// Main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Cluster traffic incidents using DBSCAN and return ranked, annotated groups.\n *\n * @param incidents - The incidents to cluster.\n * @param params - Tuneable parameters (eps, minMembers, maxClusters, preFilter).\n * @param previous - Previous clustering output's `groups` for ID stability\n * and trend detection. Pass `undefined` on first run.\n * @param sampledAt - Canonical moment the `incidents` snapshot is from. Used to\n * key the trend window per moment, not per call.\n * @param previousSampledAt - Moment `previous` was sampled. When `sampledAt` does\n * not advance past it, the latest trend sample is overwritten\n * rather than appended (so a same-snapshot re-run can't\n * double-count). Omit both to always append (e.g. in tests).\n */\nexport function runClustering(\n incidents: TrafficIncident[],\n params: ClusteringParams,\n previous?: ClusterResult[],\n sampledAt?: number,\n previousSampledAt?: number,\n): ClusteringOutput {\n if (incidents.length === 0) return { groups: [] };\n\n // --- 1. Pre-filter ---\n const excludeCategories = params.preFilter?.excludeCategories ?? DEFAULT_EXCLUDE_CATEGORIES;\n const minDelaySeconds = params.preFilter?.minDelaySeconds ?? DEFAULT_MIN_DELAY_SECONDS;\n const filtered = preFilter(incidents, excludeCategories, minDelaySeconds);\n if (filtered.length === 0) return { groups: [] };\n\n // --- 2. Point conversion ---\n const incidentById = new Map<string, TrafficIncident>();\n for (const inc of filtered) incidentById.set(inc.properties.id, inc);\n\n const points = filtered.map(toPoint);\n const pointsFC = turf.featureCollection(points);\n\n // --- 3. DBSCAN ---\n const eps = params.eps ?? DEFAULT_EPS;\n const minMembers = params.minMembers ?? DEFAULT_MIN_MEMBERS;\n const clustered = turf.clustersDbscan(pointsFC, eps, { minPoints: minMembers });\n\n // --- 4. Extract clusters ---\n // Group points by their dbscan cluster number (noise points have cluster = undefined or -1)\n const clusterMap = new Map<number, Feature<Point, { _incidentId: string }>[]>();\n for (const feat of clustered.features) {\n const clusterNum = (feat.properties as { cluster?: number }).cluster;\n if (clusterNum === undefined || clusterNum < 0) continue;\n let group = clusterMap.get(clusterNum);\n if (!group) {\n group = [];\n clusterMap.set(clusterNum, group);\n }\n group.push(feat as Feature<Point, { _incidentId: string }>);\n }\n\n const maxClusters = params.maxClusters ?? DEFAULT_MAX_CLUSTERS;\n\n const rawClusters: Omit<ClusterResult, 'id' | 'trend' | 'delaySamples'>[] = [];\n for (const [, groupPoints] of clusterMap) {\n const memberIds = groupPoints.map((p) => p.properties._incidentId);\n const members = memberIds\n .map((id) => incidentById.get(id))\n .filter((inc): inc is TrafficIncident => inc !== undefined);\n\n const totalDelaySeconds = members.reduce((sum, inc) => sum + (inc.properties.delayInSeconds ?? 0), 0);\n const peakDelaySeconds = members.reduce((max, inc) => Math.max(max, inc.properties.delayInSeconds ?? 0), 0);\n\n const groupFC = turf.featureCollection(groupPoints);\n const centroidFeature = turf.centroid(groupFC);\n const centroid = centroidFeature.geometry.coordinates as [number, number];\n\n let diameterKm = 0;\n if (groupPoints.length >= 2) {\n const bbox = turf.bbox(groupFC);\n // bbox = [minLng, minLat, maxLng, maxLat]\n diameterKm = turf.distance([bbox[0], bbox[1]], [bbox[2], bbox[3]]);\n }\n\n const primaryRoads = collectPrimaryRoads(members);\n\n rawClusters.push({\n centroid,\n memberIds,\n size: members.length,\n totalDelaySeconds,\n peakDelaySeconds,\n diameterKm,\n primaryRoads,\n primaryCategory: collectPrimaryCategory(members),\n });\n }\n\n // --- 5. Sort by totalDelaySeconds desc, take top maxClusters ---\n rawClusters.sort((a, b) => b.totalDelaySeconds - a.totalDelaySeconds);\n const topClusters = rawClusters.slice(0, maxClusters);\n\n // --- 6. ID stability ---\n const idMapping = previous\n ? matchPreviousClusters(topClusters as ClusterResult[], previous)\n : new Map<number, string>();\n\n // Build a map from previous id → previous ClusterResult for trend detection\n const prevById = new Map<string, ClusterResult>();\n if (previous) {\n for (const p of previous) prevById.set(p.id, p);\n }\n\n // A genuinely new tick extends the window; a same-moment re-run (ID-stability\n // re-read of an unchanged snapshot) overwrites the latest sample instead.\n const advancing = sampledAt === undefined || previousSampledAt === undefined || sampledAt > previousSampledAt;\n\n // --- 7. Assemble final ClusterResults, threading the trend window forward ---\n const groups: ClusterResult[] = topClusters.map((raw, idx) => {\n const matchedId = idMapping.get(idx);\n // Disjoint DBSCAN membership makes the lowest member id a stable\n // per-cluster disambiguator, so two pockets sharing a rounded centroid\n // can't collide on the fallback id.\n const id =\n matchedId ??\n `cluster-${raw.centroid[0].toFixed(3)}-${raw.centroid[1].toFixed(3)}-${[...raw.memberIds].sort()[0] ?? idx}`;\n const prev = matchedId ? prevById.get(matchedId) : undefined;\n\n // A matched cluster threads its rolling delay window forward; a fresh one starts new.\n const priorSamples = prev?.delaySamples ?? [];\n const delaySamples = prev\n ? (advancing\n ? [...priorSamples, raw.totalDelaySeconds]\n : [...priorSamples.slice(0, -1), raw.totalDelaySeconds]\n ).slice(-TREND_WINDOW)\n : [raw.totalDelaySeconds];\n // Key the label on sample count, not on whether a prior cluster matched:\n // a single sample (first sighting, or a re-read collapsed back to one) is\n // \"new\"; two or more is enough for classifyTrend to weigh in.\n const trend: ClusterTrend = delaySamples.length === 1 ? 'new' : classifyTrend(delaySamples);\n\n return {\n id,\n centroid: raw.centroid,\n memberIds: raw.memberIds,\n size: raw.size,\n totalDelaySeconds: raw.totalDelaySeconds,\n peakDelaySeconds: raw.peakDelaySeconds,\n diameterKm: raw.diameterKm,\n primaryRoads: raw.primaryRoads,\n primaryCategory: raw.primaryCategory,\n trend,\n delaySamples,\n };\n });\n\n return { groups };\n}\n\n// ---------------------------------------------------------------------------\n// Sandbox primitive\n// ---------------------------------------------------------------------------\n\n/**\n * {@link runClustering} output plus the moment it was computed. The extra `sampledAt` lets the sandbox\n * `cluster()` primitive thread the trend window forward without the caller juggling timestamps: it is\n * stamped onto the result and read back off `previous` on the next call.\n *\n * @group Agent Toolkit\n */\nexport type SandboxClusteringOutput = ClusteringOutput & { sampledAt?: number };\n\n/**\n * The `cluster(...)` primitive injected into `analyseData` / `processData` sandbox code — DBSCAN\n * incident clustering with stable IDs + trend detection, exposed as a callable so dynamic code can\n * filter/combine incidents across entries however it likes and then cluster the result in one pass.\n *\n * Ergonomic shape for generated code: `cluster(incidents, params?, previous?, now?)`.\n * - `incidents` — the (already-filtered) incident features to cluster.\n * - `params` — {@link ClusteringParams} (eps / minMembers / maxClusters / preFilter); all optional.\n * - `previous` — last call's return value, for stable IDs + trend continuity. In a monitored analysis\n * pass the injected `previous`; omit for a one-shot.\n * - `now` — the injected `now` (a `Date`) or epoch ms. Used only to key the trend window per moment;\n * omit for a one-shot (every call then counts as a fresh sample).\n *\n * Trend threading is self-contained: the moment is stamped onto the output as `sampledAt`, so passing\n * the prior output back as `previous` is all that's needed — the primitive reads `previous.sampledAt`\n * as the prior moment internally. Pure (turf + local code only), so it bundles into the iframe-worker\n * sandbox realm exactly like `routeUtils` — see `tools/shared/sandbox/sdk-utils-worker-entry.ts`.\n *\n * @group Agent Toolkit\n */\n// Normalize the optional `now` (epoch ms or a Date) to epoch ms, or undefined when unset.\nconst toSampledAt = (now?: number | Date): number | undefined => {\n if (now == null) return undefined;\n return now instanceof Date ? now.getTime() : now;\n};\n\nexport const clusterIncidents = (\n incidents: TrafficIncident[],\n params: ClusteringParams = {},\n previous?: SandboxClusteringOutput,\n now?: number | Date,\n): SandboxClusteringOutput => {\n const sampledAt = toSampledAt(now);\n const { groups } = runClustering(incidents, params, previous?.groups, sampledAt, previous?.sampledAt);\n return sampledAt === undefined ? { groups } : { groups, sampledAt };\n};\n\n/**\n * Guard that a value is a {@link SandboxClusteringOutput} (a `{ groups: [...] }` shape). The clustering\n * tool runs LLM-authored `code`, so its result is arbitrary JSON until proven otherwise — this is the\n * boundary that keeps the typed clusters store and the cluster-pin UI sound. Mirrors the consumer-side\n * narrowing the example's `clustersFromAnalysis` does.\n *\n * @group Agent Toolkit\n */\nexport const isClusteringOutput = (value: unknown): value is SandboxClusteringOutput =>\n !!value && typeof value === 'object' && Array.isArray((value as { groups?: unknown }).groups);\n","import type { BBox, TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport { StateEvents } from '../../events';\nimport type { IncidentSnapshot, MonitoredArea, PollingStatus } from './types';\n\n/** Default interval between polling ticks (ms). Customizable via {@link IncidentMonitor.start}. */\nexport const INCIDENT_MONITOR_INTERVAL_MS = 60_000;\n\n/**\n * Dependency injection for an {@link IncidentMonitor}. Production code wires\n * `fetchIncidents` to `trafficIncidentDetails`; tests can pass a deterministic fake\n * and override the timer.\n *\n * The fetcher returns just the incidents — the monitor stamps `takenAt` itself\n * so every consumer of one tick (entry data, derived analyses, sandbox `now`)\n * agrees on the same canonical moment.\n *\n * @group Agent Toolkit\n */\nexport type IncidentMonitorDeps = {\n fetchIncidents: (bbox: BBox) => Promise<TrafficIncident[]>;\n /** Override for tests. Defaults to the global `setInterval`. */\n setInterval?: (fn: () => void, ms: number) => unknown;\n clearInterval?: (handle: unknown) => void;\n};\n\n/**\n * Events fired by {@link IncidentMonitor}. The slice subscribes once and re-emits\n * translated copies (`monitor-tick` / `monitor-error`) keyed by entry id.\n */\nexport type IncidentMonitorEvents = {\n /** A poll succeeded. Payload: the fresh snapshot. */\n tick: IncidentSnapshot;\n /** A poll failed — the monitor has cleared its timer and moved to `stopped-error`. */\n error: string;\n};\n\n/**\n * One-shot polling loop scoped to a single bbox. Owns its timer, status, error,\n * and captured area. Re-starting after `stop()` is allowed and resets all state.\n *\n * The monitor never knows about the entry that owns it — it surfaces ticks/errors\n * through `events`, and the caller decides what to do with the snapshot.\n *\n * @group Agent Toolkit\n */\nexport class IncidentMonitor {\n readonly events = new StateEvents<IncidentMonitorEvents>();\n\n private _status: PollingStatus = 'idle';\n private _area?: MonitoredArea;\n private _error?: string;\n private _intervalHandle?: unknown;\n private _clearInterval?: (handle: unknown) => void;\n private _fetchIncidents?: IncidentMonitorDeps['fetchIncidents'];\n\n constructor(private readonly _bbox: BBox) {}\n\n get status(): PollingStatus {\n return this._status;\n }\n\n get monitoredArea(): MonitoredArea | undefined {\n return this._area;\n }\n\n get error(): string | undefined {\n return this._error;\n }\n\n /**\n * True when the monitor is *currently* polling. Distinct from {@link isActive},\n * which also covers `stopped-error` — a state where the timer is gone but the\n * monitor still carries an unacknowledged error. Use this for \"is the entry\n * being polled right now\"; use `isActive` for \"is there anything to clean up\".\n */\n get isRunning(): boolean {\n return this._status === 'running';\n }\n\n /** True when the monitor has any polling activity (running or stopped-error). */\n get isActive(): boolean {\n return this._status !== 'idle';\n }\n\n /**\n * Begin polling. Captures `deps` for the lifetime of the run, fires the first tick\n * immediately (unless `skipInitialTick`), then schedules an interval. No-op when already running.\n *\n * `skipInitialTick` is for the caller that JUST fetched the data itself (e.g. getTrafficIncidents\n * arming monitoring on the entry it just loaded): the entry is already fresh, so an eager first\n * tick would be a redundant duplicate fetch. Standalone callers leave it false so the monitor\n * produces an initial snapshot without waiting a full interval.\n */\n start(\n area: MonitoredArea,\n deps: IncidentMonitorDeps,\n intervalMs: number = INCIDENT_MONITOR_INTERVAL_MS,\n skipInitialTick = false,\n ): void {\n if (this._status === 'running') return;\n this._status = 'running';\n this._error = undefined;\n this._area = area;\n // Deps are NOT sticky across stop/start cycles — every restart (incl. recovery\n // from `stopped-error`) reads fresh `fetchIncidents` / timer overrides from the\n // caller. Tests that swap fakes between cycles rely on this.\n this._fetchIncidents = deps.fetchIncidents;\n this._clearInterval =\n deps.clearInterval ?? ((handle: unknown) => clearInterval(handle as ReturnType<typeof setInterval>));\n const setIntervalFn = deps.setInterval ?? setInterval;\n // Eager first tick — consumers shouldn't wait an interval for the initial snapshot, unless\n // the caller already has fresh data and only wants the recurring refresh.\n if (!skipInitialTick) void this._tick();\n this._intervalHandle = setIntervalFn(() => void this._tick(), intervalMs);\n }\n\n /** Stop polling and reset state. No-op when idle. */\n stop(): void {\n if (this._status === 'idle') return;\n this._releaseTimer();\n this._status = 'idle';\n this._error = undefined;\n this._area = undefined;\n this._fetchIncidents = undefined;\n }\n\n // _tick is the canonical timestamp authority for one polling cycle. It stamps\n // `takenAt` after the fetch resolves so every downstream consumer (entry data,\n // derived analyses, sandbox `now`) shares one byte-equal moment.\n private async _tick(): Promise<void> {\n const fetchIncidents = this._fetchIncidents;\n if (!fetchIncidents) return;\n try {\n const incidents = await fetchIncidents(this._bbox);\n const snap: IncidentSnapshot = { takenAt: Date.now(), incidents };\n this.events.emit('tick', snap);\n } catch (err) {\n this._failed(err instanceof Error ? err.message : String(err));\n }\n }\n\n private _failed(message: string): void {\n this._releaseTimer();\n this._error = message;\n this._status = 'stopped-error';\n this._fetchIncidents = undefined;\n this.events.emit('error', message);\n }\n\n private _releaseTimer(): void {\n if (this._intervalHandle != null) {\n this._clearInterval?.(this._intervalHandle);\n this._intervalHandle = undefined;\n }\n this._clearInterval = undefined;\n }\n}\n","/**\n * @module agent-toolkit-tools\n *\n * The one way a tool moves the camera onto what it just produced. Deliberately absent from\n * `tools/shared/index.ts`: that barrel is re-exported wholesale from the package root, so a symbol\n * added to it joins the published surface.\n */\n\nimport type { BBox, HasBBox } from '@tomtom-org/maps-sdk/core';\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport type { FitBoundsOptions, LngLatBoundsLike, Map } from 'maplibre-gl';\n\n/** The padding, in pixels, the camera leaves around bounds it is fit to. @ignore */\nexport const DEFAULT_MAP_FIT_PADDING = 50;\n\n/**\n * Fits the camera to the given bounds, padded by {@link DEFAULT_MAP_FIT_PADDING} unless `options`\n * carries its own. A `[W, S, E, N]` tuple is a MapLibre `LngLatBoundsLike`, so the cast lives here\n * rather than at every call site.\n *\n * @ignore\n */\nexport const fitMapToBBox = (map: Map, bbox: BBox, options?: FitBoundsOptions): void => {\n map.fitBounds(bbox as LngLatBoundsLike, { padding: DEFAULT_MAP_FIT_PADDING, ...options });\n};\n\n/**\n * Fits the camera to the bounds of the given GeoJSON, and reports whether there were bounds to fit\n * to — a caller that records \"the camera moved\" needs to know, and GeoJSON without computable\n * bounds leaves the camera where it was.\n *\n * @ignore\n */\nexport const fitMapToGeoJSON = (map: Map, geoJson: HasBBox, options?: FitBoundsOptions): boolean => {\n const bbox = bboxFromGeoJSON(geoJson);\n if (!bbox) return false;\n\n fitMapToBBox(map, bbox, options);\n return true;\n};\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type BBox, bboxFromGeoJSON, type TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport { type TomTomMap, TrafficIncidentDetailsModule } from '@tomtom-org/maps-sdk/map';\nimport type { TrafficIncidentDetailsByBBoxParams } from '@tomtom-org/maps-sdk/services';\nimport { fitMapToBBox } from '../../tools/shared/map-fit';\nimport type { StateSlice } from '../../types';\nimport type { BaseEntry } from '../entry';\nimport { collapseHistoryToLatest, hideAllEntries, pickUniqueEntryId } from '../entry-helpers';\nimport { StateEvents } from '../events';\nimport type { EntryMode } from '../state';\nimport type { SandboxClusteringOutput } from './clustering';\nimport { IncidentMonitor, type IncidentMonitorDeps } from './monitor/monitor';\nimport type { IncidentSnapshot, MonitoredArea } from './monitor/types';\n\nexport type { IncidentsAnalysis } from './analysis';\n\n/**\n * Dedicated per-entry clustering state. Incident clustering is NOT a generic analysis — it is typed,\n * first-class incidents state with its own home here. `output` is the latest {@link SandboxClusteringOutput}\n * (the DBSCAN groups + the moment it was computed); `recipe` is how to recompute it (re-used by\n * `tools/state/clusters-runtime.ts` to re-run on each monitor tick so clusters stay live). `recipe`\n * is opaque here (a tools-layer `ClusterRecipe`) — the slice never interprets it, keeping the state layer\n * tools-agnostic.\n *\n * @group Agent Toolkit\n */\nexport type ClustersRecord = { recipe: unknown; output: SandboxClusteringOutput };\n\n/**\n * A single traffic-incidents history entry: the captured query, its incidents, and the\n * per-entry rendering / monitoring state owned by {@link TrafficIncidentsState}. `analyseData` analyses\n * are NOT held here — they live in the session-level `state.analyses` registry. Clustering IS dedicated\n * incidents state, kept in the slice's `clusters` map keyed by entry id (see {@link ClustersRecord}).\n *\n * @group Agent Toolkit\n *\n * @ignore\n */\nexport type TrafficIncidentsEntry = BaseEntry<TrafficIncident[]> & {\n /**\n * The captured query that produced this entry. Replayed by the monitor on each tick so\n * polling stays in the same slice the entry was loaded with. Reuses the loader's own\n * params shape; the entry stores a resolved {@link BBox} tuple in `bbox` (the loader\n * normalises any GeoJSON value before handing the params off).\n */\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox };\n /**\n * Per-entry incident-details module. Each entry owns its own module so two areas can be\n * rendered on the map at the same time without sharing a single source. Lazy-initialised\n * via `TrafficIncidentsState.getEntryModule` — undefined until first show. Subscribe to\n * clicks via `module.events.on('click', ...)` once retrieved.\n */\n _module?: TrafficIncidentDetailsModule;\n /** True while this entry is currently rendered on the map. */\n _shown?: boolean;\n /**\n * Focused subset on this entry — the agent's `focusIds` write-side. Drives the dim/highlight\n * affordance on `_module` and feeds `focusedFeatures(entryId)` for stepping through.\n */\n _focus?: { ids: Set<string>; reason?: string };\n /**\n * Per-entry polling loop. Lazy-created on first {@link TrafficIncidentsState.startMonitoring}\n * call; reused across stop/start cycles. Owns timer, status, error, captured area.\n */\n _monitor?: IncidentMonitor;\n /**\n * Monotonic counter incremented on every {@link TrafficIncidentsState.replaceEntryData}\n * call. Used to detect out-of-order ticks: if a slow `module.show()` from an earlier\n * tick resolves after a newer tick has already overwritten `data`, the late path bails\n * before reapplying focus.\n */\n _tick?: number;\n};\n\n/**\n * Snapshot of an entry's focus subset. Returned by `getFocus` / payload on `focus-change`.\n *\n * @group Agent Toolkit\n */\nexport type IncidentFocus = { ids: Set<string>; reason?: string };\n\n/**\n * Events fired by {@link TrafficIncidentsState}. Subscribe via\n * `state.trafficIncidents.events.on(type, handler)` — returns an unsubscribe function.\n *\n * @group Agent Toolkit\n */\nexport type TrafficIncidentsStateEvents = {\n /**\n * History changed — entry added, replaced, removed, or cleared via `reset()`. `entries` is the\n * full snapshot after the change; `changedIds` lists the ids of the entries this specific change\n * added, replaced in place, or removed. The {@link JobEngine} and the clustering re-run\n * match these against the entries they depend on.\n */\n 'entries-change': { entries: readonly TrafficIncidentsEntry[]; changedIds: readonly string[] };\n /** Set of entries currently rendered on the map changed. */\n 'shown-change': ReadonlySet<TrafficIncidentsEntry['id']>;\n /** Focus subset for an entry changed (or cleared, when `focus === null`). */\n 'focus-change': { entryId: TrafficIncidentsEntry['id']; focus: IncidentFocus | null };\n /** Display policy switched between `single` and `multiple`. */\n 'mode-change': EntryMode;\n /** A monitored entry produced a fresh snapshot (per-tick). */\n 'monitor-tick': { entryId: TrafficIncidentsEntry['id']; snap: IncidentSnapshot };\n /** A monitored entry's poll failed — slice has cleared the timer. */\n 'monitor-error': { entryId: TrafficIncidentsEntry['id']; error: string };\n /**\n * Polling began on an entry (idle → running). Skipped when `startMonitoring`\n * was a no-op against an already-running monitor.\n */\n 'monitor-start': { entryId: TrafficIncidentsEntry['id'] };\n /**\n * Polling halted on an entry. `reason: 'manual'` covers explicit\n * `stopMonitoring` and entry teardown (remove / single-mode trim / reset).\n * `reason: 'error'` fires alongside `monitor-error` — error-only listeners\n * keep using `monitor-error`; lifecycle listeners can rely on this alone.\n */\n 'monitor-stop': { entryId: TrafficIncidentsEntry['id']; reason: 'manual' | 'error' };\n /**\n * An entry's clustering result changed — set, recomputed on a tick, or cleared (`clusters === null`).\n * UI that renders cluster pins re-derives off this (not `entries-change`): the clustering re-run is\n * async to the monitor tick, so the fresh result lands here a step later.\n */\n 'clusters-change': { entryId: TrafficIncidentsEntry['id']; clusters: SandboxClusteringOutput | null };\n};\n\n/**\n * State for traffic incidents: entry history and map rendering via per-entry\n * TrafficIncidentDetailsModule. Each entry can be a separate area; under the default\n * `multiple` mode several entries can render at once.\n *\n * Each entry owns its own module (lazy-init on first show) plus an optional per-entry\n * background monitor. Subscribe to changes via `events.on('...')`.\n *\n * @group Agent Toolkit\n */\nexport class TrafficIncidentsState implements StateSlice {\n private _entries: TrafficIncidentsEntry[] = [];\n private _entryMode: EntryMode = 'multiple';\n // Dedicated clustering state, keyed by entry id — clustering is typed first-class incidents state,\n // not a generic analysis. Written by `clusterIncidents` / its monitor-tick re-run; GC'd with the entry.\n private readonly _clusters = new Map<string, ClustersRecord>();\n\n /** Subscribe to state changes — see {@link TrafficIncidentsStateEvents}. */\n readonly events = new StateEvents<TrafficIncidentsStateEvents>();\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n get entries(): readonly TrafficIncidentsEntry[] {\n return this._entries;\n }\n\n /**\n * Display policy: `multiple` (default) keeps every history entry available to render\n * simultaneously; `single` enforces \"at most one entry on the map at a time\" and trims\n * the history down to the latest entry whenever it's set.\n */\n get entryMode(): EntryMode {\n return this._entryMode;\n }\n\n /**\n * Switch the display policy. When moving from `multiple` → `single` we hide every entry\n * older than the latest, drop them from history, and emit `entries-change`. No-op when the\n * mode hasn't changed.\n */\n async setEntryMode(mode: EntryMode): Promise<void> {\n if (this._entryMode === mode) return;\n this._entryMode = mode;\n if (mode === 'single' && this._entries.length > 1) {\n const droppedIds = this._entries.slice(0, -1).map((entry) => entry.id);\n this._entries = await collapseHistoryToLatest(this._entries, (entry) => this._teardownEntry(entry), {\n parallel: true,\n });\n this.events.emit('entries-change', { entries: this._entries, changedIds: droppedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n this.events.emit('mode-change', mode);\n }\n\n /** Set of entries currently rendered on the map. */\n get shownEntryIds(): ReadonlySet<TrafficIncidentsEntry['id']> {\n const out = new Set<TrafficIncidentsEntry['id']>();\n for (const entry of this._entries) if (entry._shown) out.add(entry.id);\n return out;\n }\n\n /**\n * Append a new entry to the history. Under `single` mode the new entry replaces every\n * older one (and tears their modules + monitors down). `sampledAt` is the moment the\n * data was captured — pass one canonical value per \"moment\" so future joins (entry\n * data ↔ derived analyses) are byte-equal. Returns the assigned id.\n */\n async addIncidentsEntry(\n data: TrafficIncident[],\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n label: string,\n sampledAt: number,\n explicitId?: string,\n ): Promise<string> {\n if (this._entryMode === 'single' && this._entries.length > 0) {\n // `_teardownEntry` tears down a per-entry monitor + module — each instance is\n // independent, so `parallel: true` is race-safe and faster than serial.\n await hideAllEntries(this._entries, (entry) => this._teardownEntry(entry), { parallel: true });\n this._entries = [];\n }\n // Always dedupe — `incidents-${length}` is not collision-free across removes\n // (e.g. add a, add b, remove a → length=1, fallback `incidents-1` collides with surviving `b`).\n const id = pickUniqueEntryId(\n explicitId ?? `incidents-${this._entries.length}`,\n this._entries.map((entry) => entry.id),\n );\n this._entries.push({ id, timestamp: sampledAt, label, data, params });\n this.events.emit('entries-change', { entries: this._entries, changedIds: [id] });\n return id;\n }\n\n /**\n * Replace an entry's data in place and repaint its module without moving the camera.\n * Used by the per-entry monitor tick so incident geometries and any open detail\n * panel stay in sync with the freshest snapshot. `sampledAt` is the canonical moment\n * for this update — propagated to the entry timestamp before the `entries-change` emit.\n * That emit is what drives analysis replay: the session-level {@link JobEngine} re-runs every\n * job bound to this entry off the change, ASYNCHRONOUSLY (a microtask later) — the slice itself\n * owns no analysis logic.\n * Returns true on success, false when the entry is unknown.\n */\n async replaceEntryData(\n entryId: TrafficIncidentsEntry['id'],\n data: TrafficIncident[],\n sampledAt: number,\n ): Promise<boolean> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return false;\n const tick = (entry._tick ?? 0) + 1;\n entry._tick = tick;\n entry.data = data;\n entry.timestamp = sampledAt;\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n if (entry._module) {\n await entry._module.show({ type: 'FeatureCollection', features: data });\n // A newer tick beat us across the await — its own pass will reconcile\n // focus and replay specs against fresher data, so bail.\n if (entry._tick !== tick) return true;\n if (entry._focus) {\n const present = new Set(data.map((f) => f.properties.id));\n const stillThere = [...entry._focus.ids].filter((id) => present.has(id));\n entry._module.setFocus(stillThere.length > 0 ? stillThere : null);\n if (stillThere.length === 0) {\n entry._focus = undefined;\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n }\n }\n return true;\n }\n\n /**\n * Lazy-init and return the entry's TrafficIncidentDetailsModule. Consumers subscribe to\n * clicks directly on the returned module via `module.events.on('click', ...)`;\n * Throws on unknown entry id.\n */\n async getEntryModule(entryId: TrafficIncidentsEntry['id']): Promise<TrafficIncidentDetailsModule> {\n const entry = this._requireEntry(entryId);\n entry._module ??= await TrafficIncidentDetailsModule.create(this._ttMap);\n return entry._module;\n }\n\n /**\n * Render an entry on the map. Lazy-inits its module, pushes the entry's incidents, and\n * (when `fitBounds` is true) fits the camera to the entry's bbox. Emits `shown-change`\n * once the entry transitions to shown.\n */\n async showEntry(entryId: TrafficIncidentsEntry['id'], fitBounds = true): Promise<void> {\n const entry = this._requireEntry(entryId);\n const module = await this.getEntryModule(entryId);\n await module.show({ type: 'FeatureCollection', features: entry.data });\n module.setVisible(true);\n if (fitBounds) {\n fitMapToBBox(this._ttMap.mapLibreMap, entry.params.bbox, { maxZoom: 14 });\n }\n const wasShown = !!entry._shown;\n entry._shown = true;\n if (!wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Hide an entry — clears its module + drops focus. The module reference stays on the\n * entry for cheap re-show. No-op when the entry is unknown or already hidden.\n */\n async hideEntry(entryId: TrafficIncidentsEntry['id']): Promise<void> {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._shown) return;\n if (entry._focus) {\n entry._focus = undefined;\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n entry._module?.setVisible(false);\n await entry._module?.clear();\n entry._shown = false;\n this.events.emit('shown-change', this.shownEntryIds);\n }\n\n /**\n * Drop an entry from history. Hides it from the map and stops its monitor if any.\n * Emits `entries-change` and, when the entry was shown, `shown-change`. No-op when the\n * id is unknown.\n */\n async removeEntry(entryId: TrafficIncidentsEntry['id']): Promise<void> {\n const idx = this._entries.findIndex((e) => e.id === entryId);\n if (idx === -1) return;\n const entry = this._entries[idx];\n const wasShown = !!entry._shown;\n await this._teardownEntry(entry);\n this._entries.splice(idx, 1);\n this.events.emit('entries-change', { entries: this._entries, changedIds: [entryId] });\n if (wasShown) this.events.emit('shown-change', this.shownEntryIds);\n }\n\n // ---- Clustering (dedicated typed state) ----\n\n /**\n * Store (or replace) an entry's clustering record and emit `clusters-change`. No-op when the entry is\n * unknown — clusters are entry-scoped, so an orphan record is meaningless. Called by `clusterIncidents`\n * and its monitor-tick re-run (`tools/state/clusters-runtime.ts`).\n */\n setClusters(entryId: TrafficIncidentsEntry['id'], record: ClustersRecord): void {\n if (!this._entries.some((e) => e.id === entryId)) return;\n this._clusters.set(entryId, record);\n this.events.emit('clusters-change', { entryId, clusters: record.output });\n }\n\n /** Latest clustering output for an entry, or undefined when none has been computed. */\n getClusters(entryId: TrafficIncidentsEntry['id']): SandboxClusteringOutput | undefined {\n return this._clusters.get(entryId)?.output;\n }\n\n /** Full clustering record (recipe + output) for an entry — the re-run reads this back. */\n getClustersRecord(entryId: TrafficIncidentsEntry['id']): ClustersRecord | undefined {\n return this._clusters.get(entryId);\n }\n\n /** Drop an entry's clustering. Emits `clusters-change` with `null`. No-op when nothing is stored. */\n clearClusters(entryId: TrafficIncidentsEntry['id']): void {\n if (!this._clusters.delete(entryId)) return;\n this.events.emit('clusters-change', { entryId, clusters: null });\n }\n\n /** Snapshot of an entry's focus subset, or null when no focus is active or the entry is unknown. */\n getFocus(entryId: TrafficIncidentsEntry['id']): IncidentFocus | null {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._focus) return null;\n return { ids: new Set(entry._focus.ids), reason: entry._focus.reason };\n }\n\n /**\n * Set the focus subset on a specific entry. Filters ids against the entry's current\n * incidents (drops unknown ones) and pushes the result onto the entry's module.\n * Returns the kept count and any dropped ids.\n */\n setFocus(\n entryId: TrafficIncidentsEntry['id'],\n ids: readonly string[],\n reason?: string,\n ): { focusedCount: number; droppedIds: string[] } {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) return { focusedCount: 0, droppedIds: [...ids] };\n if (ids.length === 0) {\n this.clearFocus(entryId);\n return { focusedCount: 0, droppedIds: [] };\n }\n const present = new Set(entry.data.map((f) => f.properties.id));\n const keep: string[] = [];\n const dropped: string[] = [];\n for (const id of ids) {\n if (present.has(id)) keep.push(id);\n else dropped.push(id);\n }\n if (keep.length === 0) {\n return { focusedCount: 0, droppedIds: dropped };\n }\n entry._focus = { ids: new Set(keep), reason };\n entry._module?.setFocus(keep);\n this.events.emit('focus-change', { entryId: entry.id, focus: this.getFocus(entryId) });\n return { focusedCount: keep.length, droppedIds: dropped };\n }\n\n clearFocus(entryId: TrafficIncidentsEntry['id']): void {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._focus) return;\n entry._focus = undefined;\n entry._module?.setFocus(null);\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n\n /**\n * The focused incidents in the order passed to {@link setFocus}. Empty when no focus is\n * active on the entry or when the entry is unknown. Used by UI layers that step through\n * the focused subset (prev/next).\n */\n focusedFeatures(entryId: TrafficIncidentsEntry['id']): TrafficIncident[] {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry?._focus) return [];\n const byId: Record<string, TrafficIncident> = {};\n for (const f of entry.data) {\n const id = f.properties.id;\n if (typeof id === 'string') byId[id] = f;\n }\n const ordered: TrafficIncident[] = [];\n for (const id of entry._focus.ids) {\n const feat = byId[id];\n if (feat) ordered.push(feat);\n }\n return ordered;\n }\n\n /**\n * Fly the camera to the i-th focused incident on an entry (0-based). Wraps out-of-range\n * indices so callers can pass raw prev/next counters. No-op when the entry has no active\n * focus. Line geometries fitBounds; points flyTo with a sensible zoom.\n */\n navigateFocus(entryId: TrafficIncidentsEntry['id'], index: number): void {\n const features = this.focusedFeatures(entryId);\n if (features.length === 0) return;\n const i = ((index % features.length) + features.length) % features.length;\n const feature = features[i];\n const map = this._ttMap.mapLibreMap;\n if (feature.geometry.type === 'Point') {\n map.flyTo({ center: feature.geometry.coordinates as [number, number], zoom: 15, duration: 600 });\n return;\n }\n const bbox = bboxFromGeoJSON(feature);\n if (!bbox) return;\n map.fitBounds(bbox, { padding: 120, maxZoom: 16, duration: 600 });\n }\n\n /**\n * Walk entries newest-first to find which one currently holds an incident with this id.\n * Useful for UI layers that hold an incident id without remembering which entry produced it.\n */\n findEntryWithIncident(incidentId: string): TrafficIncidentsEntry | undefined {\n for (let i = this._entries.length - 1; i >= 0; i--) {\n const entry = this._entries[i];\n if (entry.data.some((f) => f.properties.id === incidentId)) return entry;\n }\n return undefined;\n }\n\n // ---- Per-entry monitoring ----\n\n /**\n * Start polling for an entry. Lazy-creates an {@link IncidentMonitor}, wires its events\n * once, then delegates to {@link IncidentMonitor.start}. No-op when polling is already\n * running. Throws on unknown entry id.\n */\n startMonitoring(\n entryId: TrafficIncidentsEntry['id'],\n area: MonitoredArea,\n deps: IncidentMonitorDeps,\n opts?: { intervalMs?: number; skipInitialTick?: boolean },\n ): void {\n const entry = this._requireEntry(entryId);\n const monitor = (entry._monitor ??= this._createMonitorForEntry(entry));\n // Capture the pre-start state so we fire `monitor-start` on every transition\n // *into* running — that includes recovery from `stopped-error`, not just from\n // idle. `monitor.start` is itself idempotent on running.\n const wasRunning = monitor.isRunning;\n monitor.start(area, deps, opts?.intervalMs, opts?.skipInitialTick);\n if (!wasRunning) this.events.emit('monitor-start', { entryId });\n }\n\n /** Stop polling. No-op when idle or unknown. */\n stopMonitoring(entryId: TrafficIncidentsEntry['id']): void {\n const monitor = this._entries.find((e) => e.id === entryId)?._monitor;\n if (!monitor?.isActive) return;\n monitor.stop();\n this.events.emit('monitor-stop', { entryId, reason: 'manual' });\n }\n\n /**\n * True when the entry is *currently* being polled. A monitor stuck in\n * `stopped-error` returns false here so the start tool can report a truthful\n * `alreadyRunning` and a fresh `start` call actually re-arms the timer\n * instead of silently being treated as a no-op.\n */\n isMonitored(entryId: TrafficIncidentsEntry['id']): boolean {\n return this._entries.find((e) => e.id === entryId)?._monitor?.isRunning ?? false;\n }\n\n private _createMonitorForEntry(entry: TrafficIncidentsEntry): IncidentMonitor {\n const monitor = new IncidentMonitor(entry.params.bbox);\n monitor.events.on('tick', (snap) => {\n void this.replaceEntryData(entry.id, snap.incidents, snap.takenAt);\n this.events.emit('monitor-tick', { entryId: entry.id, snap });\n });\n monitor.events.on('error', (error) => {\n this.events.emit('monitor-error', { entryId: entry.id, error });\n this.events.emit('monitor-stop', { entryId: entry.id, reason: 'error' });\n });\n return monitor;\n }\n\n // ---- Internal helpers ----\n\n private _requireEntry(entryId: TrafficIncidentsEntry['id']): TrafficIncidentsEntry {\n const entry = this._entries.find((e) => e.id === entryId);\n if (!entry) throw new Error(`TrafficIncidentsState: unknown entry \"${entryId}\"`);\n return entry;\n }\n\n // Tear an entry down: stop its monitor, clear its module, drop its clustering. Idempotent.\n // Used by removeEntry, single-mode trimming, and reset.\n //\n // `analyseData` analyses bound to this entry are NOT dropped here — the {@link JobEngine} GCs each\n // orphaned job (and its record) off the `entries-change` every teardown caller emits. Clustering IS\n // dropped here: it's dedicated state this slice owns.\n //\n // Synchronous emits run before any await so callers that fire-and-forget\n // (notably `reset`, which calls `events.clear()` right after) still see them.\n private async _teardownEntry(entry: TrafficIncidentsEntry): Promise<void> {\n this.clearClusters(entry.id);\n this.stopMonitoring(entry.id);\n if (entry._focus) {\n entry._focus = undefined;\n this.events.emit('focus-change', { entryId: entry.id, focus: null });\n }\n if (entry._shown) entry._shown = false;\n if (entry._module) {\n entry._module.setVisible(false);\n await entry._module.clear();\n }\n entry._module = undefined;\n }\n\n /**\n * Hide every traffic-incidents entry currently on the map. Map-only — history is kept.\n * Implements {@link ClearableMapSlice}.\n */\n async clearShown(): Promise<void> {\n for (const id of this.shownEntryIds) await this.hideEntry(id);\n }\n\n reset(): void {\n // Tear every entry down synchronously where we can — the awaits inside _teardownEntry\n // are just module.clear()s; we run them concurrently and don't block reset() itself.\n const clearedIds = this._entries.map((entry) => entry.id);\n for (const entry of this._entries) void this._teardownEntry(entry);\n const hadEntries = this._entries.length > 0;\n this._entries = [];\n if (hadEntries) {\n this.events.emit('entries-change', { entries: this._entries, changedIds: clearedIds });\n this.events.emit('shown-change', this.shownEntryIds);\n }\n this.events.clear();\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport { type TomTomMap, TrafficFlowModule, TrafficIncidentsModule } from '@tomtom-org/maps-sdk/map';\nimport type { StateSlice } from '../../types';\n\n/**\n * State for traffic tile overlays — flow tiles and incident overlay tiles. These are\n * raster/tile layers that the user toggles on top of the base map; result caching and\n * per-result rendering for incident *features* lives in {@link TrafficIncidentsState},\n * and analytics aggregations in {@link TrafficAreaAnalyticsState}.\n *\n * @group Agent Toolkit\n */\nexport class TrafficTilesState implements StateSlice {\n private _trafficFlowModule?: TrafficFlowModule;\n private _trafficIncidentsModule?: TrafficIncidentsModule;\n\n constructor(private readonly _ttMap: TomTomMap) {}\n\n // Module getters. The SDK shares style-owned modules per map, so these are lookups —\n // the field only records that we have fetched it, for the synchronous accessors below.\n\n async getTrafficFlowModule(): Promise<TrafficFlowModule> {\n this._trafficFlowModule = await TrafficFlowModule.get(this._ttMap);\n return this._trafficFlowModule;\n }\n\n async getTrafficIncidentsModule(): Promise<TrafficIncidentsModule> {\n this._trafficIncidentsModule = await TrafficIncidentsModule.get(this._ttMap);\n return this._trafficIncidentsModule;\n }\n\n // Cached module access (for reading without triggering initialization)\n\n get trafficFlowModule(): TrafficFlowModule | undefined {\n return this._trafficFlowModule;\n }\n\n get trafficIncidentsModule(): TrafficIncidentsModule | undefined {\n return this._trafficIncidentsModule;\n }\n\n reset(): void {\n this._trafficFlowModule = undefined;\n this._trafficIncidentsModule = undefined;\n }\n}\n","/**\n * @module agent-toolkit-state\n */\n\nimport { TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport { JobEngine } from '../engine';\nimport { resolveSandboxExecutor } from '../tools/shared/sandbox';\nimport type { StateSlice, ToolState } from '../types';\nimport { Analyses } from './analyses';\nimport { BaseMapState } from './base-map';\nimport { BYODState } from './byod';\nimport { CustomGeometriesState } from './custom-geometries';\nimport { MapPOIsState } from './map-pois';\nimport { PlacesState } from './places';\nimport { RangeState } from './range';\nimport { RoutingState } from './routing';\nimport { EventsState } from './trackers';\nimport { TrafficAreaAnalyticsState } from './traffic-area-analytics';\nimport { TrafficIncidentsState } from './traffic-incidents';\nimport { TrafficTilesState } from './traffic-tiles';\n\nexport { BaseMapState } from './base-map';\nexport {\n type BYODEntry,\n type BYODSource,\n BYODState,\n type BYODStateEvents,\n type ByodSourceUrlValidation,\n type ByodSourceUrlValidator,\n} from './byod';\nexport { CustomGeometriesState, type CustomGeometriesStateEvents } from './custom-geometries/state';\nexport { MapPOIsState } from './map-pois';\nexport { type PlacesMarkerType, PlacesState, type PlacesStateEvents, placesMarkerTypes } from './places/state';\nexport { RangeState, type RangeStateEvents } from './range/state';\nexport { RoutingState, type RoutingStateEvents } from './routing/state';\nexport { TrafficAreaAnalyticsState, type TrafficAreaAnalyticsStateEvents } from './traffic-area-analytics/state';\nexport { type IncidentMonitorDeps } from './traffic-incidents/monitor/monitor';\nexport {\n type IncidentFocus,\n type IncidentsAnalysis,\n TrafficIncidentsState,\n type TrafficIncidentsStateEvents,\n} from './traffic-incidents/state';\nexport { TrafficTilesState } from './traffic-tiles/state';\n\nTomTomConfig.instance.put({ language: 'en-GB' });\n\n/**\n * Per-slice display policy. With `multiple` (default) several history entries can be on the\n * map at once; with `single` only the most recent one stays. Switching from `multiple` to\n * `single` automatically drops every entry except the latest (and clears their modules) so\n * the map matches the new policy immediately.\n *\n * @group Agent Toolkit\n */\nexport type EntryMode = 'single' | 'multiple';\n\n/**\n * State slice that owns a history of entries and can be driven into `single` or `multiple`\n * mode. Every {@link ToolState} slice with `setEntryMode` implements this shape, which lets\n * callers (the `setEntryMode` tool, `createMapAgent`'s `dataEntries` option, …) handle the\n * whole family uniformly without listing slice names.\n *\n * @group Agent Toolkit\n */\nexport interface EntryModeSlice {\n readonly entryMode: EntryMode;\n readonly entries: readonly { readonly id: string }[];\n setEntryMode(mode: EntryMode): Promise<void>;\n}\n\n/**\n * Data-entry kind names — the vocabulary used by `dataEntries` on `createMapAgent`. Superset\n * of the analyse/process input kinds (`EntryDataKind`) — adds `ranges`, which has its own\n * entry-mode-supporting slice but is not a top-level input kind for those tools.\n *\n * The kind names are LLM-facing (`routes`, `incidents`) rather than slice names (`routing`,\n * `trafficIncidents`); the slice mapping is internal — see {@link DATA_ENTRY_KIND_TO_SLICE}.\n *\n * @group Agent Toolkit\n */\nexport type DataEntryKind =\n | 'places'\n | 'routes'\n | 'incidents'\n | 'customGeometries'\n | 'trafficAreaAnalytics'\n | 'byod'\n | 'ranges';\n\n/**\n * Per-kind configuration accepted by `createMapAgent`'s `dataEntries` option.\n *\n * @group Agent Toolkit\n */\nexport type DataEntryConfig = {\n /**\n * When `false`, the kind is removed from the agent's tool surface entirely:\n * - the kind disappears from `analyseData` / `processData` scope and input fields;\n * - related recall / display / fetch tools are dropped from the registry\n * (see {@link TOOLS_BY_DATA_ENTRY_KIND}).\n *\n * Default: `true`.\n */\n enabled?: boolean;\n /**\n * Initial entry-display policy for the underlying slice. `single` keeps only the latest\n * entry on the map (others auto-hide); `multiple` (default) lets several coexist.\n */\n entryMode?: EntryMode;\n};\n\n/**\n * Mapping from {@link DataEntryKind} (LLM-facing name) to the {@link ToolState} slice key\n * that owns the entries for that kind. Used by `createMapAgent` to apply `entryMode` from\n * `dataEntries[kind].entryMode` to the right slice.\n *\n * @group Agent Toolkit\n */\nexport const DATA_ENTRY_KIND_TO_SLICE = {\n places: 'places',\n routes: 'routing',\n incidents: 'trafficIncidents',\n customGeometries: 'customGeometries',\n trafficAreaAnalytics: 'trafficAreaAnalytics',\n byod: 'byod',\n ranges: 'ranges',\n} as const satisfies Record<DataEntryKind, EntryModeSliceName>;\n\n/**\n * Compile-time union of {@link ToolState} keys whose slice supports {@link EntryMode}.\n * Names match the {@link ToolState} keys exactly — `routing` (not `routes`),\n * `customGeometries` (not `geometries`) — so the slice name in the tool API never drifts\n * from the property on `state`.\n *\n * Auto-grows when a new slice implements `setEntryMode`: the union picks up the new key,\n * and the runtime list {@link ENTRY_MODE_SLICE_NAMES} fails to type-check until the key\n * is added there too.\n *\n * @group Agent Toolkit\n */\nexport type EntryModeSliceName = {\n [K in keyof ToolState]: ToolState[K] extends EntryModeSlice ? K : never;\n}[keyof ToolState];\n\n/**\n * Runtime enumeration of slice names that support entry mode. The `satisfies` clause\n * makes drift a compile error: when a new {@link ToolState} slice gains `setEntryMode`,\n * this constant has to be updated before the package will build.\n *\n * @group Agent Toolkit\n */\nexport const ENTRY_MODE_SLICE_NAMES = Object.keys({\n places: true,\n routing: true,\n ranges: true,\n customGeometries: true,\n byod: true,\n trafficAreaAnalytics: true,\n trafficIncidents: true,\n} satisfies Record<EntryModeSliceName, true>) as readonly EntryModeSliceName[];\n\n/**\n * State slice that owns a set of \"entries\" (routes, ranges) where each entry can be hidden\n * individually by id and the slice exposes the currently-shown set. Implemented by\n * {@link RoutingState} and {@link RangeState}; consumed by helpers that need to hide\n * previously-shown entries before rendering a new one (see `hidePreviousShownEntries`).\n *\n * Not implemented by {@link PlacesState}, which uses a wider per-entry marker-type model\n * (`removeShownEntries(ids)` rather than `hideEntry(id)`).\n *\n * @group Agent Toolkit\n */\nexport interface ShownEntriesSlice extends StateSlice {\n readonly entryMode: EntryMode;\n readonly shownEntryIds: ReadonlySet<string>;\n hideEntry(entryId: string): Promise<void>;\n}\n\n/**\n * State slice that renders something on the map and can hide ALL of it in one call. The\n * {@link clearMap} tool walks every slice that implements this interface so adding a new\n * slice (e.g. a future overlay) auto-grows the tool's surface — no hand-edits required.\n *\n * `clearShown` is map-only: it hides what's currently rendered but leaves the slice's\n * history (`entries`) intact. Use the slice's `reset()` for a full wipe.\n *\n * @group Agent Toolkit\n */\nexport interface ClearableMapSlice {\n clearShown(): Promise<void>;\n}\n\n/**\n * Compile-time union of {@link ToolState} keys whose slice implements {@link ClearableMapSlice}.\n * Same pattern as {@link EntryModeSliceName}: the union auto-grows when a slice gains\n * `clearShown`, and the runtime list {@link CLEARABLE_SLICE_NAMES} fails to type-check until\n * the key is added there too.\n *\n * @group Agent Toolkit\n */\nexport type ClearableSliceName = {\n [K in keyof ToolState]: ToolState[K] extends ClearableMapSlice ? K : never;\n}[keyof ToolState];\n\n/**\n * Runtime enumeration of slice names that support `clearShown`. The `satisfies` clause\n * makes drift a compile error: when a new {@link ToolState} slice implements\n * {@link ClearableMapSlice}, this constant has to be updated before the package will build.\n *\n * @group Agent Toolkit\n */\nexport const CLEARABLE_SLICE_NAMES = Object.keys({\n places: true,\n routing: true,\n ranges: true,\n customGeometries: true,\n byod: true,\n trafficAreaAnalytics: true,\n trafficIncidents: true,\n} satisfies Record<ClearableSliceName, true>) as readonly ClearableSliceName[];\n\n/**\n * Factory that creates a fully initialized ToolState from a TomTomMap instance.\n * Optionally merges custom state slices alongside built-in state.\n *\n * @param map - TomTomMap instance for module initialization\n * @param customSlices - Additional state slices accessible by custom tools\n *\n * @group Agent Toolkit\n */\nexport const createToolState = <T extends Record<string, unknown> = Record<string, never>>(\n map: TomTomMap,\n customSlices?: T,\n): ToolState & T => {\n const base: ToolState = {\n places: new PlacesState(map),\n mapPOIs: new MapPOIsState(map),\n routing: new RoutingState(map),\n baseMap: new BaseMapState(map),\n trafficTiles: new TrafficTilesState(map),\n trafficAreaAnalytics: new TrafficAreaAnalyticsState(map),\n trafficIncidents: new TrafficIncidentsState(map),\n ranges: new RangeState(map),\n customGeometries: new CustomGeometriesState(map),\n byod: new BYODState(map),\n // Env-resolved executor (iframe-worker in the browser, main thread in Node/SSR) so even direct\n // createToolState callers get the sandbox boundary; createMapAgent re-resolves with user tuning.\n codeExecution: resolveSandboxExecutor(),\n // The single recurrence engine: analyses + trackers register jobs on it.\n engine: new JobEngine(),\n analyses: new Analyses(),\n trackers: new EventsState(),\n };\n if (customSlices) Object.assign(base, customSlices);\n // Wire the recurrence engine once, here, after customSlices are merged — so the engine subscribes its\n // entries-change handlers to the final slice objects (including any customSlices that override a core\n // source slice) and every ToolState has a live engine without the job builders wiring lazily.\n base.engine.ensureWired(base);\n return base as ToolState & T;\n};\n","/**\n * @module agent-toolkit-system-prompt\n *\n * Behavioral contract: persona, tone, dual-response, cadence — lives here.\n *\n * Tool mechanics: call shape, return shape, error handling, when-to-use vs.\n * which-tool-next — lives in the relevant tool's description string and Zod\n * schema. Each tool re-explains its own role; the LLM sees those alongside\n * this prompt. The intent classifier (see `utils/intent-classifier.ts`) also\n * scopes tools per step, so a tool's description is only in context when that\n * tool is active — keep cross-cutting safety rules here (always present), and\n * leave per-tool mechanics to the tool itself.\n *\n * If you find yourself adding lines here to fix an LLM mistake, ask first\n * whether the fix belongs in a tool description or a tool error message.\n *\n * The prompt is split into named {@link SYSTEM_PROMPT_SECTIONS} so consumers can\n * override a single section via {@link composeSystemPrompt} without rebuilding\n * the whole string.\n */\n\n/**\n * Named, individually-overridable sections of the base system prompt.\n *\n * @group Agent Toolkit\n */\nexport type SystemPromptSection =\n | 'identity'\n | 'capabilities'\n | 'rejectionRules'\n | 'responseFormatting'\n | 'dataConfidence'\n | 'toolExecution'\n | 'sessionState';\n\n/**\n * Per-section body overrides. Each key replaces that section's body; omitted\n * sections fall back to {@link SYSTEM_PROMPT_SECTIONS}. Accepted by\n * {@link composeSystemPrompt} and `MapAgentOptions.systemPrompt`.\n *\n * @remarks\n * To *extend* rather than replace a section, read its current default from the\n * exported {@link SYSTEM_PROMPT_SECTIONS} map and override with a derived string\n * — e.g. append a rule, prepend a note, or hand the default to a coding agent to\n * rewrite under some criteria:\n *\n * ```typescript\n * composeSystemPrompt({\n * // Keep the default behaviour, add one extra line.\n * responseFormatting: `${SYSTEM_PROMPT_SECTIONS.responseFormatting}\\n- Always answer in Spanish.`,\n * });\n * ```\n *\n * @group Agent Toolkit\n */\nexport type SystemPromptSectionOverrides = Partial<Record<SystemPromptSection, string>>;\n\n// Headings the composer prepends to each section's body. Kept separate from the\n// body text so a consumer overriding a section supplies only the body — the\n// `RESPONSE FORMATTING:` / `SESSION STATE:` heading is added automatically. The\n// opening prose sections (identity / capabilities) carry no heading; they read as\n// the prompt's natural-language preamble.\nconst SECTION_HEADINGS: Record<SystemPromptSection, string | null> = {\n identity: null,\n capabilities: null,\n rejectionRules: 'SCOPE & REJECTIONS',\n responseFormatting: 'RESPONSE FORMATTING',\n dataConfidence: 'DATA CONFIDENCE',\n toolExecution: 'TOOL EXECUTION',\n sessionState: 'SESSION STATE',\n};\n\n/**\n * The default body text for each base-prompt section — heading excluded.\n *\n * @remarks\n * Each value is the section's content only; the structural heading (e.g.\n * `RESPONSE FORMATTING:`) is added by {@link composeSystemPrompt}. So an override\n * passes just the body — `composeSystemPrompt({ responseFormatting: 'Reply in Spanish.' })`\n * still renders under the `RESPONSE FORMATTING:` heading.\n *\n * The behavioral prose is split so each concern is overridable on its own:\n * - `identity` — who the assistant is and its tone of voice; the natural hook to\n * rebrand or set a persona.\n * - `capabilities` — the map environment plus a compact summary of what the\n * toolkit can do. Kept at the group level (not per-tool) so it does not drift\n * as tools change; the exact mechanics live in each tool's description and the\n * classifier prompt. It also gives `rejectionRules` something concrete to steer\n * the user back toward.\n * - `rejectionRules` — how to handle in-scope / mixed / out-of-scope / illegal\n * requests, and the requirement to always explain a rejection.\n * - `dataConfidence` — how to surface incomplete, partial, stale, or conflicting\n * data instead of presenting it as complete.\n *\n * Location-reference routing (\"near me\" → getCurrentLocation, \"here\" →\n * getViewport) is intentionally NOT a section: it is a tool-selection rule\n * already owned by the classifier prompt and the two tools' cross-referencing\n * descriptions.\n *\n * This map is the canonical source of the default body text, exported so a\n * consumer can read a default and override with a derived value (extend, prefix,\n * or rewrite it) — see {@link SystemPromptSectionOverrides}.\n *\n * @group Agent Toolkit\n */\nexport const SYSTEM_PROMPT_SECTIONS: Record<SystemPromptSection, string> = {\n identity: 'You are a helpful map assistant.',\n capabilities:\n 'You operate a TomTom interactive map with location services. You can search places, POIs, and addresses ' +\n '(and reverse-geocode); plan and edit multi-stop routes (alternatives, cost models); compute reachable-area ' +\n '(isochrone) ranges; load live traffic incidents and historical traffic analytics, then cluster, monitor, or ' +\n 'focus them; run spatial analysis, aggregations, and charts over loaded data and derive geometries; add your ' +\n 'own GeoJSON layers; and control the map — pan, zoom, pitch/bearing, styles, base-map tiles, and show/hide results.',\n rejectionRules:\n \"- Handle the in-scope part fully, then note what you can't help with.\\n\" +\n \"- Fully out of scope: decline and describe what you cover — don't guess the user's need.\\n\" +\n '- Flag scope creep as it arises, not at the end.\\n' +\n '- Reject illegal requests outright with a brief reason and no detail — no partial help, rephrasing, or redirecting.\\n' +\n '- Every rejection states why and offers an in-scope next step — never a dead end.',\n responseFormatting:\n '- Use markdown; bold key info; bullet lists for multiple items.\\n' +\n '- Be concise — lead with the most relevant information.',\n dataConfidence:\n \"- Return incomplete, outdated, or low-coverage data with a clear note on what's limited and why — never present it as complete.\\n\" +\n \"- If only partial, name what's missing and offer to proceed — let the user decide.\\n\" +\n '- If signals conflict, acknowledge it, state what you used, and why.',\n toolExecution:\n \"- Run independent tools in the same step (parallel); render results with the tool's own show / \" +\n 'showOnMap in that step, not a follow-up call.\\n' +\n '- Ground every fact and effect in a tool, not your training data: anything a tool can supply or do ' +\n '(an address, a count, a category, a list, any map change) is real ONLY when you call that tool — even ' +\n 'when the request reads like a general or explanatory question (\"what categories represent X?\"). Without ' +\n 'the call, never reply as if a result exists, the map changed, or an action is underway (\"here are the ' +\n 'results\", \"Profiling X…\", \"the panel will show…\"); the call IS the action, so make it.\\n' +\n '- The ONE exception is ANSWERING from what you already have: you may reply in prose, with no tool call, ' +\n 'from what is ALREADY in this conversation (earlier tool results, session state, what the user told you) ' +\n 'or about your own capabilities (\"what can you do?\"). Using what you have is fine; inventing what a tool ' +\n 'would return is not.\\n' +\n '- Needing MORE input is not that exception: for a clear, complete request, act with sensible defaults ' +\n '(do not ask); when input is missing, implied, or ambiguous, your ONLY way to ask the user is to CALL ' +\n 'clarifyIntent. A plain-text request for the details — \"I need…\", \"please provide…\", \"could you tell ' +\n 'me…\", or a bare list of questions — without that call does NOT count; call clarifyIntent (it then states ' +\n 'whether to also write the questions as text).',\n 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.`,\n};\n\n// Canonical assembly order. Sections are joined with a blank line between them.\nconst SECTION_ORDER: SystemPromptSection[] = [\n 'identity',\n 'capabilities',\n 'rejectionRules',\n 'responseFormatting',\n 'dataConfidence',\n 'toolExecution',\n 'sessionState',\n];\n\n// Prepend the section heading (if any) to its body.\nconst renderSection = (key: SystemPromptSection, body: string): string => {\n const heading = SECTION_HEADINGS[key];\n return heading ? `${heading}:\\n${body}` : body;\n};\n\n/**\n * Composes the base system prompt from {@link SYSTEM_PROMPT_SECTIONS}, applying\n * any per-section overrides.\n *\n * @param overrides - Replacement **body** text keyed by section; omitted\n * sections fall back to the default. The section heading is added automatically,\n * so an override does not repeat it. Pass the result to\n * `MapAgentOptions.systemPrompt`.\n * @returns The composed system prompt string.\n *\n * @example\n * ```typescript\n * import { createMapAgent, composeSystemPrompt } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n *\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * // No need to repeat \"RESPONSE FORMATTING:\" — it is prepended for you.\n * systemPrompt: composeSystemPrompt({\n * responseFormatting: 'Always respond in Spanish, and keep it terse.',\n * }),\n * });\n * ```\n *\n * @group Agent Toolkit\n */\nexport const composeSystemPrompt = (overrides?: SystemPromptSectionOverrides): string =>\n SECTION_ORDER.map((key) => ({ key, body: overrides?.[key] ?? SYSTEM_PROMPT_SECTIONS[key] }))\n // Drop empty-bodied sections (e.g. a consumer overriding one to '') so they leave no dangling heading.\n .filter(({ body }) => body.trim().length > 0)\n .map(({ key, body }) => renderSection(key, body))\n .join('\\n\\n');\n\n/**\n * The base system prompt that teaches the LLM how to use the agent toolkit tools.\n *\n * @remarks\n * Equivalent to `composeSystemPrompt()` with no overrides. Export this constant\n * so consumers can reference or extend it when providing a custom system prompt\n * via `MapAgentOptions.systemPrompt`. To replace just one section, prefer\n * {@link composeSystemPrompt} instead of rebuilding the whole string.\n *\n * @example\n * ```typescript\n * import { createMapAgent, BASE_SYSTEM_PROMPT } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n *\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * systemPrompt: BASE_SYSTEM_PROMPT + '\\n\\nAlways respond in Spanish.'\n * });\n * ```\n *\n * @group Agent Toolkit\n */\nexport const BASE_SYSTEM_PROMPT = composeSystemPrompt();\n\n/**\n * Inputs to {@link buildSystemPrompt}.\n *\n * @internal\n * @ignore\n */\nexport type BuildSystemPromptOptions = {\n /**\n * Either a full prompt string that replaces the base entirely, or\n * {@link SystemPromptSectionOverrides} composed onto the base. A full string\n * ignores `prefix` and `suffix` (the caller owns the whole prompt); section\n * overrides (or none) still honor both.\n */\n customPrompt?: string | SystemPromptSectionOverrides;\n /**\n * Preamble prepended above the whole prompt, separated by a blank line and\n * carrying no heading. Ignored when `customPrompt` is a full string.\n */\n prefix?: string;\n /**\n * Additional prompt text appended under an `ADDITIONAL INSTRUCTIONS` heading.\n * Ignored when `customPrompt` is a full string.\n */\n suffix?: string;\n};\n\n/**\n * Builds the complete system prompt for the agent toolkit.\n *\n * @param options - See {@link BuildSystemPromptOptions}.\n * @returns Complete system prompt string\n *\n * @internal\n * @ignore\n */\nexport const buildSystemPrompt = ({ customPrompt, prefix, suffix }: BuildSystemPromptOptions = {}): string => {\n // A full string replacement owns the whole prompt; everything else is ignored.\n if (typeof customPrompt === 'string') {\n return customPrompt;\n }\n const base = customPrompt ? composeSystemPrompt(customPrompt) : BASE_SYSTEM_PROMPT;\n const withSuffix = suffix ? `${base}\\n\\nADDITIONAL INSTRUCTIONS:\\n${suffix}` : base;\n return prefix ? `${prefix}\\n\\n${withSuffix}` : withSuffix;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared output schema building blocks reused across multiple tools.\n * Tool-specific output schemas live next to their respective tool definitions.\n */\n\nimport { placeTypes } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { FeatureFlags } from '../types';\n\n// ---------------------------------------------------------------------------\n// Common\n// ---------------------------------------------------------------------------\n\n/** Returned by every tool when execution fails. */\nexport const toolErrorSchema = z.object({\n error: z.string(),\n});\n\n/** One analysis already attached to an entry, as a recall tool lists it. */\nexport const analysisIndexSchema = z.object({\n name: z.string(),\n outputFormat: z.enum(['json', 'chart']),\n timestamp: z.number(),\n description: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// BYOD data profile — runtime \"schema\" of a customer FeatureCollection\n// ---------------------------------------------------------------------------\n\n/**\n * Compact, runtime-derived description of a BYOD entry's shape: geometry types\n * and a per-property profile (type, coverage, examples). Lets the model learn\n * what it can filter / analyse without ever receiving the raw FeatureCollection.\n * Mirrors `BYODDataProfile` from the byod state slice.\n *\n * Note: when this profile is returned to the model it is passed through\n * `toByodSafeProfile`, which drops **string** example values (customer-supplied,\n * so a potential prompt-injection vector). Numeric / boolean examples and all\n * structural fields are kept. The schema shape is unchanged — `examples` is just\n * a string-free subset on model-facing results.\n */\nexport const byodDataProfileSchema = z.object({\n featureCount: z.number(),\n geometryTypes: z.array(z.string()).describe('Geometry types present (Point / LineString / Polygon / …).'),\n properties: z\n .array(\n z.object({\n name: z.string(),\n types: z\n .array(z.string())\n .describe('Distinct JSON value types seen for this key (e.g. [\"string\"], [\"number\",\"null\"]).'),\n coverage: z.number().describe('Fraction of features carrying this key (1 = every feature).'),\n examples: z\n .array(z.union([z.string(), z.number(), z.boolean()]))\n .describe(\n 'A few example NUMERIC / BOOLEAN values for inferring semantics. String values are ' +\n 'withheld from results (customer-supplied free text — a prompt-injection vector); ' +\n 'use `types` to know a property is a string.',\n ),\n }),\n )\n .describe('Per-property profile, highest-coverage first.'),\n propertiesOmitted: z\n .number()\n .optional()\n .describe('Count of property keys omitted when the data has more distinct keys than the cap.'),\n});\n\n// ---------------------------------------------------------------------------\n// Grounded `where` resolution — shared by every tool that resolves a named area\n// (getTrafficIncidents, discoverPlaces). Always-on awareness + a confirm/retry cue.\n// ---------------------------------------------------------------------------\n\n/**\n * Where each named `where` query actually resolved. Present whenever a `where` named an area (a\n * `queries`/`placeIds` entry or a `nearby` query) — built from the resolver's grounded match (see\n * `resolvedAreasDisclosure` / `biasDisclosure` in resolve-where). Omitted for raw bounding boxes /\n * geometries (nothing was geocoded). The grounded `matched` lets the model self-correct a wrong\n * same-name resolution.\n */\nexport const resolvedAreasOutputSchema = z\n .array(\n z.object({\n query: z.string().optional().describe('Your input query text, echoed.'),\n matched: z.string().describe('The GROUNDED place the data was actually loaded for.'),\n }),\n )\n .describe(\n 'Where each named `where` query ACTUALLY resolved. `matched` is the grounded geocoded place the ' +\n 'data was loaded for — NOT your query text. ALWAYS check it is the place the operator meant: a ' +\n 'same-name place elsewhere (\"east London\" → \"London, CA\") silently loads the wrong area. ' +\n 'If `matched` is wrong, re-issue with a more specific query. ' +\n 'Omitted for raw bounding boxes / geometries (nothing was geocoded).',\n );\n\n// ---------------------------------------------------------------------------\n// Place (compact summary) — used by geocode, reverse-geocode, search, etc.\n// ---------------------------------------------------------------------------\n\nconst basePlaceShape = {\n id: z\n .string()\n .describe('Stable place feature ID; pass as `{ placeIdOrEntryId }` to setRoute / addWaypointsToRoute / etc.'),\n type: z\n .enum(placeTypes)\n .optional()\n .describe('Place classification (POI, Street, Geography, Cross Street, …), when known.'),\n name: z.string().optional().describe('POI name if available'),\n address: z.string().optional().describe('Human-readable address'),\n position: z.array(z.number()).length(2).describe('[longitude, latitude]'),\n};\n\n/**\n * Build a compact place summary schema.\n *\n * @remarks\n * Takes {@link FeatureFlags} so a future flag can vary the shape; no flag\n * currently does, so every caller gets the same schema.\n */\nexport const buildPlaceOutputSchema = (_flags: FeatureFlags) =>\n z.object(basePlaceShape).describe('Compact place summary');\n\n/**\n * Build a compact places-collection summary. Mirrors\n * {@link buildPlaceOutputSchema} for `features[]`.\n */\nexport const buildPlacesOutputSchema = (flags: FeatureFlags) =>\n z\n .object({\n count: z.number(),\n features: z.array(buildPlaceOutputSchema(flags)),\n })\n .describe('Compact places summary');\n\n/** Token-efficient summary of a single place. */\nexport const placeOutputSchema = buildPlaceOutputSchema({});\n\n/** Token-efficient summary of a places collection. */\nexport const placesOutputSchema = buildPlacesOutputSchema({});\n\n// ---------------------------------------------------------------------------\n// Route (summarized — geometry, progress, and guidance stripped)\n// ---------------------------------------------------------------------------\n\nconst summarySchema = z.object({\n travelTimeInSeconds: z.number(),\n lengthInMeters: z.number(),\n trafficDelayInSeconds: z.number().optional(),\n trafficLengthInMeters: z.number().optional(),\n departureTime: z.string().optional(),\n arrivalTime: z.string().optional(),\n noTrafficTravelTimeInSeconds: z.number().optional(),\n historicTrafficTravelTimeInSeconds: z.number().optional(),\n liveTrafficIncidentsTravelTimeInSeconds: z.number().optional(),\n});\n\n/**\n * A single summarized route — just the route summary and optional per-leg summaries.\n */\nexport const summarizedRouteSchema = z.object({\n index: z.number().describe('0=best, 1+=alternative'),\n summary: summarySchema,\n legSummaries: z.array(summarySchema).optional().describe('Present when route has multiple legs'),\n});\n\n/** Summary of one or more calculated routes (coordinates stripped). */\nexport const routesOutputSchema = z.object({\n count: z.number(),\n routes: z.array(summarizedRouteSchema),\n});\n\n/**\n * Route summary returned by the tools that WRITE a routing entry (setRoute + the waypoint editors).\n * Adds `entryId` so follow-up tools (analyseData / processData) know which entry the route landed in.\n * Recall tools surface the id separately (top-level `id`), so they use the plain {@link routesOutputSchema}.\n */\nexport const routesWriteOutputSchema = routesOutputSchema.extend({\n entryId: z\n .string()\n .describe('Routing entry id the route was written to — pass as `routesEntryIDs` to analyseData / processData.'),\n});\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromGeoJSON, type HasBBox } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Tool schema for calculating a bounding box from GeoJSON.\n */\nexport const calculateBBoxSchema = z.object({\n geoJson: z\n .union([z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))])\n .describe('GeoJSON object or array of GeoJSON objects'),\n});\n\n/** Success shape or the standardized `{ error }` failure shape. */\nexport const calculateBBoxOutputSchema = z.union([\n z.object({\n bbox: z.array(z.number()).length(4).describe('[minLng, minLat, maxLng, maxLat]'),\n minLng: z.number(),\n minLat: z.number(),\n maxLng: z.number(),\n maxLat: z.number(),\n }),\n toolErrorSchema,\n]);\n\nexport const calculateBBoxDescription =\n 'Compute a `[minLng, minLat, maxLng, maxLat]` bbox from GeoJSON (longitude before latitude).';\n\n/** Execute function for calculateBBox — usable with ToolEntry format. */\nexport const executeCalculateBBox = async (params: z.infer<typeof calculateBBoxSchema>, _state: ToolState) => {\n const { geoJson } = params;\n try {\n // The schema validates `geoJson` only as a loose record/array; bridge through `unknown` to\n // hand it to bboxFromGeoJSON, which validates the actual GeoJSON shape at runtime.\n const bbox = bboxFromGeoJSON(geoJson as unknown as HasBBox);\n\n if (!bbox) {\n return { error: 'Could not calculate bounding box - input may be empty or invalid GeoJSON' };\n }\n\n return {\n bbox,\n minLng: bbox[0],\n minLat: bbox[1],\n maxLng: bbox[2],\n maxLat: bbox[3],\n };\n } catch (error) {\n return {\n error: `Failed to calculate bbox: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolEntry, ToolState } from '../../types';\n\n// clarifyIntent is a UI-only tool: it doesn't fetch or compute anything. It renders the agent's\n// questions as a quick multiple-choice form in the chat. The user's picks are sent back as their\n// next message, which the agent then acts on. Use it instead of a prose question when a few\n// discrete choices would unblock the task. Anything `question*` is shown to the USER; the \"stop and\n// wait\" directive lives in the description-level usage contract, not the result.\n\n/** A single clarification question shown to the user. Exported so a host can type its form state. */\nexport const questionSchema = z.object({\n id: z.string().describe('Short stable key for the answer, e.g. \"travelMode\" or \"radius\".'),\n question: z.string().describe('The question to put to the user.'),\n options: z\n .array(z.string())\n .min(2)\n .max(8)\n .describe(\n 'Concrete suggestions, shown as numbered rows the user taps to answer. A freeform \"Something else\" ' +\n 'input is always shown too, so do NOT add your own \"other\"/\"something else\" option — just offer your ' +\n 'best concrete suggestions.',\n ),\n multiSelect: z\n .boolean()\n .default(false)\n .describe(\n 'Allow more than one answer: tapping toggles options (checked) and the user confirms with the send ' +\n 'button — use for lists like \"which place types matter\". Default false = single-select, where ' +\n 'tapping an option answers and advances immediately. Each question carries its own flag, so a single ' +\n 'call may freely mix single- and multi-select questions.',\n ),\n});\n\n/** One clarification question — the unit a host UI renders. */\nexport type ClarifyIntentQuestion = z.infer<typeof questionSchema>;\n\n/**\n * Input schema for {@link executeClarifyIntent}.\n */\nexport const clarifyIntentSchema = z.object({\n intro: z.string().optional().describe('One short line of context for why you need to ask.'),\n questions: z.array(questionSchema).min(1).max(4).describe('1-4 questions that resolve the ambiguity.'),\n});\n\n/**\n * Output schema for {@link executeClarifyIntent}. The tool resolves immediately, echoing back the\n * `intro` + `questions` (so a host can render the whole form) plus an `awaiting_user_response` status.\n * No \"stop and wait\" directive is echoed here: the description-level usage contract already carries it\n * (CLARIFY_FORM_PROMPT and CLARIFY_PROSE_PROMPT both tell the agent to STOP and wait), so repeating it\n * in the result would be redundant.\n */\nexport const clarifyIntentOutputSchema = z.object({\n status: z.literal('awaiting_user_response'),\n intro: z.string().optional(),\n questions: z.array(questionSchema),\n});\n\n/**\n * Default, domain-neutral description for the clarifyIntent tool. Override via\n * {@link createClarifyIntentTool} to add domain-specific guidance (e.g. which definitional choices\n * matter for your use case).\n */\nexport const clarifyIntentDescription =\n 'Ask the user to resolve a genuinely ambiguous or missing input before acting — collects a few discrete ' +\n 'choices as a short set of questions (numbered suggestions plus a freeform answer). Use it ONLY when you ' +\n 'cannot proceed sensibly: a required input is missing with no reasonable default, or the request is genuinely ' +\n 'ambiguous (e.g. an unclear concept, or which option matters most). Do NOT use it for a clear, actionable ' +\n 'request — prefer acting with sensible defaults and stating them. Offer your best concrete suggestions. ' +\n 'Batching a few related questions in a single call is fine; put any context in the `intro` field. After ' +\n 'calling it, STOP and wait for the user — do not call other tools or answer until they reply.';\n\n/** Usage contract appended to the description when the host renders the form: silent turn, questions not restated in prose. */\nexport const CLARIFY_FORM_PROMPT =\n '- When a request is missing inputs you need to act on, do NOT call the action tool with guessed or empty ' +\n 'inputs, and do NOT ask for the missing details in prose — call clarifyIntent FIRST to collect them, then ' +\n 'STOP and wait. Asking in a sentence when you could offer a form is the most common mistake; default to the form.\\n' +\n '- Any question whose answer is a choice among a few concrete options IS a clarifyIntent form, even just two ' +\n 'or three. If you catch yourself about to ask \"A, B, or C?\" in text, call clarifyIntent with those as options instead.\\n' +\n '- A clarifyIntent call must be the ENTIRE turn: one tool call with EMPTY text content — no preamble, and ' +\n 'NEVER restate the questions or options in the message (the form already shows them). Put any one-line ' +\n \"context in the form's `intro` field, and give each question a few concrete options.\\n\" +\n '- One call can carry several related questions (the form shows them one at a time). Fall back to a prose ' +\n 'question ONLY when the answer is genuinely free-form with no suggestable options.';\n\n/** Usage contract appended to the description when there is no form: the model also writes the questions as prose so any UI shows them. */\nexport const CLARIFY_PROSE_PROMPT =\n '- When a request is missing inputs you need to act on, do NOT call the action tool with guessed or empty ' +\n 'inputs — call clarifyIntent FIRST to collect them, then STOP and wait for the answer.\\n' +\n '- Any question whose answer is a choice among a few concrete options belongs in clarifyIntent, even just ' +\n 'two or three; offer your best concrete suggestions as its options.\\n' +\n '- After calling clarifyIntent, present the questions as text in your reply: one short line of context, then a ' +\n 'numbered markdown list of each question with its options (e.g. \"1. How are people getting there? — Walking / ' +\n 'Driving / Public transport\"), then STOP and wait. This lets the user read and answer even when the host does ' +\n \"not render a form. (Echoing the tool's own returned questions is presenting a result, not narrating — do it.)\\n\" +\n '- One call can carry several related questions. Fall back to a single prose question ONLY when the answer is ' +\n 'genuinely free-form with no suggestable options.';\n\n/** Default classifier hint for the clarifyIntent tool. */\nexport const clarifyIntentClassificationPrompt =\n 'The request is under-specified or ambiguous and needs the user to choose among discrete options before the ' +\n 'task can proceed; renders the questions as an in-chat survey form.';\n\n/** Default usage examples for the clarifyIntent tool. */\nexport const clarifyIntentExamples = [\n 'clarifyIntent({ intro: \"A couple of details first.\", questions: [{ id: \"travelMode\", question: \"How are people getting there?\", options: [\"Walking\", \"Driving\", \"Public transport\"] }] })',\n 'clarifyIntent({ questions: [{ id: \"priorities\", question: \"What matters most?\", options: [\"Speed\", \"Cost\", \"Comfort\"], multiSelect: true }] })',\n];\n\n/**\n * Default natural-language prompts (shown by the help tool) for the clarifyIntent tool. The first is\n * a concrete maps prompt; the second is intentionally generic (it has implied, unstated options).\n */\nexport const clarifyIntentExamplePrompts = ['Plan a route for me', 'Help me decide between these options'];\n\n/** Execute function for clarifyIntent — usable with ToolEntry format. Echoes the intro + questions. */\nexport const executeClarifyIntent = async (\n params: z.infer<typeof clarifyIntentSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof clarifyIntentOutputSchema>> => ({\n status: 'awaiting_user_response',\n intro: params.intro,\n questions: params.questions,\n});\n\n/** Per-call overrides accepted by {@link createClarifyIntentTool}. */\nexport type ClarifyIntentToolOptions = {\n /** Replace the default tool description (e.g. add domain-specific guidance). */\n description?: string;\n /** Replace the default classifier hint. */\n classificationPrompt?: string;\n /** Replace the default usage examples. */\n examples?: string[];\n /** Replace the default natural-language example prompts. */\n examplePrompts?: string[];\n /**\n * Keep the tool active on every step regardless of the classifier (so the agent can always ask\n * to clarify, even mid-task). Defaults to `true`; set `false` to leave activation to the classifier.\n */\n alwaysActive?: boolean;\n /**\n * Does your host render the clarifyIntent form? `true` → silent form contract; `false` (default) →\n * the agent narrates the questions as prose so they reach a form-less UI.\n */\n rendersForm?: boolean;\n};\n\n/**\n * Build a clarifyIntent {@link ToolEntry}, optionally overriding its description and examples for a\n * specific domain. The schema and `execute` are fixed; only the classifier-facing metadata is\n * customizable. Pass the result to `createMapAgent`'s `tools` option to replace the built-in default:\n *\n * ```typescript\n * createMapAgent(map, {\n * model,\n * tools: {\n * clarifyIntent: createClarifyIntentTool({\n * description: 'Ask before running a site analysis — surface demand anchors, competitors, scoring weights…',\n * examplePrompts: ['Profile this site for me', 'Rank these addresses'],\n * }),\n * },\n * });\n * ```\n */\nexport const createClarifyIntentTool = (options: ClarifyIntentToolOptions = {}): ToolEntry => ({\n // rendersForm picks the usage contract; clarifyIntent is always active, so its description is always in context.\n description: `${options.description ?? clarifyIntentDescription}\\n\\n${options.rendersForm ? CLARIFY_FORM_PROMPT : CLARIFY_PROSE_PROMPT}`,\n classificationPrompt: options.classificationPrompt ?? clarifyIntentClassificationPrompt,\n inputSchema: clarifyIntentSchema,\n outputSchema: clarifyIntentOutputSchema,\n execute: executeClarifyIntent,\n tags: ['utilities'],\n examples: options.examples ?? clarifyIntentExamples,\n examplePrompts: options.examplePrompts ?? clarifyIntentExamplePrompts,\n alwaysActive: options.alwaysActive ?? true,\n // Form mode is a single silent tool call (halt the loop on the call). Prose mode instead needs a\n // trailing step so the model can write the questions as text after the tool returns them, then stop\n // naturally on that no-tool-call step. Either way the \"awaiting user\" signal lives in the result's\n // `status: 'awaiting_user_response'`, not in a separate flag.\n endsTurnOnCall: options.rendersForm ?? false,\n});\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolEntry } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst getCurrentLocationSchema = z.object({});\n\nconst getCurrentLocationOutputSchema = z.union([\n z.object({\n position: z.array(z.number()).min(2).max(2).describe('[longitude, latitude]'),\n accuracy: z.number().describe('Accuracy of the location in meters'),\n timestamp: z.number().optional().describe('Timestamp of the location data'),\n }),\n toolErrorSchema,\n]);\n\nconst getLocationErrorMessage = (error: unknown): string | undefined => {\n if (typeof error !== 'object' || error === null || !('code' in error)) {\n return undefined;\n }\n\n const code = (error as { code: number }).code;\n\n if (code === 1) {\n return 'Location permission denied by the user';\n }\n\n if (code === 2) {\n return 'Current location is unavailable';\n }\n\n if (code === 3) {\n return 'Timed out while trying to get current location';\n }\n\n return undefined;\n};\n\n// Three decimals of a degree: about 111 m of latitude, plenty for \"near me\".\nconst COARSE_DECIMALS = 3;\nconst COARSE_ACCURACY_METERS = 111;\nconst COARSE_MAX_AGE_MS = 60_000;\n\nconst roundCoordinate = (value: number): number => Math.round(value * 10 ** COARSE_DECIMALS) / 10 ** COARSE_DECIMALS;\n\nexport type GetCurrentLocationToolOptions = {\n /**\n * Send the exact position, the device's accuracy and the fix's timestamp to the model, from a\n * fresh high-accuracy fix. Defaults to `false`: the position is rounded to 3 decimals (about\n * 100 m) from a fix up to a minute old, with no timestamp.\n */\n fullPrecision?: boolean;\n};\n\nconst getCurrentLocationDescription =\n 'Browser geolocation — the user\\'s physical position [lng, lat] for \"near me\" / \"where I am\" queries (may prompt for permission). ' +\n 'For map-based reference use getViewport; if permission is denied, fall back to getViewport as the reference point.';\n\nconst getPosition = (fullPrecision: boolean): Promise<GeolocationPosition> =>\n new Promise((resolve, reject) => {\n globalThis.navigator.geolocation.getCurrentPosition(resolve, reject, {\n enableHighAccuracy: fullPrecision,\n timeout: 15000,\n maximumAge: fullPrecision ? 0 : COARSE_MAX_AGE_MS,\n });\n });\n\nconst toResult = ({ coords, timestamp }: GeolocationPosition, fullPrecision: boolean) =>\n fullPrecision\n ? { position: [coords.longitude, coords.latitude], accuracy: coords.accuracy, timestamp }\n : {\n position: [roundCoordinate(coords.longitude), roundCoordinate(coords.latitude)],\n accuracy: Math.max(Math.round(coords.accuracy), COARSE_ACCURACY_METERS),\n };\n\nconst locate = async (fullPrecision: boolean): Promise<z.infer<typeof getCurrentLocationOutputSchema>> => {\n if (globalThis.window === undefined || globalThis.navigator === undefined) {\n return { error: 'Current location is only available in a browser environment' };\n }\n\n if (!('geolocation' in globalThis.navigator)) {\n return { error: 'Geolocation is not supported by this browser' };\n }\n\n try {\n return toResult(await getPosition(fullPrecision), fullPrecision);\n } catch (error) {\n const locationErrorMessage = getLocationErrorMessage(error);\n if (locationErrorMessage) {\n return { error: locationErrorMessage };\n }\n\n return {\n error: `Failed to get current location: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n/**\n * Build a getCurrentLocation {@link ToolEntry}. Pass the result to `createMapAgent`'s `tools` option\n * to replace the built-in default, for example to send the exact position:\n *\n * ```typescript\n * createMapAgent(map, {\n * model,\n * tools: { getCurrentLocation: createGetCurrentLocationTool({ fullPrecision: true }) },\n * });\n * ```\n */\nexport const createGetCurrentLocationTool = (options: GetCurrentLocationToolOptions = {}): ToolEntry => ({\n description: getCurrentLocationDescription,\n classificationPrompt:\n 'Get the user\\'s physical GPS location from the browser; use for \"near me\" queries, not map center.',\n inputSchema: getCurrentLocationSchema,\n outputSchema: getCurrentLocationOutputSchema,\n execute: () => locate(options.fullPrecision ?? false),\n tags: ['utilities', 'location'],\n examples: ['getCurrentLocation()'],\n examplePrompts: ['Where am I?', 'Get my GPS location', 'Use my current position'],\n relatedTools: ['discoverPlaces', 'reverseGeocode', 'locatePlace'],\n});\n","/**\n * @module agent-toolkit-tools\n */\n\n/**\n * All valid tool tags for categorizing tools.\n *\n * @ignore\n */\nexport const TOOL_TAGS = [\n 'location',\n 'route',\n 'waypoint',\n 'place',\n 'locate',\n 'discover',\n 'traffic',\n 'map style',\n 'utilities',\n 'search',\n 'detour',\n 'along-route',\n 'reachable-range',\n 'isochrone',\n 'range',\n 'EV',\n 'coverage',\n 'recall',\n 'state',\n // Route section types (from SDK sectionTypes)\n 'carpool',\n 'carTrain',\n 'country',\n 'ferry',\n 'importantRoadStretch',\n 'lanes',\n 'leg',\n 'lowEmissionZone',\n 'motorway',\n 'pedestrian',\n 'roadShields',\n 'speedLimit',\n 'toll',\n 'tollVignette',\n 'tunnel',\n 'unpaved',\n 'urban',\n 'vehicleRestricted',\n] as const;\n\n/**\n * Union of all valid tool tags, derived from {@link TOOL_TAGS}.\n *\n * @ignore\n */\nexport type ToolTag = (typeof TOOL_TAGS)[number];\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Tool } from 'ai';\nimport { z } from 'zod';\nimport type { ToolMetadata } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { TOOL_TAGS, type ToolTag } from '../tool-tags';\n\nconst detailToolSchema = z.object({\n name: z.string(),\n description: z.string(),\n examplePrompts: z.array(z.string()),\n relatedTools: z.array(z.string()).optional(),\n});\n\nexport const helpOutputSchema = z.union([\n z.object({\n mode: z.literal('summary'),\n examplePrompts: z.array(z.string()),\n }),\n z.object({\n mode: z.literal('detail'),\n query: z.string().optional(),\n tag: z.enum(TOOL_TAGS).optional(),\n matchedTools: z.array(detailToolSchema),\n totalMatched: z.number(),\n }),\n toolErrorSchema,\n]);\n\nexport const helpSchema = z.object({\n mode: z\n .enum(['summary', 'detail'])\n .describe(\n '\"summary\": overview of all capabilities with example prompts mixed from all tools. ' +\n '\"detail\": searchable full tool info — use when you need to find a specific tool.',\n ),\n query: z\n .string()\n .optional()\n .describe('Detail mode only. Case-insensitive keyword search across tool names, descriptions, and tags.'),\n tag: z.enum(TOOL_TAGS).optional().describe('Detail mode only. Filter results to tools with a specific tag.'),\n});\n\nexport const helpDescription =\n 'List capabilities. `mode: \"summary\"` returns mixed example prompts; `mode: \"detail\"` returns searchable per-tool descriptions and examples. Call when the user asks what the agent can do, or to find the right tool.';\n\n/**\n * Create the help tool.\n *\n * @param getToolsMetadata - Lazy getter for resolved tool metadata.\n * Called at execution time, not at creation time, so metadata can be\n * populated after all tools are set up.\n */\nexport const createHelpTool = (getToolsMetadata: () => Record<string, ToolMetadata>): Tool =>\n ({\n description: helpDescription,\n inputSchema: helpSchema,\n outputSchema: helpOutputSchema,\n execute: async (params: z.infer<typeof helpSchema>): Promise<z.infer<typeof helpOutputSchema>> => {\n const { mode, query, tag } = params;\n\n try {\n const allTools = Object.values(getToolsMetadata());\n\n if (mode === 'summary') {\n const examplePrompts = allTools.flatMap((toolMetadata) => toolMetadata.examplePrompts ?? []);\n return { mode: 'summary', examplePrompts };\n }\n\n // detail mode\n let tools = allTools;\n\n if (tag) {\n tools = tools.filter(\n (toolMetadata) => (toolMetadata.tags as readonly ToolTag[] | undefined)?.includes(tag) ?? false,\n );\n }\n\n if (query) {\n const lowerQuery = query.toLowerCase();\n tools = tools.filter(\n (toolMetadata) =>\n toolMetadata.name.toLowerCase().includes(lowerQuery) ||\n toolMetadata.description.toLowerCase().includes(lowerQuery) ||\n (toolMetadata.tags ?? []).some((toolTag) => toolTag.toLowerCase().includes(lowerQuery)) ||\n (toolMetadata.examplePrompts ?? []).some((prompt) =>\n prompt.toLowerCase().includes(lowerQuery),\n ),\n );\n }\n\n return {\n mode: 'detail',\n query,\n tag,\n matchedTools: tools.map((toolMetadata) => ({\n name: toolMetadata.name,\n description: toolMetadata.description,\n examplePrompts: toolMetadata.examplePrompts ?? [],\n relatedTools: toolMetadata.relatedTools ?? [],\n })),\n totalMatched: tools.length,\n };\n } catch (error) {\n return {\n error: `Failed to load help: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n },\n }) as Tool;\n","import type { Tool, ToolCallOptions } from 'ai';\nimport type { z } from 'zod';\nimport { createHelpTool } from './tools/utilities';\nimport type {\n EntryDataKind,\n OnToolExecute,\n ToolBuildOptions,\n ToolDefinition,\n ToolEntry,\n ToolMetadata,\n ToolState,\n} from './types';\n\nconst errorFromOutput = (output: unknown): string | undefined => {\n const error = (output as { error?: unknown } | undefined)?.error;\n return typeof error === 'string' ? error : undefined;\n};\n\n/**\n * Converts a ToolEntry to an AI SDK Tool, binding state to execute. When\n * `onToolExecute` is supplied, the executor is wrapped to report wall-clock\n * duration and failure (thrown, or the standardized `{ error }` return) after\n * each call. Without it, the plain executor is used (zero overhead).\n *\n * The AI SDK's per-call `abortSignal` is forwarded to `execute` as `options.signal`,\n * so a cancelled turn can cancel the tool's in-flight service requests.\n */\nconst toAiTool = <S extends ToolState>(\n name: string,\n entry: ToolEntry<S>,\n state: S,\n includeOutputSchema: boolean,\n onToolExecute?: OnToolExecute,\n): Tool =>\n ({\n description: entry.description,\n inputSchema: entry.inputSchema,\n outputSchema: includeOutputSchema ? entry.outputSchema : undefined,\n execute: onToolExecute\n ? async (input: any, options?: ToolCallOptions) => {\n const start = Date.now();\n try {\n const output = await entry.execute(input, state, { signal: options?.abortSignal });\n const errorMessage = errorFromOutput(output);\n onToolExecute({\n toolName: name,\n durationMs: Date.now() - start,\n isError: errorMessage !== undefined,\n errorMessage,\n });\n return output;\n } catch (error) {\n onToolExecute({\n toolName: name,\n durationMs: Date.now() - start,\n isError: true,\n errorMessage: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n : (input: any, options?: ToolCallOptions) => entry.execute(input, state, { signal: options?.abortSignal }),\n }) as Tool;\n\n/** Extracts ToolMetadata from a ToolEntry, adding the name. */\nconst toMetadata = <S extends ToolState>(name: string, entry: ToolEntry<S>): ToolMetadata => ({\n name,\n description: entry.description,\n classificationPrompt: entry.classificationPrompt,\n tags: entry.tags,\n examples: entry.examples,\n examplePrompts: entry.examplePrompts,\n relatedTools: entry.relatedTools,\n dependsOn: entry.dependsOn,\n alwaysActive: entry.alwaysActive,\n endsTurnOnCall: entry.endsTurnOnCall,\n scopePrompt: entry.scopePrompt,\n});\n\n// Materializes a builder entry with the given build options, or returns a static entry as-is.\nconst materializeEntry = <S extends ToolState>(\n entry: ToolDefinition<S>,\n buildOptions: ToolBuildOptions,\n): ToolEntry<S> => (typeof entry === 'function' ? entry(buildOptions) : entry);\n\n/**\n * Registry entry for a scopable tool — captured at setup time so {@link prepareStep}\n * can rebuild the tool's description + inputSchema per turn from the classifier's\n * `toolScopes` output.\n *\n * @ignore\n */\nexport type ScopableToolInfo = {\n /**\n * Validate `rawScope` against the tool's scopeSchema and re-invoke its builder.\n * Returns `null` when either the parse or the rebuild fails (fail-open — callers\n * leave the default surface in place rather than crashing the step). The original\n * builder, scope schema, and any other build options are captured in the closure\n * so callers stay decoupled from the underlying validation/build mechanics.\n */\n applyScope: (rawScope: unknown) => { description: string; inputSchema: z.ZodType } | null;\n /** Default (full-surface) values, used to restore between turns. */\n defaultDescription: string;\n defaultInputSchema: z.ZodType;\n};\n\n/**\n * Converts a composed tool record into AI SDK tools and metadata.\n *\n * Each input value may be a {@link ToolEntry} or a {@link ToolEntryBuilder};\n * builders are materialized with {@link ToolBuildOptions} built from\n * `featureFlags` (and any future build-time config) so each tool can decide\n * how its description, schema, or executor are shaped per agent instance.\n *\n * When a builder produces a {@link ToolEntry} carrying a `scopeSchema`, the\n * builder reference is retained in `scopableTools` so {@link prepareStep} can\n * re-invoke it with a per-turn scope and mutate the live tool object.\n *\n * Rebinds the help tool with resolved metadata.\n *\n * @ignore\n */\nexport const setupTools = <S extends ToolState>(\n toolEntries: Record<string, ToolDefinition<S>>,\n state: S,\n options?: {\n outputSchemas?: boolean;\n featureFlags?: ToolBuildOptions['featureFlags'];\n enabledDataKinds?: readonly EntryDataKind[];\n onToolExecute?: OnToolExecute;\n },\n): {\n tools: Record<string, Tool>;\n toolsMetadata: Record<string, ToolMetadata>;\n scopableTools: Record<string, ScopableToolInfo>;\n} => {\n const includeOutputSchema = options?.outputSchemas !== false;\n const buildOptions: ToolBuildOptions = {\n featureFlags: options?.featureFlags,\n enabledDataKinds: options?.enabledDataKinds,\n };\n const tools: Record<string, Tool> = {};\n const toolsMetadata: Record<string, ToolMetadata> = {};\n const scopableTools: Record<string, ScopableToolInfo> = {};\n\n for (const [name, entryOrBuilder] of Object.entries(toolEntries)) {\n const entry = materializeEntry(entryOrBuilder, buildOptions);\n tools[name] = toAiTool(name, entry, state, includeOutputSchema, options?.onToolExecute);\n toolsMetadata[name] = toMetadata(name, entry);\n\n // Builder + scopeSchema is the precondition for per-turn rebuilding. A static\n // ToolEntry with a scopeSchema isn't rebuildable because there's no factory to\n // re-invoke; we silently treat it as a non-scopable tool to keep the contract simple.\n if (typeof entryOrBuilder === 'function' && entry.scopeSchema) {\n const builder = entryOrBuilder;\n const scopeSchema = entry.scopeSchema;\n scopableTools[name] = {\n applyScope: (rawScope) => {\n const parsed = scopeSchema.safeParse(rawScope);\n if (!parsed.success) return null;\n try {\n const rebuilt = builder({ ...buildOptions, scope: parsed.data });\n return { description: rebuilt.description, inputSchema: rebuilt.inputSchema };\n } catch {\n return null;\n }\n },\n defaultDescription: entry.description,\n defaultInputSchema: entry.inputSchema,\n };\n }\n }\n\n // Rebind help tool with resolved metadata\n if (tools.help) {\n tools.help = createHelpTool(() => toolsMetadata);\n }\n\n if (Object.keys(tools).length === 0) {\n throw new Error('MapAgent requires at least one tool.');\n }\n\n return { tools, toolsMetadata, scopableTools };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Tagged identifier for the unified geometries namespace. Every polygon\n * source the toolkit knows about — place footprints, isochrone polygons,\n * derived custom-geometries entries — is referenced through a single\n * `{ kind, id }` object so consumers don't have to disambiguate by\n * parameter name (or guess where an id comes from).\n */\n\nimport { z } from 'zod';\n\n/**\n * Possible kinds in a geometries id.\n *\n * @group Agent Toolkit\n */\nexport type GeometriesIdKind = 'place' | 'places' | 'ranges' | 'customGeometries';\n\n/**\n * Tagged geometries id. The Zod schema is exposed so tools can plug it\n * straight into their input shape (`z.array(geometriesIdSchema).min(1)`)\n * — no string parsing on the way in.\n *\n * @group Agent Toolkit\n */\nexport const geometriesIdSchema = z\n .discriminatedUnion('kind', [\n z\n .object({\n kind: z.literal('place').describe('Use the exact literal \"place\".'),\n id: z.string().min(1).describe('Id of a single place; resolves to that place’s footprint.'),\n })\n .describe('A single place footprint.'),\n z\n .object({\n kind: z.literal('places').describe('Use the exact literal \"places\".'),\n id: z.string().min(1).describe('Id of a places-entry; resolves to every footprint in the entry.'),\n })\n .describe('Every footprint in a places entry.'),\n z\n .object({\n kind: z.literal('ranges').describe('Use the exact literal \"ranges\".'),\n id: z\n .string()\n .min(1)\n .describe('Id of a ranges-entry; resolves to every isochrone polygon in the entry.'),\n })\n .describe('Every isochrone polygon in a ranges entry.'),\n z\n .object({\n kind: z.literal('customGeometries').describe('Use the exact literal \"customGeometries\".'),\n id: z\n .string()\n .min(1)\n .describe('Id of a custom-geometries entry produced by a previous `processData` call.'),\n })\n .describe('A derived entry produced by a previous `processData` call.'),\n ])\n .describe(\n 'Tagged id `{ kind, id }`. `kind` must be exactly one of: \"place\" | \"places\" | \"ranges\" | \"customGeometries\" ' +\n '(do NOT use variants like \"ranges-entry\", \"place-footprints\", etc.). ' +\n 'Use `recallState` to list every available `{kind, id}` pair.',\n );\n\n/**\n * Tagged id for any polygon source in session state.\n *\n * @group Agent Toolkit\n */\nexport type GeometriesId = z.infer<typeof geometriesIdSchema>;\n\n/**\n * Shared `geometriesEntryIDs` input schema for tools that read polygons from\n * session state (`processData`, `analyseData`). One canonical\n * description so the two tools stay aligned and the model sees the same\n * contract everywhere.\n *\n * @ignore\n */\nexport const geometriesEntryIDsSchema = z\n .array(geometriesIdSchema)\n .min(1)\n .describe(\n 'Tagged ids `{ kind, id }` of polygon sources to feed the run. `kind` must be exactly one of ' +\n '`\"place\"` | `\"places\"` | `\"ranges\"` | `\"customGeometries\"` (no variants). Mix kinds freely (e.g. union ' +\n 'place footprints with isochrones). Use `recallState` to list available ids.',\n );\n\n/**\n * One-liner describing the per-source variability of `geometry.properties`.\n * Shared between `processData` and `analyseData` `code` prompts.\n *\n * @ignore\n */\nexport const GEOMETRIES_PROPS_DOC =\n '`geometry.properties` shape varies by source — place footprints: `CommonPlaceProps`; ranges: ' +\n 'service response (`range.type`, `range.value`); custom: whatever the producing op set. Treat as ' +\n 'opaque unless you know the source. ' +\n 'Every feature also carries `properties._source: { kind, id }` (the tagged id it was passed in ' +\n 'under) — use it to filter / partition mixed inputs by role inside the sandbox.';\n\n/**\n * Shared description for `skipped` arrays in tool outputs.\n *\n * @ignore\n */\nexport const GEOMETRIES_SKIPPED_DESC =\n 'Source ids that could not contribute (unknown id, no geometry data source, fetch failure, or empty entry).';\n\n/**\n * Shared description for `sourceIds` arrays in tool outputs.\n *\n * @ignore\n */\nexport const GEOMETRIES_SOURCE_IDS_DESC =\n 'Tagged input ids that contributed at least one polygon to the run, in input order.';\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema doc for the geometry polygons consumed/produced by sandboxed\n * code in `processData` / `analyseData` — injected per source as\n * `geometriesByEntry[key]`. Each item is a\n * `PolygonFeature<CommonPlaceProps>`; only the non-GeoJSON-standard bits are\n * spelled out here.\n */\n\n/** @ignore */\nexport const GEOMETRIES_SCHEMA_DOC =\n 'Each `geometriesByEntry[key]` (key = `${kind}:${id}`) is an array of Polygon/MultiPolygon Features. Each has `id` (the geometry-data-source id, ' +\n 'matches `place.properties.dataSources.geometry.id`) and `bbox`. `properties` varies by source — see ' +\n '`GEOMETRIES_PROPS_DOC`. Use `turf.area`, `turf.union`, `turf.intersect`, `turf.bbox`, `turf.centroid`, ' +\n '`turf.buffer`, … directly on these features. For h3, convert via `h3.polygonToCells(feature.geometry.coordinates, res)`.';\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `TrafficIncident` shape, reused by every tool whose\n * LLM-authored code reads the injected `incidentsByEntry[id]` arrays (`analyseData`, `processData`,\n * `clusterIncidents`). Without this the model invents fields (e.g. `reportedDelayInSeconds`) or\n * forgets the `properties` nesting — every documented field below is read off `feature.properties`.\n */\n\n/** @ignore */\nexport const INCIDENTS_SCHEMA_DOC =\n 'Each `incidentsByEntry[id]` is a flat array of TrafficIncident Features (geometry is `Point` OR `LineString` — guard ' +\n 'with `feature.geometry.type` before line-only ops). All fields below live on `feature.properties`:\\n' +\n '```ts\\n' +\n 'type TrafficIncidentProperties = {\\n' +\n ' id: string;\\n' +\n ' category: TrafficIncidentCategory; // \"jam\" | \"accident\" | \"roadworks\" | \"road-closed\" | \"lane-closed\" | \"broken-down-vehicle\" | \"danger\" | \"flooding\" | \"fog\" | \"frost\" | \"rain\" | \"wind\" | \"other\"\\n' +\n ' magnitudeOfDelay: \"unknown\" | \"minor\" | \"moderate\" | \"major\" | \"undefined\"; // severity\\n' +\n ' delayInSeconds?: number; // delay caused by the incident (NOT every incident has one — guard with ?? 0)\\n' +\n ' lengthInMeters?: number; // affected road length\\n' +\n ' roadNumbers?: string[]; // e.g. [\"M25\", \"A1\"]\\n' +\n ' from?: string; to?: string; // human-readable start / end of the affected stretch\\n' +\n ' timeValidity: \"present\" | \"future\";\\n' +\n ' events: Array<{ description: string; code?: number; iconCategory?: number }>;\\n' +\n ' startTime?: Date; endTime?: Date; lastReportTime?: Date;\\n' +\n ' probabilityOfOccurrence?: string; numberOfReports?: number;\\n' +\n '};\\n' +\n '```\\n' +\n 'Read everything off `properties` — there is NO `reportedDelayInSeconds` / top-level delay field ' +\n '(e.g. `properties.delayInSeconds`, guard `?? 0`).';\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `Place` properties shape, reused by\n * every tool whose LLM-authored code reads or returns a Places\n * FeatureCollection (`processData`, `analyseData`) — injected per entry as\n * `placesByEntry[id]`. Only the non-obvious\n * (non-GeoJSON-standard) bits are spelled out — the wrapping `Feature` /\n * `FeatureCollection` shape is assumed known.\n */\n\nimport type { FeatureFlags } from '../../types';\n\n/**\n * Build the Place properties schema doc embedded in tool descriptions for\n * sandboxed code. `Places` is a FeatureCollection of Point Features; only the\n * `properties` shape (the TomTom-specific bit) is documented here.\n *\n * @remarks\n * Takes {@link FeatureFlags} so a future flag can vary the doc; no flag\n * currently does, so every caller gets the same text.\n *\n * @ignore\n */\nexport const buildPlacesSchemaDoc = (_flags: FeatureFlags): string =>\n 'Each `placesByEntry[id]` is a FeatureCollection of Point Features. `Place.properties` shape:\\n' +\n '```ts\\n' +\n 'type PlaceProperties = {\\n' +\n ' type: \"POI\" | \"Street\" | \"Geography\" | \"Point Address\" | \"Address Range\" | \"Cross Street\";\\n' +\n ' address: {\\n' +\n ' freeformAddress: string;\\n' +\n ' streetNumber?: string; streetName?: string;\\n' +\n ' municipality?: string; municipalitySubdivision?: string;\\n' +\n ' countrySubdivision?: string; countrySubdivisionName?: string;\\n' +\n ' countrySecondarySubdivision?: string; countryTertiarySubdivision?: string;\\n' +\n ' postalCode?: string; extendedPostalCode?: string;\\n' +\n ' countryCode?: string; countryCodeISO3?: string; country?: string;\\n' +\n ' localName?: string;\\n' +\n ' };\\n' +\n ' poi?: { // when type === \"POI\"\\n' +\n ' name: string; phone?: string; url?: string;\\n' +\n ' brands?: string[];\\n' +\n ' categories: string[]; // POICategory enum, e.g. \"RESTAURANT\", \"CAFE_PUB\", \"FUEL_STATION\", \"HOTEL_OR_MOTEL\", \"CHARGING_LOCATION\"\\n' +\n ' localizedCategories: string[]; // language-sensitive\\n' +\n ' openingHours?: { mode: string; timeRanges: Array<{ startTime: {date,hour,minute}, endTime: {date,hour,minute} }>; alwaysOpenThisPeriod: boolean };\\n' +\n ' timeZone?: { name: string };\\n' +\n ' };\\n' +\n ' // Search hits also carry:\\n' +\n ' score?: number; // relevance (higher = better)\\n' +\n ' distance?: number; // meters from geoBias, when provided\\n' +\n ' info?: string;\\n' +\n ' // Geography hits also carry:\\n' +\n ' geographyType?: Array<\"Country\"|\"CountrySubdivision\"|\"CountrySecondarySubdivision\"|\"CountryTertiarySubdivision\"|\"Municipality\"|\"MunicipalitySubdivision\"|\"Neighbourhood\"|\"PostalCodeArea\">;\\n' +\n ' // Optional: mapcodes, entryPoints, addressRanges, relatedPois, chargingPark, dataSources.\\n' +\n '};\\n' +\n '```\\n' +\n 'Areas (Street, Geography, …) also carry `feature.bbox`. Coordinates are `[lng, lat]`; guard `?` fields with `?.` / `??`. ' +\n 'The fields above describe places you READ from input. When your output is a filter / subset / merge of the input places, ' +\n 'return the source features as-is (spread them: `{ ...place }`, or `{ ...place, properties: { ...place.properties } }` when editing) ' +\n 'so their `id` and all their fields ride through — carrying real fields forward is NOT the same as inventing them, keep them. ' +\n '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 ' +\n 'for that type (`poi.name` for POIs, `address.freeformAddress` for streets / intersections (`Cross Street`) / addresses), and Point ' +\n 'coordinates required. For those computed points do NOT invent a flat `properties.name` (it is dropped, so the name would be lost), and ' +\n 'do NOT fabricate the search / POI extras (`categories`, `localizedCategories`, `score`, `distance`, `info`, …) — a computed point has no ' +\n 'such data behind it.';\n\n/**\n * Places schema doc — preserved as a named constant so non-flag-aware tools\n * keep working.\n *\n * @ignore\n */\nexport const PLACES_SCHEMA_DOC = buildPlacesSchemaDoc({});\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `Route` properties shape, reused by\n * every tool whose LLM-authored code reads a Routes FeatureCollection\n * (`analyseData`, `processData`) — injected per entry as `routesByEntry[id]`.\n * Only the non-GeoJSON-standard bits are spelled out — `Route` is a LineString Feature.\n */\n\n/** @ignore */\nexport const ROUTES_SCHEMA_DOC =\n 'Each `routesByEntry[id]` is a FeatureCollection of LineString Features (each route has `feature.bbox`). `Route.properties` shape:\\n' +\n '```ts\\n' +\n 'type RouteProperties = {\\n' +\n ' index: number; // 0 = main route, 1+ = alternatives\\n' +\n ' summary: {\\n' +\n ' lengthInMeters: number;\\n' +\n ' travelTimeInSeconds: number; // includes traffic delay\\n' +\n ' noTrafficTravelTimeInSeconds?: number;\\n' +\n ' trafficDelayInSeconds: number;\\n' +\n ' trafficLengthInMeters: number;\\n' +\n ' departureTime: Date; arrivalTime: Date;\\n' +\n ' // EV-only:\\n' +\n ' batteryConsumptionInkWh?: number; batteryConsumptionInPCT?: number;\\n' +\n ' remainingChargeAtArrivalInkWh?: number; remainingChargeAtArrivalInPCT?: number;\\n' +\n ' totalChargingTimeInSeconds?: number;\\n' +\n ' // Combustion-only:\\n' +\n ' fuelConsumptionInLiters?: number;\\n' +\n ' };\\n' +\n ' // Segments grouped by characteristic. Every section has { id, startPointIndex, endPointIndex }\\n' +\n ' // unless noted; *Index values reference `geometry.coordinates`.\\n' +\n ' sections: {\\n' +\n ' leg: Array<{ id; startPointIndex?; endPointIndex?; summary: RouteSummary; originalWaypointIndex?: number }>; // ALWAYS present\\n' +\n ' // originalWaypointIndex = which requested stop the leg arrives at; absent on the last leg and on service-inserted EV charging stops\\n' +\n ' country?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>; // ISO 3166-1 alpha-3\\n' +\n ' tollVignette?: Array<{ id; startPointIndex; endPointIndex; countryCodeISO3: string }>;\\n' +\n ' traffic?: Array<{\\n' +\n ' id; startPointIndex; endPointIndex;\\n' +\n ' categories: TrafficIncidentCategory[]; // e.g. \"Jam\",\"Accident\",\"RoadWorks\",\"Closure\"\\n' +\n ' magnitudeOfDelay: \"Unknown\"|\"Minor\"|\"Moderate\"|\"Major\"|\"Undefined\";\\n' +\n ' tec: { effectCode?: number; causes?: Array<{ mainCauseCode: number; subCauseCode?: number }> };\\n' +\n ' effectiveSpeedInKmh?: number;\\n' +\n ' delayInSeconds?: number;\\n' +\n ' eventId?: string; // join key to the traffic incident details service\\n' +\n ' }>;\\n' +\n ' importantRoadStretch?: Array<{ id; startPointIndex; endPointIndex; index: number; streetName?: string; roadNumbers?: string[] }>;\\n' +\n ' lanes?: Array<{\\n' +\n ' id; startPointIndex; endPointIndex;\\n' +\n ' lanes: Array<{ directions: PossibleLaneDirection[]; follow?: PossibleLaneDirection }>;\\n' +\n ' laneSeparators: PossibleLaneSeparator[];\\n' +\n ' properties?: string[]; // e.g. \"IS_MANEUVER\"\\n' +\n ' }>;\\n' +\n ' speedLimit?: Array<{ id; startPointIndex; endPointIndex; maxSpeedLimitInKmh: number }>;\\n' +\n ' roadShields?: Array<{ id; startPointIndex; endPointIndex; roadShieldReferences: RoadShieldReference[] }>;\\n' +\n ' // Plain section ranges (id + startPointIndex + endPointIndex only):\\n' +\n ' ferry?: SectionProps[]; motorway?: SectionProps[]; toll?: SectionProps[]; tunnel?: SectionProps[];\\n' +\n ' tollRoad?: SectionProps[]; // costs money by ANY scheme: every `toll` stretch, plus vignette motorways and city charge zones\\n' +\n ' unpaved?: SectionProps[]; urban?: SectionProps[]; pedestrian?: SectionProps[];\\n' +\n ' carpool?: SectionProps[]; carTrain?: SectionProps[];\\n' +\n ' lowEmissionZone?: SectionProps[]; vehicleRestricted?: SectionProps[];\\n' +\n ' };\\n' +\n ' progress?: Array<{ pointIndex: number; travelTimeInSeconds?: number; distanceInMeters?: number }>; // cumulative milestones\\n' +\n ' guidance?: { instructions: Array<{\\n' +\n ' pathPointIndex: number; maneuver: string;\\n' +\n ' message?: string; // ready-made localised text (\"Turn right onto Damrak\") — prefer it over mapping `maneuver` yourself\\n' +\n ' nextRoadInfo: { streetName?: { text: string }; countryCode?: string };\\n' +\n ' roundaboutType?: \"REGULAR\"|\"SMALL\";\\n' +\n ' maneuverView?: { onRouteAngle?: ManeuverAngle; offRouteAngles: ManeuverAngle[] }; // relative directions (\"RIGHT\",\"SLIGHT_LEFT\",\"BACK\"), not degrees\\n' +\n ' sideRoads?: Array<{ side: \"LEFT\"|\"RIGHT\"|\"LEFT_AND_RIGHT\"; offsetFromManeuverInMeters: number; isDrivable?: boolean }>;\\n' +\n ' }> };\\n' +\n '};\\n' +\n '```\\n' +\n '`pathPointIndex` and `pointIndex` values reference positions in `geometry.coordinates`. Guard `?` fields with `?.` / `??`. ' +\n 'For \"how long / how far\" questions, use `summary.lengthInMeters` / `summary.travelTimeInSeconds`.';\n","/**\n * @module agent-toolkit-tools\n *\n * Shared input schema building blocks reused across multiple tools.\n */\n\nimport { geometryFillStyles } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport { placesMarkerTypes } from '../../state';\n\n// Range guards for model-supplied coordinates. Written as refinements rather than\n// per-element `.min()/.max()` because one position array mixes two different valid\n// ranges (longitude ±180, latitude ±90) that a single element schema can't express.\n// Number.isFinite also rejects Infinity, which `z.number()` alone lets through.\nconst isLongitude = (value: number) => Number.isFinite(value) && value >= -180 && value <= 180;\nconst isLatitude = (value: number) => Number.isFinite(value) && value >= -90 && value <= 90;\n\nconst POSITION_RANGE_MESSAGE = 'Position must be [lng, lat] with lng in [-180, 180] and lat in [-90, 90].';\n\n/**\n * A `[lng, lat]` position with both components range-checked. Use for every\n * model-supplied point input instead of a bare `z.array(z.number()).length(2)`.\n *\n * @ignore\n */\nexport const positionSchema = z\n .array(z.number())\n .length(2)\n .refine(([lng, lat]) => isLongitude(lng) && isLatitude(lat), { message: POSITION_RANGE_MESSAGE });\n\n// A GeoJSON position inside a geometry, where a third altitude component is legal\n// (RFC 7946 §3.1.1). Only lng/lat are range-checked; altitude is left unconstrained.\nconst geoJsonPositionSchema = z\n .array(z.number())\n .min(2)\n .max(3)\n .refine(([lng, lat]) => isLongitude(lng) && isLatitude(lat), { message: POSITION_RANGE_MESSAGE });\n\n/** @ignore */\nexport const geoJsonBBoxSchema = z\n .array(z.number())\n .length(4)\n .refine(\n ([west, south, east, north]) =>\n isLongitude(west) && isLatitude(south) && isLongitude(east) && isLatitude(north),\n { message: 'BBox values out of range — lng must be in [-180, 180] and lat in [-90, 90].' },\n )\n // Latitude must be ordered, but longitude must NOT be: a bbox crossing the\n // antimeridian legitimately has minLng > maxLng (e.g. [170, -10, -170, 10]).\n .refine(([, south, , north]) => south <= north, { message: 'BBox minLat must be <= maxLat.' })\n .describe('[minLng, minLat, maxLng, maxLat] or [W,S,E,N]. lng in [-180, 180], lat in [-90, 90].');\n\n/** @ignore */\nexport const polygonInputSchema = z.object({\n type: z.literal('Polygon'),\n coordinates: z.array(z.array(geoJsonPositionSchema)),\n});\n\n/** @ignore */\nexport const multiPolygonInputSchema = z.object({\n type: z.literal('MultiPolygon'),\n coordinates: z.array(z.array(z.array(geoJsonPositionSchema))),\n});\n\n/** @ignore */\nexport const geometryInputSchema = z.union([polygonInputSchema, multiPolygonInputSchema]);\n\n/**\n * Shared factory for the optional `placesEntryIDs` input field — one or more\n * existing places-entry IDs that a tool should operate on. Each caller supplies\n * a `verb` (filter/analyse/display/…) and optional follow-up text so the LLM\n * sees a description tailored to that tool.\n *\n * @ignore\n */\nexport const placesEntryIDsSchema = ({ verb, extra }: { verb: string; extra?: string }) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing places entries to ${verb} (e.g. [\"places-2\"], [\"places-1\", \"places-2\"]). ` +\n `Use recallState to list IDs. Omit to ${verb} the latest entry.` +\n (extra ? ` ${extra}` : ''),\n );\n\n/**\n * Shared factory for the optional `routesEntryIDs` input field — one or more existing routes-entry\n * IDs that a tool should operate on. Mirrors `placesEntryIDsSchema` for routes.\n *\n * @ignore\n */\nexport const routesEntryIDsSchema = ({ verb, extra }: { verb: string; extra?: string }) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing routes entries to ${verb} (e.g. [\"routes-0\"], [\"routes-0\", \"routes-1\"]). ` +\n `Use recallState to list IDs. Omit to ${verb} the latest route.` +\n (extra ? ` ${extra}` : ''),\n );\n\n/**\n * Optional semantic id for a new places entry. Encourages the LLM to derive a compact,\n * memorable identifier from the user's prompt so later tool calls can reference the entry\n * without scanning a numeric history. The id is auto-suffixed (`-2`, `-3`, ...) on collision,\n * so the LLM never has to track uniqueness across calls itself.\n *\n * @ignore\n */\nexport const placesEntryIdHintSchema = z\n .string()\n .regex(/^[a-z0-9][a-z0-9-]{1,48}$/, 'Use lowercase letters, digits, and hyphens (2–49 chars).')\n .optional()\n .describe(\n 'Compact lowercase kebab-case id derived from the user prompt ' +\n '(e.g. \"vegan-cafes-amsterdam\", \"ev-stations-along-route\"). ' +\n 'Avoid generic names like \"places\" / \"results\". ' +\n 'Auto-suffixed on collision — do not track uniqueness yourself. Default: `places-N`.',\n );\n\n/**\n * Reusable `mode` field for show-on-map options that render polygons into an entry's\n * own `GeometriesModule`. Controls how the new polygons combine with what is **already\n * rendered on that single entry's layer** — intra-entry only. Cross-entry visibility\n * (hiding *other* entries before this one comes up) is a separate concern handled by\n * `hidePreviousEntries`.\n *\n * @ignore\n */\nexport const showModeSchema = z\n .enum(['add', 'replace'])\n .optional()\n .describe(\n 'WITHIN THE TARGETED ENTRY ONLY — controls how the new polygons combine with ' +\n \"what is already rendered on this entry's GeometriesModule layer. Does NOT \" +\n 'affect other entries (use `hidePreviousEntries` for that). ' +\n '\"replace\" (default) clears this entry\\'s layer first; ' +\n '\"add\" stacks (deduped by feature id).',\n );\n\n/**\n * How rendered polygons are filled — the SDK's `GeometryFillStyle`.\n *\n * @ignore\n */\nexport const geometryFillStyleSchema = z.enum(geometryFillStyles);\n\n/**\n * Shared schema for the geometry-rendering option on tools that produce a places entry\n * (discoverPlaces, locatePlace, ...). When provided, the tool batch-fetches boundary\n * polygons via the Geometry Data service, caches them on the entry, and optionally renders.\n *\n * @ignore\n */\nexport const showPlaceGeometriesSchema = z\n .object({\n show: z.boolean().optional().describe('Also render the polygons on the map. Default: false.'),\n mode: showModeSchema,\n fillStyle: geometryFillStyleSchema.optional().describe('How the polygons are filled. Default: \"outline\".'),\n })\n .optional()\n .describe(\n 'Batch-fetch boundary polygons for the resulting places and cache them on the entry. ' +\n 'Use when the user wants the *area / shape / boundary* (e.g. \"outline this neighbourhood\", \"show the city footprint\"). ' +\n 'Set `show: true` to also render. Skips places without a geometry source (most addresses/streets).',\n );\n\n/**\n * Schema for the geometry-rendering option on the places display tool\n * (`updatePlacesDisplay`). Presence of this object means \"render boundaries\";\n * there is no `show: boolean` toggle — that is reserved for the search-time\n * variant ({@link showPlaceGeometriesSchema}).\n *\n * @ignore\n */\nexport const geometryDisplayConfigSchema = z\n .object({\n fillStyle: geometryFillStyleSchema.optional().describe('How the polygons are filled. Default: \"outline\".'),\n placeIds: z\n .array(z.string())\n .optional()\n .describe(\n 'Subset of place ids within the targeted entries — render boundaries only for these. ' +\n 'Ids outside the entries are ignored. Default: all places with a geometry source.',\n ),\n mode: showModeSchema,\n })\n .describe(\n 'Render boundary polygons for the targeted entries. ' +\n 'Use when the user wants the *area / shape / outline / footprint* (e.g. \"outline this neighbourhood\", \"show the city footprint\"). ' +\n 'Independent of `markerType` — set both to render pins AND boundaries.',\n );\n\n/**\n * Shared marker-type enum. Used as a required field in `showPlacesSchema` (the\n * nested `show` object for discover/filter/search tools) and as an optional\n * top-level field in `showPlaces` itself.\n *\n * @ignore\n */\nexport const markerTypeSchema = z\n .enum([...placesMarkerTypes, 'none'])\n .describe(\n '\"pin\" for few results; \"base-map\" for 10+; ' +\n '\"pin-clustered\" aggregates nearby pins under a count badge (best for dense city-scale POI sets — ' +\n 'clusters expose aggregated `placeTypes` / `poiCategories`); \"none\" for towns/cities/areas.',\n );\n\n/**\n * Shared factory for the optional `hidePreviousEntries` field — controls which previously-shown\n * entries get hidden before the new ones are rendered. `entryNoun` is the slice noun\n * (`'places'`, `'routes'`, `'ranges'`) used inside the description so the LLM sees a slice-specific\n * hint.\n *\n * @ignore\n */\nexport const hidePreviousEntriesSchema = (entryNoun: string) =>\n z\n .union([z.literal('all'), z.array(z.string())])\n .optional()\n .describe(\n `Which previously-shown ${entryNoun} entries to hide before showing the new ones. ` +\n `\\`\"all\"\\` clears every previous entry (use for the typical \"show X\" — replace existing); ` +\n 'an array hides only the listed entry IDs. ' +\n `Omit to stack on top of existing entries (use for \"also show\", \"additionally\", \"and X\"). ` +\n 'The entries being shown by this call are never hidden. ' +\n `Only applied when \\`entryMode === \"multiple\"\\` (in \\`single\\` mode every other entry is auto-hidden, so this prop is ignored).`,\n );\n\n/** @ignore */\nexport const showPlacesSchema = z.object({\n markerType: markerTypeSchema,\n zoomMode: z.enum(['auto', 'none']).describe('Camera behaviour. \"none\" keeps the map still.'),\n hidePreviousEntries: hidePreviousEntriesSchema('places'),\n});\n\n/** @ignore */\nexport const shownSchema = z.object({\n markerType: z.boolean(),\n zoomMode: z.boolean(),\n});\n\n/**\n * BYOD `show` options. Mirrors {@link showPlacesSchema} but WITHOUT `markerType`\n * — BYOD entries render through their own geometry-typed MapLibre layers, so\n * there is no marker style to pick. Presence of this object means \"render now\";\n * omit it to store the entry hidden. `zoomMode` drives the camera and\n * `hidePreviousEntries` controls which already-shown BYOD entries to clear first.\n *\n * @ignore\n */\nexport const showByodSchema = z.object({\n zoomMode: z\n .enum(['auto', 'none'])\n .describe('Camera behaviour. \"auto\" fits the map to the shown BYOD entries; \"none\" keeps the map still.'),\n hidePreviousEntries: hidePreviousEntriesSchema('BYOD'),\n});\n\n/** @ignore */\nexport const shownByodSchema = z.object({\n zoomMode: z.boolean().describe('Whether the camera was moved to fit the shown entries.'),\n});\n\n// --- where schema (geographic scope) ---\n//\n// Discriminated by `mode`. Three modes are shared between locatePlace and discoverPlaces:\n// - within — one area-like input (bbox / viewport / geocoded query / placeId polygon)\n// - nearby — one point-like bias (position / viewport centre / geocoded query centroid)\n// - global — no bias\n// `within` is split between locatePlace (singular fields, defined here) and discoverPlaces\n// (plural fields, defined in discover-places.ts). Only `mode` and `viewport` are shared via\n// {@link withinSharedFields}. discoverPlaces also adds a fourth mode `maxDetour`.\n\n/**\n * Fields shared by both locatePlace's and discoverPlaces' `within` schemas — `mode` and the\n * `viewport` flag. Spread into each consumer's `z.object({...})`.\n * @ignore\n */\nexport const withinSharedFields = {\n mode: z.literal('within'),\n viewport: z\n .literal(true)\n .optional()\n .describe('When true, search within the current map viewport bounds. EXCLUSIVE with all other within fields.'),\n};\n\n/**\n * `queryAs` — what kind of thing the paired `query` string is. Drives whether resolution uses\n * POI search (venues/landmarks/businesses) or geocoding (addresses, cities, neighborhoods, parks,\n * postal codes, etc.). Optional everywhere; defaults to `'place'`.\n * @ignore\n */\nexport const queryAsSchema = z\n .enum(['poi', 'place'])\n .optional()\n .describe(\n 'Drives query resolution. ' +\n '`\"poi\"` for venues / landmarks / businesses (e.g. \"Eiffel Tower\"). ' +\n '`\"place\"` for addresses, cities, neighborhoods, parks, postal codes (e.g. \"De Jordaan, Amsterdam\", \"1600 Pennsylvania Ave\"). ' +\n 'Default: \"place\".',\n );\n\nconst locateWithinSingularKeys = ['boundingBox', 'viewport', 'query', 'placeId'] as const;\n\n/**\n * locatePlace's `within` schema — exactly one of viewport / boundingBox / query / placeId.\n * @ignore\n */\nexport const withinWhereSchema = z\n .object({\n ...withinSharedFields,\n boundingBox: geoJsonBBoxSchema\n .optional()\n .describe('Single bbox [W,S,E,N]. EXCLUSIVE with viewport / query / placeId.'),\n query: z\n .string()\n .optional()\n .describe(\n 'Resolve a name/address/POI → its boundary polygon (or bbox) as search area ' +\n '(e.g. \"Paris\", \"De Jordaan, Amsterdam\"). ' +\n 'Pair with `queryAs` to disambiguate. EXCLUSIVE with boundingBox / viewport / placeId.',\n ),\n queryAs: queryAsSchema,\n placeId: z\n .string()\n .optional()\n .describe(\n 'ID of a session place — its boundary polygon (fetched on demand) is the search area. ' +\n 'EXCLUSIVE with boundingBox / viewport / query.',\n ),\n })\n .refine((data) => locateWithinSingularKeys.filter((k) => data[k] !== undefined).length === 1, {\n message: '`within` requires EXACTLY ONE of: boundingBox, viewport, query, placeId.',\n });\n\nconst nearbySingularKeys = ['position', 'viewport', 'query'] as const;\n\n/** @ignore */\nexport const nearbyWhereSchema = z\n .object({\n mode: z.literal('nearby'),\n position: positionSchema.optional().describe('Bias point [lng, lat]. EXCLUSIVE with viewport / query.'),\n viewport: z\n .literal(true)\n .optional()\n .describe('When true, bias toward the current map viewport centre. EXCLUSIVE with position / query.'),\n query: z\n .string()\n .optional()\n .describe(\n 'Resolve a name/address/POI → its centroid as bias point. ' +\n 'Pair with `queryAs` to disambiguate. EXCLUSIVE with position / viewport.',\n ),\n queryAs: queryAsSchema,\n radiusMeters: z\n .number()\n .positive()\n .optional()\n .describe('Hard radius cap (metres) around the bias point. Independent of bias source.'),\n })\n .refine((data) => nearbySingularKeys.filter((k) => data[k] !== undefined).length === 1, {\n message: '`nearby` requires EXACTLY ONE of: position, viewport, query (radiusMeters is independent).',\n });\n\n/** @ignore */\nexport const globalWhereSchema = z.object({\n mode: z\n .literal('global')\n .describe('No spatial constraint — resolve the name anywhere. The default for a uniquely-named place.'),\n});\n\n/**\n * locatePlace's `where` schema — `within` (singular fields), `nearby`, or `global`.\n * discoverPlaces builds its own union in discover-places.ts using the plural-form within schema.\n * @ignore\n */\nexport const whereSchema = z.union([withinWhereSchema, nearbyWhereSchema, globalWhereSchema]);\n\n// --- Shared `within` fields (plural form) for discoverPlaces + getTrafficIncidents ---------------\n// Lifted from discover-places.ts so both tools build their `within` schemas from the same field\n// definitions.\n\n/**\n * The multi-region `within` field block shared by discoverPlaces and getTrafficIncidents.\n * Spread into each tool's `z.object({...})` alongside any tool-specific fields.\n * @ignore\n */\nexport const sharedWithinFields = {\n ...withinSharedFields,\n queries: z\n .array(\n z.object({\n query: z\n .string()\n .describe(\n 'Name of a CONTAINING area to search within (e.g. \"Paris\", \"De Jordaan, Amsterdam\"). ' +\n \"NEVER the user's search subject — that goes in the top-level `query`.\",\n ),\n queryAs: queryAsSchema,\n }),\n )\n .min(1)\n .optional()\n .describe(\n 'CONTAINING areas to search WITHIN — answers \"search where?\", NOT \"search for what?\". ' +\n \"Never put the user's search subject here. \" +\n 'Example: for \"cafes in Amsterdam\" → top-level `query`/`poiCategories` carry \"cafes\"; ' +\n '`where.queries: [{query: \"Amsterdam\"}]` carries the region. ' +\n 'Each entry resolves to a boundary polygon (or bbox); the union is searched. ' +\n 'Per-item `queryAs` disambiguates POI vs place. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n placeIds: z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n 'IDs of session places — a places ENTRY id (e.g. \"places-0\", or the `placesEntryId` ' +\n 'returned by locatePlace) expands to every place it holds, or a single place id. ' +\n 'Each resolved polygon (fetched on demand) joins the search area. ' +\n 'Use recallState to list IDs. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n geometries: z\n .array(geometryInputSchema)\n .min(1)\n .optional()\n .describe(\n 'Direct GeoJSON Polygons / MultiPolygons (custom drawing, external data). ' +\n 'For named or stored places use `queries` / `placeIds`. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n range: z\n .string()\n .min(1)\n .optional()\n .describe(\n 'Range ID from findReachableAreas (e.g. \"ranges-0\") — restricts to that entry; ' +\n 'multi-origin entries combine their polygons. Use recallState to list IDs. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n route: z\n .object({\n routeId: z\n .string()\n .min(1)\n .optional()\n .describe('Route entry ID (e.g. \"routes-0\"). Use recallState. Default: latest route.'),\n widthMeters: z\n .number()\n .positive()\n .describe(\n 'Total corridor width (metres) — widthMeters/2 each side. ' +\n 'Typical: 200–500 m (\"near the road\"), 2–5 km (\"broad area along the route\").',\n ),\n })\n .optional()\n .describe(\n 'Buffered corridor around a stored route — turf.buffer at widthMeters/2 around the line. ' +\n 'EXCLUSIVE with viewport; composes with other multi-region fields.',\n ),\n};\n\n/**\n * The resolver's input: the area-producing `where` fields, so the LLM-facing tool schemas and\n * the resolver input (see {@link AreaWhere}) share one definition. No `range` — that maps to\n * stored isochrones and is resolved caller-side.\n * @ignore\n */\nexport const areaWhereSchema = z.object({\n viewport: z.boolean().optional(),\n boundingBox: geoJsonBBoxSchema.optional(),\n queries: sharedWithinFields.queries,\n placeIds: sharedWithinFields.placeIds,\n geometries: sharedWithinFields.geometries,\n route: sharedWithinFields.route,\n});\n\n/** The `where` fields resolve-where.ts resolves — inferred from {@link areaWhereSchema}. */\nexport type AreaWhere = z.infer<typeof areaWhereSchema>;\n","/**\n * @module agent-toolkit-tools\n *\n * Compact schema documentation for the `TrafficAreaAnalytics` shape, reused by every tool whose\n * LLM-authored code reads the injected `trafficAreaAnalyticsByEntry[id]` collections (`analyseData`, `processData`,\n * `clusterIncidents`). The metric numbers live under `feature.properties.baseData` — NOT directly on\n * `properties` — so without this the model reads the wrong path.\n */\n\n/** @ignore */\nexport const TRAFFIC_AREA_ANALYTICS_SCHEMA_DOC =\n 'Each `trafficAreaAnalyticsByEntry[id]` is a FeatureCollection of Polygon Features (one per analysed region). The ' +\n 'aggregated metrics live under `feature.properties.baseData` (NOT directly on `properties`):\\n' +\n '```ts\\n' +\n 'type AreaAnalyticsFeatureProperties = {\\n' +\n ' name?: string; level: number; // name only when the request names the region\\n' +\n ' baseData: { // aggregated over the whole period — the usual read\\n' +\n ' speed?: number; // avg km/h\\n' +\n ' freeFlowSpeed?: number; // avg uncongested km/h\\n' +\n ' congestionLevel?: number; // % travel-time increase over free-flow\\n' +\n ' travelTime?: number; // minutes per 10 km\\n' +\n ' networkLength?: number; // meters of road network with data\\n' +\n ' };\\n' +\n ' timedData: { // same metrics per period, only periods with data; every label is UTC\\n' +\n ' daily?: Array<{ date: Date; …metrics }>; // midnight UTC: read with toISOString()\\n' +\n ' hourly?: Array<{ date: Date; hour: number; …metrics }>; // hour 0-23\\n' +\n ' average?: Array<{ day: 1|2|3|4|5|6|7; hour: number; …metrics }>; // average week, 1 = Monday\\n' +\n ' yearly?, monthly?, weekly?: …;\\n' +\n ' };\\n' +\n ' tiledData?: { tiles: Array<{ … }> }; // per-tile metrics, only when heatmap was requested\\n' +\n '};\\n' +\n \"// the FeatureCollection's own `.properties` (collection-level):\\n\" +\n 'type AreaAnalyticsCollectionProperties = {\\n' +\n ' startDate: Date; endDate: Date;\\n' +\n ' metrics: (\"speed\"|\"freeFlowSpeed\"|\"congestionLevel\"|\"travelTime\"|\"networkLength\")[];\\n' +\n ' heatmap: boolean; frcs: number[];\\n' +\n ' ranges: Partial<Record<metric, { min: number; max: number }>>; // data-driven min/max across tiles\\n' +\n '};\\n' +\n '```\\n' +\n 'Read metrics from `feature.properties.baseData.<metric>` (guard `?? 0`). Shift a UTC `day`/`hour` by the ' +\n \"area's UTC offset to report local time (Amsterdam summer, UTC+2: `{ day: 1, hour: 5 }` is Monday 07:00).\";\n","/**\n * @module agent-toolkit-tools\n *\n * Shared scaffolding for tools that consume multi-kind entry inputs (`analyseData`,\n * `processData`). Centralises the per-{@link EntryDataKind} metadata each tool needs\n * to build its `*EntryIDs` schema fields, sandbox-doc fragments, schema-doc fragments,\n * scope schema and \"at least one of …\" refine — so the tool files themselves stay\n * focused on their own contract (return-shape validator, per-tool extras, execute body).\n */\n\nimport { z } from 'zod';\nimport type { EntryDataKind } from '../../types';\nimport { geometriesEntryIDsSchema } from './geometries-id';\nimport { GEOMETRIES_SCHEMA_DOC } from './geometries-schema-doc';\nimport { INCIDENTS_SCHEMA_DOC } from './incidents-schema-doc';\nimport { PLACES_SCHEMA_DOC } from './places-schema-doc';\nimport { ROUTES_SCHEMA_DOC } from './routes-schema-doc';\nimport { placesEntryIDsSchema, routesEntryIDsSchema } from './schema';\nimport { TRAFFIC_AREA_ANALYTICS_SCHEMA_DOC } from './traffic-area-analytics-schema-doc';\n\n/**\n * Canonical ordering for {@link EntryDataKind}. Used wherever per-kind doc fragments or\n * field lists are concatenated — keeps the layout stable regardless of the order kinds\n * appear in a scope or enabled-kinds list.\n *\n * @ignore\n */\nexport const ALL_ENTRY_DATA_KINDS = [\n 'places',\n 'routes',\n 'incidents',\n 'customGeometries',\n 'trafficAreaAnalytics',\n 'byod',\n] as const satisfies readonly EntryDataKind[];\n\n/**\n * Per-kind scope envelope. The classifier emits one of these in `toolScopes.<toolName>`\n * to narrow which kinds the tool's per-turn input schema and code-doc mention.\n *\n * @ignore\n */\nexport type EntryKindScope = { kinds: readonly EntryDataKind[] };\n\n/**\n * Build a zod scope schema that only accepts kinds within the supplied enabled set.\n * Mirrors the per-tool scope guard so a classifier picking a disabled kind fails-open\n * (the malformed scope is dropped, full surface stays in place).\n *\n * @ignore\n */\nexport const buildEntryKindScopeSchema = (enabled: readonly EntryDataKind[]) =>\n z.object({\n kinds: z.array(z.enum(enabled as readonly [EntryDataKind, ...EntryDataKind[]])).min(1),\n });\n\n/**\n * `extra` fragment supplied by the tool — appended to each generated input-field\n * description so callers see tool-specific hints alongside the shared metadata.\n *\n * @ignore\n */\ntype FieldBuildOptions = {\n verb: 'analyse' | 'process';\n requiredHint: string;\n};\n\n/**\n * Per-kind metadata. One row per {@link EntryDataKind}. Owns:\n * - field name + label used in schemas and \"at least one of\" messages;\n * - the factory that produces the `*EntryIDs` zod field for a given tool verb;\n * - the one-line sandbox-doc fragment (\"• `placesByEntry` — object keyed by entry id …\");\n * - the deep schema-doc fragment (only included when scope commits to this kind);\n * - which recall tool the LLM should call to list available ids.\n *\n * @ignore\n */\nexport type EntryKindMeta = {\n fieldName: string;\n fieldLabel: string;\n buildField: (options: FieldBuildOptions) => z.ZodTypeAny;\n sandboxDoc: string;\n schemaDoc: string;\n recallTool: string;\n};\n\nconst placesField = (options: FieldBuildOptions) =>\n placesEntryIDsSchema({\n verb: options.verb,\n extra:\n 'Injected as `placesByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. ' +\n options.requiredHint,\n });\n\nconst routesField = (options: FieldBuildOptions) =>\n routesEntryIDsSchema({\n verb: options.verb,\n extra:\n 'Injected as `routesByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. ' +\n options.requiredHint,\n });\n\nconst incidentsField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing incidents entries to ${options.verb} (e.g. [\"incidents-0\"]). ` +\n 'Injected as `incidentsByEntry[id]` (a TrafficIncident[] per entry id). Undefined when omitted. ' +\n \"Use `recallState({ kind: 'incidents' })` to list incident entries. \" +\n options.requiredHint,\n );\n\nconst customGeometriesField = (options: FieldBuildOptions) =>\n geometriesEntryIDsSchema\n .optional()\n .describe(\n 'Tagged polygon sources `{ kind, id }` (see geometriesEntryIDsSchema). ' +\n 'Injected as `geometriesByEntry` (keyed by the tagged source `${kind}:${id}`). Undefined when omitted. ' +\n options.requiredHint,\n );\n\nconst trafficAreaAnalyticsField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing traffic-area-analytics entries to ${options.verb} (e.g. [\"tta-0\"]). ` +\n 'Injected as `trafficAreaAnalyticsByEntry[id]` (a FeatureCollection of tile/hex regions with metric ' +\n 'properties, per entry id). Undefined when omitted. ' +\n \"Use `recallState({ kind: 'trafficAreaAnalytics' })` to list available ids. \" +\n options.requiredHint,\n );\n\nconst byodField = (options: FieldBuildOptions) =>\n z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n `IDs of existing BYOD entries to ${options.verb} (e.g. [\"byod-0\"]) — customer-authored GeoJSON layers. ` +\n 'Injected as `byodByEntry[id]` (a FeatureCollection per entry id). Undefined when omitted. ' +\n 'Use `recallState` to list available ids. ' +\n options.requiredHint,\n );\n\n/**\n * Single source of truth for the per-kind input vocabulary. Both `analyseData` and\n * `processData` read this map to assemble their input schemas and doc fragments.\n *\n * @ignore\n */\nexport const ENTRY_KIND_META: Record<EntryDataKind, EntryKindMeta> = {\n places: {\n fieldName: 'placesEntryIDs',\n fieldLabel: '`placesEntryIDs`',\n buildField: placesField,\n sandboxDoc:\n '• `placesByEntry` — object keyed by `placesEntryIDs` id; each value a FeatureCollection of that ' +\n \"entry's places. Merge all entries with `Object.values(placesByEntry).flatMap(fc => fc.features)`.\",\n schemaDoc: PLACES_SCHEMA_DOC,\n recallTool: \"recallState({ kind: 'places' })\",\n },\n routes: {\n fieldName: 'routesEntryIDs',\n fieldLabel: '`routesEntryIDs`',\n buildField: routesField,\n sandboxDoc:\n '• `routesByEntry` — object keyed by `routesEntryIDs` id; each value a FeatureCollection of that ' +\n \"entry's Route LineStrings. Merge all entries with `Object.values(routesByEntry).flatMap(fc => fc.features)`. \" +\n 'On-route incidents live in `f.properties.sections.traffic[]` (each ' +\n '`{ categories: string[], magnitudeOfDelay, delayInSeconds }`) — there is NO `f.properties.incidents`. ' +\n 'Route totals are on `f.properties.summary` (`travelTimeInSeconds`, `trafficDelayInSeconds`).',\n schemaDoc: ROUTES_SCHEMA_DOC,\n recallTool: \"recallState({ kind: 'routes' })\",\n },\n incidents: {\n fieldName: 'incidentsEntryIDs',\n fieldLabel: '`incidentsEntryIDs`',\n buildField: incidentsField,\n sandboxDoc:\n '• `incidentsByEntry` — object keyed by `incidentsEntryIDs` id; each value a flat `TrafficIncident[]` ' +\n 'for that entry. Merge all entries with `Object.values(incidentsByEntry).flat()`.',\n schemaDoc: INCIDENTS_SCHEMA_DOC,\n recallTool: \"recallState({ kind: 'incidents' })\",\n },\n customGeometries: {\n fieldName: 'geometriesEntryIDs',\n fieldLabel: '`geometriesEntryIDs`',\n buildField: customGeometriesField,\n sandboxDoc:\n '• `geometriesByEntry` — object keyed by the tagged source `${kind}:${id}` (geometries mixes sources); ' +\n 'each value a mixed Polygon/MultiPolygon array. Merge all with `Object.values(geometriesByEntry).flat()`. ' +\n 'Every feature also carries `properties._source: { kind, id }` so you can filter by role (see `GEOMETRIES_PROPS_DOC`).',\n schemaDoc: GEOMETRIES_SCHEMA_DOC,\n recallTool: \"recallState({ kind: 'geometries' })\",\n },\n trafficAreaAnalytics: {\n fieldName: 'trafficAreaAnalyticsEntryIDs',\n fieldLabel: '`trafficAreaAnalyticsEntryIDs`',\n buildField: trafficAreaAnalyticsField,\n sandboxDoc:\n '• `trafficAreaAnalyticsByEntry` — object keyed by `trafficAreaAnalyticsEntryIDs` id; each value a ' +\n 'TrafficAreaAnalytics FeatureCollection for that entry. Each feature is a region whose metrics live under ' +\n '`feature.properties.baseData` (e.g. `congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, ' +\n \"`networkLength`). Each entry's collection keeps its own top-level `properties` (metrics list, date range, \" +\n 'ranges) intact — no cross-entry merge.',\n schemaDoc: TRAFFIC_AREA_ANALYTICS_SCHEMA_DOC,\n recallTool: \"recallState({ kind: 'trafficAreaAnalytics' })\",\n },\n byod: {\n fieldName: 'byodEntryIDs',\n fieldLabel: '`byodEntryIDs`',\n buildField: byodField,\n sandboxDoc:\n '• `byodByEntry` — object keyed by `byodEntryIDs` id; each value a customer-authored GeoJSON ' +\n 'FeatureCollection. Merge all with `Object.values(byodByEntry).flatMap(fc => fc.features)`. Mixed Point / ' +\n 'LineString / Polygon features are normal (BYOD layers carry whatever the integrator uploaded). Treat ' +\n '`feature.properties` as opaque application-specific data.',\n schemaDoc: '',\n recallTool: \"recallState({ kind: 'byod' })\",\n },\n};\n\n/**\n * Resolve the set of kinds active for a single per-turn build: the intersection of\n * the agent's enabled kinds and the classifier's per-turn scope. `scope === undefined`\n * means \"no per-turn narrowing — every enabled kind stays in scope\".\n *\n * @ignore\n */\nexport const resolveActiveKinds = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n): readonly EntryDataKind[] =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => enabled.includes(kind) && (!scope || scope.kinds.includes(kind)));\n\n/**\n * Predicate form of {@link resolveActiveKinds} for a single kind. Used when generating\n * tool-specific fields (e.g. analyseData's `monitor`) that should only appear when\n * `incidents` is in the active set.\n *\n * @ignore\n */\nexport const isKindActive = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n kind: EntryDataKind,\n): boolean => enabled.includes(kind) && (!scope || scope.kinds.includes(kind));\n\n/**\n * Slash-joined list of scoped `*EntryIDs` field labels — used in \"At least one of `A` / `B`\n * must be set.\" messages and in per-field hints. Order follows {@link ALL_ENTRY_DATA_KINDS}.\n *\n * @ignore\n */\nexport const buildEntryFieldList = (active: readonly EntryDataKind[]): string =>\n active.map((kind) => ENTRY_KIND_META[kind].fieldLabel).join(' / ');\n\n/**\n * Join the per-kind sandbox-doc fragments in canonical order for the supplied active set.\n * Returns the multi-line \"Each input is …\" block.\n *\n * @ignore\n */\nexport const buildEntryKindSandboxDocs = (active: readonly EntryDataKind[]): string =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => active.includes(kind))\n .map((kind) => ENTRY_KIND_META[kind].sandboxDoc)\n .join('\\n');\n\n/**\n * Join the per-kind deep schema-doc fragments in canonical order for the supplied active\n * set. Empty when no active kind has a schema doc (only places / routes / customGeometries\n * carry one today).\n *\n * @ignore\n */\nexport const buildEntryKindSchemaDocs = (active: readonly EntryDataKind[]): string =>\n ALL_ENTRY_DATA_KINDS.filter((kind) => active.includes(kind))\n .map((kind) => ENTRY_KIND_META[kind].schemaDoc)\n .filter((doc) => doc.length > 0)\n .join('\\n\\n');\n\n/**\n * Build the input-shape Record passed into `z.object({...})`. Both tools share this\n * shape — each kind in the active set contributes its `*EntryIDs` field via\n * {@link EntryKindMeta.buildField}. Tools layer their own per-tool fields onto the\n * resulting record before calling `z.object`.\n *\n * @ignore\n */\nexport const buildEntryIdFields = (\n active: readonly EntryDataKind[],\n options: FieldBuildOptions,\n): Record<string, z.ZodTypeAny> => {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n if (!active.includes(kind)) continue;\n const meta = ENTRY_KIND_META[kind];\n shape[meta.fieldName] = meta.buildField(options);\n }\n return shape;\n};\n\n/**\n * Set of LLM-input objects that contain any `*EntryIDs` field. Used by the shared\n * \"at least one of …\" refine so both tools agree on what counts as \"input present\".\n *\n * `geometriesEntryIDs` carries `GeometriesId[]`; the rest carry `string[]`. We treat\n * either as \"present\" when the array has length, which is what the existing inline\n * refines did.\n *\n * @ignore\n */\nexport const hasAnyEntryIds = (value: Record<string, unknown>): boolean => {\n for (const kind of ALL_ENTRY_DATA_KINDS) {\n const arr = value[ENTRY_KIND_META[kind].fieldName] as unknown[] | undefined;\n if (arr?.length) return true;\n }\n return false;\n};\n\n/**\n * Message used by the shared \"at least one of …\" refine. Lists the active kinds'\n * field labels and the slash-joined recall-tool hints for the same set.\n *\n * @ignore\n */\nexport const buildAtLeastOneEntryIdMessage = (active: readonly EntryDataKind[]): string => {\n const fields = buildEntryFieldList(active);\n const recalls = Array.from(new Set(active.map((kind) => ENTRY_KIND_META[kind].recallTool)))\n .map((tool) => `\\`${tool}\\``)\n .join(' / ');\n return `At least one of ${fields} must be set. Call ${recalls} first to list available ids.`;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared `fitOnMap` directive for sandboxed `process-*` tools — the LLM-authored\n * code can return a bbox (or `{ bbox, padding?, animate? }`) and the tool will\n * fit the camera to it without persisting any state. Lets the model use a\n * process-* tool purely for \"compute → focus the map\" without first writing a\n * new entry.\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { DEFAULT_MAP_FIT_PADDING, fitMapToBBox } from './map-fit';\nimport { geoJsonBBoxSchema } from './schema';\n\n/** @ignore */\nexport const fitOnMapInputSchema = z\n .union([\n geoJsonBBoxSchema,\n z.object({\n bbox: geoJsonBBoxSchema,\n padding: z.number().optional(),\n animate: z.boolean().optional(),\n }),\n ])\n .describe(\n 'Bounds to fit the camera to: a `[W, S, E, N]` BBox tuple, or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }`. ' +\n 'lng in [-180, 180], lat in [-90, 90]. Default padding: 50px.',\n );\n\n/** @ignore */\nexport const fittedReportSchema = z.object({\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]).describe('Bounds the camera was fit to.'),\n padding: z.number().describe('Padding (px) applied around the bbox.'),\n});\n\n/** @ignore */\nexport type FitOnMapInput = BBox | { bbox: BBox; padding?: number; animate?: boolean };\n\n/** Doc blurb to inline in sandboxed-code descriptions. @ignore */\nexport const FIT_ON_MAP_DOC =\n '`fitOnMap` (opt): `[W,S,E,N]` BBox or `{ bbox: [W,S,E,N], padding?: number, animate?: boolean }` ' +\n '(default padding 50, lng in [-180, 180], lat in [-90, 90]). Camera fits these bounds after the tool ' +\n 'runs. Returning ONLY `fitOnMap` skips ' +\n 'state writes (pure camera focus); combine with `places` / `geometries` to also write+focus. ' +\n 'Derive bboxes via `turf.bbox(feature | FeatureCollection)`.';\n\n// Delegates to `geoJsonBBoxSchema` so this guard and every zod bbox input enforce the same\n// semantics: four finite numbers, lng/lat in range, minLat <= maxLat, and an antimeridian\n// crossing (minLng > maxLng) still allowed. Arity is what makes the `BBox` narrowing sound.\n//\n// This is the only validation the sandbox `fitOnMap` path gets — the LLM-authored code's\n// return value never passes through a tool input schema — so a bare finiteness check let an\n// out-of-range latitude reach `fitBounds`, which throws after the entry has been written.\nconst isBBoxTuple = (value: unknown): value is BBox => geoJsonBBoxSchema.safeParse(value).success;\n\n/** @ignore */\nexport const isFitOnMapInput = (value: unknown): value is FitOnMapInput => {\n if (isBBoxTuple(value)) return true;\n if (!value || typeof value !== 'object') return false;\n const v = value as { bbox?: unknown; padding?: unknown; animate?: unknown };\n if (!isBBoxTuple(v.bbox)) return false;\n if (v.padding !== undefined && (typeof v.padding !== 'number' || !Number.isFinite(v.padding))) return false;\n return !(v.animate !== undefined && typeof v.animate !== 'boolean');\n};\n\n/**\n * Apply a sandbox-returned `fitOnMap` directive: move the camera to the bbox\n * with the requested padding/animation, and return a normalized record for\n * the tool's output payload.\n *\n * @ignore\n */\nexport const applyFitOnMap = (state: ToolState, fit: FitOnMapInput): z.infer<typeof fittedReportSchema> => {\n const bbox = isBBoxTuple(fit) ? fit : fit.bbox;\n const padding = (isBBoxTuple(fit) ? undefined : fit.padding) ?? DEFAULT_MAP_FIT_PADDING;\n const animate = isBBoxTuple(fit) ? undefined : fit.animate;\n fitMapToBBox(state.baseMap.mapLibreMap, bbox, { padding, ...(animate !== undefined && { animate }) });\n return { bbox, padding };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Area geocoding primitive: resolve a query to a *containing* administrative area (the \"where\" of\n * `within`-mode scope). Lives in `shared/` so the where-resolution layer (`resolve-where`,\n * `tool-state-where-context`) can depend on it without a `shared → services → shared` cycle.\n * Place-entity (\"what\") resolution is a separate concern — see `locate-places.ts`.\n */\n\nimport { type GeographyType, type Place } from '@tomtom-org/maps-sdk/core';\nimport { geocode } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport type { ToolExecuteOptions } from '../../types';\nimport { withAgentToolkitHeaders } from './agent-headers';\n\n/**\n * Administrative-AREA geography types accepted when resolving a query to a *containing area*\n * (`within` mode). Restricting the geocode to these (via `entityTypeSet`) is what stops streets and\n * POIs from drowning the area — e.g. \"De Pijp\" / \"Westminster\" geocode to Streets at rank 0 without\n * it. Excludes `Country` (too coarse) and `PostalCodeArea` (homonym noise — \"A10\" → a Kazakh\n * postcode).\n */\nexport const AREA_GEOGRAPHY_TYPES: GeographyType[] = [\n 'CountrySubdivision',\n 'CountrySecondarySubdivision',\n 'Municipality',\n 'MunicipalitySubdivision',\n 'Neighbourhood',\n];\n\n/**\n * Geocodes `query` restricted to administrative AREA geographies (not streets/POIs), biased toward\n * `bias` (the map centre). Because `geographyTypes` filters to areas, every result carries a\n * `boundingBox`. Callers choose among the candidates (e.g. the one nearest the current view) — a\n * centre bias is used rather than a hard viewport-bbox filter so out-of-view areas still resolve.\n */\nexport const geocodeAreas = async (\n query: string,\n opts: { bias?: Position; limit?: number } = {},\n execOptions?: ToolExecuteOptions,\n): Promise<Place[]> => {\n const requestParams = withAgentToolkitHeaders({\n query,\n limit: opts.limit ?? 6,\n filters: { geographyTypes: AREA_GEOGRAPHY_TYPES },\n ...(opts.bias ? { geoBias: { position: opts.bias } } : {}),\n signal: execOptions?.signal,\n });\n const result = await geocode(requestParams);\n return result.features;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shared helper that applies the optional `hidePreviousEntries` selector to a\n * routing- or ranges-style state slice before a new entry is rendered.\n */\n\nimport type { ShownEntriesSlice } from '../../state';\n\n/**\n * Hide previously-shown entries on a slice that exposes `hideEntry(id)` and a\n * `shownEntryIds` set (routing, ranges). No-op under `entryMode === 'single'`\n * (the slice auto-hides others) or when the selector is `undefined`\n * (omit ⇒ stack on top of existing entries).\n *\n * @ignore\n */\nexport const hidePreviousShownEntries = async (\n slice: ShownEntriesSlice,\n keepEntryIds: readonly string[],\n selector: 'all' | readonly string[] | undefined,\n): Promise<void> => {\n if (slice.entryMode !== 'multiple' || selector === undefined) return;\n const keep = new Set(keepEntryIds);\n const toHide =\n selector === 'all'\n ? [...slice.shownEntryIds].filter((entryId) => !keep.has(entryId))\n : selector.filter((entryId) => !keep.has(entryId));\n for (const entryId of toHide) {\n await slice.hideEntry(entryId);\n }\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Place-entity geocoding primitives: resolve a query string to a named place / POI (the \"what\").\n * Live in `shared/` (not in the `locate-place` tool) so the resolver layer (`resolve-where`,\n * `tool-state-where-context`) can depend on them without a `shared → services → shared` cycle.\n * Area (\"where\") resolution is a separate concern — see `geocode-areas.ts`.\n */\n\nimport { type Place } from '@tomtom-org/maps-sdk/core';\nimport { discoverPlaces, type GeoBias, geocode } from '@tomtom-org/maps-sdk/services';\nimport type { ToolExecuteOptions } from '../../types';\nimport { withAgentToolkitHeaders } from './agent-headers';\n\n/**\n * How {@link locatePlace} resolves a query: `'poi'` searches points of interest by name, `'place'` geocodes\n * an address or an area.\n *\n * @group Agent Toolkit\n */\nexport type QueryAs = 'poi' | 'place';\n\n/**\n * Resolves a query string to up to `limit` Places. POI search when `queryAs === 'poi'`, geocoding\n * otherwise. Optional `bias` narrows the search to a region.\n */\nexport const locatePlaces = async (\n query: string,\n queryAs: QueryAs,\n options: { limit: number; bias?: GeoBias },\n execOptions?: ToolExecuteOptions,\n): Promise<Place[]> => {\n const searchFn = queryAs === 'poi' ? discoverPlaces : geocode;\n const { limit, bias } = options;\n const requestParams = withAgentToolkitHeaders({\n query,\n limit,\n ...(bias && { geoBias: bias }),\n signal: execOptions?.signal,\n });\n const results = await searchFn(requestParams);\n\n return results.features;\n};\n\n/**\n * The best match for a query, as the `locatePlace` tool resolves it, for a custom tool that needs one place.\n * A failed or cancelled request rejects, so catch it in `execute`.\n *\n * @param bias - Favours results near a position, such as the map centre from {@link getViewportBias}.\n * @param execOptions - The `execute` options, whose `signal` cancels the request with the turn.\n * @returns The first match, or `null` when nothing matches.\n *\n * @example\n * ```ts\n * execute: async ({ name }, state, options) => {\n * const position = getViewportBias(state.baseMap);\n * const place = await locatePlace(name, 'poi', position && { position }, options);\n * // ...\n * },\n * ```\n *\n * @group Agent Toolkit\n */\nexport const locatePlace = async (\n query: string,\n queryAs: QueryAs,\n bias?: GeoBias,\n execOptions?: ToolExecuteOptions,\n): Promise<Place | null> => {\n const [first] = await locatePlaces(query, queryAs, { limit: 1, bias }, execOptions);\n return first ?? null;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport { positionSchema } from './schema';\n\n/** @ignore */\nexport type ResolvedLocation = {\n /** Resolved place: a Place (with address metadata) or a bare [lng, lat] tuple. */\n place: WaypointLike;\n position: Position;\n name: string;\n /** Original query string, when the input was resolved from text. */\n query?: string;\n};\n\n/** @ignore */\nexport const locationInputSchema = z.union([\n z.object({\n query: z.string().describe('Location string to resolve.'),\n queryAs: z\n .enum(['poi', 'place'])\n .describe(\n '\"poi\" for venues / landmarks / businesses; \"place\" for addresses, cities, neighborhoods, parks, postal codes, and other geographies.',\n ),\n }),\n z.object({\n position: positionSchema.describe(\n 'Explicit [lng, lat] — GeoJSON order, longitude first. Use when you already have a ' +\n 'position, e.g. copied straight from a getViewport / getCurrentLocation / ' +\n 'reverseGeocode result. lng in [-180, 180], lat in [-90, 90].',\n ),\n }),\n z.object({\n placeIdOrEntryId: z\n .string()\n .describe(\n 'Place already in session state, by its feature `id` or its `placesEntryId`. ' +\n 'Prefer over re-searching by name. Prefer a feature `id`: it targets one place, ' +\n 'whereas an entry id resolves to its FIRST place (fine for a single-place locatePlace ' +\n 'entry, ambiguous for a multi-place discoverPlaces one).',\n ),\n }),\n]);\n\n/** @ignore */\nexport type LocationInput = z.infer<typeof locationInputSchema>;\n\n/**\n * Names a location input in an error message, without resolving it — so the model reads back the\n * same thing it sent.\n *\n * @ignore\n */\nexport const labelForLocationInput = (location: LocationInput): string => {\n if ('query' in location) return `\"${location.query}\"`;\n\n if ('placeIdOrEntryId' in location) return `placeIdOrEntryId \"${location.placeIdOrEntryId}\"`;\n\n return JSON.stringify(location.position);\n};\n","/**\n * @module agent-toolkit-tools\n *\n * The `routeUtils` namespace injected into sandboxed model-authored code, so a routes input can be\n * sliced and measured without re-implementing it. Its own module because the worker entry\n * (`sandbox/sdk-utils-worker-entry.ts`) is bundled standalone at SDK build time, so importing it\n * from `multi-input.ts` would pull that module's prompt text into the worker bundle.\n *\n * Every function here depends only on `@turf/turf` — available in the worker as `self.turf` — and\n * pure local helpers, which is what makes the set portable into the sandbox.\n */\n\nimport {\n calculateProgressAtRoutePoint,\n getCoordinateAtRouteProgress,\n getProgressAtNearestRoutePoint,\n getRouteProgressBetween,\n getRouteProgressForSection,\n getSectionBBox,\n} from '@tomtom-org/maps-sdk/core';\n\n/**\n * @ignore\n */\nexport const routeUtils = {\n calculateProgressAtRoutePoint,\n getCoordinateAtRouteProgress,\n getProgressAtNearestRoutePoint,\n getRouteProgressBetween,\n getRouteProgressForSection,\n getSectionBBox,\n} as const;\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, Places, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { type GeometriesId, geometriesIdSchema } from './geometries-id';\n\n// Per-source skip record. `source` carries the original tagged id so the\n// caller can present the failure with the same `{kind, id}` shape the agent\n// passed in, and `reason` is a free-form explanation.\n/** @ignore */\nexport const skippedSourceSchema = z.object({\n source: geometriesIdSchema,\n reason: z.string(),\n});\n\n/** @ignore */\nexport type SkippedSource = z.infer<typeof skippedSourceSchema>;\n\n/** @ignore */\nexport type AffectedEntry = {\n /** Unqualified entry id within the owning slice. */\n id: string;\n /** Which slice the entry lives in (only kinds that carry analyses are tracked). */\n kind: 'places' | 'customGeometries';\n};\n\n/** @ignore */\nexport type CollectedGeometries = {\n geometries: PolygonFeature[];\n /** Place objects whose footprints contributed (used to seed processData provenance). */\n sourcePlaces: Place[];\n /** Entries that should receive analyses (places + customGeometries; ranges don't carry analyses). */\n affectedEntries: AffectedEntry[];\n skipped: SkippedSource[];\n /** Tagged ids that contributed at least one polygon, in input order. */\n contributingSourceIds: GeometriesId[];\n};\n\ntype ParsedByKind = {\n place: GeometriesId[];\n places: GeometriesId[];\n ranges: GeometriesId[];\n customGeometries: GeometriesId[];\n};\n\nconst groupByKind = (sources: readonly GeometriesId[]): ParsedByKind => {\n const out: ParsedByKind = { place: [], places: [], ranges: [], customGeometries: [] };\n for (const source of sources) out[source.kind].push(source);\n return out;\n};\n\nconst formatSkipReason = (skip: SkippedSource): string => `${skip.source.kind}:${skip.source.id}: ${skip.reason}`;\n\n// Collect the ids of `places` that carry a geometry data source, deduping against `seen` and recording\n// the owning entry for each. Returns how many qualifying places the entry had (0 → the caller skips it).\n// Split out of `expandPlacesEntries` so that orchestrator stays under the cognitive-complexity ceiling.\nconst collectGeometryBearingPlaceIds = (\n places: readonly Place[],\n sourceId: string,\n acc: { placeIds: string[]; placeIdToEntry: Map<string, string>; seen: Set<string> },\n): number => {\n let added = 0;\n for (const place of places) {\n if (!place.properties.dataSources?.geometry?.id || typeof place.id !== 'string') continue;\n added++;\n if (!acc.placeIdToEntry.has(place.id)) acc.placeIdToEntry.set(place.id, sourceId);\n if (acc.seen.has(place.id)) continue;\n acc.seen.add(place.id);\n acc.placeIds.push(place.id);\n }\n return added;\n};\n\n// Walks `places` inputs, validates each entry, and emits the place ids that\n// have a geometry data source. Returns a back-pointer map so per-place\n// outcomes can be attributed to the requesting entry afterwards.\nconst expandPlacesEntries = (\n sources: readonly GeometriesId[],\n state: ToolState,\n): { placeIds: string[]; placeIdToEntry: Map<string, string>; skipped: SkippedSource[] } => {\n const placeIds: string[] = [];\n const placeIdToEntry = new Map<string, string>();\n const skipped: SkippedSource[] = [];\n const seen = new Set<string>();\n\n for (const source of sources) {\n const entry = state.places.entries.find((e) => e.id === source.id);\n if (!entry) {\n skipped.push({\n source,\n reason: 'No places entry with this id in session state. Use `recallState` to list available ids.',\n });\n continue;\n }\n const added = collectGeometryBearingPlaceIds(entry.data, source.id, { placeIds, placeIdToEntry, seen });\n if (added === 0) {\n skipped.push({\n source,\n reason: 'Places entry has no places with a geometry data source (e.g. addresses and streets often lack one).',\n });\n }\n }\n return { placeIds, placeIdToEntry, skipped };\n};\n\n// Shallow-clone a feature and stamp `properties._source` with the tagged id it came from.\n// The original feature lives in state (place-geometry cache, range polygons, custom-entry\n// features) — mutating it directly would corrupt subsequent reads. Cloning keeps the\n// per-sandbox-run tag scoped to the call.\nconst tagFeatureSource = (feature: PolygonFeature, source: GeometriesId): PolygonFeature => ({\n ...feature,\n properties: { ...(feature.properties ?? {}), _source: source },\n});\n\n// Resolves one place id to its boundary polygon. Returns `success` with the\n// fetched feature, or `skip` with a reason the caller records as a `place`\n// skip. Pulled out of `collectInputGeometries` so the orchestrator stays\n// under Biome's cognitive-complexity ceiling.\ntype PlaceResolution =\n | { kind: 'success'; place: Place; feature: PolygonFeature; ownerEntryId: string }\n | { kind: 'skip'; reason: string };\n\nconst resolvePlaceGeometry = async (placeId: string, state: ToolState): Promise<PlaceResolution> => {\n const lookup = state.places.findPlaceById(placeId);\n if (!lookup) {\n return {\n kind: 'skip',\n reason:\n 'No place with this id in session state. ' + 'Use `recallState` to list known places ids and entries.',\n };\n }\n if (!lookup.place.properties.dataSources?.geometry?.id) {\n return {\n kind: 'skip',\n reason: 'Place has no geometry data source (e.g. addresses and streets often lack one).',\n };\n }\n try {\n const feature = await state.places.fetchPlaceGeometry(placeId);\n if (!feature) return { kind: 'skip', reason: 'Geometry Data service returned no feature for this place.' };\n return { kind: 'success', place: lookup.place, feature, ownerEntryId: lookup.entryId };\n } catch (error) {\n return {\n kind: 'skip',\n reason: `Geometry fetch failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n// Flatten range polygons from each `ranges` input. Self-contained so the\n// orchestrator stays under Biome's cognitive-complexity ceiling.\nconst collectRangePolygons = (\n sources: readonly GeometriesId[],\n state: ToolState,\n): { features: PolygonFeature[]; contributingIds: Set<string>; skipped: SkippedSource[] } => {\n const features: PolygonFeature[] = [];\n const contributingIds = new Set<string>();\n const skipped: SkippedSource[] = [];\n for (const source of sources) {\n const entry = state.ranges.entries.find((e) => e.id === source.id);\n if (!entry) {\n skipped.push({ source, reason: 'No ranges entry with this id in session state.' });\n continue;\n }\n const polygons = entry.data.flatMap((r) => r.polygon?.features ?? []);\n if (polygons.length === 0) {\n skipped.push({\n source,\n reason: 'Ranges entry has no polygons — recompute via findReachableAreas first.',\n });\n continue;\n }\n for (const polygon of polygons) features.push(tagFeatureSource(polygon, source));\n contributingIds.add(source.id);\n }\n return { features, contributingIds, skipped };\n};\n\n// Read polygons from each `custom` input.\nconst collectCustomGeometries = (\n sources: readonly GeometriesId[],\n state: ToolState,\n): { features: PolygonFeature[]; contributingIds: Set<string>; skipped: SkippedSource[] } => {\n const features: PolygonFeature[] = [];\n const contributingIds = new Set<string>();\n const skipped: SkippedSource[] = [];\n for (const source of sources) {\n const entry = state.customGeometries.findById(source.id);\n if (!entry) {\n skipped.push({\n source,\n reason: 'No custom-geometries entry with this id in session state. Use `recallState` to list available ids.',\n });\n continue;\n }\n if (entry.data.length === 0) {\n skipped.push({ source, reason: 'Custom-geometries entry is empty.' });\n continue;\n }\n for (const feature of entry.data) features.push(tagFeatureSource(feature, source));\n contributingIds.add(source.id);\n }\n return { features, contributingIds, skipped };\n};\n\n/**\n * Resolves a heterogeneous list of tagged geometry ids into a single\n * `PolygonFeature[]`. The id's `kind` tells the helper which slice to\n * consult: `place` and `places` go through the per-place fetch pipeline\n * (Geometry Data service for footprints, lazy cached on the places-entry);\n * `ranges` flattens isochrone polygons already in memory; `custom` reads a\n * derived entry produced by a previous `processData`.\n *\n * Validates the input precondition (at least one id) and the output\n * postcondition (at least one polygon collected). Returns `affectedEntries`\n * — the entries that should carry analyses computed from these geometries\n * (places-entries from `place`/`places` inputs, custom-entries from\n * `custom` inputs; ranges entries don't carry analyses, so they're not\n * included here).\n *\n * @ignore\n */\nexport const collectInputGeometries = async (\n sources: readonly GeometriesId[],\n state: ToolState,\n): Promise<{ value: CollectedGeometries } | { error: string }> => {\n if (sources.length === 0) {\n return {\n error:\n '`geometriesEntryIDs` must contain one or more tagged ids ' +\n \"(e.g. `{ kind: 'places', id: <entryId> }`, `{ kind: 'place', id: <placeId> }`, \" +\n \"`{ kind: 'ranges', id: <entryId> }`, `{ kind: 'customGeometries', id: <entryId> }`). \" +\n 'Use `recallState` to list available ids.',\n };\n }\n\n const grouped = groupByKind(sources);\n\n // Expand `places` entries into individual place ids, then merge them with\n // any explicit `place` inputs. The merged list runs through the single\n // per-place pipeline so fetches and skip semantics stay uniform.\n const expanded = expandPlacesEntries(grouped.places, state);\n const skipped: SkippedSource[] = [...expanded.skipped];\n\n const seenPlaceIds = new Set<string>();\n const mergedPlaceIds: string[] = [];\n const placeIdSourceQualified = new Map<string, GeometriesId>();\n for (const source of grouped.place) {\n if (seenPlaceIds.has(source.id)) continue;\n seenPlaceIds.add(source.id);\n mergedPlaceIds.push(source.id);\n placeIdSourceQualified.set(source.id, source);\n }\n for (const id of expanded.placeIds) {\n if (seenPlaceIds.has(id)) continue;\n seenPlaceIds.add(id);\n mergedPlaceIds.push(id);\n }\n\n const geometries: PolygonFeature[] = [];\n const sourcePlaces: Place[] = [];\n const affectedPlacesEntries = new Set<string>();\n const contributingPlaceIds = new Set<string>();\n const contributingPlacesEntries = new Set<string>();\n\n for (const placeId of mergedPlaceIds) {\n const result = await resolvePlaceGeometry(placeId, state);\n const explicitPlaceSource = placeIdSourceQualified.get(placeId);\n const fromPlacesEntry = expanded.placeIdToEntry.get(placeId);\n if (result.kind === 'skip') {\n // Per-place failure: report it as `place:<id>` regardless of how\n // the place was introduced. That points the agent at the exact\n // failing feature instead of blaming the parent entry.\n const skipSource: GeometriesId = explicitPlaceSource ?? { kind: 'place', id: placeId };\n skipped.push({ source: skipSource, reason: result.reason });\n continue;\n }\n // Tag with the source the agent passed in: a `place:<placeId>` direct input or a\n // `places:<entryId>` parent. Keeps the sandbox-side `_source` aligned with the input\n // shape rather than leaking the internal expansion.\n const featureSource: GeometriesId =\n explicitPlaceSource ??\n (fromPlacesEntry ? { kind: 'places', id: fromPlacesEntry } : { kind: 'place', id: placeId });\n geometries.push(tagFeatureSource(result.feature, featureSource));\n sourcePlaces.push(result.place);\n affectedPlacesEntries.add(result.ownerEntryId);\n if (explicitPlaceSource) contributingPlaceIds.add(explicitPlaceSource.id);\n if (fromPlacesEntry) contributingPlacesEntries.add(fromPlacesEntry);\n }\n\n const ranges = collectRangePolygons(grouped.ranges, state);\n geometries.push(...ranges.features);\n skipped.push(...ranges.skipped);\n\n const custom = collectCustomGeometries(grouped.customGeometries, state);\n geometries.push(...custom.features);\n skipped.push(...custom.skipped);\n const affectedCustomEntries = custom.contributingIds;\n\n if (geometries.length === 0) {\n return {\n error: `No input geometries available. ${skipped.map(formatSkipReason).join(' | ')}`,\n };\n }\n\n const affectedEntries: AffectedEntry[] = [\n ...[...affectedPlacesEntries].map((id): AffectedEntry => ({ id, kind: 'places' })),\n ...[...affectedCustomEntries].map((id): AffectedEntry => ({ id, kind: 'customGeometries' })),\n ];\n\n // Preserve the caller's input order in `contributingSourceIds`.\n const contributingSourceIds = sources.filter((source) => {\n switch (source.kind) {\n case 'place':\n return contributingPlaceIds.has(source.id);\n case 'places':\n return contributingPlacesEntries.has(source.id);\n case 'ranges':\n return ranges.contributingIds.has(source.id);\n case 'customGeometries':\n return affectedCustomEntries.has(source.id);\n }\n });\n\n return {\n value: {\n geometries,\n sourcePlaces,\n affectedEntries,\n skipped,\n contributingSourceIds,\n },\n };\n};\n\n/**\n * Resolves `placesEntryIDs` into a `Record<entryId, Places>` map for\n * sandbox cross-referencing. Returns `{ value: undefined }` when no IDs\n * are supplied so callers can pass the result straight into the sandbox.\n * Mirrors the resolution used by analyse-routes and process-routes.\n *\n * @ignore\n */\nexport const findPlacesByEntry = (\n placesEntryIDs: readonly string[] | undefined,\n state: ToolState,\n): { value: Record<string, Places> | undefined } | { error: string } => {\n if (!placesEntryIDs?.length) return { value: undefined };\n const placesByEntry: Record<string, Places> = {};\n for (const id of placesEntryIDs) {\n const placesEntry = state.places.entries.find((entry) => entry.id === id);\n if (!placesEntry) {\n return { error: `No places entry found with id \"${id}\". Use recallState to list available IDs.` };\n }\n placesByEntry[id] = { type: 'FeatureCollection', features: placesEntry.data } as Places;\n }\n return { value: placesByEntry };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Cross-slice input prep for the merged `analyseData` / `processData` tools.\n *\n * Per-type tools (analyse-places, process-routes, …) default to \"use the last\n * entry of this slice\" when their id array is omitted. The merged tools don't\n * do that: each input kind is purely opt-in. If `placesEntryIDs` is missing,\n * the sandbox sees `places === undefined`; if it's `[]`, same. The LLM\n * explicitly lists every kind it wants to consider.\n *\n * The helper resolves every requested kind, builds the sandbox-facing\n * per-entry (`<kind>ByEntry`) views, and returns the resolved entries so the\n * analyse path can attach an analysis to each contributing entry afterwards.\n */\n\nimport type { Place, PolygonFeature, Route, TrafficAreaAnalytics, TrafficIncident } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection as GeoJSONFeatureCollection } from 'geojson';\nimport type {\n BYODEntry,\n PlacesEntry,\n RoutesEntry,\n TrafficAreaAnalyticsEntry,\n TrafficIncidentsEntry,\n} from '../../state';\nimport { clusterIncidents } from '../../state';\nimport type { EntryDataKind, ToolState } from '../../types';\nimport type { GeometriesId } from './geometries-id';\nimport { routeUtils } from './route-utils';\nimport { buildSandboxCodePrompt } from './sandbox-code';\nimport { type AffectedEntry, collectInputGeometries, type SkippedSource } from './state-inputs';\n\ntype FeatureCollection<F> = { type: 'FeatureCollection'; features: F[] };\n\n/**\n * Inputs the LLM passes through the tool schema. All optional, but at least one must be set.\n *\n * @ignore\n */\nexport type MultiInputIds = {\n placesEntryIDs?: readonly string[];\n routesEntryIDs?: readonly string[];\n incidentsEntryIDs?: readonly string[];\n geometriesEntryIDs?: readonly GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: readonly string[];\n byodEntryIDs?: readonly string[];\n};\n\n/**\n * Resolved entries, kept for the attach step in analyseData. Empty arrays for kinds the caller didn't request.\n *\n * @ignore\n */\nexport type ResolvedEntries = {\n places: PlacesEntry[];\n routes: RoutesEntry[];\n incidents: TrafficIncidentsEntry[];\n trafficAreaAnalytics: TrafficAreaAnalyticsEntry[];\n byod: BYODEntry[];\n};\n\n/** Newest `timestamp` across every resolved source entry, or undefined when none carry one. The\n * canonical `sampledAt` for a recurring sandbox run over these inputs. */\nexport const latestTimestamp = (resolved: ResolvedEntries): number | undefined => {\n let latest: number | undefined;\n for (const group of Object.values(resolved)) {\n for (const entry of group) {\n const timestamp = (entry as { timestamp?: unknown }).timestamp;\n if (typeof timestamp === 'number' && (latest === undefined || timestamp > latest)) latest = timestamp;\n }\n }\n return latest;\n};\n\n/**\n * Sandbox-facing inputs. Each kind is exposed as a single `<kind>ByEntry` record keyed by the entry id\n * the caller passed in `<kind>EntryIDs`; the field is `undefined` when its kind wasn't requested. There\n * is no flat/merged companion — code accesses one entry by id (`placesByEntry[\"places-2\"]`) or merges\n * across entries explicitly with `Object.values(...)`, so it always stays aware of which entries it spans.\n *\n * @ignore\n */\nexport type SandboxInputs = {\n /**\n * Per-entry places, keyed by entry id. Each value is a FeatureCollection of that one entry's places.\n */\n placesByEntry: Record<string, FeatureCollection<Place>> | undefined;\n /**\n * Per-entry routes, keyed by entry id. Each value is a FeatureCollection of that one entry's routes.\n */\n routesByEntry: Record<string, FeatureCollection<Route>> | undefined;\n /**\n * Per-entry incidents, keyed by entry id. Each value is a plain array of that one entry's incidents.\n */\n incidentsByEntry: Record<string, TrafficIncident[]> | undefined;\n /**\n * Per-source polygons collected from every requested source (place / places / ranges / customGeometries).\n * Keyed by the tagged source `${kind}:${id}` (NOT a bare id: the same id can recur across kinds, e.g. a\n * `place` vs a `customGeometries` entry), so the kind stays unambiguous. Each feature also self-describes\n * its origin in `properties._source` (`{ kind, id }`).\n */\n geometriesByEntry: Record<string, PolygonFeature[]> | undefined;\n /**\n * Per-entry TrafficAreaAnalytics, keyed by entry id. Each value is a full FeatureCollection whose features\n * are tile/hex regions carrying the SDK metric properties (`congestionLevel`, `speed`, `travelTime`, …)\n * and whose collection-level `properties` (metrics list, date range) stay intact per entry.\n */\n trafficAreaAnalyticsByEntry: Record<string, TrafficAreaAnalytics> | undefined;\n /**\n * Per-entry BYOD, keyed by entry id. Each value is a FeatureCollection; mixed geometry types are normal\n * (BYOD layers can be Point / LineString / Polygon).\n */\n byodByEntry: Record<string, GeoJSONFeatureCollection> | undefined;\n};\n\n/**\n * Geometries metadata — only populated when `geometriesEntryIDs` was passed.\n *\n * @ignore\n */\nexport type GeometriesMeta = {\n affectedEntries: AffectedEntry[];\n sourceIds: GeometriesId[];\n skipped: SkippedSource[];\n};\n\n/**\n * Internal result of {@link prepareMultiInputs} — the resolved entries, the sandbox-facing views, and\n * the geometries metadata, bundled for the analyse / process tools.\n *\n * @ignore\n */\nexport type PreparedInputs = {\n resolved: ResolvedEntries;\n sandbox: SandboxInputs;\n geometries: GeometriesMeta;\n};\n\n// Per-id-array hint used by the \"at least one input must be set\" guard. Kept inline so the\n// guard's error message stays accurate when new kinds get added.\nconst ID_FIELDS = [\n 'placesEntryIDs',\n 'routesEntryIDs',\n 'incidentsEntryIDs',\n 'geometriesEntryIDs',\n 'trafficAreaAnalyticsEntryIDs',\n 'byodEntryIDs',\n] as const;\n\nconst hasAnyInput = (ids: MultiInputIds): boolean => ID_FIELDS.some((field) => (ids[field]?.length ?? 0) > 0);\n\n// Pull the slice-resolution stack out of `prepareMultiInputs` so each function's cognitive\n// complexity stays under the SonarQube cap. Returns the resolved entries grouped by kind, or\n// the first per-slice resolution error encountered.\nconst resolveAllSlices = (ids: MultiInputIds, state: ToolState): { value: ResolvedEntries } | { error: string } => {\n const placesResult = resolveSliceEntries(ids.placesEntryIDs, state.places, {\n missingId: (id) => `No places entry with id \"${id}\". Use recallState to list available IDs.`,\n });\n if ('error' in placesResult) return placesResult;\n\n const routesResult = resolveSliceEntries(ids.routesEntryIDs, state.routing, {\n missingId: (id) => `No routes entry with id \"${id}\". Use recallState to list available IDs.`,\n });\n if ('error' in routesResult) return routesResult;\n\n const incidentsResult = resolveSliceEntries(ids.incidentsEntryIDs, state.trafficIncidents, {\n missingId: (id) => `No incidents entry with id \"${id}\". Call getTrafficIncidents first.`,\n });\n if ('error' in incidentsResult) return incidentsResult;\n\n const trafficAreaAnalyticsResult = resolveSliceEntries(\n ids.trafficAreaAnalyticsEntryIDs,\n state.trafficAreaAnalytics,\n {\n missingId: (id) => `No traffic-area-analytics entry with id \"${id}\". Call getTrafficAreaAnalytics first.`,\n },\n );\n if ('error' in trafficAreaAnalyticsResult) return trafficAreaAnalyticsResult;\n\n const byodResult = resolveSliceEntries(ids.byodEntryIDs, state.byod, {\n missingId: (id) => `No BYOD entry with id \"${id}\". Use recallState to list available IDs.`,\n });\n if ('error' in byodResult) return byodResult;\n\n return {\n value: {\n places: placesResult.value,\n routes: routesResult.value,\n incidents: incidentsResult.value,\n trafficAreaAnalytics: trafficAreaAnalyticsResult.value,\n byod: byodResult.value,\n },\n };\n};\n\n/**\n * Resolve every requested input kind, build the sandbox views, and surface the resolved entries to the\n * caller. The caller (analyseData / processData) enforces \"at least one input must be set\" via the zod\n * schema; this helper enforces it again as a safety net.\n *\n * @ignore\n */\nexport const prepareMultiInputs = async (\n ids: MultiInputIds,\n state: ToolState,\n): Promise<{ value: PreparedInputs } | { error: string }> => {\n if (!hasAnyInput(ids)) {\n return {\n error:\n 'At least one of `placesEntryIDs`, `routesEntryIDs`, `incidentsEntryIDs`, ' +\n '`geometriesEntryIDs`, `trafficAreaAnalyticsEntryIDs`, `byodEntryIDs` must be set. ' +\n 'Use `recallState` (optionally with a `kind`) to list available ids.',\n };\n }\n\n const resolved = resolveAllSlices(ids, state);\n if ('error' in resolved) return resolved;\n\n let geometries: PolygonFeature[] | undefined;\n let geometriesMeta: GeometriesMeta = { affectedEntries: [], sourceIds: [], skipped: [] };\n if (ids.geometriesEntryIDs?.length) {\n const collected = await collectInputGeometries(ids.geometriesEntryIDs, state);\n if ('error' in collected) return collected;\n geometries = collected.value.geometries;\n geometriesMeta = {\n affectedEntries: collected.value.affectedEntries,\n sourceIds: collected.value.contributingSourceIds,\n skipped: collected.value.skipped,\n };\n }\n\n return {\n value: {\n resolved: resolved.value,\n sandbox: buildSandboxInputs(resolved.value, geometries),\n geometries: geometriesMeta,\n },\n };\n};\n\n// Assemble the sandbox-facing views from the resolved entries + collected geometries. Pulled out of\n// `prepareMultiInputs` so its cognitive complexity stays under the SonarQube cap (same reason as\n// `resolveAllSlices`). Every kind is a single `<kind>ByEntry` record keyed by entry id — there is no\n// flat/merged companion. `geometries` is the one kind not resolved per-slice: its per-source view is a\n// partition of the collected flat list by `_source` (see partitionGeometriesBySource).\nconst buildSandboxInputs = (resolved: ResolvedEntries, geometries: PolygonFeature[] | undefined): SandboxInputs => ({\n placesByEntry: resolved.places.length\n ? Object.fromEntries(resolved.places.map((e) => [e.id, { type: 'FeatureCollection', features: [...e.data] }]))\n : undefined,\n routesByEntry: resolved.routes.length\n ? Object.fromEntries(\n resolved.routes.map((e) => [e.id, { type: 'FeatureCollection', features: [...e.data.features] }]),\n )\n : undefined,\n incidentsByEntry: resolved.incidents.length\n ? Object.fromEntries(resolved.incidents.map((e) => [e.id, [...e.data]]))\n : undefined,\n geometriesByEntry: partitionGeometriesBySource(geometries),\n trafficAreaAnalyticsByEntry: resolved.trafficAreaAnalytics.length\n ? Object.fromEntries(resolved.trafficAreaAnalytics.map((e) => [e.id, e.data]))\n : undefined,\n byodByEntry: resolved.byod.length ? Object.fromEntries(resolved.byod.map((e) => [e.id, e.data])) : undefined,\n});\n\n// Partition the collected polygons into a per-source view, keyed by the tagged source `${kind}:${id}`.\n// The geometries slot is polymorphic: one input (`geometriesEntryIDs`) spanning `place` (a single place\n// id — not an entry in any slice), `places` (expands to many footprints), `ranges`, and `customGeometries`.\n// So unlike every other kind there's no single slice whose `.entries` map to it — it can't be resolved\n// per-entry the way places/routes/etc. are. The one thing common to all four sources is the `_source` tag\n// `collectInputGeometries` stamps on each feature, so we group the flat list by that. The composite key is\n// unambiguous: the same id can recur across kinds, so a bare id would collide.\nconst partitionGeometriesBySource = (\n geometries: readonly PolygonFeature[] | undefined,\n): Record<string, PolygonFeature[]> | undefined => {\n if (!geometries?.length) return undefined;\n const byEntry: Record<string, PolygonFeature[]> = {};\n for (const feature of geometries) {\n const source = feature.properties?._source as GeometriesId | undefined;\n const key = source ? `${source.kind}:${source.id}` : 'unknown';\n const group = byEntry[key] ?? [];\n group.push(feature);\n byEntry[key] = group;\n }\n return byEntry;\n};\n\nconst resolveSliceEntries = <E extends { id: string }>(\n ids: readonly string[] | undefined,\n slice: { entries: readonly E[] },\n hints: { missingId: (id: string) => string },\n): { value: E[] } | { error: string } => {\n if (!ids?.length) return { value: [] };\n const out: E[] = [];\n for (const id of ids) {\n const found = slice.entries.find((entry) => entry.id === id);\n if (!found) return { error: hints.missingId(id) };\n out.push(found);\n }\n return { value: out };\n};\n\n/**\n * LLM-facing doc string that spells out which turf/h3 operations bridge across\n * input kinds. The merged tools both expose `places` (Points) / `routes`\n * (LineStrings) / `incidents` (Points + LineStrings) / `geometries` (Polygons\n * / MultiPolygons) / `trafficAreaAnalytics` (Polygon tiles with metrics) to\n * the same sandbox — but the model frequently assumes those slots are\n * isolated and gives up. Including this in the `code` field description\n * unblocks the common cross-kind work.\n *\n * @ignore\n */\nconst CROSS_KIND_OPS_DOC =\n 'CROSS-KIND OPS — `turf` and `h3` operate on EVERY feature regardless of which entry/kind it came from. ' +\n 'You can freely combine features from `placesByEntry` (Points), `routesByEntry` (LineStrings), ' +\n '`incidentsByEntry` (Point or LineString), `geometriesByEntry` (Polygon / MultiPolygon), ' +\n '`byodByEntry` (customer GeoJSON — Point / LineString / Polygon, mixed), and ' +\n '`trafficAreaAnalyticsByEntry` (Polygon tiles with `properties` like `congestionLevel` / `speed` / ' +\n '`travelTime` / `freeFlowSpeed`) in the same call. Pull features out of a record first — one entry ' +\n '`placesByEntry[id].features`, or all entries `Object.values(placesByEntry).flatMap(fc => fc.features)` ' +\n '(`Object.values(incidentsByEntry).flat()` for the array kinds). Common bridges:\\n' +\n '• Point ↔ LineString — `turf.pointToLineDistance(point, lineFeature, { units: \"meters\" })`, ' +\n '`turf.nearestPointOnLine(line, point)`.\\n' +\n '• Point ↔ Polygon — `turf.booleanPointInPolygon(point, poly)`, ' +\n '`turf.pointsWithinPolygon(turf.featureCollection(points), poly)` — POINT-ONLY (throw \"coord must be ' +\n 'GeoJSON Point\" on a line/polygon).\\n' +\n '• ANY feature ↔ Polygon (containment / overlap) — `incidentsByEntry` and `byodByEntry` mix geometry ' +\n 'types, so when you group features into polygons (e.g. incidents per BYOD district) use ' +\n '`turf.booleanIntersects(feature, poly)`, which accepts Point / LineString / Polygon alike. Only use ' +\n '`booleanPointInPolygon` after a `feature.geometry.type === \"Point\"` guard, or first collapse a ' +\n 'line/area to a point with `turf.pointOnFeature(feature)`.\\n' +\n '• LineString ↔ Polygon — `turf.lineIntersect(line, poly)`, `turf.booleanCrosses(line, poly)`, ' +\n '`turf.lineSplit(line, poly)`.\\n' +\n '• Polygon ↔ Polygon — `turf.union/intersect/difference(turf.featureCollection([a, b]))` (Turf v7 takes a single collection).\\n' +\n '• Point/LineString ↔ trafficAreaAnalytics tile — `turf.booleanPointInPolygon(p, tile)` for points; ' +\n \"`turf.lineIntersect(routeLine, tile)` for routes; iterate a tile collection's `.features` \" +\n '(e.g. `trafficAreaAnalyticsByEntry[id].features`) to find which tile a place/route segment falls into ' +\n 'and read its metric (`tile.properties.congestionLevel`, etc.).\\n' +\n '• Buffer to bridge kinds — `turf.buffer(anyFeature, meters/1000, { units: \"kilometers\" })` turns a point or ' +\n 'line into a polygon you can then filter against.\\n' +\n '• Bbox of anything — `turf.bbox(featureOrCollection)`.\\n' +\n '• Centroid for Point↔shape distance — `turf.centroid(polygonOrLine).geometry.coordinates`.\\n' +\n '• h3 — `h3.latLngToCell(lat, lng, res)` for any Point; `h3.polygonToCells(poly.geometry.coordinates, res)` ' +\n 'for any Polygon; for a LineString iterate `geometry.coordinates` and bin each `[lng, lat]`.\\n' +\n 'Note: arguments to turf functions are GeoJSON Features — pass the whole feature ' +\n '(`placesByEntry[id].features[i]`, `routesByEntry[id].features[0]`, `geometriesByEntry[key][i]`, ' +\n '`incidentsByEntry[id][i]`, `trafficAreaAnalyticsByEntry[id].features[i]`), not the bare ' +\n '`geometry.coordinates` (a few helpers accept coords but most expect Features). Always check the function ' +\n 'signature at https://turfjs.org/docs/.';\n\n/**\n * Describes the per-entry input shape, surfaced by {@link buildSandboxToolsDoc} for every sandbox tool.\n * Each requested kind is a single `<kind>ByEntry` record keyed by entry id — there is no flat/merged\n * companion, so the model must access one entry by id or merge across entries explicitly. Spelling out\n * the merge idioms keeps the model from assuming a bare `places`/`routes` array exists (it doesn't).\n *\n * @ignore\n */\nexport const BY_ENTRY_VIEWS_DOC =\n 'INPUTS (per-entry) — each requested kind is an object keyed by the id you passed in `<kind>EntryIDs`: ' +\n '`placesByEntry`, `routesByEntry`, `incidentsByEntry`, `trafficAreaAnalyticsByEntry`, `byodByEntry`. ' +\n \"There is NO flat `places`/`routes`/… — only these per-entry records. Each value is that one entry's data: \" +\n 'a FeatureCollection (`placesByEntry`, `routesByEntry`, `byodByEntry`), a TrafficAreaAnalytics ' +\n 'FeatureCollection (`trafficAreaAnalyticsByEntry`), or a plain array (`incidentsByEntry`).\\n' +\n '• One entry — index by id: `placesByEntry[\"places-2\"]` (a FeatureCollection → `turf.bbox(placesByEntry[\"places-2\"])`).\\n' +\n '• All requested entries of a kind — merge with `Object.values(...)`: ' +\n '`Object.values(placesByEntry).flatMap(fc => fc.features)` for FeatureCollection kinds (wrap with ' +\n '`turf.featureCollection(...)` before passing to turf), `Object.values(incidentsByEntry).flat()` for arrays.\\n' +\n '• Per-entry / comparative work (\"count per entry\", \"which route set is faster\") — iterate the record\\'s ' +\n 'entries (`Object.entries(routesByEntry)`) so results stay labelled by source id.\\n' +\n '`geometriesByEntry` is the odd one out: keyed by the tagged source `${kind}:${id}` (e.g. ' +\n '`\"customGeometries:abc\"`) rather than a bare id, since geometries mixes sources — each feature also ' +\n 'carries `properties._source` (`{ kind, id }`).';\n\n/**\n * Shared `routeUtils` reference doc, surfaced by both tools when `routes` is in scope. @ignore\n */\nconst ROUTE_UTILS_DOC =\n '`routeUtils` (route section / progress helpers, only useful when `routesEntryIDs` is set; each takes a ' +\n 'single route feature, e.g. `routesByEntry[id].features[0]`; for a plain bounding box use `turf.bbox(route)`):\\n' +\n '• `getSectionBBox(route, section)` → `[W,S,E,N]`. Sections at `route.properties.sections.<type>`.\\n' +\n '• `getRouteProgressForSection(route, section)` / `getRouteProgressBetween(route, startIdx, endIdx)`.\\n' +\n '• `calculateProgressAtRoutePoint(route, pathIndex)`.\\n' +\n '• `getCoordinateAtRouteProgress(route, query)` — `query`: `{ traveledDistanceInMeters }` | `{ traveledTimeInSeconds }` | `{ clockTime: Date }`.\\n' +\n '• `getProgressAtNearestRoutePoint(route, { lng, lat })` — snap a point to the line.';\n\n/**\n * Names injected into the sandbox by analyseData / processData, in fixed order.\n *\n * @ignore\n */\nexport const MULTI_INPUT_SANDBOX_PARAMS = [\n 'placesByEntry',\n 'routesByEntry',\n 'incidentsByEntry',\n 'geometriesByEntry',\n 'trafficAreaAnalyticsByEntry',\n 'byodByEntry',\n 'h3',\n 'turf',\n 'routeUtils',\n 'cluster',\n] as const;\n\n/**\n * Shared `cluster` reference doc, surfaced by both tools when `incidents` is in scope. @ignore\n */\nconst CLUSTER_PRIMITIVE_DOC =\n '`cluster(incidents, params?, previous?, now?)` — DBSCAN clustering with stable IDs + trends (only ' +\n 'useful with `incidentsEntryIDs`; `incidents` is an array — get it via ' +\n '`Object.values(incidentsByEntry).flat()` or one entry `incidentsByEntry[id]`; filter first, then cluster).\\n' +\n '• `params`: `{ eps?, minMembers?, maxClusters?, preFilter? }` — `eps` km radius (default 0.5; ' +\n '0.3–0.5 urban, 1+ highways), `minMembers` (default 3), `maxClusters` top-N by delay (default 6).\\n' +\n '• Returns `{ groups: [{ id, centroid, memberIds, size, totalDelaySeconds, peakDelaySeconds, ' +\n 'diameterKm, primaryRoads, primaryCategory, trend }], sampledAt }`.\\n' +\n '• Live trends: pass the injected `previous` + `now` — `return cluster(incidents, { eps: 0.4 }, previous, now);`. Omit both for one-shot.';\n\n/**\n * Common \"tools available in the sandbox\" prompt section, shared verbatim by\n * analyseData and processData so the injected-library docs live in ONE place and the\n * two tools never drift. It documents the three function namespaces handed to every\n * sandbox body — `turf` / `h3` (base usage rules via {@link buildSandboxCodePrompt}),\n * the cross-kind turf cheat-sheet ({@link CROSS_KIND_OPS_DOC}), and `routeUtils`\n * ({@link ROUTE_UTILS_DOC}) — in a fixed order. The two scope-gated docs are appended\n * only when relevant: cross-kind when scope commits to more than one kind, routeUtils\n * when `routes` is in scope (matching the conditional `routeUtils` usefulness). Returns\n * a block ending in a blank line, ready to drop straight into either tool's `code` doc.\n *\n * @ignore\n */\nexport const buildSandboxToolsDoc = (active: readonly EntryDataKind[], scoped: boolean): string => {\n const crossKindDoc = scoped && active.length > 1 ? `\\n\\n${CROSS_KIND_OPS_DOC}` : '';\n const routeUtilsDoc = scoped && active.includes('routes') ? `\\n\\n${ROUTE_UTILS_DOC}` : '';\n const clusterDoc = scoped && active.includes('incidents') ? `\\n\\n${CLUSTER_PRIMITIVE_DOC}` : '';\n // Per-entry input guidance is ungated: every multi-input call receives the `<kind>ByEntry` records and\n // must know to index by id or merge with `Object.values`, regardless of how many entries are in scope.\n return `${buildSandboxCodePrompt(MULTI_INPUT_SANDBOX_PARAMS)}\\n\\n${BY_ENTRY_VIEWS_DOC}${crossKindDoc}${routeUtilsDoc}${clusterDoc}\\n\\n`;\n};\n\n/**\n * Pack `prepared.sandbox` into the positional argument array expected by\n * `runSandboxedFn`, in the same order as {@link MULTI_INPUT_SANDBOX_PARAMS}.\n *\n * The merged / per-entry views are shallow — their feature objects are the SAME\n * references held in the entry slices — so sandbox code must not mutate them in\n * place or it would corrupt live app state. We do NOT deep-copy here: the copy is\n * the executor's job, done exactly once and only where needed — the main-thread\n * executor `structuredClone`s the data args itself, while the iframe-worker\n * executor gets a copy for free as `postMessage` clones them across the worker\n * boundary. Cloning here too would be redundant double-work. Trailing `h3` / `turf` /\n * `routeUtils` / `cluster` are read-only function references, passed through untouched —\n * `routeUtils` is the shared {@link routeUtils} set and `cluster` is the\n * {@link clusterIncidents} primitive; both are injected into both tools. (In the\n * iframe-worker path these positions are dropped and refilled from the worker's own\n * globals — see {@link WORKER_PROVIDED_PARAMS} — so they must stay function references here.)\n *\n * @ignore\n */\nexport const packSandboxArgs = (sandbox: SandboxInputs, libs: { h3: unknown; turf: unknown }): readonly unknown[] => [\n sandbox.placesByEntry,\n sandbox.routesByEntry,\n sandbox.incidentsByEntry,\n sandbox.geometriesByEntry,\n sandbox.trafficAreaAnalyticsByEntry,\n sandbox.byodByEntry,\n libs.h3,\n libs.turf,\n routeUtils,\n clusterIncidents,\n];\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { MultiPolygon, Polygon } from 'geojson';\nimport type { ToolState } from '../../types';\n\n// Looks up a stored range entry and returns each origin's outermost polygon. Entries store each\n// origin's full polygon FeatureCollection — `features[0]` is the largest budget when multi-budget,\n// so one polygon per origin covers the entry's full reachable area. Shared by getTrafficIncidents\n// (maps each polygon to a bbox) and discoverPlaces (feeds the polygons straight into the search).\n// `range` stays caller-side rather than in resolveAreas because it maps to stored isochrones, not a\n// geocoded area, and the two consumers want different output shapes.\nexport const getRangePolygons = (\n state: ToolState,\n rangeId: string,\n): { polygons: (Polygon | MultiPolygon)[] } | { error: string } => {\n const rangesEntry = state.ranges.entries.find((e) => e.id === rangeId);\n if (!rangesEntry || rangesEntry.data.length === 0) {\n return { error: `Range \"${rangeId}\" not found. Use recallState to list available ranges.` };\n }\n const polygons = rangesEntry.data\n .map((r) => r.polygon?.features[0]?.geometry)\n .filter((g): g is Polygon | MultiPolygon => !!g);\n if (polygons.length === 0) {\n return { error: `Range \"${rangeId}\" has no polygons. Recompute via findReachableAreas.` };\n }\n return { polygons };\n};\n","/**\n * @module agent-toolkit-classifier\n *\n * Query intent classification for automatic tool selection.\n *\n * Uses LLM-based structured generation to map a user query directly to the\n * minimal set of tool names needed to answer it.\n *\n * The classifier prompt is built dynamically from each tool's `relatedTools`\n * in the tool metadata, so it stays in sync automatically when tools are\n * added or renamed.\n */\n\nimport { generateText, type LanguageModel, type ModelMessage, Output, type TextPart } from 'ai';\nimport { z } from 'zod';\nimport type { Classifier, ClassifierContext, ToolMetadata } from '../types';\nimport type { ClassificationResult, ClassifierOptions } from './types';\n\n/**\n * A single turn in the conversation passed to {@link classifyUserIntent}.\n *\n * @ignore\n */\nexport type ConversationMessage = { role: 'user' | 'assistant'; content: string };\n\n/** Max character length for each history message when compacting for the classifier. */\nconst MAX_HISTORY_MESSAGE_LENGTH = 300;\n\n/** Number of most-recent history messages (excluding the last user message) passed to the classifier. */\nconst MAX_CLASSIFIER_HISTORY_MESSAGES = 8;\n\n/** Formats a single tool's metadata into a compact classifier prompt entry. */\nconst formatToolEntry = (name: string, entry: ToolMetadata): string => {\n const classificationPrompt = entry.classificationPrompt ? ` ${entry.classificationPrompt}.` : '';\n // const related = entry.relatedTools?.length ? ` Related: ${entry.relatedTools.join(', ')}.` : '';\n const depends = entry.dependsOn?.length ? ` Depends on: ${entry.dependsOn.join(', ')}.` : '';\n const scope = entry.scopePrompt ? ` SCOPE: ${entry.scopePrompt}` : '';\n return `${name}${classificationPrompt}${depends}${scope}`;\n};\n\n// Names of tools whose metadata declares a `scopePrompt` — used to surface the per-turn\n// scope contract in the classifier system prompt.\nconst scopableToolNames = (toolsMetadata: Record<string, ToolMetadata>): string[] =>\n Object.entries(toolsMetadata)\n .filter(([, meta]) => meta.scopePrompt)\n .map(([name]) => name);\n\n/**\n * Builds the system prompt listing available tools for the classifier.\n *\n * @ignore\n */\nexport const buildClassifySystemPrompt = (toolsMetadata: Record<string, ToolMetadata>): string => {\n const toolEntries = Object.entries(toolsMetadata).map(([name, entry]) => formatToolEntry(name, entry));\n const scopable = scopableToolNames(toolsMetadata);\n const scopeContract = scopable.length\n ? [\n '',\n 'TOOL SCOPES — MANDATORY when selecting a scopable tool:',\n `The following tools REQUIRE a scope entry in \\`toolScopes\\` whenever they appear in \\`tools\\`: ${scopable.join(', ')}.`,\n \"For each picked scopable tool, emit `toolScopes[<name>]` with the per-tool object shape described in its SCOPE hint above. Don't omit. Don't leave it implicit.\",\n 'If the user query truly needs all input kinds the tool supports, still emit the scope explicitly — list every kind. Never skip the scope entry when picking the tool.',\n ]\n : [];\n\n return [\n 'You are a tool selector for a map assistant.',\n 'You will receive a conversation. Select the minimal set of tools needed to fulfill the LAST user message.',\n // A picked tool can only read data that already exists in state. `Depends on:` lists the tools\n // that PRODUCE that data — without co-picking them the model is hard-restricted and cannot load\n // the prerequisite, so it invents entry ids and fails. The \"unless already satisfied\" clause keeps\n // this from over-selecting when the data was loaded on an earlier turn.\n 'PREREQUISITES: each tool below may list `Depends on: <tools>` — the tools that produce the data it ' +\n 'reads. When you pick a tool, ALSO pick every tool in its `Depends on:` list, UNLESS the ' +\n 'conversation shows that prerequisite is already satisfied (the data was loaded, or the place ' +\n 'located, on an earlier turn). E.g. arming a tracker/alert on a fresh area needs getTrafficIncidents ' +\n '(to load the incidents) and locatePlace (for a named area) picked alongside it.',\n `LOCATION REFERENCE:\n - \"near me\" / \"my location\" / \"where I am\" → getCurrentLocation (user's physical GPS position, may prompt permission)\n - \"in this area\" / \"here on the map\" / \"near the map center\" → getViewport (map's current view)\n - If getCurrentLocation fails due to permission denial, fall back to getViewport as the reference point`,\n '',\n 'Available tools:',\n ...toolEntries,\n ...scopeContract,\n ].join('\\n');\n};\n\n// Per-call output schema. Scopable tools (those whose metadata carries a `scopePrompt`) must\n// appear in `toolScopes` when selected — the LLM cannot opt out.\n//\n// IMPORTANT: We can't use `z.record(z.string(), z.unknown())` for `toolScopes`. Azure / OpenAI\n// strict structured-output mode requires every object property to be declared explicitly with\n// `additionalProperties: false`, and `z.record` doesn't translate to that. Instead we build an\n// explicit `z.object({ [scopableToolName]: scopeShape.optional(), … })` with one optional field\n// per known scopable tool. The value shape is loose (`{ kinds: string[] }`) at the classifier\n// level — final validation against the tool's real `scopeSchema` happens in `prepareStep`.\n//\n// The `superRefine` then enforces \"if you picked tool X and X is scopable, you must emit\n// toolScopes[X]\". The AI SDK's structured-output retry loop sees that as a typed validation error\n// and re-runs with the message pointing at the missing entry.\nconst looseScopeShape = z.object({\n kinds: z\n .array(z.string())\n .min(1)\n .describe(\"Entry-data kinds the turn touches (one of the kinds listed in the tool's SCOPE hint).\"),\n});\n\n// Human-readable hint of the expected response shape, embedded in the system prompt. The model\n// produces any JSON (we use `output: 'no-schema'` to avoid attaching a strict schema to the API\n// request); this hint plus the SCOPE blocks already in the system prompt give it enough structure\n// to consistently emit something our zod schema can validate.\nconst buildExpectedShapeHint = (scopableNames: ReadonlySet<string>): string => {\n if (scopableNames.size === 0) {\n return '{ \"tools\": [\"toolName\", ...] }';\n }\n const scopeKeys = [...scopableNames].map((n) => ` \"${n}\": { \"kinds\": [...] } | null`).join(',\\n');\n return `{\n \"tools\": [\"toolName\", ...],\n \"toolScopes\": {\n${scopeKeys}\n }\n}\nFor every scopable tool you LIST in \"tools\", the matching \"toolScopes\" entry MUST be a non-null object \\`{ \"kinds\": [...] }\\`. For scopable tools you DON'T pick, set the corresponding \"toolScopes\" entry to \\`null\\`.`;\n};\n\nconst buildClassifySchema = (scopableNames: ReadonlySet<string>) => {\n const base = z.object({\n tools: z\n .array(z.string()) // using string to save tokens, since tool names already included in system prompt\n .min(1)\n .describe('Tool names needed to fulfill the query.'),\n });\n\n if (scopableNames.size === 0) {\n return base;\n }\n\n // Each scopable tool gets a `.nullable()` field (not `.optional()`). Reason: Azure / OpenAI\n // strict structured-output mode requires every property to be in the JSON schema's `required`\n // list — `optional` maps to \"may be absent\" which strict mode rejects. `nullable` lets the\n // LLM emit `null` for unused entries while keeping the field listed in `required`.\n // We use `generateObject` with `mode: 'json'` below which doesn't require strict-mode\n // semantics, but keeping the schema strict-mode-clean future-proofs against runtime mode\n // switches.\n const toolScopesShape: Record<string, z.ZodNullable<typeof looseScopeShape>> = {};\n for (const name of scopableNames) {\n toolScopesShape[name] = looseScopeShape.nullable();\n }\n const toolScopesObject = z\n .object(toolScopesShape)\n .nullable()\n .describe(\n 'Per-tool scope. One field per scopable tool; MANDATORY (non-null) for every scopable tool you list in ' +\n \"`tools`. For tools you didn't pick, set the corresponding field to `null`. See each tool's SCOPE hint.\",\n );\n\n return base.extend({ toolScopes: toolScopesObject }).superRefine((data, ctx) => {\n const scopes: Record<string, unknown> = (data.toolScopes ?? {}) as Record<string, unknown>;\n for (const name of data.tools) {\n if (!scopableNames.has(name)) continue;\n const value = scopes[name];\n if (value === null || value === undefined) {\n ctx.addIssue({\n code: 'custom',\n path: ['toolScopes', name],\n message: `Tool \"${name}\" requires \\`toolScopes.${name}\\` — emit it per the SCOPE hint (don't leave null when picking).`,\n });\n }\n }\n });\n};\n\n/**\n * Calls the LLM with structured output to select tools for the conversation. Tries up to\n * `MAX_CLASSIFY_ATTEMPTS` times — the structured-output mode rejects classifications missing\n * mandatory scope, and on rejection the LLM gets a retry with the validation error pointing at\n * the missing entry. If every attempt still fails, returns the latest tool list without scopes\n * and lets `prepareStep` fall back to the (terse) unscoped surface.\n */\nconst MAX_CLASSIFY_ATTEMPTS = 2;\n\ntype Usage = ClassificationResult['usage'];\ntype Partial = { tools?: unknown; toolScopes?: unknown } | null | undefined;\ntype Soft = { tools: string[]; scopes?: Record<string, unknown> } | undefined;\n\nconst addUsage = (\n target: Usage,\n source: { inputTokens?: number; outputTokens?: number; totalTokens?: number } | undefined,\n): void => {\n if (!source) return;\n target.inputTokens += source.inputTokens ?? 0;\n target.outputTokens += source.outputTokens ?? 0;\n target.totalTokens += source.totalTokens ?? 0;\n};\n\n// Pull a (best-effort) `{ tools, scopes }` pair out of whatever the LLM returned. Used by both\n// the \"valid-shape but failed local validation\" and \"generateObject threw\" paths to populate the\n// soft-fallback when every attempt fails. Returns `undefined` when nothing looks usable.\nconst extractPartialTools = (partial: Partial): Soft => {\n if (!partial || !Array.isArray(partial.tools) || !partial.tools.every((tool) => typeof tool === 'string')) {\n return undefined;\n }\n const scopes =\n partial.toolScopes && typeof partial.toolScopes === 'object'\n ? (partial.toolScopes as Record<string, unknown>)\n : undefined;\n return { tools: partial.tools, ...(scopes && { scopes }) };\n};\n\n// Compact a successful classification: drop the `null` placeholders in `toolScopes` (the \"not\n// picked\" sentinel) so prepareStep's `applyToolScopes` doesn't churn through no-ops.\nconst compactSuccess = (\n parsed: { tools: string[]; toolScopes?: Record<string, unknown> | null },\n usage: Usage,\n): { tools: string[]; toolScopes?: Record<string, unknown>; usage: Usage } => {\n const cleanedScopes = parsed.toolScopes\n ? Object.fromEntries(Object.entries(parsed.toolScopes).filter(([, value]) => value != null))\n : undefined;\n return {\n tools: parsed.tools,\n ...(cleanedScopes && Object.keys(cleanedScopes).length > 0 && { toolScopes: cleanedScopes }),\n usage,\n };\n};\n\nconst classifyQueryToTools = async (\n conversation: ConversationMessage[],\n model: LanguageModel,\n toolsMetadata: Record<string, ToolMetadata>,\n): Promise<{ tools: string[]; toolScopes?: Record<string, unknown>; usage: Usage }> => {\n const scopableNames = new Set(scopableToolNames(toolsMetadata));\n const schema = buildClassifySchema(scopableNames);\n const aggregateUsage: Usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\n\n // System prompt embeds the expected JSON shape so the model produces something we can validate\n // locally. We use `output: 'no-schema'` below (json_object mode on Azure / OpenAI), which means\n // the API doesn't see our zod schema at all — strict-mode-friendly by construction.\n const expectedShape = buildExpectedShapeHint(scopableNames);\n const system = `${buildClassifySystemPrompt(toolsMetadata)}\\n\\nReturn JSON matching this shape exactly:\\n${expectedShape}`;\n\n let soft: Soft;\n for (let attempt = 0; attempt < MAX_CLASSIFY_ATTEMPTS; attempt++) {\n try {\n // `Output.json()` puts the model into json-output mode without attaching a JSON Schema\n // to the request — strict-mode-friendly on Azure/OpenAI (their strict mode rejects\n // several Zod constructs we use). We validate locally against our zod schema after.\n const generated = await generateText({\n model,\n output: Output.json(),\n system,\n messages: conversation,\n maxOutputTokens: 400,\n });\n addUsage(aggregateUsage, generated.usage);\n\n const parsed = schema.safeParse(generated.output);\n if (parsed.success) {\n return compactSuccess(\n parsed.data as { tools: string[]; toolScopes?: Record<string, unknown> | null },\n aggregateUsage,\n );\n }\n // Validation failed locally — capture the partial for soft-fallback and retry.\n soft = extractPartialTools(generated.output as Partial) ?? soft;\n } catch (error) {\n // Best-effort: `generateText` may reject with `{ value, usage }` on output-validation\n // failures; capture both so the soft-fallback below still has something useful and\n // usage stays accurate across the retry.\n const errRecord = error as { value?: unknown; usage?: typeof aggregateUsage };\n addUsage(aggregateUsage, errRecord.usage);\n soft = extractPartialTools(errRecord.value as Partial) ?? soft;\n }\n }\n\n if (!soft) {\n throw new Error(`Classifier failed after ${MAX_CLASSIFY_ATTEMPTS} attempts to produce a valid response.`);\n }\n return {\n tools: soft.tools,\n ...(soft.scopes && { toolScopes: soft.scopes }),\n usage: aggregateUsage,\n };\n};\n\n/**\n * Classify a user conversation to determine which tools to activate.\n *\n * The `conversation` should contain the last user message plus a compaction\n * of the previous chat history (each truncated to save tokens).\n *\n * @example\n * ```typescript\n * const result = await classifyUserIntent(conversation, {\n * chatModel: openai('gpt-4o-mini'),\n * toolsMetadata,\n * });\n * // result.activeToolNames contains the selected tools\n * ```\n *\n * @group Agent Toolkit\n */\nexport const classifyUserIntent = async (\n conversation: ConversationMessage[],\n options: ClassifierOptions,\n): Promise<ClassificationResult> => {\n const { chatModel, toolsMetadata } = options;\n const startTime = performance.now();\n\n const { tools, toolScopes, usage } = await classifyQueryToTools(conversation, chatModel, toolsMetadata);\n\n const activeToolNames = tools;\n\n const timeMs = performance.now() - startTime;\n\n return {\n activeToolNames,\n ...(toolScopes && { toolScopes }),\n timeMs,\n usage,\n };\n};\n\n/** Type predicate that narrows a message content part to a TextPart. */\nconst isTextPart = (part: { type: string }): part is TextPart => part.type === 'text';\n\n/**\n * Extracts plain text from a message's content (string or structured content array).\n *\n * @ignore\n */\nexport const extractMessageText = (content: string | Array<{ type: string }>): string | null => {\n if (typeof content === 'string') return content;\n\n const textParts = content.filter(isTextPart);\n if (textParts.length > 0) return textParts.map((part) => part.text).join('');\n\n return null;\n};\n\n/**\n * Extracts the text content of the last user message in a message array.\n * Returns null if no user message is found or if content has no text.\n *\n * @group Agent Toolkit\n */\nexport const extractLastUserText = (messages: ReadonlyArray<ModelMessage>): string | null => {\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n return extractMessageText(messages[i].content);\n }\n }\n return null;\n};\n\n/**\n * Creates the default LLM-based classifier. Uses structured generation to select\n * the minimal tool set for each user message.\n *\n * @param options.model - Language model for classification (ideally fast/cheap like gpt-4o-mini)\n * @param options.maxHistoryMessages - Max prior turns for context. Default: 8\n * @param options.maxHistoryMessageLength - Max chars per history message. Default: 300\n *\n * @group Agent Toolkit\n */\nexport const createDefaultClassifier = (options: {\n model: LanguageModel;\n maxHistoryMessages?: number;\n maxHistoryMessageLength?: number;\n}): Classifier => {\n const { model, maxHistoryMessages, maxHistoryMessageLength } = options;\n\n return async (context: ClassifierContext): Promise<ClassificationResult | null> => {\n const { messages, toolsMetadata } = context;\n if (!messages?.length) return null;\n\n const historyLimit = maxHistoryMessages ?? MAX_CLASSIFIER_HISTORY_MESSAGES;\n const messageCharLimit = maxHistoryMessageLength ?? MAX_HISTORY_MESSAGE_LENGTH;\n\n // Find last user message index and extract text in one pass\n let lastUserIndex = -1;\n let lastUserText: string | null = null;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].role === 'user') {\n lastUserIndex = i;\n lastUserText = extractMessageText(messages[i].content);\n break;\n }\n }\n if (!lastUserText) return null;\n\n // Compact history\n const conversation: ConversationMessage[] = [];\n const historyStartIndex = Math.max(0, lastUserIndex - historyLimit);\n\n for (let i = historyStartIndex; i < lastUserIndex; i++) {\n const message = messages[i];\n if (message.role !== 'user' && message.role !== 'assistant') continue;\n\n const text = extractMessageText(message.content);\n if (!text) continue;\n\n const compacted = text.length > messageCharLimit ? text.slice(0, messageCharLimit) + '…' : text;\n conversation.push({ role: message.role, content: compacted });\n }\n\n conversation.push({ role: 'user', content: lastUserText });\n\n try {\n return await classifyUserIntent(conversation, { chatModel: model, toolsMetadata });\n } catch {\n return null;\n }\n };\n};\n","import type { Place, Places, ReachableRangeBudget, Routes, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { budgetUnits, formatDistance, formatDuration } from '@tomtom-org/maps-sdk/core';\n\n/**\n * Optional search context used to enrich a places history label.\n *\n * @ignore\n */\nexport type PlacesLabelContext = {\n query?: string;\n poiCategories?: string[];\n where?: string;\n /** Route label when results come from an along-route search (e.g. \"Amsterdam to Utrecht\"). */\n routeLabel?: string;\n};\n\nconst waypointAddress = (wp: WaypointLike): string | undefined => {\n if (Array.isArray(wp)) return undefined;\n // wp is the non-array branch of WaypointLike (a Waypoint feature or a HasLngLat object); only a\n // Waypoint carries address properties, so read the field structurally and let optional chaining\n // yield undefined for the plain-coordinate forms.\n return (wp as Place).properties?.address?.freeformAddress;\n};\n\n/** Builds a label for a Places FeatureCollection from its count and search context. */\nconst makeCollectionLabel = (count: number, context?: PlacesLabelContext): string => {\n const parts: string[] = [];\n\n if (context?.query) parts.push(`\"${context.query}\"`);\n if (context?.poiCategories?.length) parts.push(context.poiCategories.join(', '));\n if (context?.routeLabel) parts.push(`along ${context.routeLabel}`);\n else if (context?.where) parts.push(`in ${context.where}`);\n\n const countStr = `${count} ${count === 1 ? 'place' : 'places'}`;\n return parts.length ? `${parts.join(' ')} (${countStr})` : countStr;\n};\n\n/**\n * Generate a human-readable label for a places history entry.\n *\n * For a single Place the label is derived from result properties (name / address),\n * falling back to the query when properties are sparse.\n *\n * For a Places collection (FeatureCollection) the label is built from search\n * context (query, categories, location) plus the result count.\n *\n * @ignore\n */\nexport const makePlacesLabel = (data: Place | Places, context?: PlacesLabelContext): string => {\n if ('features' in data) return makeCollectionLabel(data.features.length, context);\n\n const name = data.properties?.poi?.name;\n const address = data.properties?.address?.freeformAddress;\n\n if (name && address) return `${name}, ${address}`;\n if (name) return name;\n if (address) return address;\n if (context?.query) return context.query;\n return 'Place';\n};\n\n/**\n * Grounded AREA label for a geocoded place: its `freeformAddress` with `, <countryCode>` appended\n * (when a code is present and the address doesn't already end with that exact `, <countryCode>`\n * suffix). Unlike {@link makePlacesLabel} it never uses the POI name or a query echo and always\n * carries the country code — it's the truthful \"where did this resolve\" label that disambiguates\n * same-name areas (e.g. \"London, GB\" vs \"London, CA\"). Returns undefined when the place has no\n * address.\n */\nexport const placeAreaLabel = (place: Place): string | undefined => {\n const address = place.properties?.address?.freeformAddress;\n if (!address) return undefined;\n const countryCode = place.properties?.address?.countryCode;\n if (!countryCode) return address;\n const suffix = `, ${countryCode}`;\n // Case-insensitive so an already-suffixed address isn't doubled by an odd-cased code.\n return address.toLowerCase().endsWith(suffix.toLowerCase()) ? address : `${address}${suffix}`;\n};\n\n/**\n * Generate a human-readable label for a routes history entry.\n *\n * @ignore\n */\nexport const makeRoutesLabel = (routes: Routes, waypoints: WaypointLike[]): string => {\n const summary = routes.features[0]?.properties?.summary;\n const duration = summary ? formatDuration(summary.travelTimeInSeconds) : undefined;\n const distance = summary ? formatDistance(summary.lengthInMeters) : undefined;\n const timeDistParts = [duration, distance].filter(Boolean);\n const timeDist = timeDistParts.length ? ` (${timeDistParts.join(', ')})` : '';\n\n const addresses = waypoints.map(waypointAddress).filter(Boolean) as string[];\n\n if (addresses.length >= 2) {\n const origin = addresses[0];\n const destination = addresses.at(-1);\n const vias = addresses.slice(1, -1);\n const path =\n vias.length > 0 ? `${origin} via ${vias.join(', ')} to ${destination}` : `${origin} to ${destination}`;\n return `${path}${timeDist}`;\n }\n\n return `Route${timeDist}`;\n};\n\n/**\n * Generate a human-readable label for a ranges history entry.\n * e.g. \"10/20/30 min from Amsterdam\" (one origin),\n * \"30 min from Amsterdam, Rotterdam\" (two origins),\n * \"30 min from 3 origins\" (more than two).\n *\n * @ignore\n */\nexport const makeRangesLabel = (budgets: ReachableRangeBudget[], originNames: string[]): string => {\n const sorted = [...budgets].sort((a, b) => a.value - b.value);\n const budgetStr = sorted.map((b) => `${b.value} ${budgetUnits[b.type]}`).join('/');\n const originStr =\n originNames.length === 1\n ? originNames[0]\n : originNames.length === 2\n ? originNames.join(', ')\n : `${originNames.length} origins`;\n return `${budgetStr} from ${originStr}`;\n};\n","/**\n * @module agent-toolkit-utils\n */\n\nimport {\n type CommonPlaceProps,\n getPosition,\n type Place,\n type Places,\n type PlaceType,\n type Route,\n type Routes,\n type Waypoint,\n type WaypointLike,\n} from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\n\n/** @ignore */\nexport type PlaceSummary = {\n id: string;\n type?: PlaceType; // Field added to prevent the LLM from infering the nature of a place e.g. intersections\n name?: string;\n address?: string;\n position: [number, number];\n};\n\n/** @ignore */\nexport type PlacesSummary = { count: number; features: PlaceSummary[] };\n\n/** Max number of place summaries echoed back to the LLM by summarizePlaces. */\nconst PLACES_SUMMARY_LIMIT = 5;\n\ntype RouteSummary = Route['properties']['summary'];\ntype LegSummary = Route['properties']['sections']['leg'][number]['summary'];\n\n// Maps Date fields to string for LLM-safe serialization (distributive so Date | undefined → string | undefined).\ntype StringDateValue<T> = T extends Date ? string : T;\n\n/** @ignore */\nexport type StringDates<T> = { [K in keyof T]: StringDateValue<T[K]> };\n\n/** @ignore */\nexport type SummarizedRoute = {\n index: number;\n summary: StringDates<RouteSummary>;\n /** Per-leg summaries; only present when the route has more than one leg. */\n legSummaries?: StringDates<LegSummary>[];\n};\n\n/** @ignore */\nexport type SummarizedRoutes = { count: number; routes: SummarizedRoute[] };\n\n/**\n * Converts a Place to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizePlace = (place: Place): PlaceSummary => ({\n id: place.id,\n type: place.properties?.type,\n name: place.properties?.poi?.name,\n address: place.properties?.address?.freeformAddress,\n position: (place.geometry?.coordinates ?? [0, 0]) as [number, number],\n});\n\n/**\n * Converts Places to a token-efficient summary for LLM consumption.\n * Returns the total count plus the first {@link PLACES_SUMMARY_LIMIT} summaries\n * to keep the tool output bounded regardless of result size.\n *\n * @ignore\n */\nexport const summarizePlaces = (places: Places | Place[]): PlacesSummary => {\n const features = Array.isArray(places) ? places : places.features;\n return {\n count: features.length,\n features: features.slice(0, PLACES_SUMMARY_LIMIT).map(summarizePlace),\n };\n};\n\n/**\n * Converts a Route to a token-efficient summary for LLM consumption.\n * Returns only the route summary and per-leg summaries (when more than one leg).\n *\n * @ignore\n */\nexport const summarizeRoute = (route: Route): SummarizedRoute => {\n const { summary, sections, index } = route.properties;\n const legs = sections.leg;\n\n const stringifyDates = <T extends { arrivalTime: Date; departureTime: Date }>(summary: T): StringDates<T> =>\n ({\n ...summary,\n arrivalTime: summary.arrivalTime.toISOString(),\n departureTime: summary.departureTime.toISOString(),\n }) as StringDates<T>;\n\n return {\n index,\n summary: stringifyDates(summary),\n ...(legs.length > 1 && { legSummaries: legs.map((leg) => stringifyDates(leg.summary)) }),\n };\n};\n\n/**\n * Converts Routes to a token-efficient summary for LLM consumption.\n *\n * @ignore\n */\nexport const summarizeRoutes = (routes: Routes): SummarizedRoutes => {\n const count = routes.features.length;\n\n return {\n count,\n routes: routes.features.map(summarizeRoute),\n };\n};\n\n/** @ignore */\nexport const waypointSummarySchema = z.object({\n slotIndex: z.number(),\n isFilled: z.boolean(),\n address: z.string().optional(),\n position: z.array(z.number()).length(2).optional().describe('[longitude, latitude]'),\n});\n\n/**\n * Token-efficient summary of a waypoint for LLM consumption.\n *\n * @ignore\n */\nexport const summarizeWaypoint = (\n waypoint: WaypointLike | null | undefined,\n): z.infer<typeof waypointSummarySchema> | null => {\n if (waypoint === null || waypoint === undefined) {\n return null;\n }\n\n const position = getPosition(waypoint);\n let address: string | undefined;\n if (!Array.isArray(waypoint)) {\n address = (waypoint as Waypoint<CommonPlaceProps>).properties?.address?.freeformAddress;\n }\n\n return {\n slotIndex: -1,\n isFilled: true,\n ...(address && { address }),\n ...(position && { position }),\n };\n};\n\n/**\n * Formats a date as its UTC `YYYY-MM-DD` day, or `''` when it is missing or invalid (an entry built without date\n * metadata).\n *\n * @ignore\n */\nexport const isoDate = (value: Date | undefined): string => {\n if (!value || Number.isNaN(value.getTime())) return '';\n return value.toISOString().slice(0, 10);\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { BBox, Place } from '@tomtom-org/maps-sdk/core';\nimport { bboxCenter, bboxFromGeoJSON, getPosition } from '@tomtom-org/maps-sdk/core';\nimport * as turf from '@turf/turf';\nimport type { Feature, MultiPolygon, Polygon, Position } from 'geojson';\nimport { placeAreaLabel } from '../../utils';\nimport type { QueryAs } from './locate-places';\nimport type { AreaWhere } from './schema';\n\nexport type WhereContext = {\n // Current map viewport, read once for both the `within` viewport fallback and the\n // `nearby`/query-rerank bias point — same underlying map read either way.\n viewport(): { bbox?: BBox; center?: Position };\n // Expands an ENTRY id to the feature ids of its geometry-bearing places. `undefined` when no\n // such entry exists (caller falls back to treating the id as a place feature id); `[]` when the\n // entry exists but holds no geometry-bearing place.\n expandEntry(id: string): string[] | undefined;\n // `queryAs` set → nearby's point-bias resolution (POI/place search via locatePlaces, any\n // geography). `queryAs` omitted → within's area resolution, restricted to administrative AREA\n // geographies (streets/POIs filtered out) so every result carries a usable bbox.\n geocodeAreas(query: string, queryAs?: QueryAs, bias?: Position): Promise<Place[]>;\n // Fetches a boundary polygon — by session place id (resolved + cached against its owning entry;\n // `undefined` covers \"unknown id\", \"no geometry data source\", and \"fetch returned nothing\" alike)\n // or directly for an already-resolved `Place` (a fresh geocode candidate with nothing to cache\n // against). Both paths hit the same Geometry Data call underneath.\n fetchGeometry(place: Place | string): Promise<Feature<Polygon | MultiPolygon> | undefined>;\n // Returns the route's geometry features (one per alternative) plus its display label, or a\n // specific `{ error }` explaining why no route is available (none calculated yet / id not found\n // / entry has no geometry) so resolveRouteToAreas can surface an actionable message.\n getRoute(routeId?: string): { features: Feature[]; label: string } | { error: string };\n};\n\nexport type ResolveAreasOptions = {\n kind: ResolveKind;\n};\n\nexport type ResolveKind = 'within' | 'nearby' | 'global';\nexport type AreaSource = 'viewport' | 'boundingBox' | 'query' | 'placeId' | 'geometries' | 'route';\n\n// Single entrypoint for every `where`-mode resolution — takes the mode as `kind` and branches to\n// the matching resolver. `within` and `nearby` need `where`/`ctx`; `global` needs neither.\nexport function resolveWhere(opts: ResolveAreasOptions, where?: AreaWhere, ctx?: WhereContext) {\n switch (opts.kind) {\n case 'within':\n return resolveWithin(where as AreaWhere, ctx as WhereContext);\n case 'nearby':\n return resolveNearby(where as AreaWhere, ctx as WhereContext);\n case 'global':\n return resolveGlobal();\n }\n}\n\n// The search scope for `within` — every explicit field resolves to its own area(s) and they're\n// unioned together; the viewport is used only as a fallback when nothing explicit resolved.\nexport const resolveWithin = (where: AreaWhere, ctx: WhereContext): Promise<ResolvedArea[] | ResolveError> =>\n gatherCandidates(gatherInputs(where, ctx), ctx);\n\n// The bias point for `nearby` — one point, never an area. Unresolvable input is not an error, it's\n// just \"no bias\" ({}), so the caller widens the search instead of failing it.\nexport const resolveNearby = async (where: AreaWhere, ctx: WhereContext): Promise<ResolvedBias> => {\n const input = gatherInputs(where, ctx);\n const center = input.mapViewport.center;\n\n if (!input.queries?.length) {\n return input.useViewport && center ? { position: center } : {};\n }\n\n const candidates = await geocodeQueryCandidates(input, ctx, center);\n if (candidates.length === 0) {\n return {};\n }\n const position = getPosition(candidates[0]);\n if (!position) {\n return {};\n }\n const label = placeAreaLabel(candidates[0]);\n return { position, query: input.queries[0].query, ...(label && { label }) };\n};\n\n// Nothing to resolve — `global` carries no `where` fields at all.\nexport const resolveGlobal = (): ResolvedBias => ({});\n\ntype LocateInputs = {\n useViewport: AreaWhere['viewport'];\n mapViewport: ReturnType<WhereContext['viewport']>;\n boundingBox: AreaWhere['boundingBox'];\n queries: AreaWhere['queries'];\n placeIds: AreaWhere['placeIds'];\n geometries: AreaWhere['geometries'];\n route: AreaWhere['route'];\n};\n\nexport type ResolvedArea = {\n bbox: BBox;\n polygon?: Polygon | MultiPolygon;\n /** The GROUNDED match label — `freeformAddress, <countryCode>` for a `query` area — i.e. what the\n * data was actually loaded for, NOT the query echo. Lets a consumer surface where a query really\n * resolved. */\n label?: string;\n source: AreaSource;\n /** The input query text for a `query` area, echoed so a consumer can pair \"asked\" with the\n * grounded `label` (\"matched\"). Absent for non-query sources. */\n query?: string;\n};\n\nexport type ResolveError = { error: string };\n\n// The nearby-mode counterpart to ResolvedArea — one bias point instead of a region. `position`\n// absent means \"couldn't resolve a bias,\" which is a valid, non-error outcome (see resolveNearby).\nexport type ResolvedBias = { position?: Position; label?: string; query?: string };\n\n// The grounded match for a resolved query — `matched` is the place the data was actually loaded\n// for, not the query echo. Callers derive this from ResolvedArea[]/ResolvedBias to surface where a\n// query resolved (see discover-places.ts, get-traffic-incidents.ts).\nexport type ResolvedAreaDisclosure = { query?: string; matched: string };\n\nexport const isResolveError = (r: unknown): r is ResolveError => typeof r === 'object' && r !== null && 'error' in r;\n\nconst gatherInputs = (where: AreaWhere, ctx: WhereContext): LocateInputs => {\n const { viewport: useViewport, boundingBox, queries, placeIds, geometries, route } = where;\n const mapViewport = ctx.viewport();\n return { useViewport, boundingBox, queries, placeIds, geometries, route, mapViewport };\n};\n\n// Resolves every explicit within-mode field to its own area(s) and unions them. Explicit input\n// always wins outright; the viewport is only used if nothing explicit resolved to anything. Each\n// `queries` entry is narrowed to its own single winner (geocode -> rerank -> pick top -> optional\n// boundary fetch) before joining the pool, so an ambiguous name can't leave several wrong-location\n// matches in the final unioned scope. A failed query/placeId/route aborts the whole resolve; a\n// degenerate `geometries` entry is just skipped — see the reference block below for why.\nconst gatherCandidates = async (inputs: LocateInputs, ctx: WhereContext): Promise<ResolvedArea[] | ResolveError> => {\n const { useViewport, boundingBox, queries, placeIds, geometries, route, mapViewport } = inputs;\n const candidates: ResolvedArea[] = [];\n\n if (boundingBox) {\n candidates.push({ bbox: boundingBox as BBox, source: 'boundingBox' });\n }\n\n const queryCandidates = await resolveQueryCandidates(queries, ctx, mapViewport);\n if (isResolveError(queryCandidates)) return queryCandidates;\n candidates.push(...queryCandidates);\n\n const placeIdCandidates = await resolvePlaceIdCandidates(placeIds, ctx);\n if (isResolveError(placeIdCandidates)) return placeIdCandidates;\n candidates.push(...placeIdCandidates, ...resolveGeometryCandidates(geometries));\n\n if (route) {\n const routeCandidates = await resolveRouteCandidates(route, ctx);\n if (isResolveError(routeCandidates)) return routeCandidates;\n candidates.push(...routeCandidates);\n }\n\n if (candidates.length > 0) return candidates;\n\n if (useViewport) {\n // Explicitly requested and unavailable is a real failure, unlike \"nothing was requested.\"\n if (!mapViewport.bbox) return { error: 'No map viewport available to resolve as an area.' };\n return [{ bbox: mapViewport.bbox, source: 'viewport' }];\n }\n\n // Nothing explicit resolved (or nothing was given at all) — an empty scope, not an error, so a\n // caller composing this with its own scope (e.g. `range`) can decide whether that's a problem.\n return candidates;\n};\n\n// Each `queries` entry is narrowed to its own single winner (geocode -> rerank -> pick top ->\n// optional boundary fetch) before joining the pool, so an ambiguous name can't leave several\n// wrong-location matches in the final unioned scope.\nconst resolveQueryCandidates = async (\n queries: LocateInputs['queries'],\n ctx: WhereContext,\n mapViewport: LocateInputs['mapViewport'],\n): Promise<ResolvedArea[] | ResolveError> => {\n const candidates: ResolvedArea[] = [];\n for (const { query } of queries ?? []) {\n const matches = (await ctx.geocodeAreas(query, undefined, mapViewport.center)).filter((p) => p.bbox);\n if (matches.length === 0) {\n return {\n error: `Could not resolve \"${query}\" to an area. Provide a boundingBox or a more specific place.`,\n };\n }\n const top = (mapViewport.center ? rerankWithNearestCandidate(matches, mapViewport.center) : matches)[0];\n const label = placeAreaLabel(top);\n const feature = top.properties.dataSources?.geometry?.id ? await ctx.fetchGeometry(top) : undefined;\n candidates.push(\n feature\n ? {\n bbox: (feature.bbox as BBox | undefined) ?? (bboxFromGeoJSON(feature) as BBox),\n polygon: feature.geometry,\n label,\n source: 'query',\n query,\n }\n : { bbox: top.bbox as BBox, label, source: 'query', query },\n );\n }\n return candidates;\n};\n\nconst resolvePlaceIdCandidates = async (\n placeIds: LocateInputs['placeIds'],\n ctx: WhereContext,\n): Promise<ResolvedArea[] | ResolveError> => {\n const candidates: ResolvedArea[] = [];\n for (const id of placeIds ?? []) {\n const entryIds = ctx.expandEntry(id);\n if (entryIds?.length === 0) {\n return {\n error: `Places entry \"${id}\" has no place with a geometry data source. Provide a boundingBox or a different entry.`,\n };\n }\n for (const placeId of entryIds ?? [id]) {\n const feature = await ctx.fetchGeometry(placeId);\n if (!feature) {\n return {\n error: `Unknown placeId \"${placeId}\" or it has no fetchable geometry. Use recallState to list available IDs.`,\n };\n }\n candidates.push({ bbox: bboxFromGeoJSON(feature) as BBox, polygon: feature.geometry, source: 'placeId' });\n }\n }\n return candidates;\n};\n\n// A degenerate entry (no bbox derivable) is just skipped, not an error — see gatherCandidates.\nconst resolveGeometryCandidates = (geometries: LocateInputs['geometries']): ResolvedArea[] => {\n const candidates: ResolvedArea[] = [];\n for (const polygon of geometries ?? []) {\n const bbox = bboxFromGeoJSON(polygon);\n if (bbox) candidates.push({ bbox, polygon, source: 'geometries' });\n }\n return candidates;\n};\n\nconst resolveRouteCandidates = async (\n route: NonNullable<LocateInputs['route']>,\n ctx: WhereContext,\n): Promise<ResolvedArea[] | ResolveError> => {\n const entry = ctx.getRoute(route.routeId);\n if ('error' in entry) return entry;\n\n const routeAreas: ResolvedArea[] = [];\n for (const feature of entry.features) {\n const buffered = turf.buffer(feature, route.widthMeters / 2, { units: 'meters' });\n if (buffered) {\n routeAreas.push({\n bbox: bboxFromGeoJSON(buffered as Feature) as BBox,\n polygon: buffered.geometry as Polygon | MultiPolygon,\n label: entry.label,\n source: 'route',\n });\n }\n }\n if (routeAreas.length === 0) return { error: 'Route produced no corridor geometry.' };\n return routeAreas;\n};\n\n// Reorders geocode candidates by averaging two rank positions: the geocoder's own relevance order\n// and a distance-to-viewport order computed here — so a top-ranked but far candidate can still lose\n// to a lower-ranked but much closer one, and vice versa.\nconst rerankWithNearestCandidate = (candidates: Place[], center: Position): Place[] => {\n const DISTANCE_TIE_BUFFER_KM = 1000;\n\n const sortedFromDistance = [...candidates].sort((a, b) => {\n if (a.bbox === undefined || b.bbox === undefined) return 0;\n const distA = turf.distance(bboxCenter(a.bbox), center);\n const distB = turf.distance(bboxCenter(b.bbox), center);\n const diff = distA - distB;\n return Math.abs(diff) > DISTANCE_TIE_BUFFER_KM ? diff : 0;\n });\n\n const scored = candidates.map((place, geocoderRank) => ({\n place,\n rank: (geocoderRank + sortedFromDistance.indexOf(place)) / 2,\n }));\n\n return scored.toSorted((a, b) => a.rank - b.rank).map((a) => a.place);\n};\n\n// Geocode candidates for nearby's `query` bias — POI-or-place search (never area-restricted), kept\n// flat since nearby only ever expects one meaningful query. No rerank here: nearby trusts the\n// geocoder's own top result as-is, unlike within's per-query resolution above. An unresolved query\n// is skipped, not an error — nearby's bias is tolerant of \"not found\" (see resolveNearby), and the\n// within-flavored \"Provide a boundingBox\" message would be misleading for this POI/place search path.\nconst geocodeQueryCandidates = async (input: LocateInputs, ctx: WhereContext, bias?: Position): Promise<Place[]> => {\n const candidates: Place[] = [];\n for (const { query, queryAs } of input.queries ?? []) {\n const matches = await ctx.geocodeAreas(query, queryAs, bias);\n candidates.push(...matches);\n }\n return candidates;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Feature } from 'geojson';\nimport type { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { hidePreviousShownEntries } from './hide-previous-entries';\nimport { fitMapToGeoJSON } from './map-fit';\nimport type { showByodSchema, shownByodSchema } from './schema';\n\n// Gather the features of every BYOD entry currently rendered on the map — the\n// target the camera fits when `zoomMode: \"auto\"`.\nconst collectShownByodFeatures = (state: ToolState): Feature[] => {\n const features: Feature[] = [];\n for (const entryId of state.byod.shownEntryIds) {\n const entry = state.byod.findById(entryId);\n if (entry) features.push(...entry.data.features);\n }\n return features;\n};\n\n/**\n * Render a BYOD entry on the map per the tool's `show` options: hide\n * previously-shown entries (per `hidePreviousEntries`), show the new entry, then\n * optionally fit the camera to every shown BYOD entry. Sequential by design —\n * map mutations must not race (see ENGINEERING-GUIDELINES §6).\n *\n * @ignore\n */\nexport const showByodOnMap = async (\n state: ToolState,\n byodEntryId: string,\n show: z.infer<typeof showByodSchema>,\n): Promise<z.infer<typeof shownByodSchema>> => {\n await hidePreviousShownEntries(state.byod, [byodEntryId], show.hidePreviousEntries);\n await state.byod.showEntry(byodEntryId);\n\n const zoomMode =\n show.zoomMode === 'auto' && fitMapToGeoJSON(state.baseMap.mapLibreMap, collectShownByodFeatures(state));\n return { zoomMode };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport type { showPlaceGeometriesSchema } from './schema';\n\n/** @ignore */\nexport type ShowEntryGeometriesResult = { fetched: number; shown: boolean };\n\n/**\n * Fetch boundary polygons for every place in an entry that has a geometry data source,\n * cache them on the entry, and optionally render them via the entry's GeometriesModule.\n *\n * Shared by tools that produce a single places entry (discoverPlaces, locatePlace) — they\n * differ only in how they shape the result into their output schemas.\n *\n * @ignore\n */\nexport const showEntryGeometries = async (\n state: ToolState,\n placesEntryId: string,\n config: NonNullable<z.infer<typeof showPlaceGeometriesSchema>>,\n options?: ToolExecuteOptions,\n): Promise<ShowEntryGeometriesResult> => {\n const fetched = await state.places.fetchGeometriesForEntry(placesEntryId, options);\n if (config.show && fetched.length > 0) {\n await state.places.showPlaceGeometries(fetched, config.fillStyle ?? 'outline', config.mode ?? 'replace');\n return { fetched: fetched.length, shown: true };\n }\n return { fetched: fetched.length, shown: false };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { fitMapToGeoJSON } from './map-fit';\nimport { shownSchema, showPlacesSchema } from './schema';\n\n/** @ignore */\nexport const showResultsOnMap = async (\n state: ToolState,\n placesEntryIds: readonly string[],\n show: z.infer<typeof showPlacesSchema>,\n): Promise<z.infer<typeof shownSchema>> => {\n const shown: z.infer<typeof shownSchema> = { markerType: false, zoomMode: false };\n if (show.markerType !== 'none') {\n const isMultiple = state.places.entryMode === 'multiple';\n if (isMultiple && show.hidePreviousEntries !== 'all') {\n if (Array.isArray(show.hidePreviousEntries)) {\n const exclude = new Set(placesEntryIds);\n const toHide = show.hidePreviousEntries.filter((id) => !exclude.has(id));\n if (toHide.length > 0) await state.places.removeShownEntries(toHide);\n }\n await state.places.addShownEntries(placesEntryIds, show.markerType);\n } else {\n await state.places.setShownEntries(placesEntryIds, show.markerType);\n }\n shown.markerType = true;\n }\n if (show.zoomMode === 'auto') {\n fitMapToGeoJSON(state.baseMap.mapLibreMap, state.places.getShownFeatures());\n shown.zoomMode = true;\n }\n return shown;\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Utility for deriving a search bias from the current map viewport.\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport type { BaseMapState } from '../../state';\n\n/**\n * Lowest zoom at which {@link getViewportBias} returns the map centre.\n *\n * @group Agent Toolkit\n */\nexport const MIN_VIEWPORT_BIAS_ZOOM = 6;\n\n/**\n * The map centre, to favour search results in view: pass it as `{ position }` to a service's `geoBias` or to\n * {@link locatePlace}'s `bias`. Below `minZoom` the view spans too wide an area to say where the user means.\n *\n * @param map - The agent's base-map slice, `state.baseMap` in a tool's `execute`.\n * @returns The centre as `[longitude, latitude]`, or `undefined` below `minZoom`.\n *\n * @group Agent Toolkit\n */\nexport const getViewportBias = (map: BaseMapState, minZoom = MIN_VIEWPORT_BIAS_ZOOM): Position | undefined => {\n const zoom = map.mapLibreMap.getZoom();\n if (zoom < minZoom) return undefined;\n const center = map.mapLibreMap.getCenter();\n return [center.lng, center.lat];\n};\n\n/** @ignore */\nexport const getViewportBoundingBox = (map: BaseMapState): BBox => {\n const bounds = map.mapLibreMap.getBounds();\n return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { BBox } from '@tomtom-org/maps-sdk/core';\nimport { geometryData } from '@tomtom-org/maps-sdk/services';\nimport type { Feature, MultiPolygon, Polygon, Position } from 'geojson';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { withAgentToolkitHeaders } from './agent-headers';\nimport { geocodeAreas as geocodeContainingAreas } from './geocode-areas';\nimport { locatePlaces } from './locate-places';\nimport type { WhereContext } from './resolve-where';\nimport { getViewportBias, getViewportBoundingBox } from './viewport-bias';\n\n/** Adapts the live `ToolState` to the narrow `WhereContext` the resolver depends on. Keeping this\n * mapping in one place is what lets resolveAreas stay testable without a ToolState. */\nexport const toolStateToWhereContext = (state: ToolState, options?: ToolExecuteOptions): WhereContext => ({\n viewport: () => {\n let bbox: BBox | undefined;\n let center: Position | undefined;\n try {\n bbox = getViewportBoundingBox(state.baseMap);\n } catch {\n bbox = undefined;\n }\n try {\n center = getViewportBias(state.baseMap);\n } catch {\n center = undefined;\n }\n return { bbox, center };\n },\n expandEntry: (id) => state.places.geometryPlaceIdsForEntry(id),\n geocodeAreas: (query, queryAs, bias) => {\n if (queryAs)\n return locatePlaces(query, queryAs, { limit: 5, bias: bias ? { position: bias } : undefined }, options);\n return geocodeContainingAreas(query, { bias }, options);\n },\n fetchGeometry: async (place) => {\n if (typeof place === 'string') return state.places.fetchPlaceGeometry(place, options);\n if (!place.properties.dataSources?.geometry?.id) return undefined;\n const requestParams = withAgentToolkitHeaders({\n geometries: [place],\n signal: options?.signal,\n });\n const result = await geometryData(requestParams);\n return result.features[0] as Feature<Polygon | MultiPolygon> | undefined;\n },\n getRoute: (routeId) => {\n const entries = state.routing.entries;\n if (entries.length === 0) {\n return {\n error: 'No routes available yet. Calculate or select a route first (e.g. calculateRoute), then retry.',\n };\n }\n const entry = routeId ? entries.find((e) => e.id === routeId) : entries.at(-1);\n if (!entry) {\n return { error: `Route \"${routeId}\" not found. Use recallState to list available route IDs.` };\n }\n const features = entry.data.features as Feature[];\n if (features.length === 0) {\n return { error: `Route \"${entry.id}\" has no geometry. Recompute it via calculateRoute.` };\n }\n return { features, label: entry.label };\n },\n});\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport type { Map as MapLibreMap } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { formatSandboxExecutionError } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst sourceDiffSchema = z.object({\n added: z.array(z.string()).describe('Source IDs added by the executed code.'),\n removed: z.array(z.string()).describe('Source IDs removed by the executed code.'),\n updated: z.array(z.string()).describe('Source IDs whose definition changed.'),\n});\n\nconst layerDiffSchema = z.object({\n added: z.array(z.string()).describe('Layer IDs added by the executed code.'),\n removed: z.array(z.string()).describe('Layer IDs removed by the executed code.'),\n updated: z.array(z.string()).describe('Layer IDs whose properties changed.'),\n});\n\n/** Input schema for the executeMaplibreCode tool. */\nexport const executeMaplibreCodeSchema = z.object({\n code: z\n .string()\n .describe(\n 'Async JavaScript code executed in an environment where `map` is the live MapLibre Map instance. ' +\n 'Full JS is supported: variables, loops, conditionals, helper functions, top-level `await`. ' +\n 'Example: await a map event with `await new Promise(r => map.once(\"idle\", r))`. ' +\n 'Return a value with `return` to include it in the result.',\n ),\n});\n\n/** Output schema for the executeMaplibreCode tool. */\nexport const executeMaplibreCodeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n result: z.unknown().optional().describe('Return value of the executed code, if any.'),\n sources: sourceDiffSchema,\n layers: layerDiffSchema,\n }),\n toolErrorSchema,\n]);\n\nexport const executeMaplibreCodeDescription =\n 'Run arbitrary MapLibre JS against the live `map` — the escape hatch for operations no dedicated tool covers ' +\n '(custom sources/layers, style mutations, animations, event sequences). If another tool already performs the ' +\n 'operation, prefer that tool. Full JS (loops, helpers, top-level `await`). Returns added/removed/updated sources & layers.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype Snapshot = Map<string, string>;\n\nconst snapshotSources = (mapLibreMap: MapLibreMap): Snapshot => {\n const sources = mapLibreMap.getStyle()?.sources ?? {};\n const snapshot: Snapshot = new Map();\n\n for (const [sourceId, source] of Object.entries(sources)) {\n snapshot.set(sourceId, JSON.stringify(source));\n }\n\n return snapshot;\n};\n\nconst snapshotLayers = (mapLibreMap: MapLibreMap): Snapshot => {\n const layers = mapLibreMap.getStyle()?.layers ?? [];\n const snapshot: Snapshot = new Map();\n\n for (const layer of layers) {\n snapshot.set(layer.id, JSON.stringify(layer));\n }\n\n return snapshot;\n};\n\nconst diffSnapshots = (\n before: Snapshot,\n after: Snapshot,\n): { added: string[]; removed: string[]; updated: string[] } => {\n const added: string[] = [];\n const removed: string[] = [];\n const updated: string[] = [];\n\n for (const [identifier, afterJson] of after) {\n const beforeJson = before.get(identifier);\n\n if (beforeJson === undefined) {\n added.push(identifier);\n } else if (beforeJson !== afterJson) {\n updated.push(identifier);\n }\n }\n\n for (const identifier of before.keys()) {\n if (!after.has(identifier)) {\n removed.push(identifier);\n }\n }\n\n return { added, removed, updated };\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/** Execute function for executeMaplibreCode — usable with ToolEntry format. */\nexport const executeExecuteMaplibreCode = async (\n params: z.infer<typeof executeMaplibreCodeSchema>,\n state: ToolState,\n) => {\n const { code } = params;\n try {\n const mapLibreMap = state.baseMap.mapLibreMap;\n const beforeSources = snapshotSources(mapLibreMap);\n const beforeLayers = snapshotLayers(mapLibreMap);\n\n // eslint-disable-next-line no-new-func\n const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor as new (\n ...args: string[]\n ) => (map: MapLibreMap) => Promise<unknown>;\n const userFunction = new AsyncFunction('map', code);\n const result = await userFunction(mapLibreMap);\n\n const sources = diffSnapshots(beforeSources, snapshotSources(mapLibreMap));\n const layers = diffSnapshots(beforeLayers, snapshotLayers(mapLibreMap));\n\n return { success: true, result: result ?? undefined, sources, layers };\n } catch (error) {\n return { error: formatSandboxExecutionError('Code', error) };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { geoJsonBBoxSchema, positionSchema } from '../shared';\nimport { DEFAULT_MAP_FIT_PADDING } from '../shared/map-fit';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the fly-to tool. */\nexport const flyToOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for fly-to. */\nexport const flyToSchema = z.object({\n where: z.union([\n z\n .object({\n boundingBox: geoJsonBBoxSchema.describe('BBox [W,S,E,N].'),\n padding: z.number().optional().describe('Padding in pixels. Default: 50.'),\n })\n .describe('Fit the camera to a bounding box.'),\n z\n .object({\n position: positionSchema.describe('[lng, lat] to fly to.'),\n zoom: z.number().optional().describe('Camera zoom level. Default: 14.'),\n })\n .describe('Fly the camera to a specific position.'),\n ]),\n});\n\nexport const flyToDescription =\n 'Move the map camera to a bounding box or a specific position. ' +\n 'Use with boundingBox to frame a region or set of results. Use with position to center on a point.';\n\n/** Execute function for flyTo — usable with ToolEntry format. */\nexport const executeFlyTo = async (params: z.infer<typeof flyToSchema>, state: ToolState) => {\n const { where } = params;\n try {\n if ('boundingBox' in where) {\n state.baseMap.mapLibreMap.fitBounds(where.boundingBox as [number, number, number, number], {\n padding: where.padding ?? DEFAULT_MAP_FIT_PADDING,\n });\n } else {\n state.baseMap.mapLibreMap.flyTo({\n center: where.position as [number, number],\n zoom: where.zoom ?? 14,\n });\n }\n return { success: true };\n } catch (error) {\n return {\n error: `flyTo failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-map-style-layers tool. */\nexport const getMapStyleLayersOutputSchema = z.union([\n z.object({\n layers: z.array(\n z.object({\n id: z.string(),\n layout: z.unknown().optional(),\n paint: z.unknown().optional(),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting map style layers.\n */\nexport const getMapStyleLayersSchema = z.object({\n layerIdQuery: z\n .string()\n .optional()\n .describe('Case-insensitive partial match on layer IDs. If omitted, all layers are returned.'),\n include: z\n .array(z.enum(['paint', 'layout']))\n .describe(\n 'Which properties to include in the response. Pass only what you need: \"paint\" for visual properties like colors and opacity, \"layout\" for structural properties like sizes, positions, and visibility.',\n ),\n});\n\nexport const getMapStyleLayersDescription =\n 'Get map layer IDs with their paint/layout properties from MapLibre style for advanced runtime manipulation. Use before setLayoutProperty/setPaintProperty for custom styling.';\n\n/** Execute function for getMapStyleLayers — usable with ToolEntry format. */\nexport const executeGetMapStyleLayers = async (params: z.infer<typeof getMapStyleLayersSchema>, state: ToolState) => {\n const { layerIdQuery, include } = params;\n try {\n const style = state.baseMap.mapLibreMap.getStyle();\n let layers = style.layers ?? [];\n\n // Filter by layerIdQuery if provided (case-insensitive partial match)\n if (layerIdQuery) {\n layers = layers.filter((layer) => layer.id.toLowerCase().includes(layerIdQuery.toLowerCase()));\n if (layers.length === 0) {\n return {\n error: `No layers found matching query: \"${layerIdQuery}\"`,\n };\n }\n }\n\n return {\n layers: layers.map((layer) => ({\n id: layer.id,\n ...(include.includes('paint') && { paint: layer.paint }),\n ...(include.includes('layout') && { layout: layer.layout }),\n })),\n };\n } catch (error) {\n return {\n error: `Failed to get style: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-viewport tool. */\nexport const getViewportOutputSchema = z.union([\n z.object({\n center: z.array(z.number()).describe('[lng, lat]'),\n zoom: z.number().describe('0–22'),\n bbox: z.array(z.number()).describe('[W, S, E, N]'),\n pitch: z.number().describe('degrees, 0=top-down, 85=near-horizontal'),\n bearing: z.number().describe('degrees, 0=north, 90=east'),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting viewport.\n */\nexport const getViewportSchema = z.object({});\n\nexport const getViewportDescription =\n 'Get the current map viewport: center coordinates [longitude, latitude], zoom level, and bounding box [minLng, minLat, maxLng, maxLat]. ' +\n 'Use for queries referencing the map view: \"in this area\", \"what I can see\", \"near the map center\". ' +\n 'Does NOT return the user\\'s physical location — use getCurrentLocation for \"near me\" queries. ' +\n 'Reads map state. Does not call any service.';\n\n/** Execute function for getViewport — usable with ToolEntry format. */\nexport const executeGetViewport = async (_params: z.infer<typeof getViewportSchema>, state: ToolState) => {\n try {\n const mapLibreMap = state.baseMap.mapLibreMap;\n return {\n center: mapLibreMap.getCenter().toArray(),\n zoom: mapLibreMap.getZoom(),\n bbox: state.baseMap.ttMap.getBBox(),\n pitch: mapLibreMap.getPitch(),\n bearing: mapLibreMap.getBearing(),\n };\n } catch (error) {\n return {\n error: `Failed to get viewport: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport type { AllLayoutProperties } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst changeResultSchema = z.union([\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n value: z.unknown(),\n success: z.literal(true),\n }),\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n error: z.string(),\n }),\n]);\n\n/** Output schema for the setLayoutProperties tool. */\nexport const setLayoutPropertiesOutputSchema = z.union([\n z.object({\n results: z.array(changeResultSchema),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the setLayoutProperties tool. */\nexport const setLayoutPropertiesSchema = z\n .object({\n changes: z.array(\n z.object({\n layerId: z.string().describe('MapLibre layer ID from getMapStyleLayers'),\n propertyName: z.string().describe('e.g. visibility|text-field|icon-size'),\n value: z.any(),\n }),\n ),\n })\n .describe('List of layout property changes to apply in one go.');\n\nexport const setLayoutPropertiesDescription =\n 'Set MapLibre layout properties (visibility, text-field, icon-size) on named layers — use getMapStyleLayers for valid IDs. ' +\n 'For colors/widths use setPaintProperties.';\n\n/** Execute function for setLayoutProperties — usable with ToolEntry format. */\nexport const executeSetLayoutProperties = async (\n params: z.infer<typeof setLayoutPropertiesSchema>,\n state: ToolState,\n) => {\n const { changes } = params;\n try {\n const results = changes.map(({ layerId, propertyName, value }) => {\n try {\n const layoutProperty = propertyName as keyof AllLayoutProperties;\n state.baseMap.mapLibreMap.setLayoutProperty(\n layerId,\n layoutProperty,\n value as AllLayoutProperties[typeof layoutProperty],\n );\n return { layerId, propertyName, value, success: true as const };\n } catch (error) {\n return {\n layerId,\n propertyName,\n error: `Failed to set layout property: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n\n return { results };\n } catch (error) {\n return {\n error: `Failed to set layout properties: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools/maplibre\n */\n\nimport type { AllPaintProperties } from 'maplibre-gl';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst changeResultSchema = z.union([\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n value: z.unknown(),\n success: z.literal(true),\n }),\n z.object({\n layerId: z.string(),\n propertyName: z.string(),\n error: z.string(),\n }),\n]);\n\n/** Output schema for the setPaintProperties tool. */\nexport const setPaintPropertiesOutputSchema = z.union([\n z.object({\n results: z.array(changeResultSchema),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for the setPaintProperties tool. */\nexport const setPaintPropertiesSchema = z.object({\n changes: z\n .array(\n z.object({\n layerId: z.string().describe('MapLibre layer ID from getMapStyleLayers'),\n propertyName: z.string().describe('e.g. fill-color|line-width|text-color'),\n value: z.any(),\n }),\n )\n .describe('List of paint property changes to apply in one go.'),\n});\n\nexport const setPaintPropertiesDescription =\n 'Set MapLibre paint properties (colors, widths, opacity) on named layers — use getMapStyleLayers for valid IDs. ' +\n 'For visibility/structure use setLayoutProperties; for route styling prefer `updateRoutesDisplay({ color })`.';\n\n/** Execute function for setPaintProperties — usable with ToolEntry format. */\nexport const executeSetPaintProperties = async (params: z.infer<typeof setPaintPropertiesSchema>, state: ToolState) => {\n const { changes } = params;\n try {\n const results = changes.map(({ layerId, propertyName, value }) => {\n try {\n const paintProperty = propertyName as keyof AllPaintProperties;\n state.baseMap.mapLibreMap.setPaintProperty(\n layerId,\n paintProperty,\n value as AllPaintProperties[typeof paintProperty],\n );\n return { layerId, propertyName, value, success: true as const };\n } catch (error) {\n return {\n layerId,\n propertyName,\n error: `Failed to set paint property: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n });\n\n return { results };\n } catch (error) {\n return {\n error: `Failed to set paint properties: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the set-pitch-bearing tool. */\nexport const setPitchBearingOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting pitch and bearing.\n */\nexport const setPitchBearingSchema = z.object({\n pitch: z.number().min(0).max(85).optional().describe('0=top-down, 85=near-horizontal'),\n bearing: z.number().min(-180).max(180).optional().describe('0=north, 90=east'),\n});\n\nexport const setPitchBearingDescription =\n 'Tilt (pitch) and/or rotate (bearing) the map camera — the dedicated tool for a tilt, 3D/perspective, or ' +\n 'rotate-to-face view. Pitch 0 = top-down, 85 = near-horizontal (3D); bearing 0 = north up, 90 = east up. ' +\n 'Provide at least one of pitch or bearing.';\n\n/** Execute function for setPitchBearing — usable with ToolEntry format. */\nexport const executeSetPitchBearing = async (params: z.infer<typeof setPitchBearingSchema>, state: ToolState) => {\n const { pitch, bearing } = params;\n\n if (pitch === undefined && bearing === undefined) {\n return { error: 'Provide at least one of pitch or bearing' };\n }\n\n try {\n const map = state.baseMap.mapLibreMap;\n map.easeTo({\n ...(pitch !== undefined && { pitch }),\n ...(bearing !== undefined && { bearing }),\n });\n\n return { success: true };\n } catch (error) {\n return {\n error: `Failed to set pitch/bearing: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the zoom-in-or-out tool. */\nexport const zoomInOrOutOutputSchema = z.union([\n z.object({\n zoom: z.number().describe('Resulting zoom level after the change'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for zoom-in-or-out. */\nexport const zoomInOrOutSchema = z.object({\n delta: z\n .number()\n .describe(\n 'Amount to zoom. Positive values zoom in, negative values zoom out. Can have decimals. Typical step: 1 or 2.',\n ),\n});\n\nexport const zoomInOrOutDescription =\n 'Zoom the map in or out by a given amount relative to the current zoom level. ' +\n 'Positive delta zooms in (closer), negative delta zooms out (further away). ' +\n 'Returns the resulting zoom level.';\n\n/** Execute function for zoomInOrOut — usable with ToolEntry format. */\nexport const executeZoomInOrOut = async (params: z.infer<typeof zoomInOrOutSchema>, state: ToolState) => {\n const { delta } = params;\n try {\n const map = state.baseMap.mapLibreMap;\n const targetZoom = map.getZoom() + delta;\n map.zoomTo(targetZoom);\n return { zoom: targetZoom };\n } catch (error) {\n return {\n error: `Zoom failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { getPosition } from '@tomtom-org/maps-sdk/core';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { type LocationInput, locatePlace, type ResolvedLocation } from '../shared';\n\n/** @ignore */\nexport const resolveLocationInput = async (\n input: LocationInput,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<ResolvedLocation | null> => {\n if ('position' in input) {\n const [lng, lat] = input.position;\n return {\n place: [lng, lat],\n position: [lng, lat],\n name: `[${lng}, ${lat}]`,\n };\n }\n\n if ('placeIdOrEntryId' in input) {\n const { placeIdOrEntryId } = input;\n // Accept EITHER a place feature id (Place.id, e.g. \"odj-…\") OR a places entry id\n // (placesEntryId / the `entryId` hint, e.g. \"berlin-brandenburg-airport\"). The model\n // routinely reuses the friendly entry id it just set on locatePlace, so fall back to\n // resolving the entry to its place when the feature-id lookup misses. A multi-place entry\n // (e.g. a discoverPlaces result) resolves to its first place — a single waypoint needs one point.\n let hit = state.places.findPlaceById(placeIdOrEntryId);\n if (!hit) {\n const entry = state.places.entries.find((e) => e.id === placeIdOrEntryId);\n const place = entry?.data[0];\n if (place) hit = { place, entryId: entry.id };\n }\n if (!hit) return null;\n const position = getPosition(hit.place);\n if (!position) return null;\n return {\n place: hit.place,\n position,\n name: hit.place.properties?.poi?.name ?? hit.place.properties?.address?.freeformAddress ?? placeIdOrEntryId,\n };\n }\n\n const resolved = await locatePlace(input.query, input.queryAs, undefined, options);\n if (!resolved) return null;\n\n const pos = getPosition(resolved);\n if (!pos) return null;\n\n return {\n place: resolved,\n position: pos,\n name: resolved.properties?.address?.freeformAddress ?? input.query,\n query: input.query,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n type Avoidable,\n avoidableTypes,\n type HasBBox,\n type Routes,\n type WaypointLike,\n} from '@tomtom-org/maps-sdk/core';\nimport {\n type CostModel,\n calculateRoute,\n type MaxNumberOfAlternatives,\n routeTypes,\n} from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { RouteParams, ToolExecuteOptions, ToolState } from '../../types';\nimport { makeRoutesLabel, summarizeRoutes } from '../../utils';\nimport {\n geoJsonBBoxSchema,\n hidePreviousEntriesSchema,\n hidePreviousShownEntries,\n locationInputSchema,\n withAgentToolkitHeaders,\n} from '../shared';\nimport { fitMapToGeoJSON } from '../shared/map-fit';\nimport { routesWriteOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\nexport const costModelSchema = z.object({\n routeType: z.enum(routeTypes).optional().describe('fast|short|efficient|thrilling'),\n traffic: z\n .enum(['live', 'historical'])\n .optional()\n .describe(\n 'live|historical. `historical` picks the TYPICAL route path (ignores current incidents when ' +\n 'routing) while still reporting live delays — use it with startRouteMonitor so each tick refreshes ' +\n 'delays on the SAME paths instead of re-routing around current traffic.',\n ),\n avoid: z\n .array(z.enum(avoidableTypes as unknown as [Avoidable, ...Avoidable[]]))\n .optional()\n .describe(\n 'tollRoads|motorways|ferries|unpavedRoads|carpools|alreadyUsedRoads|borderCrossings|tunnels|carTrains|lowEmissionZones',\n ),\n avoidAreas: z\n .array(geoJsonBBoxSchema.describe('GeoJSON bbox [minLng, minLat, maxLng, maxLat]'))\n .max(10)\n .optional()\n .describe('Up to 10 bounding boxes to bypass.'),\n});\n\nexport const whenSchema = z.object({\n option: z.enum(['departAt', 'arriveBy']),\n date: z.string().describe('ISO 8601'),\n});\n\nexport const routeParametersSchema = z.object({\n maxAlternatives: z.number().int().min(0).max(5).optional().describe('0–5, default 0'),\n costModel: costModelSchema.optional(),\n when: whenSchema.optional().describe('Omit to depart now.'),\n});\n\n/** Output schema for the set-route tool. */\nexport const setRouteOutputSchema = z.union([\n routesWriteOutputSchema,\n z.object({ success: z.literal(true) }),\n toolErrorSchema,\n]);\n\n/** Input schema for set-route. */\nexport const setRouteSchema = z\n .object({\n locations: z\n .array(locationInputSchema)\n .min(2)\n .optional()\n .describe(\n 'Ordered [origin, ...stops, destination]. Minimum 2. ' +\n 'Provide to set/replace waypoints; omit to keep the current waypoints.',\n ),\n parameters: routeParametersSchema\n .optional()\n .describe('Routing options. Omitted fields keep their current values.'),\n showOnMap: z.boolean().describe('Show the route on the map after calculation.'),\n showWaypoints: z\n .boolean()\n .optional()\n .describe(\n 'Show the origin/stop/destination pins (default true). Pass false to draw the route line ' +\n 'without waypoint markers — e.g. a monitored traffic corridor where the pins add noise. ' +\n 'Only applies when showOnMap is true.',\n ),\n showSummaryBubbles: z\n .boolean()\n .optional()\n .describe(\n 'Show the per-route ETA/summary bubbles (default true). Pass false to drop them — e.g. when ' +\n 'monitoring traffic on a corridor, where the bubbles add noise. Only applies when showOnMap is true.',\n ),\n monitor: z\n .boolean()\n .optional()\n .describe(\n 'Arm a live route monitor on the new route immediately (default false) — recalculates the same ' +\n 'route every 60s so its live-traffic delays stay current, no separate startRouteMonitor call ' +\n 'needed. Pair with `costModel.traffic: \"historical\"` + `maxAlternatives: 1-2` so the typical ' +\n 'paths stay stable while delays refresh. Ignored when no route is calculated.',\n ),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n })\n .refine((v) => v.locations !== undefined || v.parameters !== undefined, {\n message: 'Provide at least one of `locations` or `parameters`.',\n });\n\nexport const setRouteDescription =\n 'Calculate or recalculate a route. Provide `locations` to set/replace origin/stops/destination, ' +\n '`parameters` to update cost model / alternatives / when, or both in a single call. ' +\n 'At least one of `locations` or `parameters` MUST be provided. ' +\n 'If only `parameters` is given and waypoints already exist, recalculates with the new options; ' +\n 'if no waypoints exist yet, parameters are stored and no recalculation runs. Car/driving only. ' +\n 'To MONITOR traffic between places over time, calculate with `parameters.costModel.traffic: \"historical\"` + ' +\n '`maxAlternatives: 1-2` (stable typical paths) and set `monitor: true` — that arms the live monitor in the ' +\n 'same call (each tick refreshes live-traffic delays on the same routes), so no separate startRouteMonitor ' +\n 'call is needed. ' +\n 'Do NOT use to show, query, or modify an EXISTING displayed route — use updateRoutesDisplay / recallState / ' +\n 'addWaypointsToRoute / removeWaypointsFromRoute / replaceWaypointInRoute / discoverPlaces (detour|withinRoute) / analyseData instead.';\n\n/**\n * Filters out null waypoints and returns the array only if there are at least 2 valid entries.\n * Returns null if the condition is not met, preventing a route calculation call.\n */\nexport const resolveRouteWaypoints = (waypoints: (WaypointLike | null)[]): WaypointLike[] | null => {\n const valid = waypoints.filter((w): w is WaypointLike => w !== null);\n return valid.length >= 2 ? valid : null;\n};\n\nexport const buildCalculateRouteParams = (routeParams: RouteParams) => {\n const { maxAlternatives = 0, costModel, when } = routeParams;\n return {\n ...(maxAlternatives > 0 && { maxAlternatives: maxAlternatives as MaxNumberOfAlternatives }),\n ...(costModel && {\n costModel: {\n ...costModel,\n ...(costModel.avoidAreas && {\n avoidAreas: costModel.avoidAreas as unknown as HasBBox[],\n }),\n } as CostModel,\n }),\n ...(when && { when: { option: when.option, date: new Date(when.date) } }),\n };\n};\n\n// Recalc closure for a route monitor: re-runs the SAME route (captured waypoints + params) so\n// each tick refreshes only the live-traffic delays. Shared by setRoute's `monitor` flag and\n// startRouteMonitor so both arm monitors identically.\n//\n// Deliberately takes NO AbortSignal. Ticks fire on the monitor's interval long after the arming\n// turn has ended, and RoutingMonitor treats a rejected tick as fatal — it clears the interval —\n// so a turn signal here would silently kill the monitor on its first tick after the turn.\nexport const buildRouteRecalc = (waypoints: WaypointLike[], routeParams: RouteParams) => () => {\n const requestParams = withAgentToolkitHeaders({\n locations: waypoints,\n ...buildCalculateRouteParams(routeParams),\n });\n return calculateRoute(requestParams);\n};\n\nexport const showRouteOnMap = async (\n state: ToolState,\n entryId: string,\n routes: Routes,\n _waypoints: WaypointLike[],\n hidePreviousEntries?: 'all' | readonly string[],\n showWaypoints?: boolean,\n showSummaryBubbles?: boolean,\n): Promise<void> => {\n // RoutingState.showEntry handles route + waypoints rendering on the\n // entry's own RoutingModule. Under entryMode 'multiple' previously-shown\n // entries stay rendered; the optional `hidePreviousEntries` selector lets\n // callers clear them (\"all\") or hide specific ids before the new entry\n // is shown.\n await hidePreviousShownEntries(state.routing, [entryId], hidePreviousEntries);\n await state.routing.showEntry(entryId, {\n showWaypoints: showWaypoints !== false,\n showSummaryBubbles: showSummaryBubbles !== false,\n });\n fitMapToGeoJSON(state.baseMap.mapLibreMap, routes);\n};\n\n/**\n * Calculate a route from validated waypoints, register it in routing state,\n * optionally show it on the map, and return a summary.\n *\n * Shared by setRoute, addWaypointsToRoute, removeWaypointsFromRoute, and replaceWaypointInRoute.\n */\nexport const calculateAndAddRoute = async (\n state: ToolState,\n waypoints: WaypointLike[],\n showOnMap: boolean,\n hidePreviousEntries?: 'all' | readonly string[],\n monitor?: boolean,\n showWaypoints?: boolean,\n showSummaryBubbles?: boolean,\n options?: ToolExecuteOptions,\n) => {\n const requestParams = withAgentToolkitHeaders({\n locations: waypoints,\n ...buildCalculateRouteParams(state.routing.params),\n signal: options?.signal,\n });\n const routes = await calculateRoute(requestParams);\n\n const entryId = await state.routing.addRoutes(routes, waypoints, makeRoutesLabel(routes, waypoints));\n\n if (showOnMap) {\n await showRouteOnMap(state, entryId, routes, waypoints, hidePreviousEntries, showWaypoints, showSummaryBubbles);\n }\n\n if (monitor) {\n const entry = state.routing.entries.find((e) => e.id === entryId);\n if (entry) {\n state.routing.startMonitoring(entryId, {\n recalculate: buildRouteRecalc(entry.waypoints, entry.params),\n });\n }\n }\n\n // Surface the routing entry id so follow-up tools (analyseData / processData / addWaypointsToRoute)\n // know which entry the route landed in.\n return { ...summarizeRoutes(routes), entryId };\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeSetRoute = async (\n params: z.infer<typeof setRouteSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof setRouteOutputSchema>> => {\n const { locations, parameters, showOnMap, showWaypoints, showSummaryBubbles, monitor, hidePreviousEntries } =\n params;\n try {\n if (parameters) state.routing.setParams(parameters);\n\n let waypoints: WaypointLike[] | null;\n if (locations) {\n const resolved = await Promise.all(locations.map((loc) => resolveLocationInput(loc, state, options)));\n\n const unresolved: string[] = [];\n for (let i = 0; i < locations.length; i++) {\n if (resolved[i] !== null) continue;\n const loc = locations[i];\n if ('query' in loc) unresolved.push(`\"${loc.query}\"`);\n else if ('placeIdOrEntryId' in loc) unresolved.push(`placeIdOrEntryId \"${loc.placeIdOrEntryId}\"`);\n }\n if (unresolved.length > 0) {\n return { error: `Could not resolve: ${unresolved.join(', ')}` };\n }\n\n waypoints = resolveRouteWaypoints(resolved.map((r) => r?.place ?? null));\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n } else {\n waypoints = resolveRouteWaypoints(state.routing.planningSlots);\n if (!waypoints) {\n // No locations supplied and no current waypoints — params stored, nothing to calculate.\n return { success: true };\n }\n }\n\n return await calculateAndAddRoute(\n state,\n waypoints,\n showOnMap,\n hidePreviousEntries,\n monitor,\n showWaypoints,\n showSummaryBubbles,\n options,\n );\n } catch (error) {\n return {\n error: `Route calculation failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { withInsertedWaypoints } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { hidePreviousEntriesSchema, labelForLocationInput, locationInputSchema } from '../shared';\nimport { routesWriteOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\nimport { calculateAndAddRoute, resolveRouteWaypoints } from './set-route';\n\n/** Output schema for the add-waypoints-to-route tool. */\nexport const addWaypointsToRouteOutputSchema = z.union([routesWriteOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for adding waypoints to an existing route.\n */\nexport const addWaypointsToRouteSchema = z\n .object({\n origin: locationInputSchema\n .optional()\n .describe(\n 'Prepend a new origin in front of the current origin. The previous origin is kept ' +\n 'as the first intermediate stop (it is not replaced).',\n ),\n destination: locationInputSchema\n .optional()\n .describe(\n 'Append a new destination after the current destination. The previous destination ' +\n 'is kept as an intermediate stop (it is not replaced).',\n ),\n stops: z\n .array(locationInputSchema)\n .min(1)\n .optional()\n .describe(\n 'Intermediate stops to insert at optimal mid-positions along the existing route. ' +\n 'Multiple stops settle into natural along-route order regardless of input order.',\n ),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n })\n .refine((v) => v.origin !== undefined || v.destination !== undefined || (v.stops && v.stops.length > 0), {\n message: 'Provide at least one of `origin`, `destination`, or `stops`.',\n });\n\nexport const addWaypointsToRouteDescription =\n 'Extend the current route — prepend a new origin in front of the existing one, append a new ' +\n 'destination after the existing one, and/or insert intermediate stops at optimal positions along ' +\n 'the existing route geometry. Existing origin/destination are preserved as intermediate stops, ' +\n 'never replaced. Always recalculates. At least one of `origin`, `destination`, or `stops` MUST ' +\n 'be provided. Requires an existing route. To outright replace waypoints, use setRoute instead.';\n\nconst validateExistingRoute = async (state: ToolState) => {\n // Read the currently-shown entry's module — modules are now per-entry, so\n // there's only \"a route on the map\" when an entry has been shown.\n const currentModule = state.routing.currentEntryModule;\n if (!currentModule) {\n return 'No existing route on the map. Use calculate-route or updateRoutesDisplay first.';\n }\n const shownRoutes = currentModule.getShown();\n const shownRouteLines = shownRoutes?.mainLines;\n const shownWaypoints = shownRoutes?.waypoints;\n if (!shownRouteLines || !shownRouteLines.features.length) {\n return 'No existing route available. Use calculate-route first.';\n }\n if (!shownWaypoints || shownWaypoints.features.length < 2) {\n return 'No existing waypoints available. Use calculate-route first to create a route with waypoints.';\n }\n return { shownRouteLines, shownWaypoints };\n};\n\ntype ResolvedWaypoints = {\n origin?: Place | [number, number];\n destination?: Place | [number, number];\n stops: (Place | [number, number])[];\n};\n\nconst resolveAllInputs = async (\n params: {\n origin?: z.infer<typeof locationInputSchema>;\n destination?: z.infer<typeof locationInputSchema>;\n stops?: z.infer<typeof locationInputSchema>[];\n },\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<ResolvedWaypoints | { error: string }> => {\n const { origin, destination, stops = [] } = params;\n const inputs = [\n ...(origin ? [{ kind: 'origin' as const, location: origin }] : []),\n ...(destination ? [{ kind: 'destination' as const, location: destination }] : []),\n ...stops.map((location) => ({ kind: 'stop' as const, location })),\n ];\n\n const resolved = await Promise.all(inputs.map(({ location }) => resolveLocationInput(location, state, options)));\n\n const unresolved: string[] = [];\n for (let i = 0; i < inputs.length; i++) {\n if (resolved[i] === null) unresolved.push(labelForLocationInput(inputs[i].location));\n }\n if (unresolved.length > 0) {\n return { error: `Could not resolve: ${unresolved.join(', ')}` };\n }\n\n const result: ResolvedWaypoints = { stops: [] };\n for (let i = 0; i < inputs.length; i++) {\n const place = resolved[i]?.place as Place | [number, number];\n if (inputs[i].kind === 'origin') result.origin = place;\n else if (inputs[i].kind === 'destination') result.destination = place;\n else result.stops.push(place);\n }\n return result;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeAddWaypointsToRoute = async (\n params: z.infer<typeof addWaypointsToRouteSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof addWaypointsToRouteOutputSchema>> => {\n const { origin, destination, stops, showOnMap, hidePreviousEntries } = params;\n try {\n const routeResult = await validateExistingRoute(state);\n if (typeof routeResult === 'string') return { error: routeResult };\n const { shownRouteLines, shownWaypoints } = routeResult;\n\n const resolved = await resolveAllInputs({ origin, destination, stops }, state, options);\n if ('error' in resolved) return resolved;\n\n const baseWaypoints: WaypointLike[] = [\n ...(resolved.origin ? [resolved.origin] : []),\n ...shownWaypoints.features,\n ...(resolved.destination ? [resolved.destination] : []),\n ];\n\n const updatedWaypoints =\n resolved.stops.length > 0\n ? withInsertedWaypoints(shownRouteLines.features[0], baseWaypoints, resolved.stops)\n : baseWaypoints;\n\n const waypoints = resolveRouteWaypoints(updatedWaypoints);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n return await calculateAndAddRoute(\n state,\n waypoints,\n showOnMap,\n hidePreviousEntries,\n undefined,\n undefined,\n undefined,\n options,\n );\n } catch (error) {\n return {\n error: `Failed to add waypoints to route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { bboxFromBBoxes, type HasBBox, type POICategory } from '@tomtom-org/maps-sdk/core';\nimport { discoverPlaces, type GeoBias, resolvePOICategories } from '@tomtom-org/maps-sdk/services';\nimport type { MultiPolygon, Polygon, Position } from 'geojson';\nimport { z } from 'zod';\nimport type { FeatureFlags, ToolEntry, ToolEntryBuilder, ToolExecuteOptions, ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlaces } from '../../utils';\nimport {\n getRangePolygons,\n getViewportBoundingBox,\n globalWhereSchema,\n isResolveError,\n nearbyWhereSchema,\n placesEntryIdHintSchema,\n type ResolvedAreaDisclosure,\n resolveNearby,\n resolveWithin,\n sharedWithinFields,\n showEntryGeometries,\n shownSchema,\n showPlaceGeometriesSchema,\n showPlacesSchema,\n showResultsOnMap,\n withAgentToolkitHeaders,\n} from '../shared';\nimport { toolStateToWhereContext } from '../shared/tool-state-where-context';\nimport { buildPlacesOutputSchema, resolvedAreasOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Build the flag-aware output schema for the discover-places tool. */\nexport const buildDiscoverPlacesOutputSchema = (flags: FeatureFlags) =>\n z.union([\n buildPlacesOutputSchema(flags).extend({\n shown: shownSchema.optional(),\n placesEntryId: z.string().optional(),\n label: z\n .string()\n .optional()\n .describe('Human-readable label stored with the entry (e.g. \"cafe\", \"CINEMA (50 places)\").'),\n geometriesFetched: z\n .number()\n .optional()\n .describe('Count of boundary polygons fetched and cached on the entry (when `geometries` was set).'),\n geometriesShown: z.boolean().optional().describe('Whether the fetched polygons were rendered on the map.'),\n resolvedAreas: resolvedAreasOutputSchema.optional(),\n unmatchedPoiCategories: z\n .array(z.string())\n .optional()\n .describe('`poiCategories` inputs that matched no category, left out of the search.'),\n }),\n toolErrorSchema,\n ]);\n\n/** Output schema built with no feature flags set. */\nexport const discoverPlacesOutputSchema = buildDiscoverPlacesOutputSchema({});\n\n// --- schema builders ---\n//\n// `within` fields are shared with getTrafficIncidents and are imported from schema.ts, so both\n// tools build their `within` schemas from the same field definitions.\n\n// `within` mode for discoverPlaces — plural-form fields. Either `viewport` alone, or any\n// combination of the multi-region fields. `viewport` is mutually exclusive with everything else.\nconst buildDiscoverWithinWhereSchema = (_flags: FeatureFlags) =>\n z.object(sharedWithinFields).refine(\n (data) => {\n const hasViewport = data.viewport === true;\n const hasMulti =\n data.queries !== undefined ||\n data.placeIds !== undefined ||\n data.geometries !== undefined ||\n data.range !== undefined ||\n data.route !== undefined;\n if (hasViewport && hasMulti) return false;\n return hasViewport || hasMulti;\n },\n {\n message:\n '`within` requires EITHER `viewport: true` OR one or more multi-region fields ' +\n '(queries / placeIds / geometries / range / route), not both.',\n },\n );\n\n// Route-relative ranked-detour scope: `discoverPlaces` with a `route`, which ranks by detour cost.\nconst maxDetourWhereSchema = z.object({\n mode: z.literal('maxDetour'),\n routeId: z\n .string()\n .optional()\n .describe('Route entry ID (e.g. \"routes-0\"). Use recallState. Default: latest route.'),\n maxDetourTimeSeconds: z\n .number()\n .positive()\n .describe('Max extra travel time (seconds) the user accepts to reach a POI.'),\n sortBy: z\n .enum(['detourTime', 'detourOffset'])\n .optional()\n .describe('\"detourTime\" (default) ranks by extra travel time; \"detourOffset\" by distance from the route line.'),\n limit: z\n .number()\n .max(100)\n .optional()\n .describe('Max results. Default: 10. Use this mode for short ranked lists, not bulk discovery.'),\n});\n\nexport const buildDiscoverPlacesWhereSchema = (flags: FeatureFlags) => {\n const withinFieldList = 'queries/placeIds/geometries/range/route';\n return z\n .union([buildDiscoverWithinWhereSchema(flags), nearbyWhereSchema, maxDetourWhereSchema, globalWhereSchema])\n .describe(\n 'Geographic scope, by `mode`: ' +\n `\\`within\\` (area — \\`viewport\\` alone, OR any combo of ${withinFieldList}) | ` +\n '`nearby` (point bias — position/viewport/query, optional radiusMeters) | ' +\n '`maxDetour` (ranked detour list off a route) | `global` (no bias). ' +\n 'Default: `{ mode: \"within\", viewport: true }`.',\n );\n};\n\n/**\n * Tool schema for discovering places. Built from {@link FeatureFlags} so the LLM only sees\n * fields the underlying search backend actually supports.\n */\nexport const buildDiscoverPlacesSchema = (flags: FeatureFlags) => {\n const baseShape = {\n query: z\n .string()\n .optional()\n .describe(\n 'Free-text filter on POI name/address. ' +\n 'NEVER a city/region/area name (e.g. \"Paris\", \"Amsterdam\") — those go in `where`. ' +\n 'For \"restaurants in Paris\": `query` is empty (or a name filter like \"pizza\"), `where.queries: [{query: \"Paris\"}]`. ' +\n 'Omit when filtering by category — use `poiCategories` instead.',\n ),\n where: buildDiscoverPlacesWhereSchema(flags).optional(),\n poiCategories: z\n .array(z.string())\n .optional()\n .describe(\n 'CONSTANT_CASE category codes. ALWAYS batch all requested categories in ONE array — never one call per category. ' +\n 'Common: \"RESTAURANT\", \"CAFE\", \"BAR\", \"HOTEL\", \"PARK\", \"PARK_AND_RECREATION_AREA\", ' +\n '\"CHARGING_LOCATION\", \"FUEL_STATION\", \"PARKING_GARAGE\", ' +\n '\"SUPERMARKET\", \"PHARMACY\", \"ATM\". ' +\n 'For anything else, pass the plain words (e.g. \"italian food\", \"gym\"): this call resolves them to their best codes.',\n ),\n show: showPlacesSchema\n .optional()\n .describe(\n 'Display places/geometries on the map. ' +\n 'Skip for intermediate results — let the follow-up (processData / analyseData) render the final entry.',\n ),\n entryId: placesEntryIdHintSchema,\n geometries: showPlaceGeometriesSchema,\n };\n\n return z.object(baseShape).refine((data) => !!data.query || !!data.poiCategories?.length, {\n message:\n 'discoverPlaces needs a search subject: set `query` (free-text) or `poiCategories`. ' +\n 'For a single named place (no filter), use locatePlace — including for its boundary polygon (`geometry`).',\n });\n};\n\nexport const buildDiscoverPlacesDescription = (_flags: FeatureFlags): string => {\n const withinFieldList = 'queries/placeIds/geometries/range/route';\n const preferLine = 'Prefer `queries` / `placeIds` (resolve to precise polygons) over raw `geometries`. ';\n return (\n 'Search for MULTIPLE places in a region — REQUIRES a search subject (`query` and/or `poiCategories`). ' +\n 'NOT for a single named place — use locatePlace for that (including its polygon/boundary via `geometry`). ' +\n 'Stores an entry and returns `placesEntryId`. ' +\n 'CALL ONCE PER REQUEST — batch all `poiCategories` + areas in one call; split only for explicitly comparable result sets. ' +\n `\\`where.mode\\`: \\`within\\` (\\`viewport\\` OR any combo of ${withinFieldList}), ` +\n '`nearby` (position/viewport/query + optional radiusMeters), `maxDetour` (≤100 ranked detours off a route), `global`. ' +\n preferLine +\n 'Route: `within.route` = corridor scan; `maxDetour` = ranked detour list. Both cap at 100 results. ' +\n 'Default: `{ mode: \"within\", viewport: true }`. ' +\n 'Never call this with empty `query` AND empty `poiCategories` — that is locatePlace territory.'\n );\n};\n\n// Instances used by the registry's static {@link DEFAULT_TOOLS} and re-exported for any direct\n// consumers.\nconst DEFAULT_FLAGS: FeatureFlags = {};\n\n/**\n * Input schema for the discover-places tool.\n * Use {@link buildDiscoverPlacesSchema} to build a flag-aware variant.\n */\nexport const discoverPlacesSchema = buildDiscoverPlacesSchema(DEFAULT_FLAGS);\n\n/** Tool description for the discover-places tool. */\nexport const discoverPlacesDescription = buildDiscoverPlacesDescription(DEFAULT_FLAGS);\n\n// What the `where` resolvers produce before a backend is picked: at most one of the two, but which\n// one depends on the mode, so it stays loose until `toGeoBias` commits it to the service's union.\ntype WhereBias = { boundingBox?: HasBBox; position?: Position };\n\nconst toGeoBias = (bias: WhereBias | undefined, radiusMeters: number | undefined): GeoBias | undefined => {\n if (bias?.position) return { position: bias.position, radiusMeters };\n\n return bias?.boundingBox ? { boundingBox: bias.boundingBox } : undefined;\n};\n\ntype MultiFilters = {\n boundingBoxes?: HasBBox[];\n geometries?: (Polygon | MultiPolygon)[];\n};\n\ntype DiscoverPlacesWhere = z.infer<ReturnType<typeof buildDiscoverPlacesWhereSchema>>;\ntype DiscoverWithinWhere = z.infer<ReturnType<typeof buildDiscoverWithinWhereSchema>>;\ntype NearbyWhere = z.infer<typeof nearbyWhereSchema>;\ntype MaxDetourWhere = z.infer<typeof maxDetourWhereSchema>;\n\nconst searchByDetour = async (\n state: ToolState,\n detour: MaxDetourWhere,\n query: string | undefined,\n resolvedPoiCategories: POICategory[] | undefined,\n entryId: string | undefined,\n options?: ToolExecuteOptions,\n): Promise<{ result: Awaited<ReturnType<typeof discoverPlaces>>; placesEntryId: string } | { error: string }> => {\n const entries = state.routing.entries;\n if (entries.length === 0) {\n return { error: 'No routes in state. Calculate a route first via setRoute.' };\n }\n const routeEntry = detour.routeId ? entries.find((e) => e.id === detour.routeId) : entries.at(-1);\n if (!routeEntry) {\n return { error: `No route found with id \"${detour.routeId}\". Use recallState to list available routes.` };\n }\n const routeFeature = routeEntry.data.features[0];\n if (!routeFeature) {\n return { error: `Route entry \"${routeEntry.id}\" has no geometry.` };\n }\n\n const requestParams = withAgentToolkitHeaders({\n route: routeFeature,\n maxDetourTimeSeconds: detour.maxDetourTimeSeconds,\n ...(query && { query }),\n ...(resolvedPoiCategories && { filters: { poiCategories: resolvedPoiCategories } }),\n ...(detour.sortBy && { sortBy: detour.sortBy }),\n ...(detour.limit !== undefined && { limit: detour.limit }),\n signal: options?.signal,\n });\n const result = await discoverPlaces(requestParams);\n const placesEntryId = await state.places.addPlaceResult(\n result,\n makePlacesLabel(result, { query, poiCategories: resolvedPoiCategories, routeLabel: routeEntry.label }),\n entryId,\n );\n return { result, placesEntryId };\n};\n\n// `search` consumes only `geometries` out of `MultiFilters`; `boundingBoxes` are merged into the\n// bias by the callers that collect them.\nconst searchGeometries = (filters: MultiFilters): (Polygon | MultiPolygon)[] | undefined =>\n filters.geometries?.length ? filters.geometries : undefined;\n\n// Single dispatch point for the places search backend, so every caller shares one `limit` cap and\n// one filter projection.\nconst dispatchPlacesSearch = (\n params: {\n query: string | undefined;\n poiCategories: POICategory[] | undefined;\n bias?: WhereBias;\n radiusMeters?: number;\n multiFilters: MultiFilters;\n },\n options?: ToolExecuteOptions,\n): Promise<Awaited<ReturnType<typeof discoverPlaces>>> => {\n const { query, poiCategories, bias, radiusMeters, multiFilters } = params;\n const geoBias = toGeoBias(bias, radiusMeters);\n const geometries = searchGeometries(multiFilters);\n const commonParams = {\n query,\n ...(poiCategories && { filters: { poiCategories } }),\n signal: options?.signal,\n limit: 100,\n };\n // Geometry search takes no `geoBias`: the geometries are the area, so a bias is left out with them.\n const requestParams = geometries\n ? { ...commonParams, geometries }\n : { ...commonParams, ...(geoBias && { geoBias }) };\n return discoverPlaces(withAgentToolkitHeaders(requestParams));\n};\n\n// What both dispatch branches need. Each branch adds its own on top: `searchInRange`\n// a range id, `searchWithBias` the bias it resolved.\ntype DispatchBranchParams = {\n state: ToolState;\n query: string | undefined;\n resolvedPoiCategories: POICategory[] | undefined;\n multiFilters: MultiFilters;\n entryId: string | undefined;\n routeLabel: string | undefined;\n};\n\nconst searchInRange = async (\n params: DispatchBranchParams & { range: string },\n options?: ToolExecuteOptions,\n): Promise<{ result: Awaited<ReturnType<typeof discoverPlaces>>; placesEntryId: string } | { error: string }> => {\n const { state, range, query, resolvedPoiCategories, multiFilters, entryId, routeLabel } = params;\n // Combine every range's polygon into the search bias so multi-origin entries search the union\n // of their reachable areas.\n const ranged = getRangePolygons(state, range);\n if ('error' in ranged) return ranged;\n const combinedGeometries = [...ranged.polygons, ...(multiFilters.geometries ?? [])];\n const result = await dispatchPlacesSearch(\n {\n query,\n poiCategories: resolvedPoiCategories,\n multiFilters: { ...multiFilters, geometries: combinedGeometries },\n },\n options,\n );\n const placesEntryId = await state.places.addPlaceResult(\n result,\n makePlacesLabel(result, {\n query,\n poiCategories: resolvedPoiCategories,\n ...(routeLabel && { routeLabel }),\n }),\n entryId,\n );\n return { result, placesEntryId };\n};\n\n// Resolves a discoverPlaces `within` mode to the search params it should run with. Either the\n// `viewport` bias path, or the multi-region path (any combo of queries / placeIds / geometries /\n// range / route). The two paths are mutually exclusive at the schema level — see the refinement on\n// `buildDiscoverWithinWhereSchema`.\ntype WithinResolution = {\n bias: WhereBias;\n multiFilters: MultiFilters;\n range?: string;\n routeLabel?: string;\n // Where each named query actually resolved — the grounded match (e.g. \"restaurants in east\n // London\" → \"London, CA\"). Empty on the viewport path (no named query to mis-resolve) and on raw\n // bbox/geometry input.\n resolvedAreas?: ResolvedAreaDisclosure[];\n};\n\nconst resolveDiscoverWithin = async (\n where: DiscoverWithinWhere,\n state: ToolState,\n baseFilters: MultiFilters,\n options?: ToolExecuteOptions,\n): Promise<WithinResolution | { error: string }> => {\n if (where.viewport) {\n return {\n bias: { boundingBox: getViewportBoundingBox(state.baseMap) },\n multiFilters: baseFilters,\n };\n }\n\n // Resolve queries / placeIds / geometries / route via the shared resolver. resolveWithin returns\n // an empty area set (not an error) when nothing resolved, so a range-only within — whose scope is\n // carried below — composes fine here.\n const within = await resolveWithin(\n {\n boundingBox: undefined,\n queries: where.queries,\n placeIds: where.placeIds,\n geometries: where.geometries,\n route: where.route,\n },\n toolStateToWhereContext(state, options),\n );\n if (isResolveError(within)) return within;\n const resolvedGeometries = within.filter((a) => a.polygon).map((a) => a.polygon as Polygon | MultiPolygon);\n const resolvedBoundingBoxes = within.filter((a) => !a.polygon).map((a) => a.bbox);\n const routeLabel = within.find((a) => a.source === 'route')?.label;\n // Surface where each named query resolved — the grounded match (mirrors getTrafficIncidents), so\n // the agent can confirm/correct a wrong same-name area.\n const resolvedAreas = within\n .filter((a) => a.source === 'query' && a.label)\n .map((a) => ({ matched: a.label as string, ...(a.query !== undefined && { query: a.query }) }));\n\n const mergedBoundingBoxes: HasBBox[] = [...(resolvedBoundingBoxes as HasBBox[])];\n const mergedGeometries: (Polygon | MultiPolygon)[] = [...resolvedGeometries];\n\n const multiFilters: MultiFilters = {\n ...baseFilters,\n ...(mergedBoundingBoxes.length && { boundingBoxes: mergedBoundingBoxes }),\n ...(mergedGeometries.length && { geometries: mergedGeometries }),\n };\n\n // An area that resolves without a boundary polygon contributes only a bbox, and the search\n // dispatch consumes `geometries` alone. Without a bbox bias here a bbox-only resolution would\n // run unscoped — a global search wearing the label of the area the user named. When polygons\n // did resolve they carry the scope instead, and any bbox-only area alongside them is dropped:\n // narrower than asked for, which is the safer of the two ways to be wrong.\n const scopeBoundingBox = mergedGeometries.length ? undefined : bboxFromBBoxes(resolvedBoundingBoxes);\n\n return {\n bias: scopeBoundingBox ? { boundingBox: scopeBoundingBox } : {},\n multiFilters,\n range: where.range,\n routeLabel,\n ...(resolvedAreas.length > 0 && { resolvedAreas }),\n };\n};\n\n// Resolves a `nearby` bias to a position (and optional radius cap) via the shared resolver. A\n// `query` bias surfaces where it resolved (`resolvedAreas`, mirrors the within-query path) — a bias\n// landing on a same-named place elsewhere silently skews the results to the wrong locale.\nconst resolveNearbyBias = async (\n where: NearbyWhere,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<{ bias: WhereBias; radiusMeters?: number; resolvedAreas?: ResolvedAreaDisclosure[] }> => {\n // `position` is a literal point — nothing to resolve, so it never goes through resolveNearby.\n if (where.position) {\n return { bias: { position: where.position }, radiusMeters: where.radiusMeters };\n }\n const resolved = await resolveNearby(\n {\n viewport: where.viewport,\n queries: where.query ? [{ query: where.query, queryAs: where.queryAs }] : undefined,\n },\n toolStateToWhereContext(state, options),\n );\n const resolvedAreas = resolved.label\n ? [{ matched: resolved.label, ...(resolved.query !== undefined && { query: resolved.query }) }]\n : [];\n return {\n bias: resolved.position ? { position: resolved.position } : {},\n radiusMeters: where.radiusMeters,\n ...(resolvedAreas.length > 0 && { resolvedAreas }),\n };\n};\n\ntype ResolvedDiscoverBias = {\n bias: WhereBias;\n multiFilters: MultiFilters;\n radiusMeters?: number;\n range?: string;\n routeLabel?: string;\n resolvedAreas?: ResolvedAreaDisclosure[];\n};\n\n// Dispatch the `where` mode to its dedicated resolver. `maxDetour` is handled separately by the\n// caller (different SDK endpoint); `global` falls through with an empty bias.\nconst resolveDiscoverBias = async (\n effectiveWhere: DiscoverPlacesWhere,\n state: ToolState,\n baseMultiFilters: MultiFilters,\n options?: ToolExecuteOptions,\n): Promise<ResolvedDiscoverBias | { error: string }> => {\n if (effectiveWhere.mode === 'nearby') {\n const nearby = await resolveNearbyBias(effectiveWhere, state, options);\n return {\n bias: nearby.bias,\n multiFilters: baseMultiFilters,\n radiusMeters: nearby.radiusMeters,\n resolvedAreas: nearby.resolvedAreas,\n };\n }\n if (effectiveWhere.mode === 'within') {\n const resolved = await resolveDiscoverWithin(effectiveWhere, state, baseMultiFilters, options);\n if ('error' in resolved) return resolved;\n return {\n bias: resolved.bias,\n multiFilters: resolved.multiFilters,\n range: resolved.range,\n routeLabel: resolved.routeLabel,\n resolvedAreas: resolved.resolvedAreas,\n };\n }\n // mode === 'global'\n return { bias: {}, multiFilters: baseMultiFilters };\n};\n\n// Geocoded place names produce a human-readable label suffix on the entry.\nconst resolveWhereLabel = (where: DiscoverPlacesWhere | undefined): string | undefined => {\n if (!where) return undefined;\n if (where.mode === 'within' && where.queries?.length) return where.queries.map((q) => q.query).join(', ');\n if (where.mode === 'nearby' && where.query) return where.query;\n return undefined;\n};\n\nconst searchWithBias = async (\n params: DispatchBranchParams & {\n bias: WhereBias;\n radiusMeters: number | undefined;\n whereLabel: string | undefined;\n },\n options?: ToolExecuteOptions,\n): Promise<{ result: Awaited<ReturnType<typeof discoverPlaces>>; placesEntryId: string }> => {\n const { state, query, bias, resolvedPoiCategories, radiusMeters, multiFilters, entryId, whereLabel, routeLabel } =\n params;\n const result = await dispatchPlacesSearch(\n {\n query,\n poiCategories: resolvedPoiCategories,\n bias,\n radiusMeters,\n multiFilters,\n },\n options,\n );\n const placesEntryId = await state.places.addPlaceResult(\n result,\n makePlacesLabel(result, {\n query,\n poiCategories: resolvedPoiCategories,\n where: whereLabel,\n ...(routeLabel && { routeLabel }),\n }),\n entryId,\n );\n return { result, placesEntryId };\n};\n\n// Render-on-map + boundary-geometry post-processing shared by every dispatch branch.\nconst finalizeDiscoverResult = async (\n state: ToolState,\n result: Awaited<ReturnType<typeof discoverPlaces>>,\n placesEntryId: string,\n show: z.infer<ReturnType<typeof buildDiscoverPlacesSchema>>['show'],\n geometries: z.infer<ReturnType<typeof buildDiscoverPlacesSchema>>['geometries'],\n flags: FeatureFlags,\n resolvedAreas?: ResolvedAreaDisclosure[],\n options?: ToolExecuteOptions,\n) => {\n const shown = show ? await showResultsOnMap(state, [placesEntryId], show) : undefined;\n\n let geometriesFetched: number | undefined;\n let geometriesShown: boolean | undefined;\n if (geometries) {\n const geometryResult = await showEntryGeometries(state, placesEntryId, geometries, options);\n geometriesFetched = geometryResult.fetched;\n if (geometryResult.shown) geometriesShown = true;\n }\n\n const label = state.places.entries.find((entry) => entry.id === placesEntryId)?.label;\n\n return {\n ...summarizePlaces(result),\n placesEntryId,\n ...(label && { label }),\n ...(shown && { shown }),\n ...(geometriesFetched !== undefined && { geometriesFetched }),\n ...(geometriesShown !== undefined && { geometriesShown }),\n ...(resolvedAreas && resolvedAreas.length > 0 && { resolvedAreas }),\n };\n};\n\n/**\n * Build the discoverPlaces executor for a given {@link FeatureFlags} bag. Every search call goes\n * through the {@link discoverPlaces} router.\n */\nexport const buildExecuteDiscoverPlaces = (flags: FeatureFlags) => {\n return async (\n params: z.infer<ReturnType<typeof buildDiscoverPlacesSchema>>,\n state: ToolState,\n options?: ToolExecuteOptions,\n ): Promise<z.infer<typeof discoverPlacesOutputSchema>> => {\n const { query, where, poiCategories, show, entryId, geometries } = params;\n\n // Default to viewport-bounded when no `where` was supplied.\n const effectiveWhere: DiscoverPlacesWhere = where ?? { mode: 'within', viewport: true };\n const baseMultiFilters: MultiFilters = {};\n\n try {\n // Resolve once upstream: exact catalog codes pass through, natural-language terms resolve to\n // their best catalog matches. We only error when the LLM passed inputs and NONE matched\n // anything, since a no-filter search would silently widen the result set.\n // Note: this trades a hard error for confusable inputs (e.g. \"PARKING_LOT\" → parking codes\n // when the user meant parks); we accept that and rely on the description hints to steer\n // the LLM toward the right codes up-front.\n const categories = poiCategories?.length\n ? await resolvePOICategories(\n withAgentToolkitHeaders({ filters: poiCategories, signal: options?.signal }),\n )\n : undefined;\n if (categories && !categories.poiCategories.length) {\n return {\n error:\n `No POI categories matched: ${categories.unmatched.map((c) => `\"${c}\"`).join(', ')}. ` +\n 'Retry with a broader word (e.g. \"restaurant\" rather than \"trattoria\") or an exact CONSTANT_CASE code, ' +\n 'or search it as free text in `query`.',\n };\n }\n const resolvedPoiCategories = categories?.poiCategories;\n const unmatchedNote = categories?.unmatched.length\n ? { unmatchedPoiCategories: categories.unmatched }\n : undefined;\n\n // maxDetour dispatches to a different SDK endpoint and shares no params with the rest.\n if (effectiveWhere.mode === 'maxDetour') {\n const detourResult = await searchByDetour(\n state,\n effectiveWhere,\n query,\n resolvedPoiCategories,\n entryId,\n options,\n );\n if ('error' in detourResult) return detourResult;\n return {\n ...(await finalizeDiscoverResult(\n state,\n detourResult.result,\n detourResult.placesEntryId,\n show,\n geometries,\n flags,\n undefined,\n options,\n )),\n ...unmatchedNote,\n };\n }\n\n const resolved = await resolveDiscoverBias(effectiveWhere, state, baseMultiFilters, options);\n if ('error' in resolved) return resolved;\n const { bias, multiFilters, radiusMeters, range, routeLabel, resolvedAreas } = resolved;\n\n if (range) {\n const rangeResult = await searchInRange(\n { state, range, query, resolvedPoiCategories, multiFilters, entryId, routeLabel },\n options,\n );\n if ('error' in rangeResult) return rangeResult;\n return {\n ...(await finalizeDiscoverResult(\n state,\n rangeResult.result,\n rangeResult.placesEntryId,\n show,\n geometries,\n flags,\n resolvedAreas,\n options,\n )),\n ...unmatchedNote,\n };\n }\n\n // Prefer the grounded match (\"London, CA\") over the query echo (\"east London\") in the\n // entry label so the chip never masks a wrong same-name resolution.\n const groundedLabel = resolvedAreas?.length ? resolvedAreas.map((a) => a.matched).join(', ') : undefined;\n const whereLabel = groundedLabel ?? resolveWhereLabel(effectiveWhere);\n const biasResult = await searchWithBias(\n {\n state,\n query,\n bias,\n resolvedPoiCategories,\n radiusMeters,\n multiFilters,\n entryId,\n whereLabel,\n routeLabel,\n },\n options,\n );\n return {\n ...(await finalizeDiscoverResult(\n state,\n biasResult.result,\n biasResult.placesEntryId,\n show,\n geometries,\n flags,\n resolvedAreas,\n options,\n )),\n ...unmatchedNote,\n };\n } catch (error) {\n return { error: `Search failed: ${error instanceof Error ? error.message : String(error)}` };\n }\n };\n};\n\n/** Executor for the discover-places tool, built with no feature flags set. */\nexport const executeDiscoverPlaces = buildExecuteDiscoverPlaces(DEFAULT_FLAGS);\n\n/**\n * Build a complete {@link ToolEntry} for `discoverPlaces` for the given {@link FeatureFlags}.\n * Description, schema, and executor are all picked from the same flag set so they always agree.\n *\n * Anything other than schema/description/execute (tags, examples, examplePrompts, related/depends)\n * is supplied by the caller via `metadata` so the registry retains a single source of truth.\n */\nexport const buildDiscoverPlacesEntry = <S extends ToolState = ToolState>(\n flags: FeatureFlags,\n metadata: Omit<ToolEntry<S>, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>,\n): ToolEntry<S> => ({\n ...metadata,\n description: buildDiscoverPlacesDescription(flags),\n inputSchema: buildDiscoverPlacesSchema(flags),\n outputSchema: buildDiscoverPlacesOutputSchema(flags),\n execute: buildExecuteDiscoverPlaces(flags) as ToolEntry<S>['execute'],\n});\n\n// Flag-agnostic metadata for the discover-places tool. Description, schema, and executor are\n// flag-aware and produced by the builder below.\nconst discoverPlacesMetadata = {\n classificationPrompt:\n 'Search multiple places by query/category in a region. BATCH categories+areas in one call. ' +\n '`query` filters POI names ONLY — area names go in `where`. ' +\n '`where.mode`: within (area) / nearby (point bias) / maxDetour (route-relative ranked) / global. ' +\n 'For \"X in [named area]\", put X in top-level `query`/`poiCategories` and the area in `where.queries: [{query: \"...\"}]` — geocoded in one step, no precursor locatePlace. ' +\n 'Within-mode: route → corridor. Prefer geometries/placeIds over bbox. ' +\n 'Also OWNS outlining the SUB-AREAS of a place — the neighbourhoods/districts of a city (\"outline the ' +\n 'Amsterdam neighbourhoods\"); a SINGLE named place\\'s own boundary is locatePlace, and incrementally ' +\n 'adding/removing outlines among already-shown places is updatePlacesDisplay. ' +\n 'Skip for already-stored places — use recallState/processData.',\n tags: ['discover', 'place', 'location'],\n examples: [\n 'discoverPlaces({ poiCategories: [\"RESTAURANT\", \"CAFE\", \"BAR\"], entryId: \"amsterdam-food-spots\" }) // batch many categories + semantic id',\n 'discoverPlaces({ query: \"coffee\", where: { mode: \"nearby\", position: [4.9, 52.4], radiusMeters: 500 } })',\n 'discoverPlaces({ query: \"parking\", where: { mode: \"within\", queries: [{ query: \"De Jordaan, Amsterdam\", queryAs: \"place\" }] } }) // area name goes in `where.queries`, NOT top-level `query`',\n 'discoverPlaces({ query: \"coffee\", where: { mode: \"nearby\", query: \"Schiphol Airport\", queryAs: \"poi\", radiusMeters: 2000 } }) // POI-resolved bias point',\n 'discoverPlaces({ poiCategories: [\"HOTEL\"], where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }, { query: \"Utrecht\" }] }, entryId: \"ams-utrecht-hotels\" })',\n 'discoverPlaces({ query: \"bakery\", where: { mode: \"within\", range: \"ranges-0\" } })',\n 'discoverPlaces({ poiCategories: [\"CAFE\"], show: { markerType: \"pin\", zoomMode: \"auto\" } }) // omit `where` → defaults to viewport',\n 'discoverPlaces({ poiCategories: [\"RESTAURANT\"], where: { mode: \"within\", queries: [{ query: \"Paris\" }] } }) // \"restaurants in Paris\" — subject in `poiCategories`, area in `where.queries`',\n 'discoverPlaces({ poiCategories: [\"CHARGING_LOCATION\"], where: { mode: \"within\", route: { routeId: \"routes-0\", widthMeters: 1000 } } }) // corridor scan along the route',\n 'discoverPlaces({ query: \"coffee\", where: { mode: \"maxDetour\", maxDetourTimeSeconds: 300, limit: 5 } }) // top 5 coffee detours within 5 min off the latest route',\n 'discoverPlaces({ poiCategories: [\"CHARGING_LOCATION\"], where: { mode: \"maxDetour\", routeId: \"routes-1\", maxDetourTimeSeconds: 600, sortBy: \"detourOffset\" } }) // ranked EV detours on a specific route',\n 'discoverPlaces({ query: \"bakery\", where: { mode: \"within\", placeIds: [\"G55fc4abe-...\"] } }) // search inside stored place polygons (auto-fetched if needed)',\n 'discoverPlaces({ query: \"vegan\", where: { mode: \"global\" } })',\n ],\n examplePrompts: [\n 'Find coffee shops near me',\n 'Italian restaurants in Amsterdam',\n 'EV chargers in the current map area',\n 'Hotels in Amsterdam and Utrecht',\n 'Bakeries within the reachable range',\n 'Outline the Amsterdam neighbourhoods',\n // \"Show the boundaries of Amsterdam and Utrecht\" lives on locatePlace now — outlining two\n // WHOLE named cities is locatePlace's repeated `geometry: { mode: \"add\" }` path, which the\n // classifier (rightly) prefers over a discoverPlaces area search.\n 'Search for EV chargers in a corridor along my planned route',\n 'Cafes in a 500 m corridor around the planned route',\n 'Best 5 coffee stops without losing more than 5 min off my route',\n 'EV chargers I can reach with under 10 min detour',\n // Family / errand-runner persona: category search biased to an area or nearby point.\n 'Find playgrounds in this neighbourhood',\n 'Show pharmacies near Berlin Hauptbahnhof',\n ],\n relatedTools: [\n 'getPOICategoryCodes',\n 'updatePlacesDisplay',\n 'locatePlace',\n 'getViewport',\n 'findReachableAreas',\n 'processData',\n 'analyseData',\n ],\n dependsOn: ['setRoute'],\n} satisfies Omit<ToolEntry, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>;\n\n/**\n * Builder for the `discoverPlaces` default tool entry. Reads {@link FeatureFlags}\n * from the build options to pick the matching description, schema, and executor;\n * static metadata (tags, examples, related/depends) is colocated here as the\n * single source of truth.\n */\nexport const discoverPlacesBuilder: ToolEntryBuilder = (options) =>\n buildDiscoverPlacesEntry(options.featureFlags ?? {}, discoverPlacesMetadata);\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { BudgetType, Place, PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport type { GeometryFillStyle } from '@tomtom-org/maps-sdk/map';\nimport type { ReachableRangeParams } from '@tomtom-org/maps-sdk/services';\nimport { calculateReachableRanges } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ReachableRange, ToolExecuteOptions, ToolState } from '../../types';\nimport { makeRangesLabel } from '../../utils';\nimport {\n geometryFillStyleSchema,\n hidePreviousEntriesSchema,\n hidePreviousShownEntries,\n locationInputSchema,\n withAgentToolkitHeaders,\n} from '../shared';\nimport { fitMapToBBox } from '../shared/map-fit';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\nexport const budgetSchema = z.object({\n type: z\n .enum(['timeMinutes', 'distanceKM']) // TODO: 'remainingChargePCT', 'spentChargePCT', 'spentFuelLiters' these need vehicle data\n .describe('Budget type including its unit.'),\n value: z.number().describe('Budget value. e.g. 30 for 30 minutes, 50 for 50 km.'),\n});\n\nexport const findReachableAreasSchema = z.object({\n origins: z\n .array(locationInputSchema)\n .min(1)\n .describe(\n 'One or more starting points; one reachable area is computed per origin under the same budgets and bundled into a single ranges entry. Pass a single-element array for the classic one-origin call.',\n ),\n budgets: z\n .array(budgetSchema)\n .min(1)\n .describe(\n 'One or more budget constraints applied to every origin. Pass multiple for nested ranges (e.g. 10, 20, 30 min or km). Order does not matter — sorted internally.',\n ),\n fillStyle: geometryFillStyleSchema\n .optional()\n .describe(\n '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.',\n ),\n showOnMap: z.boolean().describe('Whether to render the range polygon(s) on the map.'),\n showOriginPin: z.boolean().optional().describe('Whether to drop a pin at each origin. Default: true.'),\n hidePreviousEntries: hidePreviousEntriesSchema('ranges'),\n});\n\nconst rangeOutputSchema = z.object({\n rangesId: z.string(),\n ranges: z.array(\n z.object({\n originName: z.string(),\n budgets: z.array(budgetSchema),\n }),\n ),\n skipped: z\n .array(z.object({ origin: z.string(), reason: z.string() }))\n .optional()\n .describe('Origins that could not contribute (unresolved query, empty geometry).'),\n});\n\nexport const findReachableAreasOutputSchema = z.union([\n rangeOutputSchema,\n z.object({ status: z.enum(['no_results']) }),\n toolErrorSchema,\n]);\n\nexport const findReachableAreasDescription =\n 'Compute reachable-area polygons (isochrone/isodistance) from one or more origins under shared time/distance ' +\n 'budgets. Multiple origins → compared in one call (sequential to respect QPS); multiple budgets → nested ' +\n 'ranges. Stored in state (recallState); `showOnMap: true` renders. Per-origin failures land under ' +\n '`skipped` instead of aborting the whole call.';\n\nconst computeReachableRangesForOrigin = async (\n budgets: z.infer<typeof budgetSchema>[],\n origin: Position,\n options?: ToolExecuteOptions,\n): Promise<PolygonFeatures | null> => {\n const requestParams: ReachableRangeParams[] = budgets.map((b) =>\n withAgentToolkitHeaders({\n origin,\n budget: { type: b.type as BudgetType, value: b.value },\n }),\n );\n const result = await calculateReachableRanges(requestParams, { signal: options?.signal });\n return result.features.length === 0 ? null : result;\n};\n\n// Combine every range polygon into a single FeatureCollection so the ranges\n// module can render them in one show() call and a single fitBounds covers them\n// all. Per-origin features keep their original `properties` from the service.\nconst mergeRangePolygons = (ranges: ReachableRange[]): PolygonFeatures | null => {\n const features = ranges.flatMap((r) => r.polygon?.features ?? []);\n if (features.length === 0) return null;\n return {\n type: 'FeatureCollection',\n features,\n } as PolygonFeatures;\n};\n\nconst formatOriginName = (range: ReachableRange): string =>\n range.origin.query ?? `${range.origin.position[1]}, ${range.origin.position[0]}`;\n\n// Best-effort label for an origin that never resolved, so `skipped` entries stay\n// traceable back to what the caller actually asked for.\nconst labelForUnresolvedOrigin = (origin: z.infer<typeof locationInputSchema>): string => {\n if ('query' in origin) return origin.query;\n\n if ('position' in origin) return `${origin.position[1]}, ${origin.position[0]}`;\n\n if ('placeIdOrEntryId' in origin) return origin.placeIdOrEntryId;\n\n return 'unnamed origin';\n};\n\n// Resolve each origin and compute its polygon, collecting per-origin failures into\n// `skipped` rather than aborting the whole call.\n//\n// Sequential — fan-out would race the service's per-key QPS limit when callers pass\n// many origins. The cost is latency, not correctness.\nconst resolveOriginsToRanges = async (\n origins: z.infer<typeof locationInputSchema>[],\n sortedBudgets: z.infer<typeof budgetSchema>[],\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<{ ranges: ReachableRange[]; skipped: { origin: string; reason: string }[] }> => {\n const ranges: ReachableRange[] = [];\n const skipped: { origin: string; reason: string }[] = [];\n\n for (const origin of origins) {\n const fallbackLabel = labelForUnresolvedOrigin(origin);\n const resolved = await resolveLocationInput(origin, state, options);\n if (!resolved) {\n skipped.push({ origin: fallbackLabel, reason: 'origin not found' });\n continue;\n }\n\n const polygon = await computeReachableRangesForOrigin(sortedBudgets, resolved.position, options);\n if (!polygon) {\n skipped.push({ origin: resolved.name ?? fallbackLabel, reason: 'no reachable area returned' });\n continue;\n }\n\n ranges.push({\n origin: resolved.query\n ? { query: resolved.query, position: resolved.position }\n : { position: resolved.position },\n budgets: sortedBudgets,\n polygon,\n });\n }\n\n return { ranges, skipped };\n};\n\nconst showOriginsAndPolygons = async (\n state: ToolState,\n rangesId: string,\n ranges: ReachableRange[],\n fillStyle: GeometryFillStyle,\n showOnMap: boolean,\n showOriginPin: boolean,\n) => {\n if (showOnMap) {\n const merged = mergeRangePolygons(ranges);\n if (merged) {\n // Per-entry ReachableRangesModule — each ranges entry owns its own\n // module so multiple entries can be on the map simultaneously.\n const rangesModule = await state.ranges.getEntryRangesModule(rangesId, fillStyle);\n await rangesModule.show(merged);\n if (merged.bbox) {\n fitMapToBBox(state.baseMap.mapLibreMap, merged.bbox);\n }\n }\n }\n if (showOriginPin) {\n const placesModule = await state.ranges.getEntryPlacesModule(rangesId);\n const originPlaces: Place[] = ranges.map((r, i) => ({\n type: 'Feature',\n id: `range-origin-${rangesId}-${i}`,\n geometry: { type: 'Point', coordinates: r.origin.position },\n properties: {\n type: 'Point Address',\n address: { freeformAddress: formatOriginName(r) },\n },\n }));\n await placesModule.show(originPlaces);\n }\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeFindReachableAreas = async (\n params: z.infer<typeof findReachableAreasSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof findReachableAreasOutputSchema>> => {\n const { origins, budgets, fillStyle, showOnMap, showOriginPin = true, hidePreviousEntries } = params;\n\n try {\n const sortedBudgets = [...budgets].sort((a, b) => b.value - a.value);\n\n const { ranges, skipped } = await resolveOriginsToRanges(origins, sortedBudgets, state, options);\n if (ranges.length === 0) return { status: 'no_results' };\n\n const rangesId = await state.ranges.addEntry({\n label: makeRangesLabel(sortedBudgets, ranges.map(formatOriginName)),\n data: ranges,\n });\n\n const willRender = showOnMap || showOriginPin;\n if (willRender) await hidePreviousShownEntries(state.ranges, [rangesId], hidePreviousEntries);\n\n await showOriginsAndPolygons(state, rangesId, ranges, fillStyle ?? 'outline', showOnMap, showOriginPin);\n\n // Track which entry is rendered so state-digest snapshots can surface it later.\n if (willRender) await state.ranges.markEntryShown(rangesId);\n\n // All ranges in this entry share the same `sortedBudgets`; reusing\n // it keeps the response type aligned with the input schema's narrower\n // budget enum (`ReachableRangeBudget` widens it with EV/fuel types).\n return {\n rangesId,\n ranges: ranges.map((r) => ({\n originName: formatOriginName(r),\n budgets: sortedBudgets,\n })),\n ...(skipped.length && { skipped }),\n };\n } catch (error) {\n return {\n error: `Reachable area calculation failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type Language } from '@tomtom-org/maps-sdk/core';\nimport { getPOICategoryCodes } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { withAgentToolkitHeaders } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-poi-category-codes tool. */\nexport const getPoiCategoryCodesOutputSchema = z.union([\n z.object({\n count: z.number().describe('Number of codes returned'),\n codes: z\n .array(z.string())\n .describe(\n 'POICategory enum values for discoverPlaces poiCategories. Generic codes (e.g. \"RESTAURANT\") also accepted by toggleTilesPOIs categories.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool input schema for fetching POI category codes.\n */\nexport const getPoiCategoryCodesSchema = z.object({\n filters: z\n .array(z.string())\n .optional()\n .describe(\n 'Case-insensitive filter strings matched against category names and synonyms. Omit to return all codes.',\n ),\n language: z.string().describe('Locale e.g. \"en-GB\", \"fr-FR\", \"es-ES\", \"de-DE\".'),\n});\n\nexport const getPoiCategoryCodesDescription =\n '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.';\n\n/**\n * Create the get-poi-category-codes tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetPoiCategoryCodes = async (\n params: z.infer<typeof getPoiCategoryCodesSchema>,\n _state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof getPoiCategoryCodesOutputSchema>> => {\n const { filters, language } = params;\n try {\n const requestParams = withAgentToolkitHeaders({\n filters,\n language: language as Language,\n signal: options?.signal,\n });\n const codes = await getPOICategoryCodes(requestParams);\n return {\n count: codes.length,\n codes,\n };\n } catch (error) {\n return {\n error: `Failed to fetch POI category codes: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Place, TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport type { TrafficAreaAnalyticsParams } from '@tomtom-org/maps-sdk/services';\nimport { geometryData, trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';\nimport type { MultiPolygon, Polygon } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { isoDate } from '../../utils';\nimport { locationInputSchema, withAgentToolkitHeaders } from '../shared';\nimport { fitMapToGeoJSON } from '../shared/map-fit';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\n\n// ---------------------------------------------------------------------------\n// Constants (inline — the SDK only type-exports these)\n// ---------------------------------------------------------------------------\n\nconst METRICS = ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'] as const;\n\nconst FUNCTIONAL_ROAD_CLASSES = [\n 'MOTORWAY',\n 'MAJOR_ROAD',\n 'OTHER_MAJOR_ROAD',\n 'SECONDARY_ROAD',\n 'LOCAL_CONNECTING_ROAD',\n 'LOCAL_ROAD_HIGH_IMPORTANCE',\n 'LOCAL_ROAD',\n 'LOCAL_ROAD_MINOR_IMPORTANCE',\n 'OTHER_ROAD',\n] as const;\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nexport const metricsSchema = z.object({\n speed: z.number().optional(),\n freeFlowSpeed: z.number().optional(),\n congestionLevel: z.number().optional(),\n travelTime: z.number().optional(),\n networkLength: z.number().optional(),\n});\n\n/** Output schema for the get-traffic-area-analytics tool. */\nexport const getTrafficAreaAnalyticsOutputSchema = z.union([\n z.object({\n entryId: z\n .string()\n .describe(\n 'Id of the new traffic-area-analytics entry — use with updateTrafficAreaAnalyticsDisplay / analyseData / processData.',\n ),\n name: z.string().optional(),\n dateRange: z.object({ start: z.string(), end: z.string() }),\n baseData: metricsSchema,\n metrics: z.array(z.string()),\n tileCount: z.number(),\n availableGranularities: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for fetching traffic area analytics.\n */\nexport const getTrafficAreaAnalyticsSchema = z\n .object({\n location: locationInputSchema\n .optional()\n .describe(\n 'Named location (city, region, country) to fetch the boundary for. Resolved via geocoding + geometryData. Provide this or bbox, never both.',\n ),\n bbox: z\n .array(z.number())\n .length(4)\n .optional()\n .describe(\n '[minLng, minLat, maxLng, maxLat] — use a wide area (at least 5-10km across, city-level) for meaningful results. Provide this or location, never both.',\n ),\n showOnMap: z\n .boolean()\n .optional()\n .describe(\n 'Visualize the result immediately with default settings. Use `updateTrafficAreaAnalyticsDisplay` to customize.',\n ),\n startDate: z\n .string()\n .optional()\n .describe(\n \"Start date 'YYYY-MM-DD'. Required unless `days` is given. Use with endDate for a continuous range (max 31 days).\",\n ),\n endDate: z\n .string()\n .optional()\n .describe(\n \"End date 'YYYY-MM-DD' (inclusive). Defaults to three days ago, the latest day with published data.\",\n ),\n days: z\n .array(z.string())\n .optional()\n .describe(\n \"Specific dates 'YYYY-MM-DD' for non-consecutive analysis. Mutually exclusive with startDate/endDate.\",\n ),\n metrics: z\n .union([z.literal('all'), z.array(z.enum([...METRICS])).min(1)])\n .describe(\n \"Traffic metrics to analyze. Use 'all' (preferred) or an explicit array like ['speed', 'congestionLevel', 'freeFlowSpeed', 'travelTime', 'networkLength'].\",\n ),\n functionalRoadClasses: z\n .union([z.literal('all'), z.array(z.enum([...FUNCTIONAL_ROAD_CLASSES]))])\n .optional()\n .describe(\"Road classes to include. 'all' (default) or array of specific classes.\"),\n hours: z\n .union([z.literal('all'), z.array(z.number().int().min(0).max(23))])\n .optional()\n .describe(\n \"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).\",\n ),\n })\n .refine((input) => (input.location === undefined) !== (input.bbox === undefined), {\n message: 'Provide exactly one area: either `location` or `bbox`.',\n })\n .refine((input) => !(input.days?.length && (input.startDate || input.endDate)), {\n message: 'Provide either `days` or a `startDate`/`endDate` range, not both.',\n });\n\nexport const getTrafficAreaAnalyticsDescription =\n 'Fetch historical traffic analytics for an area (≤31 days) via location (named city/region) or bbox. ' +\n 'Fetch ALL metrics in ONE call (visualization needs them together); use city-scale areas (~10 km+) for meaningful data. ' +\n 'Returns period averages — `showOnMap` visualizes immediately; use analyseData over the loaded entry for breakdowns. ' +\n 'The `hours` filter and every returned day and hour label are UTC.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n// Convert [minLng, minLat, maxLng, maxLat] to a GeoJSON Polygon.\nconst bboxToPolygon = (bbox: number[]): Polygon => {\n const [minLng, minLat, maxLng, maxLat] = bbox;\n return {\n type: 'Polygon',\n coordinates: [\n [\n [minLng, minLat],\n [maxLng, minLat],\n [maxLng, maxLat],\n [minLng, maxLat],\n [minLng, minLat],\n ],\n ],\n };\n};\n\n// Resolve the analytics area geometry from a location query or bbox.\nconst resolveGeometry = async (\n location: z.infer<typeof locationInputSchema> | undefined,\n bbox: number[] | undefined,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<Polygon | MultiPolygon | { error: string }> => {\n if (location) {\n const resolved = await resolveLocationInput(location, state, options);\n if (!resolved) return { error: 'Could not resolve the provided location.' };\n if (Array.isArray(resolved.place)) {\n return {\n error: 'A bare position cannot define an analytics area. Provide a named location query instead.',\n };\n }\n // resolved.place is a Place at runtime (locatePlace always returns Place | null)\n const requestParams = withAgentToolkitHeaders({\n geometries: [resolved.place as unknown as Place],\n signal: options?.signal,\n });\n const boundary = await geometryData(requestParams);\n const boundaryGeometry = boundary?.features?.[0]?.geometry as Polygon | MultiPolygon | undefined;\n if (!boundaryGeometry) {\n return { error: `No boundary polygon found for \"${resolved.name}\". Try a bbox instead.` };\n }\n return boundaryGeometry;\n }\n if (!bbox) return { error: 'Provide a location or bbox to define the analytics area.' };\n\n return bboxToPolygon(bbox);\n};\n\n// Resolve which date parameters to forward to the SDK from the tool inputs.\nconst resolveDateParams = (\n days: string[] | undefined,\n startDate: string | undefined,\n endDate: string | undefined,\n): { startDate?: string; endDate?: string; days?: string[] } => {\n if (days && days.length > 0) return { days };\n if (startDate) return endDate ? { startDate, endDate } : { startDate };\n return {};\n};\n\n// Show the given entry on its per-entry module with default settings, fitting the viewport.\nconst showAnalyticsOnMap = async (state: ToolState, entryId: string): Promise<void> => {\n await state.trafficAreaAnalytics.showEntry(entryId);\n const entry = state.trafficAreaAnalytics.findById(entryId);\n if (!entry) return;\n const analyticsModule = await state.trafficAreaAnalytics.getEntryModule(entryId);\n const defaultMetric = entry.data.properties?.metrics?.[0] ?? 'congestionLevel';\n analyticsModule.updateConfig({ activeMetric: defaultMetric });\n fitMapToGeoJSON(state.baseMap.mapLibreMap, entry.data.features, { pitch: 45 });\n};\n\n/** Extract metrics from a timed entry, omitting undefined values. */\nexport const extractMetrics = (entry: Record<string, unknown>) => ({\n ...(entry.speed !== undefined && { speed: entry.speed as number }),\n ...(entry.freeFlowSpeed !== undefined && { freeFlowSpeed: entry.freeFlowSpeed as number }),\n ...(entry.congestionLevel !== undefined && { congestionLevel: entry.congestionLevel as number }),\n ...(entry.travelTime !== undefined && { travelTime: entry.travelTime as number }),\n ...(entry.networkLength !== undefined && { networkLength: entry.networkLength as number }),\n});\n\n/** Compact summary of the analytics result — just headline numbers + what's available for drill-down. */\nconst summarize = (\n entryId: string,\n result: TrafficAreaAnalytics,\n dateRange: { start: string; end: string },\n): z.infer<typeof getTrafficAreaAnalyticsOutputSchema> => {\n const region = result.features[0]?.properties;\n if (!region) {\n return {\n entryId,\n dateRange,\n baseData: {},\n metrics: [],\n tileCount: 0,\n availableGranularities: [],\n };\n }\n\n const availableGranularities: string[] = [];\n if (region.timedData?.yearly?.length) availableGranularities.push('yearly');\n if (region.timedData?.monthly?.length) availableGranularities.push('monthly');\n if (region.timedData?.weekly?.length) availableGranularities.push('weekly');\n if (region.timedData?.daily?.length) availableGranularities.push('daily');\n if (region.timedData?.hourly?.length) availableGranularities.push('hourly');\n if (region.timedData?.average?.length) availableGranularities.push('average');\n\n return {\n entryId,\n ...(region.name && { name: region.name }),\n dateRange,\n baseData: region.baseData,\n metrics: result.properties?.metrics ?? [],\n tileCount: region.tiledData?.tiles?.length ?? 0,\n availableGranularities,\n };\n};\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the get traffic area analytics tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeGetTrafficAreaAnalytics = async (\n params: z.infer<typeof getTrafficAreaAnalyticsSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof getTrafficAreaAnalyticsOutputSchema>> => {\n const { location, bbox, showOnMap, startDate, endDate, days, metrics, functionalRoadClasses, hours } = params;\n\n // Move Portal API key (different from standard TomTom key)\n const apiKey = process.env.MOVE_PORTAL_KEY;\n if (!apiKey) {\n return { error: 'MOVE_PORTAL_KEY environment variable is not set.' };\n }\n\n try {\n const resolvedGeometry = await resolveGeometry(location, bbox, state, options);\n if ('error' in resolvedGeometry) return resolvedGeometry;\n\n const dateParams = resolveDateParams(days, startDate, endDate);\n\n const requestParams = withAgentToolkitHeaders({\n apiKey,\n ...dateParams,\n metrics,\n functionalRoadClasses: functionalRoadClasses ?? 'all',\n hours: hours ?? 'all',\n geometry: resolvedGeometry,\n signal: options?.signal,\n } as TrafficAreaAnalyticsParams);\n const result = await trafficAreaAnalytics(requestParams);\n\n // The response's dates, not the request's: the SDK fills in an omitted endDate.\n const dateRange = { start: isoDate(result.properties.startDate), end: isoDate(result.properties.endDate) };\n const regionName = result.features[0]?.properties?.name;\n const label = `${regionName ?? 'analytics'} (${dateRange.start} → ${dateRange.end})`;\n const entryId = await state.trafficAreaAnalytics.addEntry(result, label, {\n ...dateParams,\n metrics,\n functionalRoadClasses: functionalRoadClasses ?? 'all',\n hours: hours ?? 'all',\n });\n\n if (showOnMap) {\n await showAnalyticsOnMap(state, entryId);\n }\n\n return summarize(entryId, result, dateRange);\n } catch (error) {\n return {\n error: `Failed to get traffic area analytics: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n type BBox,\n bboxFromBBoxes,\n bboxFromGeoJSON,\n trafficIncidentRequestCategories,\n} from '@tomtom-org/maps-sdk/core';\nimport { type TrafficIncidentDetailsByBBoxParams, trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\n\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport {\n type AreaWhere,\n geoJsonBBoxSchema,\n getRangePolygons,\n isResolveError,\n type ResolvedAreaDisclosure,\n resolveWithin,\n sharedWithinFields,\n withAgentToolkitHeaders,\n} from '../shared';\nimport { toolStateToWhereContext } from '../shared/tool-state-where-context';\nimport { resolvedAreasOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n// Compact list of every entry currently held by `state.trafficIncidents`. Returned alongside\n// the freshly-fetched data so the LLM sees what's available without a separate recall call.\nconst entriesSummary = (state: ToolState) =>\n state.trafficIncidents.entries.map((e) => ({\n id: e.id,\n label: e.label,\n count: e.data.length,\n timestamp: e.timestamp,\n }));\n\n/**\n * Output schema for the get-traffic-incidents tool.\n *\n * By design this tool is a **loader**, not an aggregator. It fetches incidents into a new\n * (or already-loaded) entry, renders them on the map, and returns\n * `{ count, entryId, entries }` — `entries` is a compact summary of every entry currently\n * cached in `state.trafficIncidents`, so the agent can pick a follow-up target without a\n * separate recall call.\n *\n * Every aggregation (counts by category, severity, time-validity, top roads, top-N,\n * clustering, corridor group-by, proximity, spatial joins) is the job of `analyseData`\n * (counts / charts / deltas — results stored in the session `state.analyses` store) and `focusIncidents`\n * (highlight/filter a subset on an existing entry). Both inject the entry's full feature\n * set into the sandbox.\n *\n * Background polling rides along by default (`monitor: true`) — a shown fetch arms a monitor on the\n * new entry, but with `skipInitialTick` so the loader stays a single network call. Pass\n * `monitor: false` for a one-shot snapshot; `setTrafficIncidentsMonitor` remains for arming a\n * monitor on an entry loaded on an earlier turn.\n *\n * Historic shortcuts like a built-in summary, `topIncidents`, and `includeIncidents` were\n * removed: they let the agent skip real aggregation and misrepresent individual top-delay\n * incidents as \"clusters\" or \"hotspots\". The contract now forces the agent through\n * analyseData/focusIncidents for anything beyond `count`.\n */\nexport const getTrafficIncidentsOutputSchema = z.union([\n z.object({\n count: z.number(),\n entryId: z.string().optional(),\n resolvedAreas: resolvedAreasOutputSchema.optional(),\n entries: z.array(\n z.object({\n id: z.string(),\n label: z.string(),\n count: z.number(),\n timestamp: z.number(),\n }),\n ),\n monitoring: z\n .boolean()\n .optional()\n .describe('True when this fetch also armed a background monitor on the new entry.'),\n }),\n toolErrorSchema,\n]);\n\n// `within` mode for getTrafficIncidents — the shared multi-region block + traffic's singular\n// `boundingBox`. Accepts `viewport` alone, one+ multi-region field, OR both: when both are present\n// the executor's resolveAreas prefers the explicit multi-region scope over the ambient viewport.\nconst withinTrafficIncidentsWhereSchema = z\n .object({\n ...sharedWithinFields,\n boundingBox: geoJsonBBoxSchema\n .optional()\n .describe(\n 'Single bbox [W,S,E,N]. Composes with other multi-region fields (union); ' +\n 'may also be combined with viewport, in which case these explicit bounds take precedence over it.',\n ),\n })\n .refine(\n (data) =>\n data.viewport === true ||\n data.boundingBox !== undefined ||\n data.queries !== undefined ||\n data.placeIds !== undefined ||\n data.geometries !== undefined ||\n data.range !== undefined ||\n data.route !== undefined,\n {\n message:\n '`within` requires `viewport: true` OR one or more multi-region fields ' +\n '(boundingBox / queries / placeIds / geometries / range / route).',\n },\n );\n\nconst trafficIncidentsWhereSchema = z\n .union([withinTrafficIncidentsWhereSchema])\n .describe(\n 'Geographic scope. Only `within` mode (single area, defaults to `{ mode: \"within\", viewport: true }`). ' +\n 'Use `viewport` alone for \"the network / right now / this area\", or any combination of ' +\n 'boundingBox / queries / placeIds / geometries / range / route — each resolves to a bbox and ' +\n 'the union is sent to the SDK (≤10,000 km²).',\n );\n\n// The canonical category values listed inline so the agent maps the user's wording to the closest\n// one (e.g. \"roadblock\" → `road-closed`) instead of giving up when their term isn't a literal enum.\n// Built from `trafficIncidentRequestCategories` — single source of truth, can't drift.\nconst categoryFilterDescription = `Filter by incident category (server-side). Canonical values: ${trafficIncidentRequestCategories.join(\n ', ',\n)}. Map the user's wording to the closest value. If omitted, all categories are returned.`;\n\n/**\n * Tool schema for fetching traffic incident details.\n */\nexport const getTrafficIncidentsSchema = z.object({\n where: trafficIncidentsWhereSchema\n .optional()\n .describe('Geographic scope. Default: `{ mode: \"within\", viewport: true }`.'),\n categoryFilter: z\n .array(z.enum([...trafficIncidentRequestCategories]))\n .optional()\n .describe(categoryFilterDescription),\n timeValidityFilter: z\n .array(z.enum(['present', 'future']))\n .optional()\n .describe(\n \"Which incidents to fetch by time-validity. Default ['present'] — incidents active RIGHT NOW. \" +\n \"Only pass 'future' when the user EXPLICITLY asks about upcoming, \" +\n 'planned, or future incidents.',\n ),\n label: z\n .string()\n .optional()\n .describe(\n 'Short human-readable label for this fetch — name the place (\"Central London\", ' +\n '\"Amsterdam ring\", \"M25 corridor\"), not coordinates. Surfaced verbatim in the ' +\n 'monitor chip and on the entry chip in the UI; also used by analyseData / ' +\n 'focusIncidents to disambiguate which entry to operate on. Always pass one when ' +\n 'show is true (the default); the bbox-coordinate fallback (\"bbox a,b → c,d\") is for ' +\n 'hidden (show: false) intermediate fetches only.',\n ),\n show: z\n .boolean()\n .optional()\n .describe(\n 'Default: true — incidents auto-render so the user can confirm the agent is looking at the right area. ' +\n 'Set false ONLY for an extra throwaway load the user should not see — e.g. a second area fetched ' +\n 'purely to compare counts. It is NOT needed to run clusterIncidents or analyseData: those read the ' +\n 'entry you already loaded, so never re-fetch an area (shown or not) just to analyse it.',\n ),\n fitBounds: z.boolean().optional().describe('Default: true. Pass false to skip camera move after rendering.'),\n monitor: z\n .boolean()\n .optional()\n .describe(\n 'Keep this entry live: poll its area in the background and re-run any registered analyses on each ' +\n 'tick. Defaults to true for a shown fetch — live incidents go stale otherwise. Pass false for a ' +\n 'one-shot snapshot (e.g. \"how many incidents right now?\"). Ignored when `show` is false (a hidden ' +\n 'throwaway load is never monitored).',\n ),\n monitorIntervalMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Polling interval in ms when monitoring. Default 60000 (60s).'),\n});\n\nexport const getTrafficIncidentsDescription =\n 'LOADER. Fetches present/future traffic incidents into a cached entry, renders them, and returns ' +\n '`{ count, entryId, entries }` (`entries` lists every cached entry as a follow-up target). No per-incident ' +\n 'detail or aggregation — those belong to analyseData/focusIncidents. ' +\n '\\n\\n' +\n 'AREA via `where` (within mode only): omit `where` → current viewport; named area → `where.queries`; stored ' +\n 'places → `where.placeIds` (a places ENTRY id like \"places-0\" or locatePlace\\'s `placesEntryId`, or a single ' +\n 'place id); isochrone → `where.range`; route corridor → `where.route`; raw bounds → ' +\n '`where.boundingBox`; custom shapes → `where.geometries`. Fields union into one bbox (≤10,000 km²). ' +\n '\\n\\n' +\n 'CALL ONCE PER AREA — each call writes a new entry, so re-calling burns steps; pick a follow-up instead: ' +\n 'analyseData (counts, top-N, charts, clusters, trend via `previous`) or focusIncidents (highlight a subset). ' +\n '\\n\\n' +\n 'STAYS LIVE BY DEFAULT: a shown fetch arms a background monitor (`monitor: true`) that keeps the entry current ' +\n 'and re-runs registered analyses each tick — no separate setTrafficIncidentsMonitor call needed (that is only ' +\n 'for an entry loaded on an EARLIER turn). Pass `monitor: false` for a one-shot snapshot. `show: false` ' +\n 'suppresses rendering (throwaway loads only; never monitored); `label` names the snapshot in the UI. ' +\n '\\n\\n' +\n 'CHECK WHERE IT RESOLVED: the result `resolvedAreas` reports the GROUNDED place each named query matched ' +\n '(e.g. \"east London\" → \"London, CA\"). If it resolved wrong, re-issue with a more specific query (append a region/country).';\n\ntype WithinWhere = z.infer<typeof withinTrafficIncidentsWhereSchema>;\n\ntype ResolvedScope = { bbox: BBox; routeLabel?: string; resolvedAreas?: ResolvedAreaDisclosure[] };\n\n// Resolves `where.range` to a list of bboxes (one per origin polygon). Multi-origin entries\n// combine their reachable polygons into the union area. `range` stays caller-side because it maps\n// to stored isochrone polygons, not a geocoded area — the downstream endpoint differs.\nconst resolveRangeToBBoxes = (state: ToolState, rangeId: string): { bboxes: BBox[] } | { error: string } => {\n const ranged = getRangePolygons(state, rangeId);\n if ('error' in ranged) return ranged;\n const bboxes = ranged.polygons.map((polygon) => bboxFromGeoJSON(polygon)).filter((bbox): bbox is BBox => !!bbox);\n return { bboxes };\n};\n\n// Resolve a `within` where to a single union bbox. `range` is resolved separately (it maps to\n// stored isochrone polygons, not a geocoded area). All other area branches delegate to the shared\n// resolveAreas, which fixes the zero-area-bbox bug on street-only geocode results.\nconst resolveWithinToBBox = async (\n where: WithinWhere,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<ResolvedScope | { error: string }> => {\n const bboxes: BBox[] = [];\n\n // `range` is resolved caller-side (stored isochrones, not a geocoded area) and unioned IN with\n // any other multi-region fields — the schema promises range \"composes with\" them.\n if (where.range !== undefined) {\n const ranged = resolveRangeToBBoxes(state, where.range);\n if ('error' in ranged) return ranged;\n bboxes.push(...ranged.bboxes);\n }\n\n // resolveWithin returns an empty area set (not an error) when nothing resolved, so a range-only\n // `where` composes fine here rather than tripping a \"no area\" guard.\n const areaWhere: AreaWhere = {\n viewport: where.viewport,\n boundingBox: where.boundingBox,\n queries: where.queries,\n placeIds: where.placeIds,\n geometries: where.geometries,\n route: where.route,\n };\n const within = await resolveWithin(areaWhere, toolStateToWhereContext(state));\n if (isResolveError(within)) return within;\n bboxes.push(...within.map((a) => a.bbox));\n\n // Surface where each named query actually resolved — the grounded match, independent of the\n // model's own `label`, so the agent can confirm the place (and correct a wrong same-name\n // resolution) rather than silently trust it.\n const resolvedAreas = within\n .filter((a) => a.source === 'query' && a.label)\n .map((a) => ({ matched: a.label as string, ...(a.query !== undefined && { query: a.query }) }));\n const routeLabel = within.find((a) => a.source === 'route')?.label;\n\n if (bboxes.length === 0) return { error: 'No area resolved from `where`.' };\n return {\n bbox: bboxFromBBoxes(bboxes) as BBox,\n routeLabel,\n ...(resolvedAreas.length > 0 && { resolvedAreas }),\n };\n};\n\n// Build a human-readable label from `where` when the caller didn't pass one. Head preference:\n// truthful resolved names (the grounded geocoded match) → route label / range id → bbox-coordinate\n// string (the last for hidden show:false intermediate fetches).\nconst defaultLabel = (\n params: TrafficIncidentDetailsByBBoxParams & { bbox: BBox },\n where: WithinWhere | undefined,\n routeLabel: string | undefined,\n resolvedAreas: ResolvedAreaDisclosure[] | undefined,\n): string => {\n const filterParts: string[] = [];\n if (params.categoryFilter?.length) filterParts.push(`filtered: ${params.categoryFilter.join(',')}`);\n if (params.timeValidityFilter?.length) filterParts.push(`when: ${params.timeValidityFilter.join(',')}`);\n\n // Prefer the grounded match (\"London, CA\") over the query echo (\"east London\") so the entry chip\n // never masks a wrong same-name resolution.\n let head: string | undefined = resolvedAreas?.length ? resolvedAreas.map((a) => a.matched).join(', ') : undefined;\n if (head === undefined && where && !where.viewport) {\n if (where.queries?.length) head = where.queries.map((q) => q.query).join(', ');\n else if (routeLabel) head = `along ${routeLabel}`;\n else if (where.range) head = where.range;\n }\n\n if (!head) {\n const [minLng, minLat, maxLng, maxLat] = params.bbox;\n head = `bbox ${minLng.toFixed(2)},${minLat.toFixed(2)} → ${maxLng.toFixed(2)},${maxLat.toFixed(2)}`;\n }\n\n return filterParts.length ? `${head} — ${filterParts.join('; ')}` : head;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeGetTrafficIncidents = async (\n params: z.infer<typeof getTrafficIncidentsSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof getTrafficIncidentsOutputSchema>> => {\n const {\n where,\n categoryFilter,\n timeValidityFilter,\n label,\n show = true,\n fitBounds = true,\n monitor,\n monitorIntervalMs,\n } = params;\n\n // Default to viewport when `where` is omitted — keeps the \"no scope = current viewport\"\n // contract the system prompt promises.\n const effectiveWhere: WithinWhere = where ?? { mode: 'within', viewport: true };\n\n const filters = {\n ...(categoryFilter && { categoryFilter }),\n ...(timeValidityFilter && { timeValidityFilter }),\n };\n\n try {\n const resolved = await resolveWithinToBBox(effectiveWhere, state, options);\n if ('error' in resolved) return resolved;\n const { bbox: effectiveBbox, routeLabel, resolvedAreas } = resolved;\n\n const requestParams = withAgentToolkitHeaders({\n ...filters,\n bbox: effectiveBbox,\n signal: options?.signal,\n });\n const result = await trafficIncidentDetails(requestParams);\n\n const entryParams: TrafficIncidentDetailsByBBoxParams & { bbox: BBox } = {\n bbox: effectiveBbox,\n ...filters,\n };\n const capturedAt = Date.now();\n const entryLabel = label ?? defaultLabel(entryParams, effectiveWhere, routeLabel, resolvedAreas);\n\n // We write an emtpy entry for a zero-incident result to subsequent tool calls can still analyze it as valid data\n // resolvedAreas will catch mis-resolved areas in case of a query vs matched mistmatch\n if (!result.features.length) {\n const emptyId = await state.trafficIncidents.addIncidentsEntry([], entryParams, entryLabel, capturedAt);\n return {\n count: 0,\n entryId: emptyId,\n entries: entriesSummary(state),\n ...(resolvedAreas && resolvedAreas.length > 0 && { resolvedAreas }),\n };\n }\n\n const entryId = await state.trafficIncidents.addIncidentsEntry(\n result.features,\n entryParams,\n entryLabel,\n capturedAt,\n );\n\n if (show) {\n await state.trafficIncidents.showEntry(entryId, fitBounds);\n }\n\n // Monitoring rides along with the fetch (default on for a shown entry) so the agent no longer\n // has to co-pick a separate setTrafficIncidentsMonitor — that pairing was mandatory and\n // easy to forget. A hidden (show:false) throwaway load is never monitored. `skipInitialTick`\n // avoids re-fetching the area we just loaded; the recurring interval keeps it fresh.\n const monitoring = show && (monitor ?? true);\n if (monitoring) {\n state.trafficIncidents.startMonitoring(\n entryId,\n { bbox: effectiveBbox, capturedAt, label: entryLabel },\n {\n // No signal here, unlike the turn-scoped fetch above: ticks run on the\n // monitor's interval after this turn ends, and a rejected tick is fatal\n // to the monitor (it clears the interval).\n fetchIncidents: async (b) => {\n const tickParams = withAgentToolkitHeaders({ ...filters, bbox: b });\n return (await trafficIncidentDetails(tickParams)).features;\n },\n },\n { intervalMs: monitorIntervalMs, skipInitialTick: true },\n );\n }\n\n return {\n count: result.features.length,\n entryId,\n entries: entriesSummary(state),\n monitoring,\n ...(resolvedAreas && resolvedAreas.length > 0 && { resolvedAreas }),\n };\n } catch (error) {\n return {\n error: `Failed to get traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type BBox, bboxFromBBoxes, getPosition, type Place } from '@tomtom-org/maps-sdk/core';\nimport type { GeoBias } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlace } from '../../utils';\nimport {\n isResolveError,\n locatePlace,\n placesEntryIdHintSchema,\n resolveNearby,\n resolveWithin,\n showEntryGeometries,\n shownSchema,\n showPlaceGeometriesSchema,\n showPlacesSchema,\n showResultsOnMap,\n whereSchema,\n} from '../shared';\nimport { toolStateToWhereContext } from '../shared/tool-state-where-context';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const locatePlaceSchema = z.object({\n query: z.string().describe('Location string to resolve for downstream routes or searches.'),\n waypointIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Route slot to stage [origin, ...stops, destination]. Set if this is an origin/stop/destination.'),\n queryAs: z\n .enum(['poi', 'place'])\n .describe(\n '\"poi\" for venues / landmarks / businesses; ' +\n '\"place\" for addresses, cities, neighborhoods, parks, postal codes, geographies.',\n ),\n where: whereSchema\n .optional()\n .describe(\n 'Optional geographic scope. OMIT to resolve a uniquely-named place anywhere (mode \"global\", ' +\n 'the default) — correct for landmarks, cities, and areas like \"Schiphol\" or \"Westminster\", ' +\n 'on-screen or not. Use \"nearby\" (a SOFT bias toward a point / viewport centre / query) only to ' +\n 'disambiguate same-named places. Use \"within\" only to HARD-restrict the result to a known area ' +\n '(boundingBox / query / placeId).',\n ),\n show: showPlacesSchema\n .optional()\n .describe('Map display actions after resolving. Skip for intermediate calculations.'),\n entryId: placesEntryIdHintSchema,\n geometry: showPlaceGeometriesSchema,\n});\n\nexport const locatePlaceOutputSchema = z.union([\n placeOutputSchema.extend({\n placesEntryId: z\n .string()\n .describe(\n 'Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData, ' +\n 'or as `placeIdOrEntryId` to setRoute / addWaypointsToRoute to route to this place.',\n ),\n waypointIndex: z.number().optional(),\n shown: shownSchema.optional(),\n geometryFetched: z\n .boolean()\n .optional()\n .describe('Whether a boundary polygon was fetched and cached on the entry (when `geometry` was set).'),\n geometryShown: z.boolean().optional().describe('Whether the fetched polygon was rendered on the map.'),\n }),\n toolErrorSchema,\n]);\n\nexport const locatePlaceDescription =\n 'Resolve ONE named location (landmark, venue, city, neighborhood, address, POI) to a normalized place summary. ' +\n 'Use this for any SINGLE named place — including requests for its boundary / polygon / outline / footprint (via `geometry`), ' +\n 'pin or zoom display (via `show`), or staging as a routing waypoint. ' +\n 'For multi-place or category searches, use discoverPlaces.';\n\ntype ResolveBiasResult = { bias?: GeoBias } | { error: string };\n\nconst resolveWithinBias = async (\n where: Extract<z.infer<typeof whereSchema>, { mode: 'within' }>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<ResolveBiasResult> => {\n // Delegate to the shared resolver. The within schema enforces EXACTLY ONE field, so at most one\n // area returns; bboxFromBBoxes is a no-op here but keeps the shape correct if the schema ever\n // relaxes. A `within.query` resolves as an AREA (never a POI), matching discoverPlaces'\n // `where.queries` — so `within.queryAs` is not consulted. An unresolvable area surfaces the\n // resolver's actionable error rather than silently widening to a global search.\n const within = await resolveWithin(\n {\n viewport: where.viewport,\n boundingBox: where.boundingBox,\n queries: where.query ? [{ query: where.query, queryAs: where.queryAs }] : undefined,\n placeIds: where.placeId ? [where.placeId] : undefined,\n },\n toolStateToWhereContext(state, options),\n );\n if (isResolveError(within)) return within;\n\n if (within.length === 0) return { bias: undefined };\n\n return { bias: { boundingBox: bboxFromBBoxes(within.map((a) => a.bbox)) as BBox } };\n};\n\nconst resolveNearbyBias = async (\n where: Extract<z.infer<typeof whereSchema>, { mode: 'nearby' }>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<ResolveBiasResult> => {\n // `position` is a literal point — nothing to resolve, so it never goes through resolveNearby.\n if (where.position) return { bias: { position: where.position } };\n const { position } = await resolveNearby(\n {\n viewport: where.viewport,\n queries: where.query ? [{ query: where.query, queryAs: where.queryAs }] : undefined,\n },\n toolStateToWhereContext(state, options),\n );\n return { bias: position ? { position } : undefined };\n};\n\nconst resolveLocateBias = async (\n where: z.infer<typeof whereSchema> | undefined,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<ResolveBiasResult> => {\n // No `where` (or explicit global) → resolve the name anywhere, no bias. This is the default for a\n // uniquely-named place; the model no longer has to fill a mandatory scope and reach for the viewport.\n if (!where || where.mode === 'global') return { bias: undefined };\n if (where.mode === 'within') return resolveWithinBias(where, state, options);\n return resolveNearbyBias(where, state, options);\n};\n\n// Defer hide + marker rendering to the shared helper; only the zoom step is\n// locate-specific (the place's own bbox / centroid carries more meaning for a\n// single named place than fitting to its point representation).\nconst showLocateResult = async (\n state: ToolState,\n result: Place,\n placesEntryId: string,\n show: z.infer<typeof showPlacesSchema>,\n): Promise<z.infer<typeof shownSchema>> => {\n const shown = await showResultsOnMap(state, [placesEntryId], { ...show, zoomMode: 'none' });\n if (show.zoomMode === 'auto') {\n if (result.bbox) {\n state.baseMap.mapLibreMap.fitBounds(result.bbox);\n } else {\n const pos = getPosition(result);\n if (pos) state.baseMap.mapLibreMap.flyTo({ center: pos as [number, number], zoom: 13 });\n }\n shown.zoomMode = true;\n }\n return shown;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeLocatePlace = async (\n params: z.infer<typeof locatePlaceSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof locatePlaceOutputSchema>> => {\n const { query, queryAs, waypointIndex, where, show, entryId, geometry } = params;\n\n try {\n const resolved = await resolveLocateBias(where, state, options);\n if ('error' in resolved) return resolved;\n const result = await locatePlace(query, queryAs, resolved.bias, options);\n\n if (!result) {\n return { error: `No result found for \"${query}\"` };\n }\n\n const placesEntryId = await state.places.addPlaceResult(result, makePlacesLabel(result, { query }), entryId);\n\n if (waypointIndex !== undefined) {\n state.routing.setWaypointAt(waypointIndex, result);\n }\n\n const shown = show ? await showLocateResult(state, result, placesEntryId, show) : undefined;\n\n let geometryFetched: boolean | undefined;\n let geometryShown: boolean | undefined;\n if (geometry) {\n const geometryResult = await showEntryGeometries(state, placesEntryId, geometry, options);\n geometryFetched = geometryResult.fetched > 0;\n if (geometryResult.shown) geometryShown = true;\n }\n\n return {\n ...summarizePlace(result),\n placesEntryId,\n ...(waypointIndex !== undefined && { waypointIndex }),\n ...(shown && { shown }),\n ...(geometryFetched !== undefined && { geometryFetched }),\n ...(geometryShown !== undefined && { geometryShown }),\n };\n } catch (error) {\n return {\n error: `Location resolution failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { hidePreviousEntriesSchema } from '../shared';\nimport { routesWriteOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { calculateAndAddRoute, resolveRouteWaypoints } from './set-route';\n\n/** Output schema for the remove-waypoints-from-route tool. */\nexport const removeWaypointsFromRouteOutputSchema = z.union([routesWriteOutputSchema, toolErrorSchema]);\n\n/**\n * Tool schema for removing waypoints from an existing route.\n */\nexport const removeWaypointsFromRouteSchema = z.object({\n waypointIndices: z\n .array(z.number().int())\n .min(1)\n .describe(\n 'Indices of waypoints to remove. 0=origin, last=destination. ' +\n 'Min 2 waypoints must remain. Duplicates are not allowed.',\n ),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n});\n\nexport const removeWaypointsFromRouteDescription =\n 'Remove one or more waypoints from the current route by index; always recalculates. ' +\n 'Index 0 = origin, last = destination (both protected when removing them would leave fewer than 2). ' +\n 'Requires an existing route.';\n\nconst validateRemoveWaypoints = <T>(\n waypoints: T[] | null | undefined,\n waypointIndices: readonly number[],\n): string | T[] => {\n if (!waypoints || waypoints.length < 2) {\n return 'No existing waypoints available. Use calculate-route first to create a route with waypoints.';\n }\n const unique = new Set(waypointIndices);\n if (unique.size !== waypointIndices.length) {\n return 'Duplicate waypoint indices are not allowed.';\n }\n for (const idx of waypointIndices) {\n if (idx < 0 || idx >= waypoints.length) {\n return `Invalid waypoint index ${idx}. Valid range is 0 to ${waypoints.length - 1}.`;\n }\n }\n if (waypoints.length - waypointIndices.length < 2) {\n return 'Cannot remove waypoints: at least 2 waypoints (origin and destination) must remain.';\n }\n return waypoints.filter((_, i) => !unique.has(i));\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeRemoveWaypointsFromRoute = async (\n params: z.infer<typeof removeWaypointsFromRouteSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof removeWaypointsFromRouteOutputSchema>> => {\n const { waypointIndices, showOnMap, hidePreviousEntries } = params;\n try {\n const validated = validateRemoveWaypoints(state.routing.currentWaypoints, waypointIndices);\n if (typeof validated === 'string') return { error: validated };\n\n const waypoints = resolveRouteWaypoints(validated);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n return await calculateAndAddRoute(\n state,\n waypoints,\n showOnMap,\n hidePreviousEntries,\n undefined,\n undefined,\n undefined,\n options,\n );\n } catch (error) {\n return {\n error: `Failed to remove waypoints from route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { WaypointLike } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { hidePreviousEntriesSchema, labelForLocationInput, locationInputSchema } from '../shared';\nimport { routesWriteOutputSchema, toolErrorSchema } from '../shared-output-schemas';\nimport { resolveLocationInput } from './resolve-location-input';\nimport { calculateAndAddRoute, resolveRouteWaypoints } from './set-route';\n\n/** Output schema for the replace-waypoint-in-route tool. */\nexport const replaceWaypointInRouteOutputSchema = z.union([routesWriteOutputSchema, toolErrorSchema]);\n\nconst waypointTargetSchema = z\n .union([z.literal('origin'), z.literal('destination'), z.number().int()])\n .describe(\n \"Which waypoint to overwrite. Use 'origin' or 'destination' for the endpoints, \" +\n 'or a 0-based absolute index (0 = origin, last = destination, intermediate stops in between).',\n );\n\n/**\n * Tool schema for replacing a single waypoint in an existing route.\n */\nexport const replaceWaypointInRouteSchema = z.object({\n waypointIndex: waypointTargetSchema,\n location: locationInputSchema.describe('The new waypoint that replaces the targeted one.'),\n showOnMap: z.boolean().describe('Show the updated route and waypoints on the map after recalculation.'),\n hidePreviousEntries: hidePreviousEntriesSchema('routes'),\n});\n\nexport const replaceWaypointInRouteDescription =\n 'Replace a single waypoint in the current route in place — origin, destination, or any ' +\n 'intermediate stop by 0-based index. Always recalculates. Other waypoints are preserved. ' +\n 'Requires an existing route. Use addWaypointsToRoute to extend the route, removeWaypointsFromRoute ' +\n 'to drop a waypoint, or setRoute to fully replace all waypoints.';\n\nconst resolveTargetIndex = (target: z.infer<typeof waypointTargetSchema>, waypointsLen: number): number | string => {\n if (target === 'origin') return 0;\n if (target === 'destination') return waypointsLen - 1;\n if (target < 0 || target >= waypointsLen) {\n return `Invalid waypoint index ${target}. Valid range is 0 to ${waypointsLen - 1}.`;\n }\n return target;\n};\n\n/** Standalone execute for ToolEntry format. */\nexport const executeReplaceWaypointInRoute = async (\n params: z.infer<typeof replaceWaypointInRouteSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof replaceWaypointInRouteOutputSchema>> => {\n const { waypointIndex, location, showOnMap, hidePreviousEntries } = params;\n try {\n const current = state.routing.currentWaypoints;\n if (!current || current.length < 2) {\n return {\n error: 'No existing waypoints available. Use calculate-route first to create a route with waypoints.',\n };\n }\n\n const targetIndex = resolveTargetIndex(waypointIndex, current.length);\n if (typeof targetIndex === 'string') return { error: targetIndex };\n\n const resolved = await resolveLocationInput(location, state, options);\n if (!resolved) {\n return { error: `Could not resolve location: ${labelForLocationInput(location)}` };\n }\n\n const updated: WaypointLike[] = [...current];\n updated[targetIndex] = resolved.place;\n\n const waypoints = resolveRouteWaypoints(updated);\n if (!waypoints) {\n return { error: 'Not enough valid waypoints to calculate a route (minimum 2).' };\n }\n\n return await calculateAndAddRoute(\n state,\n waypoints,\n showOnMap,\n hidePreviousEntries,\n undefined,\n undefined,\n undefined,\n options,\n );\n } catch (error) {\n return {\n error: `Failed to replace waypoint in route: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { reverseGeocode } from '@tomtom-org/maps-sdk/services';\nimport type { Position } from 'geojson';\nimport { z } from 'zod';\nimport type { ToolExecuteOptions, ToolState } from '../../types';\nimport { makePlacesLabel, summarizePlace } from '../../utils';\nimport { positionSchema, withAgentToolkitHeaders } from '../shared';\nimport { placeOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the reverse-geocode tool. */\nexport const reverseGeocodeOutputSchema = z.union([\n placeOutputSchema.extend({\n placesEntryId: z\n .string()\n .describe(\n 'Places entry id the result was written to — pass as `placesEntryIDs` to analyseData / processData.',\n ),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for reverse geocoding (coordinates to address).\n */\nexport const reverseGeocodeSchema = z.object({\n position: positionSchema.describe('[longitude, latitude] GeoJSON position to reverse geocode'),\n});\n\nexport const reverseGeocodeDescription =\n 'Convert [lng, lat] to an address and place summary. Use after a click / getViewport / getCurrentLocation. ' +\n 'Coordinates only — for text queries use locatePlace or discoverPlaces.';\n\n/**\n * Create the reverse geocode tool.\n */\n/** Standalone execute for ToolEntry format. */\nexport const executeReverseGeocode = async (\n params: z.infer<typeof reverseGeocodeSchema>,\n state: ToolState,\n options?: ToolExecuteOptions,\n): Promise<z.infer<typeof reverseGeocodeOutputSchema>> => {\n const { position } = params;\n const pos: Position = position as Position;\n try {\n const requestParams = withAgentToolkitHeaders({\n position: pos,\n signal: options?.signal,\n });\n const result = await reverseGeocode(requestParams);\n\n // We guard for result.properties and not only result to account for phantom returns, which are succesfully-shaped Feature result with no data\n if (result.properties) {\n const placesEntryId = await state.places.addPlaceResult(result, makePlacesLabel(result));\n return { ...summarizePlace(result), placesEntryId };\n }\n\n return { error: 'No result found for the given coordinates' };\n } catch (error) {\n return {\n error: `Reverse geocoding failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Shape checks for GeoJSON arriving from outside the toolkit — model-authored sandbox output and\n * fetched BYOD payloads. Kept off `tools/shared/index.ts`, which the package root re-exports\n * wholesale.\n */\n\n/**\n * Whether the value is a `FeatureCollection` envelope. Checks the envelope only, with no per-feature\n * validation, so the caller picks the narrow its own path needs.\n *\n * @ignore\n */\nexport const isFeatureCollectionLike = <T>(value: unknown): value is T => {\n if (!value || typeof value !== 'object') return false;\n\n const candidate = value as { type?: unknown; features?: unknown };\n return candidate.type === 'FeatureCollection' && Array.isArray(candidate.features);\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { FeatureCollection } from 'geojson';\nimport { z } from 'zod';\nimport { toByodSafeProfile } from '../../state/byod';\nimport type { ToolState } from '../../types';\nimport { placesEntryIdHintSchema } from '../shared';\nimport { isFeatureCollectionLike } from '../shared/geojson-guards';\nimport { byodDataProfileSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const addByodSourceOutputSchema = z.union([\n z.object({\n byodEntryId: z\n .string()\n .describe('Id of the new BYOD entry (use with `byodEntryIDs` on analyseData / processData).'),\n label: z.string(),\n source: z\n .union([z.object({ kind: z.literal('url'), url: z.string() }), z.object({ kind: z.literal('inline') })])\n .describe('Where the data came from.'),\n profile: byodDataProfileSchema.describe(\n 'Runtime-inferred shape — geometry types plus a per-property profile (types, coverage, ' +\n 'numeric/boolean examples; string example values are withheld). Use it to pick fields for ' +\n 'analyseData / processData without re-fetching the raw GeoJSON, and to choose the ' +\n '`setByodLayers` layers that render this entry.',\n ),\n }),\n toolErrorSchema,\n]);\n\nexport const addByodSourceSchema = z\n .object({\n label: z\n .string()\n .describe('Human-readable label for the new entry (e.g. \"Sales territories\", \"Customer pins 2026-Q1\").'),\n url: z\n .url()\n .optional()\n .describe(\n 'URL serving a GeoJSON FeatureCollection. Fetched once and stored on the entry. ' +\n 'EXCLUSIVE with `data`.',\n ),\n data: z\n .unknown()\n .optional()\n .describe(\n 'Inline GeoJSON FeatureCollection (`{ type: \"FeatureCollection\", features: [...] }`). ' +\n 'EXCLUSIVE with `url`. Keep small — large inline payloads bloat the conversation.',\n ),\n entryId: placesEntryIdHintSchema,\n })\n .refine((v) => (v.url ? !v.data : !!v.data), {\n message: 'Pass EXACTLY ONE of `url` or `data`.',\n });\n\nexport const addByodSourceDescription =\n 'Register a customer-owned GeoJSON FeatureCollection as a new BYOD source from a URL (fetched once) or inline ' +\n 'GeoJSON. Returns the new entry id (usable with `byodEntryIDs` on `analyseData` / `processData` and with ' +\n '`updateByodDisplay`) plus a `profile` of the data shape (geometry types + per-property types/coverage/examples) ' +\n 'so you can reason about it without re-fetching. For very large datasets, prefer programmatic seeding via ' +\n '`state.byod.addEntry(...)`. ' +\n 'This tool ONLY ingests — the entry starts with NO layers and renders nothing (no automatic defaults). ALWAYS ' +\n 'follow it with `setByodLayers`, which picks layers + data-driven paint from the returned `profile` AND draws ' +\n 'the entry; until then nothing is shown. Choosing the layers is always your job, in every case. ' +\n 'The entry does not exist until this call RETURNS — even when you chose `entryId` yourself. So every tool that ' +\n 'reads the entry (`setByodLayers`, `analyseData` / `processData`, `updateByodDisplay`, …) belongs in a LATER ' +\n 'step: issuing one alongside this call fails with \"No BYOD entry with id …\".';\n\n// Bounded-fetch policy for BYOD URL inputs. The tool runs with whatever network reach the host\n// gives it (server-side: full reach; browser: subject to CORS). We don't try to enforce SSRF\n// guarantees that the host can't itself — instead we make accidental abuse expensive: only\n// http(s), short timeout, hard size cap.\nconst FETCH_TIMEOUT_MS = 15_000;\nconst MAX_RESPONSE_BYTES = 25 * 1024 * 1024; // 25 MB — comfortably above typical GeoJSON layers, refuses anything pathological.\nconst ALLOWED_PROTOCOLS = new Set(['http:', 'https:']);\n\nconst validateUrl = (raw: string): { url: URL } | { error: string } => {\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n return { error: `Invalid URL: \"${raw}\".` };\n }\n if (!ALLOWED_PROTOCOLS.has(parsed.protocol)) {\n return {\n error: `URL protocol \"${parsed.protocol}\" is not allowed. Only http: and https: are supported.`,\n };\n }\n return { url: parsed };\n};\n\n// Check the advertised Content-Length and bail before we read a single byte if the server\n// honestly admits the payload is too large. Servers that lie or omit the header fall through.\nconst checkAdvertisedSize = (response: Response, url: URL): { error: string } | undefined => {\n const contentLength = response.headers.get('content-length');\n if (!contentLength) return undefined;\n const advertised = Number.parseInt(contentLength, 10);\n if (Number.isFinite(advertised) && advertised > MAX_RESPONSE_BYTES) {\n return {\n error: `Response too large for \"${url}\": advertised ${advertised} bytes (max ${MAX_RESPONSE_BYTES}).`,\n };\n }\n return undefined;\n};\n\n// Streaming-cap body reader. Pulls chunks from the response and aborts as soon as the running\n// total crosses MAX_RESPONSE_BYTES, so we never page in a multi-GB payload. Falls back to a\n// single `text()` read with post-hoc size check when the runtime has no streaming reader.\nconst readBodyBounded = async (response: Response, url: URL): Promise<{ value: string } | { error: string }> => {\n const reader = response.body?.getReader();\n if (!reader) {\n const text = await response.text();\n if (text.length > MAX_RESPONSE_BYTES) {\n return { error: `Response too large for \"${url}\": ${text.length} bytes (max ${MAX_RESPONSE_BYTES}).` };\n }\n return { value: text };\n }\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_RESPONSE_BYTES) {\n await reader.cancel();\n return {\n error: `Response exceeded ${MAX_RESPONSE_BYTES} bytes while streaming \"${url}\". Refusing to load.`,\n };\n }\n chunks.push(value);\n }\n const merged = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n merged.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return { value: new TextDecoder().decode(merged) };\n};\n\n// Translate AbortController-triggered aborts into a friendlier timeout message; pass everything\n// else through verbatim. Pulled out so the main fetch flow doesn't carry the catch-block branch.\nconst wrapFetchError = (error: unknown, url: URL): { error: string } => {\n if (error instanceof Error && error.name === 'AbortError') {\n return { error: `Fetch timed out after ${FETCH_TIMEOUT_MS}ms for \"${url}\".` };\n }\n return { error: `Failed to fetch \"${url}\": ${error instanceof Error ? error.message : String(error)}` };\n};\n\n// Fetches a JSON resource with timeout + size cap. Returns either the parsed body or a structured\n// error. Body-streaming and size enforcement live in `readBodyBounded`.\nconst fetchJsonBounded = async (url: URL): Promise<{ value: unknown } | { error: string }> => {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const response = await fetch(url, { signal: controller.signal });\n if (!response.ok) {\n return { error: `Failed to fetch \"${url}\": HTTP ${response.status} ${response.statusText}` };\n }\n const sizeProblem = checkAdvertisedSize(response, url);\n if (sizeProblem) return sizeProblem;\n const body = await readBodyBounded(response, url);\n if ('error' in body) return body;\n return { value: JSON.parse(body.value) };\n } catch (error) {\n return wrapFetchError(error, url);\n } finally {\n clearTimeout(timer);\n }\n};\n\nexport const executeAddByodSource = async (\n params: z.infer<typeof addByodSourceSchema>,\n state: ToolState,\n): Promise<z.infer<typeof addByodSourceOutputSchema>> => {\n let data: unknown;\n let sourceRecord: { kind: 'url'; url: string } | { kind: 'inline' };\n\n if (params.url) {\n const urlCheck = validateUrl(params.url);\n if ('error' in urlCheck) return urlCheck;\n // App-supplied policy gate (e.g. host allowlist / metadata-endpoint block), if configured.\n // Runs after the built-in scheme check and before any network access.\n const verdict = await state.byod.sourceUrlValidator?.(urlCheck.url);\n if (verdict && !verdict.valid) return { error: verdict.reason };\n const fetched = await fetchJsonBounded(urlCheck.url);\n if ('error' in fetched) return fetched;\n data = fetched.value;\n sourceRecord = { kind: 'url', url: params.url };\n } else {\n data = params.data;\n sourceRecord = { kind: 'inline' };\n }\n\n if (!isFeatureCollectionLike<FeatureCollection>(data)) {\n return {\n error: 'Payload is not a GeoJSON FeatureCollection (`{ type: \"FeatureCollection\", features: [...] }`).',\n };\n }\n\n const byodEntryId = await state.byod.addEntry(data, params.label, {\n source: sourceRecord,\n ...(params.entryId && { explicitId: params.entryId }),\n });\n\n // The slice derives and stores the data profile at addEntry time — read it back rather than\n // recomputing. The entry is always present immediately after a successful addEntry.\n const entry = state.byod.findById(byodEntryId);\n if (!entry) {\n return { error: `addByodSource stored entry \"${byodEntryId}\" but could not read it back.` };\n }\n\n // Ingest-only: the entry has no layers yet, so there is nothing to render. The agent makes it\n // visible by following up with `setByodLayers` (which sets explicit layers + renders).\n return {\n byodEntryId,\n label: params.label,\n source: sourceRecord,\n // Strip customer-supplied string example values before they reach the model (the full\n // profile stays on `entry.profile` for the host to render). See `toByodSafeProfile`.\n profile: toByodSafeProfile(entry.profile),\n };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * The shared sandbox-replay step: run sandbox `code` over already-prepared inputs in the env-resolved\n * executor with the recurring extras (`previous` / `now` / `log`), and validate the result. This is the\n * one sandbox run sequence behind BOTH recurring paths — the {@link JobEngine} jobs that back\n * `analyseData` / trackers (`tools/state/monitoring.ts`) and the incidents clustering re-run\n * (`clusters-runtime.ts`) — so the pack → run → validate path lives in exactly one place. Callers own\n * input prep and any source-unchanged skip (they differ per caller); this owns only the realm-crossing run.\n */\n\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport type { ToolState } from '../../types';\nimport { MULTI_INPUT_SANDBOX_PARAMS, type PreparedInputs, packSandboxArgs } from './multi-input';\nimport { type AnalysisOutputFormat, runSandboxedFn, validateAnalysisResult } from './sandbox-code';\n\n/** Injected sandbox params for a recurring run: the merged inputs plus `previous` / `now` / `log`. */\nexport const STANDING_SANDBOX_PARAMS = [...MULTI_INPUT_SANDBOX_PARAMS, 'previous', 'now', 'log'] as const;\n\n/**\n * Run already-prepared sandbox `code` with the standing extras, then validate. Threads `previous` (the\n * prior result) and `now` (`new Date(sampledAt)`) into the body and runs in the env-resolved executor\n * (`state.codeExecution` — iframe-worker in the browser, main thread in Node/SSR; `createMapAgent` may\n * have overridden it, and it's in scope here because this is the tool layer). Returns `{ value }` or\n * `{ error }` and never throws.\n *\n * @ignore\n */\nexport const runPreparedSandbox = async (\n state: ToolState,\n prepared: PreparedInputs,\n args: { code: string; previous: unknown; sampledAt: number; outputFormat: AnalysisOutputFormat },\n): Promise<{ value: unknown } | { error: string }> => {\n const result = await runSandboxedFn(\n args.code,\n STANDING_SANDBOX_PARAMS,\n [...packSandboxArgs(prepared.sandbox, { h3, turf }), args.previous, new Date(args.sampledAt), () => {}],\n 'Analysis',\n state.codeExecution,\n );\n if ('error' in result) return result;\n return validateAnalysisResult(result.value, args.outputFormat);\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Job builders — the tools-layer half of monitoring. They construct the `run` closure for a standing\n * computation (prepare the multi-kind inputs → run sandbox `code` → write the result to its sink) and\n * register it as a job on {@link JobEngine}. They live here, not in the state layer, because building\n * `run` needs `prepareMultiInputs` + `runPreparedSandbox` (tools-layer). The engine never sees any of\n * that — it only ever calls the opaque `run`.\n *\n * Two sinks:\n * - {@link registerAnalysisJob}: an `analyseData` analysis. `run` re-runs the code and `attachResult`s to\n * {@link Analyses}. Registered active when monitored, paused otherwise (a one-shot keeps its recipe so\n * {@link setAnalysisMonitored} can promote it without re-declaring the code).\n * - {@link registerTrackerJob}: a tracker rule. `run` re-runs the code and feeds the {@link Verdict} to\n * {@link EventsState.ingestVerdict}, which applies the rising-edge reducer. Always active.\n *\n * Incident clustering does NOT ride this — it is dedicated, typed `TrafficIncidentsState` (`clusters`)\n * re-run from `tools/state/clusters-runtime.ts`.\n */\n\nimport type { Job } from '../../engine';\nimport { isVerdict } from '../../state';\nimport type { ToolState } from '../../types';\nimport { type AnalysisOutputFormat, latestTimestamp, type MultiInputIds, prepareMultiInputs } from '../shared';\nimport { runPreparedSandbox } from '../shared/sandbox-replay';\n\n/**\n * Register (or re-arm) an `analyseData` analysis as a job. Drops stale history when the recipe changed\n * (so the new code never inherits the old `previous`) and preserves the prior monitoring state on re-arm.\n * The job is `active` when monitored; a one-shot is registered paused so it retains its recipe.\n *\n * @ignore\n */\nexport const registerAnalysisJob = (\n state: ToolState,\n args: {\n analysisId: string;\n name: string;\n description?: string;\n outputFormat: AnalysisOutputFormat;\n code: string;\n inputs: MultiInputIds;\n affectedEntryIds: readonly string[];\n monitor: boolean;\n },\n): void => {\n const registerRecord = () =>\n state.analyses.register({\n analysisId: args.analysisId,\n name: args.name,\n description: args.description,\n outputFormat: args.outputFormat,\n affectedEntryIds: args.affectedEntryIds,\n });\n\n const existing = state.analyses.job(args.analysisId);\n // Idempotent re-run with the same recipe: keep the existing job (and any in-flight sweep + history),\n // just refresh metadata and (re-)activate if monitoring. Avoids tearing a job down mid-sweep.\n if (existing && existing.code === args.code) {\n registerRecord();\n if (args.monitor) existing.setActive(true);\n return;\n }\n\n // First arm, or the recipe changed: drop stale history (the new code must not inherit the old\n // `previous`), retire the old job, build a fresh one.\n if (existing) state.analyses.resetHistory(args.analysisId);\n const active = args.monitor || (existing?.active ?? false);\n existing?.unregister();\n registerRecord();\n\n // `job` is captured so `run` can confirm it is STILL the analysis's registered job before attaching:\n // a re-arm with changed code retires this job and registers a new one, and an in-flight sweep of the\n // old job must not write a stale-code result into the new recipe's freshly-reset history.\n let job: Job;\n // Re-run against fresh state. Re-reads the record (it may have been GC'd mid-sweep), re-prepares the\n // merged inputs, runs the stored code, attaches the result. Bails on any soft failure — a vanished\n // source entry, a sandbox throw, or an invalid result must never break the change that triggered it.\n const run = async (): Promise<void> => {\n if (!state.analyses.get(args.analysisId)) return;\n const prepared = await prepareMultiInputs(args.inputs, state);\n if ('error' in prepared) return;\n const sampledAt = latestTimestamp(prepared.value.resolved) ?? Date.now();\n const result = await runPreparedSandbox(state, prepared.value, {\n code: args.code,\n previous: state.analyses.lastResult(args.analysisId),\n sampledAt,\n outputFormat: args.outputFormat,\n });\n // Superseded by a re-arm while we awaited the sandbox? Drop this stale result.\n if ('error' in result || state.analyses.job(args.analysisId) !== job) return;\n state.analyses.attachResult(args.analysisId, {\n name: args.name,\n ...(args.description && { description: args.description }),\n outputFormat: args.outputFormat,\n data: result.value,\n timestamp: sampledAt,\n });\n };\n\n job = state.engine.register({\n affectedEntryIds: args.affectedEntryIds,\n code: args.code,\n active,\n run,\n onOrphaned: () => state.analyses.remove(args.analysisId),\n });\n state.analyses.setJob(args.analysisId, job);\n};\n\n/**\n * Toggle monitoring on an existing analysis by activating/pausing its job. Returns false when the id is\n * unknown (no job). Activating alone never replays — the next genuine source change re-runs it.\n *\n * @ignore\n */\nexport const setAnalysisMonitored = (state: ToolState, analysisId: string, monitor: boolean): boolean => {\n const job = state.analyses.job(analysisId);\n if (!job) return false;\n job.setActive(monitor);\n return true;\n};\n\n/**\n * Register a tracker rule as a job. `run` re-runs the rule and feeds each fresh {@link Verdict} to the\n * rising-edge reducer. Always active (a paused tracker pauses its job via {@link EventsState.setEnabled}).\n *\n * @ignore\n */\nexport const registerTrackerJob = (\n state: ToolState,\n args: { trackerId: string; code: string; inputs: MultiInputIds; affectedEntryIds: readonly string[] },\n): void => {\n const run = async (): Promise<void> => {\n const prepared = await prepareMultiInputs(args.inputs, state);\n if ('error' in prepared) return;\n const sampledAt = latestTimestamp(prepared.value.resolved) ?? Date.now();\n // Tracker rules read the current snapshot only (no `previous`), matching their arm-time dry-run.\n const result = await runPreparedSandbox(state, prepared.value, {\n code: args.code,\n previous: undefined,\n sampledAt,\n outputFormat: 'json',\n });\n if ('error' in result || !isVerdict(result.value)) return;\n state.trackers.ingestVerdict(args.trackerId, result.value, sampledAt);\n };\n\n const job = state.engine.register({\n affectedEntryIds: args.affectedEntryIds,\n code: args.code,\n active: true,\n run,\n onOrphaned: () => state.trackers.unregister(args.trackerId),\n });\n state.trackers.setJob(args.trackerId, job);\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Sandbox-running analysis tool. Accepts any combination of places / routes /\n * incidents / customGeometries / trafficAreaAnalytics / byod entries and exposes\n * them to user-authored JS as per-entry records (`placesByEntry`, `routesByEntry`, …),\n * with `h3`, `turf` and `routeUtils` available. The result is stored in `state.analyses`,\n * keyed to every contributing entry. This is a one-shot analysis: it never renders. Every run\n * is also registered as a (disabled) standing analysis and returns an `analysisId`; pass that to\n * `monitorAnalysis` to make it recompute on every source-entry change (the recurring sandbox then\n * also sees `previous` / `now` / `log`).\n *\n * **Scope + enablement.** Shared scaffolding in `tools/shared/entry-kinds.ts` builds\n * the per-kind input fields, the \"at least one of …\" message, the per-kind sandbox /\n * schema docs, and the scope schema. This file only adds the tool-specific surface:\n * `name` / `description` / `outputFormat`, the code-doc body, the description preamble,\n * and the executor.\n */\n\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport { makeAnalysisId } from '../../state';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport {\n ALL_ENTRY_DATA_KINDS,\n ANALYSE_OUTPUT_FORMAT_DESCRIPTION,\n buildAnalyseReturnPrompt,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxToolsDoc,\n type EntryKindScope,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n hasAnyEntryIds,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n prepareMultiInputs,\n resolveActiveKinds,\n runSandboxedFn,\n skippedSourceSchema,\n validateAnalysisResult,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { registerAnalysisJob } from './monitoring';\n\n/**\n * Per-turn scope kinds that `analyseData` understands. Identical to {@link EntryDataKind}\n * — every kind that's an analyseData input also accepts a scoped narrow.\n *\n * @group Agent Toolkit\n */\nexport type AnalyseDataKind = EntryDataKind;\n\n/**\n * Per-turn scope for `analyseData`. Emitted by the classifier in\n * `toolScopes.analyseData` when only a subset of input kinds is relevant.\n *\n * Object-shaped so future per-tool scope additions (e.g. flags) don't break\n * the wire format.\n *\n * @group Agent Toolkit\n */\nexport type AnalyseDataScope = EntryKindScope;\n\n/**\n * Default scope schema for analyseData — accepts every {@link EntryDataKind}. When\n * the agent narrows the enabled kinds via `createMapAgent`'s `dataEntries`, the\n * builder produces a narrower schema in {@link buildAnalyseDataEntry}.\n *\n * Exported for backward compat with consumers that imported the static schema.\n */\nexport const analyseDataScopeSchema = buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS);\n\n/** Output schema for analyse-data. */\nexport const analyseDataOutputSchema = z.union([\n z.object({\n affectedEntries: z\n .array(\n z.union([\n z.object({ kind: z.literal('places'), id: z.string() }),\n z.object({ kind: z.literal('routes'), id: z.string() }),\n z.object({ kind: z.literal('incidents'), id: z.string() }),\n z.object({ kind: z.literal('customGeometries'), id: z.string() }),\n z.object({ kind: z.literal('trafficAreaAnalytics'), id: z.string() }),\n z.object({ kind: z.literal('byod'), id: z.string() }),\n ]),\n )\n .describe(\n 'Entries the analysis was attached to: every contributing places / routes / incidents / ' +\n 'customGeometries / trafficAreaAnalytics / byod entry. Ranges entries feed the aggregation but do not carry analyses.',\n ),\n sourceIds: z\n .array(z.unknown())\n .optional()\n .describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only present when \\`geometriesEntryIDs\\` was set).`),\n analysisId: z\n .string()\n .describe(\n 'Stable handle for this analysis. Pass to `monitorAnalysis` to keep it recomputing whenever ' +\n 'its source entries change.',\n ),\n name: z.string().describe('Unique name of the analysis within each affected entry.'),\n description: z.string().optional(),\n outputFormat: z.enum(['json', 'chart']),\n monitoring: z\n .boolean()\n .optional()\n .describe('True when `monitor: true` was set — the analysis now recomputes on every source change.'),\n analysis: z.unknown(),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\n }),\n toolErrorSchema,\n]);\n\n/** Build the scope-aware `code` field description for analyse-data. */\nexport const buildAnalyseDataCodeDoc = (\n enabled: readonly EntryDataKind[],\n scope: AnalyseDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const sandboxList = buildEntryKindSandboxDocs(active);\n // Deep per-kind schema docs are emitted for every active kind, scoped or not for cross-kind\n const schemaBlocks = buildEntryKindSchemaDocs(active);\n // Geometries provenance doc is emitted whenever customGeometries is an active kind, scoped or\n // not, so it survives cross-kind turns.\n const showGeometriesProps = active.includes('customGeometries');\n return (\n 'Async JS that aggregates the injected inputs and returns the result.\\n\\n' +\n buildSandboxToolsDoc(active, true) +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading ' +\n '(`if (places) ...`, `routes?.features.length`, etc.). When defined:\\n' +\n `${sandboxList}\\n\\n` +\n // The cross-kind ops cheat-sheet is emitted inside `buildSandboxToolsDoc`, gated on several\n // active kinds; `monitorAnalysis` drives recurrence for every kind.\n `${buildAnalyseReturnPrompt('counts, per-kind breakdowns, hex bins, intersections, distance bins')}\\n\\n` +\n (showGeometriesProps ? `${GEOMETRIES_PROPS_DOC}\\n\\n` : '') +\n schemaBlocks\n );\n};\n\n/** Build the scope-aware analyseData tool input schema. */\nexport const buildAnalyseDataSchema = (enabled: readonly EntryDataKind[], scope: AnalyseDataScope | undefined) => {\n const active = resolveActiveKinds(enabled, scope);\n const requiredHint = `At least one of ${buildEntryFieldList(active)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(active, { verb: 'analyse', requiredHint }),\n };\n\n shape.name = z\n .string()\n .describe(\n 'Unique name within each affected entry (e.g. \"incidents-per-cluster\", \"ev-near-route\"). ' +\n 'Reusing a name replaces the previous analysis on those entries.',\n );\n shape.description = z.string().optional().describe('Optional short description of what the analysis computes.');\n shape.outputFormat = z.enum(['json', 'chart']).optional().describe(ANALYSE_OUTPUT_FORMAT_DESCRIPTION);\n shape.code = z.string().describe(buildAnalyseDataCodeDoc(enabled, scope));\n shape.monitor = z\n .boolean()\n .optional()\n .describe(\n 'When true, keep this analysis live — it recomputes on every change to its source entries (a ' +\n 'monitored incidents poll, a re-search, …), no separate monitorAnalysis call needed. Omit (or false) ' +\n 'for a one-shot run; you can still toggle it later via monitorAnalysis using the returned `analysisId`.',\n );\n\n const baseObject = z.object(shape);\n\n return baseObject.refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(active) });\n};\n\n/** Default full-surface input schema for analyse-data — every kind enabled, no per-turn scope. */\nexport const analyseDataSchema = buildAnalyseDataSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\n/** Build the scope-aware analyseData tool description. */\nexport const buildAnalyseDataDescription = (\n enabled: readonly EntryDataKind[],\n scope: AnalyseDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const kindList = active.join(' / ');\n const contrast =\n 'CONTRAST `processData`, which writes NEW renderable map entries (places / custom-geometries / BYOD); ' +\n '`analyseData` never renders or creates entries — only attaches metadata. Returns an `analysisId`; pass it ' +\n 'to `monitorAnalysis` to keep the analysis recomputing whenever its source entries change.';\n if (scope) {\n return (\n `Aggregate ${kindList} entries via dynamic JS — counts, groupings, charts, hex bins, ` +\n `cross-kind correlations. ${contrast} Each scoped input is exposed as a merged collection and a ` +\n 'per-entry record; fields are `undefined` when their `*EntryIDs` argument is omitted (guard with `?.`). ' +\n 'Result is attached as `_analysis[name]` on every contributing entry. ' +\n '`outputFormat: \"json\"` (default) or `\"chart\"` (Chart.js config).'\n );\n }\n return (\n `Aggregate ${kindList} entries via dynamic JS — counts, groupings, charts, hex bins, cross-kind correlations. ` +\n `${contrast} ` +\n 'Each input is exposed as a merged collection and a per-entry record; fields are `undefined` when their ' +\n '`*EntryIDs` argument is omitted (guard with `?.` / `if (places) …`). ' +\n 'Result is attached as `_analysis[name]` on every contributing entry (ranges feed in but do not carry ' +\n 'analyses). `outputFormat: \"json\"` (default) or `\"chart\"` (Chart.js config).'\n );\n};\n\n/** Default full-surface description — every kind enabled. */\nexport const analyseDataDescription = buildAnalyseDataDescription(ALL_ENTRY_DATA_KINDS, undefined);\n\n// Build the classifier hint dynamically so disabled kinds don't appear as scope choices.\nconst buildAnalyseDataScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((k) => `\"${k}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` ` +\n 'listing only the entry kinds the user query touches. Omit when truly cross-kind (covers all).'\n );\n};\n\ntype AnalyseDataInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n name: string;\n description?: string;\n code: string;\n outputFormat?: 'json' | 'chart';\n monitor?: boolean;\n};\n\nexport const executeAnalyseData = async (\n params: AnalyseDataInput,\n state: ToolState,\n): Promise<z.infer<typeof analyseDataOutputSchema>> => {\n const {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n name,\n description,\n code,\n } = params;\n const outputFormat = params.outputFormat ?? 'json';\n\n const prepared = await prepareMultiInputs(\n {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n state,\n );\n if ('error' in prepared) return { error: prepared.error };\n const { resolved, sandbox, geometries: geometriesMeta } = prepared.value;\n\n const sandboxResult = await runSandboxedFn(\n code,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs(sandbox, { h3, turf }),\n 'Analysis',\n state.codeExecution,\n );\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n\n const validated = validateAnalysisResult(sandboxResult.value, outputFormat);\n if ('error' in validated) return { error: validated.error };\n const analysis = validated.value;\n const timestamp = Date.now();\n\n const affectedEntries: Array<{\n kind: 'places' | 'routes' | 'incidents' | 'customGeometries' | 'trafficAreaAnalytics' | 'byod';\n id: string;\n }> = [\n ...resolved.places.map((e) => ({ kind: 'places' as const, id: e.id })),\n ...resolved.routes.map((e) => ({ kind: 'routes' as const, id: e.id })),\n ...resolved.incidents.map((e) => ({ kind: 'incidents' as const, id: e.id })),\n ...resolved.trafficAreaAnalytics.map((e) => ({ kind: 'trafficAreaAnalytics' as const, id: e.id })),\n ...resolved.byod.map((e) => ({ kind: 'byod' as const, id: e.id })),\n ...geometriesMeta.affectedEntries\n .filter((e) => e.kind === 'places' && !resolved.places.some((p) => p.id === e.id))\n .map((e) => ({ kind: 'places' as const, id: e.id })),\n ...geometriesMeta.affectedEntries\n .filter((e) => e.kind === 'customGeometries')\n .map((e) => ({ kind: 'customGeometries' as const, id: e.id })),\n ];\n\n const sourceIds: GeometriesId[] | undefined = geometriesEntryIDs ? geometriesMeta.sourceIds : undefined;\n\n // Register this run as a standing analysis job (paused unless `monitor`) so `monitorAnalysis` can\n // later make it recurring without re-declaring the code, then attach the one-shot result (which backs\n // `previous`). The id is stable across an identical re-run. `affectedEntryIds` is the per-entry lookup\n // key for the result. Activating monitoring fires no `entries-change`, so it won't replay now; only\n // the next genuine change to a source entry does.\n const affectedEntryIds = affectedEntries.map((e) => e.id);\n const analysisId = makeAnalysisId(name, affectedEntryIds);\n const monitoring = params.monitor === true;\n registerAnalysisJob(state, {\n analysisId,\n name,\n description,\n outputFormat,\n code,\n inputs: {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n affectedEntryIds,\n monitor: monitoring,\n });\n state.analyses.attachResult(analysisId, { name, description, outputFormat, data: analysis, timestamp });\n\n return {\n affectedEntries,\n ...(sourceIds && { sourceIds }),\n analysisId,\n name,\n ...(description && { description }),\n outputFormat,\n monitoring,\n analysis,\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n };\n};\n\n/**\n * Scope-aware builder for the analyseData tool. Reads classifier-resolved `scope` AND the\n * agent's `enabledDataKinds` from {@link ToolBuildOptions} so disabled kinds disappear from\n * the description, schema, code-doc, classifier scope, and the monitor refine. Without\n * either, emits the full surface unchanged. Caller supplies static metadata (tags,\n * examples, related/depends, classificationPrompt) via the second positional arg so the\n * registry stays the single source of truth for those.\n */\nexport const buildAnalyseDataEntry = (\n options: ToolBuildOptions<AnalyseDataScope>,\n metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n },\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n return {\n description: buildAnalyseDataDescription(enabled, options.scope),\n inputSchema: buildAnalyseDataSchema(enabled, options.scope),\n outputSchema: analyseDataOutputSchema,\n execute: executeAnalyseData as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildAnalyseDataScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/**\n * Default builder for the `analyseData` tool. Reads classifier-resolved scope and\n * agent-level enabled kinds from {@link ToolBuildOptions} and produces a narrowed entry;\n * without either, emits the full surface unchanged. The registry supplies the rest of\n * the metadata.\n */\nexport const analyseDataBuilder =\n (metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n }): ToolEntryBuilder<ToolState, AnalyseDataScope> =>\n (options) =>\n buildAnalyseDataEntry(options, metadata);\n","/**\n * @module agent-toolkit-tools\n *\n * `clearTracker` — stop a tracker and tear down its rule job. Owns nothing else: the source entries are\n * the operator's (the tracker never owned them), so clearing only unregisters the tracker (which\n * unregisters its {@link JobEngine} job). Defaults to the sole tracker when only one exists.\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const clearTrackerSchema = z.object({\n trackerId: z.string().optional().describe('Which tracker to clear. Omit when only one tracker exists.'),\n});\n\nexport const clearTrackerOutputSchema = z.union([\n z.object({ cleared: z.literal(true), trackerId: z.string(), name: z.string() }),\n toolErrorSchema,\n]);\n\nexport const clearTrackerDescription =\n 'Stop a tracker and remove its rule. Does not touch the watched entries (they were never the ' +\n \"tracker's). Omit `trackerId` when there is only one tracker.\";\n\nexport const executeClearTracker = async (\n params: { trackerId?: string },\n state: ToolState,\n): Promise<z.infer<typeof clearTrackerOutputSchema>> => {\n const all = state.trackers.trackers;\n if (all.length === 0) return { error: 'No trackers to clear.' };\n\n const trackerId = params.trackerId ?? (all.length === 1 ? all[0].id : undefined);\n if (trackerId === undefined) {\n return { error: `Which tracker? Pass trackerId — one of: ${all.map((t) => t.id).join(', ')}.` };\n }\n const tracker = state.trackers.get(trackerId);\n if (!tracker) return { error: `No tracker \"${trackerId}\". Active: ${all.map((t) => t.id).join(', ') || 'none'}.` };\n\n state.trackers.unregister(trackerId);\n\n return { cleared: true, trackerId, name: tracker.name };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Runtime behind incident clustering — the compute + re-run for the `clusterIncidents` tool. Clustering\n * is NOT a generic analysis: its result is typed, dedicated `TrafficIncidentsState` state (`clusters`).\n * This module owns what the slice can't, and dispatches on the recipe kind:\n *\n * - `params` (the default): TRUSTED DBSCAN over the entry's own incidents — runs `runClustering` directly\n * on the main thread. No sandbox, no worker dependency. The common path is therefore robust regardless of\n * the browser sandbox build.\n * - `code` (opt-in dynamic): UNTRUSTED LLM-authored clustering — runs in the env-resolved sandbox executor\n * (where the `cluster()` primitive is injected) over the recipe's multi-inputs, then guarded to a\n * {@link SandboxClusteringOutput}. Only this path needs `self.cluster` bundled into the worker realm.\n *\n * Either way the result lands in the dedicated `clusters` store, and the re-run is wired to the incidents\n * slice's `entries-change` so clusters stay live across monitor ticks. The re-run is ASYNC to the tick —\n * the fresh result lands on the slice's `clusters-change` a step after `entries-change`.\n */\n\nimport { type ClusteringParams, isClusteringOutput, runClustering, type SandboxClusteringOutput } from '../../state';\nimport type { ToolState } from '../../types';\nimport { type MultiInputIds, prepareMultiInputs } from '../shared';\nimport { runPreparedSandbox } from '../shared/sandbox-replay';\n\n/**\n * How an entry's clusters are (re)computed. `params` is the trusted default (main-thread `runClustering`);\n * `code` is opt-in LLM-authored clustering run in the sandbox over `inputs`.\n */\nexport type ClusterRecipe =\n | { kind: 'params'; params: ClusteringParams }\n | { kind: 'code'; code: string; inputs: MultiInputIds };\n\n// Identity of a recipe — used to decide whether to thread the prior output as `previous` (same recipe) or\n// start a fresh trend window (changed recipe). Cheap structural compare; recipes are small + JSON-safe.\nconst sameRecipe = (a: unknown, b: ClusterRecipe): boolean => JSON.stringify(a) === JSON.stringify(b);\n\n// Compute a recipe against its source entry. `params` runs the trusted clusterer directly (no sandbox);\n// `code` runs in the sandbox with the injected `cluster()` primitive and is guarded to a ClusteringOutput.\n// Returns the typed output or an error string (never throws — a bad run can't break the tick that triggered it).\nconst execClustering = async (\n state: ToolState,\n entryId: string,\n recipe: ClusterRecipe,\n previous: SandboxClusteringOutput | undefined,\n): Promise<{ value: SandboxClusteringOutput } | { error: string }> => {\n const entry = state.trafficIncidents.entries.find((e) => e.id === entryId);\n if (!entry) return { error: `unknown incidents entry \"${entryId}\"` };\n\n if (recipe.kind === 'params') {\n const { groups } = runClustering(\n entry.data,\n recipe.params,\n previous?.groups,\n entry.timestamp,\n previous?.sampledAt,\n );\n return { value: { groups, sampledAt: entry.timestamp } };\n }\n\n const prepared = await prepareMultiInputs(recipe.inputs, state);\n if ('error' in prepared) return prepared;\n const result = await runPreparedSandbox(state, prepared.value, {\n code: recipe.code,\n previous,\n sampledAt: entry.timestamp,\n outputFormat: 'json',\n });\n if ('error' in result) return result;\n // LLM-authored code can return anything; the typed store + cluster-pin UI require a real ClusteringOutput.\n if (!isClusteringOutput(result.value)) {\n return { error: 'clustering `code` must return a cluster(...) result (`{ groups }`)' };\n }\n return { value: result.value };\n};\n\n/**\n * Compute clustering for an entry and store it in the dedicated slice state. Arms the re-run wiring so the\n * clusters stay live across monitor ticks. A re-cluster with the SAME recipe threads the prior output as\n * `previous` (stable IDs + trends); a changed recipe starts fresh. Returns the typed output or an `error`\n * string (sandbox failure or a non-clustering `code` result) so the caller can surface why.\n *\n * @ignore\n */\nexport const runAndStoreClusters = async (\n state: ToolState,\n entryId: string,\n recipe: ClusterRecipe,\n): Promise<{ value: SandboxClusteringOutput } | { error: string }> => {\n ensureClusterRerunWired(state);\n const existing = state.trafficIncidents.getClustersRecord(entryId);\n const previous = existing && sameRecipe(existing.recipe, recipe) ? existing.output : undefined;\n const result = await execClustering(state, entryId, recipe, previous);\n if ('error' in result) return result;\n state.trafficIncidents.setClusters(entryId, { recipe, output: result.value });\n return result;\n};\n\n// Re-run scheduling flags per session, kept off the (pure-data) slice. `running` drops a re-entrant pass\n// (and a rare genuine change landing mid-run — self-correcting: the next change re-runs); `scheduled`\n// coalesces a synchronous burst of `entries-change` into one pass; `pending` accumulates the changed\n// entry ids across that burst so the pass re-runs exactly the entries whose incidents changed.\ntype RerunFlags = { running: boolean; scheduled: boolean; pending: Set<string> };\nconst rerunFlags = new WeakMap<ToolState, RerunFlags>();\nconst flagsFor = (state: ToolState): RerunFlags => {\n let flags = rerunFlags.get(state);\n if (!flags) {\n flags = { running: false, scheduled: false, pending: new Set() };\n rerunFlags.set(state, flags);\n }\n return flags;\n};\n\nconst wired = new WeakSet<ToolState>();\n\n/**\n * Subscribe the clustering re-run to the incidents slice's `entries-change`, exactly once per session.\n * Called from `clusterIncidents` (not the state factory) so the state layer never imports the tools layer.\n * Idempotent.\n *\n * @ignore\n */\nexport const ensureClusterRerunWired = (state: ToolState): void => {\n if (wired.has(state)) return;\n wired.add(state);\n state.trafficIncidents.events.on('entries-change', ({ changedIds }) => scheduleRerun(state, changedIds));\n};\n\nconst scheduleRerun = (state: ToolState, changedIds: readonly string[]): void => {\n const flags = flagsFor(state);\n for (const id of changedIds) flags.pending.add(id);\n if (flags.running || flags.scheduled) return;\n flags.scheduled = true;\n queueMicrotask(() => {\n flags.scheduled = false;\n void rerunClusters(state);\n });\n};\n\n// Re-run the stored clustering recipe of every entry whose incidents changed this burst, writing the\n// fresh output back into the slice. We re-run exactly the changed entries (matched by id) rather than\n// scanning every entry — an unrelated entry's change can't trigger a needless re-cluster. `setClusters`\n// emits `clusters-change` (not `entries-change`), so this never echoes into its own trigger. A recipe\n// that errors on a tick is skipped (the last good result stays).\nconst rerunClusters = async (state: ToolState): Promise<void> => {\n const flags = flagsFor(state);\n const changed = flags.pending;\n flags.pending = new Set();\n flags.running = true;\n try {\n for (const entryId of changed) {\n const record = state.trafficIncidents.getClustersRecord(entryId);\n if (!record) continue;\n const recipe = record.recipe as ClusterRecipe;\n const result = await execClustering(state, entryId, recipe, record.output);\n if ('value' in result) {\n state.trafficIncidents.setClusters(entryId, { recipe, output: result.value });\n }\n }\n } finally {\n flags.running = false;\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ClusteringParams } from '../../state';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport {\n ALL_ENTRY_DATA_KINDS,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxToolsDoc,\n type EntryKindScope,\n type GeometriesId,\n type MultiInputIds,\n resolveActiveKinds,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { type ClusterRecipe, runAndStoreClusters } from './clusters-runtime';\n\n// `incidents` is always the clustering source. The other kinds are optional CONTEXT the custom `code`\n// can filter against; they appear (as `*EntryIDs` fields + in the code docs) only when the per-turn\n// scope opts them in — default is incidents-only, matching analyseData's scope model.\nconst resolveClusterKinds = (\n enabled: readonly EntryDataKind[],\n scope: EntryKindScope | undefined,\n): { context: readonly EntryDataKind[]; active: readonly EntryDataKind[] } => {\n const context = scope ? resolveActiveKinds(enabled, scope).filter((kind) => kind !== 'incidents') : [];\n return { context, active: ['incidents', ...context] };\n};\n\n// Doc for the optional dynamic-clustering `code` body. Reuses the SAME sandbox-tools + per-kind schema\n// docs as analyseData/processData (so the model knows the real `incidents` shape — `properties.delayInSeconds`,\n// `properties.category`, … — instead of inventing fields), plus the rule that the result must come from\n// the injected `cluster()` primitive. `active` always includes `incidents`; widened by scope.\nconst buildClusterIncidentsCodeDoc = (active: readonly EntryDataKind[]): string =>\n 'OPTIONAL — omit for the default DBSCAN over all incidents (tune eps / minMembers / maxClusters). Async ' +\n 'JS body: filter/combine the inputs, then RETURN `cluster(...)` (the only way to build the required ' +\n '`{ groups }` result) — e.g. `return cluster(filtered, { eps: 0.4 }, previous, now);`.\\n\\n' +\n // `scoped: true` so the cluster() primitive doc + per-kind docs always render for this tool.\n buildSandboxToolsDoc(active, true) +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. When defined:\\n' +\n `${buildEntryKindSandboxDocs(active)}\\n\\n` +\n buildEntryKindSchemaDocs(active);\n\n/** Build the scope-aware clusterIncidents input schema. */\nexport const buildClusterIncidentsSchema = (enabled: readonly EntryDataKind[], scope: EntryKindScope | undefined) => {\n const { context, active } = resolveClusterKinds(enabled, scope);\n return z.object({\n incidentsEntryID: z.string().describe('Entry id from getTrafficIncidents — the incidents to cluster.'),\n eps: z\n .number()\n .positive()\n .optional()\n .describe(\n 'DBSCAN radius in km. Default 0.5. Use 0.3–0.5 for dense urban, 1+ for highways. Ignored when `code` is set.',\n ),\n minMembers: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe('Minimum incidents per cluster. Default 3. Ignored when `code` is set.'),\n maxClusters: z\n .number()\n .int()\n .min(1)\n .optional()\n .describe('Top N clusters by total delay. Default 6. Ignored when `code` is set.'),\n // Context inputs the custom `code` may read — only present for kinds the per-turn scope added.\n ...buildEntryIdFields(context, { verb: 'analyse', requiredHint: 'Only used by custom `code`.' }),\n code: z.string().optional().describe(buildClusterIncidentsCodeDoc(active)),\n });\n};\n\n/** Default full-surface schema (incidents-only, no per-turn scope). */\nexport const clusterIncidentsSchema = buildClusterIncidentsSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\nexport const clusterIncidentsOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string(),\n clusterCount: z.number(),\n clusters: z.array(\n z.object({\n id: z.string(),\n primaryRoads: z.array(z.string()),\n primaryCategory: z.string(),\n size: z.number(),\n totalDelaySeconds: z.number(),\n peakDelaySeconds: z.number(),\n trend: z.enum(['growing', 'fading', 'steady', 'new']),\n }),\n ),\n }),\n toolErrorSchema,\n]);\n\nconst CLUSTER_BASE_DESCRIPTION =\n 'DBSCAN clustering of traffic incidents into compact pockets — stable IDs (survive monitor ticks), ' +\n 'road/category labels, delay aggregates, multi-poll trends. Stored as dedicated clustering state on the ' +\n 'entry and re-run each monitor tick (stays live without re-calling); the UI pins each group. Returns ' +\n 'structured fields (roads, category, size, delay, trend) for the caller to phrase. One clustering per ' +\n 'entry (re-calling replaces it). Defaults to DBSCAN over all incidents (tune eps / minMembers / ' +\n 'maxClusters); pass `code` to filter/combine incidents dynamically first. Pair with getTrafficIncidents ' +\n '+ setTrafficIncidentsMonitor.';\n\n/** Build the scope-aware clusterIncidents description (notes any context kinds in scope). */\nexport const buildClusterIncidentsDescription = (active: readonly EntryDataKind[]): string => {\n const context = active.filter((kind) => kind !== 'incidents');\n return context.length\n ? `${CLUSTER_BASE_DESCRIPTION} Custom \\`code\\` can also filter incidents against ${context.join(' / ')}.`\n : CLUSTER_BASE_DESCRIPTION;\n};\n\n/** Default description (incidents-only). */\nexport const clusterIncidentsDescription = buildClusterIncidentsDescription(['incidents']);\n\n// Classifier hint: incidents is always the source; widen only when the query filters against another kind.\nconst buildClusterIncidentsScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((kind) => `\"${kind}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` — always include \"incidents\" (the clustering source). Add another kind ` +\n 'ONLY when the query filters incidents against it (e.g. \"near depots\" → places, \"along the route\" → routes, ' +\n '\"within this zone\" → customGeometries). Default: `{ kinds: [\"incidents\"] }`.'\n );\n};\n\ntype ClusterIncidentsInput = {\n incidentsEntryID: string;\n eps?: number;\n minMembers?: number;\n maxClusters?: number;\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n code?: string;\n};\n\nexport const executeClusterIncidents = async (\n params: ClusterIncidentsInput,\n state: ToolState,\n): Promise<z.infer<typeof clusterIncidentsOutputSchema>> => {\n const { incidentsEntryID, eps, minMembers, maxClusters, code } = params;\n const entry = state.trafficIncidents.entries.find((e) => e.id === incidentsEntryID);\n if (!entry) {\n return { error: `No incidents entry with id \"${incidentsEntryID}\". Call getTrafficIncidents first.` };\n }\n\n const clusterParams: ClusteringParams = {\n ...(eps != null && { eps }),\n ...(minMembers != null && { minMembers }),\n ...(maxClusters != null && { maxClusters }),\n };\n\n // Default (no `code`): trusted DBSCAN on the main thread — no sandbox, no worker dependency. Custom\n // `code`: LLM-authored, run in the sandbox over the entry's incidents plus any scoped context inputs\n // (`inputs` is cast to MultiInputIds — `prepareMultiInputs` resolves each kind by id).\n const recipe: ClusterRecipe = code\n ? {\n kind: 'code',\n code,\n inputs: {\n incidentsEntryIDs: [incidentsEntryID],\n ...(params.placesEntryIDs?.length && { placesEntryIDs: params.placesEntryIDs }),\n ...(params.routesEntryIDs?.length && { routesEntryIDs: params.routesEntryIDs }),\n ...(params.geometriesEntryIDs?.length && { geometriesEntryIDs: params.geometriesEntryIDs }),\n ...(params.trafficAreaAnalyticsEntryIDs?.length && {\n trafficAreaAnalyticsEntryIDs: params.trafficAreaAnalyticsEntryIDs,\n }),\n ...(params.byodEntryIDs?.length && { byodEntryIDs: params.byodEntryIDs }),\n } as MultiInputIds,\n }\n : { kind: 'params', params: clusterParams };\n\n // Store the typed result as dedicated clustering state on the entry. The monitor-tick re-run keeps it\n // live, threading the prior output for stable IDs + trend continuity (same recipe threads, a changed\n // one starts fresh). On failure the runtime returns the reason so the agent can correct.\n const result = await runAndStoreClusters(state, entry.id, recipe);\n if ('error' in result) {\n return { error: `Clustering failed for entry \"${entry.id}\": ${result.error}` };\n }\n const out = result.value;\n\n // Omit memberIds: clusters are UI-actionable (pins), not a chat-selection path.\n // The caller composes its read from roads, category, and the delay/trend numbers.\n return {\n incidentsEntryID: entry.id,\n clusterCount: out.groups.length,\n clusters: out.groups.map((cluster) => ({\n id: cluster.id,\n primaryRoads: cluster.primaryRoads,\n primaryCategory: cluster.primaryCategory,\n size: cluster.size,\n totalDelaySeconds: cluster.totalDelaySeconds,\n peakDelaySeconds: cluster.peakDelaySeconds,\n trend: cluster.trend,\n })),\n };\n};\n\ntype ClusterIncidentsMetadata = {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n};\n\n/**\n * Scope-aware builder for the `clusterIncidents` tool. Reads agent-enabled kinds + the classifier's\n * per-turn scope from {@link ToolBuildOptions}; the schema/docs default to incidents-only and widen to\n * include context kinds (places / routes / …) only when the query filters against them.\n */\nexport const buildClusterIncidentsEntry = (\n options: ToolBuildOptions<EntryKindScope>,\n metadata: ClusterIncidentsMetadata,\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n const { active } = resolveClusterKinds(enabled, options.scope);\n return {\n description: buildClusterIncidentsDescription(active),\n inputSchema: buildClusterIncidentsSchema(enabled, options.scope),\n outputSchema: clusterIncidentsOutputSchema,\n execute: executeClusterIncidents as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildClusterIncidentsScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/** Default builder for the `clusterIncidents` tool; the registry supplies the metadata. */\nexport const clusterIncidentsBuilder =\n (metadata: ClusterIncidentsMetadata): ToolEntryBuilder<ToolState, EntryKindScope> =>\n (options) =>\n buildClusterIncidentsEntry(options, metadata);\n","/**\n * @module agent-toolkit-tools\n *\n * `createTracker` — arm a generic tracker. A tracker is one rule: sandbox `code` that reads the same\n * multi-kind inputs as `analyseData` and returns a {@link Verdict} (`{ active, members, summary }`). The\n * tool dry-runs the code once as a gate, registers the tracker with its rising-edge bit clear, registers\n * its rule as a {@link JobEngine} job (so the verdict recomputes whenever its source entries change),\n * then feeds that first verdict through the reducer — so a condition already true at arm time crosses\n * false→true and fires one `opened` alert. From then on {@link EventsState} logs an `opened` alert /\n * `resolved` event as the verdict crosses edges.\n *\n * Mirrors `analyseData`'s input surface (`*EntryIDs` per kind) so a rule can read multiple entry kinds —\n * e.g. \"an incident within 100m of a hospital\" is one rule over a places + an incidents entry.\n */\n\nimport * as turf from '@turf/turf';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport { isVerdict, type Tracker } from '../../state';\nimport type { GeometriesId, ToolState } from '../../types';\nimport {\n ALL_ENTRY_DATA_KINDS,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildSandboxToolsDoc,\n hasAnyEntryIds,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n prepareMultiInputs,\n runSandboxedFn,\n} from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { registerTrackerJob } from './monitoring';\n\n/** The `code` contract: read the injected inputs, return a {@link Verdict}. */\nconst buildVerdictCodeDoc = (): string =>\n 'JS that inspects the injected inputs and returns a VERDICT describing whether the watched condition ' +\n 'holds RIGHT NOW. Pure + synchronous — no fetch/network/await.\\n\\n' +\n buildSandboxToolsDoc(ALL_ENTRY_DATA_KINDS, false) +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. When defined:\\n' +\n `${buildEntryKindSandboxDocs(ALL_ENTRY_DATA_KINDS)}\\n\\n` +\n 'Return exactly:\\n' +\n '`{ active: boolean, members: { entryId: string, featureIds: string[] }[], summary: string }`\\n' +\n '- `active`: is the condition met now? (false→true logs an alert; true→false resolves it)\\n' +\n '- `members`: the features that matched, GROUPED BY their source entry id — include EVERY entity the ' +\n 'alert is about (e.g. the hospital AND the nearby incidents), each under its own entry id.\\n' +\n '- `summary`: a short grounded headline built from the data (e.g. \"Incident 80m from St Mary\\'s Hospital\").\\n\\n' +\n // Full per-kind field reference (places / routes / customGeometries). createTracker is always\n // full-surface (no classifier scope to narrow on), and tracker rules are the case that most needs\n // exact field names — cross-kind spatial joins read geometry + properties across kinds.\n buildEntryKindSchemaDocs(ALL_ENTRY_DATA_KINDS);\n\n/** createTracker input schema — full multi-kind input surface (same `*EntryIDs` fields as analyseData). */\nexport const createTrackerSchema = (() => {\n const requiredHint = `At least one of ${buildEntryFieldList(ALL_ENTRY_DATA_KINDS)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(ALL_ENTRY_DATA_KINDS, { verb: 'analyse', requiredHint }),\n };\n shape.name = z.string().describe('Short tracker label, e.g. \"Incidents near hospitals\".');\n shape.rule = z\n .string()\n .describe(\n 'The watched condition in plain language, e.g. \"an incident within 100m of a hospital\". For display.',\n );\n shape.code = z.string().describe(buildVerdictCodeDoc());\n return z.object(shape).refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(ALL_ENTRY_DATA_KINDS) });\n})();\n\n/** createTracker output schema. */\nexport const createTrackerOutputSchema = z.union([\n z.object({\n trackerId: z.string(),\n name: z.string(),\n rule: z.string(),\n watching: z.array(z.string()).describe('Source entry ids the rule reads.'),\n firingNow: z\n .boolean()\n .describe('Whether the condition holds at arm time. If true, one `opened` alert fires for it.'),\n summary: z.string().describe('The arm-time verdict summary.'),\n }),\n toolErrorSchema,\n]);\n\nexport const createTrackerDescription =\n 'Arm a tracker: watch loaded entries and alert when a condition crosses from false to true (and log ' +\n 'when it clears). You write `code` returning `{ active, members, summary }`; it recomputes automatically ' +\n 'whenever the watched entries change (a monitored incidents poll, a re-search, …). Reads the same ' +\n 'multi-kind inputs as `analyseData`, so one rule can span kinds — e.g. an incident near a hospital. ' +\n 'The `*EntryIDs` you pass MUST be ids of entries already loaded this session — load them FIRST ' +\n '(e.g. call getTrafficIncidents and pass its returned entry id) and never invent ids like \"incidents-0\". ' +\n 'CONTRAST `analyseData` (one-shot metadata, no alerting). Use `clearTracker` to stop, `getTrackerHistory` ' +\n 'to read what fired.';\n\ntype CreateTrackerInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n name: string;\n rule: string;\n code: string;\n};\n\nexport const executeCreateTracker = async (\n params: CreateTrackerInput,\n state: ToolState,\n): Promise<z.infer<typeof createTrackerOutputSchema>> => {\n const inputs = {\n placesEntryIDs: params.placesEntryIDs,\n routesEntryIDs: params.routesEntryIDs,\n incidentsEntryIDs: params.incidentsEntryIDs,\n geometriesEntryIDs: params.geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs: params.trafficAreaAnalyticsEntryIDs,\n byodEntryIDs: params.byodEntryIDs,\n };\n\n const prepared = await prepareMultiInputs(inputs, state);\n if ('error' in prepared) return { error: prepared.error };\n const { resolved, sandbox, geometries: geometriesMeta } = prepared.value;\n\n // Dry-run the rule once: this both GATES (must return a Verdict) and SEEDS the rising-edge bit.\n const run = await runSandboxedFn(\n params.code,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs(sandbox, { h3, turf }),\n 'Tracker rule',\n state.codeExecution,\n );\n if ('error' in run) return { error: run.error };\n if (!isVerdict(run.value)) {\n return {\n error: 'Tracker `code` must return { active: boolean, members: {entryId, featureIds}[], summary: string }.',\n };\n }\n const verdict = run.value;\n\n const affectedEntryIds = [\n ...resolved.places.map((e) => e.id),\n ...resolved.routes.map((e) => e.id),\n ...resolved.incidents.map((e) => e.id),\n ...resolved.trafficAreaAnalytics.map((e) => e.id),\n ...resolved.byod.map((e) => e.id),\n ...geometriesMeta.affectedEntries.map((e) => e.id),\n ];\n\n const trackerId = uniqueTrackerId(params.name, state);\n const timestamp = Date.now();\n\n // Register the tracker with its rising-edge bit CLEAR and its job, THEN feed the arm-time verdict\n // through the SAME reducer as any later recompute: a condition already true at creation crosses\n // false→true and fires one `opened` alert (the user asked to watch something already happening —\n // surface it once). It does not re-toast while it stays active; only a resolve→reopen does. An\n // inactive arm-time verdict is a no-op (stays clear).\n const tracker: Tracker = {\n id: trackerId,\n name: params.name,\n rule: params.rule,\n affectedEntryIds,\n enabled: true,\n wasActive: false,\n };\n state.trackers.register(tracker);\n registerTrackerJob(state, { trackerId, code: params.code, inputs, affectedEntryIds });\n state.trackers.ingestVerdict(trackerId, verdict, timestamp);\n\n return {\n trackerId,\n name: params.name,\n rule: params.rule,\n watching: affectedEntryIds,\n firingNow: verdict.active,\n summary: verdict.summary,\n };\n};\n\n// trackerId = a slug of the name, deduped against existing trackers (`hospitals`, `hospitals-2`, …).\nconst uniqueTrackerId = (name: string, state: ToolState): string => {\n const base =\n name\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/g, '-')\n .replaceAll(/(?:^-+|-+$)/g, '') || 'tracker';\n const taken = new Set(state.trackers.trackers.map((t) => t.id));\n if (!taken.has(base)) return base;\n for (let i = 2; ; i++) if (!taken.has(`${base}-${i}`)) return `${base}-${i}`;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const focusIncidentsOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string().describe('The entry the focus was applied to.'),\n focusedCount: z.number().describe('How many of the requested ids ended up in the focus subset.'),\n droppedIds: z\n .array(z.string())\n .describe('Ids that were not present on the entry and were dropped from the focus.'),\n reason: z.string().optional().describe('Echo of the focus reason for downstream display.'),\n }),\n toolErrorSchema,\n]);\n\nexport const focusIncidentsSchema = z.object({\n incidentsEntryID: z\n .string()\n .describe(\n 'Id of the incidents entry to focus on (from `getTrafficIncidents` or any registered analysis source).',\n ),\n incidentIds: z\n .array(z.string())\n .min(1)\n .describe('Ids of incidents within the entry to highlight. Unknown ids are dropped, not errored.'),\n reason: z\n .string()\n .optional()\n .describe('Short label shown in the UI FocusChip — what this subset represents (e.g. \"top 3 by delay\").'),\n});\n\nexport const focusIncidentsDescription =\n 'Highlight a subset of incidents on an existing entry. Drives the dim/highlight on the map ' +\n 'and the FocusChip in the UI. Compose with `analyseData` to compute the ids first ' +\n '(e.g. top-N by delay, members of a cluster). Unknown ids are dropped silently and ' +\n 'returned in `droppedIds`. Errors only when the entry is unknown or NONE of the ids resolve.';\n\nexport const executeFocusIncidents = async (\n params: z.infer<typeof focusIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof focusIncidentsOutputSchema>> => {\n const { incidentsEntryID, incidentIds, reason } = params;\n const entry = state.trafficIncidents.entries.find((e) => e.id === incidentsEntryID);\n if (!entry) {\n return {\n error: `No incidents entry with id \"${incidentsEntryID}\". Call getTrafficIncidents first.`,\n };\n }\n const result = state.trafficIncidents.setFocus(incidentsEntryID, incidentIds, reason);\n if (result.focusedCount === 0) {\n return {\n error:\n `None of the ${incidentIds.length} requested ids exist on entry \"${incidentsEntryID}\". ` +\n 'The snapshot may have rolled — call getTrafficIncidents and recompute the ids before retrying.',\n };\n }\n return {\n incidentsEntryID,\n focusedCount: result.focusedCount,\n droppedIds: result.droppedIds,\n ...(reason && { reason }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeWaypoint, waypointSummarySchema } from '../../utils';\n\nexport const getCurrentWaypointsSchema = z.object({\n slotIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Exact waypoint slot to retrieve. Use when you need one known origin/stop/destination slot.'),\n includeEmptySlots: z\n .boolean()\n .optional()\n .describe('Include unfilled slots in the result set. Defaults to false to avoid null-heavy responses.'),\n offset: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe('Pagination offset within the filtered slot list. Ignored when slotIndex is provided.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(25)\n .optional()\n .describe('Maximum slots to return. Defaults to 5. Ignored when slotIndex is provided.'),\n});\n\nexport const getCurrentWaypointsOutputSchema = z.object({\n totalSlots: z.number().describe('Total waypoint slots currently staged'),\n filledSlots: z.number().describe('Number of staged slots that already contain coordinates'),\n matchedCount: z.number().describe('Total slots matching the request before pagination'),\n returnedCount: z.number().describe('Number of slots returned in this page'),\n offset: z.number().describe('Offset applied to the filtered slot list'),\n hasMore: z.boolean().describe('True when more slots can be fetched with a higher offset'),\n nextOffset: z.number().optional().describe('Offset to request the next page when hasMore=true'),\n waypoints: z.array(waypointSummarySchema),\n});\n\nexport const getCurrentWaypointsDescription =\n 'Read STAGED waypoint slots queued for the NEXT setRoute call (origin / stops / destination not yet committed to a route). ' +\n \"Distinct from `recallState({ kind: 'routes', id })`, which returns the waypoints of an existing calculated route entry. \" +\n '`slotIndex` for one slot; otherwise paged (default 5), empty slots omitted unless `includeEmptySlots: true`. No service call.';\n\nexport const executeGetCurrentWaypoints = async (\n params: z.infer<typeof getCurrentWaypointsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof getCurrentWaypointsOutputSchema>> => {\n const { slotIndex, includeEmptySlots = false, offset = 0, limit = 5 } = params;\n const slots = state.routing.planningSlots.map((waypoint, index) => {\n const normalized = summarizeWaypoint(waypoint);\n return normalized ? { ...normalized, slotIndex: index } : { slotIndex: index, isFilled: false };\n });\n const filteredSlots = includeEmptySlots ? slots : slots.filter((slot) => slot.isFilled);\n const hasExactSlot = slotIndex !== undefined;\n const selected = hasExactSlot\n ? filteredSlots.filter((slot) => slot.slotIndex === slotIndex)\n : filteredSlots.slice(offset, offset + limit);\n const nextOffset = offset + selected.length;\n\n return {\n totalSlots: slots.length,\n filledSlots: slots.filter((slot) => slot.isFilled).length,\n matchedCount: hasExactSlot ? selected.length : filteredSlots.length,\n returnedCount: selected.length,\n offset: hasExactSlot ? 0 : offset,\n hasMore: !hasExactSlot && nextOffset < filteredSlots.length,\n ...(!hasExactSlot && nextOffset < filteredSlots.length && { nextOffset }),\n waypoints: selected,\n };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * `getTrackerHistory` — read what trackers have fired (opened alerts + resolved events), newest first.\n * Reports the retained window honestly so the model never implies history older than the bounded log\n * holds. Optionally scoped to one tracker and/or a `sinceMs` cutoff.\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\nexport const getTrackerHistorySchema = z.object({\n trackerId: z.string().optional().describe('Scope to one tracker. Omit for every tracker.'),\n sinceMs: z.number().optional().describe('Only events at/after this epoch-ms sample time.'),\n});\n\nconst eventSchema = z.object({\n at: z.number(),\n trackerId: z.string(),\n trackerName: z.string(),\n type: z.enum(['alert', 'event']),\n kind: z.enum(['opened', 'resolved']),\n summary: z.string(),\n openedAt: z.number().optional(),\n});\n\nexport const getTrackerHistoryOutputSchema = z.object({\n events: z.array(eventSchema).describe('Newest first.'),\n count: z.number(),\n coverage: z\n .object({ oldestRetainedAt: z.number(), newestAt: z.number() })\n .optional()\n .describe('Sample-time span the bounded log currently retains — older events were evicted.'),\n});\n\nexport const getTrackerHistoryDescription =\n 'Read the tracker event log — `opened` alerts and `resolved` events, newest first. Optionally scope ' +\n 'to one `trackerId` or a `sinceMs` cutoff. The log is bounded; `coverage` reports how far back it reaches.';\n\nexport const executeGetTrackerHistory = async (\n params: { trackerId?: string; sinceMs?: number },\n state: ToolState,\n): Promise<z.infer<typeof getTrackerHistoryOutputSchema>> => {\n const retained = state.trackers.log({ trackerId: params.trackerId });\n const filtered = state.trackers.log({ trackerId: params.trackerId, sinceMs: params.sinceMs });\n const events = [...filtered].reverse().map((e) => ({\n at: e.at,\n trackerId: e.trackerId,\n trackerName: e.trackerName,\n type: e.type,\n kind: e.kind,\n summary: e.summary,\n ...(e.openedAt !== undefined && { openedAt: e.openedAt }),\n }));\n\n const newest = retained.at(-1);\n return {\n events,\n count: events.length,\n ...(newest && {\n coverage: { oldestRetainedAt: retained[0].at, newestAt: newest.at },\n }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * `getTrackers` — list the active trackers and their live state (what each watches, whether it is\n * currently firing). Introspection so the agent can answer \"what am I tracking\" and surface the entries\n * a tracker reads (its analysis's `affectedEntryIds`).\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\n\nexport const getTrackersSchema = z.object({});\n\nexport const getTrackersOutputSchema = z.object({\n trackers: z.array(\n z.object({\n trackerId: z.string(),\n name: z.string(),\n rule: z.string(),\n enabled: z.boolean(),\n firing: z.boolean().describe('Whether the rule is currently active (an open episode).'),\n openedAt: z.number().optional(),\n watching: z.array(z.string()).describe('Source entry ids the rule reads.'),\n }),\n ),\n count: z.number(),\n});\n\nexport const getTrackersDescription =\n 'List active trackers: name, rule, enabled, whether firing now, and which entries each watches.';\n\nexport const executeGetTrackers = async (\n _params: Record<string, never>,\n state: ToolState,\n): Promise<z.infer<typeof getTrackersOutputSchema>> => {\n const trackers = state.trackers.trackers.map((t) => ({\n trackerId: t.id,\n name: t.name,\n rule: t.rule,\n enabled: t.enabled,\n firing: t.wasActive,\n ...(t.openedAt !== undefined && { openedAt: t.openedAt }),\n watching: [...t.affectedEntryIds],\n }));\n return { trackers, count: trackers.length };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * `monitorAnalysis` — the recurring half of the analysis pair, expressed as a pure toggle. It carries\n * no code and no sandbox surface: `analyseData` defines *what to compute* and returns an `analysisId`;\n * this tool only flips *whether it stays live*. Enabling activates the analysis's {@link JobEngine} job\n * so it recomputes on every change to its source entries (incidents monitor ticks, re-searched places,\n * recalculated routes, …); disabling pauses it. The job is built in `monitoring.ts`.\n *\n * This replaces the incidents-only `monitorIncidentsAnalysis`: monitoring is now generic across every\n * analyseData input kind, and the focus side-effect is intentionally left out of the core — compose\n * with `focusIncidents` for live highlighting.\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { setAnalysisMonitored } from './monitoring';\n\n/** Structured output of `monitorAnalysis`: the toggled analysis id and its new monitoring state. */\nexport const monitorAnalysisOutputSchema = z.union([\n z.object({\n analysisId: z.string(),\n enabled: z\n .boolean()\n .describe('The analysis is now recomputed on every source-entry change (true) or not (false).'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for `monitorAnalysis` — the `analysisId` to toggle and whether to monitor it. */\nexport const monitorAnalysisSchema = z.object({\n analysisId: z\n .string()\n .describe('The `analysisId` returned by `analyseData` for the analysis to start or stop monitoring.'),\n enabled: z\n .boolean()\n .describe(\n 'true → recompute this analysis on every change to its source entries; false → stop monitoring it. ' +\n 'The latest result stays attached either way.',\n ),\n});\n\n/** Classifier-facing description of `monitorAnalysis` (the recurring toggle over an analyseData analysis). */\nexport const monitorAnalysisDescription =\n 'Start or stop MONITORING an existing analysis (one created by `analyseData`, referenced by its `analysisId`). ' +\n 'When enabled, the analysis recomputes on every change to its source entries — incidents monitor ticks, ' +\n 're-searched places, recalculated routes, refreshed BYOD/area-analytics — keeping its attached result live ' +\n '(use `previous` / `now` in the code for trend reads). Pick for \"keep this updated / tell me when it changes / ' +\n 'stop tracking it\". This is a pure on/off switch: define the computation with `analyseData` first. For a ' +\n 'one-off highlight of incidents use `focusIncidents`.';\n\n/** Toggle monitoring on the referenced analysis; errors when the `analysisId` is unknown. */\nexport const executeMonitorAnalysis = async (\n params: z.infer<typeof monitorAnalysisSchema>,\n state: ToolState,\n): Promise<z.infer<typeof monitorAnalysisOutputSchema>> => {\n const ok = setAnalysisMonitored(state, params.analysisId, params.enabled);\n if (!ok) {\n return {\n error: `No analysis with id \"${params.analysisId}\". Run analyseData first and pass the analysisId it returns.`,\n };\n }\n return { analysisId: params.analysisId, enabled: params.enabled };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Sandbox-running processing tool. Counterpart to `analyseData`: accepts any\n * combination of places / routes / incidents / customGeometries /\n * trafficAreaAnalytics / byod entries as inputs and produces a NEW places entry,\n * a NEW custom-geometries entry, a NEW BYOD entry, a camera fit, or any\n * combination of those, from a single sandbox run.\n *\n * Output disambiguation:\n * • `places` returned → writes a NEW places entry; `placeConnections` and\n * inline `geometries` (when also present) are attached to that entry.\n * • `geometries` returned without `places` → writes a NEW custom-geometries entry.\n * • `byod` returned alone → writes a NEW BYOD entry.\n * • `fitOnMap` alone → camera move only (no entry written).\n *\n * **Scope + enablement.** Shared scaffolding in `tools/shared/entry-kinds.ts` builds\n * the per-kind input fields, the per-kind sandbox / schema docs, the scope schema and\n * the \"at least one of …\" message. This file only adds the tool-specific surface:\n * the return-shape preamble, `routeUtils`, fit-on-map / show / connectMarkerType /\n * label / entryId / operation, the description, the result validator and the executor.\n */\n\nimport {\n type CommonPlaceProps,\n type Place,\n type Places,\n type PolygonFeature,\n placeTypes,\n} from '@tomtom-org/maps-sdk/core';\nimport type { GeometryFillStyle, PlaceConnectionDisplay } from '@tomtom-org/maps-sdk/map';\nimport * as turf from '@turf/turf';\nimport type { FeatureCollection as GeoJSONFeatureCollection, GeoJsonProperties } from 'geojson';\nimport * as h3 from 'h3-js';\nimport { z } from 'zod';\nimport type { EntryDataKind, ToolBuildOptions, ToolEntryBuilder, ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport {\n ALL_ENTRY_DATA_KINDS,\n applyFitOnMap,\n buildAtLeastOneEntryIdMessage,\n buildEntryFieldList,\n buildEntryIdFields,\n buildEntryKindSandboxDocs,\n buildEntryKindSchemaDocs,\n buildEntryKindScopeSchema,\n buildSandboxToolsDoc,\n type EntryKindScope,\n FIT_ON_MAP_DOC,\n type FitOnMapInput,\n fittedReportSchema,\n GEOMETRIES_PROPS_DOC,\n GEOMETRIES_SKIPPED_DESC,\n GEOMETRIES_SOURCE_IDS_DESC,\n type GeometriesId,\n type GeometriesMeta,\n geometriesIdSchema,\n geometryFillStyleSchema,\n hasAnyEntryIds,\n hidePreviousEntriesSchema,\n hidePreviousShownEntries,\n isFitOnMapInput,\n isPolygonFeature,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs,\n placesEntryIdHintSchema,\n prepareMultiInputs,\n resolveActiveKinds,\n runSandboxedFn,\n showModeSchema,\n showPlacesSchema,\n showResultsOnMap,\n skippedSourceSchema,\n} from '../shared';\nimport { isFeatureCollectionLike } from '../shared/geojson-guards';\nimport { buildPlacesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\n/**\n * Per-turn scope kinds that `processData` understands. Identical to {@link EntryDataKind}.\n *\n * @group Agent Toolkit\n */\nexport type ProcessDataKind = EntryDataKind;\n\n/**\n * Per-turn scope for `processData`. Emitted by the classifier in `toolScopes.processData`\n * when only a subset of input kinds is relevant.\n *\n * @group Agent Toolkit\n */\nexport type ProcessDataScope = EntryKindScope;\n\n/**\n * Default scope schema for processData — accepts every {@link EntryDataKind}. When the\n * agent narrows the enabled kinds via `createMapAgent`'s `dataEntries`, the builder\n * produces a narrower schema in {@link buildProcessDataEntry}.\n */\nexport const processDataScopeSchema = buildEntryKindScopeSchema(ALL_ENTRY_DATA_KINDS);\n\n// The sandbox environment is shared with analyseData (multi-input.ts): the injected\n// params (MULTI_INPUT_SANDBOX_PARAMS), the `routeUtils` namespace, and the\n// \"tools available in the sandbox\" prompt section (buildSandboxToolsDoc). processData\n// reuses all three directly — no process-data-specific param list, namespace, or doc.\n\nconst showProducedGeometriesSchema = z.object({\n fillStyle: geometryFillStyleSchema\n .optional()\n .describe('How the polygons are filled. Default: \"outline\". Use \"inverted\" for difference/inverse results.'),\n mode: showModeSchema,\n hidePreviousEntries: hidePreviousEntriesSchema('custom-geometries').optional(),\n});\n\nconst sharedProcessDataReport = {\n sourceIds: z\n .array(geometriesIdSchema)\n .optional()\n .describe(`${GEOMETRIES_SOURCE_IDS_DESC} (only when \\`geometriesEntryIDs\\` was set).`),\n skipped: z.array(skippedSourceSchema).optional().describe(GEOMETRIES_SKIPPED_DESC),\n fitted: fittedReportSchema.optional(),\n} as const;\n\n/** Output schema for process-data. */\nexport const processDataOutputSchema = z.union([\n // Places branch: code returned `places`. Attached connections + polygons go on the new entry.\n buildPlacesOutputSchema({}).extend({\n placesEntryId: z.string(),\n label: z.string().optional(),\n connectionsRendered: z.number().optional(),\n placesGeometryCount: z\n .number()\n .optional()\n .describe('Polygons attached to the new places entry (when `geometries` was returned alongside).'),\n shownPlaces: z.boolean().optional(),\n ...sharedProcessDataReport,\n }),\n // Custom-geometries branch: code returned `geometries` WITHOUT `places`.\n z.object({\n customGeometriesEntryId: geometriesIdSchema,\n customGeometriesCount: z.number(),\n label: z.string(),\n operation: z.string().optional(),\n shownCustomGeometries: z.boolean().optional(),\n ...sharedProcessDataReport,\n }),\n // BYOD branch: code returned `byod` WITHOUT `places`/`geometries`. Writes a new BYOD entry.\n z.object({\n byodEntryId: z.string(),\n byodFeatureCount: z.number(),\n label: z.string(),\n fitted: fittedReportSchema.optional(),\n }),\n // Fit-only branch: code returned `fitOnMap` alone.\n z.object({ fitted: fittedReportSchema }),\n toolErrorSchema,\n]);\n\n/** Build the scope-aware `code` field description for process-data. */\nexport const buildProcessDataCodeDoc = (\n enabled: readonly EntryDataKind[],\n scope: ProcessDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n // Deep per-kind schema docs are emitted for every active kind, scoped or not for cross-kind\n const schemaBlocks = buildEntryKindSchemaDocs(active);\n const showTrafficNote = active.includes('trafficAreaAnalytics');\n const trafficNote = showTrafficNote\n ? '`trafficAreaAnalytics` is a FeatureCollection of tile/hex regions whose `feature.properties` carry metric ' +\n 'fields (`congestionLevel`, `speed`, `freeFlowSpeed`, `travelTime`, `networkLength`); use ' +\n '`turf.booleanPointInPolygon` / `turf.lineIntersect` to bridge with places/routes.\\n\\n'\n : '';\n const geometriesProps = active.includes('customGeometries') ? `${GEOMETRIES_PROPS_DOC}\\n\\n` : '';\n const byodOutputLine = active.includes('byod')\n ? '• `byod: FeatureCollection` — writes a NEW BYOD entry (any GeoJSON: Point/Line/Polygon/mixed). Use for ' +\n 'derived BYOD layers (e.g. cleaned/filtered customer data).\\n'\n : '';\n // Per-kind sandbox-doc one-liners — always emitted for the active kinds, matching analyseData\n // (which never gated these). `active` narrows to the scoped subset when scope is set.\n const sandboxList = `${buildEntryKindSandboxDocs(active)}\\n`;\n return (\n 'Async JS returning `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`. At least one of ' +\n '`places` / `geometries` / `fitOnMap` / `byod` must be present. ' +\n '`places` (with optional attached `geometries`), standalone `geometries`, and `byod` are MUTUALLY ' +\n 'EXCLUSIVE — only one is written per call, others are dropped. `fitOnMap` composes with any of them.\\n\\n' +\n '• `places: Places` — writes a NEW places entry. Filters/merges/subsets of the input `placesByEntry[id].features`, or computed ' +\n 'point features that conform to the Place schema. Output is validated (validatePlaces): each place must be a Point Feature with a ' +\n '`properties.type` and a display name at its schema path, else it is rejected with a fixable error. When transforming an EXISTING ' +\n 'place, spread it so its `id` is kept: `{ ...place, properties: { ...place.properties, poi: { ...place.properties.poi, name } } }`. ' +\n 'A deterministic id is minted only for a feature that carries none (a freshly computed point).\\n' +\n '• `placeConnections: { from, to, id? }[]` — lines between output places (endpoints: Place or place id). ' +\n \"Requires `places`. Drawn on the new entry's PlacesModule.\\n\" +\n '• `geometries: PolygonFeature[]` — polygons (Polygon/MultiPolygon, stable `id` recommended). ' +\n 'When `places` is also present, attached to the new places entry; when `places` is absent, written as a NEW ' +\n 'custom-geometries entry.\\n' +\n '• `fitOnMap` — bbox or `{ bbox, padding?, animate? }`. Camera move; when alone, no entry written.\\n' +\n byodOutputLine +\n '\\n' +\n buildSandboxToolsDoc(active, true) +\n sandboxList +\n 'Each input is `undefined` when its `*EntryIDs` argument was omitted — guard before reading. ' +\n trafficNote +\n `${FIT_ON_MAP_DOC}\\n\\n` +\n geometriesProps +\n schemaBlocks\n );\n};\n\n/** Build the scope-aware processData input schema. */\nexport const buildProcessDataSchema = (enabled: readonly EntryDataKind[], scope: ProcessDataScope | undefined) => {\n const active = resolveActiveKinds(enabled, scope);\n const requiredHint = `At least one of ${buildEntryFieldList(active)} is REQUIRED.`;\n const shape: Record<string, z.ZodTypeAny> = {\n ...buildEntryIdFields(active, { verb: 'process', requiredHint }),\n };\n\n shape.code = z.string().describe(buildProcessDataCodeDoc(enabled, scope));\n shape.label = z\n .string()\n .optional()\n .describe('Short label for the new entry (used by both places and custom-geometries paths).');\n shape.entryId = placesEntryIdHintSchema;\n shape.operation = z\n .string()\n .optional()\n .describe(\n 'Short operation label (`union`, `buffer`, `difference`, `h3-coverage`, …) stored as provenance ' +\n 'on a new custom-geometries entry. Ignored on the places path.',\n );\n // `show` sub-fields are conditional on the corresponding kind being writable on the active\n // set: `places` write is possible whenever the code returns places (always allowed —\n // returning `places` is a sandbox concern, not a *EntryIDs concern); same for\n // `customGeometries`. We keep all three sub-fields exposed so the LLM can return any\n // supported kind without a scope twist. The executor only consults the matching field.\n shape.show = z\n .object({\n places: showPlacesSchema.optional().describe('Render the new places entry (when `places` was returned).'),\n placesGeometries: z\n .object({\n fillStyle: geometryFillStyleSchema.optional(),\n mode: showModeSchema,\n })\n .optional()\n .describe(\n 'Render the polygons attached to the new places entry (when both `places` and `geometries` were returned).',\n ),\n customGeometries: showProducedGeometriesSchema\n .optional()\n .describe('Render the new custom-geometries entry (when `geometries` was returned WITHOUT `places`).'),\n })\n .optional()\n .describe(\n 'Optional per-output render knobs. The executor only consults the field matching what the code returned ' +\n '(so leaving e.g. `customGeometries` set when `places` was returned is a silent no-op).',\n );\n shape.connectMarkerType = z\n .enum(['pin', 'base-map'])\n .optional()\n .describe(\n 'Which PlacesModule renders `placeConnections`. Default: \"pin\". Match the module holding the endpoints.',\n );\n\n return z.object(shape).refine(hasAnyEntryIds, { message: buildAtLeastOneEntryIdMessage(active) });\n};\n\n/** Default full-surface input schema for process-data — every kind enabled, no per-turn scope. */\nexport const processDataSchema = buildProcessDataSchema(ALL_ENTRY_DATA_KINDS, undefined);\n\n/** Build the scope-aware processData tool description. */\nexport const buildProcessDataDescription = (\n enabled: readonly EntryDataKind[],\n scope: ProcessDataScope | undefined,\n): string => {\n const active = resolveActiveKinds(enabled, scope);\n const kindList = active.join(' / ');\n const contrast =\n 'CONTRAST `analyseData`, which attaches JSON/chart metadata to existing entries and never renders.';\n if (scope) {\n return (\n `Run dynamic JS over ${kindList} entries to produce NEW renderable map data — a places, ` +\n 'custom-geometries, or BYOD entry (with optional `placeConnections` / attached polygon footprints), ' +\n `and/or a \\`fitOnMap\\` camera move. ${contrast} Returns ` +\n '`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`: when `places` is returned, inline ' +\n '`geometries` attach to it; otherwise they become a standalone custom-geometries entry. Inputs are ' +\n '`undefined` when their `*EntryIDs` argument is omitted — guard before reading.'\n );\n }\n return (\n `Run dynamic JS over any combination of ${kindList} entries to produce NEW renderable map data — a places, ` +\n 'custom-geometries, or BYOD entry (with optional `placeConnections` / attached polygon footprints), ' +\n `and/or a \\`fitOnMap\\` camera move. ${contrast} Returns ` +\n '`{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`: when `places` is returned, inline ' +\n '`geometries` attach to it as place footprints; otherwise they become a standalone custom-geometries entry. ' +\n '`routeUtils` is exposed alongside `h3` / `turf` for route section / progress work. Inputs are `undefined` ' +\n 'when their `*EntryIDs` argument is omitted — guard before reading.'\n );\n};\n\n/** Default full-surface description — every kind enabled. */\nexport const processDataDescription = buildProcessDataDescription(ALL_ENTRY_DATA_KINDS, undefined);\n\nconst buildProcessDataScopePrompt = (enabled: readonly EntryDataKind[]): string => {\n const list = enabled.map((k) => `\"${k}\"`).join(' | ');\n return (\n `Emit \\`{ kinds: [${list}] }\\` ` +\n 'listing only the entry kinds the user query touches. Omit when truly cross-kind.'\n );\n};\n\ntype ProcessResult = {\n places?: Places;\n placeConnections?: PlaceConnectionDisplay[];\n geometries?: PolygonFeature[];\n fitOnMap?: FitOnMapInput;\n byod?: GeoJSONFeatureCollection;\n};\n\nconst isConnectionArray = (value: unknown): value is PlaceConnectionDisplay[] => {\n if (!Array.isArray(value)) return false;\n const isEndpoint = (endpoint: unknown): boolean =>\n typeof endpoint === 'string' || (typeof endpoint === 'object' && endpoint !== null && 'geometry' in endpoint);\n return value.every((item) => {\n if (!item || typeof item !== 'object') return false;\n const connection = item as { from?: unknown; to?: unknown };\n return isEndpoint(connection.from) && isEndpoint(connection.to);\n });\n};\n\n// Walk the raw `geometries` value, narrow it to `PolygonFeature[]` with a pinpointed\n// error message on the first bad element. Splitting validation out keeps the type\n// predicate flowing into the returned `ProcessResult` without any `as` casts.\nconst validateGeometries = (raw: unknown): { value: PolygonFeature[] } | { error: string } => {\n if (!Array.isArray(raw)) {\n return { error: '`geometries` must be an array of GeoJSON Polygon/MultiPolygon Features.' };\n }\n const validated: PolygonFeature[] = [];\n for (let i = 0; i < raw.length; i++) {\n const item = raw[i];\n // Generic-explicit so the narrowed `item` matches `PolygonFeature`'s default `GeoJsonProperties`\n // (otherwise the predicate's `<P = unknown>` default leaks `PolygonFeature<unknown>` here).\n if (!isPolygonFeature<GeoJsonProperties>(item)) {\n return { error: `\\`geometries[${i}]\\` must be a GeoJSON Feature with Polygon or MultiPolygon geometry.` };\n }\n validated.push(item);\n }\n return { value: validated };\n};\n\nconst isNonEmptyString = (value: unknown): value is string => typeof value === 'string' && value.trim().length > 0;\n\nconst isValidLngLat = (coordinates: unknown): coordinates is [number, number] => {\n if (!Array.isArray(coordinates) || coordinates.length < 2) return false;\n const [lng, lat] = coordinates;\n return Number.isFinite(lng) && Number.isFinite(lat) && Math.abs(lng) <= 180 && Math.abs(lat) <= 90;\n};\n\n// We mint deterministic ids from the coordinates, and use the index as the disambiguator which is unique\n// This ensures places that share names/coordinates do no share an id. Mirrors the incident clustering id\nconst derivedPlaceId = (lng: number, lat: number, index: number): string =>\n `derived-${lng.toFixed(6)}_${lat.toFixed(6)}-${index}`;\n\n// The LLM-generated code can compute any features, but when it returns a feature as `places`, we validate and constrain it to the\n// canonical shape from `places-schema-doc`\nexport const validatePlaces = (raw: unknown): { value: Places } | { error: string } => {\n if (!isFeatureCollectionLike<Places>(raw)) {\n return { error: '`places` must be a `Places` FeatureCollection of Point features.' };\n }\n\n const seenIds = new Map<string, number>();\n for (let i = 0; i < raw.features.length; i++) {\n const feature = raw.features[i] as Partial<Place> | undefined;\n const geometry = feature?.geometry;\n if (!geometry || geometry.type !== 'Point' || !isValidLngLat(geometry.coordinates)) {\n return {\n error: `\\`places[${i}]\\` must be a Point Feature with valid [lng, lat] coordinates (lng ∈ [-180,180], lat ∈ [-90,90]).`,\n };\n }\n const properties = feature.properties as\n | { type?: unknown; poi?: { name?: unknown }; address?: { freeformAddress?: unknown } }\n | undefined;\n\n const label = [properties?.poi?.name, properties?.address?.freeformAddress].find(isNonEmptyString);\n if (!label) {\n return {\n error: `\\`places[${i}]\\` needs a display name in \\`properties.poi.name\\` (POIs) or \\`properties.address.freeformAddress\\` (streets, intersections, addresses).`,\n };\n }\n if (\n !isNonEmptyString(properties?.type) ||\n !placeTypes.includes(properties.type as (typeof placeTypes)[number])\n ) {\n return {\n error: `\\`places[${i}].properties.type\\` is required and must be one of: ${placeTypes.join(', ')}.`,\n };\n }\n\n // Preserve a real id (a source place, or one carried through by spreading the source); mint a\n // deterministic one only when the feature carries none, so the required features[].id holds.\n if (!isNonEmptyString(feature.id)) {\n const [lng, lat] = geometry.coordinates;\n feature.id = derivedPlaceId(lng, lat, i);\n }\n\n // We reject duplicate ids for model-authored duplicates as minted ids are index-keyed and unique\n const priorIndex = seenIds.get(feature.id);\n if (priorIndex !== undefined) {\n return {\n error: `\\`places[${i}]\\` duplicates id \"${feature.id}\" of \\`places[${priorIndex}]\\`; give each place a distinct id, or omit it so a unique one is derived.`,\n };\n }\n seenIds.set(feature.id, i);\n }\n return { value: raw };\n};\n\n// `validateField` collapses the \"either the optional field is missing OR it passes the shape check\"\n// pattern into one helper, so `validateProcessOutput` stays under the per-function cognitive\n// complexity cap (each per-field block was a nested `if` previously). Returning `value: undefined`\n// for an absent field lets callers assign unconditionally.\nconst validateField = <T>(\n raw: unknown,\n check: (value: unknown) => value is T,\n errorMessage: string,\n): { value: T | undefined } | { error: string } => {\n if (raw === undefined) return { value: undefined };\n if (!check(raw)) return { error: errorMessage };\n return { value: raw };\n};\n\n// Treat an empty FeatureCollection as \"no content produced\". Write sites downstream use truthy\n// checks (`if (processed)` / `if (producedByod)`), and the joint-constraint check below counts\n// fields as \"set\" by `!== undefined`. Without this normalisation, returning `{ features: [] }`\n// would slip past both — writing an empty places/byod entry while a non-empty geometries / byod\n// return on the same call gets silently dropped (single-write-per-call semantics).\nconst nonEmptyFeatureCollection = <T extends { features: readonly unknown[] }>(fc: T | undefined): T | undefined =>\n fc && fc.features.length > 0 ? fc : undefined;\n\n// Joint-shape rule: at least one writable output must be present, and placeConnections may only\n// accompany places. Split out so `validateProcessOutput`'s per-field validations stay flat.\nconst checkProcessJointConstraints = (result: ProcessResult): string | undefined => {\n const noOutput =\n result.places === undefined &&\n result.geometries === undefined &&\n result.fitOnMap === undefined &&\n result.byod === undefined;\n if (noOutput) {\n return 'At least one of `places`, `geometries`, `fitOnMap`, `byod` must produce content (empty arrays / FeatureCollections do not count).';\n }\n if (result.places === undefined && result.placeConnections !== undefined) {\n return '`placeConnections` requires `places` to be returned alongside.';\n }\n return undefined;\n};\n\nconst validateProcessOutput = (raw: unknown): ProcessResult | string => {\n if (!raw || typeof raw !== 'object') {\n return 'Process code must return `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }`.';\n }\n const candidate = raw as {\n places?: unknown;\n placeConnections?: unknown;\n geometries?: unknown;\n fitOnMap?: unknown;\n byod?: unknown;\n };\n\n const result: ProcessResult = {};\n\n if (candidate.places !== undefined) {\n const places = validatePlaces(candidate.places);\n if ('error' in places) return places.error;\n result.places = nonEmptyFeatureCollection(places.value);\n }\n\n const placeConnections = validateField(\n candidate.placeConnections,\n isConnectionArray,\n '`placeConnections` must be an array of `{ from, to, id? }` (endpoints: Place or place id).',\n );\n if ('error' in placeConnections) return placeConnections.error;\n result.placeConnections = placeConnections.value;\n\n if (candidate.geometries !== undefined) {\n const validated = validateGeometries(candidate.geometries);\n if ('error' in validated) return validated.error;\n // Empty array drops to undefined for the same reason as `nonEmptyFeatureCollection` above —\n // joint-constraint + downstream truthy checks treat absent and empty consistently.\n result.geometries = validated.value.length > 0 ? validated.value : undefined;\n }\n\n const fitOnMap = validateField(\n candidate.fitOnMap,\n isFitOnMapInput,\n '`fitOnMap` must be `[W,S,E,N]` or `{ bbox: [W,S,E,N], padding?, animate? }`.',\n );\n if ('error' in fitOnMap) return fitOnMap.error;\n result.fitOnMap = fitOnMap.value;\n\n const byod = validateField<GeoJSONFeatureCollection>(\n candidate.byod,\n isFeatureCollectionLike<GeoJSONFeatureCollection>,\n '`byod` must be a GeoJSON FeatureCollection (any geometry types).',\n );\n if ('error' in byod) return byod.error;\n result.byod = nonEmptyFeatureCollection(byod.value);\n\n const jointError = checkProcessJointConstraints(result);\n return jointError ?? result;\n};\n\ntype ProcessDataInput = {\n placesEntryIDs?: string[];\n routesEntryIDs?: string[];\n incidentsEntryIDs?: string[];\n geometriesEntryIDs?: GeometriesId[];\n trafficAreaAnalyticsEntryIDs?: string[];\n byodEntryIDs?: string[];\n code: string;\n label?: string;\n entryId?: string;\n operation?: string;\n show?: {\n places?: z.infer<typeof showPlacesSchema>;\n placesGeometries?: { fillStyle?: GeometryFillStyle; mode?: 'add' | 'replace' };\n customGeometries?: z.infer<typeof showProducedGeometriesSchema>;\n };\n connectMarkerType?: 'pin' | 'base-map';\n};\n\ntype ProcessOutput = z.infer<typeof processDataOutputSchema>;\n\n// Write the BYOD branch's output. No places/geometries override — BYOD layers manage their own\n// visibility via state.byod, so we just write the entry and optionally fit the camera.\nconst writeByodResult = async (\n state: ToolState,\n params: ProcessDataInput,\n producedByod: GeoJSONFeatureCollection,\n fitOnMap: FitOnMapInput | undefined,\n): Promise<ProcessOutput> => {\n const { label, entryId } = params;\n const byodLabel = label ? `processed: ${label}` : `processed byod (${producedByod.features.length})`;\n const newByodEntryId = await state.byod.addEntry(producedByod, byodLabel, {\n ...(entryId && { explicitId: entryId }),\n });\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n return {\n byodEntryId: newByodEntryId,\n byodFeatureCount: producedByod.features.length,\n label: byodLabel,\n ...(fitted && { fitted }),\n };\n};\n\n// Optional connection rendering — pulled out so its try/catch + module-access doesn't bloat the\n// places-path branch counter.\nconst renderConnections = async (\n state: ToolState,\n newPlacesEntryId: string,\n placeConnections: PlaceConnectionDisplay[],\n connectMarkerType: 'pin' | 'base-map' | undefined,\n): Promise<{ count: number } | { error: string }> => {\n try {\n const placesModule = await state.places.getEntryPlacesModule(newPlacesEntryId, connectMarkerType ?? 'pin');\n await placesModule.showConnections(placeConnections);\n return { count: placeConnections.length };\n } catch (error) {\n return { error: `Failed to render connections: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n// Places branch — writes the entry, optionally renders it, attaches polygons / connections, and\n// fits the camera. Returns the same shape as the executor's places branch did inline.\nconst writePlacesResult = async (\n state: ToolState,\n params: ProcessDataInput,\n validated: ProcessResult & { places: Places },\n geometriesMeta: GeometriesMeta,\n): Promise<ProcessOutput> => {\n const { label, entryId, show, connectMarkerType, geometriesEntryIDs } = params;\n const { places: processed, placeConnections, geometries: producedGeometries, fitOnMap } = validated;\n\n const count = processed.features.length;\n const labelPrefix = label ? `processed: ${label}` : 'processed';\n const placeWord = count === 1 ? 'place' : 'places';\n const entryLabel = `${labelPrefix} (${count} ${placeWord})`;\n const newPlacesEntryId = await state.places.addPlaceResult(\n processed,\n entryLabel,\n entryId,\n placeConnections,\n producedGeometries as PolygonFeature<CommonPlaceProps>[] | undefined,\n );\n\n const shownPlaces = show?.places ? await showResultsOnMap(state, [newPlacesEntryId], show.places) : undefined;\n\n let placesGeometryCount: number | undefined;\n if (producedGeometries?.length) {\n placesGeometryCount = producedGeometries.length;\n if (show?.placesGeometries) {\n await state.places.showPlaceGeometries(\n producedGeometries as PolygonFeature<CommonPlaceProps>[],\n show.placesGeometries.fillStyle ?? 'outline',\n show.placesGeometries.mode ?? 'replace',\n newPlacesEntryId,\n );\n }\n }\n\n let connectionsRendered: number | undefined;\n if (placeConnections) {\n const rendered = await renderConnections(state, newPlacesEntryId, placeConnections, connectMarkerType);\n if ('error' in rendered) return rendered;\n connectionsRendered = rendered.count;\n }\n\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n const storedLabel = state.places.entries.find((entry) => entry.id === newPlacesEntryId)?.label;\n\n return {\n ...summarizePlaces(processed),\n placesEntryId: newPlacesEntryId,\n ...(storedLabel && { label: storedLabel }),\n ...(shownPlaces && { shownPlaces: true }),\n ...(connectionsRendered !== undefined && { connectionsRendered }),\n ...(placesGeometryCount !== undefined && { placesGeometryCount }),\n ...(geometriesEntryIDs && { sourceIds: geometriesMeta.sourceIds }),\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n ...(fitted && { fitted }),\n };\n};\n\n// Custom-geometries branch — writes a new entry from the produced polygons and optionally renders.\nconst writeCustomGeometriesResult = async (\n state: ToolState,\n params: ProcessDataInput,\n producedGeometries: PolygonFeature[],\n fitOnMap: FitOnMapInput | undefined,\n geometriesMeta: GeometriesMeta,\n): Promise<ProcessOutput> => {\n const { label, entryId, operation, show, geometriesEntryIDs } = params;\n const customLabel = label ? `processed: ${label}` : `processed geometries (${producedGeometries.length})`;\n const newCustomEntryId = await state.customGeometries.addEntry(\n producedGeometries as PolygonFeature<CommonPlaceProps>[],\n { sourceIds: geometriesMeta.sourceIds, ...(operation && { operation }) },\n customLabel,\n entryId,\n );\n\n let shownCustomGeometries = false;\n if (show?.customGeometries) {\n await hidePreviousShownEntries(\n state.customGeometries,\n [newCustomEntryId],\n show.customGeometries.hidePreviousEntries,\n );\n await state.customGeometries.showEntry(\n newCustomEntryId,\n show.customGeometries.fillStyle ?? 'outline',\n show.customGeometries.mode ?? 'replace',\n );\n shownCustomGeometries = true;\n }\n\n const fitted = fitOnMap ? applyFitOnMap(state, fitOnMap) : undefined;\n const customGeometriesEntryId: GeometriesId = { kind: 'customGeometries', id: newCustomEntryId };\n\n return {\n customGeometriesEntryId,\n customGeometriesCount: producedGeometries.length,\n label: customLabel,\n ...(operation && { operation }),\n ...(geometriesEntryIDs && { sourceIds: geometriesMeta.sourceIds }),\n ...(geometriesMeta.skipped.length && { skipped: geometriesMeta.skipped }),\n ...(shownCustomGeometries && { shownCustomGeometries: true }),\n ...(fitted && { fitted }),\n };\n};\n\nexport const executeProcessData = async (params: ProcessDataInput, state: ToolState): Promise<ProcessOutput> => {\n const {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n code,\n } = params;\n\n const prepared = await prepareMultiInputs(\n {\n placesEntryIDs,\n routesEntryIDs,\n incidentsEntryIDs,\n geometriesEntryIDs,\n trafficAreaAnalyticsEntryIDs,\n byodEntryIDs,\n },\n state,\n );\n if ('error' in prepared) return { error: prepared.error };\n const { sandbox, geometries: geometriesMeta } = prepared.value;\n\n const sandboxResult = await runSandboxedFn(\n code,\n MULTI_INPUT_SANDBOX_PARAMS,\n packSandboxArgs(sandbox, { h3, turf }),\n 'Process',\n state.codeExecution,\n );\n if ('error' in sandboxResult) return { error: sandboxResult.error };\n\n const validated = validateProcessOutput(sandboxResult.value);\n if (typeof validated === 'string') return { error: validated };\n const { places: processed, geometries: producedGeometries, fitOnMap, byod: producedByod } = validated;\n\n // Places + BYOD in the same return aren't supported — places path takes precedence when both\n // are present (BYOD is silently dropped to keep single-write-per-call semantics).\n if (processed) {\n return writePlacesResult(state, params, { ...validated, places: processed }, geometriesMeta);\n }\n if (producedGeometries?.length) {\n return writeCustomGeometriesResult(state, params, producedGeometries, fitOnMap, geometriesMeta);\n }\n if (producedByod) {\n return writeByodResult(state, params, producedByod, fitOnMap);\n }\n // Fit-only: no entry produced, just move the camera.\n if (fitOnMap) {\n return { fitted: applyFitOnMap(state, fitOnMap) };\n }\n return { error: 'Process code returned no `places`, `geometries`, `fitOnMap`, or `byod`.' };\n};\n\n/**\n * Scope-aware builder for the processData tool. Reads classifier-resolved `scope` AND the\n * agent's `enabledDataKinds` from {@link ToolBuildOptions} so disabled kinds disappear from\n * the description, schema, code-doc and classifier scope. The registry composes this with\n * static classifier metadata (tags / examples / related / depends / classificationPrompt)\n * so the registry remains the single source of truth for those fields.\n */\nexport const buildProcessDataEntry = (\n options: ToolBuildOptions<ProcessDataScope>,\n metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n },\n) => {\n const enabled = options.enabledDataKinds ?? ALL_ENTRY_DATA_KINDS;\n return {\n description: buildProcessDataDescription(enabled, options.scope),\n inputSchema: buildProcessDataSchema(enabled, options.scope),\n outputSchema: processDataOutputSchema,\n execute: executeProcessData as (input: unknown, state: ToolState) => Promise<unknown>,\n scopeSchema: buildEntryKindScopeSchema(enabled),\n scopePrompt: buildProcessDataScopePrompt(enabled),\n ...metadata,\n };\n};\n\n/**\n * Builder for the `processData` default tool entry. Reads classifier-resolved scope and\n * the agent's enabled kinds from {@link ToolBuildOptions} and produces a narrowed entry;\n * without either, emits the full surface unchanged.\n */\nexport const processDataBuilder =\n (metadata: {\n classificationPrompt?: string;\n tags?: string[];\n examples?: string[];\n examplePrompts?: string[];\n relatedTools?: string[];\n dependsOn?: string[];\n }): ToolEntryBuilder<ToolState, ProcessDataScope> =>\n (options) =>\n buildProcessDataEntry(options, metadata);\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport { toByodSafeProfile } from '../../state/byod';\nimport type { ToolState } from '../../types';\nimport { byodDataProfileSchema, toolErrorSchema } from '../shared-output-schemas';\n\nconst sourceSchema = z.union([\n z.object({ kind: z.literal('integrator'), description: z.string().optional() }),\n z.object({ kind: z.literal('url'), url: z.string() }),\n z.object({ kind: z.literal('inline'), description: z.string().optional() }),\n]);\n\nconst refSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number(),\n geometryTypes: z.array(z.string()),\n propertyNames: z\n .array(z.string())\n .describe(\n 'Feature-property keys seen in the data, highest-coverage first and capped — wide datasets may omit ' +\n 'low-coverage keys. Pass `id` for the full per-property profile (types/coverage/numeric examples) ' +\n 'plus any `propertiesOmitted` count.',\n ),\n source: sourceSchema,\n shown: z.boolean(),\n});\n\nconst detailSchema = refSchema.extend({\n profile: byodDataProfileSchema.describe(\n 'Runtime-inferred shape of the data (per-property types/coverage; numeric/boolean examples only — ' +\n 'string example values are withheld). To compute over the actual feature values, pass this entry ' +\n 'to `analyseData` / `processData`; the raw GeoJSON is never returned to the model.',\n ),\n});\n\nexport const recallByodOutputSchema = z.union([\n z.object({ entries: z.array(refSchema), entryMode: z.enum(['single', 'multiple']) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallByodSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('BYOD entry id to inspect in detail. Omit to list every BYOD entry currently in session state.'),\n});\n\nexport const recallByodDescription =\n 'List or inspect BYOD (bring-your-own-data) GeoJSON layers in session state. These come from either ' +\n 'programmatic seeding by the embedding app OR from `addByodSource` calls. Call this BEFORE passing ids ' +\n 'to `byodEntryIDs` on `analyseData` / `processData` / `updateByodDisplay` — never guess. ' +\n 'No args → index (id, label, featureCount, geometryTypes, propertyNames, source, shown); ' +\n 'pass `id` → full data profile (the raw GeoJSON is never returned — use analyseData / processData to ' +\n 'compute over the feature values).';\n\nexport const executeRecallByod = async (\n params: z.infer<typeof recallByodSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallByodOutputSchema>> => {\n if (!params.id) {\n const entries = state.byod.entries.map((entry) => ({\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.profile.featureCount,\n geometryTypes: entry.profile.geometryTypes,\n propertyNames: entry.profile.properties.map((property) => property.name),\n source: entry.source,\n shown: !!entry._shown,\n }));\n return { entries, entryMode: state.byod.entryMode };\n }\n const entry = state.byod.findById(params.id);\n if (!entry) {\n return {\n error: `No BYOD entry with id \"${params.id}\". Call recallState({ kind: \"byod\" }) to list available IDs.`,\n };\n }\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.profile.featureCount,\n geometryTypes: entry.profile.geometryTypes,\n propertyNames: entry.profile.properties.map((property) => property.name),\n // Strip customer-supplied string example values, and never echo the raw GeoJSON back to\n // the model (untrusted free text + violates the \"summarize, never return full GeoJSON\"\n // contract). The full data stays in state for the host and for analyseData / processData.\n profile: toByodSafeProfile(entry.profile),\n source: entry.source,\n shown: !!entry._shown,\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { type GeometriesId, geometriesIdSchema } from '../shared';\nimport { analysisIndexSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallGeometriesSchema = z.object({\n id: geometriesIdSchema\n .optional()\n .describe(\n 'Tagged id to inspect in detail (e.g. `{ kind: \"places\", id: \"ev-stations-oost\" }`, ' +\n '`{ kind: \"ranges\", id: \"ranges-0\" }`, `{ kind: \"customGeometries\", id: \"bakery-candidates-gap-oost\" }`). ' +\n 'Omit to list every available id across all slices.',\n ),\n});\n\nconst refSchema = z.object({\n id: geometriesIdSchema.describe('Tagged id of this entry.'),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number(),\n operation: z.string().optional().describe('Operation label (custom-geometries entries only).'),\n sourceIds: z\n .array(geometriesIdSchema)\n .optional()\n .describe('Provenance: tagged ids of inputs that fed the producing op (custom only).'),\n analyses: z\n .array(analysisIndexSchema)\n .optional()\n .describe('Analyses already attached to this entry (places and custom only).'),\n});\n\nconst detailSchema = refSchema.extend({\n /**\n * GeoJSON-like structure: `{ type: 'FeatureCollection', features: PolygonFeature[] }`.\n * Schema kept loose because the toolkit serialises features verbatim.\n */\n features: z.unknown().describe(\"GeoJSON FeatureCollection of the entry's polygon features.\"),\n});\n\nexport const recallGeometriesOutputSchema = z.union([\n z.object({ entries: z.array(refSchema) }),\n detailSchema,\n toolErrorSchema,\n]);\n\nexport const recallGeometriesDescription =\n 'List or inspect every polygon source in session state — place footprints (cached on places ' +\n 'entries via `withGeometries: true`), reachable-area isochrones, and custom-geometries entries ' +\n 'produced by `processData`. ALWAYS call this before passing ids to `processData` or `analyseData` ' +\n '(`geometriesEntryIDs`); never guess. ' +\n 'No args = index across all three slices; pass `id` to retrieve features and provenance for one entry. ' +\n 'No service call.';\n\nconst summariseAnalyses = (\n analyses:\n | readonly { name: string; outputFormat: 'json' | 'chart'; timestamp: number; description?: string }[]\n | undefined,\n) =>\n analyses?.length\n ? analyses.map(({ name, outputFormat, timestamp, description }) => ({\n name,\n outputFormat,\n timestamp,\n ...(description && { description }),\n }))\n : undefined;\n\nconst indexEntries = (state: ToolState): z.infer<typeof refSchema>[] => {\n const refs: z.infer<typeof refSchema>[] = [];\n\n for (const entry of state.places.entries) {\n if (!entry.geometries?.length) continue;\n const analyses = summariseAnalyses(state.analyses.getAnalysesForEntry(entry.id));\n refs.push({\n id: { kind: 'places', id: entry.id },\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.geometries.length,\n ...(analyses && { analyses }),\n });\n }\n\n for (const entry of state.ranges.entries) {\n const featureCount = entry.data.reduce((acc, r) => acc + (r.polygon?.features.length ?? 0), 0);\n if (featureCount === 0) continue;\n refs.push({\n id: { kind: 'ranges', id: entry.id },\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount,\n });\n }\n\n for (const entry of state.customGeometries.entries) {\n const analyses = summariseAnalyses(state.analyses.getAnalysesForEntry(entry.id));\n refs.push({\n id: { kind: 'customGeometries', id: entry.id },\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.data.length,\n ...(entry.provenance.operation && { operation: entry.provenance.operation }),\n ...(entry.provenance.sourceIds.length && { sourceIds: [...entry.provenance.sourceIds] }),\n ...(analyses && { analyses }),\n });\n }\n\n return refs.sort((a, b) => b.timestamp - a.timestamp);\n};\n\ntype Detail = z.infer<typeof detailSchema>;\n\nconst detailForPlaces = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n const entry = state.places.entries.find((e) => e.id === id.id);\n if (!entry) return { error: `No places entry with id \"${id.id}\"` };\n if (!entry.geometries?.length) {\n return {\n error: `Places entry \"${id.id}\" has no cached footprints. Call discoverPlaces / locatePlace with \\`withGeometries: true\\` first.`,\n };\n }\n const analyses = summariseAnalyses(state.analyses.getAnalysesForEntry(entry.id));\n return {\n id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.geometries.length,\n features: { type: 'FeatureCollection', features: entry.geometries },\n ...(analyses && { analyses }),\n };\n};\n\nconst detailForRanges = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n const entry = state.ranges.entries.find((e) => e.id === id.id);\n if (!entry) return { error: `No ranges entry with id \"${id.id}\"` };\n const features = entry.data.flatMap((range) => range.polygon?.features ?? []);\n if (features.length === 0) return { error: `Ranges entry \"${id.id}\" has no polygons.` };\n return {\n id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: features.length,\n features: { type: 'FeatureCollection', features },\n };\n};\n\nconst detailForCustomGeometries = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n const entry = state.customGeometries.findById(id.id);\n if (!entry) return { error: `No custom-geometries entry with id \"${id.id}\"` };\n const analyses = summariseAnalyses(state.analyses.getAnalysesForEntry(entry.id));\n return {\n id,\n label: entry.label,\n timestamp: entry.timestamp,\n featureCount: entry.data.length,\n features: { type: 'FeatureCollection', features: entry.data },\n ...(entry.provenance.operation && { operation: entry.provenance.operation }),\n ...(entry.provenance.sourceIds.length && { sourceIds: [...entry.provenance.sourceIds] }),\n ...(analyses && { analyses }),\n };\n};\n\nconst detailFor = (id: GeometriesId, state: ToolState): Detail | { error: string } => {\n switch (id.kind) {\n case 'places':\n return detailForPlaces(id, state);\n case 'ranges':\n return detailForRanges(id, state);\n case 'customGeometries':\n return detailForCustomGeometries(id, state);\n default:\n return {\n error: '`{ kind: \"place\" }` is not a recallable detail target. Pass a places-entry id (`recallState({ kind: \"places\", id })`) to inspect a single place.',\n };\n }\n};\n\nexport const executeRecallGeometries = async (\n params: z.infer<typeof recallGeometriesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallGeometriesOutputSchema>> => {\n const { id } = params;\n if (!id) return { entries: indexEntries(state) };\n return detailFor(id, state);\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallIncidentsSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('Incidents entry id to inspect (e.g. \"incidents-0\"). Omit to list every incidents entry.'),\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several incidents entries render at once; ' +\n '`single` keeps only the latest entry — switching to it auto-clears the others.',\n );\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n count: z.number().describe('Number of incidents captured in this entry.'),\n shown: z.boolean().describe('True while this entry is rendered on the map.'),\n monitored: z.boolean().describe('True while a background monitor is actively polling this entry.'),\n focusedCount: z.number().describe(\"Incidents in the entry's focus subset (0 when nothing is focused).\"),\n});\n\nconst detailSchema = indexEntrySchema.extend({\n byMagnitude: z\n .record(z.string(), z.number())\n .describe('Incident count grouped by `magnitudeOfDelay` (e.g. `{ major: 3, moderate: 5, minor: 10 }`).'),\n totalDelaySeconds: z.number().describe('Sum of `delayInSeconds` across the entry (0 when no delay data).'),\n clusterCount: z\n .number()\n .optional()\n .describe('Number of incident clusters computed for this entry — present only once clustering has run.'),\n});\n\nexport const recallIncidentsOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallIncidentsDescription =\n 'List stored traffic-incidents entries, or inspect one by `id`. No args → index (id, label, count, shown, ' +\n 'monitored, focusedCount). Pass `id` → that entry plus a `magnitudeOfDelay` breakdown, total delay, and the ' +\n 'cluster count when clustering has run. Never guess IDs — list first. No service call.';\n\nexport const executeRecallIncidents = async (\n params: z.infer<typeof recallIncidentsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallIncidentsOutputSchema>> => {\n const slice = state.trafficIncidents;\n const entryMode = slice.entryMode;\n\n if (!params.id) {\n // Newest-first, matching every other recall index.\n const entries = [...slice.entries].reverse().map((entry) => ({\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n count: entry.data.length,\n shown: slice.shownEntryIds.has(entry.id),\n monitored: slice.isMonitored(entry.id),\n focusedCount: slice.getFocus(entry.id)?.ids.size ?? 0,\n }));\n return { entries, entryMode };\n }\n\n const entry = slice.entries.find((e) => e.id === params.id);\n if (!entry) {\n return {\n error: `No incidents entry with id \"${params.id}\". Call recallState({ kind: \"incidents\" }) to list available IDs.`,\n };\n }\n\n const byMagnitude: Record<string, number> = {};\n let totalDelaySeconds = 0;\n for (const incident of entry.data) {\n const magnitude = incident.properties.magnitudeOfDelay;\n byMagnitude[magnitude] = (byMagnitude[magnitude] ?? 0) + 1;\n totalDelaySeconds += incident.properties.delayInSeconds ?? 0;\n }\n const clusters = slice.getClusters(entry.id);\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n count: entry.data.length,\n shown: slice.shownEntryIds.has(entry.id),\n monitored: slice.isMonitored(entry.id),\n focusedCount: slice.getFocus(entry.id)?.ids.size ?? 0,\n byMagnitude,\n totalDelaySeconds,\n ...(clusters && { clusterCount: clusters.groups.length }),\n entryMode,\n };\n};\n","import { z } from 'zod';\nimport { placesMarkerTypes } from '../../state';\nimport type { FeatureFlags, ToolEntry, ToolEntryBuilder, ToolState } from '../../types';\nimport { summarizePlaces } from '../../utils';\nimport { analysisIndexSchema, buildPlacesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallPlacesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"places-3\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n featureCount: z.number().describe('Number of Place features stored in this entry.'),\n shown: z.boolean().describe('Whether this entry is currently rendered on the map.'),\n markerType: z\n .enum(placesMarkerTypes)\n .optional()\n .describe('Which marker type is rendering this entry (undefined when `shown` is false).'),\n analyses: z\n .array(analysisIndexSchema)\n .optional()\n .describe('Analyses already attached to this entry via analyseData (name + output format).'),\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several entries render simultaneously; ' +\n '`single` enforces \"at most one entry on the map\" — switching to it auto-clears non-latest entries.',\n );\n\n/** Build the flag-aware output schema for recall-places. */\nexport const buildRecallPlacesOutputSchema = (flags: FeatureFlags) => {\n const detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n places: buildPlacesOutputSchema(flags),\n });\n return z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n ]);\n};\n\n/** Output schema built with no feature flags set. */\nexport const recallPlacesOutputSchema = buildRecallPlacesOutputSchema({});\n\nexport const recallPlacesDescription =\n 'List / inspect places entries already in session state (from discoverPlaces / locatePlace / processData) ' +\n '— the same entries every `placesEntryIDs` parameter accepts. ALWAYS call this before referencing past ' +\n 'places; never guess IDs. No args = index (id, label, timestamp, featureCount, analyses) + `entryMode`; ' +\n 'pass `id` for full Place coordinates. No service call.';\n\n/**\n * Build the recall-places executor for a given {@link FeatureFlags} bag.\n * Controls whether the place summaries include the experimental `areaId` /\n * `areaCountry` / `areaTags` fields.\n */\nexport const buildExecuteRecallPlaces =\n (flags: FeatureFlags) =>\n async (\n params: z.infer<typeof recallPlacesSchema>,\n state: ToolState,\n ): Promise<z.infer<ReturnType<typeof buildRecallPlacesOutputSchema>>> => {\n const { id } = params;\n const allEntries = state.places.entries;\n const entryMode = state.places.entryMode;\n const shownEntryIds = state.places.shownEntryIds;\n\n if (!id) {\n const entries = [...allEntries]\n .sort((a, b) => b.timestamp - a.timestamp)\n .map(({ id, label, timestamp, data: places }) => {\n const shown = shownEntryIds.has(id);\n const markerType = shown ? state.places.getShownMarkerType(id) : undefined;\n const analyses = state.analyses.getAnalysesForEntry(id);\n return {\n id,\n label,\n timestamp,\n featureCount: places.length,\n shown,\n ...(markerType && { markerType }),\n ...(analyses.length && {\n analyses: analyses.map(({ name, outputFormat, timestamp, description }) => ({\n name,\n outputFormat,\n timestamp,\n ...(description && { description }),\n })),\n }),\n };\n });\n return { entries, entryMode };\n }\n\n const entry = allEntries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n const places = summarizePlaces(entry.data);\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n places,\n entryMode,\n };\n };\n\n/** Executor built with no feature flags set. */\nexport const executeRecallPlaces = buildExecuteRecallPlaces({});\n\n/**\n * Build a complete {@link ToolEntry} for `recallPlaces` for the given\n * {@link FeatureFlags}.\n */\nexport const buildRecallPlacesEntry = <S extends ToolState = ToolState>(\n flags: FeatureFlags,\n metadata: Omit<ToolEntry<S>, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>,\n): ToolEntry<S> => ({\n ...metadata,\n description: recallPlacesDescription,\n inputSchema: recallPlacesSchema,\n outputSchema: buildRecallPlacesOutputSchema(flags),\n execute: buildExecuteRecallPlaces(flags) as ToolEntry<S>['execute'],\n});\n\nconst recallPlacesMetadata = {\n classificationPrompt:\n 'List/inspect places entries in state — pick valid IDs before any `placesEntryIDs` tool (processData, analyseData, updatePlacesDisplay).',\n tags: ['place', 'location'],\n examples: ['recallPlaces()', 'recallPlaces({ id: \"places-0\" })'],\n examplePrompts: [\n 'What places did I search for?',\n 'List every places entry with its ID',\n 'Which IDs can I pass to processData or updatePlacesDisplay?',\n 'Recall the bakeries I discovered earlier',\n 'What is in entry places-2?',\n ],\n relatedTools: ['discoverPlaces', 'locatePlace', 'reverseGeocode', 'updatePlacesDisplay'],\n dependsOn: ['locatePlace', 'discoverPlaces'],\n} satisfies Omit<ToolEntry, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>;\n\n/**\n * Builder for the recallPlaces default tool entry.\n */\nexport const recallPlacesBuilder: ToolEntryBuilder = (options) =>\n buildRecallPlacesEntry(options.featureFlags ?? {}, recallPlacesMetadata);\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallRangesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"ranges-0\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n originCount: z.number(),\n});\n\nconst rangeSummarySchema = z.object({\n origin: z.object({\n query: z.string().optional(),\n position: z.tuple([z.number(), z.number()]),\n }),\n budgets: z.array(z.object({ type: z.string(), value: z.number() })),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n ranges: z.array(rangeSummarySchema),\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several reachable-range entries render at once; ' +\n '`single` enforces \"at most one entry on the map\" — switching to it auto-clears non-latest entries.',\n );\n\nexport const recallRangesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallRangesDescription =\n 'List stored reachable-range results, or retrieve one by `id` (returns each origin and its budgets). ' +\n 'Multi-origin entries surface every origin under `ranges`. Never guess IDs — list first. No service call.';\n\nexport const executeRecallRanges = async (\n params: z.infer<typeof recallRangesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallRangesOutputSchema>> => {\n const { id } = params;\n const entryMode = state.ranges.entryMode;\n\n if (!id) {\n const entries = [...state.ranges.entries].reverse().map(({ id, label, timestamp, data: ranges }) => ({\n id,\n label,\n timestamp,\n originCount: ranges.length,\n }));\n return { entries, entryMode };\n }\n\n const entry = state.ranges.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n ranges: entry.data.map((r) => ({\n origin: { ...r.origin, position: r.origin.position as [number, number] },\n budgets: r.budgets,\n })),\n entryMode,\n };\n};\n","import { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { summarizeRoutes, summarizeWaypoint, waypointSummarySchema } from '../../utils';\nimport { costModelSchema, whenSchema } from '../services';\nimport { routesOutputSchema, toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallRoutesSchema = z.object({\n id: z.string().optional().describe('Entry ID to retrieve (e.g. \"routes-1\"). Omit to list all entries.'),\n});\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n shown: z.boolean().describe('Whether this entry is currently rendered on the map.'),\n});\n\nconst routeParamsSchema = z.object({\n maxAlternatives: z.number().optional(),\n costModel: costModelSchema.optional(),\n when: whenSchema.optional(),\n});\n\nconst detailSchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n routes: routesOutputSchema,\n waypoints: z.array(\n z.object({\n position: z.array(z.number()).length(2).describe('[longitude, latitude]'),\n }),\n ),\n params: routeParamsSchema,\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several routes render at once; ' +\n '`single` enforces \"at most one route on the map\" — switching to it auto-clears non-latest entries.',\n );\n\nexport const recallRoutesOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallRoutesDescription =\n 'List calculated routes from session history, or retrieve one by `id` (route details + params). ' +\n 'IDs are stable and accepted by updateRoutesDisplay. Never guess IDs — list first. No service call.';\n\nexport const executeRecallRoutes = async (\n params: z.infer<typeof recallRoutesSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallRoutesOutputSchema>> => {\n const { id } = params;\n const entryMode = state.routing.entryMode;\n const shownEntryIds = state.routing.shownEntryIds;\n\n if (!id) {\n const entries = [...state.routing.entries]\n .reverse()\n .map(({ id, label, timestamp }) => ({ id, label, timestamp, shown: shownEntryIds.has(id) }));\n return { entries, entryMode };\n }\n\n const entry = state.routing.entries.find((e) => e.id === id);\n if (!entry) {\n return { error: `No entry found with id \"${id}\"` };\n }\n\n const summarized = summarizeRoutes(entry.data);\n const waypoints = entry.waypoints\n .map((wp) => summarizeWaypoint(wp))\n .filter((wp): wp is z.infer<typeof waypointSummarySchema> => wp !== null)\n .map((wp) => ({ position: wp.position ?? [0, 0] }));\n\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n routes: summarized,\n waypoints,\n params: entry.params as z.infer<typeof routeParamsSchema>,\n entryMode,\n };\n};\n","import type { StandardStyleID, StyleInput } from '@tomtom-org/maps-sdk/map';\n\n/**\n * The standard style a `TomTomMap.getStyleInput()` result loads — `standardLight` when a standard style\n * names none — or `undefined` for a custom style.\n * @ignore\n */\nexport const standardStyleIdOf = (style: StyleInput | undefined): StandardStyleID | undefined => {\n if (typeof style === 'string') return style;\n\n return style?.type === 'standard' ? (style.id ?? 'standardLight') : undefined;\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { isoDate } from '../../utils';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const recallTrafficAreaAnalyticsSchema = z.object({\n id: z\n .string()\n .optional()\n .describe('Traffic-area-analytics entry id to inspect (e.g. \"tta-0\"). Omit to list every analytics entry.'),\n});\n\nconst entryModeSchema = z\n .enum(['single', 'multiple'])\n .describe(\n 'Display policy: `multiple` (default) lets several analytics entries render at once; ' +\n '`single` keeps only the latest entry — switching to it auto-clears the others.',\n );\n\nconst indexEntrySchema = z.object({\n id: z.string(),\n label: z.string(),\n timestamp: z.number(),\n regionCount: z.number().describe('Number of analysed regions (features) in this entry.'),\n metrics: z\n .array(z.string())\n .describe('Traffic metrics included in the report (e.g. [\"congestionLevel\", \"speed\"]).'),\n shown: z.boolean().describe('True while this entry is rendered on the map.'),\n});\n\nconst detailSchema = indexEntrySchema.extend({\n dateRange: z\n .object({ start: z.string(), end: z.string() })\n .describe('Analysis period (ISO dates) — empty strings when the entry carries no date metadata.'),\n regions: z.array(z.string()).describe('Names of the analysed regions that have one, in feature order.'),\n heatmap: z.boolean().describe('Whether tile-level heatmap data is included.'),\n});\n\nexport const recallTrafficAreaAnalyticsOutputSchema = z.union([\n z.object({ entries: z.array(indexEntrySchema), entryMode: entryModeSchema }),\n detailSchema.extend({ entryMode: entryModeSchema }),\n toolErrorSchema,\n]);\n\nexport const recallTrafficAreaAnalyticsDescription =\n 'List stored traffic-area-analytics entries, or inspect one by `id`. No args → index (id, label, regionCount, ' +\n 'metrics, shown). Pass `id` → that entry plus its date range, region names, and heatmap flag. Pass an id to ' +\n '`trafficAreaAnalyticsEntryIDs` on analyseData / processData to compute over the metric values. Never guess ' +\n 'IDs — list first. No service call.';\nexport const executeRecallTrafficAreaAnalytics = async (\n params: z.infer<typeof recallTrafficAreaAnalyticsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof recallTrafficAreaAnalyticsOutputSchema>> => {\n const slice = state.trafficAreaAnalytics;\n const entryMode = slice.entryMode;\n\n if (!params.id) {\n // Newest-first, matching every other recall index.\n const entries = [...slice.entries].reverse().map((entry) => ({\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n regionCount: entry.data.features.length,\n metrics: entry.data.properties.metrics,\n shown: slice.shownEntryIds.has(entry.id),\n }));\n return { entries, entryMode };\n }\n\n const entry = slice.findById(params.id);\n if (!entry) {\n return {\n error: `No traffic-area-analytics entry with id \"${params.id}\". Call recallState({ kind: \"trafficAreaAnalytics\" }) to list available IDs.`,\n };\n }\n\n const properties = entry.data.properties;\n return {\n id: entry.id,\n label: entry.label,\n timestamp: entry.timestamp,\n regionCount: entry.data.features.length,\n metrics: properties.metrics,\n shown: slice.shownEntryIds.has(entry.id),\n dateRange: { start: isoDate(properties.startDate), end: isoDate(properties.endDate) },\n regions: entry.data.features.flatMap((feature) => feature.properties.name ?? []),\n heatmap: properties.heatmap,\n entryMode,\n };\n};\n","/**\n * @module agent-toolkit-tools\n *\n * `recallState` — the single read-only window onto session state. Three modes:\n * - no args → a session SNAPSHOT (places/routes/ranges index + map style / POIs / traffic configs);\n * - `kind` only → that kind's entry INDEX (ids + labels + per-kind summary fields);\n * - `kind` + `id` → that entry's DETAIL (full per-kind summary).\n *\n * Replaces the old per-kind `recallPlaces` / `recallRoutes` / `recallRanges` / `recallByod` /\n * `recallGeometries` tools — their logic lives on as internal executors this tool dispatches to.\n */\n\nimport { filterShowModes } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { DataEntryKind } from '../../state';\nimport type { FeatureFlags, ToolEntry, ToolEntryBuilder, ToolState } from '../../types';\nimport { type GeometriesId, geometriesIdSchema } from '../shared';\nimport { standardStyleIdOf } from '../shared/standard-style-id';\nimport { toolErrorSchema } from '../shared-output-schemas';\nimport { executeRecallByod, recallByodOutputSchema } from './recall-byod';\nimport { executeRecallGeometries, recallGeometriesOutputSchema } from './recall-geometries';\nimport { executeRecallIncidents, recallIncidentsOutputSchema } from './recall-incidents';\nimport { buildExecuteRecallPlaces, buildRecallPlacesOutputSchema } from './recall-places';\nimport { executeRecallRanges, recallRangesOutputSchema } from './recall-ranges';\nimport { executeRecallRoutes, recallRoutesOutputSchema } from './recall-routes';\nimport {\n executeRecallTrafficAreaAnalytics,\n recallTrafficAreaAnalyticsOutputSchema,\n} from './recall-traffic-area-analytics';\n\nconst idLabelSchema = z.object({\n id: z.string(),\n label: z.string(),\n});\n\nconst entryModeSchema = z.enum(['single', 'multiple']);\n\n/** The no-args session snapshot — the original `recallState` shape. */\nconst snapshotSchema = z.object({\n places: z.array(idLabelSchema).describe('Places history entries — newest first.'),\n routes: z.array(idLabelSchema).describe('Routes history entries — newest first.'),\n ranges: z.array(idLabelSchema).describe('Reachable-range history entries — newest first.'),\n entryModes: z\n .object({ places: entryModeSchema, routes: entryModeSchema, ranges: entryModeSchema })\n .describe(\n 'Per-slice display policy. `multiple` (default) lets several entries render at once; ' +\n '`single` keeps only the latest entry — set via setEntryMode tools.',\n ),\n mapStyle: z\n .object({\n id: z.string().optional().describe('Standard style id (e.g. \"standardLight\").'),\n })\n .nullable()\n .describe('Current map style. `null` when the map has no style assigned yet.'),\n mapPOIs: z\n .object({\n visible: z.boolean().optional(),\n filterMode: z.enum(filterShowModes).optional(),\n categories: z.array(z.string()).optional(),\n })\n .nullable()\n .describe('Built-in POI overlay config. `null` when the POIsModule has not been initialised in this session.'),\n traffic: z\n .object({\n flow: z\n .object({ visible: z.boolean().optional() })\n .nullable()\n .describe('Traffic-flow layer config. `null` when the module is uninitialised.'),\n incidents: z\n .object({ visible: z.boolean().optional() })\n .nullable()\n .describe('Traffic-incidents layer config. `null` when the module is uninitialised.'),\n })\n .describe('Real-time traffic visualization configs.'),\n});\n\n/** Build the flag-aware output schema — snapshot OR any per-kind index/detail OR an error. */\nexport const buildRecallStateOutputSchema = (flags: FeatureFlags) =>\n z.union([\n snapshotSchema,\n buildRecallPlacesOutputSchema(flags),\n recallRoutesOutputSchema,\n recallRangesOutputSchema,\n recallByodOutputSchema,\n recallGeometriesOutputSchema,\n recallIncidentsOutputSchema,\n recallTrafficAreaAnalyticsOutputSchema,\n toolErrorSchema,\n ]);\n\n/** Output schema built with no feature flags set. */\nexport const recallStateOutputSchema = buildRecallStateOutputSchema({});\n\n// LLM-facing kinds recallState can list/inspect — every entry-bearing slice. DERIVED from the\n// canonical entry-kind vocabulary (`DataEntryKind`, the `dataEntries` superset) rather than\n// re-spelled: the only divergence is that the `customGeometries` slice is surfaced under the shorter\n// LLM-facing name `geometries`. Deriving via `Exclude` means adding/renaming a `DataEntryKind`\n// propagates here (or trips the `satisfies` below) instead of drifting silently. The runtime array is\n// still spelled out because `z.enum` needs the literal; `satisfies` pins it to the derived type.\nexport type RecallableKind = Exclude<DataEntryKind, 'customGeometries'> | 'geometries';\nconst recallableKinds = [\n 'places',\n 'routes',\n 'ranges',\n 'geometries',\n 'byod',\n 'incidents',\n 'trafficAreaAnalytics',\n] as const satisfies readonly RecallableKind[];\n\n/** Tool schema for recall-state. */\nexport const recallStateSchema = z.object({\n kind: z\n .enum(recallableKinds)\n .optional()\n .describe(\n \"Narrow to ONE entry kind: list that kind's entries (omit `id`) or inspect one (`id`). \" +\n 'Omit `kind` for a full session snapshot (places/routes/ranges index + map style / POIs / traffic).',\n ),\n id: z\n .union([z.string(), geometriesIdSchema])\n .optional()\n .describe(\n 'Entry id to inspect in detail — requires `kind`. A plain id for places/routes/ranges/byod/incidents/' +\n 'trafficAreaAnalytics (e.g. \"places-3\"); a tagged `{ kind, id }` for geometries. Omit to list the ' +\n \"kind's entries.\",\n ),\n});\n\nexport const recallStateDescription =\n 'Read-only window onto session state — the ONE tool for \"what is already loaded?\" and \"which id can I pass ' +\n 'to a `*EntryIDs` follow-up?\". No args → session snapshot (places/routes/ranges entries + per-slice ' +\n \"`entryMode` + map style / POIs / traffic configs). `kind` → that kind's entry index (places | routes | \" +\n \"ranges | geometries | byod | incidents | trafficAreaAnalytics). `kind` + `id` → that entry's full detail \" +\n '(places coordinates, route legs, range polygons, BYOD profile, geometry features, incident delay breakdown, ' +\n 'analytics metrics). ALWAYS call before referencing past entries; never guess ids. No service call.';\n\nconst snapshot = (state: ToolState): z.infer<typeof snapshotSchema> => {\n // Newest-first across each history. Same ordering the per-kind indexes use, so the LLM\n // doesn't have to re-sort.\n const byNewest = <T extends { timestamp: number }>(arr: readonly T[]): T[] =>\n [...arr].sort((a, b) => b.timestamp - a.timestamp);\n\n const places = byNewest(state.places.entries).map(({ id, label }) => ({ id, label }));\n const routes = byNewest(state.routing.entries).map(({ id, label }) => ({ id, label }));\n const ranges = byNewest(state.ranges.entries).map(({ id, label }) => ({ id, label }));\n\n const styleId = standardStyleIdOf(state.baseMap.ttMap.getStyleInput());\n const mapStyle = styleId === undefined ? null : { id: styleId };\n\n // Read POIs / traffic configs through the cached module accessors so we\n // don't lazy-init modules the user never touched (an uninitialised\n // module reports as `null`, which the LLM should read as \"default state,\n // nothing modified\").\n const poisModule = state.mapPOIs.poisModule;\n const poisConfig = poisModule?.getConfig();\n const mapPOIs = poisConfig\n ? {\n ...(typeof poisConfig.visible === 'boolean' && { visible: poisConfig.visible }),\n ...(poisConfig.filters?.categories?.show && {\n filterMode: poisConfig.filters.categories.show,\n }),\n ...(poisConfig.filters?.categories?.values && {\n categories: [...poisConfig.filters.categories.values],\n }),\n }\n : null;\n\n const flowConfig = state.trafficTiles.trafficFlowModule?.getConfig();\n const incidentsConfig = state.trafficTiles.trafficIncidentsModule?.getConfig();\n const traffic = {\n flow: flowConfig ? { ...(typeof flowConfig.visible === 'boolean' && { visible: flowConfig.visible }) } : null,\n incidents: incidentsConfig\n ? { ...(typeof incidentsConfig.visible === 'boolean' && { visible: incidentsConfig.visible }) }\n : null,\n };\n\n const entryModes = {\n places: state.places.entryMode,\n routes: state.routing.entryMode,\n ranges: state.ranges.entryMode,\n };\n\n return { places, routes, ranges, entryModes, mapStyle, mapPOIs, traffic };\n};\n\n/**\n * Build the recall-state executor for a given {@link FeatureFlags} bag (flags propagate to the\n * places detail's experimental fields). Dispatches to the per-kind executors when `kind` is set.\n */\nexport const buildExecuteRecallState =\n (flags: FeatureFlags) =>\n async (\n params: z.infer<typeof recallStateSchema>,\n state: ToolState,\n ): Promise<z.infer<ReturnType<typeof buildRecallStateOutputSchema>>> => {\n const { kind, id } = params;\n if (!kind) return snapshot(state);\n switch (kind) {\n case 'places':\n return buildExecuteRecallPlaces(flags)({ id: id as string | undefined }, state);\n case 'routes':\n return executeRecallRoutes({ id: id as string | undefined }, state);\n case 'ranges':\n return executeRecallRanges({ id: id as string | undefined }, state);\n case 'byod':\n return executeRecallByod({ id: id as string | undefined }, state);\n case 'incidents':\n return executeRecallIncidents({ id: id as string | undefined }, state);\n case 'trafficAreaAnalytics':\n return executeRecallTrafficAreaAnalytics({ id: id as string | undefined }, state);\n case 'geometries':\n return executeRecallGeometries({ id: id as GeometriesId | undefined }, state);\n }\n };\n\n/** Executor built with no feature flags set. */\nexport const executeRecallState = buildExecuteRecallState({});\n\nconst recallStateMetadata = {\n classificationPrompt:\n 'Read session state: list / inspect loaded entries (places, routes, ranges, geometries, byod, incidents, ' +\n 'trafficAreaAnalytics) and pick valid ids before any `*EntryIDs` follow-up (analyseData, processData, ' +\n 'updatePlacesDisplay, …), or get a full snapshot of what is loaded. No args = snapshot; `kind` = that ' +\n \"kind's index; `kind`+`id` = detail.\",\n tags: ['state', 'recall', 'orientation'],\n examples: [\n 'recallState()',\n 'recallState({ kind: \"places\" })',\n 'recallState({ kind: \"places\", id: \"places-0\" })',\n 'recallState({ kind: \"routes\" })',\n 'recallState({ kind: \"geometries\", id: { kind: \"customGeometries\", id: \"buffered-routes\" } })',\n ],\n examplePrompts: [\n 'What is currently loaded in my session?',\n 'What places did I search for?',\n 'List my routes with their IDs',\n 'What is in entry places-2?',\n 'Which IDs can I pass to analyseData or processData?',\n 'Show the BYOD layers I added',\n ],\n relatedTools: ['analyseData', 'processData', 'updatePlacesDisplay', 'updateRoutesDisplay', 'updateByodDisplay'],\n dependsOn: [\n 'discoverPlaces',\n 'locatePlace',\n 'reverseGeocode',\n 'setRoute',\n 'findReachableAreas',\n 'addByodSource',\n 'processData',\n 'getTrafficAreaAnalytics',\n ],\n} satisfies Omit<ToolEntry, 'description' | 'inputSchema' | 'outputSchema' | 'execute'>;\n\n/** Builder for the recallState default tool entry (flag-aware places detail). */\nexport const recallStateBuilder: ToolEntryBuilder = (options) => {\n const flags = options.featureFlags ?? {};\n return {\n ...recallStateMetadata,\n description: recallStateDescription,\n inputSchema: recallStateSchema,\n outputSchema: buildRecallStateOutputSchema(flags),\n execute: buildExecuteRecallState(flags) as ToolEntry['execute'],\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst DEFAULT_STYLE_ID = 'standardLight';\n\n// Slices wiped by `executeResetState`. Kept as a single source of truth so the\n// output schema's enum and the returned `cleared` array can't drift apart.\nconst CLEARED_SLICES = [\n 'places',\n 'routes',\n 'ranges',\n 'customGeometries',\n 'trafficAreaAnalytics',\n 'trafficIncidents',\n 'byod',\n 'analyses',\n 'trackers',\n 'mapPOIs',\n 'trafficFlow',\n 'mapStyle',\n] as const;\n\n/** Output schema for reset-state. */\nexport const resetStateOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z.array(z.enum(CLEARED_SLICES)).describe('Slices that were wiped / reset to defaults.'),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for reset-state. */\nexport const resetStateSchema = z.object({});\n\nexport const resetStateDescription =\n 'Wipe the session: drops every places/routes/ranges entry + their map renders, resets POIs + traffic ' +\n 'configs to defaults, reverts the map style to \"standardLight\". Destructive — use only when the user ' +\n 'explicitly asks for a clean slate (\"start over\", \"reset\", \"clear everything\").';\n\nexport const executeResetState = async (\n _params: z.infer<typeof resetStateSchema>,\n state: ToolState,\n): Promise<z.infer<typeof resetStateOutputSchema>> => {\n try {\n // 1. Visually clear every initialised module BEFORE the in-memory\n // state resets. With per-entry modules, we walk every entry on\n // every slice and clear whatever it has wired up. Without this\n // pass the MapLibre sources keep the last-rendered features even\n // after the entry history is gone.\n await state.places.clearShownEntries();\n for (const entry of state.places.entries) {\n await entry._modules?.geometries?.clear();\n }\n\n for (const entry of state.routing.entries) {\n if (entry._module) {\n await entry._module.clearRoutes();\n await entry._module.clearWaypoints();\n }\n }\n\n for (const entry of state.ranges.entries) {\n await entry._modules?.ranges?.clear();\n await entry._modules?.geometries?.clear();\n await entry._modules?.places?.clear();\n }\n\n for (const entry of state.trafficAreaAnalytics.entries) {\n await entry._module?.clear();\n }\n\n // 2. POIs / traffic-flow / traffic-incidents — revert visualization\n // configs to defaults via the SDK base's `resetConfig()`. Only\n // touch modules that were actually initialised in this session;\n // hitting the lazy getter would create the module just to reset it.\n state.mapPOIs.poisModule?.resetConfig();\n state.trafficTiles.trafficFlowModule?.resetConfig();\n state.trafficTiles.trafficIncidentsModule?.resetConfig();\n\n // 3. Drop history + module references and re-emit change events so\n // the UI lights up empty. Every entry-owning slice with a `reset()`\n // needs to be wiped — missing one leaves stale entries that show up\n // in subsequent `recallState` / `recall*` calls even after a reset.\n state.places.reset();\n state.routing.reset();\n state.ranges.reset();\n state.customGeometries.reset();\n state.trafficAreaAnalytics.reset();\n state.trafficIncidents.reset();\n state.byod.reset();\n\n // Session-state owners. Resetting the entry slices above orphan-GCs their jobs, but the\n // analyses records, tracker rows, and the durable tracker alert log only clear on an explicit\n // reset — without these a \"start over\" leaves a stale alert feed and analysis history behind.\n // The engine drops any remaining jobs; its slice subscriptions persist (harmless when empty).\n state.analyses.reset();\n state.trackers.reset();\n state.engine.reset();\n\n // 4. Map style: revert to the canonical default. `resetState: true` so the SDK modules\n // re-bind to it with default configuration and nothing shown, and any sources/layers\n // added after the last `setStyle` call are dropped along with the previous style.\n await state.baseMap.ttMap.setStyle(DEFAULT_STYLE_ID, { resetState: true });\n\n return {\n success: true,\n cleared: [...CLEARED_SLICES],\n };\n } catch (error) {\n return {\n error: `Failed to reset state: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type CustomGeoJSONLayerSpec, mapStyleLayerIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { showByodOnMap, showByodSchema, shownByodSchema } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// Loose, structural mirror of `CustomGeoJSONLayerSpec` (a MapLibre layer spec minus `source`/`id`).\n// We deliberately accept raw MapLibre `paint` / `layout` / `filter` as pass-through bags rather than\n// re-typing the full MapLibre union here: the model gets the same expressive power it has in the SDK,\n// and invalid specs are caught at render time (MapLibre throws → we return a semantic tool error).\nconst byodLayerTypeSchema = z\n .enum(['circle', 'line', 'fill', 'symbol', 'heatmap', 'fill-extrusion'])\n .describe(\n 'MapLibre layer type. Must suit the geometry: Point → circle/symbol/heatmap, Line → line, Polygon → fill/fill-extrusion.',\n );\n\nconst byodLayerSpecSchema = z\n .object({\n type: byodLayerTypeSchema,\n paint: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'MapLibre PAINT properties — how the feature LOOKS: colour, opacity, halo, blur (any `*-color` / ' +\n '`*-opacity` / `*-halo-*`), plus `circle-radius` and `line-width`. Values may be data-driven ' +\n 'expressions referencing feature properties seen in the entry `profile` (e.g. ' +\n '`[\"interpolate\", [\"linear\"], [\"get\", \"revenue\"], 0, 4, 1000, 20]`).',\n ),\n layout: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n 'MapLibre LAYOUT properties — WHAT/WHERE the feature is: `visibility`, placement/anchor/offset, and ' +\n \"a symbol's text/icon content + glyph sizing (`text-field`, `text-font`, `text-size`, \" +\n '`icon-image`, `icon-size`). Everything else — all colour, opacity and halo — is `paint`. A ' +\n '`symbol` splits its text styling this way (`text-field`/`text-size` here, ' +\n '`text-color`/`text-halo-*` in `paint`); mixing them up makes MapLibre reject the layer with ' +\n '`unknown property`.',\n ),\n filter: z\n .unknown()\n .optional()\n .describe('Optional MapLibre filter expression restricting which features this layer renders.'),\n id: z.string().optional().describe('Stable layer id. Auto-generated when omitted.'),\n beforeID: z\n .string()\n .optional()\n .describe(\n 'Style layer id to draw this layer BELOW; omit to draw on top of everything. Any layer ' +\n \"already on the map is allowed (e.g. another entry's layer), but prefer these base-map \" +\n `anchors: '${mapStyleLayerIDs.lowestLabel}' for fill/line/heatmap, which keeps every map ` +\n `label readable above the data, or '${mapStyleLayerIDs.lowestRoadLine}' when the road ` +\n 'network should stay drawn over a fill; omit for point layers. An id that is not in the ' +\n 'current style silently drops the layer.',\n ),\n })\n .describe('A single MapLibre layer rendering the entry. Pick `type` by geometry; drive `paint` from the profile.');\n\nexport const setByodLayersSchema = z.object({\n byodEntryId: z.string().describe('Id of the BYOD entry to restyle (from `addByodSource` or `recallState`).'),\n layers: z\n .array(byodLayerSpecSchema)\n .min(1)\n .describe(\n 'Replacement MapLibre layer specs, applied in array order (first drawn underneath). Replaces the ' +\n \"entry's current layers wholesale. Use the entry `profile` (geometry types + per-property \" +\n 'types/coverage/examples) to choose layer `type`s and data-driven `paint`.',\n ),\n show: showByodSchema\n .optional()\n .describe(\n 'Render the entry after restyling. Omit when the entry is already shown — the new layers apply live ' +\n 'in place. Pass it to render a hidden entry (or to re-fit the camera via `zoomMode`).',\n ),\n});\n\nexport const setByodLayersOutputSchema = z.union([\n z.object({\n byodEntryId: z.string(),\n label: z.string(),\n layerCount: z.number().describe('Number of layers now configured on the entry.'),\n layerTypes: z.array(z.string()).describe('The `type` of each configured layer, in draw order.'),\n shown: shownByodSchema\n .optional()\n .describe('Render report — present only when `show` was provided (or the entry re-rendered live).'),\n }),\n toolErrorSchema,\n]);\n\nexport const setByodLayersDescription =\n 'Replace the MapLibre layers a BYOD entry renders under, styling customer data from its `profile` ' +\n '(geometry types + per-property types/coverage/examples). Operates on an entry already in state ' +\n '(`addByodSource` / `recallState`); the supplied `layers` replace the current set wholesale, applying live in ' +\n 'place if the entry is shown. Pass `show` to render a hidden entry or re-fit the camera. Invalid specs leave ' +\n \"the entry unchanged and return an error to retry, so the entry's styling is never lost. \" +\n 'This is the SECOND STEP OF EVERY BYOD INGEST and the ONLY way a BYOD entry becomes visible — `addByodSource` ' +\n 'creates the entry with NO layers — so always call it to pick layers that fit the detected schema: graduate ' +\n '`circle-radius`/colour by a numeric field (`interpolate`), colour a `fill`/`line` by a categorical field ' +\n '(`match`), or switch dense points to `symbol`; even with no dominant property, choose legible layers for the ' +\n 'geometry. ' +\n 'STYLE FOR LEGIBILITY ON THE TOMTOM BASEMAP (no need to inspect the style): translucent `fill` ' +\n '(`fill-opacity` ~0.3–0.5) with a contrasting `fill-outline-color`; `symbol` text with a white halo ' +\n \"(`text-halo-color: '#ffffff'` + `text-halo-width` ~1.5 in `paint`, while `text-field`/`text-size` ~12 go in \" +\n '`layout`); `circle` with a thin white stroke (`circle-stroke-color`/`-width`); a small, high-contrast palette ' +\n 'that avoids fully opaque black or white fills.';\n\nexport const executeSetByodLayers = async (\n params: z.infer<typeof setByodLayersSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setByodLayersOutputSchema>> => {\n const entry = state.byod.findById(params.byodEntryId);\n if (!entry) {\n return {\n error: `No BYOD entry with id \"${params.byodEntryId}\". Call recallState to list available IDs.`,\n };\n }\n\n // Snapshot the current layers so an invalid spec can be rolled back — applyConfig mutates the\n // live module before MapLibre may reject a paint expression, so a failed restyle must not leave\n // the entry half-styled.\n const previousLayers = entry.layers;\n // The structural schema is intentionally looser than the strict MapLibre union; cast at the\n // boundary and lean on MapLibre's own validation (surfaced as a tool error below).\n const nextLayers = params.layers.map(({ beforeID, ...layer }) =>\n beforeID ? { ...layer, beforeLayerConfig: { layerID: beforeID } } : layer,\n ) as CustomGeoJSONLayerSpec[];\n\n try {\n state.byod.setEntryLayers(params.byodEntryId, nextLayers);\n } catch (error) {\n // Roll back to the known-good previous specs. setEntryLayers re-applies them to the live\n // module; if the module itself is now wedged, fall back to restoring just the stored specs\n // so the entry stays internally consistent and the original error is not masked.\n try {\n state.byod.setEntryLayers(params.byodEntryId, previousLayers);\n } catch {\n entry.layers = previousLayers;\n }\n return {\n error: `Failed to apply layers to \"${params.byodEntryId}\": ${\n error instanceof Error ? error.message : String(error)\n }. The entry keeps its previous layers — fix the layer specs and retry.`,\n };\n }\n\n let shown: z.infer<typeof shownByodSchema> | undefined;\n if (params.show) {\n try {\n shown = await showByodOnMap(state, params.byodEntryId, params.show);\n } catch (error) {\n return {\n error: `Layers applied to \"${params.byodEntryId}\" but rendering failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n };\n }\n }\n\n return {\n byodEntryId: params.byodEntryId,\n label: entry.label,\n layerCount: nextLayers.length,\n layerTypes: nextLayers.map((layer) => layer.type),\n ...(shown && { shown }),\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport { ENTRY_MODE_SLICE_NAMES, type EntryModeSlice, type EntryModeSliceName } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// `z.enum` needs a non-empty tuple. ENTRY_MODE_SLICE_NAMES is derived from a\n// `Record<EntryModeSliceName, true>` check at the type level, so the runtime\n// list is always non-empty whenever the union has at least one member.\nconst sliceSchema = z.enum(ENTRY_MODE_SLICE_NAMES as unknown as [EntryModeSliceName, ...EntryModeSliceName[]]);\nconst modeSchema = z.enum(['single', 'multiple']);\n\n/** Output schema for the setEntryMode tool. */\nexport const setEntryModeOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n slice: sliceSchema,\n mode: modeSchema,\n droppedEntryIds: z\n .array(z.string())\n .describe('Entry IDs removed from history because the new mode is `single` and they were not the latest.'),\n }),\n toolErrorSchema,\n]);\n\n/** Input schema for setEntryMode. */\nexport const setEntryModeSchema = z.object({\n slice: sliceSchema.describe(\n 'Which entry-owning slice to reconfigure. Names match the keys on the agent state exactly — ' +\n '`routing` (not `routes`), `customGeometries` (not `geometries`), and so on.',\n ),\n mode: modeSchema.describe(\n '`multiple` (default) lets several entries render at once; ' +\n '`single` keeps only the latest entry — switching to it auto-clears every older entry.',\n ),\n});\n\nexport const setEntryModeDescription =\n 'Switch the display policy of one entry-owning slice between `multiple` and `single`. ' +\n `Supported slices: ${ENTRY_MODE_SLICE_NAMES.join(', ')}. ` +\n '`single` ⇒ at most one entry on the map, and any older entries get dropped from history immediately. ' +\n 'Use when the user asks for a \"clean view\" / \"only show one at a time\" or wants to compare overlays again.';\n\nexport const executeSetEntryMode = async (\n params: z.infer<typeof setEntryModeSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setEntryModeOutputSchema>> => {\n const { slice, mode } = params;\n try {\n // Index into state with the slice name — names match keys exactly, so\n // there is no `case 'routes': state.routing.…` style mismatch to maintain.\n const target = state[slice] as EntryModeSlice;\n\n // Snapshot ids the slice was about to drop so we can report them back —\n // setEntryMode runs the trim internally and emits an event, but the\n // caller benefits from a concrete list (e.g. to mention by id in the\n // chat reply).\n const beforeIds = target.entries.map((e) => e.id);\n\n await target.setEntryMode(mode);\n\n const afterIds = new Set(target.entries.map((e) => e.id));\n const droppedEntryIds = beforeIds.filter((id) => !afterIds.has(id));\n\n return { success: true, slice, mode, droppedEntryIds };\n } catch (error) {\n return {\n error: `Failed to set entry mode: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n *\n * `setTrafficIncidentsMonitor` — one toggle that arms or disarms background polling on an existing\n * incidents entry. Merges the former start/stop pair: `enabled: true` starts the poll (re-fetch the\n * entry's bbox + filters every interval, replacing its data; registered analyses re-run each tick),\n * `enabled: false` stops it. `getTrafficIncidents` still auto-monitors a fresh shown fetch, so this is\n * only needed to (re)arm an entry loaded earlier / loaded with `monitor: false`, or to pause one.\n */\n\nimport { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { withAgentToolkitHeaders } from '../shared';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const setTrafficIncidentsMonitorOutputSchema = z.union([\n z.object({\n incidentsEntryID: z.string(),\n enabled: z.boolean().describe('The monitor is now polling (true) or stopped (false).'),\n alreadyInState: z\n .boolean()\n .describe('True when the monitor was already in the requested state — this call was a no-op.'),\n }),\n toolErrorSchema,\n]);\n\nexport const setTrafficIncidentsMonitorSchema = z.object({\n incidentsEntryID: z.string().describe('Id of the incidents entry to monitor (returned by `getTrafficIncidents`).'),\n enabled: z\n .boolean()\n .describe(\n 'true → start background polling (re-fetch + re-run registered analyses each tick); false → stop it. ' +\n 'The entry keeps its last data either way.',\n ),\n intervalMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n 'Polling interval in milliseconds when enabling (default 60000). Ignored when disabling, and not ' +\n 'applied to an already-running monitor — disable then re-enable to change it.',\n ),\n});\n\nexport const setTrafficIncidentsMonitorDescription =\n 'Start or stop the background monitor for an EXISTING incidents entry (`enabled: true` / `false`). When ' +\n \"enabled, re-fetches the entry's bbox + filters every interval (default 60s) and replaces its data; the first \" +\n 'tick fires immediately, and any registered analyses re-run each tick. When disabled, polling stops and the ' +\n 'entry stays in state with its last data (use removeEntry to drop it). ' +\n '\\n\\n' +\n 'getTrafficIncidents already monitors a fresh shown fetch by default, so only enable here for an entry loaded ' +\n 'on an EARLIER turn (\"keep that area updated\") or one loaded with monitor: false. Enabling is idempotent on a ' +\n 'running monitor (no-op, alreadyInState: true); a new intervalMs is NOT applied to a running monitor — disable ' +\n 'then enable to change it. Only a displayed entry can be monitored: a show:false load is a one-shot snapshot.';\n\nexport const executeSetTrafficIncidentsMonitor = async (\n params: z.infer<typeof setTrafficIncidentsMonitorSchema>,\n state: ToolState,\n): Promise<z.infer<typeof setTrafficIncidentsMonitorOutputSchema>> => {\n const { incidentsEntryID, enabled, intervalMs } = params;\n const entry = state.trafficIncidents.entries.find((e) => e.id === incidentsEntryID);\n if (!entry) {\n return { error: `No incidents entry with id \"${incidentsEntryID}\". Call getTrafficIncidents first.` };\n }\n\n if (!enabled) {\n const wasRunning = state.trafficIncidents.isMonitored(incidentsEntryID);\n state.trafficIncidents.stopMonitoring(incidentsEntryID);\n return { incidentsEntryID, enabled: false, alreadyInState: !wasRunning };\n }\n\n // Monitoring is only for the live, displayed area: a tick refreshes data and re-runs analyses but\n // repaints nothing for a hidden entry, so a monitored-but-invisible entry would silently poll forever\n // and break the text-and-map-match contract. A `show: false` load is a one-shot snapshot by design.\n if (!state.trafficIncidents.shownEntryIds.has(incidentsEntryID)) {\n return {\n error:\n `Entry \"${incidentsEntryID}\" isn't displayed on the map, so it can't be monitored. ` +\n 'Monitoring keeps the live shown area current; a show:false load is a one-shot snapshot. ' +\n 'Re-fetch the area with getTrafficIncidents (show: true, the default) and monitor that entry.',\n };\n }\n\n const alreadyRunning = state.trafficIncidents.isMonitored(incidentsEntryID);\n\n // Capture the entry's filters in the fetcher closure so every tick (not just the first) re-issues the\n // same category / time-validity filter the entry was loaded with.\n const { bbox, categoryFilter, timeValidityFilter } = entry.params;\n const filters = {\n ...(categoryFilter && { categoryFilter }),\n ...(timeValidityFilter && { timeValidityFilter }),\n };\n\n state.trafficIncidents.startMonitoring(\n incidentsEntryID,\n { bbox, capturedAt: entry.timestamp, label: entry.label },\n {\n fetchIncidents: async (b) => {\n const requestParams = withAgentToolkitHeaders({ ...filters, bbox: b });\n return (await trafficIncidentDetails(requestParams)).features;\n },\n },\n intervalMs ? { intervalMs } : undefined,\n );\n\n return { incidentsEntryID, enabled: true, alreadyInState: alreadyRunning };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { buildRouteRecalc } from '../services/set-route';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const startRouteMonitorOutputSchema = z.union([\n z.object({\n routesEntryID: z.string(),\n alreadyRunning: z\n .boolean()\n .describe(\n 'True when the entry already had a running monitor — start is idempotent. When true any new ' +\n 'intervalMs in this call was IGNORED; stop first, then start again to change the interval.',\n ),\n }),\n toolErrorSchema,\n]);\n\nexport const startRouteMonitorSchema = z.object({\n routesEntryID: z.string().describe('Id of the routes entry to monitor (the `entryId` returned by `setRoute`).'),\n intervalMs: z\n .number()\n .int()\n .positive()\n .optional()\n .describe('Recalculation interval in milliseconds. Defaults to 60000 (60s).'),\n});\n\nexport const startRouteMonitorDescription =\n 'Start a background monitor on an EXISTING route entry: recalculates the SAME route (same waypoints + ' +\n 'parameters) every interval (default 60s) and replaces its data in place, so the route stays current with ' +\n 'live traffic. The first recalculation fires immediately. ' +\n '\\n\\n' +\n 'Pairs with a route calculated as `traffic: \"historical\"` + `maxAlternatives: 1-2`: historical input keeps the ' +\n 'typical route PATHS stable across ticks (live input would keep re-routing to dodge current traffic), while ' +\n 'each recalculation refreshes the live-traffic delays carried on those paths. ' +\n '\\n\\n' +\n 'Idempotent on a running monitor: a second call is a no-op returning alreadyRunning: true. A new intervalMs is ' +\n 'NOT applied to a running monitor — stop first, then start again. Listen to the routing `entries-change` / ' +\n '`monitor-tick` events to react to each refresh.';\n\nexport const executeStartRouteMonitor = async (\n params: z.infer<typeof startRouteMonitorSchema>,\n state: ToolState,\n): Promise<z.infer<typeof startRouteMonitorOutputSchema>> => {\n const { routesEntryID, intervalMs } = params;\n const entry = state.routing.entries.find((e) => e.id === routesEntryID);\n if (!entry) {\n return { error: `No routes entry with id \"${routesEntryID}\". Calculate a route first via setRoute.` };\n }\n\n const alreadyRunning = state.routing.isMonitored(routesEntryID);\n\n // Capture the entry's waypoints + params in the recalc closure so every tick re-runs the SAME\n // route with the same cost model / traffic mode it was calculated with — only the live-traffic\n // delays differ tick to tick.\n const { waypoints, params: routeParams } = entry;\n state.routing.startMonitoring(\n routesEntryID,\n { recalculate: buildRouteRecalc(waypoints, routeParams) },\n intervalMs ? { intervalMs } : undefined,\n );\n\n return { routesEntryID, alreadyRunning };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nexport const stopRouteMonitorOutputSchema = z.union([\n z.object({\n routesEntryID: z.string(),\n wasRunning: z.boolean().describe('True when a monitor was actually stopped; false when it was already idle.'),\n }),\n toolErrorSchema,\n]);\n\nexport const stopRouteMonitorSchema = z.object({\n routesEntryID: z.string().describe('Id of the routes entry whose monitor should stop.'),\n});\n\nexport const stopRouteMonitorDescription =\n 'Stop the background monitor for a route entry. The entry itself stays in state with its last calculated data ' +\n '— use removeEntry if you also want to drop it. No-op when no monitor is running for the entry.';\n\nexport const executeStopRouteMonitor = async (\n params: z.infer<typeof stopRouteMonitorSchema>,\n state: ToolState,\n): Promise<z.infer<typeof stopRouteMonitorOutputSchema>> => {\n const { routesEntryID } = params;\n const entry = state.routing.entries.find((e) => e.id === routesEntryID);\n if (!entry) {\n return { error: `No routes entry with id \"${routesEntryID}\".` };\n }\n\n const wasRunning = state.routing.isMonitored(routesEntryID);\n state.routing.stopMonitoring(routesEntryID);\n\n return { routesEntryID, wasRunning };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport { CLEARABLE_SLICE_NAMES, type ClearableMapSlice, type ClearableSliceName } from '../../state';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// Zod's z.enum needs a non-empty literal tuple; CLEARABLE_SLICE_NAMES is typed as readonly so\n// the cast just narrows it to the tuple-of-literals shape Zod expects. Order is irrelevant.\nconst layerEnum = z.enum(CLEARABLE_SLICE_NAMES as unknown as readonly [ClearableSliceName, ...ClearableSliceName[]]);\n\n/** Output schema for the clear-map tool. */\nexport const clearMapOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n cleared: z.array(layerEnum),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for clear-map. */\nexport const clearMapSchema = z.object({\n layers: z\n .array(layerEnum)\n .optional()\n .describe(\n 'Slice keys to clear (default: all). One per `ToolState` slice that renders on the map: ' +\n CLEARABLE_SLICE_NAMES.map((n) => `\\`${n}\\``).join(', ') +\n '. Map-only — slice histories (`entries`) are kept.',\n ),\n});\n\nexport const clearMapDescription =\n 'Hide everything a chosen set of state slices is rendering on the map (places, routing, ranges, ' +\n 'customGeometries, byod, trafficAreaAnalytics, trafficIncidents). Map-only — slice `entries` history ' +\n 'is untouched. Use to clean up before showing new results.';\n\n/**\n * Execute clear map.\n */\nexport const executeClearMap = async (params: z.infer<typeof clearMapSchema>, state: ToolState) => {\n const { layers } = params;\n const targets = layers && layers.length > 0 ? layers : CLEARABLE_SLICE_NAMES;\n try {\n for (const name of targets) {\n // Cast widens the slice instance to the ClearableMapSlice interface — the\n // CLEARABLE_SLICE_NAMES list is `satisfies`-guarded so every name reaches a\n // slice that actually implements `clearShown`.\n await (state[name] as unknown as ClearableMapSlice).clearShown();\n }\n return {\n success: true as const,\n cleared: [...targets] as ClearableSliceName[],\n };\n } catch (error) {\n return {\n error: `Failed to clear map: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the get-standard-map-styles tool. */\nexport const getStandardMapStylesOutputSchema = z.union([\n z.object({\n styles: z.array(z.string()).readonly(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for getting standard map styles.\n */\nexport const getStandardMapStylesSchema = z.object({});\n\nexport const getStandardMapStylesDescription =\n 'Get the list of available standard map style IDs. If you need more fine styling control, refer to getMapStyleLayers.';\n\n/**\n * Execute get standard map styles.\n */\nexport const executeGetStandardMapStyles = async (\n _params: z.infer<typeof getStandardMapStylesSchema>,\n _state: ToolState,\n): Promise<z.infer<typeof getStandardMapStylesOutputSchema>> => {\n try {\n return {\n styles: standardStyleIDs,\n };\n } catch (error) {\n return {\n error: `Failed to get standard map styles: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { Language, TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the set-language tool. */\nexport const setLanguageOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n language: z.string(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting language.\n */\nexport const setLanguageSchema = z.object({\n language: z.string().describe('e.g. \"en-US\", \"fr-FR\"'),\n});\n\nexport const setLanguageDescription =\n 'Set the language for map labels and all subsequent service API responses (search, geocoding, routing instructions).';\n\n/**\n * Execute set language.\n */\nexport const executeSetLanguage = async (params: z.infer<typeof setLanguageSchema>, state: ToolState) => {\n const { language } = params;\n try {\n TomTomConfig.instance.put({ language: language as Language });\n state.baseMap.ttMap.setLanguage(language as Language);\n\n return {\n success: true,\n language,\n };\n } catch (error) {\n return {\n error: `Failed to set language: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the setMapStandardStyle tool. */\nexport const setMapStandardStyleOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n style: z.string(),\n }),\n toolErrorSchema,\n]);\n\n/**\n * Tool schema for setting a standard map style.\n */\nexport const setMapStandardStyleSchema = z.object({\n style: z.enum(standardStyleIDs).describe('Map visual theme'),\n});\n\nexport const setMapStandardStyleDescription =\n \"Switch the map to a standard base-map style preset (IDs are the `style` argument's values). \" +\n 'ONLY when the user explicitly asks to change the map style/theme — never on your own initiative. ' +\n 'For finer styling use getMapStyleLayers, setLayoutProperties, setPaintProperties.';\n\n/**\n * Execute set map standard style.\n */\nexport const executeSetMapStandardStyle = async (\n params: z.infer<typeof setMapStandardStyleSchema>,\n state: ToolState,\n) => {\n const { style } = params;\n try {\n // Resolves once the style has loaded and the SDK modules have restored themselves onto it,\n // so the agent only reports success for a map that is actually showing the new style.\n await state.baseMap.ttMap.setStyle(style);\n\n return {\n success: true,\n style,\n };\n } catch (error) {\n return {\n error: `Failed to set map style: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n applyMapPreset,\n baseMapKnobCatalogue,\n type KnobEntry,\n type KnobTarget,\n type LightDark,\n type MapColors,\n mapColorNames,\n mapPresetIds,\n poisKnobCatalogue,\n resetKnob,\n type StandardStyleID,\n setKnob,\n standardStyleIDs,\n stylingFoundationsKnobCatalogue,\n type TomTomMap,\n terrainKnobCatalogue,\n trafficFlowKnobCatalogue,\n trafficIncidentsKnobCatalogue,\n} from '@tomtom-org/maps-sdk/map';\nimport {\n deriveMapColors,\n deriveMapColorsFromImage,\n mapColorsLightDark,\n mapThemeModes,\n} from '@tomtom-org/maps-sdk-plugin-map-theme';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { standardStyleIdOf } from '../shared/standard-style-id';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// A module lending the tool the knobs of how its layers look, under its map-wide prefix; the rest\n// of its catalogue (what shows, which features) is other tools' job.\ntype LookKnobs = {\n prefix: string;\n knobs: readonly KnobEntry<string>[];\n module: (state: ToolState) => Promise<KnobTarget<unknown>>;\n // The module when something already created it, since one that does not exist has nothing to\n // reset or report. Left out for the modules every map has.\n existing?: (state: ToolState) => KnobTarget<unknown> | undefined;\n};\n\nconst lookOnly = (knobs: readonly KnobEntry<string>[], isLook: (id: string) => boolean) =>\n knobs.filter(({ id }) => isLook(id));\nconst isTrafficLook = (id: string) => id.startsWith('colors.') || id === 'widthFactor';\n\nconst lookKnobs: readonly LookKnobs[] = [\n {\n prefix: '',\n knobs: stylingFoundationsKnobCatalogue,\n module: (state) => state.baseMap.getStylingFoundationsModule(),\n },\n {\n prefix: '',\n knobs: lookOnly(baseMapKnobCatalogue, (id) => id.startsWith('roads.')),\n module: (state) => state.baseMap.getBaseMapModule(),\n },\n {\n prefix: 'pois.',\n knobs: lookOnly(poisKnobCatalogue, (id) => id !== 'visible' && !id.startsWith('filters.')),\n module: (state) => state.mapPOIs.getPOIsModule(),\n existing: (state) => state.mapPOIs.poisModule,\n },\n {\n prefix: 'traffic.flow.',\n knobs: lookOnly(trafficFlowKnobCatalogue, isTrafficLook),\n module: (state) => state.trafficTiles.getTrafficFlowModule(),\n existing: (state) => state.trafficTiles.trafficFlowModule,\n },\n {\n prefix: 'traffic.incidents.',\n knobs: lookOnly(trafficIncidentsKnobCatalogue, isTrafficLook),\n module: (state) => state.trafficTiles.getTrafficIncidentsModule(),\n existing: (state) => state.trafficTiles.trafficIncidentsModule,\n },\n {\n prefix: 'terrain.',\n knobs: lookOnly(terrainKnobCatalogue, (id) => id.startsWith('hillshade.') && id !== 'hillshade.visible'),\n module: (state) => state.baseMap.getTerrainModule(),\n existing: (state) => state.baseMap.terrainModule,\n },\n];\n\n// The source of a map-wide id and its id in that module's catalogue.\nconst lookKnobOf = (id: string) => {\n for (const source of lookKnobs) {\n const local = id.startsWith(source.prefix) ? id.slice(source.prefix.length) : undefined;\n if (local !== undefined && source.knobs.some((knob) => knob.id === local)) return { source, local };\n }\n return undefined;\n};\n\n// Every id, with its range or allowed values where it has one.\nconst knownKnobs = lookKnobs\n .flatMap(({ prefix, knobs }) =>\n knobs.map(({ id, range, options }) => {\n const name = `${prefix}${id}`;\n if (range) return `${name} (${range.min}–${range.max})`;\n return options ? `${name} (${options.join('|')})` : name;\n }),\n )\n .join(', ');\nconst knobValueSchema = z.union([z.number(), z.boolean(), z.string()]);\nconst themeModeSchema = z\n .enum(mapThemeModes)\n .optional()\n .describe('Light or dark map; auto (default) decides from the colours.');\n\n/** Output schema for the set-map-styling tool. */\nexport const setMapStylingOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n /** Every knob currently overridden, after this call. */\n settings: z.record(z.string(), knobValueSchema),\n /** Knobs that were refused, with why — the rest were applied. */\n rejected: z.array(z.object({ id: z.string(), reason: z.string() })),\n /**\n * When `theme` was given: the colours it resolved to, by role — the ten base-map roles plus\n * `accent`, the colour routes and place pins take unless one was set for them.\n */\n themeColors: z.record(z.enum(mapColorNames), z.string()).optional(),\n /** When the theme came out the other light/dark than the map: the standard style switched to. */\n switchedStyle: z.enum(standardStyleIDs).optional(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for set-map-styling. */\nexport const setMapStylingSchema = z.object({\n set: z\n .record(z.string(), knobValueSchema)\n .optional()\n .describe(\n 'Knob id → value. Factors are multipliers (1 = unchanged, e.g. 1.3), toggles are booleans, colours are CSS ' +\n `colour strings. Ids: ${knownKnobs}.`,\n ),\n reset: z\n .union([z.literal(true), z.array(z.string())])\n .optional()\n .describe('true resets every knob to the style default; a list resets just those knobs. Applied first.'),\n preset: z\n .enum(mapPresetIds)\n .optional()\n .describe(\n 'Apply a named preset (replaces the current knobs; `set` is applied on top). ' +\n 'data-viz: quiet base for data overlays · night-driving: bigger labels, wider roads · minimal: bare map · ' +\n 'globe: globe projection with atmosphere.',\n ),\n theme: z\n .union([\n z.object({\n colors: z\n .array(z.string())\n .min(1)\n .max(12)\n .describe(\n 'The CSS colours the theme is built from, three to six works best — a brand palette, a website, ' +\n \"an artist's or design movement's typical palette, or a described mood turned into hex values. \" +\n 'The SDK assigns them to land, water, roads, labels… coherently (readable labels guaranteed).',\n ),\n mode: themeModeSchema,\n }),\n z.object({\n imageUrl: z\n .url({ protocol: /^https?$/ })\n .describe(\n 'A CORS-readable image URL (logo, photo, poster) whose dominant colours become the theme.',\n ),\n mode: themeModeSchema,\n }),\n ])\n .optional()\n .describe(\n 'Theme the whole map from a few colours or an image instead of naming the ten colours yourself. ' +\n 'Applied after preset and before `set`, so single colours in `set` can still override a role. ' +\n 'A dark theme on a light standard style (or the reverse) switches to its dark (light) variant first. ' +\n 'It also sets `colors.accent`, which routes and place pins follow unless a colour was set for ' +\n 'them — so one call themes the whole map, not the base map alone.',\n ),\n});\n\nexport const setMapStylingDescription =\n 'Restyle the base map semantically: label/icon/road size factors, feature toggles (exit numbers, road arrows, ' +\n 'POI micro markers…), hillshade shading, POI zoom and label colours, traffic colours, ' +\n 'the ten map colours (colors.land/water/vegetation/park/artificial/roadMajor/road/roadOutline/label/labelHalo — ' +\n 'one name recolours every derived shade, so for a dark palette switch to a dark standard style first and recolour ' +\n 'from there), a whole theme from a few colours or an image (`theme`: a brand, a ' +\n \"website's colours, an artist's palette, a described mood — turn it into three to six hex colours first), globe \" +\n 'projection and sky, or a whole preset (data-viz, night-driving, minimal, globe). ' +\n 'Durable across style switches. `colors.accent` is the colour for what is drawn on top of the map: routes ' +\n 'and place pins default to it, so a theme reaches them too. Setting one of those to a specific colour is ' +\n \"still the update*Display tools' job. Showing or hiding a whole layer group (3D buildings, road shields, road \" +\n \"labels…) is toggleTilesBaseMapLayerGroups'.\";\n\ntype ThemeParameter = NonNullable<z.infer<typeof setMapStylingSchema>['theme']>;\n\nconst unknownKnob = (id: string) => new Error(`Unknown knob '${id}'; the \\`set\\` description lists every id.`);\n\n// `StylingFoundationsModule` keys its config by the knob id itself; every other module nests it along the id's path.\nconst valueAt = (config: unknown, path: string): unknown =>\n (config as Record<string, unknown> | undefined)?.[path] ??\n path.split('.').reduce<unknown>((value, key) => (value as Record<string, unknown> | undefined)?.[key], config);\n\nconst existingModule = async (source: LookKnobs, state: ToolState) =>\n source.existing ? source.existing(state) : source.module(state);\n\n// Every look knob a module that exists holds, by its map-wide id.\nconst lookSettingsOf = async (state: ToolState): Promise<Record<string, unknown>> => {\n const settings: Record<string, unknown> = {};\n for (const source of lookKnobs) {\n const config = (await existingModule(source, state))?.getConfig();\n for (const { id } of source.knobs) {\n const value = valueAt(config, id);\n if (value !== undefined) settings[`${source.prefix}${id}`] = value;\n }\n }\n return settings;\n};\n\nconst deriveTheme = async (theme: ThemeParameter): Promise<Required<MapColors>> =>\n 'colors' in theme\n ? deriveMapColors(theme.colors, { mode: theme.mode })\n : deriveMapColorsFromImage(theme.imageUrl, { mode: theme.mode });\n\n// Each standard style's light and dark variants; satellite has none.\nconst standardVariants = { light: 'standardLight', dark: 'standardDark' } as const;\nconst drivingVariants = { light: 'drivingLight', dark: 'drivingDark' } as const;\nconst monoVariants = { light: 'monoLight', dark: 'monoDark' } as const;\nconst LIGHT_DARK_VARIANTS: Partial<Record<StandardStyleID, Record<LightDark, StandardStyleID>>> = {\n standardLight: standardVariants,\n standardDark: standardVariants,\n drivingLight: drivingVariants,\n drivingDark: drivingVariants,\n monoLight: monoVariants,\n monoDark: monoVariants,\n};\n\n// The standard style switched to so the theme recolours one of its own light or dark\n// (see `mapColorsLightDark`); custom styles and satellite have no variant and are left as they are.\nconst matchThemeStyle = async (ttMap: TomTomMap, colors: Required<MapColors>): Promise<StandardStyleID | undefined> => {\n const lightDark = mapColorsLightDark(colors);\n if (ttMap.styleLightDarkTheme === lightDark) return undefined;\n\n const current = standardStyleIdOf(ttMap.getStyleInput());\n const style = current && LIGHT_DARK_VARIANTS[current]?.[lightDark];\n if (style) await ttMap.setStyle(style);\n return style;\n};\n\n/** Execute set-map-styling. */\nexport const executeSetMapStyling = async (params: z.infer<typeof setMapStylingSchema>, state: ToolState) => {\n try {\n const styling = await state.baseMap.getStylingFoundationsModule();\n // Unknown ids, out-of-range values and a theme that cannot be resolved are reported per id\n // rather than failing the whole call.\n const rejected: { id: string; reason: string }[] = [];\n const reject = (id: string, error: unknown) => {\n rejected.push({ id, reason: error instanceof Error ? error.message : String(error) });\n };\n // One knob, set or reset through the module owning it: modules are fetched one at a time,\n // since a map mutation must not race another.\n const onKnob = async (\n id: string,\n action: (target: KnobTarget<unknown>, knobs: readonly KnobEntry<string>[], local: string) => void,\n ) => {\n try {\n const owner = lookKnobOf(id);\n if (!owner) throw unknownKnob(id);\n action(await owner.source.module(state), owner.source.knobs, owner.local);\n } catch (error) {\n reject(id, error);\n }\n };\n\n if (params.reset === true) {\n for (const source of lookKnobs) {\n const module = await existingModule(source, state);\n for (const { id } of module ? source.knobs : [])\n resetKnob(module as KnobTarget<unknown>, source.knobs, id);\n }\n } else if (Array.isArray(params.reset)) {\n for (const id of params.reset) await onKnob(id, (target, knobs, local) => resetKnob(target, knobs, local));\n }\n if (params.preset) {\n await applyMapPreset(state.baseMap.ttMap, params.preset);\n }\n let themeColors: Required<MapColors> | undefined;\n let switchedStyle: StandardStyleID | undefined;\n if (params.theme) {\n try {\n themeColors = await deriveTheme(params.theme);\n switchedStyle = await matchThemeStyle(state.baseMap.ttMap, themeColors);\n styling.setMapColors(themeColors);\n } catch (error) {\n reject('theme', error);\n }\n }\n for (const [id, value] of Object.entries(params.set ?? {})) {\n await onKnob(id, (target, knobs, local) => setKnob(target, knobs, local, value as never));\n }\n\n return {\n success: true as const,\n settings: (await lookSettingsOf(state)) as Record<string, z.infer<typeof knobValueSchema>>,\n rejected,\n ...(themeColors && { themeColors }),\n ...(switchedStyle && { switchedStyle }),\n };\n } catch (error) {\n return {\n error: `Failed to set map styling: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type BaseMapModule, baseMapLayerGroupNames } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the toggle-tiles-base-map-layer-groups tool. */\nexport const toggleTilesBaseMapLayerGroupsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n /** Every layer group hidden after this call. */\n hiddenGroups: z.array(z.enum(baseMapLayerGroupNames)),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for toggle-tiles-base-map-layer-groups. */\nexport const toggleTilesBaseMapLayerGroupsSchema = z.object({\n reset: z\n .boolean()\n .optional()\n .describe('Show every layer as the style ships it (buildings3D ships hidden). Other params ignored when true.'),\n visible: z.boolean().optional().describe('true shows the groups, false hides them.'),\n layerGroups: z\n .array(z.enum(baseMapLayerGroupNames))\n .min(1)\n .optional()\n .describe('The groups to show or hide; omit for the whole base map.'),\n});\n\nexport const toggleTilesBaseMapLayerGroupsDescription =\n 'Show / hide whole groups of the vector-tile base map: water, land, roads, railways, ferries, borders, ' +\n '2D footprints and 3D buildings (buildings2D / buildings3D), road shields, and the label groups (roadLabels, ' +\n 'cityLabels, countryLabels, houseNumbers, …). Returns every group hidden afterwards. The POI icons are ' +\n \"toggleTilesPOIs', how the visible groups look is setMapStyling's, and layers the agent drew (places, routes, \" +\n 'BYOD) are untouched.';\n\nconst hiddenGroupsOf = (baseMap: BaseMapModule) =>\n baseMapLayerGroupNames.filter((group) => !baseMap.isVisible({ layerGroups: { show: 'only', values: [group] } }));\n\n/** Execute toggle-tiles-base-map-layer-groups. */\nexport const executeToggleTilesBaseMapLayerGroups = async (\n params: z.infer<typeof toggleTilesBaseMapLayerGroupsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof toggleTilesBaseMapLayerGroupsOutputSchema>> => {\n const { reset, visible, layerGroups } = params;\n if (!reset && visible === undefined) {\n return { error: 'Give `visible` (true to show, false to hide) with the `layerGroups`, or `reset: true`.' };\n }\n\n try {\n const baseMap = await state.baseMap.getBaseMapModule();\n if (reset) baseMap.resetConfig();\n else\n baseMap.setVisible(visible === true, layerGroups && { layerGroups: { show: 'only', values: layerGroups } });\n\n return { success: true, hiddenGroups: hiddenGroupsOf(baseMap) };\n } catch (error) {\n return {\n error: `Failed to toggle base-map layer groups: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport {\n type FilterablePOICategory,\n filterShowModes,\n poisKnobCatalogue,\n validateKnobValue,\n} from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the toggle-tiles-pois tool. */\nexport const toggleTilesPOIsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n allMapPOIsVisible: z.boolean().optional(),\n filterCategories: z\n .object({\n show: z.enum(filterShowModes),\n values: z.array(z.string()),\n })\n .optional(),\n reset: z.boolean().optional(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for toggle-tiles-pois. */\nexport const toggleTilesPOIsSchema = z.object({\n reset: z\n .boolean()\n .optional()\n .describe('Reset all tile-POI configuration to defaults. Other params ignored when true.'),\n allMapPOIsVisible: z\n .boolean()\n .optional()\n .describe('Overall visibility of the vector-tile POI icons baked into the base map style.'),\n filterCategories: z\n .object({\n show: z\n .enum(filterShowModes)\n .describe('\"only\": keep only these categories. \"all-except\": hide these categories.'),\n values: z\n .array(z.string())\n .describe(\n 'MapStylePOICategory / POICategoryGroup values (e.g. \"RESTAURANT\", \"FOOD_DRINKS_GROUP\"). ' +\n 'Fine-grained codes like \"ITALIAN_RESTAURANT\" not supported here.',\n ),\n })\n .optional()\n .describe('Restrict which built-in vector-tile POI categories are shown.'),\n});\n\nexport const toggleTilesPOIsDescription =\n 'Show/hide the built-in vector-tile POI icons baked into the base map style (restaurants, hotels, gas stations, …). ' +\n 'NOT the same as places shown via `discoverPlaces` / `updatePlacesDisplay` — those are session-state pins on the ' +\n 'PlacesModule, untouched by this tool. `allMapPOIsVisible` toggles overall visibility; `filterCategories` restricts ' +\n 'which categories show; `reset: true` restores defaults.';\n\nconst categoriesKnob = poisKnobCatalogue.find(({ id }) => id === 'filters.categories.values');\n\nconst filterableCategories = new Set(categoriesKnob?.options);\n\nconst isFilterableCategory = (value: string): value is FilterablePOICategory => filterableCategories.has(value);\n\n// Why the knob refuses the categories, or `undefined` when it takes them all.\nconst categoriesProblem = (values: string[]): string | undefined => {\n if (!categoriesKnob) return undefined;\n\n try {\n validateKnobValue(categoriesKnob, values);\n return undefined;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n};\n\n/** Execute toggle-tiles-pois. */\nexport const executeToggleTilesPOIs = async (\n params: z.infer<typeof toggleTilesPOIsSchema>,\n state: ToolState,\n): Promise<z.infer<typeof toggleTilesPOIsOutputSchema>> => {\n try {\n const poisModule = await state.mapPOIs.getPOIsModule();\n\n if (params.reset) {\n poisModule.resetConfig();\n return { success: true, reset: true };\n }\n\n const { allMapPOIsVisible, filterCategories } = params;\n\n const problem = filterCategories && categoriesProblem(filterCategories.values);\n if (problem) return { error: `${problem} Use MapStylePOICategory or POICategoryGroup values.` };\n\n if (allMapPOIsVisible !== undefined) poisModule.setVisible(allMapPOIsVisible);\n if (filterCategories) {\n poisModule.updateConfig({\n filters: {\n categories: {\n show: filterCategories.show,\n values: filterCategories.values.filter(isFilterableCategory),\n },\n },\n });\n }\n\n return { success: true, allMapPOIsVisible, filterCategories };\n } catch (error) {\n return {\n error: `Failed to toggle tile POIs: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Tool schema for toggle-tiles-traffic-flow. */\nexport const toggleTilesTrafficFlowSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-tiles-traffic-flow tool. */\nexport const toggleTilesTrafficFlowOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficFlowVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for toggle-tiles-traffic-incidents. */\nexport const toggleTilesTrafficIncidentsSchema = z.object({\n visible: z.boolean(),\n});\n\n/** Output schema for the toggle-tiles-traffic-incidents tool. */\nexport const toggleTilesTrafficIncidentsOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n trafficIncidentsVisible: z.boolean(),\n }),\n toolErrorSchema,\n]);\n\nexport const toggleTilesTrafficFlowDescription =\n 'Toggle the vector-tile traffic-flow overlay (live coloured road segments). ' +\n 'NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical analytics) or `route.properties.sections.traffic[]` ' +\n '(per-route incidents). Just a base-map style layer on/off.';\n\nexport const toggleTilesTrafficIncidentsDescription =\n 'Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). ' +\n 'NOT the same as `getTrafficIncidents` (GeoJSON service → structured entries) or `analyseData` ' +\n '(sandbox over fetched entries). Just a base-map style layer on/off.';\n\n/** Execute toggle-tiles-traffic-flow. */\nexport const executeToggleTilesTrafficFlow = async (\n params: z.infer<typeof toggleTilesTrafficFlowSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n const trafficFlowModule = await state.trafficTiles.getTrafficFlowModule();\n trafficFlowModule.setVisible(visible);\n return { success: true, trafficFlowVisible: visible };\n } catch (error) {\n return {\n error: `Failed to toggle tile traffic flow: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n\n/** Execute toggle-tiles-traffic-incidents. */\nexport const executeToggleTilesTrafficIncidents = async (\n params: z.infer<typeof toggleTilesTrafficIncidentsSchema>,\n state: ToolState,\n) => {\n const { visible } = params;\n try {\n const trafficIncidentsModule = await state.trafficTiles.getTrafficIncidentsModule();\n trafficIncidentsModule.setVisible(visible);\n return { success: true, trafficIncidentsVisible: visible };\n } catch (error) {\n return {\n error: `Failed to toggle tile traffic incidents: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst actionSchema = z\n .enum(['show', 'hide', 'remove'])\n .describe(\n '`show` renders the listed entries (lazy-init their CustomGeoJSONModule); ' +\n '`hide` removes them from the map but keeps them in session state; ' +\n '`remove` hides AND drops them from history.',\n );\n\nexport const updateByodDisplaySchema = z\n .object({\n entryIds: z\n .array(z.string())\n .min(1)\n .optional()\n .describe(\n 'BYOD entry ids to act on (e.g. [\"byod-0\"]). Use `recallState` to list. ' +\n 'Omit when `action: \"hide\"` and `clearAll: true` to hide every shown BYOD entry in one call.',\n ),\n action: actionSchema,\n clearAll: z\n .boolean()\n .optional()\n .describe(\n 'Only valid with `action: \"hide\"`. When true, hides every currently-shown BYOD entry (ignores `entryIds`). Default: false.',\n ),\n hideOthers: z\n .boolean()\n .optional()\n .describe(\n 'Only valid with `action: \"show\"`. When true, hides every other BYOD entry before showing the listed ones. Default: false (stack).',\n ),\n })\n .refine(\n (v) => {\n if (v.action === 'hide' && v.clearAll) return true;\n return !!v.entryIds?.length;\n },\n { message: '`entryIds` is required unless `action: \"hide\"` with `clearAll: true`.' },\n )\n .refine((v) => !v.clearAll || v.action === 'hide', {\n message: '`clearAll: true` is only valid with `action: \"hide\"`.',\n })\n .refine((v) => !v.hideOthers || v.action === 'show', {\n message: '`hideOthers: true` is only valid with `action: \"show\"`.',\n });\n\nexport const updateByodDisplayOutputSchema = z.union([\n z.object({\n action: actionSchema,\n affectedIds: z.array(z.string()).describe('Entry ids that were actually shown / hidden / removed.'),\n shown: z.array(z.string()).describe('BYOD entry ids currently rendered on the map after this call.'),\n }),\n toolErrorSchema,\n]);\n\nexport const updateByodDisplayDescription =\n 'Toggle visibility (or drop) for one or more BYOD entries. Use after `addByodSource` or after seeing entries ' +\n 'via `recallState`. Three actions: `show` renders listed entries, `hide` removes them from the map but keeps ' +\n 'them in state, `remove` hides AND drops them from history. Pair `action: \"hide\"` with `clearAll: true` to ' +\n 'hide every shown BYOD entry in one call. Pair `action: \"show\"` with `hideOthers: true` to swap to a single ' +\n \"set of entries (replaces what's already on the map).\";\n\ntype BYODSlice = ToolState['byod'];\n\n// Reject the call up-front if any requested id is unknown — leaves the slice in its original\n// state rather than partially applying. Returns `undefined` when every id is valid.\nconst validateRequestedIds = (slice: BYODSlice, requested: readonly string[]): { error: string } | undefined => {\n for (const id of requested) {\n if (!slice.findById(id)) {\n return { error: `No BYOD entry with id \"${id}\". Call recallState to list available IDs.` };\n }\n }\n return undefined;\n};\n\nconst showEntries = async (slice: BYODSlice, requested: readonly string[], hideOthers: boolean): Promise<string[]> => {\n const affected: string[] = [];\n if (hideOthers) {\n const requestedSet = new Set(requested);\n for (const id of slice.shownEntryIds) {\n if (requestedSet.has(id)) continue;\n await slice.hideEntry(id);\n affected.push(id);\n }\n }\n for (const id of requested) {\n await slice.showEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nconst hideEntries = async (slice: BYODSlice, requested: readonly string[], clearAll: boolean): Promise<string[]> => {\n const affected: string[] = [];\n const ids = clearAll ? slice.shownEntryIds : requested;\n for (const id of ids) {\n await slice.hideEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nconst removeEntries = async (slice: BYODSlice, requested: readonly string[]): Promise<string[]> => {\n const affected: string[] = [];\n for (const id of requested) {\n await slice.removeEntry(id);\n affected.push(id);\n }\n return affected;\n};\n\nexport const executeUpdateByodDisplay = async (\n params: z.infer<typeof updateByodDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateByodDisplayOutputSchema>> => {\n const slice = state.byod;\n const { action, clearAll, hideOthers } = params;\n const requested = params.entryIds ?? [];\n\n const validationProblem = validateRequestedIds(slice, requested);\n if (validationProblem) return validationProblem;\n\n let affected: string[];\n if (action === 'show') {\n affected = await showEntries(slice, requested, !!hideOthers);\n } else if (action === 'hide') {\n affected = await hideEntries(slice, requested, !!clearAll);\n } else {\n affected = await removeEntries(slice, requested);\n }\n\n return {\n action,\n affectedIds: affected,\n shown: [...slice.shownEntryIds],\n };\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { CommonPlaceProps, Place, PolygonFeature } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport { type PlacesMarkerType, placesMarkerTypes } from '../../state';\nimport type { ToolState } from '../../types';\nimport { geometryDisplayConfigSchema } from '../shared';\nimport { fitMapToGeoJSON } from '../shared/map-fit';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\ntype GeometryDisplayConfig = z.infer<typeof geometryDisplayConfigSchema>;\ntype GeometrySkip = { placeId: string; reason: string };\n\n// Resolve one place to its boundary polygon, or a reason it can't be rendered (no geometry data\n// source, no feature returned, or a fetch error). Isolated so the per-entry / per-place loops stay flat.\ntype PlaceGeometryResult = { feature: PolygonFeature<CommonPlaceProps> } | { skip: string };\n\nconst fetchPlaceGeometryFeature = async (state: ToolState, place: Place): Promise<PlaceGeometryResult> => {\n if (!place.properties.dataSources?.geometry?.id) {\n return { skip: 'Place has no geometry data source (e.g. addresses and streets often lack one).' };\n }\n try {\n const feature = await state.places.fetchPlaceGeometry(place.id);\n if (!feature) return { skip: 'Geometry Data service returned no feature for this place.' };\n return { feature };\n } catch (error) {\n return { skip: `Geometry fetch failed: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n// Render one entry's boundary polygons (optionally filtered to `config.placeIds`) via its\n// GeometriesModule, returning the count rendered plus per-place skips.\nconst renderEntryGeometries = async (\n state: ToolState,\n entryId: string,\n config: GeometryDisplayConfig,\n): Promise<{ rendered: number; skipped: GeometrySkip[] }> => {\n const entry = state.places.entries.find((e) => e.id === entryId);\n if (!entry) return { rendered: 0, skipped: [] };\n\n const placeIdFilter = config.placeIds ? new Set(config.placeIds) : undefined;\n const targetPlaces = placeIdFilter ? entry.data.filter((p) => placeIdFilter.has(p.id)) : entry.data;\n\n const skipped: GeometrySkip[] = [];\n const features: PolygonFeature<CommonPlaceProps>[] = [];\n for (const place of targetPlaces) {\n const result = await fetchPlaceGeometryFeature(state, place);\n if ('skip' in result) skipped.push({ placeId: place.id, reason: result.skip });\n else features.push(result.feature);\n }\n\n if (features.length > 0) {\n await state.places.showPlaceGeometries(\n features,\n config.fillStyle ?? 'outline',\n config.mode ?? 'replace',\n entryId,\n );\n }\n return { rendered: features.length, skipped };\n};\n\n// Renders boundary polygons for the given entries via each entry's GeometriesModule.\n// Place ids outside the targeted entries are ignored — see `geometryDisplayConfigSchema`.\nconst applyGeometryDisplay = async (\n state: ToolState,\n entryIds: readonly string[],\n config: GeometryDisplayConfig,\n): Promise<{ rendered: number; skipped: GeometrySkip[] }> => {\n const skipped: GeometrySkip[] = [];\n let rendered = 0;\n for (const entryId of entryIds) {\n const result = await renderEntryGeometries(state, entryId, config);\n rendered += result.rendered;\n skipped.push(...result.skipped);\n }\n return { rendered, skipped };\n};\n\nconst shownEntrySummarySchema = z.object({\n id: z.string(),\n label: z.string(),\n featureCount: z.number(),\n markerType: z.enum(placesMarkerTypes).describe('How the entry is rendered on the map.'),\n});\n\nexport const updatePlacesDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z.array(shownEntrySummarySchema).describe('Entries currently on the map, with their labels.'),\n actuallyAdded: z\n .array(z.string())\n .describe('Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op.'),\n actuallyRemoved: z\n .array(z.string())\n .describe('Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op.'),\n geometriesRendered: z\n .number()\n .optional()\n .describe('Number of boundary polygons rendered when `geometry` was set.'),\n geometriesSkipped: z\n .array(z.object({ placeId: z.string(), reason: z.string() }))\n .optional()\n .describe('Places whose boundary could not be rendered (no geometry data source, fetch failure).'),\n }),\n toolErrorSchema,\n]);\n\nexport const updatePlacesDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe(\n 'Entry ids to show (e.g. [\"places-1\"]). ' +\n 'Omit while still setting `markerType` or `geometry` to target the most recently stored entry.',\n ),\n remove: z.array(z.string()).optional().describe('Entry ids to hide.'),\n addMatching: z.array(z.string()).optional().describe('Category labels to show.'),\n removeMatching: z.array(z.string()).optional().describe('Category labels to hide.'),\n clear: z.boolean().optional().describe('Hide every displayed entry.'),\n markerType: z\n .enum(placesMarkerTypes)\n .optional()\n .describe(\n 'How newly-added entries are rendered as markers: \"pin\", \"base-map\", or \"pin-clustered\" (pins with a count badge ' +\n 'where nearby ones aggregate). ' +\n 'Defaults to \"pin\" unless `geometry` is set without an explicit `markerType`, in which case markers are skipped. ' +\n 'Entries previously shown with another marker type are moved to this one.',\n ),\n geometry: geometryDisplayConfigSchema.optional(),\n fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.',\n ),\n })\n .refine(\n (data) =>\n data.add !== undefined ||\n data.remove !== undefined ||\n data.addMatching !== undefined ||\n data.removeMatching !== undefined ||\n data.clear === true ||\n data.markerType !== undefined ||\n data.geometry !== undefined,\n {\n message:\n 'updatePlacesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, markerType, geometry.',\n },\n );\n\nexport const updatePlacesDisplayDescription =\n 'Show, hide, and swap which places entries are displayed on the map in one atomic call. ' +\n 'Target entries by id (`add` / `remove`) or by category label (`addMatching` / `removeMatching`). ' +\n '`clear` hides every currently-shown entry; combine with `add` to swap the display (\"hide everything, show these\"). ' +\n 'Markers (`markerType`) and boundary polygons (`geometry`) are independent display channels — set either or both for the targeted entries. ' +\n 'Omit `add`/`addMatching` while still setting `markerType` or `geometry` to target the most recently stored entry. ' +\n 'Does not delete from history.';\n\n/** Resolve category terms to entry ids by case-insensitive substring match on the entry label. */\nconst resolveMatchingIds = (state: ToolState, terms: readonly string[]): string[] => {\n if (terms.length === 0) return [];\n const needles = terms.map((term) => term.toLowerCase());\n const matched = new Set<string>();\n for (const entry of state.places.entries) {\n const label = entry.label.toLowerCase();\n if (needles.some((needle) => label.includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\n// When markerType is implicit, leave entries that are already shown alone — otherwise an\n// \"add cafes\" call would demote a previously-clustered entry to pin. Explicit markerType\n// is treated as an intentional override and applies to every id.\nconst applyMarkerAdds = async (\n state: ToolState,\n addIds: readonly string[],\n markerType: PlacesMarkerType | undefined,\n beforeShown: ReadonlySet<string>,\n): Promise<void> => {\n const markerIds = markerType !== undefined ? addIds : addIds.filter((id) => !beforeShown.has(id));\n if (markerIds.length > 0) {\n await state.places.addShownEntries(markerIds, markerType ?? 'pin');\n }\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\n// Resolves the user-provided selectors into the concrete entry-id sets we'll act on. Handles\n// label-matching, the implicit \"latest entry\" fallback when only a display signal is set, and\n// the unknown-id check that gates the rest of the executor.\nconst resolveSelectors = (state: ToolState, params: z.infer<typeof updatePlacesDisplaySchema>): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, markerType, geometry } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n const implicitLatest =\n add === undefined && addMatching === undefined && (markerType !== undefined || geometry !== undefined);\n if (implicitLatest) {\n const latestId = state.places.entries.at(-1)?.id;\n if (!latestId) {\n return {\n error: 'No places available to display. Resolve a place or run a search first (e.g. discoverPlaces).',\n };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.places.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown places entry ids: ${unknownAdd.join(', ')}. Use recallState to list available entries.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\nexport const executeUpdatePlacesDisplay = async (\n params: z.infer<typeof updatePlacesDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updatePlacesDisplayOutputSchema>> => {\n const { clear, markerType, geometry, fitBounds } = params;\n try {\n const selectors = resolveSelectors(state, params);\n if ('error' in selectors) return selectors;\n const { add: resolvedAdd, remove: resolvedRemove } = selectors;\n\n // Snapshot the shown set BEFORE mutation so we can report diffs accurately.\n const beforeShown = new Set(state.places.shownEntryIds);\n\n if (clear) {\n await state.places.clearShownEntries();\n await state.places.clearShownGeometries();\n }\n if (resolvedRemove.length > 0) {\n await state.places.removeShownEntries(resolvedRemove);\n await state.places.clearShownGeometries(resolvedRemove);\n }\n // Markers default to 'pin' unless `geometry` was set with no explicit markerType,\n // in which case the user wants boundaries only.\n const renderMarkers = markerType !== undefined || !geometry;\n if (resolvedAdd.length > 0 && renderMarkers) {\n await applyMarkerAdds(state, resolvedAdd, markerType, beforeShown);\n }\n\n let geometryResult: { rendered: number; skipped: GeometrySkip[] } | undefined;\n if (geometry && resolvedAdd.length > 0) {\n geometryResult = await applyGeometryDisplay(state, resolvedAdd, geometry);\n }\n\n const afterShown = state.places.shownEntryIds;\n const actuallyAdded = resolvedAdd.filter((id) => !beforeShown.has(id) && afterShown.has(id));\n const actuallyRemoved = resolvedRemove.filter((id) => beforeShown.has(id) && !afterShown.has(id));\n\n const shouldFit = fitBounds ?? resolvedAdd.length > 0;\n const shownFeatures = state.places.getShownFeatures();\n if (shouldFit) {\n fitMapToGeoJSON(state.baseMap.mapLibreMap, shownFeatures);\n }\n\n const shownWithLabels = state.places.entries\n .filter((entry) => afterShown.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n label: entry.label,\n featureCount: entry.data.length,\n markerType: state.places.getShownMarkerType(entry.id) ?? 'pin',\n }));\n\n return {\n success: true as const,\n totalShown: shownFeatures.length,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n ...(geometryResult && { geometriesRendered: geometryResult.rendered }),\n ...(geometryResult?.skipped.length && { geometriesSkipped: geometryResult.skipped }),\n };\n } catch (error) {\n return {\n error: `updatePlacesDisplay failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport type { Routes } from '@tomtom-org/maps-sdk/core';\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { fitMapToGeoJSON } from '../shared/map-fit';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\nconst shownRouteEntrySummarySchema = z.object({\n id: z.string(),\n label: z.string(),\n routeCount: z.number().describe('Number of route alternatives in the entry.'),\n selectedIndex: z.number().describe('Highlighted alternative index (0 unless overridden by this call).'),\n});\n\nexport const updateRoutesDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z\n .array(shownRouteEntrySummarySchema)\n .describe('Routes entries currently on the map, with their labels.'),\n actuallyAdded: z\n .array(z.string())\n .describe('Ids from `add` / `addMatching` that were not already shown. Empty if it was a no-op.'),\n actuallyRemoved: z\n .array(z.string())\n .describe('Ids from `remove` / `removeMatching` that were actually on the map. Empty if no-op.'),\n color: z\n .string()\n .optional()\n .describe('Sticky route colour now applied to every shown route, when set by this or a previous call.'),\n }),\n toolErrorSchema,\n]);\n\nexport const updateRoutesDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe(\n 'Entry ids to show (e.g. [\"routes-1\"]). ' +\n 'Omit while still setting `selectedIndex` to target the most recently stored entry.',\n ),\n remove: z.array(z.string()).optional().describe('Entry ids to hide.'),\n addMatching: z.array(z.string()).optional().describe('Label substrings to show.'),\n removeMatching: z.array(z.string()).optional().describe('Label substrings to hide.'),\n clear: z.boolean().optional().describe('Hide every displayed entry.'),\n selectedIndex: z\n .number()\n .int()\n .min(0)\n .optional()\n .describe(\n 'Which alternative variant to highlight on newly-added entries (default: 0). ' +\n 'Use to switch the selected alternative on a multi-alternative result. ' +\n 'Applied after the entry is shown — entries previously rendered with another index are re-rendered.',\n ),\n fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the resulting display. Defaults to true when entries are being added, false otherwise.',\n ),\n color: z\n .string()\n .optional()\n .describe(\n 'Recolor the route line + waypoint icons. CSS named colour (\"coral\", \"steelblue\") or hex (\"#FF0000\"). ' +\n \"Only when a specific colour is asked for: with no color the route follows the map theme's \" +\n 'accent colour, so `setMapStyling({ theme })` already recolours it. ' +\n 'Sticky — applied to every currently-shown entry AND remembered so subsequent shows pick it up automatically. ' +\n 'Can be set by itself (no add/remove/clear) to recolor without changing visibility.',\n ),\n })\n .refine(\n (data) =>\n data.add !== undefined ||\n data.remove !== undefined ||\n data.addMatching !== undefined ||\n data.removeMatching !== undefined ||\n data.clear === true ||\n data.selectedIndex !== undefined ||\n data.color !== undefined,\n {\n message:\n 'updateRoutesDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, selectedIndex, color.',\n },\n );\n\nexport const updateRoutesDisplayDescription =\n 'Show, hide, swap, and recolor which routes entries are displayed on the map in one atomic call. ' +\n 'Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). ' +\n '`clear` hides every currently-shown entry; combine with `add` to swap the display (\"hide everything, show this\"). ' +\n '`selectedIndex` re-highlights an alternative variant on the newly-added entries; with no ' +\n '`add`/`addMatching` it implicitly targets the most recently stored entry. ' +\n '`color` recolors the route line + waypoint icons (sticky across entry swaps — applied to current shows AND remembered); ' +\n 'it does NOT auto-show a hidden entry, so set `add`/`addMatching` too if nothing is on the map yet. ' +\n 'Does not delete from history. Does not call any service.';\n\nconst resolveMatchingIds = (state: ToolState, terms: readonly string[]): string[] => {\n if (terms.length === 0) return [];\n const needles = terms.map((term) => term.toLowerCase());\n const matched = new Set<string>();\n for (const entry of state.routing.entries) {\n const label = entry.label.toLowerCase();\n if (needles.some((needle) => label.includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\n// Resolves user-provided selectors into concrete entry-id sets. Handles label-matching, the\n// implicit \"latest entry\" fallback when only `selectedIndex` is set, and the unknown-id check\n// that gates the rest of the executor.\nconst resolveSelectors = (state: ToolState, params: z.infer<typeof updateRoutesDisplaySchema>): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, selectedIndex } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n // `selectedIndex` implicitly targets \"the most recently stored entry\" when no\n // `add` / `addMatching` is supplied. `color` is handled separately by the executor\n // (sticky, applies to whatever is shown — no implicit-latest add needed).\n const implicitLatest = add === undefined && addMatching === undefined && selectedIndex !== undefined;\n if (implicitLatest) {\n const latestId = state.routing.entries.at(-1)?.id;\n if (!latestId) {\n return { error: 'No routes available to display. Calculate a route first via setRoute.' };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.routing.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown routes entry ids: ${unknownAdd.join(', ')}. Use recallState to list available entries.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\n// `state.routing.showEntry` always renders with selectedIndex 0. For a non-zero selection we\n// re-render directly on the entry's module after the show, mirroring the previous show-routes\n// behavior. Done sequentially per the SDK/MapLibre race constraints.\nconst applySelectedIndex = async (\n state: ToolState,\n entryIds: readonly string[],\n selectedIndex: number,\n): Promise<void> => {\n if (selectedIndex === 0) return;\n for (const id of entryIds) {\n const entry = state.routing.entries.find((e) => e.id === id);\n if (!entry) continue;\n const module = await state.routing.getEntryRoutingModule(id);\n await module.showRoutes(entry.data, { selectedIndex });\n if (entry.waypoints.length) await module.showWaypoints(entry.waypoints);\n }\n};\n\n// Apply the requested add / remove / clear / selectedIndex sequence and return the\n// before/after shown sets so the response layer can compute deltas. Sequential awaits — concurrent\n// show/hide on the routing slice races MapLibre's style mutation.\nconst applyAddRemove = async (\n state: ToolState,\n plan: { add: readonly string[]; remove: readonly string[]; clear: boolean; selectedIndex: number | undefined },\n): Promise<{ beforeShown: Set<string>; afterShown: ReadonlySet<string> }> => {\n const beforeShown = new Set(state.routing.shownEntryIds);\n if (plan.clear) {\n for (const id of state.routing.shownEntryIds) {\n await state.routing.hideEntry(id);\n }\n }\n for (const id of plan.remove) {\n await state.routing.hideEntry(id);\n }\n for (const id of plan.add) {\n await state.routing.showEntry(id);\n }\n if (plan.selectedIndex !== undefined) {\n await applySelectedIndex(state, plan.add, plan.selectedIndex);\n }\n return { beforeShown, afterShown: state.routing.shownEntryIds };\n};\n\n// Fit the camera around every currently-shown route. No-op when nothing is shown or the joined\n// feature collection has no computable bbox.\nconst fitCameraToShown = (state: ToolState, shownIds: ReadonlySet<string>): void => {\n const features: Routes['features'] = [];\n for (const entry of state.routing.entries) {\n if (shownIds.has(entry.id)) features.push(...entry.data.features);\n }\n fitMapToGeoJSON(state.baseMap.mapLibreMap, features);\n};\n\nexport const executeUpdateRoutesDisplay = async (\n params: z.infer<typeof updateRoutesDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateRoutesDisplayOutputSchema>> => {\n const { clear, selectedIndex, fitBounds, color } = params;\n try {\n const selectors = resolveSelectors(state, params);\n if ('error' in selectors) return selectors;\n const { add: resolvedAdd, remove: resolvedRemove } = selectors;\n\n // Persist the colour BEFORE add/remove so newly-shown entries pick it up on first paint,\n // and apply to whatever is already on the map. Sticky for the lifetime of the slice.\n if (color !== undefined) {\n state.routing.setColor(color);\n }\n\n const { beforeShown, afterShown } = await applyAddRemove(state, {\n add: resolvedAdd,\n remove: resolvedRemove,\n clear: !!clear,\n selectedIndex,\n });\n\n const actuallyAdded = resolvedAdd.filter((id) => !beforeShown.has(id) && afterShown.has(id));\n const actuallyRemoved = resolvedRemove.filter((id) => beforeShown.has(id) && !afterShown.has(id));\n\n if (fitBounds ?? resolvedAdd.length > 0) {\n fitCameraToShown(state, afterShown);\n }\n\n const shownWithLabels = state.routing.entries\n .filter((entry) => afterShown.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n label: entry.label,\n routeCount: entry.data.features.length,\n selectedIndex: actuallyAdded.includes(entry.id) ? (selectedIndex ?? 0) : 0,\n }));\n\n const currentColor = state.routing.color;\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown: shownWithLabels,\n actuallyAdded,\n actuallyRemoved,\n ...(currentColor !== undefined && { color: currentColor }),\n };\n } catch (error) {\n return {\n error: `updateRoutesDisplay failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { type AreaAnalyticsHeightConfig, areaAnalyticsPalettes } from '@tomtom-org/maps-sdk/map';\nimport { z } from 'zod';\nimport type { TrafficAreaAnalyticsEntry } from '../../state';\nimport type { ToolState } from '../../types';\nimport { fitMapToGeoJSON } from '../shared/map-fit';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n// ---------------------------------------------------------------------------\n// Schemas\n// ---------------------------------------------------------------------------\n\nconst renderKnobs = {\n mode: z\n .enum(['hexgrid-3d', 'hexgrid-2d', 'square-3d', 'square-2d', 'heatmap'])\n .optional()\n .describe(\n \"Visualization mode. 'hexgrid-3d' (default): 3D hexagons. 'hexgrid-2d': flat hexagons. 'square-3d': 3D squares. 'square-2d': flat squares. 'heatmap': density layer.\",\n ),\n metric: z\n .enum(['congestionLevel', 'speed', 'travelTime'])\n .optional()\n .describe('Metric to visualize. Defaults to first fetched metric on the targeted entries.'),\n palette: z\n .enum(areaAnalyticsPalettes)\n .optional()\n .describe(\n \"Cell colour palette, worst traffic at its last colour. 'trafficLight' (green→amber→red, default), 'heat' (blue→orange→red), 'monochrome' (light→dark grey), 'viridis' (yellow→green→blue→purple), 'plasma' (yellow→orange→magenta→purple).\",\n ),\n scaleMode: z\n .enum(['predefinedRange', 'currentRange', 'raw'])\n .optional()\n .describe(\n \"Height scaling mode. 'predefinedRange' (default): the metric's fixed range spans 0 to `maxHeightMeters`. 'currentRange': the loaded data's range does. 'raw': height = metric value × `metersPerUnit`.\",\n ),\n maxHeightMeters: z\n .number()\n .positive()\n .optional()\n .describe(\n \"Height in metres of the tallest cell, under the 'predefinedRange' and 'currentRange' scale modes. Default: metric-dependent.\",\n ),\n metersPerUnit: z\n .number()\n .positive()\n .optional()\n .describe(\n \"Metres of height per unit of the metric, under the 'raw' scale mode (default 1). Setting it switches the scale mode to 'raw'.\",\n ),\n filter: z\n .object({\n min: z.number().optional(),\n max: z.number().optional(),\n })\n .refine((filter) => filter.min !== undefined || filter.max !== undefined, {\n message: 'Filter must have at least one of min or max',\n })\n .optional()\n .describe('Filter visible tiles for the active metric by value range.'),\n} as const;\n\nconst summarySchema = z.object({\n id: z.string(),\n label: z.string(),\n activeMetric: z.string().optional(),\n displayMode: z.string().optional(),\n});\n\n/** Tool schema for update-traffic-area-analytics-display. */\nexport const updateTrafficAreaAnalyticsDisplaySchema = z\n .object({\n add: z\n .array(z.string())\n .optional()\n .describe('Entry ids to render (e.g. [\"tta-0\"]). Use `recallState` to list available ids.'),\n remove: z.array(z.string()).optional().describe('Entry ids to hide.'),\n addMatching: z.array(z.string()).optional().describe('Label substrings to show.'),\n removeMatching: z.array(z.string()).optional().describe('Label substrings to hide.'),\n clear: z.boolean().optional().describe('Hide every displayed entry.'),\n fitBounds: z\n .boolean()\n .optional()\n .describe(\n 'Pan/zoom to fit the union of currently-shown entry bboxes. Defaults to true when entries are added.',\n ),\n ...renderKnobs,\n })\n .refine(\n (data) =>\n data.add !== undefined ||\n data.remove !== undefined ||\n data.addMatching !== undefined ||\n data.removeMatching !== undefined ||\n data.clear === true ||\n data.mode !== undefined ||\n data.metric !== undefined ||\n data.palette !== undefined ||\n data.scaleMode !== undefined ||\n data.maxHeightMeters !== undefined ||\n data.metersPerUnit !== undefined ||\n data.filter !== undefined,\n {\n message:\n 'updateTrafficAreaAnalyticsDisplay requires at least one of: add, remove, addMatching, removeMatching, clear, mode, metric, palette, scaleMode, maxHeightMeters, metersPerUnit, filter.',\n },\n )\n .refine(\n (data) => data.maxHeightMeters === undefined || (data.scaleMode !== 'raw' && data.metersPerUnit === undefined),\n {\n message:\n \"maxHeightMeters applies to the 'predefinedRange' and 'currentRange' scale modes; under 'raw' set metersPerUnit instead.\",\n },\n )\n .refine((data) => data.metersPerUnit === undefined || data.scaleMode === undefined || data.scaleMode === 'raw', {\n message:\n \"metersPerUnit applies to the 'raw' scale mode only; under the other modes set maxHeightMeters instead.\",\n });\n\n/** Output schema for the update-traffic-area-analytics-display tool. */\nexport const updateTrafficAreaAnalyticsDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n totalShown: z.number(),\n shown: z.array(summarySchema).describe('Traffic-area-analytics entries currently on the map.'),\n actuallyAdded: z.array(z.string()),\n actuallyRemoved: z.array(z.string()),\n }),\n toolErrorSchema,\n]);\n\nexport const updateTrafficAreaAnalyticsDisplayDescription =\n 'Show, hide, swap, and restyle HISTORICAL traffic-area-analytics entries on the map in one atomic call. ' +\n 'Target entries by id (`add` / `remove`) or by label substring (`addMatching` / `removeMatching`). ' +\n '`clear` hides every currently-shown entry; combine with `add` to swap. ' +\n 'Render knobs (`mode`, `metric`, `palette`, `scaleMode`, `maxHeightMeters`, `metersPerUnit`, `filter`) apply to every entry being added AND every entry that stays shown after this call. ' +\n 'When only render knobs are set with no add/remove/clear, implicitly targets the most recently added entry. ' +\n 'Not real-time — use `toggleTilesTrafficFlow` for the live vector-tile flow overlay.';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst resolveMatchingIds = (state: ToolState, terms: readonly string[]): string[] => {\n if (terms.length === 0) return [];\n const needles = terms.map((term) => term.toLowerCase());\n const matched = new Set<string>();\n for (const entry of state.trafficAreaAnalytics.entries) {\n if (needles.some((needle) => entry.label.toLowerCase().includes(needle))) {\n matched.add(entry.id);\n }\n }\n return [...matched];\n};\n\ntype ResolvedSelectors = { add: string[]; remove: string[] } | { error: string };\n\nconst resolveSelectors = (\n state: ToolState,\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n): ResolvedSelectors => {\n const { add, remove, addMatching, removeMatching, clear } = params;\n let resolvedAdd = [...(add ?? []), ...resolveMatchingIds(state, addMatching ?? [])];\n const resolvedRemove = [...(remove ?? []), ...resolveMatchingIds(state, removeMatching ?? [])];\n\n // Implicit-latest: render knobs but no entry selectors → target the most recent entry.\n const onlyRenderKnobs =\n add === undefined &&\n addMatching === undefined &&\n remove === undefined &&\n removeMatching === undefined &&\n clear !== true;\n const hasRenderKnobs =\n params.mode !== undefined ||\n params.metric !== undefined ||\n params.palette !== undefined ||\n params.scaleMode !== undefined ||\n params.maxHeightMeters !== undefined ||\n params.metersPerUnit !== undefined ||\n params.filter !== undefined;\n if (onlyRenderKnobs && hasRenderKnobs) {\n const latestId = state.trafficAreaAnalytics.entries.at(-1)?.id;\n if (!latestId) {\n return { error: 'No traffic-area-analytics entries available. Call getTrafficAreaAnalytics first.' };\n }\n resolvedAdd = [latestId];\n }\n\n const knownIds = new Set(state.trafficAreaAnalytics.entries.map((entry) => entry.id));\n const unknownAdd = resolvedAdd.filter((id) => !knownIds.has(id));\n if (unknownAdd.length > 0) {\n return {\n error: `Unknown traffic-area-analytics entry ids: ${unknownAdd.join(', ')}. Use recallState to list available ids.`,\n };\n }\n return { add: resolvedAdd, remove: resolvedRemove };\n};\n\nconst heightConfigOf = ({\n scaleMode,\n maxHeightMeters,\n metersPerUnit,\n}: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>): AreaAnalyticsHeightConfig | undefined => {\n if (scaleMode === 'raw' || metersPerUnit !== undefined) {\n return { scaleMode: 'raw', ...(metersPerUnit !== undefined && { metersPerUnit }) };\n }\n if (scaleMode === undefined && maxHeightMeters === undefined) return undefined;\n return {\n ...(scaleMode !== undefined && { scaleMode }),\n ...(maxHeightMeters !== undefined && { maxHeightMeters }),\n };\n};\n\nconst applyRenderKnobs = async (\n state: ToolState,\n entryId: string,\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n): Promise<void> => {\n const entry = state.trafficAreaAnalytics.findById(entryId);\n if (!entry) return;\n const defaultMetric = entry.data.properties?.metrics?.[0] ?? 'congestionLevel';\n const mode = params.mode ?? 'hexgrid-3d';\n const metric = params.metric ?? defaultMetric;\n\n const module = await state.trafficAreaAnalytics.getEntryModule(entryId);\n module.updateConfig({ displayMode: mode, activeMetric: metric });\n // Set the palette only when this call names one. Without this guard every render-knob call\n // resets the ramp to `trafficLight`, discarding a palette a previous call set; leaving it alone\n // lets that choice persist the same way `mode` and `metric` already do.\n if (params.palette !== undefined) {\n module.setPalette(params.palette);\n }\n const heightConfig = heightConfigOf(params);\n if (heightConfig) {\n module.setHeight(heightConfig);\n }\n module.setFilters(params.filter);\n};\n\nconst summarizeEntry = (entry: TrafficAreaAnalyticsEntry): z.infer<typeof summarySchema> => ({\n id: entry.id,\n label: entry.label,\n ...(entry._config?.activeMetric && { activeMetric: entry._config.activeMetric }),\n ...(entry._config?.displayMode && { displayMode: entry._config.displayMode }),\n});\n\n// ---------------------------------------------------------------------------\n// Executor\n// ---------------------------------------------------------------------------\n\nexport const executeUpdateTrafficAreaAnalyticsDisplay = async (\n params: z.infer<typeof updateTrafficAreaAnalyticsDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateTrafficAreaAnalyticsDisplayOutputSchema>> => {\n try {\n const selectors = resolveSelectors(state, params);\n if ('error' in selectors) return selectors;\n const { add: resolvedAdd, remove: resolvedRemove } = selectors;\n\n const beforeShown = new Set(state.trafficAreaAnalytics.shownEntryIds);\n\n if (params.clear) {\n for (const id of state.trafficAreaAnalytics.shownEntryIds) {\n await state.trafficAreaAnalytics.hideEntry(id);\n }\n }\n for (const id of resolvedRemove) {\n await state.trafficAreaAnalytics.hideEntry(id);\n }\n for (const id of resolvedAdd) {\n await state.trafficAreaAnalytics.showEntry(id);\n }\n\n const afterShown = state.trafficAreaAnalytics.shownEntryIds;\n const actuallyAdded = resolvedAdd.filter((id) => !beforeShown.has(id) && afterShown.has(id));\n const actuallyRemoved = resolvedRemove.filter((id) => beforeShown.has(id) && !afterShown.has(id));\n\n // Render knobs apply to: every entry just added AND every entry currently shown after the call\n // (so existing entries pick up new mode/metric/palette/filter).\n const targets = new Set<string>([...actuallyAdded, ...afterShown]);\n for (const id of targets) await applyRenderKnobs(state, id, params);\n\n const shouldFit = params.fitBounds ?? actuallyAdded.length > 0;\n if (shouldFit && afterShown.size > 0) {\n const features = state.trafficAreaAnalytics.entries\n .filter((e) => afterShown.has(e.id))\n .flatMap((e) => e.data.features);\n fitMapToGeoJSON(state.baseMap.mapLibreMap, features, { pitch: 45 });\n }\n\n const shown = state.trafficAreaAnalytics.entries.filter((e) => afterShown.has(e.id)).map(summarizeEntry);\n\n return {\n success: true as const,\n totalShown: afterShown.size,\n shown,\n actuallyAdded,\n actuallyRemoved,\n };\n } catch (error) {\n return {\n error: `Failed to update traffic-area-analytics display: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n */\n\nimport { z } from 'zod';\nimport type { ToolState } from '../../types';\nimport { toolErrorSchema } from '../shared-output-schemas';\n\n/** Output schema for the update-waypoints-display tool. */\nexport const updateWaypointsDisplayOutputSchema = z.union([\n z.object({\n success: z.literal(true),\n visible: z.boolean(),\n count: z.number(),\n }),\n toolErrorSchema,\n]);\n\n/** Tool schema for update-waypoints-display. */\nexport const updateWaypointsDisplaySchema = z.object({\n visible: z\n .boolean()\n .optional()\n .describe(\n 'Render the staged waypoints (true, default) or clear them (false). No route line is drawn either way.',\n ),\n});\n\nexport const updateWaypointsDisplayDescription =\n 'Show or hide the staged origin/stop/destination markers held in `state.routing.planningSlots` (the slots being ' +\n 'built up for the next `setRoute` — NOT the waypoints of a calculated route entry). ' +\n 'No route line is drawn; for that use `updateRoutesDisplay`. Requires at least one routes entry to host the module.';\n\n/** Execute update-waypoints-display. */\nexport const executeUpdateWaypointsDisplay = async (\n params: z.infer<typeof updateWaypointsDisplaySchema>,\n state: ToolState,\n): Promise<z.infer<typeof updateWaypointsDisplayOutputSchema>> => {\n const visible = params.visible ?? true;\n try {\n const firstShownId = [...state.routing.shownEntryIds][0];\n const latestEntry = state.routing.entries.at(-1);\n const targetEntryId = firstShownId ?? latestEntry?.id;\n if (!targetEntryId) {\n return { error: 'No route history yet — calculate a route via setRoute first.' };\n }\n const routingModule = await state.routing.getEntryRoutingModule(targetEntryId);\n\n if (!visible) {\n await routingModule.clearWaypoints();\n return { success: true, visible: false, count: 0 };\n }\n\n const current = state.routing.planningSlots;\n await routingModule.showWaypoints(current);\n return { success: true, visible: true, count: current.length };\n } catch (error) {\n return {\n error: `Failed to update waypoints display: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n};\n","/**\n * @module agent-toolkit-tools\n *\n * Adding a new default tool here that calls a `@tomtom-org/maps-sdk/services` function directly?\n * Wrap its params with `withAgentToolkitHeaders` (`tools/shared/agent-headers.ts`) before passing\n * them in, so TomTom's traffic analytics can attribute the request to agent-toolkit — see\n * {@link ToolEntry.execute} for the pattern. A missed tag is caught by the guard test in\n * `tools/shared/tests/agent-headers.test.ts`.\n */\n\nimport { trafficIncidentRequestCategories } from '@tomtom-org/maps-sdk/core';\nimport type { DataEntryKind } from '../state';\nimport type { ToolDefinition, ToolState } from '../types';\n\n// --- MapLibre tools ---\nimport {\n executeExecuteMaplibreCode,\n executeFlyTo,\n executeGetMapStyleLayers,\n executeGetViewport,\n executeMaplibreCodeDescription,\n executeMaplibreCodeOutputSchema,\n executeMaplibreCodeSchema,\n executeSetLayoutProperties,\n executeSetPaintProperties,\n executeSetPitchBearing,\n executeZoomInOrOut,\n flyToDescription,\n flyToOutputSchema,\n flyToSchema,\n getMapStyleLayersDescription,\n getMapStyleLayersOutputSchema,\n getMapStyleLayersSchema,\n getViewportDescription,\n getViewportOutputSchema,\n getViewportSchema,\n setLayoutPropertiesDescription,\n setLayoutPropertiesOutputSchema,\n setLayoutPropertiesSchema,\n setPaintPropertiesDescription,\n setPaintPropertiesOutputSchema,\n setPaintPropertiesSchema,\n setPitchBearingDescription,\n setPitchBearingOutputSchema,\n setPitchBearingSchema,\n zoomInOrOutDescription,\n zoomInOrOutOutputSchema,\n zoomInOrOutSchema,\n} from './maplibre';\n\n// --- Service tools ---\nimport {\n addWaypointsToRouteDescription,\n addWaypointsToRouteOutputSchema,\n addWaypointsToRouteSchema,\n discoverPlacesBuilder,\n executeAddWaypointsToRoute,\n executeFindReachableAreas,\n executeGetPoiCategoryCodes,\n executeGetTrafficAreaAnalytics,\n executeGetTrafficIncidents,\n executeLocatePlace,\n executeRemoveWaypointsFromRoute,\n executeReplaceWaypointInRoute,\n executeReverseGeocode,\n executeSetRoute,\n findReachableAreasDescription,\n findReachableAreasOutputSchema,\n findReachableAreasSchema,\n getPoiCategoryCodesDescription,\n getPoiCategoryCodesOutputSchema,\n getPoiCategoryCodesSchema,\n getTrafficAreaAnalyticsDescription,\n getTrafficAreaAnalyticsOutputSchema,\n getTrafficAreaAnalyticsSchema,\n getTrafficIncidentsDescription,\n getTrafficIncidentsOutputSchema,\n getTrafficIncidentsSchema,\n locatePlaceDescription,\n locatePlaceOutputSchema,\n locatePlaceSchema,\n removeWaypointsFromRouteDescription,\n removeWaypointsFromRouteOutputSchema,\n removeWaypointsFromRouteSchema,\n replaceWaypointInRouteDescription,\n replaceWaypointInRouteOutputSchema,\n replaceWaypointInRouteSchema,\n reverseGeocodeDescription,\n reverseGeocodeOutputSchema,\n reverseGeocodeSchema,\n setRouteDescription,\n setRouteOutputSchema,\n setRouteSchema,\n} from './services';\n\n// --- State tools ---\nimport {\n addByodSourceDescription,\n addByodSourceOutputSchema,\n addByodSourceSchema,\n analyseDataBuilder,\n clearTrackerDescription,\n clearTrackerOutputSchema,\n clearTrackerSchema,\n clusterIncidentsBuilder,\n createTrackerDescription,\n createTrackerOutputSchema,\n createTrackerSchema,\n executeAddByodSource,\n executeClearTracker,\n executeCreateTracker,\n executeFocusIncidents,\n executeGetCurrentWaypoints,\n executeGetTrackerHistory,\n executeGetTrackers,\n executeMonitorAnalysis,\n executeResetState,\n executeSetByodLayers,\n executeSetEntryMode,\n executeSetTrafficIncidentsMonitor,\n executeStartRouteMonitor,\n executeStopRouteMonitor,\n focusIncidentsDescription,\n focusIncidentsOutputSchema,\n focusIncidentsSchema,\n getCurrentWaypointsDescription,\n getCurrentWaypointsOutputSchema,\n getCurrentWaypointsSchema,\n getTrackerHistoryDescription,\n getTrackerHistoryOutputSchema,\n getTrackerHistorySchema,\n getTrackersDescription,\n getTrackersOutputSchema,\n getTrackersSchema,\n monitorAnalysisDescription,\n monitorAnalysisOutputSchema,\n monitorAnalysisSchema,\n processDataBuilder,\n recallStateBuilder,\n resetStateDescription,\n resetStateOutputSchema,\n resetStateSchema,\n setByodLayersDescription,\n setByodLayersOutputSchema,\n setByodLayersSchema,\n setEntryModeDescription,\n setEntryModeOutputSchema,\n setEntryModeSchema,\n setTrafficIncidentsMonitorDescription,\n setTrafficIncidentsMonitorOutputSchema,\n setTrafficIncidentsMonitorSchema,\n startRouteMonitorDescription,\n startRouteMonitorOutputSchema,\n startRouteMonitorSchema,\n stopRouteMonitorDescription,\n stopRouteMonitorOutputSchema,\n stopRouteMonitorSchema,\n} from './state';\n\n// --- TomTom Map tools ---\nimport {\n clearMapDescription,\n clearMapOutputSchema,\n clearMapSchema,\n executeClearMap,\n executeGetStandardMapStyles,\n executeSetLanguage,\n executeSetMapStandardStyle,\n executeSetMapStyling,\n executeToggleTilesBaseMapLayerGroups,\n executeToggleTilesPOIs,\n executeToggleTilesTrafficFlow,\n executeToggleTilesTrafficIncidents,\n executeUpdateByodDisplay,\n executeUpdatePlacesDisplay,\n executeUpdateRoutesDisplay,\n executeUpdateTrafficAreaAnalyticsDisplay,\n executeUpdateWaypointsDisplay,\n getStandardMapStylesDescription,\n getStandardMapStylesOutputSchema,\n getStandardMapStylesSchema,\n setLanguageDescription,\n setLanguageOutputSchema,\n setLanguageSchema,\n setMapStandardStyleDescription,\n setMapStandardStyleOutputSchema,\n setMapStandardStyleSchema,\n setMapStylingDescription,\n setMapStylingOutputSchema,\n setMapStylingSchema,\n toggleTilesBaseMapLayerGroupsDescription,\n toggleTilesBaseMapLayerGroupsOutputSchema,\n toggleTilesBaseMapLayerGroupsSchema,\n toggleTilesPOIsDescription,\n toggleTilesPOIsOutputSchema,\n toggleTilesPOIsSchema,\n toggleTilesTrafficFlowDescription,\n toggleTilesTrafficFlowOutputSchema,\n toggleTilesTrafficFlowSchema,\n toggleTilesTrafficIncidentsDescription,\n toggleTilesTrafficIncidentsOutputSchema,\n toggleTilesTrafficIncidentsSchema,\n updateByodDisplayDescription,\n updateByodDisplayOutputSchema,\n updateByodDisplaySchema,\n updatePlacesDisplayDescription,\n updatePlacesDisplayOutputSchema,\n updatePlacesDisplaySchema,\n updateRoutesDisplayDescription,\n updateRoutesDisplayOutputSchema,\n updateRoutesDisplaySchema,\n updateTrafficAreaAnalyticsDisplayDescription,\n updateTrafficAreaAnalyticsDisplayOutputSchema,\n updateTrafficAreaAnalyticsDisplaySchema,\n updateWaypointsDisplayDescription,\n updateWaypointsDisplayOutputSchema,\n updateWaypointsDisplaySchema,\n} from './tomtom-map';\n\n// --- Utility tools ---\nimport {\n calculateBBoxDescription,\n calculateBBoxOutputSchema,\n calculateBBoxSchema,\n createClarifyIntentTool,\n createGetCurrentLocationTool,\n executeCalculateBBox,\n helpDescription,\n helpOutputSchema,\n helpSchema,\n} from './utilities';\n\n// Shared classifier-prompt framing for the two dynamic-JS data tools (`analyseData`,\n// `processData`). Both run user-authored code against the same set of injected per-entry inputs\n// (`placesByEntry` / `routesByEntry` / `incidentsByEntry` / `geometriesByEntry` /\n// `trafficAreaAnalyticsByEntry`); only the *contract* of what the code returns differs. Lifting the common framing here keeps the two\n// `classificationPrompt`s short — the per-tool prompt then only carries the verb (transforms vs\n// aggregates) plus disambiguating triggers.\nconst DATA_TOOL_INPUTS = 'places / routes / incidents / geometries / trafficAreaAnalytics / byod';\nconst DATA_TOOL_SHARED_FRAMING =\n `Dynamic-JS sandbox over any combination of ${DATA_TOOL_INPUTS} entries. ` +\n 'Each input arg is `undefined` when its `*EntryIDs` was omitted.';\n\nconst defaultTools = {\n locatePlace: {\n description: locatePlaceDescription,\n classificationPrompt:\n 'Resolve a single location string (landmark, city, address) to a place without showing it; optionally stages a waypoint slot. ' +\n 'Do NOT call as a precursor to discoverPlaces — discoverPlaces resolves named areas itself via `where.query` (or `where.municipalities` / `where.placeId`).',\n inputSchema: locatePlaceSchema,\n outputSchema: locatePlaceOutputSchema,\n execute: executeLocatePlace,\n tags: ['locate', 'place', 'location', 'waypoint'],\n examples: [\n 'locatePlace(\"Buckingham Palace\", queryAs: \"poi\", waypointIndex: 0) // omit `where` to resolve the name anywhere (global)',\n 'locatePlace(\"Tower Bridge\", queryAs: \"poi\", where: { mode: \"nearby\", viewport: true }, waypointIndex: 1)',\n 'locatePlace(\"Westminster Abbey\", queryAs: \"poi\", where: { mode: \"global\" })',\n 'locatePlace(\"10 Downing Street\", queryAs: \"place\", where: { mode: \"nearby\", position: [4.9, 52.37] })',\n 'locatePlace(\"Amsterdam\", queryAs: \"place\", where: { mode: \"global\" }, entryId: \"amsterdam-city\", geometry: { show: true }) // outline the city',\n 'locatePlace(\"Utrecht\", queryAs: \"place\", where: { mode: \"global\" }, entryId: \"utrecht-city\", geometry: { show: true, mode: \"add\" }) // also outline a second city',\n ],\n examplePrompts: [\n 'Where is the Louvre?',\n 'Pin Central Station on the map',\n 'Show Times Square on the map',\n 'Pin Schiphol Airport on the map',\n 'Outline Amsterdam on the map',\n 'Show the boundary of Berlin',\n // Two WHOLE named cities outlined via repeated `geometry: { mode: \"add\" }` (see examples\n // above) — moved here from discoverPlaces, which the classifier routes past to locatePlace.\n 'Show the boundaries of Amsterdam and Utrecht',\n // Tourist / traveller persona: single named landmark, pin or locate.\n 'Pin the Eiffel Tower on the map',\n 'Where is the Sydney Opera House?',\n ],\n relatedTools: ['setRoute', 'addWaypointsToRoute', 'getCurrentWaypoints', 'recallState'],\n },\n reverseGeocode: {\n description: reverseGeocodeDescription,\n classificationPrompt:\n 'Convert [lng, lat] coordinates to an address; use after map clicks or any tool that returns a position.',\n inputSchema: reverseGeocodeSchema,\n outputSchema: reverseGeocodeOutputSchema,\n execute: executeReverseGeocode,\n tags: ['location', 'place'],\n examples: ['reverseGeocode({ position: [4.9, 52.4] })'],\n // Self-contained coordinate prompt first: it doubles as the canonical scenario (which needs\n // no prior map-click context) and as a concrete first UI suggestion.\n examplePrompts: [\n 'Which city is [4.9, 52.4] in?',\n \"What's the address at this point?\",\n 'Name the place where I clicked',\n ],\n relatedTools: ['getViewport', 'getCurrentLocation', 'updatePlacesDisplay', 'locatePlace'],\n },\n discoverPlaces: discoverPlacesBuilder,\n processData: processDataBuilder({\n classificationPrompt:\n `${DATA_TOOL_SHARED_FRAMING} ` +\n 'WRITES NEW renderable map entries — returns `{ places?, placeConnections?, geometries?, fitOnMap?, byod? }` ' +\n 'to write a places, custom-geometries, or BYOD entry, move the camera, or any combination. ' +\n 'NOT for digests/charts/counts — those are `analyseData` (metadata only, never renders). ' +\n 'Use for: filter/merge places (set ops), connect places (hub-spoke), compute polygons ' +\n '(union/buffer/h3-coverage/difference), focus camera on route sections via `routeUtils.getSectionBBox`, ' +\n 'cross-reference (POIs near route, places-within-reachable, places-inside-high-congestion-tile).',\n tags: ['turf', 'h3', 'geometry', 'place', 'route', 'aggregation', 'connections'],\n examples: [\n // places-only: filter\n 'processData({ placesEntryIDs: [\"places-2\"], code: \"return { places: { type: \\'FeatureCollection\\', features: placesByEntry[\\'places-2\\'].features.filter(p => (p.properties.poi?.name ?? \\'\\').toLowerCase().includes(\\'vegan\\')) } };\", label: \"vegan only\", show: { places: { markerType: \"pin\", zoomMode: \"auto\", hidePreviousEntries: \"all\" } } })',\n // places-only: hub-spoke connections\n 'processData({ placesEntryIDs: [\"places-1\"], code: \"const fc = placesByEntry[\\'places-1\\']; const [hub, ...rest] = fc.features; return { places: fc, placeConnections: rest.map(p => ({ from: hub, to: p })) };\" })',\n // places + geometries (attached polygons): h3 hex coverage of result places\n 'processData({ placesEntryIDs: [\"places-1\"], code: \"const 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\" } } })',\n // routes-only: fit to worst traffic section\n 'processData({ routesEntryIDs: [\"routes-0\"], code: \"const r = routesByEntry[\\'routes-0\\'].features[0]; const sec = (r.properties.sections.traffic ?? []).slice().sort((a,b) => (b.delayInSeconds ?? 0) - (a.delayInSeconds ?? 0))[0]; const bbox = sec ? routeUtils.getSectionBBox(r, sec) : turf.bbox(r); return { fitOnMap: { bbox, padding: 80 } };\" })',\n // routes-only: fit to first toll section\n 'processData({ routesEntryIDs: [\"routes-0\"], code: \"const r = routesByEntry[\\'routes-0\\'].features[0]; const sec = r.properties.sections.toll?.[0]; if (!sec) return { fitOnMap: turf.bbox(r) }; return { fitOnMap: routeUtils.getSectionBBox(r, sec) };\" })',\n // routes + places: corridor filter into new places entry\n '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\" } } })',\n // geometries-only: union → new custom-geometries entry\n '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\" })',\n // geometries-only: buffer each\n '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\" })',\n // geometries + places: inverse (areas in X NOT reachable from Y)\n 'processData({ geometriesEntryIDs: [{ kind: \"place\", id: \"amsterdam-id\" }, { kind: \"ranges\", id: \"ranges-0\" }], code: \"const 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\" } } })',\n // geometries (ranges) + places cross-ref: keep places inside the reachable area\n 'processData({ geometriesEntryIDs: [{ kind: \"ranges\", id: \"ranges-0\" }, { kind: \"places\", id: \"ev-stations\" }], code: \"const ranges = 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\" })',\n // trafficAreaAnalytics + places cross-ref: keep places inside high-congestion tiles\n '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\" })',\n // trafficAreaAnalytics → derived geometry entry: hot-tiles polygon\n '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\" } } })',\n ],\n examplePrompts: [\n 'Keep only the vegan restaurants',\n 'Draw lines from this hub to every nearby cafe',\n 'Show the h3 hex coverage of these places',\n 'Fit the map to the worst traffic section of the route',\n 'Fit the map to the first toll section of the route',\n 'Filter my loaded EV stations to those within 500 m of the route',\n 'Union the city boundaries I loaded into one custom polygon',\n 'Compute a 500 m buffer around the neighbourhood I loaded',\n 'Areas of Amsterdam not reachable from the hospitals',\n 'Filter loaded places to those inside the reachable area',\n 'Keep only the EV stations that fall inside high-congestion tiles',\n 'Create a custom polygon outlining the congestion hot zone in my loaded analytics',\n ],\n relatedTools: ['analyseData', 'recallState'],\n dependsOn: [\n 'discoverPlaces',\n 'locatePlace',\n 'setRoute',\n 'getTrafficIncidents',\n 'getTrafficAreaAnalytics',\n 'findReachableAreas',\n 'addByodSource',\n 'recallState',\n ],\n }),\n analyseData: analyseDataBuilder({\n classificationPrompt:\n `${DATA_TOOL_SHARED_FRAMING} ` +\n 'WRITES metadata only — returns JSON or a Chart.js config (`outputFormat: \"chart\"`), attached as ' +\n '`_analysis[name]` on every contributing entry. Never renders or creates new map entries (that is ' +\n '`processData`). ' +\n 'Use for: counts / groupings / charts (by-category, hex density); route summaries (per-leg / per-country / ' +\n 'alternative compare); route TRAFFIC analysis from `route.properties.sections.traffic[]` — pick THIS for ' +\n '\"distribution / count / chart of <something> along the route\", NOT `getTrafficIncidents`; geometry stats ' +\n '(areas, h3 coverage); historical traffic-area-analytics breakdowns (congestion / speed / travel-time per ' +\n 'tile); cross-kind correlations (incidents-per-cluster, places-near-route, distance-to-route bins, ' +\n 'route-segments-by-tile-metric). ' +\n 'One-shot by default — it returns an `analysisId`; pass that to `monitorAnalysis` to keep the analysis ' +\n 'recomputing whenever its source entries change (the recurring sandbox then also sees `previous` / `now`).',\n tags: ['analysis', 'turf', 'h3', 'place', 'route', 'geometry'],\n examples: [\n // places-only: counts\n '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 };\" })',\n // places-only: h3 density\n '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 };\" })',\n // places-only: chart\n '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]) }] } };\" })',\n // routes-only: per-route summary\n '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 }));\" })',\n // routes-only: alternative-comparison chart\n '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)) }] } };\" })',\n // routes-only: distribution of traffic incidents ALONG the route (reads route.properties.sections.traffic[])\n 'analyseData({ routesEntryIDs: [\"routes-0\"], name: \"on-route-incidents-by-category\", outputFormat: \"chart\", code: \"const sections = (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\\' } } } };\" })',\n // routes-only: total delay per country section\n 'analyseData({ routesEntryIDs: [\"routes-0\"], name: \"delay-by-country\", code: \"const r = routesByEntry[\\'routes-0\\'].features[0]; const byCountry = {}; for (const c of (r.properties.sections.country ?? [])) byCountry[c.countryCodeISO3] = (byCountry[c.countryCodeISO3] ?? 0) + (r.properties.sections.traffic ?? []).filter(t => t.startPointIndex >= c.startPointIndex && t.endPointIndex <= c.endPointIndex).reduce((s, t) => s + (t.delayInSeconds ?? 0), 0); return byCountry;\" })',\n // routes + places cross-ref (count within distance)\n '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 };\" })',\n // routes + places cross-ref (distance-to-route histogram, binned in km)\n \"analyseData({ routesEntryIDs: [\\\"routes-0\\\"], placesEntryIDs: [\\\"cafes\\\"], name: \\\"cafe-distance-bins-km\\\", outputFormat: \\\"chart\\\", code: \\\"const route = 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' } } } };\\\" })\",\n // geometries-only: areas\n '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) }));\" })',\n // geometries-only: chart\n '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)) }] } };\" })',\n // cross-kind: incidents per place cluster\n '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;\" })',\n // cross-kind: incidents inside custom corridors\n '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;\" })',\n // trafficAreaAnalytics-only: congestion distribution chart\n \"analyseData({ trafficAreaAnalyticsEntryIDs: [\\\"tta-0\\\"], name: \\\"congestion-distribution\\\", outputFormat: \\\"chart\\\", code: \\\"const bins = [0, 25, 50, 75, 100]; const counts = new Array(bins.length).fill(0); for (const t of 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) }] } };\\\" })\",\n // cross-kind: places by congestion tile\n '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;\" })',\n // cross-kind: route segments by tile metric\n '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 };\" })',\n // incidents-only: counts by category (one-shot)\n '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 };\" })',\n // incidents-only: top-N by delay (one-shot)\n '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] }));\" })',\n ],\n examplePrompts: [\n 'How many of each POI category are in these results?',\n 'h3 hex density of these places',\n 'Bar chart of the top POI categories',\n 'Bar chart of the lengths of my route alternatives',\n 'Bar chart of travel times across my route alternatives',\n 'Of my loaded EV stations, how many are within 500 m of my route?',\n 'Chart the distance from each cafe to the route, binned in km',\n 'Histogram of place-to-route distances',\n 'Distribution by category of the traffic incidents I loaded along my route',\n 'Chart the incidents on the route by category',\n 'Total delay by country for the incidents on my route',\n 'How big are the city areas I loaded?',\n 'Bar chart of the sizes of the city areas I loaded',\n 'Count the loaded incidents per cluster of my loaded places',\n 'Total delay per corridor for my loaded route incidents',\n 'Congestion distribution across the analytics tiles',\n 'Chart the average historical speed per tile in my loaded analytics',\n 'Group these EV stations by the congestion level of the tile they fall in',\n 'For the route, which tiles does it cross and what is their average congestion?',\n 'How many incidents of each category?',\n 'Top 5 incidents by delay',\n 'Total delay per road across my loaded incidents',\n 'Bar chart of incidents per category',\n ],\n relatedTools: ['processData', 'focusIncidents', 'recallState'],\n dependsOn: [\n 'discoverPlaces',\n 'locatePlace',\n 'setRoute',\n 'getTrafficIncidents',\n 'getTrafficAreaAnalytics',\n 'findReachableAreas',\n 'addByodSource',\n 'processData',\n 'recallState',\n ],\n }),\n setRoute: {\n description: setRouteDescription,\n classificationPrompt:\n 'Use to define a new route or change route waypoints/options. Pass `locations` for a new origin/stops/destination, `parameters` to update cost model / alternatives / when, or both together. Do not use to show, query, or modify an existing displayed route. To MONITOR / track / keep fresh the live traffic BETWEEN two places, START here (calculate with traffic \"historical\" + maxAlternatives 1-2 for stable paths), then pair with startRouteMonitor — startRouteMonitor needs a route to exist first.',\n inputSchema: setRouteSchema,\n outputSchema: setRouteOutputSchema,\n execute: executeSetRoute,\n tags: ['route', 'waypoint', 'location', 'traffic'],\n examples: [\n 'setRoute({ locations: [{ query: \"Amsterdam\" }, { query: \"Brussels\" }] })',\n 'setRoute({ locations: [{ query: \"Eiffel Tower\" }, { query: \"Louvre Museum\" }, { query: \"Notre Dame\" }] })',\n 'setRoute({ parameters: { maxAlternatives: 2 } })',\n 'setRoute({ parameters: { costModel: { routeType: \"fast\", avoid: [\"tollRoads\"] } } })',\n 'setRoute({ parameters: { when: { option: \"departAt\", date: \"2025-06-01T09:00:00Z\" } } })',\n 'setRoute({ locations: [{ query: \"Paris\" }, { query: \"Lyon\" }], parameters: { costModel: { routeType: \"short\" } } })',\n // Monitoring in one call: historical paths + alternatives + monitor:true (no startRouteMonitor needed).\n 'setRoute({ locations: [{ query: \"Paris\" }, { query: \"Amsterdam\" }], parameters: { maxAlternatives: 2, costModel: { traffic: \"historical\" } }, monitor: true })',\n ],\n examplePrompts: [\n 'Get directions from Paris to Lyon',\n 'Route me from Amsterdam to Brussels',\n 'Plan a new trip from Amsterdam to Schiphol airport',\n 'Recalculate my route to Brussels from Schiphol airport',\n 'Recalculate the route from Utrecht to Brussels',\n 'Avoid toll roads on my current route',\n 'Find the fastest route from Berlin to Munich',\n 'Show 2 alternative routes for my current trip',\n 'Depart at 9am on my current route',\n // Monitoring intents enter through setRoute (it builds the route startRouteMonitor then watches).\n 'Monitor traffic between Paris and Amsterdam',\n 'Monitor traffic between Santa Monica and Downtown LA',\n 'Keep an eye on live traffic from the office to the airport',\n 'Show me fresh traffic between A and B',\n // Leisure road-tripper / multi-stop traveller persona.\n 'Plan a road trip from Lisbon to Porto',\n 'Take me from the Eiffel Tower to the Arc de Triomphe',\n ],\n relatedTools: [\n 'startRouteMonitor',\n 'updateRoutesDisplay',\n 'addWaypointsToRoute',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n 'recallState',\n ],\n },\n addWaypointsToRoute: {\n description: addWaypointsToRouteDescription,\n classificationPrompt:\n 'Extend the route by prepending a new origin and/or appending a new destination (preserving the existing endpoints as stops), and/or inserting intermediate stops at optimal positions, then recalculate. Use setRoute when the user wants to fully replace waypoints.',\n inputSchema: addWaypointsToRouteSchema,\n outputSchema: addWaypointsToRouteOutputSchema,\n execute: executeAddWaypointsToRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'addWaypointsToRoute({ stops: [{ query: \"Westminster Abbey\" }] }) // single intermediate stop',\n 'addWaypointsToRoute({ stops: [{ query: \"Reims\" }, { query: \"Metz\" }] }) // multiple stops, settle into along-route order',\n 'addWaypointsToRoute({ origin: { query: \"my house\" } }) // prepend new origin; previous origin becomes a stop',\n 'addWaypointsToRoute({ destination: { query: \"Heathrow Airport\" } }) // append new destination; previous destination becomes a stop',\n 'addWaypointsToRoute({ origin: { query: \"Schiphol\" }, destination: { query: \"Brussels\" } }) // extend on both ends',\n 'addWaypointsToRoute({ origin: { query: \"X\" }, stops: [{ query: \"Y\" }] }) // combine endpoint extension and stops',\n ],\n examplePrompts: [\n 'Add a stop at Westminster Abbey',\n 'Add stops at Reims and Metz on the way',\n 'Pick me up from my house first, then continue the trip',\n 'Continue past the destination to Heathrow',\n ],\n relatedTools: [\n 'locatePlace',\n 'setRoute',\n 'updateRoutesDisplay',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n ],\n dependsOn: ['setRoute'],\n },\n removeWaypointsFromRoute: {\n description: removeWaypointsFromRouteDescription,\n classificationPrompt: 'Remove one or more waypoints by index from the current route and recalculate.',\n inputSchema: removeWaypointsFromRouteSchema,\n outputSchema: removeWaypointsFromRouteOutputSchema,\n execute: executeRemoveWaypointsFromRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'removeWaypointsFromRoute({ waypointIndices: [1] }) // remove a single stop',\n 'removeWaypointsFromRoute({ waypointIndices: [1, 2] }) // remove multiple stops in one call',\n ],\n examplePrompts: [\n 'Remove the second stop from my route',\n 'Delete the Utrecht stop',\n 'Drop the last two intermediate stops',\n ],\n relatedTools: ['addWaypointsToRoute', 'replaceWaypointInRoute', 'setRoute', 'updateRoutesDisplay'],\n dependsOn: ['setRoute'],\n },\n replaceWaypointInRoute: {\n description: replaceWaypointInRouteDescription,\n classificationPrompt:\n \"Overwrite a single waypoint of the current route in place — by 'origin', 'destination', or numeric index — and recalculate. Other waypoints are kept.\",\n inputSchema: replaceWaypointInRouteSchema,\n outputSchema: replaceWaypointInRouteOutputSchema,\n execute: executeReplaceWaypointInRoute,\n tags: ['route', 'waypoint'],\n examples: [\n 'replaceWaypointInRoute({ waypointIndex: \"origin\", location: { query: \"Schiphol\" } })',\n 'replaceWaypointInRoute({ waypointIndex: \"destination\", location: { query: \"Heathrow\" } })',\n 'replaceWaypointInRoute({ waypointIndex: 1, location: { query: \"Utrecht\" } }) // overwrite the first intermediate stop',\n ],\n examplePrompts: [\n 'Change the origin to Schiphol',\n 'Make Heathrow my destination',\n 'Replace the second stop with Utrecht',\n ],\n relatedTools: ['addWaypointsToRoute', 'removeWaypointsFromRoute', 'setRoute', 'locatePlace'],\n dependsOn: ['setRoute'],\n },\n getPOICategoryCodes: {\n description: getPoiCategoryCodesDescription,\n classificationPrompt:\n 'Look up or list POICategory codes (e.g. \"what is the code for gym?\"). Not a step before discoverPlaces, which takes category words itself.',\n inputSchema: getPoiCategoryCodesSchema,\n outputSchema: getPoiCategoryCodesOutputSchema,\n execute: executeGetPoiCategoryCodes,\n tags: ['discover', 'place'],\n examples: [\n 'getPOICategoryCodes()',\n 'getPOICategoryCodes(filters: [\"gym\"])',\n 'getPOICategoryCodes(filters: [\"italian restaurant\"]) // multi-word phrase, best match first',\n 'getPOICategoryCodes(filters: [\"restaurant\"], language: \"fr-FR\")',\n ],\n examplePrompts: ['What POI categories are available?', \"What's the code for gym?\"],\n relatedTools: ['discoverPlaces', 'toggleTilesPOIs'],\n },\n getTrafficIncidents: {\n description: getTrafficIncidentsDescription,\n classificationPrompt:\n 'AREA-BOUND incident loader — bbox / viewport / \"incidents around here\" / \"accidents in <city>\". ' +\n // Built from the canonical category list so the classifier hint can't drift from the schema.\n `Covers every incident category: ${trafficIncidentRequestCategories.join(', ')}. ` +\n 'DO NOT pick for route-bound queries (\"incidents ALONG / ON the route\", \"delays on the drive from X to Y\"): ' +\n 'a calculated route already carries its incidents in `route.properties.sections.traffic[]` — call ' +\n '`analyseData` with `routesEntryIDs` for those. Monitoring is built in: a shown fetch keeps the entry live ' +\n '(`monitor: true` by default; `monitor: false` for a one-shot count) — no separate ' +\n 'setTrafficIncidentsMonitor call needed. Co-pick analyseData (aggregations / charts / clusters; pass ' +\n '`monitor: { entryId }` for the recurring spec) and focusIncidents (highlight a subset) when the query implies them.',\n inputSchema: getTrafficIncidentsSchema,\n outputSchema: getTrafficIncidentsOutputSchema,\n execute: executeGetTrafficIncidents,\n tags: ['traffic', 'location'],\n examples: [\n 'getTrafficIncidents({}) // omit `where` → current viewport',\n 'getTrafficIncidents({ where: { mode: \"within\", boundingBox: [4.728, 52.278, 5.080, 52.479] } })',\n 'getTrafficIncidents({ where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }] } }) // named area',\n 'getTrafficIncidents({ where: { mode: \"within\", queries: [{ query: \"Amsterdam\" }] }, categoryFilter: [\"accident\", \"jam\"] })',\n 'getTrafficIncidents({ where: { mode: \"within\", route: { widthMeters: 1000 } }, categoryFilter: [\"roadworks\"], timeValidityFilter: [\"future\"] }) // corridor around latest route',\n 'getTrafficIncidents({ where: { mode: \"within\", range: \"ranges-0\" } }) // inside a reachable area',\n ],\n examplePrompts: [\n 'Fetch traffic incidents in Amsterdam',\n 'Any accidents in this area?',\n 'Show roadworks across the city',\n // \"Watch this area…\" is a fetch + monitor — getTrafficIncidents monitors by default, so it\n // owns this prompt now (not setTrafficIncidentsMonitor, which is for an earlier entry).\n 'Watch this area for new accidents',\n // Commuter / dispatcher persona: area-bound incident lookups by category.\n 'Are there any road closures downtown right now?',\n 'Show me the jams around the Amsterdam ring road',\n // Counter-examples: NOT this tool — route-bound queries use analyseData over routes.\n ],\n relatedTools: ['calculateBBox', 'getViewport', 'analyseData', 'focusIncidents', 'setTrafficIncidentsMonitor'],\n },\n focusIncidents: {\n description: focusIncidentsDescription,\n classificationPrompt:\n 'Highlight a subset of incidents on an existing entry (drives map dim/highlight + FocusChip). ' +\n 'Pick for \"focus on…\", \"show only…\", \"highlight the…\". Compose with analyseData to compute the ids.',\n inputSchema: focusIncidentsSchema,\n outputSchema: focusIncidentsOutputSchema,\n execute: executeFocusIncidents,\n tags: ['traffic', 'incident', 'state', 'ui'],\n examples: [\n \"focusIncidents({ incidentsEntryID: 'incidents-0', incidentIds: ['TTI-...', 'TTI-...'], reason: 'top 3 by delay' })\",\n ],\n examplePrompts: [\n 'Focus on the 3 worst incidents by delay',\n 'Highlight the closures',\n 'Show only the incidents on the A4',\n ],\n relatedTools: ['analyseData', 'getTrafficIncidents'],\n dependsOn: ['getTrafficIncidents'],\n },\n clusterIncidents: clusterIncidentsBuilder({\n classificationPrompt:\n 'DBSCAN clustering of loaded traffic incidents — \"clusters / hotspots / dense pockets / worst areas\". ' +\n 'Produces stable IDs, road/category labels, delay aggregates, trend detection. Auto-reruns on monitor ticks. ' +\n 'Also handles clustering of a FILTERED subset (\"only jams / accidents\", \"delayed over N minutes\", ' +\n '\"along this route / within this area\") via generated `code`. Co-pick with getTrafficIncidents (which ' +\n 'keeps the entry monitored by default).',\n tags: ['traffic', 'incident', 'analysis', 'clustering'],\n examples: [\n \"clusterIncidents({ incidentsEntryID: 'incidents-0' })\",\n \"clusterIncidents({ incidentsEntryID: 'incidents-0', eps: 0.3, minMembers: 4, maxClusters: 8 })\",\n // Dynamic `code`: filter the incidents (real shape — `properties.delayInSeconds`) then cluster.\n \"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);\\\" })\",\n \"clusterIncidents({ incidentsEntryID: 'incidents-0', code: \\\"const jams = incidentsByEntry['incidents-0'].filter(i => i.properties.category === 'jam'); return cluster(jams, {}, previous, now);\\\" })\",\n ],\n examplePrompts: [\n 'Cluster the incidents',\n 'Show me the hotspots',\n 'Where are the worst congestion pockets?',\n 'Group the incidents into clusters',\n // New code-generation capability: clustering of a dynamically-filtered subset.\n 'Cluster only the major jams',\n 'Where are the hotspots of incidents delayed more than 5 minutes?',\n 'Cluster just the accidents and road closures',\n ],\n relatedTools: ['getTrafficIncidents', 'setTrafficIncidentsMonitor', 'analyseData', 'focusIncidents'],\n dependsOn: ['getTrafficIncidents'],\n }),\n monitorAnalysis: {\n description: monitorAnalysisDescription,\n classificationPrompt:\n 'Start or stop MONITORING an existing analysis by its `analysisId` (from analyseData) — a pure on/off ' +\n 'switch that recomputes the analysis on every change to its source entries (incidents monitor ticks, ' +\n 're-searched places, recalculated routes, refreshed data). When the user points at an analysis ALREADY ' +\n 'in state (\"that count\", \"the breakdown you computed\") and wants it kept live — \"keep that count updated ' +\n 'and tell me when it grows or fades\", \"stop tracking that analysis\" — pick THIS ALONE: the analysis ' +\n 'already exists, so do NOT re-fetch the data or arm a tracker. \"Tell me when it grows or fades\" about an ' +\n 'existing computed metric is monitorAnalysis, not createTracker. Define the computation with analyseData ' +\n 'first (a ONE-SHOT analysis needs analyseData alone); for a one-off highlight use focusIncidents.',\n inputSchema: monitorAnalysisSchema,\n outputSchema: monitorAnalysisOutputSchema,\n execute: executeMonitorAnalysis,\n tags: ['analysis', 'monitor'],\n examples: [\n 'monitorAnalysis({ analysisId: \"rolling-count::incidents-0\", enabled: true })',\n 'monitorAnalysis({ analysisId: \"ev-near-route::places-0,route-1\", enabled: false })',\n ],\n // Follow-up phrased — monitorAnalysis toggles an analysis analyseData already created, so its\n // prompts reference an existing one (\"that count / that analysis\") rather than a cold request.\n examplePrompts: [\n 'Keep that incident count updated and tell me when it grows or fades',\n 'Stop tracking that analysis',\n ],\n relatedTools: ['analyseData', 'getTrafficIncidents', 'setTrafficIncidentsMonitor', 'focusIncidents'],\n dependsOn: ['analyseData'],\n },\n createTracker: {\n description: createTrackerDescription,\n classificationPrompt:\n 'Arm a TRACKER (a watch that ALERTS) on already-loaded entries: you write `code` returning ' +\n '`{ active, members, summary }`, and it recomputes whenever the watched entries change, logging an ' +\n 'alert when the condition turns true and an event when it clears. Pick for \"alert me when…\", \"let me ' +\n 'know if…\", \"watch for…\", \"notify when an incident appears near…\". Reads multiple entry kinds at once ' +\n '(e.g. incidents near hospitals/places). The watched entries must already be loaded — for a fresh ' +\n 'area co-pick getTrafficIncidents (load the incidents) and locatePlace (resolve a named area) so the ' +\n 'tracker has real entry ids to read. CONTRAST analyseData (one-shot, no alerts) and monitorAnalysis: ' +\n 'keeping an EXISTING analysis fresh — \"keep that count updated, tell me when it grows or fades\" — is ' +\n 'monitorAnalysis, NOT a tracker. createTracker defines a NEW condition in `code` from scratch.',\n inputSchema: createTrackerSchema,\n outputSchema: createTrackerOutputSchema,\n execute: executeCreateTracker as (input: unknown, state: ToolState) => Promise<unknown>,\n tags: ['tracker', 'alert', 'monitor', 'turf'],\n examples: [\n '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\\' };\" })',\n \"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' };\\\" })\",\n ],\n examplePrompts: [\n 'Alert me when there is a major incident in this area',\n 'Let me know if an incident appears within 100m of a hospital',\n 'Watch these incidents and tell me when a jam forms near my route',\n ],\n relatedTools: ['getTrafficIncidents', 'analyseData', 'clearTracker', 'getTrackerHistory', 'getTrackers'],\n dependsOn: ['getTrafficIncidents', 'locatePlace', 'analyseData'],\n },\n clearTracker: {\n description: clearTrackerDescription,\n classificationPrompt:\n 'Stop a tracker armed by createTracker — \"stop watching / stop alerting / clear that tracker\". Omit ' +\n 'trackerId when only one exists. Does not touch the watched entries.',\n inputSchema: clearTrackerSchema,\n outputSchema: clearTrackerOutputSchema,\n execute: executeClearTracker as (input: unknown, state: ToolState) => Promise<unknown>,\n tags: ['tracker', 'alert'],\n examples: ['clearTracker({})', 'clearTracker({ trackerId: \"incidents-near-hospitals\" })'],\n examplePrompts: ['Stop that tracker', 'Stop alerting me about incidents near hospitals'],\n relatedTools: ['createTracker', 'getTrackers'],\n },\n getTrackerHistory: {\n description: getTrackerHistoryDescription,\n classificationPrompt:\n 'Read what trackers have FIRED — the alert/event log (\"what alerts fired\", \"what happened with my ' +\n 'trackers\", \"any incidents near hospitals today\"). Scope to one trackerId or a sinceMs cutoff.',\n inputSchema: getTrackerHistorySchema,\n outputSchema: getTrackerHistoryOutputSchema,\n execute: executeGetTrackerHistory as (input: unknown, state: ToolState) => Promise<unknown>,\n tags: ['tracker', 'alert', 'history'],\n examples: ['getTrackerHistory({})', 'getTrackerHistory({ trackerId: \"incidents-near-hospitals\" })'],\n examplePrompts: ['What alerts have fired?', 'Show me the history for that tracker'],\n relatedTools: ['createTracker', 'getTrackers'],\n },\n getTrackers: {\n description: getTrackersDescription,\n classificationPrompt:\n 'List the active trackers and their live state — \"what am I tracking / what alerts are set / is ' +\n 'anything firing\". Read-only.',\n inputSchema: getTrackersSchema,\n outputSchema: getTrackersOutputSchema,\n execute: executeGetTrackers as (input: unknown, state: ToolState) => Promise<unknown>,\n tags: ['tracker', 'alert'],\n examples: ['getTrackers({})'],\n examplePrompts: ['What am I tracking?', 'Which alerts are set up?'],\n relatedTools: ['createTracker', 'getTrackerHistory'],\n },\n setTrafficIncidentsMonitor: {\n description: setTrafficIncidentsMonitorDescription,\n classificationPrompt:\n 'Arm or pause background polling on an ALREADY-loaded incidents entry — one toggle (`enabled: true/false`). ' +\n 'Enable for \"keep updated\"/\"watch\"/\"live\"/\"refresh every N seconds\" referring to incidents loaded on an ' +\n 'EARLIER turn; disable for \"stop refreshing\"/\"pause updates\". A fresh getTrafficIncidents fetch already ' +\n 'monitors by default, so do not co-pick this to start monitoring right after it.',\n inputSchema: setTrafficIncidentsMonitorSchema,\n outputSchema: setTrafficIncidentsMonitorOutputSchema,\n execute: executeSetTrafficIncidentsMonitor,\n tags: ['traffic', 'incident', 'state'],\n examples: [\n \"setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: true })\",\n \"setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: true, intervalMs: 30000 })\",\n \"setTrafficIncidentsMonitor({ incidentsEntryID: 'incidents-0', enabled: false })\",\n ],\n examplePrompts: [\n 'Keep the incidents I loaded updated',\n 'Refresh the incidents every 30 seconds',\n 'Stop refreshing the incidents',\n ],\n relatedTools: ['getTrafficIncidents', 'analyseData', 'focusIncidents'],\n dependsOn: ['getTrafficIncidents'],\n },\n startRouteMonitor: {\n description: startRouteMonitorDescription,\n classificationPrompt:\n 'Arm background recalculation on an ALREADY-calculated route (default 60s): re-runs the SAME route ' +\n 'each tick so its live-traffic delays stay current. Pick for \"keep this route updated\" / \"watch the ' +\n 'traffic on this drive\" / \"recalculate every N seconds\". Best paired with a route calculated as ' +\n 'traffic \"historical\" + maxAlternatives 1-2 (stable typical paths, fresh delays). Not needed just to ' +\n 'calculate a route — that is setRoute.',\n inputSchema: startRouteMonitorSchema,\n outputSchema: startRouteMonitorOutputSchema,\n execute: executeStartRouteMonitor,\n tags: ['route', 'traffic', 'state'],\n examples: [\n \"startRouteMonitor({ routesEntryID: 'routes-0' })\",\n \"startRouteMonitor({ routesEntryID: 'routes-0', intervalMs: 30000 })\",\n ],\n examplePrompts: [\n 'Keep this route updated with live traffic',\n 'Watch the traffic on the drive from Paris to Amsterdam',\n 'Recalculate this route every 30 seconds',\n 'Monitor traffic between these places',\n ],\n relatedTools: ['setRoute', 'stopRouteMonitor', 'updateRoutesDisplay', 'analyseData'],\n dependsOn: ['setRoute'],\n },\n stopRouteMonitor: {\n description: stopRouteMonitorDescription,\n classificationPrompt:\n 'Stop recalculating a route entry; the entry stays in state with its last data. Pick for \"stop ' +\n 'refreshing the route\" / \"pause traffic updates on this drive\".',\n inputSchema: stopRouteMonitorSchema,\n outputSchema: stopRouteMonitorOutputSchema,\n execute: executeStopRouteMonitor,\n tags: ['route', 'traffic', 'state'],\n examples: [\"stopRouteMonitor({ routesEntryID: 'routes-0' })\"],\n examplePrompts: ['Stop refreshing the route', 'Pause traffic updates on this drive'],\n relatedTools: ['startRouteMonitor', 'setRoute'],\n dependsOn: ['startRouteMonitor'],\n },\n getTrafficAreaAnalytics: {\n description: getTrafficAreaAnalyticsDescription,\n classificationPrompt:\n 'Fetch historical traffic analytics (speed, congestion, travel time) for an area. Use analyseData over the ' +\n 'loaded entry for breakdowns (per-day / per-hour / worst-day) without re-fetching.',\n inputSchema: getTrafficAreaAnalyticsSchema,\n outputSchema: getTrafficAreaAnalyticsOutputSchema,\n execute: executeGetTrafficAreaAnalytics,\n tags: ['traffic', 'location'],\n examples: [\n \"getTrafficAreaAnalytics({ bbox: [4.728, 52.278, 5.080, 52.479], metrics: ['speed', 'congestionLevel'], startDate: '2025-03-01', endDate: '2025-03-07' })\",\n \"getTrafficAreaAnalytics({ bbox: [...], metrics: ['congestionLevel'], startDate: '2025-06-02', hours: [5, 6, 7, 15, 16] }) // Amsterdam summer rush hours [7, 8, 9, 17, 18] local (UTC+2), in UTC\",\n ],\n examplePrompts: [\n 'What was the average congestion in Amsterdam last week?',\n 'Analyze traffic patterns during rush hour in this area',\n 'Show me historical speed data for the city center',\n ],\n relatedTools: ['analyseData', 'updateTrafficAreaAnalyticsDisplay', 'calculateBBox', 'getViewport'],\n dependsOn: ['locatePlace'],\n },\n updatePlacesDisplay: {\n description: updatePlacesDisplayDescription,\n classificationPrompt:\n 'Show, hide, and swap places ENTRIES already in state on the map — add some (as pins, base-map-style ' +\n 'markers via `markerType`, and/or boundary polygons), remove others, swap atomically, or render ' +\n 'boundaries. Owns rendering a stored entry with base-map-style markers; NOT the base-map tile POI icons ' +\n '(those are toggleTilesPOIs).',\n inputSchema: updatePlacesDisplaySchema,\n outputSchema: updatePlacesDisplayOutputSchema,\n execute: executeUpdatePlacesDisplay,\n tags: ['place', 'location', 'map style', 'boundary', 'outline'],\n examples: [\n 'updatePlacesDisplay({ add: [\"places-2\"] })',\n 'updatePlacesDisplay({ add: [\"places-1\", \"places-3\"], markerType: \"base-map\" })',\n 'updatePlacesDisplay({ remove: [\"places-1\"] })',\n 'updatePlacesDisplay({ removeMatching: [\"cinema\"] }) // hide every entry labelled \"cinema\"',\n 'updatePlacesDisplay({ addMatching: [\"cafe\"] })',\n 'updatePlacesDisplay({ clear: true })',\n 'updatePlacesDisplay({ clear: true, add: [\"places-2\"] }) // atomic swap: hide everything, show this',\n 'updatePlacesDisplay({ markerType: \"pin\" }) // re-pin the most recently stored entry',\n 'updatePlacesDisplay({ geometry: {} }) // outline the most recently stored entry',\n 'updatePlacesDisplay({ add: [\"places-2\"], geometry: { mode: \"add\" } }) // also outline on top of existing boundaries',\n 'updatePlacesDisplay({ add: [\"places-2\"], markerType: \"pin\", geometry: { fillStyle: \"outline\" } }) // pin AND outline',\n 'updatePlacesDisplay({ add: [\"places-2\"], geometry: { placeIds: [\"G55fc4abe-...\"], fillStyle: \"outline\" } }) // outline a subset',\n ],\n examplePrompts: [\n 'Show the results on the map',\n 'Pin the search results',\n 'Hide the banks',\n 'Also show the cafes on the map',\n 'Additionally pin the pubs',\n 'Remove the banks from the map',\n 'Drop every entry labelled \"parking\" from the map',\n 'Toggle off the hotels without clearing the rest',\n 'Replace the pins with the new set',\n 'Render entry places-3 with the base-map marker style',\n 'Show the boundaries of the neighbourhoods I loaded',\n 'Outline these neighbourhoods',\n 'Highlight the shape of this place',\n 'Pin the cities and outline them too',\n 'Also outline Utrecht while keeping Amsterdam drawn',\n 'Add the neighbourhood boundaries on top of the pins',\n ],\n relatedTools: ['discoverPlaces', 'locatePlace', 'reverseGeocode', 'recallState', 'clearMap', 'flyTo'],\n dependsOn: ['discoverPlaces', 'locatePlace', 'processData'],\n },\n updateRoutesDisplay: {\n description: updateRoutesDisplayDescription,\n classificationPrompt:\n 'Show, hide, swap, and recolor routes entries on the map — add some, remove others, swap atomically, switch the highlighted alternative, or set `color` (CSS color / hex; sticky across entry swaps).',\n inputSchema: updateRoutesDisplaySchema,\n outputSchema: updateRoutesDisplayOutputSchema,\n execute: executeUpdateRoutesDisplay,\n tags: ['route', 'waypoint', 'map style'],\n examples: [\n 'updateRoutesDisplay({ add: [\"routes-1\"] })',\n 'updateRoutesDisplay({ add: [\"routes-0\", \"routes-2\"] })',\n 'updateRoutesDisplay({ remove: [\"routes-1\"] })',\n 'updateRoutesDisplay({ removeMatching: [\"Brussels\"] }) // hide every entry whose label mentions \"Brussels\"',\n 'updateRoutesDisplay({ addMatching: [\"Edinburgh\"] })',\n 'updateRoutesDisplay({ clear: true })',\n 'updateRoutesDisplay({ clear: true, add: [\"routes-2\"] }) // atomic swap: hide everything, show this',\n 'updateRoutesDisplay({ selectedIndex: 1 }) // re-highlight alternative #1 on the most recent route',\n 'updateRoutesDisplay({ add: [\"routes-0\"], selectedIndex: 2, fitBounds: false })',\n 'updateRoutesDisplay({ color: \"coral\" }) // recolor every currently-shown route (and future shows)',\n 'updateRoutesDisplay({ add: [\"routes-1\"], color: \"#FF0000\" }) // show in red',\n ],\n examplePrompts: [\n 'Draw the route on the map',\n 'Show the directions',\n 'Display the calculated route',\n 'Show the Edinburgh route from earlier',\n 'Also show the Brussels route',\n 'Hide the Lyon route',\n 'Switch to the second alternative',\n 'Highlight alternative 2',\n 'Replace what is shown with this route',\n 'Make the route line red',\n 'Change the route color to coral',\n 'Style the route in steelblue',\n ],\n relatedTools: ['setRoute', 'processData', 'recallState', 'clearMap'],\n dependsOn: ['setRoute'],\n },\n updateTrafficAreaAnalyticsDisplay: {\n description: updateTrafficAreaAnalyticsDisplayDescription,\n classificationPrompt:\n 'Show / hide / swap / restyle historical traffic-area-analytics entries on the map. `add` / `remove` / `addMatching` / `removeMatching` / `clear` for visibility; `mode` / `metric` / `palette` / `scaleMode` / `maxHeightMeters` / `metersPerUnit` / `filter` for the rendering. Render knobs alone implicitly target the most recent entry.',\n inputSchema: updateTrafficAreaAnalyticsDisplaySchema,\n outputSchema: updateTrafficAreaAnalyticsDisplayOutputSchema,\n execute: executeUpdateTrafficAreaAnalyticsDisplay,\n tags: ['traffic', 'map style'],\n examples: [\n 'updateTrafficAreaAnalyticsDisplay({ add: [\"tta-0\"] })',\n \"updateTrafficAreaAnalyticsDisplay({ mode: 'hexgrid-2d', metric: 'speed', palette: 'heat' }) // re-style the latest (implicit)\",\n 'updateTrafficAreaAnalyticsDisplay({ filter: { min: 50 } })',\n \"updateTrafficAreaAnalyticsDisplay({ scaleMode: 'currentRange', maxHeightMeters: 200 })\",\n 'updateTrafficAreaAnalyticsDisplay({ metersPerUnit: 5 }) // raw height: 5 m per unit of the metric',\n 'updateTrafficAreaAnalyticsDisplay({ clear: true })',\n 'updateTrafficAreaAnalyticsDisplay({ clear: true, add: [\"tta-1\"] }) // atomic swap',\n 'updateTrafficAreaAnalyticsDisplay({ remove: [\"tta-0\"] })',\n 'updateTrafficAreaAnalyticsDisplay({ addMatching: [\"Amsterdam\"] })',\n ],\n examplePrompts: [\n 'Show the analytics on the map',\n 'Visualize the congestion',\n 'Only show areas with congestion above 50%',\n 'Normalize height to the current data range',\n 'Switch to heatmap view',\n 'Also show the Amsterdam analytics',\n 'Hide the Amsterdam analytics',\n 'Replace what is on the map with this analytics',\n ],\n relatedTools: ['getTrafficAreaAnalytics', 'toggleTilesTrafficFlow', 'clearMap', 'analyseData', 'processData'],\n dependsOn: ['getTrafficAreaAnalytics'],\n },\n updateWaypointsDisplay: {\n description: updateWaypointsDisplayDescription,\n classificationPrompt:\n 'Show or hide the STAGED origin/stop/destination markers (`state.routing.planningSlots` — what is queued for the NEXT `setRoute`). No route line is drawn; for that use `updateRoutesDisplay`. Single-collection toggle: `visible: true` (default) renders the staged set, `visible: false` clears it.',\n inputSchema: updateWaypointsDisplaySchema,\n outputSchema: updateWaypointsDisplayOutputSchema,\n execute: executeUpdateWaypointsDisplay,\n tags: ['waypoint', 'route'],\n examples: [\n 'updateWaypointsDisplay()',\n 'updateWaypointsDisplay({ visible: true })',\n 'updateWaypointsDisplay({ visible: false })',\n ],\n examplePrompts: [\n 'Show the staged waypoints on the map',\n 'Mark my pending route stops',\n 'Hide the staged waypoints',\n ],\n relatedTools: ['setRoute', 'updateRoutesDisplay', 'getCurrentWaypoints', 'clearMap'],\n dependsOn: ['setRoute', 'locatePlace'],\n },\n clearMap: {\n description: clearMapDescription,\n classificationPrompt:\n 'Hide what the chosen state slices are rendering on the map. `layers` are SLICE KEYS: `places`, `routing`, `ranges`, `customGeometries`, `byod`, `trafficAreaAnalytics`, `trafficIncidents`. Omit to clear every slice. Map-only — slice history is kept.',\n inputSchema: clearMapSchema,\n outputSchema: clearMapOutputSchema,\n execute: executeClearMap,\n tags: ['place', 'route', 'map style'],\n examples: [\n 'clearMap()',\n 'clearMap({ layers: [\"places\"] })',\n 'clearMap({ layers: [\"places\", \"routing\"] })',\n 'clearMap({ layers: [\"trafficAreaAnalytics\", \"trafficIncidents\"] })',\n 'clearMap({ layers: [\"byod\"] })',\n ],\n // \"Start fresh\" → resetState (wipes the session); \"Hide the analytics overlay\" →\n // updateTrafficAreaAnalyticsDisplay (per-slice hide). Both routed away from clearMap, so the\n // examples are kept to unambiguous whole-map clears.\n examplePrompts: ['Clear the map', 'Remove all markers', 'Hide everything on the map'],\n relatedTools: ['updatePlacesDisplay', 'updateRoutesDisplay', 'updateTrafficAreaAnalyticsDisplay'],\n },\n flyTo: {\n description: flyToDescription,\n classificationPrompt:\n 'Move the camera to a bounding box or specific position; use after locating places or computing bounds.',\n inputSchema: flyToSchema,\n outputSchema: flyToOutputSchema,\n execute: executeFlyTo,\n tags: ['location', 'map style'],\n examples: [\n 'flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5] } })',\n 'flyTo({ where: { boundingBox: [4.8, 52.3, 5.0, 52.5], padding: 100 } })',\n 'flyTo({ where: { position: [4.9, 52.4], zoom: 14 } })',\n ],\n // Coordinate prompt first: it moves the camera directly (no place lookup), so it's the\n // reliable canonical. \"Go to Berlin\" / \"Center on Rome\" need a locatePlace step first, which\n // the agent may stop at, so they stay as fanout cases.\n examplePrompts: ['Fly to [4.9, 52.37]', 'Go to Berlin', 'Center the map on Rome'],\n relatedTools: ['getViewport', 'locatePlace', 'updatePlacesDisplay', 'updateRoutesDisplay', 'calculateBBox'],\n },\n getViewport: {\n description: getViewportDescription,\n classificationPrompt:\n 'Return the map center, zoom, and bounding box; use for area-relative queries like \"in this area\" or \"near the map center\".',\n inputSchema: getViewportSchema,\n outputSchema: getViewportOutputSchema,\n execute: executeGetViewport,\n tags: ['location', 'map style'],\n examples: ['getViewport()'],\n examplePrompts: [\n 'Where is the map centered?',\n \"What's the current map area?\",\n 'What are the current map bounds?',\n ],\n relatedTools: ['locatePlace', 'flyTo', 'discoverPlaces', 'reverseGeocode', 'getTrafficIncidents'],\n },\n addByodSource: {\n description: addByodSourceDescription,\n classificationPrompt:\n 'Add a customer-owned GeoJSON FeatureCollection (URL fetch or inline) as a new BYOD source. Use when the ' +\n 'user wants to import / load / overlay a customer-supplied dataset. Returns the new entry id (use with ' +\n '`byodEntryIDs` on analyseData / processData) plus a data `profile`. The entry starts with NO layers and ' +\n 'renders nothing, so co-pick setByodLayers on every ingest turn to decide the layers from that profile.',\n inputSchema: addByodSourceSchema,\n outputSchema: addByodSourceOutputSchema,\n execute: executeAddByodSource,\n tags: ['byod', 'state', 'import'],\n examples: [\n 'addByodSource({ label: \"Sales territories\", url: \"https://example.com/territories.geojson\" }) // then setByodLayers to render',\n 'addByodSource({ label: \"Inline pins\", data: { type: \"FeatureCollection\", features: [/* … */] } })',\n 'addByodSource({ label: \"Q2 pins\", data: { type: \"FeatureCollection\", features: [/* … */] } })',\n ],\n examplePrompts: [\n 'Load my territories layer from https://example.com/territories.geojson',\n 'Import the GeoJSON at https://example.com/data.geojson as a BYOD layer',\n 'Add a customer-pins layer from https://example.com/pins.geojson',\n ],\n relatedTools: ['recallState', 'setByodLayers', 'updateByodDisplay', 'analyseData', 'processData'],\n },\n setByodLayers: {\n description: setByodLayersDescription,\n classificationPrompt:\n 'Restyle a BYOD entry by replacing the MapLibre layers it renders under — pick layer types and ' +\n 'data-driven paint from the data `profile` (graduate point size by a numeric field, colour a fill by ' +\n 'category, switch points to symbols). OWNS every restyle of a loaded / imported / customer (BYOD) layer ' +\n '— recolour it, thicken its outlines, resize its points — even when phrased as generic styling; do NOT ' +\n 'route those to setPaintProperties/setLayoutProperties (which are for base-map style layers). ALWAYS ' +\n 'co-pick alongside addByodSource on EVERY BYOD ingest turn (whether or not styling was mentioned) — ' +\n 'addByodSource creates the entry with no layers, so this is the only way it becomes visible. NOT for ' +\n 'visibility (show/hide/remove → updateByodDisplay) nor for importing new data (→ addByodSource).',\n inputSchema: setByodLayersSchema,\n outputSchema: setByodLayersOutputSchema,\n execute: executeSetByodLayers,\n tags: ['byod', 'map style', 'display'],\n examples: [\n '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',\n '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',\n 'setByodLayers({ byodEntryId: \"byod-2\", layers: [{ type: \"line\", paint: { \"line-color\": \"#2ca02c\", \"line-width\": 3 } }], show: { zoomMode: \"none\" } }) // restyle a hidden entry and render it',\n ],\n examplePrompts: [\n 'Colour my loaded territories layer by region',\n 'Size my BYOD layer points by revenue',\n 'Style my loaded BYOD layer with thicker green outlines',\n 'Shade my BYOD polygons by their value',\n ],\n relatedTools: ['recallState', 'addByodSource', 'updateByodDisplay'],\n dependsOn: ['addByodSource'],\n },\n updateByodDisplay: {\n description: updateByodDisplayDescription,\n classificationPrompt:\n '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.',\n inputSchema: updateByodDisplaySchema,\n outputSchema: updateByodDisplayOutputSchema,\n execute: executeUpdateByodDisplay,\n tags: ['byod', 'display', 'visibility'],\n examples: [\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"show\" })',\n 'updateByodDisplay({ entryIds: [\"byod-0\", \"byod-1\"], action: \"show\", hideOthers: true })',\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"hide\" })',\n 'updateByodDisplay({ action: \"hide\", clearAll: true })',\n 'updateByodDisplay({ entryIds: [\"byod-0\"], action: \"remove\" })',\n ],\n examplePrompts: [\n 'Hide the customer pins layer',\n 'Show only the territories BYOD layer',\n 'Hide all BYOD layers',\n 'Drop the imported file from the session',\n ],\n relatedTools: ['recallState', 'addByodSource'],\n dependsOn: ['addByodSource'],\n },\n getCurrentWaypoints: {\n description: getCurrentWaypointsDescription,\n classificationPrompt:\n 'STAGED waypoint slots only — the origin/stops/destination being built up for the NEXT `setRoute` call ' +\n '(read off `state.routing.planningSlots`, NOT off any calculated route entry). ' +\n 'For waypoints of an EXISTING calculated route, use `recallState({ kind: \"routes\", id })` — its detail mode returns the entry\\'s own waypoints.',\n inputSchema: getCurrentWaypointsSchema,\n outputSchema: getCurrentWaypointsOutputSchema,\n execute: executeGetCurrentWaypoints,\n tags: ['waypoint', 'route'],\n examples: [\n 'getCurrentWaypoints()',\n 'getCurrentWaypoints({ slotIndex: 1 })',\n 'getCurrentWaypoints({ includeEmptySlots: true })',\n 'getCurrentWaypoints({ offset: 2, limit: 4 })',\n ],\n examplePrompts: [\n \"What's currently staged for the next route?\",\n \"What's my pending destination before I hit go?\",\n 'List the stops I have queued up',\n ],\n relatedTools: ['locatePlace', 'setRoute', 'updateWaypointsDisplay', 'recallState'],\n },\n getStandardMapStyles: {\n description: getStandardMapStylesDescription,\n classificationPrompt: 'List available standard map style IDs for use with setMapStandardStyle.',\n inputSchema: getStandardMapStylesSchema,\n outputSchema: getStandardMapStylesOutputSchema,\n execute: executeGetStandardMapStyles,\n tags: ['map style'],\n examples: ['getStandardMapStyles()'],\n examplePrompts: ['What map styles are available?', 'List the map themes'],\n relatedTools: ['setMapStandardStyle'],\n },\n setMapStandardStyle: {\n description: setMapStandardStyleDescription,\n classificationPrompt:\n '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.',\n inputSchema: setMapStandardStyleSchema,\n outputSchema: setMapStandardStyleOutputSchema,\n execute: executeSetMapStandardStyle,\n tags: ['map style'],\n examples: ['setMapStandardStyle(\"standardDark\")', 'setMapStandardStyle(\"satellite\")'],\n examplePrompts: [\n 'Switch to dark mode',\n 'Use the satellite view',\n 'Change to the light theme',\n // Driver persona: the driving-optimised standard preset.\n 'Use the driving map style',\n ],\n relatedTools: ['getStandardMapStyles', 'setLanguage', 'getMapStyleLayers'],\n },\n setLanguage: {\n description: setLanguageDescription,\n classificationPrompt: 'Change the language for map labels and all subsequent service API responses.',\n inputSchema: setLanguageSchema,\n outputSchema: setLanguageOutputSchema,\n execute: executeSetLanguage,\n tags: ['map style'],\n examples: ['setLanguage(\"fr-FR\")', 'setLanguage(\"de-DE\")'],\n examplePrompts: ['Show map labels in French', 'Switch the map to German', 'Use Spanish for place names'],\n relatedTools: ['setMapStandardStyle'],\n },\n toggleTilesPOIs: {\n description: toggleTilesPOIsDescription,\n classificationPrompt:\n 'Show / hide vector-tile POI icons baked into the base-map style (restaurants/hotels/gas stations etc. shipped with the map). NOT places shown via `discoverPlaces` / `updatePlacesDisplay` (those are PlacesModule pins, untouched).',\n inputSchema: toggleTilesPOIsSchema,\n outputSchema: toggleTilesPOIsOutputSchema,\n execute: executeToggleTilesPOIs,\n tags: ['place', 'map style'],\n examples: [\n 'toggleTilesPOIs({ allMapPOIsVisible: false })',\n 'toggleTilesPOIs({ allMapPOIsVisible: true, filterCategories: { show: \"only\", values: [\"RESTAURANT\", \"CAFE\"] } })',\n 'toggleTilesPOIs({ filterCategories: { show: \"all-except\", values: [\"PARKING_GARAGE\"] } })',\n 'toggleTilesPOIs({ reset: true })',\n ],\n examplePrompts: [\n 'Hide all map POI icons',\n 'Show only restaurant icons on the map style',\n 'Turn off parking icons in the base map',\n ],\n relatedTools: ['discoverPlaces', 'setMapStandardStyle'],\n },\n toggleTilesBaseMapLayerGroups: {\n description: toggleTilesBaseMapLayerGroupsDescription,\n classificationPrompt:\n 'Show / hide whole groups of the vector-tile base map — water, roads, railways, borders, 2D or 3D buildings, road shields, road / city / country labels, house numbers — or the whole base map, or show it again as shipped. NOT the POI icons (toggleTilesPOIs), NOT for how they look (setMapStyling), NOT for layers the agent drew.',\n inputSchema: toggleTilesBaseMapLayerGroupsSchema,\n outputSchema: toggleTilesBaseMapLayerGroupsOutputSchema,\n execute: executeToggleTilesBaseMapLayerGroups,\n tags: ['map style'],\n examples: [\n 'toggleTilesBaseMapLayerGroups({ visible: true, layerGroups: [\"buildings3D\"] })',\n 'toggleTilesBaseMapLayerGroups({ visible: false, layerGroups: [\"roadShields\", \"houseNumbers\"] })',\n 'toggleTilesBaseMapLayerGroups({ visible: false })',\n 'toggleTilesBaseMapLayerGroups({ reset: true })',\n ],\n examplePrompts: [\n 'Show 3D buildings',\n 'Hide the road labels',\n 'Turn off the building layer',\n 'Hide the route shields and house numbers',\n 'Bring back all the base map layers',\n ],\n relatedTools: ['setMapStyling', 'toggleTilesPOIs'],\n },\n setMapStyling: {\n description: setMapStylingDescription,\n classificationPrompt:\n \"Adjust how the map looks semantically — bigger/smaller labels or icons, thicker roads, hide/show exit numbers, road arrows or POI micro markers, POI label colour or zoom, the colours of the traffic congestion/incident lines, hillshade shading, map colours, a globe projection with a sky, 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. NOT for showing/hiding whole layer groups such as 3D buildings, road shields or road labels (toggleTilesBaseMapLayerGroups), NOT for turning the live traffic overlays on or off (toggleTilesTrafficFlow, toggleTilesTrafficIncidents), NOT for which POI categories show (toggleTilesPOIs), NOT for switching the whole style (setMapStandardStyle), NOT for layers the agent drew.\",\n inputSchema: setMapStylingSchema,\n outputSchema: setMapStylingOutputSchema,\n execute: executeSetMapStyling,\n tags: ['map style'],\n examples: [\n 'setMapStyling({ set: { \"labels.sizeFactor\": 1.3 } })',\n 'setMapStyling({ set: { \"roads.exitNumbers.visible\": false, \"roads.arrows.visible\": false } })',\n 'setMapStyling({ set: { \"traffic.flow.colors.slow\": \"#f59e0b\", \"traffic.flow.colors.stationary\": \"#7f1d1d\" } })',\n 'setMapStyling({ set: { \"view.projection\": \"globe\", \"view.sky\": true } })',\n 'setMapStyling({ set: { \"colors.water\": \"#0fa8c8\", \"colors.roadMajor\": \"#f59e0b\" } })',\n 'setMapStyling({ theme: { colors: [\"#0b2a3a\", \"#0fa8c8\", \"#f6efe3\", \"#d9822b\"] } })',\n 'setMapStyling({ theme: { colors: [\"#1d3f8a\", \"#e63312\", \"#f5d000\", \"#f4f1ea\", \"#111111\"], mode: \"light\" } })',\n 'setMapStyling({ theme: { imageUrl: \"https://example.com/brand/logo.png\" } })',\n 'setMapStyling({ preset: \"data-viz\" })',\n 'setMapStyling({ reset: true })',\n ],\n examplePrompts: [\n 'Make the map labels bigger',\n 'Hide the exit numbers and road arrows',\n 'Use orange for slow traffic and dark red for stationary traffic',\n 'Show the map as a globe',\n 'Make the water teal and the motorways orange',\n 'Use our brand colours on the map: dark land, light labels',\n 'Theme the map like a Miró painting',\n 'Style the map in warm earthy tones',\n 'Make the map match our website: navy, coral and off-white',\n 'Make the base map quieter so my data stands out',\n 'Reset the map styling',\n ],\n relatedTools: ['setMapStandardStyle', 'toggleTilesBaseMapLayerGroups', 'toggleTilesPOIs'],\n },\n toggleTilesTrafficFlow: {\n description: toggleTilesTrafficFlowDescription,\n classificationPrompt:\n 'Toggle the vector-tile traffic-flow overlay (live coloured road segments). NOT the same as `updateTrafficAreaAnalyticsDisplay` (historical) or route-section traffic (read via `analyseData`).',\n inputSchema: toggleTilesTrafficFlowSchema,\n outputSchema: toggleTilesTrafficFlowOutputSchema,\n execute: executeToggleTilesTrafficFlow,\n tags: ['traffic', 'map style'],\n examples: ['toggleTilesTrafficFlow({ visible: true })', 'toggleTilesTrafficFlow({ visible: false })'],\n examplePrompts: ['Show traffic flow', 'Turn off traffic colors', 'Enable the congestion overlay'],\n relatedTools: ['toggleTilesTrafficIncidents', 'setRoute', 'getTrafficIncidents'],\n },\n toggleTilesTrafficIncidents: {\n description: toggleTilesTrafficIncidentsDescription,\n classificationPrompt:\n 'Toggle the vector-tile traffic-incidents overlay (live jam/accident/closure markers). NOT the GeoJSON `getTrafficIncidents` service and NOT `analyseData` (which aggregates fetched entries).',\n inputSchema: toggleTilesTrafficIncidentsSchema,\n outputSchema: toggleTilesTrafficIncidentsOutputSchema,\n execute: executeToggleTilesTrafficIncidents,\n tags: ['traffic', 'map style'],\n examples: ['toggleTilesTrafficIncidents({ visible: true })', 'toggleTilesTrafficIncidents({ visible: false })'],\n examplePrompts: ['Show traffic incidents overlay', 'Hide accident icons', 'Enable incident markers on the map'],\n relatedTools: ['toggleTilesTrafficFlow', 'getTrafficIncidents', 'setRoute'],\n },\n getMapStyleLayers: {\n description: getMapStyleLayersDescription,\n classificationPrompt: 'List MapLibre layer IDs with their paint/layout properties for custom styling.',\n inputSchema: getMapStyleLayersSchema,\n outputSchema: getMapStyleLayersOutputSchema,\n execute: executeGetMapStyleLayers,\n tags: ['map style'],\n examples: ['getMapStyleLayers()', 'getMapStyleLayers(\"road\")', 'getMapStyleLayers(\"water\")'],\n examplePrompts: ['What are the paint properties of the water layer?', 'Show me the road layer layout'],\n relatedTools: ['setLayoutProperties', 'setPaintProperties'],\n },\n setLayoutProperties: {\n description: setLayoutPropertiesDescription,\n classificationPrompt:\n 'Set MapLibre layout properties (visibility, text-field, icon-size) on named base-map style layers. NOT ' +\n 'for loaded BYOD / imported / customer layers — restyle those with setByodLayers.',\n inputSchema: setLayoutPropertiesSchema,\n outputSchema: setLayoutPropertiesOutputSchema,\n execute: executeSetLayoutProperties,\n tags: ['map style'],\n examples: [\n 'setLayoutProperties([{ layerId: \"road-label\", propertyName: \"visibility\", value: \"none\" }])',\n 'setLayoutProperties([{ layerId: \"poi-label\", propertyName: \"text-field\", value: \"{name}\" }, { layerId: \"poi-label\", propertyName: \"icon-size\", value: 1.2 }])',\n ],\n examplePrompts: [\n \"Set the road-label layer's visibility property to none\",\n 'Change the text size on building labels',\n 'Make the city labels bigger',\n ],\n relatedTools: ['getMapStyleLayers', 'setPaintProperties'],\n dependsOn: ['getMapStyleLayers'],\n },\n setPaintProperties: {\n description: setPaintPropertiesDescription,\n classificationPrompt:\n 'Set MapLibre paint properties (colors, widths, opacity) on named base-map style layers. NOT for loaded ' +\n 'BYOD / imported / customer layers — restyle those with setByodLayers.',\n inputSchema: setPaintPropertiesSchema,\n outputSchema: setPaintPropertiesOutputSchema,\n execute: executeSetPaintProperties,\n tags: ['map style'],\n examples: [\n 'setPaintProperties([{ layerId: \"water\", propertyName: \"fill-color\", value: \"#0000ff\" }])',\n 'setPaintProperties([{ layerId: \"road\", propertyName: \"line-color\", value: \"#ff0000\" }, { layerId: \"road\", propertyName: \"line-width\", value: 3 }])',\n ],\n examplePrompts: [\n 'Make the water bright blue',\n 'Change the road color to red',\n 'Make buildings semi-transparent',\n 'Make the country borders pink',\n ],\n relatedTools: ['getMapStyleLayers', 'setLayoutProperties', 'updateRoutesDisplay'],\n dependsOn: ['getMapStyleLayers'],\n },\n setPitchBearing: {\n description: setPitchBearingDescription,\n classificationPrompt: 'Tilt (pitch) and/or rotate (bearing) the map camera for a 3D perspective.',\n inputSchema: setPitchBearingSchema,\n outputSchema: setPitchBearingOutputSchema,\n execute: executeSetPitchBearing,\n tags: ['map style'],\n examples: [\n 'setPitchBearing({ pitch: 45 })',\n 'setPitchBearing({ bearing: 90 })',\n 'setPitchBearing({ pitch: 60, bearing: -45 })',\n ],\n examplePrompts: ['Tilt the map to 45 degrees', 'Rotate the map to face north', 'Set a 3D perspective view'],\n relatedTools: ['getViewport', 'locatePlace', 'flyTo'],\n },\n zoomInOrOut: {\n description: zoomInOrOutDescription,\n classificationPrompt: 'Adjust the map zoom level by a delta; positive zooms in, negative zooms out.',\n inputSchema: zoomInOrOutSchema,\n outputSchema: zoomInOrOutOutputSchema,\n execute: executeZoomInOrOut,\n tags: ['map style'],\n examples: ['zoomInOrOut({ delta: 2 })', 'zoomInOrOut({ delta: -1 })'],\n examplePrompts: ['Zoom in', 'Zoom out 2 levels', 'Get a wider view'],\n relatedTools: ['getViewport', 'flyTo'],\n },\n executeMaplibreCode: {\n description: executeMaplibreCodeDescription,\n classificationPrompt:\n 'Execute arbitrary MapLibre JS against the live map — the escape hatch ONLY for operations no dedicated ' +\n 'tool covers: fog / atmosphere, terrain, raster / tile overlays, and time-based animations / multi-step ' +\n 'loops that one-shot tools cannot express. NOT for a globe projection or a sky (setMapStyling). If any ' +\n 'other tool already performs the operation, use that tool instead.',\n inputSchema: executeMaplibreCodeSchema,\n outputSchema: executeMaplibreCodeOutputSchema,\n execute: executeExecuteMaplibreCode,\n tags: ['map style', 'maplibre', 'layers', 'sources', 'animation'],\n examples: [\n \"executeMaplibreCode({ code: \\\"map.addSource('pts', { type: 'geojson', data: fc }); map.addLayer({ id: 'pts', type: 'circle', source: 'pts', paint: { 'circle-color': '#f00' } })\\\" })\",\n 'executeMaplibreCode({ code: \"map.easeTo({ center: [4.9, 52.37], zoom: 14, duration: 2000 })\" })',\n \"executeMaplibreCode({ code: \\\"for (let i = 0; i <= 20; i++) { map.setPaintProperty('fill', 'fill-opacity', i / 20); await new Promise(r => setTimeout(r, 50)); }\\\" })\",\n \"executeMaplibreCode({ code: \\\"map.setFog({ color: 'white', 'horizon-blend': 0.1 })\\\" })\",\n ],\n // Kept to operations no dedicated tool covers. Dropped \"Add a GeoJSON layer to the map\"\n // (the classifier rightly prefers addByodSource) and \"Animate the map camera\" (→ flyTo) —\n // the escape-hatch tool should only be exemplified by genuinely-uncovered map operations.\n // \"Set map fog or atmosphere\" first: a concrete map.setFog call with no dedicated tool, so\n // it routes reliably to the escape hatch (the canonical). \"Fade in a layer gradually\" is a\n // time-based animation no one-shot style tool can express — the classificationPrompt now\n // names that case explicitly so it routes here rather than to setPaintProperties.\n examplePrompts: ['Set map fog or atmosphere', 'Add a raster tile overlay', 'Fade in a layer gradually'],\n relatedTools: ['getMapStyleLayers', 'setLayoutProperties', 'setPaintProperties', 'flyTo'],\n dependsOn: ['getMapStyleLayers'],\n },\n calculateBBox: {\n description: calculateBBoxDescription,\n classificationPrompt:\n 'Compute a [minLng, minLat, maxLng, maxLat] bounding box from GeoJSON features or tool results.',\n inputSchema: calculateBBoxSchema,\n outputSchema: calculateBBoxOutputSchema,\n execute: executeCalculateBBox,\n tags: ['utilities', 'location'],\n examples: ['calculateBBox(place)', 'calculateBBox(routes)', 'calculateBBox([place1, place2])'],\n // Route-extent prompt first: it classifies to calculateBBox reliably and is the canonical\n // scenario. \"bounding box of these places\" is kept but is flakier (the agent often computes\n // it via processData instead).\n examplePrompts: ['Calculate the extent of this route', \"What's the bounding box of these places?\"],\n relatedTools: ['flyTo', 'getViewport', 'updatePlacesDisplay', 'updateRoutesDisplay'],\n },\n getCurrentLocation: createGetCurrentLocationTool(),\n clarifyIntent: createClarifyIntentTool(),\n help: {\n description: helpDescription,\n classificationPrompt:\n 'Return available capabilities in summary or searchable detail mode; call when the user asks what you can do.',\n inputSchema: helpSchema,\n outputSchema: helpOutputSchema,\n execute: async () => {\n throw new Error('help tool requires setupTools() — it needs resolved metadata from all tools.');\n },\n tags: ['utilities'],\n examples: [\n 'help({ mode: \"summary\" })',\n 'help({ mode: \"detail\", query: \"traffic\" })',\n 'help({ mode: \"detail\", tag: \"route\" })',\n 'help({ mode: \"detail\", query: \"route\", tag: \"utilities\" })',\n ],\n examplePrompts: ['What can you do?', 'Show me available tools', 'How do I search for places?'],\n },\n findReachableAreas: {\n description: findReachableAreasDescription,\n classificationPrompt:\n 'Calculate isochrone/isodistance polygons showing how far you can travel from one or more origins.',\n inputSchema: findReachableAreasSchema,\n outputSchema: findReachableAreasOutputSchema,\n execute: executeFindReachableAreas,\n tags: ['reachable-range', 'isochrone', 'range', 'EV', 'coverage', 'multi-origin'],\n examples: [\n 'findReachableAreas({ origins: [{ query: \"Amsterdam\", queryAs: \"place\" }], budgets: [{ type: \"timeMinutes\", value: 30 }], showOnMap: true })',\n 'findReachableAreas({ origins: [{ query: \"Amsterdam\", queryAs: \"place\" }, { query: \"Rotterdam\", queryAs: \"place\" }], budgets: [{ type: \"timeMinutes\", value: 30 }], showOnMap: true })',\n 'findReachableAreas({ origins: [{ position: [4.9, 52.3] }], budgets: [{ type: \"timeMinutes\", value: 10 }, { type: \"timeMinutes\", value: 30 }], showOnMap: true, fillStyle: \"inverted\" })',\n 'findReachableAreas({ origins: [{ query: \"my location\", queryAs: \"place\" }], budgets: [{ type: \"distanceKM\", value: 50 }], showOnMap: false })',\n ],\n examplePrompts: [\n 'Show me where I can reach in 30 minutes from Amsterdam',\n 'Compare 30-minute reach from Amsterdam, Rotterdam and Utrecht',\n 'What area is within 50 km of Berlin?',\n 'Show 10, 20, 30 minute drive zones from here',\n // Logistics / field-service planner persona: coverage from a named depot/warehouse site.\n 'Map the 20-minute drive zone around our Rotterdam depot',\n 'Show the area reachable within 25 km of the Eindhoven warehouse',\n ],\n relatedTools: ['recallState', 'locatePlace'],\n },\n recallState: recallStateBuilder,\n resetState: {\n description: resetStateDescription,\n classificationPrompt:\n 'Wipe the session: clear every places/routes/ranges entry, reset POIs/traffic configs, revert to standard style. Use only when the user asks for a clean slate.',\n inputSchema: resetStateSchema,\n outputSchema: resetStateOutputSchema,\n execute: executeResetState,\n tags: ['state', 'reset', 'destructive'],\n examples: ['resetState()'],\n examplePrompts: ['Start over', 'Reset everything', 'Clear all my data and the map'],\n relatedTools: ['recallState', 'clearMap'],\n },\n setEntryMode: {\n description: setEntryModeDescription,\n classificationPrompt:\n 'Switch any entry-owning slice (places, routing, ranges, customGeometries, byod, trafficAreaAnalytics, trafficIncidents) between `multiple` (overlay several entries) and `single` (latest only). `single` drops older entries.',\n inputSchema: setEntryModeSchema,\n outputSchema: setEntryModeOutputSchema,\n execute: executeSetEntryMode,\n tags: ['state', 'mode', 'display'],\n examples: [\n 'setEntryMode({ slice: \"places\", mode: \"single\" })',\n 'setEntryMode({ slice: \"routing\", mode: \"multiple\" })',\n 'setEntryMode({ slice: \"trafficIncidents\", mode: \"single\" })',\n ],\n examplePrompts: [\n 'Only show one route at a time',\n 'Let me overlay multiple places searches',\n 'Switch ranges back to single mode',\n 'Only keep the latest traffic incidents snapshot',\n ],\n relatedTools: ['recallState'],\n },\n} satisfies Record<string, ToolDefinition>;\n\n// Narrow the value type so the emitted d.ts captures only the keys, not the\n// inlined `z.infer<...>` signatures from each `execute` (which the d.ts bundler\n// flattens to invalid `infer<typeof X>` syntax). Must go through an intermediate\n// const so TypeScript resolves the mapped type before re-exporting.\nconst DEFAULT_TOOLS_TYPED: { [K in keyof typeof defaultTools]: ToolDefinition } = defaultTools;\n\n/**\n * Default tool set for the agent toolkit. Each entry is a {@link ToolDefinition}\n * — either a static `ToolEntry` or a `ToolEntryBuilder` that tailors the tool to\n * {@link ToolBuildOptions} (e.g. feature flags) at agent-creation time.\n *\n * Included automatically by createMapAgent unless includeDefaultTools is false.\n * Use resolveTools() for manual composition outside createMapAgent.\n *\n * @group Agent Toolkit\n */\nexport const DEFAULT_TOOLS = DEFAULT_TOOLS_TYPED;\n\n/**\n * Union of all default tool names.\n *\n * @group Agent Toolkit\n */\nexport type ToolName = keyof typeof DEFAULT_TOOLS;\n\n/**\n * All valid default tool names, derived from {@link DEFAULT_TOOLS}.\n *\n * @group Agent Toolkit\n */\nexport const TOOL_NAMES = Object.keys(DEFAULT_TOOLS) as ToolName[];\n\n/**\n * Default tool names that should be dropped from the registry when a {@link DataEntryKind}\n * is disabled. Only default-tool names are listed here — user-supplied custom tools are\n * never filtered automatically; integrators that want to gate their own tools on enabled\n * kinds can read `dataEntries` themselves.\n *\n * Tools that span multiple kinds (`clearMap`, `recallState`, `setEntryMode`, …) are\n * intentionally absent — they keep working regardless of which kinds are disabled.\n *\n * @group Agent Toolkit\n */\nexport const TOOLS_BY_DATA_ENTRY_KIND: Record<DataEntryKind, readonly ToolName[]> = {\n places: ['updatePlacesDisplay', 'locatePlace', 'discoverPlaces'],\n routes: [\n 'updateRoutesDisplay',\n 'setRoute',\n 'addWaypointsToRoute',\n 'removeWaypointsFromRoute',\n 'replaceWaypointInRoute',\n 'getCurrentWaypoints',\n 'updateWaypointsDisplay',\n 'startRouteMonitor',\n 'stopRouteMonitor',\n ],\n incidents: ['getTrafficIncidents', 'focusIncidents', 'setTrafficIncidentsMonitor', 'clusterIncidents'],\n customGeometries: [],\n trafficAreaAnalytics: ['getTrafficAreaAnalytics', 'updateTrafficAreaAnalyticsDisplay'],\n byod: ['addByodSource', 'setByodLayers', 'updateByodDisplay'],\n ranges: ['findReachableAreas'],\n};\n\n/**\n * Returns each default tool's `examplePrompts` keyed by tool name. Builders are\n * materialised with empty {@link ToolBuildOptions} so the prompts reflect the\n * tool's default (full-surface) shape. Tools without prompts map to `[]`.\n *\n * Surface these in chat UIs to keep starter suggestions in sync with what the\n * tools can actually do — the registry stays the single source of truth.\n *\n * @group Agent Toolkit\n */\nexport const getDefaultToolPrompts = (): Record<ToolName, readonly string[]> => {\n const result = {} as Record<ToolName, readonly string[]>;\n for (const name of TOOL_NAMES) {\n const entry = DEFAULT_TOOLS[name];\n const materialized = typeof entry === 'function' ? entry({}) : entry;\n result[name] = materialized.examplePrompts ?? [];\n }\n return result;\n};\n","/**\n * @module agent-toolkit\n */\n\nimport type { TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { PrepareStepFunction, Tool } from 'ai';\nimport { hasToolCall, stepCountIs, ToolLoopAgent } from 'ai';\nimport {\n applyToolScopes,\n composeStepResult,\n createPrepareStepMutex,\n destroyState,\n restoreScopableTools,\n unionActiveTools,\n} from './prepare-step-helpers';\nimport { resolveTools } from './resolve-tools';\nimport { createToolState, DATA_ENTRY_KIND_TO_SLICE, type DataEntryKind, type EntryModeSlice } from './state';\nimport { buildSystemPrompt } from './system-prompt';\nimport { type ScopableToolInfo, setupTools } from './tool-setup';\nimport { DEFAULT_TOOLS, TOOLS_BY_DATA_ENTRY_KIND } from './tools';\nimport { AGENT_TOOLKIT_USER_AGENT, ALL_ENTRY_DATA_KINDS } from './tools/shared';\nimport { resolveSandboxExecutor } from './tools/shared/sandbox';\nimport type {\n Classifier,\n EntryDataKind,\n MapAgentInstance,\n MapAgentOptions,\n ToolDefinition,\n ToolMetadata,\n ToolState,\n} from './types';\nimport type { ClassificationResult } from './utils';\nimport { createDefaultClassifier } from './utils';\n\n// CONCURRENCY: the AI SDK does not support per-step tool overrides (`PrepareStepResult` has no\n// `tools` field — only `activeTools` for name-level filtering). Mutating the shared `tools` map is\n// therefore the only path to a per-turn scoped schema, and it imposes a single-concurrent-run\n// invariant on any agent instance that has scopable tools. Two concurrent `respond()` calls on the\n// same agent would race on the mutation; we serialise `prepareStep` invocations across the agent\n// with `createPrepareStepMutex` to keep the mutation/serialize-request window atomic per call.\n// Callers who genuinely need parallel sessions should create one agent per session (the typical\n// embedding-app pattern) rather than share an agent across concurrent users.\n\n/**\n * Inputs the step-0 classification needs. Bundled into a single object so the orchestrator's\n * inner closure can pass the agent-creation-time state through to `runStep0Classification` without\n * a wide parameter list. None of the fields change across steps — the agent's whole lifetime sees\n * the same `tools` / `scopableTools` / `toolsMetadata` / `classifier`.\n */\ntype ClassificationDeps = {\n tools: Record<string, Tool>;\n scopableTools: Record<string, ScopableToolInfo>;\n toolsMetadata: Record<string, ToolMetadata>;\n classifier: Classifier | null;\n onClassify?: (result: ClassificationResult | null) => void;\n};\n\n/**\n * Step-0 pipeline: restore each scopable tool to its full surface so the classifier has a clean\n * baseline, run the classifier (swallowing any throw — networks are flaky and `null` triggers\n * fail-open), surface the result via `onClassify`, and finally mutate the live tool objects'\n * `description` + `inputSchema` using the classifier's per-tool scopes. The AI SDK reads tool\n * definitions on its next request build, so this mutation is what makes per-turn scoping work.\n *\n * Returns the classification (or null) for the caller to cache across subsequent steps.\n */\nconst runStep0Classification = async (\n deps: ClassificationDeps,\n messages: Parameters<PrepareStepFunction>[0]['messages'],\n): Promise<ClassificationResult | null> => {\n restoreScopableTools(deps.tools, deps.scopableTools);\n let result: ClassificationResult | null = null;\n if (deps.classifier) {\n try {\n result = await deps.classifier({\n messages,\n toolsMetadata: deps.toolsMetadata,\n });\n } catch {\n result = null;\n }\n }\n deps.onClassify?.(result);\n applyToolScopes(deps.tools, deps.scopableTools, result?.toolScopes);\n return result;\n};\n\n/**\n * Creates a conversational agent toolkit that gives an LLM control over a TomTom map.\n *\n * Returns a ToolLoopAgent instance (compatible with DirectChatTransport) with\n * `state` and `destroy` attached. Classification runs transparently inside\n * `prepareStep` — no wrapper type needed.\n *\n * @param map - The TomTomMap instance to control\n * @param options - Agent configuration options\n * @returns A ToolLoopAgent with state access and cleanup\n *\n * @example Basic usage\n * ```typescript\n * import { createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';\n * import { openai } from '@ai-sdk/openai';\n * import { useChat } from '@ai-sdk/react';\n * import { DirectChatTransport } from 'ai';\n *\n * const agent = createMapAgent(map, { model: openai('gpt-4o') });\n *\n * // Works directly with DirectChatTransport — no .agent property needed\n * const { messages, sendMessage } = useChat({\n * transport: new DirectChatTransport({ agent }),\n * });\n * ```\n *\n * @example Custom tools + removal\n * ```typescript\n * const agent = createMapAgent(map, {\n * model: openai('gpt-4o'),\n * tools: {\n * getCustomLocation: myLocationTool, // add custom\n * setLanguage: false, // remove default\n * },\n * });\n * ```\n * @group Agent Toolkit\n */\nexport const createMapAgent = <CS extends ToolState = ToolState>(\n map: TomTomMap,\n options: MapAgentOptions<CS>,\n): MapAgentInstance<CS> => {\n if (!options.model) {\n throw new Error('MapAgent requires a model option. Please provide an AI SDK LanguageModel instance.');\n }\n\n // MapLibre fetches tiles itself, outside any tool call, so the map has to carry the tag. That\n // counts everything it requests from here on, user panning included — tile fetching is decoupled\n // from whatever triggered it, so \"map under agent control\" is the finest attribution available.\n map._setTomTomUserAgent(AGENT_TOOLKIT_USER_AGENT);\n\n const state = createToolState(map, options.state) as CS;\n\n // App-supplied authorization hook for BYOD URL fetches, stored on the slice so\n // `addByodSource` can consult it (see ByodSourceUrlValidator). Optional — unset\n // leaves only the built-in scheme / size / timeout policy in force.\n if (options.byod?.validateSourceUrl) {\n state.byod.sourceUrlValidator = options.byod.validateSourceUrl;\n }\n\n // Select where analyseData / processData sandbox code runs (default: the iframe-worker\n // sandbox, falling back to the main thread where its browser APIs are missing).\n // Stored on state so those tools can pick it up at execute time.\n state.codeExecution = resolveSandboxExecutor(options.codeExecution);\n\n // Apply per-kind `entryMode` from `dataEntries`. Each kind maps to a single slice via\n // {@link DATA_ENTRY_KIND_TO_SLICE}. The slice histories are empty at this point so\n // `setEntryMode` resolves synchronously (no entries to hide); the promise is\n // intentionally not awaited because createMapAgent itself is sync.\n if (options.dataEntries) {\n for (const [kindKey, config] of Object.entries(options.dataEntries)) {\n const kind = kindKey as DataEntryKind;\n if (!config?.entryMode) continue;\n const sliceName = DATA_ENTRY_KIND_TO_SLICE[kind];\n void (state[sliceName] as EntryModeSlice).setEntryMode(config.entryMode);\n }\n }\n\n // Compute the set of enabled data-entry kinds. Used both to filter the tool registry\n // (drop slice-specific tools for disabled kinds) and to narrow analyseData / processData\n // input surface via {@link ToolBuildOptions.enabledDataKinds}. Default is everything\n // enabled; only kinds with an explicit `enabled: false` drop out.\n const isKindEnabled = (kind: DataEntryKind): boolean => options.dataEntries?.[kind]?.enabled !== false;\n const disabledKinds: DataEntryKind[] = (Object.keys(DATA_ENTRY_KIND_TO_SLICE) as DataEntryKind[]).filter(\n (kind) => !isKindEnabled(kind),\n );\n const enabledDataKinds: readonly EntryDataKind[] = ALL_ENTRY_DATA_KINDS.filter((kind) => isKindEnabled(kind));\n // Names of default tools to drop because their slice is disabled. Custom-tool overrides\n // (passed via `options.tools`) are NOT filtered — integrators that want to gate their\n // own tools on a disabled kind can inspect `dataEntries` themselves.\n const droppedToolNames = new Set<string>(disabledKinds.flatMap((kind) => TOOLS_BY_DATA_ENTRY_KIND[kind]));\n\n // Safe: default tools only access base ToolState properties; CS extends ToolState.\n const defaultsAll =\n options.includeDefaultTools === false ? {} : (DEFAULT_TOOLS as Record<string, ToolDefinition<CS>>);\n // Filter slice-specific default tools out before user overrides — user overrides win,\n // so a custom tool registered under the same name resurrects it on purpose.\n const defaults: Record<string, ToolDefinition<CS>> = Object.fromEntries(\n Object.entries(defaultsAll).filter(([name]) => !droppedToolNames.has(name)),\n );\n const toolEntries = resolveTools(defaults, options.tools);\n\n const { tools, toolsMetadata, scopableTools } = setupTools(toolEntries, state, {\n outputSchemas: options.outputSchemas,\n featureFlags: options.featureFlags,\n enabledDataKinds,\n onToolExecute: options.onToolExecute,\n });\n\n // Resolve classifier\n const classifier: Classifier | null =\n options.classifier === false ? null : (options.classifier ?? createDefaultClassifier({ model: options.model }));\n\n const systemPrompt = buildSystemPrompt({\n customPrompt: options.systemPrompt,\n prefix: options.systemPromptPrefix,\n suffix: options.systemPromptSuffix,\n });\n\n // Tools flagged `alwaysActive` are unioned into every step's activeTools so the model can always\n // reach them, regardless of what the classifier picked. Computed once from resolved metadata.\n const alwaysActiveToolNames = Object.entries(toolsMetadata)\n .filter(([, meta]) => meta.alwaysActive)\n .map(([name]) => name);\n\n // Tools flagged `endsTurnOnCall` halt the loop deterministically on the step that calls them — a\n // single silent tool call (e.g. clarifyIntent rendering a form). A tool that awaits the user WITHOUT\n // this flag (e.g. clarifyIntent in prose mode) instead gets one trailing step to present its result\n // as text, then stops naturally when that step makes no tool call.\n const endsTurnStops = Object.entries(toolsMetadata)\n .filter(([, meta]) => meta.endsTurnOnCall)\n .map(([name]) => hasToolCall(name));\n\n // Classification state — cached per turn (reset on step 0)\n let lastClassification: ClassificationResult | null = null;\n\n // Per-agent serialising mutex around prepareStep. Guards the tool-object mutation window so\n // concurrent `respond()` calls on the same agent don't race on `tools[name].description /\n // inputSchema`. The AI SDK reads tool definitions immediately after prepareStep returns, so\n // serialising prepareStep keeps each call's request-build atomic with respect to mutations.\n const withPrepareStepLock = createPrepareStepMutex();\n\n // Bundle classifier + tools so the step-0 helper has a single argument. None of these change\n // across steps, so the bag is constructed once and closed over by prepareStep.\n const classificationDeps: ClassificationDeps = {\n tools,\n scopableTools,\n toolsMetadata,\n classifier,\n onClassify: options.onClassify,\n };\n\n const prepareStep: PrepareStepFunction = (stepInfo) =>\n withPrepareStepLock(async () => {\n if (stepInfo.stepNumber === 0) {\n lastClassification = await runStep0Classification(classificationDeps, stepInfo.messages);\n }\n const activeTools = unionActiveTools(lastClassification?.activeToolNames, alwaysActiveToolNames);\n const userResult = await options.prepareStep?.(stepInfo);\n const stepProviderOptions = options.stepProviderOptions?.({\n activeTools,\n stepNumber: stepInfo.stepNumber,\n });\n const composed = composeStepResult(activeTools, stepProviderOptions, userResult);\n // composeStepResult intersects activeTools with a host-supplied prepareStep.activeTools,\n // which could otherwise drop always-active tools. Re-assert them on the FINAL result so\n // they stay reachable every step. (When activeTools is omitted, every tool is already\n // active — nothing to re-add.)\n if (!composed?.activeTools) return composed;\n return { ...composed, activeTools: unionActiveTools(composed.activeTools, alwaysActiveToolNames) };\n });\n\n const agent = new ToolLoopAgent({\n model: options.model,\n tools,\n instructions: systemPrompt,\n stopWhen: [stepCountIs(options.maxSteps ?? 10), ...endsTurnStops],\n prepareStep,\n providerOptions: options.providerOptions,\n });\n\n return Object.assign(agent, {\n state,\n destroy: () => destroyState(state),\n });\n};\n"],"mappings":"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"}
|