# @carto/agentic-deckgl

`@carto/agentic-deckgl` is a framework-agnostic TypeScript library that provides tool definitions, system prompt generation, and SDK converters for building AI-powered [CARTO + deck.gl](https://github.com/CartoDB/gitbook-documentation/blob/master/carto-for-developers/key-concepts/carto-for-deck.gl) map applications. For an introduction to the concepts, see [AI-powered map interaction](https://docs.carto.com/carto-for-developers/key-concepts/ai-powered-map-interaction).

## Installation

```bash
npm install @carto/agentic-deckgl
```

Peer dependencies: `@deck.gl/json` (^9.2.0) and `zod` (^4.3.6).

## buildSystemPrompt

Generates a system prompt for the AI agent, including tool-specific instructions, current map state, and optional user context.

### Usage

```typescript
import { buildSystemPrompt, TOOL_NAMES } from '@carto/agentic-deckgl';

const systemPrompt = buildSystemPrompt({
  toolNames: [TOOL_NAMES.SET_DECK_STATE, TOOL_NAMES.SET_MARKER, TOOL_NAMES.SET_MASK_LAYER],
  initialState: {
    viewState: { latitude: 40.7128, longitude: -74.006, zoom: 12 },
    layers: [{ id: 'my-layer', type: 'VectorTileLayer', visible: true }],
    activeLayerId: 'my-layer',
  },
});
```

### Options

```typescript
interface BuildSystemPromptOptions {
  toolNames: string[];          // List of tool names available to the agent
  initialState?: MapState;      // Current map state for context
  userContext?: UserContext;     // User context for business analysis
  semanticContext?: string;     // Pre-rendered data catalog markdown
  mcpToolNames?: string[];      // MCP tool names (enables MCP instructions)
  additionalPrompt?: string;    // Additional prompt text to append
}
```

* **toolNames:** The tools the agent is allowed to use. Use the `TOOL_NAMES` constant for type safety: `TOOL_NAMES.SET_DECK_STATE`, `TOOL_NAMES.SET_MARKER`, `TOOL_NAMES.SET_MASK_LAYER`.
* **initialState** (optional): The current map state, so the AI has context about what the user is viewing. Includes `viewState` (lat/lng/zoom/pitch/bearing), `layers` (array of layer state), and `activeLayerId`.
* **userContext** (optional): Business context such as country, business type, demographics, and proximity priorities. This shapes the AI's analytical responses.
* **semanticContext** (optional): A pre-rendered markdown string describing the available data tables and their columns. This is typically generated from a YAML-based data catalog.
* **mcpToolNames** (optional): When provided, the prompt includes MCP-specific instructions for the listed tool names.
* **additionalPrompt** (optional): Custom text appended to the end of the system prompt.

### Supporting types

```typescript
interface MapState {
  viewState?: MapViewState;
  layers?: LayerState[];
  activeLayerId?: string;
}

interface MapViewState {
  longitude: number;
  latitude: number;
  zoom: number;
  pitch?: number;
  bearing?: number;
}

interface LayerState {
  id: string;
  type?: string;
  visible?: boolean;
  [key: string]: unknown;
}
```

### Helper functions

```typescript
import { buildMapStateSection, buildUserContextSection } from '@carto/agentic-deckgl';

// Build just the map state portion of the prompt
const mapSection = buildMapStateSection({
  viewState: { latitude: 40.7128, longitude: -74.006, zoom: 12 },
  layers: [{ id: 'layer-1', type: 'VectorTileLayer' }],
  activeLayerId: 'layer-1',
});

// Build just the user context portion of the prompt
const userSection = buildUserContextSection({
  country: 'Spain',
  businessType: 'Retail',
});
```

## Tool definitions

The library defines 3 tools, each with a Zod validation schema. These tools are what the AI calls to manipulate the map.

### set-deck-state

Full deck.gl state control: navigation, basemap, layers, widgets, and effects.

```typescript
// Parameters (all optional — include only what you want to change)
{
  initialViewState?: {
    latitude: number;       // -90 to 90
    longitude: number;      // -180 to 180
    zoom: number;           // 0 to 22
    pitch?: number;         // 0 to 85
    bearing?: number;       // -180 to 180
    transitionDuration?: number;
  };
  mapStyle?: 'dark-matter' | 'positron' | 'voyager';
  layers?: Record<string, unknown>[];      // deck.gl JSON layer configs
  widgets?: Record<string, unknown>[];     // deck.gl JSON widget configs
  effects?: Record<string, unknown>[];     // deck.gl JSON effect configs
  layerOrder?: string[];                   // layer IDs in render order
  removeLayerIds?: string[];               // layer IDs to remove
  removeWidgetIds?: string[];              // widget IDs to remove
}
```

### set-marker

Places, removes, or clears location marker pins on the map.

```typescript
{
  action: 'add' | 'remove' | 'clear-all';  // default: 'add'
  latitude?: number;    // required for add/remove
  longitude?: number;   // required for add/remove
}
```

### set-mask-layer

Manages an editable mask layer for spatial filtering.

```typescript
{
  action: 'set' | 'enable-draw' | 'clear';
  geometry?: GeoJSON;    // GeoJSON geometry (mutually exclusive with tableName)
  tableName?: string;    // CARTO table containing mask geometry
}
```

### Accessing tool definitions

```typescript
import {
  tools,
  getToolNames,
  getTool,
  getConsolidatedToolDefinitions,
  validateToolParams,
  TOOL_NAMES,
} from '@carto/agentic-deckgl';

// Get all tool names
const names = getToolNames();
// ['set-deck-state', 'set-marker', 'set-mask-layer']

// Get a specific tool definition
const deckTool = getTool(TOOL_NAMES.SET_DECK_STATE);

// Get all tools in OpenAI function calling format
const openAITools = getConsolidatedToolDefinitions();

// Validate parameters against a tool's schema
const result = validateToolParams(TOOL_NAMES.SET_MARKER, {
  action: 'add',
  latitude: 40.7128,
  longitude: -74.006,
});
```

## SDK converters

Convert tool definitions into the format expected by each AI SDK. All converters return tool definitions with built-in Zod validation and a frontend tool marker, so the backend knows the tool should be executed on the client.

### getToolsForOpenAIAgents

```typescript
import { getToolsForOpenAIAgents } from '@carto/agentic-deckgl';

const toolDefs = getToolsForOpenAIAgents();
// Returns OpenAIAgentToolDef[] with name, description, parameters (Zod), execute
```

### getToolsForVercelAI

```typescript
import { getToolsForVercelAI, getToolsRecordForVercelAI } from '@carto/agentic-deckgl';

// As an array
const toolDefs = getToolsForVercelAI();

// As a record (for streamText({ tools: ... }))
const toolsRecord = getToolsRecordForVercelAI();
```

### getToolsForGoogleADK

```typescript
import { getToolsForGoogleADK } from '@carto/agentic-deckgl';

const toolDefs = getToolsForGoogleADK();
// Returns GoogleADKToolDef[] with name, description, parameters (Zod), execute
```

All converters accept an optional `toolNames` parameter to convert only specific tools:

```typescript
const subset = getToolsForVercelAI([TOOL_NAMES.SET_DECK_STATE, TOOL_NAMES.SET_MARKER]);
```

## Custom tool executors

The SDK converters provide default `execute` functions that validate parameters and return a frontend tool marker. On the frontend, you need a **tool executor** — a function that receives the tool name and validated parameters and applies them to your deck.gl map state.

The reference frontends in the repository each include a `tool-executor` that you can use as a starting point and customize for your application. The pattern is a factory function that maps each tool name to your own execution logic:

```typescript
import { TOOL_NAMES } from '@carto/agentic-deckgl';

function createToolExecutor(deckActions) {
  const executors = {
    [TOOL_NAMES.SET_DECK_STATE]: (params) => {
      // Apply view state, basemap, layers, widgets, effects
      // to your deck.gl instance using params
      if (params.initialViewState) {
        deckActions.setViewState(params.initialViewState);
      }
      if (params.layers) {
        deckActions.updateLayers(params.layers);
      }
      return { success: true, message: 'State updated' };
    },

    [TOOL_NAMES.SET_MARKER]: (params) => {
      // Add, remove, or clear markers on your map
      return { success: true, message: 'Marker placed' };
    },

    [TOOL_NAMES.SET_MASK_LAYER]: (params) => {
      // Apply spatial mask geometry, enable draw mode, or clear
      return { success: true, message: 'Mask applied' };
    },
  };

  return async (toolName, params) => {
    const executor = executors[toolName];
    if (!executor) return { success: false, message: `Unknown tool: ${toolName}` };
    return executor(params);
  };
}
```

This is where you control exactly how each tool call affects your application — for example, how layers are merged, how markers are rendered, or how mask geometries are fetched from CARTO tables. See the [frontend examples](https://github.com/CartoDB/carto-agentic-deckgl/tree/main/examples/frontend) for complete implementations in React, Vue, Angular, and Vanilla JS.
