From 364417597ee6d76c40d4aa56e17c33df39098586 Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Thu, 7 May 2026 12:22:26 -0300 Subject: [PATCH 1/9] chore: stabilize local CI parity Adds scripts/codex local CI and worktree helpers for Phase A0 orchestration. Applies SwiftLint cleanup needed for strict lint on origin/main 30c6522036346047f40deac3d6fbc8f263e272b8, with scoped disables for legacy policy rules. Fixes SandboxInstallLockTests to assert same-agent serialization without relying on async scheduling order. Local verification: scripts/codex/local-ci.sh PASS (attempt 3, 2026-05-07T15:20:18Z). --- .swiftlint.yml | 3 + .../osaurusUITestsLaunchTests.swift | 2 +- .../OsaurusCLICore/Commands/MCPCommand.swift | 5 +- .../OsaurusCLICore/Commands/Pull.swift | 6 +- .../Sources/OsaurusCLICore/Commands/Run.swift | 3 +- .../OsaurusCLICore/Commands/Serve.swift | 4 +- .../OsaurusCLICore/Commands/Show.swift | 3 +- .../Commands/Tools/ToolsCreate.swift | 110 ++++++++-------- .../Commands/Tools/ToolsDev.swift | 12 +- .../Commands/Tools/ToolsInstall.swift | 8 +- .../Commands/Tools/ToolsList.swift | 3 +- .../Commands/Tools/ToolsOutdated.swift | 3 +- .../Commands/Tools/ToolsPackage.swift | 9 +- .../Commands/Tools/ToolsSearch.swift | 2 +- .../Commands/Tools/ToolsUninstall.swift | 3 +- .../Commands/Tools/ToolsUpgrade.swift | 2 +- .../OsaurusCLICore/Services/AppControl.swift | 4 +- .../Folder/FolderContextService.swift | 4 +- Packages/OsaurusCore/Folder/FolderTools.swift | 10 +- .../Managers/BackgroundTaskManager.swift | 6 +- .../OsaurusCore/Managers/BlockMemoizer.swift | 3 +- .../Managers/Chat/ChatWindowManager.swift | 2 +- .../Managers/Chat/ChatWindowState.swift | 8 +- .../Managers/InferenceProgressManager.swift | 4 +- .../Managers/InsightsService.swift | 8 +- .../Managers/MCPProviderManager.swift | 5 +- .../Managers/Model/ModelManager.swift | 25 ++-- .../Managers/Plugin/PluginManager.swift | 48 +++---- .../Plugin/SandboxPluginLibrary.swift | 9 +- .../Plugin/SandboxPluginManager.swift | 3 +- .../Managers/PluginActivityManager.swift | 2 +- .../Managers/ScheduleManager.swift | 7 +- .../OsaurusCore/Managers/SpeechService.swift | 22 ++-- .../OsaurusCore/Managers/WatcherManager.swift | 2 +- .../OsaurusCore/Managers/WindowManager.swift | 2 +- .../OsaurusCore/Models/API/AnthropicAPI.swift | 7 +- .../OsaurusCore/Models/API/OpenAIAPI.swift | 47 ++++--- .../Models/API/OpenResponsesAPI.swift | 7 +- Packages/OsaurusCore/Models/Agent/Skill.swift | 6 +- .../OsaurusCore/Models/Agent/SkillStore.swift | 3 +- .../OsaurusCore/Models/Chat/ChatTurn.swift | 10 +- .../Models/Chat/ContentBlock.swift | 33 ++--- .../Models/Chat/InternalMessage.swift | 5 - .../OsaurusCore/Models/Chat/RequestLog.swift | 6 +- .../Models/Chat/ResponseWriters.swift | 12 +- .../Models/Chat/SessionSource.swift | 3 +- .../Models/Chat/SharedArtifact.swift | 6 +- .../Models/Configuration/MLXModel.swift | 16 +-- .../Models/Configuration/ModelInfo.swift | 3 +- .../Configuration/ModelMetadataParser.swift | 7 +- .../RemoteProviderConfiguration.swift | 3 +- .../Configuration/ServerConfiguration.swift | 6 +- .../Models/Tool/ToolConfiguration.swift | 4 +- .../Networking/BonjourAdvertiser.swift | 6 +- .../Networking/BonjourBrowser.swift | 2 +- .../OsaurusCore/Networking/HTTPHandler.swift | 121 ++++++++---------- .../Networking/HTTPRequestParse.swift | 2 +- .../Networking/HostAPIBridgeServer.swift | 18 +-- .../Networking/OsaurusServer.swift | 5 +- .../Networking/RelayTunnelManager.swift | 6 +- .../Networking/ServerController.swift | 15 +-- .../Services/Chat/AgentNameDetector.swift | 8 +- .../Services/Chat/ChatEngine.swift | 9 +- .../Services/Chat/ContextSizeClass.swift | 3 +- .../Services/Chat/SystemPromptComposer.swift | 29 ++--- .../Context/CapabilitySearchHealth.swift | 2 +- .../Services/Context/ClipboardService.swift | 3 +- .../Context/PreflightCapabilitySearch.swift | 6 +- .../Context/PreflightCompanions.swift | 3 +- .../Services/DirectoryPickerService.swift | 6 +- .../Services/GitHubSkillService.swift | 3 +- .../Services/HuggingFaceService.swift | 9 +- .../Inference/FoundationModelService.swift | 22 ++-- .../Services/Inference/MLXService.swift | 2 +- .../Services/Inference/ModelService.swift | 2 +- .../Keychain/ToolSecretsKeychain.swift | 6 +- .../Services/LocalGenerationDefaults.swift | 4 +- .../Services/LocalReasoningCapability.swift | 13 +- .../Services/MCP/MCPProviderKeychain.swift | 3 +- .../Services/MCP/MCPServerManager.swift | 2 +- .../Services/Memory/MemoryService.swift | 15 +-- .../Services/ModelDownloadService.swift | 16 +-- .../OsaurusCore/Services/ModelRuntime.swift | 34 ++--- .../ModelRuntime/MLXErrorRecovery.swift | 4 +- .../ModelRuntime/RollingTokenRate.swift | 2 +- .../Services/NotificationService.swift | 2 +- .../PluginHostAPI+SessionPersistence.swift | 3 +- .../Services/Plugin/PluginHostAPI.swift | 18 +-- .../Plugin/PluginRepositoryService.swift | 2 +- .../Provider/OpenAICodexOAuthService.swift | 3 +- .../Provider/RemoteProviderKeychain.swift | 3 +- .../Provider/RemoteProviderService.swift | 51 +++----- .../Provider/RemoteToolDetection.swift | 18 +-- .../Services/Sandbox/SandboxManager.swift | 22 ++-- .../Services/SystemMonitorService.swift | 8 +- .../Services/Tool/ToolSearchService.swift | 3 +- .../OsaurusCore/Services/UpdaterService.swift | 2 +- .../Voice/TranscriptionCleanupService.swift | 6 +- .../Storage/ChatHistoryDatabase.swift | 6 +- .../OsaurusCore/Storage/MemoryDatabase.swift | 6 +- .../OsaurusCore/Storage/PluginDatabase.swift | 3 +- .../OsaurusCore/Storage/StorageMigrator.swift | 3 +- .../Sandbox/SandboxInstallLockTests.swift | 96 +++++++++----- .../OsaurusCore/Tools/AgentLoopTools.swift | 3 +- .../Tools/BuiltinSandboxTools.swift | 9 +- .../OsaurusCore/Tools/MCPProviderTool.swift | 6 +- Packages/OsaurusCore/Tools/OsaurusTool.swift | 3 +- .../OsaurusCore/Tools/RenderChartTool.swift | 4 +- .../OsaurusCore/Tools/SandboxPluginTool.swift | 3 +- .../OsaurusCore/Tools/SchemaValidator.swift | 15 +-- .../OsaurusCore/Tools/ShareArtifactTool.swift | 6 +- Packages/OsaurusCore/Tools/ToolEnvelope.swift | 6 +- Packages/OsaurusCore/Tools/ToolRegistry.swift | 4 +- Packages/OsaurusCore/Utils/OsaurusPaths.swift | 14 +- .../Utils/StreamingDeltaProcessor.swift | 3 +- .../OsaurusCore/Utils/StringCleaning.swift | 3 +- .../OsaurusCore/Views/Agent/AgentsView.swift | 12 +- .../CapabilitiesTableRepresentable.swift | 6 +- .../Views/Chat/ChatEmptyState.swift | 8 +- .../Views/Chat/ChatSessionSidebar.swift | 7 +- .../OsaurusCore/Views/Chat/ChatView.swift | 40 +++--- .../OsaurusCore/Views/Chat/DocumentChip.swift | 2 +- .../Views/Chat/EditableTextView.swift | 10 +- .../Views/Chat/FloatingInputCard.swift | 53 +++----- .../Views/Chat/MarkdownImageView.swift | 2 +- .../Views/Chat/MarkdownMessageView.swift | 6 +- .../Chat/MessageTableRepresentable.swift | 39 +++--- .../Views/Chat/MessageThreadView.swift | 26 ++-- .../Views/Chat/NativeArtifactCardView.swift | 6 +- .../Views/Chat/NativeBlockViews.swift | 4 +- .../Views/Chat/NativeChartView.swift | 3 +- .../Views/Chat/NativeMarkdownView.swift | 5 +- .../Views/Chat/NativeMessageCellView.swift | 31 ++--- .../Views/Chat/NativeToolCallGroupView.swift | 24 ++-- .../OsaurusCore/Views/Chat/PromptQueue.swift | 2 +- .../Views/Chat/SelectableTextView.swift | 15 +-- .../Chat/StreamingMarkdownBalancer.swift | 5 +- .../Views/Common/CodeBlockView.swift | 7 +- .../Views/Common/GlassBackground.swift | 8 +- .../OsaurusCore/Views/Common/NotchView.swift | 3 +- .../Views/Common/NotchWindowController.swift | 9 +- .../Views/Common/SettingsEmptyState.swift | 2 +- .../Views/Common/SharedHeaderComponents.swift | 8 +- .../OsaurusCore/Views/Common/TableView.swift | 4 +- .../Views/Insights/InsightsDetailPane.swift | 2 +- .../Views/Insights/InsightsView.swift | 3 +- .../Views/Management/ManagerHeader.swift | 2 +- .../Views/Memory/MemoryComponents.swift | 2 +- .../OsaurusCore/Views/Memory/MemoryView.swift | 4 +- .../Views/Model/ModelDetailView.swift | 6 +- .../Views/Model/ModelDownloadView.swift | 12 +- .../Model/ModelPickerTableRepresentable.swift | 6 +- .../Views/Model/ModelRowView.swift | 3 +- .../OnboardingConfigureAIView.swift | 6 +- .../Views/Onboarding/OnboardingFields.swift | 2 +- .../OnboardingWalkthroughView.swift | 3 +- .../Views/Plugin/PluginConfigView.swift | 18 +-- .../Views/Plugin/PluginsView.swift | 9 +- .../Views/Plugin/ToolPermissionView.swift | 2 +- .../Views/Plugin/ToolsManagerView.swift | 3 +- .../Views/Schedule/SchedulesView.swift | 6 +- .../Views/Settings/ConfigurationView.swift | 14 +- .../Views/Settings/DirectoryPickerView.swift | 3 +- .../Views/Settings/PermissionsView.swift | 2 +- .../Views/Settings/ProvidersView.swift | 6 +- .../Settings/RemoteProviderEditSheet.swift | 3 +- .../Views/Settings/ServerView.swift | 22 ++-- .../SlashCommandsSettingsSection.swift | 4 +- .../SlashCommand/SlashCommandsView.swift | 2 +- .../Views/Theme/ThemeEditorView.swift | 8 +- .../OsaurusCore/Views/Theme/ThemesView.swift | 3 +- .../Views/Toast/ToastContainerView.swift | 6 +- .../Views/Voice/AudioSettingsTab.swift | 3 +- .../OsaurusCore/Views/Voice/VoiceView.swift | 3 +- .../Views/Watcher/WatchersView.swift | 6 +- .../Views/WhatsNew/WhatsNewView.swift | 3 +- .../CentralRepositoryManager.swift | 14 +- .../OsaurusRepository/MinisignVerifier.swift | 2 +- .../PluginInstallManager.swift | 18 +-- Packages/OsaurusRepository/PluginSpec.swift | 3 +- .../PluginInstallManagerTests.swift | 3 +- Packages/OsaurusRepository/ToolsPaths.swift | 2 +- scripts/codex/local-ci.sh | 87 +++++++++++++ scripts/codex/pr-worktree.sh | 49 +++++++ 184 files changed, 908 insertions(+), 1045 deletions(-) create mode 100755 scripts/codex/local-ci.sh create mode 100755 scripts/codex/pr-worktree.sh diff --git a/.swiftlint.yml b/.swiftlint.yml index e67d11cd4..e5f4daeaa 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -23,6 +23,9 @@ disabled_rules: - identifier_name - type_name - trailing_comma + - multiple_closures_with_trailing_closure + - inclusive_language + - discouraged_optional_boolean opt_in_rules: - empty_count diff --git a/App/osaurusUITests/osaurusUITestsLaunchTests.swift b/App/osaurusUITests/osaurusUITestsLaunchTests.swift index db6461be1..2e2280efd 100644 --- a/App/osaurusUITests/osaurusUITestsLaunchTests.swift +++ b/App/osaurusUITests/osaurusUITestsLaunchTests.swift @@ -9,7 +9,7 @@ import XCTest final class osaurusUITestsLaunchTests: XCTestCase { - override class var runsForEachTargetApplicationUIConfiguration: Bool { + override static var runsForEachTargetApplicationUIConfiguration: Bool { true } diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/MCPCommand.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/MCPCommand.swift index 0d7693aa1..24586e690 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/MCPCommand.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/MCPCommand.swift @@ -52,8 +52,7 @@ public struct MCPCommand: Command { fputs("[MCP] Tools fetched successfully\n", stderr) let tools: [MCP.Tool] if let obj = try JSONSerialization.jsonObject(with: data) as? [String: Any], - let arr = obj["tools"] as? [[String: Any]] - { + let arr = obj["tools"] as? [[String: Any]] { tools = arr.map { item in let name = (item["name"] as? String) ?? "" let description = (item["description"] as? String) ?? "" @@ -103,7 +102,7 @@ public struct MCPCommand: Command { let (data, response) = try await URLSession.shared.data(for: request) guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { - let message = String(decoding: data, as: UTF8.self) + let message = String(bytes: data, encoding: .utf8) ?? "" return .init( content: [ .text("HTTP \(String(describing: (response as? HTTPURLResponse)?.statusCode)): \(message)") diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Pull.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Pull.swift index b2299f2c9..d54d077bb 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Pull.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Pull.swift @@ -150,8 +150,7 @@ public struct PullCommand: Command { let baseDir: URL if let shared = UserDefaults(suiteName: "group.com.osaurus.shared"), let storedPath = shared.string(forKey: "modelsDirectoryPath"), - !storedPath.isEmpty - { + !storedPath.isEmpty { baseDir = URL(fileURLWithPath: storedPath, isDirectory: true) } else { baseDir = FileManager.default.homeDirectoryForCurrentUser @@ -379,8 +378,7 @@ private final class CLIFileDownloader: NSObject, URLSessionDownloadDelegate, @un guard let cont, let dest else { return } if let http = downloadTask.response as? HTTPURLResponse, - !(200 ..< 300).contains(http.statusCode) - { + !(200 ..< 300).contains(http.statusCode) { cont.resume( throwing: URLError( .badServerResponse, diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Run.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Run.swift index 622080635..b263fe14f 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Run.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Run.swift @@ -80,8 +80,7 @@ public struct RunCommand: Command { if line.isEmpty { continue } // Decode NDJSON event and print incremental content if let data = line.data(using: .utf8), - let event = try? decoder.decode(NDJSONEvent.self, from: data) - { + let event = try? decoder.decode(NDJSONEvent.self, from: data) { if let content = event.message?.content, !content.isEmpty { assistantAggregate += content print(content, terminator: "") diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Serve.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Serve.swift index db3dbdfe0..bee03d28a 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Serve.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Serve.swift @@ -12,7 +12,7 @@ public struct ServeCommand: Command { public static func execute(args: [String]) async { // Parse optional --port argument - var desiredPort: Int? = nil + var desiredPort: Int? var expose: Bool = false var assumeYes: Bool = false var i = 0 @@ -39,7 +39,7 @@ public struct ServeCommand: Command { let warning = """ WARNING: Exposing Osaurus to the local network will allow other devices on your LAN to connect to your server. Make sure you trust your network and understand the risks. - Proceed with exposure? [y/N]: + Proceed with exposure? [y/N]: """ fputs(warning, stderr) fflush(stderr) diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Show.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Show.swift index d60f45830..4f3800277 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Show.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Show.swift @@ -139,8 +139,7 @@ public struct ShowCommand: Command { if http.statusCode != 200 { // Try to parse error message if let errorResp = try? JSONDecoder().decode(ErrorResponse.self, from: data), - let message = errorResp.error?.message - { + let message = errorResp.error?.message { fputs("Error: \(message)\n", stderr) } else { fputs("Failed to get model info (status \(http.statusCode))\n", stderr) diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsCreate.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsCreate.swift index 632440b72..f6999393a 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsCreate.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsCreate.swift @@ -396,21 +396,21 @@ public struct ToolsCreate { private var api: osr_plugin_api = { var api = osr_plugin_api() - + api.free_string = { ptr in if let p = ptr { free(UnsafeMutableRawPointer(mutating: p)) } } - + api.`init` = { let ctx = PluginContext() return Unmanaged.passRetained(ctx).toOpaque() } - + api.destroy = { ctxPtr in guard let ctxPtr = ctxPtr else { return } Unmanaged.fromOpaque(ctxPtr).release() } - + api.get_manifest = { ctxPtr in let manifest = \\"\\"\\" { @@ -446,39 +446,39 @@ public struct ToolsCreate { \\"\\"\\" return makeCString(manifest) } - + api.invoke = { ctxPtr, typePtr, idPtr, payloadPtr in guard let ctxPtr = ctxPtr, let typePtr = typePtr, let idPtr = idPtr, let payloadPtr = payloadPtr else { return nil } - + let ctx = Unmanaged.fromOpaque(ctxPtr).takeUnretainedValue() let type = String(cString: typePtr) let id = String(cString: idPtr) let payload = String(cString: payloadPtr) - + if type == "tool" && id == ctx.tool.name { let result = ctx.tool.run(args: payload) return makeCString(result) } - + return makeCString("{\\"error\\": \\"Unknown capability\\"}") } - + api.version = 2 - + api.handle_route = { ctxPtr, requestJsonPtr in guard let requestJsonPtr = requestJsonPtr else { return nil } let requestJson = String(cString: requestJsonPtr) - + struct RouteRequest: Decodable { let route_id: String } guard let data = requestJson.data(using: .utf8), let req = try? JSONDecoder().decode(RouteRequest.self, from: data) else { return makeCString("{\\"status\\":400}") } - + switch req.route_id { case "health": let body: [String: Any] = ["ok": true] @@ -498,11 +498,11 @@ public struct ToolsCreate { return makeCString("{\\"status\\":404}") } } - + api.on_config_changed = { _, _, _ in } - + api.on_task_event = { _, _, _, _ in } - + return api }() @@ -1351,7 +1351,7 @@ public struct ToolsCreate { guard let taskIdPtr, let eventJsonPtr else { return } let taskId = String(cString: taskIdPtr) let eventJson = String(cString: eventJsonPtr) - + switch eventType { case 4: // COMPLETED hostAPI?.pointee.log?(1, makeCString("Task \\(taskId) completed: \\(eventJson)")) @@ -1372,7 +1372,7 @@ public struct ToolsCreate { ) { let task_id = read_c_str(task_id); let event_json = read_c_str(event_json); - + match event_type { 4 => { // COMPLETED if let Some(log) = (*HOST_API).log { @@ -1399,22 +1399,22 @@ public struct ToolsCreate { private struct MyTool { let name = "my_tool" // Must match manifest id let description = "What this tool does" - + struct Args: Decodable { let inputParam: String let optionalParam: String? } - + func run(args: String) -> String { // 1. Parse JSON input guard let data = args.data(using: .utf8), let input = try? JSONDecoder().decode(Args.self, from: data) else { return "{\\"error\\": \\"Invalid arguments\\"}" } - + // 2. Execute tool logic let result = processInput(input.inputParam) - + // 3. Return JSON response return "{\\"result\\": \\"\\(result)\\"}" } @@ -1431,10 +1431,10 @@ public struct ToolsCreate { Ok(v) => v, Err(_) => return r#"{"error": "Invalid arguments"}"#.to_string(), }; - + // 2. Execute tool logic let result = self.process_input(&input); - + // 3. Return JSON response format!(r#"{{"result": "{}"}}"#, result) } @@ -1493,7 +1493,7 @@ public struct ToolsCreate { ```swift api.invoke = { ctxPtr, typePtr, idPtr, payloadPtr in // ... existing code ... - + if type == "tool" { switch id { case ctx.helloTool.name: @@ -1504,7 +1504,7 @@ public struct ToolsCreate { return makeCString("{\\"error\\": \\"Unknown tool\\"}") } } - + return makeCString("{\\"error\\": \\"Unknown capability\\"}") } ``` @@ -1512,7 +1512,7 @@ public struct ToolsCreate { ```rust extern "C" fn invoke(ctx: *mut c_void, type_ptr: *const c_char, id_ptr: *const c_char, payload_ptr: *const c_char) -> *const c_char { // ... existing code ... - + if type_str == "tool" { match id_str { "hello_world" => make_c_string(&ctx.hello_tool.run(payload)), @@ -1558,24 +1558,24 @@ public struct ToolsCreate { ```swift private struct MyAPITool { let name = "call_api" - + struct Args: Decodable { let query: String let _secrets: [String: String]? // Secrets injected by Osaurus } - + func run(args: String) -> String { guard let data = args.data(using: .utf8), let input = try? JSONDecoder().decode(Args.self, from: data) else { return "{\\"error\\": \\"Invalid arguments\\"}" } - + // Get the API key guard let apiKey = input._secrets?["api_key"] else { return "{\\"error\\": \\"API key not configured\\"}" } - + // Use the API key in your request let result = makeAPICall(apiKey: apiKey, query: input.query) return "{\\"result\\": \\"\\(result)\\"}" @@ -1590,18 +1590,18 @@ public struct ToolsCreate { query: String, _secrets: Option>, } - + let input: Args = match serde_json::from_str(args) { Ok(v) => v, Err(_) => return r#"{"error": "Invalid arguments"}"#.to_string(), }; - + // Get the API key let api_key = match input._secrets.as_ref().and_then(|s| s.get("api_key")) { Some(key) => key, None => return r#"{"error": "API key not configured"}"#.to_string(), }; - + // Use the API key let result = self.make_api_call(api_key, &input.query); format!(r#"{{"result": "{}"}}"#, result) @@ -1649,23 +1649,23 @@ public struct ToolsCreate { ```swift private struct MyFileTool { let name = "process_file" - + struct FolderContext: Decodable { let working_directory: String } - + struct Args: Decodable { let path: String let _context: FolderContext? // Folder context injected by Osaurus } - + func run(args: String) -> String { guard let data = args.data(using: .utf8), let input = try? JSONDecoder().decode(Args.self, from: data) else { return "{\\"error\\": \\"Invalid arguments\\"}" } - + // Resolve relative path using working directory let absolutePath: String if let workingDir = input._context?.working_directory { @@ -1674,7 +1674,7 @@ public struct ToolsCreate { // No folder context - assume absolute path or return error absolutePath = input.path } - + // SECURITY: Validate path stays within working directory if let workingDir = input._context?.working_directory { let resolvedPath = URL(fileURLWithPath: absolutePath).standardized.path @@ -1682,7 +1682,7 @@ public struct ToolsCreate { return "{\\"error\\": \\"Path outside working directory\\"}" } } - + // Process the file at absolutePath... return "{\\"success\\": true}" } @@ -1695,24 +1695,24 @@ public struct ToolsCreate { struct FolderContext { working_directory: String, } - + #[derive(Deserialize)] struct Args { path: String, _context: Option, } - + let input: Args = match serde_json::from_str(args) { Ok(v) => v, Err(_) => return r#"{"error": "Invalid arguments"}"#.to_string(), }; - + // Resolve relative path let absolute_path = match &input._context { Some(ctx) => format!("{}/{}", ctx.working_directory, input.path), None => input.path.clone(), }; - + // SECURITY: Validate path stays within working directory if let Some(ctx) = &input._context { let resolved = std::path::Path::new(&absolute_path).canonicalize(); @@ -1722,7 +1722,7 @@ public struct ToolsCreate { } } } - + // Process the file... r#"{"success": true}"#.to_string() } @@ -1798,22 +1798,22 @@ public struct ToolsCreate { guard let input = parseArgs(args) else { return "{\\"error\\": \\"Invalid arguments\\"}" } - + let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/some-cli") process.arguments = [input.flag, input.value] - + let pipe = Pipe() process.standardOutput = pipe process.standardError = pipe - + do { try process.run() process.waitUntilExit() - + let data = pipe.fileHandleForReading.readDataToEndOfFile() let output = String(data: data, encoding: .utf8) ?? "" - + if process.terminationStatus == 0 { return "{\\"output\\": \\"\\(output.escapedForJSON)\\"}" } else { @@ -1831,11 +1831,11 @@ public struct ToolsCreate { Ok(v) => v, Err(_) => return r#"{"error": "Invalid arguments"}"#.to_string(), }; - + let output = std::process::Command::new("/usr/bin/some-cli") .args(&[&input.flag, &input.value]) .output(); - + match output { Ok(out) if out.status.success() => { let stdout = String::from_utf8_lossy(&out.stdout); @@ -1861,11 +1861,11 @@ public struct ToolsCreate { guard let input = parseArgs(args) else { return "{\\"error\\": \\"Invalid arguments\\"}" } - + guard let httpRequest = hostAPI?.pointee.http_request else { return "{\\"error\\": \\"HTTP client not available\\"}" } - + let body = (try? JSONEncoder().encode(input)).flatMap { String(data: $0, encoding: .utf8) } ?? "{}" let request = "{\\"method\\":\\"POST\\",\\"url\\":\\"https://api.example.com/endpoint\\",\\"headers\\":{\\"Content-Type\\":\\"application/json\\"},\\"body\\":\\"\\(body.replacingOccurrences(of: "\\"", with: "\\\\\\""))\\",\\"timeout_ms\\":10000}" let response = httpRequest(makeCString(request)) @@ -1881,13 +1881,13 @@ public struct ToolsCreate { Ok(v) => v, Err(_) => return r#"{"error": "Invalid arguments"}"#.to_string(), }; - + unsafe { let http_request = match (*HOST_API).http_request { Some(f) => f, None => return r#"{"error": "HTTP client not available"}"#.to_string(), }; - + let body = serde_json::to_string(&input).unwrap_or_default(); let req = serde_json::json!({ "method": "POST", diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDev.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDev.swift index ebb1e5188..03aeaadc6 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDev.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDev.swift @@ -14,8 +14,8 @@ import Foundation import OsaurusRepository -private nonisolated(unsafe) var devProxyConfigFile: URL? -private nonisolated(unsafe) var signalSource: DispatchSourceSignal? +nonisolated(unsafe) private var devProxyConfigFile: URL? +nonisolated(unsafe) private var signalSource: DispatchSourceSignal? public struct ToolsDev { @@ -194,7 +194,7 @@ public struct ToolsDev { at: pluginDir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles - ).filter(\.hasDirectoryPath).first + ).first(where: \.hasDirectoryPath) } guard let dir = versionDir else { return nil } @@ -370,8 +370,7 @@ public struct ToolsDev { for case let url as URL in enumerator { if let values = try? url.resourceValues(forKeys: [.contentModificationDateKey]), - let mtime = values.contentModificationDate, mtime > latest - { + let mtime = values.contentModificationDate, mtime > latest { latest = mtime } } @@ -385,8 +384,7 @@ public struct ToolsDev { for name in companions { let url = cwd.appendingPathComponent(name) if let values = try? url.resourceValues(forKeys: [.contentModificationDateKey]), - let mtime = values.contentModificationDate, mtime > latest - { + let mtime = values.contentModificationDate, mtime > latest { latest = mtime } } diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsInstall.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsInstall.swift index b6ed123f2..20cf1c377 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsInstall.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsInstall.swift @@ -25,7 +25,7 @@ public struct ToolsInstall { } private static func installFromRegistry(pluginId: String, args: [String]) async { - var preferredVersion: SemanticVersion? = nil + var preferredVersion: SemanticVersion? if let idx = args.firstIndex(of: "--version"), idx + 1 < args.count { let vstr = args[idx + 1] preferredVersion = SemanticVersion.parse(vstr) @@ -243,10 +243,8 @@ public struct ToolsInstall { else { return nil } - for case let fileURL as URL in enumerator { - if fileURL.pathExtension == "dylib" { - return fileURL - } + for case let fileURL as URL in enumerator where fileURL.pathExtension == "dylib" { + return fileURL } return nil } diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsList.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsList.swift index 5acbbddad..53667c2cf 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsList.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsList.swift @@ -41,8 +41,7 @@ public struct ToolsList { .appendingPathComponent("receipt.json") if let data = try? Data(contentsOf: receiptURL), - let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { + let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { let pluginId = (obj["plugin_id"] as? String) ?? entry let version = (obj["version"] as? String) ?? versionName print("\(pluginId) version=\(version)") diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsOutdated.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsOutdated.swift index 7f47255ae..3eac5b1ec 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsOutdated.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsOutdated.swift @@ -22,8 +22,7 @@ public struct ToolsOutdated { let pluginId = pluginDir.lastPathComponent let installed = InstalledPluginsStore.shared.latestInstalledVersion(pluginId: pluginId) guard - let available = specs.first(where: { $0.plugin_id == pluginId })?.versions.map(\.version).sorted(by: >) - .first + let available = specs.first(where: { $0.plugin_id == pluginId })?.versions.map(\.version).max() else { continue } diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsPackage.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsPackage.swift index 6e1f23161..72739022a 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsPackage.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsPackage.swift @@ -22,17 +22,14 @@ public struct ToolsPackage { let fm = FileManager.default var entries: [String] = [] - for file in companionFiles { - if fm.fileExists(atPath: directory.appendingPathComponent(file).path) { - entries.append(file) - } + for file in companionFiles where fm.fileExists(atPath: directory.appendingPathComponent(file).path) { + entries.append(file) } for dirName in companionDirs { var isDir: ObjCBool = false if fm.fileExists(atPath: directory.appendingPathComponent(dirName).path, isDirectory: &isDir), - isDir.boolValue - { + isDir.boolValue { entries.append(dirName) } } diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsSearch.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsSearch.swift index fcd1ae4e8..20637a293 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsSearch.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsSearch.swift @@ -22,7 +22,7 @@ public struct ToolsSearch { print("(no matches)") } else { for spec in filtered.sorted(by: { $0.plugin_id < $1.plugin_id }) { - let latest = spec.versions.map(\.version).sorted(by: >).first?.description ?? "-" + let latest = spec.versions.map(\.version).max()?.description ?? "-" print("\(spec.plugin_id)\tlatest: \(latest)\t\(spec.name ?? "")") } } diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUninstall.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUninstall.swift index 5f1adbc12..0c645d4e6 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUninstall.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUninstall.swift @@ -44,8 +44,7 @@ public struct ToolsUninstall { .appendingPathComponent("receipt.json") if let data = try? Data(contentsOf: receiptURL), - let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { + let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { let pluginId = (obj["plugin_id"] as? String) ?? "" if pluginId == target { dirToRemove = pluginDir diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUpgrade.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUpgrade.swift index 8e2433bd9..1d181841b 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUpgrade.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsUpgrade.swift @@ -22,7 +22,7 @@ public struct ToolsUpgrade { var failures = 0 for pid in pluginIds { guard let spec = specs.first(where: { $0.plugin_id == pid }) else { continue } - let latest = spec.versions.map(\.version).sorted(by: >).first + let latest = spec.versions.map(\.version).max() let installed = InstalledPluginsStore.shared.latestInstalledVersion(pluginId: pid) if let latest, installed == nil || latest > installed! { do { diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Services/AppControl.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Services/AppControl.swift index ec814481e..44c944a7c 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Services/AppControl.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Services/AppControl.swift @@ -68,8 +68,8 @@ public struct AppControl { "/Applications/osaurus.app", "\(home)/Applications/osaurus.app", ] - for c in candidates { - if fm.fileExists(atPath: c) { return c } + for c in candidates where fm.fileExists(atPath: c) { + return c } // Try Spotlight via mdfind, restricted to Applications folders first if let path = spotlightFind(queryArgs: [ diff --git a/Packages/OsaurusCore/Folder/FolderContextService.swift b/Packages/OsaurusCore/Folder/FolderContextService.swift index a2450433e..fb0ca3566 100644 --- a/Packages/OsaurusCore/Folder/FolderContextService.swift +++ b/Packages/OsaurusCore/Folder/FolderContextService.swift @@ -13,7 +13,7 @@ import Foundation // Lives outside the @MainActor class so the lock and storage are never actor-isolated. // Concurrency safety is enforced manually via _folderRootPathLock. private let _folderRootPathLock = NSLock() -private nonisolated(unsafe) var _folderCachedRootPath: URL? +nonisolated(unsafe) private var _folderCachedRootPath: URL? /// Service for managing work folder context @MainActor @@ -31,7 +31,7 @@ public final class FolderContextService: ObservableObject { /// Thread-safe accessor for the current folder root path. /// Reads a lock-protected cache so callers never need to hop to MainActor. - public nonisolated static var cachedRootPath: URL? { + nonisolated public static var cachedRootPath: URL? { _folderRootPathLock.withLock { _folderCachedRootPath } } diff --git a/Packages/OsaurusCore/Folder/FolderTools.swift b/Packages/OsaurusCore/Folder/FolderTools.swift index 2daf981fa..19b44897d 100644 --- a/Packages/OsaurusCore/Folder/FolderTools.swift +++ b/Packages/OsaurusCore/Folder/FolderTools.swift @@ -83,10 +83,9 @@ enum FolderToolHelpers { static func detectProjectType(_ url: URL) -> ProjectType { let fm = FileManager.default for projectType in ProjectType.allCases where projectType != .unknown { - for manifestFile in projectType.manifestFiles { - if fm.fileExists(atPath: url.appendingPathComponent(manifestFile).path) { - return projectType - } + for manifestFile in projectType.manifestFiles + where fm.fileExists(atPath: url.appendingPathComponent(manifestFile).path) { + return projectType } } return .unknown @@ -694,8 +693,7 @@ struct FileSearchTool: OsaurusTool { let regex = pattern.replacingOccurrences(of: ".", with: "\\.") .replacingOccurrences(of: "*", with: ".*") if fileURL.lastPathComponent.range(of: "^\(regex)$", options: .regularExpression) - == nil - { + == nil { continue } } diff --git a/Packages/OsaurusCore/Managers/BackgroundTaskManager.swift b/Packages/OsaurusCore/Managers/BackgroundTaskManager.swift index 3f929f4f0..71682cb09 100644 --- a/Packages/OsaurusCore/Managers/BackgroundTaskManager.swift +++ b/Packages/OsaurusCore/Managers/BackgroundTaskManager.swift @@ -424,8 +424,7 @@ public final class BackgroundTaskManager: ObservableObject { } if let loaded = PluginManager.shared.loadedPlugin(for: pluginId), - loaded.plugin.hasTaskEventHandler - { + loaded.plugin.hasTaskEventHandler { loaded.plugin.notifyTaskEvent( taskId: state.id.uuidString, eventType: type, @@ -449,8 +448,7 @@ public final class BackgroundTaskManager: ObservableObject { func releaseEventsForDispatch(taskId: UUID) { dispatchHoldTasks.remove(taskId) if let events = heldTaskEvents.removeValue(forKey: taskId), - let state = backgroundTasks[taskId] - { + let state = backgroundTasks[taskId] { for event in events { emitPluginEvent(state, type: event.type, json: event.json) } diff --git a/Packages/OsaurusCore/Managers/BlockMemoizer.swift b/Packages/OsaurusCore/Managers/BlockMemoizer.swift index 6e0af5452..40aa7b1fa 100644 --- a/Packages/OsaurusCore/Managers/BlockMemoizer.swift +++ b/Packages/OsaurusCore/Managers/BlockMemoizer.swift @@ -51,8 +51,7 @@ final class BlockMemoizer { && pendingToolName == lastPendingToolName && pendingToolArgSize == lastPendingToolArgSize && version == lastVersion && !cached.isEmpty - && streamingTurnId == lastStreamingTurnId - { + && streamingTurnId == lastStreamingTurnId { return limited(streaming: streamingTurnId != nil) } diff --git a/Packages/OsaurusCore/Managers/Chat/ChatWindowManager.swift b/Packages/OsaurusCore/Managers/Chat/ChatWindowManager.swift index 3691044ad..4e10f7066 100644 --- a/Packages/OsaurusCore/Managers/Chat/ChatWindowManager.swift +++ b/Packages/OsaurusCore/Managers/Chat/ChatWindowManager.swift @@ -45,7 +45,7 @@ public final class ChatWindowManager: NSObject, ObservableObject { private var windowStates: [UUID: ChatWindowState] = [:] private var sessionCallbacks: [UUID: () -> Void] = [:] - private override init() { + override private init() { super.init() } diff --git a/Packages/OsaurusCore/Managers/Chat/ChatWindowState.swift b/Packages/OsaurusCore/Managers/Chat/ChatWindowState.swift index df3a0f486..99eef0195 100644 --- a/Packages/OsaurusCore/Managers/Chat/ChatWindowState.swift +++ b/Packages/OsaurusCore/Managers/Chat/ChatWindowState.swift @@ -48,7 +48,7 @@ final class ChatWindowState: ObservableObject { // MARK: - Private - private nonisolated(unsafe) var notificationObservers: [NSObjectProtocol] = [] + nonisolated(unsafe) private var notificationObservers: [NSObjectProtocol] = [] private var sessionRefreshWorkItem: DispatchWorkItem? private var bonjourCancellable: AnyCancellable? private var agentsCancellable: AnyCancellable? @@ -267,8 +267,7 @@ final class ChatWindowState: ObservableObject { .sink { [weak self] agents in self?.discoveredAgents = agents if let selected = self?.selectedDiscoveredAgent, - !agents.contains(where: { $0.id == selected.id }) - { + !agents.contains(where: { $0.id == selected.id }) { self?.removeEphemeralProviderIfNeeded() self?.selectedDiscoveredAgent = nil self?.selectedDiscoveredAgentProviderId = nil @@ -377,8 +376,7 @@ final class ChatWindowState: ObservableObject { private static func loadTheme(for agentId: UUID) -> ThemeProtocol { if let themeId = AgentManager.shared.themeId(for: agentId), - let custom = ThemeManager.shared.installedThemes.first(where: { $0.metadata.id == themeId }) - { + let custom = ThemeManager.shared.installedThemes.first(where: { $0.metadata.id == themeId }) { return CustomizableTheme(config: custom) } return ThemeManager.shared.currentTheme diff --git a/Packages/OsaurusCore/Managers/InferenceProgressManager.swift b/Packages/OsaurusCore/Managers/InferenceProgressManager.swift index 55b855868..06a77334d 100644 --- a/Packages/OsaurusCore/Managers/InferenceProgressManager.swift +++ b/Packages/OsaurusCore/Managers/InferenceProgressManager.swift @@ -42,10 +42,10 @@ final class InferenceProgressManager: ObservableObject, @unchecked Sendable { /// Non-nil while a prefill is in progress. Set to the prompt token count /// just before `prepareAndGenerate` is called; cleared as soon as the first /// generated token arrives (or on error / cancellation). - @MainActor @Published var prefillTokenCount: Int? = nil + @MainActor @Published var prefillTokenCount: Int? /// Wall-clock time when the current prefill started. - @MainActor @Published var prefillStartedAt: Date? = nil + @MainActor @Published var prefillStartedAt: Date? init() {} diff --git a/Packages/OsaurusCore/Managers/InsightsService.swift b/Packages/OsaurusCore/Managers/InsightsService.swift index 90b056f34..94001d6f9 100644 --- a/Packages/OsaurusCore/Managers/InsightsService.swift +++ b/Packages/OsaurusCore/Managers/InsightsService.swift @@ -210,7 +210,7 @@ extension InsightsService { /// tool definitions, multi-turn history) without truncation in the /// common case while still bounding the 500-entry ring buffer to a few /// hundred MB worst-case. - private nonisolated static let maxBodySize = 262_144 + nonisolated private static let maxBodySize = 262_144 /// Defense-in-depth credential redactors run on every logged body so a /// future caller that forgets to scrub a `/pair` response (or any other @@ -218,7 +218,7 @@ extension InsightsService { /// the request log ring buffer. The regexes target the credential value /// itself and replace it with a marker — surrounding structure (JSON keys /// or header names) is preserved. - private nonisolated static let bearerTokenRegex: NSRegularExpression? = { + nonisolated private static let bearerTokenRegex: NSRegularExpression? = { // Match the token after a `Bearer` scheme (header or stringified header). try? NSRegularExpression( pattern: #"(?i)(bearer\s+)osk-[A-Za-z0-9._-]+"#, @@ -226,7 +226,7 @@ extension InsightsService { ) }() - private nonisolated static let oskValueRegex: NSRegularExpression? = { + nonisolated private static let oskValueRegex: NSRegularExpression? = { // Match osk-v1.. when it appears as a JSON string value. try? NSRegularExpression( pattern: #""osk-[A-Za-z0-9._-]+""#, @@ -258,7 +258,7 @@ extension InsightsService { return redacted } - private nonisolated static func truncateBody(_ body: String?) -> String? { + nonisolated private static func truncateBody(_ body: String?) -> String? { guard let body else { return nil } let scrubbed = redactCredentials(body) guard scrubbed.count > maxBodySize else { return scrubbed } diff --git a/Packages/OsaurusCore/Managers/MCPProviderManager.swift b/Packages/OsaurusCore/Managers/MCPProviderManager.swift index 1cdc7bd59..7f3e0cd26 100644 --- a/Packages/OsaurusCore/Managers/MCPProviderManager.swift +++ b/Packages/OsaurusCore/Managers/MCPProviderManager.swift @@ -297,7 +297,7 @@ public final class MCPProviderManager: ObservableObject { } /// Trampoline that runs the MCP network call outside MainActor isolation. - private nonisolated static func callMCPTool( + nonisolated private static func callMCPTool( client: MCP.Client, toolName: String, arguments: [String: MCP.Value], @@ -422,8 +422,7 @@ public final class MCPProviderManager: ObservableObject { } private func withTimeout(seconds: TimeInterval, operation: @escaping @Sendable () async throws -> T) - async throws -> T - { + async throws -> T { try await withThrowingTaskGroup(of: T.self) { group in group.addTask { try await operation() diff --git a/Packages/OsaurusCore/Managers/Model/ModelManager.swift b/Packages/OsaurusCore/Managers/Model/ModelManager.swift index 8fa17e827..800b43c5c 100644 --- a/Packages/OsaurusCore/Managers/Model/ModelManager.swift +++ b/Packages/OsaurusCore/Managers/Model/ModelManager.swift @@ -49,10 +49,10 @@ final class ModelManager: NSObject, ObservableObject { } var typeFilter: ModelTypeFilter = .all - var sizeCategory: SizeCategory? = nil - var family: String? = nil - var paramCategory: ParamCategory? = nil - var performance: PerformanceFilter? = nil + var sizeCategory: SizeCategory? + var family: String? + var paramCategory: ParamCategory? + var performance: PerformanceFilter? enum SizeCategory: String, CaseIterable, Identifiable { case small = "Small (<2 GB)" @@ -209,7 +209,7 @@ final class ModelManager: NSObject, ObservableObject { } private var cancellables = Set() - private var remoteSearchTask: Task? = nil + private var remoteSearchTask: Task? /// Test-only knob: when `true`, the constructor does NOT kick off the /// background OsaurusAI HF org fetch. Production code never sets this; @@ -306,8 +306,7 @@ final class ModelManager: NSObject, ObservableObject { // If user pasted a direct HF URL or "org/repo", immediately surface it without requiring SDK allowlist if let directId = Self.parseHuggingFaceRepoId(from: query), !directId.isEmpty, - !findExistingModel(id: directId).found - { + !findExistingModel(id: directId).found { let probe = MLXModel(id: directId, name: "", description: "", downloadURL: "") let model = MLXModel( id: directId, @@ -1119,8 +1118,8 @@ extension ModelManager { /// (auto-fetched LLM entries fall back to post-download detection). fileprivate static func inferModelType(from tags: [String]?) -> String? { guard let tags else { return nil } - for tag in tags { - if VLMDetection.isVLM(modelType: tag) { return tag } + for tag in tags where VLMDetection.isVLM(modelType: tag) { + return tag } return nil } @@ -1337,8 +1336,8 @@ extension ModelManager { } // MARK: - Local Models Cache (in-memory, cleared on app restart) - private static nonisolated let localModelsCacheLock = NSLock() - private static nonisolated(unsafe) var cachedLocalModels: [MLXModel]? + nonisolated private static let localModelsCacheLock = NSLock() + nonisolated(unsafe) private static var cachedLocalModels: [MLXModel]? nonisolated static func invalidateLocalModelsCache() { localModelsCacheLock.lock() @@ -1365,14 +1364,14 @@ extension ModelManager { return models } - private nonisolated static func scanLocalModels() -> [MLXModel] { + nonisolated private static func scanLocalModels() -> [MLXModel] { return scanLocalModels(at: DirectoryPickerService.effectiveModelsDirectory()) } /// Internal entry point used by tests so they can supply a fixture root. /// Detects both the flat (`//`) and nested (`///`) /// layouts. - internal nonisolated static func scanLocalModels(at root: URL) -> [MLXModel] { + nonisolated internal static func scanLocalModels(at root: URL) -> [MLXModel] { let fm = FileManager.default guard let topEntries = try? fm.contentsOfDirectory( diff --git a/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift b/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift index 7f36d472e..631321e8b 100644 --- a/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift +++ b/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift @@ -223,16 +223,14 @@ final class PluginManager { for section in configSpec.sections { for field in section.fields { if values[field.key] == nil, field.type != .readonly, field.type != .status, - let val = ToolSecretsKeychain.getSecret(id: field.key, for: pluginId, agentId: agentId) - { + let val = ToolSecretsKeychain.getSecret(id: field.key, for: pluginId, agentId: agentId) { values[field.key] = val } if values[field.key] == nil, let def = field.default { values[field.key] = def.stringValue } if let connKey = field.connected_when, values[connKey] == nil, - let val = ToolSecretsKeychain.getSecret(id: connKey, for: pluginId, agentId: agentId) - { + let val = ToolSecretsKeychain.getSecret(id: connKey, for: pluginId, agentId: agentId) { values[connKey] = val } } @@ -268,11 +266,10 @@ final class PluginManager { let destinations = agents.map { $0.id } for agentId in destinations { - for (key, value) in legacySecrets { + for (key, value) in legacySecrets + where ToolSecretsKeychain.getSecret(id: key, for: pluginId, agentId: agentId) == nil { // Only copy if the agent doesn't already have a value for this key. - if ToolSecretsKeychain.getSecret(id: key, for: pluginId, agentId: agentId) == nil { - ToolSecretsKeychain.saveSecret(value, id: key, for: pluginId, agentId: agentId) - } + ToolSecretsKeychain.saveSecret(value, id: key, for: pluginId, agentId: agentId) } } @@ -742,8 +739,7 @@ final class PluginManager { guard url.hasDirectoryPath, let v = SemanticVersion.parse(url.lastPathComponent) else { return nil } return (v, url) } - .sorted { $0.0 > $1.0 } - .first?.1 + .max { $0.0 < $1.0 }?.1 } nonisolated static func toolsRootDirectory() -> URL { @@ -764,11 +760,11 @@ final class PluginManager { // MARK: - Plugin Quarantine - private nonisolated static func currentlyLoadingURL() -> URL { + nonisolated private static func currentlyLoadingURL() -> URL { toolsRootDirectory().appendingPathComponent(".currently_loading", isDirectory: false) } - private nonisolated static func quarantineURL() -> URL { + nonisolated private static func quarantineURL() -> URL { toolsRootDirectory().appendingPathComponent(".quarantine", isDirectory: false) } @@ -779,7 +775,7 @@ final class PluginManager { return Set(ids) } - private nonisolated static func addToQuarantine(_ pluginId: String) { + nonisolated private static func addToQuarantine(_ pluginId: String) { var ids = quarantinedPluginIds() ids.insert(pluginId) if let data = try? JSONEncoder().encode(Array(ids)) { @@ -795,7 +791,7 @@ final class PluginManager { /// If a `.currently_loading` marker was left behind by a crash during /// dlopen/init, quarantine that plugin so it is skipped on future launches. - private nonisolated static func promoteStaleLoadingMarker() { + nonisolated private static func promoteStaleLoadingMarker() { let markerURL = currentlyLoadingURL() guard let data = try? Data(contentsOf: markerURL), let pluginId = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines), @@ -805,11 +801,11 @@ final class PluginManager { try? FileManager.default.removeItem(at: markerURL) } - private nonisolated static func writeLoadingMarker(pluginId: String) { + nonisolated private static func writeLoadingMarker(pluginId: String) { try? pluginId.data(using: .utf8)?.write(to: currentlyLoadingURL()) } - private nonisolated static func clearLoadingMarker() { + nonisolated private static func clearLoadingMarker() { try? FileManager.default.removeItem(at: currentlyLoadingURL()) } @@ -857,7 +853,7 @@ final class PluginManager { guard let v = SemanticVersion.parse(url.lastPathComponent) else { return nil } return (v, url) } - versionDir = versions.sorted(by: { $0.0 > $1.0 }).first?.1 + versionDir = versions.max(by: { $0.0 < $1.0 })?.1 } } @@ -873,16 +869,14 @@ final class PluginManager { includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles] ) { - for case let fileURL as URL in enumerator { - if fileURL.pathExtension == "dylib" { - foundDylib = true - let verifyResult = verifyDylibBeforeLoadWithError(fileURL) - switch verifyResult { - case .success: - dylibURLs.append(fileURL) - case .failure(let error): - failures[pluginId] = error.message - } + for case let fileURL as URL in enumerator where fileURL.pathExtension == "dylib" { + foundDylib = true + let verifyResult = verifyDylibBeforeLoadWithError(fileURL) + switch verifyResult { + case .success: + dylibURLs.append(fileURL) + case .failure(let error): + failures[pluginId] = error.message } } } diff --git a/Packages/OsaurusCore/Managers/Plugin/SandboxPluginLibrary.swift b/Packages/OsaurusCore/Managers/Plugin/SandboxPluginLibrary.swift index 622f790e5..2a4409581 100644 --- a/Packages/OsaurusCore/Managers/Plugin/SandboxPluginLibrary.swift +++ b/Packages/OsaurusCore/Managers/Plugin/SandboxPluginLibrary.swift @@ -105,8 +105,7 @@ public final class SandboxPluginLibrary: ObservableObject { var modifiedAt: Date? if let data = try? Data(contentsOf: file), - let p = try? decoder.decode(SandboxPlugin.self, from: data) - { + let p = try? decoder.decode(SandboxPlugin.self, from: data) { modifiedAt = p.modifiedAt } entries.append(PluginVersionEntry(version: versionNum, modifiedAt: modifiedAt)) @@ -136,8 +135,7 @@ public final class SandboxPluginLibrary: ObservableObject { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 if let restored = try? decoder.decode(SandboxPlugin.self, from: data), - let index = plugins.firstIndex(where: { $0.id == id }) - { + let index = plugins.firstIndex(where: { $0.id == id }) { plugins[index] = restored } } @@ -228,8 +226,7 @@ public final class SandboxPluginLibrary: ObservableObject { existing.version = "1" if existing.modifiedAt == nil, let attrs = try? fm.attributesOfItem(atPath: file.path), - let modDate = attrs[.modificationDate] as? Date - { + let modDate = attrs[.modificationDate] as? Date { existing.modifiedAt = modDate } diff --git a/Packages/OsaurusCore/Managers/Plugin/SandboxPluginManager.swift b/Packages/OsaurusCore/Managers/Plugin/SandboxPluginManager.swift index 38c54f280..5ab84af8d 100644 --- a/Packages/OsaurusCore/Managers/Plugin/SandboxPluginManager.swift +++ b/Packages/OsaurusCore/Managers/Plugin/SandboxPluginManager.swift @@ -305,8 +305,7 @@ public final class SandboxPluginManager: ObservableObject { let existing = self.plugin(id: pluginId, for: agentId) if existing?.status == .ready, - hostFilesIntact(plugin: plugin, agentName: agentName) - { + hostFilesIntact(plugin: plugin, agentName: agentName) { return true } diff --git a/Packages/OsaurusCore/Managers/PluginActivityManager.swift b/Packages/OsaurusCore/Managers/PluginActivityManager.swift index 20ec9a9da..5fbb0c36e 100644 --- a/Packages/OsaurusCore/Managers/PluginActivityManager.swift +++ b/Packages/OsaurusCore/Managers/PluginActivityManager.swift @@ -79,6 +79,6 @@ public final class PluginActivityManager: ObservableObject { /// Most-recently-started activity (for the compact notch indicator). public var topActivity: PluginActivityRecord? { - active.values.sorted { $0.startedAt > $1.startedAt }.first + active.values.max { $0.startedAt < $1.startedAt } } } diff --git a/Packages/OsaurusCore/Managers/ScheduleManager.swift b/Packages/OsaurusCore/Managers/ScheduleManager.swift index 3d7ac7dc2..05905c3a4 100644 --- a/Packages/OsaurusCore/Managers/ScheduleManager.swift +++ b/Packages/OsaurusCore/Managers/ScheduleManager.swift @@ -33,14 +33,14 @@ public final class ScheduleManager { /// The task that waits for the next scheduled execution @ObservationIgnored - private nonisolated(unsafe) var timerTask: Task? + nonisolated(unsafe) private var timerTask: Task? /// Active execution tasks private var executionTasks: [UUID: Task] = [:] /// Observer for timezone changes @ObservationIgnored - private nonisolated(unsafe) var timezoneObserver: NSObjectProtocol? + nonisolated(unsafe) private var timezoneObserver: NSObjectProtocol? // MARK: - Initialization @@ -301,8 +301,7 @@ public final class ScheduleManager { // Only run if lastRunAt is nil or the next run after lastRunAt is in the past if let lastRun = schedule.lastRunAt { if let nextAfterLast = schedule.frequency.nextRunDate(after: lastRun), - nextAfterLast <= now - { + nextAfterLast <= now { print("[Osaurus] Found missed recurring schedule: \(schedule.name)") executeSchedule(schedule) } diff --git a/Packages/OsaurusCore/Managers/SpeechService.swift b/Packages/OsaurusCore/Managers/SpeechService.swift index 2d2bd6c85..01d382710 100644 --- a/Packages/OsaurusCore/Managers/SpeechService.swift +++ b/Packages/OsaurusCore/Managers/SpeechService.swift @@ -153,8 +153,7 @@ public final class AudioInputManager: ObservableObject { } if let selectedId = selectedDeviceId, - !availableDevices.contains(where: { $0.id == selectedId }) - { + !availableDevices.contains(where: { $0.id == selectedId }) { selectedDeviceId = nil } } @@ -351,7 +350,7 @@ public final class SystemAudioCaptureManager: NSObject, ObservableObject { private var stream: SCStream? private var streamOutput: SystemAudioStreamOutput? - private override init() { + override private init() { super.init() } @@ -449,11 +448,11 @@ public final class SystemAudioCaptureManager: NSObject, ObservableObject { print("[SystemAudioCaptureManager] Stopped capturing system audio") } - public nonisolated func getAndClearSamples() -> [Float] { + nonisolated public func getAndClearSamples() -> [Float] { sampleBuffer.getAndClear() } - private nonisolated func appendSamples(_ samples: [Float]) { + nonisolated private func appendSamples(_ samples: [Float]) { sampleBuffer.append(samples) } } @@ -461,7 +460,7 @@ public final class SystemAudioCaptureManager: NSObject, ObservableObject { // MARK: - SCStreamDelegate extension SystemAudioCaptureManager: SCStreamDelegate { - public nonisolated func stream(_ stream: SCStream, didStopWithError error: Error) { + nonisolated public func stream(_ stream: SCStream, didStopWithError error: Error) { print("[SystemAudioCaptureManager] Stream stopped with error: \(error)") Task { @MainActor in self.isCapturing = false @@ -564,7 +563,7 @@ public final class SpeechService: ObservableObject { // MARK: - Private Properties private var sendableAsrManager: SendableAsrManager? - private nonisolated(unsafe) var vadManager: VadManager? + nonisolated(unsafe) private var vadManager: VadManager? private var activeInputDeviceId: String? private var activeInputSource: AudioInputSource? @@ -797,8 +796,7 @@ public final class SpeechService: ObservableObject { if let engine = audioEngine, engine.isRunning, inputSource == activeInputSource, selectedId == activeInputDeviceId, - activeTapFormat != nil - { + activeTapFormat != nil { print("[SpeechService] Reusing active audio engine for handoff") reuseEngine = true } else { @@ -842,7 +840,7 @@ public final class SpeechService: ObservableObject { print("[SpeechService] - Sensitivity: \(config.sensitivity)") if inputSource == .microphone { - var targetDeviceId: AudioDeviceID? = nil + var targetDeviceId: AudioDeviceID? if let selectedId { targetDeviceId = AudioInputManager.shared.getAudioDeviceId(for: selectedId) if targetDeviceId == nil { @@ -1025,7 +1023,7 @@ public final class SpeechService: ObservableObject { let bufferRef = audioBuffer systemAudioPollingTask = Task { @MainActor [weak self] in print("[SpeechService] Started system audio polling") - while let _ = self, bufferRef.isActive { + while self != nil, bufferRef.isActive { let samples = SystemAudioCaptureManager.shared.getAndClearSamples() if !samples.isEmpty { bufferRef.append(samples) @@ -1246,7 +1244,7 @@ public final class SpeechService: ObservableObject { // MARK: - Audio Device Helpers - private nonisolated static func setInputDevice(_ deviceId: AudioDeviceID, for inputNode: AVAudioInputNode) { + nonisolated private static func setInputDevice(_ deviceId: AudioDeviceID, for inputNode: AVAudioInputNode) { _ = inputNode.inputFormat(forBus: 0) guard let audioUnit = inputNode.audioUnit else { diff --git a/Packages/OsaurusCore/Managers/WatcherManager.swift b/Packages/OsaurusCore/Managers/WatcherManager.swift index d5b93cf28..e3e624f48 100644 --- a/Packages/OsaurusCore/Managers/WatcherManager.swift +++ b/Packages/OsaurusCore/Managers/WatcherManager.swift @@ -43,7 +43,7 @@ public final class WatcherManager { /// FSEvent stream reference (nonisolated so deinit can clean it up) @ObservationIgnored - private nonisolated(unsafe) var eventStream: FSEventStreamRef? + nonisolated(unsafe) private var eventStream: FSEventStreamRef? /// Per-watcher debounce tasks private var debouncers: [UUID: Task] = [:] diff --git a/Packages/OsaurusCore/Managers/WindowManager.swift b/Packages/OsaurusCore/Managers/WindowManager.swift index 70939ebe5..10205c979 100644 --- a/Packages/OsaurusCore/Managers/WindowManager.swift +++ b/Packages/OsaurusCore/Managers/WindowManager.swift @@ -82,7 +82,7 @@ public final class WindowManager: NSObject, ObservableObject { private var windows: [WindowIdentifier: NSWindow] = [:] private var windowDelegates: [WindowIdentifier: WindowManagerDelegate] = [:] - private override init() { + override private init() { super.init() loadPinnedState() } diff --git a/Packages/OsaurusCore/Models/API/AnthropicAPI.swift b/Packages/OsaurusCore/Models/API/AnthropicAPI.swift index 013183e42..9a65d8b1f 100644 --- a/Packages/OsaurusCore/Models/API/AnthropicAPI.swift +++ b/Packages/OsaurusCore/Models/API/AnthropicAPI.swift @@ -423,8 +423,7 @@ enum AnthropicResponseContentBlock: Codable, Sendable { } static func toolUseBlock(id: String, name: String, input: [String: AnyCodableValue]) - -> AnthropicResponseContentBlock - { + -> AnthropicResponseContentBlock { .toolUse(type: "tool_use", id: id, name: name, input: input) } } @@ -892,7 +891,7 @@ extension AnthropicMessagesRequest { } // Convert tools - var openAITools: [Tool]? = nil + var openAITools: [Tool]? if let tools = tools { openAITools = tools.map { tool in Tool( @@ -907,7 +906,7 @@ extension AnthropicMessagesRequest { } // Convert tool_choice - var openAIToolChoice: ToolChoiceOption? = nil + var openAIToolChoice: ToolChoiceOption? if let choice = tool_choice { switch choice { case .auto: diff --git a/Packages/OsaurusCore/Models/API/OpenAIAPI.swift b/Packages/OsaurusCore/Models/API/OpenAIAPI.swift index e7cd1c59d..e5e7de480 100644 --- a/Packages/OsaurusCore/Models/API/OpenAIAPI.swift +++ b/Packages/OsaurusCore/Models/API/OpenAIAPI.swift @@ -15,15 +15,15 @@ struct OpenAIModel: Codable, Sendable { var object: String = "model" var created: Int = 0 var owned_by: String = "osaurus" - var permission: [ModelPermission]? = nil - var root: String? = nil - var parent: String? = nil - var name: String? = nil - var model: String? = nil - var modified_at: String? = nil - var size: Int? = nil - var digest: String? = nil - var details: ModelDetails? = nil + var permission: [ModelPermission]? + var root: String? + var parent: String? + var name: String? + var model: String? + var modified_at: String? + var size: Int? + var digest: String? + var details: ModelDetails? /// Initialize from a model name (for local models) init(modelName: String) { @@ -354,8 +354,7 @@ extension ChatMessage { case .imageUrl, .audioInput, .videoUrl: return true case .text: return false } - }) - { + }) { try container.encode(parts, forKey: .content) } else if let content = content { // Only encode content if it's not nil (OpenAI rejects null content) @@ -482,7 +481,7 @@ struct ChatCompletionRequest: Codable, Sendable { let temperature: Float? let max_tokens: Int? /// OpenAI newer alias for max_tokens; accepted on inbound requests alongside max_tokens. - var max_completion_tokens: Int? = nil + var max_completion_tokens: Int? let stream: Bool? let top_p: Float? let frequency_penalty: Float? @@ -496,23 +495,23 @@ struct ChatCompletionRequest: Codable, Sendable { /// Optional session identifier for chat/history grouping. Not a KV cache key — /// vmlx-swift-lm's `CacheCoordinator` is content-addressed and discovers /// reusable prefixes autonomously. - var session_id: String? = nil + var session_id: String? /// Deterministic-sampling seed (OpenAI v1.x). When set, identical /// requests should yield identical completions on the same backend. - var seed: Int? = nil + var seed: Int? /// `{"type":"json_object"}` for OpenAI JSON mode. Other shapes /// (`text`, `json_schema`) are rejected at request validation. - var response_format: ResponseFormat? = nil + var response_format: ResponseFormat? /// `{"include_usage": true}` instructs the SSE producer to emit a /// final chunk carrying `usage` (prompt/completion/total tokens). - var stream_options: StreamOptions? = nil + var stream_options: StreamOptions? /// Model-specific options from the active ModelProfile (not serialized to JSON). - var modelOptions: [String: ModelOptionValue]? = nil + var modelOptions: [String: ModelOptionValue]? /// Optional TTFT trace for diagnostic timing (not serialized to JSON). - var ttftTrace: TTFTTrace? = nil + var ttftTrace: TTFTTrace? /// Per-request thinking toggle. Translated to `modelOptions["disableThinking"]` /// at request entry; absent preserves server defaults. - var enable_thinking: Bool? = nil + var enable_thinking: Bool? /// Resolved max tokens, preferring max_tokens then max_completion_tokens. var resolvedMaxTokens: Int? { max_tokens ?? max_completion_tokens } @@ -583,12 +582,12 @@ struct ChatCompletionResponse: Codable, Sendable { let model: String let choices: [ChatChoice] let usage: Usage - var system_fingerprint: String? = nil + var system_fingerprint: String? /// Content hash of the system prompt + tool names used for this request. /// Informational only — clients can use it to detect when the system /// prefix changed across requests. KV reuse itself is handled /// autonomously by vmlx's `CacheCoordinator` (content-addressed). - var prefix_hash: String? = nil + var prefix_hash: String? } // MARK: - Streaming Response Structures @@ -635,12 +634,12 @@ struct ChatCompletionChunk: Codable, Sendable { let created: Int let model: String let choices: [StreamChoice] - var system_fingerprint: String? = nil + var system_fingerprint: String? /// Included only in the first chunk; see `ChatCompletionResponse.prefix_hash`. - var prefix_hash: String? = nil + var prefix_hash: String? /// Final usage chunk (OpenAI `stream_options.include_usage`). Populated /// only on the dedicated penultimate SSE chunk; nil on every other. - var usage: Usage? = nil + var usage: Usage? } // MARK: - Error Response diff --git a/Packages/OsaurusCore/Models/API/OpenResponsesAPI.swift b/Packages/OsaurusCore/Models/API/OpenResponsesAPI.swift index 19d1b8ca0..f500b2001 100644 --- a/Packages/OsaurusCore/Models/API/OpenResponsesAPI.swift +++ b/Packages/OsaurusCore/Models/API/OpenResponsesAPI.swift @@ -287,8 +287,7 @@ public enum OpenResponsesToolChoice: Codable, Sendable { public init(from decoder: Decoder) throws { // Try decoding as string first if let container = try? decoder.singleValueContainer(), - let str = try? container.decode(String.self) - { + let str = try? container.decode(String.self) { switch str { case "auto": self = .auto case "none": self = .none @@ -843,7 +842,7 @@ extension OpenResponsesRequest { } // Convert tools - var openAITools: [Tool]? = nil + var openAITools: [Tool]? if let tools = tools { openAITools = tools.map { tool in Tool( @@ -858,7 +857,7 @@ extension OpenResponsesRequest { } // Convert tool choice - var openAIToolChoice: ToolChoiceOption? = nil + var openAIToolChoice: ToolChoiceOption? if let choice = tool_choice { switch choice { case .auto: diff --git a/Packages/OsaurusCore/Models/Agent/Skill.swift b/Packages/OsaurusCore/Models/Agent/Skill.swift index e1359bba7..cbbb2cbbd 100644 --- a/Packages/OsaurusCore/Models/Agent/Skill.swift +++ b/Packages/OsaurusCore/Models/Agent/Skill.swift @@ -683,8 +683,7 @@ extension Skill { let createdAt: Date if let dateString = frontmatter["createdAt"] as? String, - let parsed = dateFormatter.date(from: dateString) - { + let parsed = dateFormatter.date(from: dateString) { createdAt = parsed } else { createdAt = Date() @@ -692,8 +691,7 @@ extension Skill { let updatedAt: Date if let dateString = frontmatter["updatedAt"] as? String, - let parsed = dateFormatter.date(from: dateString) - { + let parsed = dateFormatter.date(from: dateString) { updatedAt = parsed } else { updatedAt = Date() diff --git a/Packages/OsaurusCore/Models/Agent/SkillStore.swift b/Packages/OsaurusCore/Models/Agent/SkillStore.swift index 2e0d92030..1109ad047 100644 --- a/Packages/OsaurusCore/Models/Agent/SkillStore.swift +++ b/Packages/OsaurusCore/Models/Agent/SkillStore.swift @@ -338,8 +338,7 @@ public enum SkillStore { var isDirectory: ObjCBool = false if FileManager.default.fileExists(atPath: skillDir.path, isDirectory: &isDirectory), - isDirectory.boolValue - { + isDirectory.boolValue { try? FileManager.default.removeItem(at: file) continue } diff --git a/Packages/OsaurusCore/Models/Chat/ChatTurn.swift b/Packages/OsaurusCore/Models/Chat/ChatTurn.swift index e3964a7b9..2c1095c88 100644 --- a/Packages/OsaurusCore/Models/Chat/ChatTurn.swift +++ b/Packages/OsaurusCore/Models/Chat/ChatTurn.swift @@ -152,15 +152,15 @@ final class ChatTurn: ObservableObject, Identifiable { /// File attachments (images and documents) for this turn @Published var attachments: [Attachment] = [] /// Assistant-issued tool calls attached to this turn (OpenAI compatible) - @Published var toolCalls: [ToolCall]? = nil + @Published var toolCalls: [ToolCall]? /// For role==.tool messages, associates this result with the originating call id - var toolCallId: String? = nil + var toolCallId: String? /// Convenience map for UI to show tool results grouped under the assistant turn @Published var toolResults: [String: String] = [:] /// Tool name detected during streaming before the full invocation is ready. - var pendingToolName: String? = nil + var pendingToolName: String? /// Accumulated preview of tool arguments during streaming (tail-truncated) - var pendingToolArgPreview: String? = nil + var pendingToolArgPreview: String? /// Total bytes of tool arguments received during streaming var pendingToolArgSize: Int = 0 /// Number of arg fragments received during streaming. Used by the chat @@ -170,7 +170,7 @@ final class ChatTurn: ObservableObject, Identifiable { /// mid-stream. A fragment counter makes the throttle predictable. var pendingToolArgFragmentCount: Int = 0 /// Capabilities selected by preflight search (ephemeral, not persisted) - var preflightCapabilities: [PreflightCapabilityItem]? = nil + var preflightCapabilities: [PreflightCapabilityItem]? // MARK: - Generation Benchmarks (ephemeral, not persisted) diff --git a/Packages/OsaurusCore/Models/Chat/ContentBlock.swift b/Packages/OsaurusCore/Models/Chat/ContentBlock.swift index aaa8f7268..757aeef8d 100644 --- a/Packages/OsaurusCore/Models/Chat/ContentBlock.swift +++ b/Packages/OsaurusCore/Models/Chat/ContentBlock.swift @@ -194,8 +194,7 @@ struct ContentBlock: Identifiable, Equatable, Hashable { } static func thinking(turnId: UUID, index: Int, text: String, isStreaming: Bool, position: BlockPosition) - -> ContentBlock - { + -> ContentBlock { ContentBlock( id: "think-\(turnId.uuidString)-\(index)", turnId: turnId, @@ -205,8 +204,7 @@ struct ContentBlock: Identifiable, Equatable, Hashable { } static func userMessage(turnId: UUID, text: String, attachments: [Attachment], position: BlockPosition) - -> ContentBlock - { + -> ContentBlock { ContentBlock( id: "usermsg-\(turnId.uuidString)", turnId: turnId, @@ -244,8 +242,7 @@ struct ContentBlock: Identifiable, Equatable, Hashable { } static func preflightCapabilities(turnId: UUID, items: [PreflightCapabilityItem], position: BlockPosition) - -> ContentBlock - { + -> ContentBlock { ContentBlock( id: "preflight-\(turnId.uuidString)", turnId: turnId, @@ -392,8 +389,7 @@ extension ContentBlock { } if isStreaming && turn.contentIsEmpty && !turn.hasThinking - && (turn.toolCalls ?? []).isEmpty && turn.pendingToolName == nil - { + && (turn.toolCalls ?? []).isEmpty && turn.pendingToolName == nil { // During prefill (no content/thinking/tools yet), always show the typing // indicator so the interface doesn't appear frozen. // Only add the thinking placeholder when thinking is actually enabled for @@ -440,8 +436,7 @@ extension ContentBlock { // renders as the next user bubble below. if Self.isAgentLoopToolName(call.function.name) { if call.function.name == "clarify", - let block = Self.makeClarifyQuestionBlock(turnId: turn.id, call: call) - { + let block = Self.makeClarifyQuestionBlock(turnId: turn.id, call: call) { flushRegularItems() turnBlocks.append(block) } @@ -451,14 +446,12 @@ extension ContentBlock { let result = turn.toolResults[call.id] if call.function.name == "share_artifact", let result, - let artifact = Self.parseSharedArtifactFromResult(result) - { + let artifact = Self.parseSharedArtifactFromResult(result) { flushRegularItems() turnBlocks.append(.sharedArtifact(turnId: turn.id, artifact: artifact, position: .middle)) } else if call.function.name == "render_chart", let result, - let spec = Self.parseChartSpecFromResult(result) - { + let spec = Self.parseChartSpecFromResult(result) { flushRegularItems() turnBlocks.append(.chart(turnId: turn.id, spec: spec.normalized, position: .middle)) } else { @@ -481,8 +474,7 @@ extension ContentBlock { } if !isStreaming && turn.role == .assistant, - turn.timeToFirstToken != nil || turn.generationTokensPerSecond != nil - { + turn.timeToFirstToken != nil || turn.generationTokensPerSecond != nil { turnBlocks.append( .generationStats( turnId: turn.id, @@ -499,8 +491,7 @@ extension ContentBlock { // turn in a consecutive assistant group — intermediate tool-calling turns in // an agent loop don't get their own footer. if !isStreaming && turn.role == .assistant && isLastInGroup, - !turn.contentIsEmpty || !(turn.toolCalls ?? []).isEmpty - { + !turn.contentIsEmpty || !(turn.toolCalls ?? []).isEmpty { turnBlocks.append(.assistantActions(turnId: turn.id, position: .last)) } @@ -523,8 +514,7 @@ extension ContentBlock { private static func parseChartSpecFromResult(_ result: String) -> ChartSpec? { let source: String if let payload = ToolEnvelope.successPayload(result) as? [String: Any], - let text = payload["text"] as? String - { + let text = payload["text"] as? String { source = text } else { source = result @@ -588,8 +578,7 @@ extension ContentBlock { remaining = String(afterFence[closeRange.upperBound...]) if let data = json.data(using: .utf8), - let spec = try? JSONDecoder().decode(ChartSpec.self, from: data) - { + let spec = try? JSONDecoder().decode(ChartSpec.self, from: data) { blocks.append(.chart(turnId: turnId, spec: spec.normalized, position: .middle)) } else { // Malformed JSON — show as readable code block so user can see what was emitted diff --git a/Packages/OsaurusCore/Models/Chat/InternalMessage.swift b/Packages/OsaurusCore/Models/Chat/InternalMessage.swift index 06fc72d80..6170a4bd1 100644 --- a/Packages/OsaurusCore/Models/Chat/InternalMessage.swift +++ b/Packages/OsaurusCore/Models/Chat/InternalMessage.swift @@ -19,9 +19,4 @@ public enum MessageRole: String, Codable, Sendable { struct Message: Codable, Sendable { let role: MessageRole let content: String - - init(role: MessageRole, content: String) { - self.role = role - self.content = content - } } diff --git a/Packages/OsaurusCore/Models/Chat/RequestLog.swift b/Packages/OsaurusCore/Models/Chat/RequestLog.swift index fe7833e53..e6fc3e7cc 100644 --- a/Packages/OsaurusCore/Models/Chat/RequestLog.swift +++ b/Packages/OsaurusCore/Models/Chat/RequestLog.swift @@ -213,8 +213,7 @@ struct RequestLog: Identifiable, Sendable { guard let body = requestBody, let data = body.data(using: .utf8) else { return requestBody } if let json = try? JSONSerialization.jsonObject(with: data, options: []), let prettyData = try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys]), - let prettyString = String(data: prettyData, encoding: .utf8) - { + let prettyString = String(data: prettyData, encoding: .utf8) { return prettyString } return body @@ -225,8 +224,7 @@ struct RequestLog: Identifiable, Sendable { guard let body = responseBody, let data = body.data(using: .utf8) else { return responseBody } if let json = try? JSONSerialization.jsonObject(with: data, options: []), let prettyData = try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys]), - let prettyString = String(data: prettyData, encoding: .utf8) - { + let prettyString = String(data: prettyData, encoding: .utf8) { return prettyString } return body diff --git a/Packages/OsaurusCore/Models/Chat/ResponseWriters.swift b/Packages/OsaurusCore/Models/Chat/ResponseWriters.swift index 9a0eb5273..b7ca52a6b 100644 --- a/Packages/OsaurusCore/Models/Chat/ResponseWriters.swift +++ b/Packages/OsaurusCore/Models/Chat/ResponseWriters.swift @@ -297,8 +297,7 @@ final class SSEResponseWriter: ResponseWriter { tail.writeString("data: [DONE]\n\n") context.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(tail))), promise: nil) let ctx = NIOLoopBound(context, eventLoop: context.eventLoop) - context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } @@ -435,8 +434,7 @@ final class NDJSONResponseWriter: ResponseWriter { func writeEnd(_ context: ChannelHandlerContext) { let ctx = NIOLoopBound(context, eventLoop: context.eventLoop) - context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } @@ -640,8 +638,7 @@ final class AnthropicSSEResponseWriter { /// Close the connection func writeEnd(_ context: ChannelHandlerContext) { let ctx = NIOLoopBound(context, eventLoop: context.eventLoop) - context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } @@ -1036,8 +1033,7 @@ final class OpenResponsesSSEWriter { tail.writeString("data: [DONE]\n\n") context.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(tail))), promise: nil) let ctx = NIOLoopBound(context, eventLoop: context.eventLoop) - context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } diff --git a/Packages/OsaurusCore/Models/Chat/SessionSource.swift b/Packages/OsaurusCore/Models/Chat/SessionSource.swift index 43a6df66b..d0723217a 100644 --- a/Packages/OsaurusCore/Models/Chat/SessionSource.swift +++ b/Packages/OsaurusCore/Models/Chat/SessionSource.swift @@ -90,8 +90,7 @@ public enum PluginDisplayNameResolver { public static func displayName(for pluginId: String) -> String { if let manifestName = PluginManager.shared.loadedPlugin(for: pluginId)? .plugin.manifest.name, - !manifestName.isEmpty - { + !manifestName.isEmpty { return manifestName } if pluginId.hasPrefix("sandbox:") { diff --git a/Packages/OsaurusCore/Models/Chat/SharedArtifact.swift b/Packages/OsaurusCore/Models/Chat/SharedArtifact.swift index 4b9df593d..35fb71eaa 100644 --- a/Packages/OsaurusCore/Models/Chat/SharedArtifact.swift +++ b/Packages/OsaurusCore/Models/Chat/SharedArtifact.swift @@ -401,8 +401,7 @@ extension SharedArtifact { static func fromEnrichedToolResult(_ result: String) -> SharedArtifact? { let markerSource: String if let payload = ToolEnvelope.successPayload(result) as? [String: Any], - let text = payload["text"] as? String - { + let text = payload["text"] as? String { markerSource = text } else { markerSource = result @@ -616,8 +615,7 @@ extension SharedArtifact { var inner = "" if let jsonData = try? JSONSerialization.data(withJSONObject: parsed.metadata), - let jsonStr = String(data: jsonData, encoding: .utf8) - { + let jsonStr = String(data: jsonData, encoding: .utf8) { inner = jsonStr } if !contentLines.isEmpty { diff --git a/Packages/OsaurusCore/Models/Configuration/MLXModel.swift b/Packages/OsaurusCore/Models/Configuration/MLXModel.swift index a622cb320..82dba496f 100644 --- a/Packages/OsaurusCore/Models/Configuration/MLXModel.swift +++ b/Packages/OsaurusCore/Models/Configuration/MLXModel.swift @@ -225,8 +225,8 @@ struct MLXModel: Identifiable, Codable { "opencoder": "OpenCoder", ] - for (key, value) in strongMatches { - if name.contains(key) { return value } + for (key, value) in strongMatches where name.contains(key) { + return value } // 2. Fallback heuristic: clean up the name and take the first part @@ -235,11 +235,9 @@ struct MLXModel: Identifiable, Codable { let prefixes = [ "Meta-", "Google-", "Mistral-", "MistralAI-", "Microsoft-", "NousResearch-", "Qwen-", "DeepSeek-", ] - for prefix in prefixes { - if cleaned.hasPrefix(prefix) { - cleaned = String(cleaned.dropFirst(prefix.count)) - break - } + for prefix in prefixes where cleaned.hasPrefix(prefix) { + cleaned = String(cleaned.dropFirst(prefix.count)) + break } // Take first semantic part (before dash or dot) @@ -275,8 +273,8 @@ struct MLXModel: Identifiable, Codable { ("2-bit", 0.25), ("3-bit", 0.375), ("4-bit", 0.5), ("5-bit", 0.625), ("6-bit", 0.75), ("8-bit", 1.0), ] - for (label, bytes) in bitWidths { - if quant.contains(label) { return bytes } + for (label, bytes) in bitWidths where quant.contains(label) { + return bytes } switch quant { diff --git a/Packages/OsaurusCore/Models/Configuration/ModelInfo.swift b/Packages/OsaurusCore/Models/Configuration/ModelInfo.swift index c51f712b6..e6dec70b3 100644 --- a/Packages/OsaurusCore/Models/Configuration/ModelInfo.swift +++ b/Packages/OsaurusCore/Models/Configuration/ModelInfo.swift @@ -251,8 +251,7 @@ extension ModelInfo { var repeatPenalty: Double? if let data = try? Data(contentsOf: generationConfigURL), - let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { + let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { temperature = config["temperature"] as? Double topP = config["top_p"] as? Double topK = config["top_k"] as? Int diff --git a/Packages/OsaurusCore/Models/Configuration/ModelMetadataParser.swift b/Packages/OsaurusCore/Models/Configuration/ModelMetadataParser.swift index 5e2b8adca..e2a576dcf 100644 --- a/Packages/OsaurusCore/Models/Configuration/ModelMetadataParser.swift +++ b/Packages/OsaurusCore/Models/Configuration/ModelMetadataParser.swift @@ -25,8 +25,7 @@ enum ModelMetadataParser { if let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) { let range = NSRange(text.startIndex..., in: text) if let match = regex.firstMatch(in: text, options: [], range: range), - let numRange = Range(match.range(at: 1), in: text) - { + let numRange = Range(match.range(at: 1), in: text) { let number = String(text[numRange]) let fullMatch = String(text[Range(match.range, in: text)!]).uppercased() return "\(number)\(fullMatch.contains("M") ? "M" : "B")" @@ -51,8 +50,8 @@ enum ModelMetadataParser { ("q4_0", "Q4_0"), ("q4_k_m", "Q4_K_M"), ("q8_0", "Q8_0"), ("q8_k_m", "Q8_K_M"), ] - for (pattern, result) in ggufPatterns { - if text.contains(pattern) { return result } + for (pattern, result) in ggufPatterns where text.contains(pattern) { + return result } return precisionFormat(from: repoId) } diff --git a/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift b/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift index ef143bb57..1dbc3de45 100644 --- a/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift +++ b/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift @@ -188,8 +188,7 @@ public struct RemoteProvider: Codable, Identifiable, Sendable, Equatable { // Check if host contains a port (e.g., "localhost:8080") if let colonIndex = actualHost.lastIndex(of: ":"), - let portValue = Int(String(actualHost[actualHost.index(after: colonIndex)...])) - { + let portValue = Int(String(actualHost[actualHost.index(after: colonIndex)...])) { // Extract port from host if not already set if port == nil { components.port = portValue diff --git a/Packages/OsaurusCore/Models/Configuration/ServerConfiguration.swift b/Packages/OsaurusCore/Models/Configuration/ServerConfiguration.swift index 06a359d74..5b3f7007c 100644 --- a/Packages/OsaurusCore/Models/Configuration/ServerConfiguration.swift +++ b/Packages/OsaurusCore/Models/Configuration/ServerConfiguration.swift @@ -9,9 +9,9 @@ import Foundation /// Appearance mode setting for the app public enum AppearanceMode: String, Codable, CaseIterable, Sendable { - case system = "system" - case light = "light" - case dark = "dark" + case system + case light + case dark public var displayName: String { switch self { diff --git a/Packages/OsaurusCore/Models/Tool/ToolConfiguration.swift b/Packages/OsaurusCore/Models/Tool/ToolConfiguration.swift index 4c274fab0..a741044d0 100644 --- a/Packages/OsaurusCore/Models/Tool/ToolConfiguration.swift +++ b/Packages/OsaurusCore/Models/Tool/ToolConfiguration.swift @@ -70,8 +70,8 @@ struct ToolConfiguration: Codable, Equatable, Sendable { func hasGrants(for name: String, requirements: [String]) -> Bool { guard !requirements.isEmpty else { return true } let granted = grants[name] ?? [:] - for req in requirements { - if granted[req] != true { return false } + for req in requirements where granted[req] != true { + return false } return true } diff --git a/Packages/OsaurusCore/Networking/BonjourAdvertiser.swift b/Packages/OsaurusCore/Networking/BonjourAdvertiser.swift index abdf143c9..79359fc45 100644 --- a/Packages/OsaurusCore/Networking/BonjourAdvertiser.swift +++ b/Packages/OsaurusCore/Networking/BonjourAdvertiser.swift @@ -24,7 +24,7 @@ public final class BonjourAdvertiser: NSObject { private var isAdvertising = false private var cancellables: Set = [] - private override init() { + override private init() { super.init() // Keep advertisements in sync whenever the agent list changes. AgentManager.shared.$agents @@ -108,11 +108,11 @@ public final class BonjourAdvertiser: NSObject { // MARK: - NetServiceDelegate extension BonjourAdvertiser: NetServiceDelegate { - public nonisolated func netServiceDidPublish(_ sender: NetService) { + nonisolated public func netServiceDidPublish(_ sender: NetService) { print("[Bonjour] Advertised agent '\(sender.name)' on port \(sender.port)") } - public nonisolated func netService(_ sender: NetService, didNotPublish errorDict: [String: NSNumber]) { + nonisolated public func netService(_ sender: NetService, didNotPublish errorDict: [String: NSNumber]) { print("[Bonjour] Failed to advertise agent '\(sender.name)': \(errorDict)") } } diff --git a/Packages/OsaurusCore/Networking/BonjourBrowser.swift b/Packages/OsaurusCore/Networking/BonjourBrowser.swift index 61e4a9e69..035cb28da 100644 --- a/Packages/OsaurusCore/Networking/BonjourBrowser.swift +++ b/Packages/OsaurusCore/Networking/BonjourBrowser.swift @@ -53,7 +53,7 @@ public final class BonjourBrowser: NSObject, ObservableObject { /// Services currently being resolved, keyed by NetService name. private var resolvingServices: [String: NetService] = [:] - private override init() { + override private init() { super.init() startBrowsing() } diff --git a/Packages/OsaurusCore/Networking/HTTPHandler.swift b/Packages/OsaurusCore/Networking/HTTPHandler.swift index 2032bf558..9d2a8c009 100644 --- a/Packages/OsaurusCore/Networking/HTTPHandler.swift +++ b/Packages/OsaurusCore/Networking/HTTPHandler.swift @@ -96,8 +96,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { // Reject before allocating the body buffer so a client lying // about Content-Length can't force a huge allocation up front. if let lengthStr = head.headers.first(name: "Content-Length"), - let length = Int(lengthStr) - { + let length = Int(lengthStr) { if length > stateRef.value.bodyByteLimit { rejectPayloadTooLarge( context: context, @@ -125,8 +124,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { // append so an oversize chunk never lands in our buffer. stateRef.value.bodyBytesSeen += buffer.readableBytes if stateRef.value.bodyBytesSeen > stateRef.value.bodyByteLimit, - let head = stateRef.value.requestHead - { + let head = stateRef.value.requestHead { rejectPayloadTooLarge( context: context, head: head, @@ -1461,8 +1459,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { // Send response context.write(NIOAny(HTTPServerResponsePart.head(responseHead)), promise: nil) context.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(buffer))), promise: nil) - context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + context.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } @@ -1505,8 +1502,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { headers.append(("Access-Control-Allow-Origin", "*")) } else if let origin, !origin.contains("\r"), !origin.contains("\n"), - configuration.allowedOrigins.contains(origin) - { + configuration.allowedOrigins.contains(origin) { headers.append(("Access-Control-Allow-Origin", origin)) headers.append(("Vary", "Origin")) } else { @@ -1615,7 +1611,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -1790,7 +1786,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -2000,7 +1996,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { // 5. Return the agent's address, the generated API key, and the permanence flag. let response = PairResponse(agentAddress: agentAddress, apiKey: fullKey, isPermanent: isPermanent) let json = - (try? JSONEncoder().encode(response)).map { String(decoding: $0, as: UTF8.self) } + (try? JSONEncoder().encode(response)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"error":"Encoding failed"}"# // Never log the freshly minted key. The wire response still // contains it; the request log gets a redacted copy with the @@ -2012,7 +2008,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { isPermanent: isPermanent ) let redactedJson = - (try? JSONEncoder().encode(redactedResponse)).map { String(decoding: $0, as: UTF8.self) } + (try? JSONEncoder().encode(redactedResponse)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"agentAddress":"","apiKey":""}"# hop { @@ -2062,7 +2058,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -2193,7 +2189,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { apiKey: apiKey ) return (try? JSONEncoder().encode(body)) - .map { String(decoding: $0, as: UTF8.self) } + .map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"error":"Encoding failed"}"# } @@ -2287,7 +2283,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let response = AgentListResponse(agents: items) let json = - (try? JSONEncoder().encode(response)).map { String(decoding: $0, as: UTF8.self) } ?? #"{"agents":[]}"# + (try? JSONEncoder().encode(response)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"agents":[]}"# hop { var headers = [("Content-Type", "application/json; charset=utf-8")] @@ -2386,7 +2382,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { updated_at: formatter.string(from: agent.updatedAt) ) let json = - (try? JSONEncoder().encode(item)).map { String(decoding: $0, as: UTF8.self) } ?? "{}" + (try? JSONEncoder().encode(item)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] @@ -2430,7 +2426,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -2775,7 +2771,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -2879,7 +2875,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let pollUrl = "/v1/tasks/\(resolvedId)" let resp: [String: Any] = ["id": resolvedId, "status": "running", "poll_url": pollUrl] responseBody = - (try? JSONSerialization.data(withJSONObject: resp)).flatMap { String(decoding: $0, as: UTF8.self) } + (try? JSONSerialization.data(withJSONObject: resp)).flatMap { String(bytes: $0, encoding: .utf8) } ?? "{}" status = .accepted } else { @@ -3060,7 +3056,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -3151,7 +3147,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -3198,7 +3194,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let json: String if ollamaFormat { let response = OllamaEmbedResponse(model: EmbeddingService.modelName, embeddings: embeddings) - json = (try? JSONEncoder().encode(response)).map { String(decoding: $0, as: UTF8.self) } ?? "{}" + json = (try? JSONEncoder().encode(response)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" } else { let objects = embeddings.enumerated().map { OpenAIEmbeddingObject(embedding: $1, index: $0) } let tokenCount = texts.reduce(0) { $0 + $1.split(separator: " ").count } @@ -3207,7 +3203,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { model: EmbeddingService.modelName, usage: OpenAIEmbeddingUsage(prompt_tokens: tokenCount, total_tokens: tokenCount) ) - json = (try? JSONEncoder().encode(response)).map { String(decoding: $0, as: UTF8.self) } ?? "{}" + json = (try? JSONEncoder().encode(response)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" } hop { @@ -3273,7 +3269,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -3642,7 +3638,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var headers: [(String, String)] = [("Content-Type", "application/json")] headers.append(contentsOf: cors) let headersCopy = headers - let body = String(decoding: json, as: UTF8.self) + let body = String(bytes: json, encoding: .utf8) ?? "" hop { var responseHead = HTTPResponseHead(version: head.version, status: .ok) var buffer = ctx.value.channel.allocator.buffer(capacity: body.utf8.count) @@ -3655,8 +3651,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let c = ctx.value c.write(NIOAny(HTTPServerResponsePart.head(responseHead)), promise: nil) c.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(buffer))), promise: nil) - c.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + c.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } @@ -3724,8 +3719,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let c = ctx.value c.write(NIOAny(HTTPServerResponsePart.head(responseHead)), promise: nil) c.write(NIOAny(HTTPServerResponsePart.body(.byteBuffer(buffer))), promise: nil) - c.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { - _ in + c.writeAndFlush(NIOAny(HTTPServerResponsePart.end(nil as HTTPHeaders?))).whenComplete { _ in ctx.value.close(promise: nil) } } @@ -3756,7 +3750,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -4031,7 +4025,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { "inflight": inflightObj, ] let data = try? JSONSerialization.data(withJSONObject: obj) - let body = data.flatMap { String(decoding: $0, as: UTF8.self) } ?? "{}" + let body = data.flatMap { String(bytes: $0, encoding: .utf8) } ?? "{}" let headers: [(String, String)] = [("Content-Type", "application/json; charset=utf-8")] + cors @@ -4087,7 +4081,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { models.append(contentsOf: remoteModels) let response = ModelsResponse(data: models) - let json = (try? JSONEncoder().encode(response)).map { String(decoding: $0, as: UTF8.self) } ?? "{}" + let json = (try? JSONEncoder().encode(response)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] @@ -4188,7 +4182,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { } let payload = ["models": models] - let json = (try? JSONEncoder().encode(payload)).map { String(decoding: $0, as: UTF8.self) } ?? "{}" + let json = (try? JSONEncoder().encode(payload)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] @@ -4225,7 +4219,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -4305,7 +4299,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { ], ] let jsonData = (try? JSONSerialization.data(withJSONObject: response)) ?? Data("{}".utf8) - let json = String(decoding: jsonData, as: UTF8.self) + let json = String(bytes: jsonData, encoding: .utf8) ?? "" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: cors) @@ -4386,7 +4380,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let response = modelInfo.toShowResponse() let jsonData = (try? JSONEncoder().encode(response)) ?? Data("{}".utf8) - let json = String(decoding: jsonData, as: UTF8.self) + let json = String(bytes: jsonData, encoding: .utf8) ?? "" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] @@ -4443,7 +4437,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { } let payload: [String: Any] = ["tools": tools] let data = (try? JSONSerialization.data(withJSONObject: payload)) ?? Data("{}".utf8) - let mcpToolsBody = String(decoding: data, as: UTF8.self) + let mcpToolsBody = String(bytes: data, encoding: .utf8) ?? "" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: cors) @@ -4479,7 +4473,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -4515,10 +4509,10 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { case let s as String: try container.encode(s) case let arr as [Any]: let enc = try JSONSerialization.data(withJSONObject: arr, options: []) - try container.encode(String(decoding: enc, as: UTF8.self)) + try container.encode(String(bytes: enc, encoding: .utf8) ?? "") case let dict as [String: Any]: let enc = try JSONSerialization.data(withJSONObject: dict, options: []) - try container.encode(String(decoding: enc, as: UTF8.self)) + try container.encode(String(bytes: enc, encoding: .utf8) ?? "") default: try container.encodeNil() } @@ -4547,9 +4541,8 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let argsJSON: String = { if let a = req.arguments?.value, - let d = try? JSONSerialization.data(withJSONObject: a, options: []) - { - return String(decoding: d, as: UTF8.self) + let d = try? JSONSerialization.data(withJSONObject: a, options: []) { + return String(bytes: d, encoding: .utf8) ?? "" } return "{}" }() @@ -4579,7 +4572,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { "isError": true, ] let data = (try? JSONSerialization.data(withJSONObject: payload)) ?? Data("{}".utf8) - let body = String(decoding: data, as: UTF8.self) + let body = String(bytes: data, encoding: .utf8) ?? "" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: cors) @@ -4617,7 +4610,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { "isError": false, ] let d = (try? JSONSerialization.data(withJSONObject: payload)) ?? Data("{}".utf8) - let body = String(decoding: d, as: UTF8.self) + let body = String(bytes: d, encoding: .utf8) ?? "" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: cors) @@ -4651,7 +4644,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { "isError": true, ] let d = (try? JSONSerialization.data(withJSONObject: payload)) ?? Data("{}".utf8) - let body = String(decoding: d, as: UTF8.self) + let body = String(bytes: d, encoding: .utf8) ?? "" hop { var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: cors) @@ -4698,7 +4691,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -4708,7 +4701,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { guard let anthropicReq = try? JSONDecoder().decode(AnthropicMessagesRequest.self, from: data) else { let error = AnthropicError(message: "Invalid request format", errorType: "invalid_request_error") let errorJson = - (try? JSONEncoder().encode(error)).map { String(decoding: $0, as: UTF8.self) } + (try? JSONEncoder().encode(error)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"type":"error","error":{"type":"invalid_request_error","message":"Invalid request format"}}"# var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: stateRef.value.corsHeaders) @@ -4960,8 +4953,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { // Parse arguments JSON to dictionary var inputDict: [String: AnyCodableValue] = [:] if let argsData = toolCall.function.arguments.data(using: .utf8), - let parsed = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] - { + let parsed = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] { inputDict = parsed.mapValues { AnyCodableValue($0) } } contentBlocks.append( @@ -4991,7 +4983,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var headers: [(String, String)] = [("Content-Type", "application/json")] headers.append(contentsOf: cors) let headersCopy = headers - let body = String(decoding: json, as: UTF8.self) + let body = String(bytes: json, encoding: .utf8) ?? "" hop { var responseHead = HTTPResponseHead(version: head.version, status: .ok) @@ -5075,7 +5067,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let errorResp = AnthropicError(message: error.localizedDescription, errorType: "api_error") let errorJson = (try? JSONEncoder().encode(errorResp)) - .map { String(decoding: $0, as: UTF8.self) } + .map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"type":"error","error":{"type":"api_error","message":"Internal error"}}"# var headers: [(String, String)] = [("Content-Type", "application/json")] headers.append(contentsOf: cors) @@ -5237,12 +5229,12 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { response["duration"] = duration } let jsonData = try JSONSerialization.data(withJSONObject: response) - responseBody = String(decoding: jsonData, as: UTF8.self) + responseBody = String(bytes: jsonData, encoding: .utf8) ?? "" } else { // Default JSON format let response = ["text": result.text] let jsonData = try JSONEncoder().encode(response) - responseBody = String(decoding: jsonData, as: UTF8.self) + responseBody = String(bytes: jsonData, encoding: .utf8) ?? "" } hop { @@ -5313,7 +5305,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] data = Data(bytes) - requestBodyString = String(decoding: data, as: UTF8.self) + requestBodyString = String(bytes: data, encoding: .utf8) ?? "" } else { data = Data() requestBodyString = nil @@ -5323,7 +5315,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { guard let openResponsesReq = try? JSONDecoder().decode(OpenResponsesRequest.self, from: data) else { let error = OpenResponsesErrorResponse(code: "invalid_request_error", message: "Invalid request format") let errorJson = - (try? JSONEncoder().encode(error)).map { String(decoding: $0, as: UTF8.self) } + (try? JSONEncoder().encode(error)).map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"error":{"type":"error","code":"invalid_request_error","message":"Invalid request format"}}"# var headers = [("Content-Type", "application/json; charset=utf-8")] headers.append(contentsOf: stateRef.value.corsHeaders) @@ -5608,7 +5600,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { usage: OpenResponsesUsage(inputTokens: 0, outputTokens: 0) ) return (try? JSONEncoder().encode(resp)) - .map { String(decoding: $0, as: UTF8.self) } ?? "{}" + .map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" } /// Build an Anthropic `tool_use` block for a single MLX-emitted @@ -5619,8 +5611,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let toolId = inv.toolCallId ?? Self.shortId(prefix: "toolu_") var inputDict: [String: AnyCodableValue] = [:] if let argsData = inv.jsonArguments.data(using: .utf8), - let parsed = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] - { + let parsed = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] { inputDict = parsed.mapValues { AnyCodableValue($0) } } return AnthropicResponseContentBlock.toolUseBlock( @@ -5646,7 +5637,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { usage: AnthropicUsage(inputTokens: 0, outputTokens: 0) ) return (try? JSONEncoder().encode(resp)) - .map { String(decoding: $0, as: UTF8.self) } ?? "{}" + .map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" } /// Emit a complete Anthropic `tool_use` content block for a single @@ -5780,7 +5771,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { var headers: [(String, String)] = [("Content-Type", "application/json")] headers.append(contentsOf: cors) let headersCopy = headers - let body = String(decoding: json, as: UTF8.self) + let body = String(bytes: json, encoding: .utf8) ?? "" hop { var responseHead = HTTPResponseHead(version: head.version, status: .ok) @@ -5858,7 +5849,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { let errorResp = OpenResponsesErrorResponse(code: "api_error", message: error.localizedDescription) let errorJson = (try? JSONEncoder().encode(errorResp)) - .map { String(decoding: $0, as: UTF8.self) } + .map { String(bytes: $0, encoding: .utf8) ?? "" } ?? #"{"error":{"type":"error","code":"api_error","message":"Internal error"}}"# var headers: [(String, String)] = [("Content-Type", "application/json")] headers.append(contentsOf: cors) @@ -5924,9 +5915,9 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { private func parseMultipartFormData(data: Data, boundary: String) -> MultipartParseResult { var result = MultipartParseResult() - let boundaryData = ("--" + boundary).data(using: .utf8)! - let crlfData = "\r\n".data(using: .utf8)! - let doubleCrlfData = "\r\n\r\n".data(using: .utf8)! + let boundaryData = Data(("--" + boundary).utf8) + let crlfData = Data("\r\n".utf8) + let doubleCrlfData = Data("\r\n\r\n".utf8) // Split by boundary var ranges: [Range] = [] diff --git a/Packages/OsaurusCore/Networking/HTTPRequestParse.swift b/Packages/OsaurusCore/Networking/HTTPRequestParse.swift index 34886213b..d77aa5be5 100644 --- a/Packages/OsaurusCore/Networking/HTTPRequestParse.swift +++ b/Packages/OsaurusCore/Networking/HTTPRequestParse.swift @@ -32,6 +32,6 @@ extension HTTPHandler { var bodyCopy = body let bytes = bodyCopy.readBytes(length: bodyCopy.readableBytes) ?? [] let data = Data(bytes) - return ParsedBody(data: data, text: String(decoding: data, as: UTF8.self)) + return ParsedBody(data: data, text: String(bytes: data, encoding: .utf8) ?? "") } } diff --git a/Packages/OsaurusCore/Networking/HostAPIBridgeServer.swift b/Packages/OsaurusCore/Networking/HostAPIBridgeServer.swift index c46676081..ca81dddea 100644 --- a/Packages/OsaurusCore/Networking/HostAPIBridgeServer.swift +++ b/Packages/OsaurusCore/Networking/HostAPIBridgeServer.swift @@ -103,8 +103,7 @@ private final class HostAPIBridgeHandler: ChannelInboundHandler, RemovableChanne // HTTP server: refuse before allocating into the body buffer. if let lengthStr = head.headers.first(name: "Content-Length"), let length = Int(lengthStr), - length > Self.maxBodyBytes - { + length > Self.maxBodyBytes { rejectTooLarge(context: context, head: head, declared: length) return } @@ -181,8 +180,7 @@ private final class HostAPIBridgeHandler: ChannelInboundHandler, RemovableChanne Task { let response: BridgeResponse if let token = bearerToken, - let identity = await SandboxBridgeTokenStore.shared.resolve(token: token) - { + let identity = await SandboxBridgeTokenStore.shared.resolve(token: token) { response = await handler.value.routeRequest( method: method, path: path, @@ -358,8 +356,7 @@ private final class HostAPIBridgeHandler: ChannelInboundHandler, RemovableChanne OsaurusPaths.ensureExistsSilent(configDir) var dict: [String: String] = [:] if let data = try? Data(contentsOf: configFile), - let existing = try? JSONSerialization.jsonObject(with: data) as? [String: String] - { + let existing = try? JSONSerialization.jsonObject(with: data) as? [String: String] { dict = existing } dict[key] = value @@ -473,8 +470,7 @@ private final class HostAPIBridgeHandler: ChannelInboundHandler, RemovableChanne // silently dispatch into the wrong agent. if let claimed = parsed["agent_id"] as? String, !claimed.isEmpty, - claimed.lowercased() != identity.agentId.uuidString.lowercased() - { + claimed.lowercased() != identity.agentId.uuidString.lowercased() { return .error(403, "agent_id in body does not match token-bound identity") } @@ -529,8 +525,7 @@ private final class HostAPIBridgeHandler: ChannelInboundHandler, RemovableChanne ] } if let data = try? JSONSerialization.data(withJSONObject: ["results": entries]), - let json = String(data: data, encoding: .utf8) - { + let json = String(data: data, encoding: .utf8) { return .ok(json) } return .ok("{\"results\":[]}") @@ -580,8 +575,7 @@ private final class HostAPIBridgeHandler: ChannelInboundHandler, RemovableChanne let payloadStr: String if let payloadDict = payload { if let data = try? JSONSerialization.data(withJSONObject: payloadDict), - let str = String(data: data, encoding: .utf8) - { + let str = String(data: data, encoding: .utf8) { payloadStr = str } else { payloadStr = "{}" diff --git a/Packages/OsaurusCore/Networking/OsaurusServer.swift b/Packages/OsaurusCore/Networking/OsaurusServer.swift index 46892490d..c784c5ad7 100644 --- a/Packages/OsaurusCore/Networking/OsaurusServer.swift +++ b/Packages/OsaurusCore/Networking/OsaurusServer.swift @@ -11,14 +11,13 @@ import NIOCore import NIOHTTP1 import NIOPosix -public actor OsaurusServer: Sendable { +public actor OsaurusServer { public struct Config: Sendable { public var host: String public var port: Int public var agentIndex: UInt32? public var trustLoopback: Bool - public init(host: String = "127.0.0.1", port: Int = 1337, agentIndex: UInt32? = nil, trustLoopback: Bool = true) - { + public init(host: String = "127.0.0.1", port: Int = 1337, agentIndex: UInt32? = nil, trustLoopback: Bool = true) { self.host = host self.port = port self.agentIndex = agentIndex diff --git a/Packages/OsaurusCore/Networking/RelayTunnelManager.swift b/Packages/OsaurusCore/Networking/RelayTunnelManager.swift index 1e5e637ed..fc2c12030 100644 --- a/Packages/OsaurusCore/Networking/RelayTunnelManager.swift +++ b/Packages/OsaurusCore/Networking/RelayTunnelManager.swift @@ -597,10 +597,8 @@ public final class RelayTunnelManager: ObservableObject { addressToAgentId.removeAll() pendingNonceHandler = nil - for id in configuration.enabledAgentIds { - if agentStatuses[id] != .disconnected { - agentStatuses[id] = .connecting - } + for id in configuration.enabledAgentIds where agentStatuses[id] != .disconnected { + agentStatuses[id] = .connecting } guard shouldReconnect else { return } diff --git a/Packages/OsaurusCore/Networking/ServerController.swift b/Packages/OsaurusCore/Networking/ServerController.swift index 63f37d109..4539d90c5 100644 --- a/Packages/OsaurusCore/Networking/ServerController.swift +++ b/Packages/OsaurusCore/Networking/ServerController.swift @@ -262,12 +262,9 @@ final class ServerController: ObservableObject { // MARK: - Port Probe (Network-based, concurrency-safe) private func isAnyListenerActive(on hosts: [String], port: Int, timeout: TimeInterval) async - -> Bool - { - for host in hosts { - if await isListenerActive(host: host, port: port, timeout: timeout) { - return true - } + -> Bool { + for host in hosts where await isListenerActive(host: host, port: port, timeout: timeout) { + return true } return false } @@ -280,7 +277,7 @@ final class ServerController: ObservableObject { connection.stateUpdateHandler = { state in continuation.yield(state) switch state { - case .ready, .failed(_), .cancelled: + case .ready, .failed, .cancelled: continuation.finish() // Avoid further callbacks connection.stateUpdateHandler = nil @@ -299,7 +296,7 @@ final class ServerController: ObservableObject { case .ready: connection.cancel() return true - case .failed(_), .cancelled: + case .failed, .cancelled: return false default: break @@ -357,7 +354,7 @@ final class ServerController: ObservableObject { ) == 0 { // Trim at NUL terminator before decoding to avoid deprecated cString initializer. let nulTrimmed = hostname.prefix { $0 != 0 } - let ip = String(decoding: nulTrimmed.map { UInt8(bitPattern: $0) }, as: UTF8.self) + let ip = String(bytes: nulTrimmed.map { UInt8(bitPattern: $0) }, encoding: .utf8) ?? "" let name = String(cString: ptr.pointee.ifa_name) if name.starts(with: "en") { // en0, en1, etc. are common for Wi-Fi/Ethernet on macOS address = ip diff --git a/Packages/OsaurusCore/Services/Chat/AgentNameDetector.swift b/Packages/OsaurusCore/Services/Chat/AgentNameDetector.swift index 515b722a5..cbef26ce1 100644 --- a/Packages/OsaurusCore/Services/Chat/AgentNameDetector.swift +++ b/Packages/OsaurusCore/Services/Chat/AgentNameDetector.swift @@ -126,11 +126,9 @@ public final class AgentNameDetector { var matchedWords = 0 for patternWord in patternWords { - for textWord in textWords { - if fuzzyMatch(String(patternWord), String(textWord)) { - matchedWords += 1 - break - } + for textWord in textWords where fuzzyMatch(String(patternWord), String(textWord)) { + matchedWords += 1 + break } } diff --git a/Packages/OsaurusCore/Services/Chat/ChatEngine.swift b/Packages/OsaurusCore/Services/Chat/ChatEngine.swift index 3e2725ffb..cd680644d 100644 --- a/Packages/OsaurusCore/Services/Chat/ChatEngine.swift +++ b/Packages/OsaurusCore/Services/Chat/ChatEngine.swift @@ -7,7 +7,7 @@ import Foundation -actor ChatEngine: Sendable, ChatEngineProtocol { +actor ChatEngine: ChatEngineProtocol { private let services: [ModelService] private let installedModelsProvider: @Sendable () -> [String] @@ -198,8 +198,7 @@ actor ChatEngine: Sendable, ChatEngineProtocol { withJSONObject: envelope, options: [.prettyPrinted, .sortedKeys] ), - let s = String(data: data, encoding: .utf8) - { + let s = String(data: data, encoding: .utf8) { return s } } @@ -396,8 +395,8 @@ actor ChatEngine: Sendable, ChatEngineProtocol { var outputTokenCount = 0 var deltaCount = 0 var finishReason: InferenceLog.FinishReason = .stop - var errorMsg: String? = nil - var toolInvocation: (name: String, args: String)? = nil + var errorMsg: String? + var toolInvocation: (name: String, args: String)? var lastDeltaTime = startTime // Accumulate the streamed assistant text so the Insights Response // tab can show what was produced. Only retained when logging is diff --git a/Packages/OsaurusCore/Services/Chat/ContextSizeClass.swift b/Packages/OsaurusCore/Services/Chat/ContextSizeClass.swift index 802033833..5e098ed47 100644 --- a/Packages/OsaurusCore/Services/Chat/ContextSizeClass.swift +++ b/Packages/OsaurusCore/Services/Chat/ContextSizeClass.swift @@ -158,8 +158,7 @@ public enum ContextSizeResolver { // to call it. let trimmed = modelId.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.caseInsensitiveCompare("foundation") == .orderedSame - || trimmed.caseInsensitiveCompare("default") == .orderedSame - { + || trimmed.caseInsensitiveCompare("default") == .orderedSame { return ContextWindowInfo(sizeClass: .tiny, contextLength: tinyCeiling) } diff --git a/Packages/OsaurusCore/Services/Chat/SystemPromptComposer.swift b/Packages/OsaurusCore/Services/Chat/SystemPromptComposer.swift index 2f59bca0f..89255ad12 100644 --- a/Packages/OsaurusCore/Services/Chat/SystemPromptComposer.swift +++ b/Packages/OsaurusCore/Services/Chat/SystemPromptComposer.swift @@ -158,8 +158,7 @@ public struct SystemPromptComposer: Sendable { if !trimmed.isEmpty { return trimmed } for msg in messages.reversed() where msg.role == "user" { if let content = msg.content?.trimmingCharacters(in: .whitespacesAndNewlines), - !content.isEmpty - { + !content.isEmpty { return content } } @@ -324,7 +323,7 @@ public struct SystemPromptComposer: Sendable { let lastNewline = utf8.prefix(soulMaxBytes) .lastIndex(of: UInt8(ascii: "\n")) let cutoff = lastNewline.map { $0 + 1 } ?? soulMaxBytes - let prefix = String(decoding: utf8.prefix(cutoff), as: UTF8.self) + let prefix = String(bytes: utf8.prefix(cutoff), encoding: .utf8) ?? "" // Hard-cut prefix may not end on `\n`; force one so the marker // always reads as its own line below the soul content. let separator = prefix.hasSuffix("\n") ? "" : "\n" @@ -575,8 +574,7 @@ public struct SystemPromptComposer: Sendable { // inflates context and encourages tool enumeration. // See ModelFamilyGuidance.swift. if !effectiveToolsOff, - let familyGuidance = ModelFamilyGuidance.guidance(forModelId: snapshot.model) - { + let familyGuidance = ModelFamilyGuidance.guidance(forModelId: snapshot.model) { composer.append( .static( id: "modelFamilyGuidance", @@ -592,8 +590,7 @@ public struct SystemPromptComposer: Sendable { // plugin tool that writes all qualify. The set lives at the top // of the file so it can grow as new mutation-capable tools land. if !effectiveToolsOff, - !resolvedNames.isDisjoint(with: Self.mutationToolNames) - { + !resolvedNames.isDisjoint(with: Self.mutationToolNames) { composer.append( .static( id: "codeStyle", @@ -616,8 +613,7 @@ public struct SystemPromptComposer: Sendable { // schema — in practice that's every chat where tools are on, but // the gate keeps tools-off sessions from carrying dead text. if !effectiveToolsOff, - !resolvedNames.isDisjoint(with: Self.agentLoopToolNames) - { + !resolvedNames.isDisjoint(with: Self.agentLoopToolNames) { composer.append( .static( id: "agentLoopGuidance", @@ -655,8 +651,7 @@ public struct SystemPromptComposer: Sendable { // sessions don't see irrelevant guidance. if snapshot.toolMode == .auto, !effectiveToolsOff, - tools.contains(where: { $0.function.name == "capabilities_search" }) - { + tools.contains(where: { $0.function.name == "capabilities_search" }) { composer.append( .static( id: "capabilityNudge", @@ -673,8 +668,7 @@ public struct SystemPromptComposer: Sendable { // the model hallucinates sandbox calls that never get a result. if !executionMode.usesSandboxTools, snapshot.autonomousEnabled, - let reason = SandboxToolRegistrar.shared.unavailabilityReason(for: agentId) - { + let reason = SandboxToolRegistrar.shared.unavailabilityReason(for: agentId) { composer.append( .dynamic( id: "sandboxUnavailable", @@ -698,8 +692,7 @@ public struct SystemPromptComposer: Sendable { !effectiveToolsOff, !preflight.companions.isEmpty, tools.contains(where: { $0.function.name == "capabilities_load" }), - let companionsSection = PreflightCompanions.render(preflight.companions) - { + let companionsSection = PreflightCompanions.render(preflight.companions) { composer.append( .dynamic( id: "pluginCompanions", @@ -720,8 +713,7 @@ public struct SystemPromptComposer: Sendable { // for the preview composer which doesn't pre-gate. if !effectiveToolsOff, !toolset.skillSuggestions.isEmpty, - let suggestionsSection = PreflightCompanions.renderSkillSuggestions(toolset.skillSuggestions) - { + let suggestionsSection = PreflightCompanions.renderSkillSuggestions(toolset.skillSuggestions) { composer.append( .dynamic( id: "skillSuggestions", @@ -1319,8 +1311,7 @@ public struct SystemPromptComposer: Sendable { let trimmed = content.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return } if let idx = messages.firstIndex(where: { $0.role == "system" }), - let existing = messages[idx].content, !existing.isEmpty - { + let existing = messages[idx].content, !existing.isEmpty { let combined = prepend ? trimmed + "\n\n" + existing : existing + "\n\n" + trimmed messages[idx] = ChatMessage(role: "system", content: combined) } else { diff --git a/Packages/OsaurusCore/Services/Context/CapabilitySearchHealth.swift b/Packages/OsaurusCore/Services/Context/CapabilitySearchHealth.swift index 2a33ef877..9354bb36d 100644 --- a/Packages/OsaurusCore/Services/Context/CapabilitySearchHealth.swift +++ b/Packages/OsaurusCore/Services/Context/CapabilitySearchHealth.swift @@ -164,7 +164,7 @@ public enum CapabilitySearchDiagnostics { /// `nonisolated` so non-main-actor callers (e.g. /// `CapabilitySearch.search`) don't have to hop the main actor /// just to stringify a `Sendable` struct. - public nonisolated static func formatSummary(_ health: CapabilitySearchHealth) -> String { + nonisolated public static func formatSummary(_ health: CapabilitySearchHealth) -> String { var parts: [String] = [ "registry=\(health.registryToolCount)", "index=\(health.indexedToolCount)", diff --git a/Packages/OsaurusCore/Services/Context/ClipboardService.swift b/Packages/OsaurusCore/Services/Context/ClipboardService.swift index 65cff669e..2674264c1 100644 --- a/Packages/OsaurusCore/Services/Context/ClipboardService.swift +++ b/Packages/OsaurusCore/Services/Context/ClipboardService.swift @@ -107,8 +107,7 @@ public final class ClipboardService: ObservableObject { return .image(imageData) } if let tiffData = pb.data(forType: .tiff), let nsImage = NSImage(data: tiffData), - let pngData = nsImage.pngData() - { + let pngData = nsImage.pngData() { return .image(pngData) } diff --git a/Packages/OsaurusCore/Services/Context/PreflightCapabilitySearch.swift b/Packages/OsaurusCore/Services/Context/PreflightCapabilitySearch.swift index a357edf65..e1438a8f8 100644 --- a/Packages/OsaurusCore/Services/Context/PreflightCapabilitySearch.swift +++ b/Packages/OsaurusCore/Services/Context/PreflightCapabilitySearch.swift @@ -384,7 +384,7 @@ enum CapabilitySearch { /// Marker protocol so the env-flag log path can format hits from any /// of the three `*SearchDiagnostic.Hit` types with one helper. -fileprivate protocol DiagnosticHit { +private protocol DiagnosticHit { var name: String { get } var score: Float { get } } @@ -393,7 +393,7 @@ extension ToolSearchDiagnostic.Hit: DiagnosticHit {} extension MethodSearchDiagnostic.Hit: DiagnosticHit {} extension SkillSearchDiagnostic.Hit: DiagnosticHit {} -fileprivate func formatHits(_ hits: [H]) -> String { +private func formatHits(_ hits: [H]) -> String { if hits.isEmpty { return "[]" } return hits @@ -405,7 +405,7 @@ fileprivate func formatHits(_ hits: [H]) -> String { /// renders as `name(bm25=X.XXX|n/a, embed=Y.YYY|n/a, fused=Z.ZZZ)` /// so an engineer reading Console can see at a glance which source /// surfaced the candidate (the `n/a` markers carry the H4/H5 signal). -fileprivate func formatHybridHits(_ hits: [ToolSearchHybridDiagnostic.Hit]) -> String { +private func formatHybridHits(_ hits: [ToolSearchHybridDiagnostic.Hit]) -> String { if hits.isEmpty { return "[]" } return hits diff --git a/Packages/OsaurusCore/Services/Context/PreflightCompanions.swift b/Packages/OsaurusCore/Services/Context/PreflightCompanions.swift index 120fb190e..13829ed2f 100644 --- a/Packages/OsaurusCore/Services/Context/PreflightCompanions.swift +++ b/Packages/OsaurusCore/Services/Context/PreflightCompanions.swift @@ -197,8 +197,7 @@ enum PreflightCompanions { private static func pluginDisplayName(for pluginId: String) -> String { if let loaded = PluginManager.shared.loadedPlugin(for: pluginId), let display = loaded.plugin.manifest.name, - !display.isEmpty - { + !display.isEmpty { return display } return pluginId diff --git a/Packages/OsaurusCore/Services/DirectoryPickerService.swift b/Packages/OsaurusCore/Services/DirectoryPickerService.swift index 789597300..2deaae9d2 100644 --- a/Packages/OsaurusCore/Services/DirectoryPickerService.swift +++ b/Packages/OsaurusCore/Services/DirectoryPickerService.swift @@ -20,9 +20,9 @@ final class DirectoryPickerService: ObservableObject { private var securityScopedResource: URL? // MARK: - Bookmark URL Cache (in-memory, avoids expensive IPC) - private static nonisolated let cacheLock = NSLock() - private static nonisolated(unsafe) var cachedBookmarkURL: URL? - private static nonisolated(unsafe) var cacheInitialized = false + nonisolated private static let cacheLock = NSLock() + nonisolated(unsafe) private static var cachedBookmarkURL: URL? + nonisolated(unsafe) private static var cacheInitialized = false nonisolated private static func invalidateCache() { cacheLock.lock() diff --git a/Packages/OsaurusCore/Services/GitHubSkillService.swift b/Packages/OsaurusCore/Services/GitHubSkillService.swift index e544dcef8..0a0a4a9b4 100644 --- a/Packages/OsaurusCore/Services/GitHubSkillService.swift +++ b/Packages/OsaurusCore/Services/GitHubSkillService.swift @@ -386,8 +386,7 @@ public final class GitHubSkillService: ObservableObject { } if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let defaultBranch = json["default_branch"] as? String - { + let defaultBranch = json["default_branch"] as? String { return GitHubRepo(owner: repo.owner, name: repo.name, branch: defaultBranch) } } catch { diff --git a/Packages/OsaurusCore/Services/HuggingFaceService.swift b/Packages/OsaurusCore/Services/HuggingFaceService.swift index 8c339b9f5..c8917e685 100644 --- a/Packages/OsaurusCore/Services/HuggingFaceService.swift +++ b/Packages/OsaurusCore/Services/HuggingFaceService.swift @@ -241,10 +241,8 @@ actor HuggingFaceService { } // Check for common license identifiers directly in tags let knownLicenses = ["mit", "apache-2.0", "gpl-3.0", "cc-by-4.0", "cc-by-nc-4.0", "llama2", "llama3", "gemma"] - for tag in tags { - if knownLicenses.contains(tag.lowercased()) { - return tag - } + for tag in tags where knownLicenses.contains(tag.lowercased()) { + return tag } return nil } @@ -281,8 +279,7 @@ actor HuggingFaceService { if f == "config.json" { hasConfig = true } if f.hasSuffix(".safetensors") { hasWeights = true } if f == "tokenizer.json" || f == "tokenizer.model" || f == "spiece.model" || f == "vocab.json" - || f == "vocab.txt" - { + || f == "vocab.txt" { hasTokenizer = true } } diff --git a/Packages/OsaurusCore/Services/Inference/FoundationModelService.swift b/Packages/OsaurusCore/Services/Inference/FoundationModelService.swift index 00544e3e0..2b6b015fe 100644 --- a/Packages/OsaurusCore/Services/Inference/FoundationModelService.swift +++ b/Packages/OsaurusCore/Services/Inference/FoundationModelService.swift @@ -117,8 +117,7 @@ actor FoundationModelService: ToolCapableService { } var current = snapshot.content if !stopSequences.isEmpty, - let r = stopSequences.compactMap({ current.range(of: $0)?.lowerBound }).first - { + let r = stopSequences.compactMap({ current.range(of: $0)?.lowerBound }).first { current = String(current[.. Bool { - for msg in messages { - if !msg.imageUrls.isEmpty { return true } + for msg in messages where !msg.imageUrls.isEmpty { + return true } return false } @@ -397,8 +395,7 @@ actor FoundationModelService: ToolCapableService { case .object(let dict): // enum of strings if case .array(let enumVals)? = dict["enum"], - case .string = enumVals.first - { + case .string = enumVals.first { let choices: [String] = enumVals.compactMap { v in if case .string(let s) = v { return s } else { return nil } } @@ -410,7 +407,7 @@ actor FoundationModelService: ToolCapableService { } // type can be string or array - var typeString: String? = nil + var typeString: String? if let t = dict["type"] { switch t { case .string(let s): typeString = s @@ -437,8 +434,7 @@ actor FoundationModelService: ToolCapableService { return DynamicGenerationSchema(type: Bool.self) case "array": if let items = dict["items"], - let itemSchema = dynamicSchema(from: items, name: name + "Item") - { + let itemSchema = dynamicSchema(from: items, name: name + "Item") { let minItems = jsonIntOrNil(dict["minItems"]) let maxItems = jsonIntOrNil(dict["maxItems"]) return DynamicGenerationSchema( @@ -453,7 +449,9 @@ actor FoundationModelService: ToolCapableService { minimumElements: nil, maximumElements: nil ) - case "object": fallthrough + case "object": + _ = () + fallthrough default: // Build object properties var required: Set = [] diff --git a/Packages/OsaurusCore/Services/Inference/MLXService.swift b/Packages/OsaurusCore/Services/Inference/MLXService.swift index fe0596cb5..de56d2eb7 100644 --- a/Packages/OsaurusCore/Services/Inference/MLXService.swift +++ b/Packages/OsaurusCore/Services/Inference/MLXService.swift @@ -39,7 +39,7 @@ actor MLXService: ToolCapableService { return ModelManager.installedModelNames() } - fileprivate nonisolated static func findModel(named name: String) -> LocalModelRef? { + nonisolated fileprivate static func findModel(named name: String) -> LocalModelRef? { if let found = ModelManager.findInstalledModel(named: name) { return LocalModelRef(name: found.name, modelId: found.id) } diff --git a/Packages/OsaurusCore/Services/Inference/ModelService.swift b/Packages/OsaurusCore/Services/Inference/ModelService.swift index a6591b905..cf59fadee 100644 --- a/Packages/OsaurusCore/Services/Inference/ModelService.swift +++ b/Packages/OsaurusCore/Services/Inference/ModelService.swift @@ -122,7 +122,7 @@ public enum StreamingToolHint: Sendable { struct Payload: Encodable { let id, name, arguments, result: String } let json = (try? JSONEncoder().encode(Payload(id: callId, name: name, arguments: arguments, result: result))) - .map { String(decoding: $0, as: UTF8.self) } ?? "{}" + .map { String(bytes: $0, encoding: .utf8) ?? "" } ?? "{}" return donePrefix + json } diff --git a/Packages/OsaurusCore/Services/Keychain/ToolSecretsKeychain.swift b/Packages/OsaurusCore/Services/Keychain/ToolSecretsKeychain.swift index 16767ad5e..4e9e1b064 100644 --- a/Packages/OsaurusCore/Services/Keychain/ToolSecretsKeychain.swift +++ b/Packages/OsaurusCore/Services/Keychain/ToolSecretsKeychain.swift @@ -107,8 +107,7 @@ public enum ToolSecretsKeychain { if let account = item[kSecAttrAccount as String] as? String, account.hasPrefix(accountPrefix), let data = item[kSecValueData as String] as? Data, - let value = String(data: data, encoding: .utf8) - { + let value = String(data: data, encoding: .utf8) { let secretId = String(account.dropFirst(accountPrefix.count)) secrets[secretId] = value } @@ -118,8 +117,7 @@ public enum ToolSecretsKeychain { } public static func hasAllRequiredSecrets(specs: [PluginManifest.SecretSpec], for pluginId: String, agentId: UUID) - -> Bool - { + -> Bool { for spec in specs where spec.required { if !hasSecret(id: spec.id, for: pluginId, agentId: agentId) { return false diff --git a/Packages/OsaurusCore/Services/LocalGenerationDefaults.swift b/Packages/OsaurusCore/Services/LocalGenerationDefaults.swift index 49d40a9f1..532ce2019 100644 --- a/Packages/OsaurusCore/Services/LocalGenerationDefaults.swift +++ b/Packages/OsaurusCore/Services/LocalGenerationDefaults.swift @@ -58,8 +58,8 @@ enum LocalGenerationDefaults { static let empty = Defaults() } - private static nonisolated let lock = NSLock() - private static nonisolated(unsafe) var cache: [String: Defaults] = [:] + nonisolated private static let lock = NSLock() + nonisolated(unsafe) private static var cache: [String: Defaults] = [:] /// Resolve and cache the sampling defaults for `modelId`. The id may be /// either the short picker name or the full `ORG/REPO` identifier; both diff --git a/Packages/OsaurusCore/Services/LocalReasoningCapability.swift b/Packages/OsaurusCore/Services/LocalReasoningCapability.swift index f6d223ede..4b3bc6b4c 100644 --- a/Packages/OsaurusCore/Services/LocalReasoningCapability.swift +++ b/Packages/OsaurusCore/Services/LocalReasoningCapability.swift @@ -38,8 +38,8 @@ enum LocalReasoningCapability { ) } - private static nonisolated let lock = NSLock() - private static nonisolated(unsafe) var cache: [String: Capability] = [:] + nonisolated private static let lock = NSLock() + nonisolated(unsafe) private static var cache: [String: Capability] = [:] static func capability(forModelId modelId: String) -> Capability { let key = modelId.lowercased() @@ -201,21 +201,18 @@ enum LocalReasoningCapability { let fm = FileManager.default let jinja = dir.appendingPathComponent("chat_template.jinja") if fm.fileExists(atPath: jinja.path), - let s = try? String(contentsOf: jinja, encoding: .utf8) - { + let s = try? String(contentsOf: jinja, encoding: .utf8) { return s } let tokenizerCfg = dir.appendingPathComponent("tokenizer_config.json") if fm.fileExists(atPath: tokenizerCfg.path), let data = try? Data(contentsOf: tokenizerCfg), - let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { + let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { if let tmpl = obj["chat_template"] as? String { return tmpl } // HF sometimes ships an array form: [{"name": "default", "template": "..."}] if let arr = obj["chat_template"] as? [[String: Any]], let first = arr.first, - let tmpl = first["template"] as? String - { + let tmpl = first["template"] as? String { return tmpl } } diff --git a/Packages/OsaurusCore/Services/MCP/MCPProviderKeychain.swift b/Packages/OsaurusCore/Services/MCP/MCPProviderKeychain.swift index 89002490c..264ec5b10 100644 --- a/Packages/OsaurusCore/Services/MCP/MCPProviderKeychain.swift +++ b/Packages/OsaurusCore/Services/MCP/MCPProviderKeychain.swift @@ -169,8 +169,7 @@ enum MCPProviderKeychain { for item in items { if let account = item[kSecAttrAccount as String] as? String, - account.hasPrefix(accountPrefix) - { + account.hasPrefix(accountPrefix) { let deleteQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, diff --git a/Packages/OsaurusCore/Services/MCP/MCPServerManager.swift b/Packages/OsaurusCore/Services/MCP/MCPServerManager.swift index 0db39a323..90d1c886d 100644 --- a/Packages/OsaurusCore/Services/MCP/MCPServerManager.swift +++ b/Packages/OsaurusCore/Services/MCP/MCPServerManager.swift @@ -107,7 +107,7 @@ final class MCPServerManager { }() let argsJSON: String = { if let d = argsData { - return String(decoding: d, as: UTF8.self) + return String(bytes: d, encoding: .utf8) ?? "" } return "{}" }() diff --git a/Packages/OsaurusCore/Services/Memory/MemoryService.swift b/Packages/OsaurusCore/Services/Memory/MemoryService.swift index 9ea5f9174..48fc4bc2a 100644 --- a/Packages/OsaurusCore/Services/Memory/MemoryService.swift +++ b/Packages/OsaurusCore/Services/Memory/MemoryService.swift @@ -932,31 +932,26 @@ public actor MemoryService { let trimmed = response.trimmingCharacters(in: .whitespacesAndNewlines) if let data = trimmed.data(using: .utf8), - (try? JSONSerialization.jsonObject(with: data)) != nil - { + (try? JSONSerialization.jsonObject(with: data)) != nil { return data } let fencePattern = #"```(?:json)?\s*\n?([\s\S]*?)```"# if let regex = try? NSRegularExpression(pattern: fencePattern), let match = regex.firstMatch(in: trimmed, range: NSRange(trimmed.startIndex..., in: trimmed)), - let contentRange = Range(match.range(at: 1), in: trimmed) - { + let contentRange = Range(match.range(at: 1), in: trimmed) { let jsonStr = String(trimmed[contentRange]).trimmingCharacters(in: .whitespacesAndNewlines) if let data = jsonStr.data(using: .utf8), - (try? JSONSerialization.jsonObject(with: data)) != nil - { + (try? JSONSerialization.jsonObject(with: data)) != nil { return data } } if let openIdx = trimmed.firstIndex(of: "{"), - let closeIdx = trimmed.lastIndex(of: "}"), closeIdx > openIdx - { + let closeIdx = trimmed.lastIndex(of: "}"), closeIdx > openIdx { let jsonStr = String(trimmed[openIdx ... closeIdx]) if let data = jsonStr.data(using: .utf8), - (try? JSONSerialization.jsonObject(with: data)) != nil - { + (try? JSONSerialization.jsonObject(with: data)) != nil { return data } } diff --git a/Packages/OsaurusCore/Services/ModelDownloadService.swift b/Packages/OsaurusCore/Services/ModelDownloadService.swift index 159ccd0c9..214c70869 100644 --- a/Packages/OsaurusCore/Services/ModelDownloadService.swift +++ b/Packages/OsaurusCore/Services/ModelDownloadService.swift @@ -177,8 +177,7 @@ final class ModelDownloadService: ObservableObject { let refusal = Self.storageRefusalMessage( neededBytes: bytesToDownload, freeBytes: freeBytes - ) - { + ) { await MainActor.run { if self.downloadTokens[model.id] == token { self.downloadStates[model.id] = .failed(error: refusal) @@ -211,8 +210,7 @@ final class ModelDownloadService: ObservableObject { let destination = model.localDirectory.appendingPathComponent(file.path) let baseCompleted = completedFileBytes - let onProgress: @Sendable (Int64, Int64) -> Void = { - [weak self] bytesWritten, _ in + let onProgress: @Sendable (Int64, Int64) -> Void = { [weak self] bytesWritten, _ in Task { @MainActor [weak self] in guard let self else { return } self.updateDownloadProgress( @@ -439,10 +437,9 @@ final class ModelDownloadService: ObservableObject { samples = samples.filter { now - $0.timestamp <= window } progressSamples[modelId] = samples - var speed: Double? = nil + var speed: Double? if let first = samples.first, let last = samples.last, - last.timestamp > first.timestamp - { + last.timestamp > first.timestamp { let bytesDelta = Double(last.completed - first.completed) let timeDelta = last.timestamp - first.timestamp if timeDelta > 0 { speed = max(0, bytesDelta / timeDelta) } @@ -453,7 +450,7 @@ final class ModelDownloadService: ObservableObject { speed = lastKnownSpeed[modelId] } - var eta: Double? = nil + var eta: Double? if let speed, speed > 0, totalBytes > 0 { let remaining = Double(totalBytes - completedBytes) if remaining > 0 { eta = remaining / speed } @@ -686,8 +683,7 @@ private final class DirectDownloader: NSObject, URLSessionDownloadDelegate, @unc guard let continuation, let destination else { return } if let http = downloadTask.response as? HTTPURLResponse, - !(200 ..< 300).contains(http.statusCode) - { + !(200 ..< 300).contains(http.statusCode) { continuation.resume( throwing: URLError( .badServerResponse, diff --git a/Packages/OsaurusCore/Services/ModelRuntime.swift b/Packages/OsaurusCore/Services/ModelRuntime.swift index ddd4c88c1..a6b45f2b6 100644 --- a/Packages/OsaurusCore/Services/ModelRuntime.swift +++ b/Packages/OsaurusCore/Services/ModelRuntime.swift @@ -417,7 +417,7 @@ public actor ModelRuntime { /// by vmlx-swift-lm's `OSAURUS-INTEGRATION.md` (Coordinator-owned KV /// sizing) plus osaurus's per-environment disk-path config. See the /// file-level comment for rationale on each knob. - private nonisolated static func buildCacheCoordinatorConfig( + nonisolated private static func buildCacheCoordinatorConfig( modelName: String ) -> CacheCoordinatorConfig { let diskCacheDir = OsaurusPaths.diskKVCache() @@ -505,7 +505,7 @@ public actor ModelRuntime { /// Best-effort writability probe for the disk cache directory. Uses a /// tempfile round-trip rather than `FileManager.isWritableFile(atPath:)` /// so symlinks / ACLs / out-of-disk conditions are caught. - private nonisolated static func isDirectoryWritable(_ url: URL) -> Bool { + nonisolated private static func isDirectoryWritable(_ url: URL) -> Bool { let probe = url.appendingPathComponent(".osaurus_write_probe_\(UUID().uuidString)") do { try Data().write(to: probe) @@ -563,16 +563,14 @@ public actor ModelRuntime { // Mamba layers and standard KV for the attention layers; the // `SSMStateCache` companion covers the Mamba state. if lower.contains("nemotron-3") || lower.contains("nemotron-cascade") - || lower.contains("nemotron_h") - { + || lower.contains("nemotron_h") { return true } // Qwen 3.5 / 3.6 MoE family (qwen3_5_moe model_type) covers Holo3 too. // vmlx `Models/Qwen35.swift` + `Qwen35JANGTQ.swift` allocate // `ArraysCache` for the linear-attention slots. if lower.contains("qwen3.5") || lower.contains("qwen3.6") || lower.contains("holo3") - || lower.contains("holo-3") - { + || lower.contains("holo-3") { return true } // Qwen3-Next (qwen3_next model_type) — newer hybrid MoE that vmlx @@ -949,7 +947,7 @@ public actor ModelRuntime { /// Computes a deterministic hash from system content and tool names. /// Used by the HTTP API to expose a prefix_hash field in responses. - public nonisolated static func computePrefixHash( + nonisolated public static func computePrefixHash( systemContent: String, toolNames: [String] ) -> String { @@ -1163,8 +1161,7 @@ public actor ModelRuntime { if let commaIndex = urlString.firstIndex(of: ",") { let base64String = String(urlString[urlString.index(after: commaIndex)...]) if let imageData = Data(base64Encoded: base64String), - let ciImage = CIImage(data: imageData) - { + let ciImage = CIImage(data: imageData) { sources.append(.ciImage(ciImage)) } } @@ -1299,13 +1296,10 @@ public actor ModelRuntime { ) else { return 0 } var total: Int64 = 0 - for case let fileURL as URL in enumerator { - if fileURL.pathExtension.lowercased() == "safetensors" { - if let attrs = try? fm.attributesOfItem(atPath: fileURL.path), - let size = attrs[.size] as? NSNumber - { - total += size.int64Value - } + for case let fileURL as URL in enumerator where fileURL.pathExtension.lowercased() == "safetensors" { + if let attrs = try? fm.attributesOfItem(atPath: fileURL.path), + let size = attrs[.size] as? NSNumber { + total += size.int64Value } } return total @@ -1428,8 +1422,7 @@ public actor ModelRuntime { try validateJANGTQSidecarIfRequired(at: directory, name: name) return } catch let error as NSError - where error.domain == "ModelRuntime" && error.code == 2 - { + where error.domain == "ModelRuntime" && error.code == 2 { // Forward mismatch: stamp says mxtq, sidecar missing. Try one HF fetch. // Build the candidate id list: canonical `/` first, // then — for flat-layout local ids that aren't directly mappable @@ -1658,7 +1651,7 @@ public actor ModelRuntime { /// global, and so each test's override is naturally scoped to its own /// task tree via `withValue { ... }`. @TaskLocal - static var sidecarFetcherForTests: (@Sendable (_ url: URL, _ dest: URL) async throws -> Void)? = nil + static var sidecarFetcherForTests: (@Sendable (_ url: URL, _ dest: URL) async throws -> Void)? /// Pure, testable sibling of `findLocalDirectory` that takes the root /// explicitly. Exposed at module scope so the symlink-resolution @@ -1683,8 +1676,7 @@ public actor ModelRuntime { let resolved = url.resolvingSymlinksInPath() let hasConfig = fm.fileExists(atPath: resolved.appendingPathComponent("config.json").path) if let items = try? fm.contentsOfDirectory(at: resolved, includingPropertiesForKeys: nil), - hasConfig && items.contains(where: { $0.pathExtension == "safetensors" }) - { + hasConfig && items.contains(where: { $0.pathExtension == "safetensors" }) { return resolved } return nil diff --git a/Packages/OsaurusCore/Services/ModelRuntime/MLXErrorRecovery.swift b/Packages/OsaurusCore/Services/ModelRuntime/MLXErrorRecovery.swift index 4f57e165d..1d2737616 100644 --- a/Packages/OsaurusCore/Services/ModelRuntime/MLXErrorRecovery.swift +++ b/Packages/OsaurusCore/Services/ModelRuntime/MLXErrorRecovery.swift @@ -65,9 +65,7 @@ public enum MLXErrorRecovery { } // C-convention closure: no captures (must match `@convention(c)`). - let handler: @convention(c) (UnsafePointer?, UnsafeMutableRawPointer?) -> Void = { - cMessage, - _ in + let handler: @convention(c) (UnsafePointer?, UnsafeMutableRawPointer?) -> Void = { cMessage, _ in let message = cMessage.map { String(cString: $0) } ?? "" MLXErrorRecovery.lock.withLock { MLXErrorRecovery._lastError = message diff --git a/Packages/OsaurusCore/Services/ModelRuntime/RollingTokenRate.swift b/Packages/OsaurusCore/Services/ModelRuntime/RollingTokenRate.swift index 2094272e9..4cad9fb03 100644 --- a/Packages/OsaurusCore/Services/ModelRuntime/RollingTokenRate.swift +++ b/Packages/OsaurusCore/Services/ModelRuntime/RollingTokenRate.swift @@ -144,7 +144,7 @@ struct RollingTokenRate: Sendable { /// The denominator is floored at `min(windowSeconds, now - firstAt)` so /// a paused stream doesn't get penalised for the gap. func currentRate(at now: Date = Date()) -> Double? { - guard let firstAt, let _ = lastAt else { return nil } + guard let firstAt, lastAt != nil else { return nil } let elapsedFromFirst = now.timeIntervalSince(firstAt) guard elapsedFromFirst >= Self.warmupSeconds, totalTokens >= Self.warmupTokens else { return nil } diff --git a/Packages/OsaurusCore/Services/NotificationService.swift b/Packages/OsaurusCore/Services/NotificationService.swift index b80ae34dd..0a0b7e961 100644 --- a/Packages/OsaurusCore/Services/NotificationService.swift +++ b/Packages/OsaurusCore/Services/NotificationService.swift @@ -18,7 +18,7 @@ final class NotificationService: NSObject, UNUserNotificationCenterDelegate { private let categoryId = "OSU_MODEL_READY" private let actionOpenId = "OSU_OPEN_MODELS" - private override init() { + override private init() { super.init() } diff --git a/Packages/OsaurusCore/Services/Plugin/PluginHostAPI+SessionPersistence.swift b/Packages/OsaurusCore/Services/Plugin/PluginHostAPI+SessionPersistence.swift index dc38a6c8a..af6617e20 100644 --- a/Packages/OsaurusCore/Services/Plugin/PluginHostAPI+SessionPersistence.swift +++ b/Packages/OsaurusCore/Services/Plugin/PluginHostAPI+SessionPersistence.swift @@ -47,8 +47,7 @@ extension PluginHostContext { @MainActor private static func pluginDisplayName(for pluginId: String) async -> String { if let manifestName = PluginManager.shared.loadedPlugin(for: pluginId)?.plugin - .manifest.name, !manifestName.isEmpty - { + .manifest.name, !manifestName.isEmpty { return manifestName } return pluginId diff --git a/Packages/OsaurusCore/Services/Plugin/PluginHostAPI.swift b/Packages/OsaurusCore/Services/Plugin/PluginHostAPI.swift index b75d19425..51ba01bc4 100644 --- a/Packages/OsaurusCore/Services/Plugin/PluginHostAPI.swift +++ b/Packages/OsaurusCore/Services/Plugin/PluginHostAPI.swift @@ -23,7 +23,7 @@ final class PluginHostContext: @unchecked Sendable { // MARK: - Context Registry (thread-safe) - private nonisolated(unsafe) static var contexts: [String: PluginHostContext] = [:] + nonisolated(unsafe) private static var contexts: [String: PluginHostContext] = [:] private static let contextsLock = NSLock() static func getContext(for pluginId: String) -> PluginHostContext? { @@ -540,8 +540,7 @@ final class PluginHostContext: @unchecked Sendable { if let agentId = agentCtx?.agentId, let agent = AgentManager.shared.agent(for: agentId), let override = agent.pluginInstructions?[pid], - !override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - { + !override.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return override } return PluginManager.shared.loadedPlugin(for: pid)?.plugin.manifest.instructions @@ -564,8 +563,7 @@ final class PluginHostContext: @unchecked Sendable { // Skills inject in BOTH modes — see the matching block in // `SystemPromptComposer.compose` for the full rationale. if !agentToolsOff, - let section = await SkillManager.shared.enabledSkillPromptSection(for: resolvedAgentId) - { + let section = await SkillManager.shared.enabledSkillPromptSection(for: resolvedAgentId) { SystemPromptComposer.appendSystemContent(section, into: &enriched.request.messages) } @@ -648,8 +646,7 @@ final class PluginHostContext: @unchecked Sendable { var model = request.model let trimmed = model.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.isEmpty || trimmed.caseInsensitiveCompare("default") == .orderedSame, - let agentModel = ctx.model, !agentModel.isEmpty - { + let agentModel = ctx.model, !agentModel.isEmpty { model = agentModel } @@ -1077,8 +1074,7 @@ final class PluginHostContext: @unchecked Sendable { if let calls = choice.message.tool_calls, !calls.isEmpty, choice.finish_reason == "tool_calls", - iteration < prep.options.maxIterations - { + iteration < prep.options.maxIterations { // The non-streaming path already appends the full // assistant message (with all tool_calls) once, // then appends only the tool-result messages per call. @@ -2247,7 +2243,7 @@ extension PluginHostContext { /// Serialize a dictionary to a JSON string. Falls back to "{}" on encoding failure. static func jsonString(_ dict: [String: Any]) -> String { guard let data = try? JSONSerialization.data(withJSONObject: dict, options: []) else { return "{}" } - return String(decoding: data, as: UTF8.self) + return String(bytes: data, encoding: .utf8) ?? "" } /// Parse a JSON string back into a dictionary. @@ -2292,7 +2288,7 @@ extension PluginHostContext { /// have TLS set. Protected by `fallbackLock` to avoid data races under /// concurrent execution. TLS (option 1) is the authoritative mechanism. private static let fallbackLock = NSLock() - private nonisolated(unsafe) static var _lastDispatchedPluginId: String? + nonisolated(unsafe) private static var _lastDispatchedPluginId: String? private static var lastDispatchedPluginId: String? { get { fallbackLock.withLock { _lastDispatchedPluginId } } diff --git a/Packages/OsaurusCore/Services/Plugin/PluginRepositoryService.swift b/Packages/OsaurusCore/Services/Plugin/PluginRepositoryService.swift index 8862bd7cf..3e40150fe 100644 --- a/Packages/OsaurusCore/Services/Plugin/PluginRepositoryService.swift +++ b/Packages/OsaurusCore/Services/Plugin/PluginRepositoryService.swift @@ -246,7 +246,7 @@ final class PluginRepositoryService: ObservableObject { license: spec.license, capabilities: spec.capabilities, installedVersion: InstalledPluginsStore.shared.latestInstalledVersion(pluginId: spec.plugin_id), - latestVersion: spec.versions.map(\.version).sorted(by: >).first, + latestVersion: spec.versions.map(\.version).max(), loadError: PluginManager.shared.loadError(for: spec.plugin_id) ) } diff --git a/Packages/OsaurusCore/Services/Provider/OpenAICodexOAuthService.swift b/Packages/OsaurusCore/Services/Provider/OpenAICodexOAuthService.swift index dae726b52..d25f140a8 100644 --- a/Packages/OsaurusCore/Services/Provider/OpenAICodexOAuthService.swift +++ b/Packages/OsaurusCore/Services/Provider/OpenAICodexOAuthService.swift @@ -146,8 +146,7 @@ public enum OpenAICodexOAuthService { } public static func exchangeAuthorizationCode(_ code: String, verifier: String) async throws - -> RemoteProviderOAuthTokens - { + -> RemoteProviderOAuthTokens { try await requestTokens( form: [ "grant_type": "authorization_code", diff --git a/Packages/OsaurusCore/Services/Provider/RemoteProviderKeychain.swift b/Packages/OsaurusCore/Services/Provider/RemoteProviderKeychain.swift index 56da0b61c..56aa1ab38 100644 --- a/Packages/OsaurusCore/Services/Provider/RemoteProviderKeychain.swift +++ b/Packages/OsaurusCore/Services/Provider/RemoteProviderKeychain.swift @@ -251,8 +251,7 @@ public enum RemoteProviderKeychain { for item in items { if let account = item[kSecAttrAccount as String] as? String, - account.hasPrefix(accountPrefix) - { + account.hasPrefix(accountPrefix) { let deleteQuery: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, diff --git a/Packages/OsaurusCore/Services/Provider/RemoteProviderService.swift b/Packages/OsaurusCore/Services/Provider/RemoteProviderService.swift index f0dabe4ae..fe7d3fee9 100644 --- a/Packages/OsaurusCore/Services/Provider/RemoteProviderService.swift +++ b/Packages/OsaurusCore/Services/Provider/RemoteProviderService.swift @@ -463,8 +463,7 @@ public actor RemoteProviderService: ToolCapableService { // Decode the line as UTF-8. SSE field names and the optional space after // the colon are ASCII; lossy decoding is safe for any non-UTF-8 bytes // that would only appear inside the value portion. - // swiftlint:disable:next optional_data_string_conversion - let lineStr = String(decoding: line, as: UTF8.self) + let lineStr = String(bytes: line, encoding: .utf8) ?? "" // Comment line — entire line starts with ":" (no field name). if lineStr.first == ":" { return } @@ -939,8 +938,7 @@ public actor RemoteProviderService: ToolCapableService { case "message_delta": if let deltaEvent = try? JSONDecoder().decode(MessageDeltaEvent.self, from: jsonData), - let stopReason = deltaEvent.delta.stop_reason - { + let stopReason = deltaEvent.delta.stop_reason { state.lastFinishReason = stopReason } @@ -983,8 +981,7 @@ public actor RemoteProviderService: ToolCapableService { case "response.output_item.added": if let addedEvent = try? JSONDecoder().decode(OutputItemAddedEvent.self, from: jsonData), - case .functionCall(let funcCall) = addedEvent.item - { + case .functionCall(let funcCall) = addedEvent.item { let idx = addedEvent.output_index state.accumulatedToolCalls[idx] = ( id: funcCall.call_id, name: funcCall.name, args: "", thoughtSignature: nil @@ -1027,8 +1024,7 @@ public actor RemoteProviderService: ToolCapableService { // Final confirmed item — extract args from the completed function_call // when no `.delta` events landed first (common for short calls). if let doneEvent = try? JSONDecoder().decode(OutputItemDoneEvent.self, from: jsonData), - case .functionCall(let funcCall) = doneEvent.item - { + case .functionCall(let funcCall) = doneEvent.item { let idx = doneEvent.output_index var current = state.accumulatedToolCalls[idx] ?? ( @@ -1099,16 +1095,14 @@ public actor RemoteProviderService: ToolCapableService { // it in the Think panel — without ever emitting `` literals. if state.accumulatedToolCalls.isEmpty, let reasoning = chunk.choices.first?.delta.reasoning_content, - !reasoning.isEmpty - { + !reasoning.isEmpty { yield(StreamingReasoningHint.encode(reasoning)) } // Only yield content if no tool calls have been detected, to avoid // function-call JSON leaking into the chat UI. if state.accumulatedToolCalls.isEmpty, - let delta = chunk.choices.first?.delta.content, !delta.isEmpty - { + let delta = chunk.choices.first?.delta.content, !delta.isEmpty { let (truncated, hitStop) = applyStopSequences(delta, stopSequences: state.stopSequences) state.recordYield(truncated) yield(truncated) @@ -1161,8 +1155,7 @@ public actor RemoteProviderService: ToolCapableService { let (name, args) = RemoteToolDetection.detectInlineToolCall( in: state.accumulatedContent, tools: tools - ) - { + ) { print("[Osaurus] Fallback: detected inline tool call '\(name)' in text") continuation.finish( throwing: ServiceToolInvocation( @@ -1340,8 +1333,7 @@ public actor RemoteProviderService: ToolCapableService { private static func geminiArgsJSON(from args: [String: AnyCodableValue]?) -> String { let dict = (args ?? [:]).mapValues { $0.value } if let data = try? JSONSerialization.data(withJSONObject: dict), - let s = String(data: data, encoding: .utf8) - { + let s = String(data: data, encoding: .utf8) { return s } return "{}" @@ -1477,8 +1469,7 @@ public actor RemoteProviderService: ToolCapableService { // Quick validation: try to parse as-is. if let data = trimmed.data(using: .utf8), - (try? JSONSerialization.jsonObject(with: data)) != nil - { + (try? JSONSerialization.jsonObject(with: data)) != nil { return ValidatedToolCallJSON(json: trimmed, wasRepaired: false) } @@ -1549,8 +1540,7 @@ public actor RemoteProviderService: ToolCapableService { // Verify the repair worked if let data = repaired.data(using: .utf8), - (try? JSONSerialization.jsonObject(with: data)) != nil - { + (try? JSONSerialization.jsonObject(with: data)) != nil { print("[Osaurus] Repaired incomplete tool call JSON (\(json.count) -> \(repaired.count) chars)") return ValidatedToolCallJSON(json: repaired, wasRepaired: true) } @@ -2063,8 +2053,7 @@ public actor RemoteProviderService: ToolCapableService { if let altRange = Range(match.range(at: 1), in: text), let mimeRange = Range(match.range(at: 2), in: text), - let dataRange = Range(match.range(at: 3), in: text) - { + let dataRange = Range(match.range(at: 3), in: text) { let altText = String(text[altRange]) let sig: String? = altText.hasPrefix("image|ts:") @@ -2272,8 +2261,7 @@ struct RemoteChatRequest: Encodable { var input: [String: AnyCodableValue] = [:] if let argsData = toolCall.function.arguments.data(using: .utf8), - let argsDict = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] - { + let argsDict = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] { input = argsDict.mapValues { AnyCodableValue($0) } } @@ -2404,8 +2392,7 @@ struct RemoteChatRequest: Encodable { // Parse data URLs: "data:;base64," if url.hasPrefix("data:"), let semicolonIdx = url.firstIndex(of: ";"), - let commaIdx = url.firstIndex(of: ",") - { + let commaIdx = url.firstIndex(of: ",") { let mimeType = String(url[url.index(url.startIndex, offsetBy: 5) ..< semicolonIdx]) let base64Data = String(url[url.index(after: commaIdx)...]) userParts.append( @@ -2435,8 +2422,7 @@ struct RemoteChatRequest: Encodable { for toolCall in toolCalls { var args: [String: AnyCodableValue] = [:] if let argsData = toolCall.function.arguments.data(using: .utf8), - let argsDict = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] - { + let argsDict = try? JSONSerialization.jsonObject(with: argsData) as? [String: Any] { args = argsDict.mapValues { AnyCodableValue($0) } } parts.append( @@ -2464,8 +2450,7 @@ struct RemoteChatRequest: Encodable { // Try to parse the content as JSON first if let data = content.data(using: .utf8), - let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] - { + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { responseData = json.mapValues { AnyCodableValue($0) } } else { responseData["result"] = AnyCodableValue(content) @@ -2539,8 +2524,7 @@ struct RemoteChatRequest: Encodable { var generationConfig: GeminiGenerationConfig? if temperature != nil || max_completion_tokens != nil || top_p != nil || stop != nil - || responseModalities != nil || imageConfig != nil - { + || responseModalities != nil || imageConfig != nil { generationConfig = GeminiGenerationConfig( temperature: temperature.map { Double($0) }, maxOutputTokens: max_completion_tokens, @@ -2789,8 +2773,7 @@ extension RemoteProviderService { if let (data, response) = try? await URLSession.shared.data(for: req), let http = response as? HTTPURLResponse, http.statusCode < 400, let parsed = try? JSONDecoder().decode(ModelsResponse.self, from: data), - !parsed.data.isEmpty - { + !parsed.data.isEmpty { return parsed.data.map { $0.id } } } diff --git a/Packages/OsaurusCore/Services/Provider/RemoteToolDetection.swift b/Packages/OsaurusCore/Services/Provider/RemoteToolDetection.swift index 1f57fe9dd..8da2e7fb1 100644 --- a/Packages/OsaurusCore/Services/Provider/RemoteToolDetection.swift +++ b/Packages/OsaurusCore/Services/Provider/RemoteToolDetection.swift @@ -34,8 +34,7 @@ enum RemoteToolDetection { // Fast path: Qwen-style ... XML wrapper. if let openRange = window.range(of: "", options: .backwards), let closeRange = window.range(of: "", range: openRange.upperBound ..< window.endIndex), - openRange.upperBound <= closeRange.lowerBound - { + openRange.upperBound <= closeRange.lowerBound { let inner = String(window[openRange.upperBound ..< closeRange.lowerBound]) .trimmingCharacters(in: .whitespacesAndNewlines) if let (name, argsJSON) = extractToolCall(fromJSON: inner), toolNames.contains(name) { @@ -46,13 +45,11 @@ enum RemoteToolDetection { // General path: search for a JSON object containing a known tool name field. for name in toolNames { if let range = window.range(of: #""name"\s*:\s*"\#(name)""#, options: [.regularExpression]) - ?? window.range(of: #""tool_name"\s*:\s*"\#(name)""#, options: [.regularExpression]) - { + ?? window.range(of: #""tool_name"\s*:\s*"\#(name)""#, options: [.regularExpression]) { if let jsonRange = findEnclosingJSONObject(around: range.lowerBound, in: window) { let candidate = String(window[jsonRange]) if let (detectedName, argsJSON) = extractToolCall(fromJSON: candidate), - toolNames.contains(detectedName) - { + toolNames.contains(detectedName) { return (detectedName, argsJSON) } } @@ -119,8 +116,7 @@ enum RemoteToolDetection { if let argsString = function["arguments"] as? String { return (name, argsString) } if let argsObj = function["arguments"], let argsData = try? JSONSerialization.data(withJSONObject: argsObj), - let argsJSON = String(data: argsData, encoding: .utf8) - { + let argsJSON = String(data: argsData, encoding: .utf8) { return (name, argsJSON) } } @@ -128,8 +124,7 @@ enum RemoteToolDetection { if let argsString = obj["arguments"] as? String { return (name, argsString) } if let argsObj = obj["arguments"], let argsData = try? JSONSerialization.data(withJSONObject: argsObj), - let argsJSON = String(data: argsData, encoding: .utf8) - { + let argsJSON = String(data: argsData, encoding: .utf8) { return (name, argsJSON) } } @@ -137,8 +132,7 @@ enum RemoteToolDetection { if let argsString = obj["arguments"] as? String { return (name, argsString) } if let argsObj = obj["arguments"], let argsData = try? JSONSerialization.data(withJSONObject: argsObj), - let argsJSON = String(data: argsData, encoding: .utf8) - { + let argsJSON = String(data: argsData, encoding: .utf8) { return (name, argsJSON) } } diff --git a/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift b/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift index 4de0a012a..2f5ff9ef9 100644 --- a/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift +++ b/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift @@ -600,11 +600,11 @@ public func info() async -> ContainerInfo { let currentStatus = refreshStatus() var users: [String] = [] - var disk: String? = nil - var uptime: String? = nil - var memoryUsage: String? = nil - var cpuLoad: String? = nil - var processCount: Int? = nil + var disk: String? + var uptime: String? + var memoryUsage: String? + var cpuLoad: String? + var processCount: Int? if currentStatus.isRunning { if let result = try? await exec(command: "awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd") { @@ -1313,13 +1313,11 @@ private static func friendlyError(from error: Error) -> Error { let nsError = error as NSError if nsError.domain == NSCocoaErrorDomain, - let message = startFailureHints.cocoa[nsError.code] - { + let message = startFailureHints.cocoa[nsError.code] { return SandboxError.startFailed(message) } if nsError.domain == NSPOSIXErrorDomain, - let message = startFailureHints.posix[Int32(nsError.code)] - { + let message = startFailureHints.posix[Int32(nsError.code)] { return SandboxError.startFailed(message) } let desc = String(describing: error) @@ -1455,8 +1453,7 @@ let lines = lock.withLock { if !lineBuffer.isEmpty, let s = String(data: lineBuffer, encoding: .utf8), - !s.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - { + !s.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { pendingLines.append(s) } lineBuffer.removeAll() @@ -1480,8 +1477,7 @@ var start = lineBuffer.startIndex for i in lineBuffer.indices where lineBuffer[i] == newline { if i > start, - let line = String(data: lineBuffer[start ..< i], encoding: .utf8) - { + let line = String(data: lineBuffer[start ..< i], encoding: .utf8) { pendingLines.append(line) } start = lineBuffer.index(after: i) diff --git a/Packages/OsaurusCore/Services/SystemMonitorService.swift b/Packages/OsaurusCore/Services/SystemMonitorService.swift index b3efb9ce0..63f300e72 100644 --- a/Packages/OsaurusCore/Services/SystemMonitorService.swift +++ b/Packages/OsaurusCore/Services/SystemMonitorService.swift @@ -106,7 +106,7 @@ class SystemMonitorService: ObservableObject { let userDiff = userTicks - previousUserTicks let systemDiff = systemTicks - previousSystemTicks - let _ = idleTicks - previousIdleTicks + _ = idleTicks - previousIdleTicks let niceDiff = niceTicks - previousNiceTicks let totalDiff = totalTicks - previousTotalTicks @@ -128,7 +128,7 @@ class SystemMonitorService: ObservableObject { MemoryLayout.size / MemoryLayout.size ) - let _ = withUnsafeMutablePointer(to: &info) { + _ = withUnsafeMutablePointer(to: &info) { $0.withMemoryRebound(to: integer_t.self, capacity: 1) { task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) } @@ -153,8 +153,8 @@ class SystemMonitorService: ObservableObject { let totalMemory = Double(ProcessInfo.processInfo.physicalMemory) let freeMemory = Double(vmInfo.free_count) * Double(pageSize) let inactiveMemory = Double(vmInfo.inactive_count) * Double(pageSize) - let _ = Double(vmInfo.wire_count) * Double(pageSize) - let _ = Double(vmInfo.compressor_page_count) * Double(pageSize) + _ = Double(vmInfo.wire_count) * Double(pageSize) + _ = Double(vmInfo.compressor_page_count) * Double(pageSize) let usedMemory = totalMemory - freeMemory - inactiveMemory let percentage = (usedMemory / totalMemory) * 100.0 diff --git a/Packages/OsaurusCore/Services/Tool/ToolSearchService.swift b/Packages/OsaurusCore/Services/Tool/ToolSearchService.swift index 9f86015f6..463e4fd20 100644 --- a/Packages/OsaurusCore/Services/Tool/ToolSearchService.swift +++ b/Packages/OsaurusCore/Services/Tool/ToolSearchService.swift @@ -510,8 +510,7 @@ public actor ToolSearchService { for (key, value) in properties { parts.append(key) if case .object(let propSchema) = value, - case .string(let desc) = propSchema["description"] - { + case .string(let desc) = propSchema["description"] { parts.append(desc) } } diff --git a/Packages/OsaurusCore/Services/UpdaterService.swift b/Packages/OsaurusCore/Services/UpdaterService.swift index 64cd90902..2502c2fce 100644 --- a/Packages/OsaurusCore/Services/UpdaterService.swift +++ b/Packages/OsaurusCore/Services/UpdaterService.swift @@ -20,7 +20,7 @@ final class UpdaterViewModel: NSObject, ObservableObject, SPUUpdaterDelegate { // MARK: - Published State for Update Availability @Published var updateAvailable: Bool = false - @Published var availableVersion: String? = nil + @Published var availableVersion: String? @Published var isBetaChannel: Bool { didSet { diff --git a/Packages/OsaurusCore/Services/Voice/TranscriptionCleanupService.swift b/Packages/OsaurusCore/Services/Voice/TranscriptionCleanupService.swift index 3670b3b96..1c7453444 100644 --- a/Packages/OsaurusCore/Services/Voice/TranscriptionCleanupService.swift +++ b/Packages/OsaurusCore/Services/Voice/TranscriptionCleanupService.swift @@ -132,8 +132,7 @@ public final class TranscriptionCleanupService { // MARK: - Shared post-processing - private func postProcess(response: String, rawText: String, trimmed: String, start: Date, source: String) -> String - { + private func postProcess(response: String, rawText: String, trimmed: String, start: Date, source: String) -> String { let elapsed = Date().timeIntervalSince(start) debugLog( "[cleanup] \(source) response in \(String(format: "%.2f", elapsed))s (\(response.count) chars): \(response)" @@ -154,8 +153,7 @@ public final class TranscriptionCleanupService { return rawText } if trimmed.count > 50, - Double(cleaned.count) / Double(trimmed.count) < Self.minHallucinationRatio - { + Double(cleaned.count) / Double(trimmed.count) < Self.minHallucinationRatio { debugLog( "[cleanup] FALLBACK: hallucination guard (cleaned \(cleaned.count) / raw \(trimmed.count) = \(String(format: "%.2f", Double(cleaned.count) / Double(trimmed.count)))), using raw" ) diff --git a/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift b/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift index 4ed16d244..513d3f838 100644 --- a/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift +++ b/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift @@ -477,10 +477,8 @@ public final class ChatHistoryDatabase: @unchecked Sendable { // Best-effort GC. We re-check each hash against the surviving // rows; anything still referenced stays. - for hash in ownedRefs { - if !isBlobReferenced(hash) { - AttachmentBlobStore.delete(hash) - } + for hash in ownedRefs where !isBlobReferenced(hash) { + AttachmentBlobStore.delete(hash) } } diff --git a/Packages/OsaurusCore/Storage/MemoryDatabase.swift b/Packages/OsaurusCore/Storage/MemoryDatabase.swift index 6452ceba7..888f77c2c 100644 --- a/Packages/OsaurusCore/Storage/MemoryDatabase.swift +++ b/Packages/OsaurusCore/Storage/MemoryDatabase.swift @@ -1077,7 +1077,7 @@ public final class MemoryDatabase: @unchecked Sendable { public func bumpPinnedFactUsage(ids: [String]) throws { guard !ids.isEmpty else { return } - let placeholders = ids.enumerated().map { "?\($0.offset + 1)" }.joined(separator: ",") + let placeholders = ids.indices.map { "?\($0 + 1)" }.joined(separator: ",") _ = try executeUpdate( """ UPDATE pinned_facts @@ -1152,7 +1152,7 @@ public final class MemoryDatabase: @unchecked Sendable { public func loadPinnedFactsByIds(_ ids: [String]) throws -> [PinnedFact] { guard !ids.isEmpty else { return [] } - let placeholders = ids.enumerated().map { "?\($0.offset + 1)" }.joined(separator: ",") + let placeholders = ids.indices.map { "?\($0 + 1)" }.joined(separator: ",") let sql = """ SELECT \(Self.pinnedColumns) FROM pinned_facts @@ -1441,7 +1441,7 @@ public final class MemoryDatabase: @unchecked Sendable { public func loadEpisodesByIds(_ ids: [Int]) throws -> [Episode] { guard !ids.isEmpty else { return [] } - let placeholders = ids.enumerated().map { "?\($0.offset + 1)" }.joined(separator: ",") + let placeholders = ids.indices.map { "?\($0 + 1)" }.joined(separator: ",") let sql = """ SELECT \(Self.episodeColumns) FROM episodes WHERE status = 'active' AND id IN (\(placeholders)) diff --git a/Packages/OsaurusCore/Storage/PluginDatabase.swift b/Packages/OsaurusCore/Storage/PluginDatabase.swift index 57ff1c7bc..87b892bbc 100644 --- a/Packages/OsaurusCore/Storage/PluginDatabase.swift +++ b/Packages/OsaurusCore/Storage/PluginDatabase.swift @@ -263,8 +263,7 @@ final class PluginDatabase: @unchecked Sendable { sqlite3_bind_int(stmt, idx, boolVal ? 1 : 0) default: if let jsonData = try? JSONSerialization.data(withJSONObject: param), - let jsonStr = String(data: jsonData, encoding: .utf8) - { + let jsonStr = String(data: jsonData, encoding: .utf8) { sqlite3_bind_text(stmt, idx, (jsonStr as NSString).utf8String, -1, Self.sqliteTransient) } else { sqlite3_bind_null(stmt, idx) diff --git a/Packages/OsaurusCore/Storage/StorageMigrator.swift b/Packages/OsaurusCore/Storage/StorageMigrator.swift index 6713f68eb..6daf0d1fc 100644 --- a/Packages/OsaurusCore/Storage/StorageMigrator.swift +++ b/Packages/OsaurusCore/Storage/StorageMigrator.swift @@ -691,8 +691,7 @@ public actor StorageMigrator { // pre-encryption backup as a last resort. if !fm.fileExists(atPath: plaintextURL.path), let src = backupIndex[plaintextURL.lastPathComponent], - let _ = try? fm.copyItem(at: src, to: plaintextURL) - { + (try? fm.copyItem(at: src, to: plaintextURL)) != nil { try? fm.removeItem(at: url) restored += 1 log.warning( diff --git a/Packages/OsaurusCore/Tests/Sandbox/SandboxInstallLockTests.swift b/Packages/OsaurusCore/Tests/Sandbox/SandboxInstallLockTests.swift index 2aaa898d9..a60e85f14 100644 --- a/Packages/OsaurusCore/Tests/Sandbox/SandboxInstallLockTests.swift +++ b/Packages/OsaurusCore/Tests/Sandbox/SandboxInstallLockTests.swift @@ -18,36 +18,35 @@ import Testing struct SandboxInstallLockTests { /// Two `serialize(agentName:)` calls on the same key run one after - /// the other: the second body must not start before the first - /// finishes. We assert that by recording the pre/post timestamps - /// of each body and checking the second's start is ≥ the first's - /// end. + /// the other: the second body must not start while the first body is + /// still inside the lock. @Test func sameAgent_runsSequentially() async throws { let lock = SandboxInstallLock() - let timeline = ActorTimeline() + let gate = SameAgentGate() let agentName = "agent-A" - // Kick off two operations concurrently. Both want the same lock. - // The second one MUST wait for the first to finish. - async let first: Void = lock.serialize(agentName: agentName) { - await timeline.markStart("first") - try? await Task.sleep(nanoseconds: 50_000_000) // 50ms - await timeline.markEnd("first") + let first = Task { + try await lock.serialize(agentName: agentName) { + await gate.markFirstStarted() + await gate.waitForRelease() + } } - async let second: Void = lock.serialize(agentName: agentName) { - await timeline.markStart("second") - try? await Task.sleep(nanoseconds: 10_000_000) // 10ms - await timeline.markEnd("second") + + await gate.waitUntilFirstStarted() + + let second = Task { + try await lock.serialize(agentName: agentName) { + await gate.markSecondStarted() + } } - _ = try await (first, second) - let firstEnd = try #require(await timeline.endOf("first")) - let secondStart = try #require(await timeline.startOf("second")) - #expect( - secondStart >= firstEnd, - "second op started before first finished — serialization broken" - ) + try await Task.sleep(nanoseconds: 20_000_000) // Give a broken lock time to admit the second body. + #expect(await !gate.hasSecondStarted, "second op started while first still held the lock") + + await gate.releaseFirst() + _ = try await (first.value, second.value) + #expect(await gate.hasSecondStarted, "second op never ran after first released the lock") } /// Two `serialize(agentName:)` calls on DIFFERENT keys must run @@ -120,17 +119,48 @@ struct SandboxInstallLockTests { // MARK: - Test helpers -/// Tiny actor that records start/end Dates for named operations. Lets -/// the sequential-ordering assertion above check the timeline without -/// fighting Sendable semantics on a mutable struct. -private actor ActorTimeline { - private var starts: [String: Date] = [:] - private var ends: [String: Date] = [:] - - func markStart(_ key: String) { starts[key] = Date() } - func markEnd(_ key: String) { ends[key] = Date() } - func startOf(_ key: String) -> Date? { starts[key] } - func endOf(_ key: String) -> Date? { ends[key] } +/// Coordinates the same-agent serialization test without relying on +/// `async let` scheduling order or wall-clock timestamp comparisons. +private actor SameAgentGate { + private var firstStarted = false + private var secondStarted = false + private var released = false + private var firstStartedWaiters: [CheckedContinuation] = [] + private var releaseWaiters: [CheckedContinuation] = [] + + var hasSecondStarted: Bool { secondStarted } + + func markFirstStarted() { + firstStarted = true + let waiters = firstStartedWaiters + firstStartedWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func waitUntilFirstStarted() async { + if firstStarted { return } + await withCheckedContinuation { continuation in + firstStartedWaiters.append(continuation) + } + } + + func waitForRelease() async { + if released { return } + await withCheckedContinuation { continuation in + releaseWaiters.append(continuation) + } + } + + func releaseFirst() { + released = true + let waiters = releaseWaiters + releaseWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + + func markSecondStarted() { + secondStarted = true + } } /// One-shot Sendable bool flag. Lets a `@Sendable` closure mark diff --git a/Packages/OsaurusCore/Tools/AgentLoopTools.swift b/Packages/OsaurusCore/Tools/AgentLoopTools.swift index db3eed5f3..06e975395 100644 --- a/Packages/OsaurusCore/Tools/AgentLoopTools.swift +++ b/Packages/OsaurusCore/Tools/AgentLoopTools.swift @@ -355,8 +355,7 @@ public final class ClarifyTool: OsaurusTool, @unchecked Sendable { let options: [String] if let raw = dict["options"], !(raw is NSNull), - let arr = ArgumentCoercion.stringArray(raw) - { + let arr = ArgumentCoercion.stringArray(raw) { // Cap defensively even if the tool already validated — the // intercept sees pre-validated args, but tests and other // call sites might not. diff --git a/Packages/OsaurusCore/Tools/BuiltinSandboxTools.swift b/Packages/OsaurusCore/Tools/BuiltinSandboxTools.swift index e071f2e5a..6c5dcd2ef 100644 --- a/Packages/OsaurusCore/Tools/BuiltinSandboxTools.swift +++ b/Packages/OsaurusCore/Tools/BuiltinSandboxTools.swift @@ -1995,8 +1995,7 @@ private struct SandboxInstallTool: OsaurusTool, @unchecked Sendable { agentName: SandboxInstallLock.apkSerializationKey ) { @Sendable func runAsRoot(_ cmd: String, timeout: TimeInterval) async throws - -> ContainerExecResult - { + -> ContainerExecResult { try await SandboxToolCommandRunnerRegistry.shared.execAsRoot( command: cmd, timeout: timeout, @@ -2092,8 +2091,7 @@ private struct SandboxPipInstallTool: OsaurusTool, @unchecked Sendable { let id = agentId, name = self.name, agent = agentName, root = home return try await SandboxInstallLock.shared.serialize(agentName: agentName) { @Sendable func runAsAgent(_ cmd: String, timeout: TimeInterval) async throws - -> ContainerExecResult - { + -> ContainerExecResult { try await SandboxToolCommandRunnerRegistry.shared.execAsAgent( agent, command: cmd, @@ -2214,8 +2212,7 @@ private struct SandboxNpmInstallTool: OsaurusTool, @unchecked Sendable { // (Pinned by `sandboxNpmInstall_bootstrapsPackageJsonAndUsesWorkdir`.) return try await SandboxInstallLock.shared.serialize(agentName: agentName) { @Sendable func runAsAgent(_ cmd: String, timeout: TimeInterval) async throws - -> ContainerExecResult - { + -> ContainerExecResult { try await SandboxToolCommandRunnerRegistry.shared.exec( user: "agent-\(agent)", command: cmd, diff --git a/Packages/OsaurusCore/Tools/MCPProviderTool.swift b/Packages/OsaurusCore/Tools/MCPProviderTool.swift index 14774d65c..552c6378e 100644 --- a/Packages/OsaurusCore/Tools/MCPProviderTool.swift +++ b/Packages/OsaurusCore/Tools/MCPProviderTool.swift @@ -194,8 +194,7 @@ extension MCPProviderTool { default: // Try to encode as JSON string if let jsonData = try? JSONSerialization.data(withJSONObject: value), - let jsonString = String(data: jsonData, encoding: .utf8) - { + let jsonString = String(data: jsonData, encoding: .utf8) { return .string(jsonString) } else { throw NSError( @@ -255,8 +254,7 @@ extension MCPProviderTool { // Otherwise return JSON array if let jsonData = try? JSONSerialization.data(withJSONObject: results), - let jsonString = String(data: jsonData, encoding: .utf8) - { + let jsonString = String(data: jsonData, encoding: .utf8) { return jsonString } diff --git a/Packages/OsaurusCore/Tools/OsaurusTool.swift b/Packages/OsaurusCore/Tools/OsaurusTool.swift index 95cc3788e..0577ee16c 100644 --- a/Packages/OsaurusCore/Tools/OsaurusTool.swift +++ b/Packages/OsaurusCore/Tools/OsaurusTool.swift @@ -278,8 +278,7 @@ public enum ArgumentCoercion { if let arr = value as? [String] { return arr } if let str = value as? String { if let data = str.data(using: .utf8), - let parsed = try? JSONSerialization.jsonObject(with: data) as? [String] - { + let parsed = try? JSONSerialization.jsonObject(with: data) as? [String] { return parsed } let trimmed = str.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/Packages/OsaurusCore/Tools/RenderChartTool.swift b/Packages/OsaurusCore/Tools/RenderChartTool.swift index c6d12f39f..cc73a8b70 100644 --- a/Packages/OsaurusCore/Tools/RenderChartTool.swift +++ b/Packages/OsaurusCore/Tools/RenderChartTool.swift @@ -176,7 +176,7 @@ struct RenderChartTool: OsaurusTool { } // Downsample if needed - var note: String? = nil + var note: String? var dataRows = rows if rows.count > Self.maxRows { dataRows = downsample(rows, to: Self.maxRows) @@ -204,7 +204,7 @@ struct RenderChartTool: OsaurusTool { } } } - var categories: [String]? = nil + var categories: [String]? if let xCol = resolvedXColumn, let xIdx = headers.firstIndex(of: xCol) { categories = dataRows.map { row in xIdx < row.count ? row[xIdx] : "" } } diff --git a/Packages/OsaurusCore/Tools/SandboxPluginTool.swift b/Packages/OsaurusCore/Tools/SandboxPluginTool.swift index 59d37842b..565e5a729 100644 --- a/Packages/OsaurusCore/Tools/SandboxPluginTool.swift +++ b/Packages/OsaurusCore/Tools/SandboxPluginTool.swift @@ -238,8 +238,7 @@ final class SandboxPluginTool: OsaurusTool, @unchecked Sendable { } else if let bool = value as? Bool { env[envKey] = bool ? "true" : "false" } else if let data = try? JSONSerialization.data(withJSONObject: value), - let str = String(data: data, encoding: .utf8) - { + let str = String(data: data, encoding: .utf8) { env[envKey] = str } } diff --git a/Packages/OsaurusCore/Tools/SchemaValidator.swift b/Packages/OsaurusCore/Tools/SchemaValidator.swift index ea2cc6fd1..f5b7f22cb 100644 --- a/Packages/OsaurusCore/Tools/SchemaValidator.swift +++ b/Packages/OsaurusCore/Tools/SchemaValidator.swift @@ -96,8 +96,7 @@ public struct SchemaValidator { // Recurse into nested objects that declare their own properties. if case .string("object")? = propSchemaObj["type"], case .object? = propSchemaObj["properties"], - let nested = value as? [String: Any] - { + let nested = value as? [String: Any] { let inner = validateObject(nested, schemaObject: propSchemaObj) if !inner.isValid { return inner } } @@ -162,8 +161,7 @@ public struct SchemaValidator { regex.firstMatch( in: s, range: NSRange(s.startIndex..., in: s) - ) == nil - { + ) == nil { let label = key.map { " '\($0)'" } ?? "" return .fail( "Property\(label) does not match required pattern `\(pat)`.", @@ -198,16 +196,14 @@ public struct SchemaValidator { // every element). Tuple-form `items: [schema, schema, ...]` is // not implemented — defer to the caller for that rarer shape. if case .object(let itemsSchema)? = schemaObject["items"], - let arr = value as? [Any] - { + let arr = value as? [Any] { for (idx, element) in arr.enumerated() { let elementKey = key.map { "\($0)[\(idx)]" } ?? "[\(idx)]" let res = validateValue(element, schemaObject: itemsSchema, key: elementKey) if !res.isValid { return res } if case .string("object")? = itemsSchema["type"], case .object? = itemsSchema["properties"], - let nested = element as? [String: Any] - { + let nested = element as? [String: Any] { let inner = validateObject(nested, schemaObject: itemsSchema) if !inner.isValid { return inner } } @@ -339,8 +335,7 @@ public struct SchemaValidator { if value is [Any] { return true } if let s = value as? String, let data = s.data(using: .utf8), - (try? JSONSerialization.jsonObject(with: data)) is [Any] - { + (try? JSONSerialization.jsonObject(with: data)) is [Any] { return true } return false diff --git a/Packages/OsaurusCore/Tools/ShareArtifactTool.swift b/Packages/OsaurusCore/Tools/ShareArtifactTool.swift index 542c1f5f9..9239eb72b 100644 --- a/Packages/OsaurusCore/Tools/ShareArtifactTool.swift +++ b/Packages/OsaurusCore/Tools/ShareArtifactTool.swift @@ -113,8 +113,7 @@ public struct ShareArtifactTool: OsaurusTool { let startToken = "---SHARED_ARTIFACT_START---" let endToken = "---SHARED_ARTIFACT_END---" if let rawContent, - rawContent.contains(startToken) || rawContent.contains(endToken) - { + rawContent.contains(startToken) || rawContent.contains(endToken) { return ToolEnvelope.failure( kind: .invalidArgs, message: @@ -152,8 +151,7 @@ public struct ShareArtifactTool: OsaurusTool { let metadataJSON: String if let jsonData = try? JSONSerialization.data(withJSONObject: metadataDict), - let jsonStr = String(data: jsonData, encoding: .utf8) - { + let jsonStr = String(data: jsonData, encoding: .utf8) { metadataJSON = jsonStr } else { metadataJSON = "{}" diff --git a/Packages/OsaurusCore/Tools/ToolEnvelope.swift b/Packages/OsaurusCore/Tools/ToolEnvelope.swift index 041b8ac9e..9cf1739c3 100644 --- a/Packages/OsaurusCore/Tools/ToolEnvelope.swift +++ b/Packages/OsaurusCore/Tools/ToolEnvelope.swift @@ -291,8 +291,7 @@ public enum ToolEnvelope { withJSONObject: dict, options: [.sortedKeys] ), - let json = String(data: data, encoding: .utf8) - { + let json = String(data: data, encoding: .utf8) { return json } // Hand-built fallback so we never return malformed output if the @@ -310,8 +309,7 @@ public enum ToolEnvelope { withJSONObject: dict, options: [.sortedKeys] ), - let json = String(data: data, encoding: .utf8) - { + let json = String(data: data, encoding: .utf8) { return json } // Fallback should never trigger for well-typed inputs; if it does, diff --git a/Packages/OsaurusCore/Tools/ToolRegistry.swift b/Packages/OsaurusCore/Tools/ToolRegistry.swift index 4814f7094..3d679aa62 100644 --- a/Packages/OsaurusCore/Tools/ToolRegistry.swift +++ b/Packages/OsaurusCore/Tools/ToolRegistry.swift @@ -374,7 +374,7 @@ final class ToolRegistry: ObservableObject { /// fall through unchanged: parsing is best-effort, and tool bodies /// keep their richer `requireXxx` helpers as the second line of /// defence. - private nonisolated static func preflight( + nonisolated private static func preflight( argumentsJSON: String, schema: JSONValue?, toolName: String @@ -435,7 +435,7 @@ final class ToolRegistry: ObservableObject { /// post-return throw from reaching the caller as the function's /// error — historically the slow-tool case rethrew CancellationError /// and stalled while the group drained. - internal nonisolated static func runToolBody( + nonisolated internal static func runToolBody( _ tool: OsaurusTool, argumentsJSON: String, timeoutSeconds: TimeInterval diff --git a/Packages/OsaurusCore/Utils/OsaurusPaths.swift b/Packages/OsaurusCore/Utils/OsaurusPaths.swift index e0edf8cac..e9d549cac 100644 --- a/Packages/OsaurusCore/Utils/OsaurusPaths.swift +++ b/Packages/OsaurusCore/Utils/OsaurusPaths.swift @@ -13,7 +13,7 @@ import Foundation public enum OsaurusPaths { /// Optional root directory override for tests /// Note: nonisolated(unsafe) since this is only set during test setup before any concurrent access - public nonisolated(unsafe) static var overrideRoot: URL? + nonisolated(unsafe) public static var overrideRoot: URL? // MARK: - Root Directory @@ -165,13 +165,11 @@ public enum OsaurusPaths { let url = URL(fileURLWithPath: path) let keys: Set = [.volumeAvailableCapacityForImportantUsageKey] if let values = try? url.resourceValues(forKeys: keys), - let capacity = values.volumeAvailableCapacityForImportantUsage - { + let capacity = values.volumeAvailableCapacityForImportantUsage { return capacity } if let attrs = try? FileManager.default.attributesOfFileSystem(forPath: path), - let free = (attrs[.systemFreeSize] as? NSNumber)?.int64Value - { + let free = (attrs[.systemFreeSize] as? NSNumber)?.int64Value { return free } return nil @@ -184,13 +182,11 @@ public enum OsaurusPaths { let url = URL(fileURLWithPath: path) let keys: Set = [.volumeTotalCapacityKey] if let values = try? url.resourceValues(forKeys: keys), - let capacity = values.volumeTotalCapacity - { + let capacity = values.volumeTotalCapacity { return Int64(capacity) } if let attrs = try? FileManager.default.attributesOfFileSystem(forPath: path), - let total = (attrs[.systemSize] as? NSNumber)?.int64Value - { + let total = (attrs[.systemSize] as? NSNumber)?.int64Value { return total } return nil diff --git a/Packages/OsaurusCore/Utils/StreamingDeltaProcessor.swift b/Packages/OsaurusCore/Utils/StreamingDeltaProcessor.swift index 75638e99f..762251cf0 100644 --- a/Packages/OsaurusCore/Utils/StreamingDeltaProcessor.swift +++ b/Packages/OsaurusCore/Utils/StreamingDeltaProcessor.swift @@ -185,8 +185,7 @@ final class StreamingDeltaProcessor { let timeSinceSync = now.timeIntervalSince(lastSyncTime) * 1000 if (syncCount == 0 && hasPendingContent) - || (timeSinceSync >= syncIntervalMs && hasPendingContent) - { + || (timeSinceSync >= syncIntervalMs && hasPendingContent) { syncToTurn() } } diff --git a/Packages/OsaurusCore/Utils/StringCleaning.swift b/Packages/OsaurusCore/Utils/StringCleaning.swift index 52d3b9a74..fe9c9f1cc 100644 --- a/Packages/OsaurusCore/Utils/StringCleaning.swift +++ b/Packages/OsaurusCore/Utils/StringCleaning.swift @@ -36,8 +36,7 @@ public enum StringCleaning { if let lastBrace = result.lastIndex(of: "{") { let suffix = String(result[lastBrace...]) if (suffix.contains("\"name\"") || suffix.contains("\"function\"") || suffix.contains("\"tool\"")) - && !suffix.contains("}}") - { + && !suffix.contains("}}") { result = String(result[..) - -> some View - { + -> some View { HStack { VStack(alignment: .leading, spacing: 2) { Text(title, bundle: .module) @@ -1991,8 +1990,7 @@ struct AgentDetailView: View { if let current = pluginInstructionsMap[pid], !manifestDefault.isEmpty, current.trimmingCharacters(in: .whitespacesAndNewlines) - != manifestDefault.trimmingCharacters(in: .whitespacesAndNewlines) - { + != manifestDefault.trimmingCharacters(in: .whitespacesAndNewlines) { Button { pluginInstructionsMap[pid] = manifestDefault debouncedSave() @@ -3475,7 +3473,7 @@ private struct AgentDetailSection: View { let title: String let icon: String - var subtitle: String? = nil + var subtitle: String? @ViewBuilder let content: () -> Content var body: some View { @@ -3537,7 +3535,7 @@ private struct AgentEditorSheet: View { /// the suggested name in sync. Once the user types their own value, the /// name is theirs and presets stop touching it. @State private var nameUserEdited: Bool = false - @State private var selectedAvatar: String? = nil + @State private var selectedAvatar: String? @State private var systemPrompt: String = "" @State private var selectedModel: String? @State private var pickerItems: [ModelPickerItem] = [] diff --git a/Packages/OsaurusCore/Views/Agent/CapabilitiesTableRepresentable.swift b/Packages/OsaurusCore/Views/Agent/CapabilitiesTableRepresentable.swift index 923a4a2b0..bbf3203ea 100644 --- a/Packages/OsaurusCore/Views/Agent/CapabilitiesTableRepresentable.swift +++ b/Packages/OsaurusCore/Views/Agent/CapabilitiesTableRepresentable.swift @@ -263,8 +263,8 @@ extension CapabilitiesTableRepresentable { // MARK: - Update Paths (Private) private func hasContentChanges(newLookup: [String: CapabilityRow]) -> Bool { - for id in rowIds { - if newLookup[id] != rowLookup[id] { return true } + for id in rowIds where newLookup[id] != rowLookup[id] { + return true } return false } @@ -884,7 +884,7 @@ private struct TokenBadge: View { private struct SmallCapsuleBadge: View { let text: String - var icon: String? = nil + var icon: String? @Environment(\.theme) private var theme diff --git a/Packages/OsaurusCore/Views/Chat/ChatEmptyState.swift b/Packages/OsaurusCore/Views/Chat/ChatEmptyState.swift index d3dc0cc7d..be3c74267 100644 --- a/Packages/OsaurusCore/Views/Chat/ChatEmptyState.swift +++ b/Packages/OsaurusCore/Views/Chat/ChatEmptyState.swift @@ -21,11 +21,11 @@ struct ChatEmptyState: View { let onSelectAgent: (UUID) -> Void let onOpenOnboarding: (() -> Void)? var discoveredAgents: [DiscoveredAgent] = [] - var onSelectDiscoveredAgent: ((DiscoveredAgent) -> Void)? = nil - var activeDiscoveredAgent: DiscoveredAgent? = nil + var onSelectDiscoveredAgent: ((DiscoveredAgent) -> Void)? + var activeDiscoveredAgent: DiscoveredAgent? var pairedRelayAgents: [PairedRelayAgent] = [] - var onSelectRelayAgent: ((PairedRelayAgent) -> Void)? = nil - var activeRelayAgent: PairedRelayAgent? = nil + var onSelectRelayAgent: ((PairedRelayAgent) -> Void)? + var activeRelayAgent: PairedRelayAgent? @State private var hasAppeared = false @Environment(\.theme) private var theme diff --git a/Packages/OsaurusCore/Views/Chat/ChatSessionSidebar.swift b/Packages/OsaurusCore/Views/Chat/ChatSessionSidebar.swift index 8dd344a1b..d53d09928 100644 --- a/Packages/OsaurusCore/Views/Chat/ChatSessionSidebar.swift +++ b/Packages/OsaurusCore/Views/Chat/ChatSessionSidebar.swift @@ -22,7 +22,7 @@ struct ChatSessionSidebar: View { let onDelete: (UUID) -> Void let onRename: (UUID, String) -> Void /// Optional callback for opening a session in a new window - var onOpenInNewWindow: ((ChatSessionData) -> Void)? = nil + var onOpenInNewWindow: ((ChatSessionData) -> Void)? @Environment(\.theme) private var theme @ObservedObject private var agentManager = AgentManager.shared @@ -329,7 +329,7 @@ private struct SessionRow: View { let onCancelRename: () -> Void let onDelete: () -> Void /// Optional callback for opening in a new window - var onOpenInNewWindow: (() -> Void)? = nil + var onOpenInNewWindow: (() -> Void)? @Environment(\.theme) private var theme @State private var isHovered = false @@ -461,8 +461,7 @@ private struct SessionRow: View { parts.append(origin) } if let key = session.externalSessionKey, - !key.trimmingCharacters(in: .whitespaces).isEmpty - { + !key.trimmingCharacters(in: .whitespaces).isEmpty { // Truncate noisy external keys (e.g. long Telegram chat ids) // so the row doesn't overflow horizontally. let trimmed = key.count > 14 ? "\(key.prefix(12))…" : key diff --git a/Packages/OsaurusCore/Views/Chat/ChatView.swift b/Packages/OsaurusCore/Views/Chat/ChatView.swift index 460e1d9b0..5b2c22dee 100644 --- a/Packages/OsaurusCore/Views/Chat/ChatView.swift +++ b/Packages/OsaurusCore/Views/Chat/ChatView.swift @@ -50,7 +50,7 @@ final class ChatSession: ObservableObject { let expandedBlocksStore = ExpandedBlocksStore() @Published var input: String = "" @Published var pendingAttachments: [Attachment] = [] - @Published var selectedModel: String? = nil + @Published var selectedModel: String? @Published var pickerItems: [ModelPickerItem] = [] @Published var activeModelOptions: [String: ModelOptionValue] = [:] @Published var hasAnyModel: Bool = false @@ -717,8 +717,7 @@ final class ChatSession: ObservableObject { // suppresses the auto-persist sink so a load doesn't look like // the user just picked a model. if let savedModel = data.selectedModel, - pickerItems.contains(where: { $0.id == savedModel }) - { + pickerItems.contains(where: { $0.id == savedModel }) { isLoadingModel = true selectedModel = savedModel loadActiveModelOptions(for: selectedModel) @@ -837,8 +836,7 @@ final class ChatSession: ObservableObject { // accepted too so plugin authors who emit raw markers keep working. let markerText: String if let payload = ToolEnvelope.successPayload(toolResult) as? [String: Any], - let text = payload["text"] as? String - { + let text = payload["text"] as? String { markerText = text } else { markerText = toolResult @@ -1002,8 +1000,7 @@ final class ChatSession: ObservableObject { lastTurn.role == .assistant, lastTurn.contentIsEmpty, lastTurn.toolCalls == nil, - !lastTurn.hasThinking - { + !lastTurn.hasThinking { turns.removeLast() } } @@ -1064,8 +1061,7 @@ final class ChatSession: ObservableObject { guard persistConversationArtifacts, let context else { return } if let lastAssistant = turns.last(where: { $0.role == .assistant }), - !lastAssistant.contentIsEmpty - { + !lastAssistant.contentIsEmpty { lastCompletedAssistantTurnId = lastAssistant.id } @@ -1898,8 +1894,7 @@ final class ChatSession: ObservableObject { } if inv.toolName == "clarify" { if !ToolEnvelope.isError(resultText), - let payload = Self.parseClarifyPayload(from: inv.jsonArguments) - { + let payload = Self.parseClarifyPayload(from: inv.jsonArguments) { // Build a ClarifyPromptState bound to // `self.send(...)` so the user's answer // dispatches as the next user turn @@ -1929,8 +1924,7 @@ final class ChatSession: ObservableObject { // Skipped in manual mode — the user's explicit tool set is fixed. if !isManualTools, inv.toolName == "capabilities_load" - || inv.toolName == "sandbox_plugin_register" - { + || inv.toolName == "sandbox_plugin_register" { let newTools = await CapabilityLoadBuffer.shared.drain() for tool in newTools where !toolSpecs.contains(where: { $0.function.name == tool.function.name }) { @@ -1963,8 +1957,7 @@ final class ChatSession: ObservableObject { } if inv.toolName == "sandbox_secret_set", - let prompt = SecretPromptParser.parse(resultText) - { + let prompt = SecretPromptParser.parse(resultText) { let stored: Bool = await withCheckedContinuation { continuation in let promptState = SecretPromptState( key: prompt.key, @@ -2100,13 +2093,13 @@ struct ChatView: View { @State private var editText: String = "" @State private var userImagePreview: NSImage? // Bonjour agent connection - @State private var pendingDiscoveredAgent: DiscoveredAgent? = nil + @State private var pendingDiscoveredAgent: DiscoveredAgent? // Minimap @State private var activeMinimapTurnId: UUID? @State private var scrollToTurnId: UUID? @State private var scrollToTurnTrigger: Int = 0 // What's New modal - @State private var pendingWhatsNew: WhatsNewRelease? = nil + @State private var pendingWhatsNew: WhatsNewRelease? @State private var showAutoSpeakPrompt: Bool = false /// Convenience accessor for the window's theme @@ -2177,7 +2170,7 @@ struct ChatView: View { } var body: some View { - let _ = ChatPerfTrace.shared.count("body.ChatView") + let _ = ChatPerfTrace.shared.count("body.ChatView") // swiftlint:disable:this redundant_discardable_let chatModeContent .themedAlert( "Do you want Osaurus to auto speak every reply in this chat?", @@ -2866,12 +2859,12 @@ private struct IsolatedThreadView: View { let onConfirmEdit: (() -> Void)? let onCancelEdit: (() -> Void)? let onUserImagePreview: ((String) -> Void)? - var onVisibleTopUserTurnChanged: ((UUID?) -> Void)? = nil - var scrollToTurnId: UUID? = nil + var onVisibleTopUserTurnChanged: ((UUID?) -> Void)? + var scrollToTurnId: UUID? var scrollToTurnTrigger: Int = 0 var body: some View { - let _ = ChatPerfTrace.shared.count("body.IsolatedThreadView") + let _ = ChatPerfTrace.shared.count("body.IsolatedThreadView") // swiftlint:disable:this redundant_discardable_let MessageThreadView( blocks: store.blocks, groupHeaderMap: store.groupHeaderMap, @@ -2921,8 +2914,7 @@ extension ChatView { } if let url = sharedArtifactImageURL(artifactId: attachmentId), let data = try? Data(contentsOf: url), - let img = NSImage(data: data) - { + let img = NSImage(data: data) { userImagePreview = img } } @@ -3158,7 +3150,7 @@ private struct PairingSheet: View { let onCancel: () -> Void @State private var isPairing = false - @State private var errorMessage: String? = nil + @State private var errorMessage: String? @Environment(\.theme) private var theme var body: some View { diff --git a/Packages/OsaurusCore/Views/Chat/DocumentChip.swift b/Packages/OsaurusCore/Views/Chat/DocumentChip.swift index b3c3499be..9e0ac19af 100644 --- a/Packages/OsaurusCore/Views/Chat/DocumentChip.swift +++ b/Packages/OsaurusCore/Views/Chat/DocumentChip.swift @@ -10,7 +10,7 @@ import SwiftUI struct DocumentChip: View { let attachment: Attachment - var onRemove: (() -> Void)? = nil + var onRemove: (() -> Void)? @Environment(\.theme) private var theme @State private var isHovered = false diff --git a/Packages/OsaurusCore/Views/Chat/EditableTextView.swift b/Packages/OsaurusCore/Views/Chat/EditableTextView.swift index 913c1365a..09cb51fdc 100644 --- a/Packages/OsaurusCore/Views/Chat/EditableTextView.swift +++ b/Packages/OsaurusCore/Views/Chat/EditableTextView.swift @@ -17,14 +17,14 @@ struct EditableTextView: NSViewRepresentable { @Binding var isFocused: Bool @Binding var isComposing: Bool var maxHeight: CGFloat = .infinity - var onCommit: (() -> Void)? = nil - var onShiftCommit: (() -> Void)? = nil + var onCommit: (() -> Void)? + var onShiftCommit: (() -> Void)? /// Called on ↑ arrow key. Return true to consume the event (prevents cursor movement). - var onArrowUp: (() -> Bool)? = nil + var onArrowUp: (() -> Bool)? /// Called on ↓ arrow key. Return true to consume the event (prevents cursor movement). - var onArrowDown: (() -> Bool)? = nil + var onArrowDown: (() -> Bool)? /// Called on Escape key. Return true to consume the event. - var onEscape: (() -> Bool)? = nil + var onEscape: (() -> Bool)? // MARK: - NSViewRepresentable diff --git a/Packages/OsaurusCore/Views/Chat/FloatingInputCard.swift b/Packages/OsaurusCore/Views/Chat/FloatingInputCard.swift index 4370526a2..f49d741ec 100644 --- a/Packages/OsaurusCore/Views/Chat/FloatingInputCard.swift +++ b/Packages/OsaurusCore/Views/Chat/FloatingInputCard.swift @@ -31,16 +31,16 @@ struct FloatingInputCard: View { /// Trigger to focus the input field (increment to focus) var focusTrigger: Int = 0 /// Current agent ID (used for agent-specific settings) - var agentId: UUID? = nil + var agentId: UUID? /// Window ID for targeted VAD notifications - var windowId: UUID? = nil + var windowId: UUID? /// Compact mode (sidebar open) - hides secondary chip content var isCompact: Bool = false /// Callback to clear the current chat session (triggered by /clear command). - var onClearChat: (() -> Void)? = nil + var onClearChat: (() -> Void)? /// Callback when the user selects a skill slash command. Passes the skill UUID so the /// caller can inject that skill's instructions as one-off context for the next send. - var onSkillSelected: ((UUID) -> Void)? = nil + var onSkillSelected: ((UUID) -> Void)? /// Binding to the session's pending one-off skill. Non-nil shows a dismissable skill chip. @Binding var pendingSkillId: UUID? /// Binding to the session's auto-speak preference. When true, a chip is shown @@ -149,7 +149,7 @@ struct FloatingInputCard: View { @State private var contextHoverTask: Task? @State private var isSandboxHovered = false @State private var sandboxPulseAmount: CGFloat = 1.0 - @State private var sandboxPulseTask: Task? = nil + @State private var sandboxPulseTask: Task? @State private var isClipboardHovered = false @State private var clipboardPulseAmount: CGFloat = 0.0 @State private var clipboardPulseOpacity: Double = 0.0 @@ -173,7 +173,7 @@ struct FloatingInputCard: View { /// Tracks confirmed transcription length to detect actual changes (for silence timeout) @State private var lastConfirmedLength: Int = 0 - @State private var pauseTimerCancellable: AnyCancellable? = nil + @State private var pauseTimerCancellable: AnyCancellable? // TextEditor should grow up to ~6 lines before scrolling private var inputFontSize: CGFloat { CGFloat(theme.bodySize) } @@ -229,8 +229,7 @@ struct FloatingInputCard: View { return 4096 } if let info = ModelInfo.load(modelId: model), - let ctx = info.model.contextLength - { + let ctx = info.model.contextLength { return ctx } return nil @@ -266,8 +265,7 @@ struct FloatingInputCard: View { || displayContextTokens > 0 || isSandboxAvailable || (appConfig.chatConfig.enableClipboardMonitoring && clipboardService.hasNewContent)) - && !showVoiceOverlay - { + && !showVoiceOverlay { selectorRow .padding(.top, 8) .padding(.horizontal, 20) @@ -334,7 +332,7 @@ struct FloatingInputCard: View { } var body: some View { - let _ = ChatPerfTrace.shared.count("body.FloatingInputCard") + let _ = ChatPerfTrace.shared.count("body.FloatingInputCard") // swiftlint:disable:this redundant_discardable_let mainContent .onAppear { let isReappear = !localText.isEmpty || voiceInputState != .idle @@ -568,7 +566,7 @@ struct FloatingInputCard: View { // MARK: - Voice Debug Helpers /// Standalone log helper so VoiceDebugObservers can call it without a card reference. -fileprivate func voiceDebugLog( +private func voiceDebugLog( trigger: String, enabled: Bool, micPermission: Bool, @@ -993,8 +991,7 @@ extension FloatingInputCard { @ViewBuilder private var pendingSkillChipView: some View { if let skillId = pendingSkillId, - let skill = SkillManager.shared.skill(for: skillId) - { + let skill = SkillManager.shared.skill(for: skillId) { HStack(spacing: 5) { Image(systemName: "wand.and.stars") .font(.system(size: 11, weight: .medium)) @@ -1295,8 +1292,7 @@ extension FloatingInputCard { @ViewBuilder private var thinkingToggleChip: some View { if let model = selectedModel, - let thinkingOpt = ModelProfileRegistry.profile(for: model)?.thinkingOption - { + let thinkingOpt = ModelProfileRegistry.profile(for: model)?.thinkingOption { let isCurrentlyEnabled = activeModelOptions[thinkingOpt.id]?.boolValue ?? false let isEnabled = thinkingOpt.inverted ? !isCurrentlyEnabled : isCurrentlyEnabled @@ -1888,8 +1884,7 @@ extension FloatingInputCard { if DocumentParser.isImageFile(url: url) { if let data = try? Data(contentsOf: url), let nsImage = NSImage(data: data), - let pngData = nsImage.pngData() - { + let pngData = nsImage.pngData() { withAnimation(theme.springAnimation()) { pendingAttachments.append(.image(pngData)) clipboardService.markAsRead() @@ -2223,8 +2218,7 @@ extension FloatingInputCard { if DocumentParser.isImageFile(url: url) { if let data = try? Data(contentsOf: url), data.count <= maxImageSize, let nsImage = NSImage(data: data), - let pngData = nsImage.pngData() - { + let pngData = nsImage.pngData() { appendAttachment(.image(pngData)) } return @@ -2327,15 +2321,13 @@ extension FloatingInputCard { guard let data = data, error == nil, data.count <= maxImageSize else { return } DispatchQueue.main.async { if let nsImage = NSImage(data: data), - let pngData = nsImage.pngData() - { + let pngData = nsImage.pngData() { appendAttachment(.image(pngData)) } } } } else if cap.supportsAudio, - provider.hasItemConformingToTypeIdentifier(UTType.audio.identifier) - { + provider.hasItemConformingToTypeIdentifier(UTType.audio.identifier) { handled = true // Audio path — load via fileURL so we get the extension, // not raw data identifier. @@ -2349,8 +2341,7 @@ extension FloatingInputCard { } } else if cap.supportsVideo, provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) - || provider.hasItemConformingToTypeIdentifier(UTType.video.identifier) - { + || provider.hasItemConformingToTypeIdentifier(UTType.video.identifier) { handled = true provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier) { item, _ in guard let urlData = item as? Data, @@ -2362,7 +2353,7 @@ extension FloatingInputCard { } } else if provider.hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) { handled = true - provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier) { item, error in + provider.loadItem(forTypeIdentifier: UTType.fileURL.identifier) { item, _ in guard let data = item as? Data, let url = URL(dataRepresentation: data, relativeTo: nil) else { return } @@ -2726,8 +2717,7 @@ class PasteMonitorView: NSView { if let imageData = pasteboard.data(forType: .tiff), let nsImage = NSImage(data: imageData), - let pngData = nsImage.pngData() - { + let pngData = nsImage.pngData() { onImagePaste?(pngData) return true } @@ -2739,8 +2729,7 @@ class PasteMonitorView: NSView { UTType(uti)?.conforms(to: .image) == true, let data = try? Data(contentsOf: url), let nsImage = NSImage(data: data), - let pngData = nsImage.pngData() - { + let pngData = nsImage.pngData() { onImagePaste?(pngData) return true } @@ -3522,7 +3511,7 @@ private struct StopButton: View { // MARK: - Resume Button -/// Polished resume button with accent color +// Polished resume button with accent color // MARK: - Preview #if DEBUG diff --git a/Packages/OsaurusCore/Views/Chat/MarkdownImageView.swift b/Packages/OsaurusCore/Views/Chat/MarkdownImageView.swift index 3c23f5af4..1a13a4b77 100644 --- a/Packages/OsaurusCore/Views/Chat/MarkdownImageView.swift +++ b/Packages/OsaurusCore/Views/Chat/MarkdownImageView.swift @@ -380,7 +380,7 @@ struct ImageFullScreenView: View { let image: NSImage? let altText: String /// when set (e.g. overlay presentation), avoids `Environment.dismiss` and prevents sheet-driven window sizing on macOS - var onDismiss: (() -> Void)? = nil + var onDismiss: (() -> Void)? @Environment(\.dismiss) private var dismiss @Environment(\.theme) private var theme diff --git a/Packages/OsaurusCore/Views/Chat/MarkdownMessageView.swift b/Packages/OsaurusCore/Views/Chat/MarkdownMessageView.swift index fa8f240a6..b4d77392b 100644 --- a/Packages/OsaurusCore/Views/Chat/MarkdownMessageView.swift +++ b/Packages/OsaurusCore/Views/Chat/MarkdownMessageView.swift @@ -14,7 +14,7 @@ struct MarkdownMessageView: View { let text: String let baseWidth: CGFloat /// Optional cache key passed to SelectableTextView for width-aware height caching - var cacheKey: String? = nil + var cacheKey: String? /// Whether content is actively streaming - when true, uses lighter rendering for large content var isStreaming: Bool = false @@ -642,7 +642,7 @@ func parseBlocks(_ input: String) -> [MessageBlock] { // or at minimum another pipe-delimited row. Tolerate blank lines between // rows and malformed separators (some small models emit `| :/| :---/|`). if trimmed.hasPrefix("|"), pipeCount(trimmed) >= 2 { - var nextIdx: Int? = nil + var nextIdx: Int? var j = i + 1 while j < lines.count { let lt = lines[j].trimmingWhitespace() @@ -1112,7 +1112,7 @@ private func parseOrderedListItemWithIndent(_ line: Substring, trimmed: Substrin // Check for "." followed by whitespace (or ")" for alternate syntax) guard index > content.startIndex, index < content.endIndex, - (content[index] == "." || content[index] == ")") + content[index] == "." || content[index] == ")" else { return nil } let afterDot = content.index(after: index) diff --git a/Packages/OsaurusCore/Views/Chat/MessageTableRepresentable.swift b/Packages/OsaurusCore/Views/Chat/MessageTableRepresentable.swift index 2f7f42a05..77d58a9a7 100644 --- a/Packages/OsaurusCore/Views/Chat/MessageTableRepresentable.swift +++ b/Packages/OsaurusCore/Views/Chat/MessageTableRepresentable.swift @@ -90,12 +90,12 @@ struct MessageTableRepresentable: NSViewRepresentable { let editText: Binding? let onConfirmEdit: (() -> Void)? let onCancelEdit: (() -> Void)? - var onUserImagePreview: ((String) -> Void)? = nil + var onUserImagePreview: ((String) -> Void)? // Minimap support - var onVisibleTopUserTurnChanged: ((UUID?) -> Void)? = nil + var onVisibleTopUserTurnChanged: ((UUID?) -> Void)? /// Turn ID to scroll to. Paired with `scrollToTurnTrigger` for one-shot delivery. - var scrollToTurnId: UUID? = nil + var scrollToTurnId: UUID? var scrollToTurnTrigger: Int = 0 // MARK: - NSViewRepresentable Lifecycle @@ -507,8 +507,7 @@ extension MessageTableRepresentable { let blockId = blockIds[row] heightCache.removeValue(forKey: blockId) if let cell = tableView?.view(atColumn: 0, row: row, makeIfNecessary: false) as? NativeMessageCellView, - let block = blockLookup[blockId] - { + let block = blockLookup[blockId] { configureCell(cell, with: block) } // let the hosting view settle before re-measuring @@ -527,10 +526,8 @@ extension MessageTableRepresentable { let visible = tableView.rows(in: tableView.visibleRect) guard visible.length > 0 else { return } let rows = IndexSet(integersIn: visible.location ..< visible.location + visible.length) - for row in rows { - if row < blockIds.count { - heightCache.removeValue(forKey: blockIds[row]) - } + for row in rows where row < blockIds.count { + heightCache.removeValue(forKey: blockIds[row]) } noteRowHeightsChanged(rows) } @@ -682,8 +679,8 @@ extension MessageTableRepresentable { // MARK: - Update Paths (Private) private func hasContentChanges(newLookup: [String: ContentBlock]) -> Bool { - for id in blockIds { - if newLookup[id] != blockLookup[id] { return true } + for id in blockIds where newLookup[id] != blockLookup[id] { + return true } return false } @@ -776,8 +773,7 @@ extension MessageTableRepresentable { atColumn: 0, row: row, makeIfNecessary: false - ) as? NativeMessageCellView - { + ) as? NativeMessageCellView { self.heightCache.removeValue(forKey: id) self.configureCell(cell, with: block) reconfiguredRows.insert(row) @@ -799,8 +795,7 @@ extension MessageTableRepresentable { // schedule a deferred re-measurement after the hosting view's // layout has settled, then re-pin scroll position. if streamingJustEnded, let streamId = previousStreamingBlockId, - let row = self.blockIds.firstIndex(of: streamId) - { + let row = self.blockIds.firstIndex(of: streamId) { self.schedulePostStreamingHeightFix( streamId: streamId, row: row, @@ -821,8 +816,7 @@ extension MessageTableRepresentable { ) { if autoScrollEnabled, let turnId = lastAssistantTurnId, - turnId != lastScrolledToTurnId - { + turnId != lastScrolledToTurnId { lastScrolledToTurnId = turnId let headerId = "header-\(turnId.uuidString)" if let row = blockIds.firstIndex(of: headerId) { @@ -878,8 +872,7 @@ extension MessageTableRepresentable { var affectedRows = IndexSet() for (index, blockId) in blockIds.enumerated() { guard let block = blockLookup[blockId], block.turnId == turnId else { continue } - if let cell = tableView.view(atColumn: 0, row: index, makeIfNecessary: false) as? NativeMessageCellView - { + if let cell = tableView.view(atColumn: 0, row: index, makeIfNecessary: false) as? NativeMessageCellView { heightCache.removeValue(forKey: blockId) configureCell(cell, with: block) } @@ -927,8 +920,7 @@ extension MessageTableRepresentable { if row < self.blockIds.count { let bid = self.blockIds[row] if let h = self.heightCache[bid], let noted = self.lastNotedHeight[bid], - abs(h - noted) < 0.5 - { + abs(h - noted) < 0.5 { ChatPerfTrace.shared.count("streamingHeightUpdate.skipped") return } @@ -1113,7 +1105,7 @@ extension MessageTableRepresentable { guard let tableView, let scrollView else { return } guard let callback = onVisibleTopUserTurnChanged else { return } - var newTurnId: UUID? = nil + var newTurnId: UUID? if !blockIds.isEmpty { let clip = scrollView.contentView @@ -1143,8 +1135,7 @@ extension MessageTableRepresentable { var forward = anchorRow + 1 while forward < blockIds.count { if let block = blockLookup[blockIds[forward]], - case .userMessage = block.kind - { + case .userMessage = block.kind { newTurnId = block.turnId break } diff --git a/Packages/OsaurusCore/Views/Chat/MessageThreadView.swift b/Packages/OsaurusCore/Views/Chat/MessageThreadView.swift index 18b8d425b..c2cbb3f1d 100644 --- a/Packages/OsaurusCore/Views/Chat/MessageThreadView.swift +++ b/Packages/OsaurusCore/Views/Chat/MessageThreadView.swift @@ -12,7 +12,7 @@ import SwiftUI struct MessageThreadView: View { let blocks: [ContentBlock] /// Optional precomputed group header map; falls back to local computation when nil. - var groupHeaderMap: [UUID: UUID]? = nil + var groupHeaderMap: [UUID: UUID]? let width: CGFloat let agentName: String let agentAvatar: String? @@ -29,21 +29,21 @@ struct MessageThreadView: View { // Message action callbacks let onCopy: (UUID) -> Void - var onRegenerate: ((UUID) -> Void)? = nil - var onEdit: ((UUID) -> Void)? = nil - var onDelete: ((UUID) -> Void)? = nil - var onSpeak: ((UUID) -> Void)? = nil + var onRegenerate: ((UUID) -> Void)? + var onEdit: ((UUID) -> Void)? + var onDelete: ((UUID) -> Void)? + var onSpeak: ((UUID) -> Void)? // Inline editing state (optional) - var editingTurnId: UUID? = nil - var editText: Binding? = nil - var onConfirmEdit: (() -> Void)? = nil - var onCancelEdit: (() -> Void)? = nil - var onUserImagePreview: ((String) -> Void)? = nil + var editingTurnId: UUID? + var editText: Binding? + var onConfirmEdit: (() -> Void)? + var onCancelEdit: (() -> Void)? + var onUserImagePreview: ((String) -> Void)? // Minimap - var onVisibleTopUserTurnChanged: ((UUID?) -> Void)? = nil - var scrollToTurnId: UUID? = nil + var onVisibleTopUserTurnChanged: ((UUID?) -> Void)? + var scrollToTurnId: UUID? var scrollToTurnTrigger: Int = 0 @Environment(\.theme) private var theme @@ -52,7 +52,7 @@ struct MessageThreadView: View { if let precomputed = groupHeaderMap { return precomputed } var map: [UUID: UUID] = [:] - var currentGroupHeaderId: UUID? = nil + var currentGroupHeaderId: UUID? for block in blocks { if case .groupSpacer = block.kind { diff --git a/Packages/OsaurusCore/Views/Chat/NativeArtifactCardView.swift b/Packages/OsaurusCore/Views/Chat/NativeArtifactCardView.swift index a01bc4b17..9eb0310b2 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeArtifactCardView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeArtifactCardView.swift @@ -205,8 +205,7 @@ final class NativeArtifactCardView: NSView { self.layoutSubtreeIfNeeded() var newH = max(self.fittingSize.height, 1) if !self.openInFinderButton.isHidden || !self.openInBrowserButton.isHidden, - let bound = self.boundArtifact - { + let bound = self.boundArtifact { newH = max(newH, Self.minimumHeightWithFooterChrome(for: bound)) } let oldH = self.cachedLayoutHeight @@ -780,8 +779,7 @@ final class NativeArtifactCardView: NSView { self.layoutSubtreeIfNeeded() var newH = max(self.fittingSize.height, 1) if !self.openInFinderButton.isHidden || !self.openInBrowserButton.isHidden, - let bound = self.boundArtifact - { + let bound = self.boundArtifact { newH = max(newH, Self.minimumHeightWithFooterChrome(for: bound)) } self.cachedLayoutHeight = newH diff --git a/Packages/OsaurusCore/Views/Chat/NativeBlockViews.swift b/Packages/OsaurusCore/Views/Chat/NativeBlockViews.swift index d208bab58..30ebbb054 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeBlockViews.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeBlockViews.swift @@ -690,7 +690,7 @@ final class NativeCodeBlockView: NSView { override func hitTest(_ point: NSPoint) -> NSView? { if let sub = super.hitTest(point) { return sub } - if NSPointInRect(point, bounds) { return self } + if bounds.contains(point) { return self } return nil } @@ -709,7 +709,7 @@ final class NativeCodeBlockView: NSView { // MARK: State private var lastCode = "" - private var lastLang: String? = nil + private var lastLang: String? private var lastWidth: CGFloat = 0 private var lastThemeId = "" private var copyResetTask: Task? diff --git a/Packages/OsaurusCore/Views/Chat/NativeChartView.swift b/Packages/OsaurusCore/Views/Chat/NativeChartView.swift index 6f9f653b0..6c57af4b8 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeChartView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeChartView.swift @@ -254,8 +254,7 @@ final class NativeChartView: NSView { model.categories(categories) } if let stacking = spec.stacking, - let stackingType = AAChartStackingType(rawValue: stacking) - { + let stackingType = AAChartStackingType(rawValue: stacking) { model.stacking(stackingType) } if let colors = spec.colorsTheme { diff --git a/Packages/OsaurusCore/Views/Chat/NativeMarkdownView.swift b/Packages/OsaurusCore/Views/Chat/NativeMarkdownView.swift index 4680328f0..ad9c98c32 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeMarkdownView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeMarkdownView.swift @@ -35,7 +35,7 @@ final class NativeMarkdownView: NSView { let pInSeg = convert(point, to: entry.view) if let hit = entry.view.hitTest(pInSeg) { return hit } } - if NSPointInRect(point, bounds) { return self } + if bounds.contains(point) { return self } return nil } @@ -426,8 +426,7 @@ final class NativeMarkdownView: NSView { let verticalConstraints = constraints.filter { c in if c.firstAttribute == .top || c.firstAttribute == .bottom { if let first = c.firstItem as? NSView, - subviewPointers.contains(Unmanaged.passUnretained(first).toOpaque()) - { + subviewPointers.contains(Unmanaged.passUnretained(first).toOpaque()) { return true } } diff --git a/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift b/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift index ccc833f4a..ced1043a5 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift @@ -27,19 +27,19 @@ struct CellRenderingContext { let onToggleExpand: (String) -> Void /// Called by native views after they've measured their own height. /// Coordinator updates heightCache and calls noteHeightOfRows if delta > 2pt. - var onHeightMeasured: ((CGFloat, String) -> Void)? = nil + var onHeightMeasured: ((CGFloat, String) -> Void)? var isTurnHovered: Bool = false - var editingTurnId: UUID? = nil - var editText: (() -> String, (String) -> Void)? = nil - var onConfirmEdit: (() -> Void)? = nil - var onCancelEdit: (() -> Void)? = nil - var onCopy: ((UUID) -> Void)? = nil - var onRegenerate: ((UUID) -> Void)? = nil - var onEdit: ((UUID) -> Void)? = nil - var onDelete: ((UUID) -> Void)? = nil - var onSpeak: ((UUID) -> Void)? = nil + var editingTurnId: UUID? + var editText: (() -> String, (String) -> Void)? + var onConfirmEdit: (() -> Void)? + var onCancelEdit: (() -> Void)? + var onCopy: ((UUID) -> Void)? + var onRegenerate: ((UUID) -> Void)? + var onEdit: ((UUID) -> Void)? + var onDelete: ((UUID) -> Void)? + var onSpeak: ((UUID) -> Void)? /// attachment or shared-artifact id string → full screen preview from ChatView - var onUserImagePreview: ((String) -> Void)? = nil + var onUserImagePreview: ((String) -> Void)? } // MARK: - Cell-Isolated ExpandedBlocksStore Proxy @@ -1091,10 +1091,6 @@ final class NativeMessageCellView: NSTableCellView { } required init?(coder: NSCoder) { fatalError() } - override func layout() { - super.layout() - } - /// Row height from Auto Layout — avoids drift from hand-summed constants vs. actual constraints. /// AppKit `NSView` uses `fittingSize` (UIKit’s `systemLayoutSizeFitting` is not available here). /// Table view still uses `heightOfRow:` + cache (see MessageTableRepresentable); this only feeds accurate measurements. @@ -1394,7 +1390,7 @@ final class NativeMessageCellView: NSTableCellView { } let tv = nativeThinkingView! let thinkingLen: Int? - if case .thinking(_, _, _) = block.kind { thinkingLen = text.count } else { thinkingLen = nil } + if case .thinking = block.kind { thinkingLen = text.count } else { thinkingLen = nil } let isExpanded = context.expandedIds.contains(block.id) tv.configure( @@ -1644,8 +1640,7 @@ final class NativeMessageCellView: NSTableCellView { } if wantsInlineEdit, let editPair = context.editText, let onConfirm = context.onConfirmEdit, - let onCancel = context.onCancelEdit, let ev = userInlineEditView - { + let onCancel = context.onCancelEdit, let ev = userInlineEditView { let getT = editPair.0 let setT = editPair.1 ev.configure( diff --git a/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift b/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift index 002b72196..35b4e5eba 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift @@ -80,22 +80,19 @@ enum ToolCategory { // File operations if name.contains("file") || name.contains("read") || name.contains("write") - || name.contains("path") || name.contains("directory") || name.contains("folder") - { + || name.contains("path") || name.contains("directory") || name.contains("folder") { return .file } // Search operations if name.contains("search") || name.contains("find") || name.contains("query") - || name.contains("grep") || name.contains("lookup") - { + || name.contains("grep") || name.contains("lookup") { return .search } // Terminal/command operations if name.contains("terminal") || name.contains("command") || name.contains("exec") - || name.contains("shell") || name.contains("run") || name.contains("bash") - { + || name.contains("shell") || name.contains("run") || name.contains("bash") { return .terminal } @@ -103,22 +100,19 @@ enum ToolCategory { if name.contains("http") || name.contains("api") || name.contains("fetch") || name.contains("request") || name.contains("url") || name.contains("web") || name.contains("thread") || name.contains("mailbox") || name.contains("mail") - || name.contains("messages") - { + || name.contains("messages") { return .network } // Database operations if name.contains("database") || name.contains("sql") || name.contains("db") - || name.contains("query") || name.contains("table") - { + || name.contains("query") || name.contains("table") { return .database } // Code operations if name.contains("code") || name.contains("edit") || name.contains("replace") - || name.contains("refactor") || name.contains("lint") - { + || name.contains("refactor") || name.contains("lint") { return .code } @@ -185,8 +179,7 @@ enum PreviewGenerator { // Try to parse as JSON first if let data = trimmed.data(using: .utf8), - let json = try? JSONSerialization.jsonObject(with: data) - { + let json = try? JSONSerialization.jsonObject(with: data) { // Handle JSON array if let array = json as? [Any] { @@ -866,8 +859,7 @@ final class NativeToolCallRowView: NSView { } if let payload = ToolEnvelope.successPayload(result) as? [String: Any], payload.count == 1, - let text = payload["text"] as? String - { + let text = payload["text"] as? String { return text } if let pretty = JSONFormatter.prettyPrintedJSONIfValid(trimmed) { diff --git a/Packages/OsaurusCore/Views/Chat/PromptQueue.swift b/Packages/OsaurusCore/Views/Chat/PromptQueue.swift index 228190fdc..c75bbded2 100644 --- a/Packages/OsaurusCore/Views/Chat/PromptQueue.swift +++ b/Packages/OsaurusCore/Views/Chat/PromptQueue.swift @@ -35,7 +35,7 @@ public enum PromptItem: Identifiable { case secret(SecretPromptState) case clarify(ClarifyPromptState) - public nonisolated var id: ObjectIdentifier { + nonisolated public var id: ObjectIdentifier { switch self { case .secret(let s): return ObjectIdentifier(s) case .clarify(let c): return ObjectIdentifier(c) diff --git a/Packages/OsaurusCore/Views/Chat/SelectableTextView.swift b/Packages/OsaurusCore/Views/Chat/SelectableTextView.swift index f0acb38d7..b28fae797 100644 --- a/Packages/OsaurusCore/Views/Chat/SelectableTextView.swift +++ b/Packages/OsaurusCore/Views/Chat/SelectableTextView.swift @@ -60,14 +60,14 @@ struct SelectableTextView: NSViewRepresentable { let baseWidth: CGFloat let theme: ThemeProtocol /// Optional cache key (turn ID) for persisting measured height across view recycling - var cacheKey: String? = nil + var cacheKey: String? final class Coordinator { var lastBlocks: [SelectableTextBlock] = [] var lastWidth: CGFloat = 0 var lastThemeFingerprint: String = "" var lastMeasuredHeight: CGFloat = 0 - var cacheKey: String? = nil + var cacheKey: String? var blockLengths: [Int] = [] // per-block rendered lengths for incremental updates /// Disables ThreadCache lookups once content changes (prevents stale heights during streaming) var contentChangedSinceInit: Bool = false @@ -767,8 +767,7 @@ struct SelectableTextView: NSViewRepresentable { if scalars[i] == "$" && i + 1 < scalars.count && !scalars[i + 1].properties.isWhitespace - && scalars[i + 1] != "$" - { + && scalars[i + 1] != "$" { if let closeIdx = findClosingDollar(scalars, from: i + 1) { let start = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: i + 1) let end = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: closeIdx) @@ -839,8 +838,7 @@ struct SelectableTextView: NSViewRepresentable { let markdownAttr = try? NSAttributedString( markdown: segment.text, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) - ) - { + ) { let mutable = NSMutableAttributedString(attributedString: markdownAttr) applyThemeStyling(to: mutable, baseFontSize: fontSize, baseWeight: weight, isItalic: isItalic) result.append(mutable) @@ -1048,7 +1046,7 @@ final class SelectableNSTextView: NSTextView { override func hitTest(_ point: NSPoint) -> NSView? { // if point is not in bounds, not us - guard NSPointInRect(point, bounds) else { return nil } + guard bounds.contains(point) else { return nil } // find character index for the point guard let lm = layoutManager, let tc = textContainer else { return self } @@ -1074,8 +1072,7 @@ final class SelectableNSTextView: NSTextView { let charIndex = characterIndexForInsertion(at: point) if charIndex < textStorage?.length ?? 0, - let link = textStorage?.attribute(.link, at: charIndex, effectiveRange: nil) - { + let link = textStorage?.attribute(.link, at: charIndex, effectiveRange: nil) { let url = (link as? URL) ?? (link as? String).flatMap(URL.init(string:)) if let url { if url.scheme == "artifact" { diff --git a/Packages/OsaurusCore/Views/Chat/StreamingMarkdownBalancer.swift b/Packages/OsaurusCore/Views/Chat/StreamingMarkdownBalancer.swift index e857448e0..bb8d8039f 100644 --- a/Packages/OsaurusCore/Views/Chat/StreamingMarkdownBalancer.swift +++ b/Packages/OsaurusCore/Views/Chat/StreamingMarkdownBalancer.swift @@ -128,9 +128,8 @@ enum StreamingMarkdownBalancer { // Ordered: digits followed by `.` or `)`, e.g. `1.`, `12)`. if trimmed.count >= 2, let last = trimmed.last, - (last == "." || last == ")"), - trimmed.dropLast().allSatisfy({ $0.isNumber }) - { + last == "." || last == ")", + trimmed.dropLast().allSatisfy({ $0.isNumber }) { return true } return false diff --git a/Packages/OsaurusCore/Views/Common/CodeBlockView.swift b/Packages/OsaurusCore/Views/Common/CodeBlockView.swift index 8330f38d7..327726722 100644 --- a/Packages/OsaurusCore/Views/Common/CodeBlockView.swift +++ b/Packages/OsaurusCore/Views/Common/CodeBlockView.swift @@ -76,7 +76,7 @@ struct CodeBlockView: View { @State private var isHovered = false var body: some View { - let _ = ensureHighlightrTheme(for: theme) + let _ = ensureHighlightrTheme(for: theme) // swiftlint:disable:this redundant_discardable_let let bgColor = highlightrThemeBackgroundColor() VStack(alignment: .leading, spacing: 0) { @@ -318,7 +318,7 @@ final class CodeNSTextView: NSTextView { override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } override func hitTest(_ point: NSPoint) -> NSView? { - if NSPointInRect(point, bounds) { return self } + if bounds.contains(point) { return self } return nil } @@ -330,8 +330,7 @@ final class CodeNSTextView: NSTextView { let point = convert(event.locationInWindow, from: nil) let charIndex = characterIndexForInsertion(at: point) if charIndex < textStorage?.length ?? 0, - let link = textStorage?.attribute(.link, at: charIndex, effectiveRange: nil) - { + let link = textStorage?.attribute(.link, at: charIndex, effectiveRange: nil) { let url = (link as? URL) ?? (link as? String).flatMap(URL.init(string:)) if let url { NSWorkspace.shared.open(url); return } } diff --git a/Packages/OsaurusCore/Views/Common/GlassBackground.swift b/Packages/OsaurusCore/Views/Common/GlassBackground.swift index 361a79548..1e7333f1a 100644 --- a/Packages/OsaurusCore/Views/Common/GlassBackground.swift +++ b/Packages/OsaurusCore/Views/Common/GlassBackground.swift @@ -31,10 +31,10 @@ final class GlassContainerView: NSView { private var _lastHasCustomCorners = false private var _lastCornerRadius: CGFloat = -1 private var _lastMaskBounds: CGRect = .zero - private var _lastTopLeading: CGFloat? = nil - private var _lastBottomLeading: CGFloat? = nil - private var _lastTopTrailing: CGFloat? = nil - private var _lastBottomTrailing: CGFloat? = nil + private var _lastTopLeading: CGFloat? + private var _lastBottomLeading: CGFloat? + private var _lastTopTrailing: CGFloat? + private var _lastBottomTrailing: CGFloat? init(baseGlassView: NSVisualEffectView, edgeLightingView: NSView, tintOverlayView: NSView) { self.baseGlassView = baseGlassView diff --git a/Packages/OsaurusCore/Views/Common/NotchView.swift b/Packages/OsaurusCore/Views/Common/NotchView.swift index fc942a53e..23f4d80e4 100644 --- a/Packages/OsaurusCore/Views/Common/NotchView.swift +++ b/Packages/OsaurusCore/Views/Common/NotchView.swift @@ -657,8 +657,7 @@ struct NotchView: View { for item in recent { if let cur = current, - cur.kind == item.kind, cur.title == item.title, cur.detail == item.detail - { + cur.kind == item.kind, cur.title == item.title, cur.detail == item.detail { currentCount += 1 } else { flush() diff --git a/Packages/OsaurusCore/Views/Common/NotchWindowController.swift b/Packages/OsaurusCore/Views/Common/NotchWindowController.swift index d3aecdd99..0f63ddb0c 100644 --- a/Packages/OsaurusCore/Views/Common/NotchWindowController.swift +++ b/Packages/OsaurusCore/Views/Common/NotchWindowController.swift @@ -28,8 +28,7 @@ public struct NotchScreenMetrics: Equatable { var hasNotch = false if let topLeft = screen.auxiliaryTopLeftArea?.width, - let topRight = screen.auxiliaryTopRightArea?.width - { + let topRight = screen.auxiliaryTopRightArea?.width { width = screen.frame.width - topLeft - topRight + 4 hasNotch = true } @@ -72,7 +71,7 @@ public final class NotchWindowController: NSObject, ObservableObject { /// Panel height – tall enough for the largest expanded state. private static let panelHeight: CGFloat = 500 - private override init() { + override private init() { super.init() } @@ -154,6 +153,7 @@ public final class NotchWindowController: NSObject, ObservableObject { /// Teardown the notch window. public func teardown() { + // swiftlint:disable:next notification_center_detachment NotificationCenter.default.removeObserver(self) cancellables.removeAll() notchPanel?.close() @@ -173,8 +173,7 @@ public final class NotchWindowController: NSObject, ObservableObject { let targetScreen: NSScreen if let windowId = windowId, let chatWindow = ChatWindowManager.shared.getNSWindow(id: windowId), - let windowScreen = chatWindow.screen - { + let windowScreen = chatWindow.screen { targetScreen = windowScreen } else { targetScreen = NSScreen.main ?? NSScreen.screens.first! diff --git a/Packages/OsaurusCore/Views/Common/SettingsEmptyState.swift b/Packages/OsaurusCore/Views/Common/SettingsEmptyState.swift index ca77115ac..417f295b0 100644 --- a/Packages/OsaurusCore/Views/Common/SettingsEmptyState.swift +++ b/Packages/OsaurusCore/Views/Common/SettingsEmptyState.swift @@ -34,7 +34,7 @@ struct SettingsEmptyState: View { let subtitle: String let examples: [Example] let primaryAction: Action - var secondaryAction: Action? = nil + var secondaryAction: Action? let hasAppeared: Bool @State private var glowIntensity: CGFloat = 0.6 diff --git a/Packages/OsaurusCore/Views/Common/SharedHeaderComponents.swift b/Packages/OsaurusCore/Views/Common/SharedHeaderComponents.swift index 0232d2ce3..6dc45a618 100644 --- a/Packages/OsaurusCore/Views/Common/SharedHeaderComponents.swift +++ b/Packages/OsaurusCore/Views/Common/SharedHeaderComponents.swift @@ -118,11 +118,11 @@ struct AgentPill: View { let activeAgentId: UUID let onSelectAgent: (UUID) -> Void var discoveredAgents: [DiscoveredAgent] = [] - var onSelectDiscoveredAgent: ((DiscoveredAgent) -> Void)? = nil - var activeDiscoveredAgent: DiscoveredAgent? = nil + var onSelectDiscoveredAgent: ((DiscoveredAgent) -> Void)? + var activeDiscoveredAgent: DiscoveredAgent? var pairedRelayAgents: [PairedRelayAgent] = [] - var onSelectRelayAgent: ((PairedRelayAgent) -> Void)? = nil - var activeRelayAgent: PairedRelayAgent? = nil + var onSelectRelayAgent: ((PairedRelayAgent) -> Void)? + var activeRelayAgent: PairedRelayAgent? @State private var isHovered = false @State private var isPopoverPresented = false diff --git a/Packages/OsaurusCore/Views/Common/TableView.swift b/Packages/OsaurusCore/Views/Common/TableView.swift index ffb3c203b..d0f4719c5 100644 --- a/Packages/OsaurusCore/Views/Common/TableView.swift +++ b/Packages/OsaurusCore/Views/Common/TableView.swift @@ -18,7 +18,7 @@ struct TableView: View { Grid(horizontalSpacing: 1, verticalSpacing: 1) { // Headers GridRow(alignment: .top) { - ForEach(Array(headers.enumerated()), id: \.offset) { index, header in + ForEach(Array(headers.enumerated()), id: \.offset) { _, header in TableCell( text: header, isHeader: true, @@ -30,7 +30,7 @@ struct TableView: View { // Rows ForEach(Array(rows.enumerated()), id: \.offset) { rowIndex, row in GridRow(alignment: .top) { - ForEach(Array(row.enumerated()), id: \.offset) { colIndex, cell in + ForEach(Array(row.enumerated()), id: \.offset) { _, cell in TableCell( text: cell, isHeader: false, diff --git a/Packages/OsaurusCore/Views/Insights/InsightsDetailPane.swift b/Packages/OsaurusCore/Views/Insights/InsightsDetailPane.swift index 435bf4e91..33ab9fd4b 100644 --- a/Packages/OsaurusCore/Views/Insights/InsightsDetailPane.swift +++ b/Packages/OsaurusCore/Views/Insights/InsightsDetailPane.swift @@ -938,7 +938,7 @@ private struct DetailRow: View { let label: Text let value: String - var valueColor: Color? = nil + var valueColor: Color? var body: some View { HStack(alignment: .firstTextBaseline) { diff --git a/Packages/OsaurusCore/Views/Insights/InsightsView.swift b/Packages/OsaurusCore/Views/Insights/InsightsView.swift index b601eb131..cfea7f3a0 100644 --- a/Packages/OsaurusCore/Views/Insights/InsightsView.swift +++ b/Packages/OsaurusCore/Views/Insights/InsightsView.swift @@ -359,8 +359,7 @@ where Filter: Hashable, Filter: CaseIterable, Filter: RawRepresentable, - Filter.RawValue == String -{ + Filter.RawValue == String { @Binding var selection: Filter let tint: (Filter) -> Color diff --git a/Packages/OsaurusCore/Views/Management/ManagerHeader.swift b/Packages/OsaurusCore/Views/Management/ManagerHeader.swift index b197feb64..58d5131a0 100644 --- a/Packages/OsaurusCore/Views/Management/ManagerHeader.swift +++ b/Packages/OsaurusCore/Views/Management/ManagerHeader.swift @@ -310,7 +310,7 @@ struct HeaderIconButton: View { let icon: String let action: () -> Void var isLoading: Bool = false - var help: String? = nil + var help: String? @State private var isHovering = false diff --git a/Packages/OsaurusCore/Views/Memory/MemoryComponents.swift b/Packages/OsaurusCore/Views/Memory/MemoryComponents.swift index b4ba60dfa..1558511d8 100644 --- a/Packages/OsaurusCore/Views/Memory/MemoryComponents.swift +++ b/Packages/OsaurusCore/Views/Memory/MemoryComponents.swift @@ -246,7 +246,7 @@ struct MemorySectionCard: View { let title: String let icon: String - var count: Int? = nil + var count: Int? let trailing: Trailing let content: Content diff --git a/Packages/OsaurusCore/Views/Memory/MemoryView.swift b/Packages/OsaurusCore/Views/Memory/MemoryView.swift index e078d6e2a..df736bfbb 100644 --- a/Packages/OsaurusCore/Views/Memory/MemoryView.swift +++ b/Packages/OsaurusCore/Views/Memory/MemoryView.swift @@ -534,9 +534,7 @@ struct MemoryView: View { ScrollView { VStack(alignment: .leading, spacing: 0) { - ForEach(Array(defaultAgentEpisodes.enumerated()), id: \.element.id) { - index, - episode in + ForEach(Array(defaultAgentEpisodes.enumerated()), id: \.element.id) { index, episode in if index > 0 { Divider().opacity(0.5) } diff --git a/Packages/OsaurusCore/Views/Model/ModelDetailView.swift b/Packages/OsaurusCore/Views/Model/ModelDetailView.swift index b45bd9813..b0fb37f2d 100644 --- a/Packages/OsaurusCore/Views/Model/ModelDetailView.swift +++ b/Packages/OsaurusCore/Views/Model/ModelDetailView.swift @@ -32,16 +32,16 @@ struct ModelDetailView: View, Identifiable { // MARK: - State /// Estimated download size in bytes (nil if not yet calculated) - @State private var estimatedSize: Int64? = nil + @State private var estimatedSize: Int64? /// Whether a size estimation is currently in progress @State private var isEstimating = false /// Error message if size estimation fails - @State private var estimateError: String? = nil + @State private var estimateError: String? /// Hugging Face model details (loaded asynchronously) - @State private var hfDetails: HuggingFaceService.ModelDetails? = nil + @State private var hfDetails: HuggingFaceService.ModelDetails? /// Whether HF details are currently loading @State private var isLoadingHFDetails = false diff --git a/Packages/OsaurusCore/Views/Model/ModelDownloadView.swift b/Packages/OsaurusCore/Views/Model/ModelDownloadView.swift index eefe90b3d..751166caa 100644 --- a/Packages/OsaurusCore/Views/Model/ModelDownloadView.swift +++ b/Packages/OsaurusCore/Views/Model/ModelDownloadView.swift @@ -71,13 +71,13 @@ struct ModelDownloadView: View { @State private var selectedTab: ModelListTab = .all /// Debounce task for the remote Hugging Face fetch. - @State private var searchDebounceTask: Task? = nil + @State private var searchDebounceTask: Task? /// Debounce task for the local filter / animation trigger. - @State private var localSearchDebounceTask: Task? = nil + @State private var localSearchDebounceTask: Task? /// Model to show in the detail sheet - @State private var modelToShowDetails: MLXModel? = nil + @State private var modelToShowDetails: MLXModel? /// Content has appeared (for entrance animation) @State private var hasAppeared = false @@ -96,10 +96,10 @@ struct ModelDownloadView: View { // MARK: - Deep Link Support /// Optional model ID for deep linking (e.g., from URL schemes) - var deeplinkModelId: String? = nil + var deeplinkModelId: String? /// Optional file path for deep linking - var deeplinkFile: String? = nil + var deeplinkFile: String? var body: some View { // compute the grid lists once per body pass and thread them down @@ -1379,7 +1379,7 @@ private struct HuggingFaceImportSheet: View { let onImported: (String) -> Void @State private var inputText: String = "" - @State private var errorMessage: String? = nil + @State private var errorMessage: String? @State private var isResolving = false private var trimmedInput: String { diff --git a/Packages/OsaurusCore/Views/Model/ModelPickerTableRepresentable.swift b/Packages/OsaurusCore/Views/Model/ModelPickerTableRepresentable.swift index 3d51eb750..ff8cac6eb 100644 --- a/Packages/OsaurusCore/Views/Model/ModelPickerTableRepresentable.swift +++ b/Packages/OsaurusCore/Views/Model/ModelPickerTableRepresentable.swift @@ -826,13 +826,11 @@ extension ModelPickerTableRepresentable { highlightedIndex = offset > 0 ? 0 : flatModelIds.count - 1 } if let old = oldIndex, old < flatModelIds.count, - let rowIdx = modelIdToRowIndex[flatModelIds[old]] - { + let rowIdx = modelIdToRowIndex[flatModelIds[old]] { reconfigureCell(at: rowIdx) } if let new = highlightedIndex, new < flatModelIds.count, - let rowIdx = modelIdToRowIndex[flatModelIds[new]] - { + let rowIdx = modelIdToRowIndex[flatModelIds[new]] { reconfigureCell(at: rowIdx) tableView?.scrollRowToVisible(rowIdx) } diff --git a/Packages/OsaurusCore/Views/Model/ModelRowView.swift b/Packages/OsaurusCore/Views/Model/ModelRowView.swift index 50f61b8bc..ebf28a514 100644 --- a/Packages/OsaurusCore/Views/Model/ModelRowView.swift +++ b/Packages/OsaurusCore/Views/Model/ModelRowView.swift @@ -423,8 +423,7 @@ enum ModelCardGradient { /// - Returns: Repository name in "organization/model" format, or the full URL if parsing fails func repositoryName(from urlString: String) -> String { if let url = URL(string: urlString), - url.host == "huggingface.co" - { + url.host == "huggingface.co" { let pathComponents = url.pathComponents.filter { $0 != "/" } if pathComponents.count >= 2 { return "\(pathComponents[0])/\(pathComponents[1])" diff --git a/Packages/OsaurusCore/Views/Onboarding/OnboardingConfigureAIView.swift b/Packages/OsaurusCore/Views/Onboarding/OnboardingConfigureAIView.swift index 8f70eab27..38b41abbe 100644 --- a/Packages/OsaurusCore/Views/Onboarding/OnboardingConfigureAIView.swift +++ b/Packages/OsaurusCore/Views/Onboarding/OnboardingConfigureAIView.swift @@ -119,18 +119,18 @@ final class ConfigureAIState: ObservableObject { @Published var substateDirection: OnboardingDirection = .forward // Local - @Published var selectedModel: MLXModel? = nil + @Published var selectedModel: MLXModel? @Published var showDownloadError = false @Published var downloadErrorMessage = "" // API @Published var apiKey: String = "" @Published var openAIAuthMode: OpenAIProviderCredentialMode = .chatGPTSubscription - @Published var oauthTokens: RemoteProviderOAuthTokens? = nil + @Published var oauthTokens: RemoteProviderOAuthTokens? @Published var customForm = CustomProviderForm() @Published var isTesting = false @Published var isSaving = false - @Published var testResult: APITestResult? = nil + @Published var testResult: APITestResult? init() { let foundation = FoundationModelService.isDefaultModelAvailable() diff --git a/Packages/OsaurusCore/Views/Onboarding/OnboardingFields.swift b/Packages/OsaurusCore/Views/Onboarding/OnboardingFields.swift index 3c5e46f8c..b2a0a2ded 100644 --- a/Packages/OsaurusCore/Views/Onboarding/OnboardingFields.swift +++ b/Packages/OsaurusCore/Views/Onboarding/OnboardingFields.swift @@ -59,7 +59,7 @@ private struct OnboardingFieldLabel: View { struct OnboardingSecureField: View { let placeholder: String @Binding var text: String - var label: String? = nil + var label: String? @Environment(\.theme) private var theme @FocusState private var isFocused: Bool diff --git a/Packages/OsaurusCore/Views/Onboarding/OnboardingWalkthroughView.swift b/Packages/OsaurusCore/Views/Onboarding/OnboardingWalkthroughView.swift index a50479172..3f2ffdd98 100644 --- a/Packages/OsaurusCore/Views/Onboarding/OnboardingWalkthroughView.swift +++ b/Packages/OsaurusCore/Views/Onboarding/OnboardingWalkthroughView.swift @@ -201,8 +201,7 @@ struct WalkthroughBody: View { // page feels rubbery rather than free. let raw = value.translation.width if (state.pageIndex == 0 && raw > 0) - || (state.isLastPage && raw < 0) - { + || (state.isLastPage && raw < 0) { dragOffset = raw / 3 } else { dragOffset = raw diff --git a/Packages/OsaurusCore/Views/Plugin/PluginConfigView.swift b/Packages/OsaurusCore/Views/Plugin/PluginConfigView.swift index fa18b00a4..bb6e79e1e 100644 --- a/Packages/OsaurusCore/Views/Plugin/PluginConfigView.swift +++ b/Packages/OsaurusCore/Views/Plugin/PluginConfigView.swift @@ -514,8 +514,7 @@ struct PluginConfigView: View { } else { if let connectAction = field.connect_action, connectAction.type == "oauth", - let routeId = connectAction.url_route - { + let routeId = connectAction.url_route { Button { let base = Self.resolveBaseURL(for: pluginId, agentId: agentId) let url = URL(string: "\(base)/plugins/\(pluginId)/\(routeId)")! @@ -572,16 +571,14 @@ struct PluginConfigView: View { for section in configSpec.sections { for field in section.fields { if values[field.key] == nil, field.type != .readonly, field.type != .status, - let val = ToolSecretsKeychain.getSecret(id: field.key, for: pluginId, agentId: agentId) - { + let val = ToolSecretsKeychain.getSecret(id: field.key, for: pluginId, agentId: agentId) { values[field.key] = val } if values[field.key] == nil, let def = field.default { values[field.key] = def.stringValue } if let connKey = field.connected_when, values[connKey] == nil, - let val = ToolSecretsKeychain.getSecret(id: connKey, for: pluginId, agentId: agentId) - { + let val = ToolSecretsKeychain.getSecret(id: connKey, for: pluginId, agentId: agentId) { values[connKey] = val } } @@ -596,10 +593,8 @@ struct PluginConfigView: View { private func saveConfig() { var hasErrors = false for section in configSpec.sections { - for field in section.fields { - if !validateField(key: field.key) { - hasErrors = true - } + for field in section.fields where !validateField(key: field.key) { + hasErrors = true } } guard !hasErrors else { return } @@ -651,8 +646,7 @@ struct PluginConfigView: View { if let pattern = validation.pattern, let regex = try? NSRegularExpression(pattern: pattern), - regex.firstMatch(in: value, range: NSRange(value.startIndex..., in: value)) == nil - { + regex.firstMatch(in: value, range: NSRange(value.startIndex..., in: value)) == nil { errors[key] = validation.pattern_hint ?? "Invalid format" return false } diff --git a/Packages/OsaurusCore/Views/Plugin/PluginsView.swift b/Packages/OsaurusCore/Views/Plugin/PluginsView.swift index 2f4c52d9c..0439e7213 100644 --- a/Packages/OsaurusCore/Views/Plugin/PluginsView.swift +++ b/Packages/OsaurusCore/Views/Plugin/PluginsView.swift @@ -109,8 +109,7 @@ struct PluginsView: View { } .onReceive(PluginRepositoryService.shared.$plugins) { newPlugins in if let selected = selectedPlugin, - let updated = newPlugins.first(where: { $0.pluginId == selected.pluginId }) - { + let updated = newPlugins.first(where: { $0.pluginId == selected.pluginId }) { selectedPlugin = updated } Task { await updateFilteredLists() } @@ -1181,8 +1180,7 @@ private struct PluginDetailView: View { } if plugin.isInstalled && !plugin.hasLoadError, - let webConfig = loadedPlugin?.webConfig - { + let webConfig = loadedPlugin?.webConfig { Button { let port = loadServerPort() // Browsers cannot set the X-Osaurus-Agent-Id header @@ -1482,8 +1480,7 @@ private struct PluginDetailView: View { private var externalLinksSection: some View { if let loaded = loadedPlugin, let links = loaded.plugin.manifest.docs?.links, - !links.isEmpty - { + !links.isEmpty { detailSection(title: "Links", icon: "link") { HStack(spacing: 12) { ForEach(links, id: \.url) { link in diff --git a/Packages/OsaurusCore/Views/Plugin/ToolPermissionView.swift b/Packages/OsaurusCore/Views/Plugin/ToolPermissionView.swift index a2566f6cf..1410d0080 100644 --- a/Packages/OsaurusCore/Views/Plugin/ToolPermissionView.swift +++ b/Packages/OsaurusCore/Views/Plugin/ToolPermissionView.swift @@ -40,7 +40,7 @@ struct ToolPermissionView: View { else { return argumentsJSON } - return String(decoding: pretty, as: UTF8.self) + return String(bytes: pretty, encoding: .utf8) ?? "" } private var hasArguments: Bool { diff --git a/Packages/OsaurusCore/Views/Plugin/ToolsManagerView.swift b/Packages/OsaurusCore/Views/Plugin/ToolsManagerView.swift index 0a719fc08..247758ef2 100644 --- a/Packages/OsaurusCore/Views/Plugin/ToolsManagerView.swift +++ b/Packages/OsaurusCore/Views/Plugin/ToolsManagerView.swift @@ -72,8 +72,7 @@ struct ToolsManagerView: View { .onReceive(NotificationCenter.default.publisher(for: .toolsListChanged)) { _ in reload() } - .onReceive(NotificationCenter.default.publisher(for: Foundation.Notification.Name.mcpProviderStatusChanged)) { - _ in + .onReceive(NotificationCenter.default.publisher(for: Foundation.Notification.Name.mcpProviderStatusChanged)) { _ in remoteProviderCount = providerManager.configuration.providers.count reload() } diff --git a/Packages/OsaurusCore/Views/Schedule/SchedulesView.swift b/Packages/OsaurusCore/Views/Schedule/SchedulesView.swift index 45d9d4286..427aa0034 100644 --- a/Packages/OsaurusCore/Views/Schedule/SchedulesView.swift +++ b/Packages/OsaurusCore/Views/Schedule/SchedulesView.swift @@ -65,9 +65,7 @@ struct SchedulesView: View { ], spacing: 20 ) { - ForEach(Array(scheduleManager.schedules.enumerated()), id: \.element.id) { - index, - schedule in + ForEach(Array(scheduleManager.schedules.enumerated()), id: \.element.id) { index, schedule in ScheduleCard( schedule: schedule, isRunning: scheduleManager.isRunning(schedule.id), @@ -1553,7 +1551,7 @@ struct ScheduleEditorSheet: View { let mode: Mode let onSave: (Schedule) -> Void let onCancel: () -> Void - var initialAgentId: UUID? = nil + var initialAgentId: UUID? @State private var name = "" @State private var instructions = "" diff --git a/Packages/OsaurusCore/Views/Settings/ConfigurationView.swift b/Packages/OsaurusCore/Views/Settings/ConfigurationView.swift index 02c4cf5b6..d82b03e03 100644 --- a/Packages/OsaurusCore/Views/Settings/ConfigurationView.swift +++ b/Packages/OsaurusCore/Views/Settings/ConfigurationView.swift @@ -12,14 +12,14 @@ struct ConfigurationView: View { @State private var tempExposeToNetwork: Bool = false @State private var tempStartAtLogin: Bool = false @State private var tempHideDockIcon: Bool = false - @State private var cliInstallMessage: String? = nil + @State private var cliInstallMessage: String? @State private var cliInstallSuccess: Bool = false @State private var hasAppeared = false @State private var successMessage: String? @State private var isResetting = false // Chat settings state - @State private var tempChatHotkey: Hotkey? = nil + @State private var tempChatHotkey: Hotkey? @State private var tempSystemPrompt: String = "" @State private var tempChatTemperature: String = "" @State private var tempChatMaxTokens: String = "" @@ -1008,8 +1008,7 @@ struct ConfigurationView: View { // on macOS < 26, a disconnected remote model) with an // "(unavailable)" hint so the row isn't an unlabelled orphan. if !coreModelIdentifierBinding.wrappedValue.isEmpty, - !coreModelPickerItems.contains(where: { $0.id == coreModelIdentifierBinding.wrappedValue }) - { + !coreModelPickerItems.contains(where: { $0.id == coreModelIdentifierBinding.wrappedValue }) { Text("\(coreModelIdentifierBinding.wrappedValue) (unavailable)", bundle: .module) .tag(coreModelIdentifierBinding.wrappedValue) } @@ -1335,7 +1334,7 @@ private struct SettingsField: View { @ObservedObject private var themeManager = ThemeManager.shared let label: String - var hint: String? = nil + var hint: String? @ViewBuilder let content: () -> Content var body: some View { @@ -1740,7 +1739,7 @@ private struct SettingsToggle: View { let title: String let description: String - var badge: String? = nil + var badge: String? @Binding var isOn: Bool var body: some View { @@ -1889,8 +1888,7 @@ private struct VoiceSettingsSection: View { return "Loading model..." } else if speechService.isModelLoaded { if let modelId = speechService.loadedModelId, - let model = modelManager.availableModels.first(where: { $0.id == modelId }) - { + let model = modelManager.availableModels.first(where: { $0.id == modelId }) { return model.name } return "Model Loaded" diff --git a/Packages/OsaurusCore/Views/Settings/DirectoryPickerView.swift b/Packages/OsaurusCore/Views/Settings/DirectoryPickerView.swift index d5dcbf66a..ca002844a 100644 --- a/Packages/OsaurusCore/Views/Settings/DirectoryPickerView.swift +++ b/Packages/OsaurusCore/Views/Settings/DirectoryPickerView.swift @@ -114,8 +114,7 @@ struct DirectoryPickerView: View { private var directoryDisplayText: String { if directoryPicker.hasValidDirectory, - let selectedDirectory = directoryPicker.selectedDirectory - { + let selectedDirectory = directoryPicker.selectedDirectory { return selectedDirectory.path } else { // Show effective default (env override, old default if exists, else new default) diff --git a/Packages/OsaurusCore/Views/Settings/PermissionsView.swift b/Packages/OsaurusCore/Views/Settings/PermissionsView.swift index 56f869604..7e8966380 100644 --- a/Packages/OsaurusCore/Views/Settings/PermissionsView.swift +++ b/Packages/OsaurusCore/Views/Settings/PermissionsView.swift @@ -108,7 +108,7 @@ private struct SystemPermissionRow: View { let permission: SystemPermission @State private var isTesting = false - @State private var testResult: String? = nil + @State private var testResult: String? @State private var isHovered = false private var isGranted: Bool { diff --git a/Packages/OsaurusCore/Views/Settings/ProvidersView.swift b/Packages/OsaurusCore/Views/Settings/ProvidersView.swift index 9576205c1..b1bd0a21d 100644 --- a/Packages/OsaurusCore/Views/Settings/ProvidersView.swift +++ b/Packages/OsaurusCore/Views/Settings/ProvidersView.swift @@ -23,9 +23,7 @@ struct ProvidersView: View { if manager.configuration.providers.isEmpty { emptyState } else { - ForEach(Array(manager.configuration.providers.enumerated()), id: \.element.id) { - index, - provider in + ForEach(Array(manager.configuration.providers.enumerated()), id: \.element.id) { index, provider in ProviderCard( provider: provider, state: manager.providerStates[provider.id], @@ -897,7 +895,7 @@ private struct ProviderEditSheet: View { // Note: Token not loaded for security - user must re-enter if changing } - private func testConnection() { + func testConnection() { isTesting = true testResult = nil diff --git a/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift b/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift index 3e7657898..8c7913277 100644 --- a/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift +++ b/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift @@ -296,8 +296,7 @@ private struct AddProviderFlow: View { // Help section if let preset = selectedPreset, preset.isKnown, !preset.consoleURL.isEmpty, - preset != .openai || openAIAuthMode == .platformAPIKey - { + preset != .openai || openAIAuthMode == .platformAPIKey { helpSection(for: preset) } diff --git a/Packages/OsaurusCore/Views/Settings/ServerView.swift b/Packages/OsaurusCore/Views/Settings/ServerView.swift index 911f95ee1..f362baeaa 100644 --- a/Packages/OsaurusCore/Views/Settings/ServerView.swift +++ b/Packages/OsaurusCore/Views/Settings/ServerView.swift @@ -26,7 +26,7 @@ private let sharedMediumDateFormatter: DateFormatter = { return f }() -private nonisolated(unsafe) let sharedByteCountFormatter: ByteCountFormatter = { +nonisolated(unsafe) private let sharedByteCountFormatter: ByteCountFormatter = { let f = ByteCountFormatter() f.countStyle = .file return f @@ -929,8 +929,7 @@ private struct APIReferenceTabContent: View { onToggleExpand: { toggleEndpoint(endpoint.id) if expandedEndpoint == endpoint.id, - editablePayloads[endpoint.id] == nil - { + editablePayloads[endpoint.id] == nil { editablePayloads[endpoint.id] = endpoint.examplePayload ?? "{}" } }, @@ -1088,16 +1087,16 @@ private struct APIReferenceTabContent: View { request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") var body = Data() - body.append("--\(boundary)\r\n".data(using: .utf8)!) + body.append(Data("--\(boundary)\r\n".utf8)) body.append( - "Content-Disposition: form-data; name=\"file\"; filename=\"recording.wav\"\r\n".data(using: .utf8)! + Data("Content-Disposition: form-data; name=\"file\"; filename=\"recording.wav\"\r\n".utf8) ) - body.append("Content-Type: audio/wav\r\n\r\n".data(using: .utf8)!) + body.append(Data("Content-Type: audio/wav\r\n\r\n".utf8)) body.append(audioData) - body.append("\r\n--\(boundary)\r\n".data(using: .utf8)!) - body.append("Content-Disposition: form-data; name=\"model\"\r\n\r\n".data(using: .utf8)!) - body.append(modelId.data(using: .utf8)!) - body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!) + body.append(Data("\r\n--\(boundary)\r\n".utf8)) + body.append(Data("Content-Disposition: form-data; name=\"model\"\r\n\r\n".utf8)) + body.append(Data(modelId.utf8)) + body.append(Data("\r\n--\(boundary)--\r\n".utf8)) request.httpBody = body let (data, response) = try await URLSession.shared.data(for: request) @@ -1451,8 +1450,7 @@ struct EndpointTestResult: Equatable { if let error { return "Error: \(error)" } if let json = try? JSONSerialization.jsonObject(with: body, options: []), let prettyData = try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted, .sortedKeys]), - let prettyString = String(data: prettyData, encoding: .utf8) - { + let prettyString = String(data: prettyData, encoding: .utf8) { return prettyString } return String(data: body, encoding: .utf8) ?? "(Unable to decode response)" diff --git a/Packages/OsaurusCore/Views/Settings/SlashCommandsSettingsSection.swift b/Packages/OsaurusCore/Views/Settings/SlashCommandsSettingsSection.swift index a1d1a1c63..0d72dc76a 100644 --- a/Packages/OsaurusCore/Views/Settings/SlashCommandsSettingsSection.swift +++ b/Packages/OsaurusCore/Views/Settings/SlashCommandsSettingsSection.swift @@ -15,7 +15,7 @@ struct SlashCommandsSettingsSection: View { private var registry = SlashCommandRegistry.shared @State private var showAddSheet = false - @State private var editingCommand: SlashCommand? = nil + @State private var editingCommand: SlashCommand? private var theme: ThemeProtocol { themeManager.currentTheme } @@ -198,7 +198,7 @@ struct SlashCommandEditorSheet: View { @State private var description: String = "" @State private var template: String = "" @State private var icon: String = "text.bubble" - @State private var nameError: String? = nil + @State private var nameError: String? private var theme: ThemeProtocol { themeManager.currentTheme } private var isEditing: Bool { command != nil } diff --git a/Packages/OsaurusCore/Views/SlashCommand/SlashCommandsView.swift b/Packages/OsaurusCore/Views/SlashCommand/SlashCommandsView.swift index b64ac3e1e..c6cee197e 100644 --- a/Packages/OsaurusCore/Views/SlashCommand/SlashCommandsView.swift +++ b/Packages/OsaurusCore/Views/SlashCommand/SlashCommandsView.swift @@ -15,7 +15,7 @@ struct SlashCommandsView: View { private var theme: ThemeProtocol { themeManager.currentTheme } @State private var isCreating = false - @State private var editingCommand: SlashCommand? = nil + @State private var editingCommand: SlashCommand? @State private var hasAppeared = false var body: some View { diff --git a/Packages/OsaurusCore/Views/Theme/ThemeEditorView.swift b/Packages/OsaurusCore/Views/Theme/ThemeEditorView.swift index 2d2995fe3..a634c1e1f 100644 --- a/Packages/OsaurusCore/Views/Theme/ThemeEditorView.swift +++ b/Packages/OsaurusCore/Views/Theme/ThemeEditorView.swift @@ -497,8 +497,7 @@ struct ThemeEditorView: View { VStack(spacing: 12) { if let imageData = editingTheme.background.imageData, let data = Data(base64Encoded: imageData), - let nsImage = NSImage(data: data) - { + let nsImage = NSImage(data: data) { Image(nsImage: nsImage) .resizable() .aspectRatio(contentMode: .fill) @@ -1067,7 +1066,7 @@ struct ThemeChatPreview: View { private var previewCodeBlock: some View { let themeProtocol = CustomizableTheme(config: theme) let sampleCode = "print(\"Hello, World!\")" - let _ = ensureHighlightrTheme(for: themeProtocol) + _ = ensureHighlightrTheme(for: themeProtocol) let bgColor = highlightrThemeBackgroundColor() return VStack(alignment: .leading, spacing: 0) { @@ -1122,8 +1121,7 @@ struct ThemeChatPreview: View { case .image: if let imageData = theme.background.imageData, let data = Data(base64Encoded: imageData), - let nsImage = NSImage(data: data) - { + let nsImage = NSImage(data: data) { GeometryReader { geo in imageView(nsImage: nsImage, fit: theme.background.imageFit ?? .fill, size: geo.size) .opacity(theme.background.imageOpacity ?? 1.0) diff --git a/Packages/OsaurusCore/Views/Theme/ThemesView.swift b/Packages/OsaurusCore/Views/Theme/ThemesView.swift index d153c0a00..1d73a48f4 100644 --- a/Packages/OsaurusCore/Views/Theme/ThemesView.swift +++ b/Packages/OsaurusCore/Views/Theme/ThemesView.swift @@ -916,8 +916,7 @@ struct ThemePreviewCard: View { case .image: if let imageData = theme.background.imageData, let data = Data(base64Encoded: imageData), - let nsImage = NSImage(data: data) - { + let nsImage = NSImage(data: data) { Image(nsImage: nsImage) .resizable() .aspectRatio(contentMode: .fill) diff --git a/Packages/OsaurusCore/Views/Toast/ToastContainerView.swift b/Packages/OsaurusCore/Views/Toast/ToastContainerView.swift index 483098780..8d8c1f0ae 100644 --- a/Packages/OsaurusCore/Views/Toast/ToastContainerView.swift +++ b/Packages/OsaurusCore/Views/Toast/ToastContainerView.swift @@ -177,7 +177,7 @@ public final class ToastWindowController: NSObject { private var hostingView: NSHostingView? private var cancellables = Set() - private override init() { + override private init() { super.init() } @@ -244,6 +244,7 @@ public final class ToastWindowController: NSObject { /// Teardown the toast window public func teardown() { + // swiftlint:disable:next notification_center_detachment NotificationCenter.default.removeObserver(self) cancellables.removeAll() toastPanel?.close() @@ -263,8 +264,7 @@ public final class ToastWindowController: NSObject { let targetScreen: NSScreen if let windowId = windowId, let chatWindow = ChatWindowManager.shared.getNSWindow(id: windowId), - let windowScreen = chatWindow.screen - { + let windowScreen = chatWindow.screen { targetScreen = windowScreen } else { targetScreen = NSScreen.main ?? NSScreen.screens.first! diff --git a/Packages/OsaurusCore/Views/Voice/AudioSettingsTab.swift b/Packages/OsaurusCore/Views/Voice/AudioSettingsTab.swift index 08ff72c6c..c15039c7a 100644 --- a/Packages/OsaurusCore/Views/Voice/AudioSettingsTab.swift +++ b/Packages/OsaurusCore/Views/Voice/AudioSettingsTab.swift @@ -361,8 +361,7 @@ struct AudioSettingsTab: View { private var selectedDeviceName: String { if let selectedId = audioInputManager.selectedDeviceId, - let device = audioInputManager.availableDevices.first(where: { $0.id == selectedId }) - { + let device = audioInputManager.availableDevices.first(where: { $0.id == selectedId }) { return device.name } return L("System Default") diff --git a/Packages/OsaurusCore/Views/Voice/VoiceView.swift b/Packages/OsaurusCore/Views/Voice/VoiceView.swift index 11fd74575..036fe1fc7 100644 --- a/Packages/OsaurusCore/Views/Voice/VoiceView.swift +++ b/Packages/OsaurusCore/Views/Voice/VoiceView.swift @@ -76,8 +76,7 @@ struct VoiceView: View { .onAppear { // Honour an explicit cross-view request (e.g. from the chat speaker button). if let requested = managementState.voiceSubTabRequest, - let tab = VoiceTab(rawValue: requested) - { + let tab = VoiceTab(rawValue: requested) { selectedTab = tab managementState.voiceSubTabRequest = nil } else if isSetupComplete { diff --git a/Packages/OsaurusCore/Views/Watcher/WatchersView.swift b/Packages/OsaurusCore/Views/Watcher/WatchersView.swift index 4edee739e..e097d782e 100644 --- a/Packages/OsaurusCore/Views/Watcher/WatchersView.swift +++ b/Packages/OsaurusCore/Views/Watcher/WatchersView.swift @@ -65,9 +65,7 @@ struct WatchersView: View { ], spacing: 20 ) { - ForEach(Array(watcherManager.watchers.enumerated()), id: \.element.id) { - index, - watcher in + ForEach(Array(watcherManager.watchers.enumerated()), id: \.element.id) { index, watcher in WatcherCard( watcher: watcher, isRunning: watcherManager.isRunning(watcher.id), @@ -479,7 +477,7 @@ struct WatcherEditorSheet: View { let mode: Mode let onSave: (Watcher) -> Void let onCancel: () -> Void - var initialAgentId: UUID? = nil + var initialAgentId: UUID? @State private var name = "" @State private var instructions = "" diff --git a/Packages/OsaurusCore/Views/WhatsNew/WhatsNewView.swift b/Packages/OsaurusCore/Views/WhatsNew/WhatsNewView.swift index 1a981695e..bffc711b1 100644 --- a/Packages/OsaurusCore/Views/WhatsNew/WhatsNewView.swift +++ b/Packages/OsaurusCore/Views/WhatsNew/WhatsNewView.swift @@ -126,8 +126,7 @@ public struct WhatsNewModal: View { Spacer() // Optional per-page CTA between the chevrons. if let label = release.pages[currentIndex].actionLabel, - let action = release.pages[currentIndex].action - { + let action = release.pages[currentIndex].action { Button { onAction?(action) } label: { diff --git a/Packages/OsaurusRepository/CentralRepositoryManager.swift b/Packages/OsaurusRepository/CentralRepositoryManager.swift index 2363b939a..a0b71329c 100644 --- a/Packages/OsaurusRepository/CentralRepositoryManager.swift +++ b/Packages/OsaurusRepository/CentralRepositoryManager.swift @@ -109,8 +109,7 @@ public final class CentralRepositoryManager: @unchecked Sendable { for case let fileURL as URL in enumerator { if fileURL.pathExtension.lowercased() == "json", let data = try? Data(contentsOf: fileURL), - (try? JSONDecoder().decode(PluginSpec.self, from: data)) != nil - { + (try? JSONDecoder().decode(PluginSpec.self, from: data)) != nil { return true } } @@ -131,13 +130,10 @@ public final class CentralRepositoryManager: @unchecked Sendable { else { return specs } - for case let fileURL as URL in enumr { - if fileURL.pathExtension.lowercased() == "json" { - if let data = try? Data(contentsOf: fileURL), - let spec = try? JSONDecoder().decode(PluginSpec.self, from: data) - { - specs.append(spec) - } + for case let fileURL as URL in enumr where fileURL.pathExtension.lowercased() == "json" { + if let data = try? Data(contentsOf: fileURL), + let spec = try? JSONDecoder().decode(PluginSpec.self, from: data) { + specs.append(spec) } } return specs diff --git a/Packages/OsaurusRepository/MinisignVerifier.swift b/Packages/OsaurusRepository/MinisignVerifier.swift index 2a3c95a90..09bdb421f 100644 --- a/Packages/OsaurusRepository/MinisignVerifier.swift +++ b/Packages/OsaurusRepository/MinisignVerifier.swift @@ -104,7 +104,7 @@ public enum MinisignVerifier { for (index, line) in lines.enumerated() { // "RW" = Ed25519 (non-prehashed), "RU" = Ed25519ph (prehashed) - if (line.hasPrefix("RW") || line.hasPrefix("RU")), Data(base64Encoded: line) != nil { + if line.hasPrefix("RW") || line.hasPrefix("RU"), Data(base64Encoded: line) != nil { signatureLine = line // Next non-empty line after "trusted comment:" is the global signature for nextLine in lines[(index + 1)...] { diff --git a/Packages/OsaurusRepository/PluginInstallManager.swift b/Packages/OsaurusRepository/PluginInstallManager.swift index 59faa476b..1625d6310 100644 --- a/Packages/OsaurusRepository/PluginInstallManager.swift +++ b/Packages/OsaurusRepository/PluginInstallManager.swift @@ -385,10 +385,8 @@ public final class PluginInstallManager: @unchecked Sendable { return nil } let target = filename.lowercased() - for case let fileURL as URL in enumerator { - if fileURL.lastPathComponent.lowercased() == target { - return fileURL - } + for case let fileURL as URL in enumerator where fileURL.lastPathComponent.lowercased() == target { + return fileURL } return nil } @@ -406,10 +404,8 @@ public final class PluginInstallManager: @unchecked Sendable { return [] } var results: [URL] = [] - for case let fileURL as URL in enumerator { - if fileURL.lastPathComponent.uppercased() == "SKILL.MD" { - results.append(fileURL) - } + for case let fileURL as URL in enumerator where fileURL.lastPathComponent.uppercased() == "SKILL.MD" { + results.append(fileURL) } return results } @@ -425,10 +421,8 @@ public final class PluginInstallManager: @unchecked Sendable { else { return nil } - for case let fileURL as URL in enumerator { - if fileURL.pathExtension == "dylib" { - return fileURL - } + for case let fileURL as URL in enumerator where fileURL.pathExtension == "dylib" { + return fileURL } return nil } diff --git a/Packages/OsaurusRepository/PluginSpec.swift b/Packages/OsaurusRepository/PluginSpec.swift index c27ef0396..582aa6c9b 100644 --- a/Packages/OsaurusRepository/PluginSpec.swift +++ b/Packages/OsaurusRepository/PluginSpec.swift @@ -138,8 +138,7 @@ public extension PluginSpec { if let match = sorted.first(where: { $0.version == preferred }), let art = match.artifacts.first(where: { $0.os == targetPlatform.rawValue && $0.arch == targetArch.rawValue - }) - { + }) { return PluginResolution(spec: self, version: match, artifact: art) } } diff --git a/Packages/OsaurusRepository/Tests/OsaurusRepositoryTests/PluginInstallManagerTests.swift b/Packages/OsaurusRepository/Tests/OsaurusRepositoryTests/PluginInstallManagerTests.swift index f43399c03..6f694e938 100644 --- a/Packages/OsaurusRepository/Tests/OsaurusRepositoryTests/PluginInstallManagerTests.swift +++ b/Packages/OsaurusRepository/Tests/OsaurusRepositoryTests/PluginInstallManagerTests.swift @@ -45,8 +45,7 @@ final class PluginInstallManagerTests: XCTestCase { } private func makeVersionDir(pluginId: String, version: String, withReceipt: Bool = true) throws - -> URL - { + -> URL { let semver = SemanticVersion.parse(version)! let dir = PluginInstallManager.toolsVersionDirectory(pluginId: pluginId, version: semver) try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) diff --git a/Packages/OsaurusRepository/ToolsPaths.swift b/Packages/OsaurusRepository/ToolsPaths.swift index 19a623bc7..2107175b3 100644 --- a/Packages/OsaurusRepository/ToolsPaths.swift +++ b/Packages/OsaurusRepository/ToolsPaths.swift @@ -11,7 +11,7 @@ import Foundation public enum ToolsPaths { /// Optional root directory override for tests /// Note: nonisolated(unsafe) since this is only set during test setup before any concurrent access - public nonisolated(unsafe) static var overrideRoot: URL? + nonisolated(unsafe) public static var overrideRoot: URL? /// The root data directory for Osaurus: `~/.osaurus/` public static func root() -> URL { diff --git a/scripts/codex/local-ci.sh b/scripts/codex/local-ci.sh new file mode 100755 index 000000000..ddbed0ede --- /dev/null +++ b/scripts/codex/local-ci.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +cd "$ROOT" + +DERIVED_DATA_PATH="${OSAURUS_DERIVED_DATA_PATH:-$ROOT/build/CodexDerivedData}" +APP_DESTINATION="${OSAURUS_APP_DESTINATION:-platform=macOS,arch=$(uname -m)}" + +section() { + printf '\n==> %s\n' "$1" +} + +run_xcodebuild() { + if command -v xcbeautify >/dev/null 2>&1; then + set -o pipefail + xcodebuild "$@" | xcbeautify --renderer terminal + else + xcodebuild "$@" + fi +} + +section "swiftlint" +swiftlint lint --strict --config .swiftlint.yml + +section "shellcheck" +shell_scripts=() +if [[ "${OSAURUS_SHELLCHECK_SCOPE:-touched}" == "all" ]]; then + while IFS= read -r -d '' script; do + shell_scripts+=("$script") + done < <( + find . -type f -name '*.sh' \ + -not -path './.git/*' \ + -not -path './build/*' \ + -not -path './Packages/*/.build/*' \ + -print0 + ) +else + BASE_REF="${OSAURUS_BASE_REF:-origin/main}" + if ! git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then + GH_PROMPT_DISABLED=1 GIT_TERMINAL_PROMPT=0 git fetch origin --prune + fi + while IFS= read -r path; do + if [[ -f "$path" && "$path" == *.sh ]]; then + shell_scripts+=("$path") + fi + done < <( + { + git diff --name-only --diff-filter=ACMRT "$BASE_REF"...HEAD -- + git diff --name-only --diff-filter=ACMRT -- + git diff --cached --name-only --diff-filter=ACMRT -- + git ls-files --others --exclude-standard + } | sort -u + ) +fi + +if ((${#shell_scripts[@]} > 0)); then + shellcheck "${shell_scripts[@]}" +else + echo "No shell scripts found." +fi + +section "swift build OsaurusCore" +swift build --package-path Packages/OsaurusCore + +section "swift test OsaurusCore" +swift test --package-path Packages/OsaurusCore + +section "swift build app" +run_xcodebuild build \ + -workspace osaurus.xcworkspace \ + -scheme osaurus \ + -configuration Debug \ + -destination "$APP_DESTINATION" \ + -derivedDataPath "$DERIVED_DATA_PATH" \ + -skipPackagePluginValidation \ + -skipMacroValidation \ + CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY= \ + COMPILER_INDEX_STORE_ENABLE=NO + +section "test-cli equivalent" +swift build --package-path Packages/OsaurusCLI +swift test --package-path Packages/OsaurusCLI + +section "local CI parity complete" diff --git a/scripts/codex/pr-worktree.sh b/scripts/codex/pr-worktree.sh new file mode 100755 index 000000000..c102fbb90 --- /dev/null +++ b/scripts/codex/pr-worktree.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Usage: scripts/codex/pr-worktree.sh [branch] [base] + +Creates a task worktree under /tmp/osaurus-wt/. +Defaults: + branch: codex/ + base: origin/main +USAGE +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" || $# -lt 1 ]]; then + usage + exit 0 +fi + +TASK_ID="$1" +BRANCH="${2:-codex/$TASK_ID}" +BASE="${3:-origin/main}" +ROOT="$(git rev-parse --show-toplevel)" +WORKTREE_ROOT="${OSAURUS_WORKTREE_ROOT:-/tmp/osaurus-wt}" +WORKTREE="$WORKTREE_ROOT/$TASK_ID" + +case "$TASK_ID" in + *..* | /* | *//* | '') + echo "Invalid task id: $TASK_ID" >&2 + exit 2 + ;; +esac + +cd "$ROOT" +GH_PROMPT_DISABLED=1 GIT_TERMINAL_PROMPT=0 git fetch origin --prune +mkdir -p "$WORKTREE_ROOT" + +if [[ -e "$WORKTREE" ]]; then + echo "Worktree already exists: $WORKTREE" >&2 + exit 3 +fi + +if git show-ref --verify --quiet "refs/heads/$BRANCH"; then + git worktree add "$WORKTREE" "$BRANCH" +else + git worktree add -b "$BRANCH" "$WORKTREE" "$BASE" +fi + +git -C "$WORKTREE" status --short --branch From f0576ff3075363f3228371cc10dfca02e0ad5ef0 Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Thu, 7 May 2026 13:04:15 -0300 Subject: [PATCH 2/9] fix: return tool timeouts without task-group drain --- Packages/OsaurusCore/Tools/ToolRegistry.swift | 92 +++++++++---------- scripts/codex/local-ci.sh | 35 +++++++ 2 files changed, 81 insertions(+), 46 deletions(-) diff --git a/Packages/OsaurusCore/Tools/ToolRegistry.swift b/Packages/OsaurusCore/Tools/ToolRegistry.swift index 3d679aa62..ce5361d2b 100644 --- a/Packages/OsaurusCore/Tools/ToolRegistry.swift +++ b/Packages/OsaurusCore/Tools/ToolRegistry.swift @@ -428,13 +428,10 @@ final class ToolRegistry: ObservableObject { /// tests can drive it with a small `timeoutSeconds` value without /// waiting for the full 120s production budget. /// - /// Each branch of the race converts thrown errors (including - /// `CancellationError` from the loser when we `cancelAll`) into a - /// structured `ToolEnvelope` *inside* its child task. That keeps - /// `withTaskGroup` non-throwing and prevents the cancelled sibling's - /// post-return throw from reaching the caller as the function's - /// error — historically the slow-tool case rethrew CancellationError - /// and stalled while the group drained. + /// The race uses unstructured tasks so the caller can return the + /// timeout envelope immediately instead of waiting for a cancelled + /// tool body to drain. The losing task is still cancelled, but a + /// non-cooperative tool body may keep unwinding in the background. nonisolated internal static func runToolBody( _ tool: OsaurusTool, argumentsJSON: String, @@ -448,49 +445,52 @@ final class ToolRegistry: ObservableObject { tool: toolName, retryable: true ) - // Sentinel returned by the cancelled loser branch so the - // consumer loop knows to ignore it. Cannot collide with any - // legitimate envelope because real envelopes are JSON. - let cancelledSentinel = "__osaurus_runToolBody_cancelled__" - - return await withTaskGroup(of: String.self) { group in - group.addTask { - do { - return try await tool.execute(argumentsJSON: argumentsJSON) - } catch is CancellationError { - return cancelledSentinel - } catch { - return ToolEnvelope.fromError(error, tool: toolName) - } + let race = ToolBodyRaceResult() + let bodyTask = Task { + do { + let result = try await tool.execute(argumentsJSON: argumentsJSON) + await race.resolve(result) + } catch is CancellationError { + // Cancelled because the timeout branch won. + } catch { + await race.resolve(ToolEnvelope.fromError(error, tool: toolName)) } - group.addTask { - let nanos = UInt64(timeoutSeconds * 1_000_000_000) - do { - try await Task.sleep(nanoseconds: nanos) - } catch { - // Cancelled because the body finished first — yield - // the sentinel so the caller's first non-sentinel - // result wins. - return cancelledSentinel - } - return timeoutEnvelope + } + let timeoutTask = Task { + let nanos = UInt64(max(0, timeoutSeconds) * 1_000_000_000) + do { + try await Task.sleep(nanoseconds: nanos) + } catch { + // Cancelled because the body branch won. + return } + await race.resolve(timeoutEnvelope) + } + + let result = await race.wait() + bodyTask.cancel() + timeoutTask.cancel() + return result + } + + private actor ToolBodyRaceResult { + private var value: String? + private var continuation: CheckedContinuation? - // The first non-sentinel result is the winner; cancel the - // sibling and let `withTaskGroup` auto-drain on closure - // return. The drain is safe because every child branch - // converts its own errors into envelope strings — there - // are no uncaught throws to surface. - for await result in group { - if result == cancelledSentinel { continue } - group.cancelAll() - return result + func wait() async -> String { + if let value { + return value } - return ToolEnvelope.failure( - kind: .executionError, - message: "Tool '\(toolName)' produced no result.", - tool: toolName - ) + return await withCheckedContinuation { continuation in + self.continuation = continuation + } + } + + func resolve(_ value: String) { + guard self.value == nil else { return } + self.value = value + continuation?.resume(returning: value) + continuation = nil } } diff --git a/scripts/codex/local-ci.sh b/scripts/codex/local-ci.sh index ddbed0ede..c28a98946 100755 --- a/scripts/codex/local-ci.sh +++ b/scripts/codex/local-ci.sh @@ -6,6 +6,9 @@ cd "$ROOT" DERIVED_DATA_PATH="${OSAURUS_DERIVED_DATA_PATH:-$ROOT/build/CodexDerivedData}" APP_DESTINATION="${OSAURUS_APP_DESTINATION:-platform=macOS,arch=$(uname -m)}" +SPM_CACHE="${OSAURUS_SPM_CACHE:-$ROOT/.spm-cache}" +CORE_XCRESULT_PATH="${OSAURUS_CORE_XCRESULT_PATH:-$ROOT/build/CodexCoreTests.xcresult}" +RUN_XCODE_CORE_TESTS="${OSAURUS_RUN_XCODE_CORE_TESTS:-1}" section() { printf '\n==> %s\n' "$1" @@ -66,6 +69,38 @@ swift build --package-path Packages/OsaurusCore section "swift test OsaurusCore" swift test --package-path Packages/OsaurusCore +if [[ "$RUN_XCODE_CORE_TESTS" == "1" ]]; then + section "resolve OsaurusCoreTests dependencies" + xcodebuild -resolvePackageDependencies \ + -workspace osaurus.xcworkspace \ + -scheme OsaurusCoreTests \ + -derivedDataPath "$DERIVED_DATA_PATH" \ + -clonedSourcePackagesDirPath "$SPM_CACHE" \ + -quiet + + section "xcodebuild test OsaurusCoreTests" + rm -rf "$CORE_XCRESULT_PATH" + run_xcodebuild test \ + -workspace osaurus.xcworkspace \ + -scheme OsaurusCoreTests \ + -destination "$APP_DESTINATION" \ + -derivedDataPath "$DERIVED_DATA_PATH" \ + -disableAutomaticPackageResolution \ + -clonedSourcePackagesDirPath "$SPM_CACHE" \ + -resultBundlePath "$CORE_XCRESULT_PATH" \ + -skipPackagePluginValidation \ + -skipMacroValidation \ + -enableCodeCoverage NO \ + -test-timeouts-enabled YES \ + -default-test-execution-time-allowance 60 \ + -maximum-test-execution-time-allowance 120 \ + COMPILER_INDEX_STORE_ENABLE=NO \ + SWIFT_COMPILATION_MODE=incremental +else + section "xcodebuild test OsaurusCoreTests" + printf 'Skipped because OSAURUS_RUN_XCODE_CORE_TESTS=%s\n' "$RUN_XCODE_CORE_TESTS" +fi + section "swift build app" run_xcodebuild build \ -workspace osaurus.xcworkspace \ From 98298aceedfffcf725c632d1a41531ae999f6a05 Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Thu, 7 May 2026 13:28:12 -0300 Subject: [PATCH 3/9] test: harden tool timeout fixture under xcode load --- .../Tests/Tool/ToolRegistryTimeoutTests.swift | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Packages/OsaurusCore/Tests/Tool/ToolRegistryTimeoutTests.swift b/Packages/OsaurusCore/Tests/Tool/ToolRegistryTimeoutTests.swift index c483d9141..eb7524342 100644 --- a/Packages/OsaurusCore/Tests/Tool/ToolRegistryTimeoutTests.swift +++ b/Packages/OsaurusCore/Tests/Tool/ToolRegistryTimeoutTests.swift @@ -17,22 +17,27 @@ import Testing struct ToolRegistryTimeoutTests { /// Tool body that sleeps longer than the test timeout. Mirrors a - /// hung subprocess / blocked network call in production. Returns a - /// success envelope only if it somehow completes — that branch is - /// the failure signal for the test. + /// hung subprocess / blocked network call in production: it does + /// not observe Swift task cancellation, so implementations that + /// wait for a cancelled child to drain will still stall here. + /// Returns a success envelope only if it somehow completes — that + /// branch is the failure signal for the test. private struct SlowSleepTool: OsaurusTool { - static let sleepSeconds: TimeInterval = 8 + static let sleepSeconds: TimeInterval = 10 static let timeoutSeconds: TimeInterval = 0.5 static let minimumTimeoutLeadSeconds: TimeInterval = 1 - private static let sleepNanoseconds = UInt64(sleepSeconds * 1_000_000_000) let name: String = "test_slow_sleep" - let description: String = "Test fixture: sleeps 8 seconds, exceeding the test timeout." + let description: String = "Test fixture: sleeps 10 seconds, exceeding the test timeout." let parameters: JSONValue? = .object(["type": .string("object")]) func execute(argumentsJSON: String) async throws -> String { - try await Task.sleep(nanoseconds: Self.sleepNanoseconds) - return ToolEnvelope.success(tool: name, text: "did not time out") + let toolName = name + return await withCheckedContinuation { continuation in + DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + Self.sleepSeconds) { + continuation.resume(returning: ToolEnvelope.success(tool: toolName, text: "did not time out")) + } + } } } From 67a879bac2a4265d4fd9f87b7985ab60870c8eba Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Fri, 8 May 2026 18:41:21 -0300 Subject: [PATCH 4/9] chore: fix swiftlint drift after rebase --- Packages/OsaurusCore/Models/Agent/AgentStore.swift | 3 +-- Packages/OsaurusCore/Services/ModelRuntime.swift | 6 ++---- .../Services/ModelRuntime/MLXBatchAdapter.swift | 3 +-- .../OsaurusCore/Services/Sandbox/LiveExecRegistry.swift | 6 +++--- Packages/OsaurusCore/Services/Sandbox/LiveExecSink.swift | 2 +- Packages/OsaurusCore/Tools/SchemaValidator.swift | 5 ++--- Packages/OsaurusCore/Tools/ToolRegistry.swift | 2 +- Packages/OsaurusCore/Views/Agent/AgentAvatarView.swift | 2 +- Packages/OsaurusCore/Views/Agent/AgentsView.swift | 7 +++---- .../OsaurusCore/Views/Chat/NativeMessageCellView.swift | 3 +-- .../OsaurusCore/Views/Chat/NativeToolCallGroupView.swift | 6 ++---- Packages/OsaurusCore/Views/Chat/TerminalDisplayView.swift | 3 +-- Packages/OsaurusCore/Views/Chat/TerminalSnapshot.swift | 3 +-- 13 files changed, 20 insertions(+), 31 deletions(-) diff --git a/Packages/OsaurusCore/Models/Agent/AgentStore.swift b/Packages/OsaurusCore/Models/Agent/AgentStore.swift index d9da028af..13d3e71da 100644 --- a/Packages/OsaurusCore/Models/Agent/AgentStore.swift +++ b/Packages/OsaurusCore/Models/Agent/AgentStore.swift @@ -128,8 +128,7 @@ public enum AgentStore { if let existing = try? FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil) { for f in existing where f.deletingPathExtension().lastPathComponent == agentId.uuidString - && f.lastPathComponent != filename - { + && f.lastPathComponent != filename { try? FileManager.default.removeItem(at: f) } } diff --git a/Packages/OsaurusCore/Services/ModelRuntime.swift b/Packages/OsaurusCore/Services/ModelRuntime.swift index a6b45f2b6..866399dd1 100644 --- a/Packages/OsaurusCore/Services/ModelRuntime.swift +++ b/Packages/OsaurusCore/Services/ModelRuntime.swift @@ -577,8 +577,7 @@ public actor ModelRuntime { // dispatches via `Qwen3Next.swift`. Same `ArraysCache` companion // pattern as the 3.5 / 3.6 family. if lower.contains("qwen3-next") || lower.contains("qwen3_next") - || lower.contains("qwen3next") - { + || lower.contains("qwen3next") { return true } // Bailing / Ling hybrid: Linear-Attn companion ArraysCache + MLA @@ -609,8 +608,7 @@ public actor ModelRuntime { return true } if lower.contains("granite") - && (lower.contains("moe-hybrid") || lower.contains("moe_hybrid")) - { + && (lower.contains("moe-hybrid") || lower.contains("moe_hybrid")) { return true } // Falcon-H1 (falcon_h1 model_type) — TII hybrid Mamba+Attn. vmlx diff --git a/Packages/OsaurusCore/Services/ModelRuntime/MLXBatchAdapter.swift b/Packages/OsaurusCore/Services/ModelRuntime/MLXBatchAdapter.swift index 267a23dc7..46e69a3be 100644 --- a/Packages/OsaurusCore/Services/ModelRuntime/MLXBatchAdapter.swift +++ b/Packages/OsaurusCore/Services/ModelRuntime/MLXBatchAdapter.swift @@ -249,8 +249,7 @@ struct MLXBatchAdapter { modelName: String ) -> [String: any Sendable] { if ModelFamilyNames.isLingFamily(modelName) - || ModelFamilyNames.isZayaFamily(modelName) - { + || ModelFamilyNames.isZayaFamily(modelName) { return ["enable_thinking": false] } if let disableThinking = generation.modelOptions["disableThinking"]?.boolValue { diff --git a/Packages/OsaurusCore/Services/Sandbox/LiveExecRegistry.swift b/Packages/OsaurusCore/Services/Sandbox/LiveExecRegistry.swift index 9365e3dd8..d0a03a69e 100644 --- a/Packages/OsaurusCore/Services/Sandbox/LiveExecRegistry.swift +++ b/Packages/OsaurusCore/Services/Sandbox/LiveExecRegistry.swift @@ -100,19 +100,19 @@ public actor LiveExecRegistry { /// `CurrentValueSubject` is thread-safe; `nonisolated(unsafe)` is /// the standard pattern for letting both the actor's mutating /// methods AND the `nonisolated` accessor below touch it. - private nonisolated(unsafe) let entriesSubject = CurrentValueSubject<[String: Entry], Never>([:]) + nonisolated(unsafe) private let entriesSubject = CurrentValueSubject<[String: Entry], Never>([:]) /// Live snapshot of every registered entry. The chat layer /// subscribes once and on each emission walks current tool-call /// items, attaching matching entries by `toolCallId`. - public nonisolated var entriesPublisher: AnyPublisher<[String: Entry], Never> { + nonisolated public var entriesPublisher: AnyPublisher<[String: Entry], Never> { entriesSubject.eraseToAnyPublisher() } /// Synchronous snapshot of the current entries. Lets a tool-call /// cell decide "live or static?" inline during `configure(item:)` /// without an actor hop. The underlying subject is thread-safe. - public nonisolated func currentEntries() -> [String: Entry] { + nonisolated public func currentEntries() -> [String: Entry] { entriesSubject.value } diff --git a/Packages/OsaurusCore/Services/Sandbox/LiveExecSink.swift b/Packages/OsaurusCore/Services/Sandbox/LiveExecSink.swift index 7a3ae3eb2..087f8689a 100644 --- a/Packages/OsaurusCore/Services/Sandbox/LiveExecSink.swift +++ b/Packages/OsaurusCore/Services/Sandbox/LiveExecSink.swift @@ -47,7 +47,7 @@ public final class LiveExecSink: @unchecked Sendable { /// `nonisolated(unsafe)` so the `currentStatus()` accessor below /// can read `value` without an actor hop. `CurrentValueSubject` is /// thread-safe internally. - private nonisolated(unsafe) let statusSubject = + nonisolated(unsafe) private let statusSubject = CurrentValueSubject(.running) /// Cap on the in-memory seed buffer. 1 MB is enough to render most diff --git a/Packages/OsaurusCore/Tools/SchemaValidator.swift b/Packages/OsaurusCore/Tools/SchemaValidator.swift index f5b7f22cb..0a9957e39 100644 --- a/Packages/OsaurusCore/Tools/SchemaValidator.swift +++ b/Packages/OsaurusCore/Tools/SchemaValidator.swift @@ -250,8 +250,7 @@ public struct SchemaValidator { // validator runs, but callers that bypass coercion (tests, ad-hoc // validation) still benefit from this lenient comparison. if let s = value as? String, - allowed.contains(where: { ($0 as? String)?.lowercased() == s.lowercased() }) - { + allowed.contains(where: { ($0 as? String)?.lowercased() == s.lowercased() }) { return .ok() } let label = key.map { " '\($0)'" } ?? "" @@ -480,7 +479,7 @@ public struct SchemaValidator { let nested = obj["properties"] as? [String: Any] else { return obj } let declared = Set(propsDict.keys) - guard !declared.intersection(Set(nested.keys)).isEmpty else { return obj } + guard !declared.isDisjoint(with: Set(nested.keys)) else { return obj } var out = obj out.removeValue(forKey: "properties") for (key, value) in nested where out[key] == nil { diff --git a/Packages/OsaurusCore/Tools/ToolRegistry.swift b/Packages/OsaurusCore/Tools/ToolRegistry.swift index ce5361d2b..c4dc9427c 100644 --- a/Packages/OsaurusCore/Tools/ToolRegistry.swift +++ b/Packages/OsaurusCore/Tools/ToolRegistry.swift @@ -331,7 +331,7 @@ final class ToolRegistry: ObservableObject { /// wall-clock race. Cancellation still propagates: when the calling /// task is cancelled, the body's own `Task.isCancelled` checks (or /// the underlying process signals) tear it down. - internal nonisolated static func runToolBodyUntimed( + nonisolated internal static func runToolBodyUntimed( _ tool: OsaurusTool, argumentsJSON: String ) async throws -> String { diff --git a/Packages/OsaurusCore/Views/Agent/AgentAvatarView.swift b/Packages/OsaurusCore/Views/Agent/AgentAvatarView.swift index f852cac28..b457966ea 100644 --- a/Packages/OsaurusCore/Views/Agent/AgentAvatarView.swift +++ b/Packages/OsaurusCore/Views/Agent/AgentAvatarView.swift @@ -43,7 +43,7 @@ struct AgentAvatarView: View { let diameter: CGFloat /// Optional user-supplied custom avatar image. When present, takes /// precedence over `mascotId` and the monogram fallback. - var customImageURL: URL? = nil + var customImageURL: URL? /// Font size for the monogram fallback. Callers tune this so the letter /// reads at the same visual weight as before per call site var monogramFontSize: CGFloat = 16 diff --git a/Packages/OsaurusCore/Views/Agent/AgentsView.swift b/Packages/OsaurusCore/Views/Agent/AgentsView.swift index 509774de1..c9e79fb92 100644 --- a/Packages/OsaurusCore/Views/Agent/AgentsView.swift +++ b/Packages/OsaurusCore/Views/Agent/AgentsView.swift @@ -3329,8 +3329,7 @@ struct AgentDetailView: View { } if let existing = agentSecrets.first(where: { $0.id == entryId }), - !existing.isNew, existing.key != trimmedKey - { + !existing.isNew, existing.key != trimmedKey { AgentSecretsKeychain.deleteSecret(id: existing.key, agentId: agent.id) } @@ -4178,7 +4177,7 @@ private struct ThemeOptionCard: View { // MARK: - Agent Secret Entry -fileprivate struct AgentSecretEntry: Identifiable, Equatable { +private struct AgentSecretEntry: Identifiable, Equatable { let id = UUID() var key: String var value: String @@ -4187,7 +4186,7 @@ fileprivate struct AgentSecretEntry: Identifiable, Equatable { // MARK: - Agent Secret Row -fileprivate struct AgentSecretRow: View { +private struct AgentSecretRow: View { let entry: AgentSecretEntry let isEditing: Bool let theme: ThemeProtocol diff --git a/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift b/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift index ced1043a5..8b8b1ef18 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeMessageCellView.swift @@ -181,8 +181,7 @@ final class NativeHeaderView: NSView { if let img = AvatarImageCache.shared.image(for: url) { return img } } if let avatar, !avatar.isEmpty, - let mascot = Bundle.module.image(forResource: "osaurus-avatar-\(avatar)") - { + let mascot = Bundle.module.image(forResource: "osaurus-avatar-\(avatar)") { return mascot } return Self.monogramImage( diff --git a/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift b/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift index 35b4e5eba..bf28153b0 100644 --- a/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift +++ b/Packages/OsaurusCore/Views/Chat/NativeToolCallGroupView.swift @@ -696,8 +696,7 @@ final class NativeToolCallRowView: NSView { // 1) Live path takes priority while a tool is actively running. if let entry = LiveExecRegistry.shared.currentEntries()[item.call.id], - entry.currentStatus() == .running - { + entry.currentStatus() == .running { tearDownResultSection() mountTerminalView(mode: .live(entry), theme: theme) return @@ -708,8 +707,7 @@ final class NativeToolCallRowView: NSView { // shell tools and error envelopes, which then fall through // to the markdown path below. if let result = item.result, - let snapshot = TerminalSnapshot.from(toolResult: result, item: item) - { + let snapshot = TerminalSnapshot.from(toolResult: result, item: item) { tearDownResultSection() mountTerminalView(mode: .completed(snapshot), theme: theme) return diff --git a/Packages/OsaurusCore/Views/Chat/TerminalDisplayView.swift b/Packages/OsaurusCore/Views/Chat/TerminalDisplayView.swift index 2553e0b3e..63272809a 100644 --- a/Packages/OsaurusCore/Views/Chat/TerminalDisplayView.swift +++ b/Packages/OsaurusCore/Views/Chat/TerminalDisplayView.swift @@ -333,8 +333,7 @@ final class TerminalDisplayView: NSView { private func startElapsedTimer(startedAt: Date) { elapsedTimer?.invalidate() elapsedLabel.stringValue = Self.formatElapsed(0) - elapsedTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { - [weak self] _ in + elapsedTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in guard let self else { return } MainActor.assumeIsolated { let secs = Date().timeIntervalSince(startedAt) diff --git a/Packages/OsaurusCore/Views/Chat/TerminalSnapshot.swift b/Packages/OsaurusCore/Views/Chat/TerminalSnapshot.swift index db9c5db9e..cb766cc0d 100644 --- a/Packages/OsaurusCore/Views/Chat/TerminalSnapshot.swift +++ b/Packages/OsaurusCore/Views/Chat/TerminalSnapshot.swift @@ -99,8 +99,7 @@ extension TerminalSnapshot { var command = "" if let data = item.call.function.arguments.data(using: .utf8), let args = try? JSONSerialization.jsonObject(with: data) as? [String: Any], - let cmd = args["command"] as? String - { + let cmd = args["command"] as? String { command = cmd } From 2d89350d58a20580203c8e0c601e17e5cf1978a4 Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Fri, 8 May 2026 18:44:48 -0300 Subject: [PATCH 5/9] fix: adapt sandbox exec kills to Signal API --- Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift b/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift index 2f5ff9ef9..9c7d2c077 100644 --- a/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift +++ b/Packages/OsaurusCore/Services/Sandbox/SandboxManager.swift @@ -1006,7 +1006,7 @@ // separate process registry. if let onProcessStarted { let handle = ProcessHandle(pid: process.pid) { signal in - try await process.kill(signal) + try await process.kill(Signal(rawValue: signal)) } onProcessStarted(handle) } @@ -1076,7 +1076,7 @@ return status } - try? await process.kill(15) + try? await process.kill(.term) throw SandboxError.timeout } } From a376ddbc2f3ca058d1019421ce9dcfc712c1c0cb Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Fri, 8 May 2026 18:53:42 -0300 Subject: [PATCH 6/9] chore: align workspace package pins --- .../xcshareddata/swiftpm/Package.resolved | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osaurus.xcworkspace/xcshareddata/swiftpm/Package.resolved b/osaurus.xcworkspace/xcshareddata/swiftpm/Package.resolved index 50b10bb84..436fc8927 100644 --- a/osaurus.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/osaurus.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "2b8c51e0f4bb988e1f78ce77703b6f6665e22a502866c002632abf979f47e522", + "originHash" : "3e4f43f8358b6fc258ece32dcb74914ec37518489d8320c9dbe33c0361fada1e", "pins" : [ { "identity" : "aachartkit-swift", @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/containerization.git", "state" : { - "revision" : "f1ee6f8b737ab8dffbd620bfa47283f6f0bc1822", - "version" : "0.31.0" + "revision" : "c519d5760970ac1da99a2816289d326e2525e9f6", + "version" : "0.32.0" } }, { @@ -87,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/orlandos-nl/IkigaJSON", "state" : { - "revision" : "2e90323a4543c1aaca01bc6e28e4c744c13811a2", - "version" : "2.4.3" + "revision" : "c0d8aa3eae43e1b5565d5b6b4e05e18664f1047e", + "version" : "2.4.4" } }, { @@ -166,8 +166,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-certificates.git", "state" : { - "revision" : "5aa1c0d1bc204908df47c2075bdbb39573d05e8d", - "version" : "1.19.0" + "revision" : "bde8ca32a096825dfce37467137c903418c1893d", + "version" : "1.19.1" } }, { @@ -328,8 +328,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/modelcontextprotocol/swift-sdk.git", "state" : { - "revision" : "6132fd4b5b4217ce4717c4775e4607f5c3120129", - "version" : "0.12.0" + "revision" : "a0ae212ebf6eab5f754c3129608bc5557637e605", + "version" : "0.12.1" } }, { From d6c28a479962806bfe95d41d084889ae2ecd6506 Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Sun, 10 May 2026 00:05:35 -0300 Subject: [PATCH 7/9] chore: fix swiftlint drift after main refresh --- .../Sources/OsaurusCLICore/Commands/Tools/ToolsDoctor.swift | 3 +-- Packages/OsaurusCore/Managers/Plugin/PluginManager.swift | 3 +-- Packages/OsaurusCore/Models/Plugin/ExternalPlugin.swift | 3 +-- Packages/OsaurusCore/Services/Plugin/PluginOnceLogger.swift | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDoctor.swift b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDoctor.swift index 47f1d8791..38da11647 100644 --- a/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDoctor.swift +++ b/Packages/OsaurusCLI/Sources/OsaurusCLICore/Commands/Tools/ToolsDoctor.swift @@ -111,8 +111,7 @@ public struct ToolsDoctor { let receiptURL = versionDir.appendingPathComponent("receipt.json") let receiptResult: (PluginReceipt, Result) if let rdata = try? Data(contentsOf: receiptURL), - let receipt = try? JSONDecoder().decode(PluginReceipt.self, from: rdata) - { + let receipt = try? JSONDecoder().decode(PluginReceipt.self, from: rdata) { receiptResult = (receipt, .ok("\(receipt.plugin_id)@\(receipt.version)")) } else { checks.append(.init(label: "receipt.json", result: .fail("missing or invalid"))) diff --git a/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift b/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift index 631321e8b..0c360c8cb 100644 --- a/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift +++ b/Packages/OsaurusCore/Managers/Plugin/PluginManager.swift @@ -572,8 +572,7 @@ final class PluginManager { // The runtime checks the static branch first, so any overlap means // the plugin's `handle_route` for that path can never fire. if let mount = manifest.capabilities.web?.mount, - let routes = manifest.capabilities.routes - { + let routes = manifest.capabilities.routes { let normalizedMount = mount.hasPrefix("/") ? mount : "/\(mount)" for route in routes { let routePath = route.path.hasPrefix("/") ? route.path : "/\(route.path)" diff --git a/Packages/OsaurusCore/Models/Plugin/ExternalPlugin.swift b/Packages/OsaurusCore/Models/Plugin/ExternalPlugin.swift index e767e1358..9b90f580c 100644 --- a/Packages/OsaurusCore/Models/Plugin/ExternalPlugin.swift +++ b/Packages/OsaurusCore/Models/Plugin/ExternalPlugin.swift @@ -469,8 +469,7 @@ public struct PluginManifest: Decodable, Sendable { // Path parameter match — segment-by-segment, only the first // definition wins to give plugin authors deterministic ordering. if paramMatch == nil, - let params = matchPathParams(routePath: routePath, requestPath: normalizedPath) - { + let params = matchPathParams(routePath: routePath, requestPath: normalizedPath) { paramMatch = RouteMatch(route: route, pathParams: params) } } diff --git a/Packages/OsaurusCore/Services/Plugin/PluginOnceLogger.swift b/Packages/OsaurusCore/Services/Plugin/PluginOnceLogger.swift index 87b2efa0d..c2ee0b13a 100644 --- a/Packages/OsaurusCore/Services/Plugin/PluginOnceLogger.swift +++ b/Packages/OsaurusCore/Services/Plugin/PluginOnceLogger.swift @@ -15,7 +15,7 @@ import Foundation enum PluginOnceLogger { private static let lock = NSLock() - private nonisolated(unsafe) static var seen: Set = [] + nonisolated(unsafe) private static var seen: Set = [] /// Emits `message` (formatted with `arguments`) via `NSLog` exactly /// once per `key` per process. Subsequent calls with the same key are From 71fd60df657294c84b8ef9cb800f3f8cd5a2c2dd Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Sun, 10 May 2026 00:11:39 -0300 Subject: [PATCH 8/9] test: keep chat history off keychain in tests --- Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift b/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift index 513d3f838..8ea758511 100644 --- a/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift +++ b/Packages/OsaurusCore/Storage/ChatHistoryDatabase.swift @@ -53,6 +53,13 @@ public final class ChatHistoryDatabase: @unchecked Sendable { // MARK: - Lifecycle public func open() throws { + #if DEBUG + if RuntimeEnvironment.isUnderTests, OsaurusPaths.overrideRoot == nil { + try openInMemory() + return + } + #endif + // Defensive gate: production flow already awaits the // migrator in `AppDelegate.applicationDidFinishLaunching`, // but tests + future headless entry points may call From 67b6f2defe718641389fccae5acb4750f1f9a2e5 Mon Sep 17 00:00:00 2001 From: Michael Meding Date: Sun, 10 May 2026 12:16:08 -0300 Subject: [PATCH 9/9] Add remote provider model discovery fallbacks --- .../Managers/RemoteProviderManager.swift | 53 +++++++++++++------ .../OsaurusCore/Models/API/OpenAIAPI.swift | 2 +- .../RemoteProviderConfiguration.swift | 9 ++++ .../RemoteChatRequestEncodingTests.swift | 39 ++++++++++++++ .../RemoteProviderManagerRefreshTests.swift | 52 +++++++++++++++++- .../Settings/RemoteProviderEditSheet.swift | 27 ++++++++-- 6 files changed, 161 insertions(+), 21 deletions(-) diff --git a/Packages/OsaurusCore/Managers/RemoteProviderManager.swift b/Packages/OsaurusCore/Managers/RemoteProviderManager.swift index 63aba2b5b..989f77535 100644 --- a/Packages/OsaurusCore/Managers/RemoteProviderManager.swift +++ b/Packages/OsaurusCore/Managers/RemoteProviderManager.swift @@ -238,16 +238,7 @@ public final class RemoteProviderManager: ObservableObject { } // Fetch models from the provider and merge any manually configured deployment IDs. - let discoveredModels: [String] - do { - discoveredModels = try await RemoteProviderService.fetchModels(from: provider) - } catch { - if provider.providerType == .azureOpenAI && !provider.manualModelIds.isEmpty { - discoveredModels = [] - } else { - throw error - } - } + let discoveredModels = try await fetchModelsAllowingManualFallback(for: provider) let models = provider.mergedModelIds(discovered: discoveredModels) // Create service instance – resolve headers eagerly on @MainActor @@ -353,11 +344,7 @@ public final class RemoteProviderManager: ObservableObject { let discovered: [String] do { - if let override = testFetchModelsOverride { - discovered = try await override(provider) - } else { - discovered = try await RemoteProviderService.fetchModels(from: provider) - } + discovered = try await fetchModelsAllowingManualFallback(for: provider) } catch { return } @@ -374,6 +361,42 @@ public final class RemoteProviderManager: ObservableObject { notifyModelsChanged() } + private func fetchModelsAllowingManualFallback(for provider: RemoteProvider) async throws -> [String] { + do { + if let override = testFetchModelsOverride { + return try await override(provider) + } + return try await RemoteProviderService.fetchModels(from: provider) + } catch { + if shouldUseManualModelIdsAfterDiscoveryFailure(provider: provider, error: error) { + return [] + } + throw error + } + } + + private func shouldUseManualModelIdsAfterDiscoveryFailure(provider: RemoteProvider, error: Error) -> Bool { + guard provider.providerType.supportsManualModelDiscoveryFallback, + !provider.mergedModelIds(discovered: []).isEmpty + else { return false } + + // Azure deployments are manual IDs; preserve the existing behavior of + // not requiring Azure's /models response before connecting. + if provider.providerType == .azureOpenAI { + return true + } + + guard case .requestFailed(let message) = error as? RemoteProviderServiceError else { + return false + } + + let normalized = message.lowercased() + return normalized.contains("404") + || normalized.contains("not found") + || normalized.contains("405") + || normalized.contains("method not allowed") + } + /// Refresh every enabled provider's model list, coalesced and throttled. /// Called from the picker-open path. public func refreshConnectedProviders() async { diff --git a/Packages/OsaurusCore/Models/API/OpenAIAPI.swift b/Packages/OsaurusCore/Models/API/OpenAIAPI.swift index e5e7de480..a892ed4f1 100644 --- a/Packages/OsaurusCore/Models/API/OpenAIAPI.swift +++ b/Packages/OsaurusCore/Models/API/OpenAIAPI.swift @@ -78,7 +78,7 @@ struct OpenAIModel: Codable, Sendable { name = try container.decodeIfPresent(String.self, forKey: .name) model = try container.decodeIfPresent(String.self, forKey: .model) modified_at = try container.decodeIfPresent(String.self, forKey: .modified_at) - size = try container.decodeIfPresent(Int.self, forKey: .size) + size = try? container.decodeIfPresent(Int.self, forKey: .size) digest = try container.decodeIfPresent(String.self, forKey: .digest) details = try container.decodeIfPresent(ModelDetails.self, forKey: .details) } diff --git a/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift b/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift index 1dbc3de45..ee9771144 100644 --- a/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift +++ b/Packages/OsaurusCore/Models/Configuration/RemoteProviderConfiguration.swift @@ -70,6 +70,15 @@ public enum RemoteProviderType: String, Codable, Sendable, CaseIterable { // Both use /models but response format differs return "/models" } + + public var supportsManualModelDiscoveryFallback: Bool { + switch self { + case .openaiLegacy, .openResponses, .azureOpenAI: + return true + case .anthropic, .openAICodex, .gemini, .osaurus: + return false + } + } } // MARK: - Remote Provider Model diff --git a/Packages/OsaurusCore/Tests/Provider/RemoteChatRequestEncodingTests.swift b/Packages/OsaurusCore/Tests/Provider/RemoteChatRequestEncodingTests.swift index 375eee0b9..064df3068 100644 --- a/Packages/OsaurusCore/Tests/Provider/RemoteChatRequestEncodingTests.swift +++ b/Packages/OsaurusCore/Tests/Provider/RemoteChatRequestEncodingTests.swift @@ -131,6 +131,45 @@ struct RemoteChatRequestEncodingTests { #expect(provider.mergedModelIds(discovered: ["gpt-4.1", "prod-chat"]) == ["gpt-4.1", "prod-chat", "gpt-5.4"]) } + @Test func openAICompatibleProvider_supportsManualModelDiscoveryFallback() throws { + #expect(RemoteProviderType.openaiLegacy.supportsManualModelDiscoveryFallback) + #expect(RemoteProviderType.openResponses.supportsManualModelDiscoveryFallback) + #expect(RemoteProviderType.azureOpenAI.supportsManualModelDiscoveryFallback) + #expect(!RemoteProviderType.anthropic.supportsManualModelDiscoveryFallback) + #expect(!RemoteProviderType.gemini.supportsManualModelDiscoveryFallback) + } + + @Test func openAIModelsResponse_decodesLemonadeFractionalSizePayload() throws { + let json = """ + { + "object": "list", + "data": [ + { + "id": "Cogito-v2-llama-109B-MoE-GGUF", + "object": "model", + "created": 1234567890, + "owned_by": "lemonade", + "size": 65.3, + "labels": ["vision"], + "checkpoint": "unsloth/cogito-v2-preview-llama-109B-MoE-GGUF:Q4_K_M" + }, + { + "id": "Devstral-Small-2507-GGUF", + "object": "model", + "created": 1234567890, + "owned_by": "lemonade", + "size": 14.3, + "suggested": true + } + ] + } + """ + + let response = try JSONDecoder().decode(ModelsResponse.self, from: Data(json.utf8)) + + #expect(response.data.map(\.id) == ["Cogito-v2-llama-109B-MoE-GGUF", "Devstral-Small-2507-GGUF"]) + } + @Test func remoteProvider_decodingDefaultsManualModelIdsToEmptyArray() throws { let json = """ { diff --git a/Packages/OsaurusCore/Tests/Provider/RemoteProviderManagerRefreshTests.swift b/Packages/OsaurusCore/Tests/Provider/RemoteProviderManagerRefreshTests.swift index acaaaa84f..b786b30b5 100644 --- a/Packages/OsaurusCore/Tests/Provider/RemoteProviderManagerRefreshTests.swift +++ b/Packages/OsaurusCore/Tests/Provider/RemoteProviderManagerRefreshTests.swift @@ -24,13 +24,18 @@ struct RemoteProviderManagerRefreshTests { // MARK: - Helpers - private func makeProvider(name: String = "Test Provider") -> RemoteProvider { + private func makeProvider( + name: String = "Test Provider", + providerType: RemoteProviderType = .openaiLegacy, + manualModelIds: [String] = [] + ) -> RemoteProvider { RemoteProvider( name: name, host: "127.0.0.1", basePath: "/v1", authType: .none, - providerType: .openaiLegacy + providerType: providerType, + manualModelIds: manualModelIds ) } @@ -134,6 +139,49 @@ struct RemoteProviderManagerRefreshTests { } } + @Test func connect_usesManualModelsWhenOpenAICompatibleModelsEndpointIsMissing() async throws { + try await RemoteProviderTestLock.shared.run { + let manager = RemoteProviderManager.shared + let provider = makeProvider( + name: "MiniMax", + manualModelIds: [" MiniMax-M2.7 ", "", "minimax-m2.7", "MiniMax-M2.7-highspeed"] + ) + manager._testInstallConnectedProvider(provider, discoveredModels: []) + defer { manager._testRemoveProviders(ids: [provider.id]) } + + manager.testFetchModelsOverride = { _ in + throw RemoteProviderServiceError.requestFailed("HTTP 404: Page Not Found") + } + + try await manager.connect(providerId: provider.id) + + let state = manager.providerStates[provider.id] + #expect(state?.isConnected == true) + #expect(state?.discoveredModels == ["MiniMax-M2.7", "MiniMax-M2.7-highspeed"]) + } + } + + @Test func connect_doesNotUseManualModelsForOpenAICompatibleAuthFailure() async throws { + await RemoteProviderTestLock.shared.run { + let manager = RemoteProviderManager.shared + let provider = makeProvider(name: "Unauthorized", manualModelIds: ["manual-model"]) + manager._testInstallConnectedProvider(provider, discoveredModels: []) + defer { manager._testRemoveProviders(ids: [provider.id]) } + + manager.testFetchModelsOverride = { _ in + throw RemoteProviderServiceError.requestFailed("HTTP 401: Unauthorized") + } + + await #expect(throws: Error.self) { + try await manager.connect(providerId: provider.id) + } + + let state = manager.providerStates[provider.id] + #expect(state?.isConnected == false) + #expect(state?.discoveredModels == []) + } + } + @Test func refetchModels_noopWhenProviderNotConnected() async throws { await RemoteProviderTestLock.shared.run { let manager = RemoteProviderManager.shared diff --git a/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift b/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift index 8c7913277..d6d7b9629 100644 --- a/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift +++ b/Packages/OsaurusCore/Views/Settings/RemoteProviderEditSheet.swift @@ -107,6 +107,14 @@ private struct AddProviderFlow: View { return canTest && !parseManualModelIds(manualModelIdsText).isEmpty } + private var canSaveCustomProviderWithoutSuccessfulTest: Bool { + selectedPreset == .custom && canTestCustom && !parseManualModelIds(manualModelIdsText).isEmpty + } + + private var canSaveProviderWithoutSuccessfulTest: Bool { + canSaveKnownProviderWithoutSuccessfulTest || canSaveCustomProviderWithoutSuccessfulTest + } + var body: some View { VStack(spacing: 0) { // Header bar @@ -376,6 +384,8 @@ private struct AddProviderFlow: View { ) ) + customModelIdsSection + // Advanced settings toggle advancedSettingsSection } @@ -384,7 +394,7 @@ private struct AddProviderFlow: View { // Footer sheetFooter(canProceed: canTestCustom) { - if testResult?.isSuccess == true { + if testResult?.isSuccess == true || canSaveCustomProviderWithoutSuccessfulTest { saveCustomProvider() } else { testCustomProvider() @@ -533,6 +543,16 @@ private struct AddProviderFlow: View { .onChange(of: manualModelIdsText) { _, _ in testResult = nil } } + private var customModelIdsSection: some View { + DeploymentNamesEditor( + text: $manualModelIdsText, + title: "MODEL IDS", + placeholder: "MiniMax-M2.7\nMiniMax-M2.7-highspeed", + theme: theme + ) + .onChange(of: manualModelIdsText) { _, _ in testResult = nil } + } + private func buildKnownEndpointPreview() -> String { var result = "\(knownProtocol.rawValue)://\(knownHost.trimmingCharacters(in: .whitespaces))" if let port = Int(knownPort), port != knownProtocol.defaultPort { @@ -968,7 +988,7 @@ private struct AddProviderFlow: View { private var actionButtonTitle: String { if isTesting { return openAIAuthMode == .chatGPTSubscription ? L("Signing in...") : L("Testing...") } - if testResult?.isSuccess == true || canSaveKnownProviderWithoutSuccessfulTest { return L("Add Provider") } + if testResult?.isSuccess == true || canSaveProviderWithoutSuccessfulTest { return L("Add Provider") } if case .failure = testResult { return L("Retry") } if selectedPreset == .openai && openAIAuthMode == .chatGPTSubscription { return L("Sign in with ChatGPT") @@ -977,7 +997,7 @@ private struct AddProviderFlow: View { } private var actionButtonColor: Color { - if testResult?.isSuccess == true || canSaveKnownProviderWithoutSuccessfulTest { return theme.successColor } + if testResult?.isSuccess == true || canSaveProviderWithoutSuccessfulTest { return theme.successColor } if case .failure = testResult { return theme.errorColor } return theme.accentColor } @@ -1116,6 +1136,7 @@ private struct AddProviderFlow: View { enabled: true, autoConnect: true, timeout: timeout, + manualModelIds: parseManualModelIds(manualModelIdsText), secretHeaderKeys: secretKeys )