@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 AnthropicOriginal from '@anthropic-ai/sdk';
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 isObject = value => {
@@ -203,73 +201,69 @@ function addDefaults(params) {
203
201
  traceId: params.traceId ?? v4()
204
202
  };
205
203
  }
206
- const sendEventWithErrorToPosthog = async ({
207
- client,
208
- traceId,
209
- error,
210
- ...args
211
- }) => {
212
- const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
213
- const properties = {
214
- client,
215
- traceId,
216
- httpStatus,
217
- error: JSON.stringify(error),
218
- ...args
219
- };
220
- const enrichedError = error;
221
- if (client.options?.enableExceptionAutocapture) {
222
- // assign a uuid that can be used to link the trace and exception events
223
- const exceptionId = uuidv7();
224
- client.captureException(error, undefined, {
225
- $ai_trace_id: traceId
226
- }, exceptionId);
227
- enrichedError.__posthog_previously_captured_error = true;
228
- properties.exceptionId = exceptionId;
229
- }
230
- await sendEventToPosthog(properties);
231
- return enrichedError;
232
- };
233
- const sendEventToPosthog = async ({
234
- client,
235
- eventType = AIEvent.Generation,
236
- distinctId,
237
- traceId,
238
- model,
239
- provider,
240
- input,
241
- output,
242
- latency,
243
- timeToFirstToken,
244
- baseURL,
245
- params,
246
- httpStatus = 200,
247
- usage = {},
248
- error,
249
- exceptionId,
250
- stopReason,
251
- tools,
252
- captureImmediate = false
253
- }) => {
204
+
205
+ var version = "7.17.0";
206
+
207
+ /**
208
+ * Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
209
+ * directly so that any caller — first-party SDK wrappers and external code
210
+ * alike produces an identical event.
211
+ */
212
+
213
+ /**
214
+ * Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
215
+ *
216
+ * This is the canonical primitive that every `@posthog/ai` wrapper
217
+ * (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
218
+ * external code can use it directly to instrument LLM calls made through
219
+ * arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
220
+ * same events the SDK wrappers produce.
221
+ *
222
+ * When `error` is set, the event is captured as an error. If the error is an
223
+ * object, it is mutated in place to set `__posthog_previously_captured_error`
224
+ * so callers can re-throw the original error reference safely.
225
+ */
226
+ const captureAiGeneration = async (client, options) => {
254
227
  if (!client.capture) {
255
- return Promise.resolve();
228
+ return;
256
229
  }
257
- // sanitize input and output for UTF-8 validity
258
- const safeInput = sanitizeValues(input);
259
- const safeOutput = sanitizeValues(output);
260
- const safeError = sanitizeValues(error);
230
+ const traceId = options.traceId ?? v4();
231
+ const eventType = options.eventType ?? AIEvent.Generation;
232
+ const privacyMode = options.privacyMode ?? false;
233
+ const usage = options.usage ?? {};
234
+ const safeInput = sanitizeValues(options.input);
235
+ const safeOutput = sanitizeValues(options.output);
236
+ let httpStatus = options.httpStatus;
261
237
  let errorData = {};
262
- if (error) {
238
+ if (options.error) {
239
+ if (httpStatus === undefined) {
240
+ if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
241
+ httpStatus = options.error.status;
242
+ } else {
243
+ httpStatus = 500;
244
+ }
245
+ }
246
+ let exceptionId;
247
+ if (client.options?.enableExceptionAutocapture) {
248
+ exceptionId = uuidv7();
249
+ client.captureException(options.error, undefined, {
250
+ $ai_trace_id: traceId
251
+ }, exceptionId);
252
+ if (typeof options.error === 'object') {
253
+ options.error.__posthog_previously_captured_error = true;
254
+ }
255
+ }
263
256
  errorData = {
264
257
  $ai_is_error: true,
265
- $ai_error: safeError,
258
+ $ai_error: sanitizeValues(JSON.stringify(options.error)),
266
259
  $exception_event_id: exceptionId
267
260
  };
268
261
  }
262
+ httpStatus = httpStatus ?? 200;
269
263
  let costOverrideData = {};
270
- if (params.posthogCostOverride) {
271
- const inputCostUSD = (params.posthogCostOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
272
- const outputCostUSD = (params.posthogCostOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
264
+ if (options.costOverride) {
265
+ const inputCostUSD = (options.costOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
266
+ const outputCostUSD = (options.costOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
273
267
  costOverrideData = {
274
268
  $ai_input_cost_usd: inputCostUSD,
275
269
  $ai_output_cost_usd: outputCostUSD,
@@ -296,50 +290,48 @@ const sendEventToPosthog = async ({
296
290
  const properties = {
297
291
  $ai_lib: 'posthog-ai',
298
292
  $ai_lib_version: version,
299
- $ai_provider: params.posthogProviderOverride ?? provider,
300
- $ai_model: params.posthogModelOverride ?? model,
301
- $ai_model_parameters: getModelParams(params),
302
- $ai_input: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeInput),
303
- $ai_output_choices: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeOutput),
293
+ $ai_provider: options.providerOverride ?? options.provider,
294
+ $ai_model: options.modelOverride ?? options.model,
295
+ $ai_model_parameters: options.modelParameters ?? {},
296
+ $ai_input: withPrivacyMode(client, privacyMode, safeInput),
297
+ $ai_output_choices: withPrivacyMode(client, privacyMode, safeOutput),
304
298
  $ai_http_status: httpStatus,
305
299
  $ai_input_tokens: usage.inputTokens ?? 0,
306
300
  ...(usage.outputTokens !== undefined ? {
307
301
  $ai_output_tokens: usage.outputTokens
308
302
  } : {}),
309
303
  ...additionalTokenValues,
310
- $ai_latency: latency,
311
- ...(timeToFirstToken !== undefined ? {
312
- $ai_time_to_first_token: timeToFirstToken
304
+ $ai_latency: options.latency ?? 0,
305
+ ...(options.timeToFirstToken !== undefined ? {
306
+ $ai_time_to_first_token: options.timeToFirstToken
313
307
  } : {}),
314
308
  $ai_trace_id: traceId,
315
- $ai_base_url: baseURL,
316
- ...params.posthogProperties,
317
- $ai_tokens_source: getTokensSource(params.posthogProperties),
318
- ...(distinctId ? {} : {
309
+ $ai_base_url: options.baseURL ?? '',
310
+ ...options.properties,
311
+ $ai_tokens_source: getTokensSource(options.properties),
312
+ ...(options.distinctId ? {} : {
319
313
  $process_person_profile: false
320
314
  }),
321
- ...(stopReason ? {
322
- $ai_stop_reason: stopReason
315
+ ...(options.stopReason ? {
316
+ $ai_stop_reason: options.stopReason
323
317
  } : {}),
324
- ...(tools ? {
325
- $ai_tools: tools
318
+ ...(options.tools ? {
319
+ $ai_tools: options.tools
326
320
  } : {}),
327
321
  ...errorData,
328
322
  ...costOverrideData
329
323
  };
330
324
  const event = {
331
- distinctId: distinctId ?? traceId,
325
+ distinctId: options.distinctId ?? traceId,
332
326
  event: eventType,
333
327
  properties,
334
- groups: params.posthogGroups
328
+ groups: options.groups
335
329
  };
336
- if (captureImmediate) {
337
- // await capture promise to send single event in serverless environments
330
+ if (options.captureImmediate) {
338
331
  await client.captureImmediate(event);
339
332
  } else {
340
333
  client.capture(event);
341
334
  }
342
- return Promise.resolve();
343
335
  };
344
336
 
345
337
  class PostHogAnthropic extends AnthropicOriginal {
@@ -501,8 +493,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
501
493
  text: accumulatedContent
502
494
  }]
503
495
  }];
504
- await sendEventToPosthog({
505
- client: this.phClient,
496
+ await captureAiGeneration(this.phClient, {
506
497
  ...posthogParams,
507
498
  model: anthropicParams.model,
508
499
  provider: 'anthropic',
@@ -511,15 +502,14 @@ class WrappedMessages extends AnthropicOriginal.Messages {
511
502
  latency,
512
503
  timeToFirstToken,
513
504
  baseURL: this.baseURL,
514
- params: body,
505
+ modelParameters: getModelParams(body),
515
506
  httpStatus: 200,
516
507
  usage,
517
508
  stopReason,
518
509
  tools: availableTools
519
510
  });
520
511
  } catch (error) {
521
- const enrichedError = await sendEventWithErrorToPosthog({
522
- client: this.phClient,
512
+ await captureAiGeneration(this.phClient, {
523
513
  ...posthogParams,
524
514
  model: anthropicParams.model,
525
515
  provider: 'anthropic',
@@ -527,14 +517,14 @@ class WrappedMessages extends AnthropicOriginal.Messages {
527
517
  output: [],
528
518
  latency: 0,
529
519
  baseURL: this.baseURL,
530
- params: body,
520
+ modelParameters: getModelParams(body),
531
521
  usage: {
532
522
  inputTokens: 0,
533
523
  outputTokens: 0
534
524
  },
535
525
  error: error
536
526
  });
537
- throw enrichedError;
527
+ throw error;
538
528
  }
539
529
  })();
540
530
 
@@ -548,8 +538,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
548
538
  if ('content' in result) {
549
539
  const latency = (Date.now() - startTime) / 1000;
550
540
  const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
551
- await sendEventToPosthog({
552
- client: this.phClient,
541
+ await captureAiGeneration(this.phClient, {
553
542
  ...posthogParams,
554
543
  model: anthropicParams.model,
555
544
  provider: 'anthropic',
@@ -557,7 +546,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
557
546
  output: formatResponseAnthropic(result),
558
547
  latency,
559
548
  baseURL: this.baseURL,
560
- params: body,
549
+ modelParameters: getModelParams(body),
561
550
  httpStatus: 200,
562
551
  usage: {
563
552
  inputTokens: result.usage.input_tokens ?? 0,
@@ -573,8 +562,7 @@ class WrappedMessages extends AnthropicOriginal.Messages {
573
562
  }
574
563
  return result;
575
564
  }, async error => {
576
- await sendEventToPosthog({
577
- client: this.phClient,
565
+ await captureAiGeneration(this.phClient, {
578
566
  ...posthogParams,
579
567
  model: anthropicParams.model,
580
568
  provider: 'anthropic',
@@ -582,13 +570,13 @@ class WrappedMessages extends AnthropicOriginal.Messages {
582
570
  output: [],
583
571
  latency: 0,
584
572
  baseURL: this.baseURL,
585
- params: body,
573
+ modelParameters: getModelParams(body),
586
574
  httpStatus: error?.status ? error.status : 500,
587
575
  usage: {
588
576
  inputTokens: 0,
589
577
  outputTokens: 0
590
578
  },
591
- error: JSON.stringify(error)
579
+ error: error
592
580
  });
593
581
  throw error;
594
582
  });