@posthog/ai 8.16.1 → 8.16.3

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 +157 -11
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.mjs +157 -11
  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 +4 -4
@@ -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;
@@ -405,11 +408,12 @@ function sanitizeValues(obj) {
405
408
  }
406
409
  //#endregion
407
410
  //#region package.json
408
- var version = "8.16.1";
411
+ var version = "8.16.3";
409
412
  //#endregion
410
413
  //#region src/serializeError.ts
411
414
  const DEFAULT_MAX_DEPTH = 3;
412
415
  const MAX_STACK_LINES = 20;
416
+ const OMITTED_FIELD = "requestBodyValues";
413
417
  function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
414
418
  if (depth < 0 || value === null || typeof value !== "object") return value;
415
419
  if (value instanceof Error) {
@@ -418,16 +422,17 @@ function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
418
422
  message: value.message,
419
423
  stack: truncateStack(value.stack)
420
424
  };
421
- for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
425
+ for (const key of Object.keys(value)) if (key !== OMITTED_FIELD) out[key] = serializeError(Reflect.get(value, key), depth - 1);
422
426
  if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
423
427
  return out;
424
428
  }
425
429
  if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
426
430
  return value;
427
431
  }
432
+ const omitRequestBody = (key, value) => key === OMITTED_FIELD ? void 0 : value;
428
433
  function stringifyError(error) {
429
434
  try {
430
- return JSON.stringify(sanitizeValues(serializeError(error)));
435
+ return JSON.stringify(sanitizeValues(serializeError(error)), omitRequestBody);
431
436
  } catch {
432
437
  if (error instanceof Error) return JSON.stringify({
433
438
  name: error.name,
@@ -436,6 +441,105 @@ function stringifyError(error) {
436
441
  return JSON.stringify({ message: String(error) });
437
442
  }
438
443
  }
444
+ const CLASS_NAME = /^[A-Za-z_$][A-Za-z0-9_$.]{0,127}$/;
445
+ const PROVIDER_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
446
+ const PARAM = /^[A-Za-z0-9_.[\]-]{1,128}$/;
447
+ const REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;
448
+ const MAX_CAUSES = 3;
449
+ const readField = (value, key) => {
450
+ if (value === null || typeof value !== "object" && typeof value !== "function") return;
451
+ try {
452
+ return Reflect.get(value, key);
453
+ } catch {
454
+ return;
455
+ }
456
+ };
457
+ const matching = (value, grammar) => typeof value === "string" && grammar.test(value) ? value : void 0;
458
+ const firstMatching = (values, grammar) => {
459
+ for (const value of values) {
460
+ const match = matching(value, grammar);
461
+ if (match !== void 0) return match;
462
+ }
463
+ };
464
+ const className = (value) => {
465
+ if (!(value instanceof Error)) return "NonErrorThrown";
466
+ const name = readField(value, "name");
467
+ return firstMatching([
468
+ name === "Error" ? void 0 : name,
469
+ readField(readField(value, "constructor"), "name"),
470
+ name
471
+ ], CLASS_NAME) ?? "Error";
472
+ };
473
+ const httpStatus = (error) => {
474
+ for (const key of ["status", "statusCode"]) {
475
+ const value = readField(error, key);
476
+ if (typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599) return value;
477
+ }
478
+ };
479
+ const unwrapEnvelope = (body) => {
480
+ const inner = readField(body, "error");
481
+ return inner !== null && typeof inner === "object" ? inner : body;
482
+ };
483
+ const parsedResponseBody = (error) => {
484
+ const responseBody = readField(error, "responseBody");
485
+ if (typeof responseBody !== "string") return;
486
+ try {
487
+ return unwrapEnvelope(JSON.parse(responseBody));
488
+ } catch {
489
+ return;
490
+ }
491
+ };
492
+ const causeNames = (error) => {
493
+ const names = [];
494
+ let cause = readField(error, "cause");
495
+ while (cause !== void 0 && cause !== null && names.length < MAX_CAUSES) {
496
+ names.push(className(cause));
497
+ cause = readField(cause, "cause");
498
+ }
499
+ return names;
500
+ };
501
+ const describeError = (error) => {
502
+ const sources = [
503
+ error,
504
+ unwrapEnvelope(readField(error, "error")),
505
+ parsedResponseBody(error)
506
+ ];
507
+ const codes = /* @__PURE__ */ new Set();
508
+ for (const source of sources) for (const key of [
509
+ "code",
510
+ "type",
511
+ "status"
512
+ ]) {
513
+ const code = matching(readField(source, key), PROVIDER_CODE);
514
+ if (code !== void 0) codes.add(code);
515
+ }
516
+ const status = httpStatus(error);
517
+ const param = firstMatching(sources.map((source) => readField(source, "param")), PARAM);
518
+ const requestId = firstMatching([readField(error, "requestID"), readField(error, "request_id")], REQUEST_ID);
519
+ const causes = causeNames(error);
520
+ let summary = className(error);
521
+ if (status !== void 0) summary += `: ${status}`;
522
+ for (const code of codes) summary += ` ${code}`;
523
+ if (param !== void 0) summary += ` param=${param}`;
524
+ if (requestId !== void 0) summary += ` request_id=${requestId}`;
525
+ if (causes.length > 0) summary += ` (caused by ${causes.join(", ")})`;
526
+ return summary;
527
+ };
528
+ /**
529
+ * Describes an error for `$ai_error` without its message, stack, or arbitrary fields, which
530
+ * routinely echo the prompt. Only structured fields that pass a strict grammar are kept.
531
+ * The format matches the Python SDK.
532
+ */
533
+ function summarizeErrorForPrivacy(error) {
534
+ try {
535
+ return describeError(error);
536
+ } catch {
537
+ return error instanceof Error ? "Error" : "NonErrorThrown";
538
+ }
539
+ }
540
+ function formatAiError(error, privacyMode) {
541
+ return privacyMode ? summarizeErrorForPrivacy(error) : stringifyError(error);
542
+ }
439
543
  function truncateStack(stack) {
440
544
  if (!stack) return stack;
441
545
  const lines = stack.split("\n");
@@ -443,6 +547,33 @@ function truncateStack(stack) {
443
547
  return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
444
548
  }
445
549
  //#endregion
550
+ //#region src/mcpCredentials.ts
551
+ const MCP_CREDENTIAL_FIELDS = ["headers", "authorization"];
552
+ const MCP_PROVIDER_TOOL_IDS = /* @__PURE__ */ new Set([
553
+ "openai.mcp",
554
+ "xai.mcp",
555
+ "google.mcp_server"
556
+ ]);
557
+ const isMcpProviderTool = (tool) => (tool.type === "provider" || tool.type === "provider-defined") && typeof tool.id === "string" && MCP_PROVIDER_TOOL_IDS.has(tool.id);
558
+ const withoutMcpCredentials = (config) => {
559
+ const redacted = { ...config };
560
+ for (const field of MCP_CREDENTIAL_FIELDS) if (field in redacted) redacted[field] = "[REDACTED]";
561
+ return redacted;
562
+ };
563
+ /** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */
564
+ function redactMcpToolDefinition(tool) {
565
+ if (!isObject(tool)) return tool;
566
+ if (tool.type === "mcp") return withoutMcpCredentials(tool);
567
+ if (isMcpProviderTool(tool) && isObject(tool.args)) return {
568
+ ...tool,
569
+ args: withoutMcpCredentials(tool.args)
570
+ };
571
+ return tool;
572
+ }
573
+ function redactMcpToolCredentials(tools) {
574
+ return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools;
575
+ }
576
+ //#endregion
446
577
  //#region src/gatewayWarning.ts
447
578
  const POSTHOG_AI_GATEWAY_HOSTS = [
448
579
  "gateway.posthog.com",
@@ -505,13 +636,15 @@ const captureAiGeneration = async (client, options) => {
505
636
  }
506
637
  let exceptionId;
507
638
  if (client.options?.enableExceptionAutocapture) {
508
- exceptionId = (0, _posthog_core.uuidv7)();
509
- client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
639
+ if (!shouldRedact) {
640
+ exceptionId = (0, _posthog_core.uuidv7)();
641
+ client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
642
+ }
510
643
  if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
511
644
  }
512
645
  errorData = {
513
646
  $ai_is_error: true,
514
- $ai_error: stringifyError(options.error),
647
+ $ai_error: formatAiError(options.error, shouldRedact),
515
648
  $exception_event_id: exceptionId
516
649
  };
517
650
  }
@@ -552,7 +685,7 @@ const captureAiGeneration = async (client, options) => {
552
685
  $ai_tokens_source: getTokensSource(options.properties),
553
686
  ...options.distinctId ? {} : { $process_person_profile: false },
554
687
  ...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
555
- ...options.tools ? { $ai_tools: options.tools } : {},
688
+ ...options.tools ? { $ai_tools: redactMcpToolCredentials(options.tools) } : {},
556
689
  ...options.completionId ? { $ai_completion_id: options.completionId } : {},
557
690
  ...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
558
691
  ...errorData,
@@ -816,12 +949,13 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
816
949
  $ai_framework_version: model.specificationVersion === "v3" ? "6" : "5"
817
950
  }
818
951
  };
952
+ const privacyMode = isPrivacyModeEnabled(phClient, mergedOptions.posthogPrivacyMode);
819
953
  const baseOptions = {
820
954
  distinctId: mergedOptions.posthogDistinctId,
821
955
  traceId,
822
956
  properties: mergedOptions.posthogProperties,
823
957
  groups: mergedOptions.posthogGroups,
824
- privacyMode: mergedOptions.posthogPrivacyMode,
958
+ privacyMode,
825
959
  modelOverride: mergedOptions.posthogModelOverride,
826
960
  providerOverride: mergedOptions.posthogProviderOverride,
827
961
  costOverride: mergedOptions.posthogCostOverride,
@@ -849,7 +983,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
849
983
  const usageObj = result.usage;
850
984
  const rawUsageData = {
851
985
  usage: result.usage,
852
- providerMetadata
986
+ ...privacyMode ? {} : { providerMetadata }
853
987
  };
854
988
  if (result.response && typeof result.response === "object") {
855
989
  const responseBody = result.response.body;
@@ -871,7 +1005,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
871
1005
  ...baseOptions,
872
1006
  model: modelId,
873
1007
  provider,
874
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1008
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
875
1009
  output: content,
876
1010
  latency,
877
1011
  baseURL,
@@ -888,7 +1022,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
888
1022
  ...baseOptions,
889
1023
  model: modelId,
890
1024
  provider: extractProvider(model),
891
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1025
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
892
1026
  output: [],
893
1027
  latency: (Date.now() - startTime) / 1e3,
894
1028
  baseURL,
@@ -1014,7 +1148,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1014
1148
  const finalUsage = {
1015
1149
  ...usage,
1016
1150
  webSearchCount,
1017
- rawUsage: {
1151
+ rawUsage: privacyMode ? { usage } : {
1018
1152
  usage,
1019
1153
  providerMetadata
1020
1154
  }
@@ -1025,7 +1159,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1025
1159
  ...baseOptions,
1026
1160
  model: modelId,
1027
1161
  provider,
1028
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1162
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1029
1163
  output,
1030
1164
  latency,
1031
1165
  timeToFirstToken,
@@ -1075,7 +1209,7 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
1075
1209
  ...baseOptions,
1076
1210
  model: modelId,
1077
1211
  provider,
1078
- input: mergedOptions.posthogPrivacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1212
+ input: privacyMode ? "" : mapVercelPrompt(params.prompt, phClient),
1079
1213
  output: [],
1080
1214
  latency: (Date.now() - startTime) / 1e3,
1081
1215
  baseURL,