tokentracker-cli 0.94.0 → 0.95.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.de.md +9 -7
- package/README.ja.md +8 -6
- package/README.ko.md +8 -6
- package/README.md +23 -10
- package/README.zh-CN.md +8 -6
- package/dashboard/dist/assets/{AchievementBadge-DPqwk2zI.js → AchievementBadge-Pi3Gvbm8.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-CELHoMQt.js → AchievementsPage-C4WLT8Rl.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DdAC0VCb.js → AchievementsSection-ilvR2ssr.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-Dd4I1SpM.js → BadgeDetailModal-CjEeToxa.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-DL2-4mnm.js → BotAnimated-DDf9pBuK.js} +1 -1
- package/dashboard/dist/assets/{Card-B_MpSRse.js → Card-DNous84-.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-BLZZ4rG6.js → ClawdAnimated-BLMIRntb.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-B-vfdIbH.js → CommandPalette-vVekhoFO.js} +1 -1
- package/dashboard/dist/assets/CommunityStatsModal-C0Kp_J8B.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-DLnRwUXC.js → ConfirmModal-DFdpcx12.js} +1 -1
- package/dashboard/dist/assets/{Controls-FZ8wEN5w.js → Controls-RNhqybmE.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-BQjQh1IQ.js +1 -0
- package/dashboard/dist/assets/{DevicePage-D2IzPc5o.js → DevicePage-DIhh-YAk.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-BaqQaUUk.js → DialogClose-ZqxYVIzl.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DDM4aanX.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DrbKjtuS.js → DialogTitle-BiRcejlu.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-GddKDE8i.js → FadeIn-BDBQpeGe.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DUu-Z5mE.js → HeaderGithubStar-CztIwtKT.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BmDkY35A.js → HoverTooltip-Cfso7LTC.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-CGJyqWk2.js → IpCheckPage-CNE7dLs3.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-Bd91ALUj.js → LandingPage-B2a3cweM.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-BGGUwXFd.js → LeaderboardAvatar-CwmZnIji.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-BMEqHwif.js → LeaderboardPage-DkTHj3fB.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-BSweJqm2.js → LeaderboardProfileModal-CefTO3Zh.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CRrI3RFz.js → LeaderboardProfilePage-CnOXlQ3L.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-XIRDv46K.js → LimitsPage-BIZ1-6BB.js} +2 -2
- package/dashboard/dist/assets/{LocalOnlyNotice-CCD-lGWG.js → LocalOnlyNotice-CdHQiPhy.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-lDuqSv4f.js → LoginCard-yXLA9geu.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DStt5VXN.js → LoginModal-BF2Pfkqa.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-dmVPSL72.js → LoginPage-DuOypZap.js} +1 -1
- package/dashboard/dist/assets/{PetPage-ybcZRctF.js → PetPage-BH3xDVV9.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-Dar_UjxF.js → PopoverPopup-CewF_cGz.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-Ct9tILpz.js → ProviderIcon-Dg0K0RPn.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-B4yLh5zx.js → ResetPasswordPage-CYwt85O3.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-BOE1CEzq.js → SegmentedControl-BJkXXomQ.js} +1 -1
- package/dashboard/dist/assets/{Select-D-bSh7Zz.js → Select-B3_WH0Qe.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DMYjXXq4.js → SelectItemText-BUby_DUu.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-CaleS-6_.js → ServiceStatusPage-BDlVZk4i.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-TaC9nahK.js +2 -0
- package/dashboard/dist/assets/{SettingsPage-iJH0Yyah.js → SettingsPage-CudcQh6m.js} +2 -2
- package/dashboard/dist/assets/SkillsPage-iDLQ8lpZ.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-CuXKoMPg.js → TokenGalaxy-DtYzUsXU.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-Dx5GB7F5.js → ToolbarRootContext-PC8Tg1Jz.js} +1 -1
- package/dashboard/dist/assets/TrendMonitor-r8n8YO_9.js +19 -0
- package/dashboard/dist/assets/{WidgetsPage-DUaIyGtu.js → WidgetsPage-vzfGccWb.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-BzqlcAUC.js → WrappedPage-DaKuIkYE.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BHz2N7Ih.css → __vite-browser-external-CCMdjCX2.css} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-Dnxl50k1.js +45 -0
- package/dashboard/dist/assets/{agent-logos-BdWWxza6.js → agent-logos-B7nzn3n2.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CANSzmm-.js → arrow-up-right-CsxyvCER.js} +1 -1
- package/dashboard/dist/assets/{check-BnJpzXRZ.js → check-hYcC7MdH.js} +1 -1
- package/dashboard/dist/assets/{composite-Ccj6KQmh.js → composite-C79LNbex.js} +1 -1
- package/dashboard/dist/assets/{copy-C2Yc8Evu.js → copy-Cz4qzE62.js} +1 -1
- package/dashboard/dist/assets/{download-B52JTk6Y.js → download-CUaSNE2E.js} +1 -1
- package/dashboard/dist/assets/{external-link-DayTwZx-.js → external-link-vq7kIyKK.js} +1 -1
- package/dashboard/dist/assets/{flame-B8GEX2yT.js → flame-CB6K4eR6.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-mhkbohkb.js → format-tokens-KI-e_JQ-.js} +1 -1
- package/dashboard/dist/assets/{icons-o8bPXqRP.js → icons-3maItkrp.js} +1 -1
- package/dashboard/dist/assets/{index-MBVWUKpA.js → index-BRXoZ7Jj.js} +1 -1
- package/dashboard/dist/assets/{index-CK_CduLm.js → index-C5WrfmLB.js} +1 -1
- package/dashboard/dist/assets/{index-Bp0ZHDtz.js → index-CUbC50Zp.js} +1 -1
- package/dashboard/dist/assets/{info-D94QM_vX.js → info-r0yGihUe.js} +1 -1
- package/dashboard/dist/assets/{limits-providers-BLzMyCiy.js → limits-providers-BN_94oe9.js} +1 -1
- package/dashboard/dist/assets/{link-2-CqBmFaDI.js → link-2-J9t0VXWM.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-SaxUpDR6.js → loader-circle-DCvOsPze.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-BoXqARks.js → maximize-2-CZ9JhGjf.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-Conh_-BD.js → modifiers.esm-DJ0_WKg0.js} +1 -1
- package/dashboard/dist/assets/module-xpSFyNBK.js +3 -0
- package/dashboard/dist/assets/{monitor-smartphone-D6qLR9qd.js → monitor-smartphone-nqbWjJx_.js} +1 -1
- package/dashboard/dist/assets/{plus-D51mjIc2.js → plus-Cg7wd2jK.js} +1 -1
- package/dashboard/dist/assets/{provider-display-DpHPjd_s.js → provider-display-B9L1nRY9.js} +2 -2
- package/dashboard/dist/assets/{react-D_AcI466.js → react-D2AD6Pk2.js} +1 -1
- package/dashboard/dist/assets/{search-CwLcNdvZ.js → search-uNiWba1h.js} +1 -1
- package/dashboard/dist/assets/{skills-api-Dqi7Mjgo.js → skills-api-BBYuAhiL.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BPDlUxPb.js → sortable.esm-CzPxc949.js} +1 -1
- package/dashboard/dist/assets/{store-ikeRIRGd.js → store-D4A7EUwE.js} +1 -1
- package/dashboard/dist/assets/{terminal-BfeX3L5T.js → terminal-Crmm_yKF.js} +1 -1
- package/dashboard/dist/assets/{trash-2-5_Qlp1k7.js → trash-2-ia9VjRnU.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-D90Fy2_j.js → use-community-stats-KaGCAxaF.js} +1 -1
- package/dashboard/dist/assets/use-latest-request-guard-Dy8gSDkk.js +1 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-D4BYFaa-.js → use-limits-display-prefs-JSTYlbTD.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-DJxLNdXY.js → use-ordered-list-C4JnHdE0.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-AQE0ummK.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-CAX_GqXc.js → use-session-efficiency-pref-CgyUz5Gc.js} +1 -1
- package/dashboard/dist/assets/{use-transform-CeW6Z5R0.js → use-transform-C7-jSJ9k.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-B3YYqn-v.js +1 -0
- package/dashboard/dist/assets/{useCurrency-BarGeFyK.js → useCurrency-CK_gcb3J.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-DUJ9iT9H.js → useTokenFormat-C_a-UkCA.js} +1 -1
- package/dashboard/dist/assets/{zap--m0IWoI3.js → zap-BIJNn-Ic.js} +1 -1
- package/dashboard/dist/brand-logos/lmstudio.svg +12 -0
- package/dashboard/dist/brand-logos/unsloth.svg +1 -0
- package/dashboard/dist/index.html +16 -12
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/llms.txt +4 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +2 -2
- package/src/commands/device-login.js +5 -10
- package/src/commands/init.js +28 -1
- package/src/commands/serve.js +6 -0
- package/src/commands/status.js +24 -0
- package/src/commands/sync.js +114 -18
- package/src/lib/cloud-account.js +102 -29
- package/src/lib/fs.js +30 -0
- package/src/lib/grok-context-breakdown.js +24 -33
- package/src/lib/grok-usage.js +95 -0
- package/src/lib/local-api.js +101 -13
- package/src/lib/pricing/index.js +15 -0
- package/src/lib/pricing/matcher.js +15 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1094 -72
- package/src/lib/session-analytics.js +100 -40
- package/src/lib/usage-limits.js +507 -55
- package/src/lib/zcode-limits.js +65 -8
- package/dashboard/dist/assets/CommunityStatsModal-aAico565.js +0 -1
- package/dashboard/dist/assets/DashboardPage-BByRBsbt.js +0 -1
- package/dashboard/dist/assets/DialogDescription-BUrh0Jod.js +0 -1
- package/dashboard/dist/assets/SessionsPage-DOaY6_E9.js +0 -2
- package/dashboard/dist/assets/SkillsPage-BkzJOBLk.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-BuKmsznP.js +0 -19
- package/dashboard/dist/assets/__vite-browser-external-CVIGknXl.js +0 -45
- package/dashboard/dist/assets/chevron-right-DJm0mbG0.js +0 -1
- package/dashboard/dist/assets/module-D2D5r9gC.js +0 -3
- package/dashboard/dist/assets/use-reduced-motion-yE3T3cat.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-5HP8k9a3.js +0 -1
package/src/commands/sync.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
chmod600IfPossible,
|
|
16
16
|
openLock,
|
|
17
17
|
inspectLock,
|
|
18
|
+
updateJsonLocked,
|
|
18
19
|
} = require("../lib/fs");
|
|
19
20
|
const { physicalJsonlRecords } = require("../lib/jsonl-lines");
|
|
20
21
|
const {
|
|
@@ -104,6 +105,10 @@ const {
|
|
|
104
105
|
parseRoocodeIncremental,
|
|
105
106
|
resolveZedDbPath,
|
|
106
107
|
parseZedIncremental,
|
|
108
|
+
resolveLmstudioLogFiles,
|
|
109
|
+
parseLmstudioIncremental,
|
|
110
|
+
resolveUnslothDbPath,
|
|
111
|
+
parseUnslothIncremental,
|
|
107
112
|
resolveAnythingllmDbPath,
|
|
108
113
|
parseAnythingllmIncremental,
|
|
109
114
|
resolveGooseDbPath,
|
|
@@ -260,6 +265,7 @@ const CODEX_COLD_SCAN_AUDIT_MAX_SYNCS = 288;
|
|
|
260
265
|
const MIMO_PROVIDER_REPAIR_KEY = "mimoClaudeMislabelRepair_2026_06";
|
|
261
266
|
const DSH_LEGACY_SOURCE_MIGRATION_KEY = "deepseekHarnessSourceMigration_2026_08";
|
|
262
267
|
const ZCODE_NATIVE_USAGE_REPAIR_KEY = "zcodeNativeUsageRepair_2026_08";
|
|
268
|
+
const ZCODE_INCLUSIVE_TOKEN_REPAIR_KEY = "zcodeInclusiveTokenRepair_2026_09";
|
|
263
269
|
const AUTO_SYNC_SOURCE_ALIASES = new Map([
|
|
264
270
|
["code", "every-code"],
|
|
265
271
|
["deepseek", "dsh"],
|
|
@@ -291,6 +297,7 @@ const AUTO_SYNC_SOURCES = new Set([
|
|
|
291
297
|
"kiro",
|
|
292
298
|
"kimi",
|
|
293
299
|
"kimi-code",
|
|
300
|
+
"lmstudio",
|
|
294
301
|
"mimo",
|
|
295
302
|
"omp",
|
|
296
303
|
"opencode",
|
|
@@ -301,6 +308,7 @@ const AUTO_SYNC_SOURCES = new Set([
|
|
|
301
308
|
"reasonix",
|
|
302
309
|
"roocode",
|
|
303
310
|
"trae-cn",
|
|
311
|
+
"unsloth",
|
|
304
312
|
"workbuddy",
|
|
305
313
|
"zcode",
|
|
306
314
|
"zed",
|
|
@@ -561,6 +569,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
561
569
|
legacyBaseUrlMigration = {
|
|
562
570
|
previousDeviceToken,
|
|
563
571
|
replacementDeviceToken,
|
|
572
|
+
hadPersistedAnonKey: Object.prototype.hasOwnProperty.call(config, "anonKey"),
|
|
573
|
+
persistedAnonKey: config.anonKey,
|
|
564
574
|
};
|
|
565
575
|
}
|
|
566
576
|
const codexCursorRoots = [process.env.CODEX_HOME || path.join(home, ".codex")];
|
|
@@ -1432,6 +1442,13 @@ async function cmdSync(argv, context = {}) {
|
|
|
1432
1442
|
projectQueueStatePath,
|
|
1433
1443
|
});
|
|
1434
1444
|
}
|
|
1445
|
+
await repairZcodeInclusiveTokenMigration({
|
|
1446
|
+
cursors,
|
|
1447
|
+
queuePath,
|
|
1448
|
+
queueStatePath,
|
|
1449
|
+
projectQueuePath,
|
|
1450
|
+
projectQueueStatePath,
|
|
1451
|
+
});
|
|
1435
1452
|
zcodeResult = await multiInstallParse({
|
|
1436
1453
|
paths: zcodePaths, parserFn: parseOpencodeDbForInstall, providerName: "zcode",
|
|
1437
1454
|
cursors, getParams: (p) => ({ dbPath: p, readFn: readZcodeDbMessages, source: "zcode", cursorKey: "zcode" }),
|
|
@@ -1477,6 +1494,47 @@ async function cmdSync(argv, context = {}) {
|
|
|
1477
1494
|
}
|
|
1478
1495
|
}
|
|
1479
1496
|
|
|
1497
|
+
// ── LM Studio and Unsloth Studio — passive inference usage ──
|
|
1498
|
+
let lmstudioResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1499
|
+
if (sourceAllowed("lmstudio")) {
|
|
1500
|
+
const lmstudioLogFiles = await resolveLmstudioLogFiles(process.env);
|
|
1501
|
+
if (lmstudioLogFiles.length > 0) {
|
|
1502
|
+
if (progress?.enabled) {
|
|
1503
|
+
progress.start(
|
|
1504
|
+
`Parsing LM Studio ${renderBar(0)} 0/${formatNumber(lmstudioLogFiles.length)} logs | buckets 0`,
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
try {
|
|
1508
|
+
lmstudioResult = await parseLmstudioIncremental({
|
|
1509
|
+
logFiles: lmstudioLogFiles,
|
|
1510
|
+
cursors,
|
|
1511
|
+
queuePath,
|
|
1512
|
+
onProgress: makeProviderProgress("LM Studio"),
|
|
1513
|
+
});
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
warnProviderParseFailure("LM Studio", err, opts);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
let unslothResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1521
|
+
if (sourceAllowed("unsloth")) {
|
|
1522
|
+
const unslothDbPath = resolveUnslothDbPath(process.env);
|
|
1523
|
+
if (unslothDbPath && fssync.existsSync(unslothDbPath)) {
|
|
1524
|
+
if (progress?.enabled) progress.start(`Parsing Unsloth ${renderBar(0)} | buckets 0`);
|
|
1525
|
+
try {
|
|
1526
|
+
unslothResult = await parseUnslothIncremental({
|
|
1527
|
+
dbPath: unslothDbPath,
|
|
1528
|
+
cursors,
|
|
1529
|
+
queuePath,
|
|
1530
|
+
onProgress: makeProviderProgress("Unsloth"),
|
|
1531
|
+
});
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
warnProviderParseFailure("Unsloth", err, opts);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1480
1538
|
// ── AnythingLLM Desktop (workspace_chats.response.metrics) ──
|
|
1481
1539
|
let anythingllmResult = { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
|
|
1482
1540
|
if (sourceAllowed("anythingllm")) {
|
|
@@ -2747,6 +2805,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
2747
2805
|
reasonixResult.recordsProcessed +
|
|
2748
2806
|
grokResult.recordsProcessed +
|
|
2749
2807
|
copilotResult.recordsProcessed +
|
|
2808
|
+
lmstudioResult.recordsProcessed +
|
|
2809
|
+
unslothResult.recordsProcessed +
|
|
2750
2810
|
anythingllmResult.recordsProcessed +
|
|
2751
2811
|
kiloResult.recordsProcessed +
|
|
2752
2812
|
mimoResult.recordsProcessed +
|
|
@@ -2783,6 +2843,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
2783
2843
|
reasonixResult.bucketsQueued +
|
|
2784
2844
|
grokResult.bucketsQueued +
|
|
2785
2845
|
copilotResult.bucketsQueued +
|
|
2846
|
+
lmstudioResult.bucketsQueued +
|
|
2847
|
+
unslothResult.bucketsQueued +
|
|
2786
2848
|
anythingllmResult.bucketsQueued +
|
|
2787
2849
|
kiloResult.bucketsQueued +
|
|
2788
2850
|
mimoResult.bucketsQueued +
|
|
@@ -2835,6 +2897,9 @@ async function cmdSync(argv, context = {}) {
|
|
|
2835
2897
|
progress?.stop();
|
|
2836
2898
|
|
|
2837
2899
|
const runtimeConfig = config ? { ...config } : {};
|
|
2900
|
+
if (legacyBaseUrlMigration) {
|
|
2901
|
+
delete runtimeConfig.anonKey;
|
|
2902
|
+
}
|
|
2838
2903
|
if (legacyBaseUrlMigration?.replacementDeviceToken) {
|
|
2839
2904
|
runtimeConfig.deviceToken = legacyBaseUrlMigration.replacementDeviceToken;
|
|
2840
2905
|
}
|
|
@@ -2888,6 +2953,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
2888
2953
|
const drainWithToken = (deviceToken) =>
|
|
2889
2954
|
drainQueueToCloud({
|
|
2890
2955
|
baseUrl: runtime.baseUrl,
|
|
2956
|
+
anonKey: runtime.anonKey,
|
|
2891
2957
|
deviceToken,
|
|
2892
2958
|
queuePath,
|
|
2893
2959
|
queueStatePath,
|
|
@@ -2917,15 +2983,25 @@ async function cmdSync(argv, context = {}) {
|
|
|
2917
2983
|
if (legacyBaseUrlMigration && uploadResult.batches > 0) {
|
|
2918
2984
|
// device-login does not share the sync lock and may have written a
|
|
2919
2985
|
// fresh current-backend config while the scan/upload was running.
|
|
2920
|
-
// Re-read before committing
|
|
2921
|
-
//
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2986
|
+
// Re-read before committing and only remove the anonymous key this
|
|
2987
|
+
// migration observed. A concurrent login may replace the backend URL
|
|
2988
|
+
// while preserving the old key, or another writer may replace the key
|
|
2989
|
+
// while the legacy URL is still present.
|
|
2990
|
+
await updateJsonLocked(configPath, async (latestConfig) => {
|
|
2991
|
+
const hasLegacyBaseUrl = isLegacyInsforgeBaseUrl(latestConfig.baseUrl);
|
|
2992
|
+
const hasUnchangedLegacyAnonKey =
|
|
2993
|
+
legacyBaseUrlMigration.hadPersistedAnonKey &&
|
|
2994
|
+
latestConfig.anonKey === legacyBaseUrlMigration.persistedAnonKey;
|
|
2995
|
+
if (!hasLegacyBaseUrl && !hasUnchangedLegacyAnonKey) return null;
|
|
2996
|
+
if (hasLegacyBaseUrl) {
|
|
2997
|
+
latestConfig.deviceToken = successfulDeviceToken;
|
|
2998
|
+
delete latestConfig.baseUrl;
|
|
2999
|
+
}
|
|
3000
|
+
if (hasUnchangedLegacyAnonKey) {
|
|
3001
|
+
delete latestConfig.anonKey;
|
|
3002
|
+
}
|
|
3003
|
+
return latestConfig;
|
|
3004
|
+
});
|
|
2929
3005
|
}
|
|
2930
3006
|
// Record success so the exponential backoff step resets — otherwise
|
|
2931
3007
|
// a single past failure keeps us pessimistically throttled forever.
|
|
@@ -3138,6 +3214,7 @@ module.exports = {
|
|
|
3138
3214
|
repairDroidDuplicateSessionInflation,
|
|
3139
3215
|
repairMimoClaudeMislabel,
|
|
3140
3216
|
repairZcodeNativeUsageMigration,
|
|
3217
|
+
repairZcodeInclusiveTokenMigration,
|
|
3141
3218
|
reincludeClaudeMemObserverFiles,
|
|
3142
3219
|
repairGrokQueueFromSessionSnapshots,
|
|
3143
3220
|
applyCloudConversationsBackfill,
|
|
@@ -3613,7 +3690,7 @@ const AUTO_RETRY_MAX_DELAY_MS = 2 * 60 * 60 * 1000;
|
|
|
3613
3690
|
const INGEST_SLUG = "tokentracker-ingest";
|
|
3614
3691
|
const MAX_INGEST_BUCKETS = 500;
|
|
3615
3692
|
|
|
3616
|
-
async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePath, maxBatches = 5, batchSize = 200 }) {
|
|
3693
|
+
async function drainQueueToCloud({ baseUrl, anonKey, deviceToken, queuePath, queueStatePath, maxBatches = 5, batchSize = 200 }) {
|
|
3617
3694
|
const state = (await readJson(queueStatePath)) || { offset: 0 };
|
|
3618
3695
|
let offset = Number(state.offset || 0);
|
|
3619
3696
|
let inserted = 0;
|
|
@@ -3632,7 +3709,6 @@ async function drainQueueToCloud({ baseUrl, deviceToken, queuePath, queueStatePa
|
|
|
3632
3709
|
if (result.buckets.length === 0 && result.sessionStates.length === 0) break;
|
|
3633
3710
|
|
|
3634
3711
|
const root = baseUrl.replace(/\/$/, "");
|
|
3635
|
-
const anonKey = process.env.TOKENTRACKER_INSFORGE_ANON_KEY || "";
|
|
3636
3712
|
const headers = {
|
|
3637
3713
|
"Content-Type": "application/json",
|
|
3638
3714
|
Accept: "application/json",
|
|
@@ -4116,7 +4192,7 @@ async function repairMimoClaudeMislabel({
|
|
|
4116
4192
|
return true;
|
|
4117
4193
|
}
|
|
4118
4194
|
|
|
4119
|
-
async function
|
|
4195
|
+
async function resetUploadOffsetForZcodeRepair(queueStatePath, note) {
|
|
4120
4196
|
if (typeof queueStatePath !== "string" || !queueStatePath) return false;
|
|
4121
4197
|
let state = {};
|
|
4122
4198
|
try {
|
|
@@ -4126,7 +4202,7 @@ async function resetUploadOffsetForZcodeNativeRepair(queueStatePath) {
|
|
|
4126
4202
|
}
|
|
4127
4203
|
state.offset = 0;
|
|
4128
4204
|
state.updatedAt = new Date().toISOString();
|
|
4129
|
-
state.note =
|
|
4205
|
+
state.note = note;
|
|
4130
4206
|
await ensureDir(path.dirname(queueStatePath));
|
|
4131
4207
|
await fs.writeFile(queueStatePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
4132
4208
|
return true;
|
|
@@ -4188,7 +4264,9 @@ async function dropZcodeQueueRows(filePath, { retainRetractions = false } = {})
|
|
|
4188
4264
|
return { removed, retractions: retractions.size };
|
|
4189
4265
|
}
|
|
4190
4266
|
|
|
4191
|
-
async function
|
|
4267
|
+
async function repairZcodeUsageMigration({
|
|
4268
|
+
migrationKey,
|
|
4269
|
+
queueStateNote,
|
|
4192
4270
|
cursors,
|
|
4193
4271
|
queuePath,
|
|
4194
4272
|
queueStatePath,
|
|
@@ -4197,7 +4275,7 @@ async function repairZcodeNativeUsageMigration({
|
|
|
4197
4275
|
} = {}) {
|
|
4198
4276
|
if (!cursors || typeof cursors !== "object") return false;
|
|
4199
4277
|
const migrations = (cursors.migrations ||= {});
|
|
4200
|
-
if (migrations[
|
|
4278
|
+
if (migrations[migrationKey]) return false;
|
|
4201
4279
|
|
|
4202
4280
|
const mainRepair = await dropZcodeQueueRows(queuePath, { retainRetractions: true });
|
|
4203
4281
|
const projectRepair = projectQueuePath
|
|
@@ -4225,11 +4303,13 @@ async function repairZcodeNativeUsageMigration({
|
|
|
4225
4303
|
}
|
|
4226
4304
|
delete cursors.zcode;
|
|
4227
4305
|
|
|
4228
|
-
if (mainRepair.removed > 0)
|
|
4306
|
+
if (mainRepair.removed > 0) {
|
|
4307
|
+
await resetUploadOffsetForZcodeRepair(queueStatePath, queueStateNote);
|
|
4308
|
+
}
|
|
4229
4309
|
if (projectRepair.removed > 0) {
|
|
4230
|
-
await
|
|
4310
|
+
await resetUploadOffsetForZcodeRepair(projectQueueStatePath, queueStateNote);
|
|
4231
4311
|
}
|
|
4232
|
-
migrations[
|
|
4312
|
+
migrations[migrationKey] = {
|
|
4233
4313
|
appliedAt: new Date().toISOString(),
|
|
4234
4314
|
removedMain: mainRepair.removed,
|
|
4235
4315
|
removedProject: projectRepair.removed,
|
|
@@ -4238,6 +4318,22 @@ async function repairZcodeNativeUsageMigration({
|
|
|
4238
4318
|
return true;
|
|
4239
4319
|
}
|
|
4240
4320
|
|
|
4321
|
+
async function repairZcodeNativeUsageMigration(options = {}) {
|
|
4322
|
+
return repairZcodeUsageMigration({
|
|
4323
|
+
...options,
|
|
4324
|
+
migrationKey: ZCODE_NATIVE_USAGE_REPAIR_KEY,
|
|
4325
|
+
queueStateNote: "reset_after_zcode_native_usage_repair_2026_08",
|
|
4326
|
+
});
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
async function repairZcodeInclusiveTokenMigration(options = {}) {
|
|
4330
|
+
return repairZcodeUsageMigration({
|
|
4331
|
+
...options,
|
|
4332
|
+
migrationKey: ZCODE_INCLUSIVE_TOKEN_REPAIR_KEY,
|
|
4333
|
+
queueStateNote: "reset_after_zcode_inclusive_token_repair_2026_09",
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4241
4337
|
async function repairGrokQueueFromSessionSnapshots({ cursors, queuePath, queueStatePath } = {}) {
|
|
4242
4338
|
if (!cursors || typeof cursors !== "object") return false;
|
|
4243
4339
|
const grokState = (cursors.grok ||= {});
|
package/src/lib/cloud-account.js
CHANGED
|
@@ -73,8 +73,27 @@ function decodeJwtExpMs(token) {
|
|
|
73
73
|
// The popover polls frequently, so caching avoids hammering /api/auth/refresh.
|
|
74
74
|
let tokenCache = { cacheKey: null, accessToken: null, expMs: 0 };
|
|
75
75
|
|
|
76
|
+
// In-flight refresh de-duplication ("single flight"), keyed the same way as
|
|
77
|
+
// `tokenCache`. A full popover refresh fires six account reads concurrently; on
|
|
78
|
+
// a cold cache every one of them used to POST /api/auth/refresh with the SAME
|
|
79
|
+
// refresh token. When the backend rotates refresh tokens, the losers of that
|
|
80
|
+
// race present an already-consumed token and fail — which is exactly the
|
|
81
|
+
// intermittent "Activity silently dropped to this-machine data" symptom.
|
|
82
|
+
const mintInflight = new Map();
|
|
83
|
+
|
|
76
84
|
function __resetCloudAccountCacheForTests() {
|
|
77
85
|
tokenCache = { cacheKey: null, accessToken: null, expMs: 0 };
|
|
86
|
+
mintInflight.clear();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Failure classes surfaced to local-api so it can tell an intentional local
|
|
90
|
+
// view ("signed out", "cloud sync off") apart from a temporary cloud outage.
|
|
91
|
+
class AccountAuthError extends Error {
|
|
92
|
+
constructor(code, message) {
|
|
93
|
+
super(message || code);
|
|
94
|
+
this.name = "AccountAuthError";
|
|
95
|
+
this.code = code;
|
|
96
|
+
}
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
function csrfTokenFromRefreshPayload(data) {
|
|
@@ -87,30 +106,11 @@ function csrfTokenFromRefreshPayload(data) {
|
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
109
|
+
* Perform the actual refresh POST. Throws a classified `AccountAuthError` on
|
|
110
|
+
* every failure so callers can distinguish a transient outage (timeout,
|
|
111
|
+
* offline, rotated-token rejection) from an intentionally local view.
|
|
93
112
|
*/
|
|
94
|
-
async function
|
|
95
|
-
baseUrl,
|
|
96
|
-
anonKey,
|
|
97
|
-
refreshToken,
|
|
98
|
-
fetchImpl = fetch,
|
|
99
|
-
now = Date.now,
|
|
100
|
-
skewMs = 60_000,
|
|
101
|
-
timeoutMs,
|
|
102
|
-
} = {}) {
|
|
103
|
-
if (!refreshToken) return null;
|
|
104
|
-
const root = String(baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
105
|
-
const cacheKey = `${root}\0${refreshToken}`;
|
|
106
|
-
if (
|
|
107
|
-
tokenCache.cacheKey === cacheKey &&
|
|
108
|
-
tokenCache.accessToken &&
|
|
109
|
-
tokenCache.expMs - skewMs > now()
|
|
110
|
-
) {
|
|
111
|
-
return { accessToken: tokenCache.accessToken, refreshToken: null, csrfToken: null };
|
|
112
|
-
}
|
|
113
|
-
|
|
113
|
+
async function performMint({ root, cacheKey, anonKey, refreshToken, fetchImpl, now, timeoutMs }) {
|
|
114
114
|
const headers = { "Content-Type": "application/json", Accept: "application/json" };
|
|
115
115
|
if (anonKey) headers.apikey = anonKey;
|
|
116
116
|
|
|
@@ -132,24 +132,37 @@ async function mintAccessToken({
|
|
|
132
132
|
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
133
133
|
signal,
|
|
134
134
|
});
|
|
135
|
-
} catch {
|
|
136
|
-
|
|
135
|
+
} catch (e) {
|
|
136
|
+
const aborted = e && (e.name === "AbortError" || e.name === "TimeoutError");
|
|
137
|
+
throw new AccountAuthError(
|
|
138
|
+
aborted ? "auth_timeout" : "auth_network",
|
|
139
|
+
e?.message || "token refresh failed",
|
|
140
|
+
);
|
|
137
141
|
} finally {
|
|
138
142
|
if (timeoutId) {
|
|
139
143
|
clearTimeout(timeoutId);
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
|
-
if (!res || !res.ok)
|
|
146
|
+
if (!res || !res.ok) {
|
|
147
|
+
const err = new AccountAuthError(
|
|
148
|
+
"auth_rejected",
|
|
149
|
+
`token refresh failed with HTTP ${res ? res.status : "?"}`,
|
|
150
|
+
);
|
|
151
|
+
err.status = res ? res.status : 0;
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
143
154
|
|
|
144
155
|
let data = null;
|
|
145
156
|
try {
|
|
146
157
|
data = await res.json();
|
|
147
|
-
} catch {
|
|
148
|
-
|
|
158
|
+
} catch (e) {
|
|
159
|
+
throw new AccountAuthError("auth_invalid", e?.message || "token refresh returned invalid JSON");
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
const accessToken = accessTokenFromRefreshPayload(data);
|
|
152
|
-
if (!accessToken)
|
|
163
|
+
if (!accessToken) {
|
|
164
|
+
throw new AccountAuthError("auth_invalid", "token refresh returned no access token");
|
|
165
|
+
}
|
|
153
166
|
|
|
154
167
|
const expMs = decodeJwtExpMs(accessToken) || now() + 10 * 60_000;
|
|
155
168
|
tokenCache = { cacheKey, accessToken, expMs };
|
|
@@ -166,6 +179,60 @@ async function mintAccessToken({
|
|
|
166
179
|
};
|
|
167
180
|
}
|
|
168
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Mint (or reuse a cached) InsForge access token from a refresh token.
|
|
184
|
+
*
|
|
185
|
+
* Concurrent callers with the same (baseUrl, refreshToken) share ONE in-flight
|
|
186
|
+
* refresh; see `mintInflight`.
|
|
187
|
+
*
|
|
188
|
+
* @param {boolean} [throwOnFailure] when true, reject with a classified
|
|
189
|
+
* `AccountAuthError` instead of resolving to `null`.
|
|
190
|
+
* @returns {Promise<{accessToken: string, refreshToken: string|null, csrfToken: string|null}|null>}
|
|
191
|
+
* null when no refresh token is available or the refresh failed.
|
|
192
|
+
*/
|
|
193
|
+
async function mintAccessToken({
|
|
194
|
+
baseUrl,
|
|
195
|
+
anonKey,
|
|
196
|
+
refreshToken,
|
|
197
|
+
fetchImpl = fetch,
|
|
198
|
+
now = Date.now,
|
|
199
|
+
skewMs = 60_000,
|
|
200
|
+
timeoutMs,
|
|
201
|
+
throwOnFailure = false,
|
|
202
|
+
} = {}) {
|
|
203
|
+
if (!refreshToken) {
|
|
204
|
+
if (throwOnFailure) throw new AccountAuthError("auth_missing_refresh_token", "not signed in");
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const root = String(baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
208
|
+
const cacheKey = `${root}\0${refreshToken}`;
|
|
209
|
+
if (
|
|
210
|
+
tokenCache.cacheKey === cacheKey &&
|
|
211
|
+
tokenCache.accessToken &&
|
|
212
|
+
tokenCache.expMs - skewMs > now()
|
|
213
|
+
) {
|
|
214
|
+
return { accessToken: tokenCache.accessToken, refreshToken: null, csrfToken: null };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let inflight = mintInflight.get(cacheKey);
|
|
218
|
+
if (!inflight) {
|
|
219
|
+
inflight = performMint({ root, cacheKey, anonKey, refreshToken, fetchImpl, now, timeoutMs })
|
|
220
|
+
.finally(() => {
|
|
221
|
+
// Only clear our own entry: a later caller may already have started a
|
|
222
|
+
// fresh mint after this one settled.
|
|
223
|
+
if (mintInflight.get(cacheKey) === inflight) mintInflight.delete(cacheKey);
|
|
224
|
+
});
|
|
225
|
+
mintInflight.set(cacheKey, inflight);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
return await inflight;
|
|
230
|
+
} catch (e) {
|
|
231
|
+
if (throwOnFailure) throw e;
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
169
236
|
/**
|
|
170
237
|
* GET a `tokentracker-account-*` edge function, forwarding the popover's query
|
|
171
238
|
* params (minus `account`/`scope`, which are local-only routing knobs).
|
|
@@ -237,6 +304,7 @@ async function fetchAccountUsage({
|
|
|
237
304
|
} = {}) {
|
|
238
305
|
const slug = accountSlugFor(usageSlug);
|
|
239
306
|
if (!slug) return null;
|
|
307
|
+
if (!refreshToken) return null;
|
|
240
308
|
|
|
241
309
|
const startTime = now();
|
|
242
310
|
const getRemainingTimeout = () => {
|
|
@@ -246,6 +314,9 @@ async function fetchAccountUsage({
|
|
|
246
314
|
return remaining > 0 ? remaining : 1;
|
|
247
315
|
};
|
|
248
316
|
|
|
317
|
+
// `throwOnFailure` keeps "not signed in" (null, handled above) distinct from
|
|
318
|
+
// "signed in but the refresh call failed" (throws) so the caller does not
|
|
319
|
+
// silently downgrade an account view into a this-machine view.
|
|
249
320
|
const minted = await mintAccessToken({
|
|
250
321
|
baseUrl,
|
|
251
322
|
anonKey,
|
|
@@ -253,6 +324,7 @@ async function fetchAccountUsage({
|
|
|
253
324
|
fetchImpl,
|
|
254
325
|
now,
|
|
255
326
|
timeoutMs: getRemainingTimeout(),
|
|
327
|
+
throwOnFailure: true,
|
|
256
328
|
});
|
|
257
329
|
if (!minted) return null;
|
|
258
330
|
|
|
@@ -269,6 +341,7 @@ async function fetchAccountUsage({
|
|
|
269
341
|
}
|
|
270
342
|
|
|
271
343
|
module.exports = {
|
|
344
|
+
AccountAuthError,
|
|
272
345
|
USAGE_TO_ACCOUNT_SLUG,
|
|
273
346
|
accountSlugFor,
|
|
274
347
|
accessTokenFromRefreshPayload,
|
package/src/lib/fs.js
CHANGED
|
@@ -413,6 +413,35 @@ async function openLock(
|
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
async function updateJsonLocked(
|
|
417
|
+
filePath,
|
|
418
|
+
update,
|
|
419
|
+
{ timeoutMs = 30_000, retryMs = 10 } = {},
|
|
420
|
+
) {
|
|
421
|
+
const lockPath = `${filePath}.lock`;
|
|
422
|
+
const deadline = Date.now() + timeoutMs;
|
|
423
|
+
let lock = null;
|
|
424
|
+
while (!lock) {
|
|
425
|
+
lock = await openLock(lockPath, { quietIfLocked: true });
|
|
426
|
+
if (lock) break;
|
|
427
|
+
if (Date.now() >= deadline) {
|
|
428
|
+
throw new Error(`Timed out waiting to update JSON file: ${filePath}`);
|
|
429
|
+
}
|
|
430
|
+
await new Promise((resolve) => setTimeout(resolve, retryMs));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const current = (await readJson(filePath)) || {};
|
|
435
|
+
const next = await update(current);
|
|
436
|
+
if (next == null) return current;
|
|
437
|
+
await writeJson(filePath, next);
|
|
438
|
+
await chmod600IfPossible(filePath);
|
|
439
|
+
return next;
|
|
440
|
+
} finally {
|
|
441
|
+
await lock.release();
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
416
445
|
module.exports = {
|
|
417
446
|
ensureDir,
|
|
418
447
|
writeFileAtomic,
|
|
@@ -422,4 +451,5 @@ module.exports = {
|
|
|
422
451
|
chmod600IfPossible,
|
|
423
452
|
openLock,
|
|
424
453
|
inspectLock,
|
|
454
|
+
updateJsonLocked,
|
|
425
455
|
};
|
|
@@ -26,8 +26,9 @@ const {
|
|
|
26
26
|
sanitizeCommandSignature,
|
|
27
27
|
getExecutableName,
|
|
28
28
|
} = require("./categorizer-utils");
|
|
29
|
+
const { normalizeGrokUsage } = require("./grok-usage");
|
|
29
30
|
|
|
30
|
-
const CACHE_SCHEMA_VERSION = "grok-context-
|
|
31
|
+
const CACHE_SCHEMA_VERSION = "grok-context-v3";
|
|
31
32
|
const CACHE = new Map();
|
|
32
33
|
// Result-level TTL. Per-file parse cache is the main win when flipping tabs.
|
|
33
34
|
const CACHE_TTL_MS = 5 * 60_000;
|
|
@@ -246,31 +247,7 @@ function outputSizeBucket(lines, chars) {
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
function readUsageTotals(usage) {
|
|
249
|
-
|
|
250
|
-
const inputRaw = Math.max(0, Number(usage.inputTokens ?? usage.input_tokens ?? 0) || 0);
|
|
251
|
-
const cached = Math.max(
|
|
252
|
-
0,
|
|
253
|
-
Number(usage.cachedReadTokens ?? usage.cache_read_input_tokens ?? usage.cached_input_tokens ?? 0) || 0,
|
|
254
|
-
);
|
|
255
|
-
const output = Math.max(0, Number(usage.outputTokens ?? usage.output_tokens ?? 0) || 0);
|
|
256
|
-
const reasoning = Math.max(
|
|
257
|
-
0,
|
|
258
|
-
Number(usage.reasoningTokens ?? usage.reasoning_output_tokens ?? 0) || 0,
|
|
259
|
-
);
|
|
260
|
-
const nonCachedInput = Math.max(0, inputRaw - cached);
|
|
261
|
-
// Prefer the reported totalTokens (authoritative billable total). Fallback
|
|
262
|
-
// includes reasoning so a missing total still matches usage-parser shape.
|
|
263
|
-
let total = Math.max(0, Number(usage.totalTokens ?? usage.total_tokens ?? 0) || 0);
|
|
264
|
-
if (total <= 0) total = nonCachedInput + cached + output + reasoning;
|
|
265
|
-
if (total <= 0) return null;
|
|
266
|
-
return {
|
|
267
|
-
input_tokens: nonCachedInput,
|
|
268
|
-
cached_input_tokens: cached,
|
|
269
|
-
cache_creation_input_tokens: 0,
|
|
270
|
-
output_tokens: output,
|
|
271
|
-
reasoning_output_tokens: reasoning,
|
|
272
|
-
total_tokens: total,
|
|
273
|
-
};
|
|
250
|
+
return normalizeGrokUsage(usage);
|
|
274
251
|
}
|
|
275
252
|
|
|
276
253
|
// ---------------------------------------------------------------------------
|
|
@@ -778,15 +755,17 @@ async function computeGrokContextBreakdownUncached({
|
|
|
778
755
|
}))
|
|
779
756
|
.sort((a, b) => (b.totals?.total_tokens || 0) - (a.totals?.total_tokens || 0));
|
|
780
757
|
|
|
781
|
-
// Message breakdown
|
|
782
|
-
|
|
783
|
-
|
|
758
|
+
// Message breakdown uses the same mutually exclusive columns as the parser.
|
|
759
|
+
// Grok's raw outputTokens includes reasoning, but readUsageTotals has already
|
|
760
|
+
// split it, so subtracting reasoning here would undercount assistant output.
|
|
784
761
|
const reasoning = Number(grand.reasoning_output_tokens || 0);
|
|
785
|
-
const assistantOut =
|
|
762
|
+
const assistantOut = Number(grand.output_tokens || 0);
|
|
786
763
|
// For Grok, cached reads dominate conversation history; non-cached input is
|
|
787
764
|
// closer to "current user/context injection". Heuristic only — matches Codex
|
|
788
765
|
// message_breakdown intent without reading message bodies.
|
|
789
|
-
const
|
|
766
|
+
const cachedHistoryTokens = Number(grand.cached_input_tokens || 0);
|
|
767
|
+
const cacheCreationTokens = Number(grand.cache_creation_input_tokens || 0);
|
|
768
|
+
const historyTokens = cachedHistoryTokens + cacheCreationTokens;
|
|
790
769
|
const userish = Number(grand.input_tokens || 0);
|
|
791
770
|
const messageBreakdown = [
|
|
792
771
|
{
|
|
@@ -806,8 +785,8 @@ async function computeGrokContextBreakdownUncached({
|
|
|
806
785
|
name: "Conversation history",
|
|
807
786
|
totals: roundTotals({
|
|
808
787
|
input_tokens: 0,
|
|
809
|
-
cached_input_tokens:
|
|
810
|
-
cache_creation_input_tokens:
|
|
788
|
+
cached_input_tokens: cachedHistoryTokens,
|
|
789
|
+
cache_creation_input_tokens: cacheCreationTokens,
|
|
811
790
|
output_tokens: 0,
|
|
812
791
|
reasoning_output_tokens: 0,
|
|
813
792
|
total_tokens: historyTokens,
|
|
@@ -825,6 +804,18 @@ async function computeGrokContextBreakdownUncached({
|
|
|
825
804
|
total_tokens: assistantOut,
|
|
826
805
|
}),
|
|
827
806
|
},
|
|
807
|
+
{
|
|
808
|
+
key: "reasoning",
|
|
809
|
+
name: "Reasoning",
|
|
810
|
+
totals: roundTotals({
|
|
811
|
+
input_tokens: 0,
|
|
812
|
+
cached_input_tokens: 0,
|
|
813
|
+
cache_creation_input_tokens: 0,
|
|
814
|
+
output_tokens: 0,
|
|
815
|
+
reasoning_output_tokens: reasoning,
|
|
816
|
+
total_tokens: reasoning,
|
|
817
|
+
}),
|
|
818
|
+
},
|
|
828
819
|
].sort((a, b) => (b.totals?.total_tokens || 0) - (a.totals?.total_tokens || 0));
|
|
829
820
|
|
|
830
821
|
const serializeExecRows = (rows) =>
|