agent-usage-all-in-one 0.3.0 → 0.4.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/README.md +11 -1
- package/README.zh-CN.md +7 -1
- package/dist/cli.js +788 -27
- package/dist/cli.js.map +1 -1
- package/dist/web/_app/immutable/assets/2.BWrFQDDF.css +1 -0
- package/dist/web/_app/immutable/chunks/3vuWay1s.js +3 -0
- package/dist/web/_app/immutable/chunks/CIxAYfti.js +1 -0
- package/dist/web/_app/immutable/chunks/DrMah8OK.js +1 -0
- package/dist/web/_app/immutable/entry/app.BJJ5ybn9.js +2 -0
- package/dist/web/_app/immutable/entry/start.C8pSj-JI.js +1 -0
- package/dist/web/_app/immutable/nodes/0.f03zL-fK.js +1 -0
- package/dist/web/_app/immutable/nodes/1.zxn5xzLP.js +1 -0
- package/dist/web/_app/immutable/nodes/2.CeZkCUZN.js +27 -0
- package/dist/web/_app/version.json +1 -1
- package/dist/web/brand/agent-usage-showcase.jpg +0 -0
- package/dist/web/brands/README.md +4 -4
- package/dist/web/index.html +30 -8
- package/docs/open-source.md +1 -1
- package/package.json +1 -1
- package/static/brand/agent-usage-showcase.jpg +0 -0
- package/static/brands/README.md +4 -4
- package/dist/web/_app/immutable/assets/2.BiUr1b86.css +0 -1
- package/dist/web/_app/immutable/chunks/BW9s3L2z.js +0 -3
- package/dist/web/_app/immutable/chunks/CwKqhBmI.js +0 -1
- package/dist/web/_app/immutable/entry/app.CJoUfLXD.js +0 -2
- package/dist/web/_app/immutable/entry/start.DEFna-Tq.js +0 -1
- package/dist/web/_app/immutable/nodes/0.DTcMU-UW.js +0 -1
- package/dist/web/_app/immutable/nodes/1.BRZSpfvp.js +0 -1
- package/dist/web/_app/immutable/nodes/2.BKHXaqdm.js +0 -43
- package/dist/web/brand/agent-usage-banner.svg +0 -27
- package/static/brand/agent-usage-banner.svg +0 -27
package/dist/cli.js
CHANGED
|
@@ -749,16 +749,15 @@ function priceObservation(providerId, observation, catalog, calculatedAt) {
|
|
|
749
749
|
ratePerMillion: entry.cacheWriteRatesPerMillion.oneHour
|
|
750
750
|
}
|
|
751
751
|
].filter((item) => item.tokens > 0) : null;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if (cacheWriteTokens > 0 && !exactCacheWriteItems) {
|
|
752
|
+
const cacheWriteFree = cacheWriteTokens > 0 && !exactCacheWriteItems && entry.ratesPerMillion["cache-write"] === null && !entry.cacheWriteRatesPerMillion;
|
|
753
|
+
const cacheWriteBillable = cacheWriteTokens > 0 && !exactCacheWriteItems && !cacheWriteFree;
|
|
754
|
+
if (cacheWriteBillable) {
|
|
756
755
|
billableTokens.push({ tokenKind: "cache-write", tokens: cacheWriteTokens });
|
|
757
756
|
}
|
|
758
757
|
const nonZero = billableTokens.filter((item) => item.tokens > 0);
|
|
759
758
|
if (nonZero.some((item) => entry.ratesPerMillion[item.tokenKind] === null)) {
|
|
760
759
|
return unavailable(
|
|
761
|
-
normalized.cacheWriteTokens > 0 ? "pricing-tier-ambiguous" : "token-kinds-incomplete"
|
|
760
|
+
normalized.cacheWriteTokens > 0 && !cacheWriteFree ? "pricing-tier-ambiguous" : "token-kinds-incomplete"
|
|
762
761
|
);
|
|
763
762
|
}
|
|
764
763
|
const lineItems = nonZero.map((item) => {
|
|
@@ -777,6 +776,14 @@ function priceObservation(providerId, observation, catalog, calculatedAt) {
|
|
|
777
776
|
}))
|
|
778
777
|
);
|
|
779
778
|
}
|
|
779
|
+
if (cacheWriteFree) {
|
|
780
|
+
lineItems.push({
|
|
781
|
+
tokenKind: "cache-write",
|
|
782
|
+
tokens: cacheWriteTokens,
|
|
783
|
+
ratePerMillion: 0,
|
|
784
|
+
amount: 0
|
|
785
|
+
});
|
|
786
|
+
}
|
|
780
787
|
const pricedTokens = lineItems.reduce((total, item) => total + item.tokens, 0);
|
|
781
788
|
if (pricedTokens !== normalized.recordedTokens) return unavailable("token-kinds-incomplete");
|
|
782
789
|
const amount = preciseMoney(lineItems.reduce((total, item) => total + item.amount, 0));
|
|
@@ -1184,6 +1191,382 @@ var init_retail_pricing = __esm({
|
|
|
1184
1191
|
}
|
|
1185
1192
|
});
|
|
1186
1193
|
|
|
1194
|
+
// src/core/plan-pricing.ts
|
|
1195
|
+
function buildBillingPeriod(subscription, summary, comparisonCurrency, rates) {
|
|
1196
|
+
if (!summary) return null;
|
|
1197
|
+
const start = new Date(summary.start).getTime();
|
|
1198
|
+
const end = new Date(summary.end).getTime();
|
|
1199
|
+
const observedThrough = new Date(summary.observedThrough).getTime();
|
|
1200
|
+
const totalDays = (end - start) / MILLISECONDS_PER_DAY;
|
|
1201
|
+
const elapsedDays = Math.max(0, Math.min(observedThrough, end) - start) / MILLISECONDS_PER_DAY;
|
|
1202
|
+
const periodCost = planMoneyAmount(
|
|
1203
|
+
preciseAmount(subscription.amount),
|
|
1204
|
+
subscription.currency,
|
|
1205
|
+
comparisonCurrency,
|
|
1206
|
+
// The period's own rate evidence first, so a short rolling window cannot
|
|
1207
|
+
// leave a cycle amount unconverted.
|
|
1208
|
+
[...summary.retailEquivalent.exchangeRates, ...rates],
|
|
1209
|
+
summary.observedThrough
|
|
1210
|
+
);
|
|
1211
|
+
const retailAmount = summary.retailEquivalent.status === "available" ? summary.retailEquivalent.amount : null;
|
|
1212
|
+
const breakEvenRatio = periodCost.amount !== null && periodCost.amount > 0 && retailAmount !== null ? preciseAmount(retailAmount / periodCost.amount) : null;
|
|
1213
|
+
return {
|
|
1214
|
+
start: summary.start,
|
|
1215
|
+
end: summary.end,
|
|
1216
|
+
elapsedDays: preciseAmount(elapsedDays),
|
|
1217
|
+
totalDays: preciseAmount(totalDays),
|
|
1218
|
+
progress: totalDays > 0 ? preciseAmount(Math.min(1, elapsedDays / totalDays)) : 0,
|
|
1219
|
+
periodCost,
|
|
1220
|
+
recordedTokens: summary.observationCount > 0 ? summary.recordedTokens : null,
|
|
1221
|
+
retailEquivalent: summary.retailEquivalent,
|
|
1222
|
+
breakEvenRatio,
|
|
1223
|
+
ratioBound: ratioBoundFor(breakEvenRatio, summary.retailEquivalent.pricingCoverage)
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
function planCatalogEntry(catalog, id) {
|
|
1227
|
+
return catalog.entries.find((entry) => entry.id === id) ?? null;
|
|
1228
|
+
}
|
|
1229
|
+
function planCatalogEntriesForDomain(catalog, providerId, billingDomainId) {
|
|
1230
|
+
return catalog.entries.filter(
|
|
1231
|
+
(entry) => entry.providerId === providerId && entry.billingDomainId === billingDomainId
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
function addMonths(date, months) {
|
|
1235
|
+
const day = date.getUTCDate();
|
|
1236
|
+
const shifted = new Date(
|
|
1237
|
+
Date.UTC(
|
|
1238
|
+
date.getUTCFullYear(),
|
|
1239
|
+
date.getUTCMonth() + months,
|
|
1240
|
+
1,
|
|
1241
|
+
date.getUTCHours(),
|
|
1242
|
+
date.getUTCMinutes(),
|
|
1243
|
+
date.getUTCSeconds(),
|
|
1244
|
+
date.getUTCMilliseconds()
|
|
1245
|
+
)
|
|
1246
|
+
);
|
|
1247
|
+
const lastDayOfMonth = new Date(
|
|
1248
|
+
Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth() + 1, 0)
|
|
1249
|
+
).getUTCDate();
|
|
1250
|
+
shifted.setUTCDate(Math.min(day, lastDayOfMonth));
|
|
1251
|
+
return shifted;
|
|
1252
|
+
}
|
|
1253
|
+
function billingPeriodContaining(anchorDate, billingPeriod, at) {
|
|
1254
|
+
const anchor = new Date(anchorDate.length === 10 ? `${anchorDate}T00:00:00.000Z` : anchorDate);
|
|
1255
|
+
if (Number.isNaN(anchor.getTime())) return null;
|
|
1256
|
+
const step = billingPeriod === "monthly" ? 1 : 12;
|
|
1257
|
+
const monthsApart = (at.getUTCFullYear() - anchor.getUTCFullYear()) * 12 + (at.getUTCMonth() - anchor.getUTCMonth());
|
|
1258
|
+
let index = Math.floor(monthsApart / step);
|
|
1259
|
+
let start = addMonths(anchor, index * step);
|
|
1260
|
+
while (start.getTime() > at.getTime()) {
|
|
1261
|
+
index -= 1;
|
|
1262
|
+
start = addMonths(anchor, index * step);
|
|
1263
|
+
}
|
|
1264
|
+
let end = addMonths(anchor, (index + 1) * step);
|
|
1265
|
+
while (end.getTime() <= at.getTime()) {
|
|
1266
|
+
index += 1;
|
|
1267
|
+
start = end;
|
|
1268
|
+
end = addMonths(anchor, (index + 1) * step);
|
|
1269
|
+
}
|
|
1270
|
+
return { start, end };
|
|
1271
|
+
}
|
|
1272
|
+
function windowDays(start, end) {
|
|
1273
|
+
const span = new Date(end).getTime() - new Date(start).getTime();
|
|
1274
|
+
return span > 0 ? span / MILLISECONDS_PER_DAY : 0;
|
|
1275
|
+
}
|
|
1276
|
+
function proratePlanPrice(amount, billingPeriod, days) {
|
|
1277
|
+
return amount * days / PLAN_PERIOD_DAYS[billingPeriod];
|
|
1278
|
+
}
|
|
1279
|
+
function convertPlanAmount(amount, currency, comparisonCurrency, rates, end) {
|
|
1280
|
+
if (currency.toUpperCase() === comparisonCurrency.toUpperCase()) {
|
|
1281
|
+
return { amount, reason: null, rate: null };
|
|
1282
|
+
}
|
|
1283
|
+
const rate = rates.find(
|
|
1284
|
+
(candidate) => candidate.baseCurrency.toUpperCase() === currency.toUpperCase() && candidate.quoteCurrency.toUpperCase() === comparisonCurrency.toUpperCase()
|
|
1285
|
+
);
|
|
1286
|
+
if (!rate) return { amount: null, reason: "missing-rate", rate: null };
|
|
1287
|
+
if (new Date(end).getTime() - new Date(rate.observedAt).getTime() > STALE_RATE_MILLISECONDS) {
|
|
1288
|
+
return { amount: null, reason: "stale-rate", rate };
|
|
1289
|
+
}
|
|
1290
|
+
return { amount: amount * rate.rate, reason: null, rate };
|
|
1291
|
+
}
|
|
1292
|
+
function preciseAmount(value) {
|
|
1293
|
+
return Number(value.toPrecision(12));
|
|
1294
|
+
}
|
|
1295
|
+
function planMoneyAmount(nativeAmount, nativeCurrency, comparisonCurrency, rates, end) {
|
|
1296
|
+
const converted = convertPlanAmount(nativeAmount, nativeCurrency, comparisonCurrency, rates, end);
|
|
1297
|
+
return {
|
|
1298
|
+
status: converted.amount === null ? "unavailable" : "available",
|
|
1299
|
+
amount: converted.amount === null ? null : preciseAmount(converted.amount),
|
|
1300
|
+
nativeAmount,
|
|
1301
|
+
nativeCurrency,
|
|
1302
|
+
comparisonCurrency,
|
|
1303
|
+
conversionUnavailableReason: converted.reason,
|
|
1304
|
+
exchangeRates: converted.rate ? [converted.rate] : []
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
function ratioBoundFor(ratio, pricingCoverage) {
|
|
1308
|
+
if (ratio === null) return "unavailable";
|
|
1309
|
+
return pricingCoverage !== null && pricingCoverage < 0.999 ? "lower" : "exact";
|
|
1310
|
+
}
|
|
1311
|
+
function unitPricePerMillion(amount, tokens) {
|
|
1312
|
+
if (amount === null || tokens <= 0) return null;
|
|
1313
|
+
return preciseAmount(amount / (tokens / 1e6));
|
|
1314
|
+
}
|
|
1315
|
+
function buildWorkbenchPlanValue(options) {
|
|
1316
|
+
const { comparisonCurrency, start, end, rates } = options;
|
|
1317
|
+
const days = windowDays(start, end);
|
|
1318
|
+
const subscriptionByDomain = new Map(
|
|
1319
|
+
options.subscriptions.map((subscription) => [
|
|
1320
|
+
`${subscription.providerId}:${subscription.billingDomainId}`,
|
|
1321
|
+
subscription
|
|
1322
|
+
])
|
|
1323
|
+
);
|
|
1324
|
+
const entries = [];
|
|
1325
|
+
const metered = [];
|
|
1326
|
+
const unconfigured = [];
|
|
1327
|
+
for (const domain of options.domains) {
|
|
1328
|
+
const subscription = subscriptionByDomain.get(`${domain.providerId}:${domain.billingDomainId}`);
|
|
1329
|
+
if (!subscription) {
|
|
1330
|
+
if (domain.actualCost.records > 0) {
|
|
1331
|
+
metered.push({
|
|
1332
|
+
providerId: domain.providerId,
|
|
1333
|
+
providerDisplayName: domain.providerDisplayName,
|
|
1334
|
+
billingDomainId: domain.billingDomainId,
|
|
1335
|
+
billingDomainDisplayName: domain.billingDomainDisplayName,
|
|
1336
|
+
recordedTokens: domain.observationCount > 0 ? domain.recordedTokens : null,
|
|
1337
|
+
actualCost: domain.actualCost,
|
|
1338
|
+
retailEquivalent: domain.retailEquivalent
|
|
1339
|
+
});
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (domain.observationCount > 0) {
|
|
1343
|
+
unconfigured.push({
|
|
1344
|
+
providerId: domain.providerId,
|
|
1345
|
+
providerDisplayName: domain.providerDisplayName,
|
|
1346
|
+
billingDomainId: domain.billingDomainId,
|
|
1347
|
+
billingDomainDisplayName: domain.billingDomainDisplayName,
|
|
1348
|
+
recordedTokens: domain.recordedTokens
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
const windowPlanCost = planMoneyAmount(
|
|
1354
|
+
preciseAmount(proratePlanPrice(subscription.amount, subscription.billingPeriod, days)),
|
|
1355
|
+
subscription.currency,
|
|
1356
|
+
comparisonCurrency,
|
|
1357
|
+
rates,
|
|
1358
|
+
end
|
|
1359
|
+
);
|
|
1360
|
+
const retail = domain.retailEquivalent;
|
|
1361
|
+
const retailAmount = retail.status === "available" ? retail.amount : null;
|
|
1362
|
+
const valueRatio = windowPlanCost.amount !== null && windowPlanCost.amount > 0 && retailAmount !== null ? preciseAmount(retailAmount / windowPlanCost.amount) : null;
|
|
1363
|
+
const ratioBound = ratioBoundFor(valueRatio, retail.pricingCoverage);
|
|
1364
|
+
const billingPeriod = buildBillingPeriod(
|
|
1365
|
+
subscription,
|
|
1366
|
+
options.billingPeriods?.get(`${domain.providerId}:${domain.billingDomainId}`),
|
|
1367
|
+
comparisonCurrency,
|
|
1368
|
+
rates
|
|
1369
|
+
);
|
|
1370
|
+
entries.push({
|
|
1371
|
+
providerId: domain.providerId,
|
|
1372
|
+
providerDisplayName: domain.providerDisplayName,
|
|
1373
|
+
billingDomainId: domain.billingDomainId,
|
|
1374
|
+
billingDomainDisplayName: domain.billingDomainDisplayName,
|
|
1375
|
+
includedInHeadline: domain.includedInHeadline,
|
|
1376
|
+
plan: {
|
|
1377
|
+
planId: subscription.planId,
|
|
1378
|
+
displayName: subscription.displayName,
|
|
1379
|
+
amount: subscription.amount,
|
|
1380
|
+
currency: subscription.currency,
|
|
1381
|
+
billingPeriod: subscription.billingPeriod,
|
|
1382
|
+
anchorDate: subscription.anchorDate,
|
|
1383
|
+
priceSource: subscription.priceSource,
|
|
1384
|
+
updatedAt: subscription.updatedAt
|
|
1385
|
+
},
|
|
1386
|
+
windowDays: preciseAmount(days),
|
|
1387
|
+
windowPlanCost,
|
|
1388
|
+
billingPeriod,
|
|
1389
|
+
recordedTokens: domain.observationCount > 0 ? domain.recordedTokens : null,
|
|
1390
|
+
retailEquivalent: retail,
|
|
1391
|
+
valueRatio,
|
|
1392
|
+
ratioBound,
|
|
1393
|
+
status: valueRatio === null ? "unavailable" : ratioBound === "lower" ? "partial" : "available",
|
|
1394
|
+
effectiveUnitPrice: unitPricePerMillion(windowPlanCost.amount, domain.recordedTokens),
|
|
1395
|
+
retailUnitPrice: unitPricePerMillion(retailAmount, retail.pricedTokens),
|
|
1396
|
+
pricingCoverage: retail.pricingCoverage,
|
|
1397
|
+
authorities: domain.authorities,
|
|
1398
|
+
lastObservedAt: domain.lastObservedAt
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
const rank = (entry) => entry.valueRatio ?? -1;
|
|
1402
|
+
entries.sort(
|
|
1403
|
+
(left, right) => rank(right) - rank(left) || `${left.providerId}:${left.billingDomainId}`.localeCompare(
|
|
1404
|
+
`${right.providerId}:${right.billingDomainId}`
|
|
1405
|
+
)
|
|
1406
|
+
);
|
|
1407
|
+
metered.sort(
|
|
1408
|
+
(left, right) => `${left.providerId}:${left.billingDomainId}`.localeCompare(
|
|
1409
|
+
`${right.providerId}:${right.billingDomainId}`
|
|
1410
|
+
)
|
|
1411
|
+
);
|
|
1412
|
+
unconfigured.sort(
|
|
1413
|
+
(left, right) => right.recordedTokens - left.recordedTokens || `${left.providerId}:${left.billingDomainId}`.localeCompare(
|
|
1414
|
+
`${right.providerId}:${right.billingDomainId}`
|
|
1415
|
+
)
|
|
1416
|
+
);
|
|
1417
|
+
return {
|
|
1418
|
+
windowDays: preciseAmount(days),
|
|
1419
|
+
comparisonCurrency,
|
|
1420
|
+
catalogVersion: SUBSCRIPTION_PLAN_CATALOG.version,
|
|
1421
|
+
entries,
|
|
1422
|
+
meteredDomains: metered,
|
|
1423
|
+
unconfiguredDomains: unconfigured
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
var PLAN_PERIOD_DAYS, MILLISECONDS_PER_DAY, STALE_RATE_MILLISECONDS, SUBSCRIPTION_PLAN_CATALOG;
|
|
1427
|
+
var init_plan_pricing = __esm({
|
|
1428
|
+
"src/core/plan-pricing.ts"() {
|
|
1429
|
+
"use strict";
|
|
1430
|
+
PLAN_PERIOD_DAYS = {
|
|
1431
|
+
monthly: 365.25 / 12,
|
|
1432
|
+
annual: 365.25
|
|
1433
|
+
};
|
|
1434
|
+
MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
1435
|
+
STALE_RATE_MILLISECONDS = 7 * MILLISECONDS_PER_DAY;
|
|
1436
|
+
SUBSCRIPTION_PLAN_CATALOG = {
|
|
1437
|
+
version: "2026-08-30",
|
|
1438
|
+
entries: [
|
|
1439
|
+
{
|
|
1440
|
+
id: "claude-pro-monthly",
|
|
1441
|
+
providerId: "claude-code",
|
|
1442
|
+
billingDomainId: "subscription",
|
|
1443
|
+
displayName: "Claude Pro",
|
|
1444
|
+
amount: 20,
|
|
1445
|
+
currency: "USD",
|
|
1446
|
+
billingPeriod: "monthly",
|
|
1447
|
+
source: {
|
|
1448
|
+
title: "Claude plans and pricing",
|
|
1449
|
+
url: "https://claude.com/pricing",
|
|
1450
|
+
retrievedAt: "2026-08-30"
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
id: "claude-pro-annual",
|
|
1455
|
+
providerId: "claude-code",
|
|
1456
|
+
billingDomainId: "subscription",
|
|
1457
|
+
displayName: "Claude Pro (annual billing)",
|
|
1458
|
+
amount: 17 * 12,
|
|
1459
|
+
currency: "USD",
|
|
1460
|
+
billingPeriod: "annual",
|
|
1461
|
+
source: {
|
|
1462
|
+
title: "Claude plans and pricing",
|
|
1463
|
+
url: "https://claude.com/pricing",
|
|
1464
|
+
retrievedAt: "2026-08-30"
|
|
1465
|
+
}
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
id: "claude-max-5x",
|
|
1469
|
+
providerId: "claude-code",
|
|
1470
|
+
billingDomainId: "subscription",
|
|
1471
|
+
displayName: "Claude Max 5x",
|
|
1472
|
+
amount: 100,
|
|
1473
|
+
currency: "USD",
|
|
1474
|
+
billingPeriod: "monthly",
|
|
1475
|
+
source: {
|
|
1476
|
+
title: "What is the Max plan?",
|
|
1477
|
+
url: "https://support.claude.com/en/articles/11049741-what-is-the-max-plan",
|
|
1478
|
+
retrievedAt: "2026-08-30"
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
id: "claude-max-20x",
|
|
1483
|
+
providerId: "claude-code",
|
|
1484
|
+
billingDomainId: "subscription",
|
|
1485
|
+
displayName: "Claude Max 20x",
|
|
1486
|
+
amount: 200,
|
|
1487
|
+
currency: "USD",
|
|
1488
|
+
billingPeriod: "monthly",
|
|
1489
|
+
source: {
|
|
1490
|
+
title: "What is the Max plan?",
|
|
1491
|
+
url: "https://support.claude.com/en/articles/11049741-what-is-the-max-plan",
|
|
1492
|
+
retrievedAt: "2026-08-30"
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
{
|
|
1496
|
+
id: "chatgpt-plus",
|
|
1497
|
+
providerId: "codex",
|
|
1498
|
+
billingDomainId: "subscription",
|
|
1499
|
+
displayName: "ChatGPT Plus",
|
|
1500
|
+
amount: 20,
|
|
1501
|
+
currency: "USD",
|
|
1502
|
+
billingPeriod: "monthly",
|
|
1503
|
+
source: {
|
|
1504
|
+
title: "ChatGPT pricing",
|
|
1505
|
+
url: "https://chatgpt.com/pricing/",
|
|
1506
|
+
retrievedAt: "2026-08-30"
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
id: "chatgpt-pro-100",
|
|
1511
|
+
providerId: "codex",
|
|
1512
|
+
billingDomainId: "subscription",
|
|
1513
|
+
displayName: "ChatGPT Pro (5x)",
|
|
1514
|
+
amount: 100,
|
|
1515
|
+
currency: "USD",
|
|
1516
|
+
billingPeriod: "monthly",
|
|
1517
|
+
source: {
|
|
1518
|
+
title: "About ChatGPT Pro tiers",
|
|
1519
|
+
url: "https://help.openai.com/en/articles/9793128-about-chatgpt-pro-tiers",
|
|
1520
|
+
retrievedAt: "2026-08-30"
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
id: "chatgpt-pro-200",
|
|
1525
|
+
providerId: "codex",
|
|
1526
|
+
billingDomainId: "subscription",
|
|
1527
|
+
displayName: "ChatGPT Pro (20x)",
|
|
1528
|
+
amount: 200,
|
|
1529
|
+
currency: "USD",
|
|
1530
|
+
billingPeriod: "monthly",
|
|
1531
|
+
source: {
|
|
1532
|
+
title: "About ChatGPT Pro tiers",
|
|
1533
|
+
url: "https://help.openai.com/en/articles/9793128-about-chatgpt-pro-tiers",
|
|
1534
|
+
retrievedAt: "2026-08-30"
|
|
1535
|
+
}
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
id: "chatgpt-business-seat",
|
|
1539
|
+
providerId: "codex",
|
|
1540
|
+
billingDomainId: "subscription",
|
|
1541
|
+
displayName: "ChatGPT Business (one seat)",
|
|
1542
|
+
amount: 25,
|
|
1543
|
+
currency: "USD",
|
|
1544
|
+
billingPeriod: "monthly",
|
|
1545
|
+
source: {
|
|
1546
|
+
title: "ChatGPT Business pricing",
|
|
1547
|
+
url: "https://openai.com/business/chatgpt-pricing/",
|
|
1548
|
+
retrievedAt: "2026-08-30"
|
|
1549
|
+
}
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
id: "opencode-go-monthly",
|
|
1553
|
+
providerId: "opencode-go",
|
|
1554
|
+
billingDomainId: "go-subscription",
|
|
1555
|
+
displayName: "OpenCode Go",
|
|
1556
|
+
amount: 10,
|
|
1557
|
+
currency: "USD",
|
|
1558
|
+
billingPeriod: "monthly",
|
|
1559
|
+
source: {
|
|
1560
|
+
title: "OpenCode Go",
|
|
1561
|
+
url: "https://opencode.ai/go",
|
|
1562
|
+
retrievedAt: "2026-08-30"
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
]
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1187
1570
|
// src/core/usage-application.ts
|
|
1188
1571
|
function safeConnectorFailure(error) {
|
|
1189
1572
|
if (error instanceof Error && "code" in error && typeof error.code === "string" && "recovery" in error && typeof error.recovery === "string") {
|
|
@@ -1289,6 +1672,12 @@ async function withTimeout(promise, timeoutMs, message) {
|
|
|
1289
1672
|
function notificationLevel(value) {
|
|
1290
1673
|
return value === "5" ? 2 : value === "20" ? 1 : 0;
|
|
1291
1674
|
}
|
|
1675
|
+
function pickAgentProviderIndex(overview) {
|
|
1676
|
+
return {
|
|
1677
|
+
generatedAt: overview.generatedAt,
|
|
1678
|
+
providers: overview.providers.filter((provider) => provider.id !== "opencode").map(({ id, displayName }) => ({ id, displayName }))
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1292
1681
|
var UsageApplication;
|
|
1293
1682
|
var init_usage_application = __esm({
|
|
1294
1683
|
"src/core/usage-application.ts"() {
|
|
@@ -1297,6 +1686,7 @@ var init_usage_application = __esm({
|
|
|
1297
1686
|
init_redaction();
|
|
1298
1687
|
init_usage_export();
|
|
1299
1688
|
init_retail_pricing();
|
|
1689
|
+
init_plan_pricing();
|
|
1300
1690
|
UsageApplication = class {
|
|
1301
1691
|
#repository;
|
|
1302
1692
|
#connectors;
|
|
@@ -1310,10 +1700,13 @@ var init_usage_application = __esm({
|
|
|
1310
1700
|
#notifier;
|
|
1311
1701
|
#startAtLoginManager;
|
|
1312
1702
|
#priceCatalog;
|
|
1703
|
+
#planCatalog;
|
|
1313
1704
|
#refreshPromise = null;
|
|
1314
1705
|
#refreshMode = null;
|
|
1315
1706
|
#backgroundPromise = null;
|
|
1316
1707
|
#queuedHardRebuildPromise = null;
|
|
1708
|
+
#queuedUserProcessingPromise = null;
|
|
1709
|
+
#userInitiatedProcessing = false;
|
|
1317
1710
|
#databaseWriteQueue = Promise.resolve();
|
|
1318
1711
|
#processingStatus;
|
|
1319
1712
|
constructor(options) {
|
|
@@ -1329,6 +1722,7 @@ var init_usage_application = __esm({
|
|
|
1329
1722
|
this.#notifier = options.notifier;
|
|
1330
1723
|
this.#startAtLoginManager = options.startAtLoginManager;
|
|
1331
1724
|
this.#priceCatalog = options.priceCatalog === void 0 ? OFFICIAL_PRICING_CATALOG : options.priceCatalog;
|
|
1725
|
+
this.#planCatalog = options.planCatalog ?? SUBSCRIPTION_PLAN_CATALOG;
|
|
1332
1726
|
this.#processingStatus = createProcessingStatus(this.#clock().toISOString(), false);
|
|
1333
1727
|
}
|
|
1334
1728
|
async #backfillRetailCosts(mode) {
|
|
@@ -1375,40 +1769,50 @@ var init_usage_application = __esm({
|
|
|
1375
1769
|
getProcessingStatus() {
|
|
1376
1770
|
return structuredClone(this.#processingStatus);
|
|
1377
1771
|
}
|
|
1378
|
-
startBackgroundProcessing() {
|
|
1379
|
-
return this.#startProcessing("incremental");
|
|
1772
|
+
startBackgroundProcessing(options = {}) {
|
|
1773
|
+
return this.#startProcessing("incremental", options.userInitiated === true);
|
|
1380
1774
|
}
|
|
1381
1775
|
startHardRebuild() {
|
|
1382
|
-
return this.#startProcessing("hard-rebuild");
|
|
1776
|
+
return this.#startProcessing("hard-rebuild", true);
|
|
1383
1777
|
}
|
|
1384
|
-
#startProcessing(mode) {
|
|
1778
|
+
#startProcessing(mode, userInitiated) {
|
|
1385
1779
|
if (this.#backgroundPromise) {
|
|
1386
1780
|
if (mode === "hard-rebuild" && !this.#processingStatus.hardRebuild) {
|
|
1387
1781
|
this.#queuedHardRebuildPromise ??= this.#backgroundPromise.then(
|
|
1388
|
-
() => this.#startProcessing("hard-rebuild"),
|
|
1389
|
-
() => this.#startProcessing("hard-rebuild")
|
|
1782
|
+
() => this.#startProcessing("hard-rebuild", true),
|
|
1783
|
+
() => this.#startProcessing("hard-rebuild", true)
|
|
1390
1784
|
).finally(() => {
|
|
1391
1785
|
this.#queuedHardRebuildPromise = null;
|
|
1392
1786
|
});
|
|
1393
1787
|
return this.#queuedHardRebuildPromise;
|
|
1394
1788
|
}
|
|
1789
|
+
if (userInitiated && !this.#userInitiatedProcessing) {
|
|
1790
|
+
this.#queuedUserProcessingPromise ??= this.#backgroundPromise.then(
|
|
1791
|
+
() => this.#startProcessing(mode, true),
|
|
1792
|
+
() => this.#startProcessing(mode, true)
|
|
1793
|
+
).finally(() => {
|
|
1794
|
+
this.#queuedUserProcessingPromise = null;
|
|
1795
|
+
});
|
|
1796
|
+
return this.#queuedUserProcessingPromise;
|
|
1797
|
+
}
|
|
1395
1798
|
return this.#backgroundPromise;
|
|
1396
1799
|
}
|
|
1800
|
+
this.#userInitiatedProcessing = userInitiated || mode === "hard-rebuild";
|
|
1397
1801
|
this.#processingStatus = createProcessingStatus(
|
|
1398
1802
|
this.#clock().toISOString(),
|
|
1399
1803
|
mode === "hard-rebuild"
|
|
1400
1804
|
);
|
|
1401
|
-
this.#backgroundPromise = this.#runProcessing(mode).finally(
|
|
1402
|
-
|
|
1403
|
-
|
|
1805
|
+
this.#backgroundPromise = this.#runProcessing(mode, this.#userInitiatedProcessing).finally(
|
|
1806
|
+
() => {
|
|
1807
|
+
this.#backgroundPromise = null;
|
|
1808
|
+
this.#userInitiatedProcessing = false;
|
|
1809
|
+
}
|
|
1810
|
+
);
|
|
1404
1811
|
return this.#backgroundPromise;
|
|
1405
1812
|
}
|
|
1406
|
-
async #runProcessing(mode) {
|
|
1813
|
+
async #runProcessing(mode, userInitiated) {
|
|
1407
1814
|
await this.#runModule("discovery", () => this.discoverConnectors().then(() => void 0));
|
|
1408
|
-
await this.#runModule(
|
|
1409
|
-
"usage",
|
|
1410
|
-
() => this.refresh({ userInitiated: mode === "hard-rebuild", mode })
|
|
1411
|
-
);
|
|
1815
|
+
await this.#runModule("usage", () => this.refresh({ userInitiated, mode }));
|
|
1412
1816
|
await this.#runModule(
|
|
1413
1817
|
"pricing",
|
|
1414
1818
|
() => this.#queueDatabaseWrite(async () => {
|
|
@@ -1619,6 +2023,83 @@ var init_usage_application = __esm({
|
|
|
1619
2023
|
connectors: this.#repository.getConnectorRuntimeStates()
|
|
1620
2024
|
};
|
|
1621
2025
|
}
|
|
2026
|
+
async getPlanSettings() {
|
|
2027
|
+
return {
|
|
2028
|
+
catalogVersion: this.#planCatalog.version,
|
|
2029
|
+
domains: this.#planEligibleDomains(),
|
|
2030
|
+
subscriptions: this.#repository.getPlanSubscriptions()
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
async updatePlanSubscription(input) {
|
|
2034
|
+
const domain = this.#planEligibleDomains().find(
|
|
2035
|
+
(candidate) => candidate.providerId === input.providerId && candidate.billingDomainId === input.billingDomainId
|
|
2036
|
+
);
|
|
2037
|
+
if (!domain) throw new Error("Unknown subscription billing domain");
|
|
2038
|
+
if (input.plan === null) {
|
|
2039
|
+
this.#repository.deletePlanSubscription(input.providerId, input.billingDomainId);
|
|
2040
|
+
return this.getPlanSettings();
|
|
2041
|
+
}
|
|
2042
|
+
const preset = input.plan.planId ? planCatalogEntry(this.#planCatalog, input.plan.planId) : null;
|
|
2043
|
+
if (input.plan.planId && !preset) throw new Error("Unknown plan preset");
|
|
2044
|
+
if (preset && (preset.providerId !== input.providerId || preset.billingDomainId !== input.billingDomainId)) {
|
|
2045
|
+
throw new Error("Plan preset belongs to another billing domain");
|
|
2046
|
+
}
|
|
2047
|
+
const amount = input.plan.amount ?? preset?.amount;
|
|
2048
|
+
if (amount === void 0) throw new Error("A plan price is required");
|
|
2049
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
2050
|
+
throw new Error("A plan price must be a positive amount");
|
|
2051
|
+
}
|
|
2052
|
+
const currency = (input.plan.currency ?? preset?.currency ?? "USD").toUpperCase();
|
|
2053
|
+
if (!/^[A-Z]{3}$/.test(currency)) throw new Error("A plan currency must be a 3-letter code");
|
|
2054
|
+
const billingPeriod = input.plan.billingPeriod ?? preset?.billingPeriod ?? "monthly";
|
|
2055
|
+
const anchorDate = input.plan.anchorDate ?? null;
|
|
2056
|
+
if (anchorDate !== null && Number.isNaN((/* @__PURE__ */ new Date(`${anchorDate}T00:00:00.000Z`)).getTime())) {
|
|
2057
|
+
throw new Error("A renewal date must be a calendar date");
|
|
2058
|
+
}
|
|
2059
|
+
const overridesPreset = preset !== null && (amount !== preset.amount || currency !== preset.currency.toUpperCase() || billingPeriod !== preset.billingPeriod);
|
|
2060
|
+
this.#repository.savePlanSubscription({
|
|
2061
|
+
providerId: input.providerId,
|
|
2062
|
+
billingDomainId: input.billingDomainId,
|
|
2063
|
+
planId: preset?.id ?? null,
|
|
2064
|
+
displayName: preset?.displayName ?? "",
|
|
2065
|
+
amount,
|
|
2066
|
+
currency,
|
|
2067
|
+
billingPeriod,
|
|
2068
|
+
anchorDate,
|
|
2069
|
+
priceSource: preset && !overridesPreset ? "catalog-preset" : "user-entered",
|
|
2070
|
+
updatedAt: this.#clock().toISOString()
|
|
2071
|
+
});
|
|
2072
|
+
return this.getPlanSettings();
|
|
2073
|
+
}
|
|
2074
|
+
/**
|
|
2075
|
+
* A billing domain can carry a plan price when its connector does not report
|
|
2076
|
+
* an actual metered charge. Metered domains keep their own billed amounts and
|
|
2077
|
+
* never receive a declared subscription price.
|
|
2078
|
+
*/
|
|
2079
|
+
#planEligibleDomains() {
|
|
2080
|
+
const domains = /* @__PURE__ */ new Map();
|
|
2081
|
+
for (const definition of this.#connectorDefinitions) {
|
|
2082
|
+
if (definition.expectedCoverage?.includes("actual-cost")) continue;
|
|
2083
|
+
const key = `${definition.target.provider.id}:${definition.target.billingDomain.id}`;
|
|
2084
|
+
if (domains.has(key)) continue;
|
|
2085
|
+
domains.set(key, {
|
|
2086
|
+
providerId: definition.target.provider.id,
|
|
2087
|
+
providerDisplayName: definition.target.provider.displayName,
|
|
2088
|
+
billingDomainId: definition.target.billingDomain.id,
|
|
2089
|
+
billingDomainDisplayName: definition.target.billingDomain.displayName,
|
|
2090
|
+
presets: planCatalogEntriesForDomain(
|
|
2091
|
+
this.#planCatalog,
|
|
2092
|
+
definition.target.provider.id,
|
|
2093
|
+
definition.target.billingDomain.id
|
|
2094
|
+
)
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
return [...domains.values()].sort(
|
|
2098
|
+
(left, right) => `${left.providerId}:${left.billingDomainId}`.localeCompare(
|
|
2099
|
+
`${right.providerId}:${right.billingDomainId}`
|
|
2100
|
+
)
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
1622
2103
|
async #sendNotificationTransitions() {
|
|
1623
2104
|
if (!this.#notifier) return;
|
|
1624
2105
|
const overview = this.#repository.getOverview(this.#clock());
|
|
@@ -1767,6 +2248,14 @@ var init_usage_application = __esm({
|
|
|
1767
2248
|
async getOverview(query = {}) {
|
|
1768
2249
|
return this.#repository.getOverview(this.#clock(), query);
|
|
1769
2250
|
}
|
|
2251
|
+
async getAgentProviderIndex() {
|
|
2252
|
+
const now = this.#clock();
|
|
2253
|
+
return this.#repository.getAgentProviderIndex?.(now) ?? pickAgentProviderIndex(this.#repository.getOverview(now));
|
|
2254
|
+
}
|
|
2255
|
+
async getProviderOverview(providerId, query = {}) {
|
|
2256
|
+
const now = this.#clock();
|
|
2257
|
+
return this.#repository.getProviderOverview?.(now, providerId, query) ?? this.#repository.getOverview(now, query).providers.find((provider) => provider.id === providerId) ?? null;
|
|
2258
|
+
}
|
|
1770
2259
|
async exportUsage(request) {
|
|
1771
2260
|
const overview = this.#repository.getOverview(this.#clock(), request);
|
|
1772
2261
|
const accountIdentifiers = request.includeAccountIdentifiers ? this.#repository.getProviderAccountIdentifiers() : {};
|
|
@@ -5041,6 +5530,25 @@ async function startLocalServer(options) {
|
|
|
5041
5530
|
sendOtlpProtobuf(response, 200);
|
|
5042
5531
|
return;
|
|
5043
5532
|
}
|
|
5533
|
+
if (request.method === "GET" && requestUrl.pathname === "/api/overview/providers") {
|
|
5534
|
+
sendJson(response, 200, await options.application.getAgentProviderIndex());
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5537
|
+
if (request.method === "GET" && requestUrl.pathname.startsWith("/api/overview/providers/")) {
|
|
5538
|
+
const providerId = decodeURIComponent(
|
|
5539
|
+
requestUrl.pathname.slice("/api/overview/providers/".length)
|
|
5540
|
+
);
|
|
5541
|
+
const provider = await options.application.getProviderOverview(
|
|
5542
|
+
providerId,
|
|
5543
|
+
parseUsageQuery(requestUrl)
|
|
5544
|
+
);
|
|
5545
|
+
if (!provider) {
|
|
5546
|
+
sendJson(response, 404, { error: "provider-not-found" });
|
|
5547
|
+
return;
|
|
5548
|
+
}
|
|
5549
|
+
sendJson(response, 200, provider);
|
|
5550
|
+
return;
|
|
5551
|
+
}
|
|
5044
5552
|
if (request.method === "GET" && requestUrl.pathname === "/api/overview") {
|
|
5045
5553
|
const query = parseUsageQuery(requestUrl);
|
|
5046
5554
|
sendJson(response, 200, await options.application.getOverview(query));
|
|
@@ -5051,6 +5559,13 @@ async function startLocalServer(options) {
|
|
|
5051
5559
|
sendJson(response, 403, { error: "invalid-origin" });
|
|
5052
5560
|
return;
|
|
5053
5561
|
}
|
|
5562
|
+
if (requestUrl.searchParams.get("background") === "true") {
|
|
5563
|
+
void options.application.startBackgroundProcessing({
|
|
5564
|
+
userInitiated: requestUrl.searchParams.get("mode") !== "automatic"
|
|
5565
|
+
});
|
|
5566
|
+
sendJson(response, 202, { accepted: true });
|
|
5567
|
+
return;
|
|
5568
|
+
}
|
|
5054
5569
|
await options.application.refresh({
|
|
5055
5570
|
userInitiated: requestUrl.searchParams.get("mode") !== "automatic"
|
|
5056
5571
|
});
|
|
@@ -5103,6 +5618,19 @@ async function startLocalServer(options) {
|
|
|
5103
5618
|
sendJson(response, 200, await options.application.clearData(input));
|
|
5104
5619
|
return;
|
|
5105
5620
|
}
|
|
5621
|
+
if (request.method === "GET" && requestUrl.pathname === "/api/plans") {
|
|
5622
|
+
sendJson(response, 200, await options.application.getPlanSettings());
|
|
5623
|
+
return;
|
|
5624
|
+
}
|
|
5625
|
+
if (request.method === "PATCH" && requestUrl.pathname === "/api/plans") {
|
|
5626
|
+
if (!validMutationOrigin(authentication, request, origin)) {
|
|
5627
|
+
sendJson(response, 403, { error: "invalid-origin" });
|
|
5628
|
+
return;
|
|
5629
|
+
}
|
|
5630
|
+
const input = planSubscriptionSchema.parse(await readJsonBody(request));
|
|
5631
|
+
sendJson(response, 200, await options.application.updatePlanSubscription(input));
|
|
5632
|
+
return;
|
|
5633
|
+
}
|
|
5106
5634
|
if (request.method === "GET" && requestUrl.pathname === "/api/monitoring") {
|
|
5107
5635
|
sendJson(response, 200, await options.application.getMonitoringStatus());
|
|
5108
5636
|
return;
|
|
@@ -5301,7 +5829,7 @@ function sendHtml(response, status, body) {
|
|
|
5301
5829
|
response.writeHead(status, { "content-type": "text/html; charset=utf-8" });
|
|
5302
5830
|
response.end(body);
|
|
5303
5831
|
}
|
|
5304
|
-
var SESSION_COOKIE, connectorActionSchema, usageQuerySchema, monitoringSettingsSchema, clearDataSchema, hardRebuildSchema;
|
|
5832
|
+
var SESSION_COOKIE, connectorActionSchema, usageQuerySchema, monitoringSettingsSchema, planSubscriptionSchema, clearDataSchema, hardRebuildSchema;
|
|
5305
5833
|
var init_local_server = __esm({
|
|
5306
5834
|
"src/server/local-server.ts"() {
|
|
5307
5835
|
"use strict";
|
|
@@ -5324,6 +5852,17 @@ var init_local_server = __esm({
|
|
|
5324
5852
|
notificationsEnabled: z9.boolean().optional(),
|
|
5325
5853
|
startAtLogin: z9.boolean().optional()
|
|
5326
5854
|
}).refine((value) => Object.keys(value).length > 0, "At least one setting is required");
|
|
5855
|
+
planSubscriptionSchema = z9.object({
|
|
5856
|
+
providerId: z9.string().min(1),
|
|
5857
|
+
billingDomainId: z9.string().min(1),
|
|
5858
|
+
plan: z9.object({
|
|
5859
|
+
planId: z9.string().min(1).nullable(),
|
|
5860
|
+
amount: z9.number().positive().finite().optional(),
|
|
5861
|
+
currency: z9.string().length(3).optional(),
|
|
5862
|
+
billingPeriod: z9.enum(["monthly", "annual"]).optional(),
|
|
5863
|
+
anchorDate: z9.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional()
|
|
5864
|
+
}).nullable()
|
|
5865
|
+
});
|
|
5327
5866
|
clearDataSchema = z9.object({ deleteProductSecrets: z9.boolean().default(false) });
|
|
5328
5867
|
hardRebuildSchema = z9.object({ confirmExpensiveOperation: z9.literal(true) });
|
|
5329
5868
|
}
|
|
@@ -6253,7 +6792,7 @@ function buildGlobalSummary(providers, riskSummary, now, query) {
|
|
|
6253
6792
|
tokenEvidence,
|
|
6254
6793
|
apiRetailEquivalent: {
|
|
6255
6794
|
status: sawRetailEquivalent ? "available" : "unavailable",
|
|
6256
|
-
amount: sawRetailEquivalent ?
|
|
6795
|
+
amount: sawRetailEquivalent ? preciseAmount2(retailAmount) : null,
|
|
6257
6796
|
currency: "USD",
|
|
6258
6797
|
pricingCoverage: tokenEvidence.recordedTokens === 0 ? null : retailPricedTokens / tokenEvidence.recordedTokens
|
|
6259
6798
|
},
|
|
@@ -6263,7 +6802,7 @@ function buildGlobalSummary(providers, riskSummary, now, query) {
|
|
|
6263
6802
|
contributions
|
|
6264
6803
|
};
|
|
6265
6804
|
}
|
|
6266
|
-
function buildTokenMoneyWorkbench(providers, now, query) {
|
|
6805
|
+
function buildTokenMoneyWorkbench(providers, now, query, planSubscriptions, planBillingPeriods) {
|
|
6267
6806
|
const normalized = normalizeUsageQuery(now, query);
|
|
6268
6807
|
const allHistories = allDomainHistories(providers);
|
|
6269
6808
|
const headlineHistories = allHistories.filter(({ includedInHeadline }) => includedInHeadline);
|
|
@@ -6454,9 +6993,43 @@ function buildTokenMoneyWorkbench(providers, now, query) {
|
|
|
6454
6993
|
observationCount > 0 ? recordedTokens : null,
|
|
6455
6994
|
retailEquivalent,
|
|
6456
6995
|
reportedEstimate
|
|
6457
|
-
)
|
|
6996
|
+
),
|
|
6997
|
+
planValue: buildWorkbenchPlanValue({
|
|
6998
|
+
domains: planValueDomains(allHistories, comparisonCurrency),
|
|
6999
|
+
subscriptions: planSubscriptions,
|
|
7000
|
+
comparisonCurrency,
|
|
7001
|
+
start: normalized.start.toISOString(),
|
|
7002
|
+
end: normalized.end.toISOString(),
|
|
7003
|
+
rates: uniqueExchangeRates(allHistories.flatMap(({ history }) => history.exchangeRates)),
|
|
7004
|
+
billingPeriods: planBillingPeriods
|
|
7005
|
+
})
|
|
6458
7006
|
};
|
|
6459
7007
|
}
|
|
7008
|
+
function planValueDomains(histories, comparisonCurrency) {
|
|
7009
|
+
return histories.map(({ provider, domain, history, includedInHeadline }) => {
|
|
7010
|
+
const domainTokens = history.tokenEvidence.recordedTokens;
|
|
7011
|
+
const metric = (purpose) => buildWorkbenchMetric(
|
|
7012
|
+
history.costs,
|
|
7013
|
+
purpose,
|
|
7014
|
+
comparisonCurrency,
|
|
7015
|
+
domainTokens,
|
|
7016
|
+
history.exchangeRates
|
|
7017
|
+
);
|
|
7018
|
+
return {
|
|
7019
|
+
providerId: provider.id,
|
|
7020
|
+
providerDisplayName: provider.displayName,
|
|
7021
|
+
billingDomainId: domain.id,
|
|
7022
|
+
billingDomainDisplayName: domain.displayName,
|
|
7023
|
+
includedInHeadline,
|
|
7024
|
+
recordedTokens: domainTokens,
|
|
7025
|
+
observationCount: history.tokenEvidence.observationCount,
|
|
7026
|
+
retailEquivalent: metric("retail-equivalent"),
|
|
7027
|
+
actualCost: metric("actual"),
|
|
7028
|
+
authorities: history.authorities ?? [],
|
|
7029
|
+
lastObservedAt: history.lastObservedAt ?? null
|
|
7030
|
+
};
|
|
7031
|
+
});
|
|
7032
|
+
}
|
|
6460
7033
|
function allDomainHistories(providers) {
|
|
6461
7034
|
return providers.flatMap(
|
|
6462
7035
|
(provider) => provider.billingDomains.map((domain) => ({
|
|
@@ -6512,11 +7085,11 @@ function buildWorkbenchMetric(costs, purpose, comparisonCurrency, recordedTokens
|
|
|
6512
7085
|
return {
|
|
6513
7086
|
purpose,
|
|
6514
7087
|
status,
|
|
6515
|
-
amount: status === "available" ?
|
|
7088
|
+
amount: status === "available" ? preciseAmount2(convertedAmount) : null,
|
|
6516
7089
|
comparisonCurrency,
|
|
6517
7090
|
nativeAmounts: [...native.entries()].map(([currency, amount]) => ({
|
|
6518
7091
|
currency,
|
|
6519
|
-
amount: amount.complete ?
|
|
7092
|
+
amount: amount.complete ? preciseAmount2(amount.amount) : null,
|
|
6520
7093
|
records: amount.records,
|
|
6521
7094
|
knownRecords: amount.knownRecords
|
|
6522
7095
|
})).sort((left, right) => left.currency.localeCompare(right.currency)),
|
|
@@ -7108,7 +7681,7 @@ function round(value) {
|
|
|
7108
7681
|
function pricingInputsChanged(existing, observation) {
|
|
7109
7682
|
return existing.billing_domain_id !== observation.billingDomainId || existing.model !== (observation.model?.trim() || "__unclassified__") || existing.observed_at !== observation.observedAt || Number(existing.total_tokens) !== observation.recordedTokens || Number(existing.input_tokens) !== observation.inputTokens || Number(existing.output_tokens) !== observation.outputTokens || Number(existing.reasoning_tokens) !== observation.reasoningTokens || Number(existing.cache_read_tokens) !== observation.cacheReadTokens || Number(existing.cache_write_tokens) !== observation.cacheWriteTokens || (existing.cache_write_5m_tokens === null ? null : Number(existing.cache_write_5m_tokens)) !== (observation.cacheWriteTokenBreakdown?.fiveMinute ?? null) || (existing.cache_write_1h_tokens === null ? null : Number(existing.cache_write_1h_tokens)) !== (observation.cacheWriteTokenBreakdown?.oneHour ?? null) || Number(existing.unclassified_tokens) !== observation.unclassifiedTokens || existing.reasoning_semantics !== observation.tokenSemantics.reasoning || existing.cache_read_semantics !== observation.tokenSemantics.cacheRead || existing.cache_write_semantics !== observation.tokenSemantics.cacheWrite || existing.model_attribution !== observation.modelAttribution || existing.time_precision !== observation.timePrecision || existing.aggregation_temporality !== observation.aggregationTemporality;
|
|
7110
7683
|
}
|
|
7111
|
-
function
|
|
7684
|
+
function preciseAmount2(value) {
|
|
7112
7685
|
return Number(value.toFixed(12));
|
|
7113
7686
|
}
|
|
7114
7687
|
function tokenAuthority(authorities) {
|
|
@@ -7144,6 +7717,7 @@ var init_sqlite_usage_repository = __esm({
|
|
|
7144
7717
|
"src/server/sqlite-usage-repository.ts"() {
|
|
7145
7718
|
"use strict";
|
|
7146
7719
|
init_token_normalization();
|
|
7720
|
+
init_plan_pricing();
|
|
7147
7721
|
FRESHNESS_WINDOW_MS = 15 * 60 * 1e3;
|
|
7148
7722
|
QUERY_INDEXES_SQL = `
|
|
7149
7723
|
CREATE INDEX IF NOT EXISTS usage_observed_at_idx
|
|
@@ -7704,14 +8278,38 @@ var init_sqlite_usage_repository = __esm({
|
|
|
7704
8278
|
(provider) => this.#getProviderOverview(provider, now, query)
|
|
7705
8279
|
);
|
|
7706
8280
|
const riskSummary = buildRiskSummary(overviews);
|
|
8281
|
+
const planSubscriptions = this.getPlanSubscriptions();
|
|
7707
8282
|
return {
|
|
7708
8283
|
generatedAt: now.toISOString(),
|
|
7709
8284
|
globalSummary: buildGlobalSummary(overviews, riskSummary, now, query),
|
|
7710
|
-
workbench: buildTokenMoneyWorkbench(
|
|
8285
|
+
workbench: buildTokenMoneyWorkbench(
|
|
8286
|
+
overviews,
|
|
8287
|
+
now,
|
|
8288
|
+
query,
|
|
8289
|
+
planSubscriptions,
|
|
8290
|
+
this.#planBillingPeriodSummaries(planSubscriptions, now, query)
|
|
8291
|
+
),
|
|
7711
8292
|
providers: overviews,
|
|
7712
8293
|
riskSummary
|
|
7713
8294
|
};
|
|
7714
8295
|
}
|
|
8296
|
+
getAgentProviderIndex(now) {
|
|
8297
|
+
const providers = this.#database.prepare("SELECT id, display_name FROM providers ORDER BY id").all();
|
|
8298
|
+
return {
|
|
8299
|
+
generatedAt: now.toISOString(),
|
|
8300
|
+
providers: providers.filter(
|
|
8301
|
+
(provider) => provider.id !== "opencode" && (!this.#hideDemoProvider || provider.id !== "demo")
|
|
8302
|
+
).map((provider) => ({ id: provider.id, displayName: provider.display_name }))
|
|
8303
|
+
};
|
|
8304
|
+
}
|
|
8305
|
+
getProviderOverview(now, providerId, query = {}) {
|
|
8306
|
+
if (this.#hideDemoProvider && providerId === "demo") return null;
|
|
8307
|
+
const provider = this.#database.prepare(
|
|
8308
|
+
`SELECT id, display_name, last_success_at, last_error, last_error_code, last_recovery
|
|
8309
|
+
FROM providers WHERE id = ?`
|
|
8310
|
+
).get(providerId);
|
|
8311
|
+
return provider ? this.#getProviderOverview(provider, now, query) : null;
|
|
8312
|
+
}
|
|
7715
8313
|
saveExchangeRateSnapshot(snapshot2) {
|
|
7716
8314
|
this.#database.prepare(
|
|
7717
8315
|
`INSERT INTO exchange_rate_snapshots (
|
|
@@ -7787,6 +8385,57 @@ var init_sqlite_usage_repository = __esm({
|
|
|
7787
8385
|
settings.startAtLogin ? 1 : 0
|
|
7788
8386
|
);
|
|
7789
8387
|
}
|
|
8388
|
+
getPlanSubscriptions() {
|
|
8389
|
+
const rows = this.#database.prepare(
|
|
8390
|
+
`SELECT provider_id, billing_domain_id, plan_id, display_name, amount, currency,
|
|
8391
|
+
billing_period, anchor_date, price_source, updated_at
|
|
8392
|
+
FROM plan_subscriptions
|
|
8393
|
+
ORDER BY provider_id, billing_domain_id`
|
|
8394
|
+
).all();
|
|
8395
|
+
return rows.map((row) => ({
|
|
8396
|
+
providerId: row.provider_id,
|
|
8397
|
+
billingDomainId: row.billing_domain_id,
|
|
8398
|
+
planId: row.plan_id,
|
|
8399
|
+
displayName: row.display_name,
|
|
8400
|
+
amount: row.amount,
|
|
8401
|
+
currency: row.currency,
|
|
8402
|
+
billingPeriod: row.billing_period,
|
|
8403
|
+
anchorDate: row.anchor_date,
|
|
8404
|
+
priceSource: row.price_source,
|
|
8405
|
+
updatedAt: row.updated_at
|
|
8406
|
+
}));
|
|
8407
|
+
}
|
|
8408
|
+
savePlanSubscription(subscription) {
|
|
8409
|
+
this.#database.prepare(
|
|
8410
|
+
`INSERT INTO plan_subscriptions (
|
|
8411
|
+
provider_id, billing_domain_id, plan_id, display_name, amount, currency,
|
|
8412
|
+
billing_period, anchor_date, price_source, updated_at
|
|
8413
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8414
|
+
ON CONFLICT(provider_id, billing_domain_id) DO UPDATE SET
|
|
8415
|
+
plan_id = excluded.plan_id,
|
|
8416
|
+
display_name = excluded.display_name,
|
|
8417
|
+
amount = excluded.amount,
|
|
8418
|
+
currency = excluded.currency,
|
|
8419
|
+
billing_period = excluded.billing_period,
|
|
8420
|
+
anchor_date = excluded.anchor_date,
|
|
8421
|
+
price_source = excluded.price_source,
|
|
8422
|
+
updated_at = excluded.updated_at`
|
|
8423
|
+
).run(
|
|
8424
|
+
subscription.providerId,
|
|
8425
|
+
subscription.billingDomainId,
|
|
8426
|
+
subscription.planId,
|
|
8427
|
+
subscription.displayName,
|
|
8428
|
+
subscription.amount,
|
|
8429
|
+
subscription.currency,
|
|
8430
|
+
subscription.billingPeriod,
|
|
8431
|
+
subscription.anchorDate,
|
|
8432
|
+
subscription.priceSource,
|
|
8433
|
+
subscription.updatedAt
|
|
8434
|
+
);
|
|
8435
|
+
}
|
|
8436
|
+
deletePlanSubscription(providerId, billingDomainId) {
|
|
8437
|
+
this.#database.prepare("DELETE FROM plan_subscriptions WHERE provider_id = ? AND billing_domain_id = ?").run(providerId, billingDomainId);
|
|
8438
|
+
}
|
|
7790
8439
|
getNotificationState(key) {
|
|
7791
8440
|
const row = this.#database.prepare("SELECT value FROM notification_state WHERE key = ?").get(key);
|
|
7792
8441
|
return row?.value ?? null;
|
|
@@ -8245,6 +8894,101 @@ var init_sqlite_usage_repository = __esm({
|
|
|
8245
8894
|
coverageByDomain
|
|
8246
8895
|
};
|
|
8247
8896
|
}
|
|
8897
|
+
/**
|
|
8898
|
+
* Cycle-to-date evidence for every subscription that declares a renewal date.
|
|
8899
|
+
* Each Provider is measured over its own billing period, which is why this
|
|
8900
|
+
* cannot reuse the one rolling window the rest of the workbench shares.
|
|
8901
|
+
*/
|
|
8902
|
+
#planBillingPeriodSummaries(subscriptions, now, query) {
|
|
8903
|
+
const normalized = normalizeUsageQuery(now, query);
|
|
8904
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
8905
|
+
for (const subscription of subscriptions) {
|
|
8906
|
+
if (!subscription.anchorDate) continue;
|
|
8907
|
+
const period = billingPeriodContaining(
|
|
8908
|
+
subscription.anchorDate,
|
|
8909
|
+
subscription.billingPeriod,
|
|
8910
|
+
now
|
|
8911
|
+
);
|
|
8912
|
+
if (!period) continue;
|
|
8913
|
+
const observedThrough = new Date(Math.min(now.getTime(), period.end.getTime()));
|
|
8914
|
+
summaries.set(`${subscription.providerId}:${subscription.billingDomainId}`, {
|
|
8915
|
+
start: period.start.toISOString(),
|
|
8916
|
+
end: period.end.toISOString(),
|
|
8917
|
+
observedThrough: observedThrough.toISOString(),
|
|
8918
|
+
...this.#rangeUsageSummary(
|
|
8919
|
+
subscription.providerId,
|
|
8920
|
+
subscription.billingDomainId,
|
|
8921
|
+
period.start,
|
|
8922
|
+
observedThrough,
|
|
8923
|
+
normalized.comparisonCurrency
|
|
8924
|
+
)
|
|
8925
|
+
});
|
|
8926
|
+
}
|
|
8927
|
+
return summaries;
|
|
8928
|
+
}
|
|
8929
|
+
#rangeUsageSummary(providerId, billingDomainId, start, end, comparisonCurrency) {
|
|
8930
|
+
const startIso = start.toISOString();
|
|
8931
|
+
const endIso = end.toISOString();
|
|
8932
|
+
const usage = this.#database.prepare(
|
|
8933
|
+
`SELECT id, model, observed_at, authority, total_tokens, input_tokens, output_tokens,
|
|
8934
|
+
reasoning_tokens, cache_read_tokens, cache_write_tokens, cache_write_5m_tokens,
|
|
8935
|
+
cache_write_1h_tokens, source_reported_total_tokens,
|
|
8936
|
+
unclassified_tokens, total_derivation, model_attribution, time_precision,
|
|
8937
|
+
usage_scope, aggregation_temporality, reasoning_semantics,
|
|
8938
|
+
cache_read_semantics, cache_write_semantics
|
|
8939
|
+
FROM usage_observations
|
|
8940
|
+
WHERE provider_id = ? AND billing_domain_id = ?
|
|
8941
|
+
AND observed_at >= ? AND observed_at < ?
|
|
8942
|
+
AND ${additiveUsagePredicate()}
|
|
8943
|
+
ORDER BY observed_at, id`
|
|
8944
|
+
).all(providerId, billingDomainId, startIso, endIso);
|
|
8945
|
+
const costs = this.#database.prepare(
|
|
8946
|
+
`SELECT id, source_id, billing_domain_id, observed_at, kind, amount, currency, authority,
|
|
8947
|
+
price_snapshot_id, price_snapshot_version, price_snapshot_source,
|
|
8948
|
+
price_snapshot_canonical_model, price_snapshot_effective_at,
|
|
8949
|
+
price_snapshot_effective_until, price_snapshot_currency,
|
|
8950
|
+
price_snapshot_rates_json, price_snapshot_source_url,
|
|
8951
|
+
price_snapshot_context_tier, model, usage_observation_id, priced_tokens,
|
|
8952
|
+
line_items_json, calculated_at
|
|
8953
|
+
FROM cost_records
|
|
8954
|
+
WHERE provider_id = ? AND billing_domain_id = ?
|
|
8955
|
+
AND observed_at >= ? AND observed_at < ?
|
|
8956
|
+
ORDER BY observed_at, id`
|
|
8957
|
+
).all(providerId, billingDomainId, startIso, endIso);
|
|
8958
|
+
const rateRows = this.#database.prepare(
|
|
8959
|
+
`SELECT id, base_currency, quote_currency, rate, observed_at, source
|
|
8960
|
+
FROM exchange_rate_snapshots WHERE observed_at < ? ORDER BY observed_at DESC, id`
|
|
8961
|
+
).all(endIso);
|
|
8962
|
+
const rateByCurrency = /* @__PURE__ */ new Map();
|
|
8963
|
+
for (const row of rateRows) {
|
|
8964
|
+
if (row.quote_currency === comparisonCurrency && !rateByCurrency.has(row.base_currency)) {
|
|
8965
|
+
rateByCurrency.set(row.base_currency, row);
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
const evidence = emptyTokenEvidence();
|
|
8969
|
+
for (const row of usage) addTokenEvidence(evidence, row);
|
|
8970
|
+
const finished = finishTokenEvidence(evidence);
|
|
8971
|
+
const usedRates = /* @__PURE__ */ new Map();
|
|
8972
|
+
const historyCosts = summarizeHistoryCosts(
|
|
8973
|
+
costs,
|
|
8974
|
+
finished.recordedTokens,
|
|
8975
|
+
rateByCurrency,
|
|
8976
|
+
comparisonCurrency,
|
|
8977
|
+
end,
|
|
8978
|
+
usedRates
|
|
8979
|
+
);
|
|
8980
|
+
return {
|
|
8981
|
+
recordedTokens: finished.recordedTokens,
|
|
8982
|
+
observationCount: finished.observationCount,
|
|
8983
|
+
retailEquivalent: buildWorkbenchMetric(
|
|
8984
|
+
historyCosts,
|
|
8985
|
+
"retail-equivalent",
|
|
8986
|
+
comparisonCurrency,
|
|
8987
|
+
finished.recordedTokens,
|
|
8988
|
+
[...usedRates.values()]
|
|
8989
|
+
)
|
|
8990
|
+
};
|
|
8991
|
+
}
|
|
8248
8992
|
#getBillingHistory(providerId, billingDomainId, now, query) {
|
|
8249
8993
|
const normalized = normalizeUsageQuery(now, query);
|
|
8250
8994
|
const start = normalized.start.toISOString();
|
|
@@ -8653,6 +9397,19 @@ var init_sqlite_usage_repository = __esm({
|
|
|
8653
9397
|
key TEXT PRIMARY KEY,
|
|
8654
9398
|
value TEXT NOT NULL
|
|
8655
9399
|
);
|
|
9400
|
+
CREATE TABLE IF NOT EXISTS plan_subscriptions (
|
|
9401
|
+
provider_id TEXT NOT NULL,
|
|
9402
|
+
billing_domain_id TEXT NOT NULL,
|
|
9403
|
+
plan_id TEXT,
|
|
9404
|
+
display_name TEXT NOT NULL,
|
|
9405
|
+
amount REAL NOT NULL,
|
|
9406
|
+
currency TEXT NOT NULL,
|
|
9407
|
+
billing_period TEXT NOT NULL,
|
|
9408
|
+
anchor_date TEXT,
|
|
9409
|
+
price_source TEXT NOT NULL,
|
|
9410
|
+
updated_at TEXT NOT NULL,
|
|
9411
|
+
PRIMARY KEY (provider_id, billing_domain_id)
|
|
9412
|
+
);
|
|
8656
9413
|
`);
|
|
8657
9414
|
const usageColumns = this.#database.prepare("PRAGMA table_info(usage_observations)").all();
|
|
8658
9415
|
if (!usageColumns.some((column) => column.name === "total_tokens")) {
|
|
@@ -8821,6 +9578,10 @@ var init_sqlite_usage_repository = __esm({
|
|
|
8821
9578
|
this.#database.exec(`ALTER TABLE quota_buckets ADD COLUMN ${name} ${type}`);
|
|
8822
9579
|
}
|
|
8823
9580
|
}
|
|
9581
|
+
const planColumns = this.#database.prepare("PRAGMA table_info(plan_subscriptions)").all();
|
|
9582
|
+
if (planColumns.length > 0 && !planColumns.some((column) => column.name === "anchor_date")) {
|
|
9583
|
+
this.#database.exec("ALTER TABLE plan_subscriptions ADD COLUMN anchor_date TEXT");
|
|
9584
|
+
}
|
|
8824
9585
|
}
|
|
8825
9586
|
};
|
|
8826
9587
|
}
|