@posthog/ai 7.20.13 → 7.21.0

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.
@@ -299,7 +299,7 @@ function addDefaults(params) {
299
299
  };
300
300
  }
301
301
 
302
- var version = "7.20.13";
302
+ var version = "7.21.0";
303
303
 
304
304
  const DEFAULT_MAX_DEPTH = 3;
305
305
  const MAX_STACK_LINES = 20;
@@ -291,7 +291,7 @@ function addDefaults(params) {
291
291
  };
292
292
  }
293
293
 
294
- var version = "7.20.13";
294
+ var version = "7.21.0";
295
295
 
296
296
  const DEFAULT_MAX_DEPTH = 3;
297
297
  const MAX_STACK_LINES = 20;
@@ -384,7 +384,7 @@ function addDefaults(params) {
384
384
  };
385
385
  }
386
386
 
387
- var version = "7.20.13";
387
+ var version = "7.21.0";
388
388
 
389
389
  const DEFAULT_MAX_DEPTH = 3;
390
390
  const MAX_STACK_LINES = 20;
@@ -380,7 +380,7 @@ function addDefaults(params) {
380
380
  };
381
381
  }
382
382
 
383
- var version = "7.20.13";
383
+ var version = "7.21.0";
384
384
 
385
385
  const DEFAULT_MAX_DEPTH = 3;
386
386
  const MAX_STACK_LINES = 20;
package/dist/index.cjs CHANGED
@@ -733,7 +733,7 @@ function formatOpenAIResponsesInput(input, instructions) {
733
733
  return messages;
734
734
  }
735
735
 
736
- var version = "7.20.13";
736
+ var version = "7.21.0";
737
737
 
738
738
  const DEFAULT_MAX_DEPTH = 3;
739
739
  const MAX_STACK_LINES = 20;
package/dist/index.mjs CHANGED
@@ -709,7 +709,7 @@ function formatOpenAIResponsesInput(input, instructions) {
709
709
  return messages;
710
710
  }
711
711
 
712
- var version = "7.20.13";
712
+ var version = "7.21.0";
713
713
 
714
714
  const DEFAULT_MAX_DEPTH = 3;
715
715
  const MAX_STACK_LINES = 20;
@@ -699,7 +699,7 @@ var BaseCallbackHandler = class extends BaseCallbackHandlerMethodsClass {
699
699
  }
700
700
  };
701
701
 
702
- var version = "7.20.13";
702
+ var version = "7.21.0";
703
703
 
704
704
  const DEFAULT_MAX_DEPTH = 3;
705
705
  const MAX_STACK_LINES = 20;
@@ -677,7 +677,7 @@ var BaseCallbackHandler = class extends BaseCallbackHandlerMethodsClass {
677
677
  }
678
678
  };
679
679
 
680
- var version = "7.20.13";
680
+ var version = "7.21.0";
681
681
 
682
682
  const DEFAULT_MAX_DEPTH = 3;
683
683
  const MAX_STACK_LINES = 20;
@@ -534,7 +534,7 @@ function formatOpenAIResponsesInput(input, instructions) {
534
534
  return messages;
535
535
  }
536
536
 
537
- var version = "7.20.13";
537
+ var version = "7.21.0";
538
538
 
539
539
  const DEFAULT_MAX_DEPTH = 3;
540
540
  const MAX_STACK_LINES = 20;
@@ -530,7 +530,7 @@ function formatOpenAIResponsesInput(input, instructions) {
530
530
  return messages;
531
531
  }
532
532
 
533
- var version = "7.20.13";
533
+ var version = "7.21.0";
534
534
 
535
535
  const DEFAULT_MAX_DEPTH = 3;
536
536
  const MAX_STACK_LINES = 20;
@@ -116,7 +116,7 @@ const truncate = input => {
116
116
  return `${truncatedStr}... [truncated]`;
117
117
  };
118
118
 
119
- var version = "7.20.13";
119
+ var version = "7.21.0";
120
120
 
