usage-board 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/index.mjs +1 -1
  2. package/dist/public/_nuxt/37OOe3RF.js +1 -0
  3. package/dist/public/_nuxt/65Ayv2XK.js +146 -0
  4. package/dist/public/_nuxt/BOWwkrCY.js +25 -0
  5. package/dist/public/_nuxt/C0GhHHgI.js +3 -0
  6. package/dist/public/_nuxt/{uHQwCIHg.js → D7qEPtpx.js} +1 -1
  7. package/dist/public/_nuxt/D9-Yw1TR.js +1 -0
  8. package/dist/public/_nuxt/DF2WsXH3.js +1 -0
  9. package/dist/public/_nuxt/DKaPq50Z.js +6 -0
  10. package/dist/public/_nuxt/{COIbUy5w.js → DXWxIyGU.js} +1 -1
  11. package/dist/public/_nuxt/De8DvPWL.js +1 -0
  12. package/dist/public/_nuxt/DgMMKsPE.js +258 -0
  13. package/dist/public/_nuxt/DxvuOJRP.js +1 -0
  14. package/dist/public/_nuxt/Jp5cgQZi.js +4 -0
  15. package/dist/public/_nuxt/builds/latest.json +1 -1
  16. package/dist/public/_nuxt/builds/meta/7ce9c611-6071-4cbd-8926-1e53d9ef21bd.json +1 -0
  17. package/dist/public/_nuxt/entry.DnkKc-6G.css +1 -0
  18. package/dist/public/_nuxt/qXgLTL_3.js +21 -0
  19. package/dist/public/_nuxt/y6mAKUDU.js +1 -0
  20. package/dist/server/chunks/build/client.precomputed.mjs +1 -1
  21. package/dist/server/chunks/nitro/nitro.mjs +2119 -1107
  22. package/dist/server/chunks/routes/api/analysis/agent/session.json.mjs +1 -1
  23. package/dist/server/chunks/routes/api/analysis/agent/token.json.mjs +1 -1
  24. package/dist/server/chunks/routes/api/analysis/cache.json.mjs +1 -1
  25. package/dist/server/chunks/routes/api/analysis/hot-project.json.mjs +1 -1
  26. package/dist/server/chunks/routes/api/analysis/model.json.mjs +1 -1
  27. package/dist/server/chunks/routes/api/analysis/overview-cards.json.mjs +4 -4
  28. package/dist/server/chunks/routes/api/analysis/session.json.mjs +1 -1
  29. package/dist/server/chunks/routes/api/analysis/token/daily.json.mjs +1 -1
  30. package/dist/server/chunks/routes/api/analysis/token/today-hourly.json.mjs +21 -0
  31. package/dist/server/chunks/routes/api/analysis/token.json.mjs +1 -1
  32. package/dist/server/chunks/routes/api/payload.json.mjs +2 -2
  33. package/dist/server/chunks/routes/api/projects/_project/modules.get.mjs +2 -2
  34. package/dist/server/chunks/routes/api/projects/catalog.get.mjs +2 -2
  35. package/dist/server/chunks/routes/renderer.mjs +2 -2
  36. package/dist/server/chunks/routes/ws.mjs +2 -2
  37. package/dist/server/index.mjs +2 -2
  38. package/dist/server/package.json +1 -1
  39. package/package.json +2 -2
  40. package/dist/public/_nuxt/B6C9KBQ4.js +0 -105
  41. package/dist/public/_nuxt/Bv6agYS5.js +0 -119
  42. package/dist/public/_nuxt/BvRyOET7.js +0 -1
  43. package/dist/public/_nuxt/CMWftE4h.js +0 -1
  44. package/dist/public/_nuxt/CXLmM1yO.js +0 -25
  45. package/dist/public/_nuxt/DFqWEFN4.js +0 -1
  46. package/dist/public/_nuxt/DUoLvn3A.js +0 -6
  47. package/dist/public/_nuxt/D_W11Quh.js +0 -21
  48. package/dist/public/_nuxt/DgKrPjze.js +0 -4
  49. package/dist/public/_nuxt/DkxY2YMp.js +0 -1
  50. package/dist/public/_nuxt/DtbPvE6R.js +0 -260
  51. package/dist/public/_nuxt/HiqUua3-.js +0 -1
  52. package/dist/public/_nuxt/builds/meta/ae3c6372-8821-43fb-aa55-bb47729a5660.json +0 -1
  53. package/dist/public/_nuxt/entry.vHfFzkyD.css +0 -1
@@ -1,12 +1,12 @@
1
- import process from 'node:process';globalThis._importMeta_=globalThis._importMeta_||{url:"file:///_entry.js",env:process.env};import { formatNumber } from '@lonewolfyx/utils';
2
- import http from 'node:http';
1
+ import process from 'node:process';globalThis._importMeta_=globalThis._importMeta_||{url:"file:///_entry.js",env:process.env};import http from 'node:http';
3
2
  import https from 'node:https';
4
3
  import { EventEmitter } from 'node:events';
5
4
  import { Buffer as Buffer$1 } from 'node:buffer';
6
- import { promises, existsSync, mkdirSync, readFileSync, statSync, accessSync, constants } from 'node:fs';
5
+ import { promises, existsSync, mkdirSync, readFileSync, statSync, readdirSync, accessSync, constants } from 'node:fs';
7
6
  import { resolve as resolve$1, dirname as dirname$1, join, sep, basename as basename$1 } from 'node:path';
8
7
  import { createHash } from 'node:crypto';
9
8
  import { DatabaseSync } from 'node:sqlite';
9
+ import { formatNumber } from '@lonewolfyx/utils';
10
10
  import { glob } from 'glob';
11
11
  import chokidar from 'chokidar';
12
12
  import { homedir } from 'node:os';
@@ -4163,9 +4163,13 @@ const inlineAppConfig = {
4163
4163
  "wi",
4164
4164
  "wpf",
4165
4165
  "zmdi",
4166
- "zondicons"
4166
+ "zondicons",
4167
+ "ai"
4167
4168
  ],
4168
- "fetchTimeout": 1500
4169
+ "fetchTimeout": 1500,
4170
+ "customCollections": [
4171
+ "ai"
4172
+ ]
4169
4173
  }
4170
4174
  };
4171
4175
 
@@ -4267,7 +4271,7 @@ function _expandFromEnv(value) {
4267
4271
  const _inlineRuntimeConfig = {
4268
4272
  "app": {
4269
4273
  "baseURL": "/",
4270
- "buildId": "ae3c6372-8821-43fb-aa55-bb47729a5660",
4274
+ "buildId": "7ce9c611-6071-4cbd-8926-1e53d9ef21bd",
4271
4275
  "buildAssetsDir": "/_nuxt/",
4272
4276
  "cdnURL": ""
4273
4277
  },
@@ -4295,7 +4299,7 @@ const _inlineRuntimeConfig = {
4295
4299
  }
4296
4300
  },
4297
4301
  "public": {
4298
- "appVersion": "3.2.0",
4302
+ "appVersion": "4.0.0",
4299
4303
  "home": "/Users/tangchenghui"
4300
4304
  },
4301
4305
  "icon": {
@@ -4710,7 +4714,17 @@ function defineNitroPlugin(def) {
4710
4714
  }
4711
4715
 
4712
4716
  function openSqliteDatabase(location, options) {
4713
- return new DatabaseSync(location, options != null ? options : {});
4717
+ const database = new DatabaseSync(location, options != null ? options : {});
4718
+ try {
4719
+ database.exec("PRAGMA journal_mode = WAL");
4720
+ database.exec("PRAGMA synchronous = NORMAL");
4721
+ } catch {
4722
+ }
4723
+ try {
4724
+ database.exec("PRAGMA busy_timeout = 15000");
4725
+ } catch {
4726
+ }
4727
+ return database;
4714
4728
  }
4715
4729
 
4716
4730
  const PROJECT_USAGE_PLATFORMS = [
@@ -4780,10 +4794,296 @@ function normalizeProjectUsageDetail(detail) {
4780
4794
  };
4781
4795
  }
4782
4796
 
4797
+ const compactNumberFormatter = new Intl.NumberFormat("en-US", {
4798
+ notation: "compact",
4799
+ maximumFractionDigits: 1
4800
+ });
4801
+ const currencyFormatter = new Intl.NumberFormat("en-US", {
4802
+ style: "currency",
4803
+ currency: "USD",
4804
+ minimumFractionDigits: 2,
4805
+ maximumFractionDigits: 2
4806
+ });
4807
+ const percentFormatter = new Intl.NumberFormat("en-US", {
4808
+ style: "percent",
4809
+ minimumFractionDigits: 1,
4810
+ maximumFractionDigits: 1
4811
+ });
4812
+ const dateKeyFormatter = new Intl.DateTimeFormat("en-CA", {
4813
+ day: "2-digit",
4814
+ month: "2-digit",
4815
+ year: "numeric"
4816
+ });
4817
+ const dateLabelFormatter = new Intl.DateTimeFormat("en-US", {
4818
+ day: "2-digit",
4819
+ month: "short",
4820
+ timeZone: "UTC",
4821
+ year: "numeric"
4822
+ });
4823
+ function normalizeNumber(value) {
4824
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
4825
+ }
4826
+ function roundCurrency(value) {
4827
+ return Math.round(value * 1e6) / 1e6;
4828
+ }
4829
+ function uniqueItems(items) {
4830
+ return Array.from(new Set(items));
4831
+ }
4832
+ function formatCompactNumber(value) {
4833
+ return compactNumberFormatter.format(normalizeNumber(value));
4834
+ }
4835
+ function formatCurrency(value) {
4836
+ return currencyFormatter.format(normalizeNumber(value));
4837
+ }
4838
+ function formatPercent(value) {
4839
+ return percentFormatter.format(normalizeNumber(value));
4840
+ }
4841
+ function buildPercentTrend(currentValue, previousValue) {
4842
+ const current = normalizeNumber(currentValue);
4843
+ const previous = normalizeNumber(previousValue);
4844
+ if (previous === 0) {
4845
+ return {
4846
+ label: current > 0 ? "+100%" : "0%",
4847
+ tone: current > 0 ? "up" : "neutral"
4848
+ };
4849
+ }
4850
+ const ratio = (current - previous) / previous;
4851
+ return {
4852
+ label: formatSignedPercent(ratio),
4853
+ tone: getTrendTone(ratio)
4854
+ };
4855
+ }
4856
+ function buildGrowthTrend(currentValue, previousValue, formatValue) {
4857
+ const current = Math.max(normalizeNumber(currentValue), 0);
4858
+ const previous = Math.max(normalizeNumber(previousValue), 0);
4859
+ if (previous === 0) {
4860
+ if (current === 0) {
4861
+ return {
4862
+ trend: "0.0%",
4863
+ trendTone: "neutral"
4864
+ };
4865
+ }
4866
+ return {
4867
+ trend: `+${formatValue(current)}`,
4868
+ trendTone: "up"
4869
+ };
4870
+ }
4871
+ const ratio = (current - previous) / previous;
4872
+ return {
4873
+ trend: formatSignedPercent(ratio),
4874
+ trendTone: getTrendTone(ratio)
4875
+ };
4876
+ }
4877
+ function buildInputOutputTokenSubvalue(usage) {
4878
+ var _a, _b;
4879
+ return {
4880
+ items: [
4881
+ {
4882
+ label: "In",
4883
+ value: formatCompactNumber((_a = usage == null ? void 0 : usage.inputTokens) != null ? _a : 0)
4884
+ },
4885
+ {
4886
+ label: "Out",
4887
+ value: formatCompactNumber((_b = usage == null ? void 0 : usage.outputTokens) != null ? _b : 0)
4888
+ }
4889
+ ]
4890
+ };
4891
+ }
4892
+ function buildOverviewCardsWithTodayTokenBreakdown(overviewCards, dailyTokenUsage) {
4893
+ const todayDateKey = getDateKey(/* @__PURE__ */ new Date());
4894
+ return overviewCards.map((card) => card.name === "Today Tokens" ? {
4895
+ ...card,
4896
+ subvalue: buildInputOutputTokenSubvalue(
4897
+ dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === todayDateKey)
4898
+ )
4899
+ } : card);
4900
+ }
4901
+ function getDateKey(date) {
4902
+ var _a, _b, _c, _d, _e, _f;
4903
+ const parts = dateKeyFormatter.formatToParts(date);
4904
+ const year = (_b = (_a = parts.find((part) => part.type === "year")) == null ? void 0 : _a.value) != null ? _b : "0000";
4905
+ const month = (_d = (_c = parts.find((part) => part.type === "month")) == null ? void 0 : _c.value) != null ? _d : "01";
4906
+ const day = (_f = (_e = parts.find((part) => part.type === "day")) == null ? void 0 : _e.value) != null ? _f : "01";
4907
+ return `${year}-${month}-${day}`;
4908
+ }
4909
+ function getDateKeyFromLabel(label) {
4910
+ const date = new Date(label);
4911
+ if (Number.isNaN(date.getTime())) {
4912
+ return label;
4913
+ }
4914
+ return getDateKey(date);
4915
+ }
4916
+ function getPreviousDateKey(dateKey) {
4917
+ const [year, month, day] = dateKey.split("-").map((value) => Number.parseInt(value, 10));
4918
+ const date = new Date(year || 0, (month || 1) - 1, day || 1);
4919
+ date.setDate(date.getDate() - 1);
4920
+ return getDateKey(date);
4921
+ }
4922
+ function formatDateLabelFromDateKey(dateKey, fallback = dateKey) {
4923
+ const [year, month, day] = dateKey.split("-").map((value) => Number.parseInt(value, 10));
4924
+ if (!year || !month || !day) {
4925
+ return fallback;
4926
+ }
4927
+ return dateLabelFormatter.format(new Date(Date.UTC(year, month - 1, day)));
4928
+ }
4929
+ function buildProjectUsage(sessionUsage) {
4930
+ var _a;
4931
+ const projects = /* @__PURE__ */ new Map();
4932
+ for (const session of sessionUsage) {
4933
+ const project = (_a = projects.get(session.project)) != null ? _a : {
4934
+ costUSD: 0,
4935
+ repository: session.repository,
4936
+ sessions: 0,
4937
+ tokenTotal: 0
4938
+ };
4939
+ project.costUSD += session.costUSD;
4940
+ project.sessions += 1;
4941
+ project.tokenTotal += session.tokenTotal;
4942
+ projects.set(session.project, project);
4943
+ }
4944
+ const maxCost = Math.max(...Array.from(projects.values()).map((project) => project.costUSD), 0);
4945
+ return Array.from(projects.entries()).map(([label, project]) => ({
4946
+ costUSD: project.costUSD,
4947
+ detail: `${project.sessions} sessions / ${formatCompactNumber(project.tokenTotal)} tokens`,
4948
+ label,
4949
+ percent: maxCost > 0 ? project.costUSD / maxCost * 100 : 0,
4950
+ repository: project.repository,
4951
+ sessions: project.sessions,
4952
+ tokenTotal: project.tokenTotal,
4953
+ tone: "amber",
4954
+ value: formatCurrency(project.costUSD)
4955
+ })).sort((a, b) => b.costUSD - a.costUSD);
4956
+ }
4957
+ function mergeDailyTokenUsage(items) {
4958
+ var _a, _b, _c, _d, _e;
4959
+ const groups = /* @__PURE__ */ new Map();
4960
+ for (const item of items) {
4961
+ const dateKey = getDateKeyFromLabel(item.date);
4962
+ const group = (_a = groups.get(dateKey)) != null ? _a : {
4963
+ cachedInputTokens: 0,
4964
+ costUSD: 0,
4965
+ date: formatDateLabelFromDateKey(dateKey, item.date),
4966
+ inputTokens: 0,
4967
+ models: /* @__PURE__ */ new Map(),
4968
+ outputTokens: 0,
4969
+ platforms: /* @__PURE__ */ new Map(),
4970
+ reasoningOutputTokens: 0,
4971
+ totalTokens: 0
4972
+ };
4973
+ group.cachedInputTokens += item.cachedInputTokens;
4974
+ group.costUSD += item.costUSD;
4975
+ group.inputTokens += item.inputTokens;
4976
+ group.outputTokens += item.outputTokens;
4977
+ group.reasoningOutputTokens += item.reasoningOutputTokens;
4978
+ group.totalTokens += item.totalTokens;
4979
+ for (const [modelName, usage] of Object.entries(item.models)) {
4980
+ const model = (_b = group.models.get(modelName)) != null ? _b : createEmptyModelUsage();
4981
+ model.cachedInputTokens += usage.cachedInputTokens;
4982
+ model.costUSD += usage.costUSD;
4983
+ model.inputTokens += usage.inputTokens;
4984
+ model.isFallback = model.isFallback || usage.isFallback;
4985
+ model.outputTokens += usage.outputTokens;
4986
+ model.reasoningOutputTokens += usage.reasoningOutputTokens;
4987
+ model.totalTokens += usage.totalTokens;
4988
+ group.models.set(modelName, model);
4989
+ }
4990
+ for (const [platform, platformUsage] of Object.entries((_c = item.platforms) != null ? _c : {})) {
4991
+ const currentPlatformUsage = (_d = group.platforms.get(platform)) != null ? _d : createEmptyDailyPlatformTokenUsage();
4992
+ currentPlatformUsage.cachedInputTokens += platformUsage.cachedInputTokens;
4993
+ currentPlatformUsage.costUSD += platformUsage.costUSD;
4994
+ currentPlatformUsage.inputTokens += platformUsage.inputTokens;
4995
+ currentPlatformUsage.outputTokens += platformUsage.outputTokens;
4996
+ currentPlatformUsage.reasoningOutputTokens += platformUsage.reasoningOutputTokens;
4997
+ currentPlatformUsage.totalTokens += platformUsage.totalTokens;
4998
+ for (const [modelName, usage] of Object.entries(platformUsage.models)) {
4999
+ const model = (_e = currentPlatformUsage.models[modelName]) != null ? _e : createEmptyModelUsage();
5000
+ model.cachedInputTokens += usage.cachedInputTokens;
5001
+ model.costUSD += usage.costUSD;
5002
+ model.inputTokens += usage.inputTokens;
5003
+ model.isFallback = model.isFallback || usage.isFallback;
5004
+ model.outputTokens += usage.outputTokens;
5005
+ model.reasoningOutputTokens += usage.reasoningOutputTokens;
5006
+ model.totalTokens += usage.totalTokens;
5007
+ currentPlatformUsage.models[modelName] = model;
5008
+ }
5009
+ group.platforms.set(platform, currentPlatformUsage);
5010
+ }
5011
+ groups.set(dateKey, group);
5012
+ }
5013
+ return Array.from(groups.entries()).sort((a, b) => b[0].localeCompare(a[0])).map(([, group]) => ({
5014
+ cachedInputTokens: group.cachedInputTokens,
5015
+ costUSD: roundCurrency(group.costUSD),
5016
+ date: group.date,
5017
+ inputTokens: group.inputTokens,
5018
+ models: Object.fromEntries(group.models.entries()),
5019
+ outputTokens: group.outputTokens,
5020
+ platforms: group.platforms.size > 0 ? Object.fromEntries(Array.from(group.platforms.entries()).map(([platform, usage]) => [platform, {
5021
+ ...usage,
5022
+ costUSD: roundCurrency(usage.costUSD),
5023
+ models: Object.fromEntries(Object.entries(usage.models).map(([modelName, modelUsage]) => [modelName, {
5024
+ ...modelUsage,
5025
+ costUSD: roundCurrency(modelUsage.costUSD)
5026
+ }]))
5027
+ }])) : void 0,
5028
+ reasoningOutputTokens: group.reasoningOutputTokens,
5029
+ totalTokens: group.totalTokens
5030
+ }));
5031
+ }
5032
+ function mergeMonthlyModelUsage(items) {
5033
+ var _a;
5034
+ const groups = /* @__PURE__ */ new Map();
5035
+ for (const item of items) {
5036
+ const key = `${item.month}__${item.model}`;
5037
+ const group = (_a = groups.get(key)) != null ? _a : {
5038
+ model: item.model,
5039
+ month: item.month,
5040
+ tokenTotal: 0
5041
+ };
5042
+ group.tokenTotal += item.tokenTotal;
5043
+ groups.set(key, group);
5044
+ }
5045
+ return Array.from(groups.values()).sort((a, b) => a.month.localeCompare(b.month) || a.model.localeCompare(b.model));
5046
+ }
5047
+ function formatSignedPercent(value) {
5048
+ const prefix = value > 0 ? "+" : "";
5049
+ return `${prefix}${formatPercent(value)}`;
5050
+ }
5051
+ function getTrendTone(value) {
5052
+ if (value > 0) {
5053
+ return "up";
5054
+ }
5055
+ if (value < 0) {
5056
+ return "down";
5057
+ }
5058
+ return "neutral";
5059
+ }
5060
+ function createEmptyModelUsage() {
5061
+ return {
5062
+ cachedInputTokens: 0,
5063
+ costUSD: 0,
5064
+ inputTokens: 0,
5065
+ isFallback: false,
5066
+ outputTokens: 0,
5067
+ reasoningOutputTokens: 0,
5068
+ totalTokens: 0
5069
+ };
5070
+ }
5071
+ function createEmptyDailyPlatformTokenUsage() {
5072
+ return {
5073
+ cachedInputTokens: 0,
5074
+ costUSD: 0,
5075
+ inputTokens: 0,
5076
+ models: {},
5077
+ outputTokens: 0,
5078
+ reasoningOutputTokens: 0,
5079
+ totalTokens: 0
5080
+ };
5081
+ }
5082
+
4783
5083
  var __defProp$1 = Object.defineProperty;
4784
5084
  var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4785
5085
  var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, key + "" , value);
4786
- const CACHE_SCHEMA_VERSION = 2;
5086
+ const CACHE_SCHEMA_VERSION = 9;
4787
5087
  const ROW_KEY_SEPARATOR = "";
4788
5088
  const CACHE_SCHEMA_SQL = `
4789
5089
  CREATE TABLE IF NOT EXISTS cache_schema_meta (
@@ -4800,7 +5100,8 @@ const CACHE_SCHEMA_SQL = `
4800
5100
 
4801
5101
  CREATE TABLE IF NOT EXISTS project_catalog_entries (
4802
5102
  label TEXT PRIMARY KEY,
4803
- type TEXT NOT NULL,
5103
+ platforms_json TEXT NOT NULL,
5104
+ total_tokens INTEGER NOT NULL,
4804
5105
  updated_at TEXT NOT NULL
4805
5106
  );
4806
5107
 
@@ -4845,6 +5146,7 @@ const CACHE_SCHEMA_SQL = `
4845
5146
  name TEXT NOT NULL,
4846
5147
  value TEXT NOT NULL,
4847
5148
  detail TEXT,
5149
+ subvalue_json TEXT,
4848
5150
  trend TEXT NOT NULL,
4849
5151
  trend_tone TEXT NOT NULL,
4850
5152
  PRIMARY KEY (scope_key, position),
@@ -4923,6 +5225,7 @@ const CACHE_SCHEMA_SQL = `
4923
5225
  output_tokens INTEGER NOT NULL,
4924
5226
  reasoning_output_tokens INTEGER NOT NULL,
4925
5227
  total_tokens INTEGER NOT NULL,
5228
+ cost_usd REAL NOT NULL,
4926
5229
  is_fallback INTEGER NOT NULL,
4927
5230
  PRIMARY KEY (scope_key, date, model),
4928
5231
  FOREIGN KEY (scope_key, date)
@@ -4991,6 +5294,8 @@ const CACHE_SCHEMA_SQL = `
4991
5294
 
4992
5295
  CREATE INDEX IF NOT EXISTS idx_usage_scope_sessions_order
4993
5296
  ON usage_scope_sessions(scope_key, session_order);
5297
+ CREATE INDEX IF NOT EXISTS idx_usage_scope_sessions_started_at
5298
+ ON usage_scope_sessions(scope_key, started_at);
4994
5299
 
4995
5300
  CREATE TABLE IF NOT EXISTS usage_scope_session_models (
4996
5301
  scope_key TEXT NOT NULL,
@@ -5018,6 +5323,7 @@ const CACHE_SCHEMA_SQL = `
5018
5323
  cached_input_tokens INTEGER,
5019
5324
  output_tokens INTEGER,
5020
5325
  reasoning_output_tokens INTEGER,
5326
+ extra_total_tokens INTEGER,
5021
5327
  total_tokens INTEGER,
5022
5328
  usage_cost_usd REAL,
5023
5329
  cache_creation_tokens INTEGER,
@@ -5029,10 +5335,15 @@ const CACHE_SCHEMA_SQL = `
5029
5335
  REFERENCES usage_scope_sessions(scope_key, session_key)
5030
5336
  ON DELETE CASCADE
5031
5337
  );
5338
+ CREATE INDEX IF NOT EXISTS idx_usage_scope_interactions_role_timestamp
5339
+ ON usage_scope_interactions(scope_key, role, timestamp);
5340
+ CREATE INDEX IF NOT EXISTS idx_usage_scope_interactions_timestamp
5341
+ ON usage_scope_interactions(scope_key, timestamp);
5032
5342
 
5033
5343
  CREATE TABLE IF NOT EXISTS indexed_files (
5034
5344
  path TEXT PRIMARY KEY,
5035
5345
  platform TEXT NOT NULL,
5346
+ cache_signature TEXT NOT NULL DEFAULT '',
5036
5347
  size INTEGER NOT NULL,
5037
5348
  mtime_ms INTEGER NOT NULL,
5038
5349
  updated_at TEXT NOT NULL
@@ -5079,6 +5390,7 @@ const CACHE_SCHEMA_SQL = `
5079
5390
  cached_input_tokens INTEGER,
5080
5391
  output_tokens INTEGER,
5081
5392
  reasoning_output_tokens INTEGER,
5393
+ extra_total_tokens INTEGER,
5082
5394
  total_tokens INTEGER,
5083
5395
  usage_cost_usd REAL,
5084
5396
  cache_creation_tokens INTEGER,
