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