@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.
- package/dist/anthropic/index.cjs +142 -61
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +142 -61
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +142 -85
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.mjs +142 -85
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +142 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +142 -210
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +135 -126
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +135 -126
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +136 -134
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.mjs +136 -134
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +65 -1
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.mjs +65 -1
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +141 -44
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +141 -44
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -33,228 +33,160 @@ var AnthropicOriginal__default = /*#__PURE__*/_interopDefault(AnthropicOriginal)
|
|
|
33
33
|
const isString = value => {
|
|
34
34
|
return typeof value === 'string';
|
|
35
35
|
};
|
|
36
|
-
const isObject = value => {
|
|
37
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
-
};
|
|
39
36
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// ============================================
|
|
49
|
-
// Base64 Detection Helpers
|
|
50
|
-
// ============================================
|
|
51
|
-
const isBase64DataUrl = str => {
|
|
52
|
-
return /^data:([^;]+);base64,/.test(str);
|
|
53
|
-
};
|
|
54
|
-
const isValidUrl = str => {
|
|
55
|
-
try {
|
|
56
|
-
new URL(str);
|
|
57
|
-
return true;
|
|
58
|
-
} catch {
|
|
59
|
-
// Not an absolute URL, check if it's a relative URL or path
|
|
60
|
-
return str.startsWith('/') || str.startsWith('./') || str.startsWith('../');
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
const isRawBase64 = str => {
|
|
64
|
-
// Skip if it's a valid URL or path
|
|
65
|
-
if (isValidUrl(str)) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
// Check if it's a valid base64 string
|
|
69
|
-
// Base64 images are typically at least a few hundred chars, but we'll be conservative
|
|
70
|
-
return str.length > 20 && /^[A-Za-z0-9+/]+=*$/.test(str);
|
|
71
|
-
};
|
|
72
|
-
function redactBase64DataUrl(str) {
|
|
73
|
-
if (isMultimodalEnabled()) return str;
|
|
74
|
-
if (!isString(str)) return str;
|
|
75
|
-
// Check for data URL format
|
|
76
|
-
if (isBase64DataUrl(str)) {
|
|
77
|
-
return REDACTED_IMAGE_PLACEHOLDER;
|
|
78
|
-
}
|
|
79
|
-
// Check for raw base64 (Vercel sends raw base64 for inline images)
|
|
80
|
-
if (isRawBase64(str)) {
|
|
81
|
-
return REDACTED_IMAGE_PLACEHOLDER;
|
|
82
|
-
}
|
|
83
|
-
return str;
|
|
84
|
-
}
|
|
85
|
-
const processMessages = (messages, transformContent) => {
|
|
86
|
-
if (!messages) return messages;
|
|
87
|
-
const processContent = content => {
|
|
88
|
-
if (typeof content === 'string') return content;
|
|
89
|
-
if (!content) return content;
|
|
90
|
-
if (Array.isArray(content)) {
|
|
91
|
-
return content.map(transformContent);
|
|
92
|
-
}
|
|
93
|
-
// Handle single object content
|
|
94
|
-
return transformContent(content);
|
|
95
|
-
};
|
|
96
|
-
const processMessage = msg => {
|
|
97
|
-
if (!isObject(msg) || !('content' in msg)) return msg;
|
|
98
|
-
return {
|
|
99
|
-
...msg,
|
|
100
|
-
content: processContent(msg.content)
|
|
37
|
+
const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
|
|
38
|
+
const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
|
|
39
|
+
class Base64Recognizer {
|
|
40
|
+
recognize(value, minLength) {
|
|
41
|
+
const dataUrl = DATA_URL_PREFIX_RE.exec(value);
|
|
42
|
+
if (dataUrl) return {
|
|
43
|
+
kind: 'data-url',
|
|
44
|
+
mediaType: dataUrl[1]
|
|
101
45
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (Array.isArray(messages)) {
|
|
105
|
-
return messages.map(processMessage);
|
|
106
|
-
}
|
|
107
|
-
return processMessage(messages);
|
|
108
|
-
};
|
|
109
|
-
// ============================================
|
|
110
|
-
// Provider-Specific Image Sanitizers
|
|
111
|
-
// ============================================
|
|
112
|
-
const sanitizeOpenAIImage = item => {
|
|
113
|
-
if (!isObject(item)) return item;
|
|
114
|
-
// Handle image_url format
|
|
115
|
-
if (item.type === 'image_url' && 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url) {
|
|
116
|
-
return {
|
|
117
|
-
...item,
|
|
118
|
-
image_url: {
|
|
119
|
-
...item.image_url,
|
|
120
|
-
url: redactBase64DataUrl(item.image_url.url)
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
// Handle video_url format
|
|
125
|
-
if (item.type === 'video_url' && 'video_url' in item && isObject(item.video_url) && 'url' in item.video_url) {
|
|
126
|
-
return {
|
|
127
|
-
...item,
|
|
128
|
-
video_url: {
|
|
129
|
-
...item.video_url,
|
|
130
|
-
url: redactBase64DataUrl(item.video_url.url)
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
// Handle audio format
|
|
135
|
-
if (item.type === 'audio' && 'data' in item) {
|
|
136
|
-
if (isMultimodalEnabled()) return item;
|
|
137
|
-
return {
|
|
138
|
-
...item,
|
|
139
|
-
data: REDACTED_IMAGE_PLACEHOLDER
|
|
46
|
+
if (value.length < minLength) return {
|
|
47
|
+
kind: 'none'
|
|
140
48
|
};
|
|
49
|
+
const confidencePrefix = value.slice(0, minLength);
|
|
50
|
+
if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
|
|
51
|
+
return {
|
|
52
|
+
kind: 'raw'
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
return {
|
|
56
|
+
kind: 'none'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
141
59
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
|
|
63
|
+
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']);
|
|
64
|
+
const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
|
|
65
|
+
const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
|
|
66
|
+
const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
|
|
67
|
+
class MediaTypeContext {
|
|
68
|
+
constructor(parent, key) {
|
|
69
|
+
this.parent = parent;
|
|
70
|
+
this.key = key;
|
|
71
|
+
}
|
|
72
|
+
inferMediaType() {
|
|
73
|
+
return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
|
|
74
|
+
}
|
|
75
|
+
inferFromSiblingMime() {
|
|
76
|
+
if (!this.parent) return undefined;
|
|
77
|
+
for (const hint of MIME_HINT_KEYS) {
|
|
78
|
+
const v = this.parent[hint];
|
|
79
|
+
if (typeof v === 'string') return v;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
152
82
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
...item,
|
|
162
|
-
source: {
|
|
163
|
-
...item.source,
|
|
164
|
-
data: REDACTED_IMAGE_PLACEHOLDER
|
|
165
|
-
}
|
|
166
|
-
};
|
|
83
|
+
inferFromSiblingFormat() {
|
|
84
|
+
if (!this.parent) return undefined;
|
|
85
|
+
const fmt = this.parent.format;
|
|
86
|
+
if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
|
|
87
|
+
return `audio/${fmt.toLowerCase()}`;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
167
90
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
inlineData: {
|
|
178
|
-
...part.inlineData,
|
|
179
|
-
data: REDACTED_IMAGE_PLACEHOLDER
|
|
180
|
-
}
|
|
181
|
-
};
|
|
91
|
+
inferFromParentType() {
|
|
92
|
+
if (!this.parent) return undefined;
|
|
93
|
+
const t = this.parent.type;
|
|
94
|
+
if (typeof t !== 'string') return undefined;
|
|
95
|
+
if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
|
|
96
|
+
if (t === 'audio' || t === 'input_audio') return 'audio';
|
|
97
|
+
if (t === 'video' || t === 'video_url') return 'video';
|
|
98
|
+
if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
|
|
99
|
+
return undefined;
|
|
182
100
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return
|
|
191
|
-
...item,
|
|
192
|
-
parts
|
|
193
|
-
};
|
|
101
|
+
inferFromKey() {
|
|
102
|
+
if (!this.key) return undefined;
|
|
103
|
+
const key = this.key.toLowerCase();
|
|
104
|
+
if (key.includes('audio')) return 'audio';
|
|
105
|
+
if (key.includes('video')) return 'video';
|
|
106
|
+
if (key.includes('image')) return 'image';
|
|
107
|
+
if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
|
|
108
|
+
return undefined;
|
|
194
109
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
// OpenAI style
|
|
200
|
-
if (item.type === 'image_url' && 'image_url' in item && isObject(item.image_url) && 'url' in item.image_url) {
|
|
201
|
-
return {
|
|
202
|
-
...item,
|
|
203
|
-
image_url: {
|
|
204
|
-
...item.image_url,
|
|
205
|
-
url: redactBase64DataUrl(item.image_url.url)
|
|
110
|
+
signalsBinary() {
|
|
111
|
+
if (this.parent) {
|
|
112
|
+
for (const hint of MIME_HINT_KEYS) {
|
|
113
|
+
if (typeof this.parent[hint] === 'string') return true;
|
|
206
114
|
}
|
|
207
|
-
|
|
115
|
+
const fmt = this.parent.format;
|
|
116
|
+
if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
|
|
117
|
+
const t = this.parent.type;
|
|
118
|
+
if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
|
|
119
|
+
}
|
|
120
|
+
if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
|
|
121
|
+
return false;
|
|
208
122
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
123
|
+
}
|
|
124
|
+
MediaTypeContext.EMPTY = new MediaTypeContext(undefined, undefined);
|
|
125
|
+
|
|
126
|
+
const STRONG_CONTEXT_MIN_LENGTH = 64;
|
|
127
|
+
const WEAK_CONTEXT_MIN_LENGTH = 1024;
|
|
128
|
+
class BinaryContentRedactor {
|
|
129
|
+
constructor(recognizer = new Base64Recognizer()) {
|
|
130
|
+
this.recognizer = recognizer;
|
|
131
|
+
this.visited = new WeakSet();
|
|
132
|
+
}
|
|
133
|
+
redact(value) {
|
|
134
|
+
if (this.isMultimodalEnabled()) return value;
|
|
135
|
+
this.visited = new WeakSet();
|
|
136
|
+
return this.walk(value, MediaTypeContext.EMPTY);
|
|
137
|
+
}
|
|
138
|
+
walk(value, ctx) {
|
|
139
|
+
if (value === null || value === undefined) return value;
|
|
140
|
+
if (typeof value === 'string') return this.redactString(value, ctx);
|
|
141
|
+
if (typeof value !== 'object') return value;
|
|
142
|
+
// Buffer extends Uint8Array, so this branch catches both.
|
|
143
|
+
if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
|
|
144
|
+
return this.placeholderFor(ctx.inferMediaType());
|
|
145
|
+
}
|
|
146
|
+
if (this.visited.has(value)) return null;
|
|
147
|
+
this.visited.add(value);
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
return value.map(item => this.walk(item, ctx));
|
|
150
|
+
}
|
|
151
|
+
const obj = value;
|
|
152
|
+
const out = {};
|
|
153
|
+
for (const k of Object.keys(obj)) {
|
|
154
|
+
out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
redactString(value, ctx) {
|
|
159
|
+
const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
|
|
160
|
+
const recognition = this.recognizer.recognize(value, minLength);
|
|
161
|
+
switch (recognition.kind) {
|
|
162
|
+
case 'data-url':
|
|
163
|
+
return this.placeholderFor(recognition.mediaType);
|
|
164
|
+
case 'raw':
|
|
165
|
+
return this.placeholderFor(ctx.inferMediaType());
|
|
166
|
+
case 'none':
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
215
169
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
return {
|
|
220
|
-
...item,
|
|
221
|
-
source: {
|
|
222
|
-
...item.source,
|
|
223
|
-
data: redactBase64DataUrl(item.source.data)
|
|
224
|
-
}
|
|
225
|
-
};
|
|
170
|
+
placeholderFor(mediaType) {
|
|
171
|
+
if (!mediaType) return '[base64 redacted]';
|
|
172
|
+
if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
|
|
173
|
+
return `[base64 ${mediaType} redacted]`;
|
|
226
174
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return
|
|
230
|
-
...item,
|
|
231
|
-
data: redactBase64DataUrl(item.data)
|
|
232
|
-
};
|
|
175
|
+
isMultimodalEnabled() {
|
|
176
|
+
const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
|
|
177
|
+
return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
|
|
233
178
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
};
|
|
246
|
-
const sanitizeGemini = data => {
|
|
247
|
-
// Gemini has a different structure with 'parts' directly on items instead of 'content'
|
|
248
|
-
// So we need custom processing instead of using processMessages
|
|
249
|
-
if (!data) return data;
|
|
250
|
-
if (Array.isArray(data)) {
|
|
251
|
-
return data.map(processGeminiItem);
|
|
252
|
-
}
|
|
253
|
-
return processGeminiItem(data);
|
|
254
|
-
};
|
|
255
|
-
const sanitizeLangChain = data => {
|
|
256
|
-
return processMessages(data, sanitizeLangChainImage);
|
|
257
|
-
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const redactor = new BinaryContentRedactor();
|
|
182
|
+
function redactBase64DataUrl(str) {
|
|
183
|
+
return redactor.redact(str);
|
|
184
|
+
}
|
|
185
|
+
const sanitizeOpenAI = data => redactor.redact(data);
|
|
186
|
+
const sanitizeOpenAIResponse = data => redactor.redact(data);
|
|
187
|
+
const sanitizeAnthropic = data => redactor.redact(data);
|
|
188
|
+
const sanitizeGemini = data => redactor.redact(data);
|
|
189
|
+
const sanitizeLangChain = data => redactor.redact(data);
|
|
258
190
|
|
|
259
191
|
const TOKEN_PROPERTY_KEYS = new Set(['$ai_input_tokens', '$ai_output_tokens', '$ai_cache_read_input_tokens', '$ai_cache_creation_input_tokens', '$ai_total_tokens', '$ai_reasoning_tokens']);
|
|
260
192
|
function getTokensSource(posthogProperties) {
|
|
@@ -801,7 +733,7 @@ function formatOpenAIResponsesInput(input, instructions) {
|
|
|
801
733
|
return messages;
|
|
802
734
|
}
|
|
803
735
|
|
|
804
|
-
var version = "7.
|
|
736
|
+
var version = "7.18.0";
|
|
805
737
|
|
|
806
738
|
/**
|
|
807
739
|
* Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
|