tokentracker-cli 1.5.3 → 1.5.5

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 (137) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-uuDGO9Mq.js → AchievementBadge-BYxdWhS1.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-BCTVT7Jo.js → AchievementsPage-Bv9o7Aj8.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-B6fJSJSr.js → AchievementsSection-DE5bZGxg.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-FjUN__9G.js → BadgeDetailModal-CgZaSVTp.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-B-KqY_Wr.js → BotAnimated-CwhPfUBI.js} +1 -1
  6. package/dashboard/dist/assets/{Card-DS8T8piP.js → Card-Y7qaxMuE.js} +1 -1
  7. package/dashboard/dist/assets/{CloudCheckoutPage-_0UjsFPu.js → CloudCheckoutPage-DVl7jk6b.js} +1 -1
  8. package/dashboard/dist/assets/{CloudPage-BstgVBaV.js → CloudPage-BnIvpHGH.js} +2 -2
  9. package/dashboard/dist/assets/{CommandPalette-DtVsYZgc.js → CommandPalette-CzMv2Zxn.js} +1 -1
  10. package/dashboard/dist/assets/{CommunityStatsModal-DTpEJqV3.js → CommunityStatsModal-D8sOZqUc.js} +1 -1
  11. package/dashboard/dist/assets/{ConfirmModal-Lfdh2vr7.js → ConfirmModal-Bun5xKMm.js} +1 -1
  12. package/dashboard/dist/assets/{Controls-DpwQ7pkq.js → Controls-ltfnvkFW.js} +1 -1
  13. package/dashboard/dist/assets/DashboardPage-C3Xu6_3t.js +1 -0
  14. package/dashboard/dist/assets/{DateRangePopover-CrWgrzT2.js → DateRangePopover-e3gJYqFJ.js} +1 -1
  15. package/dashboard/dist/assets/{DevicePage-DbK5KWJS.js → DevicePage-JgT16dEp.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-Dd2B_o03.js → DialogClose-DeWert7h.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-Ca6W3nCT.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-3JQBVha3.js → DialogTitle-BfK_yOeR.js} +1 -1
  19. package/dashboard/dist/assets/{ElectricLogo-LYAqG8pG.js → ElectricLogo-Bbev0Y8Q.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-DhDvUor7.js → FadeIn-fypYWLDK.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-9f1XmgYC.js → HeaderGithubStar-Cnil2Lbo.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-BXhHAkP7.js → HoverTooltip-Bw_8tru-.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-ChPdDeMl.js → IpCheckPage-CHnhUgA2.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-DnJ7Y9zw.js → LandingPage-Bq4iQS6S.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardPage-BF9isLzu.js → LeaderboardPage-D42lUdw-.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProBadge-BM4JKgkC.js → LeaderboardProBadge-eTKceT90.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-DfZABOpN.js → LeaderboardProfileModal-D-3tRrOR.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-BaZ38iT5.js → LeaderboardProfilePage-DkRCJoiA.js} +1 -1
  29. package/dashboard/dist/assets/LimitsPage-dUXjP3_1.js +6 -0
  30. package/dashboard/dist/assets/{LocalOnlyNotice-DvhlYoqQ.js → LocalOnlyNotice-CtpjB6Z2.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-g_Gbnzhs.js → LoginCard-zrRd8o54.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-DkJrI6b7.js → LoginModal-DNl5Rt1e.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-CqCKSo0n.js → LoginPage-BuB707vV.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-DbF7ROsj.js → PetPage-BpDriXkp.js} +2 -2
  35. package/dashboard/dist/assets/{PopoverPopup-4WuH41ZT.js → PopoverPopup-rQNSs8R6.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-6j0A2KD9.js → ProviderIcon-CumyO371.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-DBd-iwuM.js → ResetPasswordPage-ecUHfJAk.js} +1 -1
  38. package/dashboard/dist/assets/{Select-BBEpLeiG.js → Select-YVEqEhk4.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-Cn_U_rrO.js → SelectItemText-D1NBc4uc.js} +1 -1
  40. package/dashboard/dist/assets/{SelfHostPage-2WZc6Rml.js → SelfHostPage-8nAin68c.js} +1 -1
  41. package/dashboard/dist/assets/ServiceStatusPage-D1zxuzOg.js +1 -0
  42. package/dashboard/dist/assets/SessionsPage-B6ksEER_.js +3 -0
  43. package/dashboard/dist/assets/{SettingsPage-rXNr96i-.js → SettingsPage-COqP8ZbJ.js} +3 -3
  44. package/dashboard/dist/assets/{SkillsPage-CudTvbZL.js → SkillsPage-DCX7xTj0.js} +1 -1
  45. package/dashboard/dist/assets/TokenGalaxy-D4n7AR-R.js +4431 -0
  46. package/dashboard/dist/assets/{TooltipPopup-CsLKXhWC.js → TooltipPopup-DvSoB_eS.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-8onUTbkk.js → TrendMonitor-5GVzh0Ye.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-CcwE0lz8.js → WidgetsPage-BIc4MBiu.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-Cj9LLYqP.js → WrappedPage-BpQk4g0o.js} +1 -1
  50. package/dashboard/dist/assets/__vite-browser-external-B_rKFTTN.js +45 -0
  51. package/dashboard/dist/assets/__vite-browser-external-CMBWGkOq.css +1 -0
  52. package/dashboard/dist/assets/{agent-logos-ChBA1nAH.js → agent-logos-DVg-sTm4.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-left-Bb5mfoPc.js → arrow-left-Ch6Q7eU5.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-right-D1Ti2XUI.js → arrow-right-B2B9GvSI.js} +1 -1
  55. package/dashboard/dist/assets/{arrow-up-right-B_CjA_y8.js → arrow-up-right-DwNO_uSb.js} +1 -1
  56. package/dashboard/dist/assets/{calendar-days-DdfrAqie.js → calendar-days---NjJlBi.js} +1 -1
  57. package/dashboard/dist/assets/{check-29zGw1MK.js → check-D3mhgRrW.js} +1 -1
  58. package/dashboard/dist/assets/{chevron-down-DAzlFX7g.js → chevron-down-x-b39Zto.js} +1 -1
  59. package/dashboard/dist/assets/{cloud-checkout-C2SeCjFM.js → cloud-checkout-CycWyTsg.js} +1 -1
  60. package/dashboard/dist/assets/{composite-DIf735Hq.js → composite-CnA1qtYn.js} +1 -1
  61. package/dashboard/dist/assets/{copy-BOuOM0HQ.js → copy-JRnRMgdX.js} +1 -1
  62. package/dashboard/dist/assets/{download-50tQRpGe.js → download-CRLqGD62.js} +1 -1
  63. package/dashboard/dist/assets/{external-link-BhlQClVe.js → external-link-BrmaWV_t.js} +1 -1
  64. package/dashboard/dist/assets/{flame-CeGfA-0L.js → flame-meQ5-vRD.js} +1 -1
  65. package/dashboard/dist/assets/{format-tokens-Cq7YiAkJ.js → format-tokens-D8mIJGz1.js} +1 -1
  66. package/dashboard/dist/assets/{gift-Do-UFhiT.js → gift-Cd0FPXgO.js} +1 -1
  67. package/dashboard/dist/assets/{icons-C6EZ_HmP.js → icons-D-PTKw7b.js} +1 -1
  68. package/dashboard/dist/assets/{index-BWhbBAd9.js → index-CMV9UKNf.js} +1 -1
  69. package/dashboard/dist/assets/{index-c0crpe9Z.js → index-C_6gEM83.js} +1 -1
  70. package/dashboard/dist/assets/{index-BFrbVqsm.js → index-DCIZaLNC.js} +1 -1
  71. package/dashboard/dist/assets/index-HLYcwmPD.js +502 -0
  72. package/dashboard/dist/assets/{info-B4MXmsNE.js → info-e0tP9P3D.js} +1 -1
  73. package/dashboard/dist/assets/{link-2-DuWCslzl.js → link-2-DZ2TQhER.js} +1 -1
  74. package/dashboard/dist/assets/{loader-circle-C2I3NZAO.js → loader-circle-DjflRntu.js} +1 -1
  75. package/dashboard/dist/assets/{local-usage-transfer-BQasvsUF.js → local-usage-transfer-XTZLA3XH.js} +1 -1
  76. package/dashboard/dist/assets/{modifiers.esm-DbeNS6FT.js → modifiers.esm-BpEQ8-Gt.js} +1 -1
  77. package/dashboard/dist/assets/module-BbVsRaE2.js +3 -0
  78. package/dashboard/dist/assets/{monitor-smartphone-C6Y4ktsc.js → monitor-smartphone-VVBcI30G.js} +1 -1
  79. package/dashboard/dist/assets/{play-CwgcDJC2.js → play-DN4Sb3W_.js} +1 -1
  80. package/dashboard/dist/assets/{plus-B796wsUO.js → plus-Cz_k7arH.js} +1 -1
  81. package/dashboard/dist/assets/{provider-display-Cw26RFDm.js → provider-display-S1mSaydF.js} +1 -1
  82. package/dashboard/dist/assets/{react-d7Bk1-_K.js → react-MQBcjrn_.js} +1 -1
  83. package/dashboard/dist/assets/{safePolygon-CDE0dNv_.js → safePolygon-InKxYcXd.js} +1 -1
  84. package/dashboard/dist/assets/{search-DtKvsdRs.js → search-C-N0RDi0.js} +1 -1
  85. package/dashboard/dist/assets/{server-gAURHUyz.js → server-CZkn8vIZ.js} +1 -1
  86. package/dashboard/dist/assets/{shield-check-DFVdDTwh.js → shield-check-DPrGFouZ.js} +1 -1
  87. package/dashboard/dist/assets/{skills-api-2rdPUrJI.js → skills-api-wMwM-GpF.js} +1 -1
  88. package/dashboard/dist/assets/{sortable.esm-DFlfwnJr.js → sortable.esm-CUPP1ihT.js} +1 -1
  89. package/dashboard/dist/assets/{store-BKPCk_de.js → store-BpVdRYnQ.js} +1 -1
  90. package/dashboard/dist/assets/{terminal-BtWFakvS.js → terminal-C-7TFeKg.js} +1 -1
  91. package/dashboard/dist/assets/{trash-bpr9zTAM.js → trash-Be8cd4HD.js} +1 -1
  92. package/dashboard/dist/assets/{use-community-stats-DMnGgLne.js → use-community-stats-cG0WrB4Y.js} +1 -1
  93. package/dashboard/dist/assets/{use-latest-request-guard-BSBlJJHe.js → use-latest-request-guard-C91enyPD.js} +1 -1
  94. package/dashboard/dist/assets/use-limits-display-prefs-DdwoLqPD.js +1 -0
  95. package/dashboard/dist/assets/{use-native-settings-OthqiF6g.js → use-native-settings-BR-NiMKh.js} +1 -1
  96. package/dashboard/dist/assets/{use-ordered-list-DpAtr0R-.js → use-ordered-list-oqV724BP.js} +1 -1
  97. package/dashboard/dist/assets/use-reduced-motion-DW5YHBBI.js +1 -0
  98. package/dashboard/dist/assets/{use-transform-BRz9dz_2.js → use-transform-BsNSgVD6.js} +1 -1
  99. package/dashboard/dist/assets/{use-usage-limits-CwmhudAX.js → use-usage-limits-Dk8LcMU-.js} +1 -1
  100. package/dashboard/dist/assets/{useCurrency-CAA-tQ27.js → useCurrency-FxerRfd2.js} +1 -1
  101. package/dashboard/dist/assets/{usePositioner-Cj2DPmwM.js → usePositioner-D2J-rSfX.js} +1 -1
  102. package/dashboard/dist/assets/{useTokenFormat-Bf5-f7Hx.js → useTokenFormat-CaI2nl2C.js} +1 -1
  103. package/dashboard/dist/assets/{zap-B8Fg6PaC.js → zap-D3C37XAj.js} +1 -1
  104. package/dashboard/dist/index.html +2 -2
  105. package/dashboard/dist/ip-check.html +2 -2
  106. package/dashboard/dist/leaderboard.html +2 -2
  107. package/dashboard/dist/share.html +2 -2
  108. package/package.json +4 -4
  109. package/src/commands/serve.js +3 -2
  110. package/src/lib/accept-encoding.js +59 -0
  111. package/src/lib/cursor-config.js +18 -2
  112. package/src/lib/deepseek-limits.js +280 -0
  113. package/src/lib/grok-limits.js +40 -1
  114. package/src/lib/local-api.js +229 -31
  115. package/src/lib/opencode-status.js +88 -0
  116. package/src/lib/pricing/curated-overrides.json +14 -0
  117. package/src/lib/pricing/index.js +182 -79
  118. package/src/lib/pricing/seed-snapshot.json +1 -1
  119. package/src/lib/provider-api-key-store.js +2 -0
  120. package/src/lib/provider-status.js +17 -5
  121. package/src/lib/rollout.js +13 -4
  122. package/src/lib/session-analytics.js +194 -29
  123. package/src/lib/static-server.js +74 -2
  124. package/src/lib/usage-limits.js +801 -26
  125. package/src/lib/workbuddy-limits.js +106 -14
  126. package/dashboard/dist/assets/DashboardPage-DUFWejkE.js +0 -1
  127. package/dashboard/dist/assets/DialogDescription-CLy0arbt.js +0 -1
  128. package/dashboard/dist/assets/LimitsPage-C7LbG3LX.js +0 -6
  129. package/dashboard/dist/assets/ServiceStatusPage-B-g4MDl0.js +0 -1
  130. package/dashboard/dist/assets/SessionsPage-CWS0iQ1L.js +0 -3
  131. package/dashboard/dist/assets/TokenGalaxy-BRKrbTsJ.js +0 -4426
  132. package/dashboard/dist/assets/__vite-browser-external-07djSGmy.js +0 -45
  133. package/dashboard/dist/assets/__vite-browser-external-BDldtsD2.css +0 -1
  134. package/dashboard/dist/assets/index-D9kwYWLX.js +0 -502
  135. package/dashboard/dist/assets/module-ZyVsxBG_.js +0 -3
  136. package/dashboard/dist/assets/use-limits-display-prefs-eYlvnUKn.js +0 -1
  137. package/dashboard/dist/assets/use-reduced-motion-DF0grwHs.js +0 -1
