# llms-full.txt # @amplitude/ai 0.24.1 — Complete API Reference for AI Coding Agents # # This file is the definitive guide for any coding agent instrumenting # a JavaScript/TypeScript AI application with @amplitude/ai. # For tool discovery only, see llms.txt. ## Quick Setup for AI Coding Agents Install the SDK, then configure your coding agent's MCP server for project analysis tools (scan_project, validate_file): ```bash pnpm add @amplitude/ai ``` MCP config (one line — works with any MCP-compatible agent). Configure it per project, and always name the scoped package in npx: ```json { "amplitude-ai": { "command": "npx", "args": ["-y", "-p", "@amplitude/ai", "amplitude-ai", "mcp"] } } ``` Agent-specific setup: Cursor: Add to .cursor/mcp.json in the project root: { "mcpServers": { "amplitude-ai": { "command": "npx", "args": ["-y", "-p", "@amplitude/ai", "amplitude-ai", "mcp"] } } } Then point the agent at: node_modules/@amplitude/ai/amplitude-ai.md Claude Code (writes .mcp.json in the project root): claude mcp add --scope project amplitude-ai -- npx -y -p @amplitude/ai amplitude-ai mcp Then point the agent at: node_modules/@amplitude/ai/amplitude-ai.md Codex CLI: Add to .codex/config.toml in the project root: [mcp_servers.amplitude-ai] command = "npx" args = ["-y", "-p", "@amplitude/ai", "amplitude-ai", "mcp"] AGENTS.md in the npm package is auto-read by Codex. Without MCP: Read amplitude-ai.md for the complete guided workflow, or this file for the API reference. ## Instrumentation Workflow (4 Phases) Phase 1 — Detect: Run scan_project MCP tool (or read package.json + grep for providers manually). Phase 2 — Discover: Identify agents, call sites, delegation patterns. Read source files. Phase 3 — Instrument: Create bootstrap file, swap providers, add session wrapping, tool tracking. Phase 4 — Verify: Run tsc --noEmit, validate_file on changed files, amplitude-ai doctor. For the full guided workflow with code examples for every phase, read the MCP resource: amplitude-ai://instrument-guide --- ## Core API ### AmplitudeAI(options) Initialize the SDK. Required entry point. ``` import { AmplitudeAI, AIConfig } from '@amplitude/ai'; const ai = new AmplitudeAI({ apiKey: string, config?: new AIConfig({ contentMode?: 'full' | 'metadata_only' | 'customer_enriched', redactPii?: boolean, debug?: boolean, }), }); ``` ### patch(options) / unpatch() Zero-code instrumentation. Monkey-patches all detected provider SDKs. Captures aggregate `[Agent] AI Response` events only — no user identity, no funnels. Use as a quick verification or for legacy codebases where you cannot modify call sites. ``` import { patch, unpatch } from '@amplitude/ai'; patch({ amplitudeAI: ai }); // All provider calls are now tracked. Call unpatch() to restore. ``` ### wrap(client, ai, opts?) Wrap an existing provider client without changing its construction. ``` import { wrap } from '@amplitude/ai'; const instrumented = wrap(existingOpenAIClient, ai); ``` ### ai.agent(agentId, opts?) → BoundAgent Create a bound agent for identity and session lineage. ``` const agent = ai.agent('shopping-agent', { description?: string, // human-readable, appears in event streams userId?: string, parentAgentId?: string, customerOrgId?: string, agentVersion?: string, env?: string, context?: Record, sessionId?: string, groups?: Record, deviceId?: string, browserSessionId?: string, }); ``` ### agent.child(agentId, overrides?) → BoundAgent Create a child agent that inherits parent identity. ``` const recipeAgent = shoppingAgent.child('recipe-agent', { description: 'Finds recipes and checks ingredient availability', }); ``` ### agent.session(opts?) → Session Create a session for multi-turn tracking. ``` const session = agent.session({ sessionId?: string, // defaults to random UUID userId?: string, deviceId?: string, browserSessionId?: string, // links to Amplitude browser session autoFlush?: boolean, // auto-flush on completion (default: auto-detect serverless) }); ``` ### session.run(fn) / session.runSync(fn) Execute code within session context. Auto-ends session when fn completes. Provider calls inside run() are automatically tagged with session/agent identity. ``` await session.run(async (s) => { s.trackUserMessage(content); // ... LLM calls are auto-tracked by provider wrappers ... s.trackAiMessage(response, model, provider, latencyMs); }); // Session automatically ends here, [Agent] Session End emitted. ``` ### s.runAs(childAgent, fn) / s.runAsSync(childAgent, fn) Delegate to a child agent within the same session. Shares sessionId, traceId, turn counter. Does NOT emit Session End for child. ``` await s.runAs(recipeAgent, async (cs) => { cs.trackUserMessage('Find me a pancake recipe'); // LLM calls here are tagged with recipe-agent's agentId // parentAgentId is automatically set to shopping-agent }); ``` ### s.trackUserMessage(content, opts?) → eventId Track a user message. Returns the event ID. The `content` string becomes `$llm_message.text` in Amplitude — use a short natural-language line; put structured segmentation dimensions in `opts.context` (maps to the registered `[Agent] Context` property). Avoid `opts.eventProperties` for custom dimensions on `[Agent]` events — unregistered properties may not be queryable. ``` s.trackUserMessage('What recipes do you have for pancakes?'); s.trackUserMessage('Summarize the design doc', { context: { outline: docOutline } }); ``` ### s.trackAiMessage(content, model, provider, latencyMs, opts?) → eventId Track an AI response. Use when provider wrappers can't auto-capture (e.g. Assistants API, gateways). Pass **token usage** from the provider response; use the **real** model id (not an internal gateway label). Set `totalCostUsd` in `opts` if automatic cost lookup cannot resolve the model. ``` s.trackAiMessage('Here are some pancake recipes...', 'gpt-4o-mini', 'openai', 1250, { inputTokens: 150, outputTokens: 400, totalTokens: 550, }); ``` ### s.trackToolCall(toolName, latencyMs, success, opts?) → eventId Track a tool call explicitly (prefer tool() HOF instead). ``` s.trackToolCall('search_products', 45, true); ``` ### s.score(name, value, targetId, opts?) Track a quality score (user feedback, automated eval, reviewer). ``` s.score('user-feedback', 1, 'msg-001', { targetType: 'message', source: 'user' }); ``` ### ai.score(opts) Track a score outside session context. ``` ai.score({ userId, name: 'user-feedback', value: 1, targetId: 'msg-001', targetType: 'message', source: 'user' }); ``` ### ai.flush() / agent.flush() Flush pending events. CRITICAL for serverless (Next.js, Vercel, Lambda): ``` await session.run(async (s) => { /* ... */ }); await ai.flush(); // Must call before returning response in serverless! ``` ### tool(fn, opts?) / tool(opts)(fn) Higher-order function that wraps a function to auto-emit [Agent] Tool Call events. Works inside session.run() — automatically inherits session/agent context. ``` import { tool } from '@amplitude/ai'; const searchProducts = tool(async (args: { query: string }) => { /* ... */ }, { name: 'search_products', }); // Inside session.run(), just call the wrapped function: const results = await searchProducts({ query: 'pancakes' }); // [Agent] Tool Call event is automatically emitted with duration, success, input/output. ``` ### observe(fn, opts?) / observe(opts)(fn) Higher-order function that wraps a function to auto-emit [Agent] Span events. Creates a session boundary if none exists. ``` import { observe } from '@amplitude/ai'; const handleRequest = observe(async (req) => { /* ... */ }, { name: 'request-handler' }); ``` ### injectContext() / extractContext(headers) Cross-service context propagation for message queues and microservices. ``` import { injectContext, extractContext } from '@amplitude/ai'; // Sender (inside session.run): const headers = injectContext(); await queue.send({ payload, headers }); // Receiver: const ctx = extractContext(message.headers); const session = agent.session({ ...ctx }); ``` ### createAmplitudeAIMiddleware(opts) Express/Fastify/Hono middleware for automatic session tracking. ``` import { createAmplitudeAIMiddleware } from '@amplitude/ai'; app.use(createAmplitudeAIMiddleware({ amplitudeAI: ai, userIdResolver: (req) => req.user?.id ?? null, // authenticated user, not a request header })); ``` ### MockAmplitudeAI (testing) Deterministic test double. Captures events in-memory. ``` import { AIConfig } from '@amplitude/ai'; import { MockAmplitudeAI } from '@amplitude/ai/testing'; const mock = new MockAmplitudeAI(new AIConfig({ contentMode: 'full' })); const agent = mock.agent('test-agent', { userId: 'u1' }); await agent.session({ sessionId: 's1' }).run(async (s) => { /* ... */ }); mock.getEvents() // all events mock.getEvents('[Agent] Tool Call') // filtered by type mock.eventsForAgent('recipe-agent') // filtered by agent ID mock.assertEventTracked('[Agent] User Message', { userId: 'u1' }) mock.assertSessionClosed('s1') await mock.flush(); ``` --- ## Provider Wrappers | Provider | Import | Streaming | Tool Calls | TTFB | Cache | |-------------|----------------------------------|-----------|------------|------|-------| | OpenAI | `new OpenAI({ amplitude: ai })` | Yes | Yes | Yes | Yes | | Anthropic | `new Anthropic({ amplitude: ai })`| Yes | Yes | Yes | Yes | | Gemini | `new Gemini({ amplitude: ai })` | Yes | No | No | No | | AzureOpenAI | `new AzureOpenAI({ amplitude: ai })`| Yes | Yes | Yes | No | | Bedrock | `new Bedrock({ amplitude: ai })` | Yes | Yes | No | No | | Mistral | `new Mistral({ amplitude: ai })` | Yes | No | No | No | All wrappers are imported from '@amplitude/ai'. --- ## Canonical Patterns ### 1. Zero-code (patch) ```typescript import { AmplitudeAI, patch } from '@amplitude/ai'; const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY! }); patch({ amplitudeAI: ai }); ``` ### 2. Wrap existing client ```typescript import { AmplitudeAI, wrap } from '@amplitude/ai'; const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY! }); const instrumented = wrap(existingOpenAIClient, ai); ``` ### 3. Provider wrapper (recommended) ```typescript import { AmplitudeAI, AIConfig, OpenAI } from '@amplitude/ai'; const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY!, config: new AIConfig({ contentMode: 'full', redactPii: true }), }); const openai = new OpenAI({ amplitude: ai, apiKey: process.env.OPENAI_API_KEY! }); ``` ### 4. Multi-agent with session + runAs (recommended for multi-agent apps) ```typescript import { AmplitudeAI, AIConfig, OpenAI, tool } from '@amplitude/ai'; const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY!, config: new AIConfig({ contentMode: 'full', redactPii: true }) }); const openai = new OpenAI({ amplitude: ai, apiKey: process.env.OPENAI_API_KEY! }); const orchestrator = ai.agent('shopping-agent', { description: 'Orchestrates shopping requests' }); const recipeAgent = orchestrator.child('recipe-agent', { description: 'Finds recipes' }); const askRecipeAgent = tool(async (args: { query: string }) => { // ... calls openai inside s.runAs(recipeAgent, ...) }, { name: 'ask_recipe_agent' }); export async function POST(req: Request) { const { messages, userId } = await req.json(); const browserSessionId = req.headers.get('x-amplitude-session-id'); const deviceId = req.headers.get('x-amplitude-device-id'); return orchestrator.session({ userId, browserSessionId, deviceId }).run(async (s) => { s.trackUserMessage(messages[messages.length - 1].content); // Provider calls auto-tracked. tool() calls auto-tracked. const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages }); s.trackAiMessage(response.choices[0].message.content, 'gpt-4o-mini', 'openai', latencyMs); await ai.flush(); // serverless: flush before returning return Response.json({ content: response.choices[0].message.content }); }); } ``` ### 5. Express middleware ```typescript import { AmplitudeAI, createAmplitudeAIMiddleware } from '@amplitude/ai'; const ai = new AmplitudeAI({ apiKey: process.env.AMPLITUDE_AI_API_KEY! }); app.use(createAmplitudeAIMiddleware({ amplitudeAI: ai, userIdResolver: (req) => req.user?.id ?? null, // authenticated user, not a request header })); ``` ### 6. Testing with MockAmplitudeAI ```typescript import { AIConfig, tool } from '@amplitude/ai'; import { MockAmplitudeAI } from '@amplitude/ai/testing'; const mock = new MockAmplitudeAI(new AIConfig({ contentMode: 'full' })); const agent = mock.agent('test-agent'); const session = agent.session({ userId: 'test-user', sessionId: 'test-session' }); await session.run(async (s) => { s.trackUserMessage('hello'); }); expect(mock.getEvents('[Agent] User Message').length).toBe(1); mock.assertSessionClosed('test-session'); ``` --- ## MCP Tools (via amplitude-ai mcp) All tools are available when the MCP server is connected. ### scan_project(root_path) → ScanResult Detect framework, providers, agents, call sites, and multi-agent signals. Returns structured JSON with: framework, providers[], agents[] (each with inferred_id, file, call_site_details, tool_definitions), is_multi_agent, multi_agent_signals[], has_streaming, has_edge_runtime, has_frontend_deps, recommendations[]. Use this as the first step of instrumentation. ### validate_file(source, language?) → FileAnalysis AST-based analysis of a single file for uninstrumented LLM call sites. Returns: call_sites[] with instrumented (bool), has_patch, has_amplitude_import, has_session_context. Run before and after instrumentation to verify coverage. ### instrument_file(source, language?, scan_result?) → InstrumentedSource Apply text-based provider swap and session transforms. Best for simple import swaps. For multi-agent patterns, prefer direct editing. ### generate_verify_test(scan_result) → TestSource Generate a vitest verification test using MockAmplitudeAI. Tests each discovered agent and multi-agent delegation. ### validate_setup() → SetupStatus Check required environment variables (AMPLITUDE_AI_API_KEY, etc.). ### suggest_instrumentation(framework?, provider?, content_tier?) → Guidance Framework-specific instrumentation guidance with content-tier and privacy defaults. ### get_event_schema(event_type?) → EventSchema Return the event property catalog for all or specific event types. ### get_integration_pattern(id?) → Pattern Return canonical instrumentation patterns (zero-code, wrap, bound-agent, etc.). ### search_docs(query, max_results?) → SearchResults Keyword search over README, this file, and amplitude-ai.md. ## MCP Resources ### amplitude-ai://event-schema Full event property catalog (all [Agent] event types and their properties). ### amplitude-ai://integration-patterns Canonical instrumentation patterns as structured JSON. ### amplitude-ai://instrument-guide The complete 4-phase instrumentation workflow (Detect → Discover → Instrument → Verify) with code examples for every step. Read this for guided instrumentation. --- ## CLI - `amplitude-ai` — Print instrumentation prompt for AI coding agents - `amplitude-ai --print-guide` — Print the full amplitude-ai.md guide to stdout - `amplitude-ai mcp` — Start the MCP server for AI coding agents - `amplitude-ai doctor [--json] [--no-mock-check]` — Validate environment and event pipeline - `amplitude-ai status [--json]` — Show SDK version, installed providers, and env config - `amplitude-ai --help` / `amplitude-ai --version` --- ## Events Emitted | Event Type | Emitted By | |---|---| | [Agent] User Message | s.trackUserMessage() | | [Agent] AI Response | Provider wrappers (auto) or s.trackAiMessage() | | [Agent] Tool Call | tool() HOF (auto) or s.trackToolCall() | | [Agent] Span | observe() HOF (auto) or s.trackSpan() | | [Agent] Session End | session.run() completion (auto) or s.trackSessionEnd() | | [Agent] Score | ai.score() or s.score() | | [Agent] Embedding | provider wrappers (auto) | | [Agent] Session Enrichment | s.trackSessionEnrichment() | --- ## Common Errors - "No events captured" → Ensure session.run() wraps your LLM calls - "patch() drops events silently" → patch() requires active SessionContext; use session.run() - "flush() timeout" → Call ai.flush() before process exit in serverless (Next.js, Lambda) - "tool() not tracking" → tool() must be called inside session.run() to inherit context - "child agent events missing parentAgentId" → Use s.runAs(child, fn), not direct child calls - "observe() not emitting spans" → observe() must be called inside session.run() for session context --- ## Content shaping (excerpt from amplitude-ai.md) Full guide: `amplitude-ai.md` or MCP resource `amplitude-ai://instrument-guide`. **User message text vs structured pipeline data (critical for Agent Analytics UI):** - `s.trackUserMessage(...)` **first argument** (the string `content`) becomes **`$llm_message.text`** on `[Agent] User Message`. That string is what **session lists, segmentation, and enrichment** treat as “what the user said.” - **Do not** pass large JSON blobs, RAG context packs, or internal pipeline state as the **only** user message body — the product will show that JSON as the session title and break down charts by raw JSON. - **Do** pass a **short natural-language** line (the real end-user prompt, or a canonical summary for headless jobs, e.g. `"Summarize the attached design doc and list open questions"`). - Put structured segmentation dimensions in the **options** object: **`context`** (object → `[Agent] Context` JSON, always queryable in charts). Avoid `eventProperties` for custom dimensions on `[Agent]` events — unregistered properties may be silently dropped by schema enforcement. ```typescript // GOOD: readable user line + structured state in context s.trackUserMessage('Summarize the attached design doc and list open questions', { context: { structuredPayload: payloadRecord }, }); // BAD: entire pipeline state as the "user message" (shows up as session label / $llm_message.text) s.trackUserMessage(JSON.stringify(payloadRecord)); ``` **Provider wrapper auto-tracking caution:** Wrapped providers automatically emit `[Agent] User Message` for every `role: "user"` message in the LLM request. **Do not call `s.trackUserMessage()` for the same user input you are also passing to a provider wrapper** — you will get two `[Agent] User Message` events for a single user turn. Choose one: either call `s.trackUserMessage()` manually and suppress auto-tracking, or rely on the provider wrapper and omit the manual call. If internal delegation calls include structured prompts as `role: "user"`, those will appear as duplicate user turns unless you use `runAs()` (which suppresses auto user-message tracking). See **Step 3g-b** for the canonical fan-out pattern. Structured content channels, including tool input/output, attachments, and span state, recognize complete JSON object/array strings up to 100,000 characters, within the runtime parser limits. Within serialized JSON strings, built-in PII redaction scans decoded string values and object keys. Numeric tokens under recognized phone, card, and SSN field names (for example, `phone_number`, `creditCardNumber`, and `ssn`, ignoring case and punctuation) become string placeholders; that context extends through nested objects, arrays, and serialized JSON strings. Other numeric tokens, including timestamps and large integers, remain verbatim. Distinct original keys that would collapse to the same redacted key fail the entire serialized document closed. This field-sensitive handling does not change native object/array sanitization. Ambiguous unlabeled numbers require producer-selected content or configured custom redaction. Custom patterns and callbacks retain their original string boundary and run once after built-in redaction, followed by base64 redaction. If these later steps make an originally valid JSON document invalid, the SDK emits `[content redacted]`. Native parser limits or excessive nested JSON-string depth also fail closed. Numeric-looking strings, including timestamps embedded in HTML source, remain ordinary redaction. Malformed or oversized JSON follows the ordinary text-redaction and truncation path; existing final size limits can still truncate output. **HTML artifact capture:** HTML inside `$agent_artifact` envelopes uses the same content settings, ordinary string redaction, and serialization as other tool/span input/output and AI/user message attachments. `full` captures these fields; `metadata_only` and `customer_enriched` omit them. This runtime behavior does not require a new artifact-specific SDK API or a minimum SDK version beyond the tracking APIs you use. Version 0.24.0 adds optional `$agent_artifact` typing for inline attachment literals; it does not add schema validation or change capture. Select source data approved for analytics capture before constructing HTML. The producer owns the snapshot content. Ordinary redaction may retain text encoded in entities, split across elements, generated by CSS, or hidden by layout. The SDK does not parse HTML, inspect rendered output, enforce a CSS subset, or guarantee removal of every rendered identifier. Its configured PII patterns and custom redactor make one ordinary best-effort pass over each string value; they can change the HTML source and are not a browser-aware sanitizer. Do not add a second SDK redaction pass around these tracking calls. A custom callback should return the intended final string and can still fail closed through the existing ordinary sanitizer behavior. Preflight the completed snapshot and containing serialized field against the receiving artifact contract and existing SDK/event size limits before tracking. Ordinary serialization can truncate oversized values; it does not provide artifact-specific completeness guarantees. Omit an optional snapshot at the producer when it cannot fit intact. Keep the original event and useful metadata, and emit each event once. HTTP/OTLP/warehouse integrations have their own transport-specific capture and redaction configuration; see `docs/integrations/privacy.md`.