@posthog/ai 7.14.0 → 7.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -7
- package/dist/anthropic/index.cjs +1 -1
- package/dist/anthropic/index.mjs +1 -1
- package/dist/gemini/index.cjs +1 -1
- package/dist/gemini/index.mjs +1 -1
- package/dist/index.cjs +69 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +50 -6
- package/dist/index.mjs +69 -26
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +1 -1
- package/dist/langchain/index.mjs +1 -1
- package/dist/openai/index.cjs +1 -1
- package/dist/openai/index.mjs +1 -1
- package/dist/openai-agents/index.cjs +655 -0
- package/dist/openai-agents/index.cjs.map +1 -0
- package/dist/openai-agents/index.d.ts +101 -0
- package/dist/openai-agents/index.mjs +652 -0
- package/dist/openai-agents/index.mjs.map +1 -0
- package/dist/otel/index.cjs +103 -5
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.d.ts +66 -5
- package/dist/otel/index.mjs +103 -6
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +1 -1
- package/dist/vercel/index.mjs +1 -1
- package/package.json +21 -2
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import 'uuid';
|
|
2
|
+
import '@posthog/core';
|
|
3
|
+
|
|
4
|
+
var version = "7.16.0";
|
|
5
|
+
|
|
6
|
+
// limit large outputs by truncating to 200kb (approx 200k bytes)
|
|
7
|
+
const MAX_OUTPUT_SIZE = 200000;
|
|
8
|
+
const STRING_FORMAT = 'utf8';
|
|
9
|
+
const withPrivacyMode = (client, privacyMode, input) => {
|
|
10
|
+
return client.privacy_mode || privacyMode ? null : input;
|
|
11
|
+
};
|
|
12
|
+
function toSafeString(input) {
|
|
13
|
+
if (input === undefined || input === null) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
if (typeof input === 'string') {
|
|
17
|
+
return input;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(input);
|
|
21
|
+
} catch {
|
|
22
|
+
console.warn('Failed to stringify input', input);
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const truncate = input => {
|
|
27
|
+
const str = toSafeString(input);
|
|
28
|
+
if (str === '') {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if we need to truncate and ensure STRING_FORMAT is respected
|
|
33
|
+
const encoder = new TextEncoder();
|
|
34
|
+
const buffer = encoder.encode(str);
|
|
35
|
+
if (buffer.length <= MAX_OUTPUT_SIZE) {
|
|
36
|
+
// Ensure STRING_FORMAT is respected
|
|
37
|
+
return new TextDecoder(STRING_FORMAT).decode(buffer);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Truncate the buffer and ensure a valid string is returned
|
|
41
|
+
const truncatedBuffer = buffer.slice(0, MAX_OUTPUT_SIZE);
|
|
42
|
+
// fatal: false means we get U+FFFD at the end if truncation broke the encoding
|
|
43
|
+
const decoder = new TextDecoder(STRING_FORMAT, {
|
|
44
|
+
fatal: false
|
|
45
|
+
});
|
|
46
|
+
let truncatedStr = decoder.decode(truncatedBuffer);
|
|
47
|
+
if (truncatedStr.endsWith('\uFFFD')) {
|
|
48
|
+
truncatedStr = truncatedStr.slice(0, -1);
|
|
49
|
+
}
|
|
50
|
+
return `${truncatedStr}... [truncated]`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Normalize OpenAI Responses API input items to include a `role` field.
|
|
55
|
+
* Items like `function_call` and `function_call_result` don't have a role,
|
|
56
|
+
* causing PostHog's trace viewer to default them to "user".
|
|
57
|
+
*/
|
|
58
|
+
function normalizeInputRoles(input) {
|
|
59
|
+
if (!Array.isArray(input)) {
|
|
60
|
+
return input;
|
|
61
|
+
}
|
|
62
|
+
return input.map(item => {
|
|
63
|
+
if (item && typeof item === 'object' && !('role' in item) && 'type' in item) {
|
|
64
|
+
if (item.type === 'function_call') {
|
|
65
|
+
return {
|
|
66
|
+
...item,
|
|
67
|
+
role: 'assistant'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (item.type === 'function_call_result') {
|
|
71
|
+
return {
|
|
72
|
+
...item,
|
|
73
|
+
role: 'tool'
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return item;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function ensureSerializable(obj) {
|
|
81
|
+
if (obj === null || obj === undefined) {
|
|
82
|
+
return obj;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
JSON.stringify(obj);
|
|
86
|
+
return obj;
|
|
87
|
+
} catch {
|
|
88
|
+
return String(obj);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function exceedsMaxOutputSize(value) {
|
|
92
|
+
if (value === null || value === undefined) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const serializedValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
97
|
+
return new TextEncoder().encode(serializedValue).length > MAX_OUTPUT_SIZE;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function parseIsoTimestamp(isoStr) {
|
|
103
|
+
if (!isoStr) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const ts = new Date(isoStr).getTime();
|
|
108
|
+
return isNaN(ts) ? null : ts / 1000;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A tracing processor that sends OpenAI Agents SDK traces to PostHog.
|
|
115
|
+
*
|
|
116
|
+
* Implements the TracingProcessor interface from the OpenAI Agents SDK
|
|
117
|
+
* and maps agent traces, spans, and generations to PostHog's LLM analytics events.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* import { PostHogTracingProcessor } from '@posthog/ai/openai-agents'
|
|
122
|
+
* import { addTraceProcessor } from '@openai/agents'
|
|
123
|
+
*
|
|
124
|
+
* const processor = new PostHogTracingProcessor({
|
|
125
|
+
* client: posthog,
|
|
126
|
+
* distinctId: 'user@example.com',
|
|
127
|
+
* })
|
|
128
|
+
* addTraceProcessor(processor)
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
class PostHogTracingProcessor {
|
|
132
|
+
_spanStartTimes = new Map();
|
|
133
|
+
_traceMetadata = new Map();
|
|
134
|
+
_maxTrackedEntries = 10000;
|
|
135
|
+
constructor(options) {
|
|
136
|
+
this._client = options.client;
|
|
137
|
+
this._distinctId = options.distinctId;
|
|
138
|
+
this._privacyMode = options.privacyMode ?? false;
|
|
139
|
+
this._groups = options.groups ?? {};
|
|
140
|
+
this._properties = options.properties ?? {};
|
|
141
|
+
}
|
|
142
|
+
_getDistinctId(trace) {
|
|
143
|
+
if (typeof this._distinctId === 'function') {
|
|
144
|
+
if (trace) {
|
|
145
|
+
const result = this._distinctId(trace);
|
|
146
|
+
if (result) {
|
|
147
|
+
return String(result);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
} else if (this._distinctId) {
|
|
152
|
+
return String(this._distinctId);
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
_withPrivacyMode(value) {
|
|
157
|
+
return withPrivacyMode(this._client, this._privacyMode, value);
|
|
158
|
+
}
|
|
159
|
+
_prepareCapturedValue(value) {
|
|
160
|
+
const serializableValue = ensureSerializable(value);
|
|
161
|
+
const boundedValue = exceedsMaxOutputSize(serializableValue) ? truncate(serializableValue) : serializableValue;
|
|
162
|
+
return this._withPrivacyMode(boundedValue);
|
|
163
|
+
}
|
|
164
|
+
_evictStaleEntries() {
|
|
165
|
+
if (this._spanStartTimes.size > this._maxTrackedEntries) {
|
|
166
|
+
const entries = [...this._spanStartTimes.entries()].sort((a, b) => a[1] - b[1]);
|
|
167
|
+
const toRemove = entries.slice(0, Math.floor(entries.length / 2));
|
|
168
|
+
for (const [key] of toRemove) {
|
|
169
|
+
this._spanStartTimes.delete(key);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (this._traceMetadata.size > this._maxTrackedEntries) {
|
|
173
|
+
const keys = [...this._traceMetadata.keys()];
|
|
174
|
+
const toRemove = keys.slice(0, Math.floor(keys.length / 2));
|
|
175
|
+
for (const key of toRemove) {
|
|
176
|
+
this._traceMetadata.delete(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
_captureEvent(event, properties, distinctId) {
|
|
181
|
+
try {
|
|
182
|
+
if (!this._client?.capture) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const finalProperties = {
|
|
186
|
+
...this._properties,
|
|
187
|
+
...properties
|
|
188
|
+
};
|
|
189
|
+
const eventMessage = {
|
|
190
|
+
distinctId: distinctId || 'unknown',
|
|
191
|
+
event,
|
|
192
|
+
properties: finalProperties,
|
|
193
|
+
groups: Object.keys(this._groups).length > 0 ? this._groups : undefined
|
|
194
|
+
};
|
|
195
|
+
this._client.capture(eventMessage);
|
|
196
|
+
} catch {
|
|
197
|
+
// Silently ignore capture errors
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
_baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties) {
|
|
201
|
+
const properties = {
|
|
202
|
+
$ai_lib: 'posthog-ai',
|
|
203
|
+
$ai_lib_version: version,
|
|
204
|
+
$ai_trace_id: traceId,
|
|
205
|
+
$ai_span_id: spanId,
|
|
206
|
+
$ai_parent_id: parentId,
|
|
207
|
+
$ai_provider: 'openai',
|
|
208
|
+
$ai_framework: 'openai-agents',
|
|
209
|
+
$ai_latency: latency,
|
|
210
|
+
...errorProperties
|
|
211
|
+
};
|
|
212
|
+
if (groupId) {
|
|
213
|
+
properties.$ai_group_id = groupId;
|
|
214
|
+
}
|
|
215
|
+
return properties;
|
|
216
|
+
}
|
|
217
|
+
_getErrorProperties(error) {
|
|
218
|
+
if (!error) {
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
const errorMessage = error.message || String(error);
|
|
222
|
+
let errorType = 'unknown';
|
|
223
|
+
if (errorMessage.includes('ModelBehaviorError')) {
|
|
224
|
+
errorType = 'model_behavior_error';
|
|
225
|
+
} else if (errorMessage.includes('UserError')) {
|
|
226
|
+
errorType = 'user_error';
|
|
227
|
+
} else if (errorMessage.includes('InputGuardrailTripwireTriggered')) {
|
|
228
|
+
errorType = 'input_guardrail_triggered';
|
|
229
|
+
} else if (errorMessage.includes('OutputGuardrailTripwireTriggered')) {
|
|
230
|
+
errorType = 'output_guardrail_triggered';
|
|
231
|
+
} else if (errorMessage.includes('MaxTurnsExceeded')) {
|
|
232
|
+
errorType = 'max_turns_exceeded';
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
$ai_is_error: true,
|
|
236
|
+
$ai_error: errorMessage,
|
|
237
|
+
$ai_error_type: errorType
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// --- TracingProcessor interface ---
|
|
242
|
+
|
|
243
|
+
async onTraceStart(trace) {
|
|
244
|
+
try {
|
|
245
|
+
this._evictStaleEntries();
|
|
246
|
+
const traceId = trace.traceId;
|
|
247
|
+
const traceName = trace.name;
|
|
248
|
+
const groupId = trace.groupId ?? null;
|
|
249
|
+
const metadata = trace.metadata;
|
|
250
|
+
const distinctId = this._getDistinctId(trace);
|
|
251
|
+
this._traceMetadata.set(traceId, {
|
|
252
|
+
name: traceName,
|
|
253
|
+
groupId,
|
|
254
|
+
metadata,
|
|
255
|
+
distinctId,
|
|
256
|
+
startTime: Date.now() / 1000
|
|
257
|
+
});
|
|
258
|
+
} catch {
|
|
259
|
+
// Silently ignore errors
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async onTraceEnd(trace) {
|
|
263
|
+
try {
|
|
264
|
+
const traceId = trace.traceId;
|
|
265
|
+
const traceInfo = this._traceMetadata.get(traceId);
|
|
266
|
+
this._traceMetadata.delete(traceId);
|
|
267
|
+
const traceName = traceInfo?.name ?? trace.name;
|
|
268
|
+
const groupId = traceInfo?.groupId ?? trace.groupId ?? null;
|
|
269
|
+
const metadata = traceInfo?.metadata ?? trace.metadata;
|
|
270
|
+
const distinctId = traceInfo?.distinctId ?? this._getDistinctId(trace);
|
|
271
|
+
const startTime = traceInfo?.startTime;
|
|
272
|
+
const latency = startTime != null ? Date.now() / 1000 - startTime : undefined;
|
|
273
|
+
const properties = {
|
|
274
|
+
$ai_lib: 'posthog-ai',
|
|
275
|
+
$ai_lib_version: version,
|
|
276
|
+
$ai_trace_id: traceId,
|
|
277
|
+
$ai_trace_name: traceName,
|
|
278
|
+
$ai_provider: 'openai',
|
|
279
|
+
$ai_framework: 'openai-agents'
|
|
280
|
+
};
|
|
281
|
+
if (latency != null) {
|
|
282
|
+
properties.$ai_latency = latency;
|
|
283
|
+
}
|
|
284
|
+
if (groupId) {
|
|
285
|
+
properties.$ai_group_id = groupId;
|
|
286
|
+
}
|
|
287
|
+
if (metadata && Object.keys(metadata).length > 0) {
|
|
288
|
+
properties.$ai_trace_metadata = this._prepareCapturedValue(metadata);
|
|
289
|
+
}
|
|
290
|
+
if (distinctId == null) {
|
|
291
|
+
properties.$process_person_profile = false;
|
|
292
|
+
}
|
|
293
|
+
this._captureEvent('$ai_trace', properties, distinctId ?? traceId);
|
|
294
|
+
} catch {
|
|
295
|
+
// Silently ignore errors
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async onSpanStart(span) {
|
|
299
|
+
try {
|
|
300
|
+
this._evictStaleEntries();
|
|
301
|
+
this._spanStartTimes.set(span.spanId, Date.now() / 1000);
|
|
302
|
+
} catch {
|
|
303
|
+
// Silently ignore errors
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async onSpanEnd(span) {
|
|
307
|
+
try {
|
|
308
|
+
const spanId = span.spanId;
|
|
309
|
+
const traceId = span.traceId;
|
|
310
|
+
const parentId = span.parentId;
|
|
311
|
+
const spanData = span.spanData;
|
|
312
|
+
|
|
313
|
+
// Calculate latency
|
|
314
|
+
const startTime = this._spanStartTimes.get(spanId);
|
|
315
|
+
this._spanStartTimes.delete(spanId);
|
|
316
|
+
let latency;
|
|
317
|
+
if (startTime != null) {
|
|
318
|
+
latency = Date.now() / 1000 - startTime;
|
|
319
|
+
} else {
|
|
320
|
+
const started = parseIsoTimestamp(span.startedAt);
|
|
321
|
+
const ended = parseIsoTimestamp(span.endedAt);
|
|
322
|
+
latency = started != null && ended != null ? ended - started : 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Get distinct ID from trace metadata
|
|
326
|
+
const traceInfo = this._traceMetadata.get(traceId);
|
|
327
|
+
const userDistinctId = traceInfo?.distinctId ?? this._getDistinctId(null);
|
|
328
|
+
|
|
329
|
+
// Get group_id from trace metadata
|
|
330
|
+
const groupId = traceInfo?.groupId ?? null;
|
|
331
|
+
|
|
332
|
+
// Get error properties
|
|
333
|
+
const errorProperties = this._getErrorProperties(span.error);
|
|
334
|
+
|
|
335
|
+
// Personless mode: no user-provided distinct_id, fallback to trace_id
|
|
336
|
+
if (userDistinctId == null) {
|
|
337
|
+
errorProperties.$process_person_profile = false;
|
|
338
|
+
}
|
|
339
|
+
const distinctId = userDistinctId ?? traceId;
|
|
340
|
+
|
|
341
|
+
// Dispatch based on span data type
|
|
342
|
+
switch (spanData.type) {
|
|
343
|
+
case 'generation':
|
|
344
|
+
this._handleGenerationSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
345
|
+
break;
|
|
346
|
+
case 'response':
|
|
347
|
+
this._handleResponseSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
348
|
+
break;
|
|
349
|
+
case 'function':
|
|
350
|
+
this._handleFunctionSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
351
|
+
break;
|
|
352
|
+
case 'agent':
|
|
353
|
+
this._handleAgentSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
354
|
+
break;
|
|
355
|
+
case 'handoff':
|
|
356
|
+
this._handleHandoffSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
357
|
+
break;
|
|
358
|
+
case 'guardrail':
|
|
359
|
+
this._handleGuardrailSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
360
|
+
break;
|
|
361
|
+
case 'custom':
|
|
362
|
+
this._handleCustomSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
363
|
+
break;
|
|
364
|
+
case 'transcription':
|
|
365
|
+
case 'speech':
|
|
366
|
+
case 'speech_group':
|
|
367
|
+
this._handleAudioSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
368
|
+
break;
|
|
369
|
+
case 'mcp_tools':
|
|
370
|
+
this._handleMcpSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
371
|
+
break;
|
|
372
|
+
default:
|
|
373
|
+
this._handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
// Silently ignore errors
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async shutdown() {
|
|
381
|
+
try {
|
|
382
|
+
this._spanStartTimes.clear();
|
|
383
|
+
this._traceMetadata.clear();
|
|
384
|
+
if (typeof this._client?.flush === 'function') {
|
|
385
|
+
await this._client.flush();
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
// Silently ignore errors
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async forceFlush() {
|
|
392
|
+
try {
|
|
393
|
+
if (typeof this._client?.flush === 'function') {
|
|
394
|
+
await this._client.flush();
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
// Silently ignore errors
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// --- Span handlers ---
|
|
402
|
+
|
|
403
|
+
_handleGenerationSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
404
|
+
const usage = spanData.usage ?? {};
|
|
405
|
+
const inputTokens = usage.input_tokens || usage.prompt_tokens || 0;
|
|
406
|
+
const outputTokens = usage.output_tokens || usage.completion_tokens || 0;
|
|
407
|
+
const modelConfig = spanData.model_config ?? {};
|
|
408
|
+
const modelParams = {};
|
|
409
|
+
for (const param of ['temperature', 'max_tokens', 'top_p', 'frequency_penalty', 'presence_penalty']) {
|
|
410
|
+
if (param in modelConfig) {
|
|
411
|
+
modelParams[param] = modelConfig[param];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const properties = {
|
|
415
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
416
|
+
$ai_model: spanData.model,
|
|
417
|
+
$ai_model_parameters: Object.keys(modelParams).length > 0 ? modelParams : null,
|
|
418
|
+
$ai_input: this._prepareCapturedValue(normalizeInputRoles(spanData.input)),
|
|
419
|
+
$ai_output_choices: this._prepareCapturedValue(spanData.output),
|
|
420
|
+
$ai_input_tokens: inputTokens,
|
|
421
|
+
$ai_output_tokens: outputTokens,
|
|
422
|
+
$ai_total_tokens: inputTokens + outputTokens
|
|
423
|
+
};
|
|
424
|
+
if (usage.details) {
|
|
425
|
+
const details = usage.details;
|
|
426
|
+
if (details.reasoning_tokens) {
|
|
427
|
+
properties.$ai_reasoning_tokens = details.reasoning_tokens;
|
|
428
|
+
}
|
|
429
|
+
if (details.cache_read_input_tokens) {
|
|
430
|
+
properties.$ai_cache_read_input_tokens = details.cache_read_input_tokens;
|
|
431
|
+
}
|
|
432
|
+
if (details.cache_creation_input_tokens) {
|
|
433
|
+
properties.$ai_cache_creation_input_tokens = details.cache_creation_input_tokens;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Also check top-level usage for reasoning/cache tokens (flexible schema)
|
|
438
|
+
if (usage.reasoning_tokens) {
|
|
439
|
+
properties.$ai_reasoning_tokens = usage.reasoning_tokens;
|
|
440
|
+
}
|
|
441
|
+
if (usage.cache_read_input_tokens) {
|
|
442
|
+
properties.$ai_cache_read_input_tokens = usage.cache_read_input_tokens;
|
|
443
|
+
}
|
|
444
|
+
if (usage.cache_creation_input_tokens) {
|
|
445
|
+
properties.$ai_cache_creation_input_tokens = usage.cache_creation_input_tokens;
|
|
446
|
+
}
|
|
447
|
+
this._captureEvent('$ai_generation', properties, distinctId);
|
|
448
|
+
}
|
|
449
|
+
_handleResponseSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
450
|
+
// The OpenAI Agents SDK exposes these underscored fields for non-OpenAI tracing providers.
|
|
451
|
+
// Treat them as best-effort and avoid assuming they are always present.
|
|
452
|
+
const responseSpanData = spanData;
|
|
453
|
+
const response = responseSpanData._response;
|
|
454
|
+
const responseId = spanData.response_id ?? response?.id;
|
|
455
|
+
|
|
456
|
+
// Extract usage from response
|
|
457
|
+
const usage = response?.usage ?? {};
|
|
458
|
+
const inputTokens = usage?.input_tokens ?? 0;
|
|
459
|
+
const outputTokens = usage?.output_tokens ?? 0;
|
|
460
|
+
|
|
461
|
+
// Extract model from response
|
|
462
|
+
const model = response?.model;
|
|
463
|
+
const properties = {
|
|
464
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
465
|
+
$ai_model: model,
|
|
466
|
+
$ai_response_id: responseId,
|
|
467
|
+
$ai_input: this._prepareCapturedValue(normalizeInputRoles(responseSpanData._input)),
|
|
468
|
+
$ai_input_tokens: inputTokens,
|
|
469
|
+
$ai_output_tokens: outputTokens,
|
|
470
|
+
$ai_total_tokens: inputTokens + outputTokens
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Extract output from response
|
|
474
|
+
if (response?.output) {
|
|
475
|
+
properties.$ai_output_choices = this._prepareCapturedValue(response.output);
|
|
476
|
+
}
|
|
477
|
+
this._captureEvent('$ai_generation', properties, distinctId);
|
|
478
|
+
}
|
|
479
|
+
_handleFunctionSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
480
|
+
const properties = {
|
|
481
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
482
|
+
$ai_span_name: spanData.name,
|
|
483
|
+
$ai_span_type: 'tool',
|
|
484
|
+
$ai_input_state: this._prepareCapturedValue(spanData.input),
|
|
485
|
+
$ai_output_state: this._prepareCapturedValue(spanData.output)
|
|
486
|
+
};
|
|
487
|
+
if (spanData.mcp_data) {
|
|
488
|
+
properties.$ai_mcp_data = this._prepareCapturedValue(spanData.mcp_data);
|
|
489
|
+
}
|
|
490
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
491
|
+
}
|
|
492
|
+
_handleAgentSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
493
|
+
const properties = {
|
|
494
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
495
|
+
$ai_span_name: spanData.name,
|
|
496
|
+
$ai_span_type: 'agent'
|
|
497
|
+
};
|
|
498
|
+
if (spanData.handoffs) {
|
|
499
|
+
properties.$ai_agent_handoffs = spanData.handoffs;
|
|
500
|
+
}
|
|
501
|
+
if (spanData.tools) {
|
|
502
|
+
properties.$ai_agent_tools = spanData.tools;
|
|
503
|
+
}
|
|
504
|
+
if (spanData.output_type) {
|
|
505
|
+
properties.$ai_agent_output_type = spanData.output_type;
|
|
506
|
+
}
|
|
507
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
508
|
+
}
|
|
509
|
+
_handleHandoffSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
510
|
+
const properties = {
|
|
511
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
512
|
+
$ai_span_name: `${spanData.from_agent} -> ${spanData.to_agent}`,
|
|
513
|
+
$ai_span_type: 'handoff',
|
|
514
|
+
$ai_handoff_from_agent: spanData.from_agent,
|
|
515
|
+
$ai_handoff_to_agent: spanData.to_agent
|
|
516
|
+
};
|
|
517
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
518
|
+
}
|
|
519
|
+
_handleGuardrailSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
520
|
+
const properties = {
|
|
521
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
522
|
+
$ai_span_name: spanData.name,
|
|
523
|
+
$ai_span_type: 'guardrail',
|
|
524
|
+
$ai_guardrail_triggered: spanData.triggered
|
|
525
|
+
};
|
|
526
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
527
|
+
}
|
|
528
|
+
_handleCustomSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
529
|
+
const properties = {
|
|
530
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
531
|
+
$ai_span_name: spanData.name,
|
|
532
|
+
$ai_span_type: 'custom',
|
|
533
|
+
$ai_custom_data: this._prepareCapturedValue(spanData.data)
|
|
534
|
+
};
|
|
535
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
536
|
+
}
|
|
537
|
+
_handleAudioSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
538
|
+
const spanType = spanData.type;
|
|
539
|
+
const properties = {
|
|
540
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
541
|
+
$ai_span_name: spanType,
|
|
542
|
+
$ai_span_type: spanType
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Add model info if available
|
|
546
|
+
if ('model' in spanData && spanData.model) {
|
|
547
|
+
properties.$ai_model = spanData.model;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Add model config if available
|
|
551
|
+
if ('model_config' in spanData && spanData.model_config) {
|
|
552
|
+
properties.$ai_model_config = this._prepareCapturedValue(spanData.model_config);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Add audio format info
|
|
556
|
+
if (spanData.type === 'transcription') {
|
|
557
|
+
const transcription = spanData;
|
|
558
|
+
if (transcription.input?.format) {
|
|
559
|
+
properties.$ai_audio_input_format = transcription.input.format;
|
|
560
|
+
}
|
|
561
|
+
// Transcription output is text
|
|
562
|
+
if (transcription.output) {
|
|
563
|
+
properties.$ai_output_state = this._prepareCapturedValue(transcription.output);
|
|
564
|
+
}
|
|
565
|
+
} else if (spanData.type === 'speech') {
|
|
566
|
+
const speech = spanData;
|
|
567
|
+
if (speech.output?.format) {
|
|
568
|
+
properties.$ai_audio_output_format = speech.output.format;
|
|
569
|
+
}
|
|
570
|
+
// Text input for TTS
|
|
571
|
+
if (speech.input) {
|
|
572
|
+
properties.$ai_input = this._prepareCapturedValue(speech.input);
|
|
573
|
+
}
|
|
574
|
+
} else if (spanData.type === 'speech_group') {
|
|
575
|
+
const speechGroup = spanData;
|
|
576
|
+
if (speechGroup.input) {
|
|
577
|
+
properties.$ai_input = this._prepareCapturedValue(speechGroup.input);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
581
|
+
}
|
|
582
|
+
_handleMcpSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
583
|
+
const properties = {
|
|
584
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
585
|
+
$ai_span_name: `mcp:${spanData.server}`,
|
|
586
|
+
$ai_span_type: 'mcp_tools',
|
|
587
|
+
$ai_mcp_server: spanData.server,
|
|
588
|
+
$ai_mcp_tools: this._prepareCapturedValue(spanData.result)
|
|
589
|
+
};
|
|
590
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
591
|
+
}
|
|
592
|
+
_handleGenericSpan(spanData, traceId, spanId, parentId, latency, distinctId, groupId, errorProperties) {
|
|
593
|
+
const spanType = spanData.type || 'unknown';
|
|
594
|
+
const properties = {
|
|
595
|
+
...this._baseProperties(traceId, spanId, parentId, latency, groupId, errorProperties),
|
|
596
|
+
$ai_span_name: spanType,
|
|
597
|
+
$ai_span_type: spanType
|
|
598
|
+
};
|
|
599
|
+
this._captureEvent('$ai_span', properties, distinctId);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* One-liner to instrument OpenAI Agents SDK with PostHog tracing.
|
|
605
|
+
*
|
|
606
|
+
* This registers a PostHogTracingProcessor with the OpenAI Agents SDK,
|
|
607
|
+
* automatically capturing traces, spans, and LLM generations.
|
|
608
|
+
*
|
|
609
|
+
* @param options - Configuration options
|
|
610
|
+
* @returns The registered processor instance
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```typescript
|
|
614
|
+
* import { instrument } from '@posthog/ai/openai-agents'
|
|
615
|
+
* import PostHog from 'posthog-node'
|
|
616
|
+
*
|
|
617
|
+
* const phClient = new PostHog('<API_KEY>')
|
|
618
|
+
*
|
|
619
|
+
* // Simple setup — await before running agents
|
|
620
|
+
* await instrument({ client: phClient, distinctId: 'user@example.com' })
|
|
621
|
+
*
|
|
622
|
+
* // With dynamic distinct ID
|
|
623
|
+
* await instrument({
|
|
624
|
+
* client: phClient,
|
|
625
|
+
* distinctId: (trace) => trace.metadata?.userId,
|
|
626
|
+
* privacyMode: true,
|
|
627
|
+
* properties: { environment: 'production' },
|
|
628
|
+
* })
|
|
629
|
+
*
|
|
630
|
+
* // Now run agents as normal - traces automatically sent to PostHog
|
|
631
|
+
* import { Agent, run } from '@openai/agents'
|
|
632
|
+
* const agent = new Agent({ name: 'Assistant', instructions: 'You are helpful.' })
|
|
633
|
+
* const result = await run(agent, 'Hello!')
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
async function instrument(options) {
|
|
637
|
+
const {
|
|
638
|
+
addTraceProcessor
|
|
639
|
+
} = await import('@openai/agents-core');
|
|
640
|
+
const processor = new PostHogTracingProcessor({
|
|
641
|
+
client: options.client,
|
|
642
|
+
distinctId: options.distinctId,
|
|
643
|
+
privacyMode: options.privacyMode,
|
|
644
|
+
groups: options.groups,
|
|
645
|
+
properties: options.properties
|
|
646
|
+
});
|
|
647
|
+
addTraceProcessor(processor);
|
|
648
|
+
return processor;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export { PostHogTracingProcessor, instrument };
|
|
652
|
+
//# sourceMappingURL=index.mjs.map
|