persistent-ai

@persistent-ai/tam (0.2.0)

Published 2026-08-17 13:00:25 +00:00 by ak

Installation

@persistent-ai:registry=
npm install @persistent-ai/tam@0.2.0
"@persistent-ai/tam": "0.2.0"

About this package

@persistent-ai/tam

tam is a React package for an agent-friendly geospatial investigation canvas.

It owns:

  • validated JSON canvas commands
  • Effector-backed isolated canvas instances
  • serializable snapshots and hydration
  • React provider/hooks
  • MapLibre and deck.gl rendering primitives
  • popup state and popup rendering hooks

The package deliberately does not depend on backend APIs, graph databases, commodity models, LLM providers, or chat history storage. Host applications provide those adapters.

Install

@persistent-ai/tam is published to GitHub Packages, not npmjs.com. Consumers need a one-time .npmrc setup pointing the @persistent-ai scope at npm.pkg.github.com. This applies to public packages too — it's how GitHub Packages works.

Add to your project's .npmrc (or ~/.npmrc):

@persistent-ai:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

GITHUB_TOKEN must be a personal access token with at least the read:packages scope. Then:

npm install @persistent-ai/tam
# or
bun add @persistent-ai/tam

Peer dependencies (install separately if you don't have them already):

bun add react react-dom maplibre-gl react-map-gl @deck.gl/core @deck.gl/layers @deck.gl/mapbox

Basic Use

import {
  TamProvider,
  TamMap,
  TamPopups,
  applyTamCommands,
  createTamCanvas,
  serializeTamCanvas,
} from "@persistent-ai/tam";

const canvas = createTamCanvas();

applyTamCommands(canvas, commandBatch);

export function MapExperience() {
  return (
    <TamProvider canvas={canvas}>
      <TamMap basemapStyle="https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json" />
      <TamPopups />
    </TamProvider>
  );
}

const snapshot = serializeTamCanvas(canvas);

See fixtures/basic-investigation.commands.json for a deterministic command batch that adds nodes, edges, styles, route sets, and popups.

examples/basic-demo.tsx shows a host-side React demo that starts empty, applies the fixture command batch, serializes the canvas, and hydrates from the matching snapshot.

Mapbox Basemaps

tam renders on MapLibre, which does not resolve the mapbox:// scheme that every stock Mapbox style uses for its sources, sprites and glyphs. Passing a Mapbox style URL straight to basemapStyle gets a blank canvas. Mapbox supports the combination — it just needs the request hook MapLibre provides for it:

import { TamMap, createMapboxTransformRequest } from "@persistent-ai/tam";

const transformRequest = createMapboxTransformRequest(import.meta.env.VITE_MAPBOX_TOKEN);

<TamMap
  basemapStyle="https://api.mapbox.com/styles/v1/mapbox/outdoors-v12"
  transformRequest={transformRequest}
  cooperativeGestures
  maxPitch={85}
/>;

Use a public token (pk.…) restricted by URL in your Mapbox account — it ships in the browser bundle. A secret token (sk.…) is rejected outright. With no token the hook rewrites nothing, so a missing environment variable reads as a missing variable rather than as an outage.

Two things worth knowing before you commit to Mapbox as a tile source:

  • Consumed through MapLibre, Mapbox bills per tile request rather than per map load, so the free tier goes further on paper than in practice. Measure your own scenario.
  • The dependency stays one string. basemapStyle is the whole of it, so moving to MapTiler, Protomaps or your own tiles is a change of configuration.

transformRequest, cooperativeGestures, maxPitch and terrain are handed to MapLibre untouched; leaving one out leaves MapLibre's own default in place.

Agent Streaming With AG-UI

tam supports AG-UI-shaped streaming without depending on @ag-ui/core. The current @ag-ui/core package depends on zod v3, while tam stays on zod v4, so the integration is a small JSON bridge instead of a runtime dependency.

Agents stream map edits as CUSTOM events named tam.commands:

{
  "type": "CUSTOM",
  "name": "tam.commands",
  "value": {
    "commands": [
      {
        "type": "addNodes",
        "nodes": [
          {
            "id": "Port/Santos",
            "label": "Export Port",
            "name": "Santos",
            "latitude": -23.9618,
            "longitude": -46.3322,
            "properties": {}
          }
        ]
      }
    ]
  }
}

Host code applies those events through the bridge:

import { createTamAgUiBridge, createTamCanvas } from "@persistent-ai/tam";

const canvas = createTamCanvas();
const bridge = createTamAgUiBridge(canvas);

for await (const event of agUiEventStream) {
  const result = bridge.applyEvent(event);
  if (result) {
    sendAgUiEvent(bridge.commandResultEvent(result));
  }
}

The bridge validates the full command batch before mutating Effector state. It can also emit STATE_SNAPSHOT events containing { tam: snapshot } and CUSTOM tam.selection events so the agent can observe current map state and user selection.

See fixtures/ag-ui-stream.events.json for a deterministic AG-UI stream fixture and fixtures/ag-ui-stream.snapshot.json for the expected canvas state after applying it through createTamAgUiBridge.

SVG Marker Icons

Agents can register external SVG marker icons through canvas commands, then reference them from node styles. The icon registry is part of tam state, so it serializes, hydrates, and stays independent of any host app component registry.

[
  {
    "type": "registerSvgIcon",
    "icon": {
      "id": "export-port",
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><path d=\"M8 42h48l-8 12H16z\"/></svg>",
      "width": 64,
      "height": 64,
      "mask": true
    }
  },
  {
    "type": "addNodes",
    "nodes": [
      {
        "id": "Port/Santos",
        "label": "Export Port",
        "latitude": -23.9618,
        "longitude": -46.3322,
        "properties": {},
        "style": {
          "icon": "export-port",
          "iconSize": 28,
          "color": "#0ea5e9"
        }
      }
    ]
  }
]

Set mask: true to tint the SVG from style.color. Set mask: false when the SVG should keep its own colors.

Dependencies

Dependencies

ID Version
effector ^23.4.2
effector-react ^23.3.0
zod ^4.1.12

Development Dependencies

ID Version
@changesets/changelog-github ^0.7.0
@changesets/cli ^2.31.0
@deck.gl/core ^9.2.11
@deck.gl/layers ^9.2.11
@deck.gl/mapbox ^9.2.11
@types/geojson ^7946.0.16
@types/react ^19.0.0
@types/react-dom ^19.0.0
@vitejs/plugin-react ^4.3.0
maplibre-gl ^5.22.0
oxlint ^1.20.0
react ^19.0.0
react-dom ^19.0.0
react-map-gl ^8.1.0
typescript ~5.7.0
vite ^5.4.0
vitest ^2.1.9

Peer Dependencies

ID Version
@deck.gl/core ^9.2.0
@deck.gl/layers ^9.2.0
@deck.gl/mapbox ^9.2.0
maplibre-gl ^5.0.0
react ^19.0.0
react-dom ^19.0.0
react-map-gl ^8.1.0

Keywords

react map maplibre deck.gl geospatial agent ag-ui effector canvas investigation journalism chapters timeline
Details
npm
2026-08-17 13:00:25 +00:00
9
Persistent AI
MIT
latest
202 KiB
Assets (1)
tam-0.2.0.tgz 202 KiB
Versions (1) View all
0.2.0 2026-08-17