@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.
@@ -24,7 +24,7 @@ function _interopNamespace(e) {
24
24
 
25
25
  var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
26
26
 
27
- var version = "7.5.3";
27
+ var version = "7.6.0";
28
28
 
29
29
  // Type guards for safer type checking
30
30
 
@@ -35,6 +35,136 @@ const isObject = value => {
35
35
  return value !== null && typeof value === 'object' && !Array.isArray(value);
36
36
  };
37
37
 
38
+ const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
39
+
40
+ // ============================================
41
+ // Multimodal Feature Toggle
42
+ // ============================================
43
+
44
+ const isMultimodalEnabled = () => {
45
+ const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
46
+ return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
47
+ };
48
+
49
+ // ============================================
50
+ // Base64 Detection Helpers
51
+ // ============================================
52
+
53
+ const isBase64DataUrl = str => {
54
+ return /^data:([^;]+);base64,/.test(str);
55
+ };
56
+ const isValidUrl = str => {
57
+ try {
58
+ new URL(str);
59
+ return true;
60
+ } catch {
61
+ // Not an absolute URL, check if it's a relative URL or path
62
+ return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
63
+ }
64
+ };
65
+ const isRawBase64 = str => {
66
+ // Skip if it's a valid URL or path
67
+ if (isValidUrl(str)) {
68
+ return false;
69
+ }
70
+
71
+ // Check if it's a valid base64 string
72
+ // Base64 images are typically at least a few hundred chars, but we'll be conservative
73
+ return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
74
+ };
75
+ function redactBase64DataUrl(str) {
76
+ if (isMultimodalEnabled()) return str;
77
+ if (!isString(str)) return str;
78
+
79
+ // Check for data URL format
80
+ if (isBase64DataUrl(str)) {
81
+ return REDACTED_IMAGE_PLACEHOLDER;
82
+ }
83
+
84
+ // Check for raw base64 (Vercel sends raw base64 for inline images)
85
+ if (isRawBase64(str)) {
86
+ return REDACTED_IMAGE_PLACEHOLDER;
87
+ }
88
+ return str;
89
+ }
90
+
91
+ // ============================================
92
+ // Common Message Processing
93
+ // ============================================
94
+
95
+ const processMessages = (messages, transformContent) => {
96
+ if (!messages) return messages;
97
+ const processContent = content => {
98
+ if (typeof content === 'string') return content;
99
+ if (!content) return content;
100
+ if (Array.isArray(content)) {
101
+ return content.map(transformContent);
102
+ }
103
+
104
+ // Handle single object content
105
+ return transformContent(content);
106
+ };
107
+ const processMessage = msg => {
108
+ if (!isObject(msg) || !('content' in msg)) return msg;
109
+ return {
110
+ ...msg,
111
+ content: processContent(msg.content)
112
+ };
113
+ };
114
+
115
+ // Handle both arrays and single messages
116
+ if (Array.isArray(messages)) {
117
+ return messages.map(processMessage);
118
+ }
119
+ return processMessage(messages);
120
+ };
121
+ const sanitizeLangChainImage = item => {
122
+ if (!isObject(item)) return item;
123
+
124
+ // OpenAI style
125
+ if (item.type === 'image_url' && 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url) {
126
+ return {
127
+ ...item,
128
+ image_url: {
129
+ ...item.image_url,
130
+ url: redactBase64DataUrl(item.image_url.url)
131
+ }
132
+ };
133
+ }
134
+
135
+ // Direct image with data field
136
+ if (item.type === 'image' && 'data' in item) {
137
+ return {
138
+ ...item,
139
+ data: redactBase64DataUrl(item.data)
140
+ };
141
+ }
142
+
143
+ // Anthropic style
144
+ if (item.type === 'image' && 'source' in item && isObject(item.source) && 'data' in item.source) {
145
+ if (isMultimodalEnabled()) return item;
146
+ return {
147
+ ...item,
148
+ source: {
149
+ ...item.source,
150
+ data: redactBase64DataUrl(item.source.data)
151
+ }
152
+ };
153
+ }
154
+
155
+ // Google style
156
+ if (item.type === 'media' && 'data' in item) {
157
+ return {
158
+ ...item,
159
+ data: redactBase64DataUrl(item.data)
160
+ };
161
+ }
162
+ return item;
163
+ };
164
+ const sanitizeLangChain = data => {
165
+ return processMessages(data, sanitizeLangChainImage);
166
+ };
167
+
38
168
  /**
39
169
  * Safely converts content to a string, preserving structure for objects/arrays.
40
170
  * - If content is already a string, returns it as-is
@@ -284,6 +414,20 @@ function isSerializableLike(obj) {
284
414
  return obj !== null && typeof obj === "object" && "lc_serializable" in obj && typeof obj.toJSON === "function";
285
415
  }
286
416
  /**
417
+ * Create a "not_implemented" serialization result for objects that cannot be serialized.
418
+ */
419
+ function createNotImplemented(obj) {
420
+ let id;
421
+ if (obj !== null && typeof obj === "object") if ("lc_id" in obj && Array.isArray(obj.lc_id)) id = obj.lc_id;
422
+ else id = [obj.constructor?.name ?? "Object"];
423
+ else id = [typeof obj];
424
+ return {
425
+ lc: 1,
426
+ type: "not_implemented",
427
+ id
428
+ };
429
+ }
430
+ /**
287
431
  * Escape a value if it needs escaping (contains `lc` key).
288
432
  *
289
433
  * This is a simpler version of `serializeValue` that doesn't handle Serializable
@@ -291,18 +435,27 @@ function isSerializableLike(obj) {
291
435
  * processed by `toJSON()`.
292
436
  *
293
437
  * @param value - The value to potentially escape.
438
+ * @param pathSet - WeakSet to track ancestor objects in the current path to detect circular references.
439
+ * Objects are removed after processing to allow shared references (same object in
440
+ * multiple places) while still detecting true circular references (ancestor in descendant).
294
441
  * @returns The value with any `lc`-containing objects wrapped in escape markers.
295
442
  */
296
- function escapeIfNeeded(value) {
443
+ function escapeIfNeeded(value, pathSet = /* @__PURE__ */ new WeakSet()) {
297
444
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
445
+ if (pathSet.has(value)) return createNotImplemented(value);
298
446
  if (isSerializableLike(value)) return value;
447
+ pathSet.add(value);
299
448
  const record = value;
300
- if (needsEscaping(record)) return escapeObject(record);
449
+ if (needsEscaping(record)) {
450
+ pathSet.delete(value);
451
+ return escapeObject(record);
452
+ }
301
453
  const result = {};
302
- for (const [key, val] of Object.entries(record)) result[key] = escapeIfNeeded(val);
454
+ for (const [key, val] of Object.entries(record)) result[key] = escapeIfNeeded(val, pathSet);
455
+ pathSet.delete(value);
303
456
  return result;
304
457
  }
305
- if (Array.isArray(value)) return value.map((item) => escapeIfNeeded(item));
458
+ if (Array.isArray(value)) return value.map((item) => escapeIfNeeded(item, pathSet));
306
459
  return value;
307
460
  }
308
461
 
@@ -428,7 +581,9 @@ var Serializable = class Serializable {
428
581
  if (last in read && read[last] !== void 0) write[last] = write[last] || read[last];
429
582
  });
