Major restructuring of the agent stack to support content-type skills, data-driven runtime budgets, and the new translate skill. LLM/MLX/ - Split MLXLocalLLMService.swift (1500 → 700 lines) into Core/, Gemma4/, Installation/ subdirectories - New BundledModel: LinearBudgetFormula replaces tier tables, model parameters fully data-driven via ModelRuntimeProfile - New RuntimeBudgets: pure functions for text/thinking/multimodal budgets, zero hardcoded model IDs - Drop adjustedTextOutputTokens prepared-subtraction (chunked prefill made it obsolete and triggered false-positive truncation on long prompts) Skills/ - Add SkillType (.device / .content) metadata, parsed from SKILL.md frontmatter - New translate skill (type: content, no tools) with anaphora handling - Trim translate (4563→1804), device (2357→1680), calendar (2169→1790) - contacts SKILL.md adds multi-turn disambiguation + cancel guidance, semantics owned by body not framework - SkillRegistry validates allowed-tools against ToolRegistry at load time Agent/ - ToolCallParser becomes registry-aware: drops tool_call names not in ToolRegistry or protocol set (load_skill / list_skills); ToolRegistry is now the single white list, validation moves to parser boundary - Router sticky routing uses semantic user-turn boundary instead of fixed suffix(4); correctly handles long agent loops - AgentEngine system prompt template differentiates device vs content skills with explicit invocation rules - SYSPROMPT.md auto-migration: old single-placeholder template backed up to .bak when new dual-placeholder is detected - PromptBuilder.buildLoadedSkillPrompt receives availableTools list, explicitly tells model which tools (or none) are callable Tools/ - ToolRegistry split into Helpers/Stores/AppPermissions/Handlers/* for separation of concerns - Mark Tools/Handlers/* with explicit allowed argument signatures UI/ - Add long-press copy contextMenu on user and assistant bubbles - Enable .textSelection on user bubble Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
60 lines
1.9 KiB
Swift
60 lines
1.9 KiB
Swift
import Foundation
|
|
import MLX
|
|
import MLXLLM
|
|
import MLXLMCommon
|
|
import MLXVLM
|
|
|
|
// MARK: - Gemma 4 Model Registration
|
|
|
|
/// Register the "gemma4" model type with both the text and VLM registries.
|
|
///
|
|
/// Call this once at app startup before loading models:
|
|
/// ```swift
|
|
/// await Gemma4Registration.register()
|
|
/// ```
|
|
public enum Gemma4Registration {
|
|
private static let runtimeOptionsLock = NSLock()
|
|
private static var audioCapabilityEnabled = false
|
|
|
|
public static func setAudioCapabilityEnabled(_ enabled: Bool) {
|
|
runtimeOptionsLock.lock()
|
|
audioCapabilityEnabled = enabled
|
|
runtimeOptionsLock.unlock()
|
|
}
|
|
|
|
private static func currentAudioCapabilityEnabled() -> Bool {
|
|
runtimeOptionsLock.lock()
|
|
let enabled = audioCapabilityEnabled
|
|
runtimeOptionsLock.unlock()
|
|
return enabled
|
|
}
|
|
|
|
private static func makeRuntimeConfiguration(from data: Data) throws -> Gemma4ModelConfiguration {
|
|
let configuration = try JSONDecoder.json5().decode(
|
|
Gemma4ModelConfiguration.self,
|
|
from: data
|
|
)
|
|
return configuration.withAudioCapability(enabled: currentAudioCapabilityEnabled())
|
|
}
|
|
|
|
public static func register() async {
|
|
await LLMTypeRegistry.shared.registerModelType("gemma4") { data in
|
|
let configuration = try makeRuntimeConfiguration(from: data)
|
|
return Gemma4Model(configuration)
|
|
}
|
|
|
|
await VLMTypeRegistry.shared.registerModelType("gemma4") { data in
|
|
let configuration = try makeRuntimeConfiguration(from: data)
|
|
return Gemma4Model(configuration)
|
|
}
|
|
|
|
await VLMProcessorTypeRegistry.shared.registerProcessorType("Gemma4Processor") { data, tokenizer in
|
|
let configuration = try JSONDecoder.json5().decode(
|
|
Gemma4ProcessorConfiguration.self,
|
|
from: data
|
|
)
|
|
return Gemma4Processor(configuration, tokenizer: tokenizer)
|
|
}
|
|
}
|
|
}
|