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