@posthog/ai 8.17.1 → 8.17.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.
@@ -3,7 +3,7 @@ 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.17.1";
6
+ var version = "8.17.2";
7
7
  //#endregion
8
8
  //#region src/typeGuards.ts
9
9
  const isObject = (value) => {
@@ -2,7 +2,7 @@ import { BasePlugin } from "@google/adk";
2
2
  import { v4 } from "uuid";
3
3
  import { toJsonSafeValue, uuidv7 } from "@posthog/core";
4
4
  //#region package.json
5
- var version = "8.17.1";
5
+ var version = "8.17.2";
6
6
  //#endregion
7
7
  //#region src/typeGuards.ts
8
8
  const isObject = (value) => {
@@ -415,7 +415,7 @@ function addDefaults(params) {
415
415
  }
416
416
  //#endregion
417
417
  //#region package.json
418
- var version = "8.17.1";
418
+ var version = "8.17.2";
419
419
  //#endregion
420
420
  //#region src/serializeError.ts
421
421
  const DEFAULT_MAX_DEPTH = 3;
@@ -388,7 +388,7 @@ function addDefaults(params) {
388
388
  }
389
389
  //#endregion
390
390
  //#region package.json
391
- var version = "8.17.1";
391
+ var version = "8.17.2";
392
392
  //#endregion
393
393
  //#region src/serializeError.ts
394
394
  const DEFAULT_MAX_DEPTH = 3;
@@ -3,7 +3,7 @@ let _anthropic_ai_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
3
3
  let uuid = require("uuid");
4
4
  let _posthog_core = require("@posthog/core");
5
5
  //#region package.json
6
- var version = "8.17.1";
6
+ var version = "8.17.2";
7
7
  //#endregion
8
8
  //#region src/captureAiEvent.ts
9
9
  /** @internal */
@@ -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.17.1";
5
+ var version = "8.17.2";
6
6
  //#endregion
7
7
  //#region src/captureAiEvent.ts
8
8
  /** @internal */
@@ -466,7 +466,7 @@ function addDefaults(params) {
466
466
  }
467
467
  //#endregion
468
468
  //#region package.json
469
- var version = "8.17.1";
469
+ var version = "8.17.2";
470
470
  //#endregion
471
471
  //#region src/serializeError.ts
472
472
  const DEFAULT_MAX_DEPTH = 3;
@@ -462,7 +462,7 @@ function addDefaults(params) {
462
462
  }
463
463
  //#endregion
464
464
  //#region package.json
465
- var version = "8.17.1";
465
+ var version = "8.17.2";
466
466
  //#endregion
467
467
  //#region src/serializeError.ts
468
468
  const DEFAULT_MAX_DEPTH = 3;
package/dist/index.cjs CHANGED
@@ -413,7 +413,7 @@ function sanitizeValues(obj) {
413
413
  }
414
414
  //#endregion
415
415
  //#region package.json
416
- var version = "8.17.1";
416
+ var version = "8.17.2";
417
417
  //#endregion
418
418
  //#region src/serializeError.ts
419
419
  const DEFAULT_MAX_DEPTH = 3;
package/dist/index.mjs CHANGED
@@ -412,7 +412,7 @@ function sanitizeValues(obj) {
412
412
  }
413
413
  //#endregion
414
414
  //#region package.json
415
- var version = "8.17.1";
415
+ var version = "8.17.2";
416
416
  //#endregion
417
417
  //#region src/serializeError.ts
418
418
  const DEFAULT_MAX_DEPTH = 3;
@@ -308,7 +308,7 @@ function redactMcpToolCredentials(tools) {
308
308
  }
309
309
  //#endregion
310
310
  //#region package.json
311
- var version = "8.17.1";
311
+ var version = "8.17.2";
312
312
  //#endregion
313
313
  //#region src/serializeError.ts
314
314
  const DEFAULT_MAX_DEPTH = 3;
@@ -307,7 +307,7 @@ function redactMcpToolCredentials(tools) {
307
307
  }
308
308
  //#endregion
309
309
  //#region package.json
310
- var version = "8.17.1";
310
+ var version = "8.17.2";
311
311
  //#endregion
312
312
  //#region src/serializeError.ts
313
313
  const DEFAULT_MAX_DEPTH = 3;
@@ -313,7 +313,7 @@ function redactMcpToolCredentials(tools) {
313
313
  }
314
314
  //#endregion
315
315
  //#region package.json
316
- var version = "8.17.1";
316
+ var version = "8.17.2";
317
317
  //#endregion
318
318
  //#region src/serializeError.ts
319
319
  const DEFAULT_MAX_DEPTH = 3;
@@ -312,7 +312,7 @@ function redactMcpToolCredentials(tools) {
312
312
  }
313
313
  //#endregion
314
314
  //#region package.json
315
- var version = "8.17.1";
315
+ var version = "8.17.2";
316
316
  //#endregion
317
317
  //#region src/serializeError.ts
318
318
  const DEFAULT_MAX_DEPTH = 3;
@@ -552,7 +552,7 @@ function formatOpenAIResponsesInput(input, instructions) {
552
552
  }
553
553
  //#endregion
554
554
  //#region package.json
555
- var version = "8.17.1";
555
+ var version = "8.17.2";
556
556
  //#endregion
557
557
  //#region src/serializeError.ts
558
558
  const DEFAULT_MAX_DEPTH = 3;
@@ -548,7 +548,7 @@ function formatOpenAIResponsesInput(input, instructions) {
548
548
  }
549
549
  //#endregion
550
550
  //#region package.json
551
- var version = "8.17.1";
551
+ var version = "8.17.2";
552
552
  //#endregion
553
553
  //#region src/serializeError.ts
554
554
  const DEFAULT_MAX_DEPTH = 3;
@@ -363,7 +363,7 @@ function summarizeErrorForPrivacy(error) {
363
363
  }
364
364
  //#endregion
365
365
  //#region package.json
366
- var version = "8.17.1";
366
+ var version = "8.17.2";
367
367
  //#endregion
368
368
  //#region src/gatewayWarning.ts
369
369
  const POSTHOG_AI_GATEWAY_HOSTS = [
@@ -362,7 +362,7 @@ function summarizeErrorForPrivacy(error) {
362
362
  }
363
363
  //#endregion
364
364
  //#region package.json
365
- var version = "8.17.1";
365
+ var version = "8.17.2";
366
366
  //#endregion
367
367
  //#region src/gatewayWarning.ts
368
368
  const POSTHOG_AI_GATEWAY_HOSTS = [
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _opentelemetry_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
3
- let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
3
+ let node_module = require("node:module");
4
4
  //#region src/sanitization/base64_recognizer.ts
5
5
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
6
6
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
@@ -603,6 +603,20 @@ var PostHogTraceExporter = class extends _opentelemetry_exporter_trace_otlp_http
603
603
  }
604
604
  };
605
605
  //#endregion
606
+ //#region src/otel/batchProcessor.ts
607
+ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
608
+ function createBatchSpanProcessor(exporter) {
609
+ try {
610
+ require$1.resolve("@opentelemetry/sdk-trace");
611
+ } catch (error) {
612
+ if (error.code !== "MODULE_NOT_FOUND") throw error;
613
+ const { BatchSpanProcessor } = require$1("@opentelemetry/sdk-trace-base");
614
+ return new BatchSpanProcessor(exporter);
615
+ }
616
+ const { BatchSpanProcessor } = require$1("@opentelemetry/sdk-trace");
617
+ return new BatchSpanProcessor({ exporter });
618
+ }
619
+ //#endregion
606
620
  //#region src/privacyMode.ts
607
621
  /** Per-call settings can turn redaction on but never off. */
608
622
  const isPrivacyModeEnabled = (client, privacyMode = false) => privacyMode === true || client?.privacyMode === true || client?.privacy_mode === true || client?.options?.privacyMode === true;
@@ -752,7 +766,7 @@ var PostHogSpanProcessor = class {
752
766
  url: `${host}/i/v0/ai/otel`,
753
767
  headers: { Authorization: `Bearer ${token}` }
754
768
  });
755
- this.inner = new _opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter);
769
+ this.inner = createBatchSpanProcessor(exporter);
756
770
  }
757
771
  }
