@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 +279 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +278 -72
- package/dist/index.js.map +1 -1
- package/dist/instances/base.d.ts.map +1 -1
- package/dist/metrics/models-dev.d.ts +26 -0
- package/dist/metrics/models-dev.d.ts.map +1 -0
- package/dist/metrics/pricing-data.json +66903 -0
- package/dist/metrics/pricing-model.d.ts +0 -2
- package/dist/metrics/pricing-model.d.ts.map +1 -1
- package/dist/metrics/pricing-registry.d.ts +10 -3
- package/dist/metrics/pricing-registry.d.ts.map +1 -1
- package/dist/spans/base.d.ts +2 -1
- package/dist/spans/base.d.ts.map +1 -1
- package/package.json +7 -5
- package/dist/metrics/pricing-data.jsonl +0 -9132
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.
|
|
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
|
-
|
|
1208
|
-
|
|
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 (
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
|
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
|
|
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,
|