@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,862 +1,781 @@
1
- import 'uuid';
2
-
3
- /** @internal */
4
-
5
- /** @internal */
6
-
1
+ import "uuid";
2
+ //#region src/captureAiEvent.ts
7
3
  /** @internal */
8
4
  function isFullAiCaptureEnabled(client) {
9
- return client?.enableFullAiCapture === true;
5
+ return client?.enableFullAiCapture === true;
10
6
  }
11
-
12
7
  /** @internal */
13
8
  function captureAiEvent(client, event) {
14
- if (isFullAiCaptureEnabled(client) && typeof client.captureAi === 'function') {
15
- client.captureAi(event);
16
- return;
17
- }
18
- client.capture(event);
9
+ if (isFullAiCaptureEnabled(client) && typeof client.captureAi === "function") {
10
+ client.captureAi(event);
11
+ return;
12
+ }
13
+ client.capture(event);
19
14
  }
20
-
21
- const MIME_HINT_KEYS = ['mediaType', 'media_type', 'mimeType', 'mime_type'];
22
- 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']);
23
- const STRONG_CONTEXT_TYPES = new Set(['image', 'image_url', 'input_image', 'audio', 'input_audio', 'video', 'video_url', 'file', 'input_file', 'document', 'media', 'file-data']);
24
- const FILE_FAMILY_TYPES = new Set(['file', 'input_file', 'document', 'media', 'file-data']);
25
- const KNOWN_AUDIO_FORMATS = new Set(['wav', 'mp3', 'ogg', 'flac', 'm4a', 'aac', 'webm']);
26
- class MediaTypeContext {
27
- static EMPTY = new MediaTypeContext(undefined, undefined);
28
- constructor(parent, key, explicitMediaType) {
29
- this.parent = parent;
30
- this.key = key;
31
- this.explicitMediaType = explicitMediaType;
32
- }
33
- inferMediaType() {
34
- return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
35
- }
36
- inferFromSiblingMime() {
37
- if (this.explicitMediaType) return this.explicitMediaType;
38
- if (!this.parent) return undefined;
39
- for (const hint of MIME_HINT_KEYS) {
40
- const v = this.parent[hint];
41
- if (typeof v === 'string') return v;
42
- }
43
- return undefined;
44
- }
45
- inferFromSiblingFormat() {
46
- if (!this.parent) return undefined;
47
- const fmt = this.parent.format;
48
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) {
49
- return `audio/${fmt.toLowerCase()}`;
50
- }
51
- return undefined;
52
- }
53
- inferFromParentType() {
54
- if (!this.parent) return undefined;
55
- const t = this.parent.type;
56
- if (typeof t !== 'string') return undefined;
57
- if (t === 'image' || t === 'image_url' || t === 'input_image') return 'image';
58
- if (t === 'audio' || t === 'input_audio') return 'audio';
59
- if (t === 'video' || t === 'video_url') return 'video';
60
- if (FILE_FAMILY_TYPES.has(t)) return 'application/octet-stream';
61
- return undefined;
62
- }
63
- inferFromKey() {
64
- if (!this.key) return undefined;
65
- const key = this.key.toLowerCase();
66
- if (key.includes('audio')) return 'audio';
67
- if (key.includes('video')) return 'video';
68
- if (key.includes('image')) return 'image';
69
- if (key.includes('file') || key.includes('document')) return 'application/octet-stream';
70
- return undefined;
71
- }
72
- hasExplicitBinaryMediaType() {
73
- if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
74
- const mediaType = this.inferFromSiblingMime();
75
- return mediaType !== undefined && !mediaType.toLowerCase().startsWith('text/');
76
- }
77
- signalsBinary() {
78
- if (this.explicitMediaType) return true;
79
- if (this.parent) {
80
- for (const hint of MIME_HINT_KEYS) {
81
- if (typeof this.parent[hint] === 'string') return true;
82
- }
83
- const fmt = this.parent.format;
84
- if (typeof fmt === 'string' && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
85
- const t = this.parent.type;
86
- if (typeof t === 'string' && STRONG_CONTEXT_TYPES.has(t)) return true;
87
- }
88
- if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
89
- return false;
90
- }
91
- }
92
-
93
- // limit large outputs by truncating to 200kb (approx 200k bytes)
94
- const MAX_OUTPUT_SIZE = 200000;
95
- const STRING_FORMAT = 'utf8';
96
-
97
- // Reused across calls to avoid per-invocation allocation; truncate() runs
98
- // hundreds of times for prompts with many parts.
15
+ //#endregion
16
+ //#region src/sanitization/base64_recognizer.ts
17
+ const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
18
+ const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
19
+ var Base64Recognizer = class {
20
+ recognize(value, minLength) {
21
+ const dataUrl = DATA_URL_PREFIX_RE.exec(value);
22
+ if (dataUrl) return {
23
+ kind: "data-url",
24
+ mediaType: dataUrl[1]
25
+ };
26
+ if (value.length < minLength) return { kind: "none" };
27
+ const confidencePrefix = value.slice(0, minLength);
28
+ if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
29
+ else return { kind: "none" };
30
+ }
31
+ };
32
+ //#endregion
33
+ //#region src/sanitization/media_type_context.ts
34
+ const MIME_HINT_KEYS = [
35
+ "mediaType",
36
+ "media_type",
37
+ "mimeType",
38
+ "mime_type"
39
+ ];
40
+ const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
41
+ "data",
42
+ "file_data",
43
+ "fileData",
44
+ "image_url",
45
+ "imageUrl",
46
+ "video_url",
47
+ "videoUrl",
48
+ "audio",
49
+ "audio_data",
50
+ "audioData",
51
+ "inline_data",
52
+ "inlineData",
53
+ "source",
54
+ "result"
55
+ ]);
56
+ const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
57
+ "image",
58
+ "image_url",
59
+ "input_image",
60
+ "audio",
61
+ "input_audio",
62
+ "video",
63
+ "video_url",
64
+ "file",
65
+ "input_file",
66
+ "document",
67
+ "media",
68
+ "file-data"
69
+ ]);
70
+ const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
71
+ "file",
72
+ "input_file",
73
+ "document",
74
+ "media",
75
+ "file-data"
76
+ ]);
77
+ const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
78
+ "wav",
79
+ "mp3",
80
+ "ogg",
81
+ "flac",
82
+ "m4a",
83
+ "aac",
84
+ "webm"
85
+ ]);
86
+ var MediaTypeContext = class MediaTypeContext {
87
+ static {
88
+ this.EMPTY = new MediaTypeContext(void 0, void 0);
89
+ }
90
+ constructor(parent, key, explicitMediaType) {
91
+ this.parent = parent;
92
+ this.key = key;
93
+ this.explicitMediaType = explicitMediaType;
94
+ }
95
+ inferMediaType() {
96
+ return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
97
+ }
98
+ inferFromSiblingMime() {
99
+ if (this.explicitMediaType) return this.explicitMediaType;
100
+ if (!this.parent) return void 0;
101
+ for (const hint of MIME_HINT_KEYS) {
102
+ const v = this.parent[hint];
103
+ if (typeof v === "string") return v;
104
+ }
105
+ }
106
+ inferFromSiblingFormat() {
107
+ if (!this.parent) return void 0;
108
+ const fmt = this.parent.format;
109
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
110
+ }
111
+ inferFromParentType() {
112
+ if (!this.parent) return void 0;
113
+ const t = this.parent.type;
114
+ if (typeof t !== "string") return void 0;
115
+ if (t === "image" || t === "image_url" || t === "input_image") return "image";
116
+ if (t === "audio" || t === "input_audio") return "audio";
117
+ if (t === "video" || t === "video_url") return "video";
118
+ if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
119
+ }
120
+ inferFromKey() {
121
+ if (!this.key) return void 0;
122
+ const key = this.key.toLowerCase();
123
+ if (key.includes("audio")) return "audio";
124
+ if (key.includes("video")) return "video";
125
+ if (key.includes("image")) return "image";
126
+ if (key.includes("file") || key.includes("document")) return "application/octet-stream";
127
+ }
128
+ hasExplicitBinaryMediaType() {
129
+ if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
130
+ const mediaType = this.inferFromSiblingMime();
131
+ return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
132
+ }
133
+ signalsBinary() {
134
+ if (this.explicitMediaType) return true;
135
+ if (this.parent) {
136
+ for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
137
+ const fmt = this.parent.format;
138
+ if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
139
+ const t = this.parent.type;
140
+ if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
141
+ }
142
+ if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
143
+ return false;
144
+ }
145
+ };
146
+ //#endregion
147
+ //#region src/sanitization/binary_content_redactor.ts
148
+ const STRONG_CONTEXT_MIN_LENGTH = 64;
149
+ const WEAK_CONTEXT_MIN_LENGTH = 1024;
150
+ var BinaryContentRedactor = class {
151
+ constructor(recognizer = new Base64Recognizer()) {
152
+ this.recognizer = recognizer;
153
+ this.visited = /* @__PURE__ */ new WeakSet();
154
+ }
155
+ redact(value, mediaType) {
156
+ this.visited = /* @__PURE__ */ new WeakSet();
157
+ return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
158
+ }
159
+ walk(value, ctx) {
160
+ if (value === null || value === void 0) return value;
161
+ if (typeof value === "string") return this.redactString(value, ctx);
162
+ if (typeof value !== "object") return value;
163
+ if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
164
+ if (this.visited.has(value)) return null;
165
+ this.visited.add(value);
166
+ if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
167
+ const obj = value;
168
+ const out = {};
169
+ for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
170
+ return out;
171
+ }
172
+ redactString(value, ctx) {
173
+ const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
174
+ const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
175
+ const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
176
+ const recognition = this.recognizer.recognize(recognitionValue, minLength);
177
+ switch (recognition.kind) {
178
+ case "data-url": return this.placeholderFor(recognition.mediaType);
179
+ case "raw": return this.placeholderFor(ctx.inferMediaType());
180
+ case "none": return value;
181
+ }
182
+ }
183
+ placeholderFor(mediaType) {
184
+ if (!mediaType) return "[base64 redacted]";
185
+ if (mediaType === "application/octet-stream") return "[base64 file redacted]";
186
+ return `[base64 ${mediaType} redacted]`;
187
+ }
188
+ };
189
+ new BinaryContentRedactor();
190
+ //#endregion
191
+ //#region src/utils.ts
192
+ const MAX_OUTPUT_SIZE = 2e5;
193
+ const STRING_FORMAT = "utf8";
99
194
  const sharedTextEncoder = new TextEncoder();
