@posthog/ai 8.2.3 → 8.3.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.
@@ -74,6 +74,33 @@ const sharedTextEncoder = new TextEncoder();
74
74
  const sharedTextDecoder = new TextDecoder(STRING_FORMAT, {
75
75
  fatal: false
76
76
  });
77
+ const utf8ByteLength = str => sharedTextEncoder.encode(str).byteLength;
78
+
79
+ /**
80
+ * Safely converts content to a string, preserving structure for objects/arrays.
81
+ * - If content is already a string, returns it as-is
82
+ * - If content is an object or array, stringifies it with JSON.stringify to preserve structure
83
+ * - Otherwise, converts to string with String()
84
+ *
85
+ * This prevents the "[object Object]" bug when objects are naively converted to strings.
86
+ *
87
+ * @param content - The content to convert to a string
88
+ * @returns A string representation that preserves structure for complex types
89
+ */
90
+ function toContentString(content) {
91
+ if (typeof content === 'string') {
92
+ return content;
93
+ }
94
+ if (content !== undefined && content !== null && typeof content === 'object') {
95
+ try {
96
+ return JSON.stringify(content);
97
+ } catch {
98
+ // Fallback for circular refs, BigInt, or objects with throwing toJSON
99
+ return String(content);
100
+ }
101
+ }
102
+ return String(content);
103
+ }
77
104
  const withPrivacyMode = (client, privacyMode, input) => {
78
105
  return client.privacy_mode || privacyMode ? null : input;
79
106
  };
@@ -114,7 +141,7 @@ const truncate = input => {
114
141
  return `${truncatedStr}... [truncated]`;
115
142
  };
116
143
 
117
- var version = "8.2.3";
144
+ var version = "8.3.0";
118
145
 
119
146
  // Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway
120
147
  // emits its own $ai_generation, so each call would be captured (and, for billable
@@ -181,38 +208,46 @@ function normalizeInputRoles(input) {
181
208
  return item;
182
209
  });
183
210
  }
211
+ function safeContentString(value) {
212
+ try {
213
+ return toContentString(value);
214
+ } catch {
215
+ return Object.prototype.toString.call(value);
216
+ }
217
+ }
184
218
  function ensureSerializable(obj) {
185
219
  if (obj === null || obj === undefined) {
186
220
  return obj;
187
221
  }
188
222
  try {
189
- JSON.stringify(obj);
190
- return obj;
223
+ const serializedValue = JSON.stringify(obj);
224
+ return serializedValue === undefined ? safeContentString(obj) : obj;
191
225
  } catch {
192
- return String(obj);
226
+ return safeContentString(obj);
193
227
  }
194
228
  }
