@posthog/ai 6.1.1 → 6.2.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
@@ -6,6 +6,8 @@ import { wrapLanguageModel } from 'ai';
6
6
  import AnthropicOriginal from '@anthropic-ai/sdk';
7
7
  import { GoogleGenAI } from '@google/genai';
8
8
 
9
+ var version = "6.2.0";
10
+
9
11
  // limit large outputs by truncating to 200kb (approx 200k bytes)
10
12
  const MAX_OUTPUT_SIZE = 200000;
11
13
  const STRING_FORMAT = 'utf8';
@@ -238,9 +240,8 @@ const extractAvailableToolCalls = (provider, params) => {
238
240
  }
239
241
  return null;
240
242
  } else if (provider === 'vercel') {
241
- // Vercel AI SDK stores tools in params.mode.tools when mode type is 'regular'
242
- if (params.mode?.type === 'regular' && params.mode.tools) {
243
- return params.mode.tools;
243
+ if (params.tools) {
244
+ return params.tools;
244
245
  }
245
246
  return null;
246
247
  }
@@ -314,6 +315,8 @@ const sendEventToPosthog = async ({
314
315
  } : {})
315
316
  };
316
317
  const properties = {
318
+ $ai_lib: 'posthog-ai',
319
+ $ai_lib_version: version,
317
320
  $ai_provider: params.posthogProviderOverride ?? provider,
318
321
  $ai_model: params.posthogModelOverride ?? model,
319
322
  $ai_model_parameters: getModelParams(params),
@@ -595,14 +598,52 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
595
598
  const [stream1, stream2] = value.tee();
596
599
  (async () => {
597
600
  try {
601
+ const contentBlocks = [];
598
602
  let accumulatedContent = '';
599
603
  let usage = {
600
604
  inputTokens: 0,
601
605
  outputTokens: 0
602
606
  };
607
+ // Map to track in-progress tool calls
608
+ const toolCallsInProgress = new Map();
603
609
  for await (const chunk of stream1) {
604
- const delta = chunk?.choices?.[0]?.delta?.content ?? '';
605
- accumulatedContent += delta;
610
+ const choice = chunk?.choices?.[0];
611
+ // Handle text content
612
+ const deltaContent = choice?.delta?.content;
613
+ if (deltaContent) {
614
+ accumulatedContent += deltaContent;
615
+ }
616
+ // Handle tool calls
617
+ const deltaToolCalls = choice?.delta?.tool_calls;
618
+ if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
619
+ for (const toolCall of deltaToolCalls) {
620
+ const index = toolCall.index;
621
+ if (index !== undefined) {
622
+ if (!toolCallsInProgress.has(index)) {
623
+ // New tool call
624
+ toolCallsInProgress.set(index, {
625
+ id: toolCall.id || '',
626
+ name: toolCall.function?.name || '',
627
+ arguments: ''
628
+ });
629
+ }
630
+ const inProgressCall = toolCallsInProgress.get(index);
631
+ if (inProgressCall) {
632
+ // Update tool call data
633
+ if (toolCall.id) {
634
+ inProgressCall.id = toolCall.id;
635
+ }
636
+ if (toolCall.function?.name) {
637
+ inProgressCall.name = toolCall.function.name;
638
+ }
639
+ if (toolCall.function?.arguments) {
640
+ inProgressCall.arguments += toolCall.function.arguments;
641
+ }
642
+ }
643
+ }
644
+ }
645
+ }
646
+ // Handle usage information
606
647
  if (chunk.usage) {
607
648
  usage = {
608
649
  inputTokens: chunk.usage.prompt_tokens ?? 0,
@@ -612,6 +653,37 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
612
653
  };
613
654
  }
614
655
  }
656
+ // Build final content blocks
657
+ if (accumulatedContent) {
658
+ contentBlocks.push({
659
+ type: 'text',
660
+ text: accumulatedContent
661
+ });
662
+ }
663
+ // Add completed tool calls to content blocks
664
+ for (const toolCall of toolCallsInProgress.values()) {
665
+ if (toolCall.name) {
666
+ contentBlocks.push({
667
+ type: 'function',
668
+ id: toolCall.id,
669
+ function: {
670
+ name: toolCall.name,
671
+ arguments: toolCall.arguments
672
+ }
673
+ });
674
+ }
675
+ }
676
+ // Format output to match non-streaming version
677
+ const formattedOutput = contentBlocks.length > 0 ? [{
678
+ role: 'assistant',
679
+ content: contentBlocks
680
+ }] : [{
681
+ role: 'assistant',
682
+ content: [{
683
+ type: 'text',
684
+ text: ''
685
+ }]
686
+ }];
615
687
  const latency = (Date.now() - startTime) / 1000;
616
688
  const availableTools = extractAvailableToolCalls('openai', openAIParams);
617
689
  await sendEventToPosthog({
@@ -621,10 +693,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
621
693
  model: openAIParams.model,
622
694
  provider: 'openai',
623
695
  input: sanitizeOpenAI(openAIParams.messages),
624
- output: [{
625
- content: accumulatedContent,
626
- role: 'assistant'
627
- }],
696
+ output: formattedOutput,
628
697
  latency,
629
698
  baseURL: this.baseURL ?? '',
630
699
  params: body,
@@ -634,6 +703,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
634
703
  captureImmediate: posthogCaptureImmediate
635
704
  });
636
705
  } catch (error) {
706
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
637
707
  await sendEventToPosthog({
638
708
  client: this.phClient,
639
709
  distinctId: posthogDistinctId,
@@ -645,7 +715,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
645
715
  latency: 0,
646
716
  baseURL: this.baseURL ?? '',
647
717
  params: body,
648
- httpStatus: error?.status ? error.status : 500,
718
+ httpStatus,
649
719
  usage: {
650
720
  inputTokens: 0,
651
721
  outputTokens: 0
@@ -690,6 +760,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
690
760
  }
691
761
  return result;
692
762
  }, async error => {
763
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
693
764
  await sendEventToPosthog({
694
765
  client: this.phClient,
695
766
  distinctId: posthogDistinctId,
@@ -701,7 +772,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
701
772
  latency: 0,
702
773
  baseURL: this.baseURL ?? '',
703
774
  params: body,
704
- httpStatus: error?.status ? error.status : 500,
775
+ httpStatus,
705
776
  usage: {
706
777
  inputTokens: 0,
707
778
  outputTokens: 0
@@ -780,6 +851,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
780
851
  captureImmediate: posthogCaptureImmediate
781
852
  });
782
853
  } catch (error) {
854
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
783
855
  await sendEventToPosthog({
784
856
  client: this.phClient,
785
857
  distinctId: posthogDistinctId,
@@ -792,7 +864,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
792
864
  latency: 0,
793
865
  baseURL: this.baseURL ?? '',
794
866
  params: body,
795
- httpStatus: error?.status ? error.status : 500,
867
+ httpStatus,
796
868
  usage: {
797
869
  inputTokens: 0,
798
870
  outputTokens: 0
@@ -839,6 +911,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
839
911
  }
840
912
  return result;
841
913
  }, async error => {
914
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
842
915
  await sendEventToPosthog({
843
916
  client: this.phClient,
844
917
  distinctId: posthogDistinctId,
@@ -851,7 +924,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
851
924
  latency: 0,
852
925
  baseURL: this.baseURL ?? '',
853
926
  params: body,
854
- httpStatus: error?.status ? error.status : 500,
927
+ httpStatus,
855
928
  usage: {
856
929
  inputTokens: 0,
857
930
  outputTokens: 0
@@ -910,6 +983,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
910
983
  });
911
984
  return result;
912
985
  }, async error => {
986
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
913
987
  await sendEventToPosthog({
914
988
  client: this.phClient,
915
989
  distinctId: posthogDistinctId,
@@ -922,7 +996,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
922
996
  latency: 0,
923
997
  baseURL: this.baseURL ?? '',
924
998
  params: body,
925
- httpStatus: error?.status ? error.status : 500,
999
+ httpStatus,
926
1000
  usage: {
927
1001
  inputTokens: 0,
928
1002
  outputTokens: 0
@@ -984,14 +1058,52 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
984
1058
  const [stream1, stream2] = value.tee();
985
1059
  (async () => {
986
1060
  try {
1061
+ const contentBlocks = [];
987
1062
  let accumulatedContent = '';
988
1063
  let usage = {
989
1064
  inputTokens: 0,
990
1065
  outputTokens: 0
991
1066
  };
1067
+ // Map to track in-progress tool calls
1068
+ const toolCallsInProgress = new Map();
992
1069
  for await (const chunk of stream1) {
993
- const delta = chunk?.choices?.[0]?.delta?.content ?? '';
994
- accumulatedContent += delta;
1070
+ const choice = chunk?.choices?.[0];
1071
+ // Handle text content
1072
+ const deltaContent = choice?.delta?.content;
1073
+ if (deltaContent) {
1074
+ accumulatedContent += deltaContent;
1075
+ }
1076
+ // Handle tool calls
1077
+ const deltaToolCalls = choice?.delta?.tool_calls;
1078
+ if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
1079
+ for (const toolCall of deltaToolCalls) {
1080
+ const index = toolCall.index;
1081
+ if (index !== undefined) {
1082
+ if (!toolCallsInProgress.has(index)) {
1083
+ // New tool call
1084
+ toolCallsInProgress.set(index, {
1085
+ id: toolCall.id || '',
1086
+ name: toolCall.function?.name || '',
1087
+ arguments: ''
1088
+ });
1089
+ }
1090
+ const inProgressCall = toolCallsInProgress.get(index);
1091
+ if (inProgressCall) {
1092
+ // Update tool call data
1093
+ if (toolCall.id) {
1094
+ inProgressCall.id = toolCall.id;
1095
+ }
1096
+ if (toolCall.function?.name) {
1097
+ inProgressCall.name = toolCall.function.name;
1098
+ }
1099
+ if (toolCall.function?.arguments) {
1100
+ inProgressCall.arguments += toolCall.function.arguments;
1101
+ }
1102
+ }
1103
+ }
1104
+ }
1105
+ }
1106
+ // Handle usage information
995
1107
  if (chunk.usage) {
996
1108
  usage = {
997
1109
  inputTokens: chunk.usage.prompt_tokens ?? 0,
@@ -1001,6 +1113,37 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1001
1113
  };
1002
1114
  }
1003
1115
  }
1116
+ // Build final content blocks
1117
+ if (accumulatedContent) {
1118
+ contentBlocks.push({
1119
+ type: 'text',
1120
+ text: accumulatedContent
1121
+ });
1122
+ }
1123
+ // Add completed tool calls to content blocks
1124
+ for (const toolCall of toolCallsInProgress.values()) {
1125
+ if (toolCall.name) {
1126
+ contentBlocks.push({
1127
+ type: 'function',
1128
+ id: toolCall.id,
1129
+ function: {
1130
+ name: toolCall.name,
1131
+ arguments: toolCall.arguments
1132
+ }
1133
+ });
1134
+ }
1135
+ }
1136
+ // Format output to match non-streaming version
1137
+ const formattedOutput = contentBlocks.length > 0 ? [{
1138
+ role: 'assistant',
1139
+ content: contentBlocks
1140
+ }] : [{
1141
+ role: 'assistant',
1142
+ content: [{
1143
+ type: 'text',
1144
+ text: ''
1145
+ }]
1146
+ }];
1004
1147
  const latency = (Date.now() - startTime) / 1000;
1005
1148
  await sendEventToPosthog({
1006
1149
  client: this.phClient,
@@ -1009,10 +1152,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1009
1152
  model: openAIParams.model,
1010
1153
  provider: 'azure',
1011
1154
  input: openAIParams.messages,
1012
- output: [{
1013
- content: accumulatedContent,
1014
- role: 'assistant'
1015
- }],
1155
+ output: formattedOutput,
1016
1156
  latency,
1017
1157
  baseURL: this.baseURL ?? '',
1018
1158
  params: body,
@@ -1021,6 +1161,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1021
1161
  captureImmediate: posthogCaptureImmediate
1022
1162
  });
1023
1163
  } catch (error) {
1164
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1024
1165
  await sendEventToPosthog({
1025
1166
  client: this.phClient,
1026
1167
  distinctId: posthogDistinctId,
@@ -1032,7 +1173,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1032
1173
  latency: 0,
1033
1174
  baseURL: this.baseURL ?? '',
1034
1175
  params: body,
1035
- httpStatus: error?.status ? error.status : 500,
1176
+ httpStatus,
1036
1177
  usage: {
1037
1178
  inputTokens: 0,
1038
1179
  outputTokens: 0
@@ -1075,6 +1216,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1075
1216
  }
1076
1217
  return result;
1077
1218
  }, async error => {
1219
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1078
1220
  await sendEventToPosthog({
1079
1221
  client: this.phClient,
1080
1222
  distinctId: posthogDistinctId,
@@ -1086,7 +1228,7 @@ class WrappedCompletions extends AzureOpenAI.Chat.Completions {
1086
1228
  latency: 0,
1087
1229
  baseURL: this.baseURL ?? '',
1088
1230
  params: body,
1089
- httpStatus: error?.status ? error.status : 500,
1231
+ httpStatus,
1090
1232
  usage: {
1091
1233
  inputTokens: 0,
1092
1234
  outputTokens: 0
@@ -1163,6 +1305,7 @@ class WrappedResponses extends AzureOpenAI.Responses {
1163
1305
  captureImmediate: posthogCaptureImmediate
1164
1306
  });
1165
1307
  } catch (error) {
1308
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1166
1309
  await sendEventToPosthog({
1167
1310
  client: this.phClient,
1168
1311
  distinctId: posthogDistinctId,
@@ -1175,7 +1318,7 @@ class WrappedResponses extends AzureOpenAI.Responses {
1175
1318
  latency: 0,
1176
1319
  baseURL: this.baseURL ?? '',
1177
1320
  params: body,
1178
- httpStatus: error?.status ? error.status : 500,
1321
+ httpStatus,
1179
1322
  usage: {
1180
1323
  inputTokens: 0,
1181
1324
  outputTokens: 0
@@ -1218,6 +1361,7 @@ class WrappedResponses extends AzureOpenAI.Responses {
1218
1361
  }
1219
1362
  return result;
1220
1363
  }, async error => {
1364
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1221
1365
  await sendEventToPosthog({
1222
1366
  client: this.phClient,
1223
1367
  distinctId: posthogDistinctId,
@@ -1230,7 +1374,7 @@ class WrappedResponses extends AzureOpenAI.Responses {
1230
1374
  latency: 0,
1231
1375
  baseURL: this.baseURL ?? '',
1232
1376
  params: body,
1233
- httpStatus: error?.status ? error.status : 500,
1377
+ httpStatus,
1234
1378
  usage: {
1235
1379
  inputTokens: 0,
1236
1380
  outputTokens: 0
@@ -1597,6 +1741,8 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
1597
1741
  const provider = options.posthogProviderOverride ?? extractProvider(model);
1598
1742
  const availableTools = extractAvailableToolCalls('vercel', params);
1599
1743
  const baseURL = ''; // cannot currently get baseURL from vercel
1744
+ // Map to track in-progress tool calls
1745
+ const toolCallsInProgress = new Map();
1600
1746
  try {
1601
1747
  const {
1602
1748
  stream,
@@ -1611,6 +1757,34 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
1611
1757
  if (chunk.type === 'reasoning-delta') {
1612
1758
  reasoningText += chunk.delta; // New in v5
1613
1759
  }
1760
+ // Handle tool call chunks
1761
+ if (chunk.type === 'tool-input-start') {
1762
+ // Initialize a new tool call
1763
+ toolCallsInProgress.set(chunk.id, {
1764
+ toolCallId: chunk.id,
1765
+ toolName: chunk.toolName,
1766
+ input: ''
1767
+ });
1768
+ }
1769
+ if (chunk.type === 'tool-input-delta') {
1770
+ // Accumulate tool call arguments
1771
+ const toolCall = toolCallsInProgress.get(chunk.id);
1772
+ if (toolCall) {
1773
+ toolCall.input += chunk.delta;
1774
+ }
1775
+ }
1776
+ if (chunk.type === 'tool-input-end') {
1777
+ // Tool call is complete, keep it in the map for final processing
1778
+ // Nothing specific to do here, the tool call is already complete
1779
+ }
1780
+ if (chunk.type === 'tool-call') {
1781
+ // Direct tool call chunk (complete tool call)
1782
+ toolCallsInProgress.set(chunk.toolCallId, {
1783
+ toolCallId: chunk.toolCallId,
1784
+ toolName: chunk.toolName,
1785
+ input: chunk.input
1786
+ });
1787
+ }
1614
1788
  if (chunk.type === 'finish') {
1615
1789
  const providerMetadata = chunk.providerMetadata;
1616
1790
  const additionalTokenValues = {
@@ -1644,6 +1818,19 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
1644
1818
  text: truncate(generatedText)
1645
1819
  });
1646
1820
  }
1821
+ // Add completed tool calls to content
1822
+ for (const toolCall of toolCallsInProgress.values()) {
1823
+ if (toolCall.toolName) {
1824
+ content.push({
1825
+ type: 'tool-call',
1826
+ id: toolCall.toolCallId,
1827
+ function: {
1828
+ name: toolCall.toolName,
1829
+ arguments: toolCall.input
1830
+ }
1831
+ });
1832
+ }
1833
+ }
1647
1834
  // Structure output like mapVercelOutput does
1648
1835
  const output = content.length > 0 ? [{
1649
1836
  role: 'assistant',
@@ -1746,6 +1933,9 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1746
1933
  if (anthropicParams.stream) {
1747
1934
  return parentPromise.then(value => {
1748
1935
  let accumulatedContent = '';
1936
+ const contentBlocks = [];
1937
+ const toolsInProgress = new Map();
1938
+ let currentTextBlock = null;
1749
1939
  const usage = {
1750
1940
  inputTokens: 0,
1751
1941
  outputTokens: 0,
@@ -1757,10 +1947,70 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1757
1947
  (async () => {
1758
1948
  try {
1759
1949
  for await (const chunk of stream1) {
1950
+ // Handle content block start events
1951
+ if (chunk.type === 'content_block_start') {
1952
+ if (chunk.content_block?.type === 'text') {
1953
+ currentTextBlock = {
1954
+ type: 'text',
1955
+ text: ''
1956
+ };
1957
+ contentBlocks.push(currentTextBlock);
1958
+ } else if (chunk.content_block?.type === 'tool_use') {
1959
+ const toolBlock = {
1960
+ type: 'function',
1961
+ id: chunk.content_block.id,
1962
+ function: {
1963
+ name: chunk.content_block.name,
1964
+ arguments: {}
1965
+ }
1966
+ };
1967
+ contentBlocks.push(toolBlock);
1968
+ toolsInProgress.set(chunk.content_block.id, {
1969
+ block: toolBlock,
1970
+ inputString: ''
1971
+ });
1972
+ currentTextBlock = null;
1973
+ }
1974
+ }
1975
+ // Handle text delta events
1760
1976
  if ('delta' in chunk) {
1761
1977
  if ('text' in chunk.delta) {
1762
1978
  const delta = chunk?.delta?.text ?? '';
1763
1979
  accumulatedContent += delta;
1980
+ if (currentTextBlock) {
1981
+ currentTextBlock.text += delta;
1982
+ }
1983
+ }
1984
+ }
1985
+ // Handle tool input delta events
1986
+ if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'input_json_delta') {
1987
+ const block = chunk.index !== undefined ? contentBlocks[chunk.index] : undefined;
1988
+ const toolId = block?.type === 'function' ? block.id : undefined;
1989
+ if (toolId && toolsInProgress.has(toolId)) {
1990
+ const tool = toolsInProgress.get(toolId);
1991
+ if (tool) {
1992
+ tool.inputString += chunk.delta.partial_json || '';
1993
+ }
1994
+ }
1995
+ }
1996
+ // Handle content block stop events
1997
+ if (chunk.type === 'content_block_stop') {
1998
+ currentTextBlock = null;
1999
+ // Parse accumulated tool input
2000
+ if (chunk.index !== undefined) {
2001
+ const block = contentBlocks[chunk.index];
2002
+ if (block?.type === 'function' && block.id && toolsInProgress.has(block.id)) {
2003
+ const tool = toolsInProgress.get(block.id);
2004
+ if (tool) {
2005
+ try {
2006
+ block.function.arguments = JSON.parse(tool.inputString);
2007
+ } catch (e) {
2008
+ // Keep empty object if parsing fails
2009
+ console.error('Error parsing tool input:', e);
2010
+ }
2011
+ }
2012
+ toolsInProgress.delete(block.id);
2013
+ }
1764
2014
  }
1765
2015
  }
1766
2016
  if (chunk.type == 'message_start') {
@@ -1774,6 +2024,17 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1774
2024
  }
1775
2025
  const latency = (Date.now() - startTime) / 1000;
1776
2026
  const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
2027
+ // Format output to match non-streaming version
2028
+ const formattedOutput = contentBlocks.length > 0 ? [{
2029
+ role: 'assistant',
2030
+ content: contentBlocks
2031
+ }] : [{
2032
+ role: 'assistant',
2033
+ content: [{
2034
+ type: 'text',
2035
+ text: accumulatedContent
2036
+ }]
2037
+ }];
1777
2038
  await sendEventToPosthog({
1778
2039
  client: this.phClient,
1779
2040
  distinctId: posthogDistinctId,
@@ -1781,10 +2042,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1781
2042
  model: anthropicParams.model,
1782
2043
  provider: 'anthropic',
1783
2044
  input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, 'anthropic')),
1784
- output: [{
1785
- content: accumulatedContent,
1786
- role: 'assistant'
1787
- }],
2045
+ output: formattedOutput,
1788
2046
  latency,
1789
2047
  baseURL: this.baseURL ?? '',
1790
2048
  params: body,
@@ -1909,6 +2167,7 @@ class WrappedModels {
1909
2167
  const response = await this.client.models.generateContent(geminiParams);
1910
2168
  const latency = (Date.now() - startTime) / 1000;
1911
2169
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2170
+ const metadata = response.usageMetadata;
1912
2171
  await sendEventToPosthog({
1913
2172
  client: this.phClient,
1914
2173
  distinctId: posthogDistinctId,
@@ -1922,10 +2181,10 @@ class WrappedModels {
1922
2181
  params: params,
1923
2182
  httpStatus: 200,
1924
2183
  usage: {
1925
- inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
1926
- outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
1927
- reasoningTokens: response.usageMetadata?.thoughtsTokenCount ?? 0,
1928
- cacheReadInputTokens: response.usageMetadata?.cachedContentTokenCount ?? 0
2184
+ inputTokens: metadata?.promptTokenCount ?? 0,
2185
+ outputTokens: metadata?.candidatesTokenCount ?? 0,
2186
+ reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
2187
+ cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0
1929
2188
  },
1930
2189
  tools: availableTools,
1931
2190
  captureImmediate: posthogCaptureImmediate
@@ -1967,7 +2226,7 @@ class WrappedModels {
1967
2226
  } = params;
1968
2227
  const traceId = posthogTraceId ?? v4();
1969
2228
  const startTime = Date.now();
1970
- let accumulatedContent = '';
2229
+ const accumulatedContent = [];
1971
2230
  let usage = {
1972
2231
  inputTokens: 0,
1973
2232
  outputTokens: 0
@@ -1975,21 +2234,66 @@ class WrappedModels {
1975
2234
  try {
1976
2235
  const stream = await this.client.models.generateContentStream(geminiParams);
1977
2236
  for await (const chunk of stream) {
2237
+ // Handle text content
1978
2238
  if (chunk.text) {
1979
- accumulatedContent += chunk.text;
2239
+ // Find if we already have a text item to append to
2240
+ let lastTextItem;
2241
+ for (let i = accumulatedContent.length - 1; i >= 0; i--) {
2242
+ if (accumulatedContent[i].type === 'text') {
2243
+ lastTextItem = accumulatedContent[i];
2244
+ break;
2245
+ }
2246
+ }
2247
+ if (lastTextItem && lastTextItem.type === 'text') {
2248
+ lastTextItem.text += chunk.text;
2249
+ } else {
2250
+ accumulatedContent.push({
2251
+ type: 'text',
2252
+ text: chunk.text
2253
+ });
2254
+ }
1980
2255
  }
2256
+ // Handle function calls from candidates
2257
+ if (chunk.candidates && Array.isArray(chunk.candidates)) {
2258
+ for (const candidate of chunk.candidates) {
2259
+ if (candidate.content && candidate.content.parts) {
2260
+ for (const part of candidate.content.parts) {
2261
+ // Type-safe check for functionCall
2262
+ if ('functionCall' in part) {
2263
+ const funcCall = part.functionCall;
2264
+ if (funcCall?.name) {
2265
+ accumulatedContent.push({
2266
+ type: 'function',
2267
+ function: {
2268
+ name: funcCall.name,
2269
+ arguments: funcCall.args || {}
2270
+ }
2271
+ });
2272
+ }
2273
+ }
2274
+ }
2275
+ }
2276
+ }
2277
+ }
2278
+ // Update usage metadata - handle both old and new field names
1981
2279
  if (chunk.usageMetadata) {
2280
+ const metadata = chunk.usageMetadata;
1982
2281
  usage = {
1983
- inputTokens: chunk.usageMetadata.promptTokenCount ?? 0,
1984
- outputTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
1985
- reasoningTokens: chunk.usageMetadata.thoughtsTokenCount ?? 0,
1986
- cacheReadInputTokens: chunk.usageMetadata.cachedContentTokenCount ?? 0
2282
+ inputTokens: metadata.promptTokenCount ?? 0,
2283
+ outputTokens: metadata.candidatesTokenCount ?? 0,
2284
+ reasoningTokens: metadata.thoughtsTokenCount ?? 0,
2285
+ cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0
1987
2286
  };
1988
2287
  }
1989
2288
  yield chunk;
1990
2289
  }
1991
2290
  const latency = (Date.now() - startTime) / 1000;
1992
2291
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2292
+ // Format output similar to formatResponseGemini
2293
+ const output = accumulatedContent.length > 0 ? [{
2294
+ role: 'assistant',
2295
+ content: accumulatedContent
2296
+ }] : [];
1993
2297
  await sendEventToPosthog({
1994
2298
  client: this.phClient,
1995
2299
  distinctId: posthogDistinctId,
@@ -1997,10 +2301,7 @@ class WrappedModels {
1997
2301
  model: geminiParams.model,
1998
2302
  provider: 'gemini',
1999
2303
  input: this.formatInputForPostHog(geminiParams.contents),
2000
- output: [{
2001
- content: accumulatedContent,
2002
- role: 'assistant'
2003
- }],
2304
+ output,
2004
2305
  latency,
2005
2306
  baseURL: 'https://generativelanguage.googleapis.com',
2006
2307
  params: params,
@@ -2050,22 +2351,28 @@ class WrappedModels {
2050
2351
  };
2051
2352
  }
2052
2353
  if (item && typeof item === 'object') {
2053
- if (item.text) {
2354
+ const obj = item;
2355
+ if ('text' in obj && obj.text) {
2054
2356
  return {
2055
- role: item.role || 'user',
2056
- content: item.text
2357
+ role: obj.role || 'user',
2358
+ content: obj.text
2057
2359
  };
2058
2360
  }
2059
- if (item.content) {
2361
+ if ('content' in obj && obj.content) {
2060
2362
  return {
2061
- role: item.role || 'user',
2062
- content: item.content
2363
+ role: obj.role || 'user',
2364
+ content: obj.content
2063
2365
  };
2064
2366
  }
2065
- if (item.parts) {
2367
+ if ('parts' in obj && Array.isArray(obj.parts)) {
2066
2368
  return {
2067
- role: item.role || 'user',
2068
- content: item.parts.map(part => part.text ? part.text : part)
2369
+ role: obj.role || 'user',
2370
+ content: obj.parts.map(part => {
2371
+ if (part && typeof part === 'object' && 'text' in part) {
2372
+ return part.text;
2373
+ }
2374
+ return part;
2375
+ })
2069
2376
  };
2070
2377
  }
2071
2378
  }
@@ -2076,16 +2383,17 @@ class WrappedModels {
2076
2383
  });
2077
2384
  }
2078
2385
  if (contents && typeof contents === 'object') {
2079
- if (contents.text) {
2386
+ const obj = contents;
2387
+ if ('text' in obj && obj.text) {
2080
2388
  return [{
2081
2389
  role: 'user',
2082
- content: contents.text
2390
+ content: obj.text
2083
2391
  }];
2084
2392
  }
2085
- if (contents.content) {
2393
+ if ('content' in obj && obj.content) {
2086
2394
  return [{
2087
2395
  role: 'user',
2088
- content: contents.content
2396
+ content: obj.content
2089
2397
  }];
2090
2398
  }
2091
2399
  }
@@ -2850,6 +3158,8 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
2850
3158
  const eventName = parentRunId ? '$ai_span' : '$ai_trace';
2851
3159
  const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
2852
3160
  const eventProperties = {
3161
+ $ai_lib: 'posthog-ai',
3162
+ $ai_lib_version: version,
2853
3163
  $ai_trace_id: traceId,
2854
3164
  $ai_input_state: withPrivacyMode(this.client, this.privacyMode, run.input),
2855
3165
  $ai_latency: latency,
@@ -2890,6 +3200,8 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
2890
3200
  _captureGeneration(traceId, runId, run, output, parentRunId) {
2891
3201
  const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
2892
3202
  const eventProperties = {
3203
+ $ai_lib: 'posthog-ai',
3204
+ $ai_lib_version: version,
2893
3205
  $ai_trace_id: traceId,
2894
3206
  $ai_span_id: runId,
2895
3207
  $ai_span_name: run.name,