@mastra/observability 1.19.0 → 1.20.0-alpha.0

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/dist/index.cjs CHANGED
@@ -30,6 +30,8 @@ let zod_v4 = require("zod/v4");
30
30
  let fs = require("fs");
31
31
  fs = __toESM(fs, 1);
32
32
  let module$1 = require("module");
33
+ let os = require("os");
34
+ os = __toESM(os, 1);
33
35
  let path = require("path");
34
36
  path = __toESM(path, 1);
35
37
  let stream_web = require("stream/web");
@@ -1148,14 +1150,12 @@ var PricingModel = class {
1148
1150
  id;
1149
1151
  provider;
1150
1152
  model;
1151
- schema;
1152
1153
  currency;
1153
1154
  tiers;
1154
1155
  constructor(args) {
1155
1156
  this.id = args.id;
1156
1157
  this.provider = args.provider;
1157
1158
  this.model = args.model;
1158
- this.schema = args.schema;
1159
1159
  this.currency = args.currency;
1160
1160
  this.tiers = args.tiers;
1161
1161
  }
@@ -1168,8 +1168,145 @@ var PricingModel = class {
1168
1168
  }
1169
1169
  };
1170
1170
  //#endregion
1171
+ //#region src/metrics/models-dev.ts
1172
+ /** Same catalog the model router reads in `@mastra/core` (`gateways/models-dev.ts`). */
1173
+ const MODELS_DEV_API_URL = "https://models.dev/api.json";
1174
+ /**
1175
+ * A models.dev catalog with fewer prices than this has changed shape or is broken, so it must
1176
+ * not replace the pricing in use. The full catalog prices roughly 8,000 models.
1177
+ */
1178
+ const MIN_PRICED_MODELS = 1e3;
1179
+ /** models.dev `cost` keys (USD per 1M tokens) mapped to pricing meters. */
1180
+ const COST_KEY_TO_METER = {
1181
+ input: "input_tokens",
1182
+ output: "output_tokens",
1183
+ cache_read: "input_cache_read_tokens",
1184
+ cache_write: "input_cache_write_tokens",
1185
+ input_audio: "input_audio_tokens",
1186
+ output_audio: "output_audio_tokens",
1187
+ reasoning: "output_reasoning_tokens"
1188
+ };
1189
+ const PRICE_SIGNIFICANT_DIGITS = 12;
1190
+ /** Keep only each model's `cost` from a models.dev `api.json` payload, sorted by id so diffs stay small. */
1191
+ function pickModelsDevCosts(catalog) {
1192
+ const costs = {};
1193
+ for (const [providerId, providerInfo] of sortedEntries(catalog)) {
1194
+ if (!isRecord(providerInfo)) continue;
1195
+ for (const [modelId, modelInfo] of sortedEntries(providerInfo.models)) if (isRecord(modelInfo) && isRecord(modelInfo.cost)) (costs[providerId] ??= { models: {} }).models[modelId] = { cost: modelInfo.cost };
1196
+ }
1197
+ return costs;
1198
+ }
1199
+ /**
1200
+ * Build the pricing lookup from models.dev costs, keyed by `provider::model`.
1201
+ *
1202
+ * Each model is keyed by its own id (lowercased, dots and slashes flattened to dashes, matching
1203
+ * the lookup variants in `pricing-registry.ts`). Each model is also keyed by an alias with region,
1204
+ * vendor, version and date parts stripped, but only when no model owns that key itself, so the
1205
+ * dated `gpt-4o-2024-05-13` never replaces `gpt-4o`.
1206
+ */
1207
+ function modelsDevToPricingModels(costs) {
1208
+ const pricingModels = /* @__PURE__ */ new Map();
1209
+ const aliases = [];
1210
+ for (const [providerId, providerInfo] of Object.entries(isRecord(costs) ? costs : {})) {
1211
+ if (!isRecord(providerInfo) || !isRecord(providerInfo.models)) continue;
1212
+ const provider = normalizeProviderId(providerId);
1213
+ for (const [modelId, modelInfo] of Object.entries(providerInfo.models)) {
1214
+ const tiers = isRecord(modelInfo) ? toTiers(modelInfo.cost, modelId) : null;
1215
+ if (!tiers) continue;
1216
+ const model = modelId.toLowerCase().replace(/[./]/g, "-");
1217
+ const pricingModel = new PricingModel({
1218
+ id: `${providerId}/${modelId}`,
1219
+ provider,
1220
+ model,
1221
+ currency: "USD",
1222
+ tiers
1223
+ });
1224
+ pricingModels.set(`${provider}::${model}`, pricingModel);
1225
+ const alias = toAliasModelKey(modelId);
1226
+ if (alias && alias !== model) aliases.push({
1227
+ alias,
1228
+ modelId,
1229
+ pricingModel
1230
+ });
1231
+ }
1232
+ }
1233
+ aliases.sort((a, b) => a.modelId.length - b.modelId.length || b.modelId.localeCompare(a.modelId));
1234
+ for (const { alias, pricingModel } of aliases) {
1235
+ const key = `${pricingModel.provider}::${alias}`;
1236
+ if (!pricingModels.has(key)) {
1237
+ const { id, provider, currency, tiers } = pricingModel;
1238
+ pricingModels.set(key, new PricingModel({
1239
+ id,
1240
+ provider,
1241
+ model: alias,
1242
+ currency,
1243
+ tiers
1244
+ }));
1245
+ }
1246
+ }
1247
+ return pricingModels;
1248
+ }
1249
+ function toTiers(cost, modelId) {
1250
+ const isClaude = modelId.toLowerCase().includes("claude");
1251
+ const baseRates = toRates(cost, isClaude);
1252
+ if (!baseRates || !isRecord(cost)) return null;
1253
+ const contextTiers = (Array.isArray(cost.tiers) ? cost.tiers : []).flatMap((tier) => {
1254
+ const band = isRecord(tier) && isRecord(tier.tier) ? tier.tier : null;
1255
+ const rates = toRates(tier, isClaude);
1256
+ return band && (band.type ?? "context") === "context" && typeof band.size === "number" && band.size > 0 && rates ? [{
1257
+ size: band.size,
1258
+ rates
1259
+ }] : [];
1260
+ }).sort((a, b) => b.size - a.size);
1261
+ return [new PricingTier({
1262
+ index: 0,
1263
+ rates: baseRates
1264
+ }), ...contextTiers.map(({ size, rates }, i) => new PricingTier({
1265
+ index: i + 1,
1266
+ when: [{
1267
+ field: "total_input_tokens",
1268
+ op: "gte",
1269
+ value: size
1270
+ }],
1271
+ rates
1272
+ }))];
1273
+ }
1274
+ /** Zero prices are dropped (free endpoints and flat-fee plans), as the old platform pipeline did. */
1275
+ function toRates(cost, isClaude) {
1276
+ if (!isRecord(cost)) return null;
1277
+ const rates = {};
1278
+ for (const [costKey, meter] of Object.entries(COST_KEY_TO_METER)) {
1279
+ const pricePerMillion = cost[costKey];
1280
+ if (typeof pricePerMillion === "number" && Number.isFinite(pricePerMillion) && pricePerMillion > 0) rates[meter] = Number((pricePerMillion / 1e6).toPrecision(PRICE_SIGNIFICANT_DIGITS));
1281
+ }
1282
+ const cacheWriteRate = rates.input_cache_write_tokens;
1283
+ if (isClaude && cacheWriteRate !== void 0) {
1284
+ rates.input_cache_write_5m_tokens = cacheWriteRate;
1285
+ rates.input_cache_write_1h_tokens = cacheWriteRate * 1.6;
1286
+ }
1287
+ return rates.input_tokens && rates.output_tokens ? rates : null;
1288
+ }
1289
+ function normalizeProviderId(value) {
1290
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1291
+ }
1292
+ /** Port of the old platform pipeline's model-key normalization, used only for alias keys. */
1293
+ function toAliasModelKey(modelId) {
1294
+ return modelId.replace(/^accounts\/[^/]+\/models\//i, "").replace(/^(global|us|eu|apac)\./i, "").replace(/^[a-z0-9-]+\.(openai|anthropic|google|xai|minimax)\./i, "").replace(/^(openai|anthropic|google|xai|x-ai|minimax|minimaxai)[/:._-]+/i, "").replace(/-v\d+(?::0)?$/i, "").replace(/@\d{8}$/, "").replace(/@default$/i, "").replace(/-\d{4}-\d{2}-\d{2}$/, "").replace(/-20\d{6}$/, "").replace(/_20\d{6}$/, "").replace(/[/.:_\s]+/g, "-").replace(/[^a-zA-Z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
1295
+ }
1296
+ function sortedEntries(value) {
1297
+ return Object.entries(isRecord(value) ? value : {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1298
+ }
1299
+ function isRecord(value) {
1300
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1301
+ }
1302
+ //#endregion
1171
1303
  //#region src/metrics/pricing-registry.ts
1172
- const DATA_FILE_NAME = "pricing-data.jsonl";
1304
+ const DATA_FILE_NAME = "pricing-data.json";
1305
+ const REFRESH_INTERVAL_MS = 1440 * 60 * 1e3;
1306
+ const MIN_REFRESH_ATTEMPT_INTERVAL_MS = 3600 * 1e3;
1307
+ const REFRESH_TIMEOUT_MS = 5e3;
1308
+ const CACHE_FILE = () => path.default.join(os.default.homedir(), ".cache", "mastra", "pricing-data.json");
1309
+ const CACHE_VERSION = 1;
1173
1310
  const BEDROCK_GEOGRAPHY_PREFIXES = /* @__PURE__ */ new Set([
1174
1311
  "global",
1175
1312
  "us",
@@ -1181,100 +1318,154 @@ const BEDROCK_GEOGRAPHY_PREFIXES = /* @__PURE__ */ new Set([
1181
1318
  const AI_SDK_VERCEL_GATEWAY_PROVIDER_ID = "gateway";
1182
1319
  const VERCEL_PRICING_PROVIDER_ID = "vercel";
1183
1320
  const AI_SDK_PROVIDER_NAMESPACE_ALIASES = /* @__PURE__ */ new Map([
1321
+ ["fireworks", "fireworks-ai"],
1184
1322
  ["google.vertex", "google-vertex"],
1185
1323
  ["vertex.anthropic", "google-vertex-anthropic"],
1186
1324
  ["vertex.maas", "google-vertex"]
1187
1325
  ]);
1188
- const MINIFIED_METER_TO_CANONICAL = {
1189
- it: "input_tokens",
1190
- ot: "output_tokens",
1191
- icrt: "input_cache_read_tokens",
1192
- icwt: "input_cache_write_tokens",
1193
- icwt5m: "input_cache_write_5m_tokens",
1194
- icwt1h: "input_cache_write_1h_tokens",
1195
- iat: "input_audio_tokens",
1196
- oat: "output_audio_tokens",
1197
- ort: "output_reasoning_tokens"
1198
- };
1199
- const MINIFIED_CONDITION_FIELD_TO_CANONICAL = { tit: "total_input_tokens" };
1200
1326
  let cachedLoadError = null;
1327
+ let globalRegistry = null;
1328
+ let bundledRegistry = null;
1329
+ let refreshedAt = 0;
1330
+ let lastRefreshAttemptAt = 0;
1331
+ let refreshEtag;
1332
+ let refreshInFlight = false;
1201
1333
  var PricingRegistry = class PricingRegistry {
1202
1334
  pricingModels;
1203
- static globalRegistry = null;
1204
1335
  constructor(pricingModels) {
1205
1336
  this.pricingModels = pricingModels;
1206
1337
  }
1207
- static fromText(pricingModelText) {
1208
- return new PricingRegistry(parsePricingModelText(pricingModelText));
1338
+ /** A registry from models.dev costs, in the shape of `pricing-data.json` or `api.json`. */
1339
+ static fromModelsDev(costs) {
1340
+ return new PricingRegistry(modelsDevToPricingModels(costs));
1209
1341
  }
1342
+ /**
1343
+ * The registry used for cost estimates. It uses the bundled snapshot. When refresh is on (under
1344
+ * `mastra dev`, or with `MASTRA_AUTO_REFRESH_PRICING=true`), it starts from the models.dev copy
1345
+ * cached in `~/.cache/mastra` instead, and refreshes from models.dev in the background once a day
1346
+ * and when a model has no price. Lookups never wait for a refresh.
1347
+ */
1210
1348
  static getGlobal() {
1211
- if (PricingRegistry.globalRegistry) return PricingRegistry.globalRegistry;
1212
- const pricingModels = loadPricingModels();
1213
- if (!pricingModels) return null;
1214
- PricingRegistry.globalRegistry = new PricingRegistry(pricingModels);
1215
- return PricingRegistry.globalRegistry;
1349
+ if (!globalRegistry) {
1350
+ const cachedPricingModels = loadCachedPricingModels();
1351
+ globalRegistry = cachedPricingModels ? new PricingRegistry(cachedPricingModels) : getBundledRegistry();
1352
+ }
1353
+ if (Date.now() - refreshedAt >= REFRESH_INTERVAL_MS) refreshGlobalPricing();
1354
+ return globalRegistry;
1216
1355
  }
1217
1356
  get(args) {
1218
- for (const provider of getPricingProviderCandidates(args.provider, args.model)) {
1219
- const variants = getModelVariants(args.model, provider);
1220
- for (const variant of variants) {
1221
- const key = makePricingKey({
1222
- provider,
1223
- model: variant
1224
- });
1225
- const match = this.pricingModels.get(key);
1226
- if (match) return match;
1227
- }
1357
+ const match = this.find(args);
1358
+ if (match || this !== globalRegistry) return match;
1359
+ const bundledMatch = this === bundledRegistry ? null : getBundledRegistry()?.find(args);
1360
+ if (bundledMatch) return bundledMatch;
1361
+ refreshGlobalPricing();
1362
+ return null;
1363
+ }
1364
+ find(args) {
1365
+ for (const provider of getPricingProviderCandidates(args.provider, args.model)) for (const variant of getModelVariants(args.model, provider)) {
1366
+ const match = this.pricingModels.get(makePricingKey({
1367
+ provider,
1368
+ model: variant
1369
+ }));
1370
+ if (match) return match;
1228
1371
  }
1229
1372
  return null;
1230
1373
  }
1231
1374
  };
1375
+ function getBundledRegistry() {
1376
+ if (!bundledRegistry) {
1377
+ const pricingModels = loadPricingModels();
1378
+ if (pricingModels) bundledRegistry = new PricingRegistry(pricingModels);
1379
+ }
1380
+ return bundledRegistry;
1381
+ }
1382
+ /**
1383
+ * Same switches as the model router's auto-refresh in `@mastra/core`: on by default only under
1384
+ * `mastra dev` (`MASTRA_DEV`), opt-in elsewhere, and always off with `MASTRA_OFFLINE`.
1385
+ */
1386
+ function isRefreshEnabled() {
1387
+ const offline = process.env.MASTRA_OFFLINE;
1388
+ if (offline === "true" || offline === "1") return false;
1389
+ const isDev = process.env.MASTRA_DEV === "true" || process.env.MASTRA_DEV === "1";
1390
+ const setting = process.env.MASTRA_AUTO_REFRESH_PRICING;
1391
+ return setting === "true" || setting !== "false" && isDev;
1392
+ }
1393
+ /**
1394
+ * Start a background refresh from models.dev, unless refresh is off, one is already
1395
+ * running, or the last attempt was less than an hour ago.
1396
+ */
1397
+ function refreshGlobalPricing() {
1398
+ if (refreshInFlight || !isRefreshEnabled() || Date.now() - lastRefreshAttemptAt < MIN_REFRESH_ATTEMPT_INTERVAL_MS) return;
1399
+ refreshInFlight = true;
1400
+ lastRefreshAttemptAt = Date.now();
1401
+ fetchModelsDevPricing().catch(() => {}).finally(() => {
1402
+ refreshInFlight = false;
1403
+ });
1404
+ }
1405
+ async function fetchModelsDevPricing() {
1406
+ const response = await fetch(MODELS_DEV_API_URL, {
1407
+ headers: refreshEtag ? { "if-none-match": refreshEtag } : void 0,
1408
+ signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS)
1409
+ });
1410
+ if (response.status === 304) {
1411
+ refreshedAt = Date.now();
1412
+ await touchCacheFile();
1413
+ return;
1414
+ }
1415
+ if (!response.ok) throw new Error(`models.dev responded with ${response.status}`);
1416
+ const costs = pickModelsDevCosts(await response.json());
1417
+ const pricingModels = modelsDevToPricingModels(costs);
1418
+ if (pricingModels.size < 1e3) throw new Error(`models.dev returned ${pricingModels.size} prices, expected at least ${MIN_PRICED_MODELS}`);
1419
+ globalRegistry = new PricingRegistry(pricingModels);
1420
+ refreshedAt = Date.now();
1421
+ refreshEtag = response.headers.get("etag") ?? void 0;
1422
+ await writeCacheFile(JSON.stringify({
1423
+ version: CACHE_VERSION,
1424
+ etag: refreshEtag,
1425
+ costs
1426
+ }));
1427
+ }
1428
+ /** The cache file's mtime records when its prices were last confirmed against models.dev. */
1429
+ function loadCachedPricingModels() {
1430
+ if (!isRefreshEnabled()) return null;
1431
+ try {
1432
+ const cacheFile = CACHE_FILE();
1433
+ const cached = JSON.parse(fs.default.readFileSync(cacheFile, "utf-8"));
1434
+ if (cached.version !== CACHE_VERSION) return null;
1435
+ const pricingModels = modelsDevToPricingModels(cached.costs);
1436
+ if (pricingModels.size < 1e3) return null;
1437
+ refreshEtag = typeof cached.etag === "string" ? cached.etag : void 0;
1438
+ refreshedAt = fs.default.statSync(cacheFile).mtimeMs;
1439
+ return pricingModels;
1440
+ } catch {
1441
+ return null;
1442
+ }
1443
+ }
1444
+ /** Write-to-temp-then-rename, like the model router's cache writes, so readers never see a partial file. */
1445
+ async function writeCacheFile(content) {
1446
+ try {
1447
+ const cacheFile = CACHE_FILE();
1448
+ const tempFile = `${cacheFile}.${process.pid}.${Date.now()}.tmp`;
1449
+ await fs.default.promises.mkdir(path.default.dirname(cacheFile), { recursive: true });
1450
+ await fs.default.promises.writeFile(tempFile, content, "utf-8");
1451
+ await fs.default.promises.rename(tempFile, cacheFile);
1452
+ } catch {}
1453
+ }
1454
+ async function touchCacheFile() {
1455
+ try {
1456
+ const now = /* @__PURE__ */ new Date();
1457
+ await fs.default.promises.utimes(CACHE_FILE(), now, now);
1458
+ } catch {}
1459
+ }
1232
1460
  function loadPricingModels() {
1233
1461
  if (cachedLoadError) return null;
1234
1462
  try {
1235
- return parsePricingModelText(fs.default.readFileSync(resolvePricingModelPath(), "utf-8"));
1463
+ return modelsDevToPricingModels(JSON.parse(fs.default.readFileSync(resolvePricingModelPath(), "utf-8")));
1236
1464
  } catch (error) {
1237
1465
  cachedLoadError = error instanceof Error ? error.message : String(error);
1238
1466
  return null;
1239
1467
  }
1240
1468
  }
1241
- function parsePricingModelText(content) {
1242
- const pricingModels = /* @__PURE__ */ new Map();
1243
- for (const line of content.split("\n")) {
1244
- const trimmed = line.trim();
1245
- if (!trimmed) continue;
1246
- const pricingModel = expandPricingModelRow(JSON.parse(trimmed));
1247
- pricingModels.set(makePricingKey(pricingModel), pricingModel);
1248
- }
1249
- return pricingModels;
1250
- }
1251
- function expandRates(row, tier) {
1252
- const rates = Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]));
1253
- const cacheWriteRate = rates.input_cache_write_tokens;
1254
- if (row.m.includes("claude") && typeof cacheWriteRate === "number") {
1255
- rates.input_cache_write_5m_tokens ??= cacheWriteRate;
1256
- rates.input_cache_write_1h_tokens ??= cacheWriteRate * 1.6;
1257
- }
1258
- return rates;
1259
- }
1260
- function expandPricingModelRow(row) {
1261
- return new PricingModel({
1262
- id: row.i,
1263
- provider: row.p,
1264
- model: row.m,
1265
- schema: row.s.v,
1266
- currency: row.s.d.u,
1267
- tiers: row.s.d.t.map((tier, index) => new PricingTier({
1268
- index,
1269
- when: tier.w?.map((condition) => ({
1270
- field: MINIFIED_CONDITION_FIELD_TO_CANONICAL[condition.f],
1271
- op: condition.op,
1272
- value: condition.value
1273
- })),
1274
- rates: expandRates(row, tier)
1275
- }))
1276
- });
1277
- }
1278
1469
  function resolvePricingModelPath() {
1279
1470
  const packageRoot = getPackageRoot();
1280
1471
  const candidates = [
@@ -1284,7 +1475,7 @@ function resolvePricingModelPath() {
1284
1475
  path.default.join(process.cwd(), "src", "metrics", DATA_FILE_NAME)
1285
1476
  ];
1286
1477
  for (const candidate of candidates) if (fs.default.existsSync(candidate)) return candidate;
1287
- throw new Error(`Unable to locate pricing data JSONL at any known path: ${candidates.join(", ")}`);
1478
+ throw new Error(`Unable to locate pricing data at any known path: ${candidates.join(", ")}`);
1288
1479
  }
1289
1480
  function getPackageRoot() {
1290
1481
  try {
@@ -2905,6 +3096,17 @@ function isSpanInternal(spanType, flags) {
2905
3096
  }
2906
3097
  }
2907
3098
  /**
3099
+ * Copies the links whose IDs are valid W3C trace and span IDs (32 and 16 lowercase
3100
+ * hex characters, not all zeros); `undefined` when none remain.
3101
+ */
3102
+ function validLinks(links) {
3103
+ const valid = links?.filter((link) => /^(?!0+$)[0-9a-f]{32}$/.test(link?.traceId ?? "") && /^(?!0+$)[0-9a-f]{16}$/.test(link?.spanId ?? ""));
3104
+ return valid?.length ? valid.map(({ traceId, spanId }) => ({
3105
+ traceId,
3106
+ spanId
3107
+ })) : void 0;
3108
+ }
3109
+ /**
2908
3110
  * Get the external parent span ID from CreateSpanOptions.
2909
3111
  *
2910
3112
  * If the parent is internal or excluded from export (`excludeSpanTypes`),
@@ -2954,6 +3156,7 @@ var BaseSpan = class BaseSpan {
2954
3156
  metadata;
2955
3157
  requestContext;
2956
3158
  tags;
3159
+ links;
2957
3160
  traceState;
2958
3161
  /** Entity type that created the span (e.g., agent, workflow) */
2959
3162
  entityType;
@@ -3019,6 +3222,7 @@ var BaseSpan = class BaseSpan {
3019
3222
  this.#openChildrenOwner = parent;
3020
3223
  }
3021
3224
  this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
3225
+ this.links = validLinks(options.links);
3022
3226
  const entityParent = this.getParentSpan(false);
3023
3227
  this.entityType = options.entityType ?? entityParent?.entityType;
3024
3228
  this.entityId = options.entityId ?? entityParent?.entityId;
@@ -3219,6 +3423,7 @@ var BaseSpan = class BaseSpan {
3219
3423
  const hideInput = this.traceState?.hideInput ?? false;
3220
3424
  const hideOutput = this.traceState?.hideOutput ?? false;
3221
3425
  const nestedUnderParent = this.traceState?.nestedUnderParent ?? false;
3426
+ const links = validLinks(this.links);
3222
3427
  return {
3223
3428
  id: this.id,
3224
3429
  traceId: this.traceId,
@@ -3241,6 +3446,7 @@ var BaseSpan = class BaseSpan {
3241
3446
  parentSpanId: this.getParentSpanId(includeInternalSpans),
3242
3447
  externalParentSpanId: this.getExportedExternalParentSpanId(includeInternalSpans),
3243
3448
  ...nestedUnderParent ? { nestedUnderParent: true } : {},
3449
+ ...links ? { links } : {},
3244
3450
  ...this.isRootSpan && !nestedUnderParent && this.tags?.length ? { tags: this.tags } : {}
3245
3451
  };
3246
3452
  }
@@ -4829,6 +5035,7 @@ var BaseObservabilityInstance = class extends _mastra_core_base.MastraBase {
4829
5035
  spanId: cached.id,
4830
5036
  parentSpanId: cached.parentSpanId,
4831
5037
  externalParentSpanId: cached.externalParentSpanId,
5038
+ links: cached.links,
4832
5039
  startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
4833
5040
  input: cached.input,
4834
5041
  attributes: cached.attributes,