@@ -172,6 +172,17 @@ function computeModelPricingInfo(model, opts, sourceKey) {
172
172
  output: Number(rates.output) || 0,
173
173
  cache_read: Number(rates.cache_read) || 0,
174
174
  cache_write: Number(rates.cache_write) || 0,
175
+ // Speed-tier multipliers so the dashboard can label Fast/Ultrafast
176
+ // sub-rows with the actual premium instead of hardcoding 2x/6x (issue 886).
177
+ // Mirrored from the raw entry only when it publishes them — the info
178
+ // object must keep matching getModelPricing key-for-key
179
+ // (session-performance.test.js pins that contract).
180
+ ...(Number.isFinite(Number(rates.priority_multiplier))
181
+ ? { priority_multiplier: Number(rates.priority_multiplier) }
182
+ : null),
183
+ ...(Number.isFinite(Number(rates.ultrafast_multiplier))
184
+ ? { ultrafast_multiplier: Number(rates.ultrafast_multiplier) }
185
+ : null),
175
186
  };
176
187
  }
177
188
 
@@ -221,6 +232,110 @@ function getRowPricing(row) {
221
232
  };
222
233
  }
223
234
 
235
+ // Codex-family reasoning folding, shared by computeRowCost and the speed-tier
236
+ // allocation below. OmO, like Codex, reports reasoning as a subset of `output`
237
+ // (its own usage.cost bills no separate reasoning component), so charging it
238
+ // again here would double-bill every reasoning token. Cline is the same: its
239
+ // `outputTokens` is the AI SDK total (text + reasoning), so
240
+ // reasoning_output_tokens is a subset marker, never a second line item.
241
+ function reasoningFoldsIntoOutput(row) {
242
+ return (
243
+ row.source === "codex" ||
244
+ row.source === "acode" ||
245
+ row.source === "every-code" ||
246
+ row.source === "omo" ||
247
+ row.source === "cline" ||
248
+ row.source === "muse"
249
+ );
250
+ }
251
+
252
+ // One row's observed-subset context: the bounded long-context columns that the
253
+ // speed-tier subsets intersect with, plus whether the row's model prices a
254
+ // long-context tier at all. Pure arithmetic on the row; both computeRowCost
255
+ // and computeRowTierCosts build it so their per-tier math cannot drift.
256
+ function rowSubsetContext(row, reasoningIncludedInOutput) {
257
+ const bounded = (value, total) => Math.min(
258
+ Math.max(0, Number(value) || 0),
259
+ Math.max(0, Number(total) || 0),
260
+ );
261
+ const model = String(row?.model || "").toLowerCase();
262
+ const usesOpenAILongContextTier =
263
+ model === "gpt-5.6" || model.includes("gpt-5.6-sol") || model.includes("gpt-6-astra") ||
264
+ model.includes("gpt-6-sol") || model.includes("gpt-6.1-sol") ||
265
+ model.includes("gpt-6.1-terra");
266
+ // Both premium families below read "observed subset" columns: how much of the
267
+ // row's own token columns came from requests with a given property. They are
268
+ // annotations, never addends — total_tokens stays the sum of the base columns
269
+ // whether or not they are present, and a legacy row that omits them prices
270
+ // exactly as it did before.
271
+ //
272
+ // The parser records only usage from requests whose cache-inclusive input
273
+ // exceeded 272K. OpenAI prices the whole such request at 2x input and 1.5x
274
+ // output, so add the premium over the standard-rate base exactly once.
275
+ const longInput = bounded(row.long_context_input_tokens, row.input_tokens);
276
+ const longCached = bounded(row.long_context_cached_input_tokens, row.cached_input_tokens);
277
+ const longCacheWrite = bounded(
278
+ row.long_context_cache_creation_input_tokens,
279
+ row.cache_creation_input_tokens,
280
+ );
281
+ const longOutput = bounded(row.long_context_output_tokens, row.output_tokens);
282
+ const longReasoning = reasoningIncludedInOutput
283
+ ? 0
284
+ : bounded(row.long_context_reasoning_output_tokens, row.reasoning_output_tokens);
285
+ return {
286
+ bounded,
287
+ usesOpenAILongContextTier,
288
+ longInput,
289
+ longCached,
290
+ longCacheWrite,
291
+ longOutput,
292
+ longReasoning,
293
+ };
294
+ }
295
+
296
+ // Standard-rate cost of one observed speed-tier subset (priority_/ultrafast_):
297
+ // the tier's own token columns priced at the row's base rates, plus the same
298
+ // treatment for its long-context intersection. computeRowCost multiplies this
299
+ // by (multiplier - 1) for the premium; computeRowTierCosts books it to the
300
+ // tier itself.
301
+ function speedTierSubsetCost(row, prefix, pricing, ctx) {
302
+ const { bounded, usesOpenAILongContextTier, longInput, longCached, longCacheWrite, longOutput, longReasoning } = ctx;
303
+ const tierInput = bounded(row[`${prefix}input_tokens`], row.input_tokens);
304
+ const tierCached = bounded(row[`${prefix}cached_input_tokens`], row.cached_input_tokens);
305
+ const tierCacheWrite = bounded(
306
+ row[`${prefix}cache_creation_input_tokens`],
307
+ row.cache_creation_input_tokens,
308
+ );
309
+ const tierOutput = bounded(row[`${prefix}output_tokens`], row.output_tokens);
310
+ const tierReasoning = ctx.reasoningIncludedInOutput
311
+ ? 0
312
+ : bounded(row[`${prefix}reasoning_output_tokens`], row.reasoning_output_tokens);
313
+ const tierBase = (
314
+ tierInput * (pricing.input || 0) +
315
+ tierCached * (pricing.cache_read || 0) +
316
+ tierCacheWrite * (pricing.cache_write || 0) +
317
+ tierOutput * (pricing.output || 0) +
318
+ tierReasoning * (pricing.output || 0)
319
+ ) / 1_000_000;
320
+ const tierLong = usesOpenAILongContextTier ? (
321
+ bounded(row[`${prefix}long_context_input_tokens`], Math.min(tierInput, longInput)) *
322
+ (pricing.input || 0) +
323
+ bounded(row[`${prefix}long_context_cached_input_tokens`], Math.min(tierCached, longCached)) *
324
+ (pricing.cache_read || 0) +
325
+ bounded(
326
+ row[`${prefix}long_context_cache_creation_input_tokens`],
327
+ Math.min(tierCacheWrite, longCacheWrite),
328
+ ) * (pricing.cache_write || 0) +
329
+ 0.5 * bounded(row[`${prefix}long_context_output_tokens`], Math.min(tierOutput, longOutput)) *
330
+ (pricing.output || 0) +
331
+ 0.5 * bounded(
332
+ row[`${prefix}long_context_reasoning_output_tokens`],
333
+ Math.min(tierReasoning, longReasoning),
334
+ ) * (pricing.output || 0)
335
+ ) / 1_000_000 : 0;
336
+ return tierBase + tierLong;
337
+ }
338
+
224
339
  // Same formula and Codex/every-code reasoning-folding rule as the previous
