@posthog/ai 7.17.4 → 7.18.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.
@@ -22,144 +22,153 @@ function _interopNamespace(e) {
22
22
 
23
23
  var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
24
24
 
25
- // Type guards for safer type checking
26
-
27
- const isString = value => {
28
- return typeof value === 'string';
29
- };
30
- const isObject = value => {
31
- return value !== null && typeof value === 'object' && !Array.isArray(value);
32
- };
33
-
34
- const REDACTED_IMAGE_PLACEHOLDER = '[base64 image redacted]';
35
-
36
- // ============================================
37
- // Multimodal Feature Toggle
38
- // ============================================
39
-
40
- const isMultimodalEnabled = () => {
41
- const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
42
- return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
43
- };
44
-
45
- // ============================================
46
- // Base64 Detection Helpers
47
- // ============================================
25
+ const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
26
+ const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
27
+ class Base64Recognizer {
28
+ recognize(value, minLength) {
29
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
30
+ if (dataUrl) return {
31
+ kind: 'data-url',
32
+ mediaType: dataUrl[1]
33
+ };
34
+ if (value.length < minLength) return {
35
+ kind: 'none'
36
+ };
37
+ const confidencePrefix = value.slice(0, minLength);
38
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
39
+ return {
40
+ kind: 'raw'
41
+ };
42
+ } else {
43
+ return {
44
+ kind: 'none'
45
+ };
46
+ }
47
+ }
48
+ }
48
49
 
