@mastra/observability 1.5.0 → 1.5.1-alpha.1

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,25 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.5.1-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix cache token extraction in multi-step agent runs. Prefer AI SDK aggregated `inputTokenDetails` over `providerMetadata` (which only reflects the last step). Also fix truthiness checks to correctly handle zero values for cache and reasoning tokens. ([#14492](https://github.com/mastra-ai/mastra/pull/14492))
8
+
9
+ Fix Datadog metric keys to match dd-trace expected format: `cacheReadTokens`, `cacheWriteTokens`, `reasoningOutputTokens`.
10
+
11
+ - Updated dependencies [[`9e1a3ed`](https://github.com/mastra-ai/mastra/commit/9e1a3ed07cfafb5e8e19a796ce0bee817002d7c0), [`a579f7a`](https://github.com/mastra-ai/mastra/commit/a579f7a31e582674862b5679bc79af7ccf7429b8)]:
12
+ - @mastra/core@1.15.0-alpha.2
13
+
14
+ ## 1.5.1-alpha.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Fixed span serialization to avoid incorrect [Circular] placeholders in traces. ([#14263](https://github.com/mastra-ai/mastra/pull/14263))
19
+
20
+ - Updated dependencies [[`cb611a1`](https://github.com/mastra-ai/mastra/commit/cb611a1e89a4f4cf74c97b57e0c27bb56f2eceb5), [`62d1d3c`](https://github.com/mastra-ai/mastra/commit/62d1d3cc08fe8182e7080237fd975de862ec8c91), [`8681ecb`](https://github.com/mastra-ai/mastra/commit/8681ecb86184d5907267000e4576cc442a9a83fc), [`28d0249`](https://github.com/mastra-ai/mastra/commit/28d0249295782277040ad1e0d243e695b7ab1ce4), [`bb0f09d`](https://github.com/mastra-ai/mastra/commit/bb0f09dbac58401b36069f483acf5673202db5b5), [`5f7e9d0`](https://github.com/mastra-ai/mastra/commit/5f7e9d0db664020e1f3d97d7d18c6b0b9d4843d0)]:
21
+ - @mastra/core@1.15.0-alpha.0
22
+
3
23
  ## 1.5.0
4
24
 
5
25
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -17426,6 +17426,9 @@ var MetricsContextImpl = class {
17426
17426
  };
17427
17427
 
17428
17428
  // src/usage.ts
17429
+ function isDefined(value) {
17430
+ return value != null;
17431
+ }
17429
17432
  function extractUsageMetrics(usage, providerMetadata) {
17430
17433
  if (!usage) {
17431
17434
  return {};
@@ -17434,31 +17437,38 @@ function extractUsageMetrics(usage, providerMetadata) {
17434
17437
  const outputDetails = {};
17435
17438
  let inputTokens = usage.inputTokens;
17436
17439
  const outputTokens = usage.outputTokens;
17437
- if (usage.cachedInputTokens) {
17440
+ const aiSdkDetails = usage.inputTokenDetails;
17441
+ if (isDefined(aiSdkDetails?.cacheReadTokens)) {
17442
+ inputDetails.cacheRead = aiSdkDetails.cacheReadTokens;
17443
+ }
17444
+ if (isDefined(aiSdkDetails?.cacheWriteTokens)) {
17445
+ inputDetails.cacheWrite = aiSdkDetails.cacheWriteTokens;
17446
+ }
17447
+ if (!isDefined(inputDetails.cacheRead) && isDefined(usage.cachedInputTokens)) {
17438
17448
  inputDetails.cacheRead = usage.cachedInputTokens;
17439
17449
  }
17440
- if (usage.reasoningTokens) {
17450
+ if (isDefined(usage.reasoningTokens)) {
17441
17451
  outputDetails.reasoning = usage.reasoningTokens;
17442
17452
  }
17443
17453
  const anthropic = providerMetadata?.anthropic;
17444
17454
  if (anthropic) {
17445
- if (anthropic.cacheReadInputTokens) {
17455
+ if (!isDefined(inputDetails.cacheRead) && isDefined(anthropic.cacheReadInputTokens)) {
17446
17456
  inputDetails.cacheRead = anthropic.cacheReadInputTokens;
17447
17457
  }
17448
- if (anthropic.cacheCreationInputTokens) {
17458
+ if (!isDefined(inputDetails.cacheWrite) && isDefined(anthropic.cacheCreationInputTokens)) {
17449
17459
  inputDetails.cacheWrite = anthropic.cacheCreationInputTokens;
17450
17460
  }
17451
- if (anthropic.cacheReadInputTokens || anthropic.cacheCreationInputTokens) {
17461
+ if (isDefined(inputDetails.cacheRead) || isDefined(inputDetails.cacheWrite)) {
17452
17462
  inputDetails.text = usage.inputTokens;
17453
- inputTokens = (usage.inputTokens ?? 0) + (anthropic.cacheReadInputTokens ?? 0) + (anthropic.cacheCreationInputTokens ?? 0);
17463
+ inputTokens = (usage.inputTokens ?? 0) + (inputDetails.cacheRead ?? 0) + (inputDetails.cacheWrite ?? 0);
17454
17464
  }
17455
17465
  }
17456
17466
  const google = providerMetadata?.google;
17457
17467
  if (google?.usageMetadata) {
17458
- if (google.usageMetadata.cachedContentTokenCount) {
17468
+ if (!isDefined(inputDetails.cacheRead) && isDefined(google.usageMetadata.cachedContentTokenCount)) {
17459
17469
  inputDetails.cacheRead = google.usageMetadata.cachedContentTokenCount;
17460
17470
  }
17461
- if (google.usageMetadata.thoughtsTokenCount) {
17471
+ if (isDefined(google.usageMetadata.thoughtsTokenCount)) {
17462
17472
  outputDetails.reasoning = google.usageMetadata.thoughtsTokenCount;
17463
17473
  }
17464
17474
  }
@@ -17950,6 +17960,17 @@ function compressJsonSchema(schema, depth = 0) {
17950
17960
  if (depth > 3) {
17951
17961
  return schema.type || "object";
17952
17962
  }
17963
+ const compositionKeys = ["oneOf", "anyOf", "allOf"].filter((key) => Array.isArray(schema[key]));
17964
+ if (compositionKeys.length > 0) {
17965
+ const compressed2 = {};
17966
+ for (const key of compositionKeys) {
17967
+ compressed2[key] = schema[key].map((entry) => compressJsonSchema(entry, depth + 1));
17968
+ }
17969
+ if (typeof schema.type === "string") {
17970
+ compressed2.type = schema.type;
17971
+ }
17972
+ return compressed2;
17973
+ }
17953
17974
  if (schema.type !== "object" || !schema.properties) {
17954
17975
  return schema.type || schema;
17955
17976
  }
@@ -17983,7 +18004,7 @@ function compressJsonSchema(schema, depth = 0) {
17983
18004
  function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
17984
18005
  const { keysToStrip, maxDepth, maxStringLength, maxArrayLength, maxObjectKeys } = options;
17985
18006
  const stripSet = keysToStrip instanceof Set ? keysToStrip : new Set(Array.isArray(keysToStrip) ? keysToStrip : Object.keys(keysToStrip));
17986
- const seen = /* @__PURE__ */ new WeakSet();
18007
+ const ancestors = /* @__PURE__ */ new WeakSet();
17987
18008
  function helper(val, depth) {
17988
18009
  if (depth > maxDepth) {
17989
18010
  return "[MaxDepth]";
@@ -18016,59 +18037,85 @@ function deepClean(value, options = DEFAULT_DEEP_CLEAN_OPTIONS) {
18016
18037
  };
18017
18038
  }
18018
18039
  if (typeof val === "object") {
18019
- if (seen.has(val)) {
18040
+ if (ancestors.has(val)) {
18020
18041
  return "[Circular]";
18021
18042
  }
18022
- seen.add(val);
18043
+ ancestors.add(val);
18023
18044
  }
18024
- if (Array.isArray(val)) {
18025
- const limitedArray = val.slice(0, maxArrayLength);
18026
- const cleaned2 = limitedArray.map((item) => helper(item, depth + 1));
18027
- if (val.length > maxArrayLength) {
18028
- cleaned2.push(`[\u2026${val.length - maxArrayLength} more items]`);
18045
+ try {
18046
+ if (Array.isArray(val)) {
18047
+ const cleaned2 = [];
18048
+ for (let i = 0; i < Math.min(val.length, maxArrayLength); i++) {
18049
+ try {
18050
+ cleaned2.push(helper(val[i], depth + 1));
18051
+ } catch (error48) {
18052
+ cleaned2.push(`[${error48 instanceof Error ? truncateString(error48.message, 256) : "unknown error"}]`);
18053
+ }
18054
+ }
18055
+ if (val.length > maxArrayLength) {
18056
+ cleaned2.push(`[\u2026${val.length - maxArrayLength} more items]`);
18057
+ }
18058
+ return cleaned2;
18029
18059
  }
18030
- return cleaned2;
18031
- }
18032
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(val)) {
18033
- return `[Buffer length=${val.length}]`;
18034
- }
18035
- if (ArrayBuffer.isView(val)) {
18036
- const ctor = val.constructor?.name ?? "TypedArray";
18037
- const byteLength = val.byteLength ?? "?";
18038
- return `[${ctor} byteLength=${byteLength}]`;
18039
- }
18040
- if (val instanceof ArrayBuffer) {
18041
- return `[ArrayBuffer byteLength=${val.byteLength}]`;
18042
- }
18043
- if (typeof val.serializeForSpan === "function") {
18044
- try {
18045
- return helper(val.serializeForSpan(), depth);
18046
- } catch {
18060
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(val)) {
18061
+ return `[Buffer length=${val.length}]`;
18047
18062
  }
18048
- }
18049
- if (isJsonSchema(val)) {
18050
- return helper(compressJsonSchema(val), depth);
18051
- }
18052
- const cleaned = {};
18053
- const entries = Object.entries(val);
18054
- let keyCount = 0;
18055
- for (const [key, v] of entries) {
18056
- if (stripSet.has(key)) {
18057
- continue;
18063
+ if (ArrayBuffer.isView(val)) {
18064
+ const ctor = val.constructor?.name ?? "TypedArray";
18065
+ const byteLength = val.byteLength ?? "?";
18066
+ return `[${ctor} byteLength=${byteLength}]`;
18058
18067
  }
18059
- if (keyCount >= maxObjectKeys) {
18060
- cleaned["__truncated"] = `${entries.length - keyCount} more keys omitted`;
18061
- break;
18068
+ if (val instanceof ArrayBuffer) {
18069
+ return `[ArrayBuffer byteLength=${val.byteLength}]`;
18062
18070
  }
18071
+ let serializeForSpan;
18063
18072
  try {
18064
- cleaned[key] = helper(v, depth + 1);
18065
- keyCount++;
18073
+ serializeForSpan = val.serializeForSpan;
18066
18074
  } catch (error48) {
18067
- cleaned[key] = `[${error48 instanceof Error ? error48.message : String(error48)}]`;
18068
- keyCount++;
18075
+ return `[serializeForSpan failed: ${error48 instanceof Error ? truncateString(error48.message, 256) : "unknown error"}]`;
18076
+ }
18077
+ if (typeof serializeForSpan === "function") {
18078
+ try {
18079
+ return helper(serializeForSpan.call(val), depth);
18080
+ } catch (error48) {
18081
+ return `[serializeForSpan failed: ${error48 instanceof Error ? truncateString(error48.message, 256) : "unknown error"}]`;
18082
+ }
18083
+ }
18084
+ let looksLikeJsonSchema = false;
18085
+ try {
18086
+ looksLikeJsonSchema = isJsonSchema(val);
18087
+ } catch {
18088
+ looksLikeJsonSchema = false;
18089
+ }
18090
+ if (looksLikeJsonSchema) {
18091
+ try {
18092
+ const compressed = compressJsonSchema(val);
18093
+ return compressed === val ? "[JSONSchema]" : helper(compressed, depth);
18094
+ } catch {
18095
+ }
18096
+ }
18097
+ const cleaned = {};
18098
+ const keys = Object.keys(val).filter((key) => !stripSet.has(key));
18099
+ let keyCount = 0;
18100
+ for (const key of keys) {
18101
+ if (keyCount >= maxObjectKeys) {
18102
+ cleaned["__truncated"] = `${keys.length - keyCount} more keys omitted`;
18103
+ break;
18104
+ }
18105
+ try {
18106
+ cleaned[key] = helper(val[key], depth + 1);
18107
+ keyCount++;
18108
+ } catch (error48) {
18109
+ cleaned[key] = `[${error48 instanceof Error ? truncateString(error48.message, 256) : "unknown error"}]`;
18110
+ keyCount++;
18111
+ }
18112
+ }
18113
+ return cleaned;
18114
+ } finally {
18115
+ if (typeof val === "object" && val !== null) {
18116
+ ancestors.delete(val);
18069
18117
  }
18070
18118
  }
18071
- return cleaned;
18072
18119
  }
18073
18120
  return helper(value, 0);
18074
18121
  }