From 2e70fc384263521e4fb50cd022615e1329e5db40 Mon Sep 17 00:00:00 2001 From: Richard Li <742829+rli@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:07:18 -0700 Subject: [PATCH 01/10] feat(amazonq): expose 'Manage Subscriptions' action for builder id paid tier (#5777) --- .../META-INF/plugin-codewhisperer.xml | 2 + .../explorer/QStatusBarLoggedInActionGroup.kt | 23 ++++---- .../resources/AmazonQBundle.properties | 1 + .../actions/ManageSubscription.kt | 58 +++++++++++++++++++ 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml index 6a2088d39c0..7fdcf371ad4 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml +++ b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml @@ -139,5 +139,7 @@ class="software.aws.toolkits.jetbrains.services.cwc.commands.codescan.actions.CodeScanCompleteAction" /> + diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt index 3dd88d45d22..53f6fe82b2d 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt @@ -12,15 +12,12 @@ import com.intellij.openapi.project.Project import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.actions.SsoLogoutAction -import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.services.amazonq.actions.QSwitchProfilesAction import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererConnectOnGithubAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererLearnMoreAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererProvideFeedbackAction -import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererShowSettingsAction import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.actions.CodeWhispererCodeScanRunAction import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.ActionProvider import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Customize @@ -54,6 +51,7 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { override fun getChildren(e: AnActionEvent?) = e?.project?.let { val isPendingActiveProfile = QRegionProfileManager.getInstance().hasValidConnectionButNoActiveProfile(it) + val actionManager = ActionManager.getInstance() buildList { if (!isPendingActiveProfile) { addAll(buildActionListForActiveProfileSelected(it, actionProvider)) @@ -64,15 +62,18 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { addAll(buildActionListForConnectHelp(actionProvider)) add(Separator.create()) - add(CodeWhispererShowSettingsAction()) - ( - ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - )?.takeIf { !it.isSono() } - ?.let { add(QSwitchProfilesAction()) } - ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { c -> - (c as? AwsBearerTokenConnection)?.let { connection -> - add(SsoLogoutAction(connection)) + add(actionManager.getAction("codewhisperer.settings")) + + val connection = ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection + + if (connection != null) { + if (!connection.isSono()) { + add(actionManager.getAction("codewhisperer.switchProfiles")) + } else { + add(actionManager.getAction("q.manage.subscription")) } + + add(SsoLogoutAction(connection)) } }.toTypedArray() }.orEmpty() diff --git a/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties b/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties index ab7face28d9..2a1fb1068ed 100644 --- a/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties +++ b/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties @@ -9,6 +9,7 @@ amazonqInlineChat.popup.title=Enter Instructions for Q amazonq.refresh.panel=Refresh Chat Session amazonq.title=Amazon Q amazonq.workspace.settings.open.prompt=Workspace index is now enabled. You can disable it from Amazon Q settings. +action.q.manage.subscription.text=Manage Subscription action.q.profile.usage.text=You changed your profile action.q.profile.usage=You''re using the ''{0}'' profile for Amazon Q. action.q.switchProfiles.text=Change Profile diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt new file mode 100644 index 00000000000..10568619c64 --- /dev/null +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt @@ -0,0 +1,58 @@ +// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.aws.toolkits.jetbrains.services.codewhisperer.actions + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.progress.currentThreadCoroutineScope +import com.intellij.openapi.project.DumbAware +import kotlinx.coroutines.future.await +import kotlinx.coroutines.launch +import org.eclipse.lsp4j.ExecuteCommandParams +import software.aws.toolkits.core.utils.error +import software.aws.toolkits.core.utils.getLogger +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.core.credentials.sono.isSono +import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService + +class ManageSubscription : AnAction(), DumbAware { + override fun getActionUpdateThread() = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + val project = e.project + // disable if user is IdC + if (project == null) { + e.presentation.isEnabledAndVisible = false + } else { + val connection = ToolkitConnectionManager.getInstance(project) + .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection + + e.presentation.isEnabledAndVisible = connection.isSono() + } + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + currentThreadCoroutineScope().launch { + AmazonQLspService.getInstance(project).execute { lsp -> + lsp.workspaceService.executeCommand( + ExecuteCommandParams().apply { + this.command = "aws/chat/manageSubscription" + } + ) + }.handleAsync { _, ex -> + if (ex != null) { + LOG.error(ex) { "Failed aws/chat/manageSubscription" } + } + }.await() + } + } + + companion object { + private val LOG = getLogger() + } +} From 4b833652547c855fbb76d6be03c6395e691a69c6 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 3 Jun 2025 11:10:15 -0700 Subject: [PATCH 02/10] Revert "feat(amazonq): expose 'Manage Subscriptions' action for builder id paid tier (#5777)" This reverts commit 2e70fc384263521e4fb50cd022615e1329e5db40. --- .../META-INF/plugin-codewhisperer.xml | 2 - .../explorer/QStatusBarLoggedInActionGroup.kt | 23 ++++---- .../resources/AmazonQBundle.properties | 1 - .../actions/ManageSubscription.kt | 58 ------------------- 4 files changed, 11 insertions(+), 73 deletions(-) delete mode 100644 plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml index 7fdcf371ad4..6a2088d39c0 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml +++ b/plugins/amazonq/codewhisperer/jetbrains-community/resources/META-INF/plugin-codewhisperer.xml @@ -139,7 +139,5 @@ class="software.aws.toolkits.jetbrains.services.cwc.commands.codescan.actions.CodeScanCompleteAction" /> - diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt index 53f6fe82b2d..3dd88d45d22 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/explorer/QStatusBarLoggedInActionGroup.kt @@ -12,12 +12,15 @@ import com.intellij.openapi.project.Project import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager import software.aws.toolkits.jetbrains.core.credentials.actions.SsoLogoutAction +import software.aws.toolkits.jetbrains.core.credentials.pinning.CodeWhispererConnection import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection import software.aws.toolkits.jetbrains.core.credentials.sono.isSono +import software.aws.toolkits.jetbrains.services.amazonq.actions.QSwitchProfilesAction import software.aws.toolkits.jetbrains.services.amazonq.profile.QRegionProfileManager import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererConnectOnGithubAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererLearnMoreAction import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererProvideFeedbackAction +import software.aws.toolkits.jetbrains.services.codewhisperer.actions.CodeWhispererShowSettingsAction import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.actions.CodeWhispererCodeScanRunAction import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.ActionProvider import software.aws.toolkits.jetbrains.services.codewhisperer.explorer.actions.Customize @@ -51,7 +54,6 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { override fun getChildren(e: AnActionEvent?) = e?.project?.let { val isPendingActiveProfile = QRegionProfileManager.getInstance().hasValidConnectionButNoActiveProfile(it) - val actionManager = ActionManager.getInstance() buildList { if (!isPendingActiveProfile) { addAll(buildActionListForActiveProfileSelected(it, actionProvider)) @@ -62,18 +64,15 @@ class QStatusBarLoggedInActionGroup : DefaultActionGroup() { addAll(buildActionListForConnectHelp(actionProvider)) add(Separator.create()) - add(actionManager.getAction("codewhisperer.settings")) - - val connection = ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - - if (connection != null) { - if (!connection.isSono()) { - add(actionManager.getAction("codewhisperer.switchProfiles")) - } else { - add(actionManager.getAction("q.manage.subscription")) + add(CodeWhispererShowSettingsAction()) + ( + ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection + )?.takeIf { !it.isSono() } + ?.let { add(QSwitchProfilesAction()) } + ToolkitConnectionManager.getInstance(it).activeConnectionForFeature(CodeWhispererConnection.getInstance())?.let { c -> + (c as? AwsBearerTokenConnection)?.let { connection -> + add(SsoLogoutAction(connection)) } - - add(SsoLogoutAction(connection)) } }.toTypedArray() }.orEmpty() diff --git a/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties b/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties index 2a1fb1068ed..ab7face28d9 100644 --- a/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties +++ b/plugins/amazonq/shared/jetbrains-community/resources/software/aws/toolkits/resources/AmazonQBundle.properties @@ -9,7 +9,6 @@ amazonqInlineChat.popup.title=Enter Instructions for Q amazonq.refresh.panel=Refresh Chat Session amazonq.title=Amazon Q amazonq.workspace.settings.open.prompt=Workspace index is now enabled. You can disable it from Amazon Q settings. -action.q.manage.subscription.text=Manage Subscription action.q.profile.usage.text=You changed your profile action.q.profile.usage=You''re using the ''{0}'' profile for Amazon Q. action.q.switchProfiles.text=Change Profile diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt deleted file mode 100644 index 10568619c64..00000000000 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/actions/ManageSubscription.kt +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package software.aws.toolkits.jetbrains.services.codewhisperer.actions - -import com.intellij.openapi.actionSystem.ActionUpdateThread -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.progress.currentThreadCoroutineScope -import com.intellij.openapi.project.DumbAware -import kotlinx.coroutines.future.await -import kotlinx.coroutines.launch -import org.eclipse.lsp4j.ExecuteCommandParams -import software.aws.toolkits.core.utils.error -import software.aws.toolkits.core.utils.getLogger -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.core.credentials.sono.isSono -import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService - -class ManageSubscription : AnAction(), DumbAware { - override fun getActionUpdateThread() = ActionUpdateThread.BGT - - override fun update(e: AnActionEvent) { - val project = e.project - // disable if user is IdC - if (project == null) { - e.presentation.isEnabledAndVisible = false - } else { - val connection = ToolkitConnectionManager.getInstance(project) - .activeConnectionForFeature(QConnection.getInstance()) as? AwsBearerTokenConnection - - e.presentation.isEnabledAndVisible = connection.isSono() - } - } - - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - currentThreadCoroutineScope().launch { - AmazonQLspService.getInstance(project).execute { lsp -> - lsp.workspaceService.executeCommand( - ExecuteCommandParams().apply { - this.command = "aws/chat/manageSubscription" - } - ) - }.handleAsync { _, ex -> - if (ex != null) { - LOG.error(ex) { "Failed aws/chat/manageSubscription" } - } - }.await() - } - } - - companion object { - private val LOG = getLogger() - } -} From 38e73696523e8976c524c04f7d45f12b6b33e9ff Mon Sep 17 00:00:00 2001 From: manodnyab <66754471+manodnyab@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:01:33 -0700 Subject: [PATCH 03/10] Send active file in editor (#5783) --- .../jetbrains/services/amazonq/webview/BrowserConnector.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt index 9d39606ba8b..83d6a532ef1 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt @@ -224,7 +224,7 @@ class BrowserConnector( ) val serializedEnrichmentParams = serializer.objectMapper.valueToTree(enrichmentParams) - val chatParams: ObjectNode = (node as ObjectNode) + val chatParams: ObjectNode = (node.params as ObjectNode) .setAll(serializedEnrichmentParams) val tabId = requestFromUi.params.tabId @@ -235,7 +235,7 @@ class BrowserConnector( val result = AmazonQLspService.executeIfRunning(project) { server -> encryptionManager = this.encryptionManager - val encryptedParams = EncryptedChatParams(this.encryptionManager.encrypt(chatParams.params), partialResultToken) + val encryptedParams = EncryptedChatParams(this.encryptionManager.encrypt(chatParams), partialResultToken) rawEndpoint.request(SEND_CHAT_COMMAND_PROMPT, encryptedParams) as CompletableFuture } ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))) From 514132075a7d8cb41bdde5bf5f82fcf31b2dc194 Mon Sep 17 00:00:00 2001 From: manodnyab <66754471+manodnyab@users.noreply.github.com> Date: Wed, 4 Jun 2025 11:10:05 -0700 Subject: [PATCH 04/10] Fix creation of prompts not opening files in the editor (#5781) There was a threading issue where since the file was opened in EDT and the result was sent back to the server on another thread, it returned the result before the file was opened. This PR makes it synchronous --- .../amazonq/lsp/AmazonQLanguageClientImpl.kt | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index 01eda2ea2c1..dcaad8b7143 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -158,18 +158,21 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC // The filepath sent by the server contains unicode characters which need to be // decoded for JB file handling APIs to be handle to handle file operations val fileToOpen = URLDecoder.decode(params.uri, StandardCharsets.UTF_8.name()) - ApplicationManager.getApplication().invokeLater { - try { - val virtualFile = VirtualFileManager.getInstance().findFileByUrl(fileToOpen) - ?: throw IllegalArgumentException("Cannot find file: $fileToOpen") - - FileEditorManager.getInstance(project).openFile(virtualFile, true) - } catch (e: Exception) { - LOG.warn { "Failed to show document: $fileToOpen" } - } - } + return CompletableFuture.supplyAsync( + { + try { + val virtualFile = VirtualFileManager.getInstance().refreshAndFindFileByUrl(fileToOpen) + ?: throw IllegalArgumentException("Cannot find file: $fileToOpen") - return CompletableFuture.completedFuture(ShowDocumentResult(true)) + FileEditorManager.getInstance(project).openFile(virtualFile, true) + ShowDocumentResult(true) + } catch (e: Exception) { + LOG.warn { "Failed to show document: $fileToOpen" } + ShowDocumentResult(false) + } + }, + ApplicationManager.getApplication()::invokeLater + ) } catch (e: Exception) { LOG.warn { "Error showing document" } return CompletableFuture.completedFuture(ShowDocumentResult(false)) From 69f152243d19e6f902f7543889fbc0551fef9821 Mon Sep 17 00:00:00 2001 From: manodnyab <66754471+manodnyab@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:58:15 -0700 Subject: [PATCH 05/10] Update changelog (#5785) --- .../feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json diff --git a/.changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json b/.changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json new file mode 100644 index 00000000000..e1fcfbfe7da --- /dev/null +++ b/.changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json @@ -0,0 +1,4 @@ +{ + "type" : "feature", + "description" : "Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf" +} \ No newline at end of file From 060a13248a44e88cece515370b65175efaa38eeb Mon Sep 17 00:00:00 2001 From: Richard Li <742829+rli@users.noreply.github.com> Date: Wed, 4 Jun 2025 21:14:56 -0700 Subject: [PATCH 06/10] fix(amazonq): fix issue where chat messages get sent to all projects (#5787) * fix(amazonq): fix issue where chat messages get sent to all projects * callsites --------- Co-authored-by: manodnyab <66754471+manodnyab@users.noreply.github.com> --- .../services/cwc/commands/ActionRegistrar.kt | 4 ++-- .../codescan/actions/ExplainCodeIssueAction.kt | 10 +++++++++- .../amazonq/lsp/AmazonQLanguageClientImpl.kt | 1 + .../amazonq/lsp/flareChat/AsyncChatUiListener.kt | 14 ++++++++------ .../lsp/flareChat/ChatCommunicationManager.kt | 8 ++++---- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ActionRegistrar.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ActionRegistrar.kt index b9a6efd5b48..e5e92f7544b 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ActionRegistrar.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ActionRegistrar.kt @@ -29,7 +29,7 @@ class ActionRegistrar { fun reportMessageClick(command: EditorContextCommand, project: Project) { if (command == EditorContextCommand.GenerateUnitTests) { - AsyncChatUiListener.notifyPartialMessageUpdate(Gson().toJson(TestCommandMessage())) + AsyncChatUiListener.notifyPartialMessageUpdate(project, Gson().toJson(TestCommandMessage())) } else { // new agentic chat route ApplicationManager.getApplication().executeOnPooledThread { @@ -45,7 +45,7 @@ class ActionRegistrar { val params = SendToPromptParams(selection = codeSelection, triggerType = TriggerType.CONTEXT_MENU) uiMessage = FlareUiMessage(command = SEND_TO_PROMPT, params = params) } - AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage) + AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage) } } } diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/ExplainCodeIssueAction.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/ExplainCodeIssueAction.kt index bf744d4b569..94f572984a9 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/ExplainCodeIssueAction.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/ExplainCodeIssueAction.kt @@ -4,6 +4,7 @@ package software.aws.toolkits.jetbrains.services.cwc.commands.codescan.actions import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataKey @@ -18,7 +19,14 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendT import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType class ExplainCodeIssueAction : AnAction(), DumbAware { + override fun getActionUpdateThread() = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + e.presentation.isEnabledAndVisible = e.project != null + } + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return val issueDataKey = DataKey.create>("amazonq.codescan.explainissue") val issueContext = e.getData(issueDataKey) ?: return @@ -50,7 +58,7 @@ class ExplainCodeIssueAction : AnAction(), DumbAware { ) val uiMessage = FlareUiMessage(SEND_TO_PROMPT, params) - AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage) + AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage) } } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt index dcaad8b7143..577b51cf612 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt @@ -315,6 +315,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC override fun sendChatUpdate(params: LSPAny): CompletableFuture { AsyncChatUiListener.notifyPartialMessageUpdate( + project, FlareUiMessage( command = CHAT_SEND_UPDATE, params = params, diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AsyncChatUiListener.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AsyncChatUiListener.kt index c6992526ec9..21d812cf075 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AsyncChatUiListener.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/AsyncChatUiListener.kt @@ -3,10 +3,11 @@ package software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat -import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project import com.intellij.util.messages.Topic import java.util.EventListener +@Deprecated("Why are we using a message bus for this????????") interface AsyncChatUiListener : EventListener { @Deprecated("shouldn't need this version") fun onChange(command: String) {} @@ -14,16 +15,17 @@ interface AsyncChatUiListener : EventListener { fun onChange(command: FlareUiMessage) {} companion object { - @Topic.AppLevel + @Topic.ProjectLevel val TOPIC = Topic.create("Partial chat message provider", AsyncChatUiListener::class.java) - fun notifyPartialMessageUpdate(command: FlareUiMessage) { - ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onChange(command) + @Deprecated("Why are we using a message bus for this????????") + fun notifyPartialMessageUpdate(project: Project, command: FlareUiMessage) { + project.messageBus.syncPublisher(TOPIC).onChange(command) } @Deprecated("shouldn't need this version") - fun notifyPartialMessageUpdate(command: String) { - ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC).onChange(command) + fun notifyPartialMessageUpdate(project: Project, command: String) { + project.messageBus.syncPublisher(TOPIC).onChange(command) } } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt index bd64d7336f8..ac7deaa4134 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/flareChat/ChatCommunicationManager.kt @@ -37,7 +37,7 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap @Service(Service.Level.PROJECT) -class ChatCommunicationManager(private val cs: CoroutineScope) { +class ChatCommunicationManager(private val project: Project, private val cs: CoroutineScope) { val uiReady = CompletableDeferred() private val chatPartialResultMap = ConcurrentHashMap() private val inflightRequestByTabId = ConcurrentHashMap>() @@ -53,7 +53,7 @@ class ChatCommunicationManager(private val cs: CoroutineScope) { fun notifyUi(uiMessage: FlareUiMessage) { cs.launch { uiReady.await() - AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage) + AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage) } } @@ -148,7 +148,7 @@ class ChatCommunicationManager(private val cs: CoroutineScope) { params = partialChatResult, isPartialResult = true ) - AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage) + AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage) finalResultProcessed[token] = true ChatAsyncResultManager.getInstance(project).setResult(token, partialResultMap) return @@ -169,7 +169,7 @@ class ChatCommunicationManager(private val cs: CoroutineScope) { params = partialChatResult, isPartialResult = true ) - AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage) + AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage) } } } From bc9582385f7d3b69645db146d5ff981134df03ea Mon Sep 17 00:00:00 2001 From: Richard Li <742829+rli@users.noreply.github.com> Date: Wed, 4 Jun 2025 21:27:52 -0700 Subject: [PATCH 07/10] fix(amazonq): add metrics for flare/node resolution (#5786) We need some visibility on how prevalent failures are --- .../services/amazonq/lsp/AmazonQLspService.kt | 20 ++++ .../amazonq/lsp/artifacts/ArtifactHelper.kt | 63 +++++++--- .../amazonq/lsp/artifacts/ArtifactManager.kt | 108 ++++++++++++------ .../lsp/artifacts/ArtifactHelperTest.kt | 4 +- .../services/telemetry/otel/OtelBase.kt | 3 + 5 files changed, 148 insertions(+), 50 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index f5226359c55..28c28100f97 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -77,10 +77,12 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDoc import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig +import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata import software.aws.toolkits.jetbrains.settings.LspSettings import software.aws.toolkits.jetbrains.utils.notifyInfo import software.aws.toolkits.resources.message +import software.aws.toolkits.telemetry.Telemetry import java.io.IOException import java.io.OutputStreamWriter import java.io.PipedInputStream @@ -526,20 +528,36 @@ private class AmazonQServerInstance(private val project: Project, private val cs * may fail to start in that case. The caller should handle potential runtime initialization failures. */ private fun getNodeRuntimePath(nodePath: Path): Path { + val resolveNodeMetric = { isBundled: Boolean, success: Boolean -> + Telemetry.languageserver.setup.use { + it.id("q") + it.metadata("languageServerSetupStage", "resolveNode") + it.metadata("credentialStartUrl", getStartUrl(project)) + it.setAttribute("isBundledNode", isBundled) + it.success(success) + } + } + if (Files.exists(nodePath) && Files.isExecutable(nodePath)) { + resolveNodeMetric(true, true) return nodePath } + // use alternative node runtime if it is not found LOG.warn { "Node Runtime download failed. Fallback to user specified node runtime " } // attempt to use user provided node runtime path val nodeRuntime = LspSettings.getInstance().getNodeRuntimePath() if (!nodeRuntime.isNullOrEmpty()) { LOG.info { "Using node from $nodeRuntime " } + + resolveNodeMetric(false, true) return Path.of(nodeRuntime) } else { val localNode = locateNodeCommand() if (localNode != null) { LOG.info { "Using node from ${localNode.toAbsolutePath()}" } + + resolveNodeMetric(false, true) return localNode } notifyInfo( @@ -557,6 +575,8 @@ private class AmazonQServerInstance(private val project: Project, private val cs ) { _, notification -> notification.expire() } ) ) + + resolveNodeMetric(false, false) return nodePath } } diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt index 97f2db11fed..dc8d25235dc 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt @@ -17,7 +17,10 @@ import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.core.saveFileFromUrl +import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl import software.aws.toolkits.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.LanguageServerSetupStage +import software.aws.toolkits.telemetry.Telemetry import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -106,12 +109,12 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, } suspend fun tryDownloadLspArtifacts(project: Project, targetVersion: Version, target: VersionTarget): Path? { - val temporaryDownloadPath = Files.createTempDirectory("lsp-dl") - val downloadPath = lspArtifactsPath.resolve(targetVersion.serverVersion.toString()) + val destinationPath = lspArtifactsPath.resolve(targetVersion.serverVersion.toString()) while (currentAttempt.get() < maxDownloadAttempts) { currentAttempt.incrementAndGet() logger.info { "Attempt ${currentAttempt.get()} of $maxDownloadAttempts to download LSP artifacts" } + val temporaryDownloadPath = Files.createTempDirectory("lsp-dl") try { return withBackgroundProgress( @@ -119,20 +122,20 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, AwsCoreBundle.message("amazonqFeatureDev.placeholder.downloading_and_extracting_lsp_artifacts"), cancellable = true ) { - if (downloadLspArtifacts(temporaryDownloadPath, target) && !target.contents.isNullOrEmpty()) { - moveFilesFromSourceToDestination(temporaryDownloadPath, downloadPath) + if (downloadLspArtifacts(project, temporaryDownloadPath, target) && !target.contents.isNullOrEmpty()) { + moveFilesFromSourceToDestination(temporaryDownloadPath, destinationPath) target.contents .mapNotNull { it.filename } - .forEach { filename -> extractZipFile(downloadPath.resolve(filename), downloadPath) } - logger.info { "Successfully downloaded and moved LSP artifacts to $downloadPath" } + .forEach { filename -> extractZipFile(destinationPath.resolve(filename), destinationPath) } + logger.info { "Successfully downloaded and moved LSP artifacts to $destinationPath" } val thirdPartyLicenses = targetVersion.thirdPartyLicenses logger.info { - "Installing Amazon Q Language Server v${targetVersion.serverVersion} to: $downloadPath. " + + "Installing Amazon Q Language Server v${targetVersion.serverVersion} to: $destinationPath. " + if (thirdPartyLicenses == null) "" else "Attribution notice can be found at $thirdPartyLicenses" } - return@withBackgroundProgress downloadPath + return@withBackgroundProgress destinationPath } return@withBackgroundProgress null @@ -146,7 +149,7 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, else -> { logger.error(e) { "Failed to download/move LSP artifacts on attempt ${currentAttempt.get()}" } } } temporaryDownloadPath.toFile().deleteRecursively() - downloadPath.toFile().deleteRecursively() + destinationPath.toFile().deleteRecursively() } } logger.error { "Failed to download LSP artifacts after $maxDownloadAttempts attempts" } @@ -154,7 +157,7 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, } @VisibleForTesting - internal fun downloadLspArtifacts(downloadPath: Path, target: VersionTarget?): Boolean { + internal fun downloadLspArtifacts(project: Project, downloadPath: Path, target: VersionTarget?): Boolean { if (target == null || target.contents.isNullOrEmpty()) { logger.warn { "No target contents available for download" } return false @@ -171,7 +174,7 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, logger.warn { "No hash available for ${content.filename}" } return@forEach } - downloadAndValidateFile(content.url, filePath, contentHash) + downloadAndValidateFile(project, content.url, filePath, contentHash) } validateDownloadedFiles(downloadPath, target.contents) } catch (e: Exception) { @@ -182,18 +185,46 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, return true } - private fun downloadAndValidateFile(url: String, filePath: Path, expectedHash: String) { + private fun downloadAndValidateFile(project: Project, url: String, filePath: Path, expectedHash: String) { + val recordDownload = { runnable: () -> Unit -> + Telemetry.languageserver.setup.use { telemetry -> + telemetry.id("q") + telemetry.languageServerSetupStage(LanguageServerSetupStage.GetServer) + telemetry.metadata("credentialStartUrl", getStartUrl(project)) + telemetry.success(true) + + try { + runnable() + } catch (t: Throwable) { + telemetry.success(false) + telemetry.recordException(t) + } + } + } + try { if (!filePath.exists()) { logger.info { "Downloading file: ${filePath.fileName}" } - saveFileFromUrl(url, filePath, ProgressManager.getInstance().progressIndicator) + recordDownload { saveFileFromUrl(url, filePath, ProgressManager.getInstance().progressIndicator) } } if (!validateFileHash(filePath, expectedHash)) { logger.warn { "Hash mismatch for ${filePath.fileName}, re-downloading" } filePath.deleteIfExists() - saveFileFromUrl(url, filePath) - if (!validateFileHash(filePath, expectedHash)) { - throw LspException("Hash mismatch after re-download for ${filePath.fileName}", LspException.ErrorCode.HASH_MISMATCH) + recordDownload { saveFileFromUrl(url, filePath) } + + Telemetry.languageserver.setup.use { + it.id("q") + it.languageServerSetupStage(LanguageServerSetupStage.Validate) + it.metadata("credentialStartUrl", getStartUrl(project)) + it.success(true) + + if (!validateFileHash(filePath, expectedHash)) { + it.success(false) + + val exception = LspException("Hash mismatch after re-download for ${filePath.fileName}", LspException.ErrorCode.HASH_MISMATCH) + it.recordException(exception) + throw exception + } } } } catch (e: Exception) { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt index b0a4d8e7e9a..d7970612854 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt @@ -19,6 +19,10 @@ import software.aws.toolkits.core.utils.info import software.aws.toolkits.core.utils.warn import software.aws.toolkits.jetbrains.AwsPlugin import software.aws.toolkits.jetbrains.AwsToolkit +import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl +import software.aws.toolkits.telemetry.LanguageServerSetupStage +import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Telemetry import java.nio.file.Path @Service @@ -57,42 +61,82 @@ class ArtifactManager @NonInjectable internal constructor(private val manifestFe return mutex.withLock { coroutineScope { async { - try { - val manifest = manifestFetcher.fetch() ?: throw LspException( - "Language Support is not available, as manifest is missing.", - LspException.ErrorCode.MANIFEST_FETCH_FAILED - ) - val lspVersions = getLSPVersionsFromManifestWithSpecifiedRange(manifest) - - artifactHelper.removeDelistedVersions(lspVersions.deListedVersions) - - if (lspVersions.inRangeVersions.isEmpty()) { - // No versions are found which are in the given range. Fallback to local lsp artifacts. - val localLspArtifacts = artifactHelper.getAllLocalLspArtifactsWithinManifestRange(DEFAULT_VERSION_RANGE) - if (localLspArtifacts.isNotEmpty()) { - return@async localLspArtifacts.first().first + Telemetry.languageserver.setup.use { all -> + all.id("q") + all.languageServerSetupStage(LanguageServerSetupStage.All) + all.metadata("credentialStartUrl", getStartUrl(project)) + all.result(MetricResult.Succeeded) + + try { + val lspVersions = Telemetry.languageserver.setup.use { telemetry -> + telemetry.id("q") + telemetry.languageServerSetupStage(LanguageServerSetupStage.GetManifest) + telemetry.metadata("credentialStartUrl", getStartUrl(project)) + + val exception = LspException( + "Language Support is not available, as manifest is missing.", + LspException.ErrorCode.MANIFEST_FETCH_FAILED + ) + telemetry.success(true) + val manifest = manifestFetcher.fetch() ?: run { + telemetry.recordException(exception) + telemetry.success(false) + throw exception + } + + getLSPVersionsFromManifestWithSpecifiedRange(manifest) } - throw LspException("Language server versions not found in manifest.", LspException.ErrorCode.NO_COMPATIBLE_LSP_VERSION) - } - val targetVersion = lspVersions.inRangeVersions.first() + artifactHelper.removeDelistedVersions(lspVersions.deListedVersions) + + if (lspVersions.inRangeVersions.isEmpty()) { + // No versions are found which are in the given range. Fallback to local lsp artifacts. + val localLspArtifacts = artifactHelper.getAllLocalLspArtifactsWithinManifestRange(DEFAULT_VERSION_RANGE) + if (localLspArtifacts.isNotEmpty()) { + return@async localLspArtifacts.first().first + } + throw LspException("Language server versions not found in manifest.", LspException.ErrorCode.NO_COMPATIBLE_LSP_VERSION) + } - // If there is an LSP Manifest with the same version - val target = getTargetFromLspManifest(targetVersion) - // Get Local LSP files and check if we can re-use existing LSP Artifacts - val artifactPath: Path = if (artifactHelper.getExistingLspArtifacts(targetVersion, target)) { - artifactHelper.getAllLocalLspArtifactsWithinManifestRange(DEFAULT_VERSION_RANGE).first().first - } else { - artifactHelper.tryDownloadLspArtifacts(project, targetVersion, target) - ?: throw LspException("Failed to download LSP artifacts", LspException.ErrorCode.DOWNLOAD_FAILED) + val targetVersion = lspVersions.inRangeVersions.first() + + // If there is an LSP Manifest with the same version + val target = getTargetFromLspManifest(targetVersion) + // Get Local LSP files and check if we can re-use existing LSP Artifacts + val artifactPath: Path = if (artifactHelper.getExistingLspArtifacts(targetVersion, target)) { + artifactHelper.getAllLocalLspArtifactsWithinManifestRange(DEFAULT_VERSION_RANGE).first().first + } else { + artifactHelper.tryDownloadLspArtifacts(project, targetVersion, target) + ?: throw LspException("Failed to download LSP artifacts", LspException.ErrorCode.DOWNLOAD_FAILED) + } + + artifactHelper.deleteOlderLspArtifacts(DEFAULT_VERSION_RANGE) + + Telemetry.languageserver.setup.use { + it.id("q") + it.languageServerSetupStage(LanguageServerSetupStage.Launch) + it.metadata("credentialStartUrl", getStartUrl(project)) + it.setAttribute("isBundledArtifact", false) + it.success(true) + } + return@async artifactPath + } catch (e: Exception) { + logger.warn(e) { "Failed to resolve assets from Flare CDN" } + val path = AwsToolkit.PLUGINS_INFO[AwsPlugin.Q]?.path?.resolve("flare") ?: error("not even bundled") + logger.info { "Falling back to bundled assets at $path" } + + all.recordException(e) + all.result(MetricResult.Failed) + + Telemetry.languageserver.setup.use { + it.id("q") + it.languageServerSetupStage(LanguageServerSetupStage.Launch) + it.metadata("credentialStartUrl", getStartUrl(project)) + it.setAttribute("isBundledArtifact", true) + it.success(false) + } + return@async path } - artifactHelper.deleteOlderLspArtifacts(DEFAULT_VERSION_RANGE) - return@async artifactPath - } catch (e: Exception) { - logger.warn(e) { "Failed to resolve assets from Flare CDN" } - val path = AwsToolkit.PLUGINS_INFO[AwsPlugin.Q]?.path?.resolve("flare") ?: error("not even bundled") - logger.info { "Falling back to bundled assets at $path" } - return@async path } } }.also { diff --git a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelperTest.kt b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelperTest.kt index 367e2a31a97..72f3961fb58 100644 --- a/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelperTest.kt +++ b/plugins/amazonq/shared/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelperTest.kt @@ -199,7 +199,7 @@ class ArtifactHelperTest { val version = Version(serverVersion = "1.0.0") val spyArtifactHelper = spyk(artifactHelper) - every { spyArtifactHelper.downloadLspArtifacts(any(), any()) } returns false + every { spyArtifactHelper.downloadLspArtifacts(mockProject, any(), any()) } returns false assertThat(runBlocking { artifactHelper.tryDownloadLspArtifacts(mockProject, version, VersionTarget(contents = contents)) }).isEqualTo(null) } @@ -210,7 +210,7 @@ class ArtifactHelperTest { val target = VersionTarget(contents = contents) val spyArtifactHelper = spyk(artifactHelper) - every { spyArtifactHelper.downloadLspArtifacts(any(), any()) } returns true + every { spyArtifactHelper.downloadLspArtifacts(mockProject, any(), any()) } returns true mockkStatic("software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.LspUtilsKt") every { moveFilesFromSourceToDestination(any(), any()) } just Runs every { extractZipFile(any(), any()) } just Runs diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBase.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBase.kt index 623663d85db..22cf1231e9b 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBase.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/otel/OtelBase.kt @@ -201,6 +201,9 @@ abstract class AbstractBaseSpan>(internal override fun recordException(exception: Throwable): SpanType { delegate.recordException(exception) + + setAttribute("reason", exception::class.java.canonicalName) + setAttribute("reasonDesc", exception.message) return this as SpanType } From a6d80b4af89b7d3a419628223f8ac3c39a1dfc6b Mon Sep 17 00:00:00 2001 From: Sam Stewart Date: Thu, 5 Jun 2025 02:54:23 -0700 Subject: [PATCH 08/10] fix(amazonq): make q chat unavalable in IDE version 2024.2.1 (#5788) * make chat unaccessible on 242.2.1 * detekt * feedback * detekt * alias * simplify * fix product code * Update plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt Co-authored-by: Richard Li <742829+rli@users.noreply.github.com> * update message * Update plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties * revert --------- Co-authored-by: manodnyab <66754471+manodnyab@users.noreply.github.com> Co-authored-by: Richard Li <742829+rli@users.noreply.github.com> --- .../services/amazonq/toolwindow/AmazonQPanel.kt | 5 +++++ .../aws/toolkits/jetbrains/services/amazonq/QUtils.kt | 11 +++++++++++ .../aws/toolkits/resources/MessagesBundle.properties | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt index 2ff79e68005..53323b638b2 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt @@ -25,6 +25,7 @@ import software.aws.toolkits.jetbrains.isDeveloperMode import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext 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.isQSupportedInThisVersion import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener @@ -42,6 +43,7 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestA import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable +import software.aws.toolkits.resources.message import java.util.concurrent.CompletableFuture import javax.swing.JButton @@ -102,6 +104,9 @@ class AmazonQPanel(val project: Project, private val scope: CoroutineScope) : Di webviewContainer.add(JBTextArea("JCEF not supported")) } browser.complete(null) + } else if (!isQSupportedInThisVersion()) { + webviewContainer.add(JBTextArea("${message("q.unavailable")}\n ${message("q.unavailable.node")}")) + browser.complete(null) } else { val loadingPanel = JBLoadingPanel(null, this) val wrapper = Wrapper() diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt index f6dea5c681e..55b6c46d7ef 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/QUtils.kt @@ -3,7 +3,9 @@ package software.aws.toolkits.jetbrains.services.amazonq +import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.project.Project +import com.intellij.openapi.util.BuildNumber import com.intellij.openapi.util.SystemInfo import software.amazon.awssdk.services.codewhispererruntime.model.IdeCategory import software.amazon.awssdk.services.codewhispererruntime.model.OperatingSystem @@ -52,3 +54,12 @@ fun codeWhispererUserContext(): UserContext = ClientMetadata.getDefault().let { .ideVersion(it.awsVersion) .build() } + +fun isQSupportedInThisVersion(): Boolean { + val currentBuild = ApplicationInfo.getInstance().build.withoutProductCode() + + return !( + currentBuild.baselineVersion == 242 && + BuildNumber.fromString("242.22855.74")?.let { currentBuild < it } == true + ) +} diff --git a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties index 0871d6258f5..9f769d120ab 100644 --- a/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties +++ b/plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties @@ -1656,7 +1656,7 @@ q.session_configuration=Extend your IDE sessions q.session_configuration.description=Your maximum session length for Amazon Q can be extended to 90 days by your administrator. For more information, refer to How to extend the session duration for Amazon Q in the IDE in the IAM Identity Center User Guide. q.sign.in=Get Started q.ui.prompt.transform=/transform -q.unavailable=\ Not supported in v2023.2.0 +q.unavailable=\ Amazon Q Chat is not supported in IDE versions <= v2024.2.1 q.unavailable.node=Please update to the latest IDE version q.window.title=Amazon Q Chat rds.aurora=Aurora From ea7c07133ee1d2ec95a1965726af6ea28d22b4e3 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Thu, 5 Jun 2025 15:08:31 +0000 Subject: [PATCH 09/10] Updating version to 3.74 --- .changes/3.74.json | 11 +++++++++++ .../bugfix-061149bd-c6ef-4c86-9f12-98e38fe3b576.json | 4 ---- .../feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json | 4 ---- CHANGELOG.md | 4 ++++ gradle.properties | 2 +- 5 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .changes/3.74.json delete mode 100644 .changes/next-release/bugfix-061149bd-c6ef-4c86-9f12-98e38fe3b576.json delete mode 100644 .changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json diff --git a/.changes/3.74.json b/.changes/3.74.json new file mode 100644 index 00000000000..ceea3211ddd --- /dev/null +++ b/.changes/3.74.json @@ -0,0 +1,11 @@ +{ + "date" : "2025-06-05", + "version" : "3.74", + "entries" : [ { + "type" : "feature", + "description" : "Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf" + }, { + "type" : "bugfix", + "description" : "Support full Unicode range in inline chat panel on Windows" + } ] +} \ No newline at end of file diff --git a/.changes/next-release/bugfix-061149bd-c6ef-4c86-9f12-98e38fe3b576.json b/.changes/next-release/bugfix-061149bd-c6ef-4c86-9f12-98e38fe3b576.json deleted file mode 100644 index 3a97907cb1d..00000000000 --- a/.changes/next-release/bugfix-061149bd-c6ef-4c86-9f12-98e38fe3b576.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type" : "bugfix", - "description" : "Support full Unicode range in inline chat panel on Windows" -} \ No newline at end of file diff --git a/.changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json b/.changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json deleted file mode 100644 index e1fcfbfe7da..00000000000 --- a/.changes/next-release/feature-f8a047df-70ed-4c42-b9f7-f3b97e9c9a6e.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type" : "feature", - "description" : "Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf" -} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e0116b680f..85632cf8a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# _3.74_ (2025-06-05) +- **(Feature)** Agentic coding experience: Amazon Q can now write code and run shell commands on your behalf +- **(Bug Fix)** Support full Unicode range in inline chat panel on Windows + # _3.73_ (2025-05-29) - **(Bug Fix)** /transform: handle InvalidGrantException properly when polling job status diff --git a/gradle.properties b/gradle.properties index 65bf15a2f2f..d5491e7d80f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # Toolkit Version -toolkitVersion=3.74-SNAPSHOT +toolkitVersion=3.74 # Publish Settings publishToken= From d6d032a693de616c6748bd8dcd88ca7534beb138 Mon Sep 17 00:00:00 2001 From: aws-toolkit-automation <> Date: Thu, 5 Jun 2025 16:44:45 +0000 Subject: [PATCH 10/10] Updating SNAPSHOT version to 3.75-SNAPSHOT --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d5491e7d80f..6aabb46f9e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # Toolkit Version -toolkitVersion=3.74 +toolkitVersion=3.75-SNAPSHOT # Publish Settings publishToken=