@posthog/ai 7.4.0 → 7.4.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.
@@ -2,7 +2,7 @@ import { v4 } from 'uuid';
2
2
  import { Buffer } from 'buffer';
3
3
  import { uuidv7 } from '@posthog/core';
4
4
 
5
- var version = "7.4.0";
5
+ var version = "7.4.2";
6
6
 
7
7
  // Type guards for safer type checking
8
8
 
@@ -411,6 +411,13 @@ function redactBase64DataUrl(str) {
411
411
  return str;
412
412
  }
413
413
 
414
+ // Union types for dual version support
415
+
416
+ // Type guards
417
+ function isV3Model(model) {
418
+ return model.specificationVersion === 'v3';
419
+ }
420
+
414
421
  // Content types for the output array
415
422
 
416
423
  const mapVercelParams = params => {
@@ -639,6 +646,20 @@ const extractAdditionalTokenValues = providerMetadata => {
639
646
  return {};
640
647
  };
641
648
 
649
+ // For Anthropic providers in V3, inputTokens.total is the sum of all tokens (uncached + cache read + cache write).
650
+ // Our cost calculation expects inputTokens to be only the uncached portion for Anthropic.
651
+ // This helper subtracts cache tokens from inputTokens for Anthropic V3 models.
652
+ const adjustAnthropicV3CacheTokens = (model, provider, usage) => {
653
+ if (isV3Model(model) && provider.toLowerCase().includes('anthropic')) {
654
+ const cacheReadTokens = usage.cacheReadInputTokens || 0;
655
+ const cacheWriteTokens = usage.cacheCreationInputTokens || 0;
656
+ const cacheTokens = cacheReadTokens + cacheWriteTokens;
657
+ if (usage.inputTokens && cacheTokens > 0) {
658
+ usage.inputTokens = Math.max(usage.inputTokens - cacheTokens, 0);
659
+ }
660
+ }
661
+ };
662
+
642
663
  // Helper to extract numeric token value from V2 (number) or V3 (object with .total) usage formats
643
664
  const extractTokenCount = value => {
644
665
  if (typeof value === 'number') {
@@ -693,240 +714,252 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
693
714
  }
694
715
  };
695
716
 
696
- // Create wrapped model that preserves the original type
697
- const wrappedModel = {
698
- ...model,
699
- doGenerate: async params => {
700
- const startTime = Date.now();
701
- const mergedParams = {
702
- ...mergedOptions,
703
- ...mapVercelParams(params)
704
- };
705
- const availableTools = extractAvailableToolCalls('vercel', params);
706
- try {
707
- const result = await model.doGenerate(params);
708
- const modelId = mergedOptions.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
709
- const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
710
- const baseURL = ''; // cannot currently get baseURL from vercel
711
- const content = mapVercelOutput(result.content);
712
- const latency = (Date.now() - startTime) / 1000;
713
- const providerMetadata = result.providerMetadata;
714
- const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
715
- const webSearchCount = extractWebSearchCount(providerMetadata, result.usage);
716
-
717
- // V2 usage has simple numbers, V3 has objects with .total - normalize both
718
- const usageObj = result.usage;
719
- const usage = {
720
- inputTokens: extractTokenCount(result.usage.inputTokens),
721
- outputTokens: extractTokenCount(result.usage.outputTokens),
722
- reasoningTokens: extractReasoningTokens(usageObj),
723
- cacheReadInputTokens: extractCacheReadTokens(usageObj),
724
- webSearchCount,
725
- ...additionalTokenValues
717
+ // Create wrapped model using Object.create to preserve the prototype chain
718
+ // This automatically inherits all properties (including getters) from the model
719
+ const wrappedModel = Object.create(model, {
720
+ doGenerate: {
721
+ value: async params => {
722
+ const startTime = Date.now();
723
+ const mergedParams = {
724
+ ...mergedOptions,
725
+ ...mapVercelParams(params)
726
726
  };
727
- await sendEventToPosthog({
728
- client: phClient,
729
- distinctId: mergedOptions.posthogDistinctId,
730
- traceId: mergedOptions.posthogTraceId ?? v4(),
731
- model: modelId,
732
- provider: provider,
733
- input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
734
- output: content,
735
- latency,
736
- baseURL,
737
- params: mergedParams,
738
- httpStatus: 200,
739
- usage,
740
- tools: availableTools,
741
- captureImmediate: mergedOptions.posthogCaptureImmediate
742
- });
743
- return result;
744
- } catch (error) {
745
- const modelId = model.modelId;
746
- const enrichedError = await sendEventWithErrorToPosthog({
747
- client: phClient,
748
- distinctId: mergedOptions.posthogDistinctId,
749
- traceId: mergedOptions.posthogTraceId ?? v4(),
750
- model: modelId,
751
- provider: model.provider,
752
- input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
753
- output: [],
754
- latency: 0,
755
- baseURL: '',
756
- params: mergedParams,
757
- usage: {
758
- inputTokens: 0,
759
- outputTokens: 0
760
- },
761
- error: error,
762
- tools: availableTools,
763
- captureImmediate: mergedOptions.posthogCaptureImmediate
764
- });
765
- throw enrichedError;
766
- }
727
+ const availableTools = extractAvailableToolCalls('vercel', params);
728
+ try {
729
+ const result = await model.doGenerate(params);
730
+ const modelId = mergedOptions.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
731
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
732
+ const baseURL = ''; // cannot currently get baseURL from vercel
733
+ const content = mapVercelOutput(result.content);
734
+ const latency = (Date.now() - startTime) / 1000;
735
+ const providerMetadata = result.providerMetadata;
736
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
737
+ const webSearchCount = extractWebSearchCount(providerMetadata, result.usage);
738
+
739
+ // V2 usage has simple numbers, V3 has objects with .total - normalize both
740
+ const usageObj = result.usage;
741
+ const usage = {
742
+ inputTokens: extractTokenCount(result.usage.inputTokens),
743
+ outputTokens: extractTokenCount(result.usage.outputTokens),
744
+ reasoningTokens: extractReasoningTokens(usageObj),
745
+ cacheReadInputTokens: extractCacheReadTokens(usageObj),
746
+ webSearchCount,
747
+ ...additionalTokenValues
748
+ };
749
+ adjustAnthropicV3CacheTokens(model, provider, usage);
750
+ await sendEventToPosthog({
751
+ client: phClient,
752
+ distinctId: mergedOptions.posthogDistinctId,
753
+ traceId: mergedOptions.posthogTraceId ?? v4(),
754
+ model: modelId,
755
+ provider: provider,
756
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
757
+ output: content,
758
+ latency,
759
+ baseURL,
760
+ params: mergedParams,
761
+ httpStatus: 200,
762
+ usage,
763
+ tools: availableTools,
764
+ captureImmediate: mergedOptions.posthogCaptureImmediate
765
+ });
766
+ return result;
767
+ } catch (error) {
768
+ const modelId = model.modelId;
769
+ const enrichedError = await sendEventWithErrorToPosthog({
770
+ client: phClient,
771
+ distinctId: mergedOptions.posthogDistinctId,
772
+ traceId: mergedOptions.posthogTraceId ?? v4(),
773
+ model: modelId,
774
+ provider: model.provider,
775
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
776
+ output: [],
777
+ latency: 0,
778
+ baseURL: '',
779
+ params: mergedParams,
780
+ usage: {
781
+ inputTokens: 0,
782
+ outputTokens: 0
783
+ },
784
+ error: error,
785
+ tools: availableTools,
786
+ captureImmediate: mergedOptions.posthogCaptureImmediate
787
+ });
788
+ throw enrichedError;
789
+ }
790
+ },
791
+ writable: true,
792
+ configurable: true,
793
+ enumerable: false
767
794
  },
768
- doStream: async params => {
769
- const startTime = Date.now();
770
- let generatedText = '';
771
- let reasoningText = '';
772
- let usage = {};
773
- let providerMetadata = undefined;
774
- const mergedParams = {
775
- ...mergedOptions,
776
- ...mapVercelParams(params)
777
- };
778
- const modelId = mergedOptions.posthogModelOverride ?? model.modelId;
779
- const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
780
- const availableTools = extractAvailableToolCalls('vercel', params);
781
- const baseURL = ''; // cannot currently get baseURL from vercel
782
-
783
- // Map to track in-progress tool calls
784
- const toolCallsInProgress = new Map();
785
- try {
786
- const {
787
- stream,
788
- ...rest
789
- } = await model.doStream(params);
790
- const transformStream = new TransformStream({
791
- transform(chunk, controller) {
792
- // Handle streaming patterns - compatible with both V2 and V3
793
- if (chunk.type === 'text-delta') {
794
- generatedText += chunk.delta;
795
- }
796
- if (chunk.type === 'reasoning-delta') {
797
- reasoningText += chunk.delta;
798
- }
795
+ doStream: {
796
+ value: async params => {
797
+ const startTime = Date.now();
798
+ let generatedText = '';
799
+ let reasoningText = '';
800
+ let usage = {};
801
+ let providerMetadata = undefined;
802
+ const mergedParams = {
803
+ ...mergedOptions,
804
+ ...mapVercelParams(params)
805
+ };
806
+ const modelId = mergedOptions.posthogModelOverride ?? model.modelId;
807
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
808
+ const availableTools = extractAvailableToolCalls('vercel', params);
809
+ const baseURL = ''; // cannot currently get baseURL from vercel
799
810
 
800
- // Handle tool call chunks
801
- if (chunk.type === 'tool-input-start') {
802
- // Initialize a new tool call
803
- toolCallsInProgress.set(chunk.id, {
804
- toolCallId: chunk.id,
805
- toolName: chunk.toolName,
806
- input: ''
807
- });
808
- }
809
- if (chunk.type === 'tool-input-delta') {
810
- // Accumulate tool call arguments
811
- const toolCall = toolCallsInProgress.get(chunk.id);
812
- if (toolCall) {
813
- toolCall.input += chunk.delta;
811
+ // Map to track in-progress tool calls
812
+ const toolCallsInProgress = new Map();
813
+ try {
814
+ const {
815
+ stream,
816
+ ...rest
817
+ } = await model.doStream(params);
818
+ const transformStream = new TransformStream({
819
+ transform(chunk, controller) {
820
+ // Handle streaming patterns - compatible with both V2 and V3
821
+ if (chunk.type === 'text-delta') {
822
+ generatedText += chunk.delta;
823
+ }
824
+ if (chunk.type === 'reasoning-delta') {
825
+ reasoningText += chunk.delta;
814
826
  }
815
- }
816
- if (chunk.type === 'tool-input-end') {
817
- // Tool call is complete, keep it in the map for final processing
818
- }
819
- if (chunk.type === 'tool-call') {
820
- // Direct tool call chunk (complete tool call)
821
- toolCallsInProgress.set(chunk.toolCallId, {
822
- toolCallId: chunk.toolCallId,
823
- toolName: chunk.toolName,
824
- input: chunk.input
825
- });
826
- }
827
- if (chunk.type === 'finish') {
828
- providerMetadata = chunk.providerMetadata;
829
- const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
830
- const chunkUsage = chunk.usage || {};
831
- usage = {
832
- inputTokens: extractTokenCount(chunk.usage?.inputTokens),
833
- outputTokens: extractTokenCount(chunk.usage?.outputTokens),
834
- reasoningTokens: extractReasoningTokens(chunkUsage),
835
- cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
836
- ...additionalTokenValues
837
- };
838
- }
839
- controller.enqueue(chunk);
840
- },
841
- flush: async () => {
842
- const latency = (Date.now() - startTime) / 1000;
843
- // Build content array similar to mapVercelOutput structure
844
- const content = [];
845
- if (reasoningText) {
846
- content.push({
847
- type: 'reasoning',
848
- text: truncate(reasoningText)
849
- });
850
- }
851
- if (generatedText) {
852
- content.push({
853
- type: 'text',
854
- text: truncate(generatedText)
855
- });
856
- }
857
827
 
858
- // Add completed tool calls to content
859
- for (const toolCall of toolCallsInProgress.values()) {
860
- if (toolCall.toolName) {
828
+ // Handle tool call chunks
829
+ if (chunk.type === 'tool-input-start') {
830
+ // Initialize a new tool call
831
+ toolCallsInProgress.set(chunk.id, {
832
+ toolCallId: chunk.id,
833
+ toolName: chunk.toolName,
834
+ input: ''
835
+ });
836
+ }
837
+ if (chunk.type === 'tool-input-delta') {
838
+ // Accumulate tool call arguments
839
+ const toolCall = toolCallsInProgress.get(chunk.id);
840
+ if (toolCall) {
841
+ toolCall.input += chunk.delta;
842
+ }
843
+ }
844
+ if (chunk.type === 'tool-input-end') {
845
+ // Tool call is complete, keep it in the map for final processing
846
+ }
847
+ if (chunk.type === 'tool-call') {
848
+ // Direct tool call chunk (complete tool call)
849
+ toolCallsInProgress.set(chunk.toolCallId, {
850
+ toolCallId: chunk.toolCallId,
851
+ toolName: chunk.toolName,
852
+ input: chunk.input
853
+ });
854
+ }
855
+ if (chunk.type === 'finish') {
856
+ providerMetadata = chunk.providerMetadata;
857
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
858
+ const chunkUsage = chunk.usage || {};
859
+ usage = {
860
+ inputTokens: extractTokenCount(chunk.usage?.inputTokens),
861
+ outputTokens: extractTokenCount(chunk.usage?.outputTokens),
862
+ reasoningTokens: extractReasoningTokens(chunkUsage),
863
+ cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
864
+ ...additionalTokenValues
865
+ };
866
+ }
867
+ controller.enqueue(chunk);
868
+ },
869
+ flush: async () => {
870
+ const latency = (Date.now() - startTime) / 1000;
871
+ // Build content array similar to mapVercelOutput structure
872
+ const content = [];
873
+ if (reasoningText) {
861
874
  content.push({
862
- type: 'tool-call',
863
- id: toolCall.toolCallId,
864
- function: {
865
- name: toolCall.toolName,
866
- arguments: toolCall.input
867
- }
875
+ type: 'reasoning',
876
+ text: truncate(reasoningText)
877
+ });
878
+ }
879
+ if (generatedText) {
880
+ content.push({
881
+ type: 'text',
882
+ text: truncate(generatedText)
868
883
  });
869
884
  }
870
- }
871
885
 
872
- // Structure output like mapVercelOutput does
873
- const output = content.length > 0 ? [{
874
- role: 'assistant',
875
- content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
876
- }] : [];
877
- const webSearchCount = extractWebSearchCount(providerMetadata, usage);
878
-
879
- // Update usage with web search count
880
- const finalUsage = {
881
- ...usage,
882
- webSearchCount
883
- };
884
- await sendEventToPosthog({
885
- client: phClient,
886
- distinctId: mergedOptions.posthogDistinctId,
887
- traceId: mergedOptions.posthogTraceId ?? v4(),
888
- model: modelId,
889
- provider: provider,
890
- input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
891
- output: output,
892
- latency,
893
- baseURL,
894
- params: mergedParams,
895
- httpStatus: 200,
896
- usage: finalUsage,
897
- tools: availableTools,
898
- captureImmediate: mergedOptions.posthogCaptureImmediate
899
- });
900
- }
901
- });
902
- return {
903
- stream: stream.pipeThrough(transformStream),
904
- ...rest
905
- };
906
- } catch (error) {
907
- const enrichedError = await sendEventWithErrorToPosthog({
908
- client: phClient,
909
- distinctId: mergedOptions.posthogDistinctId,
910
- traceId: mergedOptions.posthogTraceId ?? v4(),
911
- model: modelId,
912
- provider: provider,
913
- input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
914
- output: [],
915
- latency: 0,
916
- baseURL: '',
917
- params: mergedParams,
918
- usage: {
919
- inputTokens: 0,
920
- outputTokens: 0
921
- },
922
- error: error,
923
- tools: availableTools,
924
- captureImmediate: mergedOptions.posthogCaptureImmediate
925
- });
926
- throw enrichedError;
927
- }
886
+ // Add completed tool calls to content
887
+ for (const toolCall of toolCallsInProgress.values()) {
888
+ if (toolCall.toolName) {
889
+ content.push({
890
+ type: 'tool-call',
891
+ id: toolCall.toolCallId,
892
+ function: {
893
+ name: toolCall.toolName,
894
+ arguments: toolCall.input
895
+ }
896
+ });
897
+ }
898
+ }
899
+
900
+ // Structure output like mapVercelOutput does
901
+ const output = content.length > 0 ? [{
902
+ role: 'assistant',
903
+ content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
904
+ }] : [];
905
+ const webSearchCount = extractWebSearchCount(providerMetadata, usage);
906
+
907
+ // Update usage with web search count
908
+ const finalUsage = {
909
+ ...usage,
910
+ webSearchCount
911
+ };
912
+ adjustAnthropicV3CacheTokens(model, provider, finalUsage);
913
+ await sendEventToPosthog({
914
+ client: phClient,
915
+ distinctId: mergedOptions.posthogDistinctId,
916
+ traceId: mergedOptions.posthogTraceId ?? v4(),
917
+ model: modelId,
918
+ provider: provider,
919
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
920
+ output: output,
921
+ latency,
922
+ baseURL,
923
+ params: mergedParams,
924
+ httpStatus: 200,
925
+ usage: finalUsage,
926
+ tools: availableTools,
927
+ captureImmediate: mergedOptions.posthogCaptureImmediate
928
+ });
929
+ }
930
+ });
931
+ return {
932
+ stream: stream.pipeThrough(transformStream),
933
+ ...rest
934
+ };
935
+ } catch (error) {
936
+ const enrichedError = await sendEventWithErrorToPosthog({
937
+ client: phClient,
938
+ distinctId: mergedOptions.posthogDistinctId,
939
+ traceId: mergedOptions.posthogTraceId ?? v4(),
940
+ model: modelId,
941
+ provider: provider,
942
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
943
+ output: [],
944
+ latency: 0,
945
+ baseURL: '',
946
+ params: mergedParams,
947
+ usage: {
948
+ inputTokens: 0,
949
+ outputTokens: 0
950
+ },
951
+ error: error,
952
+ tools: availableTools,
953
+ captureImmediate: mergedOptions.posthogCaptureImmediate
954
+ });
955
+ throw enrichedError;
956
+ }
957
+ },
958
+ writable: true,
959
+ configurable: true,
960
+ enumerable: false
928
961
  }
929
- };
962
+ });
930
963
  return wrappedModel;
931
964
  };
932
965