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