npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tomtom-org/maps-sdk-plugin-agent-toolkit

v0.4.4

Published

Agent toolkit plugin for TomTom Maps SDK JS using Vercel AI SDK

Readme

TomTom Maps SDK - Agent Toolkit Plugin

A headless conversational agent that gives Large Language Models tool-based control over a TomTom Map and TomTom location services, powered by Vercel AI SDK v6.

No UI is included — bring your own chat interface. No LLM provider is bundled — supply any AI SDK-compatible model.

Full documentation — guides, architecture diagrams, and tutorials are available at docs.tomtom.com.

Design principles

  1. Client-side only — uses AI SDK's DirectChatTransport + ToolLoopAgent. The consumer provides the model; no server infrastructure is required.
  2. No bundled LLM provider — supply any AI SDK-compatible LanguageModel. Keeps the package provider-agnostic.
  3. Token-efficient — all service responses are summarized before reaching the LLM. Full GeoJSON stays in ToolState.
  4. Lazy module initialization — map modules are instantiated on first use and cached in state.
  5. Coordinate convention — always [longitude, latitude] per GeoJSON standard, enforced throughout.
  6. Task-oriented tools — tool boundaries follow user tasks, not SDK API surface, so a single prompt maps to a single tool call.

Installation

pnpm add @tomtom-org/maps-sdk @tomtom-org/maps-sdk-plugin-agent-toolkit ai zod maplibre-gl @turf/turf chart.js h3-js

Install at least one AI SDK provider:

# Pick one (or more)
pnpm add @ai-sdk/openai
pnpm add @ai-sdk/anthropic
pnpm add @ai-sdk/azure

Quick start

