Files
luma/Sources/LumaCore/Agentic/LLMProvider.swift
Ole André Vadla Ravnås b74976687b gtk: Fix base URL entry and allow optional API key
The base URL only applied on Enter, and applyProviderSelection
reset it to the stored value, wiping typed text. Persist edits
after a debounce instead, and only load the stored URL when
switching provider.

Servers like open-webui need a Bearer token, so introduce an
optionalAPIKey capability, declare it on the local
OpenAI-compatible provider, and show the key field without
gating Start on it.

Refresh only the model list when the base URL or API key
changes, instead of rebuilding the whole provider section: the
full rebuild flickered, and hid the API key entry while typing
into it. Guard async model loads with a generation counter so
stale results cannot overwrite newer ones.
2026-06-04 23:31:20 +02:00

97 lines
2.6 KiB
Swift

import Foundation
public enum LLMCapability: String, Sendable, Hashable, CaseIterable, Codable {
case streaming
case promptCaching
case thinking
case toolUse
case apiKey
case optionalAPIKey
case customBaseURL
}
public struct LLMProviderCapabilities: Sendable, Hashable {
public var supported: Set<LLMCapability>
public var reasoningEffortOptions: [String]
public var defaultReasoningEffort: String?
public init(
supported: Set<LLMCapability> = [],
reasoningEffortOptions: [String] = [],
defaultReasoningEffort: String? = nil
) {
self.supported = supported
self.reasoningEffortOptions = reasoningEffortOptions
self.defaultReasoningEffort = defaultReasoningEffort
}
public func supports(_ capability: LLMCapability) -> Bool {
supported.contains(capability)
}
}
public struct LLMProviderDescriptor: Sendable, Hashable {
public var id: String
public var displayName: String
public var capabilities: LLMProviderCapabilities
public var defaultModelID: String?
public var summarizationModelID: String?
public var defaultBaseURL: URL
public init(
id: String,
displayName: String,
capabilities: LLMProviderCapabilities,
defaultModelID: String?,
summarizationModelID: String? = nil,
defaultBaseURL: URL
) {
self.id = id
self.displayName = displayName
self.capabilities = capabilities
self.defaultModelID = defaultModelID
self.summarizationModelID = summarizationModelID
self.defaultBaseURL = defaultBaseURL
}
}
public protocol LLMProvider: Sendable {
var descriptor: LLMProviderDescriptor { get }
func streamTurn(
_ request: LLMTurnRequest,
apiKey: String?,
baseURL: URL?
) -> AsyncThrowingStream<LLMTurnEvent, Error>
func suggestedModels(apiKey: String?, baseURL: URL?) async throws -> [LLMModelInfo]
}
@MainActor
public final class LLMProviderRegistry {
private var providersByID: [String: any LLMProvider] = [:]
private var orderedIDs: [String] = []
public init() {}
public func register(_ provider: any LLMProvider) {
let id = provider.descriptor.id
if providersByID[id] == nil {
orderedIDs.append(id)
}
providersByID[id] = provider
}
public func provider(id: String) -> (any LLMProvider)? {
providersByID[id]
}
public func providers() -> [any LLMProvider] {
orderedIDs.compactMap { providersByID[$0] }
}
public func descriptors() -> [LLMProviderDescriptor] {
providers().map(\.descriptor)
}
}