@mastra/observability 1.9.1-alpha.0 → 1.9.1-alpha.2

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,36 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.9.1-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Cost estimates now use the latest model pricing rates for more accurate calculations ([#15362](https://github.com/mastra-ai/mastra/pull/15362))
8
+
9
+ - Reduced observability overhead for `MODEL_STEP` spans by storing a lightweight message preview of request bodies. ([#15249](https://github.com/mastra-ai/mastra/pull/15249))
10
+
11
+ This keeps span previews readable and avoids pulling large payloads into exporter input.
12
+
13
+ - Fixed cost lookup for models with date suffixes. Providers like OpenAI often return model names with date suffixes (e.g., `gpt-5.4-mini-2026-03-17`) that don't exactly match pricing data entries. The lookup now tries multiple variants including stripping date suffixes and converting dots to dashes. ([#15349](https://github.com/mastra-ai/mastra/pull/15349))
14
+
15
+ - Added `entityVersionId`, `parentEntityVersionId`, and `rootEntityVersionId` to span correlation context, enabling version information to propagate to scores, metrics, logs, and feedback emitted during traced execution. ([#15317](https://github.com/mastra-ai/mastra/pull/15317))
16
+
17
+ - Fixed stack traces for errors reported to Sentry. Exceptions now point to the code that threw the error instead of `SentryExporter.handleSpanEnded` inside the exporter, so issues in Sentry are actually debuggable. ([#15343](https://github.com/mastra-ai/mastra/pull/15343))
18
+
19
+ This was caused by two issues, both fixed:
20
+ - `@mastra/sentry` passed the error message as a string to `Sentry.captureException`, which made Sentry synthesize a stack trace from the exporter's call site. It now passes an `Error` instance with the captured stack attached.
21
+ - `@mastra/observability` stored the wrapping `MastraError`'s stack on the span, hiding the original error's location. When the `MastraError` has a cause, the cause's stack is now preserved.
22
+
23
+ Fixes [#15337](https://github.com/mastra-ai/mastra/issues/15337).
24
+
25
+ - Updated dependencies [[`cbdf3e1`](https://github.com/mastra-ai/mastra/commit/cbdf3e12b3d0c30a6e5347be658e2009648c130a), [`8fe46d3`](https://github.com/mastra-ai/mastra/commit/8fe46d354027f3f0f0846e64219772348de106dd), [`18c67db`](https://github.com/mastra-ai/mastra/commit/18c67dbb9c9ebc26f26f65f7d3ff836e5691ef46), [`8dcc77e`](https://github.com/mastra-ai/mastra/commit/8dcc77e78a5340f5848f74b9e9f1b3da3513c1f5), [`aa67fc5`](https://github.com/mastra-ai/mastra/commit/aa67fc59ee8a5eeff1f23eb05970b8d7a536c8ff), [`fa8140b`](https://github.com/mastra-ai/mastra/commit/fa8140bcd4251d2e3ac85fdc5547dfc4f372b5be), [`190f452`](https://github.com/mastra-ai/mastra/commit/190f45258b0640e2adfc8219fa3258cdc5b8f071), [`7e7bf60`](https://github.com/mastra-ai/mastra/commit/7e7bf606886bf374a6f9d4ca9b09dd83d0533372), [`184907d`](https://github.com/mastra-ai/mastra/commit/184907d775d8609c03c26e78ccaf37315f3aa287), [`0c4cd13`](https://github.com/mastra-ai/mastra/commit/0c4cd131931c04ac5405373c932a242dbe88edd6), [`b16a753`](https://github.com/mastra-ai/mastra/commit/b16a753d5748440248d7df82e29bb987a9c8386c)]:
26
+ - @mastra/core@1.25.0-alpha.3
27
+
28
+ ## 1.9.1-alpha.1
29
+
30
+ ### Patch Changes
31
+
32
+ - Fixed double-counting of Anthropic cache tokens in usage metrics ([#15316](https://github.com/mastra-ai/mastra/pull/15316))
33
+
3
34
  ## 1.9.1-alpha.0
4
35
 
5
36
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -18093,6 +18093,7 @@ var PricingRegistry = class _PricingRegistry {
18093
18093
  constructor(pricingModels) {
18094
18094
  this.pricingModels = pricingModels;
18095
18095
  }
18096
+ pricingModels;
18096
18097
  static globalRegistry = null;
18097
18098
  static fromText(pricingModelText) {
18098
18099
  return new _PricingRegistry(parsePricingModelText(pricingModelText));
@@ -18109,12 +18110,11 @@ var PricingRegistry = class _PricingRegistry {
18109
18110
  return _PricingRegistry.globalRegistry;
18110
18111
  }
18111
18112
  get(args) {
18112
- const key = makePricingKey(args);
18113
- const exact = this.pricingModels.get(key);
18114
- if (exact) return exact;
18115
- const dashedKey = makePricingKey({ provider: args.provider, model: args.model.replace(/\./g, "-") });
18116
- if (dashedKey !== key) {
18117
- return this.pricingModels.get(dashedKey) ?? null;
18113
+ const variants = getModelVariants(args.model);
18114
+ for (const variant of variants) {
18115
+ const key = makePricingKey({ provider: args.provider, model: variant });
18116
+ const match = this.pricingModels.get(key);
18117
+ if (match) return match;
18118
18118
  }
18119
18119
  return null;
18120
18120
  }
@@ -18204,6 +18204,18 @@ function normalizeProvider(provider) {
18204
18204
  const dotIndex = normalized.indexOf(".");
18205
18205
  return dotIndex !== -1 ? normalized.substring(0, dotIndex) : normalized;
18206
18206
  }
18207
+ function getModelVariants(model) {
18208
+ const dashed = model.replace(/\./g, "-");
18209
+ return [model, dashed, stripDateSuffix(model), stripDateSuffix(dashed)];
18210
+ }
18211
+ function stripDateSuffix(model) {
18212
+ let stripped = model.replace(/-20\d{2}-\d{2}-\d{2}$/, "");
18213
+ if (stripped !== model) return stripped;
18214
+ stripped = model.replace(/-20\d{6}(-[a-z]+)?$/, "$1");
18215
+ if (stripped !== model) return stripped;
18216
+ stripped = model.replace(/-\d{2}-20\d{2}$/, "");
18217
+ return stripped;
18218
+ }
18207
18219
 
18208
18220
  // src/metrics/types.ts
18209
18221
  var PricingMeter = {
@@ -18594,7 +18606,8 @@ function extractUsageMetrics(usage, providerMetadata) {
18594
18606
  if (!isDefined(inputDetails.cacheWrite) && isDefined(anthropic.cacheCreationInputTokens)) {
18595
18607
  inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
18596
18608
  }
18597
- if (!hasV3CachedTotals && (isDefined(inputDetails.cacheRead) || isDefined(inputDetails.cacheWrite))) {
18609
+ const inputAlreadyIncludesCache = hasV3CachedTotals || isDefined(usage.cachedInputTokens) && usage.cachedInputTokens > 0;
18610
+ if (!inputAlreadyIncludesCache && (isDefined(inputDetails.cacheRead) || isDefined(inputDetails.cacheWrite))) {
18598
18611
  inputTokens = (usage.inputTokens ?? 0) + (inputDetails.cacheRead ?? 0) + (inputDetails.cacheWrite ?? 0);
18599
18612
  }
18600
18613
  }
@@ -18633,15 +18646,150 @@ function sumDefinedValues(obj, keys) {
18633
18646
  }
18634
18647
 
18635
18648
  // src/model-tracing.ts
18649
+ function formatPreviewLabel(label, fallback) {
18650
+ return typeof label === "string" && label.length > 0 ? label : fallback;
18651
+ }
18652
+ function summarizePart(part) {
18653
+ if (typeof part === "string") {
18654
+ return part;
18655
+ }
18656
+ if (!part || typeof part !== "object") {
18657
+ return "";
18658
+ }
18659
+ if ("text" in part && typeof part.text === "string") {
18660
+ return part.text;
18661
+ }
18662
+ if ("parts" in part && Array.isArray(part.parts)) {
18663
+ return part.parts.map(summarizePart).filter(Boolean).join("");
18664
+ }
18665
+ if ("inlineData" in part && part.inlineData && typeof part.inlineData === "object") {
18666
+ return `[${formatPreviewLabel(part.inlineData.mimeType, "binary")}]`;
18667
+ }
18668
+ if ("image_url" in part) {
18669
+ return "[image]";
18670
+ }
18671
+ if ("functionCall" in part && part.functionCall && typeof part.functionCall === "object") {
18672
+ return `[tool: ${formatPreviewLabel(part.functionCall.name, "unknown")}]`;
18673
+ }
18674
+ if ("function_call" in part && part.function_call && typeof part.function_call === "object") {
18675
+ return `[tool: ${formatPreviewLabel(part.function_call.name, "unknown")}]`;
18676
+ }
18677
+ if ("function" in part && part.function && typeof part.function === "object") {
18678
+ return `[tool: ${formatPreviewLabel(part.function.name, "unknown")}]`;
18679
+ }
18680
+ if ("toolName" in part) {
18681
+ return `[tool: ${formatPreviewLabel(part.toolName, "unknown")}]`;
18682
+ }
18683
+ if ("type" in part && typeof part.type === "string") {
18684
+ switch (part.type) {
18685
+ case "image":
18686
+ return "[image]";
18687
+ case "file":
18688
+ return "[file]";
18689
+ case "reasoning":
18690
+ return "[reasoning]";
18691
+ case "tool-call":
18692
+ return `[tool: ${formatPreviewLabel(part.toolName, "unknown")}]`;
18693
+ case "tool-result":
18694
+ return "[tool-result]";
18695
+ default:
18696
+ return `[${part.type}]`;
18697
+ }
18698
+ }
18699
+ if ("content" in part && typeof part.content === "string") {
18700
+ return part.content;
18701
+ }
18702
+ return "[object]";
18703
+ }
18704
+ function summarizeMessageContent(content) {
18705
+ if (typeof content === "string") {
18706
+ return content;
18707
+ }
18708
+ if (Array.isArray(content)) {
18709
+ return content.map(summarizePart).filter(Boolean).join("");
18710
+ }
18711
+ if (content && typeof content === "object") {
18712
+ if ("parts" in content && Array.isArray(content.parts)) {
18713
+ return content.parts.map(summarizePart).filter(Boolean).join("");
18714
+ }
18715
+ return summarizePart(content);
18716
+ }
18717
+ if (content == null) {
18718
+ return "";
18719
+ }
18720
+ return String(content);
18721
+ }
18722
+ function appendToolPreview(preview, toolCalls) {
18723
+ if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
18724
+ return preview;
18725
+ }
18726
+ const toolPreview = toolCalls.map((toolCall) => summarizePart(toolCall)).filter(Boolean).join(" ");
18727
+ if (!toolPreview) {
18728
+ return preview;
18729
+ }
18730
+ return preview ? `${preview} ${toolPreview}` : toolPreview;
18731
+ }
18732
+ function appendPreview(preview, addition) {
18733
+ if (!addition) {
18734
+ return preview;
18735
+ }
18736
+ return preview ? `${preview} ${addition}` : addition;
18737
+ }
18738
+ function normalizeMessages(messages) {
18739
+ return messages.map((message) => {
18740
+ if (!message || typeof message !== "object") {
18741
+ return { role: "user", content: summarizeMessageContent(message) };
18742
+ }
18743
+ const role = typeof message.role === "string" ? message.role : "user";
18744
+ const baseContent = summarizeMessageContent(message.content);
18745
+ const contentWithToolArrays = appendToolPreview(
18746
+ appendToolPreview(baseContent, message.toolCalls),
18747
+ message.tool_calls
18748
+ );
18749
+ const functionCall = message.functionCall;
18750
+ const functionCallPreview = functionCall === void 0 ? "" : summarizePart({ functionCall });
18751
+ const functionCallSnakeCase = message.function_call;
18752
+ const functionCallSnakeCasePreview = functionCallSnakeCase === void 0 ? "" : summarizePart({ function_call: functionCallSnakeCase });
18753
+ const contentWithFunctionCall = appendPreview(
18754
+ appendPreview(contentWithToolArrays, functionCallPreview),
18755
+ functionCallSnakeCasePreview
18756
+ );
18757
+ return { role, content: contentWithFunctionCall };
18758
+ });
18759
+ }
18760
+ function summarizeRequestBody(body) {
18761
+ if (body == null) {
18762
+ return void 0;
18763
+ }
18764
+ if (typeof body !== "object") {
18765
+ return typeof body === "string" ? body : String(body);
18766
+ }
18767
+ if (Array.isArray(body.messages)) {
18768
+ return normalizeMessages(body.messages);
18769
+ }
18770
+ if (Array.isArray(body.contents)) {
18771
+ return body.contents.map((item) => ({
18772
+ role: typeof item?.role === "string" ? item.role : "user",
18773
+ content: Array.isArray(item?.parts) ? item.parts.map(summarizePart).filter(Boolean).join("") : ""
18774
+ }));
18775
+ }
18776
+ const summary = {};
18777
+ if (typeof body.model === "string") {
18778
+ summary.model = body.model;
18779
+ }
18780
+ const bodyKeys = Object.keys(body).filter((key) => key !== "body");
18781
+ if (bodyKeys.length > 0) {
18782
+ summary.keys = bodyKeys;
18783
+ }
18784
+ return Object.keys(summary).length > 0 ? summary : "[request body]";
18785
+ }
18636
18786
  function extractStepInput(request) {
18637
18787
  if (!request) return void 0;
18638
18788
  const { body } = request;
18639
18789
  if (body == null) return request;
18640
18790
  try {
18641
18791
  const parsed = typeof body === "string" ? JSON.parse(body) : body;
18642
- if (Array.isArray(parsed?.messages)) return parsed.messages;
18643
- if (Array.isArray(parsed?.contents)) return parsed.contents;
18644
- return parsed;
18792
+ return summarizeRequestBody(parsed);
18645
18793
  } catch {
18646
18794
  return request;
18647
18795
  }
@@ -19228,6 +19376,10 @@ var BaseSpan = class {
19228
19376
  }
19229
19377
  const metadata = this.metadata ?? {};
19230
19378
  const getMetadataString = (key) => typeof metadata[key] === "string" ? metadata[key] : void 0;
19379
+ const getSpanMetadataString = (span, key) => {
19380
+ const m = span?.metadata;
19381
+ return m && typeof m[key] === "string" ? m[key] : void 0;
19382
+ };
19231
19383
  const parentSpan = this.getParentSpan(false);
19232
19384
  let rootSpan = this;
19233
19385
  while (rootSpan.parent) {
@@ -19241,12 +19393,15 @@ var BaseSpan = class {
19241
19393
  entityType: this.entityType,
19242
19394
  entityId: this.entityId,
19243
19395
  entityName: this.entityName,
19396
+ entityVersionId: getMetadataString("entityVersionId"),
19244
19397
  parentEntityType: parentSpan?.entityType,
19245
19398
  parentEntityId: parentSpan?.entityId,
19246
19399
  parentEntityName: parentSpan?.entityName,
19400
+ parentEntityVersionId: getSpanMetadataString(parentSpan, "entityVersionId"),
19247
19401
  rootEntityType: rootSpan.entityType,
19248
19402
  rootEntityId: rootSpan.entityId,
19249
19403
  rootEntityName: rootSpan.entityName,
19404
+ rootEntityVersionId: getSpanMetadataString(rootSpan, "entityVersionId"),
19250
19405
  userId: getMetadataString("userId"),
19251
19406
  organizationId: getMetadataString("organizationId"),
19252
19407
  resourceId: getMetadataString("resourceId"),
@@ -19383,7 +19538,10 @@ var DefaultSpan = class extends BaseSpan {
19383
19538
  domain: error48.domain,
19384
19539
  message: error48.message,
19385
19540
  name: error48.name,
19386
- stack: error48.stack
19541
+ // Prefer the original cause's stack when available. MastraError wraps
19542
+ // thrown errors, so its own stack points to the wrapping site rather
19543
+ // than where the underlying error was thrown.
19544
+ stack: error48.cause instanceof Error && error48.cause.stack || error48.stack
19387
19545
  } : {
19388
19546
  message: error48.message,
19389
19547
  name: error48.name,