430
583
  const escapedKwargs = {};
431
- for (const [key, value] of Object.entries(kwargs)) escapedKwargs[key] = escapeIfNeeded(value);
584
+ const pathSet = /* @__PURE__ */ new WeakSet();
585
+ pathSet.add(this);
586
+ for (const [key, value] of Object.entries(kwargs)) escapedKwargs[key] = escapeIfNeeded(value, pathSet);
432
587
  const kwargsWithSecrets = Object.keys(secrets).length ? replaceSecrets(escapedKwargs, secrets) : escapedKwargs;
433
588
  const processedKwargs = mapKeys(kwargsWithSecrets, keyToJson, aliases);
434
589
  return {
@@ -599,136 +754,6 @@ const isBaseCallbackHandler = (x) => {
599
754
  return callbackHandler !== void 0 && typeof callbackHandler.copy === "function" && typeof callbackHandler.name === "string" && typeof callbackHandler.awaitHandlers === "boolean";
600
755
  };
601
756
 
602
- const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
603
-
604
- // ============================================
605
- // Multimodal Feature Toggle
606
- // ============================================
607
-
608
- const isMultimodalEnabled = () => {
609
- const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
610
- return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
611
- };
612
-
613
- // ============================================
614
- // Base64 Detection Helpers
615
- // ============================================
616
-
617
- const isBase64DataUrl = str => {
618
- return /^data:([^;]+);base64,/.test(str);
619
- };
620
- const isValidUrl = str => {
621
- try {
622
- new URL(str);
623
- return true;
624
- } catch {
625
- // Not an absolute URL, check if it's a relative URL or path
626
- return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
627
- }
628
- };
629
- const isRawBase64 = str => {
630
- // Skip if it's a valid URL or path
631
- if (isValidUrl(str)) {
632
- return false;
633
- }
634
-
635
- // Check if it's a valid base64 string
636
- // Base64 images are typically at least a few hundred chars, but we'll be conservative
637
- return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
638
- };
639
- function redactBase64DataUrl(str) {
640
- if (isMultimodalEnabled()) return str;
641
- if (!isString(str)) return str;
642
-
643
- // Check for data URL format
644
- if (isBase64DataUrl(str)) {
645
- return REDACTED_IMAGE_PLACEHOLDER;
646
- }
647
-
648
- // Check for raw base64 (Vercel sends raw base64 for inline images)
649
- if (isRawBase64(str)) {
650
- return REDACTED_IMAGE_PLACEHOLDER;
651
- }
652
- return str;
653
- }
654
-
655
- // ============================================
656
- // Common Message Processing
657
- // ============================================
658
-
659
- const processMessages = (messages, transformContent) => {
660
- if (!messages) return messages;
661
- const processContent = content => {
662
- if (typeof content === 'string') return content;
663
- if (!content) return content;
664
- if (Array.isArray(content)) {
665
- return content.map(transformContent);
666
- }
667
-
668
- // Handle single object content
669
- return transformContent(content);
670
- };
671
- const processMessage = msg => {
672
- if (!isObject(msg) || !('content' in msg)) return msg;
673
- return {
674
- ...msg,
675
- content: processContent(msg.content)
676
- };
677
- };
678
-
679
- // Handle both arrays and single messages
680
- if (Array.isArray(messages)) {
681
- return messages.map(processMessage);
682
- }
683
- return processMessage(messages);
684
- };
685
- const sanitizeLangChainImage = item => {
686
- if (!isObject(item)) return item;
687
-
688
- // OpenAI style
689
- if (item.type === 'image_url' && 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url) {
690
- return {
691
- ...item,
692
- image_url: {
693
- ...item.image_url,
694
- url: redactBase64DataUrl(item.image_url.url)
695
- }
696
- };
697
- }
698
-
699
- // Direct image with data field
700
- if (item.type === 'image' && 'data' in item) {
701
- return {
702
- ...item,
703
- data: redactBase64DataUrl(item.data)
704
- };
705
- }
706
-
707
- // Anthropic style
708
- if (item.type === 'image' && 'source' in item && isObject(item.source) && 'data' in item.source) {
709
- if (isMultimodalEnabled()) return item;
710
- return {
711
- ...item,
712
- source: {
713
- ...item.source,
714
- data: redactBase64DataUrl(item.source.data)
715
- }
716
- };
717
- }
718
-
719
- // Google style
720
- if (item.type === 'media' && 'data' in item) {
721
- return {
722
- ...item,
723
- data: redactBase64DataUrl(item.data)
724
- };
725
- }
726
- return item;
727
- };
728
- const sanitizeLangChain = data => {
729
- return processMessages(data, sanitizeLangChainImage);
730
- };
731
-
732
757
  /** A run may either be a Span or a Generation */
733
758
 
734
759
  /** Storage for run metadata */