usage-board 3.2.1 → 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/{CLHpvfq1.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/{CBu27bzx.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 +2192 -1409
  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/BL-DadsH.js +0 -260
  41. package/dist/public/_nuxt/BO_d_Oyx.js +0 -6
  42. package/dist/public/_nuxt/BtuXxixc.js +0 -25
  43. package/dist/public/_nuxt/CC4_0abj.js +0 -1
  44. package/dist/public/_nuxt/CGsjOcfM.js +0 -21
  45. package/dist/public/_nuxt/COBlwcVj.js +0 -1
  46. package/dist/public/_nuxt/CjeAKHhp.js +0 -1
  47. package/dist/public/_nuxt/CvvesHM7.js +0 -105
  48. package/dist/public/_nuxt/IstswKJB.js +0 -4
  49. package/dist/public/_nuxt/N8W_Dp-N.js +0 -119
  50. package/dist/public/_nuxt/builds/meta/be10c896-ee75-4f4f-911d-c3f0a8b4d417.json +0 -1
  51. package/dist/public/_nuxt/entry.vHfFzkyD.css +0 -1
  52. package/dist/public/_nuxt/vkAmg0G5.js +0 -1
  53. package/dist/public/_nuxt/wUprbfrX.js +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": "be10c896-ee75-4f4f-911d-c3f0a8b4d417",
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.1",
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 = 3;
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,6 +5335,10 @@ 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,
@@ -5080,6 +5390,7 @@ const CACHE_SCHEMA_SQL = `
5080
5390
  cached_input_tokens INTEGER,
5081
5391
  output_tokens INTEGER,
5082
5392
  reasoning_output_tokens INTEGER,
5393
+ extra_total_tokens INTEGER,
5083
5394
  total_tokens INTEGER,
5084
5395
  usage_cost_usd REAL,
5085
5396
  cache_creation_tokens INTEGER,
@@ -5104,7 +5415,7 @@ class UsageCacheRepository {
5104
5415
  if (!meta) {
5105
5416
  return null;
5106
5417
  }
5107
- const scopes = this.loadHydratedUsageScopes("bootstrap");
5418
+ const scopes = this.loadHydratedUsageScopes("bootstrap", false);
5108
5419
  const payload = Object.fromEntries(
5109
5420
  PROJECT_USAGE_PLATFORMS.map((platform) => {
5110
5421
  var _a2;
@@ -5126,20 +5437,76 @@ class UsageCacheRepository {
5126
5437
  saveBootstrap(payload) {
5127
5438
  this.persistBootstrap(payload);
5128
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
+ }
5129
5495
  loadProjectCatalog() {
5130
5496
  const meta = this.getCacheState("project_catalog");
5131
5497
  if (!meta) {
5132
5498
  return null;
5133
5499
  }
5134
5500
  const rows = this.database.prepare(`
5135
- SELECT label, type
5501
+ SELECT label, platforms_json, total_tokens
5136
5502
  FROM project_catalog_entries
5137
5503
  ORDER BY label ASC
5138
5504
  `).all();
5139
5505
  return {
5140
5506
  payload: rows.map((row) => ({
5141
5507
  label: row.label,
5142
- type: row.type
5508
+ platforms: parseProjectCatalogPlatforms(row.platforms_json),
5509
+ totalTokens: row.total_tokens
5143
5510
  })),
5144
5511
  payloadHash: meta.payload_hash,
5145
5512
  updatedAt: meta.updated_at
@@ -5235,6 +5602,7 @@ class UsageCacheRepository {
5235
5602
  cached_input_tokens,
5236
5603
  output_tokens,
5237
5604
  reasoning_output_tokens,
5605
+ extra_total_tokens,
5238
5606
  total_tokens,
5239
5607
  usage_cost_usd,
5240
5608
  cache_creation_tokens,
@@ -5275,7 +5643,7 @@ class UsageCacheRepository {
5275
5643
  });
5276
5644
  }
5277
5645
  upsertIndexedSourceFiles(files) {
5278
- 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;
5279
5647
  if (files.length === 0) {
5280
5648
  return;
5281
5649
  }
@@ -5318,6 +5686,7 @@ class UsageCacheRepository {
5318
5686
  cached_input_tokens,
5319
5687
  output_tokens,
5320
5688
  reasoning_output_tokens,
5689
+ extra_total_tokens,
5321
5690
  total_tokens,
5322
5691
  usage_cost_usd,
5323
5692
  cache_creation_tokens,
@@ -5325,7 +5694,7 @@ class UsageCacheRepository {
5325
5694
  tool_tokens,
5326
5695
  is_fallback_model
5327
5696
  )
5328
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5697
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5329
5698
  `);
5330
5699
  this.database.exec("BEGIN");
5331
5700
  try {
@@ -5364,12 +5733,13 @@ class UsageCacheRepository {
5364
5733
  (_e = (_d = interaction.usage) == null ? void 0 : _d.cachedInputTokens) != null ? _e : null,
5365
5734
  (_g = (_f = interaction.usage) == null ? void 0 : _f.outputTokens) != null ? _g : null,
5366
5735
  (_i = (_h = interaction.usage) == null ? void 0 : _h.reasoningOutputTokens) != null ? _i : null,
5367
- (_k = (_j = interaction.usage) == null ? void 0 : _j.totalTokens) != null ? _k : null,
5368
- (_m = (_l = interaction.usage) == null ? void 0 : _l.costUSD) != null ? _m : null,
5369
- (_o = (_n = interaction.usage) == null ? void 0 : _n.cacheCreationTokens) != null ? _o : null,
5370
- (_q = (_p = interaction.usage) == null ? void 0 : _p.cacheReadTokens) != null ? _q : null,
5371
- (_s = (_r = interaction.usage) == null ? void 0 : _r.toolTokens) != null ? _s : null,
5372
- ((_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
5373
5743
  );
5374
5744
  }
5375
5745
  }
@@ -5442,6 +5812,10 @@ class UsageCacheRepository {
5442
5812
  initializeSchema() {
5443
5813
  this.database.exec(CACHE_SCHEMA_SQL);
5444
5814
  this.ensureIndexedFilesCacheSignatureColumn();
5815
+ this.ensureDailyUsageModelCostColumn();
5816
+ this.ensureOverviewCardSubvalueColumn();
5817
+ this.ensureProjectCatalogColumns();
5818
+ this.ensureInteractionExtraTotalTokenColumns();
5445
5819
  const currentSchemaVersion = this.getCurrentSchemaVersion();
5446
5820
  if (currentSchemaVersion >= CACHE_SCHEMA_VERSION) {
5447
5821
  return;
@@ -5511,9 +5885,71 @@ class UsageCacheRepository {
5511
5885
  }
5512
5886
  this.database.exec("ALTER TABLE indexed_files ADD COLUMN cache_signature TEXT NOT NULL DEFAULT ''");
5513
5887
  }
5514
- clearNormalizedTables() {
5515
- this.database.exec("BEGIN");
5516
- try {
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
+ }
5950
+ clearNormalizedTables() {
5951
+ this.database.exec("BEGIN");
5952
+ try {
5517
5953
  this.database.prepare("DELETE FROM cache_state").run();
5518
5954
  this.database.prepare("DELETE FROM project_catalog_entries").run();
5519
5955
  this.database.prepare("DELETE FROM projects").run();
@@ -5576,14 +6012,14 @@ class UsageCacheRepository {
5576
6012
  const payloadHash = (_b = options.payloadHash) != null ? _b : createPayloadHash(JSON.stringify(payload));
5577
6013
  const deleteStatement = this.database.prepare("DELETE FROM project_catalog_entries");
5578
6014
  const insertStatement = this.database.prepare(`
5579
- INSERT INTO project_catalog_entries (label, type, updated_at)
5580
- VALUES (?, ?, ?)
6015
+ INSERT INTO project_catalog_entries (label, platforms_json, total_tokens, updated_at)
6016
+ VALUES (?, ?, ?, ?)
5581
6017
  `);
5582
6018
  this.database.exec("BEGIN");
5583
6019
  try {
5584
6020
  deleteStatement.run();
5585
6021
  for (const item of payload) {
5586
- insertStatement.run(item.label, item.type, updatedAt);
6022
+ insertStatement.run(item.label, JSON.stringify(item.platforms), item.totalTokens, updatedAt);
5587
6023
  }
5588
6024
  this.upsertCacheState("project_catalog", payloadHash, updatedAt);
5589
6025
  this.database.exec("COMMIT");
@@ -5603,7 +6039,7 @@ class UsageCacheRepository {
5603
6039
  `).run(key, payloadHash, updatedAt, version != null ? version : null);
5604
6040
  }
5605
6041
  insertUsageScope(options) {
5606
- 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;
5607
6043
  const scopeKey = createUsageScopeKey(options.kind, options.platform, options.projectLabel);
5608
6044
  const payloadHash = createPayloadHash(JSON.stringify(options.usage));
5609
6045
  const insertScopeStatement = this.database.prepare(`
@@ -5631,10 +6067,11 @@ class UsageCacheRepository {
5631
6067
  name,
5632
6068
  value,
5633
6069
  detail,
6070
+ subvalue_json,
5634
6071
  trend,
5635
6072
  trend_tone
5636
6073
  )
5637
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
6074
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5638
6075
  `);
5639
6076
  const insertTokenRowStatement = this.database.prepare(`
5640
6077
  INSERT INTO usage_scope_token_rows (
@@ -5699,9 +6136,10 @@ class UsageCacheRepository {
5699
6136
  output_tokens,
5700
6137
  reasoning_output_tokens,
5701
6138
  total_tokens,
6139
+ cost_usd,
5702
6140
  is_fallback
5703
6141
  )
5704
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6142
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5705
6143
  `);
5706
6144
  const insertMonthlyModelStatement = this.database.prepare(`
5707
6145
  INSERT INTO usage_scope_monthly_model_usage (
@@ -5781,6 +6219,7 @@ class UsageCacheRepository {
5781
6219
  cached_input_tokens,
5782
6220
  output_tokens,
5783
6221
  reasoning_output_tokens,
6222
+ extra_total_tokens,
5784
6223
  total_tokens,
5785
6224
  usage_cost_usd,
5786
6225
  cache_creation_tokens,
@@ -5788,7 +6227,7 @@ class UsageCacheRepository {
5788
6227
  tool_tokens,
5789
6228
  is_fallback_model
5790
6229
  )
5791
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
6230
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5792
6231
  `);
5793
6232
  insertScopeStatement.run(
5794
6233
  scopeKey,
@@ -5812,6 +6251,7 @@ class UsageCacheRepository {
5812
6251
  card.name,
5813
6252
  card.value,
5814
6253
  (_j = card.detail) != null ? _j : null,
6254
+ card.subvalue ? JSON.stringify(card.subvalue) : null,
5815
6255
  card.trend,
5816
6256
  card.trendTone
5817
6257
  );
@@ -5865,6 +6305,7 @@ class UsageCacheRepository {
5865
6305
  usage.outputTokens,
5866
6306
  usage.reasoningOutputTokens,
5867
6307
  usage.totalTokens,
6308
+ usage.costUSD,
5868
6309
  usage.isFallback ? 1 : 0
5869
6310
  );
5870
6311
  }
@@ -5937,17 +6378,18 @@ class UsageCacheRepository {
5937
6378
  (_o = (_n = interaction.usage) == null ? void 0 : _n.cachedInputTokens) != null ? _o : null,
5938
6379
  (_q = (_p = interaction.usage) == null ? void 0 : _p.outputTokens) != null ? _q : null,
5939
6380
  (_s = (_r = interaction.usage) == null ? void 0 : _r.reasoningOutputTokens) != null ? _s : null,
5940
- (_u = (_t = interaction.usage) == null ? void 0 : _t.totalTokens) != null ? _u : null,
5941
- (_w = (_v = interaction.usage) == null ? void 0 : _v.costUSD) != null ? _w : null,
5942
- (_y = (_x = interaction.usage) == null ? void 0 : _x.cacheCreationTokens) != null ? _y : null,
5943
- (_A = (_z = interaction.usage) == null ? void 0 : _z.cacheReadTokens) != null ? _A : null,
5944
- (_C = (_B = interaction.usage) == null ? void 0 : _B.toolTokens) != null ? _C : null,
5945
- ((_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
5946
6388
  );
5947
6389
  }
5948
6390
  }
5949
6391
  }
5950
- loadHydratedUsageScopes(kind) {
6392
+ loadHydratedUsageScopes(kind, includeInteractions = true) {
5951
6393
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
5952
6394
  const scopes = this.database.prepare(`
5953
6395
  SELECT
@@ -5972,7 +6414,7 @@ class UsageCacheRepository {
5972
6414
  }
5973
6415
  const scopeSet = new Set(scopes.map((scope) => scope.scope_key));
5974
6416
  const overviewCards = groupOverviewCards(this.database.prepare(`
5975
- 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
5976
6418
  FROM usage_scope_overview_cards AS card
5977
6419
  JOIN usage_scopes AS scope ON scope.scope_key = card.scope_key
5978
6420
  WHERE scope.scope_kind = ?
@@ -6042,6 +6484,7 @@ class UsageCacheRepository {
6042
6484
  model.output_tokens,
6043
6485
  model.reasoning_output_tokens,
6044
6486
  model.total_tokens,
6487
+ model.cost_usd,
6045
6488
  model.is_fallback
6046
6489
  FROM usage_scope_daily_usage_models AS model
6047
6490
  JOIN usage_scopes AS scope ON scope.scope_key = model.scope_key
@@ -6111,33 +6554,34 @@ class UsageCacheRepository {
6111
6554
  WHERE scope.scope_kind = ?
6112
6555
  ORDER BY model.scope_key ASC, model.session_key ASC, model.model_order ASC
6113
6556
  `).all(kind),
6114
- this.database.prepare(`
6115
- SELECT
6116
- interaction.scope_key,
6117
- interaction.session_key,
6118
- interaction.interaction_order,
6119
- interaction.interaction_index,
6120
- interaction.content,
6121
- interaction.cost_usd,
6122
- interaction.model,
6123
- interaction.role,
6124
- interaction.timestamp,
6125
- interaction.type,
6126
- interaction.input_tokens,
6127
- interaction.cached_input_tokens,
6128
- interaction.output_tokens,
6129
- interaction.reasoning_output_tokens,
6130
- interaction.total_tokens,
6131
- interaction.usage_cost_usd,
6132
- interaction.cache_creation_tokens,
6133
- interaction.cache_read_tokens,
6134
- interaction.tool_tokens,
6135
- interaction.is_fallback_model
6136
- FROM usage_scope_interactions AS interaction
6137
- JOIN usage_scopes AS scope ON scope.scope_key = interaction.scope_key
6138
- WHERE scope.scope_kind = ?
6139
- ORDER BY interaction.scope_key ASC, interaction.session_key ASC, interaction.interaction_order ASC
6140
- `).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) : []
6141
6585
  );
6142
6586
  const hydrated = /* @__PURE__ */ new Map();
6143
6587
  for (const scope of scopes) {
@@ -6195,7 +6639,7 @@ class UsageCacheRepository {
6195
6639
  return null;
6196
6640
  }
6197
6641
  return {
6198
- payload: JSON.parse(row.payload),
6642
+ payload: normalizeProjectCatalogItems(JSON.parse(row.payload)),
6199
6643
  payloadHash: row.payload_hash,
6200
6644
  updatedAt: row.updated_at
6201
6645
  };
@@ -6242,6 +6686,54 @@ function getTokenRowsByBucket(usage, bucket) {
6242
6686
  return usage.weeklyRows;
6243
6687
  }
6244
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
+ }
6245
6737
  function groupProjectModels(rows) {
6246
6738
  var _a;
6247
6739
  const grouped = /* @__PURE__ */ new Map();
@@ -6291,6 +6783,7 @@ function groupOverviewCards(rows) {
6291
6783
  detail: (_b = row.detail) != null ? _b : void 0,
6292
6784
  icon: row.icon,
6293
6785
  name: row.name,
6786
+ subvalue: parseOverviewCardSubvalue(row.subvalue_json),
6294
6787
  trend: row.trend,
6295
6788
  trendTone: row.trend_tone,
6296
6789
  value: row.value
@@ -6299,6 +6792,17 @@ function groupOverviewCards(rows) {
6299
6792
  }
6300
6793
  return grouped;
6301
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
+ }
6302
6806
  function groupTokenRows(rows, modelRows, projectRows) {
6303
6807
  var _a, _b, _c, _d, _e;
6304
6808
  const modelsByRow = /* @__PURE__ */ new Map();
@@ -6350,6 +6854,7 @@ function groupDailyUsage(rows, modelRows) {
6350
6854
  const models = (_a = modelsByRow.get(key)) != null ? _a : {};
6351
6855
  models[row.model] = {
6352
6856
  cachedInputTokens: row.cached_input_tokens,
6857
+ costUSD: row.cost_usd,
6353
6858
  inputTokens: row.input_tokens,
6354
6859
  isFallback: Boolean(row.is_fallback),
6355
6860
  outputTokens: row.output_tokens,
@@ -6479,6 +6984,9 @@ function buildInteractionUsage(row) {
6479
6984
  reasoningOutputTokens: row.reasoning_output_tokens,
6480
6985
  totalTokens: row.total_tokens
6481
6986
  };
6987
+ if (row.extra_total_tokens !== null) {
6988
+ usage.extraTotalTokens = row.extra_total_tokens;
6989
+ }
6482
6990
  if (row.cache_creation_tokens !== null) {
6483
6991
  usage.cacheCreationTokens = row.cache_creation_tokens;
6484
6992
  }
@@ -6532,6 +7040,53 @@ function createCompositeKey(...parts) {
6532
7040
  function createPayloadHash(value) {
6533
7041
  return createHash("sha1").update(value).digest("hex");
6534
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
+ }
6535
7090
  function mkdirParentDirectory(filePath) {
6536
7091
  const directory = dirname$1(filePath);
6537
7092
  if (!existsSync(directory)) {
@@ -6539,234 +7094,389 @@ function mkdirParentDirectory(filePath) {
6539
7094
  }
6540
7095
  }
6541
7096
 
6542
- function normalizeStringValue(value) {
6543
- return typeof value === "string" ? value.trim() : void 0;
6544
- }
6545
- function normalizeStringList(value) {
6546
- if (Array.isArray(value)) {
6547
- 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
6548
7174
  }
6549
- if (typeof value === "string") {
6550
- 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;
6551
7185
  }
6552
- return void 0;
6553
- }
6554
- function normalizeUnknownRecord(value) {
6555
- return value && typeof value === "object" && !Array.isArray(value) ? value : null;
6556
- }
6557
- function normalizeFiniteNumberOrNull(value) {
6558
- return typeof value === "number" && Number.isFinite(value) ? value : null;
6559
- }
6560
- function normalizeTimestampValue(value) {
6561
- const normalizedValue = normalizeStringValue(value);
6562
- return normalizedValue && Number.isFinite(Date.parse(normalizedValue)) ? normalizedValue : null;
6563
- }
6564
- function splitCommaValues(value) {
6565
- return value.split(",").map((item) => item.trim()).filter(Boolean);
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;
6566
7224
  }
6567
-
6568
- const compactNumberFormatter = new Intl.NumberFormat("en-US", {
6569
- notation: "compact",
6570
- maximumFractionDigits: 1
6571
- });
6572
- const currencyFormatter = new Intl.NumberFormat("en-US", {
6573
- style: "currency",
6574
- currency: "USD",
6575
- minimumFractionDigits: 2,
6576
- maximumFractionDigits: 2
6577
- });
6578
- const percentFormatter = new Intl.NumberFormat("en-US", {
6579
- style: "percent",
6580
- minimumFractionDigits: 1,
6581
- maximumFractionDigits: 1
6582
- });
6583
- const dateKeyFormatter = new Intl.DateTimeFormat("en-CA", {
6584
- day: "2-digit",
6585
- month: "2-digit",
6586
- year: "numeric"
6587
- });
6588
- const dateLabelFormatter = new Intl.DateTimeFormat("en-US", {
6589
- day: "2-digit",
6590
- month: "short",
6591
- timeZone: "UTC",
6592
- year: "numeric"
6593
- });
6594
- function normalizeNumber(value) {
6595
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
6596
- }
6597
- function roundCurrency(value) {
6598
- return Math.round(value * 1e6) / 1e6;
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
+ };
6599
7256
  }
6600
- function uniqueItems(items) {
6601
- return Array.from(new Set(items));
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;
6602
7265
  }
6603
- function formatCompactNumber(value) {
6604
- return compactNumberFormatter.format(normalizeNumber(value));
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
+ };
6605
7340
  }
6606
- function formatCurrency(value) {
6607
- return currencyFormatter.format(normalizeNumber(value));
7341
+ function isLiteLLMPricingDataset(value) {
7342
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7343
+ return false;
7344
+ }
7345
+ return true;
6608
7346
  }
6609
- function formatPercent(value) {
6610
- return percentFormatter.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
+ ];
6611
7355
  }
6612
- function buildGrowthTrend(currentValue, previousValue, formatValue) {
6613
- const current = Math.max(normalizeNumber(currentValue), 0);
6614
- const previous = Math.max(normalizeNumber(previousValue), 0);
6615
- if (previous === 0) {
6616
- if (current === 0) {
6617
- return {
6618
- trend: "0.0%",
6619
- trendTone: "neutral"
6620
- };
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));
6621
7363
  }
6622
- return {
6623
- trend: `+${formatValue(current)}`,
6624
- trendTone: "up"
6625
- };
6626
7364
  }
6627
- const ratio = (current - previous) / previous;
6628
- return {
6629
- trend: formatSignedPercent(ratio),
6630
- trendTone: getTrendTone(ratio)
6631
- };
7365
+ return expanded;
6632
7366
  }
6633
- function getDateKey(date) {
6634
- var _a, _b, _c, _d, _e, _f;
6635
- const parts = dateKeyFormatter.formatToParts(date);
6636
- const year = (_b = (_a = parts.find((part) => part.type === "year")) == null ? void 0 : _a.value) != null ? _b : "0000";
6637
- const month = (_d = (_c = parts.find((part) => part.type === "month")) == null ? void 0 : _c.value) != null ? _d : "01";
6638
- const day = (_f = (_e = parts.find((part) => part.type === "day")) == null ? void 0 : _e.value) != null ? _f : "01";
6639
- return `${year}-${month}-${day}`;
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;
6640
7376
  }
6641
- function getDateKeyFromLabel(label) {
6642
- const date = new Date(label);
6643
- if (Number.isNaN(date.getTime())) {
6644
- return label;
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 };
7384
+ }
6645
7385
  }
6646
- return getDateKey(date);
7386
+ return null;
6647
7387
  }
6648
- function getPreviousDateKey(dateKey) {
6649
- const [year, month, day] = dateKey.split("-").map((value) => Number.parseInt(value, 10));
6650
- const date = new Date(year || 0, (month || 1) - 1, day || 1);
6651
- date.setDate(date.getDate() - 1);
6652
- return getDateKey(date);
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;
6653
7391
  }
6654
- function formatDateLabelFromDateKey(dateKey, fallback = dateKey) {
6655
- const [year, month, day] = dateKey.split("-").map((value) => Number.parseInt(value, 10));
6656
- if (!year || !month || !day) {
6657
- return fallback;
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;
7398
+ return {
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
7408
+ };
7409
+ }
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
+ }
6658
7416
  }
6659
- return dateLabelFormatter.format(new Date(Date.UTC(year, month - 1, day)));
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;
6660
7428
  }
6661
- function buildProjectUsage(sessionUsage) {
6662
- var _a;
6663
- const projects = /* @__PURE__ */ new Map();
6664
- for (const session of sessionUsage) {
6665
- const project = (_a = projects.get(session.project)) != null ? _a : {
6666
- costUSD: 0,
6667
- repository: session.repository,
6668
- sessions: 0,
6669
- tokenTotal: 0
6670
- };
6671
- project.costUSD += session.costUSD;
6672
- project.sessions += 1;
6673
- project.tokenTotal += session.tokenTotal;
6674
- projects.set(session.project, project);
7429
+ function matchesModelSuffix(part, base) {
7430
+ const index = part.lastIndexOf(base);
7431
+ if (index < 0) {
7432
+ return false;
6675
7433
  }
6676
- const maxCost = Math.max(...Array.from(projects.values()).map((project) => project.costUSD), 0);
6677
- return Array.from(projects.entries()).map(([label, project]) => ({
6678
- costUSD: project.costUSD,
6679
- detail: `${project.sessions} sessions / ${formatCompactNumber(project.tokenTotal)} tokens`,
6680
- label,
6681
- percent: maxCost > 0 ? project.costUSD / maxCost * 100 : 0,
6682
- repository: project.repository,
6683
- sessions: project.sessions,
6684
- tokenTotal: project.tokenTotal,
6685
- tone: "amber",
6686
- value: formatCurrency(project.costUSD)
6687
- })).sort((a, b) => b.costUSD - a.costUSD);
7434
+ const suffix = part.slice(index);
7435
+ return suffix === base || suffix[base.length] === "-";
6688
7436
  }
6689
- function mergeDailyTokenUsage(items) {
6690
- var _a, _b;
6691
- const groups = /* @__PURE__ */ new Map();
6692
- for (const item of items) {
6693
- const dateKey = getDateKeyFromLabel(item.date);
6694
- const group = (_a = groups.get(dateKey)) != null ? _a : {
6695
- cachedInputTokens: 0,
6696
- costUSD: 0,
6697
- date: formatDateLabelFromDateKey(dateKey, item.date),
6698
- inputTokens: 0,
6699
- models: /* @__PURE__ */ new Map(),
6700
- outputTokens: 0,
6701
- reasoningOutputTokens: 0,
6702
- totalTokens: 0
6703
- };
6704
- group.cachedInputTokens += item.cachedInputTokens;
6705
- group.costUSD += item.costUSD;
6706
- group.inputTokens += item.inputTokens;
6707
- group.outputTokens += item.outputTokens;
6708
- group.reasoningOutputTokens += item.reasoningOutputTokens;
6709
- group.totalTokens += item.totalTokens;
6710
- for (const [modelName, usage] of Object.entries(item.models)) {
6711
- const model = (_b = group.models.get(modelName)) != null ? _b : createEmptyModelUsage();
6712
- model.cachedInputTokens += usage.cachedInputTokens;
6713
- model.inputTokens += usage.inputTokens;
6714
- model.isFallback = model.isFallback || usage.isFallback;
6715
- model.outputTokens += usage.outputTokens;
6716
- model.reasoningOutputTokens += usage.reasoningOutputTokens;
6717
- model.totalTokens += usage.totalTokens;
6718
- group.models.set(modelName, model);
6719
- }
6720
- groups.set(dateKey, group);
6721
- }
6722
- return Array.from(groups.entries()).sort((a, b) => b[0].localeCompare(a[0])).map(([, group]) => ({
6723
- cachedInputTokens: group.cachedInputTokens,
6724
- costUSD: roundCurrency(group.costUSD),
6725
- date: group.date,
6726
- inputTokens: group.inputTokens,
6727
- models: Object.fromEntries(group.models.entries()),
6728
- outputTokens: group.outputTokens,
6729
- reasoningOutputTokens: group.reasoningOutputTokens,
6730
- totalTokens: group.totalTokens
6731
- }));
7437
+ function createZeroPricing() {
7438
+ return {
7439
+ cachedInputCostPerMTokens: 0,
7440
+ cacheCreationInputCostPerMTokens: 0,
7441
+ inputCostPerMTokens: 0,
7442
+ outputCostPerMTokens: 0
7443
+ };
6732
7444
  }
6733
- function mergeMonthlyModelUsage(items) {
6734
- var _a;
6735
- const groups = /* @__PURE__ */ new Map();
6736
- for (const item of items) {
6737
- const key = `${item.month}__${item.model}`;
6738
- const group = (_a = groups.get(key)) != null ? _a : {
6739
- model: item.model,
6740
- month: item.month,
6741
- tokenTotal: 0
6742
- };
6743
- group.tokenTotal += item.tokenTotal;
6744
- groups.set(key, group);
7445
+ function calculateTieredCost(tokens, baseCostPerMTokens, above200KCostPerMTokens) {
7446
+ const safeTokens = Math.max(tokens != null ? tokens : 0, 0);
7447
+ if (safeTokens === 0) {
7448
+ return 0;
6745
7449
  }
6746
- return Array.from(groups.values()).sort((a, b) => a.month.localeCompare(b.month) || a.model.localeCompare(b.model));
7450
+ if (safeTokens > 2e5 && above200KCostPerMTokens != null) {
7451
+ return 2e5 / MILLION * baseCostPerMTokens + (safeTokens - 2e5) / MILLION * above200KCostPerMTokens;
7452
+ }
7453
+ return safeTokens / MILLION * baseCostPerMTokens;
6747
7454
  }
6748
- function formatSignedPercent(value) {
6749
- const prefix = value > 0 ? "+" : "";
6750
- return `${prefix}${formatPercent(value)}`;
7455
+
7456
+ function normalizeStringValue(value) {
7457
+ return typeof value === "string" ? value.trim() : void 0;
6751
7458
  }
6752
- function getTrendTone(value) {
6753
- if (value > 0) {
6754
- return "up";
7459
+ function normalizeStringList(value) {
7460
+ if (Array.isArray(value)) {
7461
+ return value.flatMap((item) => typeof item === "string" ? splitCommaValues(item) : []);
6755
7462
  }
6756
- if (value < 0) {
6757
- return "down";
7463
+ if (typeof value === "string") {
7464
+ return splitCommaValues(value);
6758
7465
  }
6759
- return "neutral";
7466
+ return void 0;
6760
7467
  }
6761
- function createEmptyModelUsage() {
6762
- return {
6763
- cachedInputTokens: 0,
6764
- inputTokens: 0,
6765
- isFallback: false,
6766
- outputTokens: 0,
6767
- reasoningOutputTokens: 0,
6768
- totalTokens: 0
6769
- };
7468
+ function normalizeUnknownRecord(value) {
7469
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
7470
+ }
7471
+ function normalizeFiniteNumberOrNull(value) {
7472
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
7473
+ }
7474
+ function normalizeTimestampValue(value) {
7475
+ const normalizedValue = normalizeStringValue(value);
7476
+ return normalizedValue && Number.isFinite(Date.parse(normalizedValue)) ? normalizedValue : null;
7477
+ }
7478
+ function splitCommaValues(value) {
7479
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
6770
7480
  }
6771
7481
 
6772
7482
  function parseJsonlFile(filePath) {
@@ -6813,9 +7523,11 @@ function buildDailyUsageGroups(events, options = {}) {
6813
7523
  if (shouldIncludeModel(event, options)) {
6814
7524
  const modelUsage = (_b = group.modelUsage.get(event.model)) != null ? _b : {
6815
7525
  ...createEmptyUsage(),
7526
+ costUSD: 0,
6816
7527
  isFallback: false
6817
7528
  };
6818
7529
  addUsage(modelUsage, event);
7530
+ modelUsage.costUSD += getEventCostUSD(event, options);
6819
7531
  if (event.isFallbackModel) {
6820
7532
  modelUsage.isFallback = true;
6821
7533
  }
@@ -6833,6 +7545,7 @@ function buildDailyTokenUsage(dailyGroups) {
6833
7545
  inputTokens: group.inputTokens,
6834
7546
  models: Object.fromEntries(Array.from(group.modelUsage.entries()).map(([model, usage]) => [model, {
6835
7547
  cachedInputTokens: usage.cachedInputTokens,
7548
+ costUSD: roundCurrency(usage.costUSD),
6836
7549
  inputTokens: usage.inputTokens,
6837
7550
  isFallback: usage.isFallback,
6838
7551
  outputTokens: usage.outputTokens,
@@ -6973,6 +7686,10 @@ function buildOverviewCards(options) {
6973
7686
  detail: `${formatNumber(options.todayTotalTokens)} tokens used today`,
6974
7687
  icon: "solar:cpu-line-duotone",
6975
7688
  name: "Today Tokens",
7689
+ subvalue: buildInputOutputTokenSubvalue({
7690
+ inputTokens: options.todayInputTokens,
7691
+ outputTokens: options.todayOutputTokens
7692
+ }),
6976
7693
  trend: tokenTrend.trend,
6977
7694
  trendTone: tokenTrend.trendTone,
6978
7695
  value: formatCompactNumber(options.todayTotalTokens)
@@ -7004,7 +7721,7 @@ function buildOverviewCards(options) {
7004
7721
  ];
7005
7722
  }
7006
7723
  function buildLoadUsageResult(events, sessions, options = {}) {
7007
- var _a, _b, _c, _d, _e, _f;
7724
+ var _a, _b, _c, _d, _e, _f, _g, _h;
7008
7725
  const aggregateOptions = (_a = options.aggregateOptions) != null ? _a : {};
7009
7726
  const sessionOptions = (_b = options.sessionOptions) != null ? _b : {};
7010
7727
  const sortedSessions = [...sessions].sort((a, b) => getSessionSortTimestamp(b) - getSessionSortTimestamp(a));
@@ -7027,6 +7744,8 @@ function buildLoadUsageResult(events, sessions, options = {}) {
7027
7744
  overviewCards: buildOverviewCards({
7028
7745
  previousDayCost: roundCurrency((_e = previousDayDailyGroup == null ? void 0 : previousDayDailyGroup.costUSD) != null ? _e : 0),
7029
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,
7030
7749
  todayTopModel,
7031
7750
  todayTopProject,
7032
7751
  todayTotalCost,
@@ -7045,769 +7764,421 @@ function buildLoadUsageResult(events, sessions, options = {}) {
7045
7764
  function getTopProjectForDate(events) {
7046
7765
  var _a;
7047
7766
  const projects = /* @__PURE__ */ new Map();
7048
- for (const event of events) {
7049
- const sessions = (_a = projects.get(event.project)) != null ? _a : /* @__PURE__ */ new Set();
7050
- sessions.add(event.sessionId);
7051
- projects.set(event.project, sessions);
7052
- }
7053
- const topProject = Array.from(projects.entries()).map(([project, sessions]) => ({ project, sessionCount: sessions.size })).sort((a, b) => b.sessionCount - a.sessionCount || a.project.localeCompare(b.project))[0];
7054
- return topProject != null ? topProject : null;
7055
- }
7056
- function getTopModelForDate(events, options = {}) {
7057
- var _a;
7058
- const models = /* @__PURE__ */ new Map();
7059
- for (const event of events) {
7060
- if (!shouldIncludeModel(event, options)) {
7061
- continue;
7062
- }
7063
- models.set(event.model, ((_a = models.get(event.model)) != null ? _a : 0) + event.totalTokens);
7064
- }
7065
- const topModel = Array.from(models.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
7066
- return topModel ? {
7067
- model: topModel[0],
7068
- totalTokens: topModel[1]
7069
- } : null;
7070
- }
7071
- function createAggregateGroup(label) {
7072
- return {
7073
- cachedInputTokens: 0,
7074
- costUSD: 0,
7075
- inputTokens: 0,
7076
- label,
7077
- models: [],
7078
- outputTokens: 0,
7079
- projects: [],
7080
- reasoningOutputTokens: 0,
7081
- sessionCount: 0,
7082
- totalTokens: 0
7083
- };
7084
- }
7085
- function createEmptyUsage() {
7086
- return {
7087
- cachedInputTokens: 0,
7088
- inputTokens: 0,
7089
- outputTokens: 0,
7090
- reasoningOutputTokens: 0,
7091
- totalTokens: 0
7092
- };
7093
- }
7094
- function addUsage(target, usage) {
7095
- target.inputTokens += usage.inputTokens;
7096
- target.cachedInputTokens += usage.cachedInputTokens;
7097
- target.outputTokens += usage.outputTokens;
7098
- target.reasoningOutputTokens += usage.reasoningOutputTokens;
7099
- target.totalTokens += usage.totalTokens;
7100
- }
7101
- function isZeroUsage(usage) {
7102
- return usage.inputTokens === 0 && usage.cachedInputTokens === 0 && usage.outputTokens === 0 && usage.reasoningOutputTokens === 0 && usage.totalTokens === 0;
7103
- }
7104
- function getProjectName(path, fallback = "unknown") {
7105
- var _a;
7106
- if (!path) {
7107
- return fallback;
7108
- }
7109
- const parts = path.split("/").filter(Boolean);
7110
- return (_a = parts.at(-1)) != null ? _a : fallback;
7111
- }
7112
- function normalizeRepositoryUrl(repositoryUrl) {
7113
- if (!repositoryUrl) {
7114
- return "";
7115
- }
7116
- return repositoryUrl.replace(/^git@[^:]+:/u, "").replace(/^https?:\/\/[^/]+\//u, "").replace(/\.git$/u, "");
7117
- }
7118
- function getDurationMinutes(startedAt, endedAt) {
7119
- if (!endedAt) {
7120
- return 0;
7121
- }
7122
- const durationMs = Date.parse(endedAt) - Date.parse(startedAt);
7123
- if (!Number.isFinite(durationMs) || durationMs <= 0) {
7124
- return 0;
7125
- }
7126
- return Math.round(durationMs / 6e4);
7127
- }
7128
- function toIsoString(value) {
7129
- if (typeof value !== "string") {
7130
- return null;
7131
- }
7132
- const timestamp = Date.parse(value);
7133
- return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
7134
- }
7135
- function getMonthKey(date) {
7136
- return getDateKey(date).slice(0, 7);
7137
- }
7138
- function getWeekLabel(date) {
7139
- const weekStart = cloneDate(date);
7140
- const day = weekStart.getDay();
7141
- const diff = day === 0 ? -6 : 1 - day;
7142
- weekStart.setDate(weekStart.getDate() + diff);
7143
- const weekEnd = cloneDate(weekStart);
7144
- weekEnd.setDate(weekEnd.getDate() + 6);
7145
- return `${getDateKey(weekStart)} - ${getDateKey(weekEnd)}`;
7146
- }
7147
- function formatMonthLabel(monthKey) {
7148
- const [year, month] = monthKey.split("-").map((value) => Number.parseInt(value, 10));
7149
- const date = new Date(Date.UTC(year || 0, (month || 1) - 1, 1));
7150
- return new Intl.DateTimeFormat("en-US", {
7151
- month: "short",
7152
- timeZone: "UTC",
7153
- year: "numeric"
7154
- }).format(date);
7155
- }
7156
- function formatDuration(minutes) {
7157
- const hours = Math.floor(minutes / 60);
7158
- const remainingMinutes = minutes % 60;
7159
- if (hours === 0) {
7160
- return `${remainingMinutes}m`;
7161
- }
7162
- if (remainingMinutes === 0) {
7163
- return `${hours}h`;
7164
- }
7165
- return `${hours}h ${remainingMinutes}m`;
7166
- }
7167
- function normalizeRawUsage(usage) {
7168
- var _a;
7169
- if (!usage) {
7170
- return null;
7171
- }
7172
- const input = normalizeNumber(usage.input_tokens);
7173
- const cachedInput = normalizeNumber((_a = usage.cached_input_tokens) != null ? _a : usage.cache_read_input_tokens);
7174
- const output = normalizeNumber(usage.output_tokens);
7175
- const reasoning = normalizeNumber(usage.reasoning_output_tokens);
7176
- const total = normalizeNumber(usage.total_tokens);
7177
- return {
7178
- cached_input_tokens: cachedInput,
7179
- input_tokens: input,
7180
- output_tokens: output,
7181
- reasoning_output_tokens: reasoning,
7182
- total_tokens: total > 0 ? total : input + output
7183
- };
7184
- }
7185
- function subtractRawUsage(current, previous) {
7186
- var _a, _b, _c, _d, _e;
7187
- return {
7188
- cached_input_tokens: Math.max(current.cached_input_tokens - ((_a = previous == null ? void 0 : previous.cached_input_tokens) != null ? _a : 0), 0),
7189
- input_tokens: Math.max(current.input_tokens - ((_b = previous == null ? void 0 : previous.input_tokens) != null ? _b : 0), 0),
7190
- output_tokens: Math.max(current.output_tokens - ((_c = previous == null ? void 0 : previous.output_tokens) != null ? _c : 0), 0),
7191
- reasoning_output_tokens: Math.max(current.reasoning_output_tokens - ((_d = previous == null ? void 0 : previous.reasoning_output_tokens) != null ? _d : 0), 0),
7192
- total_tokens: Math.max(current.total_tokens - ((_e = previous == null ? void 0 : previous.total_tokens) != null ? _e : 0), 0)
7193
- };
7194
- }
7195
- function convertCodexRawUsage(rawUsage) {
7196
- const cachedInputTokens = Math.min(rawUsage.cached_input_tokens, rawUsage.input_tokens);
7197
- const inputTokens = Math.max(rawUsage.input_tokens - cachedInputTokens, 0);
7198
- const outputTokens = Math.max(rawUsage.output_tokens, 0);
7199
- return {
7200
- cachedInputTokens,
7201
- inputTokens,
7202
- outputTokens,
7203
- reasoningOutputTokens: Math.max(rawUsage.reasoning_output_tokens, 0),
7204
- totalTokens: rawUsage.total_tokens > 0 ? rawUsage.total_tokens : inputTokens + outputTokens
7205
- };
7206
- }
7207
- function convertGeminiTokenUsage(tokens) {
7208
- const rawInputTokens = normalizeNumber(tokens.input);
7209
- const cachedInputTokens = Math.min(normalizeNumber(tokens.cached), rawInputTokens);
7210
- const outputTokens = normalizeNumber(tokens.output);
7211
- const reasoningOutputTokens = normalizeNumber(tokens.thoughts);
7212
- const toolTokens = normalizeNumber(tokens.tool);
7213
- const totalTokens = normalizeNumber(tokens.total);
7214
- return {
7215
- cachedInputTokens,
7216
- inputTokens: Math.max(rawInputTokens - cachedInputTokens, 0),
7217
- outputTokens,
7218
- reasoningOutputTokens,
7219
- totalTokens: totalTokens > 0 ? totalTokens : rawInputTokens + outputTokens + reasoningOutputTokens + toolTokens
7220
- };
7221
- }
7222
- function extractModelName(value) {
7223
- if (!value || typeof value !== "object") {
7224
- return void 0;
7225
- }
7226
- const record = value;
7227
- const info = normalizeUnknownRecord(record.info);
7228
- const metadata = normalizeUnknownRecord(record.metadata);
7229
- const infoMetadata = normalizeUnknownRecord(info == null ? void 0 : info.metadata);
7230
- const candidates = [
7231
- record.model,
7232
- record.model_name,
7233
- info == null ? void 0 : info.model,
7234
- info == null ? void 0 : info.model_name,
7235
- infoMetadata == null ? void 0 : infoMetadata.model,
7236
- metadata == null ? void 0 : metadata.model
7237
- ];
7238
- for (const candidate of candidates) {
7239
- if (typeof candidate === "string" && candidate.trim() !== "") {
7240
- return candidate.trim();
7241
- }
7242
- }
7243
- return void 0;
7244
- }
7245
- function extractClaudeProjectFromPath(jsonlPath) {
7246
- var _a;
7247
- const normalizedPath = jsonlPath.replace(/[/\\]/g, sep);
7248
- const segments = normalizedPath.split(sep);
7249
- const projectsIndex = segments.findIndex((segment) => segment === "projects");
7250
- if (projectsIndex === -1 || projectsIndex + 1 >= segments.length) {
7251
- return "unknown";
7252
- }
7253
- return ((_a = segments[projectsIndex + 1]) == null ? void 0 : _a.trim()) || "unknown";
7254
- }
7255
- function decodeClaudeProjectPath(projectPath) {
7256
- var _a;
7257
- const normalized = projectPath.replace(/^-/, "").replace(/-/g, "/");
7258
- const parts = normalized.split("/").filter(Boolean);
7259
- return (_a = parts.at(-1)) != null ? _a : projectPath;
7260
- }
7261
- function getClaudeLookupCandidates(model) {
7262
- var _a;
7263
- const normalizedModel = model.trim();
7264
- const withoutFastSuffix = normalizedModel.replace(/-fast$/u, "");
7265
- const baseModel = (_a = withoutFastSuffix.split("/").at(-1)) != null ? _a : withoutFastSuffix;
7266
- const normalizedBaseModel = baseModel.replace(/[.@]/gu, "-");
7267
- const baseModelWithoutDate = normalizedBaseModel.replace(/-\d{8}$/u, "");
7268
- return [
7269
- normalizedModel,
7270
- withoutFastSuffix,
7271
- withoutFastSuffix.replace(/^anthropic\//u, ""),
7272
- `anthropic/${normalizedModel}`,
7273
- baseModel,
7274
- normalizedBaseModel,
7275
- baseModelWithoutDate,
7276
- `anthropic/${baseModelWithoutDate}`,
7277
- baseModelWithoutDate.replace(/^claude-3-5-/u, "claude-"),
7278
- baseModelWithoutDate.replace(/^claude-3-7-/u, "claude-")
7279
- ];
7280
- }
7281
- function getGeminiLookupCandidates(model) {
7282
- const normalizedModel = model.trim();
7283
- return [
7284
- normalizedModel,
7285
- normalizedModel.replace(/^gemini\//u, ""),
7286
- normalizedModel.replace(/^google\//u, ""),
7287
- `gemini/${normalizedModel}`,
7288
- `google/${normalizedModel}`
7289
- ];
7290
- }
7291
- function isOpenRouterFreeModel(model) {
7292
- const normalizedModel = model.trim().toLowerCase();
7293
- return normalizedModel === "openrouter/free" || normalizedModel.startsWith("openrouter/") && normalizedModel.endsWith(":free");
7294
- }
7295
- function getGeminiProjectRoot(filePath) {
7296
- const projectDir = dirname$1(dirname$1(filePath));
7297
- const projectRootFile = `${projectDir}/.project_root`;
7298
- if (!existsSync(projectRootFile)) {
7299
- return "";
7300
- }
7301
- try {
7302
- return readFileSync(projectRootFile, "utf8").trim();
7303
- } catch {
7304
- return "";
7305
- }
7306
- }
7307
- function getGeminiProjectKeyFromPath(filePath) {
7308
- var _a;
7309
- const normalizedPath = filePath.replace(/[/\\]/g, sep);
7310
- const segments = normalizedPath.split(sep);
7311
- const tmpIndex = segments.findIndex((segment) => segment === "tmp");
7312
- if (tmpIndex === -1 || tmpIndex + 1 >= segments.length) {
7313
- return "unknown";
7314
- }
7315
- return ((_a = segments[tmpIndex + 1]) == null ? void 0 : _a.trim()) || "unknown";
7316
- }
7317
- function getRepositoryNameFromProjectRoot(projectRoot) {
7318
- if (!projectRoot) {
7319
- return "";
7320
- }
7321
- const gitConfigPath = `${projectRoot}/.git/config`;
7322
- if (!existsSync(gitConfigPath)) {
7323
- return "";
7324
- }
7325
- try {
7326
- return normalizeRepositoryUrl(getOriginUrlFromGitConfig(readFileSync(gitConfigPath, "utf8")));
7327
- } catch {
7328
- return "";
7329
- }
7330
- }
7331
- function getOriginUrlFromGitConfig(config) {
7332
- let isOriginBlock = false;
7333
- for (const rawLine of config.split("\n")) {
7334
- const line = rawLine.trim();
7335
- if (line.startsWith("[")) {
7336
- isOriginBlock = line === '[remote "origin"]';
7337
- continue;
7338
- }
7339
- if (!isOriginBlock || !line.startsWith("url =")) {
7340
- continue;
7341
- }
7342
- return line.slice("url =".length).trim();
7343
- }
7344
- return "";
7345
- }
7346
- function extractGeminiMessageText(content) {
7347
- if (typeof content === "string") {
7348
- return content;
7349
- }
7350
- if (!Array.isArray(content)) {
7351
- return "";
7352
- }
7353
- return content.map((item) => {
7354
- var _a;
7355
- return (_a = item.text) == null ? void 0 : _a.trim();
7356
- }).filter(Boolean).join("\n");
7357
- }
7358
- function addEventToAggregateGroup(group, event, options) {
7359
- group.inputTokens += event.inputTokens;
7360
- group.cachedInputTokens += event.cachedInputTokens;
7361
- group.outputTokens += event.outputTokens;
7362
- group.reasoningOutputTokens += event.reasoningOutputTokens;
7363
- group.totalTokens += event.totalTokens;
7364
- group.costUSD += getEventCostUSD(event, options);
7365
- group.models = shouldIncludeModel(event, options) ? uniqueItems([...group.models, event.model]) : group.models;
7366
- group.projects = uniqueItems([...group.projects, event.project]);
7367
- }
7368
- function getEventCostUSD(event, options) {
7369
- var _a, _b, _c;
7370
- return (_c = (_b = (_a = options.getCostUSD) == null ? void 0 : _a.call(options, event)) != null ? _b : event.costUSD) != null ? _c : 0;
7371
- }
7372
- function shouldIncludeModel(event, options) {
7373
- var _a, _b;
7374
- return (_b = (_a = options.includeModel) == null ? void 0 : _a.call(options, event)) != null ? _b : true;
7375
- }
7376
- function getCachedInputTokens(session, options) {
7377
- var _a, _b;
7378
- return (_b = (_a = options.getCachedInputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "cachedInputTokens");
7379
- }
7380
- function getReasoningOutputTokens(session, options) {
7381
- var _a, _b;
7382
- return (_b = (_a = options.getReasoningOutputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "reasoningOutputTokens");
7383
- }
7384
- function getNumericProperty(value, key) {
7385
- const record = value;
7386
- const property = record[key];
7387
- return typeof property === "number" && Number.isFinite(property) ? property : 0;
7388
- }
7389
- function cloneDate(date) {
7390
- return new Date(date.getFullYear(), date.getMonth(), date.getDate());
7391
- }
7392
-
7393
- function toDiscoveredUsageFile(filePath, platform, cacheSignature = "") {
7394
- try {
7395
- const stats = statSync(filePath);
7396
- return [{
7397
- cacheSignature,
7398
- mtimeMs: stats.mtimeMs,
7399
- path: filePath,
7400
- platform,
7401
- size: stats.size
7402
- }];
7403
- } catch {
7404
- return [];
7767
+ for (const event of events) {
7768
+ const sessions = (_a = projects.get(event.project)) != null ? _a : /* @__PURE__ */ new Set();
7769
+ sessions.add(event.sessionId);
7770
+ projects.set(event.project, sessions);
7405
7771
  }
7772
+ const topProject = Array.from(projects.entries()).map(([project, sessions]) => ({ project, sessionCount: sessions.size })).sort((a, b) => b.sessionCount - a.sessionCount || a.project.localeCompare(b.project))[0];
7773
+ return topProject != null ? topProject : null;
7406
7774
  }
7407
- function createSessionFragment(options) {
7775
+ function getTopModelForDate(events, options = {}) {
7776
+ var _a;
7777
+ const models = /* @__PURE__ */ new Map();
7778
+ for (const event of events) {
7779
+ if (!shouldIncludeModel(event, options)) {
7780
+ continue;
7781
+ }
7782
+ models.set(event.model, ((_a = models.get(event.model)) != null ? _a : 0) + event.totalTokens);
7783
+ }
7784
+ const topModel = Array.from(models.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
7785
+ return topModel ? {
7786
+ model: topModel[0],
7787
+ totalTokens: topModel[1]
7788
+ } : null;
7789
+ }
7790
+ function createAggregateGroup(label) {
7408
7791
  return {
7409
- durationEndAt: "",
7410
- interactions: [],
7411
- key: getSessionLookupKey(options.repository, options.sessionId),
7412
- project: options.project,
7413
- repository: options.repository,
7414
- sessionId: options.sessionId,
7415
- startedAt: options.startedAt,
7416
- threadName: options.threadName
7792
+ cachedInputTokens: 0,
7793
+ costUSD: 0,
7794
+ inputTokens: 0,
7795
+ label,
7796
+ models: [],
7797
+ outputTokens: 0,
7798
+ projects: [],
7799
+ reasoningOutputTokens: 0,
7800
+ sessionCount: 0,
7801
+ totalTokens: 0
7417
7802
  };
7418
7803
  }
7419
- function addFragmentInteraction(fragment, interaction) {
7420
- fragment.interactions.push(interaction);
7421
- if (!interaction.timestamp) {
7422
- return;
7423
- }
7424
- if (!fragment.startedAt || Date.parse(interaction.timestamp) < Date.parse(fragment.startedAt)) {
7425
- fragment.startedAt = interaction.timestamp;
7426
- }
7427
- if (!fragment.durationEndAt || Date.parse(interaction.timestamp) > Date.parse(fragment.durationEndAt)) {
7428
- fragment.durationEndAt = interaction.timestamp;
7429
- }
7804
+ function createEmptyUsage() {
7805
+ return {
7806
+ cachedInputTokens: 0,
7807
+ inputTokens: 0,
7808
+ outputTokens: 0,
7809
+ reasoningOutputTokens: 0,
7810
+ totalTokens: 0
7811
+ };
7430
7812
  }
7431
- function getSessionLookupKey(project, sessionId) {
7432
- return `${project}:${sessionId}`;
7813
+ function addUsage(target, usage) {
7814
+ target.inputTokens += usage.inputTokens;
7815
+ target.cachedInputTokens += usage.cachedInputTokens;
7816
+ target.outputTokens += usage.outputTokens;
7817
+ target.reasoningOutputTokens += usage.reasoningOutputTokens;
7818
+ target.totalTokens += usage.totalTokens;
7433
7819
  }
7434
- function normalizeRole(value) {
7435
- const normalized = value.toLowerCase();
7436
- if (normalized.includes("user")) {
7437
- return "user";
7438
- }
7439
- if (normalized.includes("assistant") || normalized.includes("agent") || normalized.includes("gemini")) {
7440
- return "assistant";
7820
+ function isZeroUsage(usage) {
7821
+ return usage.inputTokens === 0 && usage.cachedInputTokens === 0 && usage.outputTokens === 0 && usage.reasoningOutputTokens === 0 && usage.totalTokens === 0;
7822
+ }
7823
+ function getProjectName(path, fallback = "unknown") {
7824
+ var _a;
7825
+ if (!path) {
7826
+ return fallback;
7441
7827
  }
7442
- if (normalized.includes("system")) {
7443
- return "system";
7828
+ const parts = path.split("/").filter(Boolean);
7829
+ return (_a = parts.at(-1)) != null ? _a : fallback;
7830
+ }
7831
+ function normalizeRepositoryUrl(repositoryUrl) {
7832
+ if (!repositoryUrl) {
7833
+ return "";
7444
7834
  }
7445
- if (normalized.includes("tool")) {
7446
- return "tool";
7835
+ return repositoryUrl.replace(/^git@[^:]+:/u, "").replace(/^https?:\/\/[^/]+\//u, "").replace(/\.git$/u, "");
7836
+ }
7837
+ function getDurationMinutes(startedAt, endedAt) {
7838
+ if (!endedAt) {
7839
+ return 0;
7447
7840
  }
7448
- if (normalized.includes("token") || normalized.includes("usage")) {
7449
- return "usage";
7841
+ const durationMs = Date.parse(endedAt) - Date.parse(startedAt);
7842
+ if (!Number.isFinite(durationMs) || durationMs <= 0) {
7843
+ return 0;
7450
7844
  }
7451
- return "unknown";
7845
+ return Math.round(durationMs / 6e4);
7452
7846
  }
7453
-
7454
- const MILLION = 1e6;
7455
- const DEFAULT_PRICING_CACHE_TTL_MS = 1e3 * 60 * 5;
7456
- const DEFAULT_LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
7457
- const FAST_MULTIPLIER_EXACT_OVERRIDES = {
7458
- "gpt-5.3-codex": 2,
7459
- "gpt-5.4": 2,
7460
- "gpt-5.5": 2.5
7461
- };
7462
- const FAST_MULTIPLIER_PREFIX_OVERRIDES = {
7463
- "claude-opus-4-6": 6,
7464
- "claude-opus-4-7": 6
7465
- };
7466
- const DEFAULT_FALLBACK_PRICING_TABLE = {
7467
- "gpt-5": {
7468
- cachedInputCostPerMTokens: 0.125,
7469
- cacheCreationInputCostPerMTokens: 1.25,
7470
- inputCostPerMTokens: 1.25,
7471
- outputCostPerMTokens: 10
7472
- },
7473
- "gpt-5.2-codex": {
7474
- cachedInputCostPerMTokens: 0.175,
7475
- cacheCreationInputCostPerMTokens: 1.75,
7476
- inputCostPerMTokens: 1.75,
7477
- outputCostPerMTokens: 14
7478
- },
7479
- "gpt-5.4": {
7480
- cachedInputCostPerMTokens: 0.25,
7481
- cacheCreationInputCostPerMTokens: 2.5,
7482
- inputCostPerMTokens: 2.5,
7483
- outputCostPerMTokens: 15
7484
- },
7485
- "gpt-5.5": {
7486
- cachedInputCostPerMTokens: 0.5,
7487
- cacheCreationInputCostPerMTokens: 5,
7488
- fastMultiplier: FAST_MULTIPLIER_EXACT_OVERRIDES["gpt-5.5"],
7489
- inputCostPerMTokens: 5,
7490
- outputCostPerMTokens: 30
7491
- },
7492
- "claude-haiku-4-5": {
7493
- cachedInputCostPerMTokens: 0.1,
7494
- cacheCreationInputCostPerMTokens: 1.25,
7495
- inputCostPerMTokens: 1,
7496
- outputCostPerMTokens: 5
7497
- },
7498
- "claude-opus-4-1": {
7499
- cachedInputCostPerMTokens: 1.5,
7500
- cachedInputCostPerMTokensAbove200K: 3,
7501
- cacheCreationInputCostPerMTokens: 18.75,
7502
- cacheCreationInputCostPerMTokensAbove200K: 37.5,
7503
- inputCostPerMTokens: 15,
7504
- inputCostPerMTokensAbove200K: 30,
7505
- outputCostPerMTokens: 75,
7506
- outputCostPerMTokensAbove200K: 112.5
7507
- },
7508
- "claude-opus-4-6": {
7509
- cachedInputCostPerMTokens: 0.5,
7510
- cacheCreationInputCostPerMTokens: 6.25,
7511
- fastMultiplier: FAST_MULTIPLIER_PREFIX_OVERRIDES["claude-opus-4-6"],
7512
- inputCostPerMTokens: 5,
7513
- outputCostPerMTokens: 25
7514
- },
7515
- "claude-opus-4-7": {
7516
- cachedInputCostPerMTokens: 0.5,
7517
- cacheCreationInputCostPerMTokens: 6.25,
7518
- fastMultiplier: FAST_MULTIPLIER_PREFIX_OVERRIDES["claude-opus-4-7"],
7519
- inputCostPerMTokens: 5,
7520
- outputCostPerMTokens: 25
7521
- },
7522
- "claude-sonnet-4-5": {
7523
- cachedInputCostPerMTokens: 0.3,
7524
- cachedInputCostPerMTokensAbove200K: 0.6,
7525
- cacheCreationInputCostPerMTokens: 3.75,
7526
- cacheCreationInputCostPerMTokensAbove200K: 7.5,
7527
- inputCostPerMTokens: 3,
7528
- inputCostPerMTokensAbove200K: 6,
7529
- outputCostPerMTokens: 15,
7530
- outputCostPerMTokensAbove200K: 22.5
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();
7531
7851
  }
7532
- };
7533
- const pricingCache = /* @__PURE__ */ new Map();
7534
- async function fetchLiteLLMPricingDataset(options = {}) {
7535
- var _a, _b, _c, _d;
7536
- const url = (_a = options.url) != null ? _a : DEFAULT_LITELLM_PRICING_URL;
7537
- const cacheTtlMs = (_b = options.cacheTtlMs) != null ? _b : DEFAULT_PRICING_CACHE_TTL_MS;
7538
- const now = Date.now();
7539
- const cacheEntry = pricingCache.get(url);
7540
- if (!options.forceRefresh && (cacheEntry == null ? void 0 : cacheEntry.value) && now - cacheEntry.fetchedAt < cacheTtlMs) {
7541
- return cacheEntry.value;
7852
+ if (value instanceof Date) {
7853
+ return Number.isFinite(value.getTime()) ? value.toISOString() : null;
7542
7854
  }
7543
- if (!options.forceRefresh && (cacheEntry == null ? void 0 : cacheEntry.promise)) {
7544
- return cacheEntry.promise;
7855
+ if (typeof value !== "string") {
7856
+ return null;
7545
7857
  }
7546
- const fetcher = (_c = options.fetcher) != null ? _c : globalThis.fetch;
7547
- if (typeof fetcher !== "function") {
7548
- return createFallbackLiteLLMPricingDataset();
7858
+ const normalizedValue = value.trim();
7859
+ if (!normalizedValue) {
7860
+ return null;
7549
7861
  }
7550
- const promise = fetcher(url).then(async (response) => {
7551
- if (!response.ok) {
7552
- throw new Error(`Failed to fetch LiteLLM pricing dataset: ${response.status} ${response.statusText}`);
7553
- }
7554
- const data = await response.json();
7555
- if (!isLiteLLMPricingDataset(data)) {
7556
- throw new Error("Invalid LiteLLM pricing dataset payload.");
7557
- }
7558
- const dataset = {
7559
- ...createFallbackLiteLLMPricingDataset(),
7560
- ...data
7561
- };
7562
- pricingCache.set(url, {
7563
- fetchedAt: Date.now(),
7564
- value: dataset
7565
- });
7566
- return dataset;
7567
- }).catch(() => {
7568
- const fallback = createFallbackLiteLLMPricingDataset();
7569
- pricingCache.set(url, {
7570
- fetchedAt: Date.now(),
7571
- value: fallback
7572
- });
7573
- return fallback;
7574
- });
7575
- pricingCache.set(url, {
7576
- fetchedAt: (_d = cacheEntry == null ? void 0 : cacheEntry.fetchedAt) != null ? _d : 0,
7577
- promise,
7578
- value: cacheEntry == null ? void 0 : cacheEntry.value
7579
- });
7580
- return promise;
7862
+ const timestamp = Date.parse(normalizedValue);
7863
+ return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
7864
+ }
7865
+ function getMonthKey(date) {
7866
+ return getDateKey(date).slice(0, 7);
7867
+ }
7868
+ function getWeekLabel(date) {
7869
+ const weekStart = cloneDate(date);
7870
+ const day = weekStart.getDay();
7871
+ const diff = day === 0 ? -6 : 1 - day;
7872
+ weekStart.setDate(weekStart.getDate() + diff);
7873
+ const weekEnd = cloneDate(weekStart);
7874
+ weekEnd.setDate(weekEnd.getDate() + 6);
7875
+ return `${getDateKey(weekStart)} - ${getDateKey(weekEnd)}`;
7876
+ }
7877
+ function formatMonthLabel(monthKey) {
7878
+ const [year, month] = monthKey.split("-").map((value) => Number.parseInt(value, 10));
7879
+ const date = new Date(Date.UTC(year || 0, (month || 1) - 1, 1));
7880
+ return new Intl.DateTimeFormat("en-US", {
7881
+ month: "short",
7882
+ timeZone: "UTC",
7883
+ year: "numeric"
7884
+ }).format(date);
7581
7885
  }
7582
- async function createLiteLLMPricingResolver(options = {}) {
7583
- var _a, _b, _c, _d;
7584
- const dataset = await fetchLiteLLMPricingDataset(options);
7585
- const aliases = (_a = options.aliases) != null ? _a : {};
7586
- const fallbackPricingTable = {
7587
- ...DEFAULT_FALLBACK_PRICING_TABLE,
7588
- ...(_b = options.fallbackPricingTable) != null ? _b : {}
7886
+ function formatDuration(minutes) {
7887
+ const hours = Math.floor(minutes / 60);
7888
+ const remainingMinutes = minutes % 60;
7889
+ if (hours === 0) {
7890
+ return `${remainingMinutes}m`;
7891
+ }
7892
+ if (remainingMinutes === 0) {
7893
+ return `${hours}h`;
7894
+ }
7895
+ return `${hours}h ${remainingMinutes}m`;
7896
+ }
7897
+ function normalizeRawUsage(usage) {
7898
+ var _a;
7899
+ if (!usage) {
7900
+ return null;
7901
+ }
7902
+ const input = normalizeNumber(usage.input_tokens);
7903
+ const cachedInput = normalizeNumber((_a = usage.cached_input_tokens) != null ? _a : usage.cache_read_input_tokens);
7904
+ const output = normalizeNumber(usage.output_tokens);
7905
+ const reasoning = normalizeNumber(usage.reasoning_output_tokens);
7906
+ const total = normalizeNumber(usage.total_tokens);
7907
+ return {
7908
+ cached_input_tokens: cachedInput,
7909
+ input_tokens: input,
7910
+ output_tokens: output,
7911
+ reasoning_output_tokens: reasoning,
7912
+ total_tokens: total > 0 ? total : input + output + reasoning
7589
7913
  };
7590
- const getLookupCandidates = (_c = options.getLookupCandidates) != null ? _c : defaultLookupCandidates;
7591
- const fallbackModel = options.fallbackModel;
7592
- const isZeroCostModel = (_d = options.isZeroCostModel) != null ? _d : (() => false);
7593
- return (model) => {
7594
- var _a2, _b2;
7595
- if (isZeroCostModel(model)) {
7596
- return createZeroPricing();
7597
- }
7598
- const lookupCandidates = uniqueItems(expandLookupCandidates(model, aliases, getLookupCandidates).filter(Boolean));
7599
- const datasetPricing = resolveDatasetPricing(dataset, lookupCandidates);
7600
- if (datasetPricing) {
7601
- return datasetPricing;
7602
- }
7603
- const fallbackPricing = resolveFallbackPricing(fallbackPricingTable, lookupCandidates);
7604
- if (fallbackPricing) {
7605
- return fallbackPricing;
7606
- }
7607
- if (fallbackModel) {
7608
- const fallbackCandidates = uniqueItems(expandLookupCandidates(fallbackModel, aliases, getLookupCandidates).filter(Boolean));
7609
- return (_b2 = (_a2 = resolveDatasetPricing(dataset, fallbackCandidates)) != null ? _a2 : resolveFallbackPricing(fallbackPricingTable, fallbackCandidates)) != null ? _b2 : createZeroPricing();
7610
- }
7611
- return createZeroPricing();
7914
+ }
7915
+ function subtractRawUsage(current, previous) {
7916
+ var _a, _b, _c, _d, _e;
7917
+ return {
7918
+ cached_input_tokens: Math.max(current.cached_input_tokens - ((_a = previous == null ? void 0 : previous.cached_input_tokens) != null ? _a : 0), 0),
7919
+ input_tokens: Math.max(current.input_tokens - ((_b = previous == null ? void 0 : previous.input_tokens) != null ? _b : 0), 0),
7920
+ output_tokens: Math.max(current.output_tokens - ((_c = previous == null ? void 0 : previous.output_tokens) != null ? _c : 0), 0),
7921
+ reasoning_output_tokens: Math.max(current.reasoning_output_tokens - ((_d = previous == null ? void 0 : previous.reasoning_output_tokens) != null ? _d : 0), 0),
7922
+ total_tokens: Math.max(current.total_tokens - ((_e = previous == null ? void 0 : previous.total_tokens) != null ? _e : 0), 0)
7612
7923
  };
7613
7924
  }
7614
- function calculateUsageCostUSD(usage, pricing, options = {}) {
7615
- var _a, _b, _c;
7616
- const multiplier = options.speed === "fast" ? (_b = (_a = pricing.fastMultiplier) != null ? _a : options.defaultFastMultiplier) != null ? _b : 1 : 1;
7617
- const inputCost = calculateTieredCost(usage.inputTokens, pricing.inputCostPerMTokens, pricing.inputCostPerMTokensAbove200K);
7618
- const cachedCost = calculateTieredCost(usage.cachedInputTokens, pricing.cachedInputCostPerMTokens, pricing.cachedInputCostPerMTokensAbove200K);
7619
- const cacheCreationCost = calculateTieredCost((_c = usage.cacheCreationTokens) != null ? _c : 0, pricing.cacheCreationInputCostPerMTokens, pricing.cacheCreationInputCostPerMTokensAbove200K);
7620
- const outputCost = calculateTieredCost(usage.outputTokens, pricing.outputCostPerMTokens, pricing.outputCostPerMTokensAbove200K);
7621
- return (inputCost + cachedCost + cacheCreationCost + outputCost) * multiplier;
7925
+ function convertCodexRawUsage(rawUsage) {
7926
+ const cachedInputTokens = Math.min(rawUsage.cached_input_tokens, rawUsage.input_tokens);
7927
+ const inputTokens = Math.max(rawUsage.input_tokens - cachedInputTokens, 0);
7928
+ const outputTokens = Math.max(rawUsage.output_tokens, 0);
7929
+ return {
7930
+ cachedInputTokens,
7931
+ inputTokens,
7932
+ outputTokens,
7933
+ reasoningOutputTokens: Math.max(rawUsage.reasoning_output_tokens, 0),
7934
+ totalTokens: rawUsage.total_tokens > 0 ? rawUsage.total_tokens : inputTokens + outputTokens
7935
+ };
7622
7936
  }
7623
- function createFallbackLiteLLMPricingDataset() {
7937
+ function convertGeminiTokenUsage(tokens) {
7938
+ const rawInputTokens = normalizeNumber(tokens.input);
7939
+ const cachedInputTokens = Math.min(normalizeNumber(tokens.cached), rawInputTokens);
7940
+ const outputTokens = normalizeNumber(tokens.output);
7941
+ const reasoningOutputTokens = normalizeNumber(tokens.thoughts);
7942
+ const toolTokens = normalizeNumber(tokens.tool);
7943
+ const totalTokens = normalizeNumber(tokens.total);
7624
7944
  return {
7625
- "gpt-5": {
7626
- input_cost_per_token: 125e-8,
7627
- output_cost_per_token: 1e-5,
7628
- cache_creation_input_token_cost: 125e-8,
7629
- cache_read_input_token_cost: 125e-9
7630
- },
7631
- "gpt-5.2-codex": {
7632
- input_cost_per_token: 175e-8,
7633
- output_cost_per_token: 14e-6,
7634
- cache_creation_input_token_cost: 175e-8,
7635
- cache_read_input_token_cost: 175e-9
7636
- },
7637
- "gpt-5.4": {
7638
- input_cost_per_token: 25e-7,
7639
- output_cost_per_token: 15e-6,
7640
- cache_creation_input_token_cost: 25e-7,
7641
- cache_read_input_token_cost: 25e-8
7642
- },
7643
- "gpt-5.5": {
7644
- input_cost_per_token: 5e-6,
7645
- output_cost_per_token: 3e-5,
7646
- cache_creation_input_token_cost: 5e-6,
7647
- cache_read_input_token_cost: 5e-7,
7648
- provider_specific_entry: {
7649
- fast: 2.5
7650
- }
7651
- },
7652
- "claude-haiku-4-5": {
7653
- input_cost_per_token: 1e-6,
7654
- output_cost_per_token: 5e-6,
7655
- cache_creation_input_token_cost: 125e-8,
7656
- cache_read_input_token_cost: 1e-7
7657
- },
7658
- "claude-opus-4-1": {
7659
- input_cost_per_token: 15e-6,
7660
- output_cost_per_token: 75e-6,
7661
- cache_creation_input_token_cost: 1875e-8,
7662
- cache_read_input_token_cost: 15e-7,
7663
- input_cost_per_token_above_200k_tokens: 3e-5,
7664
- output_cost_per_token_above_200k_tokens: 1125e-7,
7665
- cache_creation_input_token_cost_above_200k_tokens: 375e-7,
7666
- cache_read_input_token_cost_above_200k_tokens: 3e-6
7667
- },
7668
- "claude-opus-4-6": {
7669
- input_cost_per_token: 5e-6,
7670
- output_cost_per_token: 25e-6,
7671
- cache_creation_input_token_cost: 625e-8,
7672
- cache_read_input_token_cost: 5e-7,
7673
- provider_specific_entry: {
7674
- fast: 6
7675
- }
7676
- },
7677
- "claude-opus-4-7": {
7678
- input_cost_per_token: 5e-6,
7679
- output_cost_per_token: 25e-6,
7680
- cache_creation_input_token_cost: 625e-8,
7681
- cache_read_input_token_cost: 5e-7,
7682
- provider_specific_entry: {
7683
- fast: 6
7684
- }
7685
- },
7686
- "claude-sonnet-4-5": {
7687
- input_cost_per_token: 3e-6,
7688
- output_cost_per_token: 15e-6,
7689
- cache_creation_input_token_cost: 375e-8,
7690
- cache_read_input_token_cost: 3e-7,
7691
- input_cost_per_token_above_200k_tokens: 6e-6,
7692
- output_cost_per_token_above_200k_tokens: 225e-7,
7693
- cache_creation_input_token_cost_above_200k_tokens: 75e-7,
7694
- cache_read_input_token_cost_above_200k_tokens: 6e-7
7695
- }
7945
+ cachedInputTokens,
7946
+ inputTokens: Math.max(rawInputTokens - cachedInputTokens, 0),
7947
+ outputTokens,
7948
+ reasoningOutputTokens,
7949
+ totalTokens: totalTokens > 0 ? totalTokens : rawInputTokens + outputTokens + reasoningOutputTokens + toolTokens
7696
7950
  };
7697
7951
  }
7698
- function isLiteLLMPricingDataset(value) {
7699
- if (!value || typeof value !== "object" || Array.isArray(value)) {
7700
- return false;
7952
+ function extractModelName(value) {
7953
+ if (!value || typeof value !== "object") {
7954
+ return void 0;
7701
7955
  }
7702
- return true;
7956
+ const record = value;
7957
+ const info = normalizeUnknownRecord(record.info);
7958
+ const metadata = normalizeUnknownRecord(record.metadata);
7959
+ const infoMetadata = normalizeUnknownRecord(info == null ? void 0 : info.metadata);
7960
+ const candidates = [
7961
+ record.model,
7962
+ record.model_name,
7963
+ info == null ? void 0 : info.model,
7964
+ info == null ? void 0 : info.model_name,
7965
+ infoMetadata == null ? void 0 : infoMetadata.model,
7966
+ metadata == null ? void 0 : metadata.model
7967
+ ];
7968
+ for (const candidate of candidates) {
7969
+ if (typeof candidate === "string" && candidate.trim() !== "") {
7970
+ return candidate.trim();
7971
+ }
7972
+ }
7973
+ return void 0;
7703
7974
  }
7704
- function defaultLookupCandidates(model) {
7975
+ function extractClaudeProjectFromPath(jsonlPath) {
7976
+ var _a;
7977
+ const normalizedPath = jsonlPath.replace(/[/\\]/g, sep);
7978
+ const segments = normalizedPath.split(sep);
7979
+ const projectsIndex = segments.findIndex((segment) => segment === "projects");
7980
+ if (projectsIndex === -1 || projectsIndex + 1 >= segments.length) {
7981
+ return "unknown";
7982
+ }
7983
+ return ((_a = segments[projectsIndex + 1]) == null ? void 0 : _a.trim()) || "unknown";
7984
+ }
7985
+ function decodeClaudeProjectPath(projectPath) {
7986
+ var _a;
7987
+ const normalized = projectPath.replace(/^-/, "").replace(/-/g, "/");
7988
+ const parts = normalized.split("/").filter(Boolean);
7989
+ return (_a = parts.at(-1)) != null ? _a : projectPath;
7990
+ }
7991
+ function getClaudeLookupCandidates(model) {
7992
+ var _a;
7705
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, "");
7706
7998
  return [
7707
7999
  normalizedModel,
7708
- normalizedModel.replace(/^openai\//u, ""),
7709
- normalizedModel.replace(/^azure\//u, ""),
7710
- normalizedModel.replace(/^openrouter\/openai\//u, "")
8000
+ withoutFastSuffix,
8001
+ withoutFastSuffix.replace(/^anthropic\//u, ""),
8002
+ `anthropic/${normalizedModel}`,
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-")
8009
+ ];
8010
+ }
8011
+ function getGeminiLookupCandidates(model) {
8012
+ const normalizedModel = model.trim();
8013
+ return [
8014
+ normalizedModel,
8015
+ normalizedModel.replace(/^gemini\//u, ""),
8016
+ normalizedModel.replace(/^google\//u, ""),
8017
+ `gemini/${normalizedModel}`,
8018
+ `google/${normalizedModel}`
7711
8019
  ];
7712
8020
  }
7713
- function expandLookupCandidates(model, aliases, getLookupCandidates) {
7714
- const candidates = getLookupCandidates(model);
7715
- const expanded = [...candidates];
7716
- for (const candidate of candidates) {
7717
- const alias = aliases[candidate];
7718
- if (alias) {
7719
- expanded.push(...getLookupCandidates(alias));
7720
- }
8021
+ function isOpenRouterFreeModel(model) {
8022
+ const normalizedModel = model.trim().toLowerCase();
8023
+ return normalizedModel === "openrouter/free" || normalizedModel.startsWith("openrouter/") && normalizedModel.endsWith(":free");
8024
+ }
8025
+ function getGeminiProjectRoot(filePath) {
8026
+ const projectDir = dirname$1(dirname$1(filePath));
8027
+ const projectRootFile = `${projectDir}/.project_root`;
8028
+ if (!existsSync(projectRootFile)) {
8029
+ return "";
8030
+ }
8031
+ try {
8032
+ return readFileSync(projectRootFile, "utf8").trim();
8033
+ } catch {
8034
+ return "";
8035
+ }
8036
+ }
8037
+ function getGeminiProjectKeyFromPath(filePath) {
8038
+ var _a;
8039
+ const normalizedPath = filePath.replace(/[/\\]/g, sep);
8040
+ const segments = normalizedPath.split(sep);
8041
+ const tmpIndex = segments.findIndex((segment) => segment === "tmp");
8042
+ if (tmpIndex === -1 || tmpIndex + 1 >= segments.length) {
8043
+ return "unknown";
8044
+ }
8045
+ return ((_a = segments[tmpIndex + 1]) == null ? void 0 : _a.trim()) || "unknown";
8046
+ }
8047
+ function getRepositoryNameFromProjectRoot(projectRoot) {
8048
+ if (!projectRoot) {
8049
+ return "";
8050
+ }
8051
+ const gitConfigPath = `${projectRoot}/.git/config`;
8052
+ if (!existsSync(gitConfigPath)) {
8053
+ return "";
8054
+ }
8055
+ try {
8056
+ return normalizeRepositoryUrl(getOriginUrlFromGitConfig(readFileSync(gitConfigPath, "utf8")));
8057
+ } catch {
8058
+ return "";
7721
8059
  }
7722
- return expanded;
7723
8060
  }
7724
- function resolveDatasetPricing(dataset, candidates) {
7725
- for (const candidate of candidates) {
7726
- const pricing = dataset[candidate];
7727
- if (!pricing || !hasNonZeroTokenPricing(pricing)) {
8061
+ function getOriginUrlFromGitConfig(config) {
8062
+ let isOriginBlock = false;
8063
+ for (const rawLine of config.split("\n")) {
8064
+ const line = rawLine.trim();
8065
+ if (line.startsWith("[")) {
8066
+ isOriginBlock = line === '[remote "origin"]';
7728
8067
  continue;
7729
8068
  }
7730
- return toModelPricing(pricing, candidates);
8069
+ if (!isOriginBlock || !line.startsWith("url =")) {
8070
+ continue;
8071
+ }
8072
+ return line.slice("url =".length).trim();
7731
8073
  }
7732
- return null;
8074
+ return "";
7733
8075
  }
7734
- function resolveFallbackPricing(fallbackPricingTable, candidates) {
7735
- var _a;
7736
- for (const candidate of candidates) {
7737
- const pricing = fallbackPricingTable[candidate];
7738
- if (pricing) {
7739
- const fastMultiplier = (_a = pricing.fastMultiplier) != null ? _a : resolveFastMultiplierOverride(candidates);
7740
- return fastMultiplier == null ? pricing : { ...pricing, fastMultiplier };
7741
- }
8076
+ function extractGeminiMessageText(content) {
8077
+ if (typeof content === "string") {
8078
+ return content;
7742
8079
  }
7743
- return null;
8080
+ if (!Array.isArray(content)) {
8081
+ return "";
8082
+ }
8083
+ return content.map((item) => {
8084
+ var _a;
8085
+ return (_a = item.text) == null ? void 0 : _a.trim();
8086
+ }).filter(Boolean).join("\n");
7744
8087
  }
7745
- function hasNonZeroTokenPricing(pricing) {
7746
- var _a, _b, _c, _d;
7747
- 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;
8088
+ function addEventToAggregateGroup(group, event, options) {
8089
+ group.inputTokens += event.inputTokens;
8090
+ group.cachedInputTokens += event.cachedInputTokens;
8091
+ group.outputTokens += event.outputTokens;
8092
+ group.reasoningOutputTokens += event.reasoningOutputTokens;
8093
+ group.totalTokens += event.totalTokens;
8094
+ group.costUSD += getEventCostUSD(event, options);
8095
+ group.models = shouldIncludeModel(event, options) ? uniqueItems([...group.models, event.model]) : group.models;
8096
+ group.projects = uniqueItems([...group.projects, event.project]);
7748
8097
  }
7749
- function toModelPricing(pricing, candidates) {
7750
- var _a, _b, _c, _d, _e, _f;
7751
- const inputCostPerToken = (_a = pricing.input_cost_per_token) != null ? _a : 0;
7752
- const cachedInputCostPerToken = (_b = pricing.cache_read_input_token_cost) != null ? _b : inputCostPerToken;
7753
- const cacheCreationInputCostPerToken = (_c = pricing.cache_creation_input_token_cost) != null ? _c : inputCostPerToken;
7754
- const outputCostPerToken = (_d = pricing.output_cost_per_token) != null ? _d : 0;
8098
+ function getEventCostUSD(event, options) {
8099
+ var _a, _b, _c;
8100
+ return (_c = (_b = (_a = options.getCostUSD) == null ? void 0 : _a.call(options, event)) != null ? _b : event.costUSD) != null ? _c : 0;
8101
+ }
8102
+ function shouldIncludeModel(event, options) {
8103
+ var _a, _b;
8104
+ return (_b = (_a = options.includeModel) == null ? void 0 : _a.call(options, event)) != null ? _b : true;
8105
+ }
8106
+ function getCachedInputTokens(session, options) {
8107
+ var _a, _b;
8108
+ return (_b = (_a = options.getCachedInputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "cachedInputTokens");
8109
+ }
8110
+ function getReasoningOutputTokens(session, options) {
8111
+ var _a, _b;
8112
+ return (_b = (_a = options.getReasoningOutputTokens) == null ? void 0 : _a.call(options, session)) != null ? _b : getNumericProperty(session, "reasoningOutputTokens");
8113
+ }
8114
+ function getNumericProperty(value, key) {
8115
+ const record = value;
8116
+ const property = record[key];
8117
+ return typeof property === "number" && Number.isFinite(property) ? property : 0;
8118
+ }
8119
+ function cloneDate(date) {
8120
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
8121
+ }
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
+ }
8136
+ }
8137
+ function createSessionFragment(options) {
7755
8138
  return {
7756
- cachedInputCostPerMTokens: cachedInputCostPerToken * MILLION,
7757
- cachedInputCostPerMTokensAbove200K: pricing.cache_read_input_token_cost_above_200k_tokens != null ? pricing.cache_read_input_token_cost_above_200k_tokens * MILLION : void 0,
7758
- cacheCreationInputCostPerMTokens: cacheCreationInputCostPerToken * MILLION,
7759
- cacheCreationInputCostPerMTokensAbove200K: pricing.cache_creation_input_token_cost_above_200k_tokens != null ? pricing.cache_creation_input_token_cost_above_200k_tokens * MILLION : void 0,
7760
- fastMultiplier: (_f = (_e = pricing.provider_specific_entry) == null ? void 0 : _e.fast) != null ? _f : resolveFastMultiplierOverride(candidates),
7761
- inputCostPerMTokens: inputCostPerToken * MILLION,
7762
- inputCostPerMTokensAbove200K: pricing.input_cost_per_token_above_200k_tokens != null ? pricing.input_cost_per_token_above_200k_tokens * MILLION : void 0,
7763
- outputCostPerMTokens: outputCostPerToken * MILLION,
7764
- 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
7765
8147
  };
7766
8148
  }
7767
- function resolveFastMultiplierOverride(candidates) {
7768
- for (const candidate of candidates) {
7769
- const multiplier = FAST_MULTIPLIER_EXACT_OVERRIDES[candidate];
7770
- if (multiplier != null) {
7771
- return multiplier;
7772
- }
8149
+ function addFragmentInteraction(fragment, interaction) {
8150
+ fragment.interactions.push(interaction);
8151
+ if (!interaction.timestamp) {
8152
+ return;
7773
8153
  }
7774
- for (const candidate of candidates) {
7775
- const normalized = candidate.replace(/[.@]/gu, "-");
7776
- for (const part of normalized.split(/[/:]/u)) {
7777
- for (const [base, multiplier] of Object.entries(FAST_MULTIPLIER_PREFIX_OVERRIDES)) {
7778
- if (matchesModelSuffix(part, base)) {
7779
- return multiplier;
7780
- }
7781
- }
7782
- }
8154
+ if (!fragment.startedAt || Date.parse(interaction.timestamp) < Date.parse(fragment.startedAt)) {
8155
+ fragment.startedAt = interaction.timestamp;
7783
8156
  }
7784
- return void 0;
7785
- }
7786
- function matchesModelSuffix(part, base) {
7787
- const index = part.lastIndexOf(base);
7788
- if (index < 0) {
7789
- return false;
8157
+ if (!fragment.durationEndAt || Date.parse(interaction.timestamp) > Date.parse(fragment.durationEndAt)) {
8158
+ fragment.durationEndAt = interaction.timestamp;
7790
8159
  }
7791
- const suffix = part.slice(index);
7792
- return suffix === base || suffix[base.length] === "-";
7793
8160
  }
7794
- function createZeroPricing() {
7795
- return {
7796
- cachedInputCostPerMTokens: 0,
7797
- cacheCreationInputCostPerMTokens: 0,
7798
- inputCostPerMTokens: 0,
7799
- outputCostPerMTokens: 0
7800
- };
8161
+ function getSessionLookupKey(project, sessionId) {
8162
+ return `${project}:${sessionId}`;
7801
8163
  }
7802
- function calculateTieredCost(tokens, baseCostPerMTokens, above200KCostPerMTokens) {
7803
- const safeTokens = Math.max(tokens != null ? tokens : 0, 0);
7804
- if (safeTokens === 0) {
7805
- return 0;
8164
+ function normalizeRole(value) {
8165
+ const normalized = value.toLowerCase();
8166
+ if (normalized.includes("user")) {
8167
+ return "user";
7806
8168
  }
7807
- if (safeTokens > 2e5 && above200KCostPerMTokens != null) {
7808
- return 2e5 / MILLION * baseCostPerMTokens + (safeTokens - 2e5) / MILLION * above200KCostPerMTokens;
8169
+ if (normalized.includes("assistant") || normalized.includes("agent") || normalized.includes("gemini")) {
8170
+ return "assistant";
7809
8171
  }
7810
- 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";
7811
8182
  }
7812
8183
 
7813
8184
  const ZERO_PRICING = {
@@ -7820,12 +8191,7 @@ async function createZeroPricingResolver() {
7820
8191
  return () => ZERO_PRICING;
7821
8192
  }
7822
8193
  function applyTotalUsageFallback(usage) {
7823
- const cacheCreationTokens = normalizeUsageNumber(usage.cacheCreationTokens);
7824
- const cacheReadTokens = normalizeUsageNumber(usage.cacheReadTokens);
7825
- const inputTokens = normalizeUsageNumber(usage.inputTokens);
7826
- const outputTokens = normalizeUsageNumber(usage.outputTokens);
7827
- const totalTokens = normalizeUsageNumber(usage.totalTokens);
7828
- const baseTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
8194
+ const { baseTokens, cacheCreationTokens, cacheReadTokens, inputTokens, outputTokens, totalTokens } = readUsageParts(usage);
7829
8195
  if (baseTokens === 0 && totalTokens > 0) {
7830
8196
  return {
7831
8197
  cacheCreationTokens,
@@ -7847,6 +8213,29 @@ function applyTotalUsageFallback(usage) {
7847
8213
  reasoningOutputTokens
7848
8214
  };
7849
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
+ }
7850
8239
  function toInteractionUsage(usage) {
7851
8240
  var _a;
7852
8241
  const cacheCreationTokens = normalizeUsageNumber(usage.cacheCreationTokens);
@@ -7854,13 +8243,15 @@ function toInteractionUsage(usage) {
7854
8243
  const inputTokens = normalizeUsageNumber(usage.inputTokens);
7855
8244
  const outputTokens = normalizeUsageNumber(usage.outputTokens);
7856
8245
  const reasoningOutputTokens = normalizeUsageNumber(usage.reasoningOutputTokens);
8246
+ const extraTotalTokens = normalizeUsageNumber(usage.extraTotalTokens);
7857
8247
  const toolTokens = normalizeUsageNumber(usage.toolTokens);
7858
- const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens + reasoningOutputTokens + toolTokens;
8248
+ const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens + reasoningOutputTokens + extraTotalTokens + toolTokens;
7859
8249
  return {
7860
8250
  cacheCreationTokens,
7861
8251
  cacheReadTokens,
7862
8252
  cachedInputTokens: cacheCreationTokens + cacheReadTokens,
7863
8253
  costUSD: (_a = usage.costUSD) != null ? _a : 0,
8254
+ extraTotalTokens: extraTotalTokens > 0 ? extraTotalTokens : void 0,
7864
8255
  inputTokens,
7865
8256
  isFallbackModel: usage.isFallbackModel,
7866
8257
  outputTokens,
@@ -7870,15 +8261,15 @@ function toInteractionUsage(usage) {
7870
8261
  };
7871
8262
  }
7872
8263
  function isZeroInteractionUsage(usage) {
7873
- var _a;
7874
- 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;
7875
8266
  }
7876
8267
  function calculateUsageCostFromCandidates(usage, candidates, resolvePricing, options = {}) {
7877
- var _a, _b;
7878
- 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);
7879
8270
  for (const candidate of uniqueItems(candidates.map((candidate2) => candidate2.trim()).filter(Boolean))) {
7880
8271
  const costUSD = calculateUsageCostUSD({
7881
- cacheCreationTokens: (_b = usage.cacheCreationTokens) != null ? _b : 0,
8272
+ cacheCreationTokens: (_c = usage.cacheCreationTokens) != null ? _c : 0,
7882
8273
  cachedInputTokens: usage.cachedInputTokens,
7883
8274
  inputTokens: usage.inputTokens,
7884
8275
  outputTokens
@@ -7899,17 +8290,34 @@ function getFileModifiedAtIso(filePath) {
7899
8290
  function normalizeUsageNumber(value) {
7900
8291
  return Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
7901
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
+ }
7902
8308
 
7903
8309
  const ampUsageAdapter = {
7904
- createPricingResolver: createZeroPricingResolver,
8310
+ async createPricingResolver() {
8311
+ return createLiteLLMPricingResolver();
8312
+ },
7905
8313
  async discoverFiles(config) {
7906
8314
  const groups = await Promise.all(config.ampPaths.map((path) => glob(join(path, "threads", "**", "*.json"), {
7907
8315
  absolute: true
7908
8316
  }).catch(() => [])));
7909
8317
  return groups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "amp"));
7910
8318
  },
7911
- parseFile(filePath) {
7912
- var _a, _b, _c;
8319
+ parseFile(filePath, resolvePricing) {
8320
+ var _a, _b;
7913
8321
  const data = parseJsonFile(filePath);
7914
8322
  const record = normalizeUnknownRecord(data);
7915
8323
  const sessionId = normalizeStringValue(record == null ? void 0 : record.id) || basename$1(filePath, ".json");
@@ -7938,27 +8346,32 @@ const ampUsageAdapter = {
7938
8346
  const messageId = Number.isFinite(event.toMessageId) ? Number(event.toMessageId) : null;
7939
8347
  const [cacheCreationTokens = 0, cacheReadTokens = 0] = messageId != null ? (_b = cacheTokensByMessageId.get(messageId)) != null ? _b : [0, 0] : [0, 0];
7940
8348
  const usage = toInteractionUsage({
7941
- ...applyTotalUsageFallback({
8349
+ ...applyTotalUsageAsExtra({
7942
8350
  cacheCreationTokens,
7943
8351
  cacheReadTokens,
7944
8352
  inputTokens: getNumber$8(tokens.input),
7945
8353
  outputTokens: getNumber$8(tokens.output),
7946
8354
  totalTokens: getNumber$8(tokens.total)
7947
- }),
7948
- costUSD: (_c = normalizeFiniteNumberOrNull(event.credits)) != null ? _c : 0
8355
+ })
7949
8356
  });
7950
8357
  if (isZeroInteractionUsage(usage)) {
7951
8358
  continue;
7952
8359
  }
8360
+ const costUSD = calculateUsageCostFromCandidates(usage, [model], resolvePricing, {
8361
+ includeExtraTotalAsOutput: true
8362
+ });
7953
8363
  addFragmentInteraction(fragment, {
7954
8364
  content: "",
7955
- costUSD: usage.costUSD,
8365
+ costUSD,
7956
8366
  index,
7957
8367
  model,
7958
8368
  role: "usage",
7959
8369
  timestamp,
7960
8370
  type: "usage_ledger",
7961
- usage
8371
+ usage: toInteractionUsage({
8372
+ ...usage,
8373
+ costUSD
8374
+ })
7962
8375
  });
7963
8376
  }
7964
8377
  return fragment.interactions.length > 0 ? [fragment] : [];
@@ -7989,7 +8402,6 @@ function getNumber$8(value) {
7989
8402
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
7990
8403
  }
7991
8404
 
7992
- const CLAUDE_FALLBACK_MODEL = "claude-sonnet-4-5";
7993
8405
  const CODEX_FALLBACK_MODEL = "gpt-5";
7994
8406
  const GEMINI_FALLBACK_MODEL = "gemini-2.5-flash";
7995
8407
  const CLAUDE_MODEL_ALIASES = {
@@ -8052,7 +8464,6 @@ const claudeCodeUsageAdapter = {
8052
8464
  async createPricingResolver() {
8053
8465
  return createLiteLLMPricingResolver({
8054
8466
  aliases: CLAUDE_MODEL_ALIASES,
8055
- fallbackModel: CLAUDE_FALLBACK_MODEL,
8056
8467
  getLookupCandidates: getClaudeLookupCandidates
8057
8468
  });
8058
8469
  },
@@ -8078,6 +8489,9 @@ const claudeCodeUsageAdapter = {
8078
8489
  const fragments = /* @__PURE__ */ new Map();
8079
8490
  for (let index = 0; index < lines.length; index += 1) {
8080
8491
  const line = lines[index];
8492
+ if (!isSupportedClaudeUsageLine(line)) {
8493
+ continue;
8494
+ }
8081
8495
  const sessionId = normalizeStringValue(line.sessionId) || fallbackSessionId;
8082
8496
  const cwd = (_a = normalizeStringValue(line.cwd)) != null ? _a : "";
8083
8497
  const project = getProjectName(cwd, "") || decodeClaudeProjectPath(projectPath);
@@ -8169,6 +8583,31 @@ function getInteractionRole(line, message) {
8169
8583
  const role = normalizeStringValue(line.type) || normalizeStringValue(message == null ? void 0 : message.role) || normalizeStringValue(message == null ? void 0 : message.type) || "";
8170
8584
  return normalizeRole(role);
8171
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
+ }
8172
8611
 
8173
8612
  const CODEBUFF_DEFAULT_MODEL = "codebuff-unknown";
8174
8613
  const codebuffUsageAdapter = {
@@ -8209,41 +8648,38 @@ const codebuffUsageAdapter = {
8209
8648
  }
8210
8649
  const model = extracted.model || CODEBUFF_DEFAULT_MODEL;
8211
8650
  const usage = toInteractionUsage({
8212
- ...applyTotalUsageFallback({
8651
+ ...applyTotalUsageAsExtra({
8213
8652
  cacheCreationTokens: extracted.cacheCreationTokens,
8214
8653
  cacheReadTokens: extracted.cacheReadTokens,
8654
+ extraTotalTokens: extracted.extraTotalTokens,
8215
8655
  inputTokens: extracted.inputTokens,
8216
8656
  outputTokens: extracted.outputTokens,
8217
8657
  totalTokens: extracted.totalTokens
8218
- }),
8219
- costUSD: extracted.credits > 0 ? extracted.credits : calculateUsageCostFromCandidates(
8220
- toInteractionUsage({
8221
- ...applyTotalUsageFallback({
8222
- cacheCreationTokens: extracted.cacheCreationTokens,
8223
- cacheReadTokens: extracted.cacheReadTokens,
8224
- inputTokens: extracted.inputTokens,
8225
- outputTokens: extracted.outputTokens,
8226
- totalTokens: extracted.totalTokens
8227
- })
8228
- }),
8229
- getCodebuffCandidates(model, inferCodebuffProvider(model)),
8230
- resolvePricing
8231
- )
8658
+ })
8232
8659
  });
8233
8660
  if (isZeroInteractionUsage(usage)) {
8234
8661
  continue;
8235
8662
  }
8663
+ const costUSD = calculateUsageCostFromCandidates(
8664
+ usage,
8665
+ getCodebuffCandidates(model, inferCodebuffProvider(model)),
8666
+ resolvePricing,
8667
+ { includeExtraTotalAsOutput: true, includeReasoningAsOutput: false }
8668
+ );
8236
8669
  const timestamp = (_a = getCodebuffMessageTimestamp(message)) != null ? _a : fallbackTimestamp;
8237
8670
  addFragmentInteraction(fragment, {
8238
8671
  content: "",
8239
- costUSD: usage.costUSD,
8672
+ costUSD,
8240
8673
  dedupeKey: getCodebuffDedupeKey(message, sessionId, timestamp != null ? timestamp : "", model, usage, index),
8241
8674
  index,
8242
8675
  model,
8243
8676
  role: "assistant",
8244
8677
  timestamp,
8245
8678
  type: normalizeStringValue(message.variant) || normalizeStringValue(message.role) || "assistant",
8246
- usage
8679
+ usage: toInteractionUsage({
8680
+ ...usage,
8681
+ costUSD
8682
+ })
8247
8683
  });
8248
8684
  }
8249
8685
  return fragment.interactions.length > 0 ? [fragment] : [];
@@ -8314,7 +8750,7 @@ function parseCodebuffUsageRecord(value) {
8314
8750
  if (!record) {
8315
8751
  return null;
8316
8752
  }
8317
- const raw = applyTotalUsageFallback({
8753
+ const raw = applyTotalUsageAsExtra({
8318
8754
  cacheCreationTokens: pickUsageNumber(record, ["cacheCreationInputTokens", "cache_creation_input_tokens", "cacheCreationTokens", "cache_creation_tokens", "cachedTokensCreated", "cached_tokens_created"]),
8319
8755
  cacheReadTokens: Math.max(
8320
8756
  pickUsageNumber(record, ["cacheReadInputTokens", "cache_read_input_tokens"]),
@@ -8329,10 +8765,11 @@ function parseCodebuffUsageRecord(value) {
8329
8765
  cacheCreationTokens: raw.cacheCreationTokens,
8330
8766
  cacheReadTokens: raw.cacheReadTokens,
8331
8767
  credits: getFiniteNumber(record.credits),
8768
+ extraTotalTokens: raw.extraTotalTokens,
8332
8769
  inputTokens: raw.inputTokens,
8333
8770
  model: (_a = normalizeStringValue(record.model)) != null ? _a : null,
8334
8771
  outputTokens: raw.outputTokens,
8335
- totalTokens: raw.inputTokens + raw.outputTokens + raw.cacheCreationTokens + raw.cacheReadTokens + raw.reasoningOutputTokens
8772
+ totalTokens: raw.inputTokens + raw.outputTokens + raw.cacheCreationTokens + raw.cacheReadTokens + raw.extraTotalTokens
8336
8773
  };
8337
8774
  }
8338
8775
  function emptyCodebuffUsage() {
@@ -8340,6 +8777,7 @@ function emptyCodebuffUsage() {
8340
8777
  cacheCreationTokens: 0,
8341
8778
  cacheReadTokens: 0,
8342
8779
  credits: 0,
8780
+ extraTotalTokens: 0,
8343
8781
  inputTokens: 0,
8344
8782
  model: null,
8345
8783
  outputTokens: 0,
@@ -8362,6 +8800,9 @@ function mergeCodebuffUsage(target, fallback) {
8362
8800
  if (target.cacheReadTokens === 0) {
8363
8801
  target.cacheReadTokens = fallback.cacheReadTokens;
8364
8802
  }
8803
+ if (target.extraTotalTokens === 0) {
8804
+ target.extraTotalTokens = fallback.extraTotalTokens;
8805
+ }
8365
8806
  if (target.totalTokens === 0) {
8366
8807
  target.totalTokens = fallback.totalTokens;
8367
8808
  }
@@ -8373,7 +8814,7 @@ function mergeCodebuffUsage(target, fallback) {
8373
8814
  }
8374
8815
  }
8375
8816
  function hasCodebuffSignal(usage) {
8376
- 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;
8377
8818
  }
8378
8819
  function getCodebuffMessageTimestamp(message) {
8379
8820
  var _a;
@@ -8413,6 +8854,7 @@ function getCodebuffCandidates(model, provider) {
8413
8854
  return provider !== "unknown" && !model.startsWith(`${provider}/`) ? [model, `${provider}/${model}`] : [model];
8414
8855
  }
8415
8856
  function getCodebuffDedupeKey(message, sessionId, timestamp, model, usage, index) {
8857
+ var _a, _b, _c;
8416
8858
  const messageId = normalizeStringValue(message.id);
8417
8859
  if (messageId) {
8418
8860
  return `codebuff:${sessionId}:${messageId}`;
@@ -8425,8 +8867,9 @@ function getCodebuffDedupeKey(message, sessionId, timestamp, model, usage, index
8425
8867
  String(index),
8426
8868
  String(usage.inputTokens),
8427
8869
  String(usage.outputTokens),
8428
- String(usage.cachedInputTokens),
8429
- 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)
8430
8873
  ].join(":");
8431
8874
  }
8432
8875
  function pickUsageNumber(record, keys) {
@@ -8446,7 +8889,6 @@ const codexUsageAdapter = {
8446
8889
  async createPricingResolver() {
8447
8890
  return createLiteLLMPricingResolver({
8448
8891
  aliases: CODEX_MODEL_ALIASES,
8449
- fallbackModel: CODEX_FALLBACK_MODEL,
8450
8892
  isZeroCostModel: isOpenRouterFreeModel
8451
8893
  });
8452
8894
  },
@@ -8463,13 +8905,13 @@ const codexUsageAdapter = {
8463
8905
  return files.flatMap((filePath) => toDiscoveredUsageFile(filePath, "codex", cacheSignature));
8464
8906
  },
8465
8907
  parseFile(filePath, resolvePricing, file) {
8466
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
8908
+ var _a, _b, _c, _d, _e, _f, _g, _h;
8467
8909
  const lines = parseJsonlFile(filePath);
8468
- 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);
8469
8911
  const sessionId = getSessionId(filePath, normalizeStringValue(sessionMeta == null ? void 0 : sessionMeta.id));
8470
- 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);
8471
8913
  const project = getProjectName((_d = normalizeStringValue(sessionMeta == null ? void 0 : sessionMeta.cwd)) != null ? _d : "");
8472
- 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}`;
8473
8915
  const fragment = createSessionFragment({
8474
8916
  project,
8475
8917
  repository,
@@ -8483,21 +8925,22 @@ const codexUsageAdapter = {
8483
8925
  let currentModelIsFallback = false;
8484
8926
  for (let index = 0; index < lines.length; index += 1) {
8485
8927
  const line = lines[index];
8486
- if (line.type === "turn_context") {
8487
- const contextModel = extractModelName(line.payload);
8928
+ const payload = normalizeUnknownRecord(line.payload);
8929
+ if (normalizeStringValue(line.type) === "turn_context") {
8930
+ const contextModel = extractModelName(payload);
8488
8931
  if (contextModel) {
8489
8932
  currentModel = contextModel;
8490
8933
  currentModelIsFallback = false;
8491
8934
  }
8492
8935
  }
8493
- const timestamp = (_g = toIsoString(line.timestamp)) != null ? _g : toIsoString((_f = line.payload) == null ? void 0 : _f.timestamp);
8494
- const extractedModel = extractModelName(line.payload);
8936
+ const timestamp = (_f = getCodexTimestamp(line)) != null ? _f : getFileModifiedAtIso(filePath);
8937
+ const extractedModel = extractCodexModel(line);
8495
8938
  if (extractedModel) {
8496
8939
  currentModel = extractedModel;
8497
8940
  currentModelIsFallback = false;
8498
8941
  }
8499
8942
  const rawUsage = getCodexRawUsage(line, previousTotals);
8500
- 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);
8501
8944
  if (totalUsage) {
8502
8945
  previousTotals = totalUsage;
8503
8946
  }
@@ -8514,12 +8957,13 @@ const codexUsageAdapter = {
8514
8957
  const usage = rawUsage ? getCodexInteractionUsage(rawUsage, model != null ? model : CODEX_FALLBACK_MODEL, resolvePricing, speed) : null;
8515
8958
  addFragmentInteraction(fragment, {
8516
8959
  content: extractCodexContent(line),
8517
- 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,
8518
8962
  index,
8519
8963
  model: model != null ? model : null,
8520
- role: getCodexRole(line),
8964
+ role: getCodexRole(line, rawUsage !== null),
8521
8965
  timestamp,
8522
- 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",
8523
8967
  usage: usage ? { ...usage, isFallbackModel } : null
8524
8968
  });
8525
8969
  }
@@ -8533,14 +8977,14 @@ const codexUsageAdapter = {
8533
8977
  }
8534
8978
  };
8535
8979
  function getCodexRawUsage(line, previousTotals) {
8536
- var _a;
8537
- if (line.type !== "event_msg" || ((_a = line.payload) == null ? void 0 : _a.type) !== "token_count") {
8538
- 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;
8539
8986
  }
8540
- const info = line.payload.info;
8541
- const lastUsage = normalizeRawUsage(info == null ? void 0 : info.last_token_usage);
8542
- const totalUsage = normalizeRawUsage(info == null ? void 0 : info.total_token_usage);
8543
- return lastUsage != null ? lastUsage : totalUsage ? subtractRawUsage(totalUsage, previousTotals) : null;
8987
+ return getHeadlessCodexRawUsage(line);
8544
8988
  }
8545
8989
  function getCodexInteractionUsage(rawUsage, model, resolvePricing, speed) {
8546
8990
  const usage = convertCodexRawUsage(rawUsage);
@@ -8622,20 +9066,20 @@ function toCodexSpeed(value) {
8622
9066
  return normalized === "priority" || normalized === "fast" ? "fast" : "standard";
8623
9067
  }
8624
9068
  function extractCodexContent(line) {
8625
- const payload = line.payload;
8626
- if (!payload) {
8627
- return "";
8628
- }
8629
- const message = payload.message;
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;
8630
9074
  if (typeof message === "string") {
8631
9075
  return message;
8632
9076
  }
8633
- 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) || "";
8634
9078
  }
8635
- function getCodexRole(line) {
8636
- var _a, _b, _c;
8637
- const type = (_c = (_b = (_a = line.payload) == null ? void 0 : _a.type) != null ? _b : line.type) != null ? _c : "";
8638
- 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) {
8639
9083
  return "usage";
8640
9084
  }
8641
9085
  return normalizeRole(type);
@@ -8643,6 +9087,102 @@ function getCodexRole(line) {
8643
9087
  function getSessionId(filePath, sessionMetaId) {
8644
9088
  return (sessionMetaId == null ? void 0 : sessionMetaId.trim()) || basename$1(filePath, ".jsonl");
8645
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(":");
9185
+ }
8646
9186
 
8647
9187
  const COPILOT_MODEL_ATTRS = ["gen_ai.response.model", "gen_ai.request.model"];
8648
9188
  const COPILOT_SESSION_ATTRS = [
@@ -8679,7 +9219,10 @@ const copilotUsageAdapter = {
8679
9219
  const selectedCandidates = filterCopilotCandidates(candidates);
8680
9220
  const fragments = /* @__PURE__ */ new Map();
8681
9221
  for (const candidate of selectedCandidates) {
8682
- const costUSD = calculateUsageCostFromCandidates(candidate.usage, [candidate.model], resolvePricing);
9222
+ const costUSD = calculateUsageCostFromCandidates(candidate.usage, [candidate.model], resolvePricing, {
9223
+ includeExtraTotalAsOutput: true,
9224
+ includeReasoningAsOutput: false
9225
+ });
8683
9226
  const fragment = (_a = fragments.get(candidate.sessionId)) != null ? _a : createSessionFragment({
8684
9227
  project: "copilot",
8685
9228
  repository: "local/copilot",
@@ -8746,21 +9289,22 @@ function getCopilotCandidate(record, index, fallbackTimestamp, traceContexts) {
8746
9289
  if (!source) {
8747
9290
  return null;
8748
9291
  }
8749
- const rawUsage = applyTotalUsageFallback({
8750
- cacheCreationTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.cache_write.input_tokens", "gen_ai.usage.cache_creation.input_tokens"]),
8751
- cacheReadTokens: getCopilotAttributeNumber(attributes, "gen_ai.usage.cache_read.input_tokens"),
8752
- inputTokens: Math.max(
8753
- 0,
8754
- getCopilotAttributeNumber(attributes, "gen_ai.usage.input_tokens") - Math.min(
8755
- getCopilotAttributeNumber(attributes, "gen_ai.usage.input_tokens"),
8756
- getCopilotAttributeNumber(attributes, "gen_ai.usage.cache_read.input_tokens")
8757
- )
8758
- ),
8759
- outputTokens: getCopilotAttributeNumber(attributes, "gen_ai.usage.output_tokens"),
8760
- reasoningOutputTokens: getCopilotAttributeNumberFirst(attributes, ["gen_ai.usage.reasoning.output_tokens", "gen_ai.usage.reasoning_tokens"]),
8761
- 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
+ })
8762
9307
  });
8763
- const usage = toInteractionUsage(rawUsage);
8764
9308
  if (isZeroInteractionUsage(usage)) {
8765
9309
  return null;
8766
9310
  }
@@ -8778,7 +9322,6 @@ function getCopilotCandidate(record, index, fallbackTimestamp, traceContexts) {
8778
9322
  inputTokens: usage.inputTokens,
8779
9323
  model,
8780
9324
  outputTokens: usage.outputTokens,
8781
- reasoningOutputTokens: usage.reasoningOutputTokens,
8782
9325
  responseId,
8783
9326
  sessionId,
8784
9327
  source,
@@ -8905,7 +9448,7 @@ const droidUsageAdapter = {
8905
9448
  const groups = await Promise.all(config.droidPaths.map((path) => glob(join(path, "**", "*.settings.json"), {
8906
9449
  absolute: true
8907
9450
  }).catch(() => [])));
8908
- return groups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "droid"));
9451
+ return selectLatestDroidSettingsFiles(groups.flat()).flatMap((filePath) => toDiscoveredUsageFile(filePath, "droid"));
8909
9452
  },
8910
9453
  parseFile(filePath, resolvePricing) {
8911
9454
  var _a, _b;
@@ -8915,15 +9458,16 @@ const droidUsageAdapter = {
8915
9458
  if (!settings || !tokenUsage) {
8916
9459
  return [];
8917
9460
  }
9461
+ const extraTotalTokens = getNumber$7(tokenUsage.thinkingTokens);
8918
9462
  const usage = toInteractionUsage({
8919
9463
  ...applyTotalUsageFallback({
8920
9464
  cacheCreationTokens: getNumber$7(tokenUsage.cacheCreationTokens),
8921
9465
  cacheReadTokens: getNumber$7(tokenUsage.cacheReadTokens),
8922
9466
  inputTokens: getNumber$7(tokenUsage.inputTokens),
8923
9467
  outputTokens: getNumber$7(tokenUsage.outputTokens),
8924
- reasoningOutputTokens: getNumber$7(tokenUsage.thinkingTokens),
8925
- totalTokens: getNumber$7(tokenUsage.totalTokens)
8926
- })
9468
+ totalTokens: Math.max(getNumber$7(tokenUsage.totalTokens) - extraTotalTokens, 0)
9469
+ }),
9470
+ extraTotalTokens
8927
9471
  });
8928
9472
  if (isZeroInteractionUsage(usage)) {
8929
9473
  return [];
@@ -9071,12 +9615,29 @@ function extractDroidModelFromSidecar(settingsPath) {
9071
9615
  function getNumber$7(value) {
9072
9616
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
9073
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
+ }
9074
9636
 
9075
9637
  const geminiUsageAdapter = {
9076
9638
  async createPricingResolver() {
9077
9639
  return createLiteLLMPricingResolver({
9078
9640
  aliases: GEMINI_MODEL_ALIASES,
9079
- fallbackModel: GEMINI_FALLBACK_MODEL,
9080
9641
  fallbackPricingTable: GEMINI_FALLBACK_PRICING_TABLE,
9081
9642
  getLookupCandidates: getGeminiLookupCandidates
9082
9643
  });
@@ -9139,20 +9700,25 @@ const geminiUsageAdapter = {
9139
9700
  }
9140
9701
  };
9141
9702
  function getGeminiInteractionUsage(tokens, model, resolvePricing) {
9142
- 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
+ };
9143
9710
  if (isZeroUsage(usage)) {
9144
9711
  return null;
9145
9712
  }
9146
- const toolTokens = normalizeNumber(tokens.tool);
9147
9713
  const costUSD = calculateUsageCostUSD({
9148
9714
  cachedInputTokens: usage.cachedInputTokens,
9149
9715
  inputTokens: usage.inputTokens,
9150
- outputTokens: usage.outputTokens + usage.reasoningOutputTokens + toolTokens
9716
+ outputTokens: usage.outputTokens
9151
9717
  }, resolvePricing(model));
9152
9718
  return {
9153
9719
  ...usage,
9154
9720
  costUSD,
9155
- toolTokens
9721
+ extraTotalTokens
9156
9722
  };
9157
9723
  }
9158
9724
  function getGeminiRole(message) {
@@ -9217,7 +9783,7 @@ const gooseUsageAdapter = {
9217
9783
  addFragmentInteraction(fragment, {
9218
9784
  content: "",
9219
9785
  costUSD: entry.usage.costUSD,
9220
- dedupeKey: `goose:${entry.sessionId}`,
9786
+ dedupeKey: `goose:${filePath}:${entry.sessionId}`,
9221
9787
  index: 0,
9222
9788
  model: entry.model,
9223
9789
  role: "usage",
@@ -9248,11 +9814,11 @@ function parseGooseRow(row, resolvePricing) {
9248
9814
  const inputTokens = getNumber$6(row.accumulated_input_tokens) || getNumber$6(row.input_tokens);
9249
9815
  const outputTokens = getNumber$6(row.accumulated_output_tokens) || getNumber$6(row.output_tokens);
9250
9816
  const totalTokens = getNumber$6(row.accumulated_total_tokens) || getNumber$6(row.total_tokens) || inputTokens + outputTokens;
9251
- const reasoningOutputTokens = Math.max(0, totalTokens - inputTokens - outputTokens);
9817
+ const extraTotalTokens = Math.max(0, totalTokens - inputTokens - outputTokens);
9252
9818
  const usage = toInteractionUsage({
9819
+ extraTotalTokens,
9253
9820
  inputTokens,
9254
- outputTokens,
9255
- reasoningOutputTokens
9821
+ outputTokens
9256
9822
  });
9257
9823
  if (isZeroInteractionUsage(usage)) {
9258
9824
  return null;
@@ -9370,9 +9936,9 @@ function parseHermesRow(row, resolvePricing) {
9370
9936
  const usage = toInteractionUsage({
9371
9937
  cacheCreationTokens: toNumber(row.cache_write_tokens),
9372
9938
  cacheReadTokens: toNumber(row.cache_read_tokens),
9939
+ extraTotalTokens: toNumber(row.reasoning_tokens),
9373
9940
  inputTokens: toNumber(row.input_tokens),
9374
- outputTokens: toNumber(row.output_tokens),
9375
- reasoningOutputTokens: toNumber(row.reasoning_tokens)
9941
+ outputTokens: toNumber(row.output_tokens)
9376
9942
  });
9377
9943
  if (isZeroInteractionUsage(usage) && !toOptionalNumber(row.actual_cost_usd) && !toOptionalNumber(row.estimated_cost_usd)) {
9378
9944
  return null;
@@ -9497,15 +10063,16 @@ function parseKiloMessage(value, row, filePath, resolvePricing) {
9497
10063
  if (!tokens || !model || !timestamp) {
9498
10064
  return null;
9499
10065
  }
10066
+ const extraTotalTokens = getNumber$5(tokens.reasoning);
9500
10067
  const usage = toInteractionUsage({
9501
10068
  ...applyTotalUsageFallback({
9502
10069
  cacheCreationTokens: getNumber$5((_b = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _b.write),
9503
10070
  cacheReadTokens: getNumber$5((_c = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _c.read),
9504
10071
  inputTokens: getNumber$5(tokens.input),
9505
10072
  outputTokens: getNumber$5(tokens.output),
9506
- reasoningOutputTokens: getNumber$5(tokens.reasoning),
9507
- totalTokens: getNumber$5(tokens.total)
9508
- })
10073
+ totalTokens: Math.max(getNumber$5(tokens.total) - extraTotalTokens, 0)
10074
+ }),
10075
+ extraTotalTokens
9509
10076
  });
9510
10077
  if (isZeroInteractionUsage(usage)) {
9511
10078
  return null;
@@ -9554,7 +10121,7 @@ const kimiUsageAdapter = {
9554
10121
  return groups.flat().filter((filePath) => isKimiWireFile(join(dirname$1(dirname$1(dirname$1(filePath))), "sessions"), filePath)).flatMap((filePath) => toDiscoveredUsageFile(filePath, "kimi"));
9555
10122
  },
9556
10123
  parseFile(filePath, resolvePricing) {
9557
- var _a;
10124
+ var _a, _b, _c, _d;
9558
10125
  const sessionId = getKimiSessionId(filePath);
9559
10126
  const model = getKimiModel(filePath);
9560
10127
  const fallbackTimestamp = getFileModifiedAtIso(filePath);
@@ -9575,7 +10142,7 @@ const kimiUsageAdapter = {
9575
10142
  continue;
9576
10143
  }
9577
10144
  const usage = toInteractionUsage({
9578
- ...applyTotalUsageFallback({
10145
+ ...applyTotalUsageAsExtra({
9579
10146
  cacheCreationTokens: getNumber$4(tokenUsage.input_cache_creation),
9580
10147
  cacheReadTokens: getNumber$4(tokenUsage.input_cache_read),
9581
10148
  inputTokens: getNumber$4(tokenUsage.input_other),
@@ -9596,7 +10163,11 @@ const kimiUsageAdapter = {
9596
10163
  normalizeStringValue(payload.message_id) || "",
9597
10164
  timestamp || "",
9598
10165
  model,
9599
- 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)
9600
10171
  ].join(":"),
9601
10172
  index,
9602
10173
  model,
@@ -9652,7 +10223,7 @@ const openClawUsageAdapter = {
9652
10223
  return groups.flat().filter((filePath) => isOpenClawSessionFile(basename$1(filePath))).flatMap((filePath) => toDiscoveredUsageFile(filePath, "openclaw"));
9653
10224
  },
9654
10225
  parseFile(filePath) {
9655
- var _a, _b, _c, _d, _e;
10226
+ var _a, _b, _c, _d, _e, _f, _g, _h;
9656
10227
  const lines = readFileSync(filePath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
9657
10228
  const sessionId = getOpenClawSessionId(filePath);
9658
10229
  const fragment = createSessionFragment({
@@ -9686,7 +10257,7 @@ const openClawUsageAdapter = {
9686
10257
  continue;
9687
10258
  }
9688
10259
  const usage = toInteractionUsage({
9689
- ...applyTotalUsageFallback({
10260
+ ...applyTotalUsageAsExtra({
9690
10261
  cacheCreationTokens: getNumber$3(usageRecord.cacheWrite),
9691
10262
  cacheReadTokens: getNumber$3(usageRecord.cacheRead),
9692
10263
  inputTokens: getNumber$3(usageRecord.input),
@@ -9711,8 +10282,9 @@ const openClawUsageAdapter = {
9711
10282
  rawModel,
9712
10283
  String(usage.inputTokens),
9713
10284
  String(usage.outputTokens),
9714
- String(usage.cachedInputTokens),
9715
- 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),
9716
10288
  String(usage.costUSD)
9717
10289
  ].join(":"),
9718
10290
  index,
@@ -9820,8 +10392,12 @@ const openCodeUsageAdapter = {
9820
10392
  }
9821
10393
  const value = parseJsonFile(filePath);
9822
10394
  const record = normalizeUnknownRecord(value);
10395
+ const interactionId = normalizeStringValue(record == null ? void 0 : record.id);
10396
+ if (interactionId && isDuplicatedByOpenCodeDatabase(filePath, interactionId)) {
10397
+ return [];
10398
+ }
9823
10399
  const entry = record ? getOpenCodeMessageEntry(record, resolvePricing, {
9824
- interactionId: normalizeStringValue(record.id),
10400
+ interactionId,
9825
10401
  sessionId: normalizeStringValue(record.sessionID)
9826
10402
  }) : null;
9827
10403
  if (!entry) {
@@ -9874,7 +10450,7 @@ function getOpenCodeMessageEntry(value, resolvePricing, options = {}) {
9874
10450
  return null;
9875
10451
  }
9876
10452
  const usage = toInteractionUsage({
9877
- ...applyTotalUsageFallback({
10453
+ ...applyTotalUsageAsExtra({
9878
10454
  cacheCreationTokens: getNumber$2((_b = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _b.write),
9879
10455
  cacheReadTokens: getNumber$2((_c = normalizeUnknownRecord(tokens.cache)) == null ? void 0 : _c.read),
9880
10456
  inputTokens: getNumber$2(tokens.input),
@@ -9891,7 +10467,10 @@ function getOpenCodeMessageEntry(value, resolvePricing, options = {}) {
9891
10467
  }
9892
10468
  const sessionId = options.sessionId || "unknown";
9893
10469
  const directCost = normalizeFiniteNumberOrNull(value.cost);
9894
- 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
+ });
9895
10474
  return {
9896
10475
  interactionId: options.interactionId || normalizeStringValue(value.id) || `${sessionId}:${timestamp}:${model}`,
9897
10476
  model,
@@ -9907,6 +10486,25 @@ function getOpenCodeLookupCandidates(model) {
9907
10486
  const normalizedModel = normalizeOpenCodeModelName(resolveOpenCodeModelName(model.trim()));
9908
10487
  return [model.trim(), normalizedModel];
9909
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
+ }
9910
10508
  function getOpenCodeModelCandidates(model, provider) {
9911
10509
  const normalizedModel = normalizeOpenCodeModelName(resolveOpenCodeModelName(model));
9912
10510
  const candidates = [model, normalizedModel];
@@ -9945,6 +10543,22 @@ function parseUnknownJson(value) {
9945
10543
  return null;
9946
10544
  }
9947
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
+ }
9948
10562
 
9949
10563
  const piUsageAdapter = {
9950
10564
  createPricingResolver: createZeroPricingResolver,
@@ -9955,7 +10569,7 @@ const piUsageAdapter = {
9955
10569
  return groups.flat().flatMap((filePath) => toDiscoveredUsageFile(filePath, "pi"));
9956
10570
  },
9957
10571
  parseFile(filePath) {
9958
- var _a, _b, _c;
10572
+ var _a, _b, _c, _d, _e, _f;
9959
10573
  const lines = parseJsonlFile(filePath);
9960
10574
  const sessionId = getPiSessionId(filePath);
9961
10575
  const project = getPiProject(filePath);
@@ -9978,7 +10592,7 @@ const piUsageAdapter = {
9978
10592
  continue;
9979
10593
  }
9980
10594
  const usage = toInteractionUsage({
9981
- ...applyTotalUsageFallback({
10595
+ ...applyTotalUsageAsExtra({
9982
10596
  cacheCreationTokens: getNumber$1(usageRecord.cacheWrite),
9983
10597
  cacheReadTokens: getNumber$1(usageRecord.cacheRead),
9984
10598
  inputTokens: getNumber$1(usageRecord.input),
@@ -9994,6 +10608,19 @@ const piUsageAdapter = {
9994
10608
  addFragmentInteraction(fragment, {
9995
10609
  content: "",
9996
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(":"),
9997
10624
  index,
9998
10625
  model: rawModel ? `[pi] ${rawModel}` : null,
9999
10626
  role: "assistant",
@@ -10057,14 +10684,15 @@ const qwenUsageAdapter = {
10057
10684
  if (!record || normalizeStringValue(record.type) !== "assistant" || !usageRecord) {
10058
10685
  continue;
10059
10686
  }
10687
+ const extraTotalTokens = getNumber(usageRecord.thoughtsTokenCount);
10060
10688
  const usage = toInteractionUsage({
10061
10689
  ...applyTotalUsageFallback({
10062
10690
  cacheReadTokens: getNumber(usageRecord.cachedContentTokenCount),
10063
10691
  inputTokens: getNumber(usageRecord.promptTokenCount),
10064
10692
  outputTokens: getNumber(usageRecord.candidatesTokenCount),
10065
- reasoningOutputTokens: getNumber(usageRecord.thoughtsTokenCount),
10066
- totalTokens: getNumber(usageRecord.totalTokenCount)
10067
- })
10693
+ totalTokens: Math.max(getNumber(usageRecord.totalTokenCount) - extraTotalTokens, 0)
10694
+ }),
10695
+ extraTotalTokens
10068
10696
  });
10069
10697
  if (isZeroInteractionUsage(usage)) {
10070
10698
  continue;
@@ -10451,7 +11079,8 @@ function buildProjectUsageCatalogItemsFromDetails(details) {
10451
11079
  return Array.from(details).map(([label, detail]) => {
10452
11080
  return {
10453
11081
  label,
10454
- type: getProjectCatalogType(getProjectDetailPlatforms(detail))
11082
+ platforms: getProjectDetailPlatforms(detail),
11083
+ totalTokens: getProjectDetailTotalTokens(detail)
10455
11084
  };
10456
11085
  }).sort((a, b) => a.label.localeCompare(b.label));
10457
11086
  }
@@ -10527,8 +11156,8 @@ function getProjectDetailPlatforms(detail) {
10527
11156
  return ((_a = detail.analyzing[platform]) != null ? _a : createEmptyProjectPlatformUsage()).sessions.length > 0;
10528
11157
  });
10529
11158
  }
10530
- function getProjectCatalogType(platforms) {
10531
- return platforms.length === 1 ? platforms[0] : "mixed";
11159
+ function getProjectDetailTotalTokens(detail) {
11160
+ return getProjectDetailSessions(detail).reduce((sum, session) => sum + session.tokenTotal, 0);
10532
11161
  }
10533
11162
  function buildSessionListModulePayload(sessionRows, sessions) {
10534
11163
  const sessionList = sessions.map(({ interactions: _interactions, ...session }) => session);
@@ -10549,45 +11178,318 @@ function buildProjectPlatformPayloadMap(detail, module) {
10549
11178
  })
10550
11179
  );
10551
11180
  }
10552
- function buildProjectLoadUsageResult(sessions, platform = "all") {
10553
- const usage = buildLoadUsageResult(getProjectAggregateEvents(sessions), sessions, {
10554
- aggregateOptions: {
10555
- includeModel: (event) => platform !== "claudeCode" || event.model !== "<synthetic>"
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
+ }
10556
11439
  }
11440
+ }
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
+ };
10557
11450
  });
10558
11451
  return {
10559
- ...usage,
10560
- sessionUsage: sessions
11452
+ previousPromptCount,
11453
+ previousSessionCount,
11454
+ promptCount,
11455
+ sessionCount,
11456
+ todayHourlyUsage
10561
11457
  };
10562
11458
  }
10563
- function getProjectAggregateEvents(sessions) {
10564
- return sessions.flatMap((session) => session.interactions.filter((interaction) => interaction.usage && interaction.timestamp && hasBillableUsage(interaction.usage)).map((interaction) => {
10565
- var _a, _b;
10566
- return {
10567
- cachedInputTokens: interaction.usage.cachedInputTokens,
10568
- costUSD: interaction.usage.costUSD,
10569
- inputTokens: interaction.usage.inputTokens,
10570
- isFallbackModel: (_a = interaction.usage.isFallbackModel) != null ? _a : false,
10571
- model: (_b = interaction.model) != null ? _b : session.model,
10572
- outputTokens: interaction.usage.outputTokens,
10573
- project: session.project,
10574
- reasoningOutputTokens: interaction.usage.reasoningOutputTokens,
10575
- repository: session.repository,
10576
- sessionId: session.id,
10577
- timestamp: interaction.timestamp,
10578
- totalTokens: interaction.usage.totalTokens
10579
- };
10580
- })).sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp));
10581
- }
10582
- function hasBillableUsage(usage) {
10583
- return usage.totalTokens > 0 || usage.costUSD > 0;
10584
- }
10585
- function collectSessionModels(sessions) {
10586
- return uniqueItems(sessions.flatMap((session) => session.models)).sort((a, b) => a.localeCompare(b));
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;
10587
11465
  }
10588
- function getEarliestStartedAt(sessions) {
10589
- var _a;
10590
- 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;
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)
11491
+ }
11492
+ ];
10591
11493
  }
10592
11494
 
10593
11495
  var __defProp = Object.defineProperty;
@@ -10603,7 +11505,7 @@ class UsageDataRuntime {
10603
11505
  bootstrap: null,
10604
11506
  hydratedAt: 0,
10605
11507
  projectCatalog: [],
10606
- projectDetails: /* @__PURE__ */ new Map(),
11508
+ projectDetails: null,
10607
11509
  refreshStartedAt: 0
10608
11510
  });
10609
11511
  __publicField(this, "initializePromise", null);
@@ -10618,7 +11520,6 @@ class UsageDataRuntime {
10618
11520
  if (!this.initializePromise) {
10619
11521
  this.initializePromise = this.hydrateFromRepository().finally(() => {
10620
11522
  this.startWatcher();
10621
- void this.refreshInBackground();
10622
11523
  });
10623
11524
  }
10624
11525
  return this.initializePromise;
@@ -10634,7 +11535,7 @@ class UsageDataRuntime {
10634
11535
  }
10635
11536
  async getProjectCatalog() {
10636
11537
  await this.initialize();
10637
- if (this.state.projectCatalog.length === 0 && this.state.projectDetails.size === 0) {
11538
+ if (this.state.projectCatalog.length === 0) {
10638
11539
  await this.refreshNow();
10639
11540
  } else {
10640
11541
  this.scheduleRefreshIfStale();
@@ -10646,12 +11547,18 @@ class UsageDataRuntime {
10646
11547
  const bootstrap = await this.getBootstrap();
10647
11548
  return (_a = bootstrap[platform]) != null ? _a : createEmptyLoadUsageResult();
10648
11549
  }
11550
+ async getHomeDashboardModules() {
11551
+ return buildHomeDashboardModules(await this.getBootstrap(), this.repository.loadHomeDashboardTodayInsights());
11552
+ }
10649
11553
  async getProjectDataModules(request) {
10650
11554
  await this.initialize();
10651
11555
  const projectLabel = (request.project || "").trim();
10652
11556
  if (!projectLabel) {
10653
11557
  throw new Error("Missing project name for project data request.");
10654
11558
  }
11559
+ if (this.state.projectDetails === null) {
11560
+ this.state.projectDetails = this.repository.loadProjectDetails();
11561
+ }
10655
11562
  const detail = this.state.projectDetails.get(projectLabel);
10656
11563
  if (!detail) {
10657
11564
  await this.refreshNow();
@@ -10672,10 +11579,8 @@ class UsageDataRuntime {
10672
11579
  var _a, _b;
10673
11580
  const bootstrap = this.repository.loadBootstrap();
10674
11581
  const projectCatalog = this.repository.loadProjectCatalog();
10675
- const projectDetails = this.repository.loadProjectDetails();
10676
11582
  this.state.bootstrap = (_a = bootstrap == null ? void 0 : bootstrap.payload) != null ? _a : null;
10677
11583
  this.state.projectCatalog = (_b = projectCatalog == null ? void 0 : projectCatalog.payload) != null ? _b : [];
10678
- this.state.projectDetails = projectDetails;
10679
11584
  this.state.hydratedAt = Math.max(
10680
11585
  bootstrap ? Date.parse(bootstrap.updatedAt) : 0,
10681
11586
  projectCatalog ? Date.parse(projectCatalog.updatedAt) : 0
@@ -10720,14 +11625,16 @@ class UsageDataRuntime {
10720
11625
  ),
10721
11626
  version: this.config.version
10722
11627
  };
10723
- 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);
10724
11629
  const projectCatalog = buildProjectUsageCatalogItemsFromDetails(projectDetails.entries());
10725
11630
  this.repository.saveBootstrap(bootstrap);
10726
11631
  this.repository.saveProjectCatalog(projectCatalog);
10727
11632
  this.repository.replaceProjectDetails(projectDetails);
10728
11633
  this.state.bootstrap = bootstrap;
10729
11634
  this.state.projectCatalog = projectCatalog;
10730
- this.state.projectDetails = projectDetails;
11635
+ if (this.state.projectDetails) {
11636
+ this.state.projectDetails = projectDetails;
11637
+ }
10731
11638
  this.state.hydratedAt = Date.now();
10732
11639
  }
10733
11640
  dispose() {
@@ -11025,159 +11932,166 @@ const plugins = [
11025
11932
  ];
11026
11933
 
11027
11934
  const assets = {
11028
- "/favicon.ico": {
11029
- "type": "image/vnd.microsoft.icon",
11030
- "etag": "\"1083e-LfyFZ+1JmdianDqe/sQN2Ou0IzQ\"",
11031
- "mtime": "2026-05-23T14:23:38.580Z",
11032
- "size": 67646,
11033
- "path": "../public/favicon.ico"
11034
- },
11035
11935
  "/logo.svg": {
11036
11936
  "type": "image/svg+xml",
11037
11937
  "etag": "\"1550-fwYFdULdJ83Qp0FjnnX31iQz9oI\"",
11038
- "mtime": "2026-05-23T14:23:38.580Z",
11938
+ "mtime": "2026-05-26T08:31:48.218Z",
11039
11939
  "size": 5456,
11040
11940
  "path": "../public/logo.svg"
11041
11941
  },
11042
11942
  "/robots.txt": {
11043
11943
  "type": "text/plain; charset=utf-8",
11044
11944
  "etag": "\"18-j8OIsL9qGDmNZ+lHhp2tyH4XtaE\"",
11045
- "mtime": "2026-05-23T14:23:38.580Z",
11945
+ "mtime": "2026-05-26T08:31:48.218Z",
11046
11946
  "size": 24,
11047
11947
  "path": "../public/robots.txt"
11048
11948
  },
11049
- "/_nuxt/BO_d_Oyx.js": {
11949
+ "/_nuxt/37OOe3RF.js": {
11050
11950
  "type": "text/javascript; charset=utf-8",
11051
- "etag": "\"3cf2-0lW8nTIOrZ/84LIG/e4aAWtiK4U\"",
11052
- "mtime": "2026-05-23T14:23:38.575Z",
11053
- "size": 15602,
11054
- "path": "../public/_nuxt/BO_d_Oyx.js"
11951
+ "etag": "\"433-9g1fS7jC4SfhMOz/zYtzEkjj3l4\"",
11952
+ "mtime": "2026-05-26T08:31:48.215Z",
11953
+ "size": 1075,
11954
+ "path": "../public/_nuxt/37OOe3RF.js"
11055
11955
  },
11056
- "/_nuxt/BtuXxixc.js": {
11057
- "type": "text/javascript; charset=utf-8",
11058
- "etag": "\"4a32-RKhNLUY/9mTZffNHKzq+ZTE6NEk\"",
11059
- "mtime": "2026-05-23T14:23:38.575Z",
11060
- "size": 18994,
11061
- "path": "../public/_nuxt/BtuXxixc.js"
11956
+ "/favicon.ico": {
11957
+ "type": "image/vnd.microsoft.icon",
11958
+ "etag": "\"1083e-LfyFZ+1JmdianDqe/sQN2Ou0IzQ\"",
11959
+ "mtime": "2026-05-26T08:31:48.218Z",
11960
+ "size": 67646,
11961
+ "path": "../public/favicon.ico"
11062
11962
  },
11063
- "/_nuxt/CBu27bzx.js": {
11963
+ "/_nuxt/65Ayv2XK.js": {
11064
11964
  "type": "text/javascript; charset=utf-8",
11065
- "etag": "\"d7b-AVXq8ZkGg3EwuGLn96fmpW4OGjI\"",
11066
- "mtime": "2026-05-23T14:23:38.575Z",
11067
- "size": 3451,
11068
- "path": "../public/_nuxt/CBu27bzx.js"
11965
+ "etag": "\"bb0d-ldbgmGlpc1qx4bYEx4cXkSFSCg8\"",
11966
+ "mtime": "2026-05-26T08:31:48.215Z",
11967
+ "size": 47885,
11968
+ "path": "../public/_nuxt/65Ayv2XK.js"
11069
11969
  },
11070
- "/_nuxt/CC4_0abj.js": {
11970
+ "/_nuxt/BOWwkrCY.js": {
11071
11971
  "type": "text/javascript; charset=utf-8",
11072
- "etag": "\"14f4-HSFTyVIPyb4gKy6pzPkI1FSb2nw\"",
11073
- "mtime": "2026-05-23T14:23:38.575Z",
11074
- "size": 5364,
11075
- "path": "../public/_nuxt/CC4_0abj.js"
11972
+ "etag": "\"540a-Q/6WhDpXkujbyVqTxKp+MDIjyN8\"",
11973
+ "mtime": "2026-05-26T08:31:48.215Z",
11974
+ "size": 21514,
11975
+ "path": "../public/_nuxt/BOWwkrCY.js"
11076
11976
  },
11077
- "/_nuxt/CLHpvfq1.js": {
11977
+ "/_nuxt/D7qEPtpx.js": {
11078
11978
  "type": "text/javascript; charset=utf-8",
11079
- "etag": "\"eb4-4rjemX6Xiwgd6CWSl2KPPk0STII\"",
11080
- "mtime": "2026-05-23T14:23:38.575Z",
11979
+ "etag": "\"eb4-cYfHtGVwvBnjUla1c7r6p8WQ+fU\"",
11980
+ "mtime": "2026-05-26T08:31:48.215Z",
11081
11981
  "size": 3764,
11082
- "path": "../public/_nuxt/CLHpvfq1.js"
11982
+ "path": "../public/_nuxt/D7qEPtpx.js"
11983
+ },
11984
+ "/_nuxt/D9-Yw1TR.js": {
11985
+ "type": "text/javascript; charset=utf-8",
11986
+ "etag": "\"42dc-z2VCBVgr0YDv874MuLF5LmD+w6g\"",
11987
+ "mtime": "2026-05-26T08:31:48.215Z",
11988
+ "size": 17116,
11989
+ "path": "../public/_nuxt/D9-Yw1TR.js"
11990
+ },
11991
+ "/_nuxt/DF2WsXH3.js": {
11992
+ "type": "text/javascript; charset=utf-8",
11993
+ "etag": "\"101-2rfNy5z/IaUQM5ONN45oT3dPyOw\"",
11994
+ "mtime": "2026-05-26T08:31:48.215Z",
11995
+ "size": 257,
11996
+ "path": "../public/_nuxt/DF2WsXH3.js"
11997
+ },
11998
+ "/_nuxt/C0GhHHgI.js": {
11999
+ "type": "text/javascript; charset=utf-8",
12000
+ "etag": "\"cf91-HNazofOinHCfSBf403LLacaEnfc\"",
12001
+ "mtime": "2026-05-26T08:31:48.215Z",
12002
+ "size": 53137,
12003
+ "path": "../public/_nuxt/C0GhHHgI.js"
11083
12004
  },
11084
- "/_nuxt/COBlwcVj.js": {
12005
+ "/_nuxt/DXWxIyGU.js": {
11085
12006
  "type": "text/javascript; charset=utf-8",
11086
- "etag": "\"bc73-Y7i7npRYQlWHMyUwkaVFnYcViz8\"",
11087
- "mtime": "2026-05-23T14:23:38.575Z",
11088
- "size": 48243,
11089
- "path": "../public/_nuxt/COBlwcVj.js"
12007
+ "etag": "\"d7b-TUo4s2vJwQ7SvlmJq8Lc5JpszPo\"",
12008
+ "mtime": "2026-05-26T08:31:48.216Z",
12009
+ "size": 3451,
12010
+ "path": "../public/_nuxt/DXWxIyGU.js"
11090
12011
  },
11091
- "/_nuxt/CGsjOcfM.js": {
12012
+ "/_nuxt/De8DvPWL.js": {
11092
12013
  "type": "text/javascript; charset=utf-8",
11093
- "etag": "\"1141f-bN3EpHipXo2C1U4csFLDLC6fWCE\"",
11094
- "mtime": "2026-05-23T14:23:38.575Z",
11095
- "size": 70687,
11096
- "path": "../public/_nuxt/CGsjOcfM.js"
12014
+ "etag": "\"14f9-ru/D4lCOWqCP3lvQM3EWGPMYRaw\"",
12015
+ "mtime": "2026-05-26T08:31:48.215Z",
12016
+ "size": 5369,
12017
+ "path": "../public/_nuxt/De8DvPWL.js"
11097
12018
  },
11098
- "/_nuxt/CjeAKHhp.js": {
12019
+ "/_nuxt/DKaPq50Z.js": {
11099
12020
  "type": "text/javascript; charset=utf-8",
11100
- "etag": "\"760b-6/Vln7oc6We2Fv2JzlfIvdSbQ8o\"",
11101
- "mtime": "2026-05-23T14:23:38.575Z",
11102
- "size": 30219,
11103
- "path": "../public/_nuxt/CjeAKHhp.js"
12021
+ "etag": "\"f34c-URgC7Dz0Xf+JUUuL4yo1mSHnYmQ\"",
12022
+ "mtime": "2026-05-26T08:31:48.216Z",
12023
+ "size": 62284,
12024
+ "path": "../public/_nuxt/DKaPq50Z.js"
11104
12025
  },
11105
- "/_nuxt/CvvesHM7.js": {
12026
+ "/_nuxt/DxvuOJRP.js": {
11106
12027
  "type": "text/javascript; charset=utf-8",
11107
- "etag": "\"5e1e-OZRyFwLTAD0LX+mGdKN2HAR5jVk\"",
11108
- "mtime": "2026-05-23T14:23:38.575Z",
11109
- "size": 24094,
11110
- "path": "../public/_nuxt/CvvesHM7.js"
12028
+ "etag": "\"10890-8PgC64ZUxoVro0//XJbLva3lqK0\"",
12029
+ "mtime": "2026-05-26T08:31:48.216Z",
12030
+ "size": 67728,
12031
+ "path": "../public/_nuxt/DxvuOJRP.js"
11111
12032
  },
11112
- "/_nuxt/BL-DadsH.js": {
12033
+ "/_nuxt/DgMMKsPE.js": {
11113
12034
  "type": "text/javascript; charset=utf-8",
11114
- "etag": "\"4135e-MQb3g4SBvW5lVewRuNKmjRlf13Y\"",
11115
- "mtime": "2026-05-23T14:23:38.575Z",
11116
- "size": 267102,
11117
- "path": "../public/_nuxt/BL-DadsH.js"
12035
+ "etag": "\"35763-ZIXCcb7yuHkNvDqXH2EEhwlcyV4\"",
12036
+ "mtime": "2026-05-26T08:31:48.216Z",
12037
+ "size": 218979,
12038
+ "path": "../public/_nuxt/DgMMKsPE.js"
11118
12039
  },
11119
- "/_nuxt/N8W_Dp-N.js": {
12040
+ "/_nuxt/Jp5cgQZi.js": {
11120
12041
  "type": "text/javascript; charset=utf-8",
11121
- "etag": "\"9f6b-XuYcAlmRZBqRFlSle9M9m9mFrRI\"",
11122
- "mtime": "2026-05-23T14:23:38.577Z",
11123
- "size": 40811,
11124
- "path": "../public/_nuxt/N8W_Dp-N.js"
12042
+ "etag": "\"3c6da-89AEgPN5OI/3SCYBdFqSxRLSihE\"",
12043
+ "mtime": "2026-05-26T08:31:48.216Z",
12044
+ "size": 247514,
12045
+ "path": "../public/_nuxt/Jp5cgQZi.js"
11125
12046
  },
11126
12047
  "/_nuxt/error-404.CFBEg71j.css": {
11127
12048
  "type": "text/css; charset=utf-8",
11128
12049
  "etag": "\"97e-GvhaEAryQvrSXyDcP4RiHXzYb5o\"",
11129
- "mtime": "2026-05-23T14:23:38.578Z",
12050
+ "mtime": "2026-05-26T08:31:48.216Z",
11130
12051
  "size": 2430,
11131
12052
  "path": "../public/_nuxt/error-404.CFBEg71j.css"
11132
12053
  },
11133
12054
  "/_nuxt/error-500.BqCnH31G.css": {
11134
12055
  "type": "text/css; charset=utf-8",
11135
12056
  "etag": "\"773-Tpf6lA6A2FEDtjLyWUXKolBZ3hM\"",
11136
- "mtime": "2026-05-23T14:23:38.578Z",
12057
+ "mtime": "2026-05-26T08:31:48.216Z",
11137
12058
  "size": 1907,
11138
12059
  "path": "../public/_nuxt/error-500.BqCnH31G.css"
11139
12060
  },
11140
- "/_nuxt/vkAmg0G5.js": {
12061
+ "/_nuxt/qXgLTL_3.js": {
11141
12062
  "type": "text/javascript; charset=utf-8",
11142
- "etag": "\"2a69-vHNrnLDtyhAaSseLnkiNli6Wf3k\"",
11143
- "mtime": "2026-05-23T14:23:38.578Z",
11144
- "size": 10857,
11145
- "path": "../public/_nuxt/vkAmg0G5.js"
12063
+ "etag": "\"993e-F6dGXZ77Rf6Afn6RLLvwifZFe5Q\"",
12064
+ "mtime": "2026-05-26T08:31:48.216Z",
12065
+ "size": 39230,
12066
+ "path": "../public/_nuxt/qXgLTL_3.js"
11146
12067
  },
11147
- "/_nuxt/wUprbfrX.js": {
12068
+ "/_nuxt/y6mAKUDU.js": {
11148
12069
  "type": "text/javascript; charset=utf-8",
11149
- "etag": "\"4399-F/KjRyJ+oS+PQ4RVLxrSSwvgmVA\"",
11150
- "mtime": "2026-05-23T14:23:38.578Z",
11151
- "size": 17305,
11152
- "path": "../public/_nuxt/wUprbfrX.js"
11153
- },
11154
- "/_nuxt/entry.vHfFzkyD.css": {
11155
- "type": "text/css; charset=utf-8",
11156
- "etag": "\"1d217-AX0vUIWFE2kL17zPKjOPcQBxadQ\"",
11157
- "mtime": "2026-05-23T14:23:38.578Z",
11158
- "size": 119319,
11159
- "path": "../public/_nuxt/entry.vHfFzkyD.css"
12070
+ "etag": "\"2c57-7lVqV+qTY/Dqb4M0PdxYAItocQw\"",
12071
+ "mtime": "2026-05-26T08:31:48.216Z",
12072
+ "size": 11351,
12073
+ "path": "../public/_nuxt/y6mAKUDU.js"
11160
12074
  },
11161
12075
  "/_nuxt/builds/latest.json": {
11162
12076
  "type": "application/json",
11163
- "etag": "\"47-t9P0TIFYP6zGSxVQ5dq1HIuFqRs\"",
11164
- "mtime": "2026-05-23T14:23:38.572Z",
12077
+ "etag": "\"47-yneWhd0H1nYqEdMbDKqzhQJ6sxE\"",
12078
+ "mtime": "2026-05-26T08:31:48.213Z",
11165
12079
  "size": 71,
11166
12080
  "path": "../public/_nuxt/builds/latest.json"
11167
12081
  },
11168
- "/_nuxt/builds/meta/be10c896-ee75-4f4f-911d-c3f0a8b4d417.json": {
12082
+ "/_nuxt/builds/meta/7ce9c611-6071-4cbd-8926-1e53d9ef21bd.json": {
11169
12083
  "type": "application/json",
11170
- "etag": "\"58-gw6htu/si8XhmwFAJeIsDDnQeNw\"",
11171
- "mtime": "2026-05-23T14:23:38.570Z",
12084
+ "etag": "\"58-ozcqeghbP4BbYZVRtP6bwTCHhN8\"",
12085
+ "mtime": "2026-05-26T08:31:48.211Z",
11172
12086
  "size": 88,
11173
- "path": "../public/_nuxt/builds/meta/be10c896-ee75-4f4f-911d-c3f0a8b4d417.json"
12087
+ "path": "../public/_nuxt/builds/meta/7ce9c611-6071-4cbd-8926-1e53d9ef21bd.json"
11174
12088
  },
11175
- "/_nuxt/IstswKJB.js": {
11176
- "type": "text/javascript; charset=utf-8",
11177
- "etag": "\"35466-qpjUFamQKI+spY54orxHBSsbAV4\"",
11178
- "mtime": "2026-05-23T14:23:38.577Z",
11179
- "size": 218214,
11180
- "path": "../public/_nuxt/IstswKJB.js"
12089
+ "/_nuxt/entry.DnkKc-6G.css": {
12090
+ "type": "text/css; charset=utf-8",
12091
+ "etag": "\"1d8a6-KK9a3JMzAwbSGw5SnlSiODNKxHg\"",
12092
+ "mtime": "2026-05-26T08:31:48.216Z",
12093
+ "size": 120998,
12094
+ "path": "../public/_nuxt/entry.DnkKc-6G.css"
11181
12095
  }
11182
12096
  };
11183
12097
 
@@ -11673,9 +12587,13 @@ const inlineConfig = {
11673
12587
  "wi",
11674
12588
  "wpf",
11675
12589
  "zmdi",
11676
- "zondicons"
12590
+ "zondicons",
12591
+ "ai"
11677
12592
  ],
11678
- "fetchTimeout": 1500
12593
+ "fetchTimeout": 1500,
12594
+ "customCollections": [
12595
+ "ai"
12596
+ ]
11679
12597
  }
11680
12598
  };
11681
12599
 
@@ -11701,239 +12619,110 @@ function _deepFreeze(object) {
11701
12619
  return Object.freeze(object);
11702
12620
  }
11703
12621
 
11704
- function buildHomeDashboardModules(dashboardsByPlatform) {
11705
- var _a, _b, _c, _d;
11706
- const sessionUsage = buildSessionUsage(dashboardsByPlatform);
11707
- const dailyTokenUsage = mergeDailyTokenUsage(
11708
- PROJECT_USAGE_PLATFORMS.flatMap((platform) => getPlatformDashboard(dashboardsByPlatform, platform).dailyTokenUsage)
11709
- );
11710
- const monthlyModelUsage = mergeMonthlyModelUsage(
11711
- PROJECT_USAGE_PLATFORMS.flatMap((platform) => getPlatformDashboard(dashboardsByPlatform, platform).monthlyModelUsage)
11712
- );
11713
- const projectUsage = buildProjectUsage(sessionUsage);
11714
- const totalCost = dailyTokenUsage.reduce((sum, item) => sum + item.costUSD, 0);
11715
- const totalTokens = dailyTokenUsage.reduce((sum, item) => sum + item.totalTokens, 0);
11716
- const inputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.inputTokens, 0);
11717
- const cachedInputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.cachedInputTokens, 0);
11718
- const outputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.outputTokens, 0);
11719
- const reasoningOutputTokens = dailyTokenUsage.reduce((sum, item) => sum + item.reasoningOutputTokens, 0);
11720
- const totalSessions = sessionUsage.length;
11721
- const todayDateKey = getDateKey(/* @__PURE__ */ new Date());
11722
- const previousDayDateKey = getPreviousDateKey(todayDateKey);
11723
- const todayDailyUsage = dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === todayDateKey);
11724
- const previousDayDailyUsage = dailyTokenUsage.find((item) => getDateKeyFromLabel(item.date) === previousDayDateKey);
11725
- const costGrowthTrend = buildGrowthTrend(
11726
- (_a = todayDailyUsage == null ? void 0 : todayDailyUsage.costUSD) != null ? _a : 0,
11727
- (_b = previousDayDailyUsage == null ? void 0 : previousDayDailyUsage.costUSD) != null ? _b : 0,
11728
- formatCurrency
11729
- );
11730
- const tokenGrowthTrend = buildGrowthTrend(
11731
- (_c = todayDailyUsage == null ? void 0 : todayDailyUsage.totalTokens) != null ? _c : 0,
11732
- (_d = previousDayDailyUsage == null ? void 0 : previousDayDailyUsage.totalTokens) != null ? _d : 0,
11733
- formatCompactNumber
11734
- );
11735
- const efficiencyMetrics = buildEfficiencyMetrics({
11736
- cachedInputTokens,
11737
- inputTokens,
11738
- outputTokens,
11739
- reasoningOutputTokens,
11740
- totalTokens
11741
- });
11742
- return {
11743
- dailyTokenUsage,
11744
- efficiencyMetrics,
11745
- hotProjects: projectUsage,
11746
- modelUsage: monthlyModelUsage,
11747
- overviewCards: buildHomeOverviewCards({
11748
- cachedInputTokens,
11749
- costGrowthTrend,
11750
- inputTokens,
11751
- tokenGrowthTrend,
11752
- totalCost,
11753
- totalSessions,
11754
- totalTokens
11755
- }),
11756
- sessionAnalysis: {
11757
- items: sessionUsage,
11758
- totalSessions
11759
- }
11760
- };
11761
- }
11762
- function getPlatformDashboard(dashboardsByPlatform, platform) {
11763
- return dashboardsByPlatform[platform];
11764
- }
11765
- function buildSessionUsage(dashboardsByPlatform) {
11766
- return PROJECT_USAGE_PLATFORMS.flatMap((platform) => getPlatformDashboard(dashboardsByPlatform, platform).sessionUsage.map((session) => ({
11767
- ...session,
11768
- id: `${platform}:${session.id}`,
11769
- sessionId: `${platform}:${session.sessionId}`
11770
- }))).sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt));
11771
- }
11772
- function buildHomeOverviewCards(options) {
11773
- return [
11774
- {
11775
- detail: `${formatCurrency(options.totalCost)} total spend across all tools`,
11776
- icon: "lucide:wallet",
11777
- name: "Total Spend",
11778
- trend: options.costGrowthTrend.trend,
11779
- trendTone: options.costGrowthTrend.trendTone,
11780
- value: formatCurrency(options.totalCost)
11781
- },
11782
- {
11783
- detail: `${formatNumber(options.totalTokens)} total tokens across all tools`,
11784
- icon: "solar:cpu-line-duotone",
11785
- name: "Token Usage",
11786
- trend: options.tokenGrowthTrend.trend,
11787
- trendTone: options.tokenGrowthTrend.trendTone,
11788
- value: formatCompactNumber(options.totalTokens)
11789
- },
11790
- {
11791
- detail: `${formatNumber(options.cachedInputTokens)} of ${formatNumber(options.inputTokens)} input tokens were served from cache`,
11792
- icon: "lucide:database-zap",
11793
- name: "Cache Hit Rate",
11794
- trend: `${formatCompactNumber(options.cachedInputTokens)} cached`,
11795
- trendTone: "neutral",
11796
- value: formatPercent(options.inputTokens > 0 ? options.cachedInputTokens / options.inputTokens : 0)
11797
- },
11798
- {
11799
- detail: `${formatCurrency(options.totalCost)} across ${formatNumber(options.totalSessions)} sessions`,
11800
- icon: "lucide:receipt-text",
11801
- name: "Avg Session Cost",
11802
- trend: "across all tools",
11803
- trendTone: "neutral",
11804
- value: formatCurrency(options.totalSessions > 0 ? options.totalCost / options.totalSessions : 0)
11805
- }
11806
- ];
11807
- }
11808
- function buildEfficiencyMetrics(options) {
11809
- const cacheHitRate = safeRatio(options.cachedInputTokens, options.inputTokens);
11810
- const reasoningShare = safeRatio(options.reasoningOutputTokens, options.totalTokens);
11811
- const outputShare = safeRatio(options.outputTokens, options.totalTokens);
11812
- return [
11813
- {
11814
- detail: `${formatCompactNumber(options.cachedInputTokens)} cached input tokens`,
11815
- label: "Cache Hit Rate",
11816
- percent: cacheHitRate * 100,
11817
- tone: "green",
11818
- value: formatPercent(cacheHitRate)
11819
- },
11820
- {
11821
- detail: `${formatCompactNumber(options.reasoningOutputTokens)} reasoning output tokens`,
11822
- label: "Reasoning Token Share",
11823
- percent: reasoningShare * 100,
11824
- tone: "amber",
11825
- value: formatPercent(reasoningShare)
11826
- },
11827
- {
11828
- detail: `${formatCompactNumber(options.outputTokens)} output tokens`,
11829
- label: "Output Token Share",
11830
- percent: outputShare * 100,
11831
- tone: "sky",
11832
- value: formatPercent(outputShare)
11833
- }
11834
- ];
11835
- }
11836
- function safeRatio(numerator, denominator) {
11837
- return denominator > 0 ? numerator / denominator : 0;
11838
- }
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
+ };
11839
12629
 
11840
12630
  const PROJECT_USAGE_PLATFORM_META = {
11841
12631
  amp: {
11842
- aiIcon: "amp",
12632
+ aiIcon: "ai:amp",
11843
12633
  color: "#f34e3f",
11844
12634
  label: "Amp",
11845
12635
  slug: "amp"
11846
12636
  },
11847
12637
  claudeCode: {
11848
- aiIcon: "claude_code",
12638
+ aiIcon: "ai:claude-code",
11849
12639
  color: "#d97757",
11850
12640
  label: "Claude Code",
11851
12641
  slug: "claude_code"
11852
12642
  },
11853
12643
  codebuff: {
11854
- aiIcon: "codebuff",
12644
+ aiIcon: "ai:codebuff",
11855
12645
  color: "#14b8a6",
11856
12646
  label: "Codebuff",
11857
12647
  slug: "codebuff"
11858
12648
  },
11859
12649
  codex: {
11860
- aiIcon: "codex",
12650
+ aiIcon: "ai:codex",
11861
12651
  color: "#111827",
11862
12652
  label: "Codex",
11863
12653
  slug: "codex"
11864
12654
  },
11865
12655
  copilot: {
11866
- aiIcon: "copilot",
12656
+ aiIcon: "ai:copilot",
11867
12657
  color: "#0f766e",
11868
12658
  label: "GitHub Copilot",
11869
12659
  slug: "copilot"
11870
12660
  },
11871
12661
  droid: {
11872
- aiIcon: "droid",
12662
+ aiIcon: "ai:droid",
11873
12663
  color: "#06b6d4",
11874
12664
  label: "Droid",
11875
12665
  slug: "droid"
11876
12666
  },
11877
12667
  gemini: {
11878
- aiIcon: "gemini",
12668
+ aiIcon: "ai:gemini",
11879
12669
  color: "#0ea5e9",
11880
12670
  label: "Gemini",
11881
12671
  slug: "gemini"
11882
12672
  },
11883
12673
  goose: {
11884
- aiIcon: "goose",
12674
+ aiIcon: "ai:goose",
11885
12675
  color: "#22c55e",
11886
12676
  label: "Goose",
11887
12677
  slug: "goose"
11888
12678
  },
11889
12679
  hermes: {
11890
- aiIcon: "hermes",
12680
+ aiIcon: "ai:hermesagent",
11891
12681
  color: "#8b5cf6",
11892
12682
  label: "Hermes",
11893
12683
  slug: "hermes"
11894
12684
  },
11895
12685
  kilo: {
11896
- aiIcon: "kilo",
12686
+ aiIcon: "ai:kilo",
11897
12687
  color: "#f97316",
11898
12688
  label: "Kilo",
11899
12689
  slug: "kilo"
11900
12690
  },
11901
12691
  kimi: {
11902
- aiIcon: "kimi_code",
12692
+ aiIcon: "ai:kimi",
11903
12693
  color: "#2563eb",
11904
12694
  label: "Kimi",
11905
12695
  slug: "kimi"
11906
12696
  },
11907
12697
  openclaw: {
11908
- aiIcon: "openclaw",
12698
+ aiIcon: "ai:openclaw",
11909
12699
  color: "#ec4899",
11910
12700
  label: "OpenClaw",
11911
12701
  slug: "openclaw"
11912
12702
  },
11913
12703
  opencode: {
11914
- aiIcon: "open_code",
12704
+ aiIcon: "ai:open-code",
11915
12705
  color: "#4f46e5",
11916
12706
  label: "OpenCode",
11917
12707
  slug: "opencode"
11918
12708
  },
11919
12709
  pi: {
11920
- aiIcon: "pi",
12710
+ aiIcon: "ai:pi",
11921
12711
  color: "#a855f7",
11922
12712
  label: "Pi",
11923
12713
  slug: "pi"
11924
12714
  },
11925
12715
  qwen: {
11926
- aiIcon: "qwen_code",
12716
+ aiIcon: "ai:qwen",
11927
12717
  color: "#623ae7",
11928
12718
  label: "Qwen",
11929
12719
  slug: "qwen"
11930
12720
  }
11931
12721
  };
11932
- const platformSlugEntries = PROJECT_USAGE_PLATFORMS.map((platform) => [
12722
+ const platformBySlug = new Map(PROJECT_USAGE_PLATFORMS.map((platform) => [
11933
12723
  PROJECT_USAGE_PLATFORM_META[platform].slug,
11934
12724
  platform
11935
- ]);
11936
- const platformBySlug = new Map(platformSlugEntries);
12725
+ ]));
11937
12726
  function resolveProjectUsagePlatform(value) {
11938
12727
  var _a;
11939
12728
  const normalizedValue = value == null ? void 0 : value.trim();
@@ -11946,14 +12735,6 @@ function resolveProjectUsagePlatform(value) {
11946
12735
  return (_a = platformBySlug.get(normalizedValue)) != null ? _a : null;
11947
12736
  }
11948
12737
 
11949
- const ANALYSIS_AGENT_TOKEN_TYPES = ["day", "week", "month", "session"];
11950
- const ANALYSIS_AGENT_TOKEN_ROW_KEYS = {
11951
- day: "dailyRows",
11952
- month: "monthlyRows",
11953
- session: "sessionRows",
11954
- week: "weeklyRows"
11955
- };
11956
-
11957
12738
  function getAnalysisRuntime(event) {
11958
12739
  const runtimeConfig = useRuntimeConfig(event);
11959
12740
  const config = resolveConfig(runtimeConfig.public);
@@ -12020,7 +12801,7 @@ function defineRequiredAgentAnalysisHandler(select) {
12020
12801
  });
12021
12802
  }
12022
12803
  async function getHomeAnalysisModules(event) {
12023
- return buildHomeDashboardModules(await getAnalysisRuntime(event).getBootstrap());
12804
+ return getAnalysisRuntime(event).getHomeDashboardModules();
12024
12805
  }
12025
12806
  function normalizeQueryString(value) {
12026
12807
  if (Array.isArray(value)) {
@@ -12096,6 +12877,7 @@ const _lazy_NXGH0G = () => import('../routes/api/analysis/overview-cards.json.mj
12096
12877
  const _lazy_FZJ5Wh = () => import('../routes/api/analysis/session.json.mjs');
12097
12878
  const _lazy_k5j8NN = () => import('../routes/api/analysis/token.json.mjs');
12098
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');
12099
12881
  const _lazy_x_4o7G = () => import('../routes/api/payload.json.mjs');
12100
12882
  const _lazy_slsjW6 = () => import('../routes/api/projects/_project/modules.get.mjs');
12101
12883
  const _lazy_K00scR = () => import('../routes/api/projects/catalog.get.mjs');
@@ -12113,6 +12895,7 @@ const handlers = [
12113
12895
  { route: '/api/analysis/session.json', handler: _lazy_FZJ5Wh, lazy: true, middleware: false, method: undefined },
12114
12896
  { route: '/api/analysis/token.json', handler: _lazy_k5j8NN, lazy: true, middleware: false, method: undefined },
12115
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 },
12116
12899
  { route: '/api/payload.json', handler: _lazy_x_4o7G, lazy: true, middleware: false, method: undefined },
12117
12900
  { route: '/api/projects/:project/modules', handler: _lazy_slsjW6, lazy: true, middleware: false, method: "get" },
12118
12901
  { route: '/api/projects/catalog', handler: _lazy_K00scR, lazy: true, middleware: false, method: "get" },
@@ -12270,4 +13053,4 @@ const websocket = nitroApp.h3App.websocket ;
12270
13053
  const handler = listener;
12271
13054
  trapUnhandledNodeErrors();
12272
13055
 
12273
- 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 };