@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.
@@ -3,8 +3,6 @@
3
3
  var uuid = require('uuid');
4
4
  var core = require('@posthog/core');
5
5
 
6
- var version = "7.16.14";
7
-
8
6
  // Type guards for safer type checking
9
7
 
10
8
  const isString = value => {
@@ -294,73 +292,69 @@ function sanitizeValues(obj) {
294
292
  }
295
293
  return jsonSafe;
296
294
  }
297
- const sendEventWithErrorToPosthog = async ({
298
- client,
299
- traceId,
300
- error,
301
- ...args
302
- }) => {
303
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
304
- const properties = {
305
- client,
306
- traceId,
307
- httpStatus,
308
- error: JSON.stringify(error),
309
- ...args
310
- };
311
- const enrichedError = error;
312
- if (client.options?.enableExceptionAutocapture) {
313
- // assign a uuid that can be used to link the trace and exception events
314
- const exceptionId = core.uuidv7();
315
- client.captureException(error, undefined, {
316
- $ai_trace_id: traceId
317
- }, exceptionId);
318
- enrichedError.__posthog_previously_captured_error = true;
319
- properties.exceptionId = exceptionId;
320
- }
321
- await sendEventToPosthog(properties);
322
- return enrichedError;
323
- };
324
- const sendEventToPosthog = async ({
325
- client,
326
- eventType = AIEvent.Generation,
327
- distinctId,
328
- traceId,
329
- model,
330
- provider,
331
- input,
332
- output,
333
- latency,
334
- timeToFirstToken,
335
- baseURL,
336
- params,
337
- httpStatus = 200,
338
- usage = {},
339
- error,
340
- exceptionId,
341
- stopReason,
342
- tools,
343
- captureImmediate = false
344
- }) => {
295
+
296
+ var version = "7.17.0";
297
+
298
+ /**
299
+ * Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
300
+ * directly so that any caller — first-party SDK wrappers and external code
301
+ * alike produces an identical event.
302
+ */
303
+
304
+ /**
305
+ * Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
306
+ *
307
+ * This is the canonical primitive that every `@posthog/ai` wrapper
308
+ * (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
309
+ * external code can use it directly to instrument LLM calls made through
310
+ * arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
311
+ * same events the SDK wrappers produce.
312
+ *
313
+ * When `error` is set, the event is captured as an error. If the error is an
314
+ * object, it is mutated in place to set `__posthog_previously_captured_error`
315
+ * so callers can re-throw the original error reference safely.
316
+ */
317
+ const captureAiGeneration = async (client, options) => {
345
318
  if (!client.capture) {
346
- return Promise.resolve();
319
+ return;
347
320
  }
348
- // sanitize input and output for UTF-8 validity
349
- const safeInput = sanitizeValues(input);
350
- const safeOutput = sanitizeValues(output);
351
- const safeError = sanitizeValues(error);
321
+ const traceId = options.traceId ?? uuid.v4();
322
+ const eventType = options.eventType ?? AIEvent.Generation;
323
+ const privacyMode = options.privacyMode ?? false;
324
+ const usage = options.usage ?? {};
325
+ const safeInput = sanitizeValues(options.input);
326
+ const safeOutput = sanitizeValues(options.output);
327
+ let httpStatus = options.httpStatus;
352
328
  let errorData = {};
353
- if (error) {
329
+ if (options.error) {
330
+ if (httpStatus === undefined) {
331
+ if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
332
+ httpStatus = options.error.status;
333
+ } else {
334
+ httpStatus = 500;
335
+ }
336
+ }
337
+ let exceptionId;
338
+ if (client.options?.enableExceptionAutocapture) {
339
+ exceptionId = core.uuidv7();
340
+ client.captureException(options.error, undefined, {
341
+ $ai_trace_id: traceId
342
+ }, exceptionId);
343
+ if (typeof options.error === 'object') {
344
+ options.error.__posthog_previously_captured_error = true;
345
+ }
346
+ }
354
347
  errorData = {
355
348
  $ai_is_error: true,
356
- $ai_error: safeError,
349
+ $ai_error: sanitizeValues(JSON.stringify(options.error)),
357
350
  $exception_event_id: exceptionId
358
351
  };
359
352
  }
353
+ httpStatus = httpStatus ?? 200;
360
354
  let costOverrideData = {};
361
- if (params.posthogCostOverride) {
362
- const inputCostUSD = (params.posthogCostOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
363
- const outputCostUSD = (params.posthogCostOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
355
+ if (options.costOverride) {
356
+ const inputCostUSD = (options.costOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
357
+ const outputCostUSD = (options.costOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
364
358
  costOverrideData = {
365
359
  $ai_input_cost_usd: inputCostUSD,
366
360
  $ai_output_cost_usd: outputCostUSD,
@@ -387,50 +381,48 @@ const sendEventToPosthog = async ({
387
381
  const properties = {
388
382
  $ai_lib: 'posthog-ai',
389
383
  $ai_lib_version: version,
390
- $ai_provider: params.posthogProviderOverride ?? provider,
391
- $ai_model: params.posthogModelOverride ?? model,
392
- $ai_model_parameters: getModelParams(params),
393
- $ai_input: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeInput),
394
- $ai_output_choices: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeOutput),
384
+ $ai_provider: options.providerOverride ?? options.provider,
385
+ $ai_model: options.modelOverride ?? options.model,
386
+ $ai_model_parameters: options.modelParameters ?? {},
387
+ $ai_input: withPrivacyMode(client, privacyMode, safeInput),
388
+ $ai_output_choices: withPrivacyMode(client, privacyMode, safeOutput),
395
389
  $ai_http_status: httpStatus,
396
390
  $ai_input_tokens: usage.inputTokens ?? 0,
397
391
  ...(usage.outputTokens !== undefined ? {
398
392
  $ai_output_tokens: usage.outputTokens
399
393
  } : {}),
400
394
  ...additionalTokenValues,
401
- $ai_latency: latency,
402
- ...(timeToFirstToken !== undefined ? {
403
- $ai_time_to_first_token: timeToFirstToken
395
+ $ai_latency: options.latency ?? 0,
396
+ ...(options.timeToFirstToken !== undefined ? {
397
+ $ai_time_to_first_token: options.timeToFirstToken
404
398
  } : {}),
405
399
  $ai_trace_id: traceId,
406
- $ai_base_url: baseURL,
407
- ...params.posthogProperties,
408
- $ai_tokens_source: getTokensSource(params.posthogProperties),
409
- ...(distinctId ? {} : {
400
+ $ai_base_url: options.baseURL ?? '',
401
+ ...options.properties,
402
+ $ai_tokens_source: getTokensSource(options.properties),
403
+ ...(options.distinctId ? {} : {
410
404
  $process_person_profile: false
411
405
  }),
412
- ...(stopReason ? {
413
- $ai_stop_reason: stopReason
406
+ ...(options.stopReason ? {
407
+ $ai_stop_reason: options.stopReason
414
408
  } : {}),
415
- ...(tools ? {
416
- $ai_tools: tools
409
+ ...(options.tools ? {
410
+ $ai_tools: options.tools
417
411
  } : {}),
418
412
  ...errorData,
419
413
  ...costOverrideData
420
414
  };
421
415
  const event = {
422
- distinctId: distinctId ?? traceId,
416
+ distinctId: options.distinctId ?? traceId,
423
417
  event: eventType,
424
418
  properties,
425
- groups: params.posthogGroups
419
+ groups: options.groups
426
420
  };
427
- if (captureImmediate) {
428
- // await capture promise to send single event in serverless environments
421
+ if (options.captureImmediate) {
429
422
  await client.captureImmediate(event);
430
423
  } else {
431
424
  client.capture(event);
432
425
  }
433
- return Promise.resolve();
434
426
  };
435
427
 
436
428
  // Union types for dual version support
@@ -772,6 +764,19 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
772
764
  }
773
765
  };
774
766
 
767
+ // Shared `captureAiGeneration` options for every call site in this wrapper.
768
+ const baseOptions = {
769
+ distinctId: mergedOptions.posthogDistinctId,
770
+ traceId,
771
+ properties: mergedOptions.posthogProperties,
772
+ groups: mergedOptions.posthogGroups,
773
+ privacyMode: mergedOptions.posthogPrivacyMode,
774
+ modelOverride: mergedOptions.posthogModelOverride,
775
+ providerOverride: mergedOptions.posthogProviderOverride,
776
+ costOverride: mergedOptions.posthogCostOverride,
777
+ captureImmediate: mergedOptions.posthogCaptureImmediate
778
+ };
779
+
775
780
  // Create wrapped model using Object.create to preserve the prototype chain
776
781
  // This automatically inherits all properties (including getters) from the model
777
782
  const wrappedModel = Object.create(model, {
@@ -828,46 +833,40 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
828
833
  // Extract finish reason - V2 returns a string, V3 returns an object with .unified
829
834
  const rawFinishReason = result.finishReason;
830
835
  const finishReasonStr = typeof rawFinishReason === 'string' ? rawFinishReason : rawFinishReason && typeof rawFinishReason === 'object' && 'unified' in rawFinishReason ? String(rawFinishReason.unified) : undefined;
831
- await sendEventToPosthog({
832
- client: phClient,
833
- distinctId: mergedOptions.posthogDistinctId,
834
- traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
836
+ await captureAiGeneration(phClient, {
837
+ ...baseOptions,
835
838
  model: modelId,
836
839
  provider: provider,
837
840
  input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
838
841
  output: content,
839
842
  latency,
840
843
  baseURL,
841
- params: mergedParams,
844
+ modelParameters: getModelParams(mergedParams),
842
845
  httpStatus: 200,
843
846
  usage,
844
847
  stopReason: finishReasonStr,
845
- tools: availableTools,
846
- captureImmediate: mergedOptions.posthogCaptureImmediate
848
+ tools: availableTools
847
849
  });
848
850
  return result;
849
851
  } catch (error) {
850
852
  const modelId = model.modelId;
851
- const enrichedError = await sendEventWithErrorToPosthog({
852
- client: phClient,
853
- distinctId: mergedOptions.posthogDistinctId,
854
- traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
853
+ await captureAiGeneration(phClient, {
854
+ ...baseOptions,
855
855
  model: modelId,
856
856
  provider: model.provider,
857
857
  input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
858
858
  output: [],
859
859
  latency: 0,
860
860
  baseURL: '',
861
- params: mergedParams,
861
+ modelParameters: getModelParams(mergedParams),
862
862
  usage: {
863
863
  inputTokens: 0,
864
864
  outputTokens: 0
865
865
  },
866
866
  error: error,
867
- tools: availableTools,
868
- captureImmediate: mergedOptions.posthogCaptureImmediate
867
+ tools: availableTools
869
868
  });
870
- throw enrichedError;
869
+ throw error;
871
870
  }
872
871
  },
873
872
  writable: true,
@@ -1019,10 +1018,8 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1019
1018
  }
1020
1019
  };
1021
1020
  adjustAnthropicV3CacheTokens(model, modelId, provider, finalUsage);
1022
- await sendEventToPosthog({
1023
- client: phClient,
1024
- distinctId: mergedOptions.posthogDistinctId,
1025
- traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
1021
+ await captureAiGeneration(phClient, {
1022
+ ...baseOptions,
1026
1023
  model: modelId,
1027
1024
  provider: provider,
1028
1025
  input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
@@ -1030,12 +1027,11 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1030
1027
  latency,
1031
1028
  timeToFirstToken,
1032
1029
  baseURL,
1033
- params: mergedParams,
1030
+ modelParameters: getModelParams(mergedParams),
1034
1031
  httpStatus: 200,
1035
1032
  usage: finalUsage,
1036
1033
  stopReason,
1037
- tools: availableTools,
1038
- captureImmediate: mergedOptions.posthogCaptureImmediate
1034
+ tools: availableTools
1039
1035
  });
1040
1036
  }
1041
1037
  });
@@ -1044,26 +1040,23 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1044
1040
  ...rest
1045
1041
  };
1046
1042
  } catch (error) {
1047
- const enrichedError = await sendEventWithErrorToPosthog({
1048
- client: phClient,
1049
- distinctId: mergedOptions.posthogDistinctId,
1050
- traceId: mergedOptions.posthogTraceId ?? uuid.v4(),
1043
+ await captureAiGeneration(phClient, {
1044
+ ...baseOptions,
1051
1045
  model: modelId,
1052
1046
  provider: provider,
1053
1047
  input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
1054
1048
  output: [],
1055
1049
  latency: 0,
1056
1050
  baseURL: '',
1057
- params: mergedParams,
1051
+ modelParameters: getModelParams(mergedParams),
1058
1052
  usage: {
1059
1053
  inputTokens: 0,
1060
1054
  outputTokens: 0
1061
1055
  },
1062
1056
  error: error,
1063
- tools: availableTools,
1064
- captureImmediate: mergedOptions.posthogCaptureImmediate
1057
+ tools: availableTools
1065
1058
  });
1066
- throw enrichedError;
1059
+ throw error;
1067
1060
  }
1068
1061
  },
1069
1062
  writable: true,