741 lines · 28.3 KB · Swift
RawDownload
import CodexBarCoreimport Foundation extension SettingsStore {    private var codexPersistedActiveSource: CodexActiveSource {        self.providerConfig(for: .codex)?.codexActiveSource ?? .liveSystem    }     private enum ManagedCodexAccountStoreState {        case none        case selected(ManagedCodexAccount)        case unreadable    }     private static func failClosedManagedCodexHomePath(fileManager: FileManager = .default) -> String {        ManagedCodexHomeFactory.defaultRootURL(fileManager: fileManager)            .appendingPathComponent("managed-store-unreadable", isDirectory: true)            .path    }     private static func normalizedCodexProfileHomePaths(_ paths: [String]?) -> [String] {        var seen: Set<String> = []        var result: [String] = []        for path in (paths ?? []).compactMap({ CodexHomeScope.normalizedHomePath($0) }) {            guard seen.insert(path).inserted else { continue }            result.append(path)        }        return result    }     private func loadManagedCodexAccounts() throws -> ManagedCodexAccountSet {        #if DEBUG        if CodexManagedRemoteHomeTestingOverride.isUnreadable(for: self) {            throw CodexManagedRemoteHomeTestingOverrideError.unreadableManagedStore        }        if let override = CodexManagedRemoteHomeTestingOverride.account(for: self) {            return ManagedCodexAccountSet(                version: FileManagedCodexAccountStore.currentVersion,                accounts: [override])        }        let store = if let storeURL = CodexManagedRemoteHomeTestingOverride.managedStoreURL(for: self) {            FileManagedCodexAccountStore(fileURL: storeURL)        } else {            FileManagedCodexAccountStore()        }        #else        let store = FileManagedCodexAccountStore()        #endif         return try store.loadAccounts()    }     private func managedCodexAccountStoreState(        activeSource: CodexActiveSource? = nil) -> ManagedCodexAccountStoreState    {        let source = activeSource ?? self.codexResolvedActiveSource        guard case let .managedAccount(id) = source else {            return .none        }        do {            let accounts = try self.loadManagedCodexAccounts()            guard let account = accounts.account(id: id)            else {                return .none            }            return .selected(account)        } catch {            return .unreadable        }    }     var activeManagedCodexAccount: ManagedCodexAccount? {        guard case let .selected(account) = self.managedCodexAccountStoreState() else {            return nil        }        return account    }     var activeManagedCodexRemoteHomePath: String? {        self.managedCodexRemoteHomePath(forActiveSource: self.codexResolvedActiveSource)    }     func liveSystemCodexHomePath(forActiveSource source: CodexActiveSource) -> String? {        guard source == .liveSystem else {            return nil        }        let path = self.codexAccountReconciliationSnapshot(activeSourceOverride: source)            .liveSystemAccount?.codexHomePath.trimmingCharacters(in: .whitespacesAndNewlines)        guard let path, !path.isEmpty else {            return nil        }        return path    }     var codexProfileHomePaths: [String] {        Self.normalizedCodexProfileHomePaths(            self.configSnapshot.providerConfig(for: .codex)?.codexProfileHomePaths)    }     func profileCodexHomePath(forActiveSource source: CodexActiveSource) -> String? {        guard case let .profileHome(path) = source else {            return nil        }        guard let normalizedPath = CodexHomeScope.normalizedHomePath(path),              self.codexProfileHomePaths.contains(normalizedPath)        else {            return nil        }        return normalizedPath    }     func managedCodexRemoteHomePath(forActiveSource source: CodexActiveSource) -> String? {        guard case let .managedAccount(id) = source else {            return nil        }         #if DEBUG        if let override = CodexManagedRemoteHomeTestingOverride.homePath(for: self) {            return override        }        #endif         do {            let accounts = try self.loadManagedCodexAccounts()            // A selected managed source must never fall back to ambient ~/.codex.            return accounts.account(id: id)?.managedHomePath ?? Self.failClosedManagedCodexHomePath()        } catch {            return Self.failClosedManagedCodexHomePath()        }    }     var activeManagedCodexCookieCacheScope: CookieHeaderCache.Scope? {        switch self.managedCodexAccountStoreState() {        case let .selected(account):            .managedAccount(account.id)        case .unreadable:            .managedStoreUnreadable        case .none:            nil        }    }     var hasUnreadableManagedCodexAccountStore: Bool {        self.codexAccountReconciliationSnapshot.hasUnreadableAddedAccountStore    }     var codexUsageDataSource: CodexUsageDataSource {        get {            let source = self.configSnapshot.providerConfig(for: .codex)?.source            return Self.codexUsageDataSource(from: source)        }        set {            let source: ProviderSourceMode? = switch newValue {            case .auto: .auto            case .oauth: .oauth            case .cli: .cli            }            self.updateProviderConfig(provider: .codex) { entry in                entry.source = source            }            self.logProviderModeChange(provider: .codex, field: "usageSource", value: newValue.rawValue)        }    }     var codexActiveSource: CodexActiveSource {        get {            self.codexPersistedActiveSource        }        set {            self.invalidateCodexAccountReconciliationSnapshotCache()            self.cachedCodexAccountMenuProjection = nil            self.updateProviderConfig(provider: .codex) { entry in                entry.codexActiveSource = newValue            }        }    }     var codexResolvedActiveSource: CodexActiveSource {        self.codexResolvedActiveSourceState.resolvedSource    }     var codexResolvedActiveSourceState: CodexResolvedActiveSource {        CodexActiveSourceResolver.resolve(from: self.codexAccountReconciliationSnapshot)    }     @discardableResult    func persistResolvedCodexActiveSourceCorrectionIfNeeded() -> Bool {        let resolution = self.codexResolvedActiveSourceState        guard resolution.requiresPersistenceCorrection else { return false }        self.codexActiveSource = resolution.resolvedSource        return true    }     @discardableResult    func refreshCodexAccountReconciliationAfterManagedAccountsDidChange() -> Bool {        self.invalidateCodexAccountReconciliationSnapshotCache()        self.cachedCodexAccountMenuProjection = nil        return self.persistResolvedCodexActiveSourceCorrectionIfNeeded()    }     var codexCookieHeader: String {        get { self.configSnapshot.providerConfig(for: .codex)?.sanitizedCookieHeader ?? "" }        set {            // This is intentionally provider-scoped today. A per-managed-account manual cookie override would need            // its own storage and UI semantics so editing one account's header does not silently rewrite another's.            self.updateProviderConfig(provider: .codex) { entry in                entry.cookieHeader = self.normalizedConfigValue(newValue)            }            self.logSecretUpdate(provider: .codex, field: "cookieHeader", value: newValue)        }    }     var codexCookieSource: ProviderCookieSource {        get {            let resolved = self.resolvedCookieSource(provider: .codex, fallback: .auto)            return self.openAIWebAccessEnabled ? resolved : .off        }        set {            self.updateProviderConfig(provider: .codex) { entry in                entry.cookieSource = newValue            }            self.logProviderModeChange(provider: .codex, field: "cookieSource", value: newValue.rawValue)            self.openAIWebAccessEnabled = newValue.isEnabled        }    }     func ensureCodexCookieLoaded() {}} extension SettingsStore {    private static var codexAccountReconciliationSnapshotCacheInterval: TimeInterval {        #if DEBUG        if let codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting {            return codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting        }        #endif        return self.isRunningTests ? 0 : self.productionCodexAccountReconciliationSnapshotCacheInterval    }     func invalidateCodexAccountReconciliationSnapshotCache() {        self.cachedCodexAccountReconciliationSnapshot = nil        self.codexAccountReconciliationGeneration &+= 1    }     var codexAccountReconciliationSnapshot: CodexAccountReconciliationSnapshot {        self.codexAccountReconciliationSnapshot(activeSourceOverride: nil)    }     func codexAccountReconciliationSnapshot(        activeSourceOverride: CodexActiveSource?) -> CodexAccountReconciliationSnapshot    {        let activeSource = activeSourceOverride ?? self.codexPersistedActiveSource        let cacheInterval = Self.codexAccountReconciliationSnapshotCacheInterval        let now = Date()        if cacheInterval > 0,           let cached = self.cachedCodexAccountReconciliationSnapshot,           cached.activeSource == activeSource,           now.timeIntervalSince(cached.loadedAt) < cacheInterval        {            return cached.snapshot        }         let snapshot = self.codexAccountSnapshotLoader(activeSource: activeSource)()        let loadedAt = Date()        if cacheInterval > 0 {            self.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot(                activeSource: activeSource,                loadedAt: loadedAt,                snapshot: snapshot)        }        if activeSource == self.codexPersistedActiveSource {            self.cachedCodexAccountMenuProjection = CachedCodexAccountMenuProjection(                activeSource: activeSource,                loadedAt: loadedAt,                projection: CodexVisibleAccountProjection.make(from: snapshot))        }        return snapshot    }     /// Menu rendering must stay side-effect free: no `auth.json` reads, JWT parsing, or fingerprint hashing.    var codexVisibleAccountProjectionForMenuDisplay: CodexVisibleAccountProjection? {        let activeSource = self.codexPersistedActiveSource        guard let cached = self.cachedCodexAccountMenuProjection,              cached.activeSource == activeSource        else {            return nil        }        return cached.projection    }     var codexAccountMenuProjectionNeedsRevalidation: Bool {        let activeSource = self.codexPersistedActiveSource        guard let cached = self.cachedCodexAccountMenuProjection,              cached.activeSource == activeSource        else {            return true        }        return Date().timeIntervalSince(cached.loadedAt) >= Self.codexAccountReconciliationSnapshotCacheInterval    }     func revalidateCodexAccountMenuProjection() async -> CodexAccountMenuProjectionRevalidationResult {        guard self.codexAccountMenuProjectionNeedsRevalidation else { return .skipped }         let activeSource = self.codexPersistedActiveSource        let generation = self.codexAccountReconciliationGeneration        let loader = self.codexAccountSnapshotLoader(activeSource: activeSource)        let snapshot = await Self.loadCodexAccountSnapshot(loader)         guard generation == self.codexAccountReconciliationGeneration,              activeSource == self.codexPersistedActiveSource        else {            return .discarded        }         let now = Date()        let projection = CodexVisibleAccountProjection.make(from: snapshot)        let previousProjection = self.cachedCodexAccountMenuProjection.flatMap { cached in            cached.activeSource == activeSource ? cached.projection : nil        }        self.cachedCodexAccountMenuProjection = CachedCodexAccountMenuProjection(            activeSource: activeSource,            loadedAt: now,            projection: projection)        if Self.codexAccountReconciliationSnapshotCacheInterval > 0 {            self.cachedCodexAccountReconciliationSnapshot = CachedCodexAccountReconciliationSnapshot(                activeSource: activeSource,                loadedAt: now,                snapshot: snapshot)        }        return previousProjection == projection ? .unchanged : .updated    }     @concurrent    private nonisolated static func loadCodexAccountSnapshot(        _ loader: @escaping @Sendable () -> CodexAccountReconciliationSnapshot)        async -> CodexAccountReconciliationSnapshot    {        loader()    }     var codexVisibleAccountProjection: CodexVisibleAccountProjection {        CodexVisibleAccountProjection.make(from: self.codexAccountReconciliationSnapshot)    }     var codexVisibleAccounts: [CodexVisibleAccount] {        self.codexVisibleAccountProjection.visibleAccounts    }     @discardableResult    func selectCodexVisibleAccount(id: String) -> Bool {        guard let source = self.codexSource(forVisibleAccountID: id) else { return false }        self.invalidateCodexAccountReconciliationSnapshotCache()        self.codexActiveSource = source        return true    }     func selectDisplayedCodexVisibleAccount(_ account: CodexVisibleAccount) {        // The row already carries the exact source it represented. Re-resolving its ID would synchronously        // reload auth state from the menu click callback and can also fail after a stale snapshot is rendered.        self.codexActiveSource = account.selectionSource    }     func selectAuthenticatedManagedCodexAccount(_ account: ManagedCodexAccount) {        if let visibleAccountID = self.codexVisibleAccountProjection.visibleAccounts            .first(where: { $0.storedAccountID == account.id })?            .id,            self.selectCodexVisibleAccount(id: visibleAccountID)        {            return        }         self.invalidateCodexAccountReconciliationSnapshotCache()        self.codexActiveSource = .managedAccount(id: account.id)        _ = self.persistResolvedCodexActiveSourceCorrectionIfNeeded()    }     func codexSource(forVisibleAccountID id: String) -> CodexActiveSource? {        self.codexVisibleAccountProjection.source(forVisibleAccountID: id)    }     private func codexAccountSnapshotLoader(        activeSource: CodexActiveSource) -> @Sendable () -> CodexAccountReconciliationSnapshot    {        #if DEBUG        if let loader = self._test_codexAccountSnapshotLoader {            return { loader(activeSource) }        }        #endif        let reconciler = self.codexAccountReconciler(activeSource: activeSource)        return { reconciler.loadSnapshot() }    }     private func codexAccountReconciler(activeSource: CodexActiveSource) -> DefaultCodexAccountReconciler {        let baseEnvironment = self.codexReconciliationEnvironment()        #if DEBUG        let liveSystemAccountOverride = CodexManagedRemoteHomeTestingOverride.liveSystemAccount(for: self)        let reconciliationEnvironmentOverride = CodexManagedRemoteHomeTestingOverride            .reconciliationEnvironment(for: self)        let managedAccountOverride = CodexManagedRemoteHomeTestingOverride.account(for: self)        let managedStoreURLOverride = CodexManagedRemoteHomeTestingOverride.managedStoreURL(for: self)        let unreadableStoreOverride = CodexManagedRemoteHomeTestingOverride.isUnreadable(for: self)        guard CodexManagedRemoteHomeTestingOverride.hasAnyOverride(for: self) else {            return DefaultCodexAccountReconciler(                activeSource: activeSource,                baseEnvironment: baseEnvironment,                profileHomePaths: self.codexProfileHomePaths,                managedEnvironmentBuilder: { environment, account in                    CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath)                })        }         let storeLoader: @Sendable () throws -> ManagedCodexAccountSet        if unreadableStoreOverride {            storeLoader = { throw CodexManagedRemoteHomeTestingOverrideError.unreadableManagedStore }        } else if let managedAccountOverride {            let accounts = ManagedCodexAccountSet(                version: FileManagedCodexAccountStore.currentVersion,                accounts: [managedAccountOverride])            storeLoader = { accounts }        } else if let managedStoreURLOverride {            let store = FileManagedCodexAccountStore(fileURL: managedStoreURLOverride)            storeLoader = { try store.loadAccounts() }        } else {            let accounts = ManagedCodexAccountSet(                version: FileManagedCodexAccountStore.currentVersion,                accounts: [])            storeLoader = { accounts }        }         return DefaultCodexAccountReconciler(            storeLoader: storeLoader,            systemObserver: CodexManagedRemoteHomeTestingSystemObserver(                overrideAccount: liveSystemAccountOverride,                usesInjectedEnvironment: reconciliationEnvironmentOverride != nil),            activeSource: activeSource,            baseEnvironment: baseEnvironment,            profileHomePaths: self.codexProfileHomePaths,            managedEnvironmentBuilder: { environment, account in                CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath)            })        #else        return DefaultCodexAccountReconciler(            activeSource: activeSource,            baseEnvironment: baseEnvironment,            profileHomePaths: self.codexProfileHomePaths,            managedEnvironmentBuilder: { environment, account in                CodexHomeScope.scopedEnvironment(base: environment, codexHome: account.managedHomePath)            })        #endif    }     private func codexReconciliationEnvironment() -> [String: String] {        #if DEBUG        if let override = CodexManagedRemoteHomeTestingOverride.reconciliationEnvironment(for: self) {            return override        }        #endif        return ProcessInfo.processInfo.environment    }} #if DEBUGprivate enum CodexManagedRemoteHomeTestingOverride {    private struct Override {        var account: ManagedCodexAccount?        var homePath: String?        var unreadableStore: Bool = false        var managedStoreURL: URL?        var liveSystemAccount: ObservedSystemCodexAccount?        var reconciliationEnvironment: [String: String]?         var isEmpty: Bool {            self.account == nil && self.homePath == nil && self.unreadableStore == false && self                .managedStoreURL == nil && self.liveSystemAccount == nil && self                .reconciliationEnvironment == nil        }    }     private final class Entry {        weak var settings: SettingsStore?        var overrideValue: Override         init(settings: SettingsStore, overrideValue: Override) {            self.settings = settings            self.overrideValue = overrideValue        }    }     @MainActor    private static var values: [ObjectIdentifier: Entry] = [:]     @MainActor    private static func entry(for settings: SettingsStore) -> Entry? {        let key = ObjectIdentifier(settings)        guard let entry = self.values[key] else { return nil }        guard let storedSettings = entry.settings, storedSettings === settings else {            self.values.removeValue(forKey: key)            return nil        }        return entry

Lines 1–500 of 741.