@amplitude/ai 0.2.1 → 0.3.1

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.
Files changed (107) hide show
  1. package/.claude/commands/instrument-with-amplitude-ai.md +12 -0
  2. package/.cursor/skills/instrument-with-amplitude-ai/SKILL.md +4 -42
  3. package/AGENTS.md +86 -28
  4. package/README.md +190 -111
  5. package/amplitude-ai.md +463 -0
  6. package/bin/amplitude-ai-doctor.mjs +0 -0
  7. package/bin/amplitude-ai-instrument.mjs +0 -0
  8. package/bin/amplitude-ai-mcp.mjs +0 -0
  9. package/bin/amplitude-ai-register-catalog.mjs +0 -0
  10. package/bin/amplitude-ai-status.mjs +0 -0
  11. package/bin/amplitude-ai.mjs +14 -5
  12. package/data/agent_event_catalog.json +52 -2
  13. package/dist/bound-agent.d.ts +18 -14
  14. package/dist/bound-agent.d.ts.map +1 -1
  15. package/dist/bound-agent.js +4 -1
  16. package/dist/bound-agent.js.map +1 -1
  17. package/dist/cli/status.d.ts.map +1 -1
  18. package/dist/cli/status.js +31 -19
  19. package/dist/cli/status.js.map +1 -1
  20. package/dist/client.d.ts +14 -14
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +38 -0
  23. package/dist/client.js.map +1 -1
  24. package/dist/context.d.ts +5 -0
  25. package/dist/context.d.ts.map +1 -1
  26. package/dist/context.js +4 -0
  27. package/dist/context.js.map +1 -1
  28. package/dist/core/constants.d.ts +3 -1
  29. package/dist/core/constants.d.ts.map +1 -1
  30. package/dist/core/constants.js +3 -1
  31. package/dist/core/constants.js.map +1 -1
  32. package/dist/core/tracking.d.ts +12 -2
  33. package/dist/core/tracking.d.ts.map +1 -1
  34. package/dist/core/tracking.js +13 -2
  35. package/dist/core/tracking.js.map +1 -1
  36. package/dist/decorators.d.ts +1 -1
  37. package/dist/decorators.d.ts.map +1 -1
  38. package/dist/decorators.js +4 -3
  39. package/dist/decorators.js.map +1 -1
  40. package/dist/index.d.ts +7 -4
  41. package/dist/index.js +5 -2
  42. package/dist/mcp/contract.d.ts +4 -0
  43. package/dist/mcp/contract.d.ts.map +1 -1
  44. package/dist/mcp/contract.js +6 -2
  45. package/dist/mcp/contract.js.map +1 -1
  46. package/dist/mcp/generate-verify-test.d.ts +7 -0
  47. package/dist/mcp/generate-verify-test.d.ts.map +1 -0
  48. package/dist/mcp/generate-verify-test.js +33 -0
  49. package/dist/mcp/generate-verify-test.js.map +1 -0
  50. package/dist/mcp/index.d.ts +2 -1
  51. package/dist/mcp/index.js +2 -1
  52. package/dist/mcp/instrument-file.d.ts +14 -0
  53. package/dist/mcp/instrument-file.d.ts.map +1 -0
  54. package/dist/mcp/instrument-file.js +136 -0
  55. package/dist/mcp/instrument-file.js.map +1 -0
  56. package/dist/mcp/scan-project.d.ts +52 -0
  57. package/dist/mcp/scan-project.d.ts.map +1 -0
  58. package/dist/mcp/scan-project.js +309 -0
  59. package/dist/mcp/scan-project.js.map +1 -0
  60. package/dist/mcp/server.d.ts.map +1 -1
  61. package/dist/mcp/server.js +79 -4
  62. package/dist/mcp/server.js.map +1 -1
  63. package/dist/mcp/validate-file.d.ts +4 -0
  64. package/dist/mcp/validate-file.d.ts.map +1 -1
  65. package/dist/mcp/validate-file.js +559 -11
  66. package/dist/mcp/validate-file.js.map +1 -1
  67. package/dist/middleware.js +2 -1
  68. package/dist/middleware.js.map +1 -1
  69. package/dist/node_modules/.pnpm/acorn-typescript@1.4.13_acorn@8.16.0/node_modules/acorn-typescript/lib/index.js +2389 -0
  70. package/dist/node_modules/.pnpm/acorn-typescript@1.4.13_acorn@8.16.0/node_modules/acorn-typescript/lib/index.js.map +1 -0
  71. package/dist/node_modules/.pnpm/acorn@8.16.0/node_modules/acorn/dist/acorn.js +5128 -0
  72. package/dist/node_modules/.pnpm/acorn@8.16.0/node_modules/acorn/dist/acorn.js.map +1 -0
  73. package/dist/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js +1 -1
  74. package/dist/propagation.d.ts.map +1 -1
  75. package/dist/providers/anthropic.d.ts.map +1 -1
  76. package/dist/providers/anthropic.js +1 -0
  77. package/dist/providers/anthropic.js.map +1 -1
  78. package/dist/providers/base.d.ts +2 -1
  79. package/dist/providers/base.d.ts.map +1 -1
  80. package/dist/providers/base.js +4 -0
  81. package/dist/providers/base.js.map +1 -1
  82. package/dist/providers/gemini.d.ts.map +1 -1
  83. package/dist/providers/mistral.d.ts.map +1 -1
  84. package/dist/providers/openai.d.ts.map +1 -1
  85. package/dist/providers/openai.js +2 -0
  86. package/dist/providers/openai.js.map +1 -1
  87. package/dist/serverless.d.ts +19 -0
  88. package/dist/serverless.d.ts.map +1 -0
  89. package/dist/serverless.js +35 -0
  90. package/dist/serverless.js.map +1 -0
  91. package/dist/session.d.ts +24 -8
  92. package/dist/session.d.ts.map +1 -1
  93. package/dist/session.js +20 -1
  94. package/dist/session.js.map +1 -1
  95. package/dist/types.d.ts +1 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/types.js.map +1 -1
  98. package/dist/utils/logger.d.ts.map +1 -1
  99. package/llms-full.txt +353 -69
  100. package/llms.txt +6 -2
  101. package/mcp.schema.json +7 -3
  102. package/package.json +10 -5
  103. package/bin/amplitude-ai-init.mjs +0 -27
  104. package/dist/cli/init.d.ts +0 -14
  105. package/dist/cli/init.d.ts.map +0 -1
  106. package/dist/cli/init.js +0 -40
  107. package/dist/cli/init.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"validate-file.js","names":["callSites: CallSite[]","suggestions: string[]"],"sources":["../../src/mcp/validate-file.ts"],"sourcesContent":["export interface CallSite {\n line: number;\n provider: string;\n api: string;\n instrumented: boolean;\n}\n\nexport interface FileAnalysis {\n total_call_sites: number;\n instrumented: number;\n uninstrumented: number;\n has_amplitude_import: boolean;\n has_session_context: boolean;\n call_sites: CallSite[];\n suggestions: string[];\n}\n\nconst llmPatterns = [\n {\n pattern: /\\.chat\\.completions\\.create\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.chat\\.completions\\.create\\s*\\(/,\n provider: 'openai',\n api: 'chat.completions.create',\n },\n {\n pattern: /\\.chat\\.completions\\.parse\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.chat\\.completions\\.parse\\s*\\(/,\n provider: 'openai',\n api: 'chat.completions.parse',\n },\n {\n pattern: /\\.responses\\.create\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.responses\\.create\\s*\\(/,\n provider: 'openai',\n api: 'responses.create',\n },\n {\n pattern: /\\.messages\\.create\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.messages\\.create\\s*\\(/,\n provider: 'anthropic',\n api: 'messages.create',\n },\n {\n pattern: /\\.generateContent\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.generateContent\\s*\\(/,\n provider: 'gemini',\n api: 'generateContent',\n },\n {\n pattern: /\\.send\\s*\\(\\s*new\\s+InvokeModelCommand\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.send\\s*\\(/,\n provider: 'bedrock',\n api: 'invokeModel',\n },\n {\n pattern: /\\.send\\s*\\(\\s*new\\s+ConverseCommand\\s*\\(/g,\n receiverRe: /(\\w+)(?:\\.\\w+)*\\.send\\s*\\(/,\n provider: 'bedrock',\n api: 'converse',\n },\n];\n\nfunction findWrappedConstructors(source: string): Set<string> {\n const result = new Set<string>();\n const declRe =\n /(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+(?:OpenAI|Anthropic|Gemini|AzureOpenAI|Bedrock|Mistral)\\s*\\(/g;\n for (const m of source.matchAll(declRe)) {\n const varName = m[1] ?? '';\n if (!varName) continue;\n let depth = 1;\n let i = (m.index ?? 0) + m[0].length;\n let argBlock = '';\n while (i < source.length && depth > 0) {\n const ch = source[i];\n if (ch === '(') depth++;\n else if (ch === ')') depth--;\n if (depth > 0) argBlock += ch;\n i++;\n }\n if (/\\bamplitude\\s*:/.test(argBlock)) {\n result.add(varName);\n }\n }\n return result;\n}\n\nexport function analyzeFileInstrumentation(source: string): FileAnalysis {\n const hasPatch = /\\bpatch\\s*\\(\\s*\\{/.test(source);\n const hasSessionContext =\n /\\.session\\s*\\(/.test(source) ||\n /\\bsession\\.run\\s*\\(/.test(source) ||\n /\\.runAs\\s*\\(/.test(source) ||\n /\\bcreateAmplitudeAIMiddleware\\s*\\(/.test(source);\n\n const hasAmplitudeImport =\n /from\\s+['\"]@amplitude\\/ai['\"]/.test(source) ||\n /require\\s*\\(\\s*['\"]@amplitude\\/ai['\"]\\s*\\)/.test(source) ||\n /\\bAmplitudeAI\\b/.test(source);\n\n const wrappedClients = findWrappedConstructors(source);\n if (hasAmplitudeImport) {\n const wrapRe = /(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:\\w+\\.)*wrap\\s*\\(/g;\n for (const m of source.matchAll(wrapRe)) {\n const name = m[1] ?? '';\n if (name) wrappedClients.add(name);\n }\n }\n\n const callSites: CallSite[] = [];\n const lines = source.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? '';\n for (const { pattern, receiverRe, provider, api } of llmPatterns) {\n pattern.lastIndex = 0;\n if (pattern.test(line)) {\n const rm = line.match(receiverRe);\n const receiver = rm?.[1] ?? '';\n const instrumented = hasPatch || wrappedClients.has(receiver);\n callSites.push({ line: i + 1, provider, api, instrumented });\n }\n }\n }\n\n const uninstrumentedSites = callSites.filter((s) => !s.instrumented);\n let suggestions: string[];\n if (uninstrumentedSites.length > 0) {\n suggestions = [\n 'Now: instrument provider calls with wrapper/swap import (for example, `new OpenAI({ amplitude: ai, apiKey })`).',\n 'Next: add session lineage with `const session = ai.agent(...).session(...)` and wrap calls in `session.run(...)`.',\n 'Why: session context unlocks session-level scoring, enrichments, and reliable product-to-AI funnels.',\n 'Content tiers: choose `contentMode` (`full`, `metadata_only`, or `customer_enriched`) and prefer `redactPii: true` when using `full`.',\n 'Fallback: use `patch({ amplitudeAI: ai })` for migration speed, then graduate to wrapper + session context.',\n ];\n } else if (callSites.length > 0 && !hasSessionContext) {\n suggestions = [\n 'Tracking is present, but session context is missing.',\n 'Now: add `const session = ai.agent(...).session(...)` and execute LLM calls inside `session.run(...)` (or use middleware).',\n 'Why: without session lineage you lose high-value outcomes like session enrichments, scoring, and dependable session funnels.',\n 'Content tiers: set `contentMode` intentionally and keep `redactPii: true` when using `full`.',\n ];\n } else if (callSites.length > 0) {\n suggestions = [\n 'File appears instrumented with session lineage.',\n 'For privacy-by-default, set `contentMode` intentionally and enable `redactPii: true` when using `full`.',\n ];\n } else {\n suggestions = [\n 'No supported LLM call sites detected in this file.',\n 'If this file should emit AI telemetry, add wrapped provider calls and session context.',\n ];\n }\n\n return {\n total_call_sites: callSites.length,\n instrumented: callSites.length - uninstrumentedSites.length,\n uninstrumented: uninstrumentedSites.length,\n has_amplitude_import: hasAmplitudeImport,\n has_session_context: hasSessionContext,\n call_sites: callSites,\n suggestions,\n };\n}\n"],"mappings":";AAiBA,MAAM,cAAc;CAClB;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACD;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACD;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACD;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACD;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACD;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACD;EACE,SAAS;EACT,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CACF;AAED,SAAS,wBAAwB,QAA6B;CAC5D,MAAM,yBAAS,IAAI,KAAa;AAGhC,MAAK,MAAM,KAAK,OAAO,SADrB,sGACqC,EAAE;EACvC,MAAM,UAAU,EAAE,MAAM;AACxB,MAAI,CAAC,QAAS;EACd,IAAI,QAAQ;EACZ,IAAI,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG;EAC9B,IAAI,WAAW;AACf,SAAO,IAAI,OAAO,UAAU,QAAQ,GAAG;GACrC,MAAM,KAAK,OAAO;AAClB,OAAI,OAAO,IAAK;YACP,OAAO,IAAK;AACrB,OAAI,QAAQ,EAAG,aAAY;AAC3B;;AAEF,MAAI,kBAAkB,KAAK,SAAS,CAClC,QAAO,IAAI,QAAQ;;AAGvB,QAAO;;AAGT,SAAgB,2BAA2B,QAA8B;CACvE,MAAM,WAAW,oBAAoB,KAAK,OAAO;CACjD,MAAM,oBACJ,iBAAiB,KAAK,OAAO,IAC7B,sBAAsB,KAAK,OAAO,IAClC,eAAe,KAAK,OAAO,IAC3B,qCAAqC,KAAK,OAAO;CAEnD,MAAM,qBACJ,gCAAgC,KAAK,OAAO,IAC5C,6CAA6C,KAAK,OAAO,IACzD,kBAAkB,KAAK,OAAO;CAEhC,MAAM,iBAAiB,wBAAwB,OAAO;AACtD,KAAI,mBAEF,MAAK,MAAM,KAAK,OAAO,SADR,uDACwB,EAAE;EACvC,MAAM,OAAO,EAAE,MAAM;AACrB,MAAI,KAAM,gBAAe,IAAI,KAAK;;CAItC,MAAMA,YAAwB,EAAE;CAChC,MAAM,QAAQ,OAAO,MAAM,KAAK;AAChC,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM,MAAM;AACzB,OAAK,MAAM,EAAE,SAAS,YAAY,UAAU,SAAS,aAAa;AAChE,WAAQ,YAAY;AACpB,OAAI,QAAQ,KAAK,KAAK,EAAE;IAEtB,MAAM,WADK,KAAK,MAAM,WAAW,GACX,MAAM;IAC5B,MAAM,eAAe,YAAY,eAAe,IAAI,SAAS;AAC7D,cAAU,KAAK;KAAE,MAAM,IAAI;KAAG;KAAU;KAAK;KAAc,CAAC;;;;CAKlE,MAAM,sBAAsB,UAAU,QAAQ,MAAM,CAAC,EAAE,aAAa;CACpE,IAAIC;AACJ,KAAI,oBAAoB,SAAS,EAC/B,eAAc;EACZ;EACA;EACA;EACA;EACA;EACD;UACQ,UAAU,SAAS,KAAK,CAAC,kBAClC,eAAc;EACZ;EACA;EACA;EACA;EACD;UACQ,UAAU,SAAS,EAC5B,eAAc,CACZ,mDACA,0GACD;KAED,eAAc,CACZ,sDACA,yFACD;AAGH,QAAO;EACL,kBAAkB,UAAU;EAC5B,cAAc,UAAU,SAAS,oBAAoB;EACrD,gBAAgB,oBAAoB;EACpC,sBAAsB;EACtB,qBAAqB;EACrB,YAAY;EACZ;EACD"}