121
121
  /**
122
122
  * Normalize OpenAI Responses API input items to include a `role` field.
@@ -114,7 +114,7 @@ const truncate = input => {
114
114
  return `${truncatedStr}... [truncated]`;
115
115
  };
116
116
 
117
- var version = "7.20.13";
117
+ var version = "7.21.0";
118
118
 
119
119
  /**
120
120
  * Normalize OpenAI Responses API input items to include a `role` field.
@@ -4,6 +4,251 @@ var exporterTraceOtlpHttp = require('@opentelemetry/exporter-trace-otlp-http');
4
4
  var core = require('@opentelemetry/core');
5
5
  var sdkTraceBase = require('@opentelemetry/sdk-trace-base');
6
6
 
7
+ const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
8
+ const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
9
+ class Base64Recognizer {
10
+ recognize(value, minLength) {
11
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
12
+ if (dataUrl) return {
13
+ kind: 'data-url',
14
+ mediaType: dataUrl[1]
15
+ };
16
+ if (value.length < minLength) return {
17
+ kind: 'none'
18
+ };
19
+ const confidencePrefix = value.slice(0, minLength);
20
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
21
+ return {
22
+ kind: 'raw'
23
+ };
24
+ } else {
25
+ return {
26
+ kind: 'none'
27
+ };
28
+ }
29
+ }
30
+ }
31
+
32
+ const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
33
+ const STRONG_CONTEXT_KEYS = new Set(['data', 'file_data', 'fileData', 'image_url', 'imageUrl', 'video_url', 'videoUrl', 'audio', 'audio_data', 'audioData', 'inline_data', 'inlineData', 'source', 'result']);
34
+ const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
35
+ const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
36
+ const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
37
+ class MediaTypeContext {
38
+ static EMPTY = new MediaTypeContext(undefined, undefined);
39
+ constructor(parent, key) {
40
+ this.parent = parent;
41
+ this.key = key;
42
+ }
43
+ inferMediaType() {
44
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
45
+ }
46
+ inferFromSiblingMime() {
47
+ if (!this.parent) return undefined;
48
+ for (const hint of MIME_HINT_KEYS) {
49
+ const v = this.parent[hint];
50
+ if (typeof v === 'string') return v;
51
+ }
52
+ return undefined;
53
+ }
54
+ inferFromSiblingFormat() {
55
+ if (!this.parent) return undefined;
56
+ const fmt = this.parent.format;
57
+ if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
58
+ return `audio/${fmt.toLowerCase()}`;
59
+ }
60
+ return undefined;
61
+ }
62
+ inferFromParentType() {
63
+ if (!this.parent) return undefined;
64
+ const t = this.parent.type;
65
+ if (typeof t !== 'string') return undefined;
66
+ if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
67
+ if (t === 'audio' || t === 'input_audio') return 'audio';
68
+ if (t === 'video' || t === 'video_url') return 'video';
69
+ if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
70
+ return undefined;
71
+ }
72
+ inferFromKey() {
73
+ if (!this.key) return undefined;
74
+ const key = this.key.toLowerCase();
75
+ if (key.includes('audio')) return 'audio';
76
+ if (key.includes('video')) return 'video';
77
+ if (key.includes('image')) return 'image';
78
+ if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
79
+ return undefined;
80
+ }
81
+ signalsBinary() {
82
+ if (this.parent) {
83
+ for (const hint of MIME_HINT_KEYS) {
84
+ if (typeof this.parent[hint] === 'string') return true;
85
+ }
86
+ const fmt = this.parent.format;
87
+ if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
88
+ const t = this.parent.type;
89
+ if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
90
+ }
91
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
92
+ return false;
93
+ }
94
+ }
95
+
96
+ const STRONG_CONTEXT_MIN_LENGTH = 64;
97
+ const WEAK_CONTEXT_MIN_LENGTH = 1024;
98
+ class BinaryContentRedactor {
99
+ visited = new WeakSet();
100
+ constructor(recognizer = new Base64Recognizer()) {
101
+ this.recognizer = recognizer;
102
+ }
103
+ redact(value) {
104
+ if (this.isMultimodalEnabled()) return value;
105
+ this.visited = new WeakSet();
106
+ return this.walk(value, MediaTypeContext.EMPTY);
107
+ }
108
+ walk(value, ctx) {
109
+ if (value === null || value === undefined) return value;
110
+ if (typeof value === 'string') return this.redactString(value, ctx);
111
+ if (typeof value !== 'object') return value;
112
+
113
+ // Buffer extends Uint8Array, so this branch catches both.
114
+ if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
115
+ return this.placeholderFor(ctx.inferMediaType());
116
+ }
117
+ if (this.visited.has(value)) return null;
118
+ this.visited.add(value);
119
+ if (Array.isArray(value)) {
120
+ return value.map(item => this.walk(item, ctx));
121
+ }
122
+ const obj = value;
123
+ const out = {};
124
+ for (const k of Object.keys(obj)) {
125
+ out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
126
+ }
127
+ return out;
128
+ }
129
+ redactString(value, ctx) {
130
+ const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
131
+ const recognition = this.recognizer.recognize(value, minLength);
132
+ switch (recognition.kind) {
133
+ case 'data-url':
134
+ return this.placeholderFor(recognition.mediaType);
135
+ case 'raw':
136
+ return this.placeholderFor(ctx.inferMediaType());
137
+ case 'none':
138
+ return value;
139
+ }
140
+ }
141
+ placeholderFor(mediaType) {
142
+ if (!mediaType) return '[base64 redacted]';
143
+ if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
144
+ return `[base64 ${mediaType} redacted]`;
145
+ }
146
+ isMultimodalEnabled() {
147
+ const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
148
+ return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
149
+ }
150
+ }
151
+
152
+ const redactor = new BinaryContentRedactor();
153
+ function redactSpan(span) {
154
+ const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes;
155
+ const events = span.events ? redactEvents(span.events) : span.events;
156
+ if (attributes === span.attributes && events === span.events) {
157
+ return span;
158
+ }
159
+ // Copy rather than mutate: the span is shared across every registered processor/exporter.
160
+ return Object.create(span, {
161
+ attributes: {
162
+ value: attributes,
163
+ enumerable: true,
164
+ configurable: true
165
+ },
166
+ events: {
167
+ value: events,
168
+ enumerable: true,
169
+ configurable: true
170
+ }
171
+ });
172
+ }
173
+ function redactAttributes(attributes) {
174
+ let changed = false;
175
+ const out = {};
176
+ for (const key of Object.keys(attributes)) {
177
+ const value = attributes[key];
178
+ const redacted = value === undefined ? value : redactAttributeValue(value);
179
+ if (redacted !== value) {
180
+ changed = true;
181
+ }
182
+ out[key] = redacted;
183
+ }
184
+ return changed ? out : attributes;
185
+ }
186
+ function redactEvents(events) {
187
+ let changed = false;
188
+ const out = events.map(event => {
189
+ if (!event.attributes) {
190
+ return event;
191
+ }
192
+ const attributes = redactAttributes(event.attributes);
193
+ if (attributes === event.attributes) {
194
+ return event;
195
+ }
196
+ changed = true;
197
+ return {
198
+ ...event,
199
+ attributes
200
+ };
201
+ });
202
+ return changed ? out : events;
203
+ }
204
+ function redactAttributeValue(value) {
205
+ if (typeof value === 'string') {
206
+ return redactString(value);
207
+ }
208
+ if (isStringArray(value)) {
209
+ let changed = false;
210
+ const out = value.map(item => {
211
+ if (typeof item !== 'string') {
212
+ return item;
213
+ }
214
+ const redacted = redactString(item);
215
+ if (redacted !== item) {
216
+ changed = true;
217
+ }
218
+ return redacted;
219
+ });
220
+ return changed ? out : value;
221
+ }
222
+ return value;
223
+ }
224
+ function isStringArray(value) {
225
+ return Array.isArray(value) && value.every(item => typeof item !== 'number' && typeof item !== 'boolean');
226
+ }
227
+ function redactString(value) {
228
+ const trimmed = value.trimStart();
229
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
230
+ const redacted = redactJson(value);
231
+ if (redacted !== undefined) {
232
+ return redacted;
233
+ }
234
+ }
235
+ return redactor.redact(value);
236
+ }
237
+ function redactJson(value) {
238
+ let parsed;
239
+ try {
240
+ parsed = JSON.parse(value);
241
+ } catch {
242
+ return undefined;
243
+ }
244
+ if (parsed === null || typeof parsed !== 'object') {
245
+ return undefined;
246
+ }
247
+ const redactedStr = JSON.stringify(redactor.redact(parsed));
248
+ // Preserve the original string (and its formatting) when nothing was redacted.
249
+ return redactedStr === JSON.stringify(parsed) ? value : redactedStr;
250
+ }
251
+
7
252
  const AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'];
8
253
 
9
254
  /**
@@ -109,7 +354,7 @@ class PostHogTraceExporter extends exporterTraceOtlpHttp.OTLPTraceExporter {
109
354
  });
110
355
  return;
111
356
  }
112
- super.export(aiSpans, resultCallback);
357
+ super.export(aiSpans.map(redactSpan), resultCallback);
113
358
  }
114
359
  }
115
360
 
@@ -190,7 +435,7 @@ class PostHogSpanProcessor {
190
435
  }
191
436
  onEnd(span) {
192
437
  if (isAISpan(span)) {
193
- this.inner.onEnd(span);
438
+ this.inner.onEnd(redactSpan(span));
194
439
  }
195
440
  }
196
441
  shutdown() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/otel/spans.ts","../../src/otel/exporter.ts","../../src/otel/processor.ts"],"sourcesContent":["import type { ReadableSpan } 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","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { isAISpan } from './spans'\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\n/**\n * Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank\n * tokens disable the exporter. 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; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey?: string\n projectToken?: never\n host?: string\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 * Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.\n * You can also optionally override the `host` URL.\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\n constructor(options: PostHogTraceExporterOptions = {}) {\n const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey)\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 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: ExportResultCode.SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans, resultCallback)\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 { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(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 API key.\n */\n apiKey?: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\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 * Missing or blank project API keys disable the processor.\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 * 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 sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions = {}) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\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 ${apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\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 if (isAISpan(span)) {\n this.inner.onEnd(span)\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"],"names":["AI_SPAN_PREFIXES","isAISpan","span","some","prefix","name","startsWith","attributes","Object","keys","key","DEFAULT_OTEL_HOST","normalizeToken","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","token","projectToken","apiKey","disabled","host","URL","origin","url","headers","Authorization","console","warn","export","spans","resultCallback","code","ExportResultCode","SUCCESS","aiSpans","filter","length","normalizeApiKey","NoopSpanProcessor","onStart","_span","_parentContext","onEnd","shutdown","Promise","resolve","forceFlush","PostHogSpanProcessor","inner","_spanProcessor","exporter","BatchSpanProcessor","parentContext"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../../src/sanitization/base64_recognizer.ts","../../src/sanitization/media_type_context.ts","../../src/sanitization/binary_content_redactor.ts","../../src/otel/redact.ts","../../src/otel/spans.ts","../../src/otel/exporter.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'])\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 ) {}\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.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 signalsBinary(): boolean {\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): T\n redact(value: unknown): unknown {\n if (this.isMultimodalEnabled()) return value\n this.visited = new WeakSet()\n return this.walk(value, MediaTypeContext.EMPTY)\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.redactString(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.placeholderFor(ctx.inferMediaType())\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 out[k] = this.walk(obj[k], new MediaTypeContext(obj, k))\n }\n return out\n }\n\n private redactString(value: string, ctx: MediaTypeContext): string {\n const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH\n const recognition = this.recognizer.recognize(value, 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 private isMultimodalEnabled(): boolean {\n const val = process.env._INTERNAL_LLMA_MULTIMODAL || ''\n return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes'\n }\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'\n\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 // Copy rather than mutate: the span is shared across every registered processor/exporter.\n return Object.create(span, {\n attributes: { value: attributes, enumerable: true, configurable: true },\n events: { value: events, enumerable: true, configurable: true },\n })\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 value = attributes[key]\n const redacted = value === undefined ? value : redactAttributeValue(value)\n if (redacted !== value) {\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 { ReadableSpan } 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","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { redactSpan } from './redact'\nimport { isAISpan } from './spans'\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\n/**\n * Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank\n * tokens disable the exporter. 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; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey?: string\n projectToken?: never\n host?: string\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 * Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.\n * You can also optionally override the `host` URL.\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\n constructor(options: PostHogTraceExporterOptions = {}) {\n const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey)\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 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: ExportResultCode.SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans.map(redactSpan), resultCallback)\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 { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(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 API key.\n */\n apiKey?: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\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 * Missing or blank project API keys disable the processor.\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 * 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 sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions = {}) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\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 ${apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\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 if (isAISpan(span)) {\n this.inner.onEnd(redactSpan(span))\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"],"names":["DATA_URL_PREFIX_RE","BASE64_ALPHABET_RE","Base64Recognizer","recognize","value","minLength","dataUrl","exec","kind","mediaType","length","confidencePrefix","slice","test","MIME_HINT_KEYS","STRONG_CONTEXT_KEYS","Set","STRONG_CONTEXT_TYPES","FILE_FAMILY_TYPES","KNOWN_AUDIO_FORMATS","MediaTypeContext","EMPTY","undefined","constructor","parent","key","inferMediaType","inferFromSiblingMime","inferFromSiblingFormat","inferFromParentType","inferFromKey","hint","v","fmt","format","has","toLowerCase","t","type","includes","signalsBinary","STRONG_CONTEXT_MIN_LENGTH","WEAK_CONTEXT_MIN_LENGTH","BinaryContentRedactor","visited","WeakSet","recognizer","redact","isMultimodalEnabled","walk","ctx","redactString","Uint8Array","placeholderFor","add","Array","isArray","map","item","obj","out","k","Object","keys","recognition","val","process","env","_INTERNAL_LLMA_MULTIMODAL","redactor","redactSpan","span","attributes","redactAttributes","events","redactEvents","create","enumerable","configurable","changed","redacted","redactAttributeValue","event","isStringArray","every","trimmed","trimStart","startsWith","redactJson","parsed","JSON","parse","redactedStr","stringify","AI_SPAN_PREFIXES","isAISpan","some","prefix","name","DEFAULT_OTEL_HOST","normalizeToken","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","options","token","projectToken","apiKey","disabled","host","URL","origin","url","headers","Authorization","console","warn","export","spans","resultCallback","code","ExportResultCode","SUCCESS","aiSpans","filter","normalizeApiKey","NoopSpanProcessor","onStart","_span","_parentContext","onEnd","shutdown","Promise","resolve","forceFlush","PostHogSpanProcessor","inner","_spanProcessor","exporter","BatchSpanProcessor","parentContext"],"mappings":"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"}
