Skip to content

Commit dda89bd

Browse files
committed
fix(credentials): reduce duplicate keychain prompts
1 parent 3b2287a commit dda89bd

5 files changed

Lines changed: 39 additions & 4 deletions

File tree

AgentBar/Services/CopilotUsageProvider.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,13 @@ final class CopilotUsageProvider: UsageProviderProtocol, @unchecked Sendable {
5858
} else {
5959
self.primaryCredentialProvider = { Self.readGHCLIToken() }
6060
self.fallbackCredentialProvider = fallbackCredentialProvider ?? {
61-
KeychainManager.load(account: ServiceType.copilot.keychainAccount)
61+
guard UserDefaults.standard.bool(
62+
forKey: CopilotCredentialSettings.manualPATEnabledKey,
63+
defaultValue: false
64+
) else {
65+
return nil
66+
}
67+
return KeychainManager.load(account: ServiceType.copilot.keychainAccount)
6268
}
6369
}
6470
}

AgentBar/Services/ZaiUsageProvider.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ final class ZaiUsageProvider: UsageProviderProtocol, @unchecked Sendable {
2828

2929
private let apiClient: APIClient
3030
private let credentialProvider: @Sendable () -> String?
31+
private let credentialLock = NSLock()
32+
nonisolated(unsafe) private var cachedCredential: String??
3133

3234
/// Minimum cache TTL to avoid excessive API requests (Z.ai is API-based).
3335
static let minCacheTTL: TimeInterval = 60
@@ -43,10 +45,11 @@ final class ZaiUsageProvider: UsageProviderProtocol, @unchecked Sendable {
4345
self.credentialProvider = credentialProvider ?? {
4446
KeychainManager.load(account: ServiceType.zai.keychainAccount)
4547
}
48+
self.cachedCredential = nil
4649
}
4750

4851
func isConfigured() async -> Bool {
49-
credentialProvider() != nil
52+
resolveCredential() != nil
5053
}
5154

5255
/// Returns cached response if within minimum TTL, nil otherwise.
@@ -74,7 +77,7 @@ final class ZaiUsageProvider: UsageProviderProtocol, @unchecked Sendable {
7477
return cached
7578
}
7679

77-
guard let apiKey = credentialProvider() else {
80+
guard let apiKey = resolveCredential() else {
7881
throw APIError.unauthorized
7982
}
8083

@@ -163,4 +166,17 @@ final class ZaiUsageProvider: UsageProviderProtocol, @unchecked Sendable {
163166
}
164167
}
165168

169+
private func resolveCredential() -> String? {
170+
credentialLock.lock()
171+
if let cachedCredential {
172+
credentialLock.unlock()
173+
return cachedCredential
174+
}
175+
176+
let loadedCredential = credentialProvider()
177+
cachedCredential = loadedCredential
178+
credentialLock.unlock()
179+
return loadedCredential
180+
}
181+
166182
}

AgentBar/Utilities/UserDefaultsExtensions.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ enum BuyMeACoffeeSettings {
1919
static let hideButtonKey = "hideBuyMeACoffeeButton"
2020
}
2121

22+
enum CopilotCredentialSettings {
23+
/// Enables Keychain fallback for Copilot when gh CLI token is unavailable.
24+
static let manualPATEnabledKey = "copilotManualPATEnabled"
25+
}
26+
2227
extension UserDefaults {
2328
func bool(forKey key: String, defaultValue: Bool) -> Bool {
2429
guard object(forKey: key) != nil else { return defaultValue }

AgentBar/Views/Settings/SettingsView.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ struct SettingsView: View {
3434
@AppStorage("geminiDailyLimit") private var geminiDailyLimit: Double = 1_000
3535

3636
@AppStorage("copilotEnabled") private var copilotEnabled = true
37+
@AppStorage(CopilotCredentialSettings.manualPATEnabledKey) private var copilotManualPATEnabled = false
3738

3839
@AppStorage("cursorEnabled") private var cursorEnabled = true
3940
@AppStorage("cursorPlan") private var cursorPlan: String = CursorPlan.pro.rawValue
@@ -587,6 +588,9 @@ struct SettingsView: View {
587588
hasSavedToken: hasSavedCopilotPAT
588589
)
589590
hasSavedCopilotPAT = outcome.hasSavedToken
591+
if outcome.didSave {
592+
copilotManualPATEnabled = true
593+
}
590594
copilotPAT = outcome.tokenFieldValue
591595
return outcome.didSave
592596
}
@@ -649,7 +653,11 @@ struct SettingsView: View {
649653
}
650654

651655
private func loadAPIKeys() {
652-
hasSavedCopilotPAT = KeychainManager.load(account: ServiceType.copilot.keychainAccount) != nil
656+
if copilotManualPATEnabled {
657+
hasSavedCopilotPAT = KeychainManager.load(account: ServiceType.copilot.keychainAccount) != nil
658+
} else {
659+
hasSavedCopilotPAT = false
660+
}
653661
hasSavedZaiAPIKey = KeychainManager.load(account: ServiceType.zai.keychainAccount) != nil
654662
copilotPAT = ""
655663
zaiAPIKey = ""

docs/assets/screenshot.png

-19.3 KB
Loading

0 commit comments

Comments
 (0)