Skip to content

fix(amazonq): avoid using modeled types in chat message proxy #5710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class MessageSerializer @VisibleForTesting constructor() {
inline fun <reified T> deserializeChatMessages(value: JsonNode): T =
objectMapper.treeToValue<T>(value)

inline fun <reified T> deserializeChatMessages(value: JsonNode, clazz: Class<T>): T =
objectMapper.treeToValue(value, clazz)

// Provide singleton global access
companion object {
private val instance = MessageSerializer()
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// 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

import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethodProvider
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickResult
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_BUTTON_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CONVERSATION_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_COPY_CODE_TO_CLIPBOARD_NOTIFICATION
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CREATE_PROMPT
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FEEDBACK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FILE_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FOLLOW_UP_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INFO_LINK_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INSERT_TO_CURSOR_NOTIFICATION
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LINK_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LIST_CONVERSATIONS
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_QUICK_ACTION
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_READY
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SOURCE_LINK_CLICK
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_ADD
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ConversationClickParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CopyCodeToClipboardParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CreatePromptParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.FeedbackParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.FileClickParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.FollowUpClickParams
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.InfoLinkClickParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.InsertToCursorPositionParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LinkClickParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ListConversationsParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.PROMPT_INPUT_OPTIONS_CHANGE
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.PromptInputOptionChangeParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_CHAT_COMMAND_PROMPT
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SourceLinkClickParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TabBarActionParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TabEventParams
import kotlin.reflect.KProperty
import kotlin.reflect.full.declaredMembers

sealed interface JsonRpcMethod<Request, Response> {
val name: String
val params: Class<Request>
}

data class JsonRpcNotification<Request>(
override val name: String,
override val params: Class<Request>,
) : JsonRpcMethod<Request, Unit>

@Suppress("FunctionNaming")
fun JsonRpcNotification(name: String) = JsonRpcNotification(name, Unit::class.java)

data class JsonRpcRequest<Request, Response>(
override val name: String,
override val params: Class<Request>,
val response: Class<Response>,
) : JsonRpcMethod<Request, Response>

/**
* Messaging for the Chat feature follows this pattern:
* Mynah-UI <-> Plugin <-> Flare LSP
*
* However, the default scenario is that the plugin only cares about a subset of request/response payload and should otherwise transparently passthrough data.
* To obtain some semblance of type safety, we model the subset of values that are relevant and passthrough the rest.
*
* Generally, methods MUST be modeled here if the response type is needed, or LSP4J will return null
*/
object AmazonQChatServer : JsonRpcMethodProvider {
override fun supportedMethods() = buildMap {
AmazonQChatServer::class.declaredMembers.filter { it is KProperty }.forEach {
val method = it.call(AmazonQChatServer) as JsonRpcMethod<*, *>

// trick lsp4j into returning the complete message even if we didn't model it completely
val lsp4jMethod = when (method) {
is JsonRpcNotification<*> -> org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod.notification(method.name, Any::class.java)
is JsonRpcRequest<*, *> -> org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod.request(method.name, Any::class.java, Any::class.java)
}

put(method.name, lsp4jMethod)
}
}

val sendChatPrompt = JsonRpcRequest(

Check warning on line 94 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQChatServer.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "sendChatPrompt" is never used

Check warning

Code scanning / QDJVMC

Unused symbol Warning

Property "sendChatPrompt" is never used
SEND_CHAT_COMMAND_PROMPT,
EncryptedChatParams::class.java,
String::class.java
)

val sendQuickAction = JsonRpcRequest(

Check warning on line 100 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQChatServer.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "sendQuickAction" is never used

Check warning

Code scanning / QDJVMC

Unused symbol Warning

Property "sendQuickAction" is never used
CHAT_QUICK_ACTION,
EncryptedQuickActionChatParams::class.java,
String::class.java
)

val copyCodeToClipboard = JsonRpcNotification(
CHAT_COPY_CODE_TO_CLIPBOARD_NOTIFICATION,
CopyCodeToClipboardParams::class.java,
)

val chatReady = JsonRpcNotification(
CHAT_READY,
)

val tabAdd = JsonRpcNotification(
CHAT_TAB_ADD,
TabEventParams::class.java
)

val tabRemove = JsonRpcNotification(
CHAT_TAB_REMOVE,
TabEventParams::class.java
)

val tabChange = JsonRpcNotification(
CHAT_TAB_CHANGE,
TabEventParams::class.java
)

val feedback = JsonRpcNotification(
CHAT_FEEDBACK,
FeedbackParams::class.java
)

val insertToCursorPosition = JsonRpcNotification(
CHAT_INSERT_TO_CURSOR_NOTIFICATION,
InsertToCursorPositionParams::class.java
)

val linkClick = JsonRpcNotification(
CHAT_LINK_CLICK,
LinkClickParams::class.java
)

val infoLinkClick = JsonRpcNotification(
CHAT_INFO_LINK_CLICK,
InfoLinkClickParams::class.java
)

val sourceLinkClick = JsonRpcNotification(
CHAT_SOURCE_LINK_CLICK,
SourceLinkClickParams::class.java
)

val promptInputOptionsChange = JsonRpcNotification(
PROMPT_INPUT_OPTIONS_CHANGE,
PromptInputOptionChangeParams::class.java
)

val followUpClick = JsonRpcNotification(
CHAT_FOLLOW_UP_CLICK,
FollowUpClickParams::class.java
)

val fileClick = JsonRpcNotification(
CHAT_FILE_CLICK,
FileClickParams::class.java
)

val listConversations = JsonRpcRequest(
CHAT_LIST_CONVERSATIONS,
ListConversationsParams::class.java,
Any::class.java
)

val conversationClick = JsonRpcRequest(
CHAT_CONVERSATION_CLICK,
ConversationClickParams::class.java,
Any::class.java
)

val buttonClick = JsonRpcRequest(
CHAT_BUTTON_CLICK,
ButtonClickParams::class.java,
ButtonClickResult::class.java
)

val tabBarActions = JsonRpcRequest(
CHAT_TAB_BAR_ACTIONS,
TabBarActionParams::class.java,
Any::class.java
)

val getSerializedActions = JsonRpcRequest(

Check warning on line 194 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQChatServer.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Property "getSerializedActions" is never used

Check warning

Code scanning / QDJVMC

Unused symbol Warning

Property "getSerializedActions" is never used
GET_SERIALIZED_CHAT_REQUEST_METHOD,
GetSerializedChatParams::class.java,
GetSerializedChatResult::class.java
)

val createPrompt = JsonRpcNotification(
CHAT_CREATE_PROMPT,
CreatePromptParams::class.java
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
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_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.GET_SERIALIZED_CHAT_REQUEST_METHOD
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.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
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogResult
Expand All @@ -28,8 +28,8 @@ interface AmazonQLanguageClient : LanguageClient {
@JsonRequest("aws/credentials/getConnectionMetadata")
fun getConnectionMetadata(): CompletableFuture<ConnectionMetadata>

@JsonRequest("aws/chat/openTab")
fun openTab(params: LSPAny): CompletableFuture<OpenTabResult>
@JsonRequest(CHAT_OPEN_TAB)
fun openTab(params: LSPAny): CompletableFuture<LSPAny>

@JsonRequest(SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD)
fun showSaveFileDialog(params: ShowSaveFileDialogParams): CompletableFuture<ShowSaveFileDialogResult>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_
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.GetSerializedChatResult
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenFileDiffParams
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
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata
Expand Down Expand Up @@ -140,9 +139,9 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
}
}

override fun openTab(params: LSPAny): CompletableFuture<OpenTabResult> {
override fun openTab(params: LSPAny): CompletableFuture<LSPAny> {
val requestId = UUID.randomUUID().toString()
val result = CompletableFuture<OpenTabResult>()
val result = CompletableFuture<LSPAny>()
val chatManager = ChatCommunicationManager.getInstance(project)
chatManager.addTabOpenRequest(requestId, result)

Expand Down
Loading
Loading