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.
Files changed (130) hide show
  1. package/README.de.md +9 -7
  2. package/README.ja.md +8 -6
  3. package/README.ko.md +8 -6
  4. package/README.md +23 -10
  5. package/README.zh-CN.md +8 -6
  6. package/dashboard/dist/assets/{AchievementBadge-DPqwk2zI.js → AchievementBadge-Pi3Gvbm8.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-CELHoMQt.js → AchievementsPage-C4WLT8Rl.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-DdAC0VCb.js → AchievementsSection-ilvR2ssr.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-Dd4I1SpM.js → BadgeDetailModal-CjEeToxa.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-DL2-4mnm.js → BotAnimated-DDf9pBuK.js} +1 -1
  11. package/dashboard/dist/assets/{Card-B_MpSRse.js → Card-DNous84-.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-BLZZ4rG6.js → ClawdAnimated-BLMIRntb.js} +2 -2
  13. package/dashboard/dist/assets/{CommandPalette-B-vfdIbH.js → CommandPalette-vVekhoFO.js} +1 -1
  14. package/dashboard/dist/assets/CommunityStatsModal-C0Kp_J8B.js +1 -0
  15. package/dashboard/dist/assets/{ConfirmModal-DLnRwUXC.js → ConfirmModal-DFdpcx12.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-FZ8wEN5w.js → Controls-RNhqybmE.js} +1 -1
  17. package/dashboard/dist/assets/DashboardPage-BQjQh1IQ.js +1 -0
  18. package/dashboard/dist/assets/{DevicePage-D2IzPc5o.js → DevicePage-DIhh-YAk.js} +1 -1
  19. package/dashboard/dist/assets/{DialogClose-BaqQaUUk.js → DialogClose-ZqxYVIzl.js} +1 -1
  20. package/dashboard/dist/assets/DialogDescription-DDM4aanX.js +1 -0
  21. package/dashboard/dist/assets/{DialogTitle-DrbKjtuS.js → DialogTitle-BiRcejlu.js} +1 -1
  22. package/dashboard/dist/assets/{FadeIn-GddKDE8i.js → FadeIn-BDBQpeGe.js} +1 -1
  23. package/dashboard/dist/assets/{HeaderGithubStar-DUu-Z5mE.js → HeaderGithubStar-CztIwtKT.js} +1 -1
  24. package/dashboard/dist/assets/{HoverTooltip-BmDkY35A.js → HoverTooltip-Cfso7LTC.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-CGJyqWk2.js → IpCheckPage-CNE7dLs3.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-Bd91ALUj.js → LandingPage-B2a3cweM.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-BGGUwXFd.js → LeaderboardAvatar-CwmZnIji.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-BMEqHwif.js → LeaderboardPage-DkTHj3fB.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-BSweJqm2.js → LeaderboardProfileModal-CefTO3Zh.js} +3 -3
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-CRrI3RFz.js → LeaderboardProfilePage-CnOXlQ3L.js} +1 -1
  31. package/dashboard/dist/assets/{LimitsPage-XIRDv46K.js → LimitsPage-BIZ1-6BB.js} +2 -2
  32. package/dashboard/dist/assets/{LocalOnlyNotice-CCD-lGWG.js → LocalOnlyNotice-CdHQiPhy.js} +1 -1
  33. package/dashboard/dist/assets/{LoginCard-lDuqSv4f.js → LoginCard-yXLA9geu.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-DStt5VXN.js → LoginModal-BF2Pfkqa.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-dmVPSL72.js → LoginPage-DuOypZap.js} +1 -1
  36. package/dashboard/dist/assets/{PetPage-ybcZRctF.js → PetPage-BH3xDVV9.js} +1 -1
  37. package/dashboard/dist/assets/{PopoverPopup-Dar_UjxF.js → PopoverPopup-CewF_cGz.js} +1 -1
  38. package/dashboard/dist/assets/{ProviderIcon-Ct9tILpz.js → ProviderIcon-Dg0K0RPn.js} +1 -1
  39. package/dashboard/dist/assets/{ResetPasswordPage-B4yLh5zx.js → ResetPasswordPage-CYwt85O3.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-BOE1CEzq.js → SegmentedControl-BJkXXomQ.js} +1 -1
  41. package/dashboard/dist/assets/{Select-D-bSh7Zz.js → Select-B3_WH0Qe.js} +1 -1
  42. package/dashboard/dist/assets/{SelectItemText-DMYjXXq4.js → SelectItemText-BUby_DUu.js} +1 -1
  43. package/dashboard/dist/assets/{ServiceStatusPage-CaleS-6_.js → ServiceStatusPage-BDlVZk4i.js} +1 -1
  44. package/dashboard/dist/assets/SessionsPage-TaC9nahK.js +2 -0
  45. package/dashboard/dist/assets/{SettingsPage-iJH0Yyah.js → SettingsPage-CudcQh6m.js} +2 -2
  46. package/dashboard/dist/assets/SkillsPage-iDLQ8lpZ.js +1 -0
  47. package/dashboard/dist/assets/{TokenGalaxy-CuXKoMPg.js → TokenGalaxy-DtYzUsXU.js} +1 -1
  48. package/dashboard/dist/assets/{ToolbarRootContext-Dx5GB7F5.js → ToolbarRootContext-PC8Tg1Jz.js} +1 -1
  49. package/dashboard/dist/assets/TrendMonitor-r8n8YO_9.js +19 -0
  50. package/dashboard/dist/assets/{WidgetsPage-DUaIyGtu.js → WidgetsPage-vzfGccWb.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-BzqlcAUC.js → WrappedPage-DaKuIkYE.js} +1 -1
  52. package/dashboard/dist/assets/{__vite-browser-external-BHz2N7Ih.css → __vite-browser-external-CCMdjCX2.css} +1 -1
  53. package/dashboard/dist/assets/__vite-browser-external-Dnxl50k1.js +45 -0
  54. package/dashboard/dist/assets/{agent-logos-BdWWxza6.js → agent-logos-B7nzn3n2.js} +1 -1
  55. package/dashboard/dist/assets/{arrow-up-right-CANSzmm-.js → arrow-up-right-CsxyvCER.js} +1 -1
  56. package/dashboard/dist/assets/{check-BnJpzXRZ.js → check-hYcC7MdH.js} +1 -1
  57. package/dashboard/dist/assets/{composite-Ccj6KQmh.js → composite-C79LNbex.js} +1 -1
  58. package/dashboard/dist/assets/{copy-C2Yc8Evu.js → copy-Cz4qzE62.js} +1 -1
  59. package/dashboard/dist/assets/{download-B52JTk6Y.js → download-CUaSNE2E.js} +1 -1
  60. package/dashboard/dist/assets/{external-link-DayTwZx-.js → external-link-vq7kIyKK.js} +1 -1
  61. package/dashboard/dist/assets/{flame-B8GEX2yT.js → flame-CB6K4eR6.js} +1 -1
  62. package/dashboard/dist/assets/{format-tokens-mhkbohkb.js → format-tokens-KI-e_JQ-.js} +1 -1
  63. package/dashboard/dist/assets/{icons-o8bPXqRP.js → icons-3maItkrp.js} +1 -1
  64. package/dashboard/dist/assets/{index-MBVWUKpA.js → index-BRXoZ7Jj.js} +1 -1
  65. package/dashboard/dist/assets/{index-CK_CduLm.js → index-C5WrfmLB.js} +1 -1
  66. package/dashboard/dist/assets/{index-Bp0ZHDtz.js → index-CUbC50Zp.js} +1 -1
  67. package/dashboard/dist/assets/{info-D94QM_vX.js → info-r0yGihUe.js} +1 -1
  68. package/dashboard/dist/assets/{limits-providers-BLzMyCiy.js → limits-providers-BN_94oe9.js} +1 -1
  69. package/dashboard/dist/assets/{link-2-CqBmFaDI.js → link-2-J9t0VXWM.js} +1 -1
  70. package/dashboard/dist/assets/{loader-circle-SaxUpDR6.js → loader-circle-DCvOsPze.js} +1 -1
  71. package/dashboard/dist/assets/{maximize-2-BoXqARks.js → maximize-2-CZ9JhGjf.js} +1 -1
  72. package/dashboard/dist/assets/{modifiers.esm-Conh_-BD.js → modifiers.esm-DJ0_WKg0.js} +1 -1
  73. package/dashboard/dist/assets/module-xpSFyNBK.js +3 -0
  74. package/dashboard/dist/assets/{monitor-smartphone-D6qLR9qd.js → monitor-smartphone-nqbWjJx_.js} +1 -1
  75. package/dashboard/dist/assets/{plus-D51mjIc2.js → plus-Cg7wd2jK.js} +1 -1
  76. package/dashboard/dist/assets/{provider-display-DpHPjd_s.js → provider-display-B9L1nRY9.js} +2 -2
  77. package/dashboard/dist/assets/{react-D_AcI466.js → react-D2AD6Pk2.js} +1 -1
  78. package/dashboard/dist/assets/{search-CwLcNdvZ.js → search-uNiWba1h.js} +1 -1
  79. package/dashboard/dist/assets/{skills-api-Dqi7Mjgo.js → skills-api-BBYuAhiL.js} +1 -1
  80. package/dashboard/dist/assets/{sortable.esm-BPDlUxPb.js → sortable.esm-CzPxc949.js} +1 -1
  81. package/dashboard/dist/assets/{store-ikeRIRGd.js → store-D4A7EUwE.js} +1 -1
  82. package/dashboard/dist/assets/{terminal-BfeX3L5T.js → terminal-Crmm_yKF.js} +1 -1
  83. package/dashboard/dist/assets/{trash-2-5_Qlp1k7.js → trash-2-ia9VjRnU.js} +1 -1
  84. package/dashboard/dist/assets/{use-community-stats-D90Fy2_j.js → use-community-stats-KaGCAxaF.js} +1 -1
  85. package/dashboard/dist/assets/use-latest-request-guard-Dy8gSDkk.js +1 -0
  86. package/dashboard/dist/assets/{use-limits-display-prefs-D4BYFaa-.js → use-limits-display-prefs-JSTYlbTD.js} +1 -1
  87. package/dashboard/dist/assets/{use-ordered-list-DJxLNdXY.js → use-ordered-list-C4JnHdE0.js} +1 -1
  88. package/dashboard/dist/assets/use-reduced-motion-AQE0ummK.js +1 -0
  89. package/dashboard/dist/assets/{use-session-efficiency-pref-CAX_GqXc.js → use-session-efficiency-pref-CgyUz5Gc.js} +1 -1
  90. package/dashboard/dist/assets/{use-transform-CeW6Z5R0.js → use-transform-C7-jSJ9k.js} +1 -1
  91. package/dashboard/dist/assets/use-usage-limits-B3YYqn-v.js +1 -0
  92. package/dashboard/dist/assets/{useCurrency-BarGeFyK.js → useCurrency-CK_gcb3J.js} +1 -1
  93. package/dashboard/dist/assets/{useTokenFormat-DUJ9iT9H.js → useTokenFormat-C_a-UkCA.js} +1 -1
  94. package/dashboard/dist/assets/{zap--m0IWoI3.js → zap-BIJNn-Ic.js} +1 -1
  95. package/dashboard/dist/brand-logos/lmstudio.svg +12 -0
  96. package/dashboard/dist/brand-logos/unsloth.svg +1 -0
  97. package/dashboard/dist/index.html +16 -12
  98. package/dashboard/dist/ip-check.html +2 -2
  99. package/dashboard/dist/leaderboard.html +2 -2
  100. package/dashboard/dist/llms.txt +4 -2
  101. package/dashboard/dist/share.html +2 -2
  102. package/package.json +2 -2
  103. package/src/commands/device-login.js +5 -10
  104. package/src/commands/init.js +28 -1
  105. package/src/commands/serve.js +6 -0
  106. package/src/commands/status.js +24 -0
  107. package/src/commands/sync.js +114 -18
  108. package/src/lib/cloud-account.js +102 -29
  109. package/src/lib/fs.js +30 -0
  110. package/src/lib/grok-context-breakdown.js +24 -33
  111. package/src/lib/grok-usage.js +95 -0
  112. package/src/lib/local-api.js +101 -13
  113. package/src/lib/pricing/index.js +15 -0
  114. package/src/lib/pricing/matcher.js +15 -0
  115. package/src/lib/pricing/seed-snapshot.json +1 -1
  116. package/src/lib/rollout.js +1094 -72
  117. package/src/lib/session-analytics.js +100 -40
  118. package/src/lib/usage-limits.js +507 -55
  119. package/src/lib/zcode-limits.js +65 -8
  120. package/dashboard/dist/assets/CommunityStatsModal-aAico565.js +0 -1
  121. package/dashboard/dist/assets/DashboardPage-BByRBsbt.js +0 -1
  122. package/dashboard/dist/assets/DialogDescription-BUrh0Jod.js +0 -1
  123. package/dashboard/dist/assets/SessionsPage-DOaY6_E9.js +0 -2
  124. package/dashboard/dist/assets/SkillsPage-BkzJOBLk.js +0 -1
  125. package/dashboard/dist/assets/TrendMonitor-BuKmsznP.js +0 -19
  126. package/dashboard/dist/assets/__vite-browser-external-CVIGknXl.js +0 -45
  127. package/dashboard/dist/assets/chevron-right-DJm0mbG0.js +0 -1
  128. package/dashboard/dist/assets/module-D2D5r9gC.js +0 -3
  129. package/dashboard/dist/assets/use-reduced-motion-yE3T3cat.js +0 -1
  130. package/dashboard/dist/assets/use-usage-limits-5HP8k9a3.js +0 -1
