From 39d86dae22e2522d65154ed65c483428a85be573 Mon Sep 17 00:00:00 2001 From: OfficialAbhinavSingh Date: Sun, 19 Jul 2026 22:04:54 +0530 Subject: [PATCH] fix: clamp z.ai raw-percentage usedPercent fallback to 0...100 ZaiLimitEntry.usedPercent returns computedUsedPercent (already clamped) when quota fields are present, but the fallback returned self.percentage raw. z.ai omits/misreports quota fields, so an out-of-range API percentage flowed unclamped into RateWindow.usedPercent. Clamp the fallback like computedUsedPercent and sibling providers. --- .../Providers/Zai/ZaiUsageStats.swift | 4 +- TestsLinux/ZaiUsedPercentLinuxTests.swift | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 TestsLinux/ZaiUsedPercentLinuxTests.swift diff --git a/Sources/CodexBarCore/Providers/Zai/ZaiUsageStats.swift b/Sources/CodexBarCore/Providers/Zai/ZaiUsageStats.swift index 3443271967..39380095bf 100644 --- a/Sources/CodexBarCore/Providers/Zai/ZaiUsageStats.swift +++ b/Sources/CodexBarCore/Providers/Zai/ZaiUsageStats.swift @@ -84,7 +84,9 @@ extension ZaiLimitEntry { if let computed = self.computedUsedPercent { return computed } - return self.percentage + // The raw API percentage can fall outside 0...100 (z.ai omits/misreports quota fields); + // clamp it like computedUsedPercent and every sibling provider instead of surfacing it raw. + return min(100, max(0, self.percentage)) } public var windowMinutes: Int? { diff --git a/TestsLinux/ZaiUsedPercentLinuxTests.swift b/TestsLinux/ZaiUsedPercentLinuxTests.swift new file mode 100644 index 0000000000..481a475a31 --- /dev/null +++ b/TestsLinux/ZaiUsedPercentLinuxTests.swift @@ -0,0 +1,52 @@ +#if os(Linux) +import CodexBarCore +import Foundation +import Testing + +struct ZaiUsedPercentLinuxTests { + /// usage == nil forces computedUsedPercent to return nil, exercising the raw-percentage fallback. + private func fallbackUsedPercent(_ percentage: Double) -> Double { + ZaiLimitEntry( + type: .tokensLimit, + unit: .hours, + number: 5, + usage: nil, + currentValue: nil, + remaining: nil, + percentage: percentage, + usageDetails: [], + nextResetTime: nil).usedPercent + } + + @Test + func `raw percentage fallback clamps above 100`() { + #expect(self.fallbackUsedPercent(150) == 100) + } + + @Test + func `raw percentage fallback clamps below 0`() { + #expect(self.fallbackUsedPercent(-5) == 0) + } + + @Test + func `raw percentage fallback preserves an in-range value`() { + #expect(self.fallbackUsedPercent(42) == 42) + } + + @Test + func `computed path takes precedence and ignores the raw percentage`() { + // usage(limit)=100, currentValue(used)=25 -> computed 25%, so the raw 999 must not leak. + let entry = ZaiLimitEntry( + type: .tokensLimit, + unit: .hours, + number: 5, + usage: 100, + currentValue: 25, + remaining: nil, + percentage: 999, + usageDetails: [], + nextResetTime: nil) + #expect(entry.usedPercent == 25) + } +} +#endif