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