Skip to content

Commit 54fa53b

Browse files
committed
fix(amazonq): avoid using modeled types in chat message proxy
There is a high possibiltiy of drift in our modeling as the UI <-> Server evolves. To avoid a treadmill in making sure these are always up to date, avoid materializing these messages into concrete types
1 parent 67bd41c commit 54fa53b

File tree

11 files changed

+418
-310
lines changed

11 files changed

+418
-310
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/commands/MessageSerializer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class MessageSerializer @VisibleForTesting constructor() {
4040
inline fun <reified T> deserializeChatMessages(value: JsonNode): T =
4141
objectMapper.treeToValue<T>(value)
4242

43+
inline fun <reified T> deserializeChatMessages(value: JsonNode, clazz: Class<T>): T =
44+
objectMapper.treeToValue(value, clazz)
45+
4346
// Provide singleton global access
4447
companion object {
4548
private val instance = MessageSerializer()

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

Lines changed: 166 additions & 166 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp
5+
6+
import org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethodProvider
7+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickParams
8+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickResult
9+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_BUTTON_CLICK
10+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CONVERSATION_CLICK
11+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_COPY_CODE_TO_CLIPBOARD_NOTIFICATION
12+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_CREATE_PROMPT
13+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FEEDBACK
14+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FILE_CLICK
15+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_FOLLOW_UP_CLICK
16+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INFO_LINK_CLICK
17+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_INSERT_TO_CURSOR_NOTIFICATION
18+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LINK_CLICK
19+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_LIST_CONVERSATIONS
20+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_QUICK_ACTION
21+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_READY
22+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SOURCE_LINK_CLICK
23+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_ADD
24+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS
25+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE
26+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE
27+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ConversationClickParams
28+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CopyCodeToClipboardParams
29+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CreatePromptParams
30+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams
31+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams
32+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.FeedbackParams
33+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.FileClickParams
34+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.FollowUpClickParams
35+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
36+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatParams
37+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResult
38+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.InfoLinkClickParams
39+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.InsertToCursorPositionParams
40+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.LinkClickParams
41+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ListConversationsParams
42+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.PROMPT_INPUT_OPTIONS_CHANGE
43+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.PromptInputOptionChangeParams
44+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_CHAT_COMMAND_PROMPT
45+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SourceLinkClickParams
46+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TabBarActionParams
47+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TabEventParams
48+
import kotlin.reflect.KProperty
49+
import kotlin.reflect.full.declaredMembers
50+
51+
sealed interface JsonRpcMethod<Request, Response> {
52+
val name: String
53+
val params: Class<Request>
54+
}
55+
56+
data class JsonRpcNotification<Request>(
57+
override val name: String,
58+
override val params: Class<Request>,
59+
) : JsonRpcMethod<Request, Unit>
60+
61+
@Suppress("FunctionNaming")
62+
fun JsonRpcNotification(name: String) = JsonRpcNotification(name, Unit::class.java)
63+
64+
data class JsonRpcRequest<Request, Response>(
65+
override val name: String,
66+
override val params: Class<Request>,
67+
val response: Class<Response>,
68+
) : JsonRpcMethod<Request, Response>
69+
70+
/**
71+
* Messaging for the Chat feature follows this pattern:
72+
* Mynah-UI <-> Plugin <-> Flare LSP
73+
*
74+
* However, the default scenario is that the plugin only cares about a subset of request/response payload and should otherwise transparently passthrough data.
75+
* To obtain some semblance of type safety, we model the subset of values that are relevant and passthrough the rest.
76+
*
77+
* Generally, methods MUST be modeled here if the response type is needed, or LSP4J will return null
78+
*/
79+
object AmazonQChatServer : JsonRpcMethodProvider {
80+
override fun supportedMethods() = buildMap {
81+
AmazonQChatServer::class.declaredMembers.filter { it is KProperty }.forEach {
82+
val method = it.call(AmazonQChatServer) as JsonRpcMethod<*, *>
83+
84+
// trick lsp4j into returning the complete message even if we didn't model it completely
85+
val lsp4jMethod = when (method) {
86+
is JsonRpcNotification<*> -> org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod.notification(method.name, Any::class.java)
87+
is JsonRpcRequest<*, *> -> org.eclipse.lsp4j.jsonrpc.json.JsonRpcMethod.request(method.name, Any::class.java, Any::class.java)
88+
}
89+
90+
put(method.name, lsp4jMethod)
91+
}
92+
}
93+
94+
val sendChatPrompt = JsonRpcRequest(
95+
SEND_CHAT_COMMAND_PROMPT,
96+
EncryptedChatParams::class.java,
97+
String::class.java
98+
)
99+
100+
val sendQuickAction = JsonRpcRequest(
101+
CHAT_QUICK_ACTION,
102+
EncryptedQuickActionChatParams::class.java,
103+
String::class.java
104+
)
105+
106+
val copyCodeToClipboard = JsonRpcNotification(
107+
CHAT_COPY_CODE_TO_CLIPBOARD_NOTIFICATION,
108+
CopyCodeToClipboardParams::class.java,
109+
)
110+
111+
val chatReady = JsonRpcNotification(
112+
CHAT_READY,
113+
)
114+
115+
val tabAdd = JsonRpcNotification(
116+
CHAT_TAB_ADD,
117+
TabEventParams::class.java
118+
)
119+
120+
val tabRemove = JsonRpcNotification(
121+
CHAT_TAB_REMOVE,
122+
TabEventParams::class.java
123+
)
124+
125+
val tabChange = JsonRpcNotification(
126+
CHAT_TAB_CHANGE,
127+
TabEventParams::class.java
128+
)
129+
130+
val feedback = JsonRpcNotification(
131+
CHAT_FEEDBACK,
132+
FeedbackParams::class.java
133+
)
134+
135+
val insertToCursorPosition = JsonRpcNotification(
136+
CHAT_INSERT_TO_CURSOR_NOTIFICATION,
137+
InsertToCursorPositionParams::class.java
138+
)
139+
140+
val linkClick = JsonRpcNotification(
141+
CHAT_LINK_CLICK,
142+
LinkClickParams::class.java
143+
)
144+
145+
val infoLinkClick = JsonRpcNotification(
146+
CHAT_INFO_LINK_CLICK,
147+
InfoLinkClickParams::class.java
148+
)
149+
150+
val sourceLinkClick = JsonRpcNotification(
151+
CHAT_SOURCE_LINK_CLICK,
152+
SourceLinkClickParams::class.java
153+
)
154+
155+
val promptInputOptionsChange = JsonRpcNotification(
156+
PROMPT_INPUT_OPTIONS_CHANGE,
157+
PromptInputOptionChangeParams::class.java
158+
)
159+
160+
val followUpClick = JsonRpcNotification(
161+
CHAT_FOLLOW_UP_CLICK,
162+
FollowUpClickParams::class.java
163+
)
164+
165+
val fileClick = JsonRpcNotification(
166+
CHAT_FILE_CLICK,
167+
FileClickParams::class.java
168+
)
169+
170+
val listConversations = JsonRpcRequest(
171+
CHAT_LIST_CONVERSATIONS,
172+
ListConversationsParams::class.java,
173+
Any::class.java
174+
)
175+
176+
val conversationClick = JsonRpcRequest(
177+
CHAT_CONVERSATION_CLICK,
178+
ConversationClickParams::class.java,
179+
Any::class.java
180+
)
181+
182+
val buttonClick = JsonRpcRequest(
183+
CHAT_BUTTON_CLICK,
184+
ButtonClickParams::class.java,
185+
ButtonClickResult::class.java
186+
)
187+
188+
val tabBarActions = JsonRpcRequest(
189+
CHAT_TAB_BAR_ACTIONS,
190+
TabBarActionParams::class.java,
191+
Any::class.java
192+
)
193+
194+
val getSerializedActions = JsonRpcRequest(
195+
GET_SERIALIZED_CHAT_REQUEST_METHOD,
196+
GetSerializedChatParams::class.java,
197+
GetSerializedChatResult::class.java
198+
)
199+
200+
val createPrompt = JsonRpcNotification(
201+
CHAT_CREATE_PROMPT,
202+
CreatePromptParams::class.java
203+
)
204+
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClient.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification
77
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
88
import org.eclipse.lsp4j.services.LanguageClient
99
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.LSPAny
10+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_OPEN_TAB
1011
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SEND_CONTEXT_COMMANDS
1112
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_SEND_UPDATE
1213
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
1314
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResult
1415
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OPEN_FILE_DIFF
1516
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenFileDiffParams
16-
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResult
1717
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD
1818
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogParams
1919
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogResult
@@ -28,8 +28,8 @@ interface AmazonQLanguageClient : LanguageClient {
2828
@JsonRequest("aws/credentials/getConnectionMetadata")
2929
fun getConnectionMetadata(): CompletableFuture<ConnectionMetadata>
3030

31-
@JsonRequest("aws/chat/openTab")
32-
fun openTab(params: LSPAny): CompletableFuture<OpenTabResult>
31+
@JsonRequest(CHAT_OPEN_TAB)
32+
fun openTab(params: LSPAny): CompletableFuture<LSPAny>
3333

3434
@JsonRequest(SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD)
3535
fun showSaveFileDialog(params: ShowSaveFileDialogParams): CompletableFuture<ShowSaveFileDialogResult>

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_
4242
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
4343
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GetSerializedChatResult
4444
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenFileDiffParams
45-
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.OpenTabResult
4645
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogParams
4746
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ShowSaveFileDialogResult
4847
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.credentials.ConnectionMetadata
@@ -140,9 +139,9 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
140139
}
141140
}
142141

143-
override fun openTab(params: LSPAny): CompletableFuture<OpenTabResult> {
142+
override fun openTab(params: LSPAny): CompletableFuture<LSPAny> {
144143
val requestId = UUID.randomUUID().toString()
145-
val result = CompletableFuture<OpenTabResult>()
144+
val result = CompletableFuture<LSPAny>()
146145
val chatManager = ChatCommunicationManager.getInstance(project)
147146
chatManager.addTabOpenRequest(requestId, result)
148147

0 commit comments

Comments
 (0)