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