Skip to content
Closed
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Added
- Agent Sessions: list and focus live local or SSH-discovered Codex and Claude Code sessions from the menu and CLI.
- MiniMax: fetch Token Plan recharge credit balance from the console when a web session cookie is available, including MiniMax Agent desktop cookies and explicit manual or env cookies on API-token refreshes. Thanks @Yuxin-Qiao!
- MiniMax: show console usage-summary KPIs, cost trends, and token usage details in the menu dashboard. Thanks @Yuxin-Qiao!

### Fixed
- Ollama: recognize current WorkOS AuthKit sessions during browser-cookie discovery and manual cookie validation. Thanks @joeVenner!
Expand Down
251 changes: 248 additions & 3 deletions Sources/CodexBar/InlineUsageDashboardContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ extension UsageMenuCardView.Model {
return notes
}

if input.provider == .minimax,
input.showOptionalCreditsAndExtraUsage,
let usageSummary = input.snapshot?.minimaxUsage?.usageSummary,
usageSummary.hasDisplayableData
{
return Self.minimaxUsageSummaryNotes(usageSummary)
}

if input.provider == .minimax,
input.showOptionalCreditsAndExtraUsage,
let billing = input.snapshot?.minimaxUsage?.billingSummary
Expand Down Expand Up @@ -183,7 +191,21 @@ extension UsageMenuCardView.Model {
}

private static func resolveInlineUsageDashboard(input: Input) -> InlineUsageDashboardModel? {
if input.provider == .minimax,
input.showOptionalCreditsAndExtraUsage,
input.tokenCostInlineDashboardEnabled,
let usageSummary = input.snapshot?.minimaxUsage?.usageSummary,
usageSummary.hasDisplayableData,
let costSnapshot = primaryCostHistorySnapshot(input: input),
!costSnapshot.daily.isEmpty
{
return self.minimaxCostAndUsageSummaryInlineDashboard(
snapshot: costSnapshot,
usage: usageSummary,
comparisonPeriodsEnabled: input.costComparisonPeriodsEnabled)
}
if self.usesProviderCostHistoryAsPrimaryDashboard(input.provider),
input.provider != .minimax || input.tokenCostInlineDashboardEnabled,
let tokenSnapshot = primaryCostHistorySnapshot(input: input),
!tokenSnapshot.daily.isEmpty
Comment on lines 207 to 210

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 Gate MiniMax cost dashboard behind optional-usage toggle

When MiniMax is selected and “Show optional credits and extra usage” is disabled, this generic cost-history branch can still render a MiniMax dashboard as long as token-cost dashboards are enabled and the snapshot already contains minimaxUsage.usageSummary (for example from a previous refresh or cache). The MiniMax-specific summary branches above are guarded by showOptionalCreditsAndExtraUsage, so this path leaks the optional web usage/cost data that the setting is meant to hide.

Useful? React with 👍 / 👎.

{
Expand Down Expand Up @@ -212,6 +234,13 @@ extension UsageMenuCardView.Model {
{
return Self.zaiInlineDashboard(modelUsage: modelUsage, now: input.now)
}
if input.provider == .minimax,
input.showOptionalCreditsAndExtraUsage,
let usageSummary = input.snapshot?.minimaxUsage?.usageSummary,
usageSummary.hasDisplayableData
{
return Self.minimaxUsageSummaryInlineDashboard(usageSummary)
}
if input.provider == .minimax,
input.showOptionalCreditsAndExtraUsage,
let billing = input.snapshot?.minimaxUsage?.billingSummary,
Expand Down Expand Up @@ -246,7 +275,7 @@ extension UsageMenuCardView.Model {
}

static func usesProviderCostHistoryAsPrimaryDashboard(_ provider: UsageProvider) -> Bool {
provider == .openai || provider == .mistral
provider == .openai || provider == .mistral || provider == .minimax
}

static func primaryCostHistorySnapshot(input: Input) -> CostUsageTokenSnapshot? {
Expand All @@ -261,6 +290,11 @@ extension UsageMenuCardView.Model {
return projected
}
return input.snapshot == nil ? input.tokenSnapshot : nil
case .minimax:
if let projected = input.snapshot?.minimaxUsage?.usageSummary?.toCostUsageTokenSnapshot() {
return projected
}
return input.snapshot == nil ? input.tokenSnapshot : nil
default:
return input.tokenSnapshot
}
Expand Down Expand Up @@ -396,7 +430,9 @@ extension UsageMenuCardView.Model {
emphasis: false),
.init(
title: tokenHistoryTitle,
value: snapshot.last30DaysTokens.map(UsageFormatter.tokenCountString) ?? "—",
value: snapshot.last30DaysTokens.map {
Self.tokenCountString($0, provider: provider)
} ?? "—",
emphasis: false),
] + Self.costHistoryTrailingKPIs(snapshot: snapshot, latest: latest),
points: points,
Expand Down Expand Up @@ -638,6 +674,214 @@ extension UsageMenuCardView.Model {
detailLines: details)
}

private static func minimaxUsageSummaryInlineDashboard(_ usage: MiniMaxUsageSummary) -> InlineUsageDashboardModel {
let trendDays = usage.trendDays(last: 7)
let points = trendDays.map {
let tokenText = Self.minimaxTokenString($0.totalToken)
let cacheText = UsageFormatter.optionalPercentString($0.cacheHitPercent)
let cacheLine = String(format: L("Cache hit: %@"), cacheText)
return InlineUsageDashboardModel.Point(
id: $0.date,
label: Self.shortDayLabel($0.date),
value: Double($0.totalToken),
accessibilityValue: "\($0.date): \(tokenText) \(L("tokens")) · \(cacheLine)")
}
let cacheText = UsageFormatter.optionalPercentString(usage.snapshotDay?.cacheHitPercent)
let todayTitle = usage.lastUpdateTime
.flatMap { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.flatMap { $0.isEmpty ? nil : String(format: L("%@ usage"), $0) }
?? L("Today")

return InlineUsageDashboardModel(
accessibilityLabel: L("MiniMax 7 day token usage trend"),
valueStyle: .tokens,
kpis: [
.init(
title: todayTitle,
value: Self.minimaxTokenString(usage.latestSnapshotTokens),
emphasis: true),
.init(
title: L("7d tokens"),
value: Self.minimaxTokenString(usage.last7DaysTokens),
emphasis: false),
.init(
title: L("Cache hit"),
value: cacheText,
emphasis: false),
.init(
title: L("30d tokens"),
value: Self.minimaxTokenString(usage.last30DaysTokens),
emphasis: false),
],
points: points,
detailLines: Self.minimaxUsageSummaryCardDetailLines(usage))
}

private static func minimaxLatestUsageKPITitle(_ usage: MiniMaxUsageSummary) -> String {
if let time = usage.lastUpdateTime?.trimmingCharacters(in: .whitespacesAndNewlines), !time.isEmpty {
return String(format: L("%@ usage"), time)
}
return L("Latest usage")
}

private static func minimaxCostAndUsageSummaryInlineDashboard(
snapshot: CostUsageTokenSnapshot,
usage: MiniMaxUsageSummary,
comparisonPeriodsEnabled: Bool) -> InlineUsageDashboardModel
{
let base = self.costHistoryInlineDashboard(
provider: .minimax,
snapshot: snapshot,
comparisonPeriodsEnabled: comparisonPeriodsEnabled)
let cacheText = UsageFormatter.optionalPercentString(usage.snapshotDay?.cacheHitPercent)
return InlineUsageDashboardModel(
accessibilityLabel: base.accessibilityLabel,
valueStyle: base.valueStyle,
kpis: [
base.kpis[0],
base.kpis[1],
.init(
title: self.minimaxLatestUsageKPITitle(usage),
value: self.minimaxTokenString(usage.latestSnapshotTokens),
emphasis: false),
.init(
title: L("7d tokens"),
value: self.minimaxTokenString(usage.last7DaysTokens),
emphasis: false),
.init(
title: L("Cache hit"),
value: cacheText,
emphasis: false),
.init(
title: L("30d tokens"),
value: self.minimaxTokenString(usage.last30DaysTokens),
emphasis: false),
],
points: base.points,
detailLines: self.minimaxCostDashboardDetailLines(snapshot: snapshot, usage: usage))
}

private static func minimaxCostDashboardDetailLines(
snapshot: CostUsageTokenSnapshot,
usage: MiniMaxUsageSummary) -> [String]
{
var details: [String] = []
if let topModel = Self.topCostModel(from: snapshot.daily) {
details.append("\(L("Top model")): \(Self.shortModelName(topModel))")
}
if let day = usage.snapshotDay,
let todayLine = Self.minimaxTodayBreakdownLine(day)
{
details.append(todayLine)
}
if let hint = Self.tokenUsageHint(provider: .minimax) {
details.append(hint)
}
details.append(L("Language models only; data may be delayed"))
return details
}

private static func minimaxUsageSummaryCardDetailLines(_ usage: MiniMaxUsageSummary) -> [String] {
var details: [String] = []
if let day = usage.snapshotDay,
let todayLine = Self.minimaxTodayBreakdownLine(day)
{
details.append(todayLine)
}
if let total = usage.totalTokenConsumed, !total.isEmpty {
details.append("\(L("Total consumed")): \(total)")
}
if let cache = usage.snapshotDay?.cacheHitPercent {
details.append(
String(format: L("Cache hit: %@"), UsageFormatter.optionalPercentString(cache)))
}
if let activeDays = usage.activeDays {
details.append(String(format: L("Active days: %@"), "\(activeDays)"))
}
if let streak = usage.currentConsecutiveDays {
details.append(String(format: L("Streak: %@ days"), "\(streak)"))
}
if let rank = usage.usageRankingPercent {
details.append(
String(
format: L("Usage rank: top %@%%"),
UsageFormatter.decimalString(rank)))
}
if let updated = usage.lastUpdateTime, !updated.isEmpty {
details.append("\(L("Updated")): \(updated)")
}
if let topModel = Self.topMiniMaxUsageSummaryModel(from: usage.days) {
details.append("\(L("Top model")): \(Self.shortModelName(topModel.model)) (\(topModel.tokens))")
}
details.append(L("Language models only; data may be delayed"))
return details
}

private static func minimaxTodayBreakdownLine(_ day: MiniMaxUsageSummaryDay) -> String? {
if let topModel = day.models.max(by: { lhs, rhs in
if lhs.totalToken == rhs.totalToken { return lhs.model > rhs.model }
return lhs.totalToken < rhs.totalToken
}) {
let cacheText = UsageFormatter.optionalPercentString(topModel.cacheHitPercent)
return String(
format: L("%@ · %@ input · %@ output · %@ cache hit"),
Self.shortModelName(topModel.model),
Self.minimaxTokenString(topModel.inputToken),
Self.minimaxTokenString(topModel.outputToken),
cacheText)
}
guard day.totalToken > 0 else { return nil }
let cacheText = UsageFormatter.optionalPercentString(day.cacheHitPercent)
return String(
format: L("%@ input · %@ output · %@ cache hit"),
Self.minimaxTokenString(day.totalInputToken),
Self.minimaxTokenString(day.totalOutputToken),
cacheText)
}

private static func minimaxTokenString(_ value: Int) -> String {
UsageFormatter.tokenCountString(value, fractionDigits: 2)
}

private static func tokenCountString(_ value: Int, provider: UsageProvider) -> String {
if provider == .minimax {
return self.minimaxTokenString(value)
}
return UsageFormatter.tokenCountString(value)
}

private static func minimaxUsageSummaryNotes(_ usage: MiniMaxUsageSummary) -> [String] {
[
String(
format: L("Today: %@ tokens"),
self.minimaxTokenString(usage.latestSnapshotTokens)),
String(
format: L("Last 7 days: %@ tokens"),
self.minimaxTokenString(usage.last7DaysTokens)),
String(
format: L("Last 30 days: %@ tokens"),
self.minimaxTokenString(usage.last30DaysTokens)),
] + self.minimaxUsageSummaryCardDetailLines(usage)
}

private static func topMiniMaxUsageSummaryModel(
from days: [MiniMaxUsageSummaryDay]) -> (model: String, tokens: String)?
{
var totals: [String: Int] = [:]
for day in days {
for model in day.models {
totals[model.model, default: 0] += model.totalToken
}
}
guard let top = totals.max(by: { lhs, rhs in
if lhs.value == rhs.value { return lhs.key > rhs.key }
return lhs.value < rhs.value
}) else {
return nil
}
return (top.key, Self.minimaxTokenString(top.value))
}

private static func deepseekInlineDashboard(_ usage: DeepSeekUsageSummary) -> InlineUsageDashboardModel {
let symbol = usage.currency == "CNY" ? "¥" : "$"
let points = usage.daily.suffix(30).map {
Expand Down Expand Up @@ -810,7 +1054,8 @@ struct InlineUsageDashboardContent: View {
Text(line)
.font(.caption)
.foregroundStyle(MenuHighlightStyle.secondary(self.isHighlighted))
.lineLimit(1)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion Sources/CodexBar/MenuCardView+Costs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ extension UsageMenuCardView.Model {
L("Reported by OpenAI Admin API organization usage.")
case .mistral:
L("Reported by Mistral billing usage.")
case .minimax:
L("Estimated from MiniMax usage summary and published pay-as-you-go pricing.")
default:
nil
}
Expand Down Expand Up @@ -304,11 +306,15 @@ extension UsageMenuCardView.Model {

if provider == .minimax, cost.period == "MiniMax points balance" {
let balance = String(format: "%.0f", cost.used)
let expiresLine = cost.resetsAt.map {
String(format: L("Expires: %@"), UsageFormatter.preciseDateTimeDescription(from: $0))
}
return ProviderCostSection(
title: L("Credits"),
percentUsed: nil,
spendLine: "\(L("Balance")): \(balance)",
percentLine: nil)
percentLine: nil,
personalSpendLine: expiresLine)
}

if provider == .openai || provider == .claude || provider == .litellm, cost.limit <= 0 {
Expand Down
20 changes: 19 additions & 1 deletion Sources/CodexBar/MenuCardView+MiniMax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ extension UsageMenuCardView.Model {
percent: displayPercent,
percentStyle: percentStyle,
statusText: service.isUnlimited ? "∞ Unlimited" : nil,
resetText: Self.localizedMiniMaxResetDescription(service.resetDescription),
resetText: Self.miniMaxResetText(for: service, input: input),
detailText: nil,
detailLeftText: usageLabel,
detailRightText: nil,
Expand Down Expand Up @@ -112,6 +112,24 @@ extension UsageMenuCardView.Model {
return trimmed.isEmpty ? windowType : trimmed
}

private static func miniMaxResetText(for service: MiniMaxServiceUsage, input: Input) -> String {
if service.isUnlimited {
return self.localizedMiniMaxResetDescription(service.resetDescription)
}
if let resetsAt = service.resetsAt {
switch input.resetTimeDisplayStyle {
case .absolute:
let text = UsageFormatter.resetDescription(from: resetsAt, now: input.now)
return L("Resets %@", text)
case .countdown:
let phrase = MiniMaxServiceUsage.resetCountdownPhrase(from: resetsAt, now: input.now)
if phrase == "now" { return L("Resets now") }
return L("Resets in %@", phrase)
}
}
return Self.localizedMiniMaxResetDescription(service.resetDescription)
}

private static func localizedMiniMaxResetDescription(_ text: String) -> String {
let prefix = "Resets in "
guard text.hasPrefix(prefix) else { return text }
Expand Down
Loading