letmecode 0.1.15 → 0.1.16

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.
@@ -125,7 +125,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
125
125
  warnings.push(`Collapsed ${parsedEvents.duplicateUsageKeys} duplicate Claude usage event(s) by request/message key.`);
126
126
  }
127
127
  if (options.verbose && parsedEvents.duplicateUsageKeyCollisions > 0) {
128
- warnings.push(`Detected ${parsedEvents.duplicateUsageKeyCollisions} Claude usage key collision(s) with different token usage; keeping the highest-cost/latest event per key.`);
128
+ warnings.push(`Detected ${parsedEvents.duplicateUsageKeyCollisions} Claude usage key collision(s) with different token usage; merged same-key rows by per-field maxima to avoid double-counting cumulative snapshots.`);
129
129
  }
130
130
  if (options.verbose && parsedEvents.duplicateUnkeyedEvents > 0) {
131
131
  warnings.push(`Collapsed ${parsedEvents.duplicateUnkeyedEvents} duplicate unkeyed Claude usage event(s) by usage signature.`);
@@ -455,6 +455,7 @@ async function parseSessionFile(filePath, sessionsRoot) {
455
455
  usageSignature,
456
456
  timestampMs: eventTimeMs,
457
457
  modelId,
458
+ usage: normalizedUsage,
458
459
  totals: usageToTotals(modelId, normalizedUsage),
459
460
  rateLimits
460
461
  });
@@ -565,8 +566,11 @@ function buildUsageEventKey(payloadObject, message) {
565
566
  return `${sessionId}|${requestId || messageId}`;
566
567
  }
567
568
  function buildUsageSignature(payloadObject, modelId, usage) {
569
+ return buildUsageSignatureFromParts(String(payloadObject.sessionId ?? ""), modelId, usage);
570
+ }
571
+ function buildUsageSignatureFromParts(sessionId, modelId, usage) {
568
572
  return [
569
- String(payloadObject.sessionId ?? ""),
573
+ sessionId,
570
574
  modelId,
571
575
  usage.inputTokens,
572
576
  usage.cacheCreationInputTokens,
@@ -597,9 +601,7 @@ function recordParsedUsageEvent(parsedEvents, event) {
597
601
  if (previous.usageSignature !== event.usageSignature) {
598
602
  parsedEvents.duplicateUsageKeyCollisions += 1;
599
603
  }
600
- if (shouldReplaceUsageEvent(previous, event)) {
601
- parsedEvents.keyedEvents.set(event.usageKey, event);
602
- }
604
+ parsedEvents.keyedEvents.set(event.usageKey, mergeParsedUsageEvents(previous, event));
603
605
  return;
604
606
  }
605
607
  const previous = parsedEvents.unkeyedEvents.get(event.usageSignature);
@@ -608,18 +610,57 @@ function recordParsedUsageEvent(parsedEvents, event) {
608
610
  return;
609
611
  }
610
612
  parsedEvents.duplicateUnkeyedEvents += 1;
611
- if (shouldReplaceUsageEvent(previous, event)) {
613
+ if (normalizeTimestamp(event.timestampMs) > normalizeTimestamp(previous.timestampMs)) {
612
614
  parsedEvents.unkeyedEvents.set(event.usageSignature, event);
613
615
  }
614
616
  }
615
- function shouldReplaceUsageEvent(previous, next) {
616
- if (next.totals.estimatedCredits > previous.totals.estimatedCredits) {
617
- return true;
617
+ function mergeParsedUsageEvents(previous, next) {
618
+ const mergedUsage = mergeClaudeUsage(previous.usage, next.usage);
619
+ const modelId = selectMergedEventModelId(previous, next);
620
+ const latestEvent = normalizeTimestamp(next.timestampMs) >= normalizeTimestamp(previous.timestampMs) ? next : previous;
621
+ const sessionId = extractUsageKeySessionId(previous.usageKey) || extractUsageKeySessionId(next.usageKey);
622
+ return {
623
+ entrypoint: latestEvent.entrypoint || previous.entrypoint || next.entrypoint,
624
+ usageKey: previous.usageKey ?? next.usageKey,
625
+ usageSignature: buildUsageSignatureFromParts(sessionId, modelId, mergedUsage),
626
+ timestampMs: Math.max(normalizeTimestamp(previous.timestampMs), normalizeTimestamp(next.timestampMs)),
627
+ modelId,
628
+ usage: mergedUsage,
629
+ totals: usageToTotals(modelId, mergedUsage),
630
+ rateLimits: latestEvent.rateLimits ?? previous.rateLimits ?? next.rateLimits
631
+ };
632
+ }
633
+ function mergeClaudeUsage(previous, next) {
634
+ return {
635
+ inputTokens: Math.max(previous.inputTokens, next.inputTokens),
636
+ cacheReadInputTokens: Math.max(previous.cacheReadInputTokens, next.cacheReadInputTokens),
637
+ cacheCreationInputTokens: Math.max(previous.cacheCreationInputTokens, next.cacheCreationInputTokens),
638
+ cacheCreation5mInputTokens: Math.max(previous.cacheCreation5mInputTokens, next.cacheCreation5mInputTokens),
639
+ cacheCreation1hInputTokens: Math.max(previous.cacheCreation1hInputTokens, next.cacheCreation1hInputTokens),
640
+ outputTokens: Math.max(previous.outputTokens, next.outputTokens),
641
+ inferenceGeo: next.inferenceGeo || previous.inferenceGeo
642
+ };
643
+ }
644
+ function selectMergedEventModelId(previous, next) {
645
+ if (previous.modelId === next.modelId) {
646
+ return previous.modelId;
647
+ }
648
+ if (isInternalClaudeModel(previous.modelId) && !isInternalClaudeModel(next.modelId)) {
649
+ return next.modelId;
618
650
  }
619
- if (next.totals.estimatedCredits === previous.totals.estimatedCredits) {
620
- return normalizeTimestamp(next.timestampMs) > normalizeTimestamp(previous.timestampMs);
651
+ if (isInternalClaudeModel(next.modelId) && !isInternalClaudeModel(previous.modelId)) {
652
+ return previous.modelId;
653
+ }
654
+ return next.totals.estimatedCredits >= previous.totals.estimatedCredits
655
+ ? next.modelId
656
+ : previous.modelId;
657
+ }
658
+ function extractUsageKeySessionId(usageKey) {
659
+ if (!usageKey) {
660
+ return "";
621
661
  }
622
- return false;
662
+ const separatorIndex = usageKey.indexOf("|");
663
+ return separatorIndex >= 0 ? usageKey.slice(0, separatorIndex) : usageKey;
623
664
  }
624
665
  function normalizeTimestamp(value) {
625
666
  return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "letmecode",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Provider-based terminal usage dashboard for LetMeCode.",
5
5
  "author": "devforth.io",
6
6
  "license": "MIT",