@amplitude/ai 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AGENTS.md
2
2
 
3
- Package: `@amplitude/ai` v0.3.2
3
+ Package: `@amplitude/ai` v0.3.4
4
4
 
5
5
  ## Install
6
6
 
package/README.md CHANGED
@@ -55,8 +55,8 @@ The agent reads the guide, scans your project, discovers your agents and LLM cal
55
55
 
56
56
  | Your situation | Recommended path | What happens |
57
57
  |---|---|---|
58
- | **Manual setup** | Follow the [code example above](#amplitude-ai) | Agents + sessions + provider wrappers — the full event model |
59
- | **Just want to verify the SDK works** | `patch()` ([details below](#patching-diagnostic--legacy)) | Aggregate cost/latency monitoring only — no user analytics, no funnels |
58
+ | **Manual setup** | Follow the [Quick Start guide](#quick-start) | Agents + sessions + provider wrappers — the full event model |
59
+ | **Just want to verify the SDK works** | `patch()` ([details below](#patching)) | Aggregate cost/latency monitoring only — no user analytics, no funnels |
60
60
 
61
61
  > **Start with full instrumentation.** The coding agent workflow defaults to agents + sessions + provider wrappers. This gives you every event type, per-user analytics, and server-side enrichment. `patch()` exists for quick verification or legacy codebases where you can't modify call sites, but it only captures `[Agent] AI Response` without user identity — no funnels, no cohorts, no retention.
62
62
 
@@ -84,7 +84,7 @@ The agent reads the guide, scans your project, discovers your agents and LLM cal
84
84
  - [Session](#session)
85
85
  - [Configuration](#configuration)
86
86
  - [Context Dict Conventions](#context-dict-conventions)
87
- - [Privacy & Content Control](#privacy--content-control)
87
+ - [Privacy & Content Control](#privacy-content-control)
88
88
  - [Cache-Aware Cost Tracking](#cache-aware-cost-tracking)
89
89
  - [Semantic Cache Tracking](#semantic-cache-tracking)
90
90
  - [Model Tier Classification](#model-tier-classification)
@@ -96,7 +96,7 @@ The agent reads the guide, scans your project, discovers your agents and LLM cal
96
96
  - [Scoring Patterns](#scoring-patterns)
97
97
  - [Enrichments](#enrichments)
98
98
  - [Debug and Dry-Run Modes](#debug-and-dry-run-modes)
99
- - [Patching (Diagnostic / Legacy)](#patching-diagnostic--legacy)
99
+ - [Patching](#patching)
100
100
  - [Auto-Instrumentation CLI](#auto-instrumentation-cli)
101
101
  - [Integrations](#integrations)
102
102
  - [Data Flow](#data-flow)
@@ -413,7 +413,7 @@ const agent = ai.agent('support-bot', {
413
413
  });
414
414
  ```
415
415
 
416
- Child agents inherit context (including `description`) from their parent and automatically set `parentAgentId`:
416
+ Child agents inherit context from their parent and automatically set `parentAgentId` (note: `description` is agent-specific and is **not** inherited — pass it explicitly if needed):
417
417
 
418
418
  ```typescript
419
419
  const orchestrator = ai.agent('orchestrator', {
@@ -1367,7 +1367,7 @@ Both modes can be enabled via environment variables when using auto-instrumentat
1367
1367
  AMPLITUDE_AI_DEBUG=true amplitude-ai-instrument node app.js
1368
1368
  ```
1369
1369
 
1370
- ## Patching (Diagnostic / Legacy)
1370
+ ## Patching
1371
1371
 
1372
1372
  Monkey-patch provider SDKs to auto-track without changing call sites. This is useful for quick verification that the SDK is connected, or for legacy codebases where modifying call sites is impractical. For the full event model (user messages, sessions, scoring, enrichments), use agents + sessions as shown in [Quick Start](#quick-start).
1373
1373
 
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","names":[],"sources":["../../src/mcp/contract.ts"],"sourcesContent":[],"mappings":";cAAa,eAAA;AAAA,cAEA,SAFe,EAAA;EAEf,SAAA,cAUH,EAAA,kBAAA;EAEG,SAAA,qBAIH,EAAA,yBAAA;EAEG,SAAA,aAEH,EAAA,gBAAA;EAEG,SAAA,sBAKH,EAAA,yBAAA;EAEE,SAAA,YAAS,EAAW,eAAwB;;;;;;cAjB3C;;;;;cAMA;;;cAIA;;;;;;KAOD,SAAA,WAAoB,wBAAwB"}
1
+ {"version":3,"file":"contract.d.ts","names":[],"sources":["../../src/mcp/contract.ts"],"sourcesContent":[],"mappings":";cAAa,eAAA;AAAA,cAEA,SAFe,EAAA;EAEf,SAAA,cAUH,EAAA,kBAAA;EAEG,SAAA,qBAIH,EAAA,yBAAA;EAEG,SAAA,aAEH,EAAA,gBAAA;EAEG,SAAA,sBAKH,EAAA,yBAAA;EAEE,SAAA,YAAS,EAAA,eAAmC;;;;;;cAjB3C;;;;;cAMA;;;cAIA;;;;;;KAOD,SAAA,WAAoB,wBAAwB"}
@@ -97,7 +97,7 @@ function addSessionWrapping(source, agentId, bootstrapImportPath) {
97
97
  const agentLine = `const agent = ai.agent('${agentId}');\n`;
98
98
  if (ROUTE_HANDLER_RE.test(result)) {
99
99
  const handlerMatch = result.match(/export\s+async\s+function\s+(?:POST|GET|PUT|DELETE)\s*\([^)]*\)\s*\{/);
100
- result = result.replace(/(export\s+async\s+function\s+(?:POST|GET|PUT|DELETE)\s*\([^)]*\)\s*\{)/, `$1\n ${agentLine.trim()}\n const { messages, userId, sessionId } = await req.json();\n const _response = await agent.session({ userId, sessionId }).run(async (s) => {`);
100
+ result = result.replace(/(export\s+async\s+function\s+(?:POST|GET|PUT|DELETE)\s*\([^)]*\)\s*\{)/, `$1\n ${agentLine.trim()}\n const { messages, userId, sessionId } = await req.json();\n // TODO(required): Ensure userId comes from auth, not the request body, to prevent spoofing.\n // sessionId is optional — omit it and the SDK auto-generates a unique UUID per request.\n const _response = await agent.session({ userId, ...(sessionId && { sessionId }) }).run(async (s) => {`);
101
101
  if (handlerMatch?.index != null) {
102
102
  const openBraceIdx = result.indexOf("{", handlerMatch.index);
103
103
  if (openBraceIdx >= 0) {
@@ -112,7 +112,7 @@ function addSessionWrapping(source, agentId, bootstrapImportPath) {
112
112
  result = `${result.slice(0, closingBraceIdx)} });\n await ai.flush();\n return _response;\n${result.slice(closingBraceIdx)}`;
113
113
  }
114
114
  }
115
- } else if (EXPRESS_HANDLER_RE.test(result) || HONO_HANDLER_RE.test(result)) result = result.replace(/((?:app|router)\.\s*(?:get|post|put|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?\([^)]*\)\s*(?:=>)?\s*\{)/, `$1\n ${agentLine.trim()}\n const _response = await agent.session({ userId: 'todo-extract-user-id', sessionId: 'todo-extract-session-id' }).run(async (s) => {`);
115
+ } else if (EXPRESS_HANDLER_RE.test(result) || HONO_HANDLER_RE.test(result)) result = result.replace(/((?:app|router)\.\s*(?:get|post|put|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?\([^)]*\)\s*(?:=>)?\s*\{)/, `$1\n // TODO(required): Replace with the real user ID from your auth/request context.\n // Without a real userId, per-user funnels, retention, and cohorts won't work.\n const _userId = 'anonymous'; // e.g. req.user.id, req.auth.sub, req.session.userId\n ${agentLine.trim()}\n const _response = await agent.session({ userId: _userId }).run(async (s) => {`);
116
116
  return result;
117
117
  }
118
118
  function addUserMessageTracking(source) {
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-file.js","names":["PROVIDER_IMPORT_MAP: Record<string, { module: string; defaultExport: string; namedExport: string }>","results: Array<{ start: number; end: number; fullMatch: string }>","namedImports: string[]"],"sources":["../../src/mcp/instrument-file.ts"],"sourcesContent":["export interface InstrumentFileOptions {\n source: string;\n filePath: string;\n tier: 'quick_start' | 'standard' | 'advanced';\n bootstrapImportPath: string;\n agentId: string;\n description?: string | null;\n providers: string[];\n}\n\nconst PROVIDER_IMPORT_MAP: Record<string, { module: string; defaultExport: string; namedExport: string }> = {\n openai: { module: 'openai', defaultExport: 'OpenAI', namedExport: 'openai' },\n '@anthropic-ai/sdk': { module: '@anthropic-ai/sdk', defaultExport: 'Anthropic', namedExport: 'anthropic' },\n '@google/generative-ai': { module: '@google/generative-ai', defaultExport: 'GoogleGenerativeAI', namedExport: 'gemini' },\n '@google/genai': { module: '@google/genai', defaultExport: 'GoogleGenAI', namedExport: 'genai' },\n '@mistralai/mistralai': { module: '@mistralai/mistralai', defaultExport: 'Mistral', namedExport: 'mistral' },\n '@azure/openai': { module: '@azure/openai', defaultExport: 'AzureOpenAI', namedExport: 'azureOpenai' },\n 'cohere-ai': { module: 'cohere-ai', defaultExport: 'CohereClient', namedExport: 'cohere' },\n};\n\n// Balanced-paren constructor matcher: handles nested parens like new OpenAI({ apiKey: getKey() })\nfunction matchConstructor(source: string, constructorName: string): Array<{ start: number; end: number; fullMatch: string }> {\n const results: Array<{ start: number; end: number; fullMatch: string }> = [];\n const re = new RegExp(`new\\\\s+${constructorName}\\\\s*\\\\(`, 'g');\n for (const m of source.matchAll(re)) {\n const openIdx = (m.index ?? 0) + m[0].length - 1;\n let depth = 1;\n let i = openIdx + 1;\n while (i < source.length && depth > 0) {\n if (source[i] === '(') depth++;\n else if (source[i] === ')') depth--;\n i++;\n }\n results.push({ start: m.index ?? 0, end: i, fullMatch: source.slice(m.index ?? 0, i) });\n }\n return results;\n}\n\nconst ROUTE_HANDLER_RE =\n /export\\s+async\\s+function\\s+(?:POST|GET|PUT|DELETE)\\b/;\nconst EXPRESS_HANDLER_RE =\n /(?:app|router)\\.\\s*(?:get|post|put|delete)\\s*\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*(?:async\\s+)?\\(/;\nconst HONO_HANDLER_RE =\n /(?:app|router)\\.\\s*(?:get|post|put|delete)\\s*\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*(?:async\\s+)?\\(\\s*c\\b/;\n\nfunction replaceProviderImports(\n source: string,\n providers: string[],\n bootstrapImportPath: string,\n): string {\n let result = source;\n const namedImports: string[] = [];\n\n for (const provider of providers) {\n const mapping = PROVIDER_IMPORT_MAP[provider];\n if (!mapping) continue;\n\n // Match default import: import OpenAI from 'openai'\n const defaultImportRe = new RegExp(\n `import\\\\s+${mapping.defaultExport}\\\\s+from\\\\s+['\"]${mapping.module}['\"];?`,\n );\n // Match named import (single or multi-line): import { OpenAI } from 'openai'\n // or: import {\\n OpenAI,\\n AsyncOpenAI,\\n} from 'openai'\n const escapedModule = mapping.module.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const namedImportRe = new RegExp(\n `import\\\\s*\\\\{[^}]*\\\\b${mapping.defaultExport}\\\\b[^}]*\\\\}\\\\s*from\\\\s+['\"]${escapedModule}['\"];?`,\n 's',\n );\n if (defaultImportRe.test(result)) {\n result = result.replace(defaultImportRe, '');\n namedImports.push(mapping.namedExport);\n } else if (namedImportRe.test(result)) {\n result = result.replace(namedImportRe, '');\n namedImports.push(mapping.namedExport);\n }\n\n // Replace constructors with pre-wrapped named imports using balanced-paren matching\n const matches = matchConstructor(result, mapping.defaultExport);\n for (let i = matches.length - 1; i >= 0; i--) {\n const match = matches[i];\n if (match) {\n result = result.slice(0, match.start) + mapping.namedExport + result.slice(match.end);\n }\n }\n }\n\n if (namedImports.length > 0) {\n const importLine = `import { ${namedImports.join(', ')} } from '${bootstrapImportPath}';\\n`;\n result = importLine + result;\n }\n\n return result;\n}\n\nfunction addSessionWrapping(\n source: string,\n agentId: string,\n bootstrapImportPath: string,\n): string {\n let result = source;\n\n const importFromPath = new RegExp(\n `import\\\\s*\\\\{([^}]*)\\\\}\\\\s*from\\\\s*['\"]${bootstrapImportPath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}['\"]`,\n );\n const existingImportMatch = importFromPath.exec(result);\n if (existingImportMatch) {\n const existingNames = existingImportMatch[1] ?? '';\n const importedNames = existingNames.split(',').map(s => s.trim());\n if (!importedNames.includes('ai')) {\n const newNames = existingNames.trim() ? `ai, ${existingNames.trim()}` : 'ai';\n result = result.replace(existingImportMatch[0], `import { ${newNames} } from '${bootstrapImportPath}'`);\n }\n } else if (!result.includes(`from '${bootstrapImportPath}'`) &&\n !result.includes(`from \"${bootstrapImportPath}\"`)) {\n result = `import { ai } from '${bootstrapImportPath}';\\n${result}`;\n }\n\n const agentLine = `const agent = ai.agent('${agentId}');\\n`;\n\n // Wrap route handler body inside session.run(), with flush after session completes\n if (ROUTE_HANDLER_RE.test(result)) {\n const handlerMatch = result.match(\n /export\\s+async\\s+function\\s+(?:POST|GET|PUT|DELETE)\\s*\\([^)]*\\)\\s*\\{/,\n );\n result = result.replace(\n /(export\\s+async\\s+function\\s+(?:POST|GET|PUT|DELETE)\\s*\\([^)]*\\)\\s*\\{)/,\n `$1\\n ${agentLine.trim()}\\n const { messages, userId, sessionId } = await req.json();\\n const _response = await agent.session({ userId, sessionId }).run(async (s) => {`,\n );\n if (handlerMatch?.index != null) {\n const openBraceIdx = result.indexOf('{', handlerMatch.index);\n if (openBraceIdx >= 0) {\n let depth = 1;\n let i = openBraceIdx + 1;\n while (i < result.length && depth > 0) {\n if (result[i] === '{') depth++;\n else if (result[i] === '}') depth--;\n i++;\n }\n const closingBraceIdx = i - 1;\n result = `${result.slice(0, closingBraceIdx)} });\\n await ai.flush();\\n return _response;\\n${result.slice(closingBraceIdx)}`;\n }\n }\n } else if (EXPRESS_HANDLER_RE.test(result) || HONO_HANDLER_RE.test(result)) {\n result = result.replace(\n /((?:app|router)\\.\\s*(?:get|post|put|delete)\\s*\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*(?:async\\s+)?\\([^)]*\\)\\s*(?:=>)?\\s*\\{)/,\n `$1\\n ${agentLine.trim()}\\n const _response = await agent.session({ userId: 'todo-extract-user-id', sessionId: 'todo-extract-session-id' }).run(async (s) => {`,\n );\n }\n\n return result;\n}\n\nfunction addUserMessageTracking(source: string): string {\n const requestBodyRe = /(const\\s+\\{[^}]*\\}\\s*=\\s*(?:await\\s+)?(?:req\\.body|request\\.json\\(\\)|await\\s+request\\.json\\(\\)))/;\n const match = requestBodyRe.exec(source);\n if (match) {\n return source.replace(\n match[0],\n `${match[0]};\\n // TODO: extract user message and call s.trackUserMessage(userMessage)`,\n );\n }\n return source;\n}\n\nexport function instrumentFile(opts: InstrumentFileOptions): string {\n if (opts.tier === 'quick_start') {\n return opts.source;\n }\n\n let result = opts.source;\n\n result = replaceProviderImports(result, opts.providers, opts.bootstrapImportPath);\n\n if (opts.tier === 'advanced') {\n result = addSessionWrapping(result, opts.agentId, opts.bootstrapImportPath);\n result = addUserMessageTracking(result);\n }\n\n return result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"instrument-file.js","names":["PROVIDER_IMPORT_MAP: Record<string, { module: string; defaultExport: string; namedExport: string }>","results: Array<{ start: number; end: number; fullMatch: string }>","namedImports: string[]"],"sources":["../../src/mcp/instrument-file.ts"],"sourcesContent":["export interface InstrumentFileOptions {\n source: string;\n filePath: string;\n tier: 'quick_start' | 'standard' | 'advanced';\n bootstrapImportPath: string;\n agentId: string;\n description?: string | null;\n providers: string[];\n}\n\nconst PROVIDER_IMPORT_MAP: Record<string, { module: string; defaultExport: string; namedExport: string }> = {\n openai: { module: 'openai', defaultExport: 'OpenAI', namedExport: 'openai' },\n '@anthropic-ai/sdk': { module: '@anthropic-ai/sdk', defaultExport: 'Anthropic', namedExport: 'anthropic' },\n '@google/generative-ai': { module: '@google/generative-ai', defaultExport: 'GoogleGenerativeAI', namedExport: 'gemini' },\n '@google/genai': { module: '@google/genai', defaultExport: 'GoogleGenAI', namedExport: 'genai' },\n '@mistralai/mistralai': { module: '@mistralai/mistralai', defaultExport: 'Mistral', namedExport: 'mistral' },\n '@azure/openai': { module: '@azure/openai', defaultExport: 'AzureOpenAI', namedExport: 'azureOpenai' },\n 'cohere-ai': { module: 'cohere-ai', defaultExport: 'CohereClient', namedExport: 'cohere' },\n};\n\n// Balanced-paren constructor matcher: handles nested parens like new OpenAI({ apiKey: getKey() })\nfunction matchConstructor(source: string, constructorName: string): Array<{ start: number; end: number; fullMatch: string }> {\n const results: Array<{ start: number; end: number; fullMatch: string }> = [];\n const re = new RegExp(`new\\\\s+${constructorName}\\\\s*\\\\(`, 'g');\n for (const m of source.matchAll(re)) {\n const openIdx = (m.index ?? 0) + m[0].length - 1;\n let depth = 1;\n let i = openIdx + 1;\n while (i < source.length && depth > 0) {\n if (source[i] === '(') depth++;\n else if (source[i] === ')') depth--;\n i++;\n }\n results.push({ start: m.index ?? 0, end: i, fullMatch: source.slice(m.index ?? 0, i) });\n }\n return results;\n}\n\nconst ROUTE_HANDLER_RE =\n /export\\s+async\\s+function\\s+(?:POST|GET|PUT|DELETE)\\b/;\nconst EXPRESS_HANDLER_RE =\n /(?:app|router)\\.\\s*(?:get|post|put|delete)\\s*\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*(?:async\\s+)?\\(/;\nconst HONO_HANDLER_RE =\n /(?:app|router)\\.\\s*(?:get|post|put|delete)\\s*\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*(?:async\\s+)?\\(\\s*c\\b/;\n\nfunction replaceProviderImports(\n source: string,\n providers: string[],\n bootstrapImportPath: string,\n): string {\n let result = source;\n const namedImports: string[] = [];\n\n for (const provider of providers) {\n const mapping = PROVIDER_IMPORT_MAP[provider];\n if (!mapping) continue;\n\n // Match default import: import OpenAI from 'openai'\n const defaultImportRe = new RegExp(\n `import\\\\s+${mapping.defaultExport}\\\\s+from\\\\s+['\"]${mapping.module}['\"];?`,\n );\n // Match named import (single or multi-line): import { OpenAI } from 'openai'\n // or: import {\\n OpenAI,\\n AsyncOpenAI,\\n} from 'openai'\n const escapedModule = mapping.module.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const namedImportRe = new RegExp(\n `import\\\\s*\\\\{[^}]*\\\\b${mapping.defaultExport}\\\\b[^}]*\\\\}\\\\s*from\\\\s+['\"]${escapedModule}['\"];?`,\n 's',\n );\n if (defaultImportRe.test(result)) {\n result = result.replace(defaultImportRe, '');\n namedImports.push(mapping.namedExport);\n } else if (namedImportRe.test(result)) {\n result = result.replace(namedImportRe, '');\n namedImports.push(mapping.namedExport);\n }\n\n // Replace constructors with pre-wrapped named imports using balanced-paren matching\n const matches = matchConstructor(result, mapping.defaultExport);\n for (let i = matches.length - 1; i >= 0; i--) {\n const match = matches[i];\n if (match) {\n result = result.slice(0, match.start) + mapping.namedExport + result.slice(match.end);\n }\n }\n }\n\n if (namedImports.length > 0) {\n const importLine = `import { ${namedImports.join(', ')} } from '${bootstrapImportPath}';\\n`;\n result = importLine + result;\n }\n\n return result;\n}\n\nfunction addSessionWrapping(\n source: string,\n agentId: string,\n bootstrapImportPath: string,\n): string {\n let result = source;\n\n const importFromPath = new RegExp(\n `import\\\\s*\\\\{([^}]*)\\\\}\\\\s*from\\\\s*['\"]${bootstrapImportPath.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}['\"]`,\n );\n const existingImportMatch = importFromPath.exec(result);\n if (existingImportMatch) {\n const existingNames = existingImportMatch[1] ?? '';\n const importedNames = existingNames.split(',').map(s => s.trim());\n if (!importedNames.includes('ai')) {\n const newNames = existingNames.trim() ? `ai, ${existingNames.trim()}` : 'ai';\n result = result.replace(existingImportMatch[0], `import { ${newNames} } from '${bootstrapImportPath}'`);\n }\n } else if (!result.includes(`from '${bootstrapImportPath}'`) &&\n !result.includes(`from \"${bootstrapImportPath}\"`)) {\n result = `import { ai } from '${bootstrapImportPath}';\\n${result}`;\n }\n\n const agentLine = `const agent = ai.agent('${agentId}');\\n`;\n\n // Wrap route handler body inside session.run(), with flush after session completes\n if (ROUTE_HANDLER_RE.test(result)) {\n const handlerMatch = result.match(\n /export\\s+async\\s+function\\s+(?:POST|GET|PUT|DELETE)\\s*\\([^)]*\\)\\s*\\{/,\n );\n result = result.replace(\n /(export\\s+async\\s+function\\s+(?:POST|GET|PUT|DELETE)\\s*\\([^)]*\\)\\s*\\{)/,\n `$1\\n ${agentLine.trim()}\\n const { messages, userId, sessionId } = await req.json();\\n // TODO(required): Ensure userId comes from auth, not the request body, to prevent spoofing.\\n // sessionId is optional — omit it and the SDK auto-generates a unique UUID per request.\\n const _response = await agent.session({ userId, ...(sessionId && { sessionId }) }).run(async (s) => {`,\n );\n if (handlerMatch?.index != null) {\n const openBraceIdx = result.indexOf('{', handlerMatch.index);\n if (openBraceIdx >= 0) {\n let depth = 1;\n let i = openBraceIdx + 1;\n while (i < result.length && depth > 0) {\n if (result[i] === '{') depth++;\n else if (result[i] === '}') depth--;\n i++;\n }\n const closingBraceIdx = i - 1;\n result = `${result.slice(0, closingBraceIdx)} });\\n await ai.flush();\\n return _response;\\n${result.slice(closingBraceIdx)}`;\n }\n }\n } else if (EXPRESS_HANDLER_RE.test(result) || HONO_HANDLER_RE.test(result)) {\n result = result.replace(\n /((?:app|router)\\.\\s*(?:get|post|put|delete)\\s*\\(\\s*['\"][^'\"]+['\"]\\s*,\\s*(?:async\\s+)?\\([^)]*\\)\\s*(?:=>)?\\s*\\{)/,\n `$1\\n // TODO(required): Replace with the real user ID from your auth/request context.\\n // Without a real userId, per-user funnels, retention, and cohorts won't work.\\n const _userId = 'anonymous'; // e.g. req.user.id, req.auth.sub, req.session.userId\\n ${agentLine.trim()}\\n const _response = await agent.session({ userId: _userId }).run(async (s) => {`,\n );\n }\n\n return result;\n}\n\nfunction addUserMessageTracking(source: string): string {\n const requestBodyRe = /(const\\s+\\{[^}]*\\}\\s*=\\s*(?:await\\s+)?(?:req\\.body|request\\.json\\(\\)|await\\s+request\\.json\\(\\)))/;\n const match = requestBodyRe.exec(source);\n if (match) {\n return source.replace(\n match[0],\n `${match[0]};\\n // TODO: extract user message and call s.trackUserMessage(userMessage)`,\n );\n }\n return source;\n}\n\nexport function instrumentFile(opts: InstrumentFileOptions): string {\n if (opts.tier === 'quick_start') {\n return opts.source;\n }\n\n let result = opts.source;\n\n result = replaceProviderImports(result, opts.providers, opts.bootstrapImportPath);\n\n if (opts.tier === 'advanced') {\n result = addSessionWrapping(result, opts.agentId, opts.bootstrapImportPath);\n result = addUserMessageTracking(result);\n }\n\n return result;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.d.ts","names":[],"sources":["../../src/providers/gemini.ts"],"sourcesContent":[],"mappings":";;;;;;AAaM,cADO,gBACQ,EAAA,OAAM;AAI3B,cAJM,aAIwB,EAJT,MAKR,CAAA,MAAA,EAAA,OAEK,CAAA,GAAA,IAAA;AAQK,UAXN,aAAA,CAWM;EAuBX,SAAA,EAjCC,aAiCD;EACP,MAAA,CAAA,EAAA,MAAA;EA0EO,aAAA,CAAA,EA1GM,aA0GN,GAAA,IAAA;EACP;EAtGuB,YAAA,CAAA,EAAA,OAAA;;AA0PZ,cA1PH,MAAA,SAAe,cAAA,CAgQV;;uBA7PK;yCAuBX,0BACP;+CA0EO,0BACP;;;;iBAoJW,qBAAA;;;;;;kBAME,MAAM"}
1
+ {"version":3,"file":"gemini.d.ts","names":[],"sources":["../../src/providers/gemini.ts"],"sourcesContent":[],"mappings":";;;;;;AAaM,cADO,gBACQ,EAAM,OAAA;AAI3B,cAJM,aAIwB,EAJT,MAKR,CAAA,MAAA,EAAA,OAEK,CAAA,GAAA,IAAA;AAQK,UAXN,aAAA,CAWM;EAuBX,SAAA,EAjCC,aAiCD;EACP,MAAA,CAAA,EAAA,MAAA;EA0EO,aAAA,CAAA,EA1GM,aA0GN,GAAA,IAAA;EACP;EAtGuB,YAAA,CAAA,EAAA,OAAA;;AA0PZ,cA1PH,MAAA,SAAe,cAAA,CAgQV;;uBA7PK;yCAuBX,0BACP;+CA0EO,0BACP;;;;iBAoJW,qBAAA;;;;;;kBAME,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"mistral.d.ts","names":[],"sources":["../../src/providers/mistral.ts"],"sourcesContent":[],"mappings":";;;;;;AAaM,cADO,iBACS,EAAA,OAAM;AAI5B,cAJM,cAIyB,EAJT,MAKT,CAAA,MAAA,EAAA,OAEK,CAAA,GAAA,IAAA;AAOD,UAVA,cAAA,CAUA;EAEM,SAAA,EAXV,aAWU;EAJM,MAAA,CAAA,EAAA,MAAA;EAAc,aAAA,CAAA,EALzB,aAKyB,GAAA,IAAA;EAoC9B;EAI2B,aAAA,CAAA,EAAA,OAAA;;AAQW,cAhDtC,OAAA,SAAgB,cAAA,CAgDsB;EAmF5B,QAAA,OAAA;EAA0B,SAAA,IAAA,EAjIhC,WAiIgC;EAAO,WAAA,CAAA,OAAA,EA/HjC,cA+HiC;;;cA/F3C,WAAA;;;wCAI2B;mBAQf,0BAA0B;iBAmF5B,0BAA0B"}
1
+ {"version":3,"file":"mistral.d.ts","names":[],"sources":["../../src/providers/mistral.ts"],"sourcesContent":[],"mappings":";;;;;;AAaM,cADO,iBACS,EAAM,OAAA;AAI5B,cAJM,cAIyB,EAJT,MAKT,CAAA,MAAA,EAAA,OAEK,CAAA,GAAA,IAAA;AAOD,UAVA,cAAA,CAUA;EAEM,SAAA,EAXV,aAWU;EAJM,MAAA,CAAA,EAAA,MAAA;EAAc,aAAA,CAAA,EALzB,aAKyB,GAAA,IAAA;EAoC9B;EAI2B,aAAA,CAAA,EAAA,OAAA;;AAQW,cAhDtC,OAAA,SAAgB,cAAA,CAgDsB;EAmF5B,QAAA,OAAA;EAA0B,SAAA,IAAA,EAjIhC,WAiIgC;EAAO,WAAA,CAAA,OAAA,EA/HjC,cA+HiC;;;cA/F3C,WAAA;;;wCAI2B;mBAQf,0BAA0B;iBAmF5B,0BAA0B"}
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","names":[],"sources":["../../src/utils/logger.ts"],"sourcesContent":[],"mappings":";UAAiB,MAAA;EAAA,KAAA,CAAA,OAAM,EAAA,MAAA,CAAA,EAAA,IAAA;EAcP,KAAA,CAAA,OAAS,EAAA,MAAA,CAAA,EAAuB,IAAA;;;;iBAAhC,SAAA,uBAAgC"}
1
+ {"version":3,"file":"logger.d.ts","names":[],"sources":["../../src/utils/logger.ts"],"sourcesContent":[],"mappings":";UAAiB,MAAA;EAAA,KAAA,CAAA,OAAM,EAAA,MAAA,CAAA,EAAA,IAAA;EAcP,KAAA,CAAA,OAAS,EAAA,MAAA,CAAA,EAAA,IAAuB;;;;iBAAhC,SAAA,uBAAgC"}
package/llms-full.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  # llms-full.txt
2
- # @amplitude/ai 0.3.2 — Complete API Reference for AI Coding Agents
2
+ # @amplitude/ai 0.3.4 — Complete API Reference for AI Coding Agents
3
3
  #
4
4
  # This file is the definitive guide for any coding agent instrumenting
5
5
  # a JavaScript/TypeScript AI application with @amplitude/ai.
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- GENERATED FILE: do not edit manually. Update scripts/generate-agent-docs.mjs instead. -->
2
2
  # llms.txt
3
3
  package=@amplitude/ai
4
- version=0.3.2
4
+ version=0.3.4
5
5
 
6
6
  [mcp.tools]
7
7
  get_event_schema
package/mcp.schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "generated": true,
3
3
  "package": "@amplitude/ai",
4
- "version": "0.3.2",
4
+ "version": "0.3.4",
5
5
  "prompt": "instrument_app",
6
6
  "tools": [
7
7
  "get_event_schema",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amplitude/ai",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "private": false,
5
5
  "description": "Amplitude AI SDK - LLM usage tracking for Amplitude Analytics",
6
6
  "keywords": [