-
Notifications
You must be signed in to change notification settings - Fork 252
feat(amazonq): Add quick actions to Flare chat #5561
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
Changes from 17 commits
fe7738d
c09368c
9d037cf
e259071
bc6823b
b833b6c
fb9fd25
46b9a82
06dc03d
0f16cd4
21e4885
06e0966
8640432
930fff6
28a199e
98a43b5
a751f56
6b5abe1
1a3b0bd
274d1dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
import kotlinx.coroutines.flow.launchIn | ||
import kotlinx.coroutines.flow.merge | ||
import kotlinx.coroutines.flow.onEach | ||
import kotlinx.coroutines.future.await | ||
Check warningCode scanning / QDJVMC Unused import directive Warning
Unused import directive
|
||
import kotlinx.coroutines.launch | ||
import org.cef.browser.CefBrowser | ||
import org.eclipse.lsp4j.Position | ||
|
@@ -27,10 +28,13 @@ | |
import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.getTextDocumentIdentifier | ||
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.ChatParams | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.ChatPrompt | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CursorState | ||
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.QuickChatActionRequest | ||
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.SendChatPromptRequest | ||
import software.aws.toolkits.jetbrains.services.amazonq.util.command | ||
|
@@ -147,7 +151,7 @@ | |
private fun handleFlareChatMessages(browser: Browser, node: JsonNode) { | ||
when (node.command) { | ||
SEND_CHAT_COMMAND_PROMPT -> { | ||
val requestFromUi = serializer.deserializeChatMessages(node, SendChatPromptRequest::class.java) | ||
val requestFromUi = serializer.deserializeChatMessages<SendChatPromptRequest>(node) | ||
Check warning on line 154 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
val chatPrompt = ChatPrompt( | ||
requestFromUi.params.prompt.prompt, | ||
requestFromUi.params.prompt.escapedPrompt, | ||
|
@@ -167,32 +171,59 @@ | |
) | ||
) | ||
|
||
val partialResultToken = chatCommunicationManager.addPartialChatMessage(requestFromUi.params.tabId) | ||
val chatParams = ChatParams( | ||
requestFromUi.params.tabId, | ||
chatPrompt, | ||
textDocumentIdentifier, | ||
cursorState | ||
) | ||
|
||
val tabId = requestFromUi.params.tabId | ||
val partialResultToken = chatCommunicationManager.addPartialChatMessage(tabId) | ||
Check warning on line 182 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
|
||
var encryptionManager: JwtEncryptionManager? = null | ||
val result = AmazonQLspService.executeIfRunning(project) { server -> | ||
encryptionManager = this.encryptionManager | ||
encryptionManager?.encrypt(chatParams)?.let { EncryptedChatParams(it, partialResultToken) }?.let { server.sendChatPrompt(it) } | ||
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) | ||
showResult(result, partialResultToken, tabId, encryptionManager, browser) | ||
Check warning on line 189 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
} | ||
CHAT_QUICK_ACTION -> { | ||
val requestFromUi = serializer.deserializeChatMessages<QuickChatActionRequest>(node) | ||
val tabId = requestFromUi.params.tabId | ||
val quickActionParams = requestFromUi.params | ||
val partialResultToken = chatCommunicationManager.addPartialChatMessage(tabId) | ||
var encryptionManager: JwtEncryptionManager? = null | ||
Check warning on line 196 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
val result = AmazonQLspService.executeIfRunning(project) { server -> | ||
encryptionManager = this.encryptionManager | ||
Check warning on line 198 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we are extracting the encryptionManager, we might as well capture the server instance so that we don't need to fetch it on every invocation |
||
encryptionManager?.encrypt(quickActionParams)?.let { | ||
EncryptedQuickActionChatParams(it, partialResultToken) | ||
}?.let { | ||
server.sendQuickAction(it) | ||
} | ||
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) | ||
Check warning on line 204 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
|
||
result.whenComplete { | ||
value, error -> | ||
chatCommunicationManager.removePartialChatMessage(partialResultToken) | ||
val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( | ||
node.command, | ||
requestFromUi.params.tabId, | ||
encryptionManager?.decrypt(value).orEmpty(), | ||
isPartialResult = false | ||
) | ||
browser.postChat(messageToChat) | ||
} | ||
showResult(result, partialResultToken, tabId, encryptionManager, browser) | ||
Check warning on line 206 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
} | ||
} | ||
} | ||
|
||
private fun showResult( | ||
result: CompletableFuture<String>, | ||
partialResultToken: String, | ||
tabId: String, | ||
encryptionManager: JwtEncryptionManager?, | ||
browser: Browser, | ||
) { | ||
result.whenComplete { value, error -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need to handle error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
chatCommunicationManager.removePartialChatMessage(partialResultToken) | ||
val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat( | ||
SEND_CHAT_COMMAND_PROMPT, | ||
tabId, | ||
Check warning on line 222 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
encryptionManager?.decrypt(value).orEmpty(), | ||
isPartialResult = false | ||
Check warning on line 224 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
) | ||
browser.postChat(messageToChat) | ||
} | ||
} | ||
Check warning on line 228 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
import com.intellij.openapi.util.Disposer | ||
import com.intellij.openapi.util.Key | ||
import com.intellij.openapi.util.SystemInfo | ||
import com.intellij.util.animation.consumer | ||
Check warningCode scanning / QDJVMC Unused import directive Warning
Unused import directive
|
||
import com.intellij.util.io.await | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Deferred | ||
|
@@ -40,6 +41,10 @@ | |
import org.eclipse.lsp4j.TextDocumentClientCapabilities | ||
import org.eclipse.lsp4j.WorkspaceClientCapabilities | ||
import org.eclipse.lsp4j.jsonrpc.Launcher | ||
import org.eclipse.lsp4j.jsonrpc.Launcher.Builder | ||
Check warningCode scanning / QDJVMC Unused import directive Warning
Unused import directive
|
||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer | ||
import org.eclipse.lsp4j.jsonrpc.messages.Message | ||
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage | ||
import org.eclipse.lsp4j.launch.LSPLauncher | ||
import org.slf4j.event.Level | ||
import software.aws.toolkits.core.utils.getLogger | ||
|
@@ -50,6 +55,9 @@ | |
import software.aws.toolkits.jetbrains.services.amazonq.lsp.auth.DefaultAuthCredentialsService | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.dependencies.DefaultModuleDependenciesService | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AmazonQLspTypeAdapterFactory | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsExtendedInitializeResult | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsServerCapabilitiesProvider | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.createExtendedClientMetadata | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDocumentServiceHandler | ||
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders | ||
|
@@ -101,6 +109,10 @@ | |
|
||
@Service(Service.Level.PROJECT) | ||
class AmazonQLspService(private val project: Project, private val cs: CoroutineScope) : Disposable { | ||
private val lspInitializedMessageReceivedListener = mutableListOf<AmazonQInitializeMessageReceivedListener>() | ||
manodnyab marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fun addLspInitializeMessageListener(listener: AmazonQInitializeMessageReceivedListener) = lspInitializedMessageReceivedListener.add(listener) | ||
fun notifyInitializeMessageReceived() = lspInitializedMessageReceivedListener.forEach { it() } | ||
Check warning on line 114 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
|
||
private var instance: Deferred<AmazonQServerInstance> | ||
val capabilities | ||
get() = instance.getCompleted().initializeResult.getCompleted().capabilities | ||
|
@@ -266,6 +278,17 @@ | |
launcherHandler.startNotify() | ||
|
||
launcher = LSPLauncher.Builder<AmazonQLanguageServer>() | ||
.wrapMessages { consumer -> | ||
MessageConsumer { | ||
Check warning on line 282 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
message -> | ||
if (message is ResponseMessage && message.result is AwsExtendedInitializeResult) { | ||
val result = message.result as AwsExtendedInitializeResult | ||
AwsServerCapabilitiesProvider.getInstance(project).setAwsServerCapabilities(result.getAwsServerCapabilities()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we already expose capabilities on the service
? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Want to keep this in place to be able to access other agents potentially from this map |
||
AmazonQLspService.getInstance(project).notifyInitializeMessageReceived() | ||
Check warning on line 287 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
} | ||
consumer?.consume(message) | ||
} | ||
Check warning on line 290 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
} | ||
.setLocalService(AmazonQLanguageClientImpl(project)) | ||
.setRemoteInterface(AmazonQLanguageServer::class.java) | ||
.configureGson { | ||
|
@@ -274,6 +297,7 @@ | |
|
||
// otherwise Gson treats all numbers as double which causes deser issues | ||
it.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) | ||
it.registerTypeAdapterFactory(AmazonQLspTypeAdapterFactory()) | ||
Check warning on line 300 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
}.traceMessages( | ||
PrintWriter( | ||
object : StringWriter() { | ||
|
@@ -348,7 +372,21 @@ | |
} | ||
} | ||
|
||
class MessageTracer { | ||
Check warning on line 375 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
Check warningCode scanning / QDJVMC Unused symbol Warning
Class "MessageTracer" is never used
|
||
private val traceLogger = LOG.atLevel(if (isDeveloperMode()) Level.INFO else Level.DEBUG) | ||
|
||
fun trace(direction: String, message: Message) { | ||
Check warningCode scanning / QDJVMC Unused symbol Warning
Function "trace" is never used
|
||
traceLogger.log { | ||
buildString { | ||
append("$direction: ") | ||
append(message.toString()) | ||
} | ||
Check warning on line 383 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
} | ||
} | ||
Check warning on line 385 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt
|
||
} | ||
companion object { | ||
private val LOG = getLogger<AmazonQServerInstance>() | ||
} | ||
} | ||
|
||
typealias AmazonQInitializeMessageReceivedListener = () -> Unit |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// 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.flareChat | ||
|
||
import com.google.gson.Gson | ||
import com.google.gson.TypeAdapter | ||
import com.google.gson.TypeAdapterFactory | ||
import com.google.gson.reflect.TypeToken | ||
import com.google.gson.stream.JsonReader | ||
import com.google.gson.stream.JsonWriter | ||
import org.eclipse.lsp4j.InitializeResult | ||
import java.io.IOException | ||
|
||
class AmazonQLspTypeAdapterFactory : TypeAdapterFactory { | ||
Check warning on line 15 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { | ||
if (type.rawType === InitializeResult::class.java) { | ||
val delegate: TypeAdapter<InitializeResult?> = gson.getDelegateAdapter(this, type) as TypeAdapter<InitializeResult?> | ||
Check warning on line 18 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
|
||
return object : TypeAdapter<InitializeResult>() { | ||
Check warning on line 20 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
@Throws(IOException::class) | ||
override fun write(out: JsonWriter, value: InitializeResult?) { | ||
delegate.write(out, value) | ||
} | ||
Check warning on line 24 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
|
||
@Throws(IOException::class) | ||
override fun read(`in`: JsonReader): InitializeResult = | ||
gson.fromJson(`in`, AwsExtendedInitializeResult::class.java) | ||
Check warning on line 28 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
} as TypeAdapter<T> | ||
} | ||
return null | ||
Check warning on line 31 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
} | ||
} | ||
|
||
class AwsExtendedInitializeResult(awsServerCapabilities: AwsServerCapabilities? = null) : InitializeResult() { | ||
Check warning on line 35 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data class |
||
private var awsServerCapabilities: AwsServerCapabilities? = null | ||
|
||
fun getAwsServerCapabilities(): AwsServerCapabilities? = awsServerCapabilities | ||
Check warning on line 38 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
|
||
fun setAwsServerCapabilities(awsServerCapabilities: AwsServerCapabilities?) { | ||
Check warningCode scanning / QDJVMC Unused symbol Warning
Function "setAwsServerCapabilities" is never used
|
||
this.awsServerCapabilities = awsServerCapabilities | ||
} | ||
Check warning on line 42 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AmazonQLspTypeAdapterFactory.kt
|
||
} |
Check notice
Code scanning / QDJVMC
Function or property has platform type Note