codexmeter 1.0.24 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-8ykzgYb0.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-B2TwQuNm.js"></script>
11
11
  <link rel="stylesheet" crossorigin href="/assets/index-DdbEQTfc.css">
12
12
  </head>
13
13
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmeter",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "Local telemetry dashboard for Codex CLI usage",
5
5
  "type": "module",
6
6
  "bin": {
package/server/ingest.js CHANGED
@@ -410,7 +410,19 @@ export function createPartialAggregateSessions(sessions, toDayKey) {
410
410
  .filter(isSafeForPartialAggregation)
411
411
  .map((session) => {
412
412
  const clone = { ...session };
413
+ const isUnparsedRollout = clone.rollout_path && !clone.materialized;
414
+ if (isUnparsedRollout) {
415
+ clone.model_name = null;
416
+ clone.cost = null;
417
+ clone.cost_source = 'unavailable';
418
+ clone.has_usage_by_day = true;
419
+ clone.usage_by_day = [];
420
+ }
413
421
  finalizeSessionMetrics(clone, toDayKey);
422
+ if (isUnparsedRollout) {
423
+ clone.cost = null;
424
+ clone.cost_source = 'unavailable';
425
+ }
414
426
  return clone;
415
427
  });
416
428
  }
@@ -1,6 +1,16 @@
1
1
  import { CACHE_ASSUMPTIONS } from './cost-catalog.js';
2
2
  import { createDayKeyFormatter, splitIntervalByDay } from './day-key.js';
3
3
 
4
+ function normalizeEffortKey(effort) {
5
+ if (!effort) return 'unknown';
6
+ const k = String(effort).toLowerCase().trim().replace(/-/g, '');
7
+ if (k === 'low') return 'low';
8
+ if (k === 'medium') return 'medium';
9
+ if (k === 'high') return 'high';
10
+ if (k === 'xhigh') return 'xhigh';
11
+ return k || 'unknown';
12
+ }
13
+
4
14
  export function createLiveAggregateState(tz) {
5
15
  const now = Date.now() / 1000;
6
16
  return {
@@ -166,6 +176,8 @@ function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
166
176
  exact_priced: 0,
167
177
  heuristic_priced: 0,
168
178
  sessions: 0,
179
+ by_model: {},
180
+ by_family: {},
169
181
  });
170
182
  }
171
183
  const repo = repoMap.get(key);
@@ -177,6 +189,8 @@ function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
177
189
  if (session.cost_source === 'exact') repo.exact_priced += 1;
178
190
  if (session.cost_source === 'heuristic') repo.heuristic_priced += 1;
179
191
  }
192
+ addBreakdown(repo.by_model, session.model_name || 'unknown', session);
193
+ addBreakdown(repo.by_family, session.agent_family || 'generic', session);
180
194
 
181
195
  updateTopKeys(repoMap, topKeys, key);
182
196
  dirtySet.add(key);
@@ -185,7 +199,7 @@ function applyRepoBucket(repoMap, topKeys, session, dirtySet) {
185
199
  function applyModelBucket(modelMap, topKeys, session, dirtySet) {
186
200
  const key = session.model_name || 'unknown';
187
201
  if (!modelMap.has(key)) {
188
- modelMap.set(key, { model_name: key, tokens: 0, cost: 0, cost_known: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0 });
202
+ modelMap.set(key, { model_name: key, tokens: 0, cost: 0, cost_known: 0, exact_priced: 0, heuristic_priced: 0, sessions: 0, by_effort: {} });
189
203
  }
190
204
  const model = modelMap.get(key);
191
205
  model.tokens += session.tokens_used || 0;
@@ -196,11 +210,24 @@ function applyModelBucket(modelMap, topKeys, session, dirtySet) {
196
210
  if (session.cost_source === 'exact') model.exact_priced += 1;
197
211
  if (session.cost_source === 'heuristic') model.heuristic_priced += 1;
198
212
  }
213
+ addBreakdown(model.by_effort, normalizeEffortKey(session.reasoning_effort), session);
199
214
 
200
215
  updateTopKeys(modelMap, topKeys, key);
201
216
  dirtySet.add(key);
202
217
  }
203
218
 
219
+ function addBreakdown(target, key, session) {
220
+ if (!target[key]) target[key] = { tokens: 0, cost: 0, sessions: 0, exact_priced: 0, heuristic_priced: 0 };
221
+ const bucket = target[key];
222
+ bucket.tokens += session.tokens_used || 0;
223
+ bucket.sessions += 1;
224
+ if (session.cost !== null) {
225
+ bucket.cost += session.cost;
226
+ if (session.cost_source === 'exact') bucket.exact_priced += 1;
227
+ if (session.cost_source === 'heuristic') bucket.heuristic_priced += 1;
228
+ }
229
+ }
230
+
204
231
  function applyFamilyBucket(familyMap, topKeys, session, dirtySet) {
205
232
  const key = session.agent_family || 'generic';
206
233
  if (!familyMap.has(key)) {
@@ -549,6 +576,8 @@ function serializeRepoSummary(value) {
549
576
  exact_priced: value.exact_priced,
550
577
  heuristic_priced: value.heuristic_priced,
551
578
  sessions: value.sessions,
579
+ by_model: deepRoundClone(value.by_model || {}, ['tokens', 'cost', 'sessions', 'exact_priced', 'heuristic_priced']),
580
+ by_family: deepRoundClone(value.by_family || {}, ['tokens', 'cost', 'sessions', 'exact_priced', 'heuristic_priced']),
552
581
  };
553
582
  }
554
583
 
@@ -561,6 +590,7 @@ function serializeModelSummary(value) {
561
590
  exact_priced: value.exact_priced,
562
591
  heuristic_priced: value.heuristic_priced,
563
592
  sessions: value.sessions,
593
+ by_effort: deepRoundClone(value.by_effort || {}, ['tokens', 'cost', 'sessions', 'exact_priced', 'heuristic_priced']),
564
594
  };
565
595
  }
566
596