@mastra/observability 1.19.0-alpha.4 → 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 +9 -7
- package/dist/metrics/pricing-data.jsonl +0 -9132
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { DEFAULT_BLOCKED_LABELS, EntityType, InternalSpans, SpanType, TracingEve
|
|
|
6
6
|
import { fetchWithRetry, getNestedValue, setNestedValue } from "@mastra/core/utils";
|
|
7
7
|
import { z } from "zod/v4";
|
|
8
8
|
import fs from "fs";
|
|
9
|
+
import os from "os";
|
|
9
10
|
import path from "path";
|
|
10
11
|
import { TransformStream } from "stream/web";
|
|
11
12
|
import { coreFeatures } from "@mastra/core/features";
|
|
@@ -1123,14 +1124,12 @@ var PricingModel = class {
|
|
|
1123
1124
|
id;
|
|
1124
1125
|
provider;
|
|
1125
1126
|
model;
|
|
1126
|
-
schema;
|
|
1127
1127
|
currency;
|
|
1128
1128
|
tiers;
|
|
1129
1129
|
constructor(args) {
|
|
1130
1130
|
this.id = args.id;
|
|
1131
1131
|
this.provider = args.provider;
|
|
1132
1132
|
this.model = args.model;
|
|
1133
|
-
this.schema = args.schema;
|
|
1134
1133
|
this.currency = args.currency;
|
|
1135
1134
|
this.tiers = args.tiers;
|
|
1136
1135
|
}
|
|
@@ -1143,8 +1142,145 @@ var PricingModel = class {
|
|
|
1143
1142
|
}
|
|
1144
1143
|
};
|
|
1145
1144
|
//#endregion
|
|
1145
|
+
//#region src/metrics/models-dev.ts
|
|
1146
|
+
/** Same catalog the model router reads in `@mastra/core` (`gateways/models-dev.ts`). */
|
|
1147
|
+
const MODELS_DEV_API_URL = "https://models.dev/api.json";
|
|
1148
|
+
/**
|
|
1149
|
+
* A models.dev catalog with fewer prices than this has changed shape or is broken, so it must
|
|
1150
|
+
* not replace the pricing in use. The full catalog prices roughly 8,000 models.
|
|
1151
|
+
*/
|
|
1152
|
+
const MIN_PRICED_MODELS = 1e3;
|
|
1153
|
+
/** models.dev `cost` keys (USD per 1M tokens) mapped to pricing meters. */
|
|
1154
|
+
const COST_KEY_TO_METER = {
|
|
1155
|
+
input: "input_tokens",
|
|
1156
|
+
output: "output_tokens",
|
|
1157
|
+
cache_read: "input_cache_read_tokens",
|
|
1158
|
+
cache_write: "input_cache_write_tokens",
|
|
1159
|
+
input_audio: "input_audio_tokens",
|
|
1160
|
+
output_audio: "output_audio_tokens",
|
|
1161
|
+
reasoning: "output_reasoning_tokens"
|
|
1162
|
+
};
|
|
1163
|
+
const PRICE_SIGNIFICANT_DIGITS = 12;
|
|
1164
|
+
/** Keep only each model's `cost` from a models.dev `api.json` payload, sorted by id so diffs stay small. */
|
|
1165
|
+
function pickModelsDevCosts(catalog) {
|
|
1166
|
+
const costs = {};
|
|
1167
|
+
for (const [providerId, providerInfo] of sortedEntries(catalog)) {
|
|
1168
|
+
if (!isRecord(providerInfo)) continue;
|
|
1169
|
+
for (const [modelId, modelInfo] of sortedEntries(providerInfo.models)) if (isRecord(modelInfo) && isRecord(modelInfo.cost)) (costs[providerId] ??= { models: {} }).models[modelId] = { cost: modelInfo.cost };
|
|
1170
|
+
}
|
|
1171
|
+
return costs;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Build the pricing lookup from models.dev costs, keyed by `provider::model`.
|
|
1175
|
+
*
|
|
1176
|
+
* Each model is keyed by its own id (lowercased, dots and slashes flattened to dashes, matching
|
|
1177
|
+
* the lookup variants in `pricing-registry.ts`). Each model is also keyed by an alias with region,
|
|
1178
|
+
* vendor, version and date parts stripped, but only when no model owns that key itself, so the
|
|
1179
|
+
* dated `gpt-4o-2024-05-13` never replaces `gpt-4o`.
|
|
1180
|
+
*/
|
|
1181
|
+
function modelsDevToPricingModels(costs) {
|
|
1182
|
+
const pricingModels = /* @__PURE__ */ new Map();
|
|
1183
|
+
const aliases = [];
|
|
1184
|
+
for (const [providerId, providerInfo] of Object.entries(isRecord(costs) ? costs : {})) {
|
|
1185
|
+
if (!isRecord(providerInfo) || !isRecord(providerInfo.models)) continue;
|
|
1186
|
+
const provider = normalizeProviderId(providerId);
|
|
1187
|
+
for (const [modelId, modelInfo] of Object.entries(providerInfo.models)) {
|
|
1188
|
+
const tiers = isRecord(modelInfo) ? toTiers(modelInfo.cost, modelId) : null;
|
|
1189
|
+
if (!tiers) continue;
|
|
1190
|
+
const model = modelId.toLowerCase().replace(/[./]/g, "-");
|
|
1191
|
+
const pricingModel = new PricingModel({
|
|
1192
|
+
id: `${providerId}/${modelId}`,
|
|
1193
|
+
provider,
|
|
1194
|
+
model,
|
|
1195
|
+
currency: "USD",
|
|
1196
|
+
tiers
|
|
1197
|
+
});
|
|
1198
|
+
pricingModels.set(`${provider}::${model}`, pricingModel);
|
|
1199
|
+
const alias = toAliasModelKey(modelId);
|
|
1200
|
+
if (alias && alias !== model) aliases.push({
|
|
1201
|
+
alias,
|
|
1202
|
+
modelId,
|
|
1203
|
+
pricingModel
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
aliases.sort((a, b) => a.modelId.length - b.modelId.length || b.modelId.localeCompare(a.modelId));
|
|
1208
|
+
for (const { alias, pricingModel } of aliases) {
|
|
1209
|
+
const key = `${pricingModel.provider}::${alias}`;
|
|
1210
|
+
if (!pricingModels.has(key)) {
|
|
1211
|
+
const { id, provider, currency, tiers } = pricingModel;
|
|
1212
|
+
pricingModels.set(key, new PricingModel({
|
|
1213
|
+
id,
|
|
1214
|
+
provider,
|
|
1215
|
+
model: alias,
|
|
1216
|
+
currency,
|
|
1217
|
+
tiers
|
|
1218
|
+
}));
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return pricingModels;
|
|
1222
|
+
}
|
|
1223
|
+
function toTiers(cost, modelId) {
|
|
1224
|
+
const isClaude = modelId.toLowerCase().includes("claude");
|
|
1225
|
+
const baseRates = toRates(cost, isClaude);
|
|
1226
|
+
if (!baseRates || !isRecord(cost)) return null;
|
|
1227
|
+
const contextTiers = (Array.isArray(cost.tiers) ? cost.tiers : []).flatMap((tier) => {
|
|
1228
|
+
const band = isRecord(tier) && isRecord(tier.tier) ? tier.tier : null;
|
|
1229
|
+
const rates = toRates(tier, isClaude);
|
|
1230
|
+
return band && (band.type ?? "context") === "context" && typeof band.size === "number" && band.size > 0 && rates ? [{
|
|
1231
|
+
size: band.size,
|
|
1232
|
+
rates
|
|
1233
|
+
}] : [];
|
|
1234
|
+
}).sort((a, b) => b.size - a.size);
|
|
1235
|
+
return [new PricingTier({
|
|
1236
|
+
index: 0,
|
|
1237
|
+
rates: baseRates
|
|
1238
|
+
}), ...contextTiers.map(({ size, rates }, i) => new PricingTier({
|
|
1239
|
+
index: i + 1,
|
|
1240
|
+
when: [{
|
|
1241
|
+
field: "total_input_tokens",
|
|
1242
|
+
op: "gte",
|
|
1243
|
+
value: size
|
|
1244
|
+
}],
|
|
1245
|
+
rates
|
|
1246
|
+
}))];
|
|
1247
|
+
}
|
|
1248
|
+
/** Zero prices are dropped (free endpoints and flat-fee plans), as the old platform pipeline did. */
|
|
1249
|
+
function toRates(cost, isClaude) {
|
|
1250
|
+
if (!isRecord(cost)) return null;
|
|
1251
|
+
const rates = {};
|
|
1252
|
+
for (const [costKey, meter] of Object.entries(COST_KEY_TO_METER)) {
|
|
1253
|
+
const pricePerMillion = cost[costKey];
|
|
1254
|
+
if (typeof pricePerMillion === "number" && Number.isFinite(pricePerMillion) && pricePerMillion > 0) rates[meter] = Number((pricePerMillion / 1e6).toPrecision(PRICE_SIGNIFICANT_DIGITS));
|
|
1255
|
+
}
|
|
1256
|
+
const cacheWriteRate = rates.input_cache_write_tokens;
|
|
1257
|
+
if (isClaude && cacheWriteRate !== void 0) {
|
|
1258
|
+
rates.input_cache_write_5m_tokens = cacheWriteRate;
|
|
1259
|
+
rates.input_cache_write_1h_tokens = cacheWriteRate * 1.6;
|
|
1260
|
+
}
|
|
1261
|
+
return rates.input_tokens && rates.output_tokens ? rates : null;
|
|
1262
|
+
}
|
|
1263
|
+
function normalizeProviderId(value) {
|
|
1264
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1265
|
+
}
|
|
1266
|
+
/** Port of the old platform pipeline's model-key normalization, used only for alias keys. */
|
|
1267
|
+
function toAliasModelKey(modelId) {
|
|
1268
|
+
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();
|
|
1269
|
+
}
|
|
1270
|
+
function sortedEntries(value) {
|
|
1271
|
+
return Object.entries(isRecord(value) ? value : {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1272
|
+
}
|
|
1273
|
+
function isRecord(value) {
|
|
1274
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1275
|
+
}
|
|
1276
|
+
//#endregion
|
|
1146
1277
|
//#region src/metrics/pricing-registry.ts
|
|
1147
|
-
const DATA_FILE_NAME = "pricing-data.
|
|
1278
|
+
const DATA_FILE_NAME = "pricing-data.json";
|
|
1279
|
+
const REFRESH_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
1280
|
+
const MIN_REFRESH_ATTEMPT_INTERVAL_MS = 3600 * 1e3;
|
|
1281
|
+
const REFRESH_TIMEOUT_MS = 5e3;
|
|
1282
|
+
const CACHE_FILE = () => path.join(os.homedir(), ".cache", "mastra", "pricing-data.json");
|
|
1283
|
+
const CACHE_VERSION = 1;
|
|
1148
1284
|
const BEDROCK_GEOGRAPHY_PREFIXES = /* @__PURE__ */ new Set([
|
|
1149
1285
|
"global",
|
|
1150
1286
|
"us",
|
|
@@ -1156,100 +1292,154 @@ const BEDROCK_GEOGRAPHY_PREFIXES = /* @__PURE__ */ new Set([
|
|
|
1156
1292
|
const AI_SDK_VERCEL_GATEWAY_PROVIDER_ID = "gateway";
|
|
1157
1293
|
const VERCEL_PRICING_PROVIDER_ID = "vercel";
|
|
1158
1294
|
const AI_SDK_PROVIDER_NAMESPACE_ALIASES = /* @__PURE__ */ new Map([
|
|
1295
|
+
["fireworks", "fireworks-ai"],
|
|
1159
1296
|
["google.vertex", "google-vertex"],
|
|
1160
1297
|
["vertex.anthropic", "google-vertex-anthropic"],
|
|
1161
1298
|
["vertex.maas", "google-vertex"]
|
|
1162
1299
|
]);
|
|
1163
|
-
const MINIFIED_METER_TO_CANONICAL = {
|
|
1164
|
-
it: "input_tokens",
|
|
1165
|
-
ot: "output_tokens",
|
|
1166
|
-
icrt: "input_cache_read_tokens",
|
|
1167
|
-
icwt: "input_cache_write_tokens",
|
|
1168
|
-
icwt5m: "input_cache_write_5m_tokens",
|
|
1169
|
-
icwt1h: "input_cache_write_1h_tokens",
|
|
1170
|
-
iat: "input_audio_tokens",
|
|
1171
|
-
oat: "output_audio_tokens",
|
|
1172
|
-
ort: "output_reasoning_tokens"
|
|
1173
|
-
};
|
|
1174
|
-
const MINIFIED_CONDITION_FIELD_TO_CANONICAL = { tit: "total_input_tokens" };
|
|
1175
1300
|
let cachedLoadError = null;
|
|
1301
|
+
let globalRegistry = null;
|
|
1302
|
+
let bundledRegistry = null;
|
|
1303
|
+
let refreshedAt = 0;
|
|
1304
|
+
let lastRefreshAttemptAt = 0;
|
|
1305
|
+
let refreshEtag;
|
|
1306
|
+
let refreshInFlight = false;
|
|
1176
1307
|
var PricingRegistry = class PricingRegistry {
|
|
1177
1308
|
pricingModels;
|
|
1178
|
-
static globalRegistry = null;
|
|
1179
1309
|
constructor(pricingModels) {
|
|
1180
1310
|
this.pricingModels = pricingModels;
|
|
1181
1311
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1312
|
+
/** A registry from models.dev costs, in the shape of `pricing-data.json` or `api.json`. */
|
|
1313
|
+
static fromModelsDev(costs) {
|
|
1314
|
+
return new PricingRegistry(modelsDevToPricingModels(costs));
|
|
1184
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* The registry used for cost estimates. It uses the bundled snapshot. When refresh is on (under
|
|
1318
|
+
* `mastra dev`, or with `MASTRA_AUTO_REFRESH_PRICING=true`), it starts from the models.dev copy
|
|
1319
|
+
* cached in `~/.cache/mastra` instead, and refreshes from models.dev in the background once a day
|
|
1320
|
+
* and when a model has no price. Lookups never wait for a refresh.
|
|
1321
|
+
*/
|
|
1185
1322
|
static getGlobal() {
|
|
1186
|
-
if (
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1323
|
+
if (!globalRegistry) {
|
|
1324
|
+
const cachedPricingModels = loadCachedPricingModels();
|
|
1325
|
+
globalRegistry = cachedPricingModels ? new PricingRegistry(cachedPricingModels) : getBundledRegistry();
|
|
1326
|
+
}
|
|
1327
|
+
if (Date.now() - refreshedAt >= REFRESH_INTERVAL_MS) refreshGlobalPricing();
|
|
1328
|
+
return globalRegistry;
|
|
1191
1329
|
}
|
|
1192
1330
|
get(args) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1331
|
+
const match = this.find(args);
|
|
1332
|
+
if (match || this !== globalRegistry) return match;
|
|
1333
|
+
const bundledMatch = this === bundledRegistry ? null : getBundledRegistry()?.find(args);
|
|
1334
|
+
if (bundledMatch) return bundledMatch;
|
|
1335
|
+
refreshGlobalPricing();
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
find(args) {
|
|
1339
|
+
for (const provider of getPricingProviderCandidates(args.provider, args.model)) for (const variant of getModelVariants(args.model, provider)) {
|
|
1340
|
+
const match = this.pricingModels.get(makePricingKey({
|
|
1341
|
+
provider,
|
|
1342
|
+
model: variant
|
|
1343
|
+
}));
|
|
1344
|
+
if (match) return match;
|
|
1203
1345
|
}
|
|
1204
1346
|
return null;
|
|
1205
1347
|
}
|
|
1206
1348
|
};
|
|
1349
|
+
function getBundledRegistry() {
|
|
1350
|
+
if (!bundledRegistry) {
|
|
1351
|
+
const pricingModels = loadPricingModels();
|
|
1352
|
+
if (pricingModels) bundledRegistry = new PricingRegistry(pricingModels);
|
|
1353
|
+
}
|
|
1354
|
+
return bundledRegistry;
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Same switches as the model router's auto-refresh in `@mastra/core`: on by default only under
|
|
1358
|
+
* `mastra dev` (`MASTRA_DEV`), opt-in elsewhere, and always off with `MASTRA_OFFLINE`.
|
|
1359
|
+
*/
|
|
1360
|
+
function isRefreshEnabled() {
|
|
1361
|
+
const offline = process.env.MASTRA_OFFLINE;
|
|
1362
|
+
if (offline === "true" || offline === "1") return false;
|
|
1363
|
+
const isDev = process.env.MASTRA_DEV === "true" || process.env.MASTRA_DEV === "1";
|
|
1364
|
+
const setting = process.env.MASTRA_AUTO_REFRESH_PRICING;
|
|
1365
|
+
return setting === "true" || setting !== "false" && isDev;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Start a background refresh from models.dev, unless refresh is off, one is already
|
|
1369
|
+
* running, or the last attempt was less than an hour ago.
|
|
1370
|
+
*/
|
|
1371
|
+
function refreshGlobalPricing() {
|
|
1372
|
+
if (refreshInFlight || !isRefreshEnabled() || Date.now() - lastRefreshAttemptAt < MIN_REFRESH_ATTEMPT_INTERVAL_MS) return;
|
|
1373
|
+
refreshInFlight = true;
|
|
1374
|
+
lastRefreshAttemptAt = Date.now();
|
|
1375
|
+
fetchModelsDevPricing().catch(() => {}).finally(() => {
|
|
1376
|
+
refreshInFlight = false;
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
async function fetchModelsDevPricing() {
|
|
1380
|
+
const response = await fetch(MODELS_DEV_API_URL, {
|
|
1381
|
+
headers: refreshEtag ? { "if-none-match": refreshEtag } : void 0,
|
|
1382
|
+
signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS)
|
|
1383
|
+
});
|
|
1384
|
+
if (response.status === 304) {
|
|
1385
|
+
refreshedAt = Date.now();
|
|
1386
|
+
await touchCacheFile();
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
if (!response.ok) throw new Error(`models.dev responded with ${response.status}`);
|
|
1390
|
+
const costs = pickModelsDevCosts(await response.json());
|
|
1391
|
+
const pricingModels = modelsDevToPricingModels(costs);
|
|
1392
|
+
if (pricingModels.size < 1e3) throw new Error(`models.dev returned ${pricingModels.size} prices, expected at least ${MIN_PRICED_MODELS}`);
|
|
1393
|
+
globalRegistry = new PricingRegistry(pricingModels);
|
|
1394
|
+
refreshedAt = Date.now();
|
|
1395
|
+
refreshEtag = response.headers.get("etag") ?? void 0;
|
|
1396
|
+
await writeCacheFile(JSON.stringify({
|
|
1397
|
+
version: CACHE_VERSION,
|
|
1398
|
+
etag: refreshEtag,
|
|
1399
|
+
costs
|
|
1400
|
+
}));
|
|
1401
|
+
}
|
|
1402
|
+
/** The cache file's mtime records when its prices were last confirmed against models.dev. */
|
|
1403
|
+
function loadCachedPricingModels() {
|
|
1404
|
+
if (!isRefreshEnabled()) return null;
|
|
1405
|
+
try {
|
|
1406
|
+
const cacheFile = CACHE_FILE();
|
|
1407
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
1408
|
+
if (cached.version !== CACHE_VERSION) return null;
|
|
1409
|
+
const pricingModels = modelsDevToPricingModels(cached.costs);
|
|
1410
|
+
if (pricingModels.size < 1e3) return null;
|
|
1411
|
+
refreshEtag = typeof cached.etag === "string" ? cached.etag : void 0;
|
|
1412
|
+
refreshedAt = fs.statSync(cacheFile).mtimeMs;
|
|
1413
|
+
return pricingModels;
|
|
1414
|
+
} catch {
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
/** Write-to-temp-then-rename, like the model router's cache writes, so readers never see a partial file. */
|
|
1419
|
+
async function writeCacheFile(content) {
|
|
1420
|
+
try {
|
|
1421
|
+
const cacheFile = CACHE_FILE();
|
|
1422
|
+
const tempFile = `${cacheFile}.${process.pid}.${Date.now()}.tmp`;
|
|
1423
|
+
await fs.promises.mkdir(path.dirname(cacheFile), { recursive: true });
|
|
1424
|
+
await fs.promises.writeFile(tempFile, content, "utf-8");
|
|
1425
|
+
await fs.promises.rename(tempFile, cacheFile);
|
|
1426
|
+
} catch {}
|
|
1427
|
+
}
|
|
1428
|
+
async function touchCacheFile() {
|
|
1429
|
+
try {
|
|
1430
|
+
const now = /* @__PURE__ */ new Date();
|
|
1431
|
+
await fs.promises.utimes(CACHE_FILE(), now, now);
|
|
1432
|
+
} catch {}
|
|
1433
|
+
}
|
|
1207
1434
|
function loadPricingModels() {
|
|
1208
1435
|
if (cachedLoadError) return null;
|
|
1209
1436
|
try {
|
|
1210
|
-
return
|
|
1437
|
+
return modelsDevToPricingModels(JSON.parse(fs.readFileSync(resolvePricingModelPath(), "utf-8")));
|
|
1211
1438
|
} catch (error) {
|
|
1212
1439
|
cachedLoadError = error instanceof Error ? error.message : String(error);
|
|
1213
1440
|
return null;
|
|
1214
1441
|
}
|
|
1215
1442
|
}
|
|
1216
|
-
function parsePricingModelText(content) {
|
|
1217
|
-
const pricingModels = /* @__PURE__ */ new Map();
|
|
1218
|
-
for (const line of content.split("\n")) {
|
|
1219
|
-
const trimmed = line.trim();
|
|
1220
|
-
if (!trimmed) continue;
|
|
1221
|
-
const pricingModel = expandPricingModelRow(JSON.parse(trimmed));
|
|
1222
|
-
pricingModels.set(makePricingKey(pricingModel), pricingModel);
|
|
1223
|
-
}
|
|
1224
|
-
return pricingModels;
|
|
1225
|
-
}
|
|
1226
|
-
function expandRates(row, tier) {
|
|
1227
|
-
const rates = Object.fromEntries(Object.entries(tier.r).map(([meter, value]) => [MINIFIED_METER_TO_CANONICAL[meter], value.c]));
|
|
1228
|
-
const cacheWriteRate = rates.input_cache_write_tokens;
|
|
1229
|
-
if (row.m.includes("claude") && typeof cacheWriteRate === "number") {
|
|
1230
|
-
rates.input_cache_write_5m_tokens ??= cacheWriteRate;
|
|
1231
|
-
rates.input_cache_write_1h_tokens ??= cacheWriteRate * 1.6;
|
|
1232
|
-
}
|
|
1233
|
-
return rates;
|
|
1234
|
-
}
|
|
1235
|
-
function expandPricingModelRow(row) {
|
|
1236
|
-
return new PricingModel({
|
|
1237
|
-
id: row.i,
|
|
1238
|
-
provider: row.p,
|
|
1239
|
-
model: row.m,
|
|
1240
|
-
schema: row.s.v,
|
|
1241
|
-
currency: row.s.d.u,
|
|
1242
|
-
tiers: row.s.d.t.map((tier, index) => new PricingTier({
|
|
1243
|
-
index,
|
|
1244
|
-
when: tier.w?.map((condition) => ({
|
|
1245
|
-
field: MINIFIED_CONDITION_FIELD_TO_CANONICAL[condition.f],
|
|
1246
|
-
op: condition.op,
|
|
1247
|
-
value: condition.value
|
|
1248
|
-
})),
|
|
1249
|
-
rates: expandRates(row, tier)
|
|
1250
|
-
}))
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
1443
|
function resolvePricingModelPath() {
|
|
1254
1444
|
const packageRoot = getPackageRoot();
|
|
1255
1445
|
const candidates = [
|
|
@@ -1259,7 +1449,7 @@ function resolvePricingModelPath() {
|
|
|
1259
1449
|
path.join(process.cwd(), "src", "metrics", DATA_FILE_NAME)
|
|
1260
1450
|
];
|
|
1261
1451
|
for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
|
|
1262
|
-
throw new Error(`Unable to locate pricing data
|
|
1452
|
+
throw new Error(`Unable to locate pricing data at any known path: ${candidates.join(", ")}`);
|
|
1263
1453
|
}
|
|
1264
1454
|
function getPackageRoot() {
|
|
1265
1455
|
try {
|
|
@@ -2880,6 +3070,17 @@ function isSpanInternal(spanType, flags) {
|
|
|
2880
3070
|
}
|
|
2881
3071
|
}
|
|
2882
3072
|
/**
|
|
3073
|
+
* Copies the links whose IDs are valid W3C trace and span IDs (32 and 16 lowercase
|
|
3074
|
+
* hex characters, not all zeros); `undefined` when none remain.
|
|
3075
|
+
*/
|
|
3076
|
+
function validLinks(links) {
|
|
3077
|
+
const valid = links?.filter((link) => /^(?!0+$)[0-9a-f]{32}$/.test(link?.traceId ?? "") && /^(?!0+$)[0-9a-f]{16}$/.test(link?.spanId ?? ""));
|
|
3078
|
+
return valid?.length ? valid.map(({ traceId, spanId }) => ({
|
|
3079
|
+
traceId,
|
|
3080
|
+
spanId
|
|
3081
|
+
})) : void 0;
|
|
3082
|
+
}
|
|
3083
|
+
/**
|
|
2883
3084
|
* Get the external parent span ID from CreateSpanOptions.
|
|
2884
3085
|
*
|
|
2885
3086
|
* If the parent is internal or excluded from export (`excludeSpanTypes`),
|
|
@@ -2929,6 +3130,7 @@ var BaseSpan = class BaseSpan {
|
|
|
2929
3130
|
metadata;
|
|
2930
3131
|
requestContext;
|
|
2931
3132
|
tags;
|
|
3133
|
+
links;
|
|
2932
3134
|
traceState;
|
|
2933
3135
|
/** Entity type that created the span (e.g., agent, workflow) */
|
|
2934
3136
|
entityType;
|
|
@@ -2994,6 +3196,7 @@ var BaseSpan = class BaseSpan {
|
|
|
2994
3196
|
this.#openChildrenOwner = parent;
|
|
2995
3197
|
}
|
|
2996
3198
|
this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
|
|
3199
|
+
this.links = validLinks(options.links);
|
|
2997
3200
|
const entityParent = this.getParentSpan(false);
|
|
2998
3201
|
this.entityType = options.entityType ?? entityParent?.entityType;
|
|
2999
3202
|
this.entityId = options.entityId ?? entityParent?.entityId;
|
|
@@ -3194,6 +3397,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3194
3397
|
const hideInput = this.traceState?.hideInput ?? false;
|
|
3195
3398
|
const hideOutput = this.traceState?.hideOutput ?? false;
|
|
3196
3399
|
const nestedUnderParent = this.traceState?.nestedUnderParent ?? false;
|
|
3400
|
+
const links = validLinks(this.links);
|
|
3197
3401
|
return {
|
|
3198
3402
|
id: this.id,
|
|
3199
3403
|
traceId: this.traceId,
|
|
@@ -3216,6 +3420,7 @@ var BaseSpan = class BaseSpan {
|
|
|
3216
3420
|
parentSpanId: this.getParentSpanId(includeInternalSpans),
|
|
3217
3421
|
externalParentSpanId: this.getExportedExternalParentSpanId(includeInternalSpans),
|
|
3218
3422
|
...nestedUnderParent ? { nestedUnderParent: true } : {},
|
|
3423
|
+
...links ? { links } : {},
|
|
3219
3424
|
...this.isRootSpan && !nestedUnderParent && this.tags?.length ? { tags: this.tags } : {}
|
|
3220
3425
|
};
|
|
3221
3426
|
}
|
|
@@ -4804,6 +5009,7 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
4804
5009
|
spanId: cached.id,
|
|
4805
5010
|
parentSpanId: cached.parentSpanId,
|
|
4806
5011
|
externalParentSpanId: cached.externalParentSpanId,
|
|
5012
|
+
links: cached.links,
|
|
4807
5013
|
startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
|
|
4808
5014
|
input: cached.input,
|
|
4809
5015
|
attributes: cached.attributes,
|