ccus-cli 0.2.3 → 0.2.4
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/lib/codex-sessions.js +83 -49
- package/package.json +1 -1
|
@@ -55,16 +55,18 @@ function extractLastTokenUsage(payload) {
|
|
|
55
55
|
return isRecord(payload.info.last_token_usage) ? payload.info.last_token_usage : null;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* 解析单个 Codex rollout 文件,收集范围内的 task_started turn 节点与 token_count 用量。
|
|
59
59
|
*
|
|
60
60
|
* Codex rollout 每行一个事件,timestamp 在 top-level:
|
|
61
|
-
* - `event_msg` + `payload.type=="
|
|
61
|
+
* - `event_msg` + `payload.type=="task_started"`:一个用户 turn,带全局唯一 `turn_id`。
|
|
62
|
+
* fork / spawn sub-agent / resume 会把历史 task_started 跨文件重放,故 turn 节点交上层按
|
|
63
|
+
* turn_id 全局去重(取最早 timestamp),不能在单文件层计数。
|
|
62
64
|
* - `event_msg` + `payload.type=="token_count"`:一次模型请求,token 取 `info.last_token_usage` 增量。
|
|
63
65
|
*/
|
|
64
66
|
function summarizeRollout(content, start, end) {
|
|
65
67
|
const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
+
const result = {
|
|
69
|
+
turns: [],
|
|
68
70
|
apiRequestCount: 0,
|
|
69
71
|
inputTokens: 0,
|
|
70
72
|
outputTokens: 0,
|
|
@@ -80,16 +82,19 @@ function summarizeRollout(content, start, end) {
|
|
|
80
82
|
continue;
|
|
81
83
|
}
|
|
82
84
|
const payload = record.payload;
|
|
83
|
-
if (payload.type === "
|
|
84
|
-
|
|
85
|
+
if (payload.type === "task_started" && typeof payload.turn_id === "string") {
|
|
86
|
+
const ms = new Date(getString(record.timestamp) ?? "").getTime();
|
|
87
|
+
if (Number.isFinite(ms)) {
|
|
88
|
+
result.turns.push({ turnId: payload.turn_id, ms });
|
|
89
|
+
}
|
|
85
90
|
}
|
|
86
91
|
if (payload.type === "token_count") {
|
|
87
92
|
const usage = extractLastTokenUsage(payload);
|
|
88
93
|
if (usage) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
result.apiRequestCount += 1;
|
|
95
|
+
result.inputTokens += getNumber(usage.input_tokens);
|
|
96
|
+
result.outputTokens += getNumber(usage.output_tokens);
|
|
97
|
+
result.cacheReadInputTokens += getNumber(usage.cached_input_tokens);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -97,48 +102,79 @@ function summarizeRollout(content, start, end) {
|
|
|
97
102
|
continue;
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
|
-
return
|
|
105
|
+
return result;
|
|
101
106
|
}
|
|
102
107
|
/**
|
|
103
108
|
* 从 Codex 本地 session rollout(<CODEX_HOME>/sessions 下递归的 .jsonl)统计消息数、请求数和 token 用量。
|
|
109
|
+
*
|
|
110
|
+
* 消息数 = task_started 的 distinct turn_id(跨文件去重)。重放副本会让同一 turn_id 出现在多个文件,
|
|
111
|
+
* 故用全局 Map<turn_id, minMs> 收集(取最早 timestamp = 真实发生时刻,早于任何重放副本),最后取 size。
|
|
104
112
|
*/
|
|
105
113
|
async function summarizeCodexSessionUsage(start, end) {
|
|
106
114
|
const codexDataDir = (0, paths_1.getCodexHome)();
|
|
107
115
|
const sessionsDir = node_path_1.default.join(codexDataDir, "sessions");
|
|
108
116
|
const files = await collectRolloutFiles(sessionsDir);
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
cacheReadInputTokens: 0,
|
|
115
|
-
matchedFileCount: files.length,
|
|
116
|
-
codexDataDir,
|
|
117
|
-
};
|
|
117
|
+
const turnMinMs = new Map();
|
|
118
|
+
let apiRequestCount = 0;
|
|
119
|
+
let inputTokens = 0;
|
|
120
|
+
let outputTokens = 0;
|
|
121
|
+
let cacheReadInputTokens = 0;
|
|
118
122
|
for (const filePath of files) {
|
|
119
123
|
try {
|
|
120
124
|
const content = await promises_1.default.readFile(filePath, "utf8");
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
const parsed = summarizeRollout(content, start, end);
|
|
126
|
+
for (const turn of parsed.turns) {
|
|
127
|
+
const prev = turnMinMs.get(turn.turnId);
|
|
128
|
+
if (prev === undefined || turn.ms < prev) {
|
|
129
|
+
turnMinMs.set(turn.turnId, turn.ms);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
apiRequestCount += parsed.apiRequestCount;
|
|
133
|
+
inputTokens += parsed.inputTokens;
|
|
134
|
+
outputTokens += parsed.outputTokens;
|
|
135
|
+
cacheReadInputTokens += parsed.cacheReadInputTokens;
|
|
127
136
|
}
|
|
128
137
|
catch {
|
|
129
138
|
continue;
|
|
130
139
|
}
|
|
131
140
|
}
|
|
132
|
-
return
|
|
141
|
+
return {
|
|
142
|
+
userMessageCount: turnMinMs.size,
|
|
143
|
+
apiRequestCount,
|
|
144
|
+
inputTokens,
|
|
145
|
+
outputTokens,
|
|
146
|
+
cacheReadInputTokens,
|
|
147
|
+
matchedFileCount: files.length,
|
|
148
|
+
codexDataDir,
|
|
149
|
+
};
|
|
133
150
|
}
|
|
134
151
|
/**
|
|
135
152
|
* 按天汇总 Codex session rollout 中的消息数、请求数和 token 用量。
|
|
153
|
+
*
|
|
154
|
+
* 消息数同 weekly:先全局 Map<turn_id, minMs> 去重,再按 minMs 的本地日归桶(保证 weekly = Σ daily、
|
|
155
|
+
* 且重放副本跨天不重复)。token 维度按 token_count 事件 timestamp 的本地日累加。
|
|
136
156
|
*/
|
|
137
157
|
async function summarizeCodexSessionUsageByDay(start, end) {
|
|
138
158
|
const codexDataDir = (0, paths_1.getCodexHome)();
|
|
139
159
|
const sessionsDir = node_path_1.default.join(codexDataDir, "sessions");
|
|
140
160
|
const files = await collectRolloutFiles(sessionsDir);
|
|
161
|
+
const turnMinMs = new Map();
|
|
141
162
|
const daily = new Map();
|
|
163
|
+
const ensureDay = (date) => {
|
|
164
|
+
let current = daily.get(date);
|
|
165
|
+
if (!current) {
|
|
166
|
+
current = {
|
|
167
|
+
date,
|
|
168
|
+
userMessageCount: 0,
|
|
169
|
+
apiRequestCount: 0,
|
|
170
|
+
inputTokens: 0,
|
|
171
|
+
outputTokens: 0,
|
|
172
|
+
cacheReadInputTokens: 0,
|
|
173
|
+
};
|
|
174
|
+
daily.set(date, current);
|
|
175
|
+
}
|
|
176
|
+
return current;
|
|
177
|
+
};
|
|
142
178
|
for (const filePath of files) {
|
|
143
179
|
let content = "";
|
|
144
180
|
try {
|
|
@@ -162,36 +198,34 @@ async function summarizeCodexSessionUsageByDay(start, end) {
|
|
|
162
198
|
continue;
|
|
163
199
|
}
|
|
164
200
|
const payload = record.payload;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
201
|
+
const ms = new Date(timestamp).getTime();
|
|
202
|
+
if (payload.type === "task_started" && typeof payload.turn_id === "string") {
|
|
203
|
+
if (Number.isFinite(ms)) {
|
|
204
|
+
const prev = turnMinMs.get(payload.turn_id);
|
|
205
|
+
if (prev === undefined || ms < prev) {
|
|
206
|
+
turnMinMs.set(payload.turn_id, ms);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
169
209
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (isUserMessage) {
|
|
180
|
-
current.userMessageCount += 1;
|
|
210
|
+
if (payload.type === "token_count") {
|
|
211
|
+
const usage = extractLastTokenUsage(payload);
|
|
212
|
+
if (usage) {
|
|
213
|
+
const current = ensureDay((0, time_1.localDateKey)(new Date(ms)));
|
|
214
|
+
current.apiRequestCount += 1;
|
|
215
|
+
current.inputTokens += getNumber(usage.input_tokens);
|
|
216
|
+
current.outputTokens += getNumber(usage.output_tokens);
|
|
217
|
+
current.cacheReadInputTokens += getNumber(usage.cached_input_tokens);
|
|
218
|
+
}
|
|
181
219
|
}
|
|
182
|
-
if (usage) {
|
|
183
|
-
current.apiRequestCount += 1;
|
|
184
|
-
current.inputTokens += getNumber(usage.input_tokens);
|
|
185
|
-
current.outputTokens += getNumber(usage.output_tokens);
|
|
186
|
-
current.cacheReadInputTokens += getNumber(usage.cached_input_tokens);
|
|
187
|
-
}
|
|
188
|
-
daily.set(date, current);
|
|
189
220
|
}
|
|
190
221
|
catch {
|
|
191
222
|
continue;
|
|
192
223
|
}
|
|
193
224
|
}
|
|
194
225
|
}
|
|
226
|
+
for (const ms of turnMinMs.values()) {
|
|
227
|
+
ensureDay((0, time_1.localDateKey)(new Date(ms))).userMessageCount += 1;
|
|
228
|
+
}
|
|
195
229
|
return daily;
|
|
196
230
|
}
|
|
197
231
|
//# sourceMappingURL=codex-sessions.js.map
|