diff --git a/Package.swift b/Package.swift index f8d6303..3dec3bb 100644 --- a/Package.swift +++ b/Package.swift @@ -66,6 +66,13 @@ let package = Package( ], path: "Sources/Lite", resources: [.process("Resources")] + ), + .testTarget( + name: "LiteTests", + dependencies: [ + "Lite" + ], + path: "Sources/Tests" ) ] ) diff --git a/Sources/Lite/Intramodular/Lite.swift b/Sources/Lite/Intramodular/Lite.swift index 9c58f79..99f17f0 100644 --- a/Sources/Lite/Intramodular/Lite.swift +++ b/Sources/Lite/Intramodular/Lite.swift @@ -27,12 +27,12 @@ extension Lite { /// public final class Lite: _CancellablesProviding, Logging, ObservableObject { private let queue = TaskQueue() - + private var shouldAutoinitializeServices: Bool @Published private var autoinitializedServices: [any _MIService]? = nil @Published private var manuallyAddedServices: [any _MIService] = [] - + // @Published public var modelIdentifierScope: _MLModelIdentifierScope? public var services: [any _MIService] { @@ -54,10 +54,10 @@ public final class Lite: _CancellablesProviding, Logging, ObservableObject { self.setUp() } } - + private init() { shouldAutoinitializeServices = true - + Task { @MainActor in self.setUp() } @@ -120,11 +120,13 @@ extension Lite { /// Converts Lite accounts loaded from Lite's managed account store to CoreMI accounts. @MainActor private func _serviceAccounts() throws -> [_AnyMIServiceAccount] { - try LTAccountStore.shared.accounts.compactMap { + let allAccounts: IdentifierIndexingArrayOf = LTAccountStore.shared.accounts + (LTAccountStore.shared._testAccounts ?? []) + + return try allAccounts.compactMap { (account: LTAccount) in let credential = _MIServiceAPIKeyCredential( - apiKey: ($0.credential as! _LTAccountCredential.APIKey).key + apiKey: (account.credential as! _LTAccountCredential.APIKey).key ) - let service: _MIServiceTypeIdentifier = try $0.accountType.__conversion() + let service: _MIServiceTypeIdentifier = try account.accountType.__conversion() return _AnyMIServiceAccount( serviceIdentifier: service, @@ -132,22 +134,13 @@ extension Lite { ) } } - - private func _serviceTypes() throws -> [any _MIService.Type] { - try _SwiftRuntime.index - .fetch( - .nonAppleFramework, - .conformsTo((any _MIService).self) - ) - .map({ try cast($0) }) - } - + /// Initializes all CoreMI services that can be initialized using the loaded Lite accounts. public func _makeServices() async throws -> [any _MIService] { - let serviceTypes = try _serviceTypes() - let accounts = try await _serviceAccounts() + let serviceTypes: [any _MIService.Type] = try TypeMetadata._queryAll(.nonAppleFramework, .conformsTo((any _MIService).self)) + let serviceAccounts: [any _MIServiceAccount] = try await _serviceAccounts() - var result: [any _MIService] = await accounts + var result: [any _MIService] = await serviceAccounts .concurrentMap { account in await serviceTypes.first(byUnwrapping: { type -> (any _MIService)? in do { diff --git a/Sources/Lite/Intramodular/Miscellaneous/_PreternaturalDotFile.swift b/Sources/Lite/Intramodular/Miscellaneous/_PreternaturalDotFile.swift new file mode 100644 index 0000000..a7eb6a3 --- /dev/null +++ b/Sources/Lite/Intramodular/Miscellaneous/_PreternaturalDotFile.swift @@ -0,0 +1,10 @@ +// +// Copyright (c) Vatsal Manot +// + + +import Foundation + +public struct _PreternaturalDotFile: Codable, Hashable, Sendable { + public var TEST_OPENAI_KEY: String? +} diff --git a/Sources/Lite/Intramodular/Models/LTAccountStore.swift b/Sources/Lite/Intramodular/Models/LTAccountStore.swift index 4e26741..7035887 100644 --- a/Sources/Lite/Intramodular/Models/LTAccountStore.swift +++ b/Sources/Lite/Intramodular/Models/LTAccountStore.swift @@ -8,8 +8,9 @@ import Swallow import SwiftUIX @MainActor -@Singleton public final class LTAccountStore: ObservableObject { + public static let shared = LTAccountStore() + @FileStorage( directory: .appDocuments, path: "Lite/Accounts", @@ -19,6 +20,9 @@ public final class LTAccountStore: ObservableObject { ) public var accounts: IdentifierIndexingArrayOf + @Published + public var _testAccounts: IdentifierIndexingArrayOf? + private(set) lazy var allKnownAccountTypeDescriptions = { IdentifierIndexingArray( try! _SwiftRuntime.index @@ -32,6 +36,28 @@ public final class LTAccountStore: ObservableObject { ) .sorted(by: { $0.title < $1.title }) }() + + public init() { + _loadTestAccountsIfNeeded() + } +} + +extension LTAccountStore { + fileprivate func _loadTestAccountsIfNeeded() { + @FileStorage( + url: URL.homeDirectory.appending(path: ".preternatural.toml"), + coder: TOMLCoder() + ) + var dotfile: _PreternaturalDotFile? = nil + + if let TEST_OPENAI_KEY = dotfile?.TEST_OPENAI_KEY { + self._testAccounts = [LTAccount( + accountType: LTAccountTypeDescriptions.OpenAI().accountType, + credential: _LTAccountCredential.APIKey(key: TEST_OPENAI_KEY), + description: nil + )] + } + } } extension LTAccountStore { diff --git a/Sources/Tests/LTAccountStoreTests.swift b/Sources/Tests/LTAccountStoreTests.swift new file mode 100644 index 0000000..5d3e1f5 --- /dev/null +++ b/Sources/Tests/LTAccountStoreTests.swift @@ -0,0 +1,15 @@ +// +// Copyright (c) Vatsal Manot +// + +import Lite +import XCTest + +final class LTAccountStoreTests: XCTestCase { + @MainActor + func testAccountStore() async throws { + let store = LTAccountStore.shared + + store + } +}