@posthog/ai 6.1.0 → 6.1.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.
@@ -556,6 +556,134 @@ class BaseCallbackHandler extends BaseCallbackHandlerMethodsClass {
556
556
  }
557
557
  }
558
558
 
559
+ // Type guards for safer type checking
560
+
561
+ const isString = value => {
562
+ return typeof value === 'string';
563
+ };
564
+ const isObject = value => {
565
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
566
+ };
567
+
568
+ const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
569
+
570
+ // ============================================
571
+ // Base64 Detection Helpers
572
+ // ============================================
573
+
574
+ const isBase64DataUrl = str => {
575
+ return /^data:([^;]+);base64,/.test(str);
576
+ };
577
+ const isValidUrl = str => {
578
+ try {
579
+ new URL(str);
580
+ return true;
581
+ } catch {
582
+ // Not an absolute URL, check if it's a relative URL or path
583
+ return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
584
+ }
585
+ };
586
+ const isRawBase64 = str => {
587
+ // Skip if it's a valid URL or path
588
+ if (isValidUrl(str)) {
589
+ return false;
590
+ }
591
+
592
+ // Check if it's a valid base64 string
593
+ // Base64 images are typically at least a few hundred chars, but we'll be conservative
594
+ return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
595
+ };
596
+ function redactBase64DataUrl(str) {
597
+ if (!isString(str)) return str;
598
+
599
+ // Check for data URL format
600
+ if (isBase64DataUrl(str)) {
601
+ return REDACTED_IMAGE_PLACEHOLDER;
602
+ }
603
+
604
+ // Check for raw base64 (Vercel sends raw base64 for inline images)
605
+ if (isRawBase64(str)) {
606
+ return REDACTED_IMAGE_PLACEHOLDER;
607
+ }
608
+ return str;
609
+ }
610
+
611
+ // ============================================
612
+ // Common Message Processing
613
+ // ============================================
614
+
615
+ const processMessages = (messages, transformContent) => {
616
+ if (!messages) return messages;
617
+ const processContent = content => {
618
+ if (typeof content === 'string') return content;
619
+ if (!content) return content;
620
+ if (Array.isArray(content)) {
621
+ return content.map(transformContent);
622
+ }
623
+
624
+ // Handle single object content
625
+ return transformContent(content);
626
+ };
627
+ const processMessage = msg => {
628
+ if (!isObject(msg) || !('content' in msg)) return msg;
629
+ return {
630
+ ...msg,
631
+ content: processContent(msg.content)
632
+ };
633
+ };
634
+
635
+ // Handle both arrays and single messages
636
+ if (Array.isArray(messages)) {
637
+ return messages.map(processMessage);
638
+ }
639
+ return processMessage(messages);
640
+ };
641
+ const sanitizeLangChainImage = item => {
642
+ if (!isObject(item)) return item;
643
+
644
+ // OpenAI style
645
+ if (item.type === 'image_url' && 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url) {
646
+ return {
647
+ ...item,
648
+ image_url: {
649
+ ...item.image_url,
650
+ url: redactBase64DataUrl(item.image_url.url)
651
+ }
652
+ };
653
+ }
654
+
655
+ // Direct image with data field
656
+ if (item.type === 'image' && 'data' in item) {
657
+ return {
658
+ ...item,
659
+ data: redactBase64DataUrl(item.data)
660
+ };
661
+ }
662
+
663
+ // Anthropic style
664
+ if (item.type === 'image' && 'source' in item && isObject(item.source) && 'data' in item.source) {
665
+ return {
666
+ ...item,
667
+ source: {
668
+ ...item.source,
669
+ data: redactBase64DataUrl(item.source.data)
670
+ }
671
+ };
672
+ }
673
+
674
+ // Google style
675
+ if (item.type === 'media' && 'data' in item) {
676
+ return {
677
+ ...item,
678
+ data: redactBase64DataUrl(item.data)
679
+ };
680
+ }
681
+ return item;
682
+ };
683
+ const sanitizeLangChain = data => {
684
+ return processMessages(data, sanitizeLangChainImage);
685
+ };
686
+
559
687
  /** A run may either be a Span or a Generation */
560
688
 
561
689
  /** Storage for run metadata */
@@ -736,7 +864,7 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
736
864
  }) || 'generation';
737
865
  const generation = {
738
866
  name: runNameFound,
739
- input: messages,
867
+ input: sanitizeLangChain(messages),
740
868
  startTime: Date.now()
741
869
  };
742
870
  if (extraParams) {
@@ -1001,7 +1129,9 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
1001
1129
  ...message.additional_kwargs
1002
1130
  };
1003
1131
  }
1004
- return messageDict;
1132
+
1133
+ // Sanitize the message content to redact base64 images
1134
+ return sanitizeLangChain(messageDict);
1005
1135
  }
1006
1136
  _parseUsageModel(usage) {
1007
1137
  const conversionList = [['promptTokens', 'input'], ['completionTokens', 'output'], ['input_tokens', 'input'], ['output_tokens', 'output'], ['prompt_token_count', 'input'], ['candidates_token_count', 'output'], ['inputTokenCount', 'input'], ['outputTokenCount', 'output'], ['input_token_count', 'input'], ['generated_token_count', 'output']];