Skip to content

Commit 0cd4bca

Browse files
authored
Merge pull request #5307 from aws/feature/dev-execution
Amazon Q: Allow customers to configure the ability to run code and tests with /dev
2 parents 445dd9e + 5adb242 commit 0cd4bca

File tree

21 files changed

+407
-110
lines changed

21 files changed

+407
-110
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "feature",
3+
"description" : "Add setting to allow Q /dev to run code and test commands"
4+
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/session/PrepareDocGenerationState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class PrepareDocGenerationState(
3939
var zipFileLength: Long? = null
4040
val nextState: SessionState
4141
try {
42-
val repoZipResult = config.repoContext.getProjectZip()
42+
val repoZipResult = config.repoContext.getProjectZip(false)
4343
val zipFileChecksum = repoZipResult.checksum
4444
zipFileLength = repoZipResult.contentLength
4545
val fileToUpload = repoZipResult.payload

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/FeatureDevConstants.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const val FEATURE_EVALUATION_PRODUCT_NAME = "FeatureDev"
77

88
const val FEATURE_NAME = "Amazon Q Developer Agent for software development"
99

10+
@Suppress("MaxLineLength")
11+
const val GENERATE_DEV_FILE_PROMPT = "generate a devfile in my repository. Note that you should only use devfile version 2.0.0 and the only supported commands are install, build and test (are all optional). so you may have to bundle some commands together using '&&'. also you can use \"public.ecr.aws/aws-mde/universal-image:latest\" as universal image if you aren’t sure which image to use. here is an example for a node repository (but don't assume it's always a node project. look at the existing repository structure before generating the devfile): schemaVersion: 2.0.0 components: - name: dev container: image: public.ecr.aws/aws-mde/universal-image:latest commands: - id: install exec: component: dev commandLine: \"npm install\" - id: build exec: component: dev commandLine: \"npm run build\" - id: test exec: component: dev commandLine: \"npm run test\""
12+
1013
// Max number of times a user can attempt to retry a code generation request if it fails
1114
const val CODE_GENERATION_RETRY_LIMIT = 3
1215

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/controller/FeatureDevController.kt

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ import software.aws.toolkits.jetbrains.core.coroutines.EDT
3232
import software.aws.toolkits.jetbrains.services.amazonq.RepoSizeError
3333
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
3434
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
35+
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessagePublisher
3536
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
3637
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException
3738
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.DEFAULT_RETRY_LIMIT
3839
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FEATURE_NAME
3940
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.FeatureDevException
41+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.GENERATE_DEV_FILE_PROMPT
4042
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.InboundAppMessagesHandler
4143
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ModifySourceFolderErrorReason
4244
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError
@@ -77,6 +79,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.util.content
7779
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.FeedbackComment
7880
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
7981
import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
82+
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
8083
import software.aws.toolkits.jetbrains.ui.feedback.FeatureDevFeedbackDialog
8184
import software.aws.toolkits.jetbrains.utils.notifyError
8285
import software.aws.toolkits.resources.message
@@ -135,6 +138,16 @@ class FeatureDevController(
135138
FollowUpTypes.PROVIDE_FEEDBACK_AND_REGENERATE_CODE -> provideFeedbackAndRegenerateCode(message.tabId)
136139
FollowUpTypes.NEW_TASK -> newTask(message.tabId)
137140
FollowUpTypes.CLOSE_SESSION -> closeSession(message.tabId)
141+
FollowUpTypes.ACCEPT_AUTO_BUILD -> handleDevCommandUserSetting(message.tabId, true)
142+
FollowUpTypes.DENY_AUTO_BUILD -> handleDevCommandUserSetting(message.tabId, false)
143+
FollowUpTypes.GENERATE_DEV_FILE -> {
144+
messenger.sendAnswer(
145+
tabId = message.tabId,
146+
messageType = FeatureDevMessageType.SystemPrompt,
147+
message = message("amazonqFeatureDev.follow_up.generate_dev_file")
148+
)
149+
newTask(tabId = message.tabId, prefilledPrompt = GENERATE_DEV_FILE_PROMPT)
150+
}
138151
}
139152
}
140153

@@ -440,20 +453,38 @@ class FeatureDevController(
440453
canBeVoted = true
441454
)
442455

443-
messenger.sendSystemPrompt(
444-
tabId = tabId,
445-
followUp = listOf(
446-
FollowUp(
447-
pillText = message("amazonqFeatureDev.follow_up.new_task"),
448-
type = FollowUpTypes.NEW_TASK,
449-
status = FollowUpStatusType.Info
450-
),
456+
val followUps = mutableListOf(
457+
FollowUp(
458+
pillText = message("amazonqFeatureDev.follow_up.new_task"),
459+
type = FollowUpTypes.NEW_TASK,
460+
status = FollowUpStatusType.Info
461+
),
462+
FollowUp(
463+
pillText = message("amazonqFeatureDev.follow_up.close_session"),
464+
type = FollowUpTypes.CLOSE_SESSION,
465+
status = FollowUpStatusType.Info
466+
),
467+
)
468+
469+
if (!session.context.checkForDevFile()) {
470+
followUps.add(
451471
FollowUp(
452-
pillText = message("amazonqFeatureDev.follow_up.close_session"),
453-
type = FollowUpTypes.CLOSE_SESSION,
472+
pillText = message("amazonqFeatureDev.follow_up.generate_dev_file"),
473+
type = FollowUpTypes.GENERATE_DEV_FILE,
454474
status = FollowUpStatusType.Info
455475
)
456476
)
477+
478+
messenger.sendAnswer(
479+
tabId = tabId,
480+
message = message("amazonqFeatureDev.chat_message.generate_dev_file"),
481+
messageType = FeatureDevMessageType.Answer
482+
)
483+
}
484+
485+
messenger.sendSystemPrompt(
486+
tabId = tabId,
487+
followUp = followUps
457488
)
458489

459490
messenger.sendUpdatePlaceholder(
@@ -471,9 +502,7 @@ class FeatureDevController(
471502
}
472503
}
473504

474-
private suspend fun newTask(tabId: String, isException: Boolean? = false) {
475-
this.disablePreviousFileList(tabId)
476-
505+
private suspend fun newTask(tabId: String, isException: Boolean? = false, prefilledPrompt: String? = null) {
477506
val session = getSessionInfo(tabId)
478507
val sessionLatency = System.currentTimeMillis() - session.sessionStartTime
479508

@@ -485,15 +514,30 @@ class FeatureDevController(
485514
chatSessionStorage.deleteSession(tabId)
486515

487516
newTabOpened(tabId)
488-
if (isException != null && !isException) {
489-
messenger.sendAnswer(
490-
tabId = tabId,
491-
messageType = FeatureDevMessageType.Answer,
492-
message = message("amazonqFeatureDev.chat_message.ask_for_new_task")
493-
)
517+
518+
if (prefilledPrompt != null && isException != null && !isException) {
519+
handleChat(tabId = tabId, message = prefilledPrompt)
520+
} else {
521+
if (isException != null && !isException) {
522+
messenger.sendAnswer(
523+
tabId = tabId,
524+
messageType = FeatureDevMessageType.Answer,
525+
message = message("amazonqFeatureDev.chat_message.ask_for_new_task")
526+
)
527+
}
528+
messenger.sendUpdatePlaceholder(tabId = tabId, newPlaceholder = message("amazonqFeatureDev.placeholder.new_plan"))
529+
messenger.sendChatInputEnabledMessage(tabId = tabId, enabled = true)
494530
}
495-
messenger.sendUpdatePlaceholder(tabId = tabId, newPlaceholder = message("amazonqFeatureDev.placeholder.new_plan"))
496-
messenger.sendChatInputEnabledMessage(tabId = tabId, enabled = true)
531+
}
532+
533+
private suspend fun handleDevCommandUserSetting(tabId: String, value: Boolean) {
534+
CodeWhispererSettings.getInstance().toggleAutoBuildFeature(context.project.basePath, value)
535+
messenger.sendAnswer(
536+
tabId = tabId,
537+
message = message("amazonqFeatureDev.chat_message.setting_updated"),
538+
messageType = FeatureDevMessageType.Answer,
539+
)
540+
this.retryRequests(tabId)
497541
}
498542

499543
private suspend fun closeSession(tabId: String) {
@@ -670,6 +714,7 @@ class FeatureDevController(
670714
try {
671715
logger.debug { "$FEATURE_NAME: Processing message: $message" }
672716
session = getSessionInfo(tabId)
717+
session.latestMessage = message
673718

674719
val credentialState = authController.getAuthNeededStates(context.project).amazonQ
675720
if (credentialState != null) {
@@ -682,8 +727,18 @@ class FeatureDevController(
682727
return
683728
}
684729

685-
session.preloader(message, messenger)
730+
val codeWhispererSettings = CodeWhispererSettings.getInstance().getAutoBuildSetting()
731+
val hasDevFile = session.context.checkForDevFile()
732+
val isPromptedForAutoBuildFeature = codeWhispererSettings.containsKey(session.context.getWorkspaceRoot())
733+
734+
if (hasDevFile && !isPromptedForAutoBuildFeature) {
735+
promptAllowQCommandsConsent(messenger, tabId)
736+
return
737+
}
738+
739+
session.preloader(messenger)
686740
broadcastQEvent(QFeatureEvent.INVOCATION)
741+
687742
when (session.sessionState.phase) {
688743
SessionStatePhase.CODEGEN -> onCodeGeneration(session, message, tabId)
689744
else -> null
@@ -696,6 +751,30 @@ class FeatureDevController(
696751
}
697752
}
698753

754+
private suspend fun promptAllowQCommandsConsent(messenger: MessagePublisher, tabID: String) {
755+
messenger.sendAnswer(
756+
tabId = tabID,
757+
message = message("amazonqFeatureDev.chat_message.devFileInRepository"),
758+
messageType = FeatureDevMessageType.Answer
759+
)
760+
messenger.sendAnswer(
761+
tabId = tabID,
762+
messageType = FeatureDevMessageType.SystemPrompt,
763+
followUp = listOf(
764+
FollowUp(
765+
pillText = message("amazonqFeatureDev.follow_up.accept_for_project"),
766+
type = FollowUpTypes.ACCEPT_AUTO_BUILD,
767+
status = FollowUpStatusType.Success
768+
),
769+
FollowUp(
770+
pillText = message("amazonqFeatureDev.follow_up.decline_for_project"),
771+
type = FollowUpTypes.DENY_AUTO_BUILD,
772+
status = FollowUpStatusType.Error
773+
)
774+
)
775+
)
776+
}
777+
699778
private suspend fun retryRequests(tabId: String) {
700779
var session: Session? = null
701780
try {

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/messages/FeatureDevMessage.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ enum class FollowUpTypes(
250250
PROVIDE_FEEDBACK_AND_REGENERATE_CODE("ProvideFeedbackAndRegenerateCode"),
251251
NEW_TASK("NewTask"),
252252
CLOSE_SESSION("CloseSession"),
253+
ACCEPT_AUTO_BUILD("AcceptAutoBuild"),
254+
DENY_AUTO_BUILD("DenyAutoBuild"),
255+
GENERATE_DEV_FILE("GenerateDevFile"),
253256
}
254257

255258
// Util classes

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/PrepareCodeGenerationState.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.Cancellat
1313
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.deleteUploadArtifact
1414
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.util.uploadArtifactToS3
1515
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
16+
import software.aws.toolkits.jetbrains.settings.CodeWhispererSettings
1617
import software.aws.toolkits.resources.message
1718
import software.aws.toolkits.telemetry.AmazonqTelemetry
1819
import software.aws.toolkits.telemetry.AmazonqUploadIntent
@@ -48,7 +49,8 @@ class PrepareCodeGenerationState(
4849
messenger.sendAnswerPart(tabId = this.tabID, message = message("amazonqFeatureDev.chat_message.uploading_code"))
4950
messenger.sendUpdatePlaceholder(tabId = this.tabID, newPlaceholder = message("amazonqFeatureDev.chat_message.uploading_code"))
5051

51-
val repoZipResult = config.repoContext.getProjectZip()
52+
val isAutoBuildFeatureEnabled = CodeWhispererSettings.getInstance().isAutoBuildFeatureEnabled(this.config.repoContext.getWorkspaceRoot())
53+
val repoZipResult = config.repoContext.getProjectZip(isAutoBuildFeatureEnabled = isAutoBuildFeatureEnabled)
5254
val zipFileChecksum = repoZipResult.checksum
5355
zipFileLength = repoZipResult.contentLength
5456
val fileToUpload = repoZipResult.payload
@@ -96,7 +98,7 @@ class PrepareCodeGenerationState(
9698
credentialStartUrl = getStartUrl(config.featureDevService.project)
9799
)
98100
}
99-
// It is essential to interact with the next state outside of try-catch block for the telemetry to capture events for the states separately
101+
// It is essential to interact with the next state outside try-catch block for the telemetry to capture events for the states separately
100102
return nextState.interact(action)
101103
}
102104
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqFeatureDev/session/Session.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ class Session(val tabID: String, val project: Project) {
6767
/**
6868
* Preload any events that have to run before a chat message can be sent
6969
*/
70-
suspend fun preloader(msg: String, messenger: MessagePublisher) {
70+
suspend fun preloader(messenger: MessagePublisher) {
7171
if (!preloaderFinished) {
72-
setupConversation(msg, messenger)
72+
setupConversation(messenger)
7373
preloaderFinished = true
7474
messenger.sendAsyncEventProgress(tabId = this.tabID, inProgress = true)
7575
featureDevService.sendFeatureDevEvent(this.conversationId)
@@ -79,10 +79,7 @@ class Session(val tabID: String, val project: Project) {
7979
/**
8080
* Starts a conversation with the backend and uploads the repo for the LLMs to be able to use it.
8181
*/
82-
private fun setupConversation(msg: String, messenger: MessagePublisher) {
83-
// Store the initial message when setting up the conversation so that if it fails we can retry with this message
84-
_latestMessage = msg
85-
82+
private fun setupConversation(messenger: MessagePublisher) {
8683
_conversationId = featureDevService.createConversation()
8784
logger<Session>().info(conversationIDLog(this.conversationId))
8885

@@ -281,8 +278,11 @@ class Session(val tabID: String, val project: Project) {
281278
}
282279
}
283280

284-
val latestMessage: String
281+
var latestMessage: String
285282
get() = this._latestMessage
283+
set(value) {
284+
this._latestMessage = value
285+
}
286286

287287
val retries: Int
288288
get() = codegenRetries

0 commit comments

Comments
 (0)