From 4bf7f5d233b654e50dc7e3764afc771ce31a6d6b Mon Sep 17 00:00:00 2001 From: ROBOTVIPONE <191507985+hoangtu1sos1hot@users.noreply.github.com> Date: Tue, 13 May 2025 20:02:40 +0700 Subject: [PATCH 1/3] Update Locale.swift Co-Authored-By: Andrew Zures --- Tool/Sources/Preferences/Types/Locale.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tool/Sources/Preferences/Types/Locale.swift b/Tool/Sources/Preferences/Types/Locale.swift index 6b50d82d..094bbc2b 100644 --- a/Tool/Sources/Preferences/Types/Locale.swift +++ b/Tool/Sources/Preferences/Types/Locale.swift @@ -3,7 +3,6 @@ import Foundation public extension Locale { static var availableLocalizedLocales: [String] { let localizedLocales = Locale.isoLanguageCodes.compactMap { - Locale(identifier: "en-US").localizedString(forLanguageCode: $0) } .sorted() return localizedLocales From 799e897b6cbbb2ac7634bf744efb770d1952bc07 Mon Sep 17 00:00:00 2001 From: ROBOTVIPONE <191507985+hoangtu1sos1hot@users.noreply.github.com> Date: Tue, 13 May 2025 20:06:31 +0700 Subject: [PATCH 2/3] Revert "Pre-release 0.34.116" This reverts commit 677f73f11c3a2c68e7e0eae3c7760408df613d17. --- .gitignore | 1 - Copilot for Xcode.xcodeproj/project.pbxproj | 14 +- Copilot for Xcode/App.swift | 56 +- .../Color.colorset/Contents.json | 38 - .../Contents.json | 38 - .../Contents.json | 38 - Copilot for Xcode/Credits.rtf | 54 - Core/Package.swift | 6 +- Core/Sources/ChatService/ChatService.swift | 271 +-- .../Skills/CurrentEditorSkill.swift | 2 +- .../ToolCalls/CopilotToolRegistry.swift | 18 - .../ToolCalls/CreateFileTool.swift | 98 - .../ChatService/ToolCalls/GetErrorsTool.swift | 74 - .../ToolCalls/GetTerminalOutputTool.swift | 33 - .../ChatService/ToolCalls/ICopilotTool.swift | 53 - .../ToolCalls/InsertEditIntoFileTool.swift | 126 -- .../ToolCalls/RunInTerminalTool.swift | 22 - .../Sources/ChatService/ToolCalls/Utils.swift | 35 - Core/Sources/ConversationTab/Chat.swift | 174 +- .../ConversationTab/ChatExtension.swift | 4 +- Core/Sources/ConversationTab/ChatPanel.swift | 206 +- .../Controller/DiffViewWindowController.swift | 141 -- .../ConversationTab/ConversationTab.swift | 2 - .../ConversationTab/DiffViews/DiffView.swift | 96 - .../DiffViews/DiffWebView.swift | 184 -- Core/Sources/ConversationTab/FilePicker.swift | 3 +- .../Sources/ConversationTab/ModelPicker.swift | 177 ++ .../ModelPicker/ChatModePicker.swift | 63 - .../ModelPicker/ModeButton.swift | 30 - .../ModelPicker/ModelPicker.swift | 276 --- .../TerminalViews/RunInTerminalToolView.swift | 148 -- .../TerminalViews/XTermView.swift | 100 - .../ConversationTab/Views/BotMessage.swift | 27 +- .../Views/ConversationAgentProgressView.swift | 143 -- .../Views/WorkingSetView.swift | 192 -- Core/Sources/HostApp/HostApp.swift | 10 +- Core/Sources/HostApp/MCPConfigView.swift | 278 --- Core/Sources/HostApp/TabContainer.swift | 17 +- .../Extensions/ChatMessage+Storage.swift | 9 +- .../Eye.imageset/Contents.json | 16 - .../Assets.xcassets/Eye.imageset/eye.svg | 3 - .../EyeClosed.imageset/Contents.json | 16 - .../EyeClosed.imageset/eye-closed.svg | 3 - Server/package-lock.json | 1868 +---------------- Server/package.json | 16 +- Server/src/diffView/css/style.css | 67 - Server/src/diffView/diffView.html | 19 - Server/src/diffView/index.js | 23 - Server/src/diffView/js/api.js | 51 - Server/src/diffView/js/monaco-diff-editor.js | 162 -- Server/src/diffView/js/ui-controller.js | 130 -- Server/src/terminal/index.js | 40 - Server/src/terminal/terminal.html | 27 - Server/webpack.config.js | 69 - Tool/Package.swift | 3 +- .../ChatAPIService/Memory/ChatMemory.swift | 36 - Tool/Sources/ChatAPIService/Models.swift | 4 - .../ConversationServiceProvider.swift | 42 +- .../LSPTypes.swift | 174 -- .../ToolNames.swift | 8 - .../Conversation/ClientToolHandler.swift | 19 - .../LanguageServer/ClientToolRegistry.swift | 102 - .../CopilotLocalProcessServer.swift | 10 +- .../LanguageServer/CopilotModelManager.swift | 6 +- .../GitHubCopilotRequest+Conversation.swift | 14 +- .../LanguageServer/GitHubCopilotRequest.swift | 24 - .../LanguageServer/GitHubCopilotService.swift | 122 +- .../LanguageServer/ServerRequestHandler.swift | 6 - .../GitHubCopilotConversationService.swift | 23 +- .../HostAppActivator/HostAppActivator.swift | 22 - Tool/Sources/Preferences/Keys.swift | 8 - Tool/Sources/Terminal/TerminalSession.swift | 256 --- .../Terminal/TerminalSessionManager.swift | 26 - Tool/Sources/Workspace/WorkspaceFile.swift | 31 +- .../XcodeInspector/AppInstanceInspector.swift | 6 +- .../FetchSuggestionsTests.swift | 7 - 76 files changed, 334 insertions(+), 6382 deletions(-) delete mode 100644 Copilot for Xcode/Assets.xcassets/Color.colorset/Contents.json delete mode 100644 Copilot for Xcode/Assets.xcassets/GroupBoxBackgroundColor.colorset/Contents.json delete mode 100644 Copilot for Xcode/Assets.xcassets/GroupBoxStrokeColor.colorset/Contents.json delete mode 100644 Core/Sources/ChatService/ToolCalls/CopilotToolRegistry.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/CreateFileTool.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/GetErrorsTool.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/GetTerminalOutputTool.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/ICopilotTool.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/InsertEditIntoFileTool.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/RunInTerminalTool.swift delete mode 100644 Core/Sources/ChatService/ToolCalls/Utils.swift delete mode 100644 Core/Sources/ConversationTab/Controller/DiffViewWindowController.swift delete mode 100644 Core/Sources/ConversationTab/DiffViews/DiffView.swift delete mode 100644 Core/Sources/ConversationTab/DiffViews/DiffWebView.swift create mode 100644 Core/Sources/ConversationTab/ModelPicker.swift delete mode 100644 Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift delete mode 100644 Core/Sources/ConversationTab/ModelPicker/ModeButton.swift delete mode 100644 Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift delete mode 100644 Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift delete mode 100644 Core/Sources/ConversationTab/TerminalViews/XTermView.swift delete mode 100644 Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift delete mode 100644 Core/Sources/ConversationTab/Views/WorkingSetView.swift delete mode 100644 Core/Sources/HostApp/MCPConfigView.swift delete mode 100644 ExtensionService/Assets.xcassets/Eye.imageset/Contents.json delete mode 100644 ExtensionService/Assets.xcassets/Eye.imageset/eye.svg delete mode 100644 ExtensionService/Assets.xcassets/EyeClosed.imageset/Contents.json delete mode 100644 ExtensionService/Assets.xcassets/EyeClosed.imageset/eye-closed.svg delete mode 100644 Server/src/diffView/css/style.css delete mode 100644 Server/src/diffView/diffView.html delete mode 100644 Server/src/diffView/index.js delete mode 100644 Server/src/diffView/js/api.js delete mode 100644 Server/src/diffView/js/monaco-diff-editor.js delete mode 100644 Server/src/diffView/js/ui-controller.js delete mode 100644 Server/src/terminal/index.js delete mode 100644 Server/src/terminal/terminal.html delete mode 100644 Server/webpack.config.js delete mode 100644 Tool/Sources/ConversationServiceProvider/ToolNames.swift delete mode 100644 Tool/Sources/GitHubCopilotService/Conversation/ClientToolHandler.swift delete mode 100644 Tool/Sources/GitHubCopilotService/LanguageServer/ClientToolRegistry.swift delete mode 100644 Tool/Sources/Terminal/TerminalSession.swift delete mode 100644 Tool/Sources/Terminal/TerminalSessionManager.swift diff --git a/.gitignore b/.gitignore index 9aa8393c..136e2344 100644 --- a/.gitignore +++ b/.gitignore @@ -117,7 +117,6 @@ Core/Package.resolved # Copilot language server Server/node_modules/ -Server/dist # Releases /releases/ diff --git a/Copilot for Xcode.xcodeproj/project.pbxproj b/Copilot for Xcode.xcodeproj/project.pbxproj index 844f7d7c..8f25f9de 100644 --- a/Copilot for Xcode.xcodeproj/project.pbxproj +++ b/Copilot for Xcode.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 70; + objectVersion = 56; objects = { /* Begin PBXBuildFile section */ @@ -255,10 +255,6 @@ C8F103292A7A365000D28F4F /* launchAgent.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = launchAgent.plist; sourceTree = ""; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 9E6A029A2DBDF64200AB6BD5 /* Server */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Server; sourceTree = SOURCE_ROOT; }; -/* End PBXFileSystemSynchronizedRootGroup section */ - /* Begin PBXFrameworksBuildPhase section */ C81458892939EFDC00135263 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -361,7 +357,6 @@ C81458AE293A009800135263 /* Config.debug.xcconfig */, C8CD828229B88006008D044D /* TestPlan.xctestplan */, C828B27D2B1F241500E7612A /* ExtensionPoint.appextensionpoint */, - 9E6A029A2DBDF64200AB6BD5 /* Server */, C81D181E2A1B509B006C1B70 /* Tool */, C8189B282938979000C9DCDA /* Core */, C8189B182938972F00C9DCDA /* Copilot for Xcode */, @@ -705,21 +700,24 @@ /* Begin PBXShellScriptBuildPhase section */ 3A60421A2C8955710006B34C /* ShellScript */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( + "$(SRCROOT)/Server/package.json", + "$(SRCROOT)/Server/package-lock.json", ); outputFileListPaths = ( ); outputPaths = ( + "$(SRCROOT)/Server/node_modules/@github/copilot-language-server/native/darwin-x64/copilot-language-server", + "$(SRCROOT)/Server/node_modules/@github/copilot-language-server/native/darwin-arm64/copilot-language-server-arm64", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export PATH=/usr/local/bin:/opt/homebrew/bin:$PATH\n\nnpm -C Server install\ncp Server/node_modules/@github/copilot-language-server/native/darwin-arm64/copilot-language-server Server/node_modules/@github/copilot-language-server/native/darwin-arm64/copilot-language-server-arm64\n\necho \"Build and copy webview js/html files as the bundle resources\"\nnpm -C Server run build\nmkdir -p \"${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/Resources/webViewDist\"\ncp -R Server/dist/* \"${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/Resources/webViewDist/\"\n"; + shellScript = "npm -C Server install\ncp Server/node_modules/@github/copilot-language-server/native/darwin-arm64/copilot-language-server Server/node_modules/@github/copilot-language-server/native/darwin-arm64/copilot-language-server-arm64\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/Copilot for Xcode/App.swift b/Copilot for Xcode/App.swift index 6a5c9516..e9b7745a 100644 --- a/Copilot for Xcode/App.swift +++ b/Copilot for Xcode/App.swift @@ -19,7 +19,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { enum LaunchMode { case chat case settings - case mcp } func applicationDidFinishLaunching(_ notification: Notification) { @@ -47,8 +46,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { let launchArgs = CommandLine.arguments if launchArgs.contains("--settings") { return .settings - } else if launchArgs.contains("--mcp") { - return .mcp } else { return .chat } @@ -58,8 +55,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { switch mode { case .settings: openSettings() - case .mcp: - openMCPSettings() case .chat: openChat() } @@ -67,7 +62,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { private func openSettings() { DispatchQueue.main.async { - activateAndOpenSettings() + NSApp.activate(ignoringOtherApps: true) + if #available(macOS 14.0, *) { + let environment = SettingsEnvironment() + environment.open() + } else if #available(macOS 13.0, *) { + NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) + } else { + NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) + } } } @@ -80,13 +83,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - private func openMCPSettings() { - DispatchQueue.main.async { - activateAndOpenSettings() - hostAppStore.send(.setActiveTab(2)) - } - } - @available(macOS 13.0, *) private func checkBackgroundPermissions() { Task { @@ -175,18 +171,15 @@ struct CopilotForXcodeApp: App { queue: .main ) { _ in DispatchQueue.main.async { - activateAndOpenSettings() - } - } - - DistributedNotificationCenter.default().addObserver( - forName: .openMCPSettingsWindowRequest, - object: nil, - queue: .main - ) { _ in - DispatchQueue.main.async { - activateAndOpenSettings() - hostAppStore.send(.setActiveTab(2)) + NSApp.activate(ignoringOtherApps: true) + if #available(macOS 14.0, *) { + let environment = SettingsEnvironment() + environment.open() + } else if #available(macOS 13.0, *) { + NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) + } else { + NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) + } } } } @@ -204,17 +197,4 @@ struct CopilotForXcodeApp: App { } } -@MainActor -func activateAndOpenSettings() { - NSApp.activate(ignoringOtherApps: true) - if #available(macOS 14.0, *) { - let environment = SettingsEnvironment() - environment.open() - } else if #available(macOS 13.0, *) { - NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil) - } else { - NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil) - } -} - var isPreview: Bool { ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" } diff --git a/Copilot for Xcode/Assets.xcassets/Color.colorset/Contents.json b/Copilot for Xcode/Assets.xcassets/Color.colorset/Contents.json deleted file mode 100644 index 22c4bb0a..00000000 --- a/Copilot for Xcode/Assets.xcassets/Color.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Copilot for Xcode/Assets.xcassets/GroupBoxBackgroundColor.colorset/Contents.json b/Copilot for Xcode/Assets.xcassets/GroupBoxBackgroundColor.colorset/Contents.json deleted file mode 100644 index f7add95c..00000000 --- a/Copilot for Xcode/Assets.xcassets/GroupBoxBackgroundColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.920", - "green" : "0.910", - "red" : "0.910" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.250", - "green" : "0.250", - "red" : "0.250" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Copilot for Xcode/Assets.xcassets/GroupBoxStrokeColor.colorset/Contents.json b/Copilot for Xcode/Assets.xcassets/GroupBoxStrokeColor.colorset/Contents.json deleted file mode 100644 index 35b93a68..00000000 --- a/Copilot for Xcode/Assets.xcassets/GroupBoxStrokeColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.900", - "green" : "0.900", - "red" : "0.900" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "0.080", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Copilot for Xcode/Credits.rtf b/Copilot for Xcode/Credits.rtf index d282374b..71fc2197 100644 --- a/Copilot for Xcode/Credits.rtf +++ b/Copilot for Xcode/Credits.rtf @@ -3268,58 +3268,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ SOFTWARE.\ \ \ -Dependency: https://github.com/microsoft/monaco-editor\ -Version: 0.52.2\ -License Content:\ -The MIT License (MIT)\ -\ -Copyright (c) 2016 - present Microsoft Corporation\ -\ -Permission is hereby granted, free of charge, to any person obtaining a copy\ -of this software and associated documentation files (the "Software"), to deal\ -in the Software without restriction, including without limitation the rights\ -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ -copies of the Software, and to permit persons to whom the Software is\ -furnished to do so, subject to the following conditions:\ -\ -The above copyright notice and this permission notice shall be included in all\ -copies or substantial portions of the Software.\ -\ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\ -SOFTWARE.\ -\ -\ -Dependency: https://github.com/xtermjs/xterm.js\ -Version: @xterm/addon-fit@0.10.0, @xterm/xterm@5.5.0\ -License Content:\ -The MIT License (MIT)\ -\ -Copyright (c) 2017-2019, The xterm.js authors (https://github.com/xtermjs/xterm.js)\ -Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)\ -Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)\ -\ -Permission is hereby granted, free of charge, to any person obtaining a copy\ -of this software and associated documentation files (the "Software"), to deal\ -in the Software without restriction, including without limitation the rights\ -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ -copies of the Software, and to permit persons to whom the Software is\ -furnished to do so, subject to the following conditions:\ -\ -The above copyright notice and this permission notice shall be included in\ -all copies or substantial portions of the Software.\ -\ -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\ -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\ -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\ -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\ -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\ -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\ -THE SOFTWARE.\ -\ -\ } \ No newline at end of file diff --git a/Core/Package.swift b/Core/Package.swift index 3de53aeb..0567e481 100644 --- a/Core/Package.swift +++ b/Core/Package.swift @@ -178,8 +178,7 @@ let package = Package( .product(name: "AXHelper", package: "Tool"), .product(name: "ConversationServiceProvider", package: "Tool"), .product(name: "GitHubCopilotService", package: "Tool"), - .product(name: "Workspace", package: "Tool"), - .product(name: "Terminal", package: "Tool") + .product(name: "Workspace", package: "Tool") ]), .testTarget( name: "ChatServiceTests", @@ -199,8 +198,7 @@ let package = Package( .product(name: "MarkdownUI", package: "swift-markdown-ui"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "SwiftUIFlowLayout", package: "swiftui-flow-layout"), - .product(name: "Persist", package: "Tool"), - .product(name: "Terminal", package: "Tool") + .product(name: "Persist", package: "Tool") ] ), diff --git a/Core/Sources/ChatService/ChatService.swift b/Core/Sources/ChatService/ChatService.swift index b714dbc9..9cfd6702 100644 --- a/Core/Sources/ChatService/ChatService.swift +++ b/Core/Sources/ChatService/ChatService.swift @@ -13,63 +13,22 @@ import ChatTab import Logger import Workspace import XcodeInspector -import OrderedCollections public protocol ChatServiceType { var memory: ContextAwareAutoManagedChatMemory { get set } - func send(_ id: String, content: String, skillSet: [ConversationSkill], references: [FileReference], model: String?, agentMode: Bool) async throws + func send(_ id: String, content: String, skillSet: [ConversationSkill], references: [FileReference], model: String?) async throws func stopReceivingMessage() async func upvote(_ id: String, _ rating: ConversationRating) async func downvote(_ id: String, _ rating: ConversationRating) async func copyCode(_ id: String) async } -struct ToolCallRequest { - let requestId: JSONId - let turnId: String - let roundId: Int - let toolCallId: String - let completion: (AnyJSONRPCResponse) -> Void -} - -public struct FileEdit: Equatable { - - public enum Status: String { - case none = "none" - case kept = "kept" - case undone = "undone" - } - - public let fileURL: URL - public let originalContent: String - public var modifiedContent: String - public var status: Status - - /// Different toolName, the different undo logic. Like `insert_edit_into_file` and `create_file` - public var toolName: ToolName - - public init( - fileURL: URL, - originalContent: String, - modifiedContent: String, - status: Status = .none, - toolName: ToolName - ) { - self.fileURL = fileURL - self.originalContent = originalContent - self.modifiedContent = modifiedContent - self.status = status - self.toolName = toolName - } -} - public final class ChatService: ChatServiceType, ObservableObject { public var memory: ContextAwareAutoManagedChatMemory @Published public internal(set) var chatHistory: [ChatMessage] = [] @Published public internal(set) var isReceivingMessage = false - @Published public internal(set) var fileEditMap: OrderedDictionary = [:] - public let chatTabInfo: ChatTabInfo + private let chatTabInfo: ChatTabInfo private let conversationProvider: ConversationServiceProvider? private let conversationProgressHandler: ConversationProgressHandler private let conversationContextHandler: ConversationContextHandler = ConversationContextHandlerImpl.shared @@ -80,7 +39,6 @@ public final class ChatService: ChatServiceType, ObservableObject { private(set) public var conversationId: String? private var skillSet: [ConversationSkill] = [] private var isRestored: Bool = false - private var pendingToolCallRequests: [String: ToolCallRequest] = [:] init(provider: any ConversationServiceProvider, memory: ContextAwareAutoManagedChatMemory = ContextAwareAutoManagedChatMemory(), conversationProgressHandler: ConversationProgressHandler = ConversationProgressHandlerImpl.shared, @@ -94,7 +52,6 @@ public final class ChatService: ChatServiceType, ObservableObject { subscribeToNotifications() subscribeToConversationContextRequest() subscribeToWatchedFilesHandler() - subscribeToClientToolInvokeEvent() } private func subscribeToNotifications() { @@ -135,68 +92,7 @@ public final class ChatService: ChatServiceType, ObservableObject { self.startFileChangeWatcher() }).store(in: &cancellables) } - - private func subscribeToClientToolInvokeEvent() { - ClientToolHandlerImpl.shared.onClientToolInvokeEvent.sink(receiveValue: { [weak self] (request, completion) in - guard let params = request.params, params.conversationId == self?.conversationId else { return } - guard let copilotTool = CopilotToolRegistry.shared.getTool(name: params.name) else { - completion(AnyJSONRPCResponse(id: request.id, - result: JSONValue.array([ - JSONValue.null, - JSONValue.hash( - [ - "code": .number(-32601), - "message": .string("Tool function not found") - ]) - ]) - ) - ) - return - } - - let completed = copilotTool.invokeTool(request, completion: completion, chatHistoryUpdater: self?.appendToolCallHistory, contextProvider: self) - if !completed { - self?.pendingToolCallRequests[params.toolCallId] = ToolCallRequest( - requestId: request.id, - turnId: params.turnId, - roundId: params.roundId, - toolCallId: params.toolCallId, - completion: completion) - } - }).store(in: &cancellables) - } - - private func appendToolCallHistory(turnId: String, editAgentRounds: [AgentRound]) { - let chatTabId = self.chatTabInfo.id - Task { - let message = ChatMessage( - id: turnId, - chatTabID: chatTabId, - clsTurnID: turnId, - role: .assistant, - content: "", - references: [], - steps: [], - editAgentRounds: editAgentRounds - ) - - await self.memory.appendMessage(message) - } - } - public func updateFileEdits(by fileEdit: FileEdit) { - if let existingFileEdit = self.fileEditMap[fileEdit.fileURL] { - self.fileEditMap[fileEdit.fileURL] = .init( - fileURL: fileEdit.fileURL, - originalContent: existingFileEdit.originalContent, - modifiedContent: fileEdit.modifiedContent, - toolName: existingFileEdit.toolName - ) - } else { - self.fileEditMap[fileEdit.fileURL] = fileEdit - } - } - public static func service(for chatTabInfo: ChatTabInfo) -> ChatService { let provider = BuiltinExtensionConversationServiceProvider( extension: GitHubCopilotExtension.self @@ -217,81 +113,8 @@ public final class ChatService: ChatServiceType, ObservableObject { self.isRestored = true } - - public func updateToolCallStatus(toolCallId: String, status: AgentToolCall.ToolCallStatus, payload: Any? = nil) { - if status == .cancelled { - resetOngoingRequest() - return - } - - // Send the tool call result back to the server - if let toolCallRequest = self.pendingToolCallRequests[toolCallId], status == .completed, let result = payload { - self.pendingToolCallRequests.removeValue(forKey: toolCallId) - let toolResult = LanguageModelToolResult(content: [ - .init(value: result) - ]) - let jsonResult = try? JSONEncoder().encode(toolResult) - let jsonValue = (try? JSONDecoder().decode(JSONValue.self, from: jsonResult ?? Data())) ?? JSONValue.null - toolCallRequest.completion( - AnyJSONRPCResponse( - id: toolCallRequest.requestId, - result: JSONValue.array([ - jsonValue, - JSONValue.null - ]) - ) - ) - } - - // Update the tool call status in the chat history - Task { - guard let lastMessage = await memory.history.last, lastMessage.role == .assistant else { - return - } - - var updatedAgentRounds: [AgentRound] = [] - for i in 0.., references: Array, model: String? = nil, agentMode: Bool = false) async throws { + + public func send(_ id: String, content: String, skillSet: Array, references: Array, model: String? = nil) async throws { guard activeRequestId == nil else { return } let workDoneToken = UUID().uuidString activeRequestId = workDoneToken @@ -305,9 +128,6 @@ public final class ChatService: ChatServiceType, ObservableObject { ) await memory.appendMessage(chatMessage) - // reset file edits - self.resetFileEdits() - // persist saveChatMessageToStorage(chatMessage) @@ -337,8 +157,6 @@ public final class ChatService: ChatServiceType, ObservableObject { let ignoredSkills: [String] = skillCapabilities.filter { !supportedSkills.contains($0) } - let currentEditorSkill = skillSet.first { $0.id == CurrentEditorSkill.ID } - let activeDoc: Doc? = (currentEditorSkill as? CurrentEditorSkill).map { Doc(uri: $0.currentFile.url.absoluteString) } /// replace the `@workspace` to `@project` let newContent = replaceFirstWord(in: content, from: "@workspace", to: "@project") @@ -346,12 +164,10 @@ public final class ChatService: ChatServiceType, ObservableObject { let request = ConversationRequest(workDoneToken: workDoneToken, content: newContent, workspaceFolder: "", - activeDoc: activeDoc, skills: skillCapabilities, ignoredSkills: ignoredSkills, references: references, - model: model, - agentMode: agentMode) + model: model) self.skillSet = skillSet try await send(request) } @@ -523,7 +339,6 @@ public final class ChatService: ChatServiceType, ObservableObject { var content = "" var references: [ConversationReference] = [] var steps: [ConversationProgressStep] = [] - var editAgentRounds: [AgentRound] = [] if let reply = progress.reply { content = reply @@ -537,11 +352,7 @@ public final class ChatService: ChatServiceType, ObservableObject { steps = progressSteps } - if let progressAgentRounds = progress.editAgentRounds, !progressAgentRounds.isEmpty { - editAgentRounds = progressAgentRounds - } - - if content.isEmpty && references.isEmpty && steps.isEmpty && editAgentRounds.isEmpty { + if content.isEmpty && references.isEmpty && steps.isEmpty { return } @@ -549,7 +360,6 @@ public final class ChatService: ChatServiceType, ObservableObject { let messageContent = content let messageReferences = references let messageSteps = steps - let messageAgentRounds = editAgentRounds Task { let message = ChatMessage( @@ -559,8 +369,7 @@ public final class ChatService: ChatServiceType, ObservableObject { role: .assistant, content: messageContent, references: messageReferences, - steps: messageSteps, - editAgentRounds: messageAgentRounds + steps: messageSteps ) // will persist in resetOngoingRequest() @@ -638,22 +447,7 @@ public final class ChatService: ChatServiceType, ObservableObject { private func resetOngoingRequest() { activeRequestId = nil isReceivingMessage = false - - // cancel all pending tool call requests - for (_, request) in pendingToolCallRequests { - pendingToolCallRequests.removeValue(forKey: request.toolCallId) - request.completion(AnyJSONRPCResponse(id: request.requestId, - result: JSONValue.array([ - JSONValue.null, - JSONValue.hash( - [ - "code": .number(-32800), // client cancelled - "message": .string("The client cancelled the tool call request \(request.toolCallId)") - ]) - ]) - ) - ) - } + Task { // mark running steps to cancelled @@ -667,21 +461,8 @@ public final class ChatService: ChatServiceType, ObservableObject { history[lastIndex].steps[i].status = .cancelled } } - - for i in 0.. ICopilotTool? { - return tools[name] - } -} diff --git a/Core/Sources/ChatService/ToolCalls/CreateFileTool.swift b/Core/Sources/ChatService/ToolCalls/CreateFileTool.swift deleted file mode 100644 index c314724f..00000000 --- a/Core/Sources/ChatService/ToolCalls/CreateFileTool.swift +++ /dev/null @@ -1,98 +0,0 @@ -import JSONRPC -import ConversationServiceProvider -import Foundation -import Logger - -public class CreateFileTool: ICopilotTool { - public static let name = ToolName.createFile - - public func invokeTool( - _ request: InvokeClientToolRequest, - completion: @escaping (AnyJSONRPCResponse) -> Void, - chatHistoryUpdater: ChatHistoryUpdater?, - contextProvider: (any ToolContextProvider)? - ) -> Bool { - guard let params = request.params, - let input = params.input, - let filePath = input["filePath"]?.value as? String, - let content = input["content"]?.value as? String - else { - completeResponse(request, response: "Invalid parameters", completion: completion) - return true - } - - let fileURL = URL(fileURLWithPath: filePath) - - guard !FileManager.default.fileExists(atPath: filePath) - else { - completeResponse(request, response: "File already exists at \(filePath)", completion: completion) - return true - } - - do { - try content.write(to: fileURL, atomically: true, encoding: .utf8) - } catch { - completeResponse(request, response: "Failed to write content to file: \(error)", completion: completion) - return true - } - - guard FileManager.default.fileExists(atPath: filePath), - let writtenContent = try? String(contentsOf: fileURL, encoding: .utf8), - !writtenContent.isEmpty - else { - completeResponse(request, response: "Failed to verify file creation.", completion: completion) - return true - } - - contextProvider?.updateFileEdits(by: .init( - fileURL: URL(fileURLWithPath: filePath), - originalContent: "", - modifiedContent: content, - toolName: CreateFileTool.name - )) - - do { - if let workspacePath = contextProvider?.chatTabInfo.workspacePath, - let xcodeIntance = Utils.getXcode(by: workspacePath) { - try Utils.openFileInXcode(fileURL: URL(fileURLWithPath: filePath), xcodeInstance: xcodeIntance) - } - } catch { - Logger.client.info("Failed to open file in Xcode, \(error)") - } - - let editAgentRounds: [AgentRound] = [ - .init( - roundId: params.roundId, - reply: "", - toolCalls: [ - .init( - id: params.toolCallId, - name: params.name, - status: .completed, - invokeParams: params - ) - ] - ) - ] - - if let chatHistoryUpdater { - chatHistoryUpdater(params.turnId, editAgentRounds) - } - - completeResponse( - request, - response: "File created at \(filePath).", - completion: completion - ) - return true - } - - public static func undo(for fileURL: URL) throws { - var isDirectory: ObjCBool = false - guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory), - !isDirectory.boolValue - else { return } - - try FileManager.default.removeItem(at: fileURL) - } -} diff --git a/Core/Sources/ChatService/ToolCalls/GetErrorsTool.swift b/Core/Sources/ChatService/ToolCalls/GetErrorsTool.swift deleted file mode 100644 index f95625dc..00000000 --- a/Core/Sources/ChatService/ToolCalls/GetErrorsTool.swift +++ /dev/null @@ -1,74 +0,0 @@ -import JSONRPC -import Foundation -import ConversationServiceProvider -import XcodeInspector -import AppKit - -public class GetErrorsTool: ICopilotTool { - public func invokeTool( - _ request: InvokeClientToolRequest, - completion: @escaping (AnyJSONRPCResponse) -> Void, - chatHistoryUpdater: ChatHistoryUpdater?, - contextProvider: ToolContextProvider? - ) -> Bool { - guard let params = request.params, - let input = params.input, - let filePaths = input["filePaths"]?.value as? [String] - else { - completeResponse(request, completion: completion) - return true - } - - guard let xcodeInstance = XcodeInspector.shared.xcodes.first( - where: { - $0.workspaceURL?.path == contextProvider?.chatTabInfo.workspacePath - }), - let documentURL = xcodeInstance.realtimeDocumentURL, - filePaths.contains(where: { URL(fileURLWithPath: $0) == documentURL }) - else { - completeResponse(request, completion: completion) - return true - } - - /// Not leveraging the `getFocusedEditorContent` in `XcodeInspector`. - /// As the resolving should be sync. Especially when completion the JSONRPCResponse - let focusedElement: AXUIElement? = try? xcodeInstance.appElement.copyValue(key: kAXFocusedUIElementAttribute) - let focusedEditor: SourceEditor? - if let editorElement = focusedElement, editorElement.isSourceEditor { - focusedEditor = .init(runningApplication: xcodeInstance.runningApplication, element: editorElement) - } else if let element = focusedElement, let editorElement = element.firstParent(where: \.isSourceEditor) { - focusedEditor = .init(runningApplication: xcodeInstance.runningApplication, element: editorElement) - } else { - focusedEditor = nil - } - - var errors: String = "" - - if let focusedEditor - { - let editorContent = focusedEditor.getContent() - let errorArray: [String] = editorContent.lineAnnotations.map { - """ - \(documentURL.absoluteString) - - \($0.message) - - - \($0.line) - 0 - - - \($0.line) - 0 - - - - """ - } - errors = errorArray.joined(separator: "\n") - } - - completeResponse(request, response: errors, completion: completion) - return true - } -} diff --git a/Core/Sources/ChatService/ToolCalls/GetTerminalOutputTool.swift b/Core/Sources/ChatService/ToolCalls/GetTerminalOutputTool.swift deleted file mode 100644 index 1d298711..00000000 --- a/Core/Sources/ChatService/ToolCalls/GetTerminalOutputTool.swift +++ /dev/null @@ -1,33 +0,0 @@ -import ConversationServiceProvider -import Foundation -import JSONRPC -import Terminal - -public class GetTerminalOutputTool: ICopilotTool { - public func invokeTool(_ request: InvokeClientToolRequest, completion: @escaping (AnyJSONRPCResponse) -> Void, chatHistoryUpdater: ChatHistoryUpdater?, contextProvider: (any ToolContextProvider)?) -> Bool { - var result: String = "" - if let input = request.params?.input as? [String: AnyCodable], let terminalId = input["id"]?.value as? String{ - let session = TerminalSessionManager.shared.getSession(for: terminalId) - result = session?.getCommandOutput() ?? "Terminal id \(terminalId) not found" - } else { - result = "Invalid arguments for \(ToolName.getTerminalOutput.rawValue) tool call" - } - - let toolResult = LanguageModelToolResult(content: [ - .init(value: result) - ]) - let jsonResult = try? JSONEncoder().encode(toolResult) - let jsonValue = (try? JSONDecoder().decode(JSONValue.self, from: jsonResult ?? Data())) ?? JSONValue.null - completion( - AnyJSONRPCResponse( - id: request.id, - result: JSONValue.array([ - jsonValue, - JSONValue.null - ]) - ) - ) - - return true - } -} diff --git a/Core/Sources/ChatService/ToolCalls/ICopilotTool.swift b/Core/Sources/ChatService/ToolCalls/ICopilotTool.swift deleted file mode 100644 index 76a35772..00000000 --- a/Core/Sources/ChatService/ToolCalls/ICopilotTool.swift +++ /dev/null @@ -1,53 +0,0 @@ -import ConversationServiceProvider -import JSONRPC -import ChatTab - -public protocol ToolContextProvider { - // MARK: insert_edit_into_file - var chatTabInfo: ChatTabInfo { get } - func updateFileEdits(by fileEdit: FileEdit) -> Void -} - -public typealias ChatHistoryUpdater = (String, [AgentRound]) -> Void - -public protocol ICopilotTool { - /** - * Invokes the Copilot tool with the given request. - * - Parameters: - * - request: The tool invocation request. - * - completion: Closure called with JSON-RPC response when tool execution completes. - * - chatHistoryUpdater: Optional closure to update chat history during tool execution. - * - contextProvider: Optional provider that supplies additional context information - * needed for tool execution, such as chat tab data and file editing capabilities. - * - Returns: Boolean indicating if the tool call has completed. True if the tool call is completed, false otherwise. - */ - func invokeTool( - _ request: InvokeClientToolRequest, - completion: @escaping (AnyJSONRPCResponse) -> Void, - chatHistoryUpdater: ChatHistoryUpdater?, - contextProvider: ToolContextProvider? - ) -> Bool -} - -extension ICopilotTool { - /** - * Completes a tool response. - * - Parameters: - * - request: The original tool invocation request. - * - response: The string value to include in the response content. - * - completion: The completion handler to call with the response. - */ - func completeResponse( - _ request: InvokeClientToolRequest, - response: String = "", - completion: @escaping (AnyJSONRPCResponse) -> Void - ) { - let result: JSONValue = .array([ - .hash(["content": .array([.hash(["value": .string(response)])])]), - .null - ]) - completion(AnyJSONRPCResponse(id: request.id, result: result)) - } -} - -extension ChatService: ToolContextProvider { } diff --git a/Core/Sources/ChatService/ToolCalls/InsertEditIntoFileTool.swift b/Core/Sources/ChatService/ToolCalls/InsertEditIntoFileTool.swift deleted file mode 100644 index 95185c28..00000000 --- a/Core/Sources/ChatService/ToolCalls/InsertEditIntoFileTool.swift +++ /dev/null @@ -1,126 +0,0 @@ -import ConversationServiceProvider -import AppKit -import JSONRPC -import Foundation -import XcodeInspector -import Logger -import AXHelper - -public class InsertEditIntoFileTool: ICopilotTool { - public static let name = ToolName.insertEditIntoFile - - public func invokeTool( - _ request: InvokeClientToolRequest, - completion: @escaping (AnyJSONRPCResponse) -> Void, - chatHistoryUpdater: ChatHistoryUpdater?, - contextProvider: (any ToolContextProvider)? - ) -> Bool { - guard let params = request.params, - let input = request.params?.input, - let code = input["code"]?.value as? String, - let filePath = input["filePath"]?.value as? String, - let contextProvider - else { - return true - } - - let fileURL = URL(fileURLWithPath: filePath) - do { - let originalContent = try String(contentsOf: fileURL, encoding: .utf8) - - try InsertEditIntoFileTool.applyEdit(for: fileURL, content: code, contextProvider: contextProvider) - - contextProvider.updateFileEdits( - by: .init(fileURL: fileURL, originalContent: originalContent, modifiedContent: code, toolName: InsertEditIntoFileTool.name) - ) - - let editAgentRounds: [AgentRound] = [ - .init( - roundId: params.roundId, - reply: "", - toolCalls: [ - .init( - id: params.toolCallId, - name: params.name, - status: .completed, - invokeParams: params - ) - ] - ) - ] - - if let chatHistoryUpdater { - chatHistoryUpdater(params.turnId, editAgentRounds) - } - - completeResponse(request, response: code, completion: completion) - } catch { - Logger.client.error("Failed to apply edits, \(error)") - completeResponse(request, response: error.localizedDescription, completion: completion) - } - - return true - } - - public static func applyEdit(for fileURL: URL, content: String, contextProvider: (any ToolContextProvider), xcodeInstance: XcodeAppInstanceInspector) throws { - - /// wait a while for opening file in xcode. (3 seconds) - var retryCount = 6 - while retryCount > 0 { - guard xcodeInstance.realtimeDocumentURL != fileURL else { break } - - retryCount -= 1 - - /// Failed to get the target documentURL - if retryCount == 0 { - return - } - - Thread.sleep(forTimeInterval: 0.5) - } - - guard xcodeInstance.realtimeDocumentURL == fileURL - else { throw NSError(domain: "The file \(fileURL) is not opened in Xcode", code: 0)} - - /// keep change - guard let element: AXUIElement = try? xcodeInstance.appElement.copyValue(key: kAXFocusedUIElementAttribute) - else { - throw NSError(domain: "Failed to access xcode element", code: 0) - } - let value: String = (try? element.copyValue(key: kAXValueAttribute)) ?? "" - let lines = value.components(separatedBy: .newlines) - - var isInjectedSuccess = false - try AXHelper().injectUpdatedCodeWithAccessibilityAPI( - .init( - content: content, - newSelection: nil, - modifications: [ - .deletedSelection( - .init(start: .init(line: 0, character: 0), end: .init(line: lines.count - 1, character: (lines.last?.count ?? 100) - 1)) - ), - .inserted(0, [content]) - ] - ), - focusElement: element, - onSuccess: { - isInjectedSuccess = true - } - ) - - if !isInjectedSuccess { - throw NSError(domain: "Failed to apply edit", code: 0) - } - - } - - public static func applyEdit(for fileURL: URL, content: String, contextProvider: (any ToolContextProvider)) throws { - guard let xcodeInstance = Utils.getXcode(by: contextProvider.chatTabInfo.workspacePath) - else { - throw NSError(domain: "The workspace \(contextProvider.chatTabInfo.workspacePath) is not opened in xcode", code: 0, userInfo: nil) - } - - try Utils.openFileInXcode(fileURL: fileURL, xcodeInstance: xcodeInstance) - try applyEdit(for: fileURL, content: content, contextProvider: contextProvider, xcodeInstance: xcodeInstance) - } -} diff --git a/Core/Sources/ChatService/ToolCalls/RunInTerminalTool.swift b/Core/Sources/ChatService/ToolCalls/RunInTerminalTool.swift deleted file mode 100644 index a577dd6f..00000000 --- a/Core/Sources/ChatService/ToolCalls/RunInTerminalTool.swift +++ /dev/null @@ -1,22 +0,0 @@ -import ConversationServiceProvider -import JSONRPC - -public class RunInTerminalTool: ICopilotTool { - public func invokeTool(_ request: InvokeClientToolRequest, completion: @escaping (AnyJSONRPCResponse) -> Void, chatHistoryUpdater: ChatHistoryUpdater?, contextProvider: (any ToolContextProvider)?) -> Bool { - let params = request.params! - let editAgentRounds: [AgentRound] = [ - AgentRound(roundId: params.roundId, - reply: "", - toolCalls: [ - AgentToolCall(id: params.toolCallId, name: params.name, status: .waitForConfirmation, invokeParams: params) - ] - ) - ] - - if let chatHistoryUpdater = chatHistoryUpdater { - chatHistoryUpdater(params.turnId, editAgentRounds) - } - - return false - } -} diff --git a/Core/Sources/ChatService/ToolCalls/Utils.swift b/Core/Sources/ChatService/ToolCalls/Utils.swift deleted file mode 100644 index 30cc3b06..00000000 --- a/Core/Sources/ChatService/ToolCalls/Utils.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import XcodeInspector -import AppKit -import Logger - -class Utils { - public static func openFileInXcode(fileURL: URL, xcodeInstance: XcodeAppInstanceInspector) throws { - /// TODO: when xcode minimized, the activate not work. - guard xcodeInstance.activate(options: [.activateAllWindows, .activateIgnoringOtherApps]) else { - throw NSError(domain: "Failed to activate xcode instance", code: 0) - } - - /// wait for a while to allow activation (especially un-minimizing) to complete - Thread.sleep(forTimeInterval: 0.3) - - let configuration = NSWorkspace.OpenConfiguration() - configuration.activates = true - - NSWorkspace.shared.open( - [fileURL], - withApplicationAt: xcodeInstance.runningApplication.bundleURL!, - configuration: configuration) { app, error in - if error != nil { - Logger.client.error("Failed to open file \(String(describing: error))") - } - } - } - - public static func getXcode(by workspacePath: String) -> XcodeAppInstanceInspector? { - return XcodeInspector.shared.xcodes.first( - where: { - return $0.workspaceURL?.path == workspacePath - }) - } -} diff --git a/Core/Sources/ConversationTab/Chat.swift b/Core/Sources/ConversationTab/Chat.swift index 8ab9123d..1f1c2af2 100644 --- a/Core/Sources/ConversationTab/Chat.swift +++ b/Core/Sources/ConversationTab/Chat.swift @@ -7,9 +7,6 @@ import Terminal import ConversationServiceProvider import Persist import GitHubCopilotService -import Logger -import OrderedCollections -import SwiftUI public struct DisplayedChatMessage: Equatable { public enum Role: Equatable { @@ -27,9 +24,8 @@ public struct DisplayedChatMessage: Equatable { public var suggestedTitle: String? = nil public var errorMessage: String? = nil public var steps: [ConversationProgressStep] = [] - public var editAgentRounds: [AgentRound] = [] - public init(id: String, role: Role, text: String, references: [ConversationReference] = [], followUp: ConversationFollowUp? = nil, suggestedTitle: String? = nil, errorMessage: String? = nil, steps: [ConversationProgressStep] = [], editAgentRounds: [AgentRound] = []) { + public init(id: String, role: Role, text: String, references: [ConversationReference] = [], followUp: ConversationFollowUp? = nil, suggestedTitle: String? = nil, errorMessage: String? = nil, steps: [ConversationProgressStep] = []) { self.id = id self.role = role self.text = text @@ -38,7 +34,6 @@ public struct DisplayedChatMessage: Equatable { self.suggestedTitle = suggestedTitle self.errorMessage = errorMessage self.steps = steps - self.editAgentRounds = editAgentRounds } } @@ -62,10 +57,7 @@ struct Chat { var focusedField: Field? var currentEditor: FileReference? = nil var selectedFiles: [FileReference] = [] - /// Cache the original content - var fileEditMap: OrderedDictionary = [:] - var diffViewerController: DiffViewWindowController? = nil - var isAgentMode: Bool = AppState.shared.isAgentModeEnabled() + enum Field: String, Hashable { case textField case fileSearchBar @@ -90,18 +82,13 @@ struct Chat { case downvote(MessageID, ConversationRating) case copyCode(MessageID) case insertCode(String) - case toolCallStarted(String) - case toolCallCompleted(String, String) - case toolCallCancelled(String) case observeChatService case observeHistoryChange case observeIsReceivingMessageChange - case observeFileEditChange case historyChanged case isReceivingMessageChanged - case fileEditChanged case chatMenu(ChatMenu.Action) @@ -112,16 +99,6 @@ struct Chat { case setCurrentEditor(FileReference) case followUpButtonClicked(String, String) - - // Agent File Edit - case undoEdits(fileURLs: [URL]) - case keepEdits(fileURLs: [URL]) - case resetEdits - case discardFileEdits(fileURLs: [URL]) - case openDiffViewWindow(fileURL: URL) - case setDiffViewerController(chat: StoreOf) - - case agentModeChanged(Bool) } let service: ChatService @@ -131,11 +108,9 @@ struct Chat { case observeHistoryChange(UUID) case observeIsReceivingMessageChange(UUID) case sendMessage(UUID) - case observeFileEditChange(UUID) } @Dependency(\.openURL) var openURL - @AppStorage(\.enableCurrentEditorContext) var enableCurrentEditorContext: Bool var body: some ReducerOf { BindingReducer() @@ -154,12 +129,6 @@ struct Chat { await send(.isReceivingMessageChanged) await send(.focusOnTextField) await send(.refresh) - - let publisher = NotificationCenter.default.publisher(for: .gitHubCopilotChatModeDidChange) - for await _ in publisher.values { - let isAgentMode = AppState.shared.isAgentModeEnabled() - await send(.agentModeChanged(isAgentMode)) - } } case .refresh: @@ -170,42 +139,21 @@ struct Chat { case let .sendButtonTapped(id): guard !state.typedMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return .none } let message = state.typedMessage - let skillSet = state.buildSkillSet( - isCurrentEditorContextEnabled: enableCurrentEditorContext - ) + let skillSet = state.buildSkillSet() state.typedMessage = "" let selectedFiles = state.selectedFiles - let selectedModelFamily = AppState.shared.getSelectedModelFamily() ?? CopilotModelManager.getDefaultChatModel(scope: AppState.shared.modelScope())?.modelFamily - let agentMode = AppState.shared.isAgentModeEnabled() - return .run { _ in - try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily, agentMode: agentMode) - }.cancellable(id: CancelID.sendMessage(self.id)) - - case let .toolCallStarted(toolCallId): - guard !toolCallId.isEmpty else { return .none } - return .run { _ in - service.updateToolCallStatus(toolCallId: toolCallId, status: .running) - }.cancellable(id: CancelID.sendMessage(self.id)) - case let .toolCallCancelled(toolCallId): - guard !toolCallId.isEmpty else { return .none } + let selectedModelFamily = AppState.shared.getSelectedModelFamily() ?? CopilotModelManager.getDefaultChatLLM()?.modelFamily return .run { _ in - service.updateToolCallStatus(toolCallId: toolCallId, status: .cancelled) - }.cancellable(id: CancelID.sendMessage(self.id)) - case let .toolCallCompleted(toolCallId, result): - guard !toolCallId.isEmpty else { return .none } - return .run { _ in - service.updateToolCallStatus(toolCallId: toolCallId, status: .completed, payload: result) + try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily) }.cancellable(id: CancelID.sendMessage(self.id)) case let .followUpButtonClicked(id, message): guard !message.isEmpty else { return .none } - let skillSet = state.buildSkillSet( - isCurrentEditorContextEnabled: enableCurrentEditorContext - ) + let skillSet = state.buildSkillSet() let selectedFiles = state.selectedFiles - let selectedModelFamily = AppState.shared.getSelectedModelFamily() ?? CopilotModelManager.getDefaultChatModel(scope: AppState.shared.modelScope())?.modelFamily + let selectedModelFamily = AppState.shared.getSelectedModelFamily() ?? CopilotModelManager.getDefaultChatLLM()?.modelFamily return .run { _ in try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily) @@ -275,7 +223,6 @@ struct Chat { return .run { send in await send(.observeHistoryChange) await send(.observeIsReceivingMessageChange) - await send(.observeFileEditChange) } case .observeHistoryChange: @@ -315,25 +262,6 @@ struct Chat { id: CancelID.observeIsReceivingMessageChange(id), cancelInFlight: true ) - - case .observeFileEditChange: - return .run { send in - let stream = AsyncStream { continuation in - let cancellable = service.$fileEditMap - .sink { _ in - continuation.yield() - } - continuation.onTermination = { _ in - cancellable.cancel() - } - } - for await _ in stream { - await send(.fileEditChanged) - } - }.cancellable( - id: CancelID.observeFileEditChange(id), - cancelInFlight: true - ) case .historyChanged: state.history = service.chatHistory.flatMap { message in @@ -358,8 +286,7 @@ struct Chat { followUp: message.followUp, suggestedTitle: message.suggestedTitle, errorMessage: message.errorMessage, - steps: message.steps, - editAgentRounds: message.editAgentRounds + steps: message.steps )) return all @@ -370,42 +297,6 @@ struct Chat { case .isReceivingMessageChanged: state.isReceivingMessage = service.isReceivingMessage return .none - - case .fileEditChanged: - state.fileEditMap = service.fileEditMap - let fileEditMap = state.fileEditMap - - let diffViewerController = state.diffViewerController - - return .run { _ in - /// refresh diff view - - guard let diffViewerController, - diffViewerController.diffViewerState == .shown - else { return } - - if fileEditMap.isEmpty { - await diffViewerController.hideWindow() - return - } - - guard let currentFileEdit = diffViewerController.currentFileEdit - else { return } - - if let updatedFileEdit = fileEditMap[currentFileEdit.fileURL] { - if updatedFileEdit != currentFileEdit { - if updatedFileEdit.status == .undone, - updatedFileEdit.toolName == .createFile - { - await diffViewerController.hideWindow() - } else { - await diffViewerController.showDiffWindow(fileEdit: updatedFileEdit) - } - } - } else { - await diffViewerController.hideWindow() - } - } case .binding: return .none @@ -443,54 +334,6 @@ struct Chat { case let .setCurrentEditor(fileReference): state.currentEditor = fileReference return .none - - // MARK: - Agent Edits - - case let .undoEdits(fileURLs): - for fileURL in fileURLs { - do { - try service.undoFileEdit(for: fileURL) - } catch { - Logger.service.error("Failed to undo edit, \(error)") - } - } - - return .none - - case let .keepEdits(fileURLs): - for fileURL in fileURLs { - service.keepFileEdit(for: fileURL) - } - - return .none - - case .resetEdits: - service.resetFileEdits() - - return .none - - case let .discardFileEdits(fileURLs): - for fileURL in fileURLs { - try? service.discardFileEdit(for: fileURL) - } - return .none - - case let .openDiffViewWindow(fileURL): - guard let fileEdit = state.fileEditMap[fileURL], - let diffViewerController = state.diffViewerController - else { return .none } - - return .run { _ in - await diffViewerController.showDiffWindow(fileEdit: fileEdit) - } - - case let .setDiffViewerController(chat): - state.diffViewerController = .init(chat: chat) - return .none - - case let .agentModeChanged(isAgentMode): - state.isAgentMode = isAgentMode - return .none } } } @@ -564,4 +407,3 @@ private actor TimedDebounceFunction { await block() } } - diff --git a/Core/Sources/ConversationTab/ChatExtension.swift b/Core/Sources/ConversationTab/ChatExtension.swift index 27220a96..0e3537b1 100644 --- a/Core/Sources/ConversationTab/ChatExtension.swift +++ b/Core/Sources/ConversationTab/ChatExtension.swift @@ -2,8 +2,8 @@ import ChatService import ConversationServiceProvider extension Chat.State { - func buildSkillSet(isCurrentEditorContextEnabled: Bool) -> [ConversationSkill] { - guard let currentFile = self.currentEditor, isCurrentEditorContextEnabled else { + func buildSkillSet() -> [ConversationSkill] { + guard let currentFile = self.currentEditor else { return [] } let fileReference = FileReference( diff --git a/Core/Sources/ConversationTab/ChatPanel.swift b/Core/Sources/ConversationTab/ChatPanel.swift index 27c2cc86..655c4570 100644 --- a/Core/Sources/ConversationTab/ChatPanel.swift +++ b/Core/Sources/ConversationTab/ChatPanel.swift @@ -11,8 +11,6 @@ import SwiftUIFlowLayout import XcodeInspector import ChatTab import Workspace -import HostAppActivator -import Persist private let r: Double = 8 @@ -47,11 +45,6 @@ public struct ChatPanel: View { } } - if chat.fileEditMap.count > 0 { - WorkingSetView(chat: chat) - .padding(.trailing, 16) - } - ChatPanelInputArea(chat: chat) .padding(.trailing, 16) } @@ -345,8 +338,7 @@ struct ChatHistoryItem: View { followUp: message.followUp, errorMessage: message.errorMessage, chat: chat, - steps: message.steps, - editAgentRounds: message.editAgentRounds + steps: message.steps ) case .system: FunctionMessage(chat: chat, id: message.id, text: text) @@ -505,35 +497,12 @@ struct ChatPanelInputArea: View { @State private var showingTemplates = false @State private var dropDownShowingType: ShowingType? = nil - @AppStorage(\.enableCurrentEditorContext) var enableCurrentEditorContext: Bool - @State private var isCurrentEditorContextEnabled: Bool = UserDefaults.shared.value( - for: \.enableCurrentEditorContext - ) - var body: some View { WithPerceptionTracking { VStack(spacing: 0) { - chatContextView - - if isFilePickerPresented { - FilePicker( - allFiles: $allFiles, - onSubmit: { file in - chat.send(.addSelectedFile(file)) - }, - onExit: { - isFilePickerPresented = false - focusedField.wrappedValue = .textField - } - ) - .onAppear() { - allFiles = ContextUtils.getFilesInActiveWorkspace() - } - } - ZStack(alignment: .topLeading) { if chat.typedMessage.isEmpty { - Text("Ask Copilot or type / for commands") + Text("Ask Copilot") .font(.system(size: 14)) .foregroundColor(Color(nsColor: .placeholderTextColor)) .padding(8) @@ -567,12 +536,44 @@ struct ChatPanelInputArea: View { .frame(maxWidth: .infinity) } .padding(.top, 4) + + attachedFilesView + + if isFilePickerPresented { + FilePicker( + allFiles: $allFiles, + onSubmit: { file in + chat.send(.addSelectedFile(file)) + }, + onExit: { + isFilePickerPresented = false + focusedField.wrappedValue = .textField + } + ) + .transition(.move(edge: .bottom)) + .onAppear() { + allFiles = ContextUtils.getFilesInActiveWorkspace() + } + } HStack(spacing: 0) { - ModelPicker() + Button(action: { + withAnimation { + isFilePickerPresented.toggle() + if !isFilePickerPresented { + focusedField.wrappedValue = .textField + } + } + }) { + Image(systemName: "paperclip") + .padding(4) + } + .buttonStyle(HoverButtonStyle(padding: 0)) + .help("Attach Context") Spacer() + ModelPicker() Button(action: { submitChatMessage() }) { @@ -652,150 +653,50 @@ struct ChatPanelInputArea: View { } } - enum ChatContextButtonType { case mcpConfig, contextAttach} - - private var chatContextView: some View { - let buttonItems: [ChatContextButtonType] = chat.isAgentMode ? [.mcpConfig, .contextAttach] : [.contextAttach] - let currentEditorItem: [FileReference] = [chat.state.currentEditor].compactMap { - $0 - } - let selectedFileItems = chat.state.selectedFiles - let chatContextItems: [Any] = buttonItems.map { - $0 as ChatContextButtonType - } + currentEditorItem + selectedFileItems - return FlowLayout(mode: .scrollable, items: chatContextItems, itemSpacing: 4) { item in - if let buttonType = item as? ChatContextButtonType { - if buttonType == .mcpConfig { - // MCP Settings button - Button(action: { - try? launchHostAppMCPSettings() - }) { - Image(systemName: "wrench.and.screwdriver") - .resizable() - .scaledToFit() - .frame(width: 16, height: 16) - .foregroundColor(.primary.opacity(0.85)) - .padding(4) - } - .buttonStyle(HoverButtonStyle(padding: 0)) - .help("Configure your MCP server") - .cornerRadius(6) - .overlay( - RoundedRectangle(cornerRadius: r) - .stroke(Color(nsColor: .separatorColor), lineWidth: 1) - ) - } else if buttonType == .contextAttach { - // File picker button - Button(action: { - withAnimation { - isFilePickerPresented.toggle() - if !isFilePickerPresented { - focusedField.wrappedValue = .textField - } - } - }) { - HStack(spacing: 4) { - Image(systemName: "paperclip") - .resizable() - .scaledToFit() - .frame(width: 16, height: 16) - .foregroundColor(.primary.opacity(0.85)) - Text("Add Context...") - .foregroundColor(.primary.opacity(0.85)) - .lineLimit(1) - } - .padding(4) - } - .buttonStyle(HoverButtonStyle(padding: 0)) - .help("Add Context") - .cornerRadius(6) - .overlay( - RoundedRectangle(cornerRadius: r) - .stroke(Color(nsColor: .separatorColor), lineWidth: 1) - ) - } - } else if let select = item as? FileReference { - HStack(spacing: 0) { + private var attachedFilesView: some View { + FlowLayout(mode: .scrollable, items: [chat.state.currentEditor] + chat.state.selectedFiles, itemSpacing: 4) { file in + if let select = file { + HStack(spacing: 4) { drawFileIcon(select.url) .resizable() .scaledToFit() .frame(width: 16, height: 16) - .foregroundColor(.primary.opacity(0.85)) - .padding(4) + .foregroundColor(.secondary) Text(select.url.lastPathComponent) .lineLimit(1) .truncationMode(.middle) - .foregroundColor( - select.isCurrentEditor && !isCurrentEditorContextEnabled - ? .secondary - : .primary.opacity(0.85) - ) - .font(select.isCurrentEditor && !isCurrentEditorContextEnabled - ? .body.italic() - : .body - ) .help(select.getPathRelativeToHome()) - - if select.isCurrentEditor { - Text("Current file") - .foregroundStyle(.secondary) - .font(select.isCurrentEditor && !isCurrentEditorContextEnabled - ? .callout.italic() - : .callout - ) - .padding(.leading, 4) - } Button(action: { if select.isCurrentEditor { - enableCurrentEditorContext.toggle() - isCurrentEditorContextEnabled = enableCurrentEditorContext + chat.send(.resetCurrentEditor) } else { chat.send(.removeSelectedFile(select)) } }) { - if select.isCurrentEditor { - if isCurrentEditorContextEnabled { - Image("Eye") - .resizable() - .scaledToFit() - .frame(width: 16, height: 16) - .foregroundColor(.secondary) - .help("Disable current file context") - } else { - Image("EyeClosed") - .resizable() - .scaledToFit() - .frame(width: 16, height: 16) - .foregroundColor(.secondary) - .help("Enable current file context") - } - } else { - Image(systemName: "xmark") - .resizable() - .frame(width: 8, height: 8) - .foregroundColor(.secondary) - .padding(4) - } + Image(systemName: "xmark") + .resizable() + .frame(width: 8, height: 8) + .foregroundColor(.secondary) } .buttonStyle(HoverButtonStyle()) + .help("Remove from Context") } + .padding(4) .cornerRadius(6) + .shadow(radius: 2) +// .background( +// RoundedRectangle(cornerRadius: r) +// .fill(.ultraThickMaterial) +// ) .overlay( RoundedRectangle(cornerRadius: r) - .stroke( - Color(nsColor: .separatorColor), - style: .init( - lineWidth: 1, - dash: select.isCurrentEditor && !isCurrentEditorContextEnabled ? [4, 2] : [] - ) - ) + .stroke(Color(nsColor: .separatorColor), lineWidth: 1) ) } } .padding(.horizontal, 8) - .padding(.top, 8) } func chatTemplateCompletion(text: String) async -> [ChatTemplate] { @@ -888,6 +789,7 @@ struct ChatPanelInputArea: View { } } } + // MARK: - Previews struct ChatPanel_Preview: PreviewProvider { diff --git a/Core/Sources/ConversationTab/Controller/DiffViewWindowController.swift b/Core/Sources/ConversationTab/Controller/DiffViewWindowController.swift deleted file mode 100644 index c9aa4360..00000000 --- a/Core/Sources/ConversationTab/Controller/DiffViewWindowController.swift +++ /dev/null @@ -1,141 +0,0 @@ -import SwiftUI -import ChatService -import ComposableArchitecture -import WebKit - -enum Style { - /// default diff view frame. Same as the `ChatPanel` - static let diffViewHeight: Double = 560 - static let diffViewWidth: Double = 504 -} - -class DiffViewWindowController: NSObject, NSWindowDelegate { - enum DiffViewerState { - case shown, closed - } - - private var diffWindow: NSWindow? - private var hostingView: NSHostingView? - private let chat: StoreOf - public private(set) var currentFileEdit: FileEdit? = nil - public private(set) var diffViewerState: DiffViewerState = .closed - - public init(chat: StoreOf) { - self.chat = chat - } - - @MainActor - func showDiffWindow(fileEdit: FileEdit) { - currentFileEdit = fileEdit - // Create diff view - let newDiffView = DiffView(chat: chat, fileEdit: fileEdit) - - if let window = diffWindow, let _ = hostingView { - window.title = "Diff of \(fileEdit.fileURL.lastPathComponent)" - - let newHostingView = NSHostingView(rootView: newDiffView) - // Ensure the hosting view fills the window - newHostingView.translatesAutoresizingMaskIntoConstraints = false - - self.hostingView = newHostingView - window.contentView = newHostingView - - // Set constraints to fill the window - if let contentView = window.contentView { - newHostingView.frame = contentView.bounds - newHostingView.autoresizingMask = [.width, .height] - } - - window.makeKeyAndOrderFront(nil) - } else { - let newHostingView = NSHostingView(rootView: newDiffView) - newHostingView.translatesAutoresizingMaskIntoConstraints = false - self.hostingView = newHostingView - - let window = NSWindow( - contentRect: getDiffViewFrame(), - styleMask: [.titled, .closable, .miniaturizable, .resizable], - backing: .buffered, - defer: false - ) - - window.title = "Diff of \(fileEdit.fileURL.lastPathComponent)" - window.contentView = newHostingView - - // Set constraints to fill the window - if let contentView = window.contentView { - newHostingView.frame = contentView.bounds - newHostingView.autoresizingMask = [.width, .height] - } - - window.center() - window.delegate = self - window.isReleasedWhenClosed = false - - self.diffWindow = window - } - - NSApp.activate(ignoringOtherApps: true) - diffWindow?.makeKeyAndOrderFront(nil) - - diffViewerState = .shown - } - - func windowWillClose(_ notification: Notification) { - if let window = notification.object as? NSWindow, window == diffWindow { - DispatchQueue.main.async { - self.diffWindow?.orderOut(nil) - } - } - } - - @MainActor - func hideWindow() { - guard diffViewerState != .closed else { return } - diffWindow?.orderOut(nil) - diffViewerState = .closed - } - - func getDiffViewFrame() -> NSRect { - guard let mainScreen = NSScreen.screens.first(where: { $0.frame.origin == .zero }) - else { - /// default value - return .init(x: 0, y:0, width: Style.diffViewWidth, height: Style.diffViewHeight) - } - - let visibleScreenFrame = mainScreen.visibleFrame - // avoid too wide - let width = min(Style.diffViewWidth, visibleScreenFrame.width * 0.3) - let height = visibleScreenFrame.height - - return CGRect(x: 0, y: 0, width: width, height: height) - } - - func windowDidResize(_ notification: Notification) { - if let window = notification.object as? NSWindow, window == diffWindow { - if let hostingView = self.hostingView, - let webView = findWebView(in: hostingView) { - let script = """ - if (window.DiffViewer && window.DiffViewer.handleResize) { - window.DiffViewer.handleResize(); - } - """ - webView.evaluateJavaScript(script) - } - } - } - - private func findWebView(in view: NSView) -> WKWebView? { - if let webView = view as? WKWebView { - return webView - } - - for subview in view.subviews { - if let webView = findWebView(in: subview) { - return webView - } - } - - return nil - } -} diff --git a/Core/Sources/ConversationTab/ConversationTab.swift b/Core/Sources/ConversationTab/ConversationTab.swift index 5d6d5014..a3467d4b 100644 --- a/Core/Sources/ConversationTab/ConversationTab.swift +++ b/Core/Sources/ConversationTab/ConversationTab.swift @@ -159,8 +159,6 @@ public class ConversationTab: ChatTab { public func start() { observer = .init() cancellable = [] - - chat.send(.setDiffViewerController(chat: chat)) // chatTabStore.send(.updateTitle("Chat")) diff --git a/Core/Sources/ConversationTab/DiffViews/DiffView.swift b/Core/Sources/ConversationTab/DiffViews/DiffView.swift deleted file mode 100644 index c857528e..00000000 --- a/Core/Sources/ConversationTab/DiffViews/DiffView.swift +++ /dev/null @@ -1,96 +0,0 @@ -import SwiftUI -import WebKit -import ComposableArchitecture -import Logger -import ConversationServiceProvider -import ChatService -import ChatTab - -extension FileEdit { - var originalContentByStatus: String { - return status == .kept ? modifiedContent : originalContent - } - - var modifiedContentByStatus: String { - return status == .undone ? originalContent : modifiedContent - } -} - -struct DiffView: View { - @Perception.Bindable var chat: StoreOf - @State public var fileEdit: FileEdit - - var body: some View { - WithPerceptionTracking { - DiffWebView( - chat: chat, - fileEdit: fileEdit - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .edgesIgnoringSafeArea(.all) - } - } -} - -// preview -struct DiffView_Previews: PreviewProvider { - static var oldText = """ - import Foundation - - func calculateTotal(items: [Double]) -> Double { - var sum = 0.0 - for item in items { - sum += item - } - return sum - } - - func main() { - let prices = [10.5, 20.0, 15.75] - let total = calculateTotal(items: prices) - print("Total: \\(total)") - } - - main() - """ - - static var newText = """ - import Foundation - - func calculateTotal(items: [Double], applyDiscount: Bool = false) -> Double { - var sum = 0.0 - for item in items { - sum += item - } - - // Apply 10% discount if requested - if applyDiscount { - sum *= 0.9 - } - - return sum - } - - func main() { - let prices = [10.5, 20.0, 15.75, 5.0] - let total = calculateTotal(items: prices) - let discountedTotal = calculateTotal(items: prices, applyDiscount: true) - - print("Total: \\(total)") - print("With discount: \\(discountedTotal)") - } - - main() - """ - static let chatTabInfo = ChatTabInfo(id: "", workspacePath: "path", username: "name") - static var previews: some View { - DiffView( - chat: .init( - initialState: .init(history: ChatPanel_Preview.history, isReceivingMessage: true), - reducer: { Chat(service: ChatService.service(for: chatTabInfo)) } - ), - fileEdit: .init(fileURL: URL(fileURLWithPath: "file:///f1.swift"), originalContent: "test", modifiedContent: "abc", toolName: ToolName.insertEditIntoFile) - ) - .frame(width: 800, height: 600) - } -} diff --git a/Core/Sources/ConversationTab/DiffViews/DiffWebView.swift b/Core/Sources/ConversationTab/DiffViews/DiffWebView.swift deleted file mode 100644 index cc42af5d..00000000 --- a/Core/Sources/ConversationTab/DiffViews/DiffWebView.swift +++ /dev/null @@ -1,184 +0,0 @@ -import ComposableArchitecture -import ChatService -import SwiftUI -import WebKit -import Logger - -struct DiffWebView: NSViewRepresentable { - @Perception.Bindable var chat: StoreOf - var fileEdit: FileEdit - - init(chat: StoreOf, fileEdit: FileEdit) { - self.chat = chat - self.fileEdit = fileEdit - } - - func makeNSView(context: Context) -> WKWebView { - let configuration = WKWebViewConfiguration() - let userContentController = WKUserContentController() - - #if DEBUG - let scriptSource = """ - function captureLog(msg) { window.webkit.messageHandlers.logging.postMessage(Array.prototype.slice.call(arguments)); } - console.log = captureLog; - console.error = captureLog; - console.warn = captureLog; - console.info = captureLog; - """ - let script = WKUserScript(source: scriptSource, injectionTime: .atDocumentStart, forMainFrameOnly: true) - userContentController.addUserScript(script) - userContentController.add(context.coordinator, name: "logging") - #endif - - userContentController.add(context.coordinator, name: "swiftHandler") - configuration.userContentController = userContentController - - let webView = WKWebView(frame: .zero, configuration: configuration) - webView.navigationDelegate = context.coordinator - #if DEBUG - webView.configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") - #endif - - // Configure WebView - webView.wantsLayer = true - webView.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor - webView.layer?.borderWidth = 1 - - // Make the webview auto-resize with its container - webView.autoresizingMask = [.width, .height] - webView.translatesAutoresizingMaskIntoConstraints = true - - // Notify the webview of resize events explicitly - let resizeNotificationScript = WKUserScript( - source: """ - window.addEventListener('resize', function() { - if (window.DiffViewer && window.DiffViewer.handleResize) { - window.DiffViewer.handleResize(); - } - }); - """, - injectionTime: .atDocumentEnd, - forMainFrameOnly: true - ) - webView.configuration.userContentController.addUserScript(resizeNotificationScript) - - /// Load web asset resources - let bundleBaseURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/webViewDist/diffView") - let htmlFileURL = bundleBaseURL.appendingPathComponent("diffView.html") - webView.loadFileURL(htmlFileURL, allowingReadAccessTo: bundleBaseURL) - - return webView - } - - func updateNSView(_ webView: WKWebView, context: Context) { - if context.coordinator.shouldUpdate(fileEdit) { - // Update content via JavaScript API - let script = """ - if (typeof window.DiffViewer !== 'undefined') { - window.DiffViewer.update( - `\(escapeJSString(fileEdit.originalContentByStatus))`, - `\(escapeJSString(fileEdit.modifiedContentByStatus))`, - `\(escapeJSString(fileEdit.fileURL.absoluteString))`, - `\(fileEdit.status.rawValue)` - ); - } else { - console.error("DiffViewer is not defined in update"); - } - """ - webView.evaluateJavaScript(script) - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { - var parent: DiffWebView - private var fileEdit: FileEdit - - init(_ parent: DiffWebView) { - self.parent = parent - self.fileEdit = parent.fileEdit - } - - func shouldUpdate(_ fileEdit: FileEdit) -> Bool { - let shouldUpdate = self.fileEdit != fileEdit - - if shouldUpdate { - self.fileEdit = fileEdit - } - - return shouldUpdate - } - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - #if DEBUG - if message.name == "logging" { - if let logs = message.body as? [Any] { - let logString = logs.map { "\($0)" }.joined(separator: " ") - Logger.client.info("WebView console: \(logString)") - } - return - } - #endif - - guard message.name == "swiftHandler", - let body = message.body as? [String: Any], - let event = body["event"] as? String, - let data = body["data"] as? [String: String], - let filePath = data["filePath"], - let fileURL = URL(string: filePath) - else { return } - - switch event { - case "undoButtonClicked": - self.parent.chat.send(.undoEdits(fileURLs: [fileURL])) - case "keepButtonClicked": - self.parent.chat.send(.keepEdits(fileURLs: [fileURL])) - default: - break - } - } - - // Initialize content when the page has finished loading - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - let script = """ - if (typeof window.DiffViewer !== 'undefined') { - window.DiffViewer.init( - `\(escapeJSString(fileEdit.originalContentByStatus))`, - `\(escapeJSString(fileEdit.modifiedContentByStatus))`, - `\(escapeJSString(fileEdit.fileURL.absoluteString))`, - `\(fileEdit.status.rawValue)` - ); - } else { - console.error("DiffViewer is not defined on page load"); - } - """ - webView.evaluateJavaScript(script) { result, error in - if let error = error { - Logger.client.error("Error evaluating JavaScript: \(error)") - } - } - } - - // Handle navigation errors - func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - Logger.client.error("WebView navigation failed: \(error)") - } - - func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { - Logger.client.error("WebView provisional navigation failed: \(error)") - } - } -} - -func escapeJSString(_ string: String) -> String { - return string - .replacingOccurrences(of: "\\", with: "\\\\") - .replacingOccurrences(of: "`", with: "\\`") - .replacingOccurrences(of: "\n", with: "\\n") - .replacingOccurrences(of: "\r", with: "\\r") - .replacingOccurrences(of: "\"", with: "\\\"") - .replacingOccurrences(of: "$", with: "\\$") -} diff --git a/Core/Sources/ConversationTab/FilePicker.swift b/Core/Sources/ConversationTab/FilePicker.swift index f1a00966..71387530 100644 --- a/Core/Sources/ConversationTab/FilePicker.swift +++ b/Core/Sources/ConversationTab/FilePicker.swift @@ -123,6 +123,7 @@ public struct FilePicker: View { } .fixedSize(horizontal: false, vertical: true) .cornerRadius(6) + .shadow(radius: 2) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color(nsColor: .separatorColor), lineWidth: 1) @@ -157,7 +158,6 @@ struct FileRowView: View { HStack { drawFileIcon(doc.url) .resizable() - .scaledToFit() .frame(width: 16, height: 16) .foregroundColor(.secondary) .padding(.leading, 4) @@ -180,7 +180,6 @@ struct FileRowView: View { .onHover(perform: { hovering in isHovered = hovering }) - .transition(.move(edge: .bottom)) } } } diff --git a/Core/Sources/ConversationTab/ModelPicker.swift b/Core/Sources/ConversationTab/ModelPicker.swift new file mode 100644 index 00000000..c7664b26 --- /dev/null +++ b/Core/Sources/ConversationTab/ModelPicker.swift @@ -0,0 +1,177 @@ +import SwiftUI +import ChatService +import Persist +import ComposableArchitecture +import GitHubCopilotService +import Combine + +public let SELECTED_LLM_KEY = "selectedLLM" + +extension AppState { + func getSelectedModelFamily() -> String? { + if let savedModel = get(key: SELECTED_LLM_KEY), + let modelFamily = savedModel["modelFamily"]?.stringValue { + return modelFamily + } + return nil + } + + func getSelectedModelName() -> String? { + if let savedModel = get(key: SELECTED_LLM_KEY), + let modelName = savedModel["modelName"]?.stringValue { + return modelName + } + return nil + } + + func setSelectedModel(_ model: LLMModel) { + update(key: SELECTED_LLM_KEY, value: model) + } +} + +class CopilotModelManagerObservable: ObservableObject { + static let shared = CopilotModelManagerObservable() + + @Published var availableChatModels: [LLMModel] = [] + @Published var defaultModel: LLMModel = .init(modelName: "", modelFamily: "") + private var cancellables = Set() + + private init() { + // Initial load + availableChatModels = CopilotModelManager.getAvailableChatLLMs() + + // Setup notification to update when models change + NotificationCenter.default.publisher(for: .gitHubCopilotModelsDidChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.availableChatModels = CopilotModelManager.getAvailableChatLLMs() + self?.defaultModel = CopilotModelManager.getDefaultChatModel() + } + .store(in: &cancellables) + } +} + +extension CopilotModelManager { + static func getAvailableChatLLMs() -> [LLMModel] { + let LLMs = CopilotModelManager.getAvailableLLMs() + return LLMs.filter( + { $0.scopes.contains(.chatPanel) } + ).map { + LLMModel(modelName: $0.modelName, modelFamily: $0.modelFamily) + } + } + + static func getDefaultChatModel() -> LLMModel { + let defaultModel = CopilotModelManager.getDefaultChatLLM() + if let defaultModel = defaultModel { + return LLMModel(modelName: defaultModel.modelName, modelFamily: defaultModel.modelFamily) + } + // Fallback to a hardcoded default if no model has isChatDefault = true + return LLMModel(modelName: "GPT-4.1 (Preview)", modelFamily: "gpt-4.1") + } +} + +struct LLMModel: Codable, Hashable { + let modelName: String + let modelFamily: String +} + +struct ModelPicker: View { + @State private var selectedModel = "" + @State private var isHovered = false + @State private var isPressed = false + @ObservedObject private var modelManager = CopilotModelManagerObservable.shared + static var lastRefreshModelsTime: Date = .init(timeIntervalSince1970: 0) + + init() { + let initialModel = AppState.shared.getSelectedModelName() ?? CopilotModelManager.getDefaultChatModel().modelName + self._selectedModel = State(initialValue: initialModel) + } + + var models: [LLMModel] { + modelManager.availableChatModels + } + + var defaultModel: LLMModel { + modelManager.defaultModel + } + + func updateCurrentModel() { + selectedModel = AppState.shared.getSelectedModelName() ?? defaultModel.modelName + } + + var body: some View { + WithPerceptionTracking { + Group { + if !models.isEmpty && !selectedModel.isEmpty { + Menu(selectedModel) { + ForEach(models, id: \.self) { option in + Button { + selectedModel = option.modelName + AppState.shared.setSelectedModel(option) + } label: { + if selectedModel == option.modelName { + Text("✓ \(option.modelName)") + } else { + Text(" \(option.modelName)") + } + } + } + } + .menuStyle(BorderlessButtonMenuStyle()) + .frame(maxWidth: labelWidth()) + .padding(4) + .background( + RoundedRectangle(cornerRadius: 5) + .fill(isHovered ? Color.gray.opacity(0.1) : Color.clear) + ) + .onHover { hovering in + isHovered = hovering + } + } else { + EmptyView() + } + } + .onAppear() { + Task { + await refreshModels() + updateCurrentModel() + } + } + .onChange(of: defaultModel) { _ in + updateCurrentModel() + } + .onChange(of: models) { _ in + updateCurrentModel() + } + .help("Pick Model") + } + } + + func labelWidth() -> CGFloat { + let font = NSFont.systemFont(ofSize: NSFont.systemFontSize) + let attributes = [NSAttributedString.Key.font: font] + let width = selectedModel.size(withAttributes: attributes).width + return CGFloat(width + 20) + } + + @MainActor + func refreshModels() async { + let now = Date() + if now.timeIntervalSince(Self.lastRefreshModelsTime) < 60 { + return + } + + Self.lastRefreshModelsTime = now + let copilotModels = await SharedChatService.shared.copilotModels() + if !copilotModels.isEmpty { + CopilotModelManager.updateLLMs(copilotModels) + } + } +} + +struct ModelPicker_Previews: PreviewProvider { + static var previews: some View { + ModelPicker() + } +} diff --git a/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift b/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift deleted file mode 100644 index 5e61b4c0..00000000 --- a/Core/Sources/ConversationTab/ModelPicker/ChatModePicker.swift +++ /dev/null @@ -1,63 +0,0 @@ -import SwiftUI -import Persist -import ConversationServiceProvider - -public extension Notification.Name { - static let gitHubCopilotChatModeDidChange = Notification - .Name("com.github.CopilotForXcode.ChatModeDidChange") -} - -public struct ChatModePicker: View { - @Binding var chatMode: String - @Environment(\.colorScheme) var colorScheme - var onScopeChange: (PromptTemplateScope) -> Void - - public init(chatMode: Binding, onScopeChange: @escaping (PromptTemplateScope) -> Void = { _ in }) { - self._chatMode = chatMode - self.onScopeChange = onScopeChange - } - - public var body: some View { - HStack(spacing: -1) { - ModeButton( - title: "Ask", - isSelected: chatMode == "Ask", - activeBackground: colorScheme == .dark ? Color.white.opacity(0.25) : Color.white, - activeTextColor: Color.primary, - inactiveTextColor: Color.primary.opacity(0.5), - action: { - chatMode = "Ask" - AppState.shared.setSelectedChatMode("Ask") - onScopeChange(.chatPanel) - NotificationCenter.default.post( - name: .gitHubCopilotChatModeDidChange, - object: nil - ) - } - ) - - ModeButton( - title: "Agent", - isSelected: chatMode == "Agent", - activeBackground: Color.blue, - activeTextColor: Color.white, - inactiveTextColor: Color.primary.opacity(0.5), - action: { - chatMode = "Agent" - AppState.shared.setSelectedChatMode("Agent") - onScopeChange(.agentPanel) - NotificationCenter.default.post( - name: .gitHubCopilotChatModeDidChange, - object: nil - ) - } - ) - } - .padding(1) - .frame(height: 20, alignment: .topLeading) - .background(.primary.opacity(0.1)) - .cornerRadius(5) - .padding(4) - .help("Set Mode") - } -} diff --git a/Core/Sources/ConversationTab/ModelPicker/ModeButton.swift b/Core/Sources/ConversationTab/ModelPicker/ModeButton.swift deleted file mode 100644 index b204e04c..00000000 --- a/Core/Sources/ConversationTab/ModelPicker/ModeButton.swift +++ /dev/null @@ -1,30 +0,0 @@ -import SwiftUI - -public struct ModeButton: View { - let title: String - let isSelected: Bool - let activeBackground: Color - let activeTextColor: Color - let inactiveTextColor: Color - let action: () -> Void - - public var body: some View { - Button(action: action) { - Text(title) - .padding(.horizontal, 6) - .padding(.vertical, 0) - .frame(maxHeight: .infinity, alignment: .center) - .background(isSelected ? activeBackground : Color.clear) - .foregroundColor(isSelected ? activeTextColor : inactiveTextColor) - .cornerRadius(5) - .shadow(color: .black.opacity(0.05), radius: 0.375, x: 0, y: 1) - .shadow(color: .black.opacity(0.15), radius: 0.125, x: 0, y: 0.25) - .overlay( - RoundedRectangle(cornerRadius: 5) - .inset(by: -0.25) - .stroke(.black.opacity(0.02), lineWidth: 0.5) - ) - } - .buttonStyle(PlainButtonStyle()) - } -} diff --git a/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift b/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift deleted file mode 100644 index dc71303d..00000000 --- a/Core/Sources/ConversationTab/ModelPicker/ModelPicker.swift +++ /dev/null @@ -1,276 +0,0 @@ -import SwiftUI -import ChatService -import Persist -import ComposableArchitecture -import GitHubCopilotService -import Combine -import ConversationServiceProvider - -public let SELECTED_LLM_KEY = "selectedLLM" -public let SELECTED_CHATMODE_KEY = "selectedChatMode" - -extension AppState { - func getSelectedModelFamily() -> String? { - if let savedModel = get(key: SELECTED_LLM_KEY), - let modelFamily = savedModel["modelFamily"]?.stringValue { - return modelFamily - } - return nil - } - - func getSelectedModelName() -> String? { - if let savedModel = get(key: SELECTED_LLM_KEY), - let modelName = savedModel["modelName"]?.stringValue { - return modelName - } - return nil - } - - func setSelectedModel(_ model: LLMModel) { - update(key: SELECTED_LLM_KEY, value: model) - } - - func modelScope() -> PromptTemplateScope { - return isAgentModeEnabled() ? .agentPanel : .chatPanel - } - - func getSelectedChatMode() -> String { - if let savedMode = get(key: SELECTED_CHATMODE_KEY), - let modeName = savedMode.stringValue { - return convertChatMode(modeName) - } - return "Ask" - } - - func setSelectedChatMode(_ mode: String) { - update(key: SELECTED_CHATMODE_KEY, value: mode) - } - - func isAgentModeEnabled() -> Bool { - return getSelectedChatMode() == "Agent" - } - - private func convertChatMode(_ mode: String) -> String { - switch mode { - case "Agent": - return "Agent" - default: - return "Ask" - } - } -} - -class CopilotModelManagerObservable: ObservableObject { - static let shared = CopilotModelManagerObservable() - - @Published var availableChatModels: [LLMModel] = [] - @Published var availableAgentModels: [LLMModel] = [] - @Published var defaultChatModel: LLMModel? - @Published var defaultAgentModel: LLMModel? - private var cancellables = Set() - - private init() { - // Initial load - availableChatModels = CopilotModelManager.getAvailableChatLLMs(scope: .chatPanel) - availableAgentModels = CopilotModelManager.getAvailableChatLLMs(scope: .agentPanel) - defaultChatModel = CopilotModelManager.getDefaultChatModel(scope: .chatPanel) - defaultAgentModel = CopilotModelManager.getDefaultChatModel(scope: .agentPanel) - - // Setup notification to update when models change - NotificationCenter.default.publisher(for: .gitHubCopilotModelsDidChange) - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - self?.availableChatModels = CopilotModelManager.getAvailableChatLLMs(scope: .chatPanel) - self?.availableAgentModels = CopilotModelManager.getAvailableChatLLMs(scope: .agentPanel) - self?.defaultChatModel = CopilotModelManager.getDefaultChatModel(scope: .chatPanel) - self?.defaultAgentModel = CopilotModelManager.getDefaultChatModel(scope: .agentPanel) - } - .store(in: &cancellables) - } -} - -extension CopilotModelManager { - static func getAvailableChatLLMs(scope: PromptTemplateScope = .chatPanel) -> [LLMModel] { - let LLMs = CopilotModelManager.getAvailableLLMs() - return LLMs.filter( - { $0.scopes.contains(scope) } - ).map { - LLMModel(modelName: $0.modelName, modelFamily: $0.modelFamily) - } - } - - static func getDefaultChatModel(scope: PromptTemplateScope = .chatPanel) -> LLMModel? { - let LLMs = CopilotModelManager.getAvailableLLMs() - let LLMsInScope = LLMs.filter({ $0.scopes.contains(scope) }) - let defaultModel = LLMsInScope.first(where: { $0.isChatDefault }) - // If a default model is found, return it - if let defaultModel = defaultModel { - return LLMModel(modelName: defaultModel.modelName, modelFamily: defaultModel.modelFamily) - } - - // Fallback to gpt-4.1 if available - let gpt4_1 = LLMsInScope.first(where: { $0.modelFamily == "gpt-4.1" }) - if let gpt4_1 = gpt4_1 { - return LLMModel(modelName: gpt4_1.modelName, modelFamily: gpt4_1.modelFamily) - } - - // If no default model is found, fallback to the first available model - if let firstModel = LLMsInScope.first { - return LLMModel(modelName: firstModel.modelName, modelFamily: firstModel.modelFamily) - } - - return nil - } -} - -struct LLMModel: Codable, Hashable { - let modelName: String - let modelFamily: String -} - -struct ModelPicker: View { - @State private var selectedModel = "" - @State private var isHovered = false - @State private var isPressed = false - @ObservedObject private var modelManager = CopilotModelManagerObservable.shared - static var lastRefreshModelsTime: Date = .init(timeIntervalSince1970: 0) - - @State private var chatMode = "Ask" - @State private var isAgentPickerHovered = false - - init() { - let initialModel = AppState.shared.getSelectedModelName() ?? CopilotModelManager.getDefaultChatModel()?.modelName ?? "" - self._selectedModel = State(initialValue: initialModel) - updateAgentPicker() - } - - var models: [LLMModel] { - AppState.shared.isAgentModeEnabled() ? modelManager.availableAgentModels : modelManager.availableChatModels - } - - var defaultModel: LLMModel? { - AppState.shared.isAgentModeEnabled() ? modelManager.defaultAgentModel : modelManager.defaultChatModel - } - - func updateCurrentModel() { - selectedModel = AppState.shared.getSelectedModelName() ?? defaultModel?.modelName ?? "" - } - - func updateAgentPicker() { - self.chatMode = AppState.shared.getSelectedChatMode() - } - - func switchModelsForScope(_ scope: PromptTemplateScope) { - let newModeModels = CopilotModelManager.getAvailableChatLLMs(scope: scope) - - if let currentModel = AppState.shared.getSelectedModelName() { - if !newModeModels.isEmpty && !newModeModels.contains(where: { $0.modelName == currentModel }) { - let defaultModel = CopilotModelManager.getDefaultChatModel(scope: scope) - if let defaultModel = defaultModel { - selectedModel = defaultModel.modelName - AppState.shared.setSelectedModel(defaultModel) - } else { - selectedModel = newModeModels[0].modelName - AppState.shared.setSelectedModel(newModeModels[0]) - } - } - } - - // Force refresh models - self.updateCurrentModel() - } - - var body: some View { - WithPerceptionTracking { - HStack(spacing: 0) { - // Custom segmented control with color change - ChatModePicker(chatMode: $chatMode, onScopeChange: switchModelsForScope) - .onAppear() { - updateAgentPicker() - } - - Group{ - // Model Picker - if !models.isEmpty && !selectedModel.isEmpty { - - Menu(selectedModel) { - ForEach(models, id: \.self) { option in - Button { - selectedModel = option.modelName - AppState.shared.setSelectedModel(option) - } label: { - if selectedModel == option.modelName { - Text("✓ \(option.modelName)") - } else { - Text(" \(option.modelName)") - } - } - } - } - .menuStyle(BorderlessButtonMenuStyle()) - .frame(maxWidth: labelWidth()) - .padding(4) - .background( - RoundedRectangle(cornerRadius: 5) - .fill(isHovered ? Color.gray.opacity(0.1) : Color.clear) - ) - .onHover { hovering in - isHovered = hovering - } - } else { - EmptyView() - } - } - } - .onAppear() { - updateCurrentModel() - Task { - await refreshModels() - } - } - .onChange(of: defaultModel) { _ in - updateCurrentModel() - } - .onChange(of: models) { _ in - updateCurrentModel() - } - .onChange(of: chatMode) { _ in - updateCurrentModel() - } - } - } - - func labelWidth() -> CGFloat { - let font = NSFont.systemFont(ofSize: NSFont.systemFontSize) - let attributes = [NSAttributedString.Key.font: font] - let width = selectedModel.size(withAttributes: attributes).width - return CGFloat(width + 20) - } - - func agentPickerLabelWidth() -> CGFloat { - let font = NSFont.systemFont(ofSize: NSFont.systemFontSize) - let attributes = [NSAttributedString.Key.font: font] - let width = chatMode.size(withAttributes: attributes).width - return CGFloat(width + 20) - } - - @MainActor - func refreshModels() async { - let now = Date() - if now.timeIntervalSince(Self.lastRefreshModelsTime) < 60 { - return - } - - Self.lastRefreshModelsTime = now - let copilotModels = await SharedChatService.shared.copilotModels() - if !copilotModels.isEmpty { - CopilotModelManager.updateLLMs(copilotModels) - } - } -} - -struct ModelPicker_Previews: PreviewProvider { - static var previews: some View { - ModelPicker() - } -} diff --git a/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift b/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift deleted file mode 100644 index 8de0cccc..00000000 --- a/Core/Sources/ConversationTab/TerminalViews/RunInTerminalToolView.swift +++ /dev/null @@ -1,148 +0,0 @@ -import SwiftUI -import XcodeInspector -import ConversationServiceProvider -import ComposableArchitecture -import Terminal - -struct RunInTerminalToolView: View { - let tool: AgentToolCall - let command: String? - let explanation: String? - let isBackground: Bool? - let chat: StoreOf - private var title: String = "Run command in terminal" - - @AppStorage(\.chatFontSize) var chatFontSize - - init(tool: AgentToolCall, chat: StoreOf) { - self.tool = tool - self.chat = chat - if let input = tool.invokeParams?.input as? [String: AnyCodable] { - self.command = input["command"]?.value as? String - self.explanation = input["explanation"]?.value as? String - self.isBackground = input["isBackground"]?.value as? Bool - self.title = (isBackground != nil && isBackground!) ? "Run command in background terminal" : "Run command in terminal" - } else { - self.command = nil - self.explanation = nil - self.isBackground = nil - } - } - - var terminalSession: TerminalSession? { - return TerminalSessionManager.shared.getSession(for: tool.id) - } - - var statusIcon: some View { - Group { - switch tool.status { - case .running: - ProgressView() - .controlSize(.small) - .scaleEffect(0.7) - case .completed: - Image(systemName: "checkmark") - .foregroundColor(.green.opacity(0.5)) - case .error: - Image(systemName: "xmark.circle") - .foregroundColor(.red.opacity(0.5)) - case .cancelled: - Image(systemName: "slash.circle") - .foregroundColor(.gray.opacity(0.5)) - case .waitForConfirmation: - EmptyView() - } - } - } - - var body: some View { - WithPerceptionTracking { - if tool.status == .waitForConfirmation || terminalSession != nil { - VStack { - Text(self.title) - .font(.system(size: chatFontSize)) - .fontWeight(.semibold) - .foregroundStyle(.primary) - .background(Color.clear) - .frame(maxWidth: .infinity, alignment: .leading) - - toolView - } - .padding(8) - .cornerRadius(8) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(Color.gray.opacity(0.2), lineWidth: 1) - ) - } else { - toolView - } - } - } - - var toolView: some View { - WithPerceptionTracking { - VStack { - if command != nil { - HStack(spacing: 4) { - statusIcon - .frame(width: 16, height: 16) - - ThemedMarkdownText(text: command!, chat: chat) - .font(.system(.body, design: .monospaced)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(NSColor.textBackgroundColor)) - } - } else { - Text("Invalid parameter in the toolcall for runInTerminal") - } - - if let terminalSession = terminalSession { - XTermView( - terminalSession: terminalSession, - onTerminalInput: terminalSession.handleTerminalInput - ) - .frame(minHeight: 200, maxHeight: 400) - } else if tool.status == .waitForConfirmation { - ThemedMarkdownText(text: explanation ?? "", chat: chat) - .frame(maxWidth: .infinity, alignment: .leading) - - HStack { - Button("Continue") { - chat.send(.toolCallStarted(tool.id)) - Task { - let projectURL = await XcodeInspector.shared.safe.realtimeActiveProjectURL - let currentDirectory = projectURL?.path ?? "" - let session = TerminalSessionManager.shared.createSession(for: tool.id) - if isBackground == true { - session.executeCommand( - currentDirectory: currentDirectory, - command: command!) { result in - // do nothing - } - chat.send(.toolCallCompleted(tool.id, "Command is running in terminal with ID=\(tool.id)")) - } else { - session.executeCommand( - currentDirectory: currentDirectory, - command: command!) { result in - chat.send(.toolCallCompleted(tool.id, result.output)) - } - } - } - } - .buttonStyle(BorderedProminentButtonStyle()) - - Button("Cancel") { - chat.send(.toolCallCancelled(tool.id)) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.vertical, 8) - } - } - } - } - -} diff --git a/Core/Sources/ConversationTab/TerminalViews/XTermView.swift b/Core/Sources/ConversationTab/TerminalViews/XTermView.swift deleted file mode 100644 index 23e1fbd0..00000000 --- a/Core/Sources/ConversationTab/TerminalViews/XTermView.swift +++ /dev/null @@ -1,100 +0,0 @@ -import SwiftUI -import Logger -import WebKit -import Terminal - -struct XTermView: NSViewRepresentable { - @ObservedObject var terminalSession: TerminalSession - var onTerminalInput: (String) -> Void - - var terminalOutput: String { - terminalSession.terminalOutput - } - - func makeNSView(context: Context) -> WKWebView { - let webpagePrefs = WKWebpagePreferences() - webpagePrefs.allowsContentJavaScript = true - let preferences = WKWebViewConfiguration() - preferences.defaultWebpagePreferences = webpagePrefs - preferences.userContentController.add(context.coordinator, name: "terminalInput") - - let webView = WKWebView(frame: .zero, configuration: preferences) - webView.navigationDelegate = context.coordinator - #if DEBUG - webView.configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") - #endif - - // Load the terminal bundle resources - let terminalBundleBaseURL = Bundle.main.bundleURL.appendingPathComponent("Contents/Resources/webViewDist/terminal") - let htmlFileURL = terminalBundleBaseURL.appendingPathComponent("terminal.html") - webView.loadFileURL(htmlFileURL, allowingReadAccessTo: terminalBundleBaseURL) - return webView - } - - func updateNSView(_ webView: WKWebView, context: Context) { - // When terminalOutput changes, send the new data to the terminal - if context.coordinator.lastOutput != terminalOutput { - let newOutput = terminalOutput.suffix(from: - terminalOutput.index(terminalOutput.startIndex, - offsetBy: min(context.coordinator.lastOutput.count, terminalOutput.count))) - - if !newOutput.isEmpty { - context.coordinator.lastOutput = terminalOutput - if context.coordinator.isWebViewLoaded { - context.coordinator.writeToTerminal(text: String(newOutput), webView: webView) - } else { - context.coordinator.pendingOutput = (context.coordinator.pendingOutput ?? "") + String(newOutput) - } - } - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { - var parent: XTermView - var lastOutput: String = "" - var isWebViewLoaded = false - var pendingOutput: String? - - init(_ parent: XTermView) { - self.parent = parent - super.init() - } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - isWebViewLoaded = true - if let pending = pendingOutput { - writeToTerminal(text: pending, webView: webView) - pendingOutput = nil - } - } - - func writeToTerminal(text: String, webView: WKWebView) { - let escapedOutput = text - .replacingOccurrences(of: "\\", with: "\\\\") - .replacingOccurrences(of: "'", with: "\\'") - .replacingOccurrences(of: "\n", with: "\\r\\n") - .replacingOccurrences(of: "\r", with: "\\r") - - let jsCode = "writeToTerminal('\(escapedOutput)');" - DispatchQueue.main.async { - webView.evaluateJavaScript(jsCode) { _, error in - if let error = error { - Logger.client.info("XTerm: Error writing to terminal: \(error)") - } - } - } - } - - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - if message.name == "terminalInput", let input = message.body as? String { - DispatchQueue.main.async { - self.parent.onTerminalInput(input) - } - } - } - } -} diff --git a/Core/Sources/ConversationTab/Views/BotMessage.swift b/Core/Sources/ConversationTab/Views/BotMessage.swift index 071a6827..d9d27790 100644 --- a/Core/Sources/ConversationTab/Views/BotMessage.swift +++ b/Core/Sources/ConversationTab/Views/BotMessage.swift @@ -17,7 +17,6 @@ struct BotMessage: View { let errorMessage: String? let chat: StoreOf let steps: [ConversationProgressStep] - let editAgentRounds: [AgentRound] @Environment(\.colorScheme) var colorScheme @AppStorage(\.chatFontSize) var chatFontSize @@ -123,10 +122,6 @@ struct BotMessage: View { if steps.count > 0 { ProgressStep(steps: steps) } - - if editAgentRounds.count > 0 { - ProgressAgentRound(rounds: editAgentRounds, chat: chat) - } ThemedMarkdownText(text: text, chat: chat) @@ -187,7 +182,6 @@ struct ReferenceList: View { HStack(spacing: 8) { drawFileIcon(reference.url) .resizable() - .scaledToFit() .frame(width: 16, height: 16) Text(reference.fileName) .truncationMode(.middle) @@ -238,24 +232,6 @@ struct BotMessage_Previews: PreviewProvider { .init(id: "003", title: "failed step", description: "this is failed step", status: .failed, error: nil), .init(id: "004", title: "cancelled step", description: "this is cancelled step", status: .cancelled, error: nil) ] - - static let agentRounds: [AgentRound] = [ - .init(roundId: 1, reply: "this is agent step 1", toolCalls: [ - .init( - id: "toolcall_001", - name: "Tool Call 1", - progressMessage: "Read Tool Call 1", - status: .completed, - error: nil) - ]), - .init(roundId: 2, reply: "this is agent step 2", toolCalls: [ - .init( - id: "toolcall_002", - name: "Tool Call 2", - progressMessage: "Running Tool Call 2", - status: .running) - ]) - ] static var previews: some View { let chatTabInfo = ChatTabInfo(id: "id", workspacePath: "path", username: "name") @@ -275,8 +251,7 @@ struct BotMessage_Previews: PreviewProvider { followUp: ConversationFollowUp(message: "followup question", id: "id", type: "type"), errorMessage: "Sorry, an error occurred while generating a response.", chat: .init(initialState: .init(), reducer: { Chat(service: ChatService.service(for: chatTabInfo)) }), - steps: steps, - editAgentRounds: agentRounds + steps: steps ) .padding() .fixedSize(horizontal: true, vertical: true) diff --git a/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift b/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift deleted file mode 100644 index 13752066..00000000 --- a/Core/Sources/ConversationTab/Views/ConversationAgentProgressView.swift +++ /dev/null @@ -1,143 +0,0 @@ - -import SwiftUI -import ConversationServiceProvider -import ComposableArchitecture -import Combine -import ChatTab -import ChatService - -struct ProgressAgentRound: View { - let rounds: [AgentRound] - let chat: StoreOf - - var body: some View { - WithPerceptionTracking { - VStack(alignment: .leading, spacing: 4) { - ForEach(rounds, id: \.roundId) { round in - VStack(alignment: .leading, spacing: 4) { - ThemedMarkdownText(text: round.reply, chat: chat) - ProgressToolCalls(tools: round.toolCalls ?? [], chat: chat) - .padding(.vertical, 8) - } - } - } - .foregroundStyle(.secondary) - } - } -} - -struct ProgressToolCalls: View { - let tools: [AgentToolCall] - let chat: StoreOf - - var body: some View { - WithPerceptionTracking { - VStack(alignment: .leading, spacing: 4) { - ForEach(tools) { tool in - if tool.name == ToolName.runInTerminal.rawValue && tool.invokeParams != nil { - RunInTerminalToolView(tool: tool, chat: chat) - } else { - ToolStatusItemView(tool: tool) - } - } - } - } - } -} - -struct ToolStatusItemView: View { - - let tool: AgentToolCall - - @AppStorage(\.chatFontSize) var chatFontSize - - var statusIcon: some View { - Group { - switch tool.status { - case .running: - ProgressView() - .controlSize(.small) - .scaleEffect(0.7) - case .completed: - Image(systemName: "checkmark") - .foregroundColor(.green.opacity(0.5)) - case .error: - Image(systemName: "xmark.circle") - .foregroundColor(.red.opacity(0.5)) - case .cancelled: - Image(systemName: "slash.circle") - .foregroundColor(.gray.opacity(0.5)) - case .waitForConfirmation: - EmptyView() - } - } - } - - var progressTitleText: some View { - let message: String = { - var msg = tool.progressMessage ?? tool.name - if tool.name == ToolName.createFile.rawValue { - if let input = tool.invokeParams?.input, let filePath = input["filePath"]?.value as? String { - let fileURL = URL(fileURLWithPath: filePath) - msg += ": [\(fileURL.lastPathComponent)](\(fileURL.absoluteString))" - } - } - return msg - }() - - return Group { - if let attributedString = try? AttributedString(markdown: message) { - Text(attributedString) - .environment(\.openURL, OpenURLAction { url in - if url.scheme == "file" || url.isFileURL { - NSWorkspace.shared.open(url) - return .handled - } else { - return .systemAction - } - }) - } else { - Text(tool.progressMessage ?? tool.name) - } - } - } - - var body: some View { - WithPerceptionTracking { - HStack(spacing: 4) { - statusIcon - .frame(width: 16, height: 16) - - progressTitleText - .font(.system(size: chatFontSize)) - .lineLimit(1) - - Spacer() - } - } - } -} - -struct ProgressAgentRound_Preview: PreviewProvider { - static let agentRounds: [AgentRound] = [ - .init(roundId: 1, reply: "this is agent step", toolCalls: [ - .init( - id: "toolcall_001", - name: "Tool Call 1", - progressMessage: "Read Tool Call 1", - status: .completed, - error: nil), - .init( - id: "toolcall_002", - name: "Tool Call 2", - progressMessage: "Running Tool Call 2", - status: .running) - ]) - ] - - static var previews: some View { - let chatTabInfo = ChatTabInfo(id: "id", workspacePath: "path", username: "name") - ProgressAgentRound(rounds: agentRounds, chat: .init(initialState: .init(), reducer: { Chat(service: ChatService.service(for: chatTabInfo)) })) - .frame(width: 300, height: 300) - } -} diff --git a/Core/Sources/ConversationTab/Views/WorkingSetView.swift b/Core/Sources/ConversationTab/Views/WorkingSetView.swift deleted file mode 100644 index 2050c8eb..00000000 --- a/Core/Sources/ConversationTab/Views/WorkingSetView.swift +++ /dev/null @@ -1,192 +0,0 @@ -import SwiftUI -import ChatService -import Perception -import ComposableArchitecture -import GitHubCopilotService -import JSONRPC -import SharedUIComponents -import OrderedCollections -import ConversationServiceProvider - -struct WorkingSetView: View { - let chat: StoreOf - - private let r: Double = 8 - - var body: some View { - WithPerceptionTracking { - VStack(alignment: .leading, spacing: 4) { - - WorkingSetHeader(chat: chat) - - ForEach(chat.fileEditMap.elements, id: \.key.path) { element in - FileEditView(chat: chat, fileEdit: element.value) - } - } - .padding(.vertical, 8) - .padding(.horizontal, 16) - .frame(maxWidth: .infinity) - .background( - RoundedCorners(tl: r, tr: r, bl: 0, br: 0) - .fill(.ultraThickMaterial) - ) - .overlay( - RoundedCorners(tl: r, tr: r, bl: 0, br: 0) - .stroke(Color(nsColor: .separatorColor), lineWidth: 1) - ) - } - } -} - -struct WorkingSetHeader: View { - let chat: StoreOf - - func getTitle() -> String { - return chat.fileEditMap.count > 1 ? "\(chat.fileEditMap.count) files changed" : "1 file changed" - } - - var body: some View { - WithPerceptionTracking { - HStack { - Text(getTitle()) - .foregroundColor(.secondary) - .font(.system(size: 12)) - - Spacer() - - if chat.fileEditMap.contains(where: {_, fileEdit in - return fileEdit.status == .none - }) { - /// Undo all edits - Button("Undo") { - chat.send(.undoEdits(fileURLs: chat.fileEditMap.values.map { $0.fileURL })) - } - .help("Undo All Edits") - - Button("Keep") { - chat.send(.keepEdits(fileURLs: chat.fileEditMap.values.map { $0.fileURL })) - } - .buttonStyle(.borderedProminent) - .help("Keep All Edits") - } else { - Button("Done") { - chat.send(.resetEdits) - } - .help("Done") - } - } - } - } -} - -struct FileEditView: View { - let chat: StoreOf - let fileEdit: FileEdit - - @State private var isHovering = false - - var body: some View { - ZStack(alignment: .trailing) { - HStack(spacing: 4) { - Button(action: { - chat.send(.openDiffViewWindow(fileURL: fileEdit.fileURL)) - }) { - drawFileIcon(fileEdit.fileURL) - .resizable() - .scaledToFit() - .frame(width: 16, height: 16) - .foregroundColor(.secondary) - - Text(fileEdit.fileURL.lastPathComponent) - .bold() - .font(.system(size: 14)) - } - .buttonStyle(HoverButtonStyle()) - - Spacer() - } - - if isHovering { - HStack(spacing: 4) { - - Spacer() - - if fileEdit.status == .none { - Button { - chat.send(.undoEdits(fileURLs: [fileEdit.fileURL])) - } label: { - Image(systemName: "arrow.uturn.backward") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 14, height: 14) - .foregroundColor(.secondary) - } - .buttonStyle(HoverButtonStyle(padding: 0)) - .help("Undo") - - Button { - chat.send(.keepEdits(fileURLs: [fileEdit.fileURL])) - } label: { - Image(systemName: "checkmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 14, height: 14) - .foregroundColor(.secondary) - } - .buttonStyle(HoverButtonStyle(padding: 0)) - .help("Keep") - - Button { - chat.send(.openDiffViewWindow(fileURL: fileEdit.fileURL)) - } label: { - Image(systemName: "pencil.and.list.clipboard") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 14, height: 14) - .foregroundColor(.secondary) - } - .buttonStyle(HoverButtonStyle(padding: 0)) - .help("Open changes in Diff Editor") - } - - Button { - /// User directly close this edit. undo and remove it - chat.send(.discardFileEdits(fileURLs: [fileEdit.fileURL])) - } label: { - Image(systemName: "xmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 14, height: 14) - .foregroundColor(.secondary) - } - .buttonStyle(HoverButtonStyle(padding: 0)) - .help("Remove file") - } - } - } - .onHover { hovering in - isHovering = hovering - } - } -} - - -struct WorkingSetView_Previews: PreviewProvider { - static let fileEditMap: OrderedDictionary = [ - URL(fileURLWithPath: "file:///f1.swift"): FileEdit(fileURL: URL(fileURLWithPath: "file:///f1.swift"), originalContent: "single line", modifiedContent: "single line 1", toolName: ToolName.insertEditIntoFile), - URL(fileURLWithPath: "file:///f2.swift"): FileEdit(fileURL: URL(fileURLWithPath: "file:///f2.swift"), originalContent: "multi \n line \n end", modifiedContent: "another \n mut \n li \n", status: .kept, toolName: ToolName.insertEditIntoFile) - ] - - static var previews: some View { - WorkingSetView( - chat: .init( - initialState: .init( - history: ChatPanel_Preview.history, - isReceivingMessage: true, - fileEditMap: fileEditMap - ), - reducer: { Chat(service: ChatService.service(for: ChatPanel_Preview.chatTabInfo)) } - ) - ) - } -} diff --git a/Core/Sources/HostApp/HostApp.swift b/Core/Sources/HostApp/HostApp.swift index 93c8725a..a48b6a59 100644 --- a/Core/Sources/HostApp/HostApp.swift +++ b/Core/Sources/HostApp/HostApp.swift @@ -12,13 +12,11 @@ public struct HostApp { @ObservableState public struct State: Equatable { var general = General.State() - public var activeTabIndex: Int = 0 } public enum Action: Equatable { case appear case general(General.Action) - case setActiveTab(Int) } @Dependency(\.toast) var toast @@ -32,17 +30,13 @@ public struct HostApp { General() } - Reduce { state, action in + Reduce { _, action in switch action { case .appear: return .none case .general: return .none - - case .setActiveTab(let index): - state.activeTabIndex = index - return .none } } } @@ -72,3 +66,5 @@ extension DependencyValues { set { self[UserDefaultsDependencyKey.self] = newValue } } } + + diff --git a/Core/Sources/HostApp/MCPConfigView.swift b/Core/Sources/HostApp/MCPConfigView.swift deleted file mode 100644 index d50301af..00000000 --- a/Core/Sources/HostApp/MCPConfigView.swift +++ /dev/null @@ -1,278 +0,0 @@ -import Client -import Foundation -import Logger -import SharedUIComponents -import SwiftUI -import Toast - -extension ButtonStyle where Self == BorderedProminentWhiteButtonStyle { - static var borderedProminentWhite: BorderedProminentWhiteButtonStyle { - BorderedProminentWhiteButtonStyle() - } -} - -struct BorderedProminentWhiteButtonStyle: ButtonStyle { - @Environment(\.colorScheme) var colorScheme - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(.horizontal, 4) - .padding(.vertical, 2) - .foregroundColor(colorScheme == .dark ? .white : .primary) - .background( - colorScheme == .dark ? Color(red: 0.43, green: 0.43, blue: 0.44) : .white - ) - .cornerRadius(5) - .overlay( - RoundedRectangle(cornerRadius: 5).stroke(.clear, lineWidth: 1) - ) - .shadow(color: .black.opacity(0.05), radius: 0, x: 0, y: 0) - .shadow(color: .black.opacity(0.3), radius: 1.25, x: 0, y: 0.5) - } -} - -struct CardGroupBoxStyle: GroupBoxStyle { - func makeBody(configuration: Configuration) -> some View { - VStack(alignment: .leading, spacing: 11) { - configuration.label.foregroundColor(.primary) - configuration.content.foregroundColor(.primary) - } - .padding(8) - .frame(maxWidth: .infinity, alignment: .topLeading) - .background(Color("GroupBoxBackgroundColor")) - .cornerRadius(4) - .overlay( - RoundedRectangle(cornerRadius: 4) - .inset(by: 0.5) - .stroke(Color("GroupBoxStrokeColor"), lineWidth: 1) - ) - } -} - -struct MCPConfigView: View { - @State private var mcpConfig: String = "" - @Environment(\.toast) var toast - @State private var configFilePath: String = "" - @State private var isMonitoring: Bool = false - @State private var lastModificationDate: Date? = nil - @State private var fileMonitorTask: Task? = nil - @State private var copiedToClipboard: Bool = false - @Environment(\.colorScheme) var colorScheme - - var exampleConfig: String { - """ - { - "servers": { - "my-mcp-server": { - "type": "stdio", - "command": "my-command", - "args": [] - } - } - } - """ - } - - var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 8) { - GroupBox( - label: Text("Model Context Protocol (MCP) Configuration") - .fontWeight(.bold) - ) { - Text( - "MCP is an open standard that connects AI models to external tools. In Xcode, it enhances GitHub Copilot's agent mode by connecting to any MCP server and integrating its tools into your workflow. [Learn More](https://modelcontextprotocol.io/introduction)" - ) - }.groupBoxStyle(CardGroupBoxStyle()) - - Button { - openConfigFile() - } label: { - HStack(spacing: 4) { - Image(systemName: "plus") - Text("Edit Config") - } - } - .buttonStyle(.borderedProminentWhite) - .help("Configure your MCP server") - - GroupBox(label: Text("Example Configuration").fontWeight(.bold)) { - ZStack(alignment: .topTrailing) { - Text(exampleConfig) - .font(.system(.body, design: .monospaced)) - .padding(10) - .frame(maxWidth: .infinity, alignment: .leading) - .background( - Color(nsColor: .textBackgroundColor).opacity(0.5) - ) - .textSelection(.enabled) - .cornerRadius(8) - - CopyButton { - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(exampleConfig, forType: .string) - } - } - }.groupBoxStyle(CardGroupBoxStyle()) - } - .padding(20) - .onAppear { - setupConfigFilePath() - startMonitoringConfigFile() - } - .onDisappear { - stopMonitoringConfigFile() - } - } - } - - private func wrapBinding(_ b: Binding) -> Binding { - DebouncedBinding(b, handler: refreshConfiguration).binding - } - - private func setupConfigFilePath() { - let homeDirectory = FileManager.default.homeDirectoryForCurrentUser - configFilePath = homeDirectory.appendingPathComponent(".config/github-copilot/xcode/mcp.json").path - - // Create directory and file if they don't exist - let configDirectory = homeDirectory.appendingPathComponent(".config/github-copilot/xcode") - let fileManager = FileManager.default - - if !fileManager.fileExists(atPath: configDirectory.path) { - try? fileManager.createDirectory(at: configDirectory, withIntermediateDirectories: true) - } - - // If the file doesn't exist, create one with a proper structure - let configFileURL = URL(fileURLWithPath: configFilePath) - if !fileManager.fileExists(atPath: configFilePath) { - try? """ - { - "servers": { - - } - } - """.write(to: configFileURL, atomically: true, encoding: .utf8) - } - - // Read the current content from file and ensure it's valid JSON - mcpConfig = readAndValidateJSON(from: configFileURL) ?? "{}" - - // Get initial modification date - lastModificationDate = getFileModificationDate(url: configFileURL) - } - - /// Reads file content and validates it as JSON, returning only the "servers" object - private func readAndValidateJSON(from url: URL) -> String? { - guard let data = try? Data(contentsOf: url) else { - return nil - } - - // Try to parse as JSON to validate - do { - // First verify it's valid JSON - let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] - - // Extract the "servers" object - guard let servers = jsonObject?["servers"] as? [String: Any] else { - Logger.client.info("No 'servers' key found in MCP configuration") - toast("No 'servers' key found in MCP configuration", .error) - // Return empty object if no servers section - return "{}" - } - - // Convert the servers object back to JSON data - let serversData = try JSONSerialization.data( - withJSONObject: servers, options: [.prettyPrinted]) - - // Return as a string - return String(data: serversData, encoding: .utf8) - } catch { - // If parsing fails, return nil - Logger.client.info("Parsing MCP JSON error: \(error)") - toast("Invalid JSON in MCP configuration file", .error) - return nil - } - } - - private func getFileModificationDate(url: URL) -> Date? { - let attributes = try? FileManager.default.attributesOfItem(atPath: url.path) - return attributes?[.modificationDate] as? Date - } - - private func startMonitoringConfigFile() { - stopMonitoringConfigFile() // Stop existing monitoring if any - - isMonitoring = true - - fileMonitorTask = Task { - let configFileURL = URL(fileURLWithPath: configFilePath) - - // Check for file changes periodically - while isMonitoring { - try? await Task.sleep(nanoseconds: 3_000_000_000) // Check every 3 seconds - - let currentDate = getFileModificationDate(url: configFileURL) - - if let currentDate = currentDate, currentDate != lastModificationDate { - // File modification date has changed, update our record - lastModificationDate = currentDate - - // Read and validate the updated content - if let validJson = readAndValidateJSON(from: configFileURL) { - await MainActor.run { - mcpConfig = validJson - refreshConfiguration(validJson) - toast("MCP configuration file updated", .info) - } - } else { - // If JSON is invalid, show error - await MainActor.run { - toast("Invalid JSON in MCP configuration file", .error) - } - } - } - } - } - } - - private func stopMonitoringConfigFile() { - isMonitoring = false - fileMonitorTask?.cancel() - fileMonitorTask = nil - } - - private func openConfigFile() { - let url = URL(fileURLWithPath: configFilePath) - NSWorkspace.shared.open(url) - } - - func refreshConfiguration(_: Any) { - let fileURL = URL(fileURLWithPath: configFilePath) - if let jsonString = readAndValidateJSON(from: fileURL) { - UserDefaults.shared.set(jsonString, for: \.gitHubCopilotMCPConfig) - } - - NotificationCenter.default.post( - name: .gitHubCopilotShouldRefreshEditorInformation, - object: nil - ) - - Task { - let service = try getService() - do { - try await service.postNotification( - name: Notification.Name - .gitHubCopilotShouldRefreshEditorInformation.rawValue - ) - toast("MCP configuration updated", .info) - } catch { - toast(error.localizedDescription, .error) - } - } - } -} - -#Preview { - MCPConfigView() - .frame(width: 800, height: 600) -} diff --git a/Core/Sources/HostApp/TabContainer.swift b/Core/Sources/HostApp/TabContainer.swift index 3a4bb494..02b4459b 100644 --- a/Core/Sources/HostApp/TabContainer.swift +++ b/Core/Sources/HostApp/TabContainer.swift @@ -13,24 +13,16 @@ public struct TabContainer: View { let store: StoreOf @ObservedObject var toastController: ToastController @State private var tabBarItems = [TabBarItem]() - @Binding var tag: Int + @State var tag: Int = 0 public init() { toastController = ToastControllerDependencyKey.liveValue store = hostAppStore - _tag = Binding( - get: { hostAppStore.state.activeTabIndex }, - set: { hostAppStore.send(.setActiveTab($0)) } - ) } init(store: StoreOf, toastController: ToastController) { self.store = store self.toastController = toastController - _tag = Binding( - get: { store.state.activeTabIndex }, - set: { store.send(.setActiveTab($0)) } - ) } public var body: some View { @@ -47,15 +39,10 @@ public struct TabContainer: View { isSystemImage: false ) AdvancedSettings().tabBarItem( - tag: 1, + tag: 2, title: "Advanced", image: "gearshape.2.fill" ) - MCPConfigView().tabBarItem( - tag: 2, - title: "MCP", - image: "wrench.and.screwdriver.fill" - ) } .environment(\.tabBarTabTag, tag) .frame(minHeight: 400) diff --git a/Core/Sources/PersistMiddleware/Extensions/ChatMessage+Storage.swift b/Core/Sources/PersistMiddleware/Extensions/ChatMessage+Storage.swift index 263c9ee9..acb4b20d 100644 --- a/Core/Sources/PersistMiddleware/Extensions/ChatMessage+Storage.swift +++ b/Core/Sources/PersistMiddleware/Extensions/ChatMessage+Storage.swift @@ -14,7 +14,6 @@ extension ChatMessage { var suggestedTitle: String? var errorMessage: String? var steps: [ConversationProgressStep] - var editAgentRounds: [AgentRound] // Custom decoder to provide default value for steps init(from decoder: Decoder) throws { @@ -26,11 +25,10 @@ extension ChatMessage { suggestedTitle = try container.decodeIfPresent(String.self, forKey: .suggestedTitle) errorMessage = try container.decodeIfPresent(String.self, forKey: .errorMessage) steps = try container.decodeIfPresent([ConversationProgressStep].self, forKey: .steps) ?? [] - editAgentRounds = try container.decodeIfPresent([AgentRound].self, forKey: .editAgentRounds) ?? [] } // Default memberwise init for encoding - init(content: String, rating: ConversationRating, references: [ConversationReference], followUp: ConversationFollowUp?, suggestedTitle: String?, errorMessage: String?, steps: [ConversationProgressStep]?, editAgentRounds: [AgentRound]? = nil) { + init(content: String, rating: ConversationRating, references: [ConversationReference], followUp: ConversationFollowUp?, suggestedTitle: String?, errorMessage: String?, steps: [ConversationProgressStep]?) { self.content = content self.rating = rating self.references = references @@ -38,7 +36,6 @@ extension ChatMessage { self.suggestedTitle = suggestedTitle self.errorMessage = errorMessage self.steps = steps ?? [] - self.editAgentRounds = editAgentRounds ?? [] } } @@ -50,8 +47,7 @@ extension ChatMessage { followUp: self.followUp, suggestedTitle: self.suggestedTitle, errorMessage: self.errorMessage, - steps: self.steps, - editAgentRounds: self.editAgentRounds + steps: self.steps ) // TODO: handle exception @@ -82,7 +78,6 @@ extension ChatMessage { errorMessage: turnItemData.errorMessage, rating: turnItemData.rating, steps: turnItemData.steps, - editAgentRounds: turnItemData.editAgentRounds, createdAt: turnItem.createdAt, updatedAt: turnItem.updatedAt ) diff --git a/ExtensionService/Assets.xcassets/Eye.imageset/Contents.json b/ExtensionService/Assets.xcassets/Eye.imageset/Contents.json deleted file mode 100644 index 107bc195..00000000 --- a/ExtensionService/Assets.xcassets/Eye.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "eye.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/ExtensionService/Assets.xcassets/Eye.imageset/eye.svg b/ExtensionService/Assets.xcassets/Eye.imageset/eye.svg deleted file mode 100644 index 4b83cd92..00000000 --- a/ExtensionService/Assets.xcassets/Eye.imageset/eye.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ExtensionService/Assets.xcassets/EyeClosed.imageset/Contents.json b/ExtensionService/Assets.xcassets/EyeClosed.imageset/Contents.json deleted file mode 100644 index e874ab47..00000000 --- a/ExtensionService/Assets.xcassets/EyeClosed.imageset/Contents.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "images" : [ - { - "filename" : "eye-closed.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true, - "template-rendering-intent" : "template" - } -} diff --git a/ExtensionService/Assets.xcassets/EyeClosed.imageset/eye-closed.svg b/ExtensionService/Assets.xcassets/EyeClosed.imageset/eye-closed.svg deleted file mode 100644 index 76407a31..00000000 --- a/ExtensionService/Assets.xcassets/EyeClosed.imageset/eye-closed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/Server/package-lock.json b/Server/package-lock.json index 2edb274a..de199818 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -8,28 +8,7 @@ "name": "@github/copilot-xcode", "version": "0.0.1", "dependencies": { - "@github/copilot-language-server": "^1.310.0", - "@xterm/addon-fit": "^0.10.0", - "@xterm/xterm": "^5.5.0", - "monaco-editor": "0.52.2" - }, - "devDependencies": { - "copy-webpack-plugin": "^13.0.0", - "css-loader": "^7.1.2", - "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.14", - "webpack": "^5.99.7", - "webpack-cli": "^6.0.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17.0" + "@github/copilot-language-server": "^1.310.0" } }, "node_modules/@github/copilot-language-server": { @@ -44,1688 +23,6 @@ "copilot-language-server": "dist/language-server.js" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.15.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", - "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", - "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", - "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", - "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xterm/addon-fit": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", - "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } - }, - "node_modules/@xterm/xterm": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", - "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001715", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", - "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-webpack-plugin": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz", - "integrity": "sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-parent": "^6.0.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", - "tinyglobby": "^0.2.12" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", - "dev": true, - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.142", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.142.tgz", - "integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/monaco-editor": { - "version": "0.52.2", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", - "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "dev": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/style-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", - "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.27.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -1747,169 +44,6 @@ "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" - }, - "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.99.7", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz", - "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", - "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.6.1", - "@webpack-cli/configtest": "^3.0.1", - "@webpack-cli/info": "^3.0.1", - "@webpack-cli/serve": "^3.0.1", - "colorette": "^2.0.14", - "commander": "^12.1.0", - "cross-spawn": "^7.0.3", - "envinfo": "^7.14.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^6.0.1" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.82.0" - }, - "peerDependenciesMeta": { - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" } } } diff --git a/Server/package.json b/Server/package.json index 0654f880..f6efee42 100644 --- a/Server/package.json +++ b/Server/package.json @@ -3,21 +3,7 @@ "version": "0.0.1", "description": "Package for downloading @github/copilot-language-server", "private": true, - "scripts": { - "build": "webpack" - }, "dependencies": { - "@github/copilot-language-server": "^1.310.0", - "@xterm/addon-fit": "^0.10.0", - "@xterm/xterm": "^5.5.0", - "monaco-editor": "0.52.2" - }, - "devDependencies": { - "copy-webpack-plugin": "^13.0.0", - "css-loader": "^7.1.2", - "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.14", - "webpack": "^5.99.7", - "webpack-cli": "^6.0.1" + "@github/copilot-language-server": "^1.310.0" } } diff --git a/Server/src/diffView/css/style.css b/Server/src/diffView/css/style.css deleted file mode 100644 index c02c22f6..00000000 --- a/Server/src/diffView/css/style.css +++ /dev/null @@ -1,67 +0,0 @@ -/* Diff Viewer Styles */ -html, body { - margin: 0; - padding: 0; - height: 100%; - width: 100%; - overflow: hidden; -} - -#container { - width: 100%; - height: calc(100% - 40px); - border: none; - margin: 0; - padding: 0; - margin-top: 40px; -} - -.loading { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-family: -apple-system, BlinkMacSystemFont, sans-serif; -} - -.action-buttons { - position: absolute; - top: 0; - right: 0; - height: 40px; - display: flex; - justify-content: flex-end; - align-items: center; - padding: 0 4px; - border-top: 1px solid #ddd; -} - -.action-button { - margin-left: 8px; - padding: 6px 12px; - background-color: #007acc; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-family: -apple-system, BlinkMacSystemFont, sans-serif; - font-size: 12px; -} - -.action-button:hover { - background-color: #0062a3; -} - -.action-button.secondary { - background-color: #f0f0f0; - color: #333; - border: 1px solid #ddd; -} - -.action-button.secondary:hover { - background-color: #e0e0e0; -} - -.hidden { - display: none; -} \ No newline at end of file diff --git a/Server/src/diffView/diffView.html b/Server/src/diffView/diffView.html deleted file mode 100644 index aa366db5..00000000 --- a/Server/src/diffView/diffView.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Diff Viewer - - - -
Loading diff viewer...
-
-
- - -
-
-
- - - diff --git a/Server/src/diffView/index.js b/Server/src/diffView/index.js deleted file mode 100644 index 43f63734..00000000 --- a/Server/src/diffView/index.js +++ /dev/null @@ -1,23 +0,0 @@ -// main.js - Main entry point for the Monaco Editor diff view -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; -import { initDiffEditor } from './js/monaco-diff-editor.js'; -import { setupUI } from './js/ui-controller.js'; -import DiffViewer from './js/api.js'; - -// Initialize everything when DOM is loaded -document.addEventListener('DOMContentLoaded', () => { - // Hide loading indicator as Monaco is directly imported - const loadingElement = document.getElementById('loading'); - if (loadingElement) { - loadingElement.style.display = 'none'; - } - - // Set up UI elements and event handlers - setupUI(); -}); - -// Expose the MonacoDiffViewer API to the global scope -window.DiffViewer = DiffViewer; - -// Export the MonacoDiffViewer for webpack -export default DiffViewer; diff --git a/Server/src/diffView/js/api.js b/Server/src/diffView/js/api.js deleted file mode 100644 index 8e1f91ad..00000000 --- a/Server/src/diffView/js/api.js +++ /dev/null @@ -1,51 +0,0 @@ -// api.js - Public API for external use -import { initDiffEditor, updateDiffContent } from './monaco-diff-editor.js'; -import { updateFileMetadata } from './ui-controller.js'; - -/** - * The public API that will be exposed to the global scope - */ -const DiffViewer = { - /** - * Initialize the diff editor with content - * @param {string} originalContent - Content for the original side - * @param {string} modifiedContent - Content for the modified side - * @param {string} path - File path - * @param {string} status - File edit status - * @param {Object} options - Optional configuration for the diff editor - */ - init: function(originalContent, modifiedContent, path, status, options) { - // Initialize editor - initDiffEditor(originalContent, modifiedContent, options); - - // Update file metadata and UI - updateFileMetadata(path, status); - }, - - /** - * Update the diff editor with new content - * @param {string} originalContent - Content for the original side - * @param {string} modifiedContent - Content for the modified side - * @param {string} path - File path - * @param {string} status - File edit status - */ - update: function(originalContent, modifiedContent, path, status) { - // Update editor content - updateDiffContent(originalContent, modifiedContent); - - // Update file metadata and UI - updateFileMetadata(path, status); - }, - - /** - * Handle resize events - */ - handleResize: function() { - const editor = getEditor(); - if (editor) { - editor.layout(); - } - } -}; - -export default DiffViewer; diff --git a/Server/src/diffView/js/monaco-diff-editor.js b/Server/src/diffView/js/monaco-diff-editor.js deleted file mode 100644 index 7b047d42..00000000 --- a/Server/src/diffView/js/monaco-diff-editor.js +++ /dev/null @@ -1,162 +0,0 @@ -// monaco-diff-editor.js - Monaco Editor diff view core functionality -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; - -// Editor state -let diffEditor = null; -let originalModel = null; -let modifiedModel = null; -let resizeObserver = null; - -/** - * Initialize the Monaco diff editor - * @param {string} originalContent - Content for the original side - * @param {string} modifiedContent - Content for the modified side - * @param {Object} options - Optional configuration for the diff editor - * @returns {Object} The diff editor instance - */ -function initDiffEditor(originalContent, modifiedContent, options = {}) { - try { - // Default options - const editorOptions = { - renderSideBySide: false, - readOnly: true, - // Enable automatic layout adjustments - automaticLayout: true, - ...options - }; - - // Create the diff editor if it doesn't exist yet - if (!diffEditor) { - diffEditor = monaco.editor.createDiffEditor( - document.getElementById("container"), - editorOptions - ); - - // Add resize handling - setupResizeHandling(); - } else { - // Apply any new options - diffEditor.updateOptions(editorOptions); - } - - // Create and set models - updateModels(originalContent, modifiedContent); - - return diffEditor; - } catch (error) { - console.error("Error initializing diff editor:", error); - return null; - } -} - -/** - * Setup proper resize handling for the editor - */ -function setupResizeHandling() { - window.addEventListener('resize', () => { - if (diffEditor) { - diffEditor.layout(); - } - }); - - if (window.ResizeObserver && !resizeObserver) { - const container = document.getElementById('container'); - resizeObserver = new ResizeObserver(() => { - if (diffEditor) { - diffEditor.layout() - } - }); - - if (container) { - resizeObserver.observe(container); - } - } -} - -/** - * Create or update the models for the diff editor - * @param {string} originalContent - Content for the original side - * @param {string} modifiedContent - Content for the modified side - */ -function updateModels(originalContent, modifiedContent) { - try { - // Clean up existing models if they exist - if (originalModel) { - originalModel.dispose(); - } - if (modifiedModel) { - modifiedModel.dispose(); - } - - // Create new models with the content - originalModel = monaco.editor.createModel(originalContent || "", "plaintext"); - modifiedModel = monaco.editor.createModel(modifiedContent || "", "plaintext"); - - // Set the models to show the diff - if (diffEditor) { - diffEditor.setModel({ - original: originalModel, - modified: modifiedModel, - }); - } - } catch (error) { - console.error("Error updating models:", error); - } -} - -/** - * Update the diff view with new content - * @param {string} originalContent - Content for the original side - * @param {string} modifiedContent - Content for the modified side - */ -function updateDiffContent(originalContent, modifiedContent) { - // If editor exists, update it - if (diffEditor && diffEditor.getModel()) { - const model = diffEditor.getModel(); - - // Update model values - model.original.setValue(originalContent || ""); - model.modified.setValue(modifiedContent || ""); - } else { - // Initialize if not already done - initDiffEditor(originalContent, modifiedContent); - } -} - -/** - * Get the current diff editor instance - * @returns {Object|null} The diff editor instance or null - */ -function getEditor() { - return diffEditor; -} - -/** - * Dispose of the editor and models to clean up resources - */ -function dispose() { - if (resizeObserver) { - resizeObserver.disconnect(); - resizeObserver = null; - } - - if (originalModel) { - originalModel.dispose(); - originalModel = null; - } - if (modifiedModel) { - modifiedModel.dispose(); - modifiedModel = null; - } - if (diffEditor) { - diffEditor.dispose(); - diffEditor = null; - } -} - -export { - initDiffEditor, - updateDiffContent, - getEditor, - dispose -}; diff --git a/Server/src/diffView/js/ui-controller.js b/Server/src/diffView/js/ui-controller.js deleted file mode 100644 index 14c0facf..00000000 --- a/Server/src/diffView/js/ui-controller.js +++ /dev/null @@ -1,130 +0,0 @@ -// ui-controller.js - UI event handlers and state management -/** - * UI state and file metadata - */ -let filePath = null; -let fileEditStatus = null; - -/** - * Initialize and set up UI elements and their event handlers - * @param {string} initialPath - The initial file path - * @param {string} initialStatus - The initial file edit status - */ -function setupUI(initialPath = null, initialStatus = null) { - filePath = initialPath; - fileEditStatus = initialStatus; - - const keepButton = document.getElementById('keep-button'); - const undoButton = document.getElementById('undo-button'); - const choiceButtons = document.getElementById('choice-buttons'); - - if (!keepButton || !undoButton || !choiceButtons) { - console.error("Could not find UI elements"); - return; - } - - // Set initial UI state - updateUIStatus(initialStatus); - - // Setup event listeners - keepButton.addEventListener('click', handleKeepButtonClick); - undoButton.addEventListener('click', handleUndoButtonClick); -} - -/** - * Update the UI based on file edit status - * @param {string} status - The current file edit status - */ -function updateUIStatus(status) { - fileEditStatus = status; - const choiceButtons = document.getElementById('choice-buttons'); - - if (!choiceButtons) return; - - // Hide buttons if file has been modified - if (status && status !== "none") { - choiceButtons.classList.add('hidden'); - } else { - choiceButtons.classList.remove('hidden'); - } -} - -/** - * Update the file metadata - * @param {string} path - The file path - * @param {string} status - The file edit status - */ -function updateFileMetadata(path, status) { - filePath = path; - updateUIStatus(status); -} - -/** - * Handle the "Keep" button click - */ -function handleKeepButtonClick() { - // Send message to Swift handler - if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.swiftHandler) { - window.webkit.messageHandlers.swiftHandler.postMessage({ - event: 'keepButtonClicked', - data: { - filePath: filePath - } - }); - } else { - console.log('Keep button clicked, but no message handler found'); - } - - // Hide the choice buttons - const choiceButtons = document.getElementById('choice-buttons'); - if (choiceButtons) { - choiceButtons.classList.add('hidden'); - } -} - -/** - * Handle the "Undo" button click - */ -function handleUndoButtonClick() { - // Send message to Swift handler - if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.swiftHandler) { - window.webkit.messageHandlers.swiftHandler.postMessage({ - event: 'undoButtonClicked', - data: { - filePath: filePath - } - }); - } else { - console.log('Undo button clicked, but no message handler found'); - } - - // Hide the choice buttons - const choiceButtons = document.getElementById('choice-buttons'); - if (choiceButtons) { - choiceButtons.classList.add('hidden'); - } -} - -/** - * Get the current file path - * @returns {string} The current file path - */ -function getFilePath() { - return filePath; -} - -/** - * Get the current file edit status - * @returns {string} The current file edit status - */ -function getFileEditStatus() { - return fileEditStatus; -} - -export { - setupUI, - updateUIStatus, - updateFileMetadata, - getFilePath, - getFileEditStatus -}; \ No newline at end of file diff --git a/Server/src/terminal/index.js b/Server/src/terminal/index.js deleted file mode 100644 index 954417e5..00000000 --- a/Server/src/terminal/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import '@xterm/xterm/css/xterm.css'; -import { Terminal } from '@xterm/xterm'; -import { FitAddon } from '@xterm/addon-fit'; - -window.initializeTerminal = function() { - const term = new Terminal({ - cursorBlink: true, - theme: { - background: '#1e1e1e', - foreground: '#cccccc', - cursor: '#ffffff', - selection: 'rgba(128, 128, 128, 0.4)' - }, - fontFamily: 'Menlo, Monaco, "Courier New", monospace', - fontSize: 13 - }); - - const fitAddon = new FitAddon(); - term.loadAddon(fitAddon); - term.open(document.getElementById('terminal')); - fitAddon.fit(); - - term.onData(data => { - window.webkit.messageHandlers.terminalInput.postMessage(data); - }); - - window.addEventListener('resize', () => { - fitAddon.fit(); - }); - - window.writeToTerminal = function(text) { - term.write(text); - }; - - window.clearTerminal = function() { - term.clear(); - }; - - return term; -} diff --git a/Server/src/terminal/terminal.html b/Server/src/terminal/terminal.html deleted file mode 100644 index a35ac6fb..00000000 --- a/Server/src/terminal/terminal.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - -
- - - - diff --git a/Server/webpack.config.js b/Server/webpack.config.js deleted file mode 100644 index 6b8ec08d..00000000 --- a/Server/webpack.config.js +++ /dev/null @@ -1,69 +0,0 @@ -const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const webpack = require('webpack'); -const TerserPlugin = require('terser-webpack-plugin'); - -/* - * The folder structure of `dist` would be: - * dist/ - * ├── terminal/ - * │ ├── terminal.js - * │ └── terminal.html - * └── diffView/ - * ├── diffView.js - * ├── diffView.html - * └── css/ - * └── style.css -*/ -module.exports = { - mode: 'production', - entry: { - // Add more entry points here - terminal: './src/terminal/index.js', - diffView: './src/diffView/index.js' - }, - output: { - filename: '[name]/[name].js', - path: path.resolve(__dirname, 'dist'), - }, - module: { - rules: [ - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - } - ] - }, - plugins: [ - new CopyWebpackPlugin({ - patterns: [ - /// MARK: - Terminal component files - { - from: 'src/terminal/terminal.html', - to: 'terminal/terminal.html' - }, - - /// MARK: - DiffView component files - { - from: 'src/diffView/diffView.html', - to: 'diffView/diffView.html' - }, - { - from: 'src/diffView/css', - to: 'diffView/css' - } - ] - }), - new webpack.optimize.LimitChunkCountPlugin({ - maxChunks: 1 - }) - ], - optimization: { - minimizer: [ - new TerserPlugin({ - // Prevent extracting license comments to a separate file - extractComments: false - }) - ] - } -}; diff --git a/Tool/Package.swift b/Tool/Package.swift index 725385da..59791c36 100644 --- a/Tool/Package.swift +++ b/Tool/Package.swift @@ -90,7 +90,7 @@ let package = Package( .target(name: "Preferences", dependencies: ["Configs"]), - .target(name: "Terminal", dependencies: ["Logger"]), + .target(name: "Terminal"), .target(name: "Logger"), @@ -306,7 +306,6 @@ let package = Package( "TelemetryServiceProvider", "Status", "SystemUtils", - "Workspace", .product(name: "LanguageServerProtocol", package: "LanguageServerProtocol"), .product(name: "CopilotForXcodeKit", package: "CopilotForXcodeKit"), ] diff --git a/Tool/Sources/ChatAPIService/Memory/ChatMemory.swift b/Tool/Sources/ChatAPIService/Memory/ChatMemory.swift index 23691491..097e3d6a 100644 --- a/Tool/Sources/ChatAPIService/Memory/ChatMemory.swift +++ b/Tool/Sources/ChatAPIService/Memory/ChatMemory.swift @@ -68,41 +68,5 @@ extension ChatMessage { self.steps = mergedSteps } - - // merge agent steps - if !message.editAgentRounds.isEmpty { - var mergedAgentRounds = self.editAgentRounds - - for newRound in message.editAgentRounds { - if let index = mergedAgentRounds.firstIndex(where: { $0.roundId == newRound.roundId }) { - mergedAgentRounds[index].reply = mergedAgentRounds[index].reply + newRound.reply - - if newRound.toolCalls != nil, !newRound.toolCalls!.isEmpty { - var mergedToolCalls = mergedAgentRounds[index].toolCalls ?? [] - for newToolCall in newRound.toolCalls! { - if let toolCallIndex = mergedToolCalls.firstIndex(where: { $0.id == newToolCall.id }) { - mergedToolCalls[toolCallIndex].status = newToolCall.status - if let progressMessage = newToolCall.progressMessage, !progressMessage.isEmpty { - mergedToolCalls[toolCallIndex].progressMessage = newToolCall.progressMessage - } - if let error = newToolCall.error, !error.isEmpty { - mergedToolCalls[toolCallIndex].error = newToolCall.error - } - if let invokeParams = newToolCall.invokeParams { - mergedToolCalls[toolCallIndex].invokeParams = invokeParams - } - } else { - mergedToolCalls.append(newToolCall) - } - } - mergedAgentRounds[index].toolCalls = mergedToolCalls - } - } else { - mergedAgentRounds.append(newRound) - } - } - - self.editAgentRounds = mergedAgentRounds - } } } diff --git a/Tool/Sources/ChatAPIService/Models.swift b/Tool/Sources/ChatAPIService/Models.swift index 0cd4bbb8..7e9d7bd6 100644 --- a/Tool/Sources/ChatAPIService/Models.swift +++ b/Tool/Sources/ChatAPIService/Models.swift @@ -104,8 +104,6 @@ public struct ChatMessage: Equatable, Codable { /// The steps of conversation progress public var steps: [ConversationProgressStep] - public var editAgentRounds: [AgentRound] - /// The timestamp of the message. public var createdAt: Date public var updatedAt: Date @@ -122,7 +120,6 @@ public struct ChatMessage: Equatable, Codable { errorMessage: String? = nil, rating: ConversationRating = .unrated, steps: [ConversationProgressStep] = [], - editAgentRounds: [AgentRound] = [], createdAt: Date? = nil, updatedAt: Date? = nil ) { @@ -137,7 +134,6 @@ public struct ChatMessage: Equatable, Codable { self.errorMessage = errorMessage self.rating = rating self.steps = steps - self.editAgentRounds = editAgentRounds let now = Date.now self.createdAt = createdAt ?? now diff --git a/Tool/Sources/ConversationServiceProvider/ConversationServiceProvider.swift b/Tool/Sources/ConversationServiceProvider/ConversationServiceProvider.swift index 059a735d..2706c5ef 100644 --- a/Tool/Sources/ConversationServiceProvider/ConversationServiceProvider.swift +++ b/Tool/Sources/ConversationServiceProvider/ConversationServiceProvider.swift @@ -89,36 +89,30 @@ public struct ConversationRequest { public var workDoneToken: String public var content: String public var workspaceFolder: String - public var activeDoc: Doc? public var skills: [String] public var ignoredSkills: [String]? public var references: [FileReference]? public var model: String? public var turns: [TurnSchema] - public var agentMode: Bool = false public init( workDoneToken: String, content: String, workspaceFolder: String, - activeDoc: Doc? = nil, skills: [String], ignoredSkills: [String]? = nil, references: [FileReference]? = nil, model: String? = nil, - turns: [TurnSchema] = [], - agentMode: Bool = false + turns: [TurnSchema] = [] ) { self.workDoneToken = workDoneToken self.content = content self.workspaceFolder = workspaceFolder - self.activeDoc = activeDoc self.skills = skills self.ignoredSkills = ignoredSkills self.references = references self.model = model self.turns = turns - self.agentMode = agentMode } } @@ -197,37 +191,3 @@ public struct DidChangeWatchedFilesEvent: Codable { self.changes = changes } } - -public struct AgentRound: Codable, Equatable { - public let roundId: Int - public var reply: String - public var toolCalls: [AgentToolCall]? - - public init(roundId: Int, reply: String, toolCalls: [AgentToolCall]? = []) { - self.roundId = roundId - self.reply = reply - self.toolCalls = toolCalls - } -} - -public struct AgentToolCall: Codable, Equatable, Identifiable { - public let id: String - public let name: String - public var progressMessage: String? - public var status: ToolCallStatus - public var error: String? - public var invokeParams: InvokeClientToolParams? - - public enum ToolCallStatus: String, Codable { - case waitForConfirmation, running, completed, error, cancelled - } - - public init(id: String, name: String, progressMessage: String? = nil, status: ToolCallStatus, error: String? = nil, invokeParams: InvokeClientToolParams? = nil) { - self.id = id - self.name = name - self.progressMessage = progressMessage - self.status = status - self.error = error - self.invokeParams = invokeParams - } -} diff --git a/Tool/Sources/ConversationServiceProvider/LSPTypes.swift b/Tool/Sources/ConversationServiceProvider/LSPTypes.swift index 8bc9330e..0d50006e 100644 --- a/Tool/Sources/ConversationServiceProvider/LSPTypes.swift +++ b/Tool/Sources/ConversationServiceProvider/LSPTypes.swift @@ -1,5 +1,3 @@ -import Foundation -import JSONRPC // MARK: Conversation template public struct ChatTemplate: Codable, Equatable { @@ -78,175 +76,3 @@ public struct ChatAgent: Codable, Equatable { self.avatarUrl = avatarUrl } } - -// MARK: EditAgent - -public struct RegisterToolsParams: Codable, Equatable { - public let tools: [LanguageModelToolInformation] - - public init(tools: [LanguageModelToolInformation]) { - self.tools = tools - } -} - -public struct LanguageModelToolInformation: Codable, Equatable { - /// The name of the tool. - public let name: String - - /// A description of this tool that may be used by a language model to select it. - public let description: String - - /// A JSON schema for the input this tool accepts. The input must be an object at the top level. - /// A particular language model may not support all JSON schema features. - public let inputSchema: LanguageModelToolSchema? - - public init(name: String, description: String, inputSchema: LanguageModelToolSchema?) { - self.name = name - self.description = description - self.inputSchema = inputSchema - } -} - -public struct LanguageModelToolSchema: Codable, Equatable { - public let type: String - public let properties: [String: ToolInputPropertySchema] - public let required: [String] - - public init(type: String, properties: [String : ToolInputPropertySchema], required: [String]) { - self.type = type - self.properties = properties - self.required = required - } -} - -public struct ToolInputPropertySchema: Codable, Equatable { - public struct Items: Codable, Equatable { - public let type: String - - public init(type: String) { - self.type = type - } - } - - public let type: String - public let description: String - public let items: Items? - - public init(type: String, description: String, items: Items? = nil) { - self.type = type - self.description = description - self.items = items - } -} - -public struct InvokeClientToolParams: Codable, Equatable { - /// The name of the tool to be invoked. - public let name: String - - /// The input to the tool. - public let input: [String: AnyCodable]? - - /// The ID of the conversation this tool invocation belongs to. - public let conversationId: String - - /// The ID of the turn this tool invocation belongs to. - public let turnId: String - - /// The ID of the round this tool invocation belongs to. - public let roundId: Int - - /// The unique ID for this specific tool call. - public let toolCallId: String -} - -/// A helper type to encode/decode `Any` values in JSON. -public struct AnyCodable: Codable, Equatable { - public static func == (lhs: AnyCodable, rhs: AnyCodable) -> Bool { - switch (lhs.value, rhs.value) { - case let (lhs as Int, rhs as Int): - return lhs == rhs - case let (lhs as Double, rhs as Double): - return lhs == rhs - case let (lhs as String, rhs as String): - return lhs == rhs - case let (lhs as Bool, rhs as Bool): - return lhs == rhs - case let (lhs as [AnyCodable], rhs as [AnyCodable]): - return lhs == rhs - case let (lhs as [String: AnyCodable], rhs as [String: AnyCodable]): - return lhs == rhs - default: - return false - } - } - - public let value: Any - - public init(_ value: Any) { - self.value = value - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let intValue = try? container.decode(Int.self) { - value = intValue - } else if let doubleValue = try? container.decode(Double.self) { - value = doubleValue - } else if let stringValue = try? container.decode(String.self) { - value = stringValue - } else if let boolValue = try? container.decode(Bool.self) { - value = boolValue - } else if let arrayValue = try? container.decode([AnyCodable].self) { - value = arrayValue.map { $0.value } - } else if let dictionaryValue = try? container.decode([String: AnyCodable].self) { - value = dictionaryValue.mapValues { $0.value } - } else { - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported type") - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - if let intValue = value as? Int { - try container.encode(intValue) - } else if let doubleValue = value as? Double { - try container.encode(doubleValue) - } else if let stringValue = value as? String { - try container.encode(stringValue) - } else if let boolValue = value as? Bool { - try container.encode(boolValue) - } else if let arrayValue = value as? [Any] { - try container.encode(arrayValue.map { AnyCodable($0) }) - } else if let dictionaryValue = value as? [String: Any] { - try container.encode(dictionaryValue.mapValues { AnyCodable($0) }) - } else { - throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: container.codingPath, debugDescription: "Unsupported type")) - } - } -} - -public typealias InvokeClientToolRequest = JSONRPCRequest - -public struct LanguageModelToolResult: Codable, Equatable { - public struct Content: Codable, Equatable { - public let value: AnyCodable - - public init(value: Any) { - self.value = AnyCodable(value) - } - } - - public let content: [Content] - - public init(content: [Content]) { - self.content = content - } -} - -public struct Doc: Codable { - var uri: String - - public init(uri: String) { - self.uri = uri - } -} diff --git a/Tool/Sources/ConversationServiceProvider/ToolNames.swift b/Tool/Sources/ConversationServiceProvider/ToolNames.swift deleted file mode 100644 index 4bc31857..00000000 --- a/Tool/Sources/ConversationServiceProvider/ToolNames.swift +++ /dev/null @@ -1,8 +0,0 @@ - -public enum ToolName: String { - case runInTerminal = "run_in_terminal" - case getTerminalOutput = "get_terminal_output" - case getErrors = "get_errors" - case insertEditIntoFile = "insert_edit_into_file" - case createFile = "create_file" -} diff --git a/Tool/Sources/GitHubCopilotService/Conversation/ClientToolHandler.swift b/Tool/Sources/GitHubCopilotService/Conversation/ClientToolHandler.swift deleted file mode 100644 index 9baed83f..00000000 --- a/Tool/Sources/GitHubCopilotService/Conversation/ClientToolHandler.swift +++ /dev/null @@ -1,19 +0,0 @@ -import JSONRPC -import ConversationServiceProvider -import Combine - -public protocol ClientToolHandler { - var onClientToolInvokeEvent: PassthroughSubject<(InvokeClientToolRequest, (AnyJSONRPCResponse) -> Void), Never> { get } - func invokeClientTool(_ params: InvokeClientToolRequest, completion: @escaping (AnyJSONRPCResponse) -> Void) -} - -public final class ClientToolHandlerImpl: ClientToolHandler { - - public static let shared = ClientToolHandlerImpl() - - public let onClientToolInvokeEvent: PassthroughSubject<(InvokeClientToolRequest, (AnyJSONRPCResponse) -> Void), Never> = .init() - - public func invokeClientTool(_ request: InvokeClientToolRequest, completion: @escaping (AnyJSONRPCResponse) -> Void) { - onClientToolInvokeEvent.send((request, completion)) - } -} diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/ClientToolRegistry.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/ClientToolRegistry.swift deleted file mode 100644 index 99290743..00000000 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/ClientToolRegistry.swift +++ /dev/null @@ -1,102 +0,0 @@ - -import ConversationServiceProvider - -func registerClientTools(server: GitHubCopilotConversationServiceType) async { - var tools: [LanguageModelToolInformation] = [] - let runInTerminalTool = LanguageModelToolInformation( - name: ToolName.runInTerminal.rawValue, - description: "Run a shell command in a terminal. State is persistent across tool calls.\n- Use this tool instead of printing a shell codeblock and asking the user to run it.\n- If the command is a long-running background process, you MUST pass isBackground=true. Background terminals will return a terminal ID which you can use to check the output of a background process with get_terminal_output.\n- If a command may use a pager, you must something to disable it. For example, you can use `git --no-pager`. Otherwise you should add something like ` | cat`. Examples: git, less, man, etc.", - inputSchema: LanguageModelToolSchema( - type: "object", - properties: [ - "command": ToolInputPropertySchema( - type: "string", - description: "The command to run in the terminal."), - "explanation": ToolInputPropertySchema( - type: "string", - description: "A one-sentence description of what the command does. This will be shown to the user before the command is run."), - "isBackground": ToolInputPropertySchema( - type: "boolean", - description: "Whether the command starts a background process. If true, the command will run in the background and you will not see the output. If false, the tool call will block on the command finishing, and then you will get the output. Examples of background processes: building in watch mode, starting a server. You can check the output of a background process later on by using get_terminal_output.") - ], - required: [ - "command", - "explanation", - "isBackground" - ]) - ) - let getErrorsTool: LanguageModelToolInformation = .init( - name: ToolName.getErrors.rawValue, - description: "Get any compile or lint errors in a code file. If the user mentions errors or problems in a file, they may be referring to these. Use the tool to see the same errors that the user is seeing. Also use this tool after editing a file to validate the change.", - inputSchema: .init( - type: "object", - properties: [ - "filePaths": .init( - type: "array", - description: "The absolute paths to the files to check for errors.", - items: .init(type: "string") - ) - ], - required: ["filePaths"] - ) - ) - - let getTerminalOutputTool = LanguageModelToolInformation( - name: ToolName.getTerminalOutput.rawValue, - description: "Get the output of a terminal command previously started using run_in_terminal", - inputSchema: LanguageModelToolSchema( - type: "object", - properties: [ - "id": ToolInputPropertySchema( - type: "string", - description: "The ID of the terminal command output to check." - ) - ], - required: [ - "id" - ]) - ) - - let createFileTool: LanguageModelToolInformation = .init( - name: ToolName.createFile.rawValue, - description: "This is a tool for creating a new file in the workspace. The file will be created with the specified content.", - inputSchema: .init( - type: "object", - properties: [ - "filePath": .init( - type: "string", - description: "The absolute path to the file to create." - ), - "content": .init( - type: "string", - description: "The content to write to the file." - ) - ], - required: ["filePath", "content"] - ) - ) - - let insertEditIntoFileTool: LanguageModelToolInformation = .init( - name: ToolName.insertEditIntoFile.rawValue, - description: "Insert new code into an existing file in the workspace. Use this tool once per file that needs to be modified, even if there are multiple changes for a file. Generate the \"explanation\" property first.\nThe system is very smart and can understand how to apply your edits to the files, you just need to provide minimal hints.\nAvoid repeating existing code, instead use comments to represent regions of unchanged code. Be as concise as possible. For example:\n// ...existing code...\n{ changed code }\n// ...existing code...\n{ changed code }\n// ...existing code...\n\nHere is an example of how you should use format an edit to an existing Person class:\nclass Person {\n\t// ...existing code...\n\tage: number;\n\t// ...existing code...\n\tgetAge() {\n\treturn this.age;\n\t}\n}", - inputSchema: .init( - type: "object", - properties: [ - "filePath": .init(type: "string", description: "An absolute path to the file to edit."), - "code": .init(type: "string", description: "The code change to apply to the file.\nThe system is very smart and can understand how to apply your edits to the files, you just need to provide minimal hints.\nAvoid repeating existing code, instead use comments to represent regions of unchanged code. Be as concise as possible. For example:\n// ...existing code...\n{ changed code }\n// ...existing code...\n{ changed code }\n// ...existing code...\n\nHere is an example of how you should use format an edit to an existing Person class:\nclass Person {\n\t// ...existing code...\n\tage: number;\n\t// ...existing code...\n\tgetAge() {\n\t\treturn this.age;\n\t}\n}"), - "explanation": .init(type: "string", description: "A short explanation of the edit being made.") - ], - required: ["filePath", "code", "explanation"] - ) - ) - - tools.append(runInTerminalTool) - tools.append(getTerminalOutputTool) - tools.append(getErrorsTool) - tools.append(insertEditIntoFileTool) - tools.append(createFileTool) - - if !tools.isEmpty { - try? await server.registerTools(tools: tools) - } -} diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotLocalProcessServer.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotLocalProcessServer.swift index 7693aacf..416e7379 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotLocalProcessServer.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotLocalProcessServer.swift @@ -346,17 +346,11 @@ extension CustomJSONRPCLanguageServer { callback: @escaping (AnyJSONRPCResponse) -> Void ) -> Bool { serverRequestPublisher.send((request: request, callback: callback)) - - let methodName = request.method - switch methodName { - case "conversation/invokeClientTool": - return true - case "conversation/context": - return true + switch request.method { case "copilot/watchedFiles": return true default: - return false // delegate the default handling to the server + return false } } } diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotModelManager.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotModelManager.swift index d810025d..d6e3de6a 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotModelManager.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/CopilotModelManager.swift @@ -17,7 +17,11 @@ public class CopilotModelManager { public static func getAvailableLLMs() -> [CopilotModel] { return availableLLMs } - + + public static func getDefaultChatLLM() -> CopilotModel? { + return availableLLMs.first(where: { $0.isChatDefault }) + } + public static func hasLLMs() -> Bool { return !availableLLMs.isEmpty } diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest+Conversation.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest+Conversation.swift index bac8ee3d..70d5ed50 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest+Conversation.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest+Conversation.swift @@ -10,6 +10,11 @@ enum ConversationSource: String, Codable { case panel, inline } +public struct Doc: Codable { + var position: Position? + var uri: String +} + public struct Reference: Codable, Equatable, Hashable { public var type: String = "file" public let uri: String @@ -24,15 +29,13 @@ struct ConversationCreateParams: Codable { var workDoneToken: String var turns: [ConversationTurn] var capabilities: Capabilities - var textDocument: Doc? + var doc: Doc? var references: [Reference]? var computeSuggestions: Bool? var source: ConversationSource? var workspaceFolder: String? - var workspaceFolders: [WorkspaceFolder]? var ignoredSkills: [String]? var model: String? - var chatMode: String? struct Capabilities: Codable { var skills: [String] @@ -66,7 +69,6 @@ public struct ConversationProgressReport: BaseConversationProgress { public let reply: String? public let references: [Reference]? public let steps: [ConversationProgressStep]? - public let editAgentRounds: [AgentRound]? } public struct ConversationProgressEnd: BaseConversationProgress { @@ -131,13 +133,11 @@ struct TurnCreateParams: Codable { var workDoneToken: String var conversationId: String var message: String - var textDocument: Doc? + var doc: Doc? var ignoredSkills: [String]? var references: [Reference]? var model: String? var workspaceFolder: String? - var workspaceFolders: [WorkspaceFolder]? - var chatMode: String? } // MARK: Copy diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift index f6652806..8eddd140 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift @@ -83,21 +83,9 @@ public func editorConfiguration() -> JSONValue { return .hash([ "uri": .string(enterpriseURI) ]) } - var mcp: JSONValue? { - let mcpConfig = UserDefaults.shared.value(for: \.gitHubCopilotMCPConfig) - return JSONValue.string(mcpConfig) - } - var d: [String: JSONValue] = [:] if let http { d["http"] = http } if let authProvider { d["github-enterprise"] = authProvider } - if let mcp { - var github: [String: JSONValue] = [:] - var copilot: [String: JSONValue] = [:] - copilot["mcp"] = mcp - github["copilot"] = .hash(copilot) - d["github"] = .hash(github) - } return .hash(d) } @@ -379,18 +367,6 @@ enum GitHubCopilotRequest { } } - struct RegisterTools: GitHubCopilotRequestType { - struct Response: Codable {} - - var params: RegisterToolsParams - - var request: ClientRequest { - let data = (try? JSONEncoder().encode(params)) ?? Data() - let dict = (try? JSONDecoder().decode(JSONValue.self, from: data)) ?? .hash([:]) - return .custom("conversation/registerTools", dict) - } - } - // MARK: Copy code struct CopyCode: GitHubCopilotRequestType { diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift index 310ab959..57a0825d 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift @@ -54,35 +54,29 @@ public protocol GitHubCopilotConversationServiceType { func createConversation(_ message: String, workDoneToken: String, workspaceFolder: String, - workspaceFolders: [WorkspaceFolder]?, - activeDoc: Doc?, + doc: Doc?, skills: [String], ignoredSkills: [String]?, references: [FileReference], model: String?, - turns: [TurnSchema], - agentMode: Bool) async throws + turns: [TurnSchema]) async throws func createTurn(_ message: String, workDoneToken: String, conversationId: String, - activeDoc: Doc?, + doc: Doc?, ignoredSkills: [String]?, references: [FileReference], model: String?, - workspaceFolder: String, - workspaceFolders: [WorkspaceFolder]?, - agentMode: Bool) async throws + workspaceFolder: String?) async throws func rateConversation(turnId: String, rating: ConversationRating) async throws func copyCode(turnId: String, codeBlockIndex: Int, copyType: CopyKind, copiedCharacters: Int, totalCharacters: Int, copiedText: String) async throws func cancelProgress(token: String) async func templates() async throws -> [ChatTemplate] func models() async throws -> [CopilotModel] - func registerTools(tools: [LanguageModelToolInformation]) async throws } protocol GitHubCopilotLSP { func sendRequest(_ endpoint: E) async throws -> E.Response - func sendRequest(_ endpoint: E, timeout: TimeInterval) async throws -> E.Response func sendNotification(_ notif: ClientNotification) async throws } @@ -161,7 +155,6 @@ public class GitHubCopilotBaseService { var path = SystemUtils.shared.getXcodeBinaryPath() var args = ["--stdio"] let home = ProcessInfo.processInfo.homePath - let systemPath = getTerminalPATH() ?? ProcessInfo.processInfo.environment["PATH"] ?? "" let versionNumber = JSONValue( stringLiteral: SystemUtils.editorPluginVersion ?? "" ) @@ -181,17 +174,17 @@ public class GitHubCopilotBaseService { let nodePath = Bundle.main.infoDictionary?["NODE_PATH"] as? String ?? "node" if FileManager.default.fileExists(atPath: jsPath.path) { path = "/usr/bin/env" - args = [nodePath, "--inspect", jsPath.path, "--stdio"] + args = [nodePath, jsPath.path, "--stdio"] Logger.debug.info("Using local language server \(path) \(args)") } } // Set debug port and verbose when running in debug - let environment: [String: String] = ["HOME": home, "GH_COPILOT_DEBUG_UI_PORT": "8180", "GH_COPILOT_VERBOSE": "true", "PATH": systemPath] + let environment: [String: String] = ["HOME": home, "GH_COPILOT_DEBUG_UI_PORT": "8080", "GH_COPILOT_VERBOSE": "true"] #else let environment: [String: String] = if UserDefaults.shared.value(for: \.verboseLoggingEnabled) { - ["HOME": home, "GH_COPILOT_VERBOSE": "true", "PATH": systemPath] + ["HOME": home, "GH_COPILOT_VERBOSE": "true"] } else { - ["HOME": home, "PATH": systemPath] + ["HOME": home] } #endif @@ -211,7 +204,7 @@ public class GitHubCopilotBaseService { } let server = InitializingServer(server: localServer) // TODO: set proper timeout against different request. - server.defaultTimeout = 90 + server.defaultTimeout = 60 server.initializeParamsProvider = { let capabilities = ClientCapabilities( workspace: .init( @@ -339,41 +332,6 @@ public class GitHubCopilotBaseService { } } -func getTerminalPATH() -> String? { - let process = Process() - let pipe = Pipe() - - guard let userShell = ProcessInfo.processInfo.environment["SHELL"] else { - print("Cannot determine user's default shell.") - return nil - } - - let shellName = URL(fileURLWithPath: userShell).lastPathComponent - let command: String - - if shellName == "zsh" { - command = "source ~/.zshrc >/dev/null 2>&1; echo $PATH" - } else { - command = "source ~/.bashrc >/dev/null 2>&1; echo $PATH" - } - - process.executableURL = URL(fileURLWithPath: userShell) - process.arguments = ["-i", "-l", "-c", command] - process.standardOutput = pipe - - do { - try process.run() - process.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - return String(data: data, encoding: .utf8)? - .trimmingCharacters(in: .whitespacesAndNewlines) - } catch { - print("Error: \(error)") - return nil - } -} - @globalActor public enum GitHubCopilotSuggestionActor { public actor TheActor {} public static let shared = TheActor() @@ -409,10 +367,6 @@ public final class GitHubCopilotService: updateStatusInBackground() GitHubCopilotService.services.append(self) - - Task { - await registerClientTools(server: self) - } } catch { Logger.gitHubCopilot.error(error) throw error @@ -532,14 +486,12 @@ public final class GitHubCopilotService: public func createConversation(_ message: String, workDoneToken: String, workspaceFolder: String, - workspaceFolders: [WorkspaceFolder]? = nil, - activeDoc: Doc?, + doc: Doc?, skills: [String], ignoredSkills: [String]?, references: [FileReference], model: String?, - turns: [TurnSchema], - agentMode: Bool) async throws { + turns: [TurnSchema]) async throws { var conversationCreateTurns: [ConversationTurn] = [] // invoke conversation history if turns.count > 0 { @@ -555,7 +507,7 @@ public final class GitHubCopilotService: capabilities: ConversationCreateParams.Capabilities( skills: skills, allSkills: false), - textDocument: activeDoc, + doc: doc, references: references.map { Reference(uri: $0.url.absoluteString, position: nil, @@ -566,13 +518,12 @@ public final class GitHubCopilotService: }, source: .panel, workspaceFolder: workspaceFolder, - workspaceFolders: workspaceFolders, ignoredSkills: ignoredSkills, - model: model, - chatMode: agentMode ? "Agent" : nil) + model: model) do { _ = try await sendRequest( - GitHubCopilotRequest.CreateConversation(params: params), timeout: conversationRequestTimeout(agentMode)) + GitHubCopilotRequest.CreateConversation(params: params) + ) } catch { print("Failed to create conversation. Error: \(error)") throw error @@ -580,12 +531,12 @@ public final class GitHubCopilotService: } @GitHubCopilotSuggestionActor - public func createTurn(_ message: String, workDoneToken: String, conversationId: String, activeDoc: Doc?, ignoredSkills: [String]?, references: [FileReference], model: String?, workspaceFolder: String, workspaceFolders: [WorkspaceFolder]? = nil, agentMode: Bool) async throws { + public func createTurn(_ message: String, workDoneToken: String, conversationId: String, doc: Doc?, ignoredSkills: [String]?, references: [FileReference], model: String?, workspaceFolder: String?) async throws { do { let params = TurnCreateParams(workDoneToken: workDoneToken, conversationId: conversationId, message: message, - textDocument: activeDoc, + doc: doc, ignoredSkills: ignoredSkills, references: references.map { Reference(uri: $0.url.absoluteString, @@ -596,21 +547,17 @@ public final class GitHubCopilotService: activeAt: nil) }, model: model, - workspaceFolder: workspaceFolder, - workspaceFolders: workspaceFolders, - chatMode: agentMode ? "Agent" : nil) + workspaceFolder: workspaceFolder) + _ = try await sendRequest( - GitHubCopilotRequest.CreateTurn(params: params), timeout: conversationRequestTimeout(agentMode)) + GitHubCopilotRequest.CreateTurn(params: params) + ) } catch { print("Failed to create turn. Error: \(error)") throw error } } - private func conversationRequestTimeout(_ agentMode: Bool) -> TimeInterval { - return agentMode ? 300 /* agent mode timeout */ : 90 - } - @GitHubCopilotSuggestionActor public func templates() async throws -> [ChatTemplate] { do { @@ -647,17 +594,6 @@ public final class GitHubCopilotService: } } - @GitHubCopilotSuggestionActor - public func registerTools(tools: [LanguageModelToolInformation]) async throws { - do { - _ = try await sendRequest( - GitHubCopilotRequest.RegisterTools(params: RegisterToolsParams(tools: tools)) - ) - } catch { - throw error - } - } - @GitHubCopilotSuggestionActor public func rateConversation(turnId: String, rating: ConversationRating) async throws { do { @@ -966,13 +902,9 @@ public final class GitHubCopilotService: } } - private func sendRequest(_ endpoint: E, timeout: TimeInterval? = nil) async throws -> E.Response { + private func sendRequest(_ endpoint: E) async throws -> E.Response { do { - if let timeout = timeout { - return try await server.sendRequest(endpoint, timeout: timeout) - } else { - return try await server.sendRequest(endpoint) - } + return try await server.sendRequest(endpoint) } catch let error as ServerError { if let info = CLSErrorInfo(for: error) { // update the auth status if the error indicates it may have changed, and then rethrow @@ -1020,14 +952,6 @@ extension InitializingServer: GitHubCopilotLSP { func sendRequest(_ endpoint: E) async throws -> E.Response { try await sendRequest(endpoint.request) } - - func sendRequest(_ endpoint: E, timeout: TimeInterval) async throws -> E.Response { - return try await withCheckedThrowingContinuation { continuation in - self.sendRequest(endpoint.request, timeout: timeout) { result in - continuation.resume(with: result) - } - } - } } extension GitHubCopilotService { diff --git a/Tool/Sources/GitHubCopilotService/LanguageServer/ServerRequestHandler.swift b/Tool/Sources/GitHubCopilotService/LanguageServer/ServerRequestHandler.swift index a69fc007..8b4e30ea 100644 --- a/Tool/Sources/GitHubCopilotService/LanguageServer/ServerRequestHandler.swift +++ b/Tool/Sources/GitHubCopilotService/LanguageServer/ServerRequestHandler.swift @@ -1,5 +1,4 @@ import Foundation -import ConversationServiceProvider import Combine import JSONRPC import LanguageClient @@ -31,11 +30,6 @@ class ServerRequestHandlerImpl : ServerRequestHandler { let watchedFilesParams = try JSONDecoder().decode(WatchedFilesParams.self, from: params) watchedFilesHandler.handleWatchedFiles(WatchedFilesRequest(id: request.id, method: request.method, params: watchedFilesParams), workspaceURL: workspaceURL, completion: callback, service: service) - case "conversation/invokeClientTool": - let params = try JSONEncoder().encode(request.params) - let invokeParams = try JSONDecoder().decode(InvokeClientToolParams.self, from: params) - ClientToolHandlerImpl.shared.invokeClientTool(InvokeClientToolRequest(id: request.id, method: request.method, params: invokeParams), completion: callback) - default: break } diff --git a/Tool/Sources/GitHubCopilotService/Services/GitHubCopilotConversationService.swift b/Tool/Sources/GitHubCopilotService/Services/GitHubCopilotConversationService.swift index 0958470e..b9ade166 100644 --- a/Tool/Sources/GitHubCopilotService/Services/GitHubCopilotConversationService.swift +++ b/Tool/Sources/GitHubCopilotService/Services/GitHubCopilotConversationService.swift @@ -2,8 +2,6 @@ import CopilotForXcodeKit import Foundation import ConversationServiceProvider import BuiltinExtension -import Workspace -import LanguageServerProtocol public final class GitHubCopilotConversationService: ConversationServiceType { @@ -12,28 +10,19 @@ public final class GitHubCopilotConversationService: ConversationServiceType { init(serviceLocator: ServiceLocator) { self.serviceLocator = serviceLocator } - - private func getWorkspaceFolders(workspace: WorkspaceInfo) -> [WorkspaceFolder] { - let projects = WorkspaceFile.getProjects(workspace: workspace) - return projects.map { project in - WorkspaceFolder(uri: project.uri, name: project.name) - } - } - + public func createConversation(_ request: ConversationRequest, workspace: WorkspaceInfo) async throws { guard let service = await serviceLocator.getService(from: workspace) else { return } return try await service.createConversation(request.content, workDoneToken: request.workDoneToken, workspaceFolder: workspace.projectURL.absoluteString, - workspaceFolders: getWorkspaceFolders(workspace: workspace), - activeDoc: request.activeDoc, + doc: nil, skills: request.skills, ignoredSkills: request.ignoredSkills, references: request.references ?? [], model: request.model, - turns: request.turns, - agentMode: request.agentMode) + turns: request.turns) } public func createTurn(with conversationId: String, request: ConversationRequest, workspace: WorkspaceInfo) async throws { @@ -42,13 +31,11 @@ public final class GitHubCopilotConversationService: ConversationServiceType { return try await service.createTurn(request.content, workDoneToken: request.workDoneToken, conversationId: conversationId, - activeDoc: request.activeDoc, + doc: nil, ignoredSkills: request.ignoredSkills, references: request.references ?? [], model: request.model, - workspaceFolder: workspace.projectURL.absoluteString, - workspaceFolders: getWorkspaceFolders(workspace: workspace), - agentMode: request.agentMode) + workspaceFolder: workspace.projectURL.absoluteString) } public func cancelProgress(_ workDoneToken: String, workspace: WorkspaceInfo) async throws { diff --git a/Tool/Sources/HostAppActivator/HostAppActivator.swift b/Tool/Sources/HostAppActivator/HostAppActivator.swift index 81658337..0f540ac9 100644 --- a/Tool/Sources/HostAppActivator/HostAppActivator.swift +++ b/Tool/Sources/HostAppActivator/HostAppActivator.swift @@ -7,8 +7,6 @@ public let HostAppURL = locateHostBundleURL(url: Bundle.main.bundleURL) public extension Notification.Name { static let openSettingsWindowRequest = Notification .Name("com.github.CopilotForXcode.OpenSettingsWindowRequest") - static let openMCPSettingsWindowRequest = Notification - .Name("com.github.CopilotForXcode.OpenMCPSettingsWindowRequest") } public enum GitHubCopilotForXcodeSettingsLaunchError: Error, LocalizedError { @@ -54,26 +52,6 @@ public func launchHostAppSettings() throws { } } -public func launchHostAppMCPSettings() throws { - // Try the AppleScript approach first, but only if app is already running - if let hostApp = getRunningHostApp() { - let activated = hostApp.activate(options: [.activateIgnoringOtherApps]) - Logger.ui.info("\(hostAppName()) activated: \(activated)") - - _ = tryLaunchWithAppleScript() - - DistributedNotificationCenter.default().postNotificationName( - .openMCPSettingsWindowRequest, - object: nil - ) - Logger.ui.info("\(hostAppName()) MCP settings notification sent after activation") - return - } else { - // If app is not running, launch it with the settings flag - try launchHostAppWithArgs(args: ["--mcp"]) - } -} - private func tryLaunchWithAppleScript() -> Bool { // Try to launch settings using AppleScript let script = """ diff --git a/Tool/Sources/Preferences/Keys.swift b/Tool/Sources/Preferences/Keys.swift index 16bcbc40..aed2ef41 100644 --- a/Tool/Sources/Preferences/Keys.swift +++ b/Tool/Sources/Preferences/Keys.swift @@ -291,10 +291,6 @@ public extension UserDefaultPreferenceKeys { var keepFloatOnTopIfChatPanelAndXcodeOverlaps: PreferenceKey { .init(defaultValue: true, key: "KeepFloatOnTopIfChatPanelAndXcodeOverlaps") } - - var enableCurrentEditorContext: PreferenceKey { - .init(defaultValue: true, key: "EnableCurrentEditorContext") - } } // MARK: - Theme @@ -554,10 +550,6 @@ public extension UserDefaultPreferenceKeys { var gitHubCopilotProxyPassword: PreferenceKey { .init(defaultValue: "", key: "GitHubCopilotProxyPassword") } - - var gitHubCopilotMCPConfig: PreferenceKey { - .init(defaultValue: "", key: "GitHubCopilotMCPConfig") - } var gitHubCopilotEnterpriseURI: PreferenceKey { .init(defaultValue: "", key: "GitHubCopilotEnterpriseURI") diff --git a/Tool/Sources/Terminal/TerminalSession.swift b/Tool/Sources/Terminal/TerminalSession.swift deleted file mode 100644 index 21d5fe28..00000000 --- a/Tool/Sources/Terminal/TerminalSession.swift +++ /dev/null @@ -1,256 +0,0 @@ -import Foundation -import Logger -import Combine - -/** - * Manages shell processes for terminal emulation - */ -class ShellProcessManager { - private var process: Process? - private var outputPipe: Pipe? - private var inputPipe: Pipe? - private var isRunning = false - var onOutputReceived: ((String) -> Void)? - - private let shellIntegrationScript = """ - # Shell integration for tracking command execution and exit codes - __terminal_command_start() { - printf "\\033]133;C\\007" # Command started - } - - __terminal_command_finished() { - local EXIT="$?" - printf "\\033]133;D;%d\\007" "$EXIT" # Command finished with exit code - return $EXIT - } - - # Set up precmd and preexec hooks - autoload -Uz add-zsh-hook - add-zsh-hook precmd __terminal_command_finished - add-zsh-hook preexec __terminal_command_start - - """ - - /** - * Starts a shell process - */ - func startShell(inDirectory directory: String = NSHomeDirectory()) { - guard !isRunning else { return } - - process = Process() - outputPipe = Pipe() - inputPipe = Pipe() - - // Configure the process - process?.executableURL = URL(fileURLWithPath: "/bin/zsh") - process?.arguments = ["-i"] - - // Create temporary file for shell integration - let tempDir = FileManager.default.temporaryDirectory - let copilotZshPath = tempDir.appendingPathComponent("xcode-copilot-zsh") - - var zshdir = tempDir - if !FileManager.default.fileExists(atPath: copilotZshPath.path) { - do { - try FileManager.default.createDirectory(at: copilotZshPath, withIntermediateDirectories: true, attributes: nil) - zshdir = copilotZshPath - } catch { - Logger.client.info("Error creating zsh directory: \(error.localizedDescription)") - } - } else { - zshdir = copilotZshPath - } - - let integrationFile = zshdir.appendingPathComponent("shell_integration.zsh") - try? shellIntegrationScript.write(to: integrationFile, atomically: true, encoding: .utf8) - - var environment = ProcessInfo.processInfo.environment - // Fetch login shell environment to get correct PATH - if let shellEnv = ShellProcessManager.getLoginShellEnvironment() { - for (key, value) in shellEnv { - environment[key] = value - } - } - - let userZdotdir = environment["ZDOTDIR"] ?? NSHomeDirectory() - environment["ZDOTDIR"] = zshdir.path - environment["USER_ZDOTDIR"] = userZdotdir - environment["SHELL_INTEGRATION"] = integrationFile.path - process?.environment = environment - - // Source shell integration in zsh startup - let zshrcContent = "source \"$SHELL_INTEGRATION\"\n" - try? zshrcContent.write(to: zshdir.appendingPathComponent(".zshrc"), atomically: true, encoding: .utf8) - - process?.standardOutput = outputPipe - process?.standardError = outputPipe - process?.standardInput = inputPipe - process?.currentDirectoryURL = URL(fileURLWithPath: directory) - - // Handle output from the process - outputPipe?.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in - let data = fileHandle.availableData - if !data.isEmpty, let output = String(data: data, encoding: .utf8) { - DispatchQueue.main.async { - self?.onOutputReceived?(output) - } - } - } - - do { - try process?.run() - isRunning = true - } catch { - onOutputReceived?("Failed to start shell: \(error.localizedDescription)\r\n") - Logger.client.error("Failed to start shell: \(error.localizedDescription)") - } - } - - /// Returns the environment of a login shell (to get correct PATH and other variables) - private static func getLoginShellEnvironment() -> [String: String]? { - let task = Process() - let pipe = Pipe() - task.executableURL = URL(fileURLWithPath: "/bin/zsh") - task.arguments = ["-l", "-c", "env"] - task.standardOutput = pipe - do { - try task.run() - task.waitUntilExit() - let data = pipe.fileHandleForReading.readDataToEndOfFile() - guard let output = String(data: data, encoding: .utf8) else { return nil } - var env: [String: String] = [:] - for line in output.split(separator: "\n") { - if let idx = line.firstIndex(of: "=") { - let key = String(line[.. Void)? - - init() { - // Set up the shell process manager to handle shell output - shellManager.onOutputReceived = { [weak self] output in - self?.handleShellOutput(output) - } - } - - public func executeCommand(currentDirectory: String, command: String, completion: @escaping (CommandExecutionResult) -> Void) { - onCommandCompleted = completion - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in - self?.shellManager.startShell(inDirectory: currentDirectory.isEmpty ? NSHomeDirectory() : currentDirectory) - self?.shellManager.sendCommand("\n") - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in - self?.terminalOutput += "\(command)\n" - self?.shellManager.sendCommand(command + "\n") - self?.hasPendingCommand = true - } - } - - public func stopCommand() { - shellManager.sendCommand("\u{03}") // Send CTRL+C - } - - /** - * Handles input from the terminal view - * @param input Input received from terminal - */ - public func handleTerminalInput(_ input: String) { - DispatchQueue.main.async { [weak self] in - // Special handling for return/enter key - if input.contains("\r") { - self?.terminalOutput += "\n" - self?.shellManager.sendCommand("\n") - return - } - - // Echo the input to the terminal - self?.terminalOutput += input - self?.shellManager.sendCommand(input) - } - } - - public func getCommandOutput() -> String { - return self.pendingCommandResult - } - - /** - * Handles output from the shell process - * @param output Output from shell process - */ - private func handleShellOutput(_ output: String) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.terminalOutput += output - // Look for shell integration escape sequences - if output.contains("\u{1B}]133;D;0\u{07}") && self.hasPendingCommand { - // Command succeeded - self.onCommandCompleted?(CommandExecutionResult(success: true, output: self.pendingCommandResult)) - self.hasPendingCommand = false - } else if output.contains("\u{1B}]133;D;") && self.hasPendingCommand { - // Command failed - self.onCommandCompleted?(CommandExecutionResult(success: false, output: self.pendingCommandResult)) - self.hasPendingCommand = false - } else if output.contains("\u{1B}]133;C\u{07}") { - // Command start - } else if self.hasPendingCommand { - self.pendingCommandResult += output - } - } - } - - public func cleanup() { - shellManager.terminateShell() - } -} diff --git a/Tool/Sources/Terminal/TerminalSessionManager.swift b/Tool/Sources/Terminal/TerminalSessionManager.swift deleted file mode 100644 index 19fb9e6f..00000000 --- a/Tool/Sources/Terminal/TerminalSessionManager.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import Combine - -public class TerminalSessionManager { - public static let shared = TerminalSessionManager() - private var sessions: [String: TerminalSession] = [:] - - public func createSession(for terminalId: String) -> TerminalSession { - if let existingSession = sessions[terminalId] { - return existingSession - } else { - let newSession = TerminalSession() - sessions[terminalId] = newSession - return newSession - } - } - - public func getSession(for terminalId: String) -> TerminalSession? { - return sessions[terminalId] - } - - public func clearSession(for terminalId: String) { - sessions[terminalId]?.cleanup() - sessions.removeValue(forKey: terminalId) - } -} diff --git a/Tool/Sources/Workspace/WorkspaceFile.swift b/Tool/Sources/Workspace/WorkspaceFile.swift index df3cc8dc..bd1554ff 100644 --- a/Tool/Sources/Workspace/WorkspaceFile.swift +++ b/Tool/Sources/Workspace/WorkspaceFile.swift @@ -1,7 +1,6 @@ import Foundation import Logger import ConversationServiceProvider -import CopilotForXcodeKit public let supportedFileExtensions: Set = ["swift", "m", "mm", "h", "cpp", "c", "js", "py", "rb", "java", "applescript", "scpt", "plist", "entitlements", "md", "json", "xml", "txt", "yaml", "yml"] public let skipPatterns: [String] = [ @@ -15,10 +14,6 @@ public let skipPatterns: [String] = [ "bower_components" ] -public struct ProjectInfo { - public let uri: String - public let name: String -} public struct WorkspaceFile { @@ -96,31 +91,7 @@ public struct WorkspaceFile { } return false } - - public static func getProjects(workspace: WorkspaceInfo) -> [ProjectInfo] { - var subprojects: [ProjectInfo] = [] - if isXCWorkspace(workspace.workspaceURL) { - subprojects = getSubprojectURLs(in: workspace.workspaceURL).map( { projectURL in - ProjectInfo(uri: projectURL.absoluteString, name: getDisplayNameOfXcodeWorkspace(url: projectURL)) - }) - } else { - subprojects.append(ProjectInfo(uri: workspace.projectURL.absoluteString, name: getDisplayNameOfXcodeWorkspace(url: workspace.projectURL))) - } - return subprojects - } - - public static func getDisplayNameOfXcodeWorkspace(url: URL) -> String { - var name = url.lastPathComponent - let suffixes = [".xcworkspace", ".xcodeproj", ".playground"] - for suffix in suffixes { - if name.hasSuffix(suffix) { - name = String(name.dropLast(suffix.count)) - break - } - } - return name - } - + public static func getFilesInActiveWorkspace( workspaceURL: URL, workspaceRootURL: URL, diff --git a/Tool/Sources/XcodeInspector/AppInstanceInspector.swift b/Tool/Sources/XcodeInspector/AppInstanceInspector.swift index b842c3ba..1245d98f 100644 --- a/Tool/Sources/XcodeInspector/AppInstanceInspector.swift +++ b/Tool/Sources/XcodeInspector/AppInstanceInspector.swift @@ -2,7 +2,7 @@ import AppKit import Foundation public class AppInstanceInspector: ObservableObject { - public let runningApplication: NSRunningApplication + let runningApplication: NSRunningApplication public let processIdentifier: pid_t public let bundleURL: URL? public let bundleIdentifier: String? @@ -35,10 +35,6 @@ public class AppInstanceInspector: ObservableObject { public func activate() -> Bool { return runningApplication.activate() } - - public func activate(options: NSApplication.ActivationOptions) -> Bool { - return runningApplication.activate(options: options) - } init(runningApplication: NSRunningApplication) { self.runningApplication = runningApplication diff --git a/Tool/Tests/GitHubCopilotServiceTests/FetchSuggestionsTests.swift b/Tool/Tests/GitHubCopilotServiceTests/FetchSuggestionsTests.swift index face1f60..8bd81349 100644 --- a/Tool/Tests/GitHubCopilotServiceTests/FetchSuggestionsTests.swift +++ b/Tool/Tests/GitHubCopilotServiceTests/FetchSuggestionsTests.swift @@ -41,9 +41,6 @@ final class FetchSuggestionTests: XCTestCase { ), ]) as! E.Response } - func sendRequest(_: E, timeout: TimeInterval) async throws -> E.Response where E: GitHubCopilotRequestType { - return GitHubCopilotRequest.InlineCompletion.Response(items: []) as! E.Response - } } let service = GitHubCopilotSuggestionService(serviceLocator: TestServiceLocator(server: TestServer())) let completions = try await service.getSuggestions( @@ -83,10 +80,6 @@ final class FetchSuggestionTests: XCTestCase { ), ]) as! E.Response } - - func sendRequest(_ endpoint: E, timeout: TimeInterval) async throws -> E.Response where E : GitHubCopilotRequestType { - return GitHubCopilotRequest.InlineCompletion.Response(items: []) as! E.Response - } } let testServer = TestServer() let service = GitHubCopilotSuggestionService(serviceLocator: TestServiceLocator(server: testServer)) From 89b90b19ab02bbc658659be72754eb8a2529ca57 Mon Sep 17 00:00:00 2001 From: ROBOTVIPONE <191507985+hoangtu1sos1hot@users.noreply.github.com> Date: Sat, 17 May 2025 20:41:40 +0700 Subject: [PATCH 3/3] Update Locale.swift --- Tool/Sources/Preferences/Types/Locale.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tool/Sources/Preferences/Types/Locale.swift b/Tool/Sources/Preferences/Types/Locale.swift index 094bbc2b..87d18b51 100644 --- a/Tool/Sources/Preferences/Types/Locale.swift +++ b/Tool/Sources/Preferences/Types/Locale.swift @@ -3,6 +3,7 @@ import Foundation public extension Locale { static var availableLocalizedLocales: [String] { let localizedLocales = Locale.isoLanguageCodes.compactMap { + Locale(identifier: "vi-VN").localizedString(forLanguageCode: $0) } .sorted() return localizedLocales