@mastra/braintrust 1.0.0-beta.10 → 1.0.0-beta.11
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 +29 -0
- package/dist/index.cjs +153 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +153 -3
- package/dist/index.js.map +1 -1
- package/dist/tracing.d.ts +29 -0
- package/dist/tracing.d.ts.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @mastra/braintrust
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.11
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Formats messages in MODEL_GENERATION spans properly so the Thread view renders correctly. ([#11613](https://github.com/mastra-ai/mastra/pull/11613))
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Add embedded documentation support for Mastra packages ([#11472](https://github.com/mastra-ai/mastra/pull/11472))
|
|
12
|
+
|
|
13
|
+
Mastra packages now include embedded documentation in the published npm package under `dist/docs/`. This enables coding agents and AI assistants to understand and use the framework by reading documentation directly from `node_modules`.
|
|
14
|
+
|
|
15
|
+
Each package includes:
|
|
16
|
+
- **SKILL.md** - Entry point explaining the package's purpose and capabilities
|
|
17
|
+
- **SOURCE_MAP.json** - Machine-readable index mapping exports to types and implementation files
|
|
18
|
+
- **Topic folders** - Conceptual documentation organized by feature area
|
|
19
|
+
|
|
20
|
+
Documentation is driven by the `packages` frontmatter field in MDX files, which maps docs to their corresponding packages. CI validation ensures all docs include this field.
|
|
21
|
+
|
|
22
|
+
- Improves AI SDK message conversion for Braintrust Thread view: ([#11673](https://github.com/mastra-ai/mastra/pull/11673))
|
|
23
|
+
- Adds support for non-text content types (images, files, reasoning) with informative placeholders
|
|
24
|
+
- Handles both AI SDK v4 `result` and v5 `output` fields for tool results
|
|
25
|
+
- Gracefully handles empty content arrays and unknown content types
|
|
26
|
+
- Adds comprehensive TypeScript type definitions for message conversion
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [[`d2d3e22`](https://github.com/mastra-ai/mastra/commit/d2d3e22a419ee243f8812a84e3453dd44365ecb0), [`bc72b52`](https://github.com/mastra-ai/mastra/commit/bc72b529ee4478fe89ecd85a8be47ce0127b82a0), [`05b8bee`](https://github.com/mastra-ai/mastra/commit/05b8bee9e50e6c2a4a2bf210eca25ee212ca24fa), [`c042bd0`](https://github.com/mastra-ai/mastra/commit/c042bd0b743e0e86199d0cb83344ca7690e34a9c), [`940a2b2`](https://github.com/mastra-ai/mastra/commit/940a2b27480626ed7e74f55806dcd2181c1dd0c2), [`e0941c3`](https://github.com/mastra-ai/mastra/commit/e0941c3d7fc75695d5d258e7008fd5d6e650800c), [`0c0580a`](https://github.com/mastra-ai/mastra/commit/0c0580a42f697cd2a7d5973f25bfe7da9055038a), [`28f5f89`](https://github.com/mastra-ai/mastra/commit/28f5f89705f2409921e3c45178796c0e0d0bbb64), [`e601b27`](https://github.com/mastra-ai/mastra/commit/e601b272c70f3a5ecca610373aa6223012704892), [`3d3366f`](https://github.com/mastra-ai/mastra/commit/3d3366f31683e7137d126a3a57174a222c5801fb), [`5a4953f`](https://github.com/mastra-ai/mastra/commit/5a4953f7d25bb15ca31ed16038092a39cb3f98b3), [`eb9e522`](https://github.com/mastra-ai/mastra/commit/eb9e522ce3070a405e5b949b7bf5609ca51d7fe2), [`20e6f19`](https://github.com/mastra-ai/mastra/commit/20e6f1971d51d3ff6dd7accad8aaaae826d540ed), [`8a6734a`](https://github.com/mastra-ai/mastra/commit/8a6734a9b3eb192ada1f723d248b08b2258dd6b3), [`4f0b3c6`](https://github.com/mastra-ai/mastra/commit/4f0b3c66f196c06448487f680ccbb614d281e2f7), [`74c4f22`](https://github.com/mastra-ai/mastra/commit/74c4f22ed4c71e72598eacc346ba95cdbc00294f), [`81b6a8f`](https://github.com/mastra-ai/mastra/commit/81b6a8ff79f49a7549d15d66624ac1a0b8f5f971), [`e4d366a`](https://github.com/mastra-ai/mastra/commit/e4d366aeb500371dd4210d6aa8361a4c21d87034), [`a4f010b`](https://github.com/mastra-ai/mastra/commit/a4f010b22e4355a5fdee70a1fe0f6e4a692cc29e), [`73b0bb3`](https://github.com/mastra-ai/mastra/commit/73b0bb394dba7c9482eb467a97ab283dbc0ef4db), [`5627a8c`](https://github.com/mastra-ai/mastra/commit/5627a8c6dc11fe3711b3fa7a6ffd6eb34100a306), [`3ff45d1`](https://github.com/mastra-ai/mastra/commit/3ff45d10e0c80c5335a957ab563da72feb623520), [`251df45`](https://github.com/mastra-ai/mastra/commit/251df4531407dfa46d805feb40ff3fb49769f455), [`f894d14`](https://github.com/mastra-ai/mastra/commit/f894d148946629af7b1f452d65a9cf864cec3765), [`c2b9547`](https://github.com/mastra-ai/mastra/commit/c2b9547bf435f56339f23625a743b2147ab1c7a6), [`580b592`](https://github.com/mastra-ai/mastra/commit/580b5927afc82fe460dfdf9a38a902511b6b7e7f), [`58e3931`](https://github.com/mastra-ai/mastra/commit/58e3931af9baa5921688566210f00fb0c10479fa), [`08bb631`](https://github.com/mastra-ai/mastra/commit/08bb631ae2b14684b2678e3549d0b399a6f0561e), [`4fba91b`](https://github.com/mastra-ai/mastra/commit/4fba91bec7c95911dc28e369437596b152b04cd0), [`12b0cc4`](https://github.com/mastra-ai/mastra/commit/12b0cc4077d886b1a552637dedb70a7ade93528c)]:
|
|
29
|
+
- @mastra/core@1.0.0-beta.20
|
|
30
|
+
- @mastra/observability@1.0.0-beta.10
|
|
31
|
+
|
|
3
32
|
## 1.0.0-beta.10
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -275,15 +275,165 @@ var BraintrustExporter = class extends observability.BaseExporter {
|
|
|
275
275
|
method
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.
|
|
280
|
+
*
|
|
281
|
+
* Supports both AI SDK v4 and v5 formats:
|
|
282
|
+
* - v4 uses 'args' for tool calls and 'result' for tool results
|
|
283
|
+
* - v5 uses 'input' for tool calls and 'output' for tool results
|
|
284
|
+
*
|
|
285
|
+
* AI SDK format:
|
|
286
|
+
* { role: "user", content: [{ type: "text", text: "hello" }] }
|
|
287
|
+
* { role: "assistant", content: [{ type: "text", text: "..." }, { type: "tool-call", toolCallId: "...", toolName: "...", args: {...} }] }
|
|
288
|
+
* { role: "tool", content: [{ type: "tool-result", toolCallId: "...", result: {...} }] }
|
|
289
|
+
*
|
|
290
|
+
* OpenAI format (what Braintrust expects):
|
|
291
|
+
* { role: "user", content: "hello" }
|
|
292
|
+
* { role: "assistant", content: "...", tool_calls: [{ id: "...", type: "function", function: { name: "...", arguments: "..." } }] }
|
|
293
|
+
* { role: "tool", content: "result", tool_call_id: "..." }
|
|
294
|
+
*/
|
|
295
|
+
convertAISDKMessage(message) {
|
|
296
|
+
if (!message || typeof message !== "object") {
|
|
297
|
+
return message;
|
|
298
|
+
}
|
|
299
|
+
const { role, content, ...rest } = message;
|
|
300
|
+
if (typeof content === "string") {
|
|
301
|
+
return message;
|
|
302
|
+
}
|
|
303
|
+
if (Array.isArray(content)) {
|
|
304
|
+
if (content.length === 0) {
|
|
305
|
+
return { role, content: "", ...rest };
|
|
306
|
+
}
|
|
307
|
+
if (role === "user" || role === "system") {
|
|
308
|
+
const contentParts = content.map((part) => this.convertContentPart(part)).filter(Boolean);
|
|
309
|
+
return {
|
|
310
|
+
role,
|
|
311
|
+
content: contentParts.length > 0 ? contentParts.join("\n") : "",
|
|
312
|
+
...rest
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (role === "assistant") {
|
|
316
|
+
const contentParts = content.filter((part) => part?.type !== "tool-call").map((part) => this.convertContentPart(part)).filter(Boolean);
|
|
317
|
+
const toolCallParts = content.filter((part) => part?.type === "tool-call");
|
|
318
|
+
const result = {
|
|
319
|
+
role,
|
|
320
|
+
content: contentParts.length > 0 ? contentParts.join("\n") : "",
|
|
321
|
+
...rest
|
|
322
|
+
};
|
|
323
|
+
if (toolCallParts.length > 0) {
|
|
324
|
+
result.tool_calls = toolCallParts.map((tc) => {
|
|
325
|
+
const toolCallId = tc.toolCallId;
|
|
326
|
+
const toolName = tc.toolName;
|
|
327
|
+
const args = tc.args ?? tc.input;
|
|
328
|
+
let argsString;
|
|
329
|
+
if (typeof args === "string") {
|
|
330
|
+
argsString = args;
|
|
331
|
+
} else if (args !== void 0 && args !== null) {
|
|
332
|
+
argsString = JSON.stringify(args);
|
|
333
|
+
} else {
|
|
334
|
+
argsString = "{}";
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
id: toolCallId,
|
|
338
|
+
type: "function",
|
|
339
|
+
function: {
|
|
340
|
+
name: toolName,
|
|
341
|
+
arguments: argsString
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
if (role === "tool") {
|
|
349
|
+
const toolResult = content.find((part) => part?.type === "tool-result");
|
|
350
|
+
if (toolResult) {
|
|
351
|
+
const resultData = toolResult.output ?? toolResult.result;
|
|
352
|
+
const resultContent = this.serializeToolResult(resultData);
|
|
353
|
+
return {
|
|
354
|
+
role: "tool",
|
|
355
|
+
content: resultContent,
|
|
356
|
+
tool_call_id: toolResult.toolCallId
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return message;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Converts a content part to a string representation.
|
|
365
|
+
* Handles text, image, file, reasoning, and other content types.
|
|
366
|
+
*/
|
|
367
|
+
convertContentPart(part) {
|
|
368
|
+
if (!part || typeof part !== "object") {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
switch (part.type) {
|
|
372
|
+
case "text":
|
|
373
|
+
return part.text || null;
|
|
374
|
+
case "image":
|
|
375
|
+
return "[image]";
|
|
376
|
+
case "file": {
|
|
377
|
+
const filePart = part;
|
|
378
|
+
if (filePart.filename || filePart.name) {
|
|
379
|
+
return `[file: ${filePart.filename || filePart.name}]`;
|
|
380
|
+
}
|
|
381
|
+
return "[file]";
|
|
382
|
+
}
|
|
383
|
+
case "reasoning": {
|
|
384
|
+
const reasoningPart = part;
|
|
385
|
+
if (typeof reasoningPart.text === "string" && reasoningPart.text.length > 0) {
|
|
386
|
+
return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? "..." : ""}]`;
|
|
387
|
+
}
|
|
388
|
+
return "[reasoning]";
|
|
389
|
+
}
|
|
390
|
+
case "tool-call":
|
|
391
|
+
return null;
|
|
392
|
+
case "tool-result":
|
|
393
|
+
return null;
|
|
394
|
+
default: {
|
|
395
|
+
const unknownPart = part;
|
|
396
|
+
if (typeof unknownPart.text === "string") {
|
|
397
|
+
return unknownPart.text;
|
|
398
|
+
}
|
|
399
|
+
if (typeof unknownPart.content === "string") {
|
|
400
|
+
return unknownPart.content;
|
|
401
|
+
}
|
|
402
|
+
return `[${unknownPart.type || "unknown"}]`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Serializes tool result data to a string for OpenAI format.
|
|
408
|
+
*/
|
|
409
|
+
serializeToolResult(resultData) {
|
|
410
|
+
if (typeof resultData === "string") {
|
|
411
|
+
return resultData;
|
|
412
|
+
}
|
|
413
|
+
if (resultData && typeof resultData === "object" && "value" in resultData) {
|
|
414
|
+
return typeof resultData.value === "string" ? resultData.value : JSON.stringify(resultData.value);
|
|
415
|
+
}
|
|
416
|
+
if (resultData === void 0 || resultData === null) {
|
|
417
|
+
return "";
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
return JSON.stringify(resultData);
|
|
421
|
+
} catch {
|
|
422
|
+
return "[unserializable result]";
|
|
423
|
+
}
|
|
424
|
+
}
|
|
278
425
|
/**
|
|
279
426
|
* Transforms MODEL_GENERATION input to Braintrust Thread view format.
|
|
427
|
+
* Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires
|
|
428
|
+
* for proper rendering of threads (fixes #11023).
|
|
280
429
|
*/
|
|
281
430
|
transformInput(input, spanType) {
|
|
282
431
|
if (spanType === observability$1.SpanType.MODEL_GENERATION) {
|
|
432
|
+
if (Array.isArray(input)) {
|
|
433
|
+
return input.map((msg) => this.convertAISDKMessage(msg));
|
|
434
|
+
}
|
|
283
435
|
if (input && Array.isArray(input.messages)) {
|
|
284
|
-
return input.messages;
|
|
285
|
-
} else if (input && typeof input === "object" && "content" in input) {
|
|
286
|
-
return [{ role: input.role, content: input.content }];
|
|
436
|
+
return input.messages.map((msg) => this.convertAISDKMessage(msg));
|
|
287
437
|
}
|
|
288
438
|
}
|
|
289
439
|
return input;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/metrics.ts","../src/tracing.ts"],"names":["SpanType","BaseExporter","initLogger","currentSpan","omitKeys"],"mappings":"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","file":"index.cjs","sourcesContent":["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 * 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 { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport type { Span, Logger } from 'braintrust';\nimport { formatUsageMetrics } from './metrics';\n\nconst MASTRA_TRACE_ID_METADATA_KEY = 'mastra-trace-id';\n\nexport interface BraintrustExporterConfig extends BaseExporterConfig {\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?: Logger<true>;\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 SpanData = {\n logger: Logger<true> | Span; // Braintrust logger (for root spans) or external span\n spans: Map<string, Span>; // Maps span.id to Braintrust span\n activeIds: Set<string>; // Tracks started (non-event) spans not yet ended, including root\n isExternal: boolean; // True if logger is an external span from logger.traced() or Eval()\n};\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.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 BaseExporter {\n name = 'braintrust';\n private traceMap = new Map<string, SpanData>();\n private config: BraintrustExporterConfig;\n\n // Flags and logger for context-aware mode\n private useProvidedLogger: boolean;\n private providedLogger?: Logger<true>;\n\n constructor(config: BraintrustExporterConfig) {\n super(config);\n\n if (config.braintrustLogger) {\n // Use provided logger - enables Braintrust context integration\n this.useProvidedLogger = true;\n this.providedLogger = config.braintrustLogger;\n this.config = config;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!config.apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!config.apiKey})`);\n this.config = null as any;\n this.useProvidedLogger = false;\n return;\n }\n this.useProvidedLogger = false;\n this.config = config;\n }\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.exportedSpan.isEvent) {\n await this.handleEventSpan(event.exportedSpan);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.exportedSpan);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, true);\n break;\n }\n }\n\n private async handleSpanStarted(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleSpanStarted';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n // Refcount: track active non-event spans (including root)\n if (!span.isEvent) {\n spanData.activeIds.add(span.id);\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n ...payload,\n });\n\n // Include the Mastra trace ID in the span metadata for correlation\n // Also include tags if present (only for root spans)\n braintrustSpan.log({\n metadata: {\n [MASTRA_TRACE_ID_METADATA_KEY]: span.traceId,\n },\n ...(span.isRootSpan && span.tags?.length ? { tags: span.tags } : {}),\n });\n\n spanData.spans.set(span.id, braintrustSpan);\n }\n\n private async handleSpanUpdateOrEnd(span: AnyExportedSpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustSpan = spanData.spans.get(span.id);\n if (!braintrustSpan) {\n this.logger.warn('Braintrust exporter: No Braintrust span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n return;\n }\n\n braintrustSpan.log(this.buildSpanPayload(span));\n\n if (isEnd) {\n // End the span with the correct endTime (convert milliseconds to seconds)\n if (span.endTime) {\n braintrustSpan.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n braintrustSpan.end();\n }\n\n // Refcount: mark this span as ended\n if (!span.isEvent) {\n spanData.activeIds.delete(span.id);\n }\n\n // If no more active spans remain for this trace, clean up the trace entry\n // Don't clean up if using external spans (they're managed by Braintrust)\n if (spanData.activeIds.size === 0 && !spanData.isExternal) {\n this.traceMap.delete(span.traceId);\n }\n }\n }\n\n private async handleEventSpan(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Braintrust exporter: Creating logger for event', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleEventSpan';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n // Create zero-duration span for event (convert milliseconds to seconds)\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n ...payload,\n });\n\n braintrustSpan.end({ endTime: span.startTime.getTime() / 1000 });\n }\n\n private initTraceMap(params: { traceId: string; isExternal: boolean; logger: Logger<true> | Span }): void {\n const { traceId, isExternal, logger } = params;\n\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId });\n return;\n }\n\n this.traceMap.set(traceId, {\n logger,\n spans: new Map(),\n activeIds: new Set(),\n isExternal,\n });\n }\n\n /**\n * Creates a new logger per trace using config credentials\n */\n private async initLoggerPerTrace(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\n try {\n const loggerInstance = 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\n this.initTraceMap({ logger: loggerInstance, isExternal: false, traceId: span.traceId });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err, traceId: span.traceId });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n /**\n * Uses provided logger and detects external Braintrust spans.\n * If a Braintrust span is detected (from logger.traced() or Eval()), attaches to it.\n * Otherwise, uses the provided logger instance.\n */\n private async initLoggerOrUseContext(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\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 const braintrustSpan = currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (braintrustSpan && braintrustSpan.id) {\n // External span detected - attach Mastra traces to it\n this.initTraceMap({ logger: braintrustSpan, isExternal: true, traceId: span.traceId });\n } else {\n // No external span - use provided logger\n this.initTraceMap({ logger: this.providedLogger!, isExternal: false, traceId: span.traceId });\n }\n }\n\n private getSpanData(options: { span: AnyExportedSpan; method: string }): SpanData | undefined {\n const { span, method } = options;\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n\n this.logger.warn('Braintrust exporter: No span data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private getBraintrustParent(options: {\n spanData: SpanData;\n span: AnyExportedSpan;\n method: string;\n }): Logger<true> | Span | undefined {\n const { spanData, span, method } = options;\n\n const parentId = span.parentSpanId;\n if (!parentId) {\n return spanData.logger;\n }\n\n if (spanData.spans.has(parentId)) {\n return spanData.spans.get(parentId);\n }\n\n // If the parent exists but is the root span (not represented as a Braintrust\n // span because we use the logger as the root), attach to the logger so the\n // span is not orphaned. We need to check if parentSpanId exists but the\n // parent span is not in our spans map (indicating it's the root span).\n if (parentId && !spanData.spans.has(parentId)) {\n // This means the parent exists but isn't tracked as a Braintrust span,\n // which happens when the parent is the root span (we use logger as root)\n return spanData.logger;\n }\n\n this.logger.warn('Braintrust exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n /**\n * Transforms MODEL_GENERATION input to Braintrust Thread view format.\n */\n private transformInput(input: any, spanType: SpanType): any {\n if (spanType === SpanType.MODEL_GENERATION) {\n if (input && Array.isArray(input.messages)) {\n return input.messages;\n } else if (input && typeof input === 'object' && 'content' in input) {\n return [{ role: input.role, content: input.content }];\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 const { text, ...rest } = output;\n return { role: 'assistant', content: text, ...rest };\n }\n\n return output;\n }\n\n private buildSpanPayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (span.input !== undefined) {\n payload.input = this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = this.transformOutput(span.output, span.type);\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n payload.metadata = {\n spanType: span.type,\n ...span.metadata,\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 // 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, ['model', 'usage', 'parameters', 'completionStartTime']);\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 return payload;\n }\n\n async shutdown(): Promise<void> {\n if (!this.config) {\n return;\n }\n\n // End all active spans\n for (const [_traceId, spanData] of this.traceMap) {\n for (const [_spanId, span] of spanData.spans) {\n span.end();\n }\n // Loggers don't have an explicit shutdown method\n }\n this.traceMap.clear();\n await super.shutdown();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/metrics.ts","../src/tracing.ts"],"names":["SpanType","BaseExporter","initLogger","currentSpan","omitKeys"],"mappings":"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","file":"index.cjs","sourcesContent":["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 * 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 { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport type { Span, Logger } from 'braintrust';\nimport { formatUsageMetrics } from './metrics';\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 */\ninterface 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 */\ninterface 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\nconst MASTRA_TRACE_ID_METADATA_KEY = 'mastra-trace-id';\n\nexport interface BraintrustExporterConfig extends BaseExporterConfig {\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?: Logger<true>;\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 SpanData = {\n logger: Logger<true> | Span; // Braintrust logger (for root spans) or external span\n spans: Map<string, Span>; // Maps span.id to Braintrust span\n activeIds: Set<string>; // Tracks started (non-event) spans not yet ended, including root\n isExternal: boolean; // True if logger is an external span from logger.traced() or Eval()\n};\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.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 BaseExporter {\n name = 'braintrust';\n private traceMap = new Map<string, SpanData>();\n private config: BraintrustExporterConfig;\n\n // Flags and logger for context-aware mode\n private useProvidedLogger: boolean;\n private providedLogger?: Logger<true>;\n\n constructor(config: BraintrustExporterConfig) {\n super(config);\n\n if (config.braintrustLogger) {\n // Use provided logger - enables Braintrust context integration\n this.useProvidedLogger = true;\n this.providedLogger = config.braintrustLogger;\n this.config = config;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!config.apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!config.apiKey})`);\n this.config = null as any;\n this.useProvidedLogger = false;\n return;\n }\n this.useProvidedLogger = false;\n this.config = config;\n }\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.exportedSpan.isEvent) {\n await this.handleEventSpan(event.exportedSpan);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.exportedSpan);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, true);\n break;\n }\n }\n\n private async handleSpanStarted(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleSpanStarted';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n // Refcount: track active non-event spans (including root)\n if (!span.isEvent) {\n spanData.activeIds.add(span.id);\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n ...payload,\n });\n\n // Include the Mastra trace ID in the span metadata for correlation\n // Also include tags if present (only for root spans)\n braintrustSpan.log({\n metadata: {\n [MASTRA_TRACE_ID_METADATA_KEY]: span.traceId,\n },\n ...(span.isRootSpan && span.tags?.length ? { tags: span.tags } : {}),\n });\n\n spanData.spans.set(span.id, braintrustSpan);\n }\n\n private async handleSpanUpdateOrEnd(span: AnyExportedSpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustSpan = spanData.spans.get(span.id);\n if (!braintrustSpan) {\n this.logger.warn('Braintrust exporter: No Braintrust span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n return;\n }\n\n braintrustSpan.log(this.buildSpanPayload(span));\n\n if (isEnd) {\n // End the span with the correct endTime (convert milliseconds to seconds)\n if (span.endTime) {\n braintrustSpan.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n braintrustSpan.end();\n }\n\n // Refcount: mark this span as ended\n if (!span.isEvent) {\n spanData.activeIds.delete(span.id);\n }\n\n // If no more active spans remain for this trace, clean up the trace entry\n // Don't clean up if using external spans (they're managed by Braintrust)\n if (spanData.activeIds.size === 0 && !spanData.isExternal) {\n this.traceMap.delete(span.traceId);\n }\n }\n }\n\n private async handleEventSpan(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Braintrust exporter: Creating logger for event', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleEventSpan';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n // Create zero-duration span for event (convert milliseconds to seconds)\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n ...payload,\n });\n\n braintrustSpan.end({ endTime: span.startTime.getTime() / 1000 });\n }\n\n private initTraceMap(params: { traceId: string; isExternal: boolean; logger: Logger<true> | Span }): void {\n const { traceId, isExternal, logger } = params;\n\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId });\n return;\n }\n\n this.traceMap.set(traceId, {\n logger,\n spans: new Map(),\n activeIds: new Set(),\n isExternal,\n });\n }\n\n /**\n * Creates a new logger per trace using config credentials\n */\n private async initLoggerPerTrace(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\n try {\n const loggerInstance = 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\n this.initTraceMap({ logger: loggerInstance, isExternal: false, traceId: span.traceId });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err, traceId: span.traceId });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n /**\n * Uses provided logger and detects external Braintrust spans.\n * If a Braintrust span is detected (from logger.traced() or Eval()), attaches to it.\n * Otherwise, uses the provided logger instance.\n */\n private async initLoggerOrUseContext(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\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 const braintrustSpan = currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (braintrustSpan && braintrustSpan.id) {\n // External span detected - attach Mastra traces to it\n this.initTraceMap({ logger: braintrustSpan, isExternal: true, traceId: span.traceId });\n } else {\n // No external span - use provided logger\n this.initTraceMap({ logger: this.providedLogger!, isExternal: false, traceId: span.traceId });\n }\n }\n\n private getSpanData(options: { span: AnyExportedSpan; method: string }): SpanData | undefined {\n const { span, method } = options;\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n\n this.logger.warn('Braintrust exporter: No span data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private getBraintrustParent(options: {\n spanData: SpanData;\n span: AnyExportedSpan;\n method: string;\n }): Logger<true> | Span | undefined {\n const { spanData, span, method } = options;\n\n const parentId = span.parentSpanId;\n if (!parentId) {\n return spanData.logger;\n }\n\n if (spanData.spans.has(parentId)) {\n return spanData.spans.get(parentId);\n }\n\n // If the parent exists but is the root span (not represented as a Braintrust\n // span because we use the logger as the root), attach to the logger so the\n // span is not orphaned. We need to check if parentSpanId exists but the\n // parent span is not in our spans map (indicating it's the root span).\n if (parentId && !spanData.spans.has(parentId)) {\n // This means the parent exists but isn't tracked as a Braintrust span,\n // which happens when the parent is the root span (we use logger as root)\n return spanData.logger;\n }\n\n this.logger.warn('Braintrust exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\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 */\n private 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: any) => this.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: any) => part?.type !== 'tool-call')\n .map((part: any) => this.convertContentPart(part))\n .filter(Boolean);\n\n const toolCallParts = content.filter((part: any) => part?.type === 'tool-call');\n\n const result: any = {\n role,\n content: contentParts.length > 0 ? contentParts.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: any) => {\n const toolCallId = tc.toolCallId;\n const toolName = tc.toolName;\n // Support both v4 'args' and v5 'input'\n const args = tc.args ?? tc.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',\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 = this.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\n /**\n * Converts a content part to a string representation.\n * Handles text, image, file, reasoning, and other content types.\n */\n private 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 */\n private serializeToolResult(resultData: any): string {\n if (typeof resultData === 'string') {\n return resultData;\n }\n if (resultData && typeof resultData === 'object' && 'value' in resultData) {\n return typeof resultData.value === 'string' ? resultData.value : JSON.stringify(resultData.value);\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 * 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: any, spanType: SpanType): any {\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: AISDKMessage) => this.convertAISDKMessage(msg));\n }\n\n // If input has a messages array\n if (input && Array.isArray(input.messages)) {\n return input.messages.map((msg: AISDKMessage) => this.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 const { text, ...rest } = output;\n return { role: 'assistant', content: text, ...rest };\n }\n\n return output;\n }\n\n private buildSpanPayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (span.input !== undefined) {\n payload.input = this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = this.transformOutput(span.output, span.type);\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n payload.metadata = {\n spanType: span.type,\n ...span.metadata,\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 // 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, ['model', 'usage', 'parameters', 'completionStartTime']);\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 return payload;\n }\n\n async shutdown(): Promise<void> {\n if (!this.config) {\n return;\n }\n\n // End all active spans\n for (const [_traceId, spanData] of this.traceMap) {\n for (const [_spanId, span] of spanData.spans) {\n span.end();\n }\n // Loggers don't have an explicit shutdown method\n }\n this.traceMap.clear();\n await super.shutdown();\n }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -273,15 +273,165 @@ var BraintrustExporter = class extends BaseExporter {
|
|
|
273
273
|
method
|
|
274
274
|
});
|
|
275
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.
|
|
278
|
+
*
|
|
279
|
+
* Supports both AI SDK v4 and v5 formats:
|
|
280
|
+
* - v4 uses 'args' for tool calls and 'result' for tool results
|
|
281
|
+
* - v5 uses 'input' for tool calls and 'output' for tool results
|
|
282
|
+
*
|
|
283
|
+
* AI SDK format:
|
|
284
|
+
* { role: "user", content: [{ type: "text", text: "hello" }] }
|
|
285
|
+
* { role: "assistant", content: [{ type: "text", text: "..." }, { type: "tool-call", toolCallId: "...", toolName: "...", args: {...} }] }
|
|
286
|
+
* { role: "tool", content: [{ type: "tool-result", toolCallId: "...", result: {...} }] }
|
|
287
|
+
*
|
|
288
|
+
* OpenAI format (what Braintrust expects):
|
|
289
|
+
* { role: "user", content: "hello" }
|
|
290
|
+
* { role: "assistant", content: "...", tool_calls: [{ id: "...", type: "function", function: { name: "...", arguments: "..." } }] }
|
|
291
|
+
* { role: "tool", content: "result", tool_call_id: "..." }
|
|
292
|
+
*/
|
|
293
|
+
convertAISDKMessage(message) {
|
|
294
|
+
if (!message || typeof message !== "object") {
|
|
295
|
+
return message;
|
|
296
|
+
}
|
|
297
|
+
const { role, content, ...rest } = message;
|
|
298
|
+
if (typeof content === "string") {
|
|
299
|
+
return message;
|
|
300
|
+
}
|
|
301
|
+
if (Array.isArray(content)) {
|
|
302
|
+
if (content.length === 0) {
|
|
303
|
+
return { role, content: "", ...rest };
|
|
304
|
+
}
|
|
305
|
+
if (role === "user" || role === "system") {
|
|
306
|
+
const contentParts = content.map((part) => this.convertContentPart(part)).filter(Boolean);
|
|
307
|
+
return {
|
|
308
|
+
role,
|
|
309
|
+
content: contentParts.length > 0 ? contentParts.join("\n") : "",
|
|
310
|
+
...rest
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (role === "assistant") {
|
|
314
|
+
const contentParts = content.filter((part) => part?.type !== "tool-call").map((part) => this.convertContentPart(part)).filter(Boolean);
|
|
315
|
+
const toolCallParts = content.filter((part) => part?.type === "tool-call");
|
|
316
|
+
const result = {
|
|
317
|
+
role,
|
|
318
|
+
content: contentParts.length > 0 ? contentParts.join("\n") : "",
|
|
319
|
+
...rest
|
|
320
|
+
};
|
|
321
|
+
if (toolCallParts.length > 0) {
|
|
322
|
+
result.tool_calls = toolCallParts.map((tc) => {
|
|
323
|
+
const toolCallId = tc.toolCallId;
|
|
324
|
+
const toolName = tc.toolName;
|
|
325
|
+
const args = tc.args ?? tc.input;
|
|
326
|
+
let argsString;
|
|
327
|
+
if (typeof args === "string") {
|
|
328
|
+
argsString = args;
|
|
329
|
+
} else if (args !== void 0 && args !== null) {
|
|
330
|
+
argsString = JSON.stringify(args);
|
|
331
|
+
} else {
|
|
332
|
+
argsString = "{}";
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
id: toolCallId,
|
|
336
|
+
type: "function",
|
|
337
|
+
function: {
|
|
338
|
+
name: toolName,
|
|
339
|
+
arguments: argsString
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
if (role === "tool") {
|
|
347
|
+
const toolResult = content.find((part) => part?.type === "tool-result");
|
|
348
|
+
if (toolResult) {
|
|
349
|
+
const resultData = toolResult.output ?? toolResult.result;
|
|
350
|
+
const resultContent = this.serializeToolResult(resultData);
|
|
351
|
+
return {
|
|
352
|
+
role: "tool",
|
|
353
|
+
content: resultContent,
|
|
354
|
+
tool_call_id: toolResult.toolCallId
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return message;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Converts a content part to a string representation.
|
|
363
|
+
* Handles text, image, file, reasoning, and other content types.
|
|
364
|
+
*/
|
|
365
|
+
convertContentPart(part) {
|
|
366
|
+
if (!part || typeof part !== "object") {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
switch (part.type) {
|
|
370
|
+
case "text":
|
|
371
|
+
return part.text || null;
|
|
372
|
+
case "image":
|
|
373
|
+
return "[image]";
|
|
374
|
+
case "file": {
|
|
375
|
+
const filePart = part;
|
|
376
|
+
if (filePart.filename || filePart.name) {
|
|
377
|
+
return `[file: ${filePart.filename || filePart.name}]`;
|
|
378
|
+
}
|
|
379
|
+
return "[file]";
|
|
380
|
+
}
|
|
381
|
+
case "reasoning": {
|
|
382
|
+
const reasoningPart = part;
|
|
383
|
+
if (typeof reasoningPart.text === "string" && reasoningPart.text.length > 0) {
|
|
384
|
+
return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? "..." : ""}]`;
|
|
385
|
+
}
|
|
386
|
+
return "[reasoning]";
|
|
387
|
+
}
|
|
388
|
+
case "tool-call":
|
|
389
|
+
return null;
|
|
390
|
+
case "tool-result":
|
|
391
|
+
return null;
|
|
392
|
+
default: {
|
|
393
|
+
const unknownPart = part;
|
|
394
|
+
if (typeof unknownPart.text === "string") {
|
|
395
|
+
return unknownPart.text;
|
|
396
|
+
}
|
|
397
|
+
if (typeof unknownPart.content === "string") {
|
|
398
|
+
return unknownPart.content;
|
|
399
|
+
}
|
|
400
|
+
return `[${unknownPart.type || "unknown"}]`;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Serializes tool result data to a string for OpenAI format.
|
|
406
|
+
*/
|
|
407
|
+
serializeToolResult(resultData) {
|
|
408
|
+
if (typeof resultData === "string") {
|
|
409
|
+
return resultData;
|
|
410
|
+
}
|
|
411
|
+
if (resultData && typeof resultData === "object" && "value" in resultData) {
|
|
412
|
+
return typeof resultData.value === "string" ? resultData.value : JSON.stringify(resultData.value);
|
|
413
|
+
}
|
|
414
|
+
if (resultData === void 0 || resultData === null) {
|
|
415
|
+
return "";
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
return JSON.stringify(resultData);
|
|
419
|
+
} catch {
|
|
420
|
+
return "[unserializable result]";
|
|
421
|
+
}
|
|
422
|
+
}
|
|
276
423
|
/**
|
|
277
424
|
* Transforms MODEL_GENERATION input to Braintrust Thread view format.
|
|
425
|
+
* Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires
|
|
426
|
+
* for proper rendering of threads (fixes #11023).
|
|
278
427
|
*/
|
|
279
428
|
transformInput(input, spanType) {
|
|
280
429
|
if (spanType === SpanType.MODEL_GENERATION) {
|
|
430
|
+
if (Array.isArray(input)) {
|
|
431
|
+
return input.map((msg) => this.convertAISDKMessage(msg));
|
|
432
|
+
}
|
|
281
433
|
if (input && Array.isArray(input.messages)) {
|
|
282
|
-
return input.messages;
|
|
283
|
-
} else if (input && typeof input === "object" && "content" in input) {
|
|
284
|
-
return [{ role: input.role, content: input.content }];
|
|
434
|
+
return input.messages.map((msg) => this.convertAISDKMessage(msg));
|
|
285
435
|
}
|
|
286
436
|
}
|
|
287
437
|
return input;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/metrics.ts","../src/tracing.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["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 * 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 { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport type { Span, Logger } from 'braintrust';\nimport { formatUsageMetrics } from './metrics';\n\nconst MASTRA_TRACE_ID_METADATA_KEY = 'mastra-trace-id';\n\nexport interface BraintrustExporterConfig extends BaseExporterConfig {\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?: Logger<true>;\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 SpanData = {\n logger: Logger<true> | Span; // Braintrust logger (for root spans) or external span\n spans: Map<string, Span>; // Maps span.id to Braintrust span\n activeIds: Set<string>; // Tracks started (non-event) spans not yet ended, including root\n isExternal: boolean; // True if logger is an external span from logger.traced() or Eval()\n};\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.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 BaseExporter {\n name = 'braintrust';\n private traceMap = new Map<string, SpanData>();\n private config: BraintrustExporterConfig;\n\n // Flags and logger for context-aware mode\n private useProvidedLogger: boolean;\n private providedLogger?: Logger<true>;\n\n constructor(config: BraintrustExporterConfig) {\n super(config);\n\n if (config.braintrustLogger) {\n // Use provided logger - enables Braintrust context integration\n this.useProvidedLogger = true;\n this.providedLogger = config.braintrustLogger;\n this.config = config;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!config.apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!config.apiKey})`);\n this.config = null as any;\n this.useProvidedLogger = false;\n return;\n }\n this.useProvidedLogger = false;\n this.config = config;\n }\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.exportedSpan.isEvent) {\n await this.handleEventSpan(event.exportedSpan);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.exportedSpan);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, true);\n break;\n }\n }\n\n private async handleSpanStarted(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleSpanStarted';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n // Refcount: track active non-event spans (including root)\n if (!span.isEvent) {\n spanData.activeIds.add(span.id);\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n ...payload,\n });\n\n // Include the Mastra trace ID in the span metadata for correlation\n // Also include tags if present (only for root spans)\n braintrustSpan.log({\n metadata: {\n [MASTRA_TRACE_ID_METADATA_KEY]: span.traceId,\n },\n ...(span.isRootSpan && span.tags?.length ? { tags: span.tags } : {}),\n });\n\n spanData.spans.set(span.id, braintrustSpan);\n }\n\n private async handleSpanUpdateOrEnd(span: AnyExportedSpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustSpan = spanData.spans.get(span.id);\n if (!braintrustSpan) {\n this.logger.warn('Braintrust exporter: No Braintrust span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n return;\n }\n\n braintrustSpan.log(this.buildSpanPayload(span));\n\n if (isEnd) {\n // End the span with the correct endTime (convert milliseconds to seconds)\n if (span.endTime) {\n braintrustSpan.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n braintrustSpan.end();\n }\n\n // Refcount: mark this span as ended\n if (!span.isEvent) {\n spanData.activeIds.delete(span.id);\n }\n\n // If no more active spans remain for this trace, clean up the trace entry\n // Don't clean up if using external spans (they're managed by Braintrust)\n if (spanData.activeIds.size === 0 && !spanData.isExternal) {\n this.traceMap.delete(span.traceId);\n }\n }\n }\n\n private async handleEventSpan(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Braintrust exporter: Creating logger for event', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleEventSpan';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n // Create zero-duration span for event (convert milliseconds to seconds)\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n ...payload,\n });\n\n braintrustSpan.end({ endTime: span.startTime.getTime() / 1000 });\n }\n\n private initTraceMap(params: { traceId: string; isExternal: boolean; logger: Logger<true> | Span }): void {\n const { traceId, isExternal, logger } = params;\n\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId });\n return;\n }\n\n this.traceMap.set(traceId, {\n logger,\n spans: new Map(),\n activeIds: new Set(),\n isExternal,\n });\n }\n\n /**\n * Creates a new logger per trace using config credentials\n */\n private async initLoggerPerTrace(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\n try {\n const loggerInstance = 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\n this.initTraceMap({ logger: loggerInstance, isExternal: false, traceId: span.traceId });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err, traceId: span.traceId });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n /**\n * Uses provided logger and detects external Braintrust spans.\n * If a Braintrust span is detected (from logger.traced() or Eval()), attaches to it.\n * Otherwise, uses the provided logger instance.\n */\n private async initLoggerOrUseContext(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\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 const braintrustSpan = currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (braintrustSpan && braintrustSpan.id) {\n // External span detected - attach Mastra traces to it\n this.initTraceMap({ logger: braintrustSpan, isExternal: true, traceId: span.traceId });\n } else {\n // No external span - use provided logger\n this.initTraceMap({ logger: this.providedLogger!, isExternal: false, traceId: span.traceId });\n }\n }\n\n private getSpanData(options: { span: AnyExportedSpan; method: string }): SpanData | undefined {\n const { span, method } = options;\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n\n this.logger.warn('Braintrust exporter: No span data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private getBraintrustParent(options: {\n spanData: SpanData;\n span: AnyExportedSpan;\n method: string;\n }): Logger<true> | Span | undefined {\n const { spanData, span, method } = options;\n\n const parentId = span.parentSpanId;\n if (!parentId) {\n return spanData.logger;\n }\n\n if (spanData.spans.has(parentId)) {\n return spanData.spans.get(parentId);\n }\n\n // If the parent exists but is the root span (not represented as a Braintrust\n // span because we use the logger as the root), attach to the logger so the\n // span is not orphaned. We need to check if parentSpanId exists but the\n // parent span is not in our spans map (indicating it's the root span).\n if (parentId && !spanData.spans.has(parentId)) {\n // This means the parent exists but isn't tracked as a Braintrust span,\n // which happens when the parent is the root span (we use logger as root)\n return spanData.logger;\n }\n\n this.logger.warn('Braintrust exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n /**\n * Transforms MODEL_GENERATION input to Braintrust Thread view format.\n */\n private transformInput(input: any, spanType: SpanType): any {\n if (spanType === SpanType.MODEL_GENERATION) {\n if (input && Array.isArray(input.messages)) {\n return input.messages;\n } else if (input && typeof input === 'object' && 'content' in input) {\n return [{ role: input.role, content: input.content }];\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 const { text, ...rest } = output;\n return { role: 'assistant', content: text, ...rest };\n }\n\n return output;\n }\n\n private buildSpanPayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (span.input !== undefined) {\n payload.input = this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = this.transformOutput(span.output, span.type);\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n payload.metadata = {\n spanType: span.type,\n ...span.metadata,\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 // 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, ['model', 'usage', 'parameters', 'completionStartTime']);\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 return payload;\n }\n\n async shutdown(): Promise<void> {\n if (!this.config) {\n return;\n }\n\n // End all active spans\n for (const [_traceId, spanData] of this.traceMap) {\n for (const [_spanId, span] of spanData.spans) {\n span.end();\n }\n // Loggers don't have an explicit shutdown method\n }\n this.traceMap.clear();\n await super.shutdown();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/metrics.ts","../src/tracing.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["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 * 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 { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { initLogger, currentSpan } from 'braintrust';\nimport type { Span, Logger } from 'braintrust';\nimport { formatUsageMetrics } from './metrics';\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 */\ninterface 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 */\ninterface 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\nconst MASTRA_TRACE_ID_METADATA_KEY = 'mastra-trace-id';\n\nexport interface BraintrustExporterConfig extends BaseExporterConfig {\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?: Logger<true>;\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 SpanData = {\n logger: Logger<true> | Span; // Braintrust logger (for root spans) or external span\n spans: Map<string, Span>; // Maps span.id to Braintrust span\n activeIds: Set<string>; // Tracks started (non-event) spans not yet ended, including root\n isExternal: boolean; // True if logger is an external span from logger.traced() or Eval()\n};\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.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 BaseExporter {\n name = 'braintrust';\n private traceMap = new Map<string, SpanData>();\n private config: BraintrustExporterConfig;\n\n // Flags and logger for context-aware mode\n private useProvidedLogger: boolean;\n private providedLogger?: Logger<true>;\n\n constructor(config: BraintrustExporterConfig) {\n super(config);\n\n if (config.braintrustLogger) {\n // Use provided logger - enables Braintrust context integration\n this.useProvidedLogger = true;\n this.providedLogger = config.braintrustLogger;\n this.config = config;\n } else {\n // Validate apiKey for creating loggers per trace\n if (!config.apiKey) {\n this.setDisabled(`Missing required credentials (apiKey: ${!!config.apiKey})`);\n this.config = null as any;\n this.useProvidedLogger = false;\n return;\n }\n this.useProvidedLogger = false;\n this.config = config;\n }\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.exportedSpan.isEvent) {\n await this.handleEventSpan(event.exportedSpan);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.exportedSpan);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, true);\n break;\n }\n }\n\n private async handleSpanStarted(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleSpanStarted';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n // Refcount: track active non-event spans (including root)\n if (!span.isEvent) {\n spanData.activeIds.add(span.id);\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n ...payload,\n });\n\n // Include the Mastra trace ID in the span metadata for correlation\n // Also include tags if present (only for root spans)\n braintrustSpan.log({\n metadata: {\n [MASTRA_TRACE_ID_METADATA_KEY]: span.traceId,\n },\n ...(span.isRootSpan && span.tags?.length ? { tags: span.tags } : {}),\n });\n\n spanData.spans.set(span.id, braintrustSpan);\n }\n\n private async handleSpanUpdateOrEnd(span: AnyExportedSpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustSpan = spanData.spans.get(span.id);\n if (!braintrustSpan) {\n this.logger.warn('Braintrust exporter: No Braintrust span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n return;\n }\n\n braintrustSpan.log(this.buildSpanPayload(span));\n\n if (isEnd) {\n // End the span with the correct endTime (convert milliseconds to seconds)\n if (span.endTime) {\n braintrustSpan.end({ endTime: span.endTime.getTime() / 1000 });\n } else {\n braintrustSpan.end();\n }\n\n // Refcount: mark this span as ended\n if (!span.isEvent) {\n spanData.activeIds.delete(span.id);\n }\n\n // If no more active spans remain for this trace, clean up the trace entry\n // Don't clean up if using external spans (they're managed by Braintrust)\n if (spanData.activeIds.size === 0 && !spanData.isExternal) {\n this.traceMap.delete(span.traceId);\n }\n }\n }\n\n private async handleEventSpan(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Braintrust exporter: Creating logger for event', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n\n if (this.useProvidedLogger) {\n // Use provided logger, detect external Braintrust spans\n await this.initLoggerOrUseContext(span);\n } else {\n // Create new logger per trace\n await this.initLoggerPerTrace(span);\n }\n }\n\n const method = 'handleEventSpan';\n const spanData = this.getSpanData({ span, method });\n if (!spanData) {\n return;\n }\n\n const braintrustParent = this.getBraintrustParent({ spanData, span, method });\n if (!braintrustParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span);\n\n // Create zero-duration span for event (convert milliseconds to seconds)\n const braintrustSpan = braintrustParent.startSpan({\n spanId: span.id,\n name: span.name,\n type: mapSpanType(span.type),\n startTime: span.startTime.getTime() / 1000,\n ...payload,\n });\n\n braintrustSpan.end({ endTime: span.startTime.getTime() / 1000 });\n }\n\n private initTraceMap(params: { traceId: string; isExternal: boolean; logger: Logger<true> | Span }): void {\n const { traceId, isExternal, logger } = params;\n\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId });\n return;\n }\n\n this.traceMap.set(traceId, {\n logger,\n spans: new Map(),\n activeIds: new Set(),\n isExternal,\n });\n }\n\n /**\n * Creates a new logger per trace using config credentials\n */\n private async initLoggerPerTrace(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\n try {\n const loggerInstance = 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\n this.initTraceMap({ logger: loggerInstance, isExternal: false, traceId: span.traceId });\n } catch (err) {\n this.logger.error('Braintrust exporter: Failed to initialize logger', { error: err, traceId: span.traceId });\n this.setDisabled('Failed to initialize Braintrust logger');\n }\n }\n\n /**\n * Uses provided logger and detects external Braintrust spans.\n * If a Braintrust span is detected (from logger.traced() or Eval()), attaches to it.\n * Otherwise, uses the provided logger instance.\n */\n private async initLoggerOrUseContext(span: AnyExportedSpan): Promise<void> {\n // Check if trace already exists - reuse existing trace data\n if (this.traceMap.has(span.traceId)) {\n this.logger.debug('Braintrust exporter: Reusing existing trace from local map', { traceId: span.traceId });\n return;\n }\n\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 const braintrustSpan = currentSpan();\n\n // Check if it's a valid span (not the NOOP_SPAN)\n if (braintrustSpan && braintrustSpan.id) {\n // External span detected - attach Mastra traces to it\n this.initTraceMap({ logger: braintrustSpan, isExternal: true, traceId: span.traceId });\n } else {\n // No external span - use provided logger\n this.initTraceMap({ logger: this.providedLogger!, isExternal: false, traceId: span.traceId });\n }\n }\n\n private getSpanData(options: { span: AnyExportedSpan; method: string }): SpanData | undefined {\n const { span, method } = options;\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n\n this.logger.warn('Braintrust exporter: No span data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private getBraintrustParent(options: {\n spanData: SpanData;\n span: AnyExportedSpan;\n method: string;\n }): Logger<true> | Span | undefined {\n const { spanData, span, method } = options;\n\n const parentId = span.parentSpanId;\n if (!parentId) {\n return spanData.logger;\n }\n\n if (spanData.spans.has(parentId)) {\n return spanData.spans.get(parentId);\n }\n\n // If the parent exists but is the root span (not represented as a Braintrust\n // span because we use the logger as the root), attach to the logger so the\n // span is not orphaned. We need to check if parentSpanId exists but the\n // parent span is not in our spans map (indicating it's the root span).\n if (parentId && !spanData.spans.has(parentId)) {\n // This means the parent exists but isn't tracked as a Braintrust span,\n // which happens when the parent is the root span (we use logger as root)\n return spanData.logger;\n }\n\n this.logger.warn('Braintrust exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\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 */\n private 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: any) => this.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: any) => part?.type !== 'tool-call')\n .map((part: any) => this.convertContentPart(part))\n .filter(Boolean);\n\n const toolCallParts = content.filter((part: any) => part?.type === 'tool-call');\n\n const result: any = {\n role,\n content: contentParts.length > 0 ? contentParts.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: any) => {\n const toolCallId = tc.toolCallId;\n const toolName = tc.toolName;\n // Support both v4 'args' and v5 'input'\n const args = tc.args ?? tc.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',\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 = this.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\n /**\n * Converts a content part to a string representation.\n * Handles text, image, file, reasoning, and other content types.\n */\n private 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 */\n private serializeToolResult(resultData: any): string {\n if (typeof resultData === 'string') {\n return resultData;\n }\n if (resultData && typeof resultData === 'object' && 'value' in resultData) {\n return typeof resultData.value === 'string' ? resultData.value : JSON.stringify(resultData.value);\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 * 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: any, spanType: SpanType): any {\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: AISDKMessage) => this.convertAISDKMessage(msg));\n }\n\n // If input has a messages array\n if (input && Array.isArray(input.messages)) {\n return input.messages.map((msg: AISDKMessage) => this.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 const { text, ...rest } = output;\n return { role: 'assistant', content: text, ...rest };\n }\n\n return output;\n }\n\n private buildSpanPayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (span.input !== undefined) {\n payload.input = this.transformInput(span.input, span.type);\n }\n\n if (span.output !== undefined) {\n payload.output = this.transformOutput(span.output, span.type);\n }\n\n // Initialize metrics and metadata objects\n payload.metrics = {};\n payload.metadata = {\n spanType: span.type,\n ...span.metadata,\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 // 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, ['model', 'usage', 'parameters', 'completionStartTime']);\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 return payload;\n }\n\n async shutdown(): Promise<void> {\n if (!this.config) {\n return;\n }\n\n // End all active spans\n for (const [_traceId, spanData] of this.traceMap) {\n for (const [_spanId, span] of spanData.spans) {\n span.end();\n }\n // Loggers don't have an explicit shutdown method\n }\n this.traceMap.clear();\n await super.shutdown();\n }\n}\n"]}
|
package/dist/tracing.d.ts
CHANGED
|
@@ -51,8 +51,37 @@ export declare class BraintrustExporter extends BaseExporter {
|
|
|
51
51
|
private initLoggerOrUseContext;
|
|
52
52
|
private getSpanData;
|
|
53
53
|
private getBraintrustParent;
|
|
54
|
+
/**
|
|
55
|
+
* Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.
|
|
56
|
+
*
|
|
57
|
+
* Supports both AI SDK v4 and v5 formats:
|
|
58
|
+
* - v4 uses 'args' for tool calls and 'result' for tool results
|
|
59
|
+
* - v5 uses 'input' for tool calls and 'output' for tool results
|
|
60
|
+
*
|
|
61
|
+
* AI SDK format:
|
|
62
|
+
* { role: "user", content: [{ type: "text", text: "hello" }] }
|
|
63
|
+
* { role: "assistant", content: [{ type: "text", text: "..." }, { type: "tool-call", toolCallId: "...", toolName: "...", args: {...} }] }
|
|
64
|
+
* { role: "tool", content: [{ type: "tool-result", toolCallId: "...", result: {...} }] }
|
|
65
|
+
*
|
|
66
|
+
* OpenAI format (what Braintrust expects):
|
|
67
|
+
* { role: "user", content: "hello" }
|
|
68
|
+
* { role: "assistant", content: "...", tool_calls: [{ id: "...", type: "function", function: { name: "...", arguments: "..." } }] }
|
|
69
|
+
* { role: "tool", content: "result", tool_call_id: "..." }
|
|
70
|
+
*/
|
|
71
|
+
private convertAISDKMessage;
|
|
72
|
+
/**
|
|
73
|
+
* Converts a content part to a string representation.
|
|
74
|
+
* Handles text, image, file, reasoning, and other content types.
|
|
75
|
+
*/
|
|
76
|
+
private convertContentPart;
|
|
77
|
+
/**
|
|
78
|
+
* Serializes tool result data to a string for OpenAI format.
|
|
79
|
+
*/
|
|
80
|
+
private serializeToolResult;
|
|
54
81
|
/**
|
|
55
82
|
* Transforms MODEL_GENERATION input to Braintrust Thread view format.
|
|
83
|
+
* Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires
|
|
84
|
+
* for proper rendering of threads (fixes #11023).
|
|
56
85
|
*/
|
|
57
86
|
private transformInput;
|
|
58
87
|
/**
|
package/dist/tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA8C,MAAM,4BAA4B,CAAC;AAG3G,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAQ,MAAM,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAA8C,MAAM,4BAA4B,CAAC;AAG3G,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAQ,MAAM,EAAE,MAAM,YAAY,CAAC;AA4F/C,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAEhC,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACxC;AA0BD,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,IAAI,SAAgB;IACpB,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAA2B;IAGzC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,cAAc,CAAC,CAAe;gBAE1B,MAAM,EAAE,wBAAwB;cAqB5B,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;YAmBzD,iBAAiB;YAgDjB,qBAAqB;YA6CrB,eAAe;IA2C7B,OAAO,CAAC,YAAY;IAiBpB;;OAEG;YACW,kBAAkB;IAsBhC;;;;OAIG;YACW,sBAAsB;IAsBpC,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,mBAAmB;IAqC3B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,mBAAmB;IAgG3B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAsD1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAgBtB;;OAEG;IACH,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,gBAAgB;IA4ElB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAehC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/braintrust",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.11",
|
|
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": "^1.1.0",
|
|
28
|
-
"@mastra/observability": "1.0.0-beta.
|
|
28
|
+
"@mastra/observability": "1.0.0-beta.10"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "22.13.17",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"@vitest/ui": "4.0.12",
|
|
34
34
|
"eslint": "^9.37.0",
|
|
35
35
|
"tsup": "^8.5.0",
|
|
36
|
-
"typescript": "^5.
|
|
37
|
-
"vitest": "4.0.
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "4.0.16",
|
|
38
38
|
"@internal/lint": "0.0.53",
|
|
39
39
|
"@internal/types-builder": "0.0.28",
|
|
40
|
-
"@mastra/core": "1.0.0-beta.
|
|
40
|
+
"@mastra/core": "1.0.0-beta.20"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@mastra/core": ">=1.0.0-0 <2.0.0-0"
|