Skip to content
Merged
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

## 0.49.2 — Unreleased

### Fixed
- Sub2API: localize and group menu-card quota labels and request, token, and cost totals into a compact usage summary (#2835). Thanks @weirdo-adam!
- Codex: avoid repeatedly converting historical token snapshots during cost-cache refreshes, preventing sustained CPU usage on large session histories.

## 0.49.1 — 2026-08-09

### Fixed
- Codex: avoid repeatedly converting historical token snapshots during cost-cache refreshes, preventing sustained CPU usage on large session histories.
- DeepInfra: show billing-cycle spend against a positive spending limit in the automatic menu-bar icon (#2822). Thanks @selfagency for the report!
- z.ai: restore pace for verified 5-hour, weekly, and MCP-monthly usage windows without treating rolling 30-day limits as calendar months (#2431). Thanks @kiranmagic7!
- Codex: publish refreshed core quota immediately while optional Credits and OpenAI Web enrichment continues, without unfreezing cards whose layout still needs reconciliation (#2799). Thanks @Yuxin-Qiao!
Expand Down
50 changes: 49 additions & 1 deletion Sources/CodexBar/MenuCardView+ModelHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -471,16 +471,64 @@ extension UsageMenuCardView.Model {
AmpProviderDescriptor.secondaryLabel(snapshot: snapshot) ?? input.metadata.weeklyLabel
} else if input.provider == .alibabatokenplan {
AlibabaTokenPlanProviderDescriptor.secondaryLabel(window: snapshot.secondary) ?? input.metadata.weeklyLabel
} else if input.provider == .sub2api {
"Weekly"
Comment on lines +474 to +475

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 Update the provider gatekeeper fingerprint

Adding this .sub2api branch (together with the new .sub2api ternary just below) changes the exact provider-specific reference cluster in MenuCardView+ModelHelpers.swift, but ProviderArchitectureGatekeeperTests still allowlists the rateWindowLabels cluster with the old reference count/fingerprint. In CI, that drift tripwire will report the cluster as changed until the allowlist is updated alongside these new provider-specific references.

Useful? React with 👍 / 👎.

} else {
input.metadata.weeklyLabel
}
let tertiaryLabel = input.provider == .sub2api
? L("Monthly")
: input.metadata.opusLabel.map(L) ?? L("Sonnet")
return (
L(primaryLabel),
L(secondaryLabel),
input.metadata.opusLabel.map(L) ?? L("Sonnet"),
tertiaryLabel,
input.metadata.supportsOpus)
}

static func sub2APILocalizedDetails(_ details: [ProviderDetailSection]) -> [ProviderDetailSection] {
details.map { section in
guard section.title == "Usage summary" else { return section }

do {
var rows: [ProviderDetailSection.Row] = []
var consumedLabels: Set<String> = []

if let balance = section.rows.first(where: { $0.label == "Balance" }) {
try rows.append(ProviderDetailSection.Row(
label: L("Balance"),
value: balance.value,
secondaryValue: balance.secondaryValue))
consumedLabels.insert(balance.label)
}

for period in [
(label: "Today", requests: "Today requests", tokens: "Today tokens"),
(label: "Total", requests: "All time requests", tokens: "All time tokens"),
] {
guard let requests = section.rows.first(where: { $0.label == period.requests }),
let tokens = section.rows.first(where: { $0.label == period.tokens })
else { continue }

var secondaryParts = ["\(tokens.value) \(L("tokens"))"]
if let cost = tokens.secondaryValue {
secondaryParts.append("\(L("Cost")): \(cost)")
}
try rows.append(ProviderDetailSection.Row(
label: L(period.label),
value: "\(requests.value) \(L("requests"))",
secondaryValue: secondaryParts.joined(separator: " · ")))
consumedLabels.formUnion([period.requests, period.tokens])
}

rows.append(contentsOf: section.rows.filter { !consumedLabels.contains($0.label) })
return try ProviderDetailSection(title: L("Usage"), rows: rows, chart: section.chart)
} catch {
return section
}
}
}

static func resetText(
for window: RateWindow,
style: ResetTimeDisplayStyle,
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,9 @@ extension UsageMenuCardView.Model {
}
}
}
if input.provider == .sub2api {
details = Self.sub2APILocalizedDetails(details)
}
guard input.hidePersonalInfo else { return details }
return details.compactMap { section in
let rows = section.rows.compactMap { row in
Expand Down
44 changes: 27 additions & 17 deletions Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1838,7 +1838,7 @@ struct ProviderArchitectureGatekeeperTests {
line: 449,
anchor: "if input.provider == .factory, snapshot.tertiary != nil {",
expectedProviderIDs: ["alibabatokenplan", "amp", "crof", "cursor", "doubao", "factory", "grok", "sub2api"],
expectedReferenceCount: 10,
expectedReferenceCount: 12,
expectedReferenceFingerprint: [
"factory@0",
"cursor@4",
Expand All @@ -1850,51 +1850,53 @@ struct ProviderArchitectureGatekeeperTests {
"alibabatokenplan@16",
"amp@21",
"alibabatokenplan@23",
"sub2api@25",
"sub2api@30",
],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView+ModelHelpers.swift",
line: 562,
line: 610,
anchor: "case .minimax:",
expectedProviderIDs: ["codex", "minimax", "poe"],
expectedReferenceCount: 3,
expectedReferenceFingerprint: ["minimax@0", "poe@8", "codex@13"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView+ModelHelpers.swift",
line: 763,
line: 811,
anchor: "if input.provider == .codex, !input.showOptionalCreditsAndExtraUsage {",
expectedProviderIDs: ["claude", "codex", "copilot"],
expectedReferenceCount: 4,
expectedReferenceFingerprint: ["codex@0", "copilot@3", "codex@6", "claude@11"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView+ModelHelpers.swift",
line: 788,
line: 836,
anchor: "let resetText = input.provider == .sub2api && namedWindow.window.resetsAt == nil",
expectedProviderIDs: ["doubao", "sub2api"],
expectedReferenceCount: 3,
expectedReferenceFingerprint: ["sub2api@0", "sub2api@3", "doubao@15"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView+ModelHelpers.swift",
line: 859,
line: 907,
anchor: "if input.provider == .antigravity,",
expectedProviderIDs: ["antigravity"],
expectedReferenceCount: 1,
expectedReferenceFingerprint: ["antigravity@0"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView+ModelHelpers.swift",
line: 893,
line: 941,
anchor: "if provider == .claude, window.windowMinutes != 10080 {",
expectedProviderIDs: ["antigravity", "claude", "codex"],
expectedReferenceCount: 4,
expectedReferenceFingerprint: ["claude@0", "antigravity@3", "claude@3", "codex@3"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView+ModelHelpers.swift",
line: 925,
line: 973,
anchor: "guard input.provider == .antigravity else { return nil }",
expectedProviderIDs: ["antigravity"],
expectedReferenceCount: 1,
Expand Down Expand Up @@ -1926,39 +1928,47 @@ struct ProviderArchitectureGatekeeperTests {
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1065,
line: 1008,
anchor: "if input.provider == .sub2api {",
expectedProviderIDs: ["sub2api"],
expectedReferenceCount: 1,
expectedReferenceFingerprint: ["sub2api@0"],
reason: "The sub2api menu card localizes and groups provider-owned usage detail rows for display."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1068,
anchor: "if provider == .kiro,",
expectedProviderIDs: ["kilo", "kiro"],
expectedReferenceCount: 2,
expectedReferenceFingerprint: ["kiro@0", "kilo@5"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1088,
line: 1091,
anchor: "if provider == .minimax {",
expectedProviderIDs: ["codex", "minimax"],
expectedReferenceCount: 2,
expectedReferenceFingerprint: ["minimax@0", "codex@3"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1123,
line: 1126,
anchor: "guard let loginMethod = snapshot?.loginMethod(for: .kilo) else {",
expectedProviderIDs: ["kilo"],
expectedReferenceCount: 1,
expectedReferenceFingerprint: ["kilo@0"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1207,
line: 1210,
anchor: "if input.provider == .antigravity {",
expectedProviderIDs: ["antigravity", "mistral"],
expectedReferenceCount: 2,
expectedReferenceFingerprint: ["antigravity@0", "mistral@6"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1227,
line: 1230,
anchor: "if input.provider == .codex, let codexProjection = input.codexProjection {",
expectedProviderIDs: ["alibaba", "alibabatokenplan", "codex", "perplexity", "sub2api"],
expectedReferenceCount: 6,
Expand All @@ -1973,31 +1983,31 @@ struct ProviderArchitectureGatekeeperTests {
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1278,
line: 1281,
anchor: "if input.provider == .kilo || input.provider == .kimi,",
expectedProviderIDs: ["kilo", "kimi"],
expectedReferenceCount: 2,
expectedReferenceFingerprint: ["kilo@0", "kimi@0"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1360,
line: 1363,
anchor: "var paceDetail = if input.provider == .kimi {",
expectedProviderIDs: ["kimi"],
expectedReferenceCount: 1,
expectedReferenceFingerprint: ["kimi@0"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1376,
line: 1379,
anchor: "if input.provider == .warp,",
expectedProviderIDs: ["chutes", "kilo", "kiro", "litellm", "sub2api", "warp"],
expectedReferenceCount: 6,
expectedReferenceFingerprint: ["warp@0", "chutes@7", "kilo@7", "litellm@7", "sub2api@16", "kiro@19"],
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1409,
line: 1412,
anchor: "if input.provider == .alibaba || input.provider == .alibabatokenplan,",
expectedProviderIDs: ["alibaba", "alibabatokenplan", "copilot", "crof", "manus", "perplexity", "zenmux"],
expectedReferenceCount: 8,
Expand All @@ -2014,7 +2024,7 @@ struct ProviderArchitectureGatekeeperTests {
reason: "This exact shared renderer maps provider-owned presentation data into the generic UI model."),
AllowedProviderConstruct(
path: "Sources/CodexBar/MenuCardView.swift",
line: 1450,
line: 1453,
anchor: "if input.provider == .synthetic,",
expectedProviderIDs: ["synthetic"],
expectedReferenceCount: 1,
Expand Down
63 changes: 60 additions & 3 deletions Tests/CodexBarTests/Sub2APIMenuCardModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,66 @@ struct Sub2APIMenuCardModelTests {

#expect(model.planText == "Enterprise")
#expect(model.usageNotes.isEmpty)
#expect(model.providerDetails.first?.rows.map(\.label) == [
"Balance", "Today requests", "Today tokens", "All time requests", "All time tokens",
])
let usage = try #require(model.providerDetails.first)
#expect(usage.title == "Usage")
#expect(usage.rows.map(\.label) == ["Balance", "Today", "Total"])
#expect(usage.rows[1].value == "4 requests")
#expect(usage.rows[1].secondaryValue == "1,200 tokens · Cost: $1.25")
#expect(usage.rows[2].value == "40 requests")
#expect(usage.rows[2].secondaryValue == "12,000 tokens · Cost: $25.00")
}

@Test
func `usage summary and quota labels follow the selected language`() async throws {
let now = Date(timeIntervalSince1970: 1_720_440_000)
let json = """
{
"mode": "unrestricted",
"subscription": {
"daily_usage_usd": 12,
"weekly_usage_usd": 70,
"monthly_usage_usd": 280,
"daily_limit_usd": 120,
"weekly_limit_usd": 700,
"monthly_limit_usd": 2800
},
"usage": {
"today": { "requests": 4, "total_tokens": 1200, "actual_cost": 1.25 },
"total": { "requests": 40, "total_tokens": 12000, "actual_cost": 25 }
}
}
"""
let snapshot = try await Self.snapshot(json, now: now)
let metadata = try #require(ProviderDefaults.metadata[.sub2api])

let model = CodexBarLocalizationOverride.$appLanguage.withValue("zh-Hans") {
UsageMenuCardView.Model.make(.init(
provider: .sub2api,
metadata: metadata,
snapshot: snapshot,
credits: nil,
creditsError: nil,
dashboard: nil,
dashboardError: nil,
tokenSnapshot: nil,
tokenError: nil,
account: AccountInfo(email: nil, plan: nil),
isRefreshing: false,
lastError: nil,
usageBarsShowUsed: false,
resetTimeDisplayStyle: .countdown,
tokenCostUsageEnabled: false,
showOptionalCreditsAndExtraUsage: true,
hidePersonalInfo: false,
now: now))
}

#expect(model.metrics.map(\.title) == ["每日配额", "每周", "每月"])
let usage = try #require(model.providerDetails.first)
#expect(usage.title == "用量")
#expect(usage.rows.map(\.label) == ["今日", "总计"])
#expect(usage.rows[0].value == "4 请求")
#expect(usage.rows[0].secondaryValue == "1,200 token · 费用: $1.25")
}

@Test
Expand Down