@mastra/braintrust 1.3.6-alpha.1 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/index.cjs +32 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +32 -2
- package/dist/index.js.map +1 -1
- package/dist/tracing.d.ts +31 -0
- package/dist/tracing.d.ts.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @mastra/braintrust
|
|
2
2
|
|
|
3
|
+
## 1.3.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed suspended and resumed workflow runs appearing as two disconnected traces in Braintrust. Suspended and resumed workflow runs now appear in one Braintrust trace, with the resumed run nested under the span it was suspended from. As part of this change, Braintrust traces are grouped by the Mastra trace ID instead of a random ID, so multiple runs that share an explicitly provided trace ID now appear as one Braintrust trace. Fixes [#20771](https://github.com/mastra-ai/mastra/issues/20771). ([#21047](https://github.com/mastra-ai/mastra/pull/21047))
|
|
8
|
+
|
|
9
|
+
**Upgrade note:** a workflow run that was suspended before this upgrade and resumed after it still appears as two Braintrust traces — the older half was grouped under a random ID that the new version cannot recover — and the resumed half may display without a root span. This affects only runs in flight across the upgrade; runs suspended and resumed on the same version are unaffected. If this matters for your traces, drain suspended runs before upgrading.
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`587f6ef`](https://github.com/mastra-ai/mastra/commit/587f6efcfc25880b93760a8607d1cd381ec612fe), [`7e096f0`](https://github.com/mastra-ai/mastra/commit/7e096f02f0dddbf09b85d306458351245ed2f886), [`d7e6745`](https://github.com/mastra-ai/mastra/commit/d7e67456954863c55440ea9c49bc6ceb9949972d), [`6223446`](https://github.com/mastra-ai/mastra/commit/6223446ddce6166e96e0ba5e00d628b615dee8ca), [`15101bb`](https://github.com/mastra-ai/mastra/commit/15101bb53c0d934f31af6b8813b88191e382a5e5), [`4e7a421`](https://github.com/mastra-ai/mastra/commit/4e7a421dce8a48742f785d1e93ad2f43a572b282), [`c2c3deb`](https://github.com/mastra-ai/mastra/commit/c2c3debcf670c7082d0a5e553aa99818a864698c), [`4077487`](https://github.com/mastra-ai/mastra/commit/407748777c9bc9d5795033f36220e97df5553355), [`d8308a2`](https://github.com/mastra-ai/mastra/commit/d8308a2be3c07e777393d1017a381dcae3890d30), [`b0a2a07`](https://github.com/mastra-ai/mastra/commit/b0a2a07800d42bd9823292e7db832374ed084c9c), [`74e5bd3`](https://github.com/mastra-ai/mastra/commit/74e5bd315b8b3a1e04cb6cf480bb0f5fc4951dc8), [`242e324`](https://github.com/mastra-ai/mastra/commit/242e3241e73cbd5c9bb86a31ebb49ca0256488d4), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`d774e89`](https://github.com/mastra-ai/mastra/commit/d774e8930c781df8c9effe3763e6b501c099b6cc), [`9c27a53`](https://github.com/mastra-ai/mastra/commit/9c27a53cd9d3de4f3f025bc387d94ce371c33f95), [`8f0a332`](https://github.com/mastra-ai/mastra/commit/8f0a3321bf180368d76fe7b36aa1a8f60f00b6de), [`d6c618f`](https://github.com/mastra-ai/mastra/commit/d6c618f37496f801827d4caa76feab742f1a7383), [`0b4f108`](https://github.com/mastra-ai/mastra/commit/0b4f1089aa8d92e67c2a8e99726822c5ee410784), [`9acb50f`](https://github.com/mastra-ai/mastra/commit/9acb50f71cec9c362f06820033f90ae6b1f8282f), [`46e9e3f`](https://github.com/mastra-ai/mastra/commit/46e9e3f73babe1bc70080a596cf2ac0b9da48519), [`3f9a190`](https://github.com/mastra-ai/mastra/commit/3f9a19057c027155867b9317294ee4ca7bd0581a), [`dff25a1`](https://github.com/mastra-ai/mastra/commit/dff25a1103fa72ee082a9b6f805ebeb5ce400753), [`6db7a5d`](https://github.com/mastra-ai/mastra/commit/6db7a5dd3dd2b6f7ef75dcd804fcffef5fa83963), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`583e235`](https://github.com/mastra-ai/mastra/commit/583e23519c13af16c1746f9c49722d011216611b), [`b098de9`](https://github.com/mastra-ai/mastra/commit/b098de9d7cb9f672e0883a5c716465a3a689693d), [`e8808e3`](https://github.com/mastra-ai/mastra/commit/e8808e3d8eb585a2565be53e56a7e0e1477352a4), [`a77f8d4`](https://github.com/mastra-ai/mastra/commit/a77f8d4740d2178a74c41e4bf678b4fcd8fa0bb2), [`7f78585`](https://github.com/mastra-ai/mastra/commit/7f785857e401570e2ffb316911f126ed363aa537), [`33374ba`](https://github.com/mastra-ai/mastra/commit/33374ba359e4fb13eaa918ae925fe167a3c55414), [`940bf5c`](https://github.com/mastra-ai/mastra/commit/940bf5ccf04f2c9ebd8a1390431733222a03b1cd), [`c549e2f`](https://github.com/mastra-ai/mastra/commit/c549e2f40edc1cac5d9e74e82f90da22b48df084), [`58c43d3`](https://github.com/mastra-ai/mastra/commit/58c43d3f7cb2eeaeb8ac733ae71dde822348e588), [`ef6e295`](https://github.com/mastra-ai/mastra/commit/ef6e295b59bc25a5b61b633a89c97bcfce9fb465), [`208e1b3`](https://github.com/mastra-ai/mastra/commit/208e1b39f30f4b386e494394e9d71d96f0f90241), [`c938d34`](https://github.com/mastra-ai/mastra/commit/c938d34739936c8ecbabd67ad6a4a4396f41c4c6), [`88ddc7c`](https://github.com/mastra-ai/mastra/commit/88ddc7ce01d40175f13a3228b789a906779680bd), [`f2a4afd`](https://github.com/mastra-ai/mastra/commit/f2a4afd7e37e809669001ed17724b341a5c1f45e), [`d438148`](https://github.com/mastra-ai/mastra/commit/d438148e222c1e2fb3c652725ce75680962ebec4), [`ba05fe0`](https://github.com/mastra-ai/mastra/commit/ba05fe0738f70cb686777546e968237d09269142), [`40d358e`](https://github.com/mastra-ai/mastra/commit/40d358e29d55543803e64b49241122f598ffabc7), [`d26a8d4`](https://github.com/mastra-ai/mastra/commit/d26a8d4281f28414715b333c85bedaf70d0b2890), [`e80cd7e`](https://github.com/mastra-ai/mastra/commit/e80cd7e7683e7d732e1cc6784bcac1d2640d2ce3), [`ccbbcd9`](https://github.com/mastra-ai/mastra/commit/ccbbcd974eedff4367a54ed0e24c9ee742ab2f61), [`1d9a0ea`](https://github.com/mastra-ai/mastra/commit/1d9a0ea4a9901baee6cd56737243bd6d1f631ac0), [`677cdc6`](https://github.com/mastra-ai/mastra/commit/677cdc6af564dec29a13464d12b7ab2a4efc22e9), [`c549e2f`](https://github.com/mastra-ai/mastra/commit/c549e2f40edc1cac5d9e74e82f90da22b48df084), [`a7dd322`](https://github.com/mastra-ai/mastra/commit/a7dd32247d95afc539f483ca37f4594af0387f59), [`3f5c6f7`](https://github.com/mastra-ai/mastra/commit/3f5c6f728ea35da344248de9aa070f12849f3aa0), [`a318490`](https://github.com/mastra-ai/mastra/commit/a318490e17da32f338d50929c770d901a9b3dd72), [`b860493`](https://github.com/mastra-ai/mastra/commit/b86049391100e665d579f700c8a2034c036defc3), [`d4be8c1`](https://github.com/mastra-ai/mastra/commit/d4be8c1739d22d621e3f78790e1dd5eb5ecc3589), [`a5d2eb1`](https://github.com/mastra-ai/mastra/commit/a5d2eb10347eade1ae2816d88f466c25186c54a5), [`3667679`](https://github.com/mastra-ai/mastra/commit/3667679db057edfb086846d13369fdda4902ad65), [`49696e8`](https://github.com/mastra-ai/mastra/commit/49696e8e42f870674a0a58f5abcd22cc54dd2864), [`2ef2f23`](https://github.com/mastra-ai/mastra/commit/2ef2f230a7aed342e7dc3b2000cd42e4c43e08a7), [`4c0b961`](https://github.com/mastra-ai/mastra/commit/4c0b9611e89c5097b7a02a28c6816e0adcbcfa09), [`763e0c6`](https://github.com/mastra-ai/mastra/commit/763e0c61e04d76ad9a9efd301aa57525ca0cbea9), [`20504b2`](https://github.com/mastra-ai/mastra/commit/20504b2ecebd0e077acda3d457ab57480a98ed3e), [`77e6b1b`](https://github.com/mastra-ai/mastra/commit/77e6b1bc4c46ce94fe501023fb4393c812ec6be3), [`c5f964d`](https://github.com/mastra-ai/mastra/commit/c5f964d3f77064e978f8066ec506eed77ba5c63c), [`23e0be2`](https://github.com/mastra-ai/mastra/commit/23e0be261381e49534b4ff3101c60ee64a946cbf), [`7fc8806`](https://github.com/mastra-ai/mastra/commit/7fc880627d3cbf995d31ea0e8b807bf15417e651), [`0e02eac`](https://github.com/mastra-ai/mastra/commit/0e02eacdb2e30e1697a41910b41163742a181dc1), [`4df174c`](https://github.com/mastra-ai/mastra/commit/4df174c32bddf093a82f273070b8380aef7c9e90), [`f7c25b5`](https://github.com/mastra-ai/mastra/commit/f7c25b5106ddfb48e591f98df7a51e0f2dd01dba), [`7aad631`](https://github.com/mastra-ai/mastra/commit/7aad631b43bc10db77d5b8c66b200d7a49d18bf2), [`512100a`](https://github.com/mastra-ai/mastra/commit/512100a7d8b7e9c920f2590c6b3612f5de0d3cff), [`e81744c`](https://github.com/mastra-ai/mastra/commit/e81744cd13c46619c142dc521dc0baac47607a84), [`f8f653f`](https://github.com/mastra-ai/mastra/commit/f8f653f10980d01a73706cc3c8689ca5e40ce808), [`dc09cc1`](https://github.com/mastra-ai/mastra/commit/dc09cc1083d861cde192c1cd235324dc75b8c731), [`e81744c`](https://github.com/mastra-ai/mastra/commit/e81744cd13c46619c142dc521dc0baac47607a84), [`9ef432b`](https://github.com/mastra-ai/mastra/commit/9ef432b6faa534b57b0d182a610e13dd9a7123ff), [`36b4649`](https://github.com/mastra-ai/mastra/commit/36b4649045a3a380cbab8ceca866db4086223aff), [`b9cf308`](https://github.com/mastra-ai/mastra/commit/b9cf30846f97f99ac1906ee8a68f4f2d117b0378), [`2e1d098`](https://github.com/mastra-ai/mastra/commit/2e1d0984e325fd319d32ea182f596b3170be3847), [`377eb81`](https://github.com/mastra-ai/mastra/commit/377eb81ce43b964e3a6b541df172da74a8ff3716), [`1794a79`](https://github.com/mastra-ai/mastra/commit/1794a79178c418004a7261b1ad9114066f7ef01d), [`0cdc5dc`](https://github.com/mastra-ai/mastra/commit/0cdc5dc69024957815da4f51acc4119eb4f447d7), [`5740ec6`](https://github.com/mastra-ai/mastra/commit/5740ec60c760ffdfbfaa59d603d03b847c864e05)]:
|
|
12
|
+
- @mastra/core@1.60.0
|
|
13
|
+
- @mastra/observability@1.17.1
|
|
14
|
+
|
|
15
|
+
## 1.3.6-alpha.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Fixed suspended and resumed workflow runs appearing as two disconnected traces in Braintrust. Suspended and resumed workflow runs now appear in one Braintrust trace, with the resumed run nested under the span it was suspended from. As part of this change, Braintrust traces are grouped by the Mastra trace ID instead of a random ID, so multiple runs that share an explicitly provided trace ID now appear as one Braintrust trace. Fixes [#20771](https://github.com/mastra-ai/mastra/issues/20771). ([#21047](https://github.com/mastra-ai/mastra/pull/21047))
|
|
20
|
+
|
|
21
|
+
**Upgrade note:** a workflow run that was suspended before this upgrade and resumed after it still appears as two Braintrust traces — the older half was grouped under a random ID that the new version cannot recover — and the resumed half may display without a root span. This affects only runs in flight across the upgrade; runs suspended and resumed on the same version are unaffected. If this matters for your traces, drain suspended runs before upgrading.
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [[`4e7a421`](https://github.com/mastra-ai/mastra/commit/4e7a421dce8a48742f785d1e93ad2f43a572b282), [`4077487`](https://github.com/mastra-ai/mastra/commit/407748777c9bc9d5795033f36220e97df5553355), [`242e324`](https://github.com/mastra-ai/mastra/commit/242e3241e73cbd5c9bb86a31ebb49ca0256488d4), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`d774e89`](https://github.com/mastra-ai/mastra/commit/d774e8930c781df8c9effe3763e6b501c099b6cc), [`9c27a53`](https://github.com/mastra-ai/mastra/commit/9c27a53cd9d3de4f3f025bc387d94ce371c33f95), [`dff25a1`](https://github.com/mastra-ai/mastra/commit/dff25a1103fa72ee082a9b6f805ebeb5ce400753), [`217e967`](https://github.com/mastra-ai/mastra/commit/217e9672d8b3160eb729d8e9f0044949e88da239), [`7f78585`](https://github.com/mastra-ai/mastra/commit/7f785857e401570e2ffb316911f126ed363aa537), [`f2a4afd`](https://github.com/mastra-ai/mastra/commit/f2a4afd7e37e809669001ed17724b341a5c1f45e), [`d438148`](https://github.com/mastra-ai/mastra/commit/d438148e222c1e2fb3c652725ce75680962ebec4), [`ba05fe0`](https://github.com/mastra-ai/mastra/commit/ba05fe0738f70cb686777546e968237d09269142), [`d26a8d4`](https://github.com/mastra-ai/mastra/commit/d26a8d4281f28414715b333c85bedaf70d0b2890), [`677cdc6`](https://github.com/mastra-ai/mastra/commit/677cdc6af564dec29a13464d12b7ab2a4efc22e9), [`a318490`](https://github.com/mastra-ai/mastra/commit/a318490e17da32f338d50929c770d901a9b3dd72), [`763e0c6`](https://github.com/mastra-ai/mastra/commit/763e0c61e04d76ad9a9efd301aa57525ca0cbea9), [`23e0be2`](https://github.com/mastra-ai/mastra/commit/23e0be261381e49534b4ff3101c60ee64a946cbf), [`7fc8806`](https://github.com/mastra-ai/mastra/commit/7fc880627d3cbf995d31ea0e8b807bf15417e651), [`0e02eac`](https://github.com/mastra-ai/mastra/commit/0e02eacdb2e30e1697a41910b41163742a181dc1), [`4df174c`](https://github.com/mastra-ai/mastra/commit/4df174c32bddf093a82f273070b8380aef7c9e90), [`f7c25b5`](https://github.com/mastra-ai/mastra/commit/f7c25b5106ddfb48e591f98df7a51e0f2dd01dba), [`dc09cc1`](https://github.com/mastra-ai/mastra/commit/dc09cc1083d861cde192c1cd235324dc75b8c731), [`36b4649`](https://github.com/mastra-ai/mastra/commit/36b4649045a3a380cbab8ceca866db4086223aff), [`377eb81`](https://github.com/mastra-ai/mastra/commit/377eb81ce43b964e3a6b541df172da74a8ff3716)]:
|
|
24
|
+
- @mastra/core@1.60.0-alpha.8
|
|
25
|
+
- @mastra/observability@1.17.1-alpha.2
|
|
26
|
+
|
|
3
27
|
## 1.3.6-alpha.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -295,13 +295,14 @@ var BraintrustExporter = class extends _mastra_observability.TrackingExporter {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
startSpan(args) {
|
|
298
|
-
const { parent, span } = args;
|
|
298
|
+
const { parent, span, parentSpanIds } = args;
|
|
299
299
|
const payload = this.buildSpanPayload(span);
|
|
300
300
|
const braintrustSpan = parent.startSpan({
|
|
301
301
|
spanId: span.id,
|
|
302
302
|
name: span.name,
|
|
303
303
|
type: mapSpanType(span.type),
|
|
304
304
|
startTime: span.startTime.getTime() / 1e3,
|
|
305
|
+
...parentSpanIds ? { parentSpanIds } : {},
|
|
305
306
|
event: {
|
|
306
307
|
id: span.id,
|
|
307
308
|
...payload
|
|
@@ -315,6 +316,34 @@ var BraintrustExporter = class extends _mastra_observability.TrackingExporter {
|
|
|
315
316
|
pendingToolResults: isModelGeneration ? /* @__PURE__ */ new Map() : void 0
|
|
316
317
|
};
|
|
317
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Explicit parentage for a root span started directly from a logger.
|
|
321
|
+
*
|
|
322
|
+
* Pinning `rootSpanId` to the Mastra trace ID makes every root sharing a
|
|
323
|
+
* trace land in the same Braintrust trace — required for a resumed workflow
|
|
324
|
+
* run to rejoin the trace of its suspended half, which was exported by an
|
|
325
|
+
* earlier process. Genuine roots keep empty `span_parents` so Braintrust
|
|
326
|
+
* still treats them as trace roots; only a resumed continuation (marked by
|
|
327
|
+
* core with `resumedFromSpanId`) links to its persisted parent span. Roots
|
|
328
|
+
* with other parent IDs (e.g. an ambient OTEL span from the bridge) are not
|
|
329
|
+
* linked — their parent was never exported to Braintrust.
|
|
330
|
+
*
|
|
331
|
+
* Spans nested under an enclosing Braintrust span (Eval(), logger.traced(),
|
|
332
|
+
* or a span passed as `braintrustLogger`) get no explicit parentage: the
|
|
333
|
+
* startSpan() chain already inherits the external trace's rootSpanId.
|
|
334
|
+
*/
|
|
335
|
+
rootParentSpanIds(root, span) {
|
|
336
|
+
const kind = root.kind;
|
|
337
|
+
if (!(kind ? kind === "logger" : !("end" in root))) return;
|
|
338
|
+
if (span.parentSpanId && span.parentSpanId === span.metadata?.resumedFromSpanId) return {
|
|
339
|
+
spanId: span.parentSpanId,
|
|
340
|
+
rootSpanId: span.traceId
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
parentSpanIds: [],
|
|
344
|
+
rootSpanId: span.traceId
|
|
345
|
+
};
|
|
346
|
+
}
|
|
318
347
|
async _buildRoot(_args) {
|
|
319
348
|
if (this.#useProvidedLogger) {
|
|
320
349
|
let externalSpan;
|
|
@@ -334,7 +363,8 @@ var BraintrustExporter = class extends _mastra_observability.TrackingExporter {
|
|
|
334
363
|
const root = traceData.getRoot();
|
|
335
364
|
if (root) return this.startSpan({
|
|
336
365
|
parent: root,
|
|
337
|
-
span
|
|
366
|
+
span,
|
|
367
|
+
parentSpanIds: this.rootParentSpanIds(root, span)
|
|
338
368
|
});
|
|
339
369
|
} else {
|
|
340
370
|
const parent = traceData.getParent(args);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["SpanType","TrackingExporter","#useProvidedLogger","#providedLogger","#localLogger"],"sources":["../src/formatter.ts","../src/metrics.ts","../src/thread-reconstruction.ts","../src/tracing.ts"],"sourcesContent":["/**\n * Message format conversion utilities for Braintrust.\n *\n * Converts AI SDK message format (v4/v5) to OpenAI Chat Completion format,\n * which Braintrust requires for proper rendering of threads.\n */\n\n// ==============================================================================\n// Utility functions\n// ==============================================================================\n\n/**\n * Remove null and undefined values from an object (shallow)\n */\nexport function removeNullish<T extends Record<string, unknown>>(obj: T): Partial<T> {\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) as Partial<T>;\n}\n\n// ==============================================================================\n// Type definitions for AI SDK message format conversion to OpenAI format\n// ==============================================================================\n\n/**\n * AI SDK content part types (both v4 and v5)\n */\ninterface AISDKTextPart {\n type: 'text';\n text: string;\n}\n\ninterface AISDKImagePart {\n type: 'image';\n image?: string | Uint8Array | URL;\n mimeType?: string;\n}\n\ninterface AISDKFilePart {\n type: 'file';\n data?: string | Uint8Array | URL;\n filename?: string;\n name?: string;\n mimeType?: string;\n}\n\ninterface AISDKReasoningPart {\n type: 'reasoning';\n text?: string;\n}\n\ninterface AISDKToolCallPart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n args?: unknown; // AI SDK v4\n input?: unknown; // AI SDK v5\n}\n\ninterface AISDKToolResultPart {\n type: 'tool-result';\n toolCallId: string;\n result?: unknown; // AI SDK v4\n output?: unknown; // AI SDK v5\n}\n\ntype AISDKContentPart =\n | AISDKTextPart\n | AISDKImagePart\n | AISDKFilePart\n | AISDKReasoningPart\n | AISDKToolCallPart\n | AISDKToolResultPart\n | { type: string; [key: string]: unknown }; // Catch-all for unknown types\n\n/**\n * AI SDK message format (input format for conversion)\n */\ninterface AISDKMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string | AISDKContentPart[];\n [key: string]: unknown; // Allow additional properties\n}\n\n/**\n * OpenAI Chat Completion tool call format\n */\nexport interface OpenAIToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\n/**\n * OpenAI Chat Completion message format (output format)\n */\nexport interface OpenAIMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n [key: string]: unknown; // Allow additional properties\n}\n\n// ==============================================================================\n// Message conversion functions\n// ==============================================================================\n\n/**\n * Converts a content part to a string representation.\n * Handles text, image, file, reasoning, and other content types.\n */\nfunction convertContentPart(part: AISDKContentPart | null | undefined): string | null {\n if (!part || typeof part !== 'object') {\n return null;\n }\n\n switch (part.type) {\n case 'text':\n return (part as AISDKTextPart).text || null;\n\n case 'image':\n // Represent image content with a placeholder\n return '[image]';\n\n case 'file': {\n // Represent file content with filename if available\n const filePart = part as AISDKFilePart;\n if (filePart.filename || filePart.name) {\n return `[file: ${filePart.filename || filePart.name}]`;\n }\n return '[file]';\n }\n\n case 'reasoning': {\n // Represent reasoning/thinking content\n const reasoningPart = part as AISDKReasoningPart;\n if (typeof reasoningPart.text === 'string' && reasoningPart.text.length > 0) {\n return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? '...' : ''}]`;\n }\n return '[reasoning]';\n }\n\n case 'tool-call':\n // Tool calls are handled separately in assistant messages\n return null;\n\n case 'tool-result':\n // Tool results are handled separately in tool messages\n return null;\n\n default: {\n // For unknown types, try to extract any text-like content\n const unknownPart = part as { type?: string; text?: string; content?: string };\n if (typeof unknownPart.text === 'string') {\n return unknownPart.text;\n }\n if (typeof unknownPart.content === 'string') {\n return unknownPart.content;\n }\n // Represent unknown content type\n return `[${unknownPart.type || 'unknown'}]`;\n }\n }\n}\n\n/**\n * Serializes tool result data to a string for OpenAI format.\n */\nexport function serializeToolResult(resultData: unknown): string {\n if (typeof resultData === 'string') {\n return resultData;\n }\n if (resultData && typeof resultData === 'object' && 'value' in resultData) {\n const valueData = (resultData as { value: unknown }).value;\n return typeof valueData === 'string' ? valueData : JSON.stringify(valueData);\n }\n if (resultData === undefined || resultData === null) {\n return '';\n }\n try {\n return JSON.stringify(resultData);\n } catch {\n return '[unserializable result]';\n }\n}\n\n/**\n * Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.\n *\n * Supports both AI SDK v4 and v5 formats:\n * - v4 uses 'args' for tool calls and 'result' for tool results\n * - v5 uses 'input' for tool calls and 'output' for tool results\n *\n * AI SDK format:\n * { role: \"user\", content: [{ type: \"text\", text: \"hello\" }] }\n * { role: \"assistant\", content: [{ type: \"text\", text: \"...\" }, { type: \"tool-call\", toolCallId: \"...\", toolName: \"...\", args: {...} }] }\n * { role: \"tool\", content: [{ type: \"tool-result\", toolCallId: \"...\", result: {...} }] }\n *\n * OpenAI format (what Braintrust expects):\n * { role: \"user\", content: \"hello\" }\n * { role: \"assistant\", content: \"...\", tool_calls: [{ id: \"...\", type: \"function\", function: { name: \"...\", arguments: \"...\" } }] }\n * { role: \"tool\", content: \"result\", tool_call_id: \"...\" }\n */\nexport function convertAISDKMessage(message: AISDKMessage | OpenAIMessage | unknown): OpenAIMessage | unknown {\n if (!message || typeof message !== 'object') {\n return message;\n }\n\n const { role, content, ...rest } = message as AISDKMessage;\n\n // If content is already a string, return as-is (already in OpenAI format)\n if (typeof content === 'string') {\n return message;\n }\n\n // If content is an array (AI SDK format), convert based on role\n if (Array.isArray(content)) {\n // Handle empty content arrays\n if (content.length === 0) {\n return { role, content: '', ...rest };\n }\n\n // For user/system messages, extract text and represent non-text content\n if (role === 'user' || role === 'system') {\n const contentParts = content.map((part: AISDKContentPart) => convertContentPart(part)).filter(Boolean);\n\n return {\n role,\n content: contentParts.length > 0 ? contentParts.join('\\n') : '',\n ...rest,\n };\n }\n\n // For assistant messages, extract text, non-text content, AND tool calls\n if (role === 'assistant') {\n const contentParts = content\n .filter((part: AISDKContentPart) => part?.type !== 'tool-call')\n .map((part: AISDKContentPart) => convertContentPart(part))\n .filter(Boolean);\n\n const toolCallParts = content.filter((part: AISDKContentPart) => part?.type === 'tool-call');\n\n const result: OpenAIMessage = {\n role,\n content: contentParts.length > 0 ? (contentParts as string[]).join('\\n') : '',\n ...rest,\n };\n\n // Add tool_calls array if there are tool calls\n if (toolCallParts.length > 0) {\n result.tool_calls = toolCallParts.map((tc: AISDKContentPart) => {\n const toolCall = tc as AISDKToolCallPart;\n const toolCallId = toolCall.toolCallId;\n const toolName = toolCall.toolName;\n // Support both v4 'args' and v5 'input'\n const args = toolCall.args ?? toolCall.input;\n\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else if (args !== undefined && args !== null) {\n argsString = JSON.stringify(args);\n } else {\n argsString = '{}';\n }\n\n return {\n id: toolCallId,\n type: 'function' as const,\n function: {\n name: toolName,\n arguments: argsString,\n },\n };\n });\n }\n\n return result;\n }\n\n // For tool messages, convert to OpenAI tool message format\n if (role === 'tool') {\n const toolResult = content.find((part): part is AISDKToolResultPart => part?.type === 'tool-result');\n if (toolResult) {\n // Support both v4 'result' and v5 'output' fields\n const resultData = toolResult.output ?? toolResult.result;\n const resultContent = serializeToolResult(resultData);\n\n return {\n role: 'tool',\n content: resultContent,\n tool_call_id: toolResult.toolCallId,\n } as OpenAIMessage;\n }\n }\n }\n\n return message;\n}\n","import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * BraintrustUsageMetrics\n *\n * Canonical metric keys expected by Braintrust for LLM usage accounting.\n */\nexport interface BraintrustUsageMetrics {\n prompt_tokens?: number;\n completion_tokens?: number;\n tokens?: number;\n completion_reasoning_tokens?: number;\n prompt_cached_tokens?: number;\n prompt_cache_creation_tokens?: number;\n}\n\n/**\n * Formats UsageStats to Braintrust's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): BraintrustUsageMetrics {\n const metrics: BraintrustUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.prompt_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.completion_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.prompt_tokens !== undefined && metrics.completion_tokens !== undefined) {\n metrics.tokens = metrics.prompt_tokens + metrics.completion_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.completion_reasoning_tokens = usage.outputDetails.reasoning;\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.prompt_cached_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.prompt_cache_creation_tokens = usage.inputDetails.cacheWrite;\n }\n\n return metrics;\n}\n","/**\n * Thread view reconstruction for Braintrust.\n *\n * Reconstructs LLM output in OpenAI Chat Completion format by examining\n * child MODEL_STEP and TOOL_CALL spans. This enables Braintrust's Thread\n * view to properly display the full conversation flow including tool calls.\n *\n * See THREAD_VIEW_RECONSTRUCTION.md for details.\n */\n\nimport { removeNullish } from './formatter';\nimport type { OpenAIMessage } from './formatter';\n\n// ==============================================================================\n// Thread view reconstruction types\n// ==============================================================================\n\n/**\n * Tool call data accumulated from MODEL_STEP and TOOL_CALL spans\n */\nexport interface ThreadToolCall {\n toolCallId: string;\n toolName: string;\n args: unknown;\n result?: unknown; // filled in when TOOL_CALL ends\n startTime?: Date; // from TOOL_CALL span, for ordering multiple tool calls within a step\n}\n\n/**\n * Step data accumulated from MODEL_STEP spans for Thread view reconstruction\n */\nexport interface ThreadStepData {\n stepSpanId: string;\n stepIndex: number; // for ordering steps correctly\n text?: string;\n toolCalls?: ThreadToolCall[];\n}\n\n/**\n * Accumulated data for reconstructing Braintrust Thread view.\n * Populated for MODEL_GENERATION spans as child MODEL_STEP and TOOL_CALL spans complete.\n */\nexport type ThreadData = ThreadStepData[];\n\n/**\n * Tool result data stored when TOOL_CALL spans end (before MODEL_STEP ends)\n */\nexport interface PendingToolResult {\n result: unknown;\n startTime: Date;\n}\n\n// ==============================================================================\n// Thread view reconstruction\n// ==============================================================================\n\n/**\n * Reconstruct the Thread view output from accumulated threadData.\n * Converts to OpenAI Chat Completion message format.\n */\nexport function reconstructThreadOutput(threadData: ThreadData, originalOutput: unknown): OpenAIMessage[] {\n const messages: OpenAIMessage[] = [];\n\n // Sort steps by stepIndex\n const sortedSteps = [...threadData].sort((a, b) => a.stepIndex - b.stepIndex);\n\n for (const step of sortedSteps) {\n // Sort tool calls by startTime within each step\n const sortedToolCalls = step.toolCalls\n ? [...step.toolCalls].sort((a, b) => {\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.getTime() - b.startTime.getTime();\n })\n : [];\n\n if (sortedToolCalls.length > 0) {\n // Add assistant message with tool_calls\n messages.push({\n role: 'assistant',\n content: step.text || '',\n tool_calls: sortedToolCalls.map(tc => {\n // Clean null/undefined values from args before stringifying\n const cleanArgs =\n tc.args && typeof tc.args === 'object' && !Array.isArray(tc.args)\n ? removeNullish(tc.args as Record<string, unknown>)\n : tc.args;\n return {\n id: tc.toolCallId,\n type: 'function' as const,\n function: {\n name: tc.toolName,\n arguments: typeof cleanArgs === 'string' ? cleanArgs : JSON.stringify(cleanArgs),\n },\n };\n }),\n });\n\n // Add tool messages for each result\n for (const tc of sortedToolCalls) {\n if (tc.result !== undefined) {\n messages.push({\n role: 'tool',\n content: typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result),\n tool_call_id: tc.toolCallId,\n });\n }\n }\n } else if (step.text) {\n // Step with text only (final response)\n messages.push({\n role: 'assistant',\n content: step.text,\n });\n }\n }\n\n // If we have messages and the last one is a tool message,\n // add the final assistant text from original output\n if (messages.length > 0) {\n const lastMessage = messages[messages.length - 1]!;\n const originalText = (originalOutput as { text?: string })?.text;\n\n // If the last message is a tool response and we have final text, add it\n if (originalText && lastMessage.role === 'tool') {\n messages.push({\n role: 'assistant',\n content: originalText,\n });\n }\n }\n\n return messages;\n}\n","/**\n * Braintrust Exporter for Mastra Observability\n *\n * This exporter sends observability data to Braintrust.\n * Root spans become top-level Braintrust spans (no trace wrapper).\n * Events are handled as zero-duration spans with matching start/end times.\n */\n\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport { removeNullish, convertAISDKMessage, serializeToolResult } from './formatter';\nimport type { OpenAIMessage } from './formatter';\nimport { formatUsageMetrics } from './metrics';\nimport { reconstructThreadOutput } from './thread-reconstruction';\nimport type { ThreadData, ThreadStepData, PendingToolResult } from './thread-reconstruction';\n\ntype BraintrustSpanType = 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool';\n\n/**\n * The subset of a Braintrust span used by the exporter.\n *\n * This structural interface lets applications provide spans from a different\n * compatible Braintrust SDK instance without coupling Mastra's public types to\n * a specific SDK version.\n */\nexport interface BraintrustSpan {\n id: string;\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n event: Record<string, any>;\n }): BraintrustSpan;\n log(event: Record<string, any>): void;\n end(args?: { endTime?: number }): number;\n}\n\n/**\n * The subset of a Braintrust logger used by the exporter.\n *\n * Logger instances from compatible Braintrust SDK versions satisfy this\n * interface without sharing the same nominal SDK types as the exporter.\n */\nexport interface BraintrustLogger {\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n event: Record<string, any>;\n }): BraintrustSpan;\n logFeedback(event: {\n id: string;\n scores: Record<string, number>;\n comment?: string;\n metadata?: Record<string, any>;\n source: 'external';\n }): void;\n}\n\n/**\n * Extended Braintrust span data that includes span type and thread reconstruction data\n */\ninterface BraintrustSpanData {\n span: BraintrustSpan;\n spanType: SpanType;\n threadData?: ThreadData; // only populated for MODEL_GENERATION spans\n // Tool results stored when TOOL_CALL ends (may arrive before MODEL_STEP ends)\n pendingToolResults?: Map<string, PendingToolResult>; // keyed by toolCallId\n}\n\nexport interface BraintrustExporterConfig extends TrackingExporterConfig {\n /**\n * Optional Braintrust logger instance.\n * When provided, enables integration with Braintrust contexts such as:\n * - Evals: Agent traces nest inside eval task spans\n * - logger.traced(): Agent traces nest inside traced spans\n * - Parent spans: Auto-detects and attaches to external Braintrust spans\n */\n braintrustLogger?: BraintrustLogger;\n\n /**\n * Optional resolver for the active Braintrust span.\n *\n * Pass Braintrust's `currentSpan` from the same package instance that creates\n * `Eval()` or `logger.traced()` spans when your app and Mastra may resolve\n * different copies of the `braintrust` package.\n */\n currentSpan?: () => BraintrustSpan | undefined;\n\n /** Braintrust API key. Required if logger is not provided. */\n apiKey?: string;\n /** Optional custom endpoint */\n endpoint?: string;\n /** Braintrust project name (default: 'mastra-tracing') */\n projectName?: string;\n /** Support tuning parameters */\n tuningParameters?: Record<string, any>;\n}\n\ntype BraintrustRoot = BraintrustLogger | BraintrustSpan;\ntype BraintrustTrackedSpan = BraintrustSpanData;\ntype BraintrustEvent = BraintrustSpan;\ntype BraintrustMetadata = unknown;\ntype BraintrustTraceData = TraceData<BraintrustRoot, BraintrustTrackedSpan, BraintrustEvent, BraintrustMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'task';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, string>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'function',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'function',\n};\n\n// Mapping function - returns valid Braintrust span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool' {\n return (SPAN_TYPE_EXCEPTIONS[spanType] as any) ?? DEFAULT_SPAN_TYPE;\n}\n\nexport class BraintrustExporter extends TrackingExporter<\n BraintrustRoot,\n BraintrustTrackedSpan,\n BraintrustEvent,\n BraintrustMetadata,\n BraintrustExporterConfig\n> {\n name = 'braintrust';\n\n // Flags and logger for context-aware mode\n #useProvidedLogger: boolean;\n #providedLogger?: BraintrustLogger;\n #localLogger?: BraintrustLogger;\n\n constructor(config: BraintrustExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const resolvedApiKey = config.apiKey ?? process.env.BRAINTRUST_API_KEY;\n const resolvedEndpoint = config.endpoint ?? process.env.BRAINTRUST_ENDPOINT;\n\n super({\n ...config,\n apiKey: resolvedApiKey,\n endpoint: resolvedEndpoint,\n });\n\n this.#useProvidedLogger = !!config.braintrustLogger;\n\n if (this.#useProvidedLogger) {\n // Use provided logger - enables Braintrust context integration\n this.#providedLogger = config.braintrustLogger;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!this.config.apiKey) {\n this.setDisabled(\n `Missing required API key. Set BRAINTRUST_API_KEY environment variable or pass apiKey in config.`,\n );\n return;\n }\n // lazy create logger on first rootSpan\n this.#localLogger = undefined;\n }\n }\n\n private async getLocalLogger(): Promise<BraintrustLogger | undefined> {\n if (this.#localLogger) {\n return this.#localLogger;\n }\n try {\n const logger = await initLogger({\n projectName: this.config.projectName ?? 'mastra-tracing',\n apiKey: this.config.apiKey,\n appUrl: this.config.endpoint,\n ...this.config.tuningParameters,\n });\n this.#localLogger = logger;\n return logger;\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (this.isDisabled) return;\n\n const { score } = event;\n const rowId = score.spanId ?? score.traceId;\n if (!rowId) {\n this.logger.debug('Braintrust exporter: skipping score with no spanId or traceId', {\n scorerId: score.scorerId,\n });\n return;\n }\n\n const logger = this.#useProvidedLogger ? this.#providedLogger : await this.getLocalLogger();\n if (!logger) return;\n\n const name = score.scorerName ?? score.scorerId;\n\n try {\n logger.logFeedback({\n id: rowId,\n scores: { [name]: score.score },\n ...(score.reason ? { comment: score.reason } : {}),\n metadata: {\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n ...(score.metadata ?? {}),\n },\n source: 'external',\n });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to submit score', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n private startSpan(args: { parent: BraintrustSpan | BraintrustLogger; span: AnyExportedSpan }): BraintrustSpanData {\n const { parent, span } = args;\n const payload = this.buildSpanPayload(span);\n\n // Braintrust's startSpan() accepts data properties via the `event` parameter\n // which maps to StartSpanEventArgs (ExperimentLogPartialArgs & Partial<IdField>)\n // This includes: input, output, metadata, metrics, tags, scores, error, etc.\n const braintrustSpan = parent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n event: {\n id: span.id, // Use Mastra span ID as Braintrust row ID for logFeedback() compatibility\n ...payload,\n },\n });\n\n // Create BraintrustSpanData with span type for tree walking\n // Initialize threadData and pendingToolResults for MODEL_GENERATION spans (used for Thread view reconstruction)\n const isModelGeneration = span.type === SpanType.MODEL_GENERATION;\n return {\n span: braintrustSpan,\n spanType: span.type,\n threadData: isModelGeneration ? [] : undefined,\n pendingToolResults: isModelGeneration ? new Map() : undefined,\n };\n }\n\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustRoot | undefined> {\n if (this.#useProvidedLogger) {\n // Try to find a Braintrust span to attach to:\n // 1. Auto-detect from Braintrust's current span (logger.traced(), Eval(), etc.)\n // 2. Fall back to the configured logger\n let externalSpan: BraintrustSpan | undefined;\n try {\n externalSpan = this.config.currentSpan?.();\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to resolve configured currentSpan', { error: err });\n }\n externalSpan ??= currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (externalSpan && externalSpan.id) {\n // External span detected - attach Mastra traces to it\n return externalSpan;\n } else {\n // No external span - use provided logger\n return this.#providedLogger!;\n }\n } else {\n // Use the local logger\n return this.getLocalLogger();\n }\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustSpanData | undefined> {\n const { span, traceData } = args;\n\n if (span.isRootSpan) {\n const root = traceData.getRoot();\n if (root) {\n return this.startSpan({ parent: root, span });\n }\n } else {\n const parent = traceData.getParent(args);\n if (parent) {\n // Parent could be BraintrustSpanData (has .span) or BraintrustRoot (Logger/Span, no .span)\n const parentSpan = 'span' in parent ? parent.span : parent;\n return this.startSpan({ parent: parentSpan, span });\n }\n }\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustEvent | undefined> {\n const spanData = await this._buildSpan(args);\n\n if (!spanData) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n spanData.span.end({ endTime: args.span.startTime.getTime() / 1000 });\n return spanData.span;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n spanData.span.log(this.buildSpanPayload(span, false));\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n\n // Handle thread data accumulation for MODEL_STEP and tool spans.\n // PROVIDER_TOOL_CALL is excluded: provider results are not merged into the\n // reconstructed thread output. Since @mastra/core parents these spans under\n // the delivering MODEL_STEP, findModelGenerationAncestor would now succeed,\n // so accumulating them is possible as a follow-up.\n if (span.type === SpanType.MODEL_STEP) {\n this.accumulateModelStepData(span, traceData);\n } else if (span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) {\n this.accumulateToolCallResult(span, traceData);\n }\n\n // Build payload - for MODEL_GENERATION, may reconstruct output from threadData\n const payload =\n span.type === SpanType.MODEL_GENERATION\n ? this.buildModelGenerationPayload(span, spanData)\n : this.buildSpanPayload(span, false);\n\n spanData.span.log(payload);\n\n if (span.endTime) {\n spanData.span.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n spanData.span.end();\n }\n }\n\n protected override async _abortSpan(args: { span: BraintrustTrackedSpan; reason: SpanErrorInfo }): Promise<void> {\n const { span: spanData, reason } = args;\n spanData.span.log({\n error: reason.message,\n metadata: { errorDetails: reason },\n });\n spanData.span.end();\n }\n\n // ==============================================================================\n // Thread view reconstruction helpers\n // ==============================================================================\n\n /**\n * Walk up the tree to find the MODEL_GENERATION ancestor span.\n * Returns the BraintrustSpanData if found, undefined otherwise.\n */\n private findModelGenerationAncestor(spanId: string, traceData: BraintrustTraceData): BraintrustSpanData | undefined {\n let currentId: string | undefined = spanId;\n\n while (currentId) {\n const parentId = traceData.getParentId({ spanId: currentId });\n if (!parentId) return undefined;\n\n const parentSpanData = traceData.getSpan({ spanId: parentId });\n if (parentSpanData?.spanType === SpanType.MODEL_GENERATION) {\n return parentSpanData;\n }\n currentId = parentId;\n }\n\n return undefined;\n }\n\n /**\n * Accumulate MODEL_STEP data to the parent MODEL_GENERATION's threadData.\n * Called when a MODEL_STEP span ends.\n */\n private accumulateModelStepData(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.threadData) {\n return;\n }\n\n // Extract step data from MODEL_STEP output and attributes\n const output = span.output as\n | { text?: string; toolCalls?: Array<{ toolCallId: string; toolName: string; args: unknown }> }\n | undefined;\n const attributes = span.attributes as { stepIndex?: number } | undefined;\n\n const stepData: ThreadStepData = {\n stepSpanId: span.id,\n stepIndex: attributes?.stepIndex ?? 0,\n text: output?.text,\n toolCalls: output?.toolCalls?.map(tc => ({\n toolCallId: tc.toolCallId,\n toolName: tc.toolName,\n args: tc.args,\n })),\n };\n\n modelGenSpanData.threadData.push(stepData);\n }\n\n /**\n * Store a tool result (TOOL_CALL or MCP_TOOL_CALL) in parent MODEL_GENERATION's pendingToolResults.\n * Called when the tool span ends.\n * Results are merged into threadData when MODEL_GENERATION ends.\n */\n private accumulateToolCallResult(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.pendingToolResults) {\n return;\n }\n\n const toolCallId = this.resolveToolCallId(span);\n\n // Store the result for later merging\n modelGenSpanData.pendingToolResults.set(toolCallId, {\n result: span.output,\n startTime: span.startTime,\n });\n }\n\n /**\n * Build the payload for MODEL_GENERATION span, reconstructing output from threadData if available.\n */\n private buildModelGenerationPayload(span: AnyExportedSpan, spanData: BraintrustSpanData): Record<string, any> {\n const basePayload = this.buildSpanPayload(span, false);\n\n // Check if we have threadData with tool calls to reconstruct\n const threadData = spanData.threadData;\n if (!threadData || threadData.length === 0) {\n return basePayload;\n }\n\n // Merge pending tool results into threadData\n if (spanData.pendingToolResults && spanData.pendingToolResults.size > 0) {\n for (const step of threadData) {\n if (step.toolCalls) {\n for (const toolCall of step.toolCalls) {\n const pendingResult = spanData.pendingToolResults.get(toolCall.toolCallId);\n if (pendingResult) {\n toolCall.result = pendingResult.result;\n toolCall.startTime = pendingResult.startTime;\n }\n }\n }\n }\n }\n\n // Check if any step has tool calls\n const hasToolCalls = threadData.some(step => step.toolCalls && step.toolCalls.length > 0);\n if (!hasToolCalls) {\n return basePayload;\n }\n\n // Reconstruct output as OpenAI messages\n const reconstructedOutput = reconstructThreadOutput(threadData, span.output);\n return {\n ...basePayload,\n output: reconstructedOutput,\n };\n }\n\n /**\n * Transforms MODEL_GENERATION input to Braintrust Thread view format.\n * Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires\n * for proper rendering of threads (fixes #11023).\n */\n private transformInput(input: unknown, spanType: SpanType): unknown {\n if (spanType === SpanType.MODEL_GENERATION) {\n // If input is already an array of messages, convert AI SDK format to OpenAI format\n if (Array.isArray(input)) {\n return input.map((msg: unknown) => convertAISDKMessage(msg));\n }\n\n // If input has a messages array\n if (\n input &&\n typeof input === 'object' &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown[] }).messages)\n ) {\n return (input as { messages: unknown[] }).messages.map((msg: unknown) => convertAISDKMessage(msg));\n }\n }\n\n return input;\n }\n\n /**\n * Transforms MODEL_GENERATION output to Braintrust Thread view format.\n */\n private transformOutput(output: any, spanType: SpanType): any {\n if (spanType === SpanType.MODEL_GENERATION) {\n if (!output || typeof output !== 'object') {\n return output;\n }\n const { text, ...rest } = output;\n // Remove null/undefined values from rest to keep Thread view clean\n return { role: 'assistant', content: text, ...removeNullish(rest) };\n }\n\n return output;\n }\n\n private isToolSpan(span: AnyExportedSpan): boolean {\n return (\n span.type === SpanType.TOOL_CALL ||\n span.type === SpanType.MCP_TOOL_CALL ||\n span.type === SpanType.PROVIDER_TOOL_CALL\n );\n }\n\n private getToolName(span: AnyExportedSpan): string {\n if (span.entityName) {\n return span.entityName;\n }\n const match = /'([^']+)'/.exec(span.name);\n return match?.[1] ?? span.name;\n }\n\n private resolveToolCallId(span: AnyExportedSpan): string {\n const attrs = span.attributes as { toolCallId?: string } | undefined;\n return (\n attrs?.toolCallId ??\n span.metadata?.toolCallId ??\n (span.input as { toolCallId?: string } | undefined)?.toolCallId ??\n span.id\n );\n }\n\n private toToolCallInput(span: AnyExportedSpan): OpenAIMessage[] {\n const args = span.input;\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else {\n try {\n argsString = JSON.stringify(args ?? {});\n } catch {\n argsString = '[unserializable result]';\n }\n }\n return [\n {\n role: 'assistant',\n content: '',\n tool_calls: [\n {\n id: this.resolveToolCallId(span),\n type: 'function',\n function: {\n name: this.getToolName(span),\n arguments: argsString,\n },\n },\n ],\n },\n ];\n }\n\n private toToolCallOutput(span: AnyExportedSpan): OpenAIMessage {\n return {\n role: 'tool',\n content: serializeToolResult(span.output),\n tool_call_id: this.resolveToolCallId(span),\n };\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate = true): Record<string, any> {\n const payload: Record<string, any> = {};\n\n const isToolType = this.isToolSpan(span);\n\n if (span.input !== undefined) {\n payload.input = isToolType ? this.toToolCallInput(span) : this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = isToolType ? this.toToolCallOutput(span) : this.transformOutput(span.output, span.type);\n }\n\n if (isCreate && span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n // Spread span.metadata first, then set spanType to prevent accidental override\n payload.metadata = {\n ...span.metadata,\n spanType: span.type,\n };\n\n if (isCreate) {\n payload.metadata['mastra-trace-id'] = span.traceId;\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // Model goes to metadata\n if (modelAttr.model !== undefined) {\n payload.metadata.model = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n payload.metadata.provider = modelAttr.provider;\n }\n\n // Prefer resolved model ID (e.g. \"claude-sonnet-4-5-20250929\") over\n // gateway aliases (e.g. \"claude-sonnet-4.5\") for accurate cost estimation\n if (modelAttr.responseModel !== undefined) {\n payload.metadata.model = modelAttr.responseModel;\n }\n\n // Usage/token info goes to metrics\n payload.metrics = formatUsageMetrics(modelAttr.usage);\n\n // Time to first token (TTFT) for streaming responses\n // Braintrust expects TTFT in seconds (not milliseconds)\n if (modelAttr.completionStartTime) {\n payload.metrics.time_to_first_token =\n (modelAttr.completionStartTime.getTime() - span.startTime.getTime()) / 1000;\n }\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, [\n 'model',\n 'responseModel',\n 'usage',\n 'parameters',\n 'completionStartTime',\n ]);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...attributes,\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n // Clean up empty metrics object\n if (Object.keys(payload.metrics).length === 0) {\n delete payload.metrics;\n }\n\n // Remove null/undefined values from metadata to keep Braintrust UI clean\n payload.metadata = removeNullish(payload.metadata);\n\n return payload;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["SpanType","TrackingExporter","#useProvidedLogger","#providedLogger","#localLogger"],"sources":["../src/formatter.ts","../src/metrics.ts","../src/thread-reconstruction.ts","../src/tracing.ts"],"sourcesContent":["/**\n * Message format conversion utilities for Braintrust.\n *\n * Converts AI SDK message format (v4/v5) to OpenAI Chat Completion format,\n * which Braintrust requires for proper rendering of threads.\n */\n\n// ==============================================================================\n// Utility functions\n// ==============================================================================\n\n/**\n * Remove null and undefined values from an object (shallow)\n */\nexport function removeNullish<T extends Record<string, unknown>>(obj: T): Partial<T> {\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) as Partial<T>;\n}\n\n// ==============================================================================\n// Type definitions for AI SDK message format conversion to OpenAI format\n// ==============================================================================\n\n/**\n * AI SDK content part types (both v4 and v5)\n */\ninterface AISDKTextPart {\n type: 'text';\n text: string;\n}\n\ninterface AISDKImagePart {\n type: 'image';\n image?: string | Uint8Array | URL;\n mimeType?: string;\n}\n\ninterface AISDKFilePart {\n type: 'file';\n data?: string | Uint8Array | URL;\n filename?: string;\n name?: string;\n mimeType?: string;\n}\n\ninterface AISDKReasoningPart {\n type: 'reasoning';\n text?: string;\n}\n\ninterface AISDKToolCallPart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n args?: unknown; // AI SDK v4\n input?: unknown; // AI SDK v5\n}\n\ninterface AISDKToolResultPart {\n type: 'tool-result';\n toolCallId: string;\n result?: unknown; // AI SDK v4\n output?: unknown; // AI SDK v5\n}\n\ntype AISDKContentPart =\n | AISDKTextPart\n | AISDKImagePart\n | AISDKFilePart\n | AISDKReasoningPart\n | AISDKToolCallPart\n | AISDKToolResultPart\n | { type: string; [key: string]: unknown }; // Catch-all for unknown types\n\n/**\n * AI SDK message format (input format for conversion)\n */\ninterface AISDKMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string | AISDKContentPart[];\n [key: string]: unknown; // Allow additional properties\n}\n\n/**\n * OpenAI Chat Completion tool call format\n */\nexport interface OpenAIToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\n/**\n * OpenAI Chat Completion message format (output format)\n */\nexport interface OpenAIMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n [key: string]: unknown; // Allow additional properties\n}\n\n// ==============================================================================\n// Message conversion functions\n// ==============================================================================\n\n/**\n * Converts a content part to a string representation.\n * Handles text, image, file, reasoning, and other content types.\n */\nfunction convertContentPart(part: AISDKContentPart | null | undefined): string | null {\n if (!part || typeof part !== 'object') {\n return null;\n }\n\n switch (part.type) {\n case 'text':\n return (part as AISDKTextPart).text || null;\n\n case 'image':\n // Represent image content with a placeholder\n return '[image]';\n\n case 'file': {\n // Represent file content with filename if available\n const filePart = part as AISDKFilePart;\n if (filePart.filename || filePart.name) {\n return `[file: ${filePart.filename || filePart.name}]`;\n }\n return '[file]';\n }\n\n case 'reasoning': {\n // Represent reasoning/thinking content\n const reasoningPart = part as AISDKReasoningPart;\n if (typeof reasoningPart.text === 'string' && reasoningPart.text.length > 0) {\n return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? '...' : ''}]`;\n }\n return '[reasoning]';\n }\n\n case 'tool-call':\n // Tool calls are handled separately in assistant messages\n return null;\n\n case 'tool-result':\n // Tool results are handled separately in tool messages\n return null;\n\n default: {\n // For unknown types, try to extract any text-like content\n const unknownPart = part as { type?: string; text?: string; content?: string };\n if (typeof unknownPart.text === 'string') {\n return unknownPart.text;\n }\n if (typeof unknownPart.content === 'string') {\n return unknownPart.content;\n }\n // Represent unknown content type\n return `[${unknownPart.type || 'unknown'}]`;\n }\n }\n}\n\n/**\n * Serializes tool result data to a string for OpenAI format.\n */\nexport function serializeToolResult(resultData: unknown): string {\n if (typeof resultData === 'string') {\n return resultData;\n }\n if (resultData && typeof resultData === 'object' && 'value' in resultData) {\n const valueData = (resultData as { value: unknown }).value;\n return typeof valueData === 'string' ? valueData : JSON.stringify(valueData);\n }\n if (resultData === undefined || resultData === null) {\n return '';\n }\n try {\n return JSON.stringify(resultData);\n } catch {\n return '[unserializable result]';\n }\n}\n\n/**\n * Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.\n *\n * Supports both AI SDK v4 and v5 formats:\n * - v4 uses 'args' for tool calls and 'result' for tool results\n * - v5 uses 'input' for tool calls and 'output' for tool results\n *\n * AI SDK format:\n * { role: \"user\", content: [{ type: \"text\", text: \"hello\" }] }\n * { role: \"assistant\", content: [{ type: \"text\", text: \"...\" }, { type: \"tool-call\", toolCallId: \"...\", toolName: \"...\", args: {...} }] }\n * { role: \"tool\", content: [{ type: \"tool-result\", toolCallId: \"...\", result: {...} }] }\n *\n * OpenAI format (what Braintrust expects):\n * { role: \"user\", content: \"hello\" }\n * { role: \"assistant\", content: \"...\", tool_calls: [{ id: \"...\", type: \"function\", function: { name: \"...\", arguments: \"...\" } }] }\n * { role: \"tool\", content: \"result\", tool_call_id: \"...\" }\n */\nexport function convertAISDKMessage(message: AISDKMessage | OpenAIMessage | unknown): OpenAIMessage | unknown {\n if (!message || typeof message !== 'object') {\n return message;\n }\n\n const { role, content, ...rest } = message as AISDKMessage;\n\n // If content is already a string, return as-is (already in OpenAI format)\n if (typeof content === 'string') {\n return message;\n }\n\n // If content is an array (AI SDK format), convert based on role\n if (Array.isArray(content)) {\n // Handle empty content arrays\n if (content.length === 0) {\n return { role, content: '', ...rest };\n }\n\n // For user/system messages, extract text and represent non-text content\n if (role === 'user' || role === 'system') {\n const contentParts = content.map((part: AISDKContentPart) => convertContentPart(part)).filter(Boolean);\n\n return {\n role,\n content: contentParts.length > 0 ? contentParts.join('\\n') : '',\n ...rest,\n };\n }\n\n // For assistant messages, extract text, non-text content, AND tool calls\n if (role === 'assistant') {\n const contentParts = content\n .filter((part: AISDKContentPart) => part?.type !== 'tool-call')\n .map((part: AISDKContentPart) => convertContentPart(part))\n .filter(Boolean);\n\n const toolCallParts = content.filter((part: AISDKContentPart) => part?.type === 'tool-call');\n\n const result: OpenAIMessage = {\n role,\n content: contentParts.length > 0 ? (contentParts as string[]).join('\\n') : '',\n ...rest,\n };\n\n // Add tool_calls array if there are tool calls\n if (toolCallParts.length > 0) {\n result.tool_calls = toolCallParts.map((tc: AISDKContentPart) => {\n const toolCall = tc as AISDKToolCallPart;\n const toolCallId = toolCall.toolCallId;\n const toolName = toolCall.toolName;\n // Support both v4 'args' and v5 'input'\n const args = toolCall.args ?? toolCall.input;\n\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else if (args !== undefined && args !== null) {\n argsString = JSON.stringify(args);\n } else {\n argsString = '{}';\n }\n\n return {\n id: toolCallId,\n type: 'function' as const,\n function: {\n name: toolName,\n arguments: argsString,\n },\n };\n });\n }\n\n return result;\n }\n\n // For tool messages, convert to OpenAI tool message format\n if (role === 'tool') {\n const toolResult = content.find((part): part is AISDKToolResultPart => part?.type === 'tool-result');\n if (toolResult) {\n // Support both v4 'result' and v5 'output' fields\n const resultData = toolResult.output ?? toolResult.result;\n const resultContent = serializeToolResult(resultData);\n\n return {\n role: 'tool',\n content: resultContent,\n tool_call_id: toolResult.toolCallId,\n } as OpenAIMessage;\n }\n }\n }\n\n return message;\n}\n","import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * BraintrustUsageMetrics\n *\n * Canonical metric keys expected by Braintrust for LLM usage accounting.\n */\nexport interface BraintrustUsageMetrics {\n prompt_tokens?: number;\n completion_tokens?: number;\n tokens?: number;\n completion_reasoning_tokens?: number;\n prompt_cached_tokens?: number;\n prompt_cache_creation_tokens?: number;\n}\n\n/**\n * Formats UsageStats to Braintrust's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): BraintrustUsageMetrics {\n const metrics: BraintrustUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.prompt_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.completion_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.prompt_tokens !== undefined && metrics.completion_tokens !== undefined) {\n metrics.tokens = metrics.prompt_tokens + metrics.completion_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.completion_reasoning_tokens = usage.outputDetails.reasoning;\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.prompt_cached_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.prompt_cache_creation_tokens = usage.inputDetails.cacheWrite;\n }\n\n return metrics;\n}\n","/**\n * Thread view reconstruction for Braintrust.\n *\n * Reconstructs LLM output in OpenAI Chat Completion format by examining\n * child MODEL_STEP and TOOL_CALL spans. This enables Braintrust's Thread\n * view to properly display the full conversation flow including tool calls.\n *\n * See THREAD_VIEW_RECONSTRUCTION.md for details.\n */\n\nimport { removeNullish } from './formatter';\nimport type { OpenAIMessage } from './formatter';\n\n// ==============================================================================\n// Thread view reconstruction types\n// ==============================================================================\n\n/**\n * Tool call data accumulated from MODEL_STEP and TOOL_CALL spans\n */\nexport interface ThreadToolCall {\n toolCallId: string;\n toolName: string;\n args: unknown;\n result?: unknown; // filled in when TOOL_CALL ends\n startTime?: Date; // from TOOL_CALL span, for ordering multiple tool calls within a step\n}\n\n/**\n * Step data accumulated from MODEL_STEP spans for Thread view reconstruction\n */\nexport interface ThreadStepData {\n stepSpanId: string;\n stepIndex: number; // for ordering steps correctly\n text?: string;\n toolCalls?: ThreadToolCall[];\n}\n\n/**\n * Accumulated data for reconstructing Braintrust Thread view.\n * Populated for MODEL_GENERATION spans as child MODEL_STEP and TOOL_CALL spans complete.\n */\nexport type ThreadData = ThreadStepData[];\n\n/**\n * Tool result data stored when TOOL_CALL spans end (before MODEL_STEP ends)\n */\nexport interface PendingToolResult {\n result: unknown;\n startTime: Date;\n}\n\n// ==============================================================================\n// Thread view reconstruction\n// ==============================================================================\n\n/**\n * Reconstruct the Thread view output from accumulated threadData.\n * Converts to OpenAI Chat Completion message format.\n */\nexport function reconstructThreadOutput(threadData: ThreadData, originalOutput: unknown): OpenAIMessage[] {\n const messages: OpenAIMessage[] = [];\n\n // Sort steps by stepIndex\n const sortedSteps = [...threadData].sort((a, b) => a.stepIndex - b.stepIndex);\n\n for (const step of sortedSteps) {\n // Sort tool calls by startTime within each step\n const sortedToolCalls = step.toolCalls\n ? [...step.toolCalls].sort((a, b) => {\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.getTime() - b.startTime.getTime();\n })\n : [];\n\n if (sortedToolCalls.length > 0) {\n // Add assistant message with tool_calls\n messages.push({\n role: 'assistant',\n content: step.text || '',\n tool_calls: sortedToolCalls.map(tc => {\n // Clean null/undefined values from args before stringifying\n const cleanArgs =\n tc.args && typeof tc.args === 'object' && !Array.isArray(tc.args)\n ? removeNullish(tc.args as Record<string, unknown>)\n : tc.args;\n return {\n id: tc.toolCallId,\n type: 'function' as const,\n function: {\n name: tc.toolName,\n arguments: typeof cleanArgs === 'string' ? cleanArgs : JSON.stringify(cleanArgs),\n },\n };\n }),\n });\n\n // Add tool messages for each result\n for (const tc of sortedToolCalls) {\n if (tc.result !== undefined) {\n messages.push({\n role: 'tool',\n content: typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result),\n tool_call_id: tc.toolCallId,\n });\n }\n }\n } else if (step.text) {\n // Step with text only (final response)\n messages.push({\n role: 'assistant',\n content: step.text,\n });\n }\n }\n\n // If we have messages and the last one is a tool message,\n // add the final assistant text from original output\n if (messages.length > 0) {\n const lastMessage = messages[messages.length - 1]!;\n const originalText = (originalOutput as { text?: string })?.text;\n\n // If the last message is a tool response and we have final text, add it\n if (originalText && lastMessage.role === 'tool') {\n messages.push({\n role: 'assistant',\n content: originalText,\n });\n }\n }\n\n return messages;\n}\n","/**\n * Braintrust Exporter for Mastra Observability\n *\n * This exporter sends observability data to Braintrust.\n * Root spans become top-level Braintrust spans (no trace wrapper).\n * Events are handled as zero-duration spans with matching start/end times.\n */\n\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport { removeNullish, convertAISDKMessage, serializeToolResult } from './formatter';\nimport type { OpenAIMessage } from './formatter';\nimport { formatUsageMetrics } from './metrics';\nimport { reconstructThreadOutput } from './thread-reconstruction';\nimport type { ThreadData, ThreadStepData, PendingToolResult } from './thread-reconstruction';\n\ntype BraintrustSpanType = 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool';\n\n/**\n * Explicit span parentage accepted by Braintrust's `startSpan()`.\n * `rootSpanId` groups rows into a trace; `spanId` / `parentSpanIds` populate\n * the row's `span_parents`.\n */\nexport type BraintrustParentSpanIds =\n | { spanId: string; rootSpanId: string }\n | { parentSpanIds: string[]; rootSpanId: string };\n\n/**\n * The subset of a Braintrust span used by the exporter.\n *\n * This structural interface lets applications provide spans from a different\n * compatible Braintrust SDK instance without coupling Mastra's public types to\n * a specific SDK version.\n */\nexport interface BraintrustSpan {\n id: string;\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n parentSpanIds?: BraintrustParentSpanIds;\n event: Record<string, any>;\n }): BraintrustSpan;\n log(event: Record<string, any>): void;\n end(args?: { endTime?: number }): number;\n}\n\n/**\n * The subset of a Braintrust logger used by the exporter.\n *\n * Logger instances from compatible Braintrust SDK versions satisfy this\n * interface without sharing the same nominal SDK types as the exporter.\n */\nexport interface BraintrustLogger {\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n parentSpanIds?: BraintrustParentSpanIds;\n event: Record<string, any>;\n }): BraintrustSpan;\n logFeedback(event: {\n id: string;\n scores: Record<string, number>;\n comment?: string;\n metadata?: Record<string, any>;\n source: 'external';\n }): void;\n}\n\n/**\n * Extended Braintrust span data that includes span type and thread reconstruction data\n */\ninterface BraintrustSpanData {\n span: BraintrustSpan;\n spanType: SpanType;\n threadData?: ThreadData; // only populated for MODEL_GENERATION spans\n // Tool results stored when TOOL_CALL ends (may arrive before MODEL_STEP ends)\n pendingToolResults?: Map<string, PendingToolResult>; // keyed by toolCallId\n}\n\nexport interface BraintrustExporterConfig extends TrackingExporterConfig {\n /**\n * Optional Braintrust logger instance.\n * When provided, enables integration with Braintrust contexts such as:\n * - Evals: Agent traces nest inside eval task spans\n * - logger.traced(): Agent traces nest inside traced spans\n * - Parent spans: Auto-detects and attaches to external Braintrust spans\n */\n braintrustLogger?: BraintrustLogger;\n\n /**\n * Optional resolver for the active Braintrust span.\n *\n * Pass Braintrust's `currentSpan` from the same package instance that creates\n * `Eval()` or `logger.traced()` spans when your app and Mastra may resolve\n * different copies of the `braintrust` package.\n */\n currentSpan?: () => BraintrustSpan | undefined;\n\n /** Braintrust API key. Required if logger is not provided. */\n apiKey?: string;\n /** Optional custom endpoint */\n endpoint?: string;\n /** Braintrust project name (default: 'mastra-tracing') */\n projectName?: string;\n /** Support tuning parameters */\n tuningParameters?: Record<string, any>;\n}\n\ntype BraintrustRoot = BraintrustLogger | BraintrustSpan;\ntype BraintrustTrackedSpan = BraintrustSpanData;\ntype BraintrustEvent = BraintrustSpan;\ntype BraintrustMetadata = unknown;\ntype BraintrustTraceData = TraceData<BraintrustRoot, BraintrustTrackedSpan, BraintrustEvent, BraintrustMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'task';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, string>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'function',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'function',\n};\n\n// Mapping function - returns valid Braintrust span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool' {\n return (SPAN_TYPE_EXCEPTIONS[spanType] as any) ?? DEFAULT_SPAN_TYPE;\n}\n\nexport class BraintrustExporter extends TrackingExporter<\n BraintrustRoot,\n BraintrustTrackedSpan,\n BraintrustEvent,\n BraintrustMetadata,\n BraintrustExporterConfig\n> {\n name = 'braintrust';\n\n // Flags and logger for context-aware mode\n #useProvidedLogger: boolean;\n #providedLogger?: BraintrustLogger;\n #localLogger?: BraintrustLogger;\n\n constructor(config: BraintrustExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const resolvedApiKey = config.apiKey ?? process.env.BRAINTRUST_API_KEY;\n const resolvedEndpoint = config.endpoint ?? process.env.BRAINTRUST_ENDPOINT;\n\n super({\n ...config,\n apiKey: resolvedApiKey,\n endpoint: resolvedEndpoint,\n });\n\n this.#useProvidedLogger = !!config.braintrustLogger;\n\n if (this.#useProvidedLogger) {\n // Use provided logger - enables Braintrust context integration\n this.#providedLogger = config.braintrustLogger;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!this.config.apiKey) {\n this.setDisabled(\n `Missing required API key. Set BRAINTRUST_API_KEY environment variable or pass apiKey in config.`,\n );\n return;\n }\n // lazy create logger on first rootSpan\n this.#localLogger = undefined;\n }\n }\n\n private async getLocalLogger(): Promise<BraintrustLogger | undefined> {\n if (this.#localLogger) {\n return this.#localLogger;\n }\n try {\n const logger = await initLogger({\n projectName: this.config.projectName ?? 'mastra-tracing',\n apiKey: this.config.apiKey,\n appUrl: this.config.endpoint,\n ...this.config.tuningParameters,\n });\n this.#localLogger = logger;\n return logger;\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (this.isDisabled) return;\n\n const { score } = event;\n const rowId = score.spanId ?? score.traceId;\n if (!rowId) {\n this.logger.debug('Braintrust exporter: skipping score with no spanId or traceId', {\n scorerId: score.scorerId,\n });\n return;\n }\n\n const logger = this.#useProvidedLogger ? this.#providedLogger : await this.getLocalLogger();\n if (!logger) return;\n\n const name = score.scorerName ?? score.scorerId;\n\n try {\n logger.logFeedback({\n id: rowId,\n scores: { [name]: score.score },\n ...(score.reason ? { comment: score.reason } : {}),\n metadata: {\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n ...(score.metadata ?? {}),\n },\n source: 'external',\n });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to submit score', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n private startSpan(args: {\n parent: BraintrustSpan | BraintrustLogger;\n span: AnyExportedSpan;\n parentSpanIds?: BraintrustParentSpanIds;\n }): BraintrustSpanData {\n const { parent, span, parentSpanIds } = args;\n const payload = this.buildSpanPayload(span);\n\n // Braintrust's startSpan() accepts data properties via the `event` parameter\n // which maps to StartSpanEventArgs (ExperimentLogPartialArgs & Partial<IdField>)\n // This includes: input, output, metadata, metrics, tags, scores, error, etc.\n const braintrustSpan = parent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n ...(parentSpanIds ? { parentSpanIds } : {}),\n event: {\n id: span.id, // Use Mastra span ID as Braintrust row ID for logFeedback() compatibility\n ...payload,\n },\n });\n\n // Create BraintrustSpanData with span type for tree walking\n // Initialize threadData and pendingToolResults for MODEL_GENERATION spans (used for Thread view reconstruction)\n const isModelGeneration = span.type === SpanType.MODEL_GENERATION;\n return {\n span: braintrustSpan,\n spanType: span.type,\n threadData: isModelGeneration ? [] : undefined,\n pendingToolResults: isModelGeneration ? new Map() : undefined,\n };\n }\n\n /**\n * Explicit parentage for a root span started directly from a logger.\n *\n * Pinning `rootSpanId` to the Mastra trace ID makes every root sharing a\n * trace land in the same Braintrust trace — required for a resumed workflow\n * run to rejoin the trace of its suspended half, which was exported by an\n * earlier process. Genuine roots keep empty `span_parents` so Braintrust\n * still treats them as trace roots; only a resumed continuation (marked by\n * core with `resumedFromSpanId`) links to its persisted parent span. Roots\n * with other parent IDs (e.g. an ambient OTEL span from the bridge) are not\n * linked — their parent was never exported to Braintrust.\n *\n * Spans nested under an enclosing Braintrust span (Eval(), logger.traced(),\n * or a span passed as `braintrustLogger`) get no explicit parentage: the\n * startSpan() chain already inherits the external trace's rootSpanId.\n */\n private rootParentSpanIds(root: BraintrustRoot, span: AnyExportedSpan): BraintrustParentSpanIds | undefined {\n // Braintrust SDK objects carry a `kind` discriminant ('logger' | 'span').\n // Structural stand-ins may omit it; for those, spans are the ones that end().\n const kind = (root as { kind?: string }).kind;\n const isLoggerRoot = kind ? kind === 'logger' : !('end' in root);\n if (!isLoggerRoot) {\n return undefined;\n }\n if (span.parentSpanId && span.parentSpanId === span.metadata?.resumedFromSpanId) {\n return { spanId: span.parentSpanId, rootSpanId: span.traceId };\n }\n return { parentSpanIds: [], rootSpanId: span.traceId };\n }\n\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustRoot | undefined> {\n if (this.#useProvidedLogger) {\n // Try to find a Braintrust span to attach to:\n // 1. Auto-detect from Braintrust's current span (logger.traced(), Eval(), etc.)\n // 2. Fall back to the configured logger\n let externalSpan: BraintrustSpan | undefined;\n try {\n externalSpan = this.config.currentSpan?.();\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to resolve configured currentSpan', { error: err });\n }\n externalSpan ??= currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (externalSpan && externalSpan.id) {\n // External span detected - attach Mastra traces to it\n return externalSpan;\n } else {\n // No external span - use provided logger\n return this.#providedLogger!;\n }\n } else {\n // Use the local logger\n return this.getLocalLogger();\n }\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustSpanData | undefined> {\n const { span, traceData } = args;\n\n if (span.isRootSpan) {\n const root = traceData.getRoot();\n if (root) {\n return this.startSpan({ parent: root, span, parentSpanIds: this.rootParentSpanIds(root, span) });\n }\n } else {\n const parent = traceData.getParent(args);\n if (parent) {\n // Parent could be BraintrustSpanData (has .span) or BraintrustRoot (Logger/Span, no .span)\n const parentSpan = 'span' in parent ? parent.span : parent;\n return this.startSpan({ parent: parentSpan, span });\n }\n }\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustEvent | undefined> {\n const spanData = await this._buildSpan(args);\n\n if (!spanData) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n spanData.span.end({ endTime: args.span.startTime.getTime() / 1000 });\n return spanData.span;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n spanData.span.log(this.buildSpanPayload(span, false));\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n\n // Handle thread data accumulation for MODEL_STEP and tool spans.\n // PROVIDER_TOOL_CALL is excluded: provider results are not merged into the\n // reconstructed thread output. Since @mastra/core parents these spans under\n // the delivering MODEL_STEP, findModelGenerationAncestor would now succeed,\n // so accumulating them is possible as a follow-up.\n if (span.type === SpanType.MODEL_STEP) {\n this.accumulateModelStepData(span, traceData);\n } else if (span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) {\n this.accumulateToolCallResult(span, traceData);\n }\n\n // Build payload - for MODEL_GENERATION, may reconstruct output from threadData\n const payload =\n span.type === SpanType.MODEL_GENERATION\n ? this.buildModelGenerationPayload(span, spanData)\n : this.buildSpanPayload(span, false);\n\n spanData.span.log(payload);\n\n if (span.endTime) {\n spanData.span.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n spanData.span.end();\n }\n }\n\n protected override async _abortSpan(args: { span: BraintrustTrackedSpan; reason: SpanErrorInfo }): Promise<void> {\n const { span: spanData, reason } = args;\n spanData.span.log({\n error: reason.message,\n metadata: { errorDetails: reason },\n });\n spanData.span.end();\n }\n\n // ==============================================================================\n // Thread view reconstruction helpers\n // ==============================================================================\n\n /**\n * Walk up the tree to find the MODEL_GENERATION ancestor span.\n * Returns the BraintrustSpanData if found, undefined otherwise.\n */\n private findModelGenerationAncestor(spanId: string, traceData: BraintrustTraceData): BraintrustSpanData | undefined {\n let currentId: string | undefined = spanId;\n\n while (currentId) {\n const parentId = traceData.getParentId({ spanId: currentId });\n if (!parentId) return undefined;\n\n const parentSpanData = traceData.getSpan({ spanId: parentId });\n if (parentSpanData?.spanType === SpanType.MODEL_GENERATION) {\n return parentSpanData;\n }\n currentId = parentId;\n }\n\n return undefined;\n }\n\n /**\n * Accumulate MODEL_STEP data to the parent MODEL_GENERATION's threadData.\n * Called when a MODEL_STEP span ends.\n */\n private accumulateModelStepData(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.threadData) {\n return;\n }\n\n // Extract step data from MODEL_STEP output and attributes\n const output = span.output as\n | { text?: string; toolCalls?: Array<{ toolCallId: string; toolName: string; args: unknown }> }\n | undefined;\n const attributes = span.attributes as { stepIndex?: number } | undefined;\n\n const stepData: ThreadStepData = {\n stepSpanId: span.id,\n stepIndex: attributes?.stepIndex ?? 0,\n text: output?.text,\n toolCalls: output?.toolCalls?.map(tc => ({\n toolCallId: tc.toolCallId,\n toolName: tc.toolName,\n args: tc.args,\n })),\n };\n\n modelGenSpanData.threadData.push(stepData);\n }\n\n /**\n * Store a tool result (TOOL_CALL or MCP_TOOL_CALL) in parent MODEL_GENERATION's pendingToolResults.\n * Called when the tool span ends.\n * Results are merged into threadData when MODEL_GENERATION ends.\n */\n private accumulateToolCallResult(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.pendingToolResults) {\n return;\n }\n\n const toolCallId = this.resolveToolCallId(span);\n\n // Store the result for later merging\n modelGenSpanData.pendingToolResults.set(toolCallId, {\n result: span.output,\n startTime: span.startTime,\n });\n }\n\n /**\n * Build the payload for MODEL_GENERATION span, reconstructing output from threadData if available.\n */\n private buildModelGenerationPayload(span: AnyExportedSpan, spanData: BraintrustSpanData): Record<string, any> {\n const basePayload = this.buildSpanPayload(span, false);\n\n // Check if we have threadData with tool calls to reconstruct\n const threadData = spanData.threadData;\n if (!threadData || threadData.length === 0) {\n return basePayload;\n }\n\n // Merge pending tool results into threadData\n if (spanData.pendingToolResults && spanData.pendingToolResults.size > 0) {\n for (const step of threadData) {\n if (step.toolCalls) {\n for (const toolCall of step.toolCalls) {\n const pendingResult = spanData.pendingToolResults.get(toolCall.toolCallId);\n if (pendingResult) {\n toolCall.result = pendingResult.result;\n toolCall.startTime = pendingResult.startTime;\n }\n }\n }\n }\n }\n\n // Check if any step has tool calls\n const hasToolCalls = threadData.some(step => step.toolCalls && step.toolCalls.length > 0);\n if (!hasToolCalls) {\n return basePayload;\n }\n\n // Reconstruct output as OpenAI messages\n const reconstructedOutput = reconstructThreadOutput(threadData, span.output);\n return {\n ...basePayload,\n output: reconstructedOutput,\n };\n }\n\n /**\n * Transforms MODEL_GENERATION input to Braintrust Thread view format.\n * Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires\n * for proper rendering of threads (fixes #11023).\n */\n private transformInput(input: unknown, spanType: SpanType): unknown {\n if (spanType === SpanType.MODEL_GENERATION) {\n // If input is already an array of messages, convert AI SDK format to OpenAI format\n if (Array.isArray(input)) {\n return input.map((msg: unknown) => convertAISDKMessage(msg));\n }\n\n // If input has a messages array\n if (\n input &&\n typeof input === 'object' &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown[] }).messages)\n ) {\n return (input as { messages: unknown[] }).messages.map((msg: unknown) => convertAISDKMessage(msg));\n }\n }\n\n return input;\n }\n\n /**\n * Transforms MODEL_GENERATION output to Braintrust Thread view format.\n */\n private transformOutput(output: any, spanType: SpanType): any {\n if (spanType === SpanType.MODEL_GENERATION) {\n if (!output || typeof output !== 'object') {\n return output;\n }\n const { text, ...rest } = output;\n // Remove null/undefined values from rest to keep Thread view clean\n return { role: 'assistant', content: text, ...removeNullish(rest) };\n }\n\n return output;\n }\n\n private isToolSpan(span: AnyExportedSpan): boolean {\n return (\n span.type === SpanType.TOOL_CALL ||\n span.type === SpanType.MCP_TOOL_CALL ||\n span.type === SpanType.PROVIDER_TOOL_CALL\n );\n }\n\n private getToolName(span: AnyExportedSpan): string {\n if (span.entityName) {\n return span.entityName;\n }\n const match = /'([^']+)'/.exec(span.name);\n return match?.[1] ?? span.name;\n }\n\n private resolveToolCallId(span: AnyExportedSpan): string {\n const attrs = span.attributes as { toolCallId?: string } | undefined;\n return (\n attrs?.toolCallId ??\n span.metadata?.toolCallId ??\n (span.input as { toolCallId?: string } | undefined)?.toolCallId ??\n span.id\n );\n }\n\n private toToolCallInput(span: AnyExportedSpan): OpenAIMessage[] {\n const args = span.input;\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else {\n try {\n argsString = JSON.stringify(args ?? {});\n } catch {\n argsString = '[unserializable result]';\n }\n }\n return [\n {\n role: 'assistant',\n content: '',\n tool_calls: [\n {\n id: this.resolveToolCallId(span),\n type: 'function',\n function: {\n name: this.getToolName(span),\n arguments: argsString,\n },\n },\n ],\n },\n ];\n }\n\n private toToolCallOutput(span: AnyExportedSpan): OpenAIMessage {\n return {\n role: 'tool',\n content: serializeToolResult(span.output),\n tool_call_id: this.resolveToolCallId(span),\n };\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate = true): Record<string, any> {\n const payload: Record<string, any> = {};\n\n const isToolType = this.isToolSpan(span);\n\n if (span.input !== undefined) {\n payload.input = isToolType ? this.toToolCallInput(span) : this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = isToolType ? this.toToolCallOutput(span) : this.transformOutput(span.output, span.type);\n }\n\n if (isCreate && span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n // Spread span.metadata first, then set spanType to prevent accidental override\n payload.metadata = {\n ...span.metadata,\n spanType: span.type,\n };\n\n if (isCreate) {\n payload.metadata['mastra-trace-id'] = span.traceId;\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // Model goes to metadata\n if (modelAttr.model !== undefined) {\n payload.metadata.model = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n payload.metadata.provider = modelAttr.provider;\n }\n\n // Prefer resolved model ID (e.g. \"claude-sonnet-4-5-20250929\") over\n // gateway aliases (e.g. \"claude-sonnet-4.5\") for accurate cost estimation\n if (modelAttr.responseModel !== undefined) {\n payload.metadata.model = modelAttr.responseModel;\n }\n\n // Usage/token info goes to metrics\n payload.metrics = formatUsageMetrics(modelAttr.usage);\n\n // Time to first token (TTFT) for streaming responses\n // Braintrust expects TTFT in seconds (not milliseconds)\n if (modelAttr.completionStartTime) {\n payload.metrics.time_to_first_token =\n (modelAttr.completionStartTime.getTime() - span.startTime.getTime()) / 1000;\n }\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, [\n 'model',\n 'responseModel',\n 'usage',\n 'parameters',\n 'completionStartTime',\n ]);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...attributes,\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n // Clean up empty metrics object\n if (Object.keys(payload.metrics).length === 0) {\n delete payload.metrics;\n }\n\n // Remove null/undefined values from metadata to keep Braintrust UI clean\n payload.metadata = removeNullish(payload.metadata);\n\n return payload;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,SAAgB,cAAiD,KAAoB;CACnF,OAAO,OAAO,YAAY,OAAO,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,KAAK,IAAI,CAAC;AAC7E;;;;;AAiGA,SAAS,mBAAmB,MAA0D;CACpF,IAAI,CAAC,QAAQ,OAAO,SAAS,UAC3B,OAAO;CAGT,QAAQ,KAAK,MAAb;EACE,KAAK,QACH,OAAQ,KAAuB,QAAQ;EAEzC,KAAK,SAEH,OAAO;EAET,KAAK,QAAQ;GAEX,MAAM,WAAW;GACjB,IAAI,SAAS,YAAY,SAAS,MAChC,OAAO,UAAU,SAAS,YAAY,SAAS,KAAK;GAEtD,OAAO;EACT;EAEA,KAAK,aAAa;GAEhB,MAAM,gBAAgB;GACtB,IAAI,OAAO,cAAc,SAAS,YAAY,cAAc,KAAK,SAAS,GACxE,OAAO,eAAe,cAAc,KAAK,UAAU,GAAG,GAAG,IAAI,cAAc,KAAK,SAAS,MAAM,QAAQ,GAAG;GAE5G,OAAO;EACT;EAEA,KAAK,aAEH,OAAO;EAET,KAAK,eAEH,OAAO;EAET,SAAS;GAEP,MAAM,cAAc;GACpB,IAAI,OAAO,YAAY,SAAS,UAC9B,OAAO,YAAY;GAErB,IAAI,OAAO,YAAY,YAAY,UACjC,OAAO,YAAY;GAGrB,OAAO,IAAI,YAAY,QAAQ,UAAU;EAC3C;CACF;AACF;;;;AAKA,SAAgB,oBAAoB,YAA6B;CAC/D,IAAI,OAAO,eAAe,UACxB,OAAO;CAET,IAAI,cAAc,OAAO,eAAe,YAAY,WAAW,YAAY;EACzE,MAAM,YAAa,WAAkC;EACrD,OAAO,OAAO,cAAc,WAAW,YAAY,KAAK,UAAU,SAAS;CAC7E;CACA,IAAI,eAAe,KAAA,KAAa,eAAe,MAC7C,OAAO;CAET,IAAI;EACF,OAAO,KAAK,UAAU,UAAU;CAClC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,oBAAoB,SAA0E;CAC5G,IAAI,CAAC,WAAW,OAAO,YAAY,UACjC,OAAO;CAGT,MAAM,EAAE,MAAM,SAAS,GAAG,SAAS;CAGnC,IAAI,OAAO,YAAY,UACrB,OAAO;CAIT,IAAI,MAAM,QAAQ,OAAO,GAAG;EAE1B,IAAI,QAAQ,WAAW,GACrB,OAAO;GAAE;GAAM,SAAS;GAAI,GAAG;EAAK;EAItC,IAAI,SAAS,UAAU,SAAS,UAAU;GACxC,MAAM,eAAe,QAAQ,KAAK,SAA2B,mBAAmB,IAAI,CAAC,CAAC,CAAC,OAAO,OAAO;GAErG,OAAO;IACL;IACA,SAAS,aAAa,SAAS,IAAI,aAAa,KAAK,IAAI,IAAI;IAC7D,GAAG;GACL;EACF;EAGA,IAAI,SAAS,aAAa;GACxB,MAAM,eAAe,QAClB,QAAQ,SAA2B,MAAM,SAAS,WAAW,CAAC,CAC9D,KAAK,SAA2B,mBAAmB,IAAI,CAAC,CAAC,CACzD,OAAO,OAAO;GAEjB,MAAM,gBAAgB,QAAQ,QAAQ,SAA2B,MAAM,SAAS,WAAW;GAE3F,MAAM,SAAwB;IAC5B;IACA,SAAS,aAAa,SAAS,IAAK,aAA0B,KAAK,IAAI,IAAI;IAC3E,GAAG;GACL;GAGA,IAAI,cAAc,SAAS,GACzB,OAAO,aAAa,cAAc,KAAK,OAAyB;IAC9D,MAAM,WAAW;IACjB,MAAM,aAAa,SAAS;IAC5B,MAAM,WAAW,SAAS;IAE1B,MAAM,OAAO,SAAS,QAAQ,SAAS;IAEvC,IAAI;IACJ,IAAI,OAAO,SAAS,UAClB,aAAa;SACR,IAAI,SAAS,KAAA,KAAa,SAAS,MACxC,aAAa,KAAK,UAAU,IAAI;SAEhC,aAAa;IAGf,OAAO;KACL,IAAI;KACJ,MAAM;KACN,UAAU;MACR,MAAM;MACN,WAAW;KACb;IACF;GACF,CAAC;GAGH,OAAO;EACT;EAGA,IAAI,SAAS,QAAQ;GACnB,MAAM,aAAa,QAAQ,MAAM,SAAsC,MAAM,SAAS,aAAa;GACnG,IAAI,YAKF,OAAO;IACL,MAAM;IACN,SAJoB,oBADH,WAAW,UAAU,WAAW,MAK5B;IACrB,cAAc,WAAW;GAC3B;EAEJ;CACF;CAEA,OAAO;AACT;;;;;;ACzRA,SAAgB,mBAAmB,OAA4C;CAC7E,MAAM,UAAkC,CAAC;CAEzC,IAAI,OAAO,gBAAgB,KAAA,GACzB,QAAQ,gBAAgB,MAAM;CAGhC,IAAI,OAAO,iBAAiB,KAAA,GAC1B,QAAQ,oBAAoB,MAAM;CAIpC,IAAI,QAAQ,kBAAkB,KAAA,KAAa,QAAQ,sBAAsB,KAAA,GACvE,QAAQ,SAAS,QAAQ,gBAAgB,QAAQ;CAGnD,IAAI,OAAO,eAAe,cAAc,KAAA,GACtC,QAAQ,8BAA8B,MAAM,cAAc;CAG5D,IAAI,OAAO,cAAc,cAAc,KAAA,GACrC,QAAQ,uBAAuB,MAAM,aAAa;CAGpD,IAAI,OAAO,cAAc,eAAe,KAAA,GACtC,QAAQ,+BAA+B,MAAM,aAAa;CAG5D,OAAO;AACT;;;;;;;;;;;;;;;;ACYA,SAAgB,wBAAwB,YAAwB,gBAA0C;CACxG,MAAM,WAA4B,CAAC;CAGnC,MAAM,cAAc,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;CAE5E,KAAK,MAAM,QAAQ,aAAa;EAE9B,MAAM,kBAAkB,KAAK,YACzB,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM;GACjC,IAAI,CAAC,EAAE,aAAa,CAAC,EAAE,WAAW,OAAO;GACzC,OAAO,EAAE,UAAU,QAAQ,IAAI,EAAE,UAAU,QAAQ;EACrD,CAAC,IACD,CAAC;EAEL,IAAI,gBAAgB,SAAS,GAAG;GAE9B,SAAS,KAAK;IACZ,MAAM;IACN,SAAS,KAAK,QAAQ;IACtB,YAAY,gBAAgB,KAAI,OAAM;KAEpC,MAAM,YACJ,GAAG,QAAQ,OAAO,GAAG,SAAS,YAAY,CAAC,MAAM,QAAQ,GAAG,IAAI,IAC5D,cAAc,GAAG,IAA+B,IAChD,GAAG;KACT,OAAO;MACL,IAAI,GAAG;MACP,MAAM;MACN,UAAU;OACR,MAAM,GAAG;OACT,WAAW,OAAO,cAAc,WAAW,YAAY,KAAK,UAAU,SAAS;MACjF;KACF;IACF,CAAC;GACH,CAAC;GAGD,KAAK,MAAM,MAAM,iBACf,IAAI,GAAG,WAAW,KAAA,GAChB,SAAS,KAAK;IACZ,MAAM;IACN,SAAS,OAAO,GAAG,WAAW,WAAW,GAAG,SAAS,KAAK,UAAU,GAAG,MAAM;IAC7E,cAAc,GAAG;GACnB,CAAC;EAGP,OAAO,IAAI,KAAK,MAEd,SAAS,KAAK;GACZ,MAAM;GACN,SAAS,KAAK;EAChB,CAAC;CAEL;CAIA,IAAI,SAAS,SAAS,GAAG;EACvB,MAAM,cAAc,SAAS,SAAS,SAAS;EAC/C,MAAM,eAAgB,gBAAsC;EAG5D,IAAI,gBAAgB,YAAY,SAAS,QACvC,SAAS,KAAK;GACZ,MAAM;GACN,SAAS;EACX,CAAC;CAEL;CAEA,OAAO;AACT;;;ACTA,MAAM,oBAAoB;AAG1B,MAAM,uBAA0D;EAC7DA,2BAAAA,SAAS,mBAAmB;EAC5BA,2BAAAA,SAAS,YAAY;EACrBA,2BAAAA,SAAS,gBAAgB;EACzBA,2BAAAA,SAAS,qBAAqB;EAC9BA,2BAAAA,SAAS,4BAA4B;EACrCA,2BAAAA,SAAS,sBAAsB;AAClC;AAGA,SAAS,YAAY,UAA6E;CAChG,OAAQ,qBAAqB,aAAqB;AACpD;AAEA,IAAa,qBAAb,cAAwCC,sBAAAA,iBAMtC;CACA,OAAO;CAGP;CACA;CACA;CAEA,YAAY,SAAmC,CAAC,GAAG;EAEjD,MAAM,iBAAiB,OAAO,UAAU,QAAQ,IAAI;EACpD,MAAM,mBAAmB,OAAO,YAAY,QAAQ,IAAI;EAExD,MAAM;GACJ,GAAG;GACH,QAAQ;GACR,UAAU;EACZ,CAAC;EAED,KAAKC,qBAAqB,CAAC,CAAC,OAAO;EAEnC,IAAI,KAAKA,oBAEP,KAAKC,kBAAkB,OAAO;OACzB;GAEL,IAAI,CAAC,KAAK,OAAO,QAAQ;IACvB,KAAK,YACH,iGACF;IACA;GACF;GAEA,KAAKC,eAAe,KAAA;EACtB;CACF;CAEA,MAAc,iBAAwD;EACpE,IAAI,KAAKA,cACP,OAAO,KAAKA;EAEd,IAAI;GACF,MAAM,SAAS,OAAA,GAAA,WAAA,WAAA,CAAiB;IAC9B,aAAa,KAAK,OAAO,eAAe;IACxC,QAAQ,KAAK,OAAO;IACpB,QAAQ,KAAK,OAAO;IACpB,GAAG,KAAK,OAAO;GACjB,CAAC;GACD,KAAKA,eAAe;GACpB,OAAO;EACT,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,oDAAoD,EAAE,OAAO,IAAI,CAAC;GACpF,KAAK,YAAY,wCAAwC;EAC3D;CACF;CAEA,MAAM,aAAa,OAAkC;EACnD,IAAI,KAAK,YAAY;EAErB,MAAM,EAAE,UAAU;EAClB,MAAM,QAAQ,MAAM,UAAU,MAAM;EACpC,IAAI,CAAC,OAAO;GACV,KAAK,OAAO,MAAM,iEAAiE,EACjF,UAAU,MAAM,SAClB,CAAC;GACD;EACF;EAEA,MAAM,SAAS,KAAKF,qBAAqB,KAAKC,kBAAkB,MAAM,KAAK,eAAe;EAC1F,IAAI,CAAC,QAAQ;EAEb,MAAM,OAAO,MAAM,cAAc,MAAM;EAEvC,IAAI;GACF,OAAO,YAAY;IACjB,IAAI;IACJ,QAAQ,GAAG,OAAO,MAAM,MAAM;IAC9B,GAAI,MAAM,SAAS,EAAE,SAAS,MAAM,OAAO,IAAI,CAAC;IAChD,UAAU;KACR,UAAU,MAAM;KAChB,GAAI,MAAM,cAAc,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;KAC9D,GAAI,MAAM,YAAY,CAAC;IACzB;IACA,QAAQ;GACV,CAAC;EACH,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,+CAA+C;IAC/D,OAAO;IACP,SAAS,MAAM;IACf,QAAQ,MAAM;IACd,UAAU,MAAM;GAClB,CAAC;EACH;CACF;CAEA,UAAkB,MAIK;EACrB,MAAM,EAAE,QAAQ,MAAM,kBAAkB;EACxC,MAAM,UAAU,KAAK,iBAAiB,IAAI;EAK1C,MAAM,iBAAiB,OAAO,UAAU;GACtC,QAAQ,KAAK;GACb,MAAM,KAAK;GACX,MAAM,YAAY,KAAK,IAAI;GAC3B,WAAW,KAAK,UAAU,QAAQ,IAAI;GACtC,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;GACzC,OAAO;IACL,IAAI,KAAK;IACT,GAAG;GACL;EACF,CAAC;EAID,MAAM,oBAAoB,KAAK,SAASH,2BAAAA,SAAS;EACjD,OAAO;GACL,MAAM;GACN,UAAU,KAAK;GACf,YAAY,oBAAoB,CAAC,IAAI,KAAA;GACrC,oBAAoB,oCAAoB,IAAI,IAAI,IAAI,KAAA;EACtD;CACF;;;;;;;;;;;;;;;;;CAkBA,kBAA0B,MAAsB,MAA4D;EAG1G,MAAM,OAAQ,KAA2B;EAEzC,IAAI,EADiB,OAAO,SAAS,WAAW,EAAE,SAAS,QAEzD;EAEF,IAAI,KAAK,gBAAgB,KAAK,iBAAiB,KAAK,UAAU,mBAC5D,OAAO;GAAE,QAAQ,KAAK;GAAc,YAAY,KAAK;EAAQ;EAE/D,OAAO;GAAE,eAAe,CAAC;GAAG,YAAY,KAAK;EAAQ;CACvD;CAEA,MAAyB,WAAW,OAGI;EACtC,IAAI,KAAKE,oBAAoB;GAI3B,IAAI;GACJ,IAAI;IACF,eAAe,KAAK,OAAO,cAAc;GAC3C,SAAS,KAAK;IACZ,KAAK,OAAO,MAAM,iEAAiE,EAAE,OAAO,IAAI,CAAC;GACnG;GACA,kBAAA,GAAA,WAAA,YAAA,CAA6B;GAG7B,IAAI,gBAAgB,aAAa,IAE/B,OAAO;QAGP,OAAO,KAAKC;EAEhB,OAEE,OAAO,KAAK,eAAe;CAE/B;CAEA,MAAyB,WAAW,MAGQ;EAC1C,MAAM,EAAE,MAAM,cAAc;EAE5B,IAAI,KAAK,YAAY;GACnB,MAAM,OAAO,UAAU,QAAQ;GAC/B,IAAI,MACF,OAAO,KAAK,UAAU;IAAE,QAAQ;IAAM;IAAM,eAAe,KAAK,kBAAkB,MAAM,IAAI;GAAE,CAAC;EAEnG,OAAO;GACL,MAAM,SAAS,UAAU,UAAU,IAAI;GACvC,IAAI,QAAQ;IAEV,MAAM,aAAa,UAAU,SAAS,OAAO,OAAO;IACpD,OAAO,KAAK,UAAU;KAAE,QAAQ;KAAY;IAAK,CAAC;GACpD;EACF;CACF;CAEA,MAAyB,YAAY,MAGI;EACvC,MAAM,WAAW,MAAM,KAAK,WAAW,IAAI;EAE3C,IAAI,CAAC,UAEH;EAGF,SAAS,KAAK,IAAI,EAAE,SAAS,KAAK,KAAK,UAAU,QAAQ,IAAI,IAAK,CAAC;EACnE,OAAO,SAAS;CAClB;CAEA,MAAyB,YAAY,MAAgF;EACnH,MAAM,EAAE,MAAM,cAAc;EAE5B,MAAM,WAAW,UAAU,QAAQ,EAAE,QAAQ,KAAK,GAAG,CAAC;EACtD,IAAI,CAAC,UACH;EAEF,SAAS,KAAK,IAAI,KAAK,iBAAiB,MAAM,KAAK,CAAC;CACtD;CAEA,MAAyB,YAAY,MAAgF;EACnH,MAAM,EAAE,MAAM,cAAc;EAE5B,MAAM,WAAW,UAAU,QAAQ,EAAE,QAAQ,KAAK,GAAG,CAAC;EACtD,IAAI,CAAC,UACH;EAQF,IAAI,KAAK,SAASH,2BAAAA,SAAS,YACzB,KAAK,wBAAwB,MAAM,SAAS;OACvC,IAAI,KAAK,SAASA,2BAAAA,SAAS,aAAa,KAAK,SAASA,2BAAAA,SAAS,eACpE,KAAK,yBAAyB,MAAM,SAAS;EAI/C,MAAM,UACJ,KAAK,SAASA,2BAAAA,SAAS,mBACnB,KAAK,4BAA4B,MAAM,QAAQ,IAC/C,KAAK,iBAAiB,MAAM,KAAK;EAEvC,SAAS,KAAK,IAAI,OAAO;EAEzB,IAAI,KAAK,SACP,SAAS,KAAK,IAAI,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAAI,IAAK,CAAC;OAE5D,SAAS,KAAK,IAAI;CAEtB;CAEA,MAAyB,WAAW,MAA6E;EAC/G,MAAM,EAAE,MAAM,UAAU,WAAW;EACnC,SAAS,KAAK,IAAI;GAChB,OAAO,OAAO;GACd,UAAU,EAAE,cAAc,OAAO;EACnC,CAAC;EACD,SAAS,KAAK,IAAI;CACpB;;;;;CAUA,4BAAoC,QAAgB,WAAgE;EAClH,IAAI,YAAgC;EAEpC,OAAO,WAAW;GAChB,MAAM,WAAW,UAAU,YAAY,EAAE,QAAQ,UAAU,CAAC;GAC5D,IAAI,CAAC,UAAU,OAAO,KAAA;GAEtB,MAAM,iBAAiB,UAAU,QAAQ,EAAE,QAAQ,SAAS,CAAC;GAC7D,IAAI,gBAAgB,aAAaA,2BAAAA,SAAS,kBACxC,OAAO;GAET,YAAY;EACd;CAGF;;;;;CAMA,wBAAgC,MAAuB,WAAsC;EAC3F,MAAM,mBAAmB,KAAK,4BAA4B,KAAK,IAAI,SAAS;EAC5E,IAAI,CAAC,kBAAkB,YACrB;EAIF,MAAM,SAAS,KAAK;EAGpB,MAAM,aAAa,KAAK;EAExB,MAAM,WAA2B;GAC/B,YAAY,KAAK;GACjB,WAAW,YAAY,aAAa;GACpC,MAAM,QAAQ;GACd,WAAW,QAAQ,WAAW,KAAI,QAAO;IACvC,YAAY,GAAG;IACf,UAAU,GAAG;IACb,MAAM,GAAG;GACX,EAAE;EACJ;EAEA,iBAAiB,WAAW,KAAK,QAAQ;CAC3C;;;;;;CAOA,yBAAiC,MAAuB,WAAsC;EAC5F,MAAM,mBAAmB,KAAK,4BAA4B,KAAK,IAAI,SAAS;EAC5E,IAAI,CAAC,kBAAkB,oBACrB;EAGF,MAAM,aAAa,KAAK,kBAAkB,IAAI;EAG9C,iBAAiB,mBAAmB,IAAI,YAAY;GAClD,QAAQ,KAAK;GACb,WAAW,KAAK;EAClB,CAAC;CACH;;;;CAKA,4BAAoC,MAAuB,UAAmD;EAC5G,MAAM,cAAc,KAAK,iBAAiB,MAAM,KAAK;EAGrD,MAAM,aAAa,SAAS;EAC5B,IAAI,CAAC,cAAc,WAAW,WAAW,GACvC,OAAO;EAIT,IAAI,SAAS,sBAAsB,SAAS,mBAAmB,OAAO,GAC/D;QAAA,MAAM,QAAQ,YACjB,IAAI,KAAK,WACP,KAAK,MAAM,YAAY,KAAK,WAAW;IACrC,MAAM,gBAAgB,SAAS,mBAAmB,IAAI,SAAS,UAAU;IACzE,IAAI,eAAe;KACjB,SAAS,SAAS,cAAc;KAChC,SAAS,YAAY,cAAc;IACrC;GACF;;EAON,IAAI,CADiB,WAAW,MAAK,SAAQ,KAAK,aAAa,KAAK,UAAU,SAAS,CACvE,GACd,OAAO;EAIT,MAAM,sBAAsB,wBAAwB,YAAY,KAAK,MAAM;EAC3E,OAAO;GACL,GAAG;GACH,QAAQ;EACV;CACF;;;;;;CAOA,eAAuB,OAAgB,UAA6B;EAClE,IAAI,aAAaA,2BAAAA,SAAS,kBAAkB;GAE1C,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,QAAiB,oBAAoB,GAAG,CAAC;GAI7D,IACE,SACA,OAAO,UAAU,YACjB,cAAc,SACd,MAAM,QAAS,MAAkC,QAAQ,GAEzD,OAAQ,MAAkC,SAAS,KAAK,QAAiB,oBAAoB,GAAG,CAAC;EAErG;EAEA,OAAO;CACT;;;;CAKA,gBAAwB,QAAa,UAAyB;EAC5D,IAAI,aAAaA,2BAAAA,SAAS,kBAAkB;GAC1C,IAAI,CAAC,UAAU,OAAO,WAAW,UAC/B,OAAO;GAET,MAAM,EAAE,MAAM,GAAG,SAAS;GAE1B,OAAO;IAAE,MAAM;IAAa,SAAS;IAAM,GAAG,cAAc,IAAI;GAAE;EACpE;EAEA,OAAO;CACT;CAEA,WAAmB,MAAgC;EACjD,OACE,KAAK,SAASA,2BAAAA,SAAS,aACvB,KAAK,SAASA,2BAAAA,SAAS,iBACvB,KAAK,SAASA,2BAAAA,SAAS;CAE3B;CAEA,YAAoB,MAA+B;EACjD,IAAI,KAAK,YACP,OAAO,KAAK;EAGd,OADc,YAAY,KAAK,KAAK,IACzB,CAAC,GAAG,MAAM,KAAK;CAC5B;CAEA,kBAA0B,MAA+B;EAEvD,OADc,KAAK,YAEV,cACP,KAAK,UAAU,cACd,KAAK,OAA+C,cACrD,KAAK;CAET;CAEA,gBAAwB,MAAwC;EAC9D,MAAM,OAAO,KAAK;EAClB,IAAI;EACJ,IAAI,OAAO,SAAS,UAClB,aAAa;OAEb,IAAI;GACF,aAAa,KAAK,UAAU,QAAQ,CAAC,CAAC;EACxC,QAAQ;GACN,aAAa;EACf;EAEF,OAAO,CACL;GACE,MAAM;GACN,SAAS;GACT,YAAY,CACV;IACE,IAAI,KAAK,kBAAkB,IAAI;IAC/B,MAAM;IACN,UAAU;KACR,MAAM,KAAK,YAAY,IAAI;KAC3B,WAAW;IACb;GACF,CACF;EACF,CACF;CACF;CAEA,iBAAyB,MAAsC;EAC7D,OAAO;GACL,MAAM;GACN,SAAS,oBAAoB,KAAK,MAAM;GACxC,cAAc,KAAK,kBAAkB,IAAI;EAC3C;CACF;CAEA,iBAAyB,MAAuB,WAAW,MAA2B;EACpF,MAAM,UAA+B,CAAC;EAEtC,MAAM,aAAa,KAAK,WAAW,IAAI;EAEvC,IAAI,KAAK,UAAU,KAAA,GACjB,QAAQ,QAAQ,aAAa,KAAK,gBAAgB,IAAI,IAAI,KAAK,eAAe,KAAK,OAAO,KAAK,IAAI;EAGrG,IAAI,KAAK,WAAW,KAAA,GAClB,QAAQ,SAAS,aAAa,KAAK,iBAAiB,IAAI,IAAI,KAAK,gBAAgB,KAAK,QAAQ,KAAK,IAAI;EAGzG,IAAI,YAAY,KAAK,cAAc,KAAK,MAAM,QAC5C,QAAQ,OAAO,KAAK;EAItB,QAAQ,UAAU,CAAC;EAEnB,QAAQ,WAAW;GACjB,GAAG,KAAK;GACR,UAAU,KAAK;EACjB;EAEA,IAAI,UACF,QAAQ,SAAS,qBAAqB,KAAK;EAG7C,MAAM,aAAc,KAAK,cAAc,CAAC;EAExC,IAAI,KAAK,SAASA,2BAAAA,SAAS,kBAAkB;GAC3C,MAAM,YAAY;GAGlB,IAAI,UAAU,UAAU,KAAA,GACtB,QAAQ,SAAS,QAAQ,UAAU;GAIrC,IAAI,UAAU,aAAa,KAAA,GACzB,QAAQ,SAAS,WAAW,UAAU;GAKxC,IAAI,UAAU,kBAAkB,KAAA,GAC9B,QAAQ,SAAS,QAAQ,UAAU;GAIrC,QAAQ,UAAU,mBAAmB,UAAU,KAAK;GAIpD,IAAI,UAAU,qBACZ,QAAQ,QAAQ,uBACb,UAAU,oBAAoB,QAAQ,IAAI,KAAK,UAAU,QAAQ,KAAK;GAI3E,IAAI,UAAU,eAAe,KAAA,GAC3B,QAAQ,SAAS,kBAAkB,UAAU;GAI/C,MAAM,mBAAA,GAAA,mBAAA,SAAA,CAA2B,YAAY;IAC3C;IACA;IACA;IACA;IACA;GACF,CAAC;GACD,QAAQ,WAAW;IACjB,GAAG,QAAQ;IACX,GAAG;GACL;EACF,OAEE,QAAQ,WAAW;GACjB,GAAG,QAAQ;GACX,GAAG;EACL;EAIF,IAAI,KAAK,WAAW;GAClB,QAAQ,QAAQ,KAAK,UAAU;GAC/B,QAAQ,SAAS,eAAe,KAAK;EACvC;EAGA,IAAI,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,WAAW,GAC1C,OAAO,QAAQ;EAIjB,QAAQ,WAAW,cAAc,QAAQ,QAAQ;EAEjD,OAAO;CACT;AACF"}
|
package/dist/index.js
CHANGED
|
@@ -294,13 +294,14 @@ var BraintrustExporter = class extends TrackingExporter {
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
startSpan(args) {
|
|
297
|
-
const { parent, span } = args;
|
|
297
|
+
const { parent, span, parentSpanIds } = args;
|
|
298
298
|
const payload = this.buildSpanPayload(span);
|
|
299
299
|
const braintrustSpan = parent.startSpan({
|
|
300
300
|
spanId: span.id,
|
|
301
301
|
name: span.name,
|
|
302
302
|
type: mapSpanType(span.type),
|
|
303
303
|
startTime: span.startTime.getTime() / 1e3,
|
|
304
|
+
...parentSpanIds ? { parentSpanIds } : {},
|
|
304
305
|
event: {
|
|
305
306
|
id: span.id,
|
|
306
307
|
...payload
|
|
@@ -314,6 +315,34 @@ var BraintrustExporter = class extends TrackingExporter {
|
|
|
314
315
|
pendingToolResults: isModelGeneration ? /* @__PURE__ */ new Map() : void 0
|
|
315
316
|
};
|
|
316
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Explicit parentage for a root span started directly from a logger.
|
|
320
|
+
*
|
|
321
|
+
* Pinning `rootSpanId` to the Mastra trace ID makes every root sharing a
|
|
322
|
+
* trace land in the same Braintrust trace — required for a resumed workflow
|
|
323
|
+
* run to rejoin the trace of its suspended half, which was exported by an
|
|
324
|
+
* earlier process. Genuine roots keep empty `span_parents` so Braintrust
|
|
325
|
+
* still treats them as trace roots; only a resumed continuation (marked by
|
|
326
|
+
* core with `resumedFromSpanId`) links to its persisted parent span. Roots
|
|
327
|
+
* with other parent IDs (e.g. an ambient OTEL span from the bridge) are not
|
|
328
|
+
* linked — their parent was never exported to Braintrust.
|
|
329
|
+
*
|
|
330
|
+
* Spans nested under an enclosing Braintrust span (Eval(), logger.traced(),
|
|
331
|
+
* or a span passed as `braintrustLogger`) get no explicit parentage: the
|
|
332
|
+
* startSpan() chain already inherits the external trace's rootSpanId.
|
|
333
|
+
*/
|
|
334
|
+
rootParentSpanIds(root, span) {
|
|
335
|
+
const kind = root.kind;
|
|
336
|
+
if (!(kind ? kind === "logger" : !("end" in root))) return;
|
|
337
|
+
if (span.parentSpanId && span.parentSpanId === span.metadata?.resumedFromSpanId) return {
|
|
338
|
+
spanId: span.parentSpanId,
|
|
339
|
+
rootSpanId: span.traceId
|
|
340
|
+
};
|
|
341
|
+
return {
|
|
342
|
+
parentSpanIds: [],
|
|
343
|
+
rootSpanId: span.traceId
|
|
344
|
+
};
|
|
345
|
+
}
|
|
317
346
|
async _buildRoot(_args) {
|
|
318
347
|
if (this.#useProvidedLogger) {
|
|
319
348
|
let externalSpan;
|
|
@@ -333,7 +362,8 @@ var BraintrustExporter = class extends TrackingExporter {
|
|
|
333
362
|
const root = traceData.getRoot();
|
|
334
363
|
if (root) return this.startSpan({
|
|
335
364
|
parent: root,
|
|
336
|
-
span
|
|
365
|
+
span,
|
|
366
|
+
parentSpanIds: this.rootParentSpanIds(root, span)
|
|
337
367
|
});
|
|
338
368
|
} else {
|
|
339
369
|
const parent = traceData.getParent(args);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#useProvidedLogger","#providedLogger","#localLogger"],"sources":["../src/formatter.ts","../src/metrics.ts","../src/thread-reconstruction.ts","../src/tracing.ts"],"sourcesContent":["/**\n * Message format conversion utilities for Braintrust.\n *\n * Converts AI SDK message format (v4/v5) to OpenAI Chat Completion format,\n * which Braintrust requires for proper rendering of threads.\n */\n\n// ==============================================================================\n// Utility functions\n// ==============================================================================\n\n/**\n * Remove null and undefined values from an object (shallow)\n */\nexport function removeNullish<T extends Record<string, unknown>>(obj: T): Partial<T> {\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) as Partial<T>;\n}\n\n// ==============================================================================\n// Type definitions for AI SDK message format conversion to OpenAI format\n// ==============================================================================\n\n/**\n * AI SDK content part types (both v4 and v5)\n */\ninterface AISDKTextPart {\n type: 'text';\n text: string;\n}\n\ninterface AISDKImagePart {\n type: 'image';\n image?: string | Uint8Array | URL;\n mimeType?: string;\n}\n\ninterface AISDKFilePart {\n type: 'file';\n data?: string | Uint8Array | URL;\n filename?: string;\n name?: string;\n mimeType?: string;\n}\n\ninterface AISDKReasoningPart {\n type: 'reasoning';\n text?: string;\n}\n\ninterface AISDKToolCallPart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n args?: unknown; // AI SDK v4\n input?: unknown; // AI SDK v5\n}\n\ninterface AISDKToolResultPart {\n type: 'tool-result';\n toolCallId: string;\n result?: unknown; // AI SDK v4\n output?: unknown; // AI SDK v5\n}\n\ntype AISDKContentPart =\n | AISDKTextPart\n | AISDKImagePart\n | AISDKFilePart\n | AISDKReasoningPart\n | AISDKToolCallPart\n | AISDKToolResultPart\n | { type: string; [key: string]: unknown }; // Catch-all for unknown types\n\n/**\n * AI SDK message format (input format for conversion)\n */\ninterface AISDKMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string | AISDKContentPart[];\n [key: string]: unknown; // Allow additional properties\n}\n\n/**\n * OpenAI Chat Completion tool call format\n */\nexport interface OpenAIToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\n/**\n * OpenAI Chat Completion message format (output format)\n */\nexport interface OpenAIMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n [key: string]: unknown; // Allow additional properties\n}\n\n// ==============================================================================\n// Message conversion functions\n// ==============================================================================\n\n/**\n * Converts a content part to a string representation.\n * Handles text, image, file, reasoning, and other content types.\n */\nfunction convertContentPart(part: AISDKContentPart | null | undefined): string | null {\n if (!part || typeof part !== 'object') {\n return null;\n }\n\n switch (part.type) {\n case 'text':\n return (part as AISDKTextPart).text || null;\n\n case 'image':\n // Represent image content with a placeholder\n return '[image]';\n\n case 'file': {\n // Represent file content with filename if available\n const filePart = part as AISDKFilePart;\n if (filePart.filename || filePart.name) {\n return `[file: ${filePart.filename || filePart.name}]`;\n }\n return '[file]';\n }\n\n case 'reasoning': {\n // Represent reasoning/thinking content\n const reasoningPart = part as AISDKReasoningPart;\n if (typeof reasoningPart.text === 'string' && reasoningPart.text.length > 0) {\n return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? '...' : ''}]`;\n }\n return '[reasoning]';\n }\n\n case 'tool-call':\n // Tool calls are handled separately in assistant messages\n return null;\n\n case 'tool-result':\n // Tool results are handled separately in tool messages\n return null;\n\n default: {\n // For unknown types, try to extract any text-like content\n const unknownPart = part as { type?: string; text?: string; content?: string };\n if (typeof unknownPart.text === 'string') {\n return unknownPart.text;\n }\n if (typeof unknownPart.content === 'string') {\n return unknownPart.content;\n }\n // Represent unknown content type\n return `[${unknownPart.type || 'unknown'}]`;\n }\n }\n}\n\n/**\n * Serializes tool result data to a string for OpenAI format.\n */\nexport function serializeToolResult(resultData: unknown): string {\n if (typeof resultData === 'string') {\n return resultData;\n }\n if (resultData && typeof resultData === 'object' && 'value' in resultData) {\n const valueData = (resultData as { value: unknown }).value;\n return typeof valueData === 'string' ? valueData : JSON.stringify(valueData);\n }\n if (resultData === undefined || resultData === null) {\n return '';\n }\n try {\n return JSON.stringify(resultData);\n } catch {\n return '[unserializable result]';\n }\n}\n\n/**\n * Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.\n *\n * Supports both AI SDK v4 and v5 formats:\n * - v4 uses 'args' for tool calls and 'result' for tool results\n * - v5 uses 'input' for tool calls and 'output' for tool results\n *\n * AI SDK format:\n * { role: \"user\", content: [{ type: \"text\", text: \"hello\" }] }\n * { role: \"assistant\", content: [{ type: \"text\", text: \"...\" }, { type: \"tool-call\", toolCallId: \"...\", toolName: \"...\", args: {...} }] }\n * { role: \"tool\", content: [{ type: \"tool-result\", toolCallId: \"...\", result: {...} }] }\n *\n * OpenAI format (what Braintrust expects):\n * { role: \"user\", content: \"hello\" }\n * { role: \"assistant\", content: \"...\", tool_calls: [{ id: \"...\", type: \"function\", function: { name: \"...\", arguments: \"...\" } }] }\n * { role: \"tool\", content: \"result\", tool_call_id: \"...\" }\n */\nexport function convertAISDKMessage(message: AISDKMessage | OpenAIMessage | unknown): OpenAIMessage | unknown {\n if (!message || typeof message !== 'object') {\n return message;\n }\n\n const { role, content, ...rest } = message as AISDKMessage;\n\n // If content is already a string, return as-is (already in OpenAI format)\n if (typeof content === 'string') {\n return message;\n }\n\n // If content is an array (AI SDK format), convert based on role\n if (Array.isArray(content)) {\n // Handle empty content arrays\n if (content.length === 0) {\n return { role, content: '', ...rest };\n }\n\n // For user/system messages, extract text and represent non-text content\n if (role === 'user' || role === 'system') {\n const contentParts = content.map((part: AISDKContentPart) => convertContentPart(part)).filter(Boolean);\n\n return {\n role,\n content: contentParts.length > 0 ? contentParts.join('\\n') : '',\n ...rest,\n };\n }\n\n // For assistant messages, extract text, non-text content, AND tool calls\n if (role === 'assistant') {\n const contentParts = content\n .filter((part: AISDKContentPart) => part?.type !== 'tool-call')\n .map((part: AISDKContentPart) => convertContentPart(part))\n .filter(Boolean);\n\n const toolCallParts = content.filter((part: AISDKContentPart) => part?.type === 'tool-call');\n\n const result: OpenAIMessage = {\n role,\n content: contentParts.length > 0 ? (contentParts as string[]).join('\\n') : '',\n ...rest,\n };\n\n // Add tool_calls array if there are tool calls\n if (toolCallParts.length > 0) {\n result.tool_calls = toolCallParts.map((tc: AISDKContentPart) => {\n const toolCall = tc as AISDKToolCallPart;\n const toolCallId = toolCall.toolCallId;\n const toolName = toolCall.toolName;\n // Support both v4 'args' and v5 'input'\n const args = toolCall.args ?? toolCall.input;\n\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else if (args !== undefined && args !== null) {\n argsString = JSON.stringify(args);\n } else {\n argsString = '{}';\n }\n\n return {\n id: toolCallId,\n type: 'function' as const,\n function: {\n name: toolName,\n arguments: argsString,\n },\n };\n });\n }\n\n return result;\n }\n\n // For tool messages, convert to OpenAI tool message format\n if (role === 'tool') {\n const toolResult = content.find((part): part is AISDKToolResultPart => part?.type === 'tool-result');\n if (toolResult) {\n // Support both v4 'result' and v5 'output' fields\n const resultData = toolResult.output ?? toolResult.result;\n const resultContent = serializeToolResult(resultData);\n\n return {\n role: 'tool',\n content: resultContent,\n tool_call_id: toolResult.toolCallId,\n } as OpenAIMessage;\n }\n }\n }\n\n return message;\n}\n","import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * BraintrustUsageMetrics\n *\n * Canonical metric keys expected by Braintrust for LLM usage accounting.\n */\nexport interface BraintrustUsageMetrics {\n prompt_tokens?: number;\n completion_tokens?: number;\n tokens?: number;\n completion_reasoning_tokens?: number;\n prompt_cached_tokens?: number;\n prompt_cache_creation_tokens?: number;\n}\n\n/**\n * Formats UsageStats to Braintrust's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): BraintrustUsageMetrics {\n const metrics: BraintrustUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.prompt_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.completion_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.prompt_tokens !== undefined && metrics.completion_tokens !== undefined) {\n metrics.tokens = metrics.prompt_tokens + metrics.completion_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.completion_reasoning_tokens = usage.outputDetails.reasoning;\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.prompt_cached_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.prompt_cache_creation_tokens = usage.inputDetails.cacheWrite;\n }\n\n return metrics;\n}\n","/**\n * Thread view reconstruction for Braintrust.\n *\n * Reconstructs LLM output in OpenAI Chat Completion format by examining\n * child MODEL_STEP and TOOL_CALL spans. This enables Braintrust's Thread\n * view to properly display the full conversation flow including tool calls.\n *\n * See THREAD_VIEW_RECONSTRUCTION.md for details.\n */\n\nimport { removeNullish } from './formatter';\nimport type { OpenAIMessage } from './formatter';\n\n// ==============================================================================\n// Thread view reconstruction types\n// ==============================================================================\n\n/**\n * Tool call data accumulated from MODEL_STEP and TOOL_CALL spans\n */\nexport interface ThreadToolCall {\n toolCallId: string;\n toolName: string;\n args: unknown;\n result?: unknown; // filled in when TOOL_CALL ends\n startTime?: Date; // from TOOL_CALL span, for ordering multiple tool calls within a step\n}\n\n/**\n * Step data accumulated from MODEL_STEP spans for Thread view reconstruction\n */\nexport interface ThreadStepData {\n stepSpanId: string;\n stepIndex: number; // for ordering steps correctly\n text?: string;\n toolCalls?: ThreadToolCall[];\n}\n\n/**\n * Accumulated data for reconstructing Braintrust Thread view.\n * Populated for MODEL_GENERATION spans as child MODEL_STEP and TOOL_CALL spans complete.\n */\nexport type ThreadData = ThreadStepData[];\n\n/**\n * Tool result data stored when TOOL_CALL spans end (before MODEL_STEP ends)\n */\nexport interface PendingToolResult {\n result: unknown;\n startTime: Date;\n}\n\n// ==============================================================================\n// Thread view reconstruction\n// ==============================================================================\n\n/**\n * Reconstruct the Thread view output from accumulated threadData.\n * Converts to OpenAI Chat Completion message format.\n */\nexport function reconstructThreadOutput(threadData: ThreadData, originalOutput: unknown): OpenAIMessage[] {\n const messages: OpenAIMessage[] = [];\n\n // Sort steps by stepIndex\n const sortedSteps = [...threadData].sort((a, b) => a.stepIndex - b.stepIndex);\n\n for (const step of sortedSteps) {\n // Sort tool calls by startTime within each step\n const sortedToolCalls = step.toolCalls\n ? [...step.toolCalls].sort((a, b) => {\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.getTime() - b.startTime.getTime();\n })\n : [];\n\n if (sortedToolCalls.length > 0) {\n // Add assistant message with tool_calls\n messages.push({\n role: 'assistant',\n content: step.text || '',\n tool_calls: sortedToolCalls.map(tc => {\n // Clean null/undefined values from args before stringifying\n const cleanArgs =\n tc.args && typeof tc.args === 'object' && !Array.isArray(tc.args)\n ? removeNullish(tc.args as Record<string, unknown>)\n : tc.args;\n return {\n id: tc.toolCallId,\n type: 'function' as const,\n function: {\n name: tc.toolName,\n arguments: typeof cleanArgs === 'string' ? cleanArgs : JSON.stringify(cleanArgs),\n },\n };\n }),\n });\n\n // Add tool messages for each result\n for (const tc of sortedToolCalls) {\n if (tc.result !== undefined) {\n messages.push({\n role: 'tool',\n content: typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result),\n tool_call_id: tc.toolCallId,\n });\n }\n }\n } else if (step.text) {\n // Step with text only (final response)\n messages.push({\n role: 'assistant',\n content: step.text,\n });\n }\n }\n\n // If we have messages and the last one is a tool message,\n // add the final assistant text from original output\n if (messages.length > 0) {\n const lastMessage = messages[messages.length - 1]!;\n const originalText = (originalOutput as { text?: string })?.text;\n\n // If the last message is a tool response and we have final text, add it\n if (originalText && lastMessage.role === 'tool') {\n messages.push({\n role: 'assistant',\n content: originalText,\n });\n }\n }\n\n return messages;\n}\n","/**\n * Braintrust Exporter for Mastra Observability\n *\n * This exporter sends observability data to Braintrust.\n * Root spans become top-level Braintrust spans (no trace wrapper).\n * Events are handled as zero-duration spans with matching start/end times.\n */\n\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport { removeNullish, convertAISDKMessage, serializeToolResult } from './formatter';\nimport type { OpenAIMessage } from './formatter';\nimport { formatUsageMetrics } from './metrics';\nimport { reconstructThreadOutput } from './thread-reconstruction';\nimport type { ThreadData, ThreadStepData, PendingToolResult } from './thread-reconstruction';\n\ntype BraintrustSpanType = 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool';\n\n/**\n * The subset of a Braintrust span used by the exporter.\n *\n * This structural interface lets applications provide spans from a different\n * compatible Braintrust SDK instance without coupling Mastra's public types to\n * a specific SDK version.\n */\nexport interface BraintrustSpan {\n id: string;\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n event: Record<string, any>;\n }): BraintrustSpan;\n log(event: Record<string, any>): void;\n end(args?: { endTime?: number }): number;\n}\n\n/**\n * The subset of a Braintrust logger used by the exporter.\n *\n * Logger instances from compatible Braintrust SDK versions satisfy this\n * interface without sharing the same nominal SDK types as the exporter.\n */\nexport interface BraintrustLogger {\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n event: Record<string, any>;\n }): BraintrustSpan;\n logFeedback(event: {\n id: string;\n scores: Record<string, number>;\n comment?: string;\n metadata?: Record<string, any>;\n source: 'external';\n }): void;\n}\n\n/**\n * Extended Braintrust span data that includes span type and thread reconstruction data\n */\ninterface BraintrustSpanData {\n span: BraintrustSpan;\n spanType: SpanType;\n threadData?: ThreadData; // only populated for MODEL_GENERATION spans\n // Tool results stored when TOOL_CALL ends (may arrive before MODEL_STEP ends)\n pendingToolResults?: Map<string, PendingToolResult>; // keyed by toolCallId\n}\n\nexport interface BraintrustExporterConfig extends TrackingExporterConfig {\n /**\n * Optional Braintrust logger instance.\n * When provided, enables integration with Braintrust contexts such as:\n * - Evals: Agent traces nest inside eval task spans\n * - logger.traced(): Agent traces nest inside traced spans\n * - Parent spans: Auto-detects and attaches to external Braintrust spans\n */\n braintrustLogger?: BraintrustLogger;\n\n /**\n * Optional resolver for the active Braintrust span.\n *\n * Pass Braintrust's `currentSpan` from the same package instance that creates\n * `Eval()` or `logger.traced()` spans when your app and Mastra may resolve\n * different copies of the `braintrust` package.\n */\n currentSpan?: () => BraintrustSpan | undefined;\n\n /** Braintrust API key. Required if logger is not provided. */\n apiKey?: string;\n /** Optional custom endpoint */\n endpoint?: string;\n /** Braintrust project name (default: 'mastra-tracing') */\n projectName?: string;\n /** Support tuning parameters */\n tuningParameters?: Record<string, any>;\n}\n\ntype BraintrustRoot = BraintrustLogger | BraintrustSpan;\ntype BraintrustTrackedSpan = BraintrustSpanData;\ntype BraintrustEvent = BraintrustSpan;\ntype BraintrustMetadata = unknown;\ntype BraintrustTraceData = TraceData<BraintrustRoot, BraintrustTrackedSpan, BraintrustEvent, BraintrustMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'task';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, string>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'function',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'function',\n};\n\n// Mapping function - returns valid Braintrust span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool' {\n return (SPAN_TYPE_EXCEPTIONS[spanType] as any) ?? DEFAULT_SPAN_TYPE;\n}\n\nexport class BraintrustExporter extends TrackingExporter<\n BraintrustRoot,\n BraintrustTrackedSpan,\n BraintrustEvent,\n BraintrustMetadata,\n BraintrustExporterConfig\n> {\n name = 'braintrust';\n\n // Flags and logger for context-aware mode\n #useProvidedLogger: boolean;\n #providedLogger?: BraintrustLogger;\n #localLogger?: BraintrustLogger;\n\n constructor(config: BraintrustExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const resolvedApiKey = config.apiKey ?? process.env.BRAINTRUST_API_KEY;\n const resolvedEndpoint = config.endpoint ?? process.env.BRAINTRUST_ENDPOINT;\n\n super({\n ...config,\n apiKey: resolvedApiKey,\n endpoint: resolvedEndpoint,\n });\n\n this.#useProvidedLogger = !!config.braintrustLogger;\n\n if (this.#useProvidedLogger) {\n // Use provided logger - enables Braintrust context integration\n this.#providedLogger = config.braintrustLogger;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!this.config.apiKey) {\n this.setDisabled(\n `Missing required API key. Set BRAINTRUST_API_KEY environment variable or pass apiKey in config.`,\n );\n return;\n }\n // lazy create logger on first rootSpan\n this.#localLogger = undefined;\n }\n }\n\n private async getLocalLogger(): Promise<BraintrustLogger | undefined> {\n if (this.#localLogger) {\n return this.#localLogger;\n }\n try {\n const logger = await initLogger({\n projectName: this.config.projectName ?? 'mastra-tracing',\n apiKey: this.config.apiKey,\n appUrl: this.config.endpoint,\n ...this.config.tuningParameters,\n });\n this.#localLogger = logger;\n return logger;\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (this.isDisabled) return;\n\n const { score } = event;\n const rowId = score.spanId ?? score.traceId;\n if (!rowId) {\n this.logger.debug('Braintrust exporter: skipping score with no spanId or traceId', {\n scorerId: score.scorerId,\n });\n return;\n }\n\n const logger = this.#useProvidedLogger ? this.#providedLogger : await this.getLocalLogger();\n if (!logger) return;\n\n const name = score.scorerName ?? score.scorerId;\n\n try {\n logger.logFeedback({\n id: rowId,\n scores: { [name]: score.score },\n ...(score.reason ? { comment: score.reason } : {}),\n metadata: {\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n ...(score.metadata ?? {}),\n },\n source: 'external',\n });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to submit score', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n private startSpan(args: { parent: BraintrustSpan | BraintrustLogger; span: AnyExportedSpan }): BraintrustSpanData {\n const { parent, span } = args;\n const payload = this.buildSpanPayload(span);\n\n // Braintrust's startSpan() accepts data properties via the `event` parameter\n // which maps to StartSpanEventArgs (ExperimentLogPartialArgs & Partial<IdField>)\n // This includes: input, output, metadata, metrics, tags, scores, error, etc.\n const braintrustSpan = parent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n event: {\n id: span.id, // Use Mastra span ID as Braintrust row ID for logFeedback() compatibility\n ...payload,\n },\n });\n\n // Create BraintrustSpanData with span type for tree walking\n // Initialize threadData and pendingToolResults for MODEL_GENERATION spans (used for Thread view reconstruction)\n const isModelGeneration = span.type === SpanType.MODEL_GENERATION;\n return {\n span: braintrustSpan,\n spanType: span.type,\n threadData: isModelGeneration ? [] : undefined,\n pendingToolResults: isModelGeneration ? new Map() : undefined,\n };\n }\n\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustRoot | undefined> {\n if (this.#useProvidedLogger) {\n // Try to find a Braintrust span to attach to:\n // 1. Auto-detect from Braintrust's current span (logger.traced(), Eval(), etc.)\n // 2. Fall back to the configured logger\n let externalSpan: BraintrustSpan | undefined;\n try {\n externalSpan = this.config.currentSpan?.();\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to resolve configured currentSpan', { error: err });\n }\n externalSpan ??= currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (externalSpan && externalSpan.id) {\n // External span detected - attach Mastra traces to it\n return externalSpan;\n } else {\n // No external span - use provided logger\n return this.#providedLogger!;\n }\n } else {\n // Use the local logger\n return this.getLocalLogger();\n }\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustSpanData | undefined> {\n const { span, traceData } = args;\n\n if (span.isRootSpan) {\n const root = traceData.getRoot();\n if (root) {\n return this.startSpan({ parent: root, span });\n }\n } else {\n const parent = traceData.getParent(args);\n if (parent) {\n // Parent could be BraintrustSpanData (has .span) or BraintrustRoot (Logger/Span, no .span)\n const parentSpan = 'span' in parent ? parent.span : parent;\n return this.startSpan({ parent: parentSpan, span });\n }\n }\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustEvent | undefined> {\n const spanData = await this._buildSpan(args);\n\n if (!spanData) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n spanData.span.end({ endTime: args.span.startTime.getTime() / 1000 });\n return spanData.span;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n spanData.span.log(this.buildSpanPayload(span, false));\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n\n // Handle thread data accumulation for MODEL_STEP and tool spans.\n // PROVIDER_TOOL_CALL is excluded: provider results are not merged into the\n // reconstructed thread output. Since @mastra/core parents these spans under\n // the delivering MODEL_STEP, findModelGenerationAncestor would now succeed,\n // so accumulating them is possible as a follow-up.\n if (span.type === SpanType.MODEL_STEP) {\n this.accumulateModelStepData(span, traceData);\n } else if (span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) {\n this.accumulateToolCallResult(span, traceData);\n }\n\n // Build payload - for MODEL_GENERATION, may reconstruct output from threadData\n const payload =\n span.type === SpanType.MODEL_GENERATION\n ? this.buildModelGenerationPayload(span, spanData)\n : this.buildSpanPayload(span, false);\n\n spanData.span.log(payload);\n\n if (span.endTime) {\n spanData.span.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n spanData.span.end();\n }\n }\n\n protected override async _abortSpan(args: { span: BraintrustTrackedSpan; reason: SpanErrorInfo }): Promise<void> {\n const { span: spanData, reason } = args;\n spanData.span.log({\n error: reason.message,\n metadata: { errorDetails: reason },\n });\n spanData.span.end();\n }\n\n // ==============================================================================\n // Thread view reconstruction helpers\n // ==============================================================================\n\n /**\n * Walk up the tree to find the MODEL_GENERATION ancestor span.\n * Returns the BraintrustSpanData if found, undefined otherwise.\n */\n private findModelGenerationAncestor(spanId: string, traceData: BraintrustTraceData): BraintrustSpanData | undefined {\n let currentId: string | undefined = spanId;\n\n while (currentId) {\n const parentId = traceData.getParentId({ spanId: currentId });\n if (!parentId) return undefined;\n\n const parentSpanData = traceData.getSpan({ spanId: parentId });\n if (parentSpanData?.spanType === SpanType.MODEL_GENERATION) {\n return parentSpanData;\n }\n currentId = parentId;\n }\n\n return undefined;\n }\n\n /**\n * Accumulate MODEL_STEP data to the parent MODEL_GENERATION's threadData.\n * Called when a MODEL_STEP span ends.\n */\n private accumulateModelStepData(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.threadData) {\n return;\n }\n\n // Extract step data from MODEL_STEP output and attributes\n const output = span.output as\n | { text?: string; toolCalls?: Array<{ toolCallId: string; toolName: string; args: unknown }> }\n | undefined;\n const attributes = span.attributes as { stepIndex?: number } | undefined;\n\n const stepData: ThreadStepData = {\n stepSpanId: span.id,\n stepIndex: attributes?.stepIndex ?? 0,\n text: output?.text,\n toolCalls: output?.toolCalls?.map(tc => ({\n toolCallId: tc.toolCallId,\n toolName: tc.toolName,\n args: tc.args,\n })),\n };\n\n modelGenSpanData.threadData.push(stepData);\n }\n\n /**\n * Store a tool result (TOOL_CALL or MCP_TOOL_CALL) in parent MODEL_GENERATION's pendingToolResults.\n * Called when the tool span ends.\n * Results are merged into threadData when MODEL_GENERATION ends.\n */\n private accumulateToolCallResult(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.pendingToolResults) {\n return;\n }\n\n const toolCallId = this.resolveToolCallId(span);\n\n // Store the result for later merging\n modelGenSpanData.pendingToolResults.set(toolCallId, {\n result: span.output,\n startTime: span.startTime,\n });\n }\n\n /**\n * Build the payload for MODEL_GENERATION span, reconstructing output from threadData if available.\n */\n private buildModelGenerationPayload(span: AnyExportedSpan, spanData: BraintrustSpanData): Record<string, any> {\n const basePayload = this.buildSpanPayload(span, false);\n\n // Check if we have threadData with tool calls to reconstruct\n const threadData = spanData.threadData;\n if (!threadData || threadData.length === 0) {\n return basePayload;\n }\n\n // Merge pending tool results into threadData\n if (spanData.pendingToolResults && spanData.pendingToolResults.size > 0) {\n for (const step of threadData) {\n if (step.toolCalls) {\n for (const toolCall of step.toolCalls) {\n const pendingResult = spanData.pendingToolResults.get(toolCall.toolCallId);\n if (pendingResult) {\n toolCall.result = pendingResult.result;\n toolCall.startTime = pendingResult.startTime;\n }\n }\n }\n }\n }\n\n // Check if any step has tool calls\n const hasToolCalls = threadData.some(step => step.toolCalls && step.toolCalls.length > 0);\n if (!hasToolCalls) {\n return basePayload;\n }\n\n // Reconstruct output as OpenAI messages\n const reconstructedOutput = reconstructThreadOutput(threadData, span.output);\n return {\n ...basePayload,\n output: reconstructedOutput,\n };\n }\n\n /**\n * Transforms MODEL_GENERATION input to Braintrust Thread view format.\n * Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires\n * for proper rendering of threads (fixes #11023).\n */\n private transformInput(input: unknown, spanType: SpanType): unknown {\n if (spanType === SpanType.MODEL_GENERATION) {\n // If input is already an array of messages, convert AI SDK format to OpenAI format\n if (Array.isArray(input)) {\n return input.map((msg: unknown) => convertAISDKMessage(msg));\n }\n\n // If input has a messages array\n if (\n input &&\n typeof input === 'object' &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown[] }).messages)\n ) {\n return (input as { messages: unknown[] }).messages.map((msg: unknown) => convertAISDKMessage(msg));\n }\n }\n\n return input;\n }\n\n /**\n * Transforms MODEL_GENERATION output to Braintrust Thread view format.\n */\n private transformOutput(output: any, spanType: SpanType): any {\n if (spanType === SpanType.MODEL_GENERATION) {\n if (!output || typeof output !== 'object') {\n return output;\n }\n const { text, ...rest } = output;\n // Remove null/undefined values from rest to keep Thread view clean\n return { role: 'assistant', content: text, ...removeNullish(rest) };\n }\n\n return output;\n }\n\n private isToolSpan(span: AnyExportedSpan): boolean {\n return (\n span.type === SpanType.TOOL_CALL ||\n span.type === SpanType.MCP_TOOL_CALL ||\n span.type === SpanType.PROVIDER_TOOL_CALL\n );\n }\n\n private getToolName(span: AnyExportedSpan): string {\n if (span.entityName) {\n return span.entityName;\n }\n const match = /'([^']+)'/.exec(span.name);\n return match?.[1] ?? span.name;\n }\n\n private resolveToolCallId(span: AnyExportedSpan): string {\n const attrs = span.attributes as { toolCallId?: string } | undefined;\n return (\n attrs?.toolCallId ??\n span.metadata?.toolCallId ??\n (span.input as { toolCallId?: string } | undefined)?.toolCallId ??\n span.id\n );\n }\n\n private toToolCallInput(span: AnyExportedSpan): OpenAIMessage[] {\n const args = span.input;\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else {\n try {\n argsString = JSON.stringify(args ?? {});\n } catch {\n argsString = '[unserializable result]';\n }\n }\n return [\n {\n role: 'assistant',\n content: '',\n tool_calls: [\n {\n id: this.resolveToolCallId(span),\n type: 'function',\n function: {\n name: this.getToolName(span),\n arguments: argsString,\n },\n },\n ],\n },\n ];\n }\n\n private toToolCallOutput(span: AnyExportedSpan): OpenAIMessage {\n return {\n role: 'tool',\n content: serializeToolResult(span.output),\n tool_call_id: this.resolveToolCallId(span),\n };\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate = true): Record<string, any> {\n const payload: Record<string, any> = {};\n\n const isToolType = this.isToolSpan(span);\n\n if (span.input !== undefined) {\n payload.input = isToolType ? this.toToolCallInput(span) : this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = isToolType ? this.toToolCallOutput(span) : this.transformOutput(span.output, span.type);\n }\n\n if (isCreate && span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n // Spread span.metadata first, then set spanType to prevent accidental override\n payload.metadata = {\n ...span.metadata,\n spanType: span.type,\n };\n\n if (isCreate) {\n payload.metadata['mastra-trace-id'] = span.traceId;\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // Model goes to metadata\n if (modelAttr.model !== undefined) {\n payload.metadata.model = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n payload.metadata.provider = modelAttr.provider;\n }\n\n // Prefer resolved model ID (e.g. \"claude-sonnet-4-5-20250929\") over\n // gateway aliases (e.g. \"claude-sonnet-4.5\") for accurate cost estimation\n if (modelAttr.responseModel !== undefined) {\n payload.metadata.model = modelAttr.responseModel;\n }\n\n // Usage/token info goes to metrics\n payload.metrics = formatUsageMetrics(modelAttr.usage);\n\n // Time to first token (TTFT) for streaming responses\n // Braintrust expects TTFT in seconds (not milliseconds)\n if (modelAttr.completionStartTime) {\n payload.metrics.time_to_first_token =\n (modelAttr.completionStartTime.getTime() - span.startTime.getTime()) / 1000;\n }\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, [\n 'model',\n 'responseModel',\n 'usage',\n 'parameters',\n 'completionStartTime',\n ]);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...attributes,\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n // Clean up empty metrics object\n if (Object.keys(payload.metrics).length === 0) {\n delete payload.metrics;\n }\n\n // Remove null/undefined values from metadata to keep Braintrust UI clean\n payload.metadata = removeNullish(payload.metadata);\n\n return payload;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#useProvidedLogger","#providedLogger","#localLogger"],"sources":["../src/formatter.ts","../src/metrics.ts","../src/thread-reconstruction.ts","../src/tracing.ts"],"sourcesContent":["/**\n * Message format conversion utilities for Braintrust.\n *\n * Converts AI SDK message format (v4/v5) to OpenAI Chat Completion format,\n * which Braintrust requires for proper rendering of threads.\n */\n\n// ==============================================================================\n// Utility functions\n// ==============================================================================\n\n/**\n * Remove null and undefined values from an object (shallow)\n */\nexport function removeNullish<T extends Record<string, unknown>>(obj: T): Partial<T> {\n return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) as Partial<T>;\n}\n\n// ==============================================================================\n// Type definitions for AI SDK message format conversion to OpenAI format\n// ==============================================================================\n\n/**\n * AI SDK content part types (both v4 and v5)\n */\ninterface AISDKTextPart {\n type: 'text';\n text: string;\n}\n\ninterface AISDKImagePart {\n type: 'image';\n image?: string | Uint8Array | URL;\n mimeType?: string;\n}\n\ninterface AISDKFilePart {\n type: 'file';\n data?: string | Uint8Array | URL;\n filename?: string;\n name?: string;\n mimeType?: string;\n}\n\ninterface AISDKReasoningPart {\n type: 'reasoning';\n text?: string;\n}\n\ninterface AISDKToolCallPart {\n type: 'tool-call';\n toolCallId: string;\n toolName: string;\n args?: unknown; // AI SDK v4\n input?: unknown; // AI SDK v5\n}\n\ninterface AISDKToolResultPart {\n type: 'tool-result';\n toolCallId: string;\n result?: unknown; // AI SDK v4\n output?: unknown; // AI SDK v5\n}\n\ntype AISDKContentPart =\n | AISDKTextPart\n | AISDKImagePart\n | AISDKFilePart\n | AISDKReasoningPart\n | AISDKToolCallPart\n | AISDKToolResultPart\n | { type: string; [key: string]: unknown }; // Catch-all for unknown types\n\n/**\n * AI SDK message format (input format for conversion)\n */\ninterface AISDKMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string | AISDKContentPart[];\n [key: string]: unknown; // Allow additional properties\n}\n\n/**\n * OpenAI Chat Completion tool call format\n */\nexport interface OpenAIToolCall {\n id: string;\n type: 'function';\n function: {\n name: string;\n arguments: string;\n };\n}\n\n/**\n * OpenAI Chat Completion message format (output format)\n */\nexport interface OpenAIMessage {\n role: 'user' | 'assistant' | 'system' | 'tool';\n content: string;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n [key: string]: unknown; // Allow additional properties\n}\n\n// ==============================================================================\n// Message conversion functions\n// ==============================================================================\n\n/**\n * Converts a content part to a string representation.\n * Handles text, image, file, reasoning, and other content types.\n */\nfunction convertContentPart(part: AISDKContentPart | null | undefined): string | null {\n if (!part || typeof part !== 'object') {\n return null;\n }\n\n switch (part.type) {\n case 'text':\n return (part as AISDKTextPart).text || null;\n\n case 'image':\n // Represent image content with a placeholder\n return '[image]';\n\n case 'file': {\n // Represent file content with filename if available\n const filePart = part as AISDKFilePart;\n if (filePart.filename || filePart.name) {\n return `[file: ${filePart.filename || filePart.name}]`;\n }\n return '[file]';\n }\n\n case 'reasoning': {\n // Represent reasoning/thinking content\n const reasoningPart = part as AISDKReasoningPart;\n if (typeof reasoningPart.text === 'string' && reasoningPart.text.length > 0) {\n return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? '...' : ''}]`;\n }\n return '[reasoning]';\n }\n\n case 'tool-call':\n // Tool calls are handled separately in assistant messages\n return null;\n\n case 'tool-result':\n // Tool results are handled separately in tool messages\n return null;\n\n default: {\n // For unknown types, try to extract any text-like content\n const unknownPart = part as { type?: string; text?: string; content?: string };\n if (typeof unknownPart.text === 'string') {\n return unknownPart.text;\n }\n if (typeof unknownPart.content === 'string') {\n return unknownPart.content;\n }\n // Represent unknown content type\n return `[${unknownPart.type || 'unknown'}]`;\n }\n }\n}\n\n/**\n * Serializes tool result data to a string for OpenAI format.\n */\nexport function serializeToolResult(resultData: unknown): string {\n if (typeof resultData === 'string') {\n return resultData;\n }\n if (resultData && typeof resultData === 'object' && 'value' in resultData) {\n const valueData = (resultData as { value: unknown }).value;\n return typeof valueData === 'string' ? valueData : JSON.stringify(valueData);\n }\n if (resultData === undefined || resultData === null) {\n return '';\n }\n try {\n return JSON.stringify(resultData);\n } catch {\n return '[unserializable result]';\n }\n}\n\n/**\n * Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.\n *\n * Supports both AI SDK v4 and v5 formats:\n * - v4 uses 'args' for tool calls and 'result' for tool results\n * - v5 uses 'input' for tool calls and 'output' for tool results\n *\n * AI SDK format:\n * { role: \"user\", content: [{ type: \"text\", text: \"hello\" }] }\n * { role: \"assistant\", content: [{ type: \"text\", text: \"...\" }, { type: \"tool-call\", toolCallId: \"...\", toolName: \"...\", args: {...} }] }\n * { role: \"tool\", content: [{ type: \"tool-result\", toolCallId: \"...\", result: {...} }] }\n *\n * OpenAI format (what Braintrust expects):\n * { role: \"user\", content: \"hello\" }\n * { role: \"assistant\", content: \"...\", tool_calls: [{ id: \"...\", type: \"function\", function: { name: \"...\", arguments: \"...\" } }] }\n * { role: \"tool\", content: \"result\", tool_call_id: \"...\" }\n */\nexport function convertAISDKMessage(message: AISDKMessage | OpenAIMessage | unknown): OpenAIMessage | unknown {\n if (!message || typeof message !== 'object') {\n return message;\n }\n\n const { role, content, ...rest } = message as AISDKMessage;\n\n // If content is already a string, return as-is (already in OpenAI format)\n if (typeof content === 'string') {\n return message;\n }\n\n // If content is an array (AI SDK format), convert based on role\n if (Array.isArray(content)) {\n // Handle empty content arrays\n if (content.length === 0) {\n return { role, content: '', ...rest };\n }\n\n // For user/system messages, extract text and represent non-text content\n if (role === 'user' || role === 'system') {\n const contentParts = content.map((part: AISDKContentPart) => convertContentPart(part)).filter(Boolean);\n\n return {\n role,\n content: contentParts.length > 0 ? contentParts.join('\\n') : '',\n ...rest,\n };\n }\n\n // For assistant messages, extract text, non-text content, AND tool calls\n if (role === 'assistant') {\n const contentParts = content\n .filter((part: AISDKContentPart) => part?.type !== 'tool-call')\n .map((part: AISDKContentPart) => convertContentPart(part))\n .filter(Boolean);\n\n const toolCallParts = content.filter((part: AISDKContentPart) => part?.type === 'tool-call');\n\n const result: OpenAIMessage = {\n role,\n content: contentParts.length > 0 ? (contentParts as string[]).join('\\n') : '',\n ...rest,\n };\n\n // Add tool_calls array if there are tool calls\n if (toolCallParts.length > 0) {\n result.tool_calls = toolCallParts.map((tc: AISDKContentPart) => {\n const toolCall = tc as AISDKToolCallPart;\n const toolCallId = toolCall.toolCallId;\n const toolName = toolCall.toolName;\n // Support both v4 'args' and v5 'input'\n const args = toolCall.args ?? toolCall.input;\n\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else if (args !== undefined && args !== null) {\n argsString = JSON.stringify(args);\n } else {\n argsString = '{}';\n }\n\n return {\n id: toolCallId,\n type: 'function' as const,\n function: {\n name: toolName,\n arguments: argsString,\n },\n };\n });\n }\n\n return result;\n }\n\n // For tool messages, convert to OpenAI tool message format\n if (role === 'tool') {\n const toolResult = content.find((part): part is AISDKToolResultPart => part?.type === 'tool-result');\n if (toolResult) {\n // Support both v4 'result' and v5 'output' fields\n const resultData = toolResult.output ?? toolResult.result;\n const resultContent = serializeToolResult(resultData);\n\n return {\n role: 'tool',\n content: resultContent,\n tool_call_id: toolResult.toolCallId,\n } as OpenAIMessage;\n }\n }\n }\n\n return message;\n}\n","import type { UsageStats } from '@mastra/core/observability';\n\n/**\n * BraintrustUsageMetrics\n *\n * Canonical metric keys expected by Braintrust for LLM usage accounting.\n */\nexport interface BraintrustUsageMetrics {\n prompt_tokens?: number;\n completion_tokens?: number;\n tokens?: number;\n completion_reasoning_tokens?: number;\n prompt_cached_tokens?: number;\n prompt_cache_creation_tokens?: number;\n}\n\n/**\n * Formats UsageStats to Braintrust's expected metric format.\n */\nexport function formatUsageMetrics(usage?: UsageStats): BraintrustUsageMetrics {\n const metrics: BraintrustUsageMetrics = {};\n\n if (usage?.inputTokens !== undefined) {\n metrics.prompt_tokens = usage.inputTokens;\n }\n\n if (usage?.outputTokens !== undefined) {\n metrics.completion_tokens = usage.outputTokens;\n }\n\n // Compute total if we have both\n if (metrics.prompt_tokens !== undefined && metrics.completion_tokens !== undefined) {\n metrics.tokens = metrics.prompt_tokens + metrics.completion_tokens;\n }\n\n if (usage?.outputDetails?.reasoning !== undefined) {\n metrics.completion_reasoning_tokens = usage.outputDetails.reasoning;\n }\n\n if (usage?.inputDetails?.cacheRead !== undefined) {\n metrics.prompt_cached_tokens = usage.inputDetails.cacheRead;\n }\n\n if (usage?.inputDetails?.cacheWrite !== undefined) {\n metrics.prompt_cache_creation_tokens = usage.inputDetails.cacheWrite;\n }\n\n return metrics;\n}\n","/**\n * Thread view reconstruction for Braintrust.\n *\n * Reconstructs LLM output in OpenAI Chat Completion format by examining\n * child MODEL_STEP and TOOL_CALL spans. This enables Braintrust's Thread\n * view to properly display the full conversation flow including tool calls.\n *\n * See THREAD_VIEW_RECONSTRUCTION.md for details.\n */\n\nimport { removeNullish } from './formatter';\nimport type { OpenAIMessage } from './formatter';\n\n// ==============================================================================\n// Thread view reconstruction types\n// ==============================================================================\n\n/**\n * Tool call data accumulated from MODEL_STEP and TOOL_CALL spans\n */\nexport interface ThreadToolCall {\n toolCallId: string;\n toolName: string;\n args: unknown;\n result?: unknown; // filled in when TOOL_CALL ends\n startTime?: Date; // from TOOL_CALL span, for ordering multiple tool calls within a step\n}\n\n/**\n * Step data accumulated from MODEL_STEP spans for Thread view reconstruction\n */\nexport interface ThreadStepData {\n stepSpanId: string;\n stepIndex: number; // for ordering steps correctly\n text?: string;\n toolCalls?: ThreadToolCall[];\n}\n\n/**\n * Accumulated data for reconstructing Braintrust Thread view.\n * Populated for MODEL_GENERATION spans as child MODEL_STEP and TOOL_CALL spans complete.\n */\nexport type ThreadData = ThreadStepData[];\n\n/**\n * Tool result data stored when TOOL_CALL spans end (before MODEL_STEP ends)\n */\nexport interface PendingToolResult {\n result: unknown;\n startTime: Date;\n}\n\n// ==============================================================================\n// Thread view reconstruction\n// ==============================================================================\n\n/**\n * Reconstruct the Thread view output from accumulated threadData.\n * Converts to OpenAI Chat Completion message format.\n */\nexport function reconstructThreadOutput(threadData: ThreadData, originalOutput: unknown): OpenAIMessage[] {\n const messages: OpenAIMessage[] = [];\n\n // Sort steps by stepIndex\n const sortedSteps = [...threadData].sort((a, b) => a.stepIndex - b.stepIndex);\n\n for (const step of sortedSteps) {\n // Sort tool calls by startTime within each step\n const sortedToolCalls = step.toolCalls\n ? [...step.toolCalls].sort((a, b) => {\n if (!a.startTime || !b.startTime) return 0;\n return a.startTime.getTime() - b.startTime.getTime();\n })\n : [];\n\n if (sortedToolCalls.length > 0) {\n // Add assistant message with tool_calls\n messages.push({\n role: 'assistant',\n content: step.text || '',\n tool_calls: sortedToolCalls.map(tc => {\n // Clean null/undefined values from args before stringifying\n const cleanArgs =\n tc.args && typeof tc.args === 'object' && !Array.isArray(tc.args)\n ? removeNullish(tc.args as Record<string, unknown>)\n : tc.args;\n return {\n id: tc.toolCallId,\n type: 'function' as const,\n function: {\n name: tc.toolName,\n arguments: typeof cleanArgs === 'string' ? cleanArgs : JSON.stringify(cleanArgs),\n },\n };\n }),\n });\n\n // Add tool messages for each result\n for (const tc of sortedToolCalls) {\n if (tc.result !== undefined) {\n messages.push({\n role: 'tool',\n content: typeof tc.result === 'string' ? tc.result : JSON.stringify(tc.result),\n tool_call_id: tc.toolCallId,\n });\n }\n }\n } else if (step.text) {\n // Step with text only (final response)\n messages.push({\n role: 'assistant',\n content: step.text,\n });\n }\n }\n\n // If we have messages and the last one is a tool message,\n // add the final assistant text from original output\n if (messages.length > 0) {\n const lastMessage = messages[messages.length - 1]!;\n const originalText = (originalOutput as { text?: string })?.text;\n\n // If the last message is a tool response and we have final text, add it\n if (originalText && lastMessage.role === 'tool') {\n messages.push({\n role: 'assistant',\n content: originalText,\n });\n }\n }\n\n return messages;\n}\n","/**\n * Braintrust Exporter for Mastra Observability\n *\n * This exporter sends observability data to Braintrust.\n * Root spans become top-level Braintrust spans (no trace wrapper).\n * Events are handled as zero-duration spans with matching start/end times.\n */\n\nimport type { AnyExportedSpan, ModelGenerationAttributes, ScoreEvent, SpanErrorInfo } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { TrackingExporter } from '@mastra/observability';\nimport type { TraceData, TrackingExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport { removeNullish, convertAISDKMessage, serializeToolResult } from './formatter';\nimport type { OpenAIMessage } from './formatter';\nimport { formatUsageMetrics } from './metrics';\nimport { reconstructThreadOutput } from './thread-reconstruction';\nimport type { ThreadData, ThreadStepData, PendingToolResult } from './thread-reconstruction';\n\ntype BraintrustSpanType = 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool';\n\n/**\n * Explicit span parentage accepted by Braintrust's `startSpan()`.\n * `rootSpanId` groups rows into a trace; `spanId` / `parentSpanIds` populate\n * the row's `span_parents`.\n */\nexport type BraintrustParentSpanIds =\n | { spanId: string; rootSpanId: string }\n | { parentSpanIds: string[]; rootSpanId: string };\n\n/**\n * The subset of a Braintrust span used by the exporter.\n *\n * This structural interface lets applications provide spans from a different\n * compatible Braintrust SDK instance without coupling Mastra's public types to\n * a specific SDK version.\n */\nexport interface BraintrustSpan {\n id: string;\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n parentSpanIds?: BraintrustParentSpanIds;\n event: Record<string, any>;\n }): BraintrustSpan;\n log(event: Record<string, any>): void;\n end(args?: { endTime?: number }): number;\n}\n\n/**\n * The subset of a Braintrust logger used by the exporter.\n *\n * Logger instances from compatible Braintrust SDK versions satisfy this\n * interface without sharing the same nominal SDK types as the exporter.\n */\nexport interface BraintrustLogger {\n startSpan(args: {\n spanId: string;\n name: string;\n type: BraintrustSpanType;\n startTime: number;\n parentSpanIds?: BraintrustParentSpanIds;\n event: Record<string, any>;\n }): BraintrustSpan;\n logFeedback(event: {\n id: string;\n scores: Record<string, number>;\n comment?: string;\n metadata?: Record<string, any>;\n source: 'external';\n }): void;\n}\n\n/**\n * Extended Braintrust span data that includes span type and thread reconstruction data\n */\ninterface BraintrustSpanData {\n span: BraintrustSpan;\n spanType: SpanType;\n threadData?: ThreadData; // only populated for MODEL_GENERATION spans\n // Tool results stored when TOOL_CALL ends (may arrive before MODEL_STEP ends)\n pendingToolResults?: Map<string, PendingToolResult>; // keyed by toolCallId\n}\n\nexport interface BraintrustExporterConfig extends TrackingExporterConfig {\n /**\n * Optional Braintrust logger instance.\n * When provided, enables integration with Braintrust contexts such as:\n * - Evals: Agent traces nest inside eval task spans\n * - logger.traced(): Agent traces nest inside traced spans\n * - Parent spans: Auto-detects and attaches to external Braintrust spans\n */\n braintrustLogger?: BraintrustLogger;\n\n /**\n * Optional resolver for the active Braintrust span.\n *\n * Pass Braintrust's `currentSpan` from the same package instance that creates\n * `Eval()` or `logger.traced()` spans when your app and Mastra may resolve\n * different copies of the `braintrust` package.\n */\n currentSpan?: () => BraintrustSpan | undefined;\n\n /** Braintrust API key. Required if logger is not provided. */\n apiKey?: string;\n /** Optional custom endpoint */\n endpoint?: string;\n /** Braintrust project name (default: 'mastra-tracing') */\n projectName?: string;\n /** Support tuning parameters */\n tuningParameters?: Record<string, any>;\n}\n\ntype BraintrustRoot = BraintrustLogger | BraintrustSpan;\ntype BraintrustTrackedSpan = BraintrustSpanData;\ntype BraintrustEvent = BraintrustSpan;\ntype BraintrustMetadata = unknown;\ntype BraintrustTraceData = TraceData<BraintrustRoot, BraintrustTrackedSpan, BraintrustEvent, BraintrustMetadata>;\n\n// Default span type for all spans\nconst DEFAULT_SPAN_TYPE = 'task';\n\n// Exceptions to the default mapping\nconst SPAN_TYPE_EXCEPTIONS: Partial<Record<SpanType, string>> = {\n [SpanType.MODEL_GENERATION]: 'llm',\n [SpanType.TOOL_CALL]: 'tool',\n [SpanType.MCP_TOOL_CALL]: 'tool',\n [SpanType.PROVIDER_TOOL_CALL]: 'tool',\n [SpanType.WORKFLOW_CONDITIONAL_EVAL]: 'function',\n [SpanType.WORKFLOW_WAIT_EVENT]: 'function',\n};\n\n// Mapping function - returns valid Braintrust span types\nfunction mapSpanType(spanType: SpanType): 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool' {\n return (SPAN_TYPE_EXCEPTIONS[spanType] as any) ?? DEFAULT_SPAN_TYPE;\n}\n\nexport class BraintrustExporter extends TrackingExporter<\n BraintrustRoot,\n BraintrustTrackedSpan,\n BraintrustEvent,\n BraintrustMetadata,\n BraintrustExporterConfig\n> {\n name = 'braintrust';\n\n // Flags and logger for context-aware mode\n #useProvidedLogger: boolean;\n #providedLogger?: BraintrustLogger;\n #localLogger?: BraintrustLogger;\n\n constructor(config: BraintrustExporterConfig = {}) {\n // Resolve env vars BEFORE calling super (config is readonly in base class)\n const resolvedApiKey = config.apiKey ?? process.env.BRAINTRUST_API_KEY;\n const resolvedEndpoint = config.endpoint ?? process.env.BRAINTRUST_ENDPOINT;\n\n super({\n ...config,\n apiKey: resolvedApiKey,\n endpoint: resolvedEndpoint,\n });\n\n this.#useProvidedLogger = !!config.braintrustLogger;\n\n if (this.#useProvidedLogger) {\n // Use provided logger - enables Braintrust context integration\n this.#providedLogger = config.braintrustLogger;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!this.config.apiKey) {\n this.setDisabled(\n `Missing required API key. Set BRAINTRUST_API_KEY environment variable or pass apiKey in config.`,\n );\n return;\n }\n // lazy create logger on first rootSpan\n this.#localLogger = undefined;\n }\n }\n\n private async getLocalLogger(): Promise<BraintrustLogger | undefined> {\n if (this.#localLogger) {\n return this.#localLogger;\n }\n try {\n const logger = await initLogger({\n projectName: this.config.projectName ?? 'mastra-tracing',\n apiKey: this.config.apiKey,\n appUrl: this.config.endpoint,\n ...this.config.tuningParameters,\n });\n this.#localLogger = logger;\n return logger;\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n async onScoreEvent(event: ScoreEvent): Promise<void> {\n if (this.isDisabled) return;\n\n const { score } = event;\n const rowId = score.spanId ?? score.traceId;\n if (!rowId) {\n this.logger.debug('Braintrust exporter: skipping score with no spanId or traceId', {\n scorerId: score.scorerId,\n });\n return;\n }\n\n const logger = this.#useProvidedLogger ? this.#providedLogger : await this.getLocalLogger();\n if (!logger) return;\n\n const name = score.scorerName ?? score.scorerId;\n\n try {\n logger.logFeedback({\n id: rowId,\n scores: { [name]: score.score },\n ...(score.reason ? { comment: score.reason } : {}),\n metadata: {\n scorerId: score.scorerId,\n ...(score.scoreSource ? { scoreSource: score.scoreSource } : {}),\n ...(score.metadata ?? {}),\n },\n source: 'external',\n });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to submit score', {\n error: err,\n traceId: score.traceId,\n spanId: score.spanId,\n scorerId: score.scorerId,\n });\n }\n }\n\n private startSpan(args: {\n parent: BraintrustSpan | BraintrustLogger;\n span: AnyExportedSpan;\n parentSpanIds?: BraintrustParentSpanIds;\n }): BraintrustSpanData {\n const { parent, span, parentSpanIds } = args;\n const payload = this.buildSpanPayload(span);\n\n // Braintrust's startSpan() accepts data properties via the `event` parameter\n // which maps to StartSpanEventArgs (ExperimentLogPartialArgs & Partial<IdField>)\n // This includes: input, output, metadata, metrics, tags, scores, error, etc.\n const braintrustSpan = parent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n ...(parentSpanIds ? { parentSpanIds } : {}),\n event: {\n id: span.id, // Use Mastra span ID as Braintrust row ID for logFeedback() compatibility\n ...payload,\n },\n });\n\n // Create BraintrustSpanData with span type for tree walking\n // Initialize threadData and pendingToolResults for MODEL_GENERATION spans (used for Thread view reconstruction)\n const isModelGeneration = span.type === SpanType.MODEL_GENERATION;\n return {\n span: braintrustSpan,\n spanType: span.type,\n threadData: isModelGeneration ? [] : undefined,\n pendingToolResults: isModelGeneration ? new Map() : undefined,\n };\n }\n\n /**\n * Explicit parentage for a root span started directly from a logger.\n *\n * Pinning `rootSpanId` to the Mastra trace ID makes every root sharing a\n * trace land in the same Braintrust trace — required for a resumed workflow\n * run to rejoin the trace of its suspended half, which was exported by an\n * earlier process. Genuine roots keep empty `span_parents` so Braintrust\n * still treats them as trace roots; only a resumed continuation (marked by\n * core with `resumedFromSpanId`) links to its persisted parent span. Roots\n * with other parent IDs (e.g. an ambient OTEL span from the bridge) are not\n * linked — their parent was never exported to Braintrust.\n *\n * Spans nested under an enclosing Braintrust span (Eval(), logger.traced(),\n * or a span passed as `braintrustLogger`) get no explicit parentage: the\n * startSpan() chain already inherits the external trace's rootSpanId.\n */\n private rootParentSpanIds(root: BraintrustRoot, span: AnyExportedSpan): BraintrustParentSpanIds | undefined {\n // Braintrust SDK objects carry a `kind` discriminant ('logger' | 'span').\n // Structural stand-ins may omit it; for those, spans are the ones that end().\n const kind = (root as { kind?: string }).kind;\n const isLoggerRoot = kind ? kind === 'logger' : !('end' in root);\n if (!isLoggerRoot) {\n return undefined;\n }\n if (span.parentSpanId && span.parentSpanId === span.metadata?.resumedFromSpanId) {\n return { spanId: span.parentSpanId, rootSpanId: span.traceId };\n }\n return { parentSpanIds: [], rootSpanId: span.traceId };\n }\n\n protected override async _buildRoot(_args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustRoot | undefined> {\n if (this.#useProvidedLogger) {\n // Try to find a Braintrust span to attach to:\n // 1. Auto-detect from Braintrust's current span (logger.traced(), Eval(), etc.)\n // 2. Fall back to the configured logger\n let externalSpan: BraintrustSpan | undefined;\n try {\n externalSpan = this.config.currentSpan?.();\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to resolve configured currentSpan', { error: err });\n }\n externalSpan ??= currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (externalSpan && externalSpan.id) {\n // External span detected - attach Mastra traces to it\n return externalSpan;\n } else {\n // No external span - use provided logger\n return this.#providedLogger!;\n }\n } else {\n // Use the local logger\n return this.getLocalLogger();\n }\n }\n\n protected override async _buildSpan(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustSpanData | undefined> {\n const { span, traceData } = args;\n\n if (span.isRootSpan) {\n const root = traceData.getRoot();\n if (root) {\n return this.startSpan({ parent: root, span, parentSpanIds: this.rootParentSpanIds(root, span) });\n }\n } else {\n const parent = traceData.getParent(args);\n if (parent) {\n // Parent could be BraintrustSpanData (has .span) or BraintrustRoot (Logger/Span, no .span)\n const parentSpan = 'span' in parent ? parent.span : parent;\n return this.startSpan({ parent: parentSpan, span });\n }\n }\n }\n\n protected override async _buildEvent(args: {\n span: AnyExportedSpan;\n traceData: BraintrustTraceData;\n }): Promise<BraintrustEvent | undefined> {\n const spanData = await this._buildSpan(args);\n\n if (!spanData) {\n // parent doesn't exist and not creating rootSpan, return early data\n return;\n }\n\n spanData.span.end({ endTime: args.span.startTime.getTime() / 1000 });\n return spanData.span;\n }\n\n protected override async _updateSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n spanData.span.log(this.buildSpanPayload(span, false));\n }\n\n protected override async _finishSpan(args: { span: AnyExportedSpan; traceData: BraintrustTraceData }): Promise<void> {\n const { span, traceData } = args;\n\n const spanData = traceData.getSpan({ spanId: span.id });\n if (!spanData) {\n return;\n }\n\n // Handle thread data accumulation for MODEL_STEP and tool spans.\n // PROVIDER_TOOL_CALL is excluded: provider results are not merged into the\n // reconstructed thread output. Since @mastra/core parents these spans under\n // the delivering MODEL_STEP, findModelGenerationAncestor would now succeed,\n // so accumulating them is possible as a follow-up.\n if (span.type === SpanType.MODEL_STEP) {\n this.accumulateModelStepData(span, traceData);\n } else if (span.type === SpanType.TOOL_CALL || span.type === SpanType.MCP_TOOL_CALL) {\n this.accumulateToolCallResult(span, traceData);\n }\n\n // Build payload - for MODEL_GENERATION, may reconstruct output from threadData\n const payload =\n span.type === SpanType.MODEL_GENERATION\n ? this.buildModelGenerationPayload(span, spanData)\n : this.buildSpanPayload(span, false);\n\n spanData.span.log(payload);\n\n if (span.endTime) {\n spanData.span.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n spanData.span.end();\n }\n }\n\n protected override async _abortSpan(args: { span: BraintrustTrackedSpan; reason: SpanErrorInfo }): Promise<void> {\n const { span: spanData, reason } = args;\n spanData.span.log({\n error: reason.message,\n metadata: { errorDetails: reason },\n });\n spanData.span.end();\n }\n\n // ==============================================================================\n // Thread view reconstruction helpers\n // ==============================================================================\n\n /**\n * Walk up the tree to find the MODEL_GENERATION ancestor span.\n * Returns the BraintrustSpanData if found, undefined otherwise.\n */\n private findModelGenerationAncestor(spanId: string, traceData: BraintrustTraceData): BraintrustSpanData | undefined {\n let currentId: string | undefined = spanId;\n\n while (currentId) {\n const parentId = traceData.getParentId({ spanId: currentId });\n if (!parentId) return undefined;\n\n const parentSpanData = traceData.getSpan({ spanId: parentId });\n if (parentSpanData?.spanType === SpanType.MODEL_GENERATION) {\n return parentSpanData;\n }\n currentId = parentId;\n }\n\n return undefined;\n }\n\n /**\n * Accumulate MODEL_STEP data to the parent MODEL_GENERATION's threadData.\n * Called when a MODEL_STEP span ends.\n */\n private accumulateModelStepData(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.threadData) {\n return;\n }\n\n // Extract step data from MODEL_STEP output and attributes\n const output = span.output as\n | { text?: string; toolCalls?: Array<{ toolCallId: string; toolName: string; args: unknown }> }\n | undefined;\n const attributes = span.attributes as { stepIndex?: number } | undefined;\n\n const stepData: ThreadStepData = {\n stepSpanId: span.id,\n stepIndex: attributes?.stepIndex ?? 0,\n text: output?.text,\n toolCalls: output?.toolCalls?.map(tc => ({\n toolCallId: tc.toolCallId,\n toolName: tc.toolName,\n args: tc.args,\n })),\n };\n\n modelGenSpanData.threadData.push(stepData);\n }\n\n /**\n * Store a tool result (TOOL_CALL or MCP_TOOL_CALL) in parent MODEL_GENERATION's pendingToolResults.\n * Called when the tool span ends.\n * Results are merged into threadData when MODEL_GENERATION ends.\n */\n private accumulateToolCallResult(span: AnyExportedSpan, traceData: BraintrustTraceData): void {\n const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);\n if (!modelGenSpanData?.pendingToolResults) {\n return;\n }\n\n const toolCallId = this.resolveToolCallId(span);\n\n // Store the result for later merging\n modelGenSpanData.pendingToolResults.set(toolCallId, {\n result: span.output,\n startTime: span.startTime,\n });\n }\n\n /**\n * Build the payload for MODEL_GENERATION span, reconstructing output from threadData if available.\n */\n private buildModelGenerationPayload(span: AnyExportedSpan, spanData: BraintrustSpanData): Record<string, any> {\n const basePayload = this.buildSpanPayload(span, false);\n\n // Check if we have threadData with tool calls to reconstruct\n const threadData = spanData.threadData;\n if (!threadData || threadData.length === 0) {\n return basePayload;\n }\n\n // Merge pending tool results into threadData\n if (spanData.pendingToolResults && spanData.pendingToolResults.size > 0) {\n for (const step of threadData) {\n if (step.toolCalls) {\n for (const toolCall of step.toolCalls) {\n const pendingResult = spanData.pendingToolResults.get(toolCall.toolCallId);\n if (pendingResult) {\n toolCall.result = pendingResult.result;\n toolCall.startTime = pendingResult.startTime;\n }\n }\n }\n }\n }\n\n // Check if any step has tool calls\n const hasToolCalls = threadData.some(step => step.toolCalls && step.toolCalls.length > 0);\n if (!hasToolCalls) {\n return basePayload;\n }\n\n // Reconstruct output as OpenAI messages\n const reconstructedOutput = reconstructThreadOutput(threadData, span.output);\n return {\n ...basePayload,\n output: reconstructedOutput,\n };\n }\n\n /**\n * Transforms MODEL_GENERATION input to Braintrust Thread view format.\n * Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires\n * for proper rendering of threads (fixes #11023).\n */\n private transformInput(input: unknown, spanType: SpanType): unknown {\n if (spanType === SpanType.MODEL_GENERATION) {\n // If input is already an array of messages, convert AI SDK format to OpenAI format\n if (Array.isArray(input)) {\n return input.map((msg: unknown) => convertAISDKMessage(msg));\n }\n\n // If input has a messages array\n if (\n input &&\n typeof input === 'object' &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown[] }).messages)\n ) {\n return (input as { messages: unknown[] }).messages.map((msg: unknown) => convertAISDKMessage(msg));\n }\n }\n\n return input;\n }\n\n /**\n * Transforms MODEL_GENERATION output to Braintrust Thread view format.\n */\n private transformOutput(output: any, spanType: SpanType): any {\n if (spanType === SpanType.MODEL_GENERATION) {\n if (!output || typeof output !== 'object') {\n return output;\n }\n const { text, ...rest } = output;\n // Remove null/undefined values from rest to keep Thread view clean\n return { role: 'assistant', content: text, ...removeNullish(rest) };\n }\n\n return output;\n }\n\n private isToolSpan(span: AnyExportedSpan): boolean {\n return (\n span.type === SpanType.TOOL_CALL ||\n span.type === SpanType.MCP_TOOL_CALL ||\n span.type === SpanType.PROVIDER_TOOL_CALL\n );\n }\n\n private getToolName(span: AnyExportedSpan): string {\n if (span.entityName) {\n return span.entityName;\n }\n const match = /'([^']+)'/.exec(span.name);\n return match?.[1] ?? span.name;\n }\n\n private resolveToolCallId(span: AnyExportedSpan): string {\n const attrs = span.attributes as { toolCallId?: string } | undefined;\n return (\n attrs?.toolCallId ??\n span.metadata?.toolCallId ??\n (span.input as { toolCallId?: string } | undefined)?.toolCallId ??\n span.id\n );\n }\n\n private toToolCallInput(span: AnyExportedSpan): OpenAIMessage[] {\n const args = span.input;\n let argsString: string;\n if (typeof args === 'string') {\n argsString = args;\n } else {\n try {\n argsString = JSON.stringify(args ?? {});\n } catch {\n argsString = '[unserializable result]';\n }\n }\n return [\n {\n role: 'assistant',\n content: '',\n tool_calls: [\n {\n id: this.resolveToolCallId(span),\n type: 'function',\n function: {\n name: this.getToolName(span),\n arguments: argsString,\n },\n },\n ],\n },\n ];\n }\n\n private toToolCallOutput(span: AnyExportedSpan): OpenAIMessage {\n return {\n role: 'tool',\n content: serializeToolResult(span.output),\n tool_call_id: this.resolveToolCallId(span),\n };\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate = true): Record<string, any> {\n const payload: Record<string, any> = {};\n\n const isToolType = this.isToolSpan(span);\n\n if (span.input !== undefined) {\n payload.input = isToolType ? this.toToolCallInput(span) : this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = isToolType ? this.toToolCallOutput(span) : this.transformOutput(span.output, span.type);\n }\n\n if (isCreate && span.isRootSpan && span.tags?.length) {\n payload.tags = span.tags;\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n // Spread span.metadata first, then set spanType to prevent accidental override\n payload.metadata = {\n ...span.metadata,\n spanType: span.type,\n };\n\n if (isCreate) {\n payload.metadata['mastra-trace-id'] = span.traceId;\n }\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n // Model goes to metadata\n if (modelAttr.model !== undefined) {\n payload.metadata.model = modelAttr.model;\n }\n\n // Provider goes to metadata (if provided by attributes)\n if (modelAttr.provider !== undefined) {\n payload.metadata.provider = modelAttr.provider;\n }\n\n // Prefer resolved model ID (e.g. \"claude-sonnet-4-5-20250929\") over\n // gateway aliases (e.g. \"claude-sonnet-4.5\") for accurate cost estimation\n if (modelAttr.responseModel !== undefined) {\n payload.metadata.model = modelAttr.responseModel;\n }\n\n // Usage/token info goes to metrics\n payload.metrics = formatUsageMetrics(modelAttr.usage);\n\n // Time to first token (TTFT) for streaming responses\n // Braintrust expects TTFT in seconds (not milliseconds)\n if (modelAttr.completionStartTime) {\n payload.metrics.time_to_first_token =\n (modelAttr.completionStartTime.getTime() - span.startTime.getTime()) / 1000;\n }\n\n // Model parameters go to metadata\n if (modelAttr.parameters !== undefined) {\n payload.metadata.modelParameters = modelAttr.parameters;\n }\n\n // Other LLM attributes go to metadata\n const otherAttributes = omitKeys(attributes, [\n 'model',\n 'responseModel',\n 'usage',\n 'parameters',\n 'completionStartTime',\n ]);\n payload.metadata = {\n ...payload.metadata,\n ...otherAttributes,\n };\n } else {\n // For non-LLM spans, put all attributes in metadata\n payload.metadata = {\n ...payload.metadata,\n ...attributes,\n };\n }\n\n // Handle errors\n if (span.errorInfo) {\n payload.error = span.errorInfo.message;\n payload.metadata.errorDetails = span.errorInfo;\n }\n\n // Clean up empty metrics object\n if (Object.keys(payload.metrics).length === 0) {\n delete payload.metrics;\n }\n\n // Remove null/undefined values from metadata to keep Braintrust UI clean\n payload.metadata = removeNullish(payload.metadata);\n\n return payload;\n }\n}\n"],"mappings":"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"}
|
package/dist/tracing.d.ts
CHANGED
|
@@ -11,6 +11,18 @@ import { TrackingExporter } from '@mastra/observability';
|
|
|
11
11
|
import type { TraceData, TrackingExporterConfig } from '@mastra/observability';
|
|
12
12
|
import type { ThreadData, PendingToolResult } from './thread-reconstruction.js';
|
|
13
13
|
type BraintrustSpanType = 'llm' | 'score' | 'function' | 'eval' | 'task' | 'tool';
|
|
14
|
+
/**
|
|
15
|
+
* Explicit span parentage accepted by Braintrust's `startSpan()`.
|
|
16
|
+
* `rootSpanId` groups rows into a trace; `spanId` / `parentSpanIds` populate
|
|
17
|
+
* the row's `span_parents`.
|
|
18
|
+
*/
|
|
19
|
+
export type BraintrustParentSpanIds = {
|
|
20
|
+
spanId: string;
|
|
21
|
+
rootSpanId: string;
|
|
22
|
+
} | {
|
|
23
|
+
parentSpanIds: string[];
|
|
24
|
+
rootSpanId: string;
|
|
25
|
+
};
|
|
14
26
|
/**
|
|
15
27
|
* The subset of a Braintrust span used by the exporter.
|
|
16
28
|
*
|
|
@@ -25,6 +37,7 @@ export interface BraintrustSpan {
|
|
|
25
37
|
name: string;
|
|
26
38
|
type: BraintrustSpanType;
|
|
27
39
|
startTime: number;
|
|
40
|
+
parentSpanIds?: BraintrustParentSpanIds;
|
|
28
41
|
event: Record<string, any>;
|
|
29
42
|
}): BraintrustSpan;
|
|
30
43
|
log(event: Record<string, any>): void;
|
|
@@ -44,6 +57,7 @@ export interface BraintrustLogger {
|
|
|
44
57
|
name: string;
|
|
45
58
|
type: BraintrustSpanType;
|
|
46
59
|
startTime: number;
|
|
60
|
+
parentSpanIds?: BraintrustParentSpanIds;
|
|
47
61
|
event: Record<string, any>;
|
|
48
62
|
}): BraintrustSpan;
|
|
49
63
|
logFeedback(event: {
|
|
@@ -101,6 +115,23 @@ export declare class BraintrustExporter extends TrackingExporter<BraintrustRoot,
|
|
|
101
115
|
private getLocalLogger;
|
|
102
116
|
onScoreEvent(event: ScoreEvent): Promise<void>;
|
|
103
117
|
private startSpan;
|
|
118
|
+
/**
|
|
119
|
+
* Explicit parentage for a root span started directly from a logger.
|
|
120
|
+
*
|
|
121
|
+
* Pinning `rootSpanId` to the Mastra trace ID makes every root sharing a
|
|
122
|
+
* trace land in the same Braintrust trace — required for a resumed workflow
|
|
123
|
+
* run to rejoin the trace of its suspended half, which was exported by an
|
|
124
|
+
* earlier process. Genuine roots keep empty `span_parents` so Braintrust
|
|
125
|
+
* still treats them as trace roots; only a resumed continuation (marked by
|
|
126
|
+
* core with `resumedFromSpanId`) links to its persisted parent span. Roots
|
|
127
|
+
* with other parent IDs (e.g. an ambient OTEL span from the bridge) are not
|
|
128
|
+
* linked — their parent was never exported to Braintrust.
|
|
129
|
+
*
|
|
130
|
+
* Spans nested under an enclosing Braintrust span (Eval(), logger.traced(),
|
|
131
|
+
* or a span passed as `braintrustLogger`) get no explicit parentage: the
|
|
132
|
+
* startSpan() chain already inherits the external trace's rootSpanId.
|
|
133
|
+
*/
|
|
134
|
+
private rootParentSpanIds;
|
|
104
135
|
protected _buildRoot(_args: {
|
|
105
136
|
span: AnyExportedSpan;
|
|
106
137
|
traceData: BraintrustTraceData;
|
package/dist/tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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
|
|
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/braintrust",
|
|
3
|
-
"version": "1.3.6
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Braintrust observability provider for Mastra - includes tracing and future observability features",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"license": "Apache-2.0",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"braintrust": "^3.24.0",
|
|
28
|
-
"@mastra/observability": "1.17.1
|
|
28
|
+
"@mastra/observability": "1.17.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "22.20.1",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"tsdown": "0.22.9",
|
|
36
36
|
"typescript": "^6.0.3",
|
|
37
37
|
"vitest": "4.1.10",
|
|
38
|
-
"@internal/lint": "0.0.
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
38
|
+
"@internal/lint": "0.0.124",
|
|
39
|
+
"@mastra/core": "1.60.0",
|
|
40
|
+
"@observability/test-utils": "0.1.0",
|
|
41
|
+
"@internal/types-builder": "0.0.99"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@mastra/core": ">=1.16.0-0 <2.0.0-0",
|