Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Sources/CodexBarCore/Providers/MiMo/MiMoUsageSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,15 @@ extension MiMoUsageSnapshot {
let usedText = Self.fullCountString(self.tokenUsed)
let limitText = Self.fullCountString(self.tokenLimit)
let resetDesc = "\(usedText) / \(limitText) Credits"
// Populate windowMinutes so the plan-utilization history + pace forecast engine
// (which requires a windowed RateWindow) treats the monthly plan quota like the
// Codex/Claude windows. Only when there's a real period end to reset against.
let windowMinutes = self.planPeriodEnd == nil
? nil
: ProviderPaceCapability.monthlyWindowSentinelMinutes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route monthly windows through history and card pace

Setting the sentinel here (and in the analogous StepFun change) still does not enable either advertised card feature: UsageStore.planUtilizationSeriesSamples routes generic providers only through exact 300/10080-minute session-equivalent windows, so a lone 43200-minute primary produces no history samples, while both provider descriptors retain the default .unsupported pace capability, causing resetWindowPaceDetail to return nil. Add explicit monthly sampling and .calendarMonthResetWindow capabilities for MiMo and StepFun, with a model-seam test that verifies the history and pace output rather than only the snapshot field.

AGENTS.md reference: AGENTS.md:L25-L27

Useful? React with 👍 / 👎.

return RateWindow(
usedPercent: usedPercent,
windowMinutes: nil,
windowMinutes: windowMinutes,
resetsAt: self.planPeriodEnd,
resetDescription: resetDesc)
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,15 @@ public struct StepFunUsageSnapshot: Sendable {
let creditUsedPercent = max(0, min(100, (1.0 - creditRate) * 100))
let resetDate = self.creditResetTime ?? Date.distantFuture
let resetDescription = UsageFormatter.resetDescription(from: resetDate)
// Populate windowMinutes so the monthly credit pool feeds the plan-utilization
// history + pace forecast, matching the Codex/Claude windows. Only when the credit
// pool has a real monthly reset (otherwise the pace projection is meaningless).
let creditWindowMinutes = self.creditResetTime == nil
? nil
: ProviderPaceCapability.monthlyWindowSentinelMinutes
Comment on lines +269 to +271

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject zero StepFun reset timestamps

When a credit-plan response contains "subscription_credit_reset_time": "0" (the API's existing representation for an unconfigured reset), parsing produces a non-nil Date(timeIntervalSince1970: 0), so this nil check incorrectly marks it as a real monthly window. Validate that the timestamp/reset date is positive and meaningful before assigning the monthly sentinel; otherwise downstream consumers receive a fabricated 30-day window anchored in 1970.

Useful? React with 👍 / 👎.

let creditWindow = RateWindow(
usedPercent: creditUsedPercent,
windowMinutes: nil,
windowMinutes: creditWindowMinutes,
resetsAt: resetDate,
resetDescription: resetDescription)

Expand Down
30 changes: 30 additions & 0 deletions Tests/CodexBarTests/MiMoWindowMinutesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation
import Testing
@testable import CodexBarCore

struct MiMoWindowMinutesTests {
@Test
func `monthly token quota carries a monthly window for pace history`() {
// With a real period end, the monthly quota window carries a monthly windowMinutes so it
// feeds the plan-utilization history + pace forecast (parity with Codex/Claude). Without a
// period end there is nothing to reset against, so no window is claimed.
let withPeriod = MiMoUsageSnapshot(
balance: 0,
currency: "USD",
planPeriodEnd: Date(timeIntervalSince1970: 1_742_771_200),
tokenUsed: 10,
tokenLimit: 100,
tokenPercent: 0.1,
updatedAt: Date(timeIntervalSince1970: 1_742_000_000))
#expect(withPeriod.toUsageSnapshot().primary?.windowMinutes == 30 * 24 * 60)

let noPeriod = MiMoUsageSnapshot(
balance: 0,
currency: "USD",
tokenUsed: 10,
tokenLimit: 100,
tokenPercent: 0.1,
updatedAt: Date(timeIntervalSince1970: 1_742_000_000))
#expect(noPeriod.toUsageSnapshot().primary?.windowMinutes == nil)
}
}
4 changes: 4 additions & 0 deletions Tests/CodexBarTests/StepFunUsageFetcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ struct StepFunUsageFetcherParsingTests {
let primaryUsed = usage.primary?.usedPercent ?? -1
#expect(primaryUsed > 3.5 && primaryUsed < 3.7)

// The monthly credit pool (with a real reset time) carries a monthly windowMinutes so
// it feeds the plan-utilization history + pace forecast (parity with Codex/Claude).
#expect(usage.primary?.windowMinutes == 30 * 24 * 60)

// No secondary window for credit plans.
#expect(usage.secondary == nil)
}
Expand Down