agentwaste-core 0.1.0 → 0.1.1

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/src/scanner.js CHANGED
@@ -1,14 +1,25 @@
1
1
  import { createReadStream, existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import { homedir } from "node:os";
2
+ import { readFile } from "node:fs/promises";
3
+ import { cpus, homedir } from "node:os";
3
4
  import { basename, join, relative } from "node:path";
4
5
  import { createInterface } from "node:readline";
5
6
 
6
7
  const DEFAULT_SCAN_DAYS = 365;
7
- const DEFAULT_MAX_FILES = Number.MAX_SAFE_INTEGER;
8
+ const DEFAULT_MAX_FILES = 5000;
9
+ const EXHAUSTIVE_MAX_FILES = Number.MAX_SAFE_INTEGER;
10
+ const DEFAULT_SCAN_CONCURRENCY = Math.min(32, Math.max(4, cpus().length * 2 || 4));
11
+ const MAX_SCAN_CONCURRENCY = 64;
8
12
  const MAX_FILE_BYTES = 64 * 1024 * 1024;
9
13
  const CONTEXT_REVIEW_FRESH_INPUT_PER_TOOL_CALL = 4000;
10
14
  const DEFAULT_COST_PER_MILLION_TOKENS = 3.0;
11
15
 
16
+ export const SCAN_DEFAULTS = Object.freeze({
17
+ days: DEFAULT_SCAN_DAYS,
18
+ maxFiles: DEFAULT_MAX_FILES,
19
+ exhaustiveMaxFiles: EXHAUSTIVE_MAX_FILES,
20
+ concurrency: DEFAULT_SCAN_CONCURRENCY,
21
+ });
22
+
12
23
  const IGNORED_DIRS = new Set([
13
24
  ".git",
14
25
  ".hg",
@@ -66,20 +77,21 @@ export async function scanAgentWaste(input = {}) {
66
77
  const homeDir = input.homeDir ?? homedir();
67
78
  const projectDir = input.projectDir ?? process.cwd();
68
79
  const days = normalizeScanDays(input.days ?? DEFAULT_SCAN_DAYS);
69
- const maxFiles = input.maxFiles ?? DEFAULT_MAX_FILES;
80
+ const maxFiles = normalizeMaxFiles(input.maxFiles ?? DEFAULT_MAX_FILES);
81
+ const concurrency = normalizeConcurrency(input.concurrency ?? DEFAULT_SCAN_CONCURRENCY);
70
82
  const now = input.now ?? Date.now();
71
83
  const sinceMs = days === "all" ? 0 : now - days * 24 * 60 * 60 * 1000;
72
84
  const totals = createTotals({ homeDir, projectDir, days, sinceMs, now });
73
85
 
74
- await scanCodex(homeDir, sinceMs, maxFiles, totals);
75
- await scanClaude(homeDir, sinceMs, maxFiles, totals);
76
- await scanPi(homeDir, sinceMs, maxFiles, totals);
77
- await scanGemini(homeDir, sinceMs, maxFiles, totals);
78
- await scanOpenCode(homeDir, sinceMs, maxFiles, totals);
79
- await scanCursor(homeDir, sinceMs, maxFiles, totals);
80
- await scanWindsurf(homeDir, sinceMs, maxFiles, totals);
81
- await scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals);
82
- await scanCopilot(homeDir, sinceMs, maxFiles, totals);
86
+ await scanCodex(homeDir, sinceMs, maxFiles, totals, concurrency);
87
+ await scanClaude(homeDir, sinceMs, maxFiles, totals, concurrency);
88
+ await scanPi(homeDir, sinceMs, maxFiles, totals, concurrency);
89
+ await scanGemini(homeDir, sinceMs, maxFiles, totals, concurrency);
90
+ await scanOpenCode(homeDir, sinceMs, maxFiles, totals, concurrency);
91
+ await scanCursor(homeDir, sinceMs, maxFiles, totals, concurrency);
92
+ await scanWindsurf(homeDir, sinceMs, maxFiles, totals, concurrency);
93
+ await scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals, concurrency);
94
+ await scanCopilot(homeDir, sinceMs, maxFiles, totals, concurrency);
83
95
  scanAider(projectDir, sinceMs, totals);
84
96
  scanConfigFiles(homeDir, sinceMs, totals);
85
97
  scanGlueCode(projectDir, totals);
@@ -93,6 +105,17 @@ function normalizeScanDays(value) {
93
105
  return Number.isFinite(days) && days > 0 ? days : DEFAULT_SCAN_DAYS;
94
106
  }
95
107
 
108
+ function normalizeMaxFiles(value) {
109
+ const maxFiles = Number.parseInt(value, 10);
110
+ return Number.isFinite(maxFiles) && maxFiles > 0 ? maxFiles : DEFAULT_MAX_FILES;
111
+ }
112
+
113
+ function normalizeConcurrency(value) {
114
+ const concurrency = Number.parseInt(value, 10);
115
+ if (!Number.isFinite(concurrency) || concurrency <= 0) return DEFAULT_SCAN_CONCURRENCY;
116
+ return Math.min(MAX_SCAN_CONCURRENCY, concurrency);
117
+ }
118
+
96
119
  function createTotals({ homeDir, projectDir, days, sinceMs, now }) {
97
120
  return {
98
121
  homeDir,
@@ -175,7 +198,7 @@ function createSourceStats(label) {
175
198
  };
176
199
  }
177
200
 
178
- async function scanCodex(homeDir, sinceMs, maxFiles, totals) {
201
+ async function scanCodex(homeDir, sinceMs, maxFiles, totals, concurrency) {
179
202
  const root = join(homeDir, ".codex", "sessions");
180
203
  const source = totals.sources.codex;
181
204
  if (!existsSync(root)) {
@@ -186,7 +209,7 @@ async function scanCodex(homeDir, sinceMs, maxFiles, totals) {
186
209
  const files = recentFiles(root, sinceMs, maxFiles, (path) => path.endsWith(".jsonl"));
187
210
  source.files = files.length;
188
211
  totals.files += files.length;
189
- for (const path of files) await scanCodexFile(path, totals);
212
+ await scanFiles(files, concurrency, (path) => scanCodexFile(path, totals));
190
213
  }
191
214
 
192
215
  async function scanCodexFile(path, totals) {
@@ -274,7 +297,7 @@ async function scanCodexFile(path, totals) {
274
297
  if (!session.id) addSession(totals, source, path);
275
298
  }
276
299
 
277
- async function scanClaude(homeDir, sinceMs, maxFiles, totals) {
300
+ async function scanClaude(homeDir, sinceMs, maxFiles, totals, concurrency) {
278
301
  const root = join(homeDir, ".claude", "projects");
279
302
  const source = totals.sources.claude;
280
303
  if (!existsSync(root)) {
@@ -285,7 +308,7 @@ async function scanClaude(homeDir, sinceMs, maxFiles, totals) {
285
308
  const files = recentFiles(root, sinceMs, maxFiles, (path) => path.endsWith(".jsonl"));
286
309
  source.files = files.length;
287
310
  totals.files += files.length;
288
- for (const path of files) await scanClaudeFile(path, totals);
311
+ await scanFiles(files, concurrency, (path) => scanClaudeFile(path, totals));
289
312
 
290
313
  const facetsRoot = join(homeDir, ".claude", "usage-data", "facets");
291
314
  if (existsSync(facetsRoot)) {
@@ -357,12 +380,12 @@ async function scanClaudeFile(path, totals) {
357
380
  if (!session.id) addSession(totals, source, path);
358
381
  }
359
382
 
360
- async function scanPi(homeDir, sinceMs, maxFiles, totals) {
383
+ async function scanPi(homeDir, sinceMs, maxFiles, totals, concurrency) {
361
384
  const source = totals.sources.pi;
362
385
  const sessionRoot = join(homeDir, ".pi", "agent", "sessions");
363
386
  const files = recentFilesFromRoots([sessionRoot], sinceMs, maxFiles, (path) => path.endsWith(".jsonl"));
364
387
  if (files.length === 0 && !existsSync(sessionRoot)) source.unavailable = true;
365
- for (const path of files) await scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: true });
388
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: true }));
366
389
 
367
390
  scanKnownFiles([
368
391
  join(homeDir, ".pi", "agent", "settings.json"),
@@ -372,7 +395,7 @@ async function scanPi(homeDir, sinceMs, maxFiles, totals) {
372
395
  ], sinceMs, totals, source, { modelFacing: false, sessionPerFile: false });
373
396
  }
374
397
 
375
- async function scanGemini(homeDir, sinceMs, maxFiles, totals) {
398
+ async function scanGemini(homeDir, sinceMs, maxFiles, totals, concurrency) {
376
399
  const source = totals.sources.gemini;
377
400
  const root = join(homeDir, ".gemini");
378
401
  const sessionRoots = [join(root, "tmp"), join(root, "sessions"), join(root, "exports")];
@@ -384,7 +407,7 @@ async function scanGemini(homeDir, sinceMs, maxFiles, totals) {
384
407
  name === "collector.log";
385
408
  });
386
409
  if (files.length === 0 && !existsSync(root)) source.unavailable = true;
387
- for (const path of files) await scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: /(?:session|chat|\.jsonl)/i.test(path) });
410
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: /(?:session|chat|\.jsonl)/i.test(path) }));
388
411
 
389
412
  scanKnownFiles([
390
413
  join(root, "settings.json"),
@@ -393,12 +416,12 @@ async function scanGemini(homeDir, sinceMs, maxFiles, totals) {
393
416
  ], sinceMs, totals, source, { modelFacing: false, sessionPerFile: false });
394
417
  }
395
418
 
396
- async function scanOpenCode(homeDir, sinceMs, maxFiles, totals) {
419
+ async function scanOpenCode(homeDir, sinceMs, maxFiles, totals, concurrency) {
397
420
  const source = totals.sources.opencode;
398
421
  const root = join(homeDir, ".local", "share", "opencode");
399
422
  const files = recentFilesFromRoots([join(root, "log"), join(root, "project")], sinceMs, maxFiles, isLikelyAgentDataFile);
400
423
  if (files.length === 0 && !existsSync(root)) source.unavailable = true;
401
- for (const path of files) await scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: path.includes("/project/") });
424
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: path.includes("/project/") }));
402
425
 
403
426
  scanKnownFiles([
404
427
  join(root, "auth.json"),
@@ -407,7 +430,7 @@ async function scanOpenCode(homeDir, sinceMs, maxFiles, totals) {
407
430
  ], sinceMs, totals, source, { modelFacing: false, sessionPerFile: false });
408
431
  }
409
432
 
410
- async function scanCursor(homeDir, sinceMs, maxFiles, totals) {
433
+ async function scanCursor(homeDir, sinceMs, maxFiles, totals, concurrency) {
411
434
  const source = totals.sources.cursor;
412
435
  const roots = editorUserRoots(homeDir, "Cursor");
413
436
  const files = recentFilesFromRoots([
@@ -417,10 +440,10 @@ async function scanCursor(homeDir, sinceMs, maxFiles, totals) {
417
440
  join(homeDir, ".cursor"),
418
441
  ], sinceMs, maxFiles, isLikelyAgentDataFile);
419
442
  if (files.length === 0 && roots.every((root) => !existsSync(root)) && !existsSync(join(homeDir, ".cursor"))) source.unavailable = true;
420
- for (const path of files) await scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: false });
443
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: false }));
421
444
  }