758
772
  onStart(span, parentContext) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["redactJson","DEFAULT_OTEL_HOST","normalizeToken","normalizeHost","OTLPTraceExporter","OTLPTraceExporter","BatchSpanProcessor"],"sources":["../../src/sanitization/base64_recognizer.ts","../../src/sanitization/media_type_context.ts","../../src/sanitization/binary_content_redactor.ts","../../src/otel/spans.ts","../../src/typeGuards.ts","../../src/mcpCredentials.ts","../../src/otel/toolDefinitions.ts","../../src/otel/redact.ts","../../src/otel/privacy.ts","../../src/gatewayWarning.ts","../../src/otel/exporter.ts","../../src/privacyMode.ts","../../src/otel/requestContext.ts","../../src/otel/processor.ts"],"sourcesContent":["const DATA_URL_PREFIX_RE = /^data:([^;,\\s]+)(?:;[^;,\\s]+)*;base64,/i\nconst BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/\n\nexport type Base64Recognition = { kind: 'data-url'; mediaType: string } | { kind: 'raw' } | { kind: 'none' }\n\nexport class Base64Recognizer {\n recognize(value: string, minLength: number): Base64Recognition {\n const dataUrl = DATA_URL_PREFIX_RE.exec(value)\n if (dataUrl) return { kind: 'data-url', mediaType: dataUrl[1] }\n\n if (value.length < minLength) return { kind: 'none' }\n\n const confidencePrefix = value.slice(0, minLength)\n if (BASE64_ALPHABET_RE.test(confidencePrefix)) {\n return { kind: 'raw' }\n } else {\n return { kind: 'none' }\n }\n }\n}\n","const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'] as const\n\nconst STRONG_CONTEXT_KEYS = new Set([\n 'data',\n 'file_data',\n 'fileData',\n 'image_url',\n 'imageUrl',\n 'video_url',\n 'videoUrl',\n 'audio',\n 'audio_data',\n 'audioData',\n 'inline_data',\n 'inlineData',\n 'source',\n 'result',\n])\n\nconst STRONG_CONTEXT_TYPES = new Set([\n 'image',\n 'image_url',\n 'input_image',\n 'audio',\n 'input_audio',\n 'video',\n 'video_url',\n 'file',\n 'input_file',\n 'document',\n 'media',\n 'file-data',\n])\n\nconst FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data'])\nconst TYPED_BINARY_DATA_TYPES = new Set(['image', 'audio', 'video', 'document'])\n\nconst KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm'])\n\nexport class MediaTypeContext {\n static readonly EMPTY = new MediaTypeContext(undefined, undefined)\n\n constructor(\n private readonly parent: Record<string, unknown> | undefined,\n private readonly key: string | undefined,\n private readonly explicitMediaType?: string\n ) {}\n\n inferMediaType(): string | undefined {\n return (\n this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey()\n )\n }\n\n inferFromSiblingMime(): string | undefined {\n if (this.explicitMediaType) return this.explicitMediaType\n if (!this.parent) return undefined\n for (const hint of MIME_HINT_KEYS) {\n const v = this.parent[hint]\n if (typeof v === 'string') return v\n }\n return undefined\n }\n\n inferFromSiblingFormat(): string | undefined {\n if (!this.parent) return undefined\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {\n return `audio/${fmt.toLowerCase()}`\n }\n return undefined\n }\n\n inferFromParentType(): string | undefined {\n if (!this.parent) return undefined\n const t = this.parent.type\n if (typeof t !== 'string') return undefined\n if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image'\n if (t === 'audio' || t === 'input_audio') return 'audio'\n if (t === 'video' || t === 'video_url') return 'video'\n if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream'\n return undefined\n }\n\n inferFromKey(): string | undefined {\n if (!this.key) return undefined\n const key = this.key.toLowerCase()\n if (key.includes('audio')) return 'audio'\n if (key.includes('video')) return 'video'\n if (key.includes('image')) return 'image'\n if (key.includes('file') || key.includes('document')) return 'application/octet-stream'\n return undefined\n }\n\n hasDefiniteBinaryData(): boolean {\n if (\n this.parent &&\n this.key === 'data' &&\n typeof this.parent.type === 'string' &&\n TYPED_BINARY_DATA_TYPES.has(this.parent.type)\n )\n return true\n if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false\n const mediaType = this.inferFromSiblingMime()\n return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/')\n }\n\n signalsBinary(): boolean {\n if (this.explicitMediaType) return true\n if (this.parent) {\n for (const hint of MIME_HINT_KEYS) {\n if (typeof this.parent[hint] === 'string') return true\n }\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true\n const t = this.parent.type\n if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true\n }\n if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true\n return false\n }\n}\n","import { Base64Recognizer } from './base64_recognizer'\nimport { MediaTypeContext } from './media_type_context'\n\nconst STRONG_CONTEXT_MIN_LENGTH = 64\nconst WEAK_CONTEXT_MIN_LENGTH = 1024\n\nexport class BinaryContentRedactor {\n private visited: WeakSet<object> = new WeakSet()\n\n constructor(private readonly recognizer: Base64Recognizer = new Base64Recognizer()) {}\n\n redact<T>(value: T, mediaType?: string): T\n redact(value: unknown, mediaType?: string): unknown {\n this.visited = new WeakSet()\n return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY)\n }\n\n /** Redact one leaf while a caller owns bounded, type-preserving traversal. @internal */\n redactLeaf(value: string | Uint8Array, context: MediaTypeContext): string {\n return typeof value === 'string' ? this.redactString(value, context) : this.placeholderFor(context.inferMediaType())\n }\n\n private walk(value: unknown, ctx: MediaTypeContext): unknown {\n if (value === null || value === undefined) return value\n if (typeof value === 'string') return this.redactLeaf(value, ctx)\n if (typeof value !== 'object') return value\n\n // Buffer extends Uint8Array, so this branch catches both.\n if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {\n return this.redactLeaf(value, ctx)\n }\n\n if (this.visited.has(value)) return null\n this.visited.add(value)\n\n if (Array.isArray(value)) {\n return value.map((item) => this.walk(item, ctx))\n }\n\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj)) {\n Object.defineProperty(out, k, {\n value: this.walk(obj[k], new MediaTypeContext(obj, k)),\n enumerable: true,\n writable: true,\n configurable: true,\n })\n }\n return out\n }\n\n private redactString(value: string, ctx: MediaTypeContext): string {\n const hasDefiniteBinaryData = ctx.hasDefiniteBinaryData()\n const recognitionValue = hasDefiniteBinaryData ? value.replace(/[\\r\\n]/g, '') : value\n const minLength = hasDefiniteBinaryData\n ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH)\n : ctx.signalsBinary()\n ? STRONG_CONTEXT_MIN_LENGTH\n : WEAK_CONTEXT_MIN_LENGTH\n const recognition = this.recognizer.recognize(recognitionValue, minLength)\n switch (recognition.kind) {\n case 'data-url':\n return this.placeholderFor(recognition.mediaType)\n case 'raw':\n return this.placeholderFor(ctx.inferMediaType())\n case 'none':\n return value\n }\n }\n\n private placeholderFor(mediaType?: string): string {\n if (!mediaType) return '[base64 redacted]'\n if (mediaType === 'application/octet-stream') return '[base64 file redacted]'\n return `[base64 ${mediaType} redacted]`\n }\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n\n/** Copies rather than mutates: the span is shared across every registered processor/exporter. */\nexport function withAttributes(span: ReadableSpan, attributes: Attributes, events?: TimedEvent[]): ReadableSpan {\n return Object.create(span, {\n attributes: { value: attributes, enumerable: true, configurable: true },\n ...(events ? { events: { value: events, enumerable: true, configurable: true } } : {}),\n })\n}\n","// Type guards for safer type checking\n\nexport const isString = (value: unknown): value is string => {\n return typeof value === 'string'\n}\n\nexport const isObject = (value: unknown): value is Record<string, unknown> => {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n","import { isObject } from './typeGuards'\n\nconst MCP_CREDENTIAL_FIELDS = ['headers', 'authorization']\n\n// Vercel AI SDK provider tools that configure a remote MCP server, keyed by their `id`.\nconst MCP_PROVIDER_TOOL_IDS = new Set(['openai.mcp', 'xai.mcp', 'google.mcp_server'])\n\nconst isMcpProviderTool = (tool: Record<string, unknown>): boolean =>\n (tool.type === 'provider' || tool.type === 'provider-defined') &&\n typeof tool.id === 'string' &&\n MCP_PROVIDER_TOOL_IDS.has(tool.id)\n\nconst withoutMcpCredentials = (config: Record<string, unknown>): Record<string, unknown> => {\n const redacted: Record<string, unknown> = { ...config }\n for (const field of MCP_CREDENTIAL_FIELDS) {\n if (field in redacted) {\n redacted[field] = '[REDACTED]'\n }\n }\n return redacted\n}\n\n/** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */\nexport function redactMcpToolDefinition(tool: unknown): unknown {\n if (!isObject(tool)) {\n return tool\n }\n if (tool.type === 'mcp') {\n return withoutMcpCredentials(tool)\n }\n if (isMcpProviderTool(tool) && isObject(tool.args)) {\n return { ...tool, args: withoutMcpCredentials(tool.args) }\n }\n return tool\n}\n\nexport function redactMcpToolCredentials(tools: unknown): unknown {\n return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools\n}\n","import type { AttributeValue } from '@opentelemetry/api'\n\nimport { redactMcpToolCredentials, redactMcpToolDefinition } from '../mcpCredentials'\n\n// Instrumentations serialize tool definitions, remote MCP tools and their credentials included,\n// as a JSON array, an array of JSON tools, or one JSON tool per attribute.\nconst TOOL_DEFINITION_ATTRIBUTES = new Set(['gen_ai.tool.definitions', 'ai.prompt.tools'])\nconst TOOL_DEFINITION_PATTERN = /^llm\\.tools\\.\\d+\\.tool\\.json_schema$/\n\nexport const isToolDefinitionAttribute = (key: string): boolean =>\n TOOL_DEFINITION_ATTRIBUTES.has(key) || TOOL_DEFINITION_PATTERN.test(key)\n\nconst parseToolDefinitionJson = (value: string): object | undefined => {\n try {\n const parsed: unknown = JSON.parse(value)\n return parsed !== null && typeof parsed === 'object' ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\nconst redactJson = (value: string): string => {\n const parsed = parseToolDefinitionJson(value)\n if (parsed === undefined) {\n return value\n }\n const redacted = JSON.stringify(\n Array.isArray(parsed) ? redactMcpToolCredentials(parsed) : redactMcpToolDefinition(parsed)\n )\n return redacted === JSON.stringify(parsed) ? value : redacted\n}\n\n/** Replaces MCP credentials in a tool definition attribute; values that are not JSON are kept as is. */\nexport function redactToolDefinitionCredentials(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactJson(value)\n }\n if (Array.isArray(value) && value.every((item) => typeof item === 'string' || item == null)) {\n const redacted = value.map((item) => (typeof item === 'string' ? redactJson(item) : item))\n return redacted.some((item, index) => item !== value[index]) ? redacted : value\n }\n return value\n}\n\n/** Drops the parts of a tool definition attribute that are not JSON, since their credentials cannot be found. */\nexport function withoutUnparseableToolDefinitions(value: AttributeValue): AttributeValue | undefined {\n if (typeof value === 'string') {\n return parseToolDefinitionJson(value) === undefined ? undefined : value\n }\n if (Array.isArray(value)) {\n return value.filter(\n (item): item is string => typeof item === 'string' && parseToolDefinitionJson(item) !== undefined\n )\n }\n return undefined\n}\n","import type { Attributes, AttributeValue } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'\n\nimport { BinaryContentRedactor } from '../sanitization/binary_content_redactor'\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, redactToolDefinitionCredentials } from './toolDefinitions'\n\n// Deliberately always redacts here — the OTLP export path has no per-client passthrough gate.\nconst redactor = new BinaryContentRedactor()\n\nexport function redactSpan(span: ReadableSpan): ReadableSpan {\n const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes\n const events = span.events ? redactEvents(span.events) : span.events\n if (attributes === span.attributes && events === span.events) {\n return span\n }\n return withAttributes(span, attributes, events)\n}\n\nfunction redactAttributes(attributes: Attributes): Attributes {\n let changed = false\n const out: Attributes = {}\n for (const key of Object.keys(attributes)) {\n const original = attributes[key]\n const value =\n original !== undefined && isToolDefinitionAttribute(key) ? redactToolDefinitionCredentials(original) : original\n const redacted = value === undefined ? value : redactAttributeValue(value)\n if (redacted !== original) {\n changed = true\n }\n out[key] = redacted\n }\n return changed ? out : attributes\n}\n\nfunction redactEvents(events: TimedEvent[]): TimedEvent[] {\n let changed = false\n const out = events.map((event) => {\n if (!event.attributes) {\n return event\n }\n const attributes = redactAttributes(event.attributes)\n if (attributes === event.attributes) {\n return event\n }\n changed = true\n return { ...event, attributes }\n })\n return changed ? out : events\n}\n\nfunction redactAttributeValue(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactString(value)\n }\n if (isStringArray(value)) {\n let changed = false\n const out = value.map((item) => {\n if (typeof item !== 'string') {\n return item\n }\n const redacted = redactString(item)\n if (redacted !== item) {\n changed = true\n }\n return redacted\n })\n return changed ? out : value\n }\n return value\n}\n\nfunction isStringArray(value: AttributeValue): value is Array<string | null | undefined> {\n return Array.isArray(value) && value.every((item) => typeof item !== 'number' && typeof item !== 'boolean')\n}\n\nfunction redactString(value: string): string {\n const trimmed = value.trimStart()\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n const redacted = redactJson(value)\n if (redacted !== undefined) {\n return redacted\n }\n }\n return redactor.redact(value)\n}\n\nfunction redactJson(value: string): string | undefined {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n return undefined\n }\n if (parsed === null || typeof parsed !== 'object') {\n return undefined\n }\n const redactedStr = JSON.stringify(redactor.redact(parsed))\n // Preserve the original string (and its formatting) when nothing was redacted.\n return redactedStr === JSON.stringify(parsed) ? value : redactedStr\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'\n\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, withoutUnparseableToolDefinitions } from './toolDefinitions'\n\n// Every attribute PostHog ingestion turns into prompt, output, tool call or error text. Ingestion\n// keeps unmapped attributes as event properties, so this also covers attributes it does not map.\nconst CONTENT_ATTRIBUTES = new Set([\n // Vercel AI SDK\n 'ai.prompt',\n 'ai.prompt.messages',\n 'ai.response.text',\n 'ai.response.object',\n 'ai.response.toolCalls',\n 'ai.response.reasoning',\n 'ai.response.reasoningDetails',\n 'ai.response.files',\n 'ai.toolCall.args',\n 'ai.toolCall.result',\n 'ai.value',\n 'ai.values',\n 'ai.embedding',\n 'ai.embeddings',\n 'ai.documents',\n 'ai.request.text',\n 'ai.decision.state',\n 'ai.decision.questions',\n 'ai.decision.answers',\n // OpenTelemetry GenAI semantic conventions\n 'gen_ai.input.messages',\n 'gen_ai.output.messages',\n 'gen_ai.system_instructions',\n 'gen_ai.prompt',\n 'gen_ai.completion',\n 'gen_ai.tool.call.arguments',\n 'gen_ai.tool.call.result',\n 'gen_ai.retrieval.documents',\n 'gen_ai.retrieval.query.text',\n 'gen_ai.memory.query.text',\n 'gen_ai.memory.records',\n 'gen_ai.evaluation.explanation',\n // Traceloop / OpenLLMetry\n 'traceloop.entity.input',\n 'traceloop.entity.output',\n 'traceloop.prompt.template',\n 'gen_ai.task.input',\n 'gen_ai.task.output',\n 'gen_ai.guardrail.input',\n 'gen_ai.guardrail.output',\n 'gen_ai.guardrail.error.message',\n 'gen_ai.response.revised_prompt',\n 'mcp.response.value',\n 'db.marqo.search.query',\n // Anthropic\n 'anthropic.tool_result.content',\n // Pydantic AI / Logfire\n 'pydantic_ai.all_messages',\n 'final_result',\n 'tool_arguments',\n 'tool_response',\n 'events',\n 'model_request_parameters',\n 'inputs',\n 'prompt',\n // OpenInference\n 'input.value',\n 'output.value',\n 'llm.function_call',\n 'llm.prompt_template.template',\n 'llm.prompt_template.variables',\n // Holds request parameters such as Responses `instructions`.\n 'llm.invocation_parameters',\n 'reranker.query',\n // Langfuse\n 'langfuse.trace.input',\n 'langfuse.trace.output',\n 'langfuse.observation.input',\n 'langfuse.observation.output',\n 'langfuse.observation.status_message',\n 'langfuse.experiment.item.expected_output',\n // Errors\n 'exception.message',\n 'exception.stacktrace',\n 'error.message',\n])\n\nconst CONTENT_ATTRIBUTE_PREFIXES = [\n 'gen_ai.prompt.',\n 'gen_ai.completion.',\n 'llm.prompts.',\n 'llm.completions.',\n 'llm.input_messages.',\n 'llm.output_messages.',\n 'llm.choices.',\n 'traceloop.prompt.template_variables.',\n]\n\nconst CONTENT_ATTRIBUTE_PATTERNS = [\n /^db\\.chroma\\.(query|get|delete)\\.where_document$/,\n /^embedding\\.embeddings\\.\\d+\\.embedding\\.(text|vector)$/,\n /^retrieval\\.documents\\.\\d+\\.document\\.(content|metadata)$/,\n /^reranker\\.(input|output)_documents\\.\\d+\\.document\\.content$/,\n /^(input|output)\\.images\\.\\d+\\.image\\.url$/,\n /^mastra\\.[a-z_]+\\.(input|output)$/,\n]\n\n// Prompt management metadata that shares the `gen_ai.prompt.` prefix with prompt content.\nconst NON_CONTENT_ATTRIBUTES = new Set(['gen_ai.prompt.name', 'gen_ai.prompt.version'])\n\n// Ingestion only reads exception events. Every other event (GenAI semconv messages, vector\n// database results, framework logs) can carry content.\nconst EXCEPTION_EVENT_NAME = 'exception'\n\nconst PROVIDER_METADATA_ATTRIBUTE = 'ai.response.providerMetadata'\n\nconst isContentAttribute = (key: string): boolean =>\n !NON_CONTENT_ATTRIBUTES.has(key) &&\n (CONTENT_ATTRIBUTES.has(key) ||\n CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix) || prefix === `${key}.`) ||\n CONTENT_ATTRIBUTE_PATTERNS.some((pattern) => pattern.test(key)))\n\n// Provider metadata can carry content, such as Google grounding search queries. Ingestion only\n// reads the Vercel AI Gateway cost from it.\nfunction gatewayCostOnly(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined\n }\n try {\n const cost: unknown = JSON.parse(value)?.gateway?.cost\n return typeof cost === 'number' || typeof cost === 'string' ? JSON.stringify({ gateway: { cost } }) : undefined\n } catch {\n return undefined\n }\n}\n\nfunction stripAttributes(attributes: Attributes): Attributes {\n const out: Attributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (isContentAttribute(key)) {\n continue\n }\n if (isToolDefinitionAttribute(key)) {\n const definitions = value === undefined ? undefined : withoutUnparseableToolDefinitions(value)\n if (definitions !== undefined) {\n out[key] = definitions\n }\n continue\n }\n if (key === PROVIDER_METADATA_ATTRIBUTE) {\n const metadata = gatewayCostOnly(value)\n if (metadata !== undefined) {\n out[key] = metadata\n }\n continue\n }\n out[key] = value\n }\n return out\n}\n\nfunction stripEvents(events: TimedEvent[]): TimedEvent[] {\n return events\n .filter((event) => event.name === EXCEPTION_EVENT_NAME)\n .map((event) => (event.attributes ? { ...event, attributes: stripAttributes(event.attributes) } : event))\n}\n\n/** Returns a copy of the span without the attributes, events and status message that carry content. */\nexport function stripContent(span: ReadableSpan): ReadableSpan {\n const stripped = withAttributes(\n span,\n span.attributes ? stripAttributes(span.attributes) : span.attributes,\n span.events ? stripEvents(span.events) : span.events\n )\n if (span.status?.message) {\n Object.defineProperty(stripped, 'status', {\n value: { code: span.status.code },\n enumerable: true,\n configurable: true,\n })\n }\n return stripped\n}\n","// Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway\n// emits its own $ai_generation, so each call would be captured (and, for billable\n// products, billed) twice. We only warn — the wrapper's event carries data the\n// gateway never sees (groups, custom properties, trace hierarchy).\n\n// Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the\n// main repo). gateway.us.posthog.com is live today; the rest are listed ahead of\n// any traffic moving to them.\nexport const POSTHOG_AI_GATEWAY_HOSTS: readonly string[] = [\n 'gateway.posthog.com',\n 'gateway.us.posthog.com',\n 'gateway.eu.posthog.com',\n 'ai-gateway.us.posthog.com',\n 'ai-gateway.eu.posthog.com',\n]\n\n// Swap for the dedicated AI Gateway page once it ships.\nconst GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability'\n\nconst extractHost = (baseURL: string): string | undefined => {\n try {\n // Tolerate bare hosts that omit a scheme, e.g. \"gateway.us.posthog.com/v1\".\n const hasScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(baseURL)\n return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\nexport const isPostHogAiGatewayUrl = (baseURL: string | undefined | null): boolean => {\n if (!baseURL) {\n return false\n }\n const host = extractHost(baseURL)\n return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host)\n}\n\n// Warns on every gateway call by design: the misconfiguration is impossible to\n// miss that way, and a doubled bill is worse than noisy logs.\nexport const warnIfPostHogAiGateway = (baseURL: string | undefined | null): void => {\n if (!isPostHogAiGatewayUrl(baseURL)) {\n return\n }\n console.warn(\n '[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' +\n 'Both capture $ai_generation, so every call is double-counted and double-billed. ' +\n `Use one or the other — see ${GATEWAY_DOCS_URL}.`\n )\n}\n\n// OTel spans don't pass through captureAiGeneration, so detect the gateway from\n// the span's host/URL attributes instead. These follow the GenAI / HTTP semantic\n// conventions: `server.address` is a bare host, `url.full` a full URL, both of\n// which isPostHogAiGatewayUrl accepts.\nconst OTEL_GATEWAY_URL_ATTRIBUTES = ['server.address', 'url.full'] as const\n\nexport const warnIfPostHogAiGatewayOtelAttributes = (attributes: Record<string, unknown> | undefined): void => {\n if (!attributes) {\n return\n }\n for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {\n const value = attributes[key]\n if (typeof value === 'string' && isPostHogAiGatewayUrl(value)) {\n warnIfPostHogAiGateway(value)\n return\n }\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n// OpenTelemetry's ExportResultCode.SUCCESS is 0. Keep this local so loading the\n// published subpath does not require the exporter package's transitive dependencies.\nconst EXPORT_SUCCESS = 0\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\n/**\n * Options for the PostHogTraceExporter. `projectToken` is required; a blank token disables the\n * exporter as a defensive no-op. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions = {\n projectToken: string\n host?: string\n /** Drop prompts, outputs, tool arguments and results, and error messages from exported spans. Defaults to false. */\n privacyMode?: boolean\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * `projectToken` is required; a blank token disables the exporter as a defensive no-op.\n * You can also optionally override the `host` URL.\n *\n * Exporters only see finished spans, so they can't add per-request PostHog context;\n * use {@link PostHogSpanProcessor} with `client` for that.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n private readonly disabled: boolean\n private readonly privacyMode: boolean\n\n constructor(options: PostHogTraceExporterOptions) {\n const token = normalizeToken(options.projectToken)\n const disabled = !token\n const host = token ? new URL(normalizeHost(options.host)).origin : DEFAULT_OTEL_HOST\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : {},\n })\n\n this.disabled = disabled\n this.privacyMode = options.privacyMode === true\n if (this.disabled) {\n console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.')\n }\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n if (this.disabled) {\n // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.\n // Reporting failure would make OpenTelemetry treat every span as an export error.\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n for (const span of aiSpans) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n }\n const shareable = this.privacyMode ? aiSpans.map(stripContent) : aiSpans\n super.export(shareable.map(redactSpan), resultCallback)\n }\n}\n","/** @internal */\nexport type PrivacyModeGate = {\n readonly privacyMode?: boolean\n /** Legacy spelling, still honored for callers that set it directly. */\n readonly privacy_mode?: boolean\n /** Read for posthog-node versions that predate the `privacyMode` accessor. */\n readonly options?: { readonly privacyMode?: boolean }\n}\n\n/** Per-call settings can turn redaction on but never off. */\nexport const isPrivacyModeEnabled = (client: PrivacyModeGate | undefined, privacyMode = false): boolean =>\n privacyMode === true ||\n client?.privacyMode === true ||\n client?.privacy_mode === true ||\n client?.options?.privacyMode === true\n","import type { AttributeValue, Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nimport { withAttributes } from './spans'\n\nexport type CapturedContext = {\n distinctId?: string\n sessionId?: string\n properties?: Record<string, unknown>\n}\n\n// Structural so declarations type-check against posthog-node versions that have no getContext.\nexport type PostHogContextSource = {\n getContext?(): CapturedContext | undefined\n}\n\nconst DISTINCT_ID_ATTRIBUTE = 'posthog.distinct_id'\nconst SESSION_ID_ATTRIBUTE = '$session_id'\n\n// Ingestion prefers posthog.distinct_id, so adding it would override the user a span already names.\nconst IDENTITY_ATTRIBUTES = [\n DISTINCT_ID_ATTRIBUTE,\n 'user.id',\n 'ai.telemetry.metadata.posthog_distinct_id',\n 'ai.settings.context.posthog_distinct_id',\n]\n\ntype Primitive = string | number | boolean\n\nfunction isPrimitive(value: unknown): value is Primitive {\n return (\n typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))\n )\n}\n\nfunction isHomogeneousPrimitiveArray(value: unknown): value is string[] | number[] | boolean[] {\n return Array.isArray(value) && value.every((item) => isPrimitive(item) && typeof item === typeof value[0])\n}\n\nfunction rejectNonFinite(_key: string, value: unknown): unknown {\n if (typeof value === 'number' && !Number.isFinite(value)) {\n throw new TypeError('non-finite number')\n }\n return value\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n if (isPrimitive(value) || isHomogeneousPrimitiveArray(value)) {\n return value\n }\n try {\n const json = JSON.stringify(value, rejectNonFinite)\n if (json === undefined || json === 'null') {\n return undefined\n }\n return json.startsWith('\"') ? String(JSON.parse(json)) || undefined : json\n } catch {\n return undefined\n }\n}\n\nfunction hasValues(context: CapturedContext): boolean {\n return Boolean(context.distinctId || context.sessionId || Object.keys(context.properties ?? {}).length > 0)\n}\n\nexport function captureContext(client: PostHogContextSource): CapturedContext | undefined {\n try {\n // posthog-node versions before getContext existed are still within the peer range.\n if (typeof client.getContext !== 'function') {\n return undefined\n }\n const context = client.getContext()\n return context && hasValues(context) ? context : undefined\n } catch {\n // Capturing is best effort and must never break the instrumented call.\n return undefined\n }\n}\n\nfunction toAttributes(context: CapturedContext, spanAttributes: Attributes): Attributes {\n const hasIdentity = IDENTITY_ATTRIBUTES.some((key) => spanAttributes[key] !== undefined)\n const values: Record<string, unknown> = {\n ...(context.distinctId && !hasIdentity ? { [DISTINCT_ID_ATTRIBUTE]: context.distinctId } : {}),\n ...(context.sessionId ? { [SESSION_ID_ATTRIBUTE]: context.sessionId } : {}),\n ...context.properties,\n }\n const attributes: Attributes = {}\n for (const [key, value] of Object.entries(values)) {\n if (hasIdentity && key === DISTINCT_ID_ATTRIBUTE) {\n continue\n }\n const attribute = toAttributeValue(value)\n if (attribute !== undefined) {\n attributes[key] = attribute\n }\n }\n return attributes\n}\n\nexport function withRequestContext(span: ReadableSpan, context: CapturedContext): ReadableSpan {\n try {\n const attributes = toAttributes(context, span.attributes)\n if (Object.keys(attributes).length === 0) {\n return span\n }\n return withAttributes(span, { ...attributes, ...span.attributes })\n } catch {\n // Conversion runs inside span.end() and must never break the instrumented call.\n return span\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport { BatchSpanProcessor, type SpanProcessor, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-base'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isPrivacyModeEnabled, type PrivacyModeGate } from '../privacyMode'\nimport { captureContext, withRequestContext, type CapturedContext, type PostHogContextSource } from './requestContext'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project token (the `phc_...` key). Required; a blank token disables the\n * processor as a defensive no-op.\n */\n projectToken: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * Your posthog-node client. When set, each AI span this processor exports gets the distinct ID,\n * session ID (as `$session_id`) and properties from the context entered with `client.withContext()` or\n * `client.enterContext()`. Other processors on the provider don't see them.\n * Attributes already on the span win over properties, which win over the context's IDs.\n * The context is captured when a span starts, but its properties are read when the span ends,\n * so mutating a context object while a span is open shows up in the export.\n * Must be the same posthog-node instance you call `withContext` or `enterContext` on.\n */\n client?: PostHogContextSource & PrivacyModeGate\n\n /**\n * Drop prompts, outputs, tool arguments and results, and error messages from exported spans.\n * Also on when the `client` was created with `privacyMode: true`. Defaults to false.\n */\n privacyMode?: boolean\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\nclass NoopSpanProcessor implements SpanProcessor {\n onStart(_span: Span, _parentContext: Context): void {\n return\n }\n onEnd(_span: ReadableSpan): void {\n return\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * `projectToken` is required; a blank token disables the processor as a defensive no-op.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * Request-scoped and serverless runtimes should keep a reference to this\n * processor and await {@link PostHogSpanProcessor.forceFlush} before the request\n * lifecycle ends. This waits for queued exports without sending one request per span.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })\n * const sdk = new NodeSDK({ spanProcessors: [processor] })\n * sdk.start()\n *\n * // In request-scoped runtimes, wait for queued exports before returning.\n * await processor.forceFlush()\n * ```\n *\n * @example\n * ```ts\n * // Per request: pass your posthog-node client, then wrap the work in a context.\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...', client: posthog })\n * await posthog.withContext({ distinctId: user.id, properties: { plan: 'pro' } }, () => handleRequest())\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n private readonly client?: PostHogContextSource & PrivacyModeGate\n private readonly privacyMode: boolean = false\n private readonly capturedContexts = new WeakMap<ReadableSpan, CapturedContext>()\n\n constructor(options: PostHogSpanProcessorOptions) {\n const token = normalizeToken(options.projectToken)\n if (!token) {\n console.warn('[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\n this.client = options.client\n this.privacyMode = options.privacyMode === true\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(normalizeHost(options.host)).origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n const captured = this.client && captureContext(this.client)\n if (captured) {\n this.capturedContexts.set(span, captured)\n }\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n const captured = this.capturedContexts.get(span)\n this.capturedContexts.delete(span)\n if (isAISpan(span)) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n const contextual = captured ? withRequestContext(span, captured) : span\n const shareable = isPrivacyModeEnabled(this.client, this.privacyMode) ? stripContent(contextual) : contextual\n this.inner.onEnd(redactSpan(shareable))\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["redactJson","DEFAULT_OTEL_HOST","normalizeToken","normalizeHost","OTLPTraceExporter","require","createRequire","OTLPTraceExporter"],"sources":["../../src/sanitization/base64_recognizer.ts","../../src/sanitization/media_type_context.ts","../../src/sanitization/binary_content_redactor.ts","../../src/otel/spans.ts","../../src/typeGuards.ts","../../src/mcpCredentials.ts","../../src/otel/toolDefinitions.ts","../../src/otel/redact.ts","../../src/otel/privacy.ts","../../src/gatewayWarning.ts","../../src/otel/exporter.ts","../../src/otel/batchProcessor.ts","../../src/privacyMode.ts","../../src/otel/requestContext.ts","../../src/otel/processor.ts"],"sourcesContent":["const DATA_URL_PREFIX_RE = /^data:([^;,\\s]+)(?:;[^;,\\s]+)*;base64,/i\nconst BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/\n\nexport type Base64Recognition = { kind: 'data-url'; mediaType: string } | { kind: 'raw' } | { kind: 'none' }\n\nexport class Base64Recognizer {\n recognize(value: string, minLength: number): Base64Recognition {\n const dataUrl = DATA_URL_PREFIX_RE.exec(value)\n if (dataUrl) return { kind: 'data-url', mediaType: dataUrl[1] }\n\n if (value.length < minLength) return { kind: 'none' }\n\n const confidencePrefix = value.slice(0, minLength)\n if (BASE64_ALPHABET_RE.test(confidencePrefix)) {\n return { kind: 'raw' }\n } else {\n return { kind: 'none' }\n }\n }\n}\n","const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'] as const\n\nconst STRONG_CONTEXT_KEYS = new Set([\n 'data',\n 'file_data',\n 'fileData',\n 'image_url',\n 'imageUrl',\n 'video_url',\n 'videoUrl',\n 'audio',\n 'audio_data',\n 'audioData',\n 'inline_data',\n 'inlineData',\n 'source',\n 'result',\n])\n\nconst STRONG_CONTEXT_TYPES = new Set([\n 'image',\n 'image_url',\n 'input_image',\n 'audio',\n 'input_audio',\n 'video',\n 'video_url',\n 'file',\n 'input_file',\n 'document',\n 'media',\n 'file-data',\n])\n\nconst FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data'])\nconst TYPED_BINARY_DATA_TYPES = new Set(['image', 'audio', 'video', 'document'])\n\nconst KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm'])\n\nexport class MediaTypeContext {\n static readonly EMPTY = new MediaTypeContext(undefined, undefined)\n\n constructor(\n private readonly parent: Record<string, unknown> | undefined,\n private readonly key: string | undefined,\n private readonly explicitMediaType?: string\n ) {}\n\n inferMediaType(): string | undefined {\n return (\n this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey()\n )\n }\n\n inferFromSiblingMime(): string | undefined {\n if (this.explicitMediaType) return this.explicitMediaType\n if (!this.parent) return undefined\n for (const hint of MIME_HINT_KEYS) {\n const v = this.parent[hint]\n if (typeof v === 'string') return v\n }\n return undefined\n }\n\n inferFromSiblingFormat(): string | undefined {\n if (!this.parent) return undefined\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {\n return `audio/${fmt.toLowerCase()}`\n }\n return undefined\n }\n\n inferFromParentType(): string | undefined {\n if (!this.parent) return undefined\n const t = this.parent.type\n if (typeof t !== 'string') return undefined\n if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image'\n if (t === 'audio' || t === 'input_audio') return 'audio'\n if (t === 'video' || t === 'video_url') return 'video'\n if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream'\n return undefined\n }\n\n inferFromKey(): string | undefined {\n if (!this.key) return undefined\n const key = this.key.toLowerCase()\n if (key.includes('audio')) return 'audio'\n if (key.includes('video')) return 'video'\n if (key.includes('image')) return 'image'\n if (key.includes('file') || key.includes('document')) return 'application/octet-stream'\n return undefined\n }\n\n hasDefiniteBinaryData(): boolean {\n if (\n this.parent &&\n this.key === 'data' &&\n typeof this.parent.type === 'string' &&\n TYPED_BINARY_DATA_TYPES.has(this.parent.type)\n )\n return true\n if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false\n const mediaType = this.inferFromSiblingMime()\n return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/')\n }\n\n signalsBinary(): boolean {\n if (this.explicitMediaType) return true\n if (this.parent) {\n for (const hint of MIME_HINT_KEYS) {\n if (typeof this.parent[hint] === 'string') return true\n }\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true\n const t = this.parent.type\n if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true\n }\n if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true\n return false\n }\n}\n","import { Base64Recognizer } from './base64_recognizer'\nimport { MediaTypeContext } from './media_type_context'\n\nconst STRONG_CONTEXT_MIN_LENGTH = 64\nconst WEAK_CONTEXT_MIN_LENGTH = 1024\n\nexport class BinaryContentRedactor {\n private visited: WeakSet<object> = new WeakSet()\n\n constructor(private readonly recognizer: Base64Recognizer = new Base64Recognizer()) {}\n\n redact<T>(value: T, mediaType?: string): T\n redact(value: unknown, mediaType?: string): unknown {\n this.visited = new WeakSet()\n return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY)\n }\n\n /** Redact one leaf while a caller owns bounded, type-preserving traversal. @internal */\n redactLeaf(value: string | Uint8Array, context: MediaTypeContext): string {\n return typeof value === 'string' ? this.redactString(value, context) : this.placeholderFor(context.inferMediaType())\n }\n\n private walk(value: unknown, ctx: MediaTypeContext): unknown {\n if (value === null || value === undefined) return value\n if (typeof value === 'string') return this.redactLeaf(value, ctx)\n if (typeof value !== 'object') return value\n\n // Buffer extends Uint8Array, so this branch catches both.\n if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {\n return this.redactLeaf(value, ctx)\n }\n\n if (this.visited.has(value)) return null\n this.visited.add(value)\n\n if (Array.isArray(value)) {\n return value.map((item) => this.walk(item, ctx))\n }\n\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj)) {\n Object.defineProperty(out, k, {\n value: this.walk(obj[k], new MediaTypeContext(obj, k)),\n enumerable: true,\n writable: true,\n configurable: true,\n })\n }\n return out\n }\n\n private redactString(value: string, ctx: MediaTypeContext): string {\n const hasDefiniteBinaryData = ctx.hasDefiniteBinaryData()\n const recognitionValue = hasDefiniteBinaryData ? value.replace(/[\\r\\n]/g, '') : value\n const minLength = hasDefiniteBinaryData\n ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH)\n : ctx.signalsBinary()\n ? STRONG_CONTEXT_MIN_LENGTH\n : WEAK_CONTEXT_MIN_LENGTH\n const recognition = this.recognizer.recognize(recognitionValue, minLength)\n switch (recognition.kind) {\n case 'data-url':\n return this.placeholderFor(recognition.mediaType)\n case 'raw':\n return this.placeholderFor(ctx.inferMediaType())\n case 'none':\n return value\n }\n }\n\n private placeholderFor(mediaType?: string): string {\n if (!mediaType) return '[base64 redacted]'\n if (mediaType === 'application/octet-stream') return '[base64 file redacted]'\n return `[base64 ${mediaType} redacted]`\n }\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from './types'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n\n/** Copies rather than mutates: the span is shared across every registered processor/exporter. */\nexport function withAttributes(span: ReadableSpan, attributes: Attributes, events?: TimedEvent[]): ReadableSpan {\n return Object.create(span, {\n attributes: { value: attributes, enumerable: true, configurable: true },\n ...(events ? { events: { value: events, enumerable: true, configurable: true } } : {}),\n })\n}\n","// Type guards for safer type checking\n\nexport const isString = (value: unknown): value is string => {\n return typeof value === 'string'\n}\n\nexport const isObject = (value: unknown): value is Record<string, unknown> => {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n","import { isObject } from './typeGuards'\n\nconst MCP_CREDENTIAL_FIELDS = ['headers', 'authorization']\n\n// Vercel AI SDK provider tools that configure a remote MCP server, keyed by their `id`.\nconst MCP_PROVIDER_TOOL_IDS = new Set(['openai.mcp', 'xai.mcp', 'google.mcp_server'])\n\nconst isMcpProviderTool = (tool: Record<string, unknown>): boolean =>\n (tool.type === 'provider' || tool.type === 'provider-defined') &&\n typeof tool.id === 'string' &&\n MCP_PROVIDER_TOOL_IDS.has(tool.id)\n\nconst withoutMcpCredentials = (config: Record<string, unknown>): Record<string, unknown> => {\n const redacted: Record<string, unknown> = { ...config }\n for (const field of MCP_CREDENTIAL_FIELDS) {\n if (field in redacted) {\n redacted[field] = '[REDACTED]'\n }\n }\n return redacted\n}\n\n/** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */\nexport function redactMcpToolDefinition(tool: unknown): unknown {\n if (!isObject(tool)) {\n return tool\n }\n if (tool.type === 'mcp') {\n return withoutMcpCredentials(tool)\n }\n if (isMcpProviderTool(tool) && isObject(tool.args)) {\n return { ...tool, args: withoutMcpCredentials(tool.args) }\n }\n return tool\n}\n\nexport function redactMcpToolCredentials(tools: unknown): unknown {\n return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools\n}\n","import type { AttributeValue } from '@opentelemetry/api'\n\nimport { redactMcpToolCredentials, redactMcpToolDefinition } from '../mcpCredentials'\n\n// Instrumentations serialize tool definitions, remote MCP tools and their credentials included,\n// as a JSON array, an array of JSON tools, or one JSON tool per attribute.\nconst TOOL_DEFINITION_ATTRIBUTES = new Set(['gen_ai.tool.definitions', 'ai.prompt.tools'])\nconst TOOL_DEFINITION_PATTERN = /^llm\\.tools\\.\\d+\\.tool\\.json_schema$/\n\nexport const isToolDefinitionAttribute = (key: string): boolean =>\n TOOL_DEFINITION_ATTRIBUTES.has(key) || TOOL_DEFINITION_PATTERN.test(key)\n\nconst parseToolDefinitionJson = (value: string): object | undefined => {\n try {\n const parsed: unknown = JSON.parse(value)\n return parsed !== null && typeof parsed === 'object' ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\nconst redactJson = (value: string): string => {\n const parsed = parseToolDefinitionJson(value)\n if (parsed === undefined) {\n return value\n }\n const redacted = JSON.stringify(\n Array.isArray(parsed) ? redactMcpToolCredentials(parsed) : redactMcpToolDefinition(parsed)\n )\n return redacted === JSON.stringify(parsed) ? value : redacted\n}\n\n/** Replaces MCP credentials in a tool definition attribute; values that are not JSON are kept as is. */\nexport function redactToolDefinitionCredentials(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactJson(value)\n }\n if (Array.isArray(value) && value.every((item) => typeof item === 'string' || item == null)) {\n const redacted = value.map((item) => (typeof item === 'string' ? redactJson(item) : item))\n return redacted.some((item, index) => item !== value[index]) ? redacted : value\n }\n return value\n}\n\n/** Drops the parts of a tool definition attribute that are not JSON, since their credentials cannot be found. */\nexport function withoutUnparseableToolDefinitions(value: AttributeValue): AttributeValue | undefined {\n if (typeof value === 'string') {\n return parseToolDefinitionJson(value) === undefined ? undefined : value\n }\n if (Array.isArray(value)) {\n return value.filter(\n (item): item is string => typeof item === 'string' && parseToolDefinitionJson(item) !== undefined\n )\n }\n return undefined\n}\n","import type { Attributes, AttributeValue } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from './types'\n\nimport { BinaryContentRedactor } from '../sanitization/binary_content_redactor'\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, redactToolDefinitionCredentials } from './toolDefinitions'\n\n// Deliberately always redacts here — the OTLP export path has no per-client passthrough gate.\nconst redactor = new BinaryContentRedactor()\n\nexport function redactSpan(span: ReadableSpan): ReadableSpan {\n const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes\n const events = span.events ? redactEvents(span.events) : span.events\n if (attributes === span.attributes && events === span.events) {\n return span\n }\n return withAttributes(span, attributes, events)\n}\n\nfunction redactAttributes(attributes: Attributes): Attributes {\n let changed = false\n const out: Attributes = {}\n for (const key of Object.keys(attributes)) {\n const original = attributes[key]\n const value =\n original !== undefined && isToolDefinitionAttribute(key) ? redactToolDefinitionCredentials(original) : original\n const redacted = value === undefined ? value : redactAttributeValue(value)\n if (redacted !== original) {\n changed = true\n }\n out[key] = redacted\n }\n return changed ? out : attributes\n}\n\nfunction redactEvents(events: TimedEvent[]): TimedEvent[] {\n let changed = false\n const out = events.map((event) => {\n if (!event.attributes) {\n return event\n }\n const attributes = redactAttributes(event.attributes)\n if (attributes === event.attributes) {\n return event\n }\n changed = true\n return { ...event, attributes }\n })\n return changed ? out : events\n}\n\nfunction redactAttributeValue(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactString(value)\n }\n if (isStringArray(value)) {\n let changed = false\n const out = value.map((item) => {\n if (typeof item !== 'string') {\n return item\n }\n const redacted = redactString(item)\n if (redacted !== item) {\n changed = true\n }\n return redacted\n })\n return changed ? out : value\n }\n return value\n}\n\nfunction isStringArray(value: AttributeValue): value is Array<string | null | undefined> {\n return Array.isArray(value) && value.every((item) => typeof item !== 'number' && typeof item !== 'boolean')\n}\n\nfunction redactString(value: string): string {\n const trimmed = value.trimStart()\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n const redacted = redactJson(value)\n if (redacted !== undefined) {\n return redacted\n }\n }\n return redactor.redact(value)\n}\n\nfunction redactJson(value: string): string | undefined {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n return undefined\n }\n if (parsed === null || typeof parsed !== 'object') {\n return undefined\n }\n const redactedStr = JSON.stringify(redactor.redact(parsed))\n // Preserve the original string (and its formatting) when nothing was redacted.\n return redactedStr === JSON.stringify(parsed) ? value : redactedStr\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from './types'\n\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, withoutUnparseableToolDefinitions } from './toolDefinitions'\n\n// Every attribute PostHog ingestion turns into prompt, output, tool call or error text. Ingestion\n// keeps unmapped attributes as event properties, so this also covers attributes it does not map.\nconst CONTENT_ATTRIBUTES = new Set([\n // Vercel AI SDK\n 'ai.prompt',\n 'ai.prompt.messages',\n 'ai.response.text',\n 'ai.response.object',\n 'ai.response.toolCalls',\n 'ai.response.reasoning',\n 'ai.response.reasoningDetails',\n 'ai.response.files',\n 'ai.toolCall.args',\n 'ai.toolCall.result',\n 'ai.value',\n 'ai.values',\n 'ai.embedding',\n 'ai.embeddings',\n 'ai.documents',\n 'ai.request.text',\n 'ai.decision.state',\n 'ai.decision.questions',\n 'ai.decision.answers',\n // OpenTelemetry GenAI semantic conventions\n 'gen_ai.input.messages',\n 'gen_ai.output.messages',\n 'gen_ai.system_instructions',\n 'gen_ai.prompt',\n 'gen_ai.completion',\n 'gen_ai.tool.call.arguments',\n 'gen_ai.tool.call.result',\n 'gen_ai.retrieval.documents',\n 'gen_ai.retrieval.query.text',\n 'gen_ai.memory.query.text',\n 'gen_ai.memory.records',\n 'gen_ai.evaluation.explanation',\n // Traceloop / OpenLLMetry\n 'traceloop.entity.input',\n 'traceloop.entity.output',\n 'traceloop.prompt.template',\n 'gen_ai.task.input',\n 'gen_ai.task.output',\n 'gen_ai.guardrail.input',\n 'gen_ai.guardrail.output',\n 'gen_ai.guardrail.error.message',\n 'gen_ai.response.revised_prompt',\n 'mcp.response.value',\n 'db.marqo.search.query',\n // Anthropic\n 'anthropic.tool_result.content',\n // Pydantic AI / Logfire\n 'pydantic_ai.all_messages',\n 'final_result',\n 'tool_arguments',\n 'tool_response',\n 'events',\n 'model_request_parameters',\n 'inputs',\n 'prompt',\n // OpenInference\n 'input.value',\n 'output.value',\n 'llm.function_call',\n 'llm.prompt_template.template',\n 'llm.prompt_template.variables',\n // Holds request parameters such as Responses `instructions`.\n 'llm.invocation_parameters',\n 'reranker.query',\n // Langfuse\n 'langfuse.trace.input',\n 'langfuse.trace.output',\n 'langfuse.observation.input',\n 'langfuse.observation.output',\n 'langfuse.observation.status_message',\n 'langfuse.experiment.item.expected_output',\n // Errors\n 'exception.message',\n 'exception.stacktrace',\n 'error.message',\n])\n\nconst CONTENT_ATTRIBUTE_PREFIXES = [\n 'gen_ai.prompt.',\n 'gen_ai.completion.',\n 'llm.prompts.',\n 'llm.completions.',\n 'llm.input_messages.',\n 'llm.output_messages.',\n 'llm.choices.',\n 'traceloop.prompt.template_variables.',\n]\n\nconst CONTENT_ATTRIBUTE_PATTERNS = [\n /^db\\.chroma\\.(query|get|delete)\\.where_document$/,\n /^embedding\\.embeddings\\.\\d+\\.embedding\\.(text|vector)$/,\n /^retrieval\\.documents\\.\\d+\\.document\\.(content|metadata)$/,\n /^reranker\\.(input|output)_documents\\.\\d+\\.document\\.content$/,\n /^(input|output)\\.images\\.\\d+\\.image\\.url$/,\n /^mastra\\.[a-z_]+\\.(input|output)$/,\n]\n\n// Prompt management metadata that shares the `gen_ai.prompt.` prefix with prompt content.\nconst NON_CONTENT_ATTRIBUTES = new Set(['gen_ai.prompt.name', 'gen_ai.prompt.version'])\n\n// Ingestion only reads exception events. Every other event (GenAI semconv messages, vector\n// database results, framework logs) can carry content.\nconst EXCEPTION_EVENT_NAME = 'exception'\n\nconst PROVIDER_METADATA_ATTRIBUTE = 'ai.response.providerMetadata'\n\nconst isContentAttribute = (key: string): boolean =>\n !NON_CONTENT_ATTRIBUTES.has(key) &&\n (CONTENT_ATTRIBUTES.has(key) ||\n CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix) || prefix === `${key}.`) ||\n CONTENT_ATTRIBUTE_PATTERNS.some((pattern) => pattern.test(key)))\n\n// Provider metadata can carry content, such as Google grounding search queries. Ingestion only\n// reads the Vercel AI Gateway cost from it.\nfunction gatewayCostOnly(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined\n }\n try {\n const cost: unknown = JSON.parse(value)?.gateway?.cost\n return typeof cost === 'number' || typeof cost === 'string' ? JSON.stringify({ gateway: { cost } }) : undefined\n } catch {\n return undefined\n }\n}\n\nfunction stripAttributes(attributes: Attributes): Attributes {\n const out: Attributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (isContentAttribute(key)) {\n continue\n }\n if (isToolDefinitionAttribute(key)) {\n const definitions = value === undefined ? undefined : withoutUnparseableToolDefinitions(value)\n if (definitions !== undefined) {\n out[key] = definitions\n }\n continue\n }\n if (key === PROVIDER_METADATA_ATTRIBUTE) {\n const metadata = gatewayCostOnly(value)\n if (metadata !== undefined) {\n out[key] = metadata\n }\n continue\n }\n out[key] = value\n }\n return out\n}\n\nfunction stripEvents(events: TimedEvent[]): TimedEvent[] {\n return events\n .filter((event) => event.name === EXCEPTION_EVENT_NAME)\n .map((event) => (event.attributes ? { ...event, attributes: stripAttributes(event.attributes) } : event))\n}\n\n/** Returns a copy of the span without the attributes, events and status message that carry content. */\nexport function stripContent(span: ReadableSpan): ReadableSpan {\n const stripped = withAttributes(\n span,\n span.attributes ? stripAttributes(span.attributes) : span.attributes,\n span.events ? stripEvents(span.events) : span.events\n )\n if (span.status?.message) {\n Object.defineProperty(stripped, 'status', {\n value: { code: span.status.code },\n enumerable: true,\n configurable: true,\n })\n }\n return stripped\n}\n","// Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway\n// emits its own $ai_generation, so each call would be captured (and, for billable\n// products, billed) twice. We only warn — the wrapper's event carries data the\n// gateway never sees (groups, custom properties, trace hierarchy).\n\n// Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the\n// main repo). gateway.us.posthog.com is live today; the rest are listed ahead of\n// any traffic moving to them.\nexport const POSTHOG_AI_GATEWAY_HOSTS: readonly string[] = [\n 'gateway.posthog.com',\n 'gateway.us.posthog.com',\n 'gateway.eu.posthog.com',\n 'ai-gateway.us.posthog.com',\n 'ai-gateway.eu.posthog.com',\n]\n\n// Swap for the dedicated AI Gateway page once it ships.\nconst GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability'\n\nconst extractHost = (baseURL: string): string | undefined => {\n try {\n // Tolerate bare hosts that omit a scheme, e.g. \"gateway.us.posthog.com/v1\".\n const hasScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(baseURL)\n return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\nexport const isPostHogAiGatewayUrl = (baseURL: string | undefined | null): boolean => {\n if (!baseURL) {\n return false\n }\n const host = extractHost(baseURL)\n return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host)\n}\n\n// Warns on every gateway call by design: the misconfiguration is impossible to\n// miss that way, and a doubled bill is worse than noisy logs.\nexport const warnIfPostHogAiGateway = (baseURL: string | undefined | null): void => {\n if (!isPostHogAiGatewayUrl(baseURL)) {\n return\n }\n console.warn(\n '[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' +\n 'Both capture $ai_generation, so every call is double-counted and double-billed. ' +\n `Use one or the other — see ${GATEWAY_DOCS_URL}.`\n )\n}\n\n// OTel spans don't pass through captureAiGeneration, so detect the gateway from\n// the span's host/URL attributes instead. These follow the GenAI / HTTP semantic\n// conventions: `server.address` is a bare host, `url.full` a full URL, both of\n// which isPostHogAiGatewayUrl accepts.\nconst OTEL_GATEWAY_URL_ATTRIBUTES = ['server.address', 'url.full'] as const\n\nexport const warnIfPostHogAiGatewayOtelAttributes = (attributes: Record<string, unknown> | undefined): void => {\n if (!attributes) {\n return\n }\n for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {\n const value = attributes[key]\n if (typeof value === 'string' && isPostHogAiGatewayUrl(value)) {\n warnIfPostHogAiGateway(value)\n return\n }\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from './types'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n// OpenTelemetry's ExportResultCode.SUCCESS is 0. Keep this local so loading the\n// published subpath does not require the exporter package's transitive dependencies.\nconst EXPORT_SUCCESS = 0\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\n/**\n * Options for the PostHogTraceExporter. `projectToken` is required; a blank token disables the\n * exporter as a defensive no-op. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions = {\n projectToken: string\n host?: string\n /** Drop prompts, outputs, tool arguments and results, and error messages from exported spans. Defaults to false. */\n privacyMode?: boolean\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * `projectToken` is required; a blank token disables the exporter as a defensive no-op.\n * You can also optionally override the `host` URL.\n *\n * Exporters only see finished spans, so they can't add per-request PostHog context;\n * use {@link PostHogSpanProcessor} with `client` for that.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n private readonly disabled: boolean\n private readonly privacyMode: boolean\n\n constructor(options: PostHogTraceExporterOptions) {\n const token = normalizeToken(options.projectToken)\n const disabled = !token\n const host = token ? new URL(normalizeHost(options.host)).origin : DEFAULT_OTEL_HOST\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : {},\n })\n\n this.disabled = disabled\n this.privacyMode = options.privacyMode === true\n if (this.disabled) {\n console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.')\n }\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n if (this.disabled) {\n // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.\n // Reporting failure would make OpenTelemetry treat every span as an export error.\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n for (const span of aiSpans) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n }\n const shareable = this.privacyMode ? aiSpans.map(stripContent) : aiSpans\n super.export(shareable.map(redactSpan), resultCallback)\n }\n}\n","import { createRequire } from 'node:module'\nimport type { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n\nimport type { SpanProcessor } from './types'\n\nconst require = createRequire(import.meta.url)\n\ntype LegacyTraceModule = {\n BatchSpanProcessor: new (exporter: OTLPTraceExporter) => SpanProcessor\n}\n\ntype TraceModule = {\n BatchSpanProcessor: new (options: { exporter: OTLPTraceExporter }) => SpanProcessor\n}\n\nexport function createBatchSpanProcessor(exporter: OTLPTraceExporter): SpanProcessor {\n try {\n require.resolve('@opentelemetry/sdk-trace')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') {\n throw error\n }\n const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base') as LegacyTraceModule\n return new BatchSpanProcessor(exporter)\n }\n\n const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace') as TraceModule\n return new BatchSpanProcessor({ exporter })\n}\n","/** @internal */\nexport type PrivacyModeGate = {\n readonly privacyMode?: boolean\n /** Legacy spelling, still honored for callers that set it directly. */\n readonly privacy_mode?: boolean\n /** Read for posthog-node versions that predate the `privacyMode` accessor. */\n readonly options?: { readonly privacyMode?: boolean }\n}\n\n/** Per-call settings can turn redaction on but never off. */\nexport const isPrivacyModeEnabled = (client: PrivacyModeGate | undefined, privacyMode = false): boolean =>\n privacyMode === true ||\n client?.privacyMode === true ||\n client?.privacy_mode === true ||\n client?.options?.privacyMode === true\n","import type { AttributeValue, Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan } from './types'\n\nimport { withAttributes } from './spans'\n\nexport type CapturedContext = {\n distinctId?: string\n sessionId?: string\n properties?: Record<string, unknown>\n}\n\n// Structural so declarations type-check against posthog-node versions that have no getContext.\nexport type PostHogContextSource = {\n getContext?(): CapturedContext | undefined\n}\n\nconst DISTINCT_ID_ATTRIBUTE = 'posthog.distinct_id'\nconst SESSION_ID_ATTRIBUTE = '$session_id'\n\n// Ingestion prefers posthog.distinct_id, so adding it would override the user a span already names.\nconst IDENTITY_ATTRIBUTES = [\n DISTINCT_ID_ATTRIBUTE,\n 'user.id',\n 'ai.telemetry.metadata.posthog_distinct_id',\n 'ai.settings.context.posthog_distinct_id',\n]\n\ntype Primitive = string | number | boolean\n\nfunction isPrimitive(value: unknown): value is Primitive {\n return (\n typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))\n )\n}\n\nfunction isHomogeneousPrimitiveArray(value: unknown): value is string[] | number[] | boolean[] {\n return Array.isArray(value) && value.every((item) => isPrimitive(item) && typeof item === typeof value[0])\n}\n\nfunction rejectNonFinite(_key: string, value: unknown): unknown {\n if (typeof value === 'number' && !Number.isFinite(value)) {\n throw new TypeError('non-finite number')\n }\n return value\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n if (isPrimitive(value) || isHomogeneousPrimitiveArray(value)) {\n return value\n }\n try {\n const json = JSON.stringify(value, rejectNonFinite)\n if (json === undefined || json === 'null') {\n return undefined\n }\n return json.startsWith('\"') ? String(JSON.parse(json)) || undefined : json\n } catch {\n return undefined\n }\n}\n\nfunction hasValues(context: CapturedContext): boolean {\n return Boolean(context.distinctId || context.sessionId || Object.keys(context.properties ?? {}).length > 0)\n}\n\nexport function captureContext(client: PostHogContextSource): CapturedContext | undefined {\n try {\n // posthog-node versions before getContext existed are still within the peer range.\n if (typeof client.getContext !== 'function') {\n return undefined\n }\n const context = client.getContext()\n return context && hasValues(context) ? context : undefined\n } catch {\n // Capturing is best effort and must never break the instrumented call.\n return undefined\n }\n}\n\nfunction toAttributes(context: CapturedContext, spanAttributes: Attributes): Attributes {\n const hasIdentity = IDENTITY_ATTRIBUTES.some((key) => spanAttributes[key] !== undefined)\n const values: Record<string, unknown> = {\n ...(context.distinctId && !hasIdentity ? { [DISTINCT_ID_ATTRIBUTE]: context.distinctId } : {}),\n ...(context.sessionId ? { [SESSION_ID_ATTRIBUTE]: context.sessionId } : {}),\n ...context.properties,\n }\n const attributes: Attributes = {}\n for (const [key, value] of Object.entries(values)) {\n if (hasIdentity && key === DISTINCT_ID_ATTRIBUTE) {\n continue\n }\n const attribute = toAttributeValue(value)\n if (attribute !== undefined) {\n attributes[key] = attribute\n }\n }\n return attributes\n}\n\nexport function withRequestContext(span: ReadableSpan, context: CapturedContext): ReadableSpan {\n try {\n const attributes = toAttributes(context, span.attributes)\n if (Object.keys(attributes).length === 0) {\n return span\n }\n return withAttributes(span, { ...attributes, ...span.attributes })\n } catch {\n // Conversion runs inside span.end() and must never break the instrumented call.\n return span\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport type { SpanProcessor, ReadableSpan, Span } from './types'\nimport { createBatchSpanProcessor } from './batchProcessor'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isPrivacyModeEnabled, type PrivacyModeGate } from '../privacyMode'\nimport { captureContext, withRequestContext, type CapturedContext, type PostHogContextSource } from './requestContext'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project token (the `phc_...` key). Required; a blank token disables the\n * processor as a defensive no-op.\n */\n projectToken: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * Your posthog-node client. When set, each AI span this processor exports gets the distinct ID,\n * session ID (as `$session_id`) and properties from the context entered with `client.withContext()` or\n * `client.enterContext()`. Other processors on the provider don't see them.\n * Attributes already on the span win over properties, which win over the context's IDs.\n * The context is captured when a span starts, but its properties are read when the span ends,\n * so mutating a context object while a span is open shows up in the export.\n * Must be the same posthog-node instance you call `withContext` or `enterContext` on.\n */\n client?: PostHogContextSource & PrivacyModeGate\n\n /**\n * Drop prompts, outputs, tool arguments and results, and error messages from exported spans.\n * Also on when the `client` was created with `privacyMode: true`. Defaults to false.\n */\n privacyMode?: boolean\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\nclass NoopSpanProcessor implements SpanProcessor {\n onStart(_span: Span, _parentContext: Context): void {\n return\n }\n onEnd(_span: ReadableSpan): void {\n return\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * `projectToken` is required; a blank token disables the processor as a defensive no-op.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * Request-scoped and serverless runtimes should keep a reference to this\n * processor and await {@link PostHogSpanProcessor.forceFlush} before the request\n * lifecycle ends. This waits for queued exports without sending one request per span.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })\n * const sdk = new NodeSDK({ spanProcessors: [processor] })\n * sdk.start()\n *\n * // In request-scoped runtimes, wait for queued exports before returning.\n * await processor.forceFlush()\n * ```\n *\n * @example\n * ```ts\n * // Per request: pass your posthog-node client, then wrap the work in a context.\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...', client: posthog })\n * await posthog.withContext({ distinctId: user.id, properties: { plan: 'pro' } }, () => handleRequest())\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n private readonly client?: PostHogContextSource & PrivacyModeGate\n private readonly privacyMode: boolean = false\n private readonly capturedContexts = new WeakMap<ReadableSpan, CapturedContext>()\n\n constructor(options: PostHogSpanProcessorOptions) {\n const token = normalizeToken(options.projectToken)\n if (!token) {\n console.warn('[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\n this.client = options.client\n this.privacyMode = options.privacyMode === true\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(normalizeHost(options.host)).origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n })\n this.inner = createBatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n const captured = this.client && captureContext(this.client)\n if (captured) {\n this.capturedContexts.set(span, captured)\n }\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n const captured = this.capturedContexts.get(span)\n this.capturedContexts.delete(span)\n if (isAISpan(span)) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n const contextual = captured ? withRequestContext(span, captured) : span\n const shareable = isPrivacyModeEnabled(this.client, this.privacyMode) ? stripContent(contextual) : contextual\n this.inner.onEnd(redactSpan(shareable))\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,15 @@
1
1
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
2
- import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
3
- import { Context } from "@opentelemetry/api";
2
+ import { Context, Span } from "@opentelemetry/api";
3
+ //#region src/otel/types.d.ts
4
+ type ReadableSpan = Parameters<OTLPTraceExporter['export']>[0][number];
5
+ type Span$1 = Span & ReadableSpan;
6
+ interface SpanProcessor {
7
+ onStart(span: Span$1, parentContext: Context): void;
8
+ onEnd(span: ReadableSpan): void;
9
+ forceFlush(): Promise<void>;
10
+ shutdown(): Promise<void>;
11
+ }
12
+ //#endregion
4
13
  //#region src/otel/exporter.d.ts
5
14
  /**
6
15
  * Options for the PostHogTraceExporter. `projectToken` is required; a blank token disables the
@@ -164,7 +173,7 @@ declare class PostHogSpanProcessor implements SpanProcessor {
164
173
  private readonly privacyMode;
165
174
  private readonly capturedContexts;
166
175
  constructor(options: PostHogSpanProcessorOptions);
167
- onStart(span: Span, parentContext: Context): void;
176
+ onStart(span: Span$1, parentContext: Context): void;
168
177
  onEnd(span: ReadableSpan): void;
169
178
  shutdown(): Promise<void>;
170
179
  forceFlush(): Promise<void>;
@@ -1,5 +1,5 @@
1
+ import { createRequire } from "node:module";
1
2
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
2
- import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
3
3
  //#region src/sanitization/base64_recognizer.ts
4
4
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
5
5
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
@@ -602,6 +602,20 @@ var PostHogTraceExporter = class extends OTLPTraceExporter {
602
602
  }
603
603
  };
604
604
  //#endregion
605
+ //#region src/otel/batchProcessor.ts
606
+ const require = createRequire(import.meta.url);
607
+ function createBatchSpanProcessor(exporter) {
608
+ try {
609
+ require.resolve("@opentelemetry/sdk-trace");
610
+ } catch (error) {
611
+ if (error.code !== "MODULE_NOT_FOUND") throw error;
612
+ const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
613
+ return new BatchSpanProcessor(exporter);
614
+ }
615
+ const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace");
616
+ return new BatchSpanProcessor({ exporter });
617
+ }
618
+ //#endregion
605
619
  //#region src/privacyMode.ts
606
620
  /** Per-call settings can turn redaction on but never off. */
607
621
  const isPrivacyModeEnabled = (client, privacyMode = false) => privacyMode === true || client?.privacyMode === true || client?.privacy_mode === true || client?.options?.privacyMode === true;
@@ -751,7 +765,7 @@ var PostHogSpanProcessor = class {
751
765
  url: `${host}/i/v0/ai/otel`,
752
766
  headers: { Authorization: `Bearer ${token}` }
753
767
  });