225
340
  // computeRowCost in src/lib/local-api.js. Moved here so vite mock + local
226
341
  // server share one source of truth.
@@ -240,18 +355,7 @@ function computeRowCost(row) {
240
355
  reportedCost > 0
241
356
  ) return reportedCost;
242
357
  const pricing = getRowPricing(row);
243
- // OmO, like Codex, reports reasoning as a subset of `output` (its own
244
- // usage.cost bills no separate reasoning component), so charging it again
245
- // here would double-bill every reasoning token. Cline is the same: its
246
- // `outputTokens` is the AI SDK total (text + reasoning), so
247
- // reasoning_output_tokens is a subset marker, never a second line item.
248
- const reasoningIncludedInOutput =
249
- row.source === "codex" ||
250
- row.source === "acode" ||
251
- row.source === "every-code" ||
252
- row.source === "omo" ||
253
- row.source === "cline" ||
254
- row.source === "muse";
358
+ const reasoningIncludedInOutput = reasoningFoldsIntoOutput(row);
255
359
  const reasoningCost = reasoningIncludedInOutput
256
360
  ? 0
257
361
  : (row.reasoning_output_tokens || 0) * (pricing.output || 0);
@@ -264,11 +368,8 @@ function computeRowCost(row) {
264
368
  1_000_000
265
369
  );
