@vibescore/tracker 0.1.0 → 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 +1 -1
- package/src/lib/rollout.js +314 -25
- package/src/lib/uploader.js +3 -3
package/package.json
CHANGED
package/src/lib/rollout.js
CHANGED
|
@@ -469,49 +469,253 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
469
469
|
}
|
|
470
470
|
if (touchedGroups.size === 0) return 0;
|
|
471
471
|
|
|
472
|
-
const
|
|
472
|
+
const groupQueued = hourlyState.groupQueued && typeof hourlyState.groupQueued === 'object' ? hourlyState.groupQueued : {};
|
|
473
|
+
let codexTouched = false;
|
|
474
|
+
const legacyGroups = new Set();
|
|
475
|
+
for (const groupKey of touchedGroups) {
|
|
476
|
+
if (Object.prototype.hasOwnProperty.call(groupQueued, groupKey)) {
|
|
477
|
+
legacyGroups.add(groupKey);
|
|
478
|
+
}
|
|
479
|
+
if (!codexTouched && groupKey.startsWith(`${DEFAULT_SOURCE}${BUCKET_SEPARATOR}`)) {
|
|
480
|
+
codexTouched = true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const groupedBuckets = new Map();
|
|
473
485
|
for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
|
|
474
486
|
if (!bucket || !bucket.totals) continue;
|
|
475
487
|
const parsed = parseBucketKey(key);
|
|
476
488
|
const hourStart = parsed.hourStart;
|
|
477
489
|
if (!hourStart) continue;
|
|
478
490
|
const groupKey = groupBucketKey(parsed.source, hourStart);
|
|
479
|
-
if (!touchedGroups.has(groupKey)) continue;
|
|
491
|
+
if (!touchedGroups.has(groupKey) || legacyGroups.has(groupKey)) continue;
|
|
480
492
|
|
|
481
|
-
|
|
493
|
+
const source = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
|
|
494
|
+
const model = normalizeModelInput(parsed.model) || DEFAULT_MODEL;
|
|
495
|
+
let group = groupedBuckets.get(groupKey);
|
|
482
496
|
if (!group) {
|
|
483
|
-
group = {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
497
|
+
group = { source, hourStart, buckets: new Map() };
|
|
498
|
+
groupedBuckets.set(groupKey, group);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (bucket.queuedKey != null && typeof bucket.queuedKey !== 'string') {
|
|
502
|
+
bucket.queuedKey = null;
|
|
503
|
+
}
|
|
504
|
+
group.buckets.set(model, bucket);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (codexTouched) {
|
|
508
|
+
const recomputeGroups = new Set();
|
|
509
|
+
for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
|
|
510
|
+
if (!bucket || !bucket.totals) continue;
|
|
511
|
+
const parsed = parseBucketKey(key);
|
|
512
|
+
const hourStart = parsed.hourStart;
|
|
513
|
+
if (!hourStart) continue;
|
|
514
|
+
const source = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
|
|
515
|
+
if (source !== 'every-code') continue;
|
|
516
|
+
const groupKey = groupBucketKey(source, hourStart);
|
|
517
|
+
if (legacyGroups.has(groupKey) || groupedBuckets.has(groupKey)) continue;
|
|
518
|
+
const model = normalizeModelInput(parsed.model) || DEFAULT_MODEL;
|
|
519
|
+
if (model !== DEFAULT_MODEL) continue;
|
|
520
|
+
recomputeGroups.add(groupKey);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (recomputeGroups.size > 0) {
|
|
524
|
+
for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
|
|
525
|
+
if (!bucket || !bucket.totals) continue;
|
|
526
|
+
const parsed = parseBucketKey(key);
|
|
527
|
+
const hourStart = parsed.hourStart;
|
|
528
|
+
if (!hourStart) continue;
|
|
529
|
+
const source = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
|
|
530
|
+
const groupKey = groupBucketKey(source, hourStart);
|
|
531
|
+
if (!recomputeGroups.has(groupKey)) continue;
|
|
532
|
+
let group = groupedBuckets.get(groupKey);
|
|
533
|
+
if (!group) {
|
|
534
|
+
group = { source, hourStart, buckets: new Map() };
|
|
535
|
+
groupedBuckets.set(groupKey, group);
|
|
536
|
+
}
|
|
537
|
+
if (bucket.queuedKey != null && typeof bucket.queuedKey !== 'string') {
|
|
538
|
+
bucket.queuedKey = null;
|
|
539
|
+
}
|
|
540
|
+
const model = normalizeModelInput(parsed.model) || DEFAULT_MODEL;
|
|
541
|
+
group.buckets.set(model, bucket);
|
|
542
|
+
}
|
|
490
543
|
}
|
|
491
|
-
group.models.add(parsed.model || DEFAULT_MODEL);
|
|
492
|
-
addTotals(group.totals, bucket.totals);
|
|
493
544
|
}
|
|
494
545
|
|
|
546
|
+
const codexDominants = collectCodexDominantModels(hourlyState);
|
|
547
|
+
|
|
495
548
|
const toAppend = [];
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
549
|
+
for (const group of groupedBuckets.values()) {
|
|
550
|
+
const unknownBucket = group.buckets.get(DEFAULT_MODEL) || null;
|
|
551
|
+
const dominantModel = pickDominantModel(group.buckets);
|
|
552
|
+
let alignedModel = null;
|
|
553
|
+
if (unknownBucket?.alignedModel) {
|
|
554
|
+
const normalized = normalizeModelInput(unknownBucket.alignedModel);
|
|
555
|
+
alignedModel = normalized && normalized !== DEFAULT_MODEL ? normalized : null;
|
|
556
|
+
}
|
|
557
|
+
const zeroTotals = initTotals();
|
|
558
|
+
const zeroKey = totalsKey(zeroTotals);
|
|
559
|
+
|
|
560
|
+
if (dominantModel) {
|
|
561
|
+
if (alignedModel && !group.buckets.has(alignedModel)) {
|
|
562
|
+
toAppend.push(
|
|
563
|
+
JSON.stringify({
|
|
564
|
+
source: group.source,
|
|
565
|
+
model: alignedModel,
|
|
566
|
+
hour_start: group.hourStart,
|
|
567
|
+
input_tokens: zeroTotals.input_tokens,
|
|
568
|
+
cached_input_tokens: zeroTotals.cached_input_tokens,
|
|
569
|
+
output_tokens: zeroTotals.output_tokens,
|
|
570
|
+
reasoning_output_tokens: zeroTotals.reasoning_output_tokens,
|
|
571
|
+
total_tokens: zeroTotals.total_tokens
|
|
572
|
+
})
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (unknownBucket && !alignedModel && unknownBucket.queuedKey && unknownBucket.queuedKey !== zeroKey) {
|
|
576
|
+
if (unknownBucket.retractedUnknownKey !== zeroKey) {
|
|
577
|
+
toAppend.push(
|
|
578
|
+
JSON.stringify({
|
|
579
|
+
source: group.source,
|
|
580
|
+
model: DEFAULT_MODEL,
|
|
581
|
+
hour_start: group.hourStart,
|
|
582
|
+
input_tokens: zeroTotals.input_tokens,
|
|
583
|
+
cached_input_tokens: zeroTotals.cached_input_tokens,
|
|
584
|
+
output_tokens: zeroTotals.output_tokens,
|
|
585
|
+
reasoning_output_tokens: zeroTotals.reasoning_output_tokens,
|
|
586
|
+
total_tokens: zeroTotals.total_tokens
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
unknownBucket.retractedUnknownKey = zeroKey;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (unknownBucket) unknownBucket.alignedModel = null;
|
|
593
|
+
for (const [model, bucket] of group.buckets.entries()) {
|
|
594
|
+
if (model === DEFAULT_MODEL) continue;
|
|
595
|
+
let totals = bucket.totals;
|
|
596
|
+
if (model === dominantModel && unknownBucket?.totals) {
|
|
597
|
+
totals = cloneTotals(bucket.totals);
|
|
598
|
+
addTotals(totals, unknownBucket.totals);
|
|
599
|
+
}
|
|
600
|
+
const key = totalsKey(totals);
|
|
601
|
+
if (bucket.queuedKey === key) continue;
|
|
602
|
+
toAppend.push(
|
|
603
|
+
JSON.stringify({
|
|
604
|
+
source: group.source,
|
|
605
|
+
model,
|
|
606
|
+
hour_start: group.hourStart,
|
|
607
|
+
input_tokens: totals.input_tokens,
|
|
608
|
+
cached_input_tokens: totals.cached_input_tokens,
|
|
609
|
+
output_tokens: totals.output_tokens,
|
|
610
|
+
reasoning_output_tokens: totals.reasoning_output_tokens,
|
|
611
|
+
total_tokens: totals.total_tokens
|
|
612
|
+
})
|
|
613
|
+
);
|
|
614
|
+
bucket.queuedKey = key;
|
|
615
|
+
}
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (!unknownBucket?.totals) continue;
|
|
620
|
+
let outputModel = DEFAULT_MODEL;
|
|
621
|
+
if (group.source === 'every-code') {
|
|
622
|
+
const aligned = findNearestCodexModel(group.hourStart, codexDominants);
|
|
623
|
+
if (aligned) outputModel = aligned;
|
|
624
|
+
}
|
|
625
|
+
const nextAligned = outputModel !== DEFAULT_MODEL ? outputModel : null;
|
|
626
|
+
if (alignedModel && alignedModel !== nextAligned) {
|
|
627
|
+
toAppend.push(
|
|
628
|
+
JSON.stringify({
|
|
629
|
+
source: group.source,
|
|
630
|
+
model: alignedModel,
|
|
631
|
+
hour_start: group.hourStart,
|
|
632
|
+
input_tokens: zeroTotals.input_tokens,
|
|
633
|
+
cached_input_tokens: zeroTotals.cached_input_tokens,
|
|
634
|
+
output_tokens: zeroTotals.output_tokens,
|
|
635
|
+
reasoning_output_tokens: zeroTotals.reasoning_output_tokens,
|
|
636
|
+
total_tokens: zeroTotals.total_tokens
|
|
637
|
+
})
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (!alignedModel && nextAligned && unknownBucket.queuedKey && unknownBucket.queuedKey !== zeroKey) {
|
|
641
|
+
if (unknownBucket.retractedUnknownKey !== zeroKey) {
|
|
642
|
+
toAppend.push(
|
|
643
|
+
JSON.stringify({
|
|
644
|
+
source: group.source,
|
|
645
|
+
model: DEFAULT_MODEL,
|
|
646
|
+
hour_start: group.hourStart,
|
|
647
|
+
input_tokens: zeroTotals.input_tokens,
|
|
648
|
+
cached_input_tokens: zeroTotals.cached_input_tokens,
|
|
649
|
+
output_tokens: zeroTotals.output_tokens,
|
|
650
|
+
reasoning_output_tokens: zeroTotals.reasoning_output_tokens,
|
|
651
|
+
total_tokens: zeroTotals.total_tokens
|
|
652
|
+
})
|
|
653
|
+
);
|
|
654
|
+
unknownBucket.retractedUnknownKey = zeroKey;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (unknownBucket) unknownBucket.alignedModel = nextAligned;
|
|
658
|
+
const key = totalsKey(unknownBucket.totals);
|
|
659
|
+
const outputKey = outputModel === DEFAULT_MODEL ? key : `${key}|${outputModel}`;
|
|
660
|
+
if (unknownBucket.queuedKey === outputKey) continue;
|
|
502
661
|
toAppend.push(
|
|
503
662
|
JSON.stringify({
|
|
504
663
|
source: group.source,
|
|
505
|
-
model,
|
|
664
|
+
model: outputModel,
|
|
506
665
|
hour_start: group.hourStart,
|
|
507
|
-
input_tokens:
|
|
508
|
-
cached_input_tokens:
|
|
509
|
-
output_tokens:
|
|
510
|
-
reasoning_output_tokens:
|
|
511
|
-
total_tokens:
|
|
666
|
+
input_tokens: unknownBucket.totals.input_tokens,
|
|
667
|
+
cached_input_tokens: unknownBucket.totals.cached_input_tokens,
|
|
668
|
+
output_tokens: unknownBucket.totals.output_tokens,
|
|
669
|
+
reasoning_output_tokens: unknownBucket.totals.reasoning_output_tokens,
|
|
670
|
+
total_tokens: unknownBucket.totals.total_tokens
|
|
512
671
|
})
|
|
513
672
|
);
|
|
514
|
-
|
|
673
|
+
unknownBucket.queuedKey = outputKey;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (legacyGroups.size > 0) {
|
|
677
|
+
const grouped = new Map();
|
|
678
|
+
for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
|
|
679
|
+
if (!bucket || !bucket.totals) continue;
|
|
680
|
+
const parsed = parseBucketKey(key);
|
|
681
|
+
const hourStart = parsed.hourStart;
|
|
682
|
+
if (!hourStart) continue;
|
|
683
|
+
const groupKey = groupBucketKey(parsed.source, hourStart);
|
|
684
|
+
if (!legacyGroups.has(groupKey)) continue;
|
|
685
|
+
|
|
686
|
+
let group = grouped.get(groupKey);
|
|
687
|
+
if (!group) {
|
|
688
|
+
group = {
|
|
689
|
+
source: normalizeSourceInput(parsed.source) || DEFAULT_SOURCE,
|
|
690
|
+
hourStart,
|
|
691
|
+
models: new Set(),
|
|
692
|
+
totals: initTotals()
|
|
693
|
+
};
|
|
694
|
+
grouped.set(groupKey, group);
|
|
695
|
+
}
|
|
696
|
+
group.models.add(parsed.model || DEFAULT_MODEL);
|
|
697
|
+
addTotals(group.totals, bucket.totals);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
for (const group of grouped.values()) {
|
|
701
|
+
const model = group.models.size === 1 ? [...group.models][0] : DEFAULT_MODEL;
|
|
702
|
+
const key = totalsKey(group.totals);
|
|
703
|
+
const groupKey = groupBucketKey(group.source, group.hourStart);
|
|
704
|
+
if (groupQueued[groupKey] === key) continue;
|
|
705
|
+
toAppend.push(
|
|
706
|
+
JSON.stringify({
|
|
707
|
+
source: group.source,
|
|
708
|
+
model,
|
|
709
|
+
hour_start: group.hourStart,
|
|
710
|
+
input_tokens: group.totals.input_tokens,
|
|
711
|
+
cached_input_tokens: group.totals.cached_input_tokens,
|
|
712
|
+
output_tokens: group.totals.output_tokens,
|
|
713
|
+
reasoning_output_tokens: group.totals.reasoning_output_tokens,
|
|
714
|
+
total_tokens: group.totals.total_tokens
|
|
715
|
+
})
|
|
716
|
+
);
|
|
717
|
+
groupQueued[groupKey] = key;
|
|
718
|
+
}
|
|
515
719
|
}
|
|
516
720
|
|
|
517
721
|
hourlyState.groupQueued = groupQueued;
|
|
@@ -523,6 +727,91 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
523
727
|
return toAppend.length;
|
|
524
728
|
}
|
|
525
729
|
|
|
730
|
+
function pickDominantModel(buckets) {
|
|
731
|
+
let dominantModel = null;
|
|
732
|
+
let dominantTotal = -1;
|
|
733
|
+
for (const [model, bucket] of buckets.entries()) {
|
|
734
|
+
if (model === DEFAULT_MODEL) continue;
|
|
735
|
+
const total = Number(bucket?.totals?.total_tokens || 0);
|
|
736
|
+
if (
|
|
737
|
+
dominantModel == null ||
|
|
738
|
+
total > dominantTotal ||
|
|
739
|
+
(total === dominantTotal && model < dominantModel)
|
|
740
|
+
) {
|
|
741
|
+
dominantModel = model;
|
|
742
|
+
dominantTotal = total;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return dominantModel;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function cloneTotals(totals) {
|
|
749
|
+
const cloned = initTotals();
|
|
750
|
+
addTotals(cloned, totals || {});
|
|
751
|
+
return cloned;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function collectCodexDominantModels(hourlyState) {
|
|
755
|
+
const grouped = new Map();
|
|
756
|
+
for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
|
|
757
|
+
if (!bucket || !bucket.totals) continue;
|
|
758
|
+
const parsed = parseBucketKey(key);
|
|
759
|
+
const hourStart = parsed.hourStart;
|
|
760
|
+
if (!hourStart) continue;
|
|
761
|
+
const source = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
|
|
762
|
+
if (source !== DEFAULT_SOURCE) continue;
|
|
763
|
+
const model = normalizeModelInput(parsed.model) || DEFAULT_MODEL;
|
|
764
|
+
if (model === DEFAULT_MODEL) continue;
|
|
765
|
+
|
|
766
|
+
let models = grouped.get(hourStart);
|
|
767
|
+
if (!models) {
|
|
768
|
+
models = new Map();
|
|
769
|
+
grouped.set(hourStart, models);
|
|
770
|
+
}
|
|
771
|
+
const total = Number(bucket.totals.total_tokens || 0);
|
|
772
|
+
models.set(model, (models.get(model) || 0) + total);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const dominants = [];
|
|
776
|
+
for (const [hourStart, models] of grouped.entries()) {
|
|
777
|
+
let dominantModel = null;
|
|
778
|
+
let dominantTotal = -1;
|
|
779
|
+
for (const [model, total] of models.entries()) {
|
|
780
|
+
if (
|
|
781
|
+
dominantModel == null ||
|
|
782
|
+
total > dominantTotal ||
|
|
783
|
+
(total === dominantTotal && model < dominantModel)
|
|
784
|
+
) {
|
|
785
|
+
dominantModel = model;
|
|
786
|
+
dominantTotal = total;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (dominantModel) {
|
|
790
|
+
dominants.push({ hourStart, model: dominantModel });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return dominants;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function findNearestCodexModel(hourStart, dominants) {
|
|
798
|
+
if (!hourStart || !dominants || dominants.length === 0) return null;
|
|
799
|
+
const target = Date.parse(hourStart);
|
|
800
|
+
if (!Number.isFinite(target)) return null;
|
|
801
|
+
|
|
802
|
+
let best = null;
|
|
803
|
+
for (const entry of dominants) {
|
|
804
|
+
const candidate = Date.parse(entry.hourStart);
|
|
805
|
+
if (!Number.isFinite(candidate)) continue;
|
|
806
|
+
const diff = Math.abs(candidate - target);
|
|
807
|
+
if (!best || diff < best.diff || (diff === best.diff && candidate < best.time)) {
|
|
808
|
+
best = { diff, time: candidate, model: entry.model };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return best ? best.model : null;
|
|
813
|
+
}
|
|
814
|
+
|
|
526
815
|
function normalizeHourlyState(raw) {
|
|
527
816
|
const state = raw && typeof raw === 'object' ? raw : {};
|
|
528
817
|
const version = Number(state.version || 1);
|
package/src/lib/uploader.js
CHANGED
|
@@ -78,7 +78,7 @@ async function readBatch(queuePath, startOffset, maxBuckets) {
|
|
|
78
78
|
const model = normalizeModel(bucket?.model) || DEFAULT_MODEL;
|
|
79
79
|
bucket.source = source;
|
|
80
80
|
bucket.model = model;
|
|
81
|
-
bucketMap.set(bucketKey(source, hourStart), bucket);
|
|
81
|
+
bucketMap.set(bucketKey(source, model, hourStart), bucket);
|
|
82
82
|
linesRead += 1;
|
|
83
83
|
if (linesRead >= maxBuckets) break;
|
|
84
84
|
}
|
|
@@ -97,8 +97,8 @@ async function safeFileSize(p) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function bucketKey(source, hourStart) {
|
|
101
|
-
return `${source}${BUCKET_SEPARATOR}${hourStart}`;
|
|
100
|
+
function bucketKey(source, model, hourStart) {
|
|
101
|
+
return `${source}${BUCKET_SEPARATOR}${model}${BUCKET_SEPARATOR}${hourStart}`;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function normalizeSource(value) {
|