@posthog/ai 8.6.4 → 8.6.6

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.4";
409
+ var version = "8.6.6";
399
410
 
400
411
  const DEFAULT_MAX_DEPTH = 3;
401
412
  const MAX_STACK_LINES = 20;
@@ -593,7 +604,9 @@ const captureAiGeneration = async (client, options) => {
593
604
  $ai_output_tokens: usage.outputTokens
594
605
  } : {}),
595
606
  ...additionalTokenValues,
596
- $ai_latency: options.latency ?? 0,
607
+ ...(options.latency !== undefined ? {
608
+ $ai_latency: options.latency
609
+ } : {}),
597
610
  ...(options.timeToFirstToken !== undefined ? {
598
611
  $ai_time_to_first_token: options.timeToFirstToken
599
612
  } : {}),
@@ -645,6 +658,15 @@ function isV3Model(model) {
645
658
 
646
659
  // Content types for the output array
647
660
 
661
+ const redactFileData = (data, mediaType) => {
662
+ if (data instanceof URL) {
663
+ return redactBase64DataUrl(data.toString(), data.protocol === 'data:' ? mediaType : undefined);
664
+ }
665
+ if (isString(data)) {
666
+ return redactBase64DataUrl(data, mediaType);
667
+ }
668
+ return undefined;
669
+ };
648
670
  const mapVercelParams = params => {
649
671
  return {
650
672
  temperature: params.temperature,
@@ -677,17 +699,8 @@ const mapVercelPrompt = messages => {
677
699
  text: truncate(c.text)
678
700
  };
679
701
  } else if (c.type === 'file') {
680
- // For file type, check if it's a data URL and redact if needed
681
- let fileData;
682
- const contentData = c.data;
683
- if (contentData instanceof URL) {
684
- fileData = contentData.toString();
685
- } else if (isString(contentData)) {
686
- // Redact base64 data URLs and raw base64 to prevent oversized events
687
- fileData = redactBase64DataUrl(contentData);
688
- } else {
689
- fileData = 'raw files not supported';
690
- }
702
+ // Redact base64 data URLs and raw base64 to prevent oversized events
703
+ const fileData = redactFileData(c.data, c.mediaType) ?? 'raw files not supported';
691
704
  return {
692
705
  type: 'file',
693
706
  file: fileData,
@@ -710,7 +723,7 @@ const mapVercelPrompt = messages => {
710
723
  type: 'tool-result',
711
724
  toolCallId: c.toolCallId,
712
725
  toolName: c.toolName,
713
- output: c.output,
726
+ output: sanitizeVercel(c.output),
714
727
  isError: c.isError
715
728
  };
716
729
  }
@@ -796,18 +809,11 @@ const mapVercelOutput = result => {
796
809
  }
797
810
  if (item.type === 'file') {
798
811
  // Handle files similar to input mapping - avoid large base64 data
799
- let fileData;
800
- if (item.data instanceof URL) {
801
- fileData = item.data.toString();
802
- } else if (typeof item.data === 'string') {
803
- fileData = redactBase64DataUrl(item.data);
812
+ let fileData = redactFileData(item.data, item.mediaType) ?? `[binary ${item.mediaType} file]`;
804
813
 
805
- // If not redacted and still large, replace with size indicator
806
- if (fileData === item.data && item.data.length > 1000) {
807
- fileData = `[${item.mediaType} file - ${item.data.length} bytes]`;
808
- }
809
- } else {
810
- fileData = `[binary ${item.mediaType} file]`;
814
+ // If not redacted and still large, replace with size indicator
815
+ if (typeof item.data === 'string' && fileData === item.data && item.data.length > 1000) {
816
+ fileData = `[${item.mediaType} file - ${item.data.length} bytes]`;
811
817
  }
812
818
  return {
813
819
  type: 'file',