@posthog/ai 8.16.0 → 8.16.2

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.
Files changed (48) hide show
  1. package/README.md +12 -0
  2. package/dist/adk/index.cjs +147 -9
  3. package/dist/adk/index.cjs.map +1 -1
  4. package/dist/adk/index.mjs +147 -9
  5. package/dist/adk/index.mjs.map +1 -1
  6. package/dist/anthropic/index.cjs +141 -8
  7. package/dist/anthropic/index.cjs.map +1 -1
  8. package/dist/anthropic/index.mjs +141 -8
  9. package/dist/anthropic/index.mjs.map +1 -1
  10. package/dist/claude-agent-sdk/index.cjs +147 -9
  11. package/dist/claude-agent-sdk/index.cjs.map +1 -1
  12. package/dist/claude-agent-sdk/index.mjs +147 -9
  13. package/dist/claude-agent-sdk/index.mjs.map +1 -1
  14. package/dist/gemini/index.cjs +141 -8
  15. package/dist/gemini/index.cjs.map +1 -1
  16. package/dist/gemini/index.mjs +141 -8
  17. package/dist/gemini/index.mjs.map +1 -1
  18. package/dist/index.cjs +149 -15
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +149 -15
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/langchain/index.cjs +141 -7
  23. package/dist/langchain/index.cjs.map +1 -1
  24. package/dist/langchain/index.d.ts +1 -0
  25. package/dist/langchain/index.mjs +141 -7
  26. package/dist/langchain/index.mjs.map +1 -1
  27. package/dist/langchain/middleware/index.cjs +150 -11
  28. package/dist/langchain/middleware/index.cjs.map +1 -1
  29. package/dist/langchain/middleware/index.mjs +150 -11
  30. package/dist/langchain/middleware/index.mjs.map +1 -1
  31. package/dist/openai/index.cjs +154 -8
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.mjs +154 -8
  34. package/dist/openai/index.mjs.map +1 -1
  35. package/dist/openai-agents/index.cjs +104 -3
  36. package/dist/openai-agents/index.cjs.map +1 -1
  37. package/dist/openai-agents/index.mjs +104 -3
  38. package/dist/openai-agents/index.mjs.map +1 -1
  39. package/dist/otel/index.cjs +216 -4
  40. package/dist/otel/index.cjs.map +1 -1
  41. package/dist/otel/index.d.ts +22 -1
  42. package/dist/otel/index.mjs +216 -4
  43. package/dist/otel/index.mjs.map +1 -1
  44. package/dist/vercel/index.cjs +149 -15
  45. package/dist/vercel/index.cjs.map +1 -1
  46. package/dist/vercel/index.mjs +149 -15
  47. package/dist/vercel/index.mjs.map +1 -1
  48. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -228,6 +228,10 @@ function redactBase64DataUrl(str, mediaType) {
228
228
  const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
229
229
  const sanitizeVercel = (data, client) => sanitize(data, client);
230
230
  //#endregion
231
+ //#region src/privacyMode.ts
232
+ /** Per-call settings can turn redaction on but never off. */
233
+ const isPrivacyModeEnabled = (client, privacyMode = false) => privacyMode === true || client?.privacyMode === true || client?.privacy_mode === true || client?.options?.privacyMode === true;
234
+ //#endregion
231
235
  //#region src/utils.ts
232
236
  const TOKEN_PROPERTY_KEYS = /* @__PURE__ */ new Set([
233
237
  "$ai_input_tokens",
@@ -297,7 +301,6 @@ const getModelParams = (params, responseServiceTier) => {
297
301
  if (responseServiceTier != null) modelParams.service_tier = responseServiceTier;
298
302
  return modelParams;
299
303
  };
300
- const isPrivacyModeEnabled = (client, privacyMode) => Boolean(client.privacy_mode || privacyMode);
301
304
  function toSafeString(input) {
302
305
  if (input === void 0 || input === null) return "";
303
306
  if (typeof input === "string") return input;
@@ -409,11 +412,12 @@ function sanitizeValues(obj) {
409
412
  }
410
413
  //#endregion
411
414
  //#region package.json
412
- var version = "8.16.0";
415
+ var version = "8.16.2";
413
416
  //#endregion
414
417
  //#region src/serializeError.ts
415
418
  const DEFAULT_MAX_DEPTH = 3;
416
419
  const MAX_STACK_LINES = 20;
420
+ const OMITTED_FIELD = "requestBodyValues";
417
421
  function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
418
422
  if (depth < 0 || value === null || typeof value !== "object") return value;
419
423
  if (value instanceof Error) {
@@ -422,16 +426,17 @@ function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
422
426
  message: value.message,
423
427
  stack: truncateStack(value.stack)
424
428
  };
425
- for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
429
+ for (const key of Object.keys(value)) if (key !== OMITTED_FIELD) out[key] = serializeError(Reflect.get(value, key), depth - 1);
426
430
  if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
427
431
  return out;
428
432
  }
429
433
  if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
430
434
  return value;
431
435
  }
436
+ const omitRequestBody = (key, value) => key === OMITTED_FIELD ? void 0 : value;
432
437
  function stringifyError(error) {
433
438
  try {
434
- return JSON.stringify(sanitizeValues(serializeError(error)));
439
+ return JSON.stringify(sanitizeValues(serializeError(error)), omitRequestBody);
435
440
  } catch {
436
441
  if (error instanceof Error) return JSON.stringify({
437
442
  name: error.name,
@@ -440,6 +445,105 @@ function stringifyError(error) {
440
445
  return JSON.stringify({ message: String(error) });
441
446
  }
442
447
  }
448
+ const CLASS_NAME = /^[A-Za-z_$][A-Za-z0-9_$.]{0,127}$/;
449
+ const PROVIDER_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
450
+ const PARAM = /^[A-Za-z0-9_.[\]-]{1,128}$/;
451
+ const REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;
452
+ const MAX_CAUSES = 3;
453
+ const readField = (value, key) => {
454
+ if (value === null || typeof value !== "object" && typeof value !== "function") return;
455
+ try {
456
+ return Reflect.get(value, key);
457
+ } catch {
458
+ return;
459
+ }
460
+ };
461
+ const matching = (value, grammar) => typeof value === "string" && grammar.test(value) ? value : void 0;
462
+ const firstMatching = (values, grammar) => {
463
+ for (const value of values) {
464
+ const match = matching(value, grammar);
465
+ if (match !== void 0) return match;
466
+ }
467
+ };
468
+ const className = (value) => {
469
+ if (!(value instanceof Error)) return "NonErrorThrown";
470
+ const name = readField(value, "name");
471
+ return firstMatching([
472
+ name === "Error" ? void 0 : name,
473
+ readField(readField(value, "constructor"), "name"),
474
+ name
475
+ ], CLASS_NAME) ?? "Error";
476
+ };
477
+ const httpStatus = (error) => {
478
+ for (const key of ["status", "statusCode"]) {
479
+ const value = readField(error, key);
480
+ if (typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599) return value;
481
+ }
482
+ };
483
+ const unwrapEnvelope = (body) => {
484
+ const inner = readField(body, "error");
485
+ return inner !== null && typeof inner === "object" ? inner : body;
486
+ };
487
+ const parsedResponseBody = (error) => {
488
+ const responseBody = readField(error, "responseBody");
489
+ if (typeof responseBody !== "string") return;
490
+ try {
491
+ return unwrapEnvelope(JSON.parse(responseBody));
492
+ } catch {
493
+ return;
494
+ }
495
+ };
496
+ const causeNames = (error) => {
497
+ const names = [];
498
+ let cause = readField(error, "cause");
499
+ while (cause !== void 0 && cause !== null && names.length < MAX_CAUSES) {
500
+ names.push(className(cause));
501
+ cause = readField(cause, "cause");
502
+ }
503
+ return names;
504
+ };
505
+ const describeError = (error) => {
506
+ const sources = [
507
+ error,
508
+ unwrapEnvelope(readField(error, "error")),
509
+ parsedResponseBody(error)
510
+ ];
511
+ const codes = /* @__PURE__ */ new Set();
512
+ for (const source of sources) for (const key of [
513
+ "code",
514
+ "type",
515
+ "status"
516
+ ]) {
517
+ const code = matching(readField(source, key), PROVIDER_CODE);
518
+ if (code !== void 0) codes.add(code);
519
+ }
520
+ const status = httpStatus(error);
521
+ const param = firstMatching(sources.map((source) => readField(source, "param")), PARAM);
522
+ const requestId = firstMatching([readField(error, "requestID"), readField(error, "request_id")], REQUEST_ID);
523
+ const causes = causeNames(error);
524
+ let summary = className(error);
525
+ if (status !== void 0) summary += `: ${status}`;
526
+ for (const code of codes) summary += ` ${code}`;
527
+ if (param !== void 0) summary += ` param=${param}`;
528
+ if (requestId !== void 0) summary += ` request_id=${requestId}`;
529
+ if (causes.length > 0) summary += ` (caused by ${causes.join(", ")})`;
530
+ return summary;
531
+ };
532
+ /**
533
+ * Describes an error for `$ai_error` without its message, stack, or arbitrary fields, which
534
+ * routinely echo the prompt. Only structured fields that pass a strict grammar are kept.
535
+ * The format matches the Python SDK.
536
+ */
537
+ function summarizeErrorForPrivacy(error) {
538
+ try {
539
+ return describeError(error);
540
+ } catch {
541
+ return error instanceof Error ? "Error" : "NonErrorThrown";
542
+ }
543
+ }
544
+ function formatAiError(error, privacyMode) {
545
+ return privacyMode ? summarizeErrorForPrivacy(error) : stringifyError(error);
546
+ }
443
547
  function truncateStack(stack) {
444
548
  if (!stack) return stack;
445
549
  const lines = stack.split("\n");
@@ -447,6 +551,33 @@ function truncateStack(stack) {
447
551
  return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
448
552
  }
449
553
  //#endregion
554
+ //#region src/mcpCredentials.ts
555
+ const MCP_CREDENTIAL_FIELDS = ["headers", "authorization"];
556
+ const MCP_PROVIDER_TOOL_IDS = /* @__PURE__ */ new Set([
557
+ "openai.mcp",
558
+ "xai.mcp",
559
+ "google.mcp_server"
560
+ ]);
561
+ const isMcpProviderTool = (tool) => (tool.type === "provider" || tool.type === "provider-defined") && typeof tool.id === "string" && MCP_PROVIDER_TOOL_IDS.has(tool.id);
562
+ const withoutMcpCredentials = (config) => {
563
+ const redacted = { ...config };
564
+ for (const field of MCP_CREDENTIAL_FIELDS) if (field in redacted) redacted[field] = "[REDACTED]";
565
+ return redacted;
566
+ };
567
+ /** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */
568
+ function redactMcpToolDefinition(tool) {
569
+ if (!isObject(tool)) return tool;
570
+ if (tool.type === "mcp") return withoutMcpCredentials(tool);
571
+ if (isMcpProviderTool(tool) && isObject(tool.args)) return {
572
+ ...tool,
573
+ args: withoutMcpCredentials(tool.args)
574
+ };
575
+ return tool;
576
+ }
577
+ function redactMcpToolCredentials(tools) {
578
+ return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools;
579
+ }
580
+ //#endregion
450
581
  //#region src/gatewayWarning.ts
451
582
  const POSTHOG_AI_GATEWAY_HOSTS = [
452
583
  "gateway.posthog.com",
@@ -509,13 +640,15 @@ const captureAiGeneration = async (client, options) => {
509
640
  }
510
641
  let exceptionId;
511
642
  if (client.options?.enableExceptionAutocapture) {
512
- exceptionId = uuidv7();
513
- client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
643
+ if (!shouldRedact) {
644
+ exceptionId = uuidv7();
645
+ client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
646
+ }
514
647
  if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
515
648
  }
516
649
  errorData = {
517
650
  $ai_is_error: true,
518
- $ai_error: stringifyError(options.error),
651
+ $ai_error: formatAiError(options.error, shouldRedact),
519
652
  $exception_event_id: exceptionId
520
653
  };
521
654
  }
@@ -556,7 +689,7 @@ const captureAiGeneration = async (client, options) => {
556
689
  $ai_tokens_source: getTokensSource(options.properties),
557
690
  ...options.distinctId ? {} : { $process_person_profile: false },
558
691
  ...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
559
- ...options.tools ? { $ai_tools: options.tools } : {},
692
+ ...options.tools ? { $ai_tools: redactMcpToolCredentials(options.tools) } : {},
560
693
  ...options.completionId ? { $ai_completion_id: options.completionId } : {},
561
694
  ...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
562
695
  ...errorData,
@@ -820,12 +953,13 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
820
953
  $ai_framework_version: model.specificationVersion === "v3" ? "6" : "5"
821
954
  }
822
955
  };
956
+ const privacyMode = isPrivacyModeEnabled(phClient, mergedOptions.posthogPrivacyMode);
823
957
  const baseOptions = {
824
958
  distinctId: mergedOptions.posthogDistinctId,
825
959
  traceId,
826
960
  properties: mergedOptions.posthogProperties,
827
961
  groups: mergedOptions.posthogGroups,
828
- privacyMode: mergedOptions.posthogPrivacyMode,
962
+ privacyMode,
829
963
  modelOverride: mergedOptions.posthogModelOverride,
830
964
  providerOverride: mergedOptions.posthogProviderOverride,
831
965
  costOverride: mergedOptions.posthogCostOverride,
@@ -853,7 +987,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
853
987
  const usageObj = result.usage;
854
988
  const rawUsageData = {
855
989
  usage: result.usage,
856
- providerMetadata
990
+ ...privacyMode ? {} : { providerMetadata }
857
991
  };
858
992
  if (result.response && typeof result.response === "object") {
859
993
  const responseBody = result.response.body;
@@ -875,7 +1009,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
875
1009
  ...baseOptions,
876
1010
  model: modelId,
877
1011
  provider,
878
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1012
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
879
1013
  output: content,
880
1014
  latency,
881
1015
  baseURL,
@@ -892,7 +1026,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
892
1026
  ...baseOptions,
893
1027
  model: modelId,
894
1028
  provider: extractProvider(model),
895
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1029
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
896
1030
  output: [],
897
1031
  latency: (Date.now() - startTime) / 1e3,
898
1032
  baseURL,
@@ -1018,7 +1152,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1018
1152
  const finalUsage = {
1019
1153
  ...usage,
1020
1154
  webSearchCount,
1021
- rawUsage: {
1155
+ rawUsage: privacyMode ? { usage } : {
1022
1156
  usage,
1023
1157
  providerMetadata
1024
1158
  }
@@ -1029,7 +1163,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1029
1163
  ...baseOptions,
1030
1164
  model: modelId,
1031
1165
  provider,
1032
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1166
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1033
1167
  output,
1034
1168
  latency,
1035
1169
  timeToFirstToken,
@@ -1079,7 +1213,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1079
1213
  ...baseOptions,
1080
1214
  model: modelId,
1081
1215
  provider,
1082
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1216
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1083
1217
  output: [],
1084
1218
  latency: (Date.now() - startTime) / 1e3,
1085
1219
  baseURL,