@mastra/observability 1.9.1-alpha.1 → 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,30 @@
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
+
3
28
  ## 1.9.1-alpha.1
4
29
 
5
30
  ### 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 = {
@@ -18634,15 +18646,150 @@ function sumDefinedValues(obj, keys) {
18634
18646
  }
18635
18647
 
18636
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
+ }
18637
18786
  function extractStepInput(request) {
18638
18787
  if (!request) return void 0;
18639
18788
  const { body } = request;
18640
18789
  if (body == null) return request;
18641
18790
  try {
18642
18791
  const parsed = typeof body === "string" ? JSON.parse(body) : body;
18643
- if (Array.isArray(parsed?.messages)) return parsed.messages;
18644
- if (Array.isArray(parsed?.contents)) return parsed.contents;
18645
- return parsed;
18792
+ return summarizeRequestBody(parsed);
18646
18793
  } catch {
18647
18794
  return request;
18648
18795
  }
@@ -19229,6 +19376,10 @@ var BaseSpan = class {
19229
19376
  }
19230
19377
  const metadata = this.metadata ?? {};
19231
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
+ };
19232
19383
  const parentSpan = this.getParentSpan(false);
19233
19384
  let rootSpan = this;
19234
19385
  while (rootSpan.parent) {
@@ -19242,12 +19393,15 @@ var BaseSpan = class {
19242
19393
  entityType: this.entityType,
19243
19394
  entityId: this.entityId,
19244
19395
  entityName: this.entityName,
19396
+ entityVersionId: getMetadataString("entityVersionId"),
19245
19397
  parentEntityType: parentSpan?.entityType,
19246
19398
  parentEntityId: parentSpan?.entityId,
19247
19399
  parentEntityName: parentSpan?.entityName,
19400
+ parentEntityVersionId: getSpanMetadataString(parentSpan, "entityVersionId"),
19248
19401
  rootEntityType: rootSpan.entityType,
19249
19402
  rootEntityId: rootSpan.entityId,
19250
19403
  rootEntityName: rootSpan.entityName,
19404
+ rootEntityVersionId: getSpanMetadataString(rootSpan, "entityVersionId"),
19251
19405
  userId: getMetadataString("userId"),
19252
19406
  organizationId: getMetadataString("organizationId"),
19253
19407
  resourceId: getMetadataString("resourceId"),
@@ -19384,7 +19538,10 @@ var DefaultSpan = class extends BaseSpan {
19384
19538
  domain: error48.domain,
19385
19539
  message: error48.message,
19386
19540
  name: error48.name,
19387
- 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
19388
19545
  } : {
19389
19546
  message: error48.message,
19390
19547
  name: error48.name,