266
370
 
267
- const model = String(row?.model || "").toLowerCase();
268
- const usesOpenAILongContextTier =
269
- model === "gpt-5.6" || model.includes("gpt-5.6-sol") || model.includes("gpt-6-astra") ||
270
- model.includes("gpt-6-sol") || model.includes("gpt-6.1-sol") ||
271
- model.includes("gpt-6.1-terra");
371
+ const ctx = rowSubsetContext(row, reasoningIncludedInOutput);
372
+ const { usesOpenAILongContextTier } = ctx;
272
373
  // Only models whose curated entry carries an explicit Fast/priority rate get
273
374
  // the tier premium. The other OpenAI tiers have no published priority price,
274
375
  // and inventing one is worse than reporting Standard.
@@ -278,35 +379,12 @@ function computeRowCost(row) {
278
379
  const hasUltrafastRatesEarly = Number.isFinite(ultrafastMultiplierEarly) && ultrafastMultiplierEarly > 1;
279
380
  if (!usesOpenAILongContextTier && !hasPriorityRates && !hasUltrafastRatesEarly) return baseCost;
280
381
 
281
- const bounded = (value, total) => Math.min(
282
- Math.max(0, Number(value) || 0),
283
- Math.max(0, Number(total) || 0),
284
- );
285
- // Both premium families below read "observed subset" columns: how much of the
286
- // row's own token columns came from requests with a given property. They are
287
- // annotations, never addends — total_tokens stays the sum of the base columns
288
- // whether or not they are present, and a legacy row that omits them prices
289
- // exactly as it did before.
290
- //
291
- // The parser records only usage from requests whose cache-inclusive input
292
- // exceeded 272K. OpenAI prices the whole such request at 2x input and 1.5x
293
- // output, so add the premium over the standard-rate base exactly once.
294
- const longInput = bounded(row.long_context_input_tokens, row.input_tokens);
295
- const longCached = bounded(row.long_context_cached_input_tokens, row.cached_input_tokens);
296
- const longCacheWrite = bounded(
297
- row.long_context_cache_creation_input_tokens,
298
- row.cache_creation_input_tokens,
299
- );
300
- const longOutput = bounded(row.long_context_output_tokens, row.output_tokens);
301
- const longReasoning = reasoningIncludedInOutput
302
- ? 0
303
- : bounded(row.long_context_reasoning_output_tokens, row.reasoning_output_tokens);
304
382
  const longContextPremium = usesOpenAILongContextTier ? (
305
- longInput * (pricing.input || 0) +
306
- longCached * (pricing.cache_read || 0) +
307
- longCacheWrite * (pricing.cache_write || 0) +
308
- 0.5 * longOutput * (pricing.output || 0) +
309
- 0.5 * longReasoning * (pricing.output || 0)
383
+ ctx.longInput * (pricing.input || 0) +
384
+ ctx.longCached * (pricing.cache_read || 0) +
385
+ ctx.longCacheWrite * (pricing.cache_write || 0) +
386
+ 0.5 * ctx.longOutput * (pricing.output || 0) +
387
+ 0.5 * ctx.longReasoning * (pricing.output || 0)
310
388
  ) / 1_000_000 : 0;
311
389
 
312
390
  const ultrafastMultiplier = ultrafastMultiplierEarly;
@@ -317,40 +395,7 @@ function computeRowCost(row) {
317
395
  // are mutually exclusive per request; both can appear in one aggregated row.
318
396
  function tierPremium(multiplier, prefix) {
319
397
  if (!(Number.isFinite(multiplier) && multiplier > 1)) return 0;
320
- const tierInput = bounded(row[`${prefix}input_tokens`], row.input_tokens);
321
- const tierCached = bounded(row[`${prefix}cached_input_tokens`], row.cached_input_tokens);
322
- const tierCacheWrite = bounded(
323
- row[`${prefix}cache_creation_input_tokens`],
324
- row.cache_creation_input_tokens,
325
- );
326
- const tierOutput = bounded(row[`${prefix}output_tokens`], row.output_tokens);
327
- const tierReasoning = reasoningIncludedInOutput
328
- ? 0
329
- : bounded(row[`${prefix}reasoning_output_tokens`], row.reasoning_output_tokens);
330
- const tierBase = (
331
- tierInput * (pricing.input || 0) +
332
- tierCached * (pricing.cache_read || 0) +
333
- tierCacheWrite * (pricing.cache_write || 0) +
334
- tierOutput * (pricing.output || 0) +
335
- tierReasoning * (pricing.output || 0)
336
- ) / 1_000_000;
337
- const tierLong = usesOpenAILongContextTier ? (
338
- bounded(row[`${prefix}long_context_input_tokens`], Math.min(tierInput, longInput)) *
339
- (pricing.input || 0) +
340
- bounded(row[`${prefix}long_context_cached_input_tokens`], Math.min(tierCached, longCached)) *
341
- (pricing.cache_read || 0) +
342
- bounded(
343
- row[`${prefix}long_context_cache_creation_input_tokens`],
344
- Math.min(tierCacheWrite, longCacheWrite),
345
- ) * (pricing.cache_write || 0) +
346
- 0.5 * bounded(row[`${prefix}long_context_output_tokens`], Math.min(tierOutput, longOutput)) *
347
- (pricing.output || 0) +
348
- 0.5 * bounded(
349
- row[`${prefix}long_context_reasoning_output_tokens`],
350
- Math.min(tierReasoning, longReasoning),
351
- ) * (pricing.output || 0)
352
- ) / 1_000_000 : 0;
353
- return (multiplier - 1) * (tierBase + tierLong);
398
+ return (multiplier - 1) * speedTierSubsetCost(row, prefix, pricing, { ...ctx, reasoningIncludedInOutput });
354
399
  }
355
400
 
356
401
  // Astra Fast is a flat multiple of whichever context tier the request was on
@@ -367,6 +412,63 @@ function computeRowCost(row) {
367
412
  return baseCost + longContextPremium + priorityPremium + ultrafastPremium;
368
413
  }
369
414
 
415
+ // Speed tiers computeRowCost can allocate: prefix of the observed subset
416
+ // columns and the pricing multiplier key that prices them.
417
+ const SPEED_TIERS = [
418
+ { prefix: "priority_", multiplierKey: "priority_multiplier" },
419
+ { prefix: "ultrafast_", multiplierKey: "ultrafast_multiplier" },
420
+ ];
421
+
422
+ // Cost of one speed-tier subset: its standard-rate portion plus, when the
423
+ // model's pricing carries the tier multiplier, the same (multiplier - 1)
424
+ // premium computeRowCost adds on top. Zero-cost (local inference, Pi
425
+ // subscription) and authoritative-cost (Grok) rows have no tier allocation —
426
+ // everything stays under standard there.
427
+ const SPEED_TIER_COLUMN_SUFFIXES = [
428
+ "input_tokens",
429
+ "cached_input_tokens",
430
+ "cache_creation_input_tokens",
431
+ "output_tokens",
432
+ "reasoning_output_tokens",
433
+ ];
434
+
435
+ function speedTierCost(row, prefix, multiplierKey) {
436
+ // Skip the pricing lookup entirely for rows without observed columns of
437
+ // this tier — the allocation below is 0 for them anyway, and the
438
+ // model-breakdown endpoint calls this for every row of every source.
439
+ if (!SPEED_TIER_COLUMN_SUFFIXES.some((suffix) => Number(row?.[`${prefix}${suffix}`]) > 0)) return 0;
440
+ if (LOCAL_INFERENCE_SOURCES.has(String(row?.source || "").toLowerCase())) return 0;
441
+ if (PI_SUBSCRIPTION_SOURCES.has(String(row?.source || "").toLowerCase())) return 0;
442
+ const reportedCost = Number(row?.total_cost_usd);
443
+ if (
444
+ SOURCES_WITH_AUTHORITATIVE_COST.has(row?.source) &&
445
+ Number.isFinite(reportedCost) &&
446
+ reportedCost > 0
447
+ ) return 0;
448
+ const pricing = getRowPricing(row);
449
+ const reasoningIncludedInOutput = reasoningFoldsIntoOutput(row);
450
+ const ctx = rowSubsetContext(row, reasoningIncludedInOutput);
451
+ const portion = speedTierSubsetCost(row, prefix, pricing, { ...ctx, reasoningIncludedInOutput });
452
+ const multiplier = Number(pricing[multiplierKey]);
453
+ if (!(Number.isFinite(multiplier) && multiplier > 1)) return portion;
454
+ return portion + (multiplier - 1) * portion;
455
+ }
456
+
457
+ // Exact per-tier allocation of computeRowCost for one usage row. Fast and
458
+ // Ultrafast book their observed subset tokens at the tier's multiplier;
459
+ // everything else — tokens outside the tier subsets, rows without tier
460
+ // columns, provider-reported costs, zero-cost sources — stays under standard.
461
+ // standard is computed as the residual, so the three values always add up to
462
+ // computeRowCost(row) by construction (issue #886: surface the Fast/Ultrafast
463
+ // split that the parser already records).
464
+ function computeRowTierCosts(row) {
465
+ if (!row || typeof row !== "object") return { standard: 0, priority: 0, ultrafast: 0 };
466
+ const priority = speedTierCost(row, SPEED_TIERS[0].prefix, SPEED_TIERS[0].multiplierKey);
467
+ const ultrafast = speedTierCost(row, SPEED_TIERS[1].prefix, SPEED_TIERS[1].multiplierKey);
468
+ const standard = computeRowCost(row) - priority - ultrafast;
469
+ return { standard, priority, ultrafast };
470
+ }
471
+
370
472
  // Backwards-compatible MODEL_PRICING export. Test at
371
473
  // test/model-breakdown.test.js:236 reads `localApi.MODEL_PRICING["kiro-agent"]`
372
474
  // and expects { input, output, cache_read, cache_write } shape. We expose the
@@ -382,6 +484,7 @@ module.exports = {
382
484
  getModelPricingInfo,
383
485
  getRowPricing,
384
486
  computeRowCost,
487
+ computeRowTierCosts,
385
488
  resetPricingForTests,
386
489
  MODEL_PRICING,
387
490
  ZERO_PRICING,