@@ -2,6 +2,251 @@ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
2
2
  import { ExportResultCode } from '@opentelemetry/core';
3
3
  import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
4
4
 
5
+ const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
6
+ const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
7
+ class Base64Recognizer {
8
+ recognize(value, minLength) {
9
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
10
+ if (dataUrl) return {
11
+ kind: 'data-url',
12
+ mediaType: dataUrl[1]
13
+ };
14
+ if (value.length < minLength) return {
15
+ kind: 'none'
16
+ };
17
+ const confidencePrefix = value.slice(0, minLength);
18
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
19
+ return {
20
+ kind: 'raw'
21
+ };
22
+ } else {
23
+ return {
24
+ kind: 'none'
25
+ };
26
+ }
27
+ }
28
+ }
29
+
30
+ const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
31
+ const STRONG_CONTEXT_KEYS = new Set(['data', 'file_data', 'fileData', 'image_url', 'imageUrl', 'video_url', 'videoUrl', 'audio', 'audio_data', 'audioData', 'inline_data', 'inlineData', 'source', 'result']);
32
+ const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
33
+ const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
34
+ const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
35
+ class MediaTypeContext {
36
+ static EMPTY = new MediaTypeContext(undefined, undefined);
37
+ constructor(parent, key) {
38
+ this.parent = parent;
39
+ this.key = key;
40
+ }
41
+ inferMediaType() {
42
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
43
+ }
44
+ inferFromSiblingMime() {
45
+ if (!this.parent) return undefined;
46
+ for (const hint of MIME_HINT_KEYS) {
47
+ const v = this.parent[hint];
48
+ if (typeof v === 'string') return v;
49
+ }
50
+ return undefined;
51
+ }
52
+ inferFromSiblingFormat() {
53
+ if (!this.parent) return undefined;
54
+ const fmt = this.parent.format;
55
+ if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
56
+ return `audio/${fmt.toLowerCase()}`;
57
+ }
58
+ return undefined;
59
+ }
60
+ inferFromParentType() {
61
+ if (!this.parent) return undefined;
62
+ const t = this.parent.type;
63
+ if (typeof t !== 'string') return undefined;
64
+ if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
65
+ if (t === 'audio' || t === 'input_audio') return 'audio';
66
+ if (t === 'video' || t === 'video_url') return 'video';
67
+ if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
68
+ return undefined;
69
+ }
70
+ inferFromKey() {
71
+ if (!this.key) return undefined;
72
+ const key = this.key.toLowerCase();
73
+ if (key.includes('audio')) return 'audio';
74
+ if (key.includes('video')) return 'video';
75
+ if (key.includes('image')) return 'image';
76
+ if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
77
+ return undefined;
78
+ }
79
+ signalsBinary() {
80
+ if (this.parent) {
81
+ for (const hint of MIME_HINT_KEYS) {
82
+ if (typeof this.parent[hint] === 'string') return true;
83
+ }
84
+ const fmt = this.parent.format;
85
+ if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
86
+ const t = this.parent.type;
87
+ if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
88
+ }
89
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
90
+ return false;
91
+ }
92
+ }
93
+
94
+ const STRONG_CONTEXT_MIN_LENGTH = 64;
95
+ const WEAK_CONTEXT_MIN_LENGTH = 1024;
96
+ class BinaryContentRedactor {
97
+ visited = new WeakSet();
98
+ constructor(recognizer = new Base64Recognizer()) {
99
+ this.recognizer = recognizer;
100
+ }
101
+ redact(value) {
102
+ if (this.isMultimodalEnabled()) return value;
103
+ this.visited = new WeakSet();
104
+ return this.walk(value, MediaTypeContext.EMPTY);
105
+ }
106
+ walk(value, ctx) {
107
+ if (value === null || value === undefined) return value;
108
+ if (typeof value === 'string') return this.redactString(value, ctx);
109
+ if (typeof value !== 'object') return value;
110
+
111
+ // Buffer extends Uint8Array, so this branch catches both.
112
+ if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
113
+ return this.placeholderFor(ctx.inferMediaType());
114
+ }
115
+ if (this.visited.has(value)) return null;
116
+ this.visited.add(value);
117
+ if (Array.isArray(value)) {
118
+ return value.map(item => this.walk(item, ctx));
119
+ }
120
+ const obj = value;
121
+ const out = {};
122
+ for (const k of Object.keys(obj)) {
123
+ out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
124
+ }
125
+ return out;
126
+ }
127
+ redactString(value, ctx) {
128
+ const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
129
+ const recognition = this.recognizer.recognize(value, minLength);
130
+ switch (recognition.kind) {
131
+ case 'data-url':
132
+ return this.placeholderFor(recognition.mediaType);
133
+ case 'raw':
134
+ return this.placeholderFor(ctx.inferMediaType());
135
+ case 'none':
136
+ return value;
137
+ }
138
+ }
139
+ placeholderFor(mediaType) {
140
+ if (!mediaType) return '[base64 redacted]';
141
+ if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
142
+ return `[base64 ${mediaType} redacted]`;
143
+ }
144
+ isMultimodalEnabled() {
145
+ const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
146
+ return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
147
+ }
148
+ }
149
+
150
+ const redactor = new BinaryContentRedactor();
151
+ function redactSpan(span) {
152
+ const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes;
153
+ const events = span.events ? redactEvents(span.events) : span.events;
154
+ if (attributes === span.attributes && events === span.events) {
155
+ return span;
156
+ }
157
+ // Copy rather than mutate: the span is shared across every registered processor/exporter.
158
+ return Object.create(span, {
159
+ attributes: {
160
+ value: attributes,
161
+ enumerable: true,
162
+ configurable: true
163
+ },
164
+ events: {
165
+ value: events,
166
+ enumerable: true,
167
+ configurable: true
168
+ }
169
+ });
170
+ }
171
+ function redactAttributes(attributes) {
172
+ let changed = false;
173
+ const out = {};
174
+ for (const key of Object.keys(attributes)) {
175
+ const value = attributes[key];
176
+ const redacted = value === undefined ? value : redactAttributeValue(value);
177
+ if (redacted !== value) {
178
+ changed = true;
179
+ }
180
+ out[key] = redacted;
181
+ }
182
+ return changed ? out : attributes;
183
+ }
184
+ function redactEvents(events) {
185
+ let changed = false;
186
+ const out = events.map(event => {
187
+ if (!event.attributes) {
188
+ return event;
189
+ }
190
+ const attributes = redactAttributes(event.attributes);
191
+ if (attributes === event.attributes) {
192
+ return event;
193
+ }
194
+ changed = true;
195
+ return {
196
+ ...event,
197
+ attributes
198
+ };
199
+ });
200
+ return changed ? out : events;
201
+ }
202
+ function redactAttributeValue(value) {
203
+ if (typeof value === 'string') {
204
+ return redactString(value);
205
+ }
206
+ if (isStringArray(value)) {
207
+ let changed = false;
208
+ const out = value.map(item => {
209
+ if (typeof item !== 'string') {
210
+ return item;
211
+ }
212
+ const redacted = redactString(item);
213
+ if (redacted !== item) {
214
+ changed = true;
215
+ }
216
+ return redacted;
217
+ });
218
+ return changed ? out : value;
219
+ }
220
+ return value;
221
+ }
222
+ function isStringArray(value) {
223
+ return Array.isArray(value) && value.every(item => typeof item !== 'number' && typeof item !== 'boolean');
224
+ }
225
+ function redactString(value) {
226
+ const trimmed = value.trimStart();
227
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
228
+ const redacted = redactJson(value);
229
+ if (redacted !== undefined) {
230
+ return redacted;
231
+ }
232
+ }
233
+ return redactor.redact(value);
234
+ }
235
+ function redactJson(value) {
236
+ let parsed;
237
+ try {
238
+ parsed = JSON.parse(value);
239
+ } catch {
240
+ return undefined;
241
+ }
242
+ if (parsed === null || typeof parsed !== 'object') {
243
+ return undefined;
244
+ }
245
+ const redactedStr = JSON.stringify(redactor.redact(parsed));
246
+ // Preserve the original string (and its formatting) when nothing was redacted.
247
+ return redactedStr === JSON.stringify(parsed) ? value : redactedStr;
248
+ }
249
+
5
250
  const AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'];
