@posthog/ai 7.16.14 → 7.17.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.
@@ -2,8 +2,6 @@ import { GoogleGenAI } from '@google/genai';
2
2
  import { v4 } from 'uuid';
3
3
  import { uuidv7 } from '@posthog/core';
4
4
 
5
- var version = "7.16.14";
6
-
7
5
  // Type guards for safer type checking
8
6
 
9
7
  const isString = value => {
@@ -295,73 +293,69 @@ function addDefaults(params) {
295
293
  traceId: params.traceId ?? v4()
296
294
  };
297
295
  }
298
- const sendEventWithErrorToPosthog = async ({
299
- client,
300
- traceId,
301
- error,
302
- ...args
303
- }) => {
304
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
305
- const properties = {
306
- client,
307
- traceId,
308
- httpStatus,
309
- error: JSON.stringify(error),
310
- ...args
311
- };
312
- const enrichedError = error;
313
- if (client.options?.enableExceptionAutocapture) {
314
- // assign a uuid that can be used to link the trace and exception events
315
- const exceptionId = uuidv7();
316
- client.captureException(error, undefined, {
317
- $ai_trace_id: traceId
318
- }, exceptionId);
319
- enrichedError.__posthog_previously_captured_error = true;
320
- properties.exceptionId = exceptionId;
321
- }
322
- await sendEventToPosthog(properties);
323
- return enrichedError;
324
- };
325
- const sendEventToPosthog = async ({
326
- client,
327
- eventType = AIEvent.Generation,
328
- distinctId,
329
- traceId,
330
- model,
331
- provider,
332
- input,
333
- output,
334
- latency,
335
- timeToFirstToken,
336
- baseURL,
337
- params,
338
- httpStatus = 200,
339
- usage = {},
340
- error,
341
- exceptionId,
342
- stopReason,
343
- tools,
344
- captureImmediate = false
345
- }) => {
296
+
297
+ var version = "7.17.0";
298
+
299
+ /**
300
+ * Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
301
+ * directly so that any caller — first-party SDK wrappers and external code
302
+ * alike produces an identical event.
303
+ */
304
+
305
+ /**
306
+ * Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
307
+ *
308
+ * This is the canonical primitive that every `@posthog/ai` wrapper
309
+ * (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
310
+ * external code can use it directly to instrument LLM calls made through
311
+ * arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
312
+ * same events the SDK wrappers produce.
313
+ *
314
+ * When `error` is set, the event is captured as an error. If the error is an
315
+ * object, it is mutated in place to set `__posthog_previously_captured_error`
316
+ * so callers can re-throw the original error reference safely.
317
+ */
318
+ const captureAiGeneration = async (client, options) => {
346
319
  if (!client.capture) {
347
- return Promise.resolve();
348
- }
349
- // sanitize input and output for UTF-8 validity
350
- const safeInput = sanitizeValues(input);
351
- const safeOutput = sanitizeValues(output);
352
- const safeError = sanitizeValues(error);
320
+ return;
321
+ }
322
+ const traceId = options.traceId ?? v4();
323
+ const eventType = options.eventType ?? AIEvent.Generation;
324
+ const privacyMode = options.privacyMode ?? false;
325
+ const usage = options.usage ?? {};
326
+ const safeInput = sanitizeValues(options.input);
327
+ const safeOutput = sanitizeValues(options.output);
328
+ let httpStatus = options.httpStatus;
353
329
  let errorData = {};
354
- if (error) {
330
+ if (options.error) {
331
+ if (httpStatus === undefined) {
332
+ if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
333
+ httpStatus = options.error.status;
334
+ } else {
335
+ httpStatus = 500;
336
+ }
337
+ }
338
+ let exceptionId;
339
+ if (client.options?.enableExceptionAutocapture) {
340
+ exceptionId = uuidv7();
341
+ client.captureException(options.error, undefined, {
342
+ $ai_trace_id: traceId
343
+ }, exceptionId);
344
+ if (typeof options.error === 'object') {
345
+ options.error.__posthog_previously_captured_error = true;
346
+ }
347
+ }
355
348
  errorData = {
356
349
  $ai_is_error: true,
357
- $ai_error: safeError,
350
+ $ai_error: sanitizeValues(JSON.stringify(options.error)),
358
351
  $exception_event_id: exceptionId
359
352
  };
360
353
  }
354
+ httpStatus = httpStatus ?? 200;
361
355
  let costOverrideData = {};
362
- if (params.posthogCostOverride) {
363
- const inputCostUSD = (params.posthogCostOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
364
- const outputCostUSD = (params.posthogCostOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
356
+ if (options.costOverride) {
357
+ const inputCostUSD = (options.costOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
358
+ const outputCostUSD = (options.costOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
365
359
  costOverrideData = {
366
360
  $ai_input_cost_usd: inputCostUSD,
367
361
  $ai_output_cost_usd: outputCostUSD,
@@ -388,50 +382,48 @@ const sendEventToPosthog = async ({
388
382
  const properties = {
389
383
  $ai_lib: 'posthog-ai',
390
384
  $ai_lib_version: version,
391
- $ai_provider: params.posthogProviderOverride ?? provider,
392
- $ai_model: params.posthogModelOverride ?? model,
393
- $ai_model_parameters: getModelParams(params),
394
- $ai_input: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeInput),
395
- $ai_output_choices: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeOutput),
385
+ $ai_provider: options.providerOverride ?? options.provider,
386
+ $ai_model: options.modelOverride ?? options.model,
387
+ $ai_model_parameters: options.modelParameters ?? {},
388
+ $ai_input: withPrivacyMode(client, privacyMode, safeInput),
389
+ $ai_output_choices: withPrivacyMode(client, privacyMode, safeOutput),
396
390
  $ai_http_status: httpStatus,
397
391
  $ai_input_tokens: usage.inputTokens ?? 0,
398
392
  ...(usage.outputTokens !== undefined ? {
399
393
  $ai_output_tokens: usage.outputTokens
400
394
  } : {}),
401
395
  ...additionalTokenValues,
402
- $ai_latency: latency,
403
- ...(timeToFirstToken !== undefined ? {
404
- $ai_time_to_first_token: timeToFirstToken
396
+ $ai_latency: options.latency ?? 0,
397
+ ...(options.timeToFirstToken !== undefined ? {
398
+ $ai_time_to_first_token: options.timeToFirstToken
405
399
  } : {}),
406
400
  $ai_trace_id: traceId,
407
- $ai_base_url: baseURL,
408
- ...params.posthogProperties,
409
- $ai_tokens_source: getTokensSource(params.posthogProperties),
410
- ...(distinctId ? {} : {
401
+ $ai_base_url: options.baseURL ?? '',
402
+ ...options.properties,
403
+ $ai_tokens_source: getTokensSource(options.properties),
404
+ ...(options.distinctId ? {} : {
411
405
  $process_person_profile: false
412
406
  }),
413
- ...(stopReason ? {
414
- $ai_stop_reason: stopReason
407
+ ...(options.stopReason ? {
408
+ $ai_stop_reason: options.stopReason
415
409
  } : {}),
416
- ...(tools ? {
417
- $ai_tools: tools
410
+ ...(options.tools ? {
411
+ $ai_tools: options.tools
418
412
  } : {}),
419
413
  ...errorData,
420
414
  ...costOverrideData
421
415
  };
422
416
  const event = {
423
- distinctId: distinctId ?? traceId,
417
+ distinctId: options.distinctId ?? traceId,
424
418
  event: eventType,
425
419
  properties,
426
- groups: params.posthogGroups
420
+ groups: options.groups
427
421
  };
428
- if (captureImmediate) {
429
- // await capture promise to send single event in serverless environments
422
+ if (options.captureImmediate) {
430
423
  await client.captureImmediate(event);
431
424
  } else {
432
425
  client.capture(event);
433
426
  }
434
- return Promise.resolve();
435
427
  };
436
428
 
437
429
  class PostHogGoogleGenAI {
@@ -462,8 +454,7 @@ class WrappedModels {
462
454
  const availableTools = extractAvailableToolCalls('gemini', geminiParams);
463
455
  const metadata = response.usageMetadata;
464
456
  const finishReason = response.candidates?.[0]?.finishReason;
465
- await sendEventToPosthog({
466
- client: this.phClient,
457
+ await captureAiGeneration(this.phClient, {
467
458
  ...posthogParams,
468
459
  model: geminiParams.model,
469
460
  provider: 'gemini',
@@ -471,7 +462,7 @@ class WrappedModels {
471
462
  output: formatResponseGemini(response),
472
463
  latency,
473
464
  baseURL: 'https://generativelanguage.googleapis.com',
474
- params: params,
465
+ modelParameters: getModelParams(params),
475
466
  httpStatus: 200,
476
467
  usage: {
477
468
  inputTokens: metadata?.promptTokenCount ?? 0,
@@ -487,8 +478,7 @@ class WrappedModels {
487
478
  return response;
488
479
  } catch (error) {
489
480
  const latency = (Date.now() - startTime) / 1000;
490
- const enrichedError = await sendEventWithErrorToPosthog({
491
- client: this.phClient,
481
+ await captureAiGeneration(this.phClient, {
492
482
  ...posthogParams,
493
483
  model: geminiParams.model,
494
484
  provider: 'gemini',
@@ -496,14 +486,14 @@ class WrappedModels {
496
486
  output: [],
497
487
  latency,
498
488
  baseURL: 'https://generativelanguage.googleapis.com',
499
- params: params,
489
+ modelParameters: getModelParams(params),
500
490
  usage: {
501
491
  inputTokens: 0,
502
492
  outputTokens: 0
503
493
  },
504
- error: error
494
+ error
505
495
  });
506
- throw enrichedError;
496
+ throw error;
507
497
  }
508
498
  }
509
499
  async *generateContentStream(params) {
@@ -607,8 +597,7 @@ class WrappedModels {
607
597
  role: 'assistant',
608
598
  content: accumulatedContent
609
599
  }] : [];
610
- await sendEventToPosthog({
611
- client: this.phClient,
600
+ await captureAiGeneration(this.phClient, {
612
601
  ...posthogParams,
613
602
  model: geminiParams.model,
614
603
  provider: 'gemini',
@@ -617,7 +606,7 @@ class WrappedModels {
617
606
  latency,
618
607
  timeToFirstToken,
619
608
  baseURL: 'https://generativelanguage.googleapis.com',
620
- params: params,
609
+ modelParameters: getModelParams(params),
621
610
  httpStatus: 200,
622
611
  usage: {
623
612
  ...usage,
@@ -629,8 +618,7 @@ class WrappedModels {
629
618
  });
630
619
  } catch (error) {
631
620
  const latency = (Date.now() - startTime) / 1000;
632
- const enrichedError = await sendEventWithErrorToPosthog({
633
- client: this.phClient,
621
+ await captureAiGeneration(this.phClient, {
634
622
  ...posthogParams,
635
623
  model: geminiParams.model,
636
624
  provider: 'gemini',
@@ -638,14 +626,14 @@ class WrappedModels {
638
626
  output: [],
639
627
  latency,
640
628
  baseURL: 'https://generativelanguage.googleapis.com',
641
- params: params,
629
+ modelParameters: getModelParams(params),
642
630
  usage: {
643
631
  inputTokens: 0,
644
632
  outputTokens: 0
645
633
  },
646
- error: error
634
+ error
647
635
  });
648
- throw enrichedError;
636
+ throw error;
649
637
  }
650
638
  }
651
639
  async embedContent(params) {
@@ -658,8 +646,7 @@ class WrappedModels {
658
646
  const response = await this.client.models.embedContent(geminiParams);
659
647
  const latency = (Date.now() - startTime) / 1000;
660
648
  const inputTokens = extractEmbeddingTokenCount(response);
661
- await sendEventToPosthog({
662
- client: this.phClient,
649
+ await captureAiGeneration(this.phClient, {
663
650
  ...posthogParams,
664
651
  eventType: AIEvent.Embedding,
665
652
  model: geminiParams.model,
@@ -668,7 +655,7 @@ class WrappedModels {
668
655
  output: null,
669
656
  latency,
670
657
  baseURL: 'https://generativelanguage.googleapis.com',
671
- params: params,
658
+ modelParameters: getModelParams(params),
672
659
  httpStatus: 200,
673
660
  usage: {
674
661
  inputTokens
@@ -677,8 +664,7 @@ class WrappedModels {
677
664
  return response;
678
665
  } catch (error) {
679
666
  const latency = (Date.now() - startTime) / 1000;
680
- const enrichedError = await sendEventWithErrorToPosthog({
681
- client: this.phClient,
667
+ await captureAiGeneration(this.phClient, {
682
668
  ...posthogParams,
683
669
  eventType: AIEvent.Embedding,
684
670
  model: geminiParams.model,
@@ -687,13 +673,13 @@ class WrappedModels {
687
673
  output: null,
688
674
  latency,
689
675
  baseURL: 'https://generativelanguage.googleapis.com',
690
- params: params,
676
+ modelParameters: getModelParams(params),
691
677
  usage: {
692
678
  inputTokens: 0
693
679
  },
694
- error: error
680
+ error
695
681
  });
696
- throw enrichedError;
682
+ throw error;
697
683
  }
698
684
  }
699
685
  formatPartsAsContentBlocks(parts) {