@vibe-cafe/vibe-usage 0.10.17 → 0.10.18
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 +62 -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 = 2;
|
|
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,39 @@ import {
|
|
|
26
26
|
saveCodexFileTail,
|
|
27
27
|
} from './codex-cache.js';
|
|
28
28
|
|
|
29
|
+
const CODEX_API_BILLING_MARKER = '#billing=api';
|
|
30
|
+
const CODEX_SUBSCRIPTION_BILLING_MARKER = '#billing=subscription';
|
|
31
|
+
// Changing a model id changes its server-side bucket key. Keep pre-release
|
|
32
|
+
// history byte-for-byte stable so upgrading cannot re-upload the same tokens
|
|
33
|
+
// under decorated keys and double-count them.
|
|
34
|
+
const CODEX_BILLING_ATTRIBUTION_START_MS = Date.parse('2026-08-31T00:00:00.000Z');
|
|
35
|
+
|
|
36
|
+
function normalizeCodexServiceTier(value) {
|
|
37
|
+
if (typeof value !== 'string') return null;
|
|
38
|
+
const tier = value.trim().toLowerCase();
|
|
39
|
+
if (tier === 'fast' || tier === 'priority') return tier;
|
|
40
|
+
if (tier === 'flex' || tier === 'batch') return tier;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasSubscriptionPlan(planType) {
|
|
45
|
+
return typeof planType === 'string'
|
|
46
|
+
&& planType.trim() !== ''
|
|
47
|
+
&& planType.toLowerCase() !== 'unknown';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function decorateCodexModel(model, serviceTier, subscriptionBilling, timestampMs) {
|
|
51
|
+
let decorated = model || 'unknown';
|
|
52
|
+
if (decorated === 'unknown' || timestampMs < CODEX_BILLING_ATTRIBUTION_START_MS) {
|
|
53
|
+
return decorated;
|
|
54
|
+
}
|
|
55
|
+
if (serviceTier) decorated += `-${serviceTier}`;
|
|
56
|
+
decorated += subscriptionBilling
|
|
57
|
+
? CODEX_SUBSCRIPTION_BILLING_MARKER
|
|
58
|
+
: CODEX_API_BILLING_MARKER;
|
|
59
|
+
return decorated;
|
|
60
|
+
}
|
|
61
|
+
|
|
29
62
|
// Codex stores live sessions in $CODEX_HOME/sessions (default ~/.codex) and,
|
|
30
63
|
// once a session is "completed", moves its rollout file verbatim into
|
|
31
64
|
// $CODEX_HOME/archived_sessions. A session can be archived between two syncs,
|
|
@@ -600,6 +633,8 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
|
|
|
600
633
|
const sessionKey = fm.sessionId || filePath;
|
|
601
634
|
|
|
602
635
|
let turnContextModel = previousTail?.turnContextModel || 'unknown';
|
|
636
|
+
let serviceTier = previousTail?.serviceTier || null;
|
|
637
|
+
let subscriptionBilling = previousTail?.subscriptionBilling || false;
|
|
603
638
|
let prevTotal = previousTail?.prevTotal || null;
|
|
604
639
|
let prevCumulativeTotal = previousTail?.prevCumulativeTotal ?? null;
|
|
605
640
|
const start = previousTail?.parsedBytes || 0;
|
|
@@ -644,8 +679,11 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
|
|
|
644
679
|
}
|
|
645
680
|
}
|
|
646
681
|
|
|
647
|
-
if (obj.type === 'turn_context'
|
|
648
|
-
turnContextModel = obj.payload.model;
|
|
682
|
+
if (obj.type === 'turn_context') {
|
|
683
|
+
if (obj.payload?.model) turnContextModel = obj.payload.model;
|
|
684
|
+
if (Object.hasOwn(obj.payload || {}, 'service_tier')) {
|
|
685
|
+
serviceTier = normalizeCodexServiceTier(obj.payload.service_tier);
|
|
686
|
+
}
|
|
649
687
|
continue;
|
|
650
688
|
}
|
|
651
689
|
|
|
@@ -654,6 +692,15 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
|
|
|
654
692
|
const payload = obj.payload;
|
|
655
693
|
if (!payload) continue;
|
|
656
694
|
|
|
695
|
+
if (payload.type === 'thread_settings_applied') {
|
|
696
|
+
const settings = payload.thread_settings;
|
|
697
|
+
if (settings?.model) turnContextModel = settings.model;
|
|
698
|
+
if (Object.hasOwn(settings || {}, 'service_tier')) {
|
|
699
|
+
serviceTier = normalizeCodexServiceTier(settings.service_tier);
|
|
700
|
+
}
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
|
|
657
704
|
if (payload.type !== 'token_count') continue;
|
|
658
705
|
|
|
659
706
|
// Raw ordinals advance before validating usage/timestamp so pass 1 and
|
|
@@ -661,6 +708,10 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
|
|
|
661
708
|
const isReplayedHistory = inReplayBlock;
|
|
662
709
|
rawTokenSeen++;
|
|
663
710
|
|
|
711
|
+
if (hasSubscriptionPlan(payload.rate_limits?.plan_type)) {
|
|
712
|
+
subscriptionBilling = true;
|
|
713
|
+
}
|
|
714
|
+
|
|
664
715
|
const info = payload.info;
|
|
665
716
|
if (!info) continue;
|
|
666
717
|
|
|
@@ -709,7 +760,13 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
|
|
|
709
760
|
const timestamp = obj.timestamp ? new Date(obj.timestamp) : null;
|
|
710
761
|
if (!timestamp || isNaN(timestamp.getTime())) continue;
|
|
711
762
|
|
|
712
|
-
const
|
|
763
|
+
const rawModel = info.model || payload.model || turnContextModel || 'unknown';
|
|
764
|
+
const model = decorateCodexModel(
|
|
765
|
+
rawModel,
|
|
766
|
+
serviceTier,
|
|
767
|
+
subscriptionBilling,
|
|
768
|
+
timestamp.getTime()
|
|
769
|
+
);
|
|
713
770
|
|
|
714
771
|
// OpenAI API: input_tokens INCLUDES cached, output_tokens INCLUDES reasoning.
|
|
715
772
|
// Normalize to Anthropic-style semantics where each field is non-overlapping.
|
|
@@ -764,6 +821,8 @@ async function parseSessionFile(filePath, snapshotSize, fm, boundary, {
|
|
|
764
821
|
rawTokenSeen,
|
|
765
822
|
firstSessionMetaSeen,
|
|
766
823
|
turnContextModel,
|
|
824
|
+
serviceTier,
|
|
825
|
+
subscriptionBilling,
|
|
767
826
|
prevTotal,
|
|
768
827
|
prevCumulativeTotal,
|
|
769
828
|
buckets,
|