@mastra/posthog 1.3.16 → 1.4.0-alpha.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/dist/features.d.ts +24 -0
- package/dist/features.d.ts.map +1 -0
- package/dist/index.cjs +144 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +122 -13
- package/dist/index.js.map +1 -1
- package/dist/tracing.d.ts +22 -1
- package/dist/tracing.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature detection for paired @mastra/core and @mastra/observability versions.
|
|
3
|
+
*
|
|
4
|
+
* Both are resolved synchronously so span classification is stable from the
|
|
5
|
+
* very first span, without depending on a pending dynamic import.
|
|
6
|
+
*
|
|
7
|
+
* `@mastra/observability` is a hard dependency, but older versions do not
|
|
8
|
+
* export `observabilityFeatures`. The namespace import reads it as `undefined`
|
|
9
|
+
* in that case instead of failing to link, which is the graceful fallback
|
|
10
|
+
* documented in `observability/mastra/src/features.ts`.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when both packages report the `model-inference-span` feature,
|
|
14
|
+
* meaning MODEL_INFERENCE spans are emitted by the tracker. Drives which span
|
|
15
|
+
* is exported as `$ai_generation` (new: MODEL_INFERENCE; legacy:
|
|
16
|
+
* MODEL_GENERATION).
|
|
17
|
+
*/
|
|
18
|
+
export declare function isModelInferenceEnabled(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* @internal Test-only override. Allows tests to simulate a paired older or
|
|
21
|
+
* newer `@mastra/observability` without juggling module mocks.
|
|
22
|
+
*/
|
|
23
|
+
export declare function __setObservabilityFeaturesForTest(features: ReadonlySet<string> | undefined): void;
|
|
24
|
+
//# sourceMappingURL=features.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.d.ts","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,GAAG,IAAI,CAEjG"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,55 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
2
24
|
let _mastra_core_observability = require("@mastra/core/observability");
|
|
3
25
|
let _mastra_observability = require("@mastra/observability");
|
|
26
|
+
_mastra_observability = __toESM(_mastra_observability, 1);
|
|
4
27
|
let posthog_node = require("posthog-node");
|
|
28
|
+
let _mastra_core_features = require("@mastra/core/features");
|
|
29
|
+
//#region src/features.ts
|
|
30
|
+
/**
|
|
31
|
+
* Feature detection for paired @mastra/core and @mastra/observability versions.
|
|
32
|
+
*
|
|
33
|
+
* Both are resolved synchronously so span classification is stable from the
|
|
34
|
+
* very first span, without depending on a pending dynamic import.
|
|
35
|
+
*
|
|
36
|
+
* `@mastra/observability` is a hard dependency, but older versions do not
|
|
37
|
+
* export `observabilityFeatures`. The namespace import reads it as `undefined`
|
|
38
|
+
* in that case instead of failing to link, which is the graceful fallback
|
|
39
|
+
* documented in `observability/mastra/src/features.ts`.
|
|
40
|
+
*/
|
|
41
|
+
const FEATURE = "model-inference-span";
|
|
42
|
+
let observabilityFeatures = _mastra_observability.observabilityFeatures;
|
|
43
|
+
/**
|
|
44
|
+
* Returns true when both packages report the `model-inference-span` feature,
|
|
45
|
+
* meaning MODEL_INFERENCE spans are emitted by the tracker. Drives which span
|
|
46
|
+
* is exported as `$ai_generation` (new: MODEL_INFERENCE; legacy:
|
|
47
|
+
* MODEL_GENERATION).
|
|
48
|
+
*/
|
|
49
|
+
function isModelInferenceEnabled() {
|
|
50
|
+
return observabilityFeatures?.has(FEATURE) === true && _mastra_core_features.coreFeatures.has(FEATURE);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
5
53
|
//#region src/tracing.ts
|
|
6
54
|
/**
|
|
7
55
|
* Formats UsageStats to PostHog's expected property format.
|
|
@@ -25,6 +73,8 @@ function formatUsageMetrics(usage) {
|
|
|
25
73
|
return props;
|
|
26
74
|
}
|
|
27
75
|
const DISTINCT_ID = "distinctId";
|
|
76
|
+
/** Per-trace key prefix for the provider calls and tool results recorded under one MODEL_GENERATION. */
|
|
77
|
+
const CONVERSATION = "conversation:";
|
|
28
78
|
var PosthogExporter = class PosthogExporter extends _mastra_observability.TrackingExporter {
|
|
29
79
|
name = "posthog";
|
|
30
80
|
#client;
|
|
@@ -67,12 +117,12 @@ var PosthogExporter = class PosthogExporter extends _mastra_observability.Tracki
|
|
|
67
117
|
skipCachingEventSpans = true;
|
|
68
118
|
async _buildEvent(args) {
|
|
69
119
|
const { span, traceData } = args;
|
|
70
|
-
const
|
|
120
|
+
const isModelCall = this.isModelCall(span);
|
|
71
121
|
const distinctId = this.getDistinctId(span, traceData);
|
|
72
|
-
const properties = this.buildEventProperties(span, 0);
|
|
122
|
+
const properties = this.buildEventProperties(span, 0, false, isModelCall);
|
|
73
123
|
this.#client?.captureAi(this.withGroups({
|
|
74
124
|
distinctId,
|
|
75
|
-
event:
|
|
125
|
+
event: isModelCall ? "$ai_generation" : "$ai_span",
|
|
76
126
|
properties,
|
|
77
127
|
timestamp: span.endTime ? new Date(span.endTime) : /* @__PURE__ */ new Date()
|
|
78
128
|
}));
|
|
@@ -98,16 +148,17 @@ var PosthogExporter = class PosthogExporter extends _mastra_observability.Tracki
|
|
|
98
148
|
input: cachedSpan.input
|
|
99
149
|
} : span;
|
|
100
150
|
const eventMessage = this.buildEventMessage({
|
|
101
|
-
span: mergedSpan,
|
|
151
|
+
span: this.withCallInput(mergedSpan, traceData),
|
|
102
152
|
traceData
|
|
103
153
|
});
|
|
104
154
|
this.#client?.captureAi(this.withGroups(eventMessage));
|
|
155
|
+
this.recordForConversation(span, traceData);
|
|
105
156
|
}
|
|
106
157
|
async _abortSpan(args) {
|
|
107
158
|
const { span, reason, traceData } = args;
|
|
108
159
|
span.errorInfo = reason;
|
|
109
160
|
const eventMessage = this.buildEventMessage({
|
|
110
|
-
span,
|
|
161
|
+
span: this.withCallInput(span, traceData),
|
|
111
162
|
traceData
|
|
112
163
|
});
|
|
113
164
|
this.#client?.captureAi(this.withGroups(eventMessage));
|
|
@@ -219,22 +270,102 @@ var PosthogExporter = class PosthogExporter extends _mastra_observability.Tracki
|
|
|
219
270
|
}
|
|
220
271
|
buildChildEventMessage(args) {
|
|
221
272
|
const { span, distinctId, endTime, traceData } = args;
|
|
222
|
-
const
|
|
273
|
+
const isModelCall = this.isModelCall(span);
|
|
223
274
|
const latency = (endTime - span.startTime.getTime()) / 1e3;
|
|
224
275
|
const parentIsRootSpan = this.isParentRootSpan(span, traceData);
|
|
276
|
+
const properties = this.buildEventProperties(span, latency, parentIsRootSpan, isModelCall);
|
|
225
277
|
return {
|
|
226
278
|
distinctId,
|
|
227
|
-
event:
|
|
228
|
-
properties
|
|
279
|
+
event: isModelCall ? "$ai_generation" : "$ai_span",
|
|
280
|
+
properties,
|
|
229
281
|
timestamp: new Date(endTime)
|
|
230
282
|
};
|
|
231
283
|
}
|
|
232
284
|
toDate(timestamp) {
|
|
233
285
|
return timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
234
286
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
287
|
+
/**
|
|
288
|
+
* Whether this span is exported as `$ai_generation`. PostHog expects one per
|
|
289
|
+
* provider call and sums tokens and cost across them, so exactly one span
|
|
290
|
+
* per call may carry usage. With paired packages that emit MODEL_INFERENCE,
|
|
291
|
+
* that is the call and MODEL_GENERATION is the loop around it, exported as
|
|
292
|
+
* `$ai_span`. Older pairings only emit MODEL_GENERATION, which keeps the
|
|
293
|
+
* `$ai_generation` role.
|
|
294
|
+
*/
|
|
295
|
+
isModelCall(span) {
|
|
296
|
+
return span.type === (isModelInferenceEnabled() ? _mastra_core_observability.SpanType.MODEL_INFERENCE : _mastra_core_observability.SpanType.MODEL_GENERATION);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Give a MODEL_INFERENCE exported as `$ai_generation` the conversation that
|
|
300
|
+
* call received. The span has no input of its own, so it is rebuilt from the
|
|
301
|
+
* generation's input plus, for each earlier call in the same generation, its
|
|
302
|
+
* output and the results of the tools it called (as Braintrust's Thread view
|
|
303
|
+
* does). Earlier calls and their tools end before this call does, so they are
|
|
304
|
+
* already recorded.
|
|
305
|
+
*/
|
|
306
|
+
withCallInput(span, traceData) {
|
|
307
|
+
if (span.type !== _mastra_core_observability.SpanType.MODEL_INFERENCE || !this.isModelCall(span)) return span;
|
|
308
|
+
const generation = this.findGeneration(span, traceData);
|
|
309
|
+
if (!generation?.input) return span;
|
|
310
|
+
const messages = this.formatMessages(generation.input, "user");
|
|
311
|
+
const conversation = traceData.getExtraValue(CONVERSATION + generation.id);
|
|
312
|
+
for (const call of conversation?.calls ?? []) {
|
|
313
|
+
const output = call.output;
|
|
314
|
+
if (!output?.text && !output?.toolCalls?.length) continue;
|
|
315
|
+
messages.push(...this.formatMessages(output, "assistant"));
|
|
316
|
+
const results = (output.toolCalls ?? []).map((toolCall) => ({
|
|
317
|
+
toolCall,
|
|
318
|
+
toolSpan: conversation.toolResults.get(toolCall.toolCallId)
|
|
319
|
+
})).filter((result) => !!result.toolSpan).sort((a, b) => this.toDate(a.toolSpan.startTime).getTime() - this.toDate(b.toolSpan.startTime).getTime());
|
|
320
|
+
for (const { toolCall, toolSpan } of results) {
|
|
321
|
+
const isError = !!toolSpan.errorInfo || toolSpan.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL && toolSpan.attributes?.success === false;
|
|
322
|
+
messages.push({
|
|
323
|
+
role: "tool",
|
|
324
|
+
content: [{
|
|
325
|
+
type: "tool-result",
|
|
326
|
+
toolCallId: toolCall.toolCallId,
|
|
327
|
+
toolName: toolCall.toolName,
|
|
328
|
+
output: toolSpan.errorInfo ? toolSpan.errorInfo.message : toolSpan.output,
|
|
329
|
+
...isError ? { isError: true } : {}
|
|
330
|
+
}]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
...span,
|
|
336
|
+
input: messages
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/** Record ended calls and tool results so later calls in the same generation can rebuild their input. */
|
|
340
|
+
recordForConversation(span, traceData) {
|
|
341
|
+
const isCall = span.type === _mastra_core_observability.SpanType.MODEL_INFERENCE && !span.errorInfo;
|
|
342
|
+
const isProviderToolWithResult = span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL && typeof span.attributes?.success === "boolean";
|
|
343
|
+
const isTool = span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.MCP_TOOL_CALL || isProviderToolWithResult;
|
|
344
|
+
if (!isModelInferenceEnabled() || !isCall && !isTool) return;
|
|
345
|
+
const generation = this.findGeneration(span, traceData);
|
|
346
|
+
if (!generation) return;
|
|
347
|
+
const key = CONVERSATION + generation.id;
|
|
348
|
+
let conversation = traceData.getExtraValue(key);
|
|
349
|
+
if (!conversation) {
|
|
350
|
+
conversation = {
|
|
351
|
+
calls: [],
|
|
352
|
+
toolResults: /* @__PURE__ */ new Map()
|
|
353
|
+
};
|
|
354
|
+
traceData.setExtraValue(key, conversation);
|
|
355
|
+
}
|
|
356
|
+
if (isCall) conversation.calls.push(span);
|
|
357
|
+
else {
|
|
358
|
+
const toolCallId = this.resolveToolCallId(span);
|
|
359
|
+
if (toolCallId) conversation.toolResults.set(toolCallId, span);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
findGeneration(span, traceData) {
|
|
363
|
+
let ancestor = span.parentSpanId ? traceData.getSpan({ spanId: span.parentSpanId }) : void 0;
|
|
364
|
+
while (ancestor && ancestor.type !== _mastra_core_observability.SpanType.MODEL_GENERATION) ancestor = ancestor.parentSpanId ? traceData.getSpan({ spanId: ancestor.parentSpanId }) : void 0;
|
|
365
|
+
return ancestor;
|
|
366
|
+
}
|
|
367
|
+
resolveToolCallId(span) {
|
|
368
|
+
return span.attributes?.toolCallId ?? span.metadata?.toolCallId ?? span.input?.toolCallId;
|
|
238
369
|
}
|
|
239
370
|
getDistinctId(span, traceData) {
|
|
240
371
|
if (span.metadata?.userId) return String(span.metadata.userId);
|
|
@@ -253,7 +384,7 @@ var PosthogExporter = class PosthogExporter extends _mastra_observability.Tracki
|
|
|
253
384
|
if (parentCache) return parentCache.isRootSpan;
|
|
254
385
|
return false;
|
|
255
386
|
}
|
|
256
|
-
buildEventProperties(span, latency, parentIsRootSpan = false) {
|
|
387
|
+
buildEventProperties(span, latency, parentIsRootSpan = false, isModelCall = this.isModelCall(span)) {
|
|
257
388
|
const baseProperties = {
|
|
258
389
|
$ai_trace_id: span.traceId,
|
|
259
390
|
$ai_latency: latency,
|
|
@@ -262,7 +393,7 @@ var PosthogExporter = class PosthogExporter extends _mastra_observability.Tracki
|
|
|
262
393
|
if (span.parentSpanId) baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;
|
|
263
394
|
if (span.metadata?.sessionId) baseProperties.$ai_session_id = span.metadata.sessionId;
|
|
264
395
|
if (span.isRootSpan && span.tags?.length) for (const tag of span.tags) baseProperties[tag] = true;
|
|
265
|
-
if (
|
|
396
|
+
if (isModelCall) {
|
|
266
397
|
baseProperties.$ai_generation_id = span.id;
|
|
267
398
|
return {
|
|
268
399
|
...baseProperties,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["TrackingExporter","#client","PostHog","SpanType"],"sources":["../src/tracing.ts"],"sourcesContent":["import type {\n AnyExportedSpan,\n ExportedFeedback,\n FeedbackEvent,\n ModelGenerationAttributes,\n SpanErrorInfo,\n UsageStats,\n} from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { TrackingExporter } from '@mastra/observability';\nimport { PostHog } from 'posthog-node';\nimport type { EventMessage } from 'posthog-node';\n\n/**\n * Token usage format compatible with PostHog.\n * @see https://posthog.com/docs/llm-analytics/generations#event-properties\n */\nexport interface PostHogUsageMetrics {\n $ai_input_tokens?: number;\n $ai_output_tokens?: number;\n $ai_cache_read_input_tokens?: number;\n $ai_cache_creation_input_tokens?: number;\n $ai_cache_creation_5m_input_tokens?: number;\n $ai_cache_creation_1h_input_tokens?: number;\n}\n\n/**\n * Formats UsageStats to PostHog's expected property format.\n *\n * Pass through gross input token counts with cache fields as subsets.\n * PostHog subtracts cache tokens when computing costs for non-Anthropic\n * providers and detects Anthropic-style exclusive reporting on its own.\n *\n * @param usage - The UsageStats from span attributes\n * @returns PostHog-formatted usage properties\n */\nexport function formatUsageMetrics(usage?: UsageStats): PostHogUsageMetrics {\n if (!usage) return {};\n\n const props: PostHogUsageMetrics = {};\n\n if (usage.inputTokens !== undefined) {\n props.$ai_input_tokens = usage.inputTokens;\n }\n\n if (usage.inputDetails?.cacheRead !== undefined) {\n props.$ai_cache_read_input_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage.inputDetails?.cacheWrite !== undefined) {\n props.$ai_cache_creation_input_tokens = usage.inputDetails.cacheWrite;\n }\n if (usage.inputDetails?.cacheWrite5m !== undefined) {\n props.$ai_cache_creation_5m_input_tokens = usage.inputDetails.cacheWrite5m;\n }\n if (usage.inputDetails?.cacheWrite1h !== undefined) {\n props.$ai_cache_creation_1h_input_tokens = usage.inputDetails.cacheWrite1h;\n }\n\n if (usage.outputTokens !== undefined) {\n props.$ai_output_tokens = usage.outputTokens;\n }\n\n return props;\n}\n\ninterface PostHogMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: PostHogContent[];\n}\n\ninterface PostHogContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ninterface MastraMessage {\n role: string;\n content: string | MastraContent[];\n}\n\ninterface MastraContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ntype SpanData = string | MastraMessage[] | Record<string, unknown> | unknown;\n\nconst DISTINCT_ID = 'distinctId';\n\nexport interface PosthogExporterConfig extends TrackingExporterConfig {\n /** PostHog API key. Defaults to POSTHOG_API_KEY environment variable. */\n apiKey?: string;\n /** PostHog host URL. Defaults to POSTHOG_HOST environment variable or US region. */\n host?: string;\n flushAt?: number;\n flushInterval?: number;\n serverless?: boolean;\n defaultDistinctId?: string;\n enablePrivacyMode?: boolean;\n}\n\ntype PosthogRoot = unknown;\ntype PosthogSpan = AnyExportedSpan;\n// used as a placeholder for event data since we don't need to cache\n// event data for Posthog\ntype PosthogEvent = boolean;\ntype PosthogMetadata = unknown;\ntype PosthogTraceData = TraceData<PosthogRoot, PosthogSpan, PosthogEvent, PosthogMetadata>;\n\nexport class PosthogExporter extends TrackingExporter<\n PosthogRoot,\n PosthogSpan,\n PosthogEvent,\n PosthogMetadata,\n PosthogExporterConfig\n> {\n name = 'posthog';\n #client: PostHog | undefined;\n\n private static readonly SERVERLESS_FLUSH_AT = 10;\n private static readonly SERVERLESS_FLUSH_INTERVAL = 2000;\n private static readonly DEFAULT_FLUSH_AT = 20;\n private static readonly DEFAULT_FLUSH_INTERVAL = 10000;\n\n constructor(config: PosthogExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.POSTHOG_API_KEY;\n\n super({ ...config, apiKey });\n\n if (!apiKey) {\n this.setDisabled('Missing required API key. Set POSTHOG_API_KEY environment variable or pass apiKey in config.');\n return;\n }\n\n const clientConfig = this.buildClientConfig(this.config);\n this.#client = new PostHog(apiKey, clientConfig);\n const message =\n (config.serverless ?? false) ? 'PostHog exporter initialized in serverless mode' : 'PostHog exporter initialized';\n this.logger.debug(message, config);\n }\n\n private buildClientConfig(config: PosthogExporterConfig) {\n const isServerless = config.serverless ?? false;\n const flushAt =\n config.flushAt ?? (isServerless ? PosthogExporter.SERVERLESS_FLUSH_AT : PosthogExporter.DEFAULT_FLUSH_AT);\n const flushInterval =\n config.flushInterval ??\n (isServerless ? PosthogExporter.SERVERLESS_FLUSH_INTERVAL : PosthogExporter.DEFAULT_FLUSH_INTERVAL);\n\n const host = config.host || process.env.POSTHOG_HOST || 'https://us.i.posthog.com';\n\n if (!config.host && !process.env.POSTHOG_HOST) {\n this.logger.info(\n 'No PostHog host specified, using US default (https://us.i.posthog.com). ' +\n 'For EU region, set `host: \"https://eu.i.posthog.com\"` in config or POSTHOG_HOST env var. ' +\n 'For self-hosted, provide your instance URL.',\n );\n }\n\n return {\n host,\n flushAt,\n flushInterval,\n privacyMode: config.enablePrivacyMode,\n };\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override skipCachingEventSpans = true;\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogEvent> {\n const { span, traceData } = args;\n\n const eventName = this.mapToPostHogEvent(span.type);\n const distinctId = this.getDistinctId(span, traceData);\n const properties = this.buildEventProperties(span, 0);\n\n // captureAi() targets PostHog's dedicated AI endpoint (8 MiB per event, oversized events\n // dropped individually). Plain capture() uses /batch/, which returns 413 for large spans.\n this.#client?.captureAi(\n this.withGroups({\n distinctId,\n event: eventName,\n properties,\n timestamp: span.endTime ? new Date(span.endTime) : new Date(),\n }),\n );\n\n return true;\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogSpan | undefined> {\n const { span, traceData } = args;\n if (!traceData.hasExtraValue(DISTINCT_ID)) {\n const userId = span.metadata?.userId;\n if (userId) {\n traceData.setExtraValue(DISTINCT_ID, String(userId));\n }\n }\n\n return span;\n }\n\n protected override skipSpanUpdateEvents = true;\n protected override _updateSpan(_args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n // Merge input from cached span (SPAN_STARTED) if not present on end span\n // This handles the case where input is only sent at start\n const cachedSpan = traceData.getSpan({ spanId: span.id });\n const mergedSpan = !span.input && cachedSpan?.input ? { ...span, input: cachedSpan.input } : span;\n\n const eventMessage = this.buildEventMessage({ span: mergedSpan, traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n }\n\n protected override async _abortSpan(args: {\n span: PosthogSpan;\n reason: SpanErrorInfo;\n traceData: PosthogTraceData;\n }): Promise<void> {\n const { span, reason, traceData } = args;\n\n // update span with the abort reason\n span.errorInfo = reason;\n\n const eventMessage = this.buildEventMessage({ span, traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n }\n\n /**\n * Forward feedback recorded via `addFeedback()` to PostHog as a native\n * `$ai_feedback` event, shown as \"User feedback\" on the linked trace in\n * PostHog's AI observability UI.\n */\n async onFeedbackEvent(event: FeedbackEvent): Promise<void> {\n if (!this.#client) return;\n\n const { feedback } = event;\n if (!feedback.traceId) {\n this.logger.debug('PostHog exporter: dropping feedback with no traceId; PostHog requires $ai_trace_id', {\n feedbackId: feedback.feedbackId,\n });\n return;\n }\n\n const properties: Record<string, any> = {\n // Custom metadata goes first so it cannot overwrite the natively mapped fields below\n ...this.extractCustomMetadata(feedback.metadata),\n $ai_trace_id: feedback.traceId,\n // PostHog's trace UI only displays feedback events that carry $ai_feedback_text\n $ai_feedback_text: feedback.comment ?? String(feedback.value),\n feedback_id: feedback.feedbackId,\n feedback_type: feedback.feedbackType,\n feedback_value: feedback.value,\n };\n\n const feedbackSource = feedback.feedbackSource ?? feedback.source;\n if (feedbackSource) properties.feedback_source = feedbackSource;\n if (feedback.spanId) properties.span_id = feedback.spanId;\n if (feedback.sourceId) properties.source_id = feedback.sourceId;\n if (feedback.metadata?.sessionId) properties.$ai_session_id = feedback.metadata.sessionId;\n\n try {\n this.#client.captureAi(\n this.withGroups({\n distinctId: this.getFeedbackDistinctId(feedback),\n event: '$ai_feedback',\n properties,\n timestamp: new Date(feedback.timestamp),\n }),\n );\n } catch (err) {\n this.logger.error('PostHog exporter: failed to submit feedback', {\n error: err,\n traceId: feedback.traceId,\n feedbackId: feedback.feedbackId,\n });\n }\n }\n\n private getFeedbackDistinctId(feedback: ExportedFeedback): string {\n const userId = feedback.feedbackUserId ?? feedback.userId ?? feedback.metadata?.userId;\n if (userId) {\n return String(userId);\n }\n return this.config.defaultDistinctId ?? 'anonymous';\n }\n\n /**\n * PostHog group analytics are keyed off the top-level `groups` field on the\n * capture call. The Node SDK derives the event's `$groups` from that field and\n * overwrites any property-level `$groups`, so group metadata carried in\n * properties is dropped unless it is mirrored here.\n */\n private withGroups(message: EventMessage): EventMessage {\n const groups = message.properties?.$groups;\n if (groups && typeof groups === 'object' && !Array.isArray(groups)) {\n return { ...message, groups };\n }\n return message;\n }\n\n private buildEventMessage(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): EventMessage {\n const { span, traceData } = args;\n\n const endTime = span.endTime ? this.toDate(span.endTime).getTime() : Date.now();\n\n const distinctId = this.getDistinctId(span, traceData);\n\n if (span.isRootSpan) {\n return this.buildRootEventMessage({ span, distinctId, endTime });\n } else {\n return this.buildChildEventMessage({ span, distinctId, endTime, traceData });\n }\n }\n\n /**\n * Capture an explicit $ai_trace event for root spans.\n * This gives us control over trace-level metadata like name and tags,\n * rather than relying on PostHog's pseudo-trace auto-creation.\n */\n private buildRootEventMessage(args: { span: AnyExportedSpan; distinctId: string; endTime: number }): EventMessage {\n const { span, distinctId, endTime } = args;\n\n // Note: We don't set $ai_latency on $ai_trace events because PostHog\n // aggregates latency from child events. Setting it here causes double-counting.\n const traceProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_span_name: span.name,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.metadata?.sessionId) {\n traceProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n if (span.input) {\n traceProperties.$ai_input_state = span.input;\n }\n\n if (span.output) {\n traceProperties.$ai_output_state = span.output;\n }\n\n if (span.errorInfo) {\n traceProperties.$ai_error = {\n message: span.errorInfo.message,\n ...(span.errorInfo.id && { id: span.errorInfo.id }),\n ...(span.errorInfo.category && { category: span.errorInfo.category }),\n };\n }\n\n // Add tags as custom properties (PostHog doesn't have native tag support on traces)\n if (span.tags?.length) {\n for (const tag of span.tags) {\n traceProperties[tag] = true;\n }\n }\n\n // Add custom metadata (excluding userId and sessionId which are handled separately)\n const { userId, sessionId, ...customMetadata } = span.metadata ?? {};\n Object.assign(traceProperties, customMetadata);\n\n return {\n distinctId,\n event: '$ai_trace',\n properties: traceProperties,\n timestamp: new Date(endTime),\n };\n }\n\n private buildChildEventMessage(args: {\n span: AnyExportedSpan;\n distinctId: string;\n endTime: number;\n traceData: PosthogTraceData;\n }): EventMessage {\n const { span, distinctId, endTime, traceData } = args;\n\n const eventName = this.mapToPostHogEvent(span.type);\n const startTime = span.startTime.getTime();\n const latency = (endTime - startTime) / 1000;\n\n // Check if parent is the root span - if so, use traceId as parent_id\n // since we don't create an $ai_span for root spans\n const parentIsRootSpan = this.isParentRootSpan(span, traceData);\n const properties = this.buildEventProperties(span, latency, parentIsRootSpan);\n\n return {\n distinctId,\n event: eventName,\n properties,\n timestamp: new Date(endTime),\n };\n }\n\n private toDate(timestamp: Date | number): Date {\n return timestamp instanceof Date ? timestamp : new Date(timestamp);\n }\n\n private mapToPostHogEvent(spanType: SpanType): string {\n if (spanType == SpanType.MODEL_GENERATION) {\n return '$ai_generation';\n }\n return '$ai_span';\n }\n\n private getDistinctId(span: AnyExportedSpan, traceData?: PosthogTraceData): string {\n if (span.metadata?.userId) {\n return String(span.metadata.userId);\n }\n\n if (traceData?.hasExtraValue(DISTINCT_ID)) {\n return String(traceData.getExtraValue(DISTINCT_ID));\n }\n\n if (this.config.defaultDistinctId) {\n return this.config.defaultDistinctId;\n }\n\n return 'anonymous';\n }\n\n /**\n * Check if the parent of this span is the root span.\n * We need this because we don't create $ai_span for root spans,\n * so children of root spans should use $ai_trace_id as their $ai_parent_id.\n */\n private isParentRootSpan(span: AnyExportedSpan, traceData: PosthogTraceData): boolean {\n if (!span.parentSpanId) {\n return false;\n }\n\n // Look up the parent span in our cache to check if it's a root span\n const parentCache = traceData.getSpan({ spanId: span.parentSpanId });\n if (parentCache) {\n return parentCache.isRootSpan;\n }\n\n // Parent not found in cache - shouldn't happen normally, but default to false\n return false;\n }\n\n private buildEventProperties(\n span: AnyExportedSpan,\n latency: number,\n parentIsRootSpan: boolean = false,\n ): Record<string, any> {\n const baseProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_latency: latency,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.parentSpanId) {\n // If parent is the root span, use trace_id as parent_id since we don't\n // create an $ai_span for root spans (only $ai_trace)\n baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;\n }\n\n if (span.metadata?.sessionId) {\n baseProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n // Include tags for root spans (tags are only set on root spans by design)\n // PostHog doesn't allow setting tags directly, so we iterate through each tag\n // and set it as a property with value true\n if (span.isRootSpan && span.tags?.length) {\n for (const tag of span.tags) {\n baseProperties[tag] = true;\n }\n }\n\n if (span.type === SpanType.MODEL_GENERATION) {\n baseProperties.$ai_generation_id = span.id;\n return { ...baseProperties, ...this.buildGenerationProperties(span) };\n } else {\n baseProperties.$ai_span_id = span.id;\n baseProperties.$ai_span_name = span.name;\n return { ...baseProperties, ...this.buildSpanProperties(span) };\n }\n }\n\n private extractErrorProperties(errorInfo?: SpanErrorInfo): Record<string, any> {\n if (!errorInfo) {\n return {};\n }\n\n const props: Record<string, string> = {\n error_message: errorInfo.message,\n };\n\n if (errorInfo.id) {\n props.error_id = errorInfo.id;\n }\n\n if (errorInfo.category) {\n props.error_category = errorInfo.category;\n }\n\n return props;\n }\n\n private extractCustomMetadata(metadata?: Record<string, unknown>): Record<string, any> {\n const { userId, sessionId, ...customMetadata } = metadata ?? {};\n return customMetadata;\n }\n\n private buildGenerationProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n const attrs = (span.attributes ?? {}) as ModelGenerationAttributes;\n\n props.$ai_model = attrs.model || 'unknown-model';\n props.$ai_provider = attrs.provider || 'unknown-provider';\n\n if (span.input) props.$ai_input = this.formatMessages(span.input, 'user');\n if (span.output) props.$ai_output_choices = this.formatMessages(span.output, 'assistant');\n\n // Extract usage properties using the shared utility\n Object.assign(props, formatUsageMetrics(attrs.usage));\n\n if (attrs.parameters) {\n if (attrs.parameters.temperature !== undefined) props.$ai_temperature = attrs.parameters.temperature;\n if (attrs.parameters.maxOutputTokens !== undefined) props.$ai_max_tokens = attrs.parameters.maxOutputTokens;\n }\n if (attrs.streaming !== undefined) props.$ai_stream = attrs.streaming;\n if (attrs.tools?.length) {\n // OpenAI-style shape — the format PostHog's own AI SDKs send and its\n // trace view renders. Provider-defined tools pass through as-is.\n props.$ai_tools = attrs.tools.map(tool =>\n tool.type === 'function'\n ? {\n type: 'function',\n function: { name: tool.name, description: tool.description, parameters: tool.parameters },\n }\n : tool,\n );\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private buildSpanProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n\n if (span.input) props.$ai_input_state = span.input;\n if (span.output) props.$ai_output_state = span.output;\n\n if (span.type === SpanType.MODEL_CHUNK) {\n const attrs = span.attributes as any;\n if (attrs?.chunkType) props.chunk_type = attrs.chunkType;\n if (attrs?.sequenceNumber !== undefined) props.chunk_sequence_number = attrs.sequenceNumber;\n }\n\n if (span.attributes) {\n Object.assign(props, span.attributes);\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private formatMessages(data: SpanData, defaultRole: 'user' | 'assistant' = 'user'): PostHogMessage[] {\n // Unwrap {messages: [...]} wrapper produced by generation span inputs\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'messages' in data) {\n const wrapped = (data as Record<string, unknown>).messages;\n if (this.isMessageArray(wrapped)) {\n return wrapped.map(msg => this.normalizeMessage(msg));\n }\n }\n\n if (this.isMessageArray(data)) {\n return data.map(msg => this.normalizeMessage(msg));\n }\n\n if (typeof data === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text: data }] }];\n }\n\n if (this.isSpanOutputWithToolCalls(data)) {\n const content: PostHogContent[] = [];\n if (data.text) {\n content.push({ type: 'text', text: data.text });\n }\n for (const tc of data.toolCalls) {\n content.push({\n type: 'tool-call',\n id: tc.toolCallId,\n function: { name: tc.toolName, arguments: tc.args },\n });\n }\n return [{ role: 'assistant', content }];\n }\n\n // Extract text from output objects (e.g. generation outputs with text but no tool calls)\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'text' in data) {\n const text = (data as Record<string, unknown>).text;\n if (typeof text === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text }] }];\n }\n }\n\n return [{ role: defaultRole, content: [{ type: 'text', text: this.safeStringify(data) }] }];\n }\n\n private isSpanOutputWithToolCalls(\n data: unknown,\n ): data is { text?: string; toolCalls: Array<{ toolCallId: string; toolName: string; args: unknown }> } {\n if (typeof data !== 'object' || data === null || !('toolCalls' in data)) return false;\n const { toolCalls } = data as Record<string, unknown>;\n return Array.isArray(toolCalls) && toolCalls.length > 0;\n }\n\n private isMessageArray(data: unknown): data is MastraMessage[] {\n if (!Array.isArray(data)) {\n return false;\n }\n\n return data.every(item => typeof item === 'object' && item !== null && 'role' in item && 'content' in item);\n }\n\n private normalizeMessage(msg: MastraMessage): PostHogMessage {\n if (typeof msg.content === 'string') {\n return {\n role: msg.role as PostHogMessage['role'],\n content: [{ type: 'text', text: msg.content }],\n };\n }\n\n return {\n role: msg.role as PostHogMessage['role'],\n content: msg.content as PostHogContent[],\n };\n }\n\n private safeStringify(data: unknown): string {\n try {\n return JSON.stringify(data);\n } catch {\n if (typeof data === 'object' && data !== null) {\n return `[Non-serializable ${data.constructor?.name || 'Object'}]`;\n }\n return String(data);\n }\n }\n\n /**\n * Force flush any buffered data to PostHog without shutting down.\n */\n protected override async _flush(): Promise<void> {\n if (this.#client) {\n await this.#client.flush();\n }\n }\n\n override async _postShutdown(): Promise<void> {\n if (this.#client) {\n await this.#client.shutdown();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["observability","coreFeatures","TrackingExporter","#client","PostHog","SpanType"],"sources":["../src/features.ts","../src/tracing.ts"],"sourcesContent":["/**\n * Feature detection for paired @mastra/core and @mastra/observability versions.\n *\n * Both are resolved synchronously so span classification is stable from the\n * very first span, without depending on a pending dynamic import.\n *\n * `@mastra/observability` is a hard dependency, but older versions do not\n * export `observabilityFeatures`. The namespace import reads it as `undefined`\n * in that case instead of failing to link, which is the graceful fallback\n * documented in `observability/mastra/src/features.ts`.\n */\n\nimport { coreFeatures } from '@mastra/core/features';\nimport * as observability from '@mastra/observability';\n\nconst FEATURE = 'model-inference-span';\n\nlet observabilityFeatures: ReadonlySet<string> | undefined = (\n observability as { observabilityFeatures?: ReadonlySet<string> }\n).observabilityFeatures;\n\n/**\n * Returns true when both packages report the `model-inference-span` feature,\n * meaning MODEL_INFERENCE spans are emitted by the tracker. Drives which span\n * is exported as `$ai_generation` (new: MODEL_INFERENCE; legacy:\n * MODEL_GENERATION).\n */\nexport function isModelInferenceEnabled(): boolean {\n return observabilityFeatures?.has(FEATURE) === true && coreFeatures.has(FEATURE);\n}\n\n/**\n * @internal Test-only override. Allows tests to simulate a paired older or\n * newer `@mastra/observability` without juggling module mocks.\n */\nexport function __setObservabilityFeaturesForTest(features: ReadonlySet<string> | undefined): void {\n observabilityFeatures = features;\n}\n","import type {\n AnyExportedSpan,\n ExportedFeedback,\n FeedbackEvent,\n ModelGenerationAttributes,\n SpanErrorInfo,\n UsageStats,\n} from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { TrackingExporter } from '@mastra/observability';\nimport { PostHog } from 'posthog-node';\nimport type { EventMessage } from 'posthog-node';\nimport { isModelInferenceEnabled } from './features';\n\n/**\n * Token usage format compatible with PostHog.\n * @see https://posthog.com/docs/llm-analytics/generations#event-properties\n */\nexport interface PostHogUsageMetrics {\n $ai_input_tokens?: number;\n $ai_output_tokens?: number;\n $ai_cache_read_input_tokens?: number;\n $ai_cache_creation_input_tokens?: number;\n $ai_cache_creation_5m_input_tokens?: number;\n $ai_cache_creation_1h_input_tokens?: number;\n}\n\n/**\n * Formats UsageStats to PostHog's expected property format.\n *\n * Pass through gross input token counts with cache fields as subsets.\n * PostHog subtracts cache tokens when computing costs for non-Anthropic\n * providers and detects Anthropic-style exclusive reporting on its own.\n *\n * @param usage - The UsageStats from span attributes\n * @returns PostHog-formatted usage properties\n */\nexport function formatUsageMetrics(usage?: UsageStats): PostHogUsageMetrics {\n if (!usage) return {};\n\n const props: PostHogUsageMetrics = {};\n\n if (usage.inputTokens !== undefined) {\n props.$ai_input_tokens = usage.inputTokens;\n }\n\n if (usage.inputDetails?.cacheRead !== undefined) {\n props.$ai_cache_read_input_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage.inputDetails?.cacheWrite !== undefined) {\n props.$ai_cache_creation_input_tokens = usage.inputDetails.cacheWrite;\n }\n if (usage.inputDetails?.cacheWrite5m !== undefined) {\n props.$ai_cache_creation_5m_input_tokens = usage.inputDetails.cacheWrite5m;\n }\n if (usage.inputDetails?.cacheWrite1h !== undefined) {\n props.$ai_cache_creation_1h_input_tokens = usage.inputDetails.cacheWrite1h;\n }\n\n if (usage.outputTokens !== undefined) {\n props.$ai_output_tokens = usage.outputTokens;\n }\n\n return props;\n}\n\ninterface PostHogMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: PostHogContent[];\n}\n\ninterface PostHogContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ninterface MastraMessage {\n role: string;\n content: string | MastraContent[];\n}\n\ninterface MastraContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ntype SpanData = string | MastraMessage[] | Record<string, unknown> | unknown;\n\nconst DISTINCT_ID = 'distinctId';\n/** Per-trace key prefix for the provider calls and tool results recorded under one MODEL_GENERATION. */\nconst CONVERSATION = 'conversation:';\n\n/** Ended spans under one MODEL_GENERATION, used to rebuild each provider call's `$ai_input`. */\ninterface Conversation {\n /** Ended MODEL_INFERENCE spans, in the order they ended. */\n calls: AnyExportedSpan[];\n /** Ended TOOL_CALL / MCP_TOOL_CALL spans and PROVIDER_TOOL_CALL spans with a result, keyed by toolCallId. */\n toolResults: Map<string, AnyExportedSpan>;\n}\n\nexport interface PosthogExporterConfig extends TrackingExporterConfig {\n /** PostHog API key. Defaults to POSTHOG_API_KEY environment variable. */\n apiKey?: string;\n /** PostHog host URL. Defaults to POSTHOG_HOST environment variable or US region. */\n host?: string;\n flushAt?: number;\n flushInterval?: number;\n serverless?: boolean;\n defaultDistinctId?: string;\n enablePrivacyMode?: boolean;\n}\n\ntype PosthogRoot = unknown;\ntype PosthogSpan = AnyExportedSpan;\n// used as a placeholder for event data since we don't need to cache\n// event data for Posthog\ntype PosthogEvent = boolean;\ntype PosthogMetadata = unknown;\ntype PosthogTraceData = TraceData<PosthogRoot, PosthogSpan, PosthogEvent, PosthogMetadata>;\n\nexport class PosthogExporter extends TrackingExporter<\n PosthogRoot,\n PosthogSpan,\n PosthogEvent,\n PosthogMetadata,\n PosthogExporterConfig\n> {\n name = 'posthog';\n #client: PostHog | undefined;\n\n private static readonly SERVERLESS_FLUSH_AT = 10;\n private static readonly SERVERLESS_FLUSH_INTERVAL = 2000;\n private static readonly DEFAULT_FLUSH_AT = 20;\n private static readonly DEFAULT_FLUSH_INTERVAL = 10000;\n\n constructor(config: PosthogExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.POSTHOG_API_KEY;\n\n super({ ...config, apiKey });\n\n if (!apiKey) {\n this.setDisabled('Missing required API key. Set POSTHOG_API_KEY environment variable or pass apiKey in config.');\n return;\n }\n\n const clientConfig = this.buildClientConfig(this.config);\n this.#client = new PostHog(apiKey, clientConfig);\n const message =\n (config.serverless ?? false) ? 'PostHog exporter initialized in serverless mode' : 'PostHog exporter initialized';\n this.logger.debug(message, config);\n }\n\n private buildClientConfig(config: PosthogExporterConfig) {\n const isServerless = config.serverless ?? false;\n const flushAt =\n config.flushAt ?? (isServerless ? PosthogExporter.SERVERLESS_FLUSH_AT : PosthogExporter.DEFAULT_FLUSH_AT);\n const flushInterval =\n config.flushInterval ??\n (isServerless ? PosthogExporter.SERVERLESS_FLUSH_INTERVAL : PosthogExporter.DEFAULT_FLUSH_INTERVAL);\n\n const host = config.host || process.env.POSTHOG_HOST || 'https://us.i.posthog.com';\n\n if (!config.host && !process.env.POSTHOG_HOST) {\n this.logger.info(\n 'No PostHog host specified, using US default (https://us.i.posthog.com). ' +\n 'For EU region, set `host: \"https://eu.i.posthog.com\"` in config or POSTHOG_HOST env var. ' +\n 'For self-hosted, provide your instance URL.',\n );\n }\n\n return {\n host,\n flushAt,\n flushInterval,\n privacyMode: config.enablePrivacyMode,\n };\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override skipCachingEventSpans = true;\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogEvent> {\n const { span, traceData } = args;\n\n const isModelCall = this.isModelCall(span);\n const distinctId = this.getDistinctId(span, traceData);\n const properties = this.buildEventProperties(span, 0, false, isModelCall);\n\n // captureAi() targets PostHog's dedicated AI endpoint (8 MiB per event, oversized events\n // dropped individually). Plain capture() uses /batch/, which returns 413 for large spans.\n this.#client?.captureAi(\n this.withGroups({\n distinctId,\n event: isModelCall ? '$ai_generation' : '$ai_span',\n properties,\n timestamp: span.endTime ? new Date(span.endTime) : new Date(),\n }),\n );\n\n return true;\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogSpan | undefined> {\n const { span, traceData } = args;\n if (!traceData.hasExtraValue(DISTINCT_ID)) {\n const userId = span.metadata?.userId;\n if (userId) {\n traceData.setExtraValue(DISTINCT_ID, String(userId));\n }\n }\n\n return span;\n }\n\n protected override skipSpanUpdateEvents = true;\n protected override _updateSpan(_args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n // Merge input from cached span (SPAN_STARTED) if not present on end span\n // This handles the case where input is only sent at start\n const cachedSpan = traceData.getSpan({ spanId: span.id });\n const mergedSpan = !span.input && cachedSpan?.input ? { ...span, input: cachedSpan.input } : span;\n\n const eventMessage = this.buildEventMessage({ span: this.withCallInput(mergedSpan, traceData), traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n\n this.recordForConversation(span, traceData);\n }\n\n protected override async _abortSpan(args: {\n span: PosthogSpan;\n reason: SpanErrorInfo;\n traceData: PosthogTraceData;\n }): Promise<void> {\n const { span, reason, traceData } = args;\n\n // update span with the abort reason\n span.errorInfo = reason;\n\n const eventMessage = this.buildEventMessage({ span: this.withCallInput(span, traceData), traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n }\n\n /**\n * Forward feedback recorded via `addFeedback()` to PostHog as a native\n * `$ai_feedback` event, shown as \"User feedback\" on the linked trace in\n * PostHog's AI observability UI.\n */\n async onFeedbackEvent(event: FeedbackEvent): Promise<void> {\n if (!this.#client) return;\n\n const { feedback } = event;\n if (!feedback.traceId) {\n this.logger.debug('PostHog exporter: dropping feedback with no traceId; PostHog requires $ai_trace_id', {\n feedbackId: feedback.feedbackId,\n });\n return;\n }\n\n const properties: Record<string, any> = {\n // Custom metadata goes first so it cannot overwrite the natively mapped fields below\n ...this.extractCustomMetadata(feedback.metadata),\n $ai_trace_id: feedback.traceId,\n // PostHog's trace UI only displays feedback events that carry $ai_feedback_text\n $ai_feedback_text: feedback.comment ?? String(feedback.value),\n feedback_id: feedback.feedbackId,\n feedback_type: feedback.feedbackType,\n feedback_value: feedback.value,\n };\n\n const feedbackSource = feedback.feedbackSource ?? feedback.source;\n if (feedbackSource) properties.feedback_source = feedbackSource;\n if (feedback.spanId) properties.span_id = feedback.spanId;\n if (feedback.sourceId) properties.source_id = feedback.sourceId;\n if (feedback.metadata?.sessionId) properties.$ai_session_id = feedback.metadata.sessionId;\n\n try {\n this.#client.captureAi(\n this.withGroups({\n distinctId: this.getFeedbackDistinctId(feedback),\n event: '$ai_feedback',\n properties,\n timestamp: new Date(feedback.timestamp),\n }),\n );\n } catch (err) {\n this.logger.error('PostHog exporter: failed to submit feedback', {\n error: err,\n traceId: feedback.traceId,\n feedbackId: feedback.feedbackId,\n });\n }\n }\n\n private getFeedbackDistinctId(feedback: ExportedFeedback): string {\n const userId = feedback.feedbackUserId ?? feedback.userId ?? feedback.metadata?.userId;\n if (userId) {\n return String(userId);\n }\n return this.config.defaultDistinctId ?? 'anonymous';\n }\n\n /**\n * PostHog group analytics are keyed off the top-level `groups` field on the\n * capture call. The Node SDK derives the event's `$groups` from that field and\n * overwrites any property-level `$groups`, so group metadata carried in\n * properties is dropped unless it is mirrored here.\n */\n private withGroups(message: EventMessage): EventMessage {\n const groups = message.properties?.$groups;\n if (groups && typeof groups === 'object' && !Array.isArray(groups)) {\n return { ...message, groups };\n }\n return message;\n }\n\n private buildEventMessage(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): EventMessage {\n const { span, traceData } = args;\n\n const endTime = span.endTime ? this.toDate(span.endTime).getTime() : Date.now();\n\n const distinctId = this.getDistinctId(span, traceData);\n\n if (span.isRootSpan) {\n return this.buildRootEventMessage({ span, distinctId, endTime });\n } else {\n return this.buildChildEventMessage({ span, distinctId, endTime, traceData });\n }\n }\n\n /**\n * Capture an explicit $ai_trace event for root spans.\n * This gives us control over trace-level metadata like name and tags,\n * rather than relying on PostHog's pseudo-trace auto-creation.\n */\n private buildRootEventMessage(args: { span: AnyExportedSpan; distinctId: string; endTime: number }): EventMessage {\n const { span, distinctId, endTime } = args;\n\n // Note: We don't set $ai_latency on $ai_trace events because PostHog\n // aggregates latency from child events. Setting it here causes double-counting.\n const traceProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_span_name: span.name,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.metadata?.sessionId) {\n traceProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n if (span.input) {\n traceProperties.$ai_input_state = span.input;\n }\n\n if (span.output) {\n traceProperties.$ai_output_state = span.output;\n }\n\n if (span.errorInfo) {\n traceProperties.$ai_error = {\n message: span.errorInfo.message,\n ...(span.errorInfo.id && { id: span.errorInfo.id }),\n ...(span.errorInfo.category && { category: span.errorInfo.category }),\n };\n }\n\n // Add tags as custom properties (PostHog doesn't have native tag support on traces)\n if (span.tags?.length) {\n for (const tag of span.tags) {\n traceProperties[tag] = true;\n }\n }\n\n // Add custom metadata (excluding userId and sessionId which are handled separately)\n const { userId, sessionId, ...customMetadata } = span.metadata ?? {};\n Object.assign(traceProperties, customMetadata);\n\n return {\n distinctId,\n event: '$ai_trace',\n properties: traceProperties,\n timestamp: new Date(endTime),\n };\n }\n\n private buildChildEventMessage(args: {\n span: AnyExportedSpan;\n distinctId: string;\n endTime: number;\n traceData: PosthogTraceData;\n }): EventMessage {\n const { span, distinctId, endTime, traceData } = args;\n\n const isModelCall = this.isModelCall(span);\n const startTime = span.startTime.getTime();\n const latency = (endTime - startTime) / 1000;\n\n // Check if parent is the root span - if so, use traceId as parent_id\n // since we don't create an $ai_span for root spans\n const parentIsRootSpan = this.isParentRootSpan(span, traceData);\n const properties = this.buildEventProperties(span, latency, parentIsRootSpan, isModelCall);\n\n return {\n distinctId,\n event: isModelCall ? '$ai_generation' : '$ai_span',\n properties,\n timestamp: new Date(endTime),\n };\n }\n\n private toDate(timestamp: Date | number): Date {\n return timestamp instanceof Date ? timestamp : new Date(timestamp);\n }\n\n /**\n * Whether this span is exported as `$ai_generation`. PostHog expects one per\n * provider call and sums tokens and cost across them, so exactly one span\n * per call may carry usage. With paired packages that emit MODEL_INFERENCE,\n * that is the call and MODEL_GENERATION is the loop around it, exported as\n * `$ai_span`. Older pairings only emit MODEL_GENERATION, which keeps the\n * `$ai_generation` role.\n */\n private isModelCall(span: AnyExportedSpan): boolean {\n return span.type === (isModelInferenceEnabled() ? SpanType.MODEL_INFERENCE : SpanType.MODEL_GENERATION);\n }\n\n /**\n * Give a MODEL_INFERENCE exported as `$ai_generation` the conversation that\n * call received. The span has no input of its own, so it is rebuilt from the\n * generation's input plus, for each earlier call in the same generation, its\n * output and the results of the tools it called (as Braintrust's Thread view\n * does). Earlier calls and their tools end before this call does, so they are\n * already recorded.\n */\n private withCallInput(span: AnyExportedSpan, traceData: PosthogTraceData): AnyExportedSpan {\n if (span.type !== SpanType.MODEL_INFERENCE || !this.isModelCall(span)) return span;\n\n const generation = this.findGeneration(span, traceData);\n if (!generation?.input) return span;\n\n const messages = this.formatMessages(generation.input, 'user');\n const conversation = traceData.getExtraValue(CONVERSATION + generation.id) as Conversation | undefined;\n for (const call of conversation?.calls ?? []) {\n const output = call.output as\n | { text?: string; toolCalls?: Array<{ toolCallId: string; toolName: string }> }\n | undefined;\n if (!output?.text && !output?.toolCalls?.length) continue;\n messages.push(...this.formatMessages(output, 'assistant'));\n\n // Parallel tool calls are ordered by when each tool started.\n const results = (output.toolCalls ?? [])\n .map(toolCall => ({ toolCall, toolSpan: conversation!.toolResults.get(toolCall.toolCallId) }))\n .filter(\n (result): result is { toolCall: typeof result.toolCall; toolSpan: AnyExportedSpan } => !!result.toolSpan,\n )\n .sort((a, b) => this.toDate(a.toolSpan.startTime).getTime() - this.toDate(b.toolSpan.startTime).getTime());\n for (const { toolCall, toolSpan } of results) {\n // Provider-executed tools report failure through `success`, not `errorInfo`.\n const isError =\n !!toolSpan.errorInfo ||\n (toolSpan.type === SpanType.PROVIDER_TOOL_CALL &&\n (toolSpan.attributes as { success?: boolean } | undefined)?.success === false);\n // Same `tool-result` part shape PostHog's own AI SDK integrations send.\n messages.push({\n role: 'tool',\n content: [\n {\n type: 'tool-result',\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n output: toolSpan.errorInfo ? toolSpan.errorInfo.message : toolSpan.output,\n ...(isError ? { isError: true } : {}),\n },\n ],\n });\n }\n }\n\n return { ...span, input: messages };\n }\n\n /** Record ended calls and tool results so later calls in the same generation can rebuild their input. */\n private recordForConversation(span: AnyExportedSpan, traceData: PosthogTraceData): void {\n const isCall = span.type === SpanType.MODEL_INFERENCE && !span.errorInfo;\n // A PROVIDER_TOOL_CALL span ended without a result (e.g. the run stopped first)\n // has no `success`, and recording it would add an empty result.\n const isProviderToolWithResult =\n span.type === SpanType.PROVIDER_TOOL_CALL &&\n typeof (span.attributes as { success?: unknown } | undefined)?.success === 'boolean';\n const isTool = span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL || isProviderToolWithResult;\n if (!isModelInferenceEnabled() || (!isCall && !isTool)) return;\n\n const generation = this.findGeneration(span, traceData);\n if (!generation) return;\n\n const key = CONVERSATION + generation.id;\n let conversation = traceData.getExtraValue(key) as Conversation | undefined;\n if (!conversation) {\n conversation = { calls: [], toolResults: new Map() };\n traceData.setExtraValue(key, conversation);\n }\n if (isCall) {\n conversation.calls.push(span);\n } else {\n const toolCallId = this.resolveToolCallId(span);\n if (toolCallId) conversation.toolResults.set(toolCallId, span);\n }\n }\n\n private findGeneration(span: AnyExportedSpan, traceData: PosthogTraceData): AnyExportedSpan | undefined {\n let ancestor = span.parentSpanId ? traceData.getSpan({ spanId: span.parentSpanId }) : undefined;\n while (ancestor && ancestor.type !== SpanType.MODEL_GENERATION) {\n ancestor = ancestor.parentSpanId ? traceData.getSpan({ spanId: ancestor.parentSpanId }) : undefined;\n }\n return ancestor;\n }\n\n private resolveToolCallId(span: AnyExportedSpan): string | undefined {\n const attrs = span.attributes as { toolCallId?: string } | undefined;\n return (\n attrs?.toolCallId ??\n (span.metadata?.toolCallId as string | undefined) ??\n (span.input as { toolCallId?: string } | undefined)?.toolCallId\n );\n }\n\n private getDistinctId(span: AnyExportedSpan, traceData?: PosthogTraceData): string {\n if (span.metadata?.userId) {\n return String(span.metadata.userId);\n }\n\n if (traceData?.hasExtraValue(DISTINCT_ID)) {\n return String(traceData.getExtraValue(DISTINCT_ID));\n }\n\n if (this.config.defaultDistinctId) {\n return this.config.defaultDistinctId;\n }\n\n return 'anonymous';\n }\n\n /**\n * Check if the parent of this span is the root span.\n * We need this because we don't create $ai_span for root spans,\n * so children of root spans should use $ai_trace_id as their $ai_parent_id.\n */\n private isParentRootSpan(span: AnyExportedSpan, traceData: PosthogTraceData): boolean {\n if (!span.parentSpanId) {\n return false;\n }\n\n // Look up the parent span in our cache to check if it's a root span\n const parentCache = traceData.getSpan({ spanId: span.parentSpanId });\n if (parentCache) {\n return parentCache.isRootSpan;\n }\n\n // Parent not found in cache - shouldn't happen normally, but default to false\n return false;\n }\n\n private buildEventProperties(\n span: AnyExportedSpan,\n latency: number,\n parentIsRootSpan: boolean = false,\n isModelCall: boolean = this.isModelCall(span),\n ): Record<string, any> {\n const baseProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_latency: latency,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.parentSpanId) {\n // If parent is the root span, use trace_id as parent_id since we don't\n // create an $ai_span for root spans (only $ai_trace)\n baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;\n }\n\n if (span.metadata?.sessionId) {\n baseProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n // Include tags for root spans (tags are only set on root spans by design)\n // PostHog doesn't allow setting tags directly, so we iterate through each tag\n // and set it as a property with value true\n if (span.isRootSpan && span.tags?.length) {\n for (const tag of span.tags) {\n baseProperties[tag] = true;\n }\n }\n\n if (isModelCall) {\n baseProperties.$ai_generation_id = span.id;\n return { ...baseProperties, ...this.buildGenerationProperties(span) };\n } else {\n baseProperties.$ai_span_id = span.id;\n baseProperties.$ai_span_name = span.name;\n return { ...baseProperties, ...this.buildSpanProperties(span) };\n }\n }\n\n private extractErrorProperties(errorInfo?: SpanErrorInfo): Record<string, any> {\n if (!errorInfo) {\n return {};\n }\n\n const props: Record<string, string> = {\n error_message: errorInfo.message,\n };\n\n if (errorInfo.id) {\n props.error_id = errorInfo.id;\n }\n\n if (errorInfo.category) {\n props.error_category = errorInfo.category;\n }\n\n return props;\n }\n\n private extractCustomMetadata(metadata?: Record<string, unknown>): Record<string, any> {\n const { userId, sessionId, ...customMetadata } = metadata ?? {};\n return customMetadata;\n }\n\n private buildGenerationProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n const attrs = (span.attributes ?? {}) as ModelGenerationAttributes;\n\n props.$ai_model = attrs.model || 'unknown-model';\n props.$ai_provider = attrs.provider || 'unknown-provider';\n\n if (span.input) props.$ai_input = this.formatMessages(span.input, 'user');\n if (span.output) props.$ai_output_choices = this.formatMessages(span.output, 'assistant');\n\n // Extract usage properties using the shared utility\n Object.assign(props, formatUsageMetrics(attrs.usage));\n\n if (attrs.parameters) {\n if (attrs.parameters.temperature !== undefined) props.$ai_temperature = attrs.parameters.temperature;\n if (attrs.parameters.maxOutputTokens !== undefined) props.$ai_max_tokens = attrs.parameters.maxOutputTokens;\n }\n if (attrs.streaming !== undefined) props.$ai_stream = attrs.streaming;\n if (attrs.tools?.length) {\n // OpenAI-style shape — the format PostHog's own AI SDKs send and its\n // trace view renders. Provider-defined tools pass through as-is.\n props.$ai_tools = attrs.tools.map(tool =>\n tool.type === 'function'\n ? {\n type: 'function',\n function: { name: tool.name, description: tool.description, parameters: tool.parameters },\n }\n : tool,\n );\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private buildSpanProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n\n if (span.input) props.$ai_input_state = span.input;\n if (span.output) props.$ai_output_state = span.output;\n\n if (span.type === SpanType.MODEL_CHUNK) {\n const attrs = span.attributes as any;\n if (attrs?.chunkType) props.chunk_type = attrs.chunkType;\n if (attrs?.sequenceNumber !== undefined) props.chunk_sequence_number = attrs.sequenceNumber;\n }\n\n if (span.attributes) {\n Object.assign(props, span.attributes);\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private formatMessages(data: SpanData, defaultRole: 'user' | 'assistant' = 'user'): PostHogMessage[] {\n // Unwrap {messages: [...]} wrapper produced by generation span inputs\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'messages' in data) {\n const wrapped = (data as Record<string, unknown>).messages;\n if (this.isMessageArray(wrapped)) {\n return wrapped.map(msg => this.normalizeMessage(msg));\n }\n }\n\n if (this.isMessageArray(data)) {\n return data.map(msg => this.normalizeMessage(msg));\n }\n\n if (typeof data === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text: data }] }];\n }\n\n if (this.isSpanOutputWithToolCalls(data)) {\n const content: PostHogContent[] = [];\n if (data.text) {\n content.push({ type: 'text', text: data.text });\n }\n for (const tc of data.toolCalls) {\n content.push({\n type: 'tool-call',\n id: tc.toolCallId,\n function: { name: tc.toolName, arguments: tc.args },\n });\n }\n return [{ role: 'assistant', content }];\n }\n\n // Extract text from output objects (e.g. generation outputs with text but no tool calls)\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'text' in data) {\n const text = (data as Record<string, unknown>).text;\n if (typeof text === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text }] }];\n }\n }\n\n return [{ role: defaultRole, content: [{ type: 'text', text: this.safeStringify(data) }] }];\n }\n\n private isSpanOutputWithToolCalls(\n data: unknown,\n ): data is { text?: string; toolCalls: Array<{ toolCallId: string; toolName: string; args: unknown }> } {\n if (typeof data !== 'object' || data === null || !('toolCalls' in data)) return false;\n const { toolCalls } = data as Record<string, unknown>;\n return Array.isArray(toolCalls) && toolCalls.length > 0;\n }\n\n private isMessageArray(data: unknown): data is MastraMessage[] {\n if (!Array.isArray(data)) {\n return false;\n }\n\n return data.every(item => typeof item === 'object' && item !== null && 'role' in item && 'content' in item);\n }\n\n private normalizeMessage(msg: MastraMessage): PostHogMessage {\n if (typeof msg.content === 'string') {\n return {\n role: msg.role as PostHogMessage['role'],\n content: [{ type: 'text', text: msg.content }],\n };\n }\n\n return {\n role: msg.role as PostHogMessage['role'],\n content: msg.content as PostHogContent[],\n };\n }\n\n private safeStringify(data: unknown): string {\n try {\n return JSON.stringify(data);\n } catch {\n if (typeof data === 'object' && data !== null) {\n return `[Non-serializable ${data.constructor?.name || 'Object'}]`;\n }\n return String(data);\n }\n }\n\n /**\n * Force flush any buffered data to PostHog without shutting down.\n */\n protected override async _flush(): Promise<void> {\n if (this.#client) {\n await this.#client.flush();\n }\n }\n\n override async _postShutdown(): Promise<void> {\n if (this.#client) {\n await this.#client.shutdown();\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { SpanType } from "@mastra/core/observability";
|
|
2
|
+
import * as observability from "@mastra/observability";
|
|
2
3
|
import { TrackingExporter } from "@mastra/observability";
|
|
3
4
|
import { PostHog } from "posthog-node";
|
|
5
|
+
import { coreFeatures } from "@mastra/core/features";
|
|
6
|
+
//#region src/features.ts
|
|
7
|
+
/**
|
|
8
|
+
* Feature detection for paired @mastra/core and @mastra/observability versions.
|
|
9
|
+
*
|
|
10
|
+
* Both are resolved synchronously so span classification is stable from the
|
|
11
|
+
* very first span, without depending on a pending dynamic import.
|
|
12
|
+
*
|
|
13
|
+
* `@mastra/observability` is a hard dependency, but older versions do not
|
|
14
|
+
* export `observabilityFeatures`. The namespace import reads it as `undefined`
|
|
15
|
+
* in that case instead of failing to link, which is the graceful fallback
|
|
16
|
+
* documented in `observability/mastra/src/features.ts`.
|
|
17
|
+
*/
|
|
18
|
+
const FEATURE = "model-inference-span";
|
|
19
|
+
let observabilityFeatures = observability.observabilityFeatures;
|
|
20
|
+
/**
|
|
21
|
+
* Returns true when both packages report the `model-inference-span` feature,
|
|
22
|
+
* meaning MODEL_INFERENCE spans are emitted by the tracker. Drives which span
|
|
23
|
+
* is exported as `$ai_generation` (new: MODEL_INFERENCE; legacy:
|
|
24
|
+
* MODEL_GENERATION).
|
|
25
|
+
*/
|
|
26
|
+
function isModelInferenceEnabled() {
|
|
27
|
+
return observabilityFeatures?.has(FEATURE) === true && coreFeatures.has(FEATURE);
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
4
30
|
//#region src/tracing.ts
|
|
5
31
|
/**
|
|
6
32
|
* Formats UsageStats to PostHog's expected property format.
|
|
@@ -24,6 +50,8 @@ function formatUsageMetrics(usage) {
|
|
|
24
50
|
return props;
|
|
25
51
|
}
|
|
26
52
|
const DISTINCT_ID = "distinctId";
|
|
53
|
+
/** Per-trace key prefix for the provider calls and tool results recorded under one MODEL_GENERATION. */
|
|
54
|
+
const CONVERSATION = "conversation:";
|
|
27
55
|
var PosthogExporter = class PosthogExporter extends TrackingExporter {
|
|
28
56
|
name = "posthog";
|
|
29
57
|
#client;
|
|
@@ -66,12 +94,12 @@ var PosthogExporter = class PosthogExporter extends TrackingExporter {
|
|
|
66
94
|
skipCachingEventSpans = true;
|
|
67
95
|
async _buildEvent(args) {
|
|
68
96
|
const { span, traceData } = args;
|
|
69
|
-
const
|
|
97
|
+
const isModelCall = this.isModelCall(span);
|
|
70
98
|
const distinctId = this.getDistinctId(span, traceData);
|
|
71
|
-
const properties = this.buildEventProperties(span, 0);
|
|
99
|
+
const properties = this.buildEventProperties(span, 0, false, isModelCall);
|
|
72
100
|
this.#client?.captureAi(this.withGroups({
|
|
73
101
|
distinctId,
|
|
74
|
-
event:
|
|
102
|
+
event: isModelCall ? "$ai_generation" : "$ai_span",
|
|
75
103
|
properties,
|
|
76
104
|
timestamp: span.endTime ? new Date(span.endTime) : /* @__PURE__ */ new Date()
|
|
77
105
|
}));
|
|
@@ -97,16 +125,17 @@ var PosthogExporter = class PosthogExporter extends TrackingExporter {
|
|
|
97
125
|
input: cachedSpan.input
|
|
98
126
|
} : span;
|
|
99
127
|
const eventMessage = this.buildEventMessage({
|
|
100
|
-
span: mergedSpan,
|
|
128
|
+
span: this.withCallInput(mergedSpan, traceData),
|
|
101
129
|
traceData
|
|
102
130
|
});
|
|
103
131
|
this.#client?.captureAi(this.withGroups(eventMessage));
|
|
132
|
+
this.recordForConversation(span, traceData);
|
|
104
133
|
}
|
|
105
134
|
async _abortSpan(args) {
|
|
106
135
|
const { span, reason, traceData } = args;
|
|
107
136
|
span.errorInfo = reason;
|
|
108
137
|
const eventMessage = this.buildEventMessage({
|
|
109
|
-
span,
|
|
138
|
+
span: this.withCallInput(span, traceData),
|
|
110
139
|
traceData
|
|
111
140
|
});
|
|
112
141
|
this.#client?.captureAi(this.withGroups(eventMessage));
|
|
@@ -218,22 +247,102 @@ var PosthogExporter = class PosthogExporter extends TrackingExporter {
|
|
|
218
247
|
}
|
|
219
248
|
buildChildEventMessage(args) {
|
|
220
249
|
const { span, distinctId, endTime, traceData } = args;
|
|
221
|
-
const
|
|
250
|
+
const isModelCall = this.isModelCall(span);
|
|
222
251
|
const latency = (endTime - span.startTime.getTime()) / 1e3;
|
|
223
252
|
const parentIsRootSpan = this.isParentRootSpan(span, traceData);
|
|
253
|
+
const properties = this.buildEventProperties(span, latency, parentIsRootSpan, isModelCall);
|
|
224
254
|
return {
|
|
225
255
|
distinctId,
|
|
226
|
-
event:
|
|
227
|
-
properties
|
|
256
|
+
event: isModelCall ? "$ai_generation" : "$ai_span",
|
|
257
|
+
properties,
|
|
228
258
|
timestamp: new Date(endTime)
|
|
229
259
|
};
|
|
230
260
|
}
|
|
231
261
|
toDate(timestamp) {
|
|
232
262
|
return timestamp instanceof Date ? timestamp : new Date(timestamp);
|
|
233
263
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Whether this span is exported as `$ai_generation`. PostHog expects one per
|
|
266
|
+
* provider call and sums tokens and cost across them, so exactly one span
|
|
267
|
+
* per call may carry usage. With paired packages that emit MODEL_INFERENCE,
|
|
268
|
+
* that is the call and MODEL_GENERATION is the loop around it, exported as
|
|
269
|
+
* `$ai_span`. Older pairings only emit MODEL_GENERATION, which keeps the
|
|
270
|
+
* `$ai_generation` role.
|
|
271
|
+
*/
|
|
272
|
+
isModelCall(span) {
|
|
273
|
+
return span.type === (isModelInferenceEnabled() ? SpanType.MODEL_INFERENCE : SpanType.MODEL_GENERATION);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Give a MODEL_INFERENCE exported as `$ai_generation` the conversation that
|
|
277
|
+
* call received. The span has no input of its own, so it is rebuilt from the
|
|
278
|
+
* generation's input plus, for each earlier call in the same generation, its
|
|
279
|
+
* output and the results of the tools it called (as Braintrust's Thread view
|
|
280
|
+
* does). Earlier calls and their tools end before this call does, so they are
|
|
281
|
+
* already recorded.
|
|
282
|
+
*/
|
|
283
|
+
withCallInput(span, traceData) {
|
|
284
|
+
if (span.type !== SpanType.MODEL_INFERENCE || !this.isModelCall(span)) return span;
|
|
285
|
+
const generation = this.findGeneration(span, traceData);
|
|
286
|
+
if (!generation?.input) return span;
|
|
287
|
+
const messages = this.formatMessages(generation.input, "user");
|
|
288
|
+
const conversation = traceData.getExtraValue(CONVERSATION + generation.id);
|
|
289
|
+
for (const call of conversation?.calls ?? []) {
|
|
290
|
+
const output = call.output;
|
|
291
|
+
if (!output?.text && !output?.toolCalls?.length) continue;
|
|
292
|
+
messages.push(...this.formatMessages(output, "assistant"));
|
|
293
|
+
const results = (output.toolCalls ?? []).map((toolCall) => ({
|
|
294
|
+
toolCall,
|
|
295
|
+
toolSpan: conversation.toolResults.get(toolCall.toolCallId)
|
|
296
|
+
})).filter((result) => !!result.toolSpan).sort((a, b) => this.toDate(a.toolSpan.startTime).getTime() - this.toDate(b.toolSpan.startTime).getTime());
|
|
297
|
+
for (const { toolCall, toolSpan } of results) {
|
|
298
|
+
const isError = !!toolSpan.errorInfo || toolSpan.type === SpanType.PROVIDER_TOOL_CALL && toolSpan.attributes?.success === false;
|
|
299
|
+
messages.push({
|
|
300
|
+
role: "tool",
|
|
301
|
+
content: [{
|
|
302
|
+
type: "tool-result",
|
|
303
|
+
toolCallId: toolCall.toolCallId,
|
|
304
|
+
toolName: toolCall.toolName,
|
|
305
|
+
output: toolSpan.errorInfo ? toolSpan.errorInfo.message : toolSpan.output,
|
|
306
|
+
...isError ? { isError: true } : {}
|
|
307
|
+
}]
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
...span,
|
|
313
|
+
input: messages
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/** Record ended calls and tool results so later calls in the same generation can rebuild their input. */
|
|
317
|
+
recordForConversation(span, traceData) {
|
|
318
|
+
const isCall = span.type === SpanType.MODEL_INFERENCE && !span.errorInfo;
|
|
319
|
+
const isProviderToolWithResult = span.type === SpanType.PROVIDER_TOOL_CALL && typeof span.attributes?.success === "boolean";
|
|
320
|
+
const isTool = span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL || isProviderToolWithResult;
|
|
321
|
+
if (!isModelInferenceEnabled() || !isCall && !isTool) return;
|
|
322
|
+
const generation = this.findGeneration(span, traceData);
|
|
323
|
+
if (!generation) return;
|
|
324
|
+
const key = CONVERSATION + generation.id;
|
|
325
|
+
let conversation = traceData.getExtraValue(key);
|
|
326
|
+
if (!conversation) {
|
|
327
|
+
conversation = {
|
|
328
|
+
calls: [],
|
|
329
|
+
toolResults: /* @__PURE__ */ new Map()
|
|
330
|
+
};
|
|
331
|
+
traceData.setExtraValue(key, conversation);
|
|
332
|
+
}
|
|
333
|
+
if (isCall) conversation.calls.push(span);
|
|
334
|
+
else {
|
|
335
|
+
const toolCallId = this.resolveToolCallId(span);
|
|
336
|
+
if (toolCallId) conversation.toolResults.set(toolCallId, span);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
findGeneration(span, traceData) {
|
|
340
|
+
let ancestor = span.parentSpanId ? traceData.getSpan({ spanId: span.parentSpanId }) : void 0;
|
|
341
|
+
while (ancestor && ancestor.type !== SpanType.MODEL_GENERATION) ancestor = ancestor.parentSpanId ? traceData.getSpan({ spanId: ancestor.parentSpanId }) : void 0;
|
|
342
|
+
return ancestor;
|
|
343
|
+
}
|
|
344
|
+
resolveToolCallId(span) {
|
|
345
|
+
return span.attributes?.toolCallId ?? span.metadata?.toolCallId ?? span.input?.toolCallId;
|
|
237
346
|
}
|
|
238
347
|
getDistinctId(span, traceData) {
|
|
239
348
|
if (span.metadata?.userId) return String(span.metadata.userId);
|
|
@@ -252,7 +361,7 @@ var PosthogExporter = class PosthogExporter extends TrackingExporter {
|
|
|
252
361
|
if (parentCache) return parentCache.isRootSpan;
|
|
253
362
|
return false;
|
|
254
363
|
}
|
|
255
|
-
buildEventProperties(span, latency, parentIsRootSpan = false) {
|
|
364
|
+
buildEventProperties(span, latency, parentIsRootSpan = false, isModelCall = this.isModelCall(span)) {
|
|
256
365
|
const baseProperties = {
|
|
257
366
|
$ai_trace_id: span.traceId,
|
|
258
367
|
$ai_latency: latency,
|
|
@@ -261,7 +370,7 @@ var PosthogExporter = class PosthogExporter extends TrackingExporter {
|
|
|
261
370
|
if (span.parentSpanId) baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;
|
|
262
371
|
if (span.metadata?.sessionId) baseProperties.$ai_session_id = span.metadata.sessionId;
|
|
263
372
|
if (span.isRootSpan && span.tags?.length) for (const tag of span.tags) baseProperties[tag] = true;
|
|
264
|
-
if (
|
|
373
|
+
if (isModelCall) {
|
|
265
374
|
baseProperties.$ai_generation_id = span.id;
|
|
266
375
|
return {
|
|
267
376
|
...baseProperties,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#client"],"sources":["../src/tracing.ts"],"sourcesContent":["import type {\n AnyExportedSpan,\n ExportedFeedback,\n FeedbackEvent,\n ModelGenerationAttributes,\n SpanErrorInfo,\n UsageStats,\n} from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { TrackingExporter } from '@mastra/observability';\nimport { PostHog } from 'posthog-node';\nimport type { EventMessage } from 'posthog-node';\n\n/**\n * Token usage format compatible with PostHog.\n * @see https://posthog.com/docs/llm-analytics/generations#event-properties\n */\nexport interface PostHogUsageMetrics {\n $ai_input_tokens?: number;\n $ai_output_tokens?: number;\n $ai_cache_read_input_tokens?: number;\n $ai_cache_creation_input_tokens?: number;\n $ai_cache_creation_5m_input_tokens?: number;\n $ai_cache_creation_1h_input_tokens?: number;\n}\n\n/**\n * Formats UsageStats to PostHog's expected property format.\n *\n * Pass through gross input token counts with cache fields as subsets.\n * PostHog subtracts cache tokens when computing costs for non-Anthropic\n * providers and detects Anthropic-style exclusive reporting on its own.\n *\n * @param usage - The UsageStats from span attributes\n * @returns PostHog-formatted usage properties\n */\nexport function formatUsageMetrics(usage?: UsageStats): PostHogUsageMetrics {\n if (!usage) return {};\n\n const props: PostHogUsageMetrics = {};\n\n if (usage.inputTokens !== undefined) {\n props.$ai_input_tokens = usage.inputTokens;\n }\n\n if (usage.inputDetails?.cacheRead !== undefined) {\n props.$ai_cache_read_input_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage.inputDetails?.cacheWrite !== undefined) {\n props.$ai_cache_creation_input_tokens = usage.inputDetails.cacheWrite;\n }\n if (usage.inputDetails?.cacheWrite5m !== undefined) {\n props.$ai_cache_creation_5m_input_tokens = usage.inputDetails.cacheWrite5m;\n }\n if (usage.inputDetails?.cacheWrite1h !== undefined) {\n props.$ai_cache_creation_1h_input_tokens = usage.inputDetails.cacheWrite1h;\n }\n\n if (usage.outputTokens !== undefined) {\n props.$ai_output_tokens = usage.outputTokens;\n }\n\n return props;\n}\n\ninterface PostHogMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: PostHogContent[];\n}\n\ninterface PostHogContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ninterface MastraMessage {\n role: string;\n content: string | MastraContent[];\n}\n\ninterface MastraContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ntype SpanData = string | MastraMessage[] | Record<string, unknown> | unknown;\n\nconst DISTINCT_ID = 'distinctId';\n\nexport interface PosthogExporterConfig extends TrackingExporterConfig {\n /** PostHog API key. Defaults to POSTHOG_API_KEY environment variable. */\n apiKey?: string;\n /** PostHog host URL. Defaults to POSTHOG_HOST environment variable or US region. */\n host?: string;\n flushAt?: number;\n flushInterval?: number;\n serverless?: boolean;\n defaultDistinctId?: string;\n enablePrivacyMode?: boolean;\n}\n\ntype PosthogRoot = unknown;\ntype PosthogSpan = AnyExportedSpan;\n// used as a placeholder for event data since we don't need to cache\n// event data for Posthog\ntype PosthogEvent = boolean;\ntype PosthogMetadata = unknown;\ntype PosthogTraceData = TraceData<PosthogRoot, PosthogSpan, PosthogEvent, PosthogMetadata>;\n\nexport class PosthogExporter extends TrackingExporter<\n PosthogRoot,\n PosthogSpan,\n PosthogEvent,\n PosthogMetadata,\n PosthogExporterConfig\n> {\n name = 'posthog';\n #client: PostHog | undefined;\n\n private static readonly SERVERLESS_FLUSH_AT = 10;\n private static readonly SERVERLESS_FLUSH_INTERVAL = 2000;\n private static readonly DEFAULT_FLUSH_AT = 20;\n private static readonly DEFAULT_FLUSH_INTERVAL = 10000;\n\n constructor(config: PosthogExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.POSTHOG_API_KEY;\n\n super({ ...config, apiKey });\n\n if (!apiKey) {\n this.setDisabled('Missing required API key. Set POSTHOG_API_KEY environment variable or pass apiKey in config.');\n return;\n }\n\n const clientConfig = this.buildClientConfig(this.config);\n this.#client = new PostHog(apiKey, clientConfig);\n const message =\n (config.serverless ?? false) ? 'PostHog exporter initialized in serverless mode' : 'PostHog exporter initialized';\n this.logger.debug(message, config);\n }\n\n private buildClientConfig(config: PosthogExporterConfig) {\n const isServerless = config.serverless ?? false;\n const flushAt =\n config.flushAt ?? (isServerless ? PosthogExporter.SERVERLESS_FLUSH_AT : PosthogExporter.DEFAULT_FLUSH_AT);\n const flushInterval =\n config.flushInterval ??\n (isServerless ? PosthogExporter.SERVERLESS_FLUSH_INTERVAL : PosthogExporter.DEFAULT_FLUSH_INTERVAL);\n\n const host = config.host || process.env.POSTHOG_HOST || 'https://us.i.posthog.com';\n\n if (!config.host && !process.env.POSTHOG_HOST) {\n this.logger.info(\n 'No PostHog host specified, using US default (https://us.i.posthog.com). ' +\n 'For EU region, set `host: \"https://eu.i.posthog.com\"` in config or POSTHOG_HOST env var. ' +\n 'For self-hosted, provide your instance URL.',\n );\n }\n\n return {\n host,\n flushAt,\n flushInterval,\n privacyMode: config.enablePrivacyMode,\n };\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override skipCachingEventSpans = true;\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogEvent> {\n const { span, traceData } = args;\n\n const eventName = this.mapToPostHogEvent(span.type);\n const distinctId = this.getDistinctId(span, traceData);\n const properties = this.buildEventProperties(span, 0);\n\n // captureAi() targets PostHog's dedicated AI endpoint (8 MiB per event, oversized events\n // dropped individually). Plain capture() uses /batch/, which returns 413 for large spans.\n this.#client?.captureAi(\n this.withGroups({\n distinctId,\n event: eventName,\n properties,\n timestamp: span.endTime ? new Date(span.endTime) : new Date(),\n }),\n );\n\n return true;\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogSpan | undefined> {\n const { span, traceData } = args;\n if (!traceData.hasExtraValue(DISTINCT_ID)) {\n const userId = span.metadata?.userId;\n if (userId) {\n traceData.setExtraValue(DISTINCT_ID, String(userId));\n }\n }\n\n return span;\n }\n\n protected override skipSpanUpdateEvents = true;\n protected override _updateSpan(_args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n // Merge input from cached span (SPAN_STARTED) if not present on end span\n // This handles the case where input is only sent at start\n const cachedSpan = traceData.getSpan({ spanId: span.id });\n const mergedSpan = !span.input && cachedSpan?.input ? { ...span, input: cachedSpan.input } : span;\n\n const eventMessage = this.buildEventMessage({ span: mergedSpan, traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n }\n\n protected override async _abortSpan(args: {\n span: PosthogSpan;\n reason: SpanErrorInfo;\n traceData: PosthogTraceData;\n }): Promise<void> {\n const { span, reason, traceData } = args;\n\n // update span with the abort reason\n span.errorInfo = reason;\n\n const eventMessage = this.buildEventMessage({ span, traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n }\n\n /**\n * Forward feedback recorded via `addFeedback()` to PostHog as a native\n * `$ai_feedback` event, shown as \"User feedback\" on the linked trace in\n * PostHog's AI observability UI.\n */\n async onFeedbackEvent(event: FeedbackEvent): Promise<void> {\n if (!this.#client) return;\n\n const { feedback } = event;\n if (!feedback.traceId) {\n this.logger.debug('PostHog exporter: dropping feedback with no traceId; PostHog requires $ai_trace_id', {\n feedbackId: feedback.feedbackId,\n });\n return;\n }\n\n const properties: Record<string, any> = {\n // Custom metadata goes first so it cannot overwrite the natively mapped fields below\n ...this.extractCustomMetadata(feedback.metadata),\n $ai_trace_id: feedback.traceId,\n // PostHog's trace UI only displays feedback events that carry $ai_feedback_text\n $ai_feedback_text: feedback.comment ?? String(feedback.value),\n feedback_id: feedback.feedbackId,\n feedback_type: feedback.feedbackType,\n feedback_value: feedback.value,\n };\n\n const feedbackSource = feedback.feedbackSource ?? feedback.source;\n if (feedbackSource) properties.feedback_source = feedbackSource;\n if (feedback.spanId) properties.span_id = feedback.spanId;\n if (feedback.sourceId) properties.source_id = feedback.sourceId;\n if (feedback.metadata?.sessionId) properties.$ai_session_id = feedback.metadata.sessionId;\n\n try {\n this.#client.captureAi(\n this.withGroups({\n distinctId: this.getFeedbackDistinctId(feedback),\n event: '$ai_feedback',\n properties,\n timestamp: new Date(feedback.timestamp),\n }),\n );\n } catch (err) {\n this.logger.error('PostHog exporter: failed to submit feedback', {\n error: err,\n traceId: feedback.traceId,\n feedbackId: feedback.feedbackId,\n });\n }\n }\n\n private getFeedbackDistinctId(feedback: ExportedFeedback): string {\n const userId = feedback.feedbackUserId ?? feedback.userId ?? feedback.metadata?.userId;\n if (userId) {\n return String(userId);\n }\n return this.config.defaultDistinctId ?? 'anonymous';\n }\n\n /**\n * PostHog group analytics are keyed off the top-level `groups` field on the\n * capture call. The Node SDK derives the event's `$groups` from that field and\n * overwrites any property-level `$groups`, so group metadata carried in\n * properties is dropped unless it is mirrored here.\n */\n private withGroups(message: EventMessage): EventMessage {\n const groups = message.properties?.$groups;\n if (groups && typeof groups === 'object' && !Array.isArray(groups)) {\n return { ...message, groups };\n }\n return message;\n }\n\n private buildEventMessage(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): EventMessage {\n const { span, traceData } = args;\n\n const endTime = span.endTime ? this.toDate(span.endTime).getTime() : Date.now();\n\n const distinctId = this.getDistinctId(span, traceData);\n\n if (span.isRootSpan) {\n return this.buildRootEventMessage({ span, distinctId, endTime });\n } else {\n return this.buildChildEventMessage({ span, distinctId, endTime, traceData });\n }\n }\n\n /**\n * Capture an explicit $ai_trace event for root spans.\n * This gives us control over trace-level metadata like name and tags,\n * rather than relying on PostHog's pseudo-trace auto-creation.\n */\n private buildRootEventMessage(args: { span: AnyExportedSpan; distinctId: string; endTime: number }): EventMessage {\n const { span, distinctId, endTime } = args;\n\n // Note: We don't set $ai_latency on $ai_trace events because PostHog\n // aggregates latency from child events. Setting it here causes double-counting.\n const traceProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_span_name: span.name,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.metadata?.sessionId) {\n traceProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n if (span.input) {\n traceProperties.$ai_input_state = span.input;\n }\n\n if (span.output) {\n traceProperties.$ai_output_state = span.output;\n }\n\n if (span.errorInfo) {\n traceProperties.$ai_error = {\n message: span.errorInfo.message,\n ...(span.errorInfo.id && { id: span.errorInfo.id }),\n ...(span.errorInfo.category && { category: span.errorInfo.category }),\n };\n }\n\n // Add tags as custom properties (PostHog doesn't have native tag support on traces)\n if (span.tags?.length) {\n for (const tag of span.tags) {\n traceProperties[tag] = true;\n }\n }\n\n // Add custom metadata (excluding userId and sessionId which are handled separately)\n const { userId, sessionId, ...customMetadata } = span.metadata ?? {};\n Object.assign(traceProperties, customMetadata);\n\n return {\n distinctId,\n event: '$ai_trace',\n properties: traceProperties,\n timestamp: new Date(endTime),\n };\n }\n\n private buildChildEventMessage(args: {\n span: AnyExportedSpan;\n distinctId: string;\n endTime: number;\n traceData: PosthogTraceData;\n }): EventMessage {\n const { span, distinctId, endTime, traceData } = args;\n\n const eventName = this.mapToPostHogEvent(span.type);\n const startTime = span.startTime.getTime();\n const latency = (endTime - startTime) / 1000;\n\n // Check if parent is the root span - if so, use traceId as parent_id\n // since we don't create an $ai_span for root spans\n const parentIsRootSpan = this.isParentRootSpan(span, traceData);\n const properties = this.buildEventProperties(span, latency, parentIsRootSpan);\n\n return {\n distinctId,\n event: eventName,\n properties,\n timestamp: new Date(endTime),\n };\n }\n\n private toDate(timestamp: Date | number): Date {\n return timestamp instanceof Date ? timestamp : new Date(timestamp);\n }\n\n private mapToPostHogEvent(spanType: SpanType): string {\n if (spanType == SpanType.MODEL_GENERATION) {\n return '$ai_generation';\n }\n return '$ai_span';\n }\n\n private getDistinctId(span: AnyExportedSpan, traceData?: PosthogTraceData): string {\n if (span.metadata?.userId) {\n return String(span.metadata.userId);\n }\n\n if (traceData?.hasExtraValue(DISTINCT_ID)) {\n return String(traceData.getExtraValue(DISTINCT_ID));\n }\n\n if (this.config.defaultDistinctId) {\n return this.config.defaultDistinctId;\n }\n\n return 'anonymous';\n }\n\n /**\n * Check if the parent of this span is the root span.\n * We need this because we don't create $ai_span for root spans,\n * so children of root spans should use $ai_trace_id as their $ai_parent_id.\n */\n private isParentRootSpan(span: AnyExportedSpan, traceData: PosthogTraceData): boolean {\n if (!span.parentSpanId) {\n return false;\n }\n\n // Look up the parent span in our cache to check if it's a root span\n const parentCache = traceData.getSpan({ spanId: span.parentSpanId });\n if (parentCache) {\n return parentCache.isRootSpan;\n }\n\n // Parent not found in cache - shouldn't happen normally, but default to false\n return false;\n }\n\n private buildEventProperties(\n span: AnyExportedSpan,\n latency: number,\n parentIsRootSpan: boolean = false,\n ): Record<string, any> {\n const baseProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_latency: latency,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.parentSpanId) {\n // If parent is the root span, use trace_id as parent_id since we don't\n // create an $ai_span for root spans (only $ai_trace)\n baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;\n }\n\n if (span.metadata?.sessionId) {\n baseProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n // Include tags for root spans (tags are only set on root spans by design)\n // PostHog doesn't allow setting tags directly, so we iterate through each tag\n // and set it as a property with value true\n if (span.isRootSpan && span.tags?.length) {\n for (const tag of span.tags) {\n baseProperties[tag] = true;\n }\n }\n\n if (span.type === SpanType.MODEL_GENERATION) {\n baseProperties.$ai_generation_id = span.id;\n return { ...baseProperties, ...this.buildGenerationProperties(span) };\n } else {\n baseProperties.$ai_span_id = span.id;\n baseProperties.$ai_span_name = span.name;\n return { ...baseProperties, ...this.buildSpanProperties(span) };\n }\n }\n\n private extractErrorProperties(errorInfo?: SpanErrorInfo): Record<string, any> {\n if (!errorInfo) {\n return {};\n }\n\n const props: Record<string, string> = {\n error_message: errorInfo.message,\n };\n\n if (errorInfo.id) {\n props.error_id = errorInfo.id;\n }\n\n if (errorInfo.category) {\n props.error_category = errorInfo.category;\n }\n\n return props;\n }\n\n private extractCustomMetadata(metadata?: Record<string, unknown>): Record<string, any> {\n const { userId, sessionId, ...customMetadata } = metadata ?? {};\n return customMetadata;\n }\n\n private buildGenerationProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n const attrs = (span.attributes ?? {}) as ModelGenerationAttributes;\n\n props.$ai_model = attrs.model || 'unknown-model';\n props.$ai_provider = attrs.provider || 'unknown-provider';\n\n if (span.input) props.$ai_input = this.formatMessages(span.input, 'user');\n if (span.output) props.$ai_output_choices = this.formatMessages(span.output, 'assistant');\n\n // Extract usage properties using the shared utility\n Object.assign(props, formatUsageMetrics(attrs.usage));\n\n if (attrs.parameters) {\n if (attrs.parameters.temperature !== undefined) props.$ai_temperature = attrs.parameters.temperature;\n if (attrs.parameters.maxOutputTokens !== undefined) props.$ai_max_tokens = attrs.parameters.maxOutputTokens;\n }\n if (attrs.streaming !== undefined) props.$ai_stream = attrs.streaming;\n if (attrs.tools?.length) {\n // OpenAI-style shape — the format PostHog's own AI SDKs send and its\n // trace view renders. Provider-defined tools pass through as-is.\n props.$ai_tools = attrs.tools.map(tool =>\n tool.type === 'function'\n ? {\n type: 'function',\n function: { name: tool.name, description: tool.description, parameters: tool.parameters },\n }\n : tool,\n );\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private buildSpanProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n\n if (span.input) props.$ai_input_state = span.input;\n if (span.output) props.$ai_output_state = span.output;\n\n if (span.type === SpanType.MODEL_CHUNK) {\n const attrs = span.attributes as any;\n if (attrs?.chunkType) props.chunk_type = attrs.chunkType;\n if (attrs?.sequenceNumber !== undefined) props.chunk_sequence_number = attrs.sequenceNumber;\n }\n\n if (span.attributes) {\n Object.assign(props, span.attributes);\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private formatMessages(data: SpanData, defaultRole: 'user' | 'assistant' = 'user'): PostHogMessage[] {\n // Unwrap {messages: [...]} wrapper produced by generation span inputs\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'messages' in data) {\n const wrapped = (data as Record<string, unknown>).messages;\n if (this.isMessageArray(wrapped)) {\n return wrapped.map(msg => this.normalizeMessage(msg));\n }\n }\n\n if (this.isMessageArray(data)) {\n return data.map(msg => this.normalizeMessage(msg));\n }\n\n if (typeof data === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text: data }] }];\n }\n\n if (this.isSpanOutputWithToolCalls(data)) {\n const content: PostHogContent[] = [];\n if (data.text) {\n content.push({ type: 'text', text: data.text });\n }\n for (const tc of data.toolCalls) {\n content.push({\n type: 'tool-call',\n id: tc.toolCallId,\n function: { name: tc.toolName, arguments: tc.args },\n });\n }\n return [{ role: 'assistant', content }];\n }\n\n // Extract text from output objects (e.g. generation outputs with text but no tool calls)\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'text' in data) {\n const text = (data as Record<string, unknown>).text;\n if (typeof text === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text }] }];\n }\n }\n\n return [{ role: defaultRole, content: [{ type: 'text', text: this.safeStringify(data) }] }];\n }\n\n private isSpanOutputWithToolCalls(\n data: unknown,\n ): data is { text?: string; toolCalls: Array<{ toolCallId: string; toolName: string; args: unknown }> } {\n if (typeof data !== 'object' || data === null || !('toolCalls' in data)) return false;\n const { toolCalls } = data as Record<string, unknown>;\n return Array.isArray(toolCalls) && toolCalls.length > 0;\n }\n\n private isMessageArray(data: unknown): data is MastraMessage[] {\n if (!Array.isArray(data)) {\n return false;\n }\n\n return data.every(item => typeof item === 'object' && item !== null && 'role' in item && 'content' in item);\n }\n\n private normalizeMessage(msg: MastraMessage): PostHogMessage {\n if (typeof msg.content === 'string') {\n return {\n role: msg.role as PostHogMessage['role'],\n content: [{ type: 'text', text: msg.content }],\n };\n }\n\n return {\n role: msg.role as PostHogMessage['role'],\n content: msg.content as PostHogContent[],\n };\n }\n\n private safeStringify(data: unknown): string {\n try {\n return JSON.stringify(data);\n } catch {\n if (typeof data === 'object' && data !== null) {\n return `[Non-serializable ${data.constructor?.name || 'Object'}]`;\n }\n return String(data);\n }\n }\n\n /**\n * Force flush any buffered data to PostHog without shutting down.\n */\n protected override async _flush(): Promise<void> {\n if (this.#client) {\n await this.#client.flush();\n }\n }\n\n override async _postShutdown(): Promise<void> {\n if (this.#client) {\n await this.#client.shutdown();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#client"],"sources":["../src/features.ts","../src/tracing.ts"],"sourcesContent":["/**\n * Feature detection for paired @mastra/core and @mastra/observability versions.\n *\n * Both are resolved synchronously so span classification is stable from the\n * very first span, without depending on a pending dynamic import.\n *\n * `@mastra/observability` is a hard dependency, but older versions do not\n * export `observabilityFeatures`. The namespace import reads it as `undefined`\n * in that case instead of failing to link, which is the graceful fallback\n * documented in `observability/mastra/src/features.ts`.\n */\n\nimport { coreFeatures } from '@mastra/core/features';\nimport * as observability from '@mastra/observability';\n\nconst FEATURE = 'model-inference-span';\n\nlet observabilityFeatures: ReadonlySet<string> | undefined = (\n observability as { observabilityFeatures?: ReadonlySet<string> }\n).observabilityFeatures;\n\n/**\n * Returns true when both packages report the `model-inference-span` feature,\n * meaning MODEL_INFERENCE spans are emitted by the tracker. Drives which span\n * is exported as `$ai_generation` (new: MODEL_INFERENCE; legacy:\n * MODEL_GENERATION).\n */\nexport function isModelInferenceEnabled(): boolean {\n return observabilityFeatures?.has(FEATURE) === true && coreFeatures.has(FEATURE);\n}\n\n/**\n * @internal Test-only override. Allows tests to simulate a paired older or\n * newer `@mastra/observability` without juggling module mocks.\n */\nexport function __setObservabilityFeaturesForTest(features: ReadonlySet<string> | undefined): void {\n observabilityFeatures = features;\n}\n","import type {\n AnyExportedSpan,\n ExportedFeedback,\n FeedbackEvent,\n ModelGenerationAttributes,\n SpanErrorInfo,\n UsageStats,\n} from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { TrackingExporter } from '@mastra/observability';\nimport { PostHog } from 'posthog-node';\nimport type { EventMessage } from 'posthog-node';\nimport { isModelInferenceEnabled } from './features';\n\n/**\n * Token usage format compatible with PostHog.\n * @see https://posthog.com/docs/llm-analytics/generations#event-properties\n */\nexport interface PostHogUsageMetrics {\n $ai_input_tokens?: number;\n $ai_output_tokens?: number;\n $ai_cache_read_input_tokens?: number;\n $ai_cache_creation_input_tokens?: number;\n $ai_cache_creation_5m_input_tokens?: number;\n $ai_cache_creation_1h_input_tokens?: number;\n}\n\n/**\n * Formats UsageStats to PostHog's expected property format.\n *\n * Pass through gross input token counts with cache fields as subsets.\n * PostHog subtracts cache tokens when computing costs for non-Anthropic\n * providers and detects Anthropic-style exclusive reporting on its own.\n *\n * @param usage - The UsageStats from span attributes\n * @returns PostHog-formatted usage properties\n */\nexport function formatUsageMetrics(usage?: UsageStats): PostHogUsageMetrics {\n if (!usage) return {};\n\n const props: PostHogUsageMetrics = {};\n\n if (usage.inputTokens !== undefined) {\n props.$ai_input_tokens = usage.inputTokens;\n }\n\n if (usage.inputDetails?.cacheRead !== undefined) {\n props.$ai_cache_read_input_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage.inputDetails?.cacheWrite !== undefined) {\n props.$ai_cache_creation_input_tokens = usage.inputDetails.cacheWrite;\n }\n if (usage.inputDetails?.cacheWrite5m !== undefined) {\n props.$ai_cache_creation_5m_input_tokens = usage.inputDetails.cacheWrite5m;\n }\n if (usage.inputDetails?.cacheWrite1h !== undefined) {\n props.$ai_cache_creation_1h_input_tokens = usage.inputDetails.cacheWrite1h;\n }\n\n if (usage.outputTokens !== undefined) {\n props.$ai_output_tokens = usage.outputTokens;\n }\n\n return props;\n}\n\ninterface PostHogMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: PostHogContent[];\n}\n\ninterface PostHogContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ninterface MastraMessage {\n role: string;\n content: string | MastraContent[];\n}\n\ninterface MastraContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\ntype SpanData = string | MastraMessage[] | Record<string, unknown> | unknown;\n\nconst DISTINCT_ID = 'distinctId';\n/** Per-trace key prefix for the provider calls and tool results recorded under one MODEL_GENERATION. */\nconst CONVERSATION = 'conversation:';\n\n/** Ended spans under one MODEL_GENERATION, used to rebuild each provider call's `$ai_input`. */\ninterface Conversation {\n /** Ended MODEL_INFERENCE spans, in the order they ended. */\n calls: AnyExportedSpan[];\n /** Ended TOOL_CALL / MCP_TOOL_CALL spans and PROVIDER_TOOL_CALL spans with a result, keyed by toolCallId. */\n toolResults: Map<string, AnyExportedSpan>;\n}\n\nexport interface PosthogExporterConfig extends TrackingExporterConfig {\n /** PostHog API key. Defaults to POSTHOG_API_KEY environment variable. */\n apiKey?: string;\n /** PostHog host URL. Defaults to POSTHOG_HOST environment variable or US region. */\n host?: string;\n flushAt?: number;\n flushInterval?: number;\n serverless?: boolean;\n defaultDistinctId?: string;\n enablePrivacyMode?: boolean;\n}\n\ntype PosthogRoot = unknown;\ntype PosthogSpan = AnyExportedSpan;\n// used as a placeholder for event data since we don't need to cache\n// event data for Posthog\ntype PosthogEvent = boolean;\ntype PosthogMetadata = unknown;\ntype PosthogTraceData = TraceData<PosthogRoot, PosthogSpan, PosthogEvent, PosthogMetadata>;\n\nexport class PosthogExporter extends TrackingExporter<\n PosthogRoot,\n PosthogSpan,\n PosthogEvent,\n PosthogMetadata,\n PosthogExporterConfig\n> {\n name = 'posthog';\n #client: PostHog | undefined;\n\n private static readonly SERVERLESS_FLUSH_AT = 10;\n private static readonly SERVERLESS_FLUSH_INTERVAL = 2000;\n private static readonly DEFAULT_FLUSH_AT = 20;\n private static readonly DEFAULT_FLUSH_INTERVAL = 10000;\n\n constructor(config: PosthogExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const apiKey = config.apiKey ?? process.env.POSTHOG_API_KEY;\n\n super({ ...config, apiKey });\n\n if (!apiKey) {\n this.setDisabled('Missing required API key. Set POSTHOG_API_KEY environment variable or pass apiKey in config.');\n return;\n }\n\n const clientConfig = this.buildClientConfig(this.config);\n this.#client = new PostHog(apiKey, clientConfig);\n const message =\n (config.serverless ?? false) ? 'PostHog exporter initialized in serverless mode' : 'PostHog exporter initialized';\n this.logger.debug(message, config);\n }\n\n private buildClientConfig(config: PosthogExporterConfig) {\n const isServerless = config.serverless ?? false;\n const flushAt =\n config.flushAt ?? (isServerless ? PosthogExporter.SERVERLESS_FLUSH_AT : PosthogExporter.DEFAULT_FLUSH_AT);\n const flushInterval =\n config.flushInterval ??\n (isServerless ? PosthogExporter.SERVERLESS_FLUSH_INTERVAL : PosthogExporter.DEFAULT_FLUSH_INTERVAL);\n\n const host = config.host || process.env.POSTHOG_HOST || 'https://us.i.posthog.com';\n\n if (!config.host && !process.env.POSTHOG_HOST) {\n this.logger.info(\n 'No PostHog host specified, using US default (https://us.i.posthog.com). ' +\n 'For EU region, set `host: \"https://eu.i.posthog.com\"` in config or POSTHOG_HOST env var. ' +\n 'For self-hosted, provide your instance URL.',\n );\n }\n\n return {\n host,\n flushAt,\n flushInterval,\n privacyMode: config.enablePrivacyMode,\n };\n }\n\n protected override skipBuildRootTask = true;\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogRoot | undefined> {\n throw new Error('Method not implemented.');\n }\n\n protected override skipCachingEventSpans = true;\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogEvent> {\n const { span, traceData } = args;\n\n const isModelCall = this.isModelCall(span);\n const distinctId = this.getDistinctId(span, traceData);\n const properties = this.buildEventProperties(span, 0, false, isModelCall);\n\n // captureAi() targets PostHog's dedicated AI endpoint (8 MiB per event, oversized events\n // dropped individually). Plain capture() uses /batch/, which returns 413 for large spans.\n this.#client?.captureAi(\n this.withGroups({\n distinctId,\n event: isModelCall ? '$ai_generation' : '$ai_span',\n properties,\n timestamp: span.endTime ? new Date(span.endTime) : new Date(),\n }),\n );\n\n return true;\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: PosthogTraceData;\n }): Promise<PosthogSpan | undefined> {\n const { span, traceData } = args;\n if (!traceData.hasExtraValue(DISTINCT_ID)) {\n const userId = span.metadata?.userId;\n if (userId) {\n traceData.setExtraValue(DISTINCT_ID, String(userId));\n }\n }\n\n return span;\n }\n\n protected override skipSpanUpdateEvents = true;\n protected override _updateSpan(_args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n throw new Error('Method not implemented.');\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n // Merge input from cached span (SPAN_STARTED) if not present on end span\n // This handles the case where input is only sent at start\n const cachedSpan = traceData.getSpan({ spanId: span.id });\n const mergedSpan = !span.input && cachedSpan?.input ? { ...span, input: cachedSpan.input } : span;\n\n const eventMessage = this.buildEventMessage({ span: this.withCallInput(mergedSpan, traceData), traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n\n this.recordForConversation(span, traceData);\n }\n\n protected override async _abortSpan(args: {\n span: PosthogSpan;\n reason: SpanErrorInfo;\n traceData: PosthogTraceData;\n }): Promise<void> {\n const { span, reason, traceData } = args;\n\n // update span with the abort reason\n span.errorInfo = reason;\n\n const eventMessage = this.buildEventMessage({ span: this.withCallInput(span, traceData), traceData });\n this.#client?.captureAi(this.withGroups(eventMessage));\n }\n\n /**\n * Forward feedback recorded via `addFeedback()` to PostHog as a native\n * `$ai_feedback` event, shown as \"User feedback\" on the linked trace in\n * PostHog's AI observability UI.\n */\n async onFeedbackEvent(event: FeedbackEvent): Promise<void> {\n if (!this.#client) return;\n\n const { feedback } = event;\n if (!feedback.traceId) {\n this.logger.debug('PostHog exporter: dropping feedback with no traceId; PostHog requires $ai_trace_id', {\n feedbackId: feedback.feedbackId,\n });\n return;\n }\n\n const properties: Record<string, any> = {\n // Custom metadata goes first so it cannot overwrite the natively mapped fields below\n ...this.extractCustomMetadata(feedback.metadata),\n $ai_trace_id: feedback.traceId,\n // PostHog's trace UI only displays feedback events that carry $ai_feedback_text\n $ai_feedback_text: feedback.comment ?? String(feedback.value),\n feedback_id: feedback.feedbackId,\n feedback_type: feedback.feedbackType,\n feedback_value: feedback.value,\n };\n\n const feedbackSource = feedback.feedbackSource ?? feedback.source;\n if (feedbackSource) properties.feedback_source = feedbackSource;\n if (feedback.spanId) properties.span_id = feedback.spanId;\n if (feedback.sourceId) properties.source_id = feedback.sourceId;\n if (feedback.metadata?.sessionId) properties.$ai_session_id = feedback.metadata.sessionId;\n\n try {\n this.#client.captureAi(\n this.withGroups({\n distinctId: this.getFeedbackDistinctId(feedback),\n event: '$ai_feedback',\n properties,\n timestamp: new Date(feedback.timestamp),\n }),\n );\n } catch (err) {\n this.logger.error('PostHog exporter: failed to submit feedback', {\n error: err,\n traceId: feedback.traceId,\n feedbackId: feedback.feedbackId,\n });\n }\n }\n\n private getFeedbackDistinctId(feedback: ExportedFeedback): string {\n const userId = feedback.feedbackUserId ?? feedback.userId ?? feedback.metadata?.userId;\n if (userId) {\n return String(userId);\n }\n return this.config.defaultDistinctId ?? 'anonymous';\n }\n\n /**\n * PostHog group analytics are keyed off the top-level `groups` field on the\n * capture call. The Node SDK derives the event's `$groups` from that field and\n * overwrites any property-level `$groups`, so group metadata carried in\n * properties is dropped unless it is mirrored here.\n */\n private withGroups(message: EventMessage): EventMessage {\n const groups = message.properties?.$groups;\n if (groups && typeof groups === 'object' && !Array.isArray(groups)) {\n return { ...message, groups };\n }\n return message;\n }\n\n private buildEventMessage(args: { span: AnyExportedSpan; traceData: PosthogTraceData }): EventMessage {\n const { span, traceData } = args;\n\n const endTime = span.endTime ? this.toDate(span.endTime).getTime() : Date.now();\n\n const distinctId = this.getDistinctId(span, traceData);\n\n if (span.isRootSpan) {\n return this.buildRootEventMessage({ span, distinctId, endTime });\n } else {\n return this.buildChildEventMessage({ span, distinctId, endTime, traceData });\n }\n }\n\n /**\n * Capture an explicit $ai_trace event for root spans.\n * This gives us control over trace-level metadata like name and tags,\n * rather than relying on PostHog's pseudo-trace auto-creation.\n */\n private buildRootEventMessage(args: { span: AnyExportedSpan; distinctId: string; endTime: number }): EventMessage {\n const { span, distinctId, endTime } = args;\n\n // Note: We don't set $ai_latency on $ai_trace events because PostHog\n // aggregates latency from child events. Setting it here causes double-counting.\n const traceProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_span_name: span.name,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.metadata?.sessionId) {\n traceProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n if (span.input) {\n traceProperties.$ai_input_state = span.input;\n }\n\n if (span.output) {\n traceProperties.$ai_output_state = span.output;\n }\n\n if (span.errorInfo) {\n traceProperties.$ai_error = {\n message: span.errorInfo.message,\n ...(span.errorInfo.id && { id: span.errorInfo.id }),\n ...(span.errorInfo.category && { category: span.errorInfo.category }),\n };\n }\n\n // Add tags as custom properties (PostHog doesn't have native tag support on traces)\n if (span.tags?.length) {\n for (const tag of span.tags) {\n traceProperties[tag] = true;\n }\n }\n\n // Add custom metadata (excluding userId and sessionId which are handled separately)\n const { userId, sessionId, ...customMetadata } = span.metadata ?? {};\n Object.assign(traceProperties, customMetadata);\n\n return {\n distinctId,\n event: '$ai_trace',\n properties: traceProperties,\n timestamp: new Date(endTime),\n };\n }\n\n private buildChildEventMessage(args: {\n span: AnyExportedSpan;\n distinctId: string;\n endTime: number;\n traceData: PosthogTraceData;\n }): EventMessage {\n const { span, distinctId, endTime, traceData } = args;\n\n const isModelCall = this.isModelCall(span);\n const startTime = span.startTime.getTime();\n const latency = (endTime - startTime) / 1000;\n\n // Check if parent is the root span - if so, use traceId as parent_id\n // since we don't create an $ai_span for root spans\n const parentIsRootSpan = this.isParentRootSpan(span, traceData);\n const properties = this.buildEventProperties(span, latency, parentIsRootSpan, isModelCall);\n\n return {\n distinctId,\n event: isModelCall ? '$ai_generation' : '$ai_span',\n properties,\n timestamp: new Date(endTime),\n };\n }\n\n private toDate(timestamp: Date | number): Date {\n return timestamp instanceof Date ? timestamp : new Date(timestamp);\n }\n\n /**\n * Whether this span is exported as `$ai_generation`. PostHog expects one per\n * provider call and sums tokens and cost across them, so exactly one span\n * per call may carry usage. With paired packages that emit MODEL_INFERENCE,\n * that is the call and MODEL_GENERATION is the loop around it, exported as\n * `$ai_span`. Older pairings only emit MODEL_GENERATION, which keeps the\n * `$ai_generation` role.\n */\n private isModelCall(span: AnyExportedSpan): boolean {\n return span.type === (isModelInferenceEnabled() ? SpanType.MODEL_INFERENCE : SpanType.MODEL_GENERATION);\n }\n\n /**\n * Give a MODEL_INFERENCE exported as `$ai_generation` the conversation that\n * call received. The span has no input of its own, so it is rebuilt from the\n * generation's input plus, for each earlier call in the same generation, its\n * output and the results of the tools it called (as Braintrust's Thread view\n * does). Earlier calls and their tools end before this call does, so they are\n * already recorded.\n */\n private withCallInput(span: AnyExportedSpan, traceData: PosthogTraceData): AnyExportedSpan {\n if (span.type !== SpanType.MODEL_INFERENCE || !this.isModelCall(span)) return span;\n\n const generation = this.findGeneration(span, traceData);\n if (!generation?.input) return span;\n\n const messages = this.formatMessages(generation.input, 'user');\n const conversation = traceData.getExtraValue(CONVERSATION + generation.id) as Conversation | undefined;\n for (const call of conversation?.calls ?? []) {\n const output = call.output as\n | { text?: string; toolCalls?: Array<{ toolCallId: string; toolName: string }> }\n | undefined;\n if (!output?.text && !output?.toolCalls?.length) continue;\n messages.push(...this.formatMessages(output, 'assistant'));\n\n // Parallel tool calls are ordered by when each tool started.\n const results = (output.toolCalls ?? [])\n .map(toolCall => ({ toolCall, toolSpan: conversation!.toolResults.get(toolCall.toolCallId) }))\n .filter(\n (result): result is { toolCall: typeof result.toolCall; toolSpan: AnyExportedSpan } => !!result.toolSpan,\n )\n .sort((a, b) => this.toDate(a.toolSpan.startTime).getTime() - this.toDate(b.toolSpan.startTime).getTime());\n for (const { toolCall, toolSpan } of results) {\n // Provider-executed tools report failure through `success`, not `errorInfo`.\n const isError =\n !!toolSpan.errorInfo ||\n (toolSpan.type === SpanType.PROVIDER_TOOL_CALL &&\n (toolSpan.attributes as { success?: boolean } | undefined)?.success === false);\n // Same `tool-result` part shape PostHog's own AI SDK integrations send.\n messages.push({\n role: 'tool',\n content: [\n {\n type: 'tool-result',\n toolCallId: toolCall.toolCallId,\n toolName: toolCall.toolName,\n output: toolSpan.errorInfo ? toolSpan.errorInfo.message : toolSpan.output,\n ...(isError ? { isError: true } : {}),\n },\n ],\n });\n }\n }\n\n return { ...span, input: messages };\n }\n\n /** Record ended calls and tool results so later calls in the same generation can rebuild their input. */\n private recordForConversation(span: AnyExportedSpan, traceData: PosthogTraceData): void {\n const isCall = span.type === SpanType.MODEL_INFERENCE && !span.errorInfo;\n // A PROVIDER_TOOL_CALL span ended without a result (e.g. the run stopped first)\n // has no `success`, and recording it would add an empty result.\n const isProviderToolWithResult =\n span.type === SpanType.PROVIDER_TOOL_CALL &&\n typeof (span.attributes as { success?: unknown } | undefined)?.success === 'boolean';\n const isTool = span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL || isProviderToolWithResult;\n if (!isModelInferenceEnabled() || (!isCall && !isTool)) return;\n\n const generation = this.findGeneration(span, traceData);\n if (!generation) return;\n\n const key = CONVERSATION + generation.id;\n let conversation = traceData.getExtraValue(key) as Conversation | undefined;\n if (!conversation) {\n conversation = { calls: [], toolResults: new Map() };\n traceData.setExtraValue(key, conversation);\n }\n if (isCall) {\n conversation.calls.push(span);\n } else {\n const toolCallId = this.resolveToolCallId(span);\n if (toolCallId) conversation.toolResults.set(toolCallId, span);\n }\n }\n\n private findGeneration(span: AnyExportedSpan, traceData: PosthogTraceData): AnyExportedSpan | undefined {\n let ancestor = span.parentSpanId ? traceData.getSpan({ spanId: span.parentSpanId }) : undefined;\n while (ancestor && ancestor.type !== SpanType.MODEL_GENERATION) {\n ancestor = ancestor.parentSpanId ? traceData.getSpan({ spanId: ancestor.parentSpanId }) : undefined;\n }\n return ancestor;\n }\n\n private resolveToolCallId(span: AnyExportedSpan): string | undefined {\n const attrs = span.attributes as { toolCallId?: string } | undefined;\n return (\n attrs?.toolCallId ??\n (span.metadata?.toolCallId as string | undefined) ??\n (span.input as { toolCallId?: string } | undefined)?.toolCallId\n );\n }\n\n private getDistinctId(span: AnyExportedSpan, traceData?: PosthogTraceData): string {\n if (span.metadata?.userId) {\n return String(span.metadata.userId);\n }\n\n if (traceData?.hasExtraValue(DISTINCT_ID)) {\n return String(traceData.getExtraValue(DISTINCT_ID));\n }\n\n if (this.config.defaultDistinctId) {\n return this.config.defaultDistinctId;\n }\n\n return 'anonymous';\n }\n\n /**\n * Check if the parent of this span is the root span.\n * We need this because we don't create $ai_span for root spans,\n * so children of root spans should use $ai_trace_id as their $ai_parent_id.\n */\n private isParentRootSpan(span: AnyExportedSpan, traceData: PosthogTraceData): boolean {\n if (!span.parentSpanId) {\n return false;\n }\n\n // Look up the parent span in our cache to check if it's a root span\n const parentCache = traceData.getSpan({ spanId: span.parentSpanId });\n if (parentCache) {\n return parentCache.isRootSpan;\n }\n\n // Parent not found in cache - shouldn't happen normally, but default to false\n return false;\n }\n\n private buildEventProperties(\n span: AnyExportedSpan,\n latency: number,\n parentIsRootSpan: boolean = false,\n isModelCall: boolean = this.isModelCall(span),\n ): Record<string, any> {\n const baseProperties: Record<string, any> = {\n $ai_trace_id: span.traceId,\n $ai_latency: latency,\n $ai_is_error: !!span.errorInfo,\n };\n\n if (span.parentSpanId) {\n // If parent is the root span, use trace_id as parent_id since we don't\n // create an $ai_span for root spans (only $ai_trace)\n baseProperties.$ai_parent_id = parentIsRootSpan ? span.traceId : span.parentSpanId;\n }\n\n if (span.metadata?.sessionId) {\n baseProperties.$ai_session_id = span.metadata.sessionId;\n }\n\n // Include tags for root spans (tags are only set on root spans by design)\n // PostHog doesn't allow setting tags directly, so we iterate through each tag\n // and set it as a property with value true\n if (span.isRootSpan && span.tags?.length) {\n for (const tag of span.tags) {\n baseProperties[tag] = true;\n }\n }\n\n if (isModelCall) {\n baseProperties.$ai_generation_id = span.id;\n return { ...baseProperties, ...this.buildGenerationProperties(span) };\n } else {\n baseProperties.$ai_span_id = span.id;\n baseProperties.$ai_span_name = span.name;\n return { ...baseProperties, ...this.buildSpanProperties(span) };\n }\n }\n\n private extractErrorProperties(errorInfo?: SpanErrorInfo): Record<string, any> {\n if (!errorInfo) {\n return {};\n }\n\n const props: Record<string, string> = {\n error_message: errorInfo.message,\n };\n\n if (errorInfo.id) {\n props.error_id = errorInfo.id;\n }\n\n if (errorInfo.category) {\n props.error_category = errorInfo.category;\n }\n\n return props;\n }\n\n private extractCustomMetadata(metadata?: Record<string, unknown>): Record<string, any> {\n const { userId, sessionId, ...customMetadata } = metadata ?? {};\n return customMetadata;\n }\n\n private buildGenerationProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n const attrs = (span.attributes ?? {}) as ModelGenerationAttributes;\n\n props.$ai_model = attrs.model || 'unknown-model';\n props.$ai_provider = attrs.provider || 'unknown-provider';\n\n if (span.input) props.$ai_input = this.formatMessages(span.input, 'user');\n if (span.output) props.$ai_output_choices = this.formatMessages(span.output, 'assistant');\n\n // Extract usage properties using the shared utility\n Object.assign(props, formatUsageMetrics(attrs.usage));\n\n if (attrs.parameters) {\n if (attrs.parameters.temperature !== undefined) props.$ai_temperature = attrs.parameters.temperature;\n if (attrs.parameters.maxOutputTokens !== undefined) props.$ai_max_tokens = attrs.parameters.maxOutputTokens;\n }\n if (attrs.streaming !== undefined) props.$ai_stream = attrs.streaming;\n if (attrs.tools?.length) {\n // OpenAI-style shape — the format PostHog's own AI SDKs send and its\n // trace view renders. Provider-defined tools pass through as-is.\n props.$ai_tools = attrs.tools.map(tool =>\n tool.type === 'function'\n ? {\n type: 'function',\n function: { name: tool.name, description: tool.description, parameters: tool.parameters },\n }\n : tool,\n );\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private buildSpanProperties(span: AnyExportedSpan): Record<string, any> {\n const props: Record<string, any> = {};\n\n if (span.input) props.$ai_input_state = span.input;\n if (span.output) props.$ai_output_state = span.output;\n\n if (span.type === SpanType.MODEL_CHUNK) {\n const attrs = span.attributes as any;\n if (attrs?.chunkType) props.chunk_type = attrs.chunkType;\n if (attrs?.sequenceNumber !== undefined) props.chunk_sequence_number = attrs.sequenceNumber;\n }\n\n if (span.attributes) {\n Object.assign(props, span.attributes);\n }\n\n return { ...props, ...this.extractErrorProperties(span.errorInfo), ...this.extractCustomMetadata(span.metadata) };\n }\n\n private formatMessages(data: SpanData, defaultRole: 'user' | 'assistant' = 'user'): PostHogMessage[] {\n // Unwrap {messages: [...]} wrapper produced by generation span inputs\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'messages' in data) {\n const wrapped = (data as Record<string, unknown>).messages;\n if (this.isMessageArray(wrapped)) {\n return wrapped.map(msg => this.normalizeMessage(msg));\n }\n }\n\n if (this.isMessageArray(data)) {\n return data.map(msg => this.normalizeMessage(msg));\n }\n\n if (typeof data === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text: data }] }];\n }\n\n if (this.isSpanOutputWithToolCalls(data)) {\n const content: PostHogContent[] = [];\n if (data.text) {\n content.push({ type: 'text', text: data.text });\n }\n for (const tc of data.toolCalls) {\n content.push({\n type: 'tool-call',\n id: tc.toolCallId,\n function: { name: tc.toolName, arguments: tc.args },\n });\n }\n return [{ role: 'assistant', content }];\n }\n\n // Extract text from output objects (e.g. generation outputs with text but no tool calls)\n if (typeof data === 'object' && data !== null && !Array.isArray(data) && 'text' in data) {\n const text = (data as Record<string, unknown>).text;\n if (typeof text === 'string') {\n return [{ role: defaultRole, content: [{ type: 'text', text }] }];\n }\n }\n\n return [{ role: defaultRole, content: [{ type: 'text', text: this.safeStringify(data) }] }];\n }\n\n private isSpanOutputWithToolCalls(\n data: unknown,\n ): data is { text?: string; toolCalls: Array<{ toolCallId: string; toolName: string; args: unknown }> } {\n if (typeof data !== 'object' || data === null || !('toolCalls' in data)) return false;\n const { toolCalls } = data as Record<string, unknown>;\n return Array.isArray(toolCalls) && toolCalls.length > 0;\n }\n\n private isMessageArray(data: unknown): data is MastraMessage[] {\n if (!Array.isArray(data)) {\n return false;\n }\n\n return data.every(item => typeof item === 'object' && item !== null && 'role' in item && 'content' in item);\n }\n\n private normalizeMessage(msg: MastraMessage): PostHogMessage {\n if (typeof msg.content === 'string') {\n return {\n role: msg.role as PostHogMessage['role'],\n content: [{ type: 'text', text: msg.content }],\n };\n }\n\n return {\n role: msg.role as PostHogMessage['role'],\n content: msg.content as PostHogContent[],\n };\n }\n\n private safeStringify(data: unknown): string {\n try {\n return JSON.stringify(data);\n } catch {\n if (typeof data === 'object' && data !== null) {\n return `[Non-serializable ${data.constructor?.name || 'Object'}]`;\n }\n return String(data);\n }\n }\n\n /**\n * Force flush any buffered data to PostHog without shutting down.\n */\n protected override async _flush(): Promise<void> {\n if (this.#client) {\n await this.#client.flush();\n }\n }\n\n override async _postShutdown(): Promise<void> {\n if (this.#client) {\n await this.#client.shutdown();\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/tracing.d.ts
CHANGED
|
@@ -100,7 +100,28 @@ export declare class PosthogExporter extends TrackingExporter<PosthogRoot, Posth
|
|
|
100
100
|
private buildRootEventMessage;
|
|
101
101
|
private buildChildEventMessage;
|
|
102
102
|
private toDate;
|
|
103
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Whether this span is exported as `$ai_generation`. PostHog expects one per
|
|
105
|
+
* provider call and sums tokens and cost across them, so exactly one span
|
|
106
|
+
* per call may carry usage. With paired packages that emit MODEL_INFERENCE,
|
|
107
|
+
* that is the call and MODEL_GENERATION is the loop around it, exported as
|
|
108
|
+
* `$ai_span`. Older pairings only emit MODEL_GENERATION, which keeps the
|
|
109
|
+
* `$ai_generation` role.
|
|
110
|
+
*/
|
|
111
|
+
private isModelCall;
|
|
112
|
+
/**
|
|
113
|
+
* Give a MODEL_INFERENCE exported as `$ai_generation` the conversation that
|
|
114
|
+
* call received. The span has no input of its own, so it is rebuilt from the
|
|
115
|
+
* generation's input plus, for each earlier call in the same generation, its
|
|
116
|
+
* output and the results of the tools it called (as Braintrust's Thread view
|
|
117
|
+
* does). Earlier calls and their tools end before this call does, so they are
|
|
118
|
+
* already recorded.
|
|
119
|
+
*/
|
|
120
|
+
private withCallInput;
|
|
121
|
+
/** Record ended calls and tool results so later calls in the same generation can rebuild their input. */
|
|
122
|
+
private recordForConversation;
|
|
123
|
+
private findGeneration;
|
|
124
|
+
private resolveToolCallId;
|
|
104
125
|
private getDistinctId;
|
|
105
126
|
/**
|
|
106
127
|
* Check if the parent of this span is the root span.
|
package/dist/tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,aAAa,EAEb,aAAa,EACb,UAAU,EACX,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/posthog",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-alpha.1",
|
|
4
4
|
"description": "PostHog observability provider for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"license": "Apache-2.0",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"posthog-node": "^5.49.0",
|
|
27
|
-
"@mastra/observability": "1.
|
|
27
|
+
"@mastra/observability": "1.19.0-alpha.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "22.20.1",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"tsdown": "0.22.9",
|
|
33
33
|
"typescript": "^7.0.2",
|
|
34
34
|
"vitest": "4.1.11",
|
|
35
|
-
"@internal/lint": "0.0.141",
|
|
36
35
|
"@internal/types-builder": "0.0.116",
|
|
36
|
+
"@mastra/core": "1.76.0-alpha.3",
|
|
37
37
|
"@observability/test-utils": "0.1.0",
|
|
38
|
-
"@
|
|
38
|
+
"@internal/lint": "0.0.141"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@mastra/core": ">=1.16.0-0 <2.0.0-0"
|