@vibe-cafe/vibe-usage 0.10.11 → 0.10.12
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/claude-code.js +22 -8
package/package.json
CHANGED
|
@@ -195,7 +195,7 @@ async function scanProjectCandidate(candidate) {
|
|
|
195
195
|
if (usageScore === 0) return;
|
|
196
196
|
|
|
197
197
|
entries.push({
|
|
198
|
-
|
|
198
|
+
dedupeKey: usageDedupeKey(obj),
|
|
199
199
|
usageScore,
|
|
200
200
|
source: 'claude-code',
|
|
201
201
|
model,
|
|
@@ -239,22 +239,36 @@ async function scanBestCandidate(candidates, scanner, ctx) {
|
|
|
239
239
|
return null;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
// One API call is written as several assistant lines - one per content block -
|
|
243
|
+
// that share `message.id`/`requestId` and repeat the same `usage` object, so a
|
|
244
|
+
// per-line key counts the same call once per block. Streaming also emits an
|
|
245
|
+
// early partial line (lower `output_tokens`) before the final one under that
|
|
246
|
+
// same id. Keying on the call identity collapses both, and the existing
|
|
247
|
+
// highest-usageScore wins rule then keeps the final, complete payload.
|
|
248
|
+
// Records without either id (older logs) fall back to the line uuid.
|
|
249
|
+
function usageDedupeKey(obj) {
|
|
250
|
+
const messageId = typeof obj.message?.id === 'string' ? obj.message.id.trim() : '';
|
|
251
|
+
const requestId = typeof obj.requestId === 'string' ? obj.requestId.trim() : '';
|
|
252
|
+
if (messageId || requestId) return `call:${messageId}\u0000${requestId}`;
|
|
253
|
+
return typeof obj.uuid === 'string' && obj.uuid ? obj.uuid : null;
|
|
254
|
+
}
|
|
255
|
+
|
|
242
256
|
function mergeUsageEntry(ctx, entry) {
|
|
243
|
-
if (!entry.
|
|
257
|
+
if (!entry.dedupeKey) {
|
|
244
258
|
ctx.anonymousEntries.push(entry);
|
|
245
259
|
return;
|
|
246
260
|
}
|
|
247
|
-
const current = ctx.
|
|
248
|
-
// Claude sometimes copies the same
|
|
261
|
+
const current = ctx.entriesByKey.get(entry.dedupeKey);
|
|
262
|
+
// Claude sometimes copies the same record into another session with zeroed
|
|
249
263
|
// usage. Keep the most complete payload, independent of directory order.
|
|
250
264
|
if (!current || entry.usageScore > current.usageScore) {
|
|
251
|
-
ctx.
|
|
265
|
+
ctx.entriesByKey.set(entry.dedupeKey, entry);
|
|
252
266
|
}
|
|
253
267
|
}
|
|
254
268
|
|
|
255
269
|
export async function parse() {
|
|
256
270
|
const ctx = {
|
|
257
|
-
|
|
271
|
+
entriesByKey: new Map(),
|
|
258
272
|
anonymousEntries: [],
|
|
259
273
|
sessionEvents: [],
|
|
260
274
|
warnings: [],
|
|
@@ -283,8 +297,8 @@ export async function parse() {
|
|
|
283
297
|
|
|
284
298
|
const entries = [
|
|
285
299
|
...ctx.anonymousEntries,
|
|
286
|
-
...ctx.
|
|
287
|
-
].map(({
|
|
300
|
+
...ctx.entriesByKey.values(),
|
|
301
|
+
].map(({ dedupeKey: _dedupeKey, usageScore: _usageScore, ...entry }) => entry);
|
|
288
302
|
|
|
289
303
|
return {
|
|
290
304
|
buckets: aggregateToBuckets(entries),
|