chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
|
||||
public enum CodexActiveSource: Codable, Equatable, Sendable {
|
||||
case liveSystem
|
||||
case managedAccount(id: UUID)
|
||||
case profileHome(path: String)
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case kind
|
||||
case accountID
|
||||
case homePath
|
||||
}
|
||||
|
||||
private enum Kind: String, Codable {
|
||||
case liveSystem
|
||||
case managedAccount
|
||||
case profileHome
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
switch try container.decode(Kind.self, forKey: .kind) {
|
||||
case .liveSystem:
|
||||
if let path = try container.decodeIfPresent(String.self, forKey: .homePath),
|
||||
!path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
{
|
||||
self = .profileHome(path: path)
|
||||
} else {
|
||||
self = .liveSystem
|
||||
}
|
||||
case .managedAccount:
|
||||
let id = try container.decode(UUID.self, forKey: .accountID)
|
||||
self = .managedAccount(id: id)
|
||||
case .profileHome:
|
||||
let path = try container.decode(String.self, forKey: .homePath)
|
||||
if path.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
self = .liveSystem
|
||||
} else {
|
||||
self = .profileHome(path: path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .liveSystem:
|
||||
try container.encode(Kind.liveSystem, forKey: .kind)
|
||||
case let .managedAccount(id):
|
||||
try container.encode(Kind.managedAccount, forKey: .kind)
|
||||
try container.encode(id, forKey: .accountID)
|
||||
case let .profileHome(path):
|
||||
// Released builds only understand liveSystem/managedAccount. Keep the envelope
|
||||
// downgrade-readable while newer builds recover the profile selection from homePath.
|
||||
try container.encode(Kind.liveSystem, forKey: .kind)
|
||||
try container.encode(path, forKey: .homePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
import Foundation
|
||||
|
||||
public struct CodexBarConfig: Codable, Sendable {
|
||||
public static let currentVersion = 1
|
||||
|
||||
public var version: Int
|
||||
public var providers: [ProviderConfig]
|
||||
|
||||
public init(version: Int = Self.currentVersion, providers: [ProviderConfig]) {
|
||||
self.version = version
|
||||
self.providers = providers
|
||||
}
|
||||
|
||||
public static func makeDefault(
|
||||
metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) -> CodexBarConfig
|
||||
{
|
||||
let providers = UsageProvider.allCases.map { provider in
|
||||
Self.defaultProviderConfig(
|
||||
provider,
|
||||
metadata: metadata,
|
||||
alibabaTokenPlanRegion: .international)
|
||||
}
|
||||
return CodexBarConfig(version: Self.currentVersion, providers: providers)
|
||||
}
|
||||
|
||||
/// Alphabetical provider ordering with enabled providers on top: enabled first, then disabled,
|
||||
/// each group sorted case-insensitively by display name. Used by the Providers settings pane's
|
||||
/// alphabetical sort toggle; it never mutates the user's stored manual order.
|
||||
public static func alphabeticalProviderOrder(
|
||||
metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata,
|
||||
enablement: (UsageProvider) -> Bool) -> [UsageProvider]
|
||||
{
|
||||
UsageProvider.allCases.sorted { lhs, rhs in
|
||||
let lhsEnabled = enablement(lhs)
|
||||
let rhsEnabled = enablement(rhs)
|
||||
if lhsEnabled != rhsEnabled { return lhsEnabled }
|
||||
let lhsName = metadata[lhs]?.displayName ?? lhs.rawValue
|
||||
let rhsName = metadata[rhs]?.displayName ?? rhs.rawValue
|
||||
switch lhsName.localizedCaseInsensitiveCompare(rhsName) {
|
||||
case .orderedAscending: return true
|
||||
case .orderedDescending: return false
|
||||
case .orderedSame: return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func normalized(
|
||||
metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) -> CodexBarConfig
|
||||
{
|
||||
var seen: Set<UsageProvider> = []
|
||||
var normalized: [ProviderConfig] = []
|
||||
normalized.reserveCapacity(max(self.providers.count, UsageProvider.allCases.count))
|
||||
|
||||
for provider in self.providers {
|
||||
guard !seen.contains(provider.id) else { continue }
|
||||
seen.insert(provider.id)
|
||||
normalized.append(provider)
|
||||
}
|
||||
|
||||
for provider in UsageProvider.allCases where !seen.contains(provider) {
|
||||
normalized.append(Self.defaultProviderConfig(
|
||||
provider,
|
||||
metadata: metadata,
|
||||
alibabaTokenPlanRegion: .chinaMainland))
|
||||
}
|
||||
|
||||
return CodexBarConfig(
|
||||
version: Self.currentVersion,
|
||||
providers: normalized)
|
||||
}
|
||||
|
||||
public func orderedProviders() -> [UsageProvider] {
|
||||
self.providers.map(\.id)
|
||||
}
|
||||
|
||||
public func enabledProviders(
|
||||
metadata: [UsageProvider: ProviderMetadata] = ProviderDescriptorRegistry.metadata) -> [UsageProvider]
|
||||
{
|
||||
self.providers.compactMap { config in
|
||||
let enabled = config.enabled ?? metadata[config.id]?.defaultEnabled ?? false
|
||||
return enabled ? config.id : nil
|
||||
}
|
||||
}
|
||||
|
||||
public func providerConfig(for id: UsageProvider) -> ProviderConfig? {
|
||||
self.providers.first(where: { $0.id == id })
|
||||
}
|
||||
|
||||
public mutating func setProviderConfig(_ config: ProviderConfig) {
|
||||
if let index = self.providers.firstIndex(where: { $0.id == config.id }) {
|
||||
self.providers[index] = config
|
||||
} else {
|
||||
self.providers.append(config)
|
||||
}
|
||||
}
|
||||
|
||||
private static func defaultProviderConfig(
|
||||
_ provider: UsageProvider,
|
||||
metadata: [UsageProvider: ProviderMetadata],
|
||||
alibabaTokenPlanRegion: AlibabaTokenPlanAPIRegion) -> ProviderConfig
|
||||
{
|
||||
ProviderConfig(
|
||||
id: provider,
|
||||
enabled: metadata[provider]?.defaultEnabled,
|
||||
region: provider == .alibabatokenplan ? alibabaTokenPlanRegion.rawValue : nil)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ProviderConfig: Codable, Sendable, Identifiable {
|
||||
public let id: UsageProvider
|
||||
public var enabled: Bool?
|
||||
public var source: ProviderSourceMode?
|
||||
public var extrasEnabled: Bool?
|
||||
public var apiKey: String?
|
||||
public var secretKey: String?
|
||||
public var cookieHeader: String?
|
||||
public var cookieSource: ProviderCookieSource?
|
||||
public var region: String?
|
||||
public var workspaceID: String?
|
||||
public var enterpriseHost: String?
|
||||
public var tokenAccounts: ProviderTokenAccountData?
|
||||
public var claudeSwapEnabled: Bool?
|
||||
public var claudeSwapExecutablePath: String?
|
||||
public var codexActiveSource: CodexActiveSource?
|
||||
public var codexProfileHomePaths: [String]?
|
||||
public var quotaWarnings: QuotaWarningConfig?
|
||||
public var kiloKnownOrganizations: [KiloOrganization]?
|
||||
public var kiloEnabledOrganizationIDs: [String]?
|
||||
public var awsProfile: String?
|
||||
public var awsAuthMode: String?
|
||||
|
||||
public init(
|
||||
id: UsageProvider,
|
||||
enabled: Bool? = nil,
|
||||
source: ProviderSourceMode? = nil,
|
||||
extrasEnabled: Bool? = nil,
|
||||
apiKey: String? = nil,
|
||||
secretKey: String? = nil,
|
||||
cookieHeader: String? = nil,
|
||||
cookieSource: ProviderCookieSource? = nil,
|
||||
region: String? = nil,
|
||||
workspaceID: String? = nil,
|
||||
enterpriseHost: String? = nil,
|
||||
tokenAccounts: ProviderTokenAccountData? = nil,
|
||||
claudeSwapEnabled: Bool? = nil,
|
||||
claudeSwapExecutablePath: String? = nil,
|
||||
codexActiveSource: CodexActiveSource? = nil,
|
||||
codexProfileHomePaths: [String]? = nil,
|
||||
quotaWarnings: QuotaWarningConfig? = nil,
|
||||
kiloKnownOrganizations: [KiloOrganization]? = nil,
|
||||
kiloEnabledOrganizationIDs: [String]? = nil,
|
||||
awsProfile: String? = nil,
|
||||
awsAuthMode: String? = nil)
|
||||
{
|
||||
self.id = id
|
||||
self.enabled = enabled
|
||||
self.source = source
|
||||
self.extrasEnabled = extrasEnabled
|
||||
self.apiKey = apiKey
|
||||
self.secretKey = secretKey
|
||||
self.cookieHeader = cookieHeader
|
||||
self.cookieSource = cookieSource
|
||||
self.region = region
|
||||
self.workspaceID = workspaceID
|
||||
self.enterpriseHost = enterpriseHost
|
||||
self.tokenAccounts = tokenAccounts
|
||||
self.claudeSwapEnabled = claudeSwapEnabled
|
||||
self.claudeSwapExecutablePath = claudeSwapExecutablePath
|
||||
self.codexActiveSource = codexActiveSource
|
||||
self.codexProfileHomePaths = codexProfileHomePaths
|
||||
self.quotaWarnings = quotaWarnings
|
||||
self.kiloKnownOrganizations = kiloKnownOrganizations
|
||||
self.kiloEnabledOrganizationIDs = kiloEnabledOrganizationIDs
|
||||
self.awsProfile = awsProfile
|
||||
self.awsAuthMode = awsAuthMode
|
||||
}
|
||||
|
||||
public var sanitizedAPIKey: String? {
|
||||
Self.clean(self.apiKey)
|
||||
}
|
||||
|
||||
public var sanitizedSecretKey: String? {
|
||||
Self.clean(self.secretKey)
|
||||
}
|
||||
|
||||
public var sanitizedCookieHeader: String? {
|
||||
Self.clean(self.cookieHeader)
|
||||
}
|
||||
|
||||
public var sanitizedRegion: String? {
|
||||
Self.clean(self.region)
|
||||
}
|
||||
|
||||
public var sanitizedWorkspaceID: String? {
|
||||
Self.clean(self.workspaceID)
|
||||
}
|
||||
|
||||
public var sanitizedEnterpriseHost: String? {
|
||||
Self.clean(self.enterpriseHost)
|
||||
}
|
||||
|
||||
public var sanitizedClaudeSwapExecutablePath: String? {
|
||||
Self.clean(self.claudeSwapExecutablePath)
|
||||
}
|
||||
|
||||
public var sanitizedAWSProfile: String? {
|
||||
Self.clean(self.awsProfile)
|
||||
}
|
||||
|
||||
public var sanitizedAWSAuthMode: String? {
|
||||
Self.clean(self.awsAuthMode)
|
||||
}
|
||||
|
||||
private static func clean(_ raw: String?) -> String? {
|
||||
guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
|
||||
(value.hasPrefix("'") && value.hasSuffix("'"))
|
||||
{
|
||||
value = String(value.dropFirst().dropLast())
|
||||
}
|
||||
value = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return value.isEmpty ? nil : value
|
||||
}
|
||||
}
|
||||
|
||||
public enum QuotaWarningWindow: String, Codable, Sendable, CaseIterable {
|
||||
case session
|
||||
case weekly
|
||||
|
||||
public var displayName: String {
|
||||
switch self {
|
||||
case .session:
|
||||
"session"
|
||||
case .weekly:
|
||||
"weekly"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct QuotaWarningWindowConfig: Codable, Sendable, Equatable {
|
||||
public var thresholds: [Int]?
|
||||
public var enabled: Bool?
|
||||
|
||||
public init(thresholds: [Int]? = nil, enabled: Bool? = nil) {
|
||||
self.thresholds = thresholds.map(QuotaWarningThresholds.sanitized)
|
||||
self.enabled = enabled
|
||||
}
|
||||
|
||||
public var hasOverride: Bool {
|
||||
self.thresholds != nil || self.enabled != nil
|
||||
}
|
||||
|
||||
public func isEnabled(global: Bool) -> Bool {
|
||||
self.enabled ?? (self.thresholds != nil ? true : global)
|
||||
}
|
||||
}
|
||||
|
||||
public struct QuotaWarningConfig: Codable, Sendable, Equatable {
|
||||
public var session: QuotaWarningWindowConfig?
|
||||
public var weekly: QuotaWarningWindowConfig?
|
||||
|
||||
public init(
|
||||
session: QuotaWarningWindowConfig? = nil,
|
||||
weekly: QuotaWarningWindowConfig? = nil)
|
||||
{
|
||||
self.session = session
|
||||
self.weekly = weekly
|
||||
}
|
||||
|
||||
public func thresholds(for window: QuotaWarningWindow, global: [Int]) -> [Int] {
|
||||
switch window {
|
||||
case .session:
|
||||
QuotaWarningThresholds.sanitized(self.session?.thresholds ?? global)
|
||||
case .weekly:
|
||||
QuotaWarningThresholds.sanitized(self.weekly?.thresholds ?? global)
|
||||
}
|
||||
}
|
||||
|
||||
public func isEnabled(for window: QuotaWarningWindow, global: Bool) -> Bool {
|
||||
switch window {
|
||||
case .session:
|
||||
self.session?.isEnabled(global: global) ?? global
|
||||
case .weekly:
|
||||
self.weekly?.isEnabled(global: global) ?? global
|
||||
}
|
||||
}
|
||||
|
||||
public func hasOverride(for window: QuotaWarningWindow) -> Bool {
|
||||
switch window {
|
||||
case .session:
|
||||
self.session?.hasOverride ?? false
|
||||
case .weekly:
|
||||
self.weekly?.hasOverride ?? false
|
||||
}
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
self.session?.hasOverride != true && self.weekly?.hasOverride != true
|
||||
}
|
||||
}
|
||||
|
||||
public enum QuotaWarningThresholds {
|
||||
public static let defaults = [50, 20]
|
||||
public static let allowedRange = 0...99
|
||||
|
||||
public static func sanitized(_ raw: [Int]) -> [Int] {
|
||||
guard !raw.isEmpty else { return self.defaults }
|
||||
|
||||
let unique = Set(raw.map(self.clamped))
|
||||
let sorted = unique.sorted(by: >)
|
||||
return sorted.isEmpty ? self.defaults : sorted
|
||||
}
|
||||
|
||||
public static func active(_ raw: [Int]) -> [Int] {
|
||||
self.sanitized(raw).filter { $0 > 0 }
|
||||
}
|
||||
|
||||
public static func resolved(upper: Int?, lower: Int?) -> [Int] {
|
||||
guard upper != nil || lower != nil else { return self.defaults }
|
||||
|
||||
let resolvedUpper = self.clamped(upper ?? self.defaults[0])
|
||||
let lowerDefault = resolvedUpper < self.defaults[1] ? 0 : self.defaults[1]
|
||||
let resolvedLower = self.clamped(lower ?? lowerDefault)
|
||||
return self.sanitized([resolvedUpper, resolvedLower])
|
||||
}
|
||||
|
||||
public static func clamped(_ value: Int) -> Int {
|
||||
min(max(value, self.allowedRange.lowerBound), self.allowedRange.upperBound)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import Foundation
|
||||
|
||||
public enum CodexBarConfigStoreError: LocalizedError {
|
||||
case invalidURL
|
||||
case decodeFailed(String)
|
||||
case encodeFailed(String)
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidURL:
|
||||
"Invalid CodexBar config path."
|
||||
case let .decodeFailed(details):
|
||||
"Failed to decode CodexBar config: \(details)"
|
||||
case let .encodeFailed(details):
|
||||
"Failed to encode CodexBar config: \(details)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CodexBarConfigStore: @unchecked Sendable {
|
||||
public static let pathEnvironmentKey = "CODEXBAR_CONFIG"
|
||||
public static let xdgConfigHomeEnvironmentKey = "XDG_CONFIG_HOME"
|
||||
|
||||
public let fileURL: URL
|
||||
private let fileManager: FileManager
|
||||
|
||||
public init(fileURL: URL = Self.defaultURL(), fileManager: FileManager = .default) {
|
||||
self.fileURL = fileURL
|
||||
self.fileManager = fileManager
|
||||
}
|
||||
|
||||
public func load() throws -> CodexBarConfig? {
|
||||
guard self.fileManager.fileExists(atPath: self.fileURL.path) else { return nil }
|
||||
let data = try Data(contentsOf: self.fileURL)
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let decoded = try decoder.decode(CodexBarConfig.self, from: data)
|
||||
return decoded.normalized()
|
||||
} catch {
|
||||
throw CodexBarConfigStoreError.decodeFailed(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
public func loadOrCreateDefault() throws -> CodexBarConfig {
|
||||
if let existing = try self.load() {
|
||||
return existing
|
||||
}
|
||||
let config = CodexBarConfig.makeDefault()
|
||||
try self.save(config)
|
||||
return config
|
||||
}
|
||||
|
||||
public func save(_ config: CodexBarConfig) throws {
|
||||
let normalized = config.normalized()
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
||||
let data: Data
|
||||
do {
|
||||
data = try encoder.encode(normalized)
|
||||
} catch {
|
||||
throw CodexBarConfigStoreError.encodeFailed(error.localizedDescription)
|
||||
}
|
||||
let directory = self.fileURL.deletingLastPathComponent()
|
||||
if !self.fileManager.fileExists(atPath: directory.path) {
|
||||
try self.fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
}
|
||||
try data.write(to: self.fileURL, options: [.atomic])
|
||||
try self.applySecurePermissionsIfNeeded()
|
||||
}
|
||||
|
||||
public func deleteIfPresent() throws {
|
||||
guard self.fileManager.fileExists(atPath: self.fileURL.path) else { return }
|
||||
try self.fileManager.removeItem(at: self.fileURL)
|
||||
}
|
||||
|
||||
public static func defaultURL(
|
||||
home: URL = FileManager.default.homeDirectoryForCurrentUser,
|
||||
environment: [String: String] = ProcessInfo.processInfo.environment,
|
||||
fileManager: FileManager = .default) -> URL
|
||||
{
|
||||
if let override = environment[pathEnvironmentKey]?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!override.isEmpty
|
||||
{
|
||||
let expanded = (override as NSString).expandingTildeInPath
|
||||
return URL(fileURLWithPath: expanded)
|
||||
}
|
||||
|
||||
if let xdgConfigHome = environment[xdgConfigHomeEnvironmentKey]?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!xdgConfigHome.isEmpty
|
||||
{
|
||||
let expanded = (xdgConfigHome as NSString).expandingTildeInPath
|
||||
if (expanded as NSString).isAbsolutePath {
|
||||
return URL(fileURLWithPath: expanded, isDirectory: true)
|
||||
.appendingPathComponent("codexbar", isDirectory: true)
|
||||
.appendingPathComponent("config.json")
|
||||
}
|
||||
}
|
||||
|
||||
let xdgDefault = home
|
||||
.appendingPathComponent(".config", isDirectory: true)
|
||||
.appendingPathComponent("codexbar", isDirectory: true)
|
||||
.appendingPathComponent("config.json")
|
||||
if fileManager.fileExists(atPath: xdgDefault.path) {
|
||||
return xdgDefault
|
||||
}
|
||||
|
||||
let legacy = home
|
||||
.appendingPathComponent(".codexbar", isDirectory: true)
|
||||
.appendingPathComponent("config.json")
|
||||
if fileManager.fileExists(atPath: legacy.path) {
|
||||
return legacy
|
||||
}
|
||||
|
||||
return xdgDefault
|
||||
}
|
||||
|
||||
private func applySecurePermissionsIfNeeded() throws {
|
||||
#if os(macOS) || os(Linux)
|
||||
try self.fileManager.setAttributes([
|
||||
.posixPermissions: NSNumber(value: Int16(0o600)),
|
||||
], ofItemAtPath: self.fileURL.path)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
import Foundation
|
||||
|
||||
public enum CodexBarConfigIssueSeverity: String, Codable, Sendable {
|
||||
case warning
|
||||
case error
|
||||
}
|
||||
|
||||
public struct CodexBarConfigIssue: Codable, Sendable, Equatable {
|
||||
public let severity: CodexBarConfigIssueSeverity
|
||||
public let provider: UsageProvider?
|
||||
public let field: String?
|
||||
public let code: String
|
||||
public let message: String
|
||||
|
||||
public init(
|
||||
severity: CodexBarConfigIssueSeverity,
|
||||
provider: UsageProvider?,
|
||||
field: String?,
|
||||
code: String,
|
||||
message: String)
|
||||
{
|
||||
self.severity = severity
|
||||
self.provider = provider
|
||||
self.field = field
|
||||
self.code = code
|
||||
self.message = message
|
||||
}
|
||||
}
|
||||
|
||||
public enum CodexBarConfigValidator {
|
||||
private static let enterpriseHostProviders: [UsageProvider] = [
|
||||
.azureopenai,
|
||||
.clawrouter,
|
||||
.copilot,
|
||||
.kimi,
|
||||
.litellm,
|
||||
.llmproxy,
|
||||
.sub2api,
|
||||
.wayfinder,
|
||||
]
|
||||
|
||||
private static let workspaceIDProviders: [UsageProvider] = [
|
||||
.azureopenai,
|
||||
.openai,
|
||||
.opencode,
|
||||
.opencodego,
|
||||
.devin,
|
||||
.deepgram,
|
||||
]
|
||||
|
||||
public static func validate(_ config: CodexBarConfig) -> [CodexBarConfigIssue] {
|
||||
var issues: [CodexBarConfigIssue] = []
|
||||
|
||||
if config.version != CodexBarConfig.currentVersion {
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .error,
|
||||
provider: nil,
|
||||
field: "version",
|
||||
code: "version_mismatch",
|
||||
message: "Unsupported config version \(config.version)."))
|
||||
}
|
||||
|
||||
for entry in config.providers {
|
||||
self.validateProvider(entry, issues: &issues)
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
private static func validateProvider(_ entry: ProviderConfig, issues: inout [CodexBarConfigIssue]) {
|
||||
let provider = entry.id
|
||||
let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)
|
||||
let supportedSources = descriptor.fetchPlan.sourceModes
|
||||
let supportsWeb = supportedSources.contains(.auto) || supportedSources.contains(.web)
|
||||
let supportsAPI = supportedSources.contains(.api)
|
||||
|
||||
if let source = entry.source, !supportedSources.contains(source) {
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .error,
|
||||
provider: provider,
|
||||
field: "source",
|
||||
code: "unsupported_source",
|
||||
message: "Source \(source.rawValue) is not supported for \(provider.rawValue)."))
|
||||
}
|
||||
|
||||
if let apiKey = entry.apiKey, !apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, !supportsAPI {
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "apiKey",
|
||||
code: "api_key_unused",
|
||||
message: "apiKey is set but \(provider.rawValue) does not support api source."))
|
||||
}
|
||||
|
||||
if let source = entry.source, source == .api, !supportsAPI {
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .error,
|
||||
provider: provider,
|
||||
field: "source",
|
||||
code: "api_source_unsupported",
|
||||
message: "Source api is not supported for \(provider.rawValue)."))
|
||||
}
|
||||
|
||||
if let source = entry.source, source == .api,
|
||||
self.providerRequiresAPIKey(provider),
|
||||
!self.hasConfiguredAPICredential(entry)
|
||||
{
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "apiKey",
|
||||
code: "api_key_missing",
|
||||
message: "Source api is selected but apiKey is missing for \(provider.rawValue)."))
|
||||
}
|
||||
|
||||
if entry.cookieSource != nil, !supportsWeb {
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "cookieSource",
|
||||
code: "cookie_source_unused",
|
||||
message: "cookieSource is set but \(provider.rawValue) does not use web cookies."))
|
||||
}
|
||||
|
||||
if let cookieHeader = entry.cookieHeader,
|
||||
!cookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||
!supportsWeb
|
||||
{
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "cookieHeader",
|
||||
code: "cookie_header_unused",
|
||||
message: "cookieHeader is set but \(provider.rawValue) does not use web cookies."))
|
||||
}
|
||||
|
||||
if let cookieSource = entry.cookieSource,
|
||||
cookieSource == .manual,
|
||||
entry.cookieHeader?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? true
|
||||
{
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "cookieHeader",
|
||||
code: "cookie_header_missing",
|
||||
message: "cookieSource manual is set but cookieHeader is missing for \(provider.rawValue)."))
|
||||
}
|
||||
|
||||
self.validateSecretKey(entry, issues: &issues)
|
||||
|
||||
self.validateSub2APIBaseURL(entry, issues: &issues)
|
||||
|
||||
self.validateRegion(entry, issues: &issues)
|
||||
|
||||
self.validateZaiTeamContext(entry, issues: &issues)
|
||||
|
||||
if let workspaceID = entry.workspaceID,
|
||||
!workspaceID.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||
!self.providerSupportsWorkspaceID(provider)
|
||||
{
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "workspaceID",
|
||||
code: "workspace_unused",
|
||||
message: "workspaceID is set but only \(self.workspaceIDProviderList) support workspaceID."))
|
||||
}
|
||||
|
||||
if let enterpriseHost = entry.enterpriseHost,
|
||||
!enterpriseHost.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||
!self.providerSupportsEnterpriseHost(provider)
|
||||
{
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "enterpriseHost",
|
||||
code: "enterprise_host_unused",
|
||||
message: "enterpriseHost is set but only \(self.enterpriseHostProviderList) support enterpriseHost."))
|
||||
}
|
||||
|
||||
if let tokenAccounts = entry.tokenAccounts, !tokenAccounts.accounts.isEmpty,
|
||||
TokenAccountSupportCatalog.support(for: provider) == nil
|
||||
{
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "tokenAccounts",
|
||||
code: "token_accounts_unused",
|
||||
message: "tokenAccounts are set but \(provider.rawValue) does not support token accounts."))
|
||||
}
|
||||
}
|
||||
|
||||
private static func validateSecretKey(_ entry: ProviderConfig, issues: inout [CodexBarConfigIssue]) {
|
||||
guard let secretKey = entry.secretKey,
|
||||
!secretKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||
entry.id != .bedrock,
|
||||
entry.id != .doubao
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: entry.id,
|
||||
field: "secretKey",
|
||||
code: "secret_key_unused",
|
||||
message: "secretKey is set but only bedrock and doubao use secretKey."))
|
||||
}
|
||||
|
||||
private static func validateSub2APIBaseURL(_ entry: ProviderConfig, issues: inout [CodexBarConfigIssue]) {
|
||||
guard entry.id == .sub2api,
|
||||
let raw = entry.enterpriseHost?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!raw.isEmpty,
|
||||
Sub2APISettingsReader.baseURL(environment: [Sub2APISettingsReader.baseURLEnvironmentKey: raw]) == nil
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .error,
|
||||
provider: .sub2api,
|
||||
field: "enterpriseHost",
|
||||
code: "invalid_enterprise_host",
|
||||
message: Sub2APISettingsError.invalidBaseURL.errorDescription ?? "Invalid sub2api base URL."))
|
||||
}
|
||||
|
||||
private static func validateZaiTeamContext(_ entry: ProviderConfig, issues: inout [CodexBarConfigIssue]) {
|
||||
guard entry.id == .zai else { return }
|
||||
|
||||
guard let tokenAccounts = entry.tokenAccounts else { return }
|
||||
for account in tokenAccounts.accounts
|
||||
where account.sanitizedUsageScope?.lowercased() == ZaiUsageScope.team.rawValue
|
||||
{
|
||||
if account.sanitizedOrganizationID == nil || account.sanitizedWorkspaceID == nil {
|
||||
issues.append(self.zaiMissingTeamContextIssue(field: "tokenAccounts"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func zaiMissingTeamContextIssue(field: String) -> CodexBarConfigIssue {
|
||||
CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: .zai,
|
||||
field: field,
|
||||
code: "zai_team_context_missing",
|
||||
message: "z.ai Team mode requires both organizationID and workspaceID.")
|
||||
}
|
||||
|
||||
private static func providerSupportsWorkspaceID(_ provider: UsageProvider) -> Bool {
|
||||
self.workspaceIDProviders.contains(provider)
|
||||
}
|
||||
|
||||
private static var workspaceIDProviderList: String {
|
||||
self.formattedProviderList(self.workspaceIDProviders)
|
||||
}
|
||||
|
||||
private static func formattedProviderList(_ providers: [UsageProvider]) -> String {
|
||||
let names = providers.map(\.rawValue)
|
||||
guard let last = names.last else { return "" }
|
||||
guard names.count > 1 else { return last }
|
||||
return "\(names.dropLast().joined(separator: ", ")), and \(last)"
|
||||
}
|
||||
|
||||
private static func providerSupportsEnterpriseHost(_ provider: UsageProvider) -> Bool {
|
||||
self.enterpriseHostProviders.contains(provider)
|
||||
}
|
||||
|
||||
private static func providerRequiresAPIKey(_ provider: UsageProvider) -> Bool {
|
||||
provider != .wayfinder
|
||||
}
|
||||
|
||||
private static func hasConfiguredAPICredential(_ entry: ProviderConfig) -> Bool {
|
||||
if let apiKey = entry.apiKey?.trimmingCharacters(in: .whitespacesAndNewlines), !apiKey.isEmpty {
|
||||
return true
|
||||
}
|
||||
return entry.tokenAccounts?.accounts.contains(where: { account in
|
||||
let token = account.token.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return !token.isEmpty &&
|
||||
TokenAccountSupportCatalog.envOverride(for: entry.id, token: token)?.isEmpty == false
|
||||
}) == true
|
||||
}
|
||||
|
||||
private static var enterpriseHostProviderList: String {
|
||||
self.formattedProviderList(self.enterpriseHostProviders)
|
||||
}
|
||||
|
||||
private static func validateRegion(_ entry: ProviderConfig, issues: inout [CodexBarConfigIssue]) {
|
||||
let provider = entry.id
|
||||
guard let region = entry.region?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!region.isEmpty
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
switch provider {
|
||||
case .minimax:
|
||||
self.validateKnownRegion(
|
||||
region,
|
||||
provider: provider,
|
||||
isValid: MiniMaxAPIRegion(rawValue: region) != nil,
|
||||
displayName: "MiniMax",
|
||||
issues: &issues)
|
||||
case .zai:
|
||||
self.validateKnownRegion(
|
||||
region,
|
||||
provider: provider,
|
||||
isValid: ZaiAPIRegion(rawValue: region) != nil,
|
||||
displayName: "z.ai",
|
||||
issues: &issues)
|
||||
case .alibaba:
|
||||
self.validateKnownRegion(
|
||||
region,
|
||||
provider: provider,
|
||||
isValid: AlibabaCodingPlanAPIRegion(rawValue: region) != nil,
|
||||
displayName: "Alibaba Coding Plan",
|
||||
issues: &issues)
|
||||
case .alibabatokenplan:
|
||||
self.validateKnownRegion(
|
||||
region,
|
||||
provider: provider,
|
||||
isValid: AlibabaTokenPlanAPIRegion(rawValue: region) != nil,
|
||||
displayName: "Alibaba Token Plan",
|
||||
issues: &issues)
|
||||
case .moonshot:
|
||||
self.validateKnownRegion(
|
||||
region,
|
||||
provider: provider,
|
||||
isValid: MoonshotRegion(rawValue: region) != nil,
|
||||
displayName: "Moonshot",
|
||||
issues: &issues)
|
||||
case .bedrock, .doubao:
|
||||
break
|
||||
default:
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .warning,
|
||||
provider: provider,
|
||||
field: "region",
|
||||
code: "region_unused",
|
||||
message: "region is set but \(provider.rawValue) does not use regions."))
|
||||
}
|
||||
}
|
||||
|
||||
private static func validateKnownRegion(
|
||||
_ region: String,
|
||||
provider: UsageProvider,
|
||||
isValid: Bool,
|
||||
displayName: String,
|
||||
issues: inout [CodexBarConfigIssue])
|
||||
{
|
||||
guard !isValid else { return }
|
||||
issues.append(CodexBarConfigIssue(
|
||||
severity: .error,
|
||||
provider: provider,
|
||||
field: "region",
|
||||
code: "invalid_region",
|
||||
message: "Region \(region) is not a valid \(displayName) region."))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
import Foundation
|
||||
|
||||
public enum ProviderConfigEnvironment {
|
||||
public static func applyAPIKeyOverride(
|
||||
base: [String: String],
|
||||
provider: UsageProvider,
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
if let env = self.applyDedicatedProviderOverrides(base: base, provider: provider, config: config) {
|
||||
return env
|
||||
}
|
||||
guard let apiKey = config?.sanitizedAPIKey, !apiKey.isEmpty else { return base }
|
||||
var env = base
|
||||
if let key = self.directAPIKeyEnvironmentKey(for: provider) {
|
||||
env[key] = apiKey
|
||||
return env
|
||||
}
|
||||
|
||||
switch provider {
|
||||
case .copilot:
|
||||
env["COPILOT_API_TOKEN"] = apiKey
|
||||
case .kimik2:
|
||||
if let key = KimiK2SettingsReader.apiKeyEnvironmentKeys.first {
|
||||
env[key] = apiKey
|
||||
}
|
||||
case .warp:
|
||||
if let key = WarpSettingsReader.apiKeyEnvironmentKeys.first {
|
||||
env[key] = apiKey
|
||||
}
|
||||
case .codebuff:
|
||||
// Preserve a token already present in the process environment so that
|
||||
// runtime/CI overrides win over a key saved in Settings (matches the
|
||||
// precedence used by `ProviderTokenResolver.codebuffResolution`).
|
||||
if CodebuffSettingsReader.apiKey(environment: base) == nil {
|
||||
env[CodebuffSettingsReader.apiTokenKey] = apiKey
|
||||
}
|
||||
case .crof:
|
||||
if CrofSettingsReader.apiKey(environment: base) == nil,
|
||||
let key = CrofSettingsReader.apiKeyEnvironmentKeys.first
|
||||
{
|
||||
env[key] = apiKey
|
||||
}
|
||||
case .doubao:
|
||||
if let key = DoubaoSettingsReader.apiKeyEnvironmentKeys.first {
|
||||
env[key] = apiKey
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
public static func supportsAPIKeyOverride(for provider: UsageProvider) -> Bool {
|
||||
if self.directAPIKeyEnvironmentKey(for: provider) != nil { return true }
|
||||
switch provider {
|
||||
case .copilot, .kimik2, .warp, .codebuff, .crof, .doubao:
|
||||
return true
|
||||
case .azureopenai:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private static func baseURLEnvironmentKey(for provider: UsageProvider) -> String? {
|
||||
switch provider {
|
||||
case .llmproxy:
|
||||
LLMProxySettingsReader.baseURLEnvironmentKey
|
||||
case .litellm:
|
||||
LiteLLMSettingsReader.baseURLEnvironmentKey
|
||||
case .clawrouter:
|
||||
ClawRouterSettingsReader.baseURLEnvironmentKey
|
||||
case .sub2api:
|
||||
Sub2APISettingsReader.baseURLEnvironmentKey
|
||||
case .wayfinder:
|
||||
WayfinderSettingsReader.baseURLEnvironmentKey
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func supportsAPIKeyAndBaseURLOverride(_ provider: UsageProvider) -> Bool {
|
||||
self.baseURLEnvironmentKey(for: provider) != nil
|
||||
}
|
||||
|
||||
private static func applyDedicatedProviderOverrides(
|
||||
base: [String: String],
|
||||
provider: UsageProvider,
|
||||
config: ProviderConfig?) -> [String: String]?
|
||||
{
|
||||
if self.supportsAPIKeyAndBaseURLOverride(provider) {
|
||||
return self.applyAPIKeyAndBaseURLOverrides(base: base, provider: provider, config: config)
|
||||
}
|
||||
if let env = self.applyMultiFieldCredentialOverrides(base: base, provider: provider, config: config) {
|
||||
return env
|
||||
}
|
||||
return switch provider {
|
||||
case .deepgram:
|
||||
self.applyDeepgramOverrides(base: base, config: config)
|
||||
case .azureopenai:
|
||||
self.applyAzureOpenAIOverrides(base: base, config: config)
|
||||
case .kimi:
|
||||
self.applyKimiOverrides(base: base, config: config)
|
||||
case .doubao:
|
||||
self.applyDoubaoOverrides(base: base, config: config)
|
||||
case .sakana:
|
||||
self.applySakanaOverrides(base: base, config: config)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func applyMultiFieldCredentialOverrides(
|
||||
base: [String: String],
|
||||
provider: UsageProvider,
|
||||
config: ProviderConfig?) -> [String: String]?
|
||||
{
|
||||
switch provider {
|
||||
case .openai:
|
||||
self.applyOpenAIOverrides(base: base, config: config)
|
||||
case .bedrock:
|
||||
self.applyBedrockOverrides(base: base, config: config)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func directAPIKeyEnvironmentKey(for provider: UsageProvider) -> String? {
|
||||
switch provider {
|
||||
case .amp:
|
||||
AmpSettingsReader.apiTokenKey
|
||||
case .openai:
|
||||
OpenAIAPISettingsReader.adminAPIKeyEnvironmentKey
|
||||
case .azureopenai:
|
||||
AzureOpenAISettingsReader.apiKeyEnvironmentKey
|
||||
case .claude:
|
||||
ClaudeAdminAPISettingsReader.adminAPIKeyEnvironmentKey
|
||||
case .zai:
|
||||
ZaiSettingsReader.apiTokenKey
|
||||
case .minimax:
|
||||
MiniMaxAPISettingsReader.apiTokenKey
|
||||
case .alibaba:
|
||||
AlibabaCodingPlanSettingsReader.apiTokenKey
|
||||
case .kilo:
|
||||
KiloSettingsReader.apiTokenKey
|
||||
case .synthetic:
|
||||
SyntheticSettingsReader.apiKeyKey
|
||||
case .openrouter:
|
||||
OpenRouterSettingsReader.envKey
|
||||
case .elevenlabs:
|
||||
ElevenLabsSettingsReader.apiKeyEnvironmentKey
|
||||
case .moonshot:
|
||||
MoonshotSettingsReader.apiKeyEnvironmentKeys.first
|
||||
case .kimi:
|
||||
KimiSettingsReader.apiKeyEnvironmentKeys.first
|
||||
case .ollama:
|
||||
OllamaAPISettingsReader.apiKeyEnvironmentKeys.first
|
||||
case .venice:
|
||||
VeniceSettingsReader.apiKeyEnvironmentKey
|
||||
case .deepgram:
|
||||
DeepgramSettingsReader.apiKeyEnvironmentKey
|
||||
case .groq:
|
||||
GroqSettingsReader.apiKeyEnvironmentKey
|
||||
case .llmproxy:
|
||||
LLMProxySettingsReader.apiKeyEnvironmentKey
|
||||
case .chutes, .poe, .litellm, .crossmodel, .clawrouter, .factory, .sub2api:
|
||||
self.additionalAPIKeyEnvironmentKey(for: provider)
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func additionalAPIKeyEnvironmentKey(for provider: UsageProvider) -> String? {
|
||||
switch provider {
|
||||
case .chutes:
|
||||
ChutesSettingsReader.apiKeyEnvironmentKey
|
||||
case .poe:
|
||||
PoeSettingsReader.apiKeyEnvironmentKey
|
||||
case .litellm:
|
||||
LiteLLMSettingsReader.apiKeyEnvironmentKey
|
||||
case .crossmodel:
|
||||
CrossModelSettingsReader.envKey
|
||||
case .clawrouter:
|
||||
ClawRouterSettingsReader.apiKeyEnvironmentKey
|
||||
case .sub2api:
|
||||
Sub2APISettingsReader.apiKeyEnvironmentKey
|
||||
case .factory:
|
||||
FactorySettingsReader.apiTokenKey
|
||||
default:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func applyOpenAIOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
var env = base
|
||||
if let apiKey = config.sanitizedAPIKey {
|
||||
env[OpenAIAPISettingsReader.adminAPIKeyEnvironmentKey] = apiKey
|
||||
}
|
||||
if let projectID = config.sanitizedWorkspaceID {
|
||||
env[OpenAIAPISettingsReader.projectIDEnvironmentKey] = projectID
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
private static func applyBedrockOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
var env = base
|
||||
|
||||
// Only project an explicit auth-mode selection. When the config does not
|
||||
// specify one, leave the base environment untouched so an env-driven setup
|
||||
// (AWS_PROFILE or CODEXBAR_BEDROCK_AUTH_MODE from the launch environment) is
|
||||
// still inferred by BedrockSettingsReader instead of being forced to `keys`.
|
||||
let configMode = config.sanitizedAWSAuthMode.flatMap(BedrockAuthMode.init(rawValue:))
|
||||
if let configMode {
|
||||
env[BedrockSettingsReader.authModeKey] = configMode.rawValue
|
||||
}
|
||||
let baseMode = BedrockSettingsReader
|
||||
.cleaned(base[BedrockSettingsReader.authModeKey])
|
||||
.flatMap { BedrockAuthMode(rawValue: $0.lowercased()) }
|
||||
|
||||
let mergedAccessKey = config.sanitizedAPIKey ?? BedrockSettingsReader.accessKeyID(environment: base)
|
||||
let mergedSecretKey = config.sanitizedSecretKey ?? BedrockSettingsReader.secretAccessKey(environment: base)
|
||||
let hasMergedStaticKeys = mergedAccessKey != nil && mergedSecretKey != nil
|
||||
let effectiveMode: BedrockAuthMode = if let configMode {
|
||||
configMode
|
||||
} else if let baseMode {
|
||||
baseMode
|
||||
} else if hasMergedStaticKeys {
|
||||
// Upgrade path: a config saved before auth modes existed keeps using
|
||||
// static credentials (including env+config layering) even if AWS_PROFILE
|
||||
// is present in the base environment, so existing users are never
|
||||
// silently switched to a profile/account.
|
||||
.keys
|
||||
} else {
|
||||
BedrockSettingsReader.authMode(environment: base)
|
||||
}
|
||||
|
||||
switch effectiveMode {
|
||||
case .profile:
|
||||
if let profile = config.sanitizedAWSProfile {
|
||||
env[BedrockSettingsReader.profileKey] = profile
|
||||
}
|
||||
case .keys:
|
||||
if let accessKeyID = config.sanitizedAPIKey {
|
||||
env[BedrockSettingsReader.accessKeyIDKey] = accessKeyID
|
||||
}
|
||||
if let secretAccessKey = config.sanitizedSecretKey {
|
||||
env[BedrockSettingsReader.secretAccessKeyKey] = secretAccessKey
|
||||
}
|
||||
}
|
||||
|
||||
if let region = config.sanitizedRegion {
|
||||
env[BedrockSettingsReader.regionKeys[0]] = region
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
private static func applyDeepgramOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
|
||||
var env = base
|
||||
|
||||
if let apiKey = config.sanitizedAPIKey {
|
||||
env[DeepgramSettingsReader.apiKeyEnvironmentKey] = apiKey
|
||||
}
|
||||
|
||||
if let projectID = config.sanitizedWorkspaceID {
|
||||
env[DeepgramSettingsReader.projectIDEnvironmentKey] = projectID
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
private static func applyAPIKeyAndBaseURLOverrides(
|
||||
base: [String: String],
|
||||
provider: UsageProvider,
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
var env = base
|
||||
if let apiKey = config?.sanitizedAPIKey,
|
||||
let key = self.directAPIKeyEnvironmentKey(for: provider)
|
||||
{
|
||||
env[key] = apiKey
|
||||
}
|
||||
if let baseURL = config?.sanitizedEnterpriseHost,
|
||||
let key = self.baseURLEnvironmentKey(for: provider)
|
||||
{
|
||||
env[key] = baseURL
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
private static func applyKimiOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
var env = base
|
||||
if let apiKey = config.sanitizedAPIKey,
|
||||
let key = KimiSettingsReader.apiKeyEnvironmentKeys.first
|
||||
{
|
||||
env[key] = apiKey
|
||||
}
|
||||
if let baseURL = config.sanitizedEnterpriseHost {
|
||||
env[KimiSettingsReader.codeAPIBaseURLEnvironmentKey] = baseURL
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
private static func applyDoubaoOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
var env = base
|
||||
let apiKey = config.sanitizedAPIKey
|
||||
let secretKey = config.sanitizedSecretKey
|
||||
|
||||
if let apiKey, self.doubaoAccessKeyID(from: apiKey) == nil {
|
||||
self.clearDoubaoCodingPlanCredentialKeys(in: &env)
|
||||
env[DoubaoSettingsReader.apiKeyEnvironmentKeys[0]] = apiKey
|
||||
if let region = config.sanitizedRegion {
|
||||
env[DoubaoSettingsReader.regionEnvironmentKeys[0]] = region
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
let accessKeyID = self.doubaoAccessKeyID(from: apiKey) ?? DoubaoSettingsReader.accessKeyID(environment: base)
|
||||
let secretAccessKey = secretKey ?? DoubaoSettingsReader.secretAccessKey(environment: base)
|
||||
|
||||
if let accessKeyID, let secretAccessKey {
|
||||
env[DoubaoSettingsReader.accessKeyIDEnvironmentKeys[0]] = accessKeyID
|
||||
env[DoubaoSettingsReader.secretAccessKeyEnvironmentKeys[0]] = secretAccessKey
|
||||
if let region = config.sanitizedRegion ?? self.firstDoubaoRegionValue(in: base) {
|
||||
env[DoubaoSettingsReader.regionEnvironmentKeys[0]] = region
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
if let region = config.sanitizedRegion {
|
||||
env[DoubaoSettingsReader.regionEnvironmentKeys[0]] = region
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
private static func applySakanaOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
var env = base
|
||||
if let cookieHeader = config.sanitizedCookieHeader {
|
||||
env[SakanaSettingsReader.cookieHeaderKey] = cookieHeader
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
private static func doubaoAccessKeyID(from apiKey: String?) -> String? {
|
||||
guard let apiKey, apiKey.hasPrefix("AKLT") else { return nil }
|
||||
return apiKey
|
||||
}
|
||||
|
||||
private static func clearDoubaoCodingPlanCredentialKeys(in environment: inout [String: String]) {
|
||||
for key in DoubaoSettingsReader.accessKeyIDEnvironmentKeys {
|
||||
environment.removeValue(forKey: key)
|
||||
}
|
||||
for key in DoubaoSettingsReader.secretAccessKeyEnvironmentKeys {
|
||||
environment.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
private static func firstDoubaoRegionValue(in environment: [String: String]) -> String? {
|
||||
for key in DoubaoSettingsReader.regionEnvironmentKeys {
|
||||
guard let value = DoubaoSettingsReader.cleaned(environment[key]) else { continue }
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static func applyAzureOpenAIOverrides(
|
||||
base: [String: String],
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
guard let config else { return base }
|
||||
var env = base
|
||||
if let apiKey = config.sanitizedAPIKey {
|
||||
env[AzureOpenAISettingsReader.apiKeyEnvironmentKey] = apiKey
|
||||
}
|
||||
if let endpoint = config.sanitizedEnterpriseHost {
|
||||
env[AzureOpenAISettingsReader.endpointEnvironmentKey] = endpoint
|
||||
}
|
||||
if let deploymentName = config.sanitizedWorkspaceID {
|
||||
env[AzureOpenAISettingsReader.deploymentNameEnvironmentKey] = deploymentName
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
public static func applyProviderConfigOverrides(
|
||||
base: [String: String],
|
||||
provider: UsageProvider,
|
||||
config: ProviderConfig?) -> [String: String]
|
||||
{
|
||||
self.applyAPIKeyOverride(base: base, provider: provider, config: config)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user