754
- this.inner = new BatchSpanProcessor(exporter);
768
+ this.inner = createBatchSpanProcessor(exporter);
755
769
  }
756
770
  }
757
771
  onStart(span, parentContext) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["redactJson","DEFAULT_OTEL_HOST","normalizeToken","normalizeHost"],"sources":["../../src/sanitization/base64_recognizer.ts","../../src/sanitization/media_type_context.ts","../../src/sanitization/binary_content_redactor.ts","../../src/otel/spans.ts","../../src/typeGuards.ts","../../src/mcpCredentials.ts","../../src/otel/toolDefinitions.ts","../../src/otel/redact.ts","../../src/otel/privacy.ts","../../src/gatewayWarning.ts","../../src/otel/exporter.ts","../../src/privacyMode.ts","../../src/otel/requestContext.ts","../../src/otel/processor.ts"],"sourcesContent":["const DATA_URL_PREFIX_RE = /^data:([^;,\\s]+)(?:;[^;,\\s]+)*;base64,/i\nconst BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/\n\nexport type Base64Recognition = { kind: 'data-url'; mediaType: string } | { kind: 'raw' } | { kind: 'none' }\n\nexport class Base64Recognizer {\n recognize(value: string, minLength: number): Base64Recognition {\n const dataUrl = DATA_URL_PREFIX_RE.exec(value)\n if (dataUrl) return { kind: 'data-url', mediaType: dataUrl[1] }\n\n if (value.length < minLength) return { kind: 'none' }\n\n const confidencePrefix = value.slice(0, minLength)\n if (BASE64_ALPHABET_RE.test(confidencePrefix)) {\n return { kind: 'raw' }\n } else {\n return { kind: 'none' }\n }\n }\n}\n","const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'] as const\n\nconst STRONG_CONTEXT_KEYS = new Set([\n 'data',\n 'file_data',\n 'fileData',\n 'image_url',\n 'imageUrl',\n 'video_url',\n 'videoUrl',\n 'audio',\n 'audio_data',\n 'audioData',\n 'inline_data',\n 'inlineData',\n 'source',\n 'result',\n])\n\nconst STRONG_CONTEXT_TYPES = new Set([\n 'image',\n 'image_url',\n 'input_image',\n 'audio',\n 'input_audio',\n 'video',\n 'video_url',\n 'file',\n 'input_file',\n 'document',\n 'media',\n 'file-data',\n])\n\nconst FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data'])\nconst TYPED_BINARY_DATA_TYPES = new Set(['image', 'audio', 'video', 'document'])\n\nconst KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm'])\n\nexport class MediaTypeContext {\n static readonly EMPTY = new MediaTypeContext(undefined, undefined)\n\n constructor(\n private readonly parent: Record<string, unknown> | undefined,\n private readonly key: string | undefined,\n private readonly explicitMediaType?: string\n ) {}\n\n inferMediaType(): string | undefined {\n return (\n this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey()\n )\n }\n\n inferFromSiblingMime(): string | undefined {\n if (this.explicitMediaType) return this.explicitMediaType\n if (!this.parent) return undefined\n for (const hint of MIME_HINT_KEYS) {\n const v = this.parent[hint]\n if (typeof v === 'string') return v\n }\n return undefined\n }\n\n inferFromSiblingFormat(): string | undefined {\n if (!this.parent) return undefined\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {\n return `audio/${fmt.toLowerCase()}`\n }\n return undefined\n }\n\n inferFromParentType(): string | undefined {\n if (!this.parent) return undefined\n const t = this.parent.type\n if (typeof t !== 'string') return undefined\n if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image'\n if (t === 'audio' || t === 'input_audio') return 'audio'\n if (t === 'video' || t === 'video_url') return 'video'\n if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream'\n return undefined\n }\n\n inferFromKey(): string | undefined {\n if (!this.key) return undefined\n const key = this.key.toLowerCase()\n if (key.includes('audio')) return 'audio'\n if (key.includes('video')) return 'video'\n if (key.includes('image')) return 'image'\n if (key.includes('file') || key.includes('document')) return 'application/octet-stream'\n return undefined\n }\n\n hasDefiniteBinaryData(): boolean {\n if (\n this.parent &&\n this.key === 'data' &&\n typeof this.parent.type === 'string' &&\n TYPED_BINARY_DATA_TYPES.has(this.parent.type)\n )\n return true\n if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false\n const mediaType = this.inferFromSiblingMime()\n return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/')\n }\n\n signalsBinary(): boolean {\n if (this.explicitMediaType) return true\n if (this.parent) {\n for (const hint of MIME_HINT_KEYS) {\n if (typeof this.parent[hint] === 'string') return true\n }\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true\n const t = this.parent.type\n if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true\n }\n if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true\n return false\n }\n}\n","import { Base64Recognizer } from './base64_recognizer'\nimport { MediaTypeContext } from './media_type_context'\n\nconst STRONG_CONTEXT_MIN_LENGTH = 64\nconst WEAK_CONTEXT_MIN_LENGTH = 1024\n\nexport class BinaryContentRedactor {\n private visited: WeakSet<object> = new WeakSet()\n\n constructor(private readonly recognizer: Base64Recognizer = new Base64Recognizer()) {}\n\n redact<T>(value: T, mediaType?: string): T\n redact(value: unknown, mediaType?: string): unknown {\n this.visited = new WeakSet()\n return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY)\n }\n\n /** Redact one leaf while a caller owns bounded, type-preserving traversal. @internal */\n redactLeaf(value: string | Uint8Array, context: MediaTypeContext): string {\n return typeof value === 'string' ? this.redactString(value, context) : this.placeholderFor(context.inferMediaType())\n }\n\n private walk(value: unknown, ctx: MediaTypeContext): unknown {\n if (value === null || value === undefined) return value\n if (typeof value === 'string') return this.redactLeaf(value, ctx)\n if (typeof value !== 'object') return value\n\n // Buffer extends Uint8Array, so this branch catches both.\n if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {\n return this.redactLeaf(value, ctx)\n }\n\n if (this.visited.has(value)) return null\n this.visited.add(value)\n\n if (Array.isArray(value)) {\n return value.map((item) => this.walk(item, ctx))\n }\n\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj)) {\n Object.defineProperty(out, k, {\n value: this.walk(obj[k], new MediaTypeContext(obj, k)),\n enumerable: true,\n writable: true,\n configurable: true,\n })\n }\n return out\n }\n\n private redactString(value: string, ctx: MediaTypeContext): string {\n const hasDefiniteBinaryData = ctx.hasDefiniteBinaryData()\n const recognitionValue = hasDefiniteBinaryData ? value.replace(/[\\r\\n]/g, '') : value\n const minLength = hasDefiniteBinaryData\n ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH)\n : ctx.signalsBinary()\n ? STRONG_CONTEXT_MIN_LENGTH\n : WEAK_CONTEXT_MIN_LENGTH\n const recognition = this.recognizer.recognize(recognitionValue, minLength)\n switch (recognition.kind) {\n case 'data-url':\n return this.placeholderFor(recognition.mediaType)\n case 'raw':\n return this.placeholderFor(ctx.inferMediaType())\n case 'none':\n return value\n }\n }\n\n private placeholderFor(mediaType?: string): string {\n if (!mediaType) return '[base64 redacted]'\n if (mediaType === 'application/octet-stream') return '[base64 file redacted]'\n return `[base64 ${mediaType} redacted]`\n }\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n\n/** Copies rather than mutates: the span is shared across every registered processor/exporter. */\nexport function withAttributes(span: ReadableSpan, attributes: Attributes, events?: TimedEvent[]): ReadableSpan {\n return Object.create(span, {\n attributes: { value: attributes, enumerable: true, configurable: true },\n ...(events ? { events: { value: events, enumerable: true, configurable: true } } : {}),\n })\n}\n","// Type guards for safer type checking\n\nexport const isString = (value: unknown): value is string => {\n return typeof value === 'string'\n}\n\nexport const isObject = (value: unknown): value is Record<string, unknown> => {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n","import { isObject } from './typeGuards'\n\nconst MCP_CREDENTIAL_FIELDS = ['headers', 'authorization']\n\n// Vercel AI SDK provider tools that configure a remote MCP server, keyed by their `id`.\nconst MCP_PROVIDER_TOOL_IDS = new Set(['openai.mcp', 'xai.mcp', 'google.mcp_server'])\n\nconst isMcpProviderTool = (tool: Record<string, unknown>): boolean =>\n (tool.type === 'provider' || tool.type === 'provider-defined') &&\n typeof tool.id === 'string' &&\n MCP_PROVIDER_TOOL_IDS.has(tool.id)\n\nconst withoutMcpCredentials = (config: Record<string, unknown>): Record<string, unknown> => {\n const redacted: Record<string, unknown> = { ...config }\n for (const field of MCP_CREDENTIAL_FIELDS) {\n if (field in redacted) {\n redacted[field] = '[REDACTED]'\n }\n }\n return redacted\n}\n\n/** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */\nexport function redactMcpToolDefinition(tool: unknown): unknown {\n if (!isObject(tool)) {\n return tool\n }\n if (tool.type === 'mcp') {\n return withoutMcpCredentials(tool)\n }\n if (isMcpProviderTool(tool) && isObject(tool.args)) {\n return { ...tool, args: withoutMcpCredentials(tool.args) }\n }\n return tool\n}\n\nexport function redactMcpToolCredentials(tools: unknown): unknown {\n return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools\n}\n","import type { AttributeValue } from '@opentelemetry/api'\n\nimport { redactMcpToolCredentials, redactMcpToolDefinition } from '../mcpCredentials'\n\n// Instrumentations serialize tool definitions, remote MCP tools and their credentials included,\n// as a JSON array, an array of JSON tools, or one JSON tool per attribute.\nconst TOOL_DEFINITION_ATTRIBUTES = new Set(['gen_ai.tool.definitions', 'ai.prompt.tools'])\nconst TOOL_DEFINITION_PATTERN = /^llm\\.tools\\.\\d+\\.tool\\.json_schema$/\n\nexport const isToolDefinitionAttribute = (key: string): boolean =>\n TOOL_DEFINITION_ATTRIBUTES.has(key) || TOOL_DEFINITION_PATTERN.test(key)\n\nconst parseToolDefinitionJson = (value: string): object | undefined => {\n try {\n const parsed: unknown = JSON.parse(value)\n return parsed !== null && typeof parsed === 'object' ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\nconst redactJson = (value: string): string => {\n const parsed = parseToolDefinitionJson(value)\n if (parsed === undefined) {\n return value\n }\n const redacted = JSON.stringify(\n Array.isArray(parsed) ? redactMcpToolCredentials(parsed) : redactMcpToolDefinition(parsed)\n )\n return redacted === JSON.stringify(parsed) ? value : redacted\n}\n\n/** Replaces MCP credentials in a tool definition attribute; values that are not JSON are kept as is. */\nexport function redactToolDefinitionCredentials(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactJson(value)\n }\n if (Array.isArray(value) && value.every((item) => typeof item === 'string' || item == null)) {\n const redacted = value.map((item) => (typeof item === 'string' ? redactJson(item) : item))\n return redacted.some((item, index) => item !== value[index]) ? redacted : value\n }\n return value\n}\n\n/** Drops the parts of a tool definition attribute that are not JSON, since their credentials cannot be found. */\nexport function withoutUnparseableToolDefinitions(value: AttributeValue): AttributeValue | undefined {\n if (typeof value === 'string') {\n return parseToolDefinitionJson(value) === undefined ? undefined : value\n }\n if (Array.isArray(value)) {\n return value.filter(\n (item): item is string => typeof item === 'string' && parseToolDefinitionJson(item) !== undefined\n )\n }\n return undefined\n}\n","import type { Attributes, AttributeValue } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'\n\nimport { BinaryContentRedactor } from '../sanitization/binary_content_redactor'\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, redactToolDefinitionCredentials } from './toolDefinitions'\n\n// Deliberately always redacts here — the OTLP export path has no per-client passthrough gate.\nconst redactor = new BinaryContentRedactor()\n\nexport function redactSpan(span: ReadableSpan): ReadableSpan {\n const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes\n const events = span.events ? redactEvents(span.events) : span.events\n if (attributes === span.attributes && events === span.events) {\n return span\n }\n return withAttributes(span, attributes, events)\n}\n\nfunction redactAttributes(attributes: Attributes): Attributes {\n let changed = false\n const out: Attributes = {}\n for (const key of Object.keys(attributes)) {\n const original = attributes[key]\n const value =\n original !== undefined && isToolDefinitionAttribute(key) ? redactToolDefinitionCredentials(original) : original\n const redacted = value === undefined ? value : redactAttributeValue(value)\n if (redacted !== original) {\n changed = true\n }\n out[key] = redacted\n }\n return changed ? out : attributes\n}\n\nfunction redactEvents(events: TimedEvent[]): TimedEvent[] {\n let changed = false\n const out = events.map((event) => {\n if (!event.attributes) {\n return event\n }\n const attributes = redactAttributes(event.attributes)\n if (attributes === event.attributes) {\n return event\n }\n changed = true\n return { ...event, attributes }\n })\n return changed ? out : events\n}\n\nfunction redactAttributeValue(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactString(value)\n }\n if (isStringArray(value)) {\n let changed = false\n const out = value.map((item) => {\n if (typeof item !== 'string') {\n return item\n }\n const redacted = redactString(item)\n if (redacted !== item) {\n changed = true\n }\n return redacted\n })\n return changed ? out : value\n }\n return value\n}\n\nfunction isStringArray(value: AttributeValue): value is Array<string | null | undefined> {\n return Array.isArray(value) && value.every((item) => typeof item !== 'number' && typeof item !== 'boolean')\n}\n\nfunction redactString(value: string): string {\n const trimmed = value.trimStart()\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n const redacted = redactJson(value)\n if (redacted !== undefined) {\n return redacted\n }\n }\n return redactor.redact(value)\n}\n\nfunction redactJson(value: string): string | undefined {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n return undefined\n }\n if (parsed === null || typeof parsed !== 'object') {\n return undefined\n }\n const redactedStr = JSON.stringify(redactor.redact(parsed))\n // Preserve the original string (and its formatting) when nothing was redacted.\n return redactedStr === JSON.stringify(parsed) ? value : redactedStr\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'\n\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, withoutUnparseableToolDefinitions } from './toolDefinitions'\n\n// Every attribute PostHog ingestion turns into prompt, output, tool call or error text. Ingestion\n// keeps unmapped attributes as event properties, so this also covers attributes it does not map.\nconst CONTENT_ATTRIBUTES = new Set([\n // Vercel AI SDK\n 'ai.prompt',\n 'ai.prompt.messages',\n 'ai.response.text',\n 'ai.response.object',\n 'ai.response.toolCalls',\n 'ai.response.reasoning',\n 'ai.response.reasoningDetails',\n 'ai.response.files',\n 'ai.toolCall.args',\n 'ai.toolCall.result',\n 'ai.value',\n 'ai.values',\n 'ai.embedding',\n 'ai.embeddings',\n 'ai.documents',\n 'ai.request.text',\n 'ai.decision.state',\n 'ai.decision.questions',\n 'ai.decision.answers',\n // OpenTelemetry GenAI semantic conventions\n 'gen_ai.input.messages',\n 'gen_ai.output.messages',\n 'gen_ai.system_instructions',\n 'gen_ai.prompt',\n 'gen_ai.completion',\n 'gen_ai.tool.call.arguments',\n 'gen_ai.tool.call.result',\n 'gen_ai.retrieval.documents',\n 'gen_ai.retrieval.query.text',\n 'gen_ai.memory.query.text',\n 'gen_ai.memory.records',\n 'gen_ai.evaluation.explanation',\n // Traceloop / OpenLLMetry\n 'traceloop.entity.input',\n 'traceloop.entity.output',\n 'traceloop.prompt.template',\n 'gen_ai.task.input',\n 'gen_ai.task.output',\n 'gen_ai.guardrail.input',\n 'gen_ai.guardrail.output',\n 'gen_ai.guardrail.error.message',\n 'gen_ai.response.revised_prompt',\n 'mcp.response.value',\n 'db.marqo.search.query',\n // Anthropic\n 'anthropic.tool_result.content',\n // Pydantic AI / Logfire\n 'pydantic_ai.all_messages',\n 'final_result',\n 'tool_arguments',\n 'tool_response',\n 'events',\n 'model_request_parameters',\n 'inputs',\n 'prompt',\n // OpenInference\n 'input.value',\n 'output.value',\n 'llm.function_call',\n 'llm.prompt_template.template',\n 'llm.prompt_template.variables',\n // Holds request parameters such as Responses `instructions`.\n 'llm.invocation_parameters',\n 'reranker.query',\n // Langfuse\n 'langfuse.trace.input',\n 'langfuse.trace.output',\n 'langfuse.observation.input',\n 'langfuse.observation.output',\n 'langfuse.observation.status_message',\n 'langfuse.experiment.item.expected_output',\n // Errors\n 'exception.message',\n 'exception.stacktrace',\n 'error.message',\n])\n\nconst CONTENT_ATTRIBUTE_PREFIXES = [\n 'gen_ai.prompt.',\n 'gen_ai.completion.',\n 'llm.prompts.',\n 'llm.completions.',\n 'llm.input_messages.',\n 'llm.output_messages.',\n 'llm.choices.',\n 'traceloop.prompt.template_variables.',\n]\n\nconst CONTENT_ATTRIBUTE_PATTERNS = [\n /^db\\.chroma\\.(query|get|delete)\\.where_document$/,\n /^embedding\\.embeddings\\.\\d+\\.embedding\\.(text|vector)$/,\n /^retrieval\\.documents\\.\\d+\\.document\\.(content|metadata)$/,\n /^reranker\\.(input|output)_documents\\.\\d+\\.document\\.content$/,\n /^(input|output)\\.images\\.\\d+\\.image\\.url$/,\n /^mastra\\.[a-z_]+\\.(input|output)$/,\n]\n\n// Prompt management metadata that shares the `gen_ai.prompt.` prefix with prompt content.\nconst NON_CONTENT_ATTRIBUTES = new Set(['gen_ai.prompt.name', 'gen_ai.prompt.version'])\n\n// Ingestion only reads exception events. Every other event (GenAI semconv messages, vector\n// database results, framework logs) can carry content.\nconst EXCEPTION_EVENT_NAME = 'exception'\n\nconst PROVIDER_METADATA_ATTRIBUTE = 'ai.response.providerMetadata'\n\nconst isContentAttribute = (key: string): boolean =>\n !NON_CONTENT_ATTRIBUTES.has(key) &&\n (CONTENT_ATTRIBUTES.has(key) ||\n CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix) || prefix === `${key}.`) ||\n CONTENT_ATTRIBUTE_PATTERNS.some((pattern) => pattern.test(key)))\n\n// Provider metadata can carry content, such as Google grounding search queries. Ingestion only\n// reads the Vercel AI Gateway cost from it.\nfunction gatewayCostOnly(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined\n }\n try {\n const cost: unknown = JSON.parse(value)?.gateway?.cost\n return typeof cost === 'number' || typeof cost === 'string' ? JSON.stringify({ gateway: { cost } }) : undefined\n } catch {\n return undefined\n }\n}\n\nfunction stripAttributes(attributes: Attributes): Attributes {\n const out: Attributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (isContentAttribute(key)) {\n continue\n }\n if (isToolDefinitionAttribute(key)) {\n const definitions = value === undefined ? undefined : withoutUnparseableToolDefinitions(value)\n if (definitions !== undefined) {\n out[key] = definitions\n }\n continue\n }\n if (key === PROVIDER_METADATA_ATTRIBUTE) {\n const metadata = gatewayCostOnly(value)\n if (metadata !== undefined) {\n out[key] = metadata\n }\n continue\n }\n out[key] = value\n }\n return out\n}\n\nfunction stripEvents(events: TimedEvent[]): TimedEvent[] {\n return events\n .filter((event) => event.name === EXCEPTION_EVENT_NAME)\n .map((event) => (event.attributes ? { ...event, attributes: stripAttributes(event.attributes) } : event))\n}\n\n/** Returns a copy of the span without the attributes, events and status message that carry content. */\nexport function stripContent(span: ReadableSpan): ReadableSpan {\n const stripped = withAttributes(\n span,\n span.attributes ? stripAttributes(span.attributes) : span.attributes,\n span.events ? stripEvents(span.events) : span.events\n )\n if (span.status?.message) {\n Object.defineProperty(stripped, 'status', {\n value: { code: span.status.code },\n enumerable: true,\n configurable: true,\n })\n }\n return stripped\n}\n","// Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway\n// emits its own $ai_generation, so each call would be captured (and, for billable\n// products, billed) twice. We only warn — the wrapper's event carries data the\n// gateway never sees (groups, custom properties, trace hierarchy).\n\n// Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the\n// main repo). gateway.us.posthog.com is live today; the rest are listed ahead of\n// any traffic moving to them.\nexport const POSTHOG_AI_GATEWAY_HOSTS: readonly string[] = [\n 'gateway.posthog.com',\n 'gateway.us.posthog.com',\n 'gateway.eu.posthog.com',\n 'ai-gateway.us.posthog.com',\n 'ai-gateway.eu.posthog.com',\n]\n\n// Swap for the dedicated AI Gateway page once it ships.\nconst GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability'\n\nconst extractHost = (baseURL: string): string | undefined => {\n try {\n // Tolerate bare hosts that omit a scheme, e.g. \"gateway.us.posthog.com/v1\".\n const hasScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(baseURL)\n return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\nexport const isPostHogAiGatewayUrl = (baseURL: string | undefined | null): boolean => {\n if (!baseURL) {\n return false\n }\n const host = extractHost(baseURL)\n return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host)\n}\n\n// Warns on every gateway call by design: the misconfiguration is impossible to\n// miss that way, and a doubled bill is worse than noisy logs.\nexport const warnIfPostHogAiGateway = (baseURL: string | undefined | null): void => {\n if (!isPostHogAiGatewayUrl(baseURL)) {\n return\n }\n console.warn(\n '[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' +\n 'Both capture $ai_generation, so every call is double-counted and double-billed. ' +\n `Use one or the other — see ${GATEWAY_DOCS_URL}.`\n )\n}\n\n// OTel spans don't pass through captureAiGeneration, so detect the gateway from\n// the span's host/URL attributes instead. These follow the GenAI / HTTP semantic\n// conventions: `server.address` is a bare host, `url.full` a full URL, both of\n// which isPostHogAiGatewayUrl accepts.\nconst OTEL_GATEWAY_URL_ATTRIBUTES = ['server.address', 'url.full'] as const\n\nexport const warnIfPostHogAiGatewayOtelAttributes = (attributes: Record<string, unknown> | undefined): void => {\n if (!attributes) {\n return\n }\n for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {\n const value = attributes[key]\n if (typeof value === 'string' && isPostHogAiGatewayUrl(value)) {\n warnIfPostHogAiGateway(value)\n return\n }\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n// OpenTelemetry's ExportResultCode.SUCCESS is 0. Keep this local so loading the\n// published subpath does not require the exporter package's transitive dependencies.\nconst EXPORT_SUCCESS = 0\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\n/**\n * Options for the PostHogTraceExporter. `projectToken` is required; a blank token disables the\n * exporter as a defensive no-op. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions = {\n projectToken: string\n host?: string\n /** Drop prompts, outputs, tool arguments and results, and error messages from exported spans. Defaults to false. */\n privacyMode?: boolean\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * `projectToken` is required; a blank token disables the exporter as a defensive no-op.\n * You can also optionally override the `host` URL.\n *\n * Exporters only see finished spans, so they can't add per-request PostHog context;\n * use {@link PostHogSpanProcessor} with `client` for that.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n private readonly disabled: boolean\n private readonly privacyMode: boolean\n\n constructor(options: PostHogTraceExporterOptions) {\n const token = normalizeToken(options.projectToken)\n const disabled = !token\n const host = token ? new URL(normalizeHost(options.host)).origin : DEFAULT_OTEL_HOST\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : {},\n })\n\n this.disabled = disabled\n this.privacyMode = options.privacyMode === true\n if (this.disabled) {\n console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.')\n }\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n if (this.disabled) {\n // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.\n // Reporting failure would make OpenTelemetry treat every span as an export error.\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n for (const span of aiSpans) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n }\n const shareable = this.privacyMode ? aiSpans.map(stripContent) : aiSpans\n super.export(shareable.map(redactSpan), resultCallback)\n }\n}\n","/** @internal */\nexport type PrivacyModeGate = {\n readonly privacyMode?: boolean\n /** Legacy spelling, still honored for callers that set it directly. */\n readonly privacy_mode?: boolean\n /** Read for posthog-node versions that predate the `privacyMode` accessor. */\n readonly options?: { readonly privacyMode?: boolean }\n}\n\n/** Per-call settings can turn redaction on but never off. */\nexport const isPrivacyModeEnabled = (client: PrivacyModeGate | undefined, privacyMode = false): boolean =>\n privacyMode === true ||\n client?.privacyMode === true ||\n client?.privacy_mode === true ||\n client?.options?.privacyMode === true\n","import type { AttributeValue, Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\n\nimport { withAttributes } from './spans'\n\nexport type CapturedContext = {\n distinctId?: string\n sessionId?: string\n properties?: Record<string, unknown>\n}\n\n// Structural so declarations type-check against posthog-node versions that have no getContext.\nexport type PostHogContextSource = {\n getContext?(): CapturedContext | undefined\n}\n\nconst DISTINCT_ID_ATTRIBUTE = 'posthog.distinct_id'\nconst SESSION_ID_ATTRIBUTE = '$session_id'\n\n// Ingestion prefers posthog.distinct_id, so adding it would override the user a span already names.\nconst IDENTITY_ATTRIBUTES = [\n DISTINCT_ID_ATTRIBUTE,\n 'user.id',\n 'ai.telemetry.metadata.posthog_distinct_id',\n 'ai.settings.context.posthog_distinct_id',\n]\n\ntype Primitive = string | number | boolean\n\nfunction isPrimitive(value: unknown): value is Primitive {\n return (\n typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))\n )\n}\n\nfunction isHomogeneousPrimitiveArray(value: unknown): value is string[] | number[] | boolean[] {\n return Array.isArray(value) && value.every((item) => isPrimitive(item) && typeof item === typeof value[0])\n}\n\nfunction rejectNonFinite(_key: string, value: unknown): unknown {\n if (typeof value === 'number' && !Number.isFinite(value)) {\n throw new TypeError('non-finite number')\n }\n return value\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n if (isPrimitive(value) || isHomogeneousPrimitiveArray(value)) {\n return value\n }\n try {\n const json = JSON.stringify(value, rejectNonFinite)\n if (json === undefined || json === 'null') {\n return undefined\n }\n return json.startsWith('\"') ? String(JSON.parse(json)) || undefined : json\n } catch {\n return undefined\n }\n}\n\nfunction hasValues(context: CapturedContext): boolean {\n return Boolean(context.distinctId || context.sessionId || Object.keys(context.properties ?? {}).length > 0)\n}\n\nexport function captureContext(client: PostHogContextSource): CapturedContext | undefined {\n try {\n // posthog-node versions before getContext existed are still within the peer range.\n if (typeof client.getContext !== 'function') {\n return undefined\n }\n const context = client.getContext()\n return context && hasValues(context) ? context : undefined\n } catch {\n // Capturing is best effort and must never break the instrumented call.\n return undefined\n }\n}\n\nfunction toAttributes(context: CapturedContext, spanAttributes: Attributes): Attributes {\n const hasIdentity = IDENTITY_ATTRIBUTES.some((key) => spanAttributes[key] !== undefined)\n const values: Record<string, unknown> = {\n ...(context.distinctId && !hasIdentity ? { [DISTINCT_ID_ATTRIBUTE]: context.distinctId } : {}),\n ...(context.sessionId ? { [SESSION_ID_ATTRIBUTE]: context.sessionId } : {}),\n ...context.properties,\n }\n const attributes: Attributes = {}\n for (const [key, value] of Object.entries(values)) {\n if (hasIdentity && key === DISTINCT_ID_ATTRIBUTE) {\n continue\n }\n const attribute = toAttributeValue(value)\n if (attribute !== undefined) {\n attributes[key] = attribute\n }\n }\n return attributes\n}\n\nexport function withRequestContext(span: ReadableSpan, context: CapturedContext): ReadableSpan {\n try {\n const attributes = toAttributes(context, span.attributes)\n if (Object.keys(attributes).length === 0) {\n return span\n }\n return withAttributes(span, { ...attributes, ...span.attributes })\n } catch {\n // Conversion runs inside span.end() and must never break the instrumented call.\n return span\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport { BatchSpanProcessor, type SpanProcessor, type ReadableSpan, type Span } from '@opentelemetry/sdk-trace-base'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isPrivacyModeEnabled, type PrivacyModeGate } from '../privacyMode'\nimport { captureContext, withRequestContext, type CapturedContext, type PostHogContextSource } from './requestContext'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project token (the `phc_...` key). Required; a blank token disables the\n * processor as a defensive no-op.\n */\n projectToken: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * Your posthog-node client. When set, each AI span this processor exports gets the distinct ID,\n * session ID (as `$session_id`) and properties from the context entered with `client.withContext()` or\n * `client.enterContext()`. Other processors on the provider don't see them.\n * Attributes already on the span win over properties, which win over the context's IDs.\n * The context is captured when a span starts, but its properties are read when the span ends,\n * so mutating a context object while a span is open shows up in the export.\n * Must be the same posthog-node instance you call `withContext` or `enterContext` on.\n */\n client?: PostHogContextSource & PrivacyModeGate\n\n /**\n * Drop prompts, outputs, tool arguments and results, and error messages from exported spans.\n * Also on when the `client` was created with `privacyMode: true`. Defaults to false.\n */\n privacyMode?: boolean\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\nclass NoopSpanProcessor implements SpanProcessor {\n onStart(_span: Span, _parentContext: Context): void {\n return\n }\n onEnd(_span: ReadableSpan): void {\n return\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * `projectToken` is required; a blank token disables the processor as a defensive no-op.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * Request-scoped and serverless runtimes should keep a reference to this\n * processor and await {@link PostHogSpanProcessor.forceFlush} before the request\n * lifecycle ends. This waits for queued exports without sending one request per span.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })\n * const sdk = new NodeSDK({ spanProcessors: [processor] })\n * sdk.start()\n *\n * // In request-scoped runtimes, wait for queued exports before returning.\n * await processor.forceFlush()\n * ```\n *\n * @example\n * ```ts\n * // Per request: pass your posthog-node client, then wrap the work in a context.\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...', client: posthog })\n * await posthog.withContext({ distinctId: user.id, properties: { plan: 'pro' } }, () => handleRequest())\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n private readonly client?: PostHogContextSource & PrivacyModeGate\n private readonly privacyMode: boolean = false\n private readonly capturedContexts = new WeakMap<ReadableSpan, CapturedContext>()\n\n constructor(options: PostHogSpanProcessorOptions) {\n const token = normalizeToken(options.projectToken)\n if (!token) {\n console.warn('[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\n this.client = options.client\n this.privacyMode = options.privacyMode === true\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(normalizeHost(options.host)).origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n const captured = this.client && captureContext(this.client)\n if (captured) {\n this.capturedContexts.set(span, captured)\n }\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n const captured = this.capturedContexts.get(span)\n this.capturedContexts.delete(span)\n if (isAISpan(span)) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n const contextual = captured ? withRequestContext(span, captured) : span\n const shareable = isPrivacyModeEnabled(this.client, this.privacyMode) ? stripContent(contextual) : contextual\n this.inner.onEnd(redactSpan(shareable))\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["redactJson","DEFAULT_OTEL_HOST","normalizeToken","normalizeHost"],"sources":["../../src/sanitization/base64_recognizer.ts","../../src/sanitization/media_type_context.ts","../../src/sanitization/binary_content_redactor.ts","../../src/otel/spans.ts","../../src/typeGuards.ts","../../src/mcpCredentials.ts","../../src/otel/toolDefinitions.ts","../../src/otel/redact.ts","../../src/otel/privacy.ts","../../src/gatewayWarning.ts","../../src/otel/exporter.ts","../../src/otel/batchProcessor.ts","../../src/privacyMode.ts","../../src/otel/requestContext.ts","../../src/otel/processor.ts"],"sourcesContent":["const DATA_URL_PREFIX_RE = /^data:([^;,\\s]+)(?:;[^;,\\s]+)*;base64,/i\nconst BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/\n\nexport type Base64Recognition = { kind: 'data-url'; mediaType: string } | { kind: 'raw' } | { kind: 'none' }\n\nexport class Base64Recognizer {\n recognize(value: string, minLength: number): Base64Recognition {\n const dataUrl = DATA_URL_PREFIX_RE.exec(value)\n if (dataUrl) return { kind: 'data-url', mediaType: dataUrl[1] }\n\n if (value.length < minLength) return { kind: 'none' }\n\n const confidencePrefix = value.slice(0, minLength)\n if (BASE64_ALPHABET_RE.test(confidencePrefix)) {\n return { kind: 'raw' }\n } else {\n return { kind: 'none' }\n }\n }\n}\n","const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'] as const\n\nconst STRONG_CONTEXT_KEYS = new Set([\n 'data',\n 'file_data',\n 'fileData',\n 'image_url',\n 'imageUrl',\n 'video_url',\n 'videoUrl',\n 'audio',\n 'audio_data',\n 'audioData',\n 'inline_data',\n 'inlineData',\n 'source',\n 'result',\n])\n\nconst STRONG_CONTEXT_TYPES = new Set([\n 'image',\n 'image_url',\n 'input_image',\n 'audio',\n 'input_audio',\n 'video',\n 'video_url',\n 'file',\n 'input_file',\n 'document',\n 'media',\n 'file-data',\n])\n\nconst FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data'])\nconst TYPED_BINARY_DATA_TYPES = new Set(['image', 'audio', 'video', 'document'])\n\nconst KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm'])\n\nexport class MediaTypeContext {\n static readonly EMPTY = new MediaTypeContext(undefined, undefined)\n\n constructor(\n private readonly parent: Record<string, unknown> | undefined,\n private readonly key: string | undefined,\n private readonly explicitMediaType?: string\n ) {}\n\n inferMediaType(): string | undefined {\n return (\n this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey()\n )\n }\n\n inferFromSiblingMime(): string | undefined {\n if (this.explicitMediaType) return this.explicitMediaType\n if (!this.parent) return undefined\n for (const hint of MIME_HINT_KEYS) {\n const v = this.parent[hint]\n if (typeof v === 'string') return v\n }\n return undefined\n }\n\n inferFromSiblingFormat(): string | undefined {\n if (!this.parent) return undefined\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {\n return `audio/${fmt.toLowerCase()}`\n }\n return undefined\n }\n\n inferFromParentType(): string | undefined {\n if (!this.parent) return undefined\n const t = this.parent.type\n if (typeof t !== 'string') return undefined\n if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image'\n if (t === 'audio' || t === 'input_audio') return 'audio'\n if (t === 'video' || t === 'video_url') return 'video'\n if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream'\n return undefined\n }\n\n inferFromKey(): string | undefined {\n if (!this.key) return undefined\n const key = this.key.toLowerCase()\n if (key.includes('audio')) return 'audio'\n if (key.includes('video')) return 'video'\n if (key.includes('image')) return 'image'\n if (key.includes('file') || key.includes('document')) return 'application/octet-stream'\n return undefined\n }\n\n hasDefiniteBinaryData(): boolean {\n if (\n this.parent &&\n this.key === 'data' &&\n typeof this.parent.type === 'string' &&\n TYPED_BINARY_DATA_TYPES.has(this.parent.type)\n )\n return true\n if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false\n const mediaType = this.inferFromSiblingMime()\n return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/')\n }\n\n signalsBinary(): boolean {\n if (this.explicitMediaType) return true\n if (this.parent) {\n for (const hint of MIME_HINT_KEYS) {\n if (typeof this.parent[hint] === 'string') return true\n }\n const fmt = this.parent.format\n if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true\n const t = this.parent.type\n if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true\n }\n if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true\n return false\n }\n}\n","import { Base64Recognizer } from './base64_recognizer'\nimport { MediaTypeContext } from './media_type_context'\n\nconst STRONG_CONTEXT_MIN_LENGTH = 64\nconst WEAK_CONTEXT_MIN_LENGTH = 1024\n\nexport class BinaryContentRedactor {\n private visited: WeakSet<object> = new WeakSet()\n\n constructor(private readonly recognizer: Base64Recognizer = new Base64Recognizer()) {}\n\n redact<T>(value: T, mediaType?: string): T\n redact(value: unknown, mediaType?: string): unknown {\n this.visited = new WeakSet()\n return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY)\n }\n\n /** Redact one leaf while a caller owns bounded, type-preserving traversal. @internal */\n redactLeaf(value: string | Uint8Array, context: MediaTypeContext): string {\n return typeof value === 'string' ? this.redactString(value, context) : this.placeholderFor(context.inferMediaType())\n }\n\n private walk(value: unknown, ctx: MediaTypeContext): unknown {\n if (value === null || value === undefined) return value\n if (typeof value === 'string') return this.redactLeaf(value, ctx)\n if (typeof value !== 'object') return value\n\n // Buffer extends Uint8Array, so this branch catches both.\n if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {\n return this.redactLeaf(value, ctx)\n }\n\n if (this.visited.has(value)) return null\n this.visited.add(value)\n\n if (Array.isArray(value)) {\n return value.map((item) => this.walk(item, ctx))\n }\n\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj)) {\n Object.defineProperty(out, k, {\n value: this.walk(obj[k], new MediaTypeContext(obj, k)),\n enumerable: true,\n writable: true,\n configurable: true,\n })\n }\n return out\n }\n\n private redactString(value: string, ctx: MediaTypeContext): string {\n const hasDefiniteBinaryData = ctx.hasDefiniteBinaryData()\n const recognitionValue = hasDefiniteBinaryData ? value.replace(/[\\r\\n]/g, '') : value\n const minLength = hasDefiniteBinaryData\n ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH)\n : ctx.signalsBinary()\n ? STRONG_CONTEXT_MIN_LENGTH\n : WEAK_CONTEXT_MIN_LENGTH\n const recognition = this.recognizer.recognize(recognitionValue, minLength)\n switch (recognition.kind) {\n case 'data-url':\n return this.placeholderFor(recognition.mediaType)\n case 'raw':\n return this.placeholderFor(ctx.inferMediaType())\n case 'none':\n return value\n }\n }\n\n private placeholderFor(mediaType?: string): string {\n if (!mediaType) return '[base64 redacted]'\n if (mediaType === 'application/octet-stream') return '[base64 file redacted]'\n return `[base64 ${mediaType} redacted]`\n }\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from './types'\n\nconst AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'] as const\n\n/**\n * Returns `true` when the span is AI-related — its name or any attribute\n * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.\n */\nexport function isAISpan(span: ReadableSpan): boolean {\n if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) {\n return true\n }\n const attributes = span.attributes\n if (attributes) {\n return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)))\n }\n return false\n}\n\n/** Copies rather than mutates: the span is shared across every registered processor/exporter. */\nexport function withAttributes(span: ReadableSpan, attributes: Attributes, events?: TimedEvent[]): ReadableSpan {\n return Object.create(span, {\n attributes: { value: attributes, enumerable: true, configurable: true },\n ...(events ? { events: { value: events, enumerable: true, configurable: true } } : {}),\n })\n}\n","// Type guards for safer type checking\n\nexport const isString = (value: unknown): value is string => {\n return typeof value === 'string'\n}\n\nexport const isObject = (value: unknown): value is Record<string, unknown> => {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n","import { isObject } from './typeGuards'\n\nconst MCP_CREDENTIAL_FIELDS = ['headers', 'authorization']\n\n// Vercel AI SDK provider tools that configure a remote MCP server, keyed by their `id`.\nconst MCP_PROVIDER_TOOL_IDS = new Set(['openai.mcp', 'xai.mcp', 'google.mcp_server'])\n\nconst isMcpProviderTool = (tool: Record<string, unknown>): boolean =>\n (tool.type === 'provider' || tool.type === 'provider-defined') &&\n typeof tool.id === 'string' &&\n MCP_PROVIDER_TOOL_IDS.has(tool.id)\n\nconst withoutMcpCredentials = (config: Record<string, unknown>): Record<string, unknown> => {\n const redacted: Record<string, unknown> = { ...config }\n for (const field of MCP_CREDENTIAL_FIELDS) {\n if (field in redacted) {\n redacted[field] = '[REDACTED]'\n }\n }\n return redacted\n}\n\n/** Hides the credentials of a remote MCP tool definition; everything else is kept as sent. */\nexport function redactMcpToolDefinition(tool: unknown): unknown {\n if (!isObject(tool)) {\n return tool\n }\n if (tool.type === 'mcp') {\n return withoutMcpCredentials(tool)\n }\n if (isMcpProviderTool(tool) && isObject(tool.args)) {\n return { ...tool, args: withoutMcpCredentials(tool.args) }\n }\n return tool\n}\n\nexport function redactMcpToolCredentials(tools: unknown): unknown {\n return Array.isArray(tools) ? tools.map(redactMcpToolDefinition) : tools\n}\n","import type { AttributeValue } from '@opentelemetry/api'\n\nimport { redactMcpToolCredentials, redactMcpToolDefinition } from '../mcpCredentials'\n\n// Instrumentations serialize tool definitions, remote MCP tools and their credentials included,\n// as a JSON array, an array of JSON tools, or one JSON tool per attribute.\nconst TOOL_DEFINITION_ATTRIBUTES = new Set(['gen_ai.tool.definitions', 'ai.prompt.tools'])\nconst TOOL_DEFINITION_PATTERN = /^llm\\.tools\\.\\d+\\.tool\\.json_schema$/\n\nexport const isToolDefinitionAttribute = (key: string): boolean =>\n TOOL_DEFINITION_ATTRIBUTES.has(key) || TOOL_DEFINITION_PATTERN.test(key)\n\nconst parseToolDefinitionJson = (value: string): object | undefined => {\n try {\n const parsed: unknown = JSON.parse(value)\n return parsed !== null && typeof parsed === 'object' ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\nconst redactJson = (value: string): string => {\n const parsed = parseToolDefinitionJson(value)\n if (parsed === undefined) {\n return value\n }\n const redacted = JSON.stringify(\n Array.isArray(parsed) ? redactMcpToolCredentials(parsed) : redactMcpToolDefinition(parsed)\n )\n return redacted === JSON.stringify(parsed) ? value : redacted\n}\n\n/** Replaces MCP credentials in a tool definition attribute; values that are not JSON are kept as is. */\nexport function redactToolDefinitionCredentials(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactJson(value)\n }\n if (Array.isArray(value) && value.every((item) => typeof item === 'string' || item == null)) {\n const redacted = value.map((item) => (typeof item === 'string' ? redactJson(item) : item))\n return redacted.some((item, index) => item !== value[index]) ? redacted : value\n }\n return value\n}\n\n/** Drops the parts of a tool definition attribute that are not JSON, since their credentials cannot be found. */\nexport function withoutUnparseableToolDefinitions(value: AttributeValue): AttributeValue | undefined {\n if (typeof value === 'string') {\n return parseToolDefinitionJson(value) === undefined ? undefined : value\n }\n if (Array.isArray(value)) {\n return value.filter(\n (item): item is string => typeof item === 'string' && parseToolDefinitionJson(item) !== undefined\n )\n }\n return undefined\n}\n","import type { Attributes, AttributeValue } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from './types'\n\nimport { BinaryContentRedactor } from '../sanitization/binary_content_redactor'\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, redactToolDefinitionCredentials } from './toolDefinitions'\n\n// Deliberately always redacts here — the OTLP export path has no per-client passthrough gate.\nconst redactor = new BinaryContentRedactor()\n\nexport function redactSpan(span: ReadableSpan): ReadableSpan {\n const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes\n const events = span.events ? redactEvents(span.events) : span.events\n if (attributes === span.attributes && events === span.events) {\n return span\n }\n return withAttributes(span, attributes, events)\n}\n\nfunction redactAttributes(attributes: Attributes): Attributes {\n let changed = false\n const out: Attributes = {}\n for (const key of Object.keys(attributes)) {\n const original = attributes[key]\n const value =\n original !== undefined && isToolDefinitionAttribute(key) ? redactToolDefinitionCredentials(original) : original\n const redacted = value === undefined ? value : redactAttributeValue(value)\n if (redacted !== original) {\n changed = true\n }\n out[key] = redacted\n }\n return changed ? out : attributes\n}\n\nfunction redactEvents(events: TimedEvent[]): TimedEvent[] {\n let changed = false\n const out = events.map((event) => {\n if (!event.attributes) {\n return event\n }\n const attributes = redactAttributes(event.attributes)\n if (attributes === event.attributes) {\n return event\n }\n changed = true\n return { ...event, attributes }\n })\n return changed ? out : events\n}\n\nfunction redactAttributeValue(value: AttributeValue): AttributeValue {\n if (typeof value === 'string') {\n return redactString(value)\n }\n if (isStringArray(value)) {\n let changed = false\n const out = value.map((item) => {\n if (typeof item !== 'string') {\n return item\n }\n const redacted = redactString(item)\n if (redacted !== item) {\n changed = true\n }\n return redacted\n })\n return changed ? out : value\n }\n return value\n}\n\nfunction isStringArray(value: AttributeValue): value is Array<string | null | undefined> {\n return Array.isArray(value) && value.every((item) => typeof item !== 'number' && typeof item !== 'boolean')\n}\n\nfunction redactString(value: string): string {\n const trimmed = value.trimStart()\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n const redacted = redactJson(value)\n if (redacted !== undefined) {\n return redacted\n }\n }\n return redactor.redact(value)\n}\n\nfunction redactJson(value: string): string | undefined {\n let parsed: unknown\n try {\n parsed = JSON.parse(value)\n } catch {\n return undefined\n }\n if (parsed === null || typeof parsed !== 'object') {\n return undefined\n }\n const redactedStr = JSON.stringify(redactor.redact(parsed))\n // Preserve the original string (and its formatting) when nothing was redacted.\n return redactedStr === JSON.stringify(parsed) ? value : redactedStr\n}\n","import type { Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan, TimedEvent } from './types'\n\nimport { withAttributes } from './spans'\nimport { isToolDefinitionAttribute, withoutUnparseableToolDefinitions } from './toolDefinitions'\n\n// Every attribute PostHog ingestion turns into prompt, output, tool call or error text. Ingestion\n// keeps unmapped attributes as event properties, so this also covers attributes it does not map.\nconst CONTENT_ATTRIBUTES = new Set([\n // Vercel AI SDK\n 'ai.prompt',\n 'ai.prompt.messages',\n 'ai.response.text',\n 'ai.response.object',\n 'ai.response.toolCalls',\n 'ai.response.reasoning',\n 'ai.response.reasoningDetails',\n 'ai.response.files',\n 'ai.toolCall.args',\n 'ai.toolCall.result',\n 'ai.value',\n 'ai.values',\n 'ai.embedding',\n 'ai.embeddings',\n 'ai.documents',\n 'ai.request.text',\n 'ai.decision.state',\n 'ai.decision.questions',\n 'ai.decision.answers',\n // OpenTelemetry GenAI semantic conventions\n 'gen_ai.input.messages',\n 'gen_ai.output.messages',\n 'gen_ai.system_instructions',\n 'gen_ai.prompt',\n 'gen_ai.completion',\n 'gen_ai.tool.call.arguments',\n 'gen_ai.tool.call.result',\n 'gen_ai.retrieval.documents',\n 'gen_ai.retrieval.query.text',\n 'gen_ai.memory.query.text',\n 'gen_ai.memory.records',\n 'gen_ai.evaluation.explanation',\n // Traceloop / OpenLLMetry\n 'traceloop.entity.input',\n 'traceloop.entity.output',\n 'traceloop.prompt.template',\n 'gen_ai.task.input',\n 'gen_ai.task.output',\n 'gen_ai.guardrail.input',\n 'gen_ai.guardrail.output',\n 'gen_ai.guardrail.error.message',\n 'gen_ai.response.revised_prompt',\n 'mcp.response.value',\n 'db.marqo.search.query',\n // Anthropic\n 'anthropic.tool_result.content',\n // Pydantic AI / Logfire\n 'pydantic_ai.all_messages',\n 'final_result',\n 'tool_arguments',\n 'tool_response',\n 'events',\n 'model_request_parameters',\n 'inputs',\n 'prompt',\n // OpenInference\n 'input.value',\n 'output.value',\n 'llm.function_call',\n 'llm.prompt_template.template',\n 'llm.prompt_template.variables',\n // Holds request parameters such as Responses `instructions`.\n 'llm.invocation_parameters',\n 'reranker.query',\n // Langfuse\n 'langfuse.trace.input',\n 'langfuse.trace.output',\n 'langfuse.observation.input',\n 'langfuse.observation.output',\n 'langfuse.observation.status_message',\n 'langfuse.experiment.item.expected_output',\n // Errors\n 'exception.message',\n 'exception.stacktrace',\n 'error.message',\n])\n\nconst CONTENT_ATTRIBUTE_PREFIXES = [\n 'gen_ai.prompt.',\n 'gen_ai.completion.',\n 'llm.prompts.',\n 'llm.completions.',\n 'llm.input_messages.',\n 'llm.output_messages.',\n 'llm.choices.',\n 'traceloop.prompt.template_variables.',\n]\n\nconst CONTENT_ATTRIBUTE_PATTERNS = [\n /^db\\.chroma\\.(query|get|delete)\\.where_document$/,\n /^embedding\\.embeddings\\.\\d+\\.embedding\\.(text|vector)$/,\n /^retrieval\\.documents\\.\\d+\\.document\\.(content|metadata)$/,\n /^reranker\\.(input|output)_documents\\.\\d+\\.document\\.content$/,\n /^(input|output)\\.images\\.\\d+\\.image\\.url$/,\n /^mastra\\.[a-z_]+\\.(input|output)$/,\n]\n\n// Prompt management metadata that shares the `gen_ai.prompt.` prefix with prompt content.\nconst NON_CONTENT_ATTRIBUTES = new Set(['gen_ai.prompt.name', 'gen_ai.prompt.version'])\n\n// Ingestion only reads exception events. Every other event (GenAI semconv messages, vector\n// database results, framework logs) can carry content.\nconst EXCEPTION_EVENT_NAME = 'exception'\n\nconst PROVIDER_METADATA_ATTRIBUTE = 'ai.response.providerMetadata'\n\nconst isContentAttribute = (key: string): boolean =>\n !NON_CONTENT_ATTRIBUTES.has(key) &&\n (CONTENT_ATTRIBUTES.has(key) ||\n CONTENT_ATTRIBUTE_PREFIXES.some((prefix) => key.startsWith(prefix) || prefix === `${key}.`) ||\n CONTENT_ATTRIBUTE_PATTERNS.some((pattern) => pattern.test(key)))\n\n// Provider metadata can carry content, such as Google grounding search queries. Ingestion only\n// reads the Vercel AI Gateway cost from it.\nfunction gatewayCostOnly(value: unknown): string | undefined {\n if (typeof value !== 'string') {\n return undefined\n }\n try {\n const cost: unknown = JSON.parse(value)?.gateway?.cost\n return typeof cost === 'number' || typeof cost === 'string' ? JSON.stringify({ gateway: { cost } }) : undefined\n } catch {\n return undefined\n }\n}\n\nfunction stripAttributes(attributes: Attributes): Attributes {\n const out: Attributes = {}\n for (const [key, value] of Object.entries(attributes)) {\n if (isContentAttribute(key)) {\n continue\n }\n if (isToolDefinitionAttribute(key)) {\n const definitions = value === undefined ? undefined : withoutUnparseableToolDefinitions(value)\n if (definitions !== undefined) {\n out[key] = definitions\n }\n continue\n }\n if (key === PROVIDER_METADATA_ATTRIBUTE) {\n const metadata = gatewayCostOnly(value)\n if (metadata !== undefined) {\n out[key] = metadata\n }\n continue\n }\n out[key] = value\n }\n return out\n}\n\nfunction stripEvents(events: TimedEvent[]): TimedEvent[] {\n return events\n .filter((event) => event.name === EXCEPTION_EVENT_NAME)\n .map((event) => (event.attributes ? { ...event, attributes: stripAttributes(event.attributes) } : event))\n}\n\n/** Returns a copy of the span without the attributes, events and status message that carry content. */\nexport function stripContent(span: ReadableSpan): ReadableSpan {\n const stripped = withAttributes(\n span,\n span.attributes ? stripAttributes(span.attributes) : span.attributes,\n span.events ? stripEvents(span.events) : span.events\n )\n if (span.status?.message) {\n Object.defineProperty(stripped, 'status', {\n value: { code: span.status.code },\n enumerable: true,\n configurable: true,\n })\n }\n return stripped\n}\n","// Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway\n// emits its own $ai_generation, so each call would be captured (and, for billable\n// products, billed) twice. We only warn — the wrapper's event carries data the\n// gateway never sees (groups, custom properties, trace hierarchy).\n\n// Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the\n// main repo). gateway.us.posthog.com is live today; the rest are listed ahead of\n// any traffic moving to them.\nexport const POSTHOG_AI_GATEWAY_HOSTS: readonly string[] = [\n 'gateway.posthog.com',\n 'gateway.us.posthog.com',\n 'gateway.eu.posthog.com',\n 'ai-gateway.us.posthog.com',\n 'ai-gateway.eu.posthog.com',\n]\n\n// Swap for the dedicated AI Gateway page once it ships.\nconst GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability'\n\nconst extractHost = (baseURL: string): string | undefined => {\n try {\n // Tolerate bare hosts that omit a scheme, e.g. \"gateway.us.posthog.com/v1\".\n const hasScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(baseURL)\n return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase()\n } catch {\n return undefined\n }\n}\n\nexport const isPostHogAiGatewayUrl = (baseURL: string | undefined | null): boolean => {\n if (!baseURL) {\n return false\n }\n const host = extractHost(baseURL)\n return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host)\n}\n\n// Warns on every gateway call by design: the misconfiguration is impossible to\n// miss that way, and a doubled bill is worse than noisy logs.\nexport const warnIfPostHogAiGateway = (baseURL: string | undefined | null): void => {\n if (!isPostHogAiGatewayUrl(baseURL)) {\n return\n }\n console.warn(\n '[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' +\n 'Both capture $ai_generation, so every call is double-counted and double-billed. ' +\n `Use one or the other — see ${GATEWAY_DOCS_URL}.`\n )\n}\n\n// OTel spans don't pass through captureAiGeneration, so detect the gateway from\n// the span's host/URL attributes instead. These follow the GenAI / HTTP semantic\n// conventions: `server.address` is a bare host, `url.full` a full URL, both of\n// which isPostHogAiGatewayUrl accepts.\nconst OTEL_GATEWAY_URL_ATTRIBUTES = ['server.address', 'url.full'] as const\n\nexport const warnIfPostHogAiGatewayOtelAttributes = (attributes: Record<string, unknown> | undefined): void => {\n if (!attributes) {\n return\n }\n for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {\n const value = attributes[key]\n if (typeof value === 'string' && isPostHogAiGatewayUrl(value)) {\n warnIfPostHogAiGateway(value)\n return\n }\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from './types'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n// OpenTelemetry's ExportResultCode.SUCCESS is 0. Keep this local so loading the\n// published subpath does not require the exporter package's transitive dependencies.\nconst EXPORT_SUCCESS = 0\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\n/**\n * Options for the PostHogTraceExporter. `projectToken` is required; a blank token disables the\n * exporter as a defensive no-op. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...' })\n * ```\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n *\n * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })\n * ```\n */\nexport type PostHogTraceExporterOptions = {\n projectToken: string\n host?: string\n /** Drop prompts, outputs, tool arguments and results, and error messages from exported spans. Defaults to false. */\n privacyMode?: boolean\n}\n\n/**\n * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP\n * ingestion endpoint. PostHog converts `gen_ai.*` spans into\n * `$ai_generation` events server-side.\n *\n * Only AI-related spans (those whose name or attribute keys start with\n * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other\n * spans are silently dropped.\n *\n * Use this when the API you're integrating with only accepts a\n * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to\n * plug PostHog into an existing processor chain. Otherwise prefer\n * {@link PostHogSpanProcessor}, which is self-contained.\n *\n * `projectToken` is required; a blank token disables the exporter as a defensive no-op.\n * You can also optionally override the `host` URL.\n *\n * Exporters only see finished spans, so they can't add per-request PostHog context;\n * use {@link PostHogSpanProcessor} with `client` for that.\n *\n * @example\n * ```ts\n * import { PostHogTraceExporter } from '@posthog/ai/otel'\n * import { registerOTel } from '@vercel/otel'\n *\n * registerOTel({\n * serviceName: 'my-app',\n * traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),\n * })\n * ```\n */\nexport class PostHogTraceExporter extends OTLPTraceExporter {\n private readonly disabled: boolean\n private readonly privacyMode: boolean\n\n constructor(options: PostHogTraceExporterOptions) {\n const token = normalizeToken(options.projectToken)\n const disabled = !token\n const host = token ? new URL(normalizeHost(options.host)).origin : DEFAULT_OTEL_HOST\n super({\n url: `${host}/i/v0/ai/otel`,\n headers: token\n ? {\n Authorization: `Bearer ${token}`,\n }\n : {},\n })\n\n this.disabled = disabled\n this.privacyMode = options.privacyMode === true\n if (this.disabled) {\n console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.')\n }\n }\n\n override export(spans: ReadableSpan[], resultCallback: (result: { code: number; error?: Error }) => void): void {\n if (this.disabled) {\n // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.\n // Reporting failure would make OpenTelemetry treat every span as an export error.\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: EXPORT_SUCCESS })\n return\n }\n for (const span of aiSpans) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n }\n const shareable = this.privacyMode ? aiSpans.map(stripContent) : aiSpans\n super.export(shareable.map(redactSpan), resultCallback)\n }\n}\n","import { createRequire } from 'node:module'\nimport type { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\n\nimport type { SpanProcessor } from './types'\n\nconst require = createRequire(import.meta.url)\n\ntype LegacyTraceModule = {\n BatchSpanProcessor: new (exporter: OTLPTraceExporter) => SpanProcessor\n}\n\ntype TraceModule = {\n BatchSpanProcessor: new (options: { exporter: OTLPTraceExporter }) => SpanProcessor\n}\n\nexport function createBatchSpanProcessor(exporter: OTLPTraceExporter): SpanProcessor {\n try {\n require.resolve('@opentelemetry/sdk-trace')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') {\n throw error\n }\n const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base') as LegacyTraceModule\n return new BatchSpanProcessor(exporter)\n }\n\n const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace') as TraceModule\n return new BatchSpanProcessor({ exporter })\n}\n","/** @internal */\nexport type PrivacyModeGate = {\n readonly privacyMode?: boolean\n /** Legacy spelling, still honored for callers that set it directly. */\n readonly privacy_mode?: boolean\n /** Read for posthog-node versions that predate the `privacyMode` accessor. */\n readonly options?: { readonly privacyMode?: boolean }\n}\n\n/** Per-call settings can turn redaction on but never off. */\nexport const isPrivacyModeEnabled = (client: PrivacyModeGate | undefined, privacyMode = false): boolean =>\n privacyMode === true ||\n client?.privacyMode === true ||\n client?.privacy_mode === true ||\n client?.options?.privacyMode === true\n","import type { AttributeValue, Attributes } from '@opentelemetry/api'\nimport type { ReadableSpan } from './types'\n\nimport { withAttributes } from './spans'\n\nexport type CapturedContext = {\n distinctId?: string\n sessionId?: string\n properties?: Record<string, unknown>\n}\n\n// Structural so declarations type-check against posthog-node versions that have no getContext.\nexport type PostHogContextSource = {\n getContext?(): CapturedContext | undefined\n}\n\nconst DISTINCT_ID_ATTRIBUTE = 'posthog.distinct_id'\nconst SESSION_ID_ATTRIBUTE = '$session_id'\n\n// Ingestion prefers posthog.distinct_id, so adding it would override the user a span already names.\nconst IDENTITY_ATTRIBUTES = [\n DISTINCT_ID_ATTRIBUTE,\n 'user.id',\n 'ai.telemetry.metadata.posthog_distinct_id',\n 'ai.settings.context.posthog_distinct_id',\n]\n\ntype Primitive = string | number | boolean\n\nfunction isPrimitive(value: unknown): value is Primitive {\n return (\n typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value))\n )\n}\n\nfunction isHomogeneousPrimitiveArray(value: unknown): value is string[] | number[] | boolean[] {\n return Array.isArray(value) && value.every((item) => isPrimitive(item) && typeof item === typeof value[0])\n}\n\nfunction rejectNonFinite(_key: string, value: unknown): unknown {\n if (typeof value === 'number' && !Number.isFinite(value)) {\n throw new TypeError('non-finite number')\n }\n return value\n}\n\nfunction toAttributeValue(value: unknown): AttributeValue | undefined {\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n if (isPrimitive(value) || isHomogeneousPrimitiveArray(value)) {\n return value\n }\n try {\n const json = JSON.stringify(value, rejectNonFinite)\n if (json === undefined || json === 'null') {\n return undefined\n }\n return json.startsWith('\"') ? String(JSON.parse(json)) || undefined : json\n } catch {\n return undefined\n }\n}\n\nfunction hasValues(context: CapturedContext): boolean {\n return Boolean(context.distinctId || context.sessionId || Object.keys(context.properties ?? {}).length > 0)\n}\n\nexport function captureContext(client: PostHogContextSource): CapturedContext | undefined {\n try {\n // posthog-node versions before getContext existed are still within the peer range.\n if (typeof client.getContext !== 'function') {\n return undefined\n }\n const context = client.getContext()\n return context && hasValues(context) ? context : undefined\n } catch {\n // Capturing is best effort and must never break the instrumented call.\n return undefined\n }\n}\n\nfunction toAttributes(context: CapturedContext, spanAttributes: Attributes): Attributes {\n const hasIdentity = IDENTITY_ATTRIBUTES.some((key) => spanAttributes[key] !== undefined)\n const values: Record<string, unknown> = {\n ...(context.distinctId && !hasIdentity ? { [DISTINCT_ID_ATTRIBUTE]: context.distinctId } : {}),\n ...(context.sessionId ? { [SESSION_ID_ATTRIBUTE]: context.sessionId } : {}),\n ...context.properties,\n }\n const attributes: Attributes = {}\n for (const [key, value] of Object.entries(values)) {\n if (hasIdentity && key === DISTINCT_ID_ATTRIBUTE) {\n continue\n }\n const attribute = toAttributeValue(value)\n if (attribute !== undefined) {\n attributes[key] = attribute\n }\n }\n return attributes\n}\n\nexport function withRequestContext(span: ReadableSpan, context: CapturedContext): ReadableSpan {\n try {\n const attributes = toAttributes(context, span.attributes)\n if (Object.keys(attributes).length === 0) {\n return span\n }\n return withAttributes(span, { ...attributes, ...span.attributes })\n } catch {\n // Conversion runs inside span.end() and must never break the instrumented call.\n return span\n }\n}\n","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { Context } from '@opentelemetry/api'\nimport type { SpanProcessor, ReadableSpan, Span } from './types'\nimport { createBatchSpanProcessor } from './batchProcessor'\n\nimport { redactSpan } from './redact'\nimport { stripContent } from './privacy'\nimport { isPrivacyModeEnabled, type PrivacyModeGate } from '../privacyMode'\nimport { captureContext, withRequestContext, type CapturedContext, type PostHogContextSource } from './requestContext'\nimport { isAISpan } from './spans'\nimport { warnIfPostHogAiGatewayOtelAttributes } from '../gatewayWarning'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeToken(value?: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\nfunction normalizeHost(value?: unknown): string {\n const normalizedValue = typeof value === 'string' ? value.trim() : ''\n return normalizedValue || DEFAULT_OTEL_HOST\n}\n\nexport interface PostHogSpanProcessorOptions {\n /**\n * Your PostHog project token (the `phc_...` key). Required; a blank token disables the\n * processor as a defensive no-op.\n */\n projectToken: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\n\n /**\n * Your posthog-node client. When set, each AI span this processor exports gets the distinct ID,\n * session ID (as `$session_id`) and properties from the context entered with `client.withContext()` or\n * `client.enterContext()`. Other processors on the provider don't see them.\n * Attributes already on the span win over properties, which win over the context's IDs.\n * The context is captured when a span starts, but its properties are read when the span ends,\n * so mutating a context object while a span is open shows up in the export.\n * Must be the same posthog-node instance you call `withContext` or `enterContext` on.\n */\n client?: PostHogContextSource & PrivacyModeGate\n\n /**\n * Drop prompts, outputs, tool arguments and results, and error messages from exported spans.\n * Also on when the `client` was created with `privacyMode: true`. Defaults to false.\n */\n privacyMode?: boolean\n\n /**\n * @internal Injected processor for testing — bypasses exporter creation.\n */\n _spanProcessor?: SpanProcessor\n}\n\nclass NoopSpanProcessor implements SpanProcessor {\n onStart(_span: Span, _parentContext: Context): void {\n return\n }\n onEnd(_span: ReadableSpan): void {\n return\n }\n shutdown(): Promise<void> {\n return Promise.resolve()\n }\n forceFlush(): Promise<void> {\n return Promise.resolve()\n }\n}\n\n/**\n * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.\n *\n * `projectToken` is required; a blank token disables the processor as a defensive no-op.\n *\n * Internally batches spans and exports them to PostHog's OTLP ingestion\n * endpoint. Only AI-related spans (those whose name or attribute keys\n * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;\n * all other spans are silently dropped.\n *\n * Request-scoped and serverless runtimes should keep a reference to this\n * processor and await {@link PostHogSpanProcessor.forceFlush} before the request\n * lifecycle ends. This waits for queued exports without sending one request per span.\n *\n * This is the recommended integration point when your setup accepts a\n * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for\n * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.\n *\n * @example\n * ```ts\n * import { PostHogSpanProcessor } from '@posthog/ai/otel'\n * import { NodeSDK } from '@opentelemetry/sdk-node'\n *\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })\n * const sdk = new NodeSDK({ spanProcessors: [processor] })\n * sdk.start()\n *\n * // In request-scoped runtimes, wait for queued exports before returning.\n * await processor.forceFlush()\n * ```\n *\n * @example\n * ```ts\n * // Per request: pass your posthog-node client, then wrap the work in a context.\n * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...', client: posthog })\n * await posthog.withContext({ distinctId: user.id, properties: { plan: 'pro' } }, () => handleRequest())\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n private readonly client?: PostHogContextSource & PrivacyModeGate\n private readonly privacyMode: boolean = false\n private readonly capturedContexts = new WeakMap<ReadableSpan, CapturedContext>()\n\n constructor(options: PostHogSpanProcessorOptions) {\n const token = normalizeToken(options.projectToken)\n if (!token) {\n console.warn('[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\n this.client = options.client\n this.privacyMode = options.privacyMode === true\n\n if (options._spanProcessor) {\n this.inner = options._spanProcessor\n } else {\n const host = new URL(normalizeHost(options.host)).origin\n const exporter = new OTLPTraceExporter({\n url: `${host}/i/v0/ai/otel`,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n })\n this.inner = createBatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\n const captured = this.client && captureContext(this.client)\n if (captured) {\n this.capturedContexts.set(span, captured)\n }\n // Forwarded unconditionally — filtering happens in onEnd. We can't filter\n // here because the span hasn't finished yet and may not have AI attributes\n // set. BatchSpanProcessor.onStart is a no-op so this is safe.\n this.inner.onStart(span, parentContext)\n }\n\n onEnd(span: ReadableSpan): void {\n const captured = this.capturedContexts.get(span)\n this.capturedContexts.delete(span)\n if (isAISpan(span)) {\n warnIfPostHogAiGatewayOtelAttributes(span.attributes)\n const contextual = captured ? withRequestContext(span, captured) : span\n const shareable = isPrivacyModeEnabled(this.client, this.privacyMode) ? stripContent(contextual) : contextual\n this.inner.onEnd(redactSpan(shareable))\n }\n }\n\n shutdown(): Promise<void> {\n return this.inner.shutdown()\n }\n\n forceFlush(): Promise<void> {\n return this.inner.forceFlush()\n }\n}\n"],"mappings":"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"}
@@ -408,7 +408,7 @@ function sanitizeValues(obj) {
408
408
  }
409
409
  //#endregion
410
410
  //#region package.json
411
- var version = "8.17.1";
411
+ var version = "8.17.2";
412
412
  //#endregion
413
413
  //#region src/serializeError.ts
414
414
  const DEFAULT_MAX_DEPTH = 3;
@@ -407,7 +407,7 @@ function sanitizeValues(obj) {
407
407
  }
408
408
  //#endregion
409
409
  //#region package.json
410
- var version = "8.17.1";
410
+ var version = "8.17.2";
411
411
  //#endregion
412
412
  //#region src/serializeError.ts
413
413
  const DEFAULT_MAX_DEPTH = 3;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/ai",
3
- "version": "8.17.1",
3
+ "version": "8.17.2",
4
4
  "description": "PostHog Node.js AI integrations",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,8 +33,8 @@
33
33
  "vitest": "^1.6.1",
34
34
  "tsdown": "0.22.14",
35
35
  "typescript": "7.0.2",
36
- "posthog-node": "^5.60.0",
37
- "@posthog-tooling/tsconfig-base": "1.1.1"
36
+ "@posthog-tooling/tsconfig-base": "1.1.1",
37
+ "posthog-node": "^5.60.1"
38
38
  },
