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>
56 lines
1.7 KiB
Swift
56 lines
1.7 KiB
Swift
import Foundation
|
|
import MLXLMCommon
|
|
import Tokenizers
|
|
|
|
/// Bridges `swift-tokenizers` to `MLXLMCommon.Tokenizer`.
|
|
struct MLXTokenizerBridge: MLXLMCommon.Tokenizer {
|
|
private let upstream: any Tokenizers.Tokenizer
|
|
|
|
init(_ upstream: any Tokenizers.Tokenizer) {
|
|
self.upstream = upstream
|
|
}
|
|
|
|
func encode(text: String, addSpecialTokens: Bool) -> [Int] {
|
|
upstream.encode(text: text, addSpecialTokens: addSpecialTokens)
|
|
}
|
|
|
|
func decode(tokenIds: [Int], skipSpecialTokens: Bool) -> String {
|
|
upstream.decode(tokenIds: tokenIds, skipSpecialTokens: skipSpecialTokens)
|
|
}
|
|
|
|
func convertTokenToId(_ token: String) -> Int? {
|
|
upstream.convertTokenToId(token)
|
|
}
|
|
|
|
func convertIdToToken(_ id: Int) -> String? {
|
|
upstream.convertIdToToken(id)
|
|
}
|
|
|
|
var bosToken: String? { upstream.bosToken }
|
|
var eosToken: String? { upstream.eosToken }
|
|
var unknownToken: String? { upstream.unknownToken }
|
|
|
|
func applyChatTemplate(
|
|
messages: [[String: any Sendable]],
|
|
tools: [[String: any Sendable]]?,
|
|
additionalContext: [String: any Sendable]?
|
|
) throws -> [Int] {
|
|
do {
|
|
return try upstream.applyChatTemplate(
|
|
messages: messages,
|
|
tools: tools,
|
|
additionalContext: additionalContext
|
|
)
|
|
} catch let error as Tokenizers.TokenizerError where error == .missingChatTemplate {
|
|
throw MLXLMCommon.TokenizerError.missingChatTemplate
|
|
}
|
|
}
|
|
}
|
|
|
|
struct MLXTokenizersLoader: TokenizerLoader {
|
|
func load(from directory: URL) async throws -> any MLXLMCommon.Tokenizer {
|
|
let upstream = try await AutoTokenizer.from(directory: directory)
|
|
return MLXTokenizerBridge(upstream)
|
|
}
|
|
}
|