@posthog/ai 8.10.0 → 8.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/adk/index.cjs +908 -1198
  2. package/dist/adk/index.cjs.map +1 -1
  3. package/dist/adk/index.d.ts +109 -109
  4. package/dist/adk/index.mjs +907 -1196
  5. package/dist/adk/index.mjs.map +1 -1
  6. package/dist/anthropic/index.cjs +927 -1111
  7. package/dist/anthropic/index.cjs.map +1 -1
  8. package/dist/anthropic/index.d.ts +34 -33
  9. package/dist/anthropic/index.mjs +899 -1102
  10. package/dist/anthropic/index.mjs.map +1 -1
  11. package/dist/gemini/index.cjs +863 -1108
  12. package/dist/gemini/index.cjs.map +1 -1
  13. package/dist/gemini/index.d.ts +38 -35
  14. package/dist/gemini/index.mjs +858 -1103
  15. package/dist/gemini/index.mjs.map +1 -1
  16. package/dist/index.cjs +1218 -1546
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +170 -163
  19. package/dist/index.mjs +1216 -1544
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/langchain/index.cjs +851 -1029
  22. package/dist/langchain/index.cjs.map +1 -1
  23. package/dist/langchain/index.d.ts +75 -75
  24. package/dist/langchain/index.mjs +850 -1027
  25. package/dist/langchain/index.mjs.map +1 -1
  26. package/dist/langchain/middleware/index.cjs +1016 -1225
  27. package/dist/langchain/middleware/index.cjs.map +1 -1
  28. package/dist/langchain/middleware/index.d.ts +29 -25
  29. package/dist/langchain/middleware/index.mjs +1015 -1223
  30. package/dist/langchain/middleware/index.mjs.map +1 -1
  31. package/dist/openai/index.cjs +1990 -2523
  32. package/dist/openai/index.cjs.map +1 -1
  33. package/dist/openai/index.d.ts +106 -104
  34. package/dist/openai/index.mjs +1985 -2518
  35. package/dist/openai/index.mjs.map +1 -1
  36. package/dist/openai-agents/index.cjs +745 -827
  37. package/dist/openai-agents/index.cjs.map +1 -1
  38. package/dist/openai-agents/index.d.ts +48 -47
  39. package/dist/openai-agents/index.mjs +744 -825
  40. package/dist/openai-agents/index.mjs.map +1 -1
  41. package/dist/otel/index.cjs +427 -486
  42. package/dist/otel/index.cjs.map +1 -1
  43. package/dist/otel/index.d.ts +36 -35
  44. package/dist/otel/index.mjs +426 -484
  45. package/dist/otel/index.mjs.map +1 -1
  46. package/dist/vercel/index.cjs +992 -1343
  47. package/dist/vercel/index.cjs.map +1 -1
  48. package/dist/vercel/index.d.ts +21 -16
  49. package/dist/vercel/index.mjs +991 -1341
  50. package/dist/vercel/index.mjs.map +1 -1
  51. package/package.json +15 -14
