@posthog/ai 8.16.1 → 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 +141 -8
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.mjs +141 -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
@@ -2,7 +2,7 @@ import { SYSTEM_PROMPT_DYNAMIC_BOUNDARY, query as query$1 } from "@anthropic-ai/
2
2
  import { v4 } from "uuid";
3
3
  import { toJsonSafeValue, uuidv7 } from "@posthog/core";
4
4
  //#region package.json
5
- var version = "8.16.1";
5
+ var version = "8.16.2";
6
6
  //#endregion
7
7
  //#region src/captureAiEvent.ts
8
8
  /** @internal */
@@ -26,6 +26,11 @@ async function captureAiEventImmediate(client, event) {
26
26
  await client.captureImmediate(event);
27
27
  }
28
28
  //#endregion
29
+ //#region src/typeGuards.ts
30
+ const isObject = (value) => {
31
+ return value !== null && typeof value === "object" && !Array.isArray(value);
32
+ };
33
+ //#endregion
29
34
  //#region src/sanitization/base64_recognizer.ts
30
35
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
31
36
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
@@ -217,6 +222,10 @@ var BinaryContentRedactor = class {
217
222
  };
218
223
  new BinaryContentRedactor();
219
224
  //#endregion
225
+ //#region src/privacyMode.ts
226
+ /** Per-call settings can turn redaction on but never off. */
227
+ const isPrivacyModeEnabled = (client, privacyMode = false) => privacyMode === true || client?.privacyMode === true || client?.privacy_mode === true || client?.options?.privacyMode === true;
228
+ //#endregion
220
229
  //#region src/utils.ts
