@posthog/ai 8.6.3 → 8.6.5

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.
@@ -42,14 +42,16 @@ const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'f
42
42
  const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
43
43
  class MediaTypeContext {
44
44
  static EMPTY = new MediaTypeContext(undefined, undefined);
45
- constructor(parent, key) {
45
+ constructor(parent, key, explicitMediaType) {
46
46
  this.parent = parent;
47
47
  this.key = key;
48
+ this.explicitMediaType = explicitMediaType;
48
49
  }
49
50
  inferMediaType() {
50
51
  return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
51
52
  }
52
53
  inferFromSiblingMime() {
54
+ if (this.explicitMediaType) return this.explicitMediaType;
53
55
  if (!this.parent) return undefined;
54
56
  for (const hint of MIME_HINT_KEYS) {
55
57
  const v = this.parent[hint];
@@ -84,7 +86,13 @@ class MediaTypeContext {
84
86
  if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
85
87
  return undefined;
86
88
  }
89
+ hasExplicitBinaryMediaType() {
90
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
91
+ const mediaType = this.inferFromSiblingMime();
92
+ return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
93
+ }
87
94
  signalsBinary() {
95
+ if (this.explicitMediaType) return true;
88
96
  if (this.parent) {
89
97
  for (const hint of MIME_HINT_KEYS) {
90
98
  if (typeof this.parent[hint] === 'string') return true;
@@ -106,10 +114,10 @@ class BinaryContentRedactor {
106
114
  constructor(recognizer = new Base64Recognizer()) {
107
115
  this.recognizer = recognizer;
108
116
  }
109
- redact(value) {
117
+ redact(value, mediaType) {
110
118
  if (this.isMultimodalEnabled()) return value;
111
119
  this.visited = new WeakSet();
112
- return this.walk(value, MediaTypeContext.EMPTY);
120
+ return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
113
121
  }
114
122
  walk(value, ctx) {
115
123
  if (value === null || value === undefined) return value;
@@ -133,8 +141,10 @@ class BinaryContentRedactor {
133
141
  return out;
134
142
  }
135
143
  redactString(value, ctx) {
136
- const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
137
- const recognition = this.recognizer.recognize(value, minLength);
144
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
145
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, '') : value;
146
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
147
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
138
148
  switch (recognition.kind) {
139
149
  case 'data-url':
140
150
  return this.placeholderFor(recognition.mediaType);
@@ -156,9 +166,10 @@ class BinaryContentRedactor {
156
166
  }
157
167
 
158
168
  const redactor = new BinaryContentRedactor();
159
- function redactBase64DataUrl(str) {
160
- return redactor.redact(str);
169
+ function redactBase64DataUrl(str, mediaType) {
170
+ return redactor.redact(str, mediaType);
161
171
  }
172
+ const sanitizeVercel = data => redactor.redact(data);
162
173
 
163
174
  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']);
164
175
  function getTokensSource(posthogProperties) {
@@ -395,7 +406,7 @@ function sanitizeValues(obj) {
395
406
  return jsonSafe;
396
407
  }
397
408
 
398
- var version = "8.6.3";
409
+ var version = "8.6.5";
399
410
 
400
411
  const DEFAULT_MAX_DEPTH = 3;
401
412
  const MAX_STACK_LINES = 20;
@@ -528,6 +539,8 @@ const captureAiGeneration = async (client, options) => {
528
539
  if (httpStatus === undefined) {
529
540
  if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
530
541
  httpStatus = options.error.status;
542
+ } else if (typeof options.error === 'object' && 'statusCode' in options.error && typeof options.error.statusCode === 'number') {
543
+ httpStatus = options.error.statusCode;
531
544
  } else {
532
545
  httpStatus = 500;
533
546
  }
@@ -643,6 +656,15 @@ function isV3Model(model) {
643
656
 
644
657
  // Content types for the output array
645
658
 
659
+ const redactFileData = (data, mediaType) => {
660
+ if (data instanceof URL) {
661
+ return redactBase64DataUrl(data.toString(), data.protocol === 'data:' ? mediaType : undefined);
662
+ }
663
+ if (isString(data)) {
664
+ return redactBase64DataUrl(data, mediaType);
665
+ }
666
+ return undefined;
667
+ };
646
668
  const mapVercelParams = params => {
647
669
  return {
648
670
  temperature: params.temperature,
@@ -675,17 +697,8 @@ const mapVercelPrompt = messages => {
675
697
  text: truncate(c.text)
676
698
  };
677
699
  } else if (c.type === 'file') {
678
- // For file type, check if it's a data URL and redact if needed
679
- let fileData;
680
- const contentData = c.data;
681
- if (contentData instanceof URL) {
682
- fileData = contentData.toString();
683
- } else if (isString(contentData)) {
684
- // Redact base64 data URLs and raw base64 to prevent oversized events
685
- fileData = redactBase64DataUrl(contentData);
686
- } else {
687
- fileData = 'raw files not supported';
688
- }
700
+ // Redact base64 data URLs and raw base64 to prevent oversized events
701
+ const fileData = redactFileData(c.data, c.mediaType) ?? 'raw files not supported';
689
702
  return {
690
703
  type: 'file',
691
704
  file: fileData,
@@ -708,7 +721,7 @@ const mapVercelPrompt = messages => {
708
721
  type: 'tool-result',
709
722
  toolCallId: c.toolCallId,
710
723
  toolName: c.toolName,
711
- output: c.output,
724
+ output: sanitizeVercel(c.output),
712
725
  isError: c.isError
713
726
  };
714
727
  }
@@ -794,18 +807,11 @@ const mapVercelOutput = result => {
794
807
  }
795
808
  if (item.type === 'file') {
796
809
  // Handle files similar to input mapping - avoid large base64 data
797
- let fileData;
798
- if (item.data instanceof URL) {
799
- fileData = item.data.toString();
800
- } else if (typeof item.data === 'string') {
801
- fileData = redactBase64DataUrl(item.data);
810
+ let fileData = redactFileData(item.data, item.mediaType) ?? `[binary ${item.mediaType} file]`;
802
811
 
803
- // If not redacted and still large, replace with size indicator
804
- if (fileData === item.data && item.data.length > 1000) {
805
- fileData = `[${item.mediaType} file - ${item.data.length} bytes]`;
806
- }
807
- } else {
808
- fileData = `[binary ${item.mediaType} file]`;
812
+ // If not redacted and still large, replace with size indicator
813
+ if (typeof item.data === 'string' && fileData === item.data && item.data.length > 1000) {
814
+ fileData = `[${item.mediaType} file - ${item.data.length} bytes]`;
809
815
  }
810
816
  return {
811
817
  type: 'file',