@vibe-cafe/vibe-usage 0.10.17 → 0.10.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-cafe/vibe-usage",
3
- "version": "0.10.17",
3
+ "version": "0.10.19",
4
4
  "description": "Track your AI coding tool token usage and sync to vibecafe.ai",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -14,7 +14,7 @@ import { join } from 'node:path';
14
14
  // separate from ~/.vibe-usage/state.json, whose hashes are the authoritative
15
15
  // record of successful uploads and must remain backward-compatible.
16
16
  export const CODEX_CACHE_SCHEMA_VERSION = 1;
17
- export const CODEX_PARSER_ALGORITHM_VERSION = 1;
17
+ export const CODEX_PARSER_ALGORITHM_VERSION = 3;
18
18
 
19
19
  function hash(value, length = 24) {
20
20
  return createHash('sha256').update(value).digest('hex').slice(0, length);
@@ -26,6 +26,31 @@ import {
26
26
  saveCodexFileTail,
27
27
  } from './codex-cache.js';
28
28
 
29
+ // Changing a model id changes its server-side bucket key. Keep pre-release
30
+ // history byte-for-byte stable so upgrading cannot re-upload the same tokens
31
+ // under tier-decorated keys and double-count them.
32
+ const CODEX_SERVICE_TIER_ATTRIBUTION_START_MS = Date.parse('2026-08-31T00:00:00.000Z');
33
+
34
+ function normalizeCodexServiceTier(value) {
35
+ if (typeof value !== 'string') return null;
36
+ const tier = value.trim().toLowerCase();
37
+ if (tier === 'fast' || tier === 'priority') return tier;
38
+ if (tier === 'flex' || tier === 'batch') return tier;
39
+ return null;
40
+ }
41
+
42
+ function decorateCodexModel(model, serviceTier, timestampMs) {
43
+ const rawModel = model || 'unknown';
44
+ if (
45
+ rawModel === 'unknown'
46
+ || !serviceTier
47
+ || timestampMs < CODEX_SERVICE_TIER_ATTRIBUTION_START_MS
48
+ ) {
49
+ return rawModel;
50
+ }
51
+ return `${rawModel}-${serviceTier}`;
52
+ }
53
+
29
54
  // Codex stores live sessions in $CODEX_HOME/sessions (default ~/.codex) and,
30
55
  // once a session is "completed", moves its rollout file verbatim into
31
56
  // $CODEX_HOME/archived_sessions. A session can be archived between two syncs,
@@ -600,6 +625,7 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
600
625
  const sessionKey = fm.sessionId || filePath;
601
626
 
602
627
  let turnContextModel = previousTail?.turnContextModel || 'unknown';
628
+ let serviceTier = previousTail?.serviceTier || null;
603
629
  let prevTotal = previousTail?.prevTotal || null;
604
630
  let prevCumulativeTotal = previousTail?.prevCumulativeTotal ?? null;
605
631
  const start = previousTail?.parsedBytes || 0;
@@ -644,8 +670,11 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
644
670
  }
645
671
  }
646
672
 
647
- if (obj.type === 'turn_context' && obj.payload?.model) {
648
- turnContextModel = obj.payload.model;
673
+ if (obj.type === 'turn_context') {
674
+ if (obj.payload?.model) turnContextModel = obj.payload.model;
675
+ if (Object.hasOwn(obj.payload || {}, 'service_tier')) {
676
+ serviceTier = normalizeCodexServiceTier(obj.payload.service_tier);
677
+ }
649
678
  continue;
650
679
  }
651
680
 
@@ -654,6 +683,15 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
654
683
  const payload = obj.payload;
655
684
  if (!payload) continue;
656
685
 
686
+ if (payload.type === 'thread_settings_applied') {
687
+ const settings = payload.thread_settings;
688
+ if (settings?.model) turnContextModel = settings.model;
689
+ if (Object.hasOwn(settings || {}, 'service_tier')) {
690
+ serviceTier = normalizeCodexServiceTier(settings.service_tier);
691
+ }
692
+ continue;
693
+ }
694
+
657
695
  if (payload.type !== 'token_count') continue;
658
696
 
659
697
  // Raw ordinals advance before validating usage/timestamp so pass 1 and
@@ -709,7 +747,8 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
709
747
  const timestamp = obj.timestamp ? new Date(obj.timestamp) : null;
710
748
  if (!timestamp || isNaN(timestamp.getTime())) continue;
711
749
 
712
- const model = info.model || payload.model || turnContextModel || 'unknown';
750
+ const rawModel = info.model || payload.model || turnContextModel || 'unknown';
751
+ const model = decorateCodexModel(rawModel, serviceTier, timestamp.getTime());
713
752
 
714
753
  // OpenAI API: input_tokens INCLUDES cached, output_tokens INCLUDES reasoning.
715
754
  // Normalize to Anthropic-style semantics where each field is non-overlapping.
@@ -764,6 +803,7 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
764
803
  rawTokenSeen,
765
804
  firstSessionMetaSeen,
766
805
  turnContextModel,
806
+ serviceTier,
767
807
  prevTotal,
768
808
  prevCumulativeTotal,
769
809
  buckets,