@@ -5103,7 +5415,7 @@ class UsageCacheRepository {
5103
5415
  if (!meta) {
5104
5416
  return null;
5105
5417
  }
5106
- const scopes = this.loadHydratedUsageScopes("bootstrap");
5418
+ const scopes = this.loadHydratedUsageScopes("bootstrap", false);
5107
5419
  const payload = Object.fromEntries(
5108
5420
  PROJECT_USAGE_PLATFORMS.map((platform) => {
5109
5421
  var _a2;
@@ -5125,20 +5437,76 @@ class UsageCacheRepository {
5125
5437
  saveBootstrap(payload) {
5126
5438
  this.persistBootstrap(payload);
5127
5439
  }
5440
+ loadHomeDashboardTodayInsights() {
5441
+ const { previousDayStartAt, todayStartAt, tomorrowStartAt } = getHomeInsightRange();
5442
+ const promptCount = groupTodayInsightCounts(this.database.prepare(`
5443
+ SELECT
5444
+ CASE
5445
+ WHEN interaction.timestamp >= ? AND interaction.timestamp < ? THEN 'today'
5446
+ ELSE 'previous'
5447
+ END AS period,
5448
+ COUNT(*) AS total
5449
+ FROM usage_scope_interactions AS interaction
5450
+ JOIN usage_scopes AS scope ON scope.scope_key = interaction.scope_key
5451
+ WHERE scope.scope_kind = 'bootstrap'
5452
+ AND interaction.role = 'user'
5453
+ AND interaction.timestamp IS NOT NULL
5454
+ AND interaction.timestamp >= ?
5455
+ AND interaction.timestamp < ?
5456
+ GROUP BY period
5457
+ `).all(todayStartAt, tomorrowStartAt, previousDayStartAt, tomorrowStartAt));
5458
+ const sessionCount = groupTodayInsightCounts(this.database.prepare(`
5459
+ SELECT
5460
+ CASE
5461
+ WHEN session.started_at >= ? AND session.started_at < ? THEN 'today'
5462
+ ELSE 'previous'
5463
+ END AS period,
5464
+ COUNT(*) AS total
5465
+ FROM usage_scope_sessions AS session
5466
+ JOIN usage_scopes AS scope ON scope.scope_key = session.scope_key
5467
+ WHERE scope.scope_kind = 'bootstrap'
5468
+ AND session.started_at >= ?
5469
+ AND session.started_at < ?
5470
+ GROUP BY period
5471
+ `).all(todayStartAt, tomorrowStartAt, previousDayStartAt, tomorrowStartAt));
5472
+ return {
5473
+ previousPromptCount: promptCount.previous,
5474
+ previousSessionCount: sessionCount.previous,
5475
+ promptCount: promptCount.today,
5476
+ sessionCount: sessionCount.today,
5477
+ todayHourlyUsage: buildTodayHourlyUsage(this.database.prepare(`
5478
+ SELECT
5479
+ scope.platform AS platform,
5480
+ CAST(strftime('%H', interaction.timestamp, 'localtime') AS INTEGER) AS hour,
5481
+ ROUND(SUM(COALESCE(interaction.usage_cost_usd, 0)), 6) AS cost_usd,
5482
+ SUM(COALESCE(interaction.total_tokens, 0)) AS total_tokens
5483
+ FROM usage_scope_interactions AS interaction
5484
+ JOIN usage_scopes AS scope ON scope.scope_key = interaction.scope_key
5485
+ WHERE scope.scope_kind = 'bootstrap'
5486
+ AND interaction.timestamp IS NOT NULL
5487
+ AND interaction.total_tokens IS NOT NULL
5488
+ AND interaction.timestamp >= ?
5489
+ AND interaction.timestamp < ?
5490
+ GROUP BY scope.platform, hour
5491
+ ORDER BY hour ASC, scope.platform ASC
5492
+ `).all(todayStartAt, tomorrowStartAt))
5493
+ };
5494
+ }
5128
5495
  loadProjectCatalog() {
5129
5496
  const meta = this.getCacheState("project_catalog");
5130
5497
  if (!meta) {
5131
5498
  return null;
5132
5499
  }
5133
5500
  const rows = this.database.prepare(`
5134
- SELECT label, type
5501
+ SELECT label, platforms_json, total_tokens
5135
5502
  FROM project_catalog_entries
5136
5503
  ORDER BY label ASC
5137
5504
  `).all();
5138
5505
  return {
5139
5506
  payload: rows.map((row) => ({
5140
5507
  label: row.label,
5141
- type: row.type
5508
+ platforms: parseProjectCatalogPlatforms(row.platforms_json),
5509
+ totalTokens: row.total_tokens
5142
5510
  })),
5143
5511
  payloadHash: meta.payload_hash,
5144
5512
  updatedAt: meta.updated_at
@@ -5191,7 +5559,7 @@ class UsageCacheRepository {
5191
5559
  loadIndexedSourceFiles() {
5192
5560
  var _a, _b, _c;
5193
5561
  const files = this.database.prepare(`
5194
- SELECT path, platform, size, mtime_ms, updated_at
5562
+ SELECT path, platform, cache_signature, size, mtime_ms, updated_at
5195
5563
  FROM indexed_files
5196
5564
  ORDER BY path ASC
5197
5565
  `).all();
@@ -5234,6 +5602,7 @@ class UsageCacheRepository {
5234
5602
  cached_input_tokens,
5235
5603
  output_tokens,
5236
5604
  reasoning_output_tokens,
5605
+ extra_total_tokens,
5237
5606
  total_tokens,
5238
5607
  usage_cost_usd,
5239
5608
  cache_creation_tokens,
@@ -5262,6 +5631,7 @@ class UsageCacheRepository {
5262
5631
  return files.map((file) => {
5263
5632
  var _a2, _b2;
5264
5633
  return {
5634
+ cacheSignature: file.cache_signature,
5265
5635
  mtimeMs: file.mtime_ms,
5266
5636
  path: file.path,
5267
5637
  payload: (_a2 = fragmentsByPath.get(file.path)) != null ? _a2 : [],
@@ -5273,14 +5643,14 @@ class UsageCacheRepository {
5273
5643
  });
5274
5644
  }
5275
5645
  upsertIndexedSourceFiles(files) {
5276
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
5646
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
5277
5647
  if (files.length === 0) {
5278
5648
  return;
5279
5649
  }
5280
5650
  const deleteFileStatement = this.database.prepare("DELETE FROM indexed_files WHERE path = ?");
5281
5651
  const insertFileStatement = this.database.prepare(`
5282
- INSERT INTO indexed_files (path, platform, size, mtime_ms, updated_at)
5283
- VALUES (?, ?, ?, ?, ?)
5652
+ INSERT INTO indexed_files (path, platform, cache_signature, size, mtime_ms, updated_at)
5653
+ VALUES (?, ?, ?, ?, ?, ?)
5284
5654
  `);
5285
5655
  const insertProjectStatement = this.database.prepare(`
5286
5656
  INSERT INTO indexed_file_projects (path, project_name, project_order)
@@ -5316,6 +5686,7 @@ class UsageCacheRepository {
5316
5686
  cached_input_tokens,
5317
5687
  output_tokens,
5318
5688
  reasoning_output_tokens,
5689
+ extra_total_tokens,
5319
5690
  total_tokens,
5320
5691
  usage_cost_usd,
5321
5692
  cache_creation_tokens,
@@ -5323,13 +5694,13 @@ class UsageCacheRepository {
5323
5694
  tool_tokens,
5324
5695
  is_fallback_model
5325
5696
  )
5326
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5697
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5327
5698
  `);
5328
5699
  this.database.exec("BEGIN");
5329
5700
  try {
5330
5701
  for (const file of files) {
5331
5702
  deleteFileStatement.run(file.path);
5332
- insertFileStatement.run(file.path, file.platform, file.size, file.mtimeMs, file.updatedAt);
5703
+ insertFileStatement.run(file.path, file.platform, file.cacheSignature, file.size, file.mtimeMs, file.updatedAt);
5333
5704
  for (const [projectOrder, projectName] of file.projectNames.entries()) {
5334
5705
  insertProjectStatement.run(file.path, projectName, projectOrder);
5335
5706
  }
@@ -5362,12 +5733,13 @@ class UsageCacheRepository {
5362
5733
  (_e = (_d = interaction.usage) == null ? void 0 : _d.cachedInputTokens) != null ? _e : null,
5363
5734
  (_g = (_f = interaction.usage) == null ? void 0 : _f.outputTokens) != null ? _g : null,
5364
5735
  (_i = (_h = interaction.usage) == null ? void 0 : _h.reasoningOutputTokens) != null ? _i : null,
5365
- (_k = (_j = interaction.usage) == null ? void 0 : _j.totalTokens) != null ? _k : null,
5366
- (_m = (_l = interaction.usage) == null ? void 0 : _l.costUSD) != null ? _m : null,
5367
- (_o = (_n = interaction.usage) == null ? void 0 : _n.cacheCreationTokens) != null ? _o : null,
5368
- (_q = (_p = interaction.usage) == null ? void 0 : _p.cacheReadTokens) != null ? _q : null,
5369
- (_s = (_r = interaction.usage) == null ? void 0 : _r.toolTokens) != null ? _s : null,
5370
- ((_t = interaction.usage) == null ? void 0 : _t.isFallbackModel) ? 1 : 0
5736
+ (_k = (_j = interaction.usage) == null ? void 0 : _j.extraTotalTokens) != null ? _k : null,
5737
+ (_m = (_l = interaction.usage) == null ? void 0 : _l.totalTokens) != null ? _m : null,
5738
+ (_o = (_n = interaction.usage) == null ? void 0 : _n.costUSD) != null ? _o : null,
5739
+ (_q = (_p = interaction.usage) == null ? void 0 : _p.cacheCreationTokens) != null ? _q : null,
5740
+ (_s = (_r = interaction.usage) == null ? void 0 : _r.cacheReadTokens) != null ? _s : null,
5741
+ (_u = (_t = interaction.usage) == null ? void 0 : _t.toolTokens) != null ? _u : null,
5742
+ ((_v = interaction.usage) == null ? void 0 : _v.isFallbackModel) ? 1 : 0
5371
5743
  );
5372
5744
  }
5373
5745
  }
@@ -5439,7 +5811,13 @@ class UsageCacheRepository {
5439
5811
  }
5440
5812
  initializeSchema() {
5441
5813
  this.database.exec(CACHE_SCHEMA_SQL);
5442
- if (this.getCurrentSchemaVersion() >= CACHE_SCHEMA_VERSION) {
5814
+ this.ensureIndexedFilesCacheSignatureColumn();
5815
+ this.ensureDailyUsageModelCostColumn();
5816
+ this.ensureOverviewCardSubvalueColumn();
5817
+ this.ensureProjectCatalogColumns();
5818
+ this.ensureInteractionExtraTotalTokenColumns();
5819
+ const currentSchemaVersion = this.getCurrentSchemaVersion();
5820
+ if (currentSchemaVersion >= CACHE_SCHEMA_VERSION) {
5443
5821
  return;
5444
5822
  }
5445
5823
  this.migrateLegacyDataIfNeeded();
@@ -5462,13 +5840,13 @@ class UsageCacheRepository {
5462
5840
  `).run(version);
5463
5841
  }
5464
5842
  migrateLegacyDataIfNeeded() {
5465
- const hasLegacySnapshots = this.hasTable("cache_snapshots");
5466
- const hasLegacyProjectSnapshots = this.hasTable("project_snapshots");
5467
- const hasLegacyIndexedFiles = this.hasTable("indexed_source_files");
5468
- if (!hasLegacySnapshots && !hasLegacyProjectSnapshots && !hasLegacyIndexedFiles) {
5843
+ if (!this.hasLegacyData()) {
5469
5844
  return;
5470
5845
  }
5471
5846
  this.clearNormalizedTables();
5847
+ const hasLegacySnapshots = this.hasTable("cache_snapshots");
5848
+ const hasLegacyProjectSnapshots = this.hasTable("project_snapshots");
5849
+ const hasLegacyIndexedFiles = this.hasTable("indexed_source_files");
5472
5850
  if (hasLegacySnapshots) {
5473
5851
  const bootstrap = this.loadLegacyBootstrap();
5474
5852
  const projectCatalog = this.loadLegacyProjectCatalog();
@@ -5494,6 +5872,81 @@ class UsageCacheRepository {
5494
5872
  }
5495
5873
  this.dropLegacyTables();
5496
5874
  }
5875
+ hasLegacyData() {
5876
+ return this.hasTable("cache_snapshots") || this.hasTable("project_snapshots") || this.hasTable("indexed_source_files");
5877
+ }
5878
+ ensureIndexedFilesCacheSignatureColumn() {
5879
+ if (!this.hasTable("indexed_files")) {
5880
+ return;
5881
+ }
5882
+ const columns = this.database.prepare("PRAGMA table_info(indexed_files)").all();
5883
+ if (columns.some((column) => column.name === "cache_signature")) {
5884
+ return;
5885
+ }
5886
+ this.database.exec("ALTER TABLE indexed_files ADD COLUMN cache_signature TEXT NOT NULL DEFAULT ''");
5887
+ }
5888
+ ensureProjectCatalogColumns() {
5889
+ if (!this.hasTable("project_catalog_entries")) {
5890
+ return;
5891
+ }
5892
+ const columns = this.database.prepare("PRAGMA table_info(project_catalog_entries)").all();
5893
+ if (!columns.some((column) => column.name === "platforms_json")) {
5894
+ this.database.exec("ALTER TABLE project_catalog_entries ADD COLUMN platforms_json TEXT NOT NULL DEFAULT '[]'");
5895
+ }
5896
+ if (!columns.some((column) => column.name === "total_tokens")) {
5897
+ this.database.exec("ALTER TABLE project_catalog_entries ADD COLUMN total_tokens INTEGER NOT NULL DEFAULT 0");
5898
+ }
5899
+ if (!columns.some((column) => column.name === "type")) {
5900
+ return;
5901
+ }
5902
+ const legacyRows = this.database.prepare(`
5903
+ SELECT label, type
5904
+ FROM project_catalog_entries
5905
+ `).all();
5906
+ const updateStatement = this.database.prepare(`
5907
+ UPDATE project_catalog_entries
5908
+ SET platforms_json = ?, total_tokens = 0
5909
+ WHERE label = ?
5910
+ `);
5911
+ for (const row of legacyRows) {
5912
+ const platforms = row.type === "mixed" ? [] : [row.type];
5913
+ updateStatement.run(JSON.stringify(platforms), row.label);
5914
+ }
5915
+ }
5916
+ ensureDailyUsageModelCostColumn() {
5917
+ if (!this.hasTable("usage_scope_daily_usage_models")) {
5918
+ return;
5919
+ }
5920
+ const columns = this.database.prepare("PRAGMA table_info(usage_scope_daily_usage_models)").all();
5921
+ if (columns.some((column) => column.name === "cost_usd")) {
5922
+ return;
5923
+ }
5924
+ this.database.exec("ALTER TABLE usage_scope_daily_usage_models ADD COLUMN cost_usd REAL NOT NULL DEFAULT 0");
5925
+ }
5926
+ ensureOverviewCardSubvalueColumn() {
5927
+ if (!this.hasTable("usage_scope_overview_cards")) {
5928
+ return;
5929
+ }
5930
+ const columns = this.database.prepare("PRAGMA table_info(usage_scope_overview_cards)").all();
5931
+ if (columns.some((column) => column.name === "subvalue_json")) {
5932
+ return;
5933
+ }
5934
+ this.database.exec("ALTER TABLE usage_scope_overview_cards ADD COLUMN subvalue_json TEXT");
5935
+ }
5936
+ ensureInteractionExtraTotalTokenColumns() {
5937
+ if (this.hasTable("usage_scope_interactions")) {
5938
+ const usageScopeInteractionColumns = this.database.prepare("PRAGMA table_info(usage_scope_interactions)").all();
5939
+ if (!usageScopeInteractionColumns.some((column) => column.name === "extra_total_tokens")) {
5940
+ this.database.exec("ALTER TABLE usage_scope_interactions ADD COLUMN extra_total_tokens INTEGER");
5941
+ }
5942
+ }
5943
+ if (this.hasTable("indexed_fragment_interactions")) {
5944
+ const indexedInteractionColumns = this.database.prepare("PRAGMA table_info(indexed_fragment_interactions)").all();
5945
+ if (!indexedInteractionColumns.some((column) => column.name === "extra_total_tokens")) {
5946
+ this.database.exec("ALTER TABLE indexed_fragment_interactions ADD COLUMN extra_total_tokens INTEGER");
5947
+ }
5948
+ }
5949
+ }
5497
5950
  clearNormalizedTables() {
5498
5951
  this.database.exec("BEGIN");
5499
5952
  try {
@@ -5559,14 +6012,14 @@ class UsageCacheRepository {
5559
6012
  const payloadHash = (_b = options.payloadHash) != null ? _b : createPayloadHash(JSON.stringify(payload));
5560
6013
  const deleteStatement = this.database.prepare("DELETE FROM project_catalog_entries");
5561
6014
  const insertStatement = this.database.prepare(`
5562
- INSERT INTO project_catalog_entries (label, type, updated_at)
5563
- VALUES (?, ?, ?)
6015
+ INSERT INTO project_catalog_entries (label, platforms_json, total_tokens, updated_at)
6016
+ VALUES (?, ?, ?, ?)
5564
6017
  `);
5565
6018
  this.database.exec("BEGIN");
5566
6019
  try {
5567
6020
  deleteStatement.run();
5568
6021
  for (const item of payload) {
5569
- insertStatement.run(item.label, item.type, updatedAt);
6022
+ insertStatement.run(item.label, JSON.stringify(item.platforms), item.totalTokens, updatedAt);
5570
6023
  }
5571
6024
  this.upsertCacheState("project_catalog", payloadHash, updatedAt);
5572
6025
  this.database.exec("COMMIT");
@@ -5586,7 +6039,7 @@ class UsageCacheRepository {
5586
6039
  `).run(key, payloadHash, updatedAt, version != null ? version : null);
5587
6040
  }
5588
6041
  insertUsageScope(options) {
5589
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D;
6042
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F;
5590
6043
  const scopeKey = createUsageScopeKey(options.kind, options.platform, options.projectLabel);
5591
6044
  const payloadHash = createPayloadHash(JSON.stringify(options.usage));
5592
6045
  const insertScopeStatement = this.database.prepare(`
@@ -5614,10 +6067,11 @@ class UsageCacheRepository {
5614
6067
  name,
5615
6068
  value,
5616
6069
  detail,
6070
+ subvalue_json,
5617
6071
  trend,
5618
6072
  trend_tone
5619
6073
  )
5620
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
6074
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5621
6075
  `);
5622
6076
  const insertTokenRowStatement = this.database.prepare(`
5623
6077
  INSERT INTO usage_scope_token_rows (
@@ -5682,9 +6136,10 @@ class UsageCacheRepository {
5682
6136
  output_tokens,
5683
6137
  reasoning_output_tokens,
5684
6138
  total_tokens,
6139
+ cost_usd,
5685
6140
  is_fallback
5686
6141
  )
5687
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6142
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5688
6143
  `);
5689
6144
  const insertMonthlyModelStatement = this.database.prepare(`
5690
6145
  INSERT INTO usage_scope_monthly_model_usage (
@@ -5764,6 +6219,7 @@ class UsageCacheRepository {
5764
6219
  cached_input_tokens,
5765
6220
  output_tokens,
5766
6221
  reasoning_output_tokens,
6222
+ extra_total_tokens,
5767
6223
  total_tokens,
5768
6224
  usage_cost_usd,
5769
6225
  cache_creation_tokens,
@@ -5771,7 +6227,7 @@ class UsageCacheRepository {
5771
6227
  tool_tokens,
5772
6228
  is_fallback_model
5773
6229
  )
5774
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6230
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5775
6231
  `);
5776
6232
  insertScopeStatement.run(
5777
6233
  scopeKey,
@@ -5795,6 +6251,7 @@ class UsageCacheRepository {
5795
6251
  card.name,
5796
6252
  card.value,
5797
6253
  (_j = card.detail) != null ? _j : null,
6254
+ card.subvalue ? JSON.stringify(card.subvalue) : null,
5798
6255
  card.trend,
5799
6256
  card.trendTone
5800
6257
  );
@@ -5848,6 +6305,7 @@ class UsageCacheRepository {
5848
6305
  usage.outputTokens,
5849
6306
  usage.reasoningOutputTokens,
5850
6307
  usage.totalTokens,
6308
+ usage.costUSD,
5851
6309
  usage.isFallback ? 1 : 0
5852
6310
  );
5853
6311
  }
@@ -5920,17 +6378,18 @@ class UsageCacheRepository {
5920
6378
  (_o = (_n = interaction.usage) == null ? void 0 : _n.cachedInputTokens) != null ? _o : null,
5921
6379
  (_q = (_p = interaction.usage) == null ? void 0 : _p.outputTokens) != null ? _q : null,
5922
6380
  (_s = (_r = interaction.usage) == null ? void 0 : _r.reasoningOutputTokens) != null ? _s : null,
5923
- (_u = (_t = interaction.usage) == null ? void 0 : _t.totalTokens) != null ? _u : null,
5924
- (_w = (_v = interaction.usage) == null ? void 0 : _v.costUSD) != null ? _w : null,
5925
- (_y = (_x = interaction.usage) == null ? void 0 : _x.cacheCreationTokens) != null ? _y : null,
5926
- (_A = (_z = interaction.usage) == null ? void 0 : _z.cacheReadTokens) != null ? _A : null,
5927
- (_C = (_B = interaction.usage) == null ? void 0 : _B.toolTokens) != null ? _C : null,
5928
- ((_D = interaction.usage) == null ? void 0 : _D.isFallbackModel) ? 1 : 0
6381
+ (_u = (_t = interaction.usage) == null ? void 0 : _t.extraTotalTokens) != null ? _u : null,
6382
+ (_w = (_v = interaction.usage) == null ? void 0 : _v.totalTokens) != null ? _w : null,
6383
+ (_y = (_x = interaction.usage) == null ? void 0 : _x.costUSD) != null ? _y : null,
6384
+ (_A = (_z = interaction.usage) == null ? void 0 : _z.cacheCreationTokens) != null ? _A : null,
6385
+ (_C = (_B = interaction.usage) == null ? void 0 : _B.cacheReadTokens) != null ? _C : null,
6386
+ (_E = (_D = interaction.usage) == null ? void 0 : _D.toolTokens) != null ? _E : null,
6387
+ ((_F = interaction.usage) == null ? void 0 : _F.isFallbackModel) ? 1 : 0
5929
6388
  );
5930
6389
  }
5931
6390
  }
5932
6391
  }
5933
- loadHydratedUsageScopes(kind) {
6392
+ loadHydratedUsageScopes(kind, includeInteractions = true) {
5934
6393
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
5935
6394
  const scopes = this.database.prepare(`
5936
6395
  SELECT
@@ -5955,7 +6414,7 @@ class UsageCacheRepository {
5955
6414
  }
5956
6415
  const scopeSet = new Set(scopes.map((scope) => scope.scope_key));
5957
6416
  const overviewCards = groupOverviewCards(this.database.prepare(`
5958
- SELECT card.scope_key, card.position, card.icon, card.name, card.value, card.detail, card.trend, card.trend_tone
6417
+ SELECT card.scope_key, card.position, card.icon, card.name, card.value, card.detail, card.subvalue_json, card.trend, card.trend_tone
5959
6418
  FROM usage_scope_overview_cards AS card
5960
6419
  JOIN usage_scopes AS scope ON scope.scope_key = card.scope_key
5961
6420
  WHERE scope.scope_kind = ?
@@ -6025,6 +6484,7 @@ class UsageCacheRepository {
6025
6484
  model.output_tokens,
6026
6485
  model.reasoning_output_tokens,
6027
6486
  model.total_tokens,
6487
+ model.cost_usd,
6028
6488
  model.is_fallback
6029
6489
  FROM usage_scope_daily_usage_models AS model
6030
6490
  JOIN usage_scopes AS scope ON scope.scope_key = model.scope_key
@@ -6094,33 +6554,34 @@ class UsageCacheRepository {
6094
6554
  WHERE scope.scope_kind = ?
6095
6555
  ORDER BY model.scope_key ASC, model.session_key ASC, model.model_order ASC
6096
6556
  `).all(kind),
6097
- this.database.prepare(`
6098
- SELECT
6099
- interaction.scope_key,
6100
- interaction.session_key,
6101
- interaction.interaction_order,
6102
- interaction.interaction_index,
6103
- interaction.content,
6104
- interaction.cost_usd,
6105
- interaction.model,
6106
- interaction.role,
6107
- interaction.timestamp,
6108
- interaction.type,
6109
- interaction.input_tokens,
6110
- interaction.cached_input_tokens,
6111
- interaction.output_tokens,
6112
- interaction.reasoning_output_tokens,
6113
- interaction.total_tokens,
6114
- interaction.usage_cost_usd,
6115
- interaction.cache_creation_tokens,
6116
- interaction.cache_read_tokens,
6117
- interaction.tool_tokens,
6118
- interaction.is_fallback_model
6119
- FROM usage_scope_interactions AS interaction
6120
- JOIN usage_scopes AS scope ON scope.scope_key = interaction.scope_key
6121
- WHERE scope.scope_kind = ?
6122
- ORDER BY interaction.scope_key ASC, interaction.session_key ASC, interaction.interaction_order ASC
6123
- `).all(kind)
6557
+ includeInteractions ? this.database.prepare(`
6558
+ SELECT
6559
+ interaction.scope_key,
6560
+ interaction.session_key,
6561
+ interaction.interaction_order,
6562
+ interaction.interaction_index,
6563
+ interaction.content,
6564
+ interaction.cost_usd,
6565
+ interaction.model,
6566
+ interaction.role,
6567
+ interaction.timestamp,
6568
+ interaction.type,
6569
+ interaction.input_tokens,
6570
+ interaction.cached_input_tokens,
6571
+ interaction.output_tokens,
6572
+ interaction.reasoning_output_tokens,
6573
+ interaction.extra_total_tokens,
6574
+ interaction.total_tokens,
6575
+ interaction.usage_cost_usd,
6576
+ interaction.cache_creation_tokens,
6577
+ interaction.cache_read_tokens,
6578
+ interaction.tool_tokens,
6579
+ interaction.is_fallback_model
6580
+ FROM usage_scope_interactions AS interaction
6581
+ JOIN usage_scopes AS scope ON scope.scope_key = interaction.scope_key
6582
+ WHERE scope.scope_kind = ?
6583
+ ORDER BY interaction.scope_key ASC, interaction.session_key ASC, interaction.interaction_order ASC
6584
+ `).all(kind) : []
6124
6585
  );
6125
6586
  const hydrated = /* @__PURE__ */ new Map();
6126
6587
  for (const scope of scopes) {
@@ -6178,7 +6639,7 @@ class UsageCacheRepository {
6178
6639
  return null;
6179
6640
  }
6180
6641
  return {
6181
- payload: JSON.parse(row.payload),
6642
+ payload: normalizeProjectCatalogItems(JSON.parse(row.payload)),
6182
6643
  payloadHash: row.payload_hash,
6183
6644
  updatedAt: row.updated_at
6184
6645
  };
@@ -6202,6 +6663,7 @@ class UsageCacheRepository {
6202
6663
  ORDER BY path ASC
6203
6664
  `).all();
6204
6665
  return rows.map((row) => ({
6666
+ cacheSignature: "",
6205
6667
  mtimeMs: row.mtime_ms,
6206
6668
  path: row.path,
6207
6669
  payload: JSON.parse(row.payload),
@@ -6224,6 +6686,54 @@ function getTokenRowsByBucket(usage, bucket) {
6224
6686
  return usage.weeklyRows;
6225
6687
  }
6226
6688
  }
6689
+ function buildTodayHourlyUsage(rows) {
6690
+ var _a;
6691
+ const grouped = /* @__PURE__ */ new Map();
6692
+ for (const row of rows) {
6693
+ const item = (_a = grouped.get(row.hour)) != null ? _a : {
6694
+ agents: {},
6695
+ costUSD: 0,
6696
+ hour: row.hour,
6697
+ label: `${String(row.hour).padStart(2, "0")}:00`,
6698
+ totalTokens: 0
6699
+ };
6700
+ item.agents[row.platform] = {
6701
+ costUSD: row.cost_usd,
6702
+ totalTokens: row.total_tokens
6703
+ };
6704
+ item.costUSD = roundCurrency(item.costUSD + row.cost_usd);
6705
+ item.totalTokens += row.total_tokens;
6706
+ grouped.set(row.hour, item);
6707
+ }
6708
+ return Array.from({ length: 24 }, (_, hour) => {
6709
+ var _a2;
6710
+ return (_a2 = grouped.get(hour)) != null ? _a2 : {
6711
+ agents: {},
6712
+ costUSD: 0,
6713
+ hour,
6714
+ label: `${String(hour).padStart(2, "0")}:00`,
6715
+ totalTokens: 0
6716
+ };
6717
+ });
6718
+ }
6719
+ function groupTodayInsightCounts(rows) {
6720
+ return rows.reduce((result, row) => {
6721
+ result[row.period] = row.total;
6722
+ return result;
6723
+ }, {
6724
+ previous: 0,
6725
+ today: 0
6726
+ });
6727
+ }
6728
+ function getHomeInsightRange() {
6729
+ const today = /* @__PURE__ */ new Date();
6730
+ today.setHours(0, 0, 0, 0);
6731
+ return {
6732
+ previousDayStartAt: new Date(today.getTime() - 24 * 60 * 60 * 1e3).toISOString(),
6733
+ todayStartAt: today.toISOString(),
6734
+ tomorrowStartAt: new Date(today.getTime() + 24 * 60 * 60 * 1e3).toISOString()
6735
+ };
6736
+ }
6227
6737
  function groupProjectModels(rows) {
6228
6738
  var _a;
6229
6739
  const grouped = /* @__PURE__ */ new Map();
@@ -6273,6 +6783,7 @@ function groupOverviewCards(rows) {
6273
6783
  detail: (_b = row.detail) != null ? _b : void 0,
6274
6784
  icon: row.icon,
6275
6785
  name: row.name,
6786
+ subvalue: parseOverviewCardSubvalue(row.subvalue_json),
6276
6787
  trend: row.trend,
6277
6788
  trendTone: row.trend_tone,
6278
6789
  value: row.value
@@ -6281,6 +6792,17 @@ function groupOverviewCards(rows) {
6281
6792
  }
6282
6793
  return grouped;
6283
6794
  }
6795
+ function parseOverviewCardSubvalue(value) {
6796
+ if (!value) {
6797
+ return void 0;
6798
+ }
6799
+ try {
6800
+ const subvalue = JSON.parse(value);
6801
+ return Array.isArray(subvalue == null ? void 0 : subvalue.items) ? subvalue : void 0;
6802
+ } catch {
6803
+ return void 0;
6804
+ }
6805
+ }
6284
6806
  function groupTokenRows(rows, modelRows, projectRows) {
6285
6807
  var _a, _b, _c, _d, _e;
6286
6808
  const modelsByRow = /* @__PURE__ */ new Map();
@@ -6332,6 +6854,7 @@ function groupDailyUsage(rows, modelRows) {
6332
6854
  const models = (_a = modelsByRow.get(key)) != null ? _a : {};
6333
6855
  models[row.model] = {
6334
6856
  cachedInputTokens: row.cached_input_tokens,
6857
+ costUSD: row.cost_usd,
6335
6858
  inputTokens: row.input_tokens,
6336
6859
  isFallback: Boolean(row.is_fallback),
6337
6860
  outputTokens: row.output_tokens,
@@ -6461,6 +6984,9 @@ function buildInteractionUsage(row) {
6461
6984
  reasoningOutputTokens: row.reasoning_output_tokens,
6462
6985
  totalTokens: row.total_tokens
6463
6986
  };
6987
+ if (row.extra_total_tokens !== null) {
6988
+ usage.extraTotalTokens = row.extra_total_tokens;
6989
+ }
6464
6990
  if (row.cache_creation_tokens !== null) {
6465
6991
  usage.cacheCreationTokens = row.cache_creation_tokens;
6466
6992
  }
@@ -6514,6 +7040,53 @@ function createCompositeKey(...parts) {
6514
7040
  function createPayloadHash(value) {
6515
7041
  return createHash("sha1").update(value).digest("hex");
6516
7042
  }
7043
+ function normalizeProjectCatalogItems(value) {
7044
+ if (!Array.isArray(value)) {
7045
+ return [];
7046
+ }
7047
+ return value.flatMap((item) => {
7048
+ if (!item || typeof item !== "object") {
7049
+ return [];
7050
+ }
7051
+ const record = item;
7052
+ if (typeof record.label !== "string") {
7053
+ return [];
7054
+ }
7055
+ if (Array.isArray(record.platforms)) {
7056
+ return [{
7057
+ label: record.label,
7058
+ platforms: normalizeProjectCatalogPlatforms(record.platforms),
7059
+ totalTokens: normalizeProjectCatalogTotalTokens(record.totalTokens)
7060
+ }];
7061
+ }
7062
+ if (typeof record.type === "string") {
7063
+ return [{
7064
+ label: record.label,
7065
+ platforms: normalizeProjectCatalogPlatforms(record.type === "mixed" ? [] : [record.type]),
7066
+ totalTokens: normalizeProjectCatalogTotalTokens(record.totalTokens)
7067
+ }];
7068
+ }
7069
+ return [];
7070
+ });
7071
+ }
7072
+ function parseProjectCatalogPlatforms(value) {
7073
+ try {
7074
+ return normalizeProjectCatalogPlatforms(JSON.parse(value));
7075
+ } catch {
7076
+ return [];
7077
+ }
7078
+ }
7079
+ function normalizeProjectCatalogPlatforms(value) {
7080
+ if (!Array.isArray(value)) {
7081
+ return [];
7082
+ }
7083
+ return value.filter(
7084
+ (platform) => typeof platform === "string" && PROJECT_USAGE_PLATFORMS.includes(platform)
7085
+ );
7086
+ }
7087
+ function normalizeProjectCatalogTotalTokens(value) {
7088
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
7089
+ }
6517
7090
  function mkdirParentDirectory(filePath) {
6518
7091
  const directory = dirname$1(filePath);
6519
7092
  if (!existsSync(directory)) {
@@ -6521,234 +7094,389 @@ function mkdirParentDirectory(filePath) {
6521
7094
  }
6522
7095
  }
6523
7096
 
6524
- function normalizeStringValue(value) {
6525
- return typeof value === "string" ? value.trim() : void 0;
6526
- }
6527
- function normalizeStringList(value) {
6528
- if (Array.isArray(value)) {
6529
- return value.flatMap((item) => typeof item === "string" ? splitCommaValues(item) : []);
7097
+ const MILLION = 1e6;
7098
+ const DEFAULT_PRICING_CACHE_TTL_MS = 1e3 * 60 * 5;
7099
+ const DEFAULT_LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
7100
+ const FAST_MULTIPLIER_EXACT_OVERRIDES = {
7101
+ "gpt-5.3-codex": 2,
7102
+ "gpt-5.4": 2,
7103
+ "gpt-5.5": 2.5
7104
+ };
7105
+ const FAST_MULTIPLIER_PREFIX_OVERRIDES = {
7106
+ "claude-opus-4-6": 6,
7107
+ "claude-opus-4-7": 6
7108
+ };
7109
+ const DEFAULT_FALLBACK_PRICING_TABLE = {
7110
+ "gpt-5": {
7111
+ cachedInputCostPerMTokens: 0.125,
7112
+ cacheCreationInputCostPerMTokens: 1.25,
7113
+ inputCostPerMTokens: 1.25,
7114
+ outputCostPerMTokens: 10
7115
+ },
7116
+ "gpt-5.2-codex": {
7117
+ cachedInputCostPerMTokens: 0.175,
7118
+ cacheCreationInputCostPerMTokens: 1.75,
7119
+ inputCostPerMTokens: 1.75,
7120
+ outputCostPerMTokens: 14
7121
+ },
7122
+ "gpt-5.4": {
7123
+ cachedInputCostPerMTokens: 0.25,
7124
+ cacheCreationInputCostPerMTokens: 2.5,
7125
+ inputCostPerMTokens: 2.5,
7126
+ outputCostPerMTokens: 15
7127
+ },
7128
+ "gpt-5.5": {
7129
+ cachedInputCostPerMTokens: 0.5,
7130
+ cacheCreationInputCostPerMTokens: 5,
7131
+ fastMultiplier: FAST_MULTIPLIER_EXACT_OVERRIDES["gpt-5.5"],
7132
+ inputCostPerMTokens: 5,
7133
+ outputCostPerMTokens: 30
7134
+ },
7135
+ "claude-haiku-4-5": {
7136
+ cachedInputCostPerMTokens: 0.1,
7137
+ cacheCreationInputCostPerMTokens: 1.25,
7138
+ inputCostPerMTokens: 1,
7139
+ outputCostPerMTokens: 5
7140
+ },
7141
+ "claude-opus-4-1": {
7142
+ cachedInputCostPerMTokens: 1.5,
7143
+ cachedInputCostPerMTokensAbove200K: 3,
7144
+ cacheCreationInputCostPerMTokens: 18.75,
7145
+ cacheCreationInputCostPerMTokensAbove200K: 37.5,
7146
+ inputCostPerMTokens: 15,
7147
+ inputCostPerMTokensAbove200K: 30,
7148
+ outputCostPerMTokens: 75,
7149
+ outputCostPerMTokensAbove200K: 112.5
7150
+ },
7151
+ "claude-opus-4-6": {
7152
+ cachedInputCostPerMTokens: 0.5,
7153
+ cacheCreationInputCostPerMTokens: 6.25,
7154
+ fastMultiplier: FAST_MULTIPLIER_PREFIX_OVERRIDES["claude-opus-4-6"],
7155
+ inputCostPerMTokens: 5,
7156
+ outputCostPerMTokens: 25
7157
+ },
7158
+ "claude-opus-4-7": {
7159
+ cachedInputCostPerMTokens: 0.5,
7160
+ cacheCreationInputCostPerMTokens: 6.25,
7161
+ fastMultiplier: FAST_MULTIPLIER_PREFIX_OVERRIDES["claude-opus-4-7"],
7162
+ inputCostPerMTokens: 5,
7163
+ outputCostPerMTokens: 25
7164
+ },
7165
+ "claude-sonnet-4-5": {
7166
+ cachedInputCostPerMTokens: 0.3,
7167
+ cachedInputCostPerMTokensAbove200K: 0.6,
7168
+ cacheCreationInputCostPerMTokens: 3.75,
7169
+ cacheCreationInputCostPerMTokensAbove200K: 7.5,
7170
+ inputCostPerMTokens: 3,
7171
+ inputCostPerMTokensAbove200K: 6,
7172
+ outputCostPerMTokens: 15,
7173
+ outputCostPerMTokensAbove200K: 22.5
6530
7174
  }
6531
- if (typeof value === "string") {
6532
- return splitCommaValues(value);
7175
+ };
7176
+ const pricingCache = /* @__PURE__ */ new Map();
7177
+ async function fetchLiteLLMPricingDataset(options = {}) {
7178
+ var _a, _b, _c, _d;
7179
+ const url = (_a = options.url) != null ? _a : DEFAULT_LITELLM_PRICING_URL;
7180
+ const cacheTtlMs = (_b = options.cacheTtlMs) != null ? _b : DEFAULT_PRICING_CACHE_TTL_MS;
7181
+ const now = Date.now();
7182
+ const cacheEntry = pricingCache.get(url);
7183
+ if (!options.forceRefresh && (cacheEntry == null ? void 0 : cacheEntry.value) && now - cacheEntry.fetchedAt < cacheTtlMs) {
7184
+ return cacheEntry.value;
6533
7185
  }
6534
- return void 0;
6535
- }
6536
- function normalizeUnknownRecord(value) {
6537
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
7186
+ if (!options.forceRefresh && (cacheEntry == null ? void 0 : cacheEntry.promise)) {
7187
+ return cacheEntry.promise;
7188
+ }
7189
+ const fetcher = (_c = options.fetcher) != null ? _c : globalThis.fetch;
7190
+ if (typeof fetcher !== "function") {
7191
+ return createFallbackLiteLLMPricingDataset();
7192
+ }
7193
+ const promise = fetcher(url).then(async (response) => {
7194
+ if (!response.ok) {
7195
+ throw new Error(`Failed to fetch LiteLLM pricing dataset: ${response.status} ${response.statusText}`);
7196
+ }
7197
+ const data = await response.json();
7198
+ if (!isLiteLLMPricingDataset(data)) {
7199
+ throw new Error("Invalid LiteLLM pricing dataset payload.");
7200
+ }
7201
+ const dataset = {
7202
+ ...createFallbackLiteLLMPricingDataset(),
7203
+ ...data
7204
+ };
7205
+ pricingCache.set(url, {
7206
+ fetchedAt: Date.now(),
7207
+ value: dataset
7208
+ });
7209
+ return dataset;
7210
+ }).catch(() => {
7211
+ const fallback = createFallbackLiteLLMPricingDataset();
7212
+ pricingCache.set(url, {
7213
+ fetchedAt: Date.now(),
7214
+ value: fallback
7215
+ });
7216
+ return fallback;
7217
+ });
7218
+ pricingCache.set(url, {
7219
+ fetchedAt: (_d = cacheEntry == null ? void 0 : cacheEntry.fetchedAt) != null ? _d : 0,
7220
+ promise,
7221
+ value: cacheEntry == null ? void 0 : cacheEntry.value
7222
+ });
7223
+ return promise;
6538
7224
  }
6539
- function normalizeFiniteNumberOrNull(value) {
6540
- return typeof value === "number" && Number.isFinite(value) ? value : null;
7225
+ async function createLiteLLMPricingResolver(options = {}) {
7226
+ var _a, _b, _c, _d;
7227
+ const dataset = await fetchLiteLLMPricingDataset(options);
7228
+ const aliases = (_a = options.aliases) != null ? _a : {};
7229
+ const fallbackPricingTable = {
7230
+ ...DEFAULT_FALLBACK_PRICING_TABLE,
7231
+ ...(_b = options.fallbackPricingTable) != null ? _b : {}
7232
+ };
7233
+ const getLookupCandidates = (_c = options.getLookupCandidates) != null ? _c : defaultLookupCandidates;
7234
+ const fallbackModel = options.fallbackModel;
7235
+ const isZeroCostModel = (_d = options.isZeroCostModel) != null ? _d : (() => false);
7236
+ return (model) => {
7237
+ var _a2, _b2;
7238
+ if (isZeroCostModel(model)) {
7239
+ return createZeroPricing();
7240
+ }
7241
+ const lookupCandidates = uniqueItems(expandLookupCandidates(model, aliases, getLookupCandidates).filter(Boolean));
7242
+ const datasetPricing = resolveDatasetPricing(dataset, lookupCandidates);
7243
+ if (datasetPricing) {
7244
+ return datasetPricing;
7245
+ }
7246
+ const fallbackPricing = resolveFallbackPricing(fallbackPricingTable, lookupCandidates);
7247
+ if (fallbackPricing) {
7248
+ return fallbackPricing;
7249
+ }
7250
+ if (fallbackModel) {
7251
+ const fallbackCandidates = uniqueItems(expandLookupCandidates(fallbackModel, aliases, getLookupCandidates).filter(Boolean));
7252
+ return (_b2 = (_a2 = resolveDatasetPricing(dataset, fallbackCandidates)) != null ? _a2 : resolveFallbackPricing(fallbackPricingTable, fallbackCandidates)) != null ? _b2 : createZeroPricing();
7253
+ }
7254
+ return createZeroPricing();
7255
+ };
6541
7256
  }
6542
- function normalizeTimestampValue(value) {
6543
- const normalizedValue = normalizeStringValue(value);
6544
- return normalizedValue && Number.isFinite(Date.parse(normalizedValue)) ? normalizedValue : null;
7257
+ function calculateUsageCostUSD(usage, pricing, options = {}) {
7258
+ var _a, _b, _c;
7259
+ const multiplier = options.speed === "fast" ? (_b = (_a = pricing.fastMultiplier) != null ? _a : options.defaultFastMultiplier) != null ? _b : 1 : 1;
7260
+ const inputCost = calculateTieredCost(usage.inputTokens, pricing.inputCostPerMTokens, pricing.inputCostPerMTokensAbove200K);
7261
+ const cachedCost = calculateTieredCost(usage.cachedInputTokens, pricing.cachedInputCostPerMTokens, pricing.cachedInputCostPerMTokensAbove200K);
7262
+ const cacheCreationCost = calculateTieredCost((_c = usage.cacheCreationTokens) != null ? _c : 0, pricing.cacheCreationInputCostPerMTokens, pricing.cacheCreationInputCostPerMTokensAbove200K);
7263
+ const outputCost = calculateTieredCost(usage.outputTokens, pricing.outputCostPerMTokens, pricing.outputCostPerMTokensAbove200K);
7264
+ return (inputCost + cachedCost + cacheCreationCost + outputCost) * multiplier;
6545
7265
  }
6546
- function splitCommaValues(value) {
6547
- return value.split(",").map((item) => item.trim()).filter(Boolean);
6548
- }
6549
-
6550
- const compactNumberFormatter = new Intl.NumberFormat("en-US", {
6551
- notation: "compact",
6552
- maximumFractionDigits: 1
6553
- });
6554
- const currencyFormatter = new Intl.NumberFormat("en-US", {
6555
- style: "currency",
6556
- currency: "USD",
6557
- minimumFractionDigits: 2,
6558
- maximumFractionDigits: 2
6559
- });
6560
- const percentFormatter = new Intl.NumberFormat("en-US", {
6561
- style: "percent",
6562
- minimumFractionDigits: 1,
6563
- maximumFractionDigits: 1
6564
- });
6565
- const dateKeyFormatter = new Intl.DateTimeFormat("en-CA", {
6566
- day: "2-digit",
6567
- month: "2-digit",
6568
- year: "numeric"
6569
- });
6570
- const dateLabelFormatter = new Intl.DateTimeFormat("en-US", {
6571
- day: "2-digit",
6572
- month: "short",
6573
- timeZone: "UTC",
6574
- year: "numeric"
6575
- });
6576
- function normalizeNumber(value) {
6577
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
6578
- }
6579
- function roundCurrency(value) {
6580
- return Math.round(value * 1e6) / 1e6;
7266
+ function createFallbackLiteLLMPricingDataset() {
7267
+ return {
7268
+ "gpt-5": {
7269
+ input_cost_per_token: 125e-8,
7270
+ output_cost_per_token: 1e-5,
7271
+ cache_creation_input_token_cost: 125e-8,
7272
+ cache_read_input_token_cost: 125e-9
7273
+ },
7274
+ "gpt-5.2-codex": {
7275
+ input_cost_per_token: 175e-8,
7276
+ output_cost_per_token: 14e-6,
7277
+ cache_creation_input_token_cost: 175e-8,
7278
+ cache_read_input_token_cost: 175e-9
7279
+ },
7280
+ "gpt-5.4": {
7281
+ input_cost_per_token: 25e-7,
7282
+ output_cost_per_token: 15e-6,
7283
+ cache_creation_input_token_cost: 25e-7,
7284
+ cache_read_input_token_cost: 25e-8
7285
+ },
7286
+ "gpt-5.5": {
7287
+ input_cost_per_token: 5e-6,
7288
+ output_cost_per_token: 3e-5,
7289
+ cache_creation_input_token_cost: 5e-6,
7290
+ cache_read_input_token_cost: 5e-7,
7291
+ provider_specific_entry: {
7292
+ fast: 2.5
7293
+ }
7294
+ },
7295
+ "claude-haiku-4-5": {
7296
+ input_cost_per_token: 1e-6,
7297
+ output_cost_per_token: 5e-6,
7298
+ cache_creation_input_token_cost: 125e-8,
7299
+ cache_read_input_token_cost: 1e-7
7300
+ },
7301
+ "claude-opus-4-1": {
7302
+ input_cost_per_token: 15e-6,
7303
+ output_cost_per_token: 75e-6,
7304
+ cache_creation_input_token_cost: 1875e-8,
7305
+ cache_read_input_token_cost: 15e-7,
7306
+ input_cost_per_token_above_200k_tokens: 3e-5,
7307
+ output_cost_per_token_above_200k_tokens: 1125e-7,
7308
+ cache_creation_input_token_cost_above_200k_tokens: 375e-7,
7309
+ cache_read_input_token_cost_above_200k_tokens: 3e-6
7310
+ },
7311
+ "claude-opus-4-6": {
7312
+ input_cost_per_token: 5e-6,
7313
+ output_cost_per_token: 25e-6,
7314
+ cache_creation_input_token_cost: 625e-8,
7315
+ cache_read_input_token_cost: 5e-7,
7316
+ provider_specific_entry: {
7317
+ fast: 6
7318
+ }
7319
+ },
7320
+ "claude-opus-4-7": {
7321
+ input_cost_per_token: 5e-6,
7322
+ output_cost_per_token: 25e-6,
7323
+ cache_creation_input_token_cost: 625e-8,
7324
+ cache_read_input_token_cost: 5e-7,
7325
+ provider_specific_entry: {
7326
+ fast: 6
7327
+ }
7328
+ },
7329
+ "claude-sonnet-4-5": {
7330
+ input_cost_per_token: 3e-6,
7331
+ output_cost_per_token: 15e-6,
7332
+ cache_creation_input_token_cost: 375e-8,
7333
+ cache_read_input_token_cost: 3e-7,
7334
+ input_cost_per_token_above_200k_tokens: 6e-6,
7335
+ output_cost_per_token_above_200k_tokens: 225e-7,
7336
+ cache_creation_input_token_cost_above_200k_tokens: 75e-7,
7337
+ cache_read_input_token_cost_above_200k_tokens: 6e-7
7338
+ }
7339
+ };
6581
7340
  }
6582
- function uniqueItems(items) {
6583
- return Array.from(new Set(items));
7341
+ function isLiteLLMPricingDataset(value) {
7342
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7343
+ return false;
7344
+ }
7345
+ return true;
6584
7346
  }
6585
- function formatCompactNumber(value) {
6586
- return compactNumberFormatter.format(normalizeNumber(value));
7347
+ function defaultLookupCandidates(model) {
7348
+ const normalizedModel = model.trim();
7349
+ return [
7350
+ normalizedModel,
7351
+ normalizedModel.replace(/^openai\//u, ""),
7352
+ normalizedModel.replace(/^azure\//u, ""),
7353
+ normalizedModel.replace(/^openrouter\/openai\//u, "")
7354
+ ];
6587
7355
  }
6588
- function formatCurrency(value) {
6589
- return currencyFormatter.format(normalizeNumber(value));
7356
+ function expandLookupCandidates(model, aliases, getLookupCandidates) {
7357
+ const candidates = getLookupCandidates(model);
7358
+ const expanded = [...candidates];
7359
+ for (const candidate of candidates) {
7360
+ const alias = aliases[candidate];
7361
+ if (alias) {
7362
+ expanded.push(...getLookupCandidates(alias));
7363
+ }
7364
+ }
7365
+ return expanded;
6590
7366
  }
6591
- function formatPercent(value) {
6592
- return percentFormatter.format(normalizeNumber(value));
7367
+ function resolveDatasetPricing(dataset, candidates) {
7368
+ for (const candidate of candidates) {
7369
+ const pricing = dataset[candidate];
7370
+ if (!pricing || !hasNonZeroTokenPricing(pricing)) {
7371
+ continue;
7372
+ }
7373
+ return toModelPricing(pricing, candidates);
7374
+ }
7375
+ return null;
6593
7376
  }
6594
- function buildGrowthTrend(currentValue, previousValue, formatValue) {
6595
- const current = Math.max(normalizeNumber(currentValue), 0);
6596
- const previous = Math.max(normalizeNumber(previousValue), 0);
6597
- if (previous === 0) {
6598
- if (current === 0) {
6599
- return {
6600
- trend: "0.0%",
6601
- trendTone: "neutral"
6602
- };
7377
+ function resolveFallbackPricing(fallbackPricingTable, candidates) {
7378
+ var _a;
7379
+ for (const candidate of candidates) {
7380
+ const pricing = fallbackPricingTable[candidate];
7381
+ if (pricing) {
7382
+ const fastMultiplier = (_a = pricing.fastMultiplier) != null ? _a : resolveFastMultiplierOverride(candidates);
7383
+ return fastMultiplier == null ? pricing : { ...pricing, fastMultiplier };
6603
7384
  }
6604
- return {
6605
- trend: `+${formatValue(current)}`,
6606
- trendTone: "up"
6607
- };
6608
7385
  }
6609
- const ratio = (current - previous) / previous;
7386
+ return null;
7387
+ }
7388
+ function hasNonZeroTokenPricing(pricing) {
7389
+ var _a, _b, _c, _d;
7390
+ return ((_a = pricing.input_cost_per_token) != null ? _a : 0) > 0 || ((_b = pricing.output_cost_per_token) != null ? _b : 0) > 0 || ((_c = pricing.cache_creation_input_token_cost) != null ? _c : 0) > 0 || ((_d = pricing.cache_read_input_token_cost) != null ? _d : 0) > 0;
7391
+ }
7392
+ function toModelPricing(pricing, candidates) {
7393
+ var _a, _b, _c, _d, _e, _f;
7394
+ const inputCostPerToken = (_a = pricing.input_cost_per_token) != null ? _a : 0;
7395
+ const cachedInputCostPerToken = (_b = pricing.cache_read_input_token_cost) != null ? _b : inputCostPerToken;
7396
+ const cacheCreationInputCostPerToken = (_c = pricing.cache_creation_input_token_cost) != null ? _c : inputCostPerToken;
7397
+ const outputCostPerToken = (_d = pricing.output_cost_per_token) != null ? _d : 0;
6610
7398
  return {
6611
- trend: formatSignedPercent(ratio),
6612
- trendTone: getTrendTone(ratio)
7399
+ cachedInputCostPerMTokens: cachedInputCostPerToken * MILLION,
7400
+ cachedInputCostPerMTokensAbove200K: pricing.cache_read_input_token_cost_above_200k_tokens != null ? pricing.cache_read_input_token_cost_above_200k_tokens * MILLION : void 0,
7401
+ cacheCreationInputCostPerMTokens: cacheCreationInputCostPerToken * MILLION,
7402
+ cacheCreationInputCostPerMTokensAbove200K: pricing.cache_creation_input_token_cost_above_200k_tokens != null ? pricing.cache_creation_input_token_cost_above_200k_tokens * MILLION : void 0,
7403
+ fastMultiplier: (_f = (_e = pricing.provider_specific_entry) == null ? void 0 : _e.fast) != null ? _f : resolveFastMultiplierOverride(candidates),
7404
+ inputCostPerMTokens: inputCostPerToken * MILLION,
7405
+ inputCostPerMTokensAbove200K: pricing.input_cost_per_token_above_200k_tokens != null ? pricing.input_cost_per_token_above_200k_tokens * MILLION : void 0,
7406
+ outputCostPerMTokens: outputCostPerToken * MILLION,
7407
+ outputCostPerMTokensAbove200K: pricing.output_cost_per_token_above_200k_tokens != null ? pricing.output_cost_per_token_above_200k_tokens * MILLION : void 0
6613
7408
  };
6614
7409
  }
6615
- function getDateKey(date) {
6616
- var _a, _b, _c, _d, _e, _f;
6617
- const parts = dateKeyFormatter.formatToParts(date);
6618
- const year = (_b = (_a = parts.find((part) => part.type === "year")) == null ? void 0 : _a.value) != null ? _b : "0000";
6619
- const month = (_d = (_c = parts.find((part) => part.type === "month")) == null ? void 0 : _c.value) != null ? _d : "01";
6620
- const day = (_f = (_e = parts.find((part) => part.type === "day")) == null ? void 0 : _e.value) != null ? _f : "01";
6621
- return `${year}-${month}-${day}`;
7410
+ function resolveFastMultiplierOverride(candidates) {
7411
+ for (const candidate of candidates) {
7412
+ const multiplier = FAST_MULTIPLIER_EXACT_OVERRIDES[candidate];
7413
+ if (multiplier != null) {
7414
+ return multiplier;
7415
+ }
7416
+ }
7417
+ for (const candidate of candidates) {
7418
+ const normalized = candidate.replace(/[.@]/gu, "-");
7419
+ for (const part of normalized.split(/[/:]/u)) {
7420
+ for (const [base, multiplier] of Object.entries(FAST_MULTIPLIER_PREFIX_OVERRIDES)) {
7421
+ if (matchesModelSuffix(part, base)) {
7422
+ return multiplier;
7423
+ }
7424
+ }
7425
+ }
7426
+ }
7427
+ return void 0;
6622
7428
  }
6623
- function getDateKeyFromLabel(label) {
6624
- const date = new Date(label);
6625
- if (Number.isNaN(date.getTime())) {
6626
- return label;
7429
+ function matchesModelSuffix(part, base) {
7430
+ const index = part.lastIndexOf(base);
7431
+ if (index < 0) {
7432
+ return false;
6627
7433
  }
6628
- return getDateKey(date);
7434
+ const suffix = part.slice(index);
7435
+ return suffix === base || suffix[base.length] === "-";
6629
7436
  }
6630
- function getPreviousDateKey(dateKey) {
6631
- const [year, month, day] = dateKey.split("-").map((value) => Number.parseInt(value, 10));
6632
- const date = new Date(year || 0, (month || 1) - 1, day || 1);
6633
- date.setDate(date.getDate() - 1);
6634
- return getDateKey(date);
7437
+ function createZeroPricing() {
7438
+ return {
7439
+ cachedInputCostPerMTokens: 0,
7440
+ cacheCreationInputCostPerMTokens: 0,
7441
+ inputCostPerMTokens: 0,
7442
+ outputCostPerMTokens: 0
7443
+ };
6635
7444
  }
6636
- function formatDateLabelFromDateKey(dateKey, fallback = dateKey) {
6637
- const [year, month, day] = dateKey.split("-").map((value) => Number.parseInt(value, 10));
6638
- if (!year || !month || !day) {
6639
- return fallback;
7445
+ function calculateTieredCost(tokens, baseCostPerMTokens, above200KCostPerMTokens) {
7446
+ const safeTokens = Math.max(tokens != null ? tokens : 0, 0);
7447
+ if (safeTokens === 0) {
7448
+ return 0;
6640
7449
  }
6641
- return dateLabelFormatter.format(new Date(Date.UTC(year, month - 1, day)));
7450
+ if (safeTokens > 2e5 && above200KCostPerMTokens != null) {
7451
+ return 2e5 / MILLION * baseCostPerMTokens + (safeTokens - 2e5) / MILLION * above200KCostPerMTokens;
7452
+ }
7453
+ return safeTokens / MILLION * baseCostPerMTokens;
6642
7454
  }
6643
- function buildProjectUsage(sessionUsage) {
6644
- var _a;
6645
- const projects = /* @__PURE__ */ new Map();
6646
- for (const session of sessionUsage) {
6647
- const project = (_a = projects.get(session.project)) != null ? _a : {
6648
- costUSD: 0,
6649
- repository: session.repository,
6650
- sessions: 0,
6651
- tokenTotal: 0
6652
- };
6653
- project.costUSD += session.costUSD;
6654
- project.sessions += 1;
6655
- project.tokenTotal += session.tokenTotal;
6656
- projects.set(session.project, project);
7455
+
7456
+ function normalizeStringValue(value) {
7457
+ return typeof value === "string" ? value.trim() : void 0;
7458
+ }
7459
+ function normalizeStringList(value) {
7460
+ if (Array.isArray(value)) {
7461
+ return value.flatMap((item) => typeof item === "string" ? splitCommaValues(item) : []);
6657
7462
  }
6658
- const maxCost = Math.max(...Array.from(projects.values()).map((project) => project.costUSD), 0);
6659
- return Array.from(projects.entries()).map(([label, project]) => ({
6660
- costUSD: project.costUSD,
6661
- detail: `${project.sessions} sessions / ${formatCompactNumber(project.tokenTotal)} tokens`,
6662
- label,
6663
- percent: maxCost > 0 ? project.costUSD / maxCost * 100 : 0,
6664
- repository: project.repository,
6665
- sessions: project.sessions,
6666
- tokenTotal: project.tokenTotal,
6667
- tone: "amber",
6668
- value: formatCurrency(project.costUSD)
6669
- })).sort((a, b) => b.costUSD - a.costUSD);
7463
+ if (typeof value === "string") {
7464
+ return splitCommaValues(value);
7465
+ }
7466
+ return void 0;
6670
7467
  }
6671
- function mergeDailyTokenUsage(items) {
6672
- var _a, _b;
6673
- const groups = /* @__PURE__ */ new Map();
6674
- for (const item of items) {
6675
- const dateKey = getDateKeyFromLabel(item.date);
6676
- const group = (_a = groups.get(dateKey)) != null ? _a : {
6677
- cachedInputTokens: 0,
6678
- costUSD: 0,
6679
- date: formatDateLabelFromDateKey(dateKey, item.date),
6680
- inputTokens: 0,
6681
- models: /* @__PURE__ */ new Map(),
6682
- outputTokens: 0,
6683
- reasoningOutputTokens: 0,
6684
- totalTokens: 0
6685
- };
6686
- group.cachedInputTokens += item.cachedInputTokens;
6687
- group.costUSD += item.costUSD;
6688
- group.inputTokens += item.inputTokens;
6689
- group.outputTokens += item.outputTokens;
6690
- group.reasoningOutputTokens += item.reasoningOutputTokens;
6691
- group.totalTokens += item.totalTokens;
6692
- for (const [modelName, usage] of Object.entries(item.models)) {
6693
- const model = (_b = group.models.get(modelName)) != null ? _b : createEmptyModelUsage();
6694
- model.cachedInputTokens += usage.cachedInputTokens;
6695
- model.inputTokens += usage.inputTokens;
6696
- model.isFallback = model.isFallback || usage.isFallback;
6697
- model.outputTokens += usage.outputTokens;
6698
- model.reasoningOutputTokens += usage.reasoningOutputTokens;
6699
- model.totalTokens += usage.totalTokens;
6700
- group.models.set(modelName, model);
6701
- }
6702
- groups.set(dateKey, group);
6703
- }
6704
- return Array.from(groups.entries()).sort((a, b) => b[0].localeCompare(a[0])).map(([, group]) => ({
6705
- cachedInputTokens: group.cachedInputTokens,
6706
- costUSD: roundCurrency(group.costUSD),
6707
- date: group.date,
6708
- inputTokens: group.inputTokens,
6709
- models: Object.fromEntries(group.models.entries()),
6710
- outputTokens: group.outputTokens,
6711
- reasoningOutputTokens: group.reasoningOutputTokens,
6712
- totalTokens: group.totalTokens
6713
- }));
6714
- }
6715
- function mergeMonthlyModelUsage(items) {
6716
- var _a;
6717
- const groups = /* @__PURE__ */ new Map();
6718
- for (const item of items) {
6719
- const key = `${item.month}__${item.model}`;
6720
- const group = (_a = groups.get(key)) != null ? _a : {
6721
- model: item.model,
6722
- month: item.month,
6723
- tokenTotal: 0
6724
- };
6725
- group.tokenTotal += item.tokenTotal;
6726
- groups.set(key, group);
6727
- }
6728
- return Array.from(groups.values()).sort((a, b) => a.month.localeCompare(b.month) || a.model.localeCompare(b.model));
7468
+ function normalizeUnknownRecord(value) {
7469
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
6729
7470
  }
6730
- function formatSignedPercent(value) {
6731
- const prefix = value > 0 ? "+" : "";
6732
- return `${prefix}${formatPercent(value)}`;
7471
+ function normalizeFiniteNumberOrNull(value) {
7472
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
6733
7473
  }
6734
- function getTrendTone(value) {
6735
- if (value > 0) {
6736
- return "up";
6737
- }
6738
- if (value < 0) {
6739
- return "down";
6740
- }
6741
- return "neutral";
7474
+ function normalizeTimestampValue(value) {
7475
+ const normalizedValue = normalizeStringValue(value);
7476
+ return normalizedValue && Number.isFinite(Date.parse(normalizedValue)) ? normalizedValue : null;
6742
7477
  }
6743
- function createEmptyModelUsage() {
6744
- return {
6745
- cachedInputTokens: 0,
6746
- inputTokens: 0,
6747
- isFallback: false,
6748
- outputTokens: 0,
6749
- reasoningOutputTokens: 0,
6750
- totalTokens: 0
6751
- };
7478
+ function splitCommaValues(value) {
7479
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
6752
7480
  }
6753
7481
 
6754
7482
  function parseJsonlFile(filePath) {
@@ -6795,9 +7523,11 @@ function buildDailyUsageGroups(events, options = {}) {
6795
7523
  if (shouldIncludeModel(event, options)) {
6796
7524
  const modelUsage = (_b = group.modelUsage.get(event.model)) != null ? _b : {
6797
7525
  ...createEmptyUsage(),
7526
+ costUSD: 0,
6798
7527
  isFallback: false
6799
7528
  };
6800
7529
  addUsage(modelUsage, event);
7530
+ modelUsage.costUSD += getEventCostUSD(event, options);
6801
7531
  if (event.isFallbackModel) {
6802
7532
  modelUsage.isFallback = true;
6803
7533
  }
@@ -6815,6 +7545,7 @@ function buildDailyTokenUsage(dailyGroups) {
6815
7545
  inputTokens: group.inputTokens,
6816
7546
  models: Object.fromEntries(Array.from(group.modelUsage.entries()).map(([model, usage]) => [model, {
6817
7547
  cachedInputTokens: usage.cachedInputTokens,
7548
+ costUSD: roundCurrency(usage.costUSD),
6818
7549
  inputTokens: usage.inputTokens,
6819
7550
  isFallback: usage.isFallback,
6820
7551
  outputTokens: usage.outputTokens,
@@ -6896,22 +7627,26 @@ function buildMonthlyModelUsage(events, options = {}) {
6896
7627
  })).sort((a, b) => a.month.localeCompare(b.month) || a.model.localeCompare(b.model));
6897
7628
  }
6898
7629
  function buildSessionRows(sessions, options = {}) {
6899
- return [...sessions].sort((a, b) => getSessionSortTimestamp(b) - getSessionSortTimestamp(a)).map((session) => ({
6900
- cachedInputTokens: getCachedInputTokens(session, options),
6901
- costUSD: session.costUSD,
6902
- id: session.sessionId,
6903
- inputTokens: session.inputTokens,
6904
- label: session.sessionId,
6905
- models: session.models,
6906
- outputTokens: session.outputTokens,
6907
- period: formatDateLabelFromDateKey(getDateKey(new Date(session.lastActivity))),
6908
- projects: [session.project],
6909
- reasoningOutputTokens: getReasoningOutputTokens(session, options),
6910
- sessionCount: 1,
6911
- totalTokens: session.tokenTotal
6912
- }));
7630
+ return [...sessions].sort((a, b) => getSessionSortTimestamp(b) - getSessionSortTimestamp(a)).map((session) => {
7631
+ var _a;
7632
+ return {
7633
+ cachedInputTokens: getCachedInputTokens(session, options),
7634
+ costUSD: session.costUSD,
7635
+ id: (_a = session.id) != null ? _a : session.sessionId,
7636
+ inputTokens: session.inputTokens,
7637
+ label: session.sessionId,
7638
+ models: session.models,
7639
+ outputTokens: session.outputTokens,
7640
+ period: formatDateLabelFromDateKey(getDateKey(new Date(session.lastActivity))),
7641
+ projects: [session.project],
7642
+ reasoningOutputTokens: getReasoningOutputTokens(session, options),
7643
+ sessionCount: 1,
7644
+ totalTokens: session.tokenTotal
7645
+ };
7646
+ });
6913
7647
  }
6914
7648
  function toUsageSessionUsageItem(session, options = {}) {
7649
+ var _a;
6915
7650
  const startedAtDate = new Date(session.startedAt);
6916
7651
  return {
6917
7652
  cachedInputTokens: getCachedInputTokens(session, options),
@@ -6919,7 +7654,7 @@ function toUsageSessionUsageItem(session, options = {}) {
6919
7654
  date: formatDateLabelFromDateKey(getDateKey(startedAtDate)),
6920
7655
  duration: formatDuration(session.durationMinutes),
6921
7656
  durationMinutes: session.durationMinutes,
6922
- id: session.sessionId,
7657
+ id: (_a = session.id) != null ? _a : session.sessionId,
6923
7658
  inputTokens: session.inputTokens,
6924
7659
  model: session.topModel,
6925
7660
  month: getMonthKey(startedAtDate),
@@ -6951,6 +7686,10 @@ function buildOverviewCards(options) {
6951
7686
  detail: `${formatNumber(options.todayTotalTokens)} tokens used today`,
6952
7687
  icon: "solar:cpu-line-duotone",
6953
7688
  name: "Today Tokens",
7689
+ subvalue: buildInputOutputTokenSubvalue({
7690
+ inputTokens: options.todayInputTokens,
7691
+ outputTokens: options.todayOutputTokens
7692
+ }),
6954
7693
  trend: tokenTrend.trend,
6955
7694
  trendTone: tokenTrend.trendTone,
6956
7695
  value: formatCompactNumber(options.todayTotalTokens)
@@ -6982,7 +7721,7 @@ function buildOverviewCards(options) {
6982
7721
  ];
6983
7722
  }
6984
7723
  function buildLoadUsageResult(events, sessions, options = {}) {
6985
- var _a, _b, _c, _d, _e, _f;
7724
+ var _a, _b, _c, _d, _e, _f, _g, _h;
6986
7725
  const aggregateOptions = (_a = options.aggregateOptions) != null ? _a : {};
6987
7726
  const sessionOptions = (_b = options.sessionOptions) != null ? _b : {};
6988
7727
  const sortedSessions = [...sessions].sort((a, b) => getSessionSortTimestamp(b) - getSessionSortTimestamp(a));
@@ -7005,6 +7744,8 @@ function buildLoadUsageResult(events, sessions, options = {}) {
7005
7744
  overviewCards: buildOverviewCards({
7006
7745
  previousDayCost: roundCurrency((_e = previousDayDailyGroup == null ? void 0 : previousDayDailyGroup.costUSD) != null ? _e : 0),
7007
7746
  previousDayTokens: (_f = previousDayDailyGroup == null ? void 0 : previousDayDailyGroup.totalTokens) != null ? _f : 0,
7747
+ todayInputTokens: (_g = todayDailyGroup == null ? void 0 : todayDailyGroup.inputTokens) != null ? _g : 0,
7748
+ todayOutputTokens: (_h = todayDailyGroup == null ? void 0 : todayDailyGroup.outputTokens) != null ? _h : 0,
7008
7749
  todayTopModel,
7009
7750
  todayTopProject,
7010
7751
  todayTotalCost,
@@ -7104,10 +7845,21 @@ function getDurationMinutes(startedAt, endedAt) {
7104
7845
  return Math.round(durationMs / 6e4);
7105
7846
  }
7106
7847
  function toIsoString(value) {
7848
+ if (typeof value === "number" && Number.isFinite(value)) {
7849
+ const timestamp2 = value > 1e10 ? value : value * 1e3;
7850
+ return new Date(timestamp2).toISOString();
7851
+ }
7852
+ if (value instanceof Date) {
7853
+ return Number.isFinite(value.getTime()) ? value.toISOString() : null;
7854
+ }
7107
7855
  if (typeof value !== "string") {
7108
7856
  return null;
7109
7857
  }
7110
- const timestamp = Date.parse(value);
7858
+ const normalizedValue = value.trim();
7859
+ if (!normalizedValue) {
7860
+ return null;
7861
+ }
7862
+ const timestamp = Date.parse(normalizedValue);
7111
7863
  return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
7112
7864
  }
7113
7865
  function getMonthKey(date) {
@@ -7157,7 +7909,7 @@ function normalizeRawUsage(usage) {
7157
7909
  input_tokens: input,
7158
7910
  output_tokens: output,
7159
7911
  reasoning_output_tokens: reasoning,
7160
- total_tokens: total > 0 ? total : input + output
7912
+ total_tokens: total > 0 ? total : input + output + reasoning
7161
7913
  };
7162
7914
  }
7163
7915
  function subtractRawUsage(current, previous) {
@@ -7237,14 +7989,23 @@ function decodeClaudeProjectPath(projectPath) {
7237
7989
  return (_a = parts.at(-1)) != null ? _a : projectPath;
7238
7990
  }
7239
7991
  function getClaudeLookupCandidates(model) {
7992
+ var _a;
7240
7993
  const normalizedModel = model.trim();
7994
+ const withoutFastSuffix = normalizedModel.replace(/-fast$/u, "");
7995
+ const baseModel = (_a = withoutFastSuffix.split("/").at(-1)) != null ? _a : withoutFastSuffix;
7996
+ const normalizedBaseModel = baseModel.replace(/[.@]/gu, "-");
7997
+ const baseModelWithoutDate = normalizedBaseModel.replace(/-\d{8}$/u, "");
7241
7998
  return [
7242
7999
  normalizedModel,
7243
- normalizedModel.replace(/-fast$/u, ""),
7244
- normalizedModel.replace(/^anthropic\//u, ""),
8000
+ withoutFastSuffix,
8001
+ withoutFastSuffix.replace(/^anthropic\//u, ""),
7245
8002
  `anthropic/${normalizedModel}`,
7246
- normalizedModel.replace(/^claude-3-5-/u, "claude-"),
7247
- normalizedModel.replace(/^claude-3-7-/u, "claude-")
8003
+ baseModel,
8004
+ normalizedBaseModel,
8005
+ baseModelWithoutDate,
8006
+ `anthropic/${baseModelWithoutDate}`,
8007
+ baseModelWithoutDate.replace(/^claude-3-5-/u, "claude-"),
8008
+ baseModelWithoutDate.replace(/^claude-3-7-/u, "claude-")
7248
8009
  ];
7249
8010
  }
7250
8011
  function getGeminiLookupCandidates(model) {
@@ -7347,349 +8108,77 @@ function getCachedInputTokens(session, options) {
7347
8108
  return (_b = (_a = options.getCachedInputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "cachedInputTokens");
7348
8109
  }
7349
8110
  function getReasoningOutputTokens(session, options) {
7350
- var _a, _b;
7351
- return (_b = (_a = options.getReasoningOutputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "reasoningOutputTokens");
7352
- }
7353
- function getNumericProperty(value, key) {
7354
- const record = value;
7355
- const property = record[key];
7356
- return typeof property === "number" && Number.isFinite(property) ? property : 0;
7357
- }
7358
- function cloneDate(date) {
7359
- return new Date(date.getFullYear(), date.getMonth(), date.getDate());
7360
- }
7361
-
7362
- function toDiscoveredUsageFile(filePath, platform) {
7363
- try {
7364
- const stats = statSync(filePath);
7365
- return [{
7366
- mtimeMs: stats.mtimeMs,
7367
- path: filePath,
7368
- platform,
7369
- size: stats.size
7370
- }];
7371
- } catch {
7372
- return [];
7373
- }
7374
- }
7375
- function createSessionFragment(options) {
7376
- return {
7377
- durationEndAt: "",
7378
- interactions: [],
7379
- key: getSessionLookupKey(options.project, options.sessionId),
7380
- project: options.project,
7381
- repository: options.repository,
7382
- sessionId: options.sessionId,
7383
- startedAt: options.startedAt,
7384
- threadName: options.threadName
7385
- };
7386
- }
7387
- function addFragmentInteraction(fragment, interaction) {
7388
- fragment.interactions.push(interaction);
7389
- if (!interaction.timestamp) {
7390
- return;
7391
- }
7392
- if (!fragment.startedAt || Date.parse(interaction.timestamp) < Date.parse(fragment.startedAt)) {
7393
- fragment.startedAt = interaction.timestamp;
7394
- }
7395
- if (!fragment.durationEndAt || Date.parse(interaction.timestamp) > Date.parse(fragment.durationEndAt)) {
7396
- fragment.durationEndAt = interaction.timestamp;
7397
- }
7398
- }
7399
- function getSessionLookupKey(project, sessionId) {
7400
- return `${project}:${sessionId}`;
7401
- }
7402
- function normalizeRole(value) {
7403
- const normalized = value.toLowerCase();
7404
- if (normalized.includes("user")) {
7405
- return "user";
7406
- }
7407
- if (normalized.includes("assistant") || normalized.includes("agent") || normalized.includes("gemini")) {
7408
- return "assistant";
7409
- }
7410
- if (normalized.includes("system")) {
7411
- return "system";
7412
- }
7413
- if (normalized.includes("tool")) {
7414
- return "tool";
7415
- }
7416
- if (normalized.includes("token") || normalized.includes("usage")) {
7417
- return "usage";
7418
- }
7419
- return "unknown";
7420
- }
7421
-
7422
- const MILLION = 1e6;
7423
- const DEFAULT_PRICING_CACHE_TTL_MS = 1e3 * 60 * 5;
7424
- const DEFAULT_LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
7425
- const DEFAULT_FALLBACK_PRICING_TABLE = {
7426
- "gpt-5": {
7427
- cachedInputCostPerMTokens: 0.125,
7428
- cacheCreationInputCostPerMTokens: 1.25,
7429
- inputCostPerMTokens: 1.25,
7430
- outputCostPerMTokens: 10
7431
- },
7432
- "gpt-5.2-codex": {
7433
- cachedInputCostPerMTokens: 0.175,
7434
- cacheCreationInputCostPerMTokens: 1.75,
7435
- inputCostPerMTokens: 1.75,
7436
- outputCostPerMTokens: 14
7437
- },
7438
- "gpt-5.4": {
7439
- cachedInputCostPerMTokens: 0.25,
7440
- cacheCreationInputCostPerMTokens: 2.5,
7441
- inputCostPerMTokens: 2.5,
7442
- outputCostPerMTokens: 15
7443
- },
7444
- "claude-haiku-4-5": {
7445
- cachedInputCostPerMTokens: 0.1,
7446
- cacheCreationInputCostPerMTokens: 1.25,
7447
- inputCostPerMTokens: 1,
7448
- outputCostPerMTokens: 5
7449
- },
7450
- "claude-opus-4-1": {
7451
- cachedInputCostPerMTokens: 1.5,
7452
- cachedInputCostPerMTokensAbove200K: 3,
7453
- cacheCreationInputCostPerMTokens: 18.75,
7454
- cacheCreationInputCostPerMTokensAbove200K: 37.5,
7455
- inputCostPerMTokens: 15,
7456
- inputCostPerMTokensAbove200K: 30,
7457
- outputCostPerMTokens: 75,
7458
- outputCostPerMTokensAbove200K: 112.5
7459
- },
7460
- "claude-sonnet-4-5": {
7461
- cachedInputCostPerMTokens: 0.3,
7462
- cachedInputCostPerMTokensAbove200K: 0.6,
7463
- cacheCreationInputCostPerMTokens: 3.75,
7464
- cacheCreationInputCostPerMTokensAbove200K: 7.5,
7465
- inputCostPerMTokens: 3,
7466
- inputCostPerMTokensAbove200K: 6,
7467
- outputCostPerMTokens: 15,
7468
- outputCostPerMTokensAbove200K: 22.5
7469
- }
7470
- };
7471
- const pricingCache = /* @__PURE__ */ new Map();
7472
- async function fetchLiteLLMPricingDataset(options = {}) {
7473
- var _a, _b, _c, _d;
7474
- const url = (_a = options.url) != null ? _a : DEFAULT_LITELLM_PRICING_URL;
7475
- const cacheTtlMs = (_b = options.cacheTtlMs) != null ? _b : DEFAULT_PRICING_CACHE_TTL_MS;
7476
- const now = Date.now();
7477
- const cacheEntry = pricingCache.get(url);
7478
- if (!options.forceRefresh && (cacheEntry == null ? void 0 : cacheEntry.value) && now - cacheEntry.fetchedAt < cacheTtlMs) {
7479
- return cacheEntry.value;
7480
- }
7481
- if (!options.forceRefresh && (cacheEntry == null ? void 0 : cacheEntry.promise)) {
7482
- return cacheEntry.promise;
7483
- }
7484
- const fetcher = (_c = options.fetcher) != null ? _c : globalThis.fetch;
7485
- if (typeof fetcher !== "function") {
7486
- return createFallbackLiteLLMPricingDataset();
7487
- }
7488
- const promise = fetcher(url).then(async (response) => {
7489
- if (!response.ok) {
7490
- throw new Error(`Failed to fetch LiteLLM pricing dataset: ${response.status} ${response.statusText}`);
7491
- }
7492
- const data = await response.json();
7493
- if (!isLiteLLMPricingDataset(data)) {
7494
- throw new Error("Invalid LiteLLM pricing dataset payload.");
7495
- }
7496
- const dataset = {
7497
- ...createFallbackLiteLLMPricingDataset(),
7498
- ...data
7499
- };
7500
- pricingCache.set(url, {
7501
- fetchedAt: Date.now(),
7502
- value: dataset
7503
- });
7504
- return dataset;
7505
- }).catch(() => {
7506
- const fallback = createFallbackLiteLLMPricingDataset();
7507
- pricingCache.set(url, {
7508
- fetchedAt: Date.now(),
7509
- value: fallback
7510
- });
7511
- return fallback;
7512
- });
7513
- pricingCache.set(url, {
7514
- fetchedAt: (_d = cacheEntry == null ? void 0 : cacheEntry.fetchedAt) != null ? _d : 0,
7515
- promise,
7516
- value: cacheEntry == null ? void 0 : cacheEntry.value
7517
- });
7518
- return promise;
7519
- }
7520
- async function createLiteLLMPricingResolver(options = {}) {
7521
- var _a, _b, _c, _d;
7522
- const dataset = await fetchLiteLLMPricingDataset(options);
7523
- const aliases = (_a = options.aliases) != null ? _a : {};
7524
- const fallbackPricingTable = {
7525
- ...DEFAULT_FALLBACK_PRICING_TABLE,
7526
- ...(_b = options.fallbackPricingTable) != null ? _b : {}
7527
- };
7528
- const getLookupCandidates = (_c = options.getLookupCandidates) != null ? _c : defaultLookupCandidates;
7529
- const fallbackModel = options.fallbackModel;
7530
- const isZeroCostModel = (_d = options.isZeroCostModel) != null ? _d : (() => false);
7531
- return (model) => {
7532
- var _a2, _b2;
7533
- if (isZeroCostModel(model)) {
7534
- return createZeroPricing();
7535
- }
7536
- const lookupCandidates = uniqueItems(expandLookupCandidates(model, aliases, getLookupCandidates).filter(Boolean));
7537
- const datasetPricing = resolveDatasetPricing(dataset, lookupCandidates);
7538
- if (datasetPricing) {
7539
- return datasetPricing;
7540
- }
7541
- const fallbackPricing = resolveFallbackPricing(fallbackPricingTable, lookupCandidates);
7542
- if (fallbackPricing) {
7543
- return fallbackPricing;
7544
- }
7545
- if (fallbackModel) {
7546
- const fallbackCandidates = uniqueItems(expandLookupCandidates(fallbackModel, aliases, getLookupCandidates).filter(Boolean));
7547
- return (_b2 = (_a2 = resolveDatasetPricing(dataset, fallbackCandidates)) != null ? _a2 : resolveFallbackPricing(fallbackPricingTable, fallbackCandidates)) != null ? _b2 : createZeroPricing();
7548
- }
7549
- return createZeroPricing();
7550
- };
7551
- }
7552
- function calculateUsageCostUSD(usage, pricing, options = {}) {
7553
- var _a, _b;
7554
- const multiplier = options.speed === "fast" ? (_a = pricing.fastMultiplier) != null ? _a : 1 : 1;
7555
- const inputCost = calculateTieredCost(usage.inputTokens, pricing.inputCostPerMTokens, pricing.inputCostPerMTokensAbove200K);
7556
- const cachedCost = calculateTieredCost(usage.cachedInputTokens, pricing.cachedInputCostPerMTokens, pricing.cachedInputCostPerMTokensAbove200K);
7557
- const cacheCreationCost = calculateTieredCost((_b = usage.cacheCreationTokens) != null ? _b : 0, pricing.cacheCreationInputCostPerMTokens, pricing.cacheCreationInputCostPerMTokensAbove200K);
7558
- const outputCost = calculateTieredCost(usage.outputTokens, pricing.outputCostPerMTokens, pricing.outputCostPerMTokensAbove200K);
7559
- return (inputCost + cachedCost + cacheCreationCost + outputCost) * multiplier;
7560
- }
7561
- function createFallbackLiteLLMPricingDataset() {
7562
- return {
7563
- "gpt-5": {
7564
- input_cost_per_token: 125e-8,
7565
- output_cost_per_token: 1e-5,
7566
- cache_creation_input_token_cost: 125e-8,
7567
- cache_read_input_token_cost: 125e-9
7568
- },
7569
- "gpt-5.2-codex": {
7570
- input_cost_per_token: 175e-8,
7571
- output_cost_per_token: 14e-6,
7572
- cache_creation_input_token_cost: 175e-8,
7573
- cache_read_input_token_cost: 175e-9
7574
- },
7575
- "gpt-5.4": {
7576
- input_cost_per_token: 25e-7,
7577
- output_cost_per_token: 15e-6,
7578
- cache_creation_input_token_cost: 25e-7,
7579
- cache_read_input_token_cost: 25e-8
7580
- },
7581
- "claude-haiku-4-5": {
7582
- input_cost_per_token: 1e-6,
7583
- output_cost_per_token: 5e-6,
7584
- cache_creation_input_token_cost: 125e-8,
7585
- cache_read_input_token_cost: 1e-7
7586
- },
7587
- "claude-opus-4-1": {
7588
- input_cost_per_token: 15e-6,
7589
- output_cost_per_token: 75e-6,
7590
- cache_creation_input_token_cost: 1875e-8,
7591
- cache_read_input_token_cost: 15e-7,
7592
- input_cost_per_token_above_200k_tokens: 3e-5,
7593
- output_cost_per_token_above_200k_tokens: 1125e-7,
7594
- cache_creation_input_token_cost_above_200k_tokens: 375e-7,
7595
- cache_read_input_token_cost_above_200k_tokens: 3e-6
7596
- },
7597
- "claude-sonnet-4-5": {
7598
- input_cost_per_token: 3e-6,
7599
- output_cost_per_token: 15e-6,
7600
- cache_creation_input_token_cost: 375e-8,
7601
- cache_read_input_token_cost: 3e-7,
7602
- input_cost_per_token_above_200k_tokens: 6e-6,
7603
- output_cost_per_token_above_200k_tokens: 225e-7,
7604
- cache_creation_input_token_cost_above_200k_tokens: 75e-7,
7605
- cache_read_input_token_cost_above_200k_tokens: 6e-7
7606
- }
7607
- };
7608
- }
7609
- function isLiteLLMPricingDataset(value) {
7610
- if (!value || typeof value !== "object" || Array.isArray(value)) {
7611
- return false;
7612
- }
7613
- return true;
7614
- }
7615
- function defaultLookupCandidates(model) {
7616
- const normalizedModel = model.trim();
7617
- return [
7618
- normalizedModel,
7619
- normalizedModel.replace(/^openai\//u, ""),
7620
- normalizedModel.replace(/^azure\//u, ""),
7621
- normalizedModel.replace(/^openrouter\/openai\//u, "")
7622
- ];
7623
- }
7624
- function expandLookupCandidates(model, aliases, getLookupCandidates) {
7625
- const candidates = getLookupCandidates(model);
7626
- const expanded = [...candidates];
7627
- for (const candidate of candidates) {
7628
- const alias = aliases[candidate];
7629
- if (alias) {
7630
- expanded.push(...getLookupCandidates(alias));
7631
- }
7632
- }
7633
- return expanded;
8111
+ var _a, _b;
8112
+ return (_b = (_a = options.getReasoningOutputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "reasoningOutputTokens");
7634
8113
  }
7635
- function resolveDatasetPricing(dataset, candidates) {
7636
- for (const candidate of candidates) {
7637
- const pricing = dataset[candidate];
7638
- if (!pricing || !hasNonZeroTokenPricing(pricing)) {
7639
- continue;
7640
- }
7641
- return toModelPricing(pricing);
7642
- }
7643
- return null;
8114
+ function getNumericProperty(value, key) {
8115
+ const record = value;
8116
+ const property = record[key];
8117
+ return typeof property === "number" && Number.isFinite(property) ? property : 0;
7644
8118
  }
7645
- function resolveFallbackPricing(fallbackPricingTable, candidates) {
7646
- for (const candidate of candidates) {
7647
- const pricing = fallbackPricingTable[candidate];
7648
- if (pricing) {
7649
- return pricing;
7650
- }
7651
- }
7652
- return null;
8119
+ function cloneDate(date) {
8120
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
7653
8121
  }
7654
- function hasNonZeroTokenPricing(pricing) {
7655
- var _a, _b, _c, _d;
7656
- return ((_a = pricing.input_cost_per_token) != null ? _a : 0) > 0 || ((_b = pricing.output_cost_per_token) != null ? _b : 0) > 0 || ((_c = pricing.cache_creation_input_token_cost) != null ? _c : 0) > 0 || ((_d = pricing.cache_read_input_token_cost) != null ? _d : 0) > 0;
8122
+
8123
+ function toDiscoveredUsageFile(filePath, platform, cacheSignature = "") {
8124
+ try {
8125
+ const stats = statSync(filePath);
8126
+ return [{
8127
+ cacheSignature,
8128
+ mtimeMs: stats.mtimeMs,
8129
+ path: filePath,
8130
+ platform,
8131
+ size: stats.size
8132
+ }];
8133
+ } catch {
8134
+ return [];
8135
+ }
7657
8136
  }
7658
- function toModelPricing(pricing) {
7659
- var _a, _b, _c, _d, _e;
7660
- const inputCostPerToken = (_a = pricing.input_cost_per_token) != null ? _a : 0;
7661
- const cachedInputCostPerToken = (_b = pricing.cache_read_input_token_cost) != null ? _b : inputCostPerToken;
7662
- const cacheCreationInputCostPerToken = (_c = pricing.cache_creation_input_token_cost) != null ? _c : inputCostPerToken;
7663
- const outputCostPerToken = (_d = pricing.output_cost_per_token) != null ? _d : 0;
8137
+ function createSessionFragment(options) {
7664
8138
  return {
7665
- cachedInputCostPerMTokens: cachedInputCostPerToken * MILLION,
7666
- cachedInputCostPerMTokensAbove200K: pricing.cache_read_input_token_cost_above_200k_tokens != null ? pricing.cache_read_input_token_cost_above_200k_tokens * MILLION : void 0,
7667
- cacheCreationInputCostPerMTokens: cacheCreationInputCostPerToken * MILLION,
7668
- cacheCreationInputCostPerMTokensAbove200K: pricing.cache_creation_input_token_cost_above_200k_tokens != null ? pricing.cache_creation_input_token_cost_above_200k_tokens * MILLION : void 0,
7669
- fastMultiplier: (_e = pricing.provider_specific_entry) == null ? void 0 : _e.fast,
7670
- inputCostPerMTokens: inputCostPerToken * MILLION,
7671
- inputCostPerMTokensAbove200K: pricing.input_cost_per_token_above_200k_tokens != null ? pricing.input_cost_per_token_above_200k_tokens * MILLION : void 0,
7672
- outputCostPerMTokens: outputCostPerToken * MILLION,
7673
- outputCostPerMTokensAbove200K: pricing.output_cost_per_token_above_200k_tokens != null ? pricing.output_cost_per_token_above_200k_tokens * MILLION : void 0
8139
+ durationEndAt: "",
8140
+ interactions: [],
8141
+ key: getSessionLookupKey(options.repository, options.sessionId),
8142
+ project: options.project,
8143
+ repository: options.repository,
8144
+ sessionId: options.sessionId,
8145
+ startedAt: options.startedAt,
8146
+ threadName: options.threadName
7674
8147
  };
7675
8148
  }
7676
- function createZeroPricing() {
7677
- return {
7678
- cachedInputCostPerMTokens: 0,
7679
- cacheCreationInputCostPerMTokens: 0,
7680
- inputCostPerMTokens: 0,
7681
- outputCostPerMTokens: 0
7682
- };
8149
+ function addFragmentInteraction(fragment, interaction) {
8150
+ fragment.interactions.push(interaction);
8151
+ if (!interaction.timestamp) {
8152
+ return;
8153
+ }
8154
+ if (!fragment.startedAt || Date.parse(interaction.timestamp) < Date.parse(fragment.startedAt)) {
8155
+ fragment.startedAt = interaction.timestamp;
8156
+ }
8157
+ if (!fragment.durationEndAt || Date.parse(interaction.timestamp) > Date.parse(fragment.durationEndAt)) {
8158
+ fragment.durationEndAt = interaction.timestamp;
8159
+ }
7683
8160
  }
7684
- function calculateTieredCost(tokens, baseCostPerMTokens, above200KCostPerMTokens) {
7685
- const safeTokens = Math.max(tokens != null ? tokens : 0, 0);
7686
- if (safeTokens === 0) {
7687
- return 0;
8161
+ function getSessionLookupKey(project, sessionId) {
8162
+ return `${project}:${sessionId}`;
8163
+ }
8164
+ function normalizeRole(value) {
8165
+ const normalized = value.toLowerCase();
8166
+ if (normalized.includes("user")) {
8167
+ return "user";
7688
8168
  }
7689
- if (safeTokens > 2e5 && above200KCostPerMTokens != null) {
7690
- return 2e5 / MILLION * baseCostPerMTokens + (safeTokens - 2e5) / MILLION * above200KCostPerMTokens;
8169
+ if (normalized.includes("assistant") || normalized.includes("agent") || normalized.includes("gemini")) {
8170
+ return "assistant";
7691
8171
  }
7692
- return safeTokens / MILLION * baseCostPerMTokens;
8172
+ if (normalized.includes("system")) {
8173
+ return "system";
8174
+ }
8175
+ if (normalized.includes("tool")) {
8176
+ return "tool";
8177
+ }
8178
+ if (normalized.includes("token") || normalized.includes("usage")) {
8179
+ return "usage";
8180
+ }
8181
+ return "unknown";
7693
8182
  }
7694
8183
 
7695
8184
  const ZERO_PRICING = {
@@ -7702,12 +8191,7 @@ async function createZeroPricingResolver() {
7702
8191
  return () => ZERO_PRICING;
7703
8192
  }
7704
8193
  function applyTotalUsageFallback(usage) {
7705
- const cacheCreationTokens = normalizeUsageNumber(usage.cacheCreationTokens);
7706
- const cacheReadTokens = normalizeUsageNumber(usage.cacheReadTokens);
7707
- const inputTokens = normalizeUsageNumber(usage.inputTokens);
7708
- const outputTokens = normalizeUsageNumber(usage.outputTokens);
7709
- const totalTokens = normalizeUsageNumber(usage.totalTokens);
7710
- const baseTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
8194
+ const { baseTokens, cacheCreationTokens, cacheReadTokens, inputTokens, outputTokens, totalTokens } = readUsageParts(usage);
7711
8195
  if (baseTokens === 0 && totalTokens > 0) {
7712
8196
  return {
7713
8197
  cacheCreationTokens,
@@ -7729,6 +8213,29 @@ function applyTotalUsageFallback(usage) {
7729
8213
  reasoningOutputTokens
7730
8214
  };
7731
8215
  }
8216
+ function applyTotalUsageAsExtra(usage) {
8217
+ const { baseTokens, cacheCreationTokens, cacheReadTokens, inputTokens, outputTokens, totalTokens } = readUsageParts(usage);
8218
+ if (baseTokens === 0 && totalTokens > 0) {
8219
+ return {
8220
+ cacheCreationTokens,
8221
+ cacheReadTokens,
8222
+ inputTokens,
8223
+ outputTokens: totalTokens,
8224
+ extraTotalTokens: 0
8225
+ };
8226
+ }
8227
+ const extraTotalTokens = Math.max(
8228
+ normalizeUsageNumber(usage.extraTotalTokens),
8229
+ totalTokens > baseTokens ? totalTokens - baseTokens : 0
8230
+ );
8231
+ return {
8232
+ cacheCreationTokens,
8233
+ cacheReadTokens,
8234
+ inputTokens,
8235
+ outputTokens,
8236
+ extraTotalTokens
8237
+ };
8238
+ }
7732
8239
  function toInteractionUsage(usage) {
7733
8240
  var _a;
7734
8241
  const cacheCreationTokens = normalizeUsageNumber(usage.cacheCreationTokens);
@@ -7736,13 +8243,15 @@ function toInteractionUsage(usage) {
7736
8243
  const inputTokens = normalizeUsageNumber(usage.inputTokens);
7737
8244
  const outputTokens = normalizeUsageNumber(usage.outputTokens);
7738
8245
  const reasoningOutputTokens = normalizeUsageNumber(usage.reasoningOutputTokens);
8246
+ const extraTotalTokens = normalizeUsageNumber(usage.extraTotalTokens);
7739
8247
  const toolTokens = normalizeUsageNumber(usage.toolTokens);
7740
- const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens + reasoningOutputTokens + toolTokens;
8248
+ const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens + reasoningOutputTokens + extraTotalTokens + toolTokens;
7741
8249
  return {
7742
8250
  cacheCreationTokens,
7743
8251
  cacheReadTokens,
7744
8252
  cachedInputTokens: cacheCreationTokens + cacheReadTokens,
7745
8253
  costUSD: (_a = usage.costUSD) != null ? _a : 0,
8254
+ extraTotalTokens: extraTotalTokens > 0 ? extraTotalTokens : void 0,
7746
8255
  inputTokens,
7747
8256
  isFallbackModel: usage.isFallbackModel,
7748
8257
  outputTokens,
@@ -7752,15 +8261,15 @@ function toInteractionUsage(usage) {
7752
8261
  };
7753
8262
  }
7754
8263
  function isZeroInteractionUsage(usage) {
7755
- var _a;
7756
- return usage.totalTokens <= 0 && usage.inputTokens <= 0 && usage.cachedInputTokens <= 0 && usage.outputTokens <= 0 && usage.reasoningOutputTokens <= 0 && ((_a = usage.toolTokens) != null ? _a : 0) <= 0;
8264
+ var _a, _b;
8265
+ return usage.totalTokens <= 0 && usage.inputTokens <= 0 && usage.cachedInputTokens <= 0 && usage.outputTokens <= 0 && usage.reasoningOutputTokens <= 0 && ((_a = usage.extraTotalTokens) != null ? _a : 0) <= 0 && ((_b = usage.toolTokens) != null ? _b : 0) <= 0;
7757
8266
  }
7758
8267
  function calculateUsageCostFromCandidates(usage, candidates, resolvePricing, options = {}) {
7759
- var _a, _b;
7760
- const outputTokens = usage.outputTokens + ((_a = usage.toolTokens) != null ? _a : 0) + (options.includeReasoningAsOutput === false ? 0 : usage.reasoningOutputTokens);
8268
+ var _a, _b, _c;
8269
+ const outputTokens = usage.outputTokens + (options.includeExtraTotalAsOutput === false ? 0 : (_a = usage.extraTotalTokens) != null ? _a : 0) + ((_b = usage.toolTokens) != null ? _b : 0) + (options.includeReasoningAsOutput === false ? 0 : usage.reasoningOutputTokens);
7761
8270
  for (const candidate of uniqueItems(candidates.map((candidate2) => candidate2.trim()).filter(Boolean))) {
7762
8271
  const costUSD = calculateUsageCostUSD({
7763
- cacheCreationTokens: (_b = usage.cacheCreationTokens) != null ? _b : 0,
8272
+ cacheCreationTokens: (_c = usage.cacheCreationTokens) != null ? _c : 0,
7764
8273
  cachedInputTokens: usage.cachedInputTokens,
7765
8274
  inputTokens: usage.inputTokens,
7766
8275
  outputTokens
@@ -7781,17 +8290,34 @@ function getFileModifiedAtIso(filePath) {
7781
8290
  function normalizeUsageNumber(value) {
7782
8291
  return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
7783
8292
  }
8293
+ function readUsageParts(usage) {
8294
+ const cacheCreationTokens = normalizeUsageNumber(usage.cacheCreationTokens);
8295
+ const cacheReadTokens = normalizeUsageNumber(usage.cacheReadTokens);
8296
+ const inputTokens = normalizeUsageNumber(usage.inputTokens);
8297
+ const outputTokens = normalizeUsageNumber(usage.outputTokens);
8298
+ const totalTokens = normalizeUsageNumber(usage.totalTokens);
8299
+ return {
8300
+ baseTokens: inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens,
8301
+ cacheCreationTokens,
8302
+ cacheReadTokens,
8303
+ inputTokens,
8304
+ outputTokens,
8305
+ totalTokens
8306
+ };
8307
+ }
7784
8308
 
7785
8309
  const ampUsageAdapter = {
7786
- createPricingResolver: createZeroPricingResolver,
8310
+ async createPricingResolver() {
8311
+ return createLiteLLMPricingResolver();
8312
+ },
7787
8313
  async discoverFiles(config) {
7788
8314
  const groups = await Promise.all(config.ampPaths.map((path) => glob(join(path, "threads", "**", "*.json"), {
7789
8315
  absolute: true
7790
8316
  }).catch(() => [])));
7791
8317
  return groups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "amp"));
7792
8318
  },
7793
- parseFile(filePath) {
7794
- var _a, _b, _c;
8319
+ parseFile(filePath, resolvePricing) {
8320
+ var _a, _b;
7795
8321
  const data = parseJsonFile(filePath);
7796
8322
  const record = normalizeUnknownRecord(data);
7797
8323
  const sessionId = normalizeStringValue(record == null ? void 0 : record.id) || basename$1(filePath, ".json");
@@ -7820,27 +8346,32 @@ const ampUsageAdapter = {
7820
8346
  const messageId = Number.isFinite(event.toMessageId) ? Number(event.toMessageId) : null;
7821
8347
  const [cacheCreationTokens = 0, cacheReadTokens = 0] = messageId != null ? (_b = cacheTokensByMessageId.get(messageId)) != null ? _b : [0, 0] : [0, 0];
7822
8348
  const usage = toInteractionUsage({
7823
- ...applyTotalUsageFallback({
8349
+ ...applyTotalUsageAsExtra({
7824
8350
  cacheCreationTokens,
7825
8351
  cacheReadTokens,
7826
8352
  inputTokens: getNumber$8(tokens.input),
7827
8353
  outputTokens: getNumber$8(tokens.output),
7828
8354
  totalTokens: getNumber$8(tokens.total)
7829
- }),
7830
- costUSD: (_c = normalizeFiniteNumberOrNull(event.credits)) != null ? _c : 0
8355
+ })
7831
8356
  });
7832
8357
  if (isZeroInteractionUsage(usage)) {
7833
8358
  continue;
7834
8359
  }
8360
+ const costUSD = calculateUsageCostFromCandidates(usage, [model], resolvePricing, {
8361
+ includeExtraTotalAsOutput: true
8362
+ });
7835
8363
  addFragmentInteraction(fragment, {
7836
8364
  content: "",
7837
- costUSD: usage.costUSD,
8365
+ costUSD,
7838
8366
  index,
7839
8367
  model,
7840
8368
  role: "usage",
7841
8369
  timestamp,
7842
8370
  type: "usage_ledger",
7843
- usage
8371
+ usage: toInteractionUsage({
8372
+ ...usage,
8373
+ costUSD
8374
+ })
7844
8375
  });
7845
8376
  }
7846
8377
  return fragment.interactions.length > 0 ? [fragment] : [];
@@ -7871,7 +8402,6 @@ function getNumber$8(value) {
7871
8402
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
7872
8403
  }
7873
8404
 
7874
- const CLAUDE_FALLBACK_MODEL = "claude-sonnet-4-5";
7875
8405
  const CODEX_FALLBACK_MODEL = "gpt-5";
7876
8406
  const GEMINI_FALLBACK_MODEL = "gemini-2.5-flash";
7877
8407
  const CLAUDE_MODEL_ALIASES = {
@@ -7929,11 +8459,11 @@ const GEMINI_FALLBACK_PRICING_TABLE = {
7929
8459
  }
7930
8460
  };
7931
8461
 
8462
+ const CLAUDE_CODE_CACHE_SIGNATURE = "claude-code-dedupe:message-id-v1";
7932
8463
  const claudeCodeUsageAdapter = {
7933
8464
  async createPricingResolver() {
7934
8465
  return createLiteLLMPricingResolver({
7935
8466
  aliases: CLAUDE_MODEL_ALIASES,
7936
- fallbackModel: CLAUDE_FALLBACK_MODEL,
7937
8467
  getLookupCandidates: getClaudeLookupCandidates
7938
8468
  });
7939
8469
  },
@@ -7949,7 +8479,7 @@ const claudeCodeUsageAdapter = {
7949
8479
  absolute: true
7950
8480
  }).catch(() => []);
7951
8481
  }));
7952
- return fileGroups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "claudeCode"));
8482
+ return fileGroups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "claudeCode", CLAUDE_CODE_CACHE_SIGNATURE));
7953
8483
  },
7954
8484
  parseFile(filePath, resolvePricing) {
7955
8485
  var _a, _b, _c, _d;
@@ -7959,6 +8489,9 @@ const claudeCodeUsageAdapter = {
7959
8489
  const fragments = /* @__PURE__ */ new Map();
7960
8490
  for (let index = 0; index < lines.length; index += 1) {
7961
8491
  const line = lines[index];
8492
+ if (!isSupportedClaudeUsageLine(line)) {
8493
+ continue;
8494
+ }
7962
8495
  const sessionId = normalizeStringValue(line.sessionId) || fallbackSessionId;
7963
8496
  const cwd = (_a = normalizeStringValue(line.cwd)) != null ? _a : "";
7964
8497
  const project = getProjectName(cwd, "") || decodeClaudeProjectPath(projectPath);
@@ -8032,7 +8565,7 @@ function getClaudeUniqueHash(line) {
8032
8565
  const message = normalizeUnknownRecord(line.message);
8033
8566
  const messageId = normalizeStringValue(message == null ? void 0 : message.id);
8034
8567
  const requestId = normalizeStringValue(line.requestId);
8035
- return messageId && requestId ? `${messageId}:${requestId}` : null;
8568
+ return messageId ? `${messageId}:${requestId != null ? requestId : ""}` : null;
8036
8569
  }
8037
8570
  function extractClaudeMessageText(content) {
8038
8571
  if (typeof content === "string") {
@@ -8050,6 +8583,31 @@ function getInteractionRole(line, message) {
8050
8583
  const role = normalizeStringValue(line.type) || normalizeStringValue(message == null ? void 0 : message.role) || normalizeStringValue(message == null ? void 0 : message.type) || "";
8051
8584
  return normalizeRole(role);
8052
8585
  }
8586
+ function isSupportedClaudeUsageLine(line) {
8587
+ const message = normalizeUnknownRecord(line.message);
8588
+ const usage = normalizeUnknownRecord(message == null ? void 0 : message.usage);
8589
+ if (hasUnsupportedClaudeNullField(line, message, usage)) {
8590
+ return false;
8591
+ }
8592
+ const version = normalizeStringValue(line.version);
8593
+ if (version && !/^\d+\.\d+\.\d+/u.test(version)) {
8594
+ return false;
8595
+ }
8596
+ return !hasEmptyClaudeField(line, message);
8597
+ }
8598
+ function hasUnsupportedClaudeNullField(line, message, usage) {
8599
+ return line.cwd === null || line.costUSD === null || line.version === null || line.sessionId === null || line.requestId === null || line.isApiErrorMessage === null || (message == null ? void 0 : message.id) === null || (message == null ? void 0 : message.model) === null || (usage == null ? void 0 : usage.speed) === null || (usage == null ? void 0 : usage.cache_read_input_tokens) === null || (usage == null ? void 0 : usage.cache_creation_input_tokens) === null;
8600
+ }
8601
+ function hasEmptyClaudeField(line, message) {
8602
+ const candidates = [
8603
+ line.sessionId,
8604
+ line.requestId,
8605
+ line.version,
8606
+ message == null ? void 0 : message.id,
8607
+ message == null ? void 0 : message.model
8608
+ ];
8609
+ return candidates.some((value) => typeof value === "string" && value.trim() === "");
8610
+ }
8053
8611
 
8054
8612
  const CODEBUFF_DEFAULT_MODEL = "codebuff-unknown";
8055
8613
  const codebuffUsageAdapter = {
@@ -8090,41 +8648,38 @@ const codebuffUsageAdapter = {
8090
8648
  }
8091
8649
  const model = extracted.model || CODEBUFF_DEFAULT_MODEL;
8092
8650
  const usage = toInteractionUsage({
8093
- ...applyTotalUsageFallback({
8651
+ ...applyTotalUsageAsExtra({
8094
8652
  cacheCreationTokens: extracted.cacheCreationTokens,
8095
8653
  cacheReadTokens: extracted.cacheReadTokens,
8654
+ extraTotalTokens: extracted.extraTotalTokens,
8096
8655
  inputTokens: extracted.inputTokens,
8097
8656
  outputTokens: extracted.outputTokens,
8098
8657
  totalTokens: extracted.totalTokens
8099
- }),
8100
- costUSD: extracted.credits > 0 ? extracted.credits : calculateUsageCostFromCandidates(
8101
- toInteractionUsage({
8102
- ...applyTotalUsageFallback({
8103
- cacheCreationTokens: extracted.cacheCreationTokens,
8104
- cacheReadTokens: extracted.cacheReadTokens,
8105
- inputTokens: extracted.inputTokens,
8106
- outputTokens: extracted.outputTokens,
8107
- totalTokens: extracted.totalTokens
8108
- })
8109
- }),
8110
- getCodebuffCandidates(model, inferCodebuffProvider(model)),
8111
- resolvePricing
8112
- )
8658
+ })
8113
8659
  });
8114
8660
  if (isZeroInteractionUsage(usage)) {
8115
8661
  continue;
8116
8662
  }
8663
+ const costUSD = calculateUsageCostFromCandidates(
8664
+ usage,
8665
+ getCodebuffCandidates(model, inferCodebuffProvider(model)),
8666
+ resolvePricing,
8667
+ { includeExtraTotalAsOutput: true, includeReasoningAsOutput: false }
8668
+ );
8117
8669
  const timestamp = (_a = getCodebuffMessageTimestamp(message)) != null ? _a : fallbackTimestamp;
8118
8670
  addFragmentInteraction(fragment, {
8119
8671
  content: "",
8120
- costUSD: usage.costUSD,
8672
+ costUSD,
8121
8673
  dedupeKey: getCodebuffDedupeKey(message, sessionId, timestamp != null ? timestamp : "", model, usage, index),
8122
8674
  index,
8123
8675
  model,
8124
8676
  role: "assistant",
8125
8677
  timestamp,
8126
8678
  type: normalizeStringValue(message.variant) || normalizeStringValue(message.role) || "assistant",
8127
- usage
8679
+ usage: toInteractionUsage({
8680
+ ...usage,
8681
+ costUSD
8682
+ })
8128
8683
  });
8129
8684
  }
8130
8685
  return fragment.interactions.length > 0 ? [fragment] : [];
@@ -8195,7 +8750,7 @@ function parseCodebuffUsageRecord(value) {
8195
8750
  if (!record) {
8196
8751
  return null;
8197
8752
  }
8198
- const raw = applyTotalUsageFallback({
8753
+ const raw = applyTotalUsageAsExtra({
8199
8754
  cacheCreationTokens: pickUsageNumber(record, ["cacheCreationInputTokens", "cache_creation_input_tokens", "cacheCreationTokens", "cache_creation_tokens", "cachedTokensCreated", "cached_tokens_created"]),
8200
8755
  cacheReadTokens: Math.max(
8201
8756
  pickUsageNumber(record, ["cacheReadInputTokens", "cache_read_input_tokens"]),
@@ -8210,10 +8765,11 @@ function parseCodebuffUsageRecord(value) {
8210
8765
  cacheCreationTokens: raw.cacheCreationTokens,
8211
8766
  cacheReadTokens: raw.cacheReadTokens,
8212
8767
  credits: getFiniteNumber(record.credits),
8768
+ extraTotalTokens: raw.extraTotalTokens,
8213
8769
  inputTokens: raw.inputTokens,
8214
8770
  model: (_a = normalizeStringValue(record.model)) != null ? _a : null,
8215
8771
  outputTokens: raw.outputTokens,
8216
- totalTokens: raw.inputTokens + raw.outputTokens + raw.cacheCreationTokens + raw.cacheReadTokens + raw.reasoningOutputTokens
8772
+ totalTokens: raw.inputTokens + raw.outputTokens + raw.cacheCreationTokens + raw.cacheReadTokens + raw.extraTotalTokens
8217
8773
  };
8218
8774
  }
8219
8775
  function emptyCodebuffUsage() {
@@ -8221,6 +8777,7 @@ function emptyCodebuffUsage() {
8221
8777
  cacheCreationTokens: 0,
8222
8778
  cacheReadTokens: 0,
8223
8779
  credits: 0,
8780
+ extraTotalTokens: 0,
8224
8781
  inputTokens: 0,
8225
8782
  model: null,
8226
8783
  outputTokens: 0,
@@ -8243,6 +8800,9 @@ function mergeCodebuffUsage(target, fallback) {
8243
8800
  if (target.cacheReadTokens === 0) {
8244
8801
  target.cacheReadTokens = fallback.cacheReadTokens;
8245
8802
  }
8803
+ if (target.extraTotalTokens === 0) {
8804
+ target.extraTotalTokens = fallback.extraTotalTokens;
8805
+ }
8246
8806
  if (target.totalTokens === 0) {
8247
8807
  target.totalTokens = fallback.totalTokens;
8248
8808
  }
@@ -8254,7 +8814,7 @@ function mergeCodebuffUsage(target, fallback) {
8254
8814
  }
8255
8815
  }
8256
8816
  function hasCodebuffSignal(usage) {
8257
- return usage.inputTokens > 0 || usage.outputTokens > 0 || usage.cacheCreationTokens > 0 || usage.cacheReadTokens > 0 || usage.totalTokens > 0 || usage.credits > 0;
8817
+ return usage.inputTokens > 0 || usage.outputTokens > 0 || usage.cacheCreationTokens > 0 || usage.cacheReadTokens > 0 || usage.extraTotalTokens > 0 || usage.totalTokens > 0 || usage.credits > 0;
8258
8818
  }
8259
8819
  function getCodebuffMessageTimestamp(message) {
8260
8820
  var _a;
@@ -8294,6 +8854,7 @@ function getCodebuffCandidates(model, provider) {
8294
8854
  return provider !== "unknown" && !model.startsWith(`${provider}/`) ? [model, `${provider}/${model}`] : [model];
8295
8855
  }
8296
8856
  function getCodebuffDedupeKey(message, sessionId, timestamp, model, usage, index) {
8857
+ var _a, _b, _c;
8297
8858
  const messageId = normalizeStringValue(message.id);
8298
8859
  if (messageId) {
8299
8860
  return `codebuff:${sessionId}:${messageId}`;
@@ -8306,8 +8867,9 @@ function getCodebuffDedupeKey(message, sessionId, timestamp, model, usage, index
8306
8867
  String(index),
8307
8868
  String(usage.inputTokens),
8308
8869
  String(usage.outputTokens),
8309
- String(usage.cachedInputTokens),
8310
- String(usage.reasoningOutputTokens)
8870
+ String((_a = usage.cacheReadTokens) != null ? _a : 0),
8871
+ String((_b = usage.cacheCreationTokens) != null ? _b : 0),
8872
+ String((_c = usage.extraTotalTokens) != null ? _c : 0)
8311
8873
  ].join(":");
8312
8874
  }
8313
8875
  function pickUsageNumber(record, keys) {
@@ -8321,11 +8883,12 @@ function getFiniteNumber(value) {
8321
8883
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
8322
8884
  }
8323
8885
 
8886
+ const CODEX_DEFAULT_FAST_MULTIPLIER = 2;
8887
+ const CODEX_SPEED_CACHE_PREFIX = "codex-speed:";
8324
8888
  const codexUsageAdapter = {
8325
8889
  async createPricingResolver() {
8326
8890
  return createLiteLLMPricingResolver({
8327
8891
  aliases: CODEX_MODEL_ALIASES,
8328
- fallbackModel: CODEX_FALLBACK_MODEL,
8329
8892
  isZeroCostModel: isOpenRouterFreeModel
8330
8893
  });
8331
8894
  },
@@ -8338,16 +8901,17 @@ const codexUsageAdapter = {
8338
8901
  absolute: true,
8339
8902
  cwd: sessionsDir
8340
8903
  });
8341
- return files.flatMap((filePath) => toDiscoveredUsageFile(filePath, "codex"));
8904
+ const cacheSignature = getCodexConfigSignature(config.codexPath);
8905
+ return files.flatMap((filePath) => toDiscoveredUsageFile(filePath, "codex", cacheSignature));
8342
8906
  },
8343
- parseFile(filePath, resolvePricing) {
8344
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
8907
+ parseFile(filePath, resolvePricing, file) {
8908
+ var _a, _b, _c, _d, _e, _f, _g, _h;
8345
8909
  const lines = parseJsonlFile(filePath);
8346
- const sessionMeta = (_a = lines.find((line) => line.type === "session_meta")) == null ? void 0 : _a.payload;
8910
+ const sessionMeta = normalizeUnknownRecord((_a = lines.find((line) => normalizeStringValue(line.type) === "session_meta")) == null ? void 0 : _a.payload);
8347
8911
  const sessionId = getSessionId(filePath, normalizeStringValue(sessionMeta == null ? void 0 : sessionMeta.id));
8348
- const startedAt = (_c = toIsoString(sessionMeta == null ? void 0 : sessionMeta.timestamp)) != null ? _c : toIsoString((_b = lines[0]) == null ? void 0 : _b.timestamp);
8912
+ const startedAt = (_c = (_b = getCodexTimestamp(sessionMeta)) != null ? _b : lines.map((line) => getCodexTimestamp(line)).find(Boolean)) != null ? _c : getFileModifiedAtIso(filePath);
8349
8913
  const project = getProjectName((_d = normalizeStringValue(sessionMeta == null ? void 0 : sessionMeta.cwd)) != null ? _d : "");
8350
- const repository = normalizeRepositoryUrl((_e = sessionMeta == null ? void 0 : sessionMeta.git) == null ? void 0 : _e.repository_url) || `local/${project}`;
8914
+ const repository = normalizeRepositoryUrl(normalizeStringValue((_e = normalizeUnknownRecord(sessionMeta == null ? void 0 : sessionMeta.git)) == null ? void 0 : _e.repository_url)) || `local/${project}`;
8351
8915
  const fragment = createSessionFragment({
8352
8916
  project,
8353
8917
  repository,
@@ -8355,26 +8919,28 @@ const codexUsageAdapter = {
8355
8919
  startedAt,
8356
8920
  threadName: `Session for ${project}`
8357
8921
  });
8922
+ const speed = getCodexSpeedFromSignature(file.cacheSignature);
8358
8923
  let previousTotals = null;
8359
8924
  let currentModel;
8360
8925
  let currentModelIsFallback = false;
8361
8926
  for (let index = 0; index < lines.length; index += 1) {
8362
8927
  const line = lines[index];
8363
- if (line.type === "turn_context") {
8364
- const contextModel = extractModelName(line.payload);
8928
+ const payload = normalizeUnknownRecord(line.payload);
8929
+ if (normalizeStringValue(line.type) === "turn_context") {
8930
+ const contextModel = extractModelName(payload);
8365
8931
  if (contextModel) {
8366
8932
  currentModel = contextModel;
8367
8933
  currentModelIsFallback = false;
8368
8934
  }
8369
8935
  }
8370
- const timestamp = (_g = toIsoString(line.timestamp)) != null ? _g : toIsoString((_f = line.payload) == null ? void 0 : _f.timestamp);
8371
- const extractedModel = extractModelName(line.payload);
8936
+ const timestamp = (_f = getCodexTimestamp(line)) != null ? _f : getFileModifiedAtIso(filePath);
8937
+ const extractedModel = extractCodexModel(line);
8372
8938
  if (extractedModel) {
8373
8939
  currentModel = extractedModel;
8374
8940
  currentModelIsFallback = false;
8375
8941
  }
8376
8942
  const rawUsage = getCodexRawUsage(line, previousTotals);
8377
- const totalUsage = normalizeRawUsage((_i = (_h = line.payload) == null ? void 0 : _h.info) == null ? void 0 : _i.total_token_usage);
8943
+ const totalUsage = normalizeRawUsage((_g = normalizeUnknownRecord(payload == null ? void 0 : payload.info)) == null ? void 0 : _g.total_token_usage);
8378
8944
  if (totalUsage) {
8379
8945
  previousTotals = totalUsage;
8380
8946
  }
@@ -8388,65 +8954,234 @@ const codexUsageAdapter = {
8388
8954
  } else if (!extractedModel && currentModelIsFallback) {
8389
8955
  isFallbackModel = true;
8390
8956
  }
8391
- const usage = rawUsage ? getCodexInteractionUsage(rawUsage, model != null ? model : CODEX_FALLBACK_MODEL, resolvePricing) : null;
8957
+ const usage = rawUsage ? getCodexInteractionUsage(rawUsage, model != null ? model : CODEX_FALLBACK_MODEL, resolvePricing, speed) : null;
8392
8958
  addFragmentInteraction(fragment, {
8393
8959
  content: extractCodexContent(line),
8394
- costUSD: (_j = usage == null ? void 0 : usage.costUSD) != null ? _j : 0,
8960
+ costUSD: (_h = usage == null ? void 0 : usage.costUSD) != null ? _h : 0,
8961
+ dedupeKey: usage && timestamp ? getCodexDedupeKey(timestamp, model != null ? model : CODEX_FALLBACK_MODEL, usage) : null,
8395
8962
  index,
8396
8963
  model: model != null ? model : null,
8397
- role: getCodexRole(line),
8964
+ role: getCodexRole(line, rawUsage !== null),
8398
8965
  timestamp,
8399
- type: (_m = (_l = (_k = line.payload) == null ? void 0 : _k.type) != null ? _l : line.type) != null ? _m : "event",
8966
+ type: normalizeStringValue(payload == null ? void 0 : payload.type) || normalizeStringValue(line.type) || "event",
8400
8967
  usage: usage ? { ...usage, isFallbackModel } : null
8401
8968
  });
8402
8969
  }
8403
8970
  return [fragment];
8404
8971
  },
8405
8972
  watchPatterns(config) {
8406
- return [join(config.codexPath, "sessions", "**", "*.jsonl")];
8973
+ return [
8974
+ join(config.codexPath, "config.toml"),
8975
+ join(config.codexPath, "sessions", "**", "*.jsonl")
8976
+ ];
8407
8977
  }
8408
8978
  };
8409
8979
  function getCodexRawUsage(line, previousTotals) {
8410
- var _a;
8411
- if (line.type !== "event_msg" || ((_a = line.payload) == null ? void 0 : _a.type) !== "token_count") {
8412
- return null;
8980
+ const payload = normalizeUnknownRecord(line.payload);
8981
+ if (normalizeStringValue(line.type) === "event_msg" && normalizeStringValue(payload == null ? void 0 : payload.type) === "token_count") {
8982
+ const info = normalizeUnknownRecord(payload == null ? void 0 : payload.info);
8983
+ const lastUsage = normalizeRawUsage(info == null ? void 0 : info.last_token_usage);
8984
+ const totalUsage = normalizeRawUsage(info == null ? void 0 : info.total_token_usage);
8985
+ return lastUsage != null ? lastUsage : totalUsage ? subtractRawUsage(totalUsage, previousTotals) : null;
8413
8986
  }
8414
- const info = line.payload.info;
8415
- const lastUsage = normalizeRawUsage(info == null ? void 0 : info.last_token_usage);
8416
- const totalUsage = normalizeRawUsage(info == null ? void 0 : info.total_token_usage);
8417
- return lastUsage != null ? lastUsage : totalUsage ? subtractRawUsage(totalUsage, previousTotals) : null;
8987
+ return getHeadlessCodexRawUsage(line);
8418
8988
  }
8419
- function getCodexInteractionUsage(rawUsage, model, resolvePricing) {
8989
+ function getCodexInteractionUsage(rawUsage, model, resolvePricing, speed) {
8420
8990
  const usage = convertCodexRawUsage(rawUsage);
8421
8991
  if (isZeroUsage(usage)) {
8422
8992
  return null;
8423
8993
  }
8424
8994
  return {
8425
8995
  ...usage,
8426
- costUSD: calculateUsageCostUSD(usage, resolvePricing(model))
8996
+ costUSD: calculateUsageCostUSD(usage, resolvePricing(model), {
8997
+ defaultFastMultiplier: CODEX_DEFAULT_FAST_MULTIPLIER,
8998
+ speed
8999
+ })
8427
9000
  };
8428
9001
  }
8429
- function extractCodexContent(line) {
8430
- const payload = line.payload;
8431
- if (!payload) {
8432
- return "";
9002
+ function getCodexConfigSignature(codexPath) {
9003
+ return `${CODEX_SPEED_CACHE_PREFIX}${readCodexConfigSpeed(getCodexConfigPath(codexPath))}`;
9004
+ }
9005
+ function getCodexConfigPath(codexPath) {
9006
+ return join(codexPath, "config.toml");
9007
+ }
9008
+ function readCodexConfigSpeed(configPath) {
9009
+ var _a;
9010
+ try {
9011
+ return (_a = parseCodexConfigSpeed(readFileSync(configPath, "utf8"))) != null ? _a : "standard";
9012
+ } catch {
9013
+ return "standard";
9014
+ }
9015
+ }
9016
+ function getCodexSpeedFromSignature(cacheSignature) {
9017
+ return cacheSignature === `${CODEX_SPEED_CACHE_PREFIX}fast` ? "fast" : "standard";
9018
+ }
9019
+ const CODEX_CONFIG_ASSIGNMENT_REGEX = /^\s*([a-z_][\w-]*)\s*=\s*["']([^"']+)["']\s*(?:#.*)?$/i;
9020
+ const CODEX_CONFIG_SECTION_REGEX = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/;
9021
+ function parseCodexConfigSpeed(content) {
9022
+ var _a;
9023
+ let activeProfile;
9024
+ let currentSection = null;
9025
+ let topLevelSpeed;
9026
+ const profileSpeeds = /* @__PURE__ */ new Map();
9027
+ for (const rawLine of content.split("\n")) {
9028
+ const sectionMatch = rawLine.match(CODEX_CONFIG_SECTION_REGEX);
9029
+ if (sectionMatch) {
9030
+ currentSection = sectionMatch[1].trim();
9031
+ continue;
9032
+ }
9033
+ const match = rawLine.match(CODEX_CONFIG_ASSIGNMENT_REGEX);
9034
+ if (!match) {
9035
+ continue;
9036
+ }
9037
+ const key = match[1];
9038
+ const value = match[2].trim();
9039
+ if (!currentSection) {
9040
+ if (key === "profile") {
9041
+ activeProfile = value;
9042
+ } else if (key === "service_tier") {
9043
+ topLevelSpeed = toCodexSpeed(value);
9044
+ }
9045
+ continue;
9046
+ }
9047
+ const profileName = getCodexProfileName(currentSection);
9048
+ if (profileName && key === "service_tier") {
9049
+ profileSpeeds.set(profileName, toCodexSpeed(value));
9050
+ }
9051
+ }
9052
+ return (_a = activeProfile ? profileSpeeds.get(activeProfile) : void 0) != null ? _a : topLevelSpeed;
9053
+ }
9054
+ function getCodexProfileName(section) {
9055
+ if (!section.startsWith("profiles.")) {
9056
+ return null;
8433
9057
  }
8434
- const message = payload.message;
9058
+ return stripTomlQuotes(section.slice("profiles.".length).trim());
9059
+ }
9060
+ function stripTomlQuotes(value) {
9061
+ const quote = value[0];
9062
+ return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
9063
+ }
9064
+ function toCodexSpeed(value) {
9065
+ const normalized = value.trim().toLowerCase();
9066
+ return normalized === "priority" || normalized === "fast" ? "fast" : "standard";
9067
+ }
9068
+ function extractCodexContent(line) {
9069
+ const payload = normalizeUnknownRecord(line.payload);
9070
+ const data = normalizeUnknownRecord(line.data);
9071
+ const result = normalizeUnknownRecord(line.result);
9072
+ const response = normalizeUnknownRecord(line.response);
9073
+ const message = payload == null ? void 0 : payload.message;
8435
9074
  if (typeof message === "string") {
8436
9075
  return message;
8437
9076
  }
8438
- return normalizeStringValue(payload.text) || normalizeStringValue(payload.output) || normalizeStringValue(payload.content) || "";
9077
+ return normalizeStringValue(payload == null ? void 0 : payload.text) || normalizeStringValue(payload == null ? void 0 : payload.output) || normalizeStringValue(payload == null ? void 0 : payload.content) || normalizeStringValue(line.content) || normalizeStringValue(data == null ? void 0 : data.content) || normalizeStringValue(result == null ? void 0 : result.content) || normalizeStringValue(response == null ? void 0 : response.content) || "";
8439
9078
  }
8440
- function getCodexRole(line) {
8441
- var _a, _b, _c;
8442
- const type = (_c = (_b = (_a = line.payload) == null ? void 0 : _a.type) != null ? _b : line.type) != null ? _c : "";
8443
- if (type === "token_count") {
9079
+ function getCodexRole(line, hasUsage) {
9080
+ var _a;
9081
+ const type = normalizeStringValue((_a = normalizeUnknownRecord(line.payload)) == null ? void 0 : _a.type) || normalizeStringValue(line.type) || "";
9082
+ if (type === "token_count" || hasUsage) {
8444
9083
  return "usage";
8445
9084
  }
8446
9085
  return normalizeRole(type);
8447
9086
  }
8448
- function getSessionId(filePath, sessionMetaId) {
8449
- return (sessionMetaId == null ? void 0 : sessionMetaId.trim()) || basename$1(filePath, ".jsonl");
9087
+ function getSessionId(filePath, sessionMetaId) {
9088
+ return (sessionMetaId == null ? void 0 : sessionMetaId.trim()) || basename$1(filePath, ".jsonl");
9089
+ }
9090
+ function getHeadlessCodexRawUsage(line) {
9091
+ const usage = getHeadlessUsageRecord(line);
9092
+ if (!usage) {
9093
+ return null;
9094
+ }
9095
+ const normalizedUsage = readHeadlessCodexUsage(usage);
9096
+ return normalizedUsage && normalizedUsage.total_tokens > 0 ? normalizedUsage : normalizedUsage && (normalizedUsage.input_tokens > 0 || normalizedUsage.cached_input_tokens > 0 || normalizedUsage.output_tokens > 0 || normalizedUsage.reasoning_output_tokens > 0) ? normalizedUsage : null;
9097
+ }
9098
+ function getHeadlessUsageRecord(line) {
9099
+ var _a, _b, _c, _d;
9100
+ const candidates = [
9101
+ normalizeUnknownRecord(line.usage),
9102
+ normalizeUnknownRecord((_a = normalizeUnknownRecord(line.data)) == null ? void 0 : _a.usage),
9103
+ normalizeUnknownRecord((_b = normalizeUnknownRecord(line.result)) == null ? void 0 : _b.usage),
9104
+ normalizeUnknownRecord((_c = normalizeUnknownRecord(line.response)) == null ? void 0 : _c.usage)
9105
+ ];
9106
+ return (_d = candidates.find(Boolean)) != null ? _d : null;
9107
+ }
9108
+ function readHeadlessCodexUsage(usage) {
9109
+ var _a, _b, _c, _d, _e, _f, _g, _h;
9110
+ const hasStandardUsageField = [
9111
+ "input_tokens",
9112
+ "cached_input_tokens",
9113
+ "cache_read_input_tokens",
9114
+ "output_tokens",
9115
+ "reasoning_output_tokens",
9116
+ "total_tokens"
9117
+ ].some((key) => usage[key] !== void 0 && usage[key] !== null);
9118
+ if (hasStandardUsageField) {
9119
+ const normalized = normalizeRawUsage(usage);
9120
+ if (normalized) {
9121
+ return normalized;
9122
+ }
9123
+ }
9124
+ const inputTokens = Math.max(
9125
+ 0,
9126
+ Math.trunc((_b = (_a = normalizeFiniteNumberOrNull(usage.input_tokens)) != null ? _a : normalizeFiniteNumberOrNull(usage.prompt_tokens)) != null ? _b : 0)
9127
+ );
9128
+ const cachedInputTokens = Math.max(
9129
+ 0,
9130
+ Math.trunc((_d = (_c = normalizeFiniteNumberOrNull(usage.cached_input_tokens)) != null ? _c : normalizeFiniteNumberOrNull(usage.cached_tokens)) != null ? _d : 0)
9131
+ );
9132
+ const outputTokens = Math.max(
9133
+ 0,
9134
+ Math.trunc((_f = (_e = normalizeFiniteNumberOrNull(usage.output_tokens)) != null ? _e : normalizeFiniteNumberOrNull(usage.completion_tokens)) != null ? _f : 0)
9135
+ );
9136
+ const reasoningOutputTokens = Math.max(0, Math.trunc((_g = normalizeFiniteNumberOrNull(usage.reasoning_output_tokens)) != null ? _g : 0));
9137
+ const totalTokens = Math.max(0, Math.trunc((_h = normalizeFiniteNumberOrNull(usage.total_tokens)) != null ? _h : 0));
9138
+ return {
9139
+ cached_input_tokens: cachedInputTokens,
9140
+ input_tokens: inputTokens,
9141
+ output_tokens: outputTokens,
9142
+ reasoning_output_tokens: reasoningOutputTokens,
9143
+ total_tokens: totalTokens > 0 ? totalTokens : inputTokens + outputTokens + reasoningOutputTokens
9144
+ };
9145
+ }
9146
+ function extractCodexModel(line) {
9147
+ const payload = normalizeUnknownRecord(line.payload);
9148
+ const candidates = [
9149
+ payload,
9150
+ line,
9151
+ normalizeUnknownRecord(line.data),
9152
+ normalizeUnknownRecord(line.result),
9153
+ normalizeUnknownRecord(line.response)
9154
+ ];
9155
+ for (const candidate of candidates) {
9156
+ const model = extractModelName(candidate);
9157
+ if (model) {
9158
+ return model;
9159
+ }
9160
+ }
9161
+ return void 0;
9162
+ }
9163
+ function getCodexTimestamp(line) {
9164
+ if (!line) {
9165
+ return null;
9166
+ }
9167
+ const data = normalizeUnknownRecord(line.data);
9168
+ const result = normalizeUnknownRecord(line.result);
9169
+ const response = normalizeUnknownRecord(line.response);
9170
+ const payload = normalizeUnknownRecord(line.payload);
9171
+ return toIsoString(line.timestamp) || toIsoString(line.created_at) || toIsoString(line.createdAt) || toIsoString(payload == null ? void 0 : payload.timestamp) || toIsoString(data == null ? void 0 : data.timestamp) || toIsoString(data == null ? void 0 : data.created_at) || toIsoString(data == null ? void 0 : data.createdAt) || toIsoString(result == null ? void 0 : result.timestamp) || toIsoString(result == null ? void 0 : result.created_at) || toIsoString(result == null ? void 0 : result.createdAt) || toIsoString(response == null ? void 0 : response.timestamp) || toIsoString(response == null ? void 0 : response.created_at) || toIsoString(response == null ? void 0 : response.createdAt);
9172
+ }
9173
+ function getCodexDedupeKey(timestamp, model, usage) {
9174
+ const rawInputTokens = usage.inputTokens + usage.cachedInputTokens;
9175
+ return [
9176
+ "codex",
9177
+ timestamp,
9178
+ model,
9179
+ String(rawInputTokens),
9180
+ String(usage.cachedInputTokens),
9181
+ String(usage.outputTokens),
9182
+ String(usage.reasoningOutputTokens),
9183
+ String(usage.totalTokens)
9184
+ ].join(":");
8450
9185
  }
8451
9186
 
8452
9187
  const COPILOT_MODEL_ATTRS = ["gen_ai.response.model", "gen_ai.request.model"];
@@ -8484,7 +9219,10 @@ const copilotUsageAdapter = {
8484
9219
  const selectedCandidates = filterCopilotCandidates(candidates);
8485
9220
  const fragments = /* @__PURE__ */ new Map();
8486
9221
  for (const candidate of selectedCandidates) {
8487
- const costUSD = calculateUsageCostFromCandidates(candidate.usage, [candidate.model], resolvePricing);
9222
+ const costUSD = calculateUsageCostFromCandidates(candidate.usage, [candidate.model], resolvePricing, {
9223
+ includeExtraTotalAsOutput: true,
9224
+ includeReasoningAsOutput: false
9225
+ });
8488
9226
  const fragment = (_a = fragments.get(candidate.sessionId)) != null ? _a : createSessionFragment({
8489
9227
  project: "copilot",
8490
9228
  repository: "local/copilot",
@@ -8551,21 +9289,22 @@ function getCopilotCandidate(record, index, fallbackTimestamp, traceContexts) {
8551
9289
  if (!source) {
8552
9290
  return null;
8553
9291
  }
8554
- const rawUsage = applyTotalUsageFallback({
8555
- cacheCreationTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.cache_write.input_tokens", "gen_ai.usage.cache_creation.input_tokens"]),
8556
- cacheReadTokens: getCopilotAttributeNumber(attributes, "gen_ai.usage.cache_read.input_tokens"),
8557
- inputTokens: Math.max(
8558
- 0,
8559
- getCopilotAttributeNumber(attributes, "gen_ai.usage.input_tokens") - Math.min(
8560
- getCopilotAttributeNumber(attributes, "gen_ai.usage.input_tokens"),
8561
- getCopilotAttributeNumber(attributes, "gen_ai.usage.cache_read.input_tokens")
8562
- )
8563
- ),
8564
- outputTokens: getCopilotAttributeNumber(attributes, "gen_ai.usage.output_tokens"),
8565
- reasoningOutputTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.reasoning.output_tokens", "gen_ai.usage.reasoning_tokens"]),
8566
- totalTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.total_tokens", "gen_ai.usage.total.token_count"])
9292
+ const usage = toInteractionUsage({
9293
+ ...applyTotalUsageAsExtra({
9294
+ cacheCreationTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.cache_write.input_tokens", "gen_ai.usage.cache_creation.input_tokens"]),
9295
+ cacheReadTokens: getCopilotAttributeNumber(attributes, "gen_ai.usage.cache_read.input_tokens"),
9296
+ extraTotalTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.reasoning.output_tokens", "gen_ai.usage.reasoning_tokens"]),
9297
+ inputTokens: Math.max(
9298
+ 0,
9299
+ getCopilotAttributeNumber(attributes, "gen_ai.usage.input_tokens") - Math.min(
9300
+ getCopilotAttributeNumber(attributes, "gen_ai.usage.input_tokens"),
9301
+ getCopilotAttributeNumber(attributes, "gen_ai.usage.cache_read.input_tokens")
9302
+ )
9303
+ ),
9304
+ outputTokens: getCopilotAttributeNumber(attributes, "gen_ai.usage.output_tokens"),
9305
+ totalTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.total_tokens", "gen_ai.usage.total.token_count"])
9306
+ })
8567
9307
  });
8568
- const usage = toInteractionUsage(rawUsage);
8569
9308
  if (isZeroInteractionUsage(usage)) {
8570
9309
  return null;
8571
9310
  }
@@ -8583,7 +9322,6 @@ function getCopilotCandidate(record, index, fallbackTimestamp, traceContexts) {
8583
9322
  inputTokens: usage.inputTokens,
8584
9323
  model,
8585
9324
  outputTokens: usage.outputTokens,
8586
- reasoningOutputTokens: usage.reasoningOutputTokens,
8587
9325
  responseId,
8588
9326
  sessionId,
8589
9327
  source,
@@ -8710,7 +9448,7 @@ const droidUsageAdapter = {
8710
9448
  const groups = await Promise.all(config.droidPaths.map((path) => glob(join(path, "**", "*.settings.json"), {
8711
9449
  absolute: true
8712
9450
  }).catch(() => [])));
8713
- return groups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "droid"));
9451
+ return selectLatestDroidSettingsFiles(groups.flat()).flatMap((filePath) => toDiscoveredUsageFile(filePath, "droid"));
8714
9452
  },
8715
9453
  parseFile(filePath, resolvePricing) {
8716
9454
  var _a, _b;
@@ -8720,15 +9458,16 @@ const droidUsageAdapter = {
8720
9458
  if (!settings || !tokenUsage) {
8721
9459
  return [];
8722
9460
  }
9461
+ const extraTotalTokens = getNumber$7(tokenUsage.thinkingTokens);
8723
9462
  const usage = toInteractionUsage({
8724
9463
  ...applyTotalUsageFallback({
8725
9464
  cacheCreationTokens: getNumber$7(tokenUsage.cacheCreationTokens),
8726
9465
  cacheReadTokens: getNumber$7(tokenUsage.cacheReadTokens),
8727
9466
  inputTokens: getNumber$7(tokenUsage.inputTokens),
8728
9467
  outputTokens: getNumber$7(tokenUsage.outputTokens),
8729
- reasoningOutputTokens: getNumber$7(tokenUsage.thinkingTokens),
8730
- totalTokens: getNumber$7(tokenUsage.totalTokens)
8731
- })
9468
+ totalTokens: Math.max(getNumber$7(tokenUsage.totalTokens) - extraTotalTokens, 0)
9469
+ }),
9470
+ extraTotalTokens
8732
9471
  });
8733
9472
  if (isZeroInteractionUsage(usage)) {
8734
9473
  return [];
@@ -8876,12 +9615,29 @@ function extractDroidModelFromSidecar(settingsPath) {
8876
9615
  function getNumber$7(value) {
8877
9616
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
8878
9617
  }
9618
+ function selectLatestDroidSettingsFiles(filePaths) {
9619
+ const latestBySession = /* @__PURE__ */ new Map();
9620
+ for (const filePath of filePaths) {
9621
+ const sessionId = basename$1(filePath, ".settings.json");
9622
+ const timestamp = getDroidSnapshotTimestamp(filePath);
9623
+ const current = latestBySession.get(sessionId);
9624
+ if (!current || timestamp > current.timestamp) {
9625
+ latestBySession.set(sessionId, { filePath, timestamp });
9626
+ }
9627
+ }
9628
+ return Array.from(latestBySession.values()).map((item) => item.filePath).sort((a, b) => a.localeCompare(b));
9629
+ }
9630
+ function getDroidSnapshotTimestamp(filePath) {
9631
+ var _a;
9632
+ const settings = normalizeUnknownRecord(parseJsonFile(filePath));
9633
+ const timestamp = toIsoString(settings == null ? void 0 : settings.providerLockTimestamp);
9634
+ return timestamp ? Date.parse(timestamp) : Date.parse((_a = getFileModifiedAtIso(filePath)) != null ? _a : "") || 0;
9635
+ }
8879
9636
 
8880
9637
  const geminiUsageAdapter = {
8881
9638
  async createPricingResolver() {
8882
9639
  return createLiteLLMPricingResolver({
8883
9640
  aliases: GEMINI_MODEL_ALIASES,
8884
- fallbackModel: GEMINI_FALLBACK_MODEL,
8885
9641
  fallbackPricingTable: GEMINI_FALLBACK_PRICING_TABLE,
8886
9642
  getLookupCandidates: getGeminiLookupCandidates
8887
9643
  });
@@ -8944,20 +9700,25 @@ const geminiUsageAdapter = {
8944
9700
  }
8945
9701
  };
8946
9702
  function getGeminiInteractionUsage(tokens, model, resolvePricing) {
8947
- const usage = convertGeminiTokenUsage(tokens);
9703
+ const baseUsage = convertGeminiTokenUsage(tokens);
9704
+ const extraTotalTokens = normalizeNumber(tokens.thoughts);
9705
+ const usage = {
9706
+ ...baseUsage,
9707
+ inputTokens: baseUsage.inputTokens + normalizeNumber(tokens.tool),
9708
+ reasoningOutputTokens: 0
9709
+ };
8948
9710
  if (isZeroUsage(usage)) {
8949
9711
  return null;
8950
9712
  }
8951
- const toolTokens = normalizeNumber(tokens.tool);
8952
9713
  const costUSD = calculateUsageCostUSD({
8953
9714
  cachedInputTokens: usage.cachedInputTokens,
8954
9715
  inputTokens: usage.inputTokens,
8955
- outputTokens: usage.outputTokens + usage.reasoningOutputTokens + toolTokens
9716
+ outputTokens: usage.outputTokens
8956
9717
  }, resolvePricing(model));
8957
9718
  return {
8958
9719
  ...usage,
8959
9720
  costUSD,
8960
- toolTokens
9721
+ extraTotalTokens
8961
9722
  };
8962
9723
  }
8963
9724
  function getGeminiRole(message) {
@@ -9022,7 +9783,7 @@ const gooseUsageAdapter = {
9022
9783
  addFragmentInteraction(fragment, {
9023
9784
  content: "",
9024
9785
  costUSD: entry.usage.costUSD,
9025
- dedupeKey: `goose:${entry.sessionId}`,
9786
+ dedupeKey: `goose:${filePath}:${entry.sessionId}`,
9026
9787
  index: 0,
9027
9788
  model: entry.model,
9028
9789
  role: "usage",
@@ -9053,11 +9814,11 @@ function parseGooseRow(row, resolvePricing) {
9053
9814
  const inputTokens = getNumber$6(row.accumulated_input_tokens) || getNumber$6(row.input_tokens);
9054
9815
  const outputTokens = getNumber$6(row.accumulated_output_tokens) || getNumber$6(row.output_tokens);
9055
9816
  const totalTokens = getNumber$6(row.accumulated_total_tokens) || getNumber$6(row.total_tokens) || inputTokens + outputTokens;
9056
- const reasoningOutputTokens = Math.max(0, totalTokens - inputTokens - outputTokens);
9817
+ const extraTotalTokens = Math.max(0, totalTokens - inputTokens - outputTokens);
9057
9818
  const usage = toInteractionUsage({
9819
+ extraTotalTokens,
9058
9820
  inputTokens,
9059
- outputTokens,
9060
- reasoningOutputTokens
9821
+ outputTokens
9061
9822
  });
9062
9823
  if (isZeroInteractionUsage(usage)) {
9063
9824
  return null;
@@ -9175,9 +9936,9 @@ function parseHermesRow(row, resolvePricing) {
9175
9936
  const usage = toInteractionUsage({
9176
9937
  cacheCreationTokens: toNumber(row.cache_write_tokens),
9177
9938
  cacheReadTokens: toNumber(row.cache_read_tokens),
9939
+ extraTotalTokens: toNumber(row.reasoning_tokens),
9178
9940
  inputTokens: toNumber(row.input_tokens),
9179
- outputTokens: toNumber(row.output_tokens),
9180
- reasoningOutputTokens: toNumber(row.reasoning_tokens)
9941
+ outputTokens: toNumber(row.output_tokens)
9181
9942
  });
9182
9943
  if (isZeroInteractionUsage(usage) && !toOptionalNumber(row.actual_cost_usd) && !toOptionalNumber(row.estimated_cost_usd)) {
9183
9944
  return null;
@@ -9302,15 +10063,16 @@ function parseKiloMessage(value, row, filePath, resolvePricing) {
9302
10063
  if (!tokens || !model || !timestamp) {
9303
10064
  return null;
9304
10065
  }
10066
+ const extraTotalTokens = getNumber$5(tokens.reasoning);
9305
10067
  const usage = toInteractionUsage({
9306
10068
  ...applyTotalUsageFallback({
9307
10069
  cacheCreationTokens: getNumber$5((_b = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _b.write),
9308
10070
  cacheReadTokens: getNumber$5((_c = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _c.read),
9309
10071
  inputTokens: getNumber$5(tokens.input),
9310
10072
  outputTokens: getNumber$5(tokens.output),
9311
- reasoningOutputTokens: getNumber$5(tokens.reasoning),
9312
- totalTokens: getNumber$5(tokens.total)
9313
- })
10073
+ totalTokens: Math.max(getNumber$5(tokens.total) - extraTotalTokens, 0)
10074
+ }),
10075
+ extraTotalTokens
9314
10076
  });
9315
10077
  if (isZeroInteractionUsage(usage)) {
9316
10078
  return null;
@@ -9359,7 +10121,7 @@ const kimiUsageAdapter = {
9359
10121
  return groups.flat().filter((filePath) => isKimiWireFile(join(dirname$1(dirname$1(dirname$1(filePath))), "sessions"), filePath)).flatMap((filePath) => toDiscoveredUsageFile(filePath, "kimi"));
9360
10122
  },
9361
10123
  parseFile(filePath, resolvePricing) {
9362
- var _a;
10124
+ var _a, _b, _c, _d;
9363
10125
  const sessionId = getKimiSessionId(filePath);
9364
10126
  const model = getKimiModel(filePath);
9365
10127
  const fallbackTimestamp = getFileModifiedAtIso(filePath);
@@ -9380,7 +10142,7 @@ const kimiUsageAdapter = {
9380
10142
  continue;
9381
10143
  }
9382
10144
  const usage = toInteractionUsage({
9383
- ...applyTotalUsageFallback({
10145
+ ...applyTotalUsageAsExtra({
9384
10146
  cacheCreationTokens: getNumber$4(tokenUsage.input_cache_creation),
9385
10147
  cacheReadTokens: getNumber$4(tokenUsage.input_cache_read),
9386
10148
  inputTokens: getNumber$4(tokenUsage.input_other),
@@ -9401,7 +10163,11 @@ const kimiUsageAdapter = {
9401
10163
  normalizeStringValue(payload.message_id) || "",
9402
10164
  timestamp || "",
9403
10165
  model,
9404
- String(usage.totalTokens)
10166
+ String(usage.inputTokens),
10167
+ String(usage.outputTokens),
10168
+ String((_b = usage.cacheCreationTokens) != null ? _b : 0),
10169
+ String((_c = usage.cacheReadTokens) != null ? _c : 0),
10170
+ String((_d = usage.extraTotalTokens) != null ? _d : 0)
9405
10171
  ].join(":"),
9406
10172
  index,
9407
10173
  model,
@@ -9457,7 +10223,7 @@ const openClawUsageAdapter = {
9457
10223
  return groups.flat().filter((filePath) => isOpenClawSessionFile(basename$1(filePath))).flatMap((filePath) => toDiscoveredUsageFile(filePath, "openclaw"));
9458
10224
  },
9459
10225
  parseFile(filePath) {
9460
- var _a, _b, _c, _d, _e;
10226
+ var _a, _b, _c, _d, _e, _f, _g, _h;
9461
10227
  const lines = readFileSync(filePath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
9462
10228
  const sessionId = getOpenClawSessionId(filePath);
9463
10229
  const fragment = createSessionFragment({
@@ -9491,7 +10257,7 @@ const openClawUsageAdapter = {
9491
10257
  continue;
9492
10258
  }
9493
10259
  const usage = toInteractionUsage({
9494
- ...applyTotalUsageFallback({
10260
+ ...applyTotalUsageAsExtra({
9495
10261
  cacheCreationTokens: getNumber$3(usageRecord.cacheWrite),
9496
10262
  cacheReadTokens: getNumber$3(usageRecord.cacheRead),
9497
10263
  inputTokens: getNumber$3(usageRecord.input),
@@ -9516,8 +10282,9 @@ const openClawUsageAdapter = {
9516
10282
  rawModel,
9517
10283
  String(usage.inputTokens),
9518
10284
  String(usage.outputTokens),
9519
- String(usage.cachedInputTokens),
9520
- String(usage.reasoningOutputTokens),
10285
+ String((_f = usage.cacheCreationTokens) != null ? _f : 0),
10286
+ String((_g = usage.cacheReadTokens) != null ? _g : 0),
10287
+ String((_h = usage.extraTotalTokens) != null ? _h : 0),
9521
10288
  String(usage.costUSD)
9522
10289
  ].join(":"),
9523
10290
  index,
@@ -9625,8 +10392,12 @@ const openCodeUsageAdapter = {
9625
10392
  }
9626
10393
  const value = parseJsonFile(filePath);
9627
10394
  const record = normalizeUnknownRecord(value);
10395
+ const interactionId = normalizeStringValue(record == null ? void 0 : record.id);
10396
+ if (interactionId && isDuplicatedByOpenCodeDatabase(filePath, interactionId)) {
10397
+ return [];
10398
+ }
9628
10399
  const entry = record ? getOpenCodeMessageEntry(record, resolvePricing, {
9629
- interactionId: normalizeStringValue(record.id),
10400
+ interactionId,
9630
10401
  sessionId: normalizeStringValue(record.sessionID)
9631
10402
  }) : null;
9632
10403
  if (!entry) {
@@ -9679,7 +10450,7 @@ function getOpenCodeMessageEntry(value, resolvePricing, options = {}) {
9679
10450
  return null;
9680
10451
  }
9681
10452
  const usage = toInteractionUsage({
9682
- ...applyTotalUsageFallback({
10453
+ ...applyTotalUsageAsExtra({
9683
10454
  cacheCreationTokens: getNumber$2((_b = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _b.write),
9684
10455
  cacheReadTokens: getNumber$2((_c = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _c.read),
9685
10456
  inputTokens: getNumber$2(tokens.input),
@@ -9696,7 +10467,10 @@ function getOpenCodeMessageEntry(value, resolvePricing, options = {}) {
9696
10467
  }
9697
10468
  const sessionId = options.sessionId || "unknown";
9698
10469
  const directCost = normalizeFiniteNumberOrNull(value.cost);
9699
- const costUSD = directCost && directCost > 0 ? directCost : calculateUsageCostFromCandidates(usage, getOpenCodeModelCandidates(model, provider), resolvePricing);
10470
+ const costUSD = directCost && directCost > 0 ? directCost : calculateUsageCostFromCandidates(usage, getOpenCodeModelCandidates(model, provider), resolvePricing, {
10471
+ includeExtraTotalAsOutput: true,
10472
+ includeReasoningAsOutput: false
10473
+ });
9700
10474
  return {
9701
10475
  interactionId: options.interactionId || normalizeStringValue(value.id) || `${sessionId}:${timestamp}:${model}`,
9702
10476
  model,
@@ -9712,6 +10486,25 @@ function getOpenCodeLookupCandidates(model) {
9712
10486
  const normalizedModel = normalizeOpenCodeModelName(resolveOpenCodeModelName(model.trim()));
9713
10487
  return [model.trim(), normalizedModel];
9714
10488
  }
10489
+ function isDuplicatedByOpenCodeDatabase(filePath, interactionId) {
10490
+ const root = getOpenCodeRootFromMessageFile(filePath);
10491
+ if (!root) {
10492
+ return false;
10493
+ }
10494
+ const databaseFile = getOpenCodeDatabaseFileSync(root);
10495
+ if (!databaseFile) {
10496
+ return false;
10497
+ }
10498
+ const database = openSqliteDatabase(databaseFile, { readOnly: true });
10499
+ try {
10500
+ const row = database.prepare("SELECT id FROM message WHERE id = ? LIMIT 1").get(interactionId);
10501
+ return Boolean(row == null ? void 0 : row.id);
10502
+ } catch {
10503
+ return false;
10504
+ } finally {
10505
+ database.close();
10506
+ }
10507
+ }
9715
10508
  function getOpenCodeModelCandidates(model, provider) {
9716
10509
  const normalizedModel = normalizeOpenCodeModelName(resolveOpenCodeModelName(model));
9717
10510
  const candidates = [model, normalizedModel];
@@ -9750,6 +10543,22 @@ function parseUnknownJson(value) {
9750
10543
  return null;
9751
10544
  }
9752
10545
  }
10546
+ function getOpenCodeDatabaseFileSync(root) {
10547
+ var _a;
10548
+ const defaultPath = join(root, "opencode.db");
10549
+ if (existsSync(defaultPath)) {
10550
+ return defaultPath;
10551
+ }
10552
+ return (_a = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isFile() && isOpenCodeChannelDatabase(entry.name)).map((entry) => join(root, entry.name)).sort((left, right) => left.localeCompare(right))[0]) != null ? _a : null;
10553
+ }
10554
+ function getOpenCodeRootFromMessageFile(filePath) {
10555
+ const marker = `${join("storage", "message")}/`;
10556
+ const index = filePath.lastIndexOf(marker);
10557
+ return index >= 0 ? filePath.slice(0, index) : null;
10558
+ }
10559
+ function isOpenCodeChannelDatabase(name) {
10560
+ return /^opencode-[\w-]+\.db$/u.test(name);
10561
+ }
9753
10562
 
9754
10563
  const piUsageAdapter = {
9755
10564
  createPricingResolver: createZeroPricingResolver,
@@ -9760,7 +10569,7 @@ const piUsageAdapter = {
9760
10569
  return groups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "pi"));
9761
10570
  },
9762
10571
  parseFile(filePath) {
9763
- var _a, _b, _c;
10572
+ var _a, _b, _c, _d, _e, _f;
9764
10573
  const lines = parseJsonlFile(filePath);
9765
10574
  const sessionId = getPiSessionId(filePath);
9766
10575
  const project = getPiProject(filePath);
@@ -9783,7 +10592,7 @@ const piUsageAdapter = {
9783
10592
  continue;
9784
10593
  }
9785
10594
  const usage = toInteractionUsage({
9786
- ...applyTotalUsageFallback({
10595
+ ...applyTotalUsageAsExtra({
9787
10596
  cacheCreationTokens: getNumber$1(usageRecord.cacheWrite),
9788
10597
  cacheReadTokens: getNumber$1(usageRecord.cacheRead),
9789
10598
  inputTokens: getNumber$1(usageRecord.input),
@@ -9799,6 +10608,19 @@ const piUsageAdapter = {
9799
10608
  addFragmentInteraction(fragment, {
9800
10609
  content: "",
9801
10610
  costUSD: usage.costUSD,
10611
+ dedupeKey: [
10612
+ "pi",
10613
+ project,
10614
+ sessionId,
10615
+ timestamp,
10616
+ rawModel || "",
10617
+ String(usage.inputTokens),
10618
+ String(usage.outputTokens),
10619
+ String((_d = usage.cacheCreationTokens) != null ? _d : 0),
10620
+ String((_e = usage.cacheReadTokens) != null ? _e : 0),
10621
+ String((_f = usage.extraTotalTokens) != null ? _f : 0),
10622
+ String(usage.costUSD)
10623
+ ].join(":"),
9802
10624
  index,
9803
10625
  model: rawModel ? `[pi] ${rawModel}` : null,
9804
10626
  role: "assistant",
@@ -9862,14 +10684,15 @@ const qwenUsageAdapter = {
9862
10684
  if (!record || normalizeStringValue(record.type) !== "assistant" || !usageRecord) {
9863
10685
  continue;
9864
10686
  }
10687
+ const extraTotalTokens = getNumber(usageRecord.thoughtsTokenCount);
9865
10688
  const usage = toInteractionUsage({
9866
10689
  ...applyTotalUsageFallback({
9867
10690
  cacheReadTokens: getNumber(usageRecord.cachedContentTokenCount),
9868
10691
  inputTokens: getNumber(usageRecord.promptTokenCount),
9869
10692
  outputTokens: getNumber(usageRecord.candidatesTokenCount),
9870
- reasoningOutputTokens: getNumber(usageRecord.thoughtsTokenCount),
9871
- totalTokens: getNumber(usageRecord.totalTokenCount)
9872
- })
10693
+ totalTokens: Math.max(getNumber(usageRecord.totalTokenCount) - extraTotalTokens, 0)
10694
+ }),
10695
+ extraTotalTokens
9873
10696
  });
9874
10697
  if (isZeroInteractionUsage(usage)) {
9875
10698
  continue;
@@ -9950,7 +10773,7 @@ async function buildIncrementalUsageIndex(config, repository) {
9950
10773
  const cachedFilesByPath = new Map(cachedFiles.map((file) => [file.path, file]));
9951
10774
  const changedFiles = discoveredFiles.filter((file) => {
9952
10775
  const cached = cachedFilesByPath.get(file.path);
9953
- return !cached || cached.platform !== file.platform || cached.size !== file.size || cached.mtimeMs !== file.mtimeMs;
10776
+ return !cached || cached.platform !== file.platform || cached.cacheSignature !== file.cacheSignature || cached.size !== file.size || cached.mtimeMs !== file.mtimeMs;
9954
10777
  });
9955
10778
  const removedFiles = cachedFiles.filter((file) => !discoveredFiles.some((discovered) => discovered.path === file.path));
9956
10779
  const affectedProjects = new Set(removedFiles.flatMap((file) => file.projectNames));
@@ -10014,8 +10837,9 @@ async function discoverUsageFiles(config) {
10014
10837
  }
10015
10838
  function parseUsageFile(file, pricingResolvers) {
10016
10839
  const adapter = usagePlatformAdapters[file.platform];
10017
- const payload = adapter.parseFile(file.path, pricingResolvers[file.platform]);
10840
+ const payload = adapter.parseFile(file.path, pricingResolvers[file.platform], file);
10018
10841
  return {
10842
+ cacheSignature: file.cacheSignature,
10019
10843
  mtimeMs: file.mtimeMs,
10020
10844
  path: file.path,
10021
10845
  payload,
@@ -10033,29 +10857,61 @@ function buildPlatformSessionsByPlatform(indexedFiles) {
10033
10857
  function buildPlatformSessionsFromFiles(indexedFiles, platform) {
10034
10858
  var _a;
10035
10859
  const details = /* @__PURE__ */ new Map();
10036
- const seenDedupeKeys = /* @__PURE__ */ new Set();
10860
+ const selectedInteractions = selectDedupedInteractions(indexedFiles, platform);
10861
+ for (const { fragment, interaction } of selectedInteractions) {
10862
+ const detail = (_a = details.get(fragment.key)) != null ? _a : createSessionDetail(fragment);
10863
+ if (fragment.durationEndAt && (!detail.durationEndAt || Date.parse(fragment.durationEndAt) > Date.parse(detail.durationEndAt))) {
10864
+ detail.durationEndAt = fragment.durationEndAt;
10865
+ }
10866
+ addInteraction(detail, interaction);
10867
+ details.set(fragment.key, detail);
10868
+ }
10869
+ return Array.from(details.values()).map(finalizeSessionDetail).filter(hasBillableSessionDetail).map(toProjectSessionUsageItem).sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
10870
+ }
10871
+ function selectDedupedInteractions(indexedFiles, platform) {
10872
+ const interactionsWithoutDedupeKey = [];
10873
+ const interactionsByDedupeKey = /* @__PURE__ */ new Map();
10037
10874
  for (const file of indexedFiles) {
10038
10875
  if (file.platform !== platform) {
10039
10876
  continue;
10040
10877
  }
10041
10878
  for (const fragment of file.payload) {
10042
- const detail = (_a = details.get(fragment.key)) != null ? _a : createSessionDetail(fragment);
10043
- if (fragment.durationEndAt && (!detail.durationEndAt || Date.parse(fragment.durationEndAt) > Date.parse(detail.durationEndAt))) {
10044
- detail.durationEndAt = fragment.durationEndAt;
10045
- }
10046
10879
  for (const interaction of fragment.interactions) {
10047
- if (interaction.dedupeKey) {
10048
- if (seenDedupeKeys.has(interaction.dedupeKey)) {
10049
- continue;
10050
- }
10051
- seenDedupeKeys.add(interaction.dedupeKey);
10880
+ if (!interaction.dedupeKey) {
10881
+ interactionsWithoutDedupeKey.push({ fragment, interaction });
10882
+ continue;
10883
+ }
10884
+ const existing = interactionsByDedupeKey.get(interaction.dedupeKey);
10885
+ if (!existing) {
10886
+ interactionsByDedupeKey.set(interaction.dedupeKey, { fragment, interaction });
10887
+ } else if (shouldReplaceDedupedInteraction(interaction, existing.interaction)) {
10888
+ interactionsByDedupeKey.set(interaction.dedupeKey, { fragment: existing.fragment, interaction });
10052
10889
  }
10053
- addInteraction(detail, interaction);
10054
10890
  }
10055
- details.set(fragment.key, detail);
10056
10891
  }
10057
10892
  }
10058
- return Array.from(details.values()).map(finalizeSessionDetail).filter(hasBillableSessionDetail).map(toProjectSessionUsageItem).sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
10893
+ return [
10894
+ ...interactionsWithoutDedupeKey,
10895
+ ...interactionsByDedupeKey.values()
10896
+ ];
10897
+ }
10898
+ function shouldReplaceDedupedInteraction(candidate, existing) {
10899
+ var _a, _b, _c, _d;
10900
+ const candidateTotal = (_b = (_a = candidate.usage) == null ? void 0 : _a.totalTokens) != null ? _b : 0;
10901
+ const existingTotal = (_d = (_c = existing.usage) == null ? void 0 : _c.totalTokens) != null ? _d : 0;
10902
+ if (candidateTotal !== existingTotal) {
10903
+ return candidateTotal > existingTotal;
10904
+ }
10905
+ const candidateIsFast = isFastModel(candidate.model);
10906
+ const existingIsFast = isFastModel(existing.model);
10907
+ if (candidateIsFast !== existingIsFast) {
10908
+ return candidateIsFast;
10909
+ }
10910
+ return candidate.costUSD > existing.costUSD;
10911
+ }
10912
+ function isFastModel(model) {
10913
+ var _a;
10914
+ return (_a = model == null ? void 0 : model.endsWith("-fast")) != null ? _a : false;
10059
10915
  }
10060
10916
  function createSessionDetail(fragment) {
10061
10917
  var _a, _b;
@@ -10064,6 +10920,7 @@ function createSessionDetail(fragment) {
10064
10920
  costUSD: 0,
10065
10921
  durationEndAt: fragment.durationEndAt,
10066
10922
  durationMinutes: 0,
10923
+ key: fragment.key,
10067
10924
  inputTokens: 0,
10068
10925
  interactions: [],
10069
10926
  lastActivity: (_a = fragment.startedAt) != null ? _a : "",
@@ -10138,7 +10995,7 @@ function toProjectSessionUsageItem(detail) {
10138
10995
  date: dateKey ? formatDateLabelFromDateKey(dateKey) : "",
10139
10996
  duration: formatDuration(detail.durationMinutes),
10140
10997
  durationMinutes: detail.durationMinutes,
10141
- id: detail.sessionId,
10998
+ id: detail.key,
10142
10999
  inputTokens: detail.inputTokens,
10143
11000
  interactions: detail.interactions.map(({ dedupeKey: _dedupeKey, ...interaction }) => ({
10144
11001
  ...interaction,
@@ -10222,7 +11079,8 @@ function buildProjectUsageCatalogItemsFromDetails(details) {
10222
11079
  return Array.from(details).map(([label, detail]) => {
10223
11080
  return {
10224
11081
  label,
10225
- type: getProjectCatalogType(getProjectDetailPlatforms(detail))
11082
+ platforms: getProjectDetailPlatforms(detail),
11083
+ totalTokens: getProjectDetailTotalTokens(detail)
10226
11084
  };
10227
11085
  }).sort((a, b) => a.label.localeCompare(b.label));
10228
11086
  }
@@ -10287,78 +11145,351 @@ function assertProjectUsageDataModule(module) {
10287
11145
  throw new Error(`Unsupported project data module: ${module}.`);
10288
11146
  }
10289
11147
  }
10290
- function assertProjectUsagePlatformScope(platform) {
10291
- if (platform !== "all" && !PROJECT_USAGE_PLATFORMS.includes(platform)) {
10292
- throw new Error(`Unsupported project data platform: ${platform}.`);
11148
+ function assertProjectUsagePlatformScope(platform) {
11149
+ if (platform !== "all" && !PROJECT_USAGE_PLATFORMS.includes(platform)) {
11150
+ throw new Error(`Unsupported project data platform: ${platform}.`);
11151
+ }
11152
+ }
11153
+ function getProjectDetailPlatforms(detail) {
11154
+ return PROJECT_USAGE_PLATFORMS.filter((platform) => {
11155
+ var _a;
11156
+ return ((_a = detail.analyzing[platform]) != null ? _a : createEmptyProjectPlatformUsage()).sessions.length > 0;
11157
+ });
11158
+ }
11159
+ function getProjectDetailTotalTokens(detail) {
11160
+ return getProjectDetailSessions(detail).reduce((sum, session) => sum + session.tokenTotal, 0);
11161
+ }
11162
+ function buildSessionListModulePayload(sessionRows, sessions) {
11163
+ const sessionList = sessions.map(({ interactions: _interactions, ...session }) => session);
11164
+ return {
11165
+ sessionRows,
11166
+ sessionUsage: sessionList,
11167
+ sessions: sessionList
11168
+ };
11169
+ }
11170
+ function buildProjectPlatformPayloadMap(detail, module) {
11171
+ return Object.fromEntries(
11172
+ PROJECT_USAGE_PLATFORMS.map((platform) => {
11173
+ var _a;
11174
+ return [
11175
+ platform,
11176
+ buildPlatformModulePayload((_a = detail.analyzing[platform]) != null ? _a : createEmptyProjectPlatformUsage(), module)
11177
+ ];
11178
+ })
11179
+ );
11180
+ }
11181
+ function buildProjectLoadUsageResult(sessions, platform = "all") {
11182
+ const usage = buildLoadUsageResult(getProjectAggregateEvents(sessions), sessions, {
11183
+ aggregateOptions: {
11184
+ includeModel: (event) => platform !== "claudeCode" || event.model !== "<synthetic>"
11185
+ }
11186
+ });
11187
+ return {
11188
+ ...usage,
11189
+ sessionUsage: sessions
11190
+ };
11191
+ }
11192
+ function getProjectAggregateEvents(sessions) {
11193
+ return sessions.flatMap((session) => session.interactions.filter((interaction) => interaction.usage && interaction.timestamp && hasBillableUsage(interaction.usage)).map((interaction) => {
11194
+ var _a, _b;
11195
+ return {
11196
+ cachedInputTokens: interaction.usage.cachedInputTokens,
11197
+ costUSD: interaction.usage.costUSD,
11198
+ inputTokens: interaction.usage.inputTokens,
11199
+ isFallbackModel: (_a = interaction.usage.isFallbackModel) != null ? _a : false,
11200
+ model: (_b = interaction.model) != null ? _b : session.model,
11201
+ outputTokens: interaction.usage.outputTokens,
11202
+ project: session.project,
11203
+ reasoningOutputTokens: interaction.usage.reasoningOutputTokens,
11204
+ repository: session.repository,
11205
+ sessionId: session.id,
11206
+ timestamp: interaction.timestamp,
11207
+ totalTokens: interaction.usage.totalTokens
11208
+ };
11209
+ })).sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
11210
+ }
11211
+ function hasBillableUsage(usage) {
11212
+ return usage.totalTokens > 0 || usage.costUSD > 0;
11213
+ }
11214
+ function collectSessionModels(sessions) {
11215
+ return uniqueItems(sessions.flatMap((session) => session.models)).sort((a, b) => a.localeCompare(b));
11216
+ }
11217
+ function getEarliestStartedAt(sessions) {
11218
+ var _a;
11219
+ return (_a = sessions.map((session) => session.startedAt).filter((timestamp) => Number.isFinite(Date.parse(timestamp))).sort((a, b) => Date.parse(a) - Date.parse(b))[0]) != null ? _a : null;
11220
+ }
11221
+
11222
+ function buildHomeDashboardModules(dashboardsByPlatform, todayInsights = void 0) {
11223
+ const sessionUsage = buildSessionUsage(dashboardsByPlatform);
11224
+ const dailyTokenUsage = mergeDailyTokenUsage(
11225
+ PROJECT_USAGE_PLATFORMS.flatMap(
11226
+ (platform) => dashboardsByPlatform[platform].dailyTokenUsage.map((item) => {
11227
+ return {
11228
+ ...item,
11229
+ platforms: {
11230
+ [platform]: {
11231
+ cachedInputTokens: item.cachedInputTokens,
11232
+ costUSD: item.costUSD,
11233
+ inputTokens: item.inputTokens,
11234
+ models: item.models,
11235
+ outputTokens: item.outputTokens,
11236
+ reasoningOutputTokens: item.reasoningOutputTokens,
11237
+ totalTokens: item.totalTokens
11238
+ }
11239
+ }
11240
+ };
11241
+ })
11242
+ )
11243
+ );
11244
+ const monthlyModelUsage = mergeMonthlyModelUsage(
11245
+ PROJECT_USAGE_PLATFORMS.flatMap((platform) => dashboardsByPlatform[platform].monthlyModelUsage)
11246
+ );
11247
+ const projectUsage = buildProjectUsage(sessionUsage);
11248
+ const totalCost = dailyTokenUsage.reduce((sum, item) => sum + item.costUSD, 0);
11249
+ const totalTokens = dailyTokenUsage.reduce((sum, item) => sum + item.totalTokens, 0);
11250
+ const inputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.inputTokens, 0);
11251
+ const cachedInputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.cachedInputTokens, 0);
11252
+ const outputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.outputTokens, 0);
11253
+ const reasoningOutputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.reasoningOutputTokens, 0);
11254
+ const totalSessions = sessionUsage.length;
11255
+ const efficiencyMetrics = buildEfficiencyMetrics({
11256
+ cachedInputTokens,
11257
+ inputTokens,
11258
+ outputTokens,
11259
+ reasoningOutputTokens,
11260
+ totalTokens
11261
+ });
11262
+ const homeTodayInsights = todayInsights != null ? todayInsights : buildHomeTodayInsights(dashboardsByPlatform);
11263
+ const todayDateKey = getDateKey(/* @__PURE__ */ new Date());
11264
+ const previousDayDateKey = getPreviousDateKey(todayDateKey);
11265
+ const todayUsage = dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === todayDateKey);
11266
+ const previousUsage = dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === previousDayDateKey);
11267
+ return {
11268
+ dailyTokenUsage,
11269
+ efficiencyMetrics,
11270
+ hotProjects: projectUsage,
11271
+ modelUsage: monthlyModelUsage,
11272
+ overviewCards: buildHomeOverviewCards({
11273
+ cachedInputTokens,
11274
+ inputTokens,
11275
+ previousPromptCount: homeTodayInsights.previousPromptCount,
11276
+ previousSessionCount: homeTodayInsights.previousSessionCount,
11277
+ previousUsage,
11278
+ promptCount: homeTodayInsights.promptCount,
11279
+ sessionCount: homeTodayInsights.sessionCount,
11280
+ todayHourlyUsage: homeTodayInsights.todayHourlyUsage,
11281
+ todayUsage,
11282
+ totalCost,
11283
+ totalSessions,
11284
+ totalTokens
11285
+ }),
11286
+ sessionAnalysis: {
11287
+ items: sessionUsage,
11288
+ totalSessions
11289
+ },
11290
+ todayHourlyUsage: homeTodayInsights.todayHourlyUsage
11291
+ };
11292
+ }
11293
+ function buildSessionUsage(dashboardsByPlatform) {
11294
+ return PROJECT_USAGE_PLATFORMS.flatMap((platform) => dashboardsByPlatform[platform].sessionUsage.map((session) => ({
11295
+ ...session,
11296
+ id: `${platform}:${session.id}`,
11297
+ sessionId: `${platform}:${session.sessionId}`
11298
+ }))).sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
11299
+ }
11300
+ function buildHomeOverviewCards(options) {
11301
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
11302
+ const tokenTrend = buildGrowthTrend(
11303
+ (_b = (_a = options.todayUsage) == null ? void 0 : _a.totalTokens) != null ? _b : 0,
11304
+ (_d = (_c = options.previousUsage) == null ? void 0 : _c.totalTokens) != null ? _d : 0,
11305
+ formatCompactNumber
11306
+ );
11307
+ const costTrend = buildGrowthTrend(
11308
+ (_f = (_e = options.todayUsage) == null ? void 0 : _e.costUSD) != null ? _f : 0,
11309
+ (_h = (_g = options.previousUsage) == null ? void 0 : _g.costUSD) != null ? _h : 0,
11310
+ formatCurrency
11311
+ );
11312
+ const sessionTrend = buildPercentTrend(options.sessionCount, options.previousSessionCount);
11313
+ const promptTrend = buildPercentTrend(options.promptCount, options.previousPromptCount);
11314
+ return [
11315
+ {
11316
+ detail: `${formatNumber((_j = (_i = options.todayUsage) == null ? void 0 : _i.totalTokens) != null ? _j : 0)} tokens today. Yesterday: ${formatNumber((_l = (_k = options.previousUsage) == null ? void 0 : _k.totalTokens) != null ? _l : 0)}.`,
11317
+ icon: "solar:cpu-line-duotone",
11318
+ name: "Today Tokens",
11319
+ subvalue: buildInputOutputTokenSubvalue(options.todayUsage),
11320
+ trend: tokenTrend.trend,
11321
+ trendTone: tokenTrend.trendTone,
11322
+ value: formatCompactNumber((_n = (_m = options.todayUsage) == null ? void 0 : _m.totalTokens) != null ? _n : 0)
11323
+ },
11324
+ {
11325
+ detail: `${formatCurrency((_p = (_o = options.todayUsage) == null ? void 0 : _o.costUSD) != null ? _p : 0)} spent today. Yesterday: ${formatCurrency((_r = (_q = options.previousUsage) == null ? void 0 : _q.costUSD) != null ? _r : 0)}.`,
11326
+ icon: "lucide:wallet",
11327
+ name: "Today Spend",
11328
+ subvalue: {
11329
+ items: [
11330
+ {
11331
+ value: `${options.todayHourlyUsage.filter((item) => item.totalTokens > 0).length} active hours`
11332
+ }
11333
+ ]
11334
+ },
11335
+ trend: costTrend.trend,
11336
+ trendTone: costTrend.trendTone,
11337
+ value: formatCurrency((_t = (_s = options.todayUsage) == null ? void 0 : _s.costUSD) != null ? _t : 0)
11338
+ },
11339
+ {
11340
+ detail: `${formatNumber(options.sessionCount)} sessions started today. Yesterday: ${formatNumber(options.previousSessionCount)}.`,
11341
+ icon: "lucide:messages-square",
11342
+ name: "Today Sessions",
11343
+ trend: sessionTrend.label,
11344
+ trendTone: sessionTrend.tone,
11345
+ value: formatNumber(options.sessionCount)
11346
+ },
11347
+ {
11348
+ detail: `${formatNumber(options.promptCount)} prompts sent today. Yesterday: ${formatNumber(options.previousPromptCount)}.`,
11349
+ icon: "lucide:square-pen",
11350
+ name: "Prompt Count",
11351
+ trend: promptTrend.label,
11352
+ trendTone: promptTrend.tone,
11353
+ value: formatNumber(options.promptCount)
11354
+ },
11355
+ {
11356
+ detail: `${formatCurrency(options.totalCost)} total spend across all tools`,
11357
+ icon: "lucide:wallet",
11358
+ name: "Total Spend",
11359
+ trend: costTrend.trend,
11360
+ trendTone: costTrend.trendTone,
11361
+ value: formatCurrency(options.totalCost)
11362
+ },
11363
+ {
11364
+ detail: `${formatNumber(options.totalTokens)} total tokens across all tools`,
11365
+ icon: "solar:cpu-line-duotone",
11366
+ name: "Token Usage",
11367
+ trend: tokenTrend.trend,
11368
+ trendTone: tokenTrend.trendTone,
11369
+ value: formatCompactNumber(options.totalTokens)
11370
+ },
11371
+ {
11372
+ detail: `${formatNumber(options.cachedInputTokens)} of ${formatNumber(options.inputTokens)} input tokens were served from cache`,
11373
+ icon: "lucide:database-zap",
11374
+ name: "Cache Hit Rate",
11375
+ trend: `${formatCompactNumber(options.cachedInputTokens)} cached`,
11376
+ trendTone: "neutral",
11377
+ value: formatPercent(options.inputTokens > 0 ? options.cachedInputTokens / options.inputTokens : 0)
11378
+ },
11379
+ {
11380
+ detail: `${formatCurrency(options.totalCost)} across ${formatNumber(options.totalSessions)} sessions`,
11381
+ icon: "lucide:receipt-text",
11382
+ name: "Avg Session Cost",
11383
+ trend: "across all tools",
11384
+ trendTone: "neutral",
11385
+ value: formatCurrency(options.totalSessions > 0 ? options.totalCost / options.totalSessions : 0)
11386
+ }
11387
+ ];
11388
+ }
11389
+ function buildHomeTodayInsights(dashboardsByPlatform) {
11390
+ var _a, _b;
11391
+ const todayDateKey = getDateKey(/* @__PURE__ */ new Date());
11392
+ const previousDayDateKey = getPreviousDateKey(todayDateKey);
11393
+ const hourlyUsage = /* @__PURE__ */ new Map();
11394
+ let promptCount = 0;
11395
+ let previousPromptCount = 0;
11396
+ let sessionCount = 0;
11397
+ let previousSessionCount = 0;
11398
+ for (const platform of PROJECT_USAGE_PLATFORMS) {
11399
+ for (const session of dashboardsByPlatform[platform].sessionUsage) {
11400
+ const startedAtDateKey = getDateKeyFromTimestamp(session.startedAt);
11401
+ if (startedAtDateKey === todayDateKey) {
11402
+ sessionCount += 1;
11403
+ } else if (startedAtDateKey === previousDayDateKey) {
11404
+ previousSessionCount += 1;
11405
+ }
11406
+ for (const interaction of session.interactions) {
11407
+ const interactionDateKey = getDateKeyFromTimestamp(interaction.timestamp);
11408
+ if (interaction.role === "user") {
11409
+ if (interactionDateKey === todayDateKey) {
11410
+ promptCount += 1;
11411
+ } else if (interactionDateKey === previousDayDateKey) {
11412
+ previousPromptCount += 1;
11413
+ }
11414
+ }
11415
+ if (!interaction.usage || interactionDateKey !== todayDateKey) {
11416
+ continue;
11417
+ }
11418
+ const interactionDate = new Date(interaction.timestamp);
11419
+ if (!Number.isFinite(interactionDate.getTime())) {
11420
+ continue;
11421
+ }
11422
+ const hour = interactionDate.getHours();
11423
+ const bucket = (_a = hourlyUsage.get(hour)) != null ? _a : {
11424
+ agents: /* @__PURE__ */ new Map(),
11425
+ costUSD: 0,
11426
+ totalTokens: 0
11427
+ };
11428
+ const agentUsage = (_b = bucket.agents.get(platform)) != null ? _b : {
11429
+ costUSD: 0,
11430
+ totalTokens: 0
11431
+ };
11432
+ bucket.costUSD = roundCurrency(bucket.costUSD + interaction.usage.costUSD);
11433
+ bucket.totalTokens += interaction.usage.totalTokens;
11434
+ agentUsage.costUSD = roundCurrency(agentUsage.costUSD + interaction.usage.costUSD);
11435
+ agentUsage.totalTokens += interaction.usage.totalTokens;
11436
+ bucket.agents.set(platform, agentUsage);
11437
+ hourlyUsage.set(hour, bucket);
11438
+ }
11439
+ }
10293
11440
  }
10294
- }
10295
- function getProjectDetailPlatforms(detail) {
10296
- return PROJECT_USAGE_PLATFORMS.filter((platform) => {
10297
- var _a;
10298
- return ((_a = detail.analyzing[platform]) != null ? _a : createEmptyProjectPlatformUsage()).sessions.length > 0;
11441
+ const todayHourlyUsage = Array.from({ length: 24 }, (_, hour) => {
11442
+ var _a2, _b2, _c, _d, _e, _f;
11443
+ return {
11444
+ agents: Object.fromEntries((_b2 = (_a2 = hourlyUsage.get(hour)) == null ? void 0 : _a2.agents.entries()) != null ? _b2 : []),
11445
+ costUSD: (_d = (_c = hourlyUsage.get(hour)) == null ? void 0 : _c.costUSD) != null ? _d : 0,
11446
+ hour,
11447
+ label: `${String(hour).padStart(2, "0")}:00`,
11448
+ totalTokens: (_f = (_e = hourlyUsage.get(hour)) == null ? void 0 : _e.totalTokens) != null ? _f : 0
11449
+ };
10299
11450
  });
10300
- }
10301
- function getProjectCatalogType(platforms) {
10302
- return platforms.length === 1 ? platforms[0] : "mixed";
10303
- }
10304
- function buildSessionListModulePayload(sessionRows, sessions) {
10305
- const sessionList = sessions.map(({ interactions: _interactions, ...session }) => session);
10306
11451
  return {
10307
- sessionRows,
10308
- sessionUsage: sessionList,
10309
- sessions: sessionList
11452
+ previousPromptCount,
11453
+ previousSessionCount,
11454
+ promptCount,
11455
+ sessionCount,
11456
+ todayHourlyUsage
10310
11457
  };
10311
11458
  }
10312
- function buildProjectPlatformPayloadMap(detail, module) {
10313
- return Object.fromEntries(
10314
- PROJECT_USAGE_PLATFORMS.map((platform) => {
10315
- var _a;
10316
- return [
10317
- platform,
10318
- buildPlatformModulePayload((_a = detail.analyzing[platform]) != null ? _a : createEmptyProjectPlatformUsage(), module)
10319
- ];
10320
- })
10321
- );
11459
+ function getDateKeyFromTimestamp(value) {
11460
+ if (!value) {
11461
+ return null;
11462
+ }
11463
+ const date = new Date(value);
11464
+ return Number.isFinite(date.getTime()) ? getDateKey(date) : null;
10322
11465
  }
10323
- function buildProjectLoadUsageResult(sessions, platform = "all") {
10324
- const usage = buildLoadUsageResult(getProjectAggregateEvents(sessions), sessions, {
10325
- aggregateOptions: {
10326
- includeModel: (event) => platform !== "claudeCode" || event.model !== "<synthetic>"
11466
+ function buildEfficiencyMetrics(options) {
11467
+ const cacheHitRate = options.cachedInputTokens > 0 ? options.inputTokens / options.cachedInputTokens : 0;
11468
+ const reasoningShare = options.reasoningOutputTokens > 0 ? options.totalTokens / options.reasoningOutputTokens : 0;
11469
+ const outputShare = options.outputTokens > 0 ? options.totalTokens / options.outputTokens : 0;
11470
+ return [
11471
+ {
11472
+ detail: `${formatCompactNumber(options.cachedInputTokens)} cached input tokens`,
11473
+ label: "Cache Hit Rate",
11474
+ percent: cacheHitRate * 100,
11475
+ tone: "green",
11476
+ value: formatPercent(cacheHitRate)
11477
+ },
11478
+ {
11479
+ detail: `${formatCompactNumber(options.reasoningOutputTokens)} reasoning output tokens`,
11480
+ label: "Reasoning Token Share",
11481
+ percent: reasoningShare * 100,
11482
+ tone: "amber",
11483
+ value: formatPercent(reasoningShare)
11484
+ },
11485
+ {
11486
+ detail: `${formatCompactNumber(options.outputTokens)} output tokens`,
11487
+ label: "Output Token Share",
11488
+ percent: outputShare * 100,
11489
+ tone: "sky",
11490
+ value: formatPercent(outputShare)
10327
11491
  }
10328
- });
10329
- return {
10330
- ...usage,
10331
- sessionUsage: sessions
10332
- };
10333
- }
10334
- function getProjectAggregateEvents(sessions) {
10335
- return sessions.flatMap((session) => session.interactions.filter((interaction) => interaction.usage && interaction.timestamp && hasBillableUsage(interaction.usage)).map((interaction) => {
10336
- var _a, _b;
10337
- return {
10338
- cachedInputTokens: interaction.usage.cachedInputTokens,
10339
- costUSD: interaction.usage.costUSD,
10340
- inputTokens: interaction.usage.inputTokens,
10341
- isFallbackModel: (_a = interaction.usage.isFallbackModel) != null ? _a : false,
10342
- model: (_b = interaction.model) != null ? _b : session.model,
10343
- outputTokens: interaction.usage.outputTokens,
10344
- project: session.project,
10345
- reasoningOutputTokens: interaction.usage.reasoningOutputTokens,
10346
- repository: session.repository,
10347
- sessionId: session.sessionId,
10348
- timestamp: interaction.timestamp,
10349
- totalTokens: interaction.usage.totalTokens
10350
- };
10351
- })).sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
10352
- }
10353
- function hasBillableUsage(usage) {
10354
- return usage.totalTokens > 0 || usage.costUSD > 0;
10355
- }
10356
- function collectSessionModels(sessions) {
10357
- return uniqueItems(sessions.flatMap((session) => session.models)).sort((a, b) => a.localeCompare(b));
10358
- }
10359
- function getEarliestStartedAt(sessions) {
10360
- var _a;
10361
- return (_a = sessions.map((session) => session.startedAt).filter((timestamp) => Number.isFinite(Date.parse(timestamp))).sort((a, b) => Date.parse(a) - Date.parse(b))[0]) != null ? _a : null;
11492
+ ];
10362
11493
  }
10363
11494
 
10364
11495
  var __defProp = Object.defineProperty;
@@ -10374,7 +11505,7 @@ class UsageDataRuntime {
10374
11505
  bootstrap: null,
10375
11506
  hydratedAt: 0,
10376
11507
  projectCatalog: [],
10377
- projectDetails: /* @__PURE__ */ new Map(),
11508
+ projectDetails: null,
10378
11509
  refreshStartedAt: 0
10379
11510
  });
10380
11511
  __publicField(this, "initializePromise", null);
@@ -10389,7 +11520,6 @@ class UsageDataRuntime {
10389
11520
  if (!this.initializePromise) {
10390
11521
  this.initializePromise = this.hydrateFromRepository().finally(() => {
10391
11522
  this.startWatcher();
10392
- void this.refreshInBackground();
10393
11523
  });
10394
11524
  }
10395
11525
  return this.initializePromise;
@@ -10405,7 +11535,7 @@ class UsageDataRuntime {
10405
11535
  }
10406
11536
  async getProjectCatalog() {
10407
11537
  await this.initialize();
10408
- if (this.state.projectCatalog.length === 0 && this.state.projectDetails.size === 0) {
11538
+ if (this.state.projectCatalog.length === 0) {
10409
11539
  await this.refreshNow();
10410
11540
  } else {
10411
11541
  this.scheduleRefreshIfStale();
@@ -10417,12 +11547,18 @@ class UsageDataRuntime {
10417
11547
  const bootstrap = await this.getBootstrap();
10418
11548
  return (_a = bootstrap[platform]) != null ? _a : createEmptyLoadUsageResult();
10419
11549
  }
11550
+ async getHomeDashboardModules() {
11551
+ return buildHomeDashboardModules(await this.getBootstrap(), this.repository.loadHomeDashboardTodayInsights());
11552
+ }
10420
11553
  async getProjectDataModules(request) {
10421
11554
  await this.initialize();
10422
11555
  const projectLabel = (request.project || "").trim();
10423
11556
  if (!projectLabel) {
10424
11557
  throw new Error("Missing project name for project data request.");
10425
11558
  }
11559
+ if (this.state.projectDetails === null) {
11560
+ this.state.projectDetails = this.repository.loadProjectDetails();
11561
+ }
10426
11562
  const detail = this.state.projectDetails.get(projectLabel);
10427
11563
  if (!detail) {
10428
11564
  await this.refreshNow();
@@ -10443,10 +11579,8 @@ class UsageDataRuntime {
10443
11579
  var _a, _b;
10444
11580
  const bootstrap = this.repository.loadBootstrap();
10445
11581
  const projectCatalog = this.repository.loadProjectCatalog();
10446
- const projectDetails = this.repository.loadProjectDetails();
10447
11582
  this.state.bootstrap = (_a = bootstrap == null ? void 0 : bootstrap.payload) != null ? _a : null;
10448
11583
  this.state.projectCatalog = (_b = projectCatalog == null ? void 0 : projectCatalog.payload) != null ? _b : [];
10449
- this.state.projectDetails = projectDetails;
10450
11584
  this.state.hydratedAt = Math.max(
10451
11585
  bootstrap ? Date.parse(bootstrap.updatedAt) : 0,
10452
11586
  projectCatalog ? Date.parse(projectCatalog.updatedAt) : 0
@@ -10491,14 +11625,16 @@ class UsageDataRuntime {
10491
11625
  ),
10492
11626
  version: this.config.version
10493
11627
  };
10494
- const projectDetails = this.state.projectDetails.size > 0 ? patchProjectDetails(this.state.projectDetails, indexed.removedProjects, indexed.affectedProjects, bootstrapByPlatform) : buildAllProjectDetails(bootstrapByPlatform);
11628
+ const projectDetails = this.state.projectDetails ? patchProjectDetails(this.state.projectDetails, indexed.removedProjects, indexed.affectedProjects, bootstrapByPlatform) : buildAllProjectDetails(bootstrapByPlatform);
10495
11629
  const projectCatalog = buildProjectUsageCatalogItemsFromDetails(projectDetails.entries());
10496
11630
  this.repository.saveBootstrap(bootstrap);
10497
11631
  this.repository.saveProjectCatalog(projectCatalog);
10498
11632
  this.repository.replaceProjectDetails(projectDetails);
10499
11633
  this.state.bootstrap = bootstrap;
10500
11634
  this.state.projectCatalog = projectCatalog;
10501
- this.state.projectDetails = projectDetails;
11635
+ if (this.state.projectDetails) {
11636
+ this.state.projectDetails = projectDetails;
11637
+ }
10502
11638
  this.state.hydratedAt = Date.now();
10503
11639
  }
10504
11640
  dispose() {
@@ -10799,156 +11935,163 @@ const assets = {
10799
11935
  "/logo.svg": {
10800
11936
  "type": "image/svg+xml",
10801
11937
  "etag": "\"1550-fwYFdULdJ83Qp0FjnnX31iQz9oI\"",
10802
- "mtime": "2026-05-22T15:27:58.719Z",
11938
+ "mtime": "2026-05-26T08:31:48.218Z",
10803
11939
  "size": 5456,
10804
11940
  "path": "../public/logo.svg"
10805
11941
  },
10806
11942
  "/robots.txt": {
10807
11943
  "type": "text/plain; charset=utf-8",
10808
11944
  "etag": "\"18-j8OIsL9qGDmNZ+lHhp2tyH4XtaE\"",
10809
- "mtime": "2026-05-22T15:27:58.719Z",
11945
+ "mtime": "2026-05-26T08:31:48.218Z",
10810
11946
  "size": 24,
10811
11947
  "path": "../public/robots.txt"
10812
11948
  },
11949
+ "/_nuxt/37OOe3RF.js": {
11950
+ "type": "text/javascript; charset=utf-8",
11951
+ "etag": "\"433-9g1fS7jC4SfhMOz/zYtzEkjj3l4\"",
11952
+ "mtime": "2026-05-26T08:31:48.215Z",
11953
+ "size": 1075,
11954
+ "path": "../public/_nuxt/37OOe3RF.js"
11955
+ },
10813
11956
  "/favicon.ico": {
10814
11957
  "type": "image/vnd.microsoft.icon",
10815
11958
  "etag": "\"1083e-LfyFZ+1JmdianDqe/sQN2Ou0IzQ\"",
10816
- "mtime": "2026-05-22T15:27:58.720Z",
11959
+ "mtime": "2026-05-26T08:31:48.218Z",
10817
11960
  "size": 67646,
10818
11961
  "path": "../public/favicon.ico"
10819
11962
  },
10820
- "/_nuxt/B6C9KBQ4.js": {
11963
+ "/_nuxt/65Ayv2XK.js": {
10821
11964
  "type": "text/javascript; charset=utf-8",
10822
- "etag": "\"5e1e-I40z3Xz4z7/UJG57r4vYGuEgT7c\"",
10823
- "mtime": "2026-05-22T15:27:58.714Z",
10824
- "size": 24094,
10825
- "path": "../public/_nuxt/B6C9KBQ4.js"
11965
+ "etag": "\"bb0d-ldbgmGlpc1qx4bYEx4cXkSFSCg8\"",
11966
+ "mtime": "2026-05-26T08:31:48.215Z",
11967
+ "size": 47885,
11968
+ "path": "../public/_nuxt/65Ayv2XK.js"
10826
11969
  },
10827
- "/_nuxt/Bv6agYS5.js": {
11970
+ "/_nuxt/BOWwkrCY.js": {
10828
11971
  "type": "text/javascript; charset=utf-8",
10829
- "etag": "\"9f6b-/oK8WDX8dj11Vq4rsvDCjR0nyFo\"",
10830
- "mtime": "2026-05-22T15:27:58.713Z",
10831
- "size": 40811,
10832
- "path": "../public/_nuxt/Bv6agYS5.js"
11972
+ "etag": "\"540a-Q/6WhDpXkujbyVqTxKp+MDIjyN8\"",
11973
+ "mtime": "2026-05-26T08:31:48.215Z",
11974
+ "size": 21514,
11975
+ "path": "../public/_nuxt/BOWwkrCY.js"
10833
11976
  },
10834
- "/_nuxt/BvRyOET7.js": {
11977
+ "/_nuxt/D7qEPtpx.js": {
10835
11978
  "type": "text/javascript; charset=utf-8",
10836
- "etag": "\"760b-+u3AotZdPY2Q6cCC1nWxcJWlQgA\"",
10837
- "mtime": "2026-05-22T15:27:58.714Z",
10838
- "size": 30219,
10839
- "path": "../public/_nuxt/BvRyOET7.js"
11979
+ "etag": "\"eb4-cYfHtGVwvBnjUla1c7r6p8WQ+fU\"",
11980
+ "mtime": "2026-05-26T08:31:48.215Z",
11981
+ "size": 3764,
11982
+ "path": "../public/_nuxt/D7qEPtpx.js"
10840
11983
  },
10841
- "/_nuxt/COIbUy5w.js": {
11984
+ "/_nuxt/D9-Yw1TR.js": {
10842
11985
  "type": "text/javascript; charset=utf-8",
10843
- "etag": "\"d7b-HHVnZRYVWGAGuQkgrnXTrnj2mJI\"",
10844
- "mtime": "2026-05-22T15:27:58.714Z",
10845
- "size": 3451,
10846
- "path": "../public/_nuxt/COIbUy5w.js"
11986
+ "etag": "\"42dc-z2VCBVgr0YDv874MuLF5LmD+w6g\"",
11987
+ "mtime": "2026-05-26T08:31:48.215Z",
11988
+ "size": 17116,
11989
+ "path": "../public/_nuxt/D9-Yw1TR.js"
10847
11990
  },
10848
- "/_nuxt/CMWftE4h.js": {
11991
+ "/_nuxt/DF2WsXH3.js": {
10849
11992
  "type": "text/javascript; charset=utf-8",
10850
- "etag": "\"bc73-irs7yf0750yO82xrMn/X7jkeBxA\"",
10851
- "mtime": "2026-05-22T15:27:58.714Z",
10852
- "size": 48243,
10853
- "path": "../public/_nuxt/CMWftE4h.js"
11993
+ "etag": "\"101-2rfNy5z/IaUQM5ONN45oT3dPyOw\"",
11994
+ "mtime": "2026-05-26T08:31:48.215Z",
11995
+ "size": 257,
11996
+ "path": "../public/_nuxt/DF2WsXH3.js"
10854
11997
  },
10855
- "/_nuxt/CXLmM1yO.js": {
11998
+ "/_nuxt/C0GhHHgI.js": {
10856
11999
  "type": "text/javascript; charset=utf-8",
10857
- "etag": "\"4a32-Fw0ZEqsjg7b74APkVgMFCTcBxCQ\"",
10858
- "mtime": "2026-05-22T15:27:58.714Z",
10859
- "size": 18994,
10860
- "path": "../public/_nuxt/CXLmM1yO.js"
12000
+ "etag": "\"cf91-HNazofOinHCfSBf403LLacaEnfc\"",
12001
+ "mtime": "2026-05-26T08:31:48.215Z",
12002
+ "size": 53137,
12003
+ "path": "../public/_nuxt/C0GhHHgI.js"
10861
12004
  },
10862
- "/_nuxt/DFqWEFN4.js": {
12005
+ "/_nuxt/DXWxIyGU.js": {
10863
12006
  "type": "text/javascript; charset=utf-8",
10864
- "etag": "\"14f4-HAVLxCVCXnZm0u8oApXRDBhScWE\"",
10865
- "mtime": "2026-05-22T15:27:58.715Z",
10866
- "size": 5364,
10867
- "path": "../public/_nuxt/DFqWEFN4.js"
12007
+ "etag": "\"d7b-TUo4s2vJwQ7SvlmJq8Lc5JpszPo\"",
12008
+ "mtime": "2026-05-26T08:31:48.216Z",
12009
+ "size": 3451,
12010
+ "path": "../public/_nuxt/DXWxIyGU.js"
12011
+ },
12012
+ "/_nuxt/De8DvPWL.js": {
12013
+ "type": "text/javascript; charset=utf-8",
12014
+ "etag": "\"14f9-ru/D4lCOWqCP3lvQM3EWGPMYRaw\"",
12015
+ "mtime": "2026-05-26T08:31:48.215Z",
12016
+ "size": 5369,
12017
+ "path": "../public/_nuxt/De8DvPWL.js"
10868
12018
  },
10869
- "/_nuxt/DUoLvn3A.js": {
12019
+ "/_nuxt/DKaPq50Z.js": {
10870
12020
  "type": "text/javascript; charset=utf-8",
10871
- "etag": "\"3cf2-TwHHSkH7vVlOWjAczEQy6IP+0lw\"",
10872
- "mtime": "2026-05-22T15:27:58.715Z",
10873
- "size": 15602,
10874
- "path": "../public/_nuxt/DUoLvn3A.js"
12021
+ "etag": "\"f34c-URgC7Dz0Xf+JUUuL4yo1mSHnYmQ\"",
12022
+ "mtime": "2026-05-26T08:31:48.216Z",
12023
+ "size": 62284,
12024
+ "path": "../public/_nuxt/DKaPq50Z.js"
10875
12025
  },
10876
- "/_nuxt/DkxY2YMp.js": {
12026
+ "/_nuxt/DxvuOJRP.js": {
10877
12027
  "type": "text/javascript; charset=utf-8",
10878
- "etag": "\"4399-3rFV4vv/KswxrAJ9NQha32buQPE\"",
10879
- "mtime": "2026-05-22T15:27:58.715Z",
10880
- "size": 17305,
10881
- "path": "../public/_nuxt/DkxY2YMp.js"
12028
+ "etag": "\"10890-8PgC64ZUxoVro0//XJbLva3lqK0\"",
12029
+ "mtime": "2026-05-26T08:31:48.216Z",
12030
+ "size": 67728,
12031
+ "path": "../public/_nuxt/DxvuOJRP.js"
10882
12032
  },
10883
- "/_nuxt/D_W11Quh.js": {
12033
+ "/_nuxt/DgMMKsPE.js": {
10884
12034
  "type": "text/javascript; charset=utf-8",
10885
- "etag": "\"11426-X4U7gtaJhTDKTtUXLGjq2GLguBo\"",
10886
- "mtime": "2026-05-22T15:27:58.715Z",
10887
- "size": 70694,
10888
- "path": "../public/_nuxt/D_W11Quh.js"
12035
+ "etag": "\"35763-ZIXCcb7yuHkNvDqXH2EEhwlcyV4\"",
12036
+ "mtime": "2026-05-26T08:31:48.216Z",
12037
+ "size": 218979,
12038
+ "path": "../public/_nuxt/DgMMKsPE.js"
10889
12039
  },
10890
- "/_nuxt/HiqUua3-.js": {
12040
+ "/_nuxt/Jp5cgQZi.js": {
10891
12041
  "type": "text/javascript; charset=utf-8",
10892
- "etag": "\"2a70-eSH6QudruLCR6jtZZFjhyeyirUU\"",
10893
- "mtime": "2026-05-22T15:27:58.716Z",
10894
- "size": 10864,
10895
- "path": "../public/_nuxt/HiqUua3-.js"
12042
+ "etag": "\"3c6da-89AEgPN5OI/3SCYBdFqSxRLSihE\"",
12043
+ "mtime": "2026-05-26T08:31:48.216Z",
12044
+ "size": 247514,
12045
+ "path": "../public/_nuxt/Jp5cgQZi.js"
10896
12046
  },
10897
12047
  "/_nuxt/error-404.CFBEg71j.css": {
10898
12048
  "type": "text/css; charset=utf-8",
10899
12049
  "etag": "\"97e-GvhaEAryQvrSXyDcP4RiHXzYb5o\"",
10900
- "mtime": "2026-05-22T15:27:58.715Z",
12050
+ "mtime": "2026-05-26T08:31:48.216Z",
10901
12051
  "size": 2430,
10902
12052
  "path": "../public/_nuxt/error-404.CFBEg71j.css"
10903
12053
  },
10904
12054
  "/_nuxt/error-500.BqCnH31G.css": {
10905
12055
  "type": "text/css; charset=utf-8",
10906
12056
  "etag": "\"773-Tpf6lA6A2FEDtjLyWUXKolBZ3hM\"",
10907
- "mtime": "2026-05-22T15:27:58.716Z",
12057
+ "mtime": "2026-05-26T08:31:48.216Z",
10908
12058
  "size": 1907,
10909
12059
  "path": "../public/_nuxt/error-500.BqCnH31G.css"
10910
12060
  },
10911
- "/_nuxt/uHQwCIHg.js": {
12061
+ "/_nuxt/qXgLTL_3.js": {
10912
12062
  "type": "text/javascript; charset=utf-8",
10913
- "etag": "\"eb4-eU+Hz4IGbA/36UMYmP8kZLNbF6s\"",
10914
- "mtime": "2026-05-22T15:27:58.716Z",
10915
- "size": 3764,
10916
- "path": "../public/_nuxt/uHQwCIHg.js"
12063
+ "etag": "\"993e-F6dGXZ77Rf6Afn6RLLvwifZFe5Q\"",
12064
+ "mtime": "2026-05-26T08:31:48.216Z",
12065
+ "size": 39230,
12066
+ "path": "../public/_nuxt/qXgLTL_3.js"
12067
+ },
12068
+ "/_nuxt/y6mAKUDU.js": {
12069
+ "type": "text/javascript; charset=utf-8",
12070
+ "etag": "\"2c57-7lVqV+qTY/Dqb4M0PdxYAItocQw\"",
12071
+ "mtime": "2026-05-26T08:31:48.216Z",
12072
+ "size": 11351,
12073
+ "path": "../public/_nuxt/y6mAKUDU.js"
10917
12074
  },
10918
12075
  "/_nuxt/builds/latest.json": {
10919
12076
  "type": "application/json",
10920
- "etag": "\"47-wNnQ9XSYF1pAl7k+i6JQXN2OTYk\"",
10921
- "mtime": "2026-05-22T15:27:58.708Z",
12077
+ "etag": "\"47-yneWhd0H1nYqEdMbDKqzhQJ6sxE\"",
12078
+ "mtime": "2026-05-26T08:31:48.213Z",
10922
12079
  "size": 71,
10923
12080
  "path": "../public/_nuxt/builds/latest.json"
10924
12081
  },
10925
- "/_nuxt/builds/meta/ae3c6372-8821-43fb-aa55-bb47729a5660.json": {
12082
+ "/_nuxt/builds/meta/7ce9c611-6071-4cbd-8926-1e53d9ef21bd.json": {
10926
12083
  "type": "application/json",
10927
- "etag": "\"58-S7bLze8ZYeMP8v0W6MLjHfuBE20\"",
10928
- "mtime": "2026-05-22T15:27:58.701Z",
12084
+ "etag": "\"58-ozcqeghbP4BbYZVRtP6bwTCHhN8\"",
12085
+ "mtime": "2026-05-26T08:31:48.211Z",
10929
12086
  "size": 88,
10930
- "path": "../public/_nuxt/builds/meta/ae3c6372-8821-43fb-aa55-bb47729a5660.json"
10931
- },
10932
- "/_nuxt/DgKrPjze.js": {
10933
- "type": "text/javascript; charset=utf-8",
10934
- "etag": "\"35466-BQwxEwkorZeoi3JtsQAovpFUAlE\"",
10935
- "mtime": "2026-05-22T15:27:58.715Z",
10936
- "size": 218214,
10937
- "path": "../public/_nuxt/DgKrPjze.js"
12087
+ "path": "../public/_nuxt/builds/meta/7ce9c611-6071-4cbd-8926-1e53d9ef21bd.json"
10938
12088
  },
10939
- "/_nuxt/entry.vHfFzkyD.css": {
12089
+ "/_nuxt/entry.DnkKc-6G.css": {
10940
12090
  "type": "text/css; charset=utf-8",
10941
- "etag": "\"1d217-AX0vUIWFE2kL17zPKjOPcQBxadQ\"",
10942
- "mtime": "2026-05-22T15:27:58.716Z",
10943
- "size": 119319,
10944
- "path": "../public/_nuxt/entry.vHfFzkyD.css"
10945
- },
10946
- "/_nuxt/DtbPvE6R.js": {
10947
- "type": "text/javascript; charset=utf-8",
10948
- "etag": "\"4135e-cTbNR7mK7vcfB4JteOXxth6FUlY\"",
10949
- "mtime": "2026-05-22T15:27:58.716Z",
10950
- "size": 267102,
10951
- "path": "../public/_nuxt/DtbPvE6R.js"
12091
+ "etag": "\"1d8a6-KK9a3JMzAwbSGw5SnlSiODNKxHg\"",
12092
+ "mtime": "2026-05-26T08:31:48.216Z",
12093
+ "size": 120998,
12094
+ "path": "../public/_nuxt/entry.DnkKc-6G.css"
10952
12095
  }
10953
12096
  };
10954
12097
 
@@ -11444,9 +12587,13 @@ const inlineConfig = {
11444
12587
  "wi",
11445
12588
  "wpf",
11446
12589
  "zmdi",
11447
- "zondicons"
12590
+ "zondicons",
12591
+ "ai"
11448
12592
  ],
11449
- "fetchTimeout": 1500
12593
+ "fetchTimeout": 1500,
12594
+ "customCollections": [
12595
+ "ai"
12596
+ ]
11450
12597
  }
11451
12598
  };
11452
12599
 
@@ -11472,239 +12619,110 @@ function _deepFreeze(object) {
11472
12619
  return Object.freeze(object);
11473
12620
  }
11474
12621
 
11475
- function buildHomeDashboardModules(dashboardsByPlatform) {
11476
- var _a, _b, _c, _d;
11477
- const sessionUsage = buildSessionUsage(dashboardsByPlatform);
11478
- const dailyTokenUsage = mergeDailyTokenUsage(
11479
- PROJECT_USAGE_PLATFORMS.flatMap((platform) => getPlatformDashboard(dashboardsByPlatform, platform).dailyTokenUsage)
11480
- );
11481
- const monthlyModelUsage = mergeMonthlyModelUsage(
11482
- PROJECT_USAGE_PLATFORMS.flatMap((platform) => getPlatformDashboard(dashboardsByPlatform, platform).monthlyModelUsage)
11483
- );
11484
- const projectUsage = buildProjectUsage(sessionUsage);
11485
- const totalCost = dailyTokenUsage.reduce((sum, item) => sum + item.costUSD, 0);
11486
- const totalTokens = dailyTokenUsage.reduce((sum, item) => sum + item.totalTokens, 0);
11487
- const inputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.inputTokens, 0);
11488
- const cachedInputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.cachedInputTokens, 0);
11489
- const outputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.outputTokens, 0);
11490
- const reasoningOutputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.reasoningOutputTokens, 0);
11491
- const totalSessions = sessionUsage.length;
11492
- const todayDateKey = getDateKey(/* @__PURE__ */ new Date());
11493
- const previousDayDateKey = getPreviousDateKey(todayDateKey);
11494
- const todayDailyUsage = dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === todayDateKey);
11495
- const previousDayDailyUsage = dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === previousDayDateKey);
11496
- const costGrowthTrend = buildGrowthTrend(
11497
- (_a = todayDailyUsage == null ? void 0 : todayDailyUsage.costUSD) != null ? _a : 0,
11498
- (_b = previousDayDailyUsage == null ? void 0 : previousDayDailyUsage.costUSD) != null ? _b : 0,
11499
- formatCurrency
11500
- );
11501
- const tokenGrowthTrend = buildGrowthTrend(
11502
- (_c = todayDailyUsage == null ? void 0 : todayDailyUsage.totalTokens) != null ? _c : 0,
11503
- (_d = previousDayDailyUsage == null ? void 0 : previousDayDailyUsage.totalTokens) != null ? _d : 0,
11504
- formatCompactNumber
11505
- );
11506
- const efficiencyMetrics = buildEfficiencyMetrics({
11507
- cachedInputTokens,
11508
- inputTokens,
11509
- outputTokens,
11510
- reasoningOutputTokens,
11511
- totalTokens
11512
- });
11513
- return {
11514
- dailyTokenUsage,
11515
- efficiencyMetrics,
11516
- hotProjects: projectUsage,
11517
- modelUsage: monthlyModelUsage,
11518
- overviewCards: buildHomeOverviewCards({
11519
- cachedInputTokens,
11520
- costGrowthTrend,
11521
- inputTokens,
11522
- tokenGrowthTrend,
11523
- totalCost,
11524
- totalSessions,
11525
- totalTokens
11526
- }),
11527
- sessionAnalysis: {
11528
- items: sessionUsage,
11529
- totalSessions
11530
- }
11531
- };
11532
- }
11533
- function getPlatformDashboard(dashboardsByPlatform, platform) {
11534
- return dashboardsByPlatform[platform];
11535
- }
11536
- function buildSessionUsage(dashboardsByPlatform) {
11537
- return PROJECT_USAGE_PLATFORMS.flatMap((platform) => getPlatformDashboard(dashboardsByPlatform, platform).sessionUsage.map((session) => ({
11538
- ...session,
11539
- id: `${platform}:${session.id}`,
11540
- sessionId: `${platform}:${session.sessionId}`
11541
- }))).sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
11542
- }
11543
- function buildHomeOverviewCards(options) {
11544
- return [
11545
- {
11546
- detail: `${formatCurrency(options.totalCost)} total spend across all tools`,
11547
- icon: "lucide:wallet",
11548
- name: "Total Spend",
11549
- trend: options.costGrowthTrend.trend,
11550
- trendTone: options.costGrowthTrend.trendTone,
11551
- value: formatCurrency(options.totalCost)
11552
- },
11553
- {
11554
- detail: `${formatNumber(options.totalTokens)} total tokens across all tools`,
11555
- icon: "solar:cpu-line-duotone",
11556
- name: "Token Usage",
11557
- trend: options.tokenGrowthTrend.trend,
11558
- trendTone: options.tokenGrowthTrend.trendTone,
11559
- value: formatCompactNumber(options.totalTokens)
11560
- },
11561
- {
11562
- detail: `${formatNumber(options.cachedInputTokens)} of ${formatNumber(options.inputTokens)} input tokens were served from cache`,
11563
- icon: "lucide:database-zap",
11564
- name: "Cache Hit Rate",
11565
- trend: `${formatCompactNumber(options.cachedInputTokens)} cached`,
11566
- trendTone: "neutral",
11567
- value: formatPercent(options.inputTokens > 0 ? options.cachedInputTokens / options.inputTokens : 0)
11568
- },
11569
- {
11570
- detail: `${formatCurrency(options.totalCost)} across ${formatNumber(options.totalSessions)} sessions`,
11571
- icon: "lucide:receipt-text",
11572
- name: "Avg Session Cost",
11573
- trend: "across all tools",
11574
- trendTone: "neutral",
11575
- value: formatCurrency(options.totalSessions > 0 ? options.totalCost / options.totalSessions : 0)
11576
- }
11577
- ];
11578
- }
11579
- function buildEfficiencyMetrics(options) {
11580
- const cacheHitRate = safeRatio(options.cachedInputTokens, options.inputTokens);
11581
- const reasoningShare = safeRatio(options.reasoningOutputTokens, options.totalTokens);
11582
- const outputShare = safeRatio(options.outputTokens, options.totalTokens);
11583
- return [
11584
- {
11585
- detail: `${formatCompactNumber(options.cachedInputTokens)} cached input tokens`,
11586
- label: "Cache Hit Rate",
11587
- percent: cacheHitRate * 100,
11588
- tone: "green",
11589
- value: formatPercent(cacheHitRate)
11590
- },
11591
- {
11592
- detail: `${formatCompactNumber(options.reasoningOutputTokens)} reasoning output tokens`,
11593
- label: "Reasoning Token Share",
11594
- percent: reasoningShare * 100,
11595
- tone: "amber",
11596
- value: formatPercent(reasoningShare)
11597
- },
11598
- {
11599
- detail: `${formatCompactNumber(options.outputTokens)} output tokens`,
11600
- label: "Output Token Share",
11601
- percent: outputShare * 100,
11602
- tone: "sky",
11603
- value: formatPercent(outputShare)
11604
- }
11605
- ];
11606
- }
11607
- function safeRatio(numerator, denominator) {
11608
- return denominator > 0 ? numerator / denominator : 0;
11609
- }
12622
+ const ANALYSIS_AGENT_TOKEN_TYPES = ["day", "week", "month", "session"];
12623
+ const ANALYSIS_AGENT_TOKEN_ROW_KEYS = {
12624
+ day: "dailyRows",
12625
+ month: "monthlyRows",
12626
+ session: "sessionRows",
12627
+ week: "weeklyRows"
12628
+ };
11610
12629
 
11611
12630
  const PROJECT_USAGE_PLATFORM_META = {
11612
12631
  amp: {
11613
- aiIcon: "amp",
12632
+ aiIcon: "ai:amp",
11614
12633
  color: "#f34e3f",
11615
12634
  label: "Amp",
11616
12635
  slug: "amp"
11617
12636
  },
11618
12637
  claudeCode: {
11619
- aiIcon: "claude_code",
12638
+ aiIcon: "ai:claude-code",
11620
12639
  color: "#d97757",
11621
12640
  label: "Claude Code",
11622
12641
  slug: "claude_code"
11623
12642
  },
11624
12643
  codebuff: {
11625
- aiIcon: "codebuff",
12644
+ aiIcon: "ai:codebuff",
11626
12645
  color: "#14b8a6",
11627
12646
  label: "Codebuff",
11628
12647
  slug: "codebuff"
11629
12648
  },
11630
12649
  codex: {
11631
- aiIcon: "codex",
12650
+ aiIcon: "ai:codex",
11632
12651
  color: "#111827",
11633
12652
  label: "Codex",
11634
12653
  slug: "codex"
11635
12654
  },
11636
12655
  copilot: {
11637
- aiIcon: "copilot",
12656
+ aiIcon: "ai:copilot",
11638
12657
  color: "#0f766e",
11639
12658
  label: "GitHub Copilot",
11640
12659
  slug: "copilot"
11641
12660
  },
11642
12661
  droid: {
11643
- aiIcon: "droid",
12662
+ aiIcon: "ai:droid",
11644
12663
  color: "#06b6d4",
11645
12664
  label: "Droid",
11646
12665
  slug: "droid"
11647
12666
  },
11648
12667
  gemini: {
11649
- aiIcon: "gemini",
12668
+ aiIcon: "ai:gemini",
11650
12669
  color: "#0ea5e9",
11651
12670
  label: "Gemini",
11652
12671
  slug: "gemini"
11653
12672
  },
11654
12673
  goose: {
11655
- aiIcon: "goose",
12674
+ aiIcon: "ai:goose",
11656
12675
  color: "#22c55e",
11657
12676
  label: "Goose",
11658
12677
  slug: "goose"
11659
12678
  },
11660
12679
  hermes: {
11661
- aiIcon: "hermes",
12680
+ aiIcon: "ai:hermesagent",
11662
12681
  color: "#8b5cf6",
11663
12682
  label: "Hermes",
11664
12683
  slug: "hermes"
11665
12684
  },
11666
12685
  kilo: {
11667
- aiIcon: "kilo",
12686
+ aiIcon: "ai:kilo",
11668
12687
  color: "#f97316",
11669
12688
  label: "Kilo",
11670
12689
  slug: "kilo"
11671
12690
  },
11672
12691
  kimi: {
11673
- aiIcon: "kimi_code",
12692
+ aiIcon: "ai:kimi",
11674
12693
  color: "#2563eb",
11675
12694
  label: "Kimi",
11676
12695
  slug: "kimi"
11677
12696
  },
11678
12697
  openclaw: {
11679
- aiIcon: "openclaw",
12698
+ aiIcon: "ai:openclaw",
11680
12699
  color: "#ec4899",
11681
12700
  label: "OpenClaw",
11682
12701
  slug: "openclaw"
11683
12702
  },
11684
12703
  opencode: {
11685
- aiIcon: "open_code",
12704
+ aiIcon: "ai:open-code",
11686
12705
  color: "#4f46e5",
11687
12706
  label: "OpenCode",
11688
12707
  slug: "opencode"
11689
12708
  },
11690
12709
  pi: {
11691
- aiIcon: "pi",
12710
+ aiIcon: "ai:pi",
11692
12711
  color: "#a855f7",
11693
12712
  label: "Pi",
11694
12713
  slug: "pi"
11695
12714
  },
11696
12715
  qwen: {
11697
- aiIcon: "qwen_code",
12716
+ aiIcon: "ai:qwen",
11698
12717
  color: "#623ae7",
11699
12718
  label: "Qwen",
11700
12719
  slug: "qwen"
11701
12720
  }
11702
12721
  };
11703
- const platformSlugEntries = PROJECT_USAGE_PLATFORMS.map((platform) => [
12722
+ const platformBySlug = new Map(PROJECT_USAGE_PLATFORMS.map((platform) => [
11704
12723
  PROJECT_USAGE_PLATFORM_META[platform].slug,
11705
12724
  platform
11706
- ]);
11707
- const platformBySlug = new Map(platformSlugEntries);
12725
+ ]));
11708
12726
  function resolveProjectUsagePlatform(value) {
11709
12727
  var _a;
11710
12728
  const normalizedValue = value == null ? void 0 : value.trim();
@@ -11717,14 +12735,6 @@ function resolveProjectUsagePlatform(value) {
11717
12735
  return (_a = platformBySlug.get(normalizedValue)) != null ? _a : null;
11718
12736
  }
11719
12737
 
11720
- const ANALYSIS_AGENT_TOKEN_TYPES = ["day", "week", "month", "session"];
11721
- const ANALYSIS_AGENT_TOKEN_ROW_KEYS = {
11722
- day: "dailyRows",
11723
- month: "monthlyRows",
11724
- session: "sessionRows",
11725
- week: "weeklyRows"
11726
- };
11727
-
11728
12738
  function getAnalysisRuntime(event) {
11729
12739
  const runtimeConfig = useRuntimeConfig(event);
11730
12740
  const config = resolveConfig(runtimeConfig.public);
@@ -11791,7 +12801,7 @@ function defineRequiredAgentAnalysisHandler(select) {
11791
12801
  });
11792
12802
  }
11793
12803
  async function getHomeAnalysisModules(event) {
11794
- return buildHomeDashboardModules(await getAnalysisRuntime(event).getBootstrap());
12804
+ return getAnalysisRuntime(event).getHomeDashboardModules();
11795
12805
  }
11796
12806
  function normalizeQueryString(value) {
11797
12807
  if (Array.isArray(value)) {
@@ -11867,6 +12877,7 @@ const _lazy_NXGH0G = () => import('../routes/api/analysis/overview-cards.json.mj
11867
12877
  const _lazy_FZJ5Wh = () => import('../routes/api/analysis/session.json.mjs');
11868
12878
  const _lazy_k5j8NN = () => import('../routes/api/analysis/token.json.mjs');
11869
12879
  const _lazy_wkSLy3 = () => import('../routes/api/analysis/token/daily.json.mjs');
12880
+ const _lazy_Vzep2h = () => import('../routes/api/analysis/token/today-hourly.json.mjs');
11870
12881
  const _lazy_x_4o7G = () => import('../routes/api/payload.json.mjs');
11871
12882
  const _lazy_slsjW6 = () => import('../routes/api/projects/_project/modules.get.mjs');
11872
12883
  const _lazy_K00scR = () => import('../routes/api/projects/catalog.get.mjs');
@@ -11884,6 +12895,7 @@ const handlers = [
11884
12895
  { route: '/api/analysis/session.json', handler: _lazy_FZJ5Wh, lazy: true, middleware: false, method: undefined },
11885
12896
  { route: '/api/analysis/token.json', handler: _lazy_k5j8NN, lazy: true, middleware: false, method: undefined },
11886
12897
  { route: '/api/analysis/token/daily.json', handler: _lazy_wkSLy3, lazy: true, middleware: false, method: undefined },
12898
+ { route: '/api/analysis/token/today-hourly.json', handler: _lazy_Vzep2h, lazy: true, middleware: false, method: undefined },
11887
12899
  { route: '/api/payload.json', handler: _lazy_x_4o7G, lazy: true, middleware: false, method: undefined },
11888
12900
  { route: '/api/projects/:project/modules', handler: _lazy_slsjW6, lazy: true, middleware: false, method: "get" },
11889
12901
  { route: '/api/projects/catalog', handler: _lazy_K00scR, lazy: true, middleware: false, method: "get" },
@@ -12041,4 +13053,4 @@ const websocket = nitroApp.h3App.websocket ;
12041
13053
  const handler = listener;
12042
13054
  trapUnhandledNodeErrors();
12043
13055
 
12044
- export { ANALYSIS_AGENT_TOKEN_ROW_KEYS as A, defineHomeAnalysisHandler as a, defineScopedAnalysisHandler as b, defineEventHandler as c, defineRequiredAgentAnalysisHandler as d, getUsageDataRuntime as e, getRouterParam as f, getRequiredAnalysisAgentTokenType as g, getQuery as h, normalizeStringList as i, defineWebSocketHandler as j, buildAssetsURL as k, getResponseStatusText as l, getResponseStatus as m, normalizeStringValue as n, defineRenderHandler as o, publicAssetsURL as p, createError$1 as q, resolveConfig as r, destr as s, getRouteRules as t, useRuntimeConfig as u, joinURL as v, useNitroApp as w, handler as x, listener as y, websocket as z };
13056
+ export { ANALYSIS_AGENT_TOKEN_ROW_KEYS as A, websocket as B, defineHomeAnalysisHandler as a, defineScopedAnalysisHandler as b, buildOverviewCardsWithTodayTokenBreakdown as c, defineRequiredAgentAnalysisHandler as d, defineEventHandler as e, getUsageDataRuntime as f, getRequiredAnalysisAgentTokenType as g, getRouterParam as h, getQuery as i, normalizeStringList as j, defineWebSocketHandler as k, buildAssetsURL as l, getResponseStatusText as m, normalizeStringValue as n, getResponseStatus as o, defineRenderHandler as p, publicAssetsURL as q, resolveConfig as r, createError$1 as s, destr as t, useRuntimeConfig as u, getRouteRules as v, joinURL as w, useNitroApp as x, handler as y, listener as z };