tokentracker-cli 1.1.13 → 1.2.1

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 (126) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-DHCKPAgO.js → AchievementBadge-DPlXNqoK.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-BqlGMD7S.js → AchievementsPage-B49wjdK_.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-CTTTR9CN.js → AchievementsSection-DoYMz2Rr.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-e0Iz8ct4.js → BadgeDetailModal-DUCS6K1n.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-BLYZyT1O.js → BotAnimated-C21PdYSf.js} +1 -1
  6. package/dashboard/dist/assets/{Card-EHkn9ktD.js → Card-BU2CqxfU.js} +1 -1
  7. package/dashboard/dist/assets/{CommandPalette-CuSVstX9.js → CommandPalette-O3pOLyJo.js} +1 -1
  8. package/dashboard/dist/assets/CommunityStatsModal-Bt82neQ7.js +1 -0
  9. package/dashboard/dist/assets/{ConfirmModal-pIUQJ7QM.js → ConfirmModal-zUljpLNe.js} +1 -1
  10. package/dashboard/dist/assets/{Controls-CX4Di57j.js → Controls-D56v-YJw.js} +1 -1
  11. package/dashboard/dist/assets/DashboardPage-CKEuFzuc.js +1 -0
  12. package/dashboard/dist/assets/DevicePage-CCIyAYH3.js +1 -0
  13. package/dashboard/dist/assets/{DialogClose-Dn-nxOSp.js → DialogClose-C-QK97qp.js} +1 -1
  14. package/dashboard/dist/assets/DialogDescription-COXweCuc.js +1 -0
  15. package/dashboard/dist/assets/{DialogTitle-DuAY9sfZ.js → DialogTitle-6q0jyME6.js} +1 -1
  16. package/dashboard/dist/assets/{FadeIn-25jNO9JV.js → FadeIn-BUm1z22V.js} +1 -1
  17. package/dashboard/dist/assets/{HeaderGithubStar-0-ne32-S.js → HeaderGithubStar-CxdmloMy.js} +1 -1
  18. package/dashboard/dist/assets/{HoverTooltip-C2Qop8B3.js → HoverTooltip-CrMdGYZj.js} +1 -1
  19. package/dashboard/dist/assets/{IpCheckPage-Q9lwiFdt.js → IpCheckPage-w6UUK_1K.js} +1 -1
  20. package/dashboard/dist/assets/LandingPage-DLDE7wdR.js +2 -0
  21. package/dashboard/dist/assets/{LeaderboardAvatar-CiQrgneF.js → LeaderboardAvatar-BqD4dEvQ.js} +1 -1
  22. package/dashboard/dist/assets/LeaderboardPage-kgHSryiT.js +2 -0
  23. package/dashboard/dist/assets/{LeaderboardProfileModal-CF-MI8rL.js → LeaderboardProfileModal-zLdeOiA3.js} +4 -4
  24. package/dashboard/dist/assets/{LeaderboardProfilePage-DRbLb-fO.js → LeaderboardProfilePage-Fljho5RP.js} +2 -2
  25. package/dashboard/dist/assets/{LimitsPage-lvYidBkz.js → LimitsPage-Do_uJqdQ.js} +2 -2
  26. package/dashboard/dist/assets/{LocalOnlyNotice-CAQTnlaI.js → LocalOnlyNotice-RIj2XT_X.js} +1 -1
  27. package/dashboard/dist/assets/{LoginCard-Ci2Rrsfl.js → LoginCard-Dx6MmGjU.js} +1 -1
  28. package/dashboard/dist/assets/{LoginModal-DhyQPl-V.js → LoginModal-BWaZVRnT.js} +1 -1
  29. package/dashboard/dist/assets/{LoginPage-B6q8P6hE.js → LoginPage-BNkU-riS.js} +1 -1
  30. package/dashboard/dist/assets/{PetPage-82Cldgg6.js → PetPage-CyEgU2Vm.js} +1 -1
  31. package/dashboard/dist/assets/PopoverPopup-CmUIY8lF.js +1 -0
  32. package/dashboard/dist/assets/{ProviderIcon-aLwclDsD.js → ProviderIcon-BSI-1Cy8.js} +1 -1
  33. package/dashboard/dist/assets/{ResetPasswordPage-BlRl_aJr.js → ResetPasswordPage-CUXKkLb4.js} +1 -1
  34. package/dashboard/dist/assets/Select-B6CM0ico.js +1 -0
  35. package/dashboard/dist/assets/SelectItemText-CZ7puxuB.js +1 -0
  36. package/dashboard/dist/assets/{ServiceStatusPage-Pd6dgJua.js → ServiceStatusPage-B4lIs2fc.js} +1 -1
  37. package/dashboard/dist/assets/SessionsPage-BngTWN7Q.css +1 -0
  38. package/dashboard/dist/assets/SessionsPage-D99UfBgy.js +3 -0
  39. package/dashboard/dist/assets/SettingsPage-DT3rld91.js +2 -0
  40. package/dashboard/dist/assets/SkillsPage-Dv47kVCB.js +1 -0
  41. package/dashboard/dist/assets/{TokenGalaxy-BN71jQQ8.js → TokenGalaxy-Bk3kGSGi.js} +2 -2
  42. package/dashboard/dist/assets/TrendMonitor-mB1x3c_o.js +19 -0
  43. package/dashboard/dist/assets/WidgetsPage-BhUArowe.js +1 -0
  44. package/dashboard/dist/assets/{WrappedPage-Cq8DrzLm.js → WrappedPage-DHRmP4Ey.js} +1 -1
  45. package/dashboard/dist/assets/__vite-browser-external-BW1rxvYq.js +45 -0
  46. package/dashboard/dist/assets/__vite-browser-external-gKkiQad5.css +1 -0
  47. package/dashboard/dist/assets/{agent-logos-X7FQq7rz.js → agent-logos-D7f7AMF-.js} +1 -1
  48. package/dashboard/dist/assets/{arrow-up-right-CPxuidzy.js → arrow-up-right-c39fdiDQ.js} +1 -1
  49. package/dashboard/dist/assets/{check-D2K19ObI.js → check-8LQ7IvkH.js} +1 -1
  50. package/dashboard/dist/assets/{composite-BN1UKLj0.js → composite-BgLSXWKA.js} +1 -1
  51. package/dashboard/dist/assets/{download-CpW-TB2h.js → download-x3Gmv-wu.js} +1 -1
  52. package/dashboard/dist/assets/{external-link-ChBchK6G.js → external-link-daw-XTjZ.js} +1 -1
  53. package/dashboard/dist/assets/{flame-f7119ZMd.js → flame-B9o0_EF8.js} +1 -1
  54. package/dashboard/dist/assets/{format-tokens-COrRnk53.js → format-tokens-o9atQGtV.js} +1 -1
  55. package/dashboard/dist/assets/host-mode-GPY2DxE4.js +1 -0
  56. package/dashboard/dist/assets/{icons-Dn9vA9Qu.js → icons-CmM2z-Jt.js} +1 -1
  57. package/dashboard/dist/assets/{index-BCNOKYSO.js → index-Bfgblk6o.js} +1 -1
  58. package/dashboard/dist/assets/{index-CA-94DFW.js → index-CPcJP0uH.js} +1 -1
  59. package/dashboard/dist/assets/{index-CElyhNzh.js → index-CtQOew7C.js} +1 -1
  60. package/dashboard/dist/assets/{info-BHsPBczD.js → info-BaK8Hoi2.js} +1 -1
  61. package/dashboard/dist/assets/{link-2-BHfEWv22.js → link-2-CV3uvMH3.js} +1 -1
  62. package/dashboard/dist/assets/{loader-circle-DTIT1Uk1.js → loader-circle-DYpa-xTZ.js} +1 -1
  63. package/dashboard/dist/assets/{modifiers.esm-CPBla3OK.js → modifiers.esm-Ds4Uh7JO.js} +1 -1
  64. package/dashboard/dist/assets/{monitor-smartphone-CbsYB-S2.js → monitor-smartphone-BbeYHvY-.js} +1 -1
  65. package/dashboard/dist/assets/{plus-CZiNoMsy.js → plus-C9qGVLFv.js} +1 -1
  66. package/dashboard/dist/assets/provider-display-BWYNyitK.js +42 -0
  67. package/dashboard/dist/assets/{react-CZdcuJ28.js → react-CO1HovF5.js} +1 -1
  68. package/dashboard/dist/assets/{search-q2ARMGca.js → search-DBnhkzG_.js} +1 -1
  69. package/dashboard/dist/assets/{skills-api-CIyqyk-w.js → skills-api-BSkbxC8-.js} +1 -1
  70. package/dashboard/dist/assets/{sortable.esm-BztIEZTk.js → sortable.esm-SVl6OixH.js} +1 -1
  71. package/dashboard/dist/assets/{store-DvdXwk-7.js → store-DrEEdO_u.js} +1 -1
  72. package/dashboard/dist/assets/terminal-kUMdPZhq.js +1 -0
  73. package/dashboard/dist/assets/{trash-D0ptaWPk.js → trash-Dd_vbrYo.js} +1 -1
  74. package/dashboard/dist/assets/{use-community-stats-DYibzhAl.js → use-community-stats-Bmlmv4H-.js} +1 -1
  75. package/dashboard/dist/assets/{use-latest-request-guard-viIhH7H5.js → use-latest-request-guard-CqZwzrCW.js} +1 -1
  76. package/dashboard/dist/assets/{use-limits-display-prefs-CbXApWU1.js → use-limits-display-prefs-CDC0MZRY.js} +1 -1
  77. package/dashboard/dist/assets/{use-ordered-list-BBP8hr8y.js → use-ordered-list-DvBI7mUU.js} +1 -1
  78. package/dashboard/dist/assets/{use-pet-settings-CIrPZ9pB.js → use-pet-settings-C_FrqySO.js} +2 -2
  79. package/dashboard/dist/assets/use-reduced-motion-BEOENqBa.js +1 -0
  80. package/dashboard/dist/assets/{use-session-efficiency-pref-2Vb3tJrw.js → use-session-efficiency-pref-D8cAb4A9.js} +1 -1
  81. package/dashboard/dist/assets/{use-transform-CECRFELz.js → use-transform-uSKYTYaW.js} +1 -1
  82. package/dashboard/dist/assets/{use-usage-limits-DEnbjOVG.js → use-usage-limits-DFgOKTtv.js} +1 -1
  83. package/dashboard/dist/assets/{useCurrency-Dh9pUzDO.js → useCurrency-t1iy_pC3.js} +1 -1
  84. package/dashboard/dist/assets/useTokenFormat-0bPjtaaM.js +1 -0
  85. package/dashboard/dist/assets/{zap-BdeAxq64.js → zap-DFM6AsYo.js} +1 -1
  86. package/dashboard/dist/index.html +2 -2
  87. package/dashboard/dist/ip-check.html +2 -2
  88. package/dashboard/dist/leaderboard.html +2 -2
  89. package/dashboard/dist/legal.css +1 -0
  90. package/dashboard/dist/pricing.html +35 -0
  91. package/dashboard/dist/privacy.html +278 -0
  92. package/dashboard/dist/share.html +2 -2
  93. package/dashboard/dist/terms.html +41 -0
  94. package/package.json +1 -1
  95. package/src/lib/codex-rollout-parser.js +5 -0
  96. package/src/lib/local-api.js +5 -1
  97. package/src/lib/pricing/index.js +45 -0
  98. package/src/lib/pricing/seed-snapshot.json +1 -1
  99. package/src/lib/rollout.js +1 -0
  100. package/src/lib/session-analytics.js +74 -18
  101. package/src/lib/session-performance.js +214 -0
  102. package/dashboard/dist/assets/CommunityStatsModal-Cb1vefLE.js +0 -1
  103. package/dashboard/dist/assets/DashboardPage-D1Ox1y3E.js +0 -1
  104. package/dashboard/dist/assets/DevicePage-Q57gBE8A.js +0 -1
  105. package/dashboard/dist/assets/DialogDescription-GXslTq2z.js +0 -1
  106. package/dashboard/dist/assets/LandingPage-iX6E8Flr.js +0 -2
  107. package/dashboard/dist/assets/LeaderboardPage-BIhIA28A.js +0 -2
  108. package/dashboard/dist/assets/PopoverPopup-Btcu7vU0.js +0 -1
  109. package/dashboard/dist/assets/SegmentedControl-2O_XVL8u.js +0 -1
  110. package/dashboard/dist/assets/Select-D5uuRQwh.js +0 -1
  111. package/dashboard/dist/assets/SelectItemText-Bq_ZdWU0.js +0 -1
  112. package/dashboard/dist/assets/SessionsPage-DjHudcn6.js +0 -2
  113. package/dashboard/dist/assets/SettingsPage-DOLhZiBE.js +0 -2
  114. package/dashboard/dist/assets/SkillsPage-D9-ZorB5.js +0 -1
  115. package/dashboard/dist/assets/ToolbarRootContext-B0cqyD1i.js +0 -1
  116. package/dashboard/dist/assets/TrendMonitor-DWxtbzyQ.js +0 -19
  117. package/dashboard/dist/assets/WidgetsPage-BwmJ2Z9d.js +0 -1
  118. package/dashboard/dist/assets/__vite-browser-external-CPMSmJgP.js +0 -45
  119. package/dashboard/dist/assets/__vite-browser-external-DrXtuPP9.css +0 -1
  120. package/dashboard/dist/assets/copy-CSN17qFv.js +0 -1
  121. package/dashboard/dist/assets/host-mode-Bc_iTzHk.js +0 -1
  122. package/dashboard/dist/assets/maximize-2-BmtIBzLu.js +0 -1
  123. package/dashboard/dist/assets/provider-display-CXKRt9q1.js +0 -42
  124. package/dashboard/dist/assets/terminal-CFQOW2Ey.js +0 -1
  125. package/dashboard/dist/assets/use-reduced-motion-Dpq-cKyM.js +0 -1
  126. package/dashboard/dist/assets/useTokenFormat-CoGcVhPP.js +0 -1
