chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
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)),
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user