@@ -1,522 +1,464 @@
1
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
2
- import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
3
-
1
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
2
+ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
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+/_=-]+$/;
6
- class Base64Recognizer {
7
- recognize(value, minLength) {
8
- const dataUrl = DATA_URL_PREFIX_RE.exec(value);
9
- if (dataUrl) return {
10
- kind: 'data-url',
11
- mediaType: dataUrl[1]
12
- };
13
- if (value.length < minLength) return {
14
- kind: 'none'
15
- };
16
- const confidencePrefix = value.slice(0, minLength);
17
- if (BASE64_ALPHABET_RE.test(confidencePrefix)) {
18
- return {
19
- kind: 'raw'
20
- };
21
- } else {
22
- return {
23
- kind: 'none'
24
- };
25
- }
26
- }
27
- }
28
-
29
- const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
30
- 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']);
31
- const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
32
- const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
33
- const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
34
- class MediaTypeContext {
35
- static EMPTY = new MediaTypeContext(undefined, undefined);
36
- constructor(parent, key, explicitMediaType) {
37
- this.parent = parent;
38
- this.key = key;
39
- this.explicitMediaType = explicitMediaType;
40
- }
41
- inferMediaType() {
42
- return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
43
- }
44
- inferFromSiblingMime() {
45
- if (this.explicitMediaType) return this.explicitMediaType;
46
- if (!this.parent) return undefined;
47
- for (const hint of MIME_HINT_KEYS) {
48
- const v = this.parent[hint];
49
- if (typeof v === 'string') return v;
50
- }
51
- return undefined;
52
- }
53
- inferFromSiblingFormat() {
54
- if (!this.parent) return undefined;
55
- const fmt = this.parent.format;
56
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
57
- return `audio/${fmt.toLowerCase()}`;
58
- }
59
- return undefined;
60
- }
61
- inferFromParentType() {
62
- if (!this.parent) return undefined;
63
- const t = this.parent.type;
64
- if (typeof t !== 'string') return undefined;
65
- if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
66
- if (t === 'audio' || t === 'input_audio') return 'audio';
67
- if (t === 'video' || t === 'video_url') return 'video';
68
- if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
69
- return undefined;
70
- }
71
- inferFromKey() {
72
- if (!this.key) return undefined;
73
- const key = this.key.toLowerCase();
74
- if (key.includes('audio')) return 'audio';
75
- if (key.includes('video')) return 'video';
76
- if (key.includes('image')) return 'image';
77
- if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
78
- return undefined;
79
- }
80
- hasExplicitBinaryMediaType() {
81
- if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
82
- const mediaType = this.inferFromSiblingMime();
83
- return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
84
- }
85
- signalsBinary() {
86
- if (this.explicitMediaType) return true;
87
- if (this.parent) {
88
- for (const hint of MIME_HINT_KEYS) {
89
- if (typeof this.parent[hint] === 'string') return true;
90
- }
91
- const fmt = this.parent.format;
92
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
93
- const t = this.parent.type;
94
- if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
95
- }
96
- if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
97
- return false;
98
- }
99
- }
100
-
6
+ var Base64Recognizer = class {
7
+ recognize(value, minLength) {
8
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
9
+ if (dataUrl) return {
10
+ kind: "data-url",
11
+ mediaType: dataUrl[1]
12
+ };
13
+ if (value.length < minLength) return { kind: "none" };
14
+ const confidencePrefix = value.slice(0, minLength);
15
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
16
+ else return { kind: "none" };
17
+ }
18
+ };
19
+ //#endregion
20
+ //#region src/sanitization/media_type_context.ts
21
+ const MIME_HINT_KEYS = [
22
+ "mediaType",
23
+ "media_type",
24
+ "mimeType",
25
+ "mime_type"
26
+ ];
27
+ const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
28
+ "data",
29
+ "file_data",
30
+ "fileData",
31
+ "image_url",
32
+ "imageUrl",
33
+ "video_url",
34
+ "videoUrl",
35
+ "audio",
36
+ "audio_data",
37
+ "audioData",
38
+ "inline_data",
39
+ "inlineData",
40
+ "source",
41
+ "result"
42
+ ]);
43
+ const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
44
+ "image",
45
+ "image_url",
46
+ "input_image",
47
+ "audio",
48
+ "input_audio",
49
+ "video",
50
+ "video_url",
51
+ "file",
52
+ "input_file",
53
+ "document",
54
+ "media",
55
+ "file-data"
56
+ ]);
57
+ const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
58
+ "file",
59
+ "input_file",
60
+ "document",
61
+ "media",
62
+ "file-data"
63
+ ]);
64
+ const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
65
+ "wav",
66
+ "mp3",
67
+ "ogg",
68
+ "flac",
69
+ "m4a",
70
+ "aac",
71
+ "webm"
72
+ ]);
73
+ var MediaTypeContext = class MediaTypeContext {
74
+ static {
75
+ this.EMPTY = new MediaTypeContext(void 0, void 0);
76
+ }
77
+ constructor(parent, key, explicitMediaType) {
78
+ this.parent = parent;
79
+ this.key = key;
80
+ this.explicitMediaType = explicitMediaType;
81
+ }
82
+ inferMediaType() {
83
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
84
+ }
85
+ inferFromSiblingMime() {
86
+ if (this.explicitMediaType) return this.explicitMediaType;
87
+ if (!this.parent) return void 0;
88
+ for (const hint of MIME_HINT_KEYS) {
89
+ const v = this.parent[hint];
90
+ if (typeof v === "string") return v;
91
+ }
92
+ }
93
+ inferFromSiblingFormat() {
94
+ if (!this.parent) return void 0;
95
+ const fmt = this.parent.format;
96
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
97
+ }
98
+ inferFromParentType() {
99
+ if (!this.parent) return void 0;
100
+ const t = this.parent.type;
101
+ if (typeof t !== "string") return void 0;
102
+ if (t === "image" || t === "image_url" || t === "input_image") return "image";
103
+ if (t === "audio" || t === "input_audio") return "audio";
104
+ if (t === "video" || t === "video_url") return "video";
105
+ if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
106
+ }
107
+ inferFromKey() {
108
+ if (!this.key) return void 0;
109
+ const key = this.key.toLowerCase();
110
+ if (key.includes("audio")) return "audio";
111
+ if (key.includes("video")) return "video";
112
+ if (key.includes("image")) return "image";
113
+ if (key.includes("file") || key.includes("document")) return "application/octet-stream";
114
+ }
115
+ hasExplicitBinaryMediaType() {
116
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
117
+ const mediaType = this.inferFromSiblingMime();
118
+ return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
119
+ }
120
+ signalsBinary() {
121
+ if (this.explicitMediaType) return true;
122
+ if (this.parent) {
123
+ for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
124
+ const fmt = this.parent.format;
125
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
126
+ const t = this.parent.type;
127
+ if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
128
+ }
129
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
130
+ return false;
131
+ }
132
+ };
133
+ //#endregion
134
+ //#region src/sanitization/binary_content_redactor.ts
101
135
  const STRONG_CONTEXT_MIN_LENGTH = 64;
