@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.
@@ -1,10 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var ai = require('ai');
4
3
  var uuid = require('uuid');
5
4
  var buffer = require('buffer');
6
5
 
7
- var version = "7.2.2";
6
+ var version = "7.3.1";
8
7
 
9
8
  // Type guards for safer type checking
10
9
 
@@ -584,68 +583,126 @@ const extractProvider = model => {
584
583
  const providerName = provider.split('.')[0];
585
584
  return providerName;
586
585
  };
587
- const createInstrumentationMiddleware = (phClient, model, options) => {
588
- const middleware = {
589
- wrapGenerate: async ({
590
- doGenerate,
591
- params
592
- }) => {
586
+
587
+ // Extract web search count from provider metadata (works for both V2 and V3)
588
+ const extractWebSearchCount = (providerMetadata, usage) => {
589
+ // Try Anthropic-specific extraction
590
+ if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
591
+ const serverToolUse = providerMetadata.anthropic.server_tool_use;
592
+ if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
593
+ return serverToolUse.web_search_requests;
594
+ }
595
+ }
596
+
597
+ // Fall back to generic calculation
598
+ return calculateWebSearchCount({
599
+ usage,
600
+ providerMetadata
601
+ });
602
+ };
603
+
604
+ // Extract additional token values from provider metadata
605
+ const extractAdditionalTokenValues = providerMetadata => {
606
+ if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic) {
607
+ return {
608
+ cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
609
+ };
610
+ }
611
+ return {};
612
+ };
613
+
614
+ // Helper to extract numeric token value from V2 (number) or V3 (object with .total) usage formats
615
+ const extractTokenCount = value => {
616
+ if (typeof value === 'number') {
617
+ return value;
618
+ }
619
+ if (value && typeof value === 'object' && 'total' in value && typeof value.total === 'number') {
620
+ return value.total;
621
+ }
622
+ return undefined;
623
+ };
624
+
625
+ // Helper to extract reasoning tokens from V2 (usage.reasoningTokens) or V3 (usage.outputTokens.reasoning)
626
+ const extractReasoningTokens = usage => {
627
+ // V2 style: top-level reasoningTokens
628
+ if ('reasoningTokens' in usage) {
629
+ return usage.reasoningTokens;
630
+ }
631
+ // V3 style: nested in outputTokens.reasoning
632
+ if ('outputTokens' in usage && usage.outputTokens && typeof usage.outputTokens === 'object' && 'reasoning' in usage.outputTokens) {
633
+ return usage.outputTokens.reasoning;
634
+ }
635
+ return undefined;
636
+ };
637
+
638
+ // Helper to extract cached input tokens from V2 (usage.cachedInputTokens) or V3 (usage.inputTokens.cacheRead)
639
+ const extractCacheReadTokens = usage => {
640
+ // V2 style: top-level cachedInputTokens
641
+ if ('cachedInputTokens' in usage) {
642
+ return usage.cachedInputTokens;
643
+ }
644
+ // V3 style: nested in inputTokens.cacheRead
645
+ if ('inputTokens' in usage && usage.inputTokens && typeof usage.inputTokens === 'object' && 'cacheRead' in usage.inputTokens) {
646
+ return usage.inputTokens.cacheRead;
647
+ }
648
+ return undefined;
649
+ };
650
+
651
+ /**
652
+ * Wraps a Vercel AI SDK language model (V2 or V3) with PostHog tracing.
653
+ * Automatically detects the model version and applies appropriate instrumentation.
654
+ */
655
+ const wrapVercelLanguageModel = (model, phClient, options) => {
656
+ const traceId = options.posthogTraceId ?? uuid.v4();
657
+ const mergedOptions = {
658
+ ...options,
659
+ posthogTraceId: traceId,
660
+ posthogDistinctId: options.posthogDistinctId,
661
+ posthogProperties: {
662
+ ...options.posthogProperties,
663
+ $ai_framework: 'vercel',
664
+ $ai_framework_version: model.specificationVersion === 'v3' ? '6' : '5'
665
+ }
666
+ };
667
+
668
+ // Create wrapped model that preserves the original type
669
+ const wrappedModel = {
670
+ ...model,
671
+ doGenerate: async params => {
593
672
  const startTime = Date.now();
594
673
  const mergedParams = {
595
- ...options,
596
- ...mapVercelParams(params),
597
- posthogProperties: {
598
- ...options.posthogProperties,
599
- $ai_framework: 'vercel'
600
- }
674
+ ...mergedOptions,
675
+ ...mapVercelParams(params)
601
676
  };
602
677
  const availableTools = extractAvailableToolCalls('vercel', params);
603
678
  try {
604
- const result = await doGenerate();
605
- const modelId = options.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
606
- const provider = options.posthogProviderOverride ?? extractProvider(model);
679
+ const result = await model.doGenerate(params);
680
+ const modelId = mergedOptions.posthogModelOverride ?? (result.response?.modelId ? result.response.modelId : model.modelId);
681
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
607
682
  const baseURL = ''; // cannot currently get baseURL from vercel
608
683
  const content = mapVercelOutput(result.content);
609
684
  const latency = (Date.now() - startTime) / 1000;
610
685
  const providerMetadata = result.providerMetadata;
611
- const additionalTokenValues = {
612
- ...(providerMetadata?.anthropic ? {
613
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
614
- } : {})
615
- };
686
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
687
+ const webSearchCount = extractWebSearchCount(providerMetadata, result.usage);
616
688
 
617
- // Calculate web search count based on provider
618
- let webSearchCount = 0;
619
- if (providerMetadata?.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
620
- // Anthropic-specific extraction
621
- const serverToolUse = providerMetadata.anthropic.server_tool_use;
622
- if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
623
- webSearchCount = serverToolUse.web_search_requests;
624
- }
625
- } else {
626
- // For other providers through Vercel, pass available metadata to helper
627
- // Note: Vercel abstracts provider responses, so we may not have access to
628
- // raw citations/annotations unless Vercel exposes them in usage/metadata
629
- webSearchCount = calculateWebSearchCount({
630
- usage: result.usage,
631
- providerMetadata: providerMetadata
632
- });
633
- }
689
+ // V2 usage has simple numbers, V3 has objects with .total - normalize both
690
+ const usageObj = result.usage;
634
691
  const usage = {
635
- inputTokens: result.usage.inputTokens,
636
- outputTokens: result.usage.outputTokens,
637
- reasoningTokens: result.usage.reasoningTokens,
638
- cacheReadInputTokens: result.usage.cachedInputTokens,
692
+ inputTokens: extractTokenCount(result.usage.inputTokens),
693
+ outputTokens: extractTokenCount(result.usage.outputTokens),
694
+ reasoningTokens: extractReasoningTokens(usageObj),
695
+ cacheReadInputTokens: extractCacheReadTokens(usageObj),
639
696
  webSearchCount,
640
697
  ...additionalTokenValues
641
698
  };
642
699
  await sendEventToPosthog({
643
700
  client: phClient,
644
- distinctId: options.posthogDistinctId,
645
- traceId: options.posthogTraceId ?? uuid.v4(),
701
+ distinctId: mergedOptions.posthogDistinctId,
702
+ traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
646
703
  model: modelId,
647
704
  provider: provider,
648
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
705
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
649
706
  output: content,
650
707
  latency,
651
708
  baseURL,
@@ -653,18 +710,18 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
653
710
  httpStatus: 200,
654
711
  usage,
655
712
  tools: availableTools,
656
- captureImmediate: options.posthogCaptureImmediate
713
+ captureImmediate: mergedOptions.posthogCaptureImmediate
657
714
  });
658
715
  return result;
659
716
  } catch (error) {
660
717
  const modelId = model.modelId;
661
718
  await sendEventToPosthog({
662
719
  client: phClient,
663
- distinctId: options.posthogDistinctId,
664
- traceId: options.posthogTraceId ?? uuid.v4(),
720
+ distinctId: mergedOptions.posthogDistinctId,
721
+ traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
665
722
  model: modelId,
666
723
  provider: model.provider,
667
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
724
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
668
725
  output: [],
669
726
  latency: 0,
670
727
  baseURL: '',
@@ -677,30 +734,23 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
677
734
  isError: true,
678
735
  error: truncate(JSON.stringify(error)),
679
736
  tools: availableTools,
680
- captureImmediate: options.posthogCaptureImmediate
737
+ captureImmediate: mergedOptions.posthogCaptureImmediate
681
738
  });
682
739
  throw error;
683
740
  }
684
741
  },
685
- wrapStream: async ({
686
- doStream,
687
- params
688
- }) => {
742
+ doStream: async params => {
689
743
  const startTime = Date.now();
690
744
  let generatedText = '';
691
745
  let reasoningText = '';
692
746
  let usage = {};
693
747
  let providerMetadata = undefined;
694
748
  const mergedParams = {
695
- ...options,
696
- ...mapVercelParams(params),
697
- posthogProperties: {
698
- ...options.posthogProperties,
699
- $ai_framework: 'vercel'
700
- }
749
+ ...mergedOptions,
750
+ ...mapVercelParams(params)
701
751
  };
702
- const modelId = options.posthogModelOverride ?? model.modelId;
703
- const provider = options.posthogProviderOverride ?? extractProvider(model);
752
+ const modelId = mergedOptions.posthogModelOverride ?? model.modelId;
753
+ const provider = mergedOptions.posthogProviderOverride ?? extractProvider(model);
704
754
  const availableTools = extractAvailableToolCalls('vercel', params);
705
755
  const baseURL = ''; // cannot currently get baseURL from vercel
706
756
 
@@ -710,15 +760,15 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
710
760
  const {
711
761
  stream,
712
762
  ...rest
713
- } = await doStream();
763
+ } = await model.doStream(params);
714
764
  const transformStream = new TransformStream({
715
765
  transform(chunk, controller) {
716
- // Handle new v5 streaming patterns
766
+ // Handle streaming patterns - compatible with both V2 and V3
717
767
  if (chunk.type === 'text-delta') {
718
768
  generatedText += chunk.delta;
719
769
  }
720
770
  if (chunk.type === 'reasoning-delta') {
721
- reasoningText += chunk.delta; // New in v5
771
+ reasoningText += chunk.delta;
722
772
  }
723
773
 
724
774
  // Handle tool call chunks
@@ -739,7 +789,6 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
739
789
  }
740
790
  if (chunk.type === 'tool-input-end') {
741
791
  // Tool call is complete, keep it in the map for final processing
742
- // Nothing specific to do here, the tool call is already complete
743
792
  }
744
793
  if (chunk.type === 'tool-call') {
745
794
  // Direct tool call chunk (complete tool call)
@@ -751,14 +800,13 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
751
800
  }
752
801
  if (chunk.type === 'finish') {
753
802
  providerMetadata = chunk.providerMetadata;
754
- const additionalTokenValues = providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic ? {
755
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
756
- } : {};
803
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
804
+ const chunkUsage = chunk.usage || {};
757
805
  usage = {
758
- inputTokens: chunk.usage?.inputTokens,
759
- outputTokens: chunk.usage?.outputTokens,
760
- reasoningTokens: chunk.usage?.reasoningTokens,
761
- cacheReadInputTokens: chunk.usage?.cachedInputTokens,
806
+ inputTokens: extractTokenCount(chunk.usage?.inputTokens),
807
+ outputTokens: extractTokenCount(chunk.usage?.outputTokens),
808
+ reasoningTokens: extractReasoningTokens(chunkUsage),
809
+ cacheReadInputTokens: extractCacheReadTokens(chunkUsage),
762
810
  ...additionalTokenValues
763
811
  };
764
812
  }
@@ -800,24 +848,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
800
848
  role: 'assistant',
801
849
  content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
802
850
  }] : [];
803
-
804
- // Calculate web search count based on provider
805
- let webSearchCount = 0;
806
- if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
807
- // Anthropic-specific extraction
808
- const serverToolUse = providerMetadata.anthropic.server_tool_use;
809
- if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
810
- webSearchCount = serverToolUse.web_search_requests;
811
- }
812
- } else {
813
- // For other providers through Vercel, pass available metadata to helper
814
- // Note: Vercel abstracts provider responses, so we may not have access to
815
- // raw citations/annotations unless Vercel exposes them in usage/metadata
816
- webSearchCount = calculateWebSearchCount({
817
- usage: usage,
818
- providerMetadata: providerMetadata
819
- });
820
- }
851
+ const webSearchCount = extractWebSearchCount(providerMetadata, usage);
821
852
 
