@posthog/ai 7.2.1 → 7.3.0

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/dist/index.mjs CHANGED
@@ -2,11 +2,10 @@ import { OpenAI, AzureOpenAI } from 'openai';
2
2
  import { Buffer } from 'buffer';
3
3
  import * as uuid from 'uuid';
4
4
  import { v4 } from 'uuid';
5
- import { wrapLanguageModel } from 'ai';
6
5
  import AnthropicOriginal from '@anthropic-ai/sdk';
7
6
  import { GoogleGenAI } from '@google/genai';
8
7
 
9
- var version = "7.2.1";
8
+ var version = "7.3.0";
10
9
 
11
10
  // Type guards for safer type checking
12
11
  const isString = value => {
@@ -113,6 +112,13 @@ const formatResponseOpenAI = response => {
113
112
  });
114
113
  }
115
114
  }
115
+ // Handle audio output (gpt-4o-audio-preview)
116
+ if (choice.message.audio) {
117
+ content.push({
118
+ type: 'audio',
119
+ ...choice.message.audio
120
+ });
121
+ }
116
122
  }
117
123
  if (content.length > 0) {
118
124
  output.push({
@@ -194,6 +200,19 @@ const formatResponseGemini = response => {
194
200
  arguments: part.functionCall.args
195
201
  }
196
202
  });
203
+ } else if (part.inlineData) {
204
+ // Handle audio/media inline data
205
+ const mimeType = part.inlineData.mimeType || 'audio/pcm';
206
+ let data = part.inlineData.data;
207
+ // Handle binary data (Buffer/Uint8Array -> base64)
208
+ if (data instanceof Uint8Array || Buffer.isBuffer(data)) {
209
+ data = Buffer.from(data).toString('base64');
210
+ }
211
+ content.push({
212
+ type: 'audio',
213
+ mime_type: mimeType,
214
+ data: data
215
+ });
197
216
  }
198
217
  }
