@mastra/posthog 1.2.0 → 1.2.1
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/CHANGELOG.md +16 -0
- package/dist/index.cjs +412 -431
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +411 -429
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.cjs
CHANGED
|
@@ -1,437 +1,418 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _mastra_core_observability = require("@mastra/core/observability");
|
|
3
|
+
let _mastra_observability = require("@mastra/observability");
|
|
4
|
+
let posthog_node = require("posthog-node");
|
|
5
|
+
//#region src/tracing.ts
|
|
6
|
+
/**
|
|
7
|
+
* Formats UsageStats to PostHog's expected property format.
|
|
8
|
+
*
|
|
9
|
+
* Pass through gross input token counts with cache fields as subsets.
|
|
10
|
+
* PostHog subtracts cache tokens when computing costs for non-Anthropic
|
|
11
|
+
* providers and detects Anthropic-style exclusive reporting on its own.
|
|
12
|
+
*
|
|
13
|
+
* @param usage - The UsageStats from span attributes
|
|
14
|
+
* @returns PostHog-formatted usage properties
|
|
15
|
+
*/
|
|
8
16
|
function formatUsageMetrics(usage) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
if (usage.inputDetails?.cacheWrite !== void 0) {
|
|
18
|
-
props.$ai_cache_creation_input_tokens = usage.inputDetails.cacheWrite;
|
|
19
|
-
}
|
|
20
|
-
if (usage.outputTokens !== void 0) {
|
|
21
|
-
props.$ai_output_tokens = usage.outputTokens;
|
|
22
|
-
}
|
|
23
|
-
return props;
|
|
17
|
+
if (!usage) return {};
|
|
18
|
+
const props = {};
|
|
19
|
+
if (usage.inputTokens !== void 0) props.$ai_input_tokens = usage.inputTokens;
|
|
20
|
+
if (usage.inputDetails?.cacheRead !== void 0) props.$ai_cache_read_input_tokens = usage.inputDetails.cacheRead;
|
|
21
|
+
if (usage.inputDetails?.cacheWrite !== void 0) props.$ai_cache_creation_input_tokens = usage.inputDetails.cacheWrite;
|
|
22
|
+
if (usage.outputTokens !== void 0) props.$ai_output_tokens = usage.outputTokens;
|
|
23
|
+
return props;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
var PosthogExporter = class
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (typeof data === "object" && data !== null) {
|
|
414
|
-
return `[Non-serializable ${data.constructor?.name || "Object"}]`;
|
|
415
|
-
}
|
|
416
|
-
return String(data);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Force flush any buffered data to PostHog without shutting down.
|
|
421
|
-
*/
|
|
422
|
-
async _flush() {
|
|
423
|
-
if (this.#client) {
|
|
424
|
-
await this.#client.flush();
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
async _postShutdown() {
|
|
428
|
-
if (this.#client) {
|
|
429
|
-
await this.#client.shutdown();
|
|
430
|
-
}
|
|
431
|
-
}
|
|
25
|
+
const DISTINCT_ID = "distinctId";
|
|
26
|
+
var PosthogExporter = class PosthogExporter extends _mastra_observability.TrackingExporter {
|
|
27
|
+
name = "posthog";
|
|
28
|
+
#client;
|
|
29
|
+
static SERVERLESS_FLUSH_AT = 10;
|
|
30
|
+
static SERVERLESS_FLUSH_INTERVAL = 2e3;
|
|
31
|
+
static DEFAULT_FLUSH_AT = 20;
|
|
32
|
+
static DEFAULT_FLUSH_INTERVAL = 1e4;
|
|
33
|
+
constructor(config = {}) {
|
|
34
|
+
const apiKey = config.apiKey ?? process.env.POSTHOG_API_KEY;
|
|
35
|
+
super({
|
|
36
|
+
...config,
|
|
37
|
+
apiKey
|
|
38
|
+
});
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
this.setDisabled("Missing required API key. Set POSTHOG_API_KEY environment variable or pass apiKey in config.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const clientConfig = this.buildClientConfig(this.config);
|
|
44
|
+
this.#client = new posthog_node.PostHog(apiKey, clientConfig);
|
|
45
|
+
const message = config.serverless ?? false ? "PostHog exporter initialized in serverless mode" : "PostHog exporter initialized";
|
|
46
|
+
this.logger.debug(message, config);
|
|
47
|
+
}
|
|
48
|
+
buildClientConfig(config) {
|
|
49
|
+
const isServerless = config.serverless ?? false;
|
|
50
|
+
const flushAt = config.flushAt ?? (isServerless ? PosthogExporter.SERVERLESS_FLUSH_AT : PosthogExporter.DEFAULT_FLUSH_AT);
|
|
51
|
+
const flushInterval = config.flushInterval ?? (isServerless ? PosthogExporter.SERVERLESS_FLUSH_INTERVAL : PosthogExporter.DEFAULT_FLUSH_INTERVAL);
|
|
52
|
+
const host = config.host || process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
53
|
+
if (!config.host && !process.env.POSTHOG_HOST) this.logger.info("No PostHog host specified, using US default (https://us.i.posthog.com). For EU region, set `host: \"https://eu.i.posthog.com\"` in config or POSTHOG_HOST env var. For self-hosted, provide your instance URL.");
|
|
54
|
+
return {
|
|
55
|
+
host,
|
|
56
|
+
flushAt,
|
|
57
|
+
flushInterval,
|
|
58
|
+
privacyMode: config.enablePrivacyMode
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
skipBuildRootTask = true;
|
|
62
|
+
async _buildRoot(_args) {
|
|
63
|
+
throw new Error("Method not implemented.");
|
|
64
|
+
}
|
|
65
|
+
skipCachingEventSpans = true;
|
|
66
|
+
async _buildEvent(args) {
|
|
67
|
+
const { span, traceData } = args;
|
|
68
|
+
const eventName = this.mapToPostHogEvent(span.type);
|
|
69
|
+
const distinctId = this.getDistinctId(span, traceData);
|
|
70
|
+
const properties = this.buildEventProperties(span, 0);
|
|
71
|
+
this.#client?.capture(this.withGroups({
|
|
72
|
+
distinctId,
|
|
73
|
+
event: eventName,
|
|
74
|
+
properties,
|
|
75
|
+
timestamp: span.endTime ? new Date(span.endTime) : /* @__PURE__ */ new Date()
|
|
76
|
+
}));
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
async _buildSpan(args) {
|
|
80
|
+
const { span, traceData } = args;
|
|
81
|
+
if (!traceData.hasExtraValue(DISTINCT_ID)) {
|
|
82
|
+
const userId = span.metadata?.userId;
|
|
83
|
+
if (userId) traceData.setExtraValue(DISTINCT_ID, String(userId));
|
|
84
|
+
}
|
|
85
|
+
return span;
|
|
86
|
+
}
|
|
87
|
+
skipSpanUpdateEvents = true;
|
|
88
|
+
_updateSpan(_args) {
|
|
89
|
+
throw new Error("Method not implemented.");
|
|
90
|
+
}
|
|
91
|
+
async _finishSpan(args) {
|
|
92
|
+
const { span, traceData } = args;
|
|
93
|
+
const cachedSpan = traceData.getSpan({ spanId: span.id });
|
|
94
|
+
const mergedSpan = !span.input && cachedSpan?.input ? {
|
|
95
|
+
...span,
|
|
96
|
+
input: cachedSpan.input
|
|
97
|
+
} : span;
|
|
98
|
+
const eventMessage = this.buildEventMessage({
|
|
99
|
+
span: mergedSpan,
|
|
100
|
+
traceData
|
|
101
|
+
});
|
|
102
|
+
this.#client?.capture(this.withGroups(eventMessage));
|
|
103
|
+
}
|
|
104
|
+
async _abortSpan(args) {
|
|
105
|
+
const { span, reason, traceData } = args;
|
|
106
|
+
span.errorInfo = reason;
|
|
107
|
+
const eventMessage = this.buildEventMessage({
|
|
108
|
+
span,
|
|
109
|
+
traceData
|
|
110
|
+
});
|
|
111
|
+
this.#client?.capture(this.withGroups(eventMessage));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Forward feedback recorded via `addFeedback()` to PostHog as a native
|
|
115
|
+
* `$ai_feedback` event, shown as "User feedback" on the linked trace in
|
|
116
|
+
* PostHog's AI observability UI.
|
|
117
|
+
*/
|
|
118
|
+
async onFeedbackEvent(event) {
|
|
119
|
+
if (!this.#client) return;
|
|
120
|
+
const { feedback } = event;
|
|
121
|
+
if (!feedback.traceId) {
|
|
122
|
+
this.logger.debug("PostHog exporter: dropping feedback with no traceId; PostHog requires $ai_trace_id", { feedbackId: feedback.feedbackId });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const properties = {
|
|
126
|
+
...this.extractCustomMetadata(feedback.metadata),
|
|
127
|
+
$ai_trace_id: feedback.traceId,
|
|
128
|
+
$ai_feedback_text: feedback.comment ?? String(feedback.value),
|
|
129
|
+
feedback_id: feedback.feedbackId,
|
|
130
|
+
feedback_type: feedback.feedbackType,
|
|
131
|
+
feedback_value: feedback.value
|
|
132
|
+
};
|
|
133
|
+
const feedbackSource = feedback.feedbackSource ?? feedback.source;
|
|
134
|
+
if (feedbackSource) properties.feedback_source = feedbackSource;
|
|
135
|
+
if (feedback.spanId) properties.span_id = feedback.spanId;
|
|
136
|
+
if (feedback.sourceId) properties.source_id = feedback.sourceId;
|
|
137
|
+
if (feedback.metadata?.sessionId) properties.$ai_session_id = feedback.metadata.sessionId;
|
|
138
|
+
try {
|
|
139
|
+
this.#client.capture(this.withGroups({
|
|
140
|
+
distinctId: this.getFeedbackDistinctId(feedback),
|
|
141
|
+
event: "$ai_feedback",
|
|
142
|
+
properties,
|
|
143
|
+
timestamp: new Date(feedback.timestamp)
|
|
144
|
+
}));
|
|
145
|
+
} catch (err) {
|
|
146
|
+
this.logger.error("PostHog exporter: failed to submit feedback", {
|
|
147
|
+
error: err,
|
|
148
|
+
traceId: feedback.traceId,
|
|
149
|
+
feedbackId: feedback.feedbackId
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
getFeedbackDistinctId(feedback) {
|
|
154
|
+
const userId = feedback.feedbackUserId ?? feedback.userId ?? feedback.metadata?.userId;
|
|
155
|
+
if (userId) return String(userId);
|
|
156
|
+
return this.config.defaultDistinctId ?? "anonymous";
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* PostHog group analytics are keyed off the top-level `groups` field on the
|
|
160
|
+
* capture call. The Node SDK derives the event's `$groups` from that field and
|
|
161
|
+
* overwrites any property-level `$groups`, so group metadata carried in
|
|
162
|
+
* properties is dropped unless it is mirrored here.
|
|
163
|
+
*/
|
|
164
|
+
withGroups(message) {
|
|
165
|
+
const groups = message.properties?.$groups;
|
|
166
|
+
if (groups && typeof groups === "object" && !Array.isArray(groups)) return {
|
|
167
|
+
...message,
|
|
168
|
+
groups
|
|
169
|
+
};
|
|
170
|
+
return message;
|
|
171
|
+
}
|
|
172
|
+
buildEventMessage(args) {
|
|
173
|
+
const { span, traceData } = args;
|
|
174
|
+
const endTime = span.endTime ? this.toDate(span.endTime).getTime() : Date.now();
|
|
175
|
+
const distinctId = this.getDistinctId(span, traceData);
|
|
176
|
+
if (span.isRootSpan) return this.buildRootEventMessage({
|
|
177
|
+
span,
|
|
178
|
+
distinctId,
|
|
179
|
+
endTime
|
|
180
|
+
});
|
|
181
|
+
else return this.buildChildEventMessage({
|
|
182
|
+
span,
|
|
183
|
+
distinctId,
|
|
184
|
+
endTime,
|
|
185
|
+
traceData
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Capture an explicit $ai_trace event for root spans.
|
|
190
|
+
* This gives us control over trace-level metadata like name and tags,
|
|
191
|
+
* rather than relying on PostHog's pseudo-trace auto-creation.
|
|
192
|
+
*/
|
|
193
|
+
buildRootEventMessage(args) {
|
|
194
|
+
const { span, distinctId, endTime } = args;
|
|
195
|
+
const traceProperties = {
|
|
196
|
+
$ai_trace_id: span.traceId,
|
|
197
|
+
$ai_span_name: span.name,
|
|
198
|
+
$ai_is_error: !!span.errorInfo
|
|
199
|
+
};
|
|
200
|
+
if (span.metadata?.sessionId) traceProperties.$ai_session_id = span.metadata.sessionId;
|
|
201
|
+
if (span.input) traceProperties.$ai_input_state = span.input;
|
|
202
|
+
if (span.output) traceProperties.$ai_output_state = span.output;
|
|
203
|
+
if (span.errorInfo) traceProperties.$ai_error = {
|
|
204
|
+
message: span.errorInfo.message,
|
|
205
|
+
...span.errorInfo.id && { id: span.errorInfo.id },
|
|
206
|
+
...span.errorInfo.category && { category: span.errorInfo.category }
|
|
207
|
+
};
|
|
208
|
+
if (span.tags?.length) for (const tag of span.tags) traceProperties[tag] = true;
|
|
209
|
+
const { userId, sessionId, ...customMetadata } = span.metadata ?? {};
|
|
210
|
+
Object.assign(traceProperties, customMetadata);
|
|
211
|
+
return {
|
|
212
|
+
distinctId,
|
|
213
|
+
event: "$ai_trace",
|
|
214
|
+
properties: traceProperties,
|
|
215
|
+
timestamp: new Date(endTime)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
buildChildEventMessage(args) {
|
|
219
|
+
const { span, distinctId, endTime, traceData } = args;
|
|
220
|
+
const eventName = this.mapToPostHogEvent(span.type);
|
|
221
|
+
const latency = (endTime - span.startTime.getTime()) / 1e3;
|
|
222
|
+
const parentIsRootSpan = this.isParentRootSpan(span, traceData);
|
|
223
|
+
return {
|
|
224
|
+
distinctId,
|
|
225
|
+
event: eventName,
|
|
226
|
+
properties: this.buildEventProperties(span, latency, parentIsRootSpan),
|
|
227
|
+
timestamp: new Date(endTime)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
toDate(timestamp) {
|
|
231
|
+
return timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
232
|
+
}
|
|
233
|
+
mapToPostHogEvent(spanType) {
|
|
234
|
+
if (spanType == _mastra_core_observability.SpanType.MODEL_GENERATION) return "$ai_generation";
|
|
235
|
+
return "$ai_span";
|
|
236
|
+
}
|
|
237
|
+
getDistinctId(span, traceData) {
|
|
238
|
+
if (span.metadata?.userId) return String(span.metadata.userId);
|
|
239
|
+
if (traceData?.hasExtraValue(DISTINCT_ID)) return String(traceData.getExtraValue(DISTINCT_ID));
|
|
240
|
+
if (this.config.defaultDistinctId) return this.config.defaultDistinctId;
|
|
241
|
+
return "anonymous";
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Check if the parent of this span is the root span.
|
|
245
|
+
* We need this because we don't create $ai_span for root spans,
|
|
246
|
+
* so children of root spans should use $ai_trace_id as their $ai_parent_id.
|
|
247
|
+
*/
|
|
248
|
+
isParentRootSpan(span, traceData) {
|
|
249
|
+
if (!span.parentSpanId) return false;
|
|
250
|
+
const parentCache = traceData.getSpan({ spanId: span.parentSpanId });
|
|
251
|
+
if (parentCache) return parentCache.isRootSpan;
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
buildEventProperties(span, latency, parentIsRootSpan = false) {
|
|
255
|
+
const baseProperties = {
|
|
256
|
+
$ai_trace_id: span.traceId,
|
|
257
|
+
$ai_latency: latency,
|
|
258
|
+
$ai_is_error: !!span.errorInfo
|
|
259
|
+
};
|
|
260
|
+
if (span.parentSpanId) baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;
|
|
261
|
+
if (span.metadata?.sessionId) baseProperties.$ai_session_id = span.metadata.sessionId;
|
|
262
|
+
if (span.isRootSpan && span.tags?.length) for (const tag of span.tags) baseProperties[tag] = true;
|
|
263
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) {
|
|
264
|
+
baseProperties.$ai_generation_id = span.id;
|
|
265
|
+
return {
|
|
266
|
+
...baseProperties,
|
|
267
|
+
...this.buildGenerationProperties(span)
|
|
268
|
+
};
|
|
269
|
+
} else {
|
|
270
|
+
baseProperties.$ai_span_id = span.id;
|
|
271
|
+
baseProperties.$ai_span_name = span.name;
|
|
272
|
+
return {
|
|
273
|
+
...baseProperties,
|
|
274
|
+
...this.buildSpanProperties(span)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
extractErrorProperties(errorInfo) {
|
|
279
|
+
if (!errorInfo) return {};
|
|
280
|
+
const props = { error_message: errorInfo.message };
|
|
281
|
+
if (errorInfo.id) props.error_id = errorInfo.id;
|
|
282
|
+
if (errorInfo.category) props.error_category = errorInfo.category;
|
|
283
|
+
return props;
|
|
284
|
+
}
|
|
285
|
+
extractCustomMetadata(metadata) {
|
|
286
|
+
const { userId, sessionId, ...customMetadata } = metadata ?? {};
|
|
287
|
+
return customMetadata;
|
|
288
|
+
}
|
|
289
|
+
buildGenerationProperties(span) {
|
|
290
|
+
const props = {};
|
|
291
|
+
const attrs = span.attributes ?? {};
|
|
292
|
+
props.$ai_model = attrs.model || "unknown-model";
|
|
293
|
+
props.$ai_provider = attrs.provider || "unknown-provider";
|
|
294
|
+
if (span.input) props.$ai_input = this.formatMessages(span.input, "user");
|
|
295
|
+
if (span.output) props.$ai_output_choices = this.formatMessages(span.output, "assistant");
|
|
296
|
+
Object.assign(props, formatUsageMetrics(attrs.usage));
|
|
297
|
+
if (attrs.parameters) {
|
|
298
|
+
if (attrs.parameters.temperature !== void 0) props.$ai_temperature = attrs.parameters.temperature;
|
|
299
|
+
if (attrs.parameters.maxOutputTokens !== void 0) props.$ai_max_tokens = attrs.parameters.maxOutputTokens;
|
|
300
|
+
}
|
|
301
|
+
if (attrs.streaming !== void 0) props.$ai_stream = attrs.streaming;
|
|
302
|
+
return {
|
|
303
|
+
...props,
|
|
304
|
+
...this.extractErrorProperties(span.errorInfo),
|
|
305
|
+
...this.extractCustomMetadata(span.metadata)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
buildSpanProperties(span) {
|
|
309
|
+
const props = {};
|
|
310
|
+
if (span.input) props.$ai_input_state = span.input;
|
|
311
|
+
if (span.output) props.$ai_output_state = span.output;
|
|
312
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_CHUNK) {
|
|
313
|
+
const attrs = span.attributes;
|
|
314
|
+
if (attrs?.chunkType) props.chunk_type = attrs.chunkType;
|
|
315
|
+
if (attrs?.sequenceNumber !== void 0) props.chunk_sequence_number = attrs.sequenceNumber;
|
|
316
|
+
}
|
|
317
|
+
if (span.attributes) Object.assign(props, span.attributes);
|
|
318
|
+
return {
|
|
319
|
+
...props,
|
|
320
|
+
...this.extractErrorProperties(span.errorInfo),
|
|
321
|
+
...this.extractCustomMetadata(span.metadata)
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
formatMessages(data, defaultRole = "user") {
|
|
325
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data) && "messages" in data) {
|
|
326
|
+
const wrapped = data.messages;
|
|
327
|
+
if (this.isMessageArray(wrapped)) return wrapped.map((msg) => this.normalizeMessage(msg));
|
|
328
|
+
}
|
|
329
|
+
if (this.isMessageArray(data)) return data.map((msg) => this.normalizeMessage(msg));
|
|
330
|
+
if (typeof data === "string") return [{
|
|
331
|
+
role: defaultRole,
|
|
332
|
+
content: [{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: data
|
|
335
|
+
}]
|
|
336
|
+
}];
|
|
337
|
+
if (this.isSpanOutputWithToolCalls(data)) {
|
|
338
|
+
const content = [];
|
|
339
|
+
if (data.text) content.push({
|
|
340
|
+
type: "text",
|
|
341
|
+
text: data.text
|
|
342
|
+
});
|
|
343
|
+
for (const tc of data.toolCalls) content.push({
|
|
344
|
+
type: "tool-call",
|
|
345
|
+
id: tc.toolCallId,
|
|
346
|
+
function: {
|
|
347
|
+
name: tc.toolName,
|
|
348
|
+
arguments: tc.args
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return [{
|
|
352
|
+
role: "assistant",
|
|
353
|
+
content
|
|
354
|
+
}];
|
|
355
|
+
}
|
|
356
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data) && "text" in data) {
|
|
357
|
+
const text = data.text;
|
|
358
|
+
if (typeof text === "string") return [{
|
|
359
|
+
role: defaultRole,
|
|
360
|
+
content: [{
|
|
361
|
+
type: "text",
|
|
362
|
+
text
|
|
363
|
+
}]
|
|
364
|
+
}];
|
|
365
|
+
}
|
|
366
|
+
return [{
|
|
367
|
+
role: defaultRole,
|
|
368
|
+
content: [{
|
|
369
|
+
type: "text",
|
|
370
|
+
text: this.safeStringify(data)
|
|
371
|
+
}]
|
|
372
|
+
}];
|
|
373
|
+
}
|
|
374
|
+
isSpanOutputWithToolCalls(data) {
|
|
375
|
+
if (typeof data !== "object" || data === null || !("toolCalls" in data)) return false;
|
|
376
|
+
const { toolCalls } = data;
|
|
377
|
+
return Array.isArray(toolCalls) && toolCalls.length > 0;
|
|
378
|
+
}
|
|
379
|
+
isMessageArray(data) {
|
|
380
|
+
if (!Array.isArray(data)) return false;
|
|
381
|
+
return data.every((item) => typeof item === "object" && item !== null && "role" in item && "content" in item);
|
|
382
|
+
}
|
|
383
|
+
normalizeMessage(msg) {
|
|
384
|
+
if (typeof msg.content === "string") return {
|
|
385
|
+
role: msg.role,
|
|
386
|
+
content: [{
|
|
387
|
+
type: "text",
|
|
388
|
+
text: msg.content
|
|
389
|
+
}]
|
|
390
|
+
};
|
|
391
|
+
return {
|
|
392
|
+
role: msg.role,
|
|
393
|
+
content: msg.content
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
safeStringify(data) {
|
|
397
|
+
try {
|
|
398
|
+
return JSON.stringify(data);
|
|
399
|
+
} catch {
|
|
400
|
+
if (typeof data === "object" && data !== null) return `[Non-serializable ${data.constructor?.name || "Object"}]`;
|
|
401
|
+
return String(data);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Force flush any buffered data to PostHog without shutting down.
|
|
406
|
+
*/
|
|
407
|
+
async _flush() {
|
|
408
|
+
if (this.#client) await this.#client.flush();
|
|
409
|
+
}
|
|
410
|
+
async _postShutdown() {
|
|
411
|
+
if (this.#client) await this.#client.shutdown();
|
|
412
|
+
}
|
|
432
413
|
};
|
|
433
|
-
|
|
414
|
+
//#endregion
|
|
434
415
|
exports.PosthogExporter = PosthogExporter;
|
|
435
416
|
exports.formatUsageMetrics = formatUsageMetrics;
|
|
436
|
-
|
|
417
|
+
|
|
437
418
|
//# sourceMappingURL=index.cjs.map
|