From 69a5550b34995f986d171fe4e0279fa9490539fd Mon Sep 17 00:00:00 2001 From: Jiacheng Jiang Date: Tue, 17 Mar 2026 10:29:09 +0800 Subject: [PATCH 1/3] Fix Kimi window ordering: prioritize rate limit over weekly quota This change restores the original intent from PR #310 by @ajaxjiang96, which was partially reverted in PR #390 (commit ad6c751). PR #390 took a view-layer approach that required special-case logic in StatusItemController and UsageStore to pick the secondary window for Kimi in automatic mode. This created architectural inconsistency: - Claude: primary=5h session, secondary=weekly - Kimi: primary=weekly, secondary=5h rate limit (but automatic mode picked secondary) This fix takes a model-layer approach that aligns Kimi with Claude: 1. Swaps the primary/secondary mapping in KimiUsageSnapshot: - When rate limit exists: primary=rate limit (5h), secondary=weekly - When no rate limit: primary=weekly, secondary=nil 2. Updates menu labels to match: sessionLabel="Rate Limit", weeklyLabel="Weekly" 3. Removes the now-unnecessary .kimi special-case from automatic mode selection in both StatusItemController and UsageStore+HighestUsage 4. Updates tests to reflect new window ordering: - KimiProviderTests.swift: Updated window assertions - StatusItemAnimationTests.swift: Swapped test data values - UsageStoreHighestUsageTests.swift: Swapped test data values - UsageStoreCoverageTests.swift: Swapped test data values Result: Kimi now displays the 5-hour rate limit first in the menu (matching Claude's display order), and the menubar icon shows the rate limit percentage. --- .../Providers/Kimi/KimiProviderDescriptor.swift | 4 ++-- .../Providers/Kimi/KimiUsageSnapshot.swift | 4 ++-- Tests/CodexBarTests/KimiProviderTests.swift | 16 ++++++++-------- .../CodexBarTests/StatusItemAnimationTests.swift | 4 ++-- .../CodexBarTests/UsageStoreCoverageTests.swift | 6 ++++-- .../UsageStoreHighestUsageTests.swift | 6 ++++-- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift b/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift index 711c20bc8..bd7392baf 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiProviderDescriptor.swift @@ -10,8 +10,8 @@ public enum KimiProviderDescriptor { metadata: ProviderMetadata( id: .kimi, displayName: "Kimi", - sessionLabel: "Weekly", - weeklyLabel: "Rate Limit", + sessionLabel: "Rate Limit", + weeklyLabel: "Weekly", opusLabel: nil, supportsOpus: false, supportsCredits: false, diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift index d19ff420d..ac9feafc9 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift @@ -73,8 +73,8 @@ extension KimiUsageSnapshot { loginMethod: nil) return UsageSnapshot( - primary: weeklyWindow, - secondary: rateLimitWindow, + primary: rateLimitWindow ?? weeklyWindow, + secondary: rateLimitWindow != nil ? weeklyWindow : nil, tertiary: nil, providerCost: nil, updatedAt: self.updatedAt, diff --git a/Tests/CodexBarTests/KimiProviderTests.swift b/Tests/CodexBarTests/KimiProviderTests.swift index bcac5c943..8eca28aa3 100644 --- a/Tests/CodexBarTests/KimiProviderTests.swift +++ b/Tests/CodexBarTests/KimiProviderTests.swift @@ -191,16 +191,16 @@ struct KimiUsageSnapshotConversionTests { let usageSnapshot = snapshot.toUsageSnapshot() #expect(usageSnapshot.primary != nil) - let weeklyExpected = 375.0 / 2048.0 * 100.0 - #expect(abs((usageSnapshot.primary?.usedPercent ?? 0.0) - weeklyExpected) < 0.01) - #expect(usageSnapshot.primary?.resetDescription == "375/2048 requests") - #expect(usageSnapshot.primary?.windowMinutes == nil) + let rateExpected = 200.0 / 200.0 * 100.0 + #expect(abs((usageSnapshot.primary?.usedPercent ?? 0.0) - rateExpected) < 0.01) + #expect(usageSnapshot.primary?.windowMinutes == 300) // 5 hours + #expect(usageSnapshot.primary?.resetDescription == "Rate: 200/200 per 5 hours") #expect(usageSnapshot.secondary != nil) - let rateExpected = 200.0 / 200.0 * 100.0 - #expect(abs((usageSnapshot.secondary?.usedPercent ?? 0.0) - rateExpected) < 0.01) - #expect(usageSnapshot.secondary?.windowMinutes == 300) // 5 hours - #expect(usageSnapshot.secondary?.resetDescription == "Rate: 200/200 per 5 hours") + let weeklyExpected = 375.0 / 2048.0 * 100.0 + #expect(abs((usageSnapshot.secondary?.usedPercent ?? 0.0) - weeklyExpected) < 0.01) + #expect(usageSnapshot.secondary?.resetDescription == "375/2048 requests") + #expect(usageSnapshot.secondary?.windowMinutes == nil) #expect(usageSnapshot.tertiary == nil) #expect(usageSnapshot.updatedAt == now) diff --git a/Tests/CodexBarTests/StatusItemAnimationTests.swift b/Tests/CodexBarTests/StatusItemAnimationTests.swift index f29ada71b..9eb118a9d 100644 --- a/Tests/CodexBarTests/StatusItemAnimationTests.swift +++ b/Tests/CodexBarTests/StatusItemAnimationTests.swift @@ -308,8 +308,8 @@ struct StatusItemAnimationTests { statusBar: self.makeStatusBarForTesting()) let snapshot = UsageSnapshot( - primary: RateWindow(usedPercent: 12, windowMinutes: nil, resetsAt: nil, resetDescription: nil), - secondary: RateWindow(usedPercent: 42, windowMinutes: 300, resetsAt: nil, resetDescription: nil), + primary: RateWindow(usedPercent: 42, windowMinutes: 300, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 12, windowMinutes: nil, resetsAt: nil, resetDescription: nil), updatedAt: Date()) store._setSnapshotForTesting(snapshot, provider: .kimi) diff --git a/Tests/CodexBarTests/UsageStoreCoverageTests.swift b/Tests/CodexBarTests/UsageStoreCoverageTests.swift index 67b0a323a..04824bf98 100644 --- a/Tests/CodexBarTests/UsageStoreCoverageTests.swift +++ b/Tests/CodexBarTests/UsageStoreCoverageTests.swift @@ -95,10 +95,12 @@ struct UsageStoreCoverageTests { secondary: nil, updatedAt: now), provider: .codex) + // With the fix, Kimi's primary is now rate limit and secondary is weekly. + // In automatic mode, Kimi uses primary like other providers. store._setSnapshotForTesting( UsageSnapshot( - primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil), - secondary: RateWindow(usedPercent: 80, windowMinutes: 300, resetsAt: nil, resetDescription: nil), + primary: RateWindow(usedPercent: 80, windowMinutes: 300, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil), updatedAt: now), provider: .kimi) diff --git a/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift b/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift index 190c00892..7dd35185d 100644 --- a/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift +++ b/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift @@ -104,9 +104,11 @@ struct UsageStoreHighestUsageTests { primary: RateWindow(usedPercent: 70, windowMinutes: nil, resetsAt: nil, resetDescription: nil), secondary: nil, updatedAt: Date()) + // With the fix, Kimi's primary is now rate limit (typically lower usage %) + // and secondary is weekly. In automatic mode, Kimi uses primary like other providers. let kimiSnapshot = UsageSnapshot( - primary: RateWindow(usedPercent: 90, windowMinutes: nil, resetsAt: nil, resetDescription: nil), - secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + primary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 90, windowMinutes: nil, resetsAt: nil, resetDescription: nil), updatedAt: Date()) store._setSnapshotForTesting(codexSnapshot, provider: .codex) From 22be124b37f6757ad580d4f58be88b62de89afa9 Mon Sep 17 00:00:00 2001 From: Jiacheng Jiang Date: Wed, 25 Mar 2026 17:40:50 +0800 Subject: [PATCH 2/3] Refactor Kimi usage snapshot to prioritize rate limit as primary window --- .../Providers/Kimi/KimiUsageSnapshot.swift | 4 ++-- Tests/CodexBarTests/KimiProviderTests.swift | 10 +++++----- Tests/CodexBarTests/UsageStoreHighestUsageTests.swift | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift index ac9feafc9..54c53c160 100644 --- a/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift +++ b/Sources/CodexBarCore/Providers/Kimi/KimiUsageSnapshot.swift @@ -73,8 +73,8 @@ extension KimiUsageSnapshot { loginMethod: nil) return UsageSnapshot( - primary: rateLimitWindow ?? weeklyWindow, - secondary: rateLimitWindow != nil ? weeklyWindow : nil, + primary: rateLimitWindow, + secondary: weeklyWindow, tertiary: nil, providerCost: nil, updatedAt: self.updatedAt, diff --git a/Tests/CodexBarTests/KimiProviderTests.swift b/Tests/CodexBarTests/KimiProviderTests.swift index 8eca28aa3..6d9592e8c 100644 --- a/Tests/CodexBarTests/KimiProviderTests.swift +++ b/Tests/CodexBarTests/KimiProviderTests.swift @@ -222,10 +222,10 @@ struct KimiUsageSnapshotConversionTests { let usageSnapshot = snapshot.toUsageSnapshot() - #expect(usageSnapshot.primary != nil) + #expect(usageSnapshot.primary == nil) + #expect(usageSnapshot.secondary != nil) let weeklyExpected = 375.0 / 2048.0 * 100.0 - #expect(abs((usageSnapshot.primary?.usedPercent ?? 0.0) - weeklyExpected) < 0.01) - #expect(usageSnapshot.secondary == nil) + #expect(abs((usageSnapshot.secondary?.usedPercent ?? 0.0) - weeklyExpected) < 0.01) #expect(usageSnapshot.tertiary == nil) } @@ -244,7 +244,7 @@ struct KimiUsageSnapshotConversionTests { updatedAt: now) let usageSnapshot = snapshot.toUsageSnapshot() - #expect(usageSnapshot.primary?.usedPercent == 0.0) + #expect(usageSnapshot.secondary?.usedPercent == 0.0) } @Test @@ -262,7 +262,7 @@ struct KimiUsageSnapshotConversionTests { updatedAt: now) let usageSnapshot = snapshot.toUsageSnapshot() - #expect(usageSnapshot.primary?.usedPercent == 100.0) + #expect(usageSnapshot.secondary?.usedPercent == 100.0) } } diff --git a/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift b/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift index 7dd35185d..b5dd8f3ec 100644 --- a/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift +++ b/Tests/CodexBarTests/UsageStoreHighestUsageTests.swift @@ -80,7 +80,7 @@ struct UsageStoreHighestUsageTests { } @Test - func `automatic metric uses secondary for kimi when ranking highest usage`() { + func `automatic metric uses primary for kimi when ranking highest usage`() { let settings = SettingsStore( configStore: testConfigStore(suiteName: "UsageStoreHighestUsageTests-kimi-automatic"), zaiTokenStore: NoopZaiTokenStore(), From f3a40b3662a89b741035032fc98ffb3548740f6f Mon Sep 17 00:00:00 2001 From: Jiacheng Jiang Date: Wed, 25 Mar 2026 22:11:12 +0800 Subject: [PATCH 3/3] Align MenuBarMetricWindowResolver with Kimi model-layer fix to resolve CI test failures --- Sources/CodexBar/MenuBarMetricWindowResolver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CodexBar/MenuBarMetricWindowResolver.swift b/Sources/CodexBar/MenuBarMetricWindowResolver.swift index a154be9cf..9c77e0646 100644 --- a/Sources/CodexBar/MenuBarMetricWindowResolver.swift +++ b/Sources/CodexBar/MenuBarMetricWindowResolver.swift @@ -45,7 +45,7 @@ enum MenuBarMetricWindowResolver { if provider == .antigravity { return snapshot.primary ?? snapshot.secondary ?? snapshot.tertiary } - if provider == .factory || provider == .kimi { + if provider == .factory { return snapshot.secondary ?? snapshot.primary } if provider == .copilot,