102
136
  const WEAK_CONTEXT_MIN_LENGTH = 1024;
103
- class BinaryContentRedactor {
104
- visited = new WeakSet();
105
- constructor(recognizer = new Base64Recognizer()) {
106
- this.recognizer = recognizer;
107
- }
108
- redact(value, mediaType) {
109
- this.visited = new WeakSet();
110
- return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
111
- }
112
- walk(value, ctx) {
113
- if (value === null || value === undefined) return value;
114
- if (typeof value === 'string') return this.redactString(value, ctx);
115
- if (typeof value !== 'object') return value;
116
-
117
- // Buffer extends Uint8Array, so this branch catches both.
118
- if (typeof Uint8Array !== 'undefined' && value instanceof Uint8Array) {
119
- return this.placeholderFor(ctx.inferMediaType());
120
- }
121
- if (this.visited.has(value)) return null;
122
- this.visited.add(value);
123
- if (Array.isArray(value)) {
124
- return value.map(item => this.walk(item, ctx));
125
- }
126
- const obj = value;
127
- const out = {};
128
- for (const k of Object.keys(obj)) {
129
- out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
130
- }
131
- return out;
132
- }
133
- redactString(value, ctx) {
134
- const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
135
- const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, '') : value;
136
- const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
137
- const recognition = this.recognizer.recognize(recognitionValue, minLength);
138
- switch (recognition.kind) {
139
- case 'data-url':
140
- return this.placeholderFor(recognition.mediaType);
141
- case 'raw':
142
- return this.placeholderFor(ctx.inferMediaType());
143
- case 'none':
144
- return value;
145
- }
146
- }
147
- placeholderFor(mediaType) {
148
- if (!mediaType) return '[base64 redacted]';
149
- if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
150
- return `[base64 ${mediaType} redacted]`;
151
- }
152
- }
153
-
154
- // Deliberately always redacts here — the OTLP export path has no per-client passthrough gate.
137
+ var BinaryContentRedactor = class {
138
+ constructor(recognizer = new Base64Recognizer()) {
139
+ this.recognizer = recognizer;
140
+ this.visited = /* @__PURE__ */ new WeakSet();
141
+ }
142
+ redact(value, mediaType) {
143
+ this.visited = /* @__PURE__ */ new WeakSet();
144
+ return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
145
+ }
146
+ walk(value, ctx) {
147
+ if (value === null || value === void 0) return value;
148
+ if (typeof value === "string") return this.redactString(value, ctx);
149
+ if (typeof value !== "object") return value;
150
+ if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
151
+ if (this.visited.has(value)) return null;
152
+ this.visited.add(value);
153
+ if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
154
+ const obj = value;
155
+ const out = {};
156
+ for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
157
+ return out;
158
+ }
159
+ redactString(value, ctx) {
160
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
161
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
162
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
163
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
164
+ switch (recognition.kind) {
165
+ case "data-url": return this.placeholderFor(recognition.mediaType);
166
+ case "raw": return this.placeholderFor(ctx.inferMediaType());
167
+ case "none": return value;
168
+ }
169
+ }
170
+ placeholderFor(mediaType) {
171
+ if (!mediaType) return "[base64 redacted]";
172
+ if (mediaType === "application/octet-stream") return "[base64 file redacted]";
173
+ return `[base64 ${mediaType} redacted]`;
174
+ }
175
+ };
176
+ //#endregion
177
+ //#region src/otel/redact.ts
155
178
  const redactor = new BinaryContentRedactor();
156
179
  function redactSpan(span) {
157
- const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes;
158
- const events = span.events ? redactEvents(span.events) : span.events;
159
- if (attributes === span.attributes && events === span.events) {
160
- return span;
161
- }
162
- // Copy rather than mutate: the span is shared across every registered processor/exporter.
163
- return Object.create(span, {
164
- attributes: {
165
- value: attributes,
166
- enumerable: true,
167
- configurable: true
168
- },
169
- events: {
170
- value: events,
171
- enumerable: true,
172
- configurable: true
173
- }
174
- });
180
+ const attributes = span.attributes ? redactAttributes(span.attributes) : span.attributes;
181
+ const events = span.events ? redactEvents(span.events) : span.events;
182
+ if (attributes === span.attributes && events === span.events) return span;
183
+ return Object.create(span, {
184
+ attributes: {
185
+ value: attributes,
186
+ enumerable: true,
187
+ configurable: true
188
+ },
189
+ events: {
190
+ value: events,
191
+ enumerable: true,
192
+ configurable: true
193
+ }
194
+ });
175
195
  }
