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