195
- function exceedsMaxOutputSize(value) {
229
+ function stringifyForSizeCheck(value) {
196
230
  if (value === null || value === undefined) {
197
- return false;
231
+ return null;
232
+ }
233
+ if (typeof value === 'string') {
234
+ return value;
198
235
  }
199
236
  try {
200
- const serializedValue = typeof value === 'string' ? value : JSON.stringify(value);
201
- return new TextEncoder().encode(serializedValue).length > MAX_OUTPUT_SIZE;
237
+ return JSON.stringify(value) ?? safeContentString(value);
202
238
  } catch {
203
- return false;
239
+ return safeContentString(value);
204
240
  }
205
241
  }
242
+ function exceedsMaxOutputSize(serializedValue) {
243
+ return serializedValue === null ? false : utf8ByteLength(serializedValue) > MAX_OUTPUT_SIZE;
244
+ }
206
245
  function parseIsoTimestamp(isoStr) {
207
- if (!isoStr) {
208
- return null;
209
- }
210
- try {
211
- const ts = new Date(isoStr).getTime();
212
- return isNaN(ts) ? null : ts / 1000;
213
- } catch {
246
+ if (typeof isoStr !== 'string' || isoStr.trim() === '') {
214
247
  return null;
215
248
  }
249
+ const ts = new Date(isoStr).getTime();
250
+ return Number.isFinite(ts) ? ts / 1000 : null;
216
251
  }
217
252
  /**
218
253
  * A tracing processor that sends OpenAI Agents SDK traces to PostHog.
@@ -242,6 +277,7 @@ class PostHogTracingProcessor {
242
277
  this._privacyMode = options.privacyMode ?? false;
243
278
  this._groups = options.groups ?? {};
244
279
  this._properties = options.properties ?? {};
280
+ this._onError = options.onError;
245
281
  }
246
282
  _getDistinctId(trace) {
247
283
  if (typeof this._distinctId === 'function') {
@@ -262,7 +298,8 @@ class PostHogTracingProcessor {
262
298
  }
263
299
  _prepareCapturedValue(value) {
264
300
  const serializableValue = ensureSerializable(value);
265
- const boundedValue = exceedsMaxOutputSize(serializableValue) ? truncate(serializableValue) : serializableValue;
301
+ const serializedValue = stringifyForSizeCheck(serializableValue);
302
+ const boundedValue = exceedsMaxOutputSize(serializedValue) ? truncate(serializedValue) : serializableValue;
266
303
  return this._withPrivacyMode(boundedValue);
267
304
  }
268
305
  _evictStaleEntries() {
@@ -281,6 +318,12 @@ class PostHogTracingProcessor {
281
318
  }
282
319
  }
283
320
  }
321
+ _handleError(error, context) {
322
+ try {
323
+ this._onError?.(error, context);
324
+ } catch (handlerError) {
325
+ }
326
+ }
284
327
  _captureEvent(event, properties, distinctId) {
285
328
  try {
286
329
  if (!this._client?.capture) {
@@ -297,8 +340,8 @@ class PostHogTracingProcessor {
297
340
  groups: Object.keys(this._groups).length > 0 ? this._groups : undefined
298
341
  };
299
342
  this._client.capture(eventMessage);
300
- } catch {
301
- // Silently ignore capture errors
343
+ } catch (error) {
344
+ this._handleError(error, 'capture');
302
345
  }
303
346
  }
304
347
  _baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties) {
@@ -359,8 +402,8 @@ class PostHogTracingProcessor {
359
402
  distinctId,
360
403
  startTime: Date.now() / 1000
361
404
  });
362
- } catch {
363
- // Silently ignore errors
405
+ } catch (error) {
406
+ this._handleError(error, 'onTraceStart');
364
407
  }
365
408
  }
366
409
  async onTraceEnd(trace) {
@@ -395,16 +438,16 @@ class PostHogTracingProcessor {
395
438
  properties.$process_person_profile = false;
396
439
  }
397
440
  this._captureEvent('$ai_trace', properties, distinctId ?? traceId);
398
- } catch {
399
- // Silently ignore errors
441
+ } catch (error) {
442
+ this._handleError(error, 'onTraceEnd');
400
443
  }
401
444
  }
402
445
  async onSpanStart(span) {
403
446
  try {
404
447
  this._evictStaleEntries();
405
448
  this._spanStartTimes.set(span.spanId, Date.now() / 1000);
406
- } catch {
407
- // Silently ignore errors
449
+ } catch (error) {
450
+ this._handleError(error, 'onSpanStart');
408
451
  }
409
452
  }
410
453
  async onSpanEnd(span) {
@@ -477,8 +520,8 @@ class PostHogTracingProcessor {
477
520
  this._handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
478
521
  break;
479
522
  }
480
- } catch {
481
- // Silently ignore errors
523
+ } catch (error) {
524
+ this._handleError(error, 'onSpanEnd');
482
525
  }
483
526
  }
484
527
  async shutdown() {
@@ -488,8 +531,8 @@ class PostHogTracingProcessor {
488
531
  if (typeof this._client?.flush === 'function') {
489
532
  await this._client.flush();
490
533
  }
491
- } catch {
492
- // Silently ignore errors
534
+ } catch (error) {
535
+ this._handleError(error, 'shutdown');
493
536
  }
494
537
  }
495
538
  async forceFlush() {
@@ -497,8 +540,8 @@ class PostHogTracingProcessor {
497
540
  if (typeof this._client?.flush === 'function') {
498
541
  await this._client.flush();
499
542
  }
500
- } catch {
501
- // Silently ignore errors
543
+ } catch (error) {
544
+ this._handleError(error, 'forceFlush');
502
545
  }
503
546
  }
504
547