@@ -24617,6 +24617,7 @@ module.exports = {
24617
24617
  toUtcHalfHourStart,
24618
24618
  totalsKey,
24619
24619
  claudeMessageDedupKey,
24620
+ normalizeClaudeUsage,
24620
24621
  groupBucketKey,
24621
24622
  // Exposed for regression tests covering nested-group remote URLs.
24622
24623
  canonicalizeProjectRef,
@@ -28,9 +28,10 @@ const fsp = require("node:fs/promises");
28
28
  const os = require("node:os");
29
29
  const path = require("node:path");
30
30
  const readline = require("node:readline");
31
- const { listClaudeProjectFiles, listRolloutFilesDeep, claudeMessageDedupKey } = require("./rollout");
31
+ const { listClaudeProjectFiles, listRolloutFilesDeep, claudeMessageDedupKey, normalizeClaudeUsage } = require("./rollout");
32
32
  const { parseCodexRolloutFile } = require("./codex-rollout-parser");
33
- const { computeRowCost, getModelPricing } = require("./pricing");
33
+ const { computeRowCost, getModelPricingInfo } = require("./pricing");
34
+ const { normalizePerformance, mergePerformance, createClaudePerformanceCollector, createCodexPerformanceCollector } = require("./session-performance");
34
35
  const { USD_TICKS_PER_USD, normalizeGrokUsage } = require("./grok-usage");
35
36
  const wsl = require("./wsl-probe");
36
37
  const {
@@ -68,7 +69,8 @@ const {
68
69
  // v13 stores per-model Codex usage (including observed reroutes and the exact
69
70
  // long-context subset) and folds delivery signals into the token parser's
70
71
  // single pass instead of parsing every Codex file twice.
71
- const SIDECAR_VERSION = 13;
72
+ // v14 adds request-level estimated performance and Claude model usage.
73
+ const SIDECAR_VERSION = 14;
72
74
  const EDIT_TOOLS = new Set([
73
75
  "apply_patch",
74
76
  "edit",
@@ -172,10 +174,12 @@ function normalizeModelUsageRows(rows) {
172
174
  selected_models: new Set(),
173
175
  reroute_reasons: new Set(),
174
176
  model_attribution: "selected",
177
+ performance: normalizePerformance(),
175
178
  };
176
179
  byModel.set(model, row);
177
180
  }
178
181
  for (const field of MODEL_USAGE_SUM_FIELDS) row[field] += finite(value?.[field]);
182
+ row.performance = mergePerformance(row.performance, value?.performance);
179
183
  if (Array.isArray(value?.selected_models)) {
180
184
  for (const selected of value.selected_models) {
181
185
  const normalized = normalizeSessionModel(selected);
@@ -195,6 +199,8 @@ function normalizeModelUsageRows(rows) {
195
199
  }
196
200
  }
197
201
  return [...byModel.values()]
202
+ .filter((row) => ["total_tokens", "input_tokens", "cached_input_tokens", "cache_creation_input_tokens", "output_tokens", "reasoning_output_tokens"]
203
+ .some((field) => row[field] > 0))
198
204
  .map((row) => ({
199
205
  ...row,
200
206
  selected_models: [...row.selected_models].sort(),
@@ -205,9 +211,9 @@ function normalizeModelUsageRows(rows) {
205
211
 
206
212
  // The sidecar is re-read on every dashboard request, so keep it small: omit
207
213
  // the model_usage fields that normalizeModelUsageRows() reconstructs on read.
208
- // All four omissions are lossless - zero counters default to 0, an absent
214
+ // These omissions are lossless - zero counters default to 0, an absent
209
215
  // selected_models re-seeds from `model`, model_attribution is derived from
210
- // rerouted_usage_events, and cost_usd is always recomputed by
216
+ // rerouted_usage_events, and cost_usd/pricing are always recomputed by
211
217
  // repriceSessionRecord(). Worth ~19% of the file on a 6.8k-session history,
212
218
  // most of it the long_context_* counters for sessions with no requests over
213
219
  // OPENAI_LONG_CONTEXT_INPUT_THRESHOLD in src/lib/pricing/index.js.
@@ -223,6 +229,7 @@ function serializeModelUsageRow(row) {
223
229
  out.reroute_reasons = row.reroute_reasons;
224
230
  }
225
231
  if (row.model_attribution === "effective") out.model_attribution = "effective";
232
+ if (row.performance?.estimated_request_count || row.performance?.first_response_sample_count) out.performance = row.performance;
226
233
  return out;
227
234
  }
228
235
 
@@ -239,22 +246,22 @@ function repriceSessionRecord(record) {
239
246
  let hasUnpricedUsage = false;
240
247
  for (const row of modelUsage) {
241
248
  row.cost_usd = computeRowCost({ source: record.source, ...row });
242
- if (record.source === "codex" && row.total_tokens > 0) {
243
- const pricing = getModelPricing(row.model, { source: record.source });
244
- if (![pricing.input, pricing.output, pricing.cache_read, pricing.cache_write]
245
- .some((value) => finite(value) > 0)) {
246
- hasUnpricedUsage = true;
247
- }
248
- }
249
+ row.pricing = getModelPricingInfo(row.model, { source: record.source });
250
+ if (row.total_tokens > 0 && row.pricing.status === "unpriced") hasUnpricedUsage = true;
249
251
  }
250
252
  record.model_usage = modelUsage;
251
253
  record.model = modelUsage.length > 1 ? "mixed" : modelUsage[0].model;
252
- record.cost_usd = modelUsage.reduce((sum, row) => sum + finite(row.cost_usd), 0);
253
- record.cost_source = "model_pricing";
254
+ if (record.cost_source === "provider_reported" && Number.isFinite(Number(record.provider_cost_usd)) && Number(record.provider_cost_usd) >= 0) {
255
+ record.cost_usd = Number(record.provider_cost_usd);
256
+ if (modelUsage.length === 1) modelUsage[0].cost_usd = record.cost_usd;
257
+ } else {
258
+ record.cost_usd = modelUsage.reduce((sum, row) => sum + finite(row.cost_usd), 0);
259
+ record.cost_source = "model_pricing";
260
+ record.cost_is_partial = hasUnpricedUsage;
261
+ }
254
262
  // Internal labels such as `codex-auto-review` expose token usage but not
255
263
  // an underlying public model/rate. Keep their tokens, leave their cost at
256
264
  // zero, and explicitly mark the session estimate as a known lower bound.
257
- record.cost_is_partial = hasUnpricedUsage;
258
265
  } else {
259
266
  const providerCost = Number(record.provider_cost_usd);
260
267
  record.cost_usd = record.cost_source === "provider_reported"
@@ -262,6 +269,8 @@ function repriceSessionRecord(record) {
262
269
  && providerCost >= 0
263
270
  ? providerCost
264
271
  : computeRowCost({ source: record.source, model: record.model, ...record.tokens });
272
+ if (record.cost_source !== "provider_reported" && finite(record.total_tokens || record.tokens?.total_tokens) > 0
273
+ && getModelPricingInfo(record.model, { source: record.source }).status === "unpriced") record.cost_is_partial = true;
265
274
  }
266
275
  record.cost_per_edit = record.edit_turns > 0 ? record.cost_usd / record.edit_turns : null;
267
276
  return record;
@@ -270,7 +279,7 @@ function repriceSessionRecord(record) {
270
279
  // The model_usage wire contract is DENSE: every field dashboard/src/lib/
271
280
  // sessions-api.ts declares is present on every row. Storage is free to be
272
281
  // sparse - serializeModelUsageRow() drops zero counters, and a record with no
273
- // model_usage at all (every claude/grok session, plus codex sessions written
282
+ // model_usage at all (Grok sessions and sessions written
274
283
  // by an older sidecar) has nothing to trim in the first place - but neither
275
284
  // shape may leak into an API response, or the declared type describes a row
276
285
  // that does not exist. Densify here, the one point both the by_model
@@ -283,6 +292,7 @@ function denseModelUsageRow(overrides = {}) {
283
292
  reroute_reasons: [],
284
293
  model_attribution: "selected",
285
294
  cost_usd: 0,
295
+ performance: normalizePerformance(),
286
296
  ...overrides,
287
297
  };
288
298
  }
@@ -293,17 +303,22 @@ function modelUsageForAggregation(record) {
293
303
  if (rows.length === 0) {
294
304
  return [denseModelUsageRow({
295
305
  model: normalizeSessionModel(record?.model) || "unknown",
306
+ ...record?.tokens,
296
307
  total_tokens: finite(record?.total_tokens || record?.tokens?.total_tokens),
297
308
  cost_usd: finite(record?.cost_usd),
298
309
  edit_turns: editTurns,
310
+ performance: normalizePerformance(record?.performance),
311
+ pricing: getModelPricingInfo(record?.model, { source: record?.source }),
299
312
  })];
300
313
  }
301
314
  const priced = rows.map((row) => denseModelUsageRow({
302
315
  ...row,
316
+ pricing: getModelPricingInfo(row.model, { source: record.source }),
303
317
  cost_usd: Number.isFinite(Number(row.cost_usd))
304
318
  ? Number(row.cost_usd)
305
319
  : computeRowCost({ source: record.source, ...row }),
306
320
  }));
321
+ if (record.cost_source === "provider_reported" && priced.length === 1) priced[0].cost_usd = finite(record.cost_usd);
307
322
  // Sum(rows.edit_turns) has to equal record.edit_turns or the by_model column
308
323
  // stops summing to summary.edit_turns. It can fall short two ways: a sidecar
309
324
  // written before model_usage carried edit_turns, and an edit turn whose model
@@ -441,6 +456,7 @@ function finalizeRecord(record) {
441
456
  record.active_ms = Math.max(0, finite(record.active_ms));
442
457
  record.duration_ms = record.active_ms;
443
458
  record.total_tokens = finite(record.total_tokens || record.tokens?.total_tokens);
459
+ record.performance = normalizePerformance(record.performance);
444
460
  repriceSessionRecord(record);
445
461
  record.productive = record.edit_turns > 0;
446
462
  // A first-pass delivery has exactly one user turn containing an observed
@@ -476,6 +492,9 @@ async function scanClaudeSession(filePath) {
476
492
  // this set across that group stays deterministic. Unrelated files (including
477
493
  // same-UUID siblings inside one root) are never grouped here.
478
494
  const seenMessages = new Set();
495
+ const performanceCollector = createClaudePerformanceCollector();
496
+ const modelUsageByModel = new Map();
497
+ const editTurnsByModel = new Map();
479
498
  const bounds = emptyBounds();
480
499
  // The basename keeps grouping stable for files that never write a sessionId
481
500
  // record, but only an *observed* sessionId may become a resumable
@@ -491,12 +510,15 @@ async function scanClaudeSession(filePath) {
491
510
  let editTurns = 0;
492
511
  let retryTurns = 0;
493
512
  let currentHadEdit = false;
513
+ let currentTurnModel = null;
494
514
  let subagentCalls = 0;
495
515
  const subagentTypes = new Map();
496
516
 
497
517
  function closeTurn() {
498
518
  if (currentHadEdit) {
499
519
  editTurns += 1;
520
+ const key = currentTurnModel || "unknown";
521
+ editTurnsByModel.set(key, (editTurnsByModel.get(key) || 0) + 1);
500
522
  }
501
523
  currentHadEdit = false;
502
524
  }
@@ -521,6 +543,10 @@ async function scanClaudeSession(filePath) {
521
543
  }
522
544
  let obj;
523
545
  try { obj = JSON.parse(line); } catch { continue; }
546
+ const timingKey = claudeMessageDedupKey(obj);
547
+ const countedUsage = obj.type === "assistant" && obj.message && (!timingKey || !seenMessages.has(timingKey))
548
+ ? normalizeClaudeUsage(obj.message.usage) : null;
549
+ performanceCollector.consume(obj, timingKey, countedUsage, normalizeSessionModel(obj.message?.model) || model);
524
550
  updateBounds(bounds, obj.timestamp || obj.message?.timestamp);
525
551
  if (typeof obj.sessionId === "string" && obj.sessionId) {
526
552
  rawSessionId = obj.sessionId;
@@ -554,7 +580,15 @@ async function scanClaudeSession(filePath) {
554
580
  // model instead of letting that marker overwrite it.
555
581
  const candidateModel = normalizeSessionModel(obj.message.model);
556
582
  if (candidateModel) model = candidateModel;
557
- addTotals(tokens, tokenTotals(obj.message.usage));
583
+ currentTurnModel = model;
584
+ const usage = normalizeClaudeUsage(obj.message.usage);
585
+ addTotals(tokens, usage);
586
+ if (usage.total_tokens > 0) {
587
+ const modelUsage = modelUsageByModel.get(model) || { model, ...emptyTotals(), usage_events: 0 };
588
+ addTotals(modelUsage, usage);
589
+ modelUsage.usage_events += 1;
590
+ modelUsageByModel.set(model, modelUsage);
591
+ }
558
592
  const content = Array.isArray(obj.message.content) ? obj.message.content : [];
559
593
  for (const block of content) {
560
594
  if (!block || block.type !== "tool_use") continue;
@@ -578,6 +612,7 @@ async function scanClaudeSession(filePath) {
578
612
  }
579
613
  if (!scannedFiles) throw new Error("all grouped Claude session files failed during read");
580
614
  closeTurn();
615
+ const performance = performanceCollector.finish();
581
616
  return finalizeRecord({
582
617
  version: SIDECAR_VERSION,
583
618
  session_hash: sessionHash("claude", rawSessionId),
@@ -591,6 +626,12 @@ async function scanClaudeSession(filePath) {
591
626
  project_key: projectKey(cwd, primaryFilePath),
592
627
  project_ref: cwd || null,
593
628
  model,
629
+ model_usage: [...modelUsageByModel.values()].map((row) => ({
630
+ ...row,
631
+ edit_turns: finite(editTurnsByModel.get(row.model)),
632
+ performance: performance.byModel.get(row.model) || normalizePerformance(),
633
+ })),
634
+ performance: performance.performance,
594
635
  ...bounds,
595
636
  turns,
596
637
  edit_turns: editTurns,
@@ -693,13 +734,19 @@ async function scanCodexSession(filePath) {
693
734
  const filePaths = readableSessionPaths(filePath);
694
735
  const primaryFilePath = filePaths[0] || String(filePath || "");
695
736
  const signalCollector = createCodexDeliverySignalCollector();
737
+ const performanceCollector = createCodexPerformanceCollector();
696
738
  const parsed = await parseCodexRolloutFile(filePaths, {
697
739
  seenTokenEvents: new Set(),
698
740
  collectBreakdowns: false,
699
741
  collectModelUsage: true,
700
- onObject: signalCollector.consume,
742
+ onObject(obj, model) {
743
+ signalCollector.consume(obj, model);
744
+ performanceCollector.consume(obj, model);
745
+ },
746
+ onUsage: performanceCollector.consumeUsage,
701
747
  });
702
748
  const signals = signalCollector.finish();
749
+ const performance = performanceCollector.finish();
703
750
  // The collector keyed edit turns by the model the parser handed it, so the
704
751
  // keys are the same raw model ids parsed.modelUsage rows carry. A model whose
705
752
  // edit turns produced no billable delta has no row to land on;
@@ -709,6 +756,7 @@ async function scanCodexSession(filePath) {
709
756
  const modelUsage = (parsed.modelUsage || []).map((row) => ({
710
757
  ...row,
711
758
  edit_turns: finite(editTurnsByModel[row.model]),
759
+ performance: performance.byModel.get(row.model) || normalizePerformance(),
712
760
  }));
713
761
  const parsedModel = normalizeSessionModel(parsed.model);
714
762
  const provider = normalizeSessionModel(parsed.provider);
@@ -754,6 +802,7 @@ async function scanCodexSession(filePath) {
754
802
  project_ref: parsed.cwd || null,
755
803
  model,
756
804
  model_usage: modelUsage,
805
+ performance: performance.performance,
757
806
  ...signals.bounds,
758
807
  turns: signals.turns || finite(parsed.turnCount),
759
808
  edit_turns: signals.editTurns,
@@ -1590,6 +1639,7 @@ function summarizeSessions(sessions, { from = "", to = "", includeSessions = tru
1590
1639
  retries: 0,
1591
1640
  total_tokens: 0,
1592
1641
  cost_usd: 0,
1642
+ performance: normalizePerformance(),
1593
1643
  edit_tokens: 0,
1594
1644
  edit_cost_usd: 0,
1595
1645
  };
@@ -1601,6 +1651,7 @@ function summarizeSessions(sessions, { from = "", to = "", includeSessions = tru
1601
1651
  totals.retries += finite(row.retry_turns);
1602
1652
  totals.total_tokens += finite(row.total_tokens);
1603
1653
  totals.cost_usd += finite(row.cost_usd);
1654
+ totals.performance = mergePerformance(totals.performance, row.performance);
1604
1655
  if (row.productive) {
1605
1656
  totals.edit_tokens += finite(row.total_tokens);
1606
1657
  totals.edit_cost_usd += finite(row.cost_usd);
@@ -1626,6 +1677,7 @@ function summarizeSessions(sessions, { from = "", to = "", includeSessions = tru
1626
1677
  retries: 0,
1627
1678
  total_tokens: 0,
1628
1679
  cost_usd: 0,
1680
+ performance: normalizePerformance(),
1629
1681
  edit_tokens: 0,
1630
1682
  edit_cost_usd: 0,
1631
1683
  usage_events: 0,
@@ -1638,6 +1690,7 @@ function summarizeSessions(sessions, { from = "", to = "", includeSessions = tru
1638
1690
  if (row.first_pass ?? row.one_shot) agg.one_shot_sessions += 1;
1639
1691
  agg.total_tokens += finite(usage.total_tokens);
1640
1692
  agg.cost_usd += finite(usage.cost_usd);
1693
+ agg.performance = mergePerformance(agg.performance, usage.performance);
1641
1694
  agg.usage_events += finite(usage.usage_events);
1642
1695
  agg.rerouted_usage_events += finite(usage.rerouted_usage_events);
1643
1696
  agg.long_context_usage_events += finite(usage.long_context_usage_events);
@@ -1791,6 +1844,7 @@ function toSessionBrowserRow(row) {
1791
1844
  project_ref: row.project_ref || null,
1792
1845
  model: row.model,
1793
1846
  model_usage: modelUsageForAggregation(row),
1847
+ performance: normalizePerformance(row.performance),
1794
1848
  started_at: row.started_at || null,
1795
1849
  ended_at: row.ended_at || null,
1796
1850
  duration_ms: finite(row.duration_ms),
@@ -1849,6 +1903,7 @@ function mergeSessionFragments(rows) {
1849
1903
  ...row,
1850
1904
  tokens: row.tokens && typeof row.tokens === "object" ? { ...row.tokens } : emptyTotals(),
1851
1905
  model_usage: normalizeModelUsageRows(row.model_usage),
1906
+ performance: normalizePerformance(row.performance),
1852
1907
  _repr_tokens: finite(row.total_tokens),
1853
1908
  });
1854
1909
  continue;
@@ -1856,6 +1911,7 @@ function mergeSessionFragments(rows) {
1856
1911
  cur.turns = finite(cur.turns) + finite(row.turns);
1857
1912
  cur.edit_turns = finite(cur.edit_turns) + finite(row.edit_turns);
1858
1913
  cur.retry_turns = finite(cur.retry_turns) + finite(row.retry_turns);
1914
+ cur.performance = mergePerformance(cur.performance, row.performance);
1859
1915
  cur.subagent_calls = finite(cur.subagent_calls) + finite(row.subagent_calls);
1860
1916
  addTotals(cur.tokens, row.tokens);
1861
1917
  cur.model_usage = normalizeModelUsageRows([
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+
3
+ const { canonicalUsage } = require("./codex-token-usage");
4
+
5
+ const SUM_FIELDS = [
6
+ "estimated_output_tokens", "estimated_duration_ms", "estimated_request_count",
7
+ "first_response_total_ms", "first_response_sample_count",
8
+ ];
9
+ const MIN_OUTPUT = 200;
10
+ const MIN_DURATION_MS = 1000;
11
+ const MAX_DURATION_MS = 60 * 60 * 1000;
12
+ const SAME_FLUSH_MS = 100;
13
+
14
+ function nonNegative(value) {
15
+ const number = Number(value);
16
+ return Number.isFinite(number) && number >= 0 ? number : 0;
17
+ }
18
+
19
+ function normalizePerformance(value) {
20
+ const out = Object.fromEntries(SUM_FIELDS.map((field) => [field, nonNegative(value?.[field])]));
21
+ out.estimated_tokens_per_second = out.estimated_request_count > 0 && out.estimated_output_tokens > 0 && out.estimated_duration_ms > 0
22
+ ? out.estimated_output_tokens / (out.estimated_duration_ms / 1000) : null;
23
+ out.first_response_ms = out.first_response_sample_count > 0
24
+ ? out.first_response_total_ms / out.first_response_sample_count : null;
25
+ return out;
26
+ }
27
+
28
+ function mergePerformance(...values) {
29
+ const out = normalizePerformance();
30
+ for (const value of values) {
31
+ for (const field of SUM_FIELDS) out[field] += nonNegative(value?.[field]);
32
+ }
33
+ return normalizePerformance(out);
34
+ }
35
+
36
+ function timestampMs(value) {
37
+ if (typeof value !== "string") return null;
38
+ const number = Date.parse(value);
39
+ return Number.isFinite(number) ? number : null;
40
+ }
41
+
42
+ function accumulator() {
43
+ let total = normalizePerformance();
44
+ const byModel = new Map();
45
+ function add(model, delta) {
46
+ total = mergePerformance(total, delta);
47
+ if (model) byModel.set(model, mergePerformance(byModel.get(model), delta));
48
+ }
49
+ function request(model, outputTokens, start, end) {
50
+ const duration = start !== null && end !== null ? end - start : 0;
51
+ if (outputTokens < MIN_OUTPUT || duration < MIN_DURATION_MS || duration > MAX_DURATION_MS) return;
52
+ add(model, { estimated_output_tokens: outputTokens, estimated_duration_ms: duration, estimated_request_count: 1 });
53
+ }
54
+ return { add, request, finish: () => ({ performance: total, byModel }) };
55
+ }
56
+
57
+ function createClaudePerformanceCollector() {
58
+ const nodes = new Map();
59
+ const messages = new Map();
60
+ const totals = accumulator();
61
+
62
+ function consume(obj, key, countedUsage, model) {
63
+ const timestamp = timestampMs(obj?.timestamp || obj?.message?.timestamp);
64
+ if (typeof obj?.uuid === "string" && !nodes.has(obj.uuid)) {
65
+ nodes.set(obj.uuid, {
66
+ parent: obj.parentUuid, timestamp, type: obj.type,
67
+ invalid: Boolean(obj.isApiErrorMessage || obj.isMeta),
68
+ });
69
+ }
70
+ if (obj?.type !== "assistant" || !key) return;
71
+ let timing = messages.get(key);
72
+ if (!timing) {
73
+ // Tokens come from scanClaudeSession's canonical, once-counted delta.
74
+ // Later content blocks extend only the timing, never the numerator.
75
+ timing = {
76
+ parent: obj.parentUuid, startsAtFirstBlock: obj.apiBlockIndex == null || obj.apiBlockIndex === 0,
77
+ end: null, complete: false, invalid: false, model,
78
+ output: nonNegative(countedUsage?.output_tokens) + nonNegative(countedUsage?.reasoning_output_tokens),
79
+ };
80
+ messages.set(key, timing);
81
+ }
82
+ if (timestamp !== null) timing.end = timing.end === null ? timestamp : Math.max(timing.end, timestamp);
83
+ timing.complete ||= ["end_turn", "tool_use", "max_tokens", "stop_sequence"].includes(obj.message?.stop_reason);
84
+ timing.invalid ||= Boolean(obj.isApiErrorMessage || obj.message?.error);
85
+ }
86
+
87
+ function finish() {
88
+ for (const timing of messages.values()) {
89
+ if (!timing.complete || timing.invalid || !timing.startsAtFirstBlock) continue;
90
+ let parent = timing.parent;
91
+ for (let hop = 0; hop < 32 && parent; hop += 1) {
92
+ const node = nodes.get(parent);
93
+ if (!node) break;
94
+ if (node.type === "attachment") { parent = node.parent; continue; }
95
+ // A user row includes tool results. Their completion timestamp keeps
96
+ // time spent executing the tool outside the next inference window.
97
+ if (node.type === "user" && !node.invalid) totals.request(timing.model, timing.output, node.timestamp, timing.end);
98
+ break;
99
+ }
100
+ }
101
+ return totals.finish();
102
+ }
103
+ return { consume, finish };
104
+ }
105
+
106
+ function createCodexPerformanceCollector() {
107
+ const totals = accumulator();
108
+ let boundary = null;
109
+ let lastTokenCount = null;
110
+ let start = null;
111
+ let lastOutput = null;
112
+ let toolAfterOutput = null;
113
+ let usageRecord = null;
114
+ let invalid = false;
115
+ let turnKey = null;
116
+ let turnModels = new Set();
117
+ const seenFirstResponses = new Set();
118
+
119
+ function reset() {
120
+ start = null;
121
+ lastOutput = null;
122
+ toolAfterOutput = null;
123
+ usageRecord = null;
124
+ invalid = false;
125
+ }
126
+ function noteBoundary(ms) {
127
+ if (ms !== null) boundary = boundary === null ? ms : Math.max(boundary, ms);
128
+ }
129
+ function consume(obj, model) {
130
+ const ms = timestampMs(obj?.timestamp);
131
+ const payload = obj?.payload || {};
132
+ if (obj?.type === "compacted") {
133
+ reset();
134
+ noteBoundary(ms);
135
+ return;
136
+ }
137
+ if (obj?.type === "event_msg" && ["task_started", "turn_aborted"].includes(payload.type)) {
138
+ reset();
139
+ noteBoundary(ms);
140
+ turnModels = new Set();
141
+ turnKey = payload.turn_id || null;
142
+ if (payload.type === "turn_aborted") invalid = true;
143
+ }
144
+ if (obj?.type === "turn_context") {
145
+ const nextKey = payload.turn_id || null;
146
+ if (turnKey && nextKey && turnKey !== nextKey) { reset(); turnModels = new Set(); }
147
+ turnKey = nextKey || turnKey;
148
+ noteBoundary(ms);
149
+ }
150
+ const responseType = String(payload.type || "");
151
+ const isModelOutput = obj?.type === "response_item" && (responseType === "reasoning"
152
+ || responseType === "agent_message" || responseType.endsWith("_call")
153
+ || (responseType === "message" && payload.role === "assistant"));
154
+ if (model && (obj?.type === "turn_context" || isModelOutput
155
+ || payload.type === "model_rerouted")) turnModels.add(model);
156
+ if (obj?.type === "event_msg" && ["error", "api_error"].includes(payload.type)) invalid = true;
157
+ if (obj?.type === "event_msg" && payload.type === "task_complete") {
158
+ const raw = payload.time_to_first_token_ms;
159
+ const key = payload.turn_id || turnKey || obj.timestamp;
160
+ if (!invalid && typeof raw === "number" && Number.isFinite(raw) && raw >= 0 && !seenFirstResponses.has(key)) {
161
+ seenFirstResponses.add(key);
162
+ totals.add(turnModels.size === 1 ? [...turnModels][0] : null, { first_response_total_ms: raw, first_response_sample_count: 1 });
163
+ }
164
+ reset();
165
+ return;
166
+ }
167
+ if (obj?.type === "token_usage_record") {
168
+ usageRecord = { end: ms, usage: canonicalUsage(payload.usage) };
169
+ return;
170
+ }
171
+ if (obj?.type !== "response_item") return;
172
+ const type = String(payload.type || "");
173
+ if (type.endsWith("_output")) {
174
+ noteBoundary(ms);
175
+ if (lastOutput !== null) toolAfterOutput = ms;
176
+ } else if ((type === "message" && payload.role !== "assistant") || type === "user_message") {
177
+ noteBoundary(ms);
178
+ } else if (type === "reasoning" || type === "agent_message" || type.endsWith("_call") || (type === "message" && payload.role === "assistant")) {
179
+ // Old rollouts flush an output item just after its token_count.
180
+ if (ms === null || (start === null && lastTokenCount !== null && ms >= lastTokenCount && ms - lastTokenCount <= SAME_FLUSH_MS)) return;
181
+ if (start === null) start = boundary;
182
+ lastOutput = ms;
183
+ toolAfterOutput = null;
184
+ }
185
+ }
186
+ function consumeUsage({ timestamp, delta, rawUsage, model }) {
187
+ // A repeated cumulative snapshot can be written while the next response
188
+ // is still generating. It is not a completion boundary for that response.
189
+ if (!delta || delta.total_tokens <= 0) return;
190
+ const ms = timestampMs(timestamp);
191
+ const raw = canonicalUsage(rawUsage);
192
+ const matches = raw && usageRecord?.usage && ["input_tokens", "cached_input_tokens", "output_tokens"]
193
+ .every((field) => raw[field] === usageRecord.usage[field]);
194
+ const waitedForTools = toolAfterOutput !== null && ms !== null && ms >= toolAfterOutput;
195
+ // Once a tool result follows the final output marker, the token_count's
196
+ // timestamp contains execution time. The output marker is the available
197
+ // response boundary even if the count was flushed much later.
198
+ const end = matches ? usageRecord.end : waitedForTools ? lastOutput : ms;
199
+ // Missing output markers cannot distinguish response timing from a
200
+ // snapshot or inherited context, so even a positive token delta is skipped.
201
+ if (!invalid && lastOutput !== null) totals.request(model, nonNegative(delta.output_tokens), start, end);
202
+ noteBoundary(ms);
203
+ lastTokenCount = ms;
204
+ reset();
205
+ }
206
+ return { consume, consumeUsage, finish: totals.finish };
207
+ }
208
+
209
+ module.exports = {
210
+ normalizePerformance,
211
+ mergePerformance,
212
+ createClaudePerformanceCollector,
213
+ createCodexPerformanceCollector,
214
+ };
@@ -1 +0,0 @@
1
- import{m as R,j as e,a1 as P,o as z,r as D,c as n,X as S,l as K}from"./__vite-browser-external-CPMSmJgP.js";import{m as M}from"./react-CZdcuJ28.js";import{A as U,W as B}from"./icons-Dn9vA9Qu.js";import{u as F}from"./useTokenFormat-CoGcVhPP.js";import{P as G}from"./ProviderIcon-aLwclDsD.js";import{T as W}from"./TrendMonitor-DWxtbzyQ.js";import{T as V}from"./AchievementBadge-DHCKPAgO.js";import{T as X}from"./terminal-CFQOW2Ey.js";import{D as Y,a as Z,b as H,c as q,d as J,e as Q}from"./DialogTitle-DuAY9sfZ.js";import{D as ee}from"./DialogDescription-GXslTq2z.js";import{D as ae}from"./DialogClose-Dn-nxOSp.js";import"./useCurrency-Dh9pUzDO.js";import"./use-latest-request-guard-viIhH7H5.js";import"./PopoverPopup-Btcu7vU0.js";import"./store-DvdXwk-7.js";import"./composite-BN1UKLj0.js";import"./ToolbarRootContext-B0cqyD1i.js";import"./maximize-2-BmtIBzLu.js";import"./use-reduced-motion-Dpq-cKyM.js";import"./zap-BdeAxq64.js";import"./flame-f7119ZMd.js";const C={name:"trending-down",size:24,node:[["path",{d:"M16 17h6v-6",key:"t6n2it"}],["path",{d:"m22 17-8.5-8.5-5 5L2 7",key:"x473p"}]]};C.node;const te=R(C),O=10,_={input:"var(--community-token-input)",cache_read:"var(--community-token-cache-read)",cache_write:"var(--community-token-cache-write)",output:"var(--community-token-output)",reasoning:"var(--community-token-reasoning)"},re={CLAUDE:"var(--community-claude)",CODEX:"var(--community-codex)",GEMINI:"var(--community-gemini)",CURSOR:"var(--community-cursor)",OPENCODE:"var(--community-opencode)",HERMES:"var(--community-hermes)",KIMI:"var(--community-kimi)",MIMO:"var(--community-mimo)",COPILOT:"var(--community-copilot)",KIRO:"var(--community-kiro)",GROK:"var(--community-grok)",DEEPSEEK:"var(--community-deepseek)",ZCODE:"var(--community-zcode)",WORKBUDDY:"var(--community-workbuddy)",MINIMAX:"var(--community-minimax)",DROID:"var(--community-droid)"};function ie(t){return re[String(t||"").toUpperCase()]||"var(--community-muted)"}const se=[[/claude|fable|opus|sonnet|haiku/,"CLAUDE"],[/gpt|codex|o3|o4/,"CODEX"],[/gemini/,"GEMINI"],[/composer|cursor/,"CURSOR"],[/kimi/,"KIMI"],[/mimo/,"MIMO"],[/copilot/,"COPILOT"],[/kiro/,"KIRO"],[/grok/,"GROK"],[/deepseek/,"DEEPSEEK"],[/glm/,"ZCODE"],[/hy3/,"WORKBUDDY"],[/minimax/,"MINIMAX"]],g=["overview","providers","community","models"];function oe(t){const s=String(t||"").toLowerCase(),a=se.find(([i])=>i.test(s));return a?a[1]:""}function u(t){const s=Number(t);return Number.isFinite(s)?`${s.toFixed(1)}%`:"—"}function p(t){return K(Number(t)||0)}function ne(t){return t?new Date(`${t}T00:00:00Z`).toLocaleDateString(void 0,{month:"short",day:"numeric",timeZone:"UTC"}):""}function k(){return e.jsx("div",{className:"flex h-full min-h-40 items-center justify-center text-xs text-oai-gray-400",children:n("leaderboard.community.modal.no_data")})}function b({children:t,detail:s}){return e.jsxs("div",{className:"mb-3 flex items-baseline justify-between gap-4",children:[e.jsx("h3",{className:"text-xs font-medium uppercase tracking-wide text-oai-gray-500 dark:text-oai-gray-400",children:t}),s?e.jsx("span",{className:"text-xs text-oai-gray-400",children:s}):null]})}function I({value:t,label:s}){const a=Number(t);if(!Number.isFinite(a))return null;const i=Math.sign(a)!==-1,r=i?V:te;return e.jsxs("span",{className:`inline-flex items-center gap-1 text-xs font-medium tabular-nums ${i?"text-emerald-600 dark:text-emerald-400":"text-rose-600 dark:text-rose-400"}`,title:s,children:[e.jsx(r,{className:"size-3","aria-hidden":"true"}),i?"+":"",a.toFixed(1),"%"]})}function j({label:t,value:s,title:a,delta:i,deltaLabel:r}){return e.jsxs("div",{className:"min-w-0",children:[e.jsx("div",{className:"truncate text-xs font-medium text-oai-gray-500 dark:text-oai-gray-400",children:t}),e.jsxs("div",{className:"mt-1 flex min-w-0 items-baseline gap-2",children:[e.jsx("span",{className:"truncate text-xl font-semibold leading-none tracking-tight text-oai-black tabular-nums dark:text-white",title:a,children:s}),e.jsx(I,{value:i,label:r})]})]})}function A({fillWidth:t,share:s,color:a="var(--oai-blue)",ariaLabel:i,dataAttrs:r,children:o}){return e.jsxs("div",{...r,className:"group relative grid min-h-11 grid-cols-[minmax(0,1fr)_auto] items-center gap-3 overflow-hidden px-2.5 transition-colors hover:bg-oai-gray-50 dark:hover:bg-oai-gray-900/60",children:[e.jsx("div",{className:"pointer-events-none absolute inset-y-0 left-0 opacity-[0.15] transition-opacity group-hover:opacity-[0.22] dark:opacity-[0.24] dark:group-hover:opacity-[0.32]",style:{width:`${t}%`,backgroundColor:a},role:"progressbar","aria-label":i,"aria-valuemin":"0","aria-valuemax":"100","aria-valuenow":Number(s)||0}),o]})}function T({rank:t,provider:s,name:a,fillWidth:i,share:r,tokens:o,tokensTitle:l,dataAttrs:c}){return e.jsxs(A,{fillWidth:i,share:r,color:ie(s),ariaLabel:`${a} ${u(r)}`,dataAttrs:c,children:[e.jsxs("div",{className:"relative flex min-w-0 items-center gap-2.5",children:[e.jsx("span",{className:`w-5 shrink-0 text-xs tabular-nums ${t<=3?"font-semibold text-oai-gray-700 dark:text-oai-gray-200":"text-oai-gray-400"}`,children:t}),e.jsx("span",{className:"grid size-6 shrink-0 place-items-center text-oai-gray-700 dark:text-oai-gray-200",children:e.jsx(G,{provider:s,size:15})}),e.jsx("span",{className:"truncate text-xs font-medium text-oai-gray-700 dark:text-oai-gray-200",title:a,children:a})]}),e.jsxs("div",{className:"relative min-w-[4.5rem] text-right",children:[e.jsx("div",{className:"text-xs font-semibold text-oai-black tabular-nums dark:text-white",title:l,children:o}),e.jsx("div",{className:"text-xs text-oai-gray-500 tabular-nums dark:text-oai-gray-400",children:u(r)})]})]})}function le({rows:t,formatTokensTooltip:s}){return t.length?e.jsxs("div",{className:"mt-7",children:[e.jsx(b,{children:n("leaderboard.community.modal.token_mix")}),e.jsx("div",{className:"flex h-1.5 overflow-hidden bg-oai-gray-100 dark:bg-oai-gray-800",children:t.map(a=>e.jsx("div",{style:{width:`${Math.max(Number(a.share)||0,0)}%`,backgroundColor:_[a.key]||"var(--community-muted)"},title:`${n(`leaderboard.community.modal.token_mix.${a.key}`)} · ${u(a.share)}`},a.key))}),e.jsx("div",{className:"mt-3 flex flex-wrap gap-x-5 gap-y-1.5",children:t.map(a=>e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",title:s(a.tokens),children:[e.jsx("span",{className:"size-2",style:{backgroundColor:_[a.key]||"var(--community-muted)"}}),n(`leaderboard.community.modal.token_mix.${a.key}`),e.jsx("span",{className:"tabular-nums text-oai-gray-700 dark:text-oai-gray-200",children:u(a.share)})]},a.key))})]}):null}function f({label:t,value:s,title:a,delta:i,deltaLabel:r,className:o=""}){return e.jsxs("div",{className:`min-w-0 ${o}`,children:[e.jsx("div",{className:"truncate text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}),e.jsxs("div",{className:"mt-1 flex min-w-0 items-baseline gap-2",children:[e.jsx("span",{className:"truncate text-base font-semibold leading-none tracking-tight tabular-nums text-oai-black dark:text-white",title:a,children:s}),e.jsx(I,{value:i,label:r})]})]})}function de({trendRows:t,activeDevelopers30d:s,developerGrowthPct:a,formatTokens:i,formatTokensTooltip:r}){if(!t.length)return null;const o=t.reduce((m,h)=>m+h.value,0),l=t.reduce((m,h)=>m.value<h.value?h:m,t[0]),c=o/t.length;return e.jsxs("div",{className:"mt-5 grid grid-cols-3 divide-x divide-oai-gray-100 border-y border-oai-gray-100 py-3.5 dark:divide-oai-gray-800/80 dark:border-oai-gray-800/80",children:[e.jsx(f,{className:"pr-4",label:n("leaderboard.community.modal.daily_average"),value:i(c),title:r(c)}),e.jsx(f,{className:"px-4",label:`${n("leaderboard.community.modal.peak_day")} · ${l.day}`,value:i(l.value),title:r(l.value)}),e.jsx(f,{className:"pl-4",label:n("leaderboard.community.modal.active_30d"),value:p(s),title:Number(s).toLocaleString(),delta:a,deltaLabel:n("leaderboard.community.modal.vs_previous_week")})]})}function me({trendRows:t,mix:s,activeDevelopers30d:a,developerGrowthPct:i,formatTokens:r,formatTokensTooltip:o}){return!t.length&&!s.length?e.jsx(k,{}):e.jsxs("div",{children:[e.jsx(b,{detail:n("leaderboard.community.modal.utc_note"),children:n("leaderboard.community.modal.community_activity")}),e.jsx(W,{embedded:!0,rows:t,from:t[0]?.day,to:t.at(-1)?.day,chartHeightClass:"h-44"}),e.jsx(de,{trendRows:t,activeDevelopers30d:a,developerGrowthPct:i,formatTokens:r,formatTokensTooltip:o}),e.jsx(le,{rows:s,formatTokensTooltip:o})]})}function ce({rows:t,maxShare:s,formatTokens:a,formatTokensTooltip:i}){return t.length?e.jsx("div",{className:"space-y-0.5",children:t.map((r,o)=>e.jsx(T,{rank:o+1,dataAttrs:{"data-provider-rank":o+1,"data-provider-name":r.name},provider:String(r.name).toUpperCase(),name:r.name,fillWidth:Math.max((Number(r.share)||0)/s*100,2),share:r.share,tokens:a(r.tokens),tokensTitle:i(r.tokens)},r.name))}):e.jsx(k,{})}function xe({rows:t,maxShare:s,formatTokens:a,formatTokensTooltip:i}){return t.length?e.jsx("div",{className:"space-y-0.5",children:t.map((r,o)=>{const l=Number(r.share)||0;return e.jsx(T,{rank:o+1,dataAttrs:{"data-model-rank":o+1,"data-model-name":r.name},provider:oe(r.name),name:r.name,fillWidth:Math.max(l/s*100,2),share:l,tokens:a(r.tokens),tokensTitle:i(r.tokens)},r.name)})}):e.jsx(k,{})}function ue({name:t}){const s=String(t||"").toLowerCase();return s==="darwin"?e.jsx(U,{className:"size-3.5"}):s==="win32"?e.jsx(B,{className:"size-3.5"}):e.jsx(X,{className:"size-4","aria-hidden":"true"})}const he=.175,ye=.3;function ge({bands:t,formatTokensTooltip:s}){const a=t.reduce((r,o)=>Math.max(r,Number(o.developer_share)||0),0)||1,i=r=>`${n(`leaderboard.community.modal.band.${r.key}`)} · ${n("leaderboard.community.modal.developer_count",{count:p(r.developers)})} (${s(r.tokens)})`;return e.jsxs("div",{children:[e.jsx("div",{className:"flex gap-2 border-b border-oai-gray-200 dark:border-oai-gray-800",children:t.map((r,o)=>{const l=Number(r.developer_share)||0,c=Math.max(l/a*80,2);return e.jsxs("div",{className:"relative h-32 min-w-0 flex-1",title:i(r),children:[e.jsx("div",{role:"progressbar","aria-label":n("leaderboard.community.modal.developer_share_title",{share:u(l)}),"aria-valuemin":"0","aria-valuemax":"100","aria-valuenow":l,className:"absolute inset-x-0 bottom-0",style:{height:`${c}%`,backgroundColor:"var(--oai-blue)",opacity:ye+o*he}}),e.jsx("div",{className:"absolute inset-x-0 text-center text-xs font-medium tabular-nums text-oai-gray-700 dark:text-oai-gray-200",style:{bottom:`calc(${c}% + 4px)`},children:u(l)})]},r.key)})}),e.jsx("div",{className:"mt-2 flex gap-2",children:t.map(r=>e.jsxs("div",{className:"min-w-0 flex-1 text-center",title:i(r),children:[e.jsx("div",{className:"truncate text-xs font-medium text-oai-gray-700 dark:text-oai-gray-200",children:n(`leaderboard.community.modal.band.${r.key}`)}),e.jsx("div",{className:"truncate text-xs tabular-nums text-oai-gray-500 dark:text-oai-gray-400",children:p(r.developers)})]},r.key))})]})}function pe({bands:t,platforms:s,formatTokensTooltip:a}){return!t.length&&!s.length?e.jsx(k,{}):e.jsxs("div",{className:"space-y-8",children:[t.length?e.jsxs("div",{children:[e.jsx(b,{children:n("leaderboard.community.modal.usage_distribution")}),e.jsx(ge,{bands:t,formatTokensTooltip:a})]}):null,s.length?e.jsxs("div",{children:[e.jsx(b,{children:n("leaderboard.community.modal.platforms")}),e.jsx("div",{className:"space-y-0.5",children:s.map(i=>e.jsxs(A,{fillWidth:Math.max(Number(i.share)||0,1),share:i.share,ariaLabel:`${n(`leaderboard.community.modal.platform.${i.name}`)} ${u(i.share)}`,children:[e.jsxs("div",{className:"relative flex min-w-0 items-center gap-2.5",children:[e.jsx("span",{className:"grid size-6 shrink-0 place-items-center text-oai-gray-700 dark:text-oai-gray-200",children:e.jsx(ue,{name:i.name})}),e.jsx("span",{className:"truncate text-xs font-medium text-oai-gray-700 dark:text-oai-gray-200",children:n(`leaderboard.community.modal.platform.${i.name}`)})]}),e.jsxs("div",{className:"relative min-w-[4.5rem] text-right",children:[e.jsx("div",{className:"text-xs font-semibold text-oai-black tabular-nums dark:text-white",children:u(i.share)}),e.jsx("div",{className:"text-xs text-oai-gray-500 tabular-nums dark:text-oai-gray-400",title:Number(i.machines||0).toLocaleString(),children:n("leaderboard.community.modal.machine_count",{count:p(i.machines)})})]})]},i.name))})]}):null]})}function ve({isOpen:t,onClose:s,communityStats:a}){const{formatTokens:i,formatTokensTooltip:r}=F(),[o,l]=D.useState("overview"),c=a?.status==="ready",m=D.useMemo(()=>{if(!c)return null;const d=(Array.isArray(a.providers)?a.providers:[]).slice(0,O),x=(Array.isArray(a.topModels)?a.topModels:[]).slice(0,O),y=v=>v.reduce(($,L)=>Math.max($,Number(L.share)||0),0)||1;return{providers:d,models:x,providersMaxShare:y(d),modelsMaxShare:y(x),trendRows:(Array.isArray(a.dailyGrowth)?a.dailyGrowth:[]).map(v=>({day:ne(v.day),value:Number(v.tokens)||0})),mix:Array.isArray(a.tokenMix)?a.tokenMix:[],bands:Array.isArray(a.userDistribution)?a.userDistribution:[],platforms:Array.isArray(a.platforms)?a.platforms:[]}},[c,a]);if(!m)return null;const h=Number(a.tokenFloor)||0,N=Number(a.activeDevelopersTotal)||Number(a.totalEntries)||0,w=Number(a.tokens30d)||0,E=d=>{if(d.key!=="ArrowRight"&&d.key!=="ArrowLeft")return;d.preventDefault();const x=d.key==="ArrowRight"?1:-1,y=g[(g.indexOf(o)+x+g.length)%g.length];l(y),d.currentTarget.querySelector(`[data-tab-id="${y}"]`)?.focus()};return e.jsx(Y,{open:t,onOpenChange:d=>!d&&s(),children:e.jsxs(Z,{children:[e.jsx(H,{className:"fixed inset-0 z-[100] bg-black/50 backdrop-blur-[3px] transition-opacity duration-200 ease-out data-[ending-style]:opacity-0 data-[starting-style]:opacity-0"}),e.jsx(q,{className:"fixed inset-0 z-[101] flex items-center justify-center p-2 sm:p-5",children:e.jsxs(J,{className:"relative flex h-[min(42rem,92vh)] w-full max-w-xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl ring-1 ring-black/10 transition-[opacity,transform] duration-[220ms] ease-[cubic-bezier(0.16,1,0.3,1)] data-[ending-style]:translate-y-3 data-[ending-style]:scale-[0.98] data-[ending-style]:opacity-0 data-[starting-style]:translate-y-3 data-[starting-style]:scale-[0.98] data-[starting-style]:opacity-0 dark:bg-oai-gray-950 dark:ring-white/10",children:[e.jsxs("header",{"data-testid":"community-metrics-header",className:"shrink-0 px-5 pt-5 sm:px-6",children:[e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{children:[e.jsx(Q,{className:"text-lg font-semibold tracking-[-0.02em] text-oai-black dark:text-white",children:n("leaderboard.community.modal.title")}),e.jsx(ee,{className:"sr-only",children:n("leaderboard.community.modal.description")})]}),e.jsx(ae,{className:"-mr-1 -mt-1 grid size-8 place-items-center rounded-full text-oai-gray-400 transition-colors hover:bg-black/5 hover:text-oai-black dark:hover:bg-white/10 dark:hover:text-white","aria-label":n("shared.dialog.close"),children:e.jsx(S,{className:"size-4"})})]}),e.jsxs("div",{className:"mt-4 grid grid-cols-3 gap-x-5",children:[e.jsx(j,{label:n("leaderboard.community.modal.total_tokens"),value:i(h),title:r(h)}),e.jsx(j,{label:n("leaderboard.community.modal.tokens_30d"),value:i(w),title:r(w),delta:a.tokenGrowthPct,deltaLabel:n("leaderboard.community.modal.vs_previous_week")}),e.jsx(j,{label:n("leaderboard.community.modal.contributors"),value:p(N),title:N.toLocaleString()})]}),e.jsx("div",{className:"mt-4 flex items-end overflow-x-auto border-b border-oai-gray-100 dark:border-oai-gray-900",role:"tablist","aria-label":n("leaderboard.community.modal.views"),onKeyDown:E,children:g.map(d=>{const x=o===d;return e.jsxs("button",{type:"button",role:"tab","data-tab-id":d,"aria-selected":x,tabIndex:x?0:-1,onClick:()=>l(d),className:`relative mr-5 h-10 shrink-0 text-sm font-medium transition-colors last:mr-0 ${x?"text-oai-black dark:text-white":"text-oai-gray-500 hover:text-oai-gray-700 dark:text-oai-gray-400 dark:hover:text-oai-gray-200"}`,children:[n(`leaderboard.community.modal.tab.${d}`),x?e.jsx(M.span,{layoutId:"community-metrics-tab-underline",className:"absolute inset-x-0 -bottom-px h-0.5 rounded-full bg-oai-black dark:bg-white",transition:{type:"spring",stiffness:420,damping:34}}):null]},d)})})]}),e.jsx("div",{"data-testid":"community-metrics-content",className:"min-h-0 flex-1 overflow-y-auto px-5 py-5 sm:px-6",children:e.jsxs(M.div,{initial:{opacity:0,y:4},animate:{opacity:1,y:0},transition:{duration:.18,ease:[.16,1,.3,1]},className:"h-full",children:[o==="overview"?e.jsx(me,{trendRows:m.trendRows,mix:m.mix,activeDevelopers30d:Number(a.activeDevelopers30d)||0,developerGrowthPct:a.developerGrowthPct,formatTokens:i,formatTokensTooltip:r}):null,o==="providers"?e.jsx(ce,{rows:m.providers,maxShare:m.providersMaxShare,formatTokens:i,formatTokensTooltip:r}):null,o==="community"?e.jsx(pe,{bands:m.bands,platforms:m.platforms,formatTokensTooltip:r}):null,o==="models"?e.jsx(xe,{rows:m.models,maxShare:m.modelsMaxShare,formatTokens:i,formatTokensTooltip:r}):null]},o)})]})})]})})}function Ke(t){return e.jsx(P,{mode:z.COMPACT,children:e.jsx(ve,{...t})})}export{Ke as CommunityStatsModal};