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.
43 lines
1.9 KiB
Swift
43 lines
1.9 KiB
Swift
import AgentKit
|
|
import Foundation
|
|
|
|
/// Resolves `.chat`, deliberately, rather than introducing a Titler role.
|
|
///
|
|
/// `AIRole` argues the case: a role earns its place by wanting a *materially
|
|
/// different* model, and titling wants "whatever the chat model is, but
|
|
/// cheaper" — which the Chat picker already expresses. Cheapness comes from
|
|
/// clamping the reasoning budget instead, the same way Command Generator does.
|
|
@MainActor
|
|
public final class ConversationTitleController: ConversationTitling {
|
|
private let ai: AIConfigurationModel
|
|
private let service: ConversationTitleService
|
|
|
|
public init(ai: AIConfigurationModel, service: ConversationTitleService = ConversationTitleService()) {
|
|
self.ai = ai
|
|
self.service = service
|
|
}
|
|
|
|
public func summarize(_ context: ConversationTitleContext) async throws -> String {
|
|
guard let resolved = ai.resolve(.chat) else {
|
|
throw ConversationTitleError.missingModel
|
|
}
|
|
guard let secret = ai.modelSecret(for: resolved.provider) else {
|
|
throw ConversationTitleError.missingCredential
|
|
}
|
|
// `.off`, not `.low`. The shared clamp walks *upward* first, which is
|
|
// right for Guardian — a review that does not reason is worse
|
|
// than a slow one — but backwards here. Asking for low on a model whose
|
|
// levels are `[off, high]`, as several Kimi builds are, hands back high:
|
|
// full reasoning effort to write six words. Asking for off takes off
|
|
// where it exists and the lowest available level where it does not.
|
|
let reasoning = ModelCapabilityResolver.reasoning(
|
|
model: resolved.model, provider: resolved.provider
|
|
).clamp(.off)
|
|
let model = AgentProviderClient(
|
|
provider: resolved.provider, model: resolved.model, secret: secret,
|
|
reasoning: reasoning
|
|
)
|
|
return try await service.summarize(context: context, model: model)
|
|
}
|
|
}
|