@@ -31,6 +31,7 @@ const readline = require("node:readline");
31
31
  const { listClaudeProjectFiles, listRolloutFilesDeep, claudeMessageDedupKey } = require("./rollout");
32
32
  const { parseCodexRolloutFile } = require("./codex-rollout-parser");
33
33
  const { computeRowCost } = require("./pricing");
34
+ const { USD_TICKS_PER_USD, normalizeGrokUsage } = require("./grok-usage");
34
35
  const wsl = require("./wsl-probe");
35
36
 
36
37
  // Bump the sidecar when derived metrics change so cached rows are rebuilt
@@ -54,6 +55,8 @@ const wsl = require("./wsl-probe");
54
55
  // stay valid while Grok entries appear on the next full rebuild.
55
56
  // v11 groups native/WSL copies of one logical Claude/Codex session and scans
56
57
  // their union, deduping shared records while retaining divergent tails.
58
+ // v12 fixes Grok's mutually exclusive token columns, retains exact reported
59
+ // cost, and adds metadata-only usage diagnostics to the local session browser.
57
60
  // v12 retains Codex parent/subagent metadata locally and derives exact child
58
61
  // token totals from observed child rollouts instead of spawn-call estimates.
59
62
  const SIDECAR_VERSION = 12;
@@ -240,7 +243,12 @@ function finalizeRecord(record) {
240
243
  record.active_ms = Math.max(0, finite(record.active_ms));
241
244
  record.duration_ms = record.active_ms;
242
245
  record.total_tokens = finite(record.total_tokens || record.tokens?.total_tokens);
243
- record.cost_usd = computeRowCost({ source: record.source, model: record.model, ...record.tokens });
246
+ const providerCost = Number(record.provider_cost_usd);
247
+ record.cost_usd = record.cost_source === "provider_reported"
248
+ && Number.isFinite(providerCost)
249
+ && providerCost >= 0
250
+ ? providerCost
251
+ : computeRowCost({ source: record.source, model: record.model, ...record.tokens });
244
252
  record.productive = record.edit_turns > 0;
245
253
  // A first-pass delivery has exactly one user turn containing an observed
246
254
  // edit and no repeated user request. The legacy one_shot field stays as an
@@ -583,42 +591,6 @@ function grokTimestampIso(obj) {
583
591
  return new Date(ms).toISOString();
584
592
  }
585
593
 
586
- // Grok reports inputTokens as the full prompt (including cache hits). Split so
587
- // pricing can apply cache_read rates correctly — same authority as the usage
588
- // parser and grok-context-breakdown.readUsageTotals. Prefer the reported
589
- // totalTokens; do not re-sum input+cached (that double-counts cache hits).
590
- function grokUsageTotals(usage) {
591
- if (!usage || typeof usage !== "object") return emptyTotals();
592
- const inputRaw = finite(usage.inputTokens ?? usage.input_tokens);
593
- const cached_input_tokens = finite(
594
- usage.cachedReadTokens ?? usage.cache_read_input_tokens ?? usage.cached_input_tokens,
595
- );
596
- const cache_creation_input_tokens = finite(
597
- usage.cachedWriteTokens ?? usage.cache_creation_input_tokens,
598
- );
599
- const output_tokens = finite(usage.outputTokens ?? usage.output_tokens);
600
- const reasoning_output_tokens = finite(
601
- usage.reasoningTokens ?? usage.reasoning_output_tokens,
602
- );
603
- const input_tokens = Math.max(0, inputRaw - cached_input_tokens);
604
- let total_tokens = finite(usage.totalTokens ?? usage.total_tokens);
605
- if (total_tokens <= 0) {
606
- total_tokens = input_tokens
607
- + cached_input_tokens
608
- + cache_creation_input_tokens
609
- + output_tokens
610
- + reasoning_output_tokens;
611
- }
612
- return {
613
- input_tokens,
614
- cached_input_tokens,
615
- cache_creation_input_tokens,
616
- output_tokens,
617
- reasoning_output_tokens,
618
- total_tokens,
619
- };
620
- }
621
-
622
594
  function pickGrokModel(usage, fallback) {
623
595
  const modelUsage = usage?.modelUsage;
624
596
  if (modelUsage && typeof modelUsage === "object") {
@@ -718,6 +690,13 @@ async function scanGrokSession(filePath) {
718
690
  let currentHadEdit = false;
719
691
  let subagentCalls = 0;
720
692
  const subagentTypes = new Map();
693
+ let usageEvents = 0;
694
+ let providerCostEvents = 0;
695
+ let providerCostUsd = 0;
696
+ let modelCalls = 0;
697
+ let apiDurationMs = 0;
698
+ let usageIsIncomplete = false;
699
+ let costIsPartial = false;
721
700
  let lastPromptFingerprint = null;
722
701
  // Grok streams user_message_chunk pieces for one prompt. Accumulate until
723
702
  // turn_completed, then fingerprint the full prompt for retry detection.
@@ -786,7 +765,21 @@ async function scanGrokSession(filePath) {
786
765
  if (sessionUpdate === "turn_completed") {
787
766
  closeTurn();
788
767
  const usage = update.usage;
789
- addTotals(tokens, grokUsageTotals(usage));
768
+ const normalizedUsage = normalizeGrokUsage(usage);
769
+ if (normalizedUsage) {
770
+ usageEvents += 1;
771
+ addTotals(tokens, normalizedUsage);
772
+ modelCalls += finite(normalizedUsage.model_calls);
773
+ apiDurationMs += finite(normalizedUsage.api_duration_ms);
774
+ usageIsIncomplete ||= normalizedUsage.usage_is_incomplete;
775
+ costIsPartial ||= normalizedUsage.cost_is_partial;
776
+ if (normalizedUsage.total_cost_usd != null) {
777
+ providerCostEvents += 1;
778
+ providerCostUsd = Math.round(
779
+ (providerCostUsd + normalizedUsage.total_cost_usd) * USD_TICKS_PER_USD,
780
+ ) / USD_TICKS_PER_USD;
781
+ }
782
+ }
790
783
  const candidate = pickGrokModel(usage, model);
791
784
  if (candidate && candidate !== "unknown") model = candidate;
792
785
  continue;
@@ -810,6 +803,11 @@ async function scanGrokSession(filePath) {
810
803
  model = normalizeSessionModel(signals.primaryModelId) || model;
811
804
  }
812
805
 
806
+ const hasCompleteProviderCost = usageEvents > 0
807
+ && providerCostEvents === usageEvents
808
+ && !usageIsIncomplete
809
+ && !costIsPartial;
810
+
813
811
  return finalizeRecord({
814
812
  version: SIDECAR_VERSION,
815
813
  session_hash: sessionHash("grok", observedSessionId || filePath),
@@ -827,7 +825,29 @@ async function scanGrokSession(filePath) {
827
825
  subagent_calls: subagentCalls,
828
826
  subagent_types: Object.fromEntries([...subagentTypes.entries()].sort()),
829
827
  tokens,
830
- provenance: { source: "local-session-log", confidence: "observed", retry_confidence: "inferred", content_retained: false },
828
+ usage_events: usageEvents,
829
+ usage_precision: usageIsIncomplete ? "reported_incomplete" : (usageEvents > 0 ? "reported" : "unavailable"),
830
+ usage_is_incomplete: usageIsIncomplete,
831
+ cost_is_partial: costIsPartial,
832
+ cost_source: hasCompleteProviderCost ? "provider_reported" : "model_pricing",
833
+ provider_cost_usd: hasCompleteProviderCost ? providerCostUsd : null,
834
+ model_calls: modelCalls,
835
+ api_duration_ms: apiDurationMs,
836
+ context_tokens_used: finite(signals.contextTokensUsed),
837
+ context_window_tokens: finite(signals.contextWindowTokens),
838
+ context_usage_percent: finite(signals.contextWindowUsage),
839
+ tool_calls: finite(signals.toolCallCount),
840
+ tool_failures: finite(signals.toolFailureCount),
841
+ error_count: finite(signals.errorCount),
842
+ compaction_count: finite(signals.compactionCount),
843
+ provenance: {
844
+ source: "local-session-log",
845
+ confidence: usageIsIncomplete ? "partial" : "observed",
846
+ retry_confidence: "inferred",
847
+ content_retained: false,
848
+ usage: usageEvents > 0 ? "turn_completed.usage" : "unavailable",
849
+ cost: hasCompleteProviderCost ? "provider_reported" : "model_pricing",
850
+ },
831
851
  });
832
852
  }
833
853
 
@@ -1410,6 +1430,7 @@ function resumeCommandFor(source, sessionId) {
1410
1430
  }
1411
1431
 
1412
1432
  function toSessionBrowserRow(row) {
1433
+ const tokens = row.tokens && typeof row.tokens === "object" ? row.tokens : {};
1413
1434
  return {
1414
1435
  session_hash: row.session_hash,
1415
1436
  session_id: row.session_id || null,
@@ -1435,6 +1456,11 @@ function toSessionBrowserRow(row) {
1435
1456
  edit_turns: finite(row.edit_turns),
1436
1457
  retry_turns: finite(row.retry_turns),
1437
1458
  subagent_calls: finite(row.subagent_calls),
1459
+ input_tokens: finite(tokens.input_tokens),
1460
+ cached_input_tokens: finite(tokens.cached_input_tokens),
1461
+ cache_creation_input_tokens: finite(tokens.cache_creation_input_tokens),
1462
+ output_tokens: finite(tokens.output_tokens),
1463
+ reasoning_output_tokens: finite(tokens.reasoning_output_tokens),
1438
1464
  direct_subagent_count: finite(row.direct_subagent_count),
1439
1465
  descendant_subagent_count: finite(row.descendant_subagent_count),
1440
1466
  own_total_tokens: finite(row.total_tokens),
@@ -1445,6 +1471,20 @@ function toSessionBrowserRow(row) {
1445
1471
  subagent_cost_usd: finite(row.subagent_cost_usd),
1446
1472
  combined_cost_usd: finite(row.combined_cost_usd || row.cost_usd),
1447
1473
  cost_usd: finite(row.cost_usd),
1474
+ cost_source: row.cost_source || null,
1475
+ usage_precision: row.usage_precision || null,
1476
+ usage_is_incomplete: Boolean(row.usage_is_incomplete),
1477
+ cost_is_partial: Boolean(row.cost_is_partial),
1478
+ usage_events: finite(row.usage_events),
1479
+ model_calls: finite(row.model_calls),
1480
+ api_duration_ms: finite(row.api_duration_ms),
1481
+ context_tokens_used: finite(row.context_tokens_used),
1482
+ context_window_tokens: finite(row.context_window_tokens),
1483
+ context_usage_percent: finite(row.context_usage_percent),
1484
+ tool_calls: finite(row.tool_calls),
1485
+ tool_failures: finite(row.tool_failures),
1486
+ error_count: finite(row.error_count),
1487
+ compaction_count: finite(row.compaction_count),
1448
1488
  productive: Boolean(row.productive),
1449
1489
  first_pass: Boolean(row.first_pass ?? row.one_shot),
1450
1490
  resume_command: resumeCommandFor(row.source, row.session_id),
@@ -1463,15 +1503,35 @@ function mergeSessionFragments(rows) {
1463
1503
  const key = row.session_hash;
1464
1504
  const cur = byHash.get(key);
1465
1505
  if (!cur) {
1466
- byHash.set(key, { ...row, _repr_tokens: finite(row.total_tokens) });
1506
+ byHash.set(key, {
1507
+ ...row,
1508
+ tokens: row.tokens && typeof row.tokens === "object" ? { ...row.tokens } : emptyTotals(),
1509
+ _repr_tokens: finite(row.total_tokens),
1510
+ });
1467
1511
  continue;
1468
1512
  }
1469
1513
  cur.turns = finite(cur.turns) + finite(row.turns);
1470
1514
  cur.edit_turns = finite(cur.edit_turns) + finite(row.edit_turns);
1471
1515
  cur.retry_turns = finite(cur.retry_turns) + finite(row.retry_turns);
1472
1516
  cur.subagent_calls = finite(cur.subagent_calls) + finite(row.subagent_calls);
1517
+ addTotals(cur.tokens, row.tokens);
1473
1518
  cur.total_tokens = finite(cur.total_tokens) + finite(row.total_tokens);
1474
1519
  cur.cost_usd = finite(cur.cost_usd) + finite(row.cost_usd);
1520
+ cur.provider_cost_usd = finite(cur.provider_cost_usd) + finite(row.provider_cost_usd);
1521
+ cur.usage_events = finite(cur.usage_events) + finite(row.usage_events);
1522
+ cur.model_calls = finite(cur.model_calls) + finite(row.model_calls);
1523
+ cur.api_duration_ms = finite(cur.api_duration_ms) + finite(row.api_duration_ms);
1524
+ cur.tool_calls = finite(cur.tool_calls) + finite(row.tool_calls);
1525
+ cur.tool_failures = finite(cur.tool_failures) + finite(row.tool_failures);
1526
+ cur.error_count = finite(cur.error_count) + finite(row.error_count);
1527
+ cur.compaction_count = finite(cur.compaction_count) + finite(row.compaction_count);
1528
+ cur.context_tokens_used = Math.max(finite(cur.context_tokens_used), finite(row.context_tokens_used));
1529
+ cur.context_window_tokens = Math.max(finite(cur.context_window_tokens), finite(row.context_window_tokens));
1530
+ cur.context_usage_percent = Math.max(finite(cur.context_usage_percent), finite(row.context_usage_percent));
1531
+ cur.usage_is_incomplete = Boolean(cur.usage_is_incomplete) || Boolean(row.usage_is_incomplete);
1532
+ cur.cost_is_partial = Boolean(cur.cost_is_partial) || Boolean(row.cost_is_partial);
1533
+ if (cur.usage_precision !== row.usage_precision) cur.usage_precision = "mixed";
1534
+ if (cur.cost_source !== row.cost_source) cur.cost_source = "mixed";
1475
1535
  cur.productive = Boolean(cur.productive) || Boolean(row.productive);
1476
1536
  cur.active_ms = finite(cur.active_ms) + finite(row.active_ms);
1477
1537
  if (!cur.title && row.title) cur.title = row.title;