@vibescore/tracker 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 +1 -1
- package/src/lib/rollout.js +268 -20
package/package.json
CHANGED
package/src/lib/rollout.js
CHANGED
|
@@ -470,44 +470,207 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
470
470
|
if (touchedGroups.size === 0) return 0;
|
|
471
471
|
|
|
472
472
|
const groupQueued = hourlyState.groupQueued && typeof hourlyState.groupQueued === 'object' ? hourlyState.groupQueued : {};
|
|
473
|
+
let codexTouched = false;
|
|
473
474
|
const legacyGroups = new Set();
|
|
474
475
|
for (const groupKey of touchedGroups) {
|
|
475
476
|
if (Object.prototype.hasOwnProperty.call(groupQueued, groupKey)) {
|
|
476
477
|
legacyGroups.add(groupKey);
|
|
477
478
|
}
|
|
479
|
+
if (!codexTouched && groupKey.startsWith(`${DEFAULT_SOURCE}${BUCKET_SEPARATOR}`)) {
|
|
480
|
+
codexTouched = true;
|
|
481
|
+
}
|
|
478
482
|
}
|
|
479
483
|
|
|
480
|
-
const
|
|
481
|
-
for (const
|
|
482
|
-
|
|
484
|
+
const groupedBuckets = new Map();
|
|
485
|
+
for (const [key, bucket] of Object.entries(hourlyState.buckets || {})) {
|
|
486
|
+
if (!bucket || !bucket.totals) continue;
|
|
487
|
+
const parsed = parseBucketKey(key);
|
|
483
488
|
const hourStart = parsed.hourStart;
|
|
484
489
|
if (!hourStart) continue;
|
|
485
490
|
const groupKey = groupBucketKey(parsed.source, hourStart);
|
|
486
|
-
if (legacyGroups.has(groupKey)) continue;
|
|
491
|
+
if (!touchedGroups.has(groupKey) || legacyGroups.has(groupKey)) continue;
|
|
492
|
+
|
|
493
|
+
const source = normalizeSourceInput(parsed.source) || DEFAULT_SOURCE;
|
|
494
|
+
const model = normalizeModelInput(parsed.model) || DEFAULT_MODEL;
|
|
495
|
+
let group = groupedBuckets.get(groupKey);
|
|
496
|
+
if (!group) {
|
|
497
|
+
group = { source, hourStart, buckets: new Map() };
|
|
498
|
+
groupedBuckets.set(groupKey, group);
|
|
499
|
+
}
|
|
487
500
|
|
|
488
|
-
const normalizedKey = bucketKey(parsed.source, parsed.model, hourStart);
|
|
489
|
-
const bucket = hourlyState.buckets ? hourlyState.buckets[normalizedKey] : null;
|
|
490
|
-
if (!bucket || !bucket.totals) continue;
|
|
491
501
|
if (bucket.queuedKey != null && typeof bucket.queuedKey !== 'string') {
|
|
492
502
|
bucket.queuedKey = null;
|
|
493
503
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const codexDominants = collectCodexDominantModels(hourlyState);
|
|
547
|
+
|
|
548
|
+
const toAppend = [];
|
|
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;
|
|
498
661
|
toAppend.push(
|
|
499
662
|
JSON.stringify({
|
|
500
|
-
source,
|
|
501
|
-
model,
|
|
502
|
-
hour_start: hourStart,
|
|
503
|
-
input_tokens:
|
|
504
|
-
cached_input_tokens:
|
|
505
|
-
output_tokens:
|
|
506
|
-
reasoning_output_tokens:
|
|
507
|
-
total_tokens:
|
|
663
|
+
source: group.source,
|
|
664
|
+
model: outputModel,
|
|
665
|
+
hour_start: group.hourStart,
|
|
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
|
|
508
671
|
})
|
|
509
672
|
);
|
|
510
|
-
|
|
673
|
+
unknownBucket.queuedKey = outputKey;
|
|
511
674
|
}
|
|
512
675
|
|
|
513
676
|
if (legacyGroups.size > 0) {
|
|
@@ -564,6 +727,91 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
564
727
|
return toAppend.length;
|
|
565
728
|
}
|
|
566
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
|
+
|
|
567
815
|
function normalizeHourlyState(raw) {
|
|
568
816
|
const state = raw && typeof raw === 'object' ? raw : {};
|
|
569
817
|
const version = Number(state.version || 1);
|