6
251
 
7
252
  /**
@@ -107,7 +352,7 @@ class PostHogTraceExporter extends OTLPTraceExporter {
107
352
  });
108
353
  return;
109
354
  }
110
- super.export(aiSpans, resultCallback);
355
+ super.export(aiSpans.map(redactSpan), resultCallback);
111
356
  }
112
357
  }
113
358
 
@@ -188,7 +433,7 @@ class PostHogSpanProcessor {
188
433
  }
189
434
  onEnd(span) {
190
435
  if (isAISpan(span)) {
191
- this.inner.onEnd(span);
436
+ this.inner.onEnd(redactSpan(span));
192
437
  }
193
438
  }
194
439
  shutdown() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/otel/spans.ts","../../src/otel/exporter.ts","../../src/otel/processor.ts"],"sourcesContent":["import type { ReadableSpan } 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","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { isAISpan } from './spans'\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\n/**\n * Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank\n * tokens disable the exporter. 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; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey?: string\n projectToken?: never\n host?: string\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 * Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.\n * You can also optionally override the `host` URL.\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\n constructor(options: PostHogTraceExporterOptions = {}) {\n const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey)\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 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: ExportResultCode.SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans, resultCallback)\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 { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(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 API key.\n */\n apiKey?: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\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 * Missing or blank project API keys disable the processor.\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 * 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 sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions = {}) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\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 ${apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\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 if (isAISpan(span)) {\n this.inner.onEnd(span)\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"],"names":["AI_SPAN_PREFIXES","isAISpan","span","some","prefix","name","startsWith","attributes","Object","keys","key","DEFAULT_OTEL_HOST","normalizeToken","value","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","constructor","options","token","projectToken","apiKey","disabled","host","URL","origin","url","headers","Authorization","console","warn","export","spans","resultCallback","code","ExportResultCode","SUCCESS","aiSpans","filter","length","normalizeApiKey","NoopSpanProcessor","onStart","_span","_parentContext","onEnd","shutdown","Promise","resolve","forceFlush","PostHogSpanProcessor","inner","_spanProcessor","exporter","BatchSpanProcessor","parentContext"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/sanitization/base64_recognizer.ts","../../src/sanitization/media_type_context.ts","../../src/sanitization/binary_content_redactor.ts","../../src/otel/redact.ts","../../src/otel/spans.ts","../../src/otel/exporter.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'])\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 ) {}\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.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 signalsBinary(): boolean {\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): T\n redact(value: unknown): unknown {\n if (this.isMultimodalEnabled()) return value\n this.visited = new WeakSet()\n return this.walk(value, MediaTypeContext.EMPTY)\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.redactString(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.placeholderFor(ctx.inferMediaType())\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 out[k] = this.walk(obj[k], new MediaTypeContext(obj, k))\n }\n return out\n }\n\n private redactString(value: string, ctx: MediaTypeContext): string {\n const minLength = ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH\n const recognition = this.recognizer.recognize(value, 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 private isMultimodalEnabled(): boolean {\n const val = process.env._INTERNAL_LLMA_MULTIMODAL || ''\n return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes'\n }\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'\n\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 // Copy rather than mutate: the span is shared across every registered processor/exporter.\n return Object.create(span, {\n attributes: { value: attributes, enumerable: true, configurable: true },\n events: { value: events, enumerable: true, configurable: true },\n })\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 value = attributes[key]\n const redacted = value === undefined ? value : redactAttributeValue(value)\n if (redacted !== value) {\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 { ReadableSpan } 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","import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base'\nimport { ExportResultCode } from '@opentelemetry/core'\n\nimport { redactSpan } from './redact'\nimport { isAISpan } from './spans'\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\n/**\n * Options for the PostHogTraceExporter. Provide `projectToken` to enable exporting. Missing or blank\n * tokens disable the exporter. 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; apiKey?: never; host?: string }\n | {\n /** @deprecated Use `projectToken` instead */\n apiKey?: string\n projectToken?: never\n host?: string\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 * Provide `projectToken` to enable exporting. Missing or blank tokens disable the exporter.\n * You can also optionally override the `host` URL.\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\n constructor(options: PostHogTraceExporterOptions = {}) {\n const token = normalizeToken(options.projectToken) || normalizeToken(options.apiKey)\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 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: ExportResultCode.SUCCESS })\n return\n }\n\n const aiSpans = spans.filter(isAISpan)\n if (aiSpans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS })\n return\n }\n super.export(aiSpans.map(redactSpan), resultCallback)\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 { isAISpan } from './spans'\n\nconst DEFAULT_OTEL_HOST = 'https://us.i.posthog.com'\n\nfunction normalizeApiKey(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 API key.\n */\n apiKey?: string\n\n /**\n * PostHog host URL. Defaults to `https://us.i.posthog.com`.\n */\n host?: string\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 * Missing or blank project API keys disable the processor.\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 * 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 sdk = new NodeSDK({\n * spanProcessors: [new PostHogSpanProcessor({ apiKey: 'phc_...' })],\n * })\n * sdk.start()\n * ```\n */\nexport class PostHogSpanProcessor implements SpanProcessor {\n private readonly inner: SpanProcessor\n\n constructor(options: PostHogSpanProcessorOptions = {}) {\n const apiKey = normalizeApiKey(options.apiKey)\n if (!apiKey) {\n console.warn('[PostHogSpanProcessor] apiKey is missing or blank; the processor will be disabled.')\n this.inner = new NoopSpanProcessor()\n return\n }\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 ${apiKey}`,\n },\n })\n this.inner = new BatchSpanProcessor(exporter)\n }\n }\n\n onStart(span: Span, parentContext: Context): void {\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 if (isAISpan(span)) {\n this.inner.onEnd(redactSpan(span))\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"],"names":["DATA_URL_PREFIX_RE","BASE64_ALPHABET_RE","Base64Recognizer","recognize","value","minLength","dataUrl","exec","kind","mediaType","length","confidencePrefix","slice","test","MIME_HINT_KEYS","STRONG_CONTEXT_KEYS","Set","STRONG_CONTEXT_TYPES","FILE_FAMILY_TYPES","KNOWN_AUDIO_FORMATS","MediaTypeContext","EMPTY","undefined","constructor","parent","key","inferMediaType","inferFromSiblingMime","inferFromSiblingFormat","inferFromParentType","inferFromKey","hint","v","fmt","format","has","toLowerCase","t","type","includes","signalsBinary","STRONG_CONTEXT_MIN_LENGTH","WEAK_CONTEXT_MIN_LENGTH","BinaryContentRedactor","visited","WeakSet","recognizer","redact","isMultimodalEnabled","walk","ctx","redactString","Uint8Array","placeholderFor","add","Array","isArray","map","item","obj","out","k","Object","keys","recognition","val","process","env","_INTERNAL_LLMA_MULTIMODAL","redactor","redactSpan","span","attributes","redactAttributes","events","redactEvents","create","enumerable","configurable","changed","redacted","redactAttributeValue","event","isStringArray","every","trimmed","trimStart","startsWith","redactJson","parsed","JSON","parse","redactedStr","stringify","AI_SPAN_PREFIXES","isAISpan","some","prefix","name","DEFAULT_OTEL_HOST","normalizeToken","trim","normalizeHost","normalizedValue","PostHogTraceExporter","OTLPTraceExporter","options","token","projectToken","apiKey","disabled","host","URL","origin","url","headers","Authorization","console","warn","export","spans","resultCallback","code","ExportResultCode","SUCCESS","aiSpans","filter","normalizeApiKey","NoopSpanProcessor","onStart","_span","_parentContext","onEnd","shutdown","Promise","resolve","forceFlush","PostHogSpanProcessor","inner","_spanProcessor","exporter","BatchSpanProcessor","parentContext"],"mappings":"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"}
