import CodexBarCoreimport Foundation struct TokenAccountOverride: Sendable { let provider: UsageProvider let account: ProviderTokenAccount} struct ProviderSpec { let style: IconStyle let isEnabled: @MainActor () -> Bool let fetch: () async -> ProviderFetchOutcome} struct ProviderRegistry { let metadata: [UsageProvider: ProviderMetadata] static let shared: ProviderRegistry = .init() init(metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) { self.metadata = metadata } @MainActor func specs( settings: SettingsStore, metadata: [UsageProvider: ProviderMetadata], codexFetcher: UsageFetcher, claudeFetcher: any ClaudeUsageFetching, browserDetection: BrowserDetection) -> [UsageProvider: ProviderSpec] { var specs: [UsageProvider: ProviderSpec] = [:] specs.reserveCapacity(UsageProvider.allCases.count) for provider in UsageProvider.allCases { let descriptor = ProviderDescriptorRegistry.descriptor(for: provider) let meta = metadata[provider]! let spec = ProviderSpec( style: descriptor.branding.iconStyle, isEnabled: { settings.isProviderEnabled(provider: provider, metadata: meta) }, fetch: { let sourceMode: ProviderSourceMode = switch provider { case .codex: switch settings.codexUsageDataSource { case .auto: .auto case .oauth: .oauth case .cli: .cli } case .claude: switch settings.claudeUsageDataSource { case .auto: .auto case .oauth: .oauth case .web: .web case .cli: .cli } default: .auto } let snapshot = await MainActor.run { Self.makeSettingsSnapshot(settings: settings, tokenOverride: nil) } let env = await MainActor.run { Self.makeEnvironment( base: ProcessInfo.processInfo.environment, provider: provider, settings: settings, tokenOverride: nil) } let verbose = settings.isVerboseLoggingEnabled let context = ProviderFetchContext( runtime: .app, sourceMode: sourceMode, includeCredits: false, webTimeout: 60, webDebugDumpHTML: false, verbose: verbose, env: env, settings: snapshot, fetcher: codexFetcher, claudeFetcher: claudeFetcher, browserDetection: browserDetection) return await descriptor.fetchOutcome(context: context) }) specs[provider] = spec } return specs } private static let defaultMetadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata @MainActor static func makeSettingsSnapshot( settings: SettingsStore, tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot { settings.ensureTokenAccountsLoaded() let codexHeader = Self.manualCookieHeader( provider: .codex, settings: settings, override: tokenOverride, fallback: settings.codexCookieHeader) let claudeHeader = Self.manualCookieHeader( provider: .claude, settings: settings, override: tokenOverride, fallback: settings.claudeCookieHeader) let cursorHeader = Self.manualCookieHeader( provider: .cursor, settings: settings, override: tokenOverride, fallback: settings.cursorCookieHeader) let opencodeHeader = Self.manualCookieHeader( provider: .opencode, settings: settings, override: tokenOverride, fallback: settings.opencodeCookieHeader) let factoryHeader = Self.manualCookieHeader( provider: .factory, settings: settings, override: tokenOverride, fallback: settings.factoryCookieHeader) let minimaxHeader = Self.manualCookieHeader( provider: .minimax, settings: settings, override: tokenOverride, fallback: settings.minimaxCookieHeader) let augmentHeader = Self.manualCookieHeader( provider: .augment, settings: settings, override: tokenOverride, fallback: settings.augmentCookieHeader) let ampHeader = Self.manualCookieHeader( provider: .amp, settings: settings, override: tokenOverride, fallback: settings.ampCookieHeader) settings.ensureKimiAuthTokenLoaded() let kimiHeader = settings.kimiManualCookieHeader return ProviderSettingsSnapshot.make( debugMenuEnabled: settings.debugMenuEnabled, debugKeepCLISessionsAlive: settings.debugKeepCLISessionsAlive, codex: ProviderSettingsSnapshot.CodexProviderSettings( usageDataSource: settings.codexUsageDataSource, cookieSource: Self.cookieSource( provider: .codex, settings: settings, override: tokenOverride, fallback: settings.codexCookieSource), manualCookieHeader: codexHeader), claude: ProviderSettingsSnapshot.ClaudeProviderSettings( usageDataSource: settings.claudeUsageDataSource, webExtrasEnabled: settings.claudeWebExtrasEnabled, cookieSource: Self.cookieSource( provider: .claude, settings: settings, override: tokenOverride, fallback: settings.claudeCookieSource), manualCookieHeader: claudeHeader), cursor: ProviderSettingsSnapshot.CursorProviderSettings( cookieSource: Self.cookieSource( provider: .cursor, settings: settings, override: tokenOverride, fallback: settings.cursorCookieSource), manualCookieHeader: cursorHeader), opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings( cookieSource: Self.cookieSource( provider: .opencode, settings: settings, override: tokenOverride, fallback: settings.opencodeCookieSource), manualCookieHeader: opencodeHeader, workspaceID: settings.opencodeWorkspaceID), factory: ProviderSettingsSnapshot.FactoryProviderSettings( cookieSource: Self.cookieSource( provider: .factory, settings: settings, override: tokenOverride, fallback: settings.factoryCookieSource), manualCookieHeader: factoryHeader), minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings( cookieSource: Self.cookieSource( provider: .minimax, settings: settings, override: tokenOverride, fallback: settings.minimaxCookieSource), manualCookieHeader: minimaxHeader, apiRegion: settings.minimaxAPIRegion), zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: settings.zaiAPIRegion), copilot: ProviderSettingsSnapshot.CopilotProviderSettings(), kimi: ProviderSettingsSnapshot.KimiProviderSettings( cookieSource: Self.cookieSource( provider: .kimi, settings: settings, override: tokenOverride, fallback: settings.kimiCookieSource), manualCookieHeader: kimiHeader), augment: ProviderSettingsSnapshot.AugmentProviderSettings( cookieSource: Self.cookieSource( provider: .augment, settings: settings, override: tokenOverride, fallback: settings.augmentCookieSource), manualCookieHeader: augmentHeader), amp: ProviderSettingsSnapshot.AmpProviderSettings( cookieSource: Self.cookieSource( provider: .amp, settings: settings, override: tokenOverride, fallback: settings.ampCookieSource), manualCookieHeader: ampHeader), jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings( ideBasePath: settings.jetbrainsIDEBasePath.isEmpty ? nil : settings.jetbrainsIDEBasePath)) } @MainActor static func makeEnvironment( base: [String: String], provider: UsageProvider, settings: SettingsStore, tokenOverride: TokenAccountOverride?) -> [String: String] { let account = Self.selectedTokenAccount( provider: provider, settings: settings, override: tokenOverride) var env = base if let account, let override = TokenAccountSupportCatalog.envOverride( for: provider, token: account.token) { for (key, value) in override { env[key] = value } } return ProviderConfigEnvironment.applyAPIKeyOverride( base: env, provider: provider, config: settings.providerConfig(for: provider)) } @MainActor private static func selectedTokenAccount( provider: UsageProvider, settings: SettingsStore, override: TokenAccountOverride?) -> ProviderTokenAccount? { if let override, override.provider == provider { return override.account } return settings.selectedTokenAccount(for: provider) } @MainActor private static func manualCookieHeader( provider: UsageProvider, settings: SettingsStore, override: TokenAccountOverride?, fallback: String) -> String { guard let support = TokenAccountSupportCatalog.support(for: provider), case .cookieHeader = support.injection else { return fallback } if let account = Self.selectedTokenAccount(provider: provider, settings: settings, override: override) { if provider == .claude, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) { return "" } return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support) } return fallback } @MainActor private static func cookieSource( provider: UsageProvider, settings: SettingsStore, override: TokenAccountOverride?, fallback: ProviderCookieSource) -> ProviderCookieSource { guard let support = TokenAccountSupportCatalog.support(for: provider), support.requiresManualCookieSource else { return fallback } if provider == .claude, let account = Self.selectedTokenAccount(provider: provider, settings: settings, override: override), TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) { return .off } if settings.tokenAccounts(for: provider).isEmpty { return fallback } return .manual }}
import CodexBarCore
import Foundation
struct TokenAccountOverride: Sendable {
let provider: UsageProvider
let account: ProviderTokenAccount
}
struct ProviderSpec {
let style: IconStyle
let isEnabled: @MainActor () -> Bool
let fetch: () async -> ProviderFetchOutcome
}
struct ProviderRegistry {
let metadata: [UsageProvider: ProviderMetadata]
static let shared: ProviderRegistry = .init()
init(metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) {
self.metadata = metadata
}
@MainActor
func specs(
settings: SettingsStore,
metadata: [UsageProvider: ProviderMetadata],
codexFetcher: UsageFetcher,
claudeFetcher: any ClaudeUsageFetching,
browserDetection: BrowserDetection) -> [UsageProvider: ProviderSpec]
{
var specs: [UsageProvider: ProviderSpec] = [:]
specs.reserveCapacity(UsageProvider.allCases.count)
for provider in UsageProvider.allCases {
let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)
let meta = metadata[provider]!
let spec = ProviderSpec(
style: descriptor.branding.iconStyle,
isEnabled: { settings.isProviderEnabled(provider: provider, metadata: meta) },
fetch: {
let sourceMode: ProviderSourceMode = switch provider {
case .codex:
switch settings.codexUsageDataSource {
case .auto: .auto
case .oauth: .oauth
case .cli: .cli
}
case .claude:
switch settings.claudeUsageDataSource {
case .auto: .auto
case .oauth: .oauth
case .web: .web
case .cli: .cli
}
default:
.auto
}
let snapshot = await MainActor.run {
Self.makeSettingsSnapshot(settings: settings, tokenOverride: nil)
}
let env = await MainActor.run {
Self.makeEnvironment(
base: ProcessInfo.processInfo.environment,
provider: provider,
settings: settings,
tokenOverride: nil)
}
let verbose = settings.isVerboseLoggingEnabled
let context = ProviderFetchContext(
runtime: .app,
sourceMode: sourceMode,
includeCredits: false,
webTimeout: 60,
webDebugDumpHTML: false,
verbose: verbose,
env: env,
settings: snapshot,
fetcher: codexFetcher,
claudeFetcher: claudeFetcher,
browserDetection: browserDetection)
return await descriptor.fetchOutcome(context: context)
})
specs[provider] = spec
}
return specs
}
private static let defaultMetadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata
@MainActor
static func makeSettingsSnapshot(
settings: SettingsStore,
tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot
{
settings.ensureTokenAccountsLoaded()
let codexHeader = Self.manualCookieHeader(
provider: .codex,
settings: settings,
override: tokenOverride,
fallback: settings.codexCookieHeader)
let claudeHeader = Self.manualCookieHeader(
provider: .claude,
settings: settings,
override: tokenOverride,
fallback: settings.claudeCookieHeader)
let cursorHeader = Self.manualCookieHeader(
provider: .cursor,
settings: settings,
override: tokenOverride,
fallback: settings.cursorCookieHeader)
let opencodeHeader = Self.manualCookieHeader(
provider: .opencode,
settings: settings,
override: tokenOverride,
fallback: settings.opencodeCookieHeader)
let factoryHeader = Self.manualCookieHeader(
provider: .factory,
settings: settings,
override: tokenOverride,
fallback: settings.factoryCookieHeader)
let minimaxHeader = Self.manualCookieHeader(
provider: .minimax,
settings: settings,
override: tokenOverride,
fallback: settings.minimaxCookieHeader)
let augmentHeader = Self.manualCookieHeader(
provider: .augment,
settings: settings,
override: tokenOverride,
fallback: settings.augmentCookieHeader)
let ampHeader = Self.manualCookieHeader(
provider: .amp,
settings: settings,
override: tokenOverride,
fallback: settings.ampCookieHeader)
settings.ensureKimiAuthTokenLoaded()
let kimiHeader = settings.kimiManualCookieHeader
return ProviderSettingsSnapshot.make(
debugMenuEnabled: settings.debugMenuEnabled,
debugKeepCLISessionsAlive: settings.debugKeepCLISessionsAlive,
codex: ProviderSettingsSnapshot.CodexProviderSettings(
usageDataSource: settings.codexUsageDataSource,
cookieSource: Self.cookieSource(
provider: .codex,
settings: settings,
override: tokenOverride,
fallback: settings.codexCookieSource),
manualCookieHeader: codexHeader),
claude: ProviderSettingsSnapshot.ClaudeProviderSettings(
usageDataSource: settings.claudeUsageDataSource,
webExtrasEnabled: settings.claudeWebExtrasEnabled,
cookieSource: Self.cookieSource(
provider: .claude,
settings: settings,
override: tokenOverride,
fallback: settings.claudeCookieSource),
manualCookieHeader: claudeHeader),
cursor: ProviderSettingsSnapshot.CursorProviderSettings(
cookieSource: Self.cookieSource(
provider: .cursor,
settings: settings,
override: tokenOverride,
fallback: settings.cursorCookieSource),
manualCookieHeader: cursorHeader),
opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings(
cookieSource: Self.cookieSource(
provider: .opencode,
settings: settings,
override: tokenOverride,
fallback: settings.opencodeCookieSource),
manualCookieHeader: opencodeHeader,
workspaceID: settings.opencodeWorkspaceID),
factory: ProviderSettingsSnapshot.FactoryProviderSettings(
cookieSource: Self.cookieSource(
provider: .factory,
settings: settings,
override: tokenOverride,
fallback: settings.factoryCookieSource),
manualCookieHeader: factoryHeader),
minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings(
cookieSource: Self.cookieSource(
provider: .minimax,
settings: settings,
override: tokenOverride,
fallback: settings.minimaxCookieSource),
manualCookieHeader: minimaxHeader,
apiRegion: settings.minimaxAPIRegion),
zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: settings.zaiAPIRegion),
copilot: ProviderSettingsSnapshot.CopilotProviderSettings(),
kimi: ProviderSettingsSnapshot.KimiProviderSettings(
cookieSource: Self.cookieSource(
provider: .kimi,
settings: settings,
override: tokenOverride,
fallback: settings.kimiCookieSource),
manualCookieHeader: kimiHeader),
augment: ProviderSettingsSnapshot.AugmentProviderSettings(
cookieSource: Self.cookieSource(
provider: .augment,
settings: settings,
override: tokenOverride,
fallback: settings.augmentCookieSource),
manualCookieHeader: augmentHeader),
amp: ProviderSettingsSnapshot.AmpProviderSettings(
cookieSource: Self.cookieSource(
provider: .amp,
settings: settings,
override: tokenOverride,
fallback: settings.ampCookieSource),
manualCookieHeader: ampHeader),
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings(
ideBasePath: settings.jetbrainsIDEBasePath.isEmpty ? nil : settings.jetbrainsIDEBasePath))
}
@MainActor
static func makeEnvironment(
base: [String: String],
provider: UsageProvider,
settings: SettingsStore,
tokenOverride: TokenAccountOverride?) -> [String: String]
{
let account = Self.selectedTokenAccount(
provider: provider,
settings: settings,
override: tokenOverride)
var env = base
if let account, let override = TokenAccountSupportCatalog.envOverride(
for: provider,
token: account.token)
{
for (key, value) in override {
env[key] = value
}
}
return ProviderConfigEnvironment.applyAPIKeyOverride(
base: env,
provider: provider,
config: settings.providerConfig(for: provider))
}
@MainActor
private static func selectedTokenAccount(
provider: UsageProvider,
settings: SettingsStore,
override: TokenAccountOverride?) -> ProviderTokenAccount?
{
if let override, override.provider == provider { return override.account }
return settings.selectedTokenAccount(for: provider)
}
@MainActor
private static func manualCookieHeader(
provider: UsageProvider,
settings: SettingsStore,
override: TokenAccountOverride?,
fallback: String) -> String
{
guard let support = TokenAccountSupportCatalog.support(for: provider),
case .cookieHeader = support.injection
else {
return fallback
}
if let account = Self.selectedTokenAccount(provider: provider, settings: settings, override: override) {
if provider == .claude, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) {
return ""
}
return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)
}
return fallback
}
@MainActor
private static func cookieSource(
provider: UsageProvider,
settings: SettingsStore,
override: TokenAccountOverride?,
fallback: ProviderCookieSource) -> ProviderCookieSource
{
guard let support = TokenAccountSupportCatalog.support(for: provider),
support.requiresManualCookieSource
else {
return fallback
}
if provider == .claude,
let account = Self.selectedTokenAccount(provider: provider, settings: settings, override: override),
TokenAccountSupportCatalog.isClaudeOAuthToken(account.token)
{
return .off
}
if settings.tokenAccounts(for: provider).isEmpty { return fallback }
return .manual
}
}