codexmeter 1.0.7 → 1.0.9
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/assets/index-DQDmrG5E.js +34 -0
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/aggregator.js +37 -19
- package/server/cost-catalog.js +1 -1
- package/server/ingest.js +210 -27
- package/server/live-state.js +52 -3
- package/server/normalize.js +66 -1
- package/server/pricing-fetch.js +53 -49
- package/server/rollout-reader.js +138 -9
- package/server/sqlite-reader.js +1 -1
- package/dist/assets/index-DxtrMXuP.js +0 -34
package/server/normalize.js
CHANGED
|
@@ -60,9 +60,74 @@ export function isSubagent(agentRole) {
|
|
|
60
60
|
return agentRole != null && agentRole !== 'default';
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export function parseThreadSource(rawSource) {
|
|
64
|
+
if (!rawSource) return null;
|
|
65
|
+
if (typeof rawSource === 'object') return rawSource;
|
|
66
|
+
const text = String(rawSource).trim();
|
|
67
|
+
if (!text) return null;
|
|
68
|
+
if (text.startsWith('{') || text.startsWith('[')) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(text);
|
|
71
|
+
} catch {
|
|
72
|
+
return { kind: text };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { kind: text };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function deriveAgentRole(agentRole, rawSource, title = null) {
|
|
79
|
+
if (agentRole && agentRole !== 'default') return agentRole;
|
|
80
|
+
const source = parseThreadSource(rawSource);
|
|
81
|
+
if (source && typeof source === 'object' && typeof source.subagent === 'string') {
|
|
82
|
+
return source.subagent;
|
|
83
|
+
}
|
|
84
|
+
if (isReviewTaskTitle(title)) return 'review';
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function isReviewTaskTitle(title) {
|
|
89
|
+
if (typeof title !== 'string') return false;
|
|
90
|
+
const value = title.trim().toLowerCase();
|
|
91
|
+
return (
|
|
92
|
+
value.startsWith('review the code changes against the base branch ') ||
|
|
93
|
+
value.startsWith('you are reviewing a manually supplied diff artifact.') ||
|
|
94
|
+
value.startsWith('brief review for commit range ') ||
|
|
95
|
+
value.startsWith('independent brief review ') ||
|
|
96
|
+
value.startsWith('tight-scope integration review ') ||
|
|
97
|
+
value.startsWith('pr-scope review ') ||
|
|
98
|
+
value.startsWith('independent second-round pr review ') ||
|
|
99
|
+
value.startsWith('second independent pr-scope review ') ||
|
|
100
|
+
value.startsWith('implementation-review preflight ') ||
|
|
101
|
+
value.startsWith('implementation-review postflight ')
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isReviewLauncherSession(session) {
|
|
106
|
+
if (session?.rollout_path && !session?.materialized) return false;
|
|
107
|
+
const source = parseThreadSource(session?.source_raw);
|
|
108
|
+
const sourceKind = source?.kind || (typeof source === 'string' ? source : null);
|
|
109
|
+
return isLauncherSourceKind(sourceKind) &&
|
|
110
|
+
isReviewTaskTitle(session?.title) &&
|
|
111
|
+
!session?.model_name &&
|
|
112
|
+
!session?.usage_total &&
|
|
113
|
+
!session?.has_usage_by_day &&
|
|
114
|
+
!session?.tokens_used;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isLauncherSourceKind(sourceKind) {
|
|
118
|
+
const normalized = String(sourceKind || '').trim().toLowerCase();
|
|
119
|
+
return normalized === 'exec' ||
|
|
120
|
+
normalized === 'cli' ||
|
|
121
|
+
normalized.endsWith('-cli') ||
|
|
122
|
+
normalized.endsWith('_cli');
|
|
123
|
+
}
|
|
124
|
+
|
|
63
125
|
const MODEL_ALIASES = {
|
|
64
126
|
'codex-mini-latest': 'o4-mini',
|
|
65
|
-
'gpt-5
|
|
127
|
+
'gpt-5 mini': 'gpt-5-mini',
|
|
128
|
+
'gpt-5.4 mini': 'gpt-5.4-mini',
|
|
129
|
+
'gpt-5 nano': 'gpt-5-nano',
|
|
130
|
+
'gpt-5.4 nano': 'gpt-5.4-nano',
|
|
66
131
|
};
|
|
67
132
|
|
|
68
133
|
export function normalizeModelName(rawModel) {
|
package/server/pricing-fetch.js
CHANGED
|
@@ -1,43 +1,47 @@
|
|
|
1
1
|
// Local pricing catalog for models used in codex-cli/codex app lifetime.
|
|
2
2
|
const FALLBACK = {
|
|
3
|
-
'gpt-5.4': { input: 2.50, output: 15.00, cached_input: 0.25 },
|
|
4
|
-
'gpt-5
|
|
5
|
-
'gpt-5.
|
|
6
|
-
'gpt-5
|
|
7
|
-
'gpt-5.
|
|
8
|
-
'gpt-5.
|
|
9
|
-
'gpt-
|
|
10
|
-
'gpt-
|
|
11
|
-
'gpt-
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'gpt-
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'gpt-
|
|
20
|
-
}
|
|
21
|
-
|
|
3
|
+
'gpt-5.4': { input: 2.50, output: 15.00, cached_input: 0.25 },
|
|
4
|
+
'gpt-5-mini': { input: 0.75, output: 4.50, cached_input: 0.075},
|
|
5
|
+
'gpt-5.4-mini': { input: 0.75, output: 4.50, cached_input: 0.075},
|
|
6
|
+
'gpt-5-nano': { input: 0.15, output: 0.60, cached_input: 0.015},
|
|
7
|
+
'gpt-5.4-nano': { input: 0.15, output: 0.60, cached_input: 0.015},
|
|
8
|
+
'gpt-5.3-codex': { input: 2.00, output: 10.00, cached_input: 0.20 },
|
|
9
|
+
'gpt-5.3-codex-spark': { input: 2.00, output: 10.00, cached_input: 0.20 },
|
|
10
|
+
'gpt-5.2-codex': { input: 2.00, output: 10.00, cached_input: 0.20 },
|
|
11
|
+
'gpt-5.2': { input: 2.00, output: 10.00, cached_input: 0.20 },
|
|
12
|
+
'gpt-5.1-codex-mini': { input: 0.25, output: 2.00, cached_input: 0.025},
|
|
13
|
+
'gpt-4.1': { input: 2.00, output: 8.00, cached_input: 0.50 },
|
|
14
|
+
'gpt-4.1-mini': { input: 0.40, output: 1.60, cached_input: 0.10 },
|
|
15
|
+
'gpt-4.1-nano': { input: 0.10, output: 0.40, cached_input: 0.025 },
|
|
16
|
+
'o3': { input: 2.00, output: 8.00, cached_input: 0.50 },
|
|
17
|
+
'o3-mini': { input: 1.10, output: 4.40, cached_input: 0.275},
|
|
18
|
+
'o4-mini': { input: 1.10, output: 4.40, cached_input: 0.275},
|
|
19
|
+
'gpt-4o': { input: 2.50, output: 10.00, cached_input: 1.25 },
|
|
20
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60, cached_input: 0.075},
|
|
21
|
+
'gpt-4-turbo': { input: 10.00, output: 30.00, cached_input: 1.00 },
|
|
22
|
+
'gpt-4': { input: 30.00, output: 60.00, cached_input: 3.00 },
|
|
23
|
+
'gpt-3.5-turbo': { input: 0.50, output: 1.50, cached_input: 0.05 },
|
|
24
|
+
};
|
|
25
|
+
|
|
22
26
|
const PRICING_URL = process.env.CODEXMETER_PRICING_URL || null;
|
|
23
|
-
|
|
24
|
-
function normalizeEntry(entry) {
|
|
25
|
-
if (!entry || typeof entry !== 'object' || entry.input == null || entry.output == null) return null;
|
|
26
|
-
const cached = entry.cached_input ?? entry.cachedInput ?? entry.input * 0.1;
|
|
27
|
-
return { input: entry.input, output: entry.output, cached_input: cached };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function normalizePricing(raw) {
|
|
31
|
-
const models = raw?.models && typeof raw.models === 'object' ? raw.models : raw;
|
|
32
|
-
if (!models || typeof models !== 'object') return null;
|
|
33
|
-
const out = {};
|
|
34
|
-
for (const [name, entry] of Object.entries(models)) {
|
|
35
|
-
const norm = normalizeEntry(entry);
|
|
36
|
-
if (norm) out[name] = norm;
|
|
37
|
-
}
|
|
38
|
-
return Object.keys(out).length > 0 ? out : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
27
|
+
|
|
28
|
+
function normalizeEntry(entry) {
|
|
29
|
+
if (!entry || typeof entry !== 'object' || entry.input == null || entry.output == null) return null;
|
|
30
|
+
const cached = entry.cached_input ?? entry.cachedInput ?? entry.input * 0.1;
|
|
31
|
+
return { input: entry.input, output: entry.output, cached_input: cached };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizePricing(raw) {
|
|
35
|
+
const models = raw?.models && typeof raw.models === 'object' ? raw.models : raw;
|
|
36
|
+
if (!models || typeof models !== 'object') return null;
|
|
37
|
+
const out = {};
|
|
38
|
+
for (const [name, entry] of Object.entries(models)) {
|
|
39
|
+
const norm = normalizeEntry(entry);
|
|
40
|
+
if (norm) out[name] = norm;
|
|
41
|
+
}
|
|
42
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
export async function fetchPricing() {
|
|
42
46
|
if (!PRICING_URL) return FALLBACK;
|
|
43
47
|
|
|
@@ -45,15 +49,15 @@ export async function fetchPricing() {
|
|
|
45
49
|
const ctrl = new AbortController();
|
|
46
50
|
const t = setTimeout(() => ctrl.abort(), 3000);
|
|
47
51
|
const res = await fetch(PRICING_URL, { signal: ctrl.signal });
|
|
48
|
-
clearTimeout(t);
|
|
49
|
-
if (!res.ok) return FALLBACK;
|
|
50
|
-
const data = await res.json();
|
|
51
|
-
const normalized = normalizePricing(data);
|
|
52
|
-
if (normalized) {
|
|
53
|
-
return { ...FALLBACK, ...normalized };
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
56
|
-
// timeout, network error, invalid JSON - use fallback
|
|
57
|
-
}
|
|
58
|
-
return FALLBACK;
|
|
59
|
-
}
|
|
52
|
+
clearTimeout(t);
|
|
53
|
+
if (!res.ok) return FALLBACK;
|
|
54
|
+
const data = await res.json();
|
|
55
|
+
const normalized = normalizePricing(data);
|
|
56
|
+
if (normalized) {
|
|
57
|
+
return { ...FALLBACK, ...normalized };
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
// timeout, network error, invalid JSON - use fallback
|
|
61
|
+
}
|
|
62
|
+
return FALLBACK;
|
|
63
|
+
}
|
package/server/rollout-reader.js
CHANGED
|
@@ -21,7 +21,9 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
21
21
|
active_by_day: null,
|
|
22
22
|
usage_by_day: null,
|
|
23
23
|
parent_thread_id: null,
|
|
24
|
+
forked_from_id: null,
|
|
24
25
|
usage_total: null,
|
|
26
|
+
usage_reset_detected: false,
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
try {
|
|
@@ -34,6 +36,8 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
34
36
|
let lastInputTokens = 0;
|
|
35
37
|
let lastCachedInputTokens = 0;
|
|
36
38
|
let lastOutputTokens = 0;
|
|
39
|
+
let lastReasoningOutputTokens = 0;
|
|
40
|
+
let lastTotalTokens = 0;
|
|
37
41
|
let hasSeenUsage = false;
|
|
38
42
|
for (const line of content.split(/\r?\n/)) {
|
|
39
43
|
if (!line.trim()) continue;
|
|
@@ -79,30 +83,54 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
79
83
|
total_tokens: totalTokens,
|
|
80
84
|
};
|
|
81
85
|
|
|
86
|
+
const hasReset =
|
|
87
|
+
hasSeenUsage &&
|
|
88
|
+
totalTokens < lastTotalTokens;
|
|
89
|
+
|
|
90
|
+
if (hasReset) {
|
|
91
|
+
usageByDay.clear();
|
|
92
|
+
result.usage_reset_detected = true;
|
|
93
|
+
result.first_usage_timestamp = null;
|
|
94
|
+
hasSeenUsage = false;
|
|
95
|
+
lastInputTokens = 0;
|
|
96
|
+
lastCachedInputTokens = 0;
|
|
97
|
+
lastOutputTokens = 0;
|
|
98
|
+
lastReasoningOutputTokens = 0;
|
|
99
|
+
lastTotalTokens = 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
82
102
|
const usageDelta = hasSeenUsage
|
|
83
103
|
? {
|
|
84
104
|
input_tokens: Math.max(inputTokens - lastInputTokens, 0),
|
|
85
105
|
cached_input_tokens: Math.max(cachedInputTokens - lastCachedInputTokens, 0),
|
|
86
106
|
output_tokens: Math.max(outputTokens - lastOutputTokens, 0),
|
|
107
|
+
reasoning_output_tokens: Math.max(reasoningOutputTokens - lastReasoningOutputTokens, 0),
|
|
108
|
+
total_tokens: Math.max(totalTokens - lastTotalTokens, 0),
|
|
87
109
|
}
|
|
88
110
|
: {
|
|
89
111
|
input_tokens: inputTokens,
|
|
90
112
|
cached_input_tokens: cachedInputTokens,
|
|
91
113
|
output_tokens: outputTokens,
|
|
114
|
+
reasoning_output_tokens: reasoningOutputTokens,
|
|
115
|
+
total_tokens: totalTokens,
|
|
92
116
|
};
|
|
93
117
|
|
|
94
118
|
hasSeenUsage = true;
|
|
95
119
|
lastInputTokens = inputTokens;
|
|
96
120
|
lastCachedInputTokens = cachedInputTokens;
|
|
97
121
|
lastOutputTokens = outputTokens;
|
|
98
|
-
|
|
122
|
+
lastReasoningOutputTokens = reasoningOutputTokens;
|
|
123
|
+
lastTotalTokens = totalTokens;
|
|
124
|
+
if (obj.timestamp && hasUsageBoundarySignal(usageDelta)) {
|
|
99
125
|
const ts = new Date(obj.timestamp).getTime();
|
|
100
126
|
if (!isNaN(ts)) {
|
|
101
127
|
if (!result.first_usage_timestamp || ts < result.first_usage_timestamp) {
|
|
102
128
|
result.first_usage_timestamp = ts;
|
|
103
129
|
}
|
|
104
|
-
|
|
105
|
-
|
|
130
|
+
if (hasUsage(usageDelta)) {
|
|
131
|
+
const dayKey = toDayKey(ts);
|
|
132
|
+
mergeUsageTotals(usageByDay, dayKey, usageDelta);
|
|
133
|
+
}
|
|
106
134
|
}
|
|
107
135
|
}
|
|
108
136
|
}
|
|
@@ -121,6 +149,7 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
121
149
|
|
|
122
150
|
if (obj.type === 'session_meta' && obj.payload) {
|
|
123
151
|
if (obj.payload.model && !result.model_name) result.model_name = obj.payload.model;
|
|
152
|
+
if (!result.forked_from_id) result.forked_from_id = obj.payload?.forked_from_id || null;
|
|
124
153
|
if (!result.parent_thread_id) {
|
|
125
154
|
result.parent_thread_id =
|
|
126
155
|
obj.payload?.source?.subagent?.thread_spawn?.parent_thread_id ||
|
|
@@ -150,6 +179,68 @@ export async function enrichFromRollout(rolloutPath, opts = {}) {
|
|
|
150
179
|
return result;
|
|
151
180
|
}
|
|
152
181
|
|
|
182
|
+
export async function readUsageTimeline(rolloutPath) {
|
|
183
|
+
if (!rolloutPath || !existsSync(rolloutPath)) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const content = await readFile(rolloutPath, 'utf8');
|
|
189
|
+
const timeline = [];
|
|
190
|
+
let segmentId = 0;
|
|
191
|
+
let lastTotalTokens = 0;
|
|
192
|
+
let hasSeenUsage = false;
|
|
193
|
+
for (const line of content.split(/\r?\n/)) {
|
|
194
|
+
if (!line.trim()) continue;
|
|
195
|
+
try {
|
|
196
|
+
const obj = JSON.parse(line);
|
|
197
|
+
if (obj.type !== 'event_msg' || obj.payload?.type !== 'token_count') continue;
|
|
198
|
+
const usage = obj.payload.info?.total_token_usage;
|
|
199
|
+
if (!usage || !obj.timestamp) continue;
|
|
200
|
+
const ts = new Date(obj.timestamp).getTime();
|
|
201
|
+
if (isNaN(ts)) continue;
|
|
202
|
+
const normalizedUsage = normalizeUsageTotals(usage);
|
|
203
|
+
if (hasSeenUsage && normalizedUsage.total_tokens < lastTotalTokens) {
|
|
204
|
+
segmentId += 1;
|
|
205
|
+
}
|
|
206
|
+
timeline.push({
|
|
207
|
+
timestamp: ts,
|
|
208
|
+
segment_id: segmentId,
|
|
209
|
+
usage: normalizedUsage,
|
|
210
|
+
});
|
|
211
|
+
hasSeenUsage = true;
|
|
212
|
+
lastTotalTokens = normalizedUsage.total_tokens;
|
|
213
|
+
} catch {
|
|
214
|
+
// malformed line
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
timeline.sort((left, right) => left.timestamp - right.timestamp);
|
|
218
|
+
return timeline;
|
|
219
|
+
} catch {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function findUsageEntryAtOrBefore(timeline, timestampMs) {
|
|
225
|
+
if (!Array.isArray(timeline) || !timeline.length || !timestampMs) return null;
|
|
226
|
+
const orderedTimeline = timeline.every((entry, index) => index === 0 || timeline[index - 1].timestamp <= entry.timestamp)
|
|
227
|
+
? timeline
|
|
228
|
+
: [...timeline].sort((left, right) => left.timestamp - right.timestamp);
|
|
229
|
+
let best = null;
|
|
230
|
+
for (const entry of orderedTimeline) {
|
|
231
|
+
if ((entry?.timestamp || 0) <= timestampMs) {
|
|
232
|
+
best = entry;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
return best || null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function findUsageAtOrBefore(timeline, timestampMs) {
|
|
241
|
+
return findUsageEntryAtOrBefore(timeline, timestampMs)?.usage || null;
|
|
242
|
+
}
|
|
243
|
+
|
|
153
244
|
function normalizeUsageTotals(usage) {
|
|
154
245
|
return {
|
|
155
246
|
input_tokens: usage.input_tokens || 0,
|
|
@@ -160,16 +251,50 @@ function normalizeUsageTotals(usage) {
|
|
|
160
251
|
};
|
|
161
252
|
}
|
|
162
253
|
|
|
163
|
-
function
|
|
254
|
+
function splitUsageTotals(usage) {
|
|
255
|
+
const normalized = normalizeUsageTotals(usage || {});
|
|
256
|
+
const cachedInputTokens = Math.min(normalized.cached_input_tokens, normalized.input_tokens);
|
|
257
|
+
return {
|
|
258
|
+
uncached_input_tokens: Math.max(normalized.input_tokens - cachedInputTokens, 0),
|
|
259
|
+
cached_input_tokens: cachedInputTokens,
|
|
260
|
+
output_tokens: normalized.output_tokens,
|
|
261
|
+
reasoning_output_tokens: normalized.reasoning_output_tokens,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function combineUsageTotals(parts, currentTotalTokens = 0, previousTotalTokens = 0) {
|
|
266
|
+
const inputTokens = (parts.uncached_input_tokens || 0) + (parts.cached_input_tokens || 0);
|
|
267
|
+
const outputTokens = parts.output_tokens || 0;
|
|
164
268
|
return {
|
|
165
|
-
input_tokens:
|
|
166
|
-
cached_input_tokens:
|
|
167
|
-
output_tokens:
|
|
168
|
-
reasoning_output_tokens:
|
|
169
|
-
total_tokens: Math.max(
|
|
269
|
+
input_tokens: inputTokens,
|
|
270
|
+
cached_input_tokens: parts.cached_input_tokens || 0,
|
|
271
|
+
output_tokens: outputTokens,
|
|
272
|
+
reasoning_output_tokens: parts.reasoning_output_tokens || 0,
|
|
273
|
+
total_tokens: Math.max(inputTokens + outputTokens, Math.max(currentTotalTokens - previousTotalTokens, 0)),
|
|
170
274
|
};
|
|
171
275
|
}
|
|
172
276
|
|
|
277
|
+
export function subtractUsageTotals(current, previous) {
|
|
278
|
+
const currentParts = splitUsageTotals(current);
|
|
279
|
+
const previousParts = splitUsageTotals(previous);
|
|
280
|
+
return combineUsageTotals({
|
|
281
|
+
uncached_input_tokens: Math.max(currentParts.uncached_input_tokens - previousParts.uncached_input_tokens, 0),
|
|
282
|
+
cached_input_tokens: Math.max(currentParts.cached_input_tokens - previousParts.cached_input_tokens, 0),
|
|
283
|
+
output_tokens: Math.max(currentParts.output_tokens - previousParts.output_tokens, 0),
|
|
284
|
+
reasoning_output_tokens: Math.max(currentParts.reasoning_output_tokens - previousParts.reasoning_output_tokens, 0),
|
|
285
|
+
}, current?.total_tokens || 0, previous?.total_tokens || 0);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function hasUsageTotals(usage) {
|
|
289
|
+
return !!usage && (
|
|
290
|
+
(usage.input_tokens || 0) > 0 ||
|
|
291
|
+
(usage.cached_input_tokens || 0) > 0 ||
|
|
292
|
+
(usage.output_tokens || 0) > 0 ||
|
|
293
|
+
(usage.reasoning_output_tokens || 0) > 0 ||
|
|
294
|
+
(usage.total_tokens || 0) > 0
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
173
298
|
function hasUsage(usage) {
|
|
174
299
|
return !!usage && (
|
|
175
300
|
usage.input_tokens > 0 ||
|
|
@@ -178,6 +303,10 @@ function hasUsage(usage) {
|
|
|
178
303
|
);
|
|
179
304
|
}
|
|
180
305
|
|
|
306
|
+
function hasUsageBoundarySignal(usage) {
|
|
307
|
+
return hasUsage(usage) || (usage?.reasoning_output_tokens || 0) > 0 || (usage?.total_tokens || 0) > 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
181
310
|
function mergeUsageTotals(target, dayKey, usage) {
|
|
182
311
|
let previous = target.get(dayKey);
|
|
183
312
|
if (!previous) {
|
package/server/sqlite-reader.js
CHANGED
|
@@ -35,7 +35,7 @@ export function readThreads(codexHome, onProgress) {
|
|
|
35
35
|
rollout_path: row.rollout_path,
|
|
36
36
|
created_at: row.created_at,
|
|
37
37
|
updated_at: row.updated_at,
|
|
38
|
-
|
|
38
|
+
source_raw: row.source,
|
|
39
39
|
model_provider: row.model_provider,
|
|
40
40
|
cwd_raw: row.cwd,
|
|
41
41
|
title: row.title ? row.title.slice(0, 200) : '',
|