@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
package/README.md CHANGED
@@ -9,6 +9,18 @@ SDK usage examples and code snippets live in the official documentation so they
9
9
  - [AI observability installation docs](https://posthog.com/docs/ai-observability/installation)
10
10
  - [AI observability docs](https://posthog.com/docs/ai-observability)
11
11
 
12
+ ## Privacy mode
13
+
14
+ Create the client with `new PostHog(key, { privacyMode: true })` to turn on privacy mode for every integration.
15
+ Per-call `privacyMode` settings can turn it on for one call, but cannot turn it off.
16
+
17
+ In privacy mode, events carry no prompts, outputs, or tool inputs and results.
18
+ `$ai_error` holds only the error class, HTTP status, provider error code, parameter name, request ID, and cause classes.
19
+ Exception autocapture skips these errors, and Vercel AI SDK events omit provider metadata.
20
+ For OpenTelemetry, pass `privacyMode: true` to `PostHogTraceExporter`, or to `PostHogSpanProcessor` when its `client` is not in privacy mode.
21
+
22
+ Remote MCP tool `headers` and `authorization` values are always replaced with `[REDACTED]`.
23
+
12
24
  ## Gemini Interactions
13
25
 
14
26
  `PostHogGoogleGenAI.interactions.create` traces foreground model calls with `@google/genai` 2.18 or later in the 2.x series.
@@ -3,7 +3,12 @@ let _google_adk = require("@google/adk");
3
3
  let uuid = require("uuid");
4
4
  let _posthog_core = require("@posthog/core");
5
5
  //#region package.json
6
- var version = "8.16.1";
6
+ var version = "8.16.3";
7
+ //#endregion
8
+ //#region src/typeGuards.ts
9
+ const isObject = (value) => {
10
+ return value !== null && typeof value === "object" && !Array.isArray(value);
11
+ };
7
12
  //#endregion
8
13
  //#region src/captureAiEvent.ts
9
14
  /** @internal */
@@ -225,6 +230,10 @@ function redactBase64DataUrl(str, mediaType) {
225
230
  const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
226
231
  const sanitizeGemini = (data, client) => sanitize(data, client);
227
232
  //#endregion
233
+ //#region src/privacyMode.ts
234
+ /** Per-call settings can turn redaction on but never off. */
235
+ const isPrivacyModeEnabled = (client, privacyMode = false) => privacyMode === true || client?.privacyMode === true || client?.privacy_mode === true || client?.options?.privacyMode === true;
236
+ //#endregion
228
237
  //#region src/utils.ts
229
238
  const TOKEN_PROPERTY_KEYS = /* @__PURE__ */ new Set([
230
239
  "$ai_input_tokens",
@@ -348,7 +357,6 @@ const formatResponseGemini = (response, client) => {
348
357
  });
349
358
  return output;
350
359
  };
351
- const isPrivacyModeEnabled = (client, privacyMode) => Boolean(client.privacy_mode || privacyMode);
352
360
  const withPrivacyMode = (client, privacyMode, input) => {
353
361
  return isPrivacyModeEnabled(client, privacyMode) ? null : input;
354
362
  };
@@ -364,6 +372,7 @@ function sanitizeValues(obj) {
364
372
  //#region src/serializeError.ts
365
373
  const DEFAULT_MAX_DEPTH = 3;
366
374
  const MAX_STACK_LINES = 20;
375
+ const OMITTED_FIELD = "requestBodyValues";
367
376
  function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
368
377
  if (depth < 0 || value === null || typeof value !== "object") return value;
369
378
  if (value instanceof Error) {
@@ -372,16 +381,17 @@ function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
372
381
  message: value.message,
373
382
  stack: truncateStack(value.stack)
374
383
  };
375
- for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
384
+ for (const key of Object.keys(value)) if (key !== OMITTED_FIELD) out[key] = serializeError(Reflect.get(value, key), depth - 1);
376
385
  if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
377
386
  return out;
378
387
  }
379
388
  if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
380
389
  return value;
381
390
  }
391
+ const omitRequestBody = (key, value) => key === OMITTED_FIELD ? void 0 : value;
382
392
  function stringifyError(error) {
383
393
  try {
384
- return JSON.stringify(sanitizeValues(serializeError(error)));
394
+ return JSON.stringify(sanitizeValues(serializeError(error)), omitRequestBody);
385
395
  } catch {
386
396
  if (error instanceof Error) return JSON.stringify({
387
397
  name: error.name,
@@ -390,6 +400,105 @@ function stringifyError(error) {
390
400
  return JSON.stringify({ message: String(error) });
391
401
  }
392
402
  }
403
+ const CLASS_NAME = /^[A-Za-z_$][A-Za-z0-9_$.]{0,127}$/;
404
+ const PROVIDER_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
405
+ const PARAM = /^[A-Za-z0-9_.[\]-]{1,128}$/;
406
+ const REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;
407
+ const MAX_CAUSES = 3;
408
+ const readField = (value, key) => {
409
+ if (value === null || typeof value !== "object" && typeof value !== "function") return;
410
+ try {
411
+ return Reflect.get(value, key);
412
+ } catch {
413
+ return;
414
+ }
415
+ };
416
+ const matching = (value, grammar) => typeof value === "string" && grammar.test(value) ? value : void 0;
417
+ const firstMatching = (values, grammar) => {
418
+ for (const value of values) {
419
+ const match = matching(value, grammar);
420
+ if (match !== void 0) return match;
421
+ }
422
+ };
423
+ const className = (value) => {
424
+ if (!(value instanceof Error)) return "NonErrorThrown";
425
+ const name = readField(value, "name");
426
+ return firstMatching([
427
+ name === "Error" ? void 0 : name,
428
+ readField(readField(value, "constructor"), "name"),
429
+ name
430
+ ], CLASS_NAME) ?? "Error";
431
+ };
432
+ const httpStatus = (error) => {
433
+ for (const key of ["status", "statusCode"]) {
434
+ const value = readField(error, key);
435
+ if (typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599) return value;
436
+ }
437
+ };
438
+ const unwrapEnvelope = (body) => {
439
+ const inner = readField(body, "error");
440
+ return inner !== null && typeof inner === "object" ? inner : body;
441
+ };
442
+ const parsedResponseBody = (error) => {
443
+ const responseBody = readField(error, "responseBody");
444
+ if (typeof responseBody !== "string") return;
445
+ try {
446
+ return unwrapEnvelope(JSON.parse(responseBody));
447
+ } catch {
448
+ return;
449
+ }
450
+ };
451
+ const causeNames = (error) => {
452
+ const names = [];
453
+ let cause = readField(error, "cause");
454
+ while (cause !== void 0 && cause !== null && names.length < MAX_CAUSES) {
455
+ names.push(className(cause));
456
+ cause = readField(cause, "cause");
457
+ }
458
+ return names;
459
+ };
460
+ const describeError = (error) => {
461
+ const sources = [
462
+ error,
463
+ unwrapEnvelope(readField(error, "error")),
464
+ parsedResponseBody(error)
465
+ ];
466
+ const codes = /* @__PURE__ */ new Set();
467
+ for (const source of sources) for (const key of [
468
+ "code",
469
+ "type",
470
+ "status"
471
+ ]) {
472
+ const code = matching(readField(source, key), PROVIDER_CODE);
473
+ if (code !== void 0) codes.add(code);
474
+ }
475
+ const status = httpStatus(error);
476
+ const param = firstMatching(sources.map((source) => readField(source, "param")), PARAM);
477
+ const requestId = firstMatching([readField(error, "requestID"), readField(error, "request_id")], REQUEST_ID);
478
+ const causes = causeNames(error);
479
+ let summary = className(error);
480
+ if (status !== void 0) summary += `: ${status}`;
481
+ for (const code of codes) summary += ` ${code}`;
482
+ if (param !== void 0) summary += ` param=${param}`;
483
+ if (requestId !== void 0) summary += ` request_id=${requestId}`;
484
+ if (causes.length > 0) summary += ` (caused by ${causes.join(", ")})`;
485
+ return summary;
486
+ };
487
+ /**
488
+ * Describes an error for `$ai_error` without its message, stack, or arbitrary fields, which
489
+ * routinely echo the prompt. Only structured fields that pass a strict grammar are kept.
490
+ * The format matches the Python SDK.
491
+ */
492
+ function summarizeErrorForPrivacy(error) {
493
+ try {
494
+ return describeError(error);
495
+ } catch {
496
+ return error instanceof Error ? "Error" : "NonErrorThrown";
497
+ }
498
+ }
499
+ function formatAiError(error, privacyMode) {
500
+ return privacyMode ? summarizeErrorForPrivacy(error) : stringifyError(error);
501
+ }
393
502
  function truncateStack(stack) {
394
503
  if (!stack) return stack;
395
504
  const lines = stack.split("\n");
@@ -397,6 +506,33 @@ function truncateStack(stack) {
397
506
  return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
398
507
  }
399
508
  //#endregion
509
+ //#region src/mcpCredentials.ts
510
+ const MCP_CREDENTIAL_FIELDS = ["headers", "authorization"];
511
+ const MCP_PROVIDER_TOOL_IDS = /* @__PURE__ */ new Set([
512
+ "openai.mcp",
513
+ "xai.mcp",
514
+ "google.mcp_server"
515
+ ]);
516
+ const isMcpProviderTool = (tool) => (tool.type === "provider" || tool.type === "provider-defined") && typeof tool.id === "string" && MCP_PROVIDER_TOOL_IDS.has(tool.id);
517
+ const withoutMcpCredentials = (config) => {
518
+ const redacted = { ...config };
519
+ for (const field of MCP_CREDENTIAL_FIELDS) if (field in redacted) redacted[field] = "[REDACTED]";
520
+ return redacted;
521
+ };
522
+ /** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */
523
+ function redactMcpToolDefinition(tool) {
524
+ if (!isObject(tool)) return tool;
525
+ if (tool.type === "mcp") return withoutMcpCredentials(tool);
526
+ if (isMcpProviderTool(tool) && isObject(tool.args)) return {
527
+ ...tool,
528
+ args: withoutMcpCredentials(tool.args)
529
+ };
530
+ return tool;
531
+ }
532
+ function redactMcpToolCredentials(tools) {
533
+ return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools;
534
+ }
535
+ //#endregion
400
536
  //#region src/gatewayWarning.ts
401
537
  const POSTHOG_AI_GATEWAY_HOSTS = [
402
538
  "gateway.posthog.com",
@@ -459,13 +595,15 @@ const captureAiGeneration = async (client, options) => {
459
595
  }
460
596
  let exceptionId;
461
597
  if (client.options?.enableExceptionAutocapture) {
462
- exceptionId = (0, _posthog_core.uuidv7)();
463
- client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
598
+ if (!shouldRedact) {
599
+ exceptionId = (0, _posthog_core.uuidv7)();
600
+ client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
601
+ }
464
602
  if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
465
603
  }
466
604
  errorData = {
467
605
  $ai_is_error: true,
468
- $ai_error: stringifyError(options.error),
606
+ $ai_error: formatAiError(options.error, shouldRedact),
469
607
  $exception_event_id: exceptionId
470
608
  };
471
609
  }
@@ -506,7 +644,7 @@ const captureAiGeneration = async (client, options) => {
506
644
  $ai_tokens_source: getTokensSource(options.properties),
507
645
  ...options.distinctId ? {} : { $process_person_profile: false },
508
646
  ...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
509
- ...options.tools ? { $ai_tools: options.tools } : {},
647
+ ...options.tools ? { $ai_tools: redactMcpToolCredentials(options.tools) } : {},
510
648
  ...options.completionId ? { $ai_completion_id: options.completionId } : {},
511
649
  ...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
512
650
  ...errorData,
@@ -806,7 +944,7 @@ var PostHogADKPlugin = class extends _google_adk.BasePlugin {
806
944
  ...context.agentName ? { $ai_agent_name: context.agentName } : {},
807
945
  ...error ? {
808
946
  $ai_is_error: true,
809
- $ai_error: stringifyError(error)
947
+ $ai_error: formatAiError(error, isPrivacyModeEnabled(this._client, this._privacyMode))
810
948
  } : {}
811
949
  });
812
950
  }