221
230
  const TOKEN_PROPERTY_KEYS = /* @__PURE__ */ new Set([
222
231
  "$ai_input_tokens",
@@ -264,7 +273,6 @@ function toContentString(content) {
264
273
  }
265
274
  return String(content);
266
275
  }
267
- const isPrivacyModeEnabled = (client, privacyMode) => Boolean(client.privacy_mode || privacyMode);
268
276
  function toSafeString(input) {
269
277
  if (input === void 0 || input === null) return "";
270
278
  if (typeof input === "string") return input;
@@ -298,6 +306,7 @@ function sanitizeValues(obj) {
298
306
  //#region src/serializeError.ts
299
307
  const DEFAULT_MAX_DEPTH = 3;
300
308
  const MAX_STACK_LINES = 20;
309
+ const OMITTED_FIELD = "requestBodyValues";
301
310
  function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
302
311
  if (depth < 0 || value === null || typeof value !== "object") return value;
303
312
  if (value instanceof Error) {
@@ -306,16 +315,17 @@ function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
306
315
  message: value.message,
307
316
  stack: truncateStack(value.stack)
308
317
  };
309
- for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
318
+ for (const key of Object.keys(value)) if (key !== OMITTED_FIELD) out[key] = serializeError(Reflect.get(value, key), depth - 1);
310
319
  if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
311
320
  return out;
312
321
  }
313
322
  if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
314
323
  return value;
315
324
  }
325
+ const omitRequestBody = (key, value) => key === OMITTED_FIELD ? void 0 : value;
316
326
  function stringifyError(error) {
317
327
  try {
318
- return JSON.stringify(sanitizeValues(serializeError(error)));
328
+ return JSON.stringify(sanitizeValues(serializeError(error)), omitRequestBody);
319
329
  } catch {
320
330
  if (error instanceof Error) return JSON.stringify({
321
331
  name: error.name,
@@ -324,6 +334,105 @@ function stringifyError(error) {
324
334
  return JSON.stringify({ message: String(error) });
325
335
  }
326
336
  }
337
+ const CLASS_NAME = /^[A-Za-z_$][A-Za-z0-9_$.]{0,127}$/;
338
+ const PROVIDER_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
339
+ const PARAM = /^[A-Za-z0-9_.[\]-]{1,128}$/;
340
+ const REQUEST_ID = /^[A-Za-z0-9_.:-]{1,128}$/;
341
+ const MAX_CAUSES = 3;
342
+ const readField = (value, key) => {
343
+ if (value === null || typeof value !== "object" && typeof value !== "function") return;
344
+ try {
345
+ return Reflect.get(value, key);
346
+ } catch {
347
+ return;
348
+ }
349
+ };
350
+ const matching = (value, grammar) => typeof value === "string" && grammar.test(value) ? value : void 0;
351
+ const firstMatching = (values, grammar) => {
352
+ for (const value of values) {
353
+ const match = matching(value, grammar);
354
+ if (match !== void 0) return match;
355
+ }
356
+ };
357
+ const className = (value) => {
358
+ if (!(value instanceof Error)) return "NonErrorThrown";
359
+ const name = readField(value, "name");
360
+ return firstMatching([
361
+ name === "Error" ? void 0 : name,
362
+ readField(readField(value, "constructor"), "name"),
363
+ name
364
+ ], CLASS_NAME) ?? "Error";
365
+ };
366
+ const httpStatus = (error) => {
367
+ for (const key of ["status", "statusCode"]) {
368
+ const value = readField(error, key);
369
+ if (typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599) return value;
370
+ }
371
+ };
372
+ const unwrapEnvelope = (body) => {
373
+ const inner = readField(body, "error");
374
+ return inner !== null && typeof inner === "object" ? inner : body;
375
+ };
376
+ const parsedResponseBody = (error) => {
377
+ const responseBody = readField(error, "responseBody");
378
+ if (typeof responseBody !== "string") return;
379
+ try {
380
+ return unwrapEnvelope(JSON.parse(responseBody));
381
+ } catch {
382
+ return;
383
+ }
384
+ };
385
+ const causeNames = (error) => {
386
+ const names = [];
387
+ let cause = readField(error, "cause");
388
+ while (cause !== void 0 && cause !== null && names.length < MAX_CAUSES) {
389
+ names.push(className(cause));
390
+ cause = readField(cause, "cause");
391
+ }
392
+ return names;
393
+ };
394
+ const describeError = (error) => {
395
+ const sources = [
396
+ error,
397
+ unwrapEnvelope(readField(error, "error")),
398
+ parsedResponseBody(error)
399
+ ];
400
+ const codes = /* @__PURE__ */ new Set();
401
+ for (const source of sources) for (const key of [
402
+ "code",
403
+ "type",
404
+ "status"
405
+ ]) {
406
+ const code = matching(readField(source, key), PROVIDER_CODE);
407
+ if (code !== void 0) codes.add(code);
408
+ }
409
+ const status = httpStatus(error);
410
+ const param = firstMatching(sources.map((source) => readField(source, "param")), PARAM);
411
+ const requestId = firstMatching([readField(error, "requestID"), readField(error, "request_id")], REQUEST_ID);
412
+ const causes = causeNames(error);
413
+ let summary = className(error);
414
+ if (status !== void 0) summary += `: ${status}`;
415
+ for (const code of codes) summary += ` ${code}`;
416
+ if (param !== void 0) summary += ` param=${param}`;
417
+ if (requestId !== void 0) summary += ` request_id=${requestId}`;
418
+ if (causes.length > 0) summary += ` (caused by ${causes.join(", ")})`;
419
+ return summary;
420
+ };
421
+ /**
422
+ * Describes an error for `$ai_error` without its message, stack, or arbitrary fields, which
423
+ * routinely echo the prompt. Only structured fields that pass a strict grammar are kept.
424
+ * The format matches the Python SDK.
425
+ */
426
+ function summarizeErrorForPrivacy(error) {
427
+ try {
428
+ return describeError(error);
429
+ } catch {
430
+ return error instanceof Error ? "Error" : "NonErrorThrown";
431
+ }
432
+ }
433
+ function formatAiError(error, privacyMode) {
434
+ return privacyMode ? summarizeErrorForPrivacy(error) : stringifyError(error);
435
+ }
327
436
  function truncateStack(stack) {
328
437
  if (!stack) return stack;
329
438
  const lines = stack.split("\n");
@@ -331,6 +440,33 @@ function truncateStack(stack) {
331
440
  return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
332
441
  }
333
442
  //#endregion
443
+ //#region src/mcpCredentials.ts
444
+ const MCP_CREDENTIAL_FIELDS = ["headers", "authorization"];
445
+ const MCP_PROVIDER_TOOL_IDS = /* @__PURE__ */ new Set([
446
+ "openai.mcp",
447
+ "xai.mcp",
448
+ "google.mcp_server"
449
+ ]);
450
+ const isMcpProviderTool = (tool) => (tool.type === "provider" || tool.type === "provider-defined") && typeof tool.id === "string" && MCP_PROVIDER_TOOL_IDS.has(tool.id);
451
+ const withoutMcpCredentials = (config) => {
452
+ const redacted = { ...config };
453
+ for (const field of MCP_CREDENTIAL_FIELDS) if (field in redacted) redacted[field] = "[REDACTED]";
454
+ return redacted;
455
+ };
456
+ /** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */
457
+ function redactMcpToolDefinition(tool) {
458
+ if (!isObject(tool)) return tool;
459
+ if (tool.type === "mcp") return withoutMcpCredentials(tool);
460
+ if (isMcpProviderTool(tool) && isObject(tool.args)) return {
461
+ ...tool,
462
+ args: withoutMcpCredentials(tool.args)
463
+ };
464
+ return tool;
465
+ }
466
+ function redactMcpToolCredentials(tools) {
467
+ return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools;
468
+ }
469
+ //#endregion
334
470
  //#region src/gatewayWarning.ts
335
471
  const POSTHOG_AI_GATEWAY_HOSTS = [
336
472
  "gateway.posthog.com",
@@ -393,13 +529,15 @@ const captureAiGeneration = async (client, options) => {
393
529
  }
394
530
  let exceptionId;
395
531
  if (client.options?.enableExceptionAutocapture) {
396
- exceptionId = uuidv7();
397
- client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
532
+ if (!shouldRedact) {
533
+ exceptionId = uuidv7();
534
+ client.captureException(options.error, void 0, { $ai_trace_id: traceId }, exceptionId);
535
+ }
398
536
  if (typeof options.error === "object") options.error.__posthog_previously_captured_error = true;
399
537
  }
400
538
  errorData = {
401
539
  $ai_is_error: true,
402
- $ai_error: stringifyError(options.error),
540
+ $ai_error: formatAiError(options.error, shouldRedact),
403
541
  $exception_event_id: exceptionId
404
542
  };
405
543
  }
@@ -440,7 +578,7 @@ const captureAiGeneration = async (client, options) => {
440
578
  $ai_tokens_source: getTokensSource(options.properties),
441
579
  ...options.distinctId ? {} : { $process_person_profile: false },
442
580
  ...options.stopReason ? { $ai_stop_reason: options.stopReason } : {},
443
- ...options.tools ? { $ai_tools: options.tools } : {},
581
+ ...options.tools ? { $ai_tools: redactMcpToolCredentials(options.tools) } : {},
444
582
  ...options.completionId ? { $ai_completion_id: options.completionId } : {},
445
583
  ...options.providerMetadata && Object.keys(options.providerMetadata).length > 0 ? { $ai_provider_metadata: options.providerMetadata } : {},
446
584
  ...errorData,
@@ -1081,7 +1219,7 @@ var PostHogClaudeAgentProcessor = class {
1081
1219
  $ai_latency: latency,
1082
1220
  ...state.turnCost != null ? { $ai_total_cost_usd: state.turnCost } : {},
1083
1221
  ...isError ? { $ai_is_error: true } : {},
1084
- ...error !== void 0 ? { $ai_error: stringifyError(error) } : {},
1222
+ ...error !== void 0 ? { $ai_error: formatAiError(error, isPrivacyModeEnabled(this._client, trace.privacyMode ?? false)) } : {},
1085
1223
  ...result?.subtype === "success" && result.api_error_status != null ? { $ai_http_status: result.api_error_status } : {}
1086
1224
  });
1087
1225
  } finally {