@posthog/ai 7.5.3 → 7.6.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.
@@ -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.5.3";
5
+ var version = "7.6.0";
6
6
 
7
7
  // Type guards for safer type checking
8
8
 
@@ -10,6 +10,59 @@ const isString = value => {
10
10
  return typeof value === 'string';
11
11
  };
12
12
 
13
+ const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
14
+
15
+ // ============================================
16
+ // Multimodal Feature Toggle
17
+ // ============================================
18
+
19
+ const isMultimodalEnabled = () => {
20
+ const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
21
+ return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
22
+ };
23
+
24
+ // ============================================
25
+ // Base64 Detection Helpers
26
+ // ============================================
27
+
28
+ const isBase64DataUrl = str => {
29
+ return /^data:([^;]+);base64,/.test(str);
30
+ };
31
+ const isValidUrl = str => {
32
+ try {
33
+ new URL(str);
34
+ return true;
35
+ } catch {
36
+ // Not an absolute URL, check if it's a relative URL or path
37
+ return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
38
+ }
39
+ };
40
+ const isRawBase64 = str => {
41
+ // Skip if it's a valid URL or path
42
+ if (isValidUrl(str)) {
43
+ return false;
44
+ }
45
+
46
+ // Check if it's a valid base64 string
47
+ // Base64 images are typically at least a few hundred chars, but we'll be conservative
48
+ return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
49
+ };
50
+ function redactBase64DataUrl(str) {
51
+ if (isMultimodalEnabled()) return str;
52
+ if (!isString(str)) return str;
53
+
54
+ // Check for data URL format
55
+ if (isBase64DataUrl(str)) {
56
+ return REDACTED_IMAGE_PLACEHOLDER;
57
+ }
58
+
59
+ // Check for raw base64 (Vercel sends raw base64 for inline images)
60
+ if (isRawBase64(str)) {
61
+ return REDACTED_IMAGE_PLACEHOLDER;
62
+ }
63
+ return str;
64
+ }
65
+
13
66
  // limit large outputs by truncating to 200kb (approx 200k bytes)
14
67
  const MAX_OUTPUT_SIZE = 200000;
15
68
  const STRING_FORMAT = 'utf8';
@@ -314,6 +367,9 @@ const sendEventToPosthog = async ({
314
367
  } : {}),
315
368
  ...(usage.webSearchCount ? {
316
369
  $ai_web_search_count: usage.webSearchCount
370
+ } : {}),
371
+ ...(usage.rawUsage ? {
372
+ $ai_usage: usage.rawUsage
317
373
  } : {})
318
374
  };
319
375
  const properties = {
@@ -358,59 +414,6 @@ const sendEventToPosthog = async ({
358
414
  return Promise.resolve();
359
415
  };
360
416
 
361
- const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
362
-
363
- // ============================================
364
- // Multimodal Feature Toggle
365
- // ============================================
366
-
367
- const isMultimodalEnabled = () => {
368
- const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
369
- return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
370
- };
371
-
372
- // ============================================
373
- // Base64 Detection Helpers
374
- // ============================================
375
-
376
- const isBase64DataUrl = str => {
377
- return /^data:([^;]+);base64,/.test(str);
378
- };
379
- const isValidUrl = str => {
380
- try {
381
- new URL(str);
382
- return true;
383
- } catch {
384
- // Not an absolute URL, check if it's a relative URL or path
385
- return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
386
- }
387
- };
388
- const isRawBase64 = str => {
389
- // Skip if it's a valid URL or path
390
- if (isValidUrl(str)) {
391
- return false;
392
- }
393
-
394
- // Check if it's a valid base64 string
395
- // Base64 images are typically at least a few hundred chars, but we'll be conservative
396
- return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
397
- };
398
- function redactBase64DataUrl(str) {
399
- if (isMultimodalEnabled()) return str;
400
- if (!isString(str)) return str;
401
-
402
- // Check for data URL format
403
- if (isBase64DataUrl(str)) {
404
- return REDACTED_IMAGE_PLACEHOLDER;
405
- }
406
-
407
- // Check for raw base64 (Vercel sends raw base64 for inline images)
408
- if (isRawBase64(str)) {
409
- return REDACTED_IMAGE_PLACEHOLDER;
410
- }
411
- return str;
412
- }
413
-
414
417
  // Union types for dual version support
415
418
 
416
419
  // Type guards
@@ -738,13 +741,31 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
738
741
 
739
742
  // V2 usage has simple numbers, V3 has objects with .total - normalize both
740
743
  const usageObj = result.usage;
744
+
745
+ // Extract raw response for providers that include detailed usage metadata
746
+ // For Gemini, candidatesTokensDetails is in result.response.body.usageMetadata
747
+ const rawUsageData = {
748
+ usage: result.usage,
749
+ providerMetadata
750
+ };
751
+
752
+ // Include response body usageMetadata if it contains detailed token breakdown (e.g., candidatesTokensDetails)
753
+ if (result.response && typeof result.response === 'object') {
754
+ const responseBody = result.response.body;
755
+ if (responseBody && typeof responseBody === 'object' && 'usageMetadata' in responseBody) {
756
+ rawUsageData.rawResponse = {
757
+ usageMetadata: responseBody.usageMetadata
758
+ };
759
+ }
760
+ }
741
761
  const usage = {
742
762
  inputTokens: extractTokenCount(result.usage.inputTokens),
743
763
  outputTokens: extractTokenCount(result.usage.outputTokens),
744
764
  reasoningTokens: extractReasoningTokens(usageObj),
745
765
  cacheReadInputTokens: extractCacheReadTokens(usageObj),
746
766
  webSearchCount,
747
- ...additionalTokenValues
767
+ ...additionalTokenValues,
768
+ rawUsage: rawUsageData
748
769
  };
749
770
  adjustAnthropicV3CacheTokens(model, provider, usage);
750
771
  await sendEventToPosthog({
@@ -904,10 +925,14 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
904
925
  }] : [];
905
926
  const webSearchCount = extractWebSearchCount(providerMetadata, usage);
906
927
 
907
- // Update usage with web search count
928
+ // Update usage with web search count and raw metadata
908
929
  const finalUsage = {
909
930
  ...usage,
910
- webSearchCount
931
+ webSearchCount,
932
+ rawUsage: {
933
+ usage,
934
+ providerMetadata
935
+ }
911
936
  };
912
937
  adjustAnthropicV3CacheTokens(model, provider, finalUsage);
913
938
  await sendEventToPosthog({