@posthog/ai 7.16.2 → 7.16.4

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,7 +1,7 @@
1
1
  import { v4 } from 'uuid';
2
2
  import { uuidv7 } from '@posthog/core';
3
3
 
4
- var version = "7.16.2";
4
+ var version = "7.16.4";
5
5
 
6
6
  // Type guards for safer type checking
7
7
 
@@ -659,30 +659,6 @@ const extractWebSearchCount = (providerMetadata, usage) => {
659
659
  });
660
660
  };
661
661
 
662
- // Extract additional token values from provider metadata
663
- const extractAdditionalTokenValues = providerMetadata => {
664
- if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic) {
665
- return {
666
- cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
667
- };
668
- }
669
- return {};
670
- };
671
-
672
- // For Anthropic providers in V3, inputTokens.total is the sum of all tokens (uncached + cache read + cache write).
673
- // Our cost calculation expects inputTokens to be only the uncached portion for Anthropic.
674
- // This helper subtracts cache tokens from inputTokens for Anthropic V3 models.
675
- const adjustAnthropicV3CacheTokens = (model, provider, usage) => {
676
- if (isV3Model(model) && provider.toLowerCase().includes('anthropic')) {
677
- const cacheReadTokens = usage.cacheReadInputTokens || 0;
678
- const cacheWriteTokens = usage.cacheCreationInputTokens || 0;
679
- const cacheTokens = cacheReadTokens + cacheWriteTokens;
680
- if (usage.inputTokens && cacheTokens > 0) {
681
- usage.inputTokens = Math.max(usage.inputTokens - cacheTokens, 0);
682
- }
683
- }
684
- };
685
-
686
662
  // Helper to extract numeric token value from V2 (number) or V3 (object with .total) usage formats
687
663
  const extractTokenCount = value => {
688
664
  if (typeof value === 'number') {
@@ -720,6 +696,63 @@ const extractCacheReadTokens = usage => {
720
696
  return undefined;
721
697
  };
722
698
 
699
+ // Helper to extract cache write tokens from V3 (usage.inputTokens.cacheWrite). Providers like
700
+ // Amazon Bedrock populate this standardized field instead of providerMetadata.anthropic.
701
+ const extractCacheWriteTokens = usage => {
702
+ if ('inputTokens' in usage && usage.inputTokens && typeof usage.inputTokens === 'object' && 'cacheWrite' in usage.inputTokens) {
703
+ return usage.inputTokens.cacheWrite;
704
+ }
705
+ return undefined;
706
+ };
707
+
708
+ // Extract additional token values from provider metadata, with a V3 standardized fallback
709
+ // (e.g. Amazon Bedrock exposes cache write tokens via usage.inputTokens.cacheWrite rather
710
+ // than providerMetadata.anthropic.cacheCreationInputTokens). A cacheWrite of 0 is treated
711
+ // as absent so we preserve the pre-fallback event shape on providers that simply omit the
712
+ // field — consumers downstream saw `$ai_cache_creation_input_tokens` missing, not 0.
713
+ const extractAdditionalTokenValues = (providerMetadata, usage) => {
714
+ if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic) {
715
+ return {
716
+ cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
717
+ };
718
+ }
719
+ if (usage && typeof usage === 'object') {
720
+ const cacheWrite = extractCacheWriteTokens(usage);
721
+ if (typeof cacheWrite === 'number' && cacheWrite > 0) {
722
+ return {
723
+ cacheCreationInputTokens: cacheWrite
724
+ };
725
+ }
726
+ }
727
+ return {};
728
+ };
729
+
730
+ // Detects Anthropic Claude regardless of host (direct Anthropic, Amazon Bedrock, Google Vertex, etc.).
731
+ // The server applies exclusive cache token accounting based on the model name, so any Claude model
732
+ // needs its V3 input tokens adjusted to exclude cache tokens — not just those routed through a
733
+ // provider whose name contains "anthropic". Accepts the resolved modelId string (not the raw model)
734
+ // so it sees the same id the server does after posthogModelOverride / response.modelId fallbacks.
735
+ const isAnthropicClaudeModel = (modelId, provider) => {
736
+ if (provider.toLowerCase().includes('anthropic')) {
737
+ return true;
738
+ }
739
+ return /claude|anthropic/i.test(modelId);
740
+ };
741
+
742
+ // For Anthropic providers in V3, inputTokens.total is the sum of all tokens (uncached + cache read + cache write).
743
+ // Our cost calculation expects inputTokens to be only the uncached portion for Anthropic.
744
+ // This helper subtracts cache tokens from inputTokens for Anthropic V3 models.
745
+ const adjustAnthropicV3CacheTokens = (model, modelId, provider, usage) => {
746
+ if (isV3Model(model) && isAnthropicClaudeModel(modelId, provider)) {
747
+ const cacheReadTokens = usage.cacheReadInputTokens || 0;
748
+ const cacheWriteTokens = usage.cacheCreationInputTokens || 0;
749
+ const cacheTokens = cacheReadTokens + cacheWriteTokens;
750
+ if (usage.inputTokens && cacheTokens > 0) {
751
+ usage.inputTokens = Math.max(usage.inputTokens - cacheTokens, 0);
752
+ }
753
+ }
754
+ };
755
+
723
756
  /**
724
757
  * Wraps a Vercel AI SDK language model (V2 or V3) with PostHog tracing.
725
758
  * Automatically detects the model version and applies appropriate instrumentation.
@@ -757,7 +790,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
757
790
  const content = mapVercelOutput(result.content ?? []);
758
791
  const latency = (Date.now() - startTime) / 1000;
759
792
  const providerMetadata = result.providerMetadata;
760
- const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
793
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata, result.usage);
761
794
  const webSearchCount = extractWebSearchCount(providerMetadata, result.usage);
762
795
 
763
796
  // V2 usage has simple numbers, V3 has objects with .total - normalize both
@@ -788,7 +821,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
788
821
  ...additionalTokenValues,
789
822
  rawUsage: rawUsageData
790
823
  };
791
- adjustAnthropicV3CacheTokens(model, provider, usage);
824
+ adjustAnthropicV3CacheTokens(model, modelId, provider, usage);
792
825
 
793
826
  // Extract finish reason - V2 returns a string, V3 returns an object with .unified
794
827
  const rawFinishReason = result.finishReason;
@@ -915,8 +948,8 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
915
948
  }
916
949
  if (chunk.type === 'finish') {
917
950
  providerMetadata = chunk.providerMetadata;
918
- const additionalTokenValues = extractAdditionalTokenValues(providerMetadata);
919
951
  const chunkUsage = chunk.usage || {};
952
+ const additionalTokenValues = extractAdditionalTokenValues(providerMetadata, chunkUsage);
920
953
  usage = {
921
954
  inputTokens: extractTokenCount(chunk.usage?.inputTokens),
922
955
  outputTokens: extractTokenCount(chunk.usage?.outputTokens),
@@ -983,7 +1016,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
983
1016
  providerMetadata
984
1017
  }
985
1018
  };
986
- adjustAnthropicV3CacheTokens(model, provider, finalUsage);
1019
+ adjustAnthropicV3CacheTokens(model, modelId, provider, finalUsage);
987
1020
  await sendEventToPosthog({
988
1021
  client: phClient,
989
1022
  distinctId: mergedOptions.posthogDistinctId,