422
445
 
423
- async function scanWindsurf(homeDir, sinceMs, maxFiles, totals) {
446
+ async function scanWindsurf(homeDir, sinceMs, maxFiles, totals, concurrency) {
424
447
  const source = totals.sources.windsurf;
425
448
  const roots = editorUserRoots(homeDir, "Windsurf");
426
449
  const files = recentFilesFromRoots([
@@ -431,10 +454,10 @@ async function scanWindsurf(homeDir, sinceMs, maxFiles, totals) {
431
454
  join(homeDir, ".windsurf"),
432
455
  ], sinceMs, maxFiles, isLikelyAgentDataFile);
433
456
  if (files.length === 0 && roots.every((root) => !existsSync(root)) && !existsSync(join(homeDir, ".windsurf"))) source.unavailable = true;
434
- for (const path of files) await scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: false });
457
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: false }));
435
458
  }
436
459
 
437
- async function scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals) {
460
+ async function scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals, concurrency) {
438
461
  const editorRoots = [
439
462
  ...editorUserRoots(homeDir, "Code"),
440
463
  ...editorUserRoots(homeDir, "Code - Insiders"),
@@ -455,11 +478,11 @@ async function scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals) {
455
478
  }
456
479
  const files = recentFilesFromRoots(roots, sinceMs, Math.min(maxFiles, 1000), isLikelyAgentDataFile);
457
480
  if (files.length === 0 && roots.every((root) => !existsSync(root))) item.source.unavailable = true;
458
- for (const path of files) await scanGenericAgentFile(path, totals, item.source, { modelFacing: true, sessionPerFile: /(?:task|history|session|conversation)/i.test(path) });
481
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, item.source, { modelFacing: true, sessionPerFile: /(?:task|history|session|conversation)/i.test(path) }));
459
482
  }