100
- const sharedTextDecoder = new TextDecoder(STRING_FORMAT, {
101
- fatal: false
102
- });
103
- const utf8ByteLength = str => sharedTextEncoder.encode(str).byteLength;
104
-
195
+ const sharedTextDecoder = new TextDecoder(STRING_FORMAT, { fatal: false });
196
+ const utf8ByteLength = (str) => sharedTextEncoder.encode(str).byteLength;
105
197
  /**
106
- * Safely converts content to a string, preserving structure for objects/arrays.
107
- * - If content is already a string, returns it as-is
108
- * - If content is an object or array, stringifies it with JSON.stringify to preserve structure
109
- * - Otherwise, converts to string with String()
110
- *
111
- * This prevents the "[object Object]" bug when objects are naively converted to strings.
112
- *
113
- * @param content - The content to convert to a string
114
- * @returns A string representation that preserves structure for complex types
115
- */
198
+ * Safely converts content to a string, preserving structure for objects/arrays.
199
+ * - If content is already a string, returns it as-is
200
+ * - If content is an object or array, stringifies it with JSON.stringify to preserve structure
201
+ * - Otherwise, converts to string with String()
202
+ *
203
+ * This prevents the "[object Object]" bug when objects are naively converted to strings.
204
+ *
205
+ * @param content - The content to convert to a string
206
+ * @returns A string representation that preserves structure for complex types
207
+ */
116
208
  function toContentString(content) {
117
- if (typeof content === 'string') {
118
- return content;
119
- }
120
- if (content !== undefined && content !== null && typeof content === 'object') {
121
- try {
122
- return JSON.stringify(content);
123
- } catch {
124
- // Fallback for circular refs, BigInt, or objects with throwing toJSON
125
- return String(content);
126
- }
127
- }
128
- return String(content);
209
+ if (typeof content === "string") return content;
210
+ if (content !== void 0 && content !== null && typeof content === "object") try {
211
+ return JSON.stringify(content);
212
+ } catch {
213
+ return String(content);
214
+ }
215
+ return String(content);
129
216
  }
130
217
  const withPrivacyMode = (client, privacyMode, input) => {
131
- return client.privacy_mode || privacyMode ? null : input;
218
+ return client.privacy_mode || privacyMode ? null : input;
132
219
  };
133
220
  function toSafeString(input) {
134
- if (input === undefined || input === null) {
135
- return '';
136
- }
137
- if (typeof input === 'string') {
138
- return input;
139
- }
140
- try {
141
- return JSON.stringify(input);
142
- } catch {
143
- console.warn('Failed to stringify input', input);
144
- return '';
145
- }
221
+ if (input === void 0 || input === null) return "";
222
+ if (typeof input === "string") return input;
223
+ try {
224
+ return JSON.stringify(input);
225
+ } catch {
226
+ console.warn("Failed to stringify input", input);
227
+ return "";
228
+ }
146
229
  }
147
230
  const truncate = (input, client) => {
148
- const str = toSafeString(input);
149
- if (str === '') {
150
- return '';
151
- }
152
- if (isFullAiCaptureEnabled(client)) {
153
- return str;
154
- }
155
-
156
- // Check if we need to truncate and ensure STRING_FORMAT is respected
157
- const buffer = sharedTextEncoder.encode(str);
158
- if (buffer.length <= MAX_OUTPUT_SIZE) {
159
- // Ensure STRING_FORMAT is respected
160
- return sharedTextDecoder.decode(buffer);
161
- }
162
-
163
- // Truncate the buffer and ensure a valid string is returned.
164
- // fatal: false means we get U+FFFD at the end if truncation broke the encoding.
165
- const truncatedBuffer = buffer.slice(0, MAX_OUTPUT_SIZE);
166
- let truncatedStr = sharedTextDecoder.decode(truncatedBuffer);
167
- if (truncatedStr.endsWith('\uFFFD')) {
168
- truncatedStr = truncatedStr.slice(0, -1);
169
- }
170
- return `${truncatedStr}... [truncated]`;
231
+ const str = toSafeString(input);
232
+ if (str === "") return "";
233
+ if (isFullAiCaptureEnabled(client)) return str;
234
+ const buffer = sharedTextEncoder.encode(str);
235
+ if (buffer.length <= 2e5) return sharedTextDecoder.decode(buffer);
236
+ const truncatedBuffer = buffer.slice(0, MAX_OUTPUT_SIZE);
237
+ let truncatedStr = sharedTextDecoder.decode(truncatedBuffer);
238
+ if (truncatedStr.endsWith("�")) truncatedStr = truncatedStr.slice(0, -1);
239
+ return `${truncatedStr}... [truncated]`;
171
240
  };
