@posthog/ai 8.2.3 → 8.3.1

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