@mastra/observability 1.0.0-beta.2 → 1.0.0-beta.4

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 CHANGED
@@ -1,5 +1,45 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.0.0-beta.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed CachedToken tracking in all Observability Exporters. Also fixed TimeToFirstToken in Langfuse, Braintrust, PostHog exporters. Fixed trace formatting in Posthog Exporter. ([#11029](https://github.com/mastra-ai/mastra/pull/11029))
8
+
9
+ - Updated dependencies [[`edb07e4`](https://github.com/mastra-ai/mastra/commit/edb07e49283e0c28bd094a60e03439bf6ecf0221), [`b7e17d3`](https://github.com/mastra-ai/mastra/commit/b7e17d3f5390bb5a71efc112204413656fcdc18d), [`261473a`](https://github.com/mastra-ai/mastra/commit/261473ac637e633064a22076671e2e02b002214d), [`5d7000f`](https://github.com/mastra-ai/mastra/commit/5d7000f757cd65ea9dc5b05e662fd83dfd44e932), [`4f0331a`](https://github.com/mastra-ai/mastra/commit/4f0331a79bf6eb5ee598a5086e55de4b5a0ada03), [`8a000da`](https://github.com/mastra-ai/mastra/commit/8a000da0c09c679a2312f6b3aa05b2ca78ca7393)]:
10
+ - @mastra/core@1.0.0-beta.10
11
+
12
+ ## 1.0.0-beta.3
13
+
14
+ ### Patch Changes
15
+
16
+ - Add time-to-first-token (TTFT) support for Braintrust integration ([#10840](https://github.com/mastra-ai/mastra/pull/10840))
17
+
18
+ Adds `time_to_first_token` metric to Braintrust spans, populated from the `completionStartTime` attribute captured when the first streaming chunk arrives.
19
+
20
+ ```typescript
21
+ // time_to_first_token is now automatically sent to Braintrust
22
+ // as part of span metrics during streaming
23
+ const result = await agent.stream('Hello');
24
+ ```
25
+
26
+ - Add time-to-first-token (TTFT) support for Langfuse integration ([#10781](https://github.com/mastra-ai/mastra/pull/10781))
27
+
28
+ Adds `completionStartTime` to model generation spans, which Langfuse uses to calculate TTFT metrics. The timestamp is automatically captured when the first content chunk arrives during streaming.
29
+
30
+ ```typescript
31
+ // completionStartTime is now automatically captured and sent to Langfuse
32
+ // enabling TTFT metrics in your Langfuse dashboard
33
+ const result = await agent.stream('Hello');
34
+ ```
35
+
36
+ - Consolidated tool-output chunks from nested agents into single tool-result spans ([#10836](https://github.com/mastra-ai/mastra/pull/10836))
37
+
38
+ - link langfuse prompts and helper functions ([#10738](https://github.com/mastra-ai/mastra/pull/10738))
39
+
40
+ - Updated dependencies [[`3076c67`](https://github.com/mastra-ai/mastra/commit/3076c6778b18988ae7d5c4c5c466366974b2d63f), [`85d7ee1`](https://github.com/mastra-ai/mastra/commit/85d7ee18ff4e14d625a8a30ec6656bb49804989b), [`c6c1092`](https://github.com/mastra-ai/mastra/commit/c6c1092f8fbf76109303f69e000e96fd1960c4ce), [`81dc110`](https://github.com/mastra-ai/mastra/commit/81dc11008d147cf5bdc8996ead1aa61dbdebb6fc), [`7aedb74`](https://github.com/mastra-ai/mastra/commit/7aedb74883adf66af38e270e4068fd42e7a37036), [`8f02d80`](https://github.com/mastra-ai/mastra/commit/8f02d800777397e4b45d7f1ad041988a8b0c6630), [`d7aad50`](https://github.com/mastra-ai/mastra/commit/d7aad501ce61646b76b4b511e558ac4eea9884d0), [`ce0a73a`](https://github.com/mastra-ai/mastra/commit/ce0a73abeaa75b10ca38f9e40a255a645d50ebfb), [`a02e542`](https://github.com/mastra-ai/mastra/commit/a02e542d23179bad250b044b17ff023caa61739f), [`a372c64`](https://github.com/mastra-ai/mastra/commit/a372c640ad1fd12e8f0613cebdc682fc156b4d95), [`8846867`](https://github.com/mastra-ai/mastra/commit/8846867ffa9a3746767618e314bebac08eb77d87), [`42a42cf`](https://github.com/mastra-ai/mastra/commit/42a42cf3132b9786feecbb8c13c583dce5b0e198), [`ae08bf0`](https://github.com/mastra-ai/mastra/commit/ae08bf0ebc6a4e4da992b711c4a389c32ba84cf4), [`21735a7`](https://github.com/mastra-ai/mastra/commit/21735a7ef306963554a69a89b44f06c3bcd85141), [`1d877b8`](https://github.com/mastra-ai/mastra/commit/1d877b8d7b536a251c1a7a18db7ddcf4f68d6f8b)]:
41
+ - @mastra/core@1.0.0-beta.7
42
+
3
43
  ## 1.0.0-beta.2
4
44
 
5
45
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -948,6 +948,58 @@ var TestExporter = class extends BaseExporter {
948
948
  this.logger.info("TestExporter shutdown");
949
949
  }
950
950
  };
951
+
952
+ // src/usage.ts
953
+ function extractUsageMetrics(usage, providerMetadata) {
954
+ if (!usage) {
955
+ return {};
956
+ }
957
+ const inputDetails = {};
958
+ const outputDetails = {};
959
+ let inputTokens = usage.inputTokens;
960
+ const outputTokens = usage.outputTokens;
961
+ if (usage.cachedInputTokens) {
962
+ inputDetails.cacheRead = usage.cachedInputTokens;
963
+ }
964
+ if (usage.reasoningTokens) {
965
+ outputDetails.reasoning = usage.reasoningTokens;
966
+ }
967
+ const anthropic = providerMetadata?.anthropic;
968
+ if (anthropic) {
969
+ if (anthropic.cacheReadInputTokens) {
970
+ inputDetails.cacheRead = anthropic.cacheReadInputTokens;
971
+ }
972
+ if (anthropic.cacheCreationInputTokens) {
973
+ inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
974
+ }
975
+ if (anthropic.cacheReadInputTokens || anthropic.cacheCreationInputTokens) {
976
+ inputDetails.text = usage.inputTokens;
977
+ inputTokens = (usage.inputTokens ?? 0) + (anthropic.cacheReadInputTokens ?? 0) + (anthropic.cacheCreationInputTokens ?? 0);
978
+ }
979
+ }
980
+ const google = providerMetadata?.google;
981
+ if (google?.usageMetadata) {
982
+ if (google.usageMetadata.cachedContentTokenCount) {
983
+ inputDetails.cacheRead = google.usageMetadata.cachedContentTokenCount;
984
+ }
985
+ if (google.usageMetadata.thoughtsTokenCount) {
986
+ outputDetails.reasoning = google.usageMetadata.thoughtsTokenCount;
987
+ }
988
+ }
989
+ const result = {
990
+ inputTokens,
991
+ outputTokens
992
+ };
993
+ if (Object.keys(inputDetails).length > 0) {
994
+ result.inputDetails = inputDetails;
995
+ }
996
+ if (Object.keys(outputDetails).length > 0) {
997
+ result.outputDetails = outputDetails;
998
+ }
999
+ return result;
1000
+ }
1001
+
1002
+ // src/model-tracing.ts
951
1003
  var ModelSpanTracker = class {
952
1004
  #modelSpan;
953
1005
  #currentStepSpan;
@@ -955,9 +1007,23 @@ var ModelSpanTracker = class {
955
1007
  #accumulator = {};
956
1008
  #stepIndex = 0;
957
1009
  #chunkSequence = 0;
1010
+ #completionStartTime;
1011
+ /** Tracks tool output accumulators by toolCallId for consolidating sub-agent streams */
1012
+ #toolOutputAccumulators = /* @__PURE__ */ new Map();
1013
+ /** Tracks toolCallIds that had streaming output (to skip redundant tool-result spans) */
1014
+ #streamedToolCallIds = /* @__PURE__ */ new Set();
958
1015
  constructor(modelSpan) {
959
1016
  this.#modelSpan = modelSpan;
960
1017
  }
1018
+ /**
1019
+ * Capture the completion start time (time to first token) when the first content chunk arrives.
1020
+ */
1021
+ #captureCompletionStartTime() {
1022
+ if (this.#completionStartTime) {
1023
+ return;
1024
+ }
1025
+ this.#completionStartTime = /* @__PURE__ */ new Date();
1026
+ }
961
1027
  /**
962
1028
  * Get the tracing context for creating child spans.
963
1029
  * Returns the current step span if active, otherwise the model span.
@@ -974,10 +1040,16 @@ var ModelSpanTracker = class {
974
1040
  this.#modelSpan?.error(options);
975
1041
  }
976
1042
  /**
977
- * End the generation span
1043
+ * End the generation span with optional raw usage data.
1044
+ * If usage is provided, it will be converted to UsageStats with cache token details.
978
1045
  */
979
1046
  endGeneration(options) {
980
- this.#modelSpan?.end(options);
1047
+ const { usage, providerMetadata, ...spanOptions } = options ?? {};
1048
+ if (spanOptions.attributes) {
1049
+ spanOptions.attributes.completionStartTime = this.#completionStartTime;
1050
+ spanOptions.attributes.usage = extractUsageMetrics(usage, providerMetadata);
1051
+ }
1052
+ this.#modelSpan?.end(spanOptions);
981
1053
  }
982
1054
  /**
983
1055
  * Update the generation span
@@ -1007,9 +1079,10 @@ var ModelSpanTracker = class {
1007
1079
  #endStepSpan(payload) {
1008
1080
  if (!this.#currentStepSpan) return;
1009
1081
  const output = payload.output;
1010
- const { usage, ...otherOutput } = output;
1082
+ const { usage: rawUsage, ...otherOutput } = output;
1011
1083
  const stepResult = payload.stepResult;
1012
1084
  const metadata = payload.metadata;
1085
+ const usage = extractUsageMetrics(rawUsage, metadata?.providerMetadata);
1013
1086
  const cleanMetadata = metadata ? { ...metadata } : void 0;
1014
1087
  if (cleanMetadata?.request) {
1015
1088
  delete cleanMetadata.request;
@@ -1182,6 +1255,77 @@ var ModelSpanTracker = class {
1182
1255
  break;
1183
1256
  }
1184
1257
  }
1258
+ /**
1259
+ * Handle tool-output chunks from sub-agents.
1260
+ * Consolidates streaming text/reasoning deltas into a single span per tool call.
1261
+ */
1262
+ #handleToolOutputChunk(chunk) {
1263
+ if (chunk.type !== "tool-output") return;
1264
+ const payload = chunk.payload;
1265
+ const { output, toolCallId, toolName } = payload;
1266
+ let acc = this.#toolOutputAccumulators.get(toolCallId);
1267
+ if (!acc) {
1268
+ if (!this.#currentStepSpan) {
1269
+ this.#startStepSpan();
1270
+ }
1271
+ acc = {
1272
+ toolName: toolName || "unknown",
1273
+ toolCallId,
1274
+ text: "",
1275
+ reasoning: "",
1276
+ sequenceNumber: this.#chunkSequence++,
1277
+ // Name the span 'tool-result' for consistency (tool-call → tool-result)
1278
+ span: this.#currentStepSpan?.createChildSpan({
1279
+ name: `chunk: 'tool-result'`,
1280
+ type: observability.SpanType.MODEL_CHUNK,
1281
+ attributes: {
1282
+ chunkType: "tool-result",
1283
+ sequenceNumber: this.#chunkSequence - 1
1284
+ }
1285
+ })
1286
+ };
1287
+ this.#toolOutputAccumulators.set(toolCallId, acc);
1288
+ }
1289
+ if (output && typeof output === "object" && "type" in output) {
1290
+ const innerType = output.type;
1291
+ switch (innerType) {
1292
+ case "text-delta":
1293
+ if (output.payload?.text) {
1294
+ acc.text += output.payload.text;
1295
+ }
1296
+ break;
1297
+ case "reasoning-delta":
1298
+ if (output.payload?.text) {
1299
+ acc.reasoning += output.payload.text;
1300
+ }
1301
+ break;
1302
+ case "finish":
1303
+ case "workflow-finish":
1304
+ this.#endToolOutputSpan(toolCallId);
1305
+ break;
1306
+ }
1307
+ }
1308
+ }
1309
+ /**
1310
+ * End a tool output span and clean up the accumulator
1311
+ */
1312
+ #endToolOutputSpan(toolCallId) {
1313
+ const acc = this.#toolOutputAccumulators.get(toolCallId);
1314
+ if (!acc) return;
1315
+ const output = {
1316
+ toolCallId: acc.toolCallId,
1317
+ toolName: acc.toolName
1318
+ };
1319
+ if (acc.text) {
1320
+ output.text = acc.text;
1321
+ }
1322
+ if (acc.reasoning) {
1323
+ output.reasoning = acc.reasoning;
1324
+ }
1325
+ acc.span?.end({ output });
1326
+ this.#toolOutputAccumulators.delete(toolCallId);
1327
+ this.#streamedToolCallIds.add(toolCallId);
1328
+ }
1185
1329
  /**
1186
1330
  * Wraps a stream with model tracing transform to track MODEL_STEP and MODEL_CHUNK spans.
1187
1331
  *
@@ -1192,6 +1336,13 @@ var ModelSpanTracker = class {
1192
1336
  return stream.pipeThrough(
1193
1337
  new web.TransformStream({
1194
1338
  transform: (chunk, controller) => {
1339
+ switch (chunk.type) {
1340
+ case "text-delta":
1341
+ case "tool-call-delta":
1342
+ case "reasoning-delta":
1343
+ this.#captureCompletionStartTime();
1344
+ break;
1345
+ }
1195
1346
  controller.enqueue(chunk);
1196
1347
  switch (chunk.type) {
1197
1348
  case "text-start":
@@ -1225,6 +1376,19 @@ var ModelSpanTracker = class {
1225
1376
  case "start":
1226
1377
  case "finish":
1227
1378
  break;
1379
+ case "tool-output":
1380
+ this.#handleToolOutputChunk(chunk);
1381
+ break;
1382
+ case "tool-result": {
1383
+ const toolCallId = chunk.payload?.toolCallId;
1384
+ if (toolCallId && this.#streamedToolCallIds.has(toolCallId)) {
1385
+ this.#streamedToolCallIds.delete(toolCallId);
1386
+ break;
1387
+ }
1388
+ const { args, ...cleanPayload } = chunk.payload || {};
1389
+ this.#createEventSpan(chunk.type, cleanPayload);
1390
+ break;
1391
+ }
1228
1392
  // Default: auto-create event span for all other chunk types
1229
1393
  default: {
1230
1394
  let outputPayload = chunk.payload;
@@ -1436,6 +1600,9 @@ function deepClean(value, options = {}, _seen = /* @__PURE__ */ new WeakSet(), _
1436
1600
  return "[Circular]";
1437
1601
  }
1438
1602
  _seen.add(value);
1603
+ if (value instanceof Date) {
1604
+ return value;
1605
+ }
1439
1606
  if (Array.isArray(value)) {
1440
1607
  return value.map((item) => deepClean(item, options, _seen, _depth + 1));
1441
1608
  }
@@ -2324,6 +2491,11 @@ var Observability = class extends base.MastraBase {
2324
2491
  }
2325
2492
  };
2326
2493
 
2494
+ // src/tracing-options.ts
2495
+ function buildTracingOptions(...updaters) {
2496
+ return updaters.reduce((opts, updater) => updater(opts), {});
2497
+ }
2498
+
2327
2499
  exports.BaseExporter = BaseExporter;
2328
2500
  exports.BaseObservabilityInstance = BaseObservabilityInstance;
2329
2501
  exports.BaseSpan = BaseSpan;
@@ -2338,6 +2510,7 @@ exports.Observability = Observability;
2338
2510
  exports.SamplingStrategyType = SamplingStrategyType;
2339
2511
  exports.SensitiveDataFilter = SensitiveDataFilter;
2340
2512
  exports.TestExporter = TestExporter;
2513
+ exports.buildTracingOptions = buildTracingOptions;
2341
2514
  exports.deepClean = deepClean;
2342
2515
  exports.getExternalParentId = getExternalParentId;
2343
2516
  exports.observabilityConfigValueSchema = observabilityConfigValueSchema;