import { TomTomMap } from '@tomtom-org/maps-sdk/map';
import { createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';
import { openai } from '@ai-sdk/openai';

// 1. Create a TomTom map
const map = new TomTomMap({
    mapLibre: { container: 'map', center: [4.9, 52.4], zoom: 10 },
});

// 2. Create the agent
const agent = createMapAgent(map, {
    model: openai('gpt-4o'),
});

// 3. Send a message
const result = await agent.generate({
    messages: [{ role: 'user', content: 'Find coffee shops near Dam Square, Amsterdam' }],
});

console.log(result.text);

With React (useChat)

import { DirectChatTransport } from 'ai';
import { useChat } from '@ai-sdk/react';

const agent = createMapAgent(map, { model: openai('gpt-4o') });

function ChatPanel() {
    const { messages, sendMessage } = useChat({
        transport: new DirectChatTransport({ agent }),
    });

    return (
        <div>
            {messages.map((m) => (
                <div key={m.id}>{m.content}</div>
            ))}
            <input onKeyDown={(e) => e.key === 'Enter' && sendMessage(e.target.value)} />
        </div>
    );
}

See examples/map-chat-agent-react for a full working example.

Tools reference

The plugin ships a DEFAULT_TOOLS registry covering search, routing, traffic, reachable areas, BYOD GeoJSON, base-map control, MapLibre access, and code-generated analysis. All tools are included by default and can be individually removed or replaced via the tools option. The full per-tool reference lives in the Agent Toolkit guide.

Location & search

| Tool | Description | |---|---| | locatePlace | Resolve a location string (landmark, city, address) to a place; optionally stage as a waypoint | | reverseGeocode | Convert [longitude, latitude] coordinates to an address | | discoverPlaces | Search for places by text query or POI category within an area | | getPOICategoryCodes | Look up TomTom POI category codes from natural-language names | | getCurrentLocation | Get the user's physical GPS location from the browser | | getViewport | Get current map center, zoom, and bounding box |

Routing & reachable areas

| Tool | Description | |---|---| | setRoute | Calculate or recalculate a route — provide locations (waypoints), parameters (route options), or both | | addWaypointsToRoute | Extend the current route — prepend a new origin, append a new destination, and/or insert intermediate stops | | removeWaypointsFromRoute | Remove one or more waypoints by index from the current route | | replaceWaypointInRoute | Overwrite a single waypoint of the current route in place — origin, destination, or by index | | getCurrentWaypoints | Get the staged waypoint slots (origin, stops, destination) | | findReachableAreas | Calculate isochrone/isodistance polygons from one or more origins |

Traffic

| Tool | Description | |---|---| | getTrafficIncidents | Fetch traffic incidents within an area (viewport, named place, route corridor, polygon, IDs) | | startTrafficIncidentsMonitor | Start a live monitor that refreshes incident state on a configurable cadence | | stopTrafficIncidentsMonitor | Stop the live incident monitor | | focusIncidents | Highlight a subset of incidents (by id / category / severity) on the map and dim the rest | | getTrafficAreaAnalytics | Fetch historical traffic analytics (speed, congestion, travel time) for an area | | queryTrafficAnalytics | Query cached analytics data or check what is currently displayed | | toggleTilesTrafficFlow | Toggle the real-time traffic-flow tile overlay | | toggleTilesTrafficIncidents | Toggle the real-time traffic-incidents tile overlay | | getShownTileIncidents | List real-time incidents visible in the current viewport |

Bring-your-own-data (BYOD)

| Tool | Description | |---|---| | addByodSource | Ingest a customer-authored GeoJSON source (URL or inline FeatureCollection) and profile its shape; the entry has no layers and renders nothing until the agent sets them via setByodLayers | | recallByod | List BYOD entries, or retrieve a single entry's full FeatureCollection by id | | setByodLayers | Restyle a BYOD entry — replace its MapLibre layers (type + data-driven paint) using the entry's data profile | | updateByodDisplay | Show, hide, or clear BYOD layers on the map |

See the Bring your own data guide for ingestion patterns, visibility lifecycle, and how BYOD entries feed into analyseData / processData.

Unified data tools — scope-aware code generation

| Tool | Description | |---|---| | analyseData | Aggregate / chart entries (places, routes, incidents, geometries, trafficAreaAnalytics, byod) via dynamic JS; classifier-emitted scope narrows the schema per turn | | processData | Transform entries into new places, placeConnections, geometries, byod, or a fitOnMap camera move via dynamic JS | | executeMaplibreCode | Execute arbitrary MapLibre JS against the live Map instance — escape hatch for custom layers, animations, raster overlays |

See Code generation for the injected identifiers, output contracts, and threat model, and Scope-aware data tools for how the per-turn classifier scope keeps the prompt small.

analyseData / processData code is always isolated in the browser — a Web Worker in a sandboxed, opaque-origin iframe (no DOM/network; terminable on timeout), zero-config (the SDK lazily loads its own bundled turf/h3 for the worker). Where the code runs is chosen by environment, not configured: the browser always isolates; Node / SSR always run on the main thread (where that boundary has no equivalent — defense-in-depth only). The optional codeExecution only tunes the isolated browser run:

const agent = createMapAgent(map, {
    model,
    codeExecution: { timeoutMs: 5000 }, // wall-clock budget per isolated run (default 5000)
});

Experimental — the isolation boundary is verified by the e2e-tests/ suite (CSP egress-block, worker termination, opaque-origin isolation), which runs in CI as a dedicated job; if the iframe can't initialise it falls back to the main thread (with a warning). turf / h3 / routeUtils are all bundled into the worker, so analyseData and processData (including route-slicing) run fully in the browser. See the code-generation guide.

Map display

| Tool | Description | |---|---| | updatePlacesDisplay | Show, hide, or restyle place entries on the map | | updateRoutesDisplay | Show, hide, or restyle route entries (line color, waypoint icons, fit camera) | | updateWaypointsDisplay | Show staged waypoint markers without the route line | | updateTrafficAreaAnalyticsDisplay | Visualize traffic-area-analytics as hexgrid, heatmap, or tiles | | clearMap | Remove displayed places, routes, BYOD layers, or all features |

Map control

| Tool | Description | |---|---| | flyTo | Move the camera to a position or bounding box | | zoomInOrOut | Adjust the zoom level by a delta | | setPitchBearing | Tilt (pitch) and/or rotate (bearing) the camera | | getStandardMapStyles | List available standard map style presets | | setMapStandardStyle | Switch map style (light, dark, satellite, driving, etc.) | | setLanguage | Change the language for map labels and API responses | | toggleTilesBaseMapLayerGroups | Show/hide named layer groups (buildings3D, roadLabels, water, etc.) | | toggleTilesPOIs | Show/hide built-in map POI icons with optional category filtering |

MapLibre direct access

| Tool | Description | |---|---| | getMapStyleLayers | List MapLibre layer IDs with their paint/layout properties | | setLayoutProperties | Set MapLibre layout properties on named layers | | setPaintProperties | Set MapLibre paint properties (colors, widths, opacity) on named layers |

State & recall

| Tool | Description | |---|---| | recallPlaces | Retrieve the history of place lookups from this session | | recallRoutes | Retrieve previously calculated routes from this session | | recallRanges | Retrieve stored reachable-range results | | recallGeometries | Look up polygon sources by { kind, id } across place footprints, isochrones, and customGeometries entries | | recallByod | Retrieve BYOD entries (see BYOD) | | recallState | Summarize the current contents of every state slice | | setEntryMode | Switch a slice between multiple (default) and single entry modes | | resetState | Reset one or all state slices |

Utilities

| Tool | Description | |---|---| | calculateBBox | Compute a bounding box from GeoJSON features or tool results | | formatDistance | Format meters into a human-readable string (e.g. "2.5 km") | | formatDuration | Format seconds into a human-readable string (e.g. "1 h 30 min") | | help | List available capabilities in summary or searchable detail mode |

Customization

See Customizing tools for the full walkthrough — registry resolution, removing or replacing defaults, adding scopable custom tools, and starting from a blank slate.

createMapAgent options

// Define custom state by extending ToolState
interface MyState extends ToolState {
    fleet: FleetState;
}

const agent = createMapAgent<MyState>(map, {
    // Required: AI SDK language model instance
    model: openai('gpt-4o'),

    // Include built-in defaults (default: true). Set false for custom-only.
    includeDefaultTools: true,

    // Add, replace, or remove tools (merged with defaults)
    tools: { myCustomTool: weatherTool },

    // Append to the built-in system prompt
    systemPromptSuffix: 'Always respond in Spanish. Use metric units.',

    // Or replace it entirely (systemPromptSuffix is ignored when this is set)
    systemPrompt: 'You are a delivery route planner...',

    // Custom state slices — only custom fields needed, built-in slices are created automatically
    state: { fleet: new FleetState() },

    // Per-kind data-entry config. `enabled: false` removes the kind from the tool surface
    // (drops its recall/display/fetch tools and the kind from analyseData/processData scope).
    // `entryMode` switches the slice between 'multiple' (default) and 'single' (latest only).
    dataEntries: {
        routes: { entryMode: 'single' },
        byod: { enabled: false },
    },

    // Intent classifier: omit for default LLM-based, false to disable
    classifier: createDefaultClassifier({ model: openai('gpt-4o-mini') }),

    // Observe classifier decisions
    onClassify: (result) => console.log('Selected tools:', result?.activeToolNames),

    // Custom prepareStep hook (composed with internal classification)
    prepareStep: async (stepInfo) => ({ toolChoice: 'auto' }),

    // Disable structured output schemas for providers that don't support them
    outputSchemas: false,

    // Max tool-loop iterations (default: 10)
    maxSteps: 15,

    // Opt into experimental features (subject to change without notice)
    featureFlags: { experimentalSearch: true },

    // Provider-specific options forwarded to the AI SDK on every step
    providerOptions: {
        openai: { reasoningEffort: 'low', reasoningSummary: 'auto' },
    },

    // Per-step providerOptions override — e.g. bump reasoning only on code-exec turns
    stepProviderOptions: ({ activeTools }) =>
        activeTools?.includes('processData')
            ? { openai: { reasoningEffort: 'medium' } }
            : undefined,
});

Composing tool sets

The tools option is merged with the built-in defaults. Use false to exclude a tool.

import { createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';

// Add custom tools (defaults included automatically)
createMapAgent(map, { model, tools: { getWeather: myWeatherTool } });

// Remove specific defaults
createMapAgent(map, { model, tools: { setMapStandardStyle: false, setLanguage: false } });

// Replace a default tool (full ToolEntry required)
createMapAgent(map, { model, tools: { discoverPlaces: myCustomSearchTool } });

// Mix: add, remove, and replace in one call
createMapAgent(map, {
    model,
    tools: {
        setLanguage: false,
        discoverPlaces: myCustomSearchTool,
        getWeather: myWeatherTool,
    },
});

// No defaults — only custom tools
createMapAgent(map, { model, includeDefaultTools: false, tools: { myTool } });

Defining custom tools

Use satisfies for type-safe custom tool definitions:

import { type ToolEntry, createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';
import { z } from 'zod';

const fleetTools = {
    trackVehicle: {
        description: 'Track a vehicle by ID on the map.',
        inputSchema: z.object({ vehicleId: z.string() }),
        execute: async ({ vehicleId }, state) => {
            // state is the full ToolState — access state.places, state.routing, etc.
            const position = await fetchVehiclePosition(vehicleId);
            return { vehicleId, position };
        },
        tags: ['fleet'],
        relatedTools: ['updatePlacesDisplay'],
    },
} satisfies Record<string, ToolEntry>;

// Pass directly — merged with defaults automatically
createMapAgent(map, { model, tools: fleetTools });

Tool entry shape

Every tool (built-in or custom) follows the ToolEntry interface:

type ToolEntry<S extends ToolState = ToolState> = {
    description: string;            // Operational contract for the LLM
    inputSchema: z.ZodType;         // Zod schema for input validation
    outputSchema?: z.ZodType;       // Optional structured output schema
    execute: (input, state: S) => Promise<any>;

    // Classifier metadata (optional)
    classificationPrompt?: string;  // One-liner for the intent classifier
    tags?: string[];                // Category tags (e.g. 'location', 'route')
    examples?: string[];            // Code examples
    examplePrompts?: string[];      // Natural language prompt examples
    relatedTools?: string[];        // Tools often used together
    dependsOn?: string[];           // Tools that must run before this one
};

State management

See the State guide for a per-slice deep dive, entryMode semantics (multiple vs single), inspecting state from your application, and adding custom slices.

ToolState is organized by feature area. Each slice manages lazy-initialized map modules and an append-only history of results produced during the session. Built-in slices:

  • PlacesState — place / geometry entry history
  • RoutingState — route history, planning waypoint slots, route parameters
  • RangeState — reachable-range entries
  • CustomGeometriesState — derived polygon entries produced by processData (union, difference, h3-coverage, …)
  • BYODState — bring-your-own-data GeoJSON layer entries
  • BaseMapState — viewport, style, language, raw mapLibreMap
  • TrafficTilesState — real-time traffic flow + incident tile-overlay visibility
  • TrafficAreaAnalyticsState — historical traffic-area-analytics entries and per-entry visualisation
  • TrafficIncidentsState — fetched incident entries, registered analyses, focused subsets, and the optional polling monitor
  • MapPOIsState — POI category visibility and filters

Built-in slices are constructed automatically by createMapAgent. To add custom slices, define an interface extending ToolState and pass the type parameter — only the custom fields need to be provided:

interface MyState extends ToolState {
    fleet: FleetState;
}

const agent = createMapAgent<MyState>(map, {
    model,
    state: { fleet: new FleetState() },
});

agent.state.fleet;   // FleetState — custom slice
agent.state.places;  // PlacesState — append-only history of place entries

Intent classifier

The intent classifier is an optional per-turn optimization that selects which tools the LLM sees, reducing noise and improving accuracy. See the How it works guide for the full two-phase pipeline (classification → tool loop), prompt assembly, and observability hooks.

import { createMapAgent, createDefaultClassifier } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';

// Default: uses the main model for classification
const agent = createMapAgent(map, { model: openai('gpt-4o') });

// Use a cheaper model for classification
const agent = createMapAgent(map, {
    model: openai('gpt-4o'),
    classifier: createDefaultClassifier({ model: openai('gpt-4o-mini') }),
});

// Disable classification entirely (all tools always visible)
const agent = createMapAgent(map, {
    model: openai('gpt-4o'),
    classifier: false,
});

The classifier prompt is built dynamically from each tool's classificationPrompt and relatedTools metadata, so it stays in sync automatically when tools are added or renamed.

System prompt

The built-in BASE_SYSTEM_PROMPT covers identity, a capability summary, scope/rejection rules, response formatting, data-confidence rules, tool-execution guidance, and session-state conventions. Per-tool mechanics (coordinate order, location-reference routing, etc.) live in the tool descriptions and the classifier prompt, not here.

systemPrompt accepts either a full replacement string or a section-overrides object (Partial<Record<SystemPromptSection, string>>) — omitted sections keep their defaults, and you supply only the section body (the heading is added for you):

import {
    createMapAgent,
    BASE_SYSTEM_PROMPT,
    composeSystemPrompt,
    SYSTEM_PROMPT_SECTIONS,
} from '@tomtom-org/maps-sdk-plugin-agent-toolkit';

// Override individual sections — pass the overrides object straight to systemPrompt
const agent = createMapAgent(map, {
    model,
    systemPrompt: {
        identity: 'You are a delivery fleet dispatcher built on the TomTom map.',
        responseFormatting: 'Reply in Dutch, metric units, one short paragraph.',
        // every other section falls back to its default
    },
    // Section overrides still honor the prefix and suffix (a full string replacement does not).
    // The prefix is prepended above the whole prompt as a heading-less preamble; the suffix is
    // appended under an "ADDITIONAL INSTRUCTIONS" heading.
    systemPromptPrefix: 'You work for Acme Logistics.',
    systemPromptSuffix: 'Never expose internal entry ids to the user.',
});

// Append-only: keep the whole base prompt, add instructions
const agent = createMapAgent(map, { model, systemPromptSuffix: 'Always use metric units.' });

// composeSystemPrompt() does the same composition explicitly, if you need the string elsewhere
const prompt = composeSystemPrompt({ responseFormatting: 'Reply in Dutch.' });

// Extend a default section instead of replacing it: read its default body from
// SYSTEM_PROMPT_SECTIONS, derive a new value, and override with the result. Handy for
// handing a default to a coding agent to rewrite under some criteria.
const agentWithExtraRule = createMapAgent(map, {
    model,
    systemPrompt: {
        rejectionRules: `${SYSTEM_PROMPT_SECTIONS.rejectionRules}\n- Decline weather questions.`,
    },
});

// Full replacement (systemPromptPrefix and systemPromptSuffix are ignored)
const agent = createMapAgent(map, {
    model,
    systemPrompt: BASE_SYSTEM_PROMPT + '\n\nYou are a delivery fleet dispatcher...',
});

Advanced usage

Wrapping default tools

Add logging, analytics, or custom behavior around existing tools:

import { DEFAULT_TOOLS, createMapAgent } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';

const wrappedDiscover = {
    ...DEFAULT_TOOLS.discoverPlaces,
    execute: async (input, state) => {
        console.log('[Analytics] Search:', input.query);
        const result = await DEFAULT_TOOLS.discoverPlaces.execute(input, state);
        console.log('[Analytics] Found:', result.count, 'places');
        return result;
    },
};

createMapAgent(map, { model, tools: { discoverPlaces: wrappedDiscover } });

Common configuration patterns

Search-only agent (no routing):

const agent = createMapAgent(map, {
    model,
    tools: {
        setRoute: false,
        addWaypointsToRoute: false,
        removeWaypointsFromRoute: false,
        replaceWaypointInRoute: false,
        updateRoutesDisplay: false,
    },
});

Locked visual appearance:

const agent = createMapAgent(map, {
    model,
    tools: {
        setMapStandardStyle: false,
        setLanguage: false,
        setLayoutProperties: false,
        setPaintProperties: false,
        toggleTilesBaseMapLayerGroups: false,
    },
});

The MapAgentInstance

createMapAgent returns a ToolLoopAgent (usable directly with DirectChatTransport) with two extra properties:

const agent = createMapAgent(map, { model });

agent.state;     // Live state — typed as CS when custom state is provided, ToolState otherwise
agent.destroy(); // Reset all state slices (call on unmount)

Public API exports

The main entry point is createMapAgent; DEFAULT_TOOLS and BASE_SYSTEM_PROMPT cover the most common customizations (see the examples above).

For the complete, always-current list of exports — factories, tool-registry helpers, state introspection, the classifier, and all types — see the API reference.

Dependencies

| Type | Package | Purpose | |---|---|---| | Peer | @tomtom-org/maps-sdk | TomTom Maps SDK (types, services, map modules) | | Peer | ai@^6 | Vercel AI SDK (ToolLoopAgent, tool types) | | Peer | zod@^4 | Schema validation | | Peer | maplibre-gl@^5 | Map rendering engine | | Peer | @turf/turf@^7 | Geospatial math (distance, bbox, bearing) used by the data tools | | Peer | chart.js@^4 | Chart rendering for analyseData outputs | | Peer | h3-js@^4 | H3 hexagonal grid for processData coverage / hexgrid visualizations |

References