From bc7593947e4fd0e2c897461a90fde86f25759e12 Mon Sep 17 00:00:00 2001 From: Jordan Ang Date: Tue, 17 Sep 2024 15:59:31 -0700 Subject: [PATCH] Foundation of Chat communication with LSP (#15) --- .../chat/ChatCommunicationManager.java | 33 +++++++++++++++++ .../amazonq/chat/ChatMessageProvider.java | 33 +++++++++++++++++ .../amazonq/chat/models/ChatItemAction.java | 14 ++++++++ .../amazonq/chat/models/ChatPrompt.java | 11 ++++++ .../chat/models/ChatRequestParams.java | 11 ++++++ .../amazonq/chat/models/ChatResult.java | 14 ++++++++ .../eclipse/amazonq/chat/models/FollowUp.java | 11 ++++++ .../amazonq/chat/models/GenericTabParams.java | 10 ++++++ .../models/RecommendationContentSpan.java | 10 ++++++ .../models/ReferenceTrackerInformation.java | 13 +++++++ .../amazonq/chat/models/RelatedContent.java | 10 ++++++ .../amazonq/chat/models/SourceLink.java | 11 ++++++ .../eclipse/amazonq/lsp/AmazonQLspServer.java | 7 +++- .../eclipse/amazonq/util/JsonHandler.java | 35 +++++++++++++++++++ .../views/AmazonQChatViewActionHandler.java | 20 ++++++++--- .../amazonq/views/LoginViewCommandParser.java | 8 ++++- .../eclipse/amazonq/views/model/Command.java | 5 ++- .../amazonq/views/model/CommandRequest.java | 14 +++----- .../amazonq/views/model/ParsedCommand.java | 4 +-- 19 files changed, 253 insertions(+), 21 deletions(-) create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatCommunicationManager.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatMessageProvider.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatItemAction.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatPrompt.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatRequestParams.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatResult.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/FollowUp.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/GenericTabParams.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RecommendationContentSpan.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ReferenceTrackerInformation.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RelatedContent.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/SourceLink.java create mode 100644 plugin/src/software/aws/toolkits/eclipse/amazonq/util/JsonHandler.java diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatCommunicationManager.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatCommunicationManager.java new file mode 100644 index 00000000..0771d961 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatCommunicationManager.java @@ -0,0 +1,33 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat; + +import software.aws.toolkits.eclipse.amazonq.chat.models.GenericTabParams; +import software.aws.toolkits.eclipse.amazonq.util.JsonHandler; +import software.aws.toolkits.eclipse.amazonq.util.PluginLogger; +import software.aws.toolkits.eclipse.amazonq.views.model.Command; + +public final class ChatCommunicationManager { + + private final JsonHandler jsonHandler; + private final ChatMessageProvider chatMessageProivder; + + public ChatCommunicationManager() { + this.jsonHandler = new JsonHandler(); + this.chatMessageProivder = new ChatMessageProvider(); + } + + public void sendMessageToChatServerAsync(final Command command, final Object params) { + + String jsonParams = jsonHandler.serialize(params); + + switch (command) { + case CHAT_TAB_ADD: + GenericTabParams tabParams = jsonHandler.deserialize(jsonParams, GenericTabParams.class); + chatMessageProivder.sendTabAdd(tabParams); + break; + default: + PluginLogger.error("Unhandled chat command: " + command.toString()); + } + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatMessageProvider.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatMessageProvider.java new file mode 100644 index 00000000..9a663fa6 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/ChatMessageProvider.java @@ -0,0 +1,33 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat; + +import java.util.concurrent.ExecutionException; + +import software.aws.toolkits.eclipse.amazonq.chat.models.GenericTabParams; +import software.aws.toolkits.eclipse.amazonq.lsp.AmazonQLspServer; +import software.aws.toolkits.eclipse.amazonq.providers.LspProvider; +import software.aws.toolkits.eclipse.amazonq.util.PluginLogger; +import software.aws.toolkits.eclipse.amazonq.views.model.Command; + +public final class ChatMessageProvider { + + private AmazonQLspServer amazonQLspServer; + + public ChatMessageProvider() { + try { + amazonQLspServer = LspProvider.getAmazonQServer().get(); + } catch (InterruptedException | ExecutionException e) { + PluginLogger.error("Error occurred while retrieving Amazon Q LSP server. Failed to instantiate ChatMessageProvider."); + } + } + + public void sendTabAdd(final GenericTabParams tabParams) { + try { + PluginLogger.info("Sending " + Command.CHAT_TAB_ADD + " message to Amazon Q LSP server"); + amazonQLspServer.tabAdd(tabParams).get(); + } catch (InterruptedException | ExecutionException e) { + PluginLogger.error("Error occurred while sending message to Amazon Q LSP server for command " + Command.CHAT_TAB_ADD); + } + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatItemAction.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatItemAction.java new file mode 100644 index 00000000..77f088d3 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatItemAction.java @@ -0,0 +1,14 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ChatItemAction( + @JsonProperty("pillText") String pillText, + @JsonProperty("prompt") String prompt, + @JsonProperty("disabled") Boolean disabled, + @JsonProperty("description") String description, + @JsonProperty("type") String type +) { }; + diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatPrompt.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatPrompt.java new file mode 100644 index 00000000..b4480a39 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatPrompt.java @@ -0,0 +1,11 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ChatPrompt( + @JsonProperty("prompt") String prompt, + @JsonProperty("escapedPrompt") String escapedPrompt, + @JsonProperty("command") String command +) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatRequestParams.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatRequestParams.java new file mode 100644 index 00000000..89d50f74 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatRequestParams.java @@ -0,0 +1,11 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ChatRequestParams( + @JsonProperty("tabId") String tabId, + @JsonProperty("prompt") ChatPrompt prompt +) { } + diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatResult.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatResult.java new file mode 100644 index 00000000..632a9ff3 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ChatResult.java @@ -0,0 +1,14 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ChatResult( + @JsonProperty("body") String body, + @JsonProperty("messageId") String messageId, + @JsonProperty("canBeVoted") Boolean canBeVoted, + @JsonProperty("relatedContent") RelatedContent relatedContent, + @JsonProperty("followUp") FollowUp followUp, + @JsonProperty("codeReference") ReferenceTrackerInformation[] codeReference +) { }; diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/FollowUp.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/FollowUp.java new file mode 100644 index 00000000..dac71998 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/FollowUp.java @@ -0,0 +1,11 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record FollowUp( + @JsonProperty("text") String text, + @JsonProperty("options") ChatItemAction[] options +) { } + diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/GenericTabParams.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/GenericTabParams.java new file mode 100644 index 00000000..3455a73f --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/GenericTabParams.java @@ -0,0 +1,10 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record GenericTabParams( + @JsonProperty("tabId") String tabId +) { } + diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RecommendationContentSpan.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RecommendationContentSpan.java new file mode 100644 index 00000000..ede5bd47 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RecommendationContentSpan.java @@ -0,0 +1,10 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RecommendationContentSpan( + @JsonProperty("start") Integer start, + @JsonProperty("end") Integer end +) { }; diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ReferenceTrackerInformation.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ReferenceTrackerInformation.java new file mode 100644 index 00000000..3996e9a2 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/ReferenceTrackerInformation.java @@ -0,0 +1,13 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ReferenceTrackerInformation( + @JsonProperty("licenseName") String licenseName, + @JsonProperty("repository") String repository, + @JsonProperty("url") String url, + @JsonProperty("recommendationContentSpan") RecommendationContentSpan recommendationContentSpan, + @JsonProperty("information") String information +) { }; diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RelatedContent.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RelatedContent.java new file mode 100644 index 00000000..a6cb61b9 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/RelatedContent.java @@ -0,0 +1,10 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RelatedContent( + @JsonProperty("title") String title, + @JsonProperty("content") SourceLink[] content +) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/SourceLink.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/SourceLink.java new file mode 100644 index 00000000..5b1be756 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/chat/models/SourceLink.java @@ -0,0 +1,11 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.chat.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record SourceLink( + @JsonProperty("title") String title, + @JsonProperty("url") String url, + @JsonProperty("body") String body +) { }; diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java index 8934dae3..d152aaea 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/AmazonQLspServer.java @@ -1,5 +1,4 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 package software.aws.toolkits.eclipse.amazonq.lsp; @@ -7,8 +6,11 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.eclipse.lsp4j.services.LanguageServer; +import software.aws.toolkits.eclipse.amazonq.chat.models.ChatResult; +import software.aws.toolkits.eclipse.amazonq.chat.models.GenericTabParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionParams; import software.aws.toolkits.eclipse.amazonq.lsp.model.InlineCompletionResponse; import software.aws.toolkits.eclipse.amazonq.lsp.model.UpdateCredentialsPayload; @@ -18,6 +20,9 @@ public interface AmazonQLspServer extends LanguageServer { @JsonRequest("aws/textDocument/inlineCompletionWithReferences") CompletableFuture inlineCompletionWithReferences(InlineCompletionParams params); + @JsonNotification("aws/chat/tabAdd") + CompletableFuture tabAdd(GenericTabParams params); + @JsonRequest("aws/credentials/token/update") CompletableFuture updateTokenCredentials(UpdateCredentialsPayload payload); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/JsonHandler.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/JsonHandler.java new file mode 100644 index 00000000..cf9bbf27 --- /dev/null +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/JsonHandler.java @@ -0,0 +1,35 @@ +// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +package software.aws.toolkits.eclipse.amazonq.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public final class JsonHandler { + private final ObjectMapper objectMapper; + + public JsonHandler() { + this.objectMapper = ObjectMapperFactory.getInstance(); + } + + public String serialize(final Object obj) { + String serializedObj = null; + try { + serializedObj = objectMapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + PluginLogger.error("Error occurred while serializing object: " + obj.toString(), e); + return null; + } + return serializedObj; + } + + public T deserialize(final String jsonString, final Class cls) { + try { + T params = objectMapper.readValue(jsonString, cls); + return params; + } catch (JsonProcessingException e) { + PluginLogger.error("Error occurred while deserializing jsonString: " + jsonString, e); + } + return null; + } +} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatViewActionHandler.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatViewActionHandler.java index 9c31c98b..9a87fc40 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatViewActionHandler.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/AmazonQChatViewActionHandler.java @@ -1,26 +1,36 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 package software.aws.toolkits.eclipse.amazonq.views; import org.eclipse.swt.browser.Browser; +import software.aws.toolkits.eclipse.amazonq.chat.ChatCommunicationManager; import software.aws.toolkits.eclipse.amazonq.util.PluginLogger; +import software.aws.toolkits.eclipse.amazonq.views.model.Command; import software.aws.toolkits.eclipse.amazonq.views.model.ParsedCommand; public class AmazonQChatViewActionHandler implements ViewActionHandler { + private ChatCommunicationManager chatCommunicationManager; + + public AmazonQChatViewActionHandler() { + chatCommunicationManager = new ChatCommunicationManager(); + } + @Override public final void handleCommand(final ParsedCommand parsedCommand, final Browser browser) { - switch (parsedCommand.getCommand()) { + Command command = parsedCommand.getCommand(); + Object params = parsedCommand.getParams(); + + PluginLogger.info(command + " being processed by ActionHandler"); + + switch (command) { case CHAT_READY: - PluginLogger.info("Chat_ready command received"); break; case CHAT_TAB_ADD: - PluginLogger.info("Chat_tab_add command received with params " + parsedCommand.getParams().toString()); + chatCommunicationManager.sendMessageToChatServerAsync(command, params); break; case TELEMETRY_EVENT: - PluginLogger.info("Telemetry command received with params " + parsedCommand.getParams().toString()); break; default: PluginLogger.info("Unhandled command: " + parsedCommand.getCommand()); diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/LoginViewCommandParser.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/LoginViewCommandParser.java index 1d98c95f..e8a57af1 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/LoginViewCommandParser.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/LoginViewCommandParser.java @@ -26,7 +26,13 @@ public final Optional parseCommand(final Object[] arguments) { String jsonString = (String) arguments[0]; try { CommandRequest commandRequest = objectMapper.readValue(jsonString, CommandRequest.class); - return commandRequest.getParsedCommand(); + ParsedCommand parsedCommand = commandRequest.getParsedCommand(); + + if (parsedCommand.getCommand() == null) { + return Optional.empty(); + } + + return Optional.ofNullable(parsedCommand); } catch (JsonProcessingException e) { PluginLogger.error("Error parsing webview command JSON: " + e.getMessage()); } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/Command.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/Command.java index a3e3e675..6f0a6183 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/Command.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/Command.java @@ -1,5 +1,4 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 package software.aws.toolkits.eclipse.amazonq.views.model; @@ -33,4 +32,8 @@ public static Optional fromString(final String value) { PluginLogger.info("Unregistered command parsed: " + value); return Optional.empty(); } + + public String toString() { + return commandString; + } } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/CommandRequest.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/CommandRequest.java index 3528aeb7..64e4ace1 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/CommandRequest.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/CommandRequest.java @@ -1,22 +1,16 @@ // Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 package software.aws.toolkits.eclipse.amazonq.views.model; -import java.util.Optional; import com.fasterxml.jackson.annotation.JsonProperty; public record CommandRequest( @JsonProperty("command") String commandString, @JsonProperty("params") Object params) { - public Optional getParsedCommand() { - Optional command = Command.fromString(commandString); - if (!command.isPresent()) { - return Optional.empty(); - } - - ParsedCommand parsedCommand = new ParsedCommand(command.get(), Optional.ofNullable(params)); - return Optional.ofNullable(parsedCommand); + public ParsedCommand getParsedCommand() { + Command command = Command.fromString(commandString).orElse(null); + ParsedCommand parsedCommand = new ParsedCommand(command, params); + return parsedCommand; } } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/ParsedCommand.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/ParsedCommand.java index 6d07e645..2ea4a1d7 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/ParsedCommand.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/views/model/ParsedCommand.java @@ -2,14 +2,12 @@ package software.aws.toolkits.eclipse.amazonq.views.model; -import java.util.Optional; - public class ParsedCommand { private final Command command; private final Object params; - public ParsedCommand(final Command command, final Optional params) { + public ParsedCommand(final Command command, final Object params) { this.command = command; this.params = params; }