Map Agent

Use a lightweight assistant panel inside the map to trigger fly_to commands with natural language.

Preview

MapAgent lives inside the map tree and talks to /api/map-agent. The current implementation focuses on a single map action: fly_to.

endpoint is required. Pass the API route that accepts map-agent requests, for example /api/map-agent. Provider credentials, model names, and base URLs should stay on the server.

Props

PropTypeDefaultDescription
endpointstringRequired API endpoint for assistant requests. This should point to your map-agent route, such as /api/map-agent.
provider"openai" | "anthropic""openai"Selects which server-side provider profile should handle the request.
defaultPromptstring"Fly to downtown Shanghai with a city-level zoom."Initial prompt used for the first automatic assistant request.
autoRunbooleanfalseRuns the initial prompt automatically after the map instance is ready.
placeholderstring"Try: Fly to West Lake in Hangzhou with a scenic city view"Placeholder text shown in the assistant input field.
position"top-left" | "top-right" | "bottom-left" | "bottom-right""top-left"Controls where the assistant panel is rendered inside the map.
classNamestringAdds extra classes to the assistant container for custom layout or styling.
The assistant UI is intentionally narrow: one prompt box, one submit button, and one tool-call action. This keeps the map interaction predictable while the provider compatibility is still being tuned.

Environment Template

Keep provider credentials and model configuration on the server. The template below shows the recommended shape for .env.local.

# Map agent server-side provider config
# Restart `npm run dev` after editing this file.

OPENAI_MAP_AGENT_TOKEN=your-openai-compatible-token
OPENAI_MAP_AGENT_BASE_URL=https://api.deepseek.com/v1
OPENAI_MAP_AGENT_MODEL=deepseek-chat

# Optional Anthropic-compatible provider config
# ANTHROPIC_MAP_AGENT_TOKEN=your-anthropic-token
# ANTHROPIC_MAP_AGENT_BASE_URL=https://your-anthropic-endpoint
# ANTHROPIC_MAP_AGENT_MODEL=claude-3-5-sonnet-latest

Server Route

The example below shows the recommended API route shape. Keep provider model names, base URLs, and tokens on the server through PROVIDER_CONFIG and environment variables.

import { ChatAnthropic } from "@langchain/anthropic";
import { tool } from "@langchain/core/tools";
import { ChatOpenAI } from "@langchain/openai";
import { NextResponse } from "next/server";
import { z } from "zod";

const requestSchema = z.object({
  prompt: z.string().min(1),
  provider: z.enum(["openai", "anthropic"]).default("openai"),
});

const flyToCommandSchema = z.object({
  type: z.literal("fly_to"),
  center: z.tuple([z.number(), z.number()]),
  zoom: z.number().optional(),
  bearing: z.number().optional(),
  pitch: z.number().optional(),
  duration: z.number().optional(),
});

const flyToTool = tool(
  async (input) => JSON.stringify({ type: "fly_to", ...input }),
  {
    name: "fly_to",
    description: "Fly the map camera to a target location.",
    schema: flyToCommandSchema.omit({ type: true }),
  },
);

const PROVIDER_CONFIG = {
  openai: {
    model: process.env.OPENAI_MAP_AGENT_MODEL ?? "deepseek-chat",
    baseUrl: process.env.OPENAI_MAP_AGENT_BASE_URL,
    token: process.env.OPENAI_MAP_AGENT_TOKEN,
  },
  anthropic: {
    model: process.env.ANTHROPIC_MAP_AGENT_MODEL ?? "claude-3-5-sonnet-latest",
    baseUrl: process.env.ANTHROPIC_MAP_AGENT_BASE_URL,
    token: process.env.ANTHROPIC_MAP_AGENT_TOKEN,
  },
} as const;

export async function POST(request: Request) {
  const { prompt, provider } = requestSchema.parse(await request.json());
  const config = PROVIDER_CONFIG[provider];

  const llm =
    provider === "anthropic"
      ? new ChatAnthropic({
          model: config.model,
          apiKey: config.token,
          anthropicApiUrl: config.baseUrl,
        })
      : new ChatOpenAI({
          model: config.model,
          apiKey: config.token,
          configuration: { baseURL: config.baseUrl },
        });

  const agent = llm.bindTools([flyToTool], { tool_choice: "fly_to" });
  const response = await agent.invoke(
    [
      'You are a map camera assistant.',
      'Return exactly one "fly_to" action.',
      'center must be [longitude, latitude].',
      "",
      `User request: ${prompt}`,
    ].join("\n"),
  );

  const toolCall = response.tool_calls?.find((call) => call.name === "fly_to");
  if (!toolCall?.args) {
    return NextResponse.json(
      { error: "Model did not return a fly_to tool call." },
      { status: 500 },
    );
  }

  const command = flyToCommandSchema.parse({
    type: "fly_to",
    ...toolCall.args,
  });

  return NextResponse.json({ command, provider, model: config.model });
}

Extending The Agent

If you want the assistant to do more than fly_to, treat the change as a full contract update. The map UI, the client runtime, the server route, and the public docs all need to stay in sync.

  • Extend the frontend command contract: update src/lib/map-agent/types.ts, src/lib/map-agent/schema.ts, and src/lib/map-agent/execute.ts so the new command can be validated and executed on the client.
  • Update the assistant UI only if the public API changes: if a new feature needs extra props or different panel behavior, update src/registry/map.tsx and keep MapAgentProps aligned with the actual request shape.
  • Expand the server route tool set: update src/app/api/map-agent/route.ts to add the new tool schema, bind the tool, and normalize the returned payload to the same command shape expected by the frontend runtime.
  • Sync public documentation: update this page, API Reference, and any installation notes if the public surface or setup instructions changed.
  • Sync registry output when installable files change: if you add, remove, or rename files under src/lib/map-agent or expose new public exports, update registry.json and rebuild the registry output with npm run registry:build.
A practical rule: if you add a new command such as draw_polygon, you should expect to touch both sides of the contract at the same time. Do not change only the server tool or only the client executor.