@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 +1 -1
- package/src/parsers/codex-cache.js +1 -1
- package/src/parsers/codex.js +43 -3
package/package.json
CHANGED
|
@@ -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 =
|
|
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);
|
package/src/parsers/codex.js
CHANGED
|
@@ -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'
|
|
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
|
|
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,
|