import Foundation public enum MiniMaxProviderDescriptor { public static let descriptor: ProviderDescriptor = Self.makeDescriptor() static func makeDescriptor() -> ProviderDescriptor { ProviderDescriptor( id: .minimax, metadata: ProviderMetadata( id: .minimax, displayName: "MiniMax", sessionLabel: "Prompts", weeklyLabel: "Window", opusLabel: nil, supportsOpus: false, supportsCredits: false, creditsHint: "", toggleTitle: "Show MiniMax usage", cliName: "minimax", defaultEnabled: false, isPrimaryProvider: false, usesAccountFallback: false, browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder, dashboardURL: "https://platform.minimax.io/user-center/payment/coding-plan?cycle_type=3", statusPageURL: nil), branding: ProviderBranding( iconStyle: .minimax, iconResourceName: "ProviderIcon-minimax", color: ProviderColor(red: 254 / 255, green: 96 / 255, blue: 60 / 255)), tokenCost: ProviderTokenCostConfig( supportsTokenCost: false, noDataMessage: { "MiniMax cost summary is not supported." }), fetchPlan: ProviderFetchPlan( sourceModes: [.auto, .web, .api], pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)), cli: ProviderCLIConfig( name: "minimax", aliases: ["mini-max"], versionDetector: nil)) } private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] { switch context.sourceMode { case .web: return [MiniMaxCodingPlanFetchStrategy()] case .api: return [MiniMaxAPIFetchStrategy()] case .cli, .oauth: return [] case .auto: break } let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env) let apiKeyKind = MiniMaxAPISettingsReader.apiKeyKind(token: apiToken) let authMode = MiniMaxAuthMode.resolve( apiToken: apiToken, cookieHeader: ProviderTokenResolver.minimaxCookie(environment: context.env)) if authMode.usesAPIToken { if apiKeyKind == .standard { return [MiniMaxCodingPlanFetchStrategy()] } return [MiniMaxAPIFetchStrategy(), MiniMaxCodingPlanFetchStrategy()] } return [MiniMaxCodingPlanFetchStrategy()] }} struct MiniMaxAPIFetchStrategy: ProviderFetchStrategy { let id: String = "minimax.api" let kind: ProviderFetchKind = .apiToken func isAvailable(_ context: ProviderFetchContext) async -> Bool { let authMode = MiniMaxAuthMode.resolve( apiToken: ProviderTokenResolver.minimaxToken(environment: context.env), cookieHeader: ProviderTokenResolver.minimaxCookie(environment: context.env)) if let kind = MiniMaxAPISettingsReader.apiKeyKind(environment: context.env), kind == .standard { return false } return authMode.usesAPIToken } func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult { guard let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env) else { throw MiniMaxAPISettingsError.missingToken } 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") } func shouldFallback(on error: Error, context _: ProviderFetchContext) -> Bool { guard let minimaxError = error as? MiniMaxUsageError else { return false } switch minimaxError { case .invalidCredentials: return true case let .apiError(message): return message.contains("HTTP 404") case .networkError, .parseFailed: return false } }} struct MiniMaxCodingPlanFetchStrategy: ProviderFetchStrategy { let id: String = "minimax.web" let kind: ProviderFetchKind = .web private static let log = CodexBarLog.logger(LogCategories.minimaxWeb) func isAvailable(_ context: ProviderFetchContext) async -> Bool { if Self.resolveCookieOverride(context: context) != nil { return true } #if os(macOS) if let cached = CookieHeaderCache.load(provider: .minimax), !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return true } guard Self.allowsBrowserCookieImport(context: context) else { return false } return MiniMaxCookieImporter.hasSession(browserDetection: context.browserDetection) #else return false #endif } func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult { let fetchContext = FetchContext( region: context.settings?.minimax?.apiRegion ?? .global, environment: context.env, includeBillingHistory: context.includeOptionalUsage) if let override = Self.resolveCookieOverride(context: context) { Self.log.debug("Using MiniMax cookie header from settings/env") let snapshot = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: override.cookieHeader, authorizationToken: override.authorizationToken, groupID: override.groupID, region: fetchContext.region, environment: fetchContext.environment, includeBillingHistory: context.includeOptionalUsage) return self.makeResult( usage: snapshot.toUsageSnapshot(), sourceLabel: "web") } #if os(macOS) let tokenContext = Self.loadTokenContext(browserDetection: context.browserDetection) var lastError: Error? if let cached = CookieHeaderCache.load(provider: .minimax), !cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { switch await Self.attemptFetch( cookieHeader: cached.cookieHeader, sourceLabel: cached.sourceLabel, tokenContext: tokenContext, logLabel: "cached", fetchContext: fetchContext) { case let .success(snapshot): return self.makeResult( usage: snapshot.toUsageSnapshot(), sourceLabel: "web") case let .failure(error): lastError = error if Self.shouldTryNextBrowser(for: error) { CookieHeaderCache.clear(provider: .minimax) } else { throw error } } } guard Self.allowsBrowserCookieImport(context: context) else { if let lastError { throw lastError } throw MiniMaxSettingsError.missingCookie } let sessions = (try? MiniMaxCookieImporter.importSessions( browserDetection: context.browserDetection)) ?? [] guard !sessions.isEmpty else { if let lastError { throw lastError } throw MiniMaxSettingsError.missingCookie } for session in sessions { switch await Self.attemptFetch( cookieHeader: session.cookieHeader, sourceLabel: session.sourceLabel, tokenContext: tokenContext, logLabel: "", fetchContext: fetchContext) { case let .success(snapshot): CookieHeaderCache.store( provider: .minimax, cookieHeader: session.cookieHeader, sourceLabel: session.sourceLabel) return self.makeResult( usage: snapshot.toUsageSnapshot(), sourceLabel: "web") case let .failure(error): lastError = error if Self.shouldTryNextBrowser(for: error) { Self.log.debug("MiniMax cookies invalid from \(session.sourceLabel), trying next browser") continue } throw error } } if let lastError { throw lastError } #endif throw MiniMaxSettingsError.missingCookie } func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool { false } static func allowsBrowserCookieImport(context: ProviderFetchContext) -> Bool { context.runtime == .app && ProviderInteractionContext.current == .userInitiated } private struct TokenContext { let tokensByLabel: [String: [String]] let groupIDByLabel: [String: String] } private struct FetchContext { let region: MiniMaxAPIRegion let environment: [String: String] let includeBillingHistory: Bool } private enum FetchAttemptResult { case success(MiniMaxUsageSnapshot) case failure(Error) } private static func resolveCookieOverride(context: ProviderFetchContext) -> MiniMaxCookieOverride? { if let settings = context.settings?.minimax { guard settings.cookieSource == .manual else { return nil } return MiniMaxCookieHeader.override(from: settings.manualCookieHeader) } guard let raw = ProviderTokenResolver.minimaxCookie(environment: context.env) else { return nil } return MiniMaxCookieHeader.override(from: raw) } private static func normalizeStorageLabel(_ label: String) -> String { let suffixes = [" (Session Storage)", " (IndexedDB)"] for suffix in suffixes where label.hasSuffix(suffix) { return String(label.dropLast(suffix.count)) } return label } private static func loadTokenContext(browserDetection: BrowserDetection) -> TokenContext { #if os(macOS) let tokenLog: (String) -> Void = { msg in Self.log.debug(msg) } let accessTokens = MiniMaxLocalStorageImporter.importAccessTokens( browserDetection: browserDetection, logger: tokenLog) let groupIDs = MiniMaxLocalStorageImporter.importGroupIDs( browserDetection: browserDetection, logger: tokenLog) var tokensByLabel: [String: [String]] = [:] var groupIDByLabel: [String: String] = [:] for token in accessTokens { let normalized = Self.normalizeStorageLabel(token.sourceLabel) tokensByLabel[normalized, default: []].append(token.accessToken) if let groupID = token.groupID, groupIDByLabel[normalized] == nil { groupIDByLabel[normalized] = groupID } } for (label, groupID) in groupIDs { let normalized = Self.normalizeStorageLabel(label) if groupIDByLabel[normalized] == nil { groupIDByLabel[normalized] = groupID } } return TokenContext(tokensByLabel: tokensByLabel, groupIDByLabel: groupIDByLabel) #else _ = browserDetection return TokenContext(tokensByLabel: [:], groupIDByLabel: [:]) #endif } private static func attemptFetch( cookieHeader: String, sourceLabel: String, tokenContext: TokenContext, logLabel: String, fetchContext: FetchContext) async -> FetchAttemptResult { let normalizedLabel = Self.normalizeStorageLabel(sourceLabel) let tokenCandidates = tokenContext.tokensByLabel[normalizedLabel] ?? [] let groupID = tokenContext.groupIDByLabel[normalizedLabel] let cookieToken = Self.cookieValue(named: "HERTZ-SESSION", in: cookieHeader) var attempts: [String?] = tokenCandidates.map(\.self) if let cookieToken, !tokenCandidates.contains(cookieToken) { attempts.append(cookieToken) } attempts.append(nil) let prefix = logLabel.isEmpty ? "" : "\(logLabel) " var lastError: Error? for token in attempts { let tokenLabel: String = { guard let token else { return "" } if token == cookieToken { return " + HERTZ-SESSION bearer" } return " + access token" }() Self.log.debug("Trying MiniMax \(prefix)cookies from \(sourceLabel)\(tokenLabel)") do { let snapshot = try await MiniMaxUsageFetcher.fetchUsage( cookieHeader: cookieHeader, authorizationToken: token, groupID: groupID, region: fetchContext.region, environment: fetchContext.environment, includeBillingHistory: fetchContext.includeBillingHistory) Self.log.debug("MiniMax \(prefix)cookies valid from \(sourceLabel)") return .success(snapshot) } catch { lastError = error if Self.shouldTryNextBrowser(for: error) { continue } return .failure(error) } } if let lastError { return .failure(lastError) } return .failure(MiniMaxSettingsError.missingCookie) } private static func cookieValue(named name: String, in header: String) -> String? { let parts = header.split(separator: ";") for part in parts { let trimmed = part.trimmingCharacters(in: .whitespacesAndNewlines) guard trimmed.lowercased().hasPrefix("\(name.lowercased())=") else { continue } return String(trimmed.dropFirst(name.count + 1)) } return nil } private static func shouldTryNextBrowser(for error: Error) -> Bool { if case MiniMaxUsageError.invalidCredentials = error { return true } if case MiniMaxUsageError.parseFailed = error { return true } return false }}
import Foundation
public enum MiniMaxProviderDescriptor {
public static let descriptor: ProviderDescriptor = Self.makeDescriptor()
static func makeDescriptor() -> ProviderDescriptor {
ProviderDescriptor(
id: .minimax,
metadata: ProviderMetadata(
id: .minimax,
displayName: "MiniMax",
sessionLabel: "Prompts",
weeklyLabel: "Window",
opusLabel: nil,
supportsOpus: false,
supportsCredits: false,
creditsHint: "",
toggleTitle: "Show MiniMax usage",
cliName: "minimax",
defaultEnabled: false,
isPrimaryProvider: false,
usesAccountFallback: false,
browserCookieOrder: ProviderBrowserCookieDefaults.defaultImportOrder,
dashboardURL: "https://platform.minimax.io/user-center/payment/coding-plan?cycle_type=3",
statusPageURL: nil),
branding: ProviderBranding(
iconStyle: .minimax,
iconResourceName: "ProviderIcon-minimax",
color: ProviderColor(red: 254 / 255, green: 96 / 255, blue: 60 / 255)),
tokenCost: ProviderTokenCostConfig(
supportsTokenCost: false,
noDataMessage: { "MiniMax cost summary is not supported." }),
fetchPlan: ProviderFetchPlan(
sourceModes: [.auto, .web, .api],
pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),
cli: ProviderCLIConfig(
name: "minimax",
aliases: ["mini-max"],
versionDetector: nil))
}
private static func resolveStrategies(context: ProviderFetchContext) async -> [any ProviderFetchStrategy] {
switch context.sourceMode {
case .web:
return [MiniMaxCodingPlanFetchStrategy()]
case .api:
return [MiniMaxAPIFetchStrategy()]
case .cli, .oauth:
return []
case .auto:
break
}
let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env)
let apiKeyKind = MiniMaxAPISettingsReader.apiKeyKind(token: apiToken)
let authMode = MiniMaxAuthMode.resolve(
apiToken: apiToken,
cookieHeader: ProviderTokenResolver.minimaxCookie(environment: context.env))
if authMode.usesAPIToken {
if apiKeyKind == .standard {
return [MiniMaxCodingPlanFetchStrategy()]
}
return [MiniMaxAPIFetchStrategy(), MiniMaxCodingPlanFetchStrategy()]
}
return [MiniMaxCodingPlanFetchStrategy()]
}
}
struct MiniMaxAPIFetchStrategy: ProviderFetchStrategy {
let id: String = "minimax.api"
let kind: ProviderFetchKind = .apiToken
func isAvailable(_ context: ProviderFetchContext) async -> Bool {
let authMode = MiniMaxAuthMode.resolve(
apiToken: ProviderTokenResolver.minimaxToken(environment: context.env),
cookieHeader: ProviderTokenResolver.minimaxCookie(environment: context.env))
if let kind = MiniMaxAPISettingsReader.apiKeyKind(environment: context.env),
kind == .standard
{
return false
}
return authMode.usesAPIToken
}
func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
guard let apiToken = ProviderTokenResolver.minimaxToken(environment: context.env) else {
throw MiniMaxAPISettingsError.missingToken
}
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")
}
func shouldFallback(on error: Error, context _: ProviderFetchContext) -> Bool {
guard let minimaxError = error as? MiniMaxUsageError else { return false }
switch minimaxError {
case .invalidCredentials:
return true
case let .apiError(message):
return message.contains("HTTP 404")
case .networkError, .parseFailed:
return false
}
}
}
struct MiniMaxCodingPlanFetchStrategy: ProviderFetchStrategy {
let id: String = "minimax.web"
let kind: ProviderFetchKind = .web
private static let log = CodexBarLog.logger(LogCategories.minimaxWeb)
func isAvailable(_ context: ProviderFetchContext) async -> Bool {
if Self.resolveCookieOverride(context: context) != nil {
return true
}
#if os(macOS)
if let cached = CookieHeaderCache.load(provider: .minimax),
!cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
return true
}
guard Self.allowsBrowserCookieImport(context: context) else {
return false
}
return MiniMaxCookieImporter.hasSession(browserDetection: context.browserDetection)
#else
return false
#endif
}
func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
let fetchContext = FetchContext(
region: context.settings?.minimax?.apiRegion ?? .global,
environment: context.env,
includeBillingHistory: context.includeOptionalUsage)
if let override = Self.resolveCookieOverride(context: context) {
Self.log.debug("Using MiniMax cookie header from settings/env")
let snapshot = try await MiniMaxUsageFetcher.fetchUsage(
cookieHeader: override.cookieHeader,
authorizationToken: override.authorizationToken,
groupID: override.groupID,
region: fetchContext.region,
environment: fetchContext.environment,
includeBillingHistory: context.includeOptionalUsage)
return self.makeResult(
usage: snapshot.toUsageSnapshot(),
sourceLabel: "web")
}
#if os(macOS)
let tokenContext = Self.loadTokenContext(browserDetection: context.browserDetection)
var lastError: Error?
if let cached = CookieHeaderCache.load(provider: .minimax),
!cached.cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
switch await Self.attemptFetch(
cookieHeader: cached.cookieHeader,
sourceLabel: cached.sourceLabel,
tokenContext: tokenContext,
logLabel: "cached",
fetchContext: fetchContext)
{
case let .success(snapshot):
return self.makeResult(
usage: snapshot.toUsageSnapshot(),
sourceLabel: "web")
case let .failure(error):
lastError = error
if Self.shouldTryNextBrowser(for: error) {
CookieHeaderCache.clear(provider: .minimax)
} else {
throw error
}
}
}
guard Self.allowsBrowserCookieImport(context: context) else {
if let lastError { throw lastError }
throw MiniMaxSettingsError.missingCookie
}
let sessions = (try? MiniMaxCookieImporter.importSessions(
browserDetection: context.browserDetection)) ?? []
guard !sessions.isEmpty else {
if let lastError { throw lastError }
throw MiniMaxSettingsError.missingCookie
}
for session in sessions {
switch await Self.attemptFetch(
cookieHeader: session.cookieHeader,
sourceLabel: session.sourceLabel,
tokenContext: tokenContext,
logLabel: "",
fetchContext: fetchContext)
{
case let .success(snapshot):
CookieHeaderCache.store(
provider: .minimax,
cookieHeader: session.cookieHeader,
sourceLabel: session.sourceLabel)
return self.makeResult(
usage: snapshot.toUsageSnapshot(),
sourceLabel: "web")
case let .failure(error):
lastError = error
if Self.shouldTryNextBrowser(for: error) {
Self.log.debug("MiniMax cookies invalid from \(session.sourceLabel), trying next browser")
continue
}
throw error
}
}
if let lastError {
throw lastError
}
#endif
throw MiniMaxSettingsError.missingCookie
}
func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
false
}
static func allowsBrowserCookieImport(context: ProviderFetchContext) -> Bool {
context.runtime == .app && ProviderInteractionContext.current == .userInitiated
}
private struct TokenContext {
let tokensByLabel: [String: [String]]
let groupIDByLabel: [String: String]
}
private struct FetchContext {
let region: MiniMaxAPIRegion
let environment: [String: String]
let includeBillingHistory: Bool
}
private enum FetchAttemptResult {
case success(MiniMaxUsageSnapshot)
case failure(Error)
}
private static func resolveCookieOverride(context: ProviderFetchContext) -> MiniMaxCookieOverride? {
if let settings = context.settings?.minimax {
guard settings.cookieSource == .manual else { return nil }
return MiniMaxCookieHeader.override(from: settings.manualCookieHeader)
}
guard let raw = ProviderTokenResolver.minimaxCookie(environment: context.env) else {
return nil
}
return MiniMaxCookieHeader.override(from: raw)
}
private static func normalizeStorageLabel(_ label: String) -> String {
let suffixes = [" (Session Storage)", " (IndexedDB)"]
for suffix in suffixes where label.hasSuffix(suffix) {
return String(label.dropLast(suffix.count))
}
return label
}
private static func loadTokenContext(browserDetection: BrowserDetection) -> TokenContext {
#if os(macOS)
let tokenLog: (String) -> Void = { msg in Self.log.debug(msg) }
let accessTokens = MiniMaxLocalStorageImporter.importAccessTokens(
browserDetection: browserDetection,
logger: tokenLog)
let groupIDs = MiniMaxLocalStorageImporter.importGroupIDs(
browserDetection: browserDetection,
logger: tokenLog)
var tokensByLabel: [String: [String]] = [:]
var groupIDByLabel: [String: String] = [:]
for token in accessTokens {
let normalized = Self.normalizeStorageLabel(token.sourceLabel)
tokensByLabel[normalized, default: []].append(token.accessToken)
if let groupID = token.groupID, groupIDByLabel[normalized] == nil {
groupIDByLabel[normalized] = groupID
}
}
for (label, groupID) in groupIDs {
let normalized = Self.normalizeStorageLabel(label)
if groupIDByLabel[normalized] == nil {
groupIDByLabel[normalized] = groupID
}
}
return TokenContext(tokensByLabel: tokensByLabel, groupIDByLabel: groupIDByLabel)
#else
_ = browserDetection
return TokenContext(tokensByLabel: [:], groupIDByLabel: [:])
#endif
}
private static func attemptFetch(
cookieHeader: String,
sourceLabel: String,
tokenContext: TokenContext,
logLabel: String,
fetchContext: FetchContext) async -> FetchAttemptResult
{
let normalizedLabel = Self.normalizeStorageLabel(sourceLabel)
let tokenCandidates = tokenContext.tokensByLabel[normalizedLabel] ?? []
let groupID = tokenContext.groupIDByLabel[normalizedLabel]
let cookieToken = Self.cookieValue(named: "HERTZ-SESSION", in: cookieHeader)
var attempts: [String?] = tokenCandidates.map(\.self)
if let cookieToken, !tokenCandidates.contains(cookieToken) {
attempts.append(cookieToken)
}
attempts.append(nil)
let prefix = logLabel.isEmpty ? "" : "\(logLabel) "
var lastError: Error?
for token in attempts {
let tokenLabel: String = {
guard let token else { return "" }
if token == cookieToken { return " + HERTZ-SESSION bearer" }
return " + access token"
}()
Self.log.debug("Trying MiniMax \(prefix)cookies from \(sourceLabel)\(tokenLabel)")
do {
let snapshot = try await MiniMaxUsageFetcher.fetchUsage(
cookieHeader: cookieHeader,
authorizationToken: token,
groupID: groupID,
region: fetchContext.region,
environment: fetchContext.environment,
includeBillingHistory: fetchContext.includeBillingHistory)
Self.log.debug("MiniMax \(prefix)cookies valid from \(sourceLabel)")
return .success(snapshot)
} catch {
lastError = error
if Self.shouldTryNextBrowser(for: error) {
continue
}
return .failure(error)
}
}
if let lastError {
return .failure(lastError)
}
return .failure(MiniMaxSettingsError.missingCookie)
}
private static func cookieValue(named name: String, in header: String) -> String? {
let parts = header.split(separator: ";")
for part in parts {
let trimmed = part.trimmingCharacters(in: .whitespacesAndNewlines)
guard trimmed.lowercased().hasPrefix("\(name.lowercased())=") else { continue }
return String(trimmed.dropFirst(name.count + 1))
}
return nil
}
private static func shouldTryNextBrowser(for error: Error) -> Bool {
if case MiniMaxUsageError.invalidCredentials = error { return true }
if case MiniMaxUsageError.parseFailed = error { return true }
return false
}
}