172
-
173
- var version = "8.10.0";
174
-
175
- // Warn when a wrapper's base_url points at the PostHog AI Gateway: the gateway
176
- // emits its own $ai_generation, so each call would be captured (and, for billable
177
- // products, billed) twice. We only warn — the wrapper's event carries data the
178
- // gateway never sees (groups, custom properties, trace hierarchy).
179
-
180
- // Keep in sync with the gateway's deployed hosts (see services/llm-gateway in the
181
- // main repo). gateway.us.posthog.com is live today; the rest are listed ahead of
182
- // any traffic moving to them.
183
- 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'];
184
-
185
- // Swap for the dedicated AI Gateway page once it ships.
186
- const GATEWAY_DOCS_URL = 'https://posthog.com/docs/ai-observability';
187
- const extractHost = baseURL => {
188
- try {
189
- // Tolerate bare hosts that omit a scheme, e.g. "gateway.us.posthog.com/v1".
190
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
191
- return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
192
- } catch {
193
- return undefined;
194
- }
241
+ //#endregion
242
+ //#region package.json
243
+ var version = "8.10.2";
244
+ //#endregion
245
+ //#region src/gatewayWarning.ts
246
+ const POSTHOG_AI_GATEWAY_HOSTS = [
247
+ "gateway.posthog.com",
248
+ "gateway.us.posthog.com",
249
+ "gateway.eu.posthog.com",
250
+ "ai-gateway.us.posthog.com",
251
+ "ai-gateway.eu.posthog.com"
252
+ ];
253
+ const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
254
+ const extractHost = (baseURL) => {
255
+ try {
256
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
257
+ return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
258
+ } catch {
259
+ return;
260
+ }
195
261
  };
196
- const isPostHogAiGatewayUrl = baseURL => {
197
- if (!baseURL) {
198
- return false;
199
- }
200
- const host = extractHost(baseURL);
201
- return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
262
+ const isPostHogAiGatewayUrl = (baseURL) => {
263
+ if (!baseURL) return false;
264
+ const host = extractHost(baseURL);
265
+ return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
202
266
  };
203
-
204
- // Warns on every gateway call by design: the misconfiguration is impossible to
205
- // miss that way, and a doubled bill is worse than noisy logs.
206
- const warnIfPostHogAiGateway = baseURL => {
207
- if (!isPostHogAiGatewayUrl(baseURL)) {
208
- return;
209
- }
210
- 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}.`);
267
+ const warnIfPostHogAiGateway = (baseURL) => {
268
+ if (!isPostHogAiGatewayUrl(baseURL)) return;
269
+ 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}.`);
211
270
  };
212
-
271
+ //#endregion
272
+ //#region src/openai-agents/processor.ts
213
273
  /**
214
- * Normalize OpenAI Responses API input items to include a `role` field.
215
- * Items like `function_call` and `function_call_result` don't have a role,
216
- * causing PostHog's trace viewer to default them to "user".
217
- */
274
+ * Normalize OpenAI Responses API input items to include a `role` field.
275
+ * Items like `function_call` and `function_call_result` don't have a role,
276
+ * causing PostHog's trace viewer to default them to "user".
277
+ */
218
278
  function normalizeInputRoles(input) {
219
- if (!Array.isArray(input)) {
220
- return input;
221
- }
222
- return input.map(item => {
223
- if (item && typeof item === 'object' && !('role' in item) && 'type' in item) {
224
- if (item.type === 'function_call') {
225
- return {
226
- ...item,
227
- role: 'assistant'
228
- };
229
- }
230
- if (item.type === 'function_call_result') {
231
- return {
232
- ...item,
233
- role: 'tool'
234
- };
235
- }
236
- }
237
- return item;
238
- });
279
+ if (!Array.isArray(input)) return input;
280
+ return input.map((item) => {
281
+ if (item && typeof item === "object" && !("role" in item) && "type" in item) {
282
+ if (item.type === "function_call") return {
283
+ ...item,
284
+ role: "assistant"
285
+ };
286
+ if (item.type === "function_call_result") return {
287
+ ...item,
288
+ role: "tool"
289
+ };
290
+ }
291
+ return item;
292
+ });
239
293
  }
240
294
  function safeContentString(value) {
241
- try {
242
- return toContentString(value);
243
- } catch {
244
- return Object.prototype.toString.call(value);
245
- }
295
+ try {
296
+ return toContentString(value);
297
+ } catch {
298
+ return Object.prototype.toString.call(value);
299
+ }
246
300
  }
247
301
  function ensureSerializable(obj) {
248
- if (obj === null || obj === undefined) {
249
- return obj;
250
- }
251
- try {
252
- const serializedValue = JSON.stringify(obj);
253
- return serializedValue === undefined ? safeContentString(obj) : obj;
254
- } catch {
255
- return safeContentString(obj);
256
- }
302
+ if (obj === null || obj === void 0) return obj;
303
+ try {
304
+ return JSON.stringify(obj) === void 0 ? safeContentString(obj) : obj;
305
+ } catch {
306
+ return safeContentString(obj);
307
+ }
257
308
  }
258
309
  function stringifyForSizeCheck(value) {
259
- if (value === null || value === undefined) {
260
- return null;
261
- }
262
- if (typeof value === 'string') {
263
- return value;
264
- }
265
- try {
266
- return JSON.stringify(value) ?? safeContentString(value);
267
- } catch {
268
- return safeContentString(value);
269
- }
310
+ if (value === null || value === void 0) return null;
311
+ if (typeof value === "string") return value;
312
+ try {
313
+ return JSON.stringify(value) ?? safeContentString(value);
314
+ } catch {
315
+ return safeContentString(value);
316
+ }
270
317
  }
271
318
  function exceedsMaxOutputSize(serializedValue) {
272
- return serializedValue === null ? false : utf8ByteLength(serializedValue) > MAX_OUTPUT_SIZE;
319
+ return serializedValue === null ? false : utf8ByteLength(serializedValue) > MAX_OUTPUT_SIZE;
273
320
  }
274
321
  function parseIsoTimestamp(isoStr) {
275
- if (typeof isoStr !== 'string' || isoStr.trim() === '') {
276
- return null;
277
- }
278
- const ts = new Date(isoStr).getTime();
279
- return Number.isFinite(ts) ? ts / 1000 : null;
322
+ if (typeof isoStr !== "string" || isoStr.trim() === "") return null;
323
+ const ts = new Date(isoStr).getTime();
324
+ return Number.isFinite(ts) ? ts / 1e3 : null;
280
325
  }