1
+ {"version":3,"file":"validate-file.js","names":["LLM_METHOD_CHAINS: Array<{\n chain: string[];\n provider: string;\n api: string;\n singleMethodHints?: string[];\n}>","parts: string[]","parse: ((src: string, opts: object) => AcornNode) | null","tree: AcornNode","source","callSites: CallSite[]","toolDefinitions: string[]","functionDefinitions: string[]","suggestions: string[]","tools: string[]","fns: string[]"],"sources":["../../src/mcp/validate-file.ts"],"sourcesContent":["import type { Node as AcornNode } from 'acorn';\n\nexport interface CallSite {\n line: number;\n provider: string;\n api: string;\n instrumented: boolean;\n containing_function: string | null;\n code_context: string;\n}\n\nexport interface FileAnalysis {\n total_call_sites: number;\n instrumented: number;\n uninstrumented: number;\n has_amplitude_import: boolean;\n has_session_context: boolean;\n call_sites: CallSite[];\n suggestions: string[];\n tool_definitions: string[];\n function_definitions: string[];\n}\n\n// Method chain patterns: tail of the call chain -> (provider, api)\nconst LLM_METHOD_CHAINS: Array<{\n chain: string[];\n provider: string;\n api: string;\n singleMethodHints?: string[];\n}> = [\n { chain: ['chat', 'completions', 'create'], provider: 'openai', api: 'chat.completions.create' },\n { chain: ['chat', 'completions', 'parse'], provider: 'openai', api: 'chat.completions.parse' },\n { chain: ['responses', 'create'], provider: 'openai', api: 'responses.create' },\n { chain: ['messages', 'create'], provider: 'anthropic', api: 'messages.create' },\n { chain: ['generateContent'], provider: 'gemini', api: 'generateContent', singleMethodHints: ['gemini', 'google', 'generative', 'genai'] },\n { chain: ['converse'], provider: 'bedrock', api: 'converse', singleMethodHints: ['bedrock', 'boto', 'runtime'] },\n { chain: ['chat', 'complete'], provider: 'mistral', api: 'chat.complete' },\n { chain: ['getChatCompletions'], provider: 'azure-openai', api: 'getChatCompletions', singleMethodHints: ['azure', 'openai'] },\n { chain: ['chat'], provider: 'cohere', api: 'chat', singleMethodHints: ['cohere', 'CohereClient'] },\n { chain: ['generate'], provider: 'cohere', api: 'generate', singleMethodHints: ['cohere', 'CohereClient'] },\n];\n\n// Vercel AI SDK top-level function calls (no receiver)\nconst VERCEL_AI_SDK_FUNCTIONS = new Set(['streamText', 'generateText', 'streamObject', 'generateObject']);\n\nconst JS_KEYWORDS = new Set([\n 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'try', 'catch',\n 'finally', 'return', 'throw', 'new', 'delete', 'typeof', 'void', 'in',\n 'of', 'with', 'class', 'extends', 'super', 'import', 'export', 'default',\n 'break', 'continue', 'debugger', 'yield', 'await',\n]);\n\nconst PROVIDER_CONSTRUCTOR_NAMES = new Set([\n 'OpenAI', 'Anthropic', 'Gemini', 'AzureOpenAI', 'Bedrock', 'Mistral',\n 'GoogleGenerativeAI', 'GoogleGenAI', 'CohereClient',\n]);\n\n// ---- AST node types (minimal subset matching acorn/estree) ----\n\ninterface EstreeNode {\n type: string;\n start?: number;\n end?: number;\n loc?: { start: { line: number; column: number }; end: { line: number; column: number } };\n [key: string]: unknown;\n}\n\ninterface Identifier extends EstreeNode { type: 'Identifier'; name: string }\ninterface MemberExpression extends EstreeNode {\n type: 'MemberExpression';\n object: EstreeNode;\n property: EstreeNode;\n computed: boolean;\n}\ninterface CallExpression extends EstreeNode {\n type: 'CallExpression';\n callee: EstreeNode;\n arguments: EstreeNode[];\n}\ninterface NewExpression extends EstreeNode {\n type: 'NewExpression';\n callee: EstreeNode;\n arguments: EstreeNode[];\n}\ninterface ObjectExpression extends EstreeNode {\n type: 'ObjectExpression';\n properties: EstreeNode[];\n}\ninterface Property extends EstreeNode {\n type: 'Property';\n key: EstreeNode;\n value: EstreeNode;\n kind: string;\n}\ninterface Literal extends EstreeNode {\n type: 'Literal';\n value: string | number | boolean | null;\n}\ninterface VariableDeclarator extends EstreeNode {\n type: 'VariableDeclarator';\n id: EstreeNode;\n init: EstreeNode | null;\n}\ninterface FunctionDeclaration extends EstreeNode {\n type: 'FunctionDeclaration';\n id: Identifier | null;\n async: boolean;\n}\ninterface _ArrowFunctionExpression extends EstreeNode {\n type: 'ArrowFunctionExpression';\n async: boolean;\n}\ninterface FunctionExpression extends EstreeNode {\n type: 'FunctionExpression';\n id: Identifier | null;\n async: boolean;\n}\n\n// ---- AST walking helpers ----\n\nfunction extractMethodChain(node: EstreeNode): { chain: string[]; receiver: string | null } {\n const parts: string[] = [];\n let current = node;\n while (current.type === 'MemberExpression') {\n const mem = current as MemberExpression;\n if (!mem.computed && mem.property.type === 'Identifier') {\n parts.push((mem.property as Identifier).name);\n }\n current = mem.object as EstreeNode;\n }\n parts.reverse();\n const receiver = current.type === 'Identifier' ? (current as Identifier).name : null;\n return { chain: parts, receiver };\n}\n\nfunction chainEndsWith(chain: string[], pattern: string[]): boolean {\n if (chain.length < pattern.length) return false;\n const offset = chain.length - pattern.length;\n return pattern.every((p, i) => chain[offset + i] === p);\n}\n\nfunction hasSingleMethodHint(chain: string[], receiver: string | null, hints: string[]): boolean {\n const tokens = [...chain.slice(0, -1)];\n if (receiver) tokens.push(receiver);\n return tokens.some((t) => hints.some((h) => t.toLowerCase().includes(h.toLowerCase())));\n}\n\nfunction getPropertyValue(obj: ObjectExpression, key: string): EstreeNode | null {\n for (const prop of obj.properties) {\n if (prop.type !== 'Property') continue;\n const p = prop as Property;\n if (p.key.type === 'Identifier' && (p.key as Identifier).name === key) return p.value;\n if (p.key.type === 'Literal' && (p.key as Literal).value === key) return p.value;\n }\n return null;\n}\n\nfunction hasKeywordArg(args: EstreeNode[], keyword: string): boolean {\n for (const arg of args) {\n if (arg.type === 'ObjectExpression') {\n if (getPropertyValue(arg as ObjectExpression, keyword)) return true;\n }\n }\n return false;\n}\n\nfunction extractCodeContext(sourceLines: string[], lineNum: number, radius = 4): string {\n const idx = lineNum - 1;\n const start = Math.max(0, idx - radius);\n const end = Math.min(sourceLines.length - 1, idx + radius);\n return sourceLines.slice(start, end + 1).map((l, i) => {\n const ln = start + i + 1;\n const marker = ln === lineNum ? '>>>' : ' ';\n return `${marker} ${ln}: ${l}`;\n }).join('\\n');\n}\n\ntype WalkCallback = (node: EstreeNode, ancestors: EstreeNode[]) => void;\n\nfunction walkAST(node: EstreeNode, callback: WalkCallback, ancestors: EstreeNode[] = []): void {\n callback(node, ancestors);\n const next = [...ancestors, node];\n for (const key of Object.keys(node)) {\n if (key === 'type' || key === 'start' || key === 'end' || key === 'loc') continue;\n const val = node[key];\n if (val && typeof val === 'object') {\n if (Array.isArray(val)) {\n for (const item of val) {\n if (item && typeof item === 'object' && 'type' in item) {\n walkAST(item as EstreeNode, callback, next);\n }\n }\n } else if ('type' in val) {\n walkAST(val as EstreeNode, callback, next);\n }\n }\n }\n}\n\nfunction findContainingFunctionAST(ancestors: EstreeNode[]): string | null {\n for (let i = ancestors.length - 1; i >= 0; i--) {\n const node = ancestors[i];\n if (!node) continue;\n if (node.type === 'FunctionDeclaration') {\n const fn = node as FunctionDeclaration;\n const name = fn.id?.name;\n if (name && !JS_KEYWORDS.has(name)) return name;\n }\n if (node.type === 'FunctionExpression') {\n const fn = node as FunctionExpression;\n if (fn.id?.name && !JS_KEYWORDS.has(fn.id.name)) return fn.id.name;\n }\n if (node.type === 'VariableDeclarator') {\n const decl = node as VariableDeclarator;\n if (\n decl.id.type === 'Identifier' &&\n decl.init &&\n (decl.init.type === 'ArrowFunctionExpression' || decl.init.type === 'FunctionExpression')\n ) {\n const name = (decl.id as Identifier).name;\n if (!JS_KEYWORDS.has(name)) return name;\n }\n }\n if (node.type === 'MethodDefinition' || node.type === 'PropertyDefinition') {\n const key = (node as unknown as { key: EstreeNode }).key;\n if (key?.type === 'Identifier') {\n const name = (key as Identifier).name;\n if (!JS_KEYWORDS.has(name)) return name;\n }\n }\n }\n return null;\n}\n\n// ---- AST-based analysis ----\n\nfunction analyzeWithAST(source: string, sourceLines: string[]): FileAnalysis | null {\n let parse: ((src: string, opts: object) => AcornNode) | null = null;\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const acorn = require('acorn');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const tsModule = require('acorn-typescript');\n const tsPlugin = typeof tsModule === 'function' ? tsModule : (tsModule.tsPlugin ?? tsModule.default);\n if (typeof tsPlugin !== 'function') return null;\n parse = (src: string, opts: object) => acorn.Parser.extend(tsPlugin()).parse(src, opts);\n } catch {\n return null;\n }\n\n let tree: AcornNode;\n try {\n if (!parse) return null;\n tree = parse(source, {\n sourceType: 'module',\n ecmaVersion: 'latest',\n locations: true,\n allowImportExportEverywhere: true,\n allowReturnOutsideFunction: true,\n });\n } catch {\n return null;\n }\n\n const root = tree as unknown as EstreeNode;\n\n // --- Detect Amplitude imports and session context ---\n const hasPatch = /\\bpatch\\s*\\(\\s*\\{/.test(source);\n const hasSessionContext =\n /\\.session\\s*\\(/.test(source) ||\n /\\bsession\\.run\\s*\\(/.test(source) ||\n /\\.runAs\\s*\\(/.test(source) ||\n /\\bcreateAmplitudeAIMiddleware\\s*\\(/.test(source);\n\n const hasAmplitudeImport =\n /from\\s+['\"]@amplitude\\/ai['\"]/.test(source) ||\n /require\\s*\\(\\s*['\"]@amplitude\\/ai['\"]\\s*\\)/.test(source) ||\n /\\bAmplitudeAI\\b/.test(source);\n\n // --- Detect wrapped constructors via AST ---\n const wrappedClients = new Set<string>();\n const localImportedBindings = new Set<string>();\n const localImportedFunctions = new Set<string>();\n\n const isLocalSpecifier = (spec: string): boolean =>\n spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('@/') || spec.startsWith('~/');\n\n walkAST(root, (node) => {\n // Track named/default imports from local modules (e.g. import { openai } from './amplitude')\n if (node.type === 'ImportDeclaration') {\n const decl = node as EstreeNode;\n const source = decl.source as EstreeNode | undefined;\n if (!source || source.type !== 'Literal') return;\n const specifier = (source as Literal).value;\n if (typeof specifier !== 'string' || !isLocalSpecifier(specifier)) return;\n\n const specifiers = decl.specifiers as EstreeNode[] | undefined;\n if (!specifiers) return;\n for (const s of specifiers) {\n const local = (s as EstreeNode).local as EstreeNode | undefined;\n if (local?.type === 'Identifier') {\n const name = (local as Identifier).name;\n localImportedBindings.add(name);\n localImportedFunctions.add(name);\n }\n }\n }\n\n // new Provider({ amplitude: ... })\n if (node.type === 'NewExpression') {\n const ne = node as NewExpression;\n const calleeName = ne.callee.type === 'Identifier' ? (ne.callee as Identifier).name : null;\n if (calleeName && PROVIDER_CONSTRUCTOR_NAMES.has(calleeName)) {\n if (hasKeywordArg(ne.arguments, 'amplitude')) {\n // Find the variable this is assigned to\n // (handled by VariableDeclarator check below)\n }\n }\n }\n });\n\n walkAST(root, (node) => {\n if (node.type === 'VariableDeclarator') {\n const decl = node as VariableDeclarator;\n if (decl.id.type !== 'Identifier' || !decl.init) return;\n const varName = (decl.id as Identifier).name;\n\n // new Provider({ amplitude: ... })\n if (decl.init.type === 'NewExpression') {\n const ne = decl.init as NewExpression;\n const calleeName = ne.callee.type === 'Identifier' ? (ne.callee as Identifier).name : null;\n if (calleeName && PROVIDER_CONSTRUCTOR_NAMES.has(calleeName) && hasKeywordArg(ne.arguments, 'amplitude')) {\n wrappedClients.add(varName);\n }\n }\n\n // wrap(...) calls\n if (decl.init.type === 'CallExpression') {\n const ce = decl.init as CallExpression;\n const isWrap =\n (ce.callee.type === 'Identifier' && (ce.callee as Identifier).name === 'wrap') ||\n (ce.callee.type === 'MemberExpression' &&\n !((ce.callee as MemberExpression).computed) &&\n (ce.callee as MemberExpression).property.type === 'Identifier' &&\n ((ce.callee as MemberExpression).property as Identifier).name === 'wrap');\n\n if (isWrap && hasAmplitudeImport) {\n wrappedClients.add(varName);\n }\n\n // Variables assigned from calls to locally-imported functions\n // (e.g. const openai = getOpenAI() where getOpenAI is from a local module)\n if (hasAmplitudeImport && ce.callee.type === 'Identifier') {\n const fnName = (ce.callee as Identifier).name;\n if (localImportedFunctions.has(fnName)) {\n localImportedBindings.add(varName);\n }\n }\n }\n }\n });\n\n // --- Detect LLM call sites ---\n const callSites: CallSite[] = [];\n const assistantsApiRe = /\\.beta\\.(?:threads|assistants)\\./;\n\n // When the file has amplitude imports AND session context, treat variables\n // obtained from local modules (imports or calls to imported functions) as\n // likely wrapped. This covers patterns like:\n // import { openai } from './amplitude';\n // import { getOpenAI } from './ai'; const openai = getOpenAI();\n // where the wrapped client is created in a separate bootstrap file.\n const isReceiverInstrumented = (receiver: string | null): boolean => {\n if (hasPatch) return true;\n if (receiver !== null && wrappedClients.has(receiver)) return true;\n if (hasAmplitudeImport && hasSessionContext && receiver !== null && localImportedBindings.has(receiver)) return true;\n return false;\n };\n\n walkAST(root, (node, ancestors) => {\n if (node.type !== 'CallExpression') return;\n const ce = node as CallExpression;\n const lineNum = node.loc?.start.line ?? 0;\n if (lineNum === 0) return;\n\n // Check for Vercel AI SDK top-level function calls\n if (ce.callee.type === 'Identifier') {\n const fnName = (ce.callee as Identifier).name;\n if (VERCEL_AI_SDK_FUNCTIONS.has(fnName)) {\n callSites.push({\n line: lineNum,\n provider: 'vercel-ai-sdk',\n api: fnName,\n instrumented: hasPatch,\n containing_function: findContainingFunctionAST(ancestors),\n code_context: extractCodeContext(sourceLines, lineNum),\n });\n }\n return;\n }\n\n // Check for Bedrock .send(new InvokeModelCommand(...)) / .send(new ConverseCommand(...))\n if (ce.callee.type === 'MemberExpression') {\n const mem = ce.callee as MemberExpression;\n if (\n !mem.computed &&\n mem.property.type === 'Identifier' &&\n (mem.property as Identifier).name === 'send' &&\n ce.arguments.length > 0 &&\n ce.arguments[0]?.type === 'NewExpression'\n ) {\n const newExpr = ce.arguments[0] as NewExpression;\n const cmdName = newExpr.callee.type === 'Identifier' ? (newExpr.callee as Identifier).name : null;\n if (cmdName === 'InvokeModelCommand' || cmdName === 'InvokeModelWithResponseStreamCommand') {\n const receiver = mem.object.type === 'Identifier' ? (mem.object as Identifier).name : null;\n callSites.push({\n line: lineNum,\n provider: 'bedrock',\n api: cmdName === 'InvokeModelWithResponseStreamCommand' ? 'invokeModelWithResponseStream' : 'invokeModel',\n instrumented: isReceiverInstrumented(receiver),\n containing_function: findContainingFunctionAST(ancestors),\n code_context: extractCodeContext(sourceLines, lineNum),\n });\n return;\n }\n if (cmdName === 'ConverseCommand' || cmdName === 'ConverseStreamCommand') {\n const receiver = mem.object.type === 'Identifier' ? (mem.object as Identifier).name : null;\n callSites.push({\n line: lineNum,\n provider: 'bedrock',\n api: cmdName === 'ConverseStreamCommand' ? 'converseStream' : 'converse',\n instrumented: isReceiverInstrumented(receiver),\n containing_function: findContainingFunctionAST(ancestors),\n code_context: extractCodeContext(sourceLines, lineNum),\n });\n return;\n }\n }\n }\n\n // Check method chain patterns\n if (ce.callee.type === 'MemberExpression') {\n const { chain, receiver } = extractMethodChain(ce.callee);\n\n for (const pattern of LLM_METHOD_CHAINS) {\n if (!chainEndsWith(chain, pattern.chain)) continue;\n if (pattern.chain.length === 1 && pattern.singleMethodHints) {\n if (!hasSingleMethodHint(chain, receiver, pattern.singleMethodHints)) continue;\n }\n\n let effectiveProvider = pattern.provider;\n let effectiveApi = pattern.api;\n\n // Disambiguate Anthropic messages.create from OpenAI Assistants beta.threads.messages.create\n if (pattern.provider === 'anthropic') {\n const lineText = sourceLines[lineNum - 1] ?? '';\n if (assistantsApiRe.test(lineText) || chain.some((p) => p === 'beta' || p === 'threads')) {\n effectiveProvider = 'openai-assistants';\n effectiveApi = 'beta.threads.messages.create';\n }\n }\n\n callSites.push({\n line: lineNum,\n provider: effectiveProvider,\n api: effectiveApi,\n instrumented: isReceiverInstrumented(receiver),\n containing_function: findContainingFunctionAST(ancestors),\n code_context: extractCodeContext(sourceLines, lineNum),\n });\n break;\n }\n }\n });\n\n // --- Find tool definitions: OpenAI function-calling schema shape ---\n // Match: { type: 'function', function: { name: '...' } }\n const toolDefinitions: string[] = [];\n\n walkAST(root, (node) => {\n if (node.type !== 'ObjectExpression') return;\n const obj = node as ObjectExpression;\n const typeVal = getPropertyValue(obj, 'type');\n if (!typeVal || typeVal.type !== 'Literal' || (typeVal as Literal).value !== 'function') return;\n\n const fnVal = getPropertyValue(obj, 'function');\n if (!fnVal || fnVal.type !== 'ObjectExpression') return;\n\n const nameVal = getPropertyValue(fnVal as ObjectExpression, 'name');\n if (nameVal?.type === 'Literal' && typeof (nameVal as Literal).value === 'string') {\n const name = (nameVal as Literal).value as string;\n if (!toolDefinitions.includes(name)) toolDefinitions.push(name);\n }\n });\n\n // Also match Anthropic-style: { name: '...', input_schema: { ... } }\n walkAST(root, (node) => {\n if (node.type !== 'ObjectExpression') return;\n const obj = node as ObjectExpression;\n const nameVal = getPropertyValue(obj, 'name');\n if (!nameVal || nameVal.type !== 'Literal' || typeof (nameVal as Literal).value !== 'string') return;\n const hasInputSchema = getPropertyValue(obj, 'input_schema') !== null;\n const hasParameters = getPropertyValue(obj, 'parameters') !== null;\n if (!hasInputSchema && !hasParameters) return;\n const name = (nameVal as Literal).value as string;\n if (!toolDefinitions.includes(name)) toolDefinitions.push(name);\n });\n\n // --- Find function definitions ---\n const functionDefinitions: string[] = [];\n\n walkAST(root, (node) => {\n if (node.type === 'FunctionDeclaration') {\n const fn = node as FunctionDeclaration;\n const name = fn.id?.name;\n if (name && !functionDefinitions.includes(name)) functionDefinitions.push(name);\n }\n if (node.type === 'VariableDeclarator') {\n const decl = node as VariableDeclarator;\n if (\n decl.id.type === 'Identifier' &&\n decl.init &&\n (decl.init.type === 'ArrowFunctionExpression' || decl.init.type === 'FunctionExpression')\n ) {\n const name = (decl.id as Identifier).name;\n if (!functionDefinitions.includes(name)) functionDefinitions.push(name);\n }\n }\n });\n\n // --- Build suggestions ---\n const uninstrumentedSites = callSites.filter((s) => !s.instrumented);\n let suggestions: string[];\n if (uninstrumentedSites.length > 0) {\n suggestions = [\n 'Now: instrument provider calls with wrapper/swap import (for example, `new OpenAI({ amplitude: ai, apiKey })`).',\n 'Next: add session lineage with `const session = ai.agent(...).session(...)` and wrap calls in `session.run(...)`.',\n 'Why: session context unlocks session-level scoring, enrichments, and reliable product-to-AI funnels.',\n 'Content tiers: choose `contentMode` (`full`, `metadata_only`, or `customer_enriched`) and prefer `redactPii: true` when using `full`.',\n 'Fallback: use `patch({ amplitudeAI: ai })` for migration speed, then graduate to wrapper + session context.',\n ];\n } else if (callSites.length > 0 && !hasSessionContext) {\n suggestions = [\n 'Tracking is present, but session context is missing.',\n 'Now: add `const session = ai.agent(...).session(...)` and execute LLM calls inside `session.run(...)` (or use middleware).',\n 'Why: without session lineage you lose high-value outcomes like session enrichments, scoring, and dependable session funnels.',\n 'Content tiers: set `contentMode` intentionally and keep `redactPii: true` when using `full`.',\n ];\n } else if (callSites.length > 0) {\n suggestions = [\n 'File appears instrumented with session lineage.',\n 'For privacy-by-default, set `contentMode` intentionally and enable `redactPii: true` when using `full`.',\n ];\n } else {\n suggestions = [\n 'No supported LLM call sites detected in this file.',\n 'If this file should emit AI telemetry, add wrapped provider calls and session context.',\n ];\n }\n\n return {\n total_call_sites: callSites.length,\n instrumented: callSites.length - uninstrumentedSites.length,\n uninstrumented: uninstrumentedSites.length,\n has_amplitude_import: hasAmplitudeImport,\n has_session_context: hasSessionContext,\n call_sites: callSites,\n suggestions,\n tool_definitions: toolDefinitions,\n function_definitions: functionDefinitions,\n };\n}\n\n// ---- Regex fallback (used when acorn is not available) ----\n\nconst regexLlmPatterns = [\n { pattern: /\\.chat\\.completions\\.create\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.chat\\.completions\\.create\\s*\\(/, provider: 'openai', api: 'chat.completions.create' },\n { pattern: /\\.chat\\.completions\\.parse\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.chat\\.completions\\.parse\\s*\\(/, provider: 'openai', api: 'chat.completions.parse' },\n { pattern: /\\.responses\\.create\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.responses\\.create\\s*\\(/, provider: 'openai', api: 'responses.create' },\n { pattern: /\\.messages\\.create\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.messages\\.create\\s*\\(/, provider: 'anthropic', api: 'messages.create' },\n { pattern: /\\.generateContent\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.generateContent\\s*\\(/, provider: 'gemini', api: 'generateContent' },\n { pattern: /\\.send\\s*\\(\\s*new\\s+InvokeModelCommand\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.send\\s*\\(/, provider: 'bedrock', api: 'invokeModel' },\n { pattern: /\\.send\\s*\\(\\s*new\\s+ConverseCommand\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.send\\s*\\(/, provider: 'bedrock', api: 'converse' },\n { pattern: /\\.send\\s*\\(\\s*new\\s+InvokeModelWithResponseStreamCommand\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.send\\s*\\(/, provider: 'bedrock', api: 'invokeModelWithResponseStream' },\n { pattern: /\\.send\\s*\\(\\s*new\\s+ConverseStreamCommand\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.send\\s*\\(/, provider: 'bedrock', api: 'converseStream' },\n { pattern: /\\.chat\\.complete\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.chat\\.complete\\s*\\(/, provider: 'mistral', api: 'chat.complete' },\n { pattern: /\\.getChatCompletions\\s*\\(/g, receiverRe: /(\\w+)(?:\\.\\w+)*\\.getChatCompletions\\s*\\(/, provider: 'azure-openai', api: 'getChatCompletions' },\n { pattern: /\\bstreamText\\s*\\(/g, receiverRe: null, provider: 'vercel-ai-sdk', api: 'streamText' },\n { pattern: /\\bgenerateText\\s*\\(/g, receiverRe: null, provider: 'vercel-ai-sdk', api: 'generateText' },\n { pattern: /\\bstreamObject\\s*\\(/g, receiverRe: null, provider: 'vercel-ai-sdk', api: 'streamObject' },\n { pattern: /\\bgenerateObject\\s*\\(/g, receiverRe: null, provider: 'vercel-ai-sdk', api: 'generateObject' },\n];\n\nfunction findWrappedConstructorsRegex(source: string): Set<string> {\n const result = new Set<string>();\n const declRe = /(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+(?:OpenAI|Anthropic|Gemini|AzureOpenAI|Bedrock|Mistral|GoogleGenerativeAI|GoogleGenAI|CohereClient)\\s*\\(/g;\n for (const m of source.matchAll(declRe)) {\n const varName = m[1] ?? '';\n if (!varName) continue;\n let depth = 1;\n let i = (m.index ?? 0) + m[0].length;\n let argBlock = '';\n while (i < source.length && depth > 0) {\n const ch = source[i];\n if (ch === '(') depth++;\n else if (ch === ')') depth--;\n if (depth > 0) argBlock += ch;\n i++;\n }\n if (/\\bamplitude\\s*:/.test(argBlock)) {\n result.add(varName);\n }\n }\n return result;\n}\n\nfunction findContainingFunctionRegex(lines: string[], lineIndex: number): string | null {\n for (let i = lineIndex; i >= 0; i--) {\n const line = lines[i] ?? '';\n const fnDeclMatch = line.match(/(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/);\n if (fnDeclMatch) {\n const name = fnDeclMatch[1] ?? '';\n if (name && !JS_KEYWORDS.has(name)) return name;\n }\n const arrowMatch = line.match(/(?:export\\s+)?(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?\\(/);\n if (arrowMatch) {\n const name = arrowMatch[1] ?? '';\n if (name && !JS_KEYWORDS.has(name)) return name;\n }\n }\n return null;\n}\n\nfunction findToolDefinitionsRegex(source: string): string[] {\n const tools: string[] = [];\n // OpenAI function-calling: { type: 'function', function: { name: '...' } }\n const funcDefRe = /function:\\s*\\{[^}]*name:\\s*['\"](\\w+)['\"]/g;\n for (const m of source.matchAll(funcDefRe)) {\n const name = m[1] ?? '';\n if (name && !tools.includes(name)) tools.push(name);\n }\n // Anthropic-style: { name: '...', input_schema: { ... } }\n const anthropicRe = /name:\\s*['\"](\\w+)['\"][^}]*input_schema\\s*:/g;\n for (const m of source.matchAll(anthropicRe)) {\n const name = m[1] ?? '';\n if (name && !tools.includes(name)) tools.push(name);\n }\n return tools;\n}\n\nfunction findFunctionDefinitionsRegex(source: string): string[] {\n const fns: string[] = [];\n const re = /(?:async\\s+)?function\\s+(\\w+)|(?:export\\s+)?(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?(?:\\([^)]*\\)|[^=])\\s*=>/g;\n for (const m of source.matchAll(re)) {\n const name = m[1] ?? m[2] ?? '';\n if (name && !fns.includes(name)) fns.push(name);\n }\n return fns;\n}\n\nfunction analyzeWithRegex(source: string): FileAnalysis {\n const hasPatch = /\\bpatch\\s*\\(\\s*\\{/.test(source);\n const hasSessionContext =\n /\\.session\\s*\\(/.test(source) ||\n /\\bsession\\.run\\s*\\(/.test(source) ||\n /\\.runAs\\s*\\(/.test(source) ||\n /\\bcreateAmplitudeAIMiddleware\\s*\\(/.test(source);\n\n const hasAmplitudeImport =\n /from\\s+['\"]@amplitude\\/ai['\"]/.test(source) ||\n /require\\s*\\(\\s*['\"]@amplitude\\/ai['\"]\\s*\\)/.test(source) ||\n /\\bAmplitudeAI\\b/.test(source);\n\n const wrappedClients = findWrappedConstructorsRegex(source);\n if (hasAmplitudeImport) {\n const wrapRe = /(?:const|let|var)\\s+(\\w+)\\s*=\\s*(?:\\w+\\.)*wrap\\s*\\(/g;\n for (const m of source.matchAll(wrapRe)) {\n const name = m[1] ?? '';\n if (name) wrappedClients.add(name);\n }\n }\n\n const callSites: CallSite[] = [];\n const lines = source.split('\\n');\n const assistantsApiRe = /\\.beta\\.(?:threads|assistants)\\./;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? '';\n for (const { pattern, receiverRe, provider, api } of regexLlmPatterns) {\n pattern.lastIndex = 0;\n if (pattern.test(line)) {\n let effectiveProvider = provider;\n let effectiveApi = api;\n if (provider === 'anthropic' && assistantsApiRe.test(line)) {\n effectiveProvider = 'openai-assistants';\n effectiveApi = 'beta.threads.messages.create';\n }\n const rm = receiverRe ? line.match(receiverRe) : null;\n const receiver = rm?.[1] ?? '';\n const instrumented = hasPatch || wrappedClients.has(receiver);\n callSites.push({\n line: i + 1,\n provider: effectiveProvider,\n api: effectiveApi,\n instrumented,\n containing_function: findContainingFunctionRegex(lines, i),\n code_context: extractCodeContext(lines, i + 1),\n });\n }\n }\n }\n\n const toolDefs = findToolDefinitionsRegex(source);\n const fnDefs = findFunctionDefinitionsRegex(source);\n\n const uninstrumentedSites = callSites.filter((s) => !s.instrumented);\n let suggestions: string[];\n if (uninstrumentedSites.length > 0) {\n suggestions = [\n 'Now: instrument provider calls with wrapper/swap import (for example, `new OpenAI({ amplitude: ai, apiKey })`).',\n 'Next: add session lineage with `const session = ai.agent(...).session(...)` and wrap calls in `session.run(...)`.',\n 'Why: session context unlocks session-level scoring, enrichments, and reliable product-to-AI funnels.',\n 'Content tiers: choose `contentMode` (`full`, `metadata_only`, or `customer_enriched`) and prefer `redactPii: true` when using `full`.',\n 'Fallback: use `patch({ amplitudeAI: ai })` for migration speed, then graduate to wrapper + session context.',\n ];\n } else if (callSites.length > 0 && !hasSessionContext) {\n suggestions = [\n 'Tracking is present, but session context is missing.',\n 'Now: add `const session = ai.agent(...).session(...)` and execute LLM calls inside `session.run(...)` (or use middleware).',\n 'Why: without session lineage you lose high-value outcomes like session enrichments, scoring, and dependable session funnels.',\n 'Content tiers: set `contentMode` intentionally and keep `redactPii: true` when using `full`.',\n ];\n } else if (callSites.length > 0) {\n suggestions = [\n 'File appears instrumented with session lineage.',\n 'For privacy-by-default, set `contentMode` intentionally and enable `redactPii: true` when using `full`.',\n ];\n } else {\n suggestions = [\n 'No supported LLM call sites detected in this file.',\n 'If this file should emit AI telemetry, add wrapped provider calls and session context.',\n ];\n }\n\n return {\n total_call_sites: callSites.length,\n instrumented: callSites.length - uninstrumentedSites.length,\n uninstrumented: uninstrumentedSites.length,\n has_amplitude_import: hasAmplitudeImport,\n has_session_context: hasSessionContext,\n call_sites: callSites,\n suggestions,\n tool_definitions: toolDefs,\n function_definitions: fnDefs,\n };\n}\n\n// ---- Public API ----\n\nexport function analyzeFileInstrumentation(source: string): FileAnalysis {\n const sourceLines = source.split('\\n');\n const astResult = analyzeWithAST(source, sourceLines);\n if (astResult) return astResult;\n return analyzeWithRegex(source);\n}\n"],"mappings":"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"}
@@ -36,7 +36,8 @@ function createAmplitudeAIMiddleware(options) {
36
36
  userId,
37
37
  agentId,
38
38
  env,
39
- nextTurnIdFn: () => amplitudeAI._nextTurnId(sessionId)
39
+ nextTurnIdFn: () => amplitudeAI._nextTurnId(sessionId),
40
+ amplitude: amplitudeAI.amplitude
40
41
  });