822
853
  // Update usage with web search count
823
854
  const finalUsage = {
@@ -826,11 +857,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
826
857
  };
827
858
  await sendEventToPosthog({
828
859
  client: phClient,
829
- distinctId: options.posthogDistinctId,
830
- traceId: options.posthogTraceId ?? uuid.v4(),
860
+ distinctId: mergedOptions.posthogDistinctId,
861
+ traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
831
862
  model: modelId,
832
863
  provider: provider,
833
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
864
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
834
865
  output: output,
835
866
  latency,
836
867
  baseURL,
@@ -838,7 +869,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
838
869
  httpStatus: 200,
839
870
  usage: finalUsage,
840
871
  tools: availableTools,
841
- captureImmediate: options.posthogCaptureImmediate
872
+ captureImmediate: mergedOptions.posthogCaptureImmediate
842
873
  });
843
874
  }
844
875
  });
@@ -849,11 +880,11 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
849
880
  } catch (error) {
850
881
  await sendEventToPosthog({
851
882
  client: phClient,
852
- distinctId: options.posthogDistinctId,
853
- traceId: options.posthogTraceId ?? uuid.v4(),
883
+ distinctId: mergedOptions.posthogDistinctId,
884
+ traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
854
885
  model: modelId,
855
886
  provider: provider,
856
- input: options.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
887
+ input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
857
888
  output: [],
858
889
  latency: 0,
859
890
  baseURL: '',
@@ -866,25 +897,12 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
866
897
  isError: true,
867
898
  error: truncate(JSON.stringify(error)),
868
899
  tools: availableTools,
869
- captureImmediate: options.posthogCaptureImmediate
900
+ captureImmediate: mergedOptions.posthogCaptureImmediate
870
901
  });
871
902
  throw error;
872
903
  }
873
904
  }
874
905
  };
875
- return middleware;
876
- };
877
- const wrapVercelLanguageModel = (model, phClient, options) => {
878
- const traceId = options.posthogTraceId ?? uuid.v4();
879
- const middleware = createInstrumentationMiddleware(phClient, model, {
880
- ...options,
881
- posthogTraceId: traceId,
882
- posthogDistinctId: options.posthogDistinctId
883
- });
884
- const wrappedModel = ai.wrapLanguageModel({
885
- model,
886
- middleware
887
- });
888
906
  return wrappedModel;
889
907
  };
890
908