@token-dashboard/codex-usage-uploader 0.1.1 → 0.1.2
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 +2 -1
- package/src/cli.js +12 -29
- package/src/collector.js +59 -71
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@token-dashboard/codex-usage-uploader",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Codex 用量上报 CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
+
"dev": "node bin/codex-usage-uploader.js init --backend-url http://localhost:8086",
|
|
15
16
|
"test": "node --no-warnings --test tests/*.test.mjs"
|
|
16
17
|
},
|
|
17
18
|
"engines": {
|
package/src/cli.js
CHANGED
|
@@ -257,44 +257,21 @@ function formatDuration(durationMs) {
|
|
|
257
257
|
function printCatchUpProgress(event) {
|
|
258
258
|
switch (event.phase) {
|
|
259
259
|
case 'start':
|
|
260
|
-
console.log(
|
|
261
|
-
`[catch-up] start files=${event.totalFiles} queued_batches=${
|
|
262
|
-
event.bufferingBatchCount +
|
|
263
|
-
event.pendingBatchCount +
|
|
264
|
-
event.retryingBatchCount
|
|
265
|
-
} queued_events=${event.queuedEvents}`,
|
|
266
|
-
);
|
|
260
|
+
console.log(`[scan] start files=${event.totalFiles}`);
|
|
267
261
|
return;
|
|
268
262
|
case 'file':
|
|
269
263
|
console.log(
|
|
270
|
-
`[
|
|
271
|
-
event.bufferingBatchCount +
|
|
272
|
-
event.pendingBatchCount +
|
|
273
|
-
event.retryingBatchCount
|
|
274
|
-
} queued_events=${event.queuedEvents}`,
|
|
275
|
-
);
|
|
276
|
-
return;
|
|
277
|
-
case 'upload':
|
|
278
|
-
console.log(
|
|
279
|
-
`[catch-up] uploaded_batches=${event.batchesUploaded} last_batch=${event.batchKey} remaining_batches=${
|
|
280
|
-
event.bufferingBatchCount +
|
|
281
|
-
event.pendingBatchCount +
|
|
282
|
-
event.retryingBatchCount
|
|
283
|
-
} remaining_events=${event.queuedEvents}`,
|
|
264
|
+
`[scan] file ${event.filesProcessed}/${event.totalFiles} current=${event.file} events=${event.eventsParsed}`,
|
|
284
265
|
);
|
|
285
266
|
return;
|
|
286
267
|
case 'done':
|
|
287
268
|
console.log(
|
|
288
|
-
`[
|
|
269
|
+
`[scan] complete files=${event.filesProcessed}/${event.totalFiles} events=${event.eventsParsed} pending_batches=${event.pendingBatches} duration=${formatDuration(event.durationMs)}`,
|
|
289
270
|
);
|
|
290
271
|
return;
|
|
291
272
|
case 'error':
|
|
292
273
|
console.error(
|
|
293
|
-
`[
|
|
294
|
-
event.bufferingBatchCount +
|
|
295
|
-
event.pendingBatchCount +
|
|
296
|
-
event.retryingBatchCount
|
|
297
|
-
} remaining_events=${event.queuedEvents}`,
|
|
274
|
+
`[scan] failed stage=${event.stage} message=${event.message}`,
|
|
298
275
|
);
|
|
299
276
|
return;
|
|
300
277
|
}
|
|
@@ -351,10 +328,11 @@ async function runInit(options) {
|
|
|
351
328
|
console.log(`Install root: ${runtime.installRoot}`);
|
|
352
329
|
console.log(`Config file: ${runtime.configFile}`);
|
|
353
330
|
printIdentity(identity);
|
|
354
|
-
console.log('
|
|
331
|
+
console.log('Scanning local Codex sessions...');
|
|
355
332
|
|
|
333
|
+
let scanResult;
|
|
356
334
|
try {
|
|
357
|
-
await uploader.runForegroundCatchUp({
|
|
335
|
+
scanResult = await uploader.runForegroundCatchUp({
|
|
358
336
|
onProgress: printCatchUpProgress,
|
|
359
337
|
});
|
|
360
338
|
} catch (error) {
|
|
@@ -363,6 +341,11 @@ async function runInit(options) {
|
|
|
363
341
|
|
|
364
342
|
manager.start();
|
|
365
343
|
console.log(`${PRODUCT_NAME} initialized and started.`);
|
|
344
|
+
if (scanResult.pendingBatches > 0) {
|
|
345
|
+
console.log(
|
|
346
|
+
`Background service is uploading ${scanResult.pendingBatches} batch(es) with ${scanResult.pendingEvents} event(s).`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
366
349
|
console.log(`Use \`${CLI_NAME} status\` to check the local service.`);
|
|
367
350
|
printPathHint(runtime);
|
|
368
351
|
} finally {
|
package/src/collector.js
CHANGED
|
@@ -465,7 +465,7 @@ export class CodexUsageUploader {
|
|
|
465
465
|
const response = await fetch(`${this.backendUrl}${apiPath}`, {
|
|
466
466
|
method: 'POST',
|
|
467
467
|
headers: { 'Content-Type': 'application/json' },
|
|
468
|
-
body:
|
|
468
|
+
body: JSON.stringify(payload),
|
|
469
469
|
});
|
|
470
470
|
if (!response.ok) {
|
|
471
471
|
const text = await response.text();
|
|
@@ -489,7 +489,7 @@ export class CodexUsageUploader {
|
|
|
489
489
|
this.stateDb.setCheckpoint('last_register_at', String(nowTs()));
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
-
async flushPendingBatches({ failFast = false, onBatchUploaded } = {}) {
|
|
492
|
+
async flushPendingBatches({ failFast = false, concurrency = 5, onBatchUploaded } = {}) {
|
|
493
493
|
if (!this.backendUrl) {
|
|
494
494
|
return { uploadedBatches: 0, failedBatches: 0, lastError: null };
|
|
495
495
|
}
|
|
@@ -509,42 +509,70 @@ export class CodexUsageUploader {
|
|
|
509
509
|
let uploadedBatches = 0;
|
|
510
510
|
let failedBatches = 0;
|
|
511
511
|
let lastError = null;
|
|
512
|
+
const collectorBody = this.collectorRequestBody();
|
|
512
513
|
|
|
513
|
-
|
|
514
|
+
const rows = this.stateDb.iterDuePendingBatches();
|
|
515
|
+
|
|
516
|
+
const uploadOne = async (row) => {
|
|
514
517
|
const payload = this.sanitizeUploadPayload(JSON.parse(row.payload_json));
|
|
515
518
|
const requestBody = {
|
|
516
519
|
idempotencyKey: row.batch_key,
|
|
517
|
-
collector:
|
|
520
|
+
collector: collectorBody,
|
|
518
521
|
payloadSizeBytes: Number(row.payload_bytes),
|
|
519
522
|
sessions: payload.sessions ?? [],
|
|
520
523
|
turns: payload.turns ?? [],
|
|
521
524
|
events: payload.events ?? [],
|
|
522
525
|
};
|
|
526
|
+
await this.postJson('/codex-usage/upload', requestBody);
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const pending = new Set();
|
|
530
|
+
let rowIndex = 0;
|
|
531
|
+
|
|
532
|
+
const enqueue = () => {
|
|
533
|
+
while (pending.size < concurrency && rowIndex < rows.length) {
|
|
534
|
+
const row = rows[rowIndex++];
|
|
535
|
+
const task = uploadOne(row)
|
|
536
|
+
.then(() => {
|
|
537
|
+
this.stateDb.markBatchUploaded(row.id);
|
|
538
|
+
uploadedBatches += 1;
|
|
539
|
+
onBatchUploaded?.({ row, uploadedBatches });
|
|
540
|
+
})
|
|
541
|
+
.catch((error) => {
|
|
542
|
+
this.stateDb.markBatchFailed(
|
|
543
|
+
row.id,
|
|
544
|
+
Number(row.attempt_count) + 1,
|
|
545
|
+
error instanceof Error ? error.message : String(error),
|
|
546
|
+
);
|
|
547
|
+
failedBatches += 1;
|
|
548
|
+
lastError = error;
|
|
549
|
+
if (failFast) {
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
.finally(() => {
|
|
554
|
+
pending.delete(task);
|
|
555
|
+
});
|
|
556
|
+
pending.add(task);
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
enqueue();
|
|
561
|
+
while (pending.size > 0) {
|
|
523
562
|
try {
|
|
524
|
-
await
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
onBatchUploaded?.({
|
|
528
|
-
row,
|
|
529
|
-
uploadedBatches,
|
|
530
|
-
queueStats: this.getQueueStats(),
|
|
531
|
-
});
|
|
532
|
-
} catch (error) {
|
|
533
|
-
this.stateDb.markBatchFailed(
|
|
534
|
-
row.id,
|
|
535
|
-
Number(row.attempt_count) + 1,
|
|
536
|
-
error instanceof Error ? error.message : String(error),
|
|
537
|
-
);
|
|
538
|
-
failedBatches += 1;
|
|
539
|
-
lastError = error;
|
|
540
|
-
if (failFast) {
|
|
541
|
-
throw new Error(
|
|
542
|
-
`Upload batch ${row.batch_key} failed: ${
|
|
543
|
-
error instanceof Error ? error.message : String(error)
|
|
544
|
-
}`,
|
|
545
|
-
);
|
|
546
|
-
}
|
|
563
|
+
await Promise.race(pending);
|
|
564
|
+
} catch {
|
|
565
|
+
if (failFast) break;
|
|
547
566
|
}
|
|
567
|
+
enqueue();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (failFast && lastError) {
|
|
571
|
+
throw new Error(
|
|
572
|
+
`Upload batch failed: ${
|
|
573
|
+
lastError instanceof Error ? lastError.message : String(lastError)
|
|
574
|
+
}`,
|
|
575
|
+
);
|
|
548
576
|
}
|
|
549
577
|
|
|
550
578
|
return { uploadedBatches, failedBatches, lastError };
|
|
@@ -578,15 +606,12 @@ export class CodexUsageUploader {
|
|
|
578
606
|
filesProcessed: 0,
|
|
579
607
|
eventsParsed: 0,
|
|
580
608
|
batchesQueued: 0,
|
|
581
|
-
batchesUploaded: 0,
|
|
582
|
-
...this.getQueueStats(),
|
|
583
609
|
});
|
|
584
610
|
|
|
585
611
|
let scanResult;
|
|
586
612
|
try {
|
|
587
613
|
scanResult = await this.scanSnapshotEntries(backfillSnapshot, {
|
|
588
614
|
onFileProcessed: ({ entry, filesProcessed, totals }) => {
|
|
589
|
-
const queueStats = this.getQueueStats();
|
|
590
615
|
onProgress?.({
|
|
591
616
|
phase: 'file',
|
|
592
617
|
file: entry.progressPath ?? entry.relpath,
|
|
@@ -594,8 +619,6 @@ export class CodexUsageUploader {
|
|
|
594
619
|
filesProcessed,
|
|
595
620
|
eventsParsed: totals.events,
|
|
596
621
|
batchesQueued: totals.batchesQueued,
|
|
597
|
-
batchesUploaded: 0,
|
|
598
|
-
...queueStats,
|
|
599
622
|
});
|
|
600
623
|
},
|
|
601
624
|
});
|
|
@@ -604,44 +627,13 @@ export class CodexUsageUploader {
|
|
|
604
627
|
phase: 'error',
|
|
605
628
|
stage: 'scan',
|
|
606
629
|
message: error instanceof Error ? error.message : String(error),
|
|
607
|
-
...this.getQueueStats(),
|
|
608
630
|
});
|
|
609
631
|
throw error;
|
|
610
632
|
}
|
|
611
633
|
|
|
612
634
|
scanResult.batchesQueued += this.stateDb.sealStaleBatches(true);
|
|
613
|
-
this.stateDb.markAllPendingDue();
|
|
614
635
|
|
|
615
|
-
|
|
616
|
-
try {
|
|
617
|
-
flushResult = await this.flushPendingBatches({
|
|
618
|
-
failFast: true,
|
|
619
|
-
onBatchUploaded: ({ row, uploadedBatches, queueStats }) => {
|
|
620
|
-
onProgress?.({
|
|
621
|
-
phase: 'upload',
|
|
622
|
-
batchKey: row.batch_key,
|
|
623
|
-
totalFiles,
|
|
624
|
-
filesProcessed: totalFiles,
|
|
625
|
-
eventsParsed: scanResult.events,
|
|
626
|
-
batchesQueued: scanResult.batchesQueued,
|
|
627
|
-
batchesUploaded: uploadedBatches,
|
|
628
|
-
...queueStats,
|
|
629
|
-
});
|
|
630
|
-
},
|
|
631
|
-
});
|
|
632
|
-
} catch (error) {
|
|
633
|
-
onProgress?.({
|
|
634
|
-
phase: 'error',
|
|
635
|
-
stage: 'upload',
|
|
636
|
-
message: error instanceof Error ? error.message : String(error),
|
|
637
|
-
totalFiles,
|
|
638
|
-
filesProcessed: totalFiles,
|
|
639
|
-
eventsParsed: scanResult.events,
|
|
640
|
-
batchesQueued: scanResult.batchesQueued,
|
|
641
|
-
...this.getQueueStats(),
|
|
642
|
-
});
|
|
643
|
-
throw error;
|
|
644
|
-
}
|
|
636
|
+
await this.ensureRemoteRegistration();
|
|
645
637
|
|
|
646
638
|
const queueStats = this.getQueueStats();
|
|
647
639
|
const result = {
|
|
@@ -651,14 +643,10 @@ export class CodexUsageUploader {
|
|
|
651
643
|
sessionsParsed: scanResult.sessions,
|
|
652
644
|
turnsParsed: scanResult.turns,
|
|
653
645
|
batchesQueued: scanResult.batchesQueued,
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
queueStats.pendingBatchCount +
|
|
658
|
-
queueStats.retryingBatchCount,
|
|
659
|
-
remainingQueuedEvents: queueStats.queuedEvents,
|
|
646
|
+
pendingBatches:
|
|
647
|
+
queueStats.pendingBatchCount + queueStats.retryingBatchCount,
|
|
648
|
+
pendingEvents: queueStats.queuedEvents,
|
|
660
649
|
durationMs: Date.now() - startedAt,
|
|
661
|
-
...queueStats,
|
|
662
650
|
};
|
|
663
651
|
|
|
664
652
|
onProgress?.({
|