41
42
  _sessionStorage.run(ctx, () => {
42
43
  res.on("finish", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * HTTP middleware for automatic session tracking in Express/Koa/Hono apps.\n *\n * Port of the Python ASGI middleware (AmplitudeAIMiddleware).\n * For Express-like frameworks, use createAmplitudeAIMiddleware().\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AmplitudeAI } from './client.js';\nimport { _sessionStorage, SessionContext } from './context.js';\nimport { getLogger } from './utils/logger.js';\n\nexport interface MiddlewareOptions {\n amplitudeAI: AmplitudeAI;\n userIdResolver: (req: unknown) => string | null;\n sessionIdResolver?: (req: unknown) => string;\n agentId?: string | null;\n env?: string | null;\n trackSessionEvents?: boolean;\n flushOnResponse?: boolean;\n}\n\ninterface ExpressLikeRequest {\n headers: Record<string, string | string[] | undefined>;\n}\n\ninterface ExpressLikeResponse {\n on: (event: string, callback: () => void) => void;\n}\n\n/**\n * Creates Express-compatible middleware.\n *\n * Usage:\n * app.use(createAmplitudeAIMiddleware({\n * amplitudeAI: ai,\n * userIdResolver: (req) => req.headers['x-user-id'],\n * }));\n */\nexport function createAmplitudeAIMiddleware(options: MiddlewareOptions) {\n const {\n amplitudeAI,\n userIdResolver,\n sessionIdResolver = () => randomUUID(),\n agentId = null,\n env = null,\n trackSessionEvents = true,\n flushOnResponse = true,\n } = options;\n const logger = getLogger(amplitudeAI.amplitude);\n\n return (\n req: ExpressLikeRequest,\n res: ExpressLikeResponse,\n next: () => void,\n ): void => {\n const userId = userIdResolver(req);\n const sessionId = sessionIdResolver(req);\n\n let traceId = req.headers['x-trace-id'] as string | undefined;\n if (!traceId && req.headers.traceparent) {\n const parts = String(req.headers.traceparent).split('-');\n traceId = parts.length >= 2 ? parts[1] : undefined;\n }\n if (!traceId) traceId = randomUUID();\n\n const ctx = new SessionContext({\n sessionId,\n traceId,\n userId,\n agentId,\n env,\n nextTurnIdFn: () => amplitudeAI._nextTurnId(sessionId),\n });\n\n _sessionStorage.run(ctx, () => {\n res.on('finish', () => {\n if (trackSessionEvents && userId != null) {\n try {\n amplitudeAI.trackSessionEnd({\n userId,\n sessionId,\n traceId: traceId ?? undefined,\n env,\n agentId,\n });\n } catch (error) {\n logger.warn(\n `Failed to track session end in middleware: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n if (flushOnResponse) {\n try {\n amplitudeAI.flush();\n } catch (error) {\n logger.warn(\n `Failed to flush events in middleware: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n });\n\n next();\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,4BAA4B,SAA4B;CACtE,MAAM,EACJ,aACA,gBACA,0BAA0B,YAAY,EACtC,UAAU,MACV,MAAM,MACN,qBAAqB,MACrB,kBAAkB,SAChB;CACJ,MAAM,SAAS,UAAU,YAAY,UAAU;AAE/C,SACE,KACA,KACA,SACS;EACT,MAAM,SAAS,eAAe,IAAI;EAClC,MAAM,YAAY,kBAAkB,IAAI;EAExC,IAAI,UAAU,IAAI,QAAQ;AAC1B,MAAI,CAAC,WAAW,IAAI,QAAQ,aAAa;GACvC,MAAM,QAAQ,OAAO,IAAI,QAAQ,YAAY,CAAC,MAAM,IAAI;AACxD,aAAU,MAAM,UAAU,IAAI,MAAM,KAAK;;AAE3C,MAAI,CAAC,QAAS,WAAU,YAAY;EAEpC,MAAM,MAAM,IAAI,eAAe;GAC7B;GACA;GACA;GACA;GACA;GACA,oBAAoB,YAAY,YAAY,UAAU;GACvD,CAAC;AAEF,kBAAgB,IAAI,WAAW;AAC7B,OAAI,GAAG,gBAAgB;AACrB,QAAI,sBAAsB,UAAU,KAClC,KAAI;AACF,iBAAY,gBAAgB;MAC1B;MACA;MACA,SAAS,WAAW;MACpB;MACA;MACD,CAAC;aACK,OAAO;AACd,YAAO,KACL,8CACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAEzD;;AAIL,QAAI,gBACF,KAAI;AACF,iBAAY,OAAO;aACZ,OAAO;AACd,YAAO,KACL,yCACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAEzD;;KAGL;AAEF,SAAM;IACN"}
1
+ {"version":3,"file":"middleware.js","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["/**\n * HTTP middleware for automatic session tracking in Express/Koa/Hono apps.\n *\n * Port of the Python ASGI middleware (AmplitudeAIMiddleware).\n * For Express-like frameworks, use createAmplitudeAIMiddleware().\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { AmplitudeAI } from './client.js';\nimport { _sessionStorage, SessionContext } from './context.js';\nimport { getLogger } from './utils/logger.js';\n\nexport interface MiddlewareOptions {\n amplitudeAI: AmplitudeAI;\n userIdResolver: (req: unknown) => string | null;\n sessionIdResolver?: (req: unknown) => string;\n agentId?: string | null;\n env?: string | null;\n trackSessionEvents?: boolean;\n flushOnResponse?: boolean;\n}\n\ninterface ExpressLikeRequest {\n headers: Record<string, string | string[] | undefined>;\n}\n\ninterface ExpressLikeResponse {\n on: (event: string, callback: () => void) => void;\n}\n\n/**\n * Creates Express-compatible middleware.\n *\n * Usage:\n * app.use(createAmplitudeAIMiddleware({\n * amplitudeAI: ai,\n * userIdResolver: (req) => req.headers['x-user-id'],\n * }));\n */\nexport function createAmplitudeAIMiddleware(options: MiddlewareOptions) {\n const {\n amplitudeAI,\n userIdResolver,\n sessionIdResolver = () => randomUUID(),\n agentId = null,\n env = null,\n trackSessionEvents = true,\n flushOnResponse = true,\n } = options;\n const logger = getLogger(amplitudeAI.amplitude);\n\n return (\n req: ExpressLikeRequest,\n res: ExpressLikeResponse,\n next: () => void,\n ): void => {\n const userId = userIdResolver(req);\n const sessionId = sessionIdResolver(req);\n\n let traceId = req.headers['x-trace-id'] as string | undefined;\n if (!traceId && req.headers.traceparent) {\n const parts = String(req.headers.traceparent).split('-');\n traceId = parts.length >= 2 ? parts[1] : undefined;\n }\n if (!traceId) traceId = randomUUID();\n\n const ctx = new SessionContext({\n sessionId,\n traceId,\n userId,\n agentId,\n env,\n nextTurnIdFn: () => amplitudeAI._nextTurnId(sessionId),\n amplitude: amplitudeAI.amplitude,\n });\n\n _sessionStorage.run(ctx, () => {\n res.on('finish', () => {\n if (trackSessionEvents && userId != null) {\n try {\n amplitudeAI.trackSessionEnd({\n userId,\n sessionId,\n traceId: traceId ?? undefined,\n env,\n agentId,\n });\n } catch (error) {\n logger.warn(\n `Failed to track session end in middleware: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n if (flushOnResponse) {\n try {\n amplitudeAI.flush();\n } catch (error) {\n logger.warn(\n `Failed to flush events in middleware: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n });\n\n next();\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,4BAA4B,SAA4B;CACtE,MAAM,EACJ,aACA,gBACA,0BAA0B,YAAY,EACtC,UAAU,MACV,MAAM,MACN,qBAAqB,MACrB,kBAAkB,SAChB;CACJ,MAAM,SAAS,UAAU,YAAY,UAAU;AAE/C,SACE,KACA,KACA,SACS;EACT,MAAM,SAAS,eAAe,IAAI;EAClC,MAAM,YAAY,kBAAkB,IAAI;EAExC,IAAI,UAAU,IAAI,QAAQ;AAC1B,MAAI,CAAC,WAAW,IAAI,QAAQ,aAAa;GACvC,MAAM,QAAQ,OAAO,IAAI,QAAQ,YAAY,CAAC,MAAM,IAAI;AACxD,aAAU,MAAM,UAAU,IAAI,MAAM,KAAK;;AAE3C,MAAI,CAAC,QAAS,WAAU,YAAY;EAEpC,MAAM,MAAM,IAAI,eAAe;GAC7B;GACA;GACA;GACA;GACA;GACA,oBAAoB,YAAY,YAAY,UAAU;GACtD,WAAW,YAAY;GACxB,CAAC;AAEF,kBAAgB,IAAI,WAAW;AAC7B,OAAI,GAAG,gBAAgB;AACrB,QAAI,sBAAsB,UAAU,KAClC,KAAI;AACF,iBAAY,gBAAgB;MAC1B;MACA;MACA,SAAS,WAAW;MACpB;MACA;MACD,CAAC;aACK,OAAO;AACd,YAAO,KACL,8CACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAEzD;;AAIL,QAAI,gBACF,KAAI;AACF,iBAAY,OAAO;aACZ,OAAO;AACd,YAAO,KACL,yCACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAEzD;;KAGL;AAEF,SAAM;IACN"}