281
326
  /**
282
- * A tracing processor that sends OpenAI Agents SDK traces to PostHog.
283
- *
284
- * Implements the TracingProcessor interface from the OpenAI Agents SDK
285
- * and maps agent traces, spans, and generations to PostHog's LLM analytics events.
286
- *
287
- * @example
288
- * ```typescript
289
- * import { PostHogTracingProcessor } from '@posthog/ai/openai-agents'
290
- * import { addTraceProcessor } from '@openai/agents'
291
- *
292
- * const processor = new PostHogTracingProcessor({
293
- * client: posthog,
294
- * distinctId: 'user@example.com',
295
- * })
296
- * addTraceProcessor(processor)
297
- * ```
298
- */
299
- class PostHogTracingProcessor {
300
- _spanStartTimes = new Map();
301
- _traceMetadata = new Map();
302
- _maxTrackedEntries = 10000;
303
- constructor(options) {
304
- this._client = options.client;
305
- this._distinctId = options.distinctId;
306
- this._privacyMode = options.privacyMode ?? false;
307
- this._groups = options.groups ?? {};
308
- this._properties = options.properties ?? {};
309
- this._onError = options.onError;
310
- }
311
- _getDistinctId(trace) {
312
- if (typeof this._distinctId === 'function') {
313
- if (trace) {
314
- const result = this._distinctId(trace);
315
- if (result) {
316
- return String(result);
317
- }
318
- }
319
- return undefined;
320
- } else if (this._distinctId) {
321
- return String(this._distinctId);
322
- }
323
- return undefined;
324
- }
325
- _withPrivacyMode(value) {
326
- return withPrivacyMode(this._client, this._privacyMode, value);
327
- }
328
- _prepareCapturedValue(value) {
329
- const serializableValue = ensureSerializable(value);
330
- const serializedValue = stringifyForSizeCheck(serializableValue);
331
- const boundedValue = isFullAiCaptureEnabled(this._client) || !exceedsMaxOutputSize(serializedValue) ? serializableValue : truncate(serializedValue, this._client);
332
- return this._withPrivacyMode(boundedValue);
333
- }
334
- _evictStaleEntries() {
335
- if (this._spanStartTimes.size > this._maxTrackedEntries) {
336
- const entries = [...this._spanStartTimes.entries()].sort((a, b) => a[1] - b[1]);
337
- const toRemove = entries.slice(0, Math.floor(entries.length / 2));
338
- for (const [key] of toRemove) {
339
- this._spanStartTimes.delete(key);
340
- }
341
- }
342
- if (this._traceMetadata.size > this._maxTrackedEntries) {
343
- const keys = [...this._traceMetadata.keys()];
344
- const toRemove = keys.slice(0, Math.floor(keys.length / 2));
345
- for (const key of toRemove) {
346
- this._traceMetadata.delete(key);
347
- }
348
- }
349
- }
350
- _handleError(error, context) {
351
- try {
352
- this._onError?.(error, context);
353
- } catch (handlerError) {
354
- }
355
- }
356
- _captureEvent(event, properties, distinctId) {
357
- try {
358
- if (!this._client?.capture) {
359
- return;
360
- }
361
- const finalProperties = {
362
- ...this._properties,
363
- ...properties
364
- };
365
- const eventMessage = {
366
- distinctId: distinctId || 'unknown',
367
- event,
368
- properties: finalProperties,
369
- groups: Object.keys(this._groups).length > 0 ? this._groups : undefined
370
- };
371
- captureAiEvent(this._client, eventMessage);
372
- } catch (error) {
373
- this._handleError(error, 'capture');
374
- }
375
- }
376
- _baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties) {
377
- const properties = {
378
- $ai_lib: 'posthog-ai',
379
- $ai_lib_version: version,
380
- $ai_trace_id: traceId,
381
- $ai_span_id: spanId,
382
- $ai_parent_id: parentId,
383
- $ai_provider: 'openai',
384
- $ai_framework: 'openai-agents',
385
- $ai_latency: latency,
386
- ...errorProperties
387
- };
388
- if (groupId) {
389
- properties.$ai_session_id = groupId;
390
- properties.$ai_group_id = groupId;
391
- }
392
- return properties;
393
- }
394
- _getErrorProperties(error) {
395
- if (!error) {
396
- return {};
397
- }
398
- const errorMessage = error.message || String(error);
399
- let errorType = 'unknown';
400
- if (errorMessage.includes('ModelBehaviorError')) {
401
- errorType = 'model_behavior_error';
402
- } else if (errorMessage.includes('UserError')) {
403
- errorType = 'user_error';
404
- } else if (errorMessage.includes('InputGuardrailTripwireTriggered')) {
405
- errorType = 'input_guardrail_triggered';
406
- } else if (errorMessage.includes('OutputGuardrailTripwireTriggered')) {
407
- errorType = 'output_guardrail_triggered';
408
- } else if (errorMessage.includes('MaxTurnsExceeded')) {
409
- errorType = 'max_turns_exceeded';
410
- }
411
- return {
412
- $ai_is_error: true,
413
- $ai_error: errorMessage,
414
- $ai_error_type: errorType
415
- };
416
- }
417
-
418
- // --- TracingProcessor interface ---
419
-
420
- async onTraceStart(trace) {
421
- try {
422
- this._evictStaleEntries();
423
- const traceId = trace.traceId;
424
- const traceName = trace.name;
425
- const groupId = trace.groupId ?? null;
426
- const metadata = trace.metadata;
427
- const distinctId = this._getDistinctId(trace);
428
- this._traceMetadata.set(traceId, {
429
- name: traceName,
430
- groupId,
431
- metadata,
432
- distinctId,
433
- startTime: Date.now() / 1000
434
- });
435
- } catch (error) {
436
- this._handleError(error, 'onTraceStart');
437
- }
438
- }
439
- async onTraceEnd(trace) {
440
- try {
441
- const traceId = trace.traceId;
442
- const traceInfo = this._traceMetadata.get(traceId);
443
- this._traceMetadata.delete(traceId);
444
- const traceName = traceInfo?.name ?? trace.name;
445
- const groupId = traceInfo?.groupId ?? trace.groupId ?? null;
446
- const metadata = traceInfo?.metadata ?? trace.metadata;
447
- const distinctId = traceInfo?.distinctId ?? this._getDistinctId(trace);
448
- const startTime = traceInfo?.startTime;
449
- const latency = startTime != null ? Date.now() / 1000 - startTime : undefined;
450
- const properties = {
451
- $ai_lib: 'posthog-ai',
452
- $ai_lib_version: version,
453
- $ai_trace_id: traceId,
454
- $ai_trace_name: traceName,
455
- $ai_provider: 'openai',
456
- $ai_framework: 'openai-agents'
457
- };
458
- if (latency != null) {
459
- properties.$ai_latency = latency;
460
- }
461
-
462
- // The Agents SDK groupId links traces from one conversation, which is exactly
463
- // what PostHog calls a session. $ai_group_id is still emitted for anyone
464
- // already querying it.
465
- if (groupId) {
466
- properties.$ai_session_id = groupId;
467
- properties.$ai_group_id = groupId;
468
- }
469
- if (metadata && Object.keys(metadata).length > 0) {
470
- properties.$ai_trace_metadata = this._prepareCapturedValue(metadata);
471
- }
472
- if (distinctId == null) {
473
- properties.$process_person_profile = false;
474
- }
475
- this._captureEvent('$ai_trace', properties, distinctId ?? traceId);
476
- } catch (error) {
477
- this._handleError(error, 'onTraceEnd');
478
- }
479
- }
480
- async onSpanStart(span) {
481
- try {
482
- this._evictStaleEntries();
483
- this._spanStartTimes.set(span.spanId, Date.now() / 1000);
484
- } catch (error) {
485
- this._handleError(error, 'onSpanStart');
486
- }
487
- }
488
- async onSpanEnd(span) {
489
- try {
490
- const spanId = span.spanId;
491
- const traceId = span.traceId;
492
- const parentId = span.parentId;
493
- const spanData = span.spanData;
494
-
495
- // Calculate latency
496
- const startTime = this._spanStartTimes.get(spanId);
497
- this._spanStartTimes.delete(spanId);
498
- let latency;
499
- if (startTime != null) {
500
- latency = Date.now() / 1000 - startTime;
501
- } else {
502
- const started = parseIsoTimestamp(span.startedAt);
503
- const ended = parseIsoTimestamp(span.endedAt);
504
- latency = started != null && ended != null ? ended - started : 0;
505
- }
506
-
507
- // Get distinct ID from trace metadata
508
- const traceInfo = this._traceMetadata.get(traceId);
509
- const userDistinctId = traceInfo?.distinctId ?? this._getDistinctId(null);
510
-
511
- // Get group_id from trace metadata
512
- const groupId = traceInfo?.groupId ?? null;
513
-
514
- // Get error properties
515
- const errorProperties = this._getErrorProperties(span.error);
516
-
517
- // Personless mode: no user-provided distinct_id, fallback to trace_id
518
- if (userDistinctId == null) {
519
- errorProperties.$process_person_profile = false;
520
- }
521
- const distinctId = userDistinctId ?? traceId;
522
-
523
- // Dispatch based on span data type
524
- switch (spanData.type) {
525
- case 'generation':
526
- this._handleGenerationSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
527
- break;
528
- case 'response':
529
- this._handleResponseSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
530
- break;
531
- case 'function':
532
- this._handleFunctionSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
533
- break;
534
- case 'agent':
535
- this._handleAgentSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
536
- break;
537
- case 'handoff':
538
- this._handleHandoffSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
539
- break;
540
- case 'guardrail':
541
- this._handleGuardrailSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
542
- break;
543
- case 'custom':
544
- this._handleCustomSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
545
- break;
546
- case 'transcription':
547
- case 'speech':
548
- case 'speech_group':
549
- this._handleAudioSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
550
- break;
551
- case 'mcp_tools':
552
- this._handleMcpSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
553
- break;
554
- default:
555
- this._handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
556
- break;
557
- }
558
- } catch (error) {
559
- this._handleError(error, 'onSpanEnd');
560
- }
561
- }
562
- async shutdown() {
563
- try {
564
- this._spanStartTimes.clear();
565
- this._traceMetadata.clear();
566
- if (typeof this._client?.flush === 'function') {
567
- await this._client.flush();
568
- }
569
- } catch (error) {
570
- this._handleError(error, 'shutdown');
571
- }
572
- }
573
- async forceFlush() {
574
- try {
575
- if (typeof this._client?.flush === 'function') {
576
- await this._client.flush();
577
- }
578
- } catch (error) {
579
- this._handleError(error, 'forceFlush');
580
- }
581
- }
582
-
583
- // --- Span handlers ---
584
-
585
- _handleGenerationSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
586
- // OpenAI Agents 0.8 stores the raw Chat Completions response in output[0].
587
- // Canonical generation fields take precedence when the SDK provides them.
588
- const rawResponse = spanData.output?.[0];
589
- const rawResponseUsage = rawResponse?.usage;
590
- const usage = spanData.usage ?? rawResponseUsage ?? {};
591
- const usesRawResponseUsage = spanData.usage === undefined && rawResponseUsage !== undefined;
592
- const model = spanData.model ?? rawResponse?.model;
593
- const inputTokens = usage.input_tokens || usage.prompt_tokens || 0;
594
- const outputTokens = usage.output_tokens || usage.completion_tokens || 0;
595
- const modelConfig = spanData.model_config ?? {};
596
- const modelParams = {};
597
- for (const param of ['temperature', 'max_tokens', 'top_p', 'frequency_penalty', 'presence_penalty']) {
598
- if (param in modelConfig) {
599
- modelParams[param] = modelConfig[param];
600
- }
601
- }
602
- if (typeof modelConfig.base_url === 'string') {
603
- warnIfPostHogAiGateway(modelConfig.base_url);
604
- }
605
- const properties = {
606
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
607
- $ai_model: model,
608
- // Best-effort: Agents SDK only sets model_config.base_url for chat-completions
609
- // calls with no model settings; Responses and normal chat calls omit it, so ''.
610
- $ai_base_url: typeof modelConfig.base_url === 'string' ? modelConfig.base_url : '',
611
- $ai_model_parameters: Object.keys(modelParams).length > 0 ? modelParams : null,
612
- $ai_input: this._prepareCapturedValue(normalizeInputRoles(spanData.input)),
613
- $ai_output_choices: this._prepareCapturedValue(spanData.output),
614
- $ai_input_tokens: inputTokens,
615
- $ai_output_tokens: outputTokens,
616
- $ai_total_tokens: inputTokens + outputTokens
617
- };
618
- if (usesRawResponseUsage) {
619
- // Chat Completions prompt tokens include cached tokens rather than reporting them exclusively.
620
- properties.$ai_cache_reporting_exclusive = false;
621
- }
622
-
623
- // Raw Chat Completions usage keeps token details under provider-specific fields.
624
- const promptTokenDetails = usage.prompt_tokens_details;
625
- const completionTokenDetails = usage.completion_tokens_details;
626
- if (completionTokenDetails?.reasoning_tokens) {
627
- properties.$ai_reasoning_tokens = completionTokenDetails.reasoning_tokens;
628
- }
629
- if (promptTokenDetails?.cached_tokens) {
630
- properties.$ai_cache_read_input_tokens = promptTokenDetails.cached_tokens;
631
- }
632
- if (usage.details) {
633
- const details = usage.details;
634
- if (details.reasoning_tokens) {
635
- properties.$ai_reasoning_tokens = details.reasoning_tokens;
636
- }
637
- if (details.cache_read_input_tokens) {
638
- properties.$ai_cache_read_input_tokens = details.cache_read_input_tokens;
639
- }
640
- if (details.cache_creation_input_tokens) {
641
- properties.$ai_cache_creation_input_tokens = details.cache_creation_input_tokens;
642
- }
643
- }
644
-
645
- // Also check top-level usage for reasoning/cache tokens (flexible schema)
646
- if (usage.reasoning_tokens) {
647
- properties.$ai_reasoning_tokens = usage.reasoning_tokens;
648
- }
649
- if (usage.cache_read_input_tokens) {
650
- properties.$ai_cache_read_input_tokens = usage.cache_read_input_tokens;
651
- }
652
- if (usage.cache_creation_input_tokens) {
653
- properties.$ai_cache_creation_input_tokens = usage.cache_creation_input_tokens;
654
- }
655
- this._captureEvent('$ai_generation', properties, distinctId);
656
- }
657
- _handleResponseSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
658
- // The OpenAI Agents SDK exposes these underscored fields for non-OpenAI tracing providers.
659
- // Treat them as best-effort and avoid assuming they are always present.
660
- const responseSpanData = spanData;
661
- const response = responseSpanData._response;
662
- const responseId = spanData.response_id ?? response?.id;
663
-
664
- // Extract usage from response
665
- const usage = response?.usage ?? {};
666
- const inputTokens = usage?.input_tokens ?? 0;
667
- const outputTokens = usage?.output_tokens ?? 0;
668
-
669
- // Extract model from response
670
- const model = response?.model;
671
-
672
- // No $ai_base_url: ResponseSpanData carries no base URL, so dedup can't see these.
673
- const properties = {
674
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
675
- $ai_model: model,
676
- $ai_response_id: responseId,
677
- $ai_input: this._prepareCapturedValue(normalizeInputRoles(responseSpanData._input)),
678
- $ai_input_tokens: inputTokens,
679
- $ai_output_tokens: outputTokens,
680
- $ai_total_tokens: inputTokens + outputTokens
681
- };
682
-
683
- // Extract output from response
684
- if (response?.output) {
685
- properties.$ai_output_choices = this._prepareCapturedValue(response.output);
686
- }
687
- this._captureEvent('$ai_generation', properties, distinctId);
688
- }
689
- _handleFunctionSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
690
- const properties = {
691
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
692
- $ai_span_name: spanData.name,
693
- $ai_span_type: 'tool',
694
- $ai_input_state: this._prepareCapturedValue(spanData.input),
695
- $ai_output_state: this._prepareCapturedValue(spanData.output)
696
- };
697
- if (spanData.mcp_data) {
698
- properties.$ai_mcp_data = this._prepareCapturedValue(spanData.mcp_data);
699
- }
700
- this._captureEvent('$ai_span', properties, distinctId);
701
- }
702
- _handleAgentSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
703
- const properties = {
704
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
705
- $ai_span_name: spanData.name,
706
- $ai_span_type: 'agent'
707
- };
708
- if (spanData.handoffs) {
709
- properties.$ai_agent_handoffs = spanData.handoffs;
710
- }
711
- if (spanData.tools) {
712
- properties.$ai_agent_tools = spanData.tools;
713
- }
714
- if (spanData.output_type) {
715
- properties.$ai_agent_output_type = spanData.output_type;
716
- }
717
- this._captureEvent('$ai_span', properties, distinctId);
718
- }
719
- _handleHandoffSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
720
- const properties = {
721
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
722
- $ai_span_name: `${spanData.from_agent} -> ${spanData.to_agent}`,
723
- $ai_span_type: 'handoff',
724
- $ai_handoff_from_agent: spanData.from_agent,
725
- $ai_handoff_to_agent: spanData.to_agent
726
- };
727
- this._captureEvent('$ai_span', properties, distinctId);
728
- }
729
- _handleGuardrailSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
730
- const properties = {
731
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
732
- $ai_span_name: spanData.name,
733
- $ai_span_type: 'guardrail',
734
- $ai_guardrail_triggered: spanData.triggered
735
- };
736
- this._captureEvent('$ai_span', properties, distinctId);
737
- }
738
- _handleCustomSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
739
- const properties = {
740
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
741
- $ai_span_name: spanData.name,
742
- $ai_span_type: 'custom',
743
- $ai_custom_data: this._prepareCapturedValue(spanData.data)
744
- };
745
- this._captureEvent('$ai_span', properties, distinctId);
746
- }
747
- _handleAudioSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
748
- const spanType = spanData.type;
749
- const properties = {
750
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
751
- $ai_span_name: spanType,
752
- $ai_span_type: spanType
753
- };
754
-
755
- // Add model info if available
756
- if ('model' in spanData && spanData.model) {
757
- properties.$ai_model = spanData.model;
758
- }
759
-
760
- // Add model config if available
761
- if ('model_config' in spanData && spanData.model_config) {
762
- properties.$ai_model_config = this._prepareCapturedValue(spanData.model_config);
763
- }
764
-
765
- // Add audio format info
766
- if (spanData.type === 'transcription') {
767
- const transcription = spanData;
768
- if (transcription.input?.format) {
769
- properties.$ai_audio_input_format = transcription.input.format;
770
- }
771
- // Transcription output is text
772
- if (transcription.output) {
773
- properties.$ai_output_state = this._prepareCapturedValue(transcription.output);
774
- }
775
- } else if (spanData.type === 'speech') {
776
- const speech = spanData;
777
- if (speech.output?.format) {
778
- properties.$ai_audio_output_format = speech.output.format;
779
- }
780
- // Text input for TTS
781
- if (speech.input) {
782
- properties.$ai_input = this._prepareCapturedValue(speech.input);
783
- }
784
- } else if (spanData.type === 'speech_group') {
785
- const speechGroup = spanData;
786
- if (speechGroup.input) {
787
- properties.$ai_input = this._prepareCapturedValue(speechGroup.input);
788
- }
789
- }
790
- this._captureEvent('$ai_span', properties, distinctId);
791
- }
792
- _handleMcpSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
793
- const properties = {
794
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
795
- $ai_span_name: `mcp:${spanData.server}`,
796
- $ai_span_type: 'mcp_tools',
797
- $ai_mcp_server: spanData.server,
798
- $ai_mcp_tools: this._prepareCapturedValue(spanData.result)
799
- };
800
- this._captureEvent('$ai_span', properties, distinctId);
801
- }
802
- _handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
803
- const spanType = spanData.type || 'unknown';
804
- const properties = {
805
- ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
806
- $ai_span_name: spanType,
807
- $ai_span_type: spanType
808
- };
809
- this._captureEvent('$ai_span', properties, distinctId);
810
- }
811
- }
812
-
327
+ * A tracing processor that sends OpenAI Agents SDK traces to PostHog.
328
+ *
329
+ * Implements the TracingProcessor interface from the OpenAI Agents SDK
330
+ * and maps agent traces, spans, and generations to PostHog's LLM analytics events.
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * import { PostHogTracingProcessor } from '@posthog/ai/openai-agents'
335
+ * import { addTraceProcessor } from '@openai/agents'
336
+ *
337
+ * const processor = new PostHogTracingProcessor({
338
+ * client: posthog,
339
+ * distinctId: 'user@example.com',
340
+ * })
341
+ * addTraceProcessor(processor)
342
+ * ```
343
+ */
344
+ var PostHogTracingProcessor = class {
345
+ constructor(options) {
346
+ this._spanStartTimes = /* @__PURE__ */ new Map();
347
+ this._traceMetadata = /* @__PURE__ */ new Map();
348
+ this._maxTrackedEntries = 1e4;
349
+ this._client = options.client;
350
+ this._distinctId = options.distinctId;
351
+ this._privacyMode = options.privacyMode ?? false;
352
+ this._groups = options.groups ?? {};
353
+ this._properties = options.properties ?? {};
354
+ this._onError = options.onError;
355
+ }
356
+ _getDistinctId(trace) {
357
+ if (typeof this._distinctId === "function") {
358
+ if (trace) {
359
+ const result = this._distinctId(trace);
360
+ if (result) return String(result);
361
+ }
362
+ return;
363
+ } else if (this._distinctId) return String(this._distinctId);
364
+ }
365
+ _withPrivacyMode(value) {
366
+ return withPrivacyMode(this._client, this._privacyMode, value);
367
+ }
368
+ _prepareCapturedValue(value) {
369
+ const serializableValue = ensureSerializable(value);
370
+ const serializedValue = stringifyForSizeCheck(serializableValue);
371
+ const boundedValue = isFullAiCaptureEnabled(this._client) || !exceedsMaxOutputSize(serializedValue) ? serializableValue : truncate(serializedValue, this._client);
372
+ return this._withPrivacyMode(boundedValue);
373
+ }
374
+ _evictStaleEntries() {
375
+ if (this._spanStartTimes.size > this._maxTrackedEntries) {
376
+ const entries = [...this._spanStartTimes.entries()].sort((a, b) => a[1] - b[1]);
377
+ const toRemove = entries.slice(0, Math.floor(entries.length / 2));
378
+ for (const [key] of toRemove) this._spanStartTimes.delete(key);
379
+ }
380
+ if (this._traceMetadata.size > this._maxTrackedEntries) {
381
+ const keys = [...this._traceMetadata.keys()];
382
+ const toRemove = keys.slice(0, Math.floor(keys.length / 2));
383
+ for (const key of toRemove) this._traceMetadata.delete(key);
384
+ }
385
+ }
386
+ _handleError(error, context) {
387
+ try {
388
+ this._onError?.(error, context);
389
+ } catch (handlerError) {}
390
+ }
391
+ _captureEvent(event, properties, distinctId) {
392
+ try {
393
+ if (!this._client?.capture) return;
394
+ const finalProperties = {
395
+ ...this._properties,
396
+ ...properties
397
+ };
398
+ const eventMessage = {
399
+ distinctId: distinctId || "unknown",
400
+ event,
401
+ properties: finalProperties,
402
+ groups: Object.keys(this._groups).length > 0 ? this._groups : void 0
403
+ };
404
+ captureAiEvent(this._client, eventMessage);
405
+ } catch (error) {
406
+ this._handleError(error, "capture");
407
+ }
408
+ }
409
+ _baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties) {
410
+ const properties = {
411
+ $ai_lib: "posthog-ai",
412
+ $ai_lib_version: version,
413
+ $ai_trace_id: traceId,
414
+ $ai_span_id: spanId,
415
+ $ai_parent_id: parentId,
416
+ $ai_provider: "openai",
417
+ $ai_framework: "openai-agents",
418
+ $ai_latency: latency,
419
+ ...errorProperties
420
+ };
421
+ if (groupId) {
422
+ properties.$ai_session_id = groupId;
423
+ properties.$ai_group_id = groupId;
424
+ }
425
+ return properties;
426
+ }
427
+ _getErrorProperties(error) {
428
+ if (!error) return {};
429
+ const errorMessage = error.message || String(error);
430
+ let errorType = "unknown";
431
+ if (errorMessage.includes("ModelBehaviorError")) errorType = "model_behavior_error";
432
+ else if (errorMessage.includes("UserError")) errorType = "user_error";
433
+ else if (errorMessage.includes("InputGuardrailTripwireTriggered")) errorType = "input_guardrail_triggered";
434
+ else if (errorMessage.includes("OutputGuardrailTripwireTriggered")) errorType = "output_guardrail_triggered";
435
+ else if (errorMessage.includes("MaxTurnsExceeded")) errorType = "max_turns_exceeded";
436
+ return {
437
+ $ai_is_error: true,
438
+ $ai_error: errorMessage,
439
+ $ai_error_type: errorType
440
+ };
441
+ }
442
+ async onTraceStart(trace) {
443
+ try {
444
+ this._evictStaleEntries();
445
+ const traceId = trace.traceId;
446
+ const traceName = trace.name;
447
+ const groupId = trace.groupId ?? null;
448
+ const metadata = trace.metadata;
449
+ const distinctId = this._getDistinctId(trace);
450
+ this._traceMetadata.set(traceId, {
451
+ name: traceName,
452
+ groupId,
453
+ metadata,
454
+ distinctId,
455
+ startTime: Date.now() / 1e3
456
+ });
457
+ } catch (error) {
458
+ this._handleError(error, "onTraceStart");
459
+ }
460
+ }
461
+ async onTraceEnd(trace) {
462
+ try {
463
+ const traceId = trace.traceId;
464
+ const traceInfo = this._traceMetadata.get(traceId);
465
+ this._traceMetadata.delete(traceId);
466
+ const traceName = traceInfo?.name ?? trace.name;
467
+ const groupId = traceInfo?.groupId ?? trace.groupId ?? null;
468
+ const metadata = traceInfo?.metadata ?? trace.metadata;
469
+ const distinctId = traceInfo?.distinctId ?? this._getDistinctId(trace);
470
+ const startTime = traceInfo?.startTime;
471
+ const latency = startTime != null ? Date.now() / 1e3 - startTime : void 0;
472
+ const properties = {
473
+ $ai_lib: "posthog-ai",
474
+ $ai_lib_version: version,
475
+ $ai_trace_id: traceId,
476
+ $ai_trace_name: traceName,
477
+ $ai_provider: "openai",
478
+ $ai_framework: "openai-agents"
479
+ };
480
+ if (latency != null) properties.$ai_latency = latency;
481
+ if (groupId) {
482
+ properties.$ai_session_id = groupId;
483
+ properties.$ai_group_id = groupId;
484
+ }
485
+ if (metadata && Object.keys(metadata).length > 0) properties.$ai_trace_metadata = this._prepareCapturedValue(metadata);
486
+ if (distinctId == null) properties.$process_person_profile = false;
487
+ this._captureEvent("$ai_trace", properties, distinctId ?? traceId);
488
+ } catch (error) {
489
+ this._handleError(error, "onTraceEnd");
490
+ }
491
+ }
492
+ async onSpanStart(span) {
493
+ try {
494
+ this._evictStaleEntries();
495
+ this._spanStartTimes.set(span.spanId, Date.now() / 1e3);
496
+ } catch (error) {
497
+ this._handleError(error, "onSpanStart");
498
+ }
499
+ }
500
+ async onSpanEnd(span) {
501
+ try {
502
+ const spanId = span.spanId;
503
+ const traceId = span.traceId;
504
+ const parentId = span.parentId;
505
+ const spanData = span.spanData;
506
+ const startTime = this._spanStartTimes.get(spanId);
507
+ this._spanStartTimes.delete(spanId);
508
+ let latency;
509
+ if (startTime != null) latency = Date.now() / 1e3 - startTime;
510
+ else {
511
+ const started = parseIsoTimestamp(span.startedAt);
512
+ const ended = parseIsoTimestamp(span.endedAt);
513
+ latency = started != null && ended != null ? ended - started : 0;
514
+ }
515
+ const traceInfo = this._traceMetadata.get(traceId);
516
+ const userDistinctId = traceInfo?.distinctId ?? this._getDistinctId(null);
517
+ const groupId = traceInfo?.groupId ?? null;
518
+ const errorProperties = this._getErrorProperties(span.error);
519
+ if (userDistinctId == null) errorProperties.$process_person_profile = false;
520
+ const distinctId = userDistinctId ?? traceId;
521
+ switch (spanData.type) {
522
+ case "generation":
523
+ this._handleGenerationSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
524
+ break;
525
+ case "response":
526
+ this._handleResponseSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
527
+ break;
528
+ case "function":
529
+ this._handleFunctionSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
530
+ break;
531
+ case "agent":
532
+ this._handleAgentSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
533
+ break;
534
+ case "handoff":
535
+ this._handleHandoffSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
536
+ break;
537
+ case "guardrail":
538
+ this._handleGuardrailSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
539
+ break;
540
+ case "custom":
541
+ this._handleCustomSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
542
+ break;
543
+ case "transcription":
544
+ case "speech":
545
+ case "speech_group":
546
+ this._handleAudioSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
547
+ break;
548
+ case "mcp_tools":
549
+ this._handleMcpSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
550
+ break;
551
+ default: this._handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
552
+ }
553
+ } catch (error) {
554
+ this._handleError(error, "onSpanEnd");
555
+ }
556
+ }
557
+ async shutdown() {
558
+ try {
559
+ this._spanStartTimes.clear();
560
+ this._traceMetadata.clear();
561
+ if (typeof this._client?.flush === "function") await this._client.flush();
562
+ } catch (error) {
563
+ this._handleError(error, "shutdown");
564
+ }
565
+ }
566
+ async forceFlush() {
567
+ try {
568
+ if (typeof this._client?.flush === "function") await this._client.flush();
569
+ } catch (error) {
570
+ this._handleError(error, "forceFlush");
571
+ }
572
+ }
573
+ _handleGenerationSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
574
+ const rawResponse = spanData.output?.[0];
575
+ const rawResponseUsage = rawResponse?.usage;
576
+ const usage = spanData.usage ?? rawResponseUsage ?? {};
577
+ const usesRawResponseUsage = spanData.usage === void 0 && rawResponseUsage !== void 0;
578
+ const model = spanData.model ?? rawResponse?.model;
579
+ const inputTokens = usage.input_tokens || usage.prompt_tokens || 0;
580
+ const outputTokens = usage.output_tokens || usage.completion_tokens || 0;
581
+ const modelConfig = spanData.model_config ?? {};
582
+ const modelParams = {};
583
+ for (const param of [
584
+ "temperature",
585
+ "max_tokens",
586
+ "top_p",
587
+ "frequency_penalty",
588
+ "presence_penalty"
589
+ ]) if (param in modelConfig) modelParams[param] = modelConfig[param];
590
+ if (typeof modelConfig.base_url === "string") warnIfPostHogAiGateway(modelConfig.base_url);
591
+ const properties = {
592
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
593
+ $ai_model: model,
594
+ $ai_base_url: typeof modelConfig.base_url === "string" ? modelConfig.base_url : "",
595
+ $ai_model_parameters: Object.keys(modelParams).length > 0 ? modelParams : null,
596
+ $ai_input: this._prepareCapturedValue(normalizeInputRoles(spanData.input)),
597
+ $ai_output_choices: this._prepareCapturedValue(spanData.output),
598
+ $ai_input_tokens: inputTokens,
599
+ $ai_output_tokens: outputTokens,
600
+ $ai_total_tokens: inputTokens + outputTokens
601
+ };
602
+ if (usesRawResponseUsage) properties.$ai_cache_reporting_exclusive = false;
603
+ const promptTokenDetails = usage.prompt_tokens_details;
604
+ const completionTokenDetails = usage.completion_tokens_details;
605
+ if (completionTokenDetails?.reasoning_tokens) properties.$ai_reasoning_tokens = completionTokenDetails.reasoning_tokens;
606
+ if (promptTokenDetails?.cached_tokens) properties.$ai_cache_read_input_tokens = promptTokenDetails.cached_tokens;
607
+ if (usage.details) {
608
+ const details = usage.details;
609
+ if (details.reasoning_tokens) properties.$ai_reasoning_tokens = details.reasoning_tokens;
610
+ if (details.cache_read_input_tokens) properties.$ai_cache_read_input_tokens = details.cache_read_input_tokens;
611
+ if (details.cache_creation_input_tokens) properties.$ai_cache_creation_input_tokens = details.cache_creation_input_tokens;
612
+ }
613
+ if (usage.reasoning_tokens) properties.$ai_reasoning_tokens = usage.reasoning_tokens;
614
+ if (usage.cache_read_input_tokens) properties.$ai_cache_read_input_tokens = usage.cache_read_input_tokens;
615
+ if (usage.cache_creation_input_tokens) properties.$ai_cache_creation_input_tokens = usage.cache_creation_input_tokens;
616
+ this._captureEvent("$ai_generation", properties, distinctId);
617
+ }
618
+ _handleResponseSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
619
+ const responseSpanData = spanData;
620
+ const response = responseSpanData._response;
621
+ const responseId = spanData.response_id ?? response?.id;
622
+ const usage = response?.usage ?? {};
623
+ const inputTokens = usage?.input_tokens ?? 0;
624
+ const outputTokens = usage?.output_tokens ?? 0;
625
+ const model = response?.model;
626
+ const properties = {
627
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
628
+ $ai_model: model,
629
+ $ai_response_id: responseId,
630
+ $ai_input: this._prepareCapturedValue(normalizeInputRoles(responseSpanData._input)),
631
+ $ai_input_tokens: inputTokens,
632
+ $ai_output_tokens: outputTokens,
633
+ $ai_total_tokens: inputTokens + outputTokens
634
+ };
635
+ if (response?.output) properties.$ai_output_choices = this._prepareCapturedValue(response.output);
636
+ this._captureEvent("$ai_generation", properties, distinctId);
637
+ }
638
+ _handleFunctionSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
639
+ const properties = {
640
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
641
+ $ai_span_name: spanData.name,
642
+ $ai_span_type: "tool",
643
+ $ai_input_state: this._prepareCapturedValue(spanData.input),
644
+ $ai_output_state: this._prepareCapturedValue(spanData.output)
645
+ };
646
+ if (spanData.mcp_data) properties.$ai_mcp_data = this._prepareCapturedValue(spanData.mcp_data);
647
+ this._captureEvent("$ai_span", properties, distinctId);
648
+ }
649
+ _handleAgentSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
650
+ const properties = {
651
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
652
+ $ai_span_name: spanData.name,
653
+ $ai_span_type: "agent"
654
+ };
655
+ if (spanData.handoffs) properties.$ai_agent_handoffs = spanData.handoffs;
656
+ if (spanData.tools) properties.$ai_agent_tools = spanData.tools;
657
+ if (spanData.output_type) properties.$ai_agent_output_type = spanData.output_type;
658
+ this._captureEvent("$ai_span", properties, distinctId);
659
+ }
660
+ _handleHandoffSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
661
+ const properties = {
662
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
663
+ $ai_span_name: `${spanData.from_agent} -> ${spanData.to_agent}`,
664
+ $ai_span_type: "handoff",
665
+ $ai_handoff_from_agent: spanData.from_agent,
666
+ $ai_handoff_to_agent: spanData.to_agent
667
+ };
668
+ this._captureEvent("$ai_span", properties, distinctId);
669
+ }
670
+ _handleGuardrailSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
671
+ const properties = {
672
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
673
+ $ai_span_name: spanData.name,
674
+ $ai_span_type: "guardrail",
675
+ $ai_guardrail_triggered: spanData.triggered
676
+ };
677
+ this._captureEvent("$ai_span", properties, distinctId);
678
+ }
679
+ _handleCustomSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
680
+ const properties = {
681
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
682
+ $ai_span_name: spanData.name,
683
+ $ai_span_type: "custom",
684
+ $ai_custom_data: this._prepareCapturedValue(spanData.data)
685
+ };
686
+ this._captureEvent("$ai_span", properties, distinctId);
687
+ }
688
+ _handleAudioSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
689
+ const spanType = spanData.type;
690
+ const properties = {
691
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
692
+ $ai_span_name: spanType,
693
+ $ai_span_type: spanType
694
+ };
695
+ if ("model" in spanData && spanData.model) properties.$ai_model = spanData.model;
696
+ if ("model_config" in spanData && spanData.model_config) properties.$ai_model_config = this._prepareCapturedValue(spanData.model_config);
697
+ if (spanData.type === "transcription") {
698
+ const transcription = spanData;
699
+ if (transcription.input?.format) properties.$ai_audio_input_format = transcription.input.format;
700
+ if (transcription.output) properties.$ai_output_state = this._prepareCapturedValue(transcription.output);
701
+ } else if (spanData.type === "speech") {
702
+ const speech = spanData;
703
+ if (speech.output?.format) properties.$ai_audio_output_format = speech.output.format;
704
+ if (speech.input) properties.$ai_input = this._prepareCapturedValue(speech.input);
705
+ } else if (spanData.type === "speech_group") {
706
+ const speechGroup = spanData;
707
+ if (speechGroup.input) properties.$ai_input = this._prepareCapturedValue(speechGroup.input);
708
+ }
709
+ this._captureEvent("$ai_span", properties, distinctId);
710
+ }
711
+ _handleMcpSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
712
+ const properties = {
713
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
714
+ $ai_span_name: `mcp:${spanData.server}`,
715
+ $ai_span_type: "mcp_tools",
716
+ $ai_mcp_server: spanData.server,
717
+ $ai_mcp_tools: this._prepareCapturedValue(spanData.result)
718
+ };
719
+ this._captureEvent("$ai_span", properties, distinctId);
720
+ }
721
+ _handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
722
+ const spanType = spanData.type || "unknown";
723
+ const properties = {
724
+ ...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
725
+ $ai_span_name: spanType,
726
+ $ai_span_type: spanType
727
+ };
728
+ this._captureEvent("$ai_span", properties, distinctId);
729
+ }
730
+ };
731
+ //#endregion
732
+ //#region src/openai-agents/index.ts
813
733
  /**
814
- * One-liner to instrument OpenAI Agents SDK with PostHog tracing.
815
- *
816
- * This registers a PostHogTracingProcessor with the OpenAI Agents SDK,
817
- * automatically capturing traces, spans, and LLM generations.
818
- *
819
- * @param options - Configuration options
820
- * @returns The registered processor instance
821
- *
822
- * @example
823
- * ```typescript
824
- * import { instrument } from '@posthog/ai/openai-agents'
825
- * import PostHog from 'posthog-node'
826
- *
827
- * const phClient = new PostHog('<API_KEY>')
828
- *
829
- * // Simple setup — await before running agents
830
- * await instrument({ client: phClient, distinctId: 'user@example.com' })
831
- *
832
- * // With dynamic distinct ID
833
- * await instrument({
834
- * client: phClient,
835
- * distinctId: (trace) => trace.metadata?.userId,
836
- * privacyMode: true,
837
- * properties: { environment: 'production' },
838
- * })
839
- *
840
- * // Now run agents as normal - traces automatically sent to PostHog
841
- * import { Agent, run } from '@openai/agents'
842
- * const agent = new Agent({ name: 'Assistant', instructions: 'You are helpful.' })
843
- * const result = await run(agent, 'Hello!')
844
- * ```
845
- */
734
+ * One-liner to instrument OpenAI Agents SDK with PostHog tracing.
735
+ *
736
+ * This registers a PostHogTracingProcessor with the OpenAI Agents SDK,
737
+ * automatically capturing traces, spans, and LLM generations.
738
+ *
739
+ * @param options - Configuration options
740
+ * @returns The registered processor instance
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * import { instrument } from '@posthog/ai/openai-agents'
745
+ * import PostHog from 'posthog-node'
746
+ *
747
+ * const phClient = new PostHog('<API_KEY>')
748
+ *
749
+ * // Simple setup — await before running agents
750
+ * await instrument({ client: phClient, distinctId: 'user@example.com' })
751
+ *
752
+ * // With dynamic distinct ID
753
+ * await instrument({
754
+ * client: phClient,
755
+ * distinctId: (trace) => trace.metadata?.userId,
756
+ * privacyMode: true,
757
+ * properties: { environment: 'production' },
758
+ * })
759
+ *
760
+ * // Now run agents as normal - traces automatically sent to PostHog
761
+ * import { Agent, run } from '@openai/agents'
762
+ * const agent = new Agent({ name: 'Assistant', instructions: 'You are helpful.' })
763
+ * const result = await run(agent, 'Hello!')
764
+ * ```
765
+ */
846
766
  async function instrument(options) {
847
- const {
848
- addTraceProcessor
849
- } = await import('@openai/agents');
850
- const processor = new PostHogTracingProcessor({
851
- client: options.client,
852
- distinctId: options.distinctId,
853
- privacyMode: options.privacyMode,
854
- groups: options.groups,
855
- properties: options.properties
856
- });
857
- addTraceProcessor(processor);
858
- return processor;
767
+ const { addTraceProcessor } = await import("@openai/agents");
768
+ const processor = new PostHogTracingProcessor({
769
+ client: options.client,
770
+ distinctId: options.distinctId,
771
+ privacyMode: options.privacyMode,
772
+ groups: options.groups,
773
+ properties: options.properties
774
+ });
775
+ addTraceProcessor(processor);
776
+ return processor;
859
777
  }
860
-
778
+ //#endregion
861
779
  export { PostHogTracingProcessor, instrument };
862
- //# sourceMappingURL=index.mjs.map
780
+
781
+ //# sourceMappingURL=index.mjs.map