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.
- package/dashboard/dist/assets/{AchievementBadge-DHCKPAgO.js → AchievementBadge-DPlXNqoK.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-BqlGMD7S.js → AchievementsPage-B49wjdK_.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-CTTTR9CN.js → AchievementsSection-DoYMz2Rr.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-e0Iz8ct4.js → BadgeDetailModal-DUCS6K1n.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-BLYZyT1O.js → BotAnimated-C21PdYSf.js} +1 -1
- package/dashboard/dist/assets/{Card-EHkn9ktD.js → Card-BU2CqxfU.js} +1 -1
- package/dashboard/dist/assets/{CommandPalette-CuSVstX9.js → CommandPalette-O3pOLyJo.js} +1 -1
- package/dashboard/dist/assets/CommunityStatsModal-Bt82neQ7.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-pIUQJ7QM.js → ConfirmModal-zUljpLNe.js} +1 -1
- package/dashboard/dist/assets/{Controls-CX4Di57j.js → Controls-D56v-YJw.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-CKEuFzuc.js +1 -0
- package/dashboard/dist/assets/DevicePage-CCIyAYH3.js +1 -0
- package/dashboard/dist/assets/{DialogClose-Dn-nxOSp.js → DialogClose-C-QK97qp.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-COXweCuc.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DuAY9sfZ.js → DialogTitle-6q0jyME6.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-25jNO9JV.js → FadeIn-BUm1z22V.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-0-ne32-S.js → HeaderGithubStar-CxdmloMy.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-C2Qop8B3.js → HoverTooltip-CrMdGYZj.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-Q9lwiFdt.js → IpCheckPage-w6UUK_1K.js} +1 -1
- package/dashboard/dist/assets/LandingPage-DLDE7wdR.js +2 -0
- package/dashboard/dist/assets/{LeaderboardAvatar-CiQrgneF.js → LeaderboardAvatar-BqD4dEvQ.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-kgHSryiT.js +2 -0
- package/dashboard/dist/assets/{LeaderboardProfileModal-CF-MI8rL.js → LeaderboardProfileModal-zLdeOiA3.js} +4 -4
- package/dashboard/dist/assets/{LeaderboardProfilePage-DRbLb-fO.js → LeaderboardProfilePage-Fljho5RP.js} +2 -2
- package/dashboard/dist/assets/{LimitsPage-lvYidBkz.js → LimitsPage-Do_uJqdQ.js} +2 -2
- package/dashboard/dist/assets/{LocalOnlyNotice-CAQTnlaI.js → LocalOnlyNotice-RIj2XT_X.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-Ci2Rrsfl.js → LoginCard-Dx6MmGjU.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-DhyQPl-V.js → LoginModal-BWaZVRnT.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-B6q8P6hE.js → LoginPage-BNkU-riS.js} +1 -1
- package/dashboard/dist/assets/{PetPage-82Cldgg6.js → PetPage-CyEgU2Vm.js} +1 -1
- package/dashboard/dist/assets/PopoverPopup-CmUIY8lF.js +1 -0
- package/dashboard/dist/assets/{ProviderIcon-aLwclDsD.js → ProviderIcon-BSI-1Cy8.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-BlRl_aJr.js → ResetPasswordPage-CUXKkLb4.js} +1 -1
- package/dashboard/dist/assets/Select-B6CM0ico.js +1 -0
- package/dashboard/dist/assets/SelectItemText-CZ7puxuB.js +1 -0
- package/dashboard/dist/assets/{ServiceStatusPage-Pd6dgJua.js → ServiceStatusPage-B4lIs2fc.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-BngTWN7Q.css +1 -0
- package/dashboard/dist/assets/SessionsPage-D99UfBgy.js +3 -0
- package/dashboard/dist/assets/SettingsPage-DT3rld91.js +2 -0
- package/dashboard/dist/assets/SkillsPage-Dv47kVCB.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-BN71jQQ8.js → TokenGalaxy-Bk3kGSGi.js} +2 -2
- package/dashboard/dist/assets/TrendMonitor-mB1x3c_o.js +19 -0
- package/dashboard/dist/assets/WidgetsPage-BhUArowe.js +1 -0
- package/dashboard/dist/assets/{WrappedPage-Cq8DrzLm.js → WrappedPage-DHRmP4Ey.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-BW1rxvYq.js +45 -0
- package/dashboard/dist/assets/__vite-browser-external-gKkiQad5.css +1 -0
- package/dashboard/dist/assets/{agent-logos-X7FQq7rz.js → agent-logos-D7f7AMF-.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-CPxuidzy.js → arrow-up-right-c39fdiDQ.js} +1 -1
- package/dashboard/dist/assets/{check-D2K19ObI.js → check-8LQ7IvkH.js} +1 -1
- package/dashboard/dist/assets/{composite-BN1UKLj0.js → composite-BgLSXWKA.js} +1 -1
- package/dashboard/dist/assets/{download-CpW-TB2h.js → download-x3Gmv-wu.js} +1 -1
- package/dashboard/dist/assets/{external-link-ChBchK6G.js → external-link-daw-XTjZ.js} +1 -1
- package/dashboard/dist/assets/{flame-f7119ZMd.js → flame-B9o0_EF8.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-COrRnk53.js → format-tokens-o9atQGtV.js} +1 -1
- package/dashboard/dist/assets/host-mode-GPY2DxE4.js +1 -0
- package/dashboard/dist/assets/{icons-Dn9vA9Qu.js → icons-CmM2z-Jt.js} +1 -1
- package/dashboard/dist/assets/{index-BCNOKYSO.js → index-Bfgblk6o.js} +1 -1
- package/dashboard/dist/assets/{index-CA-94DFW.js → index-CPcJP0uH.js} +1 -1
- package/dashboard/dist/assets/{index-CElyhNzh.js → index-CtQOew7C.js} +1 -1
- package/dashboard/dist/assets/{info-BHsPBczD.js → info-BaK8Hoi2.js} +1 -1
- package/dashboard/dist/assets/{link-2-BHfEWv22.js → link-2-CV3uvMH3.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-DTIT1Uk1.js → loader-circle-DYpa-xTZ.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-CPBla3OK.js → modifiers.esm-Ds4Uh7JO.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-CbsYB-S2.js → monitor-smartphone-BbeYHvY-.js} +1 -1
- package/dashboard/dist/assets/{plus-CZiNoMsy.js → plus-C9qGVLFv.js} +1 -1
- package/dashboard/dist/assets/provider-display-BWYNyitK.js +42 -0
- package/dashboard/dist/assets/{react-CZdcuJ28.js → react-CO1HovF5.js} +1 -1
- package/dashboard/dist/assets/{search-q2ARMGca.js → search-DBnhkzG_.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CIyqyk-w.js → skills-api-BSkbxC8-.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-BztIEZTk.js → sortable.esm-SVl6OixH.js} +1 -1
- package/dashboard/dist/assets/{store-DvdXwk-7.js → store-DrEEdO_u.js} +1 -1
- package/dashboard/dist/assets/terminal-kUMdPZhq.js +1 -0
- package/dashboard/dist/assets/{trash-D0ptaWPk.js → trash-Dd_vbrYo.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DYibzhAl.js → use-community-stats-Bmlmv4H-.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-viIhH7H5.js → use-latest-request-guard-CqZwzrCW.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-CbXApWU1.js → use-limits-display-prefs-CDC0MZRY.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-BBP8hr8y.js → use-ordered-list-DvBI7mUU.js} +1 -1
- package/dashboard/dist/assets/{use-pet-settings-CIrPZ9pB.js → use-pet-settings-C_FrqySO.js} +2 -2
- package/dashboard/dist/assets/use-reduced-motion-BEOENqBa.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-2Vb3tJrw.js → use-session-efficiency-pref-D8cAb4A9.js} +1 -1
- package/dashboard/dist/assets/{use-transform-CECRFELz.js → use-transform-uSKYTYaW.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-DEnbjOVG.js → use-usage-limits-DFgOKTtv.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-Dh9pUzDO.js → useCurrency-t1iy_pC3.js} +1 -1
- package/dashboard/dist/assets/useTokenFormat-0bPjtaaM.js +1 -0
- package/dashboard/dist/assets/{zap-BdeAxq64.js → zap-DFM6AsYo.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/legal.css +1 -0
- package/dashboard/dist/pricing.html +35 -0
- package/dashboard/dist/privacy.html +278 -0
- package/dashboard/dist/share.html +2 -2
- package/dashboard/dist/terms.html +41 -0
- package/package.json +1 -1
- package/src/lib/codex-rollout-parser.js +5 -0
- package/src/lib/local-api.js +5 -1
- package/src/lib/pricing/index.js +45 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +1 -0
- package/src/lib/session-analytics.js +74 -18
- package/src/lib/session-performance.js +214 -0
- package/dashboard/dist/assets/CommunityStatsModal-Cb1vefLE.js +0 -1
- package/dashboard/dist/assets/DashboardPage-D1Ox1y3E.js +0 -1
- package/dashboard/dist/assets/DevicePage-Q57gBE8A.js +0 -1
- package/dashboard/dist/assets/DialogDescription-GXslTq2z.js +0 -1
- package/dashboard/dist/assets/LandingPage-iX6E8Flr.js +0 -2
- package/dashboard/dist/assets/LeaderboardPage-BIhIA28A.js +0 -2
- package/dashboard/dist/assets/PopoverPopup-Btcu7vU0.js +0 -1
- package/dashboard/dist/assets/SegmentedControl-2O_XVL8u.js +0 -1
- package/dashboard/dist/assets/Select-D5uuRQwh.js +0 -1
- package/dashboard/dist/assets/SelectItemText-Bq_ZdWU0.js +0 -1
- package/dashboard/dist/assets/SessionsPage-DjHudcn6.js +0 -2
- package/dashboard/dist/assets/SettingsPage-DOLhZiBE.js +0 -2
- package/dashboard/dist/assets/SkillsPage-D9-ZorB5.js +0 -1
- package/dashboard/dist/assets/ToolbarRootContext-B0cqyD1i.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-DWxtbzyQ.js +0 -19
- package/dashboard/dist/assets/WidgetsPage-BwmJ2Z9d.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-CPMSmJgP.js +0 -45
- package/dashboard/dist/assets/__vite-browser-external-DrXtuPP9.css +0 -1
- package/dashboard/dist/assets/copy-CSN17qFv.js +0 -1
- package/dashboard/dist/assets/host-mode-Bc_iTzHk.js +0 -1
- package/dashboard/dist/assets/maximize-2-BmtIBzLu.js +0 -1
- package/dashboard/dist/assets/provider-display-CXKRt9q1.js +0 -42
- package/dashboard/dist/assets/terminal-CFQOW2Ey.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-Dpq-cKyM.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-CoGcVhPP.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
243
|
-
|
|
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.
|
|
253
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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};
|