39
39
  "keywords": [
40
40
  "posthog",
@@ -64,6 +64,7 @@
64
64
  "@opentelemetry/api": "^1.9.0",
65
65
  "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0 <1.0.0",
66
66
  "@opentelemetry/sdk-trace-base": "^2.0.0",
67
+ "@opentelemetry/sdk-trace": "^2.9.0 || ^3.0.0",
67
68
  "@ai-sdk/provider": "^2.0.0 || ^3.0.0 || ^4.0.0",
68
69
  "@openai/agents": "^0.8.0",
69
70
  "langchain": ">=1.5.5 <2.0.0",
@@ -100,6 +101,9 @@
100
101
  "@opentelemetry/sdk-trace-base": {
101
102
  "optional": true
102
103
  },
104
+ "@opentelemetry/sdk-trace": {
105
+ "optional": true
106
+ },
103
107
  "@ai-sdk/provider": {
104
108
  "optional": true
105
109
  },
@@ -186,6 +190,7 @@
186
190
  "clean": "rimraf dist",
187
191
  "test:unit": "vitest run && pnpm test:module-load",
188
192
  "test:module-load": "node tests/otel-module-load.cjs && node tests/langchain-module-load.cjs && node tests/evaluations-module-load.cjs",
193
+ "test:otel-consumers": "node tests/otel-consumers.mjs",
189
194
  "test:gemini-consumers": "node tests/gemini-consumer-types.mjs",
190
195
  "test:cassettes": "vitest run --config harness/vitest.config.ts",
191
196
  "test:cassettes:offline": "node harness/run-offline.mjs",