@posthog/ai 6.1.1 → 6.1.2

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.cjs CHANGED
@@ -258,9 +258,8 @@ const extractAvailableToolCalls = (provider, params) => {
258
258
  }
259
259
  return null;
260
260
  } else if (provider === 'vercel') {
261
- // Vercel AI SDK stores tools in params.mode.tools when mode type is 'regular'
262
- if (params.mode?.type === 'regular' && params.mode.tools) {
263
- return params.mode.tools;
261
+ if (params.tools) {
262
+ return params.tools;
264
263
  }
265
264
  return null;
266
265
  }
@@ -615,14 +614,52 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
615
614
  const [stream1, stream2] = value.tee();
616
615
  (async () => {
617
616
  try {
617
+ const contentBlocks = [];
618
618
  let accumulatedContent = '';
619
619
  let usage = {
620
620
  inputTokens: 0,
621
621
  outputTokens: 0
622
622
  };
623
+ // Map to track in-progress tool calls
624
+ const toolCallsInProgress = new Map();
623
625
  for await (const chunk of stream1) {
624
- const delta = chunk?.choices?.[0]?.delta?.content ?? '';
625
- accumulatedContent += delta;
626
+ const choice = chunk?.choices?.[0];
627
+ // Handle text content
628
+ const deltaContent = choice?.delta?.content;
629
+ if (deltaContent) {
630
+ accumulatedContent += deltaContent;
631
+ }
632
+ // Handle tool calls
633
+ const deltaToolCalls = choice?.delta?.tool_calls;
634
+ if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
635
+ for (const toolCall of deltaToolCalls) {
636
+ const index = toolCall.index;
637
+ if (index !== undefined) {
638
+ if (!toolCallsInProgress.has(index)) {
639
+ // New tool call
640
+ toolCallsInProgress.set(index, {
641
+ id: toolCall.id || '',
642
+ name: toolCall.function?.name || '',
643
+ arguments: ''
644
+ });
645
+ }
646
+ const inProgressCall = toolCallsInProgress.get(index);
647
+ if (inProgressCall) {
648
+ // Update tool call data
649
+ if (toolCall.id) {
650
+ inProgressCall.id = toolCall.id;
651
+ }
652
+ if (toolCall.function?.name) {
653
+ inProgressCall.name = toolCall.function.name;
654
+ }
655
+ if (toolCall.function?.arguments) {
656
+ inProgressCall.arguments += toolCall.function.arguments;
657
+ }
658
+ }
659
+ }
660
+ }
661
+ }
662
+ // Handle usage information
626
663
  if (chunk.usage) {
627
664
  usage = {
628
665
  inputTokens: chunk.usage.prompt_tokens ?? 0,
@@ -632,6 +669,37 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
632
669
  };
633
670
  }
634
671
  }
672
+ // Build final content blocks
673
+ if (accumulatedContent) {
674
+ contentBlocks.push({
675
+ type: 'text',
676
+ text: accumulatedContent
677
+ });
678
+ }
679
+ // Add completed tool calls to content blocks
680
+ for (const toolCall of toolCallsInProgress.values()) {
681
+ if (toolCall.name) {
682
+ contentBlocks.push({
683
+ type: 'function',
684
+ id: toolCall.id,
685
+ function: {
686
+ name: toolCall.name,
687
+ arguments: toolCall.arguments
688
+ }
689
+ });
690
+ }
691
+ }
692
+ // Format output to match non-streaming version
693
+ const formattedOutput = contentBlocks.length > 0 ? [{
694
+ role: 'assistant',
695
+ content: contentBlocks
696
+ }] : [{
697
+ role: 'assistant',
698
+ content: [{
699
+ type: 'text',
700
+ text: ''
701
+ }]
702
+ }];
635
703
  const latency = (Date.now() - startTime) / 1000;
636
704
  const availableTools = extractAvailableToolCalls('openai', openAIParams);
637
705
  await sendEventToPosthog({
@@ -641,10 +709,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
641
709
  model: openAIParams.model,
642
710
  provider: 'openai',
643
711
  input: sanitizeOpenAI(openAIParams.messages),
644
- output: [{
645
- content: accumulatedContent,
646
- role: 'assistant'
647
- }],
712
+ output: formattedOutput,
648
713
  latency,
649
714
  baseURL: this.baseURL ?? '',
650
715
  params: body,
@@ -654,6 +719,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
654
719
  captureImmediate: posthogCaptureImmediate
655
720
  });
656
721
  } catch (error) {
722
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
657
723
  await sendEventToPosthog({
658
724
  client: this.phClient,
659
725
  distinctId: posthogDistinctId,
@@ -665,7 +731,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
665
731
  latency: 0,
666
732
  baseURL: this.baseURL ?? '',
667
733
  params: body,
668
- httpStatus: error?.status ? error.status : 500,
734
+ httpStatus,
669
735
  usage: {
670
736
  inputTokens: 0,
671
737
  outputTokens: 0
@@ -710,6 +776,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
710
776
  }
711
777
  return result;
712
778
  }, async error => {
779
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
713
780
  await sendEventToPosthog({
714
781
  client: this.phClient,
715
782
  distinctId: posthogDistinctId,
@@ -721,7 +788,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
721
788
  latency: 0,
722
789
  baseURL: this.baseURL ?? '',
723
790
  params: body,
724
- httpStatus: error?.status ? error.status : 500,
791
+ httpStatus,
725
792
  usage: {
726
793
  inputTokens: 0,
727
794
  outputTokens: 0
@@ -800,6 +867,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
800
867
  captureImmediate: posthogCaptureImmediate
801
868
  });
802
869
  } catch (error) {
870
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
803
871
  await sendEventToPosthog({
804
872
  client: this.phClient,
805
873
  distinctId: posthogDistinctId,
@@ -812,7 +880,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
812
880
  latency: 0,
813
881
  baseURL: this.baseURL ?? '',
814
882
  params: body,
815
- httpStatus: error?.status ? error.status : 500,
883
+ httpStatus,
816
884
  usage: {
817
885
  inputTokens: 0,
818
886
  outputTokens: 0
@@ -859,6 +927,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
859
927
  }
860
928
  return result;
861
929
  }, async error => {
930
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
862
931
  await sendEventToPosthog({
863
932
  client: this.phClient,
864
933
  distinctId: posthogDistinctId,
@@ -871,7 +940,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
871
940
  latency: 0,
872
941
  baseURL: this.baseURL ?? '',
873
942
  params: body,
874
- httpStatus: error?.status ? error.status : 500,
943
+ httpStatus,
875
944
  usage: {
876
945
  inputTokens: 0,
877
946
  outputTokens: 0
@@ -930,6 +999,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
930
999
  });
931
1000
  return result;
932
1001
  }, async error => {
1002
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
933
1003
  await sendEventToPosthog({
934
1004
  client: this.phClient,
935
1005
  distinctId: posthogDistinctId,
@@ -942,7 +1012,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
942
1012
  latency: 0,
943
1013
  baseURL: this.baseURL ?? '',
944
1014
  params: body,
945
- httpStatus: error?.status ? error.status : 500,
1015
+ httpStatus,
946
1016
  usage: {
947
1017
  inputTokens: 0,
948
1018
  outputTokens: 0
@@ -1004,14 +1074,52 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1004
1074
  const [stream1, stream2] = value.tee();
1005
1075
  (async () => {
1006
1076
  try {
1077
+ const contentBlocks = [];
1007
1078
  let accumulatedContent = '';
1008
1079
  let usage = {
1009
1080
  inputTokens: 0,
1010
1081
  outputTokens: 0
1011
1082
  };
1083
+ // Map to track in-progress tool calls
1084
+ const toolCallsInProgress = new Map();
1012
1085
  for await (const chunk of stream1) {
1013
- const delta = chunk?.choices?.[0]?.delta?.content ?? '';
1014
- accumulatedContent += delta;
1086
+ const choice = chunk?.choices?.[0];
1087
+ // Handle text content
1088
+ const deltaContent = choice?.delta?.content;
1089
+ if (deltaContent) {
1090
+ accumulatedContent += deltaContent;
1091
+ }
1092
+ // Handle tool calls
1093
+ const deltaToolCalls = choice?.delta?.tool_calls;
1094
+ if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
1095
+ for (const toolCall of deltaToolCalls) {
1096
+ const index = toolCall.index;
1097
+ if (index !== undefined) {
1098
+ if (!toolCallsInProgress.has(index)) {
1099
+ // New tool call
1100
+ toolCallsInProgress.set(index, {
1101
+ id: toolCall.id || '',
1102
+ name: toolCall.function?.name || '',
1103
+ arguments: ''
1104
+ });
1105
+ }
1106
+ const inProgressCall = toolCallsInProgress.get(index);
1107
+ if (inProgressCall) {
1108
+ // Update tool call data
1109
+ if (toolCall.id) {
1110
+ inProgressCall.id = toolCall.id;
1111
+ }
1112
+ if (toolCall.function?.name) {
1113
+ inProgressCall.name = toolCall.function.name;
1114
+ }
1115
+ if (toolCall.function?.arguments) {
1116
+ inProgressCall.arguments += toolCall.function.arguments;
1117
+ }
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+ // Handle usage information
1015
1123
  if (chunk.usage) {
1016
1124
  usage = {
1017
1125
  inputTokens: chunk.usage.prompt_tokens ?? 0,
@@ -1021,6 +1129,37 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1021
1129
  };
1022
1130
  }
1023
1131
  }
1132
+ // Build final content blocks
1133
+ if (accumulatedContent) {
1134
+ contentBlocks.push({
1135
+ type: 'text',
1136
+ text: accumulatedContent
1137
+ });
1138
+ }
1139
+ // Add completed tool calls to content blocks
1140
+ for (const toolCall of toolCallsInProgress.values()) {
1141
+ if (toolCall.name) {
1142
+ contentBlocks.push({
1143
+ type: 'function',
1144
+ id: toolCall.id,
1145
+ function: {
1146
+ name: toolCall.name,
1147
+ arguments: toolCall.arguments
1148
+ }
1149
+ });
1150
+ }
1151
+ }
1152
+ // Format output to match non-streaming version
1153
+ const formattedOutput = contentBlocks.length > 0 ? [{
1154
+ role: 'assistant',
1155
+ content: contentBlocks
1156
+ }] : [{
1157
+ role: 'assistant',
1158
+ content: [{
1159
+ type: 'text',
1160
+ text: ''
1161
+ }]
1162
+ }];
1024
1163
  const latency = (Date.now() - startTime) / 1000;
1025
1164
  await sendEventToPosthog({
1026
1165
  client: this.phClient,
@@ -1029,10 +1168,7 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1029
1168
  model: openAIParams.model,
1030
1169
  provider: 'azure',
1031
1170
  input: openAIParams.messages,
1032
- output: [{
1033
- content: accumulatedContent,
1034
- role: 'assistant'
1035
- }],
1171
+ output: formattedOutput,
1036
1172
  latency,
1037
1173
  baseURL: this.baseURL ?? '',
1038
1174
  params: body,
@@ -1041,6 +1177,7 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1041
1177
  captureImmediate: posthogCaptureImmediate
1042
1178
  });
1043
1179
  } catch (error) {
1180
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1044
1181
  await sendEventToPosthog({
1045
1182
  client: this.phClient,
1046
1183
  distinctId: posthogDistinctId,
@@ -1052,7 +1189,7 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1052
1189
  latency: 0,
1053
1190
  baseURL: this.baseURL ?? '',
1054
1191
  params: body,
1055
- httpStatus: error?.status ? error.status : 500,
1192
+ httpStatus,
1056
1193
  usage: {
1057
1194
  inputTokens: 0,
1058
1195
  outputTokens: 0
@@ -1095,6 +1232,7 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1095
1232
  }
1096
1233
  return result;
1097
1234
  }, async error => {
1235
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1098
1236
  await sendEventToPosthog({
1099
1237
  client: this.phClient,
1100
1238
  distinctId: posthogDistinctId,
@@ -1106,7 +1244,7 @@ class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
1106
1244
  latency: 0,
1107
1245
  baseURL: this.baseURL ?? '',
1108
1246
  params: body,
1109
- httpStatus: error?.status ? error.status : 500,
1247
+ httpStatus,
1110
1248
  usage: {
1111
1249
  inputTokens: 0,
1112
1250
  outputTokens: 0
@@ -1183,6 +1321,7 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1183
1321
  captureImmediate: posthogCaptureImmediate
1184
1322
  });
1185
1323
  } catch (error) {
1324
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1186
1325
  await sendEventToPosthog({
1187
1326
  client: this.phClient,
1188
1327
  distinctId: posthogDistinctId,
@@ -1195,7 +1334,7 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1195
1334
  latency: 0,
1196
1335
  baseURL: this.baseURL ?? '',
1197
1336
  params: body,
1198
- httpStatus: error?.status ? error.status : 500,
1337
+ httpStatus,
1199
1338
  usage: {
1200
1339
  inputTokens: 0,
1201
1340
  outputTokens: 0
@@ -1238,6 +1377,7 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1238
1377
  }
1239
1378
  return result;
1240
1379
  }, async error => {
1380
+ const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
1241
1381
  await sendEventToPosthog({
1242
1382
  client: this.phClient,
1243
1383
  distinctId: posthogDistinctId,
@@ -1250,7 +1390,7 @@ class WrappedResponses extends openai.AzureOpenAI.Responses {
1250
1390
  latency: 0,
1251
1391
  baseURL: this.baseURL ?? '',
1252
1392
  params: body,
1253
- httpStatus: error?.status ? error.status : 500,
1393
+ httpStatus,
1254
1394
  usage: {
1255
1395
  inputTokens: 0,
1256
1396
  outputTokens: 0
@@ -1617,6 +1757,8 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
1617
1757
  const provider = options.posthogProviderOverride ?? extractProvider(model);
1618
1758
  const availableTools = extractAvailableToolCalls('vercel', params);
1619
1759
  const baseURL = ''; // cannot currently get baseURL from vercel
1760
+ // Map to track in-progress tool calls
1761
+ const toolCallsInProgress = new Map();
1620
1762
  try {
1621
1763
  const {
1622
1764
  stream,
@@ -1631,6 +1773,34 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
1631
1773
  if (chunk.type === 'reasoning-delta') {
1632
1774
  reasoningText += chunk.delta; // New in v5
1633
1775
  }
1776
+ // Handle tool call chunks
1777
+ if (chunk.type === 'tool-input-start') {
1778
+ // Initialize a new tool call
1779
+ toolCallsInProgress.set(chunk.id, {
1780
+ toolCallId: chunk.id,
1781
+ toolName: chunk.toolName,
1782
+ input: ''
1783
+ });
1784
+ }
1785
+ if (chunk.type === 'tool-input-delta') {
1786
+ // Accumulate tool call arguments
1787
+ const toolCall = toolCallsInProgress.get(chunk.id);
1788
+ if (toolCall) {
1789
+ toolCall.input += chunk.delta;
1790
+ }
1791
+ }
1792
+ if (chunk.type === 'tool-input-end') {
1793
+ // Tool call is complete, keep it in the map for final processing
1794
+ // Nothing specific to do here, the tool call is already complete
1795
+ }
1796
+ if (chunk.type === 'tool-call') {
1797
+ // Direct tool call chunk (complete tool call)
1798
+ toolCallsInProgress.set(chunk.toolCallId, {
1799
+ toolCallId: chunk.toolCallId,
1800
+ toolName: chunk.toolName,
1801
+ input: chunk.input
1802
+ });
1803
+ }
1634
1804
  if (chunk.type === 'finish') {
1635
1805
  const providerMetadata = chunk.providerMetadata;
1636
1806
  const additionalTokenValues = {
@@ -1664,6 +1834,19 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
1664
1834
  text: truncate(generatedText)
1665
1835
  });
1666
1836
  }
1837
+ // Add completed tool calls to content
1838
+ for (const toolCall of toolCallsInProgress.values()) {
1839
+ if (toolCall.toolName) {
1840
+ content.push({
1841
+ type: 'tool-call',
1842
+ id: toolCall.toolCallId,
1843
+ function: {
1844
+ name: toolCall.toolName,
1845
+ arguments: toolCall.input
1846
+ }
1847
+ });
1848
+ }
1849
+ }
1667
1850
  // Structure output like mapVercelOutput does
1668
1851
  const output = content.length > 0 ? [{
1669
1852
  role: 'assistant',
@@ -1766,6 +1949,9 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1766
1949
  if (anthropicParams.stream) {
1767
1950
  return parentPromise.then(value => {
1768
1951
  let accumulatedContent = '';
1952
+ const contentBlocks = [];
1953
+ const toolsInProgress = new Map();
1954
+ let currentTextBlock = null;
1769
1955
  const usage = {
1770
1956
  inputTokens: 0,
1771
1957
  outputTokens: 0,
@@ -1777,10 +1963,70 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1777
1963
  (async () => {
1778
1964
  try {
1779
1965
  for await (const chunk of stream1) {
1966
+ // Handle content block start events
1967
+ if (chunk.type === 'content_block_start') {
1968
+ if (chunk.content_block?.type === 'text') {
1969
+ currentTextBlock = {
1970
+ type: 'text',
1971
+ text: ''
1972
+ };
1973
+ contentBlocks.push(currentTextBlock);
1974
+ } else if (chunk.content_block?.type === 'tool_use') {
1975
+ const toolBlock = {
1976
+ type: 'function',
1977
+ id: chunk.content_block.id,
1978
+ function: {
1979
+ name: chunk.content_block.name,
1980
+ arguments: {}
1981
+ }
1982
+ };
1983
+ contentBlocks.push(toolBlock);
1984
+ toolsInProgress.set(chunk.content_block.id, {
1985
+ block: toolBlock,
1986
+ inputString: ''
1987
+ });
1988
+ currentTextBlock = null;
1989
+ }
1990
+ }
1991
+ // Handle text delta events
1780
1992
  if ('delta' in chunk) {
1781
1993
  if ('text' in chunk.delta) {
1782
1994
  const delta = chunk?.delta?.text ?? '';
1783
1995
  accumulatedContent += delta;
1996
+ if (currentTextBlock) {
1997
+ currentTextBlock.text += delta;
1998
+ }
1999
+ }
2000
+ }
2001
+ // Handle tool input delta events
2002
+ if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'input_json_delta') {
2003
+ const block = chunk.index !== undefined ? contentBlocks[chunk.index] : undefined;
2004
+ const toolId = block?.type === 'function' ? block.id : undefined;
2005
+ if (toolId && toolsInProgress.has(toolId)) {
2006
+ const tool = toolsInProgress.get(toolId);
2007
+ if (tool) {
2008
+ tool.inputString += chunk.delta.partial_json || '';
2009
+ }
2010
+ }
2011
+ }
2012
+ // Handle content block stop events
2013
+ if (chunk.type === 'content_block_stop') {
2014
+ currentTextBlock = null;
2015
+ // Parse accumulated tool input
2016
+ if (chunk.index !== undefined) {
2017
+ const block = contentBlocks[chunk.index];
2018
+ if (block?.type === 'function' && block.id && toolsInProgress.has(block.id)) {
2019
+ const tool = toolsInProgress.get(block.id);
2020
+ if (tool) {
2021
+ try {
2022
+ block.function.arguments = JSON.parse(tool.inputString);
2023
+ } catch (e) {
2024
+ // Keep empty object if parsing fails
2025
+ console.error('Error parsing tool input:', e);
2026
+ }
2027
+ }
2028
+ toolsInProgress.delete(block.id);
2029
+ }
1784
2030
  }
1785
2031
  }
1786
2032
  if (chunk.type == 'message_start') {
@@ -1794,6 +2040,17 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1794
2040
  }
1795
2041
  const latency = (Date.now() - startTime) / 1000;
1796
2042
  const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
2043
+ // Format output to match non-streaming version
2044
+ const formattedOutput = contentBlocks.length > 0 ? [{
2045
+ role: 'assistant',
2046
+ content: contentBlocks
2047
+ }] : [{
2048
+ role: 'assistant',
2049
+ content: [{
2050
+ type: 'text',
2051
+ text: accumulatedContent
2052
+ }]
2053
+ }];
1797
2054
  await sendEventToPosthog({
1798
2055
  client: this.phClient,
1799
2056
  distinctId: posthogDistinctId,
@@ -1801,10 +2058,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
1801
2058
  model: anthropicParams.model,
1802
2059
  provider: 'anthropic',
1803
2060
  input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, 'anthropic')),
1804
- output: [{
1805
- content: accumulatedContent,
1806
- role: 'assistant'
1807
- }],
2061
+ output: formattedOutput,
1808
2062
  latency,
1809
2063
  baseURL: this.baseURL ?? '',
1810
2064
  params: body,
@@ -1929,6 +2183,7 @@ class WrappedModels {
1929
2183
  const response = await this.client.models.generateContent(geminiParams);
1930
2184
  const latency = (Date.now() - startTime) / 1000;
1931
2185
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2186
+ const metadata = response.usageMetadata;
1932
2187
  await sendEventToPosthog({
1933
2188
  client: this.phClient,
1934
2189
  distinctId: posthogDistinctId,
@@ -1942,10 +2197,10 @@ class WrappedModels {
1942
2197
  params: params,
1943
2198
  httpStatus: 200,
1944
2199
  usage: {
1945
- inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
1946
- outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
1947
- reasoningTokens: response.usageMetadata?.thoughtsTokenCount ?? 0,
1948
- cacheReadInputTokens: response.usageMetadata?.cachedContentTokenCount ?? 0
2200
+ inputTokens: metadata?.promptTokenCount ?? 0,
2201
+ outputTokens: metadata?.candidatesTokenCount ?? 0,
2202
+ reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
2203
+ cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0
1949
2204
  },
1950
2205
  tools: availableTools,
1951
2206
  captureImmediate: posthogCaptureImmediate
@@ -1987,7 +2242,7 @@ class WrappedModels {
1987
2242
  } = params;
1988
2243
  const traceId = posthogTraceId ?? uuid.v4();
1989
2244
  const startTime = Date.now();
1990
- let accumulatedContent = '';
2245
+ const accumulatedContent = [];
1991
2246
  let usage = {
1992
2247
  inputTokens: 0,
1993
2248
  outputTokens: 0
@@ -1995,21 +2250,66 @@ class WrappedModels {
1995
2250
  try {
1996
2251
  const stream = await this.client.models.generateContentStream(geminiParams);
1997
2252
  for await (const chunk of stream) {
2253
+ // Handle text content
1998
2254
  if (chunk.text) {
1999
- accumulatedContent += chunk.text;
2255
+ // Find if we already have a text item to append to
2256
+ let lastTextItem;
2257
+ for (let i = accumulatedContent.length - 1; i >= 0; i--) {
2258
+ if (accumulatedContent[i].type === 'text') {
2259
+ lastTextItem = accumulatedContent[i];
2260
+ break;
2261
+ }
2262
+ }
2263
+ if (lastTextItem && lastTextItem.type === 'text') {
2264
+ lastTextItem.text += chunk.text;
2265
+ } else {
2266
+ accumulatedContent.push({
2267
+ type: 'text',
2268
+ text: chunk.text
2269
+ });
2270
+ }
2000
2271
  }
2272
+ // Handle function calls from candidates
2273
+ if (chunk.candidates && Array.isArray(chunk.candidates)) {
2274
+ for (const candidate of chunk.candidates) {
2275
+ if (candidate.content && candidate.content.parts) {
2276
+ for (const part of candidate.content.parts) {
2277
+ // Type-safe check for functionCall
2278
+ if ('functionCall' in part) {
2279
+ const funcCall = part.functionCall;
2280
+ if (funcCall?.name) {
2281
+ accumulatedContent.push({
2282
+ type: 'function',
2283
+ function: {
2284
+ name: funcCall.name,
2285
+ arguments: funcCall.args || {}
2286
+ }
2287
+ });
2288
+ }
2289
+ }
2290
+ }
2291
+ }
2292
+ }
2293
+ }
2294
+ // Update usage metadata - handle both old and new field names
2001
2295
  if (chunk.usageMetadata) {
2296
+ const metadata = chunk.usageMetadata;
2002
2297
  usage = {
2003
- inputTokens: chunk.usageMetadata.promptTokenCount ?? 0,
2004
- outputTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
2005
- reasoningTokens: chunk.usageMetadata.thoughtsTokenCount ?? 0,
2006
- cacheReadInputTokens: chunk.usageMetadata.cachedContentTokenCount ?? 0
2298
+ inputTokens: metadata.promptTokenCount ?? 0,
2299
+ outputTokens: metadata.candidatesTokenCount ?? 0,
2300
+ reasoningTokens: metadata.thoughtsTokenCount ?? 0,
2301
+ cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0
2007
2302
  };
2008
2303
  }
2009
2304
  yield chunk;
2010
2305
  }
2011
2306
  const latency = (Date.now() - startTime) / 1000;
2012
2307
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
2308
+ // Format output similar to formatResponseGemini
2309
+ const output = accumulatedContent.length > 0 ? [{
2310
+ role: 'assistant',
2311
+ content: accumulatedContent
2312
+ }] : [];
2013
2313
  await sendEventToPosthog({
2014
2314
  client: this.phClient,
2015
2315
  distinctId: posthogDistinctId,
@@ -2017,10 +2317,7 @@ class WrappedModels {
2017
2317
  model: geminiParams.model,
2018
2318
  provider: 'gemini',
2019
2319
  input: this.formatInputForPostHog(geminiParams.contents),
2020
- output: [{
2021
- content: accumulatedContent,
2022
- role: 'assistant'
2023
- }],
2320
+ output,
2024
2321
  latency,
2025
2322
  baseURL: 'https://generativelanguage.googleapis.com',
2026
2323
  params: params,
@@ -2070,22 +2367,28 @@ class WrappedModels {
2070
2367
  };
2071
2368
  }
2072
2369
  if (item && typeof item === 'object') {
2073
- if (item.text) {
2370
+ const obj = item;
2371
+ if ('text' in obj && obj.text) {
2074
2372
  return {
2075
- role: item.role || 'user',
2076
- content: item.text
2373
+ role: obj.role || 'user',
2374
+ content: obj.text
2077
2375
  };
2078
2376
  }
2079
- if (item.content) {
2377
+ if ('content' in obj && obj.content) {
2080
2378
  return {
2081
- role: item.role || 'user',
2082
- content: item.content
2379
+ role: obj.role || 'user',
2380
+ content: obj.content
2083
2381
  };
2084
2382
  }
2085
- if (item.parts) {
2383
+ if ('parts' in obj && Array.isArray(obj.parts)) {
2086
2384
  return {
2087
- role: item.role || 'user',
2088
- content: item.parts.map(part => part.text ? part.text : part)
2385
+ role: obj.role || 'user',
2386
+ content: obj.parts.map(part => {
2387
+ if (part && typeof part === 'object' && 'text' in part) {
2388
+ return part.text;
2389
+ }
2390
+ return part;
2391
+ })
2089
2392
  };
2090
2393
  }
2091
2394
  }
@@ -2096,16 +2399,17 @@ class WrappedModels {
2096
2399
  });
2097
2400
  }
2098
2401
  if (contents && typeof contents === 'object') {
2099
- if (contents.text) {
2402
+ const obj = contents;
2403
+ if ('text' in obj && obj.text) {
2100
2404
  return [{
2101
2405
  role: 'user',
2102
- content: contents.text
2406
+ content: obj.text
2103
2407
  }];
2104
2408
  }
2105
- if (contents.content) {
2409
+ if ('content' in obj && obj.content) {
2106
2410
  return [{
2107
2411
  role: 'user',
2108
- content: contents.content
2412
+ content: obj.content
2109
2413
  }];
2110
2414
  }
2111
2415
  }