176
196
  function redactAttributes(attributes) {
177
- let changed = false;
178
- const out = {};
179
- for (const key of Object.keys(attributes)) {
180
- const value = attributes[key];
181
- const redacted = value === undefined ? value : redactAttributeValue(value);
182
- if (redacted !== value) {
183
- changed = true;
184
- }
185
- out[key] = redacted;
186
- }
187
- return changed ? out : attributes;
197
+ let changed = false;
198
+ const out = {};
199
+ for (const key of Object.keys(attributes)) {
200
+ const value = attributes[key];
201
+ const redacted = value === void 0 ? value : redactAttributeValue(value);
202
+ if (redacted !== value) changed = true;
203
+ out[key] = redacted;
204
+ }
205
+ return changed ? out : attributes;
188
206
  }
189
207
  function redactEvents(events) {
190
- let changed = false;
191
- const out = events.map(event => {
192
- if (!event.attributes) {
193
- return event;
194
- }
195
- const attributes = redactAttributes(event.attributes);
196
- if (attributes === event.attributes) {
197
- return event;
198
- }
199
- changed = true;
200
- return {
201
- ...event,
202
- attributes
203
- };
204
- });
205
- return changed ? out : events;
208
+ let changed = false;
209
+ const out = events.map((event) => {
210
+ if (!event.attributes) return event;
211
+ const attributes = redactAttributes(event.attributes);
212
+ if (attributes === event.attributes) return event;
213
+ changed = true;
214
+ return {
215
+ ...event,
216
+ attributes
217
+ };
218
+ });
219
+ return changed ? out : events;
206
220
  }
207
221
  function redactAttributeValue(value) {
208
- if (typeof value === 'string') {
209
- return redactString(value);
210
- }
211
- if (isStringArray(value)) {
212
- let changed = false;
213
- const out = value.map(item => {
214
- if (typeof item !== 'string') {
215
- return item;
216
- }
217
- const redacted = redactString(item);
218
- if (redacted !== item) {
219
- changed = true;
220
- }
221
- return redacted;
222
- });
223
- return changed ? out : value;
224
- }
225
- return value;
222
+ if (typeof value === "string") return redactString(value);
223
+ if (isStringArray(value)) {
224
+ let changed = false;
225
+ const out = value.map((item) => {
226
+ if (typeof item !== "string") return item;
227
+ const redacted = redactString(item);
228
+ if (redacted !== item) changed = true;
229
+ return redacted;
230
+ });
231
+ return changed ? out : value;
232
+ }
233
+ return value;
226
234
  }
227
235
  function isStringArray(value) {
228
- return Array.isArray(value) && value.every(item => typeof item !== 'number' && typeof item !== 'boolean');
236
+ return Array.isArray(value) && value.every((item) => typeof item !== "number" && typeof item !== "boolean");
229
237
  }
230
238
  function redactString(value) {
231
- const trimmed = value.trimStart();
232
- if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
233
- const redacted = redactJson(value);
234
- if (redacted !== undefined) {
235
- return redacted;
236
- }
237
- }
238
- return redactor.redact(value);
239
+ const trimmed = value.trimStart();
240
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
241
+ const redacted = redactJson(value);
242
+ if (redacted !== void 0) return redacted;
243
+ }
244
+ return redactor.redact(value);
239
245
  }
240
246
  function redactJson(value) {
241
- let parsed;
242
- try {
243
- parsed = JSON.parse(value);
244
- } catch {
245
- return undefined;
246
- }
247
- if (parsed === null || typeof parsed !== 'object') {
248
- return undefined;
249
- }
250
- const redactedStr = JSON.stringify(redactor.redact(parsed));
251
- // Preserve the original string (and its formatting) when nothing was redacted.
252
- return redactedStr === JSON.stringify(parsed) ? value : redactedStr;
247
+ let parsed;
248
+ try {
249
+ parsed = JSON.parse(value);
250
+ } catch {
251
+ return;
252
+ }
253
+ if (parsed === null || typeof parsed !== "object") return;
254
+ const redactedStr = JSON.stringify(redactor.redact(parsed));
255
+ return redactedStr === JSON.stringify(parsed) ? value : redactedStr;
253
256
  }
