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