agent-orchestrator-kit 0.13.0 → 0.14.0

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.
@@ -303,11 +303,13 @@ function dedupeCursorSources(sources) {
303
303
  }
304
304
 
305
305
  function leftoverWindowEnd(metrics, last) {
306
- if (metrics.pending && metrics.pending.startedAt) return metrics.pending.startedAt;
307
306
  if (!last || !last.endedAt) return null;
308
307
  const end = Date.parse(last.endedAt);
309
308
  if (!Number.isFinite(end)) return null;
310
- return new Date(end + CURSOR_LEFTOVER_GRACE_MS).toISOString();
309
+ const grace = new Date(end + CURSOR_LEFTOVER_GRACE_MS).toISOString();
310
+ const pending = metrics.pending && metrics.pending.startedAt;
311
+ if (!pending || last.threadId) return pending || grace;
312
+ return Date.parse(pending) < Date.parse(grace) ? pending : grace;
311
313
  }
312
314
 
313
315
  function leftoverEndExclusive(metrics) {
@@ -317,9 +319,7 @@ function leftoverEndExclusive(metrics) {
317
319
  function existingIds(metrics) {
318
320
  const ids = new Set();
319
321
  for (const session of metrics.sessions || []) {
320
- for (const src of session.sources || []) {
321
- if (src && src.id != null && src.id !== '') ids.add(String(src.id));
322
- }
322
+ for (const id of session.sourceIds || []) ids.add(String(id));
323
323
  }
324
324
  return ids;
325
325
  }
@@ -417,7 +417,7 @@ function sourceTotals(sources) {
417
417
  }
418
418
 
419
419
  function looksOverridden(session) {
420
- const fromSources = sourceTotals(session.sources || []);
420
+ const fromSources = sourceTotals(session.byModel || []);
421
421
  return ['inputTokens', 'outputTokens', 'totalTokens', 'costUsd'].some((key) => {
422
422
  const sessionVal = numOrNull(session[key]);
423
423
  const sourceVal = numOrNull(fromSources[key]);
@@ -489,13 +489,7 @@ function recompute(metrics) {
489
489
  phase.endedAt = session.endedAt;
490
490
  }
491
491
  for (const spendKey of ['inputTokens', 'outputTokens', 'totalTokens', 'costUsd', 'costUsdEstimated']) {
492
- const fromSession = numOrNull(session[spendKey]);
493
- let value = fromSession;
494
- if (value == null) {
495
- let sum = null;
496
- for (const src of session.sources || []) sum = addNullable(sum, numOrNull(src[spendKey]));
497
- value = sum;
498
- }
492
+ const value = numOrNull(session[spendKey]);
499
493
  phase[spendKey] = addNullable(phase[spendKey], value);
500
494
  spend[spendKey] = addNullable(spend[spendKey], value);
501
495
  }
@@ -508,27 +502,21 @@ function recompute(metrics) {
508
502
  }
509
503
  phases[key] = phase;
510
504
 
511
- let sessionSourceCostUsd = null;
512
- for (const src of session.sources || []) {
513
- sessionSourceCostUsd = addNullable(sessionSourceCostUsd, numOrNull(src.costUsd));
514
- const platform = src.platform;
515
- if (platform && byPlatform[platform]) {
516
- const bucket = byPlatform[platform];
517
- bucket.inputTokens = addNullable(bucket.inputTokens, numOrNull(src.inputTokens));
518
- bucket.outputTokens = addNullable(bucket.outputTokens, numOrNull(src.outputTokens));
519
- bucket.totalTokens = addNullable(bucket.totalTokens, numOrNull(src.totalTokens));
520
- bucket.costUsd = addNullable(bucket.costUsd, numOrNull(src.costUsd));
521
- bucket.ampCredits = addNullable(bucket.ampCredits, numOrNull(src.ampCredits));
522
- bucket.costUsdEstimated = addNullable(bucket.costUsdEstimated, numOrNull(src.costUsdEstimated));
523
- if (platform === 'claude') bucket.source = 'claude-jsonl';
524
- else if (platform === 'amp') bucket.source = 'amp-thread';
525
- else if (platform === 'cursor') bucket.source = 'cursor-hook';
505
+ const platform = session.platform;
506
+ if (platform && byPlatform[platform]) {
507
+ const bucket = byPlatform[platform];
508
+ for (const key of ['inputTokens', 'outputTokens', 'totalTokens', 'costUsd', 'ampCredits', 'costUsdEstimated']) {
509
+ bucket[key] = addNullable(bucket[key], numOrNull(session[key]));
526
510
  }
511
+ if ((session.sourceIds || []).length) bucket.source = platform === 'cursor' ? 'cursor-hook' : `${platform}-jsonl`;
512
+ }
513
+ const modelRows = (session.byModel || []).length ? session.byModel : [session];
514
+ for (const src of modelRows) {
527
515
  if (src.model) {
528
- const modelKey = `${src.model}::${src.platform || ''}`;
516
+ const modelKey = `${src.model}::${src.platform || session.platform || ''}`;
529
517
  const row = byModel.get(modelKey) || {
530
518
  model: src.model,
531
- platform: src.platform || null,
519
+ platform: src.platform || session.platform || null,
532
520
  inputTokens: null,
533
521
  outputTokens: null,
534
522
  totalTokens: null,
@@ -545,11 +533,6 @@ function recompute(metrics) {
545
533
  byModel.set(modelKey, row);
546
534
  }
547
535
  }
548
- const sessionCostUsd = numOrNull(session.costUsd);
549
- if (sessionSourceCostUsd == null && sessionCostUsd != null) {
550
- const billedBucket = session.platform && byPlatform[session.platform];
551
- if (billedBucket) billedBucket.costUsd = addNullable(billedBucket.costUsd, sessionCostUsd);
552
- }
553
536
  }
554
537
 
555
538
  if (firstStart && lastEnd) {
@@ -583,7 +566,6 @@ function incomingCursorSources(cwd, existing, fingerprints, windowStart, windowE
583
566
  const filterId = String(filterConversationId || '').trim();
584
567
  const bestById = new Map();
585
568
  for (const [id, row] of byId) {
586
- if (existing.has(id)) continue;
587
569
  if (filterId) {
588
570
  const rowConversationId = row.conversationId == null || row.conversationId === ''
589
571
  ? ''
@@ -631,14 +613,7 @@ function incomingCursorSources(cwd, existing, fingerprints, windowStart, windowE
631
613
  }
632
614
 
633
615
  function existingFingerprints(metrics) {
634
- const set = new Set();
635
- for (const session of metrics.sessions || []) {
636
- for (const src of session.sources || []) {
637
- const fp = cursorSpendFingerprint(src);
638
- if (fp) set.add(fp);
639
- }
640
- }
641
- return set;
616
+ return new Set();
642
617
  }
643
618
 
644
619
  function sessionHasSpendNumbers(session) {
@@ -661,7 +636,7 @@ function sessionSpendFrozen(session) {
661
636
 
662
637
  function syncAdapterSessionTotals(session) {
663
638
  if (sessionSpendFrozen(session)) return false;
664
- const totals = sourceTotals(session.sources || []);
639
+ const totals = sourceTotals(session.byModel || []);
665
640
  let changed = false;
666
641
  for (const key of ['inputTokens', 'outputTokens', 'totalTokens', 'costUsd', 'costUsdEstimated']) {
667
642
  if (session[key] !== totals[key]) {
@@ -669,7 +644,7 @@ function syncAdapterSessionTotals(session) {
669
644
  changed = true;
670
645
  }
671
646
  }
672
- if ((session.sources || []).length > 0 && session.spendSource !== 'adapter') {
647
+ if ((session.sourceIds || []).length > 0 && session.spendSource !== 'adapter') {
673
648
  session.spendSource = 'adapter';
674
649
  changed = true;
675
650
  }
@@ -677,21 +652,12 @@ function syncAdapterSessionTotals(session) {
677
652
  }
678
653
 
679
654
  function enrichMetrics(metrics, cwd) {
680
- const byId = loadCursorUsageById(cwd);
681
655
  let changed = false;
682
656
  for (const session of metrics.sessions || []) {
683
- const deduped = dedupeCursorSources(session.sources || []);
684
- if (deduped.length !== (session.sources || []).length) {
685
- session.sources = deduped;
686
- changed = true;
687
- } else {
688
- session.sources = deduped;
689
- }
690
- if (attachCursorEstimates(session.sources || [], byId)) changed = true;
691
657
  if (syncAdapterSessionTotals(session)) changed = true;
692
658
  if (
693
659
  session.spendSource === 'unreported'
694
- && (session.inputTokens != null || session.totalTokens != null || (session.sources || []).length)
660
+ && (session.inputTokens != null || session.totalTokens != null || (session.sourceIds || []).length)
695
661
  ) {
696
662
  session.spendSource = 'adapter';
697
663
  changed = true;
@@ -700,6 +666,101 @@ function enrichMetrics(metrics, cwd) {
700
666
  return changed;
701
667
  }
702
668
 
669
+ function compactModelRows(sources) {
670
+ const rows = new Map();
671
+ for (const source of sources || []) {
672
+ if (!source) continue;
673
+ const model = source.model == null || source.model === '' ? null : String(source.model);
674
+ const key = `${source.platform || ''}::${model || ''}`;
675
+ const row = rows.get(key) || {
676
+ model,
677
+ platform: source.platform || null,
678
+ inputTokens: null,
679
+ outputTokens: null,
680
+ totalTokens: null,
681
+ costUsd: null,
682
+ costUsdEstimated: null,
683
+ };
684
+ for (const field of ['inputTokens', 'outputTokens', 'totalTokens', 'costUsd']) {
685
+ row[field] = addNullable(row[field], numOrNull(source[field]));
686
+ }
687
+ row.costUsdEstimated = roundUsd4(addNullable(row.costUsdEstimated, numOrNull(source.costUsdEstimated)));
688
+ if (source.costSource) row.costSource = source.costSource;
689
+ rows.set(key, row);
690
+ }
691
+ return [...rows.values()];
692
+ }
693
+
694
+ function mergeModelRows(current, incoming) {
695
+ return compactModelRows([...(current || []), ...(incoming || [])]);
696
+ }
697
+
698
+ function normalizeMetricsV2(metrics) {
699
+ metrics.version = 2;
700
+ metrics.sessions = Array.isArray(metrics.sessions) ? metrics.sessions : [];
701
+ for (const session of metrics.sessions) {
702
+ if (Array.isArray(session.sources)) {
703
+ const bestById = new Map();
704
+ for (const source of session.sources) {
705
+ if (!source || source.id == null || source.id === '') continue;
706
+ const id = String(source.id);
707
+ const previous = bestById.get(id);
708
+ if (!previous || (numOrNull(source.totalTokens) ?? 0) >= (numOrNull(previous.totalTokens) ?? 0)) bestById.set(id, source);
709
+ }
710
+ const compactSources = [...bestById.values()].map((source) => ({
711
+ ...source,
712
+ model: source.model || session.model || null,
713
+ platform: source.platform || session.platform || null,
714
+ }));
715
+ session.sourceIds = [];
716
+ session.sourceTotals = {};
717
+ for (const source of compactSources) {
718
+ if (!source || source.id == null || source.id === '') continue;
719
+ const id = String(source.id);
720
+ if (!session.sourceIds.includes(id)) session.sourceIds.push(id);
721
+ const total = numOrNull(source.totalTokens);
722
+ if (total != null) session.sourceTotals[id] = Math.max(numOrNull(session.sourceTotals[id]) ?? 0, total);
723
+ }
724
+ session.byModel = compactModelRows(compactSources);
725
+ if (!session.platform && compactSources[0] && compactSources[0].platform) session.platform = compactSources[0].platform;
726
+ if (!session.model && compactSources[0] && compactSources[0].model) session.model = compactSources[0].model;
727
+ delete session.sources;
728
+ } else {
729
+ session.sourceIds = Array.isArray(session.sourceIds) ? [...new Set(session.sourceIds.map(String))] : [];
730
+ session.sourceTotals = session.sourceTotals && typeof session.sourceTotals === 'object' ? session.sourceTotals : {};
731
+ session.byModel = Array.isArray(session.byModel) ? session.byModel : [];
732
+ }
733
+ }
734
+ return metrics;
735
+ }
736
+
737
+ function mergeIncomingCompact(session, incoming) {
738
+ session.sourceIds = Array.isArray(session.sourceIds) ? session.sourceIds : [];
739
+ session.sourceTotals = session.sourceTotals && typeof session.sourceTotals === 'object' ? session.sourceTotals : {};
740
+ const deltaSources = [];
741
+ for (const source of incoming || []) {
742
+ const id = String(source.id);
743
+ const previous = numOrNull(session.sourceTotals[id]);
744
+ const next = numOrNull(source.totalTokens);
745
+ if (!session.sourceIds.includes(id)) session.sourceIds.push(id);
746
+ if (next != null) session.sourceTotals[id] = Math.max(previous ?? 0, next);
747
+ if (previous == null || next == null) {
748
+ deltaSources.push(source);
749
+ continue;
750
+ }
751
+ const delta = Math.max(0, next - previous);
752
+ if (!delta) continue;
753
+ const ratio = next > 0 ? delta / next : 0;
754
+ const copy = { ...source };
755
+ for (const field of ['inputTokens', 'outputTokens', 'totalTokens', 'costUsd', 'costUsdEstimated']) {
756
+ const value = numOrNull(copy[field]);
757
+ if (value != null) copy[field] = field.includes('Usd') ? roundUsd4(value * ratio) : Math.round(value * ratio);
758
+ }
759
+ deltaSources.push(copy);
760
+ }
761
+ session.byModel = mergeModelRows(session.byModel, compactModelRows(deltaSources));
762
+ }
763
+
703
764
  function backfillMetricsFile(cwd, filePath) {
704
765
  if (!existsSync(filePath)) return;
705
766
  let metrics;
@@ -709,18 +770,14 @@ function backfillMetricsFile(cwd, filePath) {
709
770
  return;
710
771
  }
711
772
  if (!metrics || typeof metrics !== 'object') return;
773
+ const schemaChanged = metrics.version !== 2 || (metrics.sessions || []).some((session) => Array.isArray(session.sources));
774
+ normalizeMetricsV2(metrics);
712
775
  const sessions = Array.isArray(metrics.sessions) ? metrics.sessions : [];
713
776
  if (!sessions.length) return;
714
- let collapsed = false;
715
- for (const session of sessions) {
716
- const next = dedupeCursorSources(session.sources || []);
717
- if (next.length !== (session.sources || []).length) collapsed = true;
718
- session.sources = next;
719
- }
720
777
  const last = sessions[sessions.length - 1];
721
778
  const byId = loadCursorUsageById(cwd);
722
779
  const leftoverEnd = leftoverWindowEnd(metrics, last);
723
- const incoming = last.endedAt && leftoverEnd
780
+ const incoming = (last.endedAt && leftoverEnd
724
781
  ? incomingCursorSources(
725
782
  cwd,
726
783
  existingIds(metrics),
@@ -731,11 +788,16 @@ function backfillMetricsFile(cwd, filePath) {
731
788
  leftoverEndExclusive(metrics),
732
789
  last.threadId,
733
790
  )
734
- : [];
791
+ : []).filter((source) => {
792
+ const previous = numOrNull(last.sourceTotals && last.sourceTotals[source.id]);
793
+ return previous == null || (numOrNull(source.totalTokens) ?? 0) > previous;
794
+ });
735
795
  if (incoming.length) {
736
- last.sources = [...(last.sources || []), ...incoming];
796
+ mergeIncomingCompact(last, incoming);
797
+ if (!last.platform) last.platform = 'cursor';
798
+ if (!last.model && incoming[0].model) last.model = incoming[0].model;
737
799
  if (!sessionSpendFrozen(last)) {
738
- const totals = sourceTotals(last.sources);
800
+ const totals = sourceTotals(last.byModel);
739
801
  last.inputTokens = totals.inputTokens;
740
802
  last.outputTokens = totals.outputTokens;
741
803
  last.totalTokens = totals.totalTokens;
@@ -745,7 +807,7 @@ function backfillMetricsFile(cwd, filePath) {
745
807
  }
746
808
  }
747
809
  const enriched = enrichMetrics(metrics, cwd);
748
- if (!incoming.length && !enriched && !collapsed) return;
810
+ if (!incoming.length && !enriched && !schemaChanged) return;
749
811
  metrics.updatedAt = new Date().toISOString();
750
812
  recompute(metrics);
751
813
  writeFileSync(filePath, `${JSON.stringify(metrics, null, 2)}\n`);