@posthog/ai 7.2.2 → 7.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.
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.2";
8
+ var version = "7.3.1";
10
9
 
11
10
  // Type guards for safer type checking
12
11
  const isString = value => {
@@ -859,6 +858,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
859
858
  try {
860
859
  const contentBlocks = [];
861
860
  let accumulatedContent = '';
861
+ let modelFromResponse;
862
862
  let usage = {
863
863
  inputTokens: 0,
864
864
  outputTokens: 0,
@@ -867,6 +867,10 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
867
867
  // Map to track in-progress tool calls
868
868
  const toolCallsInProgress = new Map();
869
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
+ }
870
874
  const choice = chunk?.choices?.[0];
871
875
  const chunkWebSearchCount = calculateWebSearchCount(chunk);
872
876
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
@@ -954,7 +958,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
954
958
  await sendEventToPosthog({
955
959
  client: this.phClient,
956
960
  ...posthogParams,
957
- model: openAIParams.model,
961
+ model: openAIParams.model ?? modelFromResponse,
958
962
  provider: 'openai',
959
963
  input: sanitizeOpenAI(openAIParams.messages),
960
964
  output: formattedOutput,
@@ -1007,7 +1011,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1007
1011
  await sendEventToPosthog({
1008
1012
  client: this.phClient,
1009
1013
  ...posthogParams,
1010
- model: openAIParams.model,
1014
+ model: openAIParams.model ?? result.model,
1011
1015
  provider: 'openai',
1012
1016
  input: sanitizeOpenAI(openAIParams.messages),
1013
1017
  output: formattedOutput,
@@ -1031,7 +1035,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
1031
1035
  await sendEventToPosthog({
1032
1036
  client: this.phClient,
1033
1037
  ...posthogParams,
1034
- model: String(openAIParams.model ?? ''),
1038
+ model: openAIParams.model,
1035
1039
  provider: 'openai',
1036
1040
  input: sanitizeOpenAI(openAIParams.messages),
1037
1041
  output: [],
@@ -1073,6 +1077,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1073
1077
  (async () => {
1074
1078
  try {
1075
1079
  let finalContent = [];
1080
+ let modelFromResponse;
1076
1081
  let usage = {
1077
1082
  inputTokens: 0,
1078
1083
  outputTokens: 0,
@@ -1080,6 +1085,10 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1080
1085
  };
1081
1086
  for await (const chunk of stream1) {
1082
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
+ }
1083
1092
  const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
1084
1093
  if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
1085
1094
  usage.webSearchCount = chunkWebSearchCount;
@@ -1103,8 +1112,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1103
1112
  await sendEventToPosthog({
1104
1113
  client: this.phClient,
1105
1114
  ...posthogParams,
1106
- //@ts-expect-error
1107
- model: openAIParams.model,
1115
+ model: openAIParams.model ?? modelFromResponse,
1108
1116
  provider: 'openai',
1109
1117
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1110
1118
  output: finalContent,
@@ -1126,7 +1134,6 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1126
1134
  await sendEventToPosthog({
1127
1135
  client: this.phClient,
1128
1136
  ...posthogParams,
1129
- //@ts-expect-error
1130
1137
  model: openAIParams.model,
1131
1138
  provider: 'openai',
1132
1139
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
@@ -1159,8 +1166,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1159
1166
  await sendEventToPosthog({
1160
1167
  client: this.phClient,
1161
1168
  ...posthogParams,
1162
- //@ts-expect-error
1163
- model: openAIParams.model,
1169
+ model: openAIParams.model ?? result.model,
1164
1170
  provider: 'openai',
1165
1171
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1166
1172
  output: formattedOutput,
@@ -1184,7 +1190,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1184
1190
  await sendEventToPosthog({
1185
1191
  client: this.phClient,
1186
1192
  ...posthogParams,
1187
- model: String(openAIParams.model ?? ''),
1193
+ model: openAIParams.model,
1188
1194
  provider: 'openai',
1189
1195
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1190
1196
  output: [],
@@ -1221,7 +1227,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1221
1227
  await sendEventToPosthog({
1222
1228
  client: this.phClient,
1223
1229
  ...posthogParams,
1224
- model: String(openAIParams.model ?? ''),
1230
+ model: openAIParams.model ?? result.model,
1225
1231
  provider: 'openai',
1226
1232
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1227
1233
  output: result.output,
@@ -1242,7 +1248,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
1242
1248
  await sendEventToPosthog({
1243
1249
  client: this.phClient,
1244
1250
  ...posthogParams,
1245
- model: String(openAIParams.model ?? ''),
1251
+ model: openAIParams.model,
1246
1252
  provider: 'openai',
1247
1253
  input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1248
1254
  output: [],
@@ -1417,7 +1423,7 @@ class WrappedTranscriptions extends Transcriptions {
1417
1423
  await sendEventToPosthog({
1418
1424
  client: this.phClient,
1419
1425
  ...posthogParams,
1420
- model: String(openAIParams.model ?? ''),
1426
+ model: openAIParams.model,
1421
1427
  provider: 'openai',
1422
1428
  input: openAIParams.prompt,
1423
1429
  output: result.text,
@@ -1437,7 +1443,7 @@ class WrappedTranscriptions extends Transcriptions {
1437
1443
  await sendEventToPosthog({
1438
1444
  client: this.phClient,
1439
1445
  ...posthogParams,
1440
- model: String(openAIParams.model ?? ''),
1446
+ model: openAIParams.model,
1441
1447
  provider: 'openai',
1442
1448
  input: openAIParams.prompt,
1443
1449
  output: [],
@@ -1499,6 +1505,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1499
1505
  try {
1500
1506
  const contentBlocks = [];
1501
1507
  let accumulatedContent = '';
1508
+ let modelFromResponse;
1502
1509
  let usage = {
1503
1510
  inputTokens: 0,
1504
1511
  outputTokens: 0
@@ -1506,6 +1513,10 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1506
1513
  // Map to track in-progress tool calls
1507
1514
  const toolCallsInProgress = new Map();
1508
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
+ }
1509
1520
  const choice = chunk?.choices?.[0];
1510
1521
  // Handle text content
1511
1522
  const deltaContent = choice?.delta?.content;
@@ -1587,7 +1598,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1587
1598
  await sendEventToPosthog({
1588
1599
  client: this.phClient,
1589
1600
  ...posthogParams,
1590
- model: openAIParams.model,
1601
+ model: openAIParams.model ?? modelFromResponse,
1591
1602
  provider: 'azure',
1592
1603
  input: sanitizeOpenAI(openAIParams.messages),
1593
1604
  output: formattedOutput,
@@ -1631,7 +1642,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1631
1642
  await sendEventToPosthog({
1632
1643
  client: this.phClient,
1633
1644
  ...posthogParams,
1634
- model: openAIParams.model,
1645
+ model: openAIParams.model ?? result.model,
1635
1646
  provider: 'azure',
1636
1647
  input: openAIParams.messages,
1637
1648
  output: formatResponseOpenAI(result),
@@ -1695,11 +1706,18 @@ class WrappedResponses extends AzureOpenAI.Responses {
1695
1706
  (async () => {
1696
1707
  try {
1697
1708
  let finalContent = [];
1709
+ let modelFromResponse;
1698
1710
  let usage = {
1699
1711
  inputTokens: 0,
1700
1712
  outputTokens: 0
1701
1713
  };
1702
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
+ }
1703
1721
  if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
1704
1722
  finalContent = chunk.response.output;
1705
1723
  }
@@ -1716,10 +1734,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1716
1734
  await sendEventToPosthog({
1717
1735
  client: this.phClient,
1718
1736
  ...posthogParams,
1719
- //@ts-expect-error
1720
- model: openAIParams.model,
1737
+ model: openAIParams.model ?? modelFromResponse,
1721
1738
  provider: 'azure',
1722
- input: openAIParams.input,
1739
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1723
1740
  output: finalContent,
1724
1741
  latency,
1725
1742
  baseURL: this.baseURL,
@@ -1732,10 +1749,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1732
1749
  await sendEventToPosthog({
1733
1750
  client: this.phClient,
1734
1751
  ...posthogParams,
1735
- //@ts-expect-error
1736
1752
  model: openAIParams.model,
1737
1753
  provider: 'azure',
1738
- input: openAIParams.input,
1754
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1739
1755
  output: [],
1740
1756
  latency: 0,
1741
1757
  baseURL: this.baseURL,
@@ -1761,10 +1777,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1761
1777
  await sendEventToPosthog({
1762
1778
  client: this.phClient,
1763
1779
  ...posthogParams,
1764
- //@ts-expect-error
1765
- model: openAIParams.model,
1780
+ model: openAIParams.model ?? result.model,
1766
1781
  provider: 'azure',
1767
- input: openAIParams.input,
1782
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1768
1783
  output: result.output,
1769
1784
  latency,
1770
1785
  baseURL: this.baseURL,
@@ -1784,10 +1799,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1784
1799
  await sendEventToPosthog({
1785
1800
  client: this.phClient,
1786
1801
  ...posthogParams,
1787
- //@ts-expect-error
1788
1802
  model: openAIParams.model,
1789
1803
  provider: 'azure',
1790
- input: openAIParams.input,
1804
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1791
1805
  output: [],
1792
1806
  latency: 0,
1793
1807
  baseURL: this.baseURL,
@@ -1817,9 +1831,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1817
1831
  await sendEventToPosthog({
1818
1832
  client: this.phClient,
1819
1833
  ...posthogParams,
1820
- model: String(openAIParams.model ?? ''),
1834
+ model: openAIParams.model ?? result.model,
1821
1835
  provider: 'azure',
1822
- input: openAIParams.input,
1836
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1823
1837
  output: result.output,
1824
1838
  latency,
1825
1839
  baseURL: this.baseURL,
@@ -1837,9 +1851,9 @@ class WrappedResponses extends AzureOpenAI.Responses {
1837
1851
  await sendEventToPosthog({
1838
1852
  client: this.phClient,
1839
1853
  ...posthogParams,
1840
- model: String(openAIParams.model ?? ''),
1854
+ model: openAIParams.model,
1841
1855
  provider: 'azure',
1842
- input: openAIParams.input,
1856
+ input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
1843
1857
  output: [],
1844
1858
  latency: 0,
1845
1859
  baseURL: this.baseURL,
@@ -2112,67 +2126,117 @@ const extractProvider = model => {
2112
2126
  const providerName = provider.split('.')[0];
2113
2127
  return providerName;
2114
2128
  };
2115
- const createInstrumentationMiddleware = (phClient, model, options) => {
2116
- const middleware = {
2117
- wrapGenerate: async ({
2118
- doGenerate,
2119
- params
2120
- }) => {
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 => {
2121
2207
  const startTime = Date.now();
2122
2208
  const mergedParams = {
2123
- ...options,
2124
- ...mapVercelParams(params),
2125
- posthogProperties: {
2126
- ...options.posthogProperties,
2127
- $ai_framework: 'vercel'
2128
- }
2209
+ ...mergedOptions,
2210
+ ...mapVercelParams(params)
2129
2211
  };
2130
2212
  const availableTools = extractAvailableToolCalls('vercel', params);
2131
2213
  try {
2132
- const result = await doGenerate();
2133
- const modelId = options.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
2134
- 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);
2135
2217
  const baseURL = ''; // cannot currently get baseURL from vercel
2136
2218
  const content = mapVercelOutput(result.content);
2137
2219
  const latency = (Date.now() - startTime) / 1000;
2138
2220
  const providerMetadata = result.providerMetadata;
2139
- const additionalTokenValues = {
2140
- ...(providerMetadata?.anthropic ? {
2141
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
2142
- } : {})
2143
- };
2144
- // Calculate web search count based on provider
2145
- let webSearchCount = 0;
2146
- if (providerMetadata?.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
2147
- // Anthropic-specific extraction
2148
- const serverToolUse = providerMetadata.anthropic.server_tool_use;
2149
- if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
2150
- webSearchCount = serverToolUse.web_search_requests;
2151
- }
2152
- } else {
2153
- // For other providers through Vercel, pass available metadata to helper
2154
- // Note: Vercel abstracts provider responses, so we may not have access to
2155
- // raw citations/annotations unless Vercel exposes them in usage/metadata
2156
- webSearchCount = calculateWebSearchCount({
2157
- usage: result.usage,
2158
- providerMetadata: providerMetadata
2159
- });
2160
- }
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;
2161
2225
  const usage = {
2162
- inputTokens: result.usage.inputTokens,
2163
- outputTokens: result.usage.outputTokens,
2164
- reasoningTokens: result.usage.reasoningTokens,
2165
- cacheReadInputTokens: result.usage.cachedInputTokens,
2226
+ inputTokens: extractTokenCount(result.usage.inputTokens),
2227
+ outputTokens: extractTokenCount(result.usage.outputTokens),
2228
+ reasoningTokens: extractReasoningTokens(usageObj),
2229
+ cacheReadInputTokens: extractCacheReadTokens(usageObj),
2166
2230
  webSearchCount,
2167
2231
  ...additionalTokenValues
2168
2232
  };
2169
2233
  await sendEventToPosthog({
2170
2234
  client: phClient,
2171
- distinctId: options.posthogDistinctId,
2172
- traceId: options.posthogTraceId ?? v4(),
2235
+ distinctId: mergedOptions.posthogDistinctId,
2236
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2173
2237
  model: modelId,
2174
2238
  provider: provider,
2175
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2239
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2176
2240
  output: content,
2177
2241
  latency,
2178
2242
  baseURL,
@@ -2180,18 +2244,18 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2180
2244
  httpStatus: 200,
2181
2245
  usage,
2182
2246
  tools: availableTools,
2183
- captureImmediate: options.posthogCaptureImmediate
2247
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2184
2248
  });
2185
2249
  return result;
2186
2250
  } catch (error) {
2187
2251
  const modelId = model.modelId;
2188
2252
  await sendEventToPosthog({
2189
2253
  client: phClient,
2190
- distinctId: options.posthogDistinctId,
2191
- traceId: options.posthogTraceId ?? v4(),
2254
+ distinctId: mergedOptions.posthogDistinctId,
2255
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2192
2256
  model: modelId,
2193
2257
  provider: model.provider,
2194
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2258
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2195
2259
  output: [],
2196
2260
  latency: 0,
2197
2261
  baseURL: '',
@@ -2204,30 +2268,23 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2204
2268
  isError: true,
2205
2269
  error: truncate(JSON.stringify(error)),
2206
2270
  tools: availableTools,
2207
- captureImmediate: options.posthogCaptureImmediate
2271
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2208
2272
  });
2209
2273
  throw error;
2210
2274
  }
2211
2275
  },
2212
- wrapStream: async ({
2213
- doStream,
2214
- params
2215
- }) => {
2276
+ doStream: async params => {
2216
2277
  const startTime = Date.now();
2217
2278
  let generatedText = '';
2218
2279
  let reasoningText = '';
2219
2280
  let usage = {};
2220
2281
  let providerMetadata = undefined;
2221
2282
  const mergedParams = {
2222
- ...options,
2223
- ...mapVercelParams(params),
2224
- posthogProperties: {
2225
- ...options.posthogProperties,
2226
- $ai_framework: 'vercel'
2227
- }
2283
+ ...mergedOptions,
2284
+ ...mapVercelParams(params)
2228
2285
  };
2229
- const modelId = options.posthogModelOverride ?? model.modelId;
2230
- const provider = options.posthogProviderOverride ?? extractProvider(model);
2286
+ const modelId = mergedOptions.posthogModelOverride ?? model.modelId;
2287
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
2231
2288
  const availableTools = extractAvailableToolCalls('vercel', params);
2232
2289
  const baseURL = ''; // cannot currently get baseURL from vercel
2233
2290
  // Map to track in-progress tool calls
@@ -2236,15 +2293,15 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2236
2293
  const {
2237
2294
  stream,
2238
2295
  ...rest
2239
- } = await doStream();
2296
+ } = await model.doStream(params);
2240
2297
  const transformStream = new TransformStream({
2241
2298
  transform(chunk, controller) {
2242
- // Handle new v5 streaming patterns
2299
+ // Handle streaming patterns - compatible with both V2 and V3
2243
2300
  if (chunk.type === 'text-delta') {
2244
2301
  generatedText += chunk.delta;
2245
2302
  }
2246
2303
  if (chunk.type === 'reasoning-delta') {
2247
- reasoningText += chunk.delta; // New in v5
2304
+ reasoningText += chunk.delta;
2248
2305
  }
2249
2306
  // Handle tool call chunks
2250
2307
  if (chunk.type === 'tool-input-start') {
@@ -2264,7 +2321,6 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2264
2321
  }
2265
2322
  if (chunk.type === 'tool-input-end') {
2266
2323
  // Tool call is complete, keep it in the map for final processing
2267
- // Nothing specific to do here, the tool call is already complete
2268
2324
  }
2269
2325
  if (chunk.type === 'tool-call') {
2270
2326
  // Direct tool call chunk (complete tool call)
@@ -2276,14 +2332,13 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2276
2332
  }
2277
2333
  if (chunk.type === 'finish') {
2278
2334
  providerMetadata = chunk.providerMetadata;
2279
- const additionalTokenValues = providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic ? {
2280
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
2281
- } : {};
2335
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
2336
+ const chunkUsage = chunk.usage || {};
2282
2337
  usage = {
2283
- inputTokens: chunk.usage?.inputTokens,
2284
- outputTokens: chunk.usage?.outputTokens,
2285
- reasoningTokens: chunk.usage?.reasoningTokens,
2286
- cacheReadInputTokens: chunk.usage?.cachedInputTokens,
2338
+ inputTokens: extractTokenCount(chunk.usage?.inputTokens),
2339
+ outputTokens: extractTokenCount(chunk.usage?.outputTokens),
2340
+ reasoningTokens: extractReasoningTokens(chunkUsage),
2341
+ cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
2287
2342
  ...additionalTokenValues
2288
2343
  };
2289
2344
  }
@@ -2323,23 +2378,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2323
2378
  role: 'assistant',
2324
2379
  content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
2325
2380
  }] : [];
2326
- // Calculate web search count based on provider
2327
- let webSearchCount = 0;
2328
- if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
2329
- // Anthropic-specific extraction
2330
- const serverToolUse = providerMetadata.anthropic.server_tool_use;
2331
- if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
2332
- webSearchCount = serverToolUse.web_search_requests;
2333
- }
2334
- } else {
2335
- // For other providers through Vercel, pass available metadata to helper
2336
- // Note: Vercel abstracts provider responses, so we may not have access to
2337
- // raw citations/annotations unless Vercel exposes them in usage/metadata
2338
- webSearchCount = calculateWebSearchCount({
2339
- usage: usage,
2340
- providerMetadata: providerMetadata
2341
- });
2342
- }
2381
+ const webSearchCount = extractWebSearchCount(providerMetadata, usage);
2343
2382
  // Update usage with web search count
2344
2383
  const finalUsage = {
2345
2384
  ...usage,
@@ -2347,11 +2386,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2347
2386
  };
2348
2387
  await sendEventToPosthog({
2349
2388
  client: phClient,
2350
- distinctId: options.posthogDistinctId,
2351
- traceId: options.posthogTraceId ?? v4(),
2389
+ distinctId: mergedOptions.posthogDistinctId,
2390
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2352
2391
  model: modelId,
2353
2392
  provider: provider,
2354
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2393
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2355
2394
  output: output,
2356
2395
  latency,
2357
2396
  baseURL,
@@ -2359,7 +2398,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2359
2398
  httpStatus: 200,
2360
2399
  usage: finalUsage,
2361
2400
  tools: availableTools,
2362
- captureImmediate: options.posthogCaptureImmediate
2401
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2363
2402
  });
2364
2403
  }
2365
2404
  });
@@ -2370,11 +2409,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2370
2409
  } catch (error) {
2371
2410
  await sendEventToPosthog({
2372
2411
  client: phClient,
2373
- distinctId: options.posthogDistinctId,
2374
- traceId: options.posthogTraceId ?? v4(),
2412
+ distinctId: mergedOptions.posthogDistinctId,
2413
+ traceId: mergedOptions.posthogTraceId ?? v4(),
2375
2414
  model: modelId,
2376
2415
  provider: provider,
2377
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2416
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
2378
2417
  output: [],
2379
2418
  latency: 0,
2380
2419
  baseURL: '',
@@ -2387,25 +2426,12 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
2387
2426
  isError: true,
2388
2427
  error: truncate(JSON.stringify(error)),
2389
2428
  tools: availableTools,
2390
- captureImmediate: options.posthogCaptureImmediate
2429
+ captureImmediate: mergedOptions.posthogCaptureImmediate
2391
2430
  });
2392
2431
  throw error;
2393
2432
  }
2394
2433
  }
2395
2434
  };
2396
- return middleware;
2397
- };
2398
- const wrapVercelLanguageModel = (model, phClient, options) => {
2399
- const traceId = options.posthogTraceId ?? v4();
2400
- const middleware = createInstrumentationMiddleware(phClient, model, {
2401
- ...options,
2402
- posthogTraceId: traceId,
2403
- posthogDistinctId: options.posthogDistinctId
2404
- });
2405
- const wrappedModel = wrapLanguageModel({
2406
- model,
2407
- middleware
2408
- });
2409
2435
  return wrappedModel;
2410
2436
  };
2411
2437