254
-
255
- const AI_SPAN_PREFIXES = ['gen_ai.', 'llm.', 'ai.', 'traceloop.'];
256
-
257
+ //#endregion
258
+ //#region src/otel/spans.ts
259
+ const AI_SPAN_PREFIXES = [
260
+ "gen_ai.",
261
+ "llm.",
262
+ "ai.",
263
+ "traceloop."
264
+ ];
257
265
  /**
258
- * Returns `true` when the span is AI-related — its name or any attribute
259
- * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.
260
- */
266
+ * Returns `true` when the span is AI-related — its name or any attribute
267
+ * key starts with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`.
268
+ */
261
269
  function isAISpan(span) {
262
- if (AI_SPAN_PREFIXES.some(prefix => span.name.startsWith(prefix))) {
263
- return true;
264
- }
265
- const attributes = span.attributes;
266
- if (attributes) {
267
- return Object.keys(attributes).some(key => AI_SPAN_PREFIXES.some(prefix => key.startsWith(prefix)));
268
- }
269
- return false;
270
+ if (AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))) return true;
271
+ const attributes = span.attributes;
272
+ if (attributes) return Object.keys(attributes).some((key) => AI_SPAN_PREFIXES.some((prefix) => key.startsWith(prefix)));
273
+ return false;
270
274
  }
271
-
272
- // Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway
273
- // emits its own $ai_generation, so each call would be captured (and, for billable
274
- // products, billed) twice. We only warn — the wrapper's event carries data the
275
- // gateway never sees (groups, custom properties, trace hierarchy).
276
-
277
- // Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the
278
- // main repo). gateway.us.posthog.com is live today; the rest are listed ahead of
279
- // any traffic moving to them.
280
- const POSTHOG_AI_GATEWAY_HOSTS = ['gateway.posthog.com', 'gateway.us.posthog.com', 'gateway.eu.posthog.com', 'ai-gateway.us.posthog.com', 'ai-gateway.eu.posthog.com'];
281
-
282
- // Swap for the dedicated AI Gateway page once it ships.
283
- const GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability';
284
- const extractHost = baseURL => {
285
- try {
286
- // Tolerate bare hosts that omit a scheme, e.g. "gateway.us.posthog.com/v1".
287
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
288
- return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
289
- } catch {
290
- return undefined;
291
- }
275
+ //#endregion
276
+ //#region src/gatewayWarning.ts
277
+ const POSTHOG_AI_GATEWAY_HOSTS = [
278
+ "gateway.posthog.com",
279
+ "gateway.us.posthog.com",
280
+ "gateway.eu.posthog.com",
281
+ "ai-gateway.us.posthog.com",
282
+ "ai-gateway.eu.posthog.com"
283
+ ];
284
+ const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
285
+ const extractHost = (baseURL) => {
286
+ try {
287
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
288
+ return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
289
+ } catch {
290
+ return;
291
+ }
292
292
  };
293
- const isPostHogAiGatewayUrl = baseURL => {
294
- if (!baseURL) {
295
- return false;
296
- }
297
- const host = extractHost(baseURL);
298
- return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
293
+ const isPostHogAiGatewayUrl = (baseURL) => {
294
+ if (!baseURL) return false;
295
+ const host = extractHost(baseURL);
296
+ return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
299
297
  };
300
-
301
- // Warns on every gateway call by design: the misconfiguration is impossible to
302
- // miss that way, and a doubled bill is worse than noisy logs.
303
- const warnIfPostHogAiGateway = baseURL => {
304
- if (!isPostHogAiGatewayUrl(baseURL)) {
305
- return;
306
- }
307
- console.warn('[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' + 'Both capture $ai_generation, so every call is double-counted and double-billed. ' + `Use one or the other — see ${GATEWAY_DOCS_URL}.`);
298
+ const warnIfPostHogAiGateway = (baseURL) => {
299
+ if (!isPostHogAiGatewayUrl(baseURL)) return;
300
+ console.warn(`[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. Both capture \$ai_generation, so every call is double-counted and double-billed. Use one or the other see ${GATEWAY_DOCS_URL}.`);
308
301
  };
309
-
310
- // OTel spans don't pass through captureAiGeneration, so detect the gateway from
311
- // the span's host/URL attributes instead. These follow the GenAI / HTTP semantic
312
- // conventions: `server.address` is a bare host, `url.full` a full URL, both of
313
- // which isPostHogAiGatewayUrl accepts.
314
- const OTEL_GATEWAY_URL_ATTRIBUTES = ['server.address', 'url.full'];
315
- const warnIfPostHogAiGatewayOtelAttributes = attributes => {
316
- if (!attributes) {
317
- return;
318
- }
319
- for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {
320
- const value = attributes[key];
321
- if (typeof value === 'string' && isPostHogAiGatewayUrl(value)) {
322
- warnIfPostHogAiGateway(value);
323
- return;
324
- }
325
- }
302
+ const OTEL_GATEWAY_URL_ATTRIBUTES = ["server.address", "url.full"];
303
+ const warnIfPostHogAiGatewayOtelAttributes = (attributes) => {
304
+ if (!attributes) return;
305
+ for (const key of OTEL_GATEWAY_URL_ATTRIBUTES) {
306
+ const value = attributes[key];
307
+ if (typeof value === "string" && isPostHogAiGatewayUrl(value)) {
308
+ warnIfPostHogAiGateway(value);
309
+ return;
310
+ }
311
+ }
326
312
  };
327
-
328
- const DEFAULT_OTEL_HOST$1 = 'https://us.i.posthog.com';
329
- // OpenTelemetry's ExportResultCode.SUCCESS is 0. Keep this local so loading the
330
- // published subpath does not require the exporter package's transitive dependencies.
313
+ //#endregion
314
+ //#region src/otel/exporter.ts
315
+ const DEFAULT_OTEL_HOST$1 = "https://us.i.posthog.com";
331
316
  const EXPORT_SUCCESS = 0;
332
317
  function normalizeToken$1(value) {
333
- return typeof value === 'string' ? value.trim() : '';
318
+ return typeof value === "string" ? value.trim() : "";
334
319
  }
335
320
  function normalizeHost$1(value) {
336
- const normalizedValue = typeof value === 'string' ? value.trim() : '';
337
- return normalizedValue || DEFAULT_OTEL_HOST$1;
321
+ return (typeof value === "string" ? value.trim() : "") || DEFAULT_OTEL_HOST$1;
338
322
  }
339
-
340
323
  /**
341
- * Options for the PostHogTraceExporter. `projectToken` is required; a blank token disables the
342
- * exporter as a defensive no-op. You can also optionally override the `host` URL. `host` defaults to `https://us.i.posthog.com`.
343
- *
344
- * @example
345
- * ```ts
346
- * import { PostHogTraceExporter } from '@posthog/ai/otel'
347
- *
348
- * new PostHogTraceExporter({ projectToken: 'phc_...' })
349
- * ```
350
- *
351
- * @example
352
- * ```ts
353
- * import { PostHogTraceExporter } from '@posthog/ai/otel'
354
- *
355
- * new PostHogTraceExporter({ projectToken: 'phc_...', host: 'https://eu.i.posthog.com' })
356
- * ```
357
- */
358
-
359
- /**
360
- * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP
361
- * ingestion endpoint. PostHog converts `gen_ai.*` spans into
362
- * `$ai_generation` events server-side.
363
- *
364
- * Only AI-related spans (those whose name or attribute keys start with
365
- * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other
366
- * spans are silently dropped.
367
- *
368
- * Use this when the API you're integrating with only accepts a
369
- * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to
370
- * plug PostHog into an existing processor chain. Otherwise prefer
371
- * {@link PostHogSpanProcessor}, which is self-contained.
372
- *
373
- * `projectToken` is required; a blank token disables the exporter as a defensive no-op.
374
- * You can also optionally override the `host` URL.
375
- *
376
- * @example
377
- * ```ts
378
- * import { PostHogTraceExporter } from '@posthog/ai/otel'
379
- * import { registerOTel } from '@vercel/otel'
380
- *
381
- * registerOTel({
382
- * serviceName: 'my-app',
383
- * traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),
384
- * })
385
- * ```
386
- */
387
- class PostHogTraceExporter extends OTLPTraceExporter {
388
- constructor(options) {
389
- const token = normalizeToken$1(options.projectToken);
390
- const disabled = !token;
391
- const host = token ? new URL(normalizeHost$1(options.host)).origin : DEFAULT_OTEL_HOST$1;
392
- super({
393
- url: `${host}/i/v0/ai/otel`,
394
- headers: token ? {
395
- Authorization: `Bearer ${token}`
396
- } : {}
397
- });
398
- this.disabled = disabled;
399
- if (this.disabled) {
400
- console.warn('[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.');
401
- }
402
- }
403
- export(spans, resultCallback) {
404
- if (this.disabled) {
405
- // Intentionally report success: missing or blank tokens disable exporting as a compatibility no-op.
406
- // Reporting failure would make OpenTelemetry treat every span as an export error.
407
- resultCallback({
408
- code: EXPORT_SUCCESS
409
- });
410
- return;
411
- }
412
- const aiSpans = spans.filter(isAISpan);
413
- if (aiSpans.length === 0) {
414
- resultCallback({
415
- code: EXPORT_SUCCESS
416
- });
417
- return;
418
- }
419
- for (const span of aiSpans) {
420
- warnIfPostHogAiGatewayOtelAttributes(span.attributes);
421
- }
422
- super.export(aiSpans.map(redactSpan), resultCallback);
423
- }
424
- }
425
-
426
- const DEFAULT_OTEL_HOST = 'https://us.i.posthog.com';
324
+ * An OpenTelemetry `TraceExporter` that sends AI traces to PostHog's OTLP
325
+ * ingestion endpoint. PostHog converts `gen_ai.*` spans into
326
+ * `$ai_generation` events server-side.
327
+ *
328
+ * Only AI-related spans (those whose name or attribute keys start with
329
+ * `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported; all other
330
+ * spans are silently dropped.
331
+ *
332
+ * Use this when the API you're integrating with only accepts a
333
+ * `TraceExporter` (e.g. Vercel's `registerOTel`) or when you need to
334
+ * plug PostHog into an existing processor chain. Otherwise prefer
335
+ * {@link PostHogSpanProcessor}, which is self-contained.
336
+ *
337
+ * `projectToken` is required; a blank token disables the exporter as a defensive no-op.
338
+ * You can also optionally override the `host` URL.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { PostHogTraceExporter } from '@posthog/ai/otel'
343
+ * import { registerOTel } from '@vercel/otel'
344
+ *
345
+ * registerOTel({
346
+ * serviceName: 'my-app',
347
+ * traceExporter: new PostHogTraceExporter({ projectToken: 'phc_...' }),
348
+ * })
349
+ * ```
350
+ */
351
+ var PostHogTraceExporter = class extends OTLPTraceExporter {
352
+ constructor(options) {
353
+ const token = normalizeToken$1(options.projectToken);
354
+ const disabled = !token;
355
+ const host = token ? new URL(normalizeHost$1(options.host)).origin : DEFAULT_OTEL_HOST$1;
356
+ super({
357
+ url: `${host}/i/v0/ai/otel`,
358
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
359
+ });
360
+ this.disabled = disabled;
361
+ if (this.disabled) console.warn("[PostHogTraceExporter] projectToken is missing or blank; the exporter will be disabled.");
362
+ }
363
+ export(spans, resultCallback) {
364
+ if (this.disabled) {
365
+ resultCallback({ code: EXPORT_SUCCESS });
366
+ return;
367
+ }
368
+ const aiSpans = spans.filter(isAISpan);
369
+ if (aiSpans.length === 0) {
370
+ resultCallback({ code: EXPORT_SUCCESS });
371
+ return;
372
+ }
373
+ for (const span of aiSpans) warnIfPostHogAiGatewayOtelAttributes(span.attributes);
374
+ super.export(aiSpans.map(redactSpan), resultCallback);
375
+ }
376
+ };
377
+ //#endregion
378
+ //#region src/otel/processor.ts
379
+ const DEFAULT_OTEL_HOST = "https://us.i.posthog.com";
427
380
  function normalizeToken(value) {
428
- return typeof value === 'string' ? value.trim() : '';
381
+ return typeof value === "string" ? value.trim() : "";
429
382
  }
430
383
  function normalizeHost(value) {
431
- const normalizedValue = typeof value === 'string' ? value.trim() : '';
432
- return normalizedValue || DEFAULT_OTEL_HOST;
433
- }
434
- class NoopSpanProcessor {
435
- onStart(_span, _parentContext) {
436
- return;
437
- }
438
- onEnd(_span) {
439
- return;
440
- }
441
- shutdown() {
442
- return Promise.resolve();
443
- }
444
- forceFlush() {
445
- return Promise.resolve();
446
- }
384
+ return (typeof value === "string" ? value.trim() : "") || DEFAULT_OTEL_HOST;
447
385
  }
448
-
386
+ var NoopSpanProcessor = class {
387
+ onStart(_span, _parentContext) {}
388
+ onEnd(_span) {}
389
+ shutdown() {
390
+ return Promise.resolve();
391
+ }
392
+ forceFlush() {
393
+ return Promise.resolve();
394
+ }
395
+ };
449
396
  /**
450
- * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
451
- *
452
- * `projectToken` is required; a blank token disables the processor as a defensive no-op.
453
- *
454
- * Internally batches spans and exports them to PostHog's OTLP ingestion
455
- * endpoint. Only AI-related spans (those whose name or attribute keys
456
- * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;
457
- * all other spans are silently dropped.
458
- *
459
- * Request-scoped and serverless runtimes should keep a reference to this
460
- * processor and await {@link PostHogSpanProcessor.forceFlush} before the request
461
- * lifecycle ends. This waits for queued exports without sending one request per span.
462
- *
463
- * This is the recommended integration point when your setup accepts a
464
- * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for
465
- * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.
466
- *
467
- * @example
468
- * ```ts
469
- * import { PostHogSpanProcessor } from '@posthog/ai/otel'
470
- * import { NodeSDK } from '@opentelemetry/sdk-node'
471
- *
472
- * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })
473
- * const sdk = new NodeSDK({ spanProcessors: [processor] })
474
- * sdk.start()
475
- *
476
- * // In request-scoped runtimes, wait for queued exports before returning.
477
- * await processor.forceFlush()
478
- * ```
479
- */
480
- class PostHogSpanProcessor {
481
- constructor(options) {
482
- const token = normalizeToken(options.projectToken);
483
- if (!token) {
484
- console.warn('[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.');
485
- this.inner = new NoopSpanProcessor();
486
- return;
487
- }
488
- if (options._spanProcessor) {
489
- this.inner = options._spanProcessor;
490
- } else {
491
- const host = new URL(normalizeHost(options.host)).origin;
492
- const exporter = new OTLPTraceExporter({
493
- url: `${host}/i/v0/ai/otel`,
494
- headers: {
495
- Authorization: `Bearer ${token}`
496
- }
497
- });
498
- this.inner = new BatchSpanProcessor(exporter);
499
- }
500
- }
501
- onStart(span, parentContext) {
502
- // Forwarded unconditionally — filtering happens in onEnd. We can't filter
503
- // here because the span hasn't finished yet and may not have AI attributes
504
- // set. BatchSpanProcessor.onStart is a no-op so this is safe.
505
- this.inner.onStart(span, parentContext);
506
- }
507
- onEnd(span) {
508
- if (isAISpan(span)) {
509
- warnIfPostHogAiGatewayOtelAttributes(span.attributes);
510
- this.inner.onEnd(redactSpan(span));
511
- }
512
- }
513
- shutdown() {
514
- return this.inner.shutdown();
515
- }
516
- forceFlush() {
517
- return this.inner.forceFlush();
518
- }
519
- }
520
-
397
+ * An OpenTelemetry `SpanProcessor` that sends AI traces to PostHog.
398
+ *
399
+ * `projectToken` is required; a blank token disables the processor as a defensive no-op.
400
+ *
401
+ * Internally batches spans and exports them to PostHog's OTLP ingestion
402
+ * endpoint. Only AI-related spans (those whose name or attribute keys
403
+ * start with `gen_ai.`, `llm.`, `ai.`, or `traceloop.`) are exported;
404
+ * all other spans are silently dropped.
405
+ *
406
+ * Request-scoped and serverless runtimes should keep a reference to this
407
+ * processor and await {@link PostHogSpanProcessor.forceFlush} before the request
408
+ * lifecycle ends. This waits for queued exports without sending one request per span.
409
+ *
410
+ * This is the recommended integration point when your setup accepts a
411
+ * `SpanProcessor`. If you need a `TraceExporter` instead (e.g. for
412
+ * Vercel's `registerOTel`), use {@link PostHogTraceExporter}.
413
+ *
414
+ * @example
415
+ * ```ts
416
+ * import { PostHogSpanProcessor } from '@posthog/ai/otel'
417
+ * import { NodeSDK } from '@opentelemetry/sdk-node'
418
+ *
419
+ * const processor = new PostHogSpanProcessor({ projectToken: 'phc_...' })
420
+ * const sdk = new NodeSDK({ spanProcessors: [processor] })
421
+ * sdk.start()
422
+ *
423
+ * // In request-scoped runtimes, wait for queued exports before returning.
424
+ * await processor.forceFlush()
425
+ * ```
426
+ */
427
+ var PostHogSpanProcessor = class {
428
+ constructor(options) {
429
+ const token = normalizeToken(options.projectToken);
430
+ if (!token) {
431
+ console.warn("[PostHogSpanProcessor] projectToken is missing or blank; the processor will be disabled.");
432
+ this.inner = new NoopSpanProcessor();
433
+ return;
434
+ }
435
+ if (options._spanProcessor) this.inner = options._spanProcessor;
436
+ else {
437
+ const host = new URL(normalizeHost(options.host)).origin;
438
+ const exporter = new OTLPTraceExporter({
439
+ url: `${host}/i/v0/ai/otel`,
440
+ headers: { Authorization: `Bearer ${token}` }
441
+ });
442
+ this.inner = new BatchSpanProcessor(exporter);
443
+ }
444
+ }
445
+ onStart(span, parentContext) {
446
+ this.inner.onStart(span, parentContext);
447
+ }
448
+ onEnd(span) {
449
+ if (isAISpan(span)) {
450
+ warnIfPostHogAiGatewayOtelAttributes(span.attributes);
451
+ this.inner.onEnd(redactSpan(span));
452
+ }
453
+ }
454
+ shutdown() {
455
+ return this.inner.shutdown();
456
+ }
457
+ forceFlush() {
458
+ return this.inner.forceFlush();
459
+ }
460
+ };
461
+ //#endregion
521
462
  export { PostHogSpanProcessor, PostHogTraceExporter };
522
- //# sourceMappingURL=index.mjs.map
463
+
464
+ //# sourceMappingURL=index.mjs.map