Files
oai-swift/oAI/Models/Settings.swift
T
runeandClaude Sonnet 5 30fbb4162e Revive Apple Intelligence provider (Phase 1 — on-device)
Apple Intelligence / Foundation Models is now genuinely available on
this machine (macOS 27 beta 4) — it was reverted back in June
(f63226b) when it wasn't. Ports the reverted AppleFoundationProvider
forward onto 2.4.3, adapted for everything that's changed since:
PolyForm license headers, current AIProvider protocol shape, current
Settings.Provider/ProviderRegistry/CreditsView/SettingsView structure.

Fixes a real bug found via live testing: LanguageModelSession.
GenerationError was deprecated in macOS 27.0 in favor of a new
LanguageModelError type. On a macOS 27+ runtime, generation failures
now throw LanguageModelError, not GenerationError, so the original
error-mapping catch never matched and Apple's raw error text leaked
to the user instead of oAI's friendly message. Now dispatches to
whichever type the runtime actually throws, gated with
@available(macOS 27.0, *), keeping the old GenerationError path as
a fallback for macOS 26.x (the app's actual deployment target).

Confirmed end-to-end in the live app: provider selectable, Settings
shows a live "Available" badge, chat header shows correct branding,
and — a real, expected Phase 1 limitation — oAI's default system
prompt (active Agent Skills + MCP tool guidance, ~16K tokens on this
machine) exceeds the on-device model's 4K context window on the
very first message. The friendly error message now correctly reports
this instead of Apple's raw string. Tool calling remains out of scope
until Phase 3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 09:46:17 +02:00

133 lines
3.7 KiB
Swift

//
// Settings.swift
// oAI
//
// Application settings and configuration
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling oAI or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://oai.pm>.
import Foundation
struct Settings: Codable {
// Provider settings
var defaultProvider: Provider
var openrouterAPIKey: String?
var anthropicAPIKey: String?
var openaiAPIKey: String?
var ollamaBaseURL: String
// Model settings
var defaultModel: String?
var streamEnabled: Bool
var maxTokens: Int
var systemPrompt: String?
var customPromptMode: CustomPromptMode
// Feature flags
var onlineMode: Bool
var memoryEnabled: Bool
var mcpEnabled: Bool
// Web search
var searchProvider: SearchProvider
var googleAPIKey: String?
var googleSearchEngineID: String?
// UI
var costWarningThreshold: Double
enum Provider: String, Codable, CaseIterable {
case openrouter
case anthropic
case openai
case ollama
case appleOnDevice = "apple_on_device"
var displayName: String {
switch self {
case .appleOnDevice: return "Apple Intelligence"
default: return rawValue.capitalized
}
}
var iconName: String {
switch self {
case .openrouter: return "network"
case .anthropic: return "brain"
case .openai: return "sparkles"
case .ollama: return "server.rack"
case .appleOnDevice: return "apple.logo"
}
}
}
enum SearchProvider: String, Codable, CaseIterable {
case anthropicNative = "anthropic_native"
case duckduckgo
case google
var displayName: String {
switch self {
case .anthropicNative: return "Anthropic Native"
case .duckduckgo: return "DuckDuckGo"
case .google: return "Google"
}
}
}
enum CustomPromptMode: String, Codable, CaseIterable {
case append = "append"
case replace = "replace"
var displayName: String {
switch self {
case .append: return "Append to Default"
case .replace: return "Replace Default (BYOP)"
}
}
var description: String {
switch self {
case .append: return "Your custom prompt will be added after the default system prompt"
case .replace: return "Only use your custom prompt (Bring Your Own Prompt)"
}
}
}
// Default settings
static let `default` = Settings(
defaultProvider: .openrouter,
openrouterAPIKey: nil,
anthropicAPIKey: nil,
openaiAPIKey: nil,
ollamaBaseURL: "http://localhost:11434",
defaultModel: nil,
streamEnabled: true,
maxTokens: 4096,
systemPrompt: nil,
customPromptMode: .append,
onlineMode: false,
memoryEnabled: true,
mcpEnabled: false,
searchProvider: .duckduckgo,
googleAPIKey: nil,
googleSearchEngineID: nil,
costWarningThreshold: 1.0
)
}