Skip to content

refactor: avoid manually crafting JSON blobs for postMessage(...) #5693

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 8 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -27,6 +27,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager
Expand Down Expand Up @@ -55,9 +56,15 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di
project.messageBus.connect().subscribe(
AsyncChatUiListener.TOPIC,
object : AsyncChatUiListener {
override fun onChange(message: String) {
override fun onChange(command: String) {
runInEdt {
browser.get()?.postChat(message)
browser.get()?.postChat(command)
}
}

override fun onChange(command: FlareUiMessage) {
runInEdt {
browser.get()?.postChat(command)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
package software.aws.toolkits.jetbrains.services.amazonq.webview

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.google.gson.Gson
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.jcef.JBCefJSQuery
import org.cef.CefApp
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsServerCapabilitiesProvider
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfile
import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand
import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser
Expand Down Expand Up @@ -62,6 +64,9 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)

fun component() = jcefBrowser.component

fun postChat(command: FlareUiMessage) = postChat(Gson().toJson(command))

@Deprecated("shouldn't need this version")
fun postChat(message: String) {
jcefBrowser
.cefBrowser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsServerCapabilitiesProvider
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.getTextDocumentIdentifier
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickNotification
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ButtonClickParams
Expand Down Expand Up @@ -256,7 +257,9 @@ class BrowserConnector(
var encryptionManager: JwtEncryptionManager? = null
val result = AmazonQLspService.executeIfRunning(project) { server ->
encryptionManager = this.encryptionManager
encryptionManager?.encrypt(chatParams)?.let { EncryptedChatParams(it, partialResultToken) }?.let { server.sendChatPrompt(it) }

val encryptedParams = EncryptedChatParams(this.encryptionManager.encrypt(chatParams), partialResultToken)
server.sendChatPrompt(encryptedParams)
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")))

// We assume there is only one outgoing request per tab because the input is
Expand All @@ -272,11 +275,9 @@ class BrowserConnector(
var encryptionManager: JwtEncryptionManager? = null
val result = AmazonQLspService.executeIfRunning(project) { server ->
encryptionManager = this.encryptionManager
encryptionManager?.encrypt(quickActionParams)?.let {
EncryptedQuickActionChatParams(it, partialResultToken)
}?.let {
server.sendQuickAction(it)
}

val encryptedParams = EncryptedQuickActionChatParams(this.encryptionManager.encrypt(quickActionParams), partialResultToken)
server.sendQuickAction(encryptedParams)
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")))

// We assume there is only one outgoing request per tab because the input is
Expand Down Expand Up @@ -424,13 +425,18 @@ class BrowserConnector(
handleChatNotification<TabBarActionRequest, TabBarActionParams>(node) {
server, params ->
val result = server.tabBarActions(params)
result.whenComplete { params1, error ->
result.whenComplete { actions, error ->
try {
if (error != null) {
throw error
}
val res = ChatCommunicationManager.convertNotificationToJsonForChat(CHAT_TAB_BAR_ACTIONS, params1)
browser.postChat(res)

browser.postChat(
FlareUiMessage(
command = CHAT_TAB_BAR_ACTIONS,
params = actions ?: emptyMap<Any, Any>()
)
)
} catch (e: Exception) {
LOG.error { "Failed to perform chat tab bar action $e" }
params.tabId?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp
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_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
Expand Down Expand Up @@ -43,4 +45,7 @@ interface AmazonQLanguageClient : LanguageClient {

@JsonNotification(OPEN_FILE_DIFF)
fun openFileDiff(params: OpenFileDiffParams): CompletableFuture<Unit>

@JsonNotification(CHAT_SEND_CONTEXT_COMMANDS)
fun sendContextCommands(params: LSPAny): CompletableFuture<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
@file:Suppress("BannedImports")
package software.aws.toolkits.jetbrains.services.amazonq.lsp

import com.google.gson.Gson
import com.intellij.diff.DiffContentFactory
import com.intellij.diff.DiffManager
import com.intellij.diff.requests.SimpleDiffRequest
Expand All @@ -24,14 +23,18 @@ import org.eclipse.lsp4j.PublishDiagnosticsParams
import org.eclipse.lsp4j.ShowDocumentParams
import org.eclipse.lsp4j.ShowDocumentResult
import org.eclipse.lsp4j.ShowMessageRequestParams
import software.aws.toolkits.core.utils.error
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.warn
import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
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
Expand Down Expand Up @@ -133,14 +136,13 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
val result = CompletableFuture<OpenTabResult>()
ChatCommunicationManager.pendingTabRequests[requestId] = result

val uiMessage = """
{
"command": "$CHAT_OPEN_TAB",
"params": ${Gson().toJson(params)},
"requestId": "$requestId"
}
""".trimIndent()
AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage)
AsyncChatUiListener.notifyPartialMessageUpdate(
FlareUiMessage(
command = CHAT_OPEN_TAB,
params = params,
requestId = requestId,
)
)

result.orTimeout(30000, TimeUnit.MILLISECONDS)
.whenComplete { _, error ->
Expand Down Expand Up @@ -186,14 +188,13 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC

ChatCommunicationManager.pendingSerializedChatRequests[requestId] = result

val uiMessage = """
{
"command": "$GET_SERIALIZED_CHAT_REQUEST_METHOD",
"params": ${Gson().toJson(params)},
"requestId": "$requestId"
}
""".trimIndent()
AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage)
AsyncChatUiListener.notifyPartialMessageUpdate(
FlareUiMessage(
command = GET_SERIALIZED_CHAT_REQUEST_METHOD,
params = params,
requestId = requestId,
)
)

result.orTimeout(30000, TimeUnit.MILLISECONDS)
.whenComplete { _, error ->
Expand Down Expand Up @@ -252,19 +253,17 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
try {
chatCommunicationManager.handlePartialResultProgressNotification(project, params)
} catch (e: Exception) {
error("Cannot handle partial chat")
LOG.error(e) { "Cannot handle partial chat" }
}
}

override fun sendChatUpdate(params: ChatUpdateParams): CompletableFuture<Unit> {
val uiMessage = """
{
"command":"$CHAT_SEND_UPDATE",
"params":${Gson().toJson(params)}
}
""".trimIndent()

AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage)
AsyncChatUiListener.notifyPartialMessageUpdate(
FlareUiMessage(
command = CHAT_SEND_UPDATE,
params = params,
)
)

return CompletableFuture.completedFuture(Unit)
}
Expand Down Expand Up @@ -308,6 +307,17 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
ApplicationManager.getApplication()::invokeLater
)

override fun sendContextCommands(params: LSPAny): CompletableFuture<Unit> {
AsyncChatUiListener.notifyPartialMessageUpdate(
FlareUiMessage(
command = CHAT_SEND_CONTEXT_COMMANDS,
params = params ?: error("received empty payload for $CHAT_SEND_CONTEXT_COMMANDS"),
)
)

return CompletableFuture.completedFuture(Unit)
}

companion object {
private val LOG = getLogger<AmazonQLanguageClientImpl>()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,23 +282,12 @@ private class AmazonQServerInstance(private val project: Project, private val cs

launcher = LSPLauncher.Builder<AmazonQLanguageServer>()
.wrapMessages { consumer ->
MessageConsumer {
message ->
MessageConsumer { message ->
if (message is ResponseMessage && message.result is AwsExtendedInitializeResult) {
val result = message.result as AwsExtendedInitializeResult
AwsServerCapabilitiesProvider.getInstance(project).setAwsServerCapabilities(result.getAwsServerCapabilities())
AmazonQLspService.getInstance(project).notifyInitializeMessageReceived()
}
if (message is NotificationMessage && message.method == "aws/chat/sendContextCommands") {
val showContextCommands = """
{
"command":"aws/chat/sendContextCommands",
"params": ${Gson().toJson(message.params)}
}
""".trimIndent()

AsyncChatUiListener.notifyPartialMessageUpdate(showContextCommands)
}
consumer?.consume(message)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ import com.intellij.util.messages.Topic
import java.util.EventListener

interface AsyncChatUiListener : EventListener {
fun onChange(message: String) {}
@Deprecated("shouldn't need this version")
fun onChange(command: String) {}

fun onChange(command: FlareUiMessage) {}

companion object {
@Topic.AppLevel
val TOPIC = Topic.create("Partial chat message provider", AsyncChatUiListener::class.java)

fun notifyPartialMessageUpdate(message: String) {
ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onChange(message)
fun notifyPartialMessageUpdate(command: FlareUiMessage) {
ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onChange(command)
}

@Deprecated("shouldn't need this version")
fun notifyPartialMessageUpdate(command: String) {
ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onChange(command)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,5 @@ class ChatCommunicationManager {
fun completeTabOpen(requestId: String, tabId: String) {
pendingTabRequests.remove(requestId)?.complete(OpenTabResult(tabId))
}

inline fun <reified T> convertNotificationToJsonForChat(command: String, params: T? = null) =
"""
{
"command":"$command",
"params": ${if (params != null) Gson().toJson(params) else "{}"}
}
""".trimIndent()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// 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.flareChat

data class FlareUiMessage(
val command: String,
val params: Any,
val requestId: String? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,39 @@

package software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat

const val SEND_CHAT_COMMAND_PROMPT = "aws/chat/sendChatPrompt"
const val CHAT_QUICK_ACTION = "aws/chat/sendChatQuickAction"
const val CHAT_READY = "aws/chat/ready"
const val CHAT_LINK_CLICK = "aws/chat/linkClick"
const val CHAT_INFO_LINK_CLICK = "aws/chat/infoLinkClick"
const val CHAT_SOURCE_LINK_CLICK = "aws/chat/sourceLinkClick"
const val PROMPT_INPUT_OPTIONS_CHANGE = "aws/chat/promptInputOptionChange"
const val CHAT_PROMPT_OPTION_ACKNOWLEDGED = "chatPromptOptionAcknowledged"
const val CHAT_FEEDBACK = "aws/chat/feedback"
const val CHAT_FOLLOW_UP_CLICK = "aws/chat/followUpClick"
const val CHAT_LIST_CONVERSATIONS = "aws/chat/listConversations"
const val CHAT_CONVERSATION_CLICK = "aws/chat/conversationClick"
const val CHAT_FILE_CLICK = "aws/chat/fileClick"
const val CHAT_TAB_ADD = "aws/chat/tabAdd"
const val CHAT_TAB_CHANGE = "aws/chat/tabChange"
const val CHAT_TAB_REMOVE = "aws/chat/tabRemove"
const val CHAT_OPEN_TAB = "aws/chat/openTab"
const val CHAT_BUTTON_CLICK = "aws/chat/buttonClick"
const val CHAT_DISCLAIMER_ACKNOWLEDGED = "disclaimerAcknowledged"
const val CHAT_CONVERSATION_CLICK = "aws/chat/conversationClick"
const val CHAT_COPY_CODE_TO_CLIPBOARD = "copyToClipboard"
const val CHAT_COPY_CODE_TO_CLIPBOARD_NOTIFICATION = "aws/chat/copyCodeToClipboard"
const val CHAT_CREATE_PROMPT = "aws/chat/createPrompt"
const val CHAT_DISCLAIMER_ACKNOWLEDGED = "disclaimerAcknowledged"
const val CHAT_ERROR_PARAMS = "errorMessage"
const val CHAT_FEEDBACK = "aws/chat/feedback"
const val CHAT_FILE_CLICK = "aws/chat/fileClick"
const val CHAT_FOLLOW_UP_CLICK = "aws/chat/followUpClick"
const val CHAT_INFO_LINK_CLICK = "aws/chat/infoLinkClick"
const val CHAT_INSERT_TO_CURSOR = "insertToCursorPosition"
const val CHAT_INSERT_TO_CURSOR_NOTIFICATION = "aws/chat/insertToCursorPosition"
const val CHAT_TAB_BAR_ACTIONS = "aws/chat/tabBarAction"
const val CHAT_LINK_CLICK = "aws/chat/linkClick"
const val CHAT_LIST_CONVERSATIONS = "aws/chat/listConversations"
const val CHAT_OPEN_TAB = "aws/chat/openTab"
const val CHAT_PROMPT_OPTION_ACKNOWLEDGED = "chatPromptOptionAcknowledged"
const val CHAT_QUICK_ACTION = "aws/chat/sendChatQuickAction"
const val CHAT_READY = "aws/chat/ready"
const val CHAT_SEND_CONTEXT_COMMANDS = "aws/chat/sendContextCommands"
const val CHAT_SEND_UPDATE = "aws/chat/sendChatUpdate"
const val CHAT_CREATE_PROMPT = "aws/chat/createPrompt"
const val SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD = "aws/showSaveFileDialog"
const val CHAT_SOURCE_LINK_CLICK = "aws/chat/sourceLinkClick"
const val CHAT_TAB_ADD = "aws/chat/tabAdd"
const val CHAT_TAB_BAR_ACTIONS = "aws/chat/tabBarAction"
const val CHAT_TAB_CHANGE = "aws/chat/tabChange"
const val CHAT_TAB_REMOVE = "aws/chat/tabRemove"

const val GET_SERIALIZED_CHAT_REQUEST_METHOD = "aws/chat/getSerializedChat"

const val OPEN_FILE_DIFF = "aws/openFileDiff"
const val CHAT_ERROR_PARAMS = "errorMessage"

const val PROMPT_INPUT_OPTIONS_CHANGE = "aws/chat/promptInputOptionChange"

const val SEND_CHAT_COMMAND_PROMPT = "aws/chat/sendChatPrompt"
const val SHOW_SAVE_FILE_DIALOG_REQUEST_METHOD = "aws/showSaveFileDialog"
const val STOP_CHAT_RESPONSE = "stopChatResponse"
Loading