199
218
  if (content.length > 0) {
@@ -598,6 +617,13 @@ function formatOpenAIResponsesInput(input, instructions) {
598
617
 
599
618
  const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
600
619
  // ============================================
620
+ // Multimodal Feature Toggle
621
+ // ============================================
622
+ const isMultimodalEnabled = () => {
623
+ const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
624
+ return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
625
+ };
626
+ // ============================================
601
627
  // Base64 Detection Helpers
602
628
  // ============================================
603
629
  const isBase64DataUrl = str => {
@@ -622,6 +648,7 @@ const isRawBase64 = str => {
622
648
  return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
623
649
  };
624
650
  function redactBase64DataUrl(str) {
651
+ if (isMultimodalEnabled()) return str;
625
652
  if (!isString(str)) return str;
626
653
  // Check for data URL format
627
654
  if (isBase64DataUrl(str)) {
@@ -672,12 +699,21 @@ const sanitizeOpenAIImage = item => {
672
699
  }
673
700
  };
674
701
  }
702
+ // Handle audio format
703
+ if (item.type === 'audio' && 'data' in item) {
704
+ if (isMultimodalEnabled()) return item;
705
+ return {
706
+ ...item,
707
+ data: REDACTED_IMAGE_PLACEHOLDER
708
+ };
709
+ }
675
710
  return item;
676
711
  };
677
712
  const sanitizeAnthropicImage = item => {
713
+ if (isMultimodalEnabled()) return item;
678
714
  if (!isObject(item)) return item;
679
- // Handle Anthropic's image format
680
- if (item.type === 'image' && 'source' in item && isObject(item.source) && item.source.type === 'base64' && 'data' in item.source) {
715
+ // Handle Anthropic's image and document formats (same structure, different type field)
716
+ if ((item.type === 'image' || item.type === 'document') && 'source' in item && isObject(item.source) && item.source.type === 'base64' && 'data' in item.source) {
681
717
  return {
682
718
  ...item,
683
719
  source: {
@@ -689,8 +725,9 @@ const sanitizeAnthropicImage = item => {
689
725
  return item;
690
726
  };
691
727
  const sanitizeGeminiPart = part => {
728
+ if (isMultimodalEnabled()) return part;
692
729
  if (!isObject(part)) return part;
693
- // Handle Gemini's inline data format
730
+ // Handle Gemini's inline data format (images, audio, PDFs all use inlineData)
694
731
  if ('inlineData' in part && isObject(part.inlineData) && 'data' in part.inlineData) {
695
732
  return {
696
733
  ...part,
@@ -735,6 +772,7 @@ const sanitizeLangChainImage = item => {
735
772
  }
736
773
  // Anthropic style
737
774
  if (item.type === 'image' && 'source' in item && isObject(item.source) && 'data' in item.source) {
775
+ if (isMultimodalEnabled()) return item;
738
776
  return {
739
777
  ...item,
740
778
  source: {
@@ -820,6 +858,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
820
858
  try {
821
859
  const contentBlocks = [];
822
860
  let accumulatedContent = '';
861
+ let modelFromResponse;
823
862
  let usage = {
824
863
  inputTokens: 0,
825
864
  outputTokens: 0,
@@ -828,6 +867,10 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
828
867
  // Map to track in-progress tool calls
829
868
  const toolCallsInProgress = new Map();
830
869
  for await (const chunk of stream1) {
870
+ // Extract model from chunk (Chat Completions chunks have model field)
871
+ if (!modelFromResponse && chunk.model) {
872
+ modelFromResponse = chunk.model;
873
+ }
831
874
  const choice = chunk?.choices?.[0];
832
875
  const chunkWebSearchCount = calculateWebSearchCount(chunk);
833
876
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
@@ -915,7 +958,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
915
958
  await sendEventToPosthog({
916
959
  client: this.phClient,
917
960
  ...posthogParams,
918
- model: openAIParams.model,
961
+ model: openAIParams.model ?? modelFromResponse,
919
962
  provider: 'openai',
920
963
  input: sanitizeOpenAI(openAIParams.messages),
921
964
  output: formattedOutput,
@@ -968,7 +1011,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
968
1011
  await sendEventToPosthog({
969
1012
  client: this.phClient,
970
1013
  ...posthogParams,
971
- model: openAIParams.model,
1014
+ model: openAIParams.model ?? result.model,
972
1015
  provider: 'openai',
973
1016
  input: sanitizeOpenAI(openAIParams.messages),
974
1017
  output: formattedOutput,
@@ -992,7 +1035,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
992
1035
  await sendEventToPosthog({
993
1036
  client: this.phClient,
994
1037
  ...posthogParams,
995
- model: String(openAIParams.model ?? ''),
1038
+ model: openAIParams.model,
996
1039
  provider: 'openai',
997
1040
  input: sanitizeOpenAI(openAIParams.messages),
998
1041
  output: [],
@@ -1034,6 +1077,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1034
1077
  (async () => {
1035
1078
  try {
1036
1079
  let finalContent = [];
1080
+ let modelFromResponse;
1037
1081
  let usage = {
1038
1082
  inputTokens: 0,
1039
1083
  outputTokens: 0,
@@ -1041,6 +1085,10 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1041
1085
  };
1042
1086
  for await (const chunk of stream1) {
1043
1087
  if ('response' in chunk && chunk.response) {
1088
+ // Extract model from response object in chunk (for stored prompts)
1089
+ if (!modelFromResponse && chunk.response.model) {
1090
+ modelFromResponse = chunk.response.model;
1091
+ }
1044
1092
  const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
1045
1093
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1046
1094
  usage.webSearchCount = chunkWebSearchCount;
@@ -1064,8 +1112,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1064
1112
  await sendEventToPosthog({
1065
1113
  client: this.phClient,
1066
1114
  ...posthogParams,
1067
- //@ts-expect-error
1068
- model: openAIParams.model,
1115
+ model: openAIParams.model ?? modelFromResponse,
1069
1116
  provider: 'openai',
1070
1117
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1071
1118
  output: finalContent,
@@ -1087,7 +1134,6 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1087
1134
  await sendEventToPosthog({
1088
1135
  client: this.phClient,
1089
1136
  ...posthogParams,
1090
- //@ts-expect-error
1091
1137
  model: openAIParams.model,
1092
1138
  provider: 'openai',
1093
1139
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
@@ -1120,8 +1166,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1120
1166
  await sendEventToPosthog({
1121
1167
  client: this.phClient,
1122
1168
  ...posthogParams,
1123
- //@ts-expect-error
1124
- model: openAIParams.model,
1169
+ model: openAIParams.model ?? result.model,
1125
1170
  provider: 'openai',
1126
1171
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1127
1172
  output: formattedOutput,
@@ -1145,7 +1190,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1145
1190
  await sendEventToPosthog({
1146
1191
  client: this.phClient,
1147
1192
  ...posthogParams,
1148
- model: String(openAIParams.model ?? ''),
1193
+ model: openAIParams.model,
1149
1194
  provider: 'openai',
1150
1195
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1151
1196
  output: [],
@@ -1182,7 +1227,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1182
1227
  await sendEventToPosthog({
1183
1228
  client: this.phClient,
1184
1229
  ...posthogParams,
1185
- model: String(openAIParams.model ?? ''),
1230
+ model: openAIParams.model ?? result.model,
1186
1231
  provider: 'openai',
1187
1232
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1188
1233
  output: result.output,
@@ -1203,7 +1248,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1203
1248
  await sendEventToPosthog({
1204
1249
  client: this.phClient,
1205
1250
  ...posthogParams,
1206
- model: String(openAIParams.model ?? ''),
1251
+ model: openAIParams.model,
1207
1252
  provider: 'openai',
1208
1253
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1209
1254
  output: [],
@@ -1378,7 +1423,7 @@ class WrappedTranscriptions extends Transcriptions {
1378
1423
  await sendEventToPosthog({
1379
1424
  client: this.phClient,
1380
1425
  ...posthogParams,
1381
- model: String(openAIParams.model ?? ''),
1426
+ model: openAIParams.model,
1382
1427
  provider: 'openai',
1383
1428
  input: openAIParams.prompt,
1384
1429
  output: result.text,
@@ -1398,7 +1443,7 @@ class WrappedTranscriptions extends Transcriptions {
1398
1443
  await sendEventToPosthog({
1399
1444
  client: this.phClient,
1400
1445
  ...posthogParams,
1401
- model: String(openAIParams.model ?? ''),
1446
+ model: openAIParams.model,
1402
1447
  provider: 'openai',
1403
1448
  input: openAIParams.prompt,
1404
1449
  output: [],
@@ -1460,6 +1505,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1460
1505
  try {
1461
1506
  const contentBlocks = [];
1462
1507
  let accumulatedContent = '';
1508
+ let modelFromResponse;
1463
1509
  let usage = {
1464
1510
  inputTokens: 0,
1465
1511
  outputTokens: 0
@@ -1467,6 +1513,10 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1467
1513
  // Map to track in-progress tool calls
1468
1514
  const toolCallsInProgress = new Map();
1469
1515
  for await (const chunk of stream1) {
1516
+ // Extract model from response if not in params
1517
+ if (!modelFromResponse && chunk.model) {
1518
+ modelFromResponse = chunk.model;
1519
+ }
1470
1520
  const choice = chunk?.choices?.[0];
1471
1521
  // Handle text content
1472
1522
  const deltaContent = choice?.delta?.content;
@@ -1548,7 +1598,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1548
1598
  await sendEventToPosthog({
1549
1599
  client: this.phClient,
1550
1600
  ...posthogParams,
1551
- model: openAIParams.model,
1601
+ model: openAIParams.model ?? modelFromResponse,
1552
1602
  provider: 'azure',
1553
1603
  input: sanitizeOpenAI(openAIParams.messages),
1554
1604
  output: formattedOutput,
@@ -1592,7 +1642,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1592
1642
  await sendEventToPosthog({
1593
1643
  client: this.phClient,
1594
1644
  ...posthogParams,
1595
- model: openAIParams.model,
1645
+ model: openAIParams.model ?? result.model,
1596
1646
  provider: 'azure',
1597
1647
  input: openAIParams.messages,
1598
1648
  output: formatResponseOpenAI(result),
@@ -1656,11 +1706,18 @@ class WrappedResponses extends AzureOpenAI.Responses {
1656
1706
  (async () => {
1657
1707
  try {
1658
1708
  let finalContent = [];
1709
+ let modelFromResponse;
1659
1710
  let usage = {
1660
1711
  inputTokens: 0,
1661
1712
  outputTokens: 0
1662
1713
  };
1663
1714
  for await (const chunk of stream1) {
1715
+ if ('response' in chunk && chunk.response) {
1716
+ // Extract model from response if not in params (for stored prompts)
1717
+ if (!modelFromResponse && chunk.response.model) {
1718
+ modelFromResponse = chunk.response.model;
1719
+ }
1720
+ }
1664
1721
  if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1665
1722
  finalContent = chunk.response.output;
1666
1723
  }
@@ -1677,10 +1734,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1677
1734
  await sendEventToPosthog({
1678
1735
  client: this.phClient,
1679
1736
  ...posthogParams,
1680
- //@ts-expect-error
1681
- model: openAIParams.model,
1737
+ model: openAIParams.model ?? modelFromResponse,
1682
1738
  provider: 'azure',
1683
- input: openAIParams.input,
1739
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1684
1740
  output: finalContent,
1685
1741
  latency,
1686
1742
  baseURL: this.baseURL,
@@ -1693,10 +1749,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1693
1749
  await sendEventToPosthog({
1694
1750
  client: this.phClient,
1695
1751
  ...posthogParams,
1696
- //@ts-expect-error
1697
1752
  model: openAIParams.model,
1698
1753
  provider: 'azure',
1699
- input: openAIParams.input,
1754
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1700
1755
  output: [],
1701
1756
  latency: 0,
1702
1757
  baseURL: this.baseURL,
@@ -1722,10 +1777,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1722
1777
  await sendEventToPosthog({
1723
1778
  client: this.phClient,
1724
1779
  ...posthogParams,
1725
- //@ts-expect-error
1726
- model: openAIParams.model,
1780
+ model: openAIParams.model ?? result.model,
1727
1781
  provider: 'azure',
1728
- input: openAIParams.input,
1782
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1729
1783
  output: result.output,
1730
1784
  latency,
1731
1785
  baseURL: this.baseURL,
@@ -1745,10 +1799,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1745
1799
  await sendEventToPosthog({
1746
1800
  client: this.phClient,
1747
1801
  ...posthogParams,
1748
- //@ts-expect-error
1749
1802
  model: openAIParams.model,
1750
1803
  provider: 'azure',
1751
- input: openAIParams.input,
1804
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1752
1805
  output: [],
1753
1806
  latency: 0,
1754
1807
  baseURL: this.baseURL,
@@ -1778,9 +1831,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1778
1831
  await sendEventToPosthog({
1779
1832
  client: this.phClient,
1780
1833
  ...posthogParams,
1781
- model: String(openAIParams.model ?? ''),
1834
+ model: openAIParams.model ?? result.model,
1782
1835
  provider: 'azure',
1783
- input: openAIParams.input,
1836
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1784
1837
  output: result.output,
1785
1838
  latency,
1786
1839
  baseURL: this.baseURL,
@@ -1798,9 +1851,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1798
1851
  await sendEventToPosthog({
1799
1852
  client: this.phClient,
1800
1853
  ...posthogParams,
1801
- model: String(openAIParams.model ?? ''),
1854
+ model: openAIParams.model,
1802
1855
  provider: 'azure',
1803
- input: openAIParams.input,
1856
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1804
1857
  output: [],
1805
1858
  latency: 0,
1806
1859
  baseURL: this.baseURL,
@@ -2073,67 +2126,117 @@ const extractProvider = model => {
2073
2126
  const providerName = provider.split('.')[0];
2074
2127
  return providerName;
2075
2128
  };
2076
- const createInstrumentationMiddleware = (phClient, model, options) => {
2077
- const middleware = {
2078
- wrapGenerate: async ({
2079
- doGenerate,
2080
- params
2081
- }) => {
2129
+ // Extract web search count from provider metadata (works for both V2 and V3)
2130
+ const extractWebSearchCount = (providerMetadata, usage) => {
2131
+ // Try Anthropic-specific extraction
2132
+ if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
2133
+ const serverToolUse = providerMetadata.anthropic.server_tool_use;
2134
+ if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
2135
+ return serverToolUse.web_search_requests;
2136
+ }
2137
+ }
2138
+ // Fall back to generic calculation
2139
+ return calculateWebSearchCount({
2140
+ usage,
2141
+ providerMetadata
2142
+ });
2143
+ };
2144
+ // Extract additional token values from provider metadata
2145
+ const extractAdditionalTokenValues = providerMetadata => {
2146
+ if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic) {
2147
+ return {
2148
+ cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
2149
+ };
2150
+ }
2151
+ return {};
2152
+ };
2153
+ // Helper to extract numeric token value from V2 (number) or V3 (object with .total) usage formats
2154
+ const extractTokenCount = value => {
2155
+ if (typeof value === 'number') {
2156
+ return value;
2157
+ }
2158
+ if (value && typeof value === 'object' && 'total' in value && typeof value.total === 'number') {
2159
+ return value.total;
2160
+ }
2161
+ return undefined;
2162
+ };
2163
+ // Helper to extract reasoning tokens from V2 (usage.reasoningTokens) or V3 (usage.outputTokens.reasoning)
2164
+ const extractReasoningTokens = usage => {
2165
+ // V2 style: top-level reasoningTokens
2166
+ if ('reasoningTokens' in usage) {
2167
+ return usage.reasoningTokens;
2168
+ }
2169
+ // V3 style: nested in outputTokens.reasoning
2170
+ if ('outputTokens' in usage && usage.outputTokens && typeof usage.outputTokens === 'object' && 'reasoning' in usage.outputTokens) {
2171
+ return usage.outputTokens.reasoning;
2172
+ }
2173
+ return undefined;
2174
+ };
2175
+ // Helper to extract cached input tokens from V2 (usage.cachedInputTokens) or V3 (usage.inputTokens.cacheRead)
2176
+ const extractCacheReadTokens = usage => {
2177
+ // V2 style: top-level cachedInputTokens
2178
+ if ('cachedInputTokens' in usage) {
2179
+ return usage.cachedInputTokens;
2180
+ }
2181
+ // V3 style: nested in inputTokens.cacheRead
2182
+ if ('inputTokens' in usage && usage.inputTokens && typeof usage.inputTokens === 'object' && 'cacheRead' in usage.inputTokens) {
2183
+ return usage.inputTokens.cacheRead;
2184
+ }
2185
+ return undefined;
2186
+ };
2187
+ /**
2188
+ * Wraps a Vercel AI SDK language model (V2 or V3) with PostHog tracing.
2189
+ * Automatically detects the model version and applies appropriate instrumentation.
2190
+ */
2191
+ const wrapVercelLanguageModel = (model, phClient, options) => {
2192
+ const traceId = options.posthogTraceId ?? v4();
2193
+ const mergedOptions = {
2194
+ ...options,
2195
+ posthogTraceId: traceId,
2196
+ posthogDistinctId: options.posthogDistinctId,
2197
+ posthogProperties: {
2198
+ ...options.posthogProperties,
2199
+ $ai_framework: 'vercel',
2200
+ $ai_framework_version: model.specificationVersion === 'v3' ? '6' : '5'
2201
+ }
2202
+ };
2203
+ // Create wrapped model that preserves the original type
2204
+ const wrappedModel = {
2205
+ ...model,
2206
+ doGenerate: async params => {
2082
2207
  const startTime = Date.now();
2083
2208
  const mergedParams = {
2084
- ...options,
2085
- ...mapVercelParams(params),
2086
- posthogProperties: {
2087
- ...options.posthogProperties,
2088
- $ai_framework: 'vercel'
2089
- }
2209
+ ...mergedOptions,
2210
+ ...mapVercelParams(params)
2090
2211
  };
2091
2212
  const availableTools = extractAvailableToolCalls('vercel', params);
2092
2213
  try {
2093
- const result = await doGenerate();
2094
- const modelId = options.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
2095
- const provider = options.posthogProviderOverride ?? extractProvider(model);
2214
+ const result = await model.doGenerate(params);
2215
+ const modelId = mergedOptions.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
2216
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
2096
2217
  const baseURL = ''; // cannot currently get baseURL from vercel
2097
2218
  const content = mapVercelOutput(result.content);
2098
2219
  const latency = (Date.now() - startTime) / 1000;
2099
2220
  const providerMetadata = result.providerMetadata;
2100
- const additionalTokenValues = {
2101
- ...(providerMetadata?.anthropic ? {
2102
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
2103
- } : {})
2104
- };
2105
- // Calculate web search count based on provider
2106
- let webSearchCount = 0;
2107
- if (providerMetadata?.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
2108
- // Anthropic-specific extraction
2109
- const serverToolUse = providerMetadata.anthropic.server_tool_use;
2110
- if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
2111
- webSearchCount = serverToolUse.web_search_requests;
2112
- }
2113
- } else {
2114
- // For other providers through Vercel, pass available metadata to helper
2115
- // Note: Vercel abstracts provider responses, so we may not have access to
2116
- // raw citations/annotations unless Vercel exposes them in usage/metadata
2117
- webSearchCount = calculateWebSearchCount({
2118
- usage: result.usage,
2119
- providerMetadata: providerMetadata
2120
- });
2121
- }
2221
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
2222
+ const webSearchCount = extractWebSearchCount(providerMetadata, result.usage);
2223
+ // V2 usage has simple numbers, V3 has objects with .total - normalize both
2224
+ const usageObj = result.usage;
2122
2225
  const usage = {
2123
- inputTokens: result.usage.inputTokens,
2124
- outputTokens: result.usage.outputTokens,
2125
- reasoningTokens: result.usage.reasoningTokens,
2126
- cacheReadInputTokens: result.usage.cachedInputTokens,
2226
+ inputTokens: extractTokenCount(result.usage.inputTokens),
2227
+ outputTokens: extractTokenCount(result.usage.outputTokens),
2228
+ reasoningTokens: extractReasoningTokens(usageObj),
2229
+ cacheReadInputTokens: extractCacheReadTokens(usageObj),
2127
2230
  webSearchCount,
2128
2231
  ...additionalTokenValues
2129
2232
  };
2130
2233
  await sendEventToPosthog({
2131
2234
  client: phClient,
2132
- distinctId: options.posthogDistinctId,
2133
- traceId: options.posthogTraceId ?? v4(),
2235
+ distinctId: mergedOptions.posthogDistinctId,
2236
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2134
2237
  model: modelId,
2135
2238
  provider: provider,
2136
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2239
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2137
2240
  output: content,
2138
2241
  latency,
2139
2242
  baseURL,
@@ -2141,18 +2244,18 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2141
2244
  httpStatus: 200,
2142
2245
  usage,
2143
2246
  tools: availableTools,
2144
- captureImmediate: options.posthogCaptureImmediate
2247
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2145
2248
  });
2146
2249
  return result;
2147
2250
  } catch (error) {
2148
2251
  const modelId = model.modelId;
2149
2252
  await sendEventToPosthog({
2150
2253
  client: phClient,
2151
- distinctId: options.posthogDistinctId,
2152
- traceId: options.posthogTraceId ?? v4(),
2254
+ distinctId: mergedOptions.posthogDistinctId,
2255
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2153
2256
  model: modelId,
2154
2257
  provider: model.provider,
2155
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2258
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2156
2259
  output: [],
2157
2260
  latency: 0,
2158
2261
  baseURL: '',
@@ -2165,30 +2268,23 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2165
2268
  isError: true,
2166
2269
  error: truncate(JSON.stringify(error)),
2167
2270
  tools: availableTools,
2168
- captureImmediate: options.posthogCaptureImmediate
2271
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2169
2272
  });
2170
2273
  throw error;
2171
2274
  }
2172
2275
  },
2173
- wrapStream: async ({
2174
- doStream,
2175
- params
2176
- }) => {
2276
+ doStream: async params => {
2177
2277
  const startTime = Date.now();
2178
2278
  let generatedText = '';
2179
2279
  let reasoningText = '';
2180
2280
  let usage = {};
2181
2281
  let providerMetadata = undefined;
2182
2282
  const mergedParams = {
2183
- ...options,
2184
- ...mapVercelParams(params),
2185
- posthogProperties: {
2186
- ...options.posthogProperties,
2187
- $ai_framework: 'vercel'
2188
- }
2283
+ ...mergedOptions,
2284
+ ...mapVercelParams(params)
2189
2285
  };
2190
- const modelId = options.posthogModelOverride ?? model.modelId;
2191
- const provider = options.posthogProviderOverride ?? extractProvider(model);
2286
+ const modelId = mergedOptions.posthogModelOverride ?? model.modelId;
2287
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
2192
2288
  const availableTools = extractAvailableToolCalls('vercel', params);
2193
2289
  const baseURL = ''; // cannot currently get baseURL from vercel
2194
2290
  // Map to track in-progress tool calls
@@ -2197,15 +2293,15 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2197
2293
  const {
2198
2294
  stream,
2199
2295
  ...rest
2200
- } = await doStream();
2296
+ } = await model.doStream(params);
2201
2297
  const transformStream = new TransformStream({
2202
2298
  transform(chunk, controller) {
2203
- // Handle new v5 streaming patterns
2299
+ // Handle streaming patterns - compatible with both V2 and V3
2204
2300
  if (chunk.type === 'text-delta') {
2205
2301
  generatedText += chunk.delta;
2206
2302
  }
2207
2303
  if (chunk.type === 'reasoning-delta') {
2208
- reasoningText += chunk.delta; // New in v5
2304
+ reasoningText += chunk.delta;
2209
2305
  }
2210
2306
  // Handle tool call chunks
2211
2307
  if (chunk.type === 'tool-input-start') {
@@ -2225,7 +2321,6 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2225
2321
  }
2226
2322
  if (chunk.type === 'tool-input-end') {
2227
2323
  // Tool call is complete, keep it in the map for final processing
2228
- // Nothing specific to do here, the tool call is already complete
2229
2324
  }
2230
2325
  if (chunk.type === 'tool-call') {
2231
2326
  // Direct tool call chunk (complete tool call)
@@ -2237,14 +2332,13 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2237
2332
  }
2238
2333
  if (chunk.type === 'finish') {
2239
2334
  providerMetadata = chunk.providerMetadata;
2240
- const additionalTokenValues = providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic ? {
2241
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
2242
- } : {};
2335
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
2336
+ const chunkUsage = chunk.usage || {};
2243
2337
  usage = {
2244
- inputTokens: chunk.usage?.inputTokens,
2245
- outputTokens: chunk.usage?.outputTokens,
2246
- reasoningTokens: chunk.usage?.reasoningTokens,
2247
- cacheReadInputTokens: chunk.usage?.cachedInputTokens,
2338
+ inputTokens: extractTokenCount(chunk.usage?.inputTokens),
2339
+ outputTokens: extractTokenCount(chunk.usage?.outputTokens),
2340
+ reasoningTokens: extractReasoningTokens(chunkUsage),
2341
+ cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
2248
2342
  ...additionalTokenValues
2249
2343
  };
2250
2344
  }
@@ -2284,23 +2378,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2284
2378
  role: 'assistant',
2285
2379
  content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
2286
2380
  }] : [];
2287
- // Calculate web search count based on provider
2288
- let webSearchCount = 0;
2289
- if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
2290
- // Anthropic-specific extraction
2291
- const serverToolUse = providerMetadata.anthropic.server_tool_use;
2292
- if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
2293
- webSearchCount = serverToolUse.web_search_requests;
2294
- }
2295
- } else {
2296
- // For other providers through Vercel, pass available metadata to helper
2297
- // Note: Vercel abstracts provider responses, so we may not have access to
2298
- // raw citations/annotations unless Vercel exposes them in usage/metadata
2299
- webSearchCount = calculateWebSearchCount({
2300
- usage: usage,
2301
- providerMetadata: providerMetadata
2302
- });
2303
- }
2381
+ const webSearchCount = extractWebSearchCount(providerMetadata, usage);
2304
2382
  // Update usage with web search count
2305
2383
  const finalUsage = {
2306
2384
  ...usage,
@@ -2308,11 +2386,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2308
2386
  };
2309
2387
  await sendEventToPosthog({
2310
2388
  client: phClient,
2311
- distinctId: options.posthogDistinctId,
2312
- traceId: options.posthogTraceId ?? v4(),
2389
+ distinctId: mergedOptions.posthogDistinctId,
2390
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2313
2391
  model: modelId,
2314
2392
  provider: provider,
2315
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2393
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2316
2394
  output: output,
2317
2395
  latency,
2318
2396
  baseURL,
@@ -2320,7 +2398,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2320
2398
  httpStatus: 200,
2321
2399
  usage: finalUsage,
2322
2400
  tools: availableTools,
2323
- captureImmediate: options.posthogCaptureImmediate
2401
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2324
2402
  });
2325
2403
  }
2326
2404
  });
@@ -2331,11 +2409,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2331
2409
  } catch (error) {
2332
2410
  await sendEventToPosthog({
2333
2411
  client: phClient,
2334
- distinctId: options.posthogDistinctId,
2335
- traceId: options.posthogTraceId ?? v4(),
2412
+ distinctId: mergedOptions.posthogDistinctId,
2413
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2336
2414
  model: modelId,
2337
2415
  provider: provider,
2338
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2416
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2339
2417
  output: [],
2340
2418
  latency: 0,
2341
2419
  baseURL: '',
@@ -2348,25 +2426,12 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2348
2426
  isError: true,
2349
2427
  error: truncate(JSON.stringify(error)),
2350
2428
  tools: availableTools,
2351
- captureImmediate: options.posthogCaptureImmediate
2429
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2352
2430
  });
2353
2431
  throw error;
2354
2432
  }
2355
2433
  }
2356
2434
  };
2357
- return middleware;
2358
- };
2359
- const wrapVercelLanguageModel = (model, phClient, options) => {
2360
- const traceId = options.posthogTraceId ?? v4();
2361
- const middleware = createInstrumentationMiddleware(phClient, model, {
2362
- ...options,
2363
- posthogTraceId: traceId,
2364
- posthogDistinctId: options.posthogDistinctId
2365
- });
2366
- const wrappedModel = wrapLanguageModel({
2367
- model,
2368
- middleware
2369
- });
2370
2435
  return wrappedModel;
2371
2436
  };
2372
2437
 
@@ -2790,6 +2855,39 @@ class WrappedModels {
2790
2855
  throw error;
2791
2856
  }
2792
2857
  }
2858
+ formatPartsAsContentBlocks(parts) {
2859
+ const blocks = [];
2860
+ for (const part of parts) {
2861
+ // Handle dict/object with text field
2862
+ if (part && typeof part === 'object' && 'text' in part && part.text) {
2863
+ blocks.push({
2864
+ type: 'text',
2865
+ text: String(part.text)
2866
+ });
2867
+ }
2868
+ // Handle string parts
2869
+ else if (typeof part === 'string') {
2870
+ blocks.push({
2871
+ type: 'text',
2872
+ text: part
2873
+ });
2874
+ }
2875
+ // Handle inlineData (images, audio, PDFs)
2876
+ else if (part && typeof part === 'object' && 'inlineData' in part) {
2877
+ const inlineData = part.inlineData;
2878
+ const mimeType = inlineData.mimeType || inlineData.mime_type || '';
2879
+ const contentType = mimeType.startsWith('image/') ? 'image' : 'document';
2880
+ blocks.push({
2881
+ type: contentType,
2882
+ inline_data: {
2883
+ data: inlineData.data,
2884
+ mime_type: mimeType
2885
+ }
2886
+ });
2887
+ }
2888
+ }
2889
+ return blocks;
2890
+ }
2793
2891
  formatInput(contents) {
2794
2892
  if (typeof contents === 'string') {
2795
2893
  return [{
@@ -2814,20 +2912,24 @@ class WrappedModels {
2814
2912
  };
2815
2913
  }
2816
2914
  if ('content' in obj && obj.content) {
2915
+ // If content is a list, format it as content blocks
2916
+ if (Array.isArray(obj.content)) {
2917
+ const contentBlocks = this.formatPartsAsContentBlocks(obj.content);
2918
+ return {
2919
+ role: isString(obj.role) ? obj.role : 'user',
2920
+ content: contentBlocks
2921
+ };
2922
+ }
2817
2923
  return {
2818
2924
  role: isString(obj.role) ? obj.role : 'user',
2819
2925
  content: obj.content
2820
2926
  };
2821
2927
  }
2822
2928
  if ('parts' in obj && Array.isArray(obj.parts)) {
2929
+ const contentBlocks = this.formatPartsAsContentBlocks(obj.parts);
2823
2930
  return {
2824
2931
  role: isString(obj.role) ? obj.role : 'user',
2825
- content: obj.parts.map(part => {
2826
- if (part && typeof part === 'object' && 'text' in part) {
2827
- return part.text;
2828
- }
2829
- return part;
2830
- })
2932
+ content: contentBlocks
2831
2933
  };
2832
2934
  }
2833
2935
  }
@@ -3405,7 +3507,7 @@ var BaseCallbackHandler = class extends BaseCallbackHandlerMethodsClass {
3405
3507
  }
3406
3508
  static fromMethods(methods) {
3407
3509
  class Handler extends BaseCallbackHandler {
3408
- name = uuid.v4();
3510
+ name = uuid.v7();
3409
3511
  constructor() {
3410
3512
  super();
3411
3513
  Object.assign(this, methods);