@@ -394,7 +394,7 @@ function sanitizeValues(obj) {
394
394
  return jsonSafe;
395
395
  }
396
396
 
397
- var version = "7.20.13";
397
+ var version = "7.21.0";
398
398
 
399
399
  const DEFAULT_MAX_DEPTH = 3;
400
400
  const MAX_STACK_LINES = 20;
@@ -392,7 +392,7 @@ function sanitizeValues(obj) {
392
392
  return jsonSafe;
393
393
  }
394
394
 
395
- var version = "7.20.13";
395
+ var version = "7.21.0";
396
396
 
397
397
  const DEFAULT_MAX_DEPTH = 3;
398
398
  const MAX_STACK_LINES = 20;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/ai",
3
- "version": "7.20.13",
3
+ "version": "7.21.0",
4
4
  "description": "PostHog Node.js AI integrations",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,8 +26,8 @@
26
26
  "jest": "29.7.0",
27
27
  "node-fetch": "^3.3.2",
28
28
  "@posthog-tooling/rollup-utils": "1.1.1",
29
- "posthog-node": "5.36.3",
30
- "@posthog-tooling/tsconfig-base": "1.1.1"
29
+ "@posthog-tooling/tsconfig-base": "1.1.1",
30
+ "posthog-node": "5.36.4"
31
31
  },
32
32
  "keywords": [
33
33
  "posthog",
@@ -50,7 +50,7 @@
50
50
  "openai": "^6.25.0",
51
51
  "uuid": "^11.1.0",
52
52
  "zod": "^4.1.13",
53
- "@posthog/core": "1.30.9"
53
+ "@posthog/core": "1.30.10"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@opentelemetry/api": "^1.9.0",