@ipv9/tokentracker-cli 0.39.53 → 0.39.55
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/dashboard/dist/assets/{Card-BZkG7ExP.js → Card-CtvDXZw0.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BXs8ZVPT.js → DashboardPage-Y1wWgbMc.js} +3 -3
- package/dashboard/dist/assets/{IpCheckPage-Hm0zUwdf.js → IpCheckPage-Bor8g4lv.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BxsnbmDo.js → LimitsPage-CIac9eQA.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-BvwC1vCO.js → LocalOnlyNotice-Dr74ws0K.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-HsE_7fXK.js → PopoverPopup-DhIs9KLg.js} +1 -1
- package/dashboard/dist/assets/{Select-7_WyWhaS.js → Select-B-hdqLRw.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-DbJxd3N7.js → SettingsPage-CGmMNqQ4.js} +1 -1
- package/dashboard/dist/assets/{SkillsPage-Dae3HfP-.js → SkillsPage-D70T7nYt.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-DZU9PYF5.js → WrappedPage-CXCjVq_h.js} +1 -1
- package/dashboard/dist/assets/{format-DtQGOz67.js → format-Cf1THGDG.js} +1 -1
- package/dashboard/dist/assets/{limitDisplay-B1ZUkTHM.js → limitDisplay-HHiXLwsI.js} +1 -1
- package/dashboard/dist/assets/{main-BBQHiOju.js → main-B5OWbrt1.js} +4 -3
- package/dashboard/dist/assets/{mock-data-Ct1Wvcew.js → mock-data-D7OvhHFq.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CYoiCfEE.js → use-limits-display-prefs-CJObZZbn.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-DGkZG989.js → useCurrency-AuPUpwUc.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/lib/pricing/curated-overrides.json +2 -2
- package/src/lib/pricing/matcher.js +36 -1
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +92 -34
package/src/lib/rollout.js
CHANGED
|
@@ -3446,6 +3446,7 @@ const HERMES_TOKEN_TOTAL_FIELDS = [
|
|
|
3446
3446
|
"reasoning_output_tokens",
|
|
3447
3447
|
"total_tokens",
|
|
3448
3448
|
"billable_total_tokens",
|
|
3449
|
+
"conversation_count",
|
|
3449
3450
|
];
|
|
3450
3451
|
|
|
3451
3452
|
function hermesRowTotals(row) {
|
|
@@ -3570,12 +3571,16 @@ async function appendHermesReconciliationRows({ queuePath, queueStatePath, canon
|
|
|
3570
3571
|
}
|
|
3571
3572
|
}
|
|
3572
3573
|
|
|
3573
|
-
// One-time,
|
|
3574
|
+
// One-time, per-bucket correction for the v0.39.48 adoption gate. That gate
|
|
3574
3575
|
// intentionally avoided replaying legacy aggregate snapshots, which preserved
|
|
3575
3576
|
// the grand total but left already-counted mixed-model usage on sessions.model.
|
|
3576
|
-
//
|
|
3577
|
-
//
|
|
3578
|
-
//
|
|
3577
|
+
// Compares each canonical (model, half-hour) bucket against the existing
|
|
3578
|
+
// accumulator instead of requiring global grand-total equality: production
|
|
3579
|
+
// installs cannot satisfy exact equality (source retention deletes ingested
|
|
3580
|
+
// sessions, live sessions grow between reads). Differing canonical buckets are
|
|
3581
|
+
// appended as latest-wins correction rows after the consumed upload offset;
|
|
3582
|
+
// existing buckets with no canonical counterpart that predate retained source
|
|
3583
|
+
// coverage are zeroed out explicitly.
|
|
3579
3584
|
async function reconcileHermesMixedModelUsage({ dbPaths, cursors, queuePath, queueStatePath, hermesState, updatedAt, sqliteOptions }) {
|
|
3580
3585
|
if (!cursors || typeof cursors !== "object") return { status: "invalid_cursor" };
|
|
3581
3586
|
const migrations = cursors.migrations && typeof cursors.migrations === "object" ? cursors.migrations : {};
|
|
@@ -3604,13 +3609,22 @@ async function reconcileHermesMixedModelUsage({ dbPaths, cursors, queuePath, que
|
|
|
3604
3609
|
}
|
|
3605
3610
|
|
|
3606
3611
|
const hourlyState = normalizeHourlyState(cursors.hourly);
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3612
|
+
// Retained source coverage boundary: the oldest session start still present in
|
|
3613
|
+
// any authoritative database. Existing buckets whose hour starts before this
|
|
3614
|
+
// boundary cannot have a surviving source row, so they are provably orphans.
|
|
3615
|
+
let retentionBoundaryEpochSec = null;
|
|
3616
|
+
for (const { rows } of rowsBySource) {
|
|
3617
|
+
for (const row of rows) {
|
|
3618
|
+
const startedAt = Number(row.started_at);
|
|
3619
|
+
if (Number.isFinite(startedAt) && startedAt > 0) {
|
|
3620
|
+
retentionBoundaryEpochSec = retentionBoundaryEpochSec == null
|
|
3621
|
+
? startedAt
|
|
3622
|
+
: Math.min(retentionBoundaryEpochSec, startedAt);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3610
3625
|
}
|
|
3611
3626
|
|
|
3612
3627
|
const canonicalBuckets = {};
|
|
3613
|
-
const authoritativeTotals = initTotals();
|
|
3614
3628
|
for (const { rows } of rowsBySource) {
|
|
3615
3629
|
for (const row of rows) {
|
|
3616
3630
|
const totals = hermesRowTotals(row);
|
|
@@ -3621,46 +3635,82 @@ async function reconcileHermesMixedModelUsage({ dbPaths, cursors, queuePath, que
|
|
|
3621
3635
|
const key = bucketKey("hermes", model, hourStart);
|
|
3622
3636
|
const bucket = canonicalBuckets[key] ||= { totals: initTotals(), queuedKey: null };
|
|
3623
3637
|
addTotals(bucket.totals, totals);
|
|
3624
|
-
addTotals(authoritativeTotals, totals);
|
|
3625
3638
|
}
|
|
3626
3639
|
}
|
|
3627
3640
|
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
status: "blocked_total_mismatch",
|
|
3632
|
-
updatedAt,
|
|
3633
|
-
existingTotalTokens: existingTotals.total_tokens,
|
|
3634
|
-
authoritativeTotalTokens: authoritativeTotals.total_tokens,
|
|
3635
|
-
};
|
|
3636
|
-
return cursors.migrations[HERMES_MIXED_MODEL_RECONCILIATION_MIGRATION_KEY];
|
|
3641
|
+
function bucketHourStartToEpochSec(hourStart) {
|
|
3642
|
+
const parsedMs = Date.parse(`${hourStart}Z`.replace(/Z+$/, "Z"));
|
|
3643
|
+
return Number.isFinite(parsedMs) ? parsedMs / 1000 : null;
|
|
3637
3644
|
}
|
|
3638
3645
|
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
+
const existingHermesKeys = new Set(Object.keys(hourlyState.buckets || {})
|
|
3647
|
+
.filter((key) => parseBucketKey(key).source === "hermes"));
|
|
3648
|
+
|
|
3649
|
+
const correctionRows = [];
|
|
3650
|
+
let bucketsCorrected = 0;
|
|
3651
|
+
let bucketsSkipped = 0;
|
|
3652
|
+
let orphansZeroed = 0;
|
|
3653
|
+
let stalePlacementsZeroed = 0;
|
|
3654
|
+
|
|
3655
|
+
// Corrections for canonical buckets that differ from the existing accumulator.
|
|
3646
3656
|
for (const [key, bucket] of Object.entries(canonicalBuckets)) {
|
|
3647
3657
|
const { model, hourStart } = parseBucketKey(key);
|
|
3658
|
+
const existingBucket = hourlyState.buckets[key];
|
|
3659
|
+
if (existingBucket && sameHermesTokenTotals(existingBucket?.totals, bucket.totals)) {
|
|
3660
|
+
bucketsSkipped += 1;
|
|
3661
|
+
continue;
|
|
3662
|
+
}
|
|
3648
3663
|
bucket.queuedKey = totalsKey(bucket.totals);
|
|
3649
3664
|
hourlyState.buckets[key] = bucket;
|
|
3650
|
-
|
|
3665
|
+
correctionRows.push(JSON.stringify({
|
|
3651
3666
|
source: "hermes",
|
|
3652
3667
|
model,
|
|
3653
3668
|
hour_start: hourStart,
|
|
3654
3669
|
...bucket.totals,
|
|
3655
3670
|
}));
|
|
3671
|
+
bucketsCorrected += 1;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
// Zero out existing buckets with no canonical counterpart. Outside retained
|
|
3675
|
+
// coverage they are provably orphans (source rows deleted after ingestion).
|
|
3676
|
+
// Inside coverage they are stale placements: the incremental path spread a
|
|
3677
|
+
// session's cumulative totals across observation-time buckets while the
|
|
3678
|
+
// canonical view places them at the session's current last_seen bucket.
|
|
3679
|
+
// Latest-wins upserts make the zero row retire the stale placement without
|
|
3680
|
+
// rewriting queue history; unfinished sessions re-emit growth at their new
|
|
3681
|
+
// bucket on the next incremental sync.
|
|
3682
|
+
for (const key of existingHermesKeys) {
|
|
3683
|
+
if (canonicalBuckets[key]) continue;
|
|
3684
|
+
const { model, hourStart } = parseBucketKey(key);
|
|
3685
|
+
const existingBucketTotal = Number(hourlyState.buckets[key]?.totals?.total_tokens || 0);
|
|
3686
|
+
if (existingBucketTotal === 0) continue;
|
|
3687
|
+
const hourStartSec = bucketHourStartToEpochSec(hourStart);
|
|
3688
|
+
const provablyOrphaned = hourStartSec != null
|
|
3689
|
+
&& retentionBoundaryEpochSec != null
|
|
3690
|
+
&& hourStartSec < retentionBoundaryEpochSec;
|
|
3691
|
+
const zeroTotals = initTotals();
|
|
3692
|
+
hourlyState.buckets[key] = { totals: zeroTotals, queuedKey: totalsKey(zeroTotals) };
|
|
3693
|
+
correctionRows.push(JSON.stringify({
|
|
3694
|
+
source: "hermes",
|
|
3695
|
+
model,
|
|
3696
|
+
hour_start: hourStart,
|
|
3697
|
+
...zeroTotals,
|
|
3698
|
+
}));
|
|
3699
|
+
if (provablyOrphaned) {
|
|
3700
|
+
orphansZeroed += 1;
|
|
3701
|
+
} else {
|
|
3702
|
+
stalePlacementsZeroed += 1;
|
|
3703
|
+
}
|
|
3656
3704
|
}
|
|
3657
3705
|
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3706
|
+
if (correctionRows.length > 0) {
|
|
3707
|
+
await appendHermesReconciliationRows({
|
|
3708
|
+
queuePath,
|
|
3709
|
+
queueStatePath,
|
|
3710
|
+
canonicalRows: correctionRows,
|
|
3711
|
+
updatedAt,
|
|
3712
|
+
});
|
|
3713
|
+
}
|
|
3664
3714
|
|
|
3665
3715
|
const nextHermesState = hermesState && typeof hermesState === "object" ? hermesState : {};
|
|
3666
3716
|
for (const { profileName, rows } of rowsBySource) {
|
|
@@ -3677,13 +3727,21 @@ async function reconcileHermesMixedModelUsage({ dbPaths, cursors, queuePath, que
|
|
|
3677
3727
|
);
|
|
3678
3728
|
}
|
|
3679
3729
|
}
|
|
3730
|
+
for (const key of Object.keys(hourlyState.groupQueued || {})) {
|
|
3731
|
+
if (key.startsWith("hermes|")) delete hourlyState.groupQueued[key];
|
|
3732
|
+
}
|
|
3680
3733
|
cursors.hourly = hourlyState;
|
|
3681
3734
|
cursors.migrations ||= {};
|
|
3682
3735
|
cursors.migrations[HERMES_MIXED_MODEL_RECONCILIATION_MIGRATION_KEY] = {
|
|
3683
3736
|
status: "applied",
|
|
3684
3737
|
appliedAt: updatedAt,
|
|
3685
|
-
totalTokens:
|
|
3686
|
-
|
|
3738
|
+
totalTokens: Object.values(canonicalBuckets).reduce(
|
|
3739
|
+
(sum, bucket) => sum + Number(bucket.totals.total_tokens || 0), 0),
|
|
3740
|
+
bucketsRebuilt: Object.keys(canonicalBuckets).length,
|
|
3741
|
+
bucketsCorrected,
|
|
3742
|
+
bucketsSkipped,
|
|
3743
|
+
orphansZeroed,
|
|
3744
|
+
stalePlacementsZeroed,
|
|
3687
3745
|
};
|
|
3688
3746
|
return cursors.migrations[HERMES_MIXED_MODEL_RECONCILIATION_MIGRATION_KEY];
|
|
3689
3747
|
}
|