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>
68 lines
2.2 KiB
Swift
68 lines
2.2 KiB
Swift
import Foundation
|
||
|
||
// MARK: - Install State
|
||
|
||
public enum ModelInstallState: Equatable, Sendable {
|
||
case notInstalled
|
||
case checkingSource
|
||
case downloading(completedFiles: Int, totalFiles: Int, currentFile: String)
|
||
case downloaded
|
||
case bundled
|
||
case failed(String)
|
||
}
|
||
|
||
public struct ModelDownloadMetrics: Equatable, Sendable {
|
||
public let bytesReceived: Int64
|
||
public let totalBytes: Int64?
|
||
public let bytesPerSecond: Double?
|
||
public let sourceLabel: String?
|
||
}
|
||
|
||
// MARK: - Errors
|
||
|
||
enum MLXError: LocalizedError {
|
||
case modelNotLoaded
|
||
case modelDirectoryMissing(String)
|
||
case gpuExecutionRequiresForeground
|
||
case multimodalMemoryRisk(model: String, headroomMB: Int, recommendation: String)
|
||
|
||
var errorDescription: String? {
|
||
switch self {
|
||
case .modelNotLoaded:
|
||
return "MLX model not loaded. Call load() first."
|
||
case .modelDirectoryMissing(let modelName):
|
||
return "\(modelName) 模型文件不存在,请先在配置页下载或重新安装。"
|
||
case .gpuExecutionRequiresForeground:
|
||
return "应用进入后台时,iPhone 不允许继续提交 GPU 推理任务。"
|
||
case .multimodalMemoryRisk(let model, let headroomMB, let recommendation):
|
||
return "\(model) 当前剩余内存仅约 \(headroomMB) MB,继续处理图片/音频很可能被系统直接杀掉。\(recommendation)"
|
||
}
|
||
}
|
||
}
|
||
|
||
enum DownloadError: LocalizedError {
|
||
case invalidURL(String)
|
||
case invalidResponse
|
||
case httpStatus(Int)
|
||
|
||
var errorDescription: String? {
|
||
switch self {
|
||
case .invalidURL(let file):
|
||
return "无法构造下载链接:\(file)"
|
||
case .invalidResponse:
|
||
return "下载源响应无效"
|
||
case .httpStatus(let statusCode):
|
||
switch statusCode {
|
||
case 401, 403:
|
||
return "下载源拒绝访问(\(statusCode))"
|
||
case 404:
|
||
return "模型文件不存在(404)"
|
||
case 429:
|
||
return "下载过于频繁,请稍后重试(429)"
|
||
default:
|
||
return "下载失败,HTTP \(statusCode)"
|
||
}
|
||
}
|
||
}
|
||
}
|