Allow optional API keys in desktop and mobile settings and share credential resolution across model requests. Missing stored credentials remain errors. Enable user-configured HTTP endpoints and pin signed AgentKit 0.13.1, which omits empty authentication headers for probes and inference. Validation: AgentKit's 216 tests and iOS's 461 tests passed, along with Swift formatting, localization checks, native screenshots, and real HTTP smoke tests on both apps. macOS passed 1419 of 1422 tests initially; existing Metrics timing, waveform PNG comparison, and MCP upload runner failures were observed. MCP and Metrics passed a focused rerun, while Metrics remains intermittent and waveform comparison still fails. iOS simulator LAN-IP access timed out; desktop LAN-IP and both platforms' localhost, loopback-IP, and local-domain requests passed.
77 lines
3.3 KiB
Swift
77 lines
3.3 KiB
Swift
import AgentKit
|
|
import SwiftData
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import TermindInfrastructure
|
|
import TermindPersistence
|
|
import TermindPresentation
|
|
import TermindRemoteInfrastructure
|
|
import Testing
|
|
|
|
@testable import Termind
|
|
|
|
@Suite @MainActor
|
|
struct ProviderEndpointIntegrationTests {
|
|
@Test func anonymousHTTPProviderSurvivesPersistenceReload() throws {
|
|
let container = try ModelContainer(
|
|
for: HostPersistence.schema,
|
|
configurations: [ModelConfiguration(isStoredInMemoryOnly: true, cloudKitDatabase: .none)])
|
|
let provider = ModelProvider(name: "Local", models: [AIModel(id: "local-model")])
|
|
let store = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
|
|
#expect(store.saveProvider(provider, endpoint: ProviderEndpoint(url: "http://localhost:11434/v1")))
|
|
let reloaded = AIConfigurationModel(modelContext: container.mainContext, persistsSecrets: false)
|
|
let saved = try #require(reloaded.modelProvider(provider.id))
|
|
#expect(saved.credentialRef.isEmpty)
|
|
#expect(saved.inferenceURL == "http://localhost:11434/v1/chat/completions")
|
|
#expect(saved.models.map(\.id) == ["local-model"])
|
|
#expect(saved.canFetchModels)
|
|
#expect(reloaded.modelSecret(for: saved) == "")
|
|
#expect(CommandGeneratorController(ai: reloaded).isLocallyAvailable)
|
|
}
|
|
|
|
/// The round trip the editor depends on: what was typed comes back, and so
|
|
/// does the difference between `Automatic` and picking the same format.
|
|
@Test
|
|
func endpointsSurviveAReloadIncludingTheAutomaticChoice() throws {
|
|
let container = try ModelContainer(
|
|
for: HostPersistence.schema,
|
|
configurations: [
|
|
ModelConfiguration(
|
|
"EndpointRoundTrip", schema: HostPersistence.schema,
|
|
isStoredInMemoryOnly: true, cloudKitDatabase: .none
|
|
)
|
|
]
|
|
)
|
|
|
|
let automatic = ModelProvider(name: "Automatic")
|
|
let explicit = ModelProvider(name: "Explicit")
|
|
|
|
do {
|
|
let store = AIConfigurationModel(
|
|
modelContext: container.mainContext, persistsSecrets: false
|
|
)
|
|
store.upsert(automatic)
|
|
store.upsert(explicit)
|
|
store.setEndpoint(ProviderEndpoint(url: "https://x.test/v1"), for: automatic.id)
|
|
store.setEndpoint(
|
|
ProviderEndpoint(apiType: .messages, url: "https://x.test/v1"), for: explicit.id
|
|
)
|
|
#expect(store.storageError == nil)
|
|
}
|
|
|
|
let reloaded = AIConfigurationModel(
|
|
modelContext: container.mainContext, persistsSecrets: false
|
|
)
|
|
#expect(reloaded.endpoint(for: automatic.id).apiType == .automatic)
|
|
#expect(reloaded.endpoint(for: automatic.id).url == "https://x.test/v1")
|
|
#expect(reloaded.modelProvider(automatic.id)?.apiFormat == .chatCompletions)
|
|
#expect(
|
|
reloaded.modelProvider(automatic.id)?.inferenceURL == "https://x.test/v1/chat/completions"
|
|
)
|
|
|
|
#expect(reloaded.endpoint(for: explicit.id).apiType == .messages)
|
|
#expect(reloaded.endpoint(for: explicit.id).url == "https://x.test/v1")
|
|
#expect(reloaded.modelProvider(explicit.id)?.inferenceURL == "https://x.test/v1/messages")
|
|
}
|
|
}
|