49
- const isBase64DataUrl = str => {
50
- return /^data:([^;]+);base64,/.test(str);
51
- };
52
- const isValidUrl = str => {
53
- try {
54
- new URL(str);
55
- return true;
56
- } catch {
57
- // Not an absolute URL, check if it's a relative URL or path
58
- return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
50
+ const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
51
+ const STRONG_CONTEXT_KEYS = new Set(['data', 'file_data', 'fileData', 'image_url', 'imageUrl', 'video_url', 'videoUrl', 'audio', 'audio_data', 'audioData', 'inline_data', 'inlineData', 'source', 'result']);
52
+ const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
53
+ const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
54
+ const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
55
+ class MediaTypeContext {
56
+ static EMPTY = new MediaTypeContext(undefined, undefined);
57
+ constructor(parent, key) {
58
+ this.parent = parent;
59
+ this.key = key;
59
60
  }
60
- };
61
- const isRawBase64 = str => {
62
- // Skip if it's a valid URL or path
63
- if (isValidUrl(str)) {
64
- return false;
61
+ inferMediaType() {
62
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
65
63
  }
66
-
67
- // Check if it's a valid base64 string
68
- // Base64 images are typically at least a few hundred chars, but we'll be conservative
69
- return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
70
- };
71
- function redactBase64DataUrl(str) {
72
- if (isMultimodalEnabled()) return str;
73
- if (!isString(str)) return str;
74
-
75
- // Check for data URL format
76
- if (isBase64DataUrl(str)) {
77
- return REDACTED_IMAGE_PLACEHOLDER;
64
+ inferFromSiblingMime() {
65
+ if (!this.parent) return undefined;
66
+ for (const hint of MIME_HINT_KEYS) {
67
+ const v = this.parent[hint];
68
+ if (typeof v === 'string') return v;
69
+ }
70
+ return undefined;
78
71
  }
79
-
80
- // Check for raw base64 (Vercel sends raw base64 for inline images)
81
- if (isRawBase64(str)) {
82
- return REDACTED_IMAGE_PLACEHOLDER;
72
+ inferFromSiblingFormat() {
73
+ if (!this.parent) return undefined;
74
+ const fmt = this.parent.format;
75
+ if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
76
+ return `audio/${fmt.toLowerCase()}`;
77
+ }
78
+ return undefined;
79
+ }
80
+ inferFromParentType() {
81
+ if (!this.parent) return undefined;
82
+ const t = this.parent.type;
83
+ if (typeof t !== 'string') return undefined;
84
+ if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
85
+ if (t === 'audio' || t === 'input_audio') return 'audio';
86
+ if (t === 'video' || t === 'video_url') return 'video';
87
+ if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
88
+ return undefined;
89
+ }
90
+ inferFromKey() {
91
+ if (!this.key) return undefined;
92
+ const key = this.key.toLowerCase();
93
+ if (key.includes('audio')) return 'audio';
94
+ if (key.includes('video')) return 'video';
95
+ if (key.includes('image')) return 'image';
96
+ if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
97
+ return undefined;
98
+ }
99
+ signalsBinary() {
100
+ if (this.parent) {
101
+ for (const hint of MIME_HINT_KEYS) {
102
+ if (typeof this.parent[hint] === 'string') return true;
103
+ }
104
+ const fmt = this.parent.format;
105
+ if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
106
+ const t = this.parent.type;
107
+ if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
108
+ }
109
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
110
+ return false;
83
111
  }
84
- return str;
85
112
  }
86
113
 
87
- // ============================================
88
- // Common Message Processing
89
- // ============================================
90
-
91
- const processMessages = (messages, transformContent) => {
92
- if (!messages) return messages;
93
- const processContent = content => {
94
- if (typeof content === 'string') return content;
95
- if (!content) return content;
96
- if (Array.isArray(content)) {
97
- return content.map(transformContent);
114
+ const STRONG_CONTEXT_MIN_LENGTH = 64;
115
+ const WEAK_CONTEXT_MIN_LENGTH = 1024;
116
+ class BinaryContentRedactor {
117
+ visited = new WeakSet();
118
+ constructor(recognizer = new Base64Recognizer()) {
119
+ this.recognizer = recognizer;
120
+ }
121
+ redact(value) {
122
+ if (this.isMultimodalEnabled()) return value;
123
+ this.visited = new WeakSet();
124
+ return this.walk(value, MediaTypeContext.EMPTY);
125
+ }
126
+ walk(value, ctx) {
127
+ if (value === null || value === undefined) return value;
128
+ if (typeof value === 'string') return this.redactString(value, ctx);
129
+ if (typeof value !== 'object') return value;
130
+
131
+ // Buffer extends Uint8Array, so this branch catches both.
132
+ if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
133
+ return this.placeholderFor(ctx.inferMediaType());
98
134
  }
99
-
100
- // Handle single object content
101
- return transformContent(content);
102
- };
103
- const processMessage = msg => {
104
- if (!isObject(msg) || !('content' in msg)) return msg;
105
- return {
106
- ...msg,
107
- content: processContent(msg.content)
108
- };
109
- };
110
-
111
- // Handle both arrays and single messages
112
- if (Array.isArray(messages)) {
113
- return messages.map(processMessage);
135
+ if (this.visited.has(value)) return null;
136
+ this.visited.add(value);
137
+ if (Array.isArray(value)) {
138
+ return value.map(item => this.walk(item, ctx));
139
+ }
140
+ const obj = value;
141
+ const out = {};
142
+ for (const k of Object.keys(obj)) {
143
+ out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
144
+ }
145
+ return out;
114
146
  }
115
- return processMessage(messages);
116
- };
117
- const sanitizeLangChainImage = item => {
118
- if (!isObject(item)) return item;
119
-
120
- // OpenAI style
121
- if (item.type === 'image_url' && 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url) {
122
- return {
123
- ...item,
124
- image_url: {
125
- ...item.image_url,
126
- url: redactBase64DataUrl(item.image_url.url)
127
- }
128
- };
147
+ redactString(value, ctx) {
148
+ const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
149
+ const recognition = this.recognizer.recognize(value, minLength);
150
+ switch (recognition.kind) {
151
+ case 'data-url':
152
+ return this.placeholderFor(recognition.mediaType);
153
+ case 'raw':
154
+ return this.placeholderFor(ctx.inferMediaType());
155
+ case 'none':
156
+ return value;
157
+ }
129
158
  }
130
-
131
- // Direct image with data field
132
- if (item.type === 'image' && 'data' in item) {
133
- return {
134
- ...item,
135
- data: redactBase64DataUrl(item.data)
136
- };
159
+ placeholderFor(mediaType) {
160
+ if (!mediaType) return '[base64 redacted]';
161
+ if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
162
+ return `[base64 ${mediaType} redacted]`;
137
163
  }
138
-
139
- // Anthropic style
140
- if (item.type === 'image' && 'source' in item && isObject(item.source) && 'data' in item.source) {
141
- if (isMultimodalEnabled()) return item;
142
- return {
143
- ...item,
144
- source: {
145
- ...item.source,
146
- data: redactBase64DataUrl(item.source.data)
147
- }
148
- };
164
+ isMultimodalEnabled() {
165
+ const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
166
+ return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
149
167
  }
168
+ }
150
169
 
151
- // Google style
152
- if (item.type === 'media' && 'data' in item) {
153
- return {
154
- ...item,
155
- data: redactBase64DataUrl(item.data)
156
- };
157
- }
158
- return item;
159
- };
160
- const sanitizeLangChain = data => {
161
- return processMessages(data, sanitizeLangChainImage);
162
- };
170
+ const redactor = new BinaryContentRedactor();
171
+ const sanitizeLangChain = data => redactor.redact(data);
163
172
 
164
173
  const STRING_FORMAT = 'utf8';
165
174
 
@@ -675,7 +684,7 @@ var BaseCallbackHandler = class extends BaseCallbackHandlerMethodsClass {
675
684
  }
676
685
  };
677
686
 
678
- var version = "7.17.4";
687
+ var version = "7.18.0";
679
688
 
680
689
  /** A run may either be a Span or a Generation */
681
690