{"repo": "alexcat315/guava-engine", "files": {"Editor/Sources/EditorCore/AI/EditorAISettings.swift": {"65af0dd05a07b08706e654513935d492bd643759": {"t": 1785745894.1146126, "n": 10737, "h": "25bd7d94ac92a0b8", "c": "import AIRuntime\nimport CapabilityRuntime\nimport Foundation\n#if canImport(Security)\nimport Security\n#endif\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n /// Compatibility preference for read-only/legacy flows. Capability-backed\n /// AI writes always escalate to the required preview confirmation policy.\n public var autoApprove: Bool\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel,\n autoApprove: Bool = false) {\n self.provider = provider\n self.model = model\n self.autoApprove = autoApprove\n }\n\n private enum CodingKeys: String, CodingKey { case provider, model, autoApprove }\n\n public init(from decoder: Decoder) throws {\n let c = try decoder.container(keyedBy: CodingKeys.self)\n provider = try c.decodeIfPresent(EditorAIProvider.self, forKey: .provider) ?? .none\n model = try c.decodeIfPresent(String.self, forKey: .model) ?? SessionConfig.defaultAnthropicModel\n autoApprove = try c.decodeIfPresent(Bool.self, forKey: .autoApprove) ?? false\n }\n}\n\n// MARK: - Capability Settings\n\npublic enum EditorCapabilityReleasePhase: String, Codable, Sendable, Equatable, CaseIterable {\n case stable\n case beta\n case experimental\n\n public var displayName: String {\n switch self {\n case .stable: return \"Stable\"\n case .beta: return \"Beta\"\n case .experimental: return \"Experimental\"\n }\n }\n\n var runtimePhase: CapabilityReleasePhase {\n switch self {\n case .stable: return .stable\n case .beta: return .beta\n case .experimental: return .experimental\n }\n }\n}\n\npublic struct EditorCapabilitySettings: Codable, Sendable, Equatable {\n public var releasePhase: EditorCapabilityReleasePhase\n\n public static let `default` = EditorCapabilitySettings(releasePhase: .stable)\n\n public init(releasePhase: EditorCapabilityReleasePhase = .stable) {\n self.releasePhase = releasePhase\n }\n\n private enum CodingKeys: String, CodingKey {\n case releasePhase\n }\n\n public init(from decoder: Decoder) throws {\n let values = try decoder.container(keyedBy: CodingKeys.self)\n releasePhase = try values.decodeIfPresent(EditorCapabilityReleasePhase.self,\n forKey: .releasePhase) ?? .stable\n }\n\n public func encode(to encoder: Encoder) throws {\n var values = encoder.container(keyedBy: CodingKeys.self)\n try values.encode(releasePhase, forKey: .releasePhase)\n }\n}\n\n// MARK: - Key store\n\npublic enum AIKeychainError: Error, Sendable, Equatable, LocalizedError {\n case unavailable\n case operationFailed(Int32)\n\n public var errorDescription: String? {\n switch self {\n case .unavailable:\n return \"The operating-system credential store is unavailable.\"\n case let .operationFailed(status):\n return \"The credential store operation failed with status \\(status).\"\n }\n }\n}\n\npublic enum AICredentialSource: Sendable, Equatable {\n case operatingSystemStore\n case environment(variable: String)\n}\n\n/// Stores provider API keys in the operating-system credential store. Keys are\n/// never written to project files, settings JSON, prompts, logs, or plugins.\npublic enum AIKeychain {\n private static let service = \"com.guava.editor.ai-provider\"\n\n private static var legacyKeysFileURL: URL? {\n FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)\n .first?\n .appendingPathComponent(\"Guava\", isDirectory: true)\n .appendingPathComponent(\"ai_keys.json\")\n }\n\n /// One-time migration from older development builds. The plaintext file is\n /// removed only after every non-empty value is present in Keychain; a\n /// Keychain failure must not silently destroy the user's credentials.\n private static func migrateLegacyPlaintextStoreIfNeeded() {\n#if canImport(Security)\n guard let url = legacyKeysFileURL,\n let data = try? Data(contentsOf: url),\n let keys = try? JSONDecoder().decode([String: String].self, from: data)\n else { return }\n\n var migrationSucceeded = true\n for (account, key) in keys where !key.isEmpty {\n let itemIdentity: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: account,\n ]\n var lookup = itemIdentity\n lookup[kSecMatchLimit as String] = kSecMatchLimitOne\n if SecItemCopyMatching(lookup as CFDictionary, nil) == errSecSuccess {\n continue\n }\n var insert = itemIdentity\n insert[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly\n insert[kSecValueData as String] = Data(key.utf8)\n let status = SecItemAdd(insert as CFDictionary, nil)\n migrationSucceeded = migrationSucceeded && status == errSecSuccess\n }\n if migrationSucceeded {\n try? FileManager.default.removeItem(at: url)\n }\n#endif\n }\n\n public static func save(key: String, provider: EditorAIProvider) throws {\n migrateLegacyPlaintextStoreIfNeeded()\n guard !key.isEmpty else {\n try delete(provider: provider)\n return\n }\n#if canImport(Security)\n let identity: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n ]\n let values: [String: Any] = [\n kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n kSecValueData as String: Data(key.utf8),\n ]\n let updateStatus = SecItemUpdate(identity as CFDictionary, values as CFDictionary)\n if updateStatus == errSecSuccess { return }\n guard updateStatus == errSecItemNotFound else {\n throw AIKeychainError.operationFailed(updateStatus)\n }\n var insertion = identity\n for (key, value) in values { insertion[key] = value }\n let addStatus = SecItemAdd(insertion as CFDictionary, nil)\n guard addStatus == errSecSuccess else {\n throw AIKeychainError.operationFailed(addStatus)\n }\n#else\n throw AIKeychainError.unavailable\n#endif\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n migrateLegacyPlaintextStoreIfNeeded()\n if let stored = loadFromOperatingSystemStore(provider: provider) {\n return stored\n }\n return environmentCredential(\n provider: provider,\n environment: ProcessInfo.processInfo.environment\n )?.key\n }\n\n public static func credentialSource(for provider: EditorAIProvider) -> AICredentialSource? {\n migrateLegacyPlaintextStoreIfNeeded()\n if loadFromOperatingSystemStore(provider: provider) != nil {\n return .operatingSystemStore\n }\n guard let environment = environmentCredential(\n provider: provider,\n environment: ProcessInfo.processInfo.environment\n ) else { return nil }\n return .environment(variable: environment.variable)\n }\n\n static func environmentCredential(\n provider: EditorAIProvider,\n environment: [String: String]\n ) -> (variable: String, key: String)? {\n let candidates: [String]\n switch provider {\n case .none:\n return nil\n case .anthropic:\n candidates = [\"GUAVA_ANTHROPIC_API_KEY\", \"ANTHROPIC_API_KEY\"]\n case .openai:\n candidates = [\"GUAVA_OPENAI_API_KEY\", \"OPENAI_API_KEY\"]\n case .deepseek:\n candidates = [\"GUAVA_DEEPSEEK_API_KEY\", \"DEEPSEEK_API_KEY\"]\n }\n for variable in candidates {\n guard let value = environment[variable]?.trimmingCharacters(\n in: .whitespacesAndNewlines\n ), !value.isEmpty else { continue }\n return (variable, value)\n }\n return nil\n }\n\n private static func loadFromOperatingSystemStore(provider: EditorAIProvider) -> String? {\n#if canImport(Security)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n kSecReturnData as String: true,\n kSecMatchLimit as String: kSecMatchLimitOne,\n ]\n var result: CFTypeRef?\n guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,\n let data = result as? Data,\n let key = String(data: data, encoding: .utf8),\n !key.isEmpty else { return nil }\n return key\n#else\n return nil\n#endif\n }\n\n public static func delete(provider: EditorAIProvider) throws {\n migrateLegacyPlaintextStoreIfNeeded()\n#if canImport(Security)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n ]\n let status = SecItemDelete(query as CFDictionary)\n guard status == errSecSuccess || status == errSecItemNotFound else {\n throw AIKeychainError.operationFailed(status)\n }\n#else\n throw AIKeychainError.unavailable\n#endif\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n credentialSource(for: provider) != nil\n }\n}\n"}, "fa9e6a3a9b364db1856634a95b5da1e8d5579190": {"t": 1785757741.5835378, "n": 10737, "h": "25bd7d94ac92a0b8", "c": "import AIRuntime\nimport CapabilityRuntime\nimport Foundation\n#if canImport(Security)\nimport Security\n#endif\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n /// Compatibility preference for read-only/legacy flows. Capability-backed\n /// AI writes always escalate to the required preview confirmation policy.\n public var autoApprove: Bool\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel,\n autoApprove: Bool = false) {\n self.provider = provider\n self.model = model\n self.autoApprove = autoApprove\n }\n\n private enum CodingKeys: String, CodingKey { case provider, model, autoApprove }\n\n public init(from decoder: Decoder) throws {\n let c = try decoder.container(keyedBy: CodingKeys.self)\n provider = try c.decodeIfPresent(EditorAIProvider.self, forKey: .provider) ?? .none\n model = try c.decodeIfPresent(String.self, forKey: .model) ?? SessionConfig.defaultAnthropicModel\n autoApprove = try c.decodeIfPresent(Bool.self, forKey: .autoApprove) ?? false\n }\n}\n\n// MARK: - Capability Settings\n\npublic enum EditorCapabilityReleasePhase: String, Codable, Sendable, Equatable, CaseIterable {\n case stable\n case beta\n case experimental\n\n public var displayName: String {\n switch self {\n case .stable: return \"Stable\"\n case .beta: return \"Beta\"\n case .experimental: return \"Experimental\"\n }\n }\n\n var runtimePhase: CapabilityReleasePhase {\n switch self {\n case .stable: return .stable\n case .beta: return .beta\n case .experimental: return .experimental\n }\n }\n}\n\npublic struct EditorCapabilitySettings: Codable, Sendable, Equatable {\n public var releasePhase: EditorCapabilityReleasePhase\n\n public static let `default` = EditorCapabilitySettings(releasePhase: .stable)\n\n public init(releasePhase: EditorCapabilityReleasePhase = .stable) {\n self.releasePhase = releasePhase\n }\n\n private enum CodingKeys: String, CodingKey {\n case releasePhase\n }\n\n public init(from decoder: Decoder) throws {\n let values = try decoder.container(keyedBy: CodingKeys.self)\n releasePhase = try values.decodeIfPresent(EditorCapabilityReleasePhase.self,\n forKey: .releasePhase) ?? .stable\n }\n\n public func encode(to encoder: Encoder) throws {\n var values = encoder.container(keyedBy: CodingKeys.self)\n try values.encode(releasePhase, forKey: .releasePhase)\n }\n}\n\n// MARK: - Key store\n\npublic enum AIKeychainError: Error, Sendable, Equatable, LocalizedError {\n case unavailable\n case operationFailed(Int32)\n\n public var errorDescription: String? {\n switch self {\n case .unavailable:\n return \"The operating-system credential store is unavailable.\"\n case let .operationFailed(status):\n return \"The credential store operation failed with status \\(status).\"\n }\n }\n}\n\npublic enum AICredentialSource: Sendable, Equatable {\n case operatingSystemStore\n case environment(variable: String)\n}\n\n/// Stores provider API keys in the operating-system credential store. Keys are\n/// never written to project files, settings JSON, prompts, logs, or plugins.\npublic enum AIKeychain {\n private static let service = \"com.guava.editor.ai-provider\"\n\n private static var legacyKeysFileURL: URL? {\n FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)\n .first?\n .appendingPathComponent(\"Guava\", isDirectory: true)\n .appendingPathComponent(\"ai_keys.json\")\n }\n\n /// One-time migration from older development builds. The plaintext file is\n /// removed only after every non-empty value is present in Keychain; a\n /// Keychain failure must not silently destroy the user's credentials.\n private static func migrateLegacyPlaintextStoreIfNeeded() {\n#if canImport(Security)\n guard let url = legacyKeysFileURL,\n let data = try? Data(contentsOf: url),\n let keys = try? JSONDecoder().decode([String: String].self, from: data)\n else { return }\n\n var migrationSucceeded = true\n for (account, key) in keys where !key.isEmpty {\n let itemIdentity: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: account,\n ]\n var lookup = itemIdentity\n lookup[kSecMatchLimit as String] = kSecMatchLimitOne\n if SecItemCopyMatching(lookup as CFDictionary, nil) == errSecSuccess {\n continue\n }\n var insert = itemIdentity\n insert[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly\n insert[kSecValueData as String] = Data(key.utf8)\n let status = SecItemAdd(insert as CFDictionary, nil)\n migrationSucceeded = migrationSucceeded && status == errSecSuccess\n }\n if migrationSucceeded {\n try? FileManager.default.removeItem(at: url)\n }\n#endif\n }\n\n public static func save(key: String, provider: EditorAIProvider) throws {\n migrateLegacyPlaintextStoreIfNeeded()\n guard !key.isEmpty else {\n try delete(provider: provider)\n return\n }\n#if canImport(Security)\n let identity: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n ]\n let values: [String: Any] = [\n kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n kSecValueData as String: Data(key.utf8),\n ]\n let updateStatus = SecItemUpdate(identity as CFDictionary, values as CFDictionary)\n if updateStatus == errSecSuccess { return }\n guard updateStatus == errSecItemNotFound else {\n throw AIKeychainError.operationFailed(updateStatus)\n }\n var insertion = identity\n for (key, value) in values { insertion[key] = value }\n let addStatus = SecItemAdd(insertion as CFDictionary, nil)\n guard addStatus == errSecSuccess else {\n throw AIKeychainError.operationFailed(addStatus)\n }\n#else\n throw AIKeychainError.unavailable\n#endif\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n migrateLegacyPlaintextStoreIfNeeded()\n if let stored = loadFromOperatingSystemStore(provider: provider) {\n return stored\n }\n return environmentCredential(\n provider: provider,\n environment: ProcessInfo.processInfo.environment\n )?.key\n }\n\n public static func credentialSource(for provider: EditorAIProvider) -> AICredentialSource? {\n migrateLegacyPlaintextStoreIfNeeded()\n if loadFromOperatingSystemStore(provider: provider) != nil {\n return .operatingSystemStore\n }\n guard let environment = environmentCredential(\n provider: provider,\n environment: ProcessInfo.processInfo.environment\n ) else { return nil }\n return .environment(variable: environment.variable)\n }\n\n static func environmentCredential(\n provider: EditorAIProvider,\n environment: [String: String]\n ) -> (variable: String, key: String)? {\n let candidates: [String]\n switch provider {\n case .none:\n return nil\n case .anthropic:\n candidates = [\"GUAVA_ANTHROPIC_API_KEY\", \"ANTHROPIC_API_KEY\"]\n case .openai:\n candidates = [\"GUAVA_OPENAI_API_KEY\", \"OPENAI_API_KEY\"]\n case .deepseek:\n candidates = [\"GUAVA_DEEPSEEK_API_KEY\", \"DEEPSEEK_API_KEY\"]\n }\n for variable in candidates {\n guard let value = environment[variable]?.trimmingCharacters(\n in: .whitespacesAndNewlines\n ), !value.isEmpty else { continue }\n return (variable, value)\n }\n return nil\n }\n\n private static func loadFromOperatingSystemStore(provider: EditorAIProvider) -> String? {\n#if canImport(Security)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n kSecReturnData as String: true,\n kSecMatchLimit as String: kSecMatchLimitOne,\n ]\n var result: CFTypeRef?\n guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,\n let data = result as? Data,\n let key = String(data: data, encoding: .utf8),\n !key.isEmpty else { return nil }\n return key\n#else\n return nil\n#endif\n }\n\n public static func delete(provider: EditorAIProvider) throws {\n migrateLegacyPlaintextStoreIfNeeded()\n#if canImport(Security)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n ]\n let status = SecItemDelete(query as CFDictionary)\n guard status == errSecSuccess || status == errSecItemNotFound else {\n throw AIKeychainError.operationFailed(status)\n }\n#else\n throw AIKeychainError.unavailable\n#endif\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n credentialSource(for: provider) != nil\n }\n}\n"}, "c5b93065ec5cd8724236c991336d4ef055651f34": {"t": 1785757743.1062095, "n": 7683, "h": "4367d5d41cce0311", "c": "import AIRuntime\nimport CapabilityRuntime\nimport Foundation\n#if canImport(Security)\nimport Security\n#endif\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n /// Compatibility preference for read-only/legacy flows. Capability-backed\n /// AI writes always escalate to the required preview confirmation policy.\n public var autoApprove: Bool\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel,\n autoApprove: Bool = false) {\n self.provider = provider\n self.model = model\n self.autoApprove = autoApprove\n }\n\n private enum CodingKeys: String, CodingKey { case provider, model, autoApprove }\n\n public init(from decoder: Decoder) throws {\n let c = try decoder.container(keyedBy: CodingKeys.self)\n provider = try c.decodeIfPresent(EditorAIProvider.self, forKey: .provider) ?? .none\n model = try c.decodeIfPresent(String.self, forKey: .model) ?? SessionConfig.defaultAnthropicModel\n autoApprove = try c.decodeIfPresent(Bool.self, forKey: .autoApprove) ?? false\n }\n}\n\n// MARK: - Capability Settings\n\npublic enum EditorCapabilityReleasePhase: String, Codable, Sendable, Equatable, CaseIterable {\n case stable\n case beta\n case experimental\n\n public var displayName: String {\n switch self {\n case .stable: return \"Stable\"\n case .beta: return \"Beta\"\n case .experimental: return \"Experimental\"\n }\n }\n\n var runtimePhase: CapabilityReleasePhase {\n switch self {\n case .stable: return .stable\n case .beta: return .beta\n case .experimental: return .experimental\n }\n }\n}\n\npublic struct EditorCapabilitySettings: Codable, Sendable, Equatable {\n public var releasePhase: EditorCapabilityReleasePhase\n\n public static let `default` = EditorCapabilitySettings(releasePhase: .stable)\n\n public init(releasePhase: EditorCapabilityReleasePhase = .stable) {\n self.releasePhase = releasePhase\n }\n\n private enum CodingKeys: String, CodingKey {\n case releasePhase\n }\n\n public init(from decoder: Decoder) throws {\n let values = try decoder.container(keyedBy: CodingKeys.self)\n releasePhase = try values.decodeIfPresent(EditorCapabilityReleasePhase.self,\n forKey: .releasePhase) ?? .stable\n }\n\n public func encode(to encoder: Encoder) throws {\n var values = encoder.container(keyedBy: CodingKeys.self)\n try values.encode(releasePhase, forKey: .releasePhase)\n }\n}\n\n// MARK: - Key store\n\n/// Stores provider API keys in the operating-system credential store. Keys are\n/// never written to project files, settings JSON, prompts, logs, or plugins.\npublic enum AIKeychain {\n private static let service = \"com.guava.editor.ai-provider\"\n\n private static var legacyKeysFileURL: URL? {\n FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)\n .first?\n .appendingPathComponent(\"Guava\", isDirectory: true)\n .appendingPathComponent(\"ai_keys.json\")\n }\n\n /// One-time migration from older development builds. The plaintext file is\n /// removed only after every non-empty value is present in Keychain; a\n /// Keychain failure must not silently destroy the user's credentials.\n private static func migrateLegacyPlaintextStoreIfNeeded() {\n#if canImport(Security)\n guard let url = legacyKeysFileURL,\n let data = try? Data(contentsOf: url),\n let keys = try? JSONDecoder().decode([String: String].self, from: data)\n else { return }\n\n var migrationSucceeded = true\n for (account, key) in keys where !key.isEmpty {\n let itemIdentity: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: account,\n ]\n var lookup = itemIdentity\n lookup[kSecMatchLimit as String] = kSecMatchLimitOne\n if SecItemCopyMatching(lookup as CFDictionary, nil) == errSecSuccess {\n continue\n }\n var insert = itemIdentity\n insert[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly\n insert[kSecValueData as String] = Data(key.utf8)\n let status = SecItemAdd(insert as CFDictionary, nil)\n migrationSucceeded = migrationSucceeded && status == errSecSuccess\n }\n if migrationSucceeded {\n try? FileManager.default.removeItem(at: url)\n }\n#endif\n }\n\n public static func save(key: String, provider: EditorAIProvider) {\n migrateLegacyPlaintextStoreIfNeeded()\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n#if canImport(Security)\n delete(provider: provider)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n kSecValueData as String: Data(key.utf8),\n ]\n _ = SecItemAdd(query as CFDictionary, nil)\n#endif\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n migrateLegacyPlaintextStoreIfNeeded()\n#if canImport(Security)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n kSecReturnData as String: true,\n kSecMatchLimit as String: kSecMatchLimitOne,\n ]\n var result: CFTypeRef?\n guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,\n let data = result as? Data,\n let key = String(data: data, encoding: .utf8),\n !key.isEmpty else { return nil }\n return key\n#else\n return nil\n#endif\n }\n\n public static func delete(provider: EditorAIProvider) {\n migrateLegacyPlaintextStoreIfNeeded()\n#if canImport(Security)\n let query: [String: Any] = [\n kSecClass as String: kSecClassGenericPassword,\n kSecAttrService as String: service,\n kSecAttrAccount as String: provider.rawValue,\n ]\n _ = SecItemDelete(query as CFDictionary)\n#endif\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}, "353bf1e05892c32f4039e4567bc057c87426d091": {"t": 1785757744.5317469, "n": 5434, "h": "8211491396433f38", "c": "import AIRuntime\nimport CapabilityRuntime\nimport Foundation\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n /// When `true`, AI edit plans are applied immediately without a confirmation step.\n public var autoApprove: Bool\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel,\n autoApprove: Bool = false) {\n self.provider = provider\n self.model = model\n self.autoApprove = autoApprove\n }\n\n private enum CodingKeys: String, CodingKey { case provider, model, autoApprove }\n\n public init(from decoder: Decoder) throws {\n let c = try decoder.container(keyedBy: CodingKeys.self)\n provider = try c.decodeIfPresent(EditorAIProvider.self, forKey: .provider) ?? .none\n model = try c.decodeIfPresent(String.self, forKey: .model) ?? SessionConfig.defaultAnthropicModel\n autoApprove = try c.decodeIfPresent(Bool.self, forKey: .autoApprove) ?? false\n }\n}\n\n// MARK: - Capability Settings\n\npublic enum EditorCapabilityReleasePhase: String, Codable, Sendable, Equatable, CaseIterable {\n case stable\n case beta\n case experimental\n\n public var displayName: String {\n switch self {\n case .stable: return \"Stable\"\n case .beta: return \"Beta\"\n case .experimental: return \"Experimental\"\n }\n }\n\n var runtimePhase: CapabilityReleasePhase {\n switch self {\n case .stable: return .stable\n case .beta: return .beta\n case .experimental: return .experimental\n }\n }\n}\n\npublic struct EditorCapabilitySettings: Codable, Sendable, Equatable {\n public var releasePhase: EditorCapabilityReleasePhase\n\n public static let `default` = EditorCapabilitySettings(releasePhase: .stable)\n\n public init(releasePhase: EditorCapabilityReleasePhase = .stable) {\n self.releasePhase = releasePhase\n }\n\n private enum CodingKeys: String, CodingKey {\n case releasePhase\n }\n\n public init(from decoder: Decoder) throws {\n let values = try decoder.container(keyedBy: CodingKeys.self)\n releasePhase = try values.decodeIfPresent(EditorCapabilityReleasePhase.self,\n forKey: .releasePhase) ?? .stable\n }\n\n public func encode(to encoder: Encoder) throws {\n var values = encoder.container(keyedBy: CodingKeys.self)\n try values.encode(releasePhase, forKey: .releasePhase)\n }\n}\n\n// MARK: - Key store\n\n/// Stores provider API keys as a JSON file in Application Support.\n/// Avoids Keychain permission dialogs that occur when the binary identity\n/// changes on every `swift build` during development.\npublic enum AIKeychain {\n private static var keysFileURL: URL? {\n FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)\n .first?\n .appendingPathComponent(\"Guava\", isDirectory: true)\n .appendingPathComponent(\"ai_keys.json\")\n }\n\n private static func loadAll() -> [String: String] {\n guard let url = keysFileURL,\n let data = try? Data(contentsOf: url),\n let dict = try? JSONDecoder().decode([String: String].self, from: data)\n else { return [:] }\n return dict\n }\n\n private static func saveAll(_ dict: [String: String]) {\n guard let url = keysFileURL else { return }\n try? FileManager.default.createDirectory(at: url.deletingLastPathComponent(),\n withIntermediateDirectories: true)\n guard let data = try? JSONEncoder().encode(dict) else { return }\n try? data.write(to: url, options: [.atomic])\n }\n\n public static func save(key: String, provider: EditorAIProvider) {\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n var all = loadAll()\n all[provider.rawValue] = key\n saveAll(all)\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n guard let key = loadAll()[provider.rawValue], !key.isEmpty else { return nil }\n return key\n }\n\n public static func delete(provider: EditorAIProvider) {\n var all = loadAll()\n all.removeValue(forKey: provider.rawValue)\n saveAll(all)\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}, "3d43310f17d4ee74bf51996def872c88f30a3d0a": {"t": 1785757745.8119032, "n": 4742, "h": "2f6f6c04171ddee5", "c": "import AIRuntime\nimport CapabilityRuntime\nimport Foundation\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel) {\n self.provider = provider\n self.model = model\n }\n}\n\n// MARK: - Capability Settings\n\npublic enum EditorCapabilityReleasePhase: String, Codable, Sendable, Equatable, CaseIterable {\n case stable\n case beta\n case experimental\n\n public var displayName: String {\n switch self {\n case .stable: return \"Stable\"\n case .beta: return \"Beta\"\n case .experimental: return \"Experimental\"\n }\n }\n\n var runtimePhase: CapabilityReleasePhase {\n switch self {\n case .stable: return .stable\n case .beta: return .beta\n case .experimental: return .experimental\n }\n }\n}\n\npublic struct EditorCapabilitySettings: Codable, Sendable, Equatable {\n public var releasePhase: EditorCapabilityReleasePhase\n\n public static let `default` = EditorCapabilitySettings(releasePhase: .stable)\n\n public init(releasePhase: EditorCapabilityReleasePhase = .stable) {\n self.releasePhase = releasePhase\n }\n\n private enum CodingKeys: String, CodingKey {\n case releasePhase\n }\n\n public init(from decoder: Decoder) throws {\n let values = try decoder.container(keyedBy: CodingKeys.self)\n releasePhase = try values.decodeIfPresent(EditorCapabilityReleasePhase.self,\n forKey: .releasePhase) ?? .stable\n }\n\n public func encode(to encoder: Encoder) throws {\n var values = encoder.container(keyedBy: CodingKeys.self)\n try values.encode(releasePhase, forKey: .releasePhase)\n }\n}\n\n// MARK: - Key store\n\n/// Stores provider API keys as a JSON file in Application Support.\n/// Avoids Keychain permission dialogs that occur when the binary identity\n/// changes on every `swift build` during development.\npublic enum AIKeychain {\n private static var keysFileURL: URL? {\n FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)\n .first?\n .appendingPathComponent(\"Guava\", isDirectory: true)\n .appendingPathComponent(\"ai_keys.json\")\n }\n\n private static func loadAll() -> [String: String] {\n guard let url = keysFileURL,\n let data = try? Data(contentsOf: url),\n let dict = try? JSONDecoder().decode([String: String].self, from: data)\n else { return [:] }\n return dict\n }\n\n private static func saveAll(_ dict: [String: String]) {\n guard let url = keysFileURL else { return }\n try? FileManager.default.createDirectory(at: url.deletingLastPathComponent(),\n withIntermediateDirectories: true)\n guard let data = try? JSONEncoder().encode(dict) else { return }\n try? data.write(to: url, options: [.atomic])\n }\n\n public static func save(key: String, provider: EditorAIProvider) {\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n var all = loadAll()\n all[provider.rawValue] = key\n saveAll(all)\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n guard let key = loadAll()[provider.rawValue], !key.isEmpty else { return nil }\n return key\n }\n\n public static func delete(provider: EditorAIProvider) {\n var all = loadAll()\n all.removeValue(forKey: provider.rawValue)\n saveAll(all)\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}, "6efce5ca4b7586f8ebeedfe354979f85083e99eb": {"t": 1785757747.413135, "n": 3206, "h": "554eea70581d7f39", "c": "import AIRuntime\nimport Foundation\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel) {\n self.provider = provider\n self.model = model\n }\n}\n\n// MARK: - Key store\n\n/// Stores provider API keys as a JSON file in Application Support.\n/// Avoids Keychain permission dialogs that occur when the binary identity\n/// changes on every `swift build` during development.\npublic enum AIKeychain {\n private static var keysFileURL: URL? {\n FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)\n .first?\n .appendingPathComponent(\"Guava\", isDirectory: true)\n .appendingPathComponent(\"ai_keys.json\")\n }\n\n private static func loadAll() -> [String: String] {\n guard let url = keysFileURL,\n let data = try? Data(contentsOf: url),\n let dict = try? JSONDecoder().decode([String: String].self, from: data)\n else { return [:] }\n return dict\n }\n\n private static func saveAll(_ dict: [String: String]) {\n guard let url = keysFileURL else { return }\n try? FileManager.default.createDirectory(at: url.deletingLastPathComponent(),\n withIntermediateDirectories: true)\n guard let data = try? JSONEncoder().encode(dict) else { return }\n try? data.write(to: url, options: [.atomic])\n }\n\n public static func save(key: String, provider: EditorAIProvider) {\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n var all = loadAll()\n all[provider.rawValue] = key\n saveAll(all)\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n guard let key = loadAll()[provider.rawValue], !key.isEmpty else { return nil }\n return key\n }\n\n public static func delete(provider: EditorAIProvider) {\n var all = loadAll()\n all.removeValue(forKey: provider.rawValue)\n saveAll(all)\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}, "e2e094e2153575d7fdc574556a0fa0071a001cfa": {"t": 1785757748.908845, "n": 3117, "h": "b8238abfc737dadf", "c": "import AIRuntime\nimport Foundation\nimport Security\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n case openai\n case deepseek\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n case .openai: return \"OpenAI\"\n case .deepseek: return \"DeepSeek\"\n }\n }\n\n public var defaultModel: String {\n switch self {\n case .none: return \"\"\n case .anthropic: return SessionConfig.defaultAnthropicModel\n case .openai: return SessionConfig.defaultOpenAIModel\n case .deepseek: return SessionConfig.defaultDeepSeekModel\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultAnthropicModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultAnthropicModel) {\n self.provider = provider\n self.model = model\n }\n}\n\n// MARK: - Keychain\n\n/// Stores provider API keys in the system Keychain under the Guava service name.\n/// Keys are indexed by provider raw value so each provider has an independent slot.\npublic enum AIKeychain {\n private static let service = \"dev.guava.editor.ai\"\n\n public static func save(key: String, provider: EditorAIProvider) {\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n let data = Data(key.utf8)\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n kSecValueData: data,\n ]\n SecItemDelete(query as CFDictionary)\n SecItemAdd(query as CFDictionary, nil)\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n kSecReturnData: true,\n kSecMatchLimit: kSecMatchLimitOne,\n ]\n var result: AnyObject?\n guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,\n let data = result as? Data,\n let key = String(data: data, encoding: .utf8),\n !key.isEmpty else {\n return nil\n }\n return key\n }\n\n public static func delete(provider: EditorAIProvider) {\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n ]\n SecItemDelete(query as CFDictionary)\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}, "c92313ff64a1e236c456d6c25f106cd957d1fc7d": {"t": 1785757750.7383764, "n": 2664, "h": "5c4610478f4d03a5", "c": "import AIRuntime\nimport Foundation\nimport Security\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: SessionConfig.defaultModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = SessionConfig.defaultModel) {\n self.provider = provider\n self.model = model\n }\n}\n\n// MARK: - Keychain\n\n/// Stores provider API keys in the system Keychain under the Guava service name.\n/// Keys are indexed by provider raw value so each provider has an independent slot.\npublic enum AIKeychain {\n private static let service = \"dev.guava.editor.ai\"\n\n public static func save(key: String, provider: EditorAIProvider) {\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n let data = Data(key.utf8)\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n kSecValueData: data,\n ]\n SecItemDelete(query as CFDictionary)\n SecItemAdd(query as CFDictionary, nil)\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n kSecReturnData: true,\n kSecMatchLimit: kSecMatchLimitOne,\n ]\n var result: AnyObject?\n guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,\n let data = result as? Data,\n let key = String(data: data, encoding: .utf8),\n !key.isEmpty else {\n return nil\n }\n return key\n }\n\n public static func delete(provider: EditorAIProvider) {\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n ]\n SecItemDelete(query as CFDictionary)\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}, "d90b0e0e9118781934be4f12434b909eb40b4664": {"t": 1785757752.7541568, "n": 2693, "h": "609f702d749ae5d3", "c": "import Foundation\nimport Security\n\n// MARK: - Provider\n\npublic enum EditorAIProvider: String, Codable, Sendable, Equatable, CaseIterable {\n case none\n case anthropic\n\n public var displayName: String {\n switch self {\n case .none: return \"None\"\n case .anthropic: return \"Anthropic\"\n }\n }\n}\n\n// MARK: - Settings\n\npublic struct EditorAISettings: Codable, Sendable, Equatable {\n public var provider: EditorAIProvider\n public var model: String\n\n public static let `default` = EditorAISettings(\n provider: .none,\n model: AnthropicIntentResolverBackendConfig.defaultModel\n )\n\n public init(provider: EditorAIProvider = .none,\n model: String = AnthropicIntentResolverBackendConfig.defaultModel) {\n self.provider = provider\n self.model = model\n }\n}\n\n// MARK: - Keychain\n\n/// Stores provider API keys in the system Keychain under the Guava service name.\n/// Keys are indexed by provider raw value so each provider has an independent slot.\npublic enum AIKeychain {\n private static let service = \"dev.guava.editor.ai\"\n\n public static func save(key: String, provider: EditorAIProvider) {\n guard !key.isEmpty else {\n delete(provider: provider)\n return\n }\n let data = Data(key.utf8)\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n kSecValueData: data,\n ]\n SecItemDelete(query as CFDictionary)\n SecItemAdd(query as CFDictionary, nil)\n }\n\n public static func load(provider: EditorAIProvider) -> String? {\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n kSecReturnData: true,\n kSecMatchLimit: kSecMatchLimitOne,\n ]\n var result: AnyObject?\n guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess,\n let data = result as? Data,\n let key = String(data: data, encoding: .utf8),\n !key.isEmpty else {\n return nil\n }\n return key\n }\n\n public static func delete(provider: EditorAIProvider) {\n let query: [CFString: Any] = [\n kSecClass: kSecClassGenericPassword,\n kSecAttrService: service,\n kSecAttrAccount: provider.rawValue,\n ]\n SecItemDelete(query as CFDictionary)\n }\n\n public static func hasKey(for provider: EditorAIProvider) -> Bool {\n load(provider: provider) != nil\n }\n}\n"}}, "Editor/Tests/EditorCoreTests/EditorAISettingsTests.swift": {"65af0dd05a07b08706e654513935d492bd643759": {"t": 1785745899.685233, "n": 1398, "h": "c9d44c6ff6828301", "c": "@testable import EditorCore\nimport XCTest\n\nfinal class EditorAISettingsTests: XCTestCase {\n func testEnvironmentCredentialsUseGuavaOverrideBeforeStandardVariable() {\n let credential = AIKeychain.environmentCredential(\n provider: .openai,\n environment: [\n \"OPENAI_API_KEY\": \"standard\",\n \"GUAVA_OPENAI_API_KEY\": \"override\",\n ]\n )\n\n XCTAssertEqual(credential?.variable, \"GUAVA_OPENAI_API_KEY\")\n XCTAssertEqual(credential?.key, \"override\")\n }\n\n func testEnvironmentCredentialsSupportEveryRemoteProviderAndRejectBlanks() {\n XCTAssertEqual(\n AIKeychain.environmentCredential(\n provider: .anthropic,\n environment: [\"ANTHROPIC_API_KEY\": \" anthropic-key \"]\n )?.key,\n \"anthropic-key\"\n )\n XCTAssertEqual(\n AIKeychain.environmentCredential(\n provider: .deepseek,\n environment: [\"DEEPSEEK_API_KEY\": \"deepseek-key\"]\n )?.variable,\n \"DEEPSEEK_API_KEY\"\n )\n XCTAssertNil(AIKeychain.environmentCredential(\n provider: .openai,\n environment: [\"OPENAI_API_KEY\": \" \"]\n ))\n XCTAssertNil(AIKeychain.environmentCredential(\n provider: .none,\n environment: [\"OPENAI_API_KEY\": \"ignored\"]\n ))\n }\n}\n"}, "fa9e6a3a9b364db1856634a95b5da1e8d5579190": {"t": 1785757740.9038486, "n": 1398, "h": "c9d44c6ff6828301", "c": "@testable import EditorCore\nimport XCTest\n\nfinal class EditorAISettingsTests: XCTestCase {\n func testEnvironmentCredentialsUseGuavaOverrideBeforeStandardVariable() {\n let credential = AIKeychain.environmentCredential(\n provider: .openai,\n environment: [\n \"OPENAI_API_KEY\": \"standard\",\n \"GUAVA_OPENAI_API_KEY\": \"override\",\n ]\n )\n\n XCTAssertEqual(credential?.variable, \"GUAVA_OPENAI_API_KEY\")\n XCTAssertEqual(credential?.key, \"override\")\n }\n\n func testEnvironmentCredentialsSupportEveryRemoteProviderAndRejectBlanks() {\n XCTAssertEqual(\n AIKeychain.environmentCredential(\n provider: .anthropic,\n environment: [\"ANTHROPIC_API_KEY\": \" anthropic-key \"]\n )?.key,\n \"anthropic-key\"\n )\n XCTAssertEqual(\n AIKeychain.environmentCredential(\n provider: .deepseek,\n environment: [\"DEEPSEEK_API_KEY\": \"deepseek-key\"]\n )?.variable,\n \"DEEPSEEK_API_KEY\"\n )\n XCTAssertNil(AIKeychain.environmentCredential(\n provider: .openai,\n environment: [\"OPENAI_API_KEY\": \" \"]\n ))\n XCTAssertNil(AIKeychain.environmentCredential(\n provider: .none,\n environment: [\"OPENAI_API_KEY\": \"ignored\"]\n ))\n }\n}\n"}}}}