460
483
  }
461
484
 
462
- async function scanCopilot(homeDir, sinceMs, maxFiles, totals) {
485
+ async function scanCopilot(homeDir, sinceMs, maxFiles, totals, concurrency) {
463
486
  const source = totals.sources.copilot;
464
487
  const roots = [
465
488
  join(homeDir, ".copilot", "session-state"),
@@ -472,7 +495,7 @@ async function scanCopilot(homeDir, sinceMs, maxFiles, totals) {
472
495
  return isLikelyAgentDataFile(path);
473
496
  });
474
497
  if (files.length === 0 && roots.every((root) => !existsSync(root))) source.unavailable = true;
475
- for (const path of files) await scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: !path.endsWith(".vscdb") });
498
+ await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: !path.endsWith(".vscdb") }));
476
499
  }
477
500
 
478
501
  function scanAider(projectDir, sinceMs, totals) {
@@ -529,16 +552,27 @@ function scanKnownFiles(paths, sinceMs, totals, source, options) {
529
552
  }
530
553
 
531
554
  async function scanGenericAgentFile(path, totals, source, options = {}) {
532
- registerAgentDataFile(totals, source, path);
555
+ if (!registerAgentDataFile(totals, source, path)) return;
533
556
  if (path.endsWith(".jsonl")) {
534
557
  await scanGenericJsonLines(path, totals, source, options);
535
558
  return;
536
559
  }
537
- scanGenericAgentFileSync(path, totals, source, options);
560
+ await scanGenericAgentFileText(path, totals, source, options);
561
+ }
562
+
563
+ async function scanGenericAgentFileText(path, totals, source, options = {}) {
564
+ let text;
565
+ try {
566
+ text = await readFile(path, "utf8");
567
+ } catch {
568
+ totals.diagnostics.push(`Skipped unreadable ${source.label} file ${path}`);
569
+ return;
570
+ }
571
+ scanGenericText(text, totals, source, path, options);
538
572
  }
539
573
 
540
574
  function scanGenericAgentFileSync(path, totals, source, options = {}) {
541
- registerAgentDataFile(totals, source, path);
575
+ if (!registerAgentDataFile(totals, source, path)) return;
542
576
  let text;
543
577
  try {
544
578
  text = readFileSync(path, "utf8");
@@ -546,6 +580,10 @@ function scanGenericAgentFileSync(path, totals, source, options = {}) {
546
580
  totals.diagnostics.push(`Skipped unreadable ${source.label} file ${path}`);
547
581
  return;
548
582
  }
583
+ scanGenericText(text, totals, source, path, options);
584
+ }
585
+
586
+ function scanGenericText(text, totals, source, path, options = {}) {
549
587
  scanTextForSecrets(text, totals, { modelFacing: options.modelFacing === true, file: path });
550
588
  scanTextForStateLoss(text, totals, path);
551
589
  scanTextForTrace(text, totals, path);
@@ -553,6 +591,20 @@ function scanGenericAgentFileSync(path, totals, source, options = {}) {
553
591
  if (path.endsWith(".json")) scanGenericJsonDocument(text, totals, source, path);
554
592
  }
555
593
 
594
+ async function scanFiles(files, concurrency, scanFile) {
595
+ if (files.length === 0) return;
596
+ const workerCount = Math.min(concurrency, files.length);
597
+ let next = 0;
598
+ const workers = Array.from({ length: workerCount }, async () => {
599
+ while (next < files.length) {
600
+ const path = files[next];
601
+ next += 1;
602
+ await scanFile(path);
603
+ }
604
+ });
605
+ await Promise.all(workers);
606
+ }
607
+
556
608
  async function scanGenericJsonLines(path, totals, source, options = {}) {
557
609
  const session = createSessionAccumulator(source.label.toLowerCase(), path);
558
610
  let sawRecord = false;
@@ -586,9 +638,11 @@ function noteGenericRecordSignals(record, totals, source, session) {
586
638
  addSession(totals, source, id);
587
639
  }
588
640
 
589
- const usage = normalizeGenericUsage(record.usage ?? record.tokenUsage ?? record.token_usage ?? record.metadata?.usage);
641
+ const usage = normalizeGenericUsage(record.usage ?? record.tokenUsage ?? record.token_usage ?? record.message?.usage ?? record.metadata?.usage);
590
642
  if (usage.total > 0) addUsage(totals, source, usage);
591
643
 
644
+ noteNestedMessageToolSignals(record, totals, source, session);
645
+
592
646
  if (isGenericToolStart(record)) {
593
647
  const callId = record.id ?? record.call_id ?? record.tool_call_id ?? stableSnippet(record).slice(0, 64);
594
648
  addToolCall(totals, source);
@@ -601,6 +655,27 @@ function noteGenericRecordSignals(record, totals, source, session) {
601
655
  }
602
656
  }
603
657
 
658
+ function noteNestedMessageToolSignals(record, totals, source, session) {
659
+ const message = record.message;
660
+ if (!message || typeof message !== "object") return;
661
+
662
+ for (const block of array(message.content)) {
663
+ if (!block || typeof block !== "object" || String(block.type).toLowerCase() !== "toolcall") continue;
664
+ const callId = block.id ?? record.id ?? stableSnippet(block).slice(0, 64);
665
+ addToolCall(totals, source);
666
+ noteToolStart(session, callId, toolSignature(block.name, block.arguments ?? block.input));
667
+ }
668
+
669
+ if (String(message.role).toLowerCase() !== "toolresult" && !message.toolCallId && !message.toolName) return;
670
+ const failed = message.isError === true || isGenericToolFailure({ ...record, ...message });
671
+ const callId = message.toolCallId ?? record.toolCallId ?? record.tool_call_id;
672
+ const signature = callId && session.calls.has(callId)
673
+ ? session.calls.get(callId)
674
+ : toolSignature(message.toolName ?? record.toolName ?? record.tool_name ?? "tool_result", message.content ?? record.output ?? record.result);
675
+ if (failed) addFailure(totals, source);
676
+ noteToolEnd(totals, session, signature, failed, parseTime(message.timestamp ?? record.timestamp), 0);
677
+ }
678
+
604
679
  function visitGenericJson(value, visit, depth = 0) {
605
680
  if (depth > 8 || value == null) return;
606
681
  if (Array.isArray(value)) {
@@ -640,7 +715,12 @@ function scanGlueCode(projectDir, totals) {
640
715
  }
641
716
 
642
717
  function recentFiles(root, sinceMs, maxFiles, predicate) {
718
+ return recentFileEntries(root, sinceMs, maxFiles, predicate).map((item) => item.path);
719
+ }
720
+
721
+ function recentFileEntries(root, sinceMs, maxFiles, predicate) {
643
722
  const files = [];
723
+ const shouldTrim = maxFiles < EXHAUSTIVE_MAX_FILES;
644
724
  walk(root, (path, dirent) => {
645
725
  if (dirent.isDirectory()) {
646
726
  if (IGNORED_DIRS.has(dirent.name)) return false;
@@ -650,27 +730,35 @@ function recentFiles(root, sinceMs, maxFiles, predicate) {
650
730
  const stat = safeStat(path);
651
731
  if (!stat || stat.size > MAX_FILE_BYTES || stat.mtimeMs < sinceMs) return false;
652
732
  files.push({ path, mtimeMs: stat.mtimeMs });
733
+ trimRecentFileEntries(files, maxFiles, shouldTrim);
653
734
  return false;
654
735
  });
655
736
  files.sort((a, b) => b.mtimeMs - a.mtimeMs);
656
- return files.slice(0, maxFiles).map((item) => item.path);
737
+ return files.slice(0, maxFiles);
657
738
  }
658
739
 
659
740
  function recentFilesFromRoots(roots, sinceMs, maxFiles, predicate) {
660
741
  const seen = new Set();
661
742
  const files = [];
743
+ const shouldTrim = maxFiles < EXHAUSTIVE_MAX_FILES;
662
744
  for (const root of roots) {
663
- for (const path of recentFiles(root, sinceMs, maxFiles, predicate)) {
664
- if (seen.has(path)) continue;
665
- seen.add(path);
666
- const stat = safeStat(path);
667
- if (stat) files.push({ path, mtimeMs: stat.mtimeMs });
745
+ for (const item of recentFileEntries(root, sinceMs, maxFiles, predicate)) {
746
+ if (seen.has(item.path)) continue;
747
+ seen.add(item.path);
748
+ files.push(item);
749
+ trimRecentFileEntries(files, maxFiles, shouldTrim);
668
750
  }
669
751
  }
670
752
  files.sort((a, b) => b.mtimeMs - a.mtimeMs);
671
753
  return files.slice(0, maxFiles).map((item) => item.path);
672
754
  }
673
755
 
756
+ function trimRecentFileEntries(files, maxFiles, shouldTrim) {
757
+ if (!shouldTrim || files.length <= maxFiles * 2) return;
758
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs);
759
+ files.length = maxFiles;
760
+ }
761
+
674
762
  function isLikelyAgentDataFile(path) {
675
763
  const ext = extension(path);
676
764
  const name = basename(path);
@@ -906,7 +994,7 @@ function hasToolResult(content) {
906
994
 
907
995
  function isGenericModelFacing(record) {
908
996
  const role = String(record?.role ?? record?.message?.role ?? record?.type ?? "").toLowerCase();
909
- return role === "user" || role === "assistant" || role === "model" || role === "tool_use" || role === "function_call";
997
+ return role === "user" || role === "assistant" || role === "model" || role === "tool_use" || role === "function_call" || role === "toolresult" || role === "tool_result" || role === "tool";
910
998
  }
911
999
 
912
1000
  function genericRecordText(record) {
@@ -984,10 +1072,10 @@ function normalizeClaudeUsage(usage) {
984
1072
 
985
1073
  function normalizeGenericUsage(usage) {
986
1074
  if (!usage || typeof usage !== "object") return { input: 0, freshInput: 0, output: 0, cached: 0, reasoning: 0, total: 0, active: 0 };
987
- const input = firstNumber(usage.input_tokens, usage.inputTokens, usage.prompt_tokens, usage.promptTokens, usage.promptTokenCount, usage.inputTokenCount);
988
- const output = firstNumber(usage.output_tokens, usage.outputTokens, usage.completion_tokens, usage.completionTokens, usage.candidatesTokenCount, usage.outputTokenCount);
989
- const cached = firstNumber(usage.cached_input_tokens, usage.cache_read_input_tokens, usage.cachedTokens, usage.cachedContentTokenCount);
990
- const cacheCreation = firstNumber(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens);
1075
+ const input = firstNumber(usage.input_tokens, usage.inputTokens, usage.prompt_tokens, usage.promptTokens, usage.promptTokenCount, usage.inputTokenCount, usage.input);
1076
+ const output = firstNumber(usage.output_tokens, usage.outputTokens, usage.completion_tokens, usage.completionTokens, usage.candidatesTokenCount, usage.outputTokenCount, usage.output);
1077
+ const cached = firstNumber(usage.cached_input_tokens, usage.cache_read_input_tokens, usage.cachedTokens, usage.cachedContentTokenCount, usage.cacheRead);
1078
+ const cacheCreation = firstNumber(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens, usage.cacheWrite);
991
1079
  const reasoning = firstNumber(usage.reasoning_output_tokens, usage.reasoningTokens, usage.thoughtsTokenCount);
992
1080
  const total = firstNumber(usage.total_tokens, usage.totalTokens, usage.totalTokenCount) || input + output + cached + cacheCreation;
993
1081
  const freshInput = Math.max(0, input + cacheCreation - cached);