@@ -10,9 +10,13 @@ import com.google.gson.Gson
1010import com.intellij.ide.BrowserUtil
1111import com.intellij.ide.util.RunOnceUtil
1212import com.intellij.openapi.application.runInEdt
13+ import com.intellij.openapi.application.runReadAction
14+ import com.intellij.openapi.fileEditor.FileDocumentManager
1315import com.intellij.openapi.fileEditor.FileEditorManager
1416import com.intellij.openapi.options.ShowSettingsUtil
1517import com.intellij.openapi.project.Project
18+ import com.intellij.openapi.vfs.LocalFileSystem
19+ import com.intellij.openapi.vfs.VirtualFile
1620import com.intellij.ui.jcef.JBCefJSQuery.Response
1721import kotlinx.coroutines.CancellationException
1822import kotlinx.coroutines.CompletableDeferred
@@ -21,6 +25,7 @@ import kotlinx.coroutines.coroutineScope
2125import kotlinx.coroutines.flow.Flow
2226import kotlinx.coroutines.flow.callbackFlow
2327import kotlinx.coroutines.flow.distinctUntilChanged
28+ import kotlinx.coroutines.flow.first
2429import kotlinx.coroutines.flow.launchIn
2530import kotlinx.coroutines.flow.merge
2631import kotlinx.coroutines.flow.onEach
@@ -42,7 +47,6 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcMethod
4247import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcNotification
4348import software.aws.toolkits.jetbrains.services.amazonq.lsp.JsonRpcRequest
4449import software.aws.toolkits.jetbrains.services.amazonq.lsp.encryption.JwtEncryptionManager
45- import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AwsServerCapabilitiesProvider
4650import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.ChatCommunicationManager
4751import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.FlareUiMessage
4852import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.AUTH_FOLLOW_UP_CLICKED
@@ -71,6 +75,8 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_
7175import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS
7276import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE
7377import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE
78+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CODE_REVIEW_FINDINGS_SUFFIX
79+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.DISPLAY_FINDINGS_SUFFIX
7480import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams
7581import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams
7682import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
@@ -107,10 +113,17 @@ import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestA
107113import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
108114import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
109115import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
116+ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue
117+ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager
118+ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Description
119+ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation
120+ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix
110121import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable
122+ import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
111123import software.aws.toolkits.jetbrains.settings.MeetQSettings
112124import software.aws.toolkits.telemetry.MetricResult
113125import software.aws.toolkits.telemetry.Telemetry
126+ import java.nio.file.Path
114127import java.util.concurrent.CompletableFuture
115128import java.util.concurrent.CompletionException
116129import java.util.function.Function
@@ -568,6 +581,32 @@ class BrowserConnector(
568581 }
569582 }
570583
584+ data class FlareCodeScanIssue (
585+ val startLine : Int ,
586+ val endLine : Int ,
587+ val comment : String? ,
588+ val title : String ,
589+ val description : Description ,
590+ val detectorId : String ,
591+ val detectorName : String ,
592+ val findingId : String ,
593+ val ruleId : String? ,
594+ val relatedVulnerabilities : List <String >,
595+ val severity : String ,
596+ val recommendation : Recommendation ,
597+ val suggestedFixes : List <SuggestedFix >,
598+ val scanJobId : String ,
599+ val language : String ,
600+ val autoDetected : Boolean ,
601+ val filePath : String ,
602+ val findingContext : String ,
603+ )
604+
605+ data class AggregatedCodeScanIssue (
606+ val filePath : String ,
607+ val issues : List <FlareCodeScanIssue >,
608+ )
609+
571610 private fun showResult (
572611 result : CompletableFuture <String >,
573612 partialResultToken : String ,
@@ -581,10 +620,14 @@ class BrowserConnector(
581620 throw error
582621 }
583622 chatCommunicationManager.removePartialChatMessage(partialResultToken)
623+ val decryptedMessage = Gson ().fromJson(value?.let { encryptionManager?.decrypt(it) }.orEmpty(), Map ::class .java)
624+ as Map <String , * >
625+ parseFindingsMessages(decryptedMessage)
626+
584627 val messageToChat = ChatCommunicationManager .convertToJsonToSendToChat(
585628 SEND_CHAT_COMMAND_PROMPT ,
586629 tabId,
587- value?. let { encryptionManager?.decrypt(it) }.orEmpty( ),
630+ Gson ().toJson(decryptedMessage ),
588631 isPartialResult = false
589632 )
590633 browser.postChat(messageToChat)
@@ -598,15 +641,98 @@ class BrowserConnector(
598641 }
599642 }
600643
601- private fun updateQuickActionsInBrowser (browser : Browser ) {
644+ fun parseFindingsMessages (messagesMap : Map <String , * >) {
645+ try {
646+ val additionalMessages = messagesMap[" additionalMessages" ] as ? MutableList <Map <String , Any >>
647+ val findingsMessages = additionalMessages?.filter { message ->
648+ if (message.contains(" messageId" )) {
649+ (message[" messageId" ] as String ).endsWith(CODE_REVIEW_FINDINGS_SUFFIX ) ||
650+ (message[" messageId" ] as String ).endsWith(DISPLAY_FINDINGS_SUFFIX )
651+ } else {
652+ false
653+ }
654+ }
655+ val scannedFiles = mutableListOf<VirtualFile >()
656+ if (findingsMessages != null ) {
657+ for (findingsMessage in findingsMessages) {
658+ additionalMessages.remove(findingsMessage)
659+ val gson = Gson ()
660+ val jsonFindings = gson.fromJson(findingsMessage[" body" ] as String , List ::class .java)
661+ val mappedFindings = mutableListOf<CodeWhispererCodeScanIssue >()
662+ for (aggregatedIssueUnformatted in jsonFindings) {
663+ val aggregatedIssue = gson.fromJson(gson.toJson(aggregatedIssueUnformatted), AggregatedCodeScanIssue ::class .java)
664+ val file = LocalFileSystem .getInstance().findFileByIoFile(
665+ Path .of(aggregatedIssue.filePath).toFile()
666+ )
667+ if (file?.isDirectory == false ) {
668+ scannedFiles.add(file)
669+ runReadAction {
670+ FileDocumentManager .getInstance().getDocument(file)
671+ }?.let { document ->
672+ for (issue in aggregatedIssue.issues) {
673+ val endLineInDocument = minOf(maxOf(0 , issue.endLine - 1 ), document.lineCount - 1 )
674+ val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1
675+ val isIssueIgnored = CodeWhispererCodeScanManager .getInstance(project)
676+ .isIgnoredIssue(issue.title, document, file, issue.startLine - 1 )
677+ if (isIssueIgnored) {
678+ continue
679+ }
680+ mappedFindings.add(
681+ CodeWhispererCodeScanIssue (
682+ startLine = issue.startLine,
683+ startCol = 1 ,
684+ endLine = issue.endLine,
685+ endCol = endCol,
686+ file = file,
687+ project = project,
688+ title = issue.title,
689+ description = issue.description,
690+ detectorId = issue.detectorId,
691+ detectorName = issue.detectorName,
692+ findingId = issue.findingId,
693+ ruleId = issue.ruleId,
694+ relatedVulnerabilities = issue.relatedVulnerabilities,
695+ severity = issue.severity,
696+ recommendation = issue.recommendation,
697+ suggestedFixes = issue.suggestedFixes,
698+ codeSnippet = emptyList(),
699+ isVisible = true ,
700+ autoDetected = issue.autoDetected,
701+ scanJobId = issue.scanJobId,
702+ ),
703+ )
704+ }
705+ }
706+ }
707+ }
708+
709+ CodeWhispererCodeScanManager .getInstance(project)
710+ .addOnDemandIssues(
711+ mappedFindings,
712+ scannedFiles,
713+ CodeWhispererConstants .CodeAnalysisScope .AGENTIC
714+ )
715+ CodeWhispererCodeScanManager .getInstance(project).showCodeScanUI()
716+ }
717+ }
718+ } catch (e: Exception ) {
719+ LOG .error { " Failed to parse findings message $e " }
720+ }
721+ }
722+
723+ private suspend fun updateQuickActionsInBrowser (browser : Browser ) {
602724 val isFeatureDevAvailable = isFeatureDevAvailable(project)
603725 val isCodeTransformAvailable = isCodeTransformAvailable(project)
604726 val isDocAvailable = isDocAvailable(project)
605727 val isCodeScanAvailable = isCodeScanAvailable(project)
606728 val isCodeTestAvailable = isCodeTestAvailable(project)
607729
730+ val serverCapabilities = AmazonQLspService .getInstance(project).instanceFlow.first().initializeResult.await().awsServerCapabilities
731+
732+ // language=JavaScript
608733 val script = """
609734 try {
735+ // hack to create the list of actions across all tab types
610736 const tempConnector = connectorAdapter.initiateAdapter(
611737 false,
612738 true, // the two values are not used here, needed for constructor
@@ -619,7 +745,7 @@ class BrowserConnector(
619745 );
620746
621747 const commands = tempConnector.initialQuickActions?.slice(0, 2) || [];
622- const options = ${Gson ().toJson(AwsServerCapabilitiesProvider .getInstance(project).getChatOptions() )} ;
748+ const options = ${Gson ().toJson(serverCapabilities.chatOptions )} ;
623749 options.quickActions.quickActionsCommandGroups = [
624750 ...commands,
625751 ...options.quickActions.quickActionsCommandGroups
0 commit comments