From 4af9d172b349814b8e8dc6f35d6ccaaf6fc47ad9 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 8 May 2025 17:53:33 -0700 Subject: [PATCH 1/5] fix(amazonq): do not model types that are directly passthrough to chat ui Due to mix of Jackson and Gson, history is not rendered correctly due to incorrect deserialization of enums --- .../amazonq/lsp/AmazonQLanguageClient.kt | 9 +++------ .../amazonq/lsp/AmazonQLanguageClientImpl.kt | 9 +++------ .../amazonq/lsp/model/aws/chat/ChatMessage.kt | 1 + .../lsp/model/aws/chat/ChatUpdateParams.kt | 10 ---------- .../amazonq/lsp/model/aws/chat/TabEvent.kt | 20 ------------------- 5 files changed, 7 insertions(+), 42 deletions(-) delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatUpdateParams.kt diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClient.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClient.kt index fe00aa835d6..33b51e73a21 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClient.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClient.kt @@ -9,13 +9,10 @@ import org.eclipse.lsp4j.services.LanguageClient import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LSPAny import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SEND_CONTEXT_COMMANDS import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SEND_UPDATE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatUpdateParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResult import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIFF import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenFileDiffParams -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResult import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogParams @@ -32,16 +29,16 @@ interface AmazonQLanguageClient : LanguageClient { fun getConnectionMetadata(): CompletableFuture @JsonRequest("aws/chat/openTab") - fun openTab(params: OpenTabParams): CompletableFuture + fun openTab(params: LSPAny): CompletableFuture @JsonRequest(SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD) fun showSaveFileDialog(params: ShowSaveFileDialogParams): CompletableFuture @JsonRequest(GET_SERIALIZED_CHAT_REQUEST_METHOD) - fun getSerializedChat(params: GetSerializedChatParams): CompletableFuture + fun getSerializedChat(params: LSPAny): CompletableFuture @JsonNotification(CHAT_SEND_UPDATE) - fun sendChatUpdate(params: ChatUpdateParams): CompletableFuture + fun sendChatUpdate(params: LSPAny): CompletableFuture @JsonNotification(OPEN_FILE_DIFF) fun openFileDiff(params: OpenFileDiffParams): CompletableFuture diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index aecf8692113..d839635a35f 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -37,12 +37,9 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LSPAny import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_OPEN_TAB import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SEND_CONTEXT_COMMANDS import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SEND_UPDATE -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatUpdateParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResult import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenFileDiffParams -import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResult import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogParams import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogResult @@ -133,7 +130,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC } } - override fun openTab(params: OpenTabParams): CompletableFuture { + override fun openTab(params: LSPAny): CompletableFuture { val requestId = UUID.randomUUID().toString() val result = CompletableFuture() ChatCommunicationManager.pendingTabRequests[requestId] = result @@ -185,7 +182,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC ) } - override fun getSerializedChat(params: GetSerializedChatParams): CompletableFuture { + override fun getSerializedChat(params: LSPAny): CompletableFuture { val requestId = UUID.randomUUID().toString() val result = CompletableFuture() @@ -261,7 +258,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC } } - override fun sendChatUpdate(params: ChatUpdateParams): CompletableFuture { + override fun sendChatUpdate(params: LSPAny): CompletableFuture { val uiMessage = """ { "command":"$CHAT_SEND_UPDATE", diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt index ac3251c4333..d42d9f1a76b 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt @@ -142,6 +142,7 @@ enum class ButtonStatus { ERROR, } +// https://github.com/aws/language-server-runtimes/blame/68319c975d29a8ba9b084c9fa780ebff75b286bb/types/chat.ts#L127 enum class MessageType { @JsonProperty("answer") ANSWER, diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatUpdateParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatUpdateParams.kt deleted file mode 100644 index a3dc3ed632d..00000000000 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatUpdateParams.kt +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat - -data class ChatUpdateParams( - val tabId: String, - val state: TabState? = null, - val data: TabData? = null, -) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabEvent.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabEvent.kt index faa504c3d8a..d5f3d8d1d31 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabEvent.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabEvent.kt @@ -12,11 +12,6 @@ data class TabEventParams( val tabId: String, ) -data class OpenTabParams( - val tabId: String? = null, - val newTabOptions: NewTabOptions? = null, -) - data class OpenTabResponse( val requestId: String, val command: String, @@ -31,18 +26,3 @@ data class OpenTabResponseParams( data class OpenTabResult( val tabId: String, ) - -data class NewTabOptions( - val state: TabState? = null, - val data: TabData? = null, -) - -data class TabState( - val inProgress: Boolean? = null, - val cancellable: Boolean? = null, -) - -data class TabData( - val placeholderText: String? = null, - val messages: List, -) From 855afd6a2e3d0c2800e5d246ceb8f40540b89913 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 8 May 2025 22:11:37 -0700 Subject: [PATCH 2/5] lint --- .../services/amazonq/lsp/AmazonQLanguageClientImpl.kt | 4 ++-- .../services/amazonq/lsp/model/aws/LspServerConfigurations.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index ced1e57dcca..3ebad43f8fa 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -261,7 +261,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC AsyncChatUiListener.notifyPartialMessageUpdate( FlareUiMessage( command = CHAT_SEND_UPDATE, - params = params ?: error("received empty payload for $CHAT_SEND_UPDATE"), + params = params, ) ) @@ -340,7 +340,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC AsyncChatUiListener.notifyPartialMessageUpdate( FlareUiMessage( command = CHAT_SEND_CONTEXT_COMMANDS, - params = params ?: error("received empty payload for $CHAT_SEND_CONTEXT_COMMANDS"), + params = params, ) ) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/LspServerConfigurations.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/LspServerConfigurations.kt index a0f23875b62..8d8b34cbbb3 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/LspServerConfigurations.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/LspServerConfigurations.kt @@ -14,4 +14,4 @@ data class UpdateConfigurationParams( val settings: LSPAny, ) -typealias LSPAny = Any? +typealias LSPAny = Any From 4d99a80bd783f109bb31e2254ca8cd6d1fbbf9c5 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 9 May 2025 11:37:09 -0700 Subject: [PATCH 3/5] tst --- .../lsp/flareChat/ChatCommunicationManager.kt | 8 +- .../amazonq/lsp/model/EnumJsonValueAdapter.kt | 42 +++++++ .../lsp/model/aws/chat/AuthFollowUpClicked.kt | 19 ++- .../amazonq/lsp/model/aws/chat/ChatMessage.kt | 118 ++++++------------ .../lsp/model/aws/chat/Conversations.kt | 39 ++---- .../model/aws/chat/GetSerializedChatParams.kt | 17 ++- .../lsp/model/aws/chat/TabBarActionParams.kt | 17 ++- .../lsp/model/aws/chat/ChatMessageTest.kt | 70 +++++++++++ 8 files changed, 188 insertions(+), 142 deletions(-) create mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt create mode 100644 plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt index 2fdb765ec53..6ab5337f258 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt @@ -108,14 +108,14 @@ class ChatCommunicationManager { reauthConnectionIfNeeded(project, it, isReAuth = true) } when (incomingType) { - AuthFollowupType.USE_SUPPORTED_AUTH.value -> { + AuthFollowupType.USE_SUPPORTED_AUTH -> { project.messageBus.syncPublisher(QRegionProfileSelectedListener.TOPIC) .onProfileSelected(project, QRegionProfileManager.getInstance().activeProfile(project)) return } - AuthFollowupType.RE_AUTH.value, - AuthFollowupType.MISSING_SCOPES.value, - AuthFollowupType.FULL_AUTH.value, + AuthFollowupType.RE_AUTH, + AuthFollowupType.MISSING_SCOPES, + AuthFollowupType.FULL_AUTH, -> { return } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt new file mode 100644 index 00000000000..cb7ce7710a9 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt @@ -0,0 +1,42 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.model + +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.Gson +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken + +/** + * A [Gson] [TypeAdapterFactory] that uses Jackson @[JsonValue] instead of [Enum.name] for de/serialization + */ +class EnumJsonValueAdapter : TypeAdapterFactory { + override fun create( + gson: Gson, + type: TypeToken + ): TypeAdapter? { + val rawType = type.getRawType() + if (!rawType.isEnum) { + return null + } + + val jsonField = rawType.declaredFields.firstOrNull { it.isAnnotationPresent(JsonValue::class.java) } + ?: return null + + jsonField.isAccessible = true + + return object : TypeAdapter() { + override fun write(out: com.google.gson.stream.JsonWriter, value: T) { + val result = jsonField.get(value) as Any + (gson.getAdapter(result::class.java) as TypeAdapter).write(out, result) + } + + override fun read(`in`: com.google.gson.stream.JsonReader): T { + val jsonValue = `in`.nextString() + return rawType.enumConstants.first { jsonField.get(it).toString() == jsonValue } as T + } + } as TypeAdapter + } +} diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt index f3d068947bd..deeec100587 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt @@ -3,6 +3,10 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.annotations.JsonAdapter +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.EnumJsonValueAdapter + data class AuthFollowUpClickNotification( override val command: String, override val params: AuthFollowUpClickedParams, @@ -11,21 +15,14 @@ data class AuthFollowUpClickNotification( data class AuthFollowUpClickedParams( val tabId: String, val messageId: String, - val authFollowupType: String, -) { - companion object { - fun create(tabId: String, messageId: String, authType: AuthFollowupType): AuthFollowUpClickedParams = - AuthFollowUpClickedParams(tabId, messageId, authType.value) - } -} + val authFollowupType: AuthFollowupType, +) -enum class AuthFollowupType(val value: String) { +@JsonAdapter(EnumJsonValueAdapter::class) +enum class AuthFollowupType(@JsonValue val repr: String) { FULL_AUTH("full-auth"), RE_AUTH("re-auth"), MISSING_SCOPES("missing_scopes"), USE_SUPPORTED_AUTH("use-supported-auth"), ; - - override fun toString(): String = - name.lowercase() } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt index d42d9f1a76b..775293432fd 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt @@ -3,7 +3,9 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat -import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.annotations.JsonAdapter +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.EnumJsonValueAdapter data class ChatMessage( val type: MessageType? = MessageType.ANSWER, @@ -69,93 +71,51 @@ data class Changes( val total: Int? = null, ) -enum class IconType { - @JsonProperty("file") - FILE, - - @JsonProperty("folder") - FOLDER, - - @JsonProperty("code-block") - CODE_BLOCK, - - @JsonProperty("list-add") - LIST_ADD, - - @JsonProperty("magic") - MAGIC, - - @JsonProperty("help") - HELP, - - @JsonProperty("trash") - TRASH, - - @JsonProperty("search") - SEARCH, - - @JsonProperty("calendar") - CALENDAR, +@JsonAdapter(EnumJsonValueAdapter::class) +enum class IconType(@JsonValue val repr: String) { + FILE("file"), + FOLDER("folder"), + CODE_BLOCK("code-block"), + LIST_ADD("list-add"), + MAGIC("magic"), + HELP("help"), + TRASH("trash"), + SEARCH("search"), + CALENDAR("calendar"), ; - companion object { - private val stringToEnum: Map = entries.associateBy { it.name.lowercase() } - - fun fromString(value: String): IconType = stringToEnum[value] ?: throw IllegalArgumentException("Unknown IconType: $value") - } } -enum class Status { - @JsonProperty("info") - INFO, - - @JsonProperty("success") - SUCCESS, - - @JsonProperty("warning") - WARNING, +@JsonAdapter(EnumJsonValueAdapter::class) +enum class Status(@JsonValue val repr: String) { + INFO("info"), + SUCCESS("success"), + WARNING("warning"), + ERROR("error"), + ; - @JsonProperty("error") - ERROR, } -enum class ButtonStatus { - @JsonProperty("main") - MAIN, - - @JsonProperty("primary") - PRIMARY, - - @JsonProperty("clear") - CLEAR, - - @JsonProperty("info") - INFO, - - @JsonProperty("success") - SUCCESS, - - @JsonProperty("warning") - WARNING, +@JsonAdapter(EnumJsonValueAdapter::class) +enum class ButtonStatus(@JsonValue val repr: String) { + MAIN("main"), + PRIMARY("primary"), + CLEAR("clear"), + INFO("info"), + SUCCESS("success"), + WARNING("warning"), + ERROR("error"), + ; - @JsonProperty("error") - ERROR, } // https://github.com/aws/language-server-runtimes/blame/68319c975d29a8ba9b084c9fa780ebff75b286bb/types/chat.ts#L127 -enum class MessageType { - @JsonProperty("answer") - ANSWER, - - @JsonProperty("prompt") - PROMPT, - - @JsonProperty("system-prompt") - SYSTEM_PROMPT, - - @JsonProperty("directive") - DIRECTIVE, - - @JsonProperty("tool") - TOOL, +@JsonAdapter(EnumJsonValueAdapter::class) +enum class MessageType(@JsonValue val repr: String) { + ANSWER("answer"), + PROMPT("prompt"), + SYSTEM_PROMPT("system-prompt"), + DIRECTIVE("directive"), + TOOL("tool"), + ; } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt index 87dd0e4fd50..4dc70fb11e1 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt @@ -3,8 +3,9 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.annotations.JsonAdapter +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.EnumJsonValueAdapter typealias FilterValue = String @@ -12,12 +13,7 @@ data class TextBasedFilterOption( val type: String, val placeholder: String?, val icon: IconType?, -) { - companion object { - fun create(type: TextInputType, placeholder: String, icon: IconType): TextBasedFilterOption = - TextBasedFilterOption(type.value, placeholder, icon) - } -} +) data class FilterOption( val id: String, @@ -61,43 +57,30 @@ data class ConversationsList( typealias ListConversationsResult = ConversationsList -enum class TextInputType(val value: String) { +@JsonAdapter(EnumJsonValueAdapter::class) +enum class TextInputType(@JsonValue val repr: String) { TEXTAREA("textarea"), TEXTINPUT("textinput"), ; - - override fun toString(): String = - name.lowercase() } -enum class ConversationAction(val value: String) { +@JsonAdapter(EnumJsonValueAdapter::class) +enum class ConversationAction(@JsonValue val repr: String) { DELETE("delete"), EXPORT("markdown"), ; - - override fun toString(): String = - name.lowercase() } + data class ConversationClickParams( val id: String, val action: String?, -) { - companion object { - fun create(id: String, action: ConversationAction): ConversationClickParams = - ConversationClickParams(id, action.value) - } -} +) data class ConversationClickResult( val id: String, val action: String?, val success: Boolean, -) { - companion object { - fun create(id: String, action: ConversationAction, success: Boolean): ConversationClickResult = - ConversationClickResult(id, action.value, success) - } -} +) data class ListConversationsRequest( override val command: String, diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt index d8750e9d591..9b4d90933a4 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt @@ -3,23 +3,20 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.annotations.JsonAdapter +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.EnumJsonValueAdapter + data class GetSerializedChatParams( val tabId: String, val format: String, -) { - companion object { - fun create(tabId: String, format: SerializedChatFormat): GetSerializedChatParams = - GetSerializedChatParams(tabId, format.value) - } -} +) -enum class SerializedChatFormat(val value: String) { +@JsonAdapter(EnumJsonValueAdapter::class) +enum class SerializedChatFormat(@JsonValue val repr: String) { HTML("html"), MARKDOWN("markdown"), ; - - override fun toString(): String = - name.lowercase() } data class GetSerializedChatResult( diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt index 1bbc445115c..6b96942b608 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt @@ -3,22 +3,19 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.annotations.JsonAdapter +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.EnumJsonValueAdapter + data class TabBarActionParams( val tabId: String?, val action: String, -) { - companion object { - fun create(tabId: String?, action: TabBarAction): TabBarActionParams = - TabBarActionParams(tabId, action.value) - } -} +) -enum class TabBarAction(val value: String) { +@JsonAdapter(EnumJsonValueAdapter::class) +enum class TabBarAction(@JsonValue val repr: String) { EXPORT("export"), ; - - override fun toString(): String = - name.lowercase() } data class TabBarActionResult( diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt new file mode 100644 index 00000000000..8061e0b59a8 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt @@ -0,0 +1,70 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.google.common.reflect.ClassPath +import com.google.gson.Gson +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.AutoCloseableSoftAssertions +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory +import software.aws.toolkits.jetbrains.utils.satisfiesKt +import java.util.stream.Stream +import kotlin.streams.asStream +import kotlin.test.Test + +class ChatMessageTest { + @Test + fun `sanity check`() { + val jackson = jacksonObjectMapper() + assertThat(IconType.CODE_BLOCK).satisfiesKt { + // language=JSON + val expected = """"code-block"""" + assertThat(Gson().toJson(it)).isEqualTo(expected) + assertThat(jackson.writeValueAsString(it)).isEqualTo(expected) + + assertThat(Gson().fromJson(expected, IconType::class.java)).isEqualTo(it) + assertThat(jackson.readValue(expected)).isEqualTo(it) + + } + } + + @TestFactory + fun `enum is compatible between Jackson and Gson`(): Stream = sequence { + val jackson = jacksonObjectMapper() + ClassPath.from(Thread.currentThread().contextClassLoader) + .getTopLevelClassesRecursive("software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat") + .filter { it.load().isEnum } + .forEach { enumClass -> + val clazz = enumClass.load() + val enumValues = clazz.enumConstants as Array> + enumValues.forEach { enumValue -> + val enumFqn = enumClass.name + // jackson is more straight forward so assume that it is probably the correct repr + val jacksonJson = jackson.writeValueAsString(enumValue) + + yield(DynamicTest.dynamicTest("$enumFqn.${enumValue.name}") { + AutoCloseableSoftAssertions().use { softly -> + val jacksonRead = jackson.readValue(jacksonJson, clazz) + softly.assertThat(jacksonRead) + .describedAs { "Jackson roundtrip $enumFqn: expecting ${enumValue.name}" } + .isEqualTo(enumValue) + + val gsonRead = Gson().fromJson(jacksonJson, clazz) + softly.assertThat(gsonRead) + .describedAs { "Gson deserialize $enumFqn: expecting ${enumValue.name}" } + .isEqualTo(enumValue) + + val gsonWrite = Gson().toJson(enumValue) + softly.assertThat(gsonWrite) + .describedAs { "Gson serialize $enumFqn: expecting $jacksonJson" } + .isEqualTo(jacksonJson) + } + }) + } + } + }.asStream() +} From ebf94f965bc7339ba2a601ed7e6a8667d4a94b81 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 9 May 2025 12:20:50 -0700 Subject: [PATCH 4/5] tst --- .../amazonq/lsp/model/EnumJsonValueAdapter.kt | 4 +-- .../lsp/model/aws/chat/AuthFollowUpClicked.kt | 1 - .../amazonq/lsp/model/aws/chat/ChatMessage.kt | 7 ---- .../lsp/model/aws/chat/Conversations.kt | 2 -- .../model/aws/chat/GetSerializedChatParams.kt | 1 - .../lsp/model/aws/chat/TabBarActionParams.kt | 1 - .../lsp/model/aws/chat/ChatMessageTest.kt | 35 ++++++++++--------- 7 files changed, 20 insertions(+), 31 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt index cb7ce7710a9..a2ad96ec3ea 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/EnumJsonValueAdapter.kt @@ -1,6 +1,6 @@ // Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - +@file:Suppress("BannedImports") package software.aws.toolkits.jetbrains.services.amazonq.lsp.model import com.fasterxml.jackson.annotation.JsonValue @@ -15,7 +15,7 @@ import com.google.gson.reflect.TypeToken class EnumJsonValueAdapter : TypeAdapterFactory { override fun create( gson: Gson, - type: TypeToken + type: TypeToken, ): TypeAdapter? { val rawType = type.getRawType() if (!rawType.isEnum) { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt index deeec100587..33ab663e0d2 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/AuthFollowUpClicked.kt @@ -24,5 +24,4 @@ enum class AuthFollowupType(@JsonValue val repr: String) { RE_AUTH("re-auth"), MISSING_SCOPES("missing_scopes"), USE_SUPPORTED_AUTH("use-supported-auth"), - ; } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt index 775293432fd..2729bb3451a 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessage.kt @@ -82,8 +82,6 @@ enum class IconType(@JsonValue val repr: String) { TRASH("trash"), SEARCH("search"), CALENDAR("calendar"), - ; - } @JsonAdapter(EnumJsonValueAdapter::class) @@ -92,8 +90,6 @@ enum class Status(@JsonValue val repr: String) { SUCCESS("success"), WARNING("warning"), ERROR("error"), - ; - } @JsonAdapter(EnumJsonValueAdapter::class) @@ -105,8 +101,6 @@ enum class ButtonStatus(@JsonValue val repr: String) { SUCCESS("success"), WARNING("warning"), ERROR("error"), - ; - } // https://github.com/aws/language-server-runtimes/blame/68319c975d29a8ba9b084c9fa780ebff75b286bb/types/chat.ts#L127 @@ -117,5 +111,4 @@ enum class MessageType(@JsonValue val repr: String) { SYSTEM_PROMPT("system-prompt"), DIRECTIVE("directive"), TOOL("tool"), - ; } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt index 4dc70fb11e1..81ff35f8751 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/Conversations.kt @@ -61,14 +61,12 @@ typealias ListConversationsResult = ConversationsList enum class TextInputType(@JsonValue val repr: String) { TEXTAREA("textarea"), TEXTINPUT("textinput"), - ; } @JsonAdapter(EnumJsonValueAdapter::class) enum class ConversationAction(@JsonValue val repr: String) { DELETE("delete"), EXPORT("markdown"), - ; } data class ConversationClickParams( diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt index 9b4d90933a4..476b8c76836 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GetSerializedChatParams.kt @@ -16,7 +16,6 @@ data class GetSerializedChatParams( enum class SerializedChatFormat(@JsonValue val repr: String) { HTML("html"), MARKDOWN("markdown"), - ; } data class GetSerializedChatResult( diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt index 6b96942b608..e597ef8aee3 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/TabBarActionParams.kt @@ -15,7 +15,6 @@ data class TabBarActionParams( @JsonAdapter(EnumJsonValueAdapter::class) enum class TabBarAction(@JsonValue val repr: String) { EXPORT("export"), - ; } data class TabBarActionResult( diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt index 8061e0b59a8..bf1d7e52e44 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt @@ -1,6 +1,6 @@ // Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - +@file:Suppress("BannedImports") package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper @@ -28,7 +28,6 @@ class ChatMessageTest { assertThat(Gson().fromJson(expected, IconType::class.java)).isEqualTo(it) assertThat(jackson.readValue(expected)).isEqualTo(it) - } } @@ -46,24 +45,26 @@ class ChatMessageTest { // jackson is more straight forward so assume that it is probably the correct repr val jacksonJson = jackson.writeValueAsString(enumValue) - yield(DynamicTest.dynamicTest("$enumFqn.${enumValue.name}") { - AutoCloseableSoftAssertions().use { softly -> - val jacksonRead = jackson.readValue(jacksonJson, clazz) - softly.assertThat(jacksonRead) - .describedAs { "Jackson roundtrip $enumFqn: expecting ${enumValue.name}" } - .isEqualTo(enumValue) + yield( + DynamicTest.dynamicTest("$enumFqn.${enumValue.name}") { + AutoCloseableSoftAssertions().use { softly -> + val jacksonRead = jackson.readValue(jacksonJson, clazz) + softly.assertThat(jacksonRead) + .describedAs { "Jackson roundtrip $enumFqn: expecting ${enumValue.name}" } + .isEqualTo(enumValue) - val gsonRead = Gson().fromJson(jacksonJson, clazz) - softly.assertThat(gsonRead) - .describedAs { "Gson deserialize $enumFqn: expecting ${enumValue.name}" } - .isEqualTo(enumValue) + val gsonRead = Gson().fromJson(jacksonJson, clazz) + softly.assertThat(gsonRead) + .describedAs { "Gson deserialize $enumFqn: expecting ${enumValue.name}" } + .isEqualTo(enumValue) - val gsonWrite = Gson().toJson(enumValue) - softly.assertThat(gsonWrite) - .describedAs { "Gson serialize $enumFqn: expecting $jacksonJson" } - .isEqualTo(jacksonJson) + val gsonWrite = Gson().toJson(enumValue) + softly.assertThat(gsonWrite) + .describedAs { "Gson serialize $enumFqn: expecting $jacksonJson" } + .isEqualTo(jacksonJson) + } } - }) + ) } } }.asStream() From 1ed1c5e06d00aa27dcdcf130a096d2223772fd7a Mon Sep 17 00:00:00 2001 From: Richard Li Date: Fri, 9 May 2025 13:25:38 -0700 Subject: [PATCH 5/5] tst --- .../amazonq/lsp/model/aws/chat/GenericCommandParams.kt | 7 ++++++- .../services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GenericCommandParams.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GenericCommandParams.kt index 5cdea4dc84f..7b861657348 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GenericCommandParams.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/GenericCommandParams.kt @@ -3,8 +3,13 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat +import com.fasterxml.jackson.annotation.JsonValue +import com.google.gson.annotations.JsonAdapter +import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.EnumJsonValueAdapter + // https://github.com/aws/language-server-runtimes/blob/main/chat-client-ui-types/src/uiContracts.ts#L27 -enum class TriggerType(val value: String) { +@JsonAdapter(EnumJsonValueAdapter::class) +enum class TriggerType(@JsonValue val repr: String) { HOTKEYS("hotkeys"), CLICK("click"), CONTEXT_MENU("contextMenu"), diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt index bf1d7e52e44..4c713d98b84 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/model/aws/chat/ChatMessageTest.kt @@ -47,6 +47,8 @@ class ChatMessageTest { yield( DynamicTest.dynamicTest("$enumFqn.${enumValue.name}") { + println("$enumFqn -> $jacksonJson") + AutoCloseableSoftAssertions().use { softly -> val jacksonRead = jackson.readValue(jacksonJson, clazz) softly.assertThat(jacksonRead)