diff --git a/Sources/CodexBarCore/Providers/Cursor/CursorStatusProbe.swift b/Sources/CodexBarCore/Providers/Cursor/CursorStatusProbe.swift index 3409d967..d03a72ff 100644 --- a/Sources/CodexBarCore/Providers/Cursor/CursorStatusProbe.swift +++ b/Sources/CodexBarCore/Providers/Cursor/CursorStatusProbe.swift @@ -692,8 +692,17 @@ public struct CursorStatusProbe: Sendable { // Convert cents to USD (plan percent derives from raw values to avoid percent unit mismatches). // Use breakdown.total if available (includes bonus credits), otherwise fall back to limit. let planUsedRaw = Double(summary.individualUsage?.plan?.used ?? 0) - let planLimitRaw = Double(summary.individualUsage?.plan?.breakdown?.total ?? summary.individualUsage?.plan? + var planLimitRaw = Double(summary.individualUsage?.plan?.breakdown?.total ?? summary.individualUsage?.plan? .limit ?? 0) + + // Fix for plans where the API returns an incorrect limit (e.g., "Cursor Pro_Student" returning $2000 instead of $20) + if let membershipType = summary.membershipType, + membershipType.contains("Pro_Student"), + planLimitRaw > 2000 + { + planLimitRaw = 2000 + } + let planUsed = planUsedRaw / 100.0 let planLimit = planLimitRaw / 100.0 let planPercentUsed: Double = if planLimitRaw > 0 { diff --git a/Sources/CodexBarCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift b/Sources/CodexBarCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift index 2fcfc3ef..5bf0a9c5 100644 --- a/Sources/CodexBarCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +++ b/Sources/CodexBarCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift @@ -86,7 +86,8 @@ struct MiniMaxAPIFetchStrategy: ProviderFetchStrategy { guard let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env) else { throw MiniMaxAPISettingsError.missingToken } - let usage = try await MiniMaxUsageFetcher.fetchUsage(apiToken: apiToken) + let region = context.settings?.minimax?.apiRegion ?? .global + let usage = try await MiniMaxUsageFetcher.fetchUsage(apiToken: apiToken, region: region) return self.makeResult( usage: usage.toUsageSnapshot(), sourceLabel: "api") diff --git a/Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageFetcher.swift b/Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageFetcher.swift index df1d7ce0..f9eba6a5 100644 --- a/Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +++ b/Sources/CodexBarCore/Providers/MiniMax/MiniMaxUsageFetcher.swift @@ -51,6 +51,7 @@ public struct MiniMaxUsageFetcher: Sendable { public static func fetchUsage( apiToken: String, + region: MiniMaxAPIRegion = .global, now: Date = Date()) async throws -> MiniMaxUsageSnapshot { let cleaned = apiToken.trimmingCharacters(in: .whitespacesAndNewlines) @@ -58,7 +59,7 @@ public struct MiniMaxUsageFetcher: Sendable { throw MiniMaxUsageError.invalidCredentials } - var request = URLRequest(url: self.apiRemainsURL) + var request = URLRequest(url: region.remainsURL) request.httpMethod = "GET" request.setValue("Bearer \(cleaned)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "accept") diff --git a/Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift b/Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift index 289c779f..02b524e5 100644 --- a/Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift +++ b/Sources/CodexBarCore/Providers/OpenCode/OpenCodeUsageFetcher.swift @@ -409,18 +409,29 @@ public struct OpenCodeUsageFetcher: Sendable { private static func extractServerErrorMessage(from text: String) -> String? { guard let data = text.data(using: .utf8), - let object = try? JSONSerialization.jsonObject(with: data, options: []), - let dict = object as? [String: Any] + let object = try? JSONSerialization.jsonObject(with: data, options: []) else { + // If it's not JSON, try to extract error from HTML if possible + if text.contains("