Skip to content

Commit 9b4d638

Browse files
fix(amazonq): ChatCommunicationManager blocks messages to UI until ready (#5697)
sendContextCommands pulls in changes required for the context selection/provider prompts. The server makes this call as soon as it gets the config. There may be situations where context calls are made before we can display them in the browser. Messages received by the client are sent to the browser through the chat manager and blocked until the ui is ready.
1 parent 9979e79 commit 9b4d638

File tree

3 files changed

+59
-26
lines changed

3 files changed

+59
-26
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ class BrowserConnector(
140140
// this is sent when the named agents UI is ready
141141
"ui-is-ready" -> {
142142
uiReady.complete(true)
143+
chatCommunicationManager.setUiReady()
143144
RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") {
144145
MeetQSettings.getInstance().reinvent2024OnboardingCount += 1
145146
}
@@ -324,6 +325,7 @@ class BrowserConnector(
324325
CHAT_READY -> {
325326
handleChatNotification<ChatReadyNotification, Unit>(node) { server, _ ->
326327
uiReady.complete(true)
328+
chatCommunicationManager.setUiReady()
327329
RunOnceUtil.runOnceForApp("AmazonQ-UI-Ready") {
328330
MeetQSettings.getInstance().reinvent2024OnboardingCount += 1
329331
}
@@ -349,7 +351,7 @@ class BrowserConnector(
349351
}
350352
CHAT_OPEN_TAB -> {
351353
val response = serializer.deserializeChatMessages<OpenTabResponse>(node)
352-
ChatCommunicationManager.completeTabOpen(
354+
chatCommunicationManager.completeTabOpen(
353355
response.requestId,
354356
response.params.result.tabId
355357
)
@@ -420,7 +422,7 @@ class BrowserConnector(
420422

421423
GET_SERIALIZED_CHAT_REQUEST_METHOD -> {
422424
val response = serializer.deserializeChatMessages<GetSerializedChatResponse>(node)
423-
ChatCommunicationManager.completeSerializedChatResponse(
425+
chatCommunicationManager.completeSerializedChatResponse(
424426
response.requestId,
425427
response.params.result.content
426428
)

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,10 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
137137
override fun openTab(params: OpenTabParams): CompletableFuture<OpenTabResult> {
138138
val requestId = UUID.randomUUID().toString()
139139
val result = CompletableFuture<OpenTabResult>()
140-
ChatCommunicationManager.pendingTabRequests[requestId] = result
140+
val chatManager = ChatCommunicationManager.getInstance(project)
141+
chatManager.addTabOpenRequest(requestId, result)
141142

142-
AsyncChatUiListener.notifyPartialMessageUpdate(
143+
chatManager.notifyUi(
143144
FlareUiMessage(
144145
command = CHAT_OPEN_TAB,
145146
params = params,
@@ -149,7 +150,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
149150

150151
result.orTimeout(30000, TimeUnit.MILLISECONDS)
151152
.whenComplete { _, error ->
152-
ChatCommunicationManager.pendingTabRequests.remove(requestId)
153+
chatManager.removeTabOpenRequest(requestId)
153154
}
154155

155156
return result
@@ -188,10 +189,10 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
188189
override fun getSerializedChat(params: GetSerializedChatParams): CompletableFuture<GetSerializedChatResult> {
189190
val requestId = UUID.randomUUID().toString()
190191
val result = CompletableFuture<GetSerializedChatResult>()
192+
val chatManager = ChatCommunicationManager.getInstance(project)
193+
chatManager.addSerializedChatRequest(requestId, result)
191194

192-
ChatCommunicationManager.pendingSerializedChatRequests[requestId] = result
193-
194-
AsyncChatUiListener.notifyPartialMessageUpdate(
195+
chatManager.notifyUi(
195196
FlareUiMessage(
196197
command = GET_SERIALIZED_CHAT_REQUEST_METHOD,
197198
params = params,
@@ -201,7 +202,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
201202

202203
result.orTimeout(30000, TimeUnit.MILLISECONDS)
203204
.whenComplete { _, error ->
204-
ChatCommunicationManager.pendingSerializedChatRequests.remove(requestId)
205+
chatManager.removeSerializedChatRequest(requestId)
205206
}
206207

207208
return result
@@ -340,13 +341,13 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
340341
)
341342

342343
override fun sendContextCommands(params: LSPAny): CompletableFuture<Unit> {
343-
AsyncChatUiListener.notifyPartialMessageUpdate(
344+
val chatManager = ChatCommunicationManager.getInstance(project)
345+
chatManager.notifyUi(
344346
FlareUiMessage(
345347
command = CHAT_SEND_CONTEXT_COMMANDS,
346348
params = params ?: error("received empty payload for $CHAT_SEND_CONTEXT_COMMANDS"),
347349
)
348350
)
349-
350351
return CompletableFuture.completedFuture(Unit)
351352
}
352353

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

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import com.google.gson.Gson
77
import com.intellij.openapi.components.Service
88
import com.intellij.openapi.components.service
99
import com.intellij.openapi.project.Project
10+
import kotlinx.coroutines.CompletableDeferred
11+
import kotlinx.coroutines.CoroutineScope
12+
import kotlinx.coroutines.launch
1013
import org.eclipse.lsp4j.ProgressParams
1114
import software.aws.toolkits.core.utils.getLogger
1215
import software.aws.toolkits.core.utils.warn
@@ -29,12 +32,23 @@ import java.util.concurrent.CompletableFuture
2932
import java.util.concurrent.ConcurrentHashMap
3033

3134
@Service(Service.Level.PROJECT)
32-
class ChatCommunicationManager {
35+
class ChatCommunicationManager(private val cs: CoroutineScope) {
36+
val uiReady = CompletableDeferred<Boolean>()
3337
private val chatPartialResultMap = ConcurrentHashMap<String, String>()
34-
private fun getPartialChatMessage(partialResultToken: String): String? =
35-
chatPartialResultMap.getOrDefault(partialResultToken, null)
36-
3738
private val inflightRequestByTabId = ConcurrentHashMap<String, CompletableFuture<String>>()
39+
private val pendingSerializedChatRequests = ConcurrentHashMap<String, CompletableFuture<GetSerializedChatResult>>()
40+
private val pendingTabRequests = ConcurrentHashMap<String, CompletableFuture<OpenTabResult>>()
41+
42+
fun setUiReady() {
43+
uiReady.complete(true)
44+
}
45+
46+
fun notifyUi(uiMessage: FlareUiMessage) {
47+
cs.launch {
48+
uiReady.await()
49+
AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage)
50+
}
51+
}
3852

3953
fun setInflightRequestForTab(tabId: String, result: CompletableFuture<String>) {
4054
inflightRequestByTabId[tabId] = result
@@ -53,9 +67,36 @@ class ChatCommunicationManager {
5367
return partialResultToken
5468
}
5569

70+
private fun getPartialChatMessage(partialResultToken: String): String? =
71+
chatPartialResultMap.getOrDefault(partialResultToken, null)
72+
5673
fun removePartialChatMessage(partialResultToken: String) =
5774
chatPartialResultMap.remove(partialResultToken)
5875

76+
fun addSerializedChatRequest(requestId: String, result: CompletableFuture<GetSerializedChatResult>) {
77+
pendingSerializedChatRequests[requestId] = result
78+
}
79+
80+
fun completeSerializedChatResponse(requestId: String, content: String) {
81+
pendingSerializedChatRequests.remove(requestId)?.complete(GetSerializedChatResult((content)))
82+
}
83+
84+
fun removeSerializedChatRequest(requestId: String) {
85+
pendingSerializedChatRequests.remove(requestId)
86+
}
87+
88+
fun addTabOpenRequest(requestId: String, result: CompletableFuture<OpenTabResult>) {
89+
pendingTabRequests[requestId] = result
90+
}
91+
92+
fun completeTabOpen(requestId: String, tabId: String) {
93+
pendingTabRequests.remove(requestId)?.complete(OpenTabResult(tabId))
94+
}
95+
96+
fun removeTabOpenRequest(requestId: String) {
97+
pendingTabRequests.remove(requestId)
98+
}
99+
59100
fun handlePartialResultProgressNotification(project: Project, params: ProgressParams) {
60101
val token = ProgressNotificationUtils.getToken(params)
61102
val tabId = getPartialChatMessage(token)
@@ -134,11 +175,6 @@ class ChatCommunicationManager {
134175

135176
private val LOG = getLogger<ChatCommunicationManager>()
136177

137-
val pendingSerializedChatRequests = ConcurrentHashMap<String, CompletableFuture<GetSerializedChatResult>>()
138-
fun completeSerializedChatResponse(requestId: String, content: String) {
139-
pendingSerializedChatRequests.remove(requestId)?.complete(GetSerializedChatResult((content)))
140-
}
141-
142178
fun convertToJsonToSendToChat(command: String, tabId: String, params: String, isPartialResult: Boolean): String =
143179
"""
144180
{
@@ -148,11 +184,5 @@ class ChatCommunicationManager {
148184
"isPartialResult": $isPartialResult
149185
}
150186
""".trimIndent()
151-
152-
val pendingTabRequests = ConcurrentHashMap<String, CompletableFuture<OpenTabResult>>()
153-
154-
fun completeTabOpen(requestId: String, tabId: String) {
155-
pendingTabRequests.remove(requestId)?.complete(OpenTabResult(tabId))
156-
}
157187
}
158188
}

0 commit comments

Comments
 (0)