import Foundation public enum OpenAIAPIProviderDescriptor { public static let descriptor: ProviderDescriptor = Self.makeDescriptor() static func makeDescriptor() -> ProviderDescriptor { ProviderDescriptor( id: .openai, metadata: ProviderMetadata( id: .openai, displayName: "OpenAI", sessionLabel: "Spend", weeklyLabel: "Requests", opusLabel: nil, supportsOpus: false, supportsCredits: false, creditsHint: "", toggleTitle: "Show OpenAI usage", cliName: "openai", defaultEnabled: false, isPrimaryProvider: false, usesAccountFallback: false, dashboardURL: "https://platform.openai.com/usage", statusPageURL: "https://status.openai.com"), branding: ProviderBranding( iconStyle: .openai, iconResourceName: "ProviderIcon-codex", color: ProviderColor(red: 0.06, green: 0.51, blue: 0.43), confettiPalette: [ ProviderColor(hex: 0x000000), ProviderColor(hex: 0x808080), ProviderColor(hex: 0xFFFFFF), ]), tokenCost: ProviderTokenCostConfig( supportsTokenCost: true, noDataMessage: { "OpenAI usage needs an Admin API key for organization usage." }), fetchPlan: ProviderFetchPlan( sourceModes: [.auto, .api], pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [OpenAIAPIBalanceFetchStrategy()] })), cli: ProviderCLIConfig( name: "openai", aliases: ["openai-api"], versionDetector: nil)) }} struct OpenAIAPIBalanceFetchStrategy: ProviderFetchStrategy { let id: String = "openai.api.balance" let kind: ProviderFetchKind = .apiToken let usageFetcher: @Sendable (OpenAIAPIUsageCredential, Int) async throws -> OpenAIAPIUsageSnapshot let balanceFetcher: @Sendable (String) async throws -> OpenAIAPICreditBalanceSnapshot init( usageFetcher: @escaping @Sendable (OpenAIAPIUsageCredential, Int) async throws -> OpenAIAPIUsageSnapshot = OpenAIAPIBalanceFetchStrategy.fetchUsage(credential:days:), balanceFetcher: @escaping @Sendable (String) async throws -> OpenAIAPICreditBalanceSnapshot = { apiKey in try await OpenAIAPICreditBalanceFetcher.fetchBalance(apiKey: apiKey) }) { self.usageFetcher = usageFetcher self.balanceFetcher = balanceFetcher } func isAvailable(_ context: ProviderFetchContext) async -> Bool { OpenAIAPIUsageCredential(environment: context.env) != nil } func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult { guard let credential = OpenAIAPIUsageCredential(environment: context.env) else { throw OpenAIAPISettingsError.missingToken } do { let usage = try await self.usageFetcher(credential, context.costUsageHistoryDays) return self.makeResult( usage: usage.toUsageSnapshot(), sourceLabel: credential.sourceLabel) } catch { let usageError = error if !credential.allowsLegacyBalanceFallback { throw usageError } // Preserve the older balance-only path for unscoped keys and Admin API outages. do { let balance = try await self.balanceFetcher(credential.apiKey) return self.makeResult( usage: balance.toUsageSnapshot(), sourceLabel: "billing-api") } catch { if (usageError as? OpenAIAPIUsageError)?.isCredentialRejected != true { throw usageError } throw error } } } func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool { false } private static func fetchUsage( credential: OpenAIAPIUsageCredential, days: Int) async throws -> OpenAIAPIUsageSnapshot { try await OpenAIAPIUsageFetcher.fetchUsage( apiKey: credential.apiKey, projectID: credential.projectID, historyDays: days) }} struct OpenAIAPIUsageCredential: Equatable { let apiKey: String let projectID: String? let usesAdminKey: Bool init?(environment: [String: String]) { if let adminKey = OpenAIAPISettingsReader.adminAPIKey(environment: environment) { self.apiKey = adminKey self.usesAdminKey = true } else if let apiKey = OpenAIAPISettingsReader.apiKey(environment: environment) { self.apiKey = apiKey self.usesAdminKey = false } else { return nil } self.projectID = OpenAIAPISettingsReader.projectID(environment: environment) } var sourceLabel: String { self.projectID == nil ? "admin-api" : "admin-api:project" } var allowsLegacyBalanceFallback: Bool { self.projectID == nil || !self.usesAdminKey }}
import Foundation
public enum OpenAIAPIProviderDescriptor {
public static let descriptor: ProviderDescriptor = Self.makeDescriptor()
static func makeDescriptor() -> ProviderDescriptor {
ProviderDescriptor(
id: .openai,
metadata: ProviderMetadata(
id: .openai,
displayName: "OpenAI",
sessionLabel: "Spend",
weeklyLabel: "Requests",
opusLabel: nil,
supportsOpus: false,
supportsCredits: false,
creditsHint: "",
toggleTitle: "Show OpenAI usage",
cliName: "openai",
defaultEnabled: false,
isPrimaryProvider: false,
usesAccountFallback: false,
dashboardURL: "https://platform.openai.com/usage",
statusPageURL: "https://status.openai.com"),
branding: ProviderBranding(
iconStyle: .openai,
iconResourceName: "ProviderIcon-codex",
color: ProviderColor(red: 0.06, green: 0.51, blue: 0.43),
confettiPalette: [
ProviderColor(hex: 0x000000),
ProviderColor(hex: 0x808080),
ProviderColor(hex: 0xFFFFFF),
]),
tokenCost: ProviderTokenCostConfig(
supportsTokenCost: true,
noDataMessage: { "OpenAI usage needs an Admin API key for organization usage." }),
fetchPlan: ProviderFetchPlan(
sourceModes: [.auto, .api],
pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [OpenAIAPIBalanceFetchStrategy()] })),
cli: ProviderCLIConfig(
name: "openai",
aliases: ["openai-api"],
versionDetector: nil))
}
}
struct OpenAIAPIBalanceFetchStrategy: ProviderFetchStrategy {
let id: String = "openai.api.balance"
let kind: ProviderFetchKind = .apiToken
let usageFetcher: @Sendable (OpenAIAPIUsageCredential, Int) async throws -> OpenAIAPIUsageSnapshot
let balanceFetcher: @Sendable (String) async throws -> OpenAIAPICreditBalanceSnapshot
init(
usageFetcher: @escaping @Sendable (OpenAIAPIUsageCredential, Int) async throws -> OpenAIAPIUsageSnapshot =
OpenAIAPIBalanceFetchStrategy.fetchUsage(credential:days:),
balanceFetcher: @escaping @Sendable (String) async throws -> OpenAIAPICreditBalanceSnapshot = { apiKey in
try await OpenAIAPICreditBalanceFetcher.fetchBalance(apiKey: apiKey)
})
{
self.usageFetcher = usageFetcher
self.balanceFetcher = balanceFetcher
}
func isAvailable(_ context: ProviderFetchContext) async -> Bool {
OpenAIAPIUsageCredential(environment: context.env) != nil
}
func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
guard let credential = OpenAIAPIUsageCredential(environment: context.env) else {
throw OpenAIAPISettingsError.missingToken
}
do {
let usage = try await self.usageFetcher(credential, context.costUsageHistoryDays)
return self.makeResult(
usage: usage.toUsageSnapshot(),
sourceLabel: credential.sourceLabel)
} catch {
let usageError = error
if !credential.allowsLegacyBalanceFallback {
throw usageError
}
// Preserve the older balance-only path for unscoped keys and Admin API outages.
do {
let balance = try await self.balanceFetcher(credential.apiKey)
return self.makeResult(
usage: balance.toUsageSnapshot(),
sourceLabel: "billing-api")
} catch {
if (usageError as? OpenAIAPIUsageError)?.isCredentialRejected != true {
throw usageError
}
throw error
}
}
}
func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
false
}
private static func fetchUsage(
credential: OpenAIAPIUsageCredential,
days: Int) async throws -> OpenAIAPIUsageSnapshot
{
try await OpenAIAPIUsageFetcher.fetchUsage(
apiKey: credential.apiKey,
projectID: credential.projectID,
historyDays: days)
}
}
struct OpenAIAPIUsageCredential: Equatable {
let apiKey: String
let projectID: String?
let usesAdminKey: Bool
init?(environment: [String: String]) {
if let adminKey = OpenAIAPISettingsReader.adminAPIKey(environment: environment) {
self.apiKey = adminKey
self.usesAdminKey = true
} else if let apiKey = OpenAIAPISettingsReader.apiKey(environment: environment) {
self.apiKey = apiKey
self.usesAdminKey = false
} else {
return nil
}
self.projectID = OpenAIAPISettingsReader.projectID(environment: environment)
}
var sourceLabel: String {
self.projectID == nil ? "admin-api" : "admin-api:project"
}
var allowsLegacyBalanceFallback: Bool